summaryrefslogtreecommitdiff
path: root/appl/wm/drawmux
diff options
context:
space:
mode:
Diffstat (limited to 'appl/wm/drawmux')
-rw-r--r--appl/wm/drawmux/dmview.b163
-rw-r--r--appl/wm/drawmux/dmwm.b207
-rw-r--r--appl/wm/drawmux/drawmux.b1827
-rw-r--r--appl/wm/drawmux/drawmux.m6
-rw-r--r--appl/wm/drawmux/drawoffs.m185
-rw-r--r--appl/wm/drawmux/mkfile37
6 files changed, 2425 insertions, 0 deletions
diff --git a/appl/wm/drawmux/dmview.b b/appl/wm/drawmux/dmview.b
new file mode 100644
index 00000000..92fdaa2d
--- /dev/null
+++ b/appl/wm/drawmux/dmview.b
@@ -0,0 +1,163 @@
+implement DMView;
+
+include "sys.m";
+include "draw.m";
+include "tk.m";
+include "tkclient.m";
+
+DMView : module {
+ init : fn (ctxt : ref Draw->Context, args : list of string);
+};
+
+DMPORT : con 9998;
+
+sys : Sys;
+draw : Draw;
+tk : Tk;
+tkclient : Tkclient;
+
+Display, Image, Screen, Point, Rect, Chans : import draw;
+
+display : ref Display;
+screen : ref Screen;
+
+
+init(ctxt : ref Draw->Context, args : list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ if (tk == nil)
+ fail(sys->sprint("cannot load %s: %r", Tk->PATH), "init");
+
+ tkclient = load Tkclient Tkclient->PATH;
+ if (tkclient == nil)
+ fail(sys->sprint("cannot load %s: %r", Tkclient->PATH), "init");
+
+ args = tl args;
+ if (args == nil)
+ fail("usage: dmview netaddr", "usage");
+ addr := hd args;
+ args = tl args;
+
+ display = ctxt.display;
+ screen = ctxt.screen;
+
+ tkclient ->init();
+
+ (ok, nc) := sys->dial("tcp!"+addr+"!" + string DMPORT, nil);
+ if (ok < 0)
+ fail(sys->sprint("could not connect: %r"), "init");
+
+ info := array [2 * 12] of byte;
+ if (sys->read(nc.dfd, info, len info) != len info) {
+ sys->print("protocol error\n");
+ return;
+ }
+ dispw := int string info[0:12];
+ disph := int string info[12:24];
+ info = nil;
+
+ (tktop, wmctl) := tkclient->toplevel(ctxt, "", "dmview: "+addr, Tkclient->Hide);
+ if (tktop == nil)
+ fail("cannot create window", "init");
+
+ cpos := mkframe(tktop, dispw, disph);
+ winr := Rect((0, 0), (dispw, disph));
+ newwin := display.newimage(winr, display.image.chans, 0, Draw->White);
+ # newwin := screen.newwindow(winr, Draw->Refbackup, Draw->White);
+ if (newwin == nil) {
+ sys->print("failed to create window: %r\n");
+ return;
+ }
+ tk->putimage(tktop, ".c", newwin, nil);
+ tk->cmd(tktop, ".c dirty");
+ tk->cmd(tktop, "update");
+ winr = winr.addpt(cpos);
+ newwin.origin(Point(0,0), winr.min);
+
+ pubscr := Screen.allocate(newwin, ctxt.display.black, 1);
+ if (pubscr == nil) {
+ sys->print("failed to create public screen: %r\n");
+ return;
+ }
+
+ msg := array of byte sys->sprint("%11d %11s ", pubscr.id, newwin.chans.text());
+ sys->write(nc.dfd, msg, len msg);
+ msg = nil;
+
+ pidc := chan of int;
+ spawn srv(nc.dfd, wmctl, pidc);
+ srvpid := <- pidc;
+
+ tkclient->onscreen(tktop, nil);
+ tkclient->startinput(tktop, nil);
+
+ for (;;) {
+ cmd := <- wmctl;
+ case cmd {
+ "srvexit" =>
+sys->print("srv exit: %r\n");
+ srvpid = -1;
+ "exit" =>
+ if (srvpid != -1)
+ kill(srvpid);
+ return;
+ "move" =>
+ newwin.origin(Point(0,0), display.image.r.max);
+ tkclient->wmctl(tktop, cmd);
+ x := int tk->cmd(tktop, ".c cget -actx");
+ y := int tk->cmd(tktop, ".c cget -acty");
+ newwin.origin(Point(0,0), Point(x, y));
+ "task" =>
+ newwin.origin(Point(0,0), display.image.r.max);
+ tkclient->wmctl(tktop, cmd);
+ x := int tk->cmd(tktop, ".c cget -actx");
+ y := int tk->cmd(tktop, ".c cget -acty");
+ newwin.origin(Point(0,0), Point(x, y));
+ * =>
+ tkclient->wmctl(tktop, cmd);
+ }
+ }
+}
+
+srv(fd : ref Sys->FD, done : chan of string, pidc : chan of int)
+{
+ pidc <-= sys->pctl(Sys->FORKNS, nil);
+ sys->bind("/dev/draw", "/", Sys->MREPL);
+ sys->export(fd, "/", Sys->EXPWAIT);
+ done <-= "srvexit";
+}
+
+fail(msg, exc : string)
+{
+ sys->print("%s\n", msg);
+ raise "fail:"+exc;
+}
+
+mkframe(t : ref Tk->Toplevel, w, h : int) : Point
+{
+ tk->cmd(t, "panel .c -width " + string w + " -height " + string h);
+ tk->cmd(t, "frame .f -borderwidth 3 -relief groove");
+ tk->cmd(t, "pack .c -in .f");
+ tk->cmd(t, "pack .f");
+ tk->cmd(t, "update");
+
+ x := int tk->cmd(t, ".c cget -actx");
+ y := int tk->cmd(t, ".c cget -acty");
+
+ return Point(x, y);
+}
+
+kill(pid: int)
+{
+ if ((pctl := sys->open("/prog/" + string pid + "/ctl", Sys->OWRITE)) != nil)
+ sys->fprint(pctl, "kill");
+}
+
+tkcmd(t : ref Tk->Toplevel, c : string)
+{
+ s := tk->cmd(t, c);
+ if (s != nil)
+ sys->print("%s ERROR: %s\n", c, s);
+}
diff --git a/appl/wm/drawmux/dmwm.b b/appl/wm/drawmux/dmwm.b
new file mode 100644
index 00000000..45d80f8a
--- /dev/null
+++ b/appl/wm/drawmux/dmwm.b
@@ -0,0 +1,207 @@
+implement Dmwm;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+ Screen, Display, Image, Rect, Point, Wmcontext, Pointer: import draw;
+include "drawmux.m";
+ dmux : Drawmux;
+include "wmsrv.m";
+ wmsrv: Wmsrv;
+ Window, Client: import wmsrv;
+include "tk.m";
+include "wmclient.m";
+ wmclient: Wmclient;
+include "string.m";
+ str: String;
+include "dialog.m";
+ dialog: Dialog;
+include "arg.m";
+
+Wm: module {
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Dmwm: module {
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Background: con int 16r777777FF;
+
+screen: ref Screen;
+display: ref Display;
+
+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();
+
+ dialog = load Dialog Dialog->PATH;
+ if (dialog == nil) badmodule(Dialog->PATH);
+ dialog->init();
+
+ sys->pctl(Sys->NEWPGRP|Sys->FORKNS, nil);
+ if (ctxt == nil)
+ ctxt = wmclient->makedrawcontext();
+ display = ctxt.display;
+
+ dmux = load Drawmux Drawmux->PATH;
+ if (dmux != nil) {
+ (err, disp) := dmux->init();
+ if (err != nil) {
+ dmux = nil;
+ sys->fprint(stderr(), "wm: cannot start drawmux: %s\n", err);
+ }
+ else
+ display = disp;
+ }
+
+ buts := Wmclient->Appl;
+ if(ctxt.wm == nil)
+ buts = Wmclient->Plain;
+ # win := wmclient->window(ctxt, "Wm", buts);
+ # wmclient->win.onscreen("place");
+ # wmclient->win.startinput("kbd" :: "ptr" :: nil);
+
+ # screen = makescreen(win.image);
+
+ (clientwm, join, req) := wmsrv->init();
+ clientctxt := ref Draw->Context(display, nil, nil);
+
+ sync := chan of string;
+ argv = tl argv;
+ if(argv == nil)
+ argv = "wm/toolbar" :: nil;
+ argv = "wm/wm" :: argv;
+ spawn command(clientctxt, argv, sync);
+ if((e := <-sync) != nil)
+ fatal("cannot run command: " + e);
+
+ dmuxrequest := chan of (string, ref Sys->FD);
+ if (dmux != nil)
+ spawn dmuxlistener(dmuxrequest);
+
+ for(;;) alt {
+ (name, fd) := <- dmuxrequest =>
+ spawn dmuxask(ctxt, name, fd);
+ }
+}
+
+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";
+}
+
+command(ctxt: ref Draw->Context, args: list of string, sync: chan of string)
+{
+ fds := list of {0, 1, 2};
+ pid := 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);
+}
+
+dmuxlistener(newclient : chan of (string, ref Sys->FD))
+{
+ (aok, c) := sys->announce("tcp!*!9998");
+ if (aok < 0) {
+ sys->print("cannot announce drawmux port: %r\n");
+ return;
+ }
+ buf := array [Sys->ATOMICIO] of byte;
+ for (;;) {
+ (ok, nc) := sys->listen(c);
+ if (ok < 0) {
+ sys->fprint(stderr(), "wm: dmux listen failed: %r\n");
+ return;
+ }
+ fd := sys->open(nc.dir+"/remote", Sys->OREAD);
+ name := "unknown";
+ if (fd == nil)
+ sys->fprint(stderr(), "wm: dmux cannot access remote address: %r\n");
+ else {
+ n := sys->read(fd, buf, len buf);
+ if (n > 0) {
+ name = string buf[0:n];
+ for (i := len name -1; i > 0; i--)
+ if (name[i] == '!')
+ break;
+ if (i != 0)
+ name = name[0:i];
+ }
+ }
+ fd = sys->open(nc.dir+"/data", Sys->ORDWR);
+ if (fd != nil)
+ newclient <-= (name, fd);
+ }
+}
+
+dmuxask(ctxt: ref Draw->Context, name : string, fd : ref Sys->FD)
+{
+ msg := sys->sprint("Screen snoop request\nAddress: %s\n\nProceed?", name);
+ labs := "Ok" :: "No way!" :: nil;
+ if (1 || dialog->prompt(ctxt, nil, nil, "Snoop!", msg, 1, labs) == 0)
+ dmux->newviewer(fd);
+}
+
+stderr(): ref Sys->FD
+{
+ return sys->fildes(2);
+}
diff --git a/appl/wm/drawmux/drawmux.b b/appl/wm/drawmux/drawmux.b
new file mode 100644
index 00000000..121efec0
--- /dev/null
+++ b/appl/wm/drawmux/drawmux.b
@@ -0,0 +1,1827 @@
+implement Drawmux;
+
+include "sys.m";
+include "draw.m";
+include "drawmux.m";
+
+include "drawoffs.m";
+
+sys : Sys;
+draw : Draw;
+
+Display, Point, Rect, Chans : import draw;
+
+Ehungup : con "Hangup";
+
+drawR: Draw->Rect;
+drawchans: Draw->Chans;
+drawop := Draw->SoverD;
+drawfd: ref Sys->FD;
+images: ref Imageset;
+screens: ref Screenset;
+viewers: list of ref Viewer;
+drawlock: chan of chan of int;
+readdata: array of byte;
+nhangups := 0;
+prevnhangups := 0;
+
+init() : (string, ref Draw->Display)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+
+ if (draw == nil)
+ return (sys->sprint("cannot load %s: %r", Draw->PATH), nil);
+ drawlock = chan of chan of int;
+ images = Imageset.new();
+ screens = Screenset. new();
+ res := chan of (string, ref Draw->Display);
+ spawn getdisp(res);
+ r := <- res;
+ return r;
+}
+
+newviewer(fd : ref Sys->FD)
+{
+ reply := array of byte sys->sprint("%.11d %.11d ", drawR.max.x - drawR.min.x, drawR.max.y - drawR.min.y);
+ if (sys->write(fd, reply, len reply) != len reply) {
+# sys->print("viewer hangup\n");
+ return;
+ }
+
+ buf := array [Sys->ATOMICIO] of byte;
+ n := sys->read(fd, buf, len buf);
+ if (n < 24)
+ return;
+ pubscr := int string buf[0:12];
+ chans := Chans.mk(string buf[12:24]);
+
+ sys->pctl(Sys->FORKNS, nil);
+ sys->mount(fd, nil, "/", Sys->MREPL, nil);
+ cfd := sys->open("/new", Sys->OREAD);
+ sys->read(cfd, buf, len buf);
+ cnum := int string buf[0:12];
+ cdata := sys->sprint("/%d/data", cnum);
+ datafd := sys->open(cdata, Sys->ORDWR);
+
+ if (datafd == nil) {
+# sys->print("cannot open viewer data file: %r\n");
+ return;
+ }
+ Viewer.new(datafd, pubscr, chans);
+}
+
+getdisp(result : chan of (string, ref Draw->Display))
+{
+ sys->pctl(Sys->FORKNS, nil);
+ sys->bind("#i", "/dev", Sys->MREPL);
+ sys->bind("#s", "/dev/draw", Sys->MBEFORE);
+ newio := sys->file2chan("/dev/draw", "new");
+ if (newio == nil) {
+ result <- = ("cannot create /dev/new file2chan", nil);
+ return;
+ }
+ spawn srvnew(newio);
+ disp := Display.allocate(nil);
+ if (disp == nil) {
+ result <-= (sys->sprint("%r"), nil);
+ return;
+ }
+
+ draw->disp.image.draw(disp.image.r, disp.rgb(0,0,0), nil, Point(0,0));
+ result <- = (nil, disp);
+}
+
+srvnew(newio : ref Sys->FileIO)
+{
+ for (;;) alt {
+ (offset, count, fid, rc) := <- newio.read =>
+ if (rc != nil) {
+ c := chan of (string, ref Sys->FD);
+ fd := sys->open("#i/draw/new", Sys->OREAD);
+ # +1 because of a sprint() nasty in devdraw.c
+ buf := array [(12 * 12)+1] of byte;
+ nn := sys->read(fd, buf, len buf);
+ cnum := int string buf[0:12];
+ drawchans = Chans.mk(string buf[24:36]);
+ # repl is at [36:48]
+ drawR.min.x = int string buf[48:60];
+ drawR.min.y = int string buf[60:72];
+ drawR.max.x = int string buf[72:84];
+ drawR.max.y = int string buf[84:96];
+
+ bwidth := bytesperline(drawR, drawchans);
+ img := ref Image (0, 0, 0, 0, drawchans, 0, drawR, drawR, Draw->Black, nil, drawR.min, bwidth, 0, "");
+ images.add(0, img);
+
+ cdir := sys->sprint("/dev/draw/%d", cnum);
+ dpath := sys->sprint("#i/draw/%d/data", cnum);
+ drawfd = sys->open(dpath, Sys->ORDWR);
+ fd = nil;
+ if (drawfd == nil) {
+ rc <-= (nil, sys->sprint("%r"));
+ return;
+ }
+ sys->bind("#s", cdir, Sys->MBEFORE);
+ drawio := sys->file2chan(cdir, "data");
+ spawn drawclient(drawio);
+ rc <- = (buf, nil);
+ return;
+ }
+ (offset, data, fid, wc) := <- newio.write =>
+ if (wc != nil)
+ writereply(wc, (0, "permission denied"));
+ }
+}
+
+# for simplicity make the file 'exclusive use'
+drawclient(drawio : ref Sys->FileIO)
+{
+ activefid := -1;
+ closecount := 2;
+
+ for (;closecount;) {
+ alt {
+ unlock := <- drawlock =>
+ <- unlock;
+
+ (offset, count, fid, rc) := <- drawio.read =>
+ if (activefid == -1)
+ activefid = fid;
+
+ if (rc == nil) {
+ closecount--;
+ continue;
+ }
+ if (fid != activefid) {
+ rc <-= (nil, "file busy");
+ continue;
+ }
+ if (readdata == nil) {
+ rc <-= (nil, nil);
+ continue;
+ }
+ if (count > len readdata)
+ count = len readdata;
+ rc <- = (readdata[0:count], nil);
+ readdata = nil;
+
+ (offset, data, fid, wc) := <- drawio.write =>
+ if (wc == nil) {
+ closecount--;
+ continue;
+ }
+ writereply(wc, process(data));
+ }
+ if (nhangups != prevnhangups) {
+ ok : list of ref Viewer;
+ for (ok = nil; viewers != nil; viewers = tl viewers) {
+ v := hd viewers;
+ if (!v.hungup)
+ ok = v :: ok;
+ else {
+# sys->print("shutting down Viewer\n");
+ v.output <- = (nil, nil);
+ }
+ }
+ viewers = ok;
+ prevnhangups = nhangups;
+ }
+ }
+# sys->print("DRAWIO DONE!\n");
+}
+
+writereply(wc : chan of (int, string), val : (int, string))
+{
+ alt {
+ wc <-= val =>
+ ;
+ * =>
+ ;
+ }
+}
+
+Image: adt {
+ id: int;
+ refc: int;
+ screenid: int;
+ refresh: int;
+ chans: Draw->Chans;
+ repl: int;
+ R: Draw->Rect;
+ clipR: Draw->Rect;
+ rrggbbaa: int;
+ font: ref Font;
+ lorigin: Draw->Point;
+ bwidth: int;
+ dirty: int;
+ name: string;
+};
+
+Screen: adt {
+ id: int;
+ imageid: int;
+ fillid: int;
+ windows: array of int;
+
+ setz: fn (s: self ref Screen, z: array of int, top: int);
+ addwin: fn (s: self ref Screen, wid: int);
+ delwin: fn (s: self ref Screen, wid: int);
+};
+
+Font: adt {
+ ascent: int;
+ chars: array of ref Fontchar;
+};
+
+Fontchar: adt {
+ srcid: int;
+ R: Draw->Rect;
+ P: Draw->Point;
+ left: int;
+ width: int;
+};
+
+Idpair: adt {
+ key: int;
+ val: int;
+ next: cyclic ref Idpair;
+};
+
+Idmap: adt {
+ buckets: array of ref Idpair;
+
+ new: fn (): ref Idmap;
+ add: fn (m: self ref Idmap, key, val: int);
+ del: fn (m: self ref Idmap, key: int);
+ lookup: fn (m: self ref Idmap, key: int): int;
+};
+
+Imageset: adt {
+ images: array of ref Image;
+ ixmap: ref Idmap;
+ freelist: list of int;
+ new: fn (): ref Imageset;
+ add: fn (s: self ref Imageset, id: int, img: ref Image);
+ del: fn (s: self ref Imageset, id: int);
+ lookup: fn (s: self ref Imageset, id: int): ref Image;
+ findname: fn(s: self ref Imageset, name: string): ref Image;
+};
+
+Screenset: adt {
+ screens: array of ref Screen;
+ ixmap: ref Idmap;
+ freelist: list of int;
+ new: fn (): ref Screenset;
+ add: fn (s: self ref Screenset, scr: ref Screen);
+ del: fn (s: self ref Screenset, id: int);
+ lookup: fn (s: self ref Screenset, id: int): ref Screen;
+};
+
+
+Drawreq: adt {
+ data: array of byte;
+ pick {
+# a => # allocate image
+# id: int;
+# screenid: int;
+# refresh: int;
+# ldepth: int;
+# repl: int;
+# R: Draw->Rect;
+# clipR: Draw->Rect;
+# value: int;
+ b => # new allocate image
+ id: int;
+ screenid: int;
+ refresh: int;
+ chans: Draw->Chans;
+ repl: int;
+ R: Draw->Rect;
+ clipR: Draw->Rect;
+ rrggbbaa: int;
+ A => # allocate screen
+ id: int;
+ imageid: int;
+ fillid: int;
+ c => # set clipr and repl
+ dstid: int;
+ repl: int;
+ clipR: Draw->Rect;
+# x => # move cursor
+# C => # set cursor image and hotspot
+# _: int;
+ d => # general draw op
+ dstid: int;
+ srcid: int;
+ maskid: int;
+ D => # debug mode
+ _: int;
+ e => # draw ellipse
+ dstid: int;
+ srcid: int;
+ f => # free image
+ id: int;
+ img: ref Image; # helper for Viewers
+ F => # free screen
+ id: int;
+ i => # convert image to font
+ fontid: int;
+ nchars: int;
+ ascent: int;
+ l => # load a char into font
+ fontid: int;
+ srcid: int;
+ index: int;
+ R: Draw->Rect;
+ P: Draw->Point;
+ left: int;
+ width: int;
+ L => # draw line
+ dstid: int;
+ srcid: int;
+ n => # attach to named image
+ dstid: int;
+ name: string;
+ N => # name image
+ dstid: int;
+ in: int;
+ name: string;
+ o => # set window origins
+ id: int;
+ rmin: Draw->Point;
+ screenrmin: Draw->Point;
+ O => # set next compositing op
+ op: int;
+ p => # draw polygon
+ dstid: int;
+ srcid: int;
+ r => # read pixels
+ id: int;
+ R: Draw->Rect;
+ s => # draw text
+ dstid: int;
+ srcid: int;
+ fontid: int;
+ x => # draw text with bg
+ dstid: int;
+ srcid: int;
+ fontid: int;
+ bgid: int;
+ S => # import public screen
+ t => # adjust window z order
+ top: int;
+ ids: array of int;
+ v => # flush updates to display
+ y => # write pixels
+ id: int;
+ R: Draw->Rect;
+ }
+};
+
+getreq(data : array of byte, ix : int) : (ref Drawreq, string)
+{
+ mlen := 0;
+ err := "short draw message";
+ req : ref Drawreq;
+
+ case int data[ix] {
+ 'b' => # alloc image
+ mlen = 1+4+4+1+4+1+(4*4)+(4*4)+4;
+ if (mlen+ix <= len data) {
+ data = data[ix:ix+mlen];
+ r := ref Drawreq.b;
+ r.data = data;
+ r.id = get4(data, OPb_id);
+ r.screenid = get4(data, OPb_screenid);
+ r.refresh = get1(data, OPb_refresh);
+ r.chans = Draw->Chans(get4(data, OPb_chans));
+ r.repl = get1(data, OPb_repl);
+ r.R = getR(data, OPb_R);
+ r.clipR = getR(data, OPb_clipR);
+ r.rrggbbaa = get4(data, OPb_rrggbbaa);
+ req = r;
+ }
+ 'A' => # alloc screen
+ mlen = 1+4+4+4+1;
+ if (mlen+ix <= len data) {
+ data = data[ix:ix+mlen];
+ r := ref Drawreq.A;
+ r.data = data;
+ r.id = get4(data, OPA_id);
+ r.imageid = get4(data, OPA_imageid);
+ r.fillid = get4(data, OPA_fillid);
+ req = r;
+ }
+ 'c' => # set clipR
+ mlen = 1+4+1+(4*4);
+ if (mlen+ix <= len data) {
+ data = data[ix:ix+mlen];
+ r := ref Drawreq.c;
+ r.data = data;
+ r.dstid = get4(data, OPc_dstid);
+ r.repl = get1(data, OPc_repl);
+ r.clipR = getR(data, OPc_clipR);
+ req = r;
+ }
+ 'd' => # draw
+ mlen = 1+4+4+4+(4*4)+(2*4)+(2*4);
+ if (mlen+ix <= len data) {
+ data = data[ix:ix+mlen];
+ r := ref Drawreq.d;
+ r.data = data;
+ r.dstid = get4(data, OPd_dstid);
+ r.srcid = get4(data, OPd_srcid);
+ r.maskid = get4(data, OPd_maskid);
+ req = r;
+ }
+ 'D' =>
+ # debug mode
+ mlen = 1+1;
+ if (mlen+ix <= len data) {
+ req = ref Drawreq.v;
+ req.data = data[ix:ix+mlen];
+ }
+ 'e' or
+ 'E' => # ellipse
+ mlen = 1+4+4+(2*4)+4+4+4+(2*4)+4+4;
+ if (mlen+ix <= len data) {
+ data = data[ix:ix+mlen];
+ r := ref Drawreq.e;
+ r.data = data;
+ r.dstid = get4(data, OPe_dstid);
+ r.srcid = get4(data, OPe_srcid);
+ req = r;
+ }
+ 'f' => # free image
+ mlen = 1+4;
+ if (mlen+ix <= len data) {
+ data = data[ix:ix+mlen];
+ r := ref Drawreq.f;
+ r.data = data;
+ r.id = get4(data, OPf_id);
+ req = r;
+ }
+ 'F' => # free screen
+ mlen = 1+4;
+ if (mlen+ix <= len data) {
+ data = data[ix:ix+mlen];
+ r := ref Drawreq.f;
+ r.data = data;
+ r.id = get4(data, OPF_id);
+ req = r;
+ }
+ 'i' => # alloc font
+ mlen = 1+4+4+1;
+ if (mlen+ix <= len data) {
+ data = data[ix:ix+mlen];
+ r := ref Drawreq.i;
+ r.data = data;
+ r.fontid = get4(data, OPi_fontid);
+ r.nchars = get4(data, OPi_nchars);
+ r.ascent = get1(data, OPi_ascent);
+ req = r;
+ }
+ 'l' => # load font char
+ mlen = 1+4+4+2+(4*4)+(2*4)+1+1;
+ if (mlen+ix <= len data) {
+ data = data[ix:ix+mlen];
+ r := ref Drawreq.l;
+ r.data = data;
+ r.fontid = get4(data, OPl_fontid);
+ r.srcid = get4(data, OPl_srcid);
+ r.index = get2(data, OPl_index);
+ r.R = getR(data, OPl_R);
+ r.P = getP(data, OPl_P);
+ r.left = get1(data, OPl_left);
+ r.width = get1(data, OPl_width);
+ req = r;
+ }
+ 'L' => # line
+ mlen = 1+4+(2*4)+(2*4)+4+4+4+4+(2*4);
+ if (mlen+ix <= len data) {
+ data = data[ix:ix+mlen];
+ r := ref Drawreq.L;
+ r.data = data;
+ r.dstid = get4(data, OPL_dstid);
+ r.srcid = get4(data, OPL_srcid);
+ req = r;
+ }
+ 'n' => # attach to named image
+ mlen = 1+4+1;
+ if (mlen+ix < len data) {
+ mlen += get1(data, ix+OPn_j);
+ if (mlen+ix <= len data) {
+ data = data[ix:ix+mlen];
+ r := ref Drawreq.n;
+ r.data = data;
+ r.dstid = get4(data, OPn_dstid);
+ r.name = string data[OPn_name:];
+ req = r;
+ }
+ }
+ 'N' => # name image
+ mlen = 1+4+1+1;
+ if (mlen+ix < len data) {
+ mlen += get1(data, ix+OPN_j);
+ if (mlen+ix <= len data) {
+ data = data[ix:ix+mlen];
+ r := ref Drawreq.N;
+ r.data = data;
+ r.dstid = get4(data, OPN_dstid);
+ r.in = get1(data, OPN_in);
+ r.name = string data[OPN_name:];
+ req = r;
+ }
+ }
+ 'o' => # set origins
+ mlen = 1+4+(2*4)+(2*4);
+ if (mlen+ix <= len data) {
+ data = data[ix:ix+mlen];
+ r := ref Drawreq.o;
+ r.data = data;
+ r.id = get4(data, OPo_id);
+ r.rmin = getP(data, OPo_rmin);
+ r.screenrmin = getP(data, OPo_screenrmin);
+ req = r;
+ }
+ 'O' => # set next compop
+ mlen = 1+1;
+ if (mlen+ix <= len data) {
+ data = data[ix:ix+mlen];
+ r := ref Drawreq.O;
+ r.data = data;
+ r.op = get1(data, OPO_op);
+ req = r;
+ }
+ 'p' or
+ 'P' => # polygon
+ mlen = 1+4+2+4+4+4+4+(2*4);
+ if (mlen + ix <= len data) {
+ n := get2(data, ix+OPp_n);
+ nb := coordslen(data, ix+OPp_P0, 2*(n+1));
+ if (nb == -1)
+ err = "bad coords";
+ else {
+ mlen += nb;
+ if (mlen+ix <= len data) {
+ data = data[ix:ix+mlen];
+ r := ref Drawreq.p;
+ r.data = data;
+ r.dstid = get4(data, OPp_dstid);
+ r.srcid = get4(data, OPp_srcid);
+ req = r;
+ }
+ }
+ }
+ 'r' => # read pixels
+ mlen = 1+4+(4*4);
+ if (mlen+ix <= len data) {
+ data = data[ix:ix+mlen];
+ r := ref Drawreq.r;
+ r.data = data;
+ r.id = get4(data, OPr_id);
+ r.R = getR(data, OPr_R);
+ req = r;
+ }
+ 's' => # text
+ mlen = 1+4+4+4+(2*4)+(4*4)+(2*4)+2;
+ if (ix+mlen <= len data) {
+ ni := get2(data, ix+OPs_ni);
+ mlen += (2*ni);
+ if (mlen+ix <= len data) {
+ data = data[ix:ix+mlen];
+ r := ref Drawreq.s;
+ r.data = data;
+ r.dstid = get4(data, OPs_dstid);
+ r.srcid = get4(data, OPs_srcid);
+ r.fontid = get4(data, OPs_fontid);
+ req = r;
+ }
+ }
+ 'x' => # text with bg img
+ mlen = 1+4+4+4+(2*4)+(4*4)+(2*4)+2+4+(2*4);
+ if (ix+mlen <= len data) {
+ ni := get2(data, ix+OPx_ni);
+ mlen += (2*ni);
+ if (mlen+ix <= len data) {
+ data = data[ix:ix+mlen];
+ r := ref Drawreq.x;
+ r.data = data;
+ r.dstid = get4(data, OPx_dstid);
+ r.srcid = get4(data, OPx_srcid);
+ r.fontid = get4(data, OPx_fontid);
+ r.bgid = get4(data, OPx_bgid);
+ req = r;
+ }
+ }
+ 'S' => # import public screen
+ mlen = 1+4+4;
+ if (mlen+ix <= len data) {
+ data = data[ix:ix+mlen];
+ req = ref Drawreq.S;
+ req.data = data;
+ }
+ 't' => # adjust window z order
+ mlen = 1+1+2;
+ if (ix+mlen<= len data) {
+ nw := get2(data, ix+OPt_nw);
+ mlen += (4*nw);
+ if (mlen+ix <= len data) {
+ data = data[ix:ix+mlen];
+ r := ref Drawreq.t;
+ r.data = data;
+ r.top = get1(data, OPt_top);
+ r.ids = array [nw] of int;
+ for (n := 0; n < nw; n++)
+ r.ids[n] = get4(data, OPt_id + 4*n);
+ req = r;
+ }
+ }
+ 'v' => # flush
+ req = ref Drawreq.v;
+ req.data = data[ix:ix+1];
+ 'y' or
+ 'Y' => # write pixels
+ mlen = 1+4+(4*4);
+ if (ix+mlen <= len data) {
+ imgid := get4(data, ix+OPy_id);
+ img := images.lookup(imgid);
+ compd := data[ix] == byte 'Y';
+ r := getR(data, ix+OPy_R);
+ n := imglen(img, data, ix+mlen, r, compd);
+ if (n == -1)
+ err ="bad image data";
+ mlen += n;
+ if (mlen+ix <= len data)
+ req = ref Drawreq.y (data[ix:ix+mlen], imgid, r);
+ }
+ * =>
+ err = "bad draw command";
+ }
+
+ if (req == nil)
+ return (nil, err);
+ return (req, nil);
+}
+
+process(data : array of byte) : (int, string)
+{
+ offset := 0;
+ while (offset < len data) {
+ (req, err) := getreq(data, offset);
+ if (err != nil)
+ return (0, err);
+ offset += len req.data;
+ n := sys->write(drawfd, req.data, len req.data);
+ if (n <= 0)
+ return (n, sys->sprint("[%c] %r", int req.data[0]));
+
+ readn := 0;
+ sendtoviews := 1;
+
+ # actions that must be done before sending to Viewers
+ pick r := req {
+ b => # allocate image
+ bwidth := bytesperline(r.R, r.chans);
+ img := ref Image (r.id, 0, r.screenid, r.refresh, r.chans, r.repl, r.R, r.clipR, r.rrggbbaa, nil, r.R.min, bwidth, 0, "");
+ images.add(r.id, img);
+ if (r.screenid != 0) {
+ scr := screens.lookup(r.screenid);
+ scr.addwin(r.id);
+ }
+
+ A => # allocate screen
+ scr := ref Screen (r.id, r.imageid, r.fillid, nil);
+ screens.add(scr);
+ # we never allocate public screens on our Viewers
+ put1(r.data, OPA_public, 0);
+ dirty(r.imageid, 0);
+
+ c => # set clipr and repl
+ img := images.lookup(r.dstid);
+ img.repl = r.repl;
+ img.clipR = r.clipR;
+
+ d => # general draw op
+ dirty(r.dstid, 1);
+ drawop = Draw->SoverD;
+
+ e => # draw ellipse
+ dirty(r.dstid, 1);
+ drawop = Draw->SoverD;
+
+ f => # free image
+ # help out Viewers, real work is done later
+ r.img = images.lookup(r.id);
+
+ L => # draw line
+ dirty(r.dstid, 1);
+ drawop = Draw->SoverD;
+
+ n => # attach to named image
+ img := images.findname(r.name);
+ images.add(r.dstid, img);
+
+ N => # name image
+ img := images.lookup(r.dstid);
+ if (r.in)
+ img.name = r.name;
+ else
+ img.name = nil;
+
+ o => # set image origins
+ img := images.lookup(r.id);
+ deltax := img.lorigin.x - r.rmin.x;
+ deltay := img.lorigin.y - r.rmin.y;
+ w := img.R.max.x - img.R.min.x;
+ h := img.R.max.y - img.R.min.y;
+
+ img.R = Draw->Rect(r.screenrmin, (r.screenrmin.x + w, r.screenrmin.y + h));
+ img.clipR = Draw->Rect((img.clipR.min.x - deltax, img.clipR.min.y - deltay), (img.clipR.max.x - deltax, img.clipR.max.y - deltay));
+ img.lorigin = r.rmin;
+
+ O => # set compositing op
+ drawop = r.op;
+
+ p => # draw polygon
+ dirty(r.dstid, 1);
+ drawop = Draw->SoverD;
+
+ r => # read pixels
+ img := images.lookup(r.id);
+ bpl := bytesperline(r.R, img.chans);
+ readn = bpl * (r.R.max.y - r.R.min.y);
+
+ s => # draw text
+ dirty(r.dstid, 1);
+ drawop = Draw->SoverD;
+
+ x => # draw text with bg
+ dirty(r.dstid, 1);
+ drawop = Draw->SoverD;
+
+ t => # adjust window z order
+ if (r.ids != nil) {
+ img := images.lookup(r.ids[0]);
+ scr := screens.lookup(img.screenid);
+ scr.setz(r.ids, r.top);
+ }
+
+ y => # write pixels
+ dirty(r.id, 1);
+ }
+
+ if (readn) {
+ rdata := array [readn] of byte;
+ if (sys->read(drawfd, rdata, readn) == readn)
+ readdata = rdata;
+ }
+
+ for (vs := viewers; vs != nil; vs = tl vs) {
+ v := hd vs;
+ v.process(req);
+ }
+
+ # actions that must only be done after sending to Viewers
+ pick r := req {
+ f => # free image
+ img := images.lookup(r.id);
+ if (img.screenid != 0) {
+ scr := screens.lookup(img.screenid);
+ scr.delwin(img.id);
+ }
+ images.del(r.id);
+
+ F => # free screen
+ scr := screens.lookup(r.id);
+ for (i := 0; i < len scr.windows; i++) {
+ img := images.lookup(scr.windows[i]);
+ img.screenid = 0;
+ }
+ screens.del(r.id);
+
+ i => # convert image to font
+ img := images.lookup(r.fontid);
+ font := ref Font;
+ font.ascent = r.ascent;
+ font.chars = array[r.nchars] of ref Fontchar;
+ img.font = font;
+
+ l => # load a char into font
+ img := images.lookup(r.fontid);
+ font := img.font;
+ fc := ref Fontchar(r.srcid, r.R, r.P, r.left, r.width);
+ font.chars[r.index] = fc;
+ }
+ }
+ return (offset, nil);
+}
+
+coordslen(data : array of byte, ix, n : int) : int
+{
+ start := ix;
+ dlen := len data;
+ if (ix == dlen)
+ return -1;
+ while (ix < dlen && n) {
+ n--;
+ if ((int data[ix++]) & 16r80)
+ ix += 2;
+ }
+ if (n)
+ return -1;
+ return ix - start;
+}
+
+
+imglen(i : ref Image, data : array of byte, ix : int, r : Draw->Rect, comp : int) : int
+{
+ bpl := bytesperline(r, i.chans);
+ if (!comp)
+ return (r.max.y - r.min.y) * bpl;
+ y := r.min.y;
+ lineix := byteaddr(i, r.min);
+ elineix := lineix+bpl;
+ start := ix;
+ eix := len data;
+ for (;;) {
+ if (lineix == elineix) {
+ if (++y == r.max.y)
+ break;
+ lineix = byteaddr(i, Point(r.min.x, y));
+ elineix = lineix+bpl;
+ }
+ if (ix == eix) # buffer too small
+ return -1;
+ c := int data[ix++];
+ if (c >= 128) {
+ for (cnt := c-128+1; cnt != 0; --cnt) {
+ if (ix == eix) # buffer too small
+ return -1;
+ if (lineix == elineix) # phase error
+ return -1;
+ lineix++;
+ ix++;
+ }
+ } else {
+ if (ix == eix) # short buffer
+ return -1;
+ ix++;
+ for (cnt := (c >> 2)+3; cnt != 0; --cnt) {
+ if (lineix == elineix) # phase error
+ return -1;
+ lineix++;
+ }
+ }
+ }
+ return ix-start;
+}
+
+byteaddr(i: ref Image, p: Point): int
+{
+ x := p.x - i.lorigin.x;
+ y := p.y - i.lorigin.y;
+ bits := i.chans.depth();
+ if (bits == 0)
+ # invalid chans
+ return 0;
+ return (y*i.bwidth)+(x<<3)/bits;
+}
+
+bytesperline(r: Draw->Rect, chans: Draw->Chans): int
+{
+ d := chans.depth();
+ l, t: int;
+
+ if(r.min.x >= 0){
+ l = (r.max.x*d+8-1)/8;
+ l -= (r.min.x*d)/8;
+ }else{ # make positive before divide
+ t = (-r.min.x*d+8-1)/8;
+ l = t+(r.max.x*d+8-1)/8;
+ }
+ return l;
+}
+
+get1(data : array of byte, ix : int) : int
+{
+ return int data[ix];
+}
+
+put1(data : array of byte, ix, val : int)
+{
+ data[ix] = byte val;
+}
+
+get2(data : array of byte, ix : int) : int
+{
+ return int data[ix] | ((int data[ix+1]) << 8);
+}
+
+put2(data : array of byte, ix, val : int)
+{
+ data[ix] = byte val;
+ data[ix+1] = byte (val >> 8);
+}
+
+get4(data : array of byte, ix : int) : int
+{
+ return int data[ix] | ((int data[ix+1]) << 8) | ((int data[ix+2]) << 16) | ((int data[ix+3]) << 24);
+}
+
+put4(data : array of byte, ix, val : int)
+{
+ data[ix] = byte val;
+ data[ix+1] = byte (val >> 8);
+ data[ix+2] = byte (val >> 16);
+ data[ix+3] = byte (val >> 24);
+}
+
+getP(data : array of byte, ix : int) : Draw->Point
+{
+ x := int data[ix] | ((int data[ix+1]) << 8) | ((int data[ix+2]) << 16) | ((int data[ix+3]) << 24);
+ ix += 4;
+ y := int data[ix] | ((int data[ix+1]) << 8) | ((int data[ix+2]) << 16) | ((int data[ix+3]) << 24);
+ return Draw->Point(x, y);
+}
+
+putP(data : array of byte, ix : int, P : Draw->Point)
+{
+ val := P.x;
+ data[ix] = byte val;
+ data[ix+1] = byte (val >> 8);
+ data[ix+2] = byte (val >> 16);
+ data[ix+3] = byte (val >> 24);
+ val = P.y;
+ ix += 4;
+ data[ix] = byte val;
+ data[ix+1] = byte (val >> 8);
+ data[ix+2] = byte (val >> 16);
+ data[ix+3] = byte (val >> 24);
+}
+
+getR(data : array of byte, ix : int) : Draw->Rect
+{
+ minx := int data[ix] | ((int data[ix+1]) << 8) | ((int data[ix+2]) << 16) | ((int data[ix+3]) << 24);
+ ix += 4;
+ miny := int data[ix] | ((int data[ix+1]) << 8) | ((int data[ix+2]) << 16) | ((int data[ix+3]) << 24);
+ ix += 4;
+ maxx := int data[ix] | ((int data[ix+1]) << 8) | ((int data[ix+2]) << 16) | ((int data[ix+3]) << 24);
+ ix += 4;
+ maxy := int data[ix] | ((int data[ix+1]) << 8) | ((int data[ix+2]) << 16) | ((int data[ix+3]) << 24);
+
+ return Draw->Rect(Draw->Point(minx, miny), Draw->Point(maxx, maxy));
+}
+
+putR(data : array of byte, ix : int , R : Draw->Rect)
+{
+ val := R.min.x;
+ data[ix] = byte val;
+ data[ix+1] = byte (val >> 8);
+ data[ix+2] = byte (val >> 16);
+ data[ix+3] = byte (val >> 24);
+ val = R.min.y;
+ ix += 4;
+ data[ix] = byte val;
+ data[ix+1] = byte (val >> 8);
+ data[ix+2] = byte (val >> 16);
+ data[ix+3] = byte (val >> 24);
+ val = R.max.x;
+ ix += 4;
+ data[ix] = byte val;
+ data[ix+1] = byte (val >> 8);
+ data[ix+2] = byte (val >> 16);
+ data[ix+3] = byte (val >> 24);
+ val = R.max.y;
+ ix += 4;
+ data[ix] = byte val;
+ data[ix+1] = byte (val >> 8);
+ data[ix+2] = byte (val >> 16);
+ data[ix+3] = byte (val >> 24);
+}
+
+dirty(id, v : int)
+{
+ img := images.lookup(id);
+ img.dirty = v;
+}
+
+Screen.setz(s : self ref Screen, z : array of int, top : int)
+{
+ old := s.windows;
+ nw := array [len old] of int;
+ # use a dummy idmap to ensure uniqueness;
+ ids := Idmap.new();
+ ix := 0;
+ if (top) {
+ for (i := 0; i < len z; i++) {
+ if (ids.lookup(z[i]) == -1) {
+ ids.add(z[i], 0);
+ nw[ix++] = z[i];
+ }
+ }
+ }
+ for (i := 0; i < len old; i++) {
+ if (ids.lookup(old[i]) == -1) {
+ ids.add(old[i], 0);
+ nw[ix++] = old[i];
+ }
+ }
+ if (!top) {
+ for (i = 0; i < len z; i++) {
+ if (ids.lookup(z[i]) == -1) {
+ ids.add(z[i], 0);
+ nw[ix++] = z[i];
+ }
+ }
+ }
+ s.windows = nw;
+}
+
+Screen.addwin(s : self ref Screen, wid : int)
+{
+ nw := array [len s.windows + 1] of int;
+ nw[0] = wid;
+ nw[1:] = s.windows;
+ s.windows = nw;
+}
+
+Screen.delwin(s : self ref Screen, wid : int)
+{
+ if (len s.windows == 1) {
+ # assert s.windows[0] == wid
+ s.windows = nil;
+ return;
+ }
+ nw := array [len s.windows - 1] of int;
+ ix := 0;
+ for (i := 0; i < len s.windows; i++) {
+ if (s.windows[i] == wid)
+ continue;
+ nw[ix++] = s.windows[i];
+ }
+ s.windows = nw;
+}
+
+Idmap.new() : ref Idmap
+{
+ m := ref Idmap;
+ m.buckets = array[256] of ref Idpair;
+ return m;
+}
+
+Idmap.add(m : self ref Idmap, key, val : int)
+{
+ h := key & 16rff;
+ m.buckets[h] = ref Idpair (key, val, m.buckets[h]);
+}
+
+Idmap.del(m : self ref Idmap, key : int)
+{
+ h := key &16rff;
+ prev := m.buckets[h];
+ if (prev == nil)
+ return;
+ if (prev.key == key) {
+ m.buckets[h] = m.buckets[h].next;
+ return;
+ }
+ for (idp := prev.next; idp != nil; idp = idp.next) {
+ if (idp.key == key)
+ break;
+ prev = idp;
+ }
+ if (idp != nil)
+ prev.next = idp.next;
+}
+
+Idmap.lookup(m :self ref Idmap, key : int) : int
+{
+ h := key &16rff;
+ for (idp := m.buckets[h]; idp != nil; idp = idp.next) {
+ if (idp.key == key)
+ return idp.val;
+ }
+ return -1;
+}
+
+Imageset.new() : ref Imageset
+{
+ s := ref Imageset;
+ s.images = array [32] of ref Image;
+ s.ixmap = Idmap.new();
+ for (i := 0; i < len s.images; i++)
+ s.freelist = i :: s.freelist;
+ return s;
+}
+
+Imageset.add(s: self ref Imageset, id: int, img: ref Image)
+{
+ if (s.freelist == nil) {
+ n := 2 * len s.images;
+ ni := array [n] of ref Image;
+ ni[:] = s.images;
+ for (i := len s.images; i < n; i++)
+ s.freelist = i :: s.freelist;
+ s.images = ni;
+ }
+ ix := hd s.freelist;
+ s.freelist = tl s.freelist;
+ s.images[ix] = img;
+ s.ixmap.add(id, ix);
+ img.refc++;
+}
+
+Imageset.del(s: self ref Imageset, id: int)
+{
+ ix := s.ixmap.lookup(id);
+ if (ix == -1)
+ return;
+ img := s.images[ix];
+ if (img != nil)
+ img.refc--;
+ s.images[ix] = nil;
+ s.freelist = ix :: s.freelist;
+ s.ixmap.del(id);
+}
+
+Imageset.lookup(s : self ref Imageset, id : int ) : ref Image
+{
+ ix := s.ixmap.lookup(id);
+ if (ix == -1)
+ return nil;
+ return s.images[ix];
+}
+
+Imageset.findname(s: self ref Imageset, name: string): ref Image
+{
+ for (ix := 0; ix < len s.images; ix++) {
+ img := s.images[ix];
+ if (img != nil && img.name == name)
+ return img;
+ }
+ return nil;
+}
+
+Screenset.new() : ref Screenset
+{
+ s := ref Screenset;
+ s.screens = array [32] of ref Screen;
+ s.ixmap = Idmap.new();
+ for (i := 0; i < len s.screens; i++)
+ s.freelist = i :: s.freelist;
+ return s;
+}
+
+Screenset.add(s : self ref Screenset, scr : ref Screen)
+{
+ if (s.freelist == nil) {
+ n := 2 * len s.screens;
+ ns := array [n] of ref Screen;
+ ns[:] = s.screens;
+ for (i := len s.screens; i < n; i++)
+ s.freelist = i :: s.freelist;
+ s.screens = ns;
+ }
+ ix := hd s.freelist;
+ s.freelist = tl s.freelist;
+ s.screens[ix] = scr;
+ s.ixmap.add(scr.id, ix);
+}
+
+Screenset.del(s : self ref Screenset, id : int)
+{
+ ix := s.ixmap.lookup(id);
+ if (ix == -1)
+ return;
+ s.screens[ix] = nil;
+ s.freelist = ix :: s.freelist;
+ s.ixmap.del(id);
+}
+
+Screenset.lookup(s : self ref Screenset, id : int ) : ref Screen
+{
+ ix := s.ixmap.lookup(id);
+ if (ix == -1)
+ return nil;
+ return s.screens[ix];
+}
+
+
+Viewer : adt {
+ imgmap: ref Idmap;
+ scrmap: ref Idmap;
+ chanmap: ref Idmap; # maps to 1 for images that require chan conversion
+
+ imageid: int;
+ screenid: int;
+ whiteid: int;
+ hungup: int;
+ dchans: Draw->Chans; # chans.desc of remote display img
+
+ # temporary image for chan conversion
+ tmpid: int;
+ tmpR: Draw->Rect;
+
+ output: chan of (array of byte, chan of string);
+
+ new: fn(fd: ref Sys->FD, pubscr: int, chans: Draw->Chans): string;
+ process: fn(v: self ref Viewer, req: ref Drawreq);
+ getimg: fn(v: self ref Viewer, id: int): int;
+ getscr: fn(v: self ref Viewer, id, win: int): (int, int);
+ copyimg: fn(v: self ref Viewer, img: ref Image, id: int);
+ chanconv: fn(v: self ref Viewer, img: ref Image, id: int, r: Rect, ymsg: array of byte);
+};
+
+vwriter(fd : ref Sys->FD, datac : chan of array of byte, nc : chan of string)
+{
+ for (;;) {
+ data := <- datac;
+ if (data == nil)
+ return;
+ n := sys->write(fd, data, len data);
+ if (n != len data) {
+# sys->print("[%c]: %r\n", int data[0]);
+# sys->print("[%c] datalen %d got %d error: %r\n", int data[0], len data, n);
+ nc <-= sys->sprint("%r");
+ } else {
+# sys->print("[%c]", int data[0]);
+ nc <-= nil;
+ }
+ }
+}
+
+vbmsg : adt {
+ data : array of byte;
+ rc : chan of string;
+ next : cyclic ref vbmsg;
+};
+
+vbuffer(v : ref Viewer, fd : ref Sys->FD)
+{
+ ioc := v.output;
+ datac := chan of array of byte;
+ errc := chan of string;
+ spawn vwriter(fd, datac, errc);
+ fd = nil;
+
+ msghd : ref vbmsg;
+ msgtl : ref vbmsg;
+
+Loop:
+ for (;;) alt {
+ (data, rc) := <- ioc =>
+ if (data == nil)
+ break Loop;
+ if (msgtl != nil) {
+ if (msgtl != msghd && msgtl.rc == nil && (len msgtl.data + len data) <= Sys->ATOMICIO) {
+ ndata := array [len msgtl.data + len data] of byte;
+ ndata[:] = msgtl.data;
+ ndata[len msgtl.data:] = data;
+ msgtl.data = ndata;
+ msgtl.rc = rc;
+ } else {
+ msgtl.next = ref vbmsg (data, rc, nil);
+ msgtl = msgtl.next;
+ }
+ } else {
+ msghd = ref vbmsg (data, rc, nil);
+ msgtl = msghd;
+ datac <-= data;
+ }
+ err := <- errc =>
+ if (msghd.rc != nil)
+ msghd.rc <- = err;
+ msghd = msghd.next;
+ if (msghd != nil)
+ datac <-= msghd.data;
+ else
+ msgtl = nil;
+ if (err == Ehungup) {
+ nhangups++;
+ v.hungup = 1;
+ }
+ }
+ # shutdown vwriter (may be blocked sending on errc)
+ for (;;) alt {
+ <- errc =>
+ ;
+ datac <- = nil =>
+ return;
+ }
+}
+
+Viewer.new(fd: ref Sys->FD, pubscr: int, chans: Draw->Chans): string
+{
+ v := ref Viewer;
+ v.output = chan of (array of byte, chan of string);
+ spawn vbuffer(v, fd);
+
+ v.imgmap = Idmap.new();
+ v.scrmap = Idmap.new();
+ v.chanmap = Idmap.new();
+ v.imageid = 0;
+ v.screenid = pubscr;
+ v.hungup = 0;
+ v.dchans = chans;
+ v.tmpid = 0;
+ v.tmpR = Rect((0,0), (0,0));
+
+#D := array[1+1] of byte;
+#D[0] = byte 'D';
+#D[1] = byte 1;
+#v.output <-= (D, nil);
+
+ reply := chan of string;
+ # import remote public screen into our remote draw client
+ S := array [1+4+4] of byte;
+ S[0] = byte 'S';
+ put4(S, OPS_id, pubscr);
+ put4(S, OPS_chans, chans.desc);
+ v.output <-= (S, reply);
+ err := <- reply;
+ if (err != nil) {
+ v.output <-= (nil, nil);
+ return err;
+ }
+
+ # create remote window
+ dispid := ++v.imageid;
+ b := array [1+4+4+1+4+1+(4*4)+(4*4)+4] of byte;
+ b[0] = byte 'b';
+ put4(b, OPb_id, dispid);
+ put4(b, OPb_screenid, pubscr);
+ put1(b, OPb_refresh, 0);
+ put4(b, OPb_chans, chans.desc);
+ put1(b, OPb_repl, 0);
+ putR(b, OPb_R, drawR);
+ putR(b, OPb_clipR, drawR);
+ put4(b, OPb_rrggbbaa, Draw->White);
+ v.output <-= (b, reply);
+ err = <- reply;
+ if (err != nil) {
+ v.output <-= (nil, nil);
+ return err;
+ }
+
+ # map local display image id to remote window image id
+ v.imgmap.add(0, dispid);
+ if (!drawchans.eq(chans))
+ # writepixels on this image must be chan converted
+ v.chanmap.add(0, 1);
+
+ # create 'white' repl image for use as mask
+ v.whiteid = ++v.imageid;
+ put4(b, OPb_id, v.whiteid);
+ put4(b, OPb_screenid, 0);
+ put1(b, OPb_refresh, 0);
+ put4(b, OPb_chans, (Draw->RGBA32).desc);
+ put1(b, OPb_repl, 1);
+ putR(b, OPb_R, Rect((0,0), (1,1)));
+ putR(b, OPb_clipR, Rect((-16r3FFFFFFF, -16r3FFFFFFF), (16r3FFFFFFF, 16r3FFFFFFF)));
+ put4(b, OPb_rrggbbaa, Draw->White);
+ v.output <-= (b, reply);
+ err = <- reply;
+ if (err != nil) {
+ v.output <-= (nil, nil);
+ return err;
+ }
+
+ img := images.lookup(0);
+ key := chan of int;
+ drawlock <- = key;
+ v.copyimg(img, dispid);
+
+ O := array [1+1] of byte;
+ O[0] = byte 'O';
+ O[1] = byte drawop;
+ v.output <-= (O, nil);
+
+ flush := array [1] of byte;
+ flush[0] = byte 'v';
+ v.output <- = (flush, nil);
+ viewers = v :: viewers;
+ key <-= 1;
+ return nil;
+}
+
+Viewer.process(v : self ref Viewer, req : ref Drawreq)
+{
+ data := req.data;
+ pick r := req {
+ b => # allocate image
+ imgid := ++v.imageid;
+ if (r.screenid != 0) {
+ (scrid, mapchans) := v.getscr(r.screenid, 0);
+ put4(data, OPb_screenid, scrid);
+ if (mapchans) {
+ put4(data, OPb_chans, v.dchans.desc);
+ v.chanmap.add(r.id, 1);
+ }
+ }
+ v.imgmap.add(r.id, imgid);
+ put4(data, OPb_id, imgid);
+
+ A => # allocate screen
+ imgid := v.getimg(r.imageid);
+ put4(data, OPA_fillid, v.getimg(r.fillid));
+ put4(data, OPA_imageid, imgid);
+ reply := chan of string;
+ for (i := 0; i < 25; i++) {
+ put4(data, OPA_id, ++v.screenid);
+ v.output <-= (data, reply);
+ if (<-reply == nil) {
+ v.scrmap.add(r.id, v.screenid);
+ return;
+ }
+ }
+ return;
+
+ c => # set clipr and repl
+ put4(data, OPc_dstid, v.getimg(r.dstid));
+
+ d => # general draw op
+ dstid := v.imgmap.lookup(r.dstid);
+ if (dstid == -1) {
+ # don't do draw op as getimg() will do a writepixels
+ v.getimg(r.dstid);
+ return;
+ }
+ put4(data, OPd_maskid, v.getimg(r.maskid));
+ put4(data, OPd_srcid, v.getimg(r.srcid));
+ put4(data, OPd_dstid, dstid);
+
+ e => # draw ellipse
+ dstid := v.imgmap.lookup(r.dstid);
+ if (dstid == -1) {
+ # don't do draw op as getimg() will do a writepixels
+ v.getimg(r.dstid);
+ return;
+ }
+ put4(data, OPe_srcid, v.getimg(r.srcid));
+ put4(data, OPe_dstid, dstid);
+
+ f => # free image
+ id := v.imgmap.lookup(r.img.id);
+ if (id == -1)
+ # Viewer has never seen this image - ignore
+ return;
+ v.imgmap.del(r.id);
+ # Viewers alias named images - only delete if last reference
+ if (r.img.refc > 1)
+ return;
+ v.chanmap.del(r.img.id);
+ put4(data, OPf_id, id);
+
+ F => # free screen
+ id := v.scrmap.lookup(r.id);
+ scr := screens.lookup(r.id);
+ # image and fill are free'd separately
+ #v.imgmap.del(scr.imageid);
+ #v.imgmap.del(scr.fillid);
+ if (id == -1)
+ return;
+ put4(data, OPF_id, id);
+
+ i => # convert image to font
+ put4(data, OPi_fontid, v.getimg(r.fontid));
+
+ l => # load a char into font
+ put4(data, OPl_srcid, v.getimg(r.srcid));
+ put4(data, OPl_fontid, v.getimg(r.fontid));
+
+ L => # draw line
+ dstid := v.imgmap.lookup(r.dstid);
+ if (dstid == -1) {
+ # don't do draw op as getimg() will do a writepixels
+ v.getimg(r.dstid);
+ return;
+ }
+ put4(data, OPL_srcid, v.getimg(r.srcid));
+ put4(data, OPL_dstid, dstid);
+
+# n => # attach to named image
+# N => # name
+# Handled by id remapping to avoid clashes in namespace of remote viewers.
+# If it is a name we know then the id is remapped within the images Imageset
+# Otherwise, there is nothing we can do other than ignore all ops related to the id.
+
+ o => # set image origins
+ id := v.imgmap.lookup(r.id);
+ if (id == -1)
+ # Viewer has never seen this image - ignore
+ return;
+ put4(data, OPo_id, id);
+
+ O => # set next compositing op
+ ;
+
+ p => # draw polygon
+ dstid := v.imgmap.lookup(r.dstid);
+ if (dstid == -1) {
+ # don't do draw op as getimg() will do a writepixels
+ v.getimg(r.dstid);
+ return;
+ }
+ put4(data, OPp_srcid, v.getimg(r.srcid));
+ put4(data, OPp_dstid, dstid);
+
+ s => # draw text
+ dstid := v.imgmap.lookup(r.dstid);
+ if (dstid == -1) {
+ # don't do draw op as getimg() will do a writepixels
+ v.getimg(r.dstid);
+ return;
+ }
+ put4(data, OPs_fontid, v.getimg(r.fontid));
+ put4(data, OPs_srcid, v.getimg(r.srcid));
+ put4(data, OPs_dstid, dstid);
+
+ x => # draw text with bg
+ dstid := v.imgmap.lookup(r.dstid);
+ if (dstid == -1) {
+ # don't do draw op as getimg() will do a writepixels
+ v.getimg(r.dstid);
+ return;
+ }
+ put4(data, OPx_fontid, v.getimg(r.fontid));
+ put4(data, OPx_srcid, v.getimg(r.srcid));
+ put4(data, OPx_bgid, v.getimg(r.bgid));
+ put4(data, OPx_dstid, dstid);
+
+ t => # adjust window z order
+ for (i := 0; i < len r.ids; i++)
+ put4(data, OPt_id + 4*i, v.getimg(r.ids[i]));
+
+ v => # flush updates to display
+ ;
+
+ y => # write pixels
+ id := v.imgmap.lookup(r.id);
+ if (id == -1) {
+ # don't do draw op as getimg() will do a writepixels
+ v.getimg(r.id);
+ return;
+ }
+ if (!drawchans.eq(v.dchans) && v.chanmap.lookup(r.id) != -1) {
+ # chans clash
+ img := images.lookup(r.id);
+ # copy data as other Viewers may alter contents
+ copy := (array [len data] of byte)[:] = data;
+ v.chanconv(img, id, r.R, copy);
+ return;
+ }
+ put4(data, OPy_id, id);
+
+ * =>
+ return;
+ }
+ # send out a copy of the data as other Viewers may alter contents
+ copy := array [len data] of byte;
+ copy[:] = data;
+ v.output <-= (copy, nil);
+}
+
+Viewer.getimg(v: self ref Viewer, localid: int) : int
+{
+ remid := v.imgmap.lookup(localid);
+ if (remid != -1)
+ return remid;
+
+ img := images.lookup(localid);
+ if (img.id != localid) {
+ # attached via name, see if we have the aliased image
+ remid = v.imgmap.lookup(img.id);
+ if (remid != -1) {
+ # we have it, add mapping to save us this trouble next time
+ v.imgmap.add(localid, remid);
+ return remid;
+ }
+ }
+ # is the image a window?
+ scrid := 0;
+ mapchans := 0;
+ if (img.screenid != 0)
+ (scrid, mapchans) = v.getscr(img.screenid, img.id);
+
+ vid := ++v.imageid;
+ # create the image
+ # note: clipr for image creation has to be based on screen co-ords
+ clipR := img.clipR.subpt(img.lorigin);
+ clipR = clipR.addpt(img.R.min);
+ b := array [1+4+4+1+4+1+(4*4)+(4*4)+4] of byte;
+ b[0] = byte 'b';
+ put4(b, OPb_id, vid);
+ put4(b, OPb_screenid, scrid);
+ put1(b, OPb_refresh, 0);
+ if (mapchans)
+ put4(b, OPb_chans, v.dchans.desc);
+ else
+ put4(b, OPb_chans, img.chans.desc);
+ put1(b, OPb_repl, img.repl);
+ putR(b, OPb_R, img.R);
+ putR(b, OPb_clipR, clipR);
+ put4(b, OPb_rrggbbaa, img.rrggbbaa);
+ v.output <-= (b, nil);
+
+ v.imgmap.add(img.id, vid);
+ if (mapchans)
+ v.chanmap.add(img.id, 1);
+
+ # set the origin
+ if (img.lorigin.x != img.R.min.x || img.lorigin.y != img.R.min.y) {
+ o := array [1+4+(2*4)+(2*4)] of byte;
+ o[0] = byte 'o';
+ put4(o, OPo_id, vid);
+ putP(o, OPo_rmin, img.lorigin);
+ putP(o, OPo_screenrmin, img.R.min);
+ v.output <-= (o, nil);
+ }
+
+ # is the image a font?
+ if (img.font != nil) {
+ f := img.font;
+ i := array [1+4+4+1] of byte;
+ i[0] = byte 'i';
+ put4(i, OPi_fontid, vid);
+ put4(i, OPi_nchars, len f.chars);
+ put1(i, OPi_ascent, f.ascent);
+ v.output <-= (i, nil);
+
+ for (index := 0; index < len f.chars; index++) {
+ ch := f.chars[index];
+ if (ch == nil)
+ continue;
+ l := array [1+4+4+2+(4*4)+(2*4)+1+1] of byte;
+ l[0] = byte 'l';
+ put4(l, OPl_fontid, vid);
+ put4(l, OPl_srcid, v.getimg(ch.srcid));
+ put2(l, OPl_index, index);
+ putR(l, OPl_R, ch.R);
+ putP(l, OPl_P, ch.P);
+ put1(l, OPl_left, ch.left);
+ put1(l, OPl_width, ch.width);
+ v.output <-= (l, nil);
+ }
+ }
+
+ # if 'dirty' then writepixels
+ if (img.dirty)
+ v.copyimg(img, vid);
+
+ return vid;
+}
+
+Viewer.copyimg(v : self ref Viewer, img : ref Image, id : int)
+{
+ dx := img.R.max.x - img.R.min.x;
+ dy := img.R.max.y - img.R.min.y;
+ srcR := Rect (img.lorigin, (img.lorigin.x + dx, img.lorigin.y + dy));
+ bpl := bytesperline(srcR, img.chans);
+ rlen : con 1+4+(4*4);
+ ystep := (Sys->ATOMICIO - rlen)/ bpl;
+ minx := srcR.min.x;
+ maxx := srcR.max.x;
+ maxy := srcR.max.y;
+
+ chanconv := 0;
+ if (!drawchans.eq(v.dchans) && v.chanmap.lookup(img.id) != -1)
+ chanconv = 1;
+
+ for (y := img.lorigin.y; y < maxy; y += ystep) {
+ if (y + ystep > maxy)
+ ystep = (maxy - y);
+ R := Draw->Rect((minx, y), (maxx, y+ystep));
+ r := array [rlen] of byte;
+ r[0] = byte 'r';
+ put4(r, OPr_id, img.id);
+ putR(r, OPr_R, R);
+ if (sys->write(drawfd, r, len r) != len r)
+ break;
+
+ nb := bpl * ystep;
+ ymsg := array [1+4+(4*4)+nb] of byte;
+ ymsg[0] = byte 'y';
+# put4(ymsg, OPy_id, id);
+ putR(ymsg, OPy_R, R);
+ n := sys->read(drawfd, ymsg[OPy_data:], nb);
+ if (n != nb)
+ break;
+ if (chanconv)
+ v.chanconv(img, id, R, ymsg);
+ else {
+ put4(ymsg, OPy_id, id);
+ v.output <-= (ymsg, nil);
+ }
+ }
+}
+
+Viewer.chanconv(v: self ref Viewer, img: ref Image, id: int, r: Rect, ymsg: array of byte)
+{
+ # check origin matches and enough space in conversion image
+ if (!(img.lorigin.eq(v.tmpR.min) && r.inrect(v.tmpR))) {
+ # create new tmp image
+ if (v.tmpid != 0) {
+ f := array [1+4] of byte;
+ f[0] = byte 'f';
+ put4(f, OPf_id, v.tmpid);
+ v.output <-= (f, nil);
+ }
+ v.tmpR = Rect((0,0), (img.R.dx(), img.R.dy())).addpt(img.lorigin);
+ v.tmpid = ++v.imageid;
+ b := array [1+4+4+1+4+1+(4*4)+(4*4)+4] of byte;
+ b[0] = byte 'b';
+ put4(b, OPb_id, v.tmpid);
+ put4(b, OPb_screenid, 0);
+ put1(b, OPb_refresh, 0);
+ put4(b, OPb_chans, drawchans.desc);
+ put1(b, OPb_repl, 0);
+ putR(b, OPb_R, v.tmpR);
+ putR(b, OPb_clipR, v.tmpR);
+ put4(b, OPb_rrggbbaa, Draw->Nofill);
+ v.output <-= (b, nil);
+ }
+ # writepixels to conversion image
+ put4(ymsg, OPy_id, v.tmpid);
+ v.output <-= (ymsg, nil);
+
+ # ensure that drawop is Draw->S
+ if (drawop != Draw->S) {
+ O := array [1+1] of byte;
+ O[0] = byte 'O';
+ put1(O, OPO_op, Draw->S);
+ v.output <-= (O, nil);
+ }
+ # blit across to real target
+ d := array [1+4+4+4+(4*4)+(2*4)+(2*4)] of byte;
+ d[0] = byte 'd';
+ put4(d, OPd_dstid, id);
+ put4(d, OPd_srcid, v.tmpid);
+ put4(d, OPd_maskid, v.whiteid);
+ putR(d, OPd_R, r);
+ putP(d, OPd_P0, r.min);
+ putP(d, OPd_P1, r.min);
+ v.output <-= (d, nil);
+
+ # restore drawop if necessary
+ if (drawop != Draw->S) {
+ O := array [1+1] of byte;
+ O[0] = byte 'O';
+ put1(O, OPO_op, drawop);
+ v.output <-= (O, nil);
+ }
+}
+
+# returns (rid, map)
+# rid == remote screen id
+# map indicates that chan mapping is required for windows on this screen
+
+Viewer.getscr(v : self ref Viewer, localid, winid : int) : (int, int)
+{
+ remid := v.scrmap.lookup(localid);
+ if (remid != -1) {
+ if (drawchans.eq(v.dchans))
+ return (remid, 0);
+ scr := screens.lookup(localid);
+ if (v.chanmap.lookup(scr.imageid) == -1)
+ return (remid, 0);
+ return (remid, 1);
+ }
+
+ scr := screens.lookup(localid);
+ imgid := v.getimg(scr.imageid);
+ fillid := v.getimg(scr.fillid);
+ A := array [1+4+4+4+1] of byte;
+ A[0] = byte 'A';
+ put4(A, OPA_imageid, imgid);
+ put4(A, OPA_fillid, fillid);
+ put1(A, OPA_public, 0);
+
+ reply := chan of string;
+ for (i := 0; i < 25; i++) {
+ put4(A, OPA_id, ++v.screenid);
+ v.output <-= (A, reply);
+ if (<-reply != nil)
+ continue;
+ v.scrmap.add(localid, v.screenid);
+ break;
+ }
+ # if i == 25 then we have a problem
+ # ...
+ if (i == 25) {
+# sys->print("failed to create remote screen\n");
+ return (0, 0);
+ }
+
+ # pre-construct the windows on this screen
+ for (ix := len scr.windows -1; ix >=0; ix--)
+ if (scr.windows[ix] != winid)
+ v.getimg(scr.windows[ix]);
+
+ if (drawchans.eq(v.dchans))
+ return (v.screenid, 0);
+ if (v.chanmap.lookup(scr.imageid) == -1)
+ return (v.screenid, 0);
+ return (v.screenid, 1);
+}
diff --git a/appl/wm/drawmux/drawmux.m b/appl/wm/drawmux/drawmux.m
new file mode 100644
index 00000000..cf641207
--- /dev/null
+++ b/appl/wm/drawmux/drawmux.m
@@ -0,0 +1,6 @@
+Drawmux: module {
+ PATH: con "/dis/lib/drawmux.dis";
+
+ init: fn(): (string, ref Draw->Display);
+ newviewer: fn(fd: ref Sys->FD);
+};
diff --git a/appl/wm/drawmux/drawoffs.m b/appl/wm/drawmux/drawoffs.m
new file mode 100644
index 00000000..ce5a28a2
--- /dev/null
+++ b/appl/wm/drawmux/drawoffs.m
@@ -0,0 +1,185 @@
+# allocate image (old)
+#OPa_id : con 1;
+#OPa_screenid : con 5;
+#OPa_refresh : con 9;
+#OPa_ldepth : con 10;
+#OPa_repl : con 12;
+#OPa_R : con 13;
+#OPa_clipR : con 29;
+#OPa_value : con 45;
+
+# allocate image (new)
+OPb_id : con 1;
+OPb_screenid : con 5;
+OPb_refresh : con 9;
+OPb_chans : con 10;
+OPb_repl : con 14;
+OPb_R : con 15;
+OPb_clipR : con 31;
+OPb_rrggbbaa : con 47;
+
+# allocate screen
+OPA_id : con 1;
+OPA_imageid : con 5;
+OPA_fillid : con 9;
+OPA_public : con 13;
+
+# set repl & clipr
+OPc_dstid : con 1;
+OPc_repl : con 5;
+OPc_clipR : con 6;
+
+# set cursor image and hotspot
+#OPC_id : con 1;
+#OPC_hotspot : con 5;
+
+# the primitive draw op
+OPd_dstid : con 1;
+OPd_srcid : con 5;
+OPd_maskid : con 9;
+OPd_R : con 13;
+OPd_P0 : con 29;
+OPd_P1 : con 37;
+
+# enable debug messages
+OPD_val : con 1;
+
+# ellipse
+OPe_dstid : con 1;
+OPe_srcid : con 5;
+OPe_center : con 9;
+OPe_a : con 17;
+OPe_b : con 21;
+OPe_thick : con 25;
+OPe_sp : con 29;
+OPe_alpha : con 37;
+OPe_phi : con 41;
+
+# filled ellipse
+OPE_dstid : con 1;
+OPE_srcid : con 5;
+OPE_center : con 9;
+OPE_a : con 17;
+OPE_b : con 21;
+OPE_thick : con 25;
+OPE_sp : con 29;
+OPE_alpha : con 37;
+OPE_phi : con 41;
+
+# free image
+OPf_id : con 1;
+
+# free screen
+OPF_id : con 1;
+
+# init font
+OPi_fontid : con 1;
+OPi_nchars : con 5;
+OPi_ascent : con 9;
+
+# load font char
+OPl_fontid : con 1;
+OPl_srcid : con 5;
+OPl_index : con 9;
+OPl_R : con 11;
+OPl_P : con 27;
+OPl_left : con 35;
+OPl_width : con 36;
+
+# line
+OPL_dstid : con 1;
+OPL_P0 : con 5;
+OPL_P1 : con 13;
+OPL_end0 : con 21;
+OPL_end1 : con 25;
+OPL_radius : con 29;
+OPL_srcid : con 33;
+OPL_sp : con 37;
+
+# attach to named image
+OPn_dstid : con 1;
+OPn_j : con 5;
+OPn_name : con 6;
+
+# name image
+OPN_dstid : con 1;
+OPN_in : con 5;
+OPN_j : con 6;
+OPN_name : con 7;
+
+# set window origins
+OPo_id : con 1;
+OPo_rmin : con 5;
+OPo_screenrmin : con 13;
+
+# set next compositing operator
+OPO_op : con 1;
+
+# polygon
+OPp_dstid : con 1;
+OPp_n : con 5;
+OPp_end0 : con 7;
+OPp_end1 : con 11;
+OPp_radius : con 15;
+OPp_srcid : con 19;
+OPp_sp : con 23;
+OPp_P0 : con 31;
+OPp_dp : con 39;
+
+# filled polygon
+OPP_dstid : con 1;
+OPP_n : con 5;
+OPP_wind : con 7;
+OPP_ignore : con 11;
+OPP_srcid : con 19;
+OPP_sp : con 23;
+OPP_P0 : con 31;
+OPP_dp : con 39;
+
+# read
+OPr_id : con 1;
+OPr_R : con 5;
+
+# string
+OPs_dstid : con 1;
+OPs_srcid : con 5;
+OPs_fontid : con 9;
+OPs_P : con 13;
+OPs_clipR : con 21;
+OPs_sp : con 37;
+OPs_ni : con 45;
+OPs_index : con 47;
+
+# stringbg
+OPx_dstid : con 1;
+OPx_srcid : con 5;
+OPx_fontid : con 9;
+OPx_P : con 13;
+OPx_clipR : con 21;
+OPx_sp : con 37;
+OPx_ni : con 45;
+OPx_bgid : con 47;
+OPx_bgpt : con 51;
+OPx_index : con 59;
+
+# attach to public screen
+OPS_id : con 1;
+OPS_chans : con 5;
+
+# visible
+# top or bottom windows
+OPt_top : con 1;
+OPt_nw : con 2;
+OPt_id : con 4;
+
+#OPv no fields
+
+# write
+OPy_id : con 1;
+OPy_R : con 5;
+OPy_data : con 21;
+
+# write compressed
+OPY_id : con 1;
+OPY_R : con 5;
+OPY_data : con 21;
diff --git a/appl/wm/drawmux/mkfile b/appl/wm/drawmux/mkfile
new file mode 100644
index 00000000..f4c8d7ec
--- /dev/null
+++ b/appl/wm/drawmux/mkfile
@@ -0,0 +1,37 @@
+<../../../mkconfig
+
+TARG=\
+ dmview.dis\
+ dmwm.dis\
+
+LIBTARG=\
+ drawmux.dis\
+
+MODULES=\
+ drawmux.m\
+ drawoffs.m\
+
+SYSMODULES=\
+ arg.m\
+ draw.m\
+ sh.m\
+ sys.m\
+ tk.m\
+ wmlib.m\
+
+DISBIN=$ROOT/dis/wm
+DISLIB=$ROOT/dis/lib
+
+all:V: $TARG $LIBTARG
+
+install:V: $DISBIN/dmview.dis $DISBIN/dmwm.dis $DISLIB/drawmux.dis
+
+<$ROOT/mkfiles/mkdis
+
+nuke:V: nuke-lib
+
+nuke-lib:V:
+ cd $DISLIB; rm -f $LIBTARG
+
+$DISLIB/%.dis: %.dis
+ rm -f $DISLIB/$stem.dis && cp $stem.dis $DISLIB/$stem.dis