diff options
| author | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
|---|---|---|
| committer | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
| commit | 37da2899f40661e3e9631e497da8dc59b971cbd0 (patch) | |
| tree | cbc6d4680e347d906f5fa7fca73214418741df72 /appl/lib/wmlib.b | |
| parent | 54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff) | |
20060303a
Diffstat (limited to 'appl/lib/wmlib.b')
| -rw-r--r-- | appl/lib/wmlib.b | 590 |
1 files changed, 590 insertions, 0 deletions
diff --git a/appl/lib/wmlib.b b/appl/lib/wmlib.b new file mode 100644 index 00000000..804d532e --- /dev/null +++ b/appl/lib/wmlib.b @@ -0,0 +1,590 @@ +implement Wmlib; + +# +# Copyright © 2003 Vita Nuova Holdings Limited +# + +# basic window manager functionality, used by +# tkclient and wmclient to create more usable functionality. + +include "sys.m"; + sys: Sys; +include "draw.m"; + draw: Draw; + Display, Image, Screen, Rect, Point, Pointer, Wmcontext, Context: import draw; +include "wmsrv.m"; +include "wmlib.m"; + +Client: adt{ + ptrpid: int; + kbdpid: int; + ctlpid: int; + req: chan of (array of byte, Sys->Rwrite); + dir: string; + ctlfd: ref Sys->FD; + winfd: ref Sys->FD; +}; + +DEVWM: con "/mnt/wm"; +Ptrsize: con 1+4*12; # 'm' plus 4 12-byte decimal integers + +kbdstarted: int; +ptrstarted: int; +wptr: chan of Point; # set mouse position (only if we've opened /dev/pointer directly) +cswitch: chan of (string, int, chan of string); # switch cursor images (as for wptr) + +init() +{ + sys = load Sys Sys->PATH; + draw = load Draw Draw->PATH; +} + +# (_screen, dispi) := ctxt.display.getwindow("/dev/winname", nil, nil, 1); XXX corrupts heap... fix it! + +makedrawcontext(): ref Draw->Context +{ + display := Display.allocate(nil); + if(display == nil){ + sys->fprint(sys->fildes(2), "wmlib: can't allocate Display: %r\n"); + raise "fail:no display"; + } + return ref Draw->Context(display, nil, nil); +} + +importdrawcontext(devdraw, mntwm: string): (ref Draw->Context, string) +{ + if(mntwm == nil) + mntwm = "/mnt/wm"; + + display := Display.allocate(devdraw); + if(display == nil) + return (nil, sys->sprint("cannot allocate display: %r")); + (ok, nil) := sys->stat(mntwm + "/clone"); + if(ok == -1) + return (nil, "cannot find wm namespace"); + wc := chan of (ref Draw->Context, string); + spawn wmproxy(display, mntwm, wc); + return <-wc; +} + +# XXX we have no way of knowing when this process should go away... +# perhaps a Draw->Context should hold a file descriptor +# so that we do. +wmproxy(display: ref Display, dir: string, wc: chan of (ref Draw->Context, string)) +{ + wmsrv := load Wmsrv Wmsrv->PATH; + if(wmsrv == nil){ + wc <-= (nil, sys->sprint("cannot load %s: %r", Wmsrv->PATH)); + return; + } + sys->pctl(Sys->NEWFD, 1 :: 2 :: nil); + + (wm, join, req) := wmsrv->init(); + if(wm == nil){ + wc <-= (nil, sys->sprint("%r")); + return; + } + wc <-= (ref Draw->Context(display, nil, wm), nil); + + clients: array of ref Client; + for(;;) alt{ + (sc, rc) := <-join => + sync := chan of (ref Client, string); + spawn clientproc(display, sc, dir, sync); + (c, err) := <-sync; + rc <-= err; + if(c != nil){ + if(sc.id >= len clients) + clients = (array[sc.id + 1] of ref Client)[0:] = clients; + clients[sc.id] = c; + } + (sc, data, rc) := <-req => + clients[sc.id].req <-= (data, rc); + if(rc == nil) + clients[sc.id] = nil; + } +} + +zclient: Client; +clientproc(display: ref Display, sc: ref Wmsrv->Client, dir: string, rc: chan of (ref Client, string)) +{ + ctlfd := sys->open(dir + "/clone", Sys->ORDWR); + if(ctlfd == nil){ + rc <-= (nil, sys->sprint("cannot open %s/clone: %r", dir)); + return; + } + buf := array[20] of byte; + n := sys->read(ctlfd, buf, len buf); + if(n <= 0){ + rc <-= (nil, "cannot read ctl id"); + return; + } + sys->fprint(ctlfd, "fixedorigin"); + dir += "/" + string buf[0:n]; + c := ref zclient; + c.req = chan of (array of byte, Sys->Rwrite); + c.dir = dir; + c.ctlfd = ctlfd; + if ((c.winfd = sys->open(dir + "/winname", Sys->OREAD)) == nil){ + rc <-= (nil, sys->sprint("cannot open %s/winname: %r", dir)); + return; + } + rc <-= (c, nil); + + pidc := chan of int; + spawn ctlproc(pidc, ctlfd, sc.ctl); + c.ctlpid = <-pidc; + for(;;) { + (data, drc) := <-c.req; + if(drc == nil) + break; + err := handlerequest(display, c, sc, data); + n = len data; + if(err != nil) + n = -1; + alt{ + drc <-= (n, err) =>; + * =>; + } + } + sc.stop <-= 1; + kill(c.kbdpid, "kill"); + kill(c.ptrpid, "kill"); + kill(c.ctlpid, "kill"); + c.ctlfd = nil; + c.winfd = nil; +} + +handlerequest(display: ref Display, c: ref Client, sc: ref Wmsrv->Client, data: array of byte): string +{ + req := string data; + if(req == nil) + return nil; + (w, e) := qword(req, 0); + case w { + "start" => + (w, e) = qword(req, e); + case w { + "ptr" or + "mouse" => + if(c.ptrpid == -1) + return "already started"; + fd := sys->open(c.dir + "/pointer", Sys->OREAD); + if(fd == nil) + return sys->sprint("cannot open %s: %r", c.dir + "/pointer"); + sync := chan of int; + spawn ptrproc(sync, fd, sc.ptr); + c.ptrpid = <-sync; + return nil; + "kbd" => + if(c.kbdpid == -1) + return "already started"; + sync := chan of (int, string); + spawn kbdproc(sync, c.dir + "/keyboard", sc.kbd); + (pid, err) := <-sync; + c.kbdpid = pid; + return err; + } + } + + if(sys->write(c.ctlfd, data, len data) == -1) + return sys->sprint("%r"); + if(req[0] == '!'){ + buf := array[100] of byte; + n := sys->read(c.winfd, buf, len buf); + if(n <= 0) + return sys->sprint("read winname: %r"); + name := string buf[0:n]; + # XXX this is the dodgy bit... + i := display.namedimage(name); + if(i == nil) + return sys->sprint("cannot get image %#q: %r", name); + s := Screen.allocate(i, display.white, 0); + i = s.newwindow(i.r, Draw->Refnone, Draw->Nofill); + rc := chan of int; + sc.images <-= (nil, i, rc); + if(<-rc == -1) + return "image request already in progress"; + } + return nil; +} + +connect(ctxt: ref Context): ref Wmcontext +{ + # don't automatically make a new Draw->Context, 'cos the + # client should be aware that there's no wm so multiple + # windows won't work correctly. + # ... unless there's an exported wm available, of course! + if(ctxt == nil){ + sys->fprint(sys->fildes(2), "wmlib: no draw context\n"); + raise "fail:error"; + } + if(ctxt.wm == nil){ + wm := ref Wmcontext( + chan of int, + chan of ref Draw->Pointer, + chan of string, + nil, # unused + chan of ref Image, + nil, + ctxt + ); + return wm; + } + fd := sys->open("/chan/wmctl", Sys->ORDWR); + if(fd == nil){ + sys->fprint(sys->fildes(2), "wmlib: cannot open /chan/wmctl: %r\n"); + raise "fail:error"; + } + buf := array[32] of byte; + n := sys->read(fd, buf, len buf); + if(n < 0){ + sys->fprint(sys->fildes(2), "wmlib: cannot get window token: %r\n"); + raise "fail:error"; + } + reply := chan of (string, ref Wmcontext); + ctxt.wm <-= (string buf[0:n], reply); + (err, wm) := <-reply; + if(err != nil){ + sys->fprint(sys->fildes(2), "wmlib: cannot connect: %s\n", err); + raise "fail:" + err; + } + wm.connfd = fd; + wm.ctxt = ctxt; + return wm; +} + +startinput(wm: ref Wmcontext, devs: list of string): string +{ + for(; devs != nil; devs = tl devs) + wmctl(wm, "start " + hd devs); + return nil; +} + +reshape(wm: ref Wmcontext, name: string, r: Draw->Rect, i: ref Draw->Image, how: string): ref Draw->Image +{ + if(name == nil) + return nil; + (nil, ni, err) := wmctl(wm, sys->sprint("!reshape %s -1 %d %d %d %d %s", name, r.min.x, r.min.y, r.max.x, r.max.y, how)); + if(err == nil) + return ni; + return i; +} + +# +# wmctl implements the default window behaviour +# +wmctl(wm: ref Wmcontext, request: string): (string, ref Image, string) +{ + (w, e) := qword(request, 0); + case w { + "exit" => + kill(sys->pctl(0, nil), "killgrp"); + exit; + * => + if(wm.connfd != nil){ + # standard form for requests: if request starts with '!', + # then the next word gives the tag of the window that the + # request applies to, and a new image is provided. + if(sys->fprint(wm.connfd, "%s", request) == -1){ + sys->fprint(sys->fildes(2), "wmlib: wm request '%s' failed\n", request); + return (nil, nil, sys->sprint("%r")); + } + if(request[0] == '!'){ + i := <-wm.images; + if(i == nil) + i = <-wm.images; + return (qword(request, e).t0, i, nil); + } + return (nil, nil, nil); + } + # requests we can handle ourselves, if we have to. + case w{ + "start" => + (w, e) = qword(request, e); + case w{ + "ptr" or + "mouse" => + if(!ptrstarted){ + fd := sys->open("/dev/pointer", Sys->ORDWR); + if(fd != nil) + wptr = chan of Point; + else + fd = sys->open("/dev/pointer", Sys->OREAD); + if(fd == nil) + return (nil, nil, sys->sprint("cannot open /dev/pointer: %r")); + cfd := sys->open("/dev/cursor", Sys->OWRITE); + if(cfd != nil) + cswitch = chan of (string, int, chan of string); + spawn wptrproc(fd, cfd); + sync := chan of int; + spawn ptrproc(sync, fd, wm.ptr); + <-sync; + ptrstarted = 1; + } + "kbd" => + if(!kbdstarted){ + sync := chan of (int, string); + spawn kbdproc(sync, "/dev/keyboard", wm.kbd); + (nil, err) := <-sync; + if(err != nil) + return (nil, nil, err); + spawn sendreq(wm.ctl, "haskbdfocus 1"); + kbdstarted = 1; + } + * => + return (nil, nil, "unknown input source"); + } + return (nil, nil, nil); + "ptr" => + if(wptr == nil) + return (nil, nil, "cannot change mouse position"); + p: Point; + (w, e) = qword(request, e); + p.x = int w; + (w, e) = qword(request, e); + p.y = int w; + wptr <-= p; + return (nil, nil, nil); + "cursor" => + if(cswitch == nil) + return (nil, nil, "cannot switch cursor"); + cswitch <-= (request, e, reply := chan of string); + return (nil, nil, <-reply); + * => + return (nil, nil, "unknown wmctl request"); + } + } +} + +sendreq(c: chan of string, s: string) +{ + c <-= s; +} + +ctlproc(sync: chan of int, fd: ref Sys->FD, ctl: chan of string) +{ + sync <-= sys->pctl(0, nil); + buf := array[4096] of byte; + while((n := sys->read(fd, buf, len buf)) > 0) + ctl <-= string buf[0:n]; +} + +kbdproc(sync: chan of (int, string), f: string, keys: chan of int) +{ + sys->pctl(Sys->NEWFD, nil); + fd := sys->open(f, Sys->OREAD); + if(fd == nil){ + sync <-= (-1, sys->sprint("cannot open /dev/keyboard: %r")); + return; + } + sync <-= (sys->pctl(0, nil), nil); + buf := array[12] of byte; + while((n := sys->read(fd, buf, len buf)) > 0){ + s := string buf[0:n]; + for(j := 0; j < len s; j++) + keys <-= int s[j]; + } +} + +wptrproc(pfd, cfd: ref Sys->FD) +{ + if(wptr == nil && cswitch == nil) + return; + if(wptr == nil) + wptr = chan of Point; + if(cswitch == nil) + cswitch = chan of (string, int, chan of string); + for(;;)alt{ + p := <-wptr => + sys->fprint(pfd, "m%11d %11d", p.x, p.y); + (c, start, reply) := <-cswitch => + buf: array of byte; + if(start == len c){ + buf = array[0] of byte; + }else{ + hot, size: Point; + (w, e) := qword(c, start); + hot.x = int w; + (w, e) = qword(c, e); + hot.y = int w; + (w, e) = qword(c, e); + size.x = int w; + (w, e) = qword(c, e); + size.y = int w; + ((d0, d1), nil) := splitqword(c, e); + nb := size.x/8*size.y; + if(d1 - d0 != nb * 2){ + reply <-= "inconsistent cursor image data"; + break; + } + buf = array[4*4 + nb] of byte; + bplong(buf, 0*4, hot.x); + bplong(buf, 1*4, hot.y); + bplong(buf, 2*4, size.x); + bplong(buf, 3*4, size.y); + j := 4*4; + for(i := d0; i < d1; i += 2) + buf[j++] = byte ((hexc(c[i]) << 4) | hexc(c[i+1])); + } + if(sys->write(cfd, buf, len buf) != len buf) + reply <-= sys->sprint("%r"); + else + reply <-= nil; + } +} + +hexc(c: int): int +{ + if(c >= '0' && c <= '9') + return c - '0'; + if(c >= 'a' && c <= 'f') + return c - 'a' + 10; + if(c >= 'A' && c <= 'F') + return c - 'A' + 10; + return 0; +} + +bplong(d: array of byte, o: int, x: int) +{ + d[o] = byte x; + d[o+1] = byte (x >> 8); + d[o+2] = byte (x >> 16); + d[o+3] = byte (x >> 24); +} + +ptrproc(sync: chan of int, fd: ref Sys->FD, ptr: chan of ref Draw->Pointer) +{ + sync <-= sys->pctl(0, nil); + + b:= array[Ptrsize] of byte; + while(sys->read(fd, b, len b) > 0){ + p := bytes2ptr(b); + if(p != nil) + ptr <-= p; + } +} + +bytes2ptr(b: array of byte): ref Pointer +{ + if(len b < Ptrsize || int b[0] != 'm') + return nil; + x := int string b[1:13]; + y := int string b[13:25]; + but := int string b[25:37]; + msec := int string b[37:49]; + return ref Pointer (but, (x, y), msec); +} + +snarfbuf: string; # at least we get *something* when there's no wm. + +snarfget(): string +{ + fd := sys->open("/chan/snarf", sys->OREAD); + if(fd == nil) + return snarfbuf; + + buf := array[8192] of byte; + nr := 0; + while ((n := sys->read(fd, buf[nr:], len buf - nr)) > 0) { + nr += n; + if (nr == len buf) { + nbuf := array[len buf * 2] of byte; + nbuf[0:] = buf; + buf = nbuf; + } + } + return string buf[0:nr]; +} + +snarfput(buf: string) +{ + fd := sys->open("/chan/snarf", sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "%s", buf); + else + snarfbuf = buf; +} + +# return (qslice, end). +# the slice has a leading quote if the word is quoted; it does not include the terminating quote. +splitqword(s: string, start: int): ((int, int), int) +{ + for(; start < len s; start++) + if(s[start] != ' ') + break; + if(start >= len s) + return ((start, start), start); + i := start; + end := -1; + if(s[i] == '\''){ + gotq := 0; + for(i++; i < len s; i++){ + if(s[i] == '\''){ + if(i + 1 >= len s || s[i + 1] != '\''){ + end = i+1; + break; + } + i++; + gotq = 1; + } + } + if(!gotq && i > start+1) + start++; + if(end == -1) + end = i; + } else { + for(; i < len s; i++) + if(s[i] == ' ') + break; + end = i; + } + return ((start, i), end); +} + +# unquote a string slice as returned by sliceqword. +qslice(s: string, r: (int, int)): string +{ + if(r.t0 == r.t1) + return nil; + if(s[r.t0] != '\'') + return s[r.t0:r.t1]; + t := ""; + for(i := r.t0 + 1; i < r.t1; i++){ + t[len t] = s[i]; + if(s[i] == '\'') + i++; + } + return t; +} + +qword(s: string, start: int): (string, int) +{ + (w, next) := splitqword(s, start); + return (qslice(s, w), next); +} + +s2r(s: string, e: int): (Rect, int) +{ + r: Rect; + w: string; + (w, e) = qword(s, e); + r.min.x = int w; + (w, e) = qword(s, e); + r.min.y = int w; + (w, e) = qword(s, e); + r.max.x = int w; + (w, e) = qword(s, e); + r.max.y = int w; + return (r, e); +} + +kill(pid: int, note: string): int +{ + fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); + if(fd == nil) # dodgy failover + fd = sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE); + if(fd == nil || sys->fprint(fd, "%s", note) < 0) + return -1; + return 0; +} |
