ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/wm/toolbar.b/
implement Toolbar;
include "sys.m";
sys: Sys;
include "draw.m";
draw: Draw;
Screen, Display, Image, Rect, Point, Wmcontext, Pointer: import draw;
include "tk.m";
tk: Tk;
include "tkclient.m";
tkclient: Tkclient;
include "sh.m";
shell: Sh;
Listnode, Context: import shell;
include "string.m";
str: String;
include "arg.m";
myselfbuiltin: Shellbuiltin;
Toolbar: module
{
init: fn(ctxt: ref Draw->Context, argv: list of string);
initbuiltin: fn(c: ref Context, sh: Sh): string;
runbuiltin: fn(c: ref Context, sh: Sh,
cmd: list of ref Listnode, last: int): string;
runsbuiltin: fn(c: ref Context, sh: Sh,
cmd: list of ref Listnode): list of ref Listnode;
whatis: fn(c: ref Sh->Context, sh: Sh, name: string, wtype: int): string;
getself: fn(): Shellbuiltin;
};
MAXCONSOLELINES: con 1024;
# execute this if no menu items have been created
# by the init script.
defaultscript :=
"{menu shell " +
"{{autoload=std; load $autoload; pctl newpgrp; wm/sh}&}}";
tbtop: ref Tk->Toplevel;
screenr: Rect;
badmodule(p: string)
{
sys->fprint(stderr(), "toolbar: cannot load %s: %r\n", p);
raise "fail:bad module";
}
init(ctxt: ref Draw->Context, argv: list of string)
{
sys = load Sys Sys->PATH;
draw = load Draw Draw->PATH;
if(draw == nil)
badmodule(Draw->PATH);
tk = load Tk Tk->PATH;
if(tk == nil)
badmodule(Tk->PATH);
str = load String String->PATH;
if(str == nil)
badmodule(String->PATH);
tkclient = load Tkclient Tkclient->PATH;
if(tkclient == nil)
badmodule(Tkclient->PATH);
tkclient->init();
shell = load Sh Sh->PATH;
if (shell == nil)
badmodule(Sh->PATH);
arg := load Arg Arg->PATH;
if (arg == nil)
badmodule(Arg->PATH);
myselfbuiltin = load Shellbuiltin "$self";
if (myselfbuiltin == nil)
badmodule("$self(Shellbuiltin)");
sys->pctl(Sys->NEWPGRP|Sys->FORKNS, nil);
sys->bind("#p", "/prog", sys->MREPL);
sys->bind("#s", "/chan", sys->MBEFORE);
arg->init(argv);
arg->setusage("toolbar [-s] [-p]");
startmenu := 1;
# ownsnarf := (sys->open("/chan/snarf", Sys->ORDWR) == nil);
ownsnarf := sys->stat("/chan/snarf").t0 < 0;
while((c := arg->opt()) != 0){
case c {
's' =>
startmenu = 0;
'p' =>
ownsnarf = 1;
* =>
arg->usage();
}
}
argv = arg->argv();
arg = nil;
if (ctxt == nil){
sys->fprint(sys->fildes(2), "toolbar: must run under a window manager\n");
raise "fail:no wm";
}
exec := chan of string;
task := chan of string;
tbtop = toolbar(ctxt, startmenu, exec, task);
tkclient->startinput(tbtop, "ptr" :: "control" :: nil);
layout(tbtop);
shctxt := Context.new(ctxt);
shctxt.addmodule("wm", myselfbuiltin);
snarfIO: ref Sys->FileIO;
if(ownsnarf){
snarfIO = sys->file2chan("/chan", "snarf");
if(snarfIO == nil)
fatal(sys->sprint("cannot make /chan/snarf: %r"));
}else
snarfIO = ref Sys->FileIO(chan of (int, int, int, Sys->Rread), chan of (int, array of byte, int, Sys->Rwrite));
sync := chan of string;
spawn consoleproc(ctxt, sync);
if ((err := <-sync) != nil)
fatal(err);
setupfinished := chan of int;
donesetup := 0;
spawn setup(shctxt, setupfinished);
snarf: array of byte;
# write("/prog/"+string sys->pctl(0, nil)+"/ctl", "restricted"); # for testing
for(;;) alt{
k := <-tbtop.ctxt.kbd =>
tk->keyboard(tbtop, k);
m := <-tbtop.ctxt.ptr =>
tk->pointer(tbtop, *m);
s := <-tbtop.ctxt.ctl or
s = <-tbtop.wreq =>
wmctl(tbtop, s);
s := <-exec =>
# guard against parallel access to the shctxt environment
if (donesetup){
{
shctxt.run(ref Listnode(nil, s) :: nil, 0);
} exception {
"fail:*" => ;
}
}
detask := <-task =>
deiconify(detask);
(off, data, nil, wc) := <-snarfIO.write =>
if(wc == nil)
break;
if (off == 0) # write at zero truncates
snarf = data;
else {
if (off + len data > len snarf) {
nsnarf := array[off + len data] of byte;
nsnarf[0:] = snarf;
snarf = nsnarf;
}
snarf[off:] = data;
}
wc <-= (len data, "");
(off, nbytes, nil, rc) := <-snarfIO.read =>
if(rc == nil)
break;
if (off >= len snarf) {
rc <-= (nil, ""); # XXX alt
break;
}
e := off + nbytes;
if (e > len snarf)
e = len snarf;
rc <-= (snarf[off:e], ""); # XXX alt
donesetup = <-setupfinished =>
;
}
}
wmctl(top: ref Tk->Toplevel, c: string)
{
args := str->unquoted(c);
if(args == nil)
return;
n := len args;
case hd args{
"request" =>
# request clientid args...
if(n < 3)
return;
args = tl args;
clientid := hd args;
args = tl args;
err := handlerequest(clientid, args);
if(err != nil)
sys->fprint(sys->fildes(2), "toolbar: bad wmctl request %#q: %s\n", c, err);
"newclient" =>
# newclient id
;
"delclient" =>
# delclient id
deiconify(hd tl args);
"rect" =>
tkclient->wmctl(top, c);
layout(top);
* =>
tkclient->wmctl(top, c);
}
}
handlerequest(clientid: string, args: list of string): string
{
n := len args;
case hd args {
"task" =>
# task name
if(n != 2)
return "no task label given";
iconify(clientid, hd tl args);
"untask" or
"unhide" =>
deiconify(clientid);
* =>
return "unknown request";
}
return nil;
}
iconify(id, label: string)
{
label = condenselabel(label);
e := tk->cmd(tbtop, "button .toolbar." +id+" -command {send task "+id+"} -takefocus 0");
cmd(tbtop, ".toolbar." +id+" configure -text '" + label);
if(e[0] != '!')
cmd(tbtop, "pack .toolbar."+id+" -side left -fill y");
cmd(tbtop, "update");
}
deiconify(id: string)
{
e := tk->cmd(tbtop, "destroy .toolbar."+id);
if(e == nil){
tkclient->wmctl(tbtop, sys->sprint("ctl %q untask", id));
tkclient->wmctl(tbtop, sys->sprint("ctl %q kbdfocus 1", id));
}
cmd(tbtop, "update");
}
layout(top: ref Tk->Toplevel)
{
r := top.screenr;
h := 32;
if(r.dy() < 480)
h = tk->rect(top, ".b", Tk->Border|Tk->Required).dy();
cmd(top, ". configure -x " + string r.min.x +
" -y " + string (r.max.y - h) +
" -width " + string r.dx() +
" -height " + string h);
cmd(top, "update");
tkclient->onscreen(tbtop, "exact");
}
toolbar(ctxt: ref Draw->Context, startmenu: int,
exec, task: chan of string): ref Tk->Toplevel
{
(tbtop, nil) = tkclient->toplevel(ctxt, nil, nil, Tkclient->Plain);
screenr = tbtop.screenr;
cmd(tbtop, "button .b -text {XXX}");
cmd(tbtop, "pack propagate . 0");
tk->namechan(tbtop, exec, "exec");
tk->namechan(tbtop, task, "task");
cmd(tbtop, "frame .toolbar");
if (startmenu) {
cmd(tbtop, "menubutton .toolbar.start -menu .m -borderwidth 0 -bitmap vitasmall.bit");
cmd(tbtop, "pack .toolbar.start -side left");
}
cmd(tbtop, "pack .toolbar -fill x");
cmd(tbtop, "menu .m");
return tbtop;
}
setup(shctxt: ref Context, finished: chan of int)
{
ctxt := shctxt.copy(0);
ctxt.run(shell->stringlist2list("run"::"/lib/wmsetup"::nil), 0);
# if no items in menu, then create some.
if (tk->cmd(tbtop, ".m type 0")[0] == '!')
ctxt.run(shell->stringlist2list(defaultscript::nil), 0);
cmd(tbtop, "update");
finished <-= 1;
}
condenselabel(label: string): string
{
if(len label > 15){
new := "";
l := 0;
while(len label > 15 && l < 3) {
new += label[0:15]+"\n";
label = label[15:];
for(v := 0; v < len label; v++)
if(label[v] != ' ')
break;
label = label[v:];
l++;
}
label = new + label;
}
return label;
}
initbuiltin(ctxt: ref Context, nil: Sh): string
{
if (tbtop == nil) {
sys = load Sys Sys->PATH;
sys->fprint(sys->fildes(2), "wm: cannot load wm as a builtin\n");
raise "fail:usage";
}
ctxt.addbuiltin("menu", myselfbuiltin);
ctxt.addbuiltin("delmenu", myselfbuiltin);
ctxt.addbuiltin("error", myselfbuiltin);
return nil;
}
whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string
{
return nil;
}
runbuiltin(c: ref Context, sh: Sh,
cmd: list of ref Listnode, nil: int): string
{
case (hd cmd).word {
"menu" => return builtin_menu(c, sh, cmd);
"delmenu" => return builtin_delmenu(c, sh, cmd);
}
return nil;
}
runsbuiltin(nil: ref Context, nil: Sh,
nil: list of ref Listnode): list of ref Listnode
{
return nil;
}
stderr(): ref Sys->FD
{
return sys->fildes(2);
}
word(ln: ref Listnode): string
{
if (ln.word != nil)
return ln.word;
if (ln.cmd != nil)
return shell->cmd2string(ln.cmd);
return nil;
}
menupath(title: string): string
{
mpath := ".m."+title;
for(j := 0; j < len mpath; j++)
if(mpath[j] == ' ')
mpath[j] = '_';
return mpath;
}
builtin_menu(nil: ref Context, nil: Sh, argv: list of ref Listnode): string
{
n := len argv;
if (n < 3 || n > 4) {
sys->fprint(stderr(), "usage: menu topmenu [ secondmenu ] command\n");
raise "fail:usage";
}
primary := (hd tl argv).word;
argv = tl tl argv;
if (n == 3) {
w := word(hd argv);
if (len w == 0)
cmd(tbtop, ".m insert 0 separator");
else
cmd(tbtop, ".m insert 0 command -label " + tk->quote(primary) +
" -command {send exec " + w + "}");
} else {
secondary := (hd argv).word;
argv = tl argv;
mpath := menupath(primary);
e := tk->cmd(tbtop, mpath+" cget -width");
if(e[0] == '!') {
cmd(tbtop, "menu "+mpath);
cmd(tbtop, ".m insert 0 cascade -label "+tk->quote(primary)+" -menu "+mpath);
}
w := word(hd argv);
if (len w == 0)
cmd(tbtop, mpath + " insert 0 separator");
else
cmd(tbtop, mpath+" insert 0 command -label "+tk->quote(secondary)+
" -command {send exec "+w+"}");
}
return nil;
}
builtin_delmenu(nil: ref Context, nil: Sh, nil: list of ref Listnode): string
{
delmenu(".m");
cmd(tbtop, "menu .m");
return nil;
}
delmenu(m: string)
{
for (i := int cmd(tbtop, m + " index end"); i >= 0; i--)
if (cmd(tbtop, m + " type " + string i) == "cascade")
delmenu(cmd(tbtop, m + " entrycget " + string i + " -menu"));
cmd(tbtop, "destroy " + m);
}
getself(): Shellbuiltin
{
return myselfbuiltin;
}
cmd(top: ref Tk->Toplevel, c: string): string
{
s := tk->cmd(top, c);
if (s != nil && s[0] == '!')
sys->fprint(stderr(), "tk error on %#q: %s\n", c, s);
return s;
}
kill(pid: int, note: string): int
{
fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE);
if(fd == nil || sys->fprint(fd, "%s", note) < 0)
return -1;
return 0;
}
fatal(s: string)
{
sys->fprint(sys->fildes(2), "wm: %s\n", s);
kill(sys->pctl(0, nil), "killgrp");
raise "fail:error";
}
bufferproc(in, out: chan of string)
{
h, t: list of string;
dummyout := chan of string;
for(;;){
outc := dummyout;
s: string;
if(h != nil || t != nil){
outc = out;
if(h == nil)
for(; t != nil; t = tl t)
h = hd t :: h;
s = hd h;
}
alt{
x := <-in =>
t = x :: t;
outc <-= s =>
h = tl h;
}
}
}
con_cfg := array[] of
{
"frame .cons",
"scrollbar .cons.scroll -command {.cons.t yview}",
"text .cons.t -width 60w -height 15w -bg white "+
"-fg black -font /fonts/misc/latin1.6x13.font "+
"-yscrollcommand {.cons.scroll set}",
"pack .cons.scroll -side left -fill y",
"pack .cons.t -fill both -expand 1",
"pack .cons -expand 1 -fill both",
"pack propagate . 0",
"update"
};
nlines := 0; # transcript length
consoleproc(ctxt: ref Draw->Context, sync: chan of string)
{
iostdout := sys->file2chan("/chan", "wmstdout");
if(iostdout == nil){
sync <-= sys->sprint("cannot make /chan/wmstdout: %r");
return;
}
iostderr := sys->file2chan("/chan", "wmstderr");
if(iostderr == nil){
sync <-= sys->sprint("cannot make /chan/wmstdout: %r");
return;
}
sync <-= nil;
(top, titlectl) := tkclient->toplevel(ctxt, "", "Log", tkclient->Appl);
for(i := 0; i < len con_cfg; i++)
cmd(top, con_cfg[i]);
r := tk->rect(top, ".", Tk->Border|Tk->Required);
cmd(top, ". configure -x " + string ((top.screenr.dx() - r.dx()) / 2 + top.screenr.min.x) +
" -y " + string (r.dy() / 3 + top.screenr.min.y));
tkclient->startinput(top, "ptr"::"kbd"::nil);
tkclient->onscreen(top, "onscreen");
tkclient->wmctl(top, "task");
for(;;) alt {
c := <-titlectl or
c = <-top.wreq or
c = <-top.ctxt.ctl =>
if(c == "exit")
c = "task";
tkclient->wmctl(top, c);
c := <-top.ctxt.kbd =>
tk->keyboard(top, c);
p := <-top.ctxt.ptr =>
tk->pointer(top, *p);
(nil, nil, nil, rc) := <-iostdout.read =>
if(rc != nil)
rc <-= (nil, "inappropriate use of file");
(nil, nil, nil, rc) := <-iostderr.read =>
if(rc != nil)
rc <-= (nil, "inappropriate use of file");
(nil, data, nil, wc) := <-iostdout.write =>
conout(top, data, wc);
(nil, data, nil, wc) := <-iostderr.write =>
conout(top, data, wc);
if(wc != nil)
tkclient->wmctl(top, "untask");
}
}
conout(top: ref Tk->Toplevel, data: array of byte, wc: Sys->Rwrite)
{
if(wc == nil)
return;
s := string data;
tk->cmd(top, ".cons.t insert end '"+ s);
alt{
wc <-= (len data, nil) =>;
* =>;
}
for(i := 0; i < len s; i++)
if(s[i] == '\n')
nlines++;
if(nlines > MAXCONSOLELINES){
cmd(top, ".cons.t delete 1.0 " + string (nlines/4) + ".0; update");
nlines -= nlines / 4;
}
tk->cmd(top, ".cons.t see end; update");
}