summaryrefslogtreecommitdiff
path: root/appl/wm/wm.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/wm/wm.b')
-rw-r--r--appl/wm/wm.b678
1 files changed, 678 insertions, 0 deletions
diff --git a/appl/wm/wm.b b/appl/wm/wm.b
new file mode 100644
index 00000000..d8232b0b
--- /dev/null
+++ b/appl/wm/wm.b
@@ -0,0 +1,678 @@
+implement Wm;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+ Screen, Display, Image, Rect, Point, Wmcontext, Pointer: import draw;
+include "wmsrv.m";
+ wmsrv: Wmsrv;
+ Window, Client: import wmsrv;
+include "tk.m";
+include "wmclient.m";
+ wmclient: Wmclient;
+include "string.m";
+ str: String;
+include "sh.m";
+include "winplace.m";
+ winplace: Winplace;
+
+Wm: module {
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Ptrstarted, Kbdstarted, Controlstarted, Controller, Fixedorigin: con 1<<iota;
+Bdwidth: con 3;
+Sminx, Sminy, Smaxx, Smaxy: con iota;
+Minx, Miny, Maxx, Maxy: con 1<<iota;
+Background: con int 16r777777FF;
+
+screen: ref Screen;
+display: ref Display;
+ptrfocus: ref Client;
+kbdfocus: ref Client;
+controller: ref Client;
+allowcontrol := 1;
+fakekbd: chan of string;
+fakekbdin: chan of string;
+buttons := 0;
+
+badmodule(p: string)
+{
+ sys->fprint(sys->fildes(2), "wm: 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);
+
+ str = load String String->PATH;
+ if(str == nil)
+ badmodule(String->PATH);
+
+ wmsrv = load Wmsrv Wmsrv->PATH;
+ if(wmsrv == nil)
+ badmodule(Wmsrv->PATH);
+
+ wmclient = load Wmclient Wmclient->PATH;
+ if(wmclient == nil)
+ badmodule(Wmclient->PATH);
+ wmclient->init();
+
+ winplace = load Winplace Winplace->PATH;
+ if(winplace == nil)
+ badmodule(Winplace->PATH);
+ winplace->init();
+
+ sys->pctl(Sys->NEWPGRP|Sys->FORKNS, nil);
+ if (ctxt == nil)
+ ctxt = wmclient->makedrawcontext();
+ display = ctxt.display;
+
+ buts := Wmclient->Appl;
+ if(ctxt.wm == nil)
+ buts = Wmclient->Plain;
+ win := wmclient->window(ctxt, "Wm", buts);
+ wmclient->win.reshape(((0, 0), (100, 100)));
+ wmclient->win.onscreen("place");
+ if(win.image == nil){
+ sys->fprint(sys->fildes(2), "wm: cannot get image to draw on\n");
+ raise "fail:no image";
+ }
+ wmclient->win.startinput("kbd" :: "ptr" :: nil);
+
+ wmctxt := win.ctxt;
+ screen = makescreen(win.image);
+
+ (clientwm, join, req) := wmsrv->init();
+ clientctxt := ref Draw->Context(ctxt.display, nil, clientwm);
+
+ wmrectIO := sys->file2chan("/chan", "wmrect");
+ if(wmrectIO == nil)
+ fatal(sys->sprint("cannot make /chan/wmrect: %r"));
+
+ sync := chan of string;
+ argv = tl argv;
+ if(argv == nil)
+ argv = "wm/toolbar" :: nil;
+ spawn command(clientctxt, argv, sync);
+ if((e := <-sync) != nil)
+ fatal("cannot run command: " + e);
+
+ fakekbd = chan of string;
+ for(;;) alt {
+ c := <-win.ctl or
+ c = <-wmctxt.ctl =>
+ # XXX could implement "pleaseexit" in order that
+ # applications can raise a warning message before
+ # they're unceremoniously dumped.
+ if(c == "exit")
+ for(z := wmsrv->top(); z != nil; z = z.znext)
+ z.ctl <-= "exit";
+
+ wmclient->win.wmctl(c);
+ if(win.image != screen.image)
+ reshaped(win);
+ c := <-wmctxt.kbd or
+ c = int <-fakekbd =>
+ if(kbdfocus != nil)
+ kbdfocus.kbd <-= c;
+ p := <-wmctxt.ptr =>
+ if(wmclient->win.pointer(*p))
+ break;
+ if(p.buttons && (ptrfocus == nil || buttons == 0)){
+ c := wmsrv->find(p.xy);
+ if(c != nil){
+ ptrfocus = c;
+ c.ctl <-= "raise";
+ setfocus(win, c);
+ }
+ }
+ if(ptrfocus != nil && (ptrfocus.flags & Ptrstarted) != 0){
+ # inside currently selected client or it had button down last time (might have come up)
+ buttons = p.buttons;
+ ptrfocus.ptr <-= p;
+ break;
+ }
+ buttons = 0;
+ (c, rc) := <-join =>
+ rc <-= nil;
+ # new client; inform it of the available screen rectangle.
+ # XXX do we need to do this now we've got wmrect?
+ c.ctl <-= "rect " + r2s(screen.image.r);
+ if(allowcontrol){
+ controller = c;
+ c.flags |= Controller;
+ allowcontrol = 0;
+ }else
+ controlevent("newclient " + string c.id);
+ c.cursor = "cursor";
+ (c, data, rc) := <-req =>
+ # if client leaving
+ if(rc == nil){
+ c.remove();
+ if(c == ptrfocus)
+ ptrfocus = nil;
+ if(c == kbdfocus)
+ kbdfocus = nil;
+ if(c == controller)
+ controller = nil;
+ controlevent("delclient " + string c.id);
+ for(z := wmsrv->top(); z != nil; z = z.znext)
+ if(z.flags & Kbdstarted)
+ break;
+ setfocus(win, z);
+ c.stop <-= 1;
+ break;
+ }
+ err := handlerequest(win, wmctxt, c, string data);
+ n := len data;
+ if(err != nil)
+ n = -1;
+ alt{
+ rc <-= (n, err) =>;
+ * =>;
+ }
+ (nil, nil, nil, wc) := <-wmrectIO.write =>
+ if(wc == nil)
+ break;
+ alt{
+ wc <-= (0, "cannot write") =>;
+ * =>;
+ }
+ (off, nil, nil, rc) := <-wmrectIO.read =>
+ if(rc == nil)
+ break;
+ d := array of byte r2s(screen.image.r);
+ if(off > len d)
+ off = len d;
+ alt{
+ rc <-= (d[off:], nil) =>;
+ * =>;
+ }
+ }
+}
+
+handlerequest(win: ref Wmclient->Window, wmctxt: ref Wmcontext, c: ref Client, req: string): string
+{
+#sys->print("%d: %s\n", c.id, req);
+ args := str->unquoted(req);
+ if(args == nil)
+ return "no request";
+ n := len args;
+ if(req[0] == '!' && n < 3)
+ return "bad arg count";
+ case hd args {
+ "key" =>
+ # XXX should we restrict this capability to certain clients only?
+ if(n != 2)
+ return "bad arg count";
+ if(fakekbdin == nil){
+ fakekbdin = chan of string;
+ spawn bufferproc(fakekbdin, fakekbd);
+ }
+ fakekbdin <-= hd tl args;
+ "ptr" =>
+ # ptr x y
+ if(n != 3)
+ return "bad arg count";
+ if(ptrfocus != c)
+ return "cannot move pointer";
+ e := wmclient->win.wmctl(req);
+ if(e == nil){
+ c.ptr <-= nil; # flush queue
+ c.ptr <-= ref Pointer(buttons, (int hd tl args, int hd tl tl args), sys->millisec());
+ }
+ "cursor" =>
+ # cursor hotx hoty dx dy data
+ if(n != 6 && n != 1)
+ return "bad arg count";
+ c.cursor = req;
+ if(ptrfocus == c || kbdfocus == c)
+ return wmclient->win.wmctl(c.cursor);
+ "start" =>
+ if(n != 2)
+ return "bad arg count";
+ case hd tl args {
+ "mouse" or
+ "ptr" =>
+ c.flags |= Ptrstarted;
+ "kbd" =>
+ c.flags |= Kbdstarted;
+ # XXX this means that any new window grabs the focus from the current
+ # application, but usually you want this to happen... how can we distinguish
+ # the two cases?
+ setfocus(win, c);
+ "control" =>
+ if((c.flags & Controller) == 0)
+ return "control not available";
+ c.flags |= Controlstarted;
+ * =>
+ return "unknown input source";
+ }
+ "!reshape" =>
+ # reshape tag reqid rect [how]
+ # XXX allow "how" to specify that the origin of the window is never
+ # changed - a new window will be created instead.
+ if(n < 7)
+ return "bad arg count";
+ args = tl args;
+ tag := hd args; args = tl args;
+ args = tl args; # skip reqid
+ r: Rect;
+ r.min.x = int hd args; args = tl args;
+ r.min.y = int hd args; args = tl args;
+ r.max.x = int hd args; args = tl args;
+ r.max.y = int hd args; args = tl args;
+ if(args != nil){
+ case hd args{
+ "onscreen" =>
+ r = fitrect(r, screen.image.r);
+ "place" =>
+ r = fitrect(r, screen.image.r);
+ r = newrect(r, screen.image.r);
+ "exact" =>
+ ;
+ "max" =>
+ r = screen.image.r; # XXX don't obscure toolbar?
+ * =>
+ return "unkown placement method";
+ }
+ }
+ return reshape(c, tag, r);
+ "delete" =>
+ # delete tag
+ if(tl args == nil)
+ return "tag required";
+ c.setimage(hd tl args, nil);
+ if(c.wins == nil && c == kbdfocus)
+ setfocus(win, nil);
+ "raise" =>
+ c.top();
+ "lower" =>
+ c.bottom();
+ "!move" or
+ "!size" =>
+ # !move tag reqid startx starty
+ # !size tag reqid mindx mindy
+ ismove := hd args == "!move";
+ if(n < 3)
+ return "bad arg count";
+ args = tl args;
+ tag := hd args; args = tl args;
+ args = tl args; # skip reqid
+ w := c.window(tag);
+ if(w == nil)
+ return "no such tag";
+ if(ismove){
+ if(n != 5)
+ return "bad arg count";
+ return dragwin(wmctxt.ptr, c, w, Point(int hd args, int hd tl args).sub(w.r.min));
+ }else{
+ if(n != 5)
+ return "bad arg count";
+ sizewin(wmctxt.ptr, c, w, Point(int hd args, int hd tl args));
+ }
+ "fixedorigin" =>
+ c.flags |= Fixedorigin;
+ "rect" =>
+ ;
+ "kbdfocus" =>
+ if(n != 2)
+ return "bad arg count";
+ if(int hd tl args)
+ setfocus(win, c);
+ else if(c == kbdfocus)
+ setfocus(win, nil);
+ # controller specific messages:
+ "request" => # can be used to test for control.
+ if((c.flags & Controller) == 0)
+ return "you are not in control";
+ "ctl" =>
+ # ctl id msg
+ if((c.flags & Controlstarted) == 0)
+ return "invalid request";
+ if(n < 3)
+ return "bad arg count";
+ id := int hd tl args;
+ for(z := wmsrv->top(); z != nil; z = z.znext)
+ if(z.id == id)
+ break;
+ if(z == nil)
+ return "no such client";
+ z.ctl <-= str->quoted(tl tl args);
+ "endcontrol" =>
+ if(c != controller)
+ return "invalid request";
+ controller = nil;
+ allowcontrol = 1;
+ c.flags &= ~(Controlstarted | Controller);
+ * =>
+ if(c == controller || controller == nil || (controller.flags & Controlstarted) == 0)
+ return "unknown control request";
+ controller.ctl <-= "request " + string c.id + " " + req;
+ }
+ return nil;
+}
+
+Fix: con 1000;
+# the window manager window has been reshaped;
+# allocate a new screen, and move all the
+reshaped(win: ref Wmclient->Window)
+{
+ oldr := screen.image.r;
+ newr := win.image.r;
+ mx := Fix;
+ if(oldr.dx() > 0)
+ mx = newr.dx() * Fix / oldr.dx();
+ my := Fix;
+ if(oldr.dy() > 0)
+ my = newr.dy() * Fix / oldr.dy();
+ screen = makescreen(win.image);
+ for(z := wmsrv->top(); z != nil; z = z.znext){
+ for(wl := z.wins; wl != nil; wl = tl wl){
+ w := hd wl;
+ w.img = nil;
+ nr := w.r.subpt(oldr.min);
+ nr.min.x = nr.min.x * mx / Fix;
+ nr.min.y = nr.min.y * my / Fix;
+ nr.max.x = nr.max.x * mx / Fix;
+ nr.max.y = nr.max.y * my / Fix;
+ nr = nr.addpt(newr.min);
+ w.img = screen.newwindow(nr, Draw->Refbackup, Draw->Nofill);
+ # XXX check for creation failure
+ w.r = nr;
+ z.ctl <-= sys->sprint("!reshape %q -1 %s", w.tag, r2s(nr));
+ z.ctl <-= "rect " + r2s(newr);
+ }
+ }
+}
+
+controlevent(e: string)
+{
+ if(controller != nil && (controller.flags & Controlstarted))
+ controller.ctl <-= e;
+}
+
+dragwin(ptr: chan of ref Pointer, c: ref Client, w: ref Window, off: Point): string
+{
+ if(buttons == 0)
+ return "too late";
+ p: ref Pointer;
+ do{
+ p = <-ptr;
+ w.img.origin(w.img.r.min, p.xy.sub(off));
+ } while (p.buttons != 0);
+ c.ptr <-= p;
+ buttons = 0;
+ r: Rect;
+ r.min = p.xy.sub(off);
+ r.max = r.min.add(w.r.size());
+ if(r.eq(w.r))
+ return "not moved";
+ reshape(c, w.tag, r);
+ return nil;
+}
+
+sizewin(ptrc: chan of ref Pointer, c: ref Client, w: ref Window, minsize: Point): string
+{
+ borders := array[4] of ref Image;
+ showborders(borders, w.r, Minx|Maxx|Miny|Maxy);
+ screen.image.flush(Draw->Flushnow);
+ while((ptr := <-ptrc).buttons == 0)
+ ;
+ xy := ptr.xy;
+ move, show: int;
+ offset := Point(0, 0);
+ r := w.r;
+ show = Minx|Miny|Maxx|Maxy;
+ if(xy.in(w.r) == 0){
+ r = (xy, xy);
+ move = Maxx|Maxy;
+ }else {
+ if(xy.x < (r.min.x+r.max.x)/2){
+ move=Minx;
+ offset.x = xy.x - r.min.x;
+ }else{
+ move=Maxx;
+ offset.x = xy.x - r.max.x;
+ }
+ if(xy.y < (r.min.y+r.max.y)/2){
+ move |= Miny;
+ offset.y = xy.y - r.min.y;
+ }else{
+ move |= Maxy;
+ offset.y = xy.y - r.max.y;
+ }
+ }
+ return reshape(c, w.tag, sweep(ptrc, r, offset, borders, move, show, minsize));
+}
+
+reshape(c: ref Client, tag: string, r: Rect): string
+{
+ w := c.window(tag);
+ # if window hasn't changed size, then just change its origin and use the same image.
+ if((c.flags & Fixedorigin) == 0 && w != nil && w.r.size().eq(r.size())){
+ c.setorigin(tag, r.min);
+ } else {
+ img := screen.newwindow(r, Draw->Refbackup, Draw->Nofill);
+ if(img == nil)
+ return sys->sprint("window creation failed: %r");
+ if(c.setimage(tag, img) == -1)
+ return "can't do two at once";
+ }
+ c.top();
+ return nil;
+}
+
+sweep(ptr: chan of ref Pointer, r: Rect, offset: Point, borders: array of ref Image, move, show: int, min: Point): Rect
+{
+ while((p := <-ptr).buttons != 0){
+ xy := p.xy.sub(offset);
+ if(move&Minx)
+ r.min.x = xy.x;
+ if(move&Miny)
+ r.min.y = xy.y;
+ if(move&Maxx)
+ r.max.x = xy.x;
+ if(move&Maxy)
+ r.max.y = xy.y;
+ showborders(borders, r, show);
+ }
+ r = r.canon();
+ if(r.min.y < screen.image.r.min.y){
+ r.min.y = screen.image.r.min.y;
+ r = r.canon();
+ }
+ if(r.dx() < min.x){
+ if(move & Maxx)
+ r.max.x = r.min.x + min.x;
+ else
+ r.min.x = r.max.x - min.x;
+ }
+ if(r.dy() < min.y){
+ if(move & Maxy)
+ r.max.y = r.min.y + min.y;
+ else {
+ r.min.y = r.max.y - min.y;
+ if(r.min.y < screen.image.r.min.y){
+ r.min.y = screen.image.r.min.y;
+ r.max.y = r.min.y + min.y;
+ }
+ }
+ }
+ return r;
+}
+
+showborders(b: array of ref Image, r: Rect, show: int)
+{
+ r = r.canon();
+ b[Sminx] = showborder(b[Sminx], show&Minx,
+ (r.min, (r.min.x+Bdwidth, r.max.y)));
+ b[Sminy] = showborder(b[Sminy], show&Miny,
+ ((r.min.x+Bdwidth, r.min.y), (r.max.x-Bdwidth, r.min.y+Bdwidth)));
+ b[Smaxx] = showborder(b[Smaxx], show&Maxx,
+ ((r.max.x-Bdwidth, r.min.y), (r.max.x, r.max.y)));
+ b[Smaxy] = showborder(b[Smaxy], show&Maxy,
+ ((r.min.x+Bdwidth, r.max.y-Bdwidth), (r.max.x-Bdwidth, r.max.y)));
+}
+
+showborder(b: ref Image, show: int, r: Rect): ref Image
+{
+ if(!show)
+ return nil;
+ if(b != nil && b.r.size().eq(r.size()))
+ b.origin(r.min, r.min);
+ else
+ b = screen.newwindow(r, Draw->Refbackup, Draw->Red);
+ return b;
+}
+
+r2s(r: Rect): string
+{
+ return string r.min.x + " " + string r.min.y + " " +
+ string r.max.x + " " + string r.max.y;
+}
+
+# XXX for consideration:
+# do not allow applications to grab the keyboard focus
+# unless there is currently no keyboard focus...
+# but what about launching a new app from the taskbar:
+# surely we should allow that to grab the focus?
+setfocus(win: ref Wmclient->Window, new: ref Client)
+{
+ old := kbdfocus;
+ if(old == new)
+ return;
+ if(new == nil)
+ wmclient->win.wmctl("cursor");
+ else if(old == nil || old.cursor != new.cursor)
+ wmclient->win.wmctl(new.cursor);
+ if(new != nil && (new.flags & Kbdstarted) == 0)
+ return;
+ if(old != nil)
+ old.ctl <-= "haskbdfocus 0";
+
+ if(new != nil){
+ new.ctl <-= "raise";
+ new.ctl <-= "haskbdfocus 1";
+ kbdfocus = new;
+ } else
+ kbdfocus = nil;
+}
+
+makescreen(img: ref Image): ref Screen
+{
+ screen = Screen.allocate(img, img.display.color(Background), 0);
+ img.draw(img.r, screen.fill, nil, screen.fill.r.min);
+ return screen;
+}
+
+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";
+}
+
+# fit a window rectangle to the available space.
+# try to preserve requested location if possible.
+# make sure that the window is no bigger than
+# the screen, and that its top and left-hand edges
+# will be visible at least.
+fitrect(w, r: Rect): Rect
+{
+ if(w.dx() > r.dx())
+ w.max.x = w.min.x + r.dx();
+ if(w.dy() > r.dy())
+ w.max.y = w.min.y + r.dy();
+ size := w.size();
+ if (w.max.x > r.max.x)
+ (w.min.x, w.max.x) = (r.min.x - size.x, r.max.x - size.x);
+ if (w.max.y > r.max.y)
+ (w.min.y, w.max.y) = (r.min.y - size.y, r.max.y - size.y);
+ if (w.min.x < r.min.x)
+ (w.min.x, w.max.x) = (r.min.x, r.min.x + size.x);
+ if (w.min.y < r.min.y)
+ (w.min.y, w.max.y) = (r.min.y, r.min.y + size.y);
+ return w;
+}
+
+lastrect: Rect;
+# find an suitable area for a window
+newrect(w, r: Rect): Rect
+{
+ rl: list of Rect;
+ for(z := wmsrv->top(); z != nil; z = z.znext)
+ for(wl := z.wins; wl != nil; wl = tl wl)
+ rl = (hd wl).r :: rl;
+ lastrect = winplace->place(rl, r, lastrect, w.size());
+ return lastrect;
+}
+
+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;
+ }
+ }
+}
+
+command(ctxt: ref Draw->Context, args: list of string, sync: chan of string)
+{
+ if((sh := load Sh Sh->PATH) != nil){
+ sh->run(ctxt, "{$*&}" :: args);
+ sync <-= nil;
+ return;
+ }
+ fds := list of {0, 1, 2};
+ sys->pctl(sys->NEWFD, fds);
+
+ cmd := hd args;
+ file := cmd;
+
+ if(len file<4 || file[len file-4:]!=".dis")
+ file += ".dis";
+
+ c := load Wm file;
+ if(c == nil) {
+ err := sys->sprint("%r");
+ if(err != "permission denied" && err != "access permission denied" && file[0]!='/' && file[0:2]!="./"){
+ c = load Wm "/dis/"+file;
+ if(c == nil)
+ err = sys->sprint("%r");
+ }
+ if(c == nil){
+ sync <-= sys->sprint("%s: %s\n", cmd, err);
+ exit;
+ }
+ }
+ sync <-= nil;
+ c->init(ctxt, args);
+}