ref: b548687a8ed1d0a159c9d3f3f921d93bbb56908e
dir: /appl/wm/wm.b/
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.stop == nil) break; 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 > big len d) off = big len d; alt{ rc <-= (d[int off:], nil) =>; # TODO potential bug truncating big to int * =>; } } } 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; scr := screen.image.r; Margin: con 10; do{ p = <-ptr; org := p.xy.sub(off); if(org.y < scr.min.y) org.y = scr.min.y; else if(org.y > scr.max.y - Margin) org.y = scr.max.y - Margin; if(org.x < scr.min.x && org.x + w.r.dx() < scr.min.x + Margin) org.x = scr.min.x + Margin - w.r.dx(); else if(org.x > scr.max.x - Margin) org.x = scr.max.x - Margin; w.img.origin(w.img.r.min, org); } 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); }