summaryrefslogtreecommitdiff
path: root/appl/wm
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
commit37da2899f40661e3e9631e497da8dc59b971cbd0 (patch)
treecbc6d4680e347d906f5fa7fca73214418741df72 /appl/wm
parent54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff)
20060303a
Diffstat (limited to 'appl/wm')
-rw-r--r--appl/wm/about.b72
-rw-r--r--appl/wm/avi.b384
-rw-r--r--appl/wm/bounce.b356
-rw-r--r--appl/wm/brutus.b2013
-rw-r--r--appl/wm/brutus/excerpt.b264
-rw-r--r--appl/wm/brutus/image.b259
-rw-r--r--appl/wm/brutus/mkfile24
-rw-r--r--appl/wm/brutus/mod.b335
-rw-r--r--appl/wm/brutus/table.b1478
-rw-r--r--appl/wm/c4.b718
-rw-r--r--appl/wm/calendar.b1064
-rw-r--r--appl/wm/clock.b123
-rw-r--r--appl/wm/coffee.b227
-rw-r--r--appl/wm/collide.b2180
-rw-r--r--appl/wm/colors.b153
-rw-r--r--appl/wm/cprof.b360
-rw-r--r--appl/wm/date.b78
-rw-r--r--appl/wm/deb.b1444
-rw-r--r--appl/wm/debdata.b418
-rw-r--r--appl/wm/debsrc.b633
-rw-r--r--appl/wm/dir.b511
-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
-rw-r--r--appl/wm/edit.b730
-rw-r--r--appl/wm/filename.b74
-rw-r--r--appl/wm/ftree/cptree.b136
-rw-r--r--appl/wm/ftree/cptree.m8
-rw-r--r--appl/wm/ftree/ftree.b873
-rw-r--r--appl/wm/ftree/items.b326
-rw-r--r--appl/wm/ftree/items.m30
-rw-r--r--appl/wm/ftree/mkfile36
-rw-r--r--appl/wm/ftree/wmsetup48
-rw-r--r--appl/wm/getauthinfo.b291
-rw-r--r--appl/wm/hebrew.m30
-rw-r--r--appl/wm/keyboard.b511
-rw-r--r--appl/wm/logon.b339
-rw-r--r--appl/wm/logwindow.b187
-rw-r--r--appl/wm/man.b769
-rw-r--r--appl/wm/mand.b839
-rw-r--r--appl/wm/mash.b577
-rw-r--r--appl/wm/memory.b246
-rw-r--r--appl/wm/minitel/README209
-rw-r--r--appl/wm/minitel/event.b19
-rw-r--r--appl/wm/minitel/event.m19
-rw-r--r--appl/wm/minitel/keyb.b367
-rw-r--r--appl/wm/minitel/mdisplay.b799
-rw-r--r--appl/wm/minitel/mdisplay.disbin0 -> 10304 bytes
-rw-r--r--appl/wm/minitel/mdisplay.m115
-rw-r--r--appl/wm/minitel/mdisplay.sbl1969
-rw-r--r--appl/wm/minitel/miniterm.b1187
-rw-r--r--appl/wm/minitel/miniterm.disbin0 -> 48192 bytes
-rw-r--r--appl/wm/minitel/miniterm.m120
-rw-r--r--appl/wm/minitel/miniterm.sbl6810
-rw-r--r--appl/wm/minitel/mkfile24
-rw-r--r--appl/wm/minitel/modem.b620
-rw-r--r--appl/wm/minitel/screen.b1610
-rw-r--r--appl/wm/minitel/socket.b49
-rw-r--r--appl/wm/minitel/swkeyb.b370
-rw-r--r--appl/wm/minitel/swkeyb.disbin0 -> 6496 bytes
-rw-r--r--appl/wm/minitel/swkeyb.m21
-rw-r--r--appl/wm/minitel/swkeyb.sbl724
-rw-r--r--appl/wm/mkfile103
-rw-r--r--appl/wm/mpeg.b185
-rw-r--r--appl/wm/mpeg/c0.tab261
-rw-r--r--appl/wm/mpeg/c0.vlc50
-rw-r--r--appl/wm/mpeg/c1.tab37
-rw-r--r--appl/wm/mpeg/c1.vlc18
-rw-r--r--appl/wm/mpeg/c2.tab21
-rw-r--r--appl/wm/mpeg/c2.vlc10
-rw-r--r--appl/wm/mpeg/c3.tab21
-rw-r--r--appl/wm/mpeg/c3.vlc10
-rw-r--r--appl/wm/mpeg/c4.tab9
-rw-r--r--appl/wm/mpeg/c4.vlc4
-rw-r--r--appl/wm/mpeg/c5.tab9
-rw-r--r--appl/wm/mpeg/c5.vlc4
-rw-r--r--appl/wm/mpeg/c6.tab9
-rw-r--r--appl/wm/mpeg/c6.vlc4
-rw-r--r--appl/wm/mpeg/c7.tab9
-rw-r--r--appl/wm/mpeg/c7.vlc4
-rw-r--r--appl/wm/mpeg/cbp.tab517
-rw-r--r--appl/wm/mpeg/cbp.vlc65
-rw-r--r--appl/wm/mpeg/cdc.tab261
-rw-r--r--appl/wm/mpeg/cdc.vlc11
-rw-r--r--appl/wm/mpeg/closest.m514
-rw-r--r--appl/wm/mpeg/decode.b831
-rw-r--r--appl/wm/mpeg/decode4.b709
-rw-r--r--appl/wm/mpeg/fixidct.b188
-rw-r--r--appl/wm/mpeg/fltidct.b177
-rw-r--r--appl/wm/mpeg/mai.tab2053
-rw-r--r--appl/wm/mpeg/mai.vlc35
-rw-r--r--appl/wm/mpeg/makergbvmap.b31
-rw-r--r--appl/wm/mpeg/maketables36
-rw-r--r--appl/wm/mpeg/mbb.tab69
-rw-r--r--appl/wm/mpeg/mbb.vlc13
-rw-r--r--appl/wm/mpeg/mbi.tab9
-rw-r--r--appl/wm/mpeg/mbi.vlc4
-rw-r--r--appl/wm/mpeg/mbp.tab69
-rw-r--r--appl/wm/mpeg/mbp.vlc9
-rw-r--r--appl/wm/mpeg/mkfile47
-rw-r--r--appl/wm/mpeg/motion.tab2053
-rw-r--r--appl/wm/mpeg/motion.vlc19
-rw-r--r--appl/wm/mpeg/mpeg.b285
-rw-r--r--appl/wm/mpeg/mpegio.b870
-rw-r--r--appl/wm/mpeg/mpegio.m218
-rw-r--r--appl/wm/mpeg/refidct.b58
-rw-r--r--appl/wm/mpeg/remap.b128
-rw-r--r--appl/wm/mpeg/remap1.b116
-rw-r--r--appl/wm/mpeg/remap2.b80
-rw-r--r--appl/wm/mpeg/remap24.b82
-rw-r--r--appl/wm/mpeg/remap4.b62
-rw-r--r--appl/wm/mpeg/remap8.b84
-rw-r--r--appl/wm/mpeg/rgbvmap.m258
-rw-r--r--appl/wm/mpeg/rl0f.tab517
-rw-r--r--appl/wm/mpeg/rl0f.vlc34
-rw-r--r--appl/wm/mpeg/rl0n.tab517
-rw-r--r--appl/wm/mpeg/rl0n.vlc35
-rw-r--r--appl/wm/mpeg/scidct.b160
-rw-r--r--appl/wm/mpeg/vlc.b213
-rw-r--r--appl/wm/mpeg/ydc.tab133
-rw-r--r--appl/wm/mpeg/ydc.vlc11
-rw-r--r--appl/wm/mprof.b314
-rw-r--r--appl/wm/pen.b447
-rw-r--r--appl/wm/polyhedra.b800
-rw-r--r--appl/wm/prof.b323
-rw-r--r--appl/wm/qt.b161
-rw-r--r--appl/wm/readmail.b885
-rw-r--r--appl/wm/remotelogon.b314
-rw-r--r--appl/wm/reversi.b903
-rw-r--r--appl/wm/rmtdir.b215
-rw-r--r--appl/wm/rt.b701
-rw-r--r--appl/wm/sam.b230
-rw-r--r--appl/wm/samstub.b1338
-rw-r--r--appl/wm/samstub.m132
-rw-r--r--appl/wm/samterm.m75
-rw-r--r--appl/wm/samtk.b688
-rw-r--r--appl/wm/samtk.m54
-rw-r--r--appl/wm/sendmail.b652
-rw-r--r--appl/wm/sh.b851
-rw-r--r--appl/wm/smenu.b204
-rw-r--r--appl/wm/smenu.m18
-rw-r--r--appl/wm/snake.b373
-rw-r--r--appl/wm/stopwatch.b184
-rw-r--r--appl/wm/sweeper.b330
-rw-r--r--appl/wm/task.b240
-rw-r--r--appl/wm/telnet.b820
-rw-r--r--appl/wm/tetris.b806
-rw-r--r--appl/wm/toolbar.b566
-rw-r--r--appl/wm/unibrowse.b966
-rw-r--r--appl/wm/view.b484
-rw-r--r--appl/wm/vt.b1007
-rw-r--r--appl/wm/wish.b165
-rw-r--r--appl/wm/wm.b678
-rw-r--r--appl/wm/wmdeb.m82
-rw-r--r--appl/wm/wmplay.b176
158 files changed, 65132 insertions, 0 deletions
diff --git a/appl/wm/about.b b/appl/wm/about.b
new file mode 100644
index 00000000..615f106b
--- /dev/null
+++ b/appl/wm/about.b
@@ -0,0 +1,72 @@
+implement WmAbout;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Display, Image: import draw;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+WmAbout: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+tkcfg(version: string): array of string
+{
+ return array[] of {
+ "frame .f -bg black -borderwidth 2 -relief ridge",
+ "label .b -bg black -bitmap @/icons/inferno.bit",
+ "label .l1 -bg black -fg #ff5500 -text {Inferno "+ version + "}",
+ "pack .b .l1 -in .f",
+ "pack .f -ipadx 4 -ipady 2",
+ "pack propagate . 0",
+ "update",
+ };
+}
+
+init(ctxt: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "about: no window context\n");
+ raise "fail:bad context";
+ }
+
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient= load Tkclient Tkclient->PATH;
+
+ tkclient->init();
+ (t, menubut) := tkclient->toplevel(ctxt, "", "About Inferno", 0);
+
+ tkcmds := tkcfg(rf("/dev/sysctl"));
+ for (i := 0; i < len tkcmds; i++)
+ tk->cmd(t,tkcmds[i]);
+
+ tkclient->onscreen(t, nil);
+ tkclient->startinput(t, "ptr"::nil);
+ stop := chan of int;
+ spawn tkclient->handler(t, stop);
+ while((menu := <-menubut) != "exit")
+ tkclient->wmctl(t, menu);
+ stop <-= 1;
+}
+
+rf(name: string): string
+{
+ fd := sys->open(name, Sys->OREAD);
+ if(fd == nil)
+ return nil;
+ buf := array[128] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ n = 0;
+ return string buf[0:n];
+}
diff --git a/appl/wm/avi.b b/appl/wm/avi.b
new file mode 100644
index 00000000..a1331a1e
--- /dev/null
+++ b/appl/wm/avi.b
@@ -0,0 +1,384 @@
+implement WmAVI;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Rect, Display, Image: import draw;
+
+include "tk.m";
+ tk: Tk;
+ Toplevel: import tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+ ctxt: ref Draw->Context;
+
+include "selectfile.m";
+ selectfile: Selectfile;
+
+include "dialog.m";
+ dialog: Dialog;
+
+include "riff.m";
+ avi: Riff;
+ AVIhdr, AVIstream, RD: import avi;
+ video: ref AVIstream;
+
+WmAVI: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Stopped, Playing, Paused: con iota;
+state := Stopped;
+
+
+cmap: array of byte;
+codedbuf: array of byte;
+pixelbuf: array of byte;
+pixelrec: Draw->Rect;
+
+task_cfg := array[] of {
+ "canvas .c",
+ "frame .b",
+ "button .b.File -text File -command {send cmd file}",
+ "button .b.Stop -text Stop -command {send cmd stop}",
+ "button .b.Pause -text Pause -command {send cmd pause}",
+ "button .b.Play -text Play -command {send cmd play}",
+ "frame .f",
+ "label .f.file -text {File:}",
+ "label .f.name",
+ "pack .f.file .f.name -side left",
+ "pack .b.File .b.Stop .b.Pause .b.Play -side left",
+ "pack .f -fill x",
+ "pack .b -anchor w",
+ "pack .c -side bottom -fill both -expand 1",
+ "pack propagate . 0",
+};
+
+init(xctxt: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient= load Tkclient Tkclient->PATH;
+ dialog = load Dialog Dialog->PATH;
+ selectfile = load Selectfile Selectfile->PATH;
+
+ ctxt = xctxt;
+
+ sys->pctl(Sys->NEWPGRP, nil);
+
+ tkclient->init();
+ dialog->init();
+ selectfile->init();
+
+ (t, wmctl) := tkclient->toplevel(ctxt, "", "AVI Player", 0);
+
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+
+ for (c:=0; c<len task_cfg; c++)
+ tk->cmd(t, task_cfg[c]);
+
+ tk->cmd(t, "bind . <Configure> {send cmd resize}");
+ tk->cmd(t, "update");
+ tkclient->onscreen(t, nil);
+ tkclient->startinput(t, "kbd"::"ptr"::nil);
+
+ avi = load Riff Riff->PATH;
+ if(avi == nil) {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Loading Interfaces",
+ "Failed to load the RIFF/AVI\ninterface:"+sys->sprint("%r"),
+ 0, "Exit"::nil);
+ return;
+ }
+ avi->init();
+
+ fname := "";
+ state = Stopped;
+
+ for(;;) alt {
+ s := <-t.ctxt.kbd =>
+ tk->keyboard(t, s);
+ s := <-t.ctxt.ptr =>
+ tk->pointer(t, *s);
+ s := <-t.ctxt.ctl or
+ s = <-t.wreq or
+ s = <-wmctl =>
+ if(s == "exit") {
+ state = Stopped;
+ return;
+ }
+ tkclient->wmctl(t, s);
+ press := <-cmd =>
+ case press {
+ "file" =>
+ state = Stopped;
+ patterns := list of {
+ "*.avi (Microsoft movie files)",
+ "* (All Files)"
+ };
+ fname = selectfile->filename(ctxt, t.image, "Locate AVI files",
+ patterns, nil);
+ if(fname != nil) {
+ tk->cmd(t, ".f.name configure -text {"+fname+"}");
+ tk->cmd(t, "update");
+ }
+ "play" =>
+ if (state != Stopped) {
+ state = Playing;
+ continue;
+ }
+ if(fname != nil) {
+ state = Playing;
+ spawn play(t, fname);
+ }
+ "pause" =>
+ if(state == Playing)
+ state = Paused;
+ "stop" =>
+ state = Stopped;
+ }
+ }
+}
+
+play(t: ref Toplevel, file: string)
+{
+ sp := list of { "Stop Play" };
+
+ (r, err) := avi->open(file);
+ if(err != nil) {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Open AVI file", err, 0, sp);
+ return;
+ }
+
+ err = avi->r.check4("AVI ");
+ if(err != nil) {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Read AVI format", err, 0, sp);
+ return;
+ }
+
+ (code, l) := avi->r.gethdr();
+ if(code != "LIST") {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Parse AVI headers",
+ "no list under AVI section header", 0, sp);
+ return;
+ }
+
+ err = avi->r.check4("hdrl");
+ if(err != nil) {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Read AVI header", err, 0, sp);
+ return;
+ }
+
+ avihdr: ref AVIhdr;
+ (avihdr, err) = avi->r.avihdr();
+ if(err != nil) {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Read AVI header", err, 0, sp);
+ return;
+ }
+
+ #
+ # read the stream info & format structures
+ #
+ stream := array[avihdr.streams] of ref AVIstream;
+ for(i := 0; i < avihdr.streams; i++) {
+ (stream[i], err) = avi->r.streaminfo();
+ if(err != nil) {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Parse AVI headers",
+ "Failed to parse stream headers\n"+err, 0, sp);
+ return;
+ }
+ if(stream[i].stype == "vids") {
+ video = stream[i];
+ err = video.fmt2binfo();
+ if(err != nil) {
+ dialog->prompt(ctxt, t.image, "error -fg red",
+ "Parse AVI Video format",
+ "Invalid stream headers\n"+err, 0, sp);
+ return;
+ }
+ }
+ }
+
+ img: ref Draw->Image;
+ if(video != nil) {
+ case video.binfo.compression {
+ * =>
+ dialog->prompt(ctxt, t.image, "error -fg red",
+ "Parse AVI Compression method",
+ "unknown compression/encoding method", 0, sp);
+ return;
+ avi->BI_RLE8 =>
+ cmap = array[len video.binfo.cmap] of byte;
+ for(i = 0; i < len video.binfo.cmap; i++) {
+ e := video.binfo.cmap[i];
+ cmap[i] = byte ctxt.display.rgb2cmap(e.r, e.g, e.b);
+ }
+ break;
+ }
+ chans: draw->Chans;
+ case video.binfo.bitcount {
+ * =>
+ dialog->prompt(ctxt, t.image, "error -fg red",
+ "Check AVI Video format",
+ string video.binfo.bitcount+
+ " bits per pixel not supported", 0, sp);
+ return;
+ 8 =>
+ chans = Draw->CMAP8;
+ mem := video.binfo.width*video.binfo.height;
+ pixelbuf = array[mem] of byte;
+ };
+ pixelrec.min = (0, 0);
+ pixelrec.max = (video.binfo.width, video.binfo.height);
+ img = ctxt.display.newimage(pixelrec, chans, 0, Draw->White);
+ if (img == nil) {
+ sys->fprint(sys->fildes(2), "coffee: failed to allocate image\n");
+ exit;
+ }
+ }
+
+ #
+ # Parse out the junk headers we don't understand
+ #
+ parse: for(;;) {
+ (code, l) = avi->r.gethdr();
+ if(l < 0)
+ break;
+
+ case code {
+ * =>
+# sys->print("%s %d\n", code, l);
+ avi->r.skip(l);
+ "LIST" =>
+ err = avi->r.check4("movi");
+ if(err != nil) {
+ dialog->prompt(ctxt, t.image, "error -fg red",
+ "Strip AVI headers",
+ "no movi chunk", 0, sp);
+ return;
+ }
+ break parse;
+ }
+ }
+
+ canvr := canvsize(t);
+ p := (Draw->Point)(0, 0);
+ dx := canvr.dx();
+ if(dx > video.binfo.width)
+ p.x = (dx - video.binfo.width)/2;
+
+ dy := canvr.dy();
+ if(dy > video.binfo.height)
+ p.y = (dy - video.binfo.height)/2;
+
+ canvr = canvr.addpt(p);
+
+ chunk: for(;;) {
+ while(state == Paused)
+ sys->sleep(0);
+ if(state == Stopped)
+ break chunk;
+ (code, l) = avi->r.gethdr();
+ if(l <= 0)
+ break;
+ if(l & 1)
+ l++;
+ case code {
+ * =>
+ avi->r.skip(l);
+ "00db" => # Stream 0 Video DIB
+ dib(r, img, l);
+ "00dc" => # Stream 0 Video DIB compressed
+ dibc(r, img, l);
+ t.image.draw(canvr, img, nil, img.r.min);
+ "idx1" =>
+ break chunk;
+ }
+ }
+ state = Stopped;
+}
+
+dib(r: ref RD, i: ref Draw->Image, l: int): int
+{
+ if(len codedbuf < l)
+ codedbuf = array[l] of byte;
+
+ if(r.readn(codedbuf, l) != l)
+ return -1;
+
+ case video.binfo.bitcount {
+ 8 =>
+ for(k := 0; k < l; k++)
+ codedbuf[k] = cmap[int codedbuf[k]];
+
+ i.writepixels(pixelrec, codedbuf);
+ }
+ return 0;
+}
+
+dibc(r: ref RD, i: ref Draw->Image, l: int): int
+{
+ if(len codedbuf < l)
+ codedbuf = array[l] of byte;
+
+ if(r.readn(codedbuf, l) != l)
+ return -1;
+
+ case video.binfo.compression {
+ avi->BI_RLE8 =>
+ p := 0;
+ posn := 0;
+ x := 0;
+ y := video.binfo.height-1;
+ w := video.binfo.width;
+ decomp: while(p < l) {
+ n := int codedbuf[p++];
+ if(n == 0) {
+ esc := int codedbuf[p++];
+ case esc {
+ 0 => # end of line
+ x = 0;
+ y--;
+ 1 => # end of image
+ break decomp;
+ 2 => # Delta dx,dy
+ x += int codedbuf[p++];
+ y -= int codedbuf[p++];
+ * =>
+ posn = x+y*w;
+ for(k := 0; k < esc; k++)
+ pixelbuf[posn++] = cmap[int codedbuf[p++]];
+ x += esc;
+ if(p & 1)
+ p++;
+ };
+ }
+ else {
+ posn = x+y*w;
+ v := cmap[int codedbuf[p++]];
+ for(k := 0; k < n; k++)
+ pixelbuf[posn++] = v;
+ x += n;
+ }
+ }
+ i.writepixels(pixelrec, pixelbuf);
+ }
+ return 0;
+}
+
+canvsize(t: ref Toplevel): Rect
+{
+ r: Rect;
+
+ r.min.x = int tk->cmd(t, ".c cget -actx");
+ r.min.y = int tk->cmd(t, ".c cget -acty");
+ r.max.x = r.min.x + int tk->cmd(t, ".c cget -width");
+ r.max.y = r.min.y + int tk->cmd(t, ".c cget -height");
+
+ return r;
+}
diff --git a/appl/wm/bounce.b b/appl/wm/bounce.b
new file mode 100644
index 00000000..7d6cdd1b
--- /dev/null
+++ b/appl/wm/bounce.b
@@ -0,0 +1,356 @@
+implement Bounce;
+
+# bouncing balls demo. it uses tk and multiple processes to animate a
+# number of balls bouncing around the screen. each ball has its own
+# process; CPU time is doled out fairly to each process by using
+# a central monitor loop.
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+ Point, Rect: import draw;
+include "tk.m";
+ tk: Tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+include "math.m";
+ math: Math;
+include "rand.m";
+
+Bounce: module {
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+BALLSIZE: con 4;
+ZERO: con 1e-6;
+π: con Math->Pi;
+
+Line: adt {
+ p1, p2: Point;
+};
+
+Realpoint: adt {
+ x, y: real;
+};
+
+gamecmds := array[] of {
+"canvas .c",
+"bind .c <ButtonRelease-1> {send cmd 0 %x %y}",
+"bind .c <ButtonRelease-2> {send cmd 0 %x %y}",
+"bind .c <Button-1> {send cmd 1 %x %y}",
+"bind .c <Button-2> {send cmd 2 %x %y}",
+"frame .f",
+"button .f.left -bitmap small_color_left.bit -bd 0 -command {send cmd k -1}",
+"button .f.right -bitmap small_color_right.bit -bd 0 -command {send cmd k 1}",
+"label .f.l -text {8 balls}",
+"pack .f.left .f.right -side left",
+"pack .f.l -side left",
+"pack .f -fill x",
+"pack .c -fill both -expand 1",
+};
+
+randch: chan of int;
+lines: list of (int, Line);
+lineid := 0;
+lineversion := 0;
+
+addline(win: ref Tk->Toplevel, v: Line)
+{
+ lines = (++lineid, v) :: lines;
+ cmd(win, ".c create line " + pt2s(v.p1) + " " + pt2s(v.p2) + " -width 3 -fill black" +
+ " -tags l" + string lineid);
+ lineversion++;
+}
+
+nomod(s: string)
+{
+ sys->fprint(sys->fildes(2), "bounce: cannot load %s: %r\n", s);
+ raise "fail:bad module";
+}
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ math = load Math Math->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ if (tkclient == nil)
+ nomod(Tkclient->PATH);
+ tkclient->init();
+ nballs := 8;
+ if (argv != nil && tl argv != nil)
+ nballs = int hd tl argv;
+ if (nballs < 0) {
+ sys->fprint(sys->fildes(2), "usage: bounce [nballs]\n");
+ raise "fail:usage";
+ }
+ sys->pctl(Sys->NEWPGRP, nil);
+ if(ctxt == nil)
+ ctxt = tkclient->makedrawcontext();
+ (win, wmctl) := tkclient->toplevel(ctxt, nil, "Bounce", 0);
+ cmdch := chan of string;
+ tk->namechan(win, cmdch, "cmd");
+ for (i := 0; i < len gamecmds; i++)
+ cmd(win, gamecmds[i]);
+ cmd(win, ".c configure -width 400 -height 400");
+ cmd(win, "pack propagate . 0");
+ cmd(win, ".f.l configure -text '" + string nballs + " balls");
+ tkclient->onscreen(win, nil);
+ tkclient->startinput(win, "kbd"::"ptr"::nil);
+
+ mch := chan of (int, Point);
+ randch = chan of int;
+ spawn randgenproc(randch);
+ csz := Point(int cmd(win, ".c cget -actwidth"), int cmd(win, ".c cget -actheight"));
+
+ # add edges of window
+ addline(win, ((-1, -1), (csz.x, -1)));
+ addline(win, ((csz.x, -1), csz));
+ addline(win, (csz, (-1, csz.y)));
+ addline(win, ((-1, csz.y), (-1, -1)));
+
+ spawn makelinesproc(win, mch);
+ mkball := chan of (int, Realpoint, Realpoint);
+ spawn monitor(win, mkball);
+ for (i = 0; i < nballs; i++)
+ mkball <-= (1, randpoint(csz), makeunit(randpoint(csz)));
+ for (;;) alt {
+ s := <-win.ctxt.kbd =>
+ tk->keyboard(win, s);
+ s := <-win.ctxt.ptr =>
+ tk->pointer(win, *s);
+ s := <-win.ctxt.ctl or
+ s = <-win.wreq or
+ s = <-wmctl =>
+ tkclient->wmctl(win, s);
+ c := <-cmdch =>
+ (nil, toks) := sys->tokenize(c, " ");
+ if (hd toks != "k") {
+ mch <-= (int hd toks, Point(int hd tl toks, int hd tl tl toks));
+ continue;
+ }
+ n := nballs + int hd tl toks;
+ if (n < 0)
+ n = 0;
+ dn := 1;
+ if (n < nballs)
+ dn = -1;
+ for (; nballs != n; nballs += dn)
+ mkball <-= (dn, randpoint(csz), makeunit(randpoint(csz)));
+ cmd(win, ".f.l configure -text '" + string nballs + " balls");
+ cmd(win, "update");
+ }
+}
+
+randpoint(size: Point): Realpoint
+{
+ return (randreal(size.x), randreal(size.y));
+}
+
+# return randomish real number between 1 and x-1
+randreal(x: int): real
+{
+ return real (<-randch % ((x - 1) * 100)) / 100.0 + 1.0;
+}
+
+# make sure cpu time is handed to all ball processes fairly
+# by passing a "token" around to each process in turn.
+# each process does its work when it *hasn't* got its
+# token but it can't go through two iterations without
+# waiting its turn.
+#
+# new processes can be created and destroyed by
+# sending on mkball. processes are arranged in a stack-like
+# order: new processes are added to the top of the stack, and
+# processes are destroyed from the top of the stack downwards.
+monitor(win: ref Tk->Toplevel, mkball: chan of (int, Realpoint, Realpoint))
+{
+ procl := proc := chan of int :: nil;
+ spawn nullproc(hd proc); # always there to avoid deadlock when no balls.
+ hd proc <-= 1; # hand token to dummy proc
+ for (;;) {
+ procc := hd proc;
+ alt {
+ (n, p, v) := <-mkball =>
+ if (n > 0) { # start new ball proc going.
+ procl = chan of int :: procl;
+ spawn animproc(hd procl, win, p, v);
+ } else if (tl procl != nil) { # stop a ball proc.
+ <-hd proc; # get token.
+ hd procl <-= 0; # stop proc.
+ proc = procl = tl procl; # remove proc.
+ hd proc <-= 1; # hand out token.
+ }
+ <-procc => # got token.
+ if ((proc = tl proc) == nil)
+ proc = procl;
+ hd proc <-= 1; # hand token to next process.
+ }
+ }
+}
+
+nullproc(c: chan of int)
+{
+ for (;;)
+ c <-= <-c;
+}
+
+# animate one ball. initial position and unit-velocity are
+# given by p and v.
+animproc(c: chan of int, win: ref Tk->Toplevel, p, v: Realpoint)
+{
+ speed := 0.1 + real (<-randch % 40) / 100.0;
+ ballid := cmd(win, sys->sprint(".c create oval 0 0 1 1 -fill #%.6x", <-randch & 16rffffff));
+ hitlineid := -1;
+ smallcount := 0;
+ version := lineversion;
+loop: for (;;) {
+ hitline: Line;
+ hitp: Realpoint;
+
+ dist := 1000000.0;
+ oldid := hitlineid;
+ for (l := lines; l != nil; l = tl l) {
+ (id, line) := hd l;
+ (ok, hp, hdist) := intersect(p, v, line);
+ if (ok && hdist < dist && id != oldid && (smallcount < 10 || hdist > 1.5)) {
+ (hitp, hitline, hitlineid, dist) = (hp, line, id, hdist);
+ }
+ }
+ if (dist > 10000.0) {
+ sys->print("no intersection!\n");
+# sys->print("p: [%f, %f], v: [%f, %f]\n", p.x, p.y, v.x, v.y);
+# for (l := lines; l != nil; l = tl l) {
+# (id, line) := hd l;
+# (ok, hp, hdist) := intersect(p, v, line);
+# sys->print("line: [%d %d]->[%d %d] -> %d, [%f, %f], %f\n", line.p1.x, line.p1.y, line.p2.x, line.p2.y,
+# ok, hp.x, hp.y, hdist);
+# }
+ cmd(win, ".c delete " + ballid + ";update");
+ while (c <-= <-c)
+ ;
+ exit;
+ }
+ if (dist < 0.0001)
+ smallcount++;
+ else
+ smallcount = 0;
+ bouncev := boing(v, hitline);
+ t0 := sys->millisec();
+ dt := int (dist / speed);
+ t := 0;
+ do {
+ s := real t * speed;
+ currp := Realpoint(p.x + s * v.x, p.y + s * v.y);
+ bp := Point(int currp.x, int currp.y);
+ cmd(win, ".c coords " + ballid + " " +
+ string (bp.x-BALLSIZE)+" "+string (bp.y-BALLSIZE)+" "+
+ string (bp.x+BALLSIZE)+" "+string (bp.y+BALLSIZE));
+ cmd(win, "update");
+ if (lineversion > version) {
+ (p, hitlineid, version) = (currp, oldid, lineversion);
+ continue loop;
+ }
+ # pass the token back to the monitor.
+ if (<-c == 0) {
+ cmd(win, ".c delete " + ballid + ";update");
+ exit;
+ }
+ c <-= 1;
+ t = sys->millisec() - t0;
+ } while (t < dt);
+ p = hitp;
+ v = bouncev;
+ }
+}
+
+# thread-safe access to the Rand module
+randgenproc(ch: chan of int)
+{
+ rand := load Rand Rand->PATH;
+ for (;;)
+ ch <-= rand->rand(16r7fffffff);
+}
+
+makelinesproc(win: ref Tk->Toplevel, mch: chan of (int, Point))
+{
+ for (;;) {
+ (down, p1) := <-mch;
+ addline(win, (p1, p1));
+ (id, nil) := hd lines;
+ p2 := p1;
+ do {
+ (down, p2) = <-mch;
+ cmd(win, ".c coords l" + string id + " " + pt2s(p1) + " " + pt2s(p2));
+ cmd(win, "update");
+ lines = (id, (p1, p2)) :: tl lines;
+ lineversion++;
+ if (down > 1) {
+ dp := p2.sub(p1);
+ if (dp.x*dp.x + dp.y*dp.y > 5) {
+ p1 = p2;
+ addline(win, (p2, p2));
+ (id, nil) = hd lines;
+ }
+ }
+ } while (down);
+ }
+}
+
+# make a vector of unit-length, parallel to v.
+makeunit(v: Realpoint): Realpoint
+{
+ mag := math->sqrt(v.x * v.x + v.y * v.y);
+ return (v.x / mag, v.y / mag);
+}
+
+# bounce ball travelling in direction av off line b.
+# return the new unit vector.
+boing(av: Realpoint, b: Line): Realpoint
+{
+ f := b.p2.sub(b.p1);
+ d := math->atan2(real f.y, real f.x) * 2.0 - math->atan2(av.y, av.x);
+ return (math->cos(d), math->sin(d));
+}
+
+# compute the intersection of lines a and b.
+# b is assumed to be fixed, and a is indefinitely long
+# but doesn't extend backwards from its starting point.
+# a is defined by the starting point p and the unit vector v.
+intersect(p, v: Realpoint, b: Line): (int, Realpoint, real)
+{
+ w := Realpoint(real (b.p2.x - b.p1.x), real (b.p2.y - b.p1.y));
+ det := w.x * v.y - v.x * w.y;
+ if (det > -ZERO && det < ZERO)
+ return (0, (0.0, 0.0), 0.0);
+
+ y21 := real b.p1.y - p.y;
+ x21 := real b.p1.x - p.x;
+ s := (w.x * y21 - w.y * x21) / det;
+ if (s < 0.0)
+ return (0, (0.0, 0.0), 0.0);
+
+ hp := Realpoint(p.x+v.x*s, p.y+v.y*s);
+ if (b.p1.x > b.p2.x)
+ (b.p1.x, b.p2.x) = (b.p2.x, b.p1.x);
+ if (b.p1.y > b.p2.y)
+ (b.p1.y, b.p2.y) = (b.p2.y, b.p1.y);
+
+ return (int hp.x >= b.p1.x && int hp.x <= b.p2.x
+ && int hp.y >= b.p1.y && int hp.y <= int b.p2.y, hp, s);
+}
+
+cmd(top: ref Tk->Toplevel, s: string): string
+{
+ e := tk->cmd(top, s);
+ if (e != nil && e[0] == '!')
+ sys->print("tk error %s on '%s'\n", e, s);
+ return e;
+}
+
+pt2s(p: Point): string
+{
+ return string p.x + " " + string p.y;
+}
diff --git a/appl/wm/brutus.b b/appl/wm/brutus.b
new file mode 100644
index 00000000..b18d6a9d
--- /dev/null
+++ b/appl/wm/brutus.b
@@ -0,0 +1,2013 @@
+implement Brutus;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Context: import draw;
+ ctxt: ref Context;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "dialog.m";
+ dialog: Dialog;
+
+include "selectfile.m";
+ selectfile: Selectfile;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "workdir.m";
+
+include "plumbmsg.m";
+ plumbmsg: Plumbmsg;
+ Msg: import plumbmsg;
+
+include "brutus.m";
+include "brutusext.m";
+
+EXTDIR: con "/dis/wm/brutus";
+NEXTRA: con NTAG-NFONTTAG;
+DEFFONT: con "/fonts/lucidasans/unicode.8.font";
+DEFFONTNAME: con "Roman";
+DEFSIZE: con 10;
+DEFTAG: con "Roman.10";
+SETFONT: con " -font "+DEFFONT+" ";
+FOCUS: con "focus .ft.t";
+NOSEL: con ".ft.t tag remove sel sel.first sel.last";
+UPDATE: con "update";
+
+#
+# Foreign keyboards and languages
+#
+Remaptab: adt
+{
+ in, out: int;
+};
+include "hebrew.m";
+
+BS: con 8; # ^h backspace character
+BSW: con 23; # ^w bacspace word
+BSL: con 21; # ^u backspace line
+ESC: con 27; # ^[ cut selection
+
+Name: con "Brutus";
+
+# build menu
+menu_cfg := array[] of {
+ # menu
+ "menu .m",
+ ".m add command -text Cut -command {send edit cut}",
+ ".m add command -text Paste -command {send edit paste}",
+ ".m add command -text Snarf -command {send edit snarf}",
+ ".m add command -text Look -command {send edit look}",
+};
+
+brutus_cfg := array[] of {
+ # buttons
+ "button .b.Tag -text Tag -command {send cmd tag} -state disabled",
+ "menubutton .b.Font -text Roman -menu .b.Font.menu -underline -1 -state disabled",
+ "menu .b.Font.menu",
+ ".b.Font.menu add command -label Roman -command {send cmd font Roman}",
+ ".b.Font.menu add command -label Italic -command {send cmd font Italic}",
+ ".b.Font.menu add command -label Bold -command {send cmd font Bold}",
+ ".b.Font.menu add command -label Type -command {send cmd font Type}",
+ "checkbutton .b.Applyfont -variable Applyfont -command {send cmd applyfont}} -state disabled",
+ "button .b.Applyfontnow -text Font -command {send cmd applyfontnow} -state disabled",
+ "button .b.Applysizenow -text Size -command {send cmd applysizenow} -state disabled",
+ "button .b.Applyfontsizenow -text F&S -command {send cmd applyfontsizenow} -state disabled",
+ "menubutton .b.Size -text 10pt -menu .b.Size.menu -underline -1 -state disabled",
+ "menu .b.Size.menu",
+ ".b.Size.menu add command -label 6pt -command {send cmd size 6}",
+ ".b.Size.menu add command -label 8pt -command {send cmd size 8}",
+ ".b.Size.menu add command -label 10pt -command {send cmd size 10}",
+ ".b.Size.menu add command -label 12pt -command {send cmd size 12}",
+ ".b.Size.menu add command -label 16pt -command {send cmd size 16}",
+ "button .b.Put -text Put -command {send cmd put} -state disabled",
+
+ # text
+ "frame .ft",
+ "scrollbar .ft.scroll -command {.ft.t yview}",
+ "text .ft.t -height 7c -tabs {1c} -wrap word -yscrollcommand {.ft.scroll set}",
+ FOCUS,
+
+ # pack
+ "pack .b.File .b.Ext .b.Tag .b.Applyfontnow .b.Applysizenow .b.Applyfontsizenow .b.Applyfont .b.Font .b.Size .b.Put -side left",
+ "pack .b -anchor w",
+ "pack .ft.scroll -side left -fill y",
+ "pack .ft.t -fill both -expand 1",
+ "pack .ft -fill both -expand 1",
+ "pack propagate . 0",
+};
+
+control_cfg := array[] of {
+ # text
+ "frame .ft",
+ "scrollbar .ft.scroll -command {.ft.t yview}",
+ "text .ft.t -height 4c -wrap word -yscrollcommand {.ft.scroll set}",
+ "pack .b.File",
+ "pack .b -anchor w",
+ "pack .ft.scroll -side left -fill y",
+ "pack .ft.t -fill both -expand 1",
+ "pack .ft -fill both -expand 1",
+ "pack propagate . 0",
+};
+
+# bindings to build nice controls in text widget
+input_cfg := array[] of {
+ # input
+ "bind .ft.t <Key> {send keys {%A}}",
+ "bind .ft.t <Control-h> {send keys {%A}}",
+ "bind .ft.t <Control-w> {send keys {%A}}",
+ "bind .ft.t <Control-u> {send keys {%A}}",
+ "bind .ft.t <Button-1> +{grab set .ft.t; send but1 pressed}",
+ "bind .ft.t <Double-Button-1> +{grab set .ft.t; send but1 pressed}",
+ "bind .ft.t <ButtonRelease-1> +{grab release .ft.t; send but1 released}",
+ "bind .ft.t <Button-2> {send but2 %X %Y}",
+ "bind .ft.t <Motion-Button-2-Button-1> {}",
+ "bind .ft.t <Motion-Button-2> {}",
+ "bind .ft.t <ButtonPress-3> {send but3 pressed}",
+ "bind .ft.t <ButtonRelease-3> {send but3 released %x %y}",
+ "bind .ft.t <Motion-Button-3> {}",
+ "bind .ft.t <Motion-Button-3-Button-1> {}",
+ "bind .ft.t <Double-Button-3> {}",
+ "bind .ft.t <Double-ButtonRelease-3> {}",
+ "bind .ft.t <FocusIn> +{send cmd focus}",
+ UPDATE
+};
+
+fontbuts := array[] of {
+ ".b.Ext",
+ ".b.Tag",
+ ".b.Applyfontnow",
+ ".b.Applysizenow",
+ ".b.Applyfontsizenow",
+ ".b.Applyfont",
+ ".b.Font",
+ ".b.Size",
+};
+
+fontname = array[NFONT] of {
+ "Roman",
+ "Italic",
+ "Bold",
+ "Type",
+};
+
+sizename = array[NSIZE] of {
+ "6",
+ "8",
+ "10",
+ "12",
+ "16",
+};
+
+tagname = array[NTAG] of {
+ # first NFONT*NSIZE are font/size names
+ "Roman.6",
+ "Roman.8",
+ "Roman.10",
+ "Roman.12",
+ "Roman.16",
+ "Italic.6",
+ "Italic.8",
+ "Italic.10",
+ "Italic.12",
+ "Italic.16",
+ "Bold.6",
+ "Bold.8",
+ "Bold.10",
+ "Bold.12",
+ "Bold.16",
+ "Type.6",
+ "Type.8",
+ "Type.10",
+ "Type.12",
+ "Type.16",
+ "Example",
+ "Caption",
+ "List",
+ "List-elem",
+ "Label",
+ "Label-ref",
+ "Exercise",
+ "Heading",
+ "No-fill",
+ "Author",
+ "Title",
+ "Index",
+ "Index-topic",
+};
+
+tagconfig = array[NTAG] of {
+ "-font /fonts/lucidasans/unicode.6.font",
+ "-font /fonts/lucidasans/unicode.7.font",
+ "-font /fonts/lucidasans/unicode.8.font",
+ "-font /fonts/lucidasans/unicode.10.font",
+ "-font /fonts/lucidasans/unicode.13.font",
+ "-font /fonts/lucidasans/italiclatin1.6.font",
+ "-font /fonts/lucidasans/italiclatin1.7.font",
+ "-font /fonts/lucidasans/italiclatin1.8.font",
+ "-font /fonts/lucidasans/italiclatin1.10.font",
+ "-font /fonts/lucidasans/italiclatin1.13.font",
+ "-font /fonts/lucidasans/boldlatin1.6.font",
+ "-font /fonts/lucidasans/boldlatin1.7.font",
+ "-font /fonts/lucidasans/boldlatin1.8.font",
+ "-font /fonts/lucidasans/boldlatin1.10.font",
+ "-font /fonts/lucidasans/boldlatin1.13.font",
+ "-font /fonts/lucidasans/typelatin1.6.font",
+ "-font /fonts/lucidasans/typelatin1.7.font",
+ "-font /fonts/pelm/latin1.9.font",
+ "-font /fonts/pelm/ascii.12.font",
+ "-font /fonts/pelm/ascii.16.font",
+ "-foreground #444444 -lmargin1 1c -lmargin2 1c; .ft.t tag lower Example",
+ "-foreground #444444; .ft.t tag lower Caption",
+ "-foreground #444444 -lmargin1 1c -lmargin2 1c; .ft.t tag lower List",
+ "-foreground #0000A0; .ft.t tag lower List-elem",
+ "-foreground #444444; .ft.t tag lower Label",
+ "-foreground #444444; .ft.t tag lower Label-ref",
+ "-foreground #444444; .ft.t tag lower Exercise",
+ "-foreground #444444; .ft.t tag lower Heading",
+ "-foreground #444444; .ft.t tag lower No-fill",
+ "-foreground #444444; .ft.t tag lower Author",
+ "-foreground #444444; .ft.t tag lower Title",
+ "-foreground #444444; .ft.t tag lower Index",
+ "-foreground #444444; .ft.t tag lower Index-topic",
+};
+
+enabled := array[] of {"disabled", "normal"};
+
+File: adt
+{
+ tk: ref Tk->Toplevel;
+ isctl: int;
+ applyfont: int;
+ fontsused: int;
+ name: string;
+ dirty: int;
+ font: string; # set by the buttons, not nec. by the text
+ size: int; # set by the buttons, not nec. by the text
+ fonttag: string; # set by the buttons, not nec. by the text
+ configed: array of int;
+ button1: int;
+ button3: int;
+ fontsok: int; # fonts and tags can be set
+ extensions: list of ref Ext;
+};
+
+Ext: adt
+{
+ tkname: string;
+ modname: string;
+ mod: Brutusext;
+ args: string;
+};
+
+menuindex := "0";
+snarftext := "";
+snarfsgml := "";
+central: chan of (ref File, string);
+files: array of ref File; # global but modified only by control thread
+plumbed := 0;
+curdir := "";
+lang := "";
+
+init(c: ref Context, argv: list of string)
+{
+ ctxt = c;
+ sys = load Sys Sys->PATH;
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "brutus: no window context\n");
+ raise "fail:bad context";
+ }
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ dialog = load Dialog Dialog->PATH;
+ selectfile = load Selectfile Selectfile->PATH;
+ bufio = load Bufio Bufio->PATH;
+ plumbmsg = load Plumbmsg Plumbmsg->PATH;
+
+ if(plumbmsg->init(1, "edit", 1000) >= 0){
+ plumbed = 1;
+ workdir := load Workdir Workdir->PATH;
+ curdir = workdir->init();
+ workdir = nil;
+ }
+
+ tkclient->init();
+ dialog->init();
+ selectfile->init();
+ sys->pctl(Sys->NEWPGRP, nil); # so we can pass "exit" command to tkclient
+
+ file := "";
+ if(argv != nil)
+ argv = tl argv;
+ if(argv != nil)
+ file = hd argv;
+ central = chan of (ref File, string);
+ spawn control(ctxt);
+ <-central;
+ spawn brutus(ctxt, file);
+}
+
+# build menu button for dynamically generated menu
+buttoncfg(label, enable: string): string
+{
+ return "label .b."+label+" -text "+label + " " + enable +
+ ";bind .b."+label+" <Button-1> {send cmd "+label+"}" +
+ ";bind .b."+label+" <ButtonRelease-1> {}" +
+ ";bind .b."+label+" <Motion-Button-1> {}" +
+ ";bind .b."+label+" <Double-Button-1> {}" +
+ ";bind .b."+label+" <Double-ButtonRelease-1> {}" +
+ ";bind .b."+label+" <Enter> {.b."+label+" configure -background #EEEEEE}" +
+ ";bind .b."+label+" <Leave> {.b."+label+" configure -background #DDDDDD}";
+}
+
+tkchans(t: ref Tk->Toplevel): (chan of string, chan of string, chan of string, chan of string, chan of string, chan of string, chan of string)
+{
+ keys := chan of string;
+ tk->namechan(t, keys, "keys");
+ edit := chan of string;
+ tk->namechan(t, edit, "edit");
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+ but1 := chan of string;
+ tk->namechan(t, but1, "but1");
+ but2 := chan of string;
+ tk->namechan(t, but2, "but2");
+ but3 := chan of string;
+ tk->namechan(t, but3, "but3");
+ drag := chan of string;
+ tk->namechan(t, drag, "Wm_drag");
+ return (keys, edit, cmd, but1, but2, but3, drag);
+}
+
+control(ctxt: ref Context)
+{
+ (t, titlectl) := tkclient->toplevel(ctxt, SETFONT, Name, Tkclient->Appl);
+
+ # f is not used to store anything, just to simplify interfaces
+ # shared by control and brutus
+ f := ref File (t, 1, 0, 0, "", 0, DEFFONTNAME, DEFSIZE, DEFTAG, nil, 0, 0, 0, nil);
+
+ tkcmds(t, menu_cfg);
+ tkcmd(t, "frame .b");
+ tkcmd(t, buttoncfg("File", ""));
+ tkcmds(t, control_cfg);
+ tkcmds(t, input_cfg);
+ files = array[1] of ref File;
+ files[0] = f;
+
+ (keys, edit, cmd, but1, but2, but3, drag) := tkchans(t);
+
+ tkcmd(t, ".ft.t mark set typingstart 1.0; .ft.t mark gravity typingstart left");
+ central <-= (nil, ""); # signal readiness
+# spawn tkclient->wmctl(t, "task");
+ curfile: ref File;
+
+ plumbc := chan of (string, string);
+ spawn plumbproc(plumbc);
+
+ tkclient->startinput(t, "kbd"::"ptr"::nil);
+ tkclient->onscreen(t, nil);
+ tkclient->wmctl(t, "task");
+ for(;;) alt {
+ s := <-t.ctxt.kbd =>
+ tk->keyboard(t, s);
+ s := <-t.ctxt.ptr =>
+ tk->pointer(t, *s);
+
+ menu := <-t.ctxt.ctl or
+ menu = <-t.wreq or
+ menu = <-titlectl =>
+ if(menu == "exit"){
+ if(shutdown(ctxt, t)){
+ killplumb();
+ tkclient->wmctl(t, menu);
+ }
+ break;
+ }
+ # spawn tkclient->wmctl(t, menu);
+ tkclient->wmctl(t, menu);
+
+ ecmd := <-edit =>
+ editor(f, ecmd);
+ tkcmd(t, FOCUS);
+
+ c := <-cmd =>
+ (nil, s) := sys->tokenize(c, " ");
+ case hd s {
+ * =>
+ sys->print("unknown control cmd %s\n",c );
+ "File" =>
+ filemenu(t, 0, 0);
+ "new" =>
+ (name, ok, nil) := getfilename(ctxt, t, "file for new window", f.name, 1, 0, 0);
+ if(ok)
+ spawn brutus(ctxt, name);
+ "select" =>
+ n := int hd tl s;
+ if(n > len files)
+ break;
+ if(n > 0)
+ curfile = files[n];
+ tkcmd(files[n].tk, ". map; raise .; focus .ft.t");
+ "focus" =>
+ ;
+ }
+
+ (file, action) := <-central =>
+ (nil, s) := sys->tokenize(action, " ");
+ case hd s {
+ * =>
+ sys->print("control unknown central command %s\n", action);
+ "new" =>
+ curfile = file;
+ nfiles := array[len files+1] of ref File;
+ nfiles[0:] = files;
+ files = nfiles;
+ nfiles = nil; # make sure references don't linger
+ files[len files-1] = file;
+ "name" =>
+ name := nameof(file);
+ index := 0;
+ for(i:=1; i<len files; i++)
+ if(files[i] == file){
+ index = i;
+ break;
+ }
+ if(index == 0)
+ sys->print("can't find file\n");
+ "focus" =>
+ if(file != f)
+ curfile = file;
+ "select" =>
+ n := int hd tl s;
+ if(n >= len files)
+ break;
+ if(n > 0)
+ curfile = files[n];
+ tkcmd(files[n].tk, ". map; raise .; focus .ft.t; update");
+ "exiting" =>
+ if(file == nil)
+ break;
+ if(file == curfile)
+ curfile = nil;
+ index := 0;
+ for(i:=1; i<len files; i++)
+ if(files[i] == file){
+ index = i;
+ break;
+ }
+ if(index == 0)
+ sys->print("can't find file\n");
+ else{
+ # make a new one rather than slice, to clean up references
+ nfiles := array[len files-1] of ref File;
+ for(i=0; i<index; i++)
+ nfiles[i] = files[i];
+ for(; i<len nfiles; i++)
+ nfiles[i] = files[i+1];
+ files = nfiles;
+ }
+ file = nil;
+ }
+ c := <-keys =>
+ char := typing(f, c);
+ if(curfile!=nil && char=='\n' && insat(t, "end"))
+ execute(t, curfile, tkcmd(t, ".ft.t get insert-1line insert"));
+
+ c := <-but1 =>
+ mousebut1(f, c);
+
+ c := <-but2 =>
+ mousebut2(f, c);
+
+ c := <-but3 =>
+ mousebut3(f, c);
+
+ c := <-drag =>
+ if(len c < 6 || c[0:5] != "path=")
+ break;
+ spawn brutus(ctxt, c[5:]);
+
+ (fname, addr) := <-plumbc =>
+ for(i:=1; i<len files; i++)
+ if(files[i].name == fname){
+ tkcmd(files[i].tk, ". map; raise .; focus .ft.t");
+ showaddr(files[i], addr);
+ break;
+ }
+ if(i == len files){
+ if(addr != "")
+ spawn brutus(ctxt, fname+":"+addr);
+ else
+ spawn brutus(ctxt, fname);
+ }
+ }
+}
+
+brutus(ctxt: ref Context, filename: string)
+{
+ addr := "";
+ for(i:=len filename; --i>0; ){
+ if(filename[i] == ':'){
+ (ok, dir) := sys->stat(filename[0:i]);
+ if(ok >= 0){
+ addr = filename[i+1:];
+ filename = filename[0:i];
+ break;
+ }
+ }
+ }
+
+ (t, titlectl) := tkclient->toplevel(ctxt, SETFONT, Name, Tkclient->Appl);
+
+ f := ref File (t, 0, 0, 0, filename, 0, DEFFONTNAME, DEFSIZE, DEFTAG, nil, 0, 0, 0, nil);
+ f.configed = array[NTAG] of {* => 0};
+
+ tkcmds(t, menu_cfg);
+ tkcmd(t, "frame .b");
+ tkcmd(t, buttoncfg("File", ""));
+ tkcmd(t, buttoncfg("Ext", "-state disabled"));
+
+ tkcmds(t, brutus_cfg);
+ tkcmds(t, input_cfg);
+
+ # buttons work better when they grab the mouse
+ a := array[] of {".b.Tag", ".b.Applyfontnow", ".b.Applysizenow", ".b.Applyfontsizenow"};
+ for(i=0; i<len a; i++){
+ tkcmd(t, "bind "+a[i]+" <Button-1> +{grab set "+a[i]+"}");
+ tkcmd(t, "bind "+a[i]+" <ButtonRelease-1> +{grab release "+a[i]+"}");
+ }
+
+ (keys, edit, cmd, but1, but2, but3, drag) := tkchans(t);
+
+ configfont(f, "Heading");
+ configfont(f, "Title");
+ configfont(f, f.fonttag);
+ tkcmd(t, ".ft.t mark set typingstart 1.0; .ft.t mark gravity typingstart left");
+ tkcmd(t, "image create bitmap waiting -file cursor.wait");
+
+ central <-= (f, "new");
+ setfilename(f, filename);
+
+ if(filename != "")
+ if(loadfile(f, filename) < 0)
+ dialog->prompt(ctxt, t.image, "error -fg red",
+ "Open file",
+ sys->sprint("Can't read %s:\n%r", filename),
+ 0, "Continue" :: nil);
+ else
+ showaddr(f, addr);
+
+ tkclient->onscreen(t, nil);
+ tkclient->startinput(t, "kbd"::"ptr"::nil);
+ for(;;) alt {
+ s := <-t.ctxt.kbd =>
+ tk->keyboard(t, s);
+ s := <-t.ctxt.ptr =>
+ tk->pointer(t, *s);
+
+ menu := <-t.ctxt.ctl or
+ menu = <-t.wreq or
+ menu = <-titlectl =>
+ case menu {
+ "exit" =>
+ if(f.dirty){
+ action := confirm(ctxt, t, nameof(f)+" is dirty", 1);
+ case action {
+ "cancel" =>
+ continue;
+ "exitclean" =>
+ if(dumpfile(f, f.name, f.fontsused) < 0)
+ continue;
+ break;
+ "exitdirty" =>
+ break;
+ }
+ }
+ central <-= (f, "exiting");
+ # this one tears down temporaries holding references to f
+ central <-= (nil, "exiting");
+ return;
+ "task" =>
+ tkcmd(t, ". unmap");
+ * =>
+ tkclient->wmctl(t, menu);
+ }
+
+ ecmd := <-edit =>
+ editor(f, ecmd);
+ tkcmd(t, FOCUS);
+
+ command := <-cmd =>
+ (nil, c) := sys->tokenize(command, " ");
+ case hd c {
+ * =>
+ sys->print("unknown command %s\n", command);
+ "File" =>
+ filemenu(t, 1, f.fontsok);
+ "Ext" =>
+ extmenu(t);
+ "new" =>
+ (name, ok, nil) := getfilename(ctxt, t, "file for new window", f.name, 1, 0, 0);
+ if(ok)
+ spawn brutus(ctxt, name);
+ "open" =>
+ if(f.dirty){
+ action := confirm(ctxt, t, nameof(f)+" is dirty", 1);
+ case action {
+ "cancel" =>
+ continue;
+ "exitclean" =>
+ if(dumpfile(f, f.name, f.fontsused) < 0)
+ continue;
+ break;
+ "exitdirty" =>
+ break;
+ }
+ }
+ (name, ok, nil) := getfilename(ctxt, t, "file for this window", f.name, 1, 0, 0);
+ if(ok && name!=""){
+ setfilename(f, name);
+ if(loadfile(f, name) < 0){
+ tkcmd(t, ".ft.t delete 1.0 end");
+ dialog->prompt(ctxt, t.image, "error -fg red",
+ "Open file",
+ sys->sprint("Can't open %s:\n%r", name),
+ 0, "Continue"::nil);
+ }
+ }
+ "name" =>
+ (name, ok, nil) := getfilename(ctxt, t, "remembered file name", f.name, 1, 0, 0);
+ if(ok){
+ if(name != f.name){
+ setfilename(f, name);
+ dirty(f, 1);
+ }
+ }
+ "write" =>
+ (name, ok, sgml) := getfilename(ctxt, t, "file to write", f.name, 1, 1, f.fontsused);
+ if(ok && name!=""){
+ if(f.name == ""){
+ setfilename(f, name);
+ dirty(f, 1);
+ }
+ dumpfile(f, name, sgml);
+ }
+ "fonts" =>
+ if(f.fontsok==0 && f.fontsused==0){
+ action := confirm(ctxt, t, "Converting "+nameof(f)+" to SGML", 0);
+ case action {
+ "cancel" =>
+ continue;
+ "exitdirty" =>
+ usingfonts(f);
+ dirty(f, 1);
+ }
+ }
+ enablefonts(f, !f.fontsok);
+ "language" =>
+ if(lang == "")
+ lang = "Hebrew";
+ else
+ lang = "";
+ "addext" =>
+ ext := hd tl c;
+ (args, ok, nil) := getfilename(ctxt, t, "parameters for "+ext, "", 0, 0, 0);
+ if(ok){
+ tkcmd(t, "cursor -image waiting; update");
+ addextension(f, ext+" "+args, nil);
+ usingfonts(f);
+ dirty(f, 1);
+ tkcmd(t, "cursor -default; update");
+ }
+ "select" =>
+ central <-= (f, command);
+ "tag" =>
+ tageditor(ctxt, f);
+ tkcmd(t, FOCUS);
+ "font" =>
+ f.font = hd tl c;
+ tkcmd(t, ".b.Font configure -text "+f.font+";"+UPDATE);
+ f.fonttag = f.font+"."+string f.size;
+ configfont(f, f.fonttag);
+ if(changefont(f, f.font))
+ dirty(f, 1);
+ "size" =>
+ sz := hd tl c;
+ tkcmd(t, ".b.Size configure -text "+sz+"pt; update");
+ f.size = int sz;
+ f.fonttag = f.font+"."+string f.size;
+ configfont(f, f.fonttag);
+ if(changesize(f, string f.size))
+ dirty(f, 1);
+ "applyfont" =>
+ f.applyfont = int tkcmd(t, "variable Applyfont");
+ if(f.applyfont)
+ configfont(f, f.fonttag);
+ "applyfontnow" =>
+ if(changefont(f, f.font))
+ dirty(f, 1);
+ "applysizenow" =>
+ if(changesize(f, string f.size))
+ dirty(f, 1);
+ "applyfontsizenow" =>
+ if(changefontsize(f, f.fonttag))
+ dirty(f, 1);
+ "put" =>
+ dumpfile(f, f.name, f.fontsused);
+ "focus" =>
+ central <-= (f, "focus");
+ }
+
+ c := <-keys =>
+ typing(f, c);
+
+ c := <-but1 =>
+ mousebut1(f, c);
+
+ c := <-but2 =>
+ mousebut2(f, c);
+
+ c := <-but3 =>
+ mousebut3(f, c);
+
+ c := <-drag =>
+ if(len c < 6 || c[0:5] != "path=")
+ break;
+ spawn brutus(ctxt, c[5:]);
+ }
+}
+
+kbdremap(c: int) : (int, int)
+{
+ tab: array of Remaptab;
+
+ dir := 1;
+ case lang{
+ "" =>
+ return (c, dir);
+ "Hebrew" =>
+ tab = hebrewtab;
+ dir = -1;
+ * =>
+ sys->print("unknown language %s\n", lang);
+ return (c, dir);
+ }
+ for(i:=0; i<len tab; i++)
+ if(c == tab[i].in)
+ return (tab[i].out, dir);
+ return (c, 1);
+}
+
+typing(f: ref File, c: string): int
+{
+ t := f.tk;
+ char := c[1];
+ if(char == '\\')
+ char = c[2];
+ update := ";.ft.t see insert;"+UPDATE;
+ if(char != ESC)
+ cut(f, 1);
+ case char {
+ * =>
+ dir := 1;
+ if(c[1] != '\\') # safe character; remap it
+ (c[1], dir) = kbdremap(char);
+ s := ".ft.t insert insert "+c;
+ if(dir < 0)
+ s += ";.ft.t mark set insert insert-1c";
+ if(f.applyfont){
+ usingfonts(f);
+ s += f.fonttag;
+ }
+ tkcmd(t, s+update);
+ if(f.fontsused && f.applyfont==0){
+ # nasty goo to make sure we don't insert text without a font tag;
+ # must ask after the fact if default rules set a tag.
+ names := tkcmd(t, ".ft.t tag names insert-1chars");
+ if(!somefont(names))
+ tkcmd(t, ".ft.t tag add "+DEFTAG+" insert-1chars");
+ }
+ dirty(f, 1);
+ ESC =>
+ if(nullsel(t))
+ tkcmd(t, ".ft.t tag add sel typingstart insert;"+
+ ".ft.t mark set typingstart insert");
+ else
+ cut(f, 1);
+ tkcmd(t, UPDATE);
+ BS =>
+ bs(f, "c");
+ BSL =>
+ bs(f, "l");
+ BSW =>
+ bs(f, "w");
+ }
+ return char;
+}
+
+bs(f: ref File, c: string)
+{
+ if(!insat(f.tk, "1.0")){
+ tkcmd(f.tk, ".ft.t tkTextDelIns -"+c+";.ft.t see insert;"+UPDATE);
+ dirty(f, 1);
+ }
+}
+
+mousebut1(f: ref File, c: string)
+{
+ f.button1 = (c == "pressed");
+ f.button3 = 0; # abort any pending button 3 action
+ tkcmd(f.tk, ".ft.t mark set typingstart insert");
+}
+
+mousebut2(f: ref File, c: string)
+{
+ if(f.button1){
+ cut(f, 1);
+ tk->cmd(f.tk, UPDATE);
+ }else{
+ (nil, l) := sys->tokenize(c, " ");
+ x := int hd l - 50;
+ y := int hd tl l - int tk->cmd(f.tk, ".m yposition "+menuindex) - 10;
+# tkcmd(f.tk, "focus .ft.t");
+ tkcmd(f.tk, ".m activate "+menuindex+"; .m post "+string x+" "+string y+
+ "; update");
+ }
+}
+
+mousebut3(f: ref File, c: string)
+{
+ t := f.tk;
+ if(c == "pressed"){
+ f.button3 = 1;
+ if(f.button1){
+ paste(f);
+ tk->cmd(t, "update");
+ }
+ return;
+ }
+ if(!plumbed || f.button3==0 || f.button1!=0)
+ return;
+ f.button3 = 0;
+ # Plumb message triggered by release of button 3
+ (nil, l) := sys->tokenize(c, " ");
+ x := int hd tl l;
+ y := int hd tl tl l;
+ index := tk->cmd(t, ".ft.t index @"+string x+","+string y);
+ selindex := tk->cmd(t, ".ft.t tag ranges sel");
+ if(selindex != "")
+ insel := tk->cmd(t, ".ft.t compare sel.first <= "+index)=="1" &&
+ tk->cmd(t, ".ft.t compare sel.last >= "+index)=="1";
+ else
+ insel = 0;
+ attr := "";
+ if(insel)
+ text := tk->cmd(t, ".ft.t get sel.first sel.last");
+ else{
+ # have line with text in it
+ # now extract whitespace-bounded string around click
+ (nil, w) := sys->tokenize(index, ".");
+ charno := int hd tl w;
+ left := tk->cmd(t, ".ft.t index {"+index+" linestart}");
+ right := tk->cmd(t, ".ft.t index {"+index+" lineend}");
+ line := tk->cmd(t, ".ft.t get "+left+" "+right);
+ for(i:=charno; i>0; --i)
+ if(line[i-1]==' ' || line[i-1]=='\t')
+ break;
+ for(j:=charno; j<len line; j++)
+ if(line[j]==' ' || line[j]=='\t')
+ break;
+ text = line[i:j];
+ attr = "click="+string (charno-i);
+ }
+ msg := ref Msg(
+ "Brutus",
+ "",
+ directory(f),
+ "text",
+ attr,
+ array of byte text);
+ if(msg.send() < 0)
+ sys->fprint(sys->fildes(2), "brutus: plumbing write error: %r\n");
+}
+
+directory(f: ref File): string
+{
+ for(i:=len f.name; --i>=0;)
+ if(f.name[i] == '/'){
+ if(i == 0)
+ i++;
+ return f.name[0:i];
+ }
+ return curdir;
+}
+
+enablefonts(f: ref File, enable: int)
+{
+ for(i:=0; i<len fontbuts; i++)
+ tkcmd(f.tk, fontbuts[i] + " configure -state "+enabled[enable]);
+ tkcmd(f.tk, "update");
+ f.fontsok = enable;
+}
+
+filemenu(t: ref tk->Toplevel, buttons, fontsok: int)
+{
+ tkcmd(t, "menu .b.Filemenu");
+ tkcmd(t, ".b.Filemenu add command -label New -command {send cmd new}");
+ if(buttons){
+ tkcmd(t, ".b.Filemenu add command -label Open -command {send cmd open}");
+ tkcmd(t, ".b.Filemenu add command -label Name -command {send cmd name}");
+ tkcmd(t, ".b.Filemenu add command -label Write -command {send cmd write}");
+ if(fontsok)
+ pre := "Dis";
+ else
+ pre = "En";
+ tkcmd(t, ".b.Filemenu add command -label {"
+ +pre+"able Fonts} -command {send cmd fonts}");
+ if(lang == "")
+ pre = "En";
+ else
+ pre = "Dis";
+ tkcmd(t, ".b.Filemenu add command -label {"
+ +pre+"able Hebrew} -command {send cmd language}");
+ }
+ tkcmd(t, ".b.Filemenu add command -label {["+Name+"]} -command {send cmd select 0}");
+ if(files != nil)
+ for(i:=1; i<len files; i++){
+ name := nameof(files[i]);
+ if(files[i].dirty)
+ name = "{' "+name+"}";
+ else
+ name = "{ "+name+"}";
+ tkcmd(t, ".b.Filemenu add command -label "+name+
+ " -command {send cmd select "+string i+"}");
+ }
+ tkcmd(t, "bind .b.Filemenu <Unmap> {destroy .b.Filemenu}");
+ x := tk->cmd(t, ".ft.scroll cget actx");
+ y := tk->cmd(t, ".ft.scroll cget acty");
+ tkcmd(t, ".b.Filemenu post "+x+" "+y+"; grab set .b.Filemenu; update");
+}
+
+extmenu(t: ref tk->Toplevel)
+{
+ fd := sys->open(EXTDIR, Sys->OREAD);
+ if(fd == nil || ((n,dir):=sys->dirread(fd)).t0<=0){
+ sys->print("%s: can't find extension directory %s: %r\n", Name, EXTDIR);
+ return;
+ }
+
+ tkcmd(t, "menu .b.Extmenu");
+ for(i:=0; i<n; i++){
+ name := dir[i].name;
+ if(len name>4 && name[len name-4:]==".dis"){
+ name = name[0:len name-4];
+ tkcmd(t, ".b.Extmenu add command -label {Add "+name+
+ "} -command {send cmd addext "+name+"}");
+ }
+ }
+
+ tkcmd(t, "bind .b.Extmenu <Unmap> {destroy .b.Extmenu}");
+ x := tk->cmd(t, ".ft.scroll cget actx");
+ y := tk->cmd(t, ".ft.scroll cget acty");
+ tkcmd(t, ".b.Extmenu post "+x+" "+y+"; grab set .b.Extmenu; update");
+}
+
+basepath(file: string): (string, string)
+{
+ for(i := len file-1; i >= 0; i--) {
+ if(file[i] == '/')
+ return (file[0:i], file[i+1:]);
+ }
+ return (".", file);
+}
+
+putbut(f: ref File)
+{
+ state := enabled[f.dirty];
+ if(f.name != "")
+ tkcmd(f.tk, ".b.Put configure -state "+state+"; update");
+}
+
+dirty(f: ref File, nowdirty: int)
+{
+ if(f.isctl)
+ return;
+ old := f.dirty;
+ f.dirty = nowdirty;
+ if(old != nowdirty){
+ setfilename(f, f.name);
+ putbut(f);
+ }
+}
+
+setfilename(f: ref File, name: string)
+{
+ oldname := f.name;
+ f.name = name;
+ if(oldname=="" && name!="")
+ putbut(f);
+ name = Name + ": \"" +nameof(f)+ "\"";
+ if(f.dirty)
+ name += " (dirty)";
+ tkclient->settitle(f.tk, name);
+ tkcmd(f.tk, UPDATE);
+ central <-= (f, "name");
+}
+
+configfont(f: ref File, tag: string)
+{
+ for(i:=0; i<NTAG; i++)
+ if(tag == tagname[i]){
+ if(f.configed[i] == 0){
+ tkcmd(f.tk, ".ft.t tag configure "+tag+" "+tagconfig[i]);
+ f.configed[i] = 1;
+ }
+ return;
+ }
+ sys->print("Brutus: can't configure font %s\n", tag);
+}
+
+insat(t: ref Tk->Toplevel, mark: string): int
+{
+ return tkcmd(t, ".ft.t compare insert == "+mark) == "1";
+}
+
+isalnum(s: string): int
+{
+ if(s == "")
+ return 0;
+ c := s[0];
+ if('a' <= c && c <= 'z')
+ return 1;
+ if('A' <= c && c <= 'Z')
+ return 1;
+ if('0' <= c && c <= '9')
+ return 1;
+ if(c == '_')
+ return 1;
+ if(c > 16rA0)
+ return 1;
+ return 0;
+}
+
+editor(f: ref File, ecmd: string)
+{
+
+ case ecmd {
+ "cut" =>
+ menuindex = "0";
+ cut(f, 1);
+
+ "paste" =>
+ menuindex = "1";
+ paste(f);
+
+ "snarf" =>
+ menuindex = "2";
+ if(nullsel(f.tk))
+ return;
+ snarf(f);
+
+ "look" =>
+ menuindex = "3";
+ look(f);
+ }
+ tkcmd(f.tk, UPDATE);
+}
+
+nullsel(t: ref Tk->Toplevel): int
+{
+ return tkcmd(t, ".ft.t tag ranges sel") == "";
+}
+
+cut(f: ref File, snarfit: int)
+{
+ if(nullsel(f.tk))
+ return;
+ dirty(f, 1);
+ if(snarfit)
+ snarf(f);
+ # sometimes when clicking fast, selection and insert point can
+ # separate. the only time this really matters is when typing into
+ # a double-clicked selection. it's easy to fix here.
+ tkcmd(f.tk, ".ft.t mark set insert sel.first;.ft.t delete sel.first sel.last");
+}
+
+snarf(f: ref File)
+{
+ # convert sel.first and sel.last to numeric forms because sgml()
+ # must clear selection to avoid <sel> tags in result.
+ (nil, sel) := sys->tokenize(tkcmd(f.tk, ".ft.t tag ranges sel"), " ");
+ snarftext = tkcmd(f.tk, ".ft.t get "+hd sel+" "+hd tl sel);
+ snarfsgml = sgml(f.tk, "-sgml", hd sel, hd tl sel);
+ tkclient->snarfput(snarftext);
+}
+
+paste(f: ref File)
+{
+# good question
+ snarftext = tkclient->snarfget();
+ if(snarftext == "" && (f.fontsused == 0 || snarfsgml == nil))
+ return;
+ cut(f, 0);
+ dirty(f, 1);
+
+ t := f.tk;
+ start := tkcmd(t, ".ft.t index insert");
+ if(f.fontsused == 0)
+ tkcmd(t, ".ft.t insert insert '"+snarftext);
+ else if(f.applyfont)
+ tkcmd(t, ".ft.t insert insert "+tk->quote(snarftext)+" "+f.fonttag);
+ else
+ insert(f, snarfsgml);
+ tkcmd(t, ".ft.t tag add sel "+start+" insert");
+}
+
+look(f: ref File)
+{
+ t := f.tk;
+ (sel0, sel1) := word(t);
+ if(sel0 == nil)
+ return;
+ text := tkcmd(t, ".ft.t get "+sel0+" "+sel1);
+ if(text == nil)
+ return;
+ tkcmd(t, "cursor -image waiting; update");
+ search(nil, f, text, 0, 0);
+ tkcmd(t, "cursor -default; update");
+}
+
+# First time fonts are used explicitly, establish font tags for all extant text.
+usingfonts(f: ref File)
+{
+ if(f.fontsused)
+ return;
+ tkcmd(f.tk, ".ft.t tag add "+DEFTAG+" 1.0 end");
+ f.fontsused = 1;
+}
+
+word(t: ref Tk->Toplevel): (string, string)
+{
+ start := "sel.first";
+ end := "sel.last";
+ if(nullsel(t)){
+ insert := tkcmd(t, ".ft.t index insert");
+ start = tkcmd(t, ".ft.t index {insert wordstart}");
+ if(insert == start){ # tk's definition of 'wordstart' is bogus
+ # if at beginning, tk->cmd will return !error and a0 will be false.
+ a0 := isalnum(tk->cmd(t, ".ft.t get insert-1chars"));
+ a1 := isalnum(tk->cmd(t, ".ft.t get insert"));
+ if(a0==0 && a1==0)
+ return (nil, nil);
+ if(a1 == 0)
+ start = tkcmd(t, ".ft.t index {insert-1chars wordstart}");
+ }
+ end = tkcmd(t, ".ft.t index {"+start+" wordend}");
+ if(start == end)
+ return (nil, nil);
+ }
+ return (start, end);
+}
+
+# Change the font associated with the selection
+changefont(f: ref File, font: string): int
+{
+ t := f.tk;
+ (sel0, sel1) := word(f.tk);
+ mod := 0;
+ if(sel0 == nil)
+ return mod;
+ usingfonts(f);
+ for(i:=0; i<NFONT; i++){
+ if(fontname[i] == font)
+ continue;
+ for(j:=0; j<NSIZE; j++){
+ tag := fontname[i]+"."+sizename[j];
+ start := sel0;
+ for(;;){
+ range := tkcmd(t, ".ft.t tag nextrange "+tag+" "+start+" "+sel1);
+ if(len range > 0 && range[0] == '!')
+ break;
+ (nil, tt) := sys->tokenize(range, " ");
+ if(tt == nil)
+ break;
+ tkcmd(t, ".ft.t tag remove "+tag+" "+hd tt+" "+hd tl tt);
+ fs := font+"."+sizename[j];
+ tkcmd(t, ".ft.t tag add "+fs+" "+hd tt+" "+hd tl tt);
+ configfont(f, fs);
+ start = hd tl tt;
+ mod = 1;
+ }
+ }
+ }
+ tkcmd(t, UPDATE);
+ return mod;
+}
+
+# See if tag list includes a font name
+somefont(tag: string): int
+{
+ (nil, tt) := sys->tokenize(tag, " ");
+ for(; tt!=nil; tt=tl tt)
+ for(i:=0; i<NFONT*NSIZE; i++){
+ if(tagname[i] == hd tt)
+ return 1;
+ }
+ return 0;
+}
+
+# Change the size associated with the selection
+changesize(f: ref File, size: string): int
+{
+ t := f.tk;
+ (sel0, sel1) := word(f.tk);
+ mod := 0;
+ if(sel0 == nil)
+ return mod;
+ usingfonts(f);
+ for(i:=0; i<NFONT; i++){
+ for(j:=0; j<NSIZE; j++){
+ if(sizename[j] == size)
+ continue;
+ tag := fontname[i]+"."+sizename[j];
+ start := sel0;
+ for(;;){
+ range := tkcmd(t, ".ft.t tag nextrange "+tag+" "+start+" "+sel1);
+ if(len range > 0 && range[0] == '!')
+ break;
+ (nil, tt) := sys->tokenize(range, " ");
+ if(tt == nil)
+ break;
+ tkcmd(t, ".ft.t tag remove "+tag+" "+hd tt+" "+hd tl tt);
+ fs := fontname[i]+"."+size;
+ tkcmd(t, ".ft.t tag add "+fs+" "+hd tt+" "+hd tl tt);
+ configfont(f, fs);
+ start = hd tl tt;
+ mod = 1;
+ }
+ }
+ }
+ tkcmd(t, UPDATE);
+ return mod;
+}
+
+# Change the font and size associated with the selection
+changefontsize(f: ref File, newfontsize: string): int
+{
+ t := f.tk;
+ (sel0, sel1) := word(f.tk);
+ if(sel0 == nil)
+ return 0;
+ usingfonts(f);
+ (nil, names) := sys->tokenize(tkcmd(t, ".ft.t tag names"), " ");
+ # clear old tags
+ tags := tagname[0:NFONT*NSIZE];
+ for(l:=names; l!=nil; l=tl l)
+ for(i:=0; i<len tags; i++)
+ if(tags[i] == hd l)
+ tkcmd(t, ".ft.t tag remove "+hd l+" "+sel0+" "+sel1);
+ tkcmd(t, ".ft.t tag add "+newfontsize+" "+sel0+" "+sel1+"; update");
+ return 1;
+}
+
+listtostring(l: list of string): string
+{
+ s := "{";
+ while(l != nil){
+ if(len s == 1)
+ s += hd l;
+ else
+ s += " " + hd l;
+ l = tl l;
+ }
+ s += "}";
+ return s;
+}
+
+# splitl based on indices rather than slices. this version returns char
+# position of the matching character.
+splitl(str: string, i, j: int, pat: string): int
+{
+ while(i < j){
+ c := str[i];
+ for(k:=len pat-1; k>=0; k--)
+ if(c == pat[k])
+ return i;
+ i++;
+ }
+ return i;
+}
+
+# splitstrl based on indices rather than slices. this version returns char
+# position of the beginning of the matching string.
+splitstrl(str: string, i, j: int, pat: string): int
+{
+ l := len pat;
+ if(l == 0) # shouldn't happen, but be safe
+ return j;
+ first := pat[0];
+ while(i <= j-l){
+ # check first char for speed
+ if(str[i] == first){
+ for(k:=1; k<l && str[i+k]==pat[k]; k++)
+ ;
+ if(k == l)
+ return i;
+ }
+ i++;
+ }
+ return j;
+}
+
+# place the text, as annotated by SGML tags, into document
+# where indicated by insert mark
+insert(f: ref File, sgml: string)
+{
+ taglist: list of string;
+
+ t := f.tk;
+ usingfonts(f);
+ if(f.applyfont)
+ taglist = f.fonttag :: taglist;
+ tag := listtostring(taglist);
+ end := len sgml;
+ j: int;
+ for(i:=0; i<end; i=j){
+ j = splitl(sgml, i, end, "<&");
+ tt := tag;
+ if(tt=="" || tt=="{}")
+ tt = DEFTAG; # can happen e.g. when pasting plain text
+ if(j > i)
+ tkcmd(t, ".ft.t insert insert "+tk->quote(sgml[i:j])+" "+tt);
+ if(j < end)
+ case sgml[j] {
+ '&' =>
+ if(j+4<=end && sgml[j:j+4]=="&lt;"){
+ tkcmd(t, ".ft.t insert insert "+"{<} "+tt);
+ j += 4;
+ }else{
+ tkcmd(t, ".ft.t insert insert {&} "+tt);
+ j += 1;
+ }
+ '<' =>
+ (nc, newtag, on) := tagstring(sgml, j, end);
+ if(nc < 0){
+ tkcmd(t, ".ft.t insert insert "+"{<} "+tt);
+ j += 1;
+ }else if(len newtag>9 && newtag[0:10]=="Extension "){
+ addextension(f, newtag[10:], taglist);
+ j += nc;
+ }else if(len newtag>9 && newtag[0:7]=="Window "){
+ repostextension(f, newtag[7:], taglist);
+ j += nc;
+ }else{
+ if(on){
+ taglist = newtag :: taglist;
+ configfont(f, newtag);
+ }else{
+ taglist = drop(taglist, newtag);
+ if(f.applyfont && hasfonts(taglist)==0)
+ taglist = f.fonttag :: taglist;
+ }
+ j += nc;
+ tag = listtostring(taglist);
+ }
+ }
+ }
+}
+
+drop(l: list of string, s: string): list of string
+{
+ n: list of string;
+ while(l != nil){
+ if(s != hd l)
+ n = hd l :: n;
+ l = tl l;
+ }
+ return n;
+}
+
+extid := 0;
+addextension(f: ref File, s: string, taglist: list of string)
+{
+ for(i:=0; i<len s; i++)
+ if(s[i] == ' ')
+ break;
+ if(i == 0 || i == len s){
+ sys->print("Brutus: badly formed extension %s\n", s);
+ return;
+ }
+ modname := s[0:i];
+ s = s[i+1:];
+
+ mod: Brutusext;
+ for(el:=f.extensions; el!=nil; el=tl el)
+ if(modname == (hd el).modname){
+ mod = (hd el).mod;
+ break;
+ }
+
+ if(mod == nil){
+ file := modname;
+ if(i < 4 || file[i-4:i] != ".dis")
+ file += ".dis";
+ if(file[0] != '/')
+ file = "/dis/wm/brutus/" + file;
+ mod = load Brutusext file;
+ if(mod == nil){
+ sys->print("%s: can't load module %s: %r\n", Name, file);
+ return;
+ }
+ }
+ mkextension(f, mod, modname, s, taglist);
+}
+
+repostextension(f: ref File, tkname: string, taglist: list of string)
+{
+ mod: Brutusext;
+ for(el:=f.extensions; el!=nil; el=tl el)
+ if(tkname == (hd el).tkname){
+ mod = (hd el).mod;
+ break;
+ }
+ if(mod == nil){
+ sys->print("Brutus: can't find extension widget %s: %r\n", tkname);
+ return;
+ }
+
+ mkextension(f, mod, (hd el).modname, (hd el).args, taglist);
+}
+
+mkextension(f: ref File, mod: Brutusext, modname, args: string, taglist: list of string)
+{
+ t := f.tk;
+
+ name := ".ext"+string extid++;
+ mod->init(sys, draw, bufio, tk, tkclient);
+ err := mod->create(f.name, t, name, args);
+ if(err != ""){
+ sys->print("%s: can't create extension widget %s: %s\n", Name, modname, err);
+ return;
+ }
+ tkcmd(t, ".ft.t window create insert -window "+name);
+ while(taglist != nil){
+ tkcmd(t, ".ft.t tag add "+hd taglist+" "+name);
+ taglist = tl taglist;
+ }
+ f.extensions = ref Ext(name, modname, mod, args) :: f.extensions;
+}
+
+# rewrite <window .ext1> tags into <Extension module args>
+extrewrite(f: ref File, sgml: string): string
+{
+ if(f.extensions == nil)
+ return sgml;
+
+ new := "";
+
+ end := len sgml;
+ j: int;
+ for(i:=0; i<end; i=j){
+ j = splitstrl(sgml, i, end, "<Window ");
+ if(j > i)
+ new += sgml[i:j];
+ if(j < end){
+ j += 8;
+ for(k:=j; sgml[k]!='>' && k<end; k++)
+ ;
+ tkname := sgml[j:k];
+ for(el:=f.extensions; el!=nil; el=tl el)
+ if((hd el).tkname == tkname)
+ break;
+ if(el == nil)
+ sys->print("%s: unrecognized extension %s\n", Name, tkname);
+ else{
+ e := hd el;
+ new += "<Extension "+e.modname+" "+e.args+">";
+ }
+ j = k+1; # skip '>'
+ }
+ }
+ return new;
+}
+
+hasfonts(l: list of string): int
+{
+ for(i:=0; i<NFONT*NSIZE; i++)
+ for(ll:=l; ll!=nil; ll=tl ll)
+ if(hd ll == tagname[i])
+ return 1;
+ return 0;
+}
+
+# s[i] is known to be a less-than sign
+tagstring(s: string, i, end: int): (int, string, int)
+{
+ tag: string;
+
+ j := splitl(s, i+1, end, ">");
+ if(j==end || s[j]!='>')
+ return (-1, "", 0);
+ nc := (j-i)+1;
+ on := 1;
+ if(s[i+1] == '/'){
+ on = 0;
+ i++;
+ }
+ tag = s[i+1:j];
+# NEED TO CHECK VALIDITY OF TAG
+ return (nc, tag, on);
+}
+
+sgml(t: ref Tk->Toplevel, flag, start, end: string): string
+{
+ # turn off selection, to avoid getting that in output
+ sel := tkcmd(t, ".ft.t tag ranges sel");
+ if(sel != "")
+ tkcmd(t, ".ft.t tag remove sel "+sel);
+ s := tkcmd(t, ".ft.t dump "+flag+" "+start+" "+end);
+ if(sel != "")
+ tkcmd(t, ".ft.t tag add sel "+sel);
+ return s;
+}
+
+loadfile(f: ref File, file: string): int
+{
+ f.size = DEFSIZE;
+ f.font = DEFFONTNAME;
+ f.fonttag = DEFTAG;
+ f.fontsused = 0;
+ enablefonts(f, 0);
+ t := f.tk;
+ tkcmd(t, ".b.Font configure -text "+f.font);
+ tkcmd(t, ".b.Size configure -text "+string f.size+"pt");
+ tkcmd(t, "cursor -image waiting; update");
+ r := loadfile1(f, file);
+ tkcmd(t, "cursor -default");
+ return r;
+}
+
+loadfile1(f: ref File, file: string): int
+{
+ fd := bufio->open(file, Sys->OREAD);
+ if(fd == nil)
+ return -1;
+ (ok, dir) := sys->fstat(fd.fd);
+ if(ok < 0){
+ fd.close();
+ return -1;
+ }
+ l := int dir.length;
+ a := array[l] of byte;
+ n := fd.read(a, len a);
+ fd.close();
+ if(n != len a)
+ return -1;
+ t := f.tk;
+ tkcmd(t, ".ft.t delete 1.0 end");
+ if(len a>=7 && string a[0:7]=="<SGML>\n")
+ insert(f, string a[7:n]);
+ else
+ tkcmd(t, ".ft.t insert 1.0 '"+string a[0:n]);
+ dirty(f, 0);
+ tkcmd(t, ".ft.t mark set insert 1.0; update");
+ return 1;
+}
+
+dumpfile(f: ref File, file: string, sgml: int): int
+{
+ tkcmd(f.tk, "cursor -image waiting");
+ r := dumpfile1(f, file, sgml);
+ tkcmd(f.tk, "cursor -default");
+ return r;
+}
+
+dumpfile1(f: ref File, file: string, sgml: int): int
+{
+ if(writefile(f, file, sgml) < 0){
+ dialog->prompt(ctxt, f.tk.image, "error -fg red",
+ "Write file",
+ sys->sprint("Can't write %s:\n%r", file),
+ 0, "Continue"::nil);
+ tkcmd(f.tk, FOCUS);
+ return -1;
+ }
+ return 1;
+}
+
+writefile(f: ref File, file: string, sgmlfmt: int): int
+{
+ if(file == "")
+ return -1;
+ fd := bufio->create(file, Sys->OWRITE, 8r666);
+ if(fd == nil)
+ return -1;
+
+ t := f.tk;
+ flag := "";
+ if(sgmlfmt){
+ flag = "-sgml";
+ prefix := "<SGML>\n";
+ if(f.fontsused == 0)
+ prefix += "<"+DEFTAG+">";
+ x := array of byte prefix;
+ if(fd.write(x, len x) != len x){
+ fd.close();
+ return -1;
+ }
+ }
+ sgmltext := sgml(t, flag, "1.0", "end");
+ if(sgmlfmt)
+ sgmltext = extrewrite(f, sgmltext);
+ a := array of byte sgmltext;
+ if(fd.write(a, len a) != len a){
+ fd.close();
+ return -1;
+ }
+ if(sgmlfmt && f.fontsused==0){
+ suffix := array of byte ("</"+DEFTAG+">");
+ if(fd.write(suffix, len suffix) != len suffix){
+ fd.close();
+ return -1;
+ }
+ }
+ if(fd.flush() < 0){
+ fd.close();
+ return -1;
+ }
+ fd.close();
+ if(file == f.name){
+ dirty(f, sgmlfmt!=f.fontsused);
+ tkcmd(t, UPDATE);
+ }
+ return 1;
+}
+
+shutdown(s: ref Draw->Context, t: ref Tk->Toplevel): int
+{
+ for(i:=1; i<len files; i++){
+ f := files[i];
+ if(f.dirty){
+ action := confirm(s, t, "file "+nameof(f)+" is dirty", 1);
+ case action {
+ "cancel" =>
+ return 0;
+ "exitclean" =>
+ if(dumpfile(f, f.name, f.fontsused) < 0)
+ return 0;
+ "exitdirty" =>
+ break;
+ }
+ }
+ }
+ return 1;
+}
+
+nameof(f: ref File): string
+{
+ s := f.name;
+ if(s == "")
+ s = "(unnamed)";
+ return s;
+}
+
+tkcmd(t: ref Tk->Toplevel, s: string): string
+{
+ res := tk->cmd(t, s);
+ if(len res > 0 && res[0] == '!')
+ sys->print("%s: tk error executing '%s': %s\n", Name, s, res);
+ return res;
+}
+
+confirm_cfg := array[] of {
+ "frame .f -borderwidth 2 -relief groove -padx 3 -pady 3",
+ "frame .f.f",
+# "label .f.f.l -bitmap error -foreground red",
+ "label .f.f.l -text Warning:",
+ "label .f.f.m",
+ "button .f.exitclean -text { Write and Proceed } -width 17w -command {send cmd exitclean}",
+ "button .f.exitdirty -text { Proceed } -width 17w -command {send cmd exitdirty}",
+ "button .f.cancel -text { Cancel } -width 17w -command {send cmd cancel}",
+ "pack .f.f.l .f.f.m -side left",
+ "pack .f.f .f.exitclean .f.exitdirty .f.cancel -padx 10 -pady 10",
+ "pack .f",
+};
+
+widget(parent: ref Tk->Toplevel, ctxt: ref Draw->Context, cfg: array of string): ref Tk->Toplevel
+{
+ x := int tk->cmd(parent, ". cget -x");
+ y := int tk->cmd(parent, ". cget -y");
+ where := sys->sprint("-x %d -y %d ", x+45, y+25);
+ (t,nil) := tkclient->toplevel(ctxt, where+SETFONT+" -borderwidth 2 -relief raised", "", tkclient->Plain);
+ tkcmds(t, cfg);
+ return t;
+}
+
+tkcmds(top: ref Tk->Toplevel, a: array of string)
+{
+ for(i := 0; i < len a; i++)
+ v := tk->cmd(top, a[i]);
+}
+
+confirm(ctxt: ref Draw->Context, parent: ref Tk->Toplevel, message: string, write: int): string
+{
+ s := confirm1(ctxt, parent, message, write);
+ tkcmd(parent, FOCUS);
+ return s;
+}
+
+confirm1(ctxt: ref Draw->Context, parent: ref Tk->Toplevel, message: string, write: int): string
+{
+ t := widget(parent, ctxt, confirm_cfg);
+ tkcmd(t, ".f.f.m configure -text '"+message);
+ if(write == 0)
+ tkcmd(t, "destroy .f.exitclean");
+ tkcmd(t, UPDATE);
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+ return <-cmd;
+}
+
+getfilename_cfg := array[] of {
+ "frame .f",
+ "label .f.Message",
+ "entry .f.Name -width 25w",
+ "checkbutton .f.SGML -text { Write SGML } -variable SGML",
+ "button .f.Ok -text { OK } -width 14w -command {send cmd ok}",
+ "button .f.Browse -text { Browse } -width 14w -command {send cmd browse}",
+ "button .f.Cancel -text { Cancel } -width 14w -command {send cmd cancel}",
+ "bind .f.Name <Control-j> {send cmd ok}",
+ "pack .f.Message .f.Name .f.SGML .f.Ok .f.Browse .f.Cancel -padx 10 -pady 10",
+ "pack .f",
+ "focus .f.Name",
+};
+
+getfilename(ctxt: ref Draw->Context, parent: ref Tk->Toplevel, message, name: string, browse, sgml, nowsgml: int): (string, int, int)
+{
+ (s, i, issgml) := getfilename1(ctxt, parent, message, name, browse, sgml, nowsgml);
+ tkcmd(parent, FOCUS);
+ return (s, i, issgml);
+}
+
+getfilename1(ctxt: ref Draw->Context, parent: ref Tk->Toplevel, message, name: string, browse, sgml, nowsgml: int): (string, int, int)
+{
+ t := widget(parent, ctxt, getfilename_cfg);
+ tkcmds(t, getfilename_cfg);
+
+ tkcmd(t, ".f.Message configure -text '"+message);
+ tk->cmd(t, ".f.Name insert 0 "+name);
+ if(browse == 0)
+ tkcmd(t, "destroy .f.Browse");
+ if(sgml == 0)
+ tkcmd(t, "destroy .f.SGML");
+ else if(nowsgml)
+ tkcmd(t, ".f.SGML select");
+ tkcmd(t, UPDATE);
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+ tkclient->onscreen(t, "exact");
+ tkclient->startinput(t, "kbd"::"ptr"::nil);
+ for(;;) alt {
+ s := <-t.ctxt.kbd =>
+ tk->keyboard(t, s);
+ s := <-t.ctxt.ptr =>
+ tk->pointer(t, *s);
+ c := <-cmd =>
+ case c {
+ "ok" =>
+ return (tkcmd(t, ".f.Name get"), 1, int tkcmd(t, "variable SGML"));
+ "cancel" =>
+ return ("", 0, 0);
+ "browse" =>
+ name = tkcmd(t, ".f.Name get");
+ (dir, path) := basepath(name);
+
+ pat := list of {
+ "* (All files)",
+ "*.sgml (SGML dump files)",
+ "*.html (Web source files)",
+ "*.tex (Latex source files)",
+ "*.[bm] (Limbo source files)"
+ };
+
+ path = selectfile->filename(ctxt, parent.image, message, pat, dir);
+ if(path != "")
+ name = path;
+ tk->cmd(t, ".f.Name delete 0 end; .f.Name insert 0 "+name+";focus .f.Name; update");
+ if(path != "")
+ return (name, 1, int tkcmd(t, "variable SGML"));
+ }
+ }
+}
+
+tageditor(ctxt: ref Draw->Context, f: ref File)
+{
+ (start, end) := word(f.tk);
+ if(start == nil)
+ return;
+ cfg := array[100] of string;
+ i := 0;
+ cfg[i++] = "frame .f";
+ (nil, names) := sys->tokenize(tkcmd(f.tk, ".ft.t tag names "+start), " ");
+ pack := "pack";
+ set := array[NEXTRA] of int;
+ for(j:=0; j<NEXTRA; j++){
+ n := tagname[j+NFONT*NSIZE];
+ cfg[i++] = "checkbutton .f.c"+string j+" -variable c"+string j+
+ " -text {"+n+"} -command {send cmd "+string j+"} -anchor w";
+ pack += " .f.c"+string j;
+ set[j] = 0;
+ for(l:=names; l!=nil; l=tl l)
+ if(hd l == n){
+ cfg[i++] = ".f.c"+string j+" select";
+ set[j] = 1;
+ }
+ }
+ cfg[i++] = "button .f.Ok -text { OK } -width 6w -command {send cmd ok}";
+ cfg[i++] = "button .f.Cancel -text { Cancel } -width 6w -command {send cmd cancel}";
+ cfg[i++] = pack + " -padx 3 -pady 0 -fill x";
+ cfg[i++] = "pack .f.Ok .f.Cancel -padx 2 -pady 2 -side left";
+ cfg[i++] = "pack .f; grab set .f; update";
+ t := widget(f.tk, ctxt, cfg[0:i]);
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+
+ loop:
+ for(;;){
+ case c := <-cmd {
+ "ok" =>
+ break loop;
+ "cancel" =>
+ return;
+ * =>
+ j = int c;
+ set[j] = (tkcmd(t, "variable c"+c) == "1");
+ }
+ }
+ for(j=0; j<NEXTRA; j++){
+ s := tagname[j+NFONT*NSIZE];
+ if(set[j]){
+ configfont(f, s);
+ tkcmd(f.tk, ".ft.t tag add "+s+" "+start+" "+end);
+ }else
+ tkcmd(f.tk, ".ft.t tag remove "+s+" "+start+" "+end);
+ }
+ dirty(f, 1);
+ usingfonts(f);
+ tkcmd(f.tk, UPDATE);
+}
+
+plumbpid: int;
+plumbproc(plumbc: chan of (string, string))
+{
+ plumbpid = sys->pctl(0, nil);
+
+ for(;;){
+ msg := Msg.recv();
+ if(msg == nil){
+ sys->print("Brutus: can't read /chan/plumb.edit: %r\n");
+ plumbpid = 0;
+ return;
+ }
+ if(msg.kind != "text"){
+ sys->print("Brutus: can't interpret '%s' kind of message\n", msg.kind);
+ continue;
+ }
+ text := string msg.data;
+ n := len text;
+ addr := "";
+ for(j:=0; j<n; j++)
+ if(text[j] == ':'){
+ addr = text[j+1:];
+ break;
+ }
+ file := text[0:j];
+ if(len file>0 && file[0]!='/' && len msg.dir>0){
+ if(msg.dir[len msg.dir-1] == '/')
+ file = msg.dir+file;
+ else
+ file = msg.dir+"/"+file;
+ }
+ plumbc <-= (file, addr);
+ }
+}
+
+killplumb()
+{
+ if(plumbed == 0)
+ return;
+ plumbmsg->shutdown();
+ if(plumbpid <= 0)
+ return;
+ fname := sys->sprint("#p/%d/ctl", plumbpid);
+ fd := sys->open(fname, sys->OWRITE);
+ if(fd != nil)
+ sys->write(fd, array of byte "kill\n", 8);
+}
+
+lastpat: string;
+
+execute(cmdwin: ref Tk->Toplevel, f: ref File, cmd: string)
+{
+ if(len cmd>1 && cmd[len cmd-1]=='\n')
+ cmd = cmd[0:len cmd-1];
+ if(cmd == "")
+ return;
+ if(cmd[0] == '/' || cmd[0]=='?'){
+ search(cmdwin, f, cmd[1:], cmd[0]=='?', 1);
+ return;
+ }
+ for(i:=0; i<len cmd; i++)
+ if(cmd[i]<'0' || '9'<cmd[i]){
+ sys->print("bad command %s\n", cmd);
+ return;
+ }
+ t := f.tk;
+ line := int cmd;
+ if(!nullsel(t))
+ tkcmd(t, NOSEL);
+ tkcmd(t, ".ft.t tag add sel "+string line+".0 {"+string line+".0 lineend+1char}");
+ tkcmd(t, ".ft.t mark set insert "+string line+".0; .ft.t see insert;update");
+}
+
+search(cmdwin: ref Tk->Toplevel, f: ref File, pat: string, backwards, uselast: int)
+{
+ t := f.tk;
+ if(pat == nil)
+ pat = lastpat;
+ else if(uselast)
+ lastpat = pat;
+ if(pat == nil){
+ error(cmdwin, "no pattern");
+ return;
+ }
+ cmd := ".ft.t search ";
+ if(backwards)
+ cmd += "-backwards ";
+ p := "";
+ for(i:=0; i<len pat; i++){
+ if(pat[i]== '\\' || pat[i]=='{')
+ p[len p] = '\\';
+ p[len p] = pat[i];
+ }
+ cmd += "{"+p+"} ";
+ null := nullsel(t);
+ if(null)
+ cmd += "insert";
+ else if(backwards)
+ cmd += "sel.first";
+ else
+ cmd += "sel.last";
+ s := tk->cmd(t, cmd);
+ if(s == "")
+ error(cmdwin, "not found");
+ else{
+ if(!null)
+ tkcmd(t, NOSEL);
+ tkcmd(t, ".ft.t tag add sel "+s+" "+s+"+"+string len pat+"chars");
+ tkcmd(t, ".ft.t mark set insert "+s+";.ft.t see insert; update");
+ }
+}
+
+showaddr(f: ref File, addr: string)
+{
+ if(addr=="")
+ return;
+ t := f.tk;
+ if(addr[0]=='#' || ('0'<=addr[0] && addr[0]<='9')){
+ # UGLY! just do line and character numbers until we get a
+ # decent command/address interface set up.
+ if(!nullsel(t))
+ tkcmd(t, NOSEL);
+ if(addr[0] == '#'){
+ addr = addr[1:];
+ tkcmd(t, ".ft.t mark set insert {1.0+"+addr+"char}; .ft.t see insert;update");
+ }else{
+ tkcmd(t, ".ft.t tag add sel "+addr+".0 {"+addr+".0 lineend+1char}");
+ tkcmd(t, ".ft.t mark set insert "+addr+".0; .ft.t see insert;update");
+ }
+ }
+}
+
+error(cmdwin: ref Tk->Toplevel, err: string)
+{
+ if(cmdwin == nil)
+ return;
+ tkcmd(cmdwin, ".ft.t insert end '?"+err+"\n");
+ if(!nullsel(cmdwin))
+ tkcmd(cmdwin, NOSEL);
+ tkcmd(cmdwin, ".ft.t mark set insert end");
+ tkcmd(cmdwin, ".ft.t mark set typingstart end; update");
+}
diff --git a/appl/wm/brutus/excerpt.b b/appl/wm/brutus/excerpt.b
new file mode 100644
index 00000000..ccb0e647
--- /dev/null
+++ b/appl/wm/brutus/excerpt.b
@@ -0,0 +1,264 @@
+implement Brutusext;
+
+# <Extension excerpt file [start [end]]>
+
+Name: con "Brutus entry";
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Context: import draw;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "regex.m";
+ regex: Regex;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "brutus.m";
+include "brutusext.m";
+
+init(s: Sys, d: Draw, b: Bufio, t: Tk, w: Tkclient)
+{
+ sys = s;
+ draw = d;
+ bufio = b;
+ tk = t;
+ tkclient = w;
+ regex = load Regex Regex->PATH;
+}
+
+create(parent: string, t: ref Tk->Toplevel, name, args: string): string
+{
+ (text, err) := gather(parent, args);
+ if(err != nil)
+ return err;
+ err = tk->cmd(t, "text "+name+" -tabs {1c} -wrap none -font /fonts/pelm/latin1.9.font");
+ if(len err > 0 && err[0] == '!')
+ return err;
+ (n, maxw) := nlines(text);
+ if(maxw < 40)
+ maxw = 40;
+ if(maxw > 70)
+ maxw = 70;
+ tk->cmd(t, name+" configure -height "+string n+".01h -width "+string maxw+"w");
+ return tk->cmd(t, name+" insert end '"+text);
+}
+
+gather(parent, args: string): (string, string)
+{
+ argl := tokenize(args);
+ nargs := len argl;
+ if(nargs == 0)
+ return (nil, "usage: excerpt [start] [end] file");
+ file := hd argl;
+ argl = tl argl;
+ b := bufio->open(fullname(parent, file), Bufio->OREAD);
+ if(b == nil)
+ return (nil, sys->sprint("can't open %s: %r", file));
+ start := "";
+ end := "";
+ if(argl != nil){
+ start = hd argl;
+ if(tl argl != nil)
+ end = hd tl argl;
+ }
+ (text, err) := readall(b, start, end);
+ return (text, err);
+}
+
+tokenize(s: string): list of string
+{
+ l: list of string;
+ i := 0;
+ a := "";
+ first := 1;
+ while(i < len s){
+ (a, i) = arg(first, s, i);
+ if(a != "")
+ l = a :: l;
+ first = 0;
+ }
+ rl: list of string;
+ while(l != nil){
+ rl = hd l :: rl;
+ l = tl l;
+ }
+ return rl;
+}
+
+arg(first: int, s: string, i: int): (string, int)
+{
+ while(i<len s && (s[i]==' ' || s[i]=='\t'))
+ i++;
+ if(i == len s)
+ return ("", i);
+ j := i+1;
+ if(first || s[i] != '/'){
+ while(j<len s && (s[j]!=' ' && s[j]!='\t'))
+ j++;
+ return (s[i:j], j);
+ }
+ while(j<len s && s[j]!='/')
+ if(s[j++] == '\\')
+ j++;
+ if(j == len s)
+ return (s[i:j], j);
+ return (s[i:j+1], j+1);
+}
+
+readall(b: ref Iobuf, start, end: string): (string, string)
+{
+ revlines : list of string = nil;
+ appending := 0;
+ lineno := 0;
+ for(;;){
+ line := b.gets('\n');
+ if(line == nil)
+ break;
+ lineno++;
+ if(!appending){
+ m := match(start, line, lineno);
+ if(m < 0)
+ return (nil, "error in pattern");
+ if(m)
+ appending = 1;
+ }
+ if(appending){
+ revlines = line :: revlines;
+ if(start != ""){
+ m := match(end, line, lineno);
+ if(m < 0)
+ return (nil, "error in pattern");
+ if(m)
+ break;
+ }
+ }
+ }
+ return (prep(revlines), "");
+}
+
+prep(revlines: list of string) : string
+{
+ tabstrip := -1;
+ for(l:=revlines; l != nil; l = tl l) {
+ s := hd l;
+ if(len s > 1) {
+ n := nleadtab(hd l);
+ if(tabstrip == -1 || n < tabstrip)
+ tabstrip = n;
+ }
+ }
+ # remove tabstrip tabs from each line
+ # and concatenate in reverse order
+ ans := "";
+ for(l=revlines; l != nil; l = tl l) {
+ s := hd l;
+ if(tabstrip > 0 && len s > 1)
+ s = s[tabstrip:];
+ ans = s + ans;
+ }
+ return ans;
+}
+
+nleadtab(s: string) : int
+{
+ slen := len s;
+ for(i:=0; i<slen; i++)
+ if(s[i] != '\t')
+ break;
+ return i;
+}
+
+nlines(s: string): (int, int)
+{
+ n := 0;
+ maxw := 0;
+ w := 0;
+ for(i:=0; i<len s; i++) {
+ if(s[i] == '\n') {
+ n++;
+ if(w > maxw)
+ maxw = w;
+ w = 0;
+ }
+ else if(s[i] == '\t')
+ w += 5;
+ else
+ w++;
+ }
+ if(len s>0 && s[len s-1]!='\n') {
+ n++;
+ if(w > maxw)
+ maxw = w;
+ }
+ return (n, maxw);
+}
+
+match(pat, line: string, lineno: int): int
+{
+ if(pat == "")
+ return 1;
+ case pat[0] {
+ '0' to '9' =>
+ return int pat <= lineno;
+ '/' =>
+ if(len pat < 3 || pat[len pat-1]!='/')
+ return -1;
+ re := compile(pat[1:len pat-1]);
+ if(re == nil)
+ return -1;
+ match := regex->execute(re, line);
+ return match != nil;
+ }
+ return -1;
+}
+
+pats: list of (string, Regex->Re);
+
+compile(pat: string): Regex->Re
+{
+ l := pats;
+ while(l != nil){
+ (p, r) := hd l;
+ if(p == pat)
+ return r;
+ l = tl l;
+ }
+ (re, nil) := regex->compile(pat, 0);
+ pats = (pat, re) :: pats;
+ return re;
+}
+
+cook(parent: string, nil: int, args: string): (ref Brutusext->Celem, string)
+{
+ (text, err) := gather(parent, args);
+ if(err != nil)
+ return (nil, err);
+ el1 := ref Brutusext->Celem(Brutusext->Text, text, nil, nil, nil, nil);
+ el2 := ref Brutusext->Celem(Brutus->Type*Brutus->NSIZE+Brutus->Size10, "", el1, nil, nil, nil);
+ el1.parent = el2;
+ ans := ref Brutusext->Celem(Brutus->Example, "", el2, nil, nil, nil);
+ el2.parent = ans;
+ return (ans, "");
+}
+
+fullname(parent, file: string): string
+{
+ if(len parent==0 || (len file>0 && (file[0]=='/' || file[0]=='#')))
+ return file;
+
+ for(i:=len parent-1; i>=0; i--)
+ if(parent[i] == '/')
+ return parent[0:i+1] + file;
+ return file;
+}
diff --git a/appl/wm/brutus/image.b b/appl/wm/brutus/image.b
new file mode 100644
index 00000000..906c668d
--- /dev/null
+++ b/appl/wm/brutus/image.b
@@ -0,0 +1,259 @@
+implement Brutusext;
+
+# <Extension image imagefile>
+
+Name: con "Brutus image";
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Context, Image, Display, Rect: import draw;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "imagefile.m";
+ imageremap: Imageremap;
+ readgif: RImagefile;
+ readjpg: RImagefile;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "pslib.m";
+ pslib: Pslib;
+
+include "brutus.m";
+include "brutusext.m";
+
+stderr: ref Sys->FD;
+
+Cache: adt
+{
+ args: string;
+ name: string;
+ r: Rect;
+};
+
+init(s: Sys, d: Draw, b: Bufio, t: Tk, w: Tkclient)
+{
+ sys = s;
+ draw = d;
+ bufio = b;
+ tk = t;
+ tkclient = w;
+ imageremap = load Imageremap Imageremap->PATH;
+ stderr = sys->fildes(2);
+}
+
+cache: list of ref Cache;
+
+create(parent: string, t: ref Tk->Toplevel, name, args: string): string
+{
+ if(imageremap == nil)
+ return sys->sprint(Name + ": can't load remap: %r");
+ display := t.image.display;
+ file := args;
+
+ for(cl:=cache; cl!=nil; cl=tl cl)
+ if((hd cl).args == args)
+ break;
+
+ c: ref Cache;
+ if(cl != nil)
+ c = hd cl;
+ else{
+ (im, mask, err) := loadimage(display, parent, file);
+ if(err != "")
+ return err;
+ imagename := name+file;
+ err = tk->cmd(t, "image create bitmap "+imagename);
+ if(len err > 0 && err[0] == '!')
+ return err;
+ err = tk->putimage(t, imagename, im, mask);
+ if(len err > 0 && err[0] == '!')
+ return err;
+ c = ref Cache(args, imagename, im.r);
+ cache = c :: cache;
+ }
+
+ err := tk->cmd(t, "canvas "+name+" -height "+string c.r.dy()+" -width "+string c.r.dx());
+ if(len err > 0 && err[0] == '!')
+ return err;
+ err = tk->cmd(t, name+" create image 0 0 -anchor nw -image "+c.name);
+
+ return "";
+}
+
+loadimage(display: ref Display, parent, file: string) : (ref Image, ref Image, string)
+{
+ im := display.open(fullname(parent, file));
+ mask: ref Image;
+
+ if(im == nil){
+ fd := bufio->open(fullname(parent, file), Bufio->OREAD);
+ if(fd == nil)
+ return (nil, nil, sys->sprint(Name + ": can't open %s: %r", file));
+
+ mod := filetype(file, fd);
+ if(mod == nil)
+ return (nil, nil, sys->sprint(Name + ": can't find decoder module for %s: %r", file));
+
+ (ri, err) := mod->read(fd);
+ if(ri == nil)
+ return (nil, nil, sys->sprint(Name + ": %s: %s", file, err));
+ if(err != "")
+ sys->fprint(stderr, Name + ": %s: %s", file, err);
+ mask = transparency(display, ri);
+
+ # if transparency is enabled, errdiff==1 is probably a mistake,
+ # but there's no easy solution.
+ (im, err) = imageremap->remap(ri, display, 1);
+ if(im == nil)
+ return (nil, nil, sys->sprint(Name+": remap %s: %s\n", file, err));
+ if(err != "")
+ sys->fprint(stderr, Name+": remap %s: %s\n", file, err);
+ ri = nil;
+ }
+ return(im, mask, "");
+}
+
+cook(parent: string, fmt: int, args: string): (ref Brutusext->Celem, string)
+{
+ file := args;
+ ans : ref Brutusext->Celem = nil;
+ if(fmt == Brutusext->FHtml) {
+ s := "<IMG SRC=\"" + file + "\">";
+ ans = ref Brutusext->Celem(Brutusext->Special, s, nil, nil, nil, nil);
+ }
+ else {
+ (rc, dir) := sys->stat(file);
+ if(rc < 0)
+ return (nil, "can't find " + file);
+ mtime := dir.mtime;
+
+ # psfile name: in dir of file, with .ps suffix
+ psfile := file;
+ for(i := (len psfile)-1; i >= 0; i--) {
+ if(psfile[i] == '.') {
+ psfile = psfile[0:i];
+ break;
+ }
+ }
+ psfile = psfile + ".ps";
+ (rc, dir) = sys->stat(psfile);
+ if(rc < 0 || dir.mtime < mtime) {
+ iob := bufio->create(psfile, Bufio->OWRITE, 8r664);
+ if(iob == nil)
+ return (nil, "can't create " + psfile);
+
+ display := draw->Display.allocate("");
+ (im, mask, err) := loadimage(display, parent, file);
+ if(err != "")
+ return (nil, err);
+ pslib = load Pslib Pslib->PATH;
+ if(pslib == nil)
+ return (nil, "can't load Pslib");
+ pslib->init(bufio);
+ pslib->writeimage(iob, im, 100);
+ iob.close();
+ }
+ s := "\\epsfbox{" + psfile + "}\n";
+ ans = ref Brutusext->Celem(Brutusext->Special, s, nil, nil, nil, nil);
+ }
+ return (ans, "");
+}
+
+fullname(parent, file: string): string
+{
+ if(len parent==0 || (len file>0 && (file[0]=='/' || file[0]=='#')))
+ return file;
+
+ for(i:=len parent-1; i>=0; i--)
+ if(parent[i] == '/')
+ return parent[0:i+1] + file;
+ return file;
+}
+
+#
+# rest of this is all borrowed from wm/view.
+# should probably be packaged - perhaps in RImagefile?
+#
+filetype(file: string, fd: ref Iobuf): RImagefile
+{
+ if(len file>4 && file[len file-4:]==".gif")
+ return loadgif();
+ if(len file>4 && file[len file-4:]==".jpg")
+ return loadjpg();
+
+ # sniff the header looking for a magic number
+ buf := array[20] of byte;
+ if(fd.read(buf, len buf) != len buf){
+ sys->fprint(stderr, "View: can't read %s: %r\n", file);
+ return nil;
+ }
+ fd.seek(big 0, 0);
+ if(string buf[0:6]=="GIF87a" || string buf[0:6]=="GIF89a")
+ return loadgif();
+ jpmagic := array[] of {byte 16rFF, byte 16rD8, byte 16rFF, byte 16rE0,
+ byte 0, byte 0, byte 'J', byte 'F', byte 'I', byte 'F', byte 0};
+ for(i:=0; i<len jpmagic; i++)
+ if(jpmagic[i]>byte 0 && buf[i]!=jpmagic[i])
+ break;
+ if(i == len jpmagic)
+ return loadjpg();
+ return nil;
+}
+
+loadgif(): RImagefile
+{
+ if(readgif == nil){
+ readgif = load RImagefile RImagefile->READGIFPATH;
+ if(readgif == nil)
+ sys->fprint(stderr, "Brutus image: can't load readgif: %r\n");
+ else
+ readgif->init(bufio);
+ }
+ return readgif;
+}
+
+loadjpg(): RImagefile
+{
+ if(readjpg == nil){
+ readjpg = load RImagefile RImagefile->READJPGPATH;
+ if(readjpg == nil)
+ sys->fprint(stderr, "Brutus image: can't load readjpg: %r\n");
+ else
+ readjpg->init(bufio);
+ }
+ return readjpg;
+}
+
+transparency(display: ref Display, r: ref RImagefile->Rawimage): ref Image
+{
+ if(r.transp == 0)
+ return nil;
+ if(r.nchans != 1)
+ return nil;
+ i := display.newimage(r.r, display.image.chans, 0, 0);
+ if(i == nil){
+ return nil;
+ }
+ pic := r.chans[0];
+ npic := len pic;
+ mpic := array[npic] of byte;
+ index := r.trindex;
+ for(j:=0; j<npic; j++)
+ if(pic[j] == index)
+ mpic[j] = byte 0;
+ else
+ mpic[j] = byte 16rFF;
+ i.writepixels(i.r, mpic);
+ return i;
+}
diff --git a/appl/wm/brutus/mkfile b/appl/wm/brutus/mkfile
new file mode 100644
index 00000000..50a8734e
--- /dev/null
+++ b/appl/wm/brutus/mkfile
@@ -0,0 +1,24 @@
+<../../../mkconfig
+
+TARG=\
+ excerpt.dis\
+ image.dis\
+ mod.dis\
+ table.dis\
+
+MODULES=\
+
+SYSMODULES=\
+ brutus.m\
+ brutusext.m\
+ bufio.m\
+ draw.m\
+ html.m\
+ imagefile.m\
+ pslib.m\
+ regex.m\
+ string.m\
+
+DISBIN=$ROOT/dis/wm/brutus
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/wm/brutus/mod.b b/appl/wm/brutus/mod.b
new file mode 100644
index 00000000..02f8e20e
--- /dev/null
+++ b/appl/wm/brutus/mod.b
@@ -0,0 +1,335 @@
+implement Brutusext;
+
+# <Extension mod file>
+# For module descriptions (in book)
+
+Name: con "Brutus mod";
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Context, Font: import draw;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "string.m";
+ S : String;
+
+include "brutus.m";
+ Size8, Index, Roman, Italic, Bold, Type, NFONT, NSIZE: import Brutus;
+
+include "brutusext.m";
+
+Mstring: adt
+{
+ s: string;
+ style: int;
+ indexed: int;
+ width: int;
+ next: cyclic ref Mstring;
+};
+
+fontname := array[NFONT] of {
+ "/fonts/lucidasans/unicode.7.font",
+ "/fonts/lucidasans/italiclatin1.7.font",
+ "/fonts/lucidasans/boldlatin1.7.font",
+ "/fonts/lucidasans/typelatin1.7.font",
+ };
+
+fontswitch := array[NFONT] of {
+ "\\fontseries{m}\\rmfamily ",
+ "\\itshape ",
+ "\\fontseries{b}\\rmfamily ",
+ "\\fontseries{mc}\\ttfamily ",
+ };
+
+fontref := array[NFONT] of ref Font;
+
+LEFTCHARS: con 45;
+LEFTPIX: con LEFTCHARS*7; # 7 is width of lucidasans/typelatin1.7 chars
+
+init(s: Sys, d: Draw, b: Bufio, t: Tk, w: Tkclient)
+{
+ sys = s;
+ draw = d;
+ bufio = b;
+ tk = t;
+ tkclient = w;
+ S = load String String->PATH;
+}
+
+create(parent: string, t: ref Tk->Toplevel, name, args: string): string
+{
+ (spec, err) := getspec(parent, args);
+ if(err != nil)
+ return err;
+ n := len spec;
+ if(n == 0)
+ return "empty spec";
+ d := t.image.display;
+ for(i:=0; i < NFONT; i++) {
+ if(i == Bold || fontref[i] != nil)
+ continue;
+ fontref[i] = Font.open(d, fontname[i]);
+ if(fontref[i] == nil)
+ return sys->sprint("can't open font %s: %r\n", fontname[i]);
+ }
+ (nil, nil, rw, nil) := measure(spec, 1);
+ lw := LEFTPIX;
+ wd := lw + rw;
+ fnt := fontref[Roman];
+ ht := n * fnt.height;
+ err = tk->cmd(t, "canvas " + name + " -width " + string wd
+ + " -height " + string ht
+ + " -font " + fontname[Type]);
+ if(len err > 0 && err[0] == '!')
+ return "problem creating canvas";
+ y := 0;
+ xl := 0;
+ xr := lw;
+ for(l := spec; l != nil; l = tl l) {
+ (lm, rm) := hd l;
+ canvmstring(t, name, lm, xl, y);
+ canvmstring(t, name, rm, xr, y);
+ y += fnt.height;
+ }
+ tk->cmd(t, "update");
+ return "";
+}
+
+canvmstring(t: ref Tk->Toplevel, canv: string, m: ref Mstring, x, y: int)
+{
+ # assume fonts all have same ascent
+ while(m != nil) {
+ pos := string x + " " + string y;
+ font := "";
+ if(m.style != Type)
+ font = " -font " + fontname[m.style];
+ e := tk->cmd(t, canv + " create text " + pos + " -anchor nw "
+ + font + " -text '" + m.s);
+ x += m.width;
+ m = m.next;
+ }
+}
+
+getspec(parent, args: string) : (list of (ref Mstring, ref Mstring), string)
+{
+ (n, argl) := sys->tokenize(args, " ");
+ if(n != 1)
+ return (nil, "usage: " + Name + " file");
+ b := bufio->open(fullname(parent, hd argl), Sys->OREAD);
+ if(b == nil)
+ return (nil, sys->sprint("can't open %s, the error was: %r", hd argl));
+ mm : list of (ref Mstring, ref Mstring) = nil;
+ for(;;) {
+ s := b.gets('\n');
+ if(s == "")
+ break;
+ (nf, fl) := sys->tokenize(s, " ");
+ if(nf == 0)
+ mm = (nil, nil) :: mm;
+ else {
+ sleft := "";
+ sright := "";
+ if(nf == 1) {
+ f := hd fl;
+ if(s[0] == '\t')
+ sright = f;
+ else
+ sleft = f;
+ }
+ else {
+ sleft = hd fl;
+ sright = hd tl fl;
+ }
+ mm = (tom(sleft, Type, Roman, 1), tom(sright, Italic, Type, 0)) :: mm;
+ }
+ }
+ ans : list of (ref Mstring, ref Mstring) = nil;
+ while(mm != nil) {
+ ans = hd mm :: ans;
+ mm = tl mm;
+ }
+ return (ans, "");
+}
+
+tom(str: string, defstyle, altstyle, doindex: int) : ref Mstring
+{
+ if(str == "")
+ return nil;
+ if(str[len str - 1] == '\n')
+ str = str[0: len str - 1];
+ if(str == "")
+ return nil;
+ style := defstyle;
+ if(str[0] == '|')
+ style = altstyle;
+ (nil, l) := sys->tokenize(str, "|");
+ dummy := ref Mstring;
+ last := dummy;
+ if(doindex && l != nil && S->prefix(" ", hd l))
+ doindex = 0; # continuation line
+ while(l != nil) {
+ s := hd l;
+ m : ref Mstring;
+ if(doindex && style == defstyle) {
+ # index 'words' in defstyle, but not past : or (
+ (sl,sr) := S->splitl(s, ":(");
+ while(sl != nil) {
+ a : string;
+ (a,sl) = S->splitl(sl, "a-zA-Z");
+ if(a != "") {
+ m = ref Mstring(a, style, 0, 0, nil);
+ last.next = m;
+ last = m;
+ }
+ if(sl != "") {
+ b : string;
+ (b,sl) = S->splitl(sl, "^a-zA-Z0-9_");
+ if(b != "") {
+ m = ref Mstring(b, style, 1, 0, nil);
+ last.next = m;
+ last = m;
+ }
+ }
+ }
+ if(sr != "") {
+ m = ref Mstring(sr, style, 0, 0, nil);
+ last.next = m;
+ last = m;
+ doindex = 0;
+ }
+ }
+ else {
+ m = ref Mstring(s, style, 0, 0, nil);
+ last.next = m;
+ last = m;
+ }
+ l = tl l;
+ if(style == defstyle)
+ style = altstyle;
+ else
+ style = defstyle;
+ }
+ return dummy.next;
+}
+
+measure(spec: list of (ref Mstring, ref Mstring), pixels: int) : (int, ref Mstring, int, ref Mstring)
+{
+ maxl := 0;
+ maxr := 0;
+ maxlm : ref Mstring = nil;
+ maxrm : ref Mstring = nil;
+ while(spec != nil) {
+ (lm, rm) := hd spec;
+ spec = tl spec;
+ (maxl, maxlm) = measuremax(lm, maxl, maxlm, pixels);
+ (maxr, maxrm) = measuremax(rm, maxr, maxrm, pixels);
+ }
+ return (maxl, maxlm, maxr, maxrm);
+}
+
+measuremax(m: ref Mstring, maxw: int, maxm: ref Mstring, pixels: int) : (int, ref Mstring)
+{
+ w := 0;
+ for(mm := m; mm != nil; mm = mm.next) {
+ if(pixels)
+ mm.width = fontref[mm.style].width(mm.s);
+ else
+ mm.width = len mm.s;
+ w += mm.width;
+ }
+ if(w > maxw) {
+ maxw = w;
+ maxm = m;
+ }
+ return (maxw, maxm);
+}
+
+cook(parent: string, nil: int, args: string): (ref Celem, string)
+{
+ (spec, err) := getspec(parent, args);
+ if(err != nil)
+ return (nil, err);
+ (nil, maxlm, nil, nil) := measure(spec, 0);
+ ans := fontce(Roman);
+ tail := specialce("\\begin{tabbing}\\hspace{3in}\\=\\kill\n");
+ tail = add(ans, nil, tail);
+ for(l := spec; l != nil; l = tl l) {
+ (lm, rm) := hd l;
+ tail = cookmstring(ans, tail, lm, 1);
+ tail = add(ans, tail, specialce("\\>"));
+ tail = cookmstring(ans, tail, rm, 0);
+ tail = add(ans, tail, specialce("\\\\\n"));
+ }
+ add(ans, tail, specialce("\\end{tabbing}"));
+ return (ans, "");
+}
+
+cookmstring(par, tail: ref Celem, m: ref Mstring, doindex: int) : ref Celem
+{
+ s := "";
+ if(m == nil)
+ return tail;
+ while(m != nil) {
+ e := fontce(m.style);
+ te := textce(m.s);
+ add(e, nil, te);
+ if(doindex && m.indexed) {
+ ie := ref Celem(Index, nil, nil, nil, nil, nil);
+ add(ie, nil, e);
+ e = ie;
+ }
+ tail = add(par, tail, e);
+ m = m.next;
+ }
+ return tail;
+}
+
+specialce(s: string) : ref Celem
+{
+ return ref Celem(Special, s, nil, nil, nil, nil);
+}
+
+textce(s: string) : ref Celem
+{
+ return ref Celem(Text, s, nil, nil, nil, nil);
+}
+
+fontce(sty: int) : ref Celem
+{
+ return ref Celem(sty*NSIZE+Size8, nil, nil, nil, nil, nil);
+}
+
+add(par, tail: ref Celem, e: ref Celem) : ref Celem
+{
+ if(tail == nil) {
+ par.contents = e;
+ e.parent = par;
+ }
+ else
+ tail.next = e;
+ e.prev = tail;
+ return e;
+}
+
+fullname(parent, file: string): string
+{
+ if(len parent==0 || (len file>0 && (file[0]=='/' || file[0]=='#')))
+ return file;
+
+ for(i:=len parent-1; i>=0; i--)
+ if(parent[i] == '/')
+ return parent[0:i+1] + file;
+ return file;
+}
diff --git a/appl/wm/brutus/table.b b/appl/wm/brutus/table.b
new file mode 100644
index 00000000..24740a31
--- /dev/null
+++ b/appl/wm/brutus/table.b
@@ -0,0 +1,1478 @@
+implement Brutusext;
+
+# <Extension table tablefile>
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Point, Font, Rect: import draw;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "bufio.m";
+
+include "string.m";
+ S: String;
+
+include "html.m";
+ html: HTML;
+ Lex, Attr, RBRA, Data, Ttable, Tcaption, Tcol, Ttr, Ttd: import html;
+
+include "brutus.m";
+ Size6, Size8, Size10, Size12, Size16, NSIZE,
+ Roman, Italic, Bold, Type, NFONT, NFONTTAG,
+ Example, List, Listelem, Heading, Nofill, Author, Title,
+ DefFont, DefSize, TitleFont, TitleSize, HeadingFont, HeadingSize: import Brutus;
+
+include "brutusext.m";
+
+Name: con "Table";
+
+# alignment types
+Anone, Aleft, Acenter, Aright, Ajustify, Atop, Amiddle, Abottom, Abaseline: con iota;
+
+# A cell has a number of Lines, each of which has a number of Items.
+# Each Item is a string in one font.
+Item: adt
+{
+ itemid: int; # canvas text item id
+ s: string;
+ fontnum: int; # (style*NumSizes + size)
+ pos: Point; # nw corner of text item, relative to line origin
+ width: int; # of s, in pixels, when displayed in font
+ line: cyclic ref Line; # containing line
+ prev: cyclic ref Item;
+ next: cyclic ref Item;
+};
+
+Line: adt
+{
+ items: cyclic ref Item;
+ pos: Point; # nw corner of Line relative to containing cell;
+ height: int;
+ ascent: int;
+ width: int;
+ cell: cyclic ref Tablecell; # containing cell
+ next: cyclic ref Line;
+};
+
+Align: adt
+{
+ halign: int;
+ valign: int;
+};
+
+Tablecell: adt
+{
+ cellid: int;
+ content: array of ref Lex;
+ lines: cyclic ref Line;
+ rowspan: int;
+ colspan: int;
+ nowrap: int;
+ align: Align;
+ width: int;
+ height: int;
+ ascent: int;
+ row: int;
+ col: int;
+ pos: Point; # nw corner of cell, in canvas coords
+};
+
+Tablegcell: adt
+{
+ cell: ref Tablecell;
+ drawnhere: int;
+};
+
+Tablerow: adt
+{
+ cells: list of ref Tablecell;
+ height: int;
+ ascent: int;
+ align: Align;
+ pos: Point;
+ rule: int; # width of rule below row, if > 0
+ ruleids: list of int; # canvas ids of lines used to draw rule
+};
+
+Tablecol: adt
+{
+ width: int;
+ align: Align;
+ pos: Point;
+ rule: int; # width of rule to right of col, if > 0
+ ruleids: list of int; # canvas ids of lines used to draw rule
+};
+
+Table: adt
+{
+ nrow: int;
+ ncol: int;
+ ncell: int;
+ width: int;
+ height: int;
+ capcell: ref Tablecell;
+ border: int;
+ brectid: int;
+ cols: array of ref Tablecol;
+ rows: array of ref Tablerow;
+ cells: list of ref Tablecell;
+ grid: array of array of ref Tablegcell;
+ colw: array of int;
+ rowh: array of int;
+};
+
+# Font stuff
+
+DefaultFnum: con (DefFont*NSIZE + Size10);
+
+fontnames := array[NFONTTAG] of {
+ "/fonts/lucidasans/unicode.6.font",
+ "/fonts/lucidasans/unicode.7.font",
+ "/fonts/lucidasans/unicode.8.font",
+ "/fonts/lucidasans/unicode.10.font",
+ "/fonts/lucidasans/unicode.13.font",
+ "/fonts/lucidasans/italiclatin1.6.font",
+ "/fonts/lucidasans/italiclatin1.7.font",
+ "/fonts/lucidasans/italiclatin1.8.font",
+ "/fonts/lucidasans/italiclatin1.10.font",
+ "/fonts/lucidasans/italiclatin1.13.font",
+ "/fonts/lucidasans/boldlatin1.6.font",
+ "/fonts/lucidasans/boldlatin1.7.font",
+ "/fonts/lucidasans/boldlatin1.8.font",
+ "/fonts/lucidasans/boldlatin1.10.font",
+ "/fonts/lucidasans/boldlatin1.13.font",
+ "/fonts/lucidasans/typelatin1.6.font",
+ "/fonts/lucidasans/typelatin1.7.font",
+ "/fonts/pelm/latin1.9.font",
+ "/fonts/pelm/ascii.12.font",
+ "/fonts/pelm/ascii.16.font"
+};
+
+fontrefs := array[NFONTTAG] of ref Font;
+fontused := array[NFONTTAG] of { DefaultFnum => 1, * => 0};
+
+# TABHPAD, TABVPAD are extra space between columns, rows
+TABHPAD: con 10;
+TABVPAD: con 4;
+
+tab: ref Table;
+top: ref Tk->Toplevel;
+display: ref Draw->Display;
+canv: string;
+
+init(asys: Sys, adraw: Draw, nil: Bufio, atk: Tk, aw: Tkclient)
+{
+ sys = asys;
+ draw = adraw;
+ tk = atk;
+ tkclient = aw;
+ html = load HTML HTML->PATH;
+ S = load String String->PATH;
+}
+
+create(parent: string, t: ref Tk->Toplevel, name, args: string): string
+{
+ if(html == nil)
+ return "can't load HTML module";
+ top = t;
+ display = t.image.display;
+ canv = name;
+ err := tk->cmd(t, "canvas " + canv);
+ if(len err > 0 && err[0] == '!')
+ return err_ret(err);
+
+ spec: array of ref Lex;
+ (spec, err) = getspec(parent, args);
+ if(err != "")
+ return err_ret(err);
+
+ err = parsetab(spec);
+ if(err != "")
+ return err_ret(err);
+
+ err = build();
+ if(err != "")
+ return err_ret(err);
+ return "";
+}
+
+err_ret(s: string) : string
+{
+ return Name + ": " + s;
+}
+
+getspec(parent, args: string) : (array of ref Lex, string)
+{
+ (n, argl) := sys->tokenize(args, " ");
+ if(n != 1)
+ return (nil, "usage: " + Name + " file");
+ (filebytes, err) := readfile(fullname(parent, hd argl));
+ if(err != "")
+ return (nil, err);
+ return(html->lex(filebytes, HTML->UTF8, 1), "");
+}
+
+readfile(path: string): (array of byte, string)
+{
+ fd := sys->open(path, sys->OREAD);
+ if(fd == nil)
+ return (nil, sys->sprint("can't open %s, the error was: %r", path));
+ (ok, d) := sys->fstat(fd);
+ if(ok < 0)
+ return (nil, sys->sprint("can't stat %s, the error was: %r", path));
+ if(d.mode & Sys->DMDIR)
+ return (nil, sys->sprint("%s is a directory", path));
+
+ l := int d.length;
+ buf := array[l] of byte;
+ tot := 0;
+ while(tot < l) {
+ need := l - tot;
+ n := sys->read(fd, buf[tot:], need);
+ if(n <= 0)
+ return (nil, sys->sprint("error reading %s, the error was: %r", path));
+ tot += n;
+ }
+ return (buf, "");
+}
+
+# Use HTML 3.2 table spec as external representation
+# (But no th cells, width specs; and extra "rule" attribute
+# for col and tr meaning that a rule of given width is to
+# follow the given column or row).
+# DTD elements:
+# table: - O (caption?, col*, tr*)
+# caption: - - (%text+)
+# col: - O empty
+# tr: - O td*
+# td: - O (%body.content)
+parsetab(toks: array of ref Lex) : string
+{
+ tabletlex := toks[0];
+ n := len toks;
+ (tlex, i) := nexttok(toks, n, 0);
+
+ # caption
+ capcell: ref Tablecell = nil;
+ if(tlex != nil && tlex.tag == Tcaption) {
+ for(j := i+1; j < n; j++) {
+ tlex = toks[j];
+ if(tlex.tag == Tcaption + RBRA)
+ break;
+ }
+ if(j >= n)
+ return syntax_err(tlex, j);
+ if(j > i+1) {
+ captoks := toks[i+1:j];
+ (caplines, e) := lexes2lines(captoks);
+ if(e != nil)
+ return e;
+ # we ignore caption now
+# capcell = ref Tablecell(0, captoks, caplines, 1, 1, 1, Align(Anone, Anone),
+# 0, 0, 0, 0, 0, Point(0,0));
+ }
+ (tlex, i) = nexttok(toks, n, j);
+ }
+
+ # col*
+ cols: list of ref Tablecol = nil;
+ while(tlex != nil && tlex.tag == Tcol) {
+ col := makecol(tlex);
+ if(col.align.halign == Anone)
+ col.align.halign = Aleft;
+ cols = col :: cols;
+ (tlex, i) = nexttok(toks, n, i);
+ }
+ cols = revcols(cols);
+
+ body : list of ref Tablerow = nil;
+ cells : list of ref Tablecell = nil;
+ cellid := 0;
+ rows: list of ref Tablerow = nil;
+
+ # tr*
+ while(tlex != nil && tlex.tag == Ttr) {
+ currow := ref Tablerow(nil, 0, 0, makealign(tlex), Point(0,0), makelinew(tlex, "rule"), nil);
+ rows = currow :: rows;
+
+ # td*
+ (tlex, i) = nexttok(toks, n, i);
+ while(tlex != nil && tlex.tag == Ttd) {
+ rowspan := 1;
+ (rsfnd, rs) := html->attrvalue(tlex.attr, "rowspan");
+ if(rsfnd && rs != "")
+ rowspan = int rs;
+ colspan := 1;
+ (csfnd, cs) := html->attrvalue(tlex.attr, "colspan");
+ if(csfnd && cs != "")
+ colspan = int cs;
+ nowrap := 0;
+ (nwfnd, nil) := html->attrvalue(tlex.attr, "nowrap");
+ if(nwfnd)
+ nowrap = 1;
+ align := makealign(tlex);
+ for(j := i+1; j < n; j++) {
+ tlex = toks[j];
+ tg := tlex.tag;
+ if(tg == Ttd + RBRA || tg == Ttd || tg == Ttr + RBRA || tg == Ttr)
+ break;
+ }
+ if(j == n)
+ tlex = nil;
+ content: array of ref Lex = nil;
+ if(j > i+1)
+ content = toks[i+1:j];
+ (lines, err) := lexes2lines(content);
+ if(err != "")
+ return err;
+ curcell := ref Tablecell(cellid, content, lines, rowspan, colspan, nowrap, align, 0, 0, 0, 0, 0, Point(0,0));
+ currow.cells = curcell :: currow.cells;
+ cells = curcell :: cells;
+ cellid++;
+ if(tlex != nil && tlex.tag == Ttd + RBRA)
+ (tlex, i) = nexttok(toks, n, j);
+ else
+ i = j;
+ }
+ if(tlex != nil && tlex.tag == Ttr + RBRA)
+ (tlex, i) = nexttok(toks, n, i);
+ }
+ if(tlex == nil || tlex.tag != Ttable + RBRA)
+ return syntax_err(tlex, i);
+
+ # now reverse all the lists that were built in reverse order
+ # and calculate nrow, ncol
+
+ rows = revrowl(rows);
+ nrow := len rows;
+ rowa := array[nrow] of ref Tablerow;
+ ncol := 0;
+ r := 0;
+ for(rl := rows; rl != nil; rl = tl rl) {
+ row := hd rl;
+ rowa[r++] = row;
+ rcols := 0;
+ cl := row.cells;
+ row.cells = nil;
+ while(cl != nil) {
+ c := hd cl;
+ row.cells = c :: row.cells;
+ rcols += c.colspan;
+ cl = tl cl;
+ }
+ if(rcols > ncol)
+ ncol = rcols;
+ }
+ cells = revcelll(cells);
+
+ cola := array[ncol] of ref Tablecol;
+ for(c := 0; c < ncol; c++) {
+ if(cols != nil) {
+ cola[c] = hd cols;
+ cols = tl cols;
+ }
+ else
+ cola[c] = ref Tablecol(0, Align(Anone, Anone), Point(0,0), 0, nil);
+ }
+
+ if(tabletlex.tag != Ttable)
+ return syntax_err(tabletlex, 0);
+ border := makelinew(tabletlex, "border");
+ tab = ref Table(nrow, ncol, cellid, 0, 0, capcell, border, 0, cola, rowa, cells, nil, nil, nil);
+
+ return "";
+}
+
+syntax_err(tlex: ref Lex, i: int) : string
+{
+ if(tlex == nil)
+ return "syntax error in table: premature end";
+ else
+ return "syntax error in table at token " + string i + ": " + html->lex2string(tlex);
+}
+
+# next token after toks[i], skipping whitespace
+nexttok(toks: array of ref Lex, ntoks, i: int) : (ref Lex, int)
+{
+ i++;
+ if(i >= ntoks)
+ return (nil, i);
+ t := toks[i];
+ while(t.tag == Data) {
+ if(S->drop(t.text, " \t\n\r") != "")
+ break;
+ i++;
+ if(i >= ntoks)
+ return (nil, i);
+ t = toks[i];
+ }
+# sys->print("nexttok returning (%s,%d)\n", html->lex2string(t), i);
+ return(t, i);
+}
+
+makecol(tlex: ref Lex) : ref Tablecol
+{
+ return ref Tablecol(0, makealign(tlex), Point(0,0), makelinew(tlex, "rule"), nil);
+}
+
+makelinew(tlex: ref Lex, aname: string) : int
+{
+ ans := 0;
+ (fnd, val) := html->attrvalue(tlex.attr, aname);
+ if(fnd) {
+ if(val == "")
+ ans = 1;
+ else
+ ans = int val;
+ }
+ return ans;
+}
+
+makealign(tlex: ref Lex) : Align
+{
+ (nil,h) := html->attrvalue(tlex.attr, "align");
+ (nil,v) := html->attrvalue(tlex.attr, "valign");
+ hal := align_val(h, Anone);
+ val := align_val(v, Anone);
+ return Align(hal, val);
+}
+
+align_val(sal: string, dflt: int) : int
+{
+ ans := dflt;
+ case sal {
+ "left" => ans = Aleft;
+ "center" => ans = Acenter;
+ "right" => ans = Aright;
+ "justify" => ans = Ajustify;
+ "top" => ans = Atop;
+ "middle" => ans = Amiddle;
+ "bottom" => ans = Abottom;
+ "baseline" => ans = Abaseline;
+ }
+ return ans;
+}
+
+revcols(l : list of ref Tablecol) : list of ref Tablecol
+{
+ ans : list of ref Tablecol = nil;
+ while(l != nil) {
+ ans = hd l :: ans;
+ l = tl l;
+ }
+ return ans;
+}
+
+revrowl(l : list of ref Tablerow) : list of ref Tablerow
+{
+ ans : list of ref Tablerow = nil;
+ while(l != nil) {
+ ans = hd l :: ans;
+ l = tl l;
+ }
+ return ans;
+}
+
+revcelll(l : list of ref Tablecell) : list of ref Tablecell
+{
+ ans : list of ref Tablecell = nil;
+ while(l != nil) {
+ ans = hd l :: ans;
+ l = tl l;
+ }
+ return ans;
+}
+
+revintl(l : list of int) : list of int
+{
+ ans : list of int = nil;
+ while(l != nil) {
+ ans = hd l :: ans;
+ l = tl l;
+ }
+ return ans;
+}
+
+# toks should contain only Font (i.e., size) and style changes, along with text.
+lexes2lines(toks: array of ref Lex) : (ref Line, string)
+{
+ n := len toks;
+ (tlex, i) := nexttok(toks, n, -1);
+ ans: ref Line = nil;
+ if(tlex == nil)
+ return(ans, "");
+ curline : ref Line = nil;
+ curitem : ref Item = nil;
+ stylestk := DefFont :: nil;
+ sizestk := DefSize :: nil;
+ f := DefaultFnum;
+ fontstk:= f :: nil;
+ for(;;) {
+ if(i >= n)
+ break;
+ tlex = toks[i++];
+ case tlex.tag {
+ Data =>
+ text := tlex.text;
+ while(text != "") {
+ if(curline == nil) {
+ curline = ref Line(nil, Point(0,0), 0, 0, 0, nil, nil);
+ ans = curline;
+ }
+ s : string;
+ (s, text) = S->splitl(text, "\n");
+ if(s != "") {
+ f = hd fontstk;
+ it := ref Item(0, s, f, Point(0,0), 0, curline, curitem, nil);
+ if(curitem == nil)
+ curline.items = it;
+ else
+ curitem.next = it;
+ curitem = it;
+ }
+ if(text != "") {
+ text = text[1:];
+ curline.next = ref Line(nil, Point(0,0), 0, 0, 0, nil, nil);
+ curline = curline.next;
+ curitem = nil;
+ }
+ }
+ HTML->Tfont =>
+ (fnd, ssize) := html->attrvalue(tlex.attr, "size");
+ if(fnd && len ssize > 0) {
+ # HTML size 3 == our Size10
+ sz := (int ssize) + (Size10 - 3);
+ if(sz < 0 || sz >= NSIZE)
+ return (nil, "bad font size " + ssize);
+ sizestk = sz :: sizestk;
+ fontstk = fnum(hd stylestk, sz) :: fontstk;
+ }
+ else
+ return (nil, "bad font command: no size");
+ HTML->Tfont + RBRA =>
+ fontstk = tl fontstk;
+ sizestk = tl sizestk;
+ if(sizestk == nil)
+ return (nil, "unmatched </FONT>");
+ HTML->Tb =>
+ stylestk = Bold :: stylestk;
+ fontstk = fnum(Bold, hd sizestk) :: fontstk;
+ HTML->Ti =>
+ stylestk = Italic :: stylestk;
+ fontstk = fnum(Italic, hd sizestk) :: fontstk;
+ HTML->Ttt =>
+ stylestk = Type :: stylestk;
+ fontstk = fnum(Type, hd sizestk) :: fontstk;
+ HTML->Tb + RBRA or HTML->Ti + RBRA or HTML->Ttt + RBRA =>
+ fontstk = tl fontstk;
+ stylestk = tl stylestk;
+ if(stylestk == nil)
+ return (nil, "unmatched </B>, </I>, or </TT>");
+ }
+ }
+ return (ans, "");
+}
+
+fnum(fstyle, fsize: int) : int
+{
+ ans := fstyle*NSIZE + fsize;
+ fontused[ans] = 1;
+ return ans;
+}
+
+loadfonts() : string
+{
+ for(i := 0; i < NFONTTAG; i++) {
+ if(fontused[i] && fontrefs[i] == nil) {
+ fname := fontnames[i];
+ f := Font.open(display, fname);
+ if(f == nil)
+ return sys->sprint("can't open font %s: %r", fname);
+ fontrefs[i] = f;
+ }
+ }
+ return "";
+}
+
+# Find where each cell goes in nrow x ncol grid
+setgrid()
+{
+ gcells := array[tab.nrow] of { * => array[tab.ncol] of { * => ref Tablegcell(nil, 1)} };
+
+ # The following arrays keep track of cells that are spanning
+ # multiple rows; rowspancnt[i] is the number of rows left
+ # to be spanned in column i.
+ # When done, cell's (row,col) is upper left grid point.
+ rowspancnt := array[tab.ncol] of { * => 0};
+ rowspancell := array[tab.ncol] of ref Tablecell;
+
+ ri := 0;
+ ci := 0;
+ for(ri = 0; ri < tab.nrow; ri++) {
+ row := tab.rows[ri];
+ cl := row.cells;
+ for(ci = 0; ci < tab.ncol; ) {
+ if(rowspancnt[ci] > 0) {
+ gcells[ri][ci].cell = rowspancell[ci];
+ gcells[ri][ci].drawnhere = 0;
+ rowspancnt[ci]--;
+ ci++;
+ }
+ else {
+ if(cl == nil) {
+ ci++;
+ continue;
+ }
+ c := hd cl;
+ cl = tl cl;
+ cspan := c.colspan;
+ if(cspan == 0) {
+ cspan = tab.ncol - ci;
+ c.colspan = cspan;
+ }
+ rspan := c.rowspan;
+ if(rspan == 0) {
+ rspan = tab.nrow - ri;
+ c.rowspan = rspan;
+ }
+ c.row = ri;
+ c.col = ci;
+ for(i := 0; i < cspan && ci < tab.ncol; i++) {
+ gcells[ri][ci].cell = c;
+ if(i > 0)
+ gcells[ri][ci].drawnhere = 0;
+ if(rspan > 1) {
+ rowspancnt[ci] = rspan-1;
+ rowspancell[ci] = c;
+ }
+ ci++;
+ }
+ }
+ }
+ }
+ tab.grid = gcells;
+}
+
+build() : string
+{
+ ri, ci: int;
+
+# sys->print("\n\ninitial table\n"); printtable();
+ if(tab.ncol == 0 || tab.nrow == 0)
+ return "";
+
+ setgrid();
+
+ err := loadfonts();
+ if(err != "")
+ return err;
+
+ for(cl := tab.cells; cl != nil; cl = tl cl)
+ cell_geom(hd cl);
+
+ for(ci = 0; ci < tab.ncol; ci++)
+ col_geom(ci);
+
+ for(ri = 0; ri < tab.nrow; ri++)
+ row_geom(ri);
+
+ caption_geom();
+
+ table_geom();
+# sys->print("\n\ntable after geometry set\n"); printtable();
+
+ h := tab.height;
+ w := tab.width;
+ if(tab.capcell != nil) {
+ h += tab.capcell.height;
+ if(tab.capcell.width > w)
+ w = tab.capcell.width;
+ }
+
+ err = tk->cmd(top, canv + " configure -width " + string w
+ + " -height " + string h);
+ if(len err > 0 && err[0] == '!')
+ return err;
+ err = create_cells();
+ if(err != "")
+ return err;
+ err = create_border();
+ if(err != "")
+ return err;
+ err = create_rules();
+ if(err != "")
+ return err;
+ err = create_caption();
+ if(err != "")
+ return err;
+ tk->cmd(top, "update");
+
+ return "";
+}
+
+create_cells() : string
+{
+ for(cl := tab.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ cpos := c.pos;
+ for(l := c.lines; l != nil; l = l.next) {
+ lpos := l.pos;
+ for(it := l.items; it != nil; it = it.next) {
+ ipos := it.pos;
+ pos := ipos.add(lpos.add(cpos));
+ fnt := fontrefs[it.fontnum];
+ v := tk->cmd(top, canv + " create text " + string pos.x + " "
+ + string pos.y + " -anchor nw -font " + fnt.name
+ + " -text '" + it.s);
+ if(len v > 0 && v[0] == '!')
+ return v;
+ it.itemid = int v;
+ }
+ }
+ }
+ return "";
+}
+
+create_border() : string
+{
+ bd := tab.border;
+ if(bd > 0) {
+ x1 := string (bd / 2);
+ y1 := x1;
+ x2 := string (tab.width - bd/2 -1);
+ y2 := string (tab.height - bd/2 -1);
+ v := tk->cmd(top, canv + " create rectangle "
+ + x1 + " " + y1 + " " + x2 + " " + y2 + " -width " + string bd);
+ if(len v > 0 && v[0] == '!')
+ return v;
+ tab.brectid = int v;
+ }
+ return "";
+}
+
+create_rules() : string
+{
+ ci, ri, i: int;
+ err : string;
+ c : ref Tablecell;
+ for(ci = 0; ci < tab.ncol; ci++) {
+ col := tab.cols[ci];
+ rw := col.rule;
+ if(rw > 0) {
+ x := col.pos.x + col.width + TABHPAD/2 - rw/2;
+ ids: list of int = nil;
+ startri := 0;
+ for(ri = 0; ri < tab.nrow; ri++) {
+ c = tab.grid[ri][ci].cell;
+ if(c.col+c.colspan-1 > ci) {
+ # rule would cross a spanning cell at this column
+ if(ri > startri) {
+ (err, i) = create_col_rule(startri, ri-1, x, rw);
+ if(err != "")
+ return err;
+ ids = i :: ids;
+ }
+ startri = ri+1;
+ }
+ }
+ if(ri > startri)
+ (err, i) = create_col_rule(startri, ri-1, x, rw);
+ ids = i :: ids;
+ col.ruleids = revintl(ids);
+ }
+ }
+ for(ri = 0; ri < tab.nrow; ri++) {
+ row := tab.rows[ri];
+ rw := row.rule;
+ if(rw > 0) {
+ y := row.pos.y + row.height + TABVPAD/2 - rw/2;
+ ids: list of int = nil;
+ startci := 0;
+ for(ci = 0; ci < tab.ncol; ci++) {
+ c = tab.grid[ri][ci].cell;
+ if(c.row+c.rowspan-1 > ri) {
+ # rule would cross a spanning cell at this row
+ if(ci > startci) {
+ (err, i) = create_row_rule(startci, ci-1, y, rw);
+ if(err != "")
+ return err;
+ ids = i :: ids;
+ }
+ startci = ci+1;
+ }
+ }
+ if(ci > startci)
+ (err, i) = create_row_rule(startci, ci-1, y, rw);
+ ids = i :: ids;
+ row.ruleids = revintl(ids);
+ }
+ }
+ return "";
+}
+
+create_col_rule(topri, botri, x, rw: int) : (string, int)
+{
+ y1, y2: int;
+ if(topri == 0)
+ y1 = 0;
+ else
+ y1 = tab.rows[topri].pos.y - TABVPAD/2;
+ if(botri == tab.nrow-1)
+ y2 = tab.height;
+ else
+ y2 = tab.rows[botri].pos.y + tab.rows[botri].height + TABVPAD/2;
+ sx := string x;
+ v := tk->cmd(top, canv + " create line " + sx + " "
+ + string y1 + " " + sx + " " + string y2 + " -width " + string rw);
+ if(len v > 0 && v[0] == '!')
+ return (v, 0);
+ return ("", int v);
+}
+
+create_row_rule(leftci, rightci, y, rw: int) : (string, int)
+{
+ x1, x2: int;
+ if(leftci == 0)
+ x1 = 0;
+ else
+ x1 = tab.cols[leftci].pos.x - TABHPAD/2;
+ if(rightci == tab.ncol-1)
+ x2 = tab.width;
+ else
+ x2 = tab.cols[rightci].pos.x + tab.cols[rightci].width + TABHPAD/2;
+ sy := string y;
+ v := tk->cmd(top, canv + " create line " + string x1 + " "
+ + sy + " " + string x2 + " " + sy + " -width " + string rw);
+ if(len v > 0 && v[0] == '!')
+ return (v, 0);
+ return ("", int v);
+}
+
+create_caption() : string
+{
+ if(tab.capcell == nil)
+ return "";
+ cpos := Point(0, tab.height + 2*TABVPAD);
+ for(l := tab.capcell.lines; l != nil; l = l.next) {
+ lpos := l.pos;
+ for(it := l.items; it != nil; it = it.next) {
+ ipos := it.pos;
+ pos := ipos.add(lpos.add(cpos));
+ fnt := fontrefs[it.fontnum];
+ v := tk->cmd(top, canv + " create text " + string pos.x + " "
+ + string pos.y + " -anchor nw -font " + fnt.name
+ + " -text '" + it.s);
+ if(len v > 0 && v[0] == '!')
+ return v;
+ it.itemid = int v;
+ }
+ }
+ return "";
+}
+
+# Assuming row and col geoms correct, set row, col, and cell origins
+table_geom()
+{
+ row: ref Tablerow;
+ col: ref Tablecol;
+ orig := Point(0,0);
+ bd := tab.border;
+ if(bd > 0)
+ orig = orig.add(Point(TABHPAD+bd, TABVPAD+bd));
+ o := orig;
+ for(ci := 0; ci < tab.ncol; ci++) {
+ col = tab.cols[ci];
+ col.pos = o;
+ o.x += col.width + col.rule;
+ if(ci < tab.ncol-1)
+ o.x += TABHPAD;
+ }
+ if(bd > 0)
+ o.x += TABHPAD + bd;
+ tab.width = o.x;
+
+ o = orig;
+ for(ri := 0; ri < tab.nrow; ri++) {
+ row = tab.rows[ri];
+ row.pos = o;
+ o.y += row.height + row.rule;
+ if(ri < tab.nrow-1)
+ o.y += TABVPAD;
+ }
+ if(bd > 0)
+ o.y += TABVPAD + bd;
+ tab.height = o.y;
+
+ if(tab.capcell != nil) {
+ tabw := tab.width;
+ if(tab.capcell.width > tabw)
+ tabw = tab.capcell.width;
+ for(l := tab.capcell.lines; l != nil; l = l.next)
+ l.pos.x += (tabw - l.width)/2;
+ }
+
+ for(cl := tab.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ row = tab.rows[c.row];
+ col = tab.cols[c.col];
+ x := col.pos.x;
+ y := row.pos.y;
+ w := spanned_col_width(c.col, c.col+c.colspan-1);
+ case (cellhalign(c)) {
+ Aright =>
+ x += w - c.width;
+ Acenter =>
+ x += (w - c.width) / 2;
+ }
+ h := spanned_row_height(c.row, c.row+c.rowspan-1);
+ case (cellvalign(c)) {
+ Abottom =>
+ y += h - c.height;
+ Anone or Amiddle =>
+ y += (h - c.height) / 2;
+ Abaseline =>
+ y += row.ascent - c.ascent;
+ }
+ c.pos = Point(x,y);
+ }
+}
+
+spanned_col_width(firstci, lastci: int) : int
+{
+ firstcol := tab.cols[firstci];
+ if(firstci == lastci)
+ return firstcol.width;
+ lastcol := tab.cols[lastci];
+ return (lastcol.pos.x + lastcol.width - firstcol.pos.x);
+}
+
+spanned_row_height(firstri, lastri: int) : int
+{
+ firstrow := tab.rows[firstri];
+ if(firstri == lastri)
+ return firstrow.height;
+ lastrow := tab.rows[lastri];
+ return (lastrow.pos.y + lastrow.height - firstrow.pos.y);
+}
+
+# Assuming cell geoms are correct, set col widths.
+# This code is sloppy for spanned columns;
+# it will allocate too much space for them because
+# inter-column pad is ignored, and it may make
+# narrow columns wider than they have to be.
+col_geom(ci: int)
+{
+ col := tab.cols[ci];
+ col.width = 0;
+ for(ri := 0; ri < tab.nrow; ri++) {
+ c := tab.grid[ri][ci].cell;
+ if(c == nil)
+ continue;
+ cwd := c.width / c.colspan;
+ if(cwd > col.width)
+ col.width = cwd;
+ }
+}
+
+# Assuming cell geoms are correct, set row heights
+row_geom(ri: int)
+{
+ row := tab.rows[ri];
+ # find rows's global height and ascent
+ h := 0;
+ a := 0;
+ n : int;
+ for(cl := row.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ al := cellvalign(c);
+ if(al == Abaseline) {
+ n = c.ascent;
+ if(n > a) {
+ h += (n - a);
+ a = n;
+ }
+ n = c.height - c.ascent;
+ if(n > h-a)
+ h = a + n;
+ }
+ else {
+ n = c.height;
+ if(n > h)
+ h = n;
+ }
+ }
+ row.height = h;
+ row.ascent = a;
+}
+
+cell_geom(c: ref Tablecell)
+{
+ width := 0;
+ o := Point(0,0);
+ for(l := c.lines; l != nil; l = l.next) {
+ line_geom(l, o);
+ o.y += l.height;
+ if(l.width > width)
+ width = l.width;
+ }
+ c.width = width;
+ c.height = o.y;
+ if(c.lines != nil)
+ c.ascent = c.lines.ascent;
+ else
+ c.ascent = 0;
+
+ al := cellhalign(c);
+ if(al == Acenter || al == Aright) {
+ for(l = c.lines; l != nil; l = l.next) {
+ xdelta := c.width - l.width;
+ if(al == Acenter)
+ xdelta /= 2;
+ l.pos.x += xdelta;
+ }
+ }
+}
+
+caption_geom()
+{
+ if(tab.capcell != nil) {
+ o := Point(0,TABVPAD);
+ width := 0;
+ for(l := tab.capcell.lines; l != nil; l = l.next) {
+ line_geom(l, o);
+ o.y += l.height;
+ if(l.width > width)
+ width = l.width;
+ }
+ tab.capcell.width = width;
+ tab.capcell.height = o.y + 4*TABVPAD;
+ }
+}
+
+line_geom(l: ref Line, o: Point)
+{
+ # find line's global height and ascent
+ h := 0;
+ a := 0;
+ for(it := l.items; it != nil; it = it.next) {
+ fnt := fontrefs[it.fontnum];
+ n := fnt.ascent;
+ if(n > a) {
+ h += (n - a);
+ a = n;
+ }
+ n = fnt.height - fnt.ascent;
+ if(n > h-a)
+ h = a + n;
+ }
+ l.height = h;
+ l.ascent = a;
+ # set positions
+ l.pos = o;
+ for(it = l.items; it != nil; it = it.next) {
+ fnt := fontrefs[it.fontnum];
+ it.width = fnt.width(it.s);
+ it.pos.x = o.x;
+ o.x += it.width;
+ it.pos.y = a - fnt.ascent;
+ }
+ l.width = o.x;
+}
+
+cellhalign(c: ref Tablecell) : int
+{
+ a := c.align.halign;
+ if(a == Anone)
+ a = tab.cols[c.col].align.halign;
+ return a;
+}
+
+cellvalign(c: ref Tablecell) : int
+{
+ a := c.align.valign;
+ if(a == Anone)
+ a = tab.rows[c.row].align.valign;
+ return a;
+}
+
+# table debugging
+printtable()
+{
+ if(tab == nil) {
+ sys->print("no table\n");
+ return;
+ }
+ sys->print("Table %d rows, %d cols width %d height %d\n",
+ tab.nrow, tab.ncol, tab.width, tab.height);
+ if(tab.capcell != nil)
+ sys->print(" caption: "); printlexes(tab.capcell.content, " ");
+ sys->print(" cols:\n"); printcols(tab.cols);
+ sys->print(" rows:\n"); printrows(tab.rows);
+}
+
+align2string(al: int) : string
+{
+ s := "";
+ case al {
+ Anone => s = "none";
+ Aleft => s = "left";
+ Acenter => s = "center";
+ Aright => s = "right";
+ Ajustify => s = "justify";
+ Atop => s = "top";
+ Amiddle => s = "middle";
+ Abottom => s = "bottom";
+ Abaseline => s = "baseline";
+ }
+ return s;
+}
+
+printcols(cols: array of ref Tablecol)
+{
+ n := len cols;
+ for(i := 0 ; i < n; i++) {
+ c := cols[i];
+ sys->print(" width %d align = %s,%s pos (%d,%d) rule %d\n", c.width,
+ align2string(c.align.halign), align2string(c.align.valign), c.pos.x, c.pos.y, c.rule);
+ }
+}
+
+printrows(rows: array of ref Tablerow)
+{
+ n := len rows;
+ for(i := 0; i < n; i++) {
+ tr := rows[i];
+ sys->print(" row height %d ascent %d align=%s,%s pos (%d,%d) rule %d\n", tr.height, tr.ascent,
+ align2string(tr.align.halign), align2string(tr.align.valign), tr.pos.x, tr.pos.y, tr.rule);
+ for(cl := tr.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ sys->print(" cell %d width %d height %d ascent %d align=%s,%s\n",
+ c.cellid, c.width, c.height, c.ascent,
+ align2string(c.align.halign), align2string(c.align.valign));
+ sys->print(" pos (%d,%d) rowspan=%d colspan=%d nowrap=%d\n",
+ c.pos.x, c.pos.y, c.rowspan, c.colspan, c.nowrap);
+ printlexes(c.content, " ");
+ printlines(c.lines);
+ }
+ }
+}
+
+printlexes(lexes: array of ref Lex, indent: string)
+{
+ for(i := 0; i < len lexes; i++)
+ sys->print("%s%s\n", indent, html->lex2string(lexes[i]));
+}
+
+printlines(l: ref Line)
+{
+ if(l == nil)
+ return;
+ sys->print("lines: \n");
+ while(l != nil) {
+ sys->print(" Line: pos (%d,%d), height %d ascent %d\n", l.pos.x, l.pos.y, l.height, l.ascent);
+ printitems(l.items);
+ l = l.next;
+ }
+}
+
+printitems(i: ref Item)
+{
+ while(i != nil) {
+ sys->print(" '%s' id %d fontnum %d w %d, pos (%d,%d)\n", i.s, i.itemid, i.fontnum,
+ i.width, i.pos.x, i.pos.y);
+ i = i.next;
+ }
+}
+
+printgrid(g: array of array of ref Tablegcell)
+{
+ nr := len g;
+ nc := len g[0];
+ for(r := 0; r < nr; r++) {
+ for(c := 0; c < nc; c++) {
+ x := g[r][c];
+ cell := x.cell;
+ suf := " ";
+ if(x.drawnhere == 0)
+ suf = "*";
+ if(cell == nil)
+ sys->print(" %s", suf);
+ else
+ sys->print("%5d%s", cell.cellid, suf);
+ }
+ sys->print("\n");
+ }
+}
+
+# Return (table in correct format, error string)
+cook(parent: string, fmt: int, args: string) : (ref Celem, string)
+{
+ (spec, err) := getspec(parent, args);
+ if(err != "")
+ return (nil, err);
+ if(fmt == FHtml)
+ return cookhtml(spec);
+ else
+ return cooklatex(spec);
+}
+
+# Return (table as latex, error string)
+# BUG: cells spanning multiple rows not handled correctly
+# (all their contents go in the first row of span, though hrules properly broken)
+cooklatex(spec: array of ref Lex) : (ref Celem, string)
+{
+ s : string;
+ ci, ri: int;
+ err := parsetab(spec);
+ if(err != "")
+ return (nil, err_ret(err));
+
+ setgrid();
+
+ ans := ref Celem(SGML, "", nil, nil, nil, nil);
+ cur : ref Celem = nil;
+ cur = add(ans, cur, specialce("\\begin{tabular}[t]{" + lcolspec() + "}\n"));
+ if(tab.border) {
+ if(tab.border == 1)
+ s = "\\hline\n";
+ else
+ s = "\\hline\\hline\n";
+ cur = add(ans, cur, specialce(s));
+ }
+ for(ri = 0; ri < tab.nrow; ri++) {
+ row := tab.rows[ri];
+ ci = 0;
+ anyrowspan := 0;
+ for(cl := row.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ while(ci < c.col) {
+ cur = add(ans, cur, specialce("&"));
+ ci++;
+ }
+ mcol := 0;
+ if(c.colspan > 1) {
+ cur = add(ans, cur, specialce("\\multicolumn{" + string c.colspan + "}{" +
+ lnthcolspec(ci, ci+c.colspan-1, c.align.halign) + "}{"));
+ mcol = 1;
+ }
+ else if(c.align.halign != Anone) {
+ cur = add(ans, cur, specialce("\\multicolumn{1}{" +
+ lnthcolspec(ci, ci, c.align.halign) + "}{"));
+ mcol = 1;
+ }
+ if(c.rowspan > 1)
+ anyrowspan = 1;
+ cur = addlconvlines(ans, cur, c);
+ if(mcol) {
+ cur = add(ans, cur, specialce("}"));
+ ci += c.colspan-1;
+ }
+ }
+ while(ci++ < tab.ncol-1)
+ cur = add(ans, cur, specialce("&"));
+ if(ri < tab.nrow-1 || row.rule > 0 || tab.border > 0)
+ cur = add(ans, cur, specialce("\\\\\n"));
+ if(row.rule) {
+ if(anyrowspan) {
+ startci := 0;
+ for(ci = 0; ci < tab.ncol; ci++) {
+ c := tab.grid[ri][ci].cell;
+ if(c.row+c.rowspan-1 > ri) {
+ # rule would cross a spanning cell at this row
+ if(ci > startci)
+ cur = add(ans, cur, specialce("\\cline{" +
+ string (startci+1) + "-" + string ci + "}"));
+ startci = ci+1;
+ }
+ }
+ if(ci > startci)
+ cur = add(ans, cur, specialce("\\cline{" +
+ string (startci+1) + "-" + string ci + "}"));
+ }
+ else
+ cur = add(ans, cur, specialce("\\hline\n"));
+ }
+ }
+ if(tab.border) {
+ if(tab.border == 1)
+ s = "\\hline\n";
+ else
+ s = "\\hline\\hline\n";
+ cur = add(ans, cur, specialce(s));
+ }
+ cur = add(ans, cur, specialce("\\end{tabular}\n"));
+
+ if(ans != nil)
+ ans = ans.contents;
+ return (ans, "");
+}
+
+lcolspec() : string
+{
+ ans := "";
+ for(ci := 0; ci < tab.ncol; ci++)
+ ans += lnthcolspec(ci, ci, Anone);
+ return ans;
+}
+
+lnthcolspec(ci, cie, al: int) : string
+{
+ ans := "";
+ if(ci == 0) {
+ if(tab.border == 1)
+ ans = "|";
+ else if(tab.border > 1)
+ ans = "||";
+ }
+ col := tab.cols[ci];
+ if(al == Anone)
+ al = col.align.halign;
+ case al {
+ Acenter =>
+ ans += "c";
+ Aright =>
+ ans += "r";
+ * =>
+ ans += "l";
+ }
+ if(ci == cie) {
+ if(col.rule == 1)
+ ans += "|";
+ else if(col.rule > 1)
+ ans += "||";
+ }
+ if(cie == tab.ncol - 1) {
+ if(tab.border == 1)
+ ans += "|";
+ else if(tab.border > 1)
+ ans += "||";
+ }
+ return ans;
+}
+
+addlconvlines(par, tail: ref Celem, c: ref Tablecell) : ref Celem
+{
+ line := c.lines;
+ if(line == nil)
+ return tail;
+ multiline := 0;
+ if(line.next != nil) {
+ multiline = 1;
+ val := "";
+ case cellvalign(c) {
+ Abaseline or Atop => val = "[t]";
+ Abottom => val = "[b]";
+ }
+ hal := "l";
+ case cellhalign(c) {
+ Aright => hal = "r";
+ Acenter => hal = "c";
+ }
+ # The @{}'s in the colspec eliminate extra space before and after result
+ tail = add(par, tail, specialce("\\begin{tabular}" + val + "{@{}" + hal + "@{}}\n"));
+ }
+ while(line != nil) {
+ for(it := line.items; it != nil; it = it.next) {
+ fnum := it.fontnum;
+ f := fnum / NSIZE;
+ sz := fnum % NSIZE;
+ grouped := 0;
+ if((f != DefFont || sz != DefSize) && (it.prev!=nil || it.next!=nil)) {
+ tail = add(par, tail, specialce("{"));
+ grouped = 1;
+ }
+ if(f != DefFont) {
+ fcmd := "";
+ case f {
+ Roman => fcmd = "\\rmfamily ";
+ Italic => fcmd = "\\itshape ";
+ Bold => fcmd = "\\bfseries ";
+ Type => fcmd = "\\ttfamily ";
+ }
+ tail = add(par, tail, specialce(fcmd));
+ }
+ if(sz != DefSize) {
+ szcmd := "";
+ case sz {
+ Size6 => szcmd = "\\footnotesize ";
+ Size8 => szcmd = "\\small ";
+ Size10 => szcmd = "\\normalsize ";
+ Size12 => szcmd = "\\large ";
+ Size16 => szcmd = "\\Large ";
+ }
+ tail = add(par, tail, specialce(szcmd));
+ }
+ tail = add(par, tail, textce(it.s));
+ if(grouped)
+ tail = add(par, tail, specialce("}"));
+ }
+ ln := line.next;
+ if(multiline && ln != nil)
+ tail = add(par, tail, specialce("\\\\\n"));
+ line = line.next;
+ }
+ if(multiline)
+ tail = add(par, tail, specialce("\\end{tabular}\n"));
+ return tail;
+}
+
+# Return (table as html, error string)
+cookhtml(spec: array of ref Lex) : (ref Celem, string)
+{
+ n := len spec;
+ ans := ref Celem(SGML, "", nil, nil, nil, nil);
+ cur : ref Celem = nil;
+ for(i := 0; i < n; i++) {
+ tok := spec[i];
+ if(tok.tag == Data)
+ cur = add(ans, cur, textce(tok.text));
+ else {
+ s := html->lex2string(spec[i]);
+ cur = add(ans, cur, specialce(s));
+ }
+ }
+ if(ans != nil)
+ ans = ans.contents;
+ return (ans, "");
+}
+
+textce(s: string) : ref Celem
+{
+ return ref Celem(Text, s, nil, nil, nil, nil);
+}
+
+specialce(s: string) : ref Celem
+{
+ return ref Celem(Special, s, nil, nil, nil, nil);
+}
+
+add(par, tail: ref Celem, e: ref Celem) : ref Celem
+{
+ if(tail == nil) {
+ par.contents = e;
+ e.parent = par;
+ }
+ else
+ tail.next = e;
+ e.prev = tail;
+ return e;
+}
+
+fullname(parent, file: string): string
+{
+ if(len parent==0 || (len file>0 && (file[0]=='/' || file[0]=='#')))
+ return file;
+
+ for(i:=len parent-1; i>=0; i--)
+ if(parent[i] == '/')
+ return parent[0:i+1] + file;
+ return file;
+}
diff --git a/appl/wm/c4.b b/appl/wm/c4.b
new file mode 100644
index 00000000..185b807b
--- /dev/null
+++ b/appl/wm/c4.b
@@ -0,0 +1,718 @@
+implement Connect;
+
+#
+# Copyright © 2000 Vita Nuova Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+ Point, Rect, Image, Font, Context, Screen, Display: import draw;
+include "tk.m";
+ tk: Tk;
+ Toplevel: import tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+include "daytime.m";
+ daytime: Daytime;
+include "rand.m";
+ rand: Rand;
+
+# adtize and modularize
+
+stderr: ref Sys->FD;
+
+Connect: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+nosleep, printout, auto: int;
+display: ref Draw->Display;
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ daytime = load Daytime Daytime->PATH;
+ rand = load Rand Rand->PATH;
+
+ argv = tl argv;
+ while(argv != nil){
+ s := hd argv;
+ if(s != nil && s[0] == '-'){
+ for(i := 1; i < len s; i++){
+ case s[i]{
+ 'a' => auto = 1;
+ 'p' => printout = 1;
+ 's' => nosleep = 1;
+ }
+ }
+ }
+ argv = tl argv;
+ }
+ stderr = sys->fildes(2);
+ rand->init(daytime->now());
+ daytime = nil;
+
+ if(ctxt == nil)
+ fatal("wm not running");
+ display = ctxt.display;
+ tkclient->init();
+ (win, wmcmd) := tkclient->toplevel(ctxt, "", "Connect", Tkclient->Resize | Tkclient->Hide);
+ mainwin = win;
+ sys->pctl(Sys->NEWPGRP, nil);
+ cmdch := chan of string;
+ tk->namechan(win, cmdch, "cmd");
+ for(i := 0; i < len win_config; i++)
+ cmd(win, win_config[i]);
+ pid := -1;
+ sync := chan of int;
+ mvch := chan of (int, int);
+ initboard();
+ setimage();
+ spawn game(sync, mvch);
+ pid = <- sync;
+ tkclient->onscreen(win, nil);
+ tkclient->startinput(win, "kbd"::"ptr"::nil);
+
+ for(;;){
+ alt{
+ s := <-win.ctxt.kbd =>
+ tk->keyboard(win, s);
+ s := <-win.ctxt.ptr =>
+ tk->pointer(win, *s);
+ c := <-win.ctxt.ctl or
+ c = <-win.wreq or
+ c = <-wmcmd =>
+ case c{
+ "exit" =>
+ if(pid != -1)
+ kill(pid);
+ exit;
+ * =>
+ e := tkclient->wmctl(win, c);
+ if(e == nil && c[0] == '!'){
+ setimage();
+ drawboard();
+ }
+ }
+ c := <- cmdch =>
+ (nil, toks) := sys->tokenize(c, " ");
+ case hd toks{
+ "b1" or "b2" or "b3" =>
+ alt{
+ mvch <-= (int hd tl toks, int hd tl tl toks) => ;
+ * => ;
+ }
+ "bh" or "bm" or "wh" or "wm" =>
+ colour := BLACK;
+ knd := HUMAN;
+ if((hd toks)[0] == 'w')
+ colour = WHITE;
+ if((hd toks)[1] == 'm')
+ knd = MACHINE;
+ kind[colour] = knd;
+ "blev" or "wlev" =>
+ colour := BLACK;
+ e := "be";
+ if((hd toks)[0] == 'w'){
+ colour = WHITE;
+ e = "we";
+ }
+ sk := int cmd(win, ".f0." + e + " get");
+ if(sk > MAXPLIES)
+ sk = MAXPLIES;
+ if(sk >= 0)
+ skill[colour] = sk;
+ * =>
+ ;
+ }
+ <- sync =>
+ pid = -1;
+ # exit;
+ spawn game(sync, mvch);
+ pid = <- sync;
+ }
+ }
+}
+
+WIDTH: con 400;
+HEIGHT: con 400;
+
+SZW: con 7;
+SZH: con 6;
+SZC: con 4;
+SZS: con 1024;
+PIECES: con SZW*SZH;
+
+BLACK, WHITE, EMPTY: con iota;
+MACHINE, HUMAN: con iota;
+SKILLB : con 8;
+SKILLW : con 0;
+MAXPLIES: con 10;
+
+board: array of array of int; # for display
+brd: array of array of int; # for calculations
+col: array of int;
+pieces: array of int;
+val: array of int;
+kind: array of int;
+skill: array of int;
+name: array of string;
+lines: array of array of int;
+line: array of array of list of int;
+
+mainwin: ref Toplevel;
+brdimg: ref Image;
+brdr: Rect;
+brdx, brdy: int;
+
+black, white, bg: ref Image;
+
+movech: chan of (int, int);
+
+setimage()
+{
+ brdw := int tk->cmd(mainwin, ".p cget -actwidth");
+ brdh := int tk->cmd(mainwin, ".p cget -actheight");
+ brdr = Rect((0,0), (brdw, brdh));
+ brdimg = display.newimage(brdr, display.image.chans, 0, Draw->White);
+ if(brdimg == nil)
+ fatal("not enough image memory");
+ tk->putimage(mainwin, ".p", brdimg, nil);
+}
+
+game(sync: chan of int, mvch: chan of (int, int))
+{
+ sync <-= sys->pctl(0, nil);
+ movech = mvch;
+ initbrd();
+ play();
+ sync <-= 0;
+}
+
+initboard()
+{
+ i, j, k: int;
+
+ board = array[SZW] of array of int;
+ brd = array[SZW] of array of int;
+ line = array[SZW] of array of list of int;
+ col = array[SZW] of int;
+ for(i = 0; i < SZW; i++){
+ board[i] = array[SZH] of int;
+ brd[i] = array[SZH] of int;
+ line[i] = array[SZH] of list of int;
+ }
+ pieces = array[2] of int;
+ val = array[2] of int;
+ kind = array[2] of int;
+ kind[BLACK] = MACHINE;
+ if(auto)
+ kind[WHITE] = MACHINE;
+ else
+ kind[WHITE] = HUMAN;
+ skill = array[2] of int;
+ skill[BLACK] = SKILLB;
+ skill[WHITE] = SKILLW;
+ name = array[2] of string;
+ name[BLACK] = "black";
+ name[WHITE] = "white";
+ black = display.color(Draw->Black);
+ white = display.color(Draw->White);
+ bg = display.color(Draw->Yellow);
+ n := SZW*(SZH-SZC+1)+SZH*(SZW-SZC+1)+2*(SZH-SZC+1)*(SZW-SZC+1);
+ lines = array[n] of array of int;
+ for(i = 0; i < n; i++)
+ lines[i] = array[2] of int;
+ m := 0;
+ for(i = 0; i < SZW; i++){
+ for(j = 0; j <= SZH-SZC; j++){
+ for(k = 0; k < SZC; k++){
+ line[i][j+k] = m :: line[i][j+k];
+ }
+ m++;
+ }
+ }
+ for(i = 0; i < SZH; i++){
+ for(j = 0; j <= SZW-SZC; j++){
+ for(k = 0; k < SZC; k++){
+ line[j+k][i] = m :: line[j+k][i];
+ }
+ m++;
+ }
+ }
+ for(i = 0; i <= SZW-SZC; i++){
+ for(j = 0; j <= SZH-SZC; j++){
+ for(k = 0; k < SZC; k++){
+ line[i+k][j+k] = m :: line[i+k][j+k];
+ }
+ m++;
+ }
+ }
+ for(i = 0; i <= SZW-SZC; i++){
+ for(j = 0; j <= SZH-SZC; j++){
+ for(k = 0; k < SZC; k++){
+ line[SZW-1-i-k][j+k] = m :: line[SZW-1-i-k][j+k];
+ }
+ m++;
+ }
+ }
+ if(m != n)
+ fatal(sys->sprint("%d != %d\n", m, n));
+}
+
+initbrd()
+{
+ i, j: int;
+
+ for(i = 0; i < SZW; i++){
+ col[i] = 0;
+ for(j = 0; j < SZH; j++)
+ board[i][j] = brd[i][j] = EMPTY;
+ }
+ pieces[BLACK] = pieces[WHITE] = 0;
+ val[BLACK] = val[WHITE] = 0;
+ drawboard();
+ n := len lines;
+ for(i = 0; i < n; i++)
+ lines[i][0] = lines[i][1] = 0;
+}
+
+plays := 0;
+bwins := 0;
+wwins := 0;
+
+play()
+{
+ if(plays&1)
+ (first, second) := (WHITE, BLACK);
+ else
+ (first, second) = (BLACK, WHITE);
+ for(;;){
+ if(pieces[BLACK]+pieces[WHITE] == PIECES)
+ break;
+ m1 := move(first, second);
+ if(printout)
+ sys->print("%s: %d %d %d\n", name[first], m1, val[BLACK], val[WHITE]);
+ if(win(first))
+ break;
+ if(pieces[BLACK]+pieces[WHITE] == PIECES)
+ break;
+ m2 := move(second, first);
+ if(printout)
+ sys->print("%s: %d %d %d\n", name[second], m2, val[BLACK], val[WHITE]);
+ if(win(second))
+ break;
+ }
+ if(win(BLACK)){
+ bwins++;
+ puts("black wins");
+ highlight(BLACK);
+ }
+ else if(win(WHITE)){
+ wwins++;
+ puts("white wins");
+ highlight(WHITE);
+ }
+ else
+ puts("draw");
+ sleep(2500);
+ plays++;
+ puts(sys->sprint("black %d:%d white", bwins, wwins));
+ sleep(2500);
+ if(printout)
+ sys->print("\n");
+}
+
+move(me: int, you: int): int
+{
+ if(kind[me] == MACHINE){
+ puts("machine " + name[me] + " move");
+ return genmove(me, you);
+ }
+ else{
+ m, n: int;
+
+ # mvs := findmoves();
+ for(;;){
+ puts("human " + name[me] + " move");
+ m = getmove();
+ if(m < 0 || m >= SZW)
+ continue;
+ n = col[m];
+ valid := n >= 0 && n < SZH;
+ if(valid && brd[m][n] != EMPTY)
+ fatal("! EMPTY");
+ if(valid)
+ break;
+ puts("illegal move");
+ sleep(2500);
+ }
+ makemove(m, n, me, you, 0);
+ return m*SZS+n;
+ }
+}
+
+genmove(me: int, you: int): int
+{
+ m, n, v: int;
+
+ mvs := findmoves();
+ if(skill[me] == 0){
+ l := len mvs;
+ r := rand->rand(l);
+ # r = 0;
+ while(--r >= 0)
+ mvs = tl mvs;
+ (m, n) = hd mvs;
+ }
+ else{
+ plies := skill[me];
+ left := PIECES-(pieces[BLACK]+pieces[WHITE]);
+ if(left < plies) # limit search
+ plies = left;
+ else if(left < 2*plies) # expand search to end
+ plies = left;
+ else{ # expand search nearer end of game
+ k := left/plies;
+ if(k < 3)
+ plies = ((k+2)*plies)/(k+1);
+ }
+ visits = leaves = 0;
+ (v, (m, n)) = minimax(me, you, plies, ∞);
+ if(0){
+ while(mvs != nil){
+ v0: int;
+ (a, b) := hd mvs;
+ makemove(a, b, me, you, 1);
+ (v0, (m, n)) = minimax(you, me, plies-1, ∞);
+ sys->print(" (%d, %d): %d\n", a, b, -v0);
+ undomove(a, b, me, you);
+ mvs = tl mvs;
+ }
+ sys->print("best move is %d, %d\n", m, n);
+ kind[WHITE] = HUMAN;
+ }
+ if(auto)
+ sys->print("eval = %d plies=%d goes=%d visits=%d\n", v, plies, len mvs, leaves);
+ }
+ makemove(m, n, me, you, 0);
+ return m*SZS+n;
+}
+
+findmoves(): list of (int, int)
+{
+ mvs: list of (int, int);
+
+ for(i := 0; i < SZW; i++){
+ if((j := col[i]) < SZH)
+ mvs = (i, j) :: mvs;
+ }
+ return mvs;
+}
+
+makemove(m: int, n: int, me: int, you: int, gen: int)
+{
+ pieces[me]++;
+ brd[m][n] = me;
+ col[m]++;
+ for(l := line[m][n]; l != nil; l = tl l){
+ i := hd l;
+ a := lines[i][me];
+ b := lines[i][you];
+ lines[i][me]++;
+ if(a+b >= SZC)
+ fatal("makemove a+b");
+ if(b == 0){
+ val[me] += 2*a+1;
+ if(a == SZC-1)
+ val[me] += WIN;
+ }
+ else if(a == 0)
+ val[you] -= b*b;
+ }
+ if(!gen){
+ board[m][n] = me;
+ drawpiece(m, n, me);
+ panelupdate();
+ # sleep(1000);
+ }
+}
+
+undomove(m: int, n: int, me: int, you: int)
+{
+ brd[m][n] = EMPTY;
+ pieces[me]--;
+ col[m]--;
+ for(l := line[m][n]; l != nil; l = tl l){
+ i := hd l;
+ a := lines[i][me];
+ b := lines[i][you];
+ lines[i][me]--;
+ if(a == 0 || a+b > SZC)
+ fatal("undomove a+b");
+ if(b == 0){
+ val[me] -= 2*a-1;
+ if(a == SZC)
+ val[me] -= WIN;
+ }
+ else if(a == 1)
+ val[you] += b*b;
+ }
+}
+
+win(me: int): int
+{
+ return val[me] > WIN/2;
+}
+
+highlight(me: int)
+{
+ n := len lines;
+ for(i := 0; i < n; i++){
+ if(lines[i][me] == SZC){
+ for(j := 0; j < SZW; j++){
+ for(k := 0; k < SZH; k++){
+ for(l := line[j][k]; l != nil; l = tl l){
+ if(i == hd l)
+ highpiece(j, k, board[j][k]);
+ }
+ }
+ }
+ }
+ }
+}
+
+getmove(): int
+{
+ (x, nil) := <- movech;
+ return x/brdx;
+}
+
+drawboard()
+{
+ brdx = brdr.dx()/SZW;
+ brdy = brdr.dy()/SZH;
+ brdimg.draw(brdr, bg, nil, (0, 0));
+ for(i := 1; i < SZW; i++)
+ drawline(lmap(i, 0), lmap(i, SZH), nil);
+ for(j := 1; j < SZH; j++)
+ drawline(lmap(0, j), lmap(SZW, j), nil);
+ for(i = 0; i < SZW; i++){
+ for(j = 0; j < SZH; j++){
+ if (board[i][j] == BLACK || board[i][j] == WHITE)
+ drawpiece(i, j, board[i][j]);
+ }
+ }
+ panelupdate();
+}
+
+drawpiece(m, n, p: int)
+{
+ if(p == BLACK)
+ src := black;
+ else if(p == WHITE)
+ src = white;
+ else
+ src = bg;
+ brdimg.fillellipse(cmap(m, n), 3*brdx/8, 3*brdy/8, src, (0, 0));
+}
+
+highpiece(m, n, p: int)
+{
+ if(p == BLACK)
+ src := white;
+ else if(p == WHITE)
+ src = black;
+ else
+ src = bg;
+ pt := cmap(m, n);
+ rx := (3*brdx/8, 0);
+ ry := (0, 3*brdy/8);
+ drawline(pt.add(rx), pt.sub(rx), src);
+ drawline(pt.add(ry), pt.sub(ry), src);
+}
+
+panelupdate()
+{
+ tk->cmd(mainwin, sys->sprint(".p dirty %d %d %d %d", brdr.min.x, brdr.min.y, brdr.max.x, brdr.max.y));
+ tk->cmd(mainwin, "update");
+}
+
+drawline(p0, p1: Point, c: ref Image)
+{
+ if(c == nil)
+ c = black;
+ brdimg.line(p0, p1, Draw->Endsquare, Draw->Endsquare, 0, c, (0, 0));
+}
+
+cmap(m, n: int): Point
+{
+ return brdr.min.add((m*brdx+brdx/2, (SZH-1-n)*brdy+brdy/2));
+}
+
+lmap(m, n: int): Point
+{
+ return brdr.min.add((m*brdx, n*brdy));
+}
+
+∞: con (1<<30);
+WIN: con (1<<20);
+MAXVISITS: con 1024;
+
+visits, leaves : int;
+
+minimax(me: int, you: int, plies: int, αβ: int): (int, (int, int))
+{
+ v: int;
+
+ if(plies == 0){
+ visits++;
+ leaves++;
+ if(visits == MAXVISITS){
+ visits = 0;
+ sys->sleep(0);
+ }
+ return (eval(me, you), (0, 0));
+ }
+ mvs := findmoves();
+ if(mvs == nil){
+ fatal("mvs==nil");
+ # if(mv)
+ # (v, nil) := minimax(you, me, plies, ∞);
+ # else
+ # (v, nil) = minimax(you, me, plies-1, ∞);
+ # return (-v, (0, 0));
+ }
+ bestv := -∞;
+ bestm := (0, 0);
+ e := 0;
+ for(; mvs != nil; mvs = tl mvs){
+ (m, n) := hd mvs;
+ makemove(m, n, me, you, 1);
+ if(win(me))
+ v = eval(me, you);
+ else{
+ (v, nil) = minimax(you, me, plies-1, -bestv);
+ v = -v;
+ }
+ undomove(m, n, me, you);
+ if(v > bestv || (v == bestv && rand->rand(++e) == 0)){
+ if(v > bestv)
+ e = 1;
+ bestv = v;
+ bestm = (m, n);
+ if(bestv >= αβ)
+ return (∞, (0, 0));
+ }
+ }
+ return (bestv, bestm);
+}
+
+eval(me: int, you: int): int
+{
+ return val[me]-val[you];
+}
+
+fatal(s: string)
+{
+ sys->fprint(stderr, "%s\n", s);
+ exit;
+}
+
+sleep(t: int)
+{
+ if(nosleep)
+ sys->sleep(0);
+ else
+ sys->sleep(t);
+}
+
+kill(pid: int): int
+{
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd == nil)
+ return -1;
+ if(sys->write(fd, array of byte "kill", 4) != 4)
+ return -1;
+ return 0;
+}
+
+cmd(top: ref Toplevel, s: string): string
+{
+ e := tk->cmd(top, s);
+ if (e != nil && e[0] == '!')
+ sys->fprint(stderr, "connect: tk error on '%s': %s\n", s, e);
+ return e;
+}
+
+# swidth: int;
+# sfont: ref Font;
+
+# gettxtattrs()
+# {
+# swidth = int cmd(mainwin, ".f1.txt cget -width"); # always initial value ?
+# f := cmd(mainwin, ".f1.txt cget -font");
+# sfont = Font.open(brdimg.display, f);
+# }
+
+puts(s: string)
+{
+ # while(sfont.width(s) > swidth)
+ # s = s[0: len s -1];
+ cmd(mainwin, ".f1.txt configure -text {" + s + "}");
+ cmd(mainwin, "update");
+}
+
+win_config := array[] of {
+ "frame .f",
+ "menubutton .f.bk -text Black -menu .f.bk.bm",
+ "menubutton .f.wk -text White -menu .f.wk.wm",
+ "menu .f.bk.bm",
+ ".f.bk.bm add command -label Human -command { send cmd bh }",
+ ".f.bk.bm add command -label Machine -command { send cmd bm }",
+ "menu .f.wk.wm",
+ ".f.wk.wm add command -label Human -command { send cmd wh }",
+ ".f.wk.wm add command -label Machine -command { send cmd wm }",
+ "pack .f.bk -side left",
+ "pack .f.wk -side right",
+
+ "frame .f0",
+ "label .f0.bl -text {Black level}",
+ "label .f0.wl -text {White level}",
+ "entry .f0.be -width 32",
+ "entry .f0.we -width 32",
+ ".f0.be insert 0 {" + string SKILLB+"}",
+ ".f0.we insert 0 {" + string SKILLW+"}",
+ "pack .f0.bl -side left",
+ "pack .f0.be -side left",
+ "pack .f0.wl -side right",
+ "pack .f0.we -side right",
+
+ "frame .f1",
+ "label .f1.txt -text { } -width " + string WIDTH,
+ "pack .f1.txt -side top -fill x",
+
+ "panel .p -width " + string WIDTH + " -height " + string HEIGHT,
+
+ "pack .f -side top -fill x",
+ "pack .f0 -side top -fill x",
+ "pack .f1 -side top -fill x",
+ "pack .p -side bottom -fill both -expand 1",
+ "pack propagate . 0",
+
+ "bind .p <Button-1> {send cmd b1 %x %y}",
+ "bind .p <Button-2> {send cmd b2 %x %y}",
+ "bind .p <Button-3> {send cmd b3 %x %y}",
+ # "bind .c <ButtonRelease-1> {send cmd b1r %x %y}",
+ # "bind .c <ButtonRelease-2> {send cmd b2r %x %y}",
+ # "bind .c <ButtonRelease-3> {send cmd b3r %x %y}",
+ "bind .f0.be <Key-\n> {send cmd blev}",
+ "bind .f0.we <Key-\n> {send cmd wlev}",
+ "update",
+};
diff --git a/appl/wm/calendar.b b/appl/wm/calendar.b
new file mode 100644
index 00000000..6e52afd4
--- /dev/null
+++ b/appl/wm/calendar.b
@@ -0,0 +1,1064 @@
+implement Calendar;
+
+#
+# Copyright © 2000 Vita Nuova Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+ Font, Point, Rect: import draw;
+include "daytime.m";
+ daytime: Daytime;
+ Tm: import Daytime;
+include "tk.m";
+ tk: Tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+include "dialog.m";
+ dialog: Dialog;
+include "readdir.m";
+include "translate.m";
+ translate: Translate;
+ Dict: import translate;
+include "arg.m";
+ arg: Arg;
+include "sh.m";
+
+Calendar: module {
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Cal: adt {
+ w: string;
+ dx, dy: int;
+ onepos: int;
+ top: ref Tk->Toplevel;
+ sched: ref Schedule;
+ date: int;
+ marked: array of int;
+ make: fn(top: ref Tk->Toplevel, sched: ref Schedule, w: string): (ref Cal, chan of string);
+ show: fn(cal: self ref Cal, date: int);
+ mark: fn(cal: self ref Cal, ent: Entry);
+};
+
+Entry: adt {
+ date: int; # YYYYMMDD
+ mark: int;
+};
+
+Sentry: adt {
+ ent: Entry;
+ file: int;
+};
+
+Schedule: adt {
+ dir: string;
+ entries: array of Sentry;
+ new: fn(dir: string): (ref Schedule, string);
+ getentry: fn(sched: self ref Schedule, date: int): (int, Entry);
+ readentry: fn(sched: self ref Schedule, date: int): (Entry, string);
+ setentry: fn(sched: self ref Schedule, ent: Entry, data: string): (int, string);
+};
+
+Markset: adt {
+ new: fn(top: ref Tk->Toplevel, cal: ref Cal, w: string): (ref Markset, chan of string);
+ set: fn(m: self ref Markset, kind: int);
+ get: fn(m: self ref Markset): int;
+ ctl: fn(m: self ref Markset, c: string);
+
+ top: ref Tk->Toplevel;
+ cal: ref Cal;
+ w: string;
+ curr: int;
+};
+
+DBFSPATH: con "/dis/rawdbfs.dis";
+SCHEDDIR: con "/mnt/schedule";
+
+stderr: ref Sys->FD;
+dict: ref Dict;
+font := "/fonts/lucidasans/unicode.7.font";
+days, months: array of string;
+
+packcmds := array[] of {
+"pack .ctf.show .ctf.set .ctf.date -side right",
+"pack .ctf -side top -fill x",
+
+"pack .cf.head.fwd .cf.head.bwd .cf.head.date -side right",
+"pack .cf.head -side top -fill x",
+"pack .cf.cal -side top",
+"pack .cf -side top",
+
+"pack .schedf.head.fwd .schedf.head.bwd .schedf.head.date .schedf.head.markset"
+ + " .schedf.head.save .schedf.head.del -side right",
+"pack .schedf.head -side top -fill x",
+"pack .schedf.tf.scroll -side left -fill y",
+"pack .schedf.tf.t -side top -fill both -expand 1",
+"pack .schedf.tf -side top -fill both -expand 1",
+"pack .schedf -side top -fill both -expand 1",
+};
+
+Savebut: con ".schedf.head.save";
+Delbut: con ".schedf.head.del";
+
+usage()
+{
+ sys->fprint(stderr, "usage: calendar [-f font] [/mnt/schedule | schedfile]\n");
+ raise "fail:usage";
+}
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ loadmods();
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "calendar: no window context\n");
+ raise "fail:bad context";
+ }
+ days = Xa(array[] of {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"});
+ months = Xa(array[] of {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"});
+ arg->init(argv);
+ while ((opt := arg->opt()) != 0) {
+ case opt {
+ 'f' =>
+ if ((font = arg->arg()) == nil)
+ usage();
+ * =>
+ usage();
+ }
+ }
+ argv = arg->argv();
+ scheddir := SCHEDDIR;
+ if (argv != nil)
+ scheddir = hd argv;
+ (top, wmctl) := tkclient->toplevel(ctxt, "", X("Calendar"), Tkclient->Appl);
+ if (top == nil) {
+ sys->fprint(stderr, "cal: cannot make window: %r\n");
+ raise "fail:cannot make window";
+ }
+ (sched, err) := Schedule.new(scheddir);
+ if (sched == nil)
+ sys->fprint(stderr, "cal: cannot load schedule: %s\n", err);
+ currtime := daytime->local(daytime->now());
+ if (currtime == nil) {
+ sys->fprint(stderr, "cannot get local time: %r\n");
+ raise "fail:failed to get local time";
+ }
+ date := tm2date(currtime);
+ sys->pctl(Sys->NEWPGRP|Sys->FORKNS, nil);
+
+ cmdch := chan of string;
+ tk->namechan(top, cmdch, "cmd");
+ wincmds := array[] of {
+ "frame .ctf",
+ "button .ctf.set -text {"+X("Set")+"} -command {send cmd settime}",
+ "button .ctf.show -text {"+X("Show")+"} -command {send cmd showtime}",
+
+ "frame .cf -bd 2 -relief raised",
+ "frame .cf.head",
+ "button .cf.head.bwd -text {<<} -command {send cmd bwdmonth}",
+ "button .cf.head.fwd -text {>>} -command {send cmd fwdmonth}",
+ "label .cf.head.date -text {XXX 0000}",
+
+ "frame .schedf -bd 2 -relief raised",
+ "frame .schedf.head",
+ "button .schedf.head.save -text {"+X("Save")+"} -command {send cmd save}",
+ "button .schedf.head.del -text {"+X("Del")+"} -command {send cmd del}",
+ "label .schedf.head.date -text {0000/00/00}",
+ "canvas .schedf.head.markset",
+ "button .schedf.head.bwd -text {<<} -command {send cmd bwdday}",
+ "button .schedf.head.fwd -text {>>} -command {send cmd fwdday}",
+ "frame .schedf.tf",
+ "scrollbar .schedf.tf.scroll -command {.schedf.tf.t yview}",
+ "text .schedf.tf.t -wrap word -yscrollcommand {.schedf.tf.scroll set} -height 7h -width 20w",
+ "bind .schedf.tf.t <Key> +{send cmd dirty}",
+ };
+ tkcmds(top, wincmds);
+ (cal, calch) := Cal.make(top, sched, ".cf.cal");
+ sync := chan of int;
+ spawn clock(top, ".ctf.date", sync);
+ clockpid := <-sync;
+ (ms, msch) := Markset.new(top, cal, ".schedf.head.markset");
+ tkcmds(top, packcmds);
+ if (sched == nil)
+ cmd(top, "pack forget .schedf");
+
+ showdate(top, cal, ms, date);
+ cmd(top, "pack propagate . 0");
+ cmd(top, "update");
+ if (date < 19700002)
+ raisesettime(ctxt, top);
+
+ setting := 0;
+ dirty := 0;
+ empty := scheduleempty(top);
+ currsched := 0;
+
+ tkclient->onscreen(top, nil);
+ tkclient->startinput(top, "kbd"::"ptr"::nil);
+
+ for (;;) {
+ enable(top, Savebut, dirty);
+ enable(top, Delbut, !empty);
+ cmd(top, "update");
+ ndate := date;
+ alt {
+ c := <-calch =>
+ (y,m,d) := date2ymd(date);
+ d = int c;
+ ndate = ymd2date(y,m,d);
+ c := <-msch =>
+ ms.ctl(c);
+ cal.mark(Entry(date, ms.get()));
+ dirty = 1;
+ c := <-cmdch =>
+ case c {
+ "dirty" =>
+ dirty = 1;
+ nowempty := scheduleempty(top);
+ if (nowempty != empty) {
+ if (nowempty) {
+ ms.set(0);
+ cal.mark(Entry(date, 0));
+ } else {
+ ms.set(1);
+ cal.mark(Entry(date, ms.get()));
+ }
+ empty = nowempty;
+ }
+ "bwdmonth" =>
+ ndate = decmonth(date);
+ "fwdmonth" =>
+ ndate = incmonth(date);
+ "bwdday" =>
+ ndate = adddays(date, -1);
+ "fwdday" =>
+ ndate = adddays(date, 1);
+ "del" =>
+ if (!empty) {
+ cmd(top, ".schedf.tf.t delete 1.0 end");
+ empty = 1;
+ dirty = 1;
+ cal.mark(Entry(date, 0));
+ }
+ "save" =>
+ if (dirty && save(ctxt, top, cal, ms, date) != -1)
+ dirty = 0;
+ "settime" =>
+ raisesettime(ctxt, top);
+ "showtime" =>
+ ndate = tm2date(daytime->local(daytime->now()));
+ * =>
+ sys->fprint(stderr, "cal: unknown command '%s'\n", c);
+ }
+ s := <-top.ctxt.kbd =>
+ tk->keyboard(top, s);
+ s := <-top.ctxt.ptr =>
+ tk->pointer(top, *s);
+ c := <-top.ctxt.ctl or
+ c = <-top.wreq or
+ c = <-wmctl =>
+ if (c == "exit" && dirty)
+ save(ctxt, top, cal, ms, date);
+ tkclient->wmctl(top, c);
+ }
+ if (ndate != date) {
+ e := 0;
+ if (dirty)
+ e = save(ctxt, top, cal, ms, date);
+ if (e != -1) {
+ dirty = 0;
+ showdate(top, cal, ms, ndate);
+ empty = scheduleempty(top);
+ date = ndate;
+ cmd(top, "update");
+ }
+ }
+ }
+}
+
+Markset.new(top: ref Tk->Toplevel, cal: ref Cal, w: string): (ref Markset, chan of string)
+{
+ cmd(top, w+" configure -width "+string (cal.dx * 2 + 6) +
+ " -height "+string (cal.dy + 4));
+ ch := chan of string;
+ tk->namechan(top, ch, "markcmd");
+ return (ref Markset(top, cal, w, 0), ch);
+}
+
+Markset.set(m: self ref Markset, kind: int)
+{
+ cmd(m.top, m.w + " delete x");
+ if (kind > 0) {
+ (shape, col) := kind2shapecol(kind);
+ id := cmd(m.top, m.w + " create " +
+ shapestr(m.cal, (m.cal.dx/2+2, m.cal.dy/2+2), Square) +
+ " -fill " + colours[col] + " -tags x");
+ cmd(m.top, m.w + " bind " + id + " <ButtonRelease-1> {send markcmd col}");
+ id = cmd(m.top, m.w + " create " +
+ shapestr(m.cal, (m.cal.dx * 3 / 2+4, m.cal.dy/2+2), shape) +
+ " -tags x -width 2");
+ cmd(m.top, m.w + " bind " + id + " <ButtonRelease-1> {send markcmd shape}");
+ }
+ m.curr = kind;
+}
+
+Markset.get(m: self ref Markset): int
+{
+ return m.curr;
+}
+
+Markset.ctl(m: self ref Markset, c: string)
+{
+ (shape, col) := kind2shapecol(m.curr);
+ case c {
+ "col" => col = (col + 1) % len colours;
+ "shape" => shape = (shape + 1) % Numshapes;
+ }
+ m.set(shapecol2kind((shape, col)));
+}
+
+scheduleempty(top: ref Tk->Toplevel): int
+{
+ return int cmd(top, ".schedf.tf.t compare 1.0 == end");
+}
+
+enable(top: ref Tk->Toplevel, but: string, enable: int)
+{
+ cmd(top, but + " configure -state " +
+ (array[] of {"disabled", "normal"})[!!enable]);
+}
+
+save(ctxt: ref Draw->Context, top: ref Tk->Toplevel, cal: ref Cal, ms: ref Markset, date: int): int
+{
+ s := cmd(top, ".schedf.tf.t get 1.0 end");
+ empty := scheduleempty(top);
+ mark := ms.get();
+ if (empty)
+ mark = 0;
+ ent := Entry(date, mark);
+ cal.mark(ent);
+ (ok, err) := cal.sched.setentry(ent, s);
+ if (ok == -1) {
+ notice(ctxt, top, "Cannot save entry: " + err);
+ return -1;
+ }
+ return 0;
+}
+
+notice(ctxt: ref Draw->Context, top: ref Tk->Toplevel, s: string)
+{
+ dialog->prompt(ctxt, top.image, nil, "Notice", s, 0, "OK"::nil);
+}
+
+showdate(top: ref Tk->Toplevel, cal: ref Cal, ms: ref Markset, date: int)
+{
+ (y,m,d) := date2ymd(date);
+ cal.show(date);
+ cmd(top, ".cf.head.date configure -text {" + sys->sprint("%.4d/%.2d", y, m+1) + "}");
+ cmd(top, ".schedf.head.date configure -text {" + sys->sprint("%.4d/%.2d/%.2d", y, m+1, d) + "}");
+ (ent, s) := cal.sched.readentry(date);
+ ms.set(ent.mark);
+ cmd(top, ".schedf.tf.t delete 1.0 end; .schedf.tf.t insert 1.0 '" + s);
+}
+
+nomod(s: string)
+{
+ sys->fprint(stderr, "cal: cannot load %s: %r\n", s);
+ raise "fail:bad module";
+}
+
+loadmods()
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ daytime = load Daytime Daytime->PATH;
+ if (daytime == nil)
+ nomod(Daytime->PATH);
+ tkclient = load Tkclient Tkclient->PATH;
+ if (tkclient == nil)
+ nomod(Tkclient->PATH);
+ translate = load Translate Translate->PATH;
+ if(translate != nil){
+ translate->init();
+ (dict, nil) = translate->opendict(translate->mkdictname("", "calendar"));
+ }
+ tkclient->init();
+ arg = load Arg Arg->PATH;
+ if (arg == nil)
+ nomod(Arg->PATH);
+ dialog = load Dialog Dialog->PATH;
+ if(dialog == nil)
+ nomod(Dialog->PATH);
+ dialog->init();
+}
+
+s2a(s: string, min, max: int, sep: string): array of int
+{
+ (ntoks, toks) := sys->tokenize(s, sep);
+ if (ntoks < min || ntoks > max)
+ return nil;
+ a := array[max] of int;
+ for (i := 0; toks != nil; toks = tl toks) {
+ if (!isnum(hd toks))
+ return nil;
+ a[i++] = int hd toks;
+ }
+ return a[0:i];
+}
+
+validtm(t: ref Daytime->Tm): int
+{
+ if (t.hour < 0 || t.hour > 23
+ || t.min < 0 || t.min > 59
+ || t.sec < 0 || t.sec > 59
+ || t.mday < 1 || t.mday > 31
+ || t.mon < 0 || t.mon > 11
+ || t.year < 70 || t.year > 137)
+ return 0;
+ if (t.mon == 1 && dysize(t.year+1900) > 365)
+ return t.mday <= 29;
+ return t.mday <= dmsize[t.mon];
+}
+
+clock(top: ref Tk->Toplevel, w: string, sync: chan of int)
+{
+ cmd(top, "label " + w);
+ fd := sys->open("/dev/time", Sys->OREAD);
+ if (fd == nil) {
+ sync <-= -1;
+ return;
+ }
+ buf := array[128] of byte;
+ for (;;) {
+ sys->seek(fd, big 0, Sys->SEEKSTART);
+ n := sys->read(fd, buf, len buf);
+ if (n < 0) {
+ sys->fprint(stderr, "cal: could not read time: %r\n");
+ if (sync != nil)
+ sync <-= -1;
+ break;
+ }
+ ms := big string buf[0:n] / big 1000;
+ ct := ms / big 1000;
+ t := daytime->local(int ct);
+
+ s := sys->sprint("%s %s %d %.2d:%.2d.%.2d",
+ days[t.wday], months[t.mon], t.mday, t.hour, t.min, t.sec);
+ cmd(top, w + " configure -text {" + s + "}");
+ cmd(top, "update");
+ if (sync != nil) {
+ sync <-= sys->pctl(0, nil);
+ sync = nil;
+ }
+ sys->sleep(int ((ct + big 1) * big 1000 - ms));
+ }
+}
+
+# "the world is the lord's and all it contains,
+# save the highlands and islands, which belong to macbraynes"
+Cal.make(top: ref Tk->Toplevel, sched: ref Schedule, w: string): (ref Cal, chan of string)
+{
+ f := Font.open(top.display, font);
+ if (f == nil) {
+ sys->fprint(stderr, "cal: could not open font %s: %r\n", font);
+ font = cmd(top, ". cget -font");
+ f = Font.open(top.display, font);
+ }
+ if (f == nil)
+ return (nil, nil);
+ maxw := 0;
+ for (i := 0; i < 7; i++) {
+ if ((dw := f.width(days[i] + " ")) > maxw)
+ maxw = dw;
+ }
+ for (i = 10; i < 32; i++) {
+ if ((dw := f.width(string i + " ")) > maxw)
+ maxw = dw;
+ }
+ cal := ref Cal;
+ cal.w = w;
+ cal.dx = maxw;
+ cal.dy = f.height;
+ cal.onepos = 0;
+ cal.top = top;
+ cal.sched = sched;
+ cal.marked = array[31] of {* => 0};
+ cmd(top, "canvas " + w + " -width " + string (cal.dx * 7) + " -height " + string (cal.dy * 7));
+ for (i = 0; i < 7; i++)
+ cmd(top, w + " create text " + posstr(daypos(cal, i, 0))
+ + " -text " + days[i] + " -font " + font);
+ ch := chan of string;
+ tk->namechan(top, ch, "ch" + w);
+ return (cal, ch);
+}
+
+Cal.show(cal: self ref Cal, date: int)
+{
+ if (date == cal.date)
+ return;
+ mon := (date / 100) % 100;
+ year := date / 10000;
+ cmd(cal.top, cal.w + " delete curr");
+ if (cal.date / 100 != date / 100) {
+ cmd(cal.top, cal.w + " delete date");
+ cmd(cal.top, cal.w + " delete mark");
+ for (i := 0; i < len cal.marked; i++)
+ cal.marked[i] = 0;
+ (md, wd) := monthinfo(mon, year);
+ base := year * 10000 + mon * 100;
+ cal.onepos = wd;
+ for (i = 0; i < 6; i++) {
+ for (j := 0; j < 7; j++) {
+ d := i * 7 + j - wd;
+ if (d >= 0 && d < md) {
+ id := cmd(cal.top, cal.w + " create text " + posstr(daypos(cal, j, i+1))
+ + " -tags date -text " + string (d+1)
+ + " -font " + font);
+ cmd(cal.top, cal.w + " bind " + id +
+ " <ButtonRelease-1> {send ch" + cal.w + " " + string (d+1) + "}");
+ (ok, ent) := cal.sched.getentry(base + d + 1);
+ if (ok != -1)
+ cal.mark(ent);
+ }
+ }
+ }
+ }
+ if (cal.sched != nil) {
+ e := date % 100 - 1 + cal.onepos;
+ p := daypos(cal, e % 7, e / 7 + 1);
+ cmd(cal.top, cal.w + " create " + shapestr(cal, p, Square) +
+ " -tags curr -width 3");
+ }
+ cal.date = date;
+}
+
+Cal.mark(cal: self ref Cal, ent: Entry)
+{
+ if (ent.date / 100 != ent.date / 100)
+ return;
+ (nil, nil, d) := date2ymd(ent.date);
+ d--;
+ cmd(cal.top, cal.w + " delete m" + string d);
+ if (ent.mark) {
+ e := d + cal.onepos;
+ p := daypos(cal, e % 7, e / 7 + 1);
+ id := cmd(cal.top, cal.w + " create " + itemshape(cal, p, ent.mark) +
+ " -tags {mark m"+string d + "}");
+ cmd(cal.top, cal.w + " bind " + id +
+ " <ButtonRelease-1> {send ch" + cal.w + " " + string (d+1) + "}");
+ cmd(cal.top, cal.w + " lower " + id);
+ }
+ cal.marked[d] = ent.mark;
+}
+
+Oval, Diamond, Square, Numshapes: con iota;
+
+colours := array[] of {
+ "red",
+ "yellow",
+ "#00eeee",
+ "white"
+};
+
+kind2shapecol(kind: int): (int, int)
+{
+ kind = (kind - 1) & 16rffff;
+ return ((kind & 16rff) % Numshapes, (kind >> 8) % len colours);
+}
+
+shapecol2kind(shapecol: (int, int)): int
+{
+ (shape, colour) := shapecol;
+ return (shape + (colour << 8)) + 1;
+}
+
+itemshape(cal: ref Cal, centre: Point, kind: int): string
+{
+ (shape, colour) := kind2shapecol(kind);
+ return shapestr(cal, centre, shape) + " -fill " + colours[colour];
+}
+
+shapestr(cal: ref Cal, p: Point, kind: int): string
+{
+ (hdx, hdy) := (cal.dx / 2, cal.dy / 2);
+ case kind {
+ Oval =>
+ r := Rect((p.x - hdx, p.y - hdy), (p.x + hdx, p.y + hdy));
+ return "oval " + rectstr(r);
+ Diamond =>
+ return "polygon " + string (p.x - hdx) + " " + string p.y + " " +
+ string p.x + " " + string (p.y - hdy) + " " +
+ string (p.x + hdx) + " " + string p.y + " " +
+ string p.x + " " + string (p.y + hdy) +
+ " -outline black";
+ Square =>
+ r := Rect((p.x - hdx, p.y - hdy), (p.x + hdx, p.y + hdy));
+ return "rectangle " + rectstr(r);
+ * =>
+ sys->fprint(stderr, "cal: unknown shape %d\n", kind);
+ return nil;
+ }
+}
+
+rectstr(r: Rect): string
+{
+ return string r.min.x + " " + string r.min.y + " " +
+ string r.max.x + " " + string r.max.y;
+}
+
+posstr(p: Point): string
+{
+ return string p.x + " " + string p.y;
+}
+
+# return centre point of position for day.
+daypos(cal: ref Cal, d, w: int): Point
+{
+ return Point(d * cal.dx + cal.dx / 2, w * cal.dy + cal.dy / 2);
+}
+
+body2entry(body: string): (int, Entry, string)
+{
+ for (i := 0; i < len body; i++)
+ if (body[i] == '\n')
+ break;
+ if (i == len body)
+ return (-1, (-1, -1), "invalid schedule header (no newline)");
+ (n, toks) := sys->tokenize(body[0:i], " \t\n");
+ if (n < 2)
+ return (-1, (-1, -1), "invalid schedule header (too few fields)");
+ date := int hd toks;
+ (y, m, d) := (date / 10000, (date / 100) % 100, date%100);
+ if (y < 1970 || y > 2037 || m > 12 || m < 1 || d > 31 || d < 1)
+ return (-1, (-1,-1), sys->sprint("invalid date (%.8d) in schedule header", date));
+ e := Entry(ymd2date(y, m-1, d), int hd tl toks);
+ return (0, e, body[i+1:]);
+}
+
+startdbfs(f: string): (string, string)
+{
+ dbfs := load Command DBFSPATH;
+ if (dbfs == nil)
+ return (nil, sys->sprint("cannot load %s: %r", DBFSPATH));
+ sync := chan of string;
+ spawn rundbfs(sync, dbfs, f, SCHEDDIR);
+ e := <-sync;
+ if (e != nil)
+ return (nil, e);
+ return (SCHEDDIR, nil);
+}
+
+rundbfs(sync: chan of string, dbfs: Command, f, d: string)
+{
+ sys->pctl(Sys->FORKFD, nil);
+ {
+ dbfs->init(nil, "dbfs" :: "-r" :: f :: d :: nil);
+ sync <-= nil;
+ }exception e{
+ "fail:*" =>
+ sync <-= "dbfs failed: " + e[5:];
+ exit;
+ }
+}
+
+Schedule.new(d: string): (ref Schedule, string)
+{
+ (rc, info) := sys->stat(d);
+ if (rc == -1)
+ return (nil, sys->sprint("cannot find %s: %r", d));
+ if ((info.mode & Sys->DMDIR) == 0) {
+ err: string;
+ (d, err) = startdbfs(d);
+ if (d == nil)
+ return (nil, err);
+ }
+ (rc, nil) = sys->stat(d + "/new");
+ if (rc == -1)
+ return (nil, "no dbfs mounted on " + d);
+
+ readdir := load Readdir Readdir->PATH;
+ if (readdir == nil)
+ return (nil, sys->sprint("cannot load %s: %r", Readdir->PATH));
+ sched := ref Schedule;
+ sched.dir = d;
+ (de, nil) := readdir->init(d, Readdir->NONE);
+ if (de == nil)
+ return (nil, "could not read schedule directory");
+ buf := array[Sys->ATOMICIO] of byte;
+ sched.entries = array[len de] of Sentry;
+ ne := 0;
+ for (i := 0; i < len de; i++) {
+ if (!isnum(de[i].name))
+ continue;
+ f := d + "/" + de[i].name;
+ fd := sys->open(f, Sys->OREAD);
+ if (fd == nil) {
+ sys->fprint(stderr, "cal: cannot open %s: %r\n", f);
+ } else {
+ n := sys->read(fd, buf, len buf);
+ if (n == -1) {
+ sys->fprint(stderr, "cal: error reading %s: %r\n", f);
+ } else {
+ (ok, e, err) := body2entry(string buf[0:n]);
+ if (ok == -1)
+ sys->fprint(stderr, "cal: error on entry %s: %s\n", f, err);
+ else
+ sched.entries[ne++] = (e, int de[i].name);
+ err = nil;
+ }
+ }
+ }
+ sched.entries = sched.entries[0:ne];
+ sortentries(sched.entries);
+ return (sched, nil);
+}
+
+Schedule.getentry(sched: self ref Schedule, date: int): (int, Entry)
+{
+ if (sched == nil)
+ return (-1, (-1, -1));
+ ent := search(sched, date);
+ if (ent == -1)
+ return (-1, (-1,-1));
+ return (0, sched.entries[ent].ent);
+}
+
+Schedule.readentry(sched: self ref Schedule, date: int): (Entry, string)
+{
+ if (sched == nil)
+ return ((-1, -1), nil);
+ ent := search(sched, date);
+ if (ent == -1)
+ return ((-1, -1), nil);
+ (nil, fno) := sched.entries[ent];
+
+ f := sched.dir + "/" + string fno;
+ fd := sys->open(f, Sys->OREAD);
+ if (fd == nil) {
+ sys->fprint(stderr, "cal: cannot open %s: %r", f);
+ return ((-1, -1), nil);
+ }
+ buf := array[Sys->ATOMICIO] of byte;
+ n := sys->read(fd, buf, len buf);
+ if (n == -1) {
+ sys->fprint(stderr, "cal: cannot read %s: %r", f);
+ return ((-1, -1), nil);
+ }
+ (ok, e, body) := body2entry(string buf[0:n]);
+ if (ok == -1) {
+ sys->fprint(stderr, "cal: couldn't get body in file %s: %s\n", f, body);
+ return ((-1, -1), nil);
+ }
+ return (e, body);
+}
+
+writeentry(fd: ref Sys->FD, ent: Entry, data: string): (int, string)
+{
+ ent.date += 100;
+ b := array of byte (sys->sprint("%d %d\n", ent.date, ent.mark) + data);
+ if (len b > Sys->ATOMICIO)
+ return (-1, "entry is too long");
+ if (sys->write(fd, b, len b) != len b)
+ return (-1, sys->sprint("cannot write entry: %r"));
+ return (0, nil);
+}
+
+Schedule.setentry(sched: self ref Schedule, ent: Entry, data: string): (int, string)
+{
+ if (sched == nil)
+ return (-1, "no schedule");
+ idx := search(sched, ent.date);
+ if (idx == -1) {
+ if (data == nil)
+ return (0, nil);
+ fd := sys->open(sched.dir + "/new", Sys->OWRITE);
+ if (fd == nil)
+ return (-1, sys->sprint("cannot open new: %r"));
+ (ok, info) := sys->fstat(fd);
+ if (ok == -1)
+ return (-1, sys->sprint("cannot stat new: %r"));
+ if (!isnum(info.name))
+ return (-1, "new dbfs entry is not numeric");
+ err: string;
+ (ok, err) = writeentry(fd, ent, data);
+ if (ok == -1)
+ return (ok, err);
+ (fd, data) = (nil, nil);
+ e := sched.entries;
+ for (i := 0; i < len e; i++)
+ if (ent.date < e[i].ent.date)
+ break;
+ ne := array[len e + 1] of Sentry;
+ (ne[0:], ne[i], ne[i+1:]) = (e[0:i], (ent, int info.name), e[i:]);
+ sched.entries = ne;
+ return (0, nil);
+ } else {
+ fno := sched.entries[idx].file;
+ f := sched.dir + "/" + string fno;
+ if (data == nil) {
+ sys->remove(f);
+ sched.entries[idx:] = sched.entries[idx+1:];
+ sched.entries = sched.entries[0:len sched.entries - 1];
+ return (0, nil);
+ } else {
+ sched.entries[idx] = (ent, fno);
+ fd := sys->open(f, Sys->OWRITE);
+ if (fd == nil)
+ return (-1, sys->sprint("cannot open %s: %r", sched.dir + "/" + string fno));
+ return writeentry(fd, ent, data);
+ }
+ }
+}
+
+search(sched: ref Schedule, date: int): int
+{
+ e := sched.entries;
+ lo := 0;
+ hi := len e - 1;
+ while (lo <= hi) {
+ mid := (lo + hi) / 2;
+ if (date < e[mid].ent.date)
+ hi = mid - 1;
+ else if (date > e[mid].ent.date)
+ lo = mid + 1;
+ else
+ return mid;
+ }
+ return -1;
+}
+
+sortentries(a: array of Sentry)
+{
+ m: int;
+ n := len a;
+ for(m = n; m > 1; ) {
+ if(m < 5)
+ m = 1;
+ else
+ m = (5*m-1)/11;
+ for(i := n-m-1; i >= 0; i--) {
+ tmp := a[i];
+ for(j := i+m; j <= n-1 && tmp.ent.date > a[j].ent.date; j += m)
+ a[j-m] = a[j];
+ a[j-m] = tmp;
+ }
+ }
+}
+
+raisesettime(ctxt: ref Draw->Context, top: ref Tk->Toplevel)
+{
+ panelcmds := array[] of {
+ "frame .d",
+ "label .d.title -text {"+X("Date (YYYY/MM/DD):")+"}",
+ "entry .d.de -width 11w}",
+ "frame .t",
+ "label .t.title -text {"+X("Time (HH:MM.SS):")+"}",
+ "entry .t.te -width 11w}",
+ "frame .b",
+ "button .b.set -text Set -command {send cmd set}",
+ "button .b.cancel -text Cancel -command {send cmd cancel}",
+ "pack .d .t .b -side top -fill x",
+ "pack .d.de .d.title -side right",
+ "pack .t.te .t.title -side right",
+ "pack .b.set .b.cancel -side right",
+ };
+ fd := sys->open("/dev/time", Sys->OWRITE);
+ if (fd == nil) {
+ notice(ctxt, top, X("Cannot set time: ") + sys->sprint("%r"));
+ return;
+ }
+ (panel, wmctl) := tkclient->toplevel(ctxt, "", X("Set Time"), 0);
+ tkcmds(panel, panelcmds);
+ cmdch := chan of string;
+ tk->namechan(panel, cmdch, "cmd");
+ t := daytime->local(daytime->now());
+ if (t.year < 71)
+ (t.year, t.mon, t.mday) = (100, 0, 1);
+ cmd(panel, ".d.de insert 0 " + sys->sprint("%.4d/%.2d/%.2d",
+ t.year+1900, t.mon+1, t.mday));
+ cmd(panel, ".t.te insert 0 " + sys->sprint("%.2d:%.2d.%.2d", t.hour, t.min, t.sec));
+ #cmd(panel, "grab set ."); XXX should, but not a good idea with global tk.
+ # wouldn't work with current dialog->prompt() either...
+ cmd(panel, "update");
+ tkclient->onscreen(panel, nil);
+ tkclient->startinput(panel, "kbd"::"ptr"::nil);
+
+loop: for (;;) alt {
+ s := <-panel.ctxt.kbd =>
+ tk->keyboard(panel, s);
+ s := <-panel.ctxt.ptr =>
+ tk->pointer(panel, *s);
+ c := <-cmdch =>
+ case c {
+ "set" =>
+ err := settime(fd, cmd(panel, ".d.de get"), cmd(panel, ".t.te get"));
+ if (err == nil)
+ break loop;
+ notice(ctxt, panel, X("Cannot set time: ") + err);
+ "cancel" =>
+ break loop;
+ * =>;
+ }
+ c := <-wmctl =>
+ case c {
+ "exit" =>
+ break loop;
+ * =>
+ tkclient->wmctl(panel, c);
+ }
+ }
+}
+
+settime(tfd: ref Sys->FD, date, time: string): string
+{
+ da := s2a(date, 3, 3, "/");
+ if (da == nil)
+ return X("Invalid date syntax");
+ ta := s2a(time, 2, 3, ":.");
+ if (ta == nil)
+ return X("Invalid time syntax");
+ t := ref blanktm;
+ if (da[2] > 1000)
+ (da[0], da[1], da[2]) = (da[2], da[1], da[0]);
+ (t.year, t.mon, t.mday) = (da[0]-1900, da[1]-1, da[2]);
+ if (len ta == 3)
+ (t.hour, t.min, t.sec) = (ta[0], ta[1], ta[2]);
+ else
+ (t.hour, t.min, t.sec) = (ta[0], ta[1], 0);
+ if (!validtm(t))
+ return X("Invalid time or date given");
+ s := string daytime->tm2epoch(t) + "000000";
+ if (sys->fprint(tfd, "%s", s) == -1)
+ return X("write failed:") + sys->sprint(" %r");
+ return nil;
+}
+
+
+cmd(top: ref Tk->Toplevel, cmd: string): string
+{
+ e := tk->cmd(top, cmd);
+ if (e != nil && e[0] == '!')
+ sys->fprint(stderr, "cal: tk error on '%s': %s\n", cmd, e);
+ return e;
+}
+
+tkcmds(top: ref Tk->Toplevel, a: array of string)
+{
+ for (i := 0; i < len a; i++)
+ cmd(top, a[i]);
+}
+
+isnum(s: string): int
+{
+ for (i := 0; i < len s; i++)
+ if (s[i] < '0' || s[i] > '9')
+ return 0;
+ return 1;
+}
+
+tm2date(t: ref Tm): int
+{
+ if (t == nil)
+ return 19700001;
+ return ymd2date(t.year+1900, t.mon, t.mday);
+}
+
+date2ymd(date: int): (int, int, int)
+{
+ return (date / 10000, (date / 100) % 100, date%100);
+}
+
+ymd2date(y, m, d: int): int
+{
+ return d + m* 100 + y * 10000;
+}
+
+adddays(date, delta: int): int
+{
+ t := ref blanktm;
+ t.mday = date % 100;
+ t.mon = (date / 100) % 100;
+ t.year = (date / 10000) - 1900;
+ t.hour = 12;
+ e := daytime->tm2epoch(t);
+ e += delta * 24 * 60 * 60;
+ t = daytime->gmt(e);
+ if (!validtm(t))
+ return date;
+ return tm2date(t);
+}
+
+incmonth(date: int): int
+{
+ (y,m,d) := date2ymd(date);
+ if (m < 11)
+ m++;
+ else if (y < 2037)
+ (y, m) = (y+1, 0);
+ (n, nil) := monthinfo(m, y);
+ if (d > n)
+ d = n;
+ return ymd2date(y,m,d);
+}
+
+decmonth(date: int): int
+{
+ (y,m,d) := date2ymd(date);
+ if (m > 0)
+ m--;
+ else if (y > 1970)
+ (y, m) = (y-1, 11);
+ (n, nil) := monthinfo(m, y);
+ if (d > n)
+ d = n;
+ return ymd2date(y,m,d);
+}
+
+dmsize := array[] of {
+ 31, 28, 31, 30, 31, 30,
+ 31, 31, 30, 31, 30, 31
+};
+
+dysize(y: int): int
+{
+ if( (y%4) == 0 && (y % 100 != 0 || y % 400 == 0) )
+ return 366;
+ return 365;
+}
+
+blanktm: Tm;
+
+# return number of days in month and
+# starting day of month/year.
+monthinfo(mon, year: int): (int, int)
+{
+ t := ref blanktm;
+ t.mday = 1;
+ t.mon = mon;
+ t.year = year - 1900;
+ t = daytime->gmt(daytime->tm2epoch(t));
+ md := dmsize[mon];
+ if (dysize(year) == 366 && t.mon == 1)
+ md++;
+ return (md, t.wday);
+}
+
+X(s: string): string
+{
+ #sys->print("\"%s\"\n", s);
+ if (dict == nil)
+ return s;
+ return dict.xlate(s);
+}
+
+Xa(a: array of string): array of string
+{
+ for (i := 0; i < len a; i++)
+ a[i] = X(a[i]);
+ return a;
+}
+
diff --git a/appl/wm/clock.b b/appl/wm/clock.b
new file mode 100644
index 00000000..a8022f09
--- /dev/null
+++ b/appl/wm/clock.b
@@ -0,0 +1,123 @@
+implement Clock;
+
+#
+# Subject to the Lucent Public License 1.02
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Display, Image, Point, Rect: import draw;
+
+include "math.m";
+ math: Math;
+
+include "tk.m";
+include "wmclient.m";
+ wmclient: Wmclient;
+ Window: import wmclient;
+
+include "daytime.m";
+ daytime: Daytime;
+ Tm: import daytime;
+
+Clock: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+hrhand: ref Image;
+minhand: ref Image;
+dots: ref Image;
+back: ref Image;
+
+init(ctxt: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ math = load Math Math->PATH;
+ daytime = load Daytime Daytime->PATH;
+ wmclient = load Wmclient Wmclient->PATH;
+
+ sys->pctl(Sys->NEWPGRP, nil);
+ wmclient->init();
+
+ w := wmclient->window(ctxt, "clock", Wmclient->Appl); # Plain?
+ display := w.display;
+ back = display.colormix(Draw->Palebluegreen, Draw->White);
+
+ hrhand = display.newimage(Rect((0,0),(1,1)), Draw->CMAP8, 1, Draw->Darkblue);
+ minhand = display.newimage(Rect((0,0),(1,1)), Draw->CMAP8, 1, Draw->Paleblue);
+ dots = display.newimage(Rect((0,0),(1,1)), Draw->CMAP8, 1, Draw->Blue);
+
+ w.reshape(Rect((0, 0), (100, 100)));
+ w.startinput("ptr" :: nil);
+
+ now := daytime->now();
+ w.onscreen(nil);
+ drawclock(w.image, now);
+
+ ticks := chan of int;
+ spawn timer(ticks, 30*1000);
+ for(;;) alt{
+ ctl := <-w.ctl or
+ ctl = <-w.ctxt.ctl =>
+ w.wmctl(ctl);
+ if(ctl != nil && ctl[0] == '!')
+ drawclock(w.image, now);
+ p := <-w.ctxt.ptr =>
+ w.pointer(*p);
+ <-ticks =>
+ t := daytime->now();
+ if(t != now){
+ now = t;
+ drawclock(w.image, now);
+ }
+ }
+}
+
+ZP := Point(0, 0);
+
+drawclock(screen: ref Image, t: int)
+{
+ if(screen == nil)
+ return;
+ tms := daytime->local(t);
+ anghr := 90-(tms.hour*5 + tms.min/10)*6;
+ angmin := 90-tms.min*6;
+ r := screen.r;
+ c := r.min.add(r.max).div(2);
+ if(r.dx() < r.dy())
+ rad := r.dx();
+ else
+ rad = r.dy();
+ rad /= 2;
+ rad -= 8;
+
+ screen.draw(screen.r, back, nil, ZP);
+ for(i:=0; i<12; i++)
+ screen.fillellipse(circlept(c, rad, i*(360/12)), 2, 2, dots, ZP);
+
+ screen.line(c, circlept(c, (rad*3)/4, angmin), 0, 0, 1, minhand, ZP);
+ screen.line(c, circlept(c, rad/2, anghr), 0, 0, 1, hrhand, ZP);
+
+ screen.flush(Draw->Flushnow);
+}
+
+circlept(c: Point, r: int, degrees: int): Point
+{
+ rad := real degrees * Math->Pi/180.0;
+ c.x += int (math->cos(rad)*real r);
+ c.y -= int (math->sin(rad)*real r);
+ return c;
+}
+
+timer(c: chan of int, ms: int)
+{
+ for(;;){
+ sys->sleep(ms);
+ c <-= 1;
+ }
+}
diff --git a/appl/wm/coffee.b b/appl/wm/coffee.b
new file mode 100644
index 00000000..09369bc9
--- /dev/null
+++ b/appl/wm/coffee.b
@@ -0,0 +1,227 @@
+implement Coffee;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Context, Display, Point, Rect, Image, Screen: import draw;
+
+include "tk.m";
+ tk: Tk;
+ Toplevel: import tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+Coffee: module
+{
+ init: fn(ctxt: ref Context, argv: list of string);
+};
+
+display: ref Display;
+t: ref Toplevel;
+
+NC: con 6;
+
+task_cfg := array[] of {
+ "frame .f",
+ "frame .b",
+ "button .b.Stop -text Stop -command {send cmd stop}",
+ "scale .b.Rate -from 1 -to 10 -orient horizontal"+
+ " -showvalue 0 -command {send cmd rate}",
+ "scale .b.Jitter -from 0 -to 5 -orient horizontal"+
+ " -showvalue 0 -command {send cmd jitter}",
+ "scale .b.Skip -from 0 -to 25 -orient horizontal"+
+ " -showvalue 0 -command {send cmd skip}",
+ ".b.Rate set 3",
+ ".b.Jitter set 2",
+ ".b.Skip set 5",
+ "pack .b.Stop .b.Rate .b.Jitter .b.Skip -side left",
+ "pack .b -anchor w",
+ "pack .f -side bottom -fill both -expand 1",
+};
+
+init(ctxt: ref Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+
+ sys->pctl(Sys->NEWPGRP, nil);
+
+ tkclient->init();
+ if(ctxt == nil)
+ ctxt = tkclient->makedrawcontext();
+ display = ctxt.display;
+
+ menubut: chan of string;
+ (t, menubut) = tkclient->toplevel(ctxt, "", "Infernal Coffee", 0);
+
+ cmdch := chan of string;
+ tk->namechan(t, cmdch, "cmd");
+
+ for (i := 0; i < len task_cfg; i++)
+ cmd(t, task_cfg[i]);
+
+ tk->cmd(t, "update");
+ tkclient->startinput(t, "ptr"::"kbd"::nil);
+ tkclient->onscreen(t, nil);
+
+ ctl := chan of (string, int, int);
+ spawn animate(ctl);
+
+ for(;;) alt {
+ s := <-t.ctxt.kbd =>
+ tk->keyboard(t, s);
+ s := <-t.ctxt.ptr =>
+ tk->pointer(t, *s);
+ s := <-t.ctxt.ctl or
+ s = <-t.wreq or
+ s = <-menubut =>
+ tkclient->wmctl(t, s);
+ press := <-cmdch =>
+ (nil, word) := sys->tokenize(press, " ");
+ case hd word {
+ "stop" or "go" =>
+ ctl <-= (hd word, 0, 0);
+ "rate" or "jitter" or "skip" =>
+ ctl <-= (hd word, int hd tl word, 0);
+ }
+ }
+
+}
+
+animate(ctl: chan of (string, int, int))
+{
+ stopped := 0;
+
+ fill := display.open("/icons/bigdelight.bit");
+ if (fill == nil) {
+ sys->fprint(sys->fildes(2), "coffee: failed to allocate image\n");
+ exit;
+ }
+
+ c := array[NC] of ref Image;
+ m := array[NC] of ref Image;
+
+ for(i:=0; i<NC; i++){
+ c[i] = display.open("/icons/coffee"+string i+".bit");
+ m[i] = display.open("/icons/coffee"+string i+".mask");
+ if (c[i] == nil || m[i] == nil) {
+ sys->fprint(sys->fildes(2), "coffee: failed to allocate image\n");
+ exit;
+ }
+ }
+
+ r := Rect((0, 0), (400, 300));
+ buffer := display.newimage(r, t.image.chans, 0, Draw->Black);
+ if (buffer == nil) {
+ sys->fprint(sys->fildes(2), "coffee: failed to allocate image\n");
+ exit;
+ }
+ cmd(t, "panel .f.p -bd 3 -relief flat");
+ cmd(t, "pack .f.p -fill both -expand 1");
+ cmd(t, "update");
+ # org := buffer.r.min;
+ tk->putimage(t, ".f.p", buffer, nil);
+
+ rate := 3;
+ jitter := 2;
+ skip := 5;
+
+ i = 0;
+ for(k:=0; ; k++){
+ sys->sleep(1);
+ if(k%25 > 25-skip)
+ i -= rate;
+ else
+ i += rate;
+ buffer.draw(buffer.clipr, fill, nil, fill.r.min);
+ center := buffer.r.max.div(2);
+ for(j:=0; j<NC; j++){
+ (sin, cos) := sincos(i+j*(360/NC));
+ x := (sin*150)/1000 + jitter*(k%5);
+ y := (cos*100)/1000 + jitter*(k%5);
+ p0 := center.add((x-c[j].r.dx()/2, y-c[j].r.dy()/2));
+ buffer.draw(c[j].r.addpt(p0), c[j], m[j], (0,0));
+ if(j & 1) # be nice from time to time
+ sys->sleep(0);
+ }
+ tk->cmd(t, ".f.p dirty; update");
+ sys->sleep(5);
+ alt{
+ (cmd, i0, i1) := <-ctl =>
+ Pause:
+ for(;;){
+ case cmd{
+ "go" =>
+ if(stopped){
+ tk->cmd(t, ".b.Stop configure -text Stop -command {send cmd stop}");
+ tk->cmd(t, "update");
+ stopped = 0;
+ }
+ break Pause;
+ "stop" =>
+ if(!stopped){
+ tk->cmd(t, ".b.Stop configure -text { Go } -command {send cmd go}");
+ tk->cmd(t, "update");
+ stopped = 1;
+ }
+ "rate" =>
+ rate = i0;
+ if(stopped == 0)
+ break Pause;
+ "jitter" =>
+ jitter = i0;
+ if(stopped == 0)
+ break Pause;
+ "skip" =>
+ skip = i0;
+ if(stopped == 0)
+ break Pause;
+ }
+ (cmd, i0, i1) = <-ctl;
+ }
+ * =>
+ ;
+ }
+ }
+}
+
+sintab := array[] of {
+ 0000, 0017, 0035, 0052, 0070, 0087, 0105, 0122, 0139, 0156,
+ 0174, 0191, 0208, 0225, 0242, 0259, 0276, 0292, 0309, 0326,
+ 0342, 0358, 0375, 0391, 0407, 0423, 0438, 0454, 0469, 0485,
+ 0500, 0515, 0530, 0545, 0559, 0574, 0588, 0602, 0616, 0629,
+ 0643, 0656, 0669, 0682, 0695, 0707, 0719, 0731, 0743, 0755,
+ 0766, 0777, 0788, 0799, 0809, 0819, 0829, 0839, 0848, 0857,
+ 0866, 0875, 0883, 0891, 0899, 0906, 0914, 0921, 0927, 0934,
+ 0940, 0946, 0951, 0956, 0961, 0966, 0970, 0974, 0978, 0982,
+ 0985, 0988, 0990, 0993, 0995, 0996, 0998, 0999, 0999, 1000,
+ 1000, };
+
+sincos(a: int): (int, int)
+{
+ a %= 360;
+ if(a < 0)
+ a += 360;
+
+ if(a <= 90)
+ return (sintab[a], sintab[90-a]);
+ if(a <= 180)
+ return (sintab[180-a], -sintab[a-90]);
+ if(a <= 270)
+ return (-sintab[a-180], -sintab[270-a]);
+ return (-sintab[360-a], sintab[a-270]);
+}
+
+cmd(win: ref Tk->Toplevel, s: string): string
+{
+ r := tk->cmd(win, s);
+ if (len r > 0 && r[0] == '!') {
+ sys->print("error executing '%s': %s\n", s, r[1:]);
+ }
+ return r;
+}
diff --git a/appl/wm/collide.b b/appl/wm/collide.b
new file mode 100644
index 00000000..1d8a2527
--- /dev/null
+++ b/appl/wm/collide.b
@@ -0,0 +1,2180 @@
+#
+# initially generated by c2l
+#
+
+implement Collide;
+
+include "draw.m";
+ draw: Draw;
+ Display, Image: import draw;
+
+Collide: module
+{
+ init: fn(nil: ref Draw->Context, argl: list of string);
+};
+
+include "sys.m";
+ sys: Sys;
+include "tk.m";
+ tk: Tk;
+ Toplevel: import tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+include "math.m";
+ maths: Math;
+include "rand.m";
+ rand: Rand;
+include "daytime.m";
+ daytime: Daytime;
+include "bufio.m";
+include "arg.m";
+ arg: Arg;
+include "math/polyhedra.m";
+ polyhedra: Polyhedra;
+
+init(ctxt: ref Draw->Context, argl: list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ maths = load Math Math->PATH;
+ rand = load Rand Rand->PATH;
+ arg = load Arg Arg->PATH;
+ daytime = load Daytime Daytime->PATH;
+ main(ctxt, argl);
+}
+
+π: con Math->Pi;
+∞: con real (1<<30);
+ε: con 0.001;
+√2: con 1.4142135623730950488016887242096980785696718753769486732;
+
+M1: con 1.0;
+M2: con 1.0;
+E: con 1.0; # coefficient of restitution/elasticity
+
+COLLIDE, REFLECT: con 1<<iota;
+
+MAXX, MAXY: con 512;
+
+RDisp: ref Draw->Image;
+black, white, red: ref Draw->Image;
+display: ref Draw->Display;
+toplev: ref Toplevel;
+
+Vector: adt{
+ x: real;
+ y: real;
+ z: real;
+};
+
+Line: adt{
+ a: Vector;
+ d: Vector; # normalized
+};
+
+Plane: adt{
+ id: int;
+ n: Vector; # normalized
+ d: real;
+ min: Vector;
+ max: Vector;
+ far: Vector;
+ v: array of Vector;
+};
+
+Object: adt{
+ id: int;
+ poly: ref Polyhedra->Polyhedron; # if any
+ c: ref Draw->Image; # colour
+ cb: ref Draw->Image; # border colour
+ l: ref Line; # initial point and direction
+ p: Vector; # current position
+ rp: Vector; # position after reflection
+ cp: Vector; # any collision point
+ rt: real; # time to reflection
+ ct: real; # time to collision
+ plane: ref Plane; # reflecting off
+ pmask: int; # plane mask
+ obj: cyclic ref Object; # colliding with
+ v: real; # speed
+ ω: real; # speed of rotation
+ roll: real; # roll
+ pitch: real; # pitch
+ yaw: real; # yaw
+ todo: int; # work to do
+};
+
+planes: list of ref Plane;
+
+V0: con Vector(real 0, real 0, real 0);
+VZ: con Vector(0.0, 0.0, 1.0);
+
+far: Vector;
+
+DOCIRCLE: con 1;
+POLY, FILLPOLY, CIRCLE, FILLCIRCLE, ELLIPSE, FILLELLIPSE: con iota;
+
+#
+# final object is centred on (0, 0, -objd)
+# viewer is at origin looking along (0 0 -1)
+#
+maxx, maxy: int;
+
+SCRW: con 320; # screen width
+SCRH: con 240; # screen height
+
+frac := 0.5; # % of screen for cube
+front := 0.5; # % of cube in front of screen
+hpar := 0.0; # horizontal parallax
+fov := -1.0; # field of view : 0 for parallel projection, -1 for unspecified
+objd := 500.0; # eye to middle of cube
+cubd := 100.0; # half side of cube
+icubd: real; # half side of inner cube
+icubd2: real; # square of above
+eyed := 32.0; # half eye to eye
+trkd := 5.0; # side/diameter of object
+trkd2: real; # square of above
+rpy := 0;
+roll := 0.0; # z
+pitch := 0.0; # y
+yaw := 0.0; # x
+
+scrd, objD, scrD: real; # screen distance
+left := 0; # left or right eye
+sx, sy, sz: real; # screen scale factors
+sf: real; # perspective scale factor
+fbpar: real; # -1 for front of cube, 1 for back
+vf := 1.0; # current velocity factor
+
+cmin, cmax: Vector; # cube extents
+
+# special transformation matrix without roll, pitch, yaw
+# this is needed so that spheres can be drawn as circles
+mod0 := array[4] of array of real;
+
+stereo := 0; # stereopsis
+
+SPHERE, ELLIPSOID, CUBE, POLYHEDRON: con iota;
+surr := CUBE; # surround
+
+poly := 0; # show polyhedra
+flat: int; # objects in one plane
+projx: int; # strange projection
+
+# ellipse parameters
+ef: Vector = (1.0, 0.8, 1.0);
+e2: Vector;
+
+# objects
+nobjs: int;
+objs: array of ref Object;
+me: ref Object;
+
+# circle drawing
+NC: con 72;
+cost, sint: array of real;
+
+# polyhedra
+polys: ref Polyhedra->Polyhedron;
+npolys: int;
+polyh: ref Polyhedra->Polyhedron;
+
+rgba(r: int, g: int, b: int, α: int): ref Image
+{
+ c := draw->setalpha((r<<24)|(g<<16)|(b<<8), α);
+ return display.newimage(((0, 0), (1, 1)), display.image.chans, 1, c);
+}
+
+random(a: int, b: int): int
+{
+ return a+rand->rand(b-a+1);
+}
+
+urand(): real
+{
+ M: con 1000;
+ return real random(0, M)/real M;
+}
+
+randomr(a: real, b: real): real
+{
+ return a+urand()*(b-a);
+}
+
+randomc(): ref Image
+{
+ r, g, b: int;
+
+ do{
+ r = random(0, 255);
+ g = random(0, 255);
+ b = random(0, 255);
+ }while(r+g+b < 384);
+ return rgba(r, g, b, 255);
+}
+
+randomv(a: real, b: real): Vector
+{
+ x := randomr(a, b);
+ y := randomr(a, b);
+ if(flat)
+ return (x, y, (a+b)/2.0);
+ return (x, y, randomr(a, b));
+}
+
+randomd(): Vector
+{
+ M: con 1000.0;
+ v := randomv(-M, M);
+ while(vlen(v) == 0.0)
+ v = randomv(-M, M);
+ return vnorm(v);
+}
+
+randomp(min: real, max: real): Vector
+{
+ case(surr){
+ SPHERE =>
+ return vmul(randomd(), max*maths->sqrt(urand()));
+ ELLIPSOID =>
+ return vmul(randomd(), max*vmin(ef)*maths->sqrt(urand()));
+ CUBE =>
+ return randomv(min, max);
+ * =>
+ v := randomv(min, max);
+ while(outside3(v, cmin, cmax))
+ v = randomv(min, max);
+ return v;
+ }
+}
+
+det(a: real, b: real, c: real, d: real): real
+{
+ return a*d-b*c;
+}
+
+simeq(a: real, b: real, c: real, d: real, e: real, f: real): (real, real)
+{
+ de := det(a, b, c, d);
+ return (det(e, b, f, d)/de, det(a, e, c, f)/de);
+}
+
+cksimeq(a: real, b: real, c: real, d: real, e: real, f: real): (int, real, real)
+{
+ ade := de := det(a, b, c, d);
+ if(ade < 0.0)
+ ade = -ade;
+ if(ade < ε)
+ return (0, 0.0, 0.0);
+ return (1, det(e, b, f, d)/de, det(a, e, c, f)/de);
+}
+
+ostring(o: ref Object): string
+{
+ return lstring(o.l) + "+" + vstring(o.p) + "+" + string o.v;
+}
+
+pstring(p: ref Plane): string
+{
+ return vstring(p.n) + "=" + string p.d;
+}
+
+lstring(l: ref Line): string
+{
+ return vstring(l.a) + "->" + vstring(l.d);
+}
+
+vstring(v: Vector): string
+{
+ return "(" + string v.x + " " + string v.y + " " + string v.z + ")";
+}
+
+vpt(x: real, y: real, z: real): Vector
+{
+ p: Vector;
+
+ p.x = x;
+ p.y = y;
+ p.z = z;
+ return p;
+}
+
+vdot(v1: Vector, v2: Vector): real
+{
+ return v1.x*v2.x+v1.y*v2.y+v1.z*v2.z;
+}
+
+vcross(v1: Vector, v2: Vector): Vector
+{
+ v: Vector;
+
+ v.x = v1.y*v2.z-v1.z*v2.y;
+ v.y = v1.z*v2.x-v1.x*v2.z;
+ v.z = v1.x*v2.y-v1.y*v2.x;
+ return v;
+}
+
+vadd(v1: Vector, v2: Vector): Vector
+{
+ v: Vector;
+
+ v.x = v1.x+v2.x;
+ v.y = v1.y+v2.y;
+ v.z = v1.z+v2.z;
+ return v;
+}
+
+vsub(v1: Vector, v2: Vector): Vector
+{
+ v: Vector;
+
+ v.x = v1.x-v2.x;
+ v.y = v1.y-v2.y;
+ v.z = v1.z-v2.z;
+ return v;
+}
+
+vmul(v1: Vector, s: real): Vector
+{
+ v: Vector;
+
+ v.x = s*v1.x;
+ v.y = s*v1.y;
+ v.z = s*v1.z;
+ return v;
+}
+
+vdiv(v1: Vector, s: real): Vector
+{
+ v: Vector;
+
+ v.x = v1.x/s;
+ v.y = v1.y/s;
+ v.z = v1.z/s;
+ return v;
+}
+
+vlen(v: Vector): real
+{
+ return maths->sqrt(vdot(v, v));
+}
+
+vlen2(v: Vector): (real, real)
+{
+ d2 := vdot(v, v);
+ d := maths->sqrt(d2);
+ return (d, d2);
+}
+
+vnorm(v: Vector): Vector
+{
+ d := maths->sqrt(vdot(v, v));
+ if(d == 0.0)
+ return v;
+ return vmul(v, real 1/d);
+}
+
+vnorm2(v: Vector): (real, Vector)
+{
+ d := maths->sqrt(vdot(v, v));
+ if(d == 0.0)
+ return (0.0, VZ);
+ return (d, vmul(v, real 1/d));
+}
+
+clip(x: real, d: real): real
+{
+ if(x < -d)
+ x = -d;
+ if(x > d)
+ x = d;
+ return x;
+}
+
+vclip(v: Vector, d: real): Vector
+{
+ c: Vector;
+
+ c.x = clip(v.x, d);
+ c.y = clip(v.y, d);
+ c.z = clip(v.z, d);
+ return c;
+}
+
+vout(v1: Vector, v2: Vector): int
+{
+ v := vsub(v2, v1);
+ return v.x < 0.0 || v.y < 0.0 || v.z < 0.0;
+}
+
+vmin(v: Vector): real
+{
+ m := v.x;
+ if(v.y < m)
+ m = v.y;
+ if(v.z < m)
+ m = v.z;
+ return m;
+}
+
+vvmul(v1: Vector, v2: Vector): Vector
+{
+ v: Vector;
+
+ v.x = v1.x*v2.x;
+ v.y = v1.y*v2.y;
+ v.z = v1.z*v2.z;
+ return v;
+}
+
+vvdiv(v1: Vector, v2: Vector): Vector
+{
+ v: Vector;
+
+ v.x = v1.x/v2.x;
+ v.y = v1.y/v2.y;
+ v.z = v1.z/v2.z;
+ return v;
+}
+
+vmuldiv(v1: Vector, v2: Vector, v3: Vector): real
+{
+ return vdot(vvdiv(v1, v3), v2);
+}
+
+newp(id: int, a: real, b: real, c: real, d: real, m: real, v: array of Vector)
+{
+ p := ref Plane;
+ p.id = id;
+ p.n = (a, b, c);
+ p.d = d;
+ m += ε;
+ p.min = (-m, -m, -m);
+ p.max = (+m, +m, +m);
+ p.v = v;
+ if(v != nil){
+ p.min = (∞, ∞, ∞);
+ p.max = (-∞, -∞, -∞);
+ for(i := 0; i < len v; i++){
+ vtx := v[i];
+ if(vtx.x < p.min.x)
+ p.min.x = vtx.x;
+ if(vtx.y < p.min.y)
+ p.min.y = vtx.y;
+ if(vtx.z < p.min.z)
+ p.min.z = vtx.z;
+ if(vtx.x > p.max.x)
+ p.max.x = vtx.x;
+ if(vtx.y > p.max.y)
+ p.max.y = vtx.y;
+ if(vtx.z > p.max.z)
+ p.max.z = vtx.z;
+ }
+ (x, y, z) := p.far = vmul(p.max, 2.0);
+ if(a != 0.0)
+ p.far.x = (d-b*y-c*z)/a;
+ else if(b != 0.0)
+ p.far.y = (d-c*z-a*x)/b;
+ else if(c != 0.0)
+ p.far.z = (d-a*x-b*y)/c;
+ else
+ fatal("null plane");
+ }
+ planes = p :: planes;
+}
+
+pinit()
+{
+ case(surr){
+ SPHERE or
+ ELLIPSOID =>
+ newp(0, 0.0, 0.0, 1.0, ∞, ∞, nil);
+ CUBE =>
+ newp(0, 1.0, 0.0, 0.0, -icubd, icubd, nil);
+ newp(1, 1.0, 0.0, 0.0, +icubd, icubd, nil);
+ newp(2, 0.0, 1.0, 0.0, -icubd, icubd, nil);
+ newp(3, 0.0, 1.0, 0.0, +icubd, icubd, nil);
+ newp(4, 0.0, 0.0, 1.0, -icubd, icubd, nil);
+ newp(5, 0.0, 0.0, 1.0, +icubd, icubd, nil);
+ * =>
+ p := polyh;
+ F := p.F;
+ v := p.v;
+ f := p.f;
+ fv := p.fv;
+ d := 0.0;
+ for(i := 0; i < F; i++){
+ n := vnorm(f[i]);
+ dn := vmul(n, cubd-icubd);
+ fvi := fv[i];
+ m := fvi[0];
+ av := array[m] of Vector;
+ for(j := 0; j < m; j++){
+ av[j] = vtx := vsub(vmul(v[fvi[j+1]], 2.0*cubd), dn);
+ d += vdot(n, vtx);
+ }
+ d /= real m;
+ newp(i, n.x, n.y, n.z, d, 0.0, av);
+ }
+ }
+}
+
+inside(v: Vector, vmin: Vector, vmax: Vector): int
+{
+ return !vout(vmin, v) && !vout(v, vmax);
+}
+
+inside2(v: Vector, p: ref Plane): int
+{
+ h := 0;
+ pt := p.far;
+ vs := p.v;
+ n := len p.v;
+ j := n-1;
+ for(i := 0; i < n; i++){
+ (ok, λ, μ) := cksimeq(vs[j].x-vs[i].x, v.x-pt.x, vs[j].y-vs[i].y, v.y-pt.y, v.x-vs[i].x, v.y-vs[i].y);
+ if(!ok)
+ (ok, λ, μ) = cksimeq(vs[j].y-vs[i].y, v.y-pt.y, vs[j].z-vs[i].z, v.z-pt.z, v.y-vs[i].y, v.z-vs[i].z);
+ if(!ok)
+ (ok, λ, μ) = cksimeq(vs[j].z-vs[i].z, v.z-pt.z, vs[j].x-vs[i].x, v.x-pt.x, v.z-vs[i].z, v.x-vs[i].x);
+ if(ok && μ >= 0.0 && λ >= 0.0 && λ < 1.0)
+ h++;
+ j = i;
+ }
+ return h&1;
+}
+
+inside3(v: Vector, lp: list of ref Plane): int
+{
+ h := 0;
+ l := ref Line;
+ l.a = v;
+ l.d = vnorm(vsub(far, v));
+ for( ; lp != nil; lp = tl lp){
+ (ok, nil, nil) := intersect(l, hd lp);
+ if(ok)
+ h++;
+ }
+ return h&1;
+}
+
+# outside of a face
+outside2(v: Vector, p: ref Plane): int
+{
+ if(surr == CUBE)
+ return vout(p.min, v) || vout(v, p.max);
+ else
+ return !inside2(v, p);
+}
+
+# outside of a polyhedron
+outside3(v: Vector, vmin: Vector, vmax: Vector): int
+{
+ case(surr){
+ SPHERE =>
+ return vout(vmin, v) || vout(v, vmax) || vdot(v, v) > icubd2 ;
+ ELLIPSOID =>
+ return vout(vmin, v) || vout(v, vmax) || vmuldiv(v, v, e2) > 1.0;
+ CUBE =>
+ return vout(vmin, v) || vout(v, vmax);
+ * =>
+ return !inside3(v, planes);
+ }
+}
+
+intersect(l: ref Line, p: ref Plane): (int, real, Vector)
+{
+ m := vdot(p.n, l.d);
+ if(m == real 0)
+ return (0, real 0, V0);
+ c := vdot(p.n, l.a);
+ λ := (p.d-c)/m;
+ if(λ < real 0)
+ return (0, λ, V0);
+ pt := vadd(l.a, vmul(l.d, λ));
+ if(outside2(pt, p))
+ return (0, λ, pt);
+ return (1, λ, pt);
+}
+
+reflection(tr: ref Object, lp: list of ref Plane)
+{
+ ok: int;
+ λ: real;
+
+ l := tr.l;
+ if(surr == SPHERE){
+ (ok, λ) = quadratic(1.0, 2.0*vdot(l.a, l.d), vdot(l.a, l.a)-icubd2);
+ if(!ok || λ < 0.0)
+ fatal("no sphere intersections");
+ tr.rp = vadd(l.a, vmul(l.d, λ));
+ tr.plane = hd lp; # anything
+ }
+ else if(surr == ELLIPSOID){
+ (ok, λ) = quadratic(vmuldiv(l.d, l.d, e2), 2.0*vmuldiv(l.a, l.d, e2), vmuldiv(l.a, l.a, e2)-1.0);
+ if(!ok || λ < 0.0)
+ fatal("no ellipsoid intersections");
+ tr.rp = vadd(l.a, vmul(l.d, λ));
+ tr.plane = hd lp; # anything
+ }
+ else{
+ p: ref Plane;
+ pt := V0;
+ λ = ∞;
+ for( ; lp != nil; lp = tl lp){
+ p0 := hd lp;
+ if((1<<p0.id)&tr.pmask)
+ continue;
+ (ok0, λ0, pt0) := intersect(l, p0);
+ if(ok0 && λ0 < λ){
+ λ = λ0;
+ p = p0;
+ pt = pt0;
+ }
+ }
+ if(λ == ∞)
+ fatal("no intersections");
+ tr.rp = pt;
+ tr.plane = p;
+ }
+ if(tr.v == 0.0)
+ tr.rt = ∞;
+ else
+ tr.rt = λ/tr.v;
+}
+
+reflect(tr: ref Object)
+{
+ l := tr.l;
+ if(surr == SPHERE)
+ n := vdiv(tr.rp, -icubd);
+ else if(surr == ELLIPSOID)
+ n = vnorm(vdiv(vvdiv(tr.rp, e2), -1.0));
+ else
+ n = tr.plane.n;
+ tr.l.a = tr.rp;
+ tr.l.d = vnorm(vsub(l.d, vmul(n, 2.0*vdot(n, l.d))));
+}
+
+impact(u2: real): (real, real)
+{
+ # u1 == 0
+ return simeq(M1, M2, -1.0, 1.0, M2*u2, -E*u2);
+}
+
+collision(t1: ref Object, t2: ref Object): (int, real, Vector, Vector)
+{
+ # stop t2
+ (v3, f) := vnorm2(vsub(vmul(t1.l.d, t1.v), vmul(t2.l.d, t2.v)));
+ if(v3 == 0.0)
+ return (0, 0.0, V0, V0);
+ ab := vsub(t2.p, t1.p);
+ (d, d2) := vlen2(ab);
+ cos := vdot(f, ab)/d;
+ cos2 := cos*cos;
+ if(cos < 0.0 || (disc := trkd2 - d2*(1.0-cos2)) < 0.0)
+ return (0, 0.0, V0, V0);
+ s := d*cos - maths->sqrt(disc);
+ t := s/v3;
+ s1 := t1.v*t;
+ s2 := t2.v*t;
+ cp1 := vadd(t1.p, vmul(t1.l.d, s1));
+ if(outside3(cp1, cmin, cmax))
+ return (0, 0.0, V0, V0);
+ cp2 := vadd(t2.p, vmul(t2.l.d, s2));
+ if(outside3(cp2, cmin, cmax))
+ return (0, 0.0, V0, V0);
+ return (1, t, cp1, cp2);
+}
+
+collisions(tr: ref Object)
+{
+ mincp1, mincp2: Vector;
+
+ n := nobjs;
+ t := objs;
+ tr0 := tr.obj;
+ mint := tr.ct;
+ tr1: ref Object;
+ for(i := 0; i < n; i++){
+ if((tr3 := t[i]) == tr || tr3 == tr0)
+ continue;
+ (c, tm, cp1, cp2) := collision(tr, tr3);
+ if(c && tm < mint && tm < tr3.ct){
+ mint = tm;
+ tr1 = tr3;
+ mincp1 = cp1;
+ mincp2 = cp2;
+ }
+ }
+ if(tr1 != nil){
+ tr.ct = mint;
+ tr1.ct = mint;
+ tr.obj = tr1;
+ tr2 := tr1.obj;
+ tr1.obj = tr;
+ tr.cp = mincp1;
+ tr1.cp = mincp2;
+ zerot(tr0, COLLIDE, 0);
+ zerot(tr2, COLLIDE, 0);
+ if(tr0 != nil && tr0 != tr2)
+ collisions(tr0);
+ if(tr2 != nil)
+ collisions(tr2);
+ }
+}
+
+collide(t1: ref Object, t2: ref Object)
+{
+ # stop t2
+ ov := vmul(t2.l.d, t2.v);
+ (v3, f) := vnorm2(vsub(vmul(t1.l.d, t1.v), ov));
+ ab := vsub(t2.cp, t1.cp);
+ α := vdot(f, ab)/vdot(ab, ab);
+ abr := vsub(f, vmul(ab, α));
+ (v2, v1) := impact(α*v3);
+ t1.l.a = t1.cp;
+ t2.l.a = t2.cp;
+ dir1 := vadd(vmul(ab, v1), vmul(abr, v3));
+ dir2 := vmul(ab, v2);
+ # start t2
+ (t1.v, t1.l.d) = vnorm2(vadd(dir1, ov));
+ (t2.v, t2.l.d) = vnorm2(vadd(dir2, ov));
+}
+
+deg2rad(d: real): real
+{
+ return π*d/180.0;
+}
+
+rad2deg(r: real): real
+{
+ return 180.0*r/π;
+}
+
+rp2d(r: real, p: real): Vector
+{
+ r = deg2rad(r);
+ cr := maths->cos(r);
+ sr := maths->sin(r);
+ p = deg2rad(p);
+ cp := maths->cos(p);
+ sp := maths->sin(p);
+ return (cr*cp, sr*cp, sp);
+}
+
+d2rp(v: Vector): (real, real)
+{
+ r := maths->atan2(v.y, v.x);
+ p := maths->asin(v.z);
+ return (rad2deg(r), rad2deg(p));
+}
+
+collideω(t1: ref Object, t2: ref Object)
+{
+ d1 := rp2d(t1.roll, t1.pitch);
+ d2 := rp2d(t2.roll, t2.pitch);
+ oω := vmul(d2, t2.ω);
+ (ω3, f) := vnorm2(vsub(vmul(d1, t1.ω), oω));
+ ab := vsub(t2.cp, t1.cp);
+ α := vdot(f, ab)/vdot(ab, ab);
+ abr := vsub(f, vmul(ab, α));
+ (ω2, ω1) := impact(α*ω3);
+ dir1 := vadd(vmul(ab, ω1), vmul(abr, ω3));
+ dir2 := vmul(ab, ω2);
+ (t1.ω, d1) = vnorm2(vadd(dir1, oω));
+ (t2.ω, d2) = vnorm2(vadd(dir2, oω));
+ (t1.roll, t1.pitch) = d2rp(d1);
+ (t2.roll, t2.pitch) = d2rp(d2);
+}
+
+plane(p1: Vector, p2: Vector, p3: Vector): (Vector, real)
+{
+ n := vnorm(vcross(vsub(p2, p1), vsub(p3, p1)));
+ d := vdot(n, p1);
+ return (n, d);
+}
+
+# angle subtended by the eyes at p in minutes
+angle(p: Vector): real
+{
+ l, r: Vector;
+
+ # left eye at (-eyed, 0, 0)
+ # right eye at (+eyed, 0, 0)
+ #
+ l = p;
+ l.x += eyed;
+ r = p;
+ r.x -= eyed;
+ return real 60*(real 180*maths->acos(vdot(l, r)/(maths->sqrt(vdot(l, l))*maths->sqrt(vdot(r, r))))/π);
+}
+
+# given coordinates relative to centre of cube
+disparity(p: Vector, b: Vector): real
+{
+ p.z -= objd;
+ b.z -= objd;
+ return angle(p)-angle(b);
+}
+
+# rotation about any of the axes
+# rotate(theta, &x, &y, &z) for x-axis
+# rotate(theta, &y, &z, &x) for y-axis
+# rotate(theta, &z, &x, &y) for z-axis
+#
+rotate(theta: int, x: real, y: real, z: real): (real, real, real)
+{
+ a := π*real theta/real 180;
+ c := maths->cos(a);
+ s := maths->sin(a);
+ oy := y;
+ oz := z;
+ y = c*oy-s*oz;
+ z = c*oz+s*oy;
+ return (x, y, z);
+}
+
+# solve the quadratic ax^2 + bx + c = 0, returning the larger root
+# * (a > 0)
+#
+quadratic(a: real, b: real, c: real): (int, real)
+{
+ d := b*b-real 4*a*c;
+ if(d < real 0)
+ return (0, 0.0); # no real roots
+ x := (maths->sqrt(d)-b)/(real 2*a);
+ return (1, x);
+}
+
+# calculate the values of objD, scrD given the required parallax
+dopar()
+{
+ a := real 1;
+ b, c: real;
+ f := real 2*front-real 1;
+ x: real;
+ s: int;
+ w := sx*real SCRW;
+ ok: int;
+
+ if(hpar == 0.0){ # natural parallax
+ objD = objd;
+ scrD = scrd;
+ return;
+ }
+ if(fbpar < f)
+ s = -1;
+ else
+ s = 1;
+ if(fbpar == f)
+ fatal("parallax value is zero at screen distance");
+ b = (fbpar+f)*cubd-(fbpar-f)*w*eyed*real s*frac/hpar;
+ c = fbpar*f*cubd*cubd;
+ (ok, x) = quadratic(a, b, c);
+ if(ok){
+ objD = x;
+ scrD = x+f*cubd;
+ if(objD > real 0 && scrD > real 0)
+ return;
+ }
+ fatal("unachievable parallax value");
+}
+
+# update graphics parameters
+update(init: int)
+{
+ if(fov != real 0){
+ if(objd == real 0)
+ fov = 180.0;
+ else
+ fov = real 2*(real 180*maths->atan(cubd/(frac*objd))/π);
+ }
+ scrd = objd+(real 2*front-real 1)*cubd;
+ if(init){
+ if(objd < ε)
+ objd = ε;
+ if(fov != real 0)
+ sf = real (1<<2)*cubd/(objd*frac);
+ else
+ sf = cubd/frac;
+ }
+ # dopar();
+ domats();
+}
+
+fovtodist()
+{
+ if(fov != real 0)
+ objd = cubd/(frac*maths->tan(π*(fov/real 2)/real 180));
+}
+
+getpolys()
+{
+ (n, p, b) := polyhedra->scanpolyhedra("/lib/polyhedra");
+ polyhedra->getpolyhedra(p, b);
+ polys = p;
+ npolys = n;
+ do{
+ for(i := 0; i < p.V; i++)
+ p.v[i] = vmul(p.v[i], 0.5);
+ for(i = 0; i < p.F; i++)
+ p.f[i] = vmul(p.f[i], 0.5);
+ p = p.nxt;
+ }while(p != polys);
+}
+
+randpoly(p: ref Polyhedra->Polyhedron, n: int): ref Polyhedra->Polyhedron
+{
+ r := random(0, n-1);
+ for( ; --r >= 0; p = p.nxt)
+ ;
+ return p;
+}
+
+drawpoly(p: ref Polyhedra->Polyhedron, typex: int)
+{
+ # V := p.V;
+ F := p.F;
+ v := p.v;
+ # f := p.f;
+ fv := p.fv;
+ for(i := 0; i < F; i++){
+ fvi := fv[i];
+ n := fvi[0];
+ m_begin(typex, n);
+ for(j := 0; j < n; j++){
+ vtx := v[fvi[j+1]];
+ m_vertex(vtx.x, vtx.y, vtx.z);
+ }
+ m_end();
+ }
+}
+
+# objects with unit sides/diameter
+H: con 0.5;
+
+square(typex: int)
+{
+ m_begin(typex, 4);
+ m_vertex(-H, -H, 0.0);
+ m_vertex(-H, +H, 0.0);
+ m_vertex(+H, +H, 0.0);
+ m_vertex(+H, -H, 0.0);
+ m_end();
+}
+
+diamond(typex: int)
+{
+ m_pushmatrix();
+ m_rotatez(45.0);
+ square(typex);
+ m_popmatrix();
+}
+
+circleinit()
+{
+ i: int;
+ a := 0.0;
+ cost = array[NC] of real;
+ sint = array[NC] of real;
+ for(i = 0; i < NC; i++){
+ cost[i] = H*maths->cos(a);
+ sint[i] = H*maths->sin(a);
+ a += (2.0*π)/real NC;
+ }
+}
+
+circle(typex: int)
+{
+ i: int;
+
+ if(DOCIRCLE){
+ m_begin(typex, 2);
+ m_circle(0.0, 0.0, 0.0, 0.5);
+ m_end();
+ return;
+ }
+ else{
+ m_begin(typex, NC);
+ for(i = 0; i < NC; i++)
+ m_vertex(cost[i], sint[i], 0.0);
+ m_end();
+ }
+}
+
+ellipse(typex: int)
+{
+ m_begin(typex, 4);
+ m_ellipse(0.0, 0.0, 0.0, vmul(ef, 0.5));
+ m_end();
+}
+
+hexahedron(typex: int)
+{
+ i, j, k: int;
+ V := array[8] of {
+ array[3] of {
+ -H, -H, -H,
+ },
+ array[3] of {
+ -H, -H, +H,
+ },
+ array[3] of {
+ -H, +H, -H,
+ },
+ array[3] of {
+ -H, +H, +H,
+ },
+ array[3] of {
+ +H, -H, -H,
+ },
+ array[3] of {
+ +H, -H, +H,
+ },
+ array[3] of {
+ +H, +H, -H,
+ },
+ array[3] of {
+ +H, +H, +H,
+ },
+ };
+ F := array[6] of {
+ array[4] of {
+ 0, 4, 6, 2,
+ },
+ array[4] of {
+ 0, 4, 5, 1,
+ },
+ array[4] of {
+ 0, 1, 3, 2,
+ },
+ array[4] of {
+ 1, 5, 7, 3,
+ },
+ array[4] of {
+ 2, 6, 7, 3,
+ },
+ array[4] of {
+ 4, 5, 7, 6,
+ },
+ };
+
+ for(i = 0; i < 6; i++){
+ m_begin(typex, 4);
+ for(j = 0; j < 4; j++){
+ k = F[i][j];
+ m_vertex(V[k][0], V[k][1], V[k][2]);
+ }
+ m_end();
+ }
+}
+
+zerot(tr: ref Object, zero: int, note: int)
+{
+ if(tr != nil){
+ if(zero&REFLECT){
+ tr.rt = ∞;
+ tr.plane = nil;
+ }
+ if(zero&COLLIDE){
+ tr.ct = ∞;
+ tr.obj = nil;
+ }
+ if(note)
+ tr.todo = zero;
+ }
+}
+
+newobj(t: array of ref Object, n: int, vel: int, velf: real): ref Object
+{
+ tr: ref Object;
+ p1: Vector;
+ again: int;
+
+ d := icubd-1.0;
+ cnt := 1024;
+ do{
+ p1 = randomp(-d, d);
+ again = 0;
+ for(i := 0; i < n; i++){
+ (nil, d2) := vlen2(vsub(t[i].p, p1));
+ if(d2 <= trkd2){
+ again = 1;
+ break;
+ }
+ }
+ cnt--;
+ }while(again && cnt > 0);
+ if(again)
+ return nil;
+ # p2 := randomp(-d, d);
+ p21 := randomd();
+ tr = ref Object;
+ tr.id = n;
+ tr.poly = nil;
+ if(poly){
+ if(n == 0)
+ tr.poly = randpoly(polys, npolys);
+ else
+ tr.poly = t[0].poly;
+ }
+ tr.c = randomc();
+ tr.cb = tr.c; # randomc();
+ if(vel)
+ tr.v = vf*velf*randomr(0.5, 2.0);
+ else
+ tr.v = 0.0;
+ tr.ω = vf*randomr(1.0, 10.0);
+ tr.roll = randomr(0.0, 360.0);
+ tr.pitch = randomr(0.0, 360.0);
+ tr.yaw = randomr(0.0, 360.0);
+ tr.l = ref Line(p1, vnorm(p21));
+ tr.p = p1;
+ tr.todo = 0;
+ zerot(tr, REFLECT|COLLIDE, 0);
+ tr.pmask = 0;
+ reflection(tr, planes);
+ return tr;
+}
+
+objinit(m: int, v: int)
+{
+ velf := real m/real v;
+ p := nobjs;
+ n := p+m;
+ v += p;
+ t := array[n] of ref Object;
+ for(i := 0; i < p; i++)
+ t[i] = objs[i];
+ for(i = p; i < n; i++){
+ t[i] = newobj(t, i, i < v, velf);
+ if(t[i] == nil)
+ return;
+ }
+ sort(t, n);
+ nobjs = n;
+ objs = t;
+ for(i = p; i < n; i++)
+ collisions(t[i]);
+}
+
+zobj: Object;
+
+newo(n: int, p: Vector, c: ref Draw->Image): ref Object
+{
+ o := ref Object;
+ *o = zobj;
+ o.id = n;
+ o.c = o.cb = c;
+ o.l = ref Line(p, VZ);
+ o.p = p;
+ zerot(o, REFLECT|COLLIDE, 0);
+ reflection(o, planes);
+ return o;
+}
+
+objinits(nil: int, nil: int)
+{
+ n := 16;
+ t := array[n] of ref Object;
+ r := trkd/2.0;
+ i := 0;
+ yc := 0.0;
+ for(y := 0; y < 5; y++){
+ xc := -real y*r;
+ for(x := 0; x <= y; x++){
+ t[i] = newo(i, (xc, yc, 0.0), red);
+ xc += trkd;
+ i++;
+ }
+ yc += trkd;
+ }
+ t[i] = newo(i, (0.0, -50.0, 0.0), white);
+ t[i].l.d = (0.0, 1.0, 0.0);
+ t[i].v = 1.0;
+ sort(t, n);
+ nobjs = n;
+ objs = t;
+ for(i = 0; i < n; i++)
+ collisions(t[i]);
+}
+
+initme(): ref Object
+{
+ t := newobj(nil, 0, 1, 1.0);
+ t.roll = t.pitch = t.yaw = 0.0;
+ t.v = t.ω = 0.0;
+ t.l.a = (0.0, 0.0, objd); # origin when translated
+ t.l.d = (0.0, 0.0, -1.0);
+ t.p = t.l.a;
+ zerot(t, REFLECT|COLLIDE, 0);
+ return t;
+}
+
+retime(s: real)
+{
+ r := 1.0/s;
+ n := nobjs;
+ t := objs;
+ for(i := 0; i < n; i++){
+ tr := t[i];
+ tr.v *= s;
+ tr.ω *= s;
+ tr.rt *= r;
+ tr.ct *= r;
+ }
+ me.v *= s;
+ me.ω *= s;
+ me.rt *= r;
+ me.ct *= r;
+}
+
+drawobjs()
+{
+ tr: ref Object;
+ p: Vector;
+
+ n := nobjs;
+ t := objs;
+
+ for(i := 0; i < n; i++){
+ tr = t[i];
+ tr.pmask = 0;
+ p = tr.p;
+ m_pushmatrix();
+ if(rpy && tr.poly == nil){
+ m_loadmatrix(mod0);
+ (p.x, p.y, p.z) = rotate(int yaw, p.x, p.y, p.z);
+ (p.y, p.z, p.x) = rotate(int pitch, p.y, p.z, p.x);
+ (p.z, p.x, p.y) = rotate(int roll, p.z, p.x, p.y);
+ }
+ m_translate(p.x, p.y, p.z);
+ m_scale(trkd, trkd, trkd);
+ if(tr.poly != nil){
+ m_rotatez(tr.roll);
+ m_rotatey(tr.pitch);
+ m_rotatex(tr.yaw);
+ tr.yaw += tr.ω;
+ }
+ m_matmul();
+ if(tr.cb != tr.c){
+ m_colour(tr.cb);
+ if(tr.poly != nil)
+ drawpoly(tr.poly, POLY);
+ else if(DOCIRCLE)
+ circle(CIRCLE);
+ else
+ circle(POLY);
+ }
+ m_colour(tr.c);
+ if(tr.poly != nil)
+ drawpoly(tr.poly, FILLPOLY);
+ else if(DOCIRCLE)
+ circle(FILLCIRCLE);
+ else
+ circle(FILLPOLY);
+ m_popmatrix();
+ }
+
+ tm := 1.0;
+ do{
+ δt := ∞;
+
+ for(i = 0; i < n; i++){
+ tr = t[i];
+ if(tr.rt < δt)
+ δt = tr.rt;
+ if(tr.ct < δt)
+ δt = tr.ct;
+ }
+
+ if(δt > tm)
+ δt = tm;
+
+ for(i = 0; i < n; i++){
+ tr = t[i];
+ if(tr.rt == δt){
+ tr1 := tr.obj;
+ reflect(tr);
+ tr.p = tr.rp;
+ if(δt > 0.0)
+ tr.pmask = (1<<tr.plane.id);
+ else
+ tr.pmask |= (1<<tr.plane.id);
+ zerot(tr, REFLECT|COLLIDE, 1);
+ zerot(tr1, COLLIDE, 1);
+ }
+ else if(tr.ct == δt){
+ tr1 := tr.obj ;
+ collide(tr, tr1);
+ if(0 && poly)
+ collideω(tr, tr1);
+ tr.p = tr.cp;
+ tr1.p = tr1.cp;
+ tr.pmask = tr1.pmask = 0;
+ zerot(tr, REFLECT|COLLIDE, 1);
+ zerot(tr1, REFLECT|COLLIDE, 1);
+ }
+ else if(tr.todo != (REFLECT|COLLIDE)){
+ tr.p = vclip(vadd(tr.p, vmul(tr.l.d, tr.v*δt)), icubd);
+ tr.rt -= δt;
+ tr.ct -= δt;
+ }
+ }
+
+ for(i = 0; i < n; i++){
+ tr = t[i];
+ if(tr.todo){
+ if(tr.todo&REFLECT)
+ reflection(tr, planes);
+ if(tr.todo&COLLIDE)
+ collisions(tr);
+ tr.todo = 0;
+ }
+ }
+
+ tm -= δt;
+
+ }while(tm > 0.0);
+
+ sort(t, n);
+}
+
+drawscene()
+{
+ m_pushmatrix();
+ m_scale(real 2*cubd, real 2*cubd, real 2*cubd);
+ m_colour(white);
+ m_matmul();
+ case(surr){
+ SPHERE =>
+ if(DOCIRCLE)
+ circle(CIRCLE);
+ else
+ circle(POLY);
+ ELLIPSOID =>
+ ellipse(ELLIPSE);
+ CUBE =>
+ if(flat)
+ square(POLY);
+ else
+ hexahedron(POLY);
+ * =>
+ drawpoly(polyh, POLY);
+ }
+ m_popmatrix();
+ drawobjs();
+}
+
+# ensure parallax doesn't alter between images
+adjpar(x: array of real, y: array of real, z: array of real)
+{
+ zed, incr: real;
+
+ y = nil;
+ if(z[0] < real 0)
+ zed = -z[0];
+ else
+ zed = z[0];
+ incr = eyed*zed*(real 1-scrD/(zed+objD-objd))/scrd;
+ if(!stereo || fov == real 0)
+ return;
+ if(left)
+ x[0] -= incr;
+ else
+ x[0] += incr;
+}
+
+view()
+{
+ m_mode(PROJ);
+ m_loadidentity();
+ m_scale(sx, sy, sz);
+ if(fov != real 0)
+ m_frustum(sf, real (1<<2), real (1<<20));
+ else
+ m_ortho(sf, real (1<<2), real (1<<20));
+ # m_print();
+ m_mode(MODEL);
+}
+
+model(rot: int)
+{
+ m_loadidentity();
+ m_translate(0.0, 0.0, -objd);
+ if(rpy && rot){
+ m_rotatez(roll);
+ m_rotatey(pitch);
+ m_rotatex(yaw);
+ }
+}
+
+# store projection and modelview matrices
+domats()
+{
+ model(0);
+ m_storematrix(mod0);
+ model(1);
+ view();
+}
+
+scale()
+{
+ if(maxx > maxy)
+ sx = real maxy/real maxx;
+ else
+ sx = 1.0;
+ if(maxy > maxx)
+ sy = real maxx/real maxy;
+ else
+ sy = 1.0;
+ sz = 1.0;
+}
+
+rescale(w: int, h: int)
+{
+ maxx = w;
+ maxy = h;
+ scale();
+ m_viewport(0, 0, maxx, maxy);
+}
+
+grinit()
+{
+ for(i := 0; i < 4; i++)
+ mod0[i] = array[4] of real;
+ far = (2.0*cubd, 2.0*cubd, 2.0*cubd);
+ icubd = cubd-trkd/2.0;
+ icubd2 = icubd*icubd;
+ trkd2 = trkd*trkd;
+ cmin = vpt(-icubd, -icubd, -icubd);
+ cmax = vpt(+icubd, +icubd, +icubd);
+ maxx = MAXX;
+ maxy = MAXY;
+ e2 = vmul(vvmul(ef, ef), icubd2);
+
+ m_init();
+ pinit();
+ circleinit();
+
+ m_viewport(0, 0, maxx, maxy);
+
+ scale();
+ if(fov > real 0)
+ fovtodist();
+ update(1);
+}
+
+newimage(win: ref Toplevel, init: int)
+{
+ maxx = int cmd(win, ".p cget -actwidth");
+ maxy = int cmd(win, ".p cget -actheight");
+ RDisp = display.newimage(((0, 0), (maxx, maxy)), win.image.chans, 0, Draw->Black);
+ tk->putimage(win, ".p", RDisp, nil);
+ RDisp.draw(RDisp.r, black, nil, (0, 0));
+ reveal();
+ rescale(maxx, maxy);
+ update(init);
+}
+
+reveal()
+{
+ cmd(toplev, ".p dirty; update");
+}
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "usage: collide [-cse] [-f] [-op] [-b num] [-v num]\n");
+ exit;
+}
+
+main(ctxt: ref Draw->Context, args: list of string)
+{
+ rand->init(daytime->now());
+ daytime = nil;
+
+ b := v := random(16, 32);
+
+ arg->init(args);
+ while((o := arg->opt()) != 0){
+ case(o){
+ * =>
+ usage();
+ 's' =>
+ surr = SPHERE;
+ 'e' =>
+ surr = ELLIPSOID;
+ 'c' =>
+ surr = CUBE;
+ 'o' =>
+ fov = 0.0;
+ 'p' =>
+ fov = -1.0;
+ 'f' =>
+ flat = 1;
+ 'b' =>
+ b = v = int arg->arg();
+ 'v' =>
+ v = int arg->arg();
+ }
+ }
+ if(arg->argv() != nil)
+ usage();
+
+ if(b <= 0)
+ b = 1;
+ if(b > 100)
+ b = 100;
+ if(v <= 0)
+ v = 1;
+ if(v > b)
+ v = b;
+
+ if(poly || surr == POLYHEDRON){
+ polyhedra = load Polyhedra Polyhedra->PATH;
+ getpolys();
+ }
+ if(surr == POLYHEDRON)
+ polyh = randpoly(polys, npolys);
+
+ grinit();
+
+ tkclient->init();
+ (win, wmch) := tkclient->toplevel(ctxt, "", "Collide", Tkclient->Resize | Tkclient->Hide);
+ toplev = win;
+ sys->pctl(Sys->NEWPGRP, nil);
+ cmdch := chan of string;
+ tk->namechan(win, cmdch, "cmd");
+ for(i := 0; i < len winconfig; i++)
+ cmd(win, winconfig[i]);
+ cmd(win, "update");
+
+ tkclient->onscreen(win, nil);
+ tkclient->startinput(win, "kbd"::"ptr"::nil);
+
+ display = win.image.display;
+ newimage(win, 1);
+
+ black = display.color(Draw->Black);
+ white = display.color(Draw->White);
+ red = display.color(Draw->Red);
+
+ objinit(b, v);
+ me = initme();
+
+ pid := -1;
+ sync := chan of int;
+ cmdc := chan of int;
+ spawn animate(sync, cmdc);
+ pid = <- sync;
+
+ for(;;){
+ alt{
+ c := <-win.ctxt.kbd =>
+ tk->keyboard(win, c);
+ c := <-win.ctxt.ptr =>
+ tk->pointer(win, *c);
+ c := <-win.ctxt.ctl or
+ c = <-win.wreq =>
+ tkclient->wmctl(win, c);
+ c := <- wmch =>
+ case c{
+ "exit" =>
+ if(pid != -1)
+ kill(pid);
+ exit;
+ * =>
+ sync <-= 0;
+ tkclient->wmctl(win, c);
+ if(c[0] == '!')
+ newimage(win, 0);
+ sync <-= 1;
+ }
+ c := <- cmdch =>
+ case c{
+ "stop" =>
+ cmdc <-= 's';
+ "zoomin" =>
+ cmdc <-= 'z';
+ "zoomout" =>
+ cmdc <-= 'o';
+ "slow" =>
+ cmdc <-= '-';
+ "fast" =>
+ cmdc <-= '+';
+ "objs" =>
+ sync <-= 0;
+ b >>= 1;
+ if(b == 0)
+ b = 1;
+ objinit(b, b);
+ sync <-= 1;
+ }
+ }
+ }
+}
+
+sign(r: real): real
+{
+ if(r < 0.0)
+ return -1.0;
+ return 1.0;
+}
+
+abs(r: real): real
+{
+ if(r < 0.0)
+ return -r;
+ return r;
+}
+
+animate(sync: chan of int, cmd: chan of int)
+{
+ zd := objd/250.0;
+ δ := θ := 0.0;
+ f := 8;
+
+ sync <- = sys->pctl(0, nil);
+ for(;;){
+ σ := 1.0;
+ alt{
+ <- sync =>
+ <- sync;
+ c := <- cmd =>
+ case(c){
+ 's' =>
+ δ = θ = 0.0;
+ 'z' =>
+ δ = zd;
+ 'o' =>
+ δ = -zd;
+ 'r' =>
+ θ = 1.0;
+ '+' =>
+ σ = 1.25;
+ f++;
+ if(f > 16){
+ f--;
+ σ = 1.0;
+ }
+ else
+ vf *= σ;
+ '-' =>
+ σ = 0.8;
+ f--;
+ if(f < 0){
+ f++;
+ σ = 1.0;
+ }
+ else
+ vf *= σ;
+ }
+ * =>
+ sys->sleep(0);
+ }
+
+ RDisp.draw(RDisp.r, black, nil, (0, 0));
+ drawscene();
+ reveal();
+
+ if(δ != 0.0 || θ != 0.0){
+ objd -= δ;
+ me.l.a.z -= δ;
+ if(θ != 0.0){
+ roll += θ;
+ pitch += θ;
+ yaw += θ;
+ rpy = 1;
+ }
+ update(projx);
+ }
+ if(σ != 1.0)
+ retime(σ);
+ }
+}
+
+# usually almost sorted
+sort(ts: array of ref Object, n: int)
+{
+ done: int;
+ t: ref Object;
+ q, p: int;
+
+ q = n;
+ do{
+ done = 1;
+ q--;
+ for(p = 0; p < q; p++){
+ if(ts[p].p.z > ts[p+1].p.z){
+ t = ts[p];
+ ts[p] = ts[p+1];
+ ts[p+1] = t;
+ done = 0;
+ }
+ }
+ }while(!done);
+}
+
+kill(pid: int): int
+{
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd == nil)
+ return -1;
+ if(sys->write(fd, array of byte "kill", 4) != 4)
+ return -1;
+ return 0;
+}
+
+fatal(e: string)
+{
+ sys->fprint(sys->fildes(2), "%s\n", e);
+ raise "fatal";
+}
+
+cmd(top: ref Toplevel, s: string): string
+{
+ e := tk->cmd(top, s);
+ if (e != nil && e[0] == '!')
+ fatal(sys->sprint("tk error on '%s': %s", s, e));
+ return e;
+}
+
+winconfig := array[] of {
+ "frame .f",
+ "button .f.zoomin -text {zoomin} -command {send cmd zoomin}",
+ "button .f.zoomout -text {zoomout} -command {send cmd zoomout}",
+ "button .f.stop -text {stop} -command {send cmd stop}",
+ "pack .f.zoomin -side left",
+ "pack .f.zoomout -side right",
+ "pack .f.stop -side top",
+
+ "frame .f2",
+ "button .f2.slow -text {slow} -command {send cmd slow}",
+ "button .f2.fast -text {fast} -command {send cmd fast}",
+ "button .f2.objs -text {objects} -command {send cmd objs}",
+ "pack .f2.slow -side left",
+ "pack .f2.fast -side right",
+ "pack .f2.objs -side top",
+
+ "panel .p -width " + string MAXX + " -height " + string MAXY,
+
+ "pack .f -side top -fill x",
+ "pack .f2 -side top -fill x",
+ "pack .p -side bottom -fill both -expand 1",
+
+ "pack propagate . 0",
+ "update"
+};
+
+############################################################
+
+# gl.b
+
+#
+# initially generated by c2l
+#
+
+MODEL, PROJ: con iota;
+
+Matrix: type array of array of real;
+
+Mstate: adt{
+ matl: list of Matrix;
+ modl: list of Matrix;
+ prjl: list of Matrix;
+ mull: Matrix;
+ freel: list of Matrix;
+ vk: int;
+ vr: int;
+ vrr: int;
+ vc: ref Draw->Image;
+ ap: array of Draw->Point;
+ apn: int;
+ mx, cx, my, cy: real;
+ ignore: int;
+};
+
+ms: Mstate;
+
+m_new(): Matrix
+{
+ if(ms.freel != nil){
+ m := hd ms.freel;
+ ms.freel = tl ms.freel;
+ return m;
+ }
+ m := array[4] of array of real;
+ for(i := 0; i < 4; i++)
+ m[i] = array[4] of real;
+ return m;
+}
+
+m_free(m: Matrix)
+{
+ ms.freel = m :: ms.freel;
+}
+
+m_init()
+{
+ ms.modl = m_new() :: nil;
+ ms.prjl = m_new() :: nil;
+ ms.matl = ms.modl;
+ ms.mull = m_new();
+ ms.vk = 0;
+ ms.apn = 0;
+ ms.mx = ms.cx = ms.my = ms.cy = 0.0;
+ ms.ignore = 0;
+}
+
+m_print()
+{
+ m := hd ms.matl;
+
+ for(i := 0; i < 4; i++){
+ for(j := 0; j < 4; j++)
+ sys->print("%f ", m[i][j]);
+ sys->print("\n");
+ }
+ sys->print("\n");
+}
+
+m_mode(m: int)
+{
+ if(m == PROJ)
+ ms.matl = ms.prjl;
+ else
+ ms.matl = ms.modl;
+}
+
+m_pushmatrix()
+{
+ if(ms.matl == ms.modl){
+ ms.modl = m_new() :: ms.modl;
+ ms.matl = ms.modl;
+ }
+ else{
+ ms.prjl = m_new() :: ms.prjl;
+ ms.matl = ms.prjl;
+ }
+ s := hd tl ms.matl;
+ d := hd ms.matl;
+ for(i := 0; i < 4; i++)
+ for(j := 0; j < 4; j++)
+ d[i][j] = s[i][j];
+}
+
+m_popmatrix()
+{
+ if(ms.matl == ms.modl){
+ m_free(hd ms.modl);
+ ms.modl = tl ms.modl;
+ ms.matl = ms.modl;
+ }
+ else{
+ m_free(hd ms.prjl);
+ ms.prjl = tl ms.prjl;
+ ms.matl = ms.prjl;
+ }
+}
+
+m_loadidentity()
+{
+ i, j: int;
+ m := hd ms.matl;
+
+ for(i = 0; i < 4; i++){
+ for(j = 0; j < 4; j++)
+ m[i][j] = real 0;
+ m[i][i] = real 1;
+ }
+}
+
+m_translate(x: real, y: real, z: real)
+{
+ i: int;
+ m := hd ms.matl;
+
+ for(i = 0; i < 4; i++)
+ m[i][3] = x*m[i][0]+y*m[i][1]+z*m[i][2]+m[i][3];
+}
+
+m_scale(x: real, y: real, z: real)
+{
+ i: int;
+ m := hd ms.matl;
+
+ for(i = 0; i < 4; i++){
+ m[i][0] *= x;
+ m[i][1] *= y;
+ m[i][2] *= z;
+ }
+}
+
+# rotate about x, y or z axes
+rot(deg: real, j: int, k: int)
+{
+ i: int;
+ m := hd ms.matl;
+ rad := Math->Pi*deg/real 180;
+ s := maths->sin(rad);
+ c := maths->cos(rad);
+ a, b: real;
+
+ for(i = 0; i < 4; i++){
+ a = m[i][j];
+ b = m[i][k];
+ m[i][j] = c*a+s*b;
+ m[i][k] = c*b-s*a;
+ }
+}
+
+m_rotatex(a: real)
+{
+ rot(a, 1, 2);
+}
+
+m_rotatey(a: real)
+{
+ rot(a, 2, 0);
+}
+
+m_rotatez(a: real)
+{
+ rot(a, 0, 1);
+}
+
+# (l m n) normalized
+m_rotate(deg: real, l: real, m: real, n:real)
+{
+ i: int;
+ mx := hd ms.matl;
+ rad := Math->Pi*deg/real 180;
+ s := maths->sin(rad);
+ c := maths->cos(rad);
+ f := 1.0-c;
+ m0, m1, m2: real;
+
+ for(i = 0; i < 4; i++){
+ m0 = mx[i][0];
+ m1 = mx[i][1];
+ m2 = mx[i][2];
+ mx[i][0] = m0*(l*l*f+c)+m1*(l*m*f+n*s)+m2*(l*n*f-m*s);
+ mx[i][1] = m0*(l*m*f-n*s)+m1*(m*m*f+c)+m2*(m*n*f+l*s);
+ mx[i][2] = m0*(l*n*f+m*s)+m1*(m*n*f-l*s)+m2*(n*n*f+c);
+ }
+}
+
+# Frustum(-l, l, -l, l, n, f)
+m_frustum(l: real, n: real, f: real)
+{
+ i: int;
+ m := hd ms.matl;
+ r := n/l;
+ a, b: real;
+
+ f = ∞;
+ for(i = 0; i < 4; i++){
+ a = m[i][2];
+ b = m[i][3];
+ m[i][0] *= r;
+ m[i][1] *= r;
+ m[i][2] = a+b;
+ m[i][3] = 0.0;
+ # m[i][2] = -(a*(f+n)/(f-n)+b);
+ # m[i][3] = real -2*f*n*a/(f-n);
+ }
+}
+
+# Ortho(-l, l, -l, l, n, f)
+m_ortho(l: real, n: real, f: real)
+{
+ i: int;
+ m := hd ms.matl;
+ r := real 1/l;
+ # a: real;
+
+ n = 0.0;
+ f = ∞;
+ for(i = 0; i < 4; i++){
+ # a = m[i][2];
+ m[i][0] *= r;
+ m[i][1] *= r;
+ # m[i][2] *= real -2/(f-n);
+ # m[i][3] -= a*(f+n)/(f-n);
+ }
+}
+
+m_loadmatrix(u: array of array of real)
+{
+ m := hd ms.matl;
+
+ for(i := 0; i < 4; i++)
+ for(j := 0; j < 4; j++)
+ m[i][j] = u[i][j];
+}
+
+m_storematrix(u: array of array of real)
+{
+ m := hd ms.matl;
+
+ for(i := 0; i < 4; i++)
+ for(j := 0; j < 4; j++)
+ u[i][j] = m[i][j];
+}
+
+m_matmul()
+{
+ m, p, r: Matrix;
+
+ m = hd ms.modl;
+ p = hd ms.prjl;
+ r = ms.mull;
+ for(i := 0; i < 4; i++){
+ pr := p[i];
+ rr := r[i];
+ for(j := 0; j < 4; j++)
+ rr[j] = pr[0]*m[0][j]+pr[1]*m[1][j]+pr[2]*m[2][j]+pr[3]*m[3][j];
+ }
+}
+
+m_vertexo(x: real, y: real, z: real)
+{
+ m: Matrix;
+ mr: array of real;
+ w, x1, y1, z1: real;
+
+ m = hd ms.modl;
+ mr = m[0]; x1 = x*mr[0]+y*mr[1]+z*mr[2]+mr[3];
+ mr = m[1]; y1 = x*mr[0]+y*mr[1]+z*mr[2]+mr[3];
+ mr = m[2]; z1 = x*mr[0]+y*mr[1]+z*mr[2]+mr[3];
+ mr = m[3]; w = x*mr[0]+y*mr[1]+z*mr[2]+mr[3];
+ if(w != real 1){
+ x1 /= w;
+ y1 /= w;
+ z1 /= w;
+ }
+ if(z1 >= 0.0){
+ ms.ignore = 1;
+ return;
+ }
+ m = hd ms.prjl;
+ mr = m[0]; x = x1*mr[0]+y1*mr[1]+z1*mr[2]+mr[3];
+ mr = m[1]; y = x1*mr[0]+y1*mr[1]+z1*mr[2]+mr[3];
+ mr = m[2]; z = x1*mr[0]+y1*mr[1]+z1*mr[2]+mr[3];
+ mr = m[3]; w = x1*mr[0]+y1*mr[1]+z1*mr[2]+mr[3];
+ if(w == real 0){
+ ms.ignore = 1;
+ return;
+ }
+ if(w != real 1){
+ x /= w;
+ y /= w;
+ # z /= w;
+ }
+ ms.ap[ms.apn++] = (int (ms.mx*x+ms.cx), int (ms.my*y+ms.cy));
+}
+
+m_vertex(x: real, y: real, z: real): (real, real)
+{
+ m: Matrix;
+ mr: array of real;
+ w, x1, y1, z1: real;
+
+ m = ms.mull;
+ mr = m[0]; x1 = x*mr[0]+y*mr[1]+z*mr[2]+mr[3];
+ mr = m[1]; y1 = x*mr[0]+y*mr[1]+z*mr[2]+mr[3];
+ mr = m[2]; z1 = x*mr[0]+y*mr[1]+z*mr[2]+mr[3];
+ mr = m[3]; w = x*mr[0]+y*mr[1]+z*mr[2]+mr[3];
+ if(w == real 0){
+ ms.ignore = 1;
+ return (x1, y1);
+ }
+ if(w != real 1){
+ x1 /= w;
+ y1 /= w;
+ # z1 /= w;
+ }
+ if(z1 >= 0.0){
+ ms.ignore = 1;
+ return (x1, y1);
+ }
+ ms.ap[ms.apn++] = (int (ms.mx*x1+ms.cx), int (ms.my*y1+ms.cy));
+ return (x1, y1);
+}
+
+m_circle(x: real, y: real, z: real, r: real)
+{
+ (d, nil) := m_vertex(x, y, z);
+ (e, nil) := m_vertex(x+r, y, z);
+ d -= e;
+ if(d < 0.0)
+ d = -d;
+ ms.vr = int (ms.mx*d);
+}
+
+m_ellipse(x: real, y: real, z: real, v: Vector)
+{
+ m_circle(x, y, z, v.x);
+ (nil, d) := m_vertex(x, y, z);
+ (nil, e) := m_vertex(x, y+v.y, z);
+ d -= e;
+ if(d < 0.0)
+ d = -d;
+ ms.vrr = int (ms.my*d);
+}
+
+m_begin(k: int, n: int)
+{
+ ms.ignore = 0;
+ ms.vk = k;
+ ms.ap = array[n+1] of Draw->Point;
+ ms.apn = 0;
+}
+
+m_end()
+{
+ if(ms.ignore)
+ return;
+ case(ms.vk){
+ CIRCLE =>
+ RDisp.ellipse(ms.ap[0], ms.vr, ms.vr, 0, ms.vc, (0, 0));
+ FILLCIRCLE =>
+ RDisp.fillellipse(ms.ap[0], ms.vr, ms.vr, ms.vc, (0, 0));
+ ELLIPSE =>
+ RDisp.ellipse(ms.ap[0], ms.vr, ms.vrr, 0, ms.vc, (0, 0));
+ FILLELLIPSE =>
+ RDisp.fillellipse(ms.ap[0], ms.vr, ms.vrr, ms.vc, (0, 0));
+ POLY =>
+ ms.ap[len ms.ap-1] = ms.ap[0];
+ RDisp.poly(ms.ap, Draw->Endsquare, Draw->Endsquare, 0, ms.vc, (0, 0));
+ FILLPOLY =>
+ ms.ap[len ms.ap-1] = ms.ap[0];
+ RDisp.fillpoly(ms.ap, ~0, ms.vc, (0, 0));
+ }
+}
+
+m_colour(i: ref Draw->Image)
+{
+ ms.vc = i;
+}
+
+m_viewport(x1: int, y1: int, x2: int, y2: int)
+{
+ ms.mx = real (x2-x1)/2.0;
+ ms.cx = real (x2+x1)/2.0;
+ ms.my = real (y2-y1)/2.0;
+ ms.cy = real (y2+y1)/2.0;
+}
+
+# sys->print("%d %f (%f %f %f) %s\n", ok, λ, 1.0, 2.0*vdot(l.a, l.d), vdot(l.a, l.a)-icubd2, lstring(l));
+
+# sys->print("%d %f (%f %f %f) %s\n", ok, λ, vmuldiv(l.d, l.d, e2), 2.0*vmuldiv(l.a, l.d, e2), vmuldiv(l.a, l.a, e2)-1.0, lstring(l));
+
+# for(lp = lp0 ; lp != nil; lp = tl lp){
+# p := hd lp;
+# (ok, λ, pt) := intersect(l, p);
+# sys->print("%d %x %d %f %s %s %s\n", p.id, tr.pmask, ok, λ, vstring(pt), pstring(p), lstring(l));
+# } \ No newline at end of file
diff --git a/appl/wm/colors.b b/appl/wm/colors.b
new file mode 100644
index 00000000..619cffe0
--- /dev/null
+++ b/appl/wm/colors.b
@@ -0,0 +1,153 @@
+implement Colors;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+ Display, Point, Rect, Image: import draw;
+include "tk.m";
+ tk: Tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+
+Colors: module {
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+display: ref Display;
+top: ref Tk->Toplevel;
+tmpi: ref Image;
+
+task_cfg := array[] of {
+ "panel .c",
+ "label .l -anchor w -text {col:}",
+ "pack .l -fill x",
+ "pack .c -fill both -expand 1",
+ "bind .c <Button-1> {grab set .c; send cmd %X %Y}",
+ "bind .c <ButtonRelease-1> {grab release .c}",
+};
+
+init(ctxt: ref Draw->Context, nil: list of string)
+{
+ spawn init1(ctxt);
+}
+
+init1(ctxt: ref Draw->Context)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+
+ tkclient->init();
+ display = ctxt.display;
+ tmpi = display.newimage(((0,0), (1, 1)), Draw->RGB24, 0, 0);
+
+ titlectl: chan of string;
+ (top, titlectl) = tkclient->toplevel(ctxt, "", "Colors", Tkclient->Appl);
+
+ cmdch := chan of string;
+ tk->namechan(top, cmdch, "cmd");
+
+ for (i := 0; i < len task_cfg; i++)
+ cmd(top, task_cfg[i]);
+ tk->putimage(top, ".c", cmap((256, 256)), nil);
+ cmd(top, "pack propagate . 0");
+ cmd(top, "update");
+ tkclient->onscreen(top, nil);
+ tkclient->startinput(top, "kbd"::"ptr"::nil);
+
+ for(;;) alt {
+ s := <-top.ctxt.kbd =>
+ tk->keyboard(top, s);
+ s := <-top.ctxt.ptr =>
+ tk->pointer(top, *s);
+ c := <-top.ctxt.ctl or
+ c = <-top.wreq or
+ c = <-titlectl =>
+ if(c == "exit")
+ return;
+ e := tkclient->wmctl(top, c);
+ if(e == nil && c[0] == '!'){
+ tk->putimage(top, ".c", cmap(actr(".c").size()), nil);
+ cmd(top, "update");
+ }
+
+ press := <-cmdch =>
+ (nil, toks) := sys->tokenize(press, " ");
+ color((int hd toks, int hd tl toks));
+ }
+}
+
+color(p: Point)
+{
+ r, g, b: int;
+ col: string;
+
+ cr := actr(".c");
+ if(p.in(cr)){
+ p = p.sub(cr.min);
+ p.x = (16*p.x)/cr.dx();
+ p.y = (16*p.y)/cr.dy();
+ (r, g, b) = display.cmap2rgb(16*p.y+p.x);
+ col = string (16*p.y+p.x);
+ }else{
+ tmpi.draw(tmpi.r, display.image, nil, p);
+ data := array[3] of byte;
+ ok := tmpi.readpixels(tmpi.r, data);
+ if(ok != len data)
+ return;
+ (r, g, b) = (int data[2], int data[1], int data[0]);
+ c := display.rgb2cmap(r, g, b);
+ (r1, g1, b1) := display.cmap2rgb(c);
+ if (r == r1 && g == g1 && b == b1)
+ col = string c;
+ else
+ col = "~" + string c;
+ }
+
+ cmd(top, ".l configure -text " +
+ sys->sprint("{col:%s #%.6X r%d g%d b%d}", col, (r<<16)|(g<<8)|b, r, g, b));
+ cmd(top, "update");
+}
+
+cmap(size: Point): ref Image
+{
+ # use writepixels because it's much faster than allocating all those colors.
+ img := display.newimage(((0, 0), size), Draw->CMAP8, 0, 0);
+ if (img == nil){
+ sys->print("colors: cannot make new image: %r\n");
+ return nil;
+ }
+
+ dy := (size.y / 16 + 1);
+ buf := array[size.x * dy] of byte;
+
+ for(y:=0; y<16; y++){
+ for (i := 0; i < size.x; i++)
+ buf[i] = byte (16*y + (16*i)/size.x);
+ for (i = 1; i < dy; i++)
+ buf[size.x*i:] = buf[0:size.x];
+ img.writepixels(((0, (y*size.y)/16), (size.x, ((y+1)*size.y) / 16)), buf);
+ }
+ return img;
+}
+
+actr(w: string): Rect
+{
+ r: Rect;
+ bd := int cmd(top, w + " cget -bd");
+ r.min.x = int cmd(top, w + " cget -actx") + bd;
+ r.min.y = int cmd(top, w + " cget -acty") + bd;
+ r.max.x = r.min.x + int cmd(top, w + " cget -actwidth");
+ r.max.y = r.min.y + int cmd(top, w + " cget -actheight");
+ return r;
+}
+
+cmd(top: ref Tk->Toplevel, cmd: string): string
+{
+ e := tk->cmd(top, cmd);
+ if (e != nil && e[0] == '!')
+ sys->print("colors: tk error on '%s': %s\n", cmd, e);
+ return e;
+}
diff --git a/appl/wm/cprof.b b/appl/wm/cprof.b
new file mode 100644
index 00000000..1d998236
--- /dev/null
+++ b/appl/wm/cprof.b
@@ -0,0 +1,360 @@
+implement Wmcprof;
+
+include "sys.m";
+ sys: Sys;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "draw.m";
+ draw: Draw;
+include "tk.m";
+ tk: Tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+include "arg.m";
+ arg: Arg;
+include "profile.m";
+
+Prof: module{
+ init0: fn(ctxt: ref Draw->Context, argv: list of string): Profile->Coverage;
+};
+
+prof: Prof;
+
+Wmcprof: module{
+ init: fn(ctxt: ref Draw->Context, argl: list of string);
+};
+
+usage(s: string)
+{
+ sys->fprint(sys->fildes(2), "wm/cprof: %s\n", s);
+ sys->fprint(sys->fildes(2), "usage: wm/cprof [-efr] [-m modname]... cmd [arg ... ]");
+ exit;
+}
+
+TXTBEGIN: con 3;
+
+freq: int;
+
+init(ctxt: ref Draw->Context, argl: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ arg = load Arg Arg->PATH;
+
+ if(ctxt == nil)
+ fatal("wm not running");
+ sys->pctl(Sys->NEWPGRP, nil);
+
+ arg->init(argl);
+ while((o := arg->opt()) != 0){
+ case(o){
+ 'e' or 'r' =>
+ ;
+ 'f' =>
+ freq = 1;
+ 'm' =>
+ if(arg->arg() == nil)
+ usage("missing module/file");
+ * =>
+ usage(sys->sprint("unknown option -%c", o));
+ }
+ }
+
+ cover := execprof(ctxt, argl);
+
+ tkclient->init();
+ (win, wmc) := tkclient->toplevel(ctxt, nil, hd argl, Tkclient->Resize|Tkclient->Hide);
+ tkc := chan of string;
+ tk->namechan(win, tkc, "tkc");
+ for(i := 0; i < len wincfg; i++)
+ cmd(win, wincfg[i]);
+ tkclient->onscreen(win, nil);
+ tkclient->startinput(win, "kbd"::"ptr"::nil);
+ createmenu(win, cover);
+ curc := 0;
+ curm := newprint(win, cover, curc);
+
+ for(;;){
+ alt{
+ c := <-win.ctxt.kbd =>
+ tk->keyboard(win, c);
+ c := <-win.ctxt.ptr =>
+ tk->pointer(win, *c);
+ c := <-win.ctxt.ctl or
+ c = <-win.wreq or
+ c = <-wmc =>
+ tkclient->wmctl(win, c);
+ c := <- tkc =>
+ (nil, toks) := sys->tokenize(c, " ");
+ case(hd toks){
+ "b" =>
+ if(curc > 0)
+ curm = newprint(win, cover, --curc);
+ "f" =>
+ if(curc < len cover - 1)
+ curm = newprint(win, cover, ++curc);
+ "s" =>
+ if(curm != nil)
+ scroll(win, curm);
+ "m" =>
+ x := cmd(win, ".f cget actx");
+ y := cmd(win, ".f cget acty");
+ cmd(win, ".f.menu post " + x + " " + y);
+ * =>
+ curc = int hd toks;
+ curm = newprint(win, cover, curc);
+ }
+ }
+ }
+}
+
+execprof(ctxt: ref Draw->Context, argl: list of string): Profile->Coverage
+{
+ {
+ prof = load Prof "/dis/cprof.dis";
+ if(prof == nil)
+ fatal("cannot load profiler");
+ return prof->init0(ctxt, hd argl :: "-g" :: tl argl);
+ }
+ exception{
+ "fail:*" =>
+ return nil;
+ }
+ return nil;
+}
+
+maxf(rs: list of (int, int, int)): int
+{
+ fmax := 0;
+ for(r := rs; r != nil; r = tl r){
+ (nil, nil, f) := hd r;
+ if(f > fmax)
+ fmax = f;
+ }
+ return fmax;
+}
+
+print(win: ref Tk->Toplevel, cvr: Profile->Coverage, i: int, c: chan of Profile->Coverage)
+{
+ cmd(win, ".f.t delete 1.0 end");
+ cmd(win, "update");
+ m0, m1: Profile->Coverage;
+ for(m := cvr; m != nil && --i >= 0; m = tl m)
+ m0 = m;
+ if(m == nil){
+ c <- = nil;
+ return;
+ }
+ m1 = tl m;
+ (name, cvd, ls) := hd m;
+ name0 := name1 := "nil";
+ if(m0 != nil)
+ (name0, nil, nil) = hd m0;
+ if(m1 != nil)
+ (name1, nil, nil) = hd m1;
+ if(freq){
+ cvd = 0;
+ for(l := ls; l != nil; l = tl l){
+ (rs, nil) := hd l;
+ cvd += maxf(rs);
+ }
+ }
+ else
+ name += sys->sprint(" (%d%% coverage) ", cvd);
+ cmd(win, ".f.t insert end {" + name + " <- " + name0 + " -> " + name1 + "}");
+ cmd(win, ".f.t insert end \n\n");
+ cmd(win, "update");
+ line := TXTBEGIN;
+ for(l := ls; l != nil; l = tl l){
+ tab := 0;
+ (rs, s) := hd l;
+ if(freq){
+ fmax := maxf(rs);
+ s = string fmax + "\t" + s;
+ tab = len string fmax + 1;
+ }
+ cmd(win, ".f.t insert end " + tk->quote(s));
+ for(r := rs; r != nil; r = tl r){
+ tag: string;
+ (a, b, e) := hd r;
+ if(freq){
+ tag = gettag(win, e, cvd);
+ a += tab;
+ b += tab;
+ }
+ else{
+ if(int e) # partly executed
+ tag = "halfexec";
+ else
+ tag = "notexec";
+ }
+ cmd(win, ".f.t tag add " + tag + " " + string line + "." + string a + " " + string line + "." + string b);
+ }
+ cmd(win, "update");
+ line++;
+ }
+ c <- = m;
+}
+
+newprint(win: ref Tk->Toplevel, cvr: Profile->Coverage, i: int): Profile->Coverage
+{
+ c := chan of Profile->Coverage;
+ spawn print(win, cvr, i, c);
+ return <- c;
+}
+
+index(win: ref Tk->Toplevel, x: int, y: int): int
+{
+ t := cmd(win, ".f.t index @" + string x + "," + string y);
+ (nil, l) := sys->tokenize(t, ".");
+# sys->print("%d,%d -> %s\n", x, y, t);
+ return int hd l;
+}
+
+winextent(win: ref Tk->Toplevel): (int, int)
+{
+ w := int cmd(win, ".f.t cget -actwidth");
+ h := int cmd(win, ".f.t cget -actheight");
+ lw := index(win, 0, 0);
+ uw := index(win, w-1, h-1);
+ return (lw, uw);
+}
+
+see(win: ref Tk->Toplevel, line: int)
+{
+ cmd(win, ".f.t see " + string line + ".0");
+ cmd(win, "update");
+}
+
+scroll(win: ref Tk->Toplevel, m: Profile->Coverage)
+{
+ (nil, cvd, ls) := hd m;
+ if(freq)
+ cvd = 0;
+ (nil, uw) := winextent(win);
+ line := TXTBEGIN;
+ for(l := ls; l != nil; l = tl l){
+ (rs, nil) := hd l;
+ if(rs != nil && line > uw){
+ see(win, line);
+ return;
+ }
+ line++;
+ }
+ if(cvd < 100){
+ line = TXTBEGIN;
+ for(l = ls; l != nil; l = tl l){
+ (rs, nil) := hd l;
+ if(rs != nil){
+ see(win, line);
+ return;
+ }
+ line++;
+ }
+ }
+ return;
+}
+
+cmd(top: ref Tk->Toplevel, s: string): string
+{
+ # sys->print("%s\n", s);
+ e := tk->cmd(top, s);
+ if (e != nil && e[0] == '!')
+ sys->fprint(sys->fildes(2), "tk error on '%s': %s\n", s, e);
+ return e;
+}
+
+fatal(s: string)
+{
+ sys->fprint(sys->fildes(2), "%s\n", s);
+ exit;
+}
+
+MENUMAX: con 20;
+
+createmenu(top: ref Tk->Toplevel, cvr: Profile->Coverage )
+{
+ mn := ".f.menu";
+ cmd(top, "menu " + mn);
+ i := j := 0;
+ for(m := cvr; m != nil; m = tl m){
+ (name, nil, nil) := hd m;
+ cmd(top, mn + " add command -label " + name + " -command {send tkc " + string i + "}");
+ i++;
+ j++;
+ if(j == MENUMAX && tl m != nil){
+ cmd(top, mn + " add cascade -label MORE -menu " + mn + ".menu");
+ mn += ".menu";
+ cmd(top, "menu " + mn);
+ j = 0;
+ }
+ }
+}
+
+SNT: con 16;
+NT: con SNT*SNT;
+NTF: con 256/SNT;
+
+tags := array[NT] of { * => byte 0 };
+
+gettag(win: ref Tk->Toplevel, n: int, d: int): string
+{
+ i := int ((real n/real d) * real (NT-1));
+ if(i < 0 || i > NT-1)
+ i = 0;
+ s := "tag" + string i;
+ if(tags[i] == byte 0){
+ rgb := "#" + hex2(255-NTF*0)+hex2(255-NTF*(i/SNT))+hex2(255-NTF*(i%SNT));
+ cmd(win, ".f.t tag configure " + s + " -fg black -bg " + rgb);
+ tags[i] = byte 1;
+ }
+ return s;
+}
+
+hex(i: int): int
+{
+ if(i < 10)
+ return i+'0';
+ else
+ return i-10+'A';
+}
+
+hex2(i: int): string
+{
+ s := "00";
+ s[0] = hex(i/16);
+ s[1] = hex(i%16);
+ return s;
+}
+
+wincfg := array[] of {
+ "frame .f",
+ "text .f.t -width 809 -height 500 -state disabled -wrap char -bg white -yscrollcommand {.f.s set}",
+ "scrollbar .f.s -orient vertical -command {.f.t yview}",
+ "frame .i",
+ "button .i.b -bitmap small_color_left.bit -command {send tkc b}",
+ "button .i.f -bitmap small_color_right.bit -command {send tkc f}",
+ "button .i.s -bitmap small_find.bit -command {send tkc s}",
+ "button .i.m -bitmap small_reload.bit -command {send tkc m}",
+
+ "pack .i.b -side left",
+ "pack .i.f -side left",
+ "pack .i.s -side left",
+ "pack .i.m -side left",
+
+ "pack .f.s -fill y -side left",
+ "pack .f.t -fill both -expand 1",
+
+ "pack .i -fill x",
+ "pack .f -fill both -expand 1",
+ "pack propagate . 0",
+
+ ".f.t tag configure notexec -fg white -bg red",
+ ".f.t tag configure halfexec -fg red -bg white",
+
+ "update",
+}; \ No newline at end of file
diff --git a/appl/wm/date.b b/appl/wm/date.b
new file mode 100644
index 00000000..72278b7a
--- /dev/null
+++ b/appl/wm/date.b
@@ -0,0 +1,78 @@
+implement WmDate;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "daytime.m";
+ daytime: Daytime;
+
+
+WmDate: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+tpid: int;
+
+init(ctxt: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "date: no window context\n");
+ raise "fail:bad context";
+ }
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient= load Tkclient Tkclient->PATH;
+ daytime = load Daytime Daytime->PATH;
+
+ sys->pctl(Sys->NEWPGRP, nil);
+ tkclient->init();
+ (t, wmctl) := tkclient->toplevel(ctxt, "", "Date", 0);
+
+ st := daytime->time()[0:19];
+ tk->cmd(t, "label .d -label {"+st+"}");
+ tk->cmd(t, "pack .d; pack propagate . 0");
+ tkclient->onscreen(t, nil);
+ tkclient->startinput(t, "ptr"::nil);
+ tick := chan of int;
+ spawn timer(tick);
+
+ for(;;) alt {
+ s := <-t.ctxt.kbd =>
+ tk->keyboard(t, s);
+ s := <-t.ctxt.ptr =>
+ tk->pointer(t, *s);
+ s := <-t.ctxt.ctl or
+ s = <-t.wreq or
+ s = <-wmctl =>
+ tkclient->wmctl(t, s);
+ <-tick =>
+ tk->cmd(t, ".d configure -label {"+daytime->time()[0:19]+"};update");
+ }
+}
+
+timer(c: chan of int)
+{
+ tpid = sys->pctl(0, nil);
+ for(;;) {
+ c <-= 1;
+ sys->sleep(1000);
+ }
+}
+
+kill(pid: int)
+{
+ fd := sys->open("#p/"+string pid+"/ctl", sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "kill");
+}
diff --git a/appl/wm/deb.b b/appl/wm/deb.b
new file mode 100644
index 00000000..fa8208b0
--- /dev/null
+++ b/appl/wm/deb.b
@@ -0,0 +1,1444 @@
+implement WmDebugger;
+
+include "sys.m";
+ sys: Sys;
+ stderr: ref Sys->FD;
+
+include "string.m";
+ str: String;
+
+include "arg.m";
+ arg: Arg;
+
+include "readdir.m";
+ readdir: Readdir;
+
+include "draw.m";
+ draw: Draw;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "dialog.m";
+ dialog: Dialog;
+
+include "selectfile.m";
+ selectfile: Selectfile;
+
+include "tabs.m";
+ tabs: Tabs;
+
+include "debug.m";
+ debug: Debug;
+ Prog, Exp, Module, Src, Sym: import debug;
+
+include "wmdeb.m";
+ debdata: DebData;
+ Vars: import debdata;
+ debsrc: DebSrc;
+ opendir, Mod: import debsrc;
+
+WmDebugger: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+icondir : con "debug/";
+
+tkconfig := array[] of {
+ "frame .m -relief raised -bd 1",
+ "frame .p -padx 2",
+ "frame .ctls -padx 2",
+ "frame .body",
+
+ # menu bar
+ "menubutton .m.file -text File -menu .m.file.menu",
+ "menubutton .m.search -text Search -menu .m.search.menu",
+ "button .m.stack -text Stack -command {send m stack}",
+ "pack .m.file .m.search .m.stack -side left",
+
+ # file menu
+ "menu .m.file.menu",
+ ".m.file.menu add command -label Open... -command {send m open}",
+ ".m.file.menu add command -label Thread... -command {send m pickup}",
+ ".m.file.menu add command -label Options... -command {send m options}",
+ ".m.file.menu add separator",
+
+ # search menu
+ "menu .m.search.menu",
+ ".m.search.menu add command -state disabled"+
+ " -label Look -command {send m look}",
+ ".m.search.menu add command -state disabled"+
+ " -label {Search For} -command {send m search}",
+
+ # program control
+ "image create bitmap Detach -file "+icondir+
+ "detach.bit -maskfile "+icondir+"detach.mask",
+ "image create bitmap Kill -file "+icondir+
+ "kill.bit -maskfile "+icondir+"kill.mask",
+ "image create bitmap Run -file "+icondir+
+ "run.bit -maskfile "+icondir+"run.mask",
+ "image create bitmap Stop -file "+icondir+
+ "stop.bit -maskfile "+icondir+"stop.mask",
+ "image create bitmap Bpt -file "+icondir+
+ "break.bit -maskfile "+icondir+"break.mask",
+ "image create bitmap Stepop -file "+icondir+
+ "stepop.bit -maskfile "+icondir+"stepop.mask",
+ "image create bitmap Stepin -file "+icondir+
+ "stepin.bit -maskfile "+icondir+"stepin.mask",
+ "image create bitmap Stepout -file "+icondir+
+ "stepout.bit -maskfile "+icondir+"stepout.mask",
+ "image create bitmap Stepover -file "+icondir+
+ "stepover.bit -maskfile "+icondir+"stepover.mask",
+ "button .p.kill -image Kill -command {send m killall}"+
+ " -state disabled -relief sunken",
+ "bind .p.kill <Enter> +{.p.status configure -text {kill current process}}",
+ "bind .p.kill <Leave> +{.p.status configure -text {}}",
+ "button .p.detach -image Detach -command {send m detach}"+
+ " -state disabled -relief sunken",
+ "bind .p.detach <Enter> +{.p.status configure -text {stop debugging current process}}",
+ "bind .p.detach <Leave> +{.p.status configure -text {}}",
+ "button .p.run -image Run -command {send m run}"+
+ " -state disabled -relief sunken",
+ "bind .p.run <Enter> +{.p.status configure -text {run to breakpoint}}",
+ "bind .p.run <Leave> +{.p.status configure -text {}}",
+ "button .p.step -image Stepop -command {send m step}"+
+ " -state disabled -relief sunken",
+ "bind .p.step <Enter> +{.p.status configure -text {step one operation}}",
+ "bind .p.step <Leave> +{.p.status configure -text {}}",
+ "button .p.stmt -image Stepin -command {send m stmt}"+
+ " -state disabled -relief sunken",
+ "bind .p.stmt <Enter> +{.p.status configure -text {step one statement}}",
+ "bind .p.stmt <Leave> +{.p.status configure -text {}}",
+ "button .p.over -image Stepover -command {send m over}"+
+ " -state disabled -relief sunken",
+ "bind .p.over <Enter> +{.p.status configure -text {step over calls}}",
+ "bind .p.over <Leave> +{.p.status configure -text {}}",
+ "button .p.out -image Stepout -command {send m out}"+
+ " -state disabled -relief sunken",
+ "bind .p.out <Enter> +{.p.status configure -text {step out of fn}}",
+ "bind .p.out <Leave> +{.p.status configure -text {}}",
+ "button .p.bpt -image Bpt -command {send m setbpt}"+
+ " -state disabled -relief sunken",
+ "bind .p.bpt <Enter> +{.p.status configure -text {set/clear breakpoint}}",
+ "bind .p.bpt <Leave> +{.p.status configure -text {}}",
+ "frame .p.steps",
+ "label .p.status -anchor w",
+ "pack .p.step .p.stmt .p.over .p.out -in .p.steps -side left -fill y",
+ "pack .p.kill .p.detach .p.run .p.steps .p.bpt -side left -padx 5 -fill y",
+ "pack .p.status -side left -expand 1 -fill x",
+
+ # progs
+ "frame .prog",
+ "label .prog.l -text Threads",
+ "canvas .prog.d -height 1 -width 1 -relief sunken -bd 2",
+ "frame .prog.v",
+ ".prog.d create window 0 0 -window .prog.v -anchor nw",
+ "pack .prog.l -side top -anchor w",
+ "pack .prog.d -side left -fill both -expand 1",
+
+ # breakpoints
+ "frame .bpt",
+ "label .bpt.l -text Break",
+ "canvas .bpt.d -height 1 -width 1 -relief sunken -bd 2",
+ "frame .bpt.v",
+ ".bpt.d create window 0 0 -window .bpt.v -anchor nw",
+ "pack .bpt.l -side top -anchor w",
+ "pack .bpt.d -side left -fill both -expand 1",
+
+ "pack .prog .bpt -side top -fill both -expand 1 -in .ctls",
+
+ # test body
+ "frame .body.ft -bd 1 -relief sunken -width 60w -height 20h",
+ "scrollbar .body.scy",
+ "pack .body.scy -side right -fill y",
+
+ "pack .body.ft -side top -expand 1 -fill both",
+ "pack propagate .body.ft 0",
+
+ "pack .m .p -side top -fill x",
+ "pack .ctls -side left -fill y",
+
+ "scrollbar .body.scx -orient horizontal",
+ "pack .body.scx -side bottom -fill x",
+
+ "pack .body -expand 1 -fill both",
+
+ "pack propagate . 0",
+
+ "raise .; update; cursor -default"
+};
+
+# commands for disabling or enabling buttons
+searchoff := array[] of {
+ ".m.search.menu entryconfigure 0 -state disabled",
+ ".m.search.menu entryconfigure 1 -state disabled",
+ ".m.search.menu entryconfigure 2 -state disabled",
+};
+searchon := array[] of {
+ ".m.search.menu entryconfigure 0 -state normal",
+ ".m.search.menu entryconfigure 1 -state normal",
+ ".m.search.menu entryconfigure 2 -state normal",
+};
+tkstopped := array[] of {
+ ".p.bpt configure -state normal -relief raised",
+ ".p.detach configure -state normal -relief raised",
+ ".p.kill configure -state normal -relief raised",
+ ".p.out configure -state normal -relief raised",
+ ".p.over configure -state normal -relief raised",
+ ".p.run configure -state normal -relief raised -image Run -command {send m run}",
+ ".p.step configure -state normal -relief raised",
+ ".p.stmt configure -state normal -relief raised",
+};
+tkrunning := array[] of {
+ ".p.bpt configure -state normal -relief raised",
+ ".p.detach configure -state normal -relief raised",
+ ".p.kill configure -state normal -relief raised",
+ ".p.out configure -state disabled -relief sunken",
+ ".p.over configure -state disabled -relief sunken",
+ ".p.run configure -state normal -relief raised -image Stop -command {send m stop}",
+ ".p.step configure -state disabled -relief sunken",
+ ".p.stmt configure -state disabled -relief sunken",
+};
+tkexited := array[] of {
+ ".p.bpt configure -state normal -relief raised",
+ ".p.detach configure -state normal -relief raised",
+ ".p.kill configure -state normal -relief raised",
+ ".p.out configure -state disabled -relief sunken",
+ ".p.over configure -state disabled -relief sunken",
+ ".p.run configure -state disabled -relief sunken -image Run -command {send m run}",
+ ".p.step configure -state disabled -relief sunken",
+ ".p.stmt configure -state disabled -relief sunken",
+ ".p.stop configure -state disabled -relief sunken",
+};
+tkloaded := array[] of {
+ ".p.bpt configure -state normal -relief raised",
+ ".p.detach configure -state disabled -relief sunken",
+ ".p.kill configure -state disabled -relief sunken",
+ ".p.out configure -state disabled -relief sunken",
+ ".p.over configure -state disabled -relief sunken",
+ ".p.run configure -state normal -relief raised -image Run -command {send m run}",
+ ".p.step configure -state disabled -relief sunken",
+ ".p.stmt configure -state disabled -relief sunken",
+};
+tknobody := array[] of {
+ ".p.bpt configure -state disabled -relief sunken",
+ ".p.detach configure -state disabled -relief sunken",
+ ".p.kill configure -state disabled -relief sunken",
+ ".p.out configure -state disabled -relief sunken",
+ ".p.over configure -state disabled -relief sunken",
+ ".p.run configure -state disabled -relief sunken -image Run -command {send m run}",
+ ".p.step configure -state disabled -relief sunken",
+ ".p.stmt configure -state disabled -relief sunken",
+};
+
+#tk option dialog
+tkoptpack := array[] of {
+ "frame .buts",
+
+ "pack .opts -side left -padx 10 -pady 5",
+};
+
+tkoptions := array[] of {
+ # general options
+ "frame .gen",
+ "frame .mod",
+ "label .modlab -text 'Source of executable module",
+ "entry .modent",
+ "pack .modlab -in .mod -anchor w",
+ "pack .modent -in .mod -fill x",
+
+ "frame .arg",
+ "label .arglab -text 'Program Arguments",
+ "entry .argent -width 300",
+ "pack .arglab -in .arg -anchor w",
+ "pack .argent -in .arg -fill x",
+
+ "frame .wd",
+ "label .wdlab -text 'Working Directory",
+ "entry .wdent",
+ "pack .wdlab -in .wd -anchor w",
+ "pack .wdent -in .wd -fill x",
+
+ "pack .mod .arg .wd -fill x -anchor w -pady 10 -in .gen",
+
+ # thread control options
+ "frame .prog",
+ "frame .new",
+ "radiobutton .new.run -variable new -value r -text 'Run new threads",
+ "radiobutton .new.block -variable new -value b -text 'Block new threads",
+ "pack .new.block .new.run -anchor w",
+ "frame .x",
+ "radiobutton .x.kill -variable exit -value k -text 'Kill threads on exit",
+ "radiobutton .x.detach -variable exit -value d -text 'Detach threads on exit",
+ "pack .x.kill .x.detach -anchor w",
+ "pack .new .x -expand 1 -anchor w -in .prog",
+
+ # layout options
+ "frame .layout",
+ "frame .line",
+ "radiobutton .line.wrap -variable wrap -value w -text 'Wrap lines",
+ "radiobutton .line.scroll -variable wrap -value s -text 'Horizontal scroll",
+ "pack .line.wrap .line.scroll -anchor w",
+ "frame .crlf",
+ "radiobutton .crlf.no -variable crlf -value n -text 'CR/LF as is",
+ "radiobutton .crlf.yes -variable crlf -value y -text 'CR/LF -> LF",
+ "pack .crlf.no .crlf.yes -anchor w",
+ "pack .line .crlf -expand 1 -anchor w -in .layout",
+};
+
+tkopttabs := array[] of {
+ ("General", ".gen"),
+ ("Thread", ".prog"),
+ ("Layout", ".layout"),
+};
+
+# prog listing dialog box
+tkpicktab := array[] of {
+ "frame .progs",
+ "scrollbar .progs.s -command '.progs.p yview",
+ "listbox .progs.p -width 35w -yscrollcommand '.progs.s set",
+ "bind .progs.p <Double-Button-1> 'send cmd prog",
+ "pack .progs.s -side right -fill y",
+ "pack .progs.p -fill both -expand 1",
+
+ "frame .buts",
+ "button .buts.prog -text {Add Thread} -command 'send cmd prog",
+ "button .buts.grp -text {Add Group} -command 'send cmd group",
+ "pack .buts.prog .buts.grp -expand 1 -side left -fill x -padx 4 -pady 4",
+
+ "pack .progs -fill both -expand 1",
+ "pack .buts -fill x",
+ "pack propagate . 0",
+};
+
+Bpt: adt
+{
+ id: int;
+ m: ref Mod;
+ pc: int;
+};
+
+Recv, Send, Alt, Running, Stopped, Exited, Broken, Killing, Killed: con iota;
+status := array[] of
+{
+ Running => "Running",
+ Recv => "Receive",
+ Send => "Send",
+ Alt => "Alt",
+ Stopped => "Stopped",
+ Exited => "Exited",
+ Broken => "Broken",
+ Killing => "Killed",
+ Killed => "Killed",
+};
+
+tktools : array of array of string;
+toolstate : array of string;
+
+KidGrab, KidStep, KidStmt, KidOver, KidOut, KidKill, KidRun: con iota;
+Kid: adt
+{
+ state: int;
+ prog: ref Prog;
+ watch: int; # pid of watching prog
+ run: int; # pid of stepping prog
+ pickup: int; # picking up this kid?
+ cmd: chan of int;
+ stack: ref Vars;
+};
+
+Options: adt
+{
+ start: string; # src of module to start
+ mod: ref Mod; # module to start
+ wm: int; # program is a wm program?
+ path: array of string;# search path for .src and .sbl
+ args: list of string; # argument for starting a kid
+ dir: string; # . for kid
+ tabs: int; # options to show
+ nrun: int; # run new kids?
+ xkill: int; # kill kids on exit?
+ xscroll: int; # horizontal scrolling
+ remcr: int; # CR/LF -> LF
+};
+
+tktop: ref Tk->Toplevel;
+kids: list of ref Kid;
+kid: ref Kid;
+kidctxt: ref Draw->Context;
+kidack: chan of (ref Kid, string);
+kidevent: chan of (ref Kid, string);
+bpts: list of ref Bpt;
+bptid:= 1;
+title: string;
+runok := 0;
+context: ref Draw->Context;
+opts: ref Options;
+dbpid: int;
+searchfor: string;
+initsrc: string;
+
+badmodule(p: string)
+{
+ sys->fprint(sys->fildes(2), "deb: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "deb: no window context\n");
+ raise "fail:bad context";
+ }
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ if(tkclient == nil)
+ badmodule(Tkclient->PATH);
+ selectfile = load Selectfile Selectfile->PATH;
+ if(selectfile == nil)
+ badmodule(Selectfile->PATH);
+ dialog = load Dialog Dialog->PATH;
+ if(dialog == nil)
+ badmodule(Dialog->PATH);
+ tabs = load Tabs Tabs->PATH;
+ if(tabs == nil)
+ badmodule(Tabs->PATH);
+ str = load String String->PATH;
+ if(str == nil)
+ badmodule(String->PATH);
+ readdir = load Readdir Readdir->PATH;
+ if(readdir == nil)
+ badmodule(Readdir->PATH);
+ debug = load Debug Debug->PATH;
+ if(debug == nil)
+ badmodule(Debug->PATH);
+ debdata = load DebData DebData->PATH;
+ if(debdata == nil)
+ badmodule(DebData->PATH);
+ debsrc = load DebSrc DebSrc->PATH;
+ if(debsrc == nil)
+ badmodule(DebSrc->PATH);
+ arg = load Arg Arg->PATH;
+ if(arg == nil)
+ badmodule(Arg->PATH);
+ dbpid = sys->pctl(Sys->NEWPGRP, nil);
+ opts = ref Options;
+ opts.tabs = 0;
+ opts.nrun = 0;
+ opts.xkill = 1;
+ opts.xscroll = 0;
+ opts.remcr = 0;
+ readopts(opts);
+ sysnam := sysname();
+ context = ctxt;
+
+ grabpids: list of int;
+ arg->init(argv);
+ arg->setusage("wmdeb [-p pid]");
+ while((opt := arg->opt()) != 0){
+ case opt {
+ 'f' =>
+ initsrc = arg->earg();
+ 'p' =>
+ grabpids = int arg->earg() :: grabpids;
+ * =>
+ arg->usage();
+ }
+ }
+ for(argv = arg->argv(); argv != nil; argv = tl argv)
+ grabpids = int hd argv :: grabpids;
+ arg = nil;
+
+ pickdummy := chan of int;
+ pickchan := pickdummy;
+ optdummy := chan of ref Options;
+ optchan := optdummy;
+
+ tktools = array[] of {
+ Running => tkrunning,
+ Recv => tkrunning,
+ Send => tkrunning,
+ Alt => tkrunning,
+ Stopped => tkstopped,
+ Exited => tkexited,
+ Broken => tkexited,
+ Killing => tkexited,
+ Killed => tkexited,
+ };
+
+
+ tkclient->init();
+ selectfile->init();
+ dialog->init();
+ tabs->init();
+
+ title = sysnam+":Wmdeb";
+ titlebut := chan of string;
+ (tktop, titlebut) = tkclient->toplevel(context, nil, title, Tkclient->Appl);
+ tkcmd("cursor -bitmap cursor.wait");
+
+ debug->init();
+ kidctxt = ctxt;
+
+ stderr = sys->fildes(2);
+
+ debsrc->init(context, tktop, tkclient, selectfile, dialog, str, debug, opts.xscroll, opts.remcr);
+ (datatop, datactl, datatitle) := debdata->init(context, nil, debsrc, str, debug);
+
+ m := chan of string;
+ tk->namechan(tktop, m, "m");
+ toolstate = tknobody;
+ tkcmds(tktop, tkconfig);
+ if(!opts.xscroll){
+ tkcmd("pack forget .body.scx");
+ tkcmd("pack .body -expand 1 -fill both; update");
+ }
+
+ tkcmd("cursor -default");
+ tkclient->onscreen(tktop, nil);
+ tkclient->startinput(tktop, "kbd" :: "ptr" :: nil);
+
+ kids = nil;
+ kid = nil;
+ kidack = chan of (ref Kid, string);
+ kidevent = chan of (ref Kid, string);
+
+ # pick up a src file, a kid?
+ if(initsrc != nil)
+ open1(initsrc);
+ else if(grabpids != nil)
+ for(; grabpids != nil; grabpids = tl grabpids)
+ pickup(hd grabpids);
+
+ for(exiting := 0; !exiting || kids != nil; ){
+ tkcmd("update");
+ alt {
+ c := <-tktop.ctxt.kbd =>
+ tk->keyboard(tktop, c);
+ p := <-tktop.ctxt.ptr =>
+ tk->pointer(tktop, *p);
+ s := <-tktop.ctxt.ctl or
+ s = <-tktop.wreq or
+ s = <-titlebut =>
+ case s{
+ "exit" =>
+ if(!exiting){
+ if(opts.xkill)
+ killkids();
+ else
+ detachkids();
+ tkcmd("destroy .");
+ }
+ exiting = 1;
+ break;
+ "task" =>
+ spawn task(tktop);
+ * =>
+ tkclient->wmctl(tktop, s);
+ }
+ c := <-datatop.ctxt.kbd =>
+ tk->keyboard(datatop, c);
+ p := <-datatop.ctxt.ptr =>
+ tk->pointer(datatop, *p);
+ s := <-datactl =>
+ debdata->ctl(s);
+ s := <-datatop.wreq or
+ s = <-datatop.ctxt.ctl or
+ s = <-datatitle =>
+ case s{
+ "task" =>
+ spawn debdata->wmctl(s);
+ * =>
+ debdata->wmctl(s);
+ }
+ o := <-optchan =>
+ if(o != nil && checkopts(o))
+ opts = o;
+ optchan = optdummy;
+ p := <-pickchan =>
+ if(p < 0){
+ pickchan = pickdummy;
+ break;
+ }
+ k := pickup(p);
+ if(k != nil && k != kid){
+ kid = k;
+ refresh(k);
+ }
+ s := <-m =>
+ case s {
+ "open" =>
+ open();
+ "pickup" =>
+ if(pickchan == pickdummy){
+ pickchan = chan of int;
+ spawn pickprog(pickchan);
+ }
+ "options" =>
+ if(optchan == optdummy){
+ optchan = chan of ref Options;
+ spawn options(opts, optchan);
+ }
+ "step" =>
+ step(kid, KidStep);
+ "over" =>
+ step(kid, KidOver);
+ "out" =>
+ step(kid, KidOut);
+ "stmt" =>
+ step(kid, KidStmt);
+ "run" =>
+ step(kid, KidRun);
+ "stop" =>
+ if(kid != nil)
+ kid.prog.stop();
+ "killall" =>
+ killkids();
+ "kill" =>
+ killkid(kid);
+ "detach" =>
+ detachkid(kid);
+ "setbpt" =>
+ setbpt();
+ "look" =>
+ debsrc->search(debsrc->snarf());
+ "search" =>
+ s = dialog->getstring(context, tktop.image, "Search For");
+ if(s == ""){
+ tkcmd(".m.search.menu delete 2");
+ }else{
+ if(searchfor == "")
+ tkcmd(".m.search.menu add command -command {send m research}");
+ tkcmd(".m.search.menu entryconfigure 2 -label '/"+s);
+ debsrc->search(s);
+ }
+ searchfor = s;
+ "research" =>
+ debsrc->search(searchfor);
+ "stack" =>
+ if(debdata != nil)
+ debdata->raisex();
+ * =>
+ if(str->prefix("open ", s))
+ debsrc->showstrsrc(s[len "open ":]);
+ else if(str->prefix("seeprog ", s))
+ seekid(int s[len "seeprog ":]);
+ else if(str->prefix("seebpt ", s))
+ seebpt(int s[len "seebpt ":]);
+ }
+ (k, s) := <-kidevent =>
+ case s{
+ "recv" =>
+ if(k.state == Running)
+ k.state = Recv;
+ "send" =>
+ if(k.state == Running)
+ k.state = Send;
+ "alt" =>
+ if(k.state == Running)
+ k.state = Alt;
+ "run" =>
+ if(k.state == Recv || k.state == Send || k.state == Alt)
+ k.state = Running;
+ "exited" =>
+ k.state = Exited;
+ "interrupted" or
+ "killed" =>
+ alert("Thread "+string k.prog.id+" "+s);
+ k.state = Exited;
+ * =>
+ if(str->prefix("new ", s)){
+ nk := newkid(int s[len "new ":]);
+ if(opts.nrun)
+ step(nk, KidRun);
+ break;
+ }
+ if(str->prefix("load ", s)){
+ s = s[len "load ":];
+ if(s != nil && s[0] != '$')
+ loaded(s);
+ break;
+ }
+ if(str->prefix("child: ", s))
+ s = s[len "child: ":];
+
+ if(str->prefix("broken: ", s))
+ k.state = Broken;
+ alert("Thread "+string k.prog.id+" "+s);
+ }
+ if(k == kid && k.state != Running)
+ refresh(k);
+ k = nil;
+ (k, s) := <-kidack =>
+ if(k.state == Killing){
+ k.state = Killed;
+ k.cmd <-= KidKill;
+ k = nil;
+ break;
+ }
+ if(k.state == Killed){
+ delkid(k);
+ k = nil;
+ break;
+ }
+ case s{
+ "" or "child: breakpoint" or "child: stopped" =>
+ k.state = Stopped;
+ k.prog.unstop();
+ "prog broken" =>
+ k.state = Broken;
+ * =>
+ if(!str->prefix("child: ", s))
+ alert("Debugger error "+status[k.state]+" "+string k.prog.id+" '"+s+"'");
+ }
+ if(k == kid)
+ refresh(k);
+ if(k.pickup && opts.nrun){
+ k.pickup = 0;
+ if(k.state == Stopped)
+ step(k, KidRun);
+ }
+ k = nil;
+ }
+ }
+ exitdb();
+}
+
+task(top: ref Tk->Toplevel)
+{
+ tkclient->wmctl(top, "task");
+}
+
+open()
+{
+ pattern := list of {
+ "*.b (Limbo source files)",
+ "* (All files)"
+ };
+
+ file := selectfile->filename(context, tktop.image, "Open source file", pattern, opendir);
+ if(file != nil)
+ open1(file);
+}
+
+open1(file: string)
+{
+ (opendir, nil) = str->splitr(file, "/");
+ if(opendir == "")
+ opendir = ".";
+ m := debsrc->loadsrc(file, 1);
+ if(m == nil){
+ alert("Can't open "+file);
+ return;
+ }
+ debsrc->showmodsrc(m, ref Src((file, 1, 0), (file, 1, 0)));
+ kidstate();
+ if(opts.start == nil){
+ opts.start = file;
+ opts.mod = m;
+ }
+ if(opts.dir == "")
+ opts.dir = opendir;
+}
+
+options(oo: ref Options, r: chan of ref Options)
+{
+ (t, titlebut) := tkclient->toplevel(context, nil, "Wmdeb Options", tkclient->OK);
+
+ tkcmds(t, tkoptions);
+ tabsctl := tabs->mktabs(t, ".opts", tkopttabs, oo.tabs);
+ tkcmds(t, tkoptpack);
+
+ o := ref *oo;
+ if(o.start != nil)
+ tk->cmd(t, ".modent insert end '"+o.start);
+ args := "";
+ for(oa := o.args; oa != nil; oa = tl oa){
+ if(args == "")
+ args = hd oa;
+ else
+ args += " " + hd oa;
+ }
+ tk->cmd(t, ".argent insert end '"+args);
+ tk->cmd(t, ".wdent insert end '"+o.dir);
+ if(o.xkill)
+ tk->cmd(t, ".x.kill invoke");
+ else
+ tk->cmd(t, ".x.detach invoke");
+ if(o.nrun)
+ tk->cmd(t, ".new.run invoke");
+ else
+ tk->cmd(t, ".new.block invoke");
+ if(o.xscroll)
+ tk->cmd(t, ".line.scroll invoke");
+ else
+ tk->cmd(t, ".line.wrap invoke");
+ if(o.remcr)
+ tk->cmd(t, ".crlf.yes invoke");
+ else
+ tk->cmd(t, ".crlf.no invoke");
+
+ tk->cmd(t, ".killkids configure -command 'send cmd kill");
+ tk->cmd(t, ".runkids configure -command 'send cmd run");
+ tkclient->onscreen(t, nil);
+ tkclient->startinput(t, "ptr" :: "kbd" :: nil);
+
+out: for(;;){
+ tk->cmd(t, "update");
+ alt{
+ c := <-t.ctxt.kbd =>
+ tk->keyboard(t, c);
+ m := <-t.ctxt.ptr =>
+ tk->pointer(t, *m);
+ s := <-tabsctl =>
+ o.tabs = tabs->tabsctl(t, ".opts", tkopttabs, o.tabs, s);
+ s := <-t.ctxt.ctl or
+ s = <-t.wreq or
+ s = <-titlebut =>
+ case s{
+ "exit" =>
+ r <-= nil;
+ exit;
+ "ok" =>
+ break out;
+ }
+ tkclient->wmctl(t, s);
+ }
+ }
+ xscroll := o.xscroll;
+ o.start = tk->cmd(t, ".modent get");
+ (nil, o.args) = sys->tokenize(tk->cmd(t, ".argent get"), " \t\n");
+ o.dir = tk->cmd(t, ".wdent get");
+ case tk->cmd(t, "variable new"){
+ "r" => o.nrun = 1;
+ "b" => o.nrun = 0;
+ }
+ case tk->cmd(t, "variable exit"){
+ "k" => o.xkill = 1;
+ "d" => o.xkill = 0;
+ }
+ case tk->cmd(t, "variable wrap"){
+ "s" => o.xscroll = 1;
+ "w" => o.xscroll = 0;
+ }
+ case tk->cmd(t, "variable crlf"){
+ "y" => o.remcr = 1;
+ "n" => o.remcr = 0;
+ }
+ if(o.xscroll != xscroll){
+ if(o.xscroll)
+ tkcmd("pack .body.scx -side bottom -fill x");
+ else
+ tkcmd("pack forget .body.scx");
+ tkcmd("pack .body -expand 1 -fill both; update");
+ }
+ debsrc->reinit(o.xscroll, o.remcr);
+ writeopts(o);
+ r <-= o;
+}
+
+checkopts(o: ref Options): int
+{
+ if(o.start != ""){
+ o.mod = debsrc->loadsrc(o.start, 1);
+ if(o.mod == nil)
+ o.start = "";
+ }
+ return 1;
+}
+
+pickprog(c: chan of int)
+{
+ (t, titlebut) := tkclient->toplevel(context, nil, "Wmdeb Thread List", 0);
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+
+ tkcmds(t, tkpicktab);
+ tk->cmd(t, "update");
+ ids := addpickprogs(t);
+ tkclient->onscreen(t, nil);
+ tkclient->startinput(t, "ptr" :: "kbd" :: nil);
+
+ for(;;){
+ tk->cmd(t, "update");
+ alt{
+ key := <-t.ctxt.kbd =>
+ tk->keyboard(t, key);
+ m := <-t.ctxt.ptr =>
+ tk->pointer(t, *m);
+ s := <-t.ctxt.ctl or
+ s = <-t.wreq or
+ s = <-titlebut =>
+ if(s == "exit"){
+ c <-= -1;
+ exit;
+ }
+ tkclient->wmctl(t, s);
+ s := <-cmd =>
+ case s{
+ "ok" =>
+ c <-= -1;
+ exit;
+ "prog" =>
+ sel := tk->cmd(t, ".progs.p curselection");
+ if(sel == "")
+ break;
+ pid := int tk->cmd(t, ".progs.p get "+sel);
+ c <-= pid;
+ "group" =>
+ sel := tk->cmd(t, ".progs.p curselection");
+ if(sel == "")
+ break;
+ nid := int sel;
+ if(nid > len ids || nid < 0)
+ break;
+ (nil, gid) := ids[nid];
+ nid = len ids;
+ for(i := 0; i < nid; i++){
+ (p, g) := ids[i];
+ if(g == gid)
+ c <-= p;
+ }
+ }
+ }
+ }
+}
+
+addpickprogs(t: ref Tk->Toplevel): array of (int, int)
+{
+ (d, n) := readdir->init("/prog", Readdir->NONE);
+ if(n <= 0)
+ return nil;
+ a := array[n] of { * => (-1, -1) };
+ for(i := 0; i < n; i++){
+ (p, nil) := debug->prog(int d[i].name);
+ if(p == nil)
+ continue;
+ (grp, nil, st, code) := debug->p.status();
+ if(grp < 0)
+ continue;
+ a[i] = (p.id, grp);
+ tk->cmd(t, ".progs.p insert end '"+
+ sys->sprint("%4d %4d %8s %s", p.id, grp, st, code));
+ }
+ return a;
+}
+
+step(k: ref Kid, cmd: int)
+{
+ if(k == nil){
+ if(kids != nil){
+ alert("No current thread");
+ return;
+ }
+ k = spawnkid(opts);
+ kid = k;
+ if(k != nil)
+ refresh(k);
+ return;
+ }
+ case k.state{
+ Stopped =>
+ k.cmd <-= cmd;
+ k.state = Running;
+ if(k == kid)
+ kidstate();
+ Running or Send or Recv or Alt or Exited or Broken =>
+ ;
+ * =>
+ sys->print("bad debug step state %d\n", k.state);
+ }
+}
+
+setbpt()
+{
+ (m, pc) := debsrc->getsel();
+ if(m == nil)
+ return;
+ s := m.sym.pctosrc(pc);
+ if(s == nil){
+ alert("No pc is appropriate");
+ return;
+ }
+
+ # if the breakpoint is already there, delete it
+ for(bl := bpts; bl != nil; bl = tl bl){
+ b := hd bl;
+ if(b.m == m && b.pc == pc){
+ bpts = delbpt(b, bpts);
+ return;
+ }
+ }
+
+ b := ref Bpt(bptid++, m, pc);
+ bpts = b :: bpts;
+ debsrc->attachdis(m);
+ for(kl := kids; kl != nil; kl = tl kl){
+ k := hd kl;
+ k.prog.setbpt(m.dis, pc);
+ }
+
+ # mark the breakpoint text
+ tkcmd(m.tk+" tag add bpt "+string s.start.line+"."+string s.start.pos+" "+string s.stop.line+"."+string s.stop.pos);
+
+ # add the kid to the breakpoint window
+ me := ".bpt.v."+string b.id;
+ tkcmd("label "+me+" -text "+string b.id);
+ tkcmd("pack "+me+" -side top -fill x");
+ tkcmd("bind "+me+" <ButtonRelease-1> {send m seebpt "+string b.id+"}");
+ updatebpts();
+}
+
+seebpt(bpt: int)
+{
+ for(bl := bpts; bl != nil; bl = tl bl){
+ b := hd bl;
+ if(b.id == bpt){
+ s := b.m.sym.pctosrc(b.pc);
+ debsrc->showmodsrc(b.m, s);
+ return;
+ }
+ }
+}
+
+delbpt(b: ref Bpt, bpts: list of ref Bpt): list of ref Bpt
+{
+ if(bpts == nil)
+ return nil;
+ hb := hd bpts;
+ tb := tl bpts;
+ if(b == hb){
+ # remove mark from breakpoint text
+ s := b.m.sym.pctosrc(b.pc);
+ tkcmd(b.m.tk+" tag remove bpt "+string s.start.line+"."+string s.start.pos+" "+string s.stop.line+"."+string s.stop.pos);
+
+ # remove the breakpoint window
+ tkcmd("destroy .bpt.v."+string b.id);
+
+ # remove from kids
+ disablebpt(b);
+ return tb;
+ }
+ return hb :: delbpt(b, tb);
+
+}
+
+disablebpt(b: ref Bpt)
+{
+ for(kl := kids; kl != nil; kl = tl kl){
+ k := hd kl;
+ k.prog.delbpt(b.m.dis, b.pc);
+ }
+}
+
+updatebpts()
+{
+tkcmd("update");
+ tkcmd(".bpt.d configure -scrollregion {0 0 [.bpt.v cget -width] [.bpt.v cget -height]}");
+}
+
+seekid(pid: int)
+{
+ for(kl := kids; kl != nil; kl = tl kl){
+ k := hd kl;
+ if(k.prog.id == pid){
+ kid = k;
+ kid.stack.show();
+ refresh(kid);
+ return;
+ }
+ }
+}
+
+delkid(k: ref Kid)
+{
+ kids = rdelkid(k, kids);
+ if(kid == k){
+ if(kids == nil){
+ kid = nil;
+ kidstate();
+ }else{
+ kid = hd kids;
+ refresh(kid);
+ }
+ }
+}
+
+rdelkid(k: ref Kid, kids: list of ref Kid): list of ref Kid
+{
+ if(kids == nil)
+ return nil;
+ hk := hd kids;
+ t := tl kids;
+ if(k == hk){
+ # remove kid from display
+ k.stack.delete();
+ tkcmd("destroy .prog.v."+string k.prog.id);
+ updatekids();
+ return t;
+ }
+ return hk :: rdelkid(k, t);
+}
+
+updatekids()
+{
+tkcmd("update");
+ tkcmd(".prog.d configure -scrollregion {0 0 [.prog.v cget -width] [.prog.v cget -height]}");
+}
+
+killkids()
+{
+ for(kl := kids; kl != nil; kl = tl kl)
+ killkid(hd kl);
+}
+
+killkid(k: ref Kid)
+{
+ if(k.watch >= 0){
+ killpid(k.watch);
+ k.watch = -1;
+ }
+ case k.state{
+ Exited or Broken or Stopped =>
+ k.cmd <-= KidKill;
+ k.state = Killed;
+ Running or Send or Recv or Alt or Killing =>
+ k.prog.kill();
+ k.state = Killing;
+ * =>
+ sys->print("unknown state %d in killkid\n", k.state);
+ }
+}
+
+freekids(): int
+{
+ r := 0;
+ for(kl := kids; kl != nil; kl = tl kl){
+ k := hd kl;
+ if(k.state == Exited || k.state == Killing || k.state == Killed){
+ r ++;
+ detachkid(k);
+ }
+ }
+ return r;
+}
+
+detachkids()
+{
+ for(kl := kids; kl != nil; kl = tl kl)
+ detachkid(hd kl);
+}
+
+detachkid(k: ref Kid)
+{
+ if(k == nil){
+ alert("No current thread");
+ return;
+ }
+ if(k.state == Exited){
+ killkid(k);
+ return;
+ }
+
+ # kill off the debugger progs
+ killpid(k.watch);
+ killpid(k.run);
+ err := k.prog.start();
+ if(err != "")
+ alert("Detaching thread: "+err);
+
+ delkid(k);
+}
+
+kidstate()
+{
+ ts : array of string;
+ if(kid == nil){
+ tkcmd(".Wm_t.title configure -text '"+title);
+ if(debsrc->packed == nil){
+ tkcmds(tktop, searchoff);
+ ts = tknobody;
+ }else{
+ ts = tkloaded;
+ tkcmds(tktop, searchon);
+ }
+ }else{
+ tkcmd(".Wm_t.title configure -text '"+title+" "+string kid.prog.id+" "+status[kid.state]);
+ ts = tktools[kid.state];
+ tkcmds(tktop, searchon);
+ }
+ if(ts != toolstate){
+ toolstate = ts;
+ tkcmds(tktop, ts);
+ }
+}
+
+#
+# update the stack an src displays
+# to reflect the current state of k
+#
+refresh(k: ref Kid)
+{
+ if(k.state == Killing || k.state == Killed){
+ kidstate();
+ return;
+ }
+ (s, err) := k.prog.stack();
+ if(s == nil && err == "")
+ err = "No stack";
+ if(err != ""){
+ kidstate();
+ return;
+ }
+ for(i := 0; i < len s; i++){
+ debsrc->findmod(s[i].m);
+ s[i].findsym();
+ }
+ err = s[0].findsym();
+ src := s[0].src();
+ kidstate();
+ m := s[0].m;
+ if(src == nil && len s > 1){
+ dis := s[0].m.dis();
+ if(len dis > 0 && dis[0] == '$'){
+ m = s[1].m;
+ s[1].findsym();
+ src = s[1].src();
+ }
+ }
+ debsrc->showmodsrc(debsrc->findmod(m), src);
+ k.stack.refresh(s);
+ k.stack.show();
+}
+
+pickup(pid: int): ref Kid
+{
+ for(kl := kids; kl != nil; kl = tl kl)
+ if((hd kl).prog.id == pid)
+ return hd kl;
+ k := newkid(pid);
+ if(k == nil)
+ return nil;
+ k.cmd <-= KidGrab;
+ k.state = Running;
+ k.pickup = 1;
+ if(kid == nil){
+ kid = k;
+ refresh(kid);
+ }
+ return k;
+}
+
+loaded(s: string)
+{
+ for(bl := bpts; bl != nil; bl = tl bl){
+ b := hd bl;
+ debsrc->attachdis(b.m);
+ if(s == b.m.dis){
+ for(kl := kids; kl != nil; kl = tl kl)
+ (hd kl).prog.setbpt(s, b.pc);
+ }
+ }
+}
+
+Enofd: con "no free file descriptors\n";
+
+newkid(pid: int): ref Kid
+{
+ (p, err) := debug->prog(pid);
+ if(err != ""){
+ n := len err - len Enofd;
+ if(n >= 0 && err[n: ] == Enofd && freekids()){
+ (p, err) = debug->prog(pid);
+ if(err == "")
+ return mkkid(p);
+ }
+ alert("Can't pick up thread "+err);
+ return nil;
+ }
+ return mkkid(p);
+}
+
+mkkid(p: ref Prog): ref Kid
+{
+ for(bl := bpts; bl != nil; bl = tl bl){
+ b := hd bl;
+ debsrc->attachdis(b.m);
+ p.setbpt(b.m.dis, b.pc);
+ }
+ k := ref Kid(Stopped, p, -1, -1, 0, chan of int, Vars.create());
+ kids = k :: kids;
+ c := chan of int;
+ spawn kidslave(k, c);
+ k.run = <- c;
+ spawn kidwatch(k, c);
+ k.watch = <-c;
+ me := ".prog.v."+string p.id;
+ tkcmd("label "+me+" -text "+string p.id);
+ tkcmd("pack "+me+" -side top -fill x");
+ tkcmd("bind "+me+" <ButtonRelease-1> {send m seeprog "+string p.id+"}");
+ tkcmd(".prog.d configure -scrollregion {0 0 [.prog.v cget -width] [.prog.v cget -height]}");
+ return k;
+}
+
+spawnkid(o: ref Options): ref Kid
+{
+ m := o.mod;
+ if(m == nil){
+ alert("No module to run");
+ return nil;
+ }
+
+ if(!debsrc->attachdis(m)){
+ alert("Can't load Dis file "+m.dis);
+ return nil;
+ }
+
+ (p, err) := debug->startprog(m.dis, o.dir, kidctxt, m.dis :: o.args);
+ if(err != nil){
+ alert(m.dis+" is not a debuggable Dis command module: "+err);
+ return nil;
+ }
+
+ return mkkid(p);
+}
+
+xlate := array[] of {
+ KidStep => Debug->StepExp,
+ KidStmt => Debug->StepStmt,
+ KidOver => Debug->StepOver,
+ KidOut => Debug->StepOut,
+};
+
+kidslave(k: ref Kid, me: chan of int)
+{
+ me <-= sys->pctl(0, nil);
+ me = nil;
+ for(;;){
+ c := <-k.cmd;
+ case c{
+ KidGrab =>
+ err := k.prog.grab();
+ kidack <-= (k, err);
+ KidStep or KidStmt or KidOver or KidOut =>
+ err := k.prog.step(xlate[c]);
+ kidack <-= (k, err);
+ KidKill =>
+ err := "kill "+k.prog.kill();
+ k.prog.kill(); # kill again to slay blocked progs
+ kidack <-= (k, err);
+ exit;
+ KidRun =>
+ err := k.prog.cont();
+ kidack <-= (k, err);
+ * =>
+ sys->print("kidslave: bad command %d\n", c);
+ exit;
+ }
+ }
+}
+
+kidwatch(k: ref Kid, me: chan of int)
+{
+ me <-= sys->pctl(0, nil);
+ me = nil;
+ for(;;)
+ kidevent <-= (k, k.prog.event());
+}
+
+alert(m: string)
+{
+ dialog->prompt(context, tktop.image, "warning -fg yellow",
+ "Debugger Alert", m, 0, "Dismiss"::nil);
+}
+
+tkcmd(cmd: string): string
+{
+ s := tk->cmd(tktop, cmd);
+# if(len s != 0 && s[0] == '!')
+# sys->print("%s '%s'\n", s, cmd);
+ return s;
+}
+
+sysname(): string
+{
+ fd := sys->open("#c/sysname", sys->OREAD);
+ if(fd == nil)
+ return "Anon";
+ buf := array[128] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return "Anon";
+ return string buf[:n];
+}
+
+tkcmds(top: ref Tk->Toplevel, cmds: array of string)
+{
+ for(i := 0; i < len cmds; i++)
+ tk->cmd(top, cmds[i]);
+}
+
+exitdb()
+{
+ fd := sys->open("#p/"+string dbpid+"/ctl", sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "killgrp");
+ exit;
+}
+
+killpid(pid: int)
+{
+ fd := sys->open("#p/"+string pid+"/ctl", sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "kill");
+}
+
+getuser(): string
+{
+ fd := sys->open("/dev/user", Sys->OREAD);
+ if(fd == nil)
+ return "";
+ buf := array[128] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return "";
+ return string buf[0:n];
+}
+
+debconf(): string
+{
+ return "/usr/" + getuser() + "/lib/deb";
+}
+
+readopts(o: ref Options)
+{
+ fd := sys->open(debconf(), Sys->OREAD);
+ if(fd == nil)
+ return;
+ b := array[4] of byte;
+ if(sys->read(fd, b, 4) != 4)
+ return;
+ o.nrun = int b[0]-'0';
+ o.xkill = int b[1]-'0';
+ o.xscroll = int b[2]-'0';
+ o.remcr = int b[3]-'0';
+}
+
+writeopts(o: ref Options)
+{
+ fd := sys->create(debconf(), Sys->OWRITE, 8r660);
+ if(fd == nil)
+ return;
+ b := array[4] of byte;
+ b[0] = byte (o.nrun+'0');
+ b[1] = byte (o.xkill+'0');
+ b[2] = byte (o.xscroll+'0');
+ b[3] = byte (o.remcr+'0');
+ sys->write(fd, b, 4);
+}
diff --git a/appl/wm/debdata.b b/appl/wm/debdata.b
new file mode 100644
index 00000000..1f5b6752
--- /dev/null
+++ b/appl/wm/debdata.b
@@ -0,0 +1,418 @@
+implement DebData;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "string.m";
+ str: String;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "dialog.m";
+
+include "selectfile.m";
+
+include "debug.m";
+ debug: Debug;
+ Sym, Src, Exp, Module: import debug;
+
+include "wmdeb.m";
+ debsrc: DebSrc;
+
+DatumSize: con 32;
+WalkWidth: con "20";
+
+context: ref Draw->Context;
+tktop: ref Tk->Toplevel;
+var: ref Vars;
+vid: int;
+tkids := 1; # increasing id of tk pieces
+
+icondir : con "debug/";
+
+tkconfig := array[] of {
+ "frame .body -width 400 -height 400",
+ "pack .Wm_t -side top -fill x",
+ "pack .body -expand 1 -fill both",
+ "pack propagate . 0",
+ "update",
+ "image create bitmap Itemopen -file "+icondir+
+ "open.bit -maskfile "+icondir+"open.mask",
+ "image create bitmap Itemclosed -file "+icondir+
+ "closed.bit -maskfile "+icondir+"closed.mask",
+};
+
+init(acontext: ref Draw->Context,
+ geom: string,
+ adebsrc: DebSrc,
+ astr: String,
+ adebug: Debug): (ref Tk->Toplevel, chan of string, chan of string)
+{
+ context = acontext;
+ debsrc = adebsrc;
+ sys = load Sys Sys->PATH;
+ tk = load Tk Tk->PATH;
+ str = astr;
+ debug = adebug;
+
+ tkclient = load Tkclient Tkclient->PATH;
+
+ tkclient->init();
+ titlebut: chan of string;
+ (tktop, titlebut) = tkclient->toplevel(context, geom, "Stack", Tkclient->Resize);
+ buts := chan of string;
+ tk->namechan(tktop, buts, "buts");
+
+ for(i := 0; i < len tkconfig; i++)
+ tk->cmd(tktop, tkconfig[i]);
+
+ tkcmd("update");
+ tkclient->onscreen(tktop, nil);
+ tkclient->startinput(tktop, "kbd" :: "ptr" :: nil);
+ return (tktop, buts, titlebut);
+}
+
+ctl(s: string)
+{
+ if(var == nil)
+ return;
+ arg := s[1:];
+ case s[0]{
+ 'o' =>
+ var.expand(arg);
+ var.update();
+ 'c' =>
+ var.contract(arg);
+ var.update();
+ 'y' =>
+ var.scrolly(arg);
+ 's' =>
+ var.showsrc(arg);
+ }
+ tkcmd("update");
+}
+
+wmctl(s: string)
+{
+ if(s == "exit"){
+ tkcmd(". unmap");
+ return;
+ }
+ tkclient->wmctl(tktop, s);
+ tkcmd("update");
+}
+
+Vars.create(): ref Vars
+{
+ t := ".body.v"+string vid++;
+
+ tkcmd("frame "+t);
+ tkcmd("canvas "+t+".cvar -width 2 -height 2 -yscrollcommand {"+t+".sy set} -xscrollcommand {"+t+".sxvar set}");
+ tkcmd("frame "+t+".f0");
+
+ tkcmd(t+".cvar create window 0 0 -window "+t+".f0 -anchor nw");
+ tkcmd("scrollbar "+t+".sxvar -orient horizontal -command {"+t+".cvar xview}");
+
+ tkcmd("scrollbar "+t+".sy -command {send buts y}");
+ tkcmd("pack "+t+".sy -side right -fill y -in "+t);
+ tkcmd("pack "+t+".sxvar -fill x -side bottom -in "+t);
+ tkcmd("pack "+t+".cvar -expand 1 -fill both -in "+t);
+
+ return ref Vars(t, 0, nil);
+}
+
+Vars.show(v: self ref Vars)
+{
+ if(v == var)
+ return;
+ if(var != nil)
+ tkcmd("pack forget "+var.tk);
+ var = v;
+ tkcmd("pack "+var.tk+" -expand 1 -fill both");
+ v.update();
+}
+
+Vars.delete(v: self ref Vars)
+{
+ if(var == v)
+ var = nil;
+ tkcmd("destroy "+v.tk);
+ tkcmd("update");
+}
+
+Vars.refresh(v: self ref Vars, ea: array of ref Exp)
+{
+ nea := len ea;
+ newd := array[nea] of ref Datum;
+ da := v.d;
+ nd := len da;
+ n := nea;
+ if(n > nd)
+ n = nd;
+ for(i := 0; i < n; i++){
+ d := da[nd-i-1];
+ if(!sameexp(ea[nea-i-1], d.e, 1))
+ break;
+ newd[nea-i-1] = d;
+ }
+ n = nea-i;
+ for(; i < nd; i++)
+ da[nd-i-1].destroy();
+ v.d = nil;
+ for(i = 0; i < n; i++){
+ debsrc->findmod(ea[i].m);
+ ea[i].findsym();
+ newd[i] = mkkid(ea[i], v.tk, "0", string tkids++, nil, nil, -1, "");
+ }
+ for(; i < nea; i++){
+ debsrc->findmod(ea[i].m);
+ ea[i].findsym();
+ d := newd[i];
+ newd[i] = mkkid(ea[i], v.tk, "0", d.tkid, d.kids, d.val, d.canwalk, "");
+ }
+ v.d = newd;
+ v.update();
+}
+
+Vars.update(v: self ref Vars)
+{
+ tkcmd("update");
+ tkcmd(v.tk+".cvar configure -scrollregion {0 0 ["+v.tk+".f0 cget -width] ["+v.tk+".f0 cget -height]}");
+ tkcmd("update");
+}
+
+Vars.scrolly(v: self ref Vars, pos: string)
+{
+ tkcmd(v.tk+".cvar yview"+pos);
+}
+
+Vars.showsrc(v: self ref Vars, who: string)
+{
+ (sid, kids) := str->splitl(who[1:], ".");
+ showsrc(v.d, sid, kids);
+}
+
+showsrc(da: array of ref Datum, id, kids: string)
+{
+ if(da == nil)
+ return;
+ for(i := 0; i < len da; i++){
+ d := da[i];
+ if(d.tkid != id)
+ continue;
+ if(kids == "")
+ d.showsrc();
+ else{
+ sid : string;
+ (sid, kids) = str->splitl(kids[1:], ".");
+ showsrc(d.kids, sid, kids);
+ }
+ break;
+ }
+}
+
+Vars.expand(v: self ref Vars, who: string)
+{
+ (sid, kids) := str->splitl(who[1:], ".");
+ v.d = expandkid(v.d, sid, kids, who);
+}
+
+expandkid(da: array of ref Datum, id, kids, who: string): array of ref Datum
+{
+ if(da == nil)
+ return nil;
+ for(i := 0; i < len da; i++){
+ d := da[i];
+ if(d.tkid != id)
+ continue;
+ if(kids == "")
+ da[i] = d.expand(nil, who);
+ else{
+ sid : string;
+ (sid, kids) = str->splitl(kids[1:], ".");
+ d.kids = expandkid(d.kids, sid, kids, who);
+ }
+ break;
+ }
+ return da;
+}
+
+Vars.contract(v: self ref Vars, who: string)
+{
+ (sid, kids) := str->splitl(who[1:], ".");
+ v.d = contractkid(v.d, sid, kids, who);
+}
+
+contractkid(da: array of ref Datum, id, kids, who: string): array of ref Datum
+{
+ if(da == nil)
+ return nil;
+ for(i := 0; i < len da; i++){
+ d := da[i];
+ if(d.tkid != id)
+ continue;
+ if(kids == "")
+ da[i] = d.contract(who);
+ else{
+ sid : string;
+ (sid, kids) = str->splitl(kids[1:], ".");
+ d.kids = contractkid(d.kids, sid, kids, who);
+ }
+ break;
+ }
+ return da;
+}
+
+Datum.contract(d: self ref Datum, who: string): ref Datum
+{
+ vtk := d.vtk;
+ tkid := d.tkid;
+ if(tkid == "")
+ return d;
+ kids := d.kids;
+ if(kids == nil){
+ tkcmd(vtk+".v"+tkid+".b configure -image Itemclosed -command {send buts o"+who+"}");
+ return d;
+ }
+
+ for(i := 0; i < len kids; i++)
+ kids[i].destroy();
+ d.kids = nil;
+ tkcmd("destroy "+vtk+".f"+tkid);
+ tkcmd(vtk+".v"+tkid+".b configure -image Itemclosed -command {send buts o"+who+"}");
+
+ return d;
+}
+
+Datum.showsrc(d: self ref Datum)
+{
+ debsrc->showmodsrc(debsrc->findmod(d.e.m), d.e.src());
+}
+
+Datum.destroy(d: self ref Datum)
+{
+ kids := d.kids;
+ for(i := 0; i < len kids; i++)
+ kids[i].destroy();
+ vtk := d.vtk;
+ tkid := string d.tkid;
+ if(d.kids != nil){
+ tkcmd("destroy "+vtk+".f"+tkid);
+ }
+ d.kids = nil;
+ tkcmd("destroy "+vtk+".v"+tkid);
+}
+
+mkkid(e: ref Exp, vtk, parent, me: string, okids: array of ref Datum, oval:string, owalk: int, who: string): ref Datum
+{
+ (val, walk) := e.val();
+
+ who = who+"."+me;
+
+ # make the tk goo
+ if(walk != owalk){
+ if(owalk == -1){
+ tkcmd("frame "+vtk+".v"+me);
+ tkcmd("label "+vtk+".v"+me+".l -text '"+e.name);
+ tkcmd("bind "+vtk+".v"+me+".l <ButtonRelease-1> 'send buts s"+who);
+ }else{
+ tkcmd("destroy "+vtk+".v"+me+".b");
+ }
+ if(walk)
+ tkcmd("button "+vtk+".v"+me+".b -image Itemclosed -command 'send buts o"+who);
+ else
+ tkcmd("frame "+vtk+".v"+me+".b -width "+WalkWidth);
+ }
+
+ n := 16 - len e.name;
+ if(n < 4)
+ n = 4;
+ pad := " "[:n];
+
+ # tk value goo
+ if(val == "")
+ val = " ";
+ if(oval != ""){
+ if(val != oval)
+ tkcmd(vtk+".v"+me+".val configure -text '"+pad+val);
+ }else
+ tkcmd("label "+vtk+".v"+me+".val -text '"+pad+val);
+
+ tkcmd("pack "+vtk+".v"+me+".b "+vtk+".v"+me+".l "+vtk+".v"+me+".val -side left");
+ tkcmd("pack "+vtk+".v"+me+" -side top -anchor w -in "+vtk+".f"+parent);
+
+ d := ref Datum(me, parent, vtk, e, val, walk, nil);
+ if(okids != nil){
+ if(walk)
+ return d.expand(okids, who);
+ for(i := 0; i < len okids; i++)
+ okids[i].destroy();
+ }
+ return d;
+}
+
+Datum.expand(d: self ref Datum, okids: array of ref Datum, who: string): ref Datum
+{
+ e := d.e.expand();
+ if(e == nil)
+ return d;
+
+ vtk := d.vtk;
+
+ me := d.tkid;
+
+ # make the tk goo for holding kids
+ needtk := okids == nil;
+ if(needtk){
+ tkcmd("frame "+vtk+".f"+me);
+ tkcmd("frame "+vtk+".f"+me+".x -width "+WalkWidth);
+ tkcmd("frame "+vtk+".f"+me+".v");
+ tkcmd("pack "+vtk+".f"+me+".x "+vtk+".f"+me+".v -side left -fill y -expand 1");
+ }
+
+ kids := array[len e] of ref Datum;
+ for(i := 0; i < len e; i++){
+ if(i >= len okids)
+ break;
+ ok := okids[i];
+ if(!sameexp(e[i], ok.e, 0))
+ break;
+ kids[i] = mkkid(e[i], vtk, me, ok.tkid, ok.kids, ok.val, ok.canwalk, who);
+ }
+ for(oi := i; oi < len okids; oi++)
+ okids[oi].destroy();
+ for(; i < len e; i++)
+ kids[i] = mkkid(e[i], vtk, me, string tkids++, nil, nil, -1, who);
+
+ tkcmd("pack "+vtk+".f"+me+" -side top -anchor w -after "+vtk+".v"+me);
+ tkcmd(vtk+".v"+me+".b configure -image Itemopen -command {send buts c"+who+"}");
+
+ d.kids = kids;
+ return d;
+}
+
+sameexp(e, f: ref Exp, offmatch: int): int
+{
+ if(e.m != f.m || e.p != f.p || e.name != f.name)
+ return 0;
+ return !offmatch || e.offset == f.offset;
+}
+
+tkcmd(cmd: string): string
+{
+ s := tk->cmd(tktop, cmd);
+# if(len s != 0 && s[0] == '!')
+# sys->print("%s '%s'\n", s, cmd);
+ return s;
+}
+
+raisex()
+{
+ tkcmd(". map; raise .; update");
+}
diff --git a/appl/wm/debsrc.b b/appl/wm/debsrc.b
new file mode 100644
index 00000000..57f26218
--- /dev/null
+++ b/appl/wm/debsrc.b
@@ -0,0 +1,633 @@
+implement DebSrc;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "string.m";
+ str: String;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "dialog.m";
+ dialog: Dialog;
+
+include "selectfile.m";
+ selectfile: Selectfile;
+
+include "debug.m";
+ debug: Debug;
+ Sym, Src, Exp, Module: import debug;
+
+include "wmdeb.m";
+
+include "plumbmsg.m";
+ plumbmsg: Plumbmsg;
+ Msg: import plumbmsg;
+
+include "workdir.m";
+ workdir: Workdir;
+
+include "dis.m";
+ dism: Dis;
+
+mods: list of ref Mod;
+tktop: ref Tk->Toplevel;
+context: ref Draw->Context;
+opendir = ".";
+srcid: int;
+xscroll, remcr: int;
+
+sblpath := array[] of
+{
+ ("/dis/", "/appl/"),
+ ("/dis/", "/appl/cmd/"),
+ # ("/dis/mux/", "/appl/mux/"),
+ # ("/dis/lib/", "/appl/lib/"),
+ # ("/dis/wm/", "/appl/wm/"),
+ ("/dis/sh.", "/appl/cmd/sh/sh."),
+};
+
+plumbed := 0;
+but3: chan of string;
+
+plumbbind := array[] of
+{
+ "<ButtonPress-3> {send but3 pressed}",
+ "<ButtonRelease-3> {send but3 released %x %y}",
+ "<Motion-Button-3> {}",
+ "<Double-Button-3> {}",
+ "<Double-ButtonRelease-3> {}",
+};
+
+init(acontext: ref Draw->Context,
+ atktop: ref Tk->Toplevel,
+ atkclient: Tkclient,
+ aselectfile: Selectfile,
+ adialog: Dialog,
+ astr: String,
+ adebug: Debug,
+ xscr: int,
+ rcr: int)
+{
+ context = acontext;
+ tktop = atktop;
+ sys = load Sys Sys->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = atkclient;
+ selectfile = aselectfile;
+ dialog = adialog;
+ str = astr;
+ debug = adebug;
+ xscroll = xscr;
+ remcr = rcr;
+
+ plumbmsg = load Plumbmsg Plumbmsg->PATH;
+ if(plumbmsg->init(1, nil, 0) >= 0){
+ plumbed = 1;
+ workdir = load Workdir Workdir->PATH;
+ }
+}
+
+reinit(xscr: int, rcr: int)
+{
+ if(xscroll == xscr && remcr == rcr)
+ return;
+ xscroll = xscr;
+ remcr = rcr;
+ for(ml := mods; ml != nil; ml = tl ml){
+ m := hd ml;
+ if(xscroll)
+ tkcmd(m.tk+" configure -wrap none");
+ else
+ tkcmd(m.tk+" configure -wrap char");
+ tkcmd("update");
+ fd := sys->open(m.src, sys->OREAD);
+ if(fd != nil)
+ loadfile(m.tk, fd);
+ }
+}
+
+#
+# make a Mod with a text widget for source file src
+#
+loadsrc(src: string, addpath: int): ref Mod
+{
+ if(src == "")
+ return nil;
+
+ m : ref Mod = nil;
+ for(ml := mods; ml != nil; ml = tl ml){
+ m = hd ml;
+ if(m.src == src || filesuffix(src, m.src))
+ break;
+ }
+
+ if(ml == nil || m.tk == nil){
+ if(ml == nil)
+ m = ref Mod(src, nil, nil, nil, 0, 1);
+ fd := sys->open(src, sys->OREAD);
+ if(fd == nil)
+ return nil;
+ (dir, file) := str->splitr(src, "/");
+ m.tk = ".t."+tk->quote(file)+string srcid++;
+ if(xscroll)
+ tkcmd("text "+m.tk+" -bd 0 -state disabled -wrap none");
+ else
+ tkcmd("text "+m.tk+" -bd 0 -state disabled");
+ if (but3 == nil) {
+ but3 = chan of string;
+ spawn but3proc();
+ }
+ tk->namechan(tktop, but3, "but3");
+ for (i := 0; i < len plumbbind; i++)
+ tkcmd("bind "+m.tk+" "+plumbbind[i]);
+ tkcmd(m.tk+" configure -insertwidth 2");
+ opack := packed;
+ packm(m);
+ if(!loadfile(m.tk, fd)){
+ fd = nil;
+ packm(opack);
+ tkcmd("destroy "+m.tk);
+ return nil;
+ }
+ fd = nil;
+ tkcmd(m.tk+" tag configure bpt -foreground #c00");
+ tkcmd(".m.file.menu add command -label "+src+" -command {send m open "+src+"}");
+ if(ml == nil)
+ mods = m :: mods;
+
+ if(addpath)
+ addsearch(dir);
+ }
+ return m;
+}
+
+addsearch(dir: string)
+{
+ for(i := 0; i < len searchpath; i++)
+ if(searchpath[i] == dir)
+ return;
+ s := array[i+1] of string;
+ s[0:] = searchpath;
+ s[i] = dir;
+ searchpath = s;
+}
+
+#
+# bring up the widget for src, if it exists
+#
+showstrsrc(src: string)
+{
+ m : ref Mod = nil;
+ for(ml := mods; ml != nil; ml = tl ml){
+ m = hd ml;
+ if(m.src == src)
+ break;
+ }
+ if(ml == nil)
+ return;
+
+ packm(m);
+}
+
+#
+# bring up the widget for module
+# at position s
+#
+showmodsrc(m: ref Mod, s: ref Src)
+{
+ if(s == nil)
+ return;
+
+ src := s.start.file;
+ if(src != s.stop.file)
+ s.stop = s.start;
+
+ if(m == nil || m.tk == nil || m.src != src){
+ m1 := findsrc(src);
+ if(m1 == nil)
+ return;
+ if(m1.dis == nil)
+ m1.dis = m.dis;
+ if(m1.sym == nil)
+ m1.sym = m.sym;
+ m = m1;
+ }
+
+ tkcmd(m.tk+" mark set insert "+string s.start.line+"."+string s.start.pos);
+ tkcmd(m.tk+" tag remove sel 0.0 end");
+ tkcmd(m.tk+" tag add sel insert "+string s.stop.line+"."+string s.stop.pos);
+ tkcmd(m.tk+" see insert");
+
+ packm(m);
+}
+
+packm(m: ref Mod)
+{
+ if(packed != m && packed != nil){
+ tkcmd(packed.tk+" configure -xscrollcommand {}");
+ tkcmd(packed.tk+" configure -yscrollcommand {}");
+ tkcmd(".body.scx configure -command {}");
+ tkcmd(".body.scy configure -command {}");
+ tkcmd("pack forget "+packed.tk);
+ }
+
+ if(packed != m && m != nil){
+ tkcmd(m.tk+" configure -xscrollcommand {.body.scx set}");
+ tkcmd(m.tk+" configure -yscrollcommand {.body.scy set}");
+ tkcmd(".body.scx configure -command {"+m.tk+" xview}");
+ tkcmd(".body.scy configure -command {"+m.tk+" yview}");
+ tkcmd("pack "+m.tk+" -expand 1 -fill both -in .body.ft");
+ }
+ packed = m;
+}
+
+#
+# find the dis file associated with m
+# we know that m has a valid src
+#
+attachdis(m: ref Mod): int
+{
+ c := load Diss m.dis;
+ if(c == nil){
+ m.dis = repsuff(m.src, ".b", ".dis");
+ c = load Diss m.dis;
+ }
+ if(c == nil && m.sym != nil){
+ m.dis = repsuff(m.sym.path, ".sbl", ".dis");
+ c = load Diss m.dis;
+ }
+ if(c != nil){
+ # if m.dis in /appl, prefer one in /dis if it exists (!)
+ nd := len m.dis;
+ for(i := 0; i < len sblpath; i++){
+ (disd, srcd) := sblpath[i];
+ ns := len srcd;
+ if(nd > ns && m.dis[:ns] == srcd){
+ dis := disd + m.dis[ns:];
+ d := load Diss dis;
+ if(d != nil)
+ m.dis = dis;
+ break;
+ }
+ }
+ }
+ if(c == nil){
+ (dir, file) := str->splitr(repsuff(m.src, ".b", ".dis"), "/");
+ pat := list of {
+ file+" (Dis VM module)",
+ "*.dis (Dis VM module)"
+ };
+ m.dis = selectfile->filename(context, tktop.image, "Locate Dis file", pat, dir);
+ c = load Diss m.dis;
+ }
+ return c != nil;
+}
+
+#
+# load the symbol file for m
+# works best if m has an associated source file
+#
+attachsym(m: ref Mod)
+{
+ if(m.sym != nil)
+ return;
+ sbl := repsuff(m.src, ".b", ".sbl");
+ err : string;
+ tk->cmd(tktop, "cursor -bitmap cursor.wait");
+ (m.sym, err) = debug->sym(sbl);
+ tk->cmd(tktop, "cursor -default");
+ if(m.sym != nil)
+ return;
+ if(!str->prefix("Can't open", err)){
+ alert(err);
+ return;
+ }
+ (dir, file) := str->splitr(sbl, "/");
+
+ pat := list of {
+ file+" (Symbol table file)",
+ "*.sbl (Symbol table file)"
+ };
+ sbl = selectfile->filename(context, tktop.image, "Locate Symbol file", pat, dir);
+ tk->cmd(tktop, "cursor -bitmap cursor.wait");
+ (m.sym, err) = debug->sym(sbl);
+ tk->cmd(tktop, "cursor -default");
+ if(m.sym != nil)
+ return;
+ if(!str->prefix("Can't open", err)){
+ alert(err);
+ return;
+ }
+}
+
+#
+# get the current selection
+#
+getsel(): (ref Mod, int)
+{
+ m := packed;
+ if(m == nil || m.src == nil)
+ return (nil, 0);
+ attachsym(m);
+ if(m.sym == nil){
+ alert("No symbol file for "+m.src);
+ return (nil, 0);
+ }
+ index := tkcmd(m.tk+" index insert");
+ if(len index == 0 || index[0] == '!')
+ return (nil, 0);
+ (sline, spos) := str->splitl(index, ".");
+ line := int sline;
+ pos := int spos[1:];
+ pc := m.sym.srctopc(ref Src((m.src, line, pos), (m.src, line, pos)));
+ s := m.sym.pctosrc(pc);
+ if(s == nil){
+ alert("No pc is appropriate");
+ return (nil, 0);
+ }
+ return (m, pc);
+}
+
+#
+# return the selected string
+#
+snarf(): string
+{
+ if(packed == nil)
+ return "";
+ s := tk->cmd(tktop, packed.tk+" get sel.first sel.last");
+ if(len s > 0 && s[0] == '!')
+ s = "";
+ return s;
+}
+
+plumbit(x, y: string)
+{
+ if (packed == nil)
+ return;
+ s := tk->cmd(tktop, packed.tk+" index @"+x+","+y);
+ if (s == nil || s[0] == '!')
+ return;
+ (nil, l) := sys->tokenize(s, ".");
+ msg := ref Msg(
+ "WmDeb",
+ "",
+ workdir->init(),
+ "text",
+ nil,
+ array of byte (packed.src+":"+hd l));
+ if(msg.send() < 0)
+ sys->fprint(sys->fildes(2), "deb: plumbing write error: %r\n");
+}
+
+but3proc()
+{
+ button3 := 0;
+ for (;;) {
+ s := <-but3;
+ if(s == "pressed"){
+ button3 = 1;
+ continue;
+ }
+ if(plumbed == 0 || button3 == 0)
+ continue;
+ button3 = 0;
+ (nil, l) := sys->tokenize(s, " ");
+ plumbit(hd tl l, hd tl tl l);
+ }
+}
+
+#
+# search for another occurance of s;
+# return if s was found
+#
+search(s: string): int
+{
+ if(packed == nil || s == "")
+ return 0;
+ pos := " sel.last";
+ sel := tk->cmd(tktop, packed.tk+" get sel.last");
+ if(len sel > 0 && sel[0] == '!')
+ pos = " insert";
+ pos = tk->cmd(tktop, packed.tk+" search -- "+tk->quote(s)+pos);
+ if((len pos > 0 && pos[0] == '1') || pos == "")
+ return 0;
+ tkcmd(packed.tk+" mark set insert "+pos);
+ tkcmd(packed.tk+" tag remove sel 0.0 end");
+ tkcmd(packed.tk+" tag add sel insert "+pos+"+"+string len s+"c");
+ tkcmd(packed.tk+" see insert");
+ return 1;
+}
+
+#
+# make a Mod for debugger module mod
+#
+findmod(mod: ref Module): ref Mod
+{
+ dis := mod.dis();
+ if(dis == "")
+ return nil;
+ m: ref Mod;
+ for(ml := mods; ml != nil; ml = tl ml){
+ m = hd ml;
+ if(m.dis == dis || filesuffix(dis, m.dis))
+ break;
+ }
+ if(ml == nil){
+ if(len dis > 0 && dis[0] != '$')
+ m = findsrc(repsuff(dis, ".dis", ".b"));
+ if(m == nil)
+ mods = ref Mod("", "", dis, nil, 0, 0) :: mods;
+ }
+ if(m != nil){
+ m.srcask = 0;
+ m.dis = dis;
+ if(m.symask){
+ attachsym(m);
+ m.symask = 0;
+ }
+ mod.addsym(m.sym);
+ }
+ return m;
+}
+
+# log(s: string)
+# {
+# fd := sys->open("/usr/jrf/debug", Sys->OWRITE);
+# sys->seek(fd, 0, Sys->SEEKEND);
+# sys->fprint(fd, "%s\n", s);
+# fd = nil;
+# }
+
+findbm(dis: string): ref Mod
+{
+ if(dism == nil){
+ dism = load Dis Dis->PATH;
+ if(dism != nil)
+ dism->init();
+ }
+ if(dism != nil && (b := dism->src(dis)) != nil)
+ return loadsrc(b, 1);
+ return nil;
+}
+
+findsrc(src: string): ref Mod
+{
+ m := loadsrc(src, 1);
+ if(m != nil)
+ return m;
+ m = findbm(repsuff(src, ".b", ".dis"));
+ if(m != nil)
+ return m;
+ (dir, file) := str->splitr(src, "/");
+ for(i := 0; i < len searchpath; i++){
+ if(dir != "" && dir[0] != '/')
+ m = loadsrc(searchpath[i] + src, 0);
+ if(m != nil)
+ return m;
+ m = loadsrc(searchpath[i] + file, 0);
+ if(m != nil)
+ return m;
+ }
+
+ ns := len src;
+ for(i = 0; i < len sblpath; i++){
+ (disd, srcd) := sblpath[i];
+ nd := len disd;
+ if(ns > nd && src[:nd] == disd){
+ m = loadsrc(srcd + src[nd:], 0);
+ if(m != nil)
+ return m;
+ }
+ }
+
+ (dir, file) = str->splitr(src, "/");
+ opdir := dir;
+ if(opdir == "" || opdir[0] != '/')
+ opdir = opendir;
+
+ pat := list of {
+ file+" (Limbo source)",
+ "*.b (Limbo source)"
+ };
+
+ src = selectfile->filename(context, tktop.image, "Locate Limbo Source", pat, opdir);
+ if(src == nil)
+ return nil;
+ (opendir, nil) = str->splitr(src, "/");
+ if(opendir == "")
+ opendir = ".";
+ m = loadsrc(src, 1);
+ if(m != nil
+ && dir != "" && dir[0] != '/'
+ && suffix(dir, opendir))
+ addsearch(opendir[:len opendir - len dir]);
+ else if(m != nil) # remember anyway
+ addsearch(opendir);
+ return m;
+}
+
+suffix(suff, s: string): int
+{
+ if(len suff > len s)
+ return 0;
+ return suff == s[len s - len suff:];
+}
+
+#
+# load the contents of fd into tkt
+#
+loadfile(tkt: string, fd: ref Sys->FD): int
+{
+ buf := array[512] of byte;
+ i := 0;
+
+ (ok, d) := sys->fstat(fd);
+ if(ok < 0)
+ return 0;
+ tk->cmd(tktop, "cursor -bitmap cursor.wait");
+ length := int d.length;
+ whole := array[length] of byte;
+ cr := 0;
+ for(;;){
+ if(cr){
+ buf[0] = byte '\r';
+ n := sys->read(fd, buf[1:], len buf - 1);
+ n++;
+ }
+ else
+ n := sys->read(fd, buf, len buf);
+ if(n <= 0)
+ break;
+ if(remcr){
+ for(k := 0; k < n-1; ){
+ if(buf[k] == byte '\r' && buf[k+1] == byte '\n')
+ buf[k:] = buf[k+1:n--];
+ else
+ k++;
+ }
+ if(buf[n-1] == byte '\r'){
+ n--;
+ cr = 1;
+ }
+ }
+ j := i+n;
+ if(j > length)
+ break;
+ whole[i:] = buf[:n];
+ i += n;
+ }
+ tk->cmd(tktop, tkt+" delete 1.0 end;"+tkt+" insert end '"+string whole[:i]);
+ tk->cmd(tktop, "update; cursor -default");
+ return 1;
+}
+
+delmod(mods: list of ref Mod, m: ref Mod): list of ref Mod
+{
+ if(mods == nil)
+ return nil;
+ mh := hd mods;
+ if(mh == m)
+ return tl mods;
+ return mh :: delmod(tl mods, m);
+}
+
+#
+# replace an occurance in name of suffix old with new
+#
+repsuff(name, old, new: string): string
+{
+ no := len old;
+ nn := len name;
+ if(nn >= no && name[nn-no:] == old)
+ return name[:nn-no] + new;
+ return name;
+}
+
+filesuffix(suf, s: string): int
+{
+ nsuf := len suf;
+ ns := len s;
+ return ns > nsuf
+ && suf[0] != '/'
+ && s[ns-nsuf-1] == '/'
+ && s[ns-nsuf:] == suf;
+}
+
+alert(m: string)
+{
+ dialog->prompt(context, tktop.image, "warning -fg yellow",
+ "Debugger Alert", m, 0, "Dismiss"::nil);
+}
+
+tkcmd(s: string): string
+{
+ return tk->cmd(tktop, s);
+}
diff --git a/appl/wm/dir.b b/appl/wm/dir.b
new file mode 100644
index 00000000..c4bcfe01
--- /dev/null
+++ b/appl/wm/dir.b
@@ -0,0 +1,511 @@
+implement WmDir;
+
+include "sys.m";
+ sys: Sys;
+ Dir: import sys;
+
+include "draw.m";
+ draw: Draw;
+ ctxt: ref Draw->Context;
+
+include "tk.m";
+ tk: Tk;
+ Toplevel: import tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "dialog.m";
+ dialog: Dialog;
+
+include "readdir.m";
+ readdir: Readdir;
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "plumbmsg.m";
+ plumbmsg: Plumbmsg;
+ Msg: import plumbmsg;
+
+Fontwidth: con 8;
+Xwidth: con 50;
+
+WmDir: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Wm: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Ft: adt
+{
+ ext: string;
+ cmd: string;
+ tkname: string;
+ icon: string;
+ loaded: int;
+ givearg: int;
+};
+
+dirwin_cfg := array[] of {
+ # Lay out the screen
+ "frame .fc",
+ "scrollbar .fc.scroll -command {.fc.c yview}",
+ "canvas .fc.c -relief sunken -yscrollincrement 25"+
+ " -borderwidth 2 -width 10c -height 300"+
+ " -yscrollcommand {.fc.scroll set}"+
+ " -font /fonts/misc/latin1.8x13.font",
+ "frame .mbar",
+ "menubutton .mbar.opt -text {Options} -menu .opt",
+ "pack .mbar.opt -side left",
+ "pack .fc.scroll -side right -fill y",
+ "pack .fc.c -fill both -expand 1",
+ "pack .mbar -fill x",
+ "pack .fc -fill both -expand 1",
+ "pack propagate . 0",
+
+ # prepare cursor
+ "image create bitmap waiting -file cursor.wait",
+
+ # Build the options menu
+ "menu .opt",
+ ".opt add radiobutton -text {by name}"+
+ " -variable sort -value n -command {send opt sort}",
+ ".opt add radiobutton -text {by access}"+
+ " -variable sort -value a -command {send opt sort}",
+ ".opt add radiobutton -text {by modify}"+
+ " -variable sort -value m -command {send opt sort}",
+ ".opt add radiobutton -text {by size}"+
+ " -variable sort -value s -command {send opt sort}",
+ ".opt add separator",
+ ".opt add radiobutton -text {use icons}"+
+ " -variable show -value i -command {send opt icon}",
+ ".opt add radiobutton -text {use text}"
+ +" -variable show -value t -command {send opt text}",
+ ".opt add separator",
+ ".opt add checkbutton -text {Walk} -command {send opt walk}",
+};
+
+key := Readdir->NAME;
+walk: int;
+path: string;
+usetext: int;
+cmdname: string;
+sysnam: string;
+nde: int;
+now: int;
+plumbed := 0;
+de: array of ref Sys->Dir;
+
+filetypes: array of ref Ft;
+deftype: ref Ft;
+dirtype: ref Ft;
+
+inittypes()
+{
+ deftype = ref Ft("", "/dis/wm/edit.dis", "WmDir_Dis", "file", 0, 1);
+ dirtype = ref Ft("", nil, "WmDir_Dir", "dir", 0, 1);
+ filetypes = array[] of {
+ ref Ft("dis", nil, "WmDis_Pic", "dis", 0, 0),
+ ref Ft("bit", "/dis/wm/view.dis", "WmDir_Pic", "pic", 0, 1),
+ ref Ft("gif", "/dis/wm/view.dis", "WmDir_Pic", "pic", 0, 1),
+ ref Ft("jpg", "/dis/wm/view.dis", "WmDir_Pic", "pic", 0, 1),
+ ref Ft("jpeg", "/dis/wm/view.dis", "WmDir_Pic", "pic", 0, 1),
+ ref Ft("mask", "/dis/wm/view.dis", "WmDir_Pic", "pic", 0, 1),
+ };
+}
+
+init(env: ref Draw->Context, argv: list of string)
+{
+ ctxt = env;
+
+ sys = load Sys Sys->PATH;
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "dir: no window context\n");
+ raise "fail:bad context";
+ }
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ dialog = load Dialog Dialog->PATH;
+ readdir = load Readdir Readdir->PATH;
+ plumbmsg = load Plumbmsg Plumbmsg->PATH;
+ if(plumbmsg != nil && plumbmsg->init(1, nil, 0) >= 0)
+ plumbed = 1;
+
+ tkclient->init();
+ dialog->init();
+ inittypes();
+
+ cmdname = hd argv;
+ sysnam = sysname()+":";
+
+ (t, wmctl) := tkclient->toplevel(ctxt, "", "", Tkclient->Appl);
+
+ tk->cmd(t, "cursor -image waiting");
+
+ filecmd := chan of string;
+ tk->namechan(t, filecmd, "fc");
+ conf := chan of string;
+ tk->namechan(t, conf, "cf");
+ opt := chan of string;
+ tk->namechan(t, opt, "opt");
+
+ argv = tl argv;
+ if(argv == nil)
+ getdir(t, "");
+ else
+ getdir(t, hd argv);
+ for (c:=0; c<len dirwin_cfg; c++)
+ tk->cmd(t, dirwin_cfg[c]);
+ drawdir(t);
+ tk->cmd(t, "update; cursor -default");
+ tk->cmd(t, "bind . <Configure> {send cf conf}");
+ tkclient->onscreen(t, nil);
+ tkclient->startinput(t, "kbd"::"ptr"::nil);
+
+ menu := "";
+
+f: for(;;) alt {
+ s := <-t.ctxt.kbd =>
+ tk->keyboard(t, s);
+ s := <-t.ctxt.ptr =>
+ tk->pointer(t, *s);
+ s := <-t.ctxt.ctl or
+ s = <-t.wreq or
+ s = <-wmctl =>
+ if (s == "exit")
+ exit;
+ tkclient->wmctl(t, s);
+ <-conf =>
+ #
+ # Only recompute contents if the size changed
+ #
+ if(menu[0] != 's')
+ break;
+ tk->cmd(t, ".fc.c delete all");
+ drawdir(t);
+ tk->cmd(t, ".fc.c yview moveto 0; update");
+ mopt := <-opt =>
+ case mopt {
+ "sort" =>
+ case tk->cmd(t, "variable sort") {
+ "n" => key = readdir->NAME;
+ "a" => key = readdir->ATIME;
+ "m" => key = readdir->MTIME;
+ "s" => key = readdir->SIZE;
+ }
+ (de, nde) = readdir->sortdir(de, key);
+ "walk" =>
+ walk = !walk;
+ continue f;
+ "text" =>
+ usetext = 1;
+ "icon" =>
+ usetext = 0;
+ }
+ tk->cmd(t, ".fc.c delete all");
+ drawdir(t);
+ tk->cmd(t, ".fc.c yview moveto 0; update");
+ action := <-filecmd =>
+ nd := int action[1:];
+ if(nd > len de)
+ break;
+ case action[0] {
+ '1' =>
+ button1(t, de[nd]);
+ '3' =>
+ button3(t, de[nd]);
+ }
+ }
+}
+
+getdir(t: ref Toplevel, dir: string)
+{
+ if(dir == "")
+ dir = "/";
+
+ path = dir;
+ if (path[len path - 1] != '/')
+ path[len path] = '/';
+
+ (de, nde) = readdir->init(path, key);
+ if(nde < 0) {
+ dialog->prompt(ctxt, t.image, "error -fg red",
+ "Read directory",
+ sys->sprint("Error reading \"%s\"\n%r", path),
+ 0, "Exit"::nil);
+ exit;
+ }
+
+ if(path != "/") {
+ (ok, d) := sys->stat("..");
+ if(ok >= 0) {
+ dot := array[nde+1] of ref Dir;
+ dot[0] = ref d;
+ dot[0].name = "..";
+ dot[1:] = de;
+ de = dot;
+ nde++;
+ }
+ }
+
+ for(i := 0; i < nde; i++) {
+ s := de[i].name;
+ l := len s;
+ if(l > 4 && s[l-4:] == ".dis")
+ de[i].mode |= 8r111;
+ }
+ tkclient->settitle(t, sysnam+path);
+}
+
+defcursor(t: ref Toplevel)
+{
+ tk->cmd(t, "cursor -default");
+}
+
+button1(t: ref Toplevel, item: ref Dir)
+{
+ mod: Wm;
+
+ tk->cmd(t, "cursor -image waiting");
+ npath := path;
+ name := item.name + "/";
+ if(item.name == "..") {
+ i := len path - 2;
+ while(i > 0 && path[i] != '/')
+ i--;
+ npath = path[0:i];
+ name = "/";
+ }
+
+ exec := npath+name[0:len name-1];
+ ft := filetype(t, item, exec);
+
+ if(item.mode & Sys->DMDIR) {
+ if(walk != 0) {
+ path = npath;
+ getdir(t, npath+name);
+ tk->cmd(t, ".fc.c delete all");
+ drawdir(t);
+ tk->cmd(t, ".fc.c yview moveto 0; update");
+ defcursor(t);
+ return;
+ }
+ mod = load Wm "/dis/wm/dir.dis";
+ defcursor(t);
+ if(mod == nil) {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Load Dir module",
+ sys->sprint("Error: %r"),
+ 0, "Continue"::nil);
+ return;
+ }
+ args := npath+name :: nil;
+ args = cmdname :: args;
+ spawn mod->init(ctxt, args);
+ return;
+ }
+
+ cmd := ft.cmd;
+ if(cmd == nil)
+ cmd = npath+name;
+
+ mod = load Wm cmd;
+ defcursor(t);
+ if(mod == nil) {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Load Module",
+ sys->sprint("Trying to load \"%s\"\n%r", cmd),
+ 0, "Continue"::nil);
+ return;
+ }
+ if(ft.givearg)
+ spawn applinit(mod, ctxt, item.name :: exec :: nil);
+ else
+ spawn applinit(mod, ctxt, item.name :: nil);
+}
+
+applinit(mod: Wm, ctxt: ref Draw->Context, args: list of string)
+{
+ sys->pctl(sys->NEWPGRP|sys->FORKFD, nil);
+ spawn mod->init(ctxt, args);
+}
+
+
+button3(nil: ref Toplevel, stat: ref Sys->Dir)
+{
+ if(!plumbed)
+ return;
+ msg := ref Msg(
+ "WmDir",
+ "",
+ path,
+ "text",
+ "",
+ array of byte stat.name);
+
+ msg.send();
+}
+
+filetype(t: ref Toplevel, d: ref Dir, path: string): ref Ft
+{
+ if(d.mode & Sys->DMDIR)
+ return loadtype(t, dirtype);
+
+ suffix := "";
+ for(j := len path-2; j >= 0; j--) {
+ if(path[j] == '.') {
+ suffix = path[j+1:];
+ break;
+ }
+ }
+
+ if(suffix == "")
+ return loadtype(t, deftype);
+
+ if(suffix[0] >= 'A' && suffix[0] <= 'Z') {
+ for(j = 0; j < len suffix; j++)
+ suffix[j] += ('A' - 'a');
+ }
+
+ for(i := 0; i<len filetypes; i++) {
+ if(suffix == filetypes[i].ext)
+ return loadtype(t, filetypes[i]);
+ }
+
+ return loadtype(t, deftype);
+}
+
+loadtype(t: ref Toplevel, ft: ref Ft): ref Ft
+{
+ if(ft.loaded)
+ return ft;
+
+ s := sys->sprint("image create bitmap %s -file %s.bit -maskfile %s.mask",
+ ft.tkname, ft.icon, ft.icon);
+ tk->cmd(t, s);
+
+ ft.loaded = 1;
+ return ft;
+}
+
+drawdir(t: ref Toplevel)
+{
+ if(usetext)
+ drawdirtxt(t);
+ else
+ drawdirico(t);
+}
+
+drawdirtxt(t: ref Toplevel)
+{
+ if(daytime == nil) {
+ daytime = load Daytime Daytime->PATH;
+ if(daytime == nil) {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Load Module",
+ sys->sprint("Trying to load \"%s\"\n%r", Daytime->PATH),
+ 0, "Continue"::nil);
+ return;
+ }
+ now = daytime->now();
+ }
+
+ y := 10;
+ for(i := 0; i < nde; i++) {
+ tp := "file";
+ if(de[i].mode & Sys->DMDIR)
+ tp = "dir ";
+ else
+ if(de[i].mode & 8r111)
+ tp = "exe ";
+ s := sys->sprint("%s %7bd %s %s",
+ tp,
+ de[i].length,
+ daytime->filet(now, de[i].mtime),
+ de[i].name);
+ id := tk->cmd(t, ".fc.c create text 10 "+string y+
+ " -anchor w -text {"+s+"}");
+
+ base := ".fc.c bind "+id;
+ tk->cmd(t, base+" <Double-Button-1> {send fc %b "+string i+"}");
+ tk->cmd(t, base+" <Button-3> {send fc %b "+string i+"}");
+ tk->cmd(t, base+" <Motion-Button-3> {}");
+ y += 15;
+ }
+
+ x := int tk->cmd(t, ".fc.c cget actwidth");
+ tk->cmd(t, ".fc.c configure -scrollregion { 0 0 "+string x+" "+string y+"}");
+}
+
+drawdirico(t: ref Toplevel)
+{
+ w := int tk->cmd(t, ".fc.c cget actwidth");
+
+ longest := 0;
+ for(i := 0; i < nde; i++) {
+ l := len de[i].name;
+ if(l > longest)
+ longest = l;
+ }
+ longest += 2;
+
+ minw := (longest*Fontwidth);
+ if( w < minw ){
+ w = minw + int tk->cmd(t, ".fc.scroll cget actwidth");
+ tk->cmd(t, ". configure -width "+string w);
+ w = minw;
+ }
+
+ xwid := Xwidth;
+ x := w/minw;
+ x = w/x;
+ if(x > xwid)
+ xwid = x;
+
+ x = xwid/2;
+ y := 20;
+
+ for(i = 0; i < nde; i++) {
+ sx := string x;
+ ft := filetype(t, de[i], de[i].name);
+ img := ft.tkname;
+
+ id := tk->cmd(t, ".fc.c create image "+sx+" "+
+ string y+" -image "+img);
+ tk->cmd(t, ".fc.c create text "+sx+
+ " "+string (y+25)+" -text "+de[i].name);
+
+ base := ".fc.c bind "+id;
+ tk->cmd(t, base+" <Double-Button-1> {send fc %b "+string i+"}");
+ tk->cmd(t, base+" <Button-2> {send fc %b "+string i+"}");
+ tk->cmd(t, base+" <Motion-Button-2> {}");
+ tk->cmd(t, base+" <Button-3> {send fc %b "+string i+"}");
+ tk->cmd(t, base+" <Motion-Button-3> {}");
+ x += xwid;
+ if(x > w) {
+ x = xwid/2;
+ y += 50;
+ }
+ }
+ y += 50;
+ x = int tk->cmd(t, ".fc.c cget actwidth");
+ tk->cmd(t, ".fc.c configure -scrollregion { 0 0 "+string x+" "+string y+"}");
+}
+
+sysname(): string
+{
+ syspath := "#c";
+ if ( cmdname == "wmdir" )
+ syspath = "/n/dev";
+ fd := sys->open(syspath+"/sysname", sys->OREAD);
+ if(fd == nil)
+ return "Anon";
+ buf := array[128] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return "Anon";
+ return string buf[0:n];
+}
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
diff --git a/appl/wm/edit.b b/appl/wm/edit.b
new file mode 100644
index 00000000..dd6e8229
--- /dev/null
+++ b/appl/wm/edit.b
@@ -0,0 +1,730 @@
+#
+# Copyright © 1996-1999 Lucent Technologies Inc. All rights reserved.
+# Modified version of edit
+# D.B.Knudsen
+# Revisions Copyright © 2000-2002 Vita Nuova Holdings Limited. All rights reserved.
+#
+implement WmEdit;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Rect, Screen: import draw;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "dialog.m";
+ dialog: Dialog;
+
+include "selectfile.m";
+ selectfile: Selectfile;
+
+WmEdit: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+ErrIco: con "error -fg red";
+
+ed: ref Tk->Toplevel;
+dirty := 0;
+
+BLUE : con "#0000ff";
+GREEN : con "#008800";
+
+SEARCH,
+SEARCHFOR,
+REPLACE,
+REPLACEWITH,
+REPLACEALL,
+NOSEE : con iota;
+
+ed_config := array[] of {
+ "frame .m -relief raised -bd 2",
+ "frame .b",
+ "menubutton .m.file -text File -menu .m.file.menu",
+ "menubutton .m.edit -text Edit -menu .m.edit.menu",
+ "menubutton .m.search -text Search -menu .m.search.menu",
+ "menubutton .m.options -text Options -menu .m.options.menu",
+# "label .m.filename",
+ "pack .m.file .m.edit .m.search .m.options -side left",
+# "pack .m.filename -padx 10 -side left",
+ "menu .m.file.menu",
+ ".m.file.menu add command -label New -command {send c new}",
+ ".m.file.menu add command -label Open... -command {send c open}",
+ ".m.file.menu add separator",
+ ".m.file.menu add command -label Save -command {send c save}",
+ ".m.file.menu add command -label {Save As...} -command {send c saveas}",
+ ".m.file.menu add separator",
+ ".m.file.menu add command -label {Exit} -command {send c exit}",
+ "menu .m.edit.menu",
+ ".m.edit.menu add command -label Cut -command {send c cut}",
+ ".m.edit.menu add command -label Copy -command {send c copy}",
+ ".m.edit.menu add command -label Paste -command {send c paste}",
+ "menu .m.search.menu",
+ ".m.search.menu add command -label {Find ...} " +
+ "-command {send c searchf}",
+ ".m.search.menu add command -label {Replace with...} " +
+ "-command {send c replacew}",
+ ".m.search.menu add command -label {Find Again} -command {send c search}",
+ ".m.search.menu add command -label {Find and Replace} " +
+ "-command {send c replace}",
+ ".m.search.menu add command -label {Find and Replace All} " +
+ "-command {send c replaceall}",
+ "menu .m.options.menu",
+ ".m.options.menu add checkbutton -text Limbo -command {send c limbo}",
+ ".m.options.menu add command -label Indent -command {send c indent}",
+ "text .b.t -yscrollcommand {.b.s set} -bg white",
+ "bind .b.t <Button-2> {.m.edit.menu post %X %Y}",
+ "bind .b.t <Key> +{send c dirtied {%A}}",
+ "bind .b.t <ButtonRelease-1> +{send c reindent}",
+ "scrollbar .b.s -command {.b.t yview}",
+ "pack .m -fill x",
+ "pack .b.s -fill y -side left",
+ "pack .b.t -fill both -expand 1",
+ "pack .b -fill both -expand 1",
+ "focus .b.t",
+ "pack propagate . 0",
+ ".b.t tag configure keyword -fg " + BLUE,
+ ".b.t tag configure comment -fg " + GREEN,
+ "update",
+};
+
+context : ref Draw->Context;
+curfile := "(New)";
+snarf := "";
+searchfor := "";
+replacewith := "";
+path := ".";
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ wmctl: chan of string;
+
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ selectfile = load Selectfile Selectfile->PATH;
+ dialog = load Dialog Dialog->PATH;
+
+ sys->pctl(Sys->NEWPGRP, nil);
+ tkclient->init();
+ selectfile->init();
+ dialog->init();
+
+ context = ctxt;
+
+ (ed, wmctl) = tkclient->toplevel(context, "", "Edit", Tkclient->Appl);
+
+ argv = tl argv;
+
+ c := chan of string;
+ tk->namechan(ed, c, "c");
+ for (i := 0; i < len ed_config; i++)
+ cmd(ed, ed_config[i]);
+
+ if (argv != nil) {
+ e := loadtfile(hd argv);
+ if(e != nil)
+ dialog->prompt(ctxt, ed.image, ErrIco, "Open file", e, 0, "Ok"::nil);
+ }
+
+ tkclient->settitle(ed, "Edit " + curfile);
+ tkclient->onscreen(ed, nil);
+ tkclient->startinput(ed, "ptr" :: "kbd" :: nil);
+ cmd(ed, "update");
+
+ e := cmd(ed, "variable lasterror");
+ if(e != "") {
+ sys->print("edit error: %s\n", e);
+ return;
+ }
+
+ cmdloop: for(;;) {
+ alt {
+ key := <-ed.ctxt.kbd =>
+ tk->keyboard(ed, key);
+ m := <-ed.ctxt.ptr =>
+ tk->pointer(ed, *m);
+ s := <-ed.ctxt.ctl or
+ s = <-ed.wreq or
+ s = <-wmctl =>
+ if(s == "exit") {
+ if (check_dirty())
+ break cmdloop;
+ else
+ break;
+ }
+ task_title: string;
+ if (s == "task") {
+ if (curfile == "(New)")
+ task_title = tkclient->settitle(ed, "Edit");
+ else
+ task_title = tkclient->settitle(ed, "Edit " + curfile);
+ cmd(ed, "update");
+ }
+ tkclient->wmctl(ed, s);
+ if (s == "task")
+ tkclient->settitle(ed, task_title);
+ s := <-c =>
+ if ( len s > 7 && s[:7] == "dirtied" ) {
+ set_dirty(); do_limbo_check(s);
+ }
+ else
+ case s {
+ "exit" => if ( check_dirty() ){ set_clean(); break cmdloop; }
+ "dirtied" => set_dirty(); do_limbo_check(s);
+ "new" => if ( check_dirty()) {set_clean(); do_new();}
+ "open" => if ( check_dirty() && do_open()) set_clean();
+ "save" => do_save(0);
+ "saveas" => do_save(1);
+ "cut" => do_snarf(1); set_dirty();
+ "copy" => do_snarf(0);
+ "paste" => do_paste(); set_dirty();
+ "search" => do_search(SEARCH);
+ "searchf" => do_search(SEARCHFOR);
+ "replace" => do_replace(REPLACE);
+ "replacew" => do_replace(REPLACEWITH);
+ "replaceall" => do_replaceall();
+ "limbo" => do_limbo();
+ "indent" => do_indent();
+ "reindent" => re_indent();
+ }
+ cmd(ed, "focus .b.t");
+ }
+ cmd(ed, "update");
+ e = cmd(ed, "variable lasterror");
+ if(e != "") {
+ sys->print("edit error: %s\n", e);
+ break cmdloop;
+ }
+ }
+}
+
+check_dirty() : int
+{
+ if ( dirty == 0 )
+ return 1;
+ if (dialog->prompt(context, ed.image, ErrIco, "Confirm",
+ "File was changed.\nDiscard changes?",
+ 0, "Yes" :: "No" :: nil) == 0 ) {
+ return 1;
+ }
+ return 0;
+}
+
+set_dirty()
+{
+ if(!dirty){
+ dirty = 1;
+ tkclient->settitle(ed, "Edit " + curfile + " (dirty)");
+ cmd(ed, "update");
+ }
+# We want to just remove the binding, but Inferno's tk does not
+# recognize the - in front of the command. To make it do so would
+# require changes to utils.c and ebind.c in /tk
+# cmd(ed, "bind .b.t <Key> -{send c dirtied}");
+}
+
+set_clean()
+{
+ if(dirty){
+ dirty = 0;
+ tkclient->settitle(ed, "Edit " + curfile);
+ cmd(ed, "update");
+ #cmd(ed, "bind .b.t <Key> +{send c dirtied}");
+ }
+}
+
+BLOCK, TEMP : con iota;
+is_limbo := 0; # initially not limbo
+this_word := "";
+last_keyword := "";
+in_comment := 0;
+first_char := 1;
+indent : list of int;
+last_kw_is_block := 0;
+tab := "\t";
+tabs := array[] of {
+ "", "\t", "\t\t", "\t\t\t", "\t\t\t\t", "\t\t\t\t\t",
+ "\t\t\t\t\t\t", "\t\t\t\t\t\t\t", "\t\t\t\t\t\t\t\t"
+};
+
+keywords := array[] of {
+ "adt", "alt", "array", "big", "break",
+ "byte", "case", "chan", "con", "continue",
+ "cyclic", "do", "else", "exit", "fn",
+ "for", "hd", "if", "implement", "import",
+ "include", "int", "len", "list", "load",
+ "module", "nil", "of", "or", "pick",
+ "real", "ref", "return", "self", "spawn",
+ "string", "tagof", "tl", "to", "type",
+ "while"
+};
+block_keyword := (big 1 << 40 ) | big (1 << 17) | big (1 << 15) |
+ big (1 << 12) | big (1 << 11);
+
+do_limbo()
+{
+ is_limbo = !is_limbo;
+ if ( is_limbo )
+ mark_keyw_comm();
+ else {
+ cmd(ed, ".b.t tag remove comment 1.0 end");
+ cmd(ed, ".b.t tag remove keyword 1.0 end");
+ }
+}
+
+do_limbo_check(s : string)
+{
+ if ( ! is_limbo )
+ return;
+ if ( len s < 11 )
+ return;
+#
+# Maybe we should actually remember where the insert point is.
+# In general we can get it via .b.t index insert, but for most
+# characters, we could maintain the position with simple arithmetic.
+#
+# Also, we need to insert code in cut and paste operations to keep
+# track of various things when in limbo mode. Also need to catch
+# text deletions via typeover of selection.
+#
+ char := s[9];
+ if ( char == '\\' && len s > 10 )
+ char = s[10];
+ case char {
+ ' ' or '\t' =>
+ if ( ! in_comment )
+ look_keyword(this_word);
+ this_word = "" ;
+ '\n' =>
+ if ( in_comment ) {
+ # terminate current tag
+ cmd(ed, ".b.t tag remove comment insert-1chars");
+ in_comment = 0;
+ }
+ else
+ look_keyword(this_word);
+ this_word = "" ;
+ if ( last_kw_is_block )
+ indent = TEMP :: indent;
+ else while ( indent != nil && hd indent == TEMP )
+ indent = tl indent;
+ last_kw_is_block = 0;
+ add_indent();
+ first_char = 1;
+ return;
+ '{' =>
+ indent = BLOCK :: indent;
+ last_kw_is_block = 0;
+ '}' =>
+ if ( indent != nil )
+ indent = tl indent;
+ last_kw_is_block = 0;
+ # If the line is just indentation plus '}', rewrite it
+ # to have one less indent.
+ if ( first_char ) {
+ current := int cmd(ed, ".b.t index insert");
+ cmd(ed, ".b.t delete " +
+ string current + ".0 insert");
+ add_indent();
+ cmd(ed, ".b.t insert insert '}");
+ }
+# ';' =>
+# last_kw_is_block = 0;
+# '\b' => # By the time we see this, the character has
+# # already been wiped out, probably.
+# # To know what it was we'd need a lastchar,
+# # reset for each mouse button up and \b
+# '\u007f' => # Here, we have to know what used to be ahead of the
+# # insert point.
+ '#' =>
+ # if ( ! in_quote ) {
+ # cmd(ed, ".b.t tag add comment insert-1chars");
+ in_comment = 1;
+ # }
+ 'A' to 'Z' or 'a' to 'z' or '0' to '9' or '_' =>
+ if ( ! in_comment )
+ this_word[len this_word] = char;
+ * =>
+ if ( ! in_comment )
+ look_keyword(this_word);
+ this_word = "";
+ }
+ if ( in_comment )
+ cmd(ed, ".b.t tag add comment insert-1chars");
+ first_char = 0;
+}
+
+look_keyword(word : string)
+{
+ # compare this_word to all keywords
+ if ( is_keyword(word) ) {
+ cmd(ed, ".b.t tag add keyword insert-" +
+ string (len this_word + 1) + "chars insert-1chars");
+ }
+}
+
+is_keyword(word : string) : int
+{
+ l := len keywords;
+ for ( i := 0; i < l; i++ )
+ if ( word == keywords[i] ) {
+ if ( i != 26 ) # don't set for 'nil'
+ last_kw_is_block = int (block_keyword >> i) & 1;
+ return 1;
+ }
+ return 0;
+}
+
+do_new()
+{
+ cmd(ed, ".b.t delete 1.0 end");
+ curfile = "(New)";
+ tkclient->settitle(ed, "Edit " + curfile);
+}
+
+do_open(): int
+{
+ for(;;) {
+ fname := selectfile->filename(context, ed.image, "", nil, path);
+ if(fname == "")
+ break;
+ cmd(ed, ".b.t delete 1.0 end");
+ e := loadtfile(fname);
+ if(e == nil) {
+ basepath(fname);
+ return 1;
+ }
+
+ options := list of {
+ "Cancel",
+ "Open another file"
+ };
+
+ if(dialog->prompt(context, ed.image, ErrIco, "Open file", e, 0, options) == 0)
+ break;
+ }
+ return 0;
+}
+
+basepath(file: string)
+{
+ for(i := len file-1; i >= 0; i--)
+ if(file[i] == '/') {
+ path = file[0:i];
+ break;
+ }
+}
+
+do_save(prompt: int)
+{
+ fname := curfile;
+
+ contents := tk->cmd(ed, ".b.t get 1.0 end");
+ for(;;) {
+ if(prompt || curfile == "(New)") {
+ fname = dialog->getstring(context, ed.image, "File");
+ if ( len fname > 0 && fname[0] != '/' && path != "" )
+ fname = path + "/" + fname;
+ }
+
+ if(savetfile(fname, contents)) {
+ set_clean();
+ break;
+ }
+
+ options := list of {
+ "Cancel",
+ "Try another file"
+ };
+
+ msg := sys->sprint("Trying to write file \"%s\"\n%r", fname);
+ if(dialog->prompt(context, ed.image, ErrIco, "Save file", msg, 0, options) == 0)
+ break;
+
+ prompt = 1;
+ }
+}
+
+do_snarf(del: int)
+{
+ range := cmd(ed, ".b.t tag nextrange sel 1.0");
+ if(range == "" || (len range > 0 && range[0] == '!'))
+ return;
+ snarf = tk->cmd(ed, ".b.t get " + range);
+ if(del)
+ cmd(ed, ".b.t delete " + range);
+ tkclient->snarfput(snarf);
+}
+
+do_paste()
+{
+ snarf = tkclient->snarfget();
+ if(snarf == "")
+ return;
+ cmd(ed, ".b.t insert insert '" + snarf);
+}
+
+do_search(prompt: int) : int
+{
+ if(prompt == SEARCHFOR)
+ searchfor = dialog->getstring(context, ed.image, "Search For");
+ if(searchfor == "")
+ return 0;
+ cmd(ed, "cursor -bitmap cursor.wait");
+ ix := cmd(ed, ".b.t search -- " + tk->quote(searchfor) + " insert+1c");
+ if(ix != "" && len ix > 1 && ix[0] != '!') {
+ cmd(ed, ".b.t tag remove sel 0.0 end");
+ cmd(ed, ".b.t mark set anchor " + ix);
+ cmd(ed, ".b.t mark set insert " + ix);
+ cmd(ed, ".b.t tag add sel " + ix + " " + ix + "+" +
+ string(len searchfor) + "c");
+ if ( prompt != NOSEE )
+ cmd(ed, ".b.t see " + ix);
+ cmd(ed, "cursor -default");
+ return 1;
+ }
+ cmd(ed, "cursor -default");
+ return 0;
+}
+
+do_replace(prompt : int)
+{
+ range := "";
+ if ( prompt == REPLACEWITH ) {
+ replacewith = dialog->getstring(context, ed.image, "Replacement String");
+
+ range = cmd(ed, ".b.t tag nextrange sel 1.0");
+ if(range == "" || (len range > 0 && range[0] == '!'))
+ return; # nothing currently selected
+ }
+ if ( range != "" ) { # there's something selected
+ cmd(ed, ".b.t mark set insert sel.first");
+ }
+ else { # have to find a string
+ if ( searchfor == "" ) { # no search string!
+ if ( do_search(SEARCHFOR) == 0 )
+ return;
+ }
+ else if ( do_search(SEARCH) == 0 )
+ return;
+ }
+ cmd(ed, ".b.t delete sel.first sel.last");
+ cmd(ed, ".b.t insert insert " + tk->quote(replacewith));
+}
+
+do_replaceall()
+{
+ cur := cmd(ed, ".b.t index insert");
+ if ( cur == "" || cur[0] == '!' )
+ return;
+ dirt := 0;
+ if ( searchfor == "" ) # no search string
+ searchfor = dialog->getstring(context, ed.image, "Search For");
+ if ( searchfor == "" ) # still no search string
+ return;
+ srch := tk->quote(searchfor);
+ repl := tk->quote(replacewith);
+ for ( ix := "1.0"; len ix > 0 && ix[0] != '!'; ) {
+ ix = cmd(ed, ".b.t search -- " + srch + " " + ix + " end");
+ if ( ix == "" || len ix <= 1 || ix[0] == '!')
+ break;
+ cmd(ed, ".b.t delete " + ix + " " + ix + "+" +
+ string(len searchfor) + "c");
+ if ( replacewith != "" ) {
+ cmd(ed, ".b.t insert " + ix + " " + repl);
+ ix = cmd(ed, ".b.t index " + ix + "+" +
+ string(len replacewith) + "c");
+ }
+ dirt++;
+ }
+ cmd(ed, ".b.t mark set insert " + cur);
+ if ( dirt > 0 )
+ set_dirty();
+}
+
+
+loadtfile(path: string): string
+{
+ if ( path != nil && path[0] == '/' )
+ basepath(path);
+ fd := sys->open(path, sys->OREAD);
+ if(fd == nil)
+ return "Can't open "+path+", the error was:\n"+sys->sprint("%r");
+ (ok, d) := sys->fstat(fd);
+ if(ok < 0)
+ return "Can't stat "+path+", the error was:\n"+sys->sprint("%r");
+ if(d.mode & Sys->DMDIR)
+ return path+" is a directory";
+
+ cmd(ed, "cursor -bitmap cursor.wait");
+ BLEN: con 8192;
+ buf := array[BLEN+Sys->UTFmax] of byte;
+ inset := 0;
+ for(;;) {
+ n := sys->read(fd, buf[inset:], BLEN);
+ if(n <= 0)
+ break;
+ n += inset;
+ nutf := sys->utfbytes(buf, n);
+ s := string buf[0:nutf];
+ # move any partial rune to beginning of buffer
+ inset = n-nutf;
+ buf[0:] = buf[nutf:n];
+ cmd(ed, ".b.t insert end '" + s);
+ }
+ if ( is_limbo )
+ mark_keyw_comm();
+ curfile = path;
+ tkclient->settitle(ed, "Edit " + curfile);
+ cmd(ed, "cursor -default");
+ cmd(ed, "update");
+ return "";
+}
+
+savetfile(path: string, contents: string): int
+{
+ buf := array of byte contents;
+ n := len buf;
+
+ fd := sys->create(path, sys->OWRITE, 8r664);
+ if(fd == nil)
+ return 0;
+ i := sys->write(fd, buf, n);
+ if(i != n) {
+ sys->print("savetfile only wrote %d of %d: %r\n", i, n);
+ return 0;
+ }
+ curfile = path;
+# cmd(ed, ".m.filename configure -text '" + curfile);
+ tkclient->settitle(ed, "Edit " + curfile);
+
+ return 1;
+}
+
+mark_keyw_comm()
+{
+ quote := 0;
+ start : int;
+ notkey := 0;
+ word : string;
+
+ last := int cmd(ed, ".b.t index end");
+ for ( i := 1; i <= last; i++ ) {
+ quote = 0;
+ word = "";
+ line := tk->cmd(ed, ".b.t get " + string i + ".0 " +
+ string (i+1) + ".0");
+ l := len line;
+ll : for ( j := 0; j < l; j++ ) {
+ c := line[j];
+ if ( quote && (c = line[j]) != quote )
+ continue;
+ case c {
+ '#' =>
+ cmd(ed, sys->sprint(".b.t tag add comment" +
+ " %d.%d %d.%d", i, j, i, l));
+ break ll;
+ '\'' or '\"' =>
+ if ( j != 0 && line[j-1] == '\\' )
+ break;
+ if ( c == quote )
+ quote = 0;
+ else
+ quote = line[j];
+ word = "";
+ 'a' to 'z' =>
+ if ( word == "" )
+ start = j;
+ word[len word] = c;
+ 'A' to 'Z' or '_' =>
+ notkey = 1;
+ continue;
+ * =>
+ if ( ! notkey && is_keyword(word) )
+ cmd(ed, ".b.t tag add keyword " +
+ sys->sprint("%d.%d %d.%d",
+ i, start, i, j));
+ word = "";
+ notkey = 0;
+ }
+ }
+ }
+}
+
+do_indent()
+{
+ for ( ; ; ) {
+ tab = dialog->getstring(context, ed.image, "single indent");
+ break;
+ }
+ for ( i := 1; i <= 8; i++ ) {
+ s := "";
+ for ( j := i; j > 0; j-- )
+ s += tab;
+ tabs[i] = collapse(s);
+ }
+}
+
+collapse(s : string) : string
+{
+ if ( len s >= 8 && s[0:8] == " " )
+ return "\t" + collapse(s[8:]);
+ return s;
+}
+
+add_indent()
+{
+ for ( i := len indent; i >= 8; i -= 8 )
+ cmd(ed, ".b.t insert insert '" + tabs[8]);
+ cmd(ed, ".b.t insert insert '" + tabs[i]);
+}
+#
+# We should also look at the previous line, maybe.
+# And the line after. That may be too much.
+#
+# This is also the logical place to check if we are in a keyword,
+# reinitialize this_word (which presents problems if we are in the
+# middle of a word, etc.) Also check if we are in a comment or not.
+#
+re_indent()
+{
+ pos := cmd(ed, ".b.t index insert");
+ (n, lc) := sys->tokenize(pos, ".");
+ if ( n < 2 )
+ return;
+ init := tk->cmd(ed, ".b.t get " + hd lc + ".0 insert");
+ l := len init;
+ for ( i := 8; i > 0; i-- ) {
+ lt := len tabs[i];
+ if ( l >= lt && init[:lt] == tabs[i] )
+ break;
+ }
+ for ( indent = nil; len indent < i; indent = 0 :: indent) ;
+
+ in_comment = 0; # Are we in a comment?
+ for ( i = len tabs[i]; i < l; i++ )
+ if ( init[i] == '#' ) {
+ in_comment = 1;
+ break;
+ }
+}
+
+cmd(win: ref Tk->Toplevel, s: string): string
+{
+# sys->print("%s\n", s);
+ r := tk->cmd(win, s);
+ if (r != nil && r[0] == '!') {
+ sys->print("wm/edit: error executing '%s': %s\n", s, r);
+ }
+ return r;
+}
diff --git a/appl/wm/filename.b b/appl/wm/filename.b
new file mode 100644
index 00000000..56b1203a
--- /dev/null
+++ b/appl/wm/filename.b
@@ -0,0 +1,74 @@
+implement Filename;
+
+include "sys.m";
+ sys: Sys;
+ stderr: ref Sys->FD;
+include "draw.m";
+ draw: Draw;
+ Rect: import draw;
+include "tk.m";
+include "selectfile.m";
+ selectfile: Selectfile;
+
+include "arg.m";
+
+Filename: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+usage()
+{
+ sys->fprint(stderr, "usage: filename [-g geom] [-d startdir] [pattern...]\n");
+ raise "fail:usage";
+}
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ draw = load Draw Draw->PATH;
+ selectfile = load Selectfile Selectfile->PATH;
+ if (selectfile == nil) {
+ sys->fprint(stderr, "selectfile: cannot load %s: %r\n", Selectfile->PATH);
+ raise "fail:bad module";
+ }
+ arg := load Arg Arg->PATH;
+ if (arg == nil) {
+ sys->fprint(stderr, "filename: cannot load %s: %r\n", Arg->PATH);
+ raise "fail:bad module";
+ }
+
+ if (ctxt == nil) {
+ sys->fprint(stderr, "filename: no window context\n");
+ raise "fail:bad context";
+ }
+
+ sys->pctl(Sys->NEWPGRP, nil);
+ selectfile->init();
+
+ startdir := ".";
+# geom := "-x " + string (ctxt.screen.image.r.dx() / 5) +
+# " -y " + string (ctxt.screen.image.r.dy() / 5);
+ title := "Select a file";
+ arg->init(argv);
+ while (opt := arg->opt()) {
+ case opt {
+# 'g' =>
+# geom = arg->arg();
+ 'd' =>
+ startdir = arg->arg();
+ 't' =>
+ title = arg->arg();
+ * =>
+ sys->fprint(stderr, "filename: unknown option -%c\n", opt);
+ usage();
+ }
+ }
+ if (startdir == nil || title == nil)
+ usage();
+# top := tk->toplevel(ctxt.screen, geom);
+ argv = arg->argv();
+ arg = nil;
+ sys->print("%s\n", selectfile->filename(ctxt, nil, title, argv, startdir));
+}
diff --git a/appl/wm/ftree/cptree.b b/appl/wm/ftree/cptree.b
new file mode 100644
index 00000000..5af59fff
--- /dev/null
+++ b/appl/wm/ftree/cptree.b
@@ -0,0 +1,136 @@
+implement Cptree;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "readdir.m";
+ readdir: Readdir;
+include "cptree.m";
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ readdir = load Readdir Readdir->PATH;
+}
+
+Context: adt {
+ progressch: chan of string;
+ warningch: chan of (string, chan of int);
+ finishedch: chan of string;
+};
+
+# recursively copy file/directory f into directory d;
+# the name remains the same.
+copyproc(f, d: string, progressch: chan of string,
+ warningch: chan of (string, chan of int),
+ finishedch: chan of string)
+{
+ ctxt := ref Context(progressch, warningch, finishedch);
+ (fok, fstat) := sys->stat(f);
+ if (fok == -1)
+ error(ctxt, sys->sprint("cannot stat '%s': %r", f));
+ (dok, dstat) := sys->stat(d);
+ if (dok == -1)
+ error(ctxt, sys->sprint("cannot stat '%s': %r", d));
+ if ((dstat.mode & Sys->DMDIR) == 0)
+ error(ctxt, sys->sprint("'%s' is not a directory", d));
+ if (fstat.qid.path == dstat.qid.path)
+ error(ctxt, sys->sprint("'%s' and '%s' are identical", f, d));
+
+ c := d + "/" + fname(f);
+ (cok, cstat) := sys->stat(c);
+ if (cok == 0)
+ error(ctxt, sys->sprint("'%s' already exists", c));
+ rcopy(ctxt, f, ref fstat, c);
+ finishedch <-= nil;
+}
+
+rcopy(ctxt: ref Context, src: string, srcstat: ref Sys->Dir, dst: string)
+{
+ omode := Sys->OWRITE;
+ perm := srcstat.mode;
+ if (perm & Sys->DMDIR) {
+ omode = Sys->OREAD;
+ perm |= 8r300;
+ }
+
+ dstfd := sys->create(dst, omode, perm);
+ if (dstfd == nil) {
+ warning(ctxt, sys->sprint("cannot create '%s': %r", dst));
+ return;
+ }
+ if (srcstat.mode & Sys->DMDIR) {
+ (entries, n) := readdir->init(src, Readdir->NAME | Readdir->COMPACT);
+ if (n == -1)
+ warning(ctxt, sys->sprint("cannot read dir '%s': %r", src));
+ for (i := 0; i < len entries; i++) {
+ e := entries[i];
+ rcopy(ctxt, src + "/" + e.name, e, dst + "/" + e.name);
+ }
+ if (perm != srcstat.mode) {
+ (ok, nil) := sys->fstat(dstfd);
+ if (ok != -1) {
+ dststat := sys->nulldir;
+ dststat.mode = srcstat.mode;
+ sys->fwstat(dstfd, dststat);
+ }
+ }
+ } else {
+ srcfd := sys->open(src, Sys->OREAD);
+ if (srcfd == nil) {
+ sys->remove(dst);
+ warning(ctxt, sys->sprint("cannot open '%s': %r", src));
+ return;
+ }
+ ctxt.progressch <-= "copying " + src;
+ buf := array[Sys->ATOMICIO] of byte;
+ while ((n := sys->read(srcfd, buf, len buf)) > 0) {
+ if (sys->write(dstfd, buf, n) != n) {
+ sys->remove(dst);
+ warning(ctxt, sys->sprint("error writing '%s': %r", dst));
+ return;
+ }
+ }
+ if (n == -1) {
+ sys->remove(dst);
+ warning(ctxt, sys->sprint("error reading '%s': %r", src));
+ return;
+ }
+ }
+}
+
+warning(ctxt: ref Context, msg: string)
+{
+ r := chan of int;
+ ctxt.warningch <-= (msg, r);
+ if (!<-r)
+ exit;
+}
+
+error(ctxt: ref Context, msg: string)
+{
+ ctxt.finishedch <-= msg;
+ exit;
+}
+
+fname(f: string): string
+{
+ f = cleanname(f);
+ for (i := len f - 1; i >= 0; i--)
+ if (f[i] == '/')
+ break;
+ return f[i+1:];
+}
+
+cleanname(s: string): string
+{
+ t := "";
+ i := 0;
+ while (i < len s)
+ if ((t[len t] = s[i++]) == '/')
+ while (i < len s && s[i] == '/')
+ i++;
+ if (len t > 1 && t[len t - 1] == '/')
+ t = t[0:len t - 1];
+ return t;
+}
diff --git a/appl/wm/ftree/cptree.m b/appl/wm/ftree/cptree.m
new file mode 100644
index 00000000..874a66a1
--- /dev/null
+++ b/appl/wm/ftree/cptree.m
@@ -0,0 +1,8 @@
+Cptree: module {
+ PATH: con "/dis/lib/ftree/cptree.dis";
+ init: fn();
+ copyproc: fn(f, d: string, progressch: chan of string,
+ warningch: chan of (string, chan of int),
+ finishedch: chan of string);
+};
+
diff --git a/appl/wm/ftree/ftree.b b/appl/wm/ftree/ftree.b
new file mode 100644
index 00000000..d70629d0
--- /dev/null
+++ b/appl/wm/ftree/ftree.b
@@ -0,0 +1,873 @@
+implement Ftree;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+ Point, Rect: import draw;
+include "tk.m";
+ tk: Tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+include "readdir.m";
+ readdir: Readdir;
+include "items.m";
+ items: Items;
+ Item, Expander: import items;
+include "plumbmsg.m";
+ plumbmsg: Plumbmsg;
+ Msg: import plumbmsg;
+include "sh.m";
+ sh: Sh;
+include "popup.m";
+ popup: Popup;
+include "cptree.m";
+ cptree: Cptree;
+include "string.m";
+ str: String;
+include "arg.m";
+ arg: Arg;
+
+stderr: ref Sys->FD;
+
+Ftree: module {
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Tree: adt {
+ fname: string;
+ pick {
+ L =>
+ N =>
+ e: ref Expander;
+ sub: cyclic array of ref Tree;
+ }
+};
+
+tkcmds := array[] of {
+ "frame .top",
+ "label .top.l -text |",
+ "pack .top.l -side left -expand 1 -fill x",
+ "frame .f",
+ "canvas .c -yscrollcommand {.f.s set}",
+ "scrollbar .f.s -command {.c yview}",
+ "pack .f.s -side left -fill y",
+ "pack .c -side top -in .f -fill both -expand 1",
+ "pack .top -anchor w",
+ "pack .f -fill both -expand 1",
+ "pack propagate . 0",
+ ".top.l configure -text {}",
+};
+
+badmodule(p: string)
+{
+ sys->fprint(stderr, "ftree: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+tkwin: ref Tk->Toplevel;
+root := "/";
+
+cpfile := "";
+
+usage()
+{
+ sys->fprint(stderr, "usage: ftree [-e] [-E] [-p] [-d] [root]\n");
+ raise "fail:usage";
+}
+
+plumbinprogress := 0;
+disallow := 1;
+plumbed: chan of int;
+roottree: ref Tree.N;
+rootitem: Item;
+runplumb := 1;
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ loadmods();
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "ftree: no window context\n");
+ raise "fail:bad context";
+ }
+ draw = load Draw Draw->PATH;
+ noexit := 0;
+ winopts := Tkclient->Resize | Tkclient->Hide;
+ arg->init(argv);
+ while ((opt := arg->opt()) != 0) {
+ case opt {
+ 'e' =>
+ (noexit, winopts) = (1, Tkclient->Resize);
+ 'E' =>
+ (noexit, winopts) = (1, 0);
+ 'p' =>
+ (noexit, winopts) = (0, 0);
+ 'd' =>
+ disallow = 0;
+ 'P' =>
+ runplumb = 1;
+ * =>
+ usage();
+ }
+ }
+ argv = arg->argv();
+ if (argv != nil && tl argv != nil)
+ usage();
+ if (argv != nil) {
+ root = hd argv;
+ (ok, s) := sys->stat(root);
+ if (ok == -1) {
+ sys->fprint(stderr, "ftree: %s: %r\n", root);
+ raise "fail:bad root";
+ } else if ((s.mode & Sys->DMDIR) == 0) {
+ sys->fprint(stderr, "ftree: %s is not a directory\n", root);
+ raise "fail:bad root";
+ }
+ }
+
+ sys->pctl(Sys->NEWPGRP|Sys->FORKNS, nil);
+
+ (win, wmctl) := tkclient->toplevel(ctxt, nil, "Ftree", winopts);
+ tkwin = win;
+ for (i := 0; i < len tkcmds; i++)
+ cmd(win, tkcmds[i]);
+ fittoscreen(win);
+ cmd(win, "update");
+
+ event := chan of string;
+ tk->namechan(win, event, "event");
+
+ clickfile := chan of string;
+ tk->namechan(win, clickfile, "clickfile");
+
+ sys->bind("#s", "/chan", Sys->MBEFORE);
+ fio := sys->file2chan("/chan", "plumbstart");
+ if (fio == nil) {
+ sys->fprint(stderr, "ftree: cannot make /chan/plumbstart: %r\n");
+ raise "fail:error";
+ }
+ nsfio := sys->file2chan("/chan", "nsupdate");
+ if (nsfio == nil) {
+ sys->fprint(stderr, "ftree: cannot make /chan/nsupdate: %r\n");
+ raise "fail:error";
+ }
+
+ if (runplumb){
+ if((err := sh->run(ctxt, "plumber" :: "-n" :: "-w" :: "-c/chan/plumbstart" :: nil)) != nil)
+ sys->fprint(stderr, "ftree: can't start plumber: %s\n", err);
+ }
+
+ plumbmsg = load Plumbmsg Plumbmsg->PATH;
+ if (plumbmsg != nil && plumbmsg->init(1, nil, 0) == -1) {
+ sys->fprint(stderr, "ftree: no plumber\n");
+ plumbmsg = nil;
+ }
+
+ nschanged := chan of string;
+ roottree = ref Tree.N("/", Expander.new(win, ".c"), nil);
+ rootitem = roottree.e.make(items->maketext(win, ".c", "/", "/"));
+ cmd(win, ".c configure -width " + string rootitem.r.dx() + " -height " + string rootitem.r.dy() +
+ " -scrollregion {" + r2s(rootitem.r) + "}");
+ sendevent("/", "expand");
+ tkclient->onscreen(win, nil);
+ tkclient->startinput(win, "ptr"::nil);
+ cmd(win, "update");
+
+ plumbed = chan of int;
+ for (;;) alt {
+ key := <-win.ctxt.kbd =>
+ tk->keyboard(win, key);
+ m := <-win.ctxt.ptr =>
+ tk->pointer(win, *m);
+ s := <-win.ctxt.ctl or
+ s = <-win.wreq or
+ s = <-wmctl =>
+ if (noexit && s == "exit")
+ s = "task";
+ tkclient->wmctl(win, s);
+ s := <-event =>
+ (target, ev) := eventtarget(s);
+ sendevent(target, ev);
+ m := <-clickfile =>
+ (n, toks) := sys->tokenize(m, " ");
+ (b, s) := (hd toks, hd tl toks);
+ if (b == "menu") {
+ c := chan of (ref Tree, Item, chan of Item);
+ nsu := chan of string;
+ spawn menuproc(c, nsu);
+ found := operate(s, c);
+ if (found) {
+ if ((upd := <-nsu) != nil)
+ updatens(upd);
+ }
+ } else if (b == "plumb")
+ plumbit(s);
+ ok := <-plumbed =>
+ colour := "#00ff00";
+ if (!ok)
+ colour = "red";
+ cmd(tkwin, ".c itemconfigure highlight -fill " + colour);
+ cmd(tkwin, "update");
+ plumbinprogress = 0;
+ s := <-nschanged =>
+ sys->print("got nschanged: %s\n", s);
+ updatens(s);
+ (nil, nil, nil, rc) := <-nsfio.read =>
+ if (rc != nil)
+ readreply(rc, nil, "permission denied");
+ (nil, data, nil, wc) := <-nsfio.write =>
+ if (wc == nil)
+ break;
+ s := cleanname(string data);
+ if (len s >= len root && s[0:len root] == root) {
+ s = s[len root:];
+ if (s == nil)
+ s = "/";
+ if (s[0] == '/')
+ updatens(s);
+ }
+ writereply(wc, len data, nil);
+ (nil, nil, nil, rc) := <-fio.read =>
+ if (rc != nil)
+ readreply(rc, nil, "permission denied");
+ (nil, data, nil, wc) := <-fio.write =>
+ if (wc == nil)
+ break;
+ s := string data;
+ if (len s == 0 || s[0] != 's')
+ writereply(wc, 0, "invalid write");
+ cmd := str->unquoted(s);
+ if (cmd == nil || tl cmd == nil || tl tl cmd == nil) {
+ writereply(wc, 0, "invalid write");
+ } else {
+ if (hd tl tl cmd == "+ftree")
+ runsubftree(ctxt, tl tl tl cmd);
+ else
+ sh->run(ctxt, "{$* &}" :: tl tl cmd);
+ writereply(wc, len data, nil);
+ }
+ }
+}
+
+runsubftree(ctxt: ref Draw->Context, c: list of string)
+{
+ if (len c < 2) {
+ return;
+ }
+ cmd(tkwin, ". unmap");
+ sh->run(ctxt, c);
+ cmd(tkwin, ". map");
+}
+
+sendevent(target, ev: string)
+{
+ c := chan of (ref Tree, Item, chan of Item);
+ spawn sendeventproc(ev, c);
+ operate(target, c);
+ cmd(tkwin, "update");
+}
+
+# non-blocking reply to read request, in case client has gone away.
+readreply(reply: Sys->Rread, data: array of byte, err: string)
+{
+ alt {
+ reply <-= (data, err) =>;
+ * =>;
+ }
+}
+
+# non-blocking reply to write request, in case client has gone away.
+writereply(reply: Sys->Rwrite, count: int, err: string)
+{
+ alt {
+ reply <-= (count, err) =>;
+ * =>;
+ }
+}
+
+plumbit(f: string)
+{
+ if (!plumbinprogress) {
+ highlight(f, "yellow", 2000);
+ spawn plumbproc(root + f, plumbed);
+ plumbinprogress = 1;
+ }
+}
+
+plumbproc(f: string, plumbed: chan of int)
+{
+ if (plumbmsg == nil || (ref Msg("browser", nil, nil, "text", nil, array of byte f)).send() == -1) {
+ sys->fprint(stderr, "ftree: cannot plumb %s\n", f);
+ plumbed <-= 0;
+ } else
+ plumbed <-= 1;
+}
+
+loadmods()
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ if (tkclient == nil)
+ badmodule(Tkclient->PATH);
+ tkclient->init();
+
+ readdir = load Readdir Readdir->PATH;
+ if (readdir == nil)
+ badmodule(Readdir->PATH);
+
+ str = load String String->PATH;
+ if (str == nil)
+ badmodule(String->PATH);
+
+ items = load Items Items->PATH;
+ if (items == nil)
+ badmodule(Items->PATH);
+ items->init();
+
+ sh = load Sh Sh->PATH;
+ if (sh == nil)
+ badmodule(Sh->PATH);
+
+ popup = load Popup Popup->PATH;
+ if (popup == nil)
+ badmodule(Popup->PATH);
+ popup->init();
+
+ cptree = load Cptree Cptree->PATH;
+ if (cptree == nil)
+ badmodule(Cptree->PATH);
+ cptree->init();
+
+ arg = load Arg Arg->PATH;
+ if (arg == nil)
+ badmodule(Arg->PATH);
+}
+
+updatens(s: string)
+{
+ sys->print("updatens(%s)\n", s);
+ (target, ev) := eventtarget(s);
+ spawn rereadproc(c := chan of (ref Tree, Item, chan of Item));
+ operate(target, c);
+ cmd(tkwin, "update");
+}
+
+nsupdatereaderproc(fd: ref Sys->FD, path: string, nschanged: chan of string)
+{
+ buf := array[Sys->ATOMICIO] of byte;
+ while ((n := sys->read(fd, buf, len buf)) > 0) {
+ s := string buf[0:n];
+ nschanged <-= path + string buf[0:n-1];
+ }
+ sys->print("nsupdate gave eof: (%r)\n");
+}
+
+sendeventproc(ev: string, c: chan of (ref Tree, Item, chan of Item))
+{
+ (tree, it, replyc) := <-c;
+ if (replyc == nil)
+ return;
+ pick t := tree {
+ N =>
+ if (ev == "expand")
+ expand(t, it);
+ else if (ev == "contract")
+ t.sub = nil;
+ it = t.e.event(it, ev);
+ }
+ replyc <-= it;
+}
+
+Open, Copy, Paste, Remove: con iota;
+
+menu := array[] of {
+Open => "Open",
+Copy => "Copy",
+Paste => "Paste into",
+Remove => "Remove",
+};
+
+screenx(cvs: string, x: int): int
+{
+ return x - int cmd(tkwin, cvs + " canvasx 0");
+}
+
+screeny(cvs: string, y: int): int
+{
+ return y - int cmd(tkwin, cvs + " canvasy 0");
+}
+
+menuproc(c: chan of (ref Tree, Item, chan of Item), nsu: chan of string)
+{
+ (tree, it, replyc) := <-c;
+ if (replyc == nil)
+ return;
+
+ p := Point(screenx(".c", it.r.min.x), screeny(".c", it.r.min.y));
+ m := array[len menu] of string;
+ for (i := 0; i < len m; i++)
+ m[i] = menu[i] + " " + tree.fname;
+ n := post(tkwin, p, m, 0);
+ upd: string;
+ if (n >= 0) {
+ case n {
+ Copy =>
+ cpfile = it.name;
+ Paste =>
+ if (cpfile == nil)
+ notice("no file in snarf buffer");
+ else {
+ cp(cpfile, it.name);
+ upd = it.name;
+ }
+ Remove =>
+ if ((e := rm(it.name)) != nil)
+ notice(e);
+ upd = parent(it.name);
+ Open =>
+ plumbit(it.name);
+ }
+ }
+
+# id := cmd(tkwin, ".c create rectangle " + r2s(it.r) + " -fill yellow");
+ replyc <-= it;
+ nsu <-= upd;
+}
+
+post(win: ref Tk->Toplevel, p: Point, a: array of string, n: int): int
+{
+ rc := popup->post(win, p, a, n);
+ for(;;)alt{
+ r := <-rc =>
+ return r;
+ key := <-win.ctxt.kbd =>
+ tk->keyboard(win, key);
+ m := <-win.ctxt.ptr =>
+ tk->pointer(win, *m);
+ s := <-win.ctxt.ctl or
+ s = <-win.wreq =>
+ tkclient->wmctl(win, s);
+ }
+}
+
+highlight(f: string, colour: string, time: int)
+{
+ spawn highlightproc(c := chan of (ref Tree, Item, chan of Item), colour, time);
+ operate(f, c);
+ tk->cmd(tkwin, "update");
+}
+
+unhighlight()
+{
+ cmd(tkwin, ".c delete highlight");
+ tk->cmd(tkwin, "update");
+}
+
+hpid := -1;
+highlightproc(c: chan of (ref Tree, Item, chan of Item), colour: string, time: int)
+{
+ (tree, it, replyc) := <-c;
+ if (replyc == nil)
+ return;
+ r: Rect;
+ pick t := tree {
+ N =>
+ r = t.e.titleitem.r.addpt(it.r.min);
+ L =>
+ r = it.r;
+ }
+ id := cmd(tkwin, ".c create rectangle " + r2s(r) + " -fill " + colour + " -tags highlight");
+ cmd(tkwin, ".c lower " + id);
+ kill(hpid);
+ sync := chan of int;
+ spawn highlightsleepproc(sync, time);
+ hpid = <-sync;
+ replyc <-= it;
+}
+
+highlightsleepproc(sync: chan of int, time: int)
+{
+ sync <-= sys->pctl(0, nil);
+ sys->sleep(time);
+ cmd(tkwin, ".c delete highlight");
+ cmd(tkwin, "update");
+}
+
+operate(towhom: string, c: chan of (ref Tree, Item, chan of Item)): int
+{
+ towhom = cleanname(towhom);
+ (ok, it) := operate1(roottree, rootitem, towhom, towhom, c);
+ if (!it.eq(rootitem)) {
+ cmd(tkwin, ".c configure -width " + string it.r.dx() + " -height " + string it.r.dy() +
+ " -scrollregion {" + r2s(it.r) + "}");
+ rootitem = it;
+ }
+ if (!ok)
+ c <-= (nil, it, nil);
+ return ok;
+}
+
+blankitem: Item;
+operate1(tree: ref Tree, it: Item, towhom, below: string,
+ c: chan of (ref Tree, Item, chan of Item)): (int, Item)
+{
+# sys->print("operate on %s, towhom: %s, below: %s\n", it.name, towhom, below);
+ n: ref Tree.N;
+ replyc := chan of Item;
+ if (it.name != towhom) {
+ pick t := tree {
+ L =>
+ return (0, it);
+ N =>
+ n = t;
+ }
+ below = dropelem(below);
+ if (below == nil)
+ return (0, it);
+ path := pathcat(it.name, below);
+ if (len n.e.children != len n.sub) {
+ sys->fprint(stderr, "inconsistent children in %s (%d vs sub %d)\n", it.name, len n.e.children, len n.sub);
+ return (0, it);
+ }
+ for (i := 0; i < len n.e.children; i++) {
+ f := n.e.children[i].name;
+# sys->print("checking %s against child %s\n", path, f);
+ if (len path >= len f && path[0:len f] == f &&
+ (len path == len f || path[len f] == '/')) {
+ break;
+ }
+ }
+ if (i == len n.e.children)
+ return (0, it);
+ oldit := n.e.children[i].addpt(it.r.min);
+ (ok, nit) := operate1(n.sub[i], oldit, towhom, below, c);
+ if (nit.eq(oldit))
+ return (ok, it);
+# sys->print("childchanged({%s, [%s]}, %d, {%s, [%s]})\n",
+# it.name, r2s(it.r), i, nit.name, r2s(nit.r));
+ n.e.children[i] = nit.subpt(it.r.min);
+ return (ok, n.e.childrenchanged(it));
+ }
+ c <-= (tree, it, replyc);
+ return (1, <-replyc);
+}
+
+
+dropelem(below: string): string
+{
+ if (below[0] == '/')
+ return below[1:];
+ for (i := 1; i < len below; i++)
+ if (below[i] == '/')
+ break;
+ if (i == len below)
+ return nil;
+ return below[i+1:];
+}
+
+cleanname(s: string): string
+{
+ t := "";
+ i := 0;
+ while (i < len s)
+ if ((t[len t] = s[i++]) == '/')
+ while (i < len s && s[i] == '/')
+ i++;
+ if (len t > 1 && t[len t - 1] == '/')
+ t = t[0:len t - 1];
+ return t;
+}
+
+pathcat(s1, s2: string): string
+{
+ if (s1 == nil || s2 == nil)
+ return s1 + s2;
+ if (s1[len s1 - 1] != '/' && s2[0] != '/')
+ return s1 + "/" + s2;
+ return s1 + s2;
+}
+
+# read the directory referred to by t.
+expand(t: ref Tree.N, it: Item)
+{
+ (d, n) := readdir->init(root + it.name, Readdir->NAME|Readdir->COMPACT);
+ if (d == nil) {
+ sys->print("readdir failed: %r\n");
+ d = array[0] of ref Sys->Dir;
+ }
+ sortit(d);
+ t.sub = array[len d] of ref Tree;
+ t.e.children = array[len d] of Item;
+ for (i := 0; i < len d; i++) {
+ tagname := pathcat(it.name, d[i].name);
+ (t.sub[i], t.e.children[i]) = makenode(d[i].mode & Sys->DMDIR, d[i].name, tagname);
+ # make coords relative to parent
+ t.e.children[i] = t.e.children[i].subpt(it.r.min);
+ }
+}
+
+makenode(isdir: int, title, tagname: string): (ref Tree, Item)
+{
+ tree: ref Tree;
+ it: Item;
+ if (isdir) {
+ e := Expander.new(tkwin, ".c");
+ tree = ref Tree.N(title, e, nil);
+ it = e.make(items->maketext(tkwin, ".c", tagname, title));
+ cmd(tkwin, ".c bind " + e.titleitem.name +
+ " <Button-1> {send clickfile menu " + tagname + "}");
+ } else {
+ tree = ref Tree.L(title);
+ it = items->maketext(tkwin, ".c", tagname, title);
+ cmd(tkwin, ".c bind " + tagname +
+ " <ButtonRelease-2> {send clickfile plumb " + tagname + "}");
+ cmd(tkwin, ".c bind " + tagname +
+ " <Button-1> {send clickfile menu " + tagname + "}");
+ }
+ return (tree, it);
+}
+
+rereadproc(c: chan of (ref Tree, Item, chan of Item))
+{
+ (tree, it, replyc) := <-c;
+ if (replyc == nil)
+ return;
+ pick t := tree {
+ L =>
+ replyc <-= it;
+ N =>
+ replyc <-= reread(t, it);
+ }
+}
+
+# re-read tree & update recursively as necessary.
+# _it_ is the tree's Item, in absolute coords.
+reread(tree: ref Tree.N, it: Item): Item
+{
+ (d, n) := readdir->init(root + it.name, Readdir->NAME|Readdir->COMPACT);
+ sortit(d);
+ sys->print("re-reading %s (was %d, now %d)\n", it.name, len tree.sub, len d);
+
+ sub := tree.sub;
+ newsub := array[len d] of ref Tree;
+ newchildren := array[len d] of Item;
+ i := j := 0;
+ while (i < len sub || j < len d) {
+ cmp: int;
+ if (i >= len sub)
+ cmp = 1;
+ else if (j >= len d)
+ cmp = -1;
+ else {
+ cmp = entrycmp(sub[i].fname, tagof(sub[i]) == tagof(Tree.N),
+ d[j].name, d[j].mode & Sys->DMDIR);
+ }
+ if (cmp == 0) {
+ # entry remains the same, but maybe it's changed type.
+ if ((tagof(sub[i]) == tagof(Tree.N)) != ((d[j].mode & Sys->DMDIR) != 0)) {
+ # delete old item and make new one...
+ tagname := tree.e.children[i].name;
+ cmd(tkwin, ".c delete " + tagname);
+ (newsub[j], newchildren[j]) =
+ makenode(d[j].mode & Sys->DMDIR, d[j].name, tagname);
+ newchildren[j] = newchildren[j].subpt(it.r.min);
+ } else {
+ nit := tree.e.children[i];
+ pick t := sub[i] {
+ N =>
+ if (t.e.expanded)
+ nit = reread(t, nit.addpt(it.r.min)).subpt(it.r.min);
+ }
+ (newsub[j], newchildren[j]) = (sub[i], nit);
+ }
+ i++;
+ j++;
+ } else if (cmp > 0) {
+ # new entry, d[j]
+ tagname := pathcat(it.name, d[j].name);
+ (newsub[j], newchildren[j]) =
+ makenode(d[j].mode & Sys->DMDIR, d[j].name, tagname);
+ newchildren[j] = newchildren[j].subpt(it.r.min);
+ j++;
+ } else {
+ # entry has been deleted, sub[i]
+ cmd(tkwin, ".c delete " + tree.e.children[i].name);
+ i++;
+ }
+ }
+ (tree.sub, tree.e.children) = (newsub, newchildren);
+ return tree.e.childrenchanged(it);
+}
+
+entrycmp(s1: string, isdir1: int, s2: string, isdir2: int): int
+{
+ if (!isdir1 == !isdir2) {
+ if (s1 > s2)
+ return 1;
+ else if (s1 < s2)
+ return -1;
+ else
+ return 0;
+ } else if (isdir1)
+ return -1;
+ else
+ return 1;
+}
+
+sortit(d: array of ref Sys->Dir)
+{
+ da := array[len d] of ref Sys->Dir;
+ fa := array[len d] of ref Sys->Dir;
+ nd := nf := 0;
+ for (i := 0; i < len d; i++) {
+ if (d[i].mode & Sys->DMDIR)
+ da[nd++] = d[i];
+ else
+ fa[nf++] = d[i];
+ }
+ d[0:] = da[0:nd];
+ d[nd:] = fa[0:nf];
+}
+
+eventtarget(s: string): (string, string)
+{
+ for (i := 0; i < len s; i++)
+ if (s[i] == ' ')
+ return (s[0:i], s[i+1:]);
+ return (s, nil);
+}
+
+cmd(top: ref Tk->Toplevel, s: string): string
+{
+ e := tk->cmd(top, s);
+ if (e != nil && e[0] == '!')
+ sys->fprint(sys->fildes(2), "ftree: tk error %s on '%s'\n", e, s);
+ return e;
+}
+
+r2s(r: Rect): string
+{
+ return string r.min.x + " " + string r.min.y + " " +
+ string r.max.x + " " + string r.max.y;
+}
+
+p2s(p: Point): string
+{
+ return string p.x + " " + string p.y;
+}
+
+fittoscreen(win: ref Tk->Toplevel)
+{
+ Point: import draw;
+ if (win.image == nil || win.image.screen == nil)
+ return;
+ r := win.image.screen.image.r;
+ scrsize := Point((r.max.x - r.min.x), (r.max.y - r.min.y));
+ bd := int cmd(win, ". cget -bd");
+ winsize := Point(int cmd(win, ". cget -actwidth") + bd * 2, int cmd(win, ". cget -actheight") + bd * 2);
+ if (winsize.x > scrsize.x)
+ cmd(win, ". configure -width " + string (scrsize.x - bd * 2));
+ if (winsize.y > scrsize.y)
+ cmd(win, ". configure -height " + string (scrsize.y - bd * 2));
+ actr: Rect;
+ actr.min = Point(int cmd(win, ". cget -actx"), int cmd(win, ". cget -acty"));
+ actr.max = actr.min.add((int cmd(win, ". cget -actwidth") + bd*2,
+ int cmd(win, ". cget -actheight") + bd*2));
+ (dx, dy) := (actr.dx(), actr.dy());
+ if (actr.max.x > r.max.x)
+ (actr.min.x, actr.max.x) = (r.min.x - dx, r.max.x - dx);
+ if (actr.max.y > r.max.y)
+ (actr.min.y, actr.max.y) = (r.min.y - dy, r.max.y - dy);
+ if (actr.min.x < r.min.x)
+ (actr.min.x, actr.max.x) = (r.min.x, r.min.x + dx);
+ if (actr.min.y < r.min.y)
+ (actr.min.y, actr.max.y) = (r.min.y, r.min.y + dy);
+ cmd(win, ". configure -x " + string actr.min.x + " -y " + string actr.min.y);
+}
+
+cp(src, dst: string)
+{
+ if(disallow){
+ notice("permission denied");
+ return;
+ }
+ progressch := chan of string;
+ warningch := chan of (string, chan of int);
+ finishedch := chan of string;
+ spawn cptree->copyproc(root + src, root + dst, progressch, warningch, finishedch);
+loop: for (;;) alt {
+ m := <-progressch =>
+ status(m);
+ (m, r) := <-warningch =>
+ notice("warning: " + m);
+ sys->sleep(1000);
+ r <-= 1;
+ m := <-finishedch =>
+ status(m);
+ break loop;
+ }
+}
+
+parent(f: string): string
+{
+ f = cleanname(f);
+ for (i := len f - 1; i >= 0; i--)
+ if (f[i] == '/')
+ break;
+ if (i > 0)
+ f = f[0:i];
+ return f;
+}
+
+notice(s: string)
+{
+ status(s);
+}
+
+status(s: string)
+{
+ cmd(tkwin, ".top.l configure -text '" + s);
+ cmd(tkwin, "update");
+}
+
+rm(name: string): string
+{
+ if(disallow)
+ return "permission denied";
+ name = root + name;
+ if(sys->remove(name) < 0) {
+ e := sys->sprint("%r");
+ (ok, d) := sys->stat(name);
+ if(ok >= 0 && (d.mode & Sys->DMDIR) != 0)
+ return rmdir(name);
+ return e;
+ }
+ return nil;
+}
+
+rmdir(name: string): string
+{
+ (d, n) := readdir->init(name, Readdir->NONE|Readdir->COMPACT);
+ for(i := 0; i < n; i++) {
+ path := name+"/"+d[i].name;
+ e: string;
+ if(d[i].mode & Sys->DMDIR)
+ e = rmdir(path);
+ else if (sys->remove(path) == -1)
+ e = sys->sprint("cannot remove %s: %r", path);
+ if (e != nil)
+ return e;
+ }
+ if (sys->remove(name) == -1)
+ return sys->sprint("cannot remove %s: %r", name);
+ return nil;
+}
+
+kill(pid: int)
+{
+ if ((fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE)) != nil)
+ sys->write(fd, array of byte "kill", 4);
+}
diff --git a/appl/wm/ftree/items.b b/appl/wm/ftree/items.b
new file mode 100644
index 00000000..023e3d33
--- /dev/null
+++ b/appl/wm/ftree/items.b
@@ -0,0 +1,326 @@
+implement Items;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+ Point, Rect: import draw;
+include "tk.m";
+ tk: Tk;
+include "items.m";
+
+Taglen: con 5;
+Titletaglen: con 10;
+Spotdiam: con 10;
+Lineopts: con " -width 1 -fill gray";
+Ovalopts: con " -outline gray";
+Crossopts: con " -fill red";
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+}
+
+blankexpander: Expander;
+Expander.new(win: ref Tk->Toplevel, cvs: string): ref Expander
+{
+ e := ref blankexpander;
+ e.win = win;
+ e.cvs = cvs;
+ return e;
+}
+
+moveto(win: ref Tk->Toplevel, cvs: string, tag: string, bbox: Rect, p: Point)
+{
+ if (!bbox.min.eq(p))
+ cmd(win, cvs + " move " + tag + " " + p2s(p.sub(bbox.min)));
+}
+
+bbox(win: ref Tk->Toplevel, cvs, w: string): Rect
+{
+ return s2r(cmd(win, cvs + " bbox " + w));
+}
+
+rename(win: ref Tk->Toplevel, it: Item, newname: string): Item
+{
+ (nil, itl) := sys->tokenize(cmd(win, ".c find withtag " + it.name), " ");
+ cmd(win, ".c dtag " + it.name + " " + it.name);
+ for (; itl != nil; itl = tl itl)
+ cmd(win, ".c addtag " + newname + " withtag " + hd itl);
+ it.name = newname;
+ return it;
+}
+
+Expander.make(e: self ref Expander, titleitem: Item): Item
+{
+ name := titleitem.name;
+ tag := " -tags " + name;
+
+ e.titleitem = rename(e.win, titleitem, "!!." + name);
+ cmd(e.win, e.cvs + " addtag " + name + " withtag !!." + name);
+ sc := spotcentre((0, 0), dxy(e.titleitem.r));
+ spotr := Rect(sc, sc).inset(-Spotdiam/2);
+
+ p := (spotr.max.x + Titletaglen, 0);
+ moveto(e.win, e.cvs, e.titleitem.name, e.titleitem.r, p);
+ e.titleitem.r = rmoveto(e.titleitem.r, p);
+ it := Item(name, ((0, 0), (spotr.max.x + Titletaglen + titleitem.r.dx(), titleitem.r.dy())), (0, 0));
+
+ # make line to the right of spot
+ cmd(e.win, e.cvs + " create line " +
+ p2s((spotr.max.x, sc.y)) + " " + p2s((spotr.max.x+Titletaglen, sc.y)) + tag + Lineopts);
+
+ # make spot
+ spotid := cmd(e.win, e.cvs + " create oval " +
+ r2s(spotr) + Ovalopts + tag);
+ if (e.expanded)
+ cmd(e.win, e.cvs + " bind " + spotid + " <ButtonRelease-1>"
+ + " {send event " + name + " contract}");
+ else
+ cmd(e.win, e.cvs + " bind " + spotid + " <ButtonRelease-1>"
+ + " {send event " + name + " expand}");
+
+ cmd(e.win, e.cvs + " raise " + spotid);
+ e.spotid = int spotid;
+
+ it.attach = (0, sc.y);
+ it.r.max = (e.titleitem.r.dx() + spotr.max.x + Titletaglen, e.titleitem.r.dy());
+
+ if (!e.expanded) {
+ addcross(e, it, name);
+ return it;
+ }
+
+ it.r = placechildren(e, it, name);
+ return it;
+}
+
+rmoveto(r: Rect, p: Point): Rect
+{
+ return r.addpt(p.sub(r.min));
+}
+
+# place all children of e appropriately.
+# assumes that the canvas items of all children are already made.
+# return bbox rectangle of whole thing.
+placechildren(e: ref Expander, it: Item, tags: string): Rect
+{
+ ltag := " -tags {"+ tags + " !." + it.name + "}";
+ titlesize := dxy(e.titleitem.r);
+ sc := spotcentre(it.r.min, titlesize);
+ maxwidth := 0;
+ y := it.r.min.y + titlesize.y;
+ lasty := 0;
+ for (i := 0; i < len e.children; i++) {
+ c := e.children[i];
+ if (c.r.dx() > maxwidth)
+ maxwidth = c.r.dx();
+ c.r = c.r.addpt(it.r.min);
+ r: Rect;
+ r.min = (sc.x + Taglen, y);
+ r.max = r.min.add(dxy(c.r));
+ moveto(e.win, e.cvs, c.name, c.r, r.min);
+
+ # make item coords relative to parent
+ e.children[i].r = r.subpt(it.r.min);
+ cmd(e.win, e.cvs + " addtag " + it.name + " withtag " + c.name);
+
+ # horizontal attachment
+ cmd(e.win, e.cvs + " create line " +
+ p2s((sc.x, y + c.attach.y)) + " " +
+ p2s((sc.x + Taglen + c.attach.x, y + c.attach.y)) +
+ ltag + Lineopts);
+ lasty = y + c.attach.y;
+ y += r.dy();
+ }
+
+ # vertical attachment (if there were any children)
+ if (i > 0) {
+ id := cmd(e.win, e.cvs + " create line " +
+ p2s((sc.x, sc.y + Spotdiam/2)) + " " + p2s((sc.x, lasty)) + ltag + Lineopts);
+ cmd(e.win, e.cvs + " bind " + id + " <Button-1>"+
+ " {send event " + it.name + " see}");
+ }
+ r := Rect(it.r.min,
+ (max(sc.x+Spotdiam/2+Titletaglen+titlesize.x, sc.x+Taglen+maxwidth),
+ y));
+ return r;
+}
+
+Expander.event(e: self ref Expander, it: Item, ev: string): Item
+{
+ case ev {
+ "expand" =>
+ if (e.expanded) {
+ sys->print("item %s is already expanded\n", it.name);
+ return it;
+ }
+ e.expanded = 1;
+ tags := gettags(e.win, e.cvs, string e.spotid);
+ cmd(e.win, e.cvs + " delete !." + it.name);
+ cmd(e.win, e.cvs + " bind " + string e.spotid + " <ButtonRelease-1>" +
+ + " {send event " + it.name + " contract}");
+ it.r = placechildren(e, it, tags);
+ "contract" =>
+ if (!e.expanded) {
+ sys->print("item %s is already contracted\n", it.name);
+ return it;
+ }
+ e.expanded = 0;
+ cmd(e.win, e.cvs + " delete !." + it.name);
+ for (i := 0; i < len e.children; i++)
+ cmd(e.win, e.cvs + " delete " + e.children[i].name);
+ cmd(e.win, e.cvs + " bind " + string e.spotid + " <ButtonRelease-1>" +
+ + " {send event " + it.name + " expand}");
+ tags := gettags(e.win, e.cvs, string e.spotid);
+ addcross(e, it, tags);
+ titlesize := dxy(e.titleitem.r);
+ it.r.max = it.r.min.add((Taglen * 2 + Spotdiam + titlesize.x, titlesize.y));
+ e.children = nil;
+ "see" =>
+ cmd(e.win, e.cvs + " see " + p2s(it.r.min));
+ * =>
+ sys->print("unknown event '%s' on item %s\n", ev, it.name);
+ }
+ return it;
+}
+
+Expander.childrenchanged(e: self ref Expander, it: Item): Item
+{
+ cmd(e.win, e.cvs + " delete !." + it.name);
+ tags := gettags(e.win, e.cvs, string e.spotid);
+ it.r = placechildren(e, it, tags);
+ return it;
+}
+
+gettags(win: ref Tk->Toplevel, cvs: string, name: string): string
+{
+ tags := cmd(win, cvs + " gettags " + name);
+ (n, tagl) := sys->tokenize(tags, " ");
+ ntags := "";
+ for (; tagl != nil; tagl = tl tagl) {
+ t := hd tagl;
+ if (t[0] != '!' && (t[0] < '0' || t[0] > '9'))
+ ntags += " " + t;
+ }
+ return ntags;
+}
+
+spotcentre(origin, titlesize: Point): Point
+{
+ return (origin.x + Spotdiam / 2, origin.y + titlesize.y / 2);
+}
+
+addcross(e: ref Expander, it: Item, tags: string)
+{
+ p := spotcentre(it.r.min, dxy(e.titleitem.r));
+ crosstags := " -tags {" + tags + " !." + it.name + "}";
+
+ id1 := cmd(e.win, e.cvs + " create line " +
+ p2s((p.x-Spotdiam/2, p.y)) + " " +
+ p2s((p.x+Spotdiam/2, p.y)) + crosstags + Crossopts);
+ id2 := cmd(e.win, e.cvs + " create line " +
+ p2s((p.x, p.y-Spotdiam/2)) + " " +
+ p2s((p.x, p.y+Spotdiam/2)) + crosstags + Crossopts);
+ cmd(e.win, e.cvs + " lower " + id1 + ";" + e.cvs + " lower " + id2);
+}
+
+knownfont: string;
+knownfontheight: int;
+fontheight(win: ref Tk->Toplevel, font: string): int
+{
+ Font: import draw;
+ if (font == knownfont)
+ return knownfontheight;
+ if (win.image == nil) # can happen if we run out of image memory
+ return -1;
+ f := Font.open(win.image.display, font);
+ if (f == nil)
+ return -1;
+ knownfont = font;
+ knownfontheight = f.height;
+ return f.height;
+}
+
+maketext(win: ref Tk->Toplevel, cvs: string, name: string, text: string): Item
+{
+ tag := " -tags " + name;
+ it := Item(name, ((0, 0), (0, 0)), (0, 0));
+ ttid := cmd(win, cvs + " create text 0 0 " +
+ " -anchor nw" + tag +
+ " -text '" + text);
+ it.r = bbox(win, cvs, ttid);
+ h := fontheight(win, cmd(win, cvs + " itemcget " + ttid + " -font"));
+ if (h != -1) {
+ dh := it.r.dy() - h;
+ it.r.min.y += dh / 2;
+ it.r.max.y -= dh / 2;
+ }
+ it.attach = (0, it.r.dy() / 2);
+ return it;
+}
+
+cmd(top: ref Tk->Toplevel, s: string): string
+{
+ e := tk->cmd(top, s);
+ if (e != nil && e[0] == '!')
+ sys->fprint(sys->fildes(2), "items: tk error %s on '%s'\n", e, s);
+ return e;
+}
+
+r2s(r: Rect): string
+{
+ return string r.min.x + " " + string r.min.y + " " +
+ string r.max.x + " " + string r.max.y;
+}
+
+s2r(s: string): Rect
+{
+ (n, toks) := sys->tokenize(s, " ");
+ if (n != 4) {
+ sys->print("'%s' is not a rectangle!\n", s);
+ raise "bad conversion";
+ }
+ r: Rect;
+ (r.min.x, toks) = (int hd toks, tl toks);
+ (r.min.y, toks) = (int hd toks, tl toks);
+ (r.max.x, toks) = (int hd toks, tl toks);
+ (r.max.y, toks) = (int hd toks, tl toks);
+ return r;
+}
+
+Item.eq(i: self Item, j: Item): int
+{
+ return i.r.eq(j.r) && i.attach.eq(j.attach) && i.name == j.name;
+}
+
+Item.addpt(i: self Item, p: Point): Item
+{
+ i.r = i.r.addpt(p);
+ return i;
+}
+
+Item.subpt(i: self Item, p: Point): Item
+{
+ i.r = i.r.subpt(p);
+ return i;
+}
+
+p2s(p: Point): string
+{
+ return string p.x + " " + string p.y;
+}
+
+dxy(r: Rect): Point
+{
+ return r.max.sub(r.min);
+}
+
+max(a, b: int): int
+{
+ if (a > b)
+ return a;
+ return b;
+}
diff --git a/appl/wm/ftree/items.m b/appl/wm/ftree/items.m
new file mode 100644
index 00000000..7af34d12
--- /dev/null
+++ b/appl/wm/ftree/items.m
@@ -0,0 +1,30 @@
+Items: module {
+ PATH: con "/dis/lib/ftree/items.dis";
+
+ Item: adt {
+ name: string; # tag held in common by all canvas items in this Item.
+ r: Rect; # relative to parent's Item when stored in children
+ attach: Point; # attachment point relative to r.min
+
+ eq: fn(i: self Item, j: Item): int;
+ addpt: fn(i: self Item, p: Point): Item;
+ subpt: fn(i: self Item, p: Point): Item;
+ };
+
+ Expander: adt {
+ titleitem: Item;
+ expanded: int;
+ children: array of Item;
+ win: ref Tk->Toplevel;
+ cvs: string;
+ spotid: int;
+
+ new: fn(win: ref Tk->Toplevel, cvs: string): ref Expander;
+ make: fn(e: self ref Expander, it: Item): Item;
+ event: fn(e: self ref Expander, it: Item, ev: string): Item;
+ childrenchanged: fn(e: self ref Expander, it: Item): Item;
+ };
+
+ init: fn();
+ maketext: fn(win: ref Tk->Toplevel, cvs: string, name: string, text: string): Item;
+};
diff --git a/appl/wm/ftree/mkfile b/appl/wm/ftree/mkfile
new file mode 100644
index 00000000..4f4c5f39
--- /dev/null
+++ b/appl/wm/ftree/mkfile
@@ -0,0 +1,36 @@
+<../../../mkconfig
+
+TARG=\
+ items.dis\
+ cptree.dis\
+ ftree.dis
+
+MODULES=\
+ items.m\
+ cptree.m\
+
+SYSMODULES=\
+ arg.m\
+ draw.m\
+ plumbmsg.m\
+ popup.m\
+ readdir.m\
+ sh.m\
+ string.m\
+ sys.m\
+ tk.m\
+ tkclient.m\
+
+DISBIN=$ROOT/dis/lib/ftree
+
+all:V: ftree.dis $TARG
+
+$ROOT/dis/wm/ftree.dis: ftree.dis
+ rm -f $ROOT/dis/wm/ftree.dis && cp ftree.dis $ROOT/dis/wm/ftree.dis
+
+<$ROOT/mkfiles/mkdis
+
+install:V: $ROOT/dis/wm/ftree.dis
+
+nuke:V: nuke-std
+ cd $ROOT/dis/wm; rm -f ftree.dis
diff --git a/appl/wm/ftree/wmsetup b/appl/wm/ftree/wmsetup
new file mode 100644
index 00000000..e229e63c
--- /dev/null
+++ b/appl/wm/ftree/wmsetup
@@ -0,0 +1,48 @@
+# /dis/sh script
+# wm defines "menu" and "delmenu" builtins
+load std
+prompt='% ' ''
+fn % {$*}
+autoload=std
+home=/usr/^"{cat /dev/user}
+
+if {! {~ wm ${loaded}}} {
+ echo wmsetup must run under wm >[1=2]
+ raise usage
+}
+
+fn wmrun {
+ args := $*
+ {
+ pctl newpgrp
+ fn wmrun
+ $args
+ } >[2] /chan/wmstderr &
+}
+
+fn cd {
+ builtin cd $*; echo cwd `{pwd} > /chan/shctl
+}
+
+menu Shell {wmrun wm/sh}
+menu Acme {wmrun acme}
+menu Edit {wmrun wm/edit}
+menu Charon {wmrun charon}
+menu Manual {wmrun wm/man}
+menu Files {if {ftest -d $home} {wmrun wm/dir $home} {wmrun wm/dir /}}
+menu '' ''
+menu System 'Debugger' {wmrun wm/deb}
+menu System 'Module manager' {wmrun wm/rt}
+menu System 'Task manager' {wmrun wm/task}
+menu System 'Memory monitor' {wmrun wm/memory}
+menu System 'About' {wmrun wm/about}
+menu Misc 'Tetris' {wmrun wm/tetris}
+menu Misc 'Coffee' {wmrun wm/coffee}
+menu Misc 'Colours' {wmrun wm/colors}
+menu Misc 'Winctl' {wmrun wm/winctl}
+menu Misc 'Clock' {wmrun wm/date}
+
+if {ftest -f $home/lib/wmsetup} {run $home/lib/wmsetup} {}
+
+builtin cd /usr/rog/limbo/browser
+wmrun ftree
diff --git a/appl/wm/getauthinfo.b b/appl/wm/getauthinfo.b
new file mode 100644
index 00000000..0e03cc85
--- /dev/null
+++ b/appl/wm/getauthinfo.b
@@ -0,0 +1,291 @@
+implement WmGetauthinfo;
+
+include "sys.m";
+ sys: Sys;
+
+include "security.m";
+ login: Login;
+
+include "draw.m";
+ draw: Draw;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "dialog.m";
+ dialog: Dialog;
+
+include "keyring.m";
+ kr: Keyring;
+
+include "string.m";
+
+include "sh.m";
+
+#
+# Tk version of getauthinfo command
+#
+WmGetauthinfo: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Wm: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+cfg := array[] of {
+ "frame .all -borderwidth 2 -relief raised",
+
+ "frame .u",
+ "label .u.l -text {User } -anchor w",
+ "entry .u.e",
+ "pack .u.l .u.e -side left -in .u -expand 1",
+ "bind .u.e <Key-\n> {send cmd u}",
+ "focus .u.e",
+
+ "frame .p",
+ "label .p.l -text {Password} -anchor w",
+ "entry .p.e -show *",
+ "pack .p.l .p.e -side left -in .p -expand 1",
+ "bind .p.e <Key-\n> {send cmd p}",
+
+ "frame .s",
+ "label .s.l -text {Signer } -anchor w",
+ "entry .s.e",
+ "pack .s.l .s.e -side left -in .s -expand 1",
+ "bind .s.e <Key-\n> {send cmd s}",
+
+ "frame .f",
+ "label .f.l -text {Save key} -anchor w",
+ "entry .f.e",
+ "pack .f.l .f.e -side left -in .f -expand 1",
+ "bind .f.e <Key-\n> {send cmd f}",
+
+ "frame .b",
+ "radiobutton .b.p -variable save -value p -anchor w -text '" + "Permanent",
+ "radiobutton .b.t -variable save -value t -anchor w -text '" + "Temporary",
+ "pack .b.p .b.t -side right -in .b -expand 1",
+ ".b.p invoke",
+ "pack .u .p .s .f .b -in .all",
+ "pack .Wm_t .all -fill x -expand 1",
+ "update"
+};
+
+about : con "Generate keys and\n" +
+ "request certificate for\n" +
+ "mounting remote server";
+
+
+init(ctxt: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "getauthinfo: no window context\n");
+ raise "fail:bad context";
+ }
+ kr = load Keyring Keyring->PATH;
+ str := load String String->PATH;
+
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+
+ tkclient = load Tkclient Tkclient->PATH;
+ dialog = load Dialog Dialog->PATH;
+ tkclient->init();
+ dialog->init();
+
+ (top, wmctl) := tkclient->toplevel(ctxt, "",
+ "Obtain Certificate for Server", Tkclient->Help);
+ for (c:=0; c<len cfg; c++)
+ tk->cmd(top, cfg[c]);
+ cmd := chan of string;
+ tk->namechan(top, cmd, "cmd");
+
+ login = load Login Login->PATH;
+ if(login == nil){
+ dialog->prompt(ctxt, top.image, "error -fg red", "Error",
+ "Cannot load " + Login->PATH, 0, "Exit"::nil);
+ exit;
+ }
+
+ # start interactive
+ usr := user();
+ passwd := "";
+ signer := defaultsigner();
+ dir:= "";
+ file := "net!";
+ path := "";
+ tk->cmd(top, ".u.e insert end '" + usr);
+ tk->cmd(top, ".s.e insert end '" + signer);
+ tk->cmd(top, "update");
+ tkclient->onscreen(top, nil);
+ tkclient->startinput(top, "kbd"::"ptr"::nil);
+ info : ref Keyring->Authinfo;
+ for(;;){
+ alt {
+ s := <-top.ctxt.kbd =>
+ tk->keyboard(top, s);
+ s := <-top.ctxt.ptr =>
+ tk->pointer(top, *s);
+ s := <-top.ctxt.ctl or
+ s = <-top.wreq =>
+ tkclient->wmctl(top, s);
+ menu := <-wmctl =>
+ case menu {
+ "exit" =>
+ exit;
+ "help" =>
+ dialog->prompt(ctxt, top.image, "info -fg green", "About",
+ about, 0, "OK"::nil);
+ }
+ tkclient->wmctl(top, menu);
+ rdy := <-cmd =>
+ case (rdy[0]) {
+ 'u' =>
+ usr = tk->cmd(top, ".u.e get");
+ if(usr == "")
+ tk->cmd(top, "focus .u.e; update");
+ else {
+ dir = "/usr/" + usr + "/keyring/";
+ path = dir + file;
+ tk->cmd(top, ".f.e delete 0 end");
+ tk->cmd(top, ".f.e insert end '" + path);
+ tk->cmd(top, "focus .p.e; update");
+ }
+ continue;
+ 'p' =>
+ passwd = tk->cmd(top, ".p.e get");
+ if(passwd == "")
+ tk->cmd(top, "focus .p.e; update");
+ else
+ tk->cmd(top, "focus .s.e; update");
+ continue;
+ 's' =>
+ signer = tk->cmd(top, ".s.e get");
+ if(signer == "")
+ tk->cmd(top, "focus .s.e");
+ else {
+ file = "net!" + signer;
+ path = dir + file;
+ tk->cmd(top, ".f.e delete 0 end");
+ tk->cmd(top, ".f.e insert end " + path);
+ tk->cmd(top, "focus .f.e; update");
+ }
+ continue;
+ 'f' =>
+ path = tk->cmd(top, ".f.e get");
+ if(path == "") {
+ tk->cmd(top, "focus .f.e; update");
+ continue;
+ }
+
+ # start encrypt key exchange
+ addr := "net!"+signer+"!inflogin";
+ tk->cmd(top, "cursor -bitmap cursor.wait");
+ err: string;
+ (err, info) = login->login(usr, passwd, addr);
+ tk->cmd(top, "cursor -default");
+ if(info == nil){
+ dialog->prompt(ctxt, top.image, "warning -fg yellow", "Warning",
+ err, 0, "Continue"::nil);
+ tk->cmd(top, ".p.e delete 0 end");
+ tk->cmd(top, "focus .p.e");
+ continue;
+ }
+
+ # save the info for later access
+ save := tk->cmd(top, "variable save");
+ (dir, file) = str->splitr(path, "/");
+ if(save[0] == 't')
+ spawn save2file(dir, file);
+
+ tk->cmd(top, "cursor -default");
+ if(kr->writeauthinfo(path, info) < 0){
+ dialog->prompt(ctxt, top.image, "error -fg red", "Error",
+ "Can't write to " + path, 0, "Exit"::nil);
+ exit;
+ }
+ if(save[0] == 'p')
+ dialog->prompt(ctxt, top.image, "info -fg green", "Notice",
+ "Authentication information is\nsaved in file:\n"
+ + path, 0, "OK"::nil);
+ else
+ dialog->prompt(ctxt, top.image, "info -fg green", "Notice",
+ "Authentication information is\nheld in a temporary file:\n"
+ + path, 0, "OK"::nil);
+
+ return;
+
+ }
+ }
+ }
+}
+
+
+user(): string
+{
+ sys = load Sys Sys->PATH;
+
+ fd := sys->open("/dev/user", sys->OREAD);
+ if(fd == nil)
+ return "";
+
+ buf := array[Sys->NAMEMAX] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return "";
+
+ return string buf[0:n];
+}
+
+save2file(dir, file: string)
+{
+ if(sys->bind("#s", dir, Sys->MBEFORE) < 0)
+ exit;
+ fileio := sys->file2chan(dir, file);
+ if(fileio != nil)
+ exit;
+
+ sys->pctl(Sys->NEWPGRP, nil);
+
+ infodata := array[0] of byte;
+
+ for(;;) alt {
+ (off, nbytes, fid, rc) := <-fileio.read =>
+ if(rc == nil)
+ break;
+ if(off > len infodata){
+ rc <-= (infodata[off:off], nil);
+ } else {
+ if(off + nbytes > len infodata)
+ nbytes = len infodata - off;
+ rc <-= (infodata[off:off+nbytes], nil);
+ }
+
+ (off, data, fid, wc) := <-fileio.write =>
+ if(wc == nil)
+ break;
+
+ if(off != len infodata){
+ wc <-= (0, "cannot be rewritten");
+ } else {
+ nid := array[len infodata+len data] of byte;
+ nid[0:] = infodata;
+ nid[len infodata:] = data;
+ infodata = nid;
+ wc <-= (len data, nil);
+ }
+ data = nil;
+ }
+}
+
+# get default signer server name
+defaultsigner(): string
+{
+ return "$SIGNER";
+}
diff --git a/appl/wm/hebrew.m b/appl/wm/hebrew.m
new file mode 100644
index 00000000..63515aa7
--- /dev/null
+++ b/appl/wm/hebrew.m
@@ -0,0 +1,30 @@
+hebrewtab := array[] of {
+ Remaptab(' ', ' '),
+ Remaptab('t', 16r5d0+0),
+ Remaptab('c', 16r5d0+1),
+ Remaptab('d', 16r5d0+2),
+ Remaptab('s', 16r5d0+3),
+ Remaptab('v', 16r5d0+4),
+ Remaptab('u', 16r5d0+5),
+ Remaptab('z', 16r5d0+6),
+ Remaptab('j', 16r5d0+7),
+ Remaptab('y', 16r5d0+8),
+ Remaptab('h', 16r5d0+9),
+ Remaptab('l', 16r5d0+10),
+ Remaptab('f', 16r5d0+11),
+ Remaptab('k', 16r5d0+12),
+ Remaptab('o', 16r5d0+13),
+ Remaptab('n', 16r5d0+14),
+ Remaptab('i', 16r5d0+15),
+ Remaptab('b', 16r5d0+16),
+ Remaptab('x', 16r5d0+17),
+ Remaptab('g', 16r5d0+18),
+ Remaptab(';', 16r5d0+19),
+ Remaptab('p', 16r5d0+20),
+ Remaptab('.', 16r5d0+21),
+ Remaptab('m', 16r5d0+22),
+ Remaptab('e', 16r5d0+23),
+ Remaptab('r', 16r5d0+24),
+ Remaptab('a', 16r5d0+25),
+ Remaptab(',', 16r5d0+26)
+};
diff --git a/appl/wm/keyboard.b b/appl/wm/keyboard.b
new file mode 100644
index 00000000..7e257826
--- /dev/null
+++ b/appl/wm/keyboard.b
@@ -0,0 +1,511 @@
+implement Keybd;
+
+#
+# extensive revision of code originally by N. W. Knauft
+#
+# Copyright © 1997 Lucent Technologies Inc. All rights reserved.
+# Revisions Copyright © 1998 Vita Nuova Limited. All rights reserved.
+# Rewritten code Copyright © 2001 Vita Nuova Holdings Limited. All rights reserved.
+#
+# To do:
+# input from file
+# calculate size
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Rect, Point: import draw;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "arg.m";
+
+include "keyboard.m";
+
+Keybd: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+FONT: con "/fonts/lucidasans/boldlatin1.6.font";
+SPECFONT: con "/fonts/lucidasans/unicode.6.font";
+
+# size in pixels
+#KEYSIZE: con 16;
+KEYSIZE: con 13;
+KEYSPACE: con 2;
+KEYBORDER: con 1;
+KEYGAP: con KEYSPACE - (2 * KEYBORDER);
+#ENDGAP: con 2 - KEYBORDER;
+ENDGAP: con 0;
+
+Key: adt {
+ name: string;
+ val: int;
+ size: int;
+ x: list of int;
+ on: int;
+};
+
+background: con "#dddddd";
+
+Backspace, Tab, Backslash, CapsLock, Return, Shift, Ctrl, Esc, Alt, Space: con iota;
+
+specials := array[] of {
+Backspace => Key("<-", '\b', 28, nil, 0),
+Tab => Key("Tab", '\t', 26, nil, 0),
+Backslash => Key("\\\\", '\\', KEYSIZE, nil, 0),
+CapsLock => Key("Caps", Keyboard->Caps, 40, nil, 0),
+Return => Key("Enter", '\n', 36, nil, 0),
+Shift => Key("Shift", Keyboard->LShift, 45, nil, 0),
+Esc => Key("Esc", 8r33, 21, nil, 0),
+Ctrl => Key("Ctrl", Keyboard->LCtrl, 36, nil, 0),
+Alt => Key("Alt", Keyboard->LAlt, 22, nil, 0),
+Space => Key(" ", ' ', 140, nil, 0),
+Space+1 => Key("Return", '\n', 36, nil, 0),
+};
+
+keys:= array[] of {
+ # unshifted
+ array[] of {
+ "Esc", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "\\\\", "`", nil,
+ "Tab", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "<-", nil,
+ "Ctrl", "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "Enter", nil,
+ "Shift", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "Shift", nil,
+ "Caps", "Alt", " ", "Alt", nil,
+ },
+
+ # shifted
+ array[] of {
+ "Esc", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "+", "|", "~", nil,
+ "Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "\\{", "\\}", "<-", nil,
+ "Ctrl", "A", "S", "D", "F", "G", "H", "J", "K", "L", ":", "\"", "Return", nil,
+ "Shift", "Z", "X", "C", "V", "B", "N", "M", "<", ">", "?", "Shift", nil,
+ "Caps", "Alt", " ", "Alt", nil,
+ },
+};
+
+keyvals: array of array of int;
+noexit := 0;
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "keyboard: no window context\n");
+ raise "fail:bad context";
+ }
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ arg := load Arg Arg->PATH;
+
+ taskbar := 0;
+ winopts := Tkclient->Hide;
+ arg->init(args);
+ while ((opt := arg->opt()) != 0) {
+ case opt {
+ 't' =>
+ taskbar = 1;
+ 'e' =>
+ noexit = 1;
+ winopts = 0;
+ * =>
+ sys->fprint(sys->fildes(2), "usage: keyboard [-et]\n");
+ raise "fail:usage";
+ }
+ }
+
+ sys->pctl(Sys->NEWPGRP, nil);
+ tkclient->init();
+
+ keyvals = array[] of {
+ array[len keys[0]] of int,
+ array[len keys[1]] of int,
+ };
+ setindex(keys[0], keyvals[0], specials);
+ setindex(keys[1], keyvals[1], specials);
+
+
+ (t, wcmd) := tkclient->toplevel(ctxt, "", "Kbd", winopts);
+ cmd(t, ". configure -bd 0 -relief flat");
+
+ for(i := 0; i < len keys[0]; i++)
+ if(keys[0][i] != nil)
+ cmd(t, sys->sprint("button .b%d -takefocus 0 -font %s -width %d -height %d -bd %d -activebackground %s -text {%s} -command 'send keypress %d",
+ i, FONT, KEYSIZE, KEYSIZE, KEYBORDER, background, keys[0][i], keyvals[0][i]));
+
+ for(i = 0; i < len specials; i++) {
+ k := specials[i];
+ for(xl := k.x; xl != nil; xl = tl xl)
+ cmd(t, sys->sprint(".b%d configure -font %s -width %d", hd xl, SPECFONT, k.size));
+ }
+
+ # pack buttons in rows
+ i = 0;
+ for(j:=0; i < len keys[0]; j++){
+ rowf := sys->sprint(".f%d", j);
+ cmd(t, "frame "+rowf);
+ cmd(t, sys->sprint("frame .pad%d -height %d", j, KEYGAP));
+ if(ENDGAP){
+ cmd(t, rowf + ".pad -width " + string ENDGAP);
+ cmd(t, "pack " + rowf + ".pad -side left");
+ }
+ for(; keys[0][i] != nil; i++){
+ label := keys[0][i];
+ expand := label != "\\\\" && len label > 1;
+ cmd(t, "pack .b" + string i + " -in "+ rowf + " -side left -fill x -expand "+string expand);
+ if(keys[0][i+1] != nil && KEYGAP > 0){
+ padf := sys->sprint("%s.pad%d", rowf, i);
+ cmd(t, "frame " + padf + " -width " + string KEYGAP);
+ cmd(t, "pack " + padf + " -side left");
+ }
+ }
+ if(ENDGAP){
+ padf := sys->sprint("%s.pad%d", rowf, i);
+ cmd(t, "frame " + padf + " -width " + string ENDGAP);
+ cmd(t, "pack " + padf + " -side left");
+ }
+ i++;
+ }
+ nrow := j;
+
+ # pack rows in frame
+ for(j = 0; j < nrow; j++)
+ cmd(t, sys->sprint("pack .f%d .pad%d -fill x -in .", j, j));
+
+ (w, h) := (int cmd(t, ". cget -width"), int cmd(t, ". cget -height"));
+ r := t.screenr;
+ off := (r.dx()-w)/2;
+ cmd(t, sys->sprint(". configure -x %d -y %d", r.min.x+off, r.max.y-h));
+ tkclient->onscreen(t, nil);
+ tkclient->startinput(t, "ptr" :: nil);
+
+ spawn handle_keyclicks(t, wcmd, taskbar);
+}
+
+setindex(keys: array of string, keyvals: array of int, spec: array of Key)
+{
+ for(i := 0; i < len keys; i++){
+ if(keys[i] == nil)
+ continue;
+ val := keys[i][0];
+ if(len keys[i] > 1 && val == '\\')
+ val = keys[i][1];
+ for(j := 0; j < len spec; j++)
+ if(spec[j].name == keys[i]){
+ if(!inlist(i, spec[j].x))
+ spec[j].x = i :: spec[j].x;
+ val = spec[j].val;
+ break;
+ }
+ keyvals[i] = val;
+ }
+}
+
+inlist(i: int, l: list of int): int
+{
+ for(; l != nil; l = tl l)
+ if(hd l == i)
+ return 1;
+ return 0;
+}
+
+handle_keyclicks(t: ref Tk->Toplevel, wcmd: chan of string, taskbar: int)
+{
+ keypress := chan of string;
+ tk->namechan(t, keypress, "keypress");
+
+ if(taskbar)
+ tkclient->wmctl(t, "task");
+
+ cmd(t,"update");
+
+ collecting := 0;
+ collected := "";
+ for(;;)alt {
+ k := <-keypress =>
+ c := int k;
+ case c {
+ Keyboard->Caps =>
+ active(t, Ctrl, 0);
+ active(t, Shift, 0);
+ active(t, Alt, 0);
+ active(t, CapsLock, -1);
+ redraw(t);
+ Keyboard->LShift =>
+ active(t, Shift, -1);
+ redraw(t);
+ Keyboard->LCtrl =>
+ active(t, Alt, 0);
+ active(t, Ctrl, -1);
+ active(t, Shift, 0);
+ redraw(t);
+ Keyboard->LAlt =>
+ active(t, Alt, -1);
+ active(t, Ctrl, 0);
+ active(t, Shift, 0);
+ redraw(t);
+ if(specials[Alt].on){
+ collecting = 1;
+ collected = "";
+ }else
+ collecting = 0;
+ * =>
+ if(collecting){
+ collected[len collected] = c;
+ c = latin1(collected);
+ if(c < -1)
+ continue;
+ collecting = 0;
+ if(c == -1){
+ for(i := 0; i < len collected; i++)
+ sendkey(t, collected[i]);
+ continue;
+ }
+ }
+ show := specials[Ctrl].on | specials[Alt].on | specials[Shift].on;
+ if(specials[Ctrl].on)
+ c &= 16r1F;
+ active(t, Ctrl, 0);
+ active(t, Alt, 0);
+ active(t, Shift, 0);
+ if(show)
+ redraw(t);
+ sendkey(t, c);
+ }
+ m := <-t.ctxt.ptr =>
+ tk->pointer(t, *m);
+ s := <-t.ctxt.ctl or
+ s = <-t.wreq or
+ s = <-wcmd =>
+ if (s == "exit" && noexit)
+ s = "task";
+ tkclient->wmctl(t, s);
+ }
+}
+
+sendkey(t: ref Tk->Toplevel, c: int)
+{
+ sys->fprint(t.ctxt.connfd, "key %d", c);
+}
+
+active(t: ref Tk->Toplevel, keyno: int, on: int)
+{
+ key := specials[keyno:];
+ if(on < 0)
+ key[0].on ^= 1;
+ else
+ key[0].on = on;
+ for(xl := key[0].x; xl != nil; xl = tl xl){
+ col := background;
+ if(key[0].on)
+ col = "white";
+ cmd(t, ".b"+string hd xl+" configure -bg "+col+ " -activebackground "+col);
+ }
+}
+
+redraw(t: ref Tk->Toplevel)
+{
+ shifted := specials[Shift].on;
+ bank := keys[shifted];
+ vals := keyvals[shifted];
+ for(i:=0; i<len bank; i++) {
+ key := bank[i];
+ val := vals[i];
+ if(key != nil){
+ if(specials[CapsLock].on && len key == 1){
+ if(key[0]>='A' && key[0]<='Z') # true if also shifted
+ key[0] += 'a'-'A';
+ else if(key[0] >= 'a' && key[0]<='z')
+ key[0] += 'A'-'a';
+ val = key[0];
+ }
+ cmd(t, ".b" + string i + " configure -text {" + key + "} -command 'send keypress " + string val);
+ }
+ }
+ cmd(t, "update");
+}
+
+#
+# The code makes two assumptions: strlen(ld) is 1 or 2; latintab[i].ld can be a
+# prefix of latintab[j].ld only when j<i.
+#
+Cvlist: adt
+{
+ ld: string; # must be seen before using this conversion
+ si: string; # options for last input characters
+ so: string; # the corresponding Rune for each si entry
+};
+latintab: array of Cvlist = array[] of {
+ (" ", " i", "␣ı"),
+ ("!~", "-=~", "≄≇≉"),
+ ("!", "!<=>?bmp", "¡≮≠≯‽⊄∉⊅"),
+ ("\"*", "IUiu", "ΪΫϊϋ"),
+ ("\"", "\"AEIOUYaeiouy", "¨ÄËÏÖÜŸäëïöüÿ"),
+ ("$*", "fhk", "ϕϑϰ"),
+ ("$", "BEFHILMRVaefglopv", "ℬℰℱℋℐℒℳℛƲɑℯƒℊℓℴ℘ʋ"),
+ ("\'\"", "Uu", "Ǘǘ"),
+ ("\'", "\'ACEILNORSUYZacegilnorsuyz", "´ÁĆÉÍĹŃÓŔŚÚÝŹáćéģíĺńóŕśúýź"),
+ ("*", "*ABCDEFGHIKLMNOPQRSTUWXYZabcdefghiklmnopqrstuwxyz", "∗ΑΒΞΔΕΦΓΘΙΚΛΜΝΟΠΨΡΣΤΥΩΧΗΖαβξδεφγθικλμνοπψρστυωχηζ"),
+ ("+", "-O", "±⊕"),
+ (",", ",ACEGIKLNORSTUacegiklnorstu", "¸ĄÇĘĢĮĶĻŅǪŖŞŢŲąçęģįķļņǫŗşţų"),
+ ("-*", "l", "ƛ"),
+ ("-", "+-2:>DGHILOTZbdghiltuz~", "∓­ƻ÷→ÐǤĦƗŁ⊖ŦƵƀðǥℏɨłŧʉƶ≂"),
+ (".", ".CEGILOZceglz", "·ĊĖĠİĿ⊙Żċėġŀż"),
+ ("/", "Oo", "Øø"),
+ ("1", "234568", "½⅓¼⅕⅙⅛"),
+ ("2", "-35", "ƻ⅔⅖"),
+ ("3", "458", "¾⅗⅜"),
+ ("4", "5", "⅘"),
+ ("5", "68", "⅚⅝"),
+ ("7", "8", "⅞"),
+ (":", ")-=", "☺÷≔"),
+ ("<!", "=~", "≨⋦"),
+ ("<", "-<=>~", "←«≤≶≲"),
+ ("=", ":<=>OV", "≕⋜≡⋝⊜⇒"),
+ (">!", "=~", "≩⋧"),
+ (">", "<=>~", "≷≥»≳"),
+ ("?", "!?", "‽¿"),
+ ("@\'", "\'", "ъ"),
+ ("@@", "\'EKSTYZekstyz", "ьЕКСТЫЗекстыз"),
+ ("@C", "Hh", "ЧЧ"),
+ ("@E", "Hh", "ЭЭ"),
+ ("@K", "Hh", "ХХ"),
+ ("@S", "CHch", "ЩШЩШ"),
+ ("@T", "Ss", "ЦЦ"),
+ ("@Y", "AEOUaeou", "ЯЕЁЮЯЕЁЮ"),
+ ("@Z", "Hh", "ЖЖ"),
+ ("@c", "h", "ч"),
+ ("@e", "h", "э"),
+ ("@k", "h", "х"),
+ ("@s", "ch", "щш"),
+ ("@t", "s", "ц"),
+ ("@y", "aeou", "яеёю"),
+ ("@z", "h", "ж"),
+ ("@", "ABDFGIJLMNOPRUVXabdfgijlmnopruvx", "АБДФГИЙЛМНОПРУВХабдфгийлмнопрувх"),
+ ("A", "E", "Æ"),
+ ("C", "ACU", "⋂ℂ⋃"),
+ ("Dv", "Zz", "DŽDž"),
+ ("D", "-e", "Ð∆"),
+ ("G", "-", "Ǥ"),
+ ("H", "-H", "Ħℍ"),
+ ("I", "-J", "ƗIJ"),
+ ("L", "&-Jj|", "⋀ŁLJLj⋁"),
+ ("N", "JNj", "NJℕNj"),
+ ("O", "*+-./=EIcoprx", "⊛⊕⊖⊙⊘⊜ŒƢ©⊚℗®⊗"),
+ ("P", "P", "ℙ"),
+ ("Q", "Q", "ℚ"),
+ ("R", "R", "ℝ"),
+ ("S", "123S", "¹²³§"),
+ ("T", "-u", "Ŧ⊨"),
+ ("V", "=", "⇐"),
+ ("Y", "R", "Ʀ"),
+ ("Z", "-ACSZ", "Ƶℤ"),
+ ("^", "ACEGHIJOSUWYaceghijosuwy", "ÂĈÊĜĤÎĴÔŜÛŴŶâĉêĝĥîĵôŝûŵŷ"),
+ ("_\"", "AUau", "ǞǕǟǖ"),
+ ("_,", "Oo", "Ǭǭ"),
+ ("_.", "Aa", "Ǡǡ"),
+ ("_", "AEIOU_aeiou", "ĀĒĪŌŪ¯āēīōū"),
+ ("`\"", "Uu", "Ǜǜ"),
+ ("`", "AEIOUaeiou", "ÀÈÌÒÙàèìòù"),
+ ("a", "ben", "↔æ∠"),
+ ("b", "()+-0123456789=bknpqru", "₍₎₊₋₀₁₂₃₄₅₆₇₈₉₌♝♚♞♟♛♜•"),
+ ("c", "$Oagu", "¢©∩≅∪"),
+ ("dv", "z", "dž"),
+ ("d", "-adegz", "ð↓‡°†ʣ"),
+ ("e", "$lmns", "€⋯—–∅"),
+ ("f", "a", "∀"),
+ ("g", "$-r", "¤ǥ∇"),
+ ("h", "-v", "ℏƕ"),
+ ("i", "-bfjps", "ɨ⊆∞ij⊇∫"),
+ ("l", "\"$&\'-jz|", "“£∧‘łlj⋄∨"),
+ ("m", "iou", "µ∈×"),
+ ("n", "jo", "nj¬"),
+ ("o", "AOUaeiu", "Å⊚Ůåœƣů"),
+ ("p", "Odgrt", "℗∂¶∏∝"),
+ ("r", "\"\'O", "”’®"),
+ ("s", "()+-0123456789=abnoprstu", "⁽⁾⁺⁻⁰ⁱ⁲⁳⁴⁵⁶⁷⁸⁹⁼ª⊂ⁿº⊃√ß∍∑"),
+ ("t", "-efmsu", "ŧ∃∴™ς⊢"),
+ ("u", "-AEGIOUaegiou", "ʉĂĔĞĬŎŬ↑ĕğĭŏŭ"),
+ ("v\"", "Uu", "Ǚǚ"),
+ ("v", "ACDEGIKLNORSTUZacdegijklnorstuz", "ǍČĎĚǦǏǨĽŇǑŘŠŤǓŽǎčďěǧǐǰǩľňǒřšťǔž"),
+ ("w", "bknpqr", "♗♔♘♙♕♖"),
+ ("x", "O", "⊗"),
+ ("y", "$", "¥"),
+ ("z", "-", "ƶ"),
+ ("|", "Pp|", "Þþ¦"),
+ ("~!", "=", "≆"),
+ ("~", "-=AINOUainou~", "≃≅ÃĨÑÕŨãĩñõũ≈"),
+};
+
+#
+# Given 5 characters k[0]..k[4], find the rune or return -1 for failure.
+#
+unicode(k: string): int
+{
+ c := 0;
+ for(i:=1; i<5; i++){
+ r := k[i];
+ c <<= 4;
+ if('0'<=r && r<='9')
+ c += r-'0';
+ else if('a'<=r && r<='f')
+ c += 10 + r-'a';
+ else if('A'<=r && r<='F')
+ c += 10 + r-'A';
+ else
+ return -1;
+ }
+ return c;
+}
+
+#
+# Given n characters k[0]..k[n-1], find the corresponding rune or return -1 for
+# failure, or something < -1 if n is too small. In the latter case, the result
+# is minus the required n.
+#
+latin1(k: string): int
+{
+ n := len k;
+ if(k[0] == 'X' || n>1 && k[0] == 'x' && k[1]!='O') # 'x' to avoid having to Shift as well
+ if(n>=5)
+ return unicode(k);
+ else
+ return -5;
+ for(i := 0; i < len latintab; i++){
+ l := latintab[i];
+ if(k[0] == l.ld[0]){
+ if(n == 1)
+ return -2;
+ c := 0;
+ if(len l.ld == 1)
+ c = k[1];
+ else if(l.ld[1] != k[1])
+ continue;
+ else if(n == 2)
+ return -3;
+ else
+ c = k[2];
+ for(p:=0; p < len l.si; p++)
+ if(l.si[p] == c && p < len l.so)
+ return l.so[p];
+ return -1;
+ }
+ }
+ return -1;
+}
+
+cmd(top: ref Tk->Toplevel, c: string): string
+{
+ e := tk->cmd(top, c);
+ if (e != nil && e[0] == '!')
+ sys->fprint(sys->fildes(2), "keyboard: tk error on '%s': %s\n", c, e);
+ return e;
+}
diff --git a/appl/wm/logon.b b/appl/wm/logon.b
new file mode 100644
index 00000000..00643e87
--- /dev/null
+++ b/appl/wm/logon.b
@@ -0,0 +1,339 @@
+implement WmLogon;
+#
+# Logon program for Wm environment
+#
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Screen, Display, Image, Context, Point, Rect: import draw;
+ ctxt: ref Context;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "readdir.m";
+
+include "arg.m";
+include "sh.m";
+include "newns.m";
+include "keyring.m";
+include "security.m";
+
+WmLogon: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+cfg := array[] of {
+ "label .p -bitmap @/icons/inferno.bit -borderwidth 2 -relief raised",
+ "frame .l -bg red",
+ "label .l.u -fg black -bg silver -text {User Name:} -anchor w",
+ "pack .l.u -fill x",
+ "frame .e",
+ "entry .e.u -bg white",
+ "pack .e.u -fill x",
+ "frame .f -borderwidth 2 -relief raised",
+ "pack .l .e -side left -in .f",
+ "pack .p .f -fill x",
+ "bind .e.u <Key-\n> {send cmd ok}",
+ "focus .e.u"
+};
+
+listcfg := array[] of {
+ "frame .f",
+ "listbox .f.lb -yscrollcommand {.f.sb set}",
+ "scrollbar .f.sb -orient vertical -command {.f.lb yview}",
+ "button .login -text {Login} -command {send cmd login}",
+ "pack .f.sb .f.lb -in .f -side left -fill both -expand 1",
+ "pack .f -side top -anchor center -fill y -expand 1",
+ "pack .login -side top",
+# "pack propagate . 0",
+};
+
+init(actxt: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ if(tkclient == nil){
+ sys->fprint(stderr(), "logon: cannot load %s: %r\n", Tkclient->PATH);
+ raise "fail:bad module";
+ }
+ sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil);
+ tkclient->init();
+ ctxt = actxt;
+
+ dolist := 0;
+ usr := "";
+ nsfile := "namespace";
+ arg := load Arg Arg->PATH;
+ if(arg != nil){
+ arg->init(args);
+ arg->setusage("logon [-l] [-n namespace] [-u user]");
+ while((opt := arg->opt()) != 0){
+ case opt{
+ 'u' =>
+ usr = arg->earg();
+ 'l' =>
+ dolist = 1;
+ 'n' =>
+ nsfile = arg->earg();
+ * =>
+ arg->usage();
+ }
+ }
+ args = arg->argv();
+ arg = nil;
+ } else
+ args = nil;
+ if(ctxt == nil)
+ sys->fprint(stderr(), "logon: must run under a window manager\n");
+
+ (ctlwin, nil) := tkclient->toplevel(ctxt, nil, nil, Tkclient->Plain);
+ if(sys->fprint(ctlwin.ctxt.connfd, "request") == -1){
+ sys->fprint(stderr(), "logon: must be run as principal wm application\n");
+ raise "fail:lack of control";
+ }
+
+ if(dolist)
+ usr = chooseuser(ctxt);
+
+ if (usr == nil || !logon(usr)) {
+ (panel, cmd) := makepanel(ctxt, cfg);
+ stop := chan of int;
+ spawn tkclient->handler(panel, stop);
+ for(;;) {
+ tk->cmd(panel, "focus .e.u; update");
+ <-cmd;
+ usr = tk->cmd(panel, ".e.u get");
+ if(usr == "") {
+ notice("You must supply a user name to login");
+ continue;
+ }
+ if(logon(usr)) {
+ panel = nil;
+ stop <-= 1;
+ break;
+ }
+ tk->cmd(panel, ".e.u delete 0 end");
+ }
+ }
+ ok: int;
+ if(nsfile != nil){
+ (ok, nil) = sys->stat(nsfile);
+ if(ok < 0){
+ nsfile = nil;
+ (ok, nil) = sys->stat("namespace");
+ }
+ }else
+ (ok, nil) = sys->stat("namespace");
+ if(ok >= 0) {
+ ns := load Newns Newns->PATH;
+ if(ns == nil)
+ notice("failed to load namespace builder");
+ else if ((nserr := ns->newns(nil, nsfile)) != nil)
+ notice("namespace error:\n"+nserr);
+ }
+ tkclient->wmctl(ctlwin, "endcontrol");
+ errch := chan of string;
+ spawn exec(ctxt, args, errch);
+ err := <-errch;
+ if (err != nil) {
+ sys->fprint(stderr(), "logon: %s\n", err);
+ raise "fail:exec failed";
+ }
+}
+
+makepanel(ctxt: ref Draw->Context, cmds: array of string): (ref Tk->Toplevel, chan of string)
+{
+ (t, nil) := tkclient->toplevel(ctxt, "-bg silver", nil, Tkclient->Plain);
+
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+
+ for(i := 0; i < len cmds; i++)
+ tk->cmd(t, cmds[i]);
+ err := tk->cmd(t, "variable lasterr");
+ if(err != nil) {
+ sys->fprint(stderr(), "logon: tk error: %s\n", err);
+ raise "fail:config error";
+ }
+ tk->cmd(t, "update");
+ centre(t);
+ tkclient->startinput(t, "kbd" :: "ptr" :: nil);
+ tkclient->onscreen(t, "onscreen");
+ return (t, cmd);
+}
+
+exec(ctxt: ref Draw->Context, argv: list of string, errch: chan of string)
+{
+ sys->pctl(sys->NEWFD, 0 :: 1 :: 2 :: nil);
+ {
+ argv = "/dis/wm/toolbar.dis" :: nil;
+ cmd := load Command hd argv;
+ if (cmd == nil) {
+ errch <-= sys->sprint("cannot load %s: %r", hd argv);
+ } else {
+ errch <-= nil;
+ spawn cmd->init(ctxt, argv);
+ }
+ }exception{
+ "fail:*" =>
+ exit;
+ }
+}
+
+logon(user: string): int
+{
+ userdir := "/usr/"+user;
+ if(sys->chdir(userdir) < 0) {
+ notice("There is no home directory for \""+
+ user+"\"\nmounted on this machine");
+ return 0;
+ }
+
+ chmod("/chan", Sys->DMDIR|8r777);
+ chmod("/chan/wmrect", 8r666);
+ chmod("/chan/wmctl", 8r666);
+
+ #
+ # Set the user id
+ #
+ fd := sys->open("/dev/user", sys->OWRITE);
+ if(fd == nil) {
+ notice(sys->sprint("failed to open /dev/user: %r"));
+ return 0;
+ }
+ b := array of byte user;
+ if(sys->write(fd, b, len b) < 0) {
+ notice("failed to write /dev/user\nwith error "+sys->sprint("%r"));
+ return 0;
+ }
+
+ return 1;
+}
+
+chmod(file: string, mode: int): int
+{
+ d := sys->nulldir;
+ d.mode = mode;
+ if(sys->wstat(file, d) < 0){
+ notice(sys->sprint("failed to chmod %s: %r", file));
+ return -1;
+ }
+ return 0;
+}
+
+chooseuser(ctxt: ref Draw->Context): string
+{
+ (t, cmd) := makepanel(ctxt, listcfg);
+ usrlist := getusers();
+ if(usrlist == nil)
+ usrlist = "inferno" :: nil;
+ for(; usrlist != nil; usrlist = tl usrlist)
+ tkcmd(t, ".f.lb insert end '" + hd usrlist);
+ tkcmd(t, "update");
+ stop := chan of int;
+ spawn tkclient->handler(t, stop);
+ u := "";
+ for(;;){
+ <-cmd;
+ sel := tkcmd(t, ".f.lb curselection");
+ if(sel == nil)
+ continue;
+ u = tkcmd(t, ".f.lb get " + sel);
+ if(u != nil)
+ break;
+ }
+ stop <-= 1;
+ return u;
+}
+
+getusers(): list of string
+{
+ readdir := load Readdir Readdir->PATH;
+ if(readdir == nil)
+ return nil;
+ (dirs, nil) := readdir->init("/usr", Readdir->NAME);
+ n: list of string;
+ for (i := len dirs -1; i >=0; i--)
+ if (dirs[i].qid.qtype & Sys->QTDIR)
+ n = dirs[i].name :: n;
+ return n;
+}
+
+notecmd := array[] of {
+ "frame .f",
+ "label .f.l -bitmap error -foreground red",
+ "button .b -text Continue -command {send cmd done}",
+ "focus .f",
+ "bind .f <Key-\n> {send cmd done}",
+ "pack .f.l .f.m -side left -expand 1",
+ "pack .f .b",
+ "pack propagate . 0",
+};
+
+centre(t: ref Tk->Toplevel)
+{
+ org: Point;
+ ir := tk->rect(t, ".", Tk->Border|Tk->Required);
+ org.x = t.screenr.dx() / 2 - ir.dx() / 2;
+ org.y = t.screenr.dy() / 3 - ir.dy() / 2;
+#sys->print("ir: %d %d %d %d\n", ir.min.x, ir.min.y, ir.max.x, ir.max.y);
+ if (org.y < 0)
+ org.y = 0;
+ tk->cmd(t, ". configure -x " + string org.x + " -y " + string org.y);
+}
+
+notice(message: string)
+{
+ (t, nil) := tkclient->toplevel(ctxt, "-borderwidth 2 -relief raised", nil, Tkclient->Plain);
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+ tk->cmd(t, "label .f.m -anchor nw -text '"+message);
+ for(i := 0; i < len notecmd; i++)
+ tk->cmd(t, notecmd[i]);
+ centre(t);
+ tkclient->onscreen(t, "onscreen");
+ tkclient->startinput(t, "kbd"::"ptr"::nil);
+ stop := chan of int;
+ spawn tkclient->handler(t, stop);
+ tk->cmd(t, "update; cursor -default");
+ <-cmd;
+ stop <-= 1;
+}
+
+tkcmd(t: ref Tk->Toplevel, cmd: string): string
+{
+ s := tk->cmd(t, cmd);
+ if (s != nil && s[0] == '!') {
+ sys->print("%s\n", cmd);
+ sys->print("tk error: %s\n", s);
+ }
+ return s;
+}
+
+stderr(): ref Sys->FD
+{
+ return sys->fildes(2);
+}
+
+rf(path: string) : string
+{
+ fd := sys->open(path, sys->OREAD);
+ if(fd == nil)
+ return nil;
+
+ buf := array[512] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n <= 0)
+ return nil;
+
+ return string buf[0:n];
+}
diff --git a/appl/wm/logwindow.b b/appl/wm/logwindow.b
new file mode 100644
index 00000000..4d0326b4
--- /dev/null
+++ b/appl/wm/logwindow.b
@@ -0,0 +1,187 @@
+implement Logwindow;
+
+#
+# Copyright © 1999 Vita Nuova Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+ stderr: ref Sys->FD;
+include "draw.m";
+ draw: Draw;
+include "tk.m";
+ tk: Tk;
+ cmd: import tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+include "arg.m";
+
+Logwindow: module {
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+cfg := array[] of {
+ "frame .bf",
+ "checkbutton .bf.scroll -text Scroll -variable scroll -command {send cmd scroll}",
+ ".bf.scroll select",
+ "checkbutton .bf.popup -text {Pop up} -variable popup -command {send cmd popup}",
+ ".bf.popup select",
+ "pack .bf.scroll .bf.popup -side left",
+ "frame .t",
+ "scrollbar .t.scroll -command {.t.t yview}",
+ "text .t.t -height 7c -yscrollcommand {.t.scroll set}",
+ "pack .t.scroll -side left -fill y",
+ "pack .t.t -fill both -expand 1",
+ "pack .Wm_t -fill x",
+ "pack .bf -anchor w",
+ "pack .t -fill both -expand 1",
+ "pack propagate . 0",
+};
+
+eflag := 0;
+
+badmodule(p: string)
+{
+ sys->fprint(stderr, "logwindow: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+
+ tkclient = load Tkclient Tkclient->PATH;
+ if (tkclient == nil)
+ badmodule(Tkclient->PATH);
+ tkclient->init();
+
+ tk = load Tk Tk->PATH;
+ if (tk == nil)
+ badmodule(Tk->PATH);
+
+ arg := load Arg Arg->PATH;
+ if (arg == nil)
+ badmodule(Arg->PATH);
+
+ if (ctxt == nil) {
+ sys->fprint(stderr, "logwindow: nil Draw->Context\n");
+ raise "fail:no draw context";
+ }
+ gflag := 0;
+ title := "Log Window";
+ arg->init(argv);
+ while ((opt := arg->opt()) != 0) {
+ case opt {
+ 'e' =>
+ eflag = 1;
+ 'g' =>
+ gflag = 1;
+ * =>
+ sys->fprint(stderr, "usage: logwindow [-ge] [title]\n");
+ raise "fail:usage";
+ }
+ }
+ argv = arg->argv();
+ if (argv != nil)
+ title = hd argv;
+
+ if (!gflag)
+ sys->pctl(Sys->NEWPGRP, nil);
+
+ (top, wmchan) := tkclient->toplevel(ctxt, "", title, Tkclient->Hide|Tkclient->Resize);
+ if (top == nil) {
+ sys->fprint(stderr, "logwindow: couldn't make window\n");
+ raise "fail: no window";
+ }
+ cmd(top, ". unmap");
+
+ for (c:=0; c<len cfg; c++)
+ tk->cmd(top, cfg[c]);
+ if ((err := tk->cmd(top, "variable lasterror")) != nil) {
+ sys->fprint(stderr, "logwindow: tk error: %s\n", err);
+ raise "fail: tk error";
+ }
+
+ logwin(sys->fildes(0), top, wmchan);
+}
+
+scrolling := 1;
+popup := 1;
+
+logwin(fd: ref Sys->FD, top: ref Tk->Toplevel, wmchan: chan of string)
+{
+ cmd := chan of string;
+ tk->namechan(top, cmd, "cmd");
+ raised := 0;
+ ichan := chan of int;
+ spawn inputmon(fd, top, ichan);
+ tkclient->onscreen(top, nil);
+ tkclient->startinput(top, "kbd"::"ptr"::nil);
+ tkclient->wmctl(top, "task");
+ for (;;) alt {
+ s := <-top.ctxt.kbd =>
+ tk->keyboard(top, s);
+ s := <-top.ctxt.ptr =>
+ tk->pointer(top, *s);
+ s := <-top.ctxt.ctl or
+ s = <-top.wreq or
+ s = <-wmchan =>
+ case s {
+ "task" =>
+ raised = 0;
+ "untask" =>
+ raised = 1;
+ }
+ tkclient->wmctl(top, s);
+ e := <-ichan =>
+ if (e == 0 && eflag) {
+ tkclient->wmctl(top, "exit");
+ exit;
+ }
+ if (!raised && popup)
+ tkclient->wmctl(top, "untask");
+ msg := <-cmd =>
+ case msg {
+ "scroll" =>
+ scrolling = int tk->cmd(top, "variable scroll");
+ "popup" =>
+ popup = int tk->cmd(top, "variable popup");
+ }
+ }
+}
+
+inputmon(fd: ref Sys->FD, top: ref Tk->Toplevel, ichan: chan of int)
+{
+ buf := array[Sys->ATOMICIO] of byte;
+ t := 0;
+ while ((n := sys->read(fd, buf[t:], len buf-t)) > 0) {
+ t += n;
+ cl := 0;
+ for (i := t - 1; i >= 0; i--) {
+ (nil, cl, nil) = sys->byte2char(buf, i);
+ if (cl > 0)
+ break;
+ }
+ if (cl == 0)
+ continue;
+ logmsg(top, ichan, string buf[0:i+cl]);
+ buf[0:] = buf[i+cl:t];
+ t -= i + cl;
+ }
+ if (n < 0)
+ logmsg(top, ichan, sys->sprint("Input error: %r\n"));
+ else
+ logmsg(top, ichan, "Got EOF\n");
+ if (eflag)
+ ichan <-= 0;
+}
+
+logmsg(top: ref Tk->Toplevel, ichan: chan of int, m: string)
+{
+ tk->cmd(top, ".t.t insert end '"+m);
+ if (scrolling)
+ tk->cmd(top, ".t.t see end");
+ tk->cmd(top, "update");
+ ichan <-= 1;
+}
diff --git a/appl/wm/man.b b/appl/wm/man.b
new file mode 100644
index 00000000..89b4d12f
--- /dev/null
+++ b/appl/wm/man.b
@@ -0,0 +1,769 @@
+implement WmMan;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+include "tk.m";
+ tk: Tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+include "plumbmsg.m";
+include "man.m";
+ man: Man;
+
+WmMan: module {
+ init: fn (ctxt: ref Draw->Context, argv: list of string);
+};
+
+window: ref Tk->Toplevel;
+
+W: adt {
+ textwidth: fn(nil: self ref W, text: Parseman->Text): int;
+};
+
+ROMAN: con "/fonts/lucidasans/unicode.7.font";
+BOLD: con "/fonts/lucidasans/typelatin1.7.font";
+ITALIC: con "/fonts/lucidasans/italiclatin1.7.font";
+HEADING1: con "/fonts/lucidasans/boldlatin1.7.font";
+HEADING2: con "/fonts/lucidasans/italiclatin1.7.font";
+rfont, bfont, ifont, h1font, h2font: ref Draw->Font;
+
+GOATTR: con Parseman->ATTR_LAST << iota;
+MANPATH: con "/man/1/man";
+INDENT: con 40;
+
+metrics: Parseman->Metrics;
+parser: Parseman;
+
+
+tkconfig := array [] of {
+ "frame .input",
+ "frame .view",
+ "text .view.t -state disabled -width 0 -height 0 -bg white -yscrollcommand {.view.yscroll set} -xscrollcommand {.view.xscroll set}",
+ "scrollbar .view.yscroll -orient vertical -command {.view.t yview}",
+ "scrollbar .view.xscroll -orient horizontal -command {.view.t xview}",
+ "entry .input.e -bg white",
+ "button .input.back -state disabled -bitmap small_color_left.bit -command {send nav b}",
+ "button .input.forward -state disabled -bitmap small_color_right.bit -command {send nav f}",
+
+ "pack .input.back .input.forward -side left -anchor w",
+ "pack .input.e -expand 1 -fill x",
+
+ "pack .view.yscroll -fill y -side left",
+ "pack .view.t -expand 1 -fill both",
+
+ "bind .input.e <Key-\n> {send nav e}",
+ "bind .input.e <Button-1> +{grab set .input.e}",
+ "bind .input.e <ButtonRelease-1> +{grab release .input.e}",
+ "bind .view.t <Button-1> +{grab set .view.t}",
+ "bind .view.t <ButtonRelease-1> +{grab release .view.t}",
+ "bind .view.t <ButtonRelease-3> {send plumb %x %y}",
+
+ "pack .input -fill x",
+ "pack .view -expand 1 -fill both",
+ "pack propagate . 0",
+ ". configure -width 500 -height 500",
+ "focus .input.e",
+};
+
+History: adt {
+ prev: cyclic ref History;
+ next: cyclic ref History;
+ topline: string;
+ searchstart: string;
+ searchend: string;
+ pick {
+ Search =>
+ search: list of string;
+ Go =>
+ path: string;
+ }
+};
+
+history: ref History;
+
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ doplumb := 0;
+
+ sys = load Sys Sys->PATH;
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "man: no window context\n");
+ raise "fail:bad context";
+ }
+ sys->pctl(Sys->NEWPGRP, nil);
+
+ draw = load Draw Draw->PATH;
+ if (draw == nil)
+ loaderr("Draw");
+
+ tk = load Tk Tk->PATH;
+ if (tk == nil)
+ loaderr(Tk->PATH);
+
+ man = load Man Man->PATH;
+ if (man == nil)
+ loaderr(Man->PATH);
+
+ tkclient = load Tkclient Tkclient->PATH;
+ if (tkclient == nil)
+ loaderr(Tkclient->PATH);
+
+ parser = load Parseman Parseman->PATH;
+ if (parser == nil)
+ loaderr(Parseman->PATH);
+ parser->init();
+
+ plumber := load Plumbmsg Plumbmsg->PATH;
+ if (plumber != nil) {
+ if (plumber->init(1, nil, 0) >= 0)
+ doplumb = 1;
+ }
+
+ argv = tl argv;
+
+ rfont = draw->(Draw->Font).open(ctxt.display, ROMAN);
+ bfont = draw->(Draw->Font).open(ctxt.display, BOLD);
+ ifont = draw->(Draw->Font).open(ctxt.display, ITALIC);
+ h1font = draw->(Draw->Font).open(ctxt.display, HEADING1);
+ h2font = draw->(Draw->Font).open(ctxt.display, HEADING2);
+
+ em := draw->rfont.width("m");
+ en := draw->rfont.width("n");
+ metrics = Parseman->Metrics(490, 80, em, en, 14, 40, 20);
+
+ tkclient->init();
+ buts := Tkclient->Resize | Tkclient->Hide;
+ winctl: chan of string;
+ (window, winctl) = tkclient->toplevel(ctxt, nil, "Man", buts);
+ nav := chan of string;
+ plumb := chan of string;
+ tk->namechan(window, nav, "nav");
+ tk->namechan(window, plumb, "plumb");
+ for(tc:=0; tc<len tkconfig; tc++)
+ tkcmd(window, tkconfig[tc]);
+ if ((err := tkcmd(window, "variable lasterror")) != nil) {
+ sys->fprint(sys->fildes(2), "man: tk initialization failed: %s\n", err);
+ raise "fail:tk";
+ }
+ fittoscreen(window);
+ tkcmd(window, "update");
+ mktags();
+
+ vw := int tkcmd(window, ".view.t cget -actwidth") - 10;
+ if (vw <= 0)
+ vw = 1;
+ metrics.pagew = vw;
+
+ linechan := chan of list of (int, Parseman->Text);
+ man->loadsections(nil);
+
+ pidc := chan of int;
+
+ if (argv != nil) {
+ if (hd argv == "-f") {
+ first: ref History;
+ for (argv = tl argv; argv != nil; argv = tl argv) {
+ hnode := ref History.Go(history, nil, "", "", "", hd argv);
+ if (history != nil)
+ history.next = hnode;
+ history = hnode;
+ if (first == nil)
+ first = history;
+ }
+ history = first;
+ } else
+ history = ref History.Search(nil, nil, "", "", "", argv);
+ }
+
+ if (history == nil)
+ history = ref History.Go(nil, nil, "", "", "", MANPATH);
+
+ setbuttons();
+ spawn printman(pidc, linechan, history);
+ layoutpid := <- pidc;
+ tkclient->onscreen(window, nil);
+ tkclient->startinput(window, "kbd"::"ptr"::nil);
+ for (;;) alt {
+ s := <-window.ctxt.kbd =>
+ tk->keyboard(window, s);
+ s := <-window.ctxt.ptr =>
+ tk->pointer(window, *s);
+ s := <-window.ctxt.ctl or
+ s = <-window.wreq or
+ s = <-winctl =>
+ e := tkclient->wmctl(window, s);
+ if (e == nil && s[0] == '!') {
+ topline := tkcmd(window, ".view.t yview");
+ (nil, toptoks) := sys->tokenize(topline, " ");
+ if (toptoks != nil)
+ history.topline = hd toptoks;
+ vw = int tkcmd(window, ".view.t cget -actwidth") - 10;
+ if (vw <= 0)
+ vw = 1;
+ if (vw != metrics.pagew) {
+ if (layoutpid != -1)
+ kill(layoutpid);
+ metrics.pagew = vw;
+ tkcmd(window, ".view.t delete 1.0 end");
+ tkcmd(window, "update");
+ spawn printman(pidc, linechan, history);
+ layoutpid = <- pidc;
+ }
+ }
+ line := <- linechan =>
+ if (line == nil) {
+ # layout done
+ if (history.topline != "") {
+ topline := tkcmd(window, ".view.t yview");
+ (nil, toptoks) := sys->tokenize(topline, " ");
+ if (toptoks != nil)
+ if (hd toptoks == "0")
+ tkcmd(window, ".view.t yview moveto " + history.topline);
+ }
+ tkcmd(window, "update");
+ } else
+ setline(line);
+ go := <- nav =>
+ topline := tkcmd(window, ".view.t yview");
+ (nil, toptoks) := sys->tokenize(topline, " ");
+ if (toptoks != nil)
+ history.topline = hd toptoks;
+ case go[0] {
+ 'f' =>
+ # forward
+ history = history.next;
+ setbuttons();
+ if (layoutpid != -1)
+ kill(layoutpid);
+ tkcmd(window, ".view.t delete 1.0 end");
+ tkcmd(window, "update");
+ spawn printman(pidc, linechan, history);
+ layoutpid = <- pidc;
+ 'b' =>
+ # back
+ history = history.prev;
+ setbuttons();
+ if (layoutpid != -1)
+ kill(layoutpid);
+ tkcmd(window, ".view.t delete 1.0 end");
+ tkcmd(window, "update");
+ spawn printman(pidc, linechan, history);
+ layoutpid = <- pidc;
+ 'e' or 'l' =>
+ t := "";
+ if (go[0] == 'l') {
+ # link
+ t = go[1:];
+ } else {
+ # entry
+ t = tkcmd(window, ".input.e get");
+ for (i := 0; i < len t; i++)
+ if (!(t[i] == ' ' || t[i] == '\t'))
+ break;
+ if (i == len t)
+ break;
+ t = t[i:];
+ if (t[0] == '/' || t[0] == '?') {
+ search(t);
+ break;
+ }
+ }
+ (n, toks) := sys->tokenize(t, " \t");
+ if (n == 0)
+ continue;
+ h := ref History.Search(history, nil, "", "", "", toks);
+ history.next = h;
+ history = h;
+ setbuttons();
+ if (layoutpid != -1)
+ kill(layoutpid);
+ tkcmd(window, ".view.t delete 1.0 end");
+ tkcmd(window, "update");
+ spawn printman(pidc, linechan, history);
+ layoutpid = <- pidc;
+ 'g' =>
+ # goto file
+ h := ref History.Go(history, nil, "", "", "", go[1:]);
+ history.next = h;
+ history = h;
+ setbuttons();
+ if (layoutpid != 0)
+ kill(layoutpid);
+ tkcmd(window, ".view.t delete 1.0 end");
+ tkcmd(window, "update");
+ spawn printman(pidc, linechan, history);
+ layoutpid = <- pidc;
+ }
+ p := <- plumb =>
+ if (!doplumb)
+ break;
+ (nil, l) := sys->tokenize(p, " ");
+ x := int hd l;
+ y := int hd tl l;
+ index := tkcmd(window, ".view.t index @"+string x+","+string y);
+ selindex := tkcmd(window, ".view.t tag ranges sel");
+ insel := 0;
+ if(selindex != "")
+ insel = tkcmd(window, ".view.t compare sel.first <= "+index)=="1" &&
+ tkcmd(window, ".view.t compare sel.last >= "+index)=="1";
+ text := "";
+ attr := "";
+ if (insel)
+ text = tkcmd(window, ".view.t get sel.first sel.last");
+ else{
+ # have line with text in it
+ # now extract whitespace-bounded string around click
+ (nil, w) := sys->tokenize(index, ".");
+ charno := int hd tl w;
+ left := tkcmd(window, ".view.t index {"+index+" linestart}");
+ right := tkcmd(window, ".view.t index {"+index+" lineend}");
+ line := tkcmd(window, ".view.t get "+left+" "+right);
+ for(i:=charno; i>0; --i)
+ if(line[i-1]==' ' || line[i-1]=='\t')
+ break;
+ for(j:=charno; j<len line; j++)
+ if(line[j]==' ' || line[j]=='\t')
+ break;
+ text = line[i:j];
+ attr = "click="+string (charno-i);
+ }
+ msg := ref Plumbmsg->Msg(
+ "WmMan",
+ "",
+ "",
+ "text",
+ attr,
+ array of byte text);
+ plumber->msg.send();
+
+ layoutpid = <- pidc =>
+ ;
+ }
+}
+
+search(pat: string)
+{
+ dir: string;
+ start: string;
+ if (pat[0] == '/') {
+ dir = "-forwards";
+ start = history.searchend;
+ } else {
+ dir = "-backwards";
+ start = history.searchstart;
+ }
+ pat = pat[1:];
+ if (start == "")
+ start = "1.0";
+ r := tkcmd(window, ".view.t search " + dir + " -- " + tk->quote(pat) + " " + start);
+ if (r != nil) {
+ history.searchstart = r;
+ history.searchend = r + "+" + string len pat + "c";
+ tkcmd(window, ".view.t tag remove sel 1.0 end");
+ tkcmd(window, ".view.t tag add sel " + history.searchstart + " " + history.searchend);
+ tkcmd(window, ".view.t see " + r);
+ tkcmd(window, "update");
+ }
+}
+
+setbuttons()
+{
+ if (history.prev == nil)
+ tkcmd(window, ".input.back configure -state disabled");
+ else
+ tkcmd(window, ".input.back configure -state normal");
+ if (history.next == nil)
+ tkcmd(window, ".input.forward configure -state disabled");
+ else
+ tkcmd(window, ".input.forward configure -state normal");
+}
+
+dolayout(linechan: chan of list of (int, Parseman->Text), path: string)
+{
+ fd := sys->open(path, Sys->OREAD);
+ if (fd == nil) {
+ layouterror(linechan, sys->sprint("cannot open file %s: %r", path));
+ return;
+ }
+ w: ref W;
+ parser->parseman(fd, metrics, 0, w, linechan);
+}
+
+printman(pidc: chan of int, linechan: chan of list of (int, Parseman->Text), h: ref History)
+{
+ pidc <-= sys->pctl(0, nil);
+ args: list of string;
+ pick hp := h {
+ Search =>
+ args = hp.search;
+ Go =>
+ dolayout(linechan, hp.path);
+ pidc <-= -1;
+ return;
+ }
+ sections: list of string;
+ argstext := "";
+ addsections := 1;
+ keywords: list of string;
+ for (; args != nil; args = tl args) {
+ arg := hd args;
+ if (arg == nil)
+ continue;
+ if (addsections && !isint(arg)) {
+ addsections = 0;
+ keywords = args;
+ }
+ if (addsections)
+ sections = arg :: sections;
+ argstext = argstext + " " + arg;
+ }
+ manpages := man->getfiles(sections, keywords);
+ pagelist := sortpages(manpages);
+ if (len pagelist == 1) {
+ (nil, path, nil) := hd pagelist;
+ dolayout(linechan, path);
+ pidc <-= -1;
+ return;
+ }
+
+ tt := Parseman->Text(Parseman->FONT_ROMAN, 0, "Search:", 1, nil);
+ at := Parseman->Text(Parseman->FONT_BOLD, 0, argstext, 0, nil);
+ linechan <-= (0, tt)::(0, at)::nil;
+ tt.text = "";
+ linechan <-= (0, tt)::nil;
+
+ if (pagelist == nil) {
+ donet := Parseman->Text(Parseman->FONT_ROMAN, 0, "No matches", 0, nil);
+ linechan <-= (INDENT, donet) :: nil;
+ linechan <-= nil;
+ pidc <-= -1;
+ return;
+ }
+
+ linelist: list of list of Parseman->Text;
+ pathlist: list of Parseman->Text;
+
+ maxkwlen := 0;
+ comma := Parseman->Text(Parseman->FONT_ROMAN, 0, ", ", 0, "");
+ for (; pagelist != nil; pagelist = tl pagelist) {
+ (n, p, kwl) := hd pagelist;
+ l := 0;
+ keywords: list of Parseman->Text = nil;
+ for (; kwl != nil; kwl = tl kwl) {
+ kw := hd kwl;
+ kwt := Parseman->Text(Parseman->FONT_ITALIC, GOATTR, kw, 0, p);
+ nt := Parseman->Text(Parseman->FONT_ROMAN, GOATTR, "(" + string n + ")", 0, p);
+ l += textwidth(kwt) + textwidth(nt);
+ if (keywords != nil) {
+ l += textwidth(comma);
+ keywords = nt :: kwt :: comma :: keywords;
+ } else
+ keywords = nt :: kwt :: nil;
+ }
+ if (l > maxkwlen)
+ maxkwlen = l;
+ linelist = keywords :: linelist;
+ ptext := Parseman->Text(Parseman->FONT_ROMAN, GOATTR, p, 0, "");
+ pathlist = ptext :: pathlist;
+ }
+
+ for (; pathlist != nil; (pathlist, linelist) = (tl pathlist, tl linelist)) {
+ line := (10 + INDENT + maxkwlen, hd pathlist) :: nil;
+ for (ll := hd linelist; ll != nil; ll = tl ll) {
+ litem := hd ll;
+ if (tl ll == nil)
+ line = (INDENT, litem) :: line;
+ else
+ line = (0, litem) :: line;
+ }
+ linechan <-= line;
+ }
+ linechan <-= nil;
+ pidc <-= -1;
+}
+
+layouterror(linechan: chan of list of (int, Parseman->Text), msg: string)
+{
+ text := "ERROR: " + msg;
+ t := Parseman->Text(Parseman->FONT_ROMAN, 0, text, 0, nil);
+ linechan <-= (0, t)::nil;
+ linechan <-= nil;
+}
+
+loaderr(modname: string)
+{
+ sys->print("cannot load %s module: %r\n", modname);
+ raise "fail:init";
+}
+
+W.textwidth(nil: self ref W, text: Parseman->Text): int
+{
+ return textwidth(text);
+}
+
+textwidth(text: Parseman->Text): int
+{
+ f: ref Draw->Font;
+ if (text.heading == 1)
+ f = h1font;
+ else if (text.heading == 2)
+ f = h2font;
+ else {
+ case text.font {
+ Parseman->FONT_ROMAN =>
+ f = rfont;
+ Parseman->FONT_BOLD =>
+ f = bfont;
+ Parseman->FONT_ITALIC =>
+ f = ifont;
+ * =>
+ return 8 * len text.text;
+ }
+ }
+ return draw->f.width(text.text);
+}
+
+lnum := 0;
+
+setline(line: list of (int, Parseman->Text))
+{
+ tabstr := "";
+ linestr := "";
+ lastoff := 0;
+ curfont := Parseman->FONT_ROMAN;
+ curlink := "";
+ curgtag := "";
+ curheading := 0;
+ fonttext := "";
+
+ for (l := line; l != nil; l = tl l) {
+ (offset, nil) := hd l;
+ if (offset != 0) {
+ lastoff = offset;
+ if (tabstr != "")
+ tabstr[len tabstr] = ' ';
+ tabstr = tabstr + string offset;
+ }
+ }
+ # fudge up tabs for rest of line
+ if (lastoff != 0)
+ tabstr = tabstr + " " + string lastoff + " " + string (lastoff + INDENT);
+ ttag := "";
+ gtag := "";
+ if (tabstr != nil)
+ ttag = tabtag(tabstr) + " ";
+
+ for (l = line; l != nil; l = tl l) {
+ (offset, text) := hd l;
+ gtag = "";
+ if (text.link != nil) {
+ if (text.attr & GOATTR)
+ gtag = gotag(text.link) + " ";
+ else {
+ gtag = linktag(text.link) + " ";
+ }
+ }
+ if (offset != 0)
+ fonttext[len fonttext] = '\t';
+ if (text.font != curfont || text.link != curlink || text.heading != curheading || gtag != curgtag) {
+ # need to change tags
+ linestr = linestr + " " + tk->quote(fonttext) + " {" + ttag + curgtag + fonttag(curfont, curheading) + "}";
+ ttag = "";
+ curgtag = gtag;
+ fonttext = "";
+ curfont = text.font;
+ curlink = text.link;
+ curheading = text.heading;
+ }
+ fonttext = fonttext + text.text;
+ }
+ if (fonttext != nil)
+ linestr = linestr + " " + tk->quote(fonttext) + " {" + ttag + curgtag + fonttag(curfont, curheading) + "}";
+ tkcmd(window, ".view.t insert end " + linestr);
+ tkcmd(window, ".view.t insert end {\n}");
+ # only update on every other line
+ if (lnum++ & 1)
+ tkcmd(window, "update");
+}
+
+mktags()
+{
+ tkcmd(window, ".view.t tag configure ROMAN -font " + ROMAN);
+ tkcmd(window, ".view.t tag configure BOLD -font " + BOLD);
+ tkcmd(window, ".view.t tag configure ITALIC -font " + ITALIC);
+ tkcmd(window, ".view.t tag configure H1 -font " + HEADING1);
+ tkcmd(window, ".view.t tag configure H2 -font " + HEADING2);
+}
+
+fonttag(font, heading: int): string
+{
+ if (heading == 1)
+ return "H1";
+ if (heading == 2)
+ return "H2";
+ case font {
+ Parseman->FONT_ROMAN =>
+ return "ROMAN";
+ Parseman->FONT_BOLD =>
+ return "BOLD";
+ Parseman->FONT_ITALIC =>
+ return "ITALIC";
+ }
+ return nil;
+}
+
+nexttag := 0;
+lasttabstr := "";
+lasttagname := "";
+
+tabtag(tabstr: string): string
+{
+ if (tabstr == lasttabstr)
+ return lasttagname;
+ lasttagname = "TAB" + string nexttag++;
+ lasttabstr = tabstr;
+ tkcmd(window, ".view.t tag configure " + lasttagname + " -tabs " + tk->quote(tabstr));
+ return lasttagname;
+}
+
+# optimise this!
+gotag(path: string): string
+{
+ cmd := "{send nav g" + path + "}";
+ name := "GO" + string nexttag++;
+ tkcmd(window, ".view.t tag bind " + name + " <ButtonRelease-1> +" + cmd);
+ tkcmd(window, ".view.t tag configure " + name + " -fg green");
+ return name;
+}
+
+# and this!
+linktag(search: string): string
+{
+ cmd := tk->quote("send nav l" + search);
+ name := "LN" + string nexttag++;
+ tkcmd(window, ".view.t tag bind " + name + " <ButtonRelease-1> +" + cmd);
+ tkcmd(window, ".view.t tag configure " + name + " -fg green");
+ return name;
+}
+
+isint(s: string): int
+{
+ for (i := 0; i < len s; i++)
+ if (s[i] < '0' || s[i] > '9')
+ return 0;
+ return 1;
+}
+
+kill(pid: int)
+{
+ pctl := sys->open("/prog/" + string pid + "/ctl", Sys->OWRITE);
+ if (pctl != nil) {
+ poison := array of byte "kill";
+ sys->write(pctl, poison, len poison);
+ }
+}
+
+revsortuniq(strlist: list of string): list of string
+{
+ strs := array [len strlist] of string;
+ for (i := 0; strlist != nil; (i, strlist) = (i+1, tl strlist))
+ strs[i] = hd strlist;
+
+ # simple sort (ascending)
+ for (i = 0; i < len strs - 1; i++) {
+ for (j := i+1; j < len strs; j++)
+ if (strs[i] < strs[j])
+ (strs[i], strs[j]) = (strs[j], strs[i]);
+ }
+
+ # construct list (result is descending)
+ r: list of string;
+ prev := "";
+ for (i = 0; i < len strs; i++) {
+ if (strs[i] != prev) {
+ r = strs[i] :: r;
+ prev = strs[i];
+ }
+ }
+ return r;
+}
+
+sortpages(pagelist: list of (int, string, string)): list of (int, string, list of string)
+{
+ pages := array [len pagelist] of (int, string, string);
+ for (i := 0; pagelist != nil; (i, pagelist) = (i+1, tl pagelist))
+ pages[i] = hd pagelist;
+
+ for (i = 0; i < len pages - 1; i++) {
+ for (j := i+1; j < len pages; j++) {
+ (nil, nil, ipath) := pages[i];
+ (nil, nil, jpath) := pages[j];
+ if (ipath > jpath)
+ (pages[i], pages[j]) = (pages[j], pages[i]);
+ }
+ }
+
+ r: list of (int, string, list of string);
+ filecmds: list of string;
+ lastfile := "";
+ lastsect := 0;
+ for (i = 0; i < len pages; i++) {
+ (section, cmd, file) := pages[i];
+ if (lastfile == "") {
+ lastfile = file;
+ lastsect = section;
+ }
+
+ if (file != lastfile) {
+ r = (lastsect, lastfile, filecmds) :: r;
+ lastfile = file;
+ lastsect = section;
+ filecmds = nil;
+ }
+ filecmds = cmd :: filecmds;
+ }
+ if (filecmds != nil)
+ r = (lastsect, lastfile, revsortuniq(filecmds)) :: r;
+ return r;
+}
+
+fittoscreen(win: ref Tk->Toplevel)
+{
+ Point, Rect: import draw;
+ if (win.image == nil || win.image.screen == nil)
+ return;
+ r := win.image.screen.image.r;
+ scrsize := Point((r.max.x - r.min.x), (r.max.y - r.min.y));
+ bd := int tkcmd(win, ". cget -bd");
+ winsize := Point(int tkcmd(win, ". cget -actwidth") + bd * 2, int tkcmd(win, ". cget -actheight") + bd * 2);
+ if (winsize.x > scrsize.x)
+ tkcmd(win, ". configure -width " + string (scrsize.x - bd * 2));
+ if (winsize.y > scrsize.y)
+ tkcmd(win, ". configure -height " + string (scrsize.y - bd * 2));
+ actr: Rect;
+ actr.min = Point(int tkcmd(win, ". cget -actx"), int tkcmd(win, ". cget -acty"));
+ actr.max = actr.min.add((int tkcmd(win, ". cget -actwidth") + bd*2,
+ int tkcmd(win, ". cget -actheight") + bd*2));
+ (dx, dy) := (actr.dx(), actr.dy());
+ if (actr.max.x > r.max.x)
+ (actr.min.x, actr.max.x) = (r.max.x - dx, r.max.x);
+ if (actr.max.y > r.max.y)
+ (actr.min.y, actr.max.y) = (r.max.y - dy, r.max.y);
+ if (actr.min.x < r.min.x)
+ (actr.min.x, actr.max.x) = (r.min.x, r.min.x + dx);
+ if (actr.min.y < r.min.y)
+ (actr.min.y, actr.max.y) = (r.min.y, r.min.y + dy);
+ tkcmd(win, ". configure -x " + string actr.min.x + " -y " + string actr.min.y);
+}
+
+tkcmd(top: ref Tk->Toplevel, s: string): string
+{
+ e := tk->cmd(top, s);
+ if (e != nil && e[0] == '!') {
+ sys->print("tk error %s on '%s'\n", e, s);
+ }
+ return e;
+}
diff --git a/appl/wm/mand.b b/appl/wm/mand.b
new file mode 100644
index 00000000..7e060722
--- /dev/null
+++ b/appl/wm/mand.b
@@ -0,0 +1,839 @@
+implement Mand;
+
+#
+# Copyright © 2000 Vita Nuova Limited. All rights reserved.
+#
+
+# mandelbrot/julia fractal browser:
+# button 1 - drag a rectangle to zoom into
+# button 2 - (from mandel only) show julia at point
+# button 3 - zoom out
+
+include "sys.m";
+ sys : Sys;
+include "draw.m";
+ draw : Draw;
+ Point, Rect, Image, Context, Screen, Display : import draw;
+include "tk.m";
+ tk: Tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+
+Mand : module
+{
+ init : fn(nil : ref Context, argv : list of string);
+};
+
+colours: array of ref Image;
+stderr : ref Sys->FD;
+
+FIX: type big;
+
+Calc: adt {
+ xr, yr: array of FIX;
+ parx, pary: FIX;
+ # column order
+ dispbase: array of COL; # auxiliary display and border
+ imgch: chan of (ref Image, Rect);
+ img: ref Image;
+ maxx, maxy, supx, supy: int;
+ disp: int; # origin of auxiliary display
+ morj : int;
+ winr: Rect;
+ kdivisor: int;
+ pointsdone: int;
+};
+
+# BASE, LIMIT, MAXCOUNT, MINDELTA may be varied
+
+#
+# calls with 256X128 on initial set
+# ---------------------------------
+# crawl 58 (5% of time)
+# fillline 894 (6% of time)
+# isblank 5012 (0% of time)
+# mcount 6928 (55% of time)
+# getcolour 52942 (11% of time)
+# displayset 1 (15% of time)
+#
+WHITE : con 16r0;
+BLACK : con 16rff;
+
+COL : type byte;
+
+BASE : con 60; # 28
+HBASE : con (BASE/2);
+SCALE : con (big 1<<BASE);
+TWO : con (big 1<<(BASE+1));
+FOUR : con (big 1<<(BASE+2));
+NEG : con (~((big 1<<(32-HBASE))-big 1));
+MINDELTA : con (big 1<<(HBASE-1)); # (1<<(HBASE-2))
+
+SCHEDCOUNT: con 100;
+
+BLANK : con 0; # blank pixel
+BORDER : con 255; # border pixel
+LIMIT : con 4; # 4 or 5
+
+# pointcolour() returns values in the range 1..MAXCOUNT+1
+# these must not clash with 0 or 255
+# hence 0 <= MAXCOUNT <= 253
+#
+MAXCOUNT : con 253; # 92 64
+
+# colour cube
+R, G, B : int;
+
+# initial width and height
+WIDTH: con 400;
+HEIGHT: con 400;
+
+Fracpoint: adt {
+ x, y: real;
+};
+
+Fracrect: adt {
+ min, max: Fracpoint;
+};
+
+Params: adt {
+ r: Fracrect;
+ p: Fracpoint;
+ m: int;
+ kdivisor: int;
+ fill: int;
+};
+
+Usercmd: adt {
+ pick {
+ Zoomin =>
+ r: Rect;
+ Julia =>
+ p: Point;
+ Zoomout or
+ Restart =>
+ # nothing
+ }
+};
+
+badmod(mod: string)
+{
+ sys->fprint(stderr, "mand: cannot load %s: %r\n", mod);
+ raise "fail:bad module";
+}
+
+win_config := array[] of {
+ "frame .f",
+ "label .f.dl -text Depth",
+ "entry .f.depth",
+ ".f.depth insert 0 1",
+ "checkbutton .f.fill -text {Fill} -command {send cmd fillchanged} -variable fill",
+ ".f.fill select",
+ "pack .f.dl -side left",
+ "pack .f.fill -side right",
+ "pack .f.depth -side top -fill x",
+ "frame .c -bd 3 -relief sunken -width " + string WIDTH + " -height " + string HEIGHT,
+ "pack .f -side top -fill x",
+ "pack .c -side bottom -fill both -expand 1",
+ "pack propagate . 0",
+ "bind .c <Button-1> {send cmd b1 %x %y}",
+ "bind .c <ButtonRelease-2> {send cmd b2 %x %y}",
+ "bind .c <ButtonRelease-1> {send cmd b1r %x %y}",
+ "bind .c <ButtonRelease-3> {send cmd b3 %x %y}",
+
+ "bind .f.depth <Key-\n> {send cmd setkdivisor}",
+ "update",
+};
+
+mouseproc(win: ref Tk->Toplevel)
+{
+ for(;;)
+ tk->pointer(win, *<-win.ctxt.ptr);
+}
+
+init(ctxt: ref Context, argv : list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+
+ tkclient = load Tkclient Tkclient->PATH;
+ if (tkclient == nil) badmod(Tkclient->PATH);
+
+ tkclient->init();
+ if (ctxt == nil)
+ ctxt = tkclient->makedrawcontext();
+ (win, wmcmd) := tkclient->toplevel(ctxt, "", "Fractals", Tkclient->Appl);
+ sys->pctl(Sys->NEWPGRP, nil);
+
+ cmdch := chan of string;
+ tk->namechan(win, cmdch, "cmd");
+ for (i := 0; i < len win_config; i++)
+ cmd(win, win_config[i]);
+ tkclient->onscreen(win, nil);
+ tkclient->startinput(win, "kbd"::"ptr"::nil);
+ fittoscreen(win);
+ cmd(win, "update");
+ spawn mouseproc(win);
+
+ R = G = B = 6;
+ argv = tl argv;
+ if (argv != nil) { (R, argv) = (int hd argv, tl argv); if (R <= 0) R = 1; }
+ if (argv != nil) { (G, argv) = (int hd argv, tl argv); if (G <= 0) G = 1; }
+ if (argv != nil) { (B, argv) = (int hd argv, tl argv); if (B <= 0) B = 1; }
+ colours = array[256] of ref Image;
+ for (i = 0; i < len colours; i++)
+ # colours[i] = ctxt.display.color(i);
+ colours[i] = ctxt.display.rgb(col(i/(G*B), R),
+ col(i/(1*B), G),
+ col(i/(1*1), B));
+ specr := Fracrect((-2.0, -1.5), (1.0, 1.5));
+ p := Params(
+ correctratio(specr, win),
+ (0.0, 0.0),
+ 1, # m
+ 1, # kdivisor
+ int cmd(win, "variable fill")
+ );
+ pid := -1;
+ sync := chan of int;
+ imgch := chan of (ref Image, Rect);
+ canvr := canvposn(win);
+ spawn docalculate(sync, p, imgch);
+ pid = <-sync;
+ imgch <-= (win.image, canvr);
+
+ stack: list of (Fracrect, Params);
+ for(;;){
+ restart := 0;
+ alt {
+ s := <-win.ctxt.kbd =>
+ tk->keyboard(win, s);
+ c := <-win.ctxt.ctl or
+ c = <-win.wreq or
+ c = <-wmcmd =>
+ if(c[0] == '!'){
+ if(pid != -1)
+ restart = winreq(win, c, imgch, sync);
+ else
+ restart = winreq(win, c, nil, nil);
+ }else{
+ tkclient->wmctl(win, c);
+ if(c == "task" && pid != -1){
+ kill(pid);
+ pid = -1;
+ }
+ }
+ press := <-cmdch =>
+ (nil, toks) := sys->tokenize(press, " ");
+ ucmd: ref Usercmd = nil;
+ case hd toks {
+ "start" =>
+ ucmd = ref Usercmd.Restart;
+ "b1" or "b2" or "b3" =>
+ #cmd(win, "grab set .c");
+ #fiximage(win);
+ ucmd = trackmouse(win, cmdch, hd toks, Point(int hd tl toks, int hd tl tl toks));
+ #cmd(win, "grab release .c");
+ "fillchanged" =>
+ p.fill = int cmd(win, "variable fill");
+ ucmd = ref Usercmd.Restart;
+ "setkdivisor" =>
+ p.kdivisor = int cmd(win, ".f.depth get");
+ if (p.kdivisor < 1)
+ p.kdivisor = 1;
+ ucmd = ref Usercmd.Restart;
+ }
+ if (ucmd != nil) {
+ pick u := ucmd {
+ Zoomin =>
+ # sys->print("zoomin to %s\n", r2s(u.r));
+ if (u.r.dx() > 0 && u.r.dy() > 0) {
+ stack = (specr, p) :: stack;
+ specr.min = pt2real(u.r.min, win, p.r);
+ specr.max = pt2real(u.r.max, win, p.r);
+ (specr.min.y, specr.max.y) = (specr.max.y, specr.min.y); # canonicalise
+ restart = 1;
+ }
+ Zoomout =>
+ if (stack != nil) {
+ ((specr, p), stack) = (hd stack, tl stack);
+ cmd(win, ".f.depth delete 0 end");
+ cmd(win, ".f.depth insert 0 " + string p.kdivisor);
+ if (p.fill)
+ cmd(win, ".f.fill select");
+ else
+ cmd(win, ".f.fill deselect");
+ cmd(win, "update");
+ restart = 1;
+ }
+ Julia =>
+ # pt := pt2real(u.p, win, p.r);
+ if (p.m) {
+ stack = (specr, p) :: stack;
+ p.p = pt2real(u.p, win, p.r);
+ specr = ((-2.0, -1.5), (1.0, 1.5));
+ p.m = 0;
+ restart = 1;
+ }
+ Restart =>
+ restart = 1;
+ }
+ }
+ <-sync =>
+ win.image.flush(Draw->Flushon);
+ pid = -1;
+ }
+ if (restart) {
+ if (pid != -1)
+ kill(pid);
+ win.image.flush(Draw->Flushoff);
+ p.r = correctratio(specr, win);
+ sync = chan of int;
+ spawn docalculate(sync, p, imgch);
+ pid = <-sync;
+ imgch <-= (win.image, canvposn(win));
+ }
+ }
+}
+
+winreq(win: ref Tk->Toplevel, c: string, imgch: chan of (ref Image, Rect), terminated: chan of int): int
+{
+ oldimage := win.image;
+ if (imgch != nil) {
+ # halt calculation process
+ alt {
+ imgch <-= (nil, ((0,0), (0,0))) =>;
+ <-terminated =>
+ imgch = nil;
+ }
+ }
+ tkclient->wmctl(win, c);
+ if(win.image != oldimage)
+ return 1;
+ if(imgch != nil)
+ imgch <-= (win.image, canvposn(win));
+ return 0;
+}
+
+correctratio(r: Fracrect, win: ref Tk->Toplevel): Fracrect
+{
+ # make sure calculation rectangle is in
+ # the same ratio as bitmap (also make sure that
+ # calculated area always includes desired area)
+ wr := canvposn(win);
+ (btall, atall) := (real wr.dy() / real wr.dx(), (r.max.y - r.min.y) / (r.max.x - r.min.x));
+ if (btall > atall) {
+ # bitmap is taller than area, so expand area vertically
+ excess := (r.max.x - r.min.x) * btall - (r.max.y - r.min.y);
+ r.min.y -= excess / 2.0;
+ r.max.y += excess / 2.0;
+ } else {
+ # area is taller than bitmap, so expand area horizontally
+ excess := (r.max.y - r.min.y) / btall - (r.max.x - r.min.x);
+ r.min.x -= excess / 2.0;
+ r.max.x += excess / 2.0;
+ }
+ return r;
+}
+
+pt2real(pt: Point, win: ref Tk->Toplevel, r: Fracrect): Fracpoint
+{
+ sz := Point(int cmd(win, ".c cget -actwidth"), int cmd(win, ".c cget -actheight"));
+ return (real pt.x / real sz.x * (r.max.x- r.min.x) + r.min.x,
+ real (sz.y - pt.y) / real sz.y * (r.max.y - r.min.y) + r.min.y);
+}
+
+pt2s(pt: Point): string
+{
+ return string pt.x + " " + string pt.y;
+}
+
+r2s(r: Rect): string
+{
+ return pt2s(r.min) + " " + pt2s(r.max);
+}
+
+trackmouse(win: ref Tk->Toplevel, cmdch: chan of string, but: string, p: Point): ref Usercmd
+{
+ case but {
+ "b1" =>
+ cr := canvposn(win);
+ display := win.image.display;
+ save := display.newimage(cr, win.image.chans, 0, Draw->Nofill);
+ save.draw(cr, win.image, nil, cr.min);
+ oclip := win.image.clipr;
+ win.image.clipr = cr;
+
+ p = p.add(cr.min);
+ r := Rect(p, p);
+ win.image.border(r, 1, display.white, (0, 0));
+ win.image.flush(Draw->Flushnow);
+ do {
+ but = <-cmdch;
+ (nil, toks) := sys->tokenize(but, " ");
+ but = hd toks;
+ if(but == "b1"){
+ xr := r.canon();
+ win.image.draw(xr, save, nil, xr.min);
+ (r.max.x, r.max.y) = (int hd tl toks + cr.min.x, int hd tl tl toks + cr.min.y);
+ win.image.border(r.canon(), 1, display.white, (0, 0));
+ win.image.flush(Draw->Flushnow);
+ }
+ } while (but != "b1r");
+ r = r.canon();
+ win.image.draw(r, save, nil, r.min);
+ win.image.clipr = oclip;
+ r = r.subpt(cr.min);
+ return ref Usercmd.Zoomin(r);
+ "b2" =>
+ return ref Usercmd.Julia(p);
+ "b3" =>
+ return ref Usercmd.Zoomout;
+ }
+ return nil;
+}
+
+poll(calc: ref Calc)
+{
+ calc.img.flush(Draw->Flushnow);
+ alt {
+ <-calc.imgch =>
+ calc.img = nil;
+ (calc.img, calc.winr) = <-calc.imgch;
+ * =>;
+ }
+}
+
+docalculate(sync: chan of int, p: Params, imgch: chan of (ref Image, Rect))
+{
+ if (p.m)
+ ; # sys->print("mandel [[%g,%g],[%g,%g]]\n", r.min.x, r.min.y, r.max.x, r.max.y);
+ else
+ ; # sys->print("julia [[%g,%g],[%g,%g]] [%g,%g]\n", r.min.x, r.min.y, r.max.x, r.max.y, p.p.x, p.p.y);
+ sync <-= sys->pctl(0, nil);
+ calculate(p, imgch);
+ sync <-= 0;
+}
+
+canvposn(win: ref Tk->Toplevel): Rect
+{
+ return tk->rect(win, ".c", Tk->Local);
+}
+
+calculate(p: Params, imgch: chan of (ref Image, Rect))
+{
+ calc := ref Calc;
+ (calc.img, calc.winr) = <-imgch;
+ r := calc.winr;
+ calc.maxx = r.dx();
+ calc.maxy = r.dy();
+ calc.supx = calc.maxx + 2;
+ calc.supy = calc.maxy + 2;
+ calc.imgch = imgch;
+ calc.xr = array[calc.maxx] of FIX;
+ calc.yr = array[calc.maxy] of FIX;
+ calc.morj = p.m;
+ initr(calc, p);
+ calc.img.drawop(r, calc.img.display.white, nil, (0,0), Draw->S);
+
+ if (p.fill) {
+ calc.dispbase = array[calc.supx*calc.supy] of COL; # auxiliary display and border
+ calc.disp = calc.maxy + 3;
+ setdisp(calc);
+ displayset(calc);
+ } else {
+ for (x := 0; x < calc.maxx; x++) {
+ for (y := 0; y < calc.maxy; y++)
+ point(calc, calc.img, (x, y), pointcolour(calc, x, y));
+ }
+ }
+}
+
+setdisp(calc: ref Calc)
+{
+ d : int;
+ i : int;
+
+ for (i = 0; i < calc.supx*calc.supy; i++)
+ calc.dispbase[i] = byte BLANK;
+
+ i = 0;
+ for (d = 0; i < calc.supx; d += calc.supy) {
+ calc.dispbase[d] = byte BORDER;
+ i++;
+ }
+ i = 0;
+ for (d = 0; i < calc.supy; d++) {
+ calc.dispbase[d] = byte BORDER;
+ i++;
+ }
+ i = 0;
+ for (d = 0+calc.supx*calc.supy-1; i < calc.supx; d -= calc.supy) {
+ calc.dispbase[d] = byte BORDER;
+ i++;
+ }
+ i = 0;
+ for (d = 0+calc.supx*calc.supy-1; i < calc.supy; d--) {
+ calc.dispbase[d] = byte BORDER;
+ i++;
+ }
+}
+
+initr(calc: ref Calc, p: Params): int
+{
+ r := p.r;
+ dp := real2fix((r.max.x-r.min.x)/(real calc.maxx));
+ dq := real2fix((r.max.y-r.min.y)/(real calc.maxy));
+ calc.xr[0] = real2fix(r.min.x)-(big calc.maxx*dp-(real2fix(r.max.x)-real2fix(r.min.x)))/big 2;
+ for (x := 1; x < calc.maxx; x++)
+ calc.xr[x] = calc.xr[x-1] + dp;
+ calc.yr[0] = real2fix(r.max.y)+(big calc.maxy*dq-(real2fix(r.max.y)-real2fix(r.min.y)))/big 2;
+ for (y := 1; y < calc.maxy; y++)
+ calc.yr[y] = calc.yr[y-1] - dq;
+ calc.parx = real2fix(p.p.x);
+ calc.pary = real2fix(p.p.y);
+ calc.kdivisor = p.kdivisor;
+ calc.pointsdone = 0;
+ return dp >= MINDELTA && dq >= MINDELTA;
+}
+
+fillline(calc: ref Calc, x, y, d, dir, dird, col: int)
+{
+ x0 := x;
+
+ while (calc.dispbase[d] == byte BLANK) {
+ calc.dispbase[d] = byte col;
+ x -= dir;
+ d -= dird;
+ }
+ if (0 && pointcolour(calc, (x0+x+dir)/2, y) != col) { # midpoint of line (island code)
+ # island - undo colouring or do properly
+ do {
+ d += dird;
+ x += dir;
+ # *d = BLANK;
+ calc.dispbase[d] = byte pointcolour(calc, x, y);
+ point(calc, calc.img, (x, y), int calc.dispbase[d]);
+ } while (x != x0);
+ return; # abort crawl ?
+ }
+ horizline(calc, calc.img, x0, x, y, col);
+}
+
+crawlt(calc: ref Calc, x, y, d, col: int)
+{
+ yinc, dyinc : int;
+
+ firstd := d;
+ xinc := 1;
+ dxinc := calc.supy;
+
+ for (;;) {
+ if (getcolour(calc, x+xinc, y, d+dxinc) == col) {
+ x += xinc;
+ d += dxinc;
+ yinc = -xinc;
+ dyinc = -dxinc;
+ # if (isblank(x+xinc, y, d+dxinc))
+ if (calc.dispbase[d+dxinc] == byte BLANK)
+ fillline(calc, x+xinc, y, d+dxinc, yinc, dyinc, col);
+ if (d == firstd)
+ break;
+ }
+ else {
+ yinc = xinc;
+ dyinc = dxinc;
+ }
+ if (getcolour(calc, x, y+yinc, d+yinc) == col) {
+ y += yinc;
+ d += yinc;
+ xinc = yinc;
+ dxinc = dyinc;
+ # if (isblank(x-xinc, y, d-dxinc))
+ if (calc.dispbase[d-dxinc] == byte BLANK)
+ fillline(calc, x-xinc, y, d-dxinc, yinc, dyinc, col);
+ if (d == firstd)
+ break;
+ }
+ else {
+ xinc = -yinc;
+ dxinc = -dyinc;
+ }
+ }
+}
+
+# spurious lines problem - disallow all acw paths
+#
+# 43--------->
+# 12--------->
+#
+# 654------------>
+# 7 3------------>
+# 812------------>
+#
+
+# Given a closed curve completely described by unit movements LRUD (left,
+# right, up, and down), calculate the enclosed area. The description
+# may be cw or acw and of arbitrary shape.
+#
+# Based on Green's Theorem :- area = integral ydx
+# C
+# area = 0;
+# count = ARBITRARY_VALUE;
+# while( moves_are_left() ){
+# move = next_move();
+# switch(move){
+# case L:
+# area -= count;
+# break;
+# case R:
+# area += count;
+# break;
+# case U:
+# count++;
+# break;
+# case D:
+# count--;
+# break;
+# }
+# area = abs(area);
+
+crawlf(calc: ref Calc, x, y, d, col: int)
+{
+ xinc, yinc, dxinc, dyinc : int;
+ firstx, firsty : int;
+ firstd : int;
+ area := 0;
+ count := 0;
+
+ firstx = x;
+ firsty = y;
+ firstd = d;
+ xinc = 1;
+ dxinc = calc.supy;
+
+ # acw on success, cw on failure
+ for (;;) {
+ if (getcolour(calc, x+xinc, y, d+dxinc) == col) {
+ x += xinc;
+ d += dxinc;
+ yinc = -xinc;
+ dyinc = -dxinc;
+ area += xinc*count;
+ if (d == firstd)
+ break;
+ } else {
+ yinc = xinc;
+ dyinc = dxinc;
+ }
+ if (getcolour(calc, x, y+yinc, d+yinc) == col) {
+ y += yinc;
+ d += yinc;
+ xinc = yinc;
+ dxinc = dyinc;
+ count -= yinc;
+ if (d == firstd)
+ break;
+ } else {
+ xinc = -yinc;
+ dxinc = -dyinc;
+ }
+ }
+ if (area > 0) # cw
+ crawlt(calc, firstx, firsty, firstd, col);
+}
+
+displayset(calc: ref Calc)
+{
+ edge : int;
+ last := BLANK;
+ d := calc.disp;
+
+ for (x := 0; x < calc.maxx; x++) {
+ for (y := 0; y < calc.maxy; y++) {
+ col := calc.dispbase[d];
+ if (col == byte BLANK) {
+ col = calc.dispbase[d] = byte pointcolour(calc, x, y);
+ point(calc, calc.img, (x, y), int col);
+ if (col == byte last)
+ edge++;
+ else {
+ last = int col;
+ edge = 0;
+ }
+ if (edge >= LIMIT) {
+ crawlf(calc, x, y-edge, d-edge, last);
+ # prevent further crawlf()
+ last = BLANK;
+ }
+ }
+ else {
+ if (col == byte last)
+ edge++;
+ else {
+ last = int col;
+ edge = 0;
+ }
+ }
+ d++;
+ }
+ last = BLANK;
+ d += 2;
+ }
+}
+
+pointcolour(calc: ref Calc, x, y: int) : int
+{
+ if (++calc.pointsdone >= SCHEDCOUNT) {
+ calc.pointsdone = 0;
+ sys->sleep(0);
+ poll(calc);
+ }
+ if (calc.morj)
+ return mcount(calc, x, y) + 1;
+ else
+ return jcount(calc, x, y) + 1;
+}
+
+mcount(calc: ref Calc, x_coord, y_coord: int): int
+{
+ (p, q) := (calc.xr[x_coord], calc.yr[y_coord]);
+ (x, y) := (calc.parx, calc.pary);
+ k := 0;
+ maxcount := MAXCOUNT * calc.kdivisor;
+ while (k < maxcount) {
+ if (x >= TWO || y >= TWO || x <= -TWO || y <= -TWO)
+ break;
+
+ if (0) {
+ # x = (x < 0) ? (x>>HBASE)|NEG : x>>HBASE;
+ # y = (y < 0) ? (y>>HBASE)|NEG : y>>HBASE;
+ }
+
+ x >>= HBASE;
+ y >>= HBASE;
+ t := y*y;
+ y = big 2*x*y+q; # possible unserious overflow when BASE == 28
+ x *= x;
+ if (x+t >= FOUR)
+ break;
+ x -= t-p;
+ k++;
+ }
+ return k / calc.kdivisor;
+}
+
+jcount(calc: ref Calc, x_coord, y_coord: int): int
+{
+ (x, y) := (calc.xr[x_coord], calc.yr[y_coord]);
+ (p, q) := (calc.parx, calc.pary);
+ k := 0;
+ maxcount := MAXCOUNT * calc.kdivisor;
+ while (k < maxcount) {
+ if (x >= TWO || y >= TWO || x <= -TWO || y <= -TWO)
+ break;
+
+ if (0) {
+ # x = (x < 0) ? (x>>HBASE)|NEG : x>>HBASE;
+ # y = (y < 0) ? (y>>HBASE)|NEG : y>>HBASE;
+ }
+
+ x >>= HBASE;
+ y >>= HBASE;
+ t := y*y;
+ y = big 2*x*y+q; # possible unserious overflow when BASE == 28
+ x *= x;
+ if (x+t >= FOUR)
+ break;
+ x -= t-p;
+ k++;
+ }
+ return k / calc.kdivisor;
+}
+
+getcolour(calc: ref Calc, x, y, d: int): int
+{
+ if (calc.dispbase[d] == byte BLANK) {
+ calc.dispbase[d] = byte pointcolour(calc, x, y);
+ point(calc, calc.img, (x, y), int calc.dispbase[d]);
+ }
+ return int calc.dispbase[d];
+}
+
+point(calc: ref Calc, d: ref Image, p: Point, col: int)
+{
+ d.draw(Rect(p, p.add((1,1))).addpt(calc.winr.min), colours[col], nil, (0,0));
+}
+
+horizline(calc: ref Calc, d: ref Image, x0, x1, y: int, col: int)
+{
+ if (x0 < x1)
+ r := Rect((x0, y), (x1, y+1));
+ else
+ r = Rect((x1+1, y), (x0+1, y+1));
+ d.draw(r.addpt(calc.winr.min), colours[col], nil, (0, 0));
+ # r := Rect((x0, y), (x1, y)).canon();
+ # r.max = r.max.add((1, 1));
+}
+
+real2fix(x: real): FIX
+{
+ return big (x * real SCALE);
+}
+
+cmd(top: ref Tk->Toplevel, s: string): string
+{
+ e := tk->cmd(top, s);
+ if (e != nil && e[0] == '!')
+ sys->fprint(stderr, "mand: tk error on '%s': %s\n", s, e);
+ return e;
+}
+
+kill(pid: int): int
+{
+ fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE);
+ if (fd == nil)
+ return -1;
+ if (sys->write(fd, array of byte "kill", 4) != 4)
+ return -1;
+ return 0;
+}
+
+col(i, r : int) : int
+{
+ if (r == 1)
+ return 0;
+ return (255*(i%r))/(r-1);
+}
+
+fittoscreen(win: ref Tk->Toplevel)
+{
+ Point: import draw;
+ if (win.image == nil || win.image.screen == nil)
+ return;
+ r := win.image.screen.image.r;
+ scrsize := Point((r.max.x - r.min.x), (r.max.y - r.min.y));
+ bd := int cmd(win, ". cget -bd");
+ winsize := Point(int cmd(win, ". cget -actwidth") + bd * 2, int cmd(win, ". cget -actheight") + bd * 2);
+ if (winsize.x > scrsize.x)
+ cmd(win, ". configure -width " + string (scrsize.x - bd * 2));
+ if (winsize.y > scrsize.y)
+ cmd(win, ". configure -height " + string (scrsize.y - bd * 2));
+ actr: Rect;
+ actr.min = Point(int cmd(win, ". cget -actx"), int cmd(win, ". cget -acty"));
+ actr.max = actr.min.add((int cmd(win, ". cget -actwidth") + bd*2,
+ int cmd(win, ". cget -actheight") + bd*2));
+ (dx, dy) := (actr.dx(), actr.dy());
+ if (actr.max.x > r.max.x)
+ (actr.min.x, actr.max.x) = (r.max.x - dx, r.max.x);
+ if (actr.max.y > r.max.y)
+ (actr.min.y, actr.max.y) = (r.max.y - dy, r.max.y);
+ if (actr.min.x < r.min.x)
+ (actr.min.x, actr.max.x) = (r.min.x, r.min.x + dx);
+ if (actr.min.y < r.min.y)
+ (actr.min.y, actr.max.y) = (r.min.y, r.min.y + dy);
+ cmd(win, ". configure -x " + string actr.min.x + " -y " + string actr.min.y);
+}
diff --git a/appl/wm/mash.b b/appl/wm/mash.b
new file mode 100644
index 00000000..f83b347e
--- /dev/null
+++ b/appl/wm/mash.b
@@ -0,0 +1,577 @@
+implement WmMash;
+
+include "sys.m";
+ sys: Sys;
+ FileIO: import sys;
+
+include "draw.m";
+ draw: Draw;
+ Context: import draw;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "plumbmsg.m";
+ plumbmsg: Plumbmsg;
+ Msg: import plumbmsg;
+
+include "workdir.m";
+ workdir: Workdir;
+
+WmMash: module
+{
+ init: fn(ctxt: ref Draw->Context, args: list of string);
+};
+
+Command: module
+{
+ tkinit: fn(ctxt: ref Draw->Context, t: ref Tk->Toplevel, args: list of string);
+};
+
+BS: con 8; # ^h backspace character
+BSW: con 23; # ^w bacspace word
+BSL: con 21; # ^u backspace line
+EOT: con 4; # ^d end of file
+ESC: con 27; # hold mode
+
+HIWAT: con 2000; # maximum number of lines in transcript
+LOWAT: con 1500; # amount to reduce to after high water
+
+Name: con "Mash";
+
+Rdreq: adt
+{
+ off: int;
+ nbytes: int;
+ fid: int;
+ rc: chan of (array of byte, string);
+};
+
+shwin_cfg := array[] of {
+ "menu .m",
+ ".m add command -text Cut -command {send edit cut}",
+ ".m add command -text Paste -command {send edit paste}",
+ ".m add command -text Snarf -command {send edit snarf}",
+ ".m add command -text Send -command {send edit send}",
+ "frame .b -bd 1 -relief ridge",
+ "frame .ft -bd 0",
+ "scrollbar .ft.scroll -width 14 -bd 0 -relief ridge -command {.ft.t yview}",
+ "text .ft.t -bd 1 -relief flat -width 520 -height 7c -yscrollcommand {.ft.scroll set}",
+ "pack .ft.scroll -side left -fill y",
+ "pack .ft.t -fill both -expand 1",
+ "pack .Wm_t -fill x",
+ "pack .b -anchor w -fill x",
+ "pack .ft -fill both -expand 1",
+ "pack propagate . 0",
+ "focus .ft.t",
+ "bind .ft.t <Key> {send keys {%A}}",
+ "bind .ft.t <Control-d> {send keys {%A}}",
+ "bind .ft.t <Control-h> {send keys {%A}}",
+ "bind .ft.t <Button-1> +{grab set .ft.t; send but1 pressed}",
+ "bind .ft.t <Double-Button-1> +{grab set .ft.t; send but1 pressed}",
+ "bind .ft.t <ButtonRelease-1> +{grab release .ft.t; send but1 released}",
+ "bind .ft.t <ButtonPress-2> {send but2 %X %Y}",
+ "bind .ft.t <Motion-Button-2-Button-1> {}",
+ "bind .ft.t <Motion-ButtonPress-2> {}",
+ "bind .ft.t <ButtonPress-3> {send but3 pressed}",
+ "bind .ft.t <ButtonRelease-3> {send but3 released %x %y}",
+ "bind .ft.t <Motion-Button-3> {}",
+ "bind .ft.t <Motion-Button-3-Button-1> {}",
+ "bind .ft.t <Double-Button-3> {}",
+ "bind .ft.t <Double-ButtonRelease-3> {}",
+ "update"
+};
+
+rdreq: list of Rdreq;
+menuindex := "0";
+holding := 0;
+plumbed := 0;
+rawon := 0;
+rawinput := "";
+
+init(ctxt: ref Context, argv: list of string)
+{
+ s: string;
+
+ sys = load Sys Sys->PATH;
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "mash: no window context\n");
+ raise "fail:bad context";
+ }
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ plumbmsg = load Plumbmsg Plumbmsg->PATH;
+
+ sys->pctl(Sys->FORKNS | Sys->NEWPGRP, nil);
+
+ tkclient->init();
+
+ if(plumbmsg->init(1, nil, 0) >= 0){
+ plumbed = 1;
+ workdir = load Workdir Workdir->PATH;
+ }
+
+ argv = tl argv; # strip off command name
+ (t, titlectl) := tkclient->toplevel(ctxt, "", Name, Tkclient->Appl);
+
+ edit := chan of string;
+ tk->namechan(t, edit, "edit");
+# mash := chan of string;
+# tk->namechan(t, mash, "mash");
+
+ tkcmds(t, shwin_cfg);
+
+ tkclient->onscreen(t, nil);
+ tkclient->startinput(t, "kbd"::"ptr"::nil);
+
+ ioc := chan of (int, ref FileIO, ref FileIO, string);
+ spawn newsh(ctxt, t, ioc, argv);
+
+ (pid, file, filectl, consfile) := <-ioc;
+ if(file == nil || filectl == nil) {
+ sys->print("newsh: %r\n");
+ return;
+ }
+
+ keys := chan of string;
+ tk->namechan(t, keys, "keys");
+
+ but1 := chan of string;
+ tk->namechan(t, but1, "but1");
+ but2 := chan of string;
+ tk->namechan(t, but2, "but2");
+ but3 := chan of string;
+ tk->namechan(t, but3, "but3");
+ button1 := 0;
+ button3 := 0;
+
+ rdrpc: Rdreq;
+
+ # outpoint is place in text to insert characters printed by programs
+ tk->cmd(t, ".ft.t mark set outpoint end; .ft.t mark gravity outpoint left");
+
+ for(;;) alt {
+ c := <-t.ctxt.kbd =>
+ tk->keyboard(t, c);
+ c := <-t.ctxt.ptr =>
+ tk->pointer(t, *c);
+ c := <-t.ctxt.ctl or
+ c = <-t.wreq =>
+ tkclient->wmctl(t, c);
+ menu := <-titlectl =>
+ if(menu == "exit") {
+ kill(pid);
+ return;
+ }
+ tkclient->wmctl(t, menu);
+ tk->cmd(t, "focus .ft.t");
+
+ ecmd := <-edit =>
+ editor(t, ecmd);
+ sendinput(t);
+ tk->cmd(t, "focus .ft.t");
+
+ c := <-keys =>
+ cut(t, 1);
+ if(rawon) {
+ rawinput += c[1:2];
+ rawinput = sendraw(rawinput);
+ break;
+ }
+ char := c[1];
+ if(char == '\\')
+ char = c[2];
+ update := ";.ft.t see insert;update";
+ case char {
+ * =>
+ tk->cmd(t, ".ft.t insert insert "+c+update);
+ '\n' or EOT =>
+ tk->cmd(t, ".ft.t insert insert "+c+update);
+ sendinput(t);
+ BS =>
+ tk->cmd(t, ".ft.t tkTextDelIns -c"+update);
+ BSL =>
+ tk->cmd(t, ".ft.t tkTextDelIns -l"+update);
+ BSW =>
+ tk->cmd(t, ".ft.t tkTextDelIns -w"+update);
+ ESC =>
+ holding ^= 1;
+ color := "blue";
+ if(!holding){
+ color = "black";
+ tkclient->settitle(t, Name);
+ sendinput(t);
+ }else
+ tkclient->settitle(t, Name+" (holding)");
+ tk->cmd(t, ".ft.t configure -foreground "+color+update);
+ }
+
+ c := <-but1 =>
+ button1 = (c == "pressed");
+ button3 = 0; # abort any pending button 3 action
+
+ c := <-but2 =>
+ if(button1){
+ cut(t, 1);
+ tk->cmd(t, "update");
+ break;
+ }
+ (nil, l) := sys->tokenize(c, " ");
+ x := int hd l - 50;
+ y := int hd tl l - int tk->cmd(t, ".m yposition "+menuindex) - 10;
+ tk->cmd(t, ".m activate "+menuindex+"; .m post "+string x+" "+string y+
+ "; grab set .m; update");
+ button3 = 0; # abort any pending button 3 action
+
+ c := <-but3 =>
+ if(c == "pressed"){
+ button3 = 1;
+ if(button1){
+ paste(t);
+ tk->cmd(t, "update");
+ }
+ break;
+ }
+ if(plumbed == 0 || button3 == 0 || button1 != 0)
+ break;
+ button3 = 0;
+ # Plumb message triggered by release of button 3
+ (nil, l) := sys->tokenize(c, " ");
+ x := int hd tl l;
+ y := int hd tl tl l;
+ index := tk->cmd(t, ".ft.t index @"+string x+","+string y);
+ selindex := tk->cmd(t, ".ft.t tag ranges sel");
+ if(selindex != "")
+ insel := tk->cmd(t, ".ft.t compare sel.first <= "+index)=="1" &&
+ tk->cmd(t, ".ft.t compare sel.last >= "+index)=="1";
+ else
+ insel = 0;
+ attr := "";
+ if(insel)
+ text := tk->cmd(t, ".ft.t get sel.first sel.last");
+ else{
+ # have line with text in it
+ # now extract whitespace-bounded string around click
+ (nil, w) := sys->tokenize(index, ".");
+ charno := int hd tl w;
+ left := tk->cmd(t, ".ft.t index {"+index+" linestart}");
+ right := tk->cmd(t, ".ft.t index {"+index+" lineend}");
+ line := tk->cmd(t, ".ft.t get "+left+" "+right);
+ for(i:=charno; i>0; --i)
+ if(line[i-1]==' ' || line[i-1]=='\t')
+ break;
+ for(j:=charno; j<len line; j++)
+ if(line[j]==' ' || line[j]=='\t')
+ break;
+ text = line[i:j];
+ attr = "click="+string (charno-i);
+ }
+ msg := ref Msg(
+ "WmSh",
+ "",
+ workdir->init(),
+ "text",
+ attr,
+ array of byte text);
+ if(msg.send() < 0)
+ sys->fprint(sys->fildes(2), "sh: plumbing write error: %r\n");
+
+ rdrpc = <-filectl.read =>
+ if(rdrpc.rc == nil)
+ continue;
+ rdrpc.rc <-= ( nil, "not allowed" );
+
+ (nil, data, nil, wc) := <-filectl.write =>
+ if(wc == nil) {
+ # consctl closed - revert to cooked mode
+ rawon = 0;
+ continue;
+ }
+ (nc, cmdlst) := sys->tokenize(string data, " \n");
+ if(nc == 1) {
+ case hd cmdlst {
+ "rawon" =>
+ rawon = 1;
+ rawinput = "";
+ # discard previous input
+ advance := string (len tk->cmd(t, ".ft.t get outpoint end") +1);
+ tk->cmd(t, ".ft.t mark set outpoint outpoint+" + advance + "chars");
+ "rawoff" =>
+ rawon = 0;
+ * =>
+ wc <-= (0, "unknown consctl request");
+ continue;
+ }
+ wc <-= (len data, nil);
+ continue;
+ }
+ wc <-= (0, "unknown consctl request");
+
+ rdrpc = <-file.read =>
+ if(rdrpc.rc == nil) {
+ (ok, nil) := sys->stat(consfile);
+ if (ok < 0)
+ return;
+ continue;
+ }
+ append(rdrpc);
+ sendinput(t);
+
+ (off, data, fid, wc) := <-file.write =>
+ if(wc == nil) {
+ (ok, nil) := sys->stat(consfile);
+ if (ok < 0)
+ return;
+ continue;
+ }
+ cdata := stripbs(t, string data);
+ ncdata := string len cdata + "chars;";
+ moveins := insat(t, "outpoint");
+ tk->cmd(t, ".ft.t insert outpoint '"+ cdata);
+ wc <-= (len data, nil);
+ data = nil;
+ s = ".ft.t mark set outpoint outpoint+" + ncdata;
+ s += ".ft.t see outpoint;";
+ if(moveins)
+ s += ".ft.t mark set insert insert+" + ncdata;
+ s += "update";
+ tk->cmd(t, s);
+ nlines := int tk->cmd(t, ".ft.t index end");
+ if(nlines > HIWAT){
+ s = ".ft.t delete 1.0 "+ string (nlines-LOWAT) +".0;update";
+ tk->cmd(t, s);
+ }
+ }
+}
+
+RPCread: type (int, int, int, chan of (array of byte, string));
+
+append(r: RPCread)
+{
+ t := r :: nil;
+ while(rdreq != nil) {
+ t = hd rdreq :: t;
+ rdreq = tl rdreq;
+ }
+ rdreq = t;
+}
+
+insat(t: ref Tk->Toplevel, mark: string): int
+{
+ return tk->cmd(t, ".ft.t compare insert == "+mark) == "1";
+}
+
+insininput(t: ref Tk->Toplevel): int
+{
+ if(tk->cmd(t, ".ft.t compare insert >= outpoint") != "1")
+ return 0;
+ return tk->cmd(t, ".ft.t compare {insert linestart} == {outpoint linestart}") == "1";
+}
+
+isalnum(s: string): int
+{
+ if(s == "")
+ return 0;
+ c := s[0];
+ if('a' <= c && c <= 'z')
+ return 1;
+ if('A' <= c && c <= 'Z')
+ return 1;
+ if('0' <= c && c <= '9')
+ return 1;
+ if(c == '_')
+ return 1;
+ if(c > 16rA0)
+ return 1;
+ return 0;
+}
+
+stripbs(t: ref Tk->Toplevel, s: string): string
+{
+ l := len s;
+ for(i := 0; i < l; i++)
+ if(s[i] == '\b') {
+ pre := "";
+ rem := "";
+ if(i + 1 < l)
+ rem = s[i+1:];
+ if(i == 0) { # erase existing character in line
+ if(tk->cmd(t, ".ft.t get " +
+ "{outpoint linestart} outpoint") != "")
+ tk->cmd(t, ".ft.t delete outpoint-1char");
+ } else {
+ if(s[i-1] != '\n') # don't erase newlines
+ i--;
+ if(i)
+ pre = s[:i];
+ }
+ s = pre + rem;
+ l = len s;
+ i = len pre - 1;
+ }
+ return s;
+}
+
+editor(t: ref Tk->Toplevel, ecmd: string)
+{
+ s, snarf: string;
+
+ case ecmd {
+ "cut" =>
+ menuindex = "0";
+ cut(t, 1);
+
+ "paste" =>
+ menuindex = "1";
+ paste(t);
+
+ "snarf" =>
+ menuindex = "2";
+ if(tk->cmd(t, ".ft.t tag ranges sel") == "")
+ break;
+ snarf = tk->cmd(t, ".ft.t get sel.first sel.last");
+ tkclient->snarfput(snarf);
+
+ "send" =>
+ menuindex = "3";
+ if(tk->cmd(t, ".ft.t tag ranges sel") != ""){
+ snarf = tk->cmd(t, ".ft.t get sel.first sel.last");
+ tkclient->snarfput(snarf);
+ }else
+ snarf = tkclient->snarfget();
+ if(snarf != "")
+ s = snarf;
+ else
+ return;
+ if(s[len s-1] != '\n' && s[len s-1] != EOT)
+ s[len s] = '\n';
+ tk->cmd(t, ".ft.t see end; .ft.t insert end '"+s);
+ tk->cmd(t, ".ft.t mark set insert end");
+ tk->cmd(t, ".ft.t tag remove sel sel.first sel.last");
+ }
+ tk->cmd(t, "update");
+}
+
+cut(t: ref Tk->Toplevel, snarfit: int)
+{
+ if(tk->cmd(t, ".ft.t tag ranges sel") == "")
+ return;
+ if(snarfit)
+ tkclient->snarfput(tk->cmd(t, ".ft.t get sel.first sel.last"));
+ tk->cmd(t, ".ft.t delete sel.first sel.last");
+}
+
+paste(t: ref Tk->Toplevel)
+{
+ snarf := tkclient->snarfget();
+ if(snarf == "")
+ return;
+ cut(t, 0);
+ tk->cmd(t, ".ft.t insert insert '"+snarf);
+ tk->cmd(t, ".ft.t tag add sel insert-"+string len snarf+"chars insert");
+ sendinput(t);
+}
+
+sendinput(t: ref Tk->Toplevel)
+{
+ if(holding)
+ return;
+ input := tk->cmd(t, ".ft.t get outpoint end");
+ slen := len input;
+ if(slen == 0 || rdreq == nil)
+ return;
+
+ r := hd rdreq;
+ for(i := 0; i < slen; i++)
+ if(input[i] == '\n' || input[i] == EOT)
+ break;
+
+ if(i >= slen && slen < r.nbytes)
+ return;
+
+ if(i >= r.nbytes)
+ i = r.nbytes-1;
+ advance := string (i+1);
+ if(input[i] == EOT)
+ input = input[0:i];
+ else
+ input = input[0:i+1];
+
+ rdreq = tl rdreq;
+
+ alt {
+ r.rc <-= (array of byte input, "") =>
+ tk->cmd(t, ".ft.t mark set outpoint outpoint+" + advance + "chars");
+ * =>
+ # requester has disappeared; ignore his request and try again
+ sendinput(t);
+ }
+}
+
+sendraw(input : string) : string
+{
+ i := len input;
+ if(i == 0 || rdreq == nil)
+ return input;
+
+ r := hd rdreq;
+ rdreq = tl rdreq;
+
+ if(i > r.nbytes)
+ i = r.nbytes;
+
+ alt {
+ r.rc <-= (array of byte input[0:i], "") =>
+ input = input[i:];
+ * =>
+ ;# requester has disappeared; ignore his request and try again
+ }
+ return input;
+}
+
+newsh(ctxt: ref Context, t: ref Tk->Toplevel, ioc: chan of (int, ref FileIO, ref FileIO, string), args: list of string)
+{
+ pid := sys->pctl(sys->NEWFD, nil);
+
+ sh := load Command "/dis/mash.dis";
+ if(sh == nil) {
+ ioc <-= (0, nil, nil, nil);
+ return;
+ }
+
+ tty := "cons."+string pid;
+
+ sys->bind("#s","/chan",sys->MBEFORE);
+ fio := sys->file2chan("/chan", tty);
+ fioctl := sys->file2chan("/chan", tty + "ctl");
+
+ ioc <-= (pid, fio, fioctl, "/chan/"+tty);
+ if(fio == nil || fioctl == nil)
+ return;
+
+ sys->bind("/chan/"+tty, "/dev/cons", sys->MREPL);
+ sys->bind("/chan/"+tty+"ctl", "/dev/consctl", sys->MREPL);
+
+ fd0 := sys->open("/dev/cons", sys->OREAD|sys->ORCLOSE);
+ fd1 := sys->open("/dev/cons", sys->OWRITE);
+ fd2 := sys->open("/dev/cons", sys->OWRITE);
+
+ sh->tkinit(ctxt, t, "mash" :: args);
+}
+
+kill(pid: int)
+{
+ fd := sys->open("#p/"+string pid+"/ctl", sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "killgrp");
+}
+
+tkcmds(t: ref Tk->Toplevel, cfg: array of string)
+{
+ for(i := 0; i < len cfg; i++)
+ tk->cmd(t, cfg[i]);
+}
diff --git a/appl/wm/memory.b b/appl/wm/memory.b
new file mode 100644
index 00000000..6bbfa68b
--- /dev/null
+++ b/appl/wm/memory.b
@@ -0,0 +1,246 @@
+implement WmMemory;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Display, Image, Rect: import draw;
+
+include "tk.m";
+ tk: Tk;
+ t: ref Tk->Toplevel;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+WmMemory: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Arena: adt
+{
+ name: string;
+ limit: int;
+ size: int;
+ hw: int;
+ allocs: int;
+ frees: int;
+ exts: int;
+ chunk: int;
+ y: int;
+ tag: string;
+ tagsz: string;
+ taghw: string;
+ tagiu: string;
+};
+a := array[10] of Arena;
+
+mem_cfg := array[] of {
+ "canvas .c -width 240 -height 45",
+ "pack .c",
+ "update",
+};
+
+init(ctxt: ref Draw->Context, nil: list of string)
+{
+ spawn realinit(ctxt);
+}
+
+realinit(ctxt: ref Draw->Context)
+{
+ sys = load Sys Sys->PATH;
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "memory: no window context\n");
+ raise "fail:bad context";
+ }
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ sys->pctl(Sys->NEWPGRP, nil);
+ tkclient->init();
+
+ menubut := chan of string;
+ (t, menubut) = tkclient->toplevel(ctxt, "", "Memory", 0);
+ for(j := 0; j < len mem_cfg; j++)
+ cmd(t, mem_cfg[j]);
+ tkclient->startinput(t, "ptr"::nil);
+ tkclient->onscreen(t, nil);
+
+ tick := chan of int;
+ spawn ticker(tick);
+
+ mfd := sys->open("/dev/memory", sys->OREAD);
+
+ n := getmem(mfd);
+ maxx := initdraw(n);
+
+ pid: int;
+ for(;;) alt {
+ s := <-t.ctxt.ptr =>
+ tk->pointer(t, *s);
+ s := <-t.ctxt.ctl or
+ s = <-t.wreq or
+ s = <-menubut =>
+ if(s == "exit"){
+ kill(pid);
+ return;
+ }
+ tkclient->wmctl(t, s);
+ pid = <-tick =>
+ update(mfd);
+ for(i := 0; i < n; i++) {
+ if(a[i].limit <= 0)
+ continue;
+ x := int ((big a[i].size * big (230-maxx)) / big a[i].limit);
+ s := sys->sprint(".c coords %s %d %d %d %d",
+ a[i].tag,
+ maxx,
+ a[i].y + 4,
+ maxx + x,
+ a[i].y + 8);
+ cmd(t, s);
+ x = int ((big a[i].hw * big (230-maxx)) / big a[i].limit);
+ s = sys->sprint(".c coords %s %d %d %d %d",
+ a[i].taghw,
+ maxx,
+ a[i].y + 4,
+ maxx+x,
+ a[i].y + 8);
+ cmd(t, s);
+ s = sys->sprint(".c itemconfigure %s -text '%s", a[i].tagsz, string a[i].size);
+ cmd(t, s);
+ s = sys->sprint(".c itemconfigure %s -text '%d", a[i].tagiu, a[i].allocs-a[i].frees);
+ cmd(t, s);
+ }
+ cmd(t, "update");
+ }
+}
+
+ticker(c: chan of int)
+{
+ pid := sys->pctl(0, nil);
+ for(;;) {
+ c <-= pid;
+ sys->sleep(1000);
+ }
+}
+
+initdraw(n: int): int
+{
+ y := 15;
+ maxx := 0;
+ for (i := 0; i < n; i++) {
+ id := cmd(t, ".c create text 5 "+string y+" -anchor w -text "+a[i].name);
+ r := s2r(cmd(t, ".c bbox " + id));
+ if (r.max.x > maxx)
+ maxx = r.max.x;
+ y += 20;
+ }
+ maxx += 5;
+ y = 15;
+ for(i = 0; i < n; i++) {
+ s := sys->sprint(".c create rectangle %d %d 230 %d -fill white", maxx, y+4, y+8);
+ cmd(t, s);
+ s = sys->sprint(".c create rectangle %d %d 230 %d -fill white", maxx, y+4, y+8);
+ a[i].taghw = cmd(t, s);
+ s = sys->sprint(".c create rectangle %d %d 230 %d -fill red", maxx, y+4, y+8);
+ a[i].tag = cmd(t, s);
+ s = sys->sprint(".c create text 230 %d -anchor e -text '%s", y - 2, sizestr(a[i].limit));
+ cmd(t, s);
+ s = sys->sprint(".c create text %d %d -anchor w -text '%s", maxx, y - 2, string a[i].size);
+ a[i].tagsz = cmd(t, s);
+ s = sys->sprint(".c create text 120 %d -fill red -anchor w -text '%d", y - 2, a[i].allocs-a[i].frees);
+ a[i].tagiu = cmd(t, s);
+ a[i].y = y;
+ y += 20;
+ }
+ cmd(t, ".c configure -height "+string y);
+ cmd(t, "update");
+ return maxx;
+}
+
+sizestr(n: int): string
+{
+ if ((n / 1024) % 1024 == 0)
+ return string (n / (1024 * 1024)) + "M";
+ return string (n / 1024) + "K";
+}
+
+buf := array[8192] of byte;
+
+update(mfd: ref Sys->FD): int
+{
+ sys->seek(mfd, big 0, Sys->SEEKSTART);
+ n := sys->read(mfd, buf, len buf);
+ if(n <= 0)
+ exit;
+ (nil, l) := sys->tokenize(string buf[0:n], "\n");
+ i := 0;
+ while(l != nil) {
+ s := hd l;
+ a[i].size = int s[0:];
+ a[i].hw = int s[24:];
+ a[i].allocs = int s[3*12:];
+ a[i].frees = int s[4*12:];
+ a[i].exts = int s[5*12:];
+ a[i++].chunk = int s[6*12:];
+ l = tl l;
+ }
+ return i;
+}
+
+getmem(mfd: ref Sys->FD): int
+{
+ n := sys->read(mfd, buf, len buf);
+ if(n <= 0)
+ exit;
+ (nil, l) := sys->tokenize(string buf[0:n], "\n");
+ i := 0;
+ while(l != nil) {
+ s := hd l;
+ a[i].size = int s[0:];
+ a[i].limit = int s[12:];
+ a[i].hw = int s[2*12:];
+ a[i].allocs = int s[3*12:];
+ a[i].frees = int s[4*12:];
+ a[i].exts = int s[5*12:];
+ a[i].chunk = int s[6*12:];
+ a[i].name = s[7*12:];
+ i++;
+ l = tl l;
+ }
+ return i;
+}
+
+s2r(s: string): Rect
+{
+ (n, toks) := sys->tokenize(s, " ");
+ if (n != 4) {
+ sys->print("'%s' is not a rectangle!\n", s);
+ raise "bad conversion";
+ }
+ r: Rect;
+ (r.min.x, toks) = (int hd toks, tl toks);
+ (r.min.y, toks) = (int hd toks, tl toks);
+ (r.max.x, toks) = (int hd toks, tl toks);
+ (r.max.y, toks) = (int hd toks, tl toks);
+ return r;
+}
+
+kill(pid: int)
+{
+ fd := sys->open("#p/"+string pid+"/ctl", sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "kill");
+}
+
+
+cmd(top: ref Tk->Toplevel, s: string): string
+{
+ e := tk->cmd(top, s);
+ if (e != nil && e[0] == '!')
+ sys->print("memory: tk error on '%s': %s\n", s, e);
+ return e;
+}
diff --git a/appl/wm/minitel/README b/appl/wm/minitel/README
new file mode 100644
index 00000000..82f3202e
--- /dev/null
+++ b/appl/wm/minitel/README
@@ -0,0 +1,209 @@
+Minitel Emulation for Inferno
+
+This directory contains the source of `miniterm', a minitel emulator
+for Inferno. Miniterm is written in Limbo. The main components are:
+
+ miniterm.m - common constants
+ miniterm.b - terminal emulator, messaging and Minitel `protocol`
+ event.[mb] - inter-module message format
+ keyb.b - Minitel keyboard module
+ modem.b - Minitel modem module
+ screen.b - Minitel screen module
+ socket.b - Minitel socket module
+ arg.m - basic command line argument handling
+ mdisplay.[mb] - Videotex display module
+ swkeyb.[mb] - Minitel aware software keyboard
+
+ fonts.tgz which expands into:
+
+ fonts/minitel - external and subfont directory (`bind -b' into /fonts)
+ fonts/minitel/f40x25 - 40 column external font
+ fonts/minitel/14x17
+ fonts/minitel/14x17xoe
+ fonts/minitel/14x17arrow
+ fonts/minitel/f40x25g1 - 40 column semigraphic external font
+ fonts/minitel/vid14x17
+ fonts/minitel/f40x25h - 40 column double height external font
+ fonts/minitel/14x34
+ fonts/minitel/14x34xoe
+ fonts/minitel/14x34arrow
+ fonts/minitel/f40x25w - 40 column double width external font
+ fonts/minitel/28x17
+ fonts/minitel/28x17xoe
+ fonts/minitel/28x17arrow
+ fonts/minitel/f40x25s - 40 column double size external font
+ fonts/minitel/28x34xoe
+ fonts/minitel/28x34arrow
+ fonts/minitel/f80x25 - 80 column external font
+ fonts/minitel/8x12
+ fonts/minitel/8x12xoe
+ fonts/minitel/8x12arrow
+
+The fonts subdirectory should be bound into /fonts:
+ bind -b fonts /fonts
+or the directory fonts/minitel copied to /fonts/minitel before invoking the emulator.
+The names of the external fonts are
+known to the Videotex display module. Similarly, the files:
+ /dev/modem
+ /dev/modemctl
+are known to the modem module, but you can ignore them if
+(as is almost certain) you are using the Internet-minitel gateway
+and you haven't got appropriate modem hardware anyway.
+
+To build
+ mkdir /usr/inferno/dis/wm/minitel
+ mk install
+
+The code models the structure outlined in the Minitel 1B specification
+provided by France Telecom. However, much more interpretation was
+required to display the majority of screens currently seen on Minitel.
+Additional information (although sketchy) was found on the Internet by
+searching for Minitel or Videotex and also by examination of the codes
+sent by minitel servers and experimenting with replies. There must be
+some more up to date information somewhere!
+
+We don't support downloadable fonts, but correctly filter them out.
+
+The file miniterm.b contains the code for the minitel `terminal' with
+which the other modules communicate. The keyboard, modem, socket,
+screen and terminal are run as separate threads which communicate by
+calling:
+ send(e: ref Event)
+The clue to the intermodule communication is in Terminal.run which
+does something like:
+ for(;;) {
+ ev =<- t.in =>
+ eva := protocol(ev);
+ while(len eva > 0) {
+ post(eva[0]);
+ eva = eva[1:];
+ }
+ # then deliver any `posted' messages (without blocking)
+ }
+An Event `ev' may typically be an Edata type (say from the modem) or
+an Eproto type for internal interpretation. In the call:
+ eva := protocol(ev)
+The function protocol() dissects Edata messages to produce an inline
+sequence of Edata and Eproto messages. The function post() queues
+messages for delivery to the appropriate modules. For example, data
+from the modem might be destined for the screen and the socket module.
+Messages are queued until they can be delivered. That way the line:
+ ev =<- t.in
+is executed in a timely way and the other modules can be written to
+make blocking writes (via send()) and to service reads when they are
+ready.
+
+In many places in the code lines appear with comments like:
+ if(p.skip < 1 || p.skip > 127) # 5.0
+These refer to sections of the Minitel specification which explain the
+code.
+
+The mdisplay code provides a Videotex display using Inferno
+primitives. The screen, keyboard and modem modules interpret data as
+described in the equivalent section of the Minitel specification. The
+socket module has not been implemented but currently performs a `null'
+function and could easily be added if required.
+
+
+- Namespace
+We always expect the fonts to appear in /fonts and the softmodem
+to appear as /dev/modem and /dev/modemctl.
+
+- Invocation
+If invoked with no argument, miniterm uses the France Telecom
+internet gateway by default (tcp!193.252.252.250!513).
+If the argument starts with `modem' then
+a direct connection through /dev/modem will be established.
+
+An argument beginning with anything other than `modem' will
+be assumed to be an address suitable for dial(). For example:
+
+ wm/minitel/miniterm tcp!193.252.252.250!513
+
+will connect to the current France Telecom internet server.
+
+For direct connections a modem `init' string and an optional
+phone number can follow the modem prefix, as in:
+
+ wm/minitel/miniterm modem!F3!3615
+
+or
+
+ wm/minitel/miniterm modem!F3!01133836431414
+
+The `F3' is the code which instructs the softmodem to enable V.23
+and needs to be passed when connecting to the FT servers.
+To use pulse dialing instead of tone dialing the phone number
+can be prefixed with a 'P' as in:
+
+ wm/minitel/miniterm modem!F3!P3614
+
+If the parameter specifies a network connection or a direct connection
+with a phone number the software will attempt to connect immediately.
+If Cx/Fin is used to disconnect and then re-connect it will use the
+same IP address for a network connection or prompt for a new
+phone number in the case of a direct connection. When prompting
+for a new number the top row of the screen is used to allow the user
+to edit the last used number. Simple editing is available, and the minitel
+keys do the obvious things.
+
+
+
+** Notes on the 15th December 1998 Release **
+
+- Software keyboard
+A version of the software keyboard which understands some of
+the minitel keyboard mappings is included. For example, hitting 'A' results
+in a capital 'A' on the screen in spite of the Videotex case mapping.
+
+- Minitel function keys
+The minitel keys are displayed on the right hand side of the screen
+in 40 column mode on a network connection
+and can be swapped to the left hand side by hitting the <- key.
+In direct dial mode and 80 column network mode the keys are
+displayed at the bottom of the screen.
+In network mode they are re-displayed as appropriate on 40 to 80
+column mode changes.
+
+
+Known Omission
+-------------
+- Error Correction (direct dial only)
+There is no screen button to enable error correction in the release.
+If a server asks for error correction it will be enabled. It looks as though
+we need to include a key to enable it. Without it direct dial screens are
+occasionally corrupted.
+
+- Software Keyboard Handling
+We need to add some code to update the software keyboard and
+bring it to the foreground on a mode change.
+
+- Full 80 column support
+I am aware of some screens which don't look correct in 80 column
+mode (and others that do). See `EMAIL' then choose USENET and
+press SUITE a few times. I believe it behaves as specified but as we
+have seen with the 40 column Videotex mode the specification
+is not sufficient to display most of the minitel screens correctly.
+80 column support needs just a little more work.
+It may be, too, that the 80 column font could be made much more
+readable by utilising a few more pixels on the screen now that we
+are able to cover the toolbar.
+
+- Full toolbar integration
+Experimentation will show whether there needs to be more
+integration with the toolbar.
+
+Known Bugs
+----------
+- Softmodem disconnection
+Often, the modem does not hangup correctly.
+
+- Choose `USA' from a network connection
+USA (from a network connection) gives an `iC' in bottom left hand
+corner of screen. Possibly a server issue. Doesn't occur when
+connecting directly. The server is really sending this sequence.
+Both the FT emulator and their explorer plug-in suffer from it too.
+
+
+John Bates
+Vita Nuova Limited
diff --git a/appl/wm/minitel/event.b b/appl/wm/minitel/event.b
new file mode 100644
index 00000000..f751f55b
--- /dev/null
+++ b/appl/wm/minitel/event.b
@@ -0,0 +1,19 @@
+#
+# Copyright © 1998 Vita Nuova Limited. All rights reserved.
+#
+
+Event.str(ev: self ref Event) : string
+{
+ s := "?";
+ pick e := ev {
+ Edata =>
+ s = sprint("Edata %d = ", len e.data);
+ for(i:=0; i<len e.data; i++)
+ s += hex(int e.data[i], 2) + " ";
+ Equit =>
+ s = "Equit";
+ Eproto =>
+ s = sprint("Eproto %ux (%s)", e.cmd, e.s);
+ }
+ return s;
+}
diff --git a/appl/wm/minitel/event.m b/appl/wm/minitel/event.m
new file mode 100644
index 00000000..1b524363
--- /dev/null
+++ b/appl/wm/minitel/event.m
@@ -0,0 +1,19 @@
+#
+# Copyright © 1998 Vita Nuova Limited. All rights reserved.
+#
+
+Event: adt {
+ path: int; # path for delivery
+ from: int; # sending module (for reply)
+ pick {
+ Edata =>
+ data: array of byte;
+ Eproto =>
+ cmd: int;
+ s: string;
+ a0, a1, a2: int; # parameters
+ Equit =>
+ }
+
+ str: fn(e: self ref Event) : string; # convert to readable form
+};
diff --git a/appl/wm/minitel/keyb.b b/appl/wm/minitel/keyb.b
new file mode 100644
index 00000000..aba5485d
--- /dev/null
+++ b/appl/wm/minitel/keyb.b
@@ -0,0 +1,367 @@
+#
+# Copyright © 1998 Vita Nuova Limited. All rights reserved.
+#
+
+# special keyboard operations
+Extend, # enable cursor and editing keys and control chars
+C0keys, # cursor keys send BS,HT,LF and VT
+Invert # case inversion
+ : con 1 << iota;
+
+Keyb: adt {
+ m: ref Module; # common attributes
+ in: chan of ref Event;
+
+ cmd: chan of string; # from Tk (keypresses and focus)
+ spec: int; # special keyboard extensions
+
+ init: fn(k: self ref Keyb, toplevel: ref Tk->Toplevel);
+ reset: fn(k: self ref Keyb);
+ run: fn(k: self ref Keyb);
+ quit: fn(k: self ref Keyb);
+ map: fn(k: self ref Keyb, key:int): array of byte;
+};
+
+Keyb.init(k: self ref Keyb, toplevel: ref Tk->Toplevel)
+{
+ k.in = chan of ref Event;
+ k.cmd = chan of string;
+ tk->namechan(toplevel, k.cmd, "keyb"); # Tk -> keyboard
+ k.reset();
+}
+
+Keyb.reset(k: self ref Keyb)
+{
+ k.m = ref Module(Pmodem|Psocket, 0);
+}
+
+ask(in: chan of string, out: chan of string)
+{
+ keys: string;
+
+ T.mode = Videotex;
+ S.setmode(Videotex);
+# clear(S);
+ prompt: con "Numéroter: ";
+ number := M.lastdialstr;
+ S.msg(prompt);
+
+Input:
+ for(;;) {
+ n := len prompt + len number;
+ # guard length must be > len prompt
+ if (n > 30)
+ n -= 30;
+ else
+ n = 0;
+ S.msg(prompt + number[n:]);
+ keys = <- in;
+ if (keys == nil)
+ return;
+
+ keys = canoncmd(keys);
+
+ case keys {
+ "connect" or "send" =>
+ break Input;
+ "correct" =>
+ if(len number > 0)
+ number = number[0: len number -1];
+ "cancel" =>
+ number = "";
+ break Input;
+ "repeat" or "index" or "guide" or "next" or "previous" =>
+ ;
+ * =>
+ number += keys;
+ }
+ }
+
+ S.msg(nil);
+ for (;;) alt {
+ out <- = number =>
+ return;
+ keys = <- in =>
+ if (keys == nil)
+ return;
+ }
+}
+
+Keyb.run(k: self ref Keyb)
+{
+ dontask := chan of string;
+ askchan := dontask;
+ askkeys := chan of string;
+Runloop:
+ for(;;){
+ alt {
+ ev := <- k.in =>
+ pick e := ev {
+ Equit =>
+ break Runloop;
+ Eproto =>
+ case e.cmd {
+ Creset =>
+ k.reset();
+ Cproto =>
+ case e.a0 {
+ START =>
+ case e.a1 {
+ LOWERCASE =>
+ k.spec |= Invert;
+ }
+ STOP =>
+ case e.a1 {
+ LOWERCASE =>
+ k.spec &= ~Invert;
+ }
+ }
+ * => break;
+ }
+ }
+ cmd := <- k.cmd =>
+ if(debug['k'] > 0) {
+ fprint(stderr, "Tk %s\n", cmd);
+ }
+ (n, args) := sys->tokenize(cmd, " ");
+ if(n >0)
+ case hd args {
+ "key" =>
+ (key, nil) := toint(hd tl args, 16);
+ if(askchan != dontask) {
+ s := minikey(key);
+ if (s == nil)
+ s[0] = key;
+ askkeys <-= s;
+ break;
+ }
+ keys := k.map(key);
+ if(keys != nil) {
+ send(ref Event.Edata(k.m.path, Mkeyb, keys));
+ }
+ "skey" => # minitel key hit (soft key)
+ if(hd tl args == "Exit") {
+ if(askchan != dontask) {
+ askchan = dontask;
+ askkeys <-= nil;
+ }
+ if(T.state == Online || T.state == Connecting) {
+ seq := keyseq("connect");
+ if(seq != nil) {
+ send(ref Event.Edata(k.m.path, Mkeyb, seq));
+ send(ref Event.Edata(k.m.path, Mkeyb, seq));
+ }
+ send(ref Event.Eproto(Pmodem, Mkeyb, Cdisconnect, "", 0,0,0));
+ }
+ send(ref Event.Equit(0, 0));
+ break;
+ }
+ if(askchan != dontask) {
+ askkeys <-= hd tl args;
+ break;
+ }
+ case hd tl args {
+ "Connect" =>
+ case T.state {
+ Local =>
+ if(M.connect == Network)
+ send(ref Event.Eproto(Pmodem, Mkeyb, Cconnect, "", 0,0,0));
+ else {
+ askchan = chan of string;
+ spawn ask(askkeys, askchan);
+ }
+ Connecting =>
+ send(ref Event.Eproto(Pmodem, Mkeyb, Cdisconnect, "", 0,0,0));
+ Online =>
+ seq := keyseq("connect");
+ if(seq != nil)
+ send(ref Event.Edata(k.m.path, Mkeyb, seq));
+ }
+ * =>
+ seq := keyseq(hd tl args);
+ if(seq != nil)
+ send(ref Event.Edata(k.m.path, Mkeyb, seq));
+ }
+ "click" => # fetch a word from the display
+ x := int hd tl args;
+ y := int hd tl tl args;
+ word := disp->GetWord(Point(x, y));
+ if(word != nil) {
+ if (askchan != dontask) {
+ askkeys <- = word;
+ break;
+ }
+ if (T.state == Local) {
+ if (canoncmd(word) == "connect") {
+ if(M.connect == Network)
+ send(ref Event.Eproto(Pmodem, Mkeyb, Cconnect, "", 0,0,0));
+ else {
+ askchan = chan of string;
+ spawn ask(askkeys, askchan);
+ }
+ break;
+ }
+ }
+ seq := keyseq(word);
+ if(seq != nil)
+ send(ref Event.Edata(k.m.path, Mkeyb, seq));
+ else {
+ send(ref Event.Edata(k.m.path, Mkeyb, array of byte word ));
+ send(ref Event.Edata(k.m.path, Mkeyb, keyseq("send")));
+ }
+ }
+
+ }
+ dialstr := <-askchan =>
+ askchan = dontask;
+ if(dialstr != nil) {
+ M.dialstr = dialstr;
+ send(ref Event.Eproto(Pmodem, Mkeyb, Cconnect, "", 0,0,0));
+ }
+ }
+ }
+ send(nil);
+}
+
+
+# Perform mode specific key translation
+# returns nil on invalid keypress,
+Keyb.map(nil: self ref Keyb, key: int): array of byte
+{
+ # hardware to minitel keyboard mapping
+ cmd := minikey(key);
+ if (cmd != nil) {
+ seq := keyseq(cmd);
+ if(seq != nil)
+ return seq;
+ }
+
+ # alphabetic (with case mapping)
+ case T.mode {
+ Videotex =>
+ if(key >= 'A' && key <= 'Z')
+ return array [] of { byte ('a' + (key - 'A'))};
+ if(key >= 'a' && key <= 'z')
+ return array [] of {byte ('A' + (key - 'a'))};
+ Mixed or Ascii =>
+ if(key >= 'A' && key <= 'Z' || key >= 'a' && key <= 'z')
+ return array [] of {byte key};
+ };
+
+ # Numeric
+ if(key >= '0' && key <= '9')
+ return array [] of {byte key};
+
+ # Control-A -> Control-Z, Esc - columns 0 and 1
+ if(key >= 16r00 && key <=16r1f)
+ case T.mode {
+ Videotex =>
+ return nil;
+ Mixed or Ascii =>
+ return array [] of {byte key};
+ }
+
+ # miscellaneous key mapping
+ case key {
+ 16r20 => ; # space
+ 16ra3 => return array [] of { byte 16r19, byte 16r23 }; # pound
+ '!' or '"' or '#' or '$'
+ or '%' or '&' or '\'' or '(' or ')'
+ or '*' or '+' or ',' or '-'
+ or '.' or ':' or ';' or '<'
+ or '=' or '>' or '?' or '@' => ;
+ KF13 => # request for error correction - usually Fnct M + C
+ if((M.spec&Ecp) == 0 && T.state == Online && T.connect == Direct) {
+fprint(stderr, "requesting Ecp\n");
+ return array [] of { byte SEP, byte 16r4a };
+ }
+ return nil;
+ * => return nil;
+ }
+ return array [] of {byte key};
+}
+
+Keyb.quit(k: self ref Keyb)
+{
+ if(k==nil);
+}
+
+canoncmd(s : string) : string
+{
+ s = tolower(s);
+ case s {
+ "connect" or "cx/fin" or
+ "connexion" or "fin" => return "connect";
+ "send" or "envoi" => return "send";
+ "repeat" or "repetition" => return "repeat";
+ "index" or "sommaire" or "somm"
+ => return "index";
+ "guide" => return "guide";
+ "correct" or "correction" => return "correct";
+ "cancel" or "annulation" or "annul" or "annu"
+ => return "cancel";
+ "next" or "suite" => return "next";
+ "previous" or "retour" or "retou"
+ => return "previous";
+ }
+ return s;
+}
+
+# map softkey names to the appropriate byte sequences
+keyseq(skey: string): array of byte
+{
+ b2 := 0;
+ asterisk := 0;
+ if(skey == nil || len skey == 0)
+ return nil;
+ if(skey[0] == '*') {
+ asterisk = 1;
+ skey = skey[1:];
+ }
+ skey = canoncmd(skey);
+ case skey {
+ "connect" => b2 = 16r49;
+ "send" => b2 = 16r41;
+ "repeat" => b2 = 16r43;
+ "index" => b2 = 16r46;
+ "guide" => b2 = 16r44;
+ "correct" => b2 = 16r47;
+ "cancel" => b2 = 16r45;
+ "next" => b2 = 16r48;
+ "previous" => b2 = 16r42;
+ }
+ if(b2) {
+ if(asterisk)
+ return array [] of { byte '*', byte SEP, byte b2};
+ else
+ return array [] of { byte SEP, byte b2};
+ } else
+ return nil;
+}
+
+# map hardware or software keyboard presses to minitel functions
+minikey(key: int): string
+{
+ case key {
+ Kup or KupPC =>
+ return"previous";
+ Kdown or KdownPC =>
+ return "next";
+ Kenter =>
+ return "send";
+ Kback =>
+ return "correct";
+ Kesc =>
+ return "cancel";
+ KF1 =>
+ return "guide";
+ KF2 =>
+ return "connect";
+ KF3 =>
+ return "repeat";
+ KF4 =>
+ return "index";
+ * =>
+ return nil;
+ }
+} \ No newline at end of file
diff --git a/appl/wm/minitel/mdisplay.b b/appl/wm/minitel/mdisplay.b
new file mode 100644
index 00000000..b3c629f9
--- /dev/null
+++ b/appl/wm/minitel/mdisplay.b
@@ -0,0 +1,799 @@
+implement MDisplay;
+
+#
+# Copyright © 1998 Vita Nuova Limited. All rights reserved.
+#
+# - best viewed with acme!
+
+include "sys.m";
+include "draw.m";
+include "mdisplay.m";
+
+sys : Sys;
+draw : Draw;
+
+Context, Point, Rect, Font, Image, Display, Screen : import draw;
+
+
+# len cell == number of lines
+# len cell[0] == number of cellmap cells per char
+# (x,y)*cellsize == font glyph clipr
+
+cellS := array [] of {array [] of {(0, 0)}};
+cellW := array [] of {array [] of {(0, 0), (1, 0)}};
+cellH := array [] of {array [] of {(0, 1)}, array [] of {(0, 0)}};
+cellWH := array [] of {array [] of {(0, 1), (1, 1)}, array [] of {(0, 0), (1, 0)}};
+
+Cellinfo : adt {
+ font : ref Font;
+ ch, attr : int;
+ clipmod : (int, int);
+};
+
+
+# current display attributes
+display : ref Display;
+window : ref Image;
+frames := array [2] of ref Image;
+update : chan of int;
+
+colours : array of ref Image;
+bright : ref Image;
+
+# current mode attributes
+cellmap : array of Cellinfo;
+nrows : int;
+ncols : int;
+ulheight : int;
+curpos : Point;
+winoff : Point;
+cellsize : Point;
+modeattr : con fgWhite | bgBlack;
+showC := 0;
+delims := 0;
+modbbox := Rect((0,0),(0,0));
+blankrow : array of Cellinfo;
+
+ctxt : ref Context;
+font : ref Font; # g0 videotex font - extended with unicode g2 syms
+fonth : ref Font; # double height version of font
+fontw : ref Font; # double width
+fonts : ref Font; # double size
+fontg1 : ref Font; # semigraphic videotex font (ch+128=separated)
+fontfr : ref Font; # french character set
+fontusa : ref Font; # american character set
+
+
+Init(c : ref Context) : string
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+
+ if (c == nil || c.display == nil)
+ return "no display context";
+
+ ctxt = c;
+ disp := ctxt.display;
+
+ black := disp.rgb2cmap(0, 0, 0);
+ blue := disp.rgb2cmap(0, 0, 255);
+ red := disp.rgb2cmap(255, 0, 0);
+ magenta := disp.rgb2cmap(255, 0, 255);
+ green := disp.rgb2cmap(0, 255, 0);
+ cyan := disp.rgb2cmap(0, 255, 255);
+ yellow := disp.rgb2cmap(255, 255, 0);
+ white := disp.rgb2cmap(240, 240, 240);
+
+ iblack := disp.color(black);
+ iblue := disp.color(blue);
+ ired := disp.color(red);
+ imagenta := disp.color(magenta);
+ igreen := disp.color(green);
+ icyan := disp.color(cyan);
+ iyellow := disp.color(yellow);
+ iwhite := disp.color(white);
+
+ colours = array [] of { iblack, iblue, ired, imagenta,
+ igreen, icyan, iyellow, iwhite};
+ bright = disp.color(disp.rgb2cmap(255, 255, 255));
+
+ update = chan of int;
+ spawn Update(update);
+ display = disp;
+ return nil;
+}
+
+Quit()
+{
+ if (update != nil)
+ update <- = QuitUpdate;
+ update = nil;
+ window = nil;
+ frames[0] = nil;
+ frames[1] = nil;
+ cellmap = nil;
+ display = nil;
+}
+
+Mode(r : Draw->Rect, w, h, ulh, d : int, fontpath : string) : (string, ref Draw->Image)
+{
+ if (display == nil)
+ # module not properly Init()'d
+ return ("not initialized", nil);
+
+ curpos = Point(-1, -1);
+ if (window != nil)
+ update <- = Pause;
+
+ cellmap = nil;
+ window = nil;
+ (dx, dy) := (r.dx(), r.dy());
+ if (dx == 0 || dy == 0) {
+ return (nil, nil);
+ }
+
+ black := display.rgb2cmap(0, 0, 0);
+ window = ctxt.screen.newwindow(r, Draw->Refbackup, black);
+ if (window == nil)
+ return ("cannot create window", nil);
+
+ window.origin(Point(0,0), r.min);
+ winr := Rect((0,0), (dx, dy));
+ frames[0] = display.newimage(winr, window.chans, 0, black);
+ frames[1] = display.newimage(winr, window.chans, 0, black);
+
+ if (window == nil || frames[0] == nil || frames[1] == nil) {
+ window = nil;
+ return ("cannot allocate display resources", nil);
+ }
+
+ ncols = w;
+ nrows = h;
+ ulheight = ulh;
+ delims = d;
+ showC = 0;
+
+ cellmap = array [ncols * nrows] of Cellinfo;
+
+ font = Font.open(display, fontpath);
+ fontw = Font.open(display, fontpath + "w");
+ fonth = Font.open(display, fontpath + "h");
+ fonts = Font.open(display, fontpath + "s");
+ fontg1 = Font.open(display, fontpath + "g1");
+ fontfr = Font.open(display, fontpath + "fr");
+ fontusa = Font.open(display, fontpath + "usa");
+
+ if (font != nil)
+ cellsize = Point(font.width(" "), font.height);
+ else
+ cellsize = Point(dx/ncols, dy / nrows);
+
+ winoff.x = (dx - (cellsize.x * ncols)) / 2;
+ winoff.y = (dy - (cellsize.y * nrows)) /2;
+ if (winoff.x < 0)
+ winoff.x = 0;
+ if (winoff.y < 0)
+ winoff.y = 0;
+
+ blankrow = array [ncols] of {* => Cellinfo(font, ' ', modeattr | fgWhite, (0,0))};
+ for (y := 0; y < nrows; y++) {
+ col0 := y * ncols;
+ cellmap[col0:] = blankrow;
+ }
+
+# frames[0].clipr = frames[0].r;
+# frames[1].clipr = frames[1].r;
+# frames[0].draw(frames[0].r, colours[0], nil, Point(0,0));
+# frames[1].draw(frames[1].r, colours[0], nil, Point(0,0));
+# window.draw(window.r, colours[0], nil, Point(0,0));
+ update <- = Continue;
+ return (nil, window);
+}
+
+Cursor(pt : Point)
+{
+ if (update == nil || cellmap == nil)
+ # update thread (cursor/character flashing) not running
+ return;
+
+ # normalize pt
+ pt.x--;
+
+ curpos = pt;
+ update <- = CursorSet;
+}
+
+Put(str : string, pt : Point, charset, attr, insert : int)
+{
+ if (cellmap == nil || str == nil)
+ # nothing to do
+ return;
+
+ # normalize pt
+ pt.x--;
+
+ f : ref Font;
+ cell := cellS;
+
+ case charset {
+ videotex =>
+ if (!(attr & attrD))
+ attr &= (fgMask | attrF | attrH | attrW | attrP);
+ if (attr & attrW && attr & attrH) {
+ cell = cellWH;
+ f = fonts;
+ } else if (attr & attrH) {
+ cell = cellH;
+ f = fonth;
+ } else if (attr & attrW) {
+ cell = cellW;
+ f = fontw;
+ } else {
+ f = font;
+ }
+
+ semigraphic =>
+ f = fontg1;
+ if (attr & attrL) {
+ # convert to "separated"
+ newstr := "";
+ for (ix := 0; ix < len str; ix++)
+ newstr[ix] = str[ix] + 16r80;
+ str = newstr;
+ }
+ # semigraphic charset does not support size / polarity attributes
+ # attrD always set later once field attr established
+ attr &= ~(attrD | attrH | attrW | attrP | attrL);
+
+ french => f = fontfr;
+ american => f = fontusa;
+ * => f = font;
+ }
+
+ update <- = Pause;
+
+ txty := pt.y - (len cell - 1);
+ for (cellix := len cell - 1; cellix >= 0; cellix--) {
+ y := pt.y - cellix;
+
+ if (y < 0)
+ continue;
+ if (y >= nrows)
+ break;
+
+ col0 := y * ncols;
+ colbase := pt.y * ncols;
+
+ if (delims && !(attr & attrD)) {
+ # seek back for a delimiter
+ mask : int;
+ delimattr := modeattr;
+
+ # semigraphics only inherit attrC from current field
+ if (charset == semigraphic)
+ mask = attrC;
+ else
+ mask = bgMask | attrC | attrL;
+
+ for (ix := pt.x-1; ix >= 0; ix--) {
+ cix := ix + col0;
+ if (cellmap[cix].attr & attrD) {
+ if (cellmap[cix].font == fontg1 && f != fontg1)
+ # don't carry over attrL from semigraphic field
+ mask &= ~attrL;
+
+ delimattr = cellmap[cix].attr;
+ break;
+ }
+ }
+ attr = (attr & ~mask) | (delimattr & mask);
+
+ # semigraphics validate background colour
+ if (charset == semigraphic)
+ attr |= attrD;
+ }
+
+ strlen := len cell[0] * len str;
+ gfxwidth := cellsize.x * strlen;
+ srco := Point(pt.x*cellsize.x, y*cellsize.y);
+
+ if (insert) {
+ # copy existing cells and display to new position
+ if (pt.x + strlen < ncols) {
+ for (destx := ncols -1; destx > pt.x; destx--) {
+ srcx := destx - strlen;
+ if (srcx < 0)
+ break;
+ cellmap[col0 + destx] = cellmap[col0 + srcx];
+ }
+
+ # let draw() do the clipping for us
+ dsto := Point(srco.x + gfxwidth, srco.y);
+ dstr := Rect((dsto.x, srco.y), (ncols * cellsize.x, srco.y + cellsize.y));
+
+ frames[0].clipr = frames[0].r;
+ frames[1].clipr = frames[1].r;
+ frames[0].draw(dstr, frames[0], nil, srco);
+ frames[1].draw(dstr, frames[1], nil, srco);
+ if (modbbox.dx() == 0)
+ modbbox = dstr;
+ else
+ modbbox = boundingrect(modbbox, dstr);
+ }
+ }
+
+ # copy-in new string
+ x := pt.x;
+ for (strix := 0; x < ncols && strix < len str; strix++) {
+ for (clipix := 0; clipix < len cell[cellix]; (x, clipix) = (x+1, clipix+1)) {
+ if (x < 0)
+ continue;
+ if (x >= ncols)
+ break;
+ cmix := col0 + x;
+ cellmap[cmix].font = f;
+ cellmap[cmix].ch = str[strix];
+ cellmap[cmix].attr = attr;
+ cellmap[cmix].clipmod = cell[cellix][clipix];
+ }
+ }
+
+ # render the new string
+ txto := Point(srco.x, txty * cellsize.y);
+ strr := Rect(srco, (srco.x + gfxwidth, srco.y + cellsize.y));
+ if (strr.max.x > ncols * cellsize.x)
+ strr.max.x = ncols * cellsize.x;
+
+ drawstr(str, f, strr, txto, attr);
+
+ # redraw remainder of line until find cell not needing redraw
+
+ # this could be optimised by
+ # spotting strings with same attrs, font and clipmod pairs
+ # and write out whole string rather than processing
+ # a char at a time
+
+ attr2 := attr;
+ mask := bgMask | attrC | attrL;
+ s := "";
+ for (; delims && x < ncols; x++) {
+ if (x < 0)
+ continue;
+ newattr := cellmap[col0 + x].attr;
+
+ if (cellmap[col0 + x].font == fontg1) {
+ # semigraphics act as bg colour delimiter
+ attr2 = (attr2 & ~bgMask) | (newattr & bgMask);
+ mask &= ~attrL;
+ } else
+ if (newattr & attrD)
+ break;
+
+ if ((attr2 & mask) == (newattr & mask))
+ break;
+ newattr = (newattr & ~mask) | (attr2 & mask);
+ cellmap[col0 + x].attr = newattr;
+ s[0] = cellmap[col0 + x].ch;
+ (cx, cy) := cellmap[col0 + x].clipmod;
+ f2 := cellmap[col0 + x].font;
+
+ cellpos := Point(x * cellsize.x, y * cellsize.y);
+ clipr := Rect(cellpos, cellpos.add(Point(cellsize.x, cellsize.y)));
+ drawpt := cellpos.sub(Point(cx*cellsize.x, cy*cellsize.y));
+ drawstr(s, f2, clipr, drawpt, newattr);
+ }
+ }
+ update <- = Continue;
+}
+
+Scroll(topline, nlines : int)
+{
+ if (cellmap == nil || nlines == 0)
+ return;
+
+ blankr : Rect;
+ scr := Rect((0,topline * cellsize.y), (ncols * cellsize.x, nrows * cellsize.y));
+
+ update <- = Pause;
+
+ frames[0].clipr = scr;
+ frames[1].clipr = scr;
+ dstr := scr.subpt(Point(0, nlines * cellsize.y));
+
+ frames[0].draw(dstr, frames[0], nil, frames[0].clipr.min);
+ frames[1].draw(dstr, frames[1], nil, frames[1].clipr.min);
+
+ if (nlines > 0) {
+ # scroll up - copy up from top
+ if (nlines > nrows - topline)
+ nlines = nrows - topline;
+ for (y := nlines + topline; y < nrows; y++) {
+ srccol0 := y * ncols;
+ dstcol0 := (y - nlines) * ncols;
+ cellmap[dstcol0:] = cellmap[srccol0:srccol0+ncols];
+ }
+ for (y = nrows - nlines; y < nrows; y++) {
+ col0 := y * ncols;
+ cellmap[col0:] = blankrow;
+ }
+ blankr = Rect(Point(0, scr.max.y - (nlines * cellsize.y)), scr.max);
+ } else {
+ # scroll down - copy down from bottom
+ nlines = -nlines;
+ if (nlines > nrows - topline)
+ nlines = nrows - topline;
+ for (y := (nrows - 1) - nlines; y >= topline; y--) {
+ srccol0 := y * ncols;
+ dstcol0 := (y + nlines) * ncols;
+ cellmap[dstcol0:] = cellmap[srccol0:srccol0+ncols];
+ }
+ for (y = topline; y < nlines; y++) {
+ col0 := y * ncols;
+ cellmap[col0:] = blankrow;
+ }
+ blankr = Rect(scr.min, (scr.max.x, scr.min.y + (nlines * cellsize.y)));
+ }
+ frames[0].draw(blankr, colours[0], nil, Point(0,0));
+ frames[1].draw(blankr, colours[0], nil, Point(0,0));
+ if (modbbox.dx() == 0)
+ modbbox = scr;
+ else
+ modbbox = boundingrect(modbbox, scr);
+ update <- = Continue;
+}
+
+Reveal(show : int)
+{
+ showC = show;
+ if (cellmap == nil)
+ return;
+
+ update <- = Pause;
+ for (y := 0; y < nrows; y++) {
+ col0 := y * ncols;
+ for (x := 0; x < ncols; x++) {
+ attr := cellmap[col0+x].attr;
+ if (!(attr & attrC))
+ continue;
+
+ s := "";
+ s[0] = cellmap[col0 + x].ch;
+ (cx, cy) := cellmap[col0 + x].clipmod;
+ f := cellmap[col0 + x].font;
+ cellpos := Point(x * cellsize.x, y * cellsize.y);
+ clipr := Rect(cellpos, cellpos.add(Point(cellsize.x, cellsize.y)));
+ drawpt := cellpos.sub(Point(cx*cellsize.x, cy*cellsize.y));
+
+ drawstr(s, f, clipr, drawpt, attr);
+ }
+ }
+ update <- = Continue;
+}
+
+# expects that pt.x already normalized
+wordchar(pt : Point) : int
+{
+ if (pt.x < 0 || pt.x >= ncols)
+ return 0;
+ if (pt.y < 0 || pt.y >= nrows)
+ return 0;
+
+ col0 := pt.y * ncols;
+ c := cellmap[col0 + pt.x];
+
+ if (c.attr & attrC && !showC)
+ # don't let clicking on screen 'reveal' concealed chars!
+ return 0;
+
+ if (c.font == fontg1)
+ return 0;
+
+ if (c.attr & attrW) {
+ # check for both parts of character
+ (modx, nil) := c.clipmod;
+ if (modx == 1) {
+ # rhs of char - check lhs is the same
+ if (pt.x <= 0)
+ return 0;
+ lhc := cellmap[col0 + pt.x-1];
+ (lhmodx, nil) := lhc.clipmod;
+ if (!((lhc.attr & attrW) && (lhc.font == c.font) && (lhc.ch == c.ch) && (lhmodx == 0)))
+ return 0;
+ } else {
+ # lhs of char - check rhs is the same
+ if (pt.x >= ncols - 1)
+ return 0;
+ rhc := cellmap[col0 + pt.x + 1];
+ (rhmodx, nil) := rhc.clipmod;
+ if (!((rhc.attr & attrW) && (rhc.font == c.font) && (rhc.ch == c.ch) && (rhmodx == 1)))
+ return 0;
+ }
+ }
+ if (c.ch >= 16r30 && c.ch <= 16r39)
+ # digits
+ return 1;
+ if (c.ch >= 16r41 && c.ch <= 16r5a)
+ # capitals
+ return 1;
+ if (c.ch >= 16r61 && c.ch <= 16r7a)
+ # lowercase
+ return 1;
+ if (c.ch == '*' || c.ch == '/')
+ return 1;
+ return 0;
+}
+
+GetWord(gfxpt : Point) : string
+{
+ if (cellmap == nil)
+ return nil;
+
+ scr := Rect((0,0), (ncols * cellsize.x, nrows * cellsize.y));
+ gfxpt = gfxpt.sub(winoff);
+
+ if (!gfxpt.in(scr))
+ return nil;
+
+ x := gfxpt.x / cellsize.x;
+ y := gfxpt.y / cellsize.y;
+ col0 := y * ncols;
+
+ s := "";
+
+ # seek back
+ for (sx := x; sx >= 0; sx--)
+ if (!wordchar(Point(sx, y)))
+ break;
+
+ if (sx++ == x)
+ return nil;
+
+ # seek forward, constructing s
+ for (; sx < ncols; sx++) {
+ if (!wordchar(Point(sx, y)))
+ break;
+ c := cellmap[col0 + sx];
+ s[len s] = c.ch;
+ if (c.attr & attrW)
+ sx++;
+ }
+ return s;
+}
+
+Refresh()
+{
+ if (window == nil || modbbox.dx() == 0)
+ return;
+
+ if (update != nil)
+ update <- = Redraw;
+}
+
+framecolours(attr : int) : (ref Image, ref Image, ref Image, ref Image)
+{
+ fg : ref Image;
+ fgcol := attr & fgMask;
+ if (fgcol == fgWhite && attr & attrB)
+ fg = bright;
+ else
+ fg = colours[fgcol / fgBase];
+
+ bg : ref Image;
+ bgcol := attr & bgMask;
+ if (bgcol == bgWhite && attr & attrB)
+ bg = bright;
+ else
+ bg = colours[bgcol / bgBase];
+
+ (fg0, fg1) := (fg, fg);
+ (bg0, bg1) := (bg, bg);
+
+ if (attr & attrP)
+ (fg0, bg0, fg1, bg1) = (bg1, fg1, bg0, fg0);
+
+ if (attr & attrF) {
+ fg0 = fg;
+ fg1 = bg;
+ }
+
+ if ((attr & attrC) && !showC)
+ (fg0, fg1) = (bg0, bg1);
+ return (fg0, bg0, fg1, bg1);
+}
+
+kill(pid : int)
+{
+ prog := "/prog/" + string pid + "/ctl";
+ fd := sys->open(prog, Sys->OWRITE);
+ if (fd != nil) {
+ cmd := array of byte "kill";
+ sys->write(fd, cmd, len cmd);
+ }
+}
+
+timer(ms : int, pc, tick : chan of int)
+{
+ pc <- = sys->pctl(0, nil);
+ for (;;) {
+ sys->sleep(ms);
+ tick <- = 1;
+ }
+}
+
+# Update() commands
+Redraw, Pause, Continue, CursorSet, QuitUpdate : con iota;
+
+Update(cmd : chan of int)
+{
+ flashtick := chan of int;
+ cursortick := chan of int;
+ pc := chan of int;
+ spawn timer(1000, pc, flashtick);
+ flashpid := <- pc;
+ spawn timer(500, pc, cursortick);
+ cursorpid := <- pc;
+
+ cursor : Point;
+ showcursor := 0;
+ cursoron := 0;
+ quit := 0;
+ nultick := chan of int;
+ flashchan := nultick;
+ pcount := 1;
+ fgframe := 0;
+
+ for (;!quit ;) alt {
+ c := <- cmd =>
+ case c {
+ Redraw =>
+ frames[0].clipr = frames[0].r;
+ frames[1].clipr = frames[1].r;
+ r := modbbox.addpt(winoff);
+ window.draw(r.addpt(window.r.min), frames[fgframe], nil, modbbox.min);
+ if (showcursor && cursoron)
+ drawcursor(cursor, fgframe, 1);
+ modbbox = Rect((0,0),(0,0));
+
+ Pause =>
+ if (pcount++ == 0)
+ flashchan = nultick;
+
+ Continue =>
+ pcount--;
+ if (pcount == 0)
+ flashchan = flashtick;
+
+ QuitUpdate =>
+ quit++;
+
+ CursorSet =>
+ frames[0].clipr = frames[0].r;
+ frames[1].clipr = frames[1].r;
+ if (showcursor && cursoron)
+ drawcursor(cursor, fgframe, 0);
+ cursoron = 0;
+ if (curpos.x < 0 || curpos.x >= ncols || curpos.y < 0 || curpos.y >= nrows)
+ showcursor = 0;
+ else {
+ cursor = curpos;
+ showcursor = 1;
+ drawcursor(cursor, fgframe, 1);
+ cursoron = 1;
+ }
+ }
+
+ <- flashchan =>
+ # flip displays...
+ fgframe = (fgframe + 1 ) % 2;
+ modbbox = Rect((0,0),(0,0));
+ frames[0].clipr = frames[0].r;
+ frames[1].clipr = frames[1].r;
+ window.draw(window.r.addpt(winoff), frames[fgframe], nil, Point(0,0));
+ if (showcursor && cursoron)
+ drawcursor(cursor, fgframe, 1);
+
+ <- cursortick =>
+ if (showcursor) {
+ cursoron = !cursoron;
+ drawcursor(cursor, fgframe, cursoron);
+ }
+ }
+ kill(flashpid);
+ kill(cursorpid);
+}
+
+
+drawstr(s : string, f : ref Font, clipr : Rect, drawpt : Point, attr : int)
+{
+ (fg0, bg0, fg1, bg1) := framecolours(attr);
+ frames[0].clipr = clipr;
+ frames[1].clipr = clipr;
+ frames[0].draw(clipr, bg0, nil, Point(0,0));
+ frames[1].draw(clipr, bg1, nil, Point(0,0));
+ ulrect : Rect;
+ ul := (attr & attrL) && ! (attr & attrD);
+
+ if (f != nil) {
+ if (ul)
+ ulrect = Rect((drawpt.x, drawpt.y + f.height - ulheight), (drawpt.x + clipr.dx(), drawpt.y + f.height));
+ if (fg0 != bg0) {
+ frames[0].text(drawpt, fg0, Point(0,0), f, s);
+ if (ul)
+ frames[0].draw(ulrect, fg0, nil, Point(0,0));
+ }
+ if (fg1 != bg1) {
+ frames[1].text(drawpt, fg1, Point(0,0), f, s);
+ if (ul)
+ frames[1].draw(ulrect, fg1, nil, Point(0,0));
+ }
+ }
+ if (modbbox.dx() == 0)
+ modbbox = clipr;
+ else
+ modbbox = boundingrect(modbbox, clipr);
+}
+
+boundingrect(r1, r2 : Rect) : Rect
+{
+ if (r2.min.x < r1.min.x)
+ r1.min.x = r2.min.x;
+ if (r2.min.y < r1.min.y)
+ r1.min.y = r2.min.y;
+ if (r2.max.x > r1.max.x)
+ r1.max.x = r2.max.x;
+ if (r2.max.y > r1.max.y)
+ r1.max.y = r2.max.y;
+ return r1;
+}
+
+drawcursor(pt : Point, srcix, show : int)
+{
+ col0 := pt.y * ncols;
+ c := cellmap[col0 + pt.x];
+ s := "";
+
+ s[0] = c.ch;
+ (cx, cy) := c.clipmod;
+ cellpos := Point(pt.x * cellsize.x, pt.y * cellsize.y);
+ clipr := Rect(cellpos, cellpos.add(Point(cellsize.x, cellsize.y)));
+ clipr = clipr.addpt(winoff);
+ clipr = clipr.addpt(window.r.min);
+
+ drawpt := cellpos.sub(Point(cx*cellsize.x, cy*cellsize.y));
+ drawpt = drawpt.add(winoff);
+ drawpt = drawpt.add(window.r.min);
+
+ if (!show) {
+ # copy from appropriate frame buffer
+ window.draw(clipr, frames[srcix], nil, cellpos);
+ return;
+ }
+
+ # invert colours
+ attr := c.attr ^ (fgMask | bgMask);
+
+ fg, bg : ref Image;
+ f := c.font;
+ if (srcix == 0)
+ (fg, bg, nil, nil) = framecolours(attr);
+ else
+ (nil, nil, fg, bg) = framecolours(attr);
+
+ prevclipr := window.clipr;
+ window.clipr = clipr;
+
+ window.draw(clipr, bg, nil, Point(0,0));
+ ulrect : Rect;
+ ul := (attr & attrL) && ! (attr & attrD);
+
+ if (f != nil) {
+ if (ul)
+ ulrect = Rect((drawpt.x, drawpt.y + f.height - ulheight), (drawpt.x + clipr.dx(), drawpt.y + f.height));
+ if (fg != bg) {
+ window.text(drawpt, fg, Point(0,0), f, s);
+ if (ul)
+ window.draw(ulrect, fg, nil, Point(0,0));
+ }
+ }
+ window.clipr = prevclipr;
+}
diff --git a/appl/wm/minitel/mdisplay.dis b/appl/wm/minitel/mdisplay.dis
new file mode 100644
index 00000000..fd193994
--- /dev/null
+++ b/appl/wm/minitel/mdisplay.dis
Binary files differ
diff --git a/appl/wm/minitel/mdisplay.m b/appl/wm/minitel/mdisplay.m
new file mode 100644
index 00000000..24d7173f
--- /dev/null
+++ b/appl/wm/minitel/mdisplay.m
@@ -0,0 +1,115 @@
+#
+# Minitel display handling module
+#
+# © 1998 Vita Nuova Limited. All rights reserved.
+#
+
+MDisplay: module
+{
+
+ PATH: con "/dis/wm/minitel/mdisplay.dis";
+
+ # Available character sets
+ videotex, semigraphic, french, american : con iota;
+
+ # Fill() attributes bit mask
+ #
+ # DL CFPH WBbb bfff
+ #
+ # D = Delimiter (set "serial" attributes for rest of line)
+ # L = Lining (underlined text & "separated" graphics)
+ # C = Concealing
+ # F = Flashing
+ # P = polarity (1 = "inverse")
+ # H = double height
+ # W = double width (set H+W for double size)
+ # B = bright (0: fgwhite=lt.grey, 1: fgwhite=white)
+ # bbb = background colour
+ # fff = foreground colour
+
+ fgBase : con 8r001;
+ bgBase : con 8r010;
+ attrBase : con 8r100;
+
+ fgMask : con 8r007;
+ bgMask : con 8r070;
+ attrMask : con ~0 ^ (fgMask | bgMask);
+
+ fgBlack, fgBlue, fgRed, fgMagenta,
+ fgGreen, fgCyan, fgYellow, fgWhite : con iota * fgBase;
+
+ bgBlack, bgBlue, bgRed, bgMagenta,
+ bgGreen, bgCyan, bgYellow, bgWhite : con iota * bgBase;
+
+ attrB, attrW, attrH, attrP, attrF, attrC, attrL, attrD : con attrBase << iota;
+
+ #
+ # Init (ctxt) : string
+ # performs general module initialisation
+ # creates the display window of size/position r using the
+ # given display context.
+ # spawns refresh thread
+ # returns reason for error, or nil on success
+ #
+ # Mode(rect, width, height, ulheight, delims, fontpath) : (string, ref Draw->Image)
+ # set/reset display to given rectangle and character grid size
+ # ulheight == underline height from bottom of character cell
+ # if delims != 0 then "field" attrs for Put() are derived from
+ # preceding delimiter otherwise Put() attrs are taken as is
+ #
+ # load fonts:
+ # <fontpath> videotex
+ # <fontpath>w videotex double width
+ # <fontpath>h videotex double height
+ # <fontpath>s videotex double size
+ # <fontpath>g1 videotex semigraphics
+ # <fontpath>fr french character set
+ # <fontpath>usa american character set
+ # Note:
+ # charset g2 is not directly supported, instead the symbols
+ # of g2 that do not appear in g0 (standard videotex charset)
+ # are available in videotex font using unicode char codes.
+ # Therefore controlling s/w must map g2 codes to unicode.
+ #
+ # Cursor(pt)
+ # move cursor to given position
+ # row number (y) is 0 based
+ # column number (x) is 1 based
+ # move cursor off-screen to hide
+ #
+ # Put(str, pt, charset, attr, insert)
+ # render string str at position pt in the given character set
+ # using specified attributes.
+ # if insert is non-zero, all characters from given position to end
+ # of line are moved right by len str positions.
+ #
+ # Scroll(topline, nlines)
+ # move the whole displayby nlines (+ve = scroll up).
+ # exposed lines of display are set to spaces rendered with
+ # the current mode attribute flags.
+ # scroll region is from topline to bottom of display
+ #
+ # Reveal(reveal)
+ # reveal/hide all chars affected by Concealing attribute.
+ #
+ # Refresh()
+ # force screen update
+ #
+ # GetWord(pt) : string
+ # returns on-screen word at given graphics co-ords
+ # returns nil if blank or semigraphic charset at location
+ #
+ # Quit()
+ # undo Init()
+
+
+ Init : fn (ctxt : ref Draw->Context) : string;
+ Mode : fn (r : Draw->Rect, width, height, ulh, attr : int, fontpath : string) : (string, ref Draw->Image);
+ Cursor : fn (pt : Draw->Point);
+ Put : fn (str : string, pt : Draw->Point, chset, attr, insert : int);
+ Scroll : fn (topline, nlines : int);
+ Reveal : fn (reveal : int);
+ Refresh : fn ();
+ GetWord : fn (gfxpt : Draw->Point) : string;
+ Quit : fn ();
+};
diff --git a/appl/wm/minitel/mdisplay.sbl b/appl/wm/minitel/mdisplay.sbl
new file mode 100644
index 00000000..c97ec8e7
--- /dev/null
+++ b/appl/wm/minitel/mdisplay.sbl
@@ -0,0 +1,1969 @@
+limbo .sbl 2.1
+MDisplay
+4
+mdisplay.b
+sys.m
+draw.m
+mdisplay.m
+1460
+69.1,25 0
+70.1,28 1
+72.5,13 2
+17,33 2
+73.9,29 3
+2,29 3
+75.1,9 4
+76.1,21 5
+78.1,32 6
+10,14 6
+24,25 6
+27,28 6
+30,31 6
+1,32 6
+1,32 6
+79.1,34 7
+10,14 7
+24,25 7
+27,28 7
+30,33 7
+1,34 7
+1,34 7
+80.1,33 8
+9,13 8
+23,26 8
+28,29 8
+31,32 8
+1,33 8
+1,33 8
+81.1,38 9
+12,16 9
+26,29 9
+31,32 9
+34,37 9
+1,38 9
+1,38 9
+82.1,34 10
+10,14 10
+24,25 10
+27,30 10
+32,33 10
+1,34 10
+1,34 10
+83.1,36 11
+10,14 11
+24,25 11
+27,30 11
+32,35 11
+1,36 11
+1,36 11
+84.1,37 12
+11,15 12
+25,28 12
+30,33 12
+35,36 12
+1,37 12
+1,37 12
+85.1,38 13
+10,14 13
+24,27 13
+29,32 13
+34,37 13
+1,38 13
+1,38 13
+87.1,28 14
+11,15 14
+22,27 14
+1,28 14
+1,28 14
+88.1,27 15
+11,15 15
+22,26 15
+1,27 15
+1,27 15
+89.1,25 16
+10,14 16
+21,24 16
+1,25 16
+1,25 16
+90.1,32 17
+13,17 17
+24,31 17
+1,32 17
+1,32 17
+91.1,28 18
+11,15 18
+22,27 18
+1,28 18
+1,28 18
+92.1,26 19
+10,14 19
+21,25 19
+1,26 19
+1,26 19
+93.1,30 20
+12,16 20
+23,29 20
+1,30 20
+1,30 20
+94.1,28 21
+11,15 21
+22,27 21
+1,28 21
+1,28 21
+96.1,97.37 22
+96.25,31 22
+25,31 22
+33,38 22
+33,38 22
+40,44 22
+40,44 22
+46,54 22
+46,54 22
+97.6,12 22
+6,12 22
+14,19 22
+14,19 22
+21,28 22
+21,28 22
+30,36 22
+30,36 22
+98.21,49 23
+21,25 23
+35,38 23
+40,43 23
+45,48 23
+21,49 23
+21,49 23
+1,50 23
+10,14 23
+10,14 23
+1,50 23
+1,50 23
+100.1,21 24
+101.1,21 25
+14,20 25
+1,21 25
+102.1,15 26
+103.8,11 27
+1,11 27
+108.5,18 28
+109.2,24 29
+110.1,13 30
+111.1,13 31
+112.1,10 32
+1,16 32
+113.1,10 33
+1,16 33
+114.1,14 34
+115.1,14 35
+116.0,1 36
+120.5,19 37
+122.10,27 38
+29,32 38
+2,33 38
+124.16,18 39
+20,22 39
+125.5,18 40
+126.2,19 41
+128.1,14 42
+129.1,13 43
+130.14,20 44
+14,15 44
+14,20 44
+14,20 44
+22,28 44
+22,23 44
+22,28 44
+22,28 44
+131.5,12 45
+16,23 45
+132.10,13 46
+15,18 46
+2,19 46
+135.1,35 47
+10,17 47
+27,28 47
+30,31 47
+33,34 47
+1,35 47
+1,35 47
+136.1,58 48
+10,21 48
+32,33 48
+35,50 48
+52,57 48
+1,58 48
+1,58 48
+137.5,18 49
+138.10,32 50
+34,37 50
+2,38 50
+140.1,33 51
+1,7 51
+21,22 51
+23,24 51
+27,32 51
+1,33 51
+1,33 51
+141.15,16 52
+17,18 52
+21,29 52
+142.1,10 53
+1,59 53
+13,20 53
+30,34 53
+36,48 53
+50,51 53
+53,58 53
+1,59 53
+1,59 53
+143.1,10 54
+1,59 54
+13,20 54
+30,34 54
+36,48 54
+50,51 54
+53,58 54
+1,59 54
+1,59 54
+145.5,18 55
+22,31 55
+22,38 55
+42,51 55
+42,58 55
+146.2,14 56
+147.10,45 57
+47,50 57
+2,51 57
+150.1,10 58
+151.1,10 59
+152.1,15 60
+153.1,11 61
+154.1,10 62
+156.18,31 63
+1,44 63
+158.1,37 64
+19,26 64
+28,36 64
+1,37 64
+1,37 64
+159.1,43 65
+19,26 65
+28,42 65
+1,43 65
+1,43 65
+160.1,43 66
+19,26 66
+28,42 66
+1,43 66
+1,43 66
+161.1,44 67
+20,27 67
+29,43 67
+1,44 67
+1,44 67
+162.1,45 68
+20,27 68
+29,44 68
+1,45 68
+1,45 68
+163.1,45 69
+20,27 69
+29,44 69
+1,45 69
+1,45 69
+164.1,47 70
+21,28 70
+30,46 70
+1,47 70
+1,47 70
+166.5,16 71
+167.19,34 72
+19,23 72
+30,33 72
+19,34 72
+19,34 72
+36,47 72
+36,47 73
+169.19,27 74
+29,39 74
+171.18,38 75
+12,39 75
+1,43 75
+172.18,38 76
+12,39 76
+1,42 76
+173.5,17 77
+174.2,14 78
+175.5,17 79
+176.2,14 80
+178.1,82 81
+35,81 81
+35,81 81
+35,81 81
+44,48 81
+50,53 81
+55,73 81
+76,77 81
+78,79 81
+35,81 81
+35,81 81
+35,81 81
+179.6,12 82
+14,23 83
+180.2,19 84
+181.2,27 85
+179.25,28 86
+25,28 86
+189.1,21 87
+190.9,12 88
+14,20 88
+1,21 88
+195.5,18 89
+22,36 89
+197.2,8 90
+200.1,7 91
+202.1,12 92
+203.1,22 93
+204.0,1 94
+208.5,19 95
+23,33 95
+210.2,8 96
+213.1,7 97
+216.1,14 98
+218.6,13 99
+6,13 99
+6,13 99
+220.7,21 100
+7,21 100
+221.3,51 101
+222.6,18 102
+6,18 102
+22,34 102
+22,34 102
+223.3,16 103
+224.3,12 104
+3,12 105
+225.13,25 106
+13,25 106
+226.3,15 107
+227.3,12 108
+3,12 109
+228.13,25 110
+13,25 110
+229.3,15 111
+230.3,12 112
+3,12 113
+232.3,11 114
+3,11 99
+236.2,12 115
+237.6,18 116
+6,18 116
+239.3,15 117
+240.8,15 118
+22,29 119
+17,29 119
+241.17,24 120
+17,32 120
+4,32 120
+240.31,35 121
+31,35 121
+242.3,15 122
+3,15 123
+246.2,50 124
+2,50 99
+248.12,22 125
+12,22 99
+249.14,25 126
+14,25 99
+250.8,16 127
+8,16 99
+253.1,18 128
+255.17,25 129
+16,30 129
+1,30 129
+256.16,24 130
+6,28 130
+30,41 131
+257.2,20 132
+259.6,11 133
+6,11 134
+260.3,11 135
+261.6,16 136
+6,16 137
+262.3,8 138
+264.2,19 139
+265.2,25 140
+267.6,12 141
+17,31 141
+17,31 141
+270.3,24 142
+273.7,29 143
+274.4,16 144
+4,16 145
+276.4,34 146
+278.8,20 147
+22,29 148
+279.4,20 149
+280.8,20 150
+8,33 150
+8,33 150
+281.9,21 151
+9,36 151
+40,51 151
+283.6,20 152
+285.17,29 153
+5,34 153
+286.5,10 154
+278.31,35 155
+31,35 155
+289.18,23 156
+10,24 156
+27,45 156
+3,45 156
+292.7,29 157
+293.4,17 158
+296.16,23 159
+12,23 159
+26,33 159
+2,33 159
+297.2,33 160
+298.16,31 161
+33,45 161
+300.6,12 162
+302.7,20 163
+7,28 163
+303.9,26 164
+28,40 165
+304.5,27 166
+305.9,17 167
+306.6,11 168
+307.13,25 169
+5,26 169
+37,48 169
+29,49 169
+5,49 169
+303.42,49 170
+42,49 170
+311.18,35 171
+37,43 171
+312.18,24 172
+26,32 172
+36,54 172
+56,75 172
+314.4,13 173
+4,13 173
+22,31 173
+22,31 173
+4,33 173
+4,33 174
+4,33 175
+315.4,13 176
+4,13 176
+22,31 176
+22,31 176
+4,33 176
+4,33 177
+4,33 178
+316.4,46 179
+4,13 179
+4,13 179
+19,23 179
+25,34 179
+25,34 179
+36,39 179
+41,45 179
+4,46 179
+317.4,46 180
+4,13 180
+4,13 180
+19,23 180
+25,34 180
+25,34 180
+36,39 180
+41,45 180
+4,46 180
+318.8,20 181
+8,15 181
+8,20 181
+8,20 181
+8,25 181
+319.5,19 182
+5,19 183
+321.5,42 184
+28,35 184
+37,41 184
+5,42 184
+5,42 184
+326.2,11 185
+327.7,17 186
+19,28 187
+40,47 187
+32,47 187
+328.8,19 188
+34,46 189
+30,46 189
+21,46 189
+329.8,13 190
+330.5,13 191
+331.8,18 192
+332.5,10 193
+333.4,20 194
+334.4,17 195
+4,26 195
+335.4,17 196
+23,33 196
+336.4,17 197
+4,29 197
+337.4,17 198
+28,40 198
+28,48 198
+4,48 198
+328.63,66 199
+68,76 199
+49,50 199
+52,58 199
+52,58 199
+327.49,56 200
+49,56 200
+342.16,22 201
+24,41 201
+343.15,19 202
+22,39 202
+41,60 202
+344.19,37 203
+6,37 203
+345.3,34 204
+347.2,35 205
+10,13 205
+15,16 205
+18,22 205
+24,28 205
+30,34 205
+2,35 205
+356.2,15 206
+357.2,32 207
+358.2,9 208
+359.9,15 209
+19,28 209
+360.7,12 210
+7,12 211
+361.4,12 212
+362.22,30 213
+14,31 213
+3,36 213
+364.15,23 214
+7,24 214
+7,39 214
+366.12,29 215
+32,50 215
+4,50 215
+367.4,18 216
+4,18 217
+369.8,23 218
+8,23 218
+8,23 219
+370.5,10 220
+372.25,41 221
+7,21 221
+7,41 221
+7,41 222
+373.4,9 223
+374.24,29 224
+13,30 224
+33,47 224
+3,47 224
+375.11,19 225
+3,20 225
+3,35 225
+376.18,26 226
+10,27 226
+3,30 226
+377.23,31 227
+15,32 227
+15,40 227
+378.17,25 228
+9,26 228
+3,31 228
+380.20,34 229
+36,50 229
+381.17,24 230
+26,68 230
+26,33 230
+44,54 230
+56,66 230
+26,68 230
+26,68 230
+382.3,61 231
+13,20 231
+31,44 231
+46,59 231
+3,61 231
+3,61 231
+383.3,41 232
+11,12 232
+14,16 232
+18,23 232
+25,31 232
+33,40 232
+3,41 232
+3,41 233
+359.30,33 234
+30,33 234
+30,33 235
+256.43,51 236
+43,51 236
+386.1,21 237
+387.0,1 238
+391.5,19 239
+23,34 239
+392.2,8 240
+395.14,15 241
+16,36 241
+40,58 241
+60,78 241
+397.1,18 242
+399.1,10 243
+1,10 243
+1,22 243
+1,22 244
+400.1,10 245
+1,10 245
+1,22 245
+1,22 246
+401.1,49 247
+9,12 247
+25,26 247
+28,47 247
+1,49 247
+1,49 247
+403.1,58 248
+1,10 248
+1,10 248
+16,20 248
+22,31 248
+22,31 248
+33,36 248
+38,47 248
+38,47 248
+38,57 248
+38,57 249
+1,58 248
+404.1,58 250
+1,10 250
+1,10 250
+16,20 250
+22,31 250
+22,31 250
+33,36 250
+38,47 250
+38,47 250
+38,57 250
+38,57 251
+1,58 250
+406.5,15 252
+408.15,30 253
+6,30 253
+409.3,27 254
+410.7,28 255
+30,39 256
+411.3,23 257
+412.14,26 258
+3,34 258
+413.39,52 259
+23,30 259
+23,53 259
+3,53 259
+3,53 260
+410.41,44 261
+41,44 261
+415.7,25 262
+27,36 263
+416.3,20 264
+417.3,28 265
+415.38,41 266
+38,41 266
+419.22,23 267
+37,58 267
+25,58 267
+61,68 267
+61,68 268
+422.2,18 269
+423.15,30 270
+6,30 270
+424.3,27 271
+425.12,23 272
+7,32 272
+34,46 273
+426.3,23 274
+427.14,26 275
+3,34 275
+428.39,52 276
+23,30 276
+23,53 276
+3,53 276
+3,53 277
+425.48,51 278
+48,51 278
+430.7,18 279
+20,30 280
+431.3,20 281
+432.3,28 282
+430.32,35 283
+32,35 283
+434.16,23 284
+26,35 284
+49,70 284
+37,70 284
+436.1,52 285
+1,10 285
+1,10 285
+16,22 285
+24,34 285
+24,34 285
+36,39 285
+47,48 285
+49,50 285
+1,52 285
+437.1,52 286
+1,10 286
+1,10 286
+16,22 286
+24,34 286
+24,34 286
+36,39 286
+47,48 286
+49,50 286
+1,52 286
+438.5,17 287
+5,12 287
+5,17 287
+5,17 287
+5,23 287
+439.2,15 288
+2,15 289
+441.2,38 290
+25,32 290
+34,37 290
+2,38 290
+2,38 290
+442.1,21 291
+443.0,1 292
+447.1,13 293
+448.5,19 294
+449.2,8 295
+451.1,18 296
+452.6,12 297
+14,23 298
+453.2,19 299
+454.7,13 300
+15,24 301
+455.19,25 302
+11,26 302
+3,31 302
+456.8,22 303
+8,22 303
+8,22 304
+8,22 305
+457.4,12 306
+459.3,10 307
+460.18,26 308
+10,27 308
+3,30 308
+461.23,31 309
+15,32 309
+15,40 309
+462.16,24 310
+8,25 310
+3,30 310
+463.20,34 311
+36,50 311
+464.17,24 312
+26,68 312
+26,33 312
+44,54 312
+56,66 312
+26,68 312
+26,68 312
+465.3,61 313
+13,20 313
+31,44 313
+46,59 313
+3,61 313
+3,61 313
+467.3,37 314
+11,12 314
+14,15 314
+17,22 314
+24,30 314
+32,36 314
+3,37 314
+3,37 315
+3,37 316
+454.26,29 317
+26,29 317
+452.25,28 318
+25,28 318
+470.1,21 319
+471.0,1 320
+476.5,13 321
+17,30 321
+477.9,10 322
+2,10 322
+478.5,13 323
+17,30 323
+479.9,10 324
+2,10 324
+481.1,21 325
+482.14,25 326
+6,26 326
+1,26 326
+484.5,19 327
+5,19 327
+24,29 327
+486.9,10 328
+2,10 328
+488.5,21 329
+489.9,10 330
+2,10 330
+491.5,19 331
+5,19 331
+493.17,26 332
+3,7 332
+494.6,15 333
+496.7,16 334
+497.11,12 335
+4,12 335
+498.18,29 336
+18,31 336
+10,32 336
+3,32 336
+499.20,31 337
+4,10 337
+500.9,27 338
+9,27 338
+31,51 338
+55,71 338
+75,88 338
+501.11,12 339
+4,12 339
+4,12 340
+4,12 341
+504.15,24 342
+7,24 342
+505.11,12 343
+4,12 343
+506.18,29 344
+18,33 344
+10,34 344
+3,34 344
+507.20,31 345
+4,10 345
+508.9,27 346
+9,27 346
+31,51 346
+55,71 346
+75,88 346
+509.11,12 347
+4,12 347
+4,12 348
+512.5,18 349
+22,35 349
+514.9,10 350
+2,10 350
+515.5,18 351
+22,35 351
+517.9,10 352
+2,10 352
+518.5,18 353
+22,35 353
+520.9,10 354
+2,10 354
+521.5,16 355
+20,31 355
+522.9,10 356
+2,10 356
+523.8,9 357
+1,9 357
+528.5,19 358
+529.9,12 359
+2,12 359
+531.14,15 360
+16,17 360
+21,39 360
+41,59 360
+532.1,26 361
+9,14 361
+19,25 361
+1,26 361
+1,26 361
+534.6,19 362
+6,11 362
+15,18 362
+6,19 362
+6,19 362
+6,19 362
+535.9,12 363
+2,12 363
+537.1,26 364
+538.1,26 365
+539.1,18 366
+541.1,8 367
+544.6,13 368
+15,22 369
+545.7,29 370
+22,24 370
+26,27 370
+7,29 370
+7,29 370
+7,29 370
+546.3,8 371
+544.24,28 372
+24,28 372
+548.5,9 373
+5,9 373
+5,14 373
+549.9,12 374
+2,12 374
+552.8,18 375
+553.7,29 376
+22,24 376
+26,27 376
+7,29 376
+7,29 376
+7,29 376
+7,29 377
+554.3,8 378
+555.15,24 379
+7,25 379
+2,25 379
+556.4,9 380
+2,17 380
+557.6,20 381
+6,20 381
+558.3,7 382
+3,7 383
+552.20,24 384
+20,24 384
+560.8,9 385
+1,9 385
+565.5,18 386
+22,34 386
+22,29 386
+22,34 386
+22,34 386
+22,39 386
+566.2,8 387
+568.5,18 388
+569.2,20 389
+570.0,1 390
+575.1,23 391
+576.5,21 392
+25,37 392
+25,37 392
+577.2,13 393
+2,13 394
+579.7,30 395
+2,30 395
+582.1,23 396
+583.5,21 397
+25,37 397
+25,37 397
+584.2,13 398
+2,13 399
+586.15,29 400
+7,30 400
+2,30 400
+588.16,18 401
+20,22 401
+589.16,18 402
+20,22 402
+591.5,17 403
+5,17 403
+592.26,29 404
+31,34 404
+36,39 404
+41,44 404
+3,6 404
+8,11 404
+13,16 404
+18,21 404
+18,21 405
+18,21 406
+18,21 407
+18,21 408
+594.5,17 409
+5,17 409
+595.2,10 410
+596.2,10 411
+599.5,19 412
+5,19 412
+24,29 412
+600.15,25 413
+601.9,12 414
+14,17 414
+19,22 414
+24,27 414
+1,28 414
+606.20,30 415
+9,30 415
+1,39 415
+1,39 416
+607.1,35 417
+17,21 417
+23,34 417
+1,35 417
+1,35 417
+608.5,14 418
+609.2,29 419
+610.2,30 420
+13,15 420
+17,20 420
+22,29 420
+2,30 420
+2,30 420
+2,30 421
+612.0,1 422
+616.9,26 423
+19,20 423
+22,25 423
+9,26 423
+9,26 423
+1,26 423
+618.2,16 424
+13,15 424
+2,16 424
+2,16 424
+619.2,13 425
+2,13 425
+628.1,25 426
+629.1,26 427
+630.1,18 428
+631.1,33 429
+13,17 429
+19,21 429
+23,32 429
+1,33 429
+632.1,18 430
+633.1,33 431
+13,16 431
+18,20 431
+22,32 431
+1,33 431
+634.1,19 432
+637.1,16 433
+638.1,14 434
+639.1,11 435
+640.1,23 436
+641.1,21 437
+642.1,12 438
+643.1,13 439
+645.8,12 440
+646.9,12 441
+9,12 441
+685.4,13 441
+4,13 441
+695.4,14 441
+4,14 441
+645.16,22 441
+16,22 441
+16,22 441
+16,22 441
+647.7,8 442
+7,8 442
+7,8 442
+649.3,12 443
+3,12 443
+21,30 443
+21,30 443
+3,32 443
+3,32 444
+3,32 445
+650.3,12 446
+3,12 446
+21,30 446
+21,30 446
+3,32 446
+3,32 447
+3,32 448
+651.3,29 449
+8,15 449
+22,28 449
+3,29 449
+3,29 449
+652.15,36 450
+15,16 450
+23,35 450
+15,36 450
+15,36 450
+3,72 450
+3,9 450
+3,9 450
+38,53 450
+38,53 450
+55,58 450
+60,71 450
+3,72 450
+653.7,17 451
+21,29 451
+654.4,34 452
+15,21 452
+23,30 452
+32,33 452
+4,34 452
+655.19,20 453
+21,22 453
+25,26 453
+27,28 453
+27,28 442
+658.7,15 454
+7,15 454
+7,20 454
+659.4,23 455
+4,23 442
+662.3,11 456
+663.7,18 457
+664.4,25 458
+4,25 442
+667.3,9 459
+3,9 442
+670.3,12 460
+3,12 460
+21,30 460
+21,30 460
+3,32 460
+3,32 461
+3,32 462
+671.3,12 463
+3,12 463
+21,30 463
+21,30 463
+3,32 463
+3,32 464
+3,32 465
+672.7,17 466
+21,29 466
+673.4,34 467
+15,21 467
+23,30 467
+32,33 467
+4,34 467
+674.3,15 468
+675.7,19 469
+23,40 469
+44,56 469
+61,78 469
+676.4,18 470
+4,18 471
+678.4,19 472
+679.4,18 473
+680.4,34 474
+15,21 474
+23,30 474
+32,33 474
+4,34 474
+681.4,16 475
+4,16 442
+4,16 441
+687.12,26 476
+2,30 476
+688.18,19 477
+20,21 477
+24,25 477
+26,27 477
+689.2,11 478
+2,11 478
+20,29 478
+20,29 478
+2,31 478
+2,31 479
+2,31 480
+690.2,11 481
+2,11 481
+20,29 481
+20,29 481
+2,31 481
+2,31 482
+2,31 483
+691.14,36 484
+14,22 484
+29,35 484
+14,36 484
+14,36 484
+2,71 484
+2,8 484
+2,8 484
+38,53 484
+38,53 484
+55,58 484
+66,67 484
+68,69 484
+2,71 484
+692.6,16 485
+20,28 485
+693.3,33 486
+14,20 486
+22,29 486
+31,32 486
+3,33 486
+3,33 441
+696.6,16 487
+697.15,23 488
+3,23 488
+3,23 488
+3,23 488
+698.3,40 489
+14,20 489
+22,29 489
+31,39 489
+3,40 489
+3,40 441
+701.1,15 490
+6,14 490
+1,15 490
+702.1,16 491
+6,15 491
+1,16 491
+703.0,1 492
+708.25,43 493
+38,42 493
+25,43 493
+25,43 493
+709.1,10 494
+1,10 494
+1,24 494
+1,24 495
+710.1,10 496
+1,10 496
+1,24 496
+1,24 497
+711.1,44 498
+1,10 498
+1,10 498
+16,21 498
+23,26 498
+28,31 498
+39,40 498
+41,42 498
+1,44 498
+712.1,44 499
+1,10 499
+1,10 499
+16,21 499
+23,26 499
+28,31 499
+39,40 499
+41,42 499
+1,44 499
+714.7,21 500
+7,21 500
+27,41 500
+27,41 500
+1,41 500
+1,41 500
+1,41 500
+716.5,13 501
+717.6,8 502
+718.18,26 503
+28,47 503
+28,58 503
+73,83 503
+73,78 503
+73,83 503
+73,83 503
+62,83 503
+85,104 503
+719.6,16 504
+720.3,48 505
+3,12 505
+3,12 505
+18,24 505
+26,29 505
+37,38 505
+39,40 505
+43,44 505
+46,47 505
+3,48 505
+3,48 505
+721.7,9 506
+722.4,48 507
+4,13 507
+4,13 507
+19,25 507
+27,30 507
+32,35 507
+43,44 507
+45,46 507
+4,48 507
+724.6,16 508
+725.3,48 509
+3,12 509
+3,12 509
+18,24 509
+26,29 509
+37,38 509
+39,40 509
+43,44 509
+46,47 509
+3,48 509
+3,48 509
+726.7,9 510
+727.4,48 511
+4,13 511
+4,13 511
+19,25 511
+27,30 511
+32,35 511
+43,44 511
+45,46 511
+4,48 511
+730.5,17 512
+5,12 512
+5,17 512
+5,17 512
+5,22 512
+731.2,17 513
+734.0,1 514
+733.2,40 515
+25,32 515
+34,39 515
+2,40 515
+2,40 515
+734.0,1 514
+738.5,24 516
+739.2,21 517
+740.5,24 518
+741.2,21 519
+742.5,24 520
+743.2,21 521
+744.5,24 522
+745.2,21 523
+746.8,10 524
+1,10 524
+751.1,21 525
+752.14,25 526
+6,26 526
+1,26 526
+753.1,8 527
+755.1,12 528
+756.13,22 529
+757.18,35 530
+37,54 530
+758.15,22 531
+24,66 531
+24,31 531
+42,52 531
+54,64 531
+24,66 531
+24,66 531
+759.1,28 532
+9,14 532
+21,27 532
+1,28 532
+1,28 532
+760.1,34 533
+9,14 533
+21,33 533
+1,34 533
+1,34 533
+762.1,59 534
+11,18 534
+29,42 534
+44,57 534
+1,59 534
+1,59 534
+763.1,28 535
+10,16 535
+21,27 535
+1,28 535
+1,28 535
+764.1,34 536
+10,16 536
+21,33 536
+1,34 536
+1,34 536
+766.6,10 537
+768.2,49 538
+2,8 538
+14,19 538
+21,34 538
+21,34 538
+36,39 538
+41,48 538
+2,49 538
+769.2,8 539
+773.1,35 540
+776.1,12 541
+777.5,15 542
+778.23,41 543
+36,40 543
+23,41 543
+23,41 543
+3,5 543
+7,9 543
+7,9 544
+7,9 545
+7,9 546
+7,9 547
+7,9 548
+780.23,41 549
+36,40 549
+23,41 549
+23,41 549
+13,15 549
+17,19 549
+17,19 550
+17,19 551
+17,19 552
+17,19 553
+782.1,26 554
+783.1,21 555
+785.1,40 556
+1,7 556
+13,18 556
+20,22 556
+24,27 556
+35,36 556
+37,38 556
+1,40 556
+787.7,21 557
+7,21 557
+27,41 557
+27,41 557
+1,41 557
+1,41 557
+1,41 557
+789.5,13 558
+790.6,8 559
+791.18,26 560
+28,47 560
+28,58 560
+73,83 560
+73,78 560
+73,83 560
+73,83 560
+62,83 560
+85,104 560
+792.6,14 561
+793.3,44 562
+3,9 562
+15,21 562
+23,25 562
+33,34 562
+35,36 562
+39,40 562
+42,43 562
+3,44 562
+3,44 562
+794.7,9 563
+795.4,44 564
+4,10 564
+16,22 564
+24,26 564
+28,31 564
+39,40 564
+41,42 564
+4,44 564
+798.1,25 565
+799.0,1 566
+14
+aSys->Dir 1:26.1,39.2 64
+11
+0:name:28.2,6 s
+4:uid:29.2,5 s
+8:gid:30.2,5 s
+12:muid:31.2,6 s
+16:qid:32.2,5 @1
+
+32:mode:33.2,6 i
+36:atime:34.2,7 i
+40:mtime:35.2,7 i
+48:length:36.2,8 B
+56:dtype:37.2,7 i
+60:dev:38.2,5 i
+aSys->Qid 11.1,16.2 16
+3
+0:path:13.2,6 B
+8:vers:14.2,6 i
+12:qtype:15.2,7 i
+aDraw->Chans 2:70.1,82.2 4
+1
+0:desc:72.2,6 i
+aDraw->Image 142.1,198.2 56
+8
+0:r:146.2,3 @4
+
+16:clipr:147.2,7 @4
+
+32:depth:148.2,7 i
+36:chans:149.2,7 @2
+
+40:repl:150.2,6 i
+44:display:151.2,9 R@6
+
+48:screen:152.2,8 R@7
+
+52:iname:153.2,7 s
+aDraw->Rect 116.1,139.2 16
+2
+0:min:118.2,5 @5
+
+8:max:119.2,5 @5
+
+aDraw->Point 99.1,113.2 8
+2
+0:x:101.2,3 i
+4:y:102.2,3 i
+aDraw->Display 201.1,230.2 20
+5
+0:image:203.2,7 R@3
+
+4:white:204.2,7 R@3
+
+8:black:205.2,7 R@3
+
+12:opaque:206.2,8 R@3
+
+16:transparent:207.2,13 R@3
+
+aDraw->Screen 249.1,263.2 16
+4
+0:id:251.2,4 i
+4:image:252.2,7 R@3
+
+8:fill:253.2,6 R@3
+
+12:display:254.2,9 R@6
+
+aDraw->Context 274.1,279.2 12
+3
+0:display:276.2,9 R@6
+
+4:screen:277.2,8 R@7
+
+8:wm:278.2,4 Ct8.2
+0:t0:15,21 s
+4:t1:15,21 Ct8.2
+0:t0:32,38 s
+4:t1:32,38 R@9
+
+
+
+aDraw->Wmcontext 282.1,291.2 28
+7
+0:kbd:284.2,5 Ci
+4:ptr:285.2,5 CR@10
+
+8:ctl:286.2,5 Cs
+12:wctl:287.2,6 Cs
+16:images:288.2,8 CR@3
+
+20:connfd:289.2,8 R@11
+
+24:ctxt:290.2,6 R@8
+
+aDraw->Pointer 266.1,271.2 16
+3
+0:buttons:268.2,9 i
+4:xy:269.2,4 @5
+
+12:msec:270.2,6 i
+aSys->FD 1:45.1,48.2 4
+1
+0:fd:47.2,4 i
+aDraw->Font 2:233.1,246.2 16
+4
+0:name:235.2,6 s
+4:height:236.2,8 i
+8:ascent:237.2,8 i
+12:display:238.2,9 R@6
+
+aCellinfo 0:27.0,31.1 20
+4
+0:font:28.1,5 R@12
+
+4:ch:29.1,3 i
+8:attr:5,9 i
+12:clipmod:30.1,8 t8.2
+0:t0:12,15 i
+4:t1:12,15 i
+
+17
+0:Init
+1
+32:c:67.5,6 R@8
+
+17
+36:disp:76.1,5 R@6
+
+40:black:78.1,6 i
+44:blue:79.1,5 i
+48:cyan:83.1,5 i
+52:green:82.1,6 i
+56:iblack:87.1,7 R@3
+
+60:iblue:88.1,6 R@3
+
+64:icyan:92.1,6 R@3
+
+68:igreen:91.1,7 R@3
+
+72:imagenta:90.1,9 R@3
+
+76:ired:89.1,5 R@3
+
+80:iwhite:94.1,7 R@3
+
+84:iyellow:93.1,8 R@3
+
+88:magenta:81.1,8 i
+92:red:80.1,4 i
+96:white:85.1,6 i
+100:yellow:84.1,7 i
+s140:Quit
+0
+0
+n151:Mode
+6
+32:r:118.5,6 @4
+
+48:w:21,22 i
+52:h:24,25 i
+56:ulh:27,30 i
+60:d:32,33 i
+64:fontpath:41,49 s
+6
+68:dx:130.2,4 i
+72:dy:6,8 i
+76:black:135.1,6 i
+80:y:179.6,7 i
+84:col0:180.2,6 i
+96:winr:141.1,5 @4
+
+t8.2
+0:t0:118.63,69 s
+4:t1:63,69 R@3
+
+313:Cursor
+1
+32:pt:193.7,9 @5
+
+0
+n320:Put
+5
+32:str:206.4,7 s
+36:pt:18,20 @5
+
+44:charset:30,37 i
+48:attr:39,43 i
+52:insert:45,51 i
+36
+56:col0:264.2,6 i
+60:f:215.1,2 R@12
+
+64:cell:216.1,5 AAt8.2
+0:t0:22.37,38 i
+4:t1:40,41 i
+
+68:newattr:362.3,10 i
+72:cellix:256.6,12 i
+76:y:257.2,3 i
+80:clipix:328.8,14 i
+84:cmix:333.4,8 i
+88:ix:240.8,10 i
+92:mask:269.3,7 i
+96:cix:279.4,7 i
+100:ix:278.8,10 i
+104:delimattr:270.3,12 i
+108:newstr:239.3,9 s
+112:s:358.2,3 s
+116:cx:377.4,6 i
+120:cy:8,10 i
+124:f2:378.3,5 R@12
+
+144:colbase:265.2,9 i
+148:srco:298.2,6 @5
+
+156:dstr:312.4,8 @4
+
+172:cellpos:380.3,10 @5
+
+180:drawpt:382.3,9 @5
+
+188:dsto:311.4,8 @5
+
+196:clipr:381.3,8 @4
+
+104:x:326.2,3 i
+80:mask:357.2,6 i
+84:attr2:356.2,7 i
+104:destx:303.9,14 i
+100:strix:327.7,12 i
+96:strlen:296.2,8 i
+92:gfxwidth:297.2,10 i
+100:srcx:304.5,9 i
+88:txty:255.1,5 i
+156:strr:343.2,6 @4
+
+188:txto:342.2,6 @5
+
+n609:Scroll
+2
+32:topline:389.7,14 i
+36:nlines:16,22 i
+11
+40:y:410.7,8 i
+44:srccol0:411.3,10 i
+48:dstcol0:412.3,10 i
+64:scr:395.1,4 @4
+
+80:blankr:394.1,7 @4
+
+96:dstr:401.1,5 @4
+
+44:y:425.7,8 i
+48:srccol0:426.3,10 i
+44:col0:416.3,7 i
+48:col0:431.3,7 i
+40:dstcol0:427.3,10 i
+n743:Reveal
+1
+32:show:445.7,11 i
+11
+36:x:454.7,8 i
+40:col0:453.2,6 i
+44:y:452.6,7 i
+48:attr:455.3,7 i
+52:s:459.3,4 s
+56:cx:461.4,6 i
+60:cy:8,10 i
+64:f:462.3,4 R@12
+
+72:cellpos:463.3,10 @5
+
+80:drawpt:465.3,9 @5
+
+88:clipr:464.3,8 @4
+
+n800:wordchar
+1
+32:pt:474.9,11 @5
+
+7
+40:col0:481.1,5 i
+44:lhmodx:499.4,10 i
+48:modx:493.3,7 i
+56:c:482.1,2 @13
+
+76:lhc:498.3,6 @13
+
+96:rhc:506.3,6 @13
+
+44:rhmodx:507.4,10 i
+i879:GetWord
+1
+32:gfxpt:526.8,13 @5
+
+7
+40:sx:544.6,8 i
+44:s:541.1,2 s
+48:y:538.1,2 i
+52:x:537.1,2 i
+56:col0:539.1,5 i
+68:c:555.2,3 @13
+
+88:scr:531.1,4 @4
+
+s941:Refresh
+0
+0
+n951:framecolours
+1
+32:attr:572.13,17 i
+8
+36:fg0:588.2,5 R@3
+
+40:fg1:7,10 R@3
+
+44:bg:581.1,3 R@3
+
+48:bg0:589.2,5 R@3
+
+52:bg1:7,10 R@3
+
+56:fg:574.1,3 R@3
+
+60:bgcol:582.1,6 i
+64:fgcol:575.1,6 i
+t16.4
+0:t0:572.28,37 R@3
+
+4:t1:28,37 R@3
+
+8:t2:28,37 R@3
+
+12:t3:28,37 R@3
+
+999:kill
+1
+32:pid:604.5,8 i
+3
+36:cmd:609.2,5 Ab
+40:fd:607.1,3 R@11
+
+44:prog:606.1,5 s
+n1018:timer
+3
+32:ms:614.6,8 i
+36:pc:16,18 Ci
+40:tick:20,24 Ci
+0
+n1030:Update
+1
+32:cmd:626.7,10 Ci
+15
+36:fgframe:643.1,8 i
+40:cursoron:638.1,9 i
+44:showcursor:637.1,11 i
+48:pc:630.1,3 Ci
+52:flashchan:641.1,10 Ci
+56:pcount:642.1,7 i
+60:cursortick:629.1,11 Ci
+64:flashtick:628.1,10 Ci
+68:nultick:640.1,8 Ci
+72:quit:639.1,5 i
+76:c:646.1,2 i
+80:cursorpid:634.1,10 i
+84:flashpid:632.1,9 i
+104:cursor:636.1,7 @5
+
+128:r:651.3,4 @4
+
+n1219:drawstr
+5
+32:s:706.8,9 s
+36:f:20,21 R@12
+
+40:clipr:34,39 @4
+
+56:drawpt:48,54 @5
+
+64:attr:64,68 i
+6
+68:fg0:708.2,5 R@3
+
+72:bg0:7,10 R@3
+
+76:fg1:12,15 R@3
+
+80:bg1:17,20 R@3
+
+84:ul:714.1,3 i
+100:ulrect:713.1,7 @4
+
+n1324:boundingrect
+2
+32:r1:736.13,15 @4
+
+48:r2:17,19 @4
+
+0
+@4
+1334:drawcursor
+3
+32:pt:749.11,13 @5
+
+40:srcix:23,28 i
+44:show:30,34 i
+15
+48:attr:773.1,5 i
+52:f:776.1,2 R@12
+
+56:fg:775.1,3 R@3
+
+60:bg:5,7 R@3
+
+64:s:753.1,2 s
+68:ul:787.1,3 i
+72:col0:751.1,5 i
+76:cx:756.2,4 i
+80:cy:6,8 i
+92:drawpt:762.1,7 @5
+
+100:clipr:758.1,6 @4
+
+116:cellpos:757.1,8 @5
+
+124:c:752.1,2 @13
+
+160:prevclipr:782.1,10 @4
+
+176:ulrect:786.1,7 @4
+
+n31
+88:blankrow:55.0,8 A@13
+
+92:bright:41.0,6 R@3
+
+104:cellH:24.0,5 AAt8.2
+0:t0:37,38 i
+4:t1:40,41 i
+
+108:cellS:22.0,5 AAt8.2
+0:t0:37,38 i
+4:t1:40,41 i
+
+112:cellW:23.0,5 AAt8.2
+0:t0:36,37 i
+4:t1:39,40 i
+
+116:cellWH:25.0,6 AAt8.2
+0:t0:37,38 i
+4:t1:40,41 i
+
+120:cellmap:44.0,7 A@13
+
+124:cellsize:50.0,8 @5
+
+132:colours:40.0,7 AR@3
+
+136:ctxt:57.0,4 R@8
+
+140:curpos:48.0,6 @5
+
+148:delims:53.0,6 i
+152:display:35.0,7 R@6
+
+156:draw:13.0,4 mDraw
+2:1.0,298.1 0
+
+160:font:0:58.0,4 R@12
+
+164:fontfr:63.0,6 R@12
+
+168:fontg1:62.0,6 R@12
+
+172:fonth:59.0,5 R@12
+
+176:fonts:61.0,5 R@12
+
+180:fontusa:64.0,7 R@12
+
+184:fontw:60.0,5 R@12
+
+192:frames:37.0,6 AR@3
+
+208:modbbox:54.0,7 @4
+
+224:ncols:46.0,5 i
+240:nrows:45.0,5 i
+248:showC:52.0,5 i
+252:sys:12.0,3 mSys
+1:4.0,160.1 0
+
+256:ulheight:0:47.0,8 i
+260:update:38.0,6 Ci
+272:window:36.0,6 R@3
+
+276:winoff:49.0,6 @5
+
diff --git a/appl/wm/minitel/miniterm.b b/appl/wm/minitel/miniterm.b
new file mode 100644
index 00000000..1c6ff759
--- /dev/null
+++ b/appl/wm/minitel/miniterm.b
@@ -0,0 +1,1187 @@
+#
+# Copyright © 1998 Vita Nuova Limited. All rights reserved.
+#
+
+implement Miniterm;
+
+include "sys.m";
+ sys: Sys;
+ print, fprint, sprint, read: import sys;
+include "draw.m";
+ draw: Draw;
+include "tk.m";
+ tk: Tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "miniterm.m";
+
+Miniterm: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+
+};
+
+pgrp: int = 0;
+debug: array of int = array[256] of {* => 0};
+stderr: ref Sys->FD;
+
+# Minitel terminal identification request - reply sequence
+TERMINALID1 := array [] of {
+ byte SOH,
+ byte 'S', byte 'X', byte '1', byte 'H', byte 'N',
+ byte EOT
+};
+TERMINALID2 := array [] of {
+ byte SOH,
+ byte 'C', byte 'g', byte '1',
+ byte EOT
+};
+
+# Minitel module identifiers
+Mscreen, Mmodem, Mkeyb, Msocket, Nmodule: con iota;
+Pscreen, Pmodem, Pkeyb, Psocket: con (1 << iota);
+Modname := array [Nmodule] of {
+ Mscreen => "S",
+ Mmodem => "M",
+ Mkeyb => "K",
+ Msocket => "C",
+ * => "?",
+};
+
+# attributes common to all modules
+Module: adt {
+ path: int; # bitset to connected modules
+ disabled: int;
+};
+
+# A BufChan queues events from the terminal to the modules
+BufChan: adt {
+ path: int; # id bit
+ ch: chan of ref Event; # set to `in' or `dummy' channel
+ ev: ref Event; # next event to send
+ in: chan of ref Event; # real channel for Events to the device
+ q: array of ref Event; # subsequent events to send
+};
+
+# holds state information for the minitel `protocol` (chapter 6)
+PState: adt {
+ state: int;
+ arg: array of int; # up to 3 arguments: X,Y,Z
+ nargs: int; # expected number of arguments
+ n: int; # progress
+ skip: int; # transparency; bytes to skip
+};
+PSstart, PSesc, PSarg: con iota; # states
+
+# Terminal display modes
+Videotex, Mixed, Ascii,
+
+# Connection methods
+Direct, Network,
+
+# Terminal connection states
+Local, Connecting, Online,
+
+# Special features
+Echo
+ : con (1 << iota);
+
+Terminal: adt {
+ in: chan of ref Event;
+ out: array of ref BufChan; # buffered output to the minitel modules
+
+ mode: int; # display mode
+ state: int; # connection state
+ spec: int; # special features
+ connect: int; # Direct, or Network
+ toplevel: ref Tk->Toplevel;
+ cmd: chan of string; # from Tk
+ proto: array of ref PState; # minitel protocol state
+ netaddr: string; # network address to dial
+ buttonsleft: int; # display buttons on the LHS (40 cols)
+ terminalid: array of byte; # ENQROM response
+ kbctl: chan of string; # softkeyboard control
+ kbmode: string; # softkeyboard mode
+
+ init: fn(t: self ref Terminal, toplevel: ref Tk->Toplevel, connect: int);
+ run: fn(t: self ref Terminal, done: chan of int);
+ reset: fn(t: self ref Terminal);
+ quit: fn(t: self ref Terminal);
+ layout: fn(t: self ref Terminal, cols: int);
+ setkbmode: fn(t: self ref Terminal, tmode: int);
+};
+
+include "arg.m";
+include "event.m";
+include "event.b";
+
+include "keyb.b";
+include "modem.b";
+include "socket.b";
+include "screen.b";
+
+K: ref Keyb;
+M: ref Modem;
+C: ref Socket;
+S: ref Screen;
+T: ref Terminal;
+Modules: array of ref Module;
+
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ s: string;
+ netaddr: string = nil;
+
+ sys = load Sys Sys->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ tkclient->init();
+ draw = load Draw Draw->PATH;
+ stderr = sys->fildes(2);
+ pgrp = sys->pctl(Sys->NEWPGRP|Sys->FORKNS, nil);
+
+ arg := load Arg Arg->PATH;
+ arg->init(argv);
+ arg->setusage("miniterm [netaddr]");
+ while((c := arg->opt()) != 0){
+ case c {
+ 'D' =>
+ s = arg->earg();
+ for(i := 0; i < len s; i++){
+ c = s[i];
+ if(c < len debug)
+ debug[c] += 1;
+ }
+ * =>
+ arg->usage();
+ }
+ }
+ argv = arg->argv();
+ if(len argv > 0) {
+ netaddr = hd argv;
+ argv = tl argv;
+ }
+
+ if(argv != nil)
+ arg->usage();
+ arg = nil;
+
+ # usage: miniterm modem[!init[!number]]
+ # or miniterm tcp!a.b.c.d
+ connect: int;
+ initstr := dialstr := string nil;
+ if(netaddr == nil)
+ netaddr = "tcp!pdc.minitelfr.com!513"; # gateway
+ (nil, words) := sys->tokenize(netaddr, "!");
+ if(len words == 0) {
+ connect = Direct;
+ words = "modem" :: nil;
+ }
+ if(hd words == "modem") {
+ connect = Direct;
+ words = tl words;
+ if(words != nil) {
+ initstr = hd words;
+ words = tl words;
+ if(words != nil)
+ dialstr = hd words;
+ }
+ if(initstr == "*")
+ initstr = nil;
+ if(dialstr == "*")
+ dialstr = nil;
+ } else {
+ connect = Network;
+ dialstr = netaddr;
+ }
+
+ T = ref Terminal;
+ K = ref Keyb;
+ M = ref Modem;
+ C = ref Socket;
+ S = ref Screen;
+ Modules = array [Nmodule] of {
+ Mscreen => S.m,
+ Mmodem => M.m,
+ Mkeyb => K.m,
+ Msocket => C.m,
+ };
+
+ toplevel := tk->toplevel(ctxt.display, "");
+ inittk(toplevel, connect);
+
+ T.init(toplevel, connect);
+ K.init(toplevel);
+ M.init(connect, initstr, dialstr);
+ C.init();
+ case connect {
+ Direct =>
+ S.init(ctxt, Rect((0,0), (640,425)), Rect((0,0), (640,425)));
+ Network =>
+ S.init(ctxt, Rect((0,0), (596,440)), Rect((0,50), (640,350)));
+ }
+
+ done := chan of int;
+ spawn K.run();
+ spawn M.run();
+ spawn C.run();
+ spawn S.run();
+ spawn T.run(done);
+ <- done;
+
+ # now tidy up
+ K.quit();
+ M.quit();
+ C.quit();
+ S.quit();
+ T.quit();
+}
+
+# the keyboard module handles keypresses and focus
+BTN40x25: con "-height 24 -font {/fonts/lucidasans/unicode.6.font}";
+BTNCTL: con "-width 60 -height 20 -font {/fonts/lucidasans/unicode.7.font}";
+BTNMAIN: con "-width 80 -height 20 -font {/fonts/lucidasans/unicode.7.font}";
+
+tkinitbs := array[] of {
+ "button .cxfin -text {Cx/Fin} -command {send keyb skey Connect}",
+ "button .done -text {Quitter} -command {send keyb skey Exit}",
+ "button .hup -text {Raccr.} -command {send term hangup}",
+ "button .somm -text {Somm.} -command {send keyb skey Index}",
+ "button .guide -text {Guide} -command {send keyb skey Guide}",
+ "button .annul -text {Annul.} -command {send keyb skey Cancel}",
+ "button .corr -text {Corr.} -command {send keyb skey Correct}",
+ "button .retour -text {Retour} -command {send keyb skey Previous}",
+ "button .suite -text {Suite} -command {send keyb skey Next}",
+ "button .repet -text {Répét.} -command {send keyb skey Repeat}",
+ "button .envoi -text {Envoi} -command {send keyb skey Send}",
+ "button .play -text {P} -command {send term play}",
+# "button .db -text {D} -command {send term debug}" ,
+ "button .kb -text {Clavier} -command {send term keyboard}",
+ "button .move -text {<-} -command {send term buttonsleft} " + BTN40x25,
+};
+
+tkinitdirect := array [] of {
+ ". configure -background black -height 480 -width 640",
+
+ ".cxfin configure " + BTNCTL,
+ ".hup configure " + BTNCTL,
+ ".done configure " + BTNCTL,
+ ".somm configure " + BTNMAIN,
+ ".guide configure " + BTNMAIN,
+ ".annul configure " + BTNMAIN,
+ ".corr configure " + BTNMAIN,
+ ".retour configure " + BTNMAIN,
+ ".suite configure " + BTNMAIN,
+ ".repet configure " + BTNMAIN,
+ ".envoi configure " + BTNMAIN,
+# ".play configure " + BTNCTL,
+# ".db configure " + BTNCTL,
+ ".kb configure " + BTNCTL,
+
+ "canvas .c -height 425 -width 640 -background black",
+ "bind .c <Configure> {send term resize}",
+ "bind .c <Key> {send keyb key %K}",
+ "bind .c <FocusIn> {send keyb focusin}",
+ "bind .c <FocusOut> {send keyb focusout}",
+ "bind .c <ButtonRelease> {focus .c; send keyb click %x %y}",
+ "frame .k -height 55 -width 640 -background black",
+ "pack propagate .k no",
+ "frame .klhs -background black",
+ "frame .krhs -background black",
+ "frame .krows -background black",
+ "frame .k1 -background black",
+ "frame .k2 -background black",
+ "pack .cxfin -in .klhs -anchor w -pady 4",
+ "pack .hup -in .klhs -anchor w",
+ "pack .somm .annul .retour .repet -in .k1 -side left -padx 2",
+ "pack .guide .corr .suite .envoi -in .k2 -side left -padx 2",
+ "pack .kb -in .krhs -anchor e -pady 4",
+ "pack .done -in .krhs -anchor e",
+ "pack .k1 -in .krows -pady 4",
+ "pack .k2 -in .krows",
+ "pack .klhs .krows .krhs -in .k -side left -expand 1 -fill x",
+ "pack .c .k",
+ "focus .c",
+ "update",
+};
+
+tkinitip := array [] of {
+ ". configure -background black -height 440 -width 640",
+
+ # ip 40x25 mode support
+ "canvas .c40 -height 440 -width 596 -background black",
+ "bind .c40 <Configure> {send term resize}",
+ "bind .c40 <Key> {send keyb key %K}",
+ "bind .c40 <FocusIn> {send keyb focusin}",
+ "bind .c40 <FocusOut> {send keyb focusout}",
+ "bind .c40 <ButtonRelease> {focus .c40; send keyb click %x %y}",
+ "frame .k -height 427 -width 44 -background black",
+ "frame .gap1 -background black",
+ "frame .gap2 -background black",
+ "pack propagate .k no",
+
+ # ip 80x25 mode support
+ "frame .padtop -height 50",
+ "canvas .c80 -height 300 -width 640 -background black",
+ "bind .c80 <Configure> {send term resize}",
+ "bind .c80 <Key> {send keyb key %K}",
+ "bind .c80 <FocusIn> {send keyb focusin}",
+ "bind .c80 <FocusOut> {send keyb focusout}",
+ "bind .c80 <ButtonRelease> {focus .c80; send keyb click %x %y}",
+ "frame .k80 -height 90 -width 640 -background black",
+ "pack propagate .k80 no",
+ "frame .klhs -background black",
+ "frame .krows -background black",
+ "frame .krow1 -background black",
+ "frame .krow2 -background black",
+ "frame .krhs -background black",
+ "pack .krow1 .krow2 -in .krows -pady 2",
+ "pack .klhs -in .k80 -side left",
+ "pack .krows -in .k80 -side left -expand 1",
+ "pack .krhs -in .k80 -side left",
+};
+
+tkip40x25show := array [] of {
+ ".cxfin configure " + BTN40x25,
+ ".hup configure " + BTN40x25,
+ ".done configure " + BTN40x25,
+ ".somm configure " + BTN40x25,
+ ".guide configure " + BTN40x25,
+ ".annul configure " + BTN40x25,
+ ".corr configure " + BTN40x25,
+ ".retour configure " + BTN40x25,
+ ".suite configure " + BTN40x25,
+ ".repet configure " + BTN40x25,
+ ".envoi configure " + BTN40x25,
+ ".play configure " + BTN40x25,
+# ".db configure " + BTN40x25,
+ ".kb configure " + BTN40x25,
+ "pack .cxfin -in .k -side top -fill x",
+ "pack .gap1 -in .k -side top -expand 1",
+ "pack .guide .repet .somm .annul .corr .retour .suite .envoi -in .k -side top -fill x",
+ "pack .gap2 -in .k -side top -expand 1",
+ "pack .done .hup .kb .move -in .k -side bottom -pady 2 -fill x",
+# "pack .db -in .k -side bottom",
+};
+
+tkip40x25lhs := array [] of {
+ ".move configure -text {->} -command {send term buttonsright}",
+ "pack .k .c40 -side left",
+ "focus .c40",
+ "update",
+};
+
+tkip40x25rhs := array [] of {
+ ".move configure -text {<-} -command {send term buttonsleft}",
+ "pack .c40 .k -side left",
+ "focus .c40",
+ "update",
+};
+
+tkip40x25hide := array [] of {
+ "pack forget .k .c40",
+};
+
+tkip80x25show := array [] of {
+ ".cxfin configure " + BTNCTL,
+ ".hup configure " + BTNCTL,
+ ".done configure " + BTNCTL,
+ ".somm configure " + BTNMAIN,
+ ".guide configure " + BTNMAIN,
+ ".annul configure " + BTNMAIN,
+ ".corr configure " + BTNMAIN,
+ ".retour configure " + BTNMAIN,
+ ".suite configure " + BTNMAIN,
+ ".repet configure " + BTNMAIN,
+ ".envoi configure " + BTNMAIN,
+# ".play configure " + BTNCTL,
+# ".db configure " + BTNCTL,
+ ".kb configure " + BTNCTL,
+
+ "pack .cxfin .hup -in .klhs -anchor w -pady 2",
+ "pack .somm .annul .retour .repet -in .krow1 -side left -padx 2",
+ "pack .guide .corr .suite .envoi -in .krow2 -side left -padx 2",
+ "pack .done .kb -in .krhs -anchor e -pady 2",
+ "pack .padtop .c80 .k80 -side top",
+ "focus .c80",
+ "update",
+};
+
+tkip80x25hide := array [] of {
+ "pack forget .padtop .c80 .k80",
+};
+
+inittk(toplevel: ref Tk->Toplevel, connect: int)
+{
+ tkcmds(toplevel, tkinitbs);
+ if(connect == Direct)
+ tkcmds(toplevel, tkinitdirect);
+ else
+ tkcmds(toplevel, tkinitip);
+}
+
+Terminal.layout(t: self ref Terminal, cols: int)
+{
+ if(t.connect == Direct)
+ return;
+ if(cols == 80) {
+ tkcmds(t.toplevel, tkip40x25hide);
+ tkcmds(t.toplevel, tkip80x25show);
+ } else {
+ tkcmds(t.toplevel, tkip80x25hide);
+ tkcmds(t.toplevel, tkip40x25show);
+ if (t.buttonsleft)
+ tkcmds(t.toplevel, tkip40x25lhs);
+ else
+ tkcmds(t.toplevel, tkip40x25rhs);
+ }
+}
+
+Terminal.init(t: self ref Terminal, toplevel: ref Tk->Toplevel, connect: int)
+{
+ t.in = chan of ref Event;
+ t.proto = array [Nmodule] of {
+ Mscreen => ref PState(PSstart, array [] of {0,0,0}, 0, 0, 0),
+ Mmodem => ref PState(PSstart, array [] of {0,0,0}, 0, 0, 0),
+ Mkeyb => ref PState(PSstart, array [] of {0,0,0}, 0, 0, 0),
+ Msocket => ref PState(PSstart, array [] of {0,0,0}, 0, 0, 0),
+ };
+
+ t.toplevel = toplevel;
+ t.connect = connect;
+ if (t.connect == Direct)
+ t.spec = 0;
+ else
+ t.spec = Echo;
+ t.cmd = chan of string;
+ tk->namechan(t.toplevel, t.cmd, "term"); # Tk -> terminal
+ t.state = Local;
+ t.buttonsleft = 0;
+ t.kbctl = nil;
+ t.kbmode = "minitel";
+ t.reset();
+}
+
+Terminal.reset(t: self ref Terminal)
+{
+ t.mode = Videotex;
+}
+
+Terminal.run(t: self ref Terminal, done: chan of int)
+{
+ t.out = array [Nmodule] of {
+ Mscreen => ref BufChan(Pscreen, nil, nil, S.in, array [0] of ref Event),
+ Mmodem => ref BufChan(Pmodem, nil, nil, M.in, array [0] of ref Event),
+ Mkeyb => ref BufChan(Pkeyb, nil, nil, K.in, array [0] of ref Event),
+ Msocket => ref BufChan(Psocket, nil, nil, C.in, array [0] of ref Event),
+ };
+ modcount := Nmodule;
+ if(debug['P'])
+ post(ref Event.Eproto(Pmodem, 0, Cplay, "play", 0,0,0));
+Evloop:
+ for(;;) {
+ ev: ref Event = nil;
+ post(nil);
+ alt {
+ # recv message from one of the modules
+ ev =<- t.in =>
+ if(ev == nil) { # modules ack Equit with nil
+ if(--modcount == 0)
+ break Evloop;
+ continue;
+ }
+ pick e := ev {
+ Equit => # close modules down
+ post(ref Event.Equit(Pscreen|Pmodem|Pkeyb|Psocket,0));
+ continue;
+ }
+
+ eva := protocol(ev);
+ while(len eva > 0) {
+ post(eva[0]);
+ eva = eva[1:];
+ }
+
+ # send message to `plumbed' modules
+ t.out[Mscreen].ch <- = t.out[Mscreen].ev =>
+ t.out[Mscreen].ev = nil;
+ t.out[Mmodem].ch <- = t.out[Mmodem].ev =>
+ t.out[Mmodem].ev = nil;
+ t.out[Mkeyb].ch <- = t.out[Mkeyb].ev =>
+ t.out[Mkeyb].ev = nil;
+ t.out[Msocket].ch <- = t.out[Msocket].ev =>
+ t.out[Msocket].ev = nil;
+
+ # recv message from Tk
+ cmd := <- t.cmd =>
+ (n, word) := sys->tokenize(cmd, " ");
+ if(n >0)
+ case hd word {
+ "resize" => ;
+ "play" => # for testing only
+ post(ref Event.Eproto(Pmodem, Mmodem, Cplay, "play", 0,0,0));
+ "keyboard" =>
+ if (t.kbctl == nil) {
+ e: string;
+ (e, t.kbctl) = kb(t);
+ if (e != nil)
+ sys->print("cannot start keyboard: %s\n", e);
+ } else
+ t.kbctl <- = "click";
+ "hangup" =>
+ if(T.state == Online || T.state == Connecting)
+ post(ref Event.Eproto(Pmodem, 0, Cdisconnect, "",0,0,0));
+ "buttonsleft" =>
+ tkcmds(t.toplevel, tkip40x25lhs);
+ t.buttonsleft = 1;
+ if(S.image != nil)
+ draw->(S.image.origin)(Point(0,0), Point(44, 0));
+ if (t.kbctl != nil)
+ t.kbctl <- = "fg";
+ "buttonsright" =>
+ tkcmds(t.toplevel, tkip40x25rhs);
+ t.buttonsleft = 0;
+ if(S.image != nil)
+ draw->(S.image.origin)(Point(0,0), Point(0, 0));
+ if (t.kbctl != nil)
+ t.kbctl <- = "fg";
+ "debug" =>
+ debug['s'] ^= 1;
+ debug['m'] ^= 1;
+ }
+ }
+
+ }
+ if (t.kbctl != nil)
+ t.kbctl <- = "quit";
+ t.kbctl = nil;
+ done <-= 0;
+}
+
+kb(t: ref Terminal): (string, chan of string)
+{
+ s := chan of string;
+ spawn dokb(t, s);
+ e := <- s;
+ if (e != nil)
+ return (e, nil);
+ return (nil, s);
+}
+
+Terminal.setkbmode(t: self ref Terminal, tmode: int)
+{
+ case tmode {
+ Videotex =>
+ t.kbmode = "minitel";
+ Mixed or Ascii =>
+ t.kbmode = "standard";
+ }
+ if(t.kbctl != nil) {
+ t.kbctl <-= "mode";
+ t.kbctl <-= "fg";
+ }
+}
+
+include "swkeyb.m";
+dokb(t: ref Terminal, c: chan of string)
+{
+ keyboard := load Keyboard Keyboard->PATH;
+ if (keyboard == nil) {
+ c <- = "cannot load keyboard";
+ return;
+ }
+
+ kbctl := chan of string;
+ (top, m) := tkclient->toplevel(S.ctxt, "", "Keyboard", 0);
+ tk->cmd(top, "pack .Wm_t -fill x");
+ tk->cmd(top, "update");
+ keyboard->chaninit(top, S.ctxt, ".keys", kbctl);
+ tk->cmd(top, "pack .keys");
+
+ kbctl <-= t.kbmode ;
+
+ kbon := 1;
+ c <- = nil; # all ok, we are now ready to accept commands
+
+ for (;;) alt {
+ mcmd := <- m =>
+ if (mcmd == "exit") {
+ if (kbon) {
+ tk->cmd(top, ". unmap; update");
+ kbon = 0;
+ }
+ } else
+ tkclient->wmctl(top, mcmd);
+ kbcmd := <- c =>
+ case kbcmd {
+ "fg" =>
+ if (kbon)
+ tk->cmd(top, "raise .;update");
+ "click" =>
+ if (kbon) {
+ tk->cmd(top, ". unmap; update");
+ kbon = 0;
+ } else {
+ tk->cmd(top, ". map; raise .");
+ kbon = 1;
+ }
+ "mode" =>
+ kbctl <- = t.kbmode;
+ "quit" =>
+ kbctl <- = "kill";
+ top = nil;
+ # ensure tkclient not blocked on a send to us (probably overkill!)
+ alt {
+ <- m => ;
+ * => ;
+ }
+ return;
+ }
+ }
+}
+
+
+Terminal.quit(nil: self ref Terminal)
+{
+}
+
+# a minitel module sends an event to the terminal for routing
+send(e: ref Event)
+{
+ if(debug['e'] && e != nil)
+ fprint(stderr, "%s: -> %s\n", Modname[e.from], e.str());
+ T.in <- = e;
+}
+
+# post an event to one or more modules
+post(e: ref Event)
+{
+ i,l: int;
+ for(i=0; i<Nmodule; i++) {
+ # `ev' is cleared once sent, reload it from the front of `q'
+ b: ref BufChan = T.out[i];
+ l = len b.q;
+ if(b.ev == nil && l != 0) {
+ b.ev = b.q[0];
+ na := array [l-1] of ref Event;
+ na[0:] = b.q[1:];
+ b.q = na;
+ }
+ if (e != nil) {
+ if(e.path & b.path) {
+ if(debug['e'] > 0) {
+ pick de := e {
+ * =>
+ fprint(stderr, "[%s<-%s] %s\n", Modname[i], Modname[e.from], e.str());
+ }
+ }
+ if(b.ev == nil) # nothing queued
+ b.ev = e;
+ else { # enqueue it
+ l = len b.q;
+ na := array [l+1] of ref Event;
+ na[0:] = b.q[0:];
+ na[l] = e;
+ b.q = na;
+ }
+ }
+ }
+ # set a dummy channel if nothing to send
+ if(b.ev == nil)
+ b.ch = chan of ref Event;
+ else
+ b.ch = b.in;
+ }
+}
+
+# run the terminal protocol
+protocol(ev: ref Event): array of ref Event
+{
+ # Introduced by the following sequences, the minitel protocol can be
+ # embedded in any normal data sequence
+ # ESC,0x39,X
+ # ESC,0x3a,X,Y
+ # ESC,0x3b,X,Y,Z
+ # ESC,0x61 - cursor position request
+
+ ea := array [0] of ref Event; # resulting sequence of Events
+ changed := 0; # if set, results are found in `ea'
+
+ pick e := ev {
+ Edata =>
+ d0 := 0; # offset of start of last data sequence
+ p := T.proto[e.from];
+ for(i:=0; i<len e.data; i++) {
+ ch := int e.data[i];
+# if(debug['p'])
+# fprint(stderr, "protocol: [%s] %d %ux (%c)\n", Modname[e.from], p.state, ch, ch);
+ if(p.skip > 0) { # in transparency mode
+ if(ch == 0 && e.from == Mmodem) # 5.0
+ continue;
+ p.skip--;
+ continue;
+ }
+ case p.state {
+ PSstart =>
+ if(ch == ESC) {
+ p.state = PSesc;
+ changed = 1;
+ if(i > d0)
+ ea = eappend(ea, ref Event.Edata(e.path, e.from, e.data[d0:i]));
+ d0 = i+1;
+ }
+ PSesc =>
+ p.state = PSarg;
+ p.n = 0;
+ d0 = i+1;
+ changed = 1;
+ if(ch >= 16r39 && ch <= 16r3b) #PRO1,2,3
+ p.nargs = ch - 16r39 + 1;
+ else if(ch == 16r61) # cursor position request
+ p.nargs = 0;
+ else if(ch == ESC) {
+ ea = eappend(ea, ref Event.Edata(e.path, e.from, array [] of { byte ESC }));
+ p.state = PSesc;
+ } else {
+ # false alarm, restore as data
+ ea = eappend(ea, ref Event.Edata(e.path, e.from, array [] of { byte ESC, byte ch }));
+ p.state = PSstart;
+ }
+ PSarg => # expect `nargs' bytes
+ d0 = i+1;
+ changed =1;
+ if(p.n < p.nargs)
+ p.arg[p.n++] = ch;
+ if(p.n == p.nargs) {
+ # got complete protocol sequence
+ pe := proto(e.from, p);
+ if(pe != nil)
+ ea = eappend(ea, pe);
+ p.state = PSstart;
+ }
+ }
+ }
+ if(changed) { # some interpretation, results in `ea'
+ if(i > d0)
+ ea = eappend(ea, ref Event.Edata(e.path, e.from, e.data[d0:i]));
+ return ea;
+ }
+ ev = e;
+ return array [] of {ev};
+ }
+ return array [] of {ev};
+}
+
+# append to an Event array
+eappend(ea: array of ref Event, e: ref Event): array of ref Event
+{
+ l := len ea;
+ na := array [l+1] of ref Event;
+ na[0:] = ea[0:];
+ na[l] = e;
+ return na;
+}
+
+# act on a received protocol sequence
+# some sequences are handled here by the terminal and result in a posted reply
+# others are returned `inline' as Eproto events with the normal data stream.
+proto(from: int, p: ref PState): ref Event
+{
+ if(debug['p']) {
+ fprint(stderr, "PRO%d: %ux", p.nargs, p.arg[0]);
+ if(p.nargs > 1)
+ fprint(stderr, " %ux", p.arg[1]);
+ if(p.nargs > 2)
+ fprint(stderr, " %ux", p.arg[2]);
+ fprint(stderr, " (%s)\n", Modname[from]);
+ }
+ case p.nargs {
+ 0 => # cursor position request ESC 0x61
+ reply := array [] of { byte US, byte S.pos.y, byte S.pos.x };
+ post(ref Event.Edata(Pmodem, from, reply));
+ 1 =>
+ case p.arg[0] {
+ PROTOCOLSTATUS => ;
+ ENQROM => # identification request
+ post(ref Event.Edata(Pmodem, from, T.terminalid));
+ if(T.terminalid == TERMINALID1)
+ T.terminalid = TERMINALID2;
+ SETRAM1 or SETRAM2 => ;
+ FUNCTIONINGSTATUS => # 11.3
+ PRO2(Pmodem, from, REPFUNCTIONINGSTATUS, osb());
+ CONNECT => ;
+ DISCONNECT =>
+ return ref Event.Eproto(Pscreen, from, Cscreenoff, "",0,0,0);
+ RESET => # reset the minitel terminal
+ all := Pscreen|Pmodem|Pkeyb|Psocket;
+ post(ref Event.Eproto(all, from, Creset, "",0,0,0)); # check
+ T.reset();
+ reply := array [] of { byte SEP, byte 16r5E };
+ post(ref Event.Edata(Pmodem, from, reply));
+ }
+ 2 =>
+ case p.arg[0] {
+ TO => # request for module status
+ PRO3(Pmodem, from, FROM, p.arg[1], psb(p.arg[1]));
+ NOBROADCAST => ;
+ BROADCAST => ;
+ TRANSPARENCY => # transparency mode - skip bytes
+ p.skip = p.arg[1];
+ if(p.skip < 1 || p.skip > 127) # 5.0
+ p.skip = 0;
+ else {
+ reply := array [] of { byte SEP, byte 16r57 };
+ post(ref Event.Edata(Pmodem, from, reply));
+ }
+ KEYBOARDSTATUS =>
+ if(p.arg[1] == RxKeyb)
+ PRO3(Pmodem, from, REPKEYBOARDSTATUS, RxKeyb, kosb());
+ START =>
+ x := osb();
+ if(p.arg[1] == PROCEDURE)
+ x |= 16r04;
+ if(p.arg[1] == SCROLLING)
+ x |= 16r02;
+ PRO2(Pmodem, from, REPFUNCTIONINGSTATUS, x);
+ case p.arg[1] {
+ PROCEDURE => # activate error correction procedure
+ sys->print("activate error correction\n");
+ return ref Event.Eproto(Pmodem, from, Cstartecp, "",0,0,0);
+ SCROLLING => # set screen to scroll
+ return ref Event.Eproto(Pscreen, from, Cproto, "",START,SCROLLING,0);
+ LOWERCASE => # set keyb to invert case
+ return ref Event.Eproto(Pkeyb, from, Cproto, "",START,LOWERCASE,0);
+ }
+ STOP =>
+ x := osb();
+ if(p.arg[1] == SCROLLING)
+ x &= ~16r02;
+ PRO2(Pmodem, from, REPFUNCTIONINGSTATUS, osb());
+ case p.arg[1] {
+ PROCEDURE => # deactivate error correction procedure
+ sys->print("deactivate error correction\n");
+ return ref Event.Eproto(Pmodem, from, Cstopecp, "",0,0,0);
+ SCROLLING => # set screen to no scroll
+ return ref Event.Eproto(Pscreen, from, Cproto, "",STOP,SCROLLING,0);
+ LOWERCASE => # set keyb to not invert case
+ return ref Event.Eproto(Pkeyb, from, Cproto, "",STOP,LOWERCASE,0);
+ }
+ COPY => # copy screen to socket
+ # not implemented
+ ;
+ MIXED => # change video mode (12.1)
+ case p.arg[1] {
+ MIXED1 => # videotex -> mixed
+ reply := array [] of { byte SEP, byte 16r70 };
+ return ref Event.Eproto(Pscreen, from, Cproto, "",MIXED,MIXED1,0);
+ MIXED2 => # mixed -> videotex
+ reply := array [] of { byte SEP, byte 16r71 };
+ return ref Event.Eproto(Pscreen, from, Cproto, "",MIXED,MIXED2,0);
+ }
+ ASCII => # change video mode (12.2)
+ # TODO
+ ;
+ }
+ 3 =>
+ case p.arg[0] {
+ OFF or ON => # link, unlink, enable, disable
+ modcmd(p.arg[0], p.arg[1], p.arg[2]);
+ PRO3(Pmodem, from, FROM, p.arg[1], psb(TxCode(p.arg[1])));
+ START =>
+ case p.arg[1] {
+ RxKeyb => # keyboard mode
+ case p.arg[2] {
+ ETEN => # extended keyboard
+ K.spec |= Extend;
+ C0 => # cursor control key coding from col 0
+ K.spec |= C0keys;
+ }
+ PRO3(Pmodem, from, REPKEYBOARDSTATUS, RxKeyb, kosb());
+ }
+ STOP => # keyboard mode
+ case p.arg[1] {
+ RxKeyb => # keyboard mode
+ case p.arg[2] {
+ ETEN => # extended keyboard
+ K.spec &= ~Extend;
+ C0 => # cursor control key coding from col 0
+ K.spec &= ~C0keys;
+ }
+ PRO3(Pmodem, from, REPKEYBOARDSTATUS, RxKeyb, kosb());
+ }
+ }
+ }
+ return nil;
+}
+
+# post a PRO3 sequence to all modules on `path'
+PRO3(path, from, x, y, z: int)
+{
+ data := array [] of { byte ESC, byte 16r3b, byte x, byte y, byte z};
+ post(ref Event.Edata(path, from, data));
+}
+
+# post a PRO2 sequence to all modules on `path'
+PRO2(path, from, x, y: int)
+{
+ data := array [] of { byte ESC, byte 16r3a, byte x, byte y};
+ post(ref Event.Edata(path, from, data));
+}
+
+# post a PRO1 sequence to all modules on `path'
+PRO1(path, from, x: int)
+{
+ data := array [] of { byte ESC, byte 16r39, byte x};
+ post(ref Event.Edata(path, from, data));
+}
+
+# make or break links between modules, or enable and disable
+modcmd(cmd, from, targ: int)
+{
+ from = RxTx(from);
+ targ = RxTx(targ);
+ if(from == targ) # enable or disable module
+ if(cmd == ON)
+ Modules[from].disabled = 0;
+ else
+ Modules[from].disabled = 1;
+ else # modify path
+ if(cmd == ON)
+ Modules[from].path |= (1<<targ);
+ else
+ Modules[from].path &= ~(1<<targ);
+}
+
+# determine the path status byte (3.4)
+# if bit 3 of `code' is set then a receive path status byte is returned
+# otherwise a transmit path status byte
+psb(code: int): int
+{
+ this := RxTx(code);
+ b := 16r40; # bit 6 always set
+ if(code == RxCode(code)) { # want a receive path status byte
+ mask := (1<<this);
+ if(Modules[Mscreen].path & mask)
+ b |= 16r01;
+ if(Modules[Mkeyb].path & mask)
+ b |= 16r02;
+ if(Modules[Mmodem].path & mask)
+ b |= 16r04;
+ if(Modules[Msocket].path & mask)
+ b |= 16r08;
+ } else {
+ mod := Modules[this];
+ if(mod.path & Mscreen)
+ b |= 16r01;
+ if(mod.path & Mkeyb)
+ b |= 16r02;
+ if(mod.path & Mmodem)
+ b |= 16r04;
+ if(mod.path & Msocket)
+ b |= 16r08;
+ }
+# if(parity(b))
+# b ^= 16r80;
+ return b;
+}
+
+# convert `code' to a receive code by setting bit 3
+RxCode(code: int): int
+{
+ return (code | 16r08)&16rff;
+}
+
+# covert `code' to a send code by clearing bit 3
+TxCode(code: int): int
+{
+ return (code & ~16r08)&16rff;
+}
+
+# return 0 on even parity, 1 otherwise
+# only the bottom 8 bits are considered
+parity(b: int): int
+{
+ bits := 8;
+ p := 0;
+ while(bits-- > 0) {
+ if(b&1)
+ p ^= 1;
+ b >>= 1;
+ }
+ return p;
+}
+
+# convert Rx or Tx code to a module code
+RxTx(code: int): int
+{
+ rv := 0;
+ case code {
+ TxScreen or RxScreen => rv = Mscreen;
+ TxKeyb or RxKeyb => rv = Mkeyb;
+ TxModem or RxModem => rv = Mmodem;
+ TxSocket or RxSocket => rv = Msocket;
+ * =>
+ fatal("invalid module code");
+ }
+ return rv;
+}
+
+# generate an operating status byte (11.2)
+osb(): int
+{
+ b := 16r40;
+ if(S.cols == 80)
+ b |= 16r01;
+ if(S.spec & Scroll)
+ b |= 16r02;
+ if(M.spec & Ecp)
+ b |= 16r04;
+ if(K.spec & Invert)
+ b |= 16r08;
+# if(parity(b))
+# b ^= 16r80;
+ return b;
+}
+
+# generate a keyboard operating status byte (9.1.2)
+kosb(): int
+{
+ b := 16r40;
+ if(K.spec & Extend)
+ b |= 16r01;
+ if(K.spec & C0keys)
+ b |= 16r04;
+# if(parity(b))
+# b ^= 16r80;
+ return b;
+}
+
+hex(v, n: int): string
+{
+ return sprint("%.*ux", n, v);
+}
+
+tostr(ch: int): string
+{
+ str := "";
+ str[0] = ch;
+ return str;
+}
+
+toint(s: string, base: int): (int, string)
+{
+ if(base < 0 || base > 36)
+ return (0, s);
+
+ c := 0;
+ for(i := 0; i < len s; i++) {
+ c = s[i];
+ if(c != ' ' && c != '\t' && c != '\n')
+ break;
+ }
+
+ neg := 0;
+ if(c == '+' || c == '-') {
+ if(c == '-')
+ neg = 1;
+ i++;
+ }
+
+ ok := 0;
+ n := 0;
+ for(; i < len s; i++) {
+ c = s[i];
+ v := base;
+ case c {
+ 'a' to 'z' =>
+ v = c - 'a' + 10;
+ 'A' to 'Z' =>
+ v = c - 'A' + 10;
+ '0' to '9' =>
+ v = c - '0';
+ }
+ if(v >= base)
+ break;
+ ok = 1;
+ n = n * base + v;
+ }
+
+ if(!ok)
+ return (0, s);
+ if(neg)
+ n = -n;
+ return (n, s[i:]);
+}
+
+tolower(s: string): string
+{
+ r := s;
+ for(i := 0; i < len r; i++) {
+ c := r[i];
+ if(c >= int 'A' && c <= int 'Z')
+ r[i] = r[i] + (int 'a' - int 'A');
+ }
+ return r;
+}
+
+# duplicate `ch' exactly `n' times
+dup(ch, n: int): string
+{
+ str := "";
+ for(i:=0; i<n; i++)
+ str[i] = ch;
+ return str;
+}
+
+fatal(msg: string)
+{
+ fprint(stderr, "fatal: %s\n", msg);
+ exits(msg);
+}
+
+exits(s: string)
+{
+ if(s==nil);
+# raise "fail: miniterm " + s;
+ fd := sys->open("#p/" + string pgrp + "/ctl", sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "killgrp");
+ exit;
+}
+
+# Minitel byte MSB and LSB classification (p.87)
+MSB(ch: int): int
+{
+ return (ch&16r70)>>4;
+}
+LSB(ch: int): int
+{
+ return (ch&16r0f);
+}
+
+# Minitel character set classification (p.92)
+ISC0(ch: int): int
+{
+ msb := (ch&16r70)>>4;
+ return msb == 0 || msb == 1;
+}
+
+ISC1(ch: int): int
+{
+ return ch >= 16r40 && ch <= 16r5f;
+}
+
+ISG0(ch: int): int
+{
+ # 0x20 (space) and 0x7f (DEL) are not in G0
+ return ch > 16r20 && ch < 16r7f;
+}
+
+tkcmds(t: ref Tk->Toplevel, cmds: array of string)
+{
+ n := len cmds;
+ for (ix := 0; ix < n; ix++)
+ tk->cmd(t, cmds[ix]);
+}
diff --git a/appl/wm/minitel/miniterm.dis b/appl/wm/minitel/miniterm.dis
new file mode 100644
index 00000000..39c6ba5e
--- /dev/null
+++ b/appl/wm/minitel/miniterm.dis
Binary files differ
diff --git a/appl/wm/minitel/miniterm.m b/appl/wm/minitel/miniterm.m
new file mode 100644
index 00000000..e0345f81
--- /dev/null
+++ b/appl/wm/minitel/miniterm.m
@@ -0,0 +1,120 @@
+#
+# Copyright © 1998 Vita Nuova Limited. All rights reserved.
+#
+
+# Common control bytes
+NUL: con 16r00;
+SOH: con 16r01;
+EOT: con 16r04;
+ENQ: con 16r05;
+BEL: con 16r07;
+BS: con 16r08;
+HT: con 16r09;
+LF: con 16r0a;
+VT: con 16r0b;
+FF: con 16r0c;
+CR: con 16r0d;
+SO: con 16r0e;
+SI: con 16r0f;
+DLE: con 16r10;
+CON: con 16r11;
+XON: con 16r11;
+REP: con 16r12;
+SEP: con 16r13;
+XOFF: con 16r13;
+COFF: con 16r14;
+NACK: con 16r15;
+SYN: con 16r16;
+CAN: con 16r18;
+SS2: con 16r19;
+SUB: con 16r1a;
+ESC: con 16r1b;
+SS3: con 16r1d;
+RS: con 16r1e;
+US: con 16r1f;
+
+SP: con 16r20;
+DEL: con 16r7f;
+
+# Minitel Protocol - some are duplicated (chapter 6)
+ASCII: con 16r31;
+MIXED: con 16r32;
+ETEN: con 16r41;
+C0: con 16r43;
+SCROLLING: con 16r43;
+PROCEDURE: con 16r44;
+LOWERCASE: con 16r45;
+OFF: con 16r60;
+ON: con 16r61;
+TO: con 16r62;
+FROM: con 16r63;
+NOBROADCAST: con 16r64;
+BROADCAST: con 16r65;
+NONRETURN: con 16r64;
+RETURN: con 16r65;
+TRANSPARENCY: con 16r66;
+DISCONNECT: con 16r67;
+CONNECT: con 16r68;
+START: con 16r69;
+STOP: con 16r6a;
+KEYBOARDSTATUS: con 16r72;
+REPKEYBOARDSTATUS: con 16r73;
+FUNCTIONINGSTATUS: con 16r72;
+REPFUNCTIONINGSTATUS: con 16r73;
+EXCHANGERATESTATUS: con 16r74;
+REPEXCHANGERATESTATUS: con 16r75;
+PROTOCOLSTATUS: con 16r76;
+REPPROTOCOLSTATUS: con 16r77;
+SETRAM1: con 16r78;
+SETRAM2: con 16r79;
+ENQROM: con 16r7b;
+COPY: con 16r7c;
+ASCII1: con 16r7d;
+MIXED1: con 16r7d;
+MIXED2: con 16r7e;
+RESET: con 16r7f;
+
+# Module send and receive codes (chapter 6)
+TxScreen: con 16r50;
+TxKeyb: con 16r51;
+TxModem: con 16r52;
+TxSocket: con 16r53;
+RxScreen: con 16r58;
+RxKeyb: con 16r59;
+RxModem: con 16r5a;
+RxSocket: con 16r5b;
+
+# Internal Event.Eproto command constants
+Cplay, # for testing
+Cconnect, # e.s contains the address to dial
+Cdisconnect, #
+Crequestecp, # ask server to start ecp
+Creset, # reset module
+Cstartecp, # start error correction
+Cstopecp, # stop error correction
+Cproto, # minitel protocol
+Ccursor, # update screen cursor
+Cindicators, # update row 0 indicators
+
+# softmodem bug: Cscreenoff, Cscreenon
+Cscreenoff, # screen: ignore data
+Cscreenon, # screen: don't ignore data
+
+Clast
+ : con iota;
+
+# Special keys - hardware returned byte
+KupPC: con 16r0203; # pc emu
+KdownPC: con 16r0204; # pc emu
+Kup: con 16rE012;
+Kdown: con 16rE013;
+Kenter: con 16r000a;
+Kback: con 16r0008;
+Kesc: con 16r001b;
+KF1: con 16rE041;
+KF2: con 16rE042;
+KF3: con 16rE043;
+KF4: con 16rE044;
+KF13: con 16rE04D;
+
+
diff --git a/appl/wm/minitel/miniterm.sbl b/appl/wm/minitel/miniterm.sbl
new file mode 100644
index 00000000..a34fa8b1
--- /dev/null
+++ b/appl/wm/minitel/miniterm.sbl
@@ -0,0 +1,6810 @@
+limbo .sbl 2.1
+Miniterm
+15
+miniterm.b
+sys.m
+draw.m
+tk.m
+tkclient.m
+miniterm.m
+arg.m
+event.m
+event.b
+keyb.b
+modem.b
+socket.b
+screen.b
+mdisplay.m
+swkeyb.m
+5725
+8:7.1,9 0
+8.6,13 1
+6,13 1
+10.3,40 2
+14,27 2
+29,39 2
+3,40 2
+3,40 2
+11.7,11 3
+15,25 4
+13,25 4
+12.9,30 5
+9,30 5
+28,29 5
+17,26 5
+13,26 5
+9,30 5
+9,30 5
+9,36 5
+4,36 5
+4,36 6
+11.27,30 7
+27,30 7
+14.3,14 8
+3,14 1
+16.3,44 9
+14,31 9
+33,38 9
+40,43 9
+3,44 9
+3,44 9
+3,44 1
+18.8,9 10
+1,9 10
+9:27.1,25 11
+28.1,23 12
+29.1,38 13
+14,22 13
+24,29 13
+31,37 13
+1,38 13
+1,38 13
+1,38 14
+30.1,10 15
+1,2 15
+1,10 15
+31.0,1 16
+35.1,36 17
+18,32 17
+34,35 17
+1,36 17
+1,36 18
+36.0,1 19
+42.1,18 20
+43.1,20 21
+1,2 21
+11,19 21
+1,20 21
+46.1,24 22
+47.1,14 23
+1,2 23
+7,13 23
+1,14 23
+51.20,30 24
+2,30 24
+53.6,12 25
+54.3,10 26
+3,10 27
+56.3,8 28
+57.2,28 29
+2,3 29
+17,27 29
+17,23 29
+17,27 29
+8,27 29
+8,27 30
+2,28 29
+58.2,14 31
+59.6,17 32
+60.3,9 33
+62.2,23 34
+18,22 34
+2,23 34
+2,23 34
+64.7,11 35
+66.3,8 36
+68.6,16 37
+6,20 37
+69.23,33 38
+23,36 38
+4,37 38
+4,37 35
+71.3,14 39
+72.3,8 40
+73.2,21 35
+76.3,17 41
+3,17 35
+80.1,11 42
+1,2 42
+7,10 42
+1,11 42
+82.1,4 43
+1,4 43
+84.11,13 43
+11,13 43
+81.10,16 43
+10,16 43
+10,16 43
+10,16 43
+83.2,8 44
+85.6,17 45
+86.3,9 46
+92.1,26 47
+93.1,19 48
+94.1,26 49
+98.11,15 50
+11,15 50
+122.12,17 50
+12,17 50
+215.15,22 50
+15,22 50
+97.2,8 50
+2,8 50
+2,8 50
+2,8 50
+99.8,15 51
+8,15 51
+8,15 52
+101.4,9 53
+103.9,14 54
+9,14 54
+9,14 54
+9,14 54
+9,14 54
+105.5,14 55
+5,6 55
+5,14 55
+5,14 54
+107.10,14 56
+109.11,15 57
+111.7,23 58
+7,23 57
+7,23 56
+114.11,15 59
+116.7,24 60
+7,24 59
+7,24 56
+7,24 54
+119.9,14 61
+9,14 62
+9,14 50
+123.6,16 63
+6,20 63
+124.4,34 64
+11,17 64
+19,28 64
+30,33 64
+4,34 64
+4,34 64
+126.16,39 65
+30,33 65
+35,38 65
+16,39 65
+16,39 65
+4,5 65
+7,11 65
+7,11 66
+127.6,10 67
+128.9,16 68
+9,16 68
+130.19,40 69
+28,35 69
+25,35 69
+25,35 70
+37,39 69
+19,40 69
+19,40 69
+6,9 69
+6,9 71
+131.8,26 72
+132.6,23 73
+19,22 73
+6,23 73
+6,23 73
+133.10,18 74
+134.7,17 75
+135.6,19 76
+6,19 77
+6,19 78
+136.6,11 79
+138.5,23 80
+13,14 80
+19,22 80
+5,23 80
+5,23 80
+139.8,19 81
+140.6,50 82
+11,49 82
+15,49 82
+27,30 82
+27,35 82
+27,35 83
+37,42 82
+44,48 82
+11,49 82
+11,49 84
+6,50 82
+6,50 85
+6,50 68
+143.11,18 86
+8,18 86
+8,28 86
+8,28 87
+144.9,27 88
+145.7,24 89
+146.7,22 90
+148.9,26 91
+30,51 91
+149.7,31 92
+21,30 92
+7,31 92
+7,31 92
+150.10,20 93
+151.8,51 94
+13,50 94
+17,50 94
+29,32 94
+29,37 94
+29,37 95
+39,44 94
+46,49 94
+13,50 94
+13,50 96
+8,51 94
+152.8,51 97
+13,50 97
+17,50 97
+29,32 97
+29,37 97
+29,37 98
+39,44 97
+46,49 97
+13,50 97
+13,50 99
+8,51 97
+154.7,68 100
+12,67 100
+16,67 100
+29,35 100
+37,42 100
+44,55 100
+57,59 100
+61,62 100
+63,64 100
+65,66 100
+12,67 100
+12,67 101
+7,68 100
+7,68 102
+156.6,33 103
+11,32 103
+15,32 103
+27,28 103
+30,31 103
+11,32 103
+11,32 104
+6,33 103
+157.6,11 105
+159.8,26 106
+160.21,28 107
+18,28 107
+6,28 107
+6,28 108
+161.6,11 109
+163.13,20 110
+10,20 110
+10,20 110
+165.11,18 111
+167.10,30 112
+168.8,66 113
+13,65 113
+17,65 113
+30,36 113
+38,43 113
+45,53 113
+55,57 113
+59,60 113
+61,62 113
+63,64 113
+13,65 113
+13,65 114
+8,66 113
+8,66 115
+170.8,32 116
+171.8,35 117
+18,25 117
+27,34 117
+8,35 117
+8,35 111
+174.7,68 118
+12,67 118
+16,67 118
+29,35 118
+37,42 118
+44,55 118
+57,59 118
+61,62 118
+63,64 118
+65,66 118
+12,67 118
+12,67 119
+7,68 118
+7,68 111
+176.7,31 120
+21,30 120
+7,31 120
+7,31 120
+177.10,20 121
+178.8,51 122
+13,50 122
+17,50 122
+29,32 122
+29,37 122
+29,37 123
+39,44 122
+46,49 122
+13,50 122
+13,50 124
+8,51 122
+8,51 125
+8,51 111
+8,51 110
+181.6,31 126
+23,30 126
+20,30 126
+20,30 127
+6,31 126
+6,31 126
+182.9,19 128
+183.7,50 129
+12,49 129
+16,49 129
+28,31 129
+28,36 129
+28,36 130
+38,43 129
+45,48 129
+12,49 129
+12,49 131
+7,50 129
+7,50 132
+7,50 110
+186.17,24 133
+14,24 133
+5,24 133
+5,24 134
+187.20,27 135
+17,27 135
+14,27 135
+5,27 135
+5,27 136
+188.5,39 137
+33,34 137
+36,37 137
+5,39 137
+5,39 137
+189.8,19 138
+190.10,28 139
+191.7,24 140
+7,24 141
+7,24 142
+192.7,12 143
+194.10,26 144
+195.11,25 145
+20,24 145
+11,25 145
+11,25 145
+11,38 145
+11,38 146
+196.11,31 147
+197.9,67 148
+14,66 148
+18,66 148
+31,37 148
+39,44 148
+46,54 148
+56,58 148
+60,61 148
+62,63 148
+64,65 148
+14,66 148
+14,66 149
+9,67 148
+9,67 150
+199.9,33 151
+200.9,36 152
+19,26 152
+28,35 152
+9,36 152
+9,36 153
+9,36 154
+202.8,13 155
+205.6,25 156
+20,24 156
+6,25 156
+6,25 156
+206.9,19 157
+207.7,50 158
+12,49 158
+16,49 158
+28,31 158
+28,36 158
+28,36 159
+38,43 158
+45,48 158
+12,49 158
+12,49 160
+7,50 158
+7,50 161
+209.7,66 162
+12,65 162
+16,65 162
+28,31 162
+28,36 162
+28,36 163
+38,43 162
+45,63 162
+12,65 162
+12,65 164
+7,66 162
+210.12,60 165
+16,60 165
+28,31 165
+28,36 165
+28,36 166
+38,43 165
+45,59 165
+52,58 165
+45,59 165
+45,59 165
+7,61 165
+7,61 165
+7,61 167
+7,61 165
+7,61 168
+7,61 169
+7,61 68
+7,61 170
+7,61 171
+7,61 50
+216.3,20 172
+217.6,20 173
+218.4,23 174
+219.4,62 175
+9,61 175
+13,61 175
+26,32 175
+34,39 175
+41,49 175
+51,53 175
+55,56 175
+57,58 175
+59,60 175
+9,61 175
+9,61 176
+4,62 175
+4,62 177
+4,62 50
+223.1,10 178
+6,9 178
+1,10 178
+224.0,1 179
+232.1,20 180
+16,19 180
+1,20 180
+1,20 180
+233.5,15 181
+234.2,20 182
+16,19 182
+2,20 182
+2,20 182
+235.5,15 183
+236.10,13 184
+3,13 184
+3,13 185
+240.6,12 186
+6,12 186
+6,12 186
+6,12 186
+6,12 186
+242.5,15 187
+19,29 187
+243.10,49 188
+24,48 188
+36,47 188
+29,48 188
+24,48 188
+3,49 188
+244.5,15 189
+19,29 189
+245.10,48 190
+23,47 190
+35,46 190
+28,47 190
+23,47 190
+3,48 190
+247.5,15 191
+19,29 191
+33,43 191
+47,57 191
+248.10,32 192
+23,31 192
+23,31 192
+3,32 192
+252.4,14 193
+18,28 193
+253.9,31 194
+22,30 194
+22,30 194
+2,31 194
+256.4,16 195
+20,31 195
+257.7,13 196
+7,13 196
+7,13 196
+7,13 196
+7,13 196
+259.10,13 197
+3,13 197
+261.10,32 198
+23,31 198
+23,31 198
+3,32 198
+265.6,9 199
+266.1,6 199
+267.17,55 200
+31,41 200
+31,41 200
+43,53 200
+43,53 200
+10,55 200
+268.1,11 199
+274.5,17 201
+5,22 201
+26,43 201
+47,66 201
+275.0,34 202
+7,13 202
+15,33 202
+0,34 202
+0,34 202
+276.10,46 203
+24,32 203
+24,32 203
+34,44 203
+34,44 203
+3,46 203
+278.9,12 204
+2,12 204
+279.14,17 205
+7,17 205
+281.8,30 206
+21,29 206
+21,29 206
+1,30 206
+286.4,10 207
+287.0,1 208
+291.1,15 209
+13,14 209
+1,15 209
+1,15 209
+292.6,7 210
+294.33,42 211
+26,42 211
+295.31,37 212
+24,37 212
+296.36,44 213
+29,44 213
+298.16,23 214
+9,23 214
+299.22,29 215
+15,29 215
+300.37,46 216
+30,46 216
+302.16,24 217
+9,24 217
+303.30,36 218
+23,36 218
+305.16,26 219
+9,26 219
+307.8,9 220
+1,9 220
+313.1,8 221
+314.1,14 222
+315.4,15 223
+19,27 223
+19,32 223
+316.9,12 224
+2,12 224
+317.4,11 225
+4,18 225
+318.2,14 226
+319.2,17 227
+2,17 227
+321.1,22 228
+17,21 228
+1,22 228
+1,22 228
+322.6,10 229
+323.15,25 230
+15,25 229
+324.14,24 231
+14,24 229
+325.14,24 232
+14,24 229
+326.13,23 233
+13,23 229
+327.13,23 234
+13,23 229
+328.15,25 235
+15,25 229
+329.14,24 236
+14,24 229
+330.12,22 237
+12,22 229
+331.16,26 238
+16,26 229
+333.4,6 239
+334.5,13 240
+335.10,52 241
+24,32 241
+24,32 241
+34,42 241
+34,42 241
+44,51 241
+44,51 241
+3,52 241
+337.10,42 242
+24,32 242
+24,32 242
+34,41 242
+34,41 242
+3,42 242
+339.9,12 243
+2,12 243
+345.6,9 244
+347.8,18 245
+2,18 245
+349.9,15 246
+2,15 246
+351.9,15 247
+2,15 247
+353.9,18 248
+2,18 248
+355.9,17 249
+2,17 249
+357.9,16 250
+2,16 250
+359.9,18 251
+2,18 251
+361.9,17 252
+2,17 252
+363.9,16 253
+2,16 253
+365.9,12 254
+2,12 254
+10:51.1,28 255
+18,24 255
+26,27 255
+1,28 255
+1,28 255
+52.5,11 256
+13,18 257
+53.7,37 258
+19,26 258
+32,36 258
+28,36 258
+7,37 258
+7,37 258
+2,37 258
+2,37 259
+52.20,23 260
+20,23 260
+54.8,9 261
+1,9 261
+59.1,28 262
+60.5,11 263
+13,20 264
+61.5,14 265
+12,13 265
+5,14 265
+5,14 265
+5,14 265
+62.3,12 266
+20,31 266
+3,31 266
+3,31 267
+64.3,12 268
+3,21 268
+60.22,25 269
+22,25 269
+65.1,25 270
+66.1,20 271
+67.1,18 272
+68.1,20 273
+69.1,20 274
+70.1,10 275
+71.1,11 276
+72.1,10 277
+73.1,14 278
+74.1,15 279
+75.1,16 280
+76.1,27 281
+77.1,29 282
+78.1,10 283
+1,2 283
+1,10 283
+79.0,1 284
+83.1,29 285
+18,25 285
+27,28 285
+1,29 285
+1,29 286
+84.0,1 287
+88.4,20 288
+89.2,61 289
+7,60 289
+11,60 289
+24,30 289
+32,38 289
+40,48 289
+50,52 289
+54,55 289
+56,57 289
+58,59 289
+7,60 289
+7,60 290
+2,61 289
+93.11,15 291
+11,15 291
+192.10,14 291
+10,14 291
+92.2,8 291
+2,8 291
+2,8 291
+2,8 291
+94.8,15 292
+8,15 292
+8,15 293
+96.4,9 294
+98.7,17 295
+7,21 295
+99.37,44 296
+37,38 296
+37,44 296
+37,44 296
+5,45 296
+12,18 296
+20,35 296
+20,35 296
+20,35 297
+5,45 296
+5,45 296
+100.4,19 298
+4,5 298
+12,18 298
+4,19 298
+4,19 298
+101.7,23 299
+27,40 299
+27,40 299
+102.8,23 300
+103.6,64 301
+11,63 301
+15,63 301
+28,35 301
+37,42 301
+44,51 301
+53,55 301
+57,58 301
+59,60 301
+61,62 301
+11,63 301
+11,63 302
+6,64 301
+104.6,51 303
+11,50 303
+15,50 303
+27,34 303
+36,41 303
+43,49 303
+11,50 303
+11,50 304
+6,51 303
+6,51 292
+108.9,14 305
+9,14 305
+9,14 305
+110.5,14 306
+5,6 306
+5,14 306
+5,14 305
+112.8,18 307
+113.6,11 308
+114.5,20 309
+115.5,25 310
+116.5,67 311
+10,66 311
+14,66 311
+27,34 311
+36,42 311
+44,55 311
+57,59 311
+60,61 311
+62,63 311
+64,65 311
+10,66 311
+10,66 312
+5,67 311
+118.10,19 313
+120.6,38 314
+6,7 314
+12,30 314
+12,37 314
+12,37 315
+6,38 314
+121.6,25 316
+122.9,26 317
+19,20 317
+22,25 317
+9,26 317
+9,26 317
+9,30 317
+123.7,30 318
+7,8 318
+13,29 318
+7,30 318
+124.7,22 319
+125.7,69 320
+12,68 320
+16,68 320
+29,36 320
+38,44 320
+46,57 320
+59,61 320
+62,63 320
+64,65 320
+66,67 320
+12,68 320
+12,68 321
+7,69 320
+7,69 322
+126.7,12 323
+128.6,25 324
+129.6,18 325
+130.6,16 326
+14,15 326
+6,16 326
+131.6,32 327
+6,32 328
+6,32 313
+133.6,39 329
+6,7 329
+12,38 329
+6,39 329
+134.9,19 330
+9,23 330
+27,37 330
+27,41 330
+135.7,42 331
+18,30 331
+32,41 331
+7,42 331
+7,42 331
+136.18,42 332
+28,37 332
+39,41 332
+18,42 332
+18,42 332
+137.10,18 333
+138.7,37 334
+7,8 334
+13,36 334
+7,37 334
+139.7,22 335
+140.7,69 336
+12,68 336
+16,68 336
+29,36 336
+38,44 336
+46,57 336
+59,61 336
+62,63 336
+64,65 336
+66,67 336
+12,68 336
+12,68 337
+7,69 336
+141.10,20 338
+10,24 338
+142.8,52 339
+19,40 339
+42,51 339
+8,52 339
+8,52 339
+8,52 340
+8,52 341
+8,52 342
+8,52 343
+8,52 344
+8,52 345
+143.7,12 346
+145.6,52 347
+23,39 347
+41,51 347
+6,52 347
+6,52 347
+146.6,20 348
+147.9,22 349
+9,27 349
+31,40 349
+31,45 349
+31,54 349
+31,54 350
+148.7,22 351
+149.9,20 352
+150.7,17 353
+7,8 353
+13,16 353
+7,17 353
+151.7,28 354
+152.7,23 355
+153.7,69 356
+12,68 356
+16,68 356
+29,36 356
+38,44 356
+46,57 356
+59,61 356
+62,63 356
+64,65 356
+66,67 356
+12,68 356
+12,68 357
+7,69 356
+155.6,32 358
+6,32 359
+6,32 360
+6,32 361
+6,32 362
+6,32 363
+6,32 364
+6,32 313
+157.8,19 365
+158.6,25 366
+159.6,26 367
+12,13 367
+21,25 367
+6,26 367
+160.6,20 368
+6,20 369
+6,20 305
+163.8,18 370
+164.6,30 371
+6,7 371
+12,29 371
+6,30 371
+165.6,31 372
+167.8,27 373
+168.6,15 374
+13,14 374
+6,15 374
+6,15 375
+170.6,18 376
+16,17 376
+6,18 376
+6,18 305
+172.10,13 377
+174.6,15 378
+13,14 378
+6,15 378
+6,15 377
+6,15 305
+177.8,20 379
+8,20 379
+178.6,20 380
+179.6,11 381
+181.5,47 382
+5,6 382
+13,46 382
+25,33 382
+25,33 382
+35,45 382
+35,45 382
+5,47 382
+5,47 382
+182.0,39 383
+11,38 383
+0,39 383
+0,39 383
+0,39 305
+184.5,18 384
+185.5,14 385
+186.5,18 386
+5,18 305
+188.5,19 387
+5,19 305
+189.9,14 388
+9,14 389
+9,14 291
+193.6,16 390
+6,20 390
+194.36,49 391
+41,42 391
+43,48 391
+36,49 391
+36,49 391
+4,50 391
+11,17 391
+19,34 391
+19,34 391
+19,34 392
+4,50 391
+4,50 391
+196.6,14 393
+197.4,13 394
+198.9,16 395
+200.5,25 396
+5,6 396
+11,24 396
+5,25 396
+5,25 395
+202.5,15 397
+5,6 397
+11,14 397
+5,15 397
+5,15 395
+204.4,21 398
+205.4,19 399
+206.4,64 400
+9,63 400
+13,63 400
+26,33 400
+35,41 400
+43,52 400
+54,56 400
+57,58 400
+59,60 400
+61,62 400
+9,63 400
+9,63 401
+4,64 400
+207.4,66 402
+9,65 402
+13,65 402
+26,33 402
+35,41 402
+43,54 402
+56,58 402
+59,60 402
+61,62 402
+63,64 402
+9,65 402
+9,65 403
+4,66 402
+4,66 404
+208.4,9 405
+210.3,16 406
+3,4 406
+14,15 406
+3,16 406
+3,16 407
+3,16 291
+213.4,14 408
+214.2,13 409
+7,12 409
+2,13 409
+215.1,10 410
+6,9 410
+1,10 410
+216.0,1 411
+220.0,1 412
+224.4,14 413
+4,16 413
+225.42,62 414
+47,51 414
+53,61 414
+42,62 414
+42,62 414
+2,63 414
+13,31 414
+33,40 414
+33,40 414
+33,40 415
+2,63 414
+2,63 414
+226.6,13 416
+6,13 416
+6,13 416
+6,13 416
+6,13 416
+256.0,1 417
+229.6,10 418
+14,22 419
+12,22 419
+230.13,20 420
+3,20 420
+231.6,16 421
+20,30 421
+232.12,23 422
+4,29 422
+4,29 423
+233.4,12 424
+235.18,36 425
+28,35 425
+18,36 425
+18,36 425
+236.8,12 426
+8,12 426
+8,12 426
+237.3,14 426
+239.4,14 427
+4,5 427
+10,13 427
+4,14 427
+240.4,25 428
+241.4,20 429
+242.4,66 430
+9,65 430
+13,65 430
+26,33 430
+35,41 430
+43,54 430
+56,58 430
+59,60 430
+61,62 430
+63,64 430
+9,65 430
+9,65 431
+4,66 430
+4,66 426
+244.4,13 432
+11,12 432
+4,13 432
+245.4,14 433
+4,5 433
+10,13 433
+4,14 433
+246.4,21 434
+247.4,19 435
+248.4,66 436
+9,65 436
+13,65 436
+26,33 436
+35,41 436
+43,54 436
+56,58 436
+59,60 436
+61,62 436
+63,64 436
+9,65 436
+9,65 437
+4,66 436
+4,66 426
+250.3,15 438
+3,15 439
+3,15 440
+229.24,27 441
+24,27 441
+253.2,47 442
+7,46 442
+11,46 442
+23,26 442
+23,31 442
+23,31 443
+33,39 442
+41,45 442
+7,46 442
+7,46 444
+2,47 442
+256.0,1 417
+0,1 417
+0,1 417
+260.4,15 445
+261.9,11 446
+2,11 446
+262.4,12 447
+4,17 447
+263.9,10 448
+2,10 448
+264.4,15 449
+266.14,22 450
+2,31 450
+267.6,12 451
+16,24 452
+14,24 452
+268.3,8 453
+22,29 453
+18,29 453
+18,37 453
+11,38 453
+3,38 453
+267.26,29 454
+26,29 454
+269.2,11 455
+2,11 456
+271.4,14 457
+4,16 457
+272.27,47 458
+32,36 458
+38,46 458
+27,47 458
+27,47 458
+2,48 458
+13,25 458
+13,25 458
+13,25 459
+2,48 458
+2,48 458
+273.8,40 460
+19,23 460
+25,29 460
+31,39 460
+8,40 460
+8,40 460
+1,40 460
+292.1,27 461
+293.5,11 462
+13,20 463
+294.2,8 464
+295.2,10 465
+296.6,12 466
+14,19 467
+297.3,12 468
+298.6,13 469
+6,21 469
+6,21 469
+299.4,18 470
+300.3,10 471
+296.21,24 472
+21,24 472
+302.2,11 473
+14,22 473
+2,30 473
+293.22,25 474
+22,25 474
+304.0,1 475
+310.7,12 476
+311.5,9 477
+5,21 477
+312.3,8 478
+310.14,17 479
+14,17 479
+313.8,9 480
+1,9 480
+320.4,14 481
+4,16 481
+321.29,44 482
+34,35 482
+37,43 482
+29,44 482
+29,44 482
+2,45 482
+13,27 482
+13,27 482
+13,27 483
+2,45 482
+2,45 482
+322.1,12 484
+323.15,26 485
+1,26 485
+324.1,9 486
+325.1,8 487
+326.1,9 488
+327.5,9 489
+11,21 490
+328.11,15 491
+2,15 491
+329.2,17 492
+330.12,22 493
+8,22 493
+5,23 493
+5,31 493
+5,31 493
+331.3,11 494
+332.15,22 495
+8,23 495
+2,23 495
+334.6,9 496
+335.6,15 497
+19,22 497
+19,33 497
+336.4,11 498
+337.4,12 499
+339.6,15 500
+340.4,12 501
+342.2,9 502
+343.4,8 503
+4,8 503
+2,9 503
+2,19 503
+327.23,26 504
+23,26 504
+345.4,10 505
+346.5,15 506
+5,19 506
+347.3,29 507
+14,28 507
+3,29 507
+3,29 507
+348.9,12 508
+2,12 508
+350.7,15 509
+1,21 509
+351.8,19 510
+4,19 510
+4,29 510
+352.5,15 511
+5,19 511
+353.3,67 512
+14,41 512
+43,49 512
+55,66 512
+51,66 512
+3,67 512
+3,67 512
+354.9,12 513
+2,12 513
+356.1,23 514
+357.9,10 515
+9,16 515
+1,16 515
+1,16 516
+358.4,14 517
+4,18 517
+359.37,47 518
+42,43 518
+44,46 518
+37,47 518
+37,47 518
+2,61 518
+13,35 518
+13,35 518
+13,35 519
+49,52 518
+54,60 518
+2,61 518
+2,61 518
+360.8,9 520
+1,9 520
+365.10,27 521
+20,21 521
+23,26 521
+10,27 521
+10,27 521
+1,27 521
+366.4,17 522
+367.2,10 523
+2,10 523
+368.1,34 524
+369.1,11 525
+370.7,18 526
+371.8,49 527
+19,23 527
+25,34 527
+25,26 527
+25,34 527
+36,41 527
+36,47 527
+8,49 527
+8,49 527
+8,49 527
+8,53 527
+372.3,13 528
+373.3,12 529
+374.6,20 530
+6,25 530
+375.4,25 531
+376.8,14 532
+16,19 533
+377.5,9 534
+22,26 534
+18,26 534
+17,35 534
+5,35 534
+376.21,24 535
+21,24 535
+378.4,14 536
+4,14 537
+4,14 538
+381.4,10 539
+382.7,16 540
+383.5,43 541
+16,35 541
+37,42 541
+5,43 541
+5,43 541
+384.5,30 542
+385.5,15 543
+386.11,19 544
+387.9,13 545
+9,20 545
+26,29 545
+24,30 545
+24,37 545
+48,51 545
+46,52 545
+42,52 545
+41,59 545
+63,68 545
+41,68 545
+388.7,13 546
+389.7,20 547
+390.7,51 548
+18,38 548
+40,45 548
+47,50 548
+7,51 548
+7,51 548
+391.7,12 549
+386.21,24 550
+21,24 550
+394.4,19 551
+395.10,34 552
+25,26 552
+28,29 552
+31,32 552
+10,34 552
+10,34 552
+10,34 552
+10,41 552
+396.5,23 553
+17,22 553
+17,18 553
+17,22 553
+5,23 553
+5,23 553
+397.8,18 554
+22,30 554
+398.6,20 555
+399.6,26 556
+400.6,10 557
+6,22 557
+401.6,10 558
+18,33 558
+6,33 558
+402.6,37 559
+17,29 559
+31,36 559
+6,37 559
+6,37 559
+403.6,16 560
+6,7 560
+14,15 560
+6,16 560
+6,16 560
+404.6,19 561
+405.6,11 562
+6,11 563
+406.6,11 564
+408.13,22 565
+5,29 565
+409.5,15 566
+5,15 567
+395.43,54 568
+43,54 568
+411.7,12 569
+412.13,14 570
+13,19 570
+5,19 570
+5,19 571
+413.5,16 572
+5,16 572
+417.5,11 573
+418.3,8 574
+421.1,13 575
+422.0,1 576
+429.1,27 577
+431.1,7 578
+432.1,26 579
+433.1,13 580
+434.4,17 581
+435.2,47 582
+21,34 582
+36,46 582
+2,47 582
+2,47 582
+436.4,17 583
+437.2,8 584
+438.1,8 585
+439.1,13 586
+440.1,17 587
+441.1,9 588
+442.1,11 589
+445.2,38 590
+17,23 590
+25,28 590
+30,37 590
+2,38 590
+2,38 590
+446.5,11 591
+447.3,8 592
+448.6,10 593
+12,15 594
+449.13,19 595
+3,19 595
+450.6,8 596
+451.9,11 597
+452.11,22 598
+11,22 597
+453.11,22 599
+454.9,14 600
+455.7,23 601
+18,22 601
+7,23 601
+7,23 601
+7,23 597
+456.12,21 602
+12,21 597
+457.12,17 603
+459.6,16 604
+460.4,10 605
+4,10 606
+462.4,10 607
+463.6,13 608
+464.4,12 609
+465.7,12 610
+466.4,12 611
+467.6,22 612
+28,37 612
+41,50 612
+56,65 612
+69,78 612
+468.7,13 613
+4,19 613
+4,19 614
+469.11,20 615
+470.4,19 616
+471.16,29 617
+22,24 617
+26,28 617
+16,29 617
+16,29 617
+5,6 617
+5,6 618
+472.7,10 619
+7,10 619
+4,11 619
+4,20 619
+473.7,16 620
+474.5,48 621
+10,47 621
+14,47 621
+26,29 621
+26,34 621
+26,34 622
+36,42 621
+44,46 621
+10,47 621
+10,47 623
+5,48 621
+475.5,10 624
+476.5,29 625
+477.5,19 626
+16,18 626
+5,19 626
+5,19 626
+479.4,11 627
+4,11 628
+480.12,21 629
+481.4,19 630
+448.17,20 631
+17,20 631
+484.1,13 632
+486.0,1 633
+490.17,27 634
+9,27 634
+1,36 634
+1,36 635
+491.1,35 636
+17,21 636
+23,34 636
+1,35 636
+1,35 636
+492.5,14 637
+493.2,29 638
+494.2,30 639
+13,15 639
+17,20 639
+22,29 639
+2,30 639
+2,30 639
+2,30 640
+496.0,1 641
+526.1,21 642
+527.8,34 643
+19,23 643
+25,26 643
+28,33 643
+8,34 643
+8,34 643
+1,34 643
+535.1,10 644
+536.5,11 645
+17,22 646
+13,22 646
+537.7,11 647
+538.6,13 648
+2,18 648
+539.5,14 649
+24,29 649
+23,33 649
+18,33 649
+540.6,15 650
+541.8,15 651
+4,23 651
+542.6,19 652
+12,13 652
+15,18 652
+6,19 652
+6,19 652
+6,23 652
+543.11,18 653
+4,18 653
+544.3,11 654
+536.24,27 655
+24,27 655
+547.8,10 656
+1,10 656
+552.1,34 657
+18,21 657
+23,33 657
+1,34 657
+1,34 657
+553.1,41 658
+19,28 658
+30,40 658
+1,41 658
+1,41 658
+554.4,15 659
+19,31 659
+555.9,11 660
+2,11 660
+561.8,9 661
+1,9 661
+566.1,17 662
+12,16 662
+1,17 662
+1,17 662
+567.1,16 663
+7,8 663
+10,15 663
+1,16 663
+1,16 663
+568.1,17 664
+12,16 664
+1,17 664
+1,17 664
+569.1,17 665
+7,8 665
+10,16 665
+1,17 665
+1,17 665
+570.1,11 666
+572.1,40 667
+12,17 667
+19,36 667
+38,39 667
+1,40 667
+1,40 667
+573.1,12 668
+575.1,37 669
+11,23 669
+25,36 669
+1,37 669
+1,37 669
+1,37 670
+576.0,1 671
+580.1,11 672
+581.1,45 673
+12,17 673
+19,41 673
+43,44 673
+1,45 673
+1,45 673
+582.1,12 674
+583.0,1 675
+591.5,11 676
+17,25 677
+13,25 677
+592.18,25 678
+14,30 678
+5,10 678
+5,30 678
+59,66 678
+59,71 678
+42,49 678
+38,54 678
+34,35 678
+34,55 678
+34,71 678
+34,71 679
+34,71 680
+593.11,18 681
+11,23 681
+25,32 681
+25,38 681
+3,39 681
+591.27,30 682
+27,30 682
+595.9,14 683
+16,17 683
+1,18 683
+613.4,20 684
+614.2,26 685
+8,9 685
+11,25 685
+2,26 685
+2,26 685
+615.4,20 686
+616.2,27 687
+8,9 687
+11,26 687
+2,27 687
+2,27 687
+617.2,27 688
+618.2,17 689
+620.0,1 690
+11:17.1,25 691
+18.1,10 692
+1,2 692
+1,10 692
+19.0,1 693
+23.1,29 694
+18,25 694
+27,28 694
+1,29 694
+1,29 695
+24.0,1 696
+30.2,15 697
+31.7,14 698
+7,14 698
+7,14 699
+33.3,8 700
+35.8,13 701
+37.4,13 702
+4,5 702
+4,13 702
+4,13 701
+38.8,13 703
+40.2,7 698
+2,7 704
+2,7 704
+43.1,10 705
+6,9 705
+1,10 705
+44.0,1 706
+48.4,10 707
+49.0,1 708
+12:88.1,37 709
+89.4,15 710
+90.2,57 711
+8,56 711
+2,57 711
+92.1,23 712
+18,19 712
+21,22 712
+1,23 712
+1,23 713
+93.1,14 714
+94.1,16 715
+95.1,16 716
+96.1,16 717
+97.1,25 718
+98.1,19 719
+12,18 719
+1,19 719
+1,19 719
+1,19 720
+99.1,10 721
+1,2 721
+1,10 721
+100.15,16 722
+18,19 722
+1,20 722
+101.1,13 723
+102.1,13 724
+103.1,17 725
+104.1,19 726
+105.0,1 727
+109.1,18 728
+1,2 728
+11,17 728
+1,18 728
+110.1,14 729
+12,13 729
+1,14 729
+111.1,17 730
+112.0,1 731
+118.10,14 732
+10,14 732
+117.9,15 732
+9,15 732
+9,15 732
+9,15 732
+119.7,14 733
+7,14 733
+7,14 734
+121.3,8 735
+123.8,13 736
+8,13 736
+8,13 736
+8,13 736
+8,13 736
+125.4,13 737
+4,5 737
+4,13 737
+4,13 736
+127.9,13 738
+129.10,14 739
+131.6,22 740
+6,22 739
+6,22 738
+134.10,14 741
+136.6,23 742
+6,23 741
+6,23 738
+139.10,14 743
+141.9,24 744
+142.7,23 745
+7,8 745
+17,22 745
+7,23 745
+143.6,20 746
+6,20 743
+145.9,27 747
+146.7,26 748
+7,8 748
+17,25 748
+7,26 748
+147.6,23 749
+6,23 743
+6,23 738
+6,23 736
+151.4,16 750
+4,16 736
+153.4,17 751
+15,16 751
+4,17 751
+4,17 736
+155.4,20 752
+156.4,20 753
+4,20 736
+158.4,20 754
+4,20 736
+159.8,13 755
+162.6,18 756
+6,18 757
+6,18 758
+163.4,12 759
+164.3,18 760
+165.3,20 761
+166.3,26 762
+16,17 762
+19,25 762
+3,26 762
+3,26 762
+167.9,15 763
+9,19 763
+168.4,21 764
+4,5 764
+15,20 764
+15,20 764
+4,21 764
+169.4,15 765
+4,15 765
+4,15 765
+172.6,25 766
+29,48 766
+52,67 766
+68,84 766
+52,84 766
+52,84 766
+173.4,16 767
+174.6,14 768
+175.7,22 769
+7,22 769
+176.5,24 770
+18,23 770
+5,24 770
+5,24 771
+178.5,31 772
+24,26 772
+27,29 772
+5,31 772
+179.4,16 773
+180.4,13 774
+4,13 774
+4,13 775
+181.13,28 776
+182.4,13 777
+4,13 777
+4,13 778
+4,13 733
+4,13 779
+4,13 732
+185.1,10 780
+6,9 780
+1,10 780
+186.0,1 781
+194.1,24 782
+195.6,13 783
+197.2,10 784
+2,10 783
+199.2,10 785
+200.2,15 786
+2,15 783
+202.2,10 787
+2,10 783
+204.4,16 788
+205.2,10 789
+206.2,15 790
+2,15 791
+208.2,10 792
+209.1,48 793
+11,13 793
+21,24 793
+26,27 793
+30,38 793
+40,44 793
+46,47 793
+1,48 793
+210.0,1 794
+217.1,12 795
+218.1,11 796
+219.10,28 797
+1,28 797
+220.1,14 798
+221.27,29 799
+31,33 799
+9,34 799
+1,34 799
+222.1,13 800
+223.6,11 801
+6,11 801
+6,11 801
+6,11 801
+225.2,19 802
+226.2,13 803
+227.2,19 804
+228.2,12 805
+229.2,14 806
+230.16,17 807
+18,19 807
+2,20 807
+231.2,19 808
+2,19 801
+234.2,20 809
+235.2,13 810
+236.2,19 811
+237.2,12 812
+238.2,14 813
+239.2,18 814
+240.16,17 815
+19,20 815
+2,21 815
+2,21 801
+242.2,17 816
+243.2,13 817
+244.2,19 818
+245.2,12 819
+246.2,14 820
+2,14 801
+248.4,23 821
+249.19,61 822
+32,33 822
+34,35 822
+38,39 822
+40,41 822
+45,46 822
+48,49 822
+51,52 822
+54,55 822
+57,60 822
+19,61 822
+19,61 822
+8,15 822
+8,15 823
+8,15 824
+250.2,18 825
+2,3 825
+11,17 825
+2,18 825
+251.2,61 826
+21,44 826
+46,52 826
+54,60 826
+2,61 826
+2,61 826
+252.19,80 827
+30,35 827
+37,43 827
+45,51 827
+53,61 827
+63,69 827
+71,79 827
+19,80 827
+19,80 827
+8,15 827
+8,15 828
+8,15 829
+253.2,20 830
+2,3 830
+14,19 830
+2,20 830
+2,20 831
+255.1,16 832
+14,15 832
+1,16 832
+256.1,27 833
+20,22 833
+23,25 833
+1,27 833
+257.1,19 834
+258.0,1 835
+262.1,13 836
+1,13 836
+263.0,1 837
+267.7,15 838
+7,19 838
+268.7,13 839
+7,13 839
+7,13 839
+7,13 839
+7,13 839
+270.3,25 840
+17,18 840
+20,24 840
+3,25 840
+3,25 840
+3,25 839
+272.3,25 841
+17,18 841
+20,24 841
+3,25 841
+3,25 841
+3,25 839
+274.3,25 842
+3,25 839
+3,25 839
+276.0,1 843
+281.6,8 844
+6,8 844
+6,8 844
+286.2,16 845
+381.0,1 846
+288.2,16 847
+381.0,1 846
+290.2,16 848
+381.0,1 846
+292.2,22 849
+293.2,32 850
+294.2,18 851
+381.0,1 846
+296.2,19 852
+297.2,18 853
+298.2,32 854
+381.0,1 846
+300.2,17 855
+381.0,1 846
+302.5,17 856
+303.6,18 857
+381.0,1 846
+305.6,18 858
+306.14,20 859
+4,24 859
+4,24 860
+308.4,16 861
+309.3,19 862
+381.0,1 846
+311.3,15 863
+381.0,1 846
+313.16,22 864
+5,22 864
+314.6,18 865
+381.0,1 846
+316.17,23 866
+17,27 866
+6,13 866
+6,27 866
+317.4,15 867
+4,15 868
+319.4,16 869
+320.3,14 870
+381.0,1 846
+322.3,15 871
+381.0,1 846
+324.16,22 872
+16,26 872
+5,12 872
+5,26 872
+325.6,19 873
+6,19 873
+326.4,16 874
+11,12 874
+14,15 874
+4,16 874
+381.0,1 846
+328.4,15 875
+381.0,1 846
+329.10,22 876
+330.3,20 877
+331.3,22 878
+381.0,1 846
+333.3,15 879
+381.0,1 846
+335.5,17 880
+336.6,19 881
+6,19 881
+337.4,17 882
+11,12 882
+14,16 882
+4,17 882
+381.0,1 846
+339.14,20 883
+4,24 883
+381.0,1 846
+340.10,22 884
+381.0,1 846
+343.3,15 885
+381.0,1 846
+345.2,13 886
+381.0,1 846
+347.10,16 887
+10,26 887
+2,30 887
+348.12,26 888
+16,19 888
+21,25 888
+12,26 888
+12,26 888
+2,70 888
+2,70 888
+2,70 889
+34,41 888
+42,49 888
+52,58 888
+60,66 888
+68,69 888
+2,70 888
+381.0,1 846
+351.2,16 890
+381.0,1 846
+353.2,19 891
+354.2,16 892
+355.16,17 893
+18,19 893
+2,20 893
+356.2,19 894
+357.2,14 895
+358.2,10 896
+8,9 896
+2,10 896
+381.0,1 846
+360.2,19 897
+361.2,16 898
+362.16,17 899
+18,19 899
+2,20 899
+363.2,19 900
+364.2,14 901
+381.0,1 846
+366.2,18 902
+367.2,14 903
+381.0,1 846
+369.2,19 904
+370.2,14 905
+381.0,1 846
+373.2,19 906
+381.0,1 846
+0,1 846
+0,1 846
+0,1 846
+386.4,12 907
+9,11 907
+4,12 907
+4,12 907
+4,12 907
+387.2,18 908
+388.2,12 909
+6,7 909
+9,11 909
+2,12 909
+389.2,8 910
+391.4,15 911
+19,30 911
+392.5,16 912
+393.3,25 913
+3,25 914
+394.10,21 915
+395.3,21 916
+3,21 917
+397.3,21 918
+398.9,17 919
+2,17 919
+399.2,8 920
+402.7,15 921
+1,15 921
+403.6,8 922
+6,8 922
+6,8 922
+6,8 922
+407.2,17 923
+408.2,8 924
+411.9,17 925
+2,17 925
+412.5,16 926
+413.3,18 927
+414.2,8 928
+417.10,22 929
+10,22 922
+418.10,20 930
+10,20 922
+419.10,22 931
+10,22 922
+420.10,23 932
+10,23 922
+421.10,21 933
+10,21 922
+422.10,24 934
+10,24 922
+423.10,21 935
+10,21 922
+424.10,22 936
+10,22 922
+427.10,22 937
+10,22 922
+428.10,20 938
+10,20 922
+429.10,22 939
+10,22 922
+430.10,23 940
+10,23 922
+431.10,21 941
+10,21 922
+432.10,24 942
+10,24 922
+433.10,21 943
+10,21 922
+434.10,22 944
+10,22 922
+437.10,25 945
+10,25 922
+438.10,26 946
+10,26 922
+441.10,25 947
+442.3,16 948
+3,16 922
+443.10,26 949
+444.3,16 950
+3,16 922
+447.10,25 951
+448.3,16 952
+3,16 922
+449.10,26 953
+450.3,16 954
+3,16 922
+453.10,25 955
+10,25 922
+454.10,26 956
+10,26 922
+458.2,26 957
+2,26 922
+462.5,16 958
+463.3,8 959
+464.2,26 960
+465.2,17 961
+2,17 922
+469.5,16 962
+470.3,8 963
+471.2,26 964
+472.2,17 965
+2,17 922
+476.5,16 966
+477.3,8 967
+478.2,25 968
+2,25 922
+480.4,11 969
+481.2,19 970
+482.2,14 971
+484.4,11 972
+485.2,19 973
+486.2,14 974
+487.2,15 975
+489.1,17 976
+490.0,1 977
+496.4,12 978
+9,11 978
+4,12 978
+4,12 978
+4,12 978
+497.2,18 979
+498.2,12 980
+6,7 980
+9,11 980
+2,12 980
+499.2,8 981
+501.6,8 982
+507.2,11 983
+508.2,19 984
+509.2,8 985
+510.10,18 986
+10,18 982
+511.10,18 987
+10,18 982
+512.10,18 988
+10,18 982
+513.10,18 989
+10,18 982
+514.10,20 990
+10,20 982
+515.10,20 991
+10,20 982
+516.10,20 992
+10,20 982
+517.10,20 993
+10,20 982
+518.10,18 994
+10,18 982
+519.10,18 995
+10,18 982
+520.10,18 996
+10,18 982
+521.10,18 997
+10,18 982
+522.10,18 998
+10,18 982
+523.10,18 999
+10,18 982
+524.10,18 1000
+10,18 982
+525.10,18 1001
+10,18 982
+526.10,18 1002
+10,18 982
+528.7,16 1003
+13,15 1003
+7,16 1003
+7,16 1003
+1,17 1003
+1,2 1003
+1,2 1003
+1,2 1004
+1,17 1003
+529.1,14 1005
+530.1,17 1006
+531.0,1 1007
+536.6,13 1008
+538.7,9 1009
+541.3,18 1010
+542.6,17 1011
+543.4,15 1012
+4,15 1009
+547.3,18 1013
+548.17,23 1014
+6,23 1014
+549.14,20 1015
+4,24 1015
+4,24 1009
+553.3,18 1016
+554.16,22 1017
+6,22 1017
+555.4,20 1018
+4,20 1009
+559.3,18 1019
+560.6,17 1020
+561.4,15 1021
+4,15 1009
+565.3,18 1022
+566.3,9 1023
+569.8,12 1024
+8,12 1024
+8,12 1024
+572.4,38 1025
+13,20 1025
+22,29 1025
+31,37 1025
+4,38 1025
+573.8,20 1026
+22,30 1027
+574.5,27 1028
+14,15 1028
+17,18 1028
+20,26 1028
+5,27 1028
+573.32,35 1029
+32,35 1029
+577.8,12 1030
+14,23 1031
+578.5,27 1032
+14,15 1032
+17,18 1032
+20,26 1032
+5,27 1032
+577.25,28 1033
+25,28 1033
+579.4,33 1034
+13,20 1034
+22,23 1034
+25,32 1034
+4,33 1034
+4,33 1024
+582.4,12 1035
+10,11 1035
+4,12 1035
+4,12 1024
+4,12 1009
+586.8,12 1036
+8,12 1036
+8,12 1036
+588.8,42 1037
+17,24 1037
+26,33 1037
+35,41 1037
+8,42 1037
+8,42 1036
+591.8,37 1038
+17,24 1038
+26,27 1038
+29,36 1038
+8,37 1038
+8,37 1036
+594.8,36 1039
+17,24 1039
+26,27 1039
+29,35 1039
+8,36 1039
+8,36 1036
+8,36 1009
+599.3,45 1040
+12,19 1040
+21,28 1040
+30,37 1040
+30,42 1040
+30,44 1040
+3,45 1040
+3,45 1009
+603.13,27 1041
+17,20 1041
+22,26 1041
+13,27 1041
+13,27 1041
+3,71 1041
+3,71 1041
+3,71 1042
+35,42 1041
+43,50 1041
+53,59 1041
+61,67 1041
+69,70 1041
+3,71 1041
+3,71 1009
+607.6,15 1043
+608.4,20 1044
+4,20 1009
+611.6,15 1045
+612.4,21 1046
+4,21 1009
+616.3,24 1047
+10,17 1047
+19,23 1047
+3,24 1047
+3,24 1009
+620.3,27 1048
+10,17 1048
+19,26 1048
+3,27 1048
+3,27 1009
+622.2,18 1049
+632.0,1 1050
+624.7,9 1051
+627.6,14 1052
+25,31 1052
+18,31 1052
+35,43 1052
+55,61 1052
+47,61 1052
+628.18,22 1053
+24,28 1053
+4,29 1053
+4,29 1051
+630.2,18 1054
+632.0,1 1050
+0,1 1050
+638.5,10 1055
+16,24 1056
+12,24 1056
+639.12,19 1057
+2,19 1057
+641.5,15 1058
+5,15 1058
+642.3,9 1059
+643.6,22 1060
+24,32 1061
+24,32 1062
+39,45 1063
+644.3,120 1064
+10,16 1064
+18,64 1064
+66,73 1064
+75,77 1064
+79,81 1064
+83,89 1064
+91,97 1064
+99,101 1064
+103,110 1064
+112,119 1064
+3,120 1064
+3,120 1064
+3,120 1065
+646.7,14 1066
+7,14 1066
+7,14 1066
+648.6,14 1067
+6,14 1067
+18,26 1067
+649.4,10 1068
+650.4,13 1069
+651.14,22 1070
+10,22 1070
+652.14,21 1071
+5,21 1071
+653.8,16 1072
+8,16 1072
+20,28 1072
+654.10,13 1073
+10,13 1073
+26,29 1073
+26,29 1073
+21,30 1073
+17,30 1073
+6,30 1073
+6,30 1074
+656.6,9 1075
+657.6,11 1076
+660.7,12 1077
+661.8,18 1078
+8,18 1078
+662.6,46 1079
+13,19 1079
+21,40 1079
+42,45 1079
+6,46 1079
+6,46 1079
+663.5,15 1080
+5,6 1080
+11,14 1080
+5,15 1080
+664.20,23 1081
+16,24 1081
+16,24 1082
+16,24 1083
+666.13,21 1084
+18,20 1084
+13,21 1084
+13,21 1084
+13,21 1084
+667.4,14 1085
+8,9 1085
+11,13 1085
+4,14 1085
+4,14 1086
+668.11,20 1087
+669.7,28 1088
+670.5,15 1089
+671.10,19 1090
+16,18 1090
+10,19 1090
+10,19 1090
+4,20 1090
+4,5 1090
+4,5 1090
+4,5 1091
+4,20 1090
+672.4,17 1092
+4,17 1066
+675.6,15 1093
+676.4,12 1094
+677.6,27 1095
+678.4,12 1096
+679.3,14 1097
+8,9 1097
+11,13 1097
+3,14 1097
+3,14 1066
+681.6,15 1098
+682.4,12 1099
+683.3,13 1100
+7,8 1100
+10,12 1100
+3,13 1100
+3,13 1066
+687.6,15 1101
+688.4,12 1102
+689.6,14 1103
+11,13 1103
+6,14 1103
+6,14 1103
+6,14 1103
+690.4,20 1104
+691.4,14 1105
+8,9 1105
+11,13 1105
+4,14 1105
+692.4,9 1106
+694.6,17 1107
+21,32 1107
+695.10,35 1108
+14,22 1108
+24,34 1108
+10,35 1108
+10,35 1108
+4,36 1108
+4,5 1108
+4,5 1108
+4,5 1109
+4,36 1108
+696.3,19 1110
+3,19 1066
+698.8,12 1111
+8,12 1111
+8,12 1111
+8,12 1111
+8,12 1111
+700.9,11 1112
+701.11,19 1113
+11,19 1112
+702.11,19 1114
+11,19 1112
+703.11,19 1115
+11,19 1112
+11,19 1111
+706.9,11 1116
+707.11,19 1117
+11,19 1116
+11,19 1111
+710.9,11 1118
+711.11,19 1119
+11,19 1118
+712.11,19 1120
+11,19 1118
+713.12,20 1121
+12,20 1118
+714.11,19 1122
+11,19 1118
+715.11,19 1123
+11,19 1118
+11,19 1111
+718.9,11 1124
+719.11,19 1125
+11,19 1124
+720.11,19 1126
+11,19 1124
+721.12,20 1127
+12,20 1124
+722.11,19 1128
+11,19 1124
+723.11,19 1129
+11,19 1124
+11,19 1111
+726.9,11 1130
+727.11,19 1131
+11,19 1130
+11,19 1111
+730.9,18 1132
+15,17 1132
+9,18 1132
+9,18 1132
+3,19 1132
+3,4 1132
+3,4 1132
+3,4 1133
+3,19 1132
+731.3,16 1134
+732.3,19 1135
+3,19 1066
+734.6,17 1136
+21,32 1136
+735.4,14 1137
+736.12,24 1138
+4,24 1138
+4,24 1139
+737.14,25 1140
+29,40 1140
+46,57 1140
+61,72 1140
+738.4,12 1141
+739.4,22 1142
+4,22 1143
+741.4,15 1144
+9,10 1144
+12,14 1144
+4,15 1144
+4,15 1066
+743.6,17 1145
+21,32 1145
+744.4,14 1146
+745.12,24 1147
+4,24 1147
+4,24 1148
+747.4,15 1149
+9,10 1149
+12,14 1149
+4,15 1149
+4,15 1066
+749.6,17 1150
+750.4,19 1151
+751.4,12 1152
+752.4,9 1153
+754.6,17 1154
+21,31 1154
+755.4,23 1155
+4,23 1156
+756.11,22 1157
+26,37 1157
+757.4,23 1158
+4,23 1159
+759.4,13 1160
+760.3,17 1161
+3,17 1066
+762.6,17 1162
+21,31 1162
+763.4,23 1163
+4,23 1164
+764.11,22 1165
+26,37 1165
+765.4,23 1166
+766.11,18 1167
+21,25 1167
+4,25 1167
+767.4,12 1168
+4,12 1169
+769.4,13 1170
+771.6,15 1171
+26,32 1171
+19,32 1171
+36,44 1171
+56,62 1171
+48,62 1171
+772.7,16 1172
+20,31 1172
+773.5,22 1173
+774.5,24 1174
+776.18,22 1175
+24,28 1175
+4,29 1175
+777.4,17 1176
+778.4,18 1177
+779.4,21 1178
+781.3,19 1179
+3,19 1066
+784.3,19 1180
+785.6,14 1181
+11,13 1181
+6,14 1181
+6,14 1181
+6,14 1181
+786.4,14 1182
+8,9 1182
+11,13 1182
+4,14 1182
+4,14 1066
+789.6,16 1183
+790.4,20 1184
+4,20 1066
+795.6,15 1185
+796.4,12 1186
+797.6,14 1187
+11,13 1187
+6,14 1187
+6,14 1187
+6,14 1187
+798.4,20 1188
+799.4,14 1189
+8,9 1189
+11,13 1189
+4,14 1189
+800.4,9 1190
+802.3,9 1191
+803.6,15 1192
+804.7,18 1193
+22,33 1193
+805.5,10 1194
+807.7,16 1195
+20,31 1195
+35,46 1195
+808.5,21 1196
+809.5,10 1197
+811.3,19 1198
+812.9,19 1199
+15,18 1199
+9,19 1199
+9,19 1199
+3,20 1199
+3,4 1199
+3,4 1199
+3,4 1200
+3,20 1199
+3,20 1066
+817.6,14 1201
+11,13 1201
+6,14 1201
+6,14 1201
+6,14 1201
+818.4,20 1202
+819.4,14 1203
+8,9 1203
+11,13 1203
+4,14 1203
+820.4,9 1204
+822.6,17 1205
+21,32 1205
+823.5,10 1206
+824.6,17 1207
+21,32 1207
+825.4,20 1208
+826.4,9 1209
+828.3,19 1210
+829.9,19 1211
+15,18 1211
+9,19 1211
+9,19 1211
+3,20 1211
+3,4 1211
+3,4 1211
+3,4 1212
+3,20 1211
+3,20 1066
+834.3,837.4 1213
+835.5,38 1213
+5,38 1213
+19,22 1213
+19,22 1213
+24,29 1213
+24,29 1213
+31,36 1213
+31,36 1213
+836.5,38 1213
+5,38 1213
+19,22 1213
+19,22 1213
+24,29 1213
+24,29 1213
+31,36 1213
+31,36 1213
+838.6,15 1214
+839.11,19 1215
+4,19 1215
+4,19 1216
+840.4,9 1217
+842.12,20 1218
+12,26 1218
+6,26 1218
+843.4,10 1219
+4,10 1220
+845.4,12 1221
+846.12,20 1222
+12,26 1222
+6,26 1222
+847.4,10 1223
+4,10 1224
+849.4,12 1225
+850.6,15 1226
+19,28 1226
+851.4,20 1227
+4,20 1228
+4,20 1066
+853.6,14 1229
+854.4,10 1230
+855.7,16 1231
+856.5,21 1232
+857.5,10 1233
+859.13,21 1234
+860.4,18 1235
+4,18 1236
+861.11,22 1237
+862.4,12 1238
+4,12 1066
+869.8,12 1239
+871.7,18 1240
+22,33 1240
+872.5,14 1241
+5,14 1239
+874.9,11 1242
+876.5,20 1243
+18,19 1243
+5,20 1243
+877.5,20 1244
+5,20 1244
+5,20 1242
+879.5,20 1245
+18,19 1245
+5,20 1245
+880.5,20 1246
+5,20 1246
+5,20 1242
+882.4,20 1247
+4,20 1239
+884.4,20 1248
+4,20 1239
+4,20 1066
+638.26,29 1249
+26,29 1249
+888.9,17 1250
+5,17 1250
+889.9,17 1251
+9,13 1251
+9,17 1251
+2,17 1251
+891.9,12 1252
+2,12 1252
+899.5,10 1253
+16,24 1254
+12,24 1254
+900.12,19 1255
+2,19 1255
+902.5,15 1256
+5,15 1256
+903.3,9 1257
+904.6,22 1258
+24,32 1259
+24,32 1260
+39,45 1261
+905.3,122 1262
+10,16 1262
+18,64 1262
+66,73 1262
+75,77 1262
+79,81 1262
+83,89 1262
+91,99 1262
+101,103 1262
+105,112 1262
+114,121 1262
+3,122 1262
+3,122 1262
+3,122 1263
+907.7,14 1264
+7,14 1264
+7,14 1264
+909.6,14 1265
+6,14 1265
+18,26 1265
+910.4,10 1266
+911.4,13 1267
+912.14,22 1268
+10,22 1268
+913.14,21 1269
+5,21 1269
+914.8,16 1270
+8,16 1270
+20,28 1270
+915.10,13 1271
+10,13 1271
+26,29 1271
+26,29 1271
+21,30 1271
+17,30 1271
+6,30 1271
+6,30 1272
+917.6,9 1273
+918.6,11 1274
+921.7,12 1275
+922.8,18 1276
+8,18 1276
+923.6,46 1277
+13,19 1277
+21,40 1277
+42,45 1277
+6,46 1277
+6,46 1277
+924.5,15 1278
+5,6 1278
+11,14 1278
+5,15 1278
+925.20,23 1279
+16,24 1279
+16,24 1280
+16,24 1281
+927.13,21 1282
+18,20 1282
+13,21 1282
+13,21 1282
+13,21 1282
+928.4,14 1283
+8,9 1283
+11,13 1283
+4,14 1283
+4,14 1284
+929.11,20 1285
+930.7,28 1286
+931.5,15 1287
+932.10,19 1288
+16,18 1288
+10,19 1288
+10,19 1288
+4,20 1288
+4,5 1288
+4,5 1288
+4,5 1289
+4,20 1288
+933.4,17 1290
+4,17 1264
+936.6,15 1291
+937.4,12 1292
+938.3,13 1293
+7,8 1293
+10,12 1293
+3,13 1293
+3,13 1264
+940.6,17 1294
+21,32 1294
+941.4,14 1295
+942.12,24 1296
+4,24 1296
+4,24 1297
+943.13,22 1298
+944.4,14 1299
+4,14 1300
+946.4,15 1301
+9,10 1301
+12,14 1301
+4,15 1301
+947.6,21 1302
+948.4,9 1303
+950.6,17 1304
+21,32 1304
+951.4,14 1305
+952.12,24 1306
+4,24 1306
+4,24 1307
+954.4,15 1308
+9,10 1308
+12,14 1308
+4,15 1308
+4,15 1264
+956.6,17 1309
+21,31 1309
+957.4,23 1310
+4,23 1311
+958.11,22 1312
+26,37 1312
+959.4,23 1313
+4,23 1314
+961.4,13 1315
+962.3,17 1316
+3,17 1264
+964.6,17 1317
+21,31 1317
+965.4,23 1318
+4,23 1319
+966.11,22 1320
+26,37 1320
+967.4,23 1321
+968.11,18 1322
+21,25 1322
+4,25 1322
+969.4,12 1323
+4,12 1324
+971.4,13 1325
+973.6,15 1326
+26,32 1326
+19,32 1326
+36,44 1326
+56,62 1326
+48,62 1326
+974.7,16 1327
+20,31 1327
+975.5,22 1328
+976.5,24 1329
+978.18,22 1330
+24,28 1330
+4,29 1330
+979.4,17 1331
+980.4,18 1332
+981.4,21 1333
+983.3,19 1334
+3,19 1264
+988.6,14 1335
+11,13 1335
+6,14 1335
+6,14 1335
+6,14 1335
+989.4,20 1336
+990.4,14 1337
+8,9 1337
+11,13 1337
+4,14 1337
+991.4,9 1338
+993.6,17 1339
+21,32 1339
+994.5,10 1340
+995.6,17 1341
+21,32 1341
+996.4,20 1342
+997.4,9 1343
+999.3,19 1344
+1000.9,19 1345
+15,18 1345
+9,19 1345
+9,19 1345
+3,20 1345
+3,4 1345
+3,4 1345
+3,4 1346
+3,20 1345
+3,20 1264
+899.26,29 1347
+26,29 1347
+1003.9,17 1348
+5,17 1348
+1004.9,17 1349
+9,13 1349
+9,17 1349
+2,17 1349
+1006.9,12 1350
+2,12 1350
+1013.6,8 1351
+6,8 1351
+6,8 1351
+1015.2,16 1352
+1060.0,1 1353
+0,1 1353
+0,1 1353
+1023.5,16 1354
+1024.3,15 1355
+1060.0,1 1353
+1026.2,14 1356
+1027.15,21 1357
+5,21 1357
+1028.3,19 1358
+1060.0,1 1353
+1030.16,22 1359
+16,26 1359
+5,12 1359
+5,26 1359
+1031.6,19 1360
+6,19 1360
+1032.4,16 1361
+11,12 1361
+14,15 1361
+4,16 1361
+1060.0,1 1353
+1034.4,15 1362
+1060.0,1 1353
+1035.10,22 1363
+1036.6,14 1364
+1037.4,21 1365
+1038.4,23 1366
+1060.0,1 1353
+1041.3,15 1367
+1060.0,1 1353
+1043.2,13 1368
+1060.0,1 1353
+1045.12,25 1369
+16,21 1369
+23,24 1369
+12,25 1369
+12,25 1369
+2,69 1369
+2,69 1369
+2,69 1370
+33,40 1369
+41,48 1369
+51,57 1369
+59,65 1369
+67,68 1369
+2,69 1369
+1060.0,1 1353
+0,1 1353
+0,1 1353
+0,1 1353
+0,1 1353
+1058.2,16 1371
+1060.0,1 1353
+0,1 1353
+1065.4,12 1372
+9,11 1372
+4,12 1372
+4,12 1372
+4,12 1372
+1066.2,18 1373
+1067.2,12 1374
+6,7 1374
+9,11 1374
+2,12 1374
+1068.2,8 1375
+1070.6,8 1376
+1072.9,17 1377
+2,17 1377
+1073.5,16 1378
+1074.3,18 1379
+1075.2,8 1380
+1079.5,16 1381
+1080.3,14 1382
+1081.16,22 1383
+16,26 1383
+5,12 1383
+5,26 1383
+1082.6,19 1384
+6,19 1384
+1083.4,16 1385
+11,12 1385
+14,15 1385
+4,16 1385
+4,16 1386
+1085.4,15 1387
+4,15 1388
+1086.10,22 1389
+1087.3,20 1390
+1088.3,22 1391
+3,22 1392
+1090.3,15 1393
+3,15 1376
+1092.5,17 1394
+1093.6,19 1395
+6,19 1395
+1094.4,17 1396
+11,12 1396
+14,16 1396
+4,17 1396
+4,17 1397
+1096.14,20 1398
+4,24 1398
+4,24 1399
+1097.10,22 1400
+1098.3,8 1401
+1100.3,15 1402
+3,15 1376
+1102.1,17 1403
+1103.0,1 1404
+1109.6,13 1405
+1111.7,9 1406
+1114.6,15 1407
+1115.4,12 1408
+1116.3,18 1409
+1117.6,17 1410
+1118.4,15 1411
+4,15 1406
+1122.6,15 1412
+1123.4,12 1413
+1124.3,18 1414
+1125.17,23 1415
+6,23 1415
+1126.14,20 1416
+4,24 1416
+4,24 1406
+1130.6,15 1417
+1131.4,12 1418
+1132.3,18 1419
+1133.16,22 1420
+6,22 1420
+1134.4,20 1421
+4,20 1406
+1138.6,15 1422
+1139.4,12 1423
+1140.3,18 1424
+1141.6,17 1425
+1142.4,15 1426
+4,15 1406
+1146.3,18 1427
+1147.3,9 1428
+1150.8,12 1429
+8,12 1429
+8,12 1429
+1153.4,38 1430
+13,20 1430
+22,29 1430
+31,37 1430
+4,38 1430
+1154.8,20 1431
+22,30 1432
+1155.5,27 1433
+14,15 1433
+17,18 1433
+20,26 1433
+5,27 1433
+1154.32,35 1434
+32,35 1434
+1158.8,12 1435
+14,23 1436
+1159.5,27 1437
+14,15 1437
+17,18 1437
+20,26 1437
+5,27 1437
+1158.25,28 1438
+25,28 1438
+1160.4,33 1439
+13,20 1439
+22,23 1439
+25,32 1439
+4,33 1439
+4,33 1429
+1163.4,12 1440
+10,11 1440
+4,12 1440
+4,12 1429
+4,12 1406
+1167.8,12 1441
+8,12 1441
+8,12 1441
+1169.8,42 1442
+17,24 1442
+26,33 1442
+35,41 1442
+8,42 1442
+8,42 1441
+1172.8,37 1443
+17,24 1443
+26,27 1443
+29,36 1443
+8,37 1443
+8,37 1441
+1175.8,36 1444
+17,24 1444
+26,27 1444
+29,35 1444
+8,36 1444
+8,36 1441
+8,36 1406
+1180.13,27 1445
+17,20 1445
+22,26 1445
+13,27 1445
+13,27 1445
+3,71 1445
+3,71 1445
+3,71 1446
+35,42 1445
+43,50 1445
+53,59 1445
+61,67 1445
+69,70 1445
+3,71 1445
+3,71 1406
+1184.6,15 1447
+1185.4,20 1448
+4,20 1406
+1188.6,15 1449
+1189.4,21 1450
+4,21 1406
+1193.3,27 1451
+10,17 1451
+19,26 1451
+3,27 1451
+1194.3,14 1452
+3,14 1406
+1198.3,24 1453
+10,17 1453
+19,23 1453
+3,24 1453
+1199.3,14 1454
+3,14 1406
+1203.3,45 1455
+12,19 1455
+21,28 1455
+30,37 1455
+30,42 1455
+30,44 1455
+3,45 1455
+3,45 1406
+1207.6,17 1456
+1208.4,21 1457
+1209.4,21 1458
+4,5 1458
+14,20 1458
+4,21 1458
+4,21 1406
+1214.8,12 1459
+1215.9,45 1460
+9,45 1459
+1216.9,24 1461
+9,24 1459
+1217.9,24 1462
+9,24 1459
+1218.9,24 1463
+9,24 1459
+1219.9,24 1464
+9,24 1459
+1220.9,25 1465
+9,25 1459
+1221.9,25 1466
+9,25 1459
+1222.9,25 1467
+9,25 1459
+1223.9,25 1468
+9,25 1459
+9,25 1406
+1227.6,15 1469
+1228.4,12 1470
+1229.6,15 1471
+1230.4,12 1472
+1231.6,14 1473
+25,31 1473
+18,31 1473
+35,43 1473
+55,61 1473
+47,61 1473
+1232.18,22 1474
+24,28 1474
+4,29 1474
+4,29 1406
+1234.2,18 1475
+1248.0,1 1476
+1236.7,9 1477
+1239.6,15 1478
+1240.4,12 1479
+1241.6,15 1480
+1242.4,12 1481
+1243.6,14 1482
+25,31 1482
+18,31 1482
+35,43 1482
+55,61 1482
+47,61 1482
+1244.18,22 1483
+24,28 1483
+4,29 1483
+4,29 1477
+1246.2,18 1484
+1248.0,1 1476
+0,1 1476
+1260.7,21 1485
+7,21 1485
+7,25 1485
+1261.7,13 1486
+7,23 1486
+2,27 1486
+1262.5,19 1487
+5,19 1487
+1263.6,11 1488
+1264.4,10 1489
+1266.5,10 1490
+1267.3,8 1491
+1268.5,14 1492
+1269.7,11 1493
+13,16 1494
+1270.7,13 1495
+7,20 1495
+1271.5,10 1496
+1269.18,21 1497
+18,21 1497
+1272.6,11 1498
+1273.4,61 1499
+14,17 1499
+14,22 1499
+24,29 1499
+31,37 1499
+39,45 1499
+47,60 1499
+4,61 1499
+1274.4,16 1500
+11,12 1500
+14,15 1500
+4,16 1500
+1276.6,11 1501
+1277.7,17 1502
+7,17 1502
+1278.5,11 1503
+1279.8,24 1504
+26,34 1505
+26,34 1506
+41,47 1507
+1280.5,51 1508
+12,18 1508
+20,32 1508
+34,46 1508
+48,50 1508
+5,51 1508
+5,51 1508
+5,51 1509
+1282.14,27 1510
+20,26 1510
+14,27 1510
+14,27 1510
+4,72 1510
+4,72 1510
+4,72 1511
+29,34 1510
+36,42 1510
+44,56 1510
+58,71 1510
+4,72 1510
+1283.4,16 1512
+11,12 1512
+14,15 1512
+4,16 1512
+1284.4,17 1513
+1287.9,15 1514
+1289.5,29 1515
+5,29 1514
+1291.5,23 1516
+5,23 1514
+1294.10,13 1517
+6,13 1517
+1295.4,63 1518
+18,21 1518
+14,17 1518
+14,24 1518
+26,31 1518
+33,39 1518
+41,47 1518
+49,62 1518
+4,63 1518
+1296.4,22 1519
+11,12 1519
+16,21 1519
+14,21 1519
+4,22 1519
+4,22 1520
+1299.3,60 1521
+13,16 1521
+13,21 1521
+23,28 1521
+30,36 1521
+38,44 1521
+46,59 1521
+3,60 1521
+1300.3,15 1522
+10,11 1522
+13,14 1522
+3,15 1522
+1302.9,16 1523
+5,16 1523
+1303.3,16 1524
+3,16 1524
+3,16 1525
+1305.3,12 1526
+3,12 1526
+1309.0,1 1527
+1315.4,18 1528
+4,18 1528
+1316.13,16 1529
+2,16 1529
+2,16 1530
+1318.2,14 1531
+1319.14,20 1532
+4,20 1532
+1320.5,17 1533
+1321.3,19 1534
+1340.0,1 1535
+1323.3,14 1536
+1324.17,23 1537
+17,27 1537
+6,13 1537
+6,27 1537
+31,44 1537
+31,44 1537
+1325.7,21 1538
+7,21 1538
+1326.5,17 1539
+12,13 1539
+15,16 1539
+5,17 1539
+1340.0,1 1535
+1328.5,17 1540
+12,13 1540
+15,16 1540
+5,17 1540
+1329.5,33 1541
+14,21 1541
+23,24 1541
+26,32 1541
+5,33 1541
+1340.0,1 1535
+1332.7,21 1542
+7,21 1542
+1333.5,17 1543
+5,17 1544
+1335.5,17 1545
+1336.18,24 1546
+7,24 1546
+1337.17,23 1547
+16,26 1547
+5,26 1547
+1340.0,1 1535
+1346.11,35 1548
+15,20 1548
+22,32 1548
+22,34 1548
+11,35 1548
+11,35 1548
+1,77 1548
+1,77 1548
+1,77 1549
+43,48 1548
+49,50 1548
+53,64 1548
+66,73 1548
+75,76 1548
+1,77 1548
+1348.0,1 1550
+1352.5,9 1551
+11,19 1552
+1353.2,24 1553
+11,12 1553
+14,15 1553
+17,23 1553
+2,24 1553
+1352.21,24 1554
+21,24 1554
+1354.0,1 1555
+1359.1,16 1556
+1,16 1556
+1360.0,1 1557
+1365.1,30 1558
+14,21 1558
+23,29 1558
+1,30 1558
+1366.1,16 1559
+1,16 1559
+1367.0,1 1560
+1373.6,12 1561
+6,12 1561
+6,12 1561
+6,12 1561
+6,12 1561
+1375.9,25 1562
+17,18 1562
+20,24 1562
+9,25 1562
+9,25 1562
+2,25 1562
+1377.9,25 1563
+17,18 1563
+20,24 1563
+9,25 1563
+9,25 1563
+2,25 1563
+1379.9,25 1564
+9,25 1564
+20,24 1564
+2,25 1564
+1381.8,11 1565
+1,11 1565
+1387.1,33 1566
+1388.1,13 1567
+1390.1,8 1568
+1391.5,9 1569
+13,21 1570
+11,21 1570
+1392.12,19 1571
+2,19 1571
+1393.7,15 1572
+7,15 1572
+7,15 1572
+1395.6,15 1573
+1396.4,20 1574
+1397.4,15 1575
+1398.7,13 1576
+1399.5,33 1577
+18,20 1577
+22,26 1577
+22,32 1577
+5,33 1577
+5,33 1577
+1400.4,12 1578
+4,12 1572
+1403.3,11 1579
+1404.3,14 1580
+1405.6,15 1581
+1406.4,21 1582
+1407.4,32 1583
+1408.4,14 1584
+1412.4,14 1585
+4,14 1586
+1413.13,22 1587
+1414.4,46 1588
+17,19 1588
+21,45 1588
+35,43 1588
+35,43 1588
+4,46 1588
+4,46 1588
+1415.4,20 1589
+4,20 1590
+1418.4,55 1591
+17,19 1591
+21,54 1591
+35,43 1591
+35,43 1591
+45,52 1591
+45,52 1591
+4,55 1591
+4,55 1591
+1419.4,22 1592
+4,22 1572
+1422.3,11 1593
+1423.3,14 1594
+1424.6,17 1595
+21,32 1595
+1425.8,18 1596
+22,32 1596
+37,46 1596
+1426.5,15 1597
+1427.16,28 1598
+16,32 1598
+4,41 1598
+1428.12,24 1599
+12,20 1599
+12,24 1599
+4,24 1599
+4,24 1600
+1429.6,11 1601
+6,15 1601
+4,16 1601
+4,26 1601
+1430.4,16 1602
+4,16 1603
+4,16 1604
+1432.4,14 1605
+1433.10,12 1606
+1434.4,7 1606
+1435.4,7 1606
+1436.4,7 1606
+1437.4,7 1606
+1438.4,7 1606
+1439.4,7 1606
+1440.4,7 1606
+1441.4,7 1606
+1442.4,7 1606
+1443.4,7 1606
+1444.4,7 1606
+1445.4,7 1606
+1446.4,7 1606
+1448.5,14 1607
+5,14 1606
+1450.7,13 1608
+1451.5,14 1609
+1452.7,17 1610
+7,17 1610
+1453.5,69 1611
+12,18 1611
+20,40 1611
+42,47 1611
+49,64 1611
+66,68 1611
+5,69 1611
+5,69 1611
+1454.7,12 1612
+1455.5,57 1613
+18,20 1613
+22,56 1613
+36,44 1613
+36,44 1613
+46,54 1613
+46,54 1613
+5,57 1613
+5,57 1613
+1456.5,31 1614
+18,20 1614
+22,30 1614
+5,31 1614
+5,31 1614
+1457.5,47 1615
+18,20 1615
+22,45 1615
+36,43 1615
+36,43 1615
+5,47 1615
+5,47 1615
+1459.4,22 1616
+4,22 1572
+1461.2,8 1572
+1391.23,26 1617
+23,26 1617
+1464.4,11 1618
+1465.5,11 1619
+1466.3,31 1620
+16,18 1620
+20,24 1620
+20,30 1620
+3,31 1620
+3,31 1620
+1467.9,11 1621
+2,11 1621
+1469.8,28 1622
+22,26 1622
+22,26 1622
+1,28 1622
+1475.1,33 1623
+1476.1,13 1624
+1478.1,8 1625
+1479.5,9 1626
+13,21 1627
+11,21 1627
+1480.12,19 1628
+2,19 1628
+1481.7,15 1629
+7,15 1629
+7,15 1629
+1483.8,10 1630
+1485.4,20 1631
+1486.4,15 1632
+1487.7,13 1633
+1488.5,33 1634
+18,20 1634
+22,26 1634
+22,32 1634
+5,33 1634
+5,33 1634
+1489.4,12 1635
+4,12 1630
+1491.4,20 1636
+1492.4,15 1637
+1493.7,13 1638
+1494.5,33 1639
+18,20 1639
+22,26 1639
+22,32 1639
+5,33 1639
+5,33 1639
+1495.4,12 1640
+4,12 1630
+4,12 1629
+1498.3,11 1641
+1499.3,14 1642
+1500.6,15 1643
+1501.4,21 1644
+1502.4,32 1645
+1503.4,14 1646
+4,14 1647
+1504.13,22 1648
+1505.4,46 1649
+17,19 1649
+21,45 1649
+35,43 1649
+35,43 1649
+4,46 1649
+4,46 1649
+1506.4,20 1650
+4,20 1651
+1509.4,55 1652
+17,19 1652
+21,54 1652
+35,43 1652
+35,43 1652
+45,52 1652
+45,52 1652
+4,55 1652
+4,55 1652
+1510.4,22 1653
+4,22 1629
+1513.3,11 1654
+1514.3,14 1655
+1515.6,15 1656
+1516.4,46 1657
+17,19 1657
+21,45 1657
+35,43 1657
+35,43 1657
+4,46 1657
+4,46 1657
+1517.4,20 1658
+4,20 1659
+1518.13,22 1660
+1519.4,46 1661
+17,19 1661
+21,45 1661
+35,43 1661
+35,43 1661
+4,46 1661
+4,46 1661
+1520.4,20 1662
+4,20 1663
+1522.7,18 1664
+22,33 1664
+1523.5,57 1665
+18,20 1665
+22,56 1665
+36,44 1665
+36,44 1665
+47,54 1665
+47,54 1665
+5,57 1665
+5,57 1665
+1525.4,22 1666
+4,22 1629
+1528.3,11 1667
+1529.3,14 1668
+1530.6,17 1669
+21,32 1669
+1531.8,18 1670
+22,32 1670
+37,46 1670
+50,59 1670
+1532.5,15 1671
+1533.16,28 1672
+16,32 1672
+4,41 1672
+1534.12,24 1673
+12,20 1673
+12,24 1673
+4,24 1673
+4,24 1674
+1535.6,11 1675
+6,15 1675
+4,16 1675
+4,26 1675
+1536.4,16 1676
+4,16 1677
+4,16 1678
+1538.4,14 1679
+1539.10,12 1680
+1540.4,7 1680
+1541.4,7 1680
+1542.4,7 1680
+1543.4,7 1680
+1544.4,7 1680
+1545.4,7 1680
+1546.4,7 1680
+1547.4,7 1680
+1548.4,7 1680
+1549.4,7 1680
+1550.4,7 1680
+1551.4,7 1680
+1552.4,7 1680
+1553.4,7 1680
+1555.5,22 1681
+1556.8,14 1682
+27,30 1682
+18,31 1682
+18,43 1682
+1557.6,16 1683
+6,16 1680
+1559.5,14 1684
+5,14 1680
+1561.7,13 1685
+1562.5,14 1686
+1563.7,17 1687
+7,17 1687
+1564.5,69 1688
+12,18 1688
+20,40 1688
+42,47 1688
+49,64 1688
+66,68 1688
+5,69 1688
+5,69 1688
+1565.7,12 1689
+1566.5,57 1690
+18,20 1690
+22,56 1690
+36,44 1690
+36,44 1690
+46,54 1690
+46,54 1690
+5,57 1690
+5,57 1690
+1567.5,31 1691
+18,20 1691
+22,30 1691
+5,31 1691
+5,31 1691
+1568.5,47 1692
+18,20 1692
+22,45 1692
+36,43 1692
+36,43 1692
+5,47 1692
+5,47 1692
+1570.4,22 1693
+4,22 1629
+1572.2,8 1629
+1479.23,26 1694
+23,26 1694
+1575.4,11 1695
+1576.5,11 1696
+1577.3,31 1697
+16,18 1697
+20,24 1697
+20,30 1697
+3,31 1697
+3,31 1697
+1578.9,11 1698
+2,11 1698
+1580.8,28 1699
+22,26 1699
+22,26 1699
+1,28 1699
+1592.1,12 1700
+1593.14,17 1701
+1,35 1701
+1594.10,16 1702
+10,12 1702
+10,16 1702
+1,16 1702
+1,16 1703
+1595.1,6 1704
+1,10 1704
+1596.8,10 1705
+1,10 1705
+1602.24,30 1706
+24,33 1706
+17,53 1706
+44,52 1706
+44,52 1706
+44,52 1706
+44,52 1706
+44,52 1706
+44,52 1706
+1,53 1706
+1,53 1707
+1603.1,13 1708
+1604.8,14 1709
+8,18 1709
+4,18 1709
+1605.6,12 1710
+2,16 1710
+1606.1,46 1711
+11,16 1711
+24,25 1711
+27,28 1711
+31,39 1711
+41,42 1711
+44,45 1711
+1,46 1711
+1607.4,14 1712
+1608.2,62 1713
+12,15 1713
+12,20 1713
+28,29 1713
+31,32 1713
+35,43 1713
+45,58 1713
+60,61 1713
+2,62 1713
+1609.1,16 1714
+1,16 1714
+1610.0,1 1715
+0:135.19,22 1716
+137.1,25 1717
+138.1,22 1718
+139.1,40 1719
+140.1,17 1720
+1,17 1720
+141.1,28 1721
+142.1,24 1722
+22,23 1722
+1,24 1722
+1,24 1722
+143.1,48 1723
+18,42 1723
+44,47 1723
+1,48 1723
+1,48 1723
+145.1,26 1724
+146.1,16 1725
+11,15 1725
+1,16 1725
+147.1,36 1726
+15,35 1726
+1,36 1726
+148.7,24 1727
+7,24 1727
+7,24 1727
+7,24 1727
+7,29 1727
+149.7,8 1728
+151.3,18 1729
+3,18 1729
+3,18 1729
+152.7,13 1730
+19,24 1731
+15,24 1731
+153.8,12 1732
+154.11,20 1733
+7,20 1733
+155.5,13 1734
+5,18 1734
+152.26,29 1735
+26,29 1735
+158.3,15 1736
+3,15 1736
+3,15 1728
+161.1,19 1737
+1,19 1737
+1,19 1737
+162.4,12 1738
+4,16 1738
+163.2,19 1739
+164.2,16 1740
+167.4,15 1741
+168.2,14 1742
+2,14 1742
+169.1,10 1743
+174.12,33 1744
+1,33 1744
+175.4,18 1745
+176.2,39 1746
+177.17,44 1747
+31,38 1747
+40,43 1747
+17,44 1747
+17,44 1747
+7,12 1747
+7,12 1748
+178.4,13 1749
+4,18 1749
+179.2,18 1750
+180.21,24 1751
+2,24 1751
+2,24 1751
+2,24 1752
+182.4,12 1753
+4,23 1753
+4,23 1754
+183.2,18 1755
+184.2,18 1756
+185.5,17 1757
+186.3,21 1758
+187.3,19 1759
+188.6,18 1760
+189.4,22 1761
+191.5,19 1762
+192.3,16 1763
+193.5,19 1764
+194.3,16 1765
+3,16 1766
+196.2,19 1767
+197.2,19 1768
+200.1,17 1769
+201.1,13 1770
+202.1,14 1771
+203.1,15 1772
+204.1,15 1773
+205.1,210.2 1774
+206.2,9 1774
+13,16 1774
+207.2,8 1774
+12,15 1774
+208.2,7 1774
+12,15 1774
+209.2,9 1774
+13,16 1774
+212.1,43 1775
+26,38 1775
+40,42 1775
+1,43 1775
+1,43 1775
+213.1,26 1776
+8,16 1776
+18,25 1776
+1,26 1776
+215.1,26 1777
+1,2 1777
+8,16 1777
+18,25 1777
+1,26 1777
+216.1,17 1778
+1,2 1778
+8,16 1778
+1,17 1778
+217.1,34 1779
+1,2 1779
+8,15 1779
+17,24 1779
+26,33 1779
+1,34 1779
+218.1,9 1780
+1,2 1780
+1,9 1780
+219.6,13 1781
+221.2,62 1782
+2,3 1782
+9,13 1782
+21,22 1782
+23,24 1782
+28,31 1782
+32,35 1782
+45,46 1782
+47,48 1782
+52,55 1782
+56,59 1782
+2,62 1782
+2,62 1781
+223.2,63 1783
+2,3 1783
+9,13 1783
+21,22 1783
+23,24 1783
+28,31 1783
+32,35 1783
+45,46 1783
+47,49 1783
+53,56 1783
+57,60 1783
+2,63 1783
+2,63 1781
+226.1,20 1784
+227.1,14 1785
+7,8 1785
+1,14 1785
+228.1,14 1786
+7,8 1786
+1,14 1786
+229.1,14 1787
+7,8 1787
+1,14 1787
+230.1,14 1788
+7,8 1788
+1,14 1788
+231.1,18 1789
+7,8 1789
+13,17 1789
+1,18 1789
+232.1,8 1790
+235.1,9 1791
+1,2 1791
+1,9 1791
+236.1,9 1792
+1,2 1792
+1,9 1792
+237.1,9 1793
+1,2 1793
+1,9 1793
+238.1,9 1794
+1,2 1794
+1,9 1794
+239.1,9 1795
+1,2 1795
+1,9 1795
+240.0,1 1796
+418.1,27 1797
+8,16 1797
+18,26 1797
+1,27 1797
+419.4,21 1798
+420.2,32 1799
+9,17 1799
+19,31 1799
+2,32 1799
+423.0,1 1800
+422.2,28 1801
+9,17 1801
+19,27 1801
+2,28 1801
+423.0,1 1800
+427.4,23 1802
+428.2,8 1803
+429.4,14 1804
+430.2,35 1805
+9,19 1805
+21,34 1805
+2,35 1805
+431.2,35 1806
+9,19 1806
+21,34 1806
+2,35 1806
+440.0,1 1807
+433.2,35 1808
+9,19 1808
+21,34 1808
+2,35 1808
+434.2,35 1809
+9,19 1809
+21,34 1809
+2,35 1809
+435.6,19 1810
+436.3,35 1811
+10,20 1811
+22,34 1811
+3,35 1811
+440.0,1 1807
+438.3,35 1812
+10,20 1812
+22,34 1812
+3,35 1812
+440.0,1 1807
+444.1,25 1813
+445.1,450.2 1814
+446.2,9 1814
+13,62 1814
+24,31 1814
+33,52 1814
+46,47 1814
+46,47 1814
+48,49 1814
+48,49 1814
+50,51 1814
+50,51 1814
+54,55 1814
+57,58 1814
+60,61 1814
+13,62 1814
+13,62 1815
+447.2,8 1814
+12,61 1814
+23,30 1814
+32,51 1814
+45,46 1814
+45,46 1814
+47,48 1814
+47,48 1814
+49,50 1814
+49,50 1814
+53,54 1814
+56,57 1814
+59,60 1814
+12,61 1814
+12,61 1816
+448.2,7 1814
+11,60 1814
+22,29 1814
+31,50 1814
+44,45 1814
+44,45 1814
+46,47 1814
+46,47 1814
+48,49 1814
+48,49 1814
+52,53 1814
+55,56 1814
+58,59 1814
+11,60 1814
+11,60 1817
+449.2,9 1814
+13,62 1814
+24,31 1814
+33,52 1814
+46,47 1814
+46,47 1814
+48,49 1814
+48,49 1814
+50,51 1814
+50,51 1814
+54,55 1814
+57,58 1814
+60,61 1814
+13,62 1814
+13,62 1818
+452.1,22 1819
+453.1,20 1820
+454.5,24 1821
+455.2,12 1822
+2,12 1823
+457.2,15 1824
+458.1,23 1825
+459.1,40 1826
+14,24 1826
+26,31 1826
+33,39 1826
+1,40 1826
+1,40 1826
+1,40 1827
+460.1,16 1828
+461.1,18 1829
+462.1,14 1830
+463.1,21 1831
+464.1,10 1832
+1,2 1832
+1,10 1832
+465.0,1 1833
+469.1,18 1834
+470.0,1 1835
+474.1,479.2 1836
+475.2,9 1836
+13,73 1836
+25,32 1836
+34,37 1836
+39,42 1836
+44,48 1836
+50,72 1836
+13,73 1836
+13,73 1837
+476.2,8 1836
+12,71 1836
+24,30 1836
+32,35 1836
+37,40 1836
+42,46 1836
+48,70 1836
+12,71 1836
+12,71 1838
+477.2,7 1836
+12,70 1836
+24,29 1836
+31,34 1836
+36,39 1836
+41,45 1836
+47,69 1836
+12,70 1836
+12,70 1839
+478.2,9 1836
+13,73 1836
+25,32 1836
+34,37 1836
+39,42 1836
+44,48 1836
+50,72 1836
+13,73 1836
+13,73 1840
+480.1,20 1841
+481.4,14 1842
+4,14 1842
+482.2,57 1843
+7,56 1843
+11,56 1843
+24,30 1843
+32,33 1843
+35,40 1843
+42,48 1843
+50,51 1843
+52,53 1843
+54,55 1843
+7,56 1843
+7,56 1844
+2,57 1843
+485.18,21 1845
+486.2,11 1846
+7,10 1846
+2,11 1846
+489.9,13 1847
+9,13 1847
+508.2,16 1847
+2,16 1847
+2,19 1847
+2,19 1848
+25,39 1847
+25,39 1847
+25,42 1847
+2,19 1847
+510.2,15 1847
+2,15 1847
+2,18 1847
+2,18 1849
+24,37 1847
+24,37 1847
+24,40 1847
+2,18 1847
+512.2,14 1847
+2,14 1847
+2,17 1847
+2,17 1850
+24,36 1847
+24,36 1847
+24,39 1847
+2,17 1847
+514.2,16 1847
+2,16 1847
+2,19 1847
+2,19 1851
+25,39 1847
+25,39 1847
+25,42 1847
+2,19 1847
+518.12,17 1847
+12,17 1847
+487.2,8 1847
+2,8 1847
+2,8 1847
+2,8 1847
+490.6,15 1852
+491.7,17 1853
+7,17 1853
+7,22 1853
+7,22 1854
+7,22 1855
+492.5,10 1856
+5,10 1857
+5,10 1858
+493.4,12 1859
+495.8,15 1860
+8,15 1860
+497.4,57 1861
+9,56 1861
+13,56 1861
+25,53 1861
+54,55 1861
+9,56 1861
+9,56 1862
+4,57 1861
+4,57 1863
+4,57 1864
+498.4,12 1865
+501.3,22 1866
+19,21 1866
+3,22 1866
+3,22 1866
+502.9,16 1867
+9,20 1867
+503.4,16 1868
+9,15 1868
+9,15 1868
+4,16 1868
+504.4,17 1869
+4,17 1869
+4,17 1869
+4,17 1870
+4,17 1847
+4,17 1871
+509.3,17 1872
+3,17 1872
+3,26 1872
+3,26 1873
+3,26 1847
+3,26 1874
+511.3,16 1875
+3,16 1875
+3,25 1875
+3,25 1876
+3,25 1847
+3,25 1877
+513.3,15 1878
+3,15 1878
+3,24 1878
+3,24 1879
+3,24 1847
+3,24 1880
+515.3,17 1881
+3,17 1881
+3,26 1881
+3,26 1882
+3,26 1847
+519.16,39 1883
+30,33 1883
+35,38 1883
+16,39 1883
+16,39 1883
+4,5 1883
+7,11 1883
+7,11 1884
+520.6,10 1885
+521.9,16 1886
+9,16 1886
+522.4,12 1886
+524.5,65 1887
+10,64 1887
+14,64 1887
+27,33 1887
+35,41 1887
+43,48 1887
+50,56 1887
+58,59 1887
+60,61 1887
+62,63 1887
+10,64 1887
+10,64 1888
+5,65 1887
+5,65 1886
+526.9,23 1889
+528.21,26 1890
+24,25 1890
+21,26 1890
+21,26 1890
+7,8 1890
+10,17 1890
+10,17 1891
+10,17 1892
+529.10,18 1893
+530.7,51 1894
+18,47 1894
+49,50 1894
+7,51 1894
+7,51 1894
+7,51 1895
+7,51 1896
+532.6,26 1897
+6,26 1886
+534.8,25 1898
+29,50 1898
+535.6,62 1899
+11,61 1899
+15,61 1899
+28,34 1899
+36,37 1899
+39,50 1899
+52,54 1899
+55,56 1899
+57,58 1899
+59,60 1899
+11,61 1899
+11,61 1900
+6,62 1899
+6,62 1886
+537.5,37 1901
+12,22 1901
+24,36 1901
+5,37 1901
+538.5,22 1902
+539.8,22 1903
+540.6,54 1904
+13,20 1904
+35,36 1904
+37,38 1904
+47,49 1904
+51,52 1904
+6,54 1904
+6,54 1904
+541.9,23 1905
+542.6,23 1906
+6,23 1886
+544.5,37 1907
+12,22 1907
+24,36 1907
+5,37 1907
+545.5,22 1908
+546.8,22 1909
+547.6,53 1910
+13,20 1910
+35,36 1910
+37,38 1910
+47,48 1910
+50,51 1910
+6,53 1910
+6,53 1910
+548.9,23 1911
+549.6,23 1912
+6,23 1886
+551.5,15 1913
+5,20 1913
+552.5,15 1914
+5,20 1914
+5,20 1886
+5,20 1915
+5,20 1916
+5,20 1847
+5,20 1917
+5,20 1917
+557.5,19 1918
+558.2,21 1919
+559.1,14 1920
+560.1,11 1921
+561.0,1 1922
+565.1,20 1923
+566.1,17 1924
+12,13 1924
+15,16 1924
+1,17 1924
+567.1,10 1925
+568.5,13 1926
+569.10,11 1927
+13,16 1927
+2,17 1927
+570.9,12 1928
+14,15 1928
+1,16 1928
+575.6,11 1929
+6,11 1929
+6,11 1929
+6,11 1929
+577.2,22 1930
+2,22 1929
+579.2,23 1931
+2,23 1929
+581.4,18 1932
+582.2,20 1933
+583.2,18 1934
+585.0,1 1935
+590.1,41 1936
+591.5,20 1937
+592.2,31 1938
+593.2,8 1939
+596.1,24 1940
+597.13,58 1941
+32,38 1941
+40,42 1941
+44,54 1941
+56,57 1941
+13,58 1941
+13,58 1941
+598.1,35 1942
+9,12 1942
+14,34 1942
+1,35 1942
+1,35 1942
+1,35 1943
+599.1,23 1944
+9,12 1944
+14,22 1944
+1,23 1944
+1,23 1944
+1,23 1945
+600.1,48 1946
+20,23 1946
+25,31 1946
+33,40 1946
+42,47 1946
+1,48 1946
+1,48 1946
+1,48 1947
+601.1,27 1948
+9,12 1948
+14,26 1948
+1,27 1948
+1,27 1948
+1,27 1949
+603.1,19 1950
+605.1,10 1951
+606.1,11 1952
+609.12,13 1953
+12,13 1953
+617.13,14 1953
+13,14 1953
+608.10,16 1953
+10,16 1953
+10,16 1953
+10,16 1953
+610.6,20 1954
+611.7,11 1955
+612.4,35 1956
+12,15 1956
+17,34 1956
+4,35 1956
+4,35 1956
+4,35 1957
+613.4,12 1958
+4,12 1959
+616.3,29 1960
+19,22 1960
+24,28 1960
+3,29 1960
+3,29 1960
+3,29 1961
+3,29 1962
+3,29 1953
+618.7,12 1963
+620.7,11 1964
+621.4,34 1965
+12,15 1965
+17,33 1965
+4,34 1965
+4,34 1965
+4,34 1966
+4,34 1963
+623.7,11 1967
+624.4,35 1968
+12,15 1968
+17,34 1968
+4,35 1968
+4,35 1968
+4,35 1969
+625.4,12 1970
+4,12 1971
+627.4,34 1972
+12,15 1972
+17,33 1972
+4,34 1972
+4,34 1972
+4,34 1973
+628.4,12 1974
+4,12 1963
+631.3,22 1975
+3,22 1963
+633.3,20 1976
+634.3,12 1977
+637.7,8 1978
+7,8 1978
+636.3,9 1978
+3,9 1978
+3,9 1978
+3,9 1978
+3,9 1979
+640.3,9 1980
+3,9 1980
+3,9 1981
+3,9 1953
+648.0,1 1982
+653.4,14 1983
+4,14 1983
+18,26 1983
+654.49,56 1984
+49,50 1984
+49,56 1984
+49,56 1984
+2,57 1984
+9,15 1984
+17,30 1984
+32,47 1984
+32,47 1984
+32,47 1984
+32,47 1985
+2,57 1984
+2,57 1984
+655.1,12 1986
+656.0,1 1987
+662.5,8 1988
+10,19 1989
+664.19,27 1990
+19,27 1990
+665.2,13 1991
+666.5,16 1992
+20,26 1992
+667.10,16 1993
+3,16 1993
+668.16,19 1994
+3,33 1994
+669.12,19 1995
+12,15 1995
+12,19 1995
+3,19 1995
+3,19 1996
+670.3,11 1997
+3,11 1998
+672.6,14 1999
+673.6,12 2000
+6,21 2000
+6,21 2000
+674.7,17 2001
+7,21 2001
+675.10,17 2002
+10,17 2002
+677.67,74 2003
+67,68 2003
+67,74 2003
+67,74 2003
+6,75 2003
+13,19 2003
+21,36 2003
+38,48 2003
+38,48 2003
+50,65 2003
+50,65 2003
+50,65 2003
+50,65 2004
+6,75 2003
+6,75 2003
+6,75 2002
+680.7,18 2005
+681.5,13 2006
+5,13 2007
+683.5,16 2008
+684.18,21 2009
+5,35 2009
+685.14,21 2010
+14,17 2010
+14,21 2010
+5,21 2010
+5,21 2011
+686.5,10 2012
+5,14 2012
+687.5,13 2013
+5,13 2014
+692.5,16 2015
+693.3,27 2016
+3,27 2017
+695.3,14 2018
+3,14 2019
+662.21,24 2020
+21,24 2020
+697.0,1 2021
+709.1,29 2022
+710.1,13 2023
+712.6,13 2024
+6,13 2024
+714.2,9 2025
+715.7,22 2026
+2,22 2026
+716.6,10 2027
+14,24 2028
+12,24 2028
+717.13,22 2029
+3,22 2029
+720.6,16 2030
+721.7,14 2031
+18,34 2031
+722.5,13 2032
+723.4,12 2033
+724.4,12 2034
+726.8,15 2035
+8,15 2035
+8,15 2035
+728.7,16 2036
+729.5,20 2037
+730.5,16 2038
+731.8,14 2039
+732.6,69 2040
+19,21 2040
+23,68 2040
+27,68 2040
+39,45 2040
+47,53 2040
+55,61 2040
+55,67 2040
+23,68 2040
+23,68 2041
+6,69 2040
+6,69 2040
+733.5,13 2042
+5,13 2035
+736.4,19 2043
+737.4,11 2044
+738.4,12 2045
+739.4,15 2046
+740.7,18 2047
+22,33 2047
+741.15,25 2048
+5,29 2048
+5,29 2049
+742.12,23 2050
+743.5,16 2051
+5,16 2052
+744.12,21 2053
+745.5,80 2054
+18,20 2054
+22,79 2054
+26,79 2054
+38,44 2054
+46,52 2054
+54,78 2054
+68,76 2054
+68,76 2054
+22,79 2054
+22,79 2055
+5,80 2054
+5,80 2054
+746.5,20 2056
+5,20 2057
+749.5,89 2058
+18,20 2058
+22,88 2058
+26,88 2058
+38,44 2058
+46,52 2058
+54,87 2058
+68,76 2058
+68,76 2058
+78,85 2058
+78,85 2058
+22,88 2058
+22,88 2059
+5,89 2058
+5,89 2058
+750.5,22 2060
+5,22 2035
+753.4,12 2061
+754.4,14 2062
+755.13,20 2063
+7,20 2063
+756.11,16 2064
+11,16 2064
+5,17 2064
+5,22 2064
+757.14,21 2065
+7,21 2065
+759.5,27 2066
+17,23 2066
+25,26 2066
+5,27 2066
+5,27 2066
+760.8,17 2067
+761.6,26 2068
+19,21 2068
+23,25 2068
+6,26 2068
+6,26 2068
+762.5,22 2069
+5,22 2070
+5,22 2035
+716.26,29 2071
+26,29 2071
+766.5,12 2072
+767.6,12 2073
+768.4,67 2074
+17,19 2074
+21,66 2074
+25,66 2074
+37,43 2074
+45,51 2074
+53,59 2074
+53,65 2074
+21,66 2074
+21,66 2075
+4,67 2074
+4,67 2074
+769.10,12 2076
+3,12 2076
+771.2,8 2077
+772.9,25 2078
+22,24 2078
+22,24 2078
+2,25 2078
+774.8,24 2079
+21,23 2079
+21,23 2079
+1,24 2079
+780.1,12 2080
+781.14,17 2081
+1,31 2081
+782.10,16 2082
+10,12 2082
+10,16 2082
+1,16 2082
+1,16 2083
+783.1,6 2084
+1,10 2084
+784.8,10 2085
+1,10 2085
+792.4,14 2086
+4,14 2086
+793.2,49 2087
+9,15 2087
+17,29 2087
+31,38 2087
+40,48 2087
+40,48 2087
+2,49 2087
+2,49 2087
+794.5,16 2088
+795.3,35 2089
+10,16 2089
+18,24 2089
+26,34 2089
+26,34 2089
+3,35 2089
+3,35 2089
+796.5,16 2090
+797.3,35 2091
+10,16 2091
+18,24 2091
+26,34 2091
+26,34 2091
+3,35 2091
+3,35 2091
+798.2,42 2092
+9,15 2092
+17,26 2092
+28,41 2092
+28,41 2092
+2,42 2092
+2,42 2092
+800.6,13 2093
+6,13 2093
+6,13 2093
+802.2,62 2094
+25,32 2094
+25,32 2094
+34,46 2094
+34,46 2094
+48,60 2094
+48,60 2094
+803.2,44 2095
+7,43 2095
+11,43 2095
+23,29 2095
+31,35 2095
+37,42 2095
+7,43 2095
+7,43 2096
+2,44 2095
+2,44 2097
+2,44 2093
+805.7,15 2098
+7,15 2098
+806.2,16 2098
+808.3,52 2099
+8,51 2099
+12,51 2099
+24,30 2099
+32,36 2099
+38,50 2099
+8,51 2099
+8,51 2100
+3,52 2099
+809.6,33 2101
+810.4,30 2102
+4,30 2098
+811.2,20 2098
+813.44,49 2103
+44,49 2103
+44,49 2103
+3,50 2103
+8,14 2103
+16,20 2103
+22,42 2103
+22,42 2103
+3,50 2103
+3,50 2098
+814.2,9 2098
+816.10,63 2104
+14,63 2104
+27,34 2104
+36,40 2104
+42,52 2104
+54,56 2104
+57,58 2104
+59,60 2104
+61,62 2104
+10,63 2104
+10,63 2105
+3,63 2104
+818.3,38 2106
+819.3,54 2107
+8,53 2107
+12,53 2107
+25,28 2107
+30,34 2107
+36,42 2107
+44,46 2107
+47,48 2107
+49,50 2107
+51,52 2107
+8,53 2107
+8,53 2108
+3,54 2107
+820.3,12 2109
+3,4 2109
+3,12 2109
+821.3,48 2110
+26,34 2110
+26,34 2110
+36,46 2110
+36,46 2110
+822.3,45 2111
+8,44 2111
+12,44 2111
+24,30 2111
+32,36 2111
+38,43 2111
+8,44 2111
+8,44 2112
+3,45 2111
+3,45 2113
+3,45 2098
+3,45 2093
+825.7,15 2114
+7,15 2114
+827.38,51 2115
+42,50 2115
+42,50 2115
+38,51 2115
+38,51 2115
+3,52 2115
+8,14 2115
+16,20 2115
+22,26 2115
+28,36 2115
+28,36 2115
+28,36 2115
+3,52 2115
+3,52 2114
+828.2,13 2114
+829.2,11 2114
+831.12,20 2116
+3,20 2116
+832.6,16 2117
+20,32 2117
+833.4,14 2118
+4,14 2119
+835.4,49 2120
+27,35 2120
+27,35 2120
+37,47 2120
+37,47 2120
+836.4,46 2121
+9,45 2121
+13,45 2121
+25,31 2121
+33,37 2121
+39,44 2121
+9,45 2121
+9,45 2122
+4,46 2121
+4,46 2123
+4,46 2114
+839.6,14 2124
+6,24 2124
+840.50,56 2125
+50,56 2125
+50,56 2125
+4,57 2125
+9,15 2125
+17,21 2125
+23,40 2125
+42,48 2125
+42,48 2125
+4,57 2125
+4,57 2114
+842.3,13 2126
+3,13 2126
+3,13 2126
+843.6,14 2127
+6,27 2127
+844.4,14 2128
+845.6,14 2129
+6,27 2129
+846.4,14 2130
+847.3,46 2131
+8,14 2131
+16,20 2131
+22,42 2131
+44,45 2131
+3,46 2131
+848.8,16 2132
+8,16 2132
+8,16 2132
+8,16 2132
+8,16 2132
+8,16 2132
+850.4,45 2133
+15,44 2133
+4,45 2133
+4,45 2133
+851.11,62 2134
+15,62 2134
+28,34 2134
+36,40 2134
+42,51 2134
+53,55 2134
+56,57 2134
+58,59 2134
+60,61 2134
+11,62 2134
+11,62 2135
+4,62 2134
+853.11,72 2136
+15,72 2136
+28,35 2136
+37,41 2136
+43,49 2136
+51,53 2136
+54,59 2136
+60,69 2136
+70,71 2136
+11,72 2136
+11,72 2137
+4,72 2136
+855.11,70 2138
+15,70 2138
+28,33 2138
+35,39 2138
+41,47 2138
+49,51 2138
+52,57 2138
+58,67 2138
+68,69 2138
+11,70 2138
+11,70 2139
+4,70 2138
+4,70 2114
+858.3,13 2140
+3,13 2140
+3,13 2140
+859.6,14 2141
+6,27 2141
+860.4,15 2142
+861.44,49 2143
+44,49 2143
+44,49 2143
+3,50 2143
+8,14 2143
+16,20 2143
+22,42 2143
+22,42 2143
+3,50 2143
+862.8,16 2144
+8,16 2144
+8,16 2144
+8,16 2144
+8,16 2144
+8,16 2144
+864.4,47 2145
+15,46 2145
+4,47 2145
+4,47 2145
+865.11,61 2146
+15,61 2146
+28,34 2146
+36,40 2146
+42,50 2146
+52,54 2146
+55,56 2146
+57,58 2146
+59,60 2146
+11,61 2146
+11,61 2147
+4,61 2146
+867.11,71 2148
+15,71 2148
+28,35 2148
+37,41 2148
+43,49 2148
+51,53 2148
+54,58 2148
+59,68 2148
+69,70 2148
+11,71 2148
+11,71 2149
+4,71 2148
+869.11,69 2150
+15,69 2150
+28,33 2150
+35,39 2150
+41,47 2150
+49,51 2150
+52,56 2150
+57,66 2150
+67,68 2150
+11,69 2150
+11,69 2151
+4,69 2150
+4,69 2114
+871.2,6 2114
+875.8,16 2152
+8,16 2152
+877.4,49 2153
+27,35 2153
+27,35 2153
+37,47 2153
+37,47 2153
+878.11,69 2154
+15,69 2154
+28,35 2154
+37,41 2154
+43,49 2154
+51,53 2154
+54,59 2154
+60,66 2154
+67,68 2154
+11,69 2154
+11,69 2155
+4,69 2154
+880.4,49 2156
+27,35 2156
+27,35 2156
+37,47 2156
+37,47 2156
+881.11,69 2157
+15,69 2157
+28,35 2157
+37,41 2157
+43,49 2157
+51,53 2157
+54,59 2157
+60,66 2157
+67,68 2157
+11,69 2157
+11,69 2158
+4,69 2157
+4,69 2114
+883.2,7 2114
+2,7 2093
+888.7,15 2159
+7,15 2159
+890.3,39 2160
+10,18 2160
+10,18 2160
+20,28 2160
+20,28 2160
+30,38 2160
+30,38 2160
+3,39 2160
+891.38,59 2161
+49,57 2161
+42,58 2161
+38,59 2161
+38,59 2161
+3,60 2161
+8,14 2161
+16,20 2161
+22,26 2161
+28,36 2161
+28,36 2161
+28,36 2161
+3,60 2161
+3,60 2159
+893.8,16 2162
+8,16 2162
+895.9,17 2163
+9,17 2163
+9,17 2163
+9,17 2163
+9,17 2163
+9,17 2163
+897.5,21 2164
+5,21 2163
+899.5,21 2165
+5,21 2163
+901.50,56 2166
+50,56 2166
+50,56 2166
+4,57 2166
+9,15 2166
+17,21 2166
+23,40 2166
+42,48 2166
+42,48 2166
+4,57 2166
+4,57 2162
+4,57 2159
+904.8,16 2167
+8,16 2167
+906.9,17 2168
+9,17 2168
+9,17 2168
+9,17 2168
+9,17 2168
+9,17 2168
+908.5,22 2169
+5,22 2168
+910.5,22 2170
+5,22 2168
+912.50,56 2171
+50,56 2171
+50,56 2171
+4,57 2171
+9,15 2171
+17,21 2171
+23,40 2171
+42,48 2171
+42,48 2171
+4,57 2171
+4,57 2167
+4,57 2159
+4,57 2093
+916.8,11 2172
+1,11 2172
+922.1,68 2173
+23,31 2173
+23,31 2173
+33,43 2173
+33,43 2173
+45,51 2173
+45,51 2173
+53,59 2173
+53,59 2173
+61,67 2173
+61,67 2173
+923.1,40 2174
+6,39 2174
+10,39 2174
+22,26 2174
+28,32 2174
+34,38 2174
+6,39 2174
+6,39 2175
+1,40 2174
+924.0,1 2176
+929.1,60 2177
+23,31 2177
+23,31 2177
+33,43 2177
+33,43 2177
+45,51 2177
+45,51 2177
+53,59 2177
+53,59 2177
+930.1,40 2178
+6,39 2178
+10,39 2178
+22,26 2178
+28,32 2178
+34,38 2178
+6,39 2178
+6,39 2179
+1,40 2178
+931.0,1 2180
+943.1,18 2181
+13,17 2181
+1,18 2181
+1,18 2181
+944.1,18 2182
+13,17 2182
+1,18 2182
+1,18 2182
+945.4,16 2183
+946.5,14 2184
+947.3,16 2185
+3,16 2185
+3,29 2185
+3,29 2186
+955.0,1 2187
+949.3,16 2188
+3,16 2188
+3,29 2188
+3,29 2189
+955.0,1 2187
+951.5,14 2190
+952.3,16 2191
+3,16 2191
+25,34 2191
+3,34 2191
+3,34 2192
+955.0,1 2187
+954.3,16 2193
+3,16 2193
+26,35 2193
+25,35 2193
+3,35 2193
+3,35 2194
+955.0,1 2187
+962.1,19 2195
+14,18 2195
+1,19 2195
+1,19 2195
+963.1,11 2196
+964.12,24 2197
+12,24 2197
+4,24 2197
+965.2,19 2198
+966.5,21 2199
+5,21 2199
+5,33 2199
+5,33 2200
+5,33 2199
+967.3,13 2201
+968.5,19 2202
+5,19 2202
+5,31 2202
+5,31 2203
+5,31 2202
+969.3,13 2204
+970.5,20 2205
+5,20 2205
+5,32 2205
+5,32 2206
+5,32 2205
+971.3,13 2207
+972.5,21 2208
+5,21 2208
+5,33 2208
+5,33 2209
+5,33 2208
+973.3,13 2210
+3,13 2211
+975.9,22 2212
+2,22 2212
+976.5,23 2213
+5,23 2213
+977.3,13 2214
+978.5,21 2215
+5,21 2215
+979.3,13 2216
+980.5,22 2217
+5,22 2217
+981.3,13 2218
+982.5,23 2219
+5,23 2219
+983.3,13 2220
+3,13 2221
+987.8,9 2222
+1,9 2222
+1006.1,10 2223
+1007.1,7 2224
+1008.7,13 2225
+7,13 2225
+7,17 2225
+1009.5,8 2226
+5,8 2226
+1010.3,9 2227
+1011.2,9 2228
+2,9 2228
+1013.8,9 2229
+1,9 2229
+1019.1,8 2230
+1020.6,10 2231
+6,10 2231
+6,10 2231
+6,10 2231
+1021.25,37 2232
+25,37 2231
+1022.22,32 2233
+22,32 2231
+1023.23,34 2234
+23,34 2231
+1024.25,37 2235
+25,37 2231
+1026.2,30 2236
+8,29 2236
+2,30 2236
+2,30 2231
+1028.8,10 2237
+1,10 2237
+1034.1,11 2238
+1035.4,16 2239
+1036.2,12 2240
+1037.4,19 2241
+4,19 2241
+1038.2,12 2242
+1039.4,16 2243
+4,16 2243
+1040.2,12 2244
+1041.4,19 2245
+4,19 2245
+1042.2,12 2246
+1045.8,9 2247
+1,9 2247
+1051.1,11 2248
+1052.4,19 2249
+4,19 2249
+1053.2,12 2250
+1054.4,19 2251
+4,19 2251
+1055.2,12 2252
+1058.8,9 2253
+1,9 2253
+1068.1,10 2254
+1069.1,12 2255
+1070.8,11 2256
+1,11 2256
+1075.4,12 2257
+16,25 2257
+1076.10,11 2258
+13,14 2258
+2,15 2258
+1078.1,7 2259
+1079.5,11 2260
+17,22 2261
+13,22 2261
+1080.6,10 2262
+1081.5,13 2263
+17,26 2263
+30,39 2263
+1082.3,8 2264
+1079.24,27 2265
+24,27 2265
+1085.1,9 2266
+1086.4,12 2267
+16,24 2267
+1087.5,13 2268
+1088.3,10 2269
+1089.2,5 2270
+1092.1,8 2271
+1093.1,7 2272
+1094.11,16 2273
+7,16 2273
+1095.6,10 2274
+1096.2,11 2275
+1097.7,8 2276
+1099.7,14 2277
+3,19 2277
+3,19 2276
+1101.7,14 2278
+3,19 2278
+3,19 2276
+1103.3,14 2279
+3,14 2276
+1105.5,14 2280
+1106.3,8 2281
+1107.2,8 2282
+1108.6,14 2283
+2,18 2283
+1094.18,21 2284
+18,21 2284
+1111.5,7 2285
+1112.10,11 2286
+13,14 2286
+2,15 2286
+1113.4,7 2287
+1114.2,8 2288
+1115.9,10 2289
+12,17 2289
+12,13 2289
+12,17 2289
+1,18 2289
+1120.1,7 2290
+1121.5,11 2291
+17,22 2292
+13,22 2292
+1122.7,11 2293
+1123.5,17 2294
+21,33 2294
+1124.10,14 2295
+10,36 2295
+3,36 2295
+1121.24,27 2296
+24,27 2296
+1126.8,9 2297
+1,9 2297
+1132.1,10 2298
+1133.5,9 2299
+11,14 2300
+1134.2,13 2301
+1133.16,19 2302
+16,19 2302
+1135.8,11 2303
+1,11 2303
+1140.1,35 2304
+8,14 2304
+16,29 2304
+31,34 2304
+1,35 2304
+1,35 2304
+1141.1,11 2305
+7,10 2305
+1,11 2305
+1142.0,1 2306
+1146.4,10 2307
+1148.1,59 2308
+25,36 2308
+17,36 2308
+17,45 2308
+17,45 2309
+47,58 2308
+1,59 2308
+1,59 2308
+1149.4,13 2310
+1150.2,28 2311
+14,16 2311
+18,27 2311
+2,28 2311
+2,28 2311
+1151.1,5 2312
+1167.8,18 2313
+1,21 2313
+1168.8,16 2314
+20,28 2314
+8,28 2314
+1,28 2314
+8,28 2314
+1,28 2314
+1184.1,14 2315
+1185.6,13 2316
+15,21 2317
+1186.2,22 2318
+10,11 2318
+13,21 2318
+13,21 2318
+2,22 2318
+2,22 2318
+2,22 2319
+1185.23,27 2320
+23,27 2320
+1187.0,1 2321
+24
+aSys->Dir 1:26.1,39.2 64
+11
+0:name:28.2,6 s
+4:uid:29.2,5 s
+8:gid:30.2,5 s
+12:muid:31.2,6 s
+16:qid:32.2,5 @1
+
+32:mode:33.2,6 i
+36:atime:34.2,7 i
+40:mtime:35.2,7 i
+48:length:36.2,8 B
+56:dtype:37.2,7 i
+60:dev:38.2,5 i
+aSys->Qid 11.1,16.2 16
+3
+0:path:13.2,6 B
+8:vers:14.2,6 i
+12:qtype:15.2,7 i
+aDraw->Chans 2:70.1,82.2 4
+1
+0:desc:72.2,6 i
+pEvent 7:5.0,19.1 0
+2
+4:path:6.1,5 i
+8:from:7.1,5 i
+3
+Edata:9.2,7 16
+1
+12:data:10.3,7 Ab
+Eproto:11.2,8 32
+5
+12:cmd:12.3,6 i
+16:s:13.3,4 s
+20:a0:14.3,5 i
+24:a1:7,9 i
+28:a2:11,13 i
+Equit:15.2,7 12
+0
+aKeyb 9:11.0,23.1 16
+4
+0:m:12.1,2 R@5
+
+4:in:13.1,3 CR@3
+
+8:cmd:15.1,4 Cs
+12:spec:16.1,5 i
+aModule 0:53.0,56.1 8
+2
+0:path:54.1,5 i
+4:disabled:55.1,9 i
+aTk->Toplevel 3:5.1,12.2 32
+5
+0:display:7.2,9 R@7
+
+4:wreq:8.2,6 Cs
+8:image:9.2,7 R@8
+
+12:ctxt:10.2,6 R@12
+
+16:screenr:11.2,9 @9
+
+aDraw->Display 2:201.1,230.2 20
+5
+0:image:203.2,7 R@8
+
+4:white:204.2,7 R@8
+
+8:black:205.2,7 R@8
+
+12:opaque:206.2,8 R@8
+
+16:transparent:207.2,13 R@8
+
+aDraw->Image 142.1,198.2 56
+8
+0:r:146.2,3 @9
+
+16:clipr:147.2,7 @9
+
+32:depth:148.2,7 i
+36:chans:149.2,7 @2
+
+40:repl:150.2,6 i
+44:display:151.2,9 R@7
+
+48:screen:152.2,8 R@11
+
+52:iname:153.2,7 s
+aDraw->Rect 116.1,139.2 16
+2
+0:min:118.2,5 @10
+
+8:max:119.2,5 @10
+
+aDraw->Point 99.1,113.2 8
+2
+0:x:101.2,3 i
+4:y:102.2,3 i
+aDraw->Screen 249.1,263.2 16
+4
+0:id:251.2,4 i
+4:image:252.2,7 R@8
+
+8:fill:253.2,6 R@8
+
+12:display:254.2,9 R@7
+
+aDraw->Wmcontext 282.1,291.2 28
+7
+0:kbd:284.2,5 Ci
+4:ptr:285.2,5 CR@13
+
+8:ctl:286.2,5 Cs
+12:wctl:287.2,6 Cs
+16:images:288.2,8 CR@8
+
+20:connfd:289.2,8 R@14
+
+24:ctxt:290.2,6 R@15
+
+aDraw->Pointer 266.1,271.2 16
+3
+0:buttons:268.2,9 i
+4:xy:269.2,4 @10
+
+12:msec:270.2,6 i
+aSys->FD 1:45.1,48.2 4
+1
+0:fd:47.2,4 i
+aDraw->Context 2:274.1,279.2 12
+3
+0:display:276.2,9 R@7
+
+4:screen:277.2,8 R@11
+
+8:wm:278.2,4 Ct8.2
+0:t0:15,21 s
+4:t1:15,21 Ct8.2
+0:t0:32,38 s
+4:t1:32,38 R@12
+
+
+
+aModem 10:14.0,45.1 76
+19
+0:m:15.1,2 R@5
+
+4:in:16.1,3 CR@3
+
+8:connect:18.1,8 i
+12:state:19.1,6 i
+16:saved:20.1,6 s
+20:initstr:21.1,8 s
+24:dialstr:22.1,8 s
+28:lastdialstr:23.1,12 s
+32:spec:25.1,5 i
+36:fd:26.1,3 R@14
+
+40:cfd:27.1,4 R@14
+
+44:devpath:28.1,8 s
+48:avail:29.1,6 Ab
+52:rd:30.1,3 CAb
+56:pid:31.1,4 i
+60:seq:33.1,4 i
+64:waitsyn:34.1,8 i
+68:errforce:35.1,9 i
+72:addparity:36.1,10 i
+aMsg 508.0,512.1 12
+3
+0:text:509.1,5 s
+4:trans:510.1,6 s
+8:code:511.1,5 i
+aSocket 11:5.0,13.1 8
+2
+0:m:6.1,2 R@5
+
+4:in:7.1,3 CR@3
+
+aScreen 12:45.0,84.1 132
+25
+0:m:46.1,2 R@5
+
+4:ctxt:47.1,5 R@15
+
+8:in:48.1,3 CR@3
+
+12:image:50.1,6 R@8
+
+16:dispr40:51.1,8 @9
+
+32:dispr80:10,17 @9
+
+48:oldtmode:52.1,9 i
+52:rows:53.1,5 i
+56:cols:54.1,5 i
+60:cset:55.1,5 i
+64:pos:57.1,4 @10
+
+72:attr:58.1,5 i
+76:spec:59.1,5 i
+80:savepos:60.1,8 @10
+
+88:saveattr:61.1,9 i
+92:savech:62.1,7 i
+96:delimit:63.1,8 i
+100:cursor:64.1,7 i
+104:state:66.1,6 i
+108:a0:67.1,3 i
+112:a1:68.1,3 i
+116:fstate:70.1,7 i
+120:fsaved:71.1,7 Ab
+124:badp:72.1,5 i
+128:ignoredata:74.1,11 i
+aTerminal 0:90.0,113.1 56
+14
+0:in:91.1,3 CR@3
+
+4:out:92.1,4 AR@21
+
+8:mode:94.1,5 i
+12:state:95.1,6 i
+16:spec:96.1,5 i
+20:connect:97.1,8 i
+24:toplevel:98.1,9 R@6
+
+28:cmd:99.1,4 Cs
+32:proto:100.1,6 AR@22
+
+36:netaddr:101.1,8 s
+40:buttonsleft:102.1,12 i
+44:terminalid:103.1,11 Ab
+48:kbctl:104.1,6 Cs
+52:kbmode:105.1,7 s
+aBufChan 59.0,65.1 20
+5
+0:path:60.1,5 i
+4:ch:61.1,3 CR@3
+
+8:ev:62.1,3 R@3
+
+12:in:63.1,3 CR@3
+
+16:q:64.1,2 AR@3
+
+aPState 68.0,74.1 20
+5
+0:state:69.1,6 i
+4:arg:70.1,4 Ai
+8:nargs:71.1,6 i
+12:n:72.1,2 i
+16:skip:73.1,5 i
+aSys->Connection 1:52.1,57.2 12
+3
+0:dfd:54.2,5 R@14
+
+4:cfd:55.2,5 R@14
+
+8:dir:56.2,5 s
+92
+0:Event.str
+1
+32:ev:8:5.10,12 R@3
+
+3
+36:e:8.6,7 R@3
+
+40:s:7.1,2 s
+44:i:11.7,8 i
+s34:Keyb.init
+2
+32:k:9:25.10,11 R@4
+
+36:toplevel:28,36 R@6
+
+0
+n47:Keyb.reset
+1
+32:k:33.11,12 R@4
+
+0
+n53:ask
+2
+32:in:38.4,6 Cs
+36:out:24,27 Cs
+3
+40:number:46.1,7 s
+44:keys:40.1,5 s
+48:n:51.2,3 i
+n112:Keyb.run
+1
+32:k:90.9,10 R@4
+
+19
+36:askchan:93.1,8 Cs
+40:dontask:92.1,8 Cs
+44:askkeys:94.1,8 Cs
+48:e:99.8,9 R@3
+
+52:word:188.5,9 s
+56:key:130.6,9 i
+60:s:132.6,7 s
+64:seq:149.7,10 Ab
+68:cmd:122.2,5 s
+72:dialstr:215.2,9 s
+76:keys:138.5,9 Ab
+80:seq:176.7,10 Ab
+84:seq:181.6,9 Ab
+88:seq:205.6,9 Ab
+92:ev:98.2,4 R@3
+
+96:n:126.4,5 i
+100:args:7,11 Ls
+104:y:187.5,6 i
+56:x:186.5,6 i
+n472:Keyb.map
+1
+36:key:229.29,32 i
+2
+40:cmd:232.1,4 s
+44:seq:234.2,5 Ab
+Ab565:Keyb.quit
+1
+32:k:284.10,11 R@4
+
+0
+n567:canoncmd
+1
+32:s:289.9,10 s
+0
+s592:keyseq
+1
+32:skey:311.7,11 s
+2
+36:b2:313.1,3 i
+40:asterisk:314.1,9 i
+Ab645:minikey
+1
+32:key:343.8,11 i
+0
+s666:dump
+2
+32:a:10:49.5,6 Ab
+36:n:23,24 i
+2
+40:i:52.5,6 i
+44:s:51.1,2 s
+s685:Modem.init
+4
+32:m:57.11,12 R@16
+
+36:connect:30,37 i
+40:initstr:44,51 s
+44:dialstr:53,60 s
+1
+48:c:60.5,6 i
+n718:Modem.reset
+1
+32:m:81.12,13 R@16
+
+0
+n724:Modem.run
+1
+32:m:86.10,11 R@16
+
+7
+36:e:94.8,9 R@3
+
+40:b:192.2,3 Ab
+44:pidc:158.6,10 Ci
+48:dev:121.6,9 s
+52:ev:93.2,4 R@3
+
+56:ok:136.7,9 i
+60:cx:11,13 @23
+
+n1081:Modem.quit
+0
+0
+n1082:Modem.runstate
+2
+32:m:222.15,16 R@16
+
+36:data:34,38 Ab
+4
+40:ch:230.3,5 i
+44:i:229.6,7 i
+48:code:235.4,8 i
+52:str:10,13 s
+n1183:Modem.write
+2
+32:m:258.12,13 R@16
+
+36:data:31,35 Ab
+2
+40:i:267.6,7 i
+44:pa:266.2,4 Ab
+i1226:mktabs
+0
+4
+32:c:293.5,6 i
+36:crc:295.2,5 i
+40:i:296.6,7 i
+44:v:294.2,3 i
+n1247:nextblock
+3
+32:a:308.10,11 Ab
+36:i:28,29 i
+40:n:36,37 i
+0
+i1255:decode
+1
+32:a:318.7,8 Ab
+9
+36:crc:324.1,4 i
+40:i:327.5,6 i
+44:nc:329.2,4 i
+48:op:325.1,3 i
+52:b:356.1,2 Ab
+56:c:328.2,3 i
+60:dle:326.1,4 i
+64:oldcrc:323.1,7 i
+68:badpar:322.1,7 i
+Ab1350:Modem.reader
+2
+32:m:363.13,14 R@16
+
+36:pidc:32,36 Ci
+10
+40:syn:384.5,8 b
+44:n:371.9,10 i
+48:a:368.1,2 Ab
+52:b:396.5,6 Ab
+56:inbuf:369.1,6 i
+60:i:376.8,9 i
+64:b:375.4,5 Ab
+68:lim:385.5,8 i
+60:i:381.4,5 i
+68:lim:394.4,7 i
+n1477:replay
+1
+32:m:427.7,8 R@16
+
+12
+36:ch:449.3,5 i
+40:hs:441.1,3 s
+44:buf:429.1,4 Ab
+48:d:431.1,2 i
+52:da:432.1,3 Ab
+56:discard:439.1,8 i
+60:i:448.6,7 i
+64:nl:438.1,3 i
+68:start:442.1,6 i
+72:state:440.1,6 i
+76:n:445.2,3 i
+80:v:471.5,6 i
+n1575:kill
+1
+32:pid:488.5,8 i
+3
+36:cmd:493.2,5 Ab
+40:fd:491.1,3 R@14
+
+44:prog:490.1,5 s
+n1594:msend
+2
+32:m:524.6,7 R@16
+
+36:x:20,21 s
+1
+40:a:526.1,2 Ab
+i1602:apply
+2
+32:m:533.6,7 R@16
+
+36:s:20,21 s
+3
+40:buf:535.1,4 s
+44:i:536.5,6 i
+48:c:537.2,3 i
+i1629:openmodem
+2
+32:m:550.10,11 R@16
+
+36:dev:24,27 s
+0
+i1645:hangup
+1
+32:m:564.7,8 R@16
+
+0
+n1678:nethangup
+1
+32:m:578.10,11 R@16
+
+0
+n1687:seenreply
+1
+32:s:589.10,11 s
+1
+36:k:591.5,6 i
+t8.2
+0:t0:589.23,26 i
+4:t1:23,26 s
+1713:dialout
+1
+32:m:611.8,9 R@16
+
+0
+n1728:Socket.init
+1
+32:c:11:15.12,13 R@18
+
+0
+n1733:Socket.reset
+1
+32:c:21.13,14 R@18
+
+0
+n1739:Socket.run
+1
+32:c:26.11,12 R@18
+
+2
+36:e:31.7,8 R@3
+
+40:ev:30.2,4 R@3
+
+n1757:Socket.quit
+1
+32:c:46.12,13 R@18
+
+0
+n1759:Screen.init
+4
+32:s:12:86.12,13 R@19
+
+36:ctxt:32,36 R@15
+
+40:r40:57,60 @9
+
+56:r80:62,65 @9
+
+0
+n1790:Screen.reset
+1
+32:s:107.13,14 R@19
+
+0
+n1799:Screen.run
+1
+32:s:114.11,12 R@19
+
+5
+36:e:119.7,8 R@3
+
+40:da:166.3,5 AAb
+44:ev:118.1,3 R@3
+
+48:oldspec:165.3,10 i
+60:oldpos:164.3,9 @10
+
+n1910:indicators
+1
+32:s:189.11,12 R@19
+
+3
+36:attr:194.1,5 i
+40:ch:192.1,3 s
+44:col:191.1,4 i
+n1933:Screen.setmode
+2
+32:s:212.15,16 R@19
+
+36:tmode:35,40 i
+4
+40:delims:215.1,7 i
+44:ulheight:216.1,9 i
+48:fontpath:251.2,10 s
+56:dispr:214.1,6 @9
+
+n2025:Screen.quit
+0
+0
+n2028:Screen.runstate
+2
+32:s:265.16,17 R@19
+
+36:data:36,40 Ab
+0
+n2051:vc0
+2
+32:s:279.4,5 R@19
+
+36:ch:19,21 i
+1
+40:cols:347.2,6 i
+n2183:vc1
+2
+32:s:384.4,5 R@19
+
+36:ch:19,21 i
+2
+40:bg:402.7,9 i
+44:fg:1,3 i
+n2296:vss2
+2
+32:s:494.5,6 R@19
+
+36:ch:20,22 i
+0
+n2357:vcsi
+2
+32:s:534.5,6 R@19
+
+36:ch:20,22 i
+2
+40:r:573.8,9 i
+40:r:577.8,9 i
+n2494:vstate
+2
+32:s:635.7,8 R@19
+
+36:data:22,26 Ab
+6
+40:ch:639.2,4 i
+44:i:637.1,2 i
+48:str:650.4,7 s
+52:cs:642.3,5 s
+56:n:649.4,5 i
+60:match:834.3,8 AAi
+Ab2921:mstate
+2
+32:s:895.7,8 R@19
+
+36:data:22,26 Ab
+5
+40:ch:900.2,4 i
+44:i:897.1,2 i
+48:str:911.4,7 s
+52:cs:903.3,5 s
+56:n:910.4,5 i
+Ab3127:mc0
+2
+32:s:1011.4,5 R@19
+
+36:ch:19,21 i
+0
+n3186:mc1
+2
+32:s:1063.4,5 R@19
+
+36:ch:19,21 i
+0
+n3241:mcsi
+2
+32:s:1107.5,6 R@19
+
+36:ch:20,22 i
+2
+40:r:1154.8,9 i
+40:r:1158.8,9 i
+n3433:Screen.put
+2
+32:s:1258.11,12 R@19
+
+36:str:31,34 s
+4
+40:n:1261.2,3 i
+44:i:1269.7,8 i
+48:cs:1278.5,7 s
+52:l:1260.8,9 i
+n3541:incpos
+2
+32:s:1313.7,8 R@19
+
+36:n:22,23 i
+0
+n3587:rowclear
+3
+32:r:1343.9,10 i
+36:first:12,17 i
+40:last:19,23 i
+0
+n3603:clear
+1
+32:s:1350.6,7 R@19
+
+1
+36:r:1352.5,6 i
+n3613:refresh
+0
+0
+n3616:scroll
+2
+32:topline:1363.7,14 i
+36:nlines:16,22 i
+0
+n3623:filter
+2
+32:s:1371.7,8 R@19
+
+36:data:22,26 Ab
+0
+AAb3646:vfilter
+2
+32:s:1385.8,9 R@19
+
+36:data:23,27 Ab
+7
+40:ba:1387.1,3 AAb
+44:ch:1392.2,4 i
+48:i:1391.5,6 i
+52:d0:1390.1,3 i
+56:a:1427.4,5 Ab
+60:changed:1388.1,8 i
+64:valid:1432.4,9 i
+AAb3791:mfilter
+2
+32:s:1473.8,9 R@19
+
+36:data:23,27 Ab
+8
+40:ba:1475.1,3 AAb
+44:ch:1480.2,4 i
+48:i:1479.5,6 i
+52:d0:1478.1,3 i
+56:changed:1476.1,8 i
+60:a:1533.4,5 Ab
+64:valid:1538.4,9 i
+68:n:1555.5,6 i
+AAb3991:dappend
+2
+32:ba:1590.8,10 AAb
+36:b:36,37 Ab
+2
+40:na:1593.1,3 AAb
+44:l:1592.1,2 i
+AAb4003:Screen.msg
+2
+32:s:1600.11,12 R@19
+
+36:str:31,34 s
+2
+40:n:1603.1,2 i
+44:blank:1602.1,6 s
+n4041:init
+2
+32:ctxt:0:132.5,9 R@15
+
+36:argv:30,34 Ls
+11
+40:words:177.7,12 Ls
+44:arg:145.1,4 mArg
+6:1.0,14.1 0
+
+48:dialstr:0:174.12,19 s
+52:netaddr:135.1,8 s
+56:c:148.8,9 i
+60:initstr:174.1,8 s
+64:i:152.7,8 i
+68:toplevel:212.1,9 R@6
+
+72:done:226.1,5 Ci
+76:s:134.1,2 s
+64:connect:173.1,8 i
+n4234:inittk
+2
+32:toplevel:416.7,15 R@6
+
+36:connect:35,42 i
+0
+n4249:Terminal.layout
+2
+32:t:425.16,17 R@20
+
+36:cols:38,42 i
+0
+n4280:Terminal.init
+3
+32:t:442.14,15 R@20
+
+36:toplevel:36,44 R@6
+
+40:connect:64,71 i
+0
+n4364:Terminal.reset
+1
+32:t:467.15,16 R@20
+
+0
+n4366:Terminal.run
+2
+32:t:472.13,14 R@20
+
+36:done:35,39 Ci
+8
+40:ev:485.2,4 R@3
+
+44:eva:501.3,6 AR@3
+
+48:e:527.6,7 s
+52:cmd:518.2,5 s
+56:e:495.8,9 R@3
+
+60:modcount:480.1,9 i
+64:n:519.4,5 i
+68:word:7,11 Ls
+n4635:kb
+1
+32:t:563.3,4 R@20
+
+2
+36:s:565.1,2 Cs
+40:e:567.1,2 s
+t8.2
+0:t0:563.22,28 s
+4:t1:22,28 Cs
+4648:Terminal.setkbmode
+2
+32:t:573.19,20 R@20
+
+36:tmode:41,46 i
+0
+n4660:dokb
+2
+32:t:588.5,6 R@20
+
+36:c:22,23 Cs
+7
+40:top:597.2,5 R@6
+
+44:m:7,8 Cs
+48:kbon:605.1,5 i
+52:kbctl:596.1,6 Cs
+56:keyboard:590.1,9 mKeyboard
+14:13.0,21.1 0
+
+60:mcmd:0:609.1,5 s
+64:kbcmd:617.1,6 s
+n4768:Terminal.quit
+0
+0
+n4769:send
+1
+32:e:651.5,6 R@3
+
+0
+n4787:post
+1
+32:e:659.5,6 R@3
+
+6
+36:b:664.2,3 R@21
+
+40:l:661.3,4 i
+44:i:1,2 i
+48:na:684.5,7 AR@3
+
+52:na:668.3,5 AR@3
+
+56:de:675.10,12 R@3
+
+n4852:protocol
+1
+32:ev:700.9,11 R@3
+
+8
+36:p:715.2,3 R@22
+
+40:e:712.6,7 R@3
+
+44:ea:709.1,3 AR@3
+
+48:i:716.6,7 i
+52:ch:717.3,5 i
+56:d0:714.2,4 i
+60:changed:710.1,8 i
+64:pe:759.5,7 R@3
+
+AR@3
+4987:eappend
+2
+32:ea:778.8,10 AR@3
+
+36:e:32,33 R@3
+
+2
+40:na:781.1,3 AR@3
+
+44:l:780.1,2 i
+AR@3
+4999:proto
+2
+32:from:790.6,10 i
+36:p:17,18 R@22
+
+8
+40:all:818.3,6 i
+44:reply:802.2,7 Ab
+48:reply:821.3,8 Ab
+52:reply:835.4,9 Ab
+76:reply:877.4,9 Ab
+80:reply:880.4,9 Ab
+40:x:842.3,4 i
+40:x:858.3,4 i
+R@3
+5418:PRO3
+5
+32:path:920.5,9 i
+36:from:11,15 i
+40:x:17,18 i
+44:y:20,21 i
+48:z:23,24 i
+1
+52:data:922.1,5 Ab
+n5439:PRO2
+4
+32:path:927.5,9 i
+36:from:11,15 i
+40:x:17,18 i
+44:y:20,21 i
+1
+48:data:929.1,5 Ab
+n5458:modcmd
+3
+32:cmd:941.7,10 i
+36:from:12,16 i
+40:targ:18,22 i
+0
+n5492:psb
+1
+32:code:960.4,8 i
+4
+36:b:963.1,2 i
+40:mask:965.2,6 i
+44:mod:975.2,5 R@5
+
+48:this:962.1,5 i
+i5543:parity
+1
+32:b:1004.7,8 i
+2
+36:p:1007.1,2 i
+40:bits:1006.1,5 i
+i5555:RxTx
+1
+32:code:1017.5,9 i
+1
+36:rv:1019.1,3 i
+i5574:osb
+0
+1
+32:b:1034.1,2 i
+i5588:kosb
+0
+1
+32:b:1051.1,2 i
+i5597:tostr
+1
+32:ch:1066.6,8 i
+1
+36:str:1068.1,4 s
+s5601:toint
+2
+32:s:1073.6,7 s
+36:base:17,21 i
+6
+40:c:1078.1,2 i
+44:i:1079.5,6 i
+48:n:1093.1,2 i
+52:v:1096.2,3 i
+56:neg:1085.1,4 i
+60:ok:1092.1,3 i
+t8.2
+0:t0:1073.30,33 i
+4:t1:30,33 s
+5656:tolower
+1
+32:s:1118.8,9 s
+3
+36:i:1121.5,6 i
+40:r:1120.1,2 s
+44:c:1122.2,3 i
+s5670:dup
+2
+32:ch:1130.4,6 i
+36:n:8,9 i
+2
+40:i:1133.5,6 i
+44:str:1132.1,4 s
+s5678:fatal
+1
+32:msg:1138.6,9 s
+0
+n5688:exits
+1
+32:s:1144.6,7 s
+1
+36:fd:1148.1,3 R@14
+
+n5704:ISC0
+1
+32:ch:1165.5,7 i
+1
+36:msb:1167.1,4 i
+i5712:tkcmds
+2
+32:t:1182.7,8 R@6
+
+36:cmds:28,32 As
+2
+40:ix:1185.6,8 i
+44:n:1184.1,2 i
+n30
+5144:C:126.0,1 R@18
+
+5196:K:124.0,1 R@4
+
+5204:M:125.0,1 R@16
+
+5220:Modname:44.0,7 As
+5224:Modules:129.0,7 AR@5
+
+5244:S:127.0,1 R@19
+
+5248:T:128.0,1 R@20
+
+5252:TERMINALID1:30.0,11 Ab
+5256:TERMINALID2:35.0,11 Ab
+5320:crctab:10:285.0,6 Ai
+5332:debug:0:26.0,5 Ai
+5340:disp:12:10.0,4 mMDisplay
+13:7.0,115.1 0
+
+5344:draw:0:11.1,5 mDraw
+2:1.0,298.1 0
+
+5420:msgs:10:514.0,4 A@17
+
+5448:partab:47.0,6 Ab
+5452:pgrp:0:25.0,4 i
+5460:playfd:10:424.0,6 R@14
+
+5508:stderr:0:27.0,6 R@14
+
+5512:sys:8.1,4 mSys
+1:4.0,160.1 0
+
+5528:tk:0:13.1,3 mTk
+3:1.0,25.1 0
+
+5532:tkclient:0:15.1,9 mTkclient
+4:1.0,26.1 0
+
+5536:tkinitbs:0:247.0,8 As
+5540:tkinitdirect:265.0,12 As
+5544:tkinitip:310.0,8 As
+5548:tkip40x25hide:383.0,13 As
+5552:tkip40x25lhs:369.0,12 As
+5556:tkip40x25rhs:376.0,12 As
+5560:tkip40x25show:346.0,13 As
+5564:tkip80x25hide:412.0,13 As
+5568:tkip80x25show:387.0,13 As
diff --git a/appl/wm/minitel/mkfile b/appl/wm/minitel/mkfile
new file mode 100644
index 00000000..16f816a6
--- /dev/null
+++ b/appl/wm/minitel/mkfile
@@ -0,0 +1,24 @@
+<../../../mkconfig
+
+TARG=\
+ mdisplay.dis\
+ miniterm.dis\
+ swkeyb.dis\
+
+MODULES=\
+ mdisplay.m\
+ miniterm.m\
+ event.m\
+ swkeyb.m\
+
+SYSMODULES=\
+ arg.m\
+ sys.m\
+ debug.m\
+ draw.m\
+ tk.m\
+ wmlib.m\
+
+DISBIN=$ROOT/dis/wm/minitel
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/wm/minitel/modem.b b/appl/wm/minitel/modem.b
new file mode 100644
index 00000000..b7a21c1d
--- /dev/null
+++ b/appl/wm/minitel/modem.b
@@ -0,0 +1,620 @@
+#
+# Copyright © 1998 Vita Nuova Limited. All rights reserved.
+#
+
+#modem states for direct connection
+MSstart, MSdialing, MSconnected, MSdisconnecting,
+
+# special features
+Ecp # error correction
+ : con (1 << iota);
+
+Ecplen: con 17; # error correction block length: data[15], crc, validation (=0)
+
+Modem: adt {
+ m: ref Module; # common attributes
+ in: chan of ref Event;
+
+ connect: int; # None, Direct, Network
+ state: int; # modem dialing state
+ saved: string; # response, so far (direct dial)
+ initstr: string; # softmodem init string (direct dial)
+ dialstr: string; # softmodem dial string (direct dial)
+ lastdialstr: string;
+
+ spec: int; # special features
+ fd: ref Sys->FD; # modem data file, if != nil
+ cfd: ref Sys->FD; # modem ctl file, if != nil (direct dial only)
+ devpath: string; # path to the modem;
+ avail: array of byte; # already read
+ rd: chan of array of byte; # reader -> rd
+ pid: int; # reader pid if != 0
+
+ seq: int; # ECP block sequence number
+ waitsyn: int; # awaiting restart SYN SYN ... sequence
+ errforce: int;
+ addparity: int; # must add parity to outgoing data
+
+ init: fn(m: self ref Modem, connect: int, initstr, dialstr: string);
+ reset: fn(m: self ref Modem);
+ run: fn(m: self ref Modem);
+ quit: fn(m: self ref Modem);
+ runstate: fn(m: self ref Modem, data: array of byte);
+ write: fn(m: self ref Modem, data: array of byte):int; # to network
+ reader: fn(m: self ref Modem, pidc: chan of int);
+};
+
+partab: array of byte;
+
+dump(a: array of byte, n: int): string
+{
+ s := sys->sprint("[%d]", n);
+ for(i := 0; i < n; i++)
+ s += sys->sprint(" %.2x", int a[i]);
+ return s;
+}
+
+Modem.init(m: self ref Modem, connect: int, initstr, dialstr: string)
+{
+ partab = array[128] of byte;
+ for(c := 0; c < 128; c++)
+ if(parity(c))
+ partab[c] = byte (c | 16r80);
+ else
+ partab[c] = byte c;
+ m.in = chan of ref Event;
+ m.connect = connect;
+ m.state = MSstart;
+ m.initstr = initstr;
+ m.dialstr = dialstr;
+ m.pid = 0;
+ m.spec = 0;
+ m.seq = 0;
+ m.waitsyn = 0;
+ m.errforce = 0;
+ m.addparity = 0;
+ m.avail = array[0] of byte;
+ m.rd = chan of array of byte;
+ m.reset();
+}
+
+Modem.reset(m: self ref Modem)
+{
+ m.m = ref Module(Pscreen, 0);
+}
+
+Modem.run(m: self ref Modem)
+{
+ if(m.dialstr != nil)
+ send(ref Event.Eproto(Pmodem, Mmodem, Cconnect, "", 0,0,0));
+Runloop:
+ for(;;){
+ alt {
+ ev := <- m.in =>
+ pick e := ev {
+ Equit =>
+ break Runloop;
+ Edata =>
+ if(debug['m'] > 0)
+ fprint(stderr, "Modem <- %s\n", e.str());
+ m.write(e.data);
+ if(T.state == Local || T.spec & Echo) { # loopback
+ if(e.from == Mkeyb) {
+ send(ref Event.Eproto(Pscreen, Mkeyb, Ccursor, "", 0,0,0));
+ send(ref Event.Edata(Pscreen, Mkeyb, e.data));
+ }
+ }
+ Eproto =>
+ case e.cmd {
+ Creset =>
+ m.reset();
+ Cconnect =>
+ if(m.pid != 0)
+ break;
+ m.addparity = 1;
+ T.state = Connecting;
+ send(ref Event.Eproto(Pscreen, Mmodem, Cindicators, "",0,0,0));
+
+ case m.connect {
+ Direct =>
+ S.msg("Appel "+m.dialstr+" ...");
+ dev := "/dev/modem";
+ if(openmodem(m, dev) < 0) {
+ S.msg("Modem non prêt");
+ T.state = Local;
+ send(ref Event.Eproto(Pscreen, Mmodem, Cindicators, "",0,0,0));
+ break;
+ }
+ m.state = MSdialing;
+ m.saved = "";
+ dialout(m);
+ T.terminalid = TERMINALID2;
+ Network =>
+ S.msg("Connexion au serveur ...");
+ if(debug['m'] > 0 || debug['M'] > 0)
+ sys->print("dial(%s)\n", m.dialstr);
+ (ok, cx) := sys->dial(m.dialstr, "");
+ if (ok == -1){
+ S.msg("Echec de la connexion");
+ T.state = Local;
+ send(ref Event.Eproto(Pscreen, Mmodem, Cindicators, "",0,0,0));
+ if(debug['m'] > 0)
+ sys->print("can't dial %s: %r\n", m.dialstr);
+ break;
+ }
+ m.fd = sys->open(cx.dir + "/data", Sys->ORDWR);
+ m.cfd = cx.cfd;
+ if(len m.dialstr >= 3 && m.dialstr[0:3] == "tcp")
+ m.addparity = 0; # Internet gateway apparently doesn't require parity
+ if(m.fd != nil) {
+ S.msg(nil);
+ m.state = MSconnected;
+ T.state = Online;
+ send(ref Event.Eproto(Pscreen, Mmodem, Cindicators, "",0,0,0));
+ }
+ T.terminalid = TERMINALID1;
+ }
+ if(m.fd != nil) {
+ pidc := chan of int;
+ spawn m.reader(pidc);
+ m.pid = <-pidc;
+ }
+ Cdisconnect =>
+ if(m.pid != 0) {
+ S.msg("Déconnexion ...");
+ m.state = MSdisconnecting;
+ }
+ if(m.connect == Direct)
+ hangup(m);
+ else
+ nethangup(m);
+ Cplay => # for testing
+ case e.s {
+ "play" =>
+ replay(m);
+ }
+ Crequestecp =>
+ if(m.spec & Ecp){ # for testing: if already active, force an error
+ m.errforce = 1;
+ break;
+ }
+ m.write(array[] of {byte SEP, byte 16r4A});
+sys->print("sending request for ecp\n");
+ Cstartecp =>
+ m.spec |= Ecp;
+ m.seq = 0; # not in spec
+ m.waitsyn = 0; # not in spec
+ Cstopecp =>
+ m.spec &= ~Ecp;
+ * => break;
+ }
+ }
+ b := <- m.rd =>
+ if(debug['m'] > 0){
+ fprint(stderr, "Modem -> %s\n", dump(b,len b));
+ }
+ if(b == nil) {
+ m.pid = 0;
+ case m.state {
+ MSdialing =>
+ S.msg("Echec appel");
+ MSdisconnecting =>
+ S.msg(nil);
+ }
+ m.state = MSstart;
+ T.state = Local;
+ send(ref Event.Eproto(Pscreen, Mmodem, Cscreenon, "",0,0,0));
+ send(ref Event.Eproto(Pscreen, Mmodem, Cindicators, "",0,0,0));
+ break;
+ }
+ m.runstate(b);
+ }
+ }
+ if(m.pid != 0)
+ kill(m.pid);
+ send(nil);
+}
+
+Modem.quit(nil: self ref Modem)
+{
+}
+
+Modem.runstate(m: self ref Modem, data: array of byte)
+{
+ if(debug['m']>0)
+ sys->print("runstate %d %s\n", m.state, dump(data, len data));
+ case m.state {
+ MSstart => ;
+ MSdialing =>
+ for(i:=0; i<len data; i++) {
+ ch := int data[i];
+ if(ch != '\n' && ch != '\r') {
+ m.saved[len m.saved] = ch;
+ continue;
+ }
+ (code, str) := seenreply(m.saved);
+ case code {
+ Noise or Ok => ;
+ Success =>
+ S.msg(nil);
+ m.state = MSconnected;
+ T.state = Online;
+ send(ref Event.Eproto(Pscreen, Mmodem, Cindicators, "",0,0,0));
+ Failure =>
+ hangup(m);
+ S.msg(str);
+ m.state = MSstart;
+ T.state = Local;
+ send(ref Event.Eproto(Pscreen, Mmodem, Cindicators, "",0,0,0));
+ }
+ m.saved = "";
+ }
+ MSconnected =>
+ send(ref Event.Edata(m.m.path, Mmodem, data));
+ MSdisconnecting => ;
+ }
+}
+
+Modem.write(m: self ref Modem, data: array of byte): int
+{
+ if(m.fd == nil)
+ return -1;
+ if(len data == 0)
+ return 0;
+ if(m.addparity){
+ # unfortunately must copy data to add parity for direct modem connection
+ pa := array[len data] of byte;
+ for(i := 0; i<len data; i++)
+ pa[i] = partab[int data[i] & 16r7F];
+ data = pa;
+ }
+ if(debug['m']>0)
+ sys->print("WRITE %s\n", dump(data, len data));
+ return sys->write(m.fd, data, len data);
+}
+
+#
+# minitel error correction protocol
+#
+# SYN, SYN, block number start of retransmission
+# NUL ignored
+# DLE escapes {DLE, SYN, NACK, NUL}
+# NACK, block restart request
+#
+
+crctab: array of int;
+Crcpoly: con 16r9; # crc7 = x^7+x^3+1
+
+# precalculate the CRC7 remainder for all bytes
+
+mktabs()
+{
+ crctab = array[256] of int;
+ for(c := 0; c < 256; c++){
+ v := c;
+ crc := 0;
+ for(i := 0; i < 8; i++){
+ crc <<= 1; # align remainder's MSB with value's
+ if((v^crc) & 16r80)
+ crc ^= Crcpoly;
+ v <<= 1;
+ }
+ crctab[c] = (crc<<1) & 16rFE; # pre-align the result to save <<1 later
+ }
+}
+
+# return the index of the first non-NUL character (the start of a block)
+
+nextblock(a: array of byte, i: int, n: int): int
+{
+ for(; i < n; i++)
+ if(a[i] != byte NUL)
+ break;
+ return i;
+}
+
+# return the data in the ecp block in a[0:Ecplen] (return nil for bad format)
+
+decode(a: array of byte): array of byte
+{
+ if(debug['M']>0)
+ sys->print("DECODE: %s\n", dump(a, Ecplen));
+ badpar := 0;
+ oldcrc := int a[Ecplen-2];
+ crc := 0;
+ op := 0;
+ dle := 0;
+ for(i:=0; i<Ecplen-2; i++){ # first byte is high-order byte of polynomial (MSB first)
+ c := int a[i];
+ nc := c & 16r7F; # strip parity
+ if((c^int partab[nc]) & 16r80)
+ badpar++;
+ crc = crctab[crc ^ c];
+ # collapse DLE sequences
+ if(!dle){
+ if(nc == DLE && i+1 < Ecplen-2){
+ dle = 1;
+ continue;
+ }
+ if(nc == NUL)
+ continue; # strip non-escaped NULs
+ }
+ dle = 0;
+ a[op++] = byte nc;
+ }
+ if(badpar){
+ if(debug['E'] > 0)
+ sys->print("bad parity\n");
+ return nil;
+ }
+ crc = (crc>>1)&16r7F;
+ if(int partab[crc] != oldcrc){
+ if(debug['E'] > 0)
+ sys->print("bad crc: in %ux got %ux\n", oldcrc, int partab[crc]);
+ return nil;
+ }
+ b := array[op] of byte;
+ b[0:] = a[0:op];
+ if(debug['M'] > 0)
+ sys->print("OUT: %s [%x :: %x]\n", dump(b,op), crc, oldcrc);
+ return b;
+}
+
+Modem.reader(m: self ref Modem, pidc: chan of int)
+{
+ pidc <-= sys->pctl(0, nil);
+ if(crctab == nil)
+ mktabs();
+ a := array[Sys->ATOMICIO] of byte;
+ inbuf := 0;
+ while(m.fd != nil) {
+ while((n := read(m.fd, a[inbuf:], len a-inbuf)) > 0){
+ n += inbuf;
+ inbuf = 0;
+ if((m.spec & Ecp) == 0){
+ b := array[n] of byte;
+ for(i := 0; i<n; i++)
+ b[i] = byte (int a[i] & 16r7F); # strip parity
+ m.rd <-= b;
+ }else{
+ #sys->print("IN: %s\n", dump(a,n));
+ i := 0;
+ if(m.waitsyn){
+ sys->print("seeking SYN #%x\n", m.seq);
+ syn := byte (SYN | 16r80);
+ lim := n-3;
+ for(; i <= lim; i++)
+ if(a[i] == syn && a[i+1] == syn && (int a[i+2]&16r0F) == m.seq){
+ i += 3;
+ m.waitsyn = 0;
+ sys->print("found SYN #%x@%d\n", m.seq, i-3);
+ break;
+ }
+ }
+ lim := n-Ecplen;
+ for(; (i = nextblock(a, i, n)) <= lim; i += Ecplen){
+ b := decode(a[i:]);
+ if(m.errforce || b == nil){
+ m.errforce = 0;
+ b = array[2] of byte;
+ b[0] = byte NACK;
+ b[1] = byte (m.seq | 16r40);
+ sys->print("NACK #%x\n", m.seq);
+ m.write(b);
+ m.waitsyn = 1;
+ i = n; # discard rest of block
+ break;
+ }
+ m.seq = (m.seq+1) & 16rF; # mod 16 counter
+ m.rd <-= b;
+ }
+ if(i < n){
+ a[0:] = a[i:n];
+ inbuf = n-i;
+ }
+ }
+ }
+ if(n <= 0)
+ break;
+ }
+# m.fd = nil;
+ m.rd <-= nil;
+}
+
+playfd: ref Sys->FD;
+in_code, in_char: con iota;
+
+replay(m: ref Modem)
+{
+ buf := array[8192] of byte;
+ DMAX: con 10;
+ d := 0;
+ da := array[DMAX] of byte;
+ playfd = nil;
+ if(playfd == nil)
+ playfd = sys->open("minitel.txt", Sys->OREAD);
+ if(playfd == nil)
+ return;
+ nl := 1;
+ discard := 1;
+ state := in_code;
+ hs := "";
+ start := 0;
+mainloop:
+ for(;;) {
+ n := sys->read(playfd, buf, len buf);
+ if(n <= 0)
+ break;
+ for(i:=0; i<n; i++) {
+ ch := int buf[i];
+ if(nl)
+ case ch {
+ '>' => discard = 0;
+ '<' => discard = 1;
+ if(start)
+ sys->sleep(1000);
+ '{' => start = 1;
+ '}' => break mainloop;
+ }
+ if(ch == '\n')
+ nl = 1;
+ else
+ nl = 0;
+ if(discard)
+ continue;
+ if(!start)
+ continue;
+ if(state == in_code && ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z')))
+ hs[len hs] = ch;
+ else if(ch == '(') {
+ state = in_char;
+ (v, nil) := toint(hs, 16);
+ da[d++] = byte v;
+ if(d == DMAX) {
+ send(ref Event.Edata(m.m.path, Mmodem, da));
+ d = 0;
+ da = array[DMAX] of byte;
+ sys->sleep(50);
+ }
+ hs = "";
+ }else if(ch == ')')
+ state = in_code;
+ }
+ }
+ playfd = nil;
+
+}
+
+kill(pid : int)
+{
+ prog := "#p/" + string pid + "/ctl";
+ fd := sys->open(prog, Sys->OWRITE);
+ if (fd != nil) {
+ cmd := array of byte "kill";
+ sys->write(fd, cmd, len cmd);
+ }
+}
+
+
+# Modem stuff
+
+
+# modem return codes
+Ok, Success, Failure, Noise, Found: con iota;
+
+#
+# modem return messages
+#
+Msg: adt {
+ text: string;
+ trans: string;
+ code: int;
+};
+
+msgs: array of Msg = array [] of {
+ ("OK", "Ok", Ok),
+ ("NO CARRIER", "No carrier", Failure),
+ ("ERROR", "Bad modem command", Failure),
+ ("NO DIALTONE", "No dial tone", Failure),
+ ("BUSY", "Busy tone", Failure),
+ ("NO ANSWER", "No answer", Failure),
+ ("CONNECT", "", Success),
+};
+
+msend(m: ref Modem, x: string): int
+{
+ a := array of byte x;
+ return sys->write(m.fd, a, len a);
+}
+
+#
+# apply a string of commands to modem
+#
+apply(m: ref Modem, s: string): int
+{
+ buf := "";
+ for(i := 0; i < len s; i++){
+ c := s[i];
+ buf[len buf] = c; # assume no Unicode
+ if(c == '\r' || i == (len s -1)){
+ if(c != '\r')
+ buf[len buf] = '\r';
+ if(msend(m, buf) < 0)
+ return Failure;
+ buf = "";
+ }
+ }
+ return Ok;
+}
+
+openmodem(m: ref Modem, dev: string): int
+{
+ m.fd = sys->open(dev, Sys->ORDWR);
+ m.cfd = sys->open(dev+"ctl", Sys->ORDWR);
+ if(m.fd == nil || m.cfd == nil)
+ return -1;
+# hangup(m);
+# m.fd = sys->open(dev, Sys->ORDWR);
+# m.cfd = sys->open(dev+"ctl", Sys->ORDWR);
+# if(m.fd == nil || m.cfd == nil)
+# return -1;
+ return 0;
+}
+
+hangup(m: ref Modem)
+{
+ sys->sleep(1020);
+ msend(m, "+++");
+ sys->sleep(1020);
+ apply(m, "ATH0");
+ m.fd = nil;
+# sys->write(m.cfd, array of byte "f", 1);
+ sys->write(m.cfd, array of byte "h", 1);
+ m.cfd = nil;
+ # HACK: shannon softmodem "off-hook" bug fix
+ sys->open("/dev/modem", Sys->OWRITE);
+}
+
+nethangup(m: ref Modem)
+{
+ m.fd = nil;
+ sys->write(m.cfd, array of byte "hangup", 6);
+ m.cfd = nil;
+}
+
+
+#
+# check `s' for a known reply or `substr'
+#
+seenreply(s: string): (int, string)
+{
+ for(k := 0; k < len msgs; k++)
+ if(len s >= len msgs[k].text && s[0:len msgs[k].text] == msgs[k].text) {
+ return (msgs[k].code, msgs[k].trans);
+ }
+ return (Noise, s);
+}
+
+contains(s, t: string): int
+{
+ if(t == nil)
+ return 1;
+ if(s == nil)
+ return 0;
+ n := len t;
+ for(i := 0; i+n <= len s; i++)
+ if(s[i:i+n] == t)
+ return 1;
+ return 0;
+}
+
+dialout(m: ref Modem)
+{
+ if(m.initstr != nil)
+ apply(m, "AT"+m.initstr);
+ if(m.dialstr != nil) {
+ apply(m, "ATD"+m.dialstr);
+ m.lastdialstr = m.dialstr;
+ m.dialstr = nil;
+ }
+}
diff --git a/appl/wm/minitel/screen.b b/appl/wm/minitel/screen.b
new file mode 100644
index 00000000..4313d48d
--- /dev/null
+++ b/appl/wm/minitel/screen.b
@@ -0,0 +1,1610 @@
+#
+# Occasional references are made to sections and tables in the
+# France Telecom Minitel specification
+#
+# Copyright © 1998 Vita Nuova Limited. All rights reserved.
+#
+
+include "mdisplay.m";
+
+disp: MDisplay;
+
+Rect, Point : import Draw;
+
+# display character sets
+videotex, semigraphic, french, american :import MDisplay;
+
+# display foreground colour attributes
+fgBlack, fgBlue, fgRed, fgMagenta,
+fgGreen, fgCyan, fgYellow, fgWhite :import MDisplay;
+
+# display background colour attributes
+bgBlack, bgBlue, bgRed, bgMagenta,
+bgGreen, bgCyan, bgYellow, bgWhite :import MDisplay;
+
+fgMask, bgMask : import MDisplay;
+
+# display formatting attributes
+attrB, attrW, attrH, attrP, attrF, attrC, attrL, attrD :import MDisplay;
+
+# Initial attributes - white on black
+ATTR0: con fgWhite|bgBlack&~(attrB|attrW|attrH|attrP|attrF|attrC|attrL|attrD);
+
+# special features
+Cursor, Scroll, Insert
+ : con (1 << iota);
+
+# Screen states
+Sstart, Sss2, Sesc, Srepeat, Saccent, Scsi0, Scsi1, Sus0, Sus1, Sskip,
+Siso2022, Siso6429, Stransparent, Sdrcs, Sconceal, Swaitfor
+ : con iota;
+
+# Filter states
+FSstart, FSesc, FSsep, FS6429, FS2022: con iota;
+
+Screen: adt {
+ m: ref Module; # common attributes
+ ctxt: ref Draw->Context;
+ in: chan of ref Event; # from the terminal
+
+ image: ref Draw->Image; # Mdisplay image
+ dispr40, dispr80: Rect; # 40 and 80 column display region
+ oldtmode: int; # old terminal mode
+ rows: int; # number of screen rows (25 for minitel)
+ cols: int; # number of screen cols (40 or 80)
+ cset: int; # current display charset
+
+ pos: Point; # current writing position (x:1, y:0)
+ attr: int; # display attribute set
+ spec: int; # special features
+ savepos: Point; # `pos' before moving to row zero
+ saveattr: int; # `attr' before moving to row zero
+ savech: int; # last character `Put'
+ delimit: int; # attr changed, make next space a delimiter
+ cursor: int; # update cursor soon
+
+ state: int; # recogniser state
+ a0: int; # recogniser arg 0
+ a1: int; # recogniser arg 1
+
+ fstate: int; # filter state
+ fsaved: array of byte; # filter `chars so far'
+ badp: int; # filter because of bad parameter
+
+ ignoredata: int; # ignore data from
+
+ init: fn(s: self ref Screen, ctxt: ref Draw->Context, r40, r80: Rect);
+ reset: fn(s: self ref Screen);
+ run: fn(s: self ref Screen);
+ quit: fn(s: self ref Screen);
+ setmode: fn(s: self ref Screen, tmode: int);
+ runstate: fn(s: self ref Screen, data: array of byte);
+ put: fn(s: self ref Screen, str: string);
+ msg: fn(s: self ref Screen, str: string);
+};
+
+Screen.init(s: self ref Screen, ctxt: ref Draw->Context, r40, r80: Rect)
+{
+ disp = load MDisplay MDisplay->PATH;
+ if(disp == nil)
+ fatal("can't load the display module: "+MDisplay->PATH);
+
+ s.m = ref Module(0, 0);
+ s.ctxt = ctxt;
+ s.dispr40 = r40;
+ s.dispr80 = r80;
+ s.oldtmode = -1;
+ s.in = chan of ref Event;
+ disp->Init(s.ctxt);
+ s.reset();
+ s.pos = Point(1, 1);
+ s.savech = 0;
+ s.cursor = 1;
+ s.ignoredata = 0;
+ s.fstate = FSstart;
+}
+
+Screen.reset(s: self ref Screen)
+{
+ s.setmode(T.mode);
+ indicators(s);
+ s.state = Sstart;
+}
+
+Screen.run(s: self ref Screen)
+{
+Runloop:
+ for(;;) alt {
+ ev := <- s.in =>
+ pick e := ev {
+ Equit =>
+ break Runloop;
+ Eproto =>
+ case e.cmd {
+ Creset =>
+ s.reset();
+ Cproto =>
+ case e.a0 {
+ START =>
+ case e.a1 {
+ SCROLLING =>
+ s.spec |= Scroll;
+ }
+ STOP =>
+ case e.a1 {
+ SCROLLING =>
+ s.spec &= ~Scroll;
+ }
+ MIXED =>
+ case e.a1 {
+ MIXED1 => # videotex -> mixed
+ if(T.mode != Mixed)
+ s.setmode(Mixed);
+ T.mode = Mixed;
+ MIXED2 => # mixed -> videotex
+ if(T.mode != Videotex)
+ s.setmode(Videotex);
+ T.mode = Videotex;
+ }
+ }
+ Ccursor => # update the cursor soon
+ s.cursor = 1;
+ Cindicators =>
+ indicators(s);
+ Cscreenoff =>
+ s.ignoredata = 1;
+ s.state = Sstart;
+ Cscreenon =>
+ s.ignoredata = 0;
+ * => break;
+ }
+ Edata =>
+ if(s.ignoredata)
+ continue;
+ oldpos := s.pos;
+ oldspec := s.spec;
+ da := filter(s, e.data);
+ while(len da > 0) {
+ s.runstate(da[0]);
+ da = da[1:];
+ }
+
+ if(s.pos.x != oldpos.x || s.pos.y != oldpos.y || (s.spec&Cursor)^(oldspec&Cursor))
+ s.cursor = 1;
+ if(s.cursor) {
+ if(s.spec & Cursor)
+ disp->Cursor(s.pos);
+ else
+ disp->Cursor(Point(-1,-1));
+ s.cursor = 0;
+ refresh();
+ } else if(e.from == Mkeyb)
+ refresh();
+ }
+ }
+ send(nil);
+}
+
+# row0 indicators (1.2.2)
+indicators(s: ref Screen)
+{
+ col: int;
+ ch: string;
+
+ attr := fgWhite|bgBlack;
+ case T.state {
+ Local =>
+ ch = "F";
+ Connecting =>
+ ch = "C";
+ attr |= attrF;
+ Online =>
+ ch = "C";
+ }
+ if(s.cols == 40) {
+ col = 39;
+ attr |= attrP;
+ } else
+ col = 77;
+ disp->Put(ch, Point(col, 0), videotex, attr, 0);
+}
+
+Screen.setmode(s: self ref Screen, tmode: int)
+{
+ dispr: Rect;
+ delims: int;
+ ulheight: int;
+ s.rows = 25;
+ s.spec = 0;
+ s.attr = s.saveattr = ATTR0;
+ s.delimit = 0;
+ s.pos = s.savepos = Point(-1, -1);
+ s.cursor = 1;
+ case tmode {
+ Videotex =>
+ s.cset = videotex;
+ s.cols = 40;
+ dispr = s.dispr40;
+ delims = 1;
+ ulheight = 2;
+ s.pos = Point(1,1);
+ s.spec &= ~Cursor;
+ Mixed =>
+# s.cset = french;
+ s.cset = videotex;
+ s.cols = 80;
+ dispr = s.dispr80;
+ delims = 0;
+ ulheight = 1;
+ s.spec |= Scroll;
+ s.pos = Point(1, 1);
+ Ascii =>
+ s.cset = french;
+ s.cols = 80;
+ dispr = s.dispr80;
+ delims = 0;
+ ulheight = 1;
+ };
+ if(tmode != s.oldtmode) {
+ (nil, s.image) = disp->Mode(((0,0),(0,0)), 0, 0, 0, 0, nil);
+ T.layout(s.cols);
+ fontpath := sprint("/fonts/minitel/f%dx%d", s.cols, s.rows);
+ (nil, s.image) = disp->Mode(dispr, s.cols, s.rows, ulheight, delims, fontpath);
+ T.setkbmode(tmode);
+ }
+ disp->Reveal(0); # concealing enabled (1.2.2)
+ disp->Cursor(Point(-1,-1));
+ s.oldtmode = tmode;
+}
+
+Screen.quit(nil: self ref Screen)
+{
+ disp->Quit();
+}
+
+Screen.runstate(s: self ref Screen, data: array of byte)
+{
+ while(len data > 0)
+ case T.mode {
+ Videotex =>
+ data = vstate(s, data);
+ Mixed =>
+ data = mstate(s, data);
+ Ascii =>
+ data = astate(s, data);
+ };
+}
+
+# process a byte from set C0
+vc0(s: ref Screen, ch: int)
+{
+ case ch {
+# SOH => # not in spec, wait for 16r04
+# s.a0 = 16r04;
+# s.state = Swaitfor;
+ SS2 =>
+ s.state = Sss2;
+ SYN =>
+ s.state = Sss2; # not in the spec, but acts like SS2
+ ESC =>
+ s.state = Sesc;
+ SO =>
+ s.cset = semigraphic;
+ s.attr &= ~(attrH|attrW|attrP); # 1.2.4.2
+ s.attr &= ~attrL; # 1.2.4.3
+ SI =>
+ s.cset = videotex;
+ s.attr &= ~attrL; # 1.2.4.3
+ s.attr &= ~(attrH|attrW|attrP); # some servers seem to assume this too
+ SEP or SS3 => # 1.2.7
+ s.state = Sskip;
+ BS =>
+ if(s.pos.x == 1) {
+ if(s.pos.y == 0)
+ break;
+ if(s.pos.y == 1)
+ s.pos.y = s.rows - 1;
+ else
+ s.pos.y -= 1;
+ s.pos.x = s.cols;
+ } else
+ s.pos.x -= 1;
+ HT =>
+ if(s.pos.x == s.cols) {
+ if(s.pos.y == 0)
+ break;
+ if(s.pos.y == s.rows - 1)
+ s.pos.y = 1;
+ else
+ s.pos.y += 1;
+ s.pos.x = 1;
+ } else
+ s.pos.x += 1;
+ LF =>
+ if(s.pos.y == s.rows - 1)
+ if(s.spec&Scroll)
+ scroll(1, 1);
+ else
+ s.pos.y = 1;
+ else if(s.pos.y == 0) { # restore attributes on leaving row zero
+ s.pos = s.savepos;
+ s.attr = s.saveattr;
+ } else
+ s.pos.y += 1;
+ VT =>
+ if(s.pos.y == 1)
+ if(s.spec&Scroll)
+ scroll(1, -1);
+ else
+ s.pos.y = s.rows - 1;
+ else if(s.pos.y == 0)
+ break;
+ else
+ s.pos.y -= 1;
+ CR =>
+ s.pos.x = 1;
+ CAN =>
+ cols := s.cols - s.pos.x + 1;
+ disp->Put(dup(' ', cols), Point(s.pos.x,s.pos.y), s.cset, s.attr, 0);
+ US =>
+ # expect US row, col
+ s.state = Sus0;
+ FF =>
+ s.cset = videotex;
+ s.attr = ATTR0;
+ s.pos = Point(1,1);
+ s.spec &= ~Cursor;
+ s.cursor = 1;
+ clear(s);
+ RS =>
+ s.cset = videotex;
+ s.attr = ATTR0;
+ s.pos = Point(1,1);
+ s.spec &= ~Cursor;
+ s.cursor = 1;
+ CON =>
+ s.spec |= Cursor;
+ s.cursor = 1;
+ COFF =>
+ s.spec &= ~Cursor;
+ s.cursor = 1;
+ REP =>
+ # repeat
+ s.state = Srepeat;
+ NUL =>
+ # padding character - ignore, but may appear anywhere
+ ;
+ BEL =>
+ # ah ...
+ ;
+ }
+}
+
+# process a byte from the set c1 - introduced by the ESC character
+vc1(s: ref Screen, ch: int)
+{
+ if(ISC0(ch)) {
+ s.state = Sstart;
+ vc0(s, ch);
+ return;
+ }
+ if(ch >= 16r20 && ch <= 16r2f) {
+ if(ch == 16r25)
+ s.state = Stransparent;
+ else if(ch == 16r23)
+ s.state = Sconceal;
+ else
+ s.state = Siso2022;
+ s.a0 = s.a1 = 0;
+ return;
+ }
+
+ fg := bg := -1;
+ case ch {
+ 16r35 or
+ 16r36 or
+ 16r37 =>
+ s.state = Sskip; # skip next char unless C0
+ return;
+
+ 16r5b => # CSI sequence
+ s.a0 = s.a1 = 0;
+ if(s.pos.y > 0) # 1.2.5.2
+ s.state = Scsi0;
+ return;
+
+ # foreground colour
+ 16r40 => fg = fgBlack;
+ 16r41 => fg = fgRed;
+ 16r42 => fg = fgGreen;
+ 16r43 => fg = fgYellow;
+ 16r44 => fg = fgBlue;
+ 16r45 => fg = fgMagenta;
+ 16r46 => fg = fgCyan;
+ 16r47 => fg = fgWhite;
+
+ # background colour
+ 16r50 => bg = bgBlack;
+ 16r51 => bg = bgRed;
+ 16r52 => bg = bgGreen;
+ 16r53 => bg = bgYellow;
+ 16r54 => bg = bgBlue;
+ 16r55 => bg = bgMagenta;
+ 16r56 => bg = bgCyan;
+ 16r57 => bg = bgWhite;
+
+ # flashing
+ 16r48 => s.attr |= attrF;
+ 16r49 => s.attr &= ~attrF;
+
+ # conceal (serial attribute)
+ 16r58 => s.attr |= attrC;
+ s.delimit = 1;
+ 16r5f => s.attr &= ~attrC;
+ s.delimit = 1;
+
+ # start lining (+separated graphics) (serial attribute)
+ 16r5a => s.attr |= attrL;
+ s.delimit = 1;
+ 16r59 => s.attr &= ~attrL;
+ s.delimit = 1;
+
+ # reverse polarity
+ 16r5d => s.attr |= attrP;
+ 16r5c => s.attr &= ~attrP;
+
+ # normal size
+ 16r4c =>
+ s.attr &= ~(attrW|attrH);
+
+ # double height
+ 16r4d =>
+ if(s.pos.y < 2)
+ break;
+ s.attr &= ~(attrW|attrH);
+ s.attr |= attrH;
+
+ # double width
+ 16r4e =>
+ if(s.pos.y < 1)
+ break;
+ s.attr &= ~(attrW|attrH);
+ s.attr |= attrW;
+
+ # double size
+ 16r4f =>
+ if(s.pos.y < 2)
+ break;
+ s.attr |= (attrW|attrH);
+ }
+ if(fg >= 0) {
+ s.attr &= ~fgMask;
+ s.attr |= fg;
+ }
+ if(bg >= 0) {
+ s.attr &= ~bgMask;
+ s.attr |= bg;
+ s.delimit = 1;
+ }
+ s.state = Sstart;
+}
+
+
+# process a SS2 character
+vss2(s: ref Screen, ch: int)
+{
+ if(ISC0(ch)) {
+ s.state = Sstart;
+ vc0(s, ch);
+ return;
+ }
+ case ch {
+ 16r41 or # grave # 5.1.2
+ 16r42 or # acute
+ 16r43 or # circumflex
+ 16r48 or # umlaut
+ 16r4b => # cedilla
+ s.a0 = ch;
+ s.state = Saccent;
+ return;
+ 16r23 => ch = '£'; # Figure 2.8
+ 16r24 => ch = '$';
+ 16r26 => ch = '#';
+ 16r27 => ch = '§';
+ 16r2c => ch = 16rc3; # '←';
+ 16r2d => ch = 16rc0; # '↑';
+ 16r2e => ch = 16rc4; # '→';
+ 16r2f => ch = 16rc5; # '↓';
+ 16r30 => ch = '°';
+ 16r31 => ch = '±';
+ 16r38 => ch = '÷';
+ 16r3c => ch = '¼';
+ 16r3d => ch = '½';
+ 16r3e => ch = '¾';
+ 16r7a => ch = 'œ';
+ 16r6a => ch = 'Œ';
+ 16r7b => ch = 'ß';
+ }
+ s.put(tostr(ch));
+ s.savech = ch;
+ s.state = Sstart;
+}
+
+# process CSI functions
+vcsi(s: ref Screen, ch: int)
+{
+ case s.state {
+ Scsi0 =>
+ case ch {
+ # move cursor up n rows, stop at top of screen
+ 'A' =>
+ s.pos.y -= s.a0;
+ if(s.pos.y < 1)
+ s.pos.y = 1;
+
+ # move cursor down n rows, stop at bottom of screen
+ 'B' =>
+ s.pos.y += s.a0;
+ if(s.pos.y >= s.rows)
+ s.pos.y = s.rows - 1;
+
+ # move cursor n columns right, stop at edge of screen
+ 'C' =>
+ s.pos.x += s.a0;
+ if(s.pos.x > s.cols)
+ s.pos.x = s.cols;
+
+ # move cursor n columns left, stop at edge of screen
+ 'D' =>
+ s.pos.x -= s.a0;
+ if(s.pos.x < 1)
+ s.pos.x = 1;
+
+ # direct cursor addressing
+ ';' =>
+ s.state = Scsi1;
+ return;
+
+ 'J' =>
+ case s.a0 {
+ # clears from the cursor to the end of the screen inclusive
+ 0 =>
+ rowclear(s.pos.y, s.pos.x, s.cols);
+ for(r:=s.pos.y+1; r<s.rows; r++)
+ rowclear(r, 1, s.cols);
+ # clears from the beginning of the screen to the cursor inclusive
+ 1 =>
+ for(r:=1; r<s.pos.y; r++)
+ rowclear(r, 1, s.cols);
+ rowclear(s.pos.y, 1, s.pos.x);
+ # clears the entire screen
+ 2 =>
+ clear(s);
+ }
+
+ 'K' =>
+ case s.a0 {
+ # clears from the cursor to the end of the row
+ 0 => rowclear(s.pos.y, s.pos.x, s.cols);
+
+ # clears from the start of the row to the cursor
+ 1 => rowclear(s.pos.y, 1, s.pos.x);
+
+ # clears the entire row in which the cursor is positioned
+ 2 => rowclear(s.pos.y, 1, s.cols);
+ }
+
+ # deletes n characters from cursor position
+ 'P' =>
+ rowclear(s.pos.y, s.pos.x, s.pos.x+s.a0-1);
+
+ # inserts n characters from cursor position
+ '@' =>
+ disp->Put(dup(' ', s.a0), Point(s.pos.x,s.pos.y), s.cset, s.attr, 1);
+
+ # starts cursor insert mode
+ 'h' =>
+ if(s.a0 == 4)
+ s.spec |= Insert;
+
+ 'l' => # ends cursor insert mode
+ if(s.a0 == 4)
+ s.spec &= ~Insert;
+
+ # deletes n rows from cursor row
+ 'M' =>
+ scroll(s.pos.y, s.a0);
+
+ # inserts n rows from cursor row
+ 'L' =>
+ scroll(s.pos.y, -1*s.a0);
+ }
+ s.state = Sstart;
+ Scsi1 =>
+ case ch {
+ # direct cursor addressing
+ 'H' =>
+ if(s.a0 > 0 && s.a0 < s.rows && s.a1 > 0 && s.a1 <= s.cols)
+ s.pos = Point(s.a1, s.a0);
+ }
+ s.state = Sstart;
+ }
+}
+
+# Screen state - Videotex mode
+vstate(s: ref Screen, data: array of byte): array of byte
+{
+ i: int;
+ for(i = 0; i < len data; i++) {
+ ch := int data[i];
+
+ if(debug['s']) {
+ cs:="";
+ if(s.cset==videotex) cs = "v"; else cs="s";
+ fprint(stderr, "vstate %d, %ux (%c) %.4ux %.4ux %s (%d,%d)\n", s.state, ch, ch, s.attr, s.spec, cs, s.pos.y, s.pos.x);
+ }
+ case s.state {
+ Sstart =>
+ if(ISG0(ch) || ch == SP) {
+ n := 0;
+ str := "";
+ while(i < len data) {
+ ch = int data[i];
+ if(ISG0(ch) || ch == SP)
+ str[n++] = int data[i++];
+ else {
+ i--;
+ break;
+ }
+ }
+ if(n > 0) {
+ if(debug['s'])
+ fprint(stderr, "vstate puts(%s)\n", str);
+ s.put(str);
+ s.savech = str[n-1];
+ }
+ } else if(ISC0(ch))
+ vc0(s, ch);
+ else if(ch == DEL) {
+ if(s.cset == semigraphic)
+ ch = 16r5f;
+ s.put(tostr(ch));
+ s.savech = ch;
+ }
+ Sss2 =>
+ if(ch == NUL) # 1.2.6.1
+ continue;
+ if(s.cset == semigraphic) # 1.2.3.4
+ continue;
+ vss2(s, ch);
+ Sesc =>
+ if(ch == NUL)
+ continue;
+ vc1(s, ch);
+ Srepeat =>
+ # byte from `columns' 4 to 7 gives repeat count on 6 bits
+ # of the last `Put' character
+ if(ch == NUL)
+ continue;
+ if(ISC0(ch)) {
+ s.state = Sstart;
+ vc0(s, ch);
+ break;
+ }
+ if(ch >= 16r40 && ch <= 16r7f)
+ s.put(dup(s.savech, (ch-16r40)));
+ s.state = Sstart;
+ Saccent =>
+ case s.a0 {
+ 16r41 => # grave
+ case ch {
+ 'a' => ch = 'à';
+ 'e' => ch = 'è';
+ 'u' => ch = 'ù';
+ }
+ 16r42 => # acute
+ case ch {
+ 'e' => ch = 'é';
+ }
+ 16r43 => # circumflex
+ case ch {
+ 'a' => ch = 'â';
+ 'e' => ch = 'ê';
+ 'i' => ch = 'î';
+ 'o' => ch = 'ô';
+ 'u' => ch = 'û';
+ }
+ 16r48 => # umlaut
+ case ch {
+ 'a' => ch = 'ä';
+ 'e' => ch = 'ë';
+ 'i' => ch = 'ï';
+ 'o' => ch = 'ö';
+ 'u' => ch = 'ü';
+ }
+ 16r4b => # cedilla
+ case ch {
+ 'c' => ch = 'ç';
+ }
+ }
+ s.put(tostr(ch));
+ s.savech = ch;
+ s.state = Sstart;
+ Scsi0 =>
+ if(ch >= 16r30 && ch <= 16r39) {
+ s.a0 *= 10;
+ s.a0 += (ch - 16r30);
+ } else if((ch >= 16r20 && ch <= 16r29) || (ch >= 16r3a && ch <= 16r3f)) { # 1.2.7
+ s.a0 = 0;
+ s.state = Siso6429;
+ } else
+ vcsi(s, ch);
+ Scsi1 =>
+ if(ch >= 16r30 && ch <= 16r39) {
+ s.a1 *= 10;
+ s.a1 += (ch - 16r30);
+ } else
+ vcsi(s, ch);
+ Sus0 =>
+ if(ch == 16r23) { # start DRCS definition
+ s.state = Sdrcs;
+ s.a0 = 0;
+ break;
+ }
+ if(ch >= 16r40 && ch < 16r80)
+ s.a0 = (ch - 16r40);
+ else if(ch >= 16r30 && ch <= 16r32)
+ s.a0 = (ch - 16r30);
+ else
+ s.a0 = -1;
+ s.state = Sus1;
+ Sus1 =>
+ if(ch >= 16r40 && ch < 16r80)
+ s.a1 = (ch - 16r40);
+ else if(ch >= 16r30 && ch <= 16r39) {
+ s.a1 = (ch - 16r30);
+ s.a0 = s.a0*10 + s.a1; # shouldn't be used any more
+ s.a1 = 1;
+ } else
+ s.a1 = -1;
+ # US row, col : this is how you get to row zero
+ if(s.a0 >= 0 && s.a0 < s.rows && s.a1 > 0 && s.a1 <= s.cols) {
+ if(s.a0 == 0 && s.pos.y > 0) {
+ s.savepos = s.pos;
+ s.saveattr = s.attr;
+ }
+ s.pos = Point(s.a1, s.a0);
+ s.delimit = 0; # 1.2.5.3, don't reset serial attributes
+ s.attr = ATTR0;
+ s.cset = videotex;
+ }
+ s.state = Sstart;
+ Sskip =>
+ # swallow the next character unless from C0
+ s.state = Sstart;
+ if(ISC0(ch))
+ vc0(s, ch);
+ Swaitfor =>
+ # ignore characters until the character in a0 inclusive
+ if(ch == s.a0)
+ s.state = Sstart;
+ Siso2022 =>
+ # 1.2.7
+ # swallow (upto) 3 characters from column 2,
+ # then 1 character from columns 3 to 7
+ if(ch == NUL)
+ continue;
+ if(ISC0(ch)) {
+ s.state = Sstart;
+ vc0(s, ch);
+ break;
+ }
+ s.a0++;
+ if(s.a0 <= 3) {
+ if(ch >= 16r20 && ch <= 16r2f)
+ break;
+ }
+ if (s.a0 <= 4 && ch >= 16r30 && ch <= 16r7f) {
+ s.state = Sstart;
+ break;
+ }
+ s.state = Sstart;
+ s.put(tostr(DEL));
+ Siso6429 =>
+ # 1.2.7
+ # swallow characters from column 3,
+ # or column 2, then 1 from column 4 to 7
+ if(ISC0(ch)) {
+ s.state = Sstart;
+ vc0(s, ch);
+ break;
+ }
+ if(ch >= 16r20 && ch <= 16r3f)
+ break;
+ if(ch >= 16r40 && ch <= 16r7f) {
+ s.state = Sstart;
+ break;
+ }
+ s.state = Sstart;
+ s.put(tostr(DEL));
+ Stransparent =>
+ # 1.2.7
+ # ignore all codes until ESC, 25, 40 or ESC, 2F, 3F
+ # progress in s.a0 and s.a1
+ match := array [] of {
+ array [] of { ESC, 16r25, 16r40 },
+ array [] of { ESC, 16r2f, 16r3f },
+ };
+ if(ch == ESC) {
+ s.a0 = s.a1 = 1;
+ break;
+ }
+ if(ch == match[0][s.a0])
+ s.a0++;
+ else
+ s.a0 = 0;
+ if(ch == match[1][s.a1])
+ s.a1++;
+ else
+ s.a1 = 0;
+ if(s.a0 == 3 || s.a1 == 3)
+ s.state = Sstart;
+ Sdrcs =>
+ if(s.a0 > 0) { # fixed number of bytes to skip in a0
+ s.a0--;
+ if(s.a0 == 0) {
+ s.state = Sstart;
+ break;
+ }
+ } else if(ch == US) # US XX YY - end of DRCS
+ s.state = Sus0;
+ else if(ch == 16r20) # US 23 20 20 20 4[23] 49
+ s.a0 = 4;
+ Sconceal =>
+ # 1.2.4.4
+ # ESC 23 20 58 - Conceal fields
+ # ESC 23 20 5F - Reveal fields
+ # ESC 23 21 XX - Filter
+ # progress in s.a0
+ case s.a0 {
+ 0 =>
+ if(ch == 16r20 || ch == 16r21)
+ s.a0 = ch;
+ 16r20 =>
+ case ch {
+ 16r58 =>
+ disp->Reveal(0);
+ disp->Refresh();
+ 16r5f =>
+ disp->Reveal(1);
+ disp->Refresh();
+ }
+ s.state = Sstart;
+ 16r21 =>
+ s.state = Sstart;
+ }
+ }
+ }
+ if (i < len data)
+ return data[i:];
+ else
+ return nil;
+}
+
+# Screen state - Mixed mode
+mstate(s: ref Screen, data: array of byte): array of byte
+{
+ i: int;
+Stateloop:
+ for(i = 0; i < len data; i++) {
+ ch := int data[i];
+
+ if(debug['s']) {
+ cs:="";
+ if(s.cset==videotex) cs = "v"; else cs="s";
+ fprint(stderr, "mstate %d, %ux (%c) %.4ux %.4ux %s (%d,%d)\n", s.state, ch, ch, s.attr, s.fstate, cs, s.pos.y, s.pos.x);
+ }
+ case s.state {
+ Sstart =>
+ if(ISG0(ch) || ch == SP) {
+ n := 0;
+ str := "";
+ while(i < len data) {
+ ch = int data[i];
+ if(ISG0(ch) || ch == SP)
+ str[n++] = int data[i++];
+ else {
+ i--;
+ break;
+ }
+ }
+ if(n > 0) {
+ if(debug['s'])
+ fprint(stderr, "mstate puts(%s)\n", str);
+ s.put(str);
+ s.savech = str[n-1];
+ }
+ } else if(ISC0(ch))
+ mc0(s, ch);
+ else if(ch == DEL) {
+ if(s.cset == semigraphic)
+ ch = 16r5f;
+ s.put(tostr(ch));
+ s.savech = ch;
+ }
+ Sesc =>
+ if(ch == NUL)
+ continue;
+ mc1(s, ch);
+ Scsi0 =>
+ if(ch >= 16r30 && ch <= 16r39) {
+ s.a0 *= 10;
+ s.a0 += (ch - 16r30);
+ } else if(ch == '?') {
+ s.a0 = '?';
+ } else
+ mcsi(s, ch);
+ if(T.mode != Mixed) # CSI ? { changes to Videotex mode
+ break Stateloop;
+ Scsi1 =>
+ if(ch >= 16r30 && ch <= 16r39) {
+ s.a1 *= 10;
+ s.a1 += (ch - 16r30);
+ } else
+ mcsi(s, ch);
+ Sus0 =>
+ if(ch >= 16r40 && ch < 16r80)
+ s.a0 = (ch - 16r40);
+ else if(ch >= 16r30 && ch <= 16r32)
+ s.a0 = (ch - 16r30);
+ else
+ s.a0 = -1;
+ s.state = Sus1;
+ Sus1 =>
+ if(ch >= 16r40 && ch < 16r80)
+ s.a1 = (ch - 16r40);
+ else if(ch >= 16r30 && ch <= 16r39) {
+ s.a1 = (ch - 16r30);
+ s.a0 = s.a0*10 + s.a1; # shouldn't be used any more
+ s.a1 = 1;
+ } else
+ s.a1 = -1;
+ # US row, col : this is how you get to row zero
+ if(s.a0 >= 0 && s.a0 < s.rows && s.a1 > 0 && s.a1 <= s.cols) {
+ if(s.a0 == 0 && s.pos.y > 0) {
+ s.savepos = s.pos;
+ s.saveattr = s.attr;
+ }
+ s.pos = Point(s.a1, s.a0);
+ s.delimit = 0; # 1.2.5.3, don't reset serial attributes
+ s.attr = ATTR0;
+ s.cset = videotex;
+ }
+ s.state = Sstart;
+ Siso6429 =>
+ # 1.2.7
+ # swallow characters from column 3,
+ # or column 2, then 1 from column 4 to 7
+ if(ISC0(ch)) {
+ s.state = Sstart;
+ mc0(s, ch);
+ break;
+ }
+ if(ch >= 16r20 && ch <= 16r3f)
+ break;
+ if(ch >= 16r40 && ch <= 16r7f) {
+ s.state = Sstart;
+ break;
+ }
+ s.state = Sstart;
+ s.put(tostr(DEL));
+ }
+ }
+ if (i < len data)
+ return data[i:];
+ else
+ return nil;
+ return nil;
+}
+
+# process a byte from set C0 - Mixed mode
+mc0(s: ref Screen, ch: int)
+{
+ case ch {
+ ESC =>
+ s.state = Sesc;
+ SO =>
+# s.cset = french;
+ ;
+ SI =>
+# s.cset = american;
+ ;
+ BS =>
+ if(s.pos.x > 1)
+ s.pos.x -= 1;
+ HT =>
+ s.pos.x += 8;
+ if(s.pos.x > s.cols)
+ s.pos.x = s.cols;
+ LF or VT or FF =>
+ if(s.pos.y == s.rows - 1)
+ if(s.spec&Scroll)
+ scroll(1, 1);
+ else
+ s.pos.y = 1;
+ else if(s.pos.y == 0) { # restore attributes on leaving row zero
+ if(ch == LF) { # 4.5
+ s.pos = s.savepos;
+ s.attr = s.saveattr;
+ }
+ } else
+ s.pos.y += 1;
+ CR =>
+ s.pos.x = 1;
+ CAN or SUB => # displays the error symbol - filled in rectangle
+ disp->Put(dup(16r5f, 1), Point(s.pos.x,s.pos.y), s.cset, s.attr, 0);
+ NUL =>
+ # padding character - ignore, but may appear anywhere
+ ;
+ BEL =>
+ # ah ...
+ ;
+ XON => # screen copying
+ ;
+ XOFF => # screen copying
+ ;
+ US =>
+ # expect US row, col
+ s.state = Sus0;
+ }
+}
+
+# process a byte from the set c1 - introduced by the ESC character - Mixed mode
+mc1(s: ref Screen, ch: int)
+{
+ if(ISC0(ch)) {
+ s.state = Sstart;
+ mc0(s, ch);
+ return;
+ }
+ case ch {
+ 16r5b => # CSI sequence
+ s.a0 = s.a1 = 0;
+ if(s.pos.y > 0) # 1.2.5.2
+ s.state = Scsi0;
+ return;
+
+ 16r44 or # IND like LF
+ 16r45 => # NEL like CR LF
+ if(ch == 16r45)
+ s.pos.x = 1;
+ if(s.pos.y == s.rows - 1)
+ if(s.spec&Scroll)
+ scroll(1, 1);
+ else
+ s.pos.y = 1;
+ else if(s.pos.y == 0) { # restore attributes on leaving row zero
+ s.pos = s.savepos;
+ s.attr = s.saveattr;
+ } else
+ s.pos.y += 1;
+ 16r4d => # RI
+ if(s.pos.y == 1)
+ if(s.spec&Scroll)
+ scroll(1, -1);
+ else
+ s.pos.y = s.rows - 1;
+ else if(s.pos.y == 0)
+ break;
+ else
+ s.pos.y -= 1;
+ }
+ s.state = Sstart;
+}
+
+
+# process CSI functions - Mixed mode
+mcsi(s: ref Screen, ch: int)
+{
+ case s.state {
+ Scsi0 =>
+ case ch {
+ # move cursor up n rows, stop at top of screen
+ 'A' =>
+ if(s.a0 == 0)
+ s.a0 = 1;
+ s.pos.y -= s.a0;
+ if(s.pos.y < 1)
+ s.pos.y = 1;
+
+ # move cursor down n rows, stop at bottom of screen
+ 'B' =>
+ if(s.a0 == 0)
+ s.a0 = 1;
+ s.pos.y += s.a0;
+ if(s.pos.y >= s.rows)
+ s.pos.y = s.rows - 1;
+
+ # move cursor n columns right, stop at edge of screen
+ 'C' =>
+ if(s.a0 == 0)
+ s.a0 = 1;
+ s.pos.x += s.a0;
+ if(s.pos.x > s.cols)
+ s.pos.x = s.cols;
+
+ # move cursor n columns left, stop at edge of screen
+ 'D' =>
+ if(s.a0 == 0)
+ s.a0 = 1;
+ s.pos.x -= s.a0;
+ if(s.pos.x < 1)
+ s.pos.x = 1;
+
+ # second parameter
+ ';' =>
+ s.state = Scsi1;
+ return;
+
+ 'J' =>
+ case s.a0 {
+ # clears from the cursor to the end of the screen inclusive
+ 0 =>
+ rowclear(s.pos.y, s.pos.x, s.cols);
+ for(r:=s.pos.y+1; r<s.rows; r++)
+ rowclear(r, 1, s.cols);
+ # clears from the beginning of the screen to the cursor inclusive
+ 1 =>
+ for(r:=1; r<s.pos.y; r++)
+ rowclear(r, 1, s.cols);
+ rowclear(s.pos.y, 1, s.pos.x);
+ # clears the entire screen
+ 2 =>
+ clear(s);
+ }
+
+ 'K' =>
+ case s.a0 {
+ # clears from the cursor to the end of the row
+ 0 => rowclear(s.pos.y, s.pos.x, s.cols);
+
+ # clears from the start of the row to the cursor
+ 1 => rowclear(s.pos.y, 1, s.pos.x);
+
+ # clears the entire row in which the cursor is positioned
+ 2 => rowclear(s.pos.y, 1, s.cols);
+ }
+
+ # inserts n characters from cursor position
+ '@' =>
+ disp->Put(dup(' ', s.a0), Point(s.pos.x,s.pos.y), s.cset, s.attr, 1);
+
+ # starts cursor insert mode
+ 'h' =>
+ if(s.a0 == 4)
+ s.spec |= Insert;
+
+ 'l' => # ends cursor insert mode
+ if(s.a0 == 4)
+ s.spec &= ~Insert;
+
+ # inserts n rows from cursor row
+ 'L' =>
+ scroll(s.pos.y, -1*s.a0);
+ s.pos.x = 1;
+
+ # deletes n rows from cursor row
+ 'M' =>
+ scroll(s.pos.y, s.a0);
+ s.pos.x = 1;
+
+ # deletes n characters from cursor position
+ 'P' =>
+ rowclear(s.pos.y, s.pos.x, s.pos.x+s.a0-1);
+
+ # select Videotex mode
+ '{' =>
+ if(s.a0 == '?') {
+ T.mode = Videotex;
+ s.setmode(T.mode);
+ }
+
+ # display attributes
+ 'm' =>
+ case s.a0 {
+ 0 => s.attr &= ~(attrL|attrF|attrP|attrB);
+ 1 => s.attr |= attrB;
+ 4 => s.attr |= attrL;
+ 5 => s.attr |= attrF;
+ 7 => s.attr |= attrP;
+ 22 => s.attr &= ~attrB;
+ 24 => s.attr &= ~attrL;
+ 25 => s.attr &= ~attrF;
+ 27 => s.attr &= ~attrP;
+ }
+ # direct cursor addressing
+ 'H' =>
+ if(s.a0 == 0)
+ s.a0 = 1;
+ if(s.a1 == 0)
+ s.a1 = 1;
+ if(s.a0 > 0 && s.a0 < s.rows && s.a1 > 0 && s.a1 <= s.cols)
+ s.pos = Point(s.a1, s.a0);
+ }
+ s.state = Sstart;
+ Scsi1 =>
+ case ch {
+ # direct cursor addressing
+ 'H' =>
+ if(s.a0 == 0)
+ s.a0 = 1;
+ if(s.a1 == 0)
+ s.a1 = 1;
+ if(s.a0 > 0 && s.a0 < s.rows && s.a1 > 0 && s.a1 <= s.cols)
+ s.pos = Point(s.a1, s.a0);
+ }
+ s.state = Sstart;
+ }
+}
+
+
+# Screen state - ASCII mode
+astate(nil: ref Screen, nil: array of byte): array of byte
+{
+ return nil;
+}
+
+# Put a string in the current attributes to the current writing position
+Screen.put(s: self ref Screen, str: string)
+{
+ while((l := len str) > 0) {
+ n := s.cols - s.pos.x + 1; # characters that will fit on this row
+ if(s.attr & attrW) {
+ if(n > 1) # fit normal width character in last column
+ n /= 2;
+ }
+ if(n > l)
+ n = l;
+ if(s.delimit) { # set delimiter bit on 1st space (if any)
+ for(i:=0; i<n; i++)
+ if(str[i] == ' ')
+ break;
+ if(i > 0) {
+ disp->Put(str[0:i], s.pos, s.cset, s.attr, s.spec&Insert);
+ incpos(s, i);
+ }
+ if(i < n) {
+ if(debug['s']) {
+ cs:="";
+ if(s.cset==videotex) cs = "v"; else cs="s";
+ fprint(stderr, "D %ux %s\n", s.attr|attrD, cs);
+ }
+ disp->Put(tostr(str[i]), s.pos, s.cset, s.attr|attrD, s.spec&Insert);
+ incpos(s, 1);
+ s.delimit = 0;
+ # clear serial attributes once used
+ # hang onto background attribute - needed for semigraphics
+ case s.cset {
+ videotex =>
+ s.attr &= ~(attrL|attrC);
+ semigraphic =>
+ s.attr &= ~(attrC);
+ }
+ }
+ if(i < n-1) {
+ disp->Put(str[i+1:n], s.pos, s.cset, s.attr, s.spec&Insert);
+ incpos(s, n-(i+1));
+ }
+ } else {
+ disp->Put(str[0:n], s.pos, s.cset, s.attr, s.spec&Insert);
+ incpos(s, n);
+ }
+ if(n < len str)
+ str = str[n:];
+ else
+ str = nil;
+ }
+# if(T.state == Local || T.spec&Echo)
+# refresh();
+}
+
+# increment the current writing position by `n' cells.
+# caller must ensure that `n' characters can fit
+incpos(s: ref Screen, n: int)
+{
+ if(s.attr & attrW)
+ s.pos.x += 2*n;
+ else
+ s.pos.x += n;
+ if(s.pos.x > s.cols)
+ if(s.pos.y == 0) # no wraparound from row zero
+ s.pos.x = s.cols;
+ else {
+ s.pos.x = 1;
+ if(s.pos.y == s.rows - 1 && s.spec&Scroll) {
+ if(s.attr & attrH) {
+ scroll(1, 2);
+ } else {
+ scroll(1, 1);
+ rowclear(s.pos.y, 1, s.cols);
+ }
+ } else {
+ if(s.attr & attrH)
+ s.pos.y += 2;
+ else
+ s.pos.y += 1;
+ if(s.pos.y >= s.rows)
+ s.pos.y -= (s.rows-1);
+ }
+ }
+}
+
+# clear row `r' from `first' to `last' column inclusive
+rowclear(r, first, last: int)
+{
+ # 16r5f is the semi-graphic black rectangle
+ disp->Put(dup(16r5f, last-first+1), Point(first,r), semigraphic, fgBlack, 0);
+# disp->Put(dup(' ', last-first+1), Point(first,r), S.cset, fgBlack, 0);
+}
+
+clear(s: ref Screen)
+{
+ for(r:=1; r<s.rows; r++)
+ rowclear(r, 1, s.cols);
+}
+
+# called to suggest a display update
+refresh()
+{
+ disp->Refresh();
+}
+
+# scroll the screen
+scroll(topline, nlines: int)
+{
+ disp->Scroll(topline, nlines);
+ disp->Refresh();
+}
+
+# filter the specified ISO6429 and ISO2022 codes from the screen input
+# TODO: filter some ISO2022 sequences
+filter(s: ref Screen, data: array of byte): array of array of byte
+{
+ case T.mode {
+ Videotex =>
+ return vfilter(s, data);
+ Mixed =>
+ return mfilter(s, data);
+ Ascii =>
+ return afilter(s, data);
+ }
+ return nil;
+}
+
+# filter the specified ISO6429 and ISO2022 codes from the screen input
+vfilter(s: ref Screen, data: array of byte): array of array of byte
+{
+ ba := array [0] of array of byte;
+ changed := 0;
+
+ d0 := 0;
+ for(i:=0; i<len data; i++) {
+ ch := int data[i];
+ case s.fstate {
+ FSstart =>
+ if(ch == ESC) {
+ s.fstate = FSesc;
+ changed = 1;
+ if(i > d0)
+ ba = dappend(ba, data[d0:i]);
+ d0 = i+1;
+ }
+ FSesc =>
+ d0 = i+1;
+ changed = 1;
+ if(ch == '[') {
+ s.fstate = FS6429;
+ s.fsaved = array [0] of byte;
+ s.badp = 0;
+# } else if(ch == 16r20) {
+# s.fstate = FS2022;
+# s.fsaved = array [0] of byte;
+ s.badp = 0;
+ } else if(ch == ESC) {
+ ba = dappend(ba, array [] of { byte ESC });
+ s.fstate = FSesc;
+ } else {
+ # false alarm - don't filter
+ ba = dappend(ba, array [] of { byte ESC, byte ch });
+ s.fstate = FSstart;
+ }
+ FS6429 => # filter out invalid CSI sequences
+ d0 = i+1;
+ changed = 1;
+ if(ch >= 16r20 && ch <= 16r3f) {
+ if((ch < 16r30 || ch > 16r39) && ch != ';')
+ s.badp = 1;
+ a := array [len s.fsaved + 1] of byte;
+ a[0:] = s.fsaved[0:];
+ a[len a - 1] = byte ch;
+ s.fsaved = a;
+ } else {
+ valid := 1;
+ case ch {
+ 'A' => ;
+ 'B' => ;
+ 'C' => ;
+ 'D' => ;
+ 'H' => ;
+ 'J' => ;
+ 'K' => ;
+ 'P' => ;
+ '@' => ;
+ 'h' => ;
+ 'l' => ;
+ 'M' => ;
+ 'L' => ;
+ * =>
+ valid = 0;
+ }
+ if(s.badp)
+ valid = 0;
+ if(debug['f'])
+ fprint(stderr, "vfilter %d: %s%c\n", valid, string s.fsaved, ch);
+ if(valid) { # false alarm - don't filter
+ ba = dappend(ba, array [] of { byte ESC, byte '[' });
+ ba = dappend(ba, s.fsaved);
+ ba = dappend(ba, array [] of { byte ch } );
+ }
+ s.fstate = FSstart;
+ }
+ FS2022 => ;
+ }
+ }
+ if(changed) {
+ if(i > d0)
+ ba = dappend(ba, data[d0:i]);
+ return ba;
+ }
+ return array [] of { data };
+}
+
+# filter the specified ISO6429 and ISO2022 codes from the screen input - Videotex
+mfilter(s: ref Screen, data: array of byte): array of array of byte
+{
+ ba := array [0] of array of byte;
+ changed := 0;
+
+ d0 := 0;
+ for(i:=0; i<len data; i++) {
+ ch := int data[i];
+ case s.fstate {
+ FSstart =>
+ case ch {
+ ESC =>
+ s.fstate = FSesc;
+ changed = 1;
+ if(i > d0)
+ ba = dappend(ba, data[d0:i]);
+ d0 = i+1;
+ SEP =>
+ s.fstate = FSsep;
+ changed = 1;
+ if(i > d0)
+ ba = dappend(ba, data[d0:i]);
+ d0 = i+1;
+ }
+ FSesc =>
+ d0 = i+1;
+ changed = 1;
+ if(ch == '[') {
+ s.fstate = FS6429;
+ s.fsaved = array [0] of byte;
+ s.badp = 0;
+ } else if(ch == ESC) {
+ ba = dappend(ba, array [] of { byte ESC });
+ s.fstate = FSesc;
+ } else {
+ # false alarm - don't filter
+ ba = dappend(ba, array [] of { byte ESC, byte ch });
+ s.fstate = FSstart;
+ }
+ FSsep =>
+ d0 = i+1;
+ changed = 1;
+ if(ch == ESC) {
+ ba = dappend(ba, array [] of { byte SEP });
+ s.fstate = FSesc;
+ } else if(ch == SEP) {
+ ba = dappend(ba, array [] of { byte SEP });
+ s.fstate = FSsep;
+ } else {
+ if(ch >= 16r00 && ch <= 16r1f)
+ ba = dappend(ba, array [] of { byte SEP , byte ch });
+ # consume the character
+ s.fstate = FSstart;
+ }
+ FS6429 => # filter out invalid CSI sequences
+ d0 = i+1;
+ changed = 1;
+ if(ch >= 16r20 && ch <= 16r3f) {
+ if((ch < 16r30 || ch > 16r39) && ch != ';' && ch != '?')
+ s.badp = 1;
+ a := array [len s.fsaved + 1] of byte;
+ a[0:] = s.fsaved[0:];
+ a[len a - 1] = byte ch;
+ s.fsaved = a;
+ } else {
+ valid := 1;
+ case ch {
+ 'm' => ;
+ 'A' => ;
+ 'B' => ;
+ 'C' => ;
+ 'D' => ;
+ 'H' => ;
+ 'J' => ;
+ 'K' => ;
+ '@' => ;
+ 'h' => ;
+ 'l' => ;
+ 'L' => ;
+ 'M' => ;
+ 'P' => ;
+ '{' => # allow CSI ? {
+ n := len s.fsaved;
+ if(n == 0 || s.fsaved[n-1] != byte '?')
+ s.badp = 1;
+ * =>
+ valid = 0;
+ }
+ if(s.badp) # only decimal params
+ valid = 0;
+ if(debug['f'])
+ fprint(stderr, "mfilter %d: %s%c\n", valid, string s.fsaved, ch);
+ if(valid) { # false alarm - don't filter
+ ba = dappend(ba, array [] of { byte ESC, byte '[' });
+ ba = dappend(ba, s.fsaved);
+ ba = dappend(ba, array [] of { byte ch } );
+ }
+ s.fstate = FSstart;
+ }
+ FS2022 => ;
+ }
+ }
+ if(changed) {
+ if(i > d0)
+ ba = dappend(ba, data[d0:i]);
+ return ba;
+ }
+ return array [] of { data };
+}
+
+# filter the specified ISO6429 and ISO2022 codes from the screen input - Videotex
+afilter(nil: ref Screen, data: array of byte): array of array of byte
+{
+ return array [] of { data };
+}
+
+# append to an array of array of byte
+dappend(ba: array of array of byte, b: array of byte): array of array of byte
+{
+ l := len ba;
+ na := array [l+1] of array of byte;
+ na[0:] = ba[0:];
+ na[l] = b;
+ return na;
+}
+
+# Put a diagnostic string to row 0
+Screen.msg(s: self ref Screen, str: string)
+{
+ blank := string array [s.cols -4] of {* => byte ' '};
+ n := len str;
+ if(n > s.cols - 4)
+ n = s.cols - 4;
+ disp->Put(blank, Point(1, 0), videotex, 0, 0);
+ if(str != nil)
+ disp->Put(str[0:n], Point(1, 0), videotex, fgWhite|attrB, 0);
+ disp->Refresh();
+} \ No newline at end of file
diff --git a/appl/wm/minitel/socket.b b/appl/wm/minitel/socket.b
new file mode 100644
index 00000000..b3ce7fcf
--- /dev/null
+++ b/appl/wm/minitel/socket.b
@@ -0,0 +1,49 @@
+#
+# Copyright © 1998 Vita Nuova Limited. All rights reserved.
+#
+
+Socket: adt {
+ m: ref Module; # common attributes
+ in: chan of ref Event;
+
+ init: fn(c: self ref Socket);
+ reset: fn(c: self ref Socket);
+ run: fn(c: self ref Socket);
+ quit: fn(c: self ref Socket);
+};
+
+Socket.init(c: self ref Socket)
+{
+ c.in = chan of ref Event;
+ c.reset();
+}
+
+Socket.reset(c: self ref Socket)
+{
+ c.m = ref Module(Pscreen, 0);
+}
+
+Socket.run(c: self ref Socket)
+{
+Runloop:
+ for(;;){
+ ev := <- c.in;
+ pick e := ev {
+ Equit =>
+ break Runloop;
+ Eproto =>
+ case e.cmd {
+ Creset =>
+ c.reset();
+ * => break;
+ }
+ Edata =>
+ }
+ }
+ send(nil);
+}
+
+Socket.quit(c: self ref Socket)
+{
+ if(c==nil);
+}
diff --git a/appl/wm/minitel/swkeyb.b b/appl/wm/minitel/swkeyb.b
new file mode 100644
index 00000000..50cb238f
--- /dev/null
+++ b/appl/wm/minitel/swkeyb.b
@@ -0,0 +1,370 @@
+###
+### This data and information is not to be used as the basis of manufacture,
+### or be reproduced or copied, or be distributed to another party, in whole
+### or in part, without the prior written consent of Lucent Technologies.
+###
+### (C) Copyright 1997 Lucent Technologies
+###
+### Written by N. W. Knauft
+###
+#
+# Revisions Copyright © 1998 Vita Nuova Limited.
+
+implement Keyboard;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "swkeyb.m";
+
+#Icon path
+ICPATH: con "keybd/";
+
+#Font
+FONT: con "/fonts/lucidasans/latin1.7.font";
+SPECFONT: con "/fonts/lucidasans/latin1.6.font";
+
+# Dimension constants
+KBDWIDTH: con 360;
+KBDHEIGHT: con 120;
+KEYSIZE: con "19";
+KEYSPACE: con 5;
+KEYBORDER: con 1;
+KEYGAP: con KEYSPACE - (2 * KEYBORDER);
+ENDGAP: con 2 - KEYBORDER;
+
+# Row size constants (cumulative)
+ROW1: con 14;
+ROW2: con 28;
+ROW3: con 41;
+ROW4: con 53;
+NKEYS: con 63;
+
+#Special key number constants
+DELKEY: con 13;
+TABKEY: con 14;
+BACKSLASHKEY: con 27;
+CAPSLOCKKEY: con 28 ;
+RETURNKEY: con 40;
+LSHIFTKEY: con 41;
+RSHIFTKEY: con 52;
+ESCKEY: con 53;
+CTRLKEY: con 54;
+METAKEY: con 55;
+ALTKEY: con 56;
+SPACEKEY: con 57;
+ENTERKEY: con 58;
+LEFTKEY: con 59;
+RIGHTKEY: con 60;
+DOWNKEY: con 61;
+UPKEY: con 62;
+
+#Special key code constants
+CAPSLOCK: con -1 ;
+SHIFT: con -2;
+CTRL: con -3;
+ALT: con -4;
+META: con -5;
+MAGIC_PREFIX: con 256;
+ARROW_OFFSET: con 57344;
+ARROW_PREFIX: con ARROW_OFFSET + 18;
+
+#Special key width constants
+DELSIZE: con 44;
+TABSIZE: con 32;
+BACKSLASHSIZE: con 31;
+CAPSLOCKSIZE: con 44;
+RETURNSIZE: con 43;
+LSHIFTSIZE: con 56;
+RSHIFTSIZE: con 55;
+ESCSIZE: con 21;
+CTRLSIZE: con 23;
+METASIZE: con 38;
+ALTSIZE: con 22;
+SPACESIZE: con 100;
+ENTERSIZE: con 31;
+
+#Arrow key code constants
+UP: con ARROW_PREFIX;
+DOWN: con ARROW_PREFIX + 1;
+LEFT: con ARROW_PREFIX + 2;
+RIGHT: con ARROW_PREFIX + 3;
+
+direction:= array[] of {"up", "down", "left", "right"};
+row_dimensions:= array[] of {0, ROW1, ROW2, ROW3, ROW4, NKEYS};
+
+special_keys:= array[] of {
+ (DELKEY, DELSIZE),
+ (TABKEY, TABSIZE),
+ (BACKSLASHKEY, BACKSLASHSIZE),
+ (CAPSLOCKKEY, CAPSLOCKSIZE),
+ (RETURNKEY, RETURNSIZE),
+ (LSHIFTKEY, LSHIFTSIZE),
+ (RSHIFTKEY, RSHIFTSIZE),
+ (ESCKEY, ESCSIZE),
+ (CTRLKEY, CTRLSIZE),
+ (METAKEY, METASIZE),
+ (ALTKEY, ALTSIZE),
+ (SPACEKEY, SPACESIZE),
+ (ENTERKEY, ENTERSIZE),
+};
+
+keys:= array[] of {
+ # Unshifted
+ "`", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "Delete",
+ "Tab", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", "\\\\",
+ "CapLoc", "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "\'", "Return",
+ "Shift", "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", "Shift",
+ "Esc", "Ctrl", " ", "Alt", " ", "Enter", "<-", "->", "v", "^",
+ # Shifted
+ "~", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "+", "Delete",
+ "Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "\\{", "\\}", "|",
+ "CapLoc", "A", "S", "D", "F", "G", "H", "J", "K", "L", ":", "\"", "Return",
+ "Shift", "Z", "X", "C", "V", "B", "N", "M", "<", ">", "?", "Shift",
+ "Esc", "Ctrl", " ", "Alt", " ", "Enter", "<-", "->", "v", "^",
+};
+
+keyvals:= array[] of {
+ # Unshifted
+ '`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',
+ '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\',
+ CAPSLOCK, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '\n',
+ SHIFT, 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', SHIFT,
+ 27, CTRL, META, ALT, 32, '\n', LEFT, RIGHT, DOWN, UP,
+ # Shifted
+ '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b',
+ '\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '|',
+ CAPSLOCK, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '\n',
+ SHIFT, 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', SHIFT,
+ 27, CTRL, META, ALT, 32, '\n', LEFT, RIGHT, DOWN, UP,
+};
+
+rowlayout := array[] of {
+ "frame .f1",
+ "frame .f2",
+ "frame .f3",
+ "frame .f4",
+ "frame .f5",
+ "frame .dummy0 -height " + string (ENDGAP),
+ "frame .dummy1 -height " + string KEYGAP,
+ "frame .dummy2 -height " + string KEYGAP,
+ "frame .dummy3 -height " + string KEYGAP,
+ "frame .dummy4 -height " + string KEYGAP,
+ "frame .dummy5 -height " + string (ENDGAP + 1),
+};
+
+# Move key flags
+move_key_enabled := 0;
+meta_active := 0;
+
+# Create keyboard widget, spawn keystroke handler
+initialize(t: ref Tk->Toplevel, ctxt : ref Draw->Context, dot: string): chan of string
+{
+ return chaninit(t, ctxt, dot, chan of string);
+}
+
+chaninit(t: ref Tk->Toplevel, ctxt : ref Draw->Context, dot: string, rc: chan of string): chan of string
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+
+ tkclient->init();
+
+ tk->cmd(t, "frame " + dot + " -bd 2 -relief raised -width " + string KBDWIDTH
+ + " -height " + string KBDHEIGHT);
+ tkcmds(t, rowlayout);
+
+ for(i := 0; i < NKEYS; i++) {
+ tk->cmd(t, "button .b" + string i + " -font " + FONT + " -width " + KEYSIZE
+ + " -height " + KEYSIZE + " -bd " + string KEYBORDER);
+
+ tk->cmd(t, ".b" + string i + " configure -text {" + keys[i] +
+ "} -command 'send keypress " + string keyvals[i]);
+ }
+
+ for(i = 0; i < len special_keys; i++) {
+ (keynum, keysize) := special_keys[i];
+ tk->cmd(t, ".b" + string keynum + " configure -font " + SPECFONT + " -width " + string keysize);
+ }
+
+ tk->cmd(t, "image create bitmap Capslock_on -file " + ICPATH + "capson.bit -maskfile " + ICPATH + "capson.bit");
+ tk->cmd(t, "image create bitmap Capslock_off -file " + ICPATH + "capsoff.bit -maskfile " + ICPATH + "capsoff.bit");
+ tk->cmd(t, "image create bitmap Left_arrow -file " + ICPATH + "larrow.bit -maskfile " + ICPATH + "larrow.bit");
+ tk->cmd(t, "image create bitmap Right_arrow -file " + ICPATH + "rarrow.bit -maskfile " + ICPATH + "rarrow.bit");
+ tk->cmd(t, "image create bitmap Down_arrow -file " + ICPATH + "darrow.bit -maskfile " + ICPATH + "darrow.bit");
+ tk->cmd(t, "image create bitmap Up_arrow -file " + ICPATH + "uarrow.bit -maskfile " + ICPATH + "uarrow.bit");
+ tk->cmd(t, "image create bitmap Move_on -file " + ICPATH + "moveon.bit -maskfile " + ICPATH + "moveon.bit");
+ tk->cmd(t, "image create bitmap Move_off -file " + ICPATH + "moveoff.bit -maskfile " + ICPATH + "moveoff.bit");
+ tk->cmd(t, "image create bitmap None -file " + ICPATH + "none.bit -maskfile " + ICPATH + "none.bit");
+ tk->cmd(t, ".b" + string CAPSLOCKKEY + " configure -image Capslock_off");
+ tk->cmd(t, ".b" + string LEFTKEY + " configure -image Left_arrow");
+ tk->cmd(t, ".b" + string RIGHTKEY + " configure -image Right_arrow");
+ tk->cmd(t, ".b" + string DOWNKEY + " configure -image Down_arrow");
+ tk->cmd(t, ".b" + string UPKEY + " configure -image Up_arrow");
+
+ for(j:=1; j < len row_dimensions; j++) {
+ rowstart := row_dimensions[j-1];
+ rowend := row_dimensions[j];
+ for(i=rowstart; i<rowend; i++) {
+ if (i == rowstart) {
+ tk->cmd(t, "frame .f" + string j + ".dummy -width " + string ENDGAP);
+ tk->cmd(t, "pack .f" + string j + ".dummy -side left");
+ }
+ tk->cmd(t, "pack .b" + string i + " -in .f" + string j + " -side left");
+ if (i == rowend-1)
+ tk->cmd(t, "frame .f" + string j + ".dummy" + string i + " -width " + string ENDGAP);
+ else
+ tk->cmd(t, "frame .f" + string j + ".dummy" + string i + " -width " + string KEYGAP);
+ tk->cmd(t, "pack .f" + string j + ".dummy" + string i + " -side left");
+ }
+ }
+
+ tk->cmd(t, "pack .dummy0 .f1 .dummy1 .f2 .dummy2 .f3 .dummy3 .f4 .dummy4 .f5 .dummy5 -in " + dot);
+ tk->cmd(t,"update");
+
+ key := chan of string;
+ spawn handle_keyclicks(t, ctxt, key, rc);
+ return key;
+}
+
+tkcmds(t: ref Tk->Toplevel, cmds: array of string)
+{
+ for(i := 0; i < len cmds; i++)
+ tk->cmd(t, cmds[i]);
+}
+
+# Process key clicks and hand keycodes off to Tk
+handle_keyclicks(t: ref Tk->Toplevel, ctxt : ref Draw->Context, sc, rc: chan of string)
+{
+ keypress := chan of string;
+ tk->namechan(t, keypress, "keypress");
+
+ minitel := 0;
+ caps_locked := 0;
+ shifted := 0;
+ ctrl_active := 0;
+ alt_active := 0;
+
+Work:
+ for(;;){
+ alt {
+ k := <-keypress =>
+ (n, cmdstr) := sys->tokenize(k, " \t\n");
+ keycode := int hd cmdstr;
+ case keycode {
+ CAPSLOCK =>
+ redisplay_keyboard(t, minitel, caps_locked ^= 1, caps_locked);
+ shifted = 0;
+ ctrl_active = 0;
+ alt_active = 0;
+ SHIFT =>
+ redisplay_keyboard(t, minitel, (shifted ^= 1) ^ caps_locked, caps_locked);
+ CTRL =>
+ ctrl_active ^= 1;
+ if (shifted) {
+ redisplay_keyboard(t, minitel, caps_locked, caps_locked);
+ shifted = 0;
+ }
+ alt_active = 0;
+ ALT =>
+ alt_active ^= 1;
+ if (shifted) {
+ redisplay_keyboard(t, minitel, caps_locked, caps_locked);
+ shifted = 0;
+ }
+ ctrl_active = 0;
+ META =>
+ if (move_key_enabled) {
+ if (meta_active ^= 1)
+ tk->cmd(t, ".b" + string METAKEY + " configure -image Move_on");
+ else
+ tk->cmd(t, ".b" + string METAKEY + " configure -image Move_off");
+ }
+ redisplay_keyboard(t, minitel, caps_locked, caps_locked);
+ shifted = 0;
+ ctrl_active = 0;
+ alt_active = 0;
+ * =>
+ if (ctrl_active) {
+ keycode &= 16r1F;
+ ctrl_active = 0;
+ } else if (alt_active) {
+ keycode += MAGIC_PREFIX;
+ alt_active = 0;
+ }
+ if (meta_active && UP <= keycode && keycode <= RIGHT) {
+ spawn send_move_msg(direction[keycode - ARROW_PREFIX], sc);
+ } else
+ tk->keyboard(t, keycode);
+ if (shifted) {
+ redisplay_keyboard(t, minitel, caps_locked, caps_locked);
+ shifted = 0;
+ }
+ }
+ s := <-rc =>
+ case s {
+ "kill" =>
+ break Work;
+ "minitel" =>
+ if (!minitel) {
+ minitel = 1;
+ redisplay_keyboard(t, minitel, shifted, caps_locked);
+ }
+ "standard" =>
+ if (minitel) {
+ minitel = 0;
+ redisplay_keyboard(t, minitel, shifted, caps_locked);
+ }
+ }
+ }
+ }
+}
+
+send_move_msg(dir: string, ch: chan of string)
+{
+ ch <-= dir;
+}
+
+
+# Redisplay keyboard to reflect current state (shifted or unshifted)
+redisplay_keyboard(t: ref Tk->Toplevel, minitel, shifted, caps_locked: int)
+{
+ base: int;
+
+ if (shifted)
+ base = NKEYS;
+ else
+ base = 0;
+
+ for(i:=0; i<NKEYS; i++) {
+ n := base + i;
+ val := keyvals[n];
+ key := keys[n];
+ if (minitel) {
+ if (val >= int 'A' && val <= int 'Z') {
+ key = keys[n-NKEYS];
+ } else if (val >= int 'a' && val <= int 'z') {
+ key = keys[n+NKEYS];
+ }
+ }
+
+ tk->cmd(t, ".b" + string i + " configure -text {" + key
+ + "} -command 'send keypress " + string val);
+ }
+ if (caps_locked)
+ tk->cmd(t, ".b" + string CAPSLOCKKEY + " configure -image Capslock_on");
+ else
+ tk->cmd(t, ".b" + string CAPSLOCKKEY + " configure -image Capslock_off");
+ tk->cmd(t, "update");
+}
diff --git a/appl/wm/minitel/swkeyb.dis b/appl/wm/minitel/swkeyb.dis
new file mode 100644
index 00000000..2928c713
--- /dev/null
+++ b/appl/wm/minitel/swkeyb.dis
Binary files differ
diff --git a/appl/wm/minitel/swkeyb.m b/appl/wm/minitel/swkeyb.m
new file mode 100644
index 00000000..52206801
--- /dev/null
+++ b/appl/wm/minitel/swkeyb.m
@@ -0,0 +1,21 @@
+###
+### This data and information is not to be used as the basis of manufacture,
+### or be reproduced or copied, or be distributed to another party, in whole
+### or in part, without the prior written consent of Lucent Technologies.
+###
+### (C) Copyright 1997 Lucent Technologies
+###
+### Written by N. W. Knauft
+###
+
+# Revisions Copyright © 1998 Vita Nuova Limited.
+
+Keyboard: module
+{
+ PATH: con "/dis/wm/minitel/swkeyb.dis";
+
+ initialize: fn(t: ref Tk->Toplevel, ctxt : ref Draw->Context,
+ dot: string): chan of string;
+ chaninit: fn(t: ref Tk->Toplevel, ctxt : ref Draw->Context,
+ dot: string, rc: chan of string): chan of string;
+};
diff --git a/appl/wm/minitel/swkeyb.sbl b/appl/wm/minitel/swkeyb.sbl
new file mode 100644
index 00000000..f79889f8
--- /dev/null
+++ b/appl/wm/minitel/swkeyb.sbl
@@ -0,0 +1,724 @@
+limbo .sbl 2.1
+Keyboard
+6
+swkeyb.b
+sys.m
+draw.m
+tk.m
+tkclient.m
+swkeyb.m
+504
+172.8,46 0
+17,18 0
+20,24 0
+26,29 0
+31,45 0
+8,46 0
+8,46 0
+1,46 0
+177.1,25 1
+178.1,28 2
+179.1,22 3
+180.1,40 4
+182.1,17 5
+1,17 5
+184.1,185.35 6
+184.9,10 6
+12,26 6
+12,60 6
+12,60 7
+1,185.35 6
+184.1,185.35 6
+184.1,185.35 8
+186.1,21 9
+8,9 9
+11,20 9
+1,21 9
+188.5,11 10
+13,22 11
+189.2,190.59 12
+189.10,11 12
+27,35 12
+13,35 12
+13,47 12
+13,47 13
+2,190.59 12
+189.2,190.59 12
+189.2,190.59 14
+192.2,193.54 15
+192.10,11 15
+20,28 15
+13,28 15
+13,51 15
+54,61 15
+13,61 15
+13,193.33 15
+43,53 15
+36,53 15
+192.13,193.53 15
+192.13,193.53 16
+192.13,193.53 17
+192.2,193.54 15
+192.2,193.54 15
+192.2,193.54 18
+188.24,27 19
+24,27 19
+196.5,10 20
+16,32 21
+12,32 21
+197.23,38 22
+23,38 22
+198.2,97 23
+10,11 23
+20,33 23
+13,33 23
+13,55 23
+82,96 23
+13,96 23
+13,96 24
+13,96 25
+2,97 23
+2,97 23
+2,97 26
+196.34,37 27
+34,37 27
+201.1,112 28
+9,10 28
+12,111 28
+1,112 28
+1,112 28
+1,112 29
+202.1,115 30
+9,10 30
+12,114 30
+1,115 30
+1,115 30
+1,115 31
+203.1,111 32
+9,10 32
+12,110 32
+1,111 32
+1,111 32
+1,111 33
+204.1,112 34
+9,10 34
+12,111 34
+1,112 34
+1,112 34
+1,112 35
+205.1,111 36
+9,10 36
+12,110 36
+1,111 36
+1,111 36
+1,111 37
+206.1,109 38
+9,10 38
+12,108 38
+1,109 38
+1,109 38
+1,109 39
+207.1,108 40
+9,10 40
+12,107 40
+1,108 40
+1,108 40
+1,108 41
+208.1,111 42
+9,10 42
+12,110 42
+1,111 42
+1,111 42
+1,111 43
+209.1,101 44
+9,10 44
+12,100 44
+1,101 44
+1,101 44
+1,101 45
+210.1,73 46
+9,10 46
+12,72 46
+1,73 46
+1,73 46
+1,73 47
+211.1,67 48
+9,10 48
+12,66 48
+1,67 48
+1,67 48
+1,67 49
+212.1,69 50
+9,10 50
+12,68 50
+1,69 50
+1,69 50
+1,69 51
+213.1,67 52
+9,10 52
+12,66 52
+1,67 52
+1,67 52
+1,67 53
+214.1,63 54
+9,10 54
+12,62 54
+1,63 54
+1,63 54
+1,63 55
+216.5,9 56
+15,33 57
+11,33 57
+217.29,32 58
+14,33 58
+2,33 58
+218.12,29 59
+2,29 59
+219.6,16 60
+18,26 61
+220.7,20 62
+221.4,72 63
+12,13 63
+28,36 63
+15,36 63
+15,55 63
+15,55 64
+4,72 63
+4,72 63
+4,72 65
+222.4,58 66
+12,13 66
+27,35 66
+15,35 66
+15,57 66
+15,57 67
+4,58 66
+4,58 66
+4,58 68
+224.3,74 69
+11,12 69
+26,34 69
+14,34 69
+14,46 69
+49,57 69
+14,57 69
+14,57 70
+14,73 69
+14,73 71
+3,74 69
+3,74 69
+3,74 72
+225.12,20 73
+7,20 73
+226.4,88 74
+12,13 74
+28,36 74
+15,36 74
+15,47 74
+50,58 74
+15,58 74
+15,58 75
+15,71 74
+15,71 76
+4,88 74
+4,88 74
+4,88 77
+4,88 78
+228.4,88 79
+12,13 79
+28,36 79
+15,36 79
+15,47 79
+50,58 79
+15,58 79
+15,58 80
+15,71 79
+15,71 81
+4,88 79
+4,88 79
+4,88 82
+229.3,73 83
+11,12 83
+26,34 83
+14,34 83
+14,45 83
+48,56 83
+14,56 83
+14,56 84
+14,72 83
+14,72 85
+3,73 83
+3,73 83
+3,73 86
+219.28,31 87
+28,31 87
+216.35,38 88
+35,38 88
+233.1,98 89
+9,10 89
+12,97 89
+1,98 89
+1,98 89
+1,98 90
+234.1,20 91
+9,10 91
+11,19 91
+1,20 91
+1,20 91
+1,20 92
+236.1,22 93
+237.1,41 94
+24,25 94
+27,31 94
+33,36 94
+38,40 94
+1,41 94
+238.8,11 95
+1,11 95
+243.5,11 96
+17,25 97
+13,25 97
+244.2,21 98
+10,11 98
+13,20 98
+13,20 98
+2,21 98
+2,21 98
+2,21 99
+243.27,30 100
+27,30 100
+245.0,1 101
+250.1,27 102
+251.1,38 103
+14,15 103
+17,25 103
+27,37 103
+1,38 103
+1,38 103
+1,38 104
+253.1,13 105
+254.1,17 106
+255.1,13 107
+256.1,17 108
+257.1,16 109
+262.9,17 110
+9,17 110
+315.9,11 110
+9,11 110
+261.2,8 110
+2,8 110
+2,8 110
+2,8 110
+263.18,43 111
+32,33 111
+35,42 111
+18,43 111
+18,43 111
+4,5 111
+7,13 111
+7,13 112
+264.18,27 113
+3,27 113
+3,27 114
+265.8,15 115
+8,15 115
+8,15 115
+8,15 115
+267.4,65 116
+23,24 116
+26,33 116
+35,51 116
+35,51 116
+53,64 116
+4,65 116
+268.4,15 117
+269.4,19 118
+270.4,18 119
+4,18 115
+272.4,77 120
+23,24 120
+26,33 120
+35,49 120
+35,49 120
+35,63 120
+65,76 120
+4,77 120
+4,77 115
+274.4,20 121
+275.8,15 122
+276.5,61 123
+24,25 123
+27,34 123
+36,47 123
+49,60 123
+5,61 123
+277.5,16 124
+279.4,18 125
+4,18 115
+281.4,19 126
+282.8,15 127
+283.5,61 128
+24,25 128
+27,34 128
+36,47 128
+49,60 128
+5,61 128
+284.5,16 129
+286.4,19 130
+4,19 115
+288.8,24 131
+289.9,25 132
+9,25 132
+9,25 132
+290.6,69 133
+14,15 133
+17,68 133
+6,69 133
+6,69 133
+6,69 134
+6,69 135
+292.6,70 136
+14,15 136
+17,69 136
+6,70 136
+6,70 136
+6,70 137
+294.4,60 138
+23,24 138
+26,33 138
+35,46 138
+48,59 138
+4,60 138
+295.4,15 139
+296.4,19 140
+297.4,18 141
+4,18 115
+299.8,19 142
+300.5,21 143
+301.5,20 144
+5,20 145
+302.15,25 146
+303.5,28 147
+304.5,19 148
+306.8,19 149
+23,36 149
+40,56 149
+307.5,63 150
+35,57 150
+25,58 150
+25,58 150
+60,62 150
+5,63 150
+5,63 151
+309.5,29 152
+18,19 152
+21,28 152
+5,29 152
+310.8,15 153
+311.5,61 154
+24,25 154
+27,34 154
+36,47 154
+49,60 154
+5,61 154
+312.5,16 155
+5,16 115
+5,16 156
+5,16 157
+5,16 110
+316.8,9 158
+8,9 159
+332.0,1 160
+320.9,16 161
+321.5,16 162
+322.5,57 163
+24,25 163
+27,34 163
+36,43 163
+45,56 163
+5,57 163
+5,57 158
+325.8,15 164
+326.5,16 165
+327.5,57 166
+24,25 166
+27,34 166
+36,43 166
+45,56 166
+5,57 166
+5,57 158
+5,57 167
+5,57 110
+336.1,11 168
+337.0,1 169
+345.5,12 170
+346.2,14 171
+2,14 172
+348.2,10 173
+350.5,9 174
+11,18 175
+351.2,15 176
+352.9,19 177
+2,19 177
+353.9,16 178
+2,16 178
+354.6,13 179
+355.7,21 180
+25,39 180
+356.15,22 181
+10,23 181
+4,23 181
+4,23 182
+357.14,28 183
+32,46 183
+358.15,22 184
+10,23 184
+4,23 184
+362.2,363.59 185
+362.10,11 185
+20,28 185
+13,28 185
+13,51 185
+13,57 185
+13,363.45 185
+48,58 185
+362.13,363.58 185
+362.13,363.58 186
+362.13,363.58 187
+362.2,363.59 185
+362.2,363.59 185
+362.2,363.59 188
+362.2,363.59 189
+350.20,23 190
+20,23 190
+365.5,16 191
+366.2,73 192
+10,11 192
+13,72 192
+2,73 192
+2,73 192
+2,73 193
+2,73 194
+368.2,74 195
+10,11 195
+13,73 195
+2,74 195
+2,74 195
+2,74 196
+369.1,21 197
+9,10 197
+12,20 197
+1,21 197
+1,21 197
+1,21 198
+370.0,1 199
+13
+aSys->Dir 1:26.1,39.2 64
+11
+0:name:28.2,6 s
+4:uid:29.2,5 s
+8:gid:30.2,5 s
+12:muid:31.2,6 s
+16:qid:32.2,5 @1
+
+32:mode:33.2,6 i
+36:atime:34.2,7 i
+40:mtime:35.2,7 i
+48:length:36.2,8 B
+56:dtype:37.2,7 i
+60:dev:38.2,5 i
+aSys->Qid 11.1,16.2 16
+3
+0:path:13.2,6 B
+8:vers:14.2,6 i
+12:qtype:15.2,7 i
+aDraw->Chans 2:70.1,82.2 4
+1
+0:desc:72.2,6 i
+aTk->Toplevel 3:5.1,12.2 32
+5
+0:display:7.2,9 R@4
+
+4:wreq:8.2,6 Cs
+8:image:9.2,7 R@5
+
+12:ctxt:10.2,6 R@9
+
+16:screenr:11.2,9 @6
+
+aDraw->Display 2:201.1,230.2 20
+5
+0:image:203.2,7 R@5
+
+4:white:204.2,7 R@5
+
+8:black:205.2,7 R@5
+
+12:opaque:206.2,8 R@5
+
+16:transparent:207.2,13 R@5
+
+aDraw->Image 142.1,198.2 56
+8
+0:r:146.2,3 @6
+
+16:clipr:147.2,7 @6
+
+32:depth:148.2,7 i
+36:chans:149.2,7 @2
+
+40:repl:150.2,6 i
+44:display:151.2,9 R@4
+
+48:screen:152.2,8 R@8
+
+52:iname:153.2,7 s
+aDraw->Rect 116.1,139.2 16
+2
+0:min:118.2,5 @7
+
+8:max:119.2,5 @7
+
+aDraw->Point 99.1,113.2 8
+2
+0:x:101.2,3 i
+4:y:102.2,3 i
+aDraw->Screen 249.1,263.2 16
+4
+0:id:251.2,4 i
+4:image:252.2,7 R@5
+
+8:fill:253.2,6 R@5
+
+12:display:254.2,9 R@4
+
+aDraw->Wmcontext 282.1,291.2 28
+7
+0:kbd:284.2,5 Ci
+4:ptr:285.2,5 CR@10
+
+8:ctl:286.2,5 Cs
+12:wctl:287.2,6 Cs
+16:images:288.2,8 CR@5
+
+20:connfd:289.2,8 R@11
+
+24:ctxt:290.2,6 R@12
+
+aDraw->Pointer 266.1,271.2 16
+3
+0:buttons:268.2,9 i
+4:xy:269.2,4 @7
+
+12:msec:270.2,6 i
+aSys->FD 1:45.1,48.2 4
+1
+0:fd:47.2,4 i
+aDraw->Context 2:274.1,279.2 12
+3
+0:display:276.2,9 R@4
+
+4:screen:277.2,8 R@8
+
+8:wm:278.2,4 Ct8.2
+0:t0:15,21 s
+4:t1:15,21 Ct8.2
+0:t0:32,38 s
+4:t1:32,38 R@9
+
+
+
+6
+0:initialize
+3
+32:t:0:170.11,12 R@3
+
+36:ctxt:32,36 R@12
+
+40:dot:58,61 s
+0
+Cs8:chaninit
+4
+32:t:175.9,10 R@3
+
+36:ctxt:30,34 R@12
+
+40:dot:56,59 s
+44:rc:69,71 Cs
+7
+48:i:188.5,6 i
+52:j:216.5,6 i
+56:key:236.1,4 Cs
+60:rowend:218.2,8 i
+64:rowstart:217.2,10 i
+68:keynum:197.3,9 i
+72:keysize:11,18 i
+Cs267:tkcmds
+2
+32:t:241.7,8 R@3
+
+36:cmds:28,32 As
+1
+40:i:243.5,6 i
+n280:handle_keyclicks
+4
+32:t:248.17,18 R@3
+
+36:ctxt:38,42 R@12
+
+40:sc:64,66 Cs
+44:rc:68,70 Cs
+11
+48:caps_locked:254.1,12 i
+52:minitel:253.1,8 i
+56:shifted:255.1,8 i
+60:keycode:264.3,10 i
+64:alt_active:257.1,11 i
+68:ctrl_active:256.1,12 i
+72:keypress:250.1,9 Cs
+76:k:262.2,3 s
+80:s:315.2,3 s
+96:n:263.4,5 i
+100:cmdstr:7,13 Ls
+n441:send_move_msg
+2
+32:dir:334.14,17 s
+36:ch:27,29 Cs
+0
+n443:redisplay_keyboard
+4
+32:t:341.19,20 R@3
+
+36:minitel:40,47 i
+40:shifted:49,56 i
+44:caps_locked:58,69 i
+5
+48:val:352.2,5 i
+52:i:350.5,6 i
+56:n:351.2,3 i
+60:key:353.2,5 s
+64:base:343.1,5 i
+n12
+188:direction:102.0,9 As
+192:draw:19.8,12 mDraw
+2:1.0,298.1 0
+
+244:keys:0:121.0,4 As
+248:keyvals:136.0,7 Ai
+252:meta_active:167.0,11 i
+256:move_key_enabled:166.0,16 i
+276:row_dimensions:103.0,14 Ai
+280:rowlayout:151.0,9 As
+284:special_keys:105.0,12 At8.2
+0:t0:106.2,8 i
+4:t1:10,17 i
+
+288:sys:16.8,11 mSys
+1:4.0,160.1 0
+
+292:tk:0:22.8,10 mTk
+3:1.0,25.1 0
+
+296:tkclient:0:8,16 mTkclient
+4:1.0,26.1 0
+
diff --git a/appl/wm/mkfile b/appl/wm/mkfile
new file mode 100644
index 00000000..fd9f2157
--- /dev/null
+++ b/appl/wm/mkfile
@@ -0,0 +1,103 @@
+<../../mkconfig
+
+DIRS=\
+ brutus\
+ camera\
+# diary\
+ drawmux\
+ ftree\
+ mailtool\
+ mpeg\
+# minitel\
+
+TARG=\
+ about.dis\
+ avi.dis\
+ bounce.dis\
+ brutus.dis\
+ c4.dis\
+ calendar.dis\
+ clock.dis\
+ coffee.dis\
+ collide.dis\
+ colors.dis\
+ cprof.dis\
+ date.dis\
+ deb.dis\
+ debdata.dis\
+ debsrc.dis\
+ dir.dis\
+ edit.dis\
+ filename.dis\
+ getauthinfo.dis\
+ keyboard.dis\
+ logon.dis\
+ logwindow.dis\
+ man.dis\
+ mand.dis\
+ mash.dis\
+ memory.dis\
+## mpeg.dis\
+ mprof.dis\
+ pen.dis\
+ polyhedra.dis\
+ prof.dis\
+## qt.dis\
+ readmail.dis\
+ remotelogon.dis\
+ reversi.dis\
+ rmtdir.dis\
+ rt.dis\
+ sendmail.dis\
+ sh.dis\
+ smenu.dis\
+ snake.dis\
+ stopwatch.dis\
+ sweeper.dis\
+ task.dis\
+ telnet.dis\
+ tetris.dis\
+ toolbar.dis\
+ unibrowse.dis\
+ view.dis\
+ vt.dis\
+ wish.dis\
+ wm.dis\
+ wmplay.dis\
+
+MODULES=\
+ wmdeb.m\
+
+SYSMODULES=\
+ bufio.m\
+ cci.m\
+ daytime.m\
+ debug.m\
+ draw.m\
+ filepat.m\
+ html.m\
+ keyring.m\
+ man.m\
+ mpeg.m\
+ newns.m\
+ plumbmsg.m\
+ quicktime.m\
+ rand.m\
+ readdir.m\
+ riff.m\
+ security.m\
+ sh.m\
+ string.m\
+ sys.m\
+ tk.m\
+ tkclient.m\
+ url.m\
+ webget.m\
+ wmclient.m\
+ wmsrv.m\
+ workdir.m\
+
+DISBIN=$ROOT/dis/wm
+
+<$ROOT/mkfiles/mkdis
+<$ROOT/mkfiles/mksubdirs
diff --git a/appl/wm/mpeg.b b/appl/wm/mpeg.b
new file mode 100644
index 00000000..619aa338
--- /dev/null
+++ b/appl/wm/mpeg.b
@@ -0,0 +1,185 @@
+implement WmMpeg;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Rect, Display, Image: import draw;
+ ctxt: ref Draw->Context;
+
+include "tk.m";
+ tk: Tk;
+ Toplevel: import tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "mpeg.m";
+ mpeg: Mpeg;
+
+WmMpeg: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Stopped, Playing: con iota;
+
+dx, dy: int;
+dw, dh: int;
+adjust: int;
+
+task_cfg := array[] of {
+ "canvas .c -background =5",
+ "frame .b",
+ "button .b.File -text File -command {send cmd file}",
+ "button .b.Stop -text Stop -command {send cmd stop}",
+ "button .b.Pause -text Pause -command {send cmd pause}",
+ "button .b.Play -text Play -command {send cmd play}",
+ "button .b.Picture -text Picture -command {send cmd pict}",
+ "frame .f",
+ "label .f.file -text {File:}",
+ "label .f.name",
+ "pack .f.file .f.name -side left",
+ "pack .b.File .b.Stop .b.Pause .b.Play .b.Picture -side left",
+ "pack .f -fill x",
+ "pack .b -anchor w",
+ "pack .c -side bottom -fill both -expand 1",
+ "pack propagate . 0",
+};
+
+init(xctxt: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ mpeg = load Mpeg Mpeg->PATH;
+
+ ctxt = xctxt;
+
+ tkclient->init();
+
+ (t, menubut) := tkclient->toplevel(ctxt.screen, "", "Mpeg Player", Tkclient->Appl);
+
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+
+ tkclient->tkcmds(t, task_cfg);
+
+ tk->cmd(t, "bind . <Configure> {send cmd resize}");
+ tk->cmd(t, "update");
+
+ fname := "";
+ ctl := chan of string;
+ state := Stopped;
+
+ for(;;) alt {
+ menu := <-menubut =>
+ if(menu == "exit") {
+ if(state == Playing) {
+ mpeg->ctl("stop");
+ <-ctl;
+ }
+ return;
+ }
+ tkclient->wmctl(t, menu);
+ press := <-cmd =>
+ case press {
+ "file" =>
+ pat := list of {
+ "*.mpg (MPEG movie file)"
+ };
+ fname = tkclient->filename(ctxt.screen, t, "Locate MPEG clip", pat, "");
+ if(fname != nil) {
+ tk->cmd(t, ".f.name configure -text {"+fname+"}");
+ tk->cmd(t, "update");
+ }
+ "play" =>
+ s := mpeg->play(ctxt.display, nil, 0, canvsize(t), fname, ctl);
+ if(s != nil) {
+ tkclient->dialog(t, "error -fg red", "Play MPEG",
+ "Media player error:\n"+s,
+ 0, "Stop Play"::nil);
+ break;
+ }
+ state = Playing;
+ "resize" =>
+ if(state != Playing)
+ break;
+ r := canvsize(t);
+ s := sys->sprint("window %d %d %d %d",
+ r.min.x, r.min.y, r.max.x, r.max.y);
+ mpeg->ctl(s);
+ "pict" =>
+ if(adjust)
+ break;
+ adjust = 1;
+ spawn pict(t);
+ * =>
+ # Stop & Pause
+ mpeg->ctl(press);
+ }
+ done := <-ctl =>
+ state = Stopped;
+ }
+}
+
+canvsize(t: ref Toplevel): Rect
+{
+ r: Rect;
+
+ r.min.x = int tk->cmd(t, ".c cget -actx") + dx;
+ r.min.y = int tk->cmd(t, ".c cget -acty") + dy;
+ r.max.x = r.min.x + int tk->cmd(t, ".c cget -width") + dw;
+ r.max.y = r.min.y + int tk->cmd(t, ".c cget -height") + dh;
+
+ return r;
+}
+
+pict_cfg := array[] of {
+ "scale .dx -orient horizontal -from -5 -to 5 -label {Origin X}"+
+ " -command { send c dx}",
+ "scale .dy -orient horizontal -from -5 -to 5 -label {Origin Y}"+
+ " -command { send c dy}",
+ "scale .dw -orient horizontal -from -5 -to 5 -label {Width}"+
+ " -command {send c dw}",
+ "scale .dh -orient horizontal -from -5 -to 5 -label {Height}"+
+ " -command {send c dh}",
+ "pack .Wm_t -fill x",
+ "pack .dx .dy .dw .dh -fill x",
+ "pack propagate . 0",
+ "update",
+};
+
+pict(parent: ref Toplevel)
+{
+ targ := +" -borderwidth 2 -relief raised";
+
+ (t, menubut) := tkclient->toplevel(ctxt.screen, tkclient->geom(parent), "Mpeg Picture", 0);
+
+ pchan := chan of string;
+ tk->namechan(t, pchan, "c");
+
+ tkclient->tkcmds(t, pict_cfg);
+
+ for(;;) alt {
+ menu := <-menubut =>
+ if(menu == "exit") {
+ adjust = 0;
+ return;
+ }
+ tkclient->wmctl(t, menu);
+ tcip := <-pchan =>
+ case tcip {
+ "dx" => dx = int tk->cmd(t, ".dx get");
+ "dy" => dy = int tk->cmd(t, ".dy get");
+ "dw" => dw = int tk->cmd(t, ".dw get");
+ "dh" => dh = int tk->cmd(t, ".dh get");
+ }
+ r := canvsize(parent);
+ s := sys->sprint("window %d %d %d %d",
+ r.min.x, r.min.y, r.max.x, r.max.y);
+ mpeg->ctl(s);
+ }
+}
diff --git a/appl/wm/mpeg/c0.tab b/appl/wm/mpeg/c0.tab
new file mode 100644
index 00000000..3949d009
--- /dev/null
+++ b/appl/wm/mpeg/c0.tab
@@ -0,0 +1,261 @@
+# vlc -uUNDEF,UNDEF c0
+c0_size: con 256;
+c0_bits: con 8;
+c0_table:= array[] of {
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (0, UNDEF,UNDEF),
+ (8, 18,1),
+ (8, -18,1),
+ (8, 17,1),
+ (8, -17,1),
+ (8, 16,1),
+ (8, -16,1),
+ (8, 15,1),
+ (8, -15,1),
+ (8, 3,6),
+ (8, -3,6),
+ (8, 2,16),
+ (8, -2,16),
+ (8, 2,15),
+ (8, -2,15),
+ (8, 2,14),
+ (8, -2,14),
+ (8, 2,13),
+ (8, -2,13),
+ (8, 2,12),
+ (8, -2,12),
+ (8, 2,11),
+ (8, -2,11),
+ (8, 1,31),
+ (8, -1,31),
+ (8, 1,30),
+ (8, -1,30),
+ (8, 1,29),
+ (8, -1,29),
+ (8, 1,28),
+ (8, -1,28),
+ (8, 1,27),
+ (8, -1,27),
+ (7, 40,0),
+ (7, 40,0),
+ (7, -40,0),
+ (7, -40,0),
+ (7, 39,0),
+ (7, 39,0),
+ (7, -39,0),
+ (7, -39,0),
+ (7, 38,0),
+ (7, 38,0),
+ (7, -38,0),
+ (7, -38,0),
+ (7, 37,0),
+ (7, 37,0),
+ (7, -37,0),
+ (7, -37,0),
+ (7, 36,0),
+ (7, 36,0),
+ (7, -36,0),
+ (7, -36,0),
+ (7, 35,0),
+ (7, 35,0),
+ (7, -35,0),
+ (7, -35,0),
+ (7, 34,0),
+ (7, 34,0),
+ (7, -34,0),
+ (7, -34,0),
+ (7, 33,0),
+ (7, 33,0),
+ (7, -33,0),
+ (7, -33,0),
+ (7, 32,0),
+ (7, 32,0),
+ (7, -32,0),
+ (7, -32,0),
+ (7, 14,1),
+ (7, 14,1),
+ (7, -14,1),
+ (7, -14,1),
+ (7, 13,1),
+ (7, 13,1),
+ (7, -13,1),
+ (7, -13,1),
+ (7, 12,1),
+ (7, 12,1),
+ (7, -12,1),
+ (7, -12,1),
+ (7, 11,1),
+ (7, 11,1),
+ (7, -11,1),
+ (7, -11,1),
+ (7, 10,1),
+ (7, 10,1),
+ (7, -10,1),
+ (7, -10,1),
+ (7, 9,1),
+ (7, 9,1),
+ (7, -9,1),
+ (7, -9,1),
+ (7, 8,1),
+ (7, 8,1),
+ (7, -8,1),
+ (7, -8,1),
+ (6, 31,0),
+ (6, 31,0),
+ (6, 31,0),
+ (6, 31,0),
+ (6, -31,0),
+ (6, -31,0),
+ (6, -31,0),
+ (6, -31,0),
+ (6, 30,0),
+ (6, 30,0),
+ (6, 30,0),
+ (6, 30,0),
+ (6, -30,0),
+ (6, -30,0),
+ (6, -30,0),
+ (6, -30,0),
+ (6, 29,0),
+ (6, 29,0),
+ (6, 29,0),
+ (6, 29,0),
+ (6, -29,0),
+ (6, -29,0),
+ (6, -29,0),
+ (6, -29,0),
+ (6, 28,0),
+ (6, 28,0),
+ (6, 28,0),
+ (6, 28,0),
+ (6, -28,0),
+ (6, -28,0),
+ (6, -28,0),
+ (6, -28,0),
+ (6, 27,0),
+ (6, 27,0),
+ (6, 27,0),
+ (6, 27,0),
+ (6, -27,0),
+ (6, -27,0),
+ (6, -27,0),
+ (6, -27,0),
+ (6, 26,0),
+ (6, 26,0),
+ (6, 26,0),
+ (6, 26,0),
+ (6, -26,0),
+ (6, -26,0),
+ (6, -26,0),
+ (6, -26,0),
+ (6, 25,0),
+ (6, 25,0),
+ (6, 25,0),
+ (6, 25,0),
+ (6, -25,0),
+ (6, -25,0),
+ (6, -25,0),
+ (6, -25,0),
+ (6, 24,0),
+ (6, 24,0),
+ (6, 24,0),
+ (6, 24,0),
+ (6, -24,0),
+ (6, -24,0),
+ (6, -24,0),
+ (6, -24,0),
+ (6, 23,0),
+ (6, 23,0),
+ (6, 23,0),
+ (6, 23,0),
+ (6, -23,0),
+ (6, -23,0),
+ (6, -23,0),
+ (6, -23,0),
+ (6, 22,0),
+ (6, 22,0),
+ (6, 22,0),
+ (6, 22,0),
+ (6, -22,0),
+ (6, -22,0),
+ (6, -22,0),
+ (6, -22,0),
+ (6, 21,0),
+ (6, 21,0),
+ (6, 21,0),
+ (6, 21,0),
+ (6, -21,0),
+ (6, -21,0),
+ (6, -21,0),
+ (6, -21,0),
+ (6, 20,0),
+ (6, 20,0),
+ (6, 20,0),
+ (6, 20,0),
+ (6, -20,0),
+ (6, -20,0),
+ (6, -20,0),
+ (6, -20,0),
+ (6, 19,0),
+ (6, 19,0),
+ (6, 19,0),
+ (6, 19,0),
+ (6, -19,0),
+ (6, -19,0),
+ (6, -19,0),
+ (6, -19,0),
+ (6, 18,0),
+ (6, 18,0),
+ (6, 18,0),
+ (6, 18,0),
+ (6, -18,0),
+ (6, -18,0),
+ (6, -18,0),
+ (6, -18,0),
+ (6, 17,0),
+ (6, 17,0),
+ (6, 17,0),
+ (6, 17,0),
+ (6, -17,0),
+ (6, -17,0),
+ (6, -17,0),
+ (6, -17,0),
+ (6, 16,0),
+ (6, 16,0),
+ (6, 16,0),
+ (6, 16,0),
+ (6, -16,0),
+ (6, -16,0),
+ (6, -16,0),
+ (6, -16,0),
+};
diff --git a/appl/wm/mpeg/c0.vlc b/appl/wm/mpeg/c0.vlc
new file mode 100644
index 00000000..cc1af6e6
--- /dev/null
+++ b/appl/wm/mpeg/c0.vlc
@@ -0,0 +1,50 @@
+# Run/Level continuation 0
+# vlc -uUNDEF,UNDEF c0 < c0.vlc > c0.tab
+11111s 16,0
+11110s 17,0
+11101s 18,0
+11100s 19,0
+11011s 20,0
+11010s 21,0
+11001s 22,0
+11000s 23,0
+10111s 24,0
+10110s 25,0
+10101s 26,0
+10100s 27,0
+10011s 28,0
+10010s 29,0
+10001s 30,0
+10000s 31,0
+011000s 32,0
+010111s 33,0
+010110s 34,0
+010101s 35,0
+010100s 36,0
+010011s 37,0
+010010s 38,0
+010001s 39,0
+010000s 40,0
+011111s 8,1
+011110s 9,1
+011101s 10,1
+011100s 11,1
+011011s 12,1
+011010s 13,1
+011001s 14,1
+0010011s 15,1
+0010010s 16,1
+0010001s 17,1
+0010000s 18,1
+0010100s 3,6
+0011010s 2,11
+0011001s 2,12
+0011000s 2,13
+0010111s 2,14
+0010110s 2,15
+0010101s 2,16
+0011111s 1,27
+0011110s 1,28
+0011101s 1,29
+0011100s 1,30
+0011011s 1,31
diff --git a/appl/wm/mpeg/c1.tab b/appl/wm/mpeg/c1.tab
new file mode 100644
index 00000000..ff834508
--- /dev/null
+++ b/appl/wm/mpeg/c1.tab
@@ -0,0 +1,37 @@
+# vlc -cfp c1
+c1_size: con 32;
+c1_bits: con 5;
+c1_table:= array[] of {
+ (2,10),
+ (-2,10),
+ (2,9),
+ (-2,9),
+ (3,5),
+ (-3,5),
+ (4,3),
+ (-4,3),
+ (5,2),
+ (-5,2),
+ (7,1),
+ (-7,1),
+ (6,1),
+ (-6,1),
+ (15,0),
+ (-15,0),
+ (14,0),
+ (-14,0),
+ (13,0),
+ (-13,0),
+ (12,0),
+ (-12,0),
+ (1,26),
+ (-1,26),
+ (1,25),
+ (-1,25),
+ (1,24),
+ (-1,24),
+ (1,23),
+ (-1,23),
+ (1,22),
+ (-1,22),
+};
diff --git a/appl/wm/mpeg/c1.vlc b/appl/wm/mpeg/c1.vlc
new file mode 100644
index 00000000..1e18d599
--- /dev/null
+++ b/appl/wm/mpeg/c1.vlc
@@ -0,0 +1,18 @@
+# Run/Level continuation 1
+# vlc -cfp c1 < c1.vlc > c1.tab
+1010s 12,0
+1001s 13,0
+1000s 14,0
+0111s 15,0
+0110s 6,1
+0101s 7,1
+0100s 5,2
+0011s 4,3
+0010s 3,5
+0001s 2,9
+0000s 2,10
+1111s 1,22
+1110s 1,23
+1101s 1,24
+1100s 1,25
+1011s 1,26
diff --git a/appl/wm/mpeg/c2.tab b/appl/wm/mpeg/c2.tab
new file mode 100644
index 00000000..69c0ebf2
--- /dev/null
+++ b/appl/wm/mpeg/c2.tab
@@ -0,0 +1,21 @@
+# vlc -cfp c2
+c2_size: con 16;
+c2_bits: con 4;
+c2_table:= array[] of {
+ (11,0),
+ (-11,0),
+ (2,8),
+ (-2,8),
+ (3,4),
+ (-3,4),
+ (10,0),
+ (-10,0),
+ (4,2),
+ (-4,2),
+ (2,7),
+ (-2,7),
+ (1,21),
+ (-1,21),
+ (1,20),
+ (-1,20),
+};
diff --git a/appl/wm/mpeg/c2.vlc b/appl/wm/mpeg/c2.vlc
new file mode 100644
index 00000000..167d011a
--- /dev/null
+++ b/appl/wm/mpeg/c2.vlc
@@ -0,0 +1,10 @@
+# Run/Level continuation 2
+# vlc -cfp c2 < c2.vlc > c2.tab
+011s 10,0
+000s 11,0
+100s 4,2
+010s 3,4
+101s 2,7
+001s 2,8
+111s 1,20
+110s 1,21
diff --git a/appl/wm/mpeg/c3.tab b/appl/wm/mpeg/c3.tab
new file mode 100644
index 00000000..06fd7cfb
--- /dev/null
+++ b/appl/wm/mpeg/c3.tab
@@ -0,0 +1,21 @@
+# vlc -cfp c3
+c3_size: con 16;
+c3_bits: con 4;
+c3_table:= array[] of {
+ (9,0),
+ (-9,0),
+ (1,19),
+ (-1,19),
+ (1,18),
+ (-1,18),
+ (5,1),
+ (-5,1),
+ (3,3),
+ (-3,3),
+ (8,0),
+ (-8,0),
+ (2,6),
+ (-2,6),
+ (1,17),
+ (-1,17),
+};
diff --git a/appl/wm/mpeg/c3.vlc b/appl/wm/mpeg/c3.vlc
new file mode 100644
index 00000000..df6a8c46
--- /dev/null
+++ b/appl/wm/mpeg/c3.vlc
@@ -0,0 +1,10 @@
+# Run/Level continuation 3
+# vlc -cfp c3 < c3.vlc > c3.tab
+101s 8,0
+000s 9,0
+011s 5,1
+100s 3,3
+110s 2,6
+111s 1,17
+010s 1,18
+001s 1,19
diff --git a/appl/wm/mpeg/c4.tab b/appl/wm/mpeg/c4.tab
new file mode 100644
index 00000000..db1c3a56
--- /dev/null
+++ b/appl/wm/mpeg/c4.tab
@@ -0,0 +1,9 @@
+# vlc -cfp c4
+c4_size: con 4;
+c4_bits: con 2;
+c4_table:= array[] of {
+ (1,16),
+ (-1,16),
+ (2,5),
+ (-2,5),
+};
diff --git a/appl/wm/mpeg/c4.vlc b/appl/wm/mpeg/c4.vlc
new file mode 100644
index 00000000..cd6797d1
--- /dev/null
+++ b/appl/wm/mpeg/c4.vlc
@@ -0,0 +1,4 @@
+# Run/Level continuation 4
+# vlc -cfp c4 < c4.vlc > c4.tab
+0s 1,16
+1s 2,5
diff --git a/appl/wm/mpeg/c5.tab b/appl/wm/mpeg/c5.tab
new file mode 100644
index 00000000..98a1d285
--- /dev/null
+++ b/appl/wm/mpeg/c5.tab
@@ -0,0 +1,9 @@
+# vlc -cfp c5
+c5_size: con 4;
+c5_bits: con 2;
+c5_table:= array[] of {
+ (7,0),
+ (-7,0),
+ (3,2),
+ (-3,2),
+};
diff --git a/appl/wm/mpeg/c5.vlc b/appl/wm/mpeg/c5.vlc
new file mode 100644
index 00000000..ae0c10a8
--- /dev/null
+++ b/appl/wm/mpeg/c5.vlc
@@ -0,0 +1,4 @@
+# Run/Level continuation 5
+# vlc -cfp c5 < c5.vlc > c5.tab
+0s 7,0
+1s 3,2
diff --git a/appl/wm/mpeg/c6.tab b/appl/wm/mpeg/c6.tab
new file mode 100644
index 00000000..fe1c5c35
--- /dev/null
+++ b/appl/wm/mpeg/c6.tab
@@ -0,0 +1,9 @@
+# vlc -cfp c6
+c6_size: con 4;
+c6_bits: con 2;
+c6_table:= array[] of {
+ (4,1),
+ (-4,1),
+ (1,15),
+ (-1,15),
+};
diff --git a/appl/wm/mpeg/c6.vlc b/appl/wm/mpeg/c6.vlc
new file mode 100644
index 00000000..86165acf
--- /dev/null
+++ b/appl/wm/mpeg/c6.vlc
@@ -0,0 +1,4 @@
+# Run/Level continuation 6
+# vlc -cfp c6 < c6.vlc > c6.tab
+0s 4,1
+1s 1,15
diff --git a/appl/wm/mpeg/c7.tab b/appl/wm/mpeg/c7.tab
new file mode 100644
index 00000000..a0385192
--- /dev/null
+++ b/appl/wm/mpeg/c7.tab
@@ -0,0 +1,9 @@
+# vlc -cfp c7
+c7_size: con 4;
+c7_bits: con 2;
+c7_table:= array[] of {
+ (1,14),
+ (-1,14),
+ (2,4),
+ (-2,4),
+};
diff --git a/appl/wm/mpeg/c7.vlc b/appl/wm/mpeg/c7.vlc
new file mode 100644
index 00000000..45986054
--- /dev/null
+++ b/appl/wm/mpeg/c7.vlc
@@ -0,0 +1,4 @@
+# Run/Level continuation 7
+# vlc -cfp c7 < c7.vlc > c7.tab
+0s 1,14
+1s 2,4
diff --git a/appl/wm/mpeg/cbp.tab b/appl/wm/mpeg/cbp.tab
new file mode 100644
index 00000000..ee97febb
--- /dev/null
+++ b/appl/wm/mpeg/cbp.tab
@@ -0,0 +1,517 @@
+# vlc cbp
+cbp_size: con 512;
+cbp_bits: con 9;
+cbp_table:= array[] of {
+ (0, UNDEF),
+ (0, UNDEF),
+ (9, 39),
+ (9, 27),
+ (9, 59),
+ (9, 55),
+ (9, 47),
+ (9, 31),
+ (8, 58),
+ (8, 58),
+ (8, 54),
+ (8, 54),
+ (8, 46),
+ (8, 46),
+ (8, 30),
+ (8, 30),
+ (8, 57),
+ (8, 57),
+ (8, 53),
+ (8, 53),
+ (8, 45),
+ (8, 45),
+ (8, 29),
+ (8, 29),
+ (8, 38),
+ (8, 38),
+ (8, 26),
+ (8, 26),
+ (8, 37),
+ (8, 37),
+ (8, 25),
+ (8, 25),
+ (8, 43),
+ (8, 43),
+ (8, 23),
+ (8, 23),
+ (8, 51),
+ (8, 51),
+ (8, 15),
+ (8, 15),
+ (8, 42),
+ (8, 42),
+ (8, 22),
+ (8, 22),
+ (8, 50),
+ (8, 50),
+ (8, 14),
+ (8, 14),
+ (8, 41),
+ (8, 41),
+ (8, 21),
+ (8, 21),
+ (8, 49),
+ (8, 49),
+ (8, 13),
+ (8, 13),
+ (8, 35),
+ (8, 35),
+ (8, 19),
+ (8, 19),
+ (8, 11),
+ (8, 11),
+ (8, 7),
+ (8, 7),
+ (7, 34),
+ (7, 34),
+ (7, 34),
+ (7, 34),
+ (7, 18),
+ (7, 18),
+ (7, 18),
+ (7, 18),
+ (7, 10),
+ (7, 10),
+ (7, 10),
+ (7, 10),
+ (7, 6),
+ (7, 6),
+ (7, 6),
+ (7, 6),
+ (7, 33),
+ (7, 33),
+ (7, 33),
+ (7, 33),
+ (7, 17),
+ (7, 17),
+ (7, 17),
+ (7, 17),
+ (7, 9),
+ (7, 9),
+ (7, 9),
+ (7, 9),
+ (7, 5),
+ (7, 5),
+ (7, 5),
+ (7, 5),
+ (6, 63),
+ (6, 63),
+ (6, 63),
+ (6, 63),
+ (6, 63),
+ (6, 63),
+ (6, 63),
+ (6, 63),
+ (6, 3),
+ (6, 3),
+ (6, 3),
+ (6, 3),
+ (6, 3),
+ (6, 3),
+ (6, 3),
+ (6, 3),
+ (6, 36),
+ (6, 36),
+ (6, 36),
+ (6, 36),
+ (6, 36),
+ (6, 36),
+ (6, 36),
+ (6, 36),
+ (6, 24),
+ (6, 24),
+ (6, 24),
+ (6, 24),
+ (6, 24),
+ (6, 24),
+ (6, 24),
+ (6, 24),
+ (5, 62),
+ (5, 62),
+ (5, 62),
+ (5, 62),
+ (5, 62),
+ (5, 62),
+ (5, 62),
+ (5, 62),
+ (5, 62),
+ (5, 62),
+ (5, 62),
+ (5, 62),
+ (5, 62),
+ (5, 62),
+ (5, 62),
+ (5, 62),
+ (5, 2),
+ (5, 2),
+ (5, 2),
+ (5, 2),
+ (5, 2),
+ (5, 2),
+ (5, 2),
+ (5, 2),
+ (5, 2),
+ (5, 2),
+ (5, 2),
+ (5, 2),
+ (5, 2),
+ (5, 2),
+ (5, 2),
+ (5, 2),
+ (5, 61),
+ (5, 61),
+ (5, 61),
+ (5, 61),
+ (5, 61),
+ (5, 61),
+ (5, 61),
+ (5, 61),
+ (5, 61),
+ (5, 61),
+ (5, 61),
+ (5, 61),
+ (5, 61),
+ (5, 61),
+ (5, 61),
+ (5, 61),
+ (5, 1),
+ (5, 1),
+ (5, 1),
+ (5, 1),
+ (5, 1),
+ (5, 1),
+ (5, 1),
+ (5, 1),
+ (5, 1),
+ (5, 1),
+ (5, 1),
+ (5, 1),
+ (5, 1),
+ (5, 1),
+ (5, 1),
+ (5, 1),
+ (5, 56),
+ (5, 56),
+ (5, 56),
+ (5, 56),
+ (5, 56),
+ (5, 56),
+ (5, 56),
+ (5, 56),
+ (5, 56),
+ (5, 56),
+ (5, 56),
+ (5, 56),
+ (5, 56),
+ (5, 56),
+ (5, 56),
+ (5, 56),
+ (5, 52),
+ (5, 52),
+ (5, 52),
+ (5, 52),
+ (5, 52),
+ (5, 52),
+ (5, 52),
+ (5, 52),
+ (5, 52),
+ (5, 52),
+ (5, 52),
+ (5, 52),
+ (5, 52),
+ (5, 52),
+ (5, 52),
+ (5, 52),
+ (5, 44),
+ (5, 44),
+ (5, 44),
+ (5, 44),
+ (5, 44),
+ (5, 44),
+ (5, 44),
+ (5, 44),
+ (5, 44),
+ (5, 44),
+ (5, 44),
+ (5, 44),
+ (5, 44),
+ (5, 44),
+ (5, 44),
+ (5, 44),
+ (5, 28),
+ (5, 28),
+ (5, 28),
+ (5, 28),
+ (5, 28),
+ (5, 28),
+ (5, 28),
+ (5, 28),
+ (5, 28),
+ (5, 28),
+ (5, 28),
+ (5, 28),
+ (5, 28),
+ (5, 28),
+ (5, 28),
+ (5, 28),
+ (5, 40),
+ (5, 40),
+ (5, 40),
+ (5, 40),
+ (5, 40),
+ (5, 40),
+ (5, 40),
+ (5, 40),
+ (5, 40),
+ (5, 40),
+ (5, 40),
+ (5, 40),
+ (5, 40),
+ (5, 40),
+ (5, 40),
+ (5, 40),
+ (5, 20),
+ (5, 20),
+ (5, 20),
+ (5, 20),
+ (5, 20),
+ (5, 20),
+ (5, 20),
+ (5, 20),
+ (5, 20),
+ (5, 20),
+ (5, 20),
+ (5, 20),
+ (5, 20),
+ (5, 20),
+ (5, 20),
+ (5, 20),
+ (5, 48),
+ (5, 48),
+ (5, 48),
+ (5, 48),
+ (5, 48),
+ (5, 48),
+ (5, 48),
+ (5, 48),
+ (5, 48),
+ (5, 48),
+ (5, 48),
+ (5, 48),
+ (5, 48),
+ (5, 48),
+ (5, 48),
+ (5, 48),
+ (5, 12),
+ (5, 12),
+ (5, 12),
+ (5, 12),
+ (5, 12),
+ (5, 12),
+ (5, 12),
+ (5, 12),
+ (5, 12),
+ (5, 12),
+ (5, 12),
+ (5, 12),
+ (5, 12),
+ (5, 12),
+ (5, 12),
+ (5, 12),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 32),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 16),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 8),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+ (3, 60),
+};
diff --git a/appl/wm/mpeg/cbp.vlc b/appl/wm/mpeg/cbp.vlc
new file mode 100644
index 00000000..c7c9438c
--- /dev/null
+++ b/appl/wm/mpeg/cbp.vlc
@@ -0,0 +1,65 @@
+# Coded Block Pattern
+# vlc cbp < cbp.vlc > cbp.tab
+01011 1
+01001 2
+001101 3
+1101 4
+0010111 5
+0010011 6
+00011111 7
+1100 8
+0010110 9
+0010010 10
+00011110 11
+10011 12
+00011011 13
+00010111 14
+00010011 15
+1011 16
+0010101 17
+0010001 18
+00011101 19
+10001 20
+00011001 21
+00010101 22
+00010001 23
+001111 24
+00001111 25
+00001101 26
+000000011 27
+01111 28
+00001011 29
+00000111 30
+000000111 31
+1010 32
+0010100 33
+0010000 34
+00011100 35
+001110 36
+00001110 37
+00001100 38
+000000010 39
+10000 40
+00011000 41
+00010100 42
+00010000 43
+01110 44
+00001010 45
+00000110 46
+000000110 47
+10010 48
+00011010 49
+00010110 50
+00010010 51
+01101 52
+00001001 53
+00000101 54
+000000101 55
+01100 56
+00001000 57
+00000100 58
+000000100 59
+111 60
+01010 61
+01000 62
+001100 63
diff --git a/appl/wm/mpeg/cdc.tab b/appl/wm/mpeg/cdc.tab
new file mode 100644
index 00000000..91ccb057
--- /dev/null
+++ b/appl/wm/mpeg/cdc.tab
@@ -0,0 +1,261 @@
+# vlc cdc
+cdc_size: con 256;
+cdc_bits: con 8;
+cdc_table:= array[] of {
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 0),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (5, 5),
+ (5, 5),
+ (5, 5),
+ (5, 5),
+ (5, 5),
+ (5, 5),
+ (5, 5),
+ (5, 5),
+ (6, 6),
+ (6, 6),
+ (6, 6),
+ (6, 6),
+ (7, 7),
+ (7, 7),
+ (8, 8),
+ (0, UNDEF),
+};
diff --git a/appl/wm/mpeg/cdc.vlc b/appl/wm/mpeg/cdc.vlc
new file mode 100644
index 00000000..fadf199f
--- /dev/null
+++ b/appl/wm/mpeg/cdc.vlc
@@ -0,0 +1,11 @@
+# Chrominance DC
+# vlc cdc < cdc.vlc > cdc.tab
+00 0
+01 1
+10 2
+110 3
+1110 4
+11110 5
+111110 6
+1111110 7
+11111110 8
diff --git a/appl/wm/mpeg/closest.m b/appl/wm/mpeg/closest.m
new file mode 100644
index 00000000..eccd7dab
--- /dev/null
+++ b/appl/wm/mpeg/closest.m
@@ -0,0 +1,514 @@
+closest := array[16*16*16] of {
+ byte 255,byte 255,byte 255,byte 254,byte 254,byte 237,byte 220,byte 203,
+ byte 253,byte 236,byte 219,byte 202,byte 252,byte 235,byte 218,byte 201,
+ byte 255,byte 255,byte 255,byte 254,byte 254,byte 237,byte 220,byte 203,
+ byte 253,byte 236,byte 219,byte 202,byte 252,byte 235,byte 218,byte 201,
+ byte 255,byte 255,byte 255,byte 250,byte 250,byte 250,byte 220,byte 249,
+ byte 249,byte 249,byte 232,byte 248,byte 248,byte 248,byte 231,byte 201,
+ byte 251,byte 251,byte 250,byte 250,byte 250,byte 250,byte 249,byte 249,
+ byte 249,byte 249,byte 232,byte 248,byte 248,byte 248,byte 231,byte 201,
+ byte 251,byte 251,byte 250,byte 250,byte 250,byte 233,byte 233,byte 249,
+ byte 249,byte 232,byte 215,byte 215,byte 248,byte 231,byte 214,byte 197,
+ byte 234,byte 234,byte 250,byte 250,byte 233,byte 233,byte 216,byte 216,
+ byte 249,byte 232,byte 215,byte 198,byte 198,byte 231,byte 214,byte 197,
+ byte 217,byte 217,byte 217,byte 246,byte 233,byte 216,byte 216,byte 199,
+ byte 199,byte 215,byte 215,byte 198,byte 198,byte 198,byte 214,byte 197,
+ byte 200,byte 200,byte 246,byte 246,byte 246,byte 216,byte 199,byte 199,
+ byte 245,byte 245,byte 198,byte 244,byte 244,byte 244,byte 227,byte 197,
+ byte 247,byte 247,byte 246,byte 246,byte 246,byte 246,byte 199,byte 245,
+ byte 245,byte 245,byte 228,byte 244,byte 244,byte 244,byte 227,byte 193,
+ byte 230,byte 230,byte 246,byte 246,byte 229,byte 229,byte 212,byte 245,
+ byte 245,byte 228,byte 228,byte 211,byte 244,byte 227,byte 210,byte 193,
+ byte 213,byte 213,byte 229,byte 229,byte 212,byte 212,byte 212,byte 195,
+ byte 228,byte 228,byte 211,byte 211,byte 194,byte 227,byte 210,byte 193,
+ byte 196,byte 196,byte 242,byte 242,byte 212,byte 195,byte 195,byte 241,
+ byte 241,byte 211,byte 211,byte 194,byte 194,byte 240,byte 210,byte 193,
+ byte 243,byte 243,byte 242,byte 242,byte 242,byte 195,byte 195,byte 241,
+ byte 241,byte 241,byte 194,byte 194,byte 240,byte 240,byte 239,byte 205,
+ byte 226,byte 226,byte 242,byte 242,byte 225,byte 225,byte 195,byte 241,
+ byte 241,byte 224,byte 224,byte 240,byte 240,byte 239,byte 239,byte 205,
+ byte 209,byte 209,byte 225,byte 225,byte 208,byte 208,byte 208,byte 224,
+ byte 224,byte 223,byte 223,byte 223,byte 239,byte 239,byte 222,byte 205,
+ byte 192,byte 192,byte 192,byte 192,byte 207,byte 207,byte 207,byte 207,
+ byte 206,byte 206,byte 206,byte 206,byte 205,byte 205,byte 205,byte 205,
+ byte 255,byte 255,byte 255,byte 254,byte 254,byte 237,byte 220,byte 203,
+ byte 253,byte 236,byte 219,byte 202,byte 252,byte 235,byte 218,byte 201,
+ byte 255,byte 238,byte 221,byte 221,byte 254,byte 237,byte 220,byte 203,
+ byte 253,byte 236,byte 219,byte 202,byte 252,byte 235,byte 218,byte 201,
+ byte 255,byte 221,byte 221,byte 221,byte 204,byte 250,byte 220,byte 249,
+ byte 249,byte 249,byte 232,byte 248,byte 248,byte 248,byte 231,byte 201,
+ byte 251,byte 221,byte 221,byte 204,byte 250,byte 250,byte 249,byte 249,
+ byte 249,byte 249,byte 232,byte 248,byte 248,byte 248,byte 231,byte 201,
+ byte 251,byte 251,byte 204,byte 250,byte 250,byte 233,byte 233,byte 249,
+ byte 249,byte 232,byte 215,byte 215,byte 248,byte 231,byte 214,byte 197,
+ byte 234,byte 234,byte 250,byte 250,byte 233,byte 233,byte 216,byte 216,
+ byte 249,byte 232,byte 215,byte 198,byte 198,byte 231,byte 214,byte 197,
+ byte 217,byte 217,byte 217,byte 246,byte 233,byte 216,byte 216,byte 199,
+ byte 199,byte 215,byte 215,byte 198,byte 198,byte 198,byte 214,byte 197,
+ byte 200,byte 200,byte 246,byte 246,byte 246,byte 216,byte 199,byte 199,
+ byte 245,byte 245,byte 198,byte 244,byte 244,byte 244,byte 227,byte 197,
+ byte 247,byte 247,byte 246,byte 246,byte 246,byte 246,byte 199,byte 245,
+ byte 245,byte 245,byte 228,byte 244,byte 244,byte 244,byte 227,byte 193,
+ byte 230,byte 230,byte 246,byte 246,byte 229,byte 229,byte 212,byte 245,
+ byte 245,byte 228,byte 228,byte 211,byte 244,byte 227,byte 210,byte 193,
+ byte 213,byte 213,byte 229,byte 229,byte 212,byte 212,byte 212,byte 195,
+ byte 228,byte 228,byte 211,byte 211,byte 194,byte 227,byte 210,byte 193,
+ byte 196,byte 196,byte 242,byte 242,byte 212,byte 195,byte 195,byte 241,
+ byte 241,byte 211,byte 211,byte 194,byte 194,byte 240,byte 210,byte 193,
+ byte 243,byte 243,byte 242,byte 242,byte 242,byte 195,byte 195,byte 241,
+ byte 241,byte 241,byte 194,byte 194,byte 240,byte 240,byte 239,byte 205,
+ byte 226,byte 226,byte 242,byte 242,byte 225,byte 225,byte 195,byte 241,
+ byte 241,byte 224,byte 224,byte 240,byte 240,byte 239,byte 239,byte 205,
+ byte 209,byte 209,byte 225,byte 225,byte 208,byte 208,byte 208,byte 224,
+ byte 224,byte 223,byte 223,byte 223,byte 239,byte 239,byte 222,byte 205,
+ byte 192,byte 192,byte 192,byte 192,byte 207,byte 207,byte 207,byte 207,
+ byte 206,byte 206,byte 206,byte 206,byte 205,byte 205,byte 205,byte 205,
+ byte 255,byte 255,byte 255,byte 191,byte 191,byte 191,byte 220,byte 190,
+ byte 190,byte 190,byte 173,byte 189,byte 189,byte 189,byte 172,byte 201,
+ byte 255,byte 221,byte 221,byte 221,byte 204,byte 191,byte 220,byte 190,
+ byte 190,byte 190,byte 173,byte 189,byte 189,byte 189,byte 172,byte 201,
+ byte 255,byte 221,byte 221,byte 204,byte 204,byte 204,byte 186,byte 186,
+ byte 186,byte 186,byte 186,byte 185,byte 185,byte 185,byte 168,byte 201,
+ byte 188,byte 221,byte 204,byte 204,byte 204,byte 187,byte 186,byte 186,
+ byte 186,byte 186,byte 232,byte 185,byte 185,byte 185,byte 168,byte 201,
+ byte 188,byte 204,byte 204,byte 204,byte 187,byte 187,byte 186,byte 186,
+ byte 186,byte 186,byte 169,byte 185,byte 185,byte 185,byte 168,byte 197,
+ byte 188,byte 188,byte 204,byte 187,byte 187,byte 233,byte 216,byte 186,
+ byte 186,byte 186,byte 215,byte 185,byte 185,byte 185,byte 168,byte 197,
+ byte 217,byte 217,byte 183,byte 183,byte 183,byte 216,byte 216,byte 199,
+ byte 182,byte 182,byte 215,byte 198,byte 198,byte 181,byte 214,byte 197,
+ byte 184,byte 184,byte 183,byte 183,byte 183,byte 183,byte 199,byte 182,
+ byte 182,byte 182,byte 182,byte 181,byte 181,byte 181,byte 181,byte 197,
+ byte 184,byte 184,byte 183,byte 183,byte 183,byte 183,byte 182,byte 182,
+ byte 182,byte 182,byte 182,byte 181,byte 181,byte 181,byte 164,byte 193,
+ byte 184,byte 184,byte 183,byte 183,byte 183,byte 183,byte 182,byte 182,
+ byte 182,byte 228,byte 165,byte 181,byte 181,byte 164,byte 164,byte 193,
+ byte 167,byte 167,byte 183,byte 229,byte 166,byte 212,byte 212,byte 182,
+ byte 182,byte 165,byte 211,byte 211,byte 181,byte 164,byte 210,byte 193,
+ byte 180,byte 180,byte 179,byte 179,byte 179,byte 179,byte 195,byte 178,
+ byte 178,byte 178,byte 211,byte 194,byte 177,byte 177,byte 177,byte 193,
+ byte 180,byte 180,byte 179,byte 179,byte 179,byte 179,byte 195,byte 178,
+ byte 178,byte 178,byte 178,byte 177,byte 177,byte 177,byte 177,byte 205,
+ byte 180,byte 180,byte 179,byte 179,byte 179,byte 179,byte 178,byte 178,
+ byte 178,byte 161,byte 161,byte 177,byte 177,byte 177,byte 160,byte 205,
+ byte 163,byte 163,byte 162,byte 162,byte 162,byte 162,byte 208,byte 178,
+ byte 161,byte 161,byte 223,byte 177,byte 177,byte 160,byte 160,byte 205,
+ byte 192,byte 192,byte 192,byte 192,byte 207,byte 207,byte 207,byte 207,
+ byte 206,byte 206,byte 206,byte 206,byte 205,byte 205,byte 205,byte 205,
+ byte 176,byte 176,byte 191,byte 191,byte 191,byte 191,byte 190,byte 190,
+ byte 190,byte 190,byte 173,byte 189,byte 189,byte 189,byte 172,byte 201,
+ byte 176,byte 221,byte 221,byte 204,byte 191,byte 191,byte 190,byte 190,
+ byte 190,byte 190,byte 173,byte 189,byte 189,byte 189,byte 172,byte 201,
+ byte 188,byte 221,byte 204,byte 204,byte 204,byte 187,byte 186,byte 186,
+ byte 186,byte 186,byte 173,byte 185,byte 185,byte 185,byte 168,byte 201,
+ byte 188,byte 204,byte 204,byte 204,byte 187,byte 187,byte 186,byte 186,
+ byte 186,byte 186,byte 169,byte 185,byte 185,byte 185,byte 168,byte 201,
+ byte 188,byte 188,byte 204,byte 187,byte 187,byte 187,byte 186,byte 186,
+ byte 186,byte 186,byte 169,byte 185,byte 185,byte 185,byte 168,byte 197,
+ byte 188,byte 188,byte 187,byte 187,byte 187,byte 170,byte 170,byte 186,
+ byte 186,byte 169,byte 169,byte 185,byte 185,byte 168,byte 168,byte 197,
+ byte 184,byte 184,byte 183,byte 183,byte 183,byte 170,byte 170,byte 182,
+ byte 182,byte 169,byte 152,byte 152,byte 181,byte 168,byte 151,byte 197,
+ byte 184,byte 184,byte 183,byte 183,byte 183,byte 183,byte 182,byte 182,
+ byte 182,byte 182,byte 182,byte 181,byte 181,byte 181,byte 164,byte 197,
+ byte 184,byte 184,byte 183,byte 183,byte 183,byte 183,byte 182,byte 182,
+ byte 182,byte 182,byte 165,byte 181,byte 181,byte 181,byte 164,byte 193,
+ byte 184,byte 184,byte 183,byte 183,byte 183,byte 166,byte 166,byte 182,
+ byte 182,byte 165,byte 165,byte 181,byte 181,byte 164,byte 164,byte 193,
+ byte 167,byte 167,byte 167,byte 166,byte 166,byte 166,byte 149,byte 182,
+ byte 165,byte 165,byte 165,byte 148,byte 181,byte 164,byte 147,byte 193,
+ byte 180,byte 180,byte 179,byte 179,byte 179,byte 179,byte 149,byte 178,
+ byte 178,byte 178,byte 148,byte 177,byte 177,byte 177,byte 147,byte 193,
+ byte 180,byte 180,byte 179,byte 179,byte 179,byte 179,byte 178,byte 178,
+ byte 178,byte 178,byte 178,byte 177,byte 177,byte 177,byte 160,byte 205,
+ byte 180,byte 180,byte 179,byte 179,byte 179,byte 162,byte 162,byte 178,
+ byte 178,byte 161,byte 161,byte 177,byte 177,byte 160,byte 160,byte 205,
+ byte 163,byte 163,byte 162,byte 162,byte 162,byte 162,byte 145,byte 161,
+ byte 161,byte 161,byte 144,byte 144,byte 160,byte 160,byte 160,byte 205,
+ byte 192,byte 192,byte 192,byte 192,byte 207,byte 207,byte 207,byte 207,
+ byte 206,byte 206,byte 206,byte 206,byte 205,byte 205,byte 205,byte 205,
+ byte 176,byte 176,byte 191,byte 191,byte 191,byte 174,byte 174,byte 190,
+ byte 190,byte 173,byte 156,byte 156,byte 189,byte 172,byte 155,byte 138,
+ byte 176,byte 176,byte 204,byte 191,byte 191,byte 174,byte 174,byte 190,
+ byte 190,byte 173,byte 156,byte 156,byte 189,byte 172,byte 155,byte 138,
+ byte 188,byte 204,byte 204,byte 204,byte 187,byte 187,byte 186,byte 186,
+ byte 186,byte 186,byte 169,byte 185,byte 185,byte 185,byte 168,byte 138,
+ byte 188,byte 188,byte 204,byte 187,byte 187,byte 187,byte 186,byte 186,
+ byte 186,byte 186,byte 169,byte 185,byte 185,byte 185,byte 168,byte 138,
+ byte 188,byte 188,byte 187,byte 187,byte 187,byte 170,byte 170,byte 186,
+ byte 186,byte 169,byte 169,byte 185,byte 185,byte 168,byte 151,byte 134,
+ byte 171,byte 171,byte 187,byte 187,byte 170,byte 170,byte 170,byte 186,
+ byte 186,byte 169,byte 152,byte 152,byte 185,byte 168,byte 151,byte 134,
+ byte 171,byte 171,byte 183,byte 183,byte 170,byte 170,byte 170,byte 153,
+ byte 182,byte 169,byte 152,byte 135,byte 135,byte 168,byte 151,byte 134,
+ byte 184,byte 184,byte 183,byte 183,byte 183,byte 183,byte 153,byte 182,
+ byte 182,byte 182,byte 182,byte 181,byte 181,byte 181,byte 164,byte 134,
+ byte 184,byte 184,byte 183,byte 183,byte 183,byte 183,byte 182,byte 182,
+ byte 182,byte 182,byte 165,byte 181,byte 181,byte 181,byte 164,byte 130,
+ byte 167,byte 167,byte 183,byte 183,byte 166,byte 166,byte 166,byte 182,
+ byte 182,byte 165,byte 165,byte 181,byte 181,byte 164,byte 147,byte 130,
+ byte 150,byte 150,byte 166,byte 166,byte 166,byte 149,byte 149,byte 182,
+ byte 165,byte 165,byte 148,byte 148,byte 164,byte 164,byte 147,byte 130,
+ byte 150,byte 150,byte 179,byte 179,byte 179,byte 149,byte 132,byte 178,
+ byte 178,byte 178,byte 148,byte 131,byte 177,byte 177,byte 147,byte 130,
+ byte 180,byte 180,byte 179,byte 179,byte 179,byte 179,byte 132,byte 178,
+ byte 178,byte 178,byte 161,byte 177,byte 177,byte 177,byte 160,byte 142,
+ byte 163,byte 163,byte 179,byte 179,byte 162,byte 162,byte 162,byte 178,
+ byte 178,byte 161,byte 161,byte 177,byte 177,byte 160,byte 160,byte 142,
+ byte 146,byte 146,byte 162,byte 162,byte 145,byte 145,byte 145,byte 161,
+ byte 161,byte 144,byte 144,byte 144,byte 160,byte 160,byte 159,byte 142,
+ byte 129,byte 129,byte 129,byte 129,byte 128,byte 128,byte 128,byte 128,
+ byte 143,byte 143,byte 143,byte 143,byte 142,byte 142,byte 142,byte 142,
+ byte 175,byte 175,byte 191,byte 191,byte 174,byte 174,byte 157,byte 157,
+ byte 190,byte 173,byte 156,byte 139,byte 139,byte 172,byte 155,byte 138,
+ byte 175,byte 175,byte 191,byte 191,byte 174,byte 174,byte 157,byte 157,
+ byte 190,byte 173,byte 156,byte 139,byte 139,byte 172,byte 155,byte 138,
+ byte 188,byte 188,byte 204,byte 187,byte 187,byte 187,byte 157,byte 186,
+ byte 186,byte 186,byte 156,byte 185,byte 185,byte 185,byte 168,byte 138,
+ byte 188,byte 188,byte 187,byte 187,byte 187,byte 170,byte 170,byte 186,
+ byte 186,byte 169,byte 169,byte 185,byte 185,byte 168,byte 168,byte 138,
+ byte 171,byte 171,byte 187,byte 187,byte 170,byte 170,byte 170,byte 186,
+ byte 186,byte 169,byte 152,byte 152,byte 185,byte 168,byte 151,byte 134,
+ byte 171,byte 171,byte 187,byte 170,byte 170,byte 170,byte 170,byte 153,
+ byte 169,byte 169,byte 152,byte 135,byte 135,byte 168,byte 151,byte 134,
+ byte 154,byte 154,byte 154,byte 170,byte 170,byte 170,byte 153,byte 153,
+ byte 169,byte 152,byte 152,byte 135,byte 135,byte 135,byte 151,byte 134,
+ byte 154,byte 154,byte 183,byte 183,byte 183,byte 153,byte 153,byte 153,
+ byte 182,byte 182,byte 135,byte 135,byte 181,byte 181,byte 164,byte 134,
+ byte 184,byte 184,byte 183,byte 183,byte 183,byte 166,byte 166,byte 182,
+ byte 182,byte 165,byte 165,byte 181,byte 181,byte 164,byte 164,byte 130,
+ byte 167,byte 167,byte 183,byte 166,byte 166,byte 166,byte 149,byte 182,
+ byte 165,byte 165,byte 165,byte 148,byte 181,byte 164,byte 147,byte 130,
+ byte 150,byte 150,byte 150,byte 166,byte 149,byte 149,byte 149,byte 132,
+ byte 165,byte 165,byte 148,byte 148,byte 131,byte 147,byte 147,byte 130,
+ byte 133,byte 133,byte 179,byte 179,byte 149,byte 132,byte 132,byte 132,
+ byte 178,byte 148,byte 148,byte 131,byte 131,byte 131,byte 130,byte 130,
+ byte 133,byte 133,byte 179,byte 179,byte 179,byte 132,byte 132,byte 178,
+ byte 178,byte 178,byte 131,byte 131,byte 131,byte 177,byte 160,byte 142,
+ byte 163,byte 163,byte 179,byte 162,byte 162,byte 162,byte 132,byte 178,
+ byte 161,byte 161,byte 144,byte 131,byte 177,byte 160,byte 160,byte 142,
+ byte 146,byte 146,byte 162,byte 162,byte 145,byte 145,byte 145,byte 161,
+ byte 161,byte 144,byte 144,byte 143,byte 160,byte 160,byte 159,byte 142,
+ byte 129,byte 129,byte 129,byte 129,byte 128,byte 128,byte 128,byte 128,
+ byte 143,byte 143,byte 143,byte 143,byte 142,byte 142,byte 142,byte 142,
+ byte 158,byte 158,byte 158,byte 112,byte 174,byte 157,byte 157,byte 140,
+ byte 140,byte 156,byte 156,byte 139,byte 139,byte 139,byte 155,byte 138,
+ byte 158,byte 158,byte 158,byte 112,byte 174,byte 157,byte 157,byte 140,
+ byte 140,byte 156,byte 156,byte 139,byte 139,byte 139,byte 155,byte 138,
+ byte 158,byte 158,byte 124,byte 124,byte 124,byte 157,byte 157,byte 140,
+ byte 123,byte 123,byte 156,byte 139,byte 139,byte 122,byte 155,byte 138,
+ byte 125,byte 125,byte 124,byte 124,byte 124,byte 170,byte 170,byte 123,
+ byte 123,byte 169,byte 152,byte 152,byte 122,byte 168,byte 151,byte 138,
+ byte 171,byte 171,byte 124,byte 124,byte 170,byte 170,byte 170,byte 153,
+ byte 123,byte 169,byte 152,byte 135,byte 135,byte 168,byte 151,byte 134,
+ byte 154,byte 154,byte 154,byte 170,byte 170,byte 170,byte 153,byte 153,
+ byte 169,byte 152,byte 152,byte 135,byte 135,byte 135,byte 151,byte 134,
+ byte 154,byte 154,byte 154,byte 170,byte 170,byte 153,byte 153,byte 153,
+ byte 136,byte 152,byte 135,byte 135,byte 135,byte 135,byte 134,byte 134,
+ byte 137,byte 137,byte 137,byte 120,byte 153,byte 153,byte 153,byte 136,
+ byte 136,byte 136,byte 135,byte 135,byte 135,byte 118,byte 164,byte 134,
+ byte 137,byte 137,byte 120,byte 120,byte 120,byte 166,byte 136,byte 136,
+ byte 136,byte 165,byte 165,byte 118,byte 118,byte 164,byte 147,byte 130,
+ byte 150,byte 150,byte 120,byte 166,byte 166,byte 149,byte 149,byte 136,
+ byte 165,byte 165,byte 148,byte 148,byte 118,byte 164,byte 147,byte 130,
+ byte 150,byte 150,byte 150,byte 149,byte 149,byte 149,byte 132,byte 132,
+ byte 165,byte 148,byte 148,byte 131,byte 131,byte 147,byte 147,byte 130,
+ byte 133,byte 133,byte 133,byte 149,byte 132,byte 132,byte 132,byte 132,
+ byte 115,byte 148,byte 131,byte 131,byte 131,byte 131,byte 130,byte 130,
+ byte 133,byte 133,byte 133,byte 116,byte 132,byte 132,byte 132,byte 132,
+ byte 115,byte 115,byte 131,byte 131,byte 131,byte 131,byte 160,byte 142,
+ byte 133,byte 133,byte 116,byte 162,byte 162,byte 132,byte 132,byte 115,
+ byte 161,byte 161,byte 144,byte 131,byte 131,byte 160,byte 160,byte 142,
+ byte 146,byte 146,byte 146,byte 145,byte 145,byte 145,byte 128,byte 161,
+ byte 144,byte 144,byte 144,byte 143,byte 160,byte 160,byte 159,byte 142,
+ byte 129,byte 129,byte 129,byte 129,byte 128,byte 128,byte 128,byte 128,
+ byte 143,byte 143,byte 143,byte 143,byte 142,byte 142,byte 142,byte 142,
+ byte 141,byte 141,byte 112,byte 112,byte 112,byte 157,byte 140,byte 140,
+ byte 140,byte 127,byte 139,byte 126,byte 126,byte 126,byte 109,byte 138,
+ byte 141,byte 141,byte 112,byte 112,byte 112,byte 157,byte 140,byte 140,
+ byte 140,byte 127,byte 139,byte 126,byte 126,byte 126,byte 109,byte 138,
+ byte 125,byte 125,byte 124,byte 124,byte 124,byte 124,byte 140,byte 123,
+ byte 123,byte 123,byte 123,byte 122,byte 122,byte 122,byte 122,byte 138,
+ byte 125,byte 125,byte 124,byte 124,byte 124,byte 124,byte 123,byte 123,
+ byte 123,byte 123,byte 123,byte 122,byte 122,byte 122,byte 105,byte 138,
+ byte 125,byte 125,byte 124,byte 124,byte 124,byte 124,byte 153,byte 123,
+ byte 123,byte 123,byte 152,byte 122,byte 122,byte 122,byte 105,byte 134,
+ byte 154,byte 154,byte 124,byte 124,byte 124,byte 153,byte 153,byte 153,
+ byte 123,byte 123,byte 135,byte 135,byte 122,byte 122,byte 105,byte 134,
+ byte 137,byte 137,byte 137,byte 120,byte 153,byte 153,byte 153,byte 136,
+ byte 136,byte 136,byte 135,byte 135,byte 135,byte 118,byte 105,byte 134,
+ byte 137,byte 137,byte 120,byte 120,byte 120,byte 153,byte 136,byte 136,
+ byte 136,byte 119,byte 119,byte 118,byte 118,byte 118,byte 118,byte 134,
+ byte 137,byte 137,byte 120,byte 120,byte 120,byte 120,byte 136,byte 136,
+ byte 119,byte 119,byte 119,byte 118,byte 118,byte 118,byte 101,byte 130,
+ byte 121,byte 121,byte 120,byte 120,byte 120,byte 120,byte 136,byte 119,
+ byte 119,byte 119,byte 102,byte 118,byte 118,byte 118,byte 101,byte 130,
+ byte 133,byte 133,byte 120,byte 120,byte 149,byte 132,byte 132,byte 119,
+ byte 119,byte 102,byte 148,byte 131,byte 131,byte 101,byte 101,byte 130,
+ byte 117,byte 117,byte 116,byte 116,byte 116,byte 132,byte 132,byte 115,
+ byte 115,byte 115,byte 131,byte 131,byte 114,byte 114,byte 114,byte 130,
+ byte 117,byte 117,byte 116,byte 116,byte 116,byte 116,byte 132,byte 115,
+ byte 115,byte 115,byte 131,byte 114,byte 114,byte 114,byte 114,byte 142,
+ byte 117,byte 117,byte 116,byte 116,byte 116,byte 116,byte 115,byte 115,
+ byte 115,byte 115,byte 98,byte 114,byte 114,byte 114,byte 97,byte 142,
+ byte 100,byte 100,byte 116,byte 99,byte 99,byte 99,byte 99,byte 115,
+ byte 98,byte 98,byte 98,byte 114,byte 114,byte 97,byte 97,byte 142,
+ byte 129,byte 129,byte 129,byte 129,byte 128,byte 128,byte 128,byte 128,
+ byte 143,byte 143,byte 143,byte 143,byte 142,byte 142,byte 142,byte 142,
+ byte 113,byte 113,byte 112,byte 112,byte 112,byte 112,byte 140,byte 140,
+ byte 127,byte 127,byte 110,byte 126,byte 126,byte 126,byte 109,byte 75,
+ byte 113,byte 113,byte 112,byte 112,byte 112,byte 112,byte 140,byte 140,
+ byte 127,byte 127,byte 110,byte 126,byte 126,byte 126,byte 109,byte 75,
+ byte 125,byte 125,byte 124,byte 124,byte 124,byte 124,byte 123,byte 123,
+ byte 123,byte 123,byte 123,byte 122,byte 122,byte 122,byte 105,byte 75,
+ byte 125,byte 125,byte 124,byte 124,byte 124,byte 124,byte 123,byte 123,
+ byte 123,byte 123,byte 106,byte 122,byte 122,byte 122,byte 105,byte 75,
+ byte 125,byte 125,byte 124,byte 124,byte 124,byte 124,byte 123,byte 123,
+ byte 123,byte 123,byte 106,byte 122,byte 122,byte 122,byte 105,byte 71,
+ byte 125,byte 125,byte 124,byte 124,byte 124,byte 107,byte 107,byte 123,
+ byte 123,byte 106,byte 106,byte 122,byte 122,byte 105,byte 105,byte 71,
+ byte 137,byte 137,byte 120,byte 120,byte 120,byte 107,byte 136,byte 136,
+ byte 136,byte 106,byte 106,byte 118,byte 118,byte 105,byte 88,byte 71,
+ byte 137,byte 137,byte 120,byte 120,byte 120,byte 120,byte 136,byte 136,
+ byte 119,byte 119,byte 119,byte 118,byte 118,byte 118,byte 101,byte 71,
+ byte 121,byte 121,byte 120,byte 120,byte 120,byte 120,byte 136,byte 119,
+ byte 119,byte 119,byte 102,byte 118,byte 118,byte 118,byte 101,byte 67,
+ byte 121,byte 121,byte 120,byte 120,byte 120,byte 103,byte 103,byte 119,
+ byte 119,byte 102,byte 102,byte 118,byte 118,byte 101,byte 101,byte 67,
+ byte 104,byte 104,byte 120,byte 103,byte 103,byte 103,byte 103,byte 119,
+ byte 102,byte 102,byte 102,byte 118,byte 118,byte 101,byte 84,byte 67,
+ byte 117,byte 117,byte 116,byte 116,byte 116,byte 116,byte 115,byte 115,
+ byte 115,byte 115,byte 115,byte 114,byte 114,byte 114,byte 114,byte 67,
+ byte 117,byte 117,byte 116,byte 116,byte 116,byte 116,byte 115,byte 115,
+ byte 115,byte 115,byte 115,byte 114,byte 114,byte 114,byte 97,byte 79,
+ byte 117,byte 117,byte 116,byte 116,byte 116,byte 99,byte 99,byte 115,
+ byte 115,byte 98,byte 98,byte 114,byte 114,byte 97,byte 97,byte 79,
+ byte 100,byte 100,byte 99,byte 99,byte 99,byte 99,byte 82,byte 98,
+ byte 98,byte 98,byte 81,byte 114,byte 97,byte 97,byte 97,byte 79,
+ byte 66,byte 66,byte 66,byte 66,byte 65,byte 65,byte 65,byte 65,
+ byte 64,byte 64,byte 64,byte 64,byte 79,byte 79,byte 79,byte 79,
+ byte 96,byte 96,byte 112,byte 112,byte 111,byte 111,byte 94,byte 127,
+ byte 127,byte 110,byte 110,byte 93,byte 126,byte 109,byte 92,byte 75,
+ byte 96,byte 96,byte 112,byte 112,byte 111,byte 111,byte 94,byte 127,
+ byte 127,byte 110,byte 110,byte 93,byte 126,byte 109,byte 92,byte 75,
+ byte 125,byte 125,byte 124,byte 124,byte 124,byte 124,byte 123,byte 123,
+ byte 123,byte 123,byte 106,byte 122,byte 122,byte 105,byte 105,byte 75,
+ byte 125,byte 125,byte 124,byte 124,byte 124,byte 107,byte 107,byte 123,
+ byte 123,byte 106,byte 106,byte 122,byte 122,byte 105,byte 105,byte 75,
+ byte 108,byte 108,byte 124,byte 124,byte 107,byte 107,byte 107,byte 123,
+ byte 123,byte 106,byte 106,byte 122,byte 122,byte 105,byte 88,byte 71,
+ byte 108,byte 108,byte 124,byte 107,byte 107,byte 107,byte 90,byte 123,
+ byte 106,byte 106,byte 106,byte 89,byte 122,byte 105,byte 88,byte 71,
+ byte 91,byte 91,byte 120,byte 107,byte 107,byte 90,byte 90,byte 136,
+ byte 106,byte 106,byte 89,byte 89,byte 118,byte 105,byte 88,byte 71,
+ byte 121,byte 121,byte 120,byte 120,byte 120,byte 120,byte 136,byte 119,
+ byte 119,byte 119,byte 102,byte 118,byte 118,byte 118,byte 101,byte 71,
+ byte 121,byte 121,byte 120,byte 120,byte 120,byte 103,byte 103,byte 119,
+ byte 119,byte 102,byte 102,byte 118,byte 118,byte 101,byte 101,byte 67,
+ byte 104,byte 104,byte 120,byte 103,byte 103,byte 103,byte 103,byte 119,
+ byte 102,byte 102,byte 102,byte 118,byte 118,byte 101,byte 84,byte 67,
+ byte 104,byte 104,byte 103,byte 103,byte 103,byte 103,byte 86,byte 102,
+ byte 102,byte 102,byte 85,byte 85,byte 101,byte 101,byte 84,byte 67,
+ byte 87,byte 87,byte 116,byte 116,byte 116,byte 86,byte 86,byte 115,
+ byte 115,byte 115,byte 85,byte 85,byte 114,byte 114,byte 84,byte 67,
+ byte 117,byte 117,byte 116,byte 116,byte 116,byte 116,byte 115,byte 115,
+ byte 115,byte 115,byte 98,byte 114,byte 114,byte 114,byte 97,byte 79,
+ byte 100,byte 100,byte 99,byte 99,byte 99,byte 99,byte 99,byte 115,
+ byte 98,byte 98,byte 98,byte 114,byte 114,byte 97,byte 97,byte 79,
+ byte 83,byte 83,byte 99,byte 99,byte 82,byte 82,byte 82,byte 98,
+ byte 98,byte 81,byte 81,byte 81,byte 97,byte 97,byte 80,byte 79,
+ byte 66,byte 66,byte 66,byte 66,byte 65,byte 65,byte 65,byte 65,
+ byte 64,byte 64,byte 64,byte 64,byte 79,byte 79,byte 79,byte 79,
+ byte 95,byte 95,byte 111,byte 111,byte 94,byte 94,byte 94,byte 77,
+ byte 110,byte 110,byte 93,byte 93,byte 76,byte 109,byte 92,byte 75,
+ byte 95,byte 95,byte 111,byte 111,byte 94,byte 94,byte 94,byte 77,
+ byte 110,byte 110,byte 93,byte 93,byte 76,byte 109,byte 92,byte 75,
+ byte 108,byte 108,byte 124,byte 111,byte 107,byte 94,byte 94,byte 123,
+ byte 123,byte 106,byte 93,byte 93,byte 122,byte 105,byte 92,byte 75,
+ byte 108,byte 108,byte 108,byte 107,byte 107,byte 107,byte 90,byte 123,
+ byte 106,byte 106,byte 106,byte 89,byte 122,byte 105,byte 88,byte 75,
+ byte 91,byte 91,byte 107,byte 107,byte 107,byte 90,byte 90,byte 123,
+ byte 106,byte 106,byte 89,byte 89,byte 105,byte 105,byte 88,byte 71,
+ byte 91,byte 91,byte 91,byte 107,byte 90,byte 90,byte 90,byte 73,
+ byte 106,byte 106,byte 89,byte 89,byte 72,byte 88,byte 88,byte 71,
+ byte 91,byte 91,byte 91,byte 90,byte 90,byte 90,byte 73,byte 73,
+ byte 106,byte 89,byte 89,byte 72,byte 72,byte 88,byte 88,byte 71,
+ byte 74,byte 74,byte 120,byte 120,byte 120,byte 73,byte 73,byte 119,
+ byte 119,byte 102,byte 89,byte 72,byte 72,byte 101,byte 101,byte 71,
+ byte 104,byte 104,byte 120,byte 103,byte 103,byte 103,byte 103,byte 119,
+ byte 102,byte 102,byte 102,byte 118,byte 118,byte 101,byte 84,byte 67,
+ byte 104,byte 104,byte 103,byte 103,byte 103,byte 103,byte 86,byte 102,
+ byte 102,byte 102,byte 85,byte 85,byte 101,byte 101,byte 84,byte 67,
+ byte 87,byte 87,byte 87,byte 103,byte 86,byte 86,byte 86,byte 86,
+ byte 102,byte 85,byte 85,byte 85,byte 85,byte 84,byte 84,byte 67,
+ byte 87,byte 87,byte 87,byte 86,byte 86,byte 86,byte 69,byte 69,
+ byte 115,byte 85,byte 85,byte 85,byte 68,byte 68,byte 67,byte 67,
+ byte 70,byte 70,byte 116,byte 116,byte 99,byte 69,byte 69,byte 69,
+ byte 115,byte 98,byte 85,byte 68,byte 68,byte 97,byte 97,byte 79,
+ byte 100,byte 100,byte 99,byte 99,byte 99,byte 82,byte 82,byte 98,
+ byte 98,byte 98,byte 81,byte 68,byte 97,byte 97,byte 97,byte 79,
+ byte 83,byte 83,byte 83,byte 82,byte 82,byte 82,byte 82,byte 98,
+ byte 81,byte 81,byte 81,byte 64,byte 97,byte 97,byte 80,byte 79,
+ byte 66,byte 66,byte 66,byte 66,byte 65,byte 65,byte 65,byte 65,
+ byte 64,byte 64,byte 64,byte 64,byte 79,byte 79,byte 79,byte 79,
+ byte 78,byte 78,byte 49,byte 49,byte 94,byte 77,byte 77,byte 48,
+ byte 48,byte 93,byte 93,byte 76,byte 76,byte 63,byte 92,byte 75,
+ byte 78,byte 78,byte 49,byte 49,byte 94,byte 77,byte 77,byte 48,
+ byte 48,byte 93,byte 93,byte 76,byte 76,byte 63,byte 92,byte 75,
+ byte 62,byte 62,byte 61,byte 61,byte 61,byte 61,byte 77,byte 60,
+ byte 60,byte 60,byte 93,byte 76,byte 59,byte 59,byte 59,byte 75,
+ byte 62,byte 62,byte 61,byte 61,byte 61,byte 61,byte 90,byte 60,
+ byte 60,byte 60,byte 89,byte 59,byte 59,byte 59,byte 88,byte 75,
+ byte 91,byte 91,byte 61,byte 61,byte 61,byte 90,byte 73,byte 60,
+ byte 60,byte 60,byte 89,byte 72,byte 59,byte 59,byte 88,byte 71,
+ byte 74,byte 74,byte 61,byte 61,byte 90,byte 73,byte 73,byte 73,
+ byte 60,byte 89,byte 89,byte 72,byte 72,byte 72,byte 71,byte 71,
+ byte 74,byte 74,byte 74,byte 90,byte 73,byte 73,byte 73,byte 73,
+ byte 56,byte 89,byte 72,byte 72,byte 72,byte 72,byte 71,byte 71,
+ byte 58,byte 58,byte 57,byte 57,byte 57,byte 73,byte 73,byte 56,
+ byte 56,byte 56,byte 72,byte 72,byte 55,byte 55,byte 55,byte 71,
+ byte 58,byte 58,byte 57,byte 57,byte 57,byte 57,byte 56,byte 56,
+ byte 56,byte 56,byte 56,byte 55,byte 55,byte 55,byte 55,byte 67,
+ byte 87,byte 87,byte 57,byte 57,byte 57,byte 86,byte 86,byte 56,
+ byte 56,byte 56,byte 85,byte 85,byte 55,byte 55,byte 84,byte 67,
+ byte 87,byte 87,byte 87,byte 86,byte 86,byte 86,byte 69,byte 69,
+ byte 56,byte 85,byte 85,byte 85,byte 68,byte 68,byte 67,byte 67,
+ byte 70,byte 70,byte 70,byte 53,byte 69,byte 69,byte 69,byte 69,
+ byte 52,byte 85,byte 85,byte 68,byte 68,byte 68,byte 67,byte 67,
+ byte 70,byte 70,byte 53,byte 53,byte 53,byte 69,byte 69,byte 52,
+ byte 52,byte 52,byte 68,byte 68,byte 68,byte 51,byte 51,byte 79,
+ byte 54,byte 54,byte 53,byte 53,byte 53,byte 69,byte 69,byte 52,
+ byte 52,byte 52,byte 68,byte 68,byte 51,byte 51,byte 80,byte 79,
+ byte 83,byte 83,byte 53,byte 82,byte 82,byte 65,byte 65,byte 52,
+ byte 52,byte 81,byte 64,byte 64,byte 51,byte 80,byte 80,byte 79,
+ byte 66,byte 66,byte 66,byte 66,byte 65,byte 65,byte 65,byte 65,
+ byte 64,byte 64,byte 64,byte 64,byte 79,byte 79,byte 79,byte 79,
+ byte 50,byte 50,byte 49,byte 49,byte 49,byte 77,byte 77,byte 48,
+ byte 48,byte 48,byte 76,byte 76,byte 63,byte 63,byte 46,byte 12,
+ byte 50,byte 50,byte 49,byte 49,byte 49,byte 77,byte 77,byte 48,
+ byte 48,byte 48,byte 76,byte 76,byte 63,byte 63,byte 46,byte 12,
+ byte 62,byte 62,byte 61,byte 61,byte 61,byte 61,byte 77,byte 60,
+ byte 60,byte 60,byte 60,byte 59,byte 59,byte 59,byte 59,byte 12,
+ byte 62,byte 62,byte 61,byte 61,byte 61,byte 61,byte 60,byte 60,
+ byte 60,byte 60,byte 60,byte 59,byte 59,byte 59,byte 42,byte 12,
+ byte 62,byte 62,byte 61,byte 61,byte 61,byte 61,byte 73,byte 60,
+ byte 60,byte 60,byte 43,byte 59,byte 59,byte 59,byte 42,byte 8,
+ byte 74,byte 74,byte 61,byte 61,byte 61,byte 73,byte 73,byte 60,
+ byte 60,byte 60,byte 72,byte 72,byte 72,byte 59,byte 42,byte 8,
+ byte 74,byte 74,byte 74,byte 57,byte 73,byte 73,byte 73,byte 73,
+ byte 56,byte 56,byte 72,byte 72,byte 72,byte 72,byte 42,byte 8,
+ byte 58,byte 58,byte 57,byte 57,byte 57,byte 57,byte 73,byte 56,
+ byte 56,byte 56,byte 72,byte 55,byte 55,byte 55,byte 55,byte 8,
+ byte 58,byte 58,byte 57,byte 57,byte 57,byte 57,byte 56,byte 56,
+ byte 56,byte 56,byte 56,byte 55,byte 55,byte 55,byte 38,byte 4,
+ byte 58,byte 58,byte 57,byte 57,byte 57,byte 57,byte 56,byte 56,
+ byte 56,byte 56,byte 39,byte 55,byte 55,byte 55,byte 38,byte 4,
+ byte 70,byte 70,byte 57,byte 57,byte 40,byte 69,byte 69,byte 69,
+ byte 56,byte 39,byte 85,byte 68,byte 68,byte 38,byte 38,byte 4,
+ byte 70,byte 70,byte 53,byte 53,byte 53,byte 69,byte 69,byte 52,
+ byte 52,byte 52,byte 68,byte 68,byte 68,byte 51,byte 51,byte 4,
+ byte 54,byte 54,byte 53,byte 53,byte 53,byte 69,byte 69,byte 52,
+ byte 52,byte 52,byte 68,byte 68,byte 51,byte 51,byte 51,byte 0,
+ byte 54,byte 54,byte 53,byte 53,byte 53,byte 53,byte 69,byte 52,
+ byte 52,byte 52,byte 35,byte 51,byte 51,byte 51,byte 34,byte 0,
+ byte 37,byte 37,byte 53,byte 36,byte 36,byte 36,byte 36,byte 52,
+ byte 35,byte 35,byte 35,byte 51,byte 51,byte 34,byte 34,byte 0,
+ byte 3,byte 3,byte 3,byte 3,byte 2,byte 2,byte 2,byte 2,
+ byte 1,byte 1,byte 1,byte 1,byte 0,byte 0,byte 0,byte 0,
+ byte 33,byte 33,byte 49,byte 49,byte 32,byte 32,byte 77,byte 48,
+ byte 48,byte 47,byte 47,byte 63,byte 63,byte 46,byte 46,byte 12,
+ byte 33,byte 33,byte 49,byte 49,byte 32,byte 32,byte 77,byte 48,
+ byte 48,byte 47,byte 47,byte 63,byte 63,byte 46,byte 46,byte 12,
+ byte 62,byte 62,byte 61,byte 61,byte 61,byte 61,byte 60,byte 60,
+ byte 60,byte 43,byte 43,byte 59,byte 59,byte 59,byte 42,byte 12,
+ byte 62,byte 62,byte 61,byte 61,byte 61,byte 44,byte 44,byte 60,
+ byte 60,byte 43,byte 43,byte 59,byte 59,byte 42,byte 42,byte 12,
+ byte 45,byte 45,byte 61,byte 61,byte 44,byte 44,byte 44,byte 60,
+ byte 60,byte 43,byte 43,byte 59,byte 59,byte 42,byte 42,byte 8,
+ byte 45,byte 45,byte 61,byte 44,byte 44,byte 44,byte 73,byte 60,
+ byte 43,byte 43,byte 26,byte 72,byte 59,byte 42,byte 42,byte 8,
+ byte 74,byte 74,byte 57,byte 44,byte 44,byte 73,byte 73,byte 56,
+ byte 43,byte 43,byte 26,byte 72,byte 72,byte 42,byte 42,byte 8,
+ byte 58,byte 58,byte 57,byte 57,byte 57,byte 57,byte 56,byte 56,
+ byte 56,byte 56,byte 39,byte 55,byte 55,byte 55,byte 38,byte 8,
+ byte 58,byte 58,byte 57,byte 57,byte 57,byte 40,byte 40,byte 56,
+ byte 56,byte 39,byte 39,byte 55,byte 55,byte 38,byte 38,byte 4,
+ byte 41,byte 41,byte 40,byte 40,byte 40,byte 40,byte 40,byte 56,
+ byte 39,byte 39,byte 39,byte 55,byte 55,byte 38,byte 38,byte 4,
+ byte 41,byte 41,byte 40,byte 40,byte 40,byte 23,byte 23,byte 39,
+ byte 39,byte 39,byte 22,byte 68,byte 38,byte 38,byte 38,byte 4,
+ byte 54,byte 54,byte 53,byte 53,byte 53,byte 69,byte 69,byte 52,
+ byte 52,byte 52,byte 68,byte 68,byte 51,byte 51,byte 21,byte 4,
+ byte 54,byte 54,byte 53,byte 53,byte 53,byte 53,byte 69,byte 52,
+ byte 52,byte 52,byte 35,byte 51,byte 51,byte 51,byte 34,byte 0,
+ byte 37,byte 37,byte 53,byte 36,byte 36,byte 36,byte 36,byte 52,
+ byte 35,byte 35,byte 35,byte 51,byte 51,byte 34,byte 34,byte 0,
+ byte 37,byte 37,byte 36,byte 36,byte 36,byte 36,byte 36,byte 35,
+ byte 35,byte 35,byte 35,byte 18,byte 34,byte 34,byte 34,byte 0,
+ byte 3,byte 3,byte 3,byte 3,byte 2,byte 2,byte 2,byte 2,
+ byte 1,byte 1,byte 1,byte 1,byte 0,byte 0,byte 0,byte 0,
+ byte 16,byte 16,byte 32,byte 32,byte 31,byte 31,byte 31,byte 47,
+ byte 47,byte 30,byte 30,byte 30,byte 46,byte 46,byte 29,byte 12,
+ byte 16,byte 16,byte 32,byte 32,byte 31,byte 31,byte 31,byte 47,
+ byte 47,byte 30,byte 30,byte 30,byte 46,byte 46,byte 29,byte 12,
+ byte 45,byte 45,byte 44,byte 44,byte 44,byte 44,byte 31,byte 60,
+ byte 43,byte 43,byte 30,byte 59,byte 59,byte 42,byte 42,byte 12,
+ byte 45,byte 45,byte 44,byte 44,byte 44,byte 44,byte 27,byte 43,
+ byte 43,byte 43,byte 26,byte 26,byte 42,byte 42,byte 42,byte 12,
+ byte 28,byte 28,byte 44,byte 44,byte 27,byte 27,byte 27,byte 43,
+ byte 43,byte 26,byte 26,byte 26,byte 42,byte 42,byte 25,byte 8,
+ byte 28,byte 28,byte 44,byte 44,byte 27,byte 27,byte 27,byte 43,
+ byte 43,byte 26,byte 26,byte 9,byte 42,byte 42,byte 25,byte 8,
+ byte 28,byte 28,byte 28,byte 27,byte 27,byte 27,byte 10,byte 43,
+ byte 26,byte 26,byte 26,byte 9,byte 42,byte 42,byte 25,byte 8,
+ byte 41,byte 41,byte 57,byte 40,byte 40,byte 40,byte 40,byte 56,
+ byte 39,byte 39,byte 39,byte 55,byte 55,byte 38,byte 38,byte 8,
+ byte 41,byte 41,byte 40,byte 40,byte 40,byte 40,byte 23,byte 39,
+ byte 39,byte 39,byte 22,byte 55,byte 38,byte 38,byte 38,byte 4,
+ byte 24,byte 24,byte 40,byte 40,byte 23,byte 23,byte 23,byte 39,
+ byte 39,byte 22,byte 22,byte 22,byte 38,byte 38,byte 21,byte 4,
+ byte 24,byte 24,byte 24,byte 23,byte 23,byte 23,byte 23,byte 39,
+ byte 22,byte 22,byte 22,byte 5,byte 38,byte 38,byte 21,byte 4,
+ byte 24,byte 24,byte 53,byte 23,byte 23,byte 6,byte 6,byte 52,
+ byte 52,byte 22,byte 5,byte 5,byte 51,byte 21,byte 21,byte 4,
+ byte 37,byte 37,byte 53,byte 36,byte 36,byte 36,byte 36,byte 52,
+ byte 35,byte 35,byte 35,byte 51,byte 51,byte 34,byte 34,byte 0,
+ byte 37,byte 37,byte 36,byte 36,byte 36,byte 36,byte 36,byte 35,
+ byte 35,byte 35,byte 35,byte 18,byte 34,byte 34,byte 34,byte 0,
+ byte 20,byte 20,byte 36,byte 36,byte 19,byte 19,byte 19,byte 35,
+ byte 35,byte 18,byte 18,byte 18,byte 34,byte 34,byte 17,byte 0,
+ byte 3,byte 3,byte 3,byte 3,byte 2,byte 2,byte 2,byte 2,
+ byte 1,byte 1,byte 1,byte 1,byte 0,byte 0,byte 0,byte 0,
+ byte 15,byte 15,byte 15,byte 15,byte 14,byte 14,byte 14,byte 14,
+ byte 13,byte 13,byte 13,byte 13,byte 12,byte 12,byte 12,byte 12,
+ byte 15,byte 15,byte 15,byte 15,byte 14,byte 14,byte 14,byte 14,
+ byte 13,byte 13,byte 13,byte 13,byte 12,byte 12,byte 12,byte 12,
+ byte 15,byte 15,byte 15,byte 15,byte 14,byte 14,byte 14,byte 14,
+ byte 13,byte 13,byte 13,byte 13,byte 12,byte 12,byte 12,byte 12,
+ byte 15,byte 15,byte 15,byte 15,byte 14,byte 14,byte 14,byte 14,
+ byte 13,byte 13,byte 13,byte 13,byte 12,byte 12,byte 12,byte 12,
+ byte 11,byte 11,byte 11,byte 11,byte 10,byte 10,byte 10,byte 10,
+ byte 9,byte 9,byte 9,byte 9,byte 8,byte 8,byte 8,byte 8,
+ byte 11,byte 11,byte 11,byte 11,byte 10,byte 10,byte 10,byte 10,
+ byte 9,byte 9,byte 9,byte 9,byte 8,byte 8,byte 8,byte 8,
+ byte 11,byte 11,byte 11,byte 11,byte 10,byte 10,byte 10,byte 10,
+ byte 9,byte 9,byte 9,byte 9,byte 8,byte 8,byte 8,byte 8,
+ byte 11,byte 11,byte 11,byte 11,byte 10,byte 10,byte 10,byte 10,
+ byte 9,byte 9,byte 9,byte 9,byte 8,byte 8,byte 8,byte 8,
+ byte 7,byte 7,byte 7,byte 7,byte 6,byte 6,byte 6,byte 6,
+ byte 5,byte 5,byte 5,byte 5,byte 4,byte 4,byte 4,byte 4,
+ byte 7,byte 7,byte 7,byte 7,byte 6,byte 6,byte 6,byte 6,
+ byte 5,byte 5,byte 5,byte 5,byte 4,byte 4,byte 4,byte 4,
+ byte 7,byte 7,byte 7,byte 7,byte 6,byte 6,byte 6,byte 6,
+ byte 5,byte 5,byte 5,byte 5,byte 4,byte 4,byte 4,byte 4,
+ byte 7,byte 7,byte 7,byte 7,byte 6,byte 6,byte 6,byte 6,
+ byte 5,byte 5,byte 5,byte 5,byte 4,byte 4,byte 4,byte 4,
+ byte 3,byte 3,byte 3,byte 3,byte 2,byte 2,byte 2,byte 2,
+ byte 1,byte 1,byte 1,byte 1,byte 0,byte 0,byte 0,byte 0,
+ byte 3,byte 3,byte 3,byte 3,byte 2,byte 2,byte 2,byte 2,
+ byte 1,byte 1,byte 1,byte 1,byte 0,byte 0,byte 0,byte 0,
+ byte 3,byte 3,byte 3,byte 3,byte 2,byte 2,byte 2,byte 2,
+ byte 1,byte 1,byte 1,byte 1,byte 0,byte 0,byte 0,byte 0,
+ byte 3,byte 3,byte 3,byte 3,byte 2,byte 2,byte 2,byte 2,
+ byte 1,byte 1,byte 1,byte 1,byte 0,byte 0,byte 0,byte 0,
+};
diff --git a/appl/wm/mpeg/decode.b b/appl/wm/mpeg/decode.b
new file mode 100644
index 00000000..16906c99
--- /dev/null
+++ b/appl/wm/mpeg/decode.b
@@ -0,0 +1,831 @@
+implement Mpegd;
+
+include "sys.m";
+include "mpegio.m";
+
+sys: Sys;
+idct: IDCT;
+
+Mpegi, Picture, Slice, MacroBlock, YCbCr, Pair: import Mpegio;
+
+intra_tab := array[64] of {
+ 8, 16, 19, 22, 26, 27, 29, 34,
+ 16, 16, 22, 24, 27, 29, 34, 37,
+ 19, 22, 26, 27, 29, 34, 34, 38,
+ 22, 22, 26, 27, 29, 34, 37, 40,
+ 22, 26, 27, 29, 32, 35, 40, 48,
+ 26, 27, 29, 32, 35, 40, 48, 58,
+ 26, 27, 29, 34, 38, 46, 56, 69,
+ 27, 29, 35, 38, 46, 56, 69, 83,
+};
+
+nintra_tab := array[64] of { * => 16 };
+
+CLOFF: con 256;
+
+intraQ, nintraQ: array of int;
+rtmp: array of array of int;
+rflag := array[6] of int;
+rforw, dforw, rback, dback: int;
+rforw2, dforw2, rback2, dback2: int;
+ydb, ydf, cdb, cdf: int;
+vflags: int;
+past := array[3] of int;
+pinit := array[3] of { * => 128 * 8 };
+zeros := array[64] of { * => 0 };
+zeros1: array of int;
+clamp := array[CLOFF + 256 + CLOFF] of byte;
+width, height, w2, h2: int;
+mpi, mps, yadj, cadj, yskip: int;
+I, B0: ref YCbCr;
+Ps := array[2] of ref YCbCr;
+Rs := array[2] of ref YCbCr;
+P, B, R, M, N: ref YCbCr;
+pn: int = 0;
+rn: int = 0;
+
+zig := array[64] of {
+ 0, 1, 8, 16, 9, 2, 3, 10, 17,
+ 24, 32, 25, 18, 11, 4, 5,
+ 12, 19, 26, 33, 40, 48, 41, 34,
+ 27, 20, 13, 6, 7, 14, 21, 28,
+ 35, 42, 49, 56, 57, 50, 43, 36,
+ 29, 22, 15, 23, 30, 37, 44, 51,
+ 58, 59, 52, 45, 38, 31, 39, 46,
+ 53, 60, 61, 54, 47, 55, 62, 63,
+};
+
+init(m: ref Mpegi)
+{
+ sys = load Sys Sys->PATH;
+ idct = load IDCT IDCT->PATH;
+ if (idct == nil) {
+ sys->print("could not open %s: %r\n", IDCT->PATH);
+ exit;
+ }
+ idct->init();
+ width = m.width;
+ height = m.height;
+ w2 = width >> 1;
+ h2 = height >> 1;
+ mps = width >> 4;
+ mpi = mps * height >> 4;
+ yskip = 8 * width;
+ yadj = 16 * width - (width - 16);
+ cadj = 8 * w2 - (w2 - 8);
+ I = frame();
+ Ps[0] = frame();
+ Ps[1] = frame();
+ Rs[0] = Ps[0];
+ Rs[1] = Ps[1];
+ B0 = frame();
+ for (i := 0; i < CLOFF; i++)
+ clamp[i] = byte 0;
+ for (i = 0; i < 256; i++)
+ clamp[i + CLOFF] = byte i;
+ for (i = CLOFF + 256; i < CLOFF + 256 + CLOFF; i++)
+ clamp[i] = byte 255;
+ if (m.intra == nil)
+ intraQ = intra_tab;
+ else
+ intraQ = zigof(m.intra);
+ if (m.nintra == nil)
+ nintraQ = nintra_tab;
+ else
+ nintraQ = zigof(m.nintra);
+ rtmp = array[6] of array of int;
+ for (i = 0; i < 6; i++)
+ rtmp[i] = array[64] of int;
+ zeros1 = zeros[1:];
+}
+
+zarray(n: int, v: byte): array of byte
+{
+ return array[n] of { * => v };
+}
+
+frame(): ref YCbCr
+{
+ y := zarray(width * height, byte 0);
+ b := zarray(w2 * h2, byte 128);
+ r := zarray(w2 * h2, byte 128);
+ return ref YCbCr(y, b, r);
+}
+
+zigof(a: array of int): array of int
+{
+ z := array[64] of int;
+ for (i := 0; i < 64; i++)
+ z[zig[i]] = a[i];
+ return z;
+}
+
+invQ_intra(a: array of Pair, q: int, b: array of int)
+{
+ (nil, t) := a[0];
+ b[0] = t * 8;
+ b[1:] = zeros1;
+ n := 1;
+ i := 1;
+ while (n < len a) {
+ (r, l) := a[n++];
+ i += r;
+ x := zig[i++];
+ if (l > 0) {
+ v := l * q * intraQ[x] >> 3;
+ if (v > 2047)
+ b[x] = 2047;
+ else
+ b[x] = (v - 1) | 1;
+ } else {
+ v := (l * q * intraQ[x] + 7) >> 3;
+ if (v < -2048)
+ b[x] = -2048;
+ else
+ b[x] = v | 1;
+ }
+ #sys->print("%d %d %d %d\n", x, r, l, b[x]);
+ }
+}
+
+invQ_nintra(a: array of Pair, q: int, b: array of int)
+{
+ b[0:] = zeros;
+ i := 0;
+ for (n := 0; n < len a; n++) {
+ (r, l) := a[n];
+ i += r;
+ if (l == 0) {
+ raisex("zero level");
+ i++;
+ continue;
+ }
+ x := zig[i++];
+ if (l > 0) {
+ v := ((l << 1) + 1) * q * nintraQ[x] >> 4;
+ if (v > 2047)
+ b[x] = 2047;
+ else
+ b[x] = (v - 1) | 1;
+ } else {
+ v := (((l << 1) - 1) * q * nintraQ[x] + 15) >> 4;
+ if (v < -2048)
+ b[x] = -2048;
+ else
+ b[x] = v | 1;
+ }
+ #sys->print("%d %d %d %d\n", x, r, l, b[x]);
+ }
+}
+
+yzero(v: array of byte, base: int)
+{
+ x := 0;
+ i := 8;
+ do {
+ n := base;
+ j := 8;
+ do
+ v[n++] = byte 0;
+ while (--j > 0);
+ base += width;
+ } while (--i > 0);
+}
+
+czero(v: array of byte, base: int)
+{
+ x := 0;
+ i := 8;
+ do {
+ n := base;
+ j := 8;
+ do
+ v[n++] = byte 128;
+ while (--j > 0);
+ base += w2;
+ } while (--i > 0);
+
+}
+
+blockzero(d: ref YCbCr)
+{
+ yzero(d.Y, ybase);
+ yzero(d.Y, ybase + 8);
+ yzero(d.Y, ybase + yskip);
+ yzero(d.Y, ybase + 8 + yskip);
+ czero(d.Cb, cbase);
+ czero(d.Cr, cbase);
+}
+
+ydistr(a: array of int, v: array of byte, base: int)
+{
+ x := 0;
+ i := 8;
+ do {
+ n := base;
+ j := 8;
+ do
+ v[n++] = clamp[a[x++] + CLOFF];
+ while (--j > 0);
+ base += width;
+ } while (--i > 0);
+}
+
+cdistr(a: array of int, v: array of byte, base: int)
+{
+ x := 0;
+ i := 8;
+ do {
+ n := base;
+ j := 8;
+ do
+ v[n++] = clamp[a[x++] + CLOFF];
+ while (--j > 0);
+ base += w2;
+ } while (--i > 0);
+
+}
+
+invQ_intra_block(b: array of array of Pair, q: int, pred: int, d: ref YCbCr)
+{
+ a, dc: array of int;
+ if (pred)
+ dc = past;
+ else
+ dc = pinit;
+ p := dc[0];
+ for (i := 0; i < 4; i++) {
+ a = rtmp[i];
+ #sys->print("%d\n", i);
+ invQ_intra(b[i], q, a);
+ p += a[0];
+ a[0] = p;
+ #sys->print("%d\n", a[0]);
+ idct->idct(a);
+ }
+ past[0] = p;
+ for (i = 4; i < 6; i++) {
+ p = dc[i - 3];
+ a = rtmp[i];
+ #sys->print("%d\n", i);
+ invQ_intra(b[i], q, a);
+ p += a[0];
+ a[0] = p;
+ #sys->print("%d\n", a[0]);
+ past[i - 3] = p;
+ idct->idct(a);
+ }
+ ydistr(rtmp[0], d.Y, ybase);
+ ydistr(rtmp[1], d.Y, ybase + 8);
+ ydistr(rtmp[2], d.Y, ybase + yskip);
+ ydistr(rtmp[3], d.Y, ybase + 8 + yskip);
+ cdistr(rtmp[4], d.Cb, cbase);
+ cdistr(rtmp[5], d.Cr, cbase);
+}
+
+invQ_nintra_block(b: array of array of Pair, q: int)
+{
+ for (i := 0; i < 6; i++) {
+ p := b[i];
+ if (p != nil) {
+ a := rtmp[i];
+ #sys->print("%d\n", i);
+ invQ_nintra(p, q, a);
+ idct->idct(a);
+ rflag[i] = 1;
+ } else
+ rflag[i] = 0;
+ }
+}
+
+mbr, ybase, cbase: int;
+
+nextmb()
+{
+ if (--mbr == 0) {
+ ybase += yadj;
+ cbase += cadj;
+ mbr = mps;
+ } else {
+ ybase += 16;
+ cbase += 8;
+ }
+}
+
+copyblock(s, d: array of byte, b, n, w: int)
+{
+ i := 8;
+ do {
+ d[b:] = s[b:b+n];
+ b += w;
+ } while (--i > 0);
+}
+
+copyblockdisp(s, d: array of byte, b, n, w, p: int)
+{
+ i := 8;
+ p += b;
+ do {
+ d[b:] = s[p:p+n];
+ b += w;
+ p += w;
+ } while (--i > 0);
+}
+
+interpblock(s0, s1, d: array of byte, b, n, w, p0, p1: int)
+{
+ i := 8;
+ do {
+ dx := b;
+ s0x := b + p0;
+ s1x := b + p1;
+ j := n;
+ do
+ d[dx++] = byte ((int s0[s0x++] + int s1[s1x++] + 1) >> 1);
+ while (--j > 0);
+ b += w;
+ } while (--i > 0);
+}
+
+deltablock(s: array of byte, r: array of int, d: array of byte, b, w, o: int)
+{
+ rx := 0;
+ i := 8;
+ do {
+ dx := b;
+ sx := b + o;
+ j := 8;
+ do
+ d[dx++] = clamp[CLOFF + int s[sx++] + r[rx++]];
+ while (--j > 0);
+ b += w;
+ } while (--i > 0);
+}
+
+deltainterpblock(s0, s1: array of byte, r: array of int, d: array of byte, b, w, o0, o1: int)
+{
+ rx := 0;
+ i := 8;
+ do {
+ dx := b;
+ s0x := b + o0;
+ s1x := b + o1;
+ j := 8;
+ do
+ d[dx++] = clamp[CLOFF + ((int s0[s0x++] + int s1[s1x++] + 1) >> 1) + r[rx++]];
+ while (--j > 0);
+ b += w;
+ } while (--i > 0);
+}
+
+dispblock(s, d: array of byte, n, b, w, o: int)
+{
+ if (rflag[n])
+ deltablock(s, rtmp[n], d, b, w, o);
+ else
+ copyblockdisp(s, d, b, 8, w, o);
+}
+
+genblock(s0, s1, d: array of byte, n, b, w, o0, o1: int)
+{
+ if (rflag[n])
+ deltainterpblock(s0, s1, rtmp[n], d, b, w, o0, o1);
+ else
+ interpblock(s0, s1, d, b, 8, w, o0, o1);
+}
+
+copymb()
+{
+ copyblock(R.Y, P.Y, ybase, 16, width);
+ copyblock(R.Y, P.Y, ybase + yskip, 16, width);
+ copyblock(R.Cb, P.Cb, cbase, 8, w2);
+ copyblock(R.Cr, P.Cr, cbase, 8, w2);
+}
+
+deltamb()
+{
+ dispblock(R.Y, P.Y, 0, ybase, width, 0);
+ dispblock(R.Y, P.Y, 1, ybase + 8, width, 0);
+ dispblock(R.Y, P.Y, 2, ybase + yskip, width, 0);
+ dispblock(R.Y, P.Y, 3, ybase + 8 + yskip, width, 0);
+ dispblock(R.Cb, P.Cb, 4, cbase, w2, 0);
+ dispblock(R.Cr, P.Cr, 5, cbase, w2, 0);
+}
+
+copymbforw()
+{
+ copyblockdisp(N.Y, B.Y, ybase, 16, width, ydf);
+ copyblockdisp(N.Y, B.Y, ybase + yskip, 16, width, ydf);
+ copyblockdisp(N.Cb, B.Cb, cbase, 8, w2, cdf);
+ copyblockdisp(N.Cr, B.Cr, cbase, 8, w2, cdf);
+}
+
+copymbback()
+{
+ copyblockdisp(M.Y, B.Y, ybase, 16, width, ydb);
+ copyblockdisp(M.Y, B.Y, ybase + yskip, 16, width, ydb);
+ copyblockdisp(M.Cb, B.Cb, cbase, 8, w2, cdb);
+ copyblockdisp(M.Cr, B.Cr, cbase, 8, w2, cdb);
+}
+
+copymbbackforw()
+{
+ interpblock(M.Y, N.Y, B.Y, ybase, 16, width, ydb, ydf);
+ interpblock(M.Y, N.Y, B.Y, ybase + yskip, 16, width, ydb, ydf);
+ interpblock(M.Cb, N.Cb, B.Cb, cbase, 8, w2, cdb, cdf);
+ interpblock(M.Cr, N.Cr, B.Cr, cbase, 8, w2, cdb, cdf);
+}
+
+deltambforw()
+{
+ dispblock(N.Y, B.Y, 0, ybase, width, ydf);
+ dispblock(N.Y, B.Y, 1, ybase + 8, width, ydf);
+ dispblock(N.Y, B.Y, 2, ybase + yskip, width, ydf);
+ dispblock(N.Y, B.Y, 3, ybase + 8 + yskip, width, ydf);
+ dispblock(N.Cb, B.Cb, 4, cbase, w2, cdf);
+ dispblock(N.Cr, B.Cr, 5, cbase, w2, cdf);
+}
+
+deltambback()
+{
+ dispblock(M.Y, B.Y, 0, ybase, width, ydb);
+ dispblock(M.Y, B.Y, 1, ybase + 8, width, ydb);
+ dispblock(M.Y, B.Y, 2, ybase + yskip, width, ydb);
+ dispblock(M.Y, B.Y, 3, ybase + 8 + yskip, width, ydb);
+ dispblock(M.Cb, B.Cb, 4, cbase, w2, cdb);
+ dispblock(M.Cr, B.Cr, 5, cbase, w2, cdb);
+}
+
+deltambbackforw()
+{
+ genblock(M.Y, N.Y, B.Y, 0, ybase, width, ydb, ydf);
+ genblock(M.Y, N.Y, B.Y, 1, ybase + 8, width, ydb, ydf);
+ genblock(M.Y, N.Y, B.Y, 2, ybase + yskip, width, ydb, ydf);
+ genblock(M.Y, N.Y, B.Y, 3, ybase + 8 + yskip, width, ydb, ydf);
+ genblock(M.Cb, N.Cb, B.Cb, 4, cbase, w2, cdb, cdf);
+ genblock(M.Cr, N.Cr, B.Cr, 5, cbase, w2, cdb, cdf);
+}
+
+deltambinterp()
+{
+ case vflags & (Mpegio->MB_MF | Mpegio->MB_MB) {
+ Mpegio->MB_MF =>
+ deltambforw();
+ Mpegio->MB_MB =>
+ deltambback();
+ Mpegio->MB_MF | Mpegio->MB_MB =>
+ deltambbackforw();
+ * =>
+ raisex("bad vflags");
+ }
+}
+
+interpmb()
+{
+ case vflags & (Mpegio->MB_MF | Mpegio->MB_MB) {
+ Mpegio->MB_MF =>
+ copymbforw();
+ Mpegio->MB_MB =>
+ copymbback();
+ Mpegio->MB_MF | Mpegio->MB_MB =>
+ copymbbackforw();
+ * =>
+ raisex("bad vflags");
+ }
+}
+
+Idecode(p: ref Picture): ref YCbCr
+{
+ sa := p.slices;
+ n := 0;
+ mbr = mps;
+ ybase = 0;
+ cbase = 0;
+ for (i := 0; i < len sa; i++) {
+ pred := 0;
+ ba := sa[i].blocks;
+ for (j := 0; j < len ba; j++) {
+ invQ_intra_block(ba[j].rls, ba[j].qscale, pred, I);
+ nextmb();
+ n++;
+ pred = 1;
+ }
+ }
+ if (n != mpi)
+ raisex("I mb count");
+ R = I;
+ Rs[rn] = I;
+ rn ^= 1;
+ return I;
+}
+
+Pdecode(p: ref Picture): ref YCbCr
+{
+ rforwp, dforwp: int;
+ md, c: int;
+ P = Ps[pn];
+ N = R;
+ B = P;
+ pn ^= 1;
+ fs := 1 << p.forwfc;
+ fsr := fs << 5;
+ fsmin := -(fs << 4);
+ fsmax := (fs << 4) - 1;
+ sa := p.slices;
+ n := 0;
+ mbr = mps;
+ ybase = 0;
+ cbase = 0;
+ for (i := 0; i < len sa; i++) {
+ pred := 0;
+ ipred := 0;
+ ba := sa[i].blocks;
+ for (j := 0; j < len ba; j++) {
+ mb := ba[j];
+ while (n < mb.addr) {
+ copymb();
+ ipred = 0;
+ pred = 0;
+ nextmb();
+ n++;
+ }
+ if (mb.flags & Mpegio->MB_I) {
+ invQ_intra_block(mb.rls, mb.qscale, ipred, P);
+ #blockzero(P);
+ ipred = 1;
+ pred = 0;
+ } else {
+ if (mb.flags & Mpegio->MB_MF) {
+ if (fs == 1 || mb.mhfc == 0)
+ md = mb.mhfc;
+ else if ((c = mb.mhfc) < 0)
+ md = (c + 1) * fs - mb.mhfr - 1;
+ else
+ md = (c - 1) * fs + mb.mhfr + 1;
+ if (pred)
+ md += rforwp;
+ if (md > fsmax)
+ rforw = md - fsr;
+ else if (md < fsmin)
+ rforw = md + fsr;
+ else
+ rforw = md;
+ rforwp = rforw;
+ if (fs == 1 || mb.mvfc == 0)
+ md = mb.mvfc;
+ else if ((c = mb.mvfc) < 0)
+ md = (c + 1) * fs - mb.mvfr - 1;
+ else
+ md = (c - 1) * fs + mb.mvfr + 1;
+ if (pred)
+ md += dforwp;
+ if (md > fsmax)
+ dforw = md - fsr;
+ else if (md < fsmin)
+ dforw = md + fsr;
+ else
+ dforw = md;
+ dforwp = dforw;
+ if (p.flags & Mpegio->FPFV) {
+ rforw2 = rforw;
+ dforw2 = dforw;
+ rforw <<= 1;
+ dforw <<= 1;
+ ydf = rforw2 + dforw2 * width;
+ cdf = (rforw2 >> 1) + (dforw2 >> 1) * w2;
+ } else {
+ if (rforw < 0)
+ rforw2 = (rforw + 1) >> 1;
+ else
+ rforw2 = rforw >> 1;
+ if (dforw < 0)
+ dforw2 = (dforw + 1) >> 1;
+ else
+ dforw2 = dforw >> 1;
+ ydf = (rforw >> 1) + (dforw >> 1) * width;
+ cdf = (rforw2 >> 1) + (dforw2 >> 1) * w2;
+ }
+ pred = 1;
+ if (mb.rls != nil) {
+ invQ_nintra_block(mb.rls, mb.qscale);
+ deltambforw();
+ } else
+ copymbforw();
+ } else {
+ if (mb.rls == nil)
+ raisex("empty delta");
+ invQ_nintra_block(mb.rls, mb.qscale);
+ deltamb();
+ pred = 0;
+ }
+ ipred = 0;
+ }
+ nextmb();
+ n++;
+ }
+ }
+ while (n < mpi) {
+ copymb();
+ nextmb();
+ n++;
+ }
+ R = P;
+ Rs[rn] = P;
+ rn ^= 1;
+ return P;
+}
+
+Bdecode(p: ref Mpegio->Picture): ref Mpegio->YCbCr
+{
+ return Bdecode2(p, Rs[rn ^ 1], Rs[rn]);
+}
+
+Bdecode2(p: ref Mpegio->Picture, f0, f1: ref Mpegio->YCbCr): ref Mpegio->YCbCr
+{
+ rforwp, dforwp, rbackp, dbackp: int;
+ md, c: int;
+ M = f0;
+ N = f1;
+ B = B0;
+ fs := 1 << p.forwfc;
+ fsr := fs << 5;
+ fsmin := -(fs << 4);
+ fsmax := (fs << 4) - 1;
+ bs := 1 << p.backfc;
+ bsr := bs << 5;
+ bsmin := -(bs << 4);
+ bsmax := (bs << 4) - 1;
+ sa := p.slices;
+ n := 0;
+ mbr = mps;
+ ybase = 0;
+ cbase = 0;
+ for (i := 0; i < len sa; i++) {
+ ipred := 0;
+ rback = 0;
+ rforw = 0;
+ dback = 0;
+ dforw = 0;
+ rbackp = 0;
+ rforwp = 0;
+ dbackp = 0;
+ dforwp = 0;
+ rback2 = 0;
+ rforw2 = 0;
+ dback2 = 0;
+ dforw2 = 0;
+ ydb = 0;
+ ydf = 0;
+ cdb = 0;
+ cdf = 0;
+ ba := sa[i].blocks;
+ for (j := 0; j < len ba; j++) {
+ mb := ba[j];
+ while (n < mb.addr) {
+ interpmb();
+ nextmb();
+ ipred = 0;
+ n++;
+ }
+ if (mb.flags & Mpegio->MB_I) {
+ invQ_intra_block(mb.rls, mb.qscale, ipred, B);
+ ipred = 1;
+ rback = 0;
+ rforw = 0;
+ dback = 0;
+ dforw = 0;
+ rbackp = 0;
+ rforwp = 0;
+ dbackp = 0;
+ dforwp = 0;
+ rback2 = 0;
+ rforw2 = 0;
+ dback2 = 0;
+ dforw2 = 0;
+ ydb = 0;
+ ydf = 0;
+ cdb = 0;
+ cdf = 0;
+ } else {
+ if (mb.flags & Mpegio->MB_MF) {
+ if (fs == 1 || mb.mhfc == 0)
+ md = mb.mhfc;
+ else if ((c = mb.mhfc) < 0)
+ md = (c + 1) * fs - mb.mhfr - 1;
+ else
+ md = (c - 1) * fs + mb.mhfr + 1;
+ md += rforwp;
+ if (md > fsmax)
+ rforw = md - fsr;
+ else if (md < fsmin)
+ rforw = md + fsr;
+ else
+ rforw = md;
+ rforwp = rforw;
+ if (fs == 1 || mb.mvfc == 0)
+ md = mb.mvfc;
+ else if ((c = mb.mvfc) < 0)
+ md = (c + 1) * fs - mb.mvfr - 1;
+ else
+ md = (c - 1) * fs + mb.mvfr + 1;
+ md += dforwp;
+ if (md > fsmax)
+ dforw = md - fsr;
+ else if (md < fsmin)
+ dforw = md + fsr;
+ else
+ dforw = md;
+ dforwp = dforw;
+ if (p.flags & Mpegio->FPFV) {
+ rforw2 = rforw;
+ dforw2 = dforw;
+ rforw <<= 1;
+ dforw <<= 1;
+ ydf = rforw2 + dforw2 * width;
+ cdf = (rforw2 >> 1) + (dforw2 >> 1) * w2;
+ } else {
+ if (rforw < 0)
+ rforw2 = (rforw + 1) >> 1;
+ else
+ rforw2 = rforw >> 1;
+ if (dforw < 0)
+ dforw2 = (dforw + 1) >> 1;
+ else
+ dforw2 = dforw >> 1;
+ ydf = (rforw >> 1) + (dforw >> 1) * width;
+ cdf = (rforw2 >> 1) + (dforw2 >> 1) * w2;
+ }
+ }
+ if (mb.flags & Mpegio->MB_MB) {
+ if (bs == 1 || mb.mhbc == 0)
+ md = mb.mhbc;
+ else if ((c = mb.mhbc) < 0)
+ md = (c + 1) * bs - mb.mhbr - 1;
+ else
+ md = (c - 1) * bs + mb.mhbr + 1;
+ md += rbackp;
+ if (md > bsmax)
+ rback = md - bsr;
+ else if (md < bsmin)
+ rback = md + bsr;
+ else
+ rback = md;
+ rbackp = rback;
+ if (bs == 1 || mb.mvbc == 0)
+ md = mb.mvbc;
+ else if ((c = mb.mvbc) < 0)
+ md = (c + 1) * bs - mb.mvbr - 1;
+ else
+ md = (c - 1) * bs + mb.mvbr + 1;
+ md += dbackp;
+ if (md > bsmax)
+ dback = md - bsr;
+ else if (md < bsmin)
+ dback = md + bsr;
+ else
+ dback = md;
+ dbackp = dback;
+ if (p.flags & Mpegio->FPBV) {
+ rback2 = rback;
+ dback2 = dback;
+ rback <<= 1;
+ dback <<= 1;
+ ydb = rback2 + dback2 * width;
+ cdb = (rback2 >> 1) + (dback2 >> 1) * w2;
+ } else {
+ if (rback < 0)
+ rback2 = (rback + 1) >> 1;
+ else
+ rback2 = rback >> 1;
+ if (dback < 0)
+ dback2 = (dback + 1) >> 1;
+ else
+ dback2 = dback >> 1;
+ ydb = (rback >> 1) + (dback >> 1) * width;
+ cdb = (rback2 >> 1) + (dback2 >> 1) * w2;
+ }
+ }
+ vflags = mb.flags;
+ if (mb.rls != nil) {
+ invQ_nintra_block(mb.rls, mb.qscale);
+ deltambinterp();
+ } else
+ interpmb();
+ ipred = 0;
+ }
+ nextmb();
+ n++;
+ }
+ }
+ while (n < mpi) {
+ interpmb();
+ nextmb();
+ n++;
+ }
+ return B;
+}
+
+raisex(nil: string)
+{
+ raise "decode error";
+}
diff --git a/appl/wm/mpeg/decode4.b b/appl/wm/mpeg/decode4.b
new file mode 100644
index 00000000..c4a968e8
--- /dev/null
+++ b/appl/wm/mpeg/decode4.b
@@ -0,0 +1,709 @@
+implement Mpegd;
+
+include "sys.m";
+include "mpegio.m";
+
+sys: Sys;
+idct: IDCT;
+
+Mpegi, Picture, Slice, MacroBlock, YCbCr, Pair: import Mpegio;
+
+intra_tab := array[64] of {
+ 8, 16, 19, 22, 26, 27, 29, 34,
+ 16, 16, 22, 24, 27, 29, 34, 37,
+ 19, 22, 26, 27, 29, 34, 34, 38,
+ 22, 22, 26, 27, 29, 34, 37, 40,
+ 22, 26, 27, 29, 32, 35, 40, 48,
+ 26, 27, 29, 32, 35, 40, 48, 58,
+ 26, 27, 29, 34, 38, 46, 56, 69,
+ 27, 29, 35, 38, 46, 56, 69, 83,
+};
+
+nintra_tab := array[64] of { * => 16 };
+
+CLOFF: con 256;
+
+intraQ, nintraQ: array of int;
+rtmp: array of array of int;
+rflag := array[6] of int;
+rforw, dforw, rback, dback: int;
+ydb, ydf: int;
+vflags: int;
+past := array[3] of int;
+pinit := array[3] of { * => 128 * 8 };
+zeros := array[64] of { * => 0 };
+zeros1: array of int;
+clamp := array[CLOFF + 256 + CLOFF] of byte;
+width, height, w2, h2: int;
+mpi, mps, yadj, yskip: int;
+I, B0: ref YCbCr;
+Ps := array[2] of ref YCbCr;
+Rs := array[2] of ref YCbCr;
+P, B, R, M, N: ref YCbCr;
+pn: int = 0;
+rn: int = 0;
+
+zig := array[64] of {
+ 0, 1, 8, 16, 9, 2, 3, 10, 17,
+ 24, 32, 25, 18, 11, 4, 5,
+ 12, 19, 26, 33, 40, 48, 41, 34,
+ 27, 20, 13, 6, 7, 14, 21, 28,
+ 35, 42, 49, 56, 57, 50, 43, 36,
+ 29, 22, 15, 23, 30, 37, 44, 51,
+ 58, 59, 52, 45, 38, 31, 39, 46,
+ 53, 60, 61, 54, 47, 55, 62, 63,
+};
+
+init(m: ref Mpegi)
+{
+ sys = load Sys Sys->PATH;
+ idct = load IDCT IDCT->SPATH;
+ if (idct == nil) {
+ sys->print("could not open %s: %r\n", IDCT->PATH);
+ exit;
+ }
+ idct->init();
+ width = m.width;
+ height = m.height;
+ w2 = width >> 1;
+ h2 = height >> 1;
+ mps = width >> 4;
+ mpi = mps * height >> 4;
+ yskip = 8 * width;
+ yadj = 16 * width - (width - 16);
+ I = frame();
+ Ps[0] = frame();
+ Ps[1] = frame();
+ Rs[0] = Ps[0];
+ Rs[1] = Ps[1];
+ B0 = frame();
+ for (i := 0; i < CLOFF; i++)
+ clamp[i] = byte 0;
+ for (i = 0; i < 256; i++)
+ clamp[i + CLOFF] = byte i;
+ for (i = CLOFF + 256; i < CLOFF + 256 + CLOFF; i++)
+ clamp[i] = byte 255;
+ if (m.intra == nil)
+ intraQ = intra_tab;
+ else
+ intraQ = zigof(m.intra);
+ if (m.nintra == nil)
+ nintraQ = nintra_tab;
+ else
+ nintraQ = zigof(m.nintra);
+ rtmp = array[6] of array of int;
+ for (i = 0; i < 6; i++)
+ rtmp[i] = array[64] of int;
+ zeros1 = zeros[1:];
+}
+
+zarray(n: int, v: byte): array of byte
+{
+ return array[n] of { * => v };
+}
+
+frame(): ref YCbCr
+{
+ y := zarray(width * height, byte 0);
+ return ref YCbCr(y, nil, nil);
+}
+
+zigof(a: array of int): array of int
+{
+ z := array[64] of int;
+ for (i := 0; i < 64; i++)
+ z[zig[i]] = a[i];
+ return z;
+}
+
+invQ_intra(a: array of Pair, q: int, b: array of int)
+{
+ (nil, t) := a[0];
+ b[0] = t * 8;
+ b[1:] = zeros1;
+ n := 1;
+ i := 1;
+ while (n < len a) {
+ (r, l) := a[n++];
+ i += r;
+ x := zig[i++];
+ if (l > 0) {
+ v := l * q * intraQ[x] >> 3;
+ if (v > 2047)
+ b[x] = 2047;
+ else
+ b[x] = (v - 1) | 1;
+ } else {
+ v := (l * q * intraQ[x] + 7) >> 3;
+ if (v < -2048)
+ b[x] = -2048;
+ else
+ b[x] = v | 1;
+ }
+ #sys->print("%d %d %d %d\n", x, r, l, b[x]);
+ }
+}
+
+invQ_nintra(a: array of Pair, q: int, b: array of int)
+{
+ b[0:] = zeros;
+ i := 0;
+ for (n := 0; n < len a; n++) {
+ (r, l) := a[n];
+ i += r;
+ if (l == 0) {
+ raisex("zero level");
+ i++;
+ continue;
+ }
+ x := zig[i++];
+ if (l > 0) {
+ v := ((l << 1) + 1) * q * nintraQ[x] >> 4;
+ if (v > 2047)
+ b[x] = 2047;
+ else
+ b[x] = (v - 1) | 1;
+ } else {
+ v := (((l << 1) - 1) * q * nintraQ[x] + 15) >> 4;
+ if (v < -2048)
+ b[x] = -2048;
+ else
+ b[x] = v | 1;
+ }
+ #sys->print("%d %d %d %d\n", x, r, l, b[x]);
+ }
+}
+
+yzero(v: array of byte, base: int)
+{
+ x := 0;
+ i := 8;
+ do {
+ n := base;
+ j := 8;
+ do
+ v[n++] = byte 0;
+ while (--j > 0);
+ base += width;
+ } while (--i > 0);
+}
+
+blockzero(d: ref YCbCr)
+{
+ yzero(d.Y, ybase);
+ yzero(d.Y, ybase + 8);
+ yzero(d.Y, ybase + yskip);
+ yzero(d.Y, ybase + 8 + yskip);
+}
+
+ydistr(a: array of int, v: array of byte, base: int)
+{
+ x := 0;
+ i := 8;
+ do {
+ n := base;
+ j := 8;
+ do
+ v[n++] = clamp[a[x++] + CLOFF];
+ while (--j > 0);
+ base += width;
+ } while (--i > 0);
+}
+
+invQ_intra_block(b: array of array of Pair, q: int, pred: int, d: ref YCbCr)
+{
+ a, dc: array of int;
+ if (pred)
+ dc = past;
+ else
+ dc = pinit;
+ p := dc[0];
+ for (i := 0; i < 4; i++) {
+ a = rtmp[i];
+ #sys->print("%d\n", i);
+ invQ_intra(b[i], q, a);
+ p += a[0];
+ a[0] = p;
+ #sys->print("%d\n", a[0]);
+ idct->idct(a);
+ }
+ past[0] = p;
+ ydistr(rtmp[0], d.Y, ybase);
+ ydistr(rtmp[1], d.Y, ybase + 8);
+ ydistr(rtmp[2], d.Y, ybase + yskip);
+ ydistr(rtmp[3], d.Y, ybase + 8 + yskip);
+}
+
+invQ_nintra_block(b: array of array of Pair, q: int)
+{
+ for (i := 0; i < 4; i++) {
+ p := b[i];
+ if (p != nil) {
+ a := rtmp[i];
+ #sys->print("%d\n", i);
+ invQ_nintra(p, q, a);
+ idct->idct(a);
+ rflag[i] = 1;
+ } else
+ rflag[i] = 0;
+ }
+}
+
+mbr, ybase: int;
+
+nextmb()
+{
+ if (--mbr == 0) {
+ ybase += yadj;
+ mbr = mps;
+ } else
+ ybase += 16;
+}
+
+copyblock(s, d: array of byte, b, n, w: int)
+{
+ i := 8;
+ do {
+ d[b:] = s[b:b+n];
+ b += w;
+ } while (--i > 0);
+}
+
+copyblockdisp(s, d: array of byte, b, n, w, p: int)
+{
+ i := 8;
+ p += b;
+ do {
+ d[b:] = s[p:p+n];
+ b += w;
+ p += w;
+ } while (--i > 0);
+}
+
+interpblock(s0, s1, d: array of byte, b, n, w, p0, p1: int)
+{
+ i := 8;
+ do {
+ dx := b;
+ s0x := b + p0;
+ s1x := b + p1;
+ j := n;
+ do
+ d[dx++] = byte ((int s0[s0x++] + int s1[s1x++] + 1) >> 1);
+ while (--j > 0);
+ b += w;
+ } while (--i > 0);
+}
+
+deltablock(s: array of byte, r: array of int, d: array of byte, b, w, o: int)
+{
+ rx := 0;
+ i := 8;
+ do {
+ dx := b;
+ sx := b + o;
+ j := 8;
+ do
+ d[dx++] = clamp[CLOFF + int s[sx++] + r[rx++]];
+ while (--j > 0);
+ b += w;
+ } while (--i > 0);
+}
+
+deltainterpblock(s0, s1: array of byte, r: array of int, d: array of byte, b, w, o0, o1: int)
+{
+ rx := 0;
+ i := 8;
+ do {
+ dx := b;
+ s0x := b + o0;
+ s1x := b + o1;
+ j := 8;
+ do
+ d[dx++] = clamp[CLOFF + ((int s0[s0x++] + int s1[s1x++] + 1) >> 1) + r[rx++]];
+ while (--j > 0);
+ b += w;
+ } while (--i > 0);
+}
+
+dispblock(s, d: array of byte, n, b, w, o: int)
+{
+ if (rflag[n])
+ deltablock(s, rtmp[n], d, b, w, o);
+ else
+ copyblockdisp(s, d, b, 8, w, o);
+}
+
+genblock(s0, s1, d: array of byte, n, b, w, o0, o1: int)
+{
+ if (rflag[n])
+ deltainterpblock(s0, s1, rtmp[n], d, b, w, o0, o1);
+ else
+ interpblock(s0, s1, d, b, 8, w, o0, o1);
+}
+
+copymb()
+{
+ copyblock(R.Y, P.Y, ybase, 16, width);
+ copyblock(R.Y, P.Y, ybase + yskip, 16, width);
+}
+
+deltamb()
+{
+ dispblock(R.Y, P.Y, 0, ybase, width, 0);
+ dispblock(R.Y, P.Y, 1, ybase + 8, width, 0);
+ dispblock(R.Y, P.Y, 2, ybase + yskip, width, 0);
+ dispblock(R.Y, P.Y, 3, ybase + 8 + yskip, width, 0);
+}
+
+copymbforw()
+{
+ copyblockdisp(N.Y, B.Y, ybase, 16, width, ydf);
+ copyblockdisp(N.Y, B.Y, ybase + yskip, 16, width, ydf);
+}
+
+copymbback()
+{
+ copyblockdisp(M.Y, B.Y, ybase, 16, width, ydb);
+ copyblockdisp(M.Y, B.Y, ybase + yskip, 16, width, ydb);
+}
+
+copymbbackforw()
+{
+ interpblock(M.Y, N.Y, B.Y, ybase, 16, width, ydb, ydf);
+ interpblock(M.Y, N.Y, B.Y, ybase + yskip, 16, width, ydb, ydf);
+}
+
+deltambforw()
+{
+ dispblock(N.Y, B.Y, 0, ybase, width, ydf);
+ dispblock(N.Y, B.Y, 1, ybase + 8, width, ydf);
+ dispblock(N.Y, B.Y, 2, ybase + yskip, width, ydf);
+ dispblock(N.Y, B.Y, 3, ybase + 8 + yskip, width, ydf);
+}
+
+deltambback()
+{
+ dispblock(M.Y, B.Y, 0, ybase, width, ydb);
+ dispblock(M.Y, B.Y, 1, ybase + 8, width, ydb);
+ dispblock(M.Y, B.Y, 2, ybase + yskip, width, ydb);
+ dispblock(M.Y, B.Y, 3, ybase + 8 + yskip, width, ydb);
+}
+
+deltambbackforw()
+{
+ genblock(M.Y, N.Y, B.Y, 0, ybase, width, ydb, ydf);
+ genblock(M.Y, N.Y, B.Y, 1, ybase + 8, width, ydb, ydf);
+ genblock(M.Y, N.Y, B.Y, 2, ybase + yskip, width, ydb, ydf);
+ genblock(M.Y, N.Y, B.Y, 3, ybase + 8 + yskip, width, ydb, ydf);
+}
+
+deltambinterp()
+{
+ case vflags & (Mpegio->MB_MF | Mpegio->MB_MB) {
+ Mpegio->MB_MF =>
+ deltambforw();
+ Mpegio->MB_MB =>
+ deltambback();
+ Mpegio->MB_MF | Mpegio->MB_MB =>
+ deltambbackforw();
+ * =>
+ raisex("bad vflags");
+ }
+}
+
+interpmb()
+{
+ case vflags & (Mpegio->MB_MF | Mpegio->MB_MB) {
+ Mpegio->MB_MF =>
+ copymbforw();
+ Mpegio->MB_MB =>
+ copymbback();
+ Mpegio->MB_MF | Mpegio->MB_MB =>
+ copymbbackforw();
+ * =>
+ raisex("bad vflags");
+ }
+}
+
+Idecode(p: ref Picture): ref YCbCr
+{
+ sa := p.slices;
+ n := 0;
+ mbr = mps;
+ ybase = 0;
+ for (i := 0; i < len sa; i++) {
+ pred := 0;
+ ba := sa[i].blocks;
+ for (j := 0; j < len ba; j++) {
+ invQ_intra_block(ba[j].rls, ba[j].qscale, pred, I);
+ nextmb();
+ n++;
+ pred = 1;
+ }
+ }
+ if (n != mpi)
+ raisex("I mb count");
+ R = I;
+ Rs[rn] = I;
+ rn ^= 1;
+ return I;
+}
+
+Pdecode(p: ref Picture): ref YCbCr
+{
+ rforwp, dforwp: int;
+ md, c: int;
+ P = Ps[pn];
+ N = R;
+ B = P;
+ pn ^= 1;
+ fs := 1 << p.forwfc;
+ fsr := fs << 5;
+ fsmin := -(fs << 4);
+ fsmax := (fs << 4) - 1;
+ sa := p.slices;
+ n := 0;
+ mbr = mps;
+ ybase = 0;
+ for (i := 0; i < len sa; i++) {
+ pred := 0;
+ ipred := 0;
+ ba := sa[i].blocks;
+ for (j := 0; j < len ba; j++) {
+ mb := ba[j];
+ while (n < mb.addr) {
+ copymb();
+ ipred = 0;
+ pred = 0;
+ nextmb();
+ n++;
+ }
+ if (mb.flags & Mpegio->MB_I) {
+ invQ_intra_block(mb.rls, mb.qscale, ipred, P);
+ #blockzero(P);
+ ipred = 1;
+ pred = 0;
+ } else {
+ if (mb.flags & Mpegio->MB_MF) {
+ if (fs == 1 || mb.mhfc == 0)
+ md = mb.mhfc;
+ else if ((c = mb.mhfc) < 0)
+ md = (c + 1) * fs - mb.mhfr - 1;
+ else
+ md = (c - 1) * fs + mb.mhfr + 1;
+ if (pred)
+ md += rforwp;
+ if (md > fsmax)
+ rforw = md - fsr;
+ else if (md < fsmin)
+ rforw = md + fsr;
+ else
+ rforw = md;
+ rforwp = rforw;
+ if (fs == 1 || mb.mvfc == 0)
+ md = mb.mvfc;
+ else if ((c = mb.mvfc) < 0)
+ md = (c + 1) * fs - mb.mvfr - 1;
+ else
+ md = (c - 1) * fs + mb.mvfr + 1;
+ if (pred)
+ md += dforwp;
+ if (md > fsmax)
+ dforw = md - fsr;
+ else if (md < fsmin)
+ dforw = md + fsr;
+ else
+ dforw = md;
+ dforwp = dforw;
+ if (p.flags & Mpegio->FPFV) {
+ ydf = rforw + dforw * width;
+ rforw <<= 1;
+ dforw <<= 1;
+ } else
+ ydf = (rforw >> 1) + (dforw >> 1) * width;
+ pred = 1;
+ if (mb.rls != nil) {
+ invQ_nintra_block(mb.rls, mb.qscale);
+ deltambforw();
+ } else
+ copymbforw();
+ } else {
+ if (mb.rls == nil)
+ raisex("empty delta");
+ invQ_nintra_block(mb.rls, mb.qscale);
+ deltamb();
+ pred = 0;
+ }
+ ipred = 0;
+ }
+ nextmb();
+ n++;
+ }
+ }
+ while (n < mpi) {
+ copymb();
+ nextmb();
+ n++;
+ }
+ R = P;
+ Rs[rn] = P;
+ rn ^= 1;
+ return P;
+}
+
+Bdecode(p: ref Mpegio->Picture): ref Mpegio->YCbCr
+{
+ return Bdecode2(p, Rs[rn ^ 1], Rs[rn]);
+}
+
+Bdecode2(p: ref Mpegio->Picture, f0, f1: ref Mpegio->YCbCr): ref Mpegio->YCbCr
+{
+ rforwp, dforwp, rbackp, dbackp: int;
+ md, c: int;
+ M = f0;
+ N = f1;
+ B = B0;
+ fs := 1 << p.forwfc;
+ fsr := fs << 5;
+ fsmin := -(fs << 4);
+ fsmax := (fs << 4) - 1;
+ bs := 1 << p.backfc;
+ bsr := bs << 5;
+ bsmin := -(bs << 4);
+ bsmax := (bs << 4) - 1;
+ sa := p.slices;
+ n := 0;
+ mbr = mps;
+ ybase = 0;
+ for (i := 0; i < len sa; i++) {
+ ipred := 0;
+ rback = 0;
+ rforw = 0;
+ dback = 0;
+ dforw = 0;
+ rbackp = 0;
+ rforwp = 0;
+ dbackp = 0;
+ dforwp = 0;
+ ydb = 0;
+ ydf = 0;
+ ba := sa[i].blocks;
+ for (j := 0; j < len ba; j++) {
+ mb := ba[j];
+ while (n < mb.addr) {
+ interpmb();
+ nextmb();
+ ipred = 0;
+ n++;
+ }
+ if (mb.flags & Mpegio->MB_I) {
+ invQ_intra_block(mb.rls, mb.qscale, ipred, B);
+ ipred = 1;
+ rback = 0;
+ rforw = 0;
+ dback = 0;
+ dforw = 0;
+ rbackp = 0;
+ rforwp = 0;
+ dbackp = 0;
+ dforwp = 0;
+ ydb = 0;
+ ydf = 0;
+ } else {
+ if (mb.flags & Mpegio->MB_MF) {
+ if (fs == 1 || mb.mhfc == 0)
+ md = mb.mhfc;
+ else if ((c = mb.mhfc) < 0)
+ md = (c + 1) * fs - mb.mhfr - 1;
+ else
+ md = (c - 1) * fs + mb.mhfr + 1;
+ md += rforwp;
+ if (md > fsmax)
+ rforw = md - fsr;
+ else if (md < fsmin)
+ rforw = md + fsr;
+ else
+ rforw = md;
+ rforwp = rforw;
+ if (fs == 1 || mb.mvfc == 0)
+ md = mb.mvfc;
+ else if ((c = mb.mvfc) < 0)
+ md = (c + 1) * fs - mb.mvfr - 1;
+ else
+ md = (c - 1) * fs + mb.mvfr + 1;
+ md += dforwp;
+ if (md > fsmax)
+ dforw = md - fsr;
+ else if (md < fsmin)
+ dforw = md + fsr;
+ else
+ dforw = md;
+ dforwp = dforw;
+ if (p.flags & Mpegio->FPFV) {
+ ydf = rforw + dforw * width;
+ rforw <<= 1;
+ dforw <<= 1;
+ } else
+ ydf = (rforw >> 1) + (dforw >> 1) * width;
+ }
+ if (mb.flags & Mpegio->MB_MB) {
+ if (bs == 1 || mb.mhbc == 0)
+ md = mb.mhbc;
+ else if ((c = mb.mhbc) < 0)
+ md = (c + 1) * bs - mb.mhbr - 1;
+ else
+ md = (c - 1) * bs + mb.mhbr + 1;
+ md += rbackp;
+ if (md > bsmax)
+ rback = md - bsr;
+ else if (md < bsmin)
+ rback = md + bsr;
+ else
+ rback = md;
+ rbackp = rback;
+ if (bs == 1 || mb.mvbc == 0)
+ md = mb.mvbc;
+ else if ((c = mb.mvbc) < 0)
+ md = (c + 1) * bs - mb.mvbr - 1;
+ else
+ md = (c - 1) * bs + mb.mvbr + 1;
+ md += dbackp;
+ if (md > bsmax)
+ dback = md - bsr;
+ else if (md < bsmin)
+ dback = md + bsr;
+ else
+ dback = md;
+ dbackp = dback;
+ if (p.flags & Mpegio->FPBV) {
+ ydb = rback + dback * width;
+ rback <<= 1;
+ dback <<= 1;
+ } else
+ ydb = (rback >> 1) + (dback >> 1) * width;
+ }
+ vflags = mb.flags;
+ if (mb.rls != nil) {
+ invQ_nintra_block(mb.rls, mb.qscale);
+ deltambinterp();
+ } else
+ interpmb();
+ ipred = 0;
+ }
+ nextmb();
+ n++;
+ }
+ }
+ while (n < mpi) {
+ interpmb();
+ nextmb();
+ n++;
+ }
+ return B;
+}
+
+raisex(nil: string)
+{
+ raise "decode error";
+}
diff --git a/appl/wm/mpeg/fixidct.b b/appl/wm/mpeg/fixidct.b
new file mode 100644
index 00000000..992cf837
--- /dev/null
+++ b/appl/wm/mpeg/fixidct.b
@@ -0,0 +1,188 @@
+implement IDCT;
+
+include "sys.m";
+include "mpegio.m";
+
+init()
+{
+}
+
+# IDCT based on Arai, Agui, and Nakajima, using flow chart Figure 4.8
+# of Pennebaker & Mitchell, JPEG: Still Image Data Compression Standard.
+# Remember IDCT is reverse of flow of DCT.
+# Nasty truncated integer version (not compliant).
+
+B0: con 16;
+B1: con 16;
+M: con (1 << B0);
+N: con (1 << B1);
+
+a0: con 1.414;
+a1: con 0.707;
+a2: con 0.541;
+a3: con 0.707;
+a4: con 1.307;
+a5: con -0.383;
+
+A0: con int (a0 * real N);
+A1: con int (a1 * real M);
+A2: con int (a2 * real M);
+A3: con int (a3 * real M);
+A4: con int (a4 * real M);
+A5: con int (a5 * real M);
+
+# scaling factors from eqn 4-35 of P&M
+s1: con 1.0196;
+s2: con 1.0823;
+s3: con 1.2026;
+s4: con 1.4142;
+s5: con 1.8000;
+s6: con 2.6131;
+s7: con 5.1258;
+
+S1: con int (s1 * real N);
+S2: con int (s2 * real N);
+S3: con int (s3 * real N);
+S4: con int (s4 * real N);
+S5: con int (s5 * real N);
+S6: con int (s6 * real N);
+S7: con int (s7 * real N);
+
+# overall normalization of 1/16, folded into premultiplication on vertical pass
+S: con 4;
+scale: con 0.0625;
+
+idct(b: array of int)
+{
+ x, y: int;
+
+ r := array[8*8] of int;
+
+ # transform horizontally
+ for(y=0; y<8; y++){
+ eighty := y<<3;
+ # if all non-DC components are zero, just propagate the DC term
+ if(b[eighty+1]==0)
+ if(b[eighty+2]==0 && b[eighty+3]==0)
+ if(b[eighty+4]==0 && b[eighty+5]==0)
+ if(b[eighty+6]==0 && b[eighty+7]==0){
+ v := b[eighty]*A0;
+ r[eighty+0] = v;
+ r[eighty+1] = v;
+ r[eighty+2] = v;
+ r[eighty+3] = v;
+ r[eighty+4] = v;
+ r[eighty+5] = v;
+ r[eighty+6] = v;
+ r[eighty+7] = v;
+ continue;
+ }
+
+ # step 5
+ in1 := S1*b[eighty+1];
+ in3 := S3*b[eighty+3];
+ in5 := S5*b[eighty+5];
+ in7 := S7*b[eighty+7];
+ f2 := S2*b[eighty+2];
+ f3 := S6*b[eighty+6];
+ f5 := (in1+in7);
+ f7 := (in5+in3);
+
+ # step 4
+ g2 := f2-f3;
+ g4 := (in5-in3);
+ g6 := (in1-in7);
+ g7 := f5+f7;
+
+ # step 3.5
+ t := ((g4+g6)>>B0)*A5;
+
+ # step 3
+ f0 := A0*b[eighty+0];
+ f1 := S4*b[eighty+4];
+ f3 += f2;
+ f2 = A1*(g2>>B0);
+
+ # step 2
+ g0 := f0+f1;
+ g1 := f0-f1;
+ g3 := f2+f3;
+ g4 = t-A2*(g4>>B0);
+ g5 := A3*((f5-f7)>>B0);
+ g6 = A4*(g6>>B0)+t;
+
+ # step 1
+ f0 = g0+g3;
+ f1 = g1+f2;
+ f2 = g1-f2;
+ f3 = g0-g3;
+ f5 = g5-g4;
+ f6 := g5+g6;
+ f7 = g6+g7;
+
+ # step 6
+ r[eighty+0] = (f0+f7);
+ r[eighty+1] = (f1+f6);
+ r[eighty+2] = (f2+f5);
+ r[eighty+3] = (f3-g4);
+ r[eighty+4] = (f3+g4);
+ r[eighty+5] = (f2-f5);
+ r[eighty+6] = (f1-f6);
+ r[eighty+7] = (f0-f7);
+ }
+
+ # transform vertically
+ for(x=0; x<8; x++){
+ # step 5
+ in1 := S1*(r[x+8]>>(B1+S));
+ in3 := S3*(r[x+24]>>(B1+S));
+ in5 := S5*(r[x+40]>>(B1+S));
+ in7 := S7*(r[x+56]>>(B1+S));
+ f2 := S2*(r[x+16]>>(B1+S));
+ f3 := S6*(r[x+48]>>(B1+S));
+ f5 := (in1+in7);
+ f7 := (in5+in3);
+
+ # step 4
+ g2 := f2-f3;
+ g4 := (in5-in3);
+ g6 := (in1-in7);
+ g7 := f5+f7;
+
+ # step 3.5
+ t := ((g4+g6)>>B0)*A5;
+
+ # step 3
+ f0 := A0*(r[x]>>(B1+S));
+ f1 := S4*(r[x+32]>>(B1+S));
+ f3 += f2;
+ f2 = A1*(g2>>B0);
+
+ # step 2
+ g0 := f0+f1;
+ g1 := f0-f1;
+ g3 := f2+f3;
+ g4 = t-A2*(g4>>B0);
+ g5 := A3*((f5-f7)>>B0);
+ g6 = A4*(g6>>B0)+t;
+
+ # step 1
+ f0 = g0+g3;
+ f1 = g1+f2;
+ f2 = g1-f2;
+ f3 = g0-g3;
+ f5 = g5-g4;
+ f6 := g5+g6;
+ f7 = g6+g7;
+
+ # step 6
+ b[x] = (f0+f7)>>B1;
+ b[x+8] = (f1+f6)>>B1;
+ b[x+16] = (f2+f5)>>B1;
+ b[x+24] = (f3-g4)>>B1;
+ b[x+32] = (f3+g4)>>B1;
+ b[x+40] = (f2-f5)>>B1;
+ b[x+48] = (f1-f6)>>B1;
+ b[x+56] = (f0-f7)>>B1;
+ }
+}
diff --git a/appl/wm/mpeg/fltidct.b b/appl/wm/mpeg/fltidct.b
new file mode 100644
index 00000000..24c80fe2
--- /dev/null
+++ b/appl/wm/mpeg/fltidct.b
@@ -0,0 +1,177 @@
+implement IDCT;
+
+include "sys.m";
+include "mpegio.m";
+
+init()
+{
+}
+
+# IDCT based on Arai, Agui, and Nakajima, using flow chart Figure 4.8
+# of Pennebaker & Mitchell, JPEG: Still Image Data Compression Standard.
+# Remember IDCT is reverse of flow of DCT.
+# Based on rob's readjpeg.b
+
+a0: con 1.414;
+a1: con 0.707;
+a2: con 0.541;
+a3: con 0.707;
+a4: con 1.307;
+a5: con -0.383;
+
+# scaling factors from eqn 4-35 of P&M
+s1: con 1.0196;
+s2: con 1.0823;
+s3: con 1.2026;
+s4: con 1.4142;
+s5: con 1.8000;
+s6: con 2.6131;
+s7: con 5.1258;
+
+# overall normalization of 1/16, folded into premultiplication on vertical pass
+scale: con 0.0625;
+
+ridct(zin: array of real, zout: array of real)
+{
+ x, y: int;
+
+ r := array[8*8] of real;
+
+ # transform horizontally
+ for(y=0; y<8; y++){
+ eighty := y<<3;
+ # if all non-DC components are zero, just propagate the DC term
+ if(zin[eighty+1]==0.)
+ if(zin[eighty+2]==0. && zin[eighty+3]==0.)
+ if(zin[eighty+4]==0. && zin[eighty+5]==0.)
+ if(zin[eighty+6]==0. && zin[eighty+7]==0.){
+ v := zin[eighty]*a0;
+ r[eighty+0] = v;
+ r[eighty+1] = v;
+ r[eighty+2] = v;
+ r[eighty+3] = v;
+ r[eighty+4] = v;
+ r[eighty+5] = v;
+ r[eighty+6] = v;
+ r[eighty+7] = v;
+ continue;
+ }
+
+ # step 5
+ in1 := s1*zin[eighty+1];
+ in3 := s3*zin[eighty+3];
+ in5 := s5*zin[eighty+5];
+ in7 := s7*zin[eighty+7];
+ f2 := s2*zin[eighty+2];
+ f3 := s6*zin[eighty+6];
+ f5 := (in1+in7);
+ f7 := (in5+in3);
+
+ # step 4
+ g2 := f2-f3;
+ g4 := (in5-in3);
+ g6 := (in1-in7);
+ g7 := f5+f7;
+
+ # step 3.5
+ t := (g4+g6)*a5;
+
+ # step 3
+ f0 := a0*zin[eighty+0];
+ f1 := s4*zin[eighty+4];
+ f3 += f2;
+ f2 = a1*g2;
+
+ # step 2
+ g0 := f0+f1;
+ g1 := f0-f1;
+ g3 := f2+f3;
+ g4 = t-a2*g4;
+ g5 := a3*(f5-f7);
+ g6 = a4*g6+t;
+
+ # step 1
+ f0 = g0+g3;
+ f1 = g1+f2;
+ f2 = g1-f2;
+ f3 = g0-g3;
+ f5 = g5-g4;
+ f6 := g5+g6;
+ f7 = g6+g7;
+
+ # step 6
+ r[eighty+0] = (f0+f7);
+ r[eighty+1] = (f1+f6);
+ r[eighty+2] = (f2+f5);
+ r[eighty+3] = (f3-g4);
+ r[eighty+4] = (f3+g4);
+ r[eighty+5] = (f2-f5);
+ r[eighty+6] = (f1-f6);
+ r[eighty+7] = (f0-f7);
+ }
+
+ # transform vertically
+ for(x=0; x<8; x++){
+ # step 5
+ in1 := scale*s1*r[x+8];
+ in3 := scale*s3*r[x+24];
+ in5 := scale*s5*r[x+40];
+ in7 := scale*s7*r[x+56];
+ f2 := scale*s2*r[x+16];
+ f3 := scale*s6*r[x+48];
+ f5 := (in1+in7);
+ f7 := (in5+in3);
+
+ # step 4
+ g2 := f2-f3;
+ g4 := (in5-in3);
+ g6 := (in1-in7);
+ g7 := f5+f7;
+
+ # step 3.5
+ t := (g4+g6)*a5;
+
+ # step 3
+ f0 := scale*a0*r[x];
+ f1 := scale*s4*r[x+32];
+ f3 += f2;
+ f2 = a1*g2;
+
+ # step 2
+ g0 := f0+f1;
+ g1 := f0-f1;
+ g3 := f2+f3;
+ g4 = t-a2*g4;
+ g5 := a3*(f5-f7);
+ g6 = a4*g6+t;
+
+ # step 1
+ f0 = g0+g3;
+ f1 = g1+f2;
+ f2 = g1-f2;
+ f3 = g0-g3;
+ f5 = g5-g4;
+ f6 := g5+g6;
+ f7 = g6+g7;
+
+ # step 6
+ zout[x] = (f0+f7);
+ zout[x+8] = (f1+f6);
+ zout[x+16] = (f2+f5);
+ zout[x+24] = (f3-g4);
+ zout[x+32] = (f3+g4);
+ zout[x+40] = (f2-f5);
+ zout[x+48] = (f1-f6);
+ zout[x+56] = (f0-f7);
+ }
+}
+
+idct(b: array of int)
+{
+ tmp := array[64] of real;
+ for (i := 0; i < 64; i++)
+ tmp[i] = real b[i];
+ ridct(tmp, tmp);
+ for (i = 0; i < 64; i++)
+ b[i] = int tmp[i];
+}
diff --git a/appl/wm/mpeg/mai.tab b/appl/wm/mpeg/mai.tab
new file mode 100644
index 00000000..8884adad
--- /dev/null
+++ b/appl/wm/mpeg/mai.tab
@@ -0,0 +1,2053 @@
+# vlc mai
+mai_size: con 2048;
+mai_bits: con 11;
+mai_table:= array[] of {
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (11, 33),
+ (11, 32),
+ (11, 31),
+ (11, 30),
+ (11, 29),
+ (11, 28),
+ (11, 27),
+ (11, 26),
+ (11, 25),
+ (11, 24),
+ (11, 23),
+ (11, 22),
+ (10, 21),
+ (10, 21),
+ (10, 20),
+ (10, 20),
+ (10, 19),
+ (10, 19),
+ (10, 18),
+ (10, 18),
+ (10, 17),
+ (10, 17),
+ (10, 16),
+ (10, 16),
+ (8, 15),
+ (8, 15),
+ (8, 15),
+ (8, 15),
+ (8, 15),
+ (8, 15),
+ (8, 15),
+ (8, 15),
+ (8, 14),
+ (8, 14),
+ (8, 14),
+ (8, 14),
+ (8, 14),
+ (8, 14),
+ (8, 14),
+ (8, 14),
+ (8, 13),
+ (8, 13),
+ (8, 13),
+ (8, 13),
+ (8, 13),
+ (8, 13),
+ (8, 13),
+ (8, 13),
+ (8, 12),
+ (8, 12),
+ (8, 12),
+ (8, 12),
+ (8, 12),
+ (8, 12),
+ (8, 12),
+ (8, 12),
+ (8, 11),
+ (8, 11),
+ (8, 11),
+ (8, 11),
+ (8, 11),
+ (8, 11),
+ (8, 11),
+ (8, 11),
+ (8, 10),
+ (8, 10),
+ (8, 10),
+ (8, 10),
+ (8, 10),
+ (8, 10),
+ (8, 10),
+ (8, 10),
+ (7, 9),
+ (7, 9),
+ (7, 9),
+ (7, 9),
+ (7, 9),
+ (7, 9),
+ (7, 9),
+ (7, 9),
+ (7, 9),
+ (7, 9),
+ (7, 9),
+ (7, 9),
+ (7, 9),
+ (7, 9),
+ (7, 9),
+ (7, 9),
+ (7, 8),
+ (7, 8),
+ (7, 8),
+ (7, 8),
+ (7, 8),
+ (7, 8),
+ (7, 8),
+ (7, 8),
+ (7, 8),
+ (7, 8),
+ (7, 8),
+ (7, 8),
+ (7, 8),
+ (7, 8),
+ (7, 8),
+ (7, 8),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 7),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (4, 4),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (3, 2),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+ (1, 1),
+};
diff --git a/appl/wm/mpeg/mai.vlc b/appl/wm/mpeg/mai.vlc
new file mode 100644
index 00000000..108c4658
--- /dev/null
+++ b/appl/wm/mpeg/mai.vlc
@@ -0,0 +1,35 @@
+# Macroblock Address Increment
+# vlc mai < mai.vlc > mai.tab
+1 1
+011 2
+010 3
+0011 4
+0010 5
+00011 6
+00010 7
+0000111 8
+0000110 9
+00001011 10
+00001010 11
+00001001 12
+00001000 13
+00000111 14
+00000110 15
+0000010111 16
+0000010110 17
+0000010101 18
+0000010100 19
+0000010011 20
+0000010010 21
+00000100011 22
+00000100010 23
+00000100001 24
+00000100000 25
+00000011111 26
+00000011110 27
+00000011101 28
+00000011100 29
+00000011011 30
+00000011010 31
+00000011001 32
+00000011000 33
diff --git a/appl/wm/mpeg/makergbvmap.b b/appl/wm/mpeg/makergbvmap.b
new file mode 100644
index 00000000..9e7e7ffa
--- /dev/null
+++ b/appl/wm/mpeg/makergbvmap.b
@@ -0,0 +1,31 @@
+implement MakeRGBVMap;
+
+include "sys.m";
+include "draw.m";
+
+draw: Draw;
+sys: Sys;
+
+Display: import draw;
+
+MakeRGBVMap: module
+{
+ init: fn(ctxt: ref Draw->Context, nil: list of string);
+};
+
+init(ctxt: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ if (draw == nil) {
+ sys->print("could not load %s: %r\n", Draw->PATH);
+ exit;
+ }
+ d := ctxt.display;
+ sys->print("rgbvmap := array[3*256] of {\n");
+ for (i := 0; i < 256; i++) {
+ (r, g, b) := d.cmap2rgb(i);
+ sys->print("\tbyte\t%d,byte\t%d,byte\t%d,\n", r, g, b);
+ }
+ sys->print("};\n");
+}
diff --git a/appl/wm/mpeg/maketables b/appl/wm/mpeg/maketables
new file mode 100644
index 00000000..d663a36e
--- /dev/null
+++ b/appl/wm/mpeg/maketables
@@ -0,0 +1,36 @@
+echo motion:
+vlc motion < motion.vlc > motion.tab
+echo rl0f:
+vlc -c rl0f < rl0f.vlc > rl0f.tab
+echo rl0n:
+vlc -c rl0n < rl0n.vlc > rl0n.tab
+echo c0:
+vlc -uUNDEF,UNDEF c0 < c0.vlc > c0.tab
+echo c1:
+vlc -cfp c1 < c1.vlc > c1.tab
+echo c2:
+vlc -cfp c2 < c2.vlc > c2.tab
+echo c3:
+vlc -cfp c3 < c3.vlc > c3.tab
+echo c4:
+vlc -cfp c4 < c4.vlc > c4.tab
+echo c5:
+vlc -cfp c5 < c5.vlc > c5.tab
+echo c6:
+vlc -cfp c6 < c6.vlc > c6.tab
+echo c7:
+vlc -cfp c7 < c7.vlc > c7.tab
+echo mai:
+vlc mai < mai.vlc > mai.tab
+echo mbi:
+vlc mbi < mbi.vlc > mbi.tab
+echo mbp:
+vlc mbp < mbp.vlc > mbp.tab
+echo mbb:
+vlc mbb < mbb.vlc > mbb.tab
+echo cbp:
+vlc cbp < cbp.vlc > cbp.tab
+echo cdc:
+vlc cdc < cdc.vlc > cdc.tab
+echo ydc:
+vlc ydc < ydc.vlc > ydc.tab
diff --git a/appl/wm/mpeg/mbb.tab b/appl/wm/mpeg/mbb.tab
new file mode 100644
index 00000000..4707d394
--- /dev/null
+++ b/appl/wm/mpeg/mbb.tab
@@ -0,0 +1,69 @@
+# vlc mbb
+mbb_size: con 64;
+mbb_bits: con 6;
+mbb_table:= array[] of {
+ (0, UNDEF),
+ (6, 10),
+ (6, 6),
+ (6, 4),
+ (5, 8),
+ (5, 8),
+ (5, 9),
+ (5, 9),
+ (4, 0),
+ (4, 0),
+ (4, 0),
+ (4, 0),
+ (4, 3),
+ (4, 3),
+ (4, 3),
+ (4, 3),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 5),
+ (3, 5),
+ (3, 5),
+ (3, 5),
+ (3, 5),
+ (3, 5),
+ (3, 5),
+ (3, 5),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 7),
+ (2, 7),
+ (2, 7),
+ (2, 7),
+ (2, 7),
+ (2, 7),
+ (2, 7),
+ (2, 7),
+ (2, 7),
+ (2, 7),
+ (2, 7),
+ (2, 7),
+ (2, 7),
+ (2, 7),
+ (2, 7),
+ (2, 7),
+};
diff --git a/appl/wm/mpeg/mbb.vlc b/appl/wm/mpeg/mbb.vlc
new file mode 100644
index 00000000..1cc57796
--- /dev/null
+++ b/appl/wm/mpeg/mbb.vlc
@@ -0,0 +1,13 @@
+# Macroblock Type-B
+# vlc mbb < mbb.vlc > mbb.tab
+0010 0
+010 1
+10 2
+0011 3
+000011 4
+011 5
+000010 6
+11 7
+00010 8
+00011 9
+000001 10
diff --git a/appl/wm/mpeg/mbi.tab b/appl/wm/mpeg/mbi.tab
new file mode 100644
index 00000000..7a72eb3f
--- /dev/null
+++ b/appl/wm/mpeg/mbi.tab
@@ -0,0 +1,9 @@
+# vlc mbi
+mbi_size: con 4;
+mbi_bits: con 2;
+mbi_table:= array[] of {
+ (0, UNDEF),
+ (2, 1),
+ (1, 0),
+ (1, 0),
+};
diff --git a/appl/wm/mpeg/mbi.vlc b/appl/wm/mpeg/mbi.vlc
new file mode 100644
index 00000000..4b4349bc
--- /dev/null
+++ b/appl/wm/mpeg/mbi.vlc
@@ -0,0 +1,4 @@
+# Macroblock Type-I
+# vlc mbi < mbi.vlc > mbi.tab
+1 0
+01 1
diff --git a/appl/wm/mpeg/mbp.tab b/appl/wm/mpeg/mbp.tab
new file mode 100644
index 00000000..26b41fec
--- /dev/null
+++ b/appl/wm/mpeg/mbp.tab
@@ -0,0 +1,69 @@
+# vlc mbp
+mbp_size: con 64;
+mbp_bits: con 6;
+mbp_table:= array[] of {
+ (0, UNDEF),
+ (6, 6),
+ (5, 2),
+ (5, 2),
+ (5, 4),
+ (5, 4),
+ (5, 5),
+ (5, 5),
+ (3, 0),
+ (3, 0),
+ (3, 0),
+ (3, 0),
+ (3, 0),
+ (3, 0),
+ (3, 0),
+ (3, 0),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+ (1, 3),
+};
diff --git a/appl/wm/mpeg/mbp.vlc b/appl/wm/mpeg/mbp.vlc
new file mode 100644
index 00000000..8893ff9b
--- /dev/null
+++ b/appl/wm/mpeg/mbp.vlc
@@ -0,0 +1,9 @@
+# Macroblock Type-P
+# vlc mbp < mbp.vlc > mbp.tab
+001 0
+01 1
+00001 2
+1 3
+00010 4
+00011 5
+000001 6
diff --git a/appl/wm/mpeg/mkfile b/appl/wm/mpeg/mkfile
new file mode 100644
index 00000000..1b23f370
--- /dev/null
+++ b/appl/wm/mpeg/mkfile
@@ -0,0 +1,47 @@
+<../../../mkconfig
+
+TARG=\
+ decode.dis\
+ decode4.dis\
+ fixidct.dis\
+ fltidct.dis\
+ makergbvmap.dis\
+ mpegio.dis\
+ refidct.dis\
+ remap.dis\
+ remap1.dis\
+ remap2.dis\
+ remap4.dis\
+ remap24.dis\
+ remap8.dis\
+ scidct.dis\
+ vlc.dis\
+
+MODULES=\
+ closest.m\
+ mpegio.m\
+ rgbvmap.m\
+
+SYSMODULES=\
+ bufio.m\
+ draw.m\
+ math.m\
+ sys.m\
+ tk.m\
+ wmlib.m\
+
+DISBIN=$ROOT/dis/mpeg
+
+<$ROOT/mkfiles/mkdis
+
+all:V: mpeg.dis
+
+install:V: $ROOT/dis/mpeg/mpeg.dis
+
+$ROOT/dis/mpeg/mpeg.dis: mpeg.dis
+ rm -f $target && cp mpeg.dis $target
+
+mpeg.dis: $MODULES $SYS_MODULE
+
+nuke:V:
+ rm -f $ROOT/dis/mpeg/mpeg.dis
diff --git a/appl/wm/mpeg/motion.tab b/appl/wm/mpeg/motion.tab
new file mode 100644
index 00000000..ca619976
--- /dev/null
+++ b/appl/wm/mpeg/motion.tab
@@ -0,0 +1,2053 @@
+# vlc motion
+motion_size: con 2048;
+motion_bits: con 11;
+motion_table:= array[] of {
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (0, UNDEF),
+ (11, 16),
+ (11, -16),
+ (11, 15),
+ (11, -15),
+ (11, 14),
+ (11, -14),
+ (11, 13),
+ (11, -13),
+ (11, 12),
+ (11, -12),
+ (11, 11),
+ (11, -11),
+ (10, 10),
+ (10, 10),
+ (10, -10),
+ (10, -10),
+ (10, 9),
+ (10, 9),
+ (10, -9),
+ (10, -9),
+ (10, 8),
+ (10, 8),
+ (10, -8),
+ (10, -8),
+ (8, 7),
+ (8, 7),
+ (8, 7),
+ (8, 7),
+ (8, 7),
+ (8, 7),
+ (8, 7),
+ (8, 7),
+ (8, -7),
+ (8, -7),
+ (8, -7),
+ (8, -7),
+ (8, -7),
+ (8, -7),
+ (8, -7),
+ (8, -7),
+ (8, 6),
+ (8, 6),
+ (8, 6),
+ (8, 6),
+ (8, 6),
+ (8, 6),
+ (8, 6),
+ (8, 6),
+ (8, -6),
+ (8, -6),
+ (8, -6),
+ (8, -6),
+ (8, -6),
+ (8, -6),
+ (8, -6),
+ (8, -6),
+ (8, 5),
+ (8, 5),
+ (8, 5),
+ (8, 5),
+ (8, 5),
+ (8, 5),
+ (8, 5),
+ (8, 5),
+ (8, -5),
+ (8, -5),
+ (8, -5),
+ (8, -5),
+ (8, -5),
+ (8, -5),
+ (8, -5),
+ (8, -5),
+ (7, 4),
+ (7, 4),
+ (7, 4),
+ (7, 4),
+ (7, 4),
+ (7, 4),
+ (7, 4),
+ (7, 4),
+ (7, 4),
+ (7, 4),
+ (7, 4),
+ (7, 4),
+ (7, 4),
+ (7, 4),
+ (7, 4),
+ (7, 4),
+ (7, -4),
+ (7, -4),
+ (7, -4),
+ (7, -4),
+ (7, -4),
+ (7, -4),
+ (7, -4),
+ (7, -4),
+ (7, -4),
+ (7, -4),
+ (7, -4),
+ (7, -4),
+ (7, -4),
+ (7, -4),
+ (7, -4),
+ (7, -4),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, 3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (5, -3),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, 2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (4, -2),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, 1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (3, -1),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+ (1, 0),
+};
diff --git a/appl/wm/mpeg/motion.vlc b/appl/wm/mpeg/motion.vlc
new file mode 100644
index 00000000..db98581e
--- /dev/null
+++ b/appl/wm/mpeg/motion.vlc
@@ -0,0 +1,19 @@
+# Motion Codes
+# vlc motion < motion.vlc > motion.tab
+1 0
+01s 1
+001s 2
+0001s 3
+000011s 4
+0000101s 5
+0000100s 6
+0000011s 7
+000001011s 8
+000001010s 9
+000001001s 10
+0000010001s 11
+0000010000s 12
+0000001111s 13
+0000001110s 14
+0000001101s 15
+0000001100s 16
diff --git a/appl/wm/mpeg/mpeg.b b/appl/wm/mpeg/mpeg.b
new file mode 100644
index 00000000..1ac8c276
--- /dev/null
+++ b/appl/wm/mpeg/mpeg.b
@@ -0,0 +1,285 @@
+implement WmMpeg;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Point, Rect, Display, Image: import draw;
+
+include "tk.m";
+ tk: Tk;
+ Toplevel: import tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+ ctxt: ref Draw->Context;
+
+include "dialog.m";
+ dialog: Dialog;
+
+include "selectfile.m";
+ selectfile: Selectfile;
+
+include "mpegio.m";
+
+include "arg.m";
+
+mio: Mpegio;
+decode: Mpegd;
+remap: Remap;
+Mpegi: import mio;
+
+WmMpeg: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Stopped, Playing, Stepping, Paused: con iota;
+state := Stopped;
+depth := -1;
+sdepth: int;
+cvt: ref Image;
+
+pixelrec: Draw->Rect;
+
+decoders := array[] of {
+1=> Mpegd->PATH4,
+2=> Mpegd->PATH4,
+4=> Mpegd->PATH4,
+8 or 16 or 24 or 32 => Mpegd->PATH,
+};
+
+remappers := array[] of {
+1=> Remap->PATH1,
+2=> Remap->PATH2,
+4=> Remap->PATH4,
+8 or 16 or 24 or 32 => Remap->PATH,
+};
+
+task_cfg := array[] of {
+ "canvas .c",
+ "frame .b",
+ "button .b.File -text File -command {send cmd file}",
+ "button .b.Stop -text Stop -command {send cmd stop}",
+ "button .b.Pause -text Pause -command {send cmd pause}",
+ "button .b.Step -text Step -command {send cmd step}",
+ "button .b.Play -text Play -command {send cmd play}",
+ "frame .f",
+ "label .f.file -text {File:}",
+ "label .f.name",
+ "pack .f.file .f.name -side left",
+ "pack .b.File .b.Stop .b.Pause .b.Step .b.Play -side left",
+ "pack .f -fill x",
+ "pack .b -anchor w",
+ "pack .c -side bottom -fill both -expand 1",
+ "pack propagate . 0",
+};
+
+init(xctxt: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient= load Tkclient Tkclient->PATH;
+ dialog = load Dialog Dialog->PATH;
+ selectfile= load Selectfile Selectfile->PATH;
+
+ ctxt = xctxt;
+ tkclient->init();
+ dialog->init();
+ selectfile->init();
+
+ darg, tkarg: string;
+ arg := load Arg Arg->PATH;
+ arg->init(argv);
+ while((c := arg->opt()) != 0)
+ case c {
+ 'x' =>
+ tkarg = arg->arg();
+ 'd' =>
+ darg = arg->arg();
+ }
+ args := arg->argv();
+ arg = nil;
+ if(darg != nil)
+ depth = int darg;
+ sdepth = ctxt.display.image.depth;
+ if (depth < 0 || depth > sdepth)
+ depth = sdepth;
+ (t, menubut) := tkclient->toplevel(ctxt, tkarg, "MPEG Player", 0);
+
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+
+ for(i:=0; i<len task_cfg; i++)
+ tk->cmd(t, task_cfg[i]);
+
+ tk->cmd(t, "bind . <Configure> {send cmd resize}");
+ tk->cmd(t, "update");
+ tkclient->onscreen(t, nil);
+ tkclient->startinput(t, "kbd"::"ptr"::nil);
+
+ mio = load Mpegio Mpegio->PATH;
+ decode = load Mpegd decoders[depth];
+ remap = load Remap remappers[depth];
+ if(mio == nil || decode == nil || remap == nil) {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Loading Interfaces",
+ "Failed to load the MPEG\ninterface: "+sys->sprint("%r"),
+ 0, "Exit"::nil);
+ return;
+ }
+ mio->init();
+
+ fname := "";
+ ctl := chan of string;
+ state = Stopped;
+
+ for(;;) alt {
+ s := <-t.ctxt.kbd =>
+ tk->keyboard(t, s);
+ s := <-t.ctxt.ptr =>
+ tk->pointer(t, *s);
+ s := <-t.ctxt.ctl or
+ s = <-t.wreq =>
+ tkclient->wmctl(t, s);
+ s := <-menubut =>
+ if(s == "exit"){
+ state = Stopped;
+ return;
+ }
+ tkclient->wmctl(t, s);
+ press := <-cmd =>
+ case press {
+ "file" =>
+ state = Stopped;
+ patterns := list of {
+ "*.mpg (MPEG movie files)",
+ "* (All Files)"
+ };
+ fname = selectfile->filename(ctxt, t.image, "Locate MPEG files",
+ patterns, nil);
+ if(fname != nil) {
+ tk->cmd(t, ".f.name configure -text {"+fname+"}");
+ tk->cmd(t, "update");
+ }
+ "play" =>
+ if (state != Stopped) {
+ state = Playing;
+ continue;
+ }
+ if(fname != nil) {
+ state = Playing;
+ spawn play(t, fname);
+ }
+ "step" =>
+ if (state != Stopped) {
+ state = Stepping;
+ continue;
+ }
+ if(fname != nil) {
+ state = Stepping;
+ spawn play(t, fname);
+ }
+ "pause" =>
+ if(state == Playing)
+ state = Paused;
+ "stop" =>
+ state = Stopped;
+ }
+ }
+}
+
+play(t: ref Toplevel, file: string)
+{
+ sp := list of { "Stop Play" };
+
+ fd := sys->open(file, Sys->OREAD);
+ if(fd == nil) {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Open MPEG file", sys->sprint("%r"), 0, sp);
+ return;
+ }
+ m := mio->prepare(fd, file);
+ m.streaminit(Mpegio->VIDEO_STR0);
+ p := m.getpicture(1);
+ decode->init(m);
+ remap->init(m);
+
+ canvr := canvsize(t);
+ o := Point(0, 0);
+ dx := canvr.dx();
+ if(dx > m.width)
+ o.x = (dx - m.width)/2;
+ dy := canvr.dy();
+ if(dy > m.height)
+ o.y = (dy - m.height)/2;
+ canvr.min = canvr.min.add(o);
+ canvr.max = canvr.min.add(Point(m.width, m.height));
+
+ if (depth != sdepth){
+ chans := Draw->CMAP8;
+ case depth {
+ 0 => chans = Draw->GREY1;
+ 1 => chans = Draw->GREY2;
+ 2 => chans = Draw->GREY4;
+ 3 => chans = Draw->CMAP8;
+ 4 => chans = Draw->RGB16;
+ 5 => chans = Draw->RGB24; # ?
+ }
+ cvt = ctxt.display.newimage(Rect((0, 0), (m.width, m.height)), chans, 0, 0);
+ }
+
+ f, pf: ref Mpegio->YCbCr;
+ for(;;) {
+ if(state == Stopped)
+ break;
+ case p.ptype {
+ Mpegio->IPIC =>
+ f = decode->Idecode(p);
+ Mpegio->PPIC =>
+ f = decode->Pdecode(p);
+ Mpegio->BPIC =>
+ f = decode->Bdecode(p);
+ }
+ while(state == Paused)
+ sys->sleep(0);
+ if (p.ptype == Mpegio->BPIC) {
+ writepixels(t, canvr, remap->remap(f));
+ if(state == Stepping)
+ state = Paused;
+ } else {
+ if (pf != nil) {
+ writepixels(t, canvr, remap->remap(pf));
+ if(state == Stepping)
+ state = Paused;
+ }
+ pf = f;
+ }
+ if ((p = m.getpicture(1)) == nil) {
+ writepixels(t, canvr, remap->remap(pf));
+ break;
+ }
+ }
+ state = Stopped;
+}
+
+writepixels(t: ref Toplevel, r: Rect, b: array of byte)
+{
+ if (cvt != nil) {
+ cvt.writepixels(cvt.r, b);
+ t.image.draw(r, cvt, nil, (0, 0));
+ } else
+ t.image.writepixels(r, b);
+}
+
+canvsize(t: ref Toplevel): Rect
+{
+ r: Rect;
+
+ r.min.x = int tk->cmd(t, ".c cget -actx");
+ r.min.y = int tk->cmd(t, ".c cget -acty");
+ r.max.x = r.min.x + int tk->cmd(t, ".c cget -width");
+ r.max.y = r.min.y + int tk->cmd(t, ".c cget -height");
+
+ return r;
+}
diff --git a/appl/wm/mpeg/mpegio.b b/appl/wm/mpeg/mpegio.b
new file mode 100644
index 00000000..3206776b
--- /dev/null
+++ b/appl/wm/mpeg/mpegio.b
@@ -0,0 +1,870 @@
+implement Mpegio;
+
+#
+# MPEG ISO 11172 IO module.
+#
+
+include "sys.m";
+include "mpegio.m";
+
+sys: Sys;
+
+init()
+{
+ sys = load Sys Sys->PATH;
+}
+
+raisex(s: string)
+{
+ raise MEXCEPT + s;
+}
+
+prepare(fd: ref Sys->FD, name: string): ref Mpegi
+{
+ m := ref Mpegi;
+ m.fd = fd;
+ m.name = name;
+ m.seek = 0;
+ m.looked = 0;
+ m.index = 0;
+ m.size = 0;
+ m.buff = array[MBSZ] of byte;
+ return m;
+}
+
+Mpegi.startsys(m: self ref Mpegi)
+{
+ # 2.4.3.2
+ m.xnextsc(PACK_SC);
+ m.packhdr();
+ m.xnextsc(SYSHD_SC);
+ m.syssz = m.getw();
+ m.boundmr = m.get22("boundmr");
+ m.syspar = m.getw();
+ if ((m.syspar & 16r20) == 0 || m.getb() != 16rFF)
+ m.fmterr("syspar");
+ t := m.syssz - 6;
+ if (t <= 0 || (t % 3) != 0)
+ m.fmterr("syssz");
+ t /= 3;
+ m.nstream = t;
+ m.streams = array[t] of Stream;
+ for (i := 0; i < t; i++) {
+ v := m.getb();
+ if ((v & 16r80) == 0)
+ m.fmterr("streamid");
+ w := m.getb();
+ if ((w & 16rC0) != 16rC0)
+ m.fmterr("stream mark");
+ m.streams[i] = (byte v, byte ((w >> 5) & 1), ((w & 16r1F) << 8) | m.getb(), nil);
+ }
+}
+
+Mpegi.packetcp(m: self ref Mpegi): int
+{
+ while ((c := m.nextsc()) != STREAM_EC) {
+ case c {
+ PACK_SC =>
+ m.packhdr();
+ SYSHD_SC =>
+ m.syshdr();
+ * =>
+ if (c < STREAM_BASE)
+ m.fmterr(sys->sprint("stream code %x", c));
+ # 2.4.3.3
+ l := m.getw();
+ fd := m.getfd(c);
+ if (fd != nil) {
+ if (c != PRIVSTREAM2)
+ l -= m.stamps();
+ if (m.log != nil)
+ sys->fprint(m.log, "%x %d %d\n", c & 16rFF, m.tell(), l);
+ m.cpn(fd, l);
+ } else
+ m.skipn(l);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+Mpegi.getfd(m: self ref Mpegi, c: int): ref Sys->FD
+{
+ id := byte c;
+ n := m.nstream;
+ for (i := 0; i < n; i++) {
+ if (m.streams[i].id == id)
+ return m.streams[i].fd;
+ }
+ return nil;
+}
+
+Mpegi.packhdr(m: self ref Mpegi)
+{
+ # 2.4.3.2
+ t := m.getb();
+ if ((t & 16rF1) != 16r21)
+ m.fmterr("pack tag");
+ m.packt0 = (t >> 1) & 7;
+ v := m.getb() << 22;
+ t = m.getb();
+ if ((t & 1) == 0)
+ m.fmterr("packt mark 1");
+ v |= ((t & ~1) << 15) | (m.getb() << 7);
+ t = m.getb();
+ if ((t & 1) == 0)
+ m.fmterr("packt mark 2");
+ m.packt1 = v | (t >> 1);
+ m.packmr = m.get22("packmr");
+}
+
+Mpegi.syshdr(m: self ref Mpegi)
+{
+ l := m.getw();
+ if (l != m.syssz)
+ m.fmterr("syshdr size mismatch");
+ m.skipn(l);
+}
+
+Mpegi.stamps(m: self ref Mpegi): int
+{
+ # 2.4.3.3
+ n := 1;
+ while ((c := m.getb()) == 16rFF)
+ n++;
+ if ((c >> 6) == 1) {
+ m.getb();
+ c = m.getb();
+ n += 2;
+ }
+ case c >> 4 {
+ 2 =>
+ m.skipn(4);
+ n += 4;
+ 3 =>
+ m.skipn(9);
+ n += 9;
+ * =>
+ if (c != 16rF)
+ m.fmterr("stamps");
+ }
+ return n;
+}
+
+Mpegi.streaminit(m: self ref Mpegi, c: int)
+{
+ m.inittables();
+ m.sid = c;
+ s := m.peeksc();
+ if (s == PACK_SC) {
+ m.startsys();
+ f := 0;
+ id := byte m.sid;
+ for (i := 0; i < m.nstream; i++) {
+ if (m.streams[i].id == id) {
+ f = 1;
+ break;
+ }
+ }
+ if (!f)
+ m.fmterr(sys->sprint("%x: stream not found", c));
+ m.sseek();
+ } else if (s == SEQUENCE_SC) {
+ m.sresid = -1;
+ m.slim = m.size;
+ } else
+ m.fmterr(sys->sprint("start code = %x", s));
+ m.sbits = 0;
+}
+
+Mpegi.sseek(m: self ref Mpegi)
+{
+ while ((c := m.nextsc()) != STREAM_EC) {
+ case c {
+ PACK_SC =>
+ m.packhdr();
+ SYSHD_SC =>
+ m.syshdr();
+ * =>
+ if (c < STREAM_BASE)
+ m.fmterr(sys->sprint("stream code %x", c));
+ # 2.4.3.3
+ l := m.getw();
+ if (c == m.sid) {
+ if (c != PRIVSTREAM2)
+ l -= m.stamps();
+ n := m.size - m.index;
+ if (l <= n) {
+ m.slim = m.index + l;
+ m.sresid = 0;
+ } else {
+ m.slim = m.size;
+ m.sresid = l - n;
+ }
+ return;
+ } else
+ m.skipn(l);
+ }
+ }
+ m.fmterr("end of stream");
+}
+
+Mpegi.getpicture(m: self ref Mpegi, detail: int): ref Picture
+{
+ g := 0;
+ for (;;) {
+ case c := m.snextsc() {
+ SEQUENCE_SC =>
+ m.seqhdr();
+ GROUP_SC =>
+ m.grphdr();
+ g = 1;
+ PICTURE_SC =>
+ p := m.picture(detail);
+ if (g)
+ p.flags |= GSTART;
+ return p;
+ SEQUENCE_EC =>
+ return nil;
+ * =>
+ m.fmterr(sys->sprint("start code %x", c));
+ }
+ }
+}
+
+Mpegi.seqhdr(m: self ref Mpegi)
+{
+ # 2.4.2.3
+ c := m.sgetb();
+ d := m.sgetb();
+ m.width = (c << 4) | (d >> 4);
+ m.height = ((d & 16rF) << 8) | m.sgetb();
+ c = m.sgetb();
+ m.aspect = c >> 4;
+ m.frames = c & 16rF;
+ m.rate = m.sgetn(18);
+ m.smarker();
+ m.vbv = m.sgetn(10);
+ m.flags = 0;
+ if (m.sgetn(1))
+ m.flags |= CONSTRAINED;
+ if (m.sgetn(1))
+ m.intra = m.getquant();
+ if (m.sgetn(1))
+ m.nintra = m.getquant();
+ if (m.speeksc() == EXTENSION_SC)
+ m.sseeksc();
+ if (m.speeksc() == USER_SC)
+ m.sseeksc();
+}
+
+Mpegi.grphdr(m: self ref Mpegi)
+{
+ # 2.4.2.4
+ v := m.sgetb() << 17;
+ v |= m.sgetb() << 9;
+ v |= m.sgetb() << 1;
+ c := m.sgetb();
+ m.smpte = v | (c >> 7);
+ if (c & (1 << 6))
+ m.flags |= CLOSED;
+ else
+ m.flags &= ~CLOSED;
+ if (c & (1 << 5))
+ m.flags |= BROKEN;
+ else
+ m.flags &= ~BROKEN;
+ if (m.speeksc() == EXTENSION_SC)
+ m.sseeksc();
+ if (m.speeksc() == USER_SC)
+ m.sseeksc();
+}
+
+Mpegi.getquant(m: self ref Mpegi): array of int
+{
+ a := array[64] of int;
+ for (i := 0; i < 64; i++)
+ a[i] = m.sgetn(8);
+ return a;
+}
+
+Mpegi.picture(m: self ref Mpegi, detail: int): ref Picture
+{
+ # 2.4.2.5
+ p := ref Picture;
+ p.temporal = m.sgetn(10);
+ p.ptype = m.sgetn(3);
+ p.vbvdelay = m.sgetn(16);
+ p.flags = 0;
+ if (p.ptype == PPIC || p.ptype == BPIC) {
+ if (m.sgetn(1))
+ p.flags |= FPFV;
+ p.forwfc = m.sgetn(3);
+ if (p.forwfc == 0)
+ m.fmterr("forwfc");
+ p.forwfc--;
+ if (p.ptype == BPIC) {
+ if (m.sgetn(1))
+ p.flags |= FPBV;
+ p.backfc = m.sgetn(3);
+ if (p.backfc == 0)
+ m.fmterr("backfc");
+ p.backfc--;
+ } else
+ p.backfc = 0;
+ } else {
+ p.forwfc = 0;
+ p.backfc = 0;
+ }
+ while (m.sgetn(1))
+ m.sgetn(8);
+ if (m.speeksc() == EXTENSION_SC)
+ m.sseeksc();
+ if (m.speeksc() == USER_SC)
+ m.sseeksc();
+ p.seek = m.tell() - 3;
+ if (m.sresid < 0)
+ p.eos = -1;
+ else
+ p.eos = m.seek - m.size + m.slim + m.sresid;
+ if (detail)
+ m.detail(p);
+ else
+ m.skipdetail();
+ return p;
+}
+
+Mpegi.detail(m: self ref Mpegi, p: ref Picture)
+{
+ l: list of ref Slice;
+ p.addr = -1;
+ while ((c := m.speeksc()) >= SLICE1_SC && c <= SLICEN_SC)
+ l = m.slice(p) :: l;
+ if (l == nil)
+ m.fmterr("slice sc");
+ n := len l;
+ a := array[n] of ref Slice;
+ while (--n >= 0) {
+ a[n] = hd l;
+ l = tl l;
+ }
+ p.slices = a;
+}
+
+Mpegi.skipdetail(m: self ref Mpegi)
+{
+ while ((c := m.speeksc()) >= SLICE1_SC && c <= SLICEN_SC) {
+ m.looked = 0;
+ m.sseeksc();
+ }
+}
+
+ESC, EOB, C0, C1, C2, C3, C4, C5, C6, C7: con -(iota + 1);
+
+include "mai.tab";
+include "mbi.tab";
+include "mbp.tab";
+include "mbb.tab";
+include "motion.tab";
+include "cbp.tab";
+include "cdc.tab";
+include "ydc.tab";
+include "rl0f.tab";
+include "rl0n.tab";
+include "c0.tab";
+include "c1.tab";
+include "c2.tab";
+include "c3.tab";
+include "c4.tab";
+include "c5.tab";
+include "c6.tab";
+include "c7.tab";
+
+mbif := array[] of {
+ MB_I,
+ MB_I | MB_Q,
+};
+
+mbpf := array[] of {
+ MB_MF,
+ MB_P,
+ MB_P | MB_Q,
+ MB_P | MB_MF,
+ MB_P | MB_MF | MB_Q,
+ MB_I,
+ MB_I | MB_Q,
+};
+
+mbbf := array[] of {
+ MB_MF,
+ MB_MB,
+ MB_MB | MB_MF,
+ MB_P | MB_MF,
+ MB_P | MB_MF | MB_Q,
+ MB_P | MB_MB,
+ MB_P | MB_MB | MB_Q,
+ MB_P | MB_MB | MB_MF,
+ MB_P | MB_MB | MB_MF | MB_Q,
+ MB_I,
+ MB_I | MB_Q,
+};
+
+c_bits := array[] of {
+ c1_bits,
+ c2_bits,
+ c3_bits,
+ c4_bits,
+ c5_bits,
+ c6_bits,
+ c7_bits,
+};
+
+c_tables: array of array of Pair;
+
+patcode := array[] of {
+ 1<<5, 1<<4, 1<<3, 1<<2, 1<<1, 1<<0,
+};
+
+Mpegi.inittables()
+{
+ if (c_tables == nil) {
+ c_tables = array[] of {
+ c1_table,
+ c2_table,
+ c3_table,
+ c4_table,
+ c5_table,
+ c6_table,
+ c7_table,
+ };
+ }
+}
+
+Mpegi.slice(m: self ref Mpegi, p: ref Picture): ref Slice
+{
+ m.snextsc();
+ s := ref Slice;
+ q := m.sgetn(5);
+ while (m.sgetn(1))
+ m.sgetn(8);
+ x := p.addr;
+ l: list of ref MacroBlock;
+ while (m.speekn(23) != 0) {
+ while (m.speekn(11) == 16rF)
+ m.sbits -= 11;
+ while (m.speekn(11) == 16r8) {
+ x += 33;
+ m.sbits -= 11;
+ }
+ i := m.svlc(mai_table, mai_bits, "mai");
+ x += i;
+ b := ref MacroBlock;
+ b.addr = x;
+ case p.ptype {
+ IPIC =>
+ b.flags = mbif[m.svlc(mbi_table, mbi_bits, "mbi")];
+ PPIC =>
+ b.flags = mbpf[m.svlc(mbp_table, mbp_bits, "mbp")];
+ BPIC =>
+ b.flags = mbbf[m.svlc(mbb_table, mbb_bits, "mbb")];
+ DPIC =>
+ if (!m.sgetn(1))
+ m.fmterr("mbd flags");
+ b.flags = MB_I;
+ * =>
+ m.fmterr("ptype");
+ }
+ if (b.flags & MB_Q)
+ q = m.sgetn(5);
+ b.qscale = q;
+ if (b.flags & MB_MF) {
+ i = m.svlc(motion_table, motion_bits, "mhfc");
+ b.mhfc = i;
+ if (i != 0 && p.forwfc != 0)
+ b.mhfr = m.sgetn(p.forwfc);
+ i = m.svlc(motion_table, motion_bits, "mvfc");
+ b.mvfc = i;
+ if (i != 0 && p.forwfc != 0)
+ b.mvfr = m.sgetn(p.forwfc);
+ }
+ if (b.flags & MB_MB) {
+ i = m.svlc(motion_table, motion_bits, "mhbc");
+ b.mhbc = i;
+ if (i != 0 && p.backfc != 0)
+ b.mhbr = m.sgetn(p.backfc);
+ i = m.svlc(motion_table, motion_bits, "mvbc");
+ b.mvbc = i;
+ if (i != 0 && p.backfc != 0)
+ b.mvbr = m.sgetn(p.backfc);
+ }
+ if (b.flags & MB_I)
+ i = 16r3F;
+ else if (b.flags & MB_P)
+ i = m.svlc(cbp_table, cbp_bits, "cbp");
+ else
+ i = 0;
+ b.pcode = i;
+ if (i != 0) {
+ b.rls = array[6] of array of Pair;
+ for (j := 0; j < 6; j++) {
+ if (i & patcode[j]) {
+ rl: list of Pair;
+ R, L: int;
+ if (b.flags & MB_I) {
+ if (j < 4)
+ L = m.svlc(ydc_table, ydc_bits, "ydc");
+ else
+ L = m.svlc(cdc_table, cdc_bits, "cdc");
+ if (L != 0)
+ L = m.sdiffn(L);
+ rl = (0, L) :: nil;
+ } else
+ rl = m.sdct(rl0f_table, "rl0f") :: nil;
+ if (p.ptype != DPIC) {
+ for (;;) {
+ (R, L) = m.sdct(rl0n_table, "rl0n");
+ if (R == EOB)
+ break;
+ rl = (R, L) :: rl;
+ }
+ }
+ mn := len rl;
+ ma := array[mn] of Pair;
+ while (--mn >= 0) {
+ ma[mn] = hd rl;
+ rl = tl rl;
+ }
+ b.rls[j] = ma;
+ }
+ }
+ }
+ l = b :: l;
+ }
+ p.addr = x;
+ if (l == nil)
+ m.fmterr("macroblock");
+ n := len l;
+ a := array[n] of ref MacroBlock;
+ while (--n >= 0) {
+ a[n] = hd l;
+ l = tl l;
+ }
+ s.blocks = a;
+ return s;
+}
+
+Mpegi.cpn(m: self ref Mpegi, fd: ref Sys->FD, n: int)
+{
+ for (;;) {
+ r := m.size - m.index;
+ if (r >= n) {
+ if (sys->write(fd, m.buff[m.index:], n) < 0)
+ raisex(X_WRITE);
+ m.index += n;
+ return;
+ }
+ if (sys->write(fd, m.buff[m.index:], r) < 0)
+ raisex(X_WRITE);
+ m.fill();
+ n -= r;
+ }
+}
+
+Mpegi.fill(m: self ref Mpegi)
+{
+ n := sys->read(m.fd, m.buff, MBSZ);
+ if (n < 0) {
+ m.error = sys->sprint("%r");
+ raisex(X_READ);
+ }
+ if (n == 0)
+ raisex(X_EOF);
+ m.seek += n;
+ m.index = 0;
+ m.size = n;
+}
+
+Mpegi.tell(m: self ref Mpegi): int
+{
+ return m.seek - m.size + m.index;
+}
+
+Mpegi.skipn(m: self ref Mpegi, n: int)
+{
+ for (;;) {
+ r := m.size - m.index;
+ if (r >= n) {
+ m.index += n;
+ return;
+ }
+ n -= r;
+ m.fill();
+ }
+}
+
+Mpegi.getb(m: self ref Mpegi): int
+{
+ if (m.index == m.size)
+ m.fill();
+ return int m.buff[m.index++];
+}
+
+Mpegi.getw(m: self ref Mpegi): int
+{
+ t := m.getb();
+ return (t << 8) | m.getb();
+}
+
+Mpegi.get22(m: self ref Mpegi, s: string): int
+{
+ u := m.getb();
+ if ((u & 16r80) == 0)
+ m.fmterr(s + " mark 0");
+ v := m.getb();
+ w := m.getb();
+ if ((w & 1) == 0)
+ m.fmterr(s + " mark 1");
+ return ((u & 16r7F) << 15) | (v << 7) | (w >> 1);
+}
+
+Mpegi.getsc(m: self ref Mpegi): int
+{
+ if (m.getb() != 0 || m.getb() != 0)
+ m.fmterr("start code 0s");
+ while ((c := m.getb()) == 0)
+ ;
+ if (c != 1)
+ m.fmterr("start code 1");
+ return 16r100 | m.getb();
+}
+
+Mpegi.nextsc(m: self ref Mpegi): int
+{
+ if (m.looked) {
+ m.looked = 0;
+ return m.value;
+ } else
+ return m.getsc();
+}
+
+Mpegi.peeksc(m: self ref Mpegi): int
+{
+ if (!m.looked) {
+ m.value = m.getsc();
+ m.looked = 1;
+ }
+ return m.value;
+}
+
+Mpegi.xnextsc(m: self ref Mpegi, x: int)
+{
+ c := m.nextsc();
+ if (c != x)
+ m.fmterr(sys->sprint("startcode %x, got %x", x, c));
+}
+
+Mpegi.sfill(m: self ref Mpegi)
+{
+ r := m.sresid;
+ if (r < 0) {
+ m.fill();
+ m.slim = m.size;
+ } else if (r > 0) {
+ m.fill();
+ if (r <= m.size) {
+ m.slim = r;
+ m.sresid = 0;
+ } else {
+ m.slim = m.size;
+ m.sresid = r - m.size;
+ }
+ } else
+ m.sseek();
+}
+
+bits := array[] of {
+ 0,
+ 16r1, 16r3, 16r7, 16rF,
+ 16r1F, 16r3F, 16r7F, 16rFF,
+ 16r1FF, 16r3FF, 16r7FF, 16rFFF,
+ 16r1FFF, 16r3FFF, 16r7FFF, 16rFFFF,
+ 16r1FFFF, 16r3FFFF, 16r7FFFF, 16rFFFFF,
+ 16r1FFFFF, 16r3FFFFF, 16r7FFFFF, 16rFFFFFF,
+ 16r1FFFFFF, 16r3FFFFFF, 16r7FFFFFF, 16rFFFFFFF,
+ 16r1FFFFFFF, 16r3FFFFFFF, 16r7FFFFFFF, int 16rFFFFFFFF,
+};
+
+sign := array[] of {
+ 0,
+ 16r1, 16r2, 16r4, 16r8,
+ 16r10, 16r20, 16r40, 16r80,
+};
+
+Mpegi.sgetn(m: self ref Mpegi, n: int): int
+{
+ b := m.sbits;
+ v := m.svalue;
+ if (b < n) {
+ do {
+ v = (v << 8) | m.sgetb();
+ b += 8;
+ } while (b < n);
+ m.svalue = v;
+ }
+ b -= n;
+ m.sbits = b;
+ return (v >> b) & bits[n];
+}
+
+Mpegi.sdiffn(m: self ref Mpegi, n: int): int
+{
+ i := m.sgetn(n);
+ if (i & sign[n])
+ return i;
+ else
+ return i - bits[n];
+}
+
+Mpegi.speekn(m: self ref Mpegi, n: int): int
+{
+ b := m.sbits;
+ v := m.svalue;
+ if (b < n) {
+ do {
+ v = (v << 8) | m.sgetb();
+ b += 8;
+ } while (b < n);
+ m.sbits = b;
+ m.svalue = v;
+ }
+ return (v >> (b - n)) & bits[n];
+}
+
+Mpegi.sgetb(m: self ref Mpegi): int
+{
+ if (m.index == m.slim)
+ m.sfill();
+ return int m.buff[m.index++];
+}
+
+Mpegi.smarker(m: self ref Mpegi)
+{
+ if (!m.sgetn(1))
+ m.fmterr("marker");
+}
+
+Mpegi.sgetsc(m: self ref Mpegi): int
+{
+ b := m.sbits;
+ if (b >= 8) {
+ if (b >= 16) {
+ if (b >= 24) {
+ case m.svalue & 16rFFFFFF {
+ 0 =>
+ break;
+ 1 =>
+ m.sbits = 0;
+ return 16r100 | m.sgetb();
+ * =>
+ m.fmterr("start code 0s - 3");
+ }
+ } else if ((m.svalue & 16rFFFF) != 0)
+ m.fmterr("start code 0s - 2");
+ } else if ((m.svalue & 16rFF) != 0 || m.sgetb() != 0)
+ m.fmterr("start code 0s - 1");
+ } else if (m.sgetb() != 0 || m.sgetb() != 0)
+ m.fmterr("start code 0s");
+ m.sbits = 0;
+ while ((c := m.sgetb()) == 0)
+ ;
+ if (c != 1)
+ m.fmterr("start code 1");
+ return 16r100 | m.sgetb();
+}
+
+Mpegi.snextsc(m: self ref Mpegi): int
+{
+ if (m.looked) {
+ m.looked = 0;
+ return m.value;
+ } else
+ return m.sgetsc();
+}
+
+Mpegi.speeksc(m: self ref Mpegi): int
+{
+ if (!m.looked) {
+ m.value = m.sgetsc();
+ m.looked = 1;
+ }
+ return m.value;
+}
+
+Mpegi.sseeksc(m: self ref Mpegi)
+{
+ n := 0;
+ for (;;) {
+ case m.sgetb() {
+ 0 =>
+ n++;
+ 1 =>
+ if (n >= 2) {
+ m.value = 16r100 | m.sgetb();
+ m.looked = 1;
+ return;
+ }
+ n = 0;
+ * =>
+ n = 0;
+ }
+ }
+}
+
+Mpegi.svlc(m: self ref Mpegi, a: array of Pair, n: int, s: string): int
+{
+ (b, v) := a[m.speekn(n)];
+ if (v == UNDEF)
+ m.fmterr(s + " vlc");
+ m.sbits -= b;
+ return v;
+}
+
+Mpegi.sdct(m: self ref Mpegi, a: array of Triple, s: string): Pair
+{
+ (b, l, r) := a[m.speekn(rl0f_bits)];
+ m.sbits -= b;
+ if (r < 0) {
+ case r {
+ EOB =>
+ break;
+ ESC =>
+ r = m.sgetn(6);
+ l = m.sgetn(8);
+ if (l == 0) {
+ l = m.sgetn(8);
+ if (l < 128)
+ m.fmterr(s + " esc +7");
+ } else if (l == 128) {
+ l = m.sgetn(8) - 256;
+ if (l > -128)
+ m.fmterr(s + " esc -7");
+ } else
+ l = (l << 24) >> 24;
+ C0 =>
+ (b, l, r) = c0_table[m.speekn(c0_bits)];
+ if (r == UNDEF)
+ m.fmterr(s + " c0 vlc");
+ m.sbits -= b;
+ * =>
+ r = C1 - r;
+ (l, r) = c_tables[r][m.sgetn(c_bits[r])];
+ }
+ }
+ return (r, l);
+}
+
+Mpegi.fmterr(m: self ref Mpegi, s: string)
+{
+ m.error = s;
+ raisex(X_FORMAT);
+}
diff --git a/appl/wm/mpeg/mpegio.m b/appl/wm/mpeg/mpegio.m
new file mode 100644
index 00000000..378db8aa
--- /dev/null
+++ b/appl/wm/mpeg/mpegio.m
@@ -0,0 +1,218 @@
+#
+# MPEG ISO 11172 IO module.
+#
+Mpegio: module
+{
+ PATH: con "/dis/mpeg/mpegio.dis";
+
+ MBSZ: con Sys->ATOMICIO;
+
+ PICTURE_SC: con 16r100;
+ SLICE1_SC: con 16r101;
+ SLICEN_SC: con 16r1AF;
+ USER_SC: con 16r1B2;
+ SEQUENCE_SC: con 16r1B3;
+ EXTENSION_SC: con 16r1B5;
+ SEQUENCE_EC: con 16r1B7;
+ GROUP_SC: con 16r1B8;
+ STREAM_EC: con 16r1B9;
+ PACK_SC: con 16r1BA;
+ SYSHD_SC: con 16r1BB;
+ STREAM_BASE: con 16r1BC;
+ PRIVSTREAM2: con 16r1BF;
+ AUDIO_STR0: con 16r1C0;
+ VIDEO_STR0: con 16r1E0;
+
+ MEXCEPT: con "mpeg: ";
+ X_FORMAT: con "fmt error";
+ X_READ: con "read error";
+ X_WRITE: con "write error";
+ X_EOF: con "premature eof";
+
+ UNDEF: con 100;
+
+ CONSTRAINED, CLOSED, BROKEN: con 1 << iota;
+ FPFV, FPBV, GSTART: con 1 << iota;
+
+ IPIC: con 1;
+ PPIC: con 2;
+ BPIC: con 3;
+ DPIC: con 4;
+
+ ptypes: con "0IPBD";
+
+ MB_Q, MB_MF, MB_MB, MB_P, MB_I: con 1 << iota;
+
+ Stream: adt
+ {
+ id: byte;
+ scale: byte;
+ bound: int;
+ fd: ref Sys->FD;
+ };
+
+ Picture: adt
+ {
+ seek: int;
+ eos: int;
+ temporal: int;
+ ptype: int;
+ vbvdelay: int;
+ flags: int;
+ forwfc: int;
+ backfc: int;
+ slices: array of ref Slice;
+ addr: int;
+ };
+
+ Slice: adt
+ {
+ blocks: array of ref MacroBlock;
+ };
+
+ MacroBlock: adt
+ {
+ flags: int;
+ qscale: int;
+ mhfc, mhfr, mvfc, mvfr: int;
+ mhbc, mhbr, mvbc, mvbr: int;
+ pcode: int;
+ rls: array of array of Pair;
+ addr: int;
+ };
+
+ YCbCr: adt
+ {
+ Y, Cb, Cr: array of byte;
+ };
+
+ Pair: type (int, int);
+ Triple: type (int, int, int);
+
+ Mpegi: adt
+ {
+ fd: ref Sys->FD;
+ name: string;
+ error: string;
+ looked: int;
+ value: int;
+ # info
+ width: int;
+ height: int;
+ aspect: int;
+ frames: int;
+ rate: int;
+ vbv: int;
+ flags: int;
+ intra: array of int;
+ nintra: array of int;
+ smpte: int;
+ # real buffer
+ seek: int;
+ index: int;
+ size: int;
+ buff: array of byte;
+ # stream buffer
+ sid: int; # stream id
+ slim: int; # stream limit <= size
+ sresid: int; # stream residual (-1 entire file)
+ sbits: int; # bits remaining
+ svalue: int; # current value
+
+ packt0: int;
+ packt1: int;
+ packmr: int;
+ syssz: int;
+ boundmr: int;
+ syspar: int;
+ nstream: int;
+ streams: array of Stream;
+ log: ref Sys->FD;
+
+ startsys: fn(m: self ref Mpegi);
+ packhdr: fn(m: self ref Mpegi);
+ syshdr: fn(m: self ref Mpegi);
+ packetcp: fn(m: self ref Mpegi): int;
+ getfd: fn(m: self ref Mpegi, c: int): ref Sys->FD;
+ stamps: fn(m: self ref Mpegi): int;
+
+ streaminit: fn(m: self ref Mpegi, c: int);
+ inittables: fn();
+ sseek: fn(m: self ref Mpegi);
+ seqhdr: fn(m: self ref Mpegi);
+ grphdr: fn(m: self ref Mpegi);
+ getquant: fn(m: self ref Mpegi): array of int;
+ getpicture: fn(m: self ref Mpegi, detail: int): ref Picture;
+ picture: fn(m: self ref Mpegi, detail: int): ref Picture;
+ detail: fn(m: self ref Mpegi, p: ref Picture);
+ skipdetail: fn(m: self ref Mpegi);
+ slice: fn(m: self ref Mpegi, p: ref Picture): ref Slice;
+
+ cpn: fn(m: self ref Mpegi, fd: ref Sys->FD, n: int);
+ fill: fn(m: self ref Mpegi);
+ tell: fn(m: self ref Mpegi): int;
+ skipn: fn(m: self ref Mpegi, n: int);
+ getb: fn(m: self ref Mpegi): int;
+ getw: fn(m: self ref Mpegi): int;
+ get22: fn(m: self ref Mpegi, s: string): int;
+ getsc: fn(m: self ref Mpegi): int;
+ nextsc: fn(m: self ref Mpegi): int;
+ peeksc: fn(m: self ref Mpegi): int;
+ xnextsc: fn(m: self ref Mpegi, code: int);
+
+ sfill: fn(m: self ref Mpegi);
+ sgetb: fn(m: self ref Mpegi): int;
+ sgetn: fn(m: self ref Mpegi, n: int): int;
+ sdiffn: fn(m: self ref Mpegi, n: int): int;
+ sdct: fn(m: self ref Mpegi, a: array of Triple, s: string): Pair;
+ speekn: fn(m: self ref Mpegi, n: int): int;
+ smarker: fn(m: self ref Mpegi);
+ sgetsc: fn(m: self ref Mpegi): int;
+ snextsc: fn(m: self ref Mpegi): int;
+ speeksc: fn(m: self ref Mpegi): int;
+ sseeksc: fn(m: self ref Mpegi);
+ svlc: fn(m: self ref Mpegi, a: array of Pair, n: int, s: string): int;
+
+ fmterr: fn(m: self ref Mpegi, s: string);
+ };
+
+ init: fn();
+ prepare: fn(fd: ref Sys->FD, name: string): ref Mpegi;
+ raisex: fn(s: string);
+};
+
+Mpegd: module
+{
+ PATH: con "/dis/mpeg/decode.dis";
+ PATH4: con "/dis/mpeg/decode4.dis";
+
+ init: fn(m: ref Mpegio->Mpegi);
+ Idecode: fn(p: ref Mpegio->Picture): ref Mpegio->YCbCr;
+ Pdecode: fn(p: ref Mpegio->Picture): ref Mpegio->YCbCr;
+ Bdecode: fn(p: ref Mpegio->Picture): ref Mpegio->YCbCr;
+ Bdecode2: fn(p: ref Mpegio->Picture, f0, f1: ref Mpegio->YCbCr): ref Mpegio->YCbCr;
+};
+
+IDCT: module
+{
+ FPATH: con "/dis/mpeg/fltidct.dis"; # based on rob's jpeg
+ RPATH: con "/dis/mpeg/refidct.dis"; # reference (full idct)
+ SPATH: con "/dis/mpeg/scidct.dis"; # scaled integer implementation
+ XPATH: con "/dis/mpeg/fixidct.dis"; # nasty fixed point
+ PATH: con SPATH;
+
+ init: fn();
+ idct: fn(block: array of int);
+};
+
+Remap: module
+{
+ PATH: con "/dis/mpeg/remap.dis";
+ PATH1: con "/dis/mpeg/remap1.dis";
+ PATH2: con "/dis/mpeg/remap2.dis";
+ PATH4: con "/dis/mpeg/remap4.dis";
+ PATH24: con "/dis/mpeg/remap24.dis";
+
+ init: fn(m: ref Mpegio->Mpegi);
+ remap: fn(p: ref Mpegio->YCbCr): array of byte;
+};
diff --git a/appl/wm/mpeg/refidct.b b/appl/wm/mpeg/refidct.b
new file mode 100644
index 00000000..e02ab1f7
--- /dev/null
+++ b/appl/wm/mpeg/refidct.b
@@ -0,0 +1,58 @@
+implement IDCT;
+
+include "sys.m";
+include "math.m";
+include "mpegio.m";
+
+sys: Sys;
+math: Math;
+
+#
+# Reference IDCT. Full expanded 2-d IDCT.
+#
+
+coeff: array of array of real;
+
+init()
+{
+ sys = load Sys Sys->PATH;
+ math = load Math Math->PATH;
+ if (math == nil) {
+ sys->fprint(sys->fildes(2), "could not load %s: %r\n", Math->PATH);
+ exit;
+ }
+ init_idct();
+}
+
+init_idct()
+{
+ coeff = array[8] of array of real;
+ for (f := 0; f < 8; f++) {
+ coeff[f] = array[8] of real;
+ s := 0.5;
+ if (f == 0)
+ s = math->sqrt(0.125);
+ a := real f * (Math->Pi / 8.0);
+ for (t := 0; t < 8; t++)
+ coeff[f][t] = s * math->cos(a * (real t + 0.5));
+ }
+}
+
+idct(block: array of int)
+{
+ tmp := array[64] of real;
+ for (i := 0; i < 8; i++)
+ for (j := 0; j < 8; j++) {
+ p := 0.0;
+ for (k := 0; k < 8; k++)
+ p += coeff[k][j] * real block[8 * i + k];
+ tmp[8 * i + j] = p;
+ }
+ for (j = 0; j < 8; j++)
+ for (i = 0; i < 8; i++) {
+ p := 0.0;
+ for (k := 0; k < 8; k++)
+ p += coeff[k][i] * tmp[8 * k + j];
+ block[8 * i + j] = int p;
+ }
+}
diff --git a/appl/wm/mpeg/remap.b b/appl/wm/mpeg/remap.b
new file mode 100644
index 00000000..4432048c
--- /dev/null
+++ b/appl/wm/mpeg/remap.b
@@ -0,0 +1,128 @@
+implement Remap;
+
+include "sys.m";
+include "mpegio.m";
+
+Mpegi, YCbCr: import Mpegio;
+
+CLOFF: con 255;
+
+width, height, w2, h2: int;
+out: array of byte;
+ered, egrn, eblu: array of int;
+b0r1, b1, r0: array of int;
+clamp := array[CLOFF + 256 + CLOFF] of int;
+clamp16 := array[CLOFF + 256 + CLOFF] of int;
+
+init(m: ref Mpegi)
+{
+ width = m.width;
+ height = m.height;
+ w2 = width >> 1;
+ h2 = height >> 1;
+ out = array[width * height] of byte;
+ b0r1 = array[w2] of int;
+ b1 = array[w2] of int;
+ r0 = array[w2] of int;
+ ered = array[width + 1] of int;
+ egrn = array[width + 1] of int;
+ eblu = array[width + 1] of int;
+ for (i := 0; i < CLOFF; i++) {
+ clamp[i] = 0;
+ clamp16[i] = 0;
+ }
+ for (i = 0; i < 256; i++) {
+ clamp[i + CLOFF] = i;
+ clamp16[i + CLOFF] = i >> 4;
+ }
+ for (i = CLOFF + 256; i < CLOFF + 256 + CLOFF; i++) {
+ clamp[i] = 255;
+ clamp16[i] = 255 >> 4;
+ }
+}
+
+include "closest.m";
+include "rgbvmap.m";
+
+# rgb(y, cb, cr: int): (int, int, int)
+# {
+# Y := real y;
+# Cb := real (cb - 128);
+# Cr := real (cr - 128);
+# r := int (Y+1.402*Cr);
+# g := int (Y-0.34414*Cb-0.71414*Cr);
+# b := int (Y+1.772*Cb);
+# return (r, g, b);
+# }
+
+B: con 16;
+M: con (1 << B);
+B0: con int (-0.34414 * real M);
+B1: con int (1.772 * real M);
+R0: con int (1.402 * real M);
+R1: con int (-0.71414 * real M);
+
+remap(p: ref Mpegio->YCbCr): array of byte
+{
+ Y := p.Y;
+ Cb := p.Cb;
+ Cr := p.Cr;
+ for (e := 0; e <= width; e++)
+ ered[e] = 0;
+ egrn[0:] = ered[0:];
+ eblu[0:] = ered[0:];
+ m := 0;
+ n := 0;
+ for (i := 0; i < h2; i++) {
+ for (j := 0; j < w2; j++) {
+ cb := int Cb[m] - 128;
+ cr := int Cr[m] - 128;
+ b0r1[j] = B0 * cb + R1 * cr;
+ b1[j] = B1 * cb;
+ r0[j] = R0 * cr;
+ m++;
+ }
+ j = 2;
+ do {
+ ex := 0;
+ er := 0;
+ eg := 0;
+ eb := 0;
+ for (k := 0; k < w2; k++) {
+ l := 2;
+ do {
+ y := int Y[n] << B;
+ r := clamp[((y + r0[k]) >> B) + CLOFF] + ered[ex];
+ g := clamp[((y + b0r1[k]) >> B) + CLOFF] + egrn[ex];
+ b := clamp[((y + b1[k]) >> B) + CLOFF] + eblu[ex];
+ rc := clamp16[r + CLOFF];
+ gc := clamp16[g + CLOFF];
+ bc := clamp16[b + CLOFF];
+ col := int closest[bc + 16 * (gc + 16 * rc)];
+ out[n++] = byte col;
+
+ col *= 3;
+ r -= int rgbvmap[col + 0];
+ t := (3 * r) >> 4;
+ ered[ex] = t + er;
+ ered[ex + 1] += t;
+ er = r - 3 * t;
+
+ g -= int rgbvmap[col + 1];
+ t = (3 * g) >> 4;
+ egrn[ex] = t + eg;
+ egrn[ex + 1] += t;
+ eg = g - 3 * t;
+
+ b -= int rgbvmap[col + 2];
+ t = (3 * b) >> 4;
+ eblu[ex] = t + eb;
+ eblu[ex + 1] += t;
+ eb = b - 3 * t;
+ ex++;
+ } while (--l > 0);
+ }
+ } while (--j > 0);
+ }
+ return out;
+}
diff --git a/appl/wm/mpeg/remap1.b b/appl/wm/mpeg/remap1.b
new file mode 100644
index 00000000..09ad4646
--- /dev/null
+++ b/appl/wm/mpeg/remap1.b
@@ -0,0 +1,116 @@
+implement Remap;
+
+include "sys.m";
+include "mpegio.m";
+
+Mpegi, YCbCr: import Mpegio;
+
+CLOFF: con 511;
+
+width, height, w8: int;
+out: array of byte;
+elum: array of int;
+clamp2 := array[CLOFF + 256 + CLOFF] of int;
+
+init(m: ref Mpegi)
+{
+ width = m.width;
+ height = m.height;
+ w8 = width >> 3;
+ out = array[w8 * height] of byte;
+ elum = array[width + 1] of int;
+ for (i := 0; i < CLOFF; i++)
+ clamp2[i] = 0;
+ for (i = 0; i < 256; i++)
+ clamp2[i + CLOFF] = i >> 7;
+ for (i = CLOFF + 256; i < CLOFF + 256 + CLOFF; i++)
+ clamp2[i] = 255 >> 7;
+}
+
+remap(p: ref Mpegio->YCbCr): array of byte
+{
+ Y := p.Y;
+ for (e := 0; e <= width; e++)
+ elum[e] = 0;
+ m := 0;
+ n := 0;
+ for (i := 0; i < height; i++) {
+ el := 0;
+ ex := 0;
+ for (k := 0; k < w8; k++) {
+ y := (256 - int Y[n++]) + elum[ex];
+ l := clamp2[y + CLOFF] << 7;
+ b := l;
+ y -= l;
+ t := (3 * y) >> 4;
+ elum[ex] = t + el;
+ elum[ex + 1] += t;
+ el = y - 3 * t;
+ ex++;
+ y = (256 - int Y[n++]) + elum[ex];
+ l = clamp2[y + CLOFF];
+ b |= l << 6;
+ y -= l << 7;
+ t = (3 * y) >> 4;
+ elum[ex] = t + el;
+ elum[ex + 1] += t;
+ el = y - 3 * t;
+ ex++;
+ y = (256 - int Y[n++]) + elum[ex];
+ l = clamp2[y + CLOFF];
+ b |= l << 5;
+ y -= l << 7;
+ t = (3 * y) >> 4;
+ elum[ex] = t + el;
+ elum[ex + 1] += t;
+ el = y - 3 * t;
+ ex++;
+ y = (256 - int Y[n++]) + elum[ex];
+ l = clamp2[y + CLOFF];
+ b |= l << 4;
+ y -= l << 7;
+ t = (3 * y) >> 4;
+ elum[ex] = t + el;
+ elum[ex + 1] += t;
+ el = y - 3 * t;
+ ex++;
+ y = (256 - int Y[n++]) + elum[ex];
+ l = clamp2[y + CLOFF];
+ b |= l << 3;
+ y -= l << 7;
+ t = (3 * y) >> 4;
+ elum[ex] = t + el;
+ elum[ex + 1] += t;
+ el = y - 3 * t;
+ ex++;
+ y = (256 - int Y[n++]) + elum[ex];
+ l = clamp2[y + CLOFF];
+ b |= l << 2;
+ y -= l << 7;
+ t = (3 * y) >> 4;
+ elum[ex] = t + el;
+ elum[ex + 1] += t;
+ el = y - 3 * t;
+ ex++;
+ y = (256 - int Y[n++]) + elum[ex];
+ l = clamp2[y + CLOFF];
+ b |= l << 1;
+ y -= l << 7;
+ t = (3 * y) >> 4;
+ elum[ex] = t + el;
+ elum[ex + 1] += t;
+ el = y - 3 * t;
+ ex++;
+ y = (256 - int Y[n++]) + elum[ex];
+ l = clamp2[y + CLOFF];
+ out[m++] = byte (b | l);
+ y -= l << 7;
+ t = (3 * y) >> 4;
+ elum[ex] = t + el;
+ elum[ex + 1] += t;
+ el = y - 3 * t;
+ ex++;
+ }
+ }
+ return out;
+}
diff --git a/appl/wm/mpeg/remap2.b b/appl/wm/mpeg/remap2.b
new file mode 100644
index 00000000..5bae164f
--- /dev/null
+++ b/appl/wm/mpeg/remap2.b
@@ -0,0 +1,80 @@
+implement Remap;
+
+include "sys.m";
+include "mpegio.m";
+
+Mpegi, YCbCr: import Mpegio;
+
+CLOFF: con 255;
+
+width, height, w4: int;
+out: array of byte;
+elum: array of int;
+clamp4 := array[CLOFF + 256 + CLOFF] of int;
+
+init(m: ref Mpegi)
+{
+ width = m.width;
+ height = m.height;
+ w4 = width >> 2;
+ out = array[w4 * height] of byte;
+ elum = array[width + 1] of int;
+ for (i := 0; i < CLOFF; i++)
+ clamp4[i] = 0;
+ for (i = 0; i < 256; i++)
+ clamp4[i + CLOFF] = i >> 6;
+ for (i = CLOFF + 256; i < CLOFF + 256 + CLOFF; i++)
+ clamp4[i] = 255 >> 6;
+}
+
+remap(p: ref Mpegio->YCbCr): array of byte
+{
+ Y := p.Y;
+ for (e := 0; e <= width; e++)
+ elum[e] = 0;
+ m := 0;
+ n := 0;
+ for (i := 0; i < height; i++) {
+ el := 0;
+ ex := 0;
+ for (k := 0; k < w4; k++) {
+ y := (256 - int Y[n++]) + elum[ex];
+ l := clamp4[y + CLOFF] << 6;
+ b := l;
+ y -= l;
+ t := (3 * y) >> 4;
+ elum[ex] = t + el;
+ elum[ex + 1] += t;
+ el = y - 3 * t;
+ ex++;
+ y = (256 - int Y[n++]) + elum[ex];
+ l = clamp4[y + CLOFF];
+ b |= l << 4;
+ y -= l << 6;
+ t = (3 * y) >> 4;
+ elum[ex] = t + el;
+ elum[ex + 1] += t;
+ el = y - 3 * t;
+ ex++;
+ y = (256 - int Y[n++]) + elum[ex];
+ l = clamp4[y + CLOFF];
+ b |= l << 2;
+ y -= l << 6;
+ t = (3 * y) >> 4;
+ elum[ex] = t + el;
+ elum[ex + 1] += t;
+ el = y - 3 * t;
+ ex++;
+ y = (256 - int Y[n++]) + elum[ex];
+ l = clamp4[y + CLOFF];
+ out[m++] = byte (b | l);
+ y -= l << 6;
+ t = (3 * y) >> 4;
+ elum[ex] = t + el;
+ elum[ex + 1] += t;
+ el = y - 3 * t;
+ ex++;
+ }
+ }
+ return out;
+}
diff --git a/appl/wm/mpeg/remap24.b b/appl/wm/mpeg/remap24.b
new file mode 100644
index 00000000..23e80815
--- /dev/null
+++ b/appl/wm/mpeg/remap24.b
@@ -0,0 +1,82 @@
+implement Remap;
+
+include "sys.m";
+include "mpegio.m";
+
+Mpegi, YCbCr: import Mpegio;
+
+CLOFF: con 255;
+
+width, height, w2, h2: int;
+out: array of byte;
+b0r1, b1, r0: array of int;
+clamp := array[CLOFF + 256 + CLOFF] of byte;
+
+init(m: ref Mpegi)
+{
+ width = m.width;
+ height = m.height;
+ w2 = width >> 1;
+ h2 = height >> 1;
+ out = array[3 * width * height] of byte;
+ b0r1 = array[w2] of int;
+ b1 = array[w2] of int;
+ r0 = array[w2] of int;
+ for (i := 0; i < CLOFF; i++)
+ clamp[i] = byte 0;
+ for (i = 0; i < 256; i++)
+ clamp[i + CLOFF] = byte i;
+ for (i = CLOFF + 256; i < CLOFF + 256 + CLOFF; i++)
+ clamp[i] = byte 255;
+}
+
+# rgb(y, cb, cr: int): (int, int, int)
+# {
+# Y := real y;
+# Cb := real (cb - 128);
+# Cr := real (cr - 128);
+# r := int (Y+1.402*Cr);
+# g := int (Y-0.34414*Cb-0.71414*Cr);
+# b := int (Y+1.772*Cb);
+# return (r, g, b);
+# }
+
+B: con 16;
+M: con (1 << B);
+B0: con int (-0.34414 * real M);
+B1: con int (1.772 * real M);
+R0: con int (1.402 * real M);
+R1: con int (-0.71414 * real M);
+
+remap(p: ref Mpegio->YCbCr): array of byte
+{
+ Y := p.Y;
+ Cb := p.Cb;
+ Cr := p.Cr;
+ m := 0;
+ n := 0;
+ x := 0;
+ for (i := 0; i < h2; i++) {
+ for (j := 0; j < w2; j++) {
+ cb := int Cb[m] - 128;
+ cr := int Cr[m] - 128;
+ b0r1[j] = B0 * cb + R1 * cr;
+ b1[j] = B1 * cb;
+ r0[j] = R0 * cr;
+ m++;
+ }
+ j = 2;
+ do {
+ for (k := 0; k < w2; k++) {
+ l := 2;
+ do {
+ y := int Y[n++] << B;
+ out[x++] = clamp[((y + r0[k]) >> B) + CLOFF];
+ out[x++] = clamp[((y + b0r1[k]) >> B) + CLOFF];
+ out[x++] = clamp[((y + b1[k]) >> B) + CLOFF];
+ } while (--l > 0);
+ }
+ } while (--j > 0);
+ }
+ return out;
+}
diff --git a/appl/wm/mpeg/remap4.b b/appl/wm/mpeg/remap4.b
new file mode 100644
index 00000000..20566fc2
--- /dev/null
+++ b/appl/wm/mpeg/remap4.b
@@ -0,0 +1,62 @@
+implement Remap;
+
+include "sys.m";
+include "mpegio.m";
+
+Mpegi, YCbCr: import Mpegio;
+
+CLOFF: con 255;
+
+width, height, w2: int;
+out: array of byte;
+elum: array of int;
+clamp16 := array[CLOFF + 256 + CLOFF] of int;
+
+init(m: ref Mpegi)
+{
+ width = m.width;
+ height = m.height;
+ w2 = width >> 1;
+ out = array[w2 * height] of byte;
+ elum = array[width + 1] of int;
+ for (i := 0; i < CLOFF; i++)
+ clamp16[i] = 0;
+ for (i = 0; i < 256; i++)
+ clamp16[i + CLOFF] = i >> 4;
+ for (i = CLOFF + 256; i < CLOFF + 256 + CLOFF; i++)
+ clamp16[i] = 255 >> 4;
+}
+
+remap(p: ref Mpegio->YCbCr): array of byte
+{
+ Y := p.Y;
+ for (e := 0; e <= width; e++)
+ elum[e] = 0;
+ m := 0;
+ n := 0;
+ for (i := 0; i < height; i++) {
+ el := 0;
+ ex := 0;
+ for (k := 0; k < w2; k++) {
+ y := (256 - int Y[n++]) + elum[ex];
+ l := clamp16[y + CLOFF] << 4;
+ b := l;
+ y -= l;
+ t := (3 * y) >> 4;
+ elum[ex] = t + el;
+ elum[ex + 1] += t;
+ el = y - 3 * t;
+ ex++;
+ y = (256 - int Y[n++]) + elum[ex];
+ l = clamp16[y + CLOFF];
+ out[m++] = byte (b | l);
+ y -= l << 4;
+ t = (3 * y) >> 4;
+ elum[ex] = t + el;
+ elum[ex + 1] += t;
+ el = y - 3 * t;
+ ex++;
+ }
+ }
+ return out;
+}
diff --git a/appl/wm/mpeg/remap8.b b/appl/wm/mpeg/remap8.b
new file mode 100644
index 00000000..957b72aa
--- /dev/null
+++ b/appl/wm/mpeg/remap8.b
@@ -0,0 +1,84 @@
+implement Remap;
+
+include "sys.m";
+include "mpegio.m";
+
+Mpegi, YCbCr: import Mpegio;
+
+CLOFF: con 255;
+
+width, height, w2, h2: int;
+out: array of byte;
+b0r1, b1, r0: array of int;
+clamp16 := array[CLOFF + 256 + CLOFF] of int;
+
+init(m: ref Mpegi)
+{
+ width = m.width;
+ height = m.height;
+ w2 = width >> 1;
+ h2 = height >> 1;
+ out = array[width * height] of byte;
+ b0r1 = array[w2] of int;
+ b1 = array[w2] of int;
+ r0 = array[w2] of int;
+ for (i := 0; i < CLOFF; i++)
+ clamp16[i] = 0;
+ for (i = 0; i < 256; i++)
+ clamp16[i + CLOFF] = i >> 4;
+ for (i = CLOFF + 256; i < CLOFF + 256 + CLOFF; i++)
+ clamp16[i] = 255 >> 4;
+}
+
+include "closest.m";
+
+# rgb(y, cb, cr: int): (int, int, int)
+# {
+# Y := real y;
+# Cb := real (cb - 128);
+# Cr := real (cr - 128);
+# r := int (Y+1.402*Cr);
+# g := int (Y-0.34414*Cb-0.71414*Cr);
+# b := int (Y+1.772*Cb);
+# return (r, g, b);
+# }
+
+B: con 16;
+M: con (1 << B);
+B0: con int (-0.34414 * real M);
+B1: con int (1.772 * real M);
+R0: con int (1.402 * real M);
+R1: con int (-0.71414 * real M);
+
+remap(p: ref Mpegio->YCbCr): array of byte
+{
+ Y := p.Y;
+ Cb := p.Cb;
+ Cr := p.Cr;
+ m := 0;
+ n := 0;
+ for (i := 0; i < h2; i++) {
+ for (j := 0; j < w2; j++) {
+ cb := int Cb[m] - 128;
+ cr := int Cr[m] - 128;
+ b0r1[j] = B0 * cb + R1 * cr;
+ b1[j] = B1 * cb;
+ r0[j] = R0 * cr;
+ m++;
+ }
+ j = 2;
+ do {
+ for (k := 0; k < w2; k++) {
+ l := 2;
+ do {
+ y := int Y[n] << B;
+ rc := clamp16[((y + r0[k]) >> B) + CLOFF];
+ gc := clamp16[((y + b0r1[k]) >> B) + CLOFF];
+ bc := clamp16[((y + b1[k]) >> B) + CLOFF];
+ out[n++] = closest[bc + 16 * (gc + 16 * rc)];
+ } while (--l > 0);
+ }
+ } while (--j > 0);
+ }
+ return out;
+}
diff --git a/appl/wm/mpeg/rgbvmap.m b/appl/wm/mpeg/rgbvmap.m
new file mode 100644
index 00000000..d53b1b04
--- /dev/null
+++ b/appl/wm/mpeg/rgbvmap.m
@@ -0,0 +1,258 @@
+rgbvmap := array[3*256] of {
+ byte 255,byte 255,byte 255,
+ byte 255,byte 255,byte 170,
+ byte 255,byte 255,byte 85,
+ byte 255,byte 255,byte 0,
+ byte 255,byte 170,byte 255,
+ byte 255,byte 170,byte 170,
+ byte 255,byte 170,byte 85,
+ byte 255,byte 170,byte 0,
+ byte 255,byte 85,byte 255,
+ byte 255,byte 85,byte 170,
+ byte 255,byte 85,byte 85,
+ byte 255,byte 85,byte 0,
+ byte 255,byte 0,byte 255,
+ byte 255,byte 0,byte 170,
+ byte 255,byte 0,byte 85,
+ byte 255,byte 0,byte 0,
+ byte 238,byte 0,byte 0,
+ byte 238,byte 238,byte 238,
+ byte 238,byte 238,byte 158,
+ byte 238,byte 238,byte 79,
+ byte 238,byte 238,byte 0,
+ byte 238,byte 158,byte 238,
+ byte 238,byte 158,byte 158,
+ byte 238,byte 158,byte 79,
+ byte 238,byte 158,byte 0,
+ byte 238,byte 79,byte 238,
+ byte 238,byte 79,byte 158,
+ byte 238,byte 79,byte 79,
+ byte 238,byte 79,byte 0,
+ byte 238,byte 0,byte 238,
+ byte 238,byte 0,byte 158,
+ byte 238,byte 0,byte 79,
+ byte 221,byte 0,byte 73,
+ byte 221,byte 0,byte 0,
+ byte 221,byte 221,byte 221,
+ byte 221,byte 221,byte 147,
+ byte 221,byte 221,byte 73,
+ byte 221,byte 221,byte 0,
+ byte 221,byte 147,byte 221,
+ byte 221,byte 147,byte 147,
+ byte 221,byte 147,byte 73,
+ byte 221,byte 147,byte 0,
+ byte 221,byte 73,byte 221,
+ byte 221,byte 73,byte 147,
+ byte 221,byte 73,byte 73,
+ byte 221,byte 73,byte 0,
+ byte 221,byte 0,byte 221,
+ byte 221,byte 0,byte 147,
+ byte 204,byte 0,byte 136,
+ byte 204,byte 0,byte 68,
+ byte 204,byte 0,byte 0,
+ byte 204,byte 204,byte 204,
+ byte 204,byte 204,byte 136,
+ byte 204,byte 204,byte 68,
+ byte 204,byte 204,byte 0,
+ byte 204,byte 136,byte 204,
+ byte 204,byte 136,byte 136,
+ byte 204,byte 136,byte 68,
+ byte 204,byte 136,byte 0,
+ byte 204,byte 68,byte 204,
+ byte 204,byte 68,byte 136,
+ byte 204,byte 68,byte 68,
+ byte 204,byte 68,byte 0,
+ byte 204,byte 0,byte 204,
+ byte 170,byte 255,byte 170,
+ byte 170,byte 255,byte 85,
+ byte 170,byte 255,byte 0,
+ byte 170,byte 170,byte 255,
+ byte 187,byte 187,byte 187,
+ byte 187,byte 187,byte 93,
+ byte 187,byte 187,byte 0,
+ byte 170,byte 85,byte 255,
+ byte 187,byte 93,byte 187,
+ byte 187,byte 93,byte 93,
+ byte 187,byte 93,byte 0,
+ byte 170,byte 0,byte 255,
+ byte 187,byte 0,byte 187,
+ byte 187,byte 0,byte 93,
+ byte 187,byte 0,byte 0,
+ byte 170,byte 255,byte 255,
+ byte 158,byte 238,byte 238,
+ byte 158,byte 238,byte 158,
+ byte 158,byte 238,byte 79,
+ byte 158,byte 238,byte 0,
+ byte 158,byte 158,byte 238,
+ byte 170,byte 170,byte 170,
+ byte 170,byte 170,byte 85,
+ byte 170,byte 170,byte 0,
+ byte 158,byte 79,byte 238,
+ byte 170,byte 85,byte 170,
+ byte 170,byte 85,byte 85,
+ byte 170,byte 85,byte 0,
+ byte 158,byte 0,byte 238,
+ byte 170,byte 0,byte 170,
+ byte 170,byte 0,byte 85,
+ byte 170,byte 0,byte 0,
+ byte 153,byte 0,byte 0,
+ byte 147,byte 221,byte 221,
+ byte 147,byte 221,byte 147,
+ byte 147,byte 221,byte 73,
+ byte 147,byte 221,byte 0,
+ byte 147,byte 147,byte 221,
+ byte 153,byte 153,byte 153,
+ byte 153,byte 153,byte 76,
+ byte 153,byte 153,byte 0,
+ byte 147,byte 73,byte 221,
+ byte 153,byte 76,byte 153,
+ byte 153,byte 76,byte 76,
+ byte 153,byte 76,byte 0,
+ byte 147,byte 0,byte 221,
+ byte 153,byte 0,byte 153,
+ byte 153,byte 0,byte 76,
+ byte 136,byte 0,byte 68,
+ byte 136,byte 0,byte 0,
+ byte 136,byte 204,byte 204,
+ byte 136,byte 204,byte 136,
+ byte 136,byte 204,byte 68,
+ byte 136,byte 204,byte 0,
+ byte 136,byte 136,byte 204,
+ byte 136,byte 136,byte 136,
+ byte 136,byte 136,byte 68,
+ byte 136,byte 136,byte 0,
+ byte 136,byte 68,byte 204,
+ byte 136,byte 68,byte 136,
+ byte 136,byte 68,byte 68,
+ byte 136,byte 68,byte 0,
+ byte 136,byte 0,byte 204,
+ byte 136,byte 0,byte 136,
+ byte 85,byte 255,byte 85,
+ byte 85,byte 255,byte 0,
+ byte 85,byte 170,byte 255,
+ byte 93,byte 187,byte 187,
+ byte 93,byte 187,byte 93,
+ byte 93,byte 187,byte 0,
+ byte 85,byte 85,byte 255,
+ byte 93,byte 93,byte 187,
+ byte 119,byte 119,byte 119,
+ byte 119,byte 119,byte 0,
+ byte 85,byte 0,byte 255,
+ byte 93,byte 0,byte 187,
+ byte 119,byte 0,byte 119,
+ byte 119,byte 0,byte 0,
+ byte 85,byte 255,byte 255,
+ byte 85,byte 255,byte 170,
+ byte 79,byte 238,byte 158,
+ byte 79,byte 238,byte 79,
+ byte 79,byte 238,byte 0,
+ byte 79,byte 158,byte 238,
+ byte 85,byte 170,byte 170,
+ byte 85,byte 170,byte 85,
+ byte 85,byte 170,byte 0,
+ byte 79,byte 79,byte 238,
+ byte 85,byte 85,byte 170,
+ byte 102,byte 102,byte 102,
+ byte 102,byte 102,byte 0,
+ byte 79,byte 0,byte 238,
+ byte 85,byte 0,byte 170,
+ byte 102,byte 0,byte 102,
+ byte 102,byte 0,byte 0,
+ byte 79,byte 238,byte 238,
+ byte 73,byte 221,byte 221,
+ byte 73,byte 221,byte 147,
+ byte 73,byte 221,byte 73,
+ byte 73,byte 221,byte 0,
+ byte 73,byte 147,byte 221,
+ byte 76,byte 153,byte 153,
+ byte 76,byte 153,byte 76,
+ byte 76,byte 153,byte 0,
+ byte 73,byte 73,byte 221,
+ byte 76,byte 76,byte 153,
+ byte 85,byte 85,byte 85,
+ byte 85,byte 85,byte 0,
+ byte 73,byte 0,byte 221,
+ byte 76,byte 0,byte 153,
+ byte 85,byte 0,byte 85,
+ byte 85,byte 0,byte 0,
+ byte 68,byte 0,byte 0,
+ byte 68,byte 204,byte 204,
+ byte 68,byte 204,byte 136,
+ byte 68,byte 204,byte 68,
+ byte 68,byte 204,byte 0,
+ byte 68,byte 136,byte 204,
+ byte 68,byte 136,byte 136,
+ byte 68,byte 136,byte 68,
+ byte 68,byte 136,byte 0,
+ byte 68,byte 68,byte 204,
+ byte 68,byte 68,byte 136,
+ byte 68,byte 68,byte 68,
+ byte 68,byte 68,byte 0,
+ byte 68,byte 0,byte 204,
+ byte 68,byte 0,byte 136,
+ byte 68,byte 0,byte 68,
+ byte 0,byte 255,byte 0,
+ byte 0,byte 170,byte 255,
+ byte 0,byte 187,byte 187,
+ byte 0,byte 187,byte 93,
+ byte 0,byte 187,byte 0,
+ byte 0,byte 85,byte 255,
+ byte 0,byte 93,byte 187,
+ byte 0,byte 119,byte 119,
+ byte 0,byte 119,byte 0,
+ byte 0,byte 0,byte 255,
+ byte 0,byte 0,byte 187,
+ byte 0,byte 0,byte 119,
+ byte 51,byte 51,byte 51,
+ byte 0,byte 255,byte 255,
+ byte 0,byte 255,byte 170,
+ byte 0,byte 255,byte 85,
+ byte 0,byte 238,byte 79,
+ byte 0,byte 238,byte 0,
+ byte 0,byte 158,byte 238,
+ byte 0,byte 170,byte 170,
+ byte 0,byte 170,byte 85,
+ byte 0,byte 170,byte 0,
+ byte 0,byte 79,byte 238,
+ byte 0,byte 85,byte 170,
+ byte 0,byte 102,byte 102,
+ byte 0,byte 102,byte 0,
+ byte 0,byte 0,byte 238,
+ byte 0,byte 0,byte 170,
+ byte 0,byte 0,byte 102,
+ byte 34,byte 34,byte 34,
+ byte 0,byte 238,byte 238,
+ byte 0,byte 238,byte 158,
+ byte 0,byte 221,byte 147,
+ byte 0,byte 221,byte 73,
+ byte 0,byte 221,byte 0,
+ byte 0,byte 147,byte 221,
+ byte 0,byte 153,byte 153,
+ byte 0,byte 153,byte 76,
+ byte 0,byte 153,byte 0,
+ byte 0,byte 73,byte 221,
+ byte 0,byte 76,byte 153,
+ byte 0,byte 85,byte 85,
+ byte 0,byte 85,byte 0,
+ byte 0,byte 0,byte 221,
+ byte 0,byte 0,byte 153,
+ byte 0,byte 0,byte 85,
+ byte 17,byte 17,byte 17,
+ byte 0,byte 221,byte 221,
+ byte 0,byte 204,byte 204,
+ byte 0,byte 204,byte 136,
+ byte 0,byte 204,byte 68,
+ byte 0,byte 204,byte 0,
+ byte 0,byte 136,byte 204,
+ byte 0,byte 136,byte 136,
+ byte 0,byte 136,byte 68,
+ byte 0,byte 136,byte 0,
+ byte 0,byte 68,byte 204,
+ byte 0,byte 68,byte 136,
+ byte 0,byte 68,byte 68,
+ byte 0,byte 68,byte 0,
+ byte 0,byte 0,byte 204,
+ byte 0,byte 0,byte 136,
+ byte 0,byte 0,byte 68,
+ byte 0,byte 0,byte 0,
+};
diff --git a/appl/wm/mpeg/rl0f.tab b/appl/wm/mpeg/rl0f.tab
new file mode 100644
index 00000000..38da34ee
--- /dev/null
+++ b/appl/wm/mpeg/rl0f.tab
@@ -0,0 +1,517 @@
+# vlc -c rl0f
+rl0f_size: con 512;
+rl0f_bits: con 9;
+rl0f_table:= array[] of {
+ (9, 0,C0),
+ (9, 0,C1),
+ (9, 0,C2),
+ (9, 0,C3),
+ (9, 0,C4),
+ (9, 0,C5),
+ (9, 0,C6),
+ (9, 0,C7),
+ (6, 0,ESC),
+ (6, 0,ESC),
+ (6, 0,ESC),
+ (6, 0,ESC),
+ (6, 0,ESC),
+ (6, 0,ESC),
+ (6, 0,ESC),
+ (6, 0,ESC),
+ (8, 2,2),
+ (8, 2,2),
+ (8, -2,2),
+ (8, -2,2),
+ (8, 1,9),
+ (8, 1,9),
+ (8, -1,9),
+ (8, -1,9),
+ (8, 4,0),
+ (8, 4,0),
+ (8, -4,0),
+ (8, -4,0),
+ (8, 1,8),
+ (8, 1,8),
+ (8, -1,8),
+ (8, -1,8),
+ (7, 1,7),
+ (7, 1,7),
+ (7, 1,7),
+ (7, 1,7),
+ (7, -1,7),
+ (7, -1,7),
+ (7, -1,7),
+ (7, -1,7),
+ (7, 1,6),
+ (7, 1,6),
+ (7, 1,6),
+ (7, 1,6),
+ (7, -1,6),
+ (7, -1,6),
+ (7, -1,6),
+ (7, -1,6),
+ (7, 2,1),
+ (7, 2,1),
+ (7, 2,1),
+ (7, 2,1),
+ (7, -2,1),
+ (7, -2,1),
+ (7, -2,1),
+ (7, -2,1),
+ (7, 1,5),
+ (7, 1,5),
+ (7, 1,5),
+ (7, 1,5),
+ (7, -1,5),
+ (7, -1,5),
+ (7, -1,5),
+ (7, -1,5),
+ (9, 1,13),
+ (9, -1,13),
+ (9, 6,0),
+ (9, -6,0),
+ (9, 1,12),
+ (9, -1,12),
+ (9, 1,11),
+ (9, -1,11),
+ (9, 2,3),
+ (9, -2,3),
+ (9, 3,1),
+ (9, -3,1),
+ (9, 5,0),
+ (9, -5,0),
+ (9, 1,10),
+ (9, -1,10),
+ (6, 3,0),
+ (6, 3,0),
+ (6, 3,0),
+ (6, 3,0),
+ (6, 3,0),
+ (6, 3,0),
+ (6, 3,0),
+ (6, 3,0),
+ (6, -3,0),
+ (6, -3,0),
+ (6, -3,0),
+ (6, -3,0),
+ (6, -3,0),
+ (6, -3,0),
+ (6, -3,0),
+ (6, -3,0),
+ (6, 1,4),
+ (6, 1,4),
+ (6, 1,4),
+ (6, 1,4),
+ (6, 1,4),
+ (6, 1,4),
+ (6, 1,4),
+ (6, 1,4),
+ (6, -1,4),
+ (6, -1,4),
+ (6, -1,4),
+ (6, -1,4),
+ (6, -1,4),
+ (6, -1,4),
+ (6, -1,4),
+ (6, -1,4),
+ (6, 1,3),
+ (6, 1,3),
+ (6, 1,3),
+ (6, 1,3),
+ (6, 1,3),
+ (6, 1,3),
+ (6, 1,3),
+ (6, 1,3),
+ (6, -1,3),
+ (6, -1,3),
+ (6, -1,3),
+ (6, -1,3),
+ (6, -1,3),
+ (6, -1,3),
+ (6, -1,3),
+ (6, -1,3),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, 1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+ (2, -1,0),
+};
diff --git a/appl/wm/mpeg/rl0f.vlc b/appl/wm/mpeg/rl0f.vlc
new file mode 100644
index 00000000..1f9eb590
--- /dev/null
+++ b/appl/wm/mpeg/rl0f.vlc
@@ -0,0 +1,34 @@
+# Run/Level First base (first 9 bits)
+# vlc -c rl0f < rl0f.vlc > rl0f.tab
+1s 1,0
+0100s 2,0
+00101s 3,0
+0000110s 4,0
+00100110s 5,0
+00100001s 6,0
+000000101 0,C5
+000000011 0,C3
+000000010 0,C2
+000000001 0,C1
+000000000 0,C0
+011s 1,1
+000110s 2,1
+00100101s 3,1
+000000110 0,C6
+0101s 1,2
+0000100s 2,2
+00111s 1,3
+00100100s 2,3
+00110s 1,4
+000000111 0,C7
+000111s 1,5
+000000100 0,C4
+000101s 1,6
+000100s 1,7
+0000111s 1,8
+0000101s 1,9
+00100111s 1,10
+00100011s 1,11
+00100010s 1,12
+00100000s 1,13
+000001 0,ESC
diff --git a/appl/wm/mpeg/rl0n.tab b/appl/wm/mpeg/rl0n.tab
new file mode 100644
index 00000000..2820979b
--- /dev/null
+++ b/appl/wm/mpeg/rl0n.tab
@@ -0,0 +1,517 @@
+# vlc -c rl0n
+rl0n_size: con 512;
+rl0n_bits: con 9;
+rl0n_table:= array[] of {
+ (9, 0,C0),
+ (9, 0,C1),
+ (9, 0,C2),
+ (9, 0,C3),
+ (9, 0,C4),
+ (9, 0,C5),
+ (9, 0,C6),
+ (9, 0,C7),
+ (6, 0,ESC),
+ (6, 0,ESC),
+ (6, 0,ESC),
+ (6, 0,ESC),
+ (6, 0,ESC),
+ (6, 0,ESC),
+ (6, 0,ESC),
+ (6, 0,ESC),
+ (8, 2,2),
+ (8, 2,2),
+ (8, -2,2),
+ (8, -2,2),
+ (8, 1,9),
+ (8, 1,9),
+ (8, -1,9),
+ (8, -1,9),
+ (8, 4,0),
+ (8, 4,0),
+ (8, -4,0),
+ (8, -4,0),
+ (8, 1,8),
+ (8, 1,8),
+ (8, -1,8),
+ (8, -1,8),
+ (7, 1,7),
+ (7, 1,7),
+ (7, 1,7),
+ (7, 1,7),
+ (7, -1,7),
+ (7, -1,7),
+ (7, -1,7),
+ (7, -1,7),
+ (7, 1,6),
+ (7, 1,6),
+ (7, 1,6),
+ (7, 1,6),
+ (7, -1,6),
+ (7, -1,6),
+ (7, -1,6),
+ (7, -1,6),
+ (7, 2,1),
+ (7, 2,1),
+ (7, 2,1),
+ (7, 2,1),
+ (7, -2,1),
+ (7, -2,1),
+ (7, -2,1),
+ (7, -2,1),
+ (7, 1,5),
+ (7, 1,5),
+ (7, 1,5),
+ (7, 1,5),
+ (7, -1,5),
+ (7, -1,5),
+ (7, -1,5),
+ (7, -1,5),
+ (9, 1,13),
+ (9, -1,13),
+ (9, 6,0),
+ (9, -6,0),
+ (9, 1,12),
+ (9, -1,12),
+ (9, 1,11),
+ (9, -1,11),
+ (9, 2,3),
+ (9, -2,3),
+ (9, 3,1),
+ (9, -3,1),
+ (9, 5,0),
+ (9, -5,0),
+ (9, 1,10),
+ (9, -1,10),
+ (6, 3,0),
+ (6, 3,0),
+ (6, 3,0),
+ (6, 3,0),
+ (6, 3,0),
+ (6, 3,0),
+ (6, 3,0),
+ (6, 3,0),
+ (6, -3,0),
+ (6, -3,0),
+ (6, -3,0),
+ (6, -3,0),
+ (6, -3,0),
+ (6, -3,0),
+ (6, -3,0),
+ (6, -3,0),
+ (6, 1,4),
+ (6, 1,4),
+ (6, 1,4),
+ (6, 1,4),
+ (6, 1,4),
+ (6, 1,4),
+ (6, 1,4),
+ (6, 1,4),
+ (6, -1,4),
+ (6, -1,4),
+ (6, -1,4),
+ (6, -1,4),
+ (6, -1,4),
+ (6, -1,4),
+ (6, -1,4),
+ (6, -1,4),
+ (6, 1,3),
+ (6, 1,3),
+ (6, 1,3),
+ (6, 1,3),
+ (6, 1,3),
+ (6, 1,3),
+ (6, 1,3),
+ (6, 1,3),
+ (6, -1,3),
+ (6, -1,3),
+ (6, -1,3),
+ (6, -1,3),
+ (6, -1,3),
+ (6, -1,3),
+ (6, -1,3),
+ (6, -1,3),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, 2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, -2,0),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, 1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (5, -1,2),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, 1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (4, -1,1),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (2, 0,EOB),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, 1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+ (3, -1,0),
+};
diff --git a/appl/wm/mpeg/rl0n.vlc b/appl/wm/mpeg/rl0n.vlc
new file mode 100644
index 00000000..8cd5da23
--- /dev/null
+++ b/appl/wm/mpeg/rl0n.vlc
@@ -0,0 +1,35 @@
+# Run/Level Next base (first 9 bits)
+# vlc -c rl0n < rl0n.vlc > rl0n.tab
+11s 1,0
+0100s 2,0
+00101s 3,0
+0000110s 4,0
+00100110s 5,0
+00100001s 6,0
+000000101 0,C5
+000000011 0,C3
+000000010 0,C2
+000000001 0,C1
+000000000 0,C0
+011s 1,1
+000110s 2,1
+00100101s 3,1
+000000110 0,C6
+0101s 1,2
+0000100s 2,2
+00111s 1,3
+00100100s 2,3
+00110s 1,4
+000000111 0,C7
+000111s 1,5
+000000100 0,C4
+000101s 1,6
+000100s 1,7
+0000111s 1,8
+0000101s 1,9
+00100111s 1,10
+00100011s 1,11
+00100010s 1,12
+00100000s 1,13
+10 0,EOB
+000001 0,ESC
diff --git a/appl/wm/mpeg/scidct.b b/appl/wm/mpeg/scidct.b
new file mode 100644
index 00000000..f59c2217
--- /dev/null
+++ b/appl/wm/mpeg/scidct.b
@@ -0,0 +1,160 @@
+implement IDCT;
+
+include "sys.m";
+include "mpegio.m";
+
+init()
+{
+}
+
+# Scaled integer implementation.
+# inverse two dimensional DCT, Chen-Wang algorithm
+# (IEEE ASSP-32, pp. 803-816, Aug. 1984)
+# 32-bit integer arithmetic (8 bit coefficients)
+# 11 mults, 29 adds per DCT
+#
+# coefficients extended to 12 bit for IEEE1180-1990
+# compliance
+
+W1: con 2841; # 2048*sqrt(2)*cos(1*pi/16)
+W2: con 2676; # 2048*sqrt(2)*cos(2*pi/16)
+W3: con 2408; # 2048*sqrt(2)*cos(3*pi/16)
+W5: con 1609; # 2048*sqrt(2)*cos(5*pi/16)
+W6: con 1108; # 2048*sqrt(2)*cos(6*pi/16)
+W7: con 565; # 2048*sqrt(2)*cos(7*pi/16)
+
+W1pW7: con 3406; # W1+W7
+W1mW7: con 2276; # W1-W7
+W3pW5: con 4017; # W3+W5
+W3mW5: con 799; # W3-W5
+W2pW6: con 3784; # W2+W6
+W2mW6: con 1567; # W2-W6
+
+R2: con 181; # 256/sqrt(2)
+
+idct(b: array of int)
+{
+ # transform horizontally
+ for(y:=0; y<8; y++){
+ eighty := y<<3;
+ # if all non-DC components are zero, just propagate the DC term
+ if(b[eighty+1]==0)
+ if(b[eighty+2]==0 && b[eighty+3]==0)
+ if(b[eighty+4]==0 && b[eighty+5]==0)
+ if(b[eighty+6]==0 && b[eighty+7]==0){
+ v := b[eighty]<<3;
+ b[eighty+0] = v;
+ b[eighty+1] = v;
+ b[eighty+2] = v;
+ b[eighty+3] = v;
+ b[eighty+4] = v;
+ b[eighty+5] = v;
+ b[eighty+6] = v;
+ b[eighty+7] = v;
+ continue;
+ }
+ # prescale
+ x0 := (b[eighty+0]<<11)+128;
+ x1 := b[eighty+4]<<11;
+ x2 := b[eighty+6];
+ x3 := b[eighty+2];
+ x4 := b[eighty+1];
+ x5 := b[eighty+7];
+ x6 := b[eighty+5];
+ x7 := b[eighty+3];
+ # first stage
+ x8 := W7*(x4+x5);
+ x4 = x8 + W1mW7*x4;
+ x5 = x8 - W1pW7*x5;
+ x8 = W3*(x6+x7);
+ x6 = x8 - W3mW5*x6;
+ x7 = x8 - W3pW5*x7;
+ # second stage
+ x8 = x0 + x1;
+ x0 -= x1;
+ x1 = W6*(x3+x2);
+ x2 = x1 - W2pW6*x2;
+ x3 = x1 + W2mW6*x3;
+ x1 = x4 + x6;
+ x4 -= x6;
+ x6 = x5 + x7;
+ x5 -= x7;
+ # third stage
+ x7 = x8 + x3;
+ x8 -= x3;
+ x3 = x0 + x2;
+ x0 -= x2;
+ x2 = (R2*(x4+x5)+128)>>8;
+ x4 = (R2*(x4-x5)+128)>>8;
+ # fourth stage
+ b[eighty+0] = (x7+x1)>>8;
+ b[eighty+1] = (x3+x2)>>8;
+ b[eighty+2] = (x0+x4)>>8;
+ b[eighty+3] = (x8+x6)>>8;
+ b[eighty+4] = (x8-x6)>>8;
+ b[eighty+5] = (x0-x4)>>8;
+ b[eighty+6] = (x3-x2)>>8;
+ b[eighty+7] = (x7-x1)>>8;
+ }
+ # transform vertically
+ for(x:=0; x<8; x++){
+ # if all non-DC components are zero, just propagate the DC term
+ if(b[x+8*1]==0)
+ if(b[x+8*2]==0 && b[x+8*3]==0)
+ if(b[x+8*4]==0 && b[x+8*5]==0)
+ if(b[x+8*6]==0 && b[x+8*7]==0){
+ v := (b[x+8*0]+32)>>6;
+ b[x+8*0] = v;
+ b[x+8*1] = v;
+ b[x+8*2] = v;
+ b[x+8*3] = v;
+ b[x+8*4] = v;
+ b[x+8*5] = v;
+ b[x+8*6] = v;
+ b[x+8*7] = v;
+ continue;
+ }
+ # prescale
+ x0 := (b[x+8*0]<<8)+8192;
+ x1 := b[x+8*4]<<8;
+ x2 := b[x+8*6];
+ x3 := b[x+8*2];
+ x4 := b[x+8*1];
+ x5 := b[x+8*7];
+ x6 := b[x+8*5];
+ x7 := b[x+8*3];
+ # first stage
+ x8 := W7*(x4+x5) + 4;
+ x4 = (x8+W1mW7*x4)>>3;
+ x5 = (x8-W1pW7*x5)>>3;
+ x8 = W3*(x6+x7) + 4;
+ x6 = (x8-W3mW5*x6)>>3;
+ x7 = (x8-W3pW5*x7)>>3;
+ # second stage
+ x8 = x0 + x1;
+ x0 -= x1;
+ x1 = W6*(x3+x2) + 4;
+ x2 = (x1-W2pW6*x2)>>3;
+ x3 = (x1+W2mW6*x3)>>3;
+ x1 = x4 + x6;
+ x4 -= x6;
+ x6 = x5 + x7;
+ x5 -= x7;
+ # third stage
+ x7 = x8 + x3;
+ x8 -= x3;
+ x3 = x0 + x2;
+ x0 -= x2;
+ x2 = (R2*(x4+x5)+128)>>8;
+ x4 = (R2*(x4-x5)+128)>>8;
+ # fourth stage
+ b[x+8*0] = (x7+x1)>>14;
+ b[x+8*1] = (x3+x2)>>14;
+ b[x+8*2] = (x0+x4)>>14;
+ b[x+8*3] = (x8+x6)>>14;
+ b[x+8*4] = (x8-x6)>>14;
+ b[x+8*5] = (x0-x4)>>14;
+ b[x+8*6] = (x3-x2)>>14;
+ b[x+8*7] = (x7-x1)>>14;
+ }
+}
diff --git a/appl/wm/mpeg/vlc.b b/appl/wm/mpeg/vlc.b
new file mode 100644
index 00000000..96e136e9
--- /dev/null
+++ b/appl/wm/mpeg/vlc.b
@@ -0,0 +1,213 @@
+implement Vlc;
+
+include "sys.m";
+include "draw.m";
+include "bufio.m";
+
+#
+# Construct expanded Vlc (variable length code) tables
+# from vlc description files.
+#
+
+sys: Sys;
+bufio: Bufio;
+Iobuf: import bufio;
+
+stderr: ref Sys->FD;
+
+sv: adt
+{
+ s: int;
+ v: string;
+};
+
+s2list: type list of (string, string);
+bits, size: int;
+table: array of sv;
+prog: string;
+undef: string = "UNDEF";
+xfixed: int = 0;
+complete: int = 0;
+paren: int = 0;
+
+Vlc: module
+{
+ init: fn(nil: ref Draw->Context, args: list of string);
+};
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ sargs := makestr(args);
+ prog = hd args;
+ args = tl args;
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil) {
+ sys->fprint(stderr, "%s: could not load %s: %r\n", prog, Bufio->PATH);
+ return;
+ }
+ inf := bufio->fopen(sys->fildes(0), Bufio->OREAD);
+ if (inf == nil) {
+ sys->fprint(stderr, "%s: fopen stdin failed: %r\n", prog);
+ return;
+ }
+ while (args != nil && len hd args && (a := hd args)[0] == '-') {
+ flag:
+ for (x := 1; x < len a; x++) {
+ case a[x] {
+ 'c' =>
+ complete = 1;
+ 'f' =>
+ xfixed = 1;
+ 'p' =>
+ paren = 1;
+ 'u' =>
+ if (++x == len a) {
+ args = tl args;
+ if (args == nil)
+ usage();
+ undef = hd args;
+ } else
+ undef = a[x:];
+ break flag;
+ * =>
+ usage();
+ return;
+ }
+ }
+ args = tl args;
+ }
+ vlc := "vlc";
+ if (args != nil) {
+ if (tl args != nil) {
+ usage();
+ return;
+ }
+ vlc = hd args;
+ }
+ il: s2list;
+ while ((l := inf.gets('\n')) != nil) {
+ if (l[0] == '#')
+ continue;
+ (n, t) := sys->tokenize(l, " \t\n");
+ if (n != 2) {
+ sys->fprint(stderr, "%s: bad input: %s", prog, l);
+ return;
+ }
+ il = (hd t, hd tl t) :: il;
+ }
+ (n, nl) := expand(il);
+ bits = n;
+ size = 1 << bits;
+ table = array[size] of sv;
+ maketable(nl);
+ printtable(vlc, sargs);
+}
+
+usage()
+{
+ sys->fprint(stderr, "usage: %s [-cfp] [-u undef] [stem]\n", prog);
+}
+
+makestr(l: list of string): string
+{
+ s, t: string;
+ while (l != nil) {
+ s = s + t + hd l;
+ t = " ";
+ l = tl l;
+ }
+ return s;
+}
+
+expand(l: s2list): (int, s2list)
+{
+ nl: s2list;
+ max := 0;
+ while (l != nil) {
+ (bs, val) := hd l;
+ n := len bs;
+ if (n > max)
+ max = n;
+ if (bs[n - 1] == 's') {
+ t := bs[:n - 1];
+ nl = (t + "0", val) :: (t + "1", "-" + val) :: nl;
+ } else
+ nl = (bs, val) :: nl;
+ l = tl l;
+ }
+ return (max, nl);
+}
+
+maketable(l: s2list)
+{
+ while (l != nil) {
+ (bs, val) := hd l;
+ z := len bs;
+ if (xfixed && z != bits)
+ error(sys->sprint("string %s too short", bs));
+ s := bits - z;
+ v := value(bs) << s;
+ n := 1 << s;
+ for (i := 0; i < n; i++) {
+ if (table[v].v != nil)
+ error(sys->sprint("repeat match for %x", v));
+ table[v] = (z, val);
+ v++;
+ }
+ l = tl l;
+ }
+}
+
+value(s: string): int
+{
+ n := len s;
+ v := 0;
+ for (i := 0; i < n; i++) {
+ case s[i] {
+ '0' =>
+ v <<= 1;
+ '1'=>
+ v = (v << 1) | 1;
+ * =>
+ error("bad bitstream: " + s);
+ }
+ }
+ return v;
+}
+
+printtable(s, a: string)
+{
+ sys->print("# %s\n", a);
+ sys->print("%s_size: con %d;\n", s, size);
+ sys->print("%s_bits: con %d;\n", s, bits);
+ sys->print("%s_table:= array[] of {\n", s);
+ for (i := 0; i < size; i++) {
+ if (table[i].v != nil) {
+ if (xfixed) {
+ if (paren)
+ sys->print("\t(%s),\n", table[i].v);
+ else
+ sys->print("\t%s,\n", table[i].v);
+ } else
+ sys->print("\t(%d, %s),\n", table[i].s, table[i].v);
+ } else if (!complete) {
+ if (xfixed) {
+ if (paren)
+ sys->print("\t(%s),\n", undef);
+ else
+ sys->print("\t%s,\n", undef);
+ } else
+ sys->print("\t(0, %s),\n", undef);
+ } else
+ error(sys->sprint("no match for %x", i));
+ }
+ sys->print("};\n");
+}
+
+error(s: string)
+{
+ sys->fprint(stderr, "%s: error: %s\n", prog, s);
+ exit;
+}
diff --git a/appl/wm/mpeg/ydc.tab b/appl/wm/mpeg/ydc.tab
new file mode 100644
index 00000000..f2dff729
--- /dev/null
+++ b/appl/wm/mpeg/ydc.tab
@@ -0,0 +1,133 @@
+# vlc ydc
+ydc_size: con 128;
+ydc_bits: con 7;
+ydc_table:= array[] of {
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 1),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (2, 2),
+ (3, 0),
+ (3, 0),
+ (3, 0),
+ (3, 0),
+ (3, 0),
+ (3, 0),
+ (3, 0),
+ (3, 0),
+ (3, 0),
+ (3, 0),
+ (3, 0),
+ (3, 0),
+ (3, 0),
+ (3, 0),
+ (3, 0),
+ (3, 0),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 3),
+ (3, 4),
+ (3, 4),
+ (3, 4),
+ (3, 4),
+ (3, 4),
+ (3, 4),
+ (3, 4),
+ (3, 4),
+ (3, 4),
+ (3, 4),
+ (3, 4),
+ (3, 4),
+ (3, 4),
+ (3, 4),
+ (3, 4),
+ (3, 4),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (4, 5),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (5, 6),
+ (6, 7),
+ (6, 7),
+ (7, 8),
+ (0, UNDEF),
+};
diff --git a/appl/wm/mpeg/ydc.vlc b/appl/wm/mpeg/ydc.vlc
new file mode 100644
index 00000000..660ce582
--- /dev/null
+++ b/appl/wm/mpeg/ydc.vlc
@@ -0,0 +1,11 @@
+# Luminance DC
+# vlc ydc < ydc.vlc > ydc.tab
+100 0
+00 1
+01 2
+101 3
+110 4
+1110 5
+11110 6
+111110 7
+1111110 8
diff --git a/appl/wm/mprof.b b/appl/wm/mprof.b
new file mode 100644
index 00000000..625f085d
--- /dev/null
+++ b/appl/wm/mprof.b
@@ -0,0 +1,314 @@
+implement Wmmprof;
+
+include "sys.m";
+ sys: Sys;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "draw.m";
+ draw: Draw;
+include "tk.m";
+ tk: Tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+include "arg.m";
+ arg: Arg;
+include "profile.m";
+
+Prof: module{
+ init0: fn(ctxt: ref Draw->Context, argv: list of string): Profile->Prof;
+};
+
+prof: Prof;
+
+Wmmprof: module{
+ init: fn(ctxt: ref Draw->Context, argl: list of string);
+};
+
+usage(s: string)
+{
+ sys->fprint(sys->fildes(2), "wm/mprof: %s\n", s);
+ sys->fprint(sys->fildes(2), "usage: wm/mprof [-e] [-m modname]... cmd [arg ... ]");
+ exit;
+}
+
+TXTBEGIN: con 3;
+
+init(ctxt: ref Draw->Context, argl: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ arg = load Arg Arg->PATH;
+
+ if(ctxt == nil)
+ fatal("wm not running");
+ sys->pctl(Sys->NEWPGRP, nil);
+
+ arg->init(argl);
+ while((o := arg->opt()) != 0){
+ case(o){
+ '1' or '2' or '3' or 'e' => ;
+ 'm' =>
+ if(arg->arg() == nil)
+ usage("missing module/file");
+ * =>
+ usage(sys->sprint("unknown option -%c", o));
+ }
+ }
+
+ stats := execprof(ctxt, argl);
+ if(stats.mods == nil)
+ exit;
+
+ tkclient->init();
+ (win, wmc) := tkclient->toplevel(ctxt, nil, hd argl, Tkclient->Resize|Tkclient->Hide);
+ tkc := chan of string;
+ tk->namechan(win, tkc, "tkc");
+ for(i := 0; i < len wincfg; i++)
+ cmd(win, wincfg[i]);
+ tkclient->onscreen(win, nil);
+ tkclient->startinput(win, "kbd"::"ptr"::nil);
+ createmenu(win, stats);
+ curc := 0;
+ cura := newprint(win, stats, curc);
+
+ for(;;){
+ alt{
+ c := <-win.ctxt.kbd =>
+ tk->keyboard(win, c);
+ c := <-win.ctxt.ptr =>
+ tk->pointer(win, *c);
+ c := <-win.ctxt.ctl or
+ c = <-win.wreq or
+ c = <-wmc =>
+ tkclient->wmctl(win, c);
+ c := <- tkc =>
+ (nil, toks) := sys->tokenize(c, " ");
+ case(hd toks){
+ "b" =>
+ if(curc > 0)
+ cura = newprint(win, stats, --curc);
+ "f" =>
+ if(curc < len stats.mods - 1)
+ cura = newprint(win, stats, ++curc);
+ "s" =>
+ if(cura != nil)
+ scroll(win, cura);
+ "m" =>
+ x := cmd(win, ".f cget actx");
+ y := cmd(win, ".f cget acty");
+ cmd(win, ".f.menu post " + x + " " + y);
+ * =>
+ curc = int hd toks;
+ cura = newprint(win, stats, curc);
+ }
+ }
+ }
+}
+
+execprof(ctxt: ref Draw->Context, argl: list of string): Profile->Prof
+{
+ {
+ prof = load Prof "/dis/mprof.dis";
+ if(prof == nil)
+ fatal("cannot load profiler");
+ return prof->init0(ctxt, hd argl :: "-g" :: tl argl);
+ }
+ exception{
+ "fail:*" =>
+ return (nil, 0, nil);
+ }
+ return (nil, 0, nil);
+}
+
+newprint(win: ref Tk->Toplevel, p: Profile->Prof, i: int): array of int
+{
+ cmd(win, ".f.t delete 1.0 end");
+ cmd(win, "update");
+ m0, m1: list of Profile->Modprof;
+ for(m := p.mods; m != nil && --i >= 0; m = tl m)
+ m0 = m;
+ if(m == nil)
+ return nil;
+ m1 = tl m;
+ (name, nil, spath, nil, line, nil, nil, tot, tots, nil) := hd m;
+ name0 := name1 := "nil";
+ if(m0 != nil)
+ name0 = (hd m0).name;
+ if(m1 != nil)
+ name1 = (hd m1).name;
+ a := len name;
+ name += sys->sprint(" (%d %d) ", tot, tots[0]);
+ cmd(win, ".f.t insert end {" + name + " <- " + name0 + " -> " + name1 + "}");
+ tag := gettag(win, tot+tots[0], p.total+p.totals[0]);
+ cmd(win, ".f.t tag add " + tag + " " + "1.0" + " " + "1." + string a);
+ cmd(win, ".f.t insert end \n\n");
+ cmd(win, "update");
+ lineno := TXTBEGIN;
+ bio := bufio->open(spath, Bufio->OREAD);
+ if(bio == nil)
+ return nil;
+ i = 1;
+ ll := len line/2;
+ while((s := bio.gets('\n')) != nil){
+ f := g := 0;
+ if(i < ll){
+ f = line[2*i];
+ g = line[2*i+1];
+ }
+ a = len s;
+ s = sys->sprint("%d\t%d\t%s", f, g, s);
+ b := len s;
+ cmd(win, ".f.t insert end " + tk->quote(s));
+ tag = gettag(win, f+g, tot+tots[0]);
+ cmd(win, ".f.t tag add " + tag + " " + string lineno + "." + string (b-a) + " " + string lineno + "." + string (b-1));
+ cmd(win, "update");
+ lineno++;
+ i++;
+ }
+ return line;
+}
+
+index(win: ref Tk->Toplevel, x: int, y: int): int
+{
+ t := cmd(win, ".f.t index @" + string x + "," + string y);
+ (nil, l) := sys->tokenize(t, ".");
+# sys->print("%d,%d -> %s\n", x, y, t);
+ return int hd l;
+}
+
+winextent(win: ref Tk->Toplevel): (int, int)
+{
+ w := int cmd(win, ".f.t cget -actwidth");
+ h := int cmd(win, ".f.t cget -actheight");
+ lw := index(win, 0, 0);
+ uw := index(win, w-1, h-1);
+ return (lw, uw);
+}
+
+see(win: ref Tk->Toplevel, line: int)
+{
+ cmd(win, ".f.t see " + string line + ".0");
+ cmd(win, "update");
+}
+
+scroll(win: ref Tk->Toplevel, line: array of int)
+{
+ (nil, uw) := winextent(win);
+ lno := TXTBEGIN;
+ ll := len line/2;
+ for(i := 1; i < ll; i++){
+ n := line[2*i]+line[2*i+1];
+ if(n > 0 && lno > uw){
+ see(win, lno);
+ return;
+ }
+ lno++;
+ }
+ lno = TXTBEGIN;
+ ll = len line/2;
+ for(i = 1; i < ll; i++){
+ n := line[2*i]+line[2*i+1];
+ if(n > 0){
+ see(win, lno);
+ return;
+ }
+ lno++;
+ }
+}
+
+cmd(top: ref Tk->Toplevel, s: string): string
+{
+ # sys->print("%s\n", s);
+ e := tk->cmd(top, s);
+ if (e != nil && e[0] == '!')
+ sys->fprint(sys->fildes(2), "tk error on '%s': %s\n", s, e);
+ return e;
+}
+
+fatal(s: string)
+{
+ sys->fprint(sys->fildes(2), "%s\n", s);
+ exit;
+}
+
+MENUMAX: con 20;
+
+createmenu(top: ref Tk->Toplevel, p: Profile->Prof )
+{
+ mn := ".f.menu";
+ cmd(top, "menu " + mn);
+ i := j := 0;
+ for(m := p.mods; m != nil; m = tl m){
+ name := (hd m).name;
+ cmd(top, mn + " add command -label " + name + " -command {send tkc " + string i + "}");
+ i++;
+ j++;
+ if(j == MENUMAX && tl m != nil){
+ cmd(top, mn + " add cascade -label MORE -menu " + mn + ".menu");
+ mn += ".menu";
+ cmd(top, "menu " + mn);
+ j = 0;
+ }
+ }
+}
+
+tags := array[256] of { * => byte 0 };
+
+gettag(win: ref Tk->Toplevel, n: int, d: int): string
+{
+ i := int ((real n/real d) * real 15);
+ if(i < 0 || i > 15)
+ i = 0;
+ s := "tag" + string i;
+ if(tags[i] == byte 0){
+ rgb := "#" + hex2(255-64*0)+hex2(255-64*(i/4))+hex2(255-64*(i%4));
+ cmd(win, ".f.t tag configure " + s + " -fg black -bg " + rgb);
+ tags[i] = byte 1;
+ }
+ return s;
+}
+
+hex(i: int): int
+{
+ if(i < 10)
+ return i+'0';
+ else
+ return i-10+'A';
+}
+
+hex2(i: int): string
+{
+ s := "00";
+ s[0] = hex(i/16);
+ s[1] = hex(i%16);
+ return s;
+}
+
+wincfg := array[] of {
+ "frame .f",
+ "text .f.t -width 809 -height 500 -state disabled -wrap char -bg white -yscrollcommand {.f.s set}",
+ "scrollbar .f.s -orient vertical -command {.f.t yview}",
+ "frame .i",
+ "button .i.b -bitmap small_color_left.bit -command {send tkc b}",
+ "button .i.f -bitmap small_color_right.bit -command {send tkc f}",
+ "button .i.s -bitmap small_find.bit -command {send tkc s}",
+ "button .i.m -bitmap small_reload.bit -command {send tkc m}",
+
+ "pack .i.b -side left",
+ "pack .i.f -side left",
+ "pack .i.s -side left",
+ "pack .i.m -side left",
+
+ "pack .f.s -fill y -side left",
+ "pack .f.t -fill both -expand 1",
+
+ "pack .i -fill x",
+ "pack .f -fill both -expand 1",
+ "pack propagate . 0",
+
+ "update",
+}; \ No newline at end of file
diff --git a/appl/wm/pen.b b/appl/wm/pen.b
new file mode 100644
index 00000000..17b2be39
--- /dev/null
+++ b/appl/wm/pen.b
@@ -0,0 +1,447 @@
+implement Pen;
+
+#
+# pen input on touch screen
+#
+# Copyright © 2001,2002 Vita Nuova Holdings Limited. All rights reserved.
+#
+# This may be used or modified by anyone for any purpose.
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Point, Rect: import draw;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "strokes.m";
+ strokes: Strokes;
+ Classifier, Penpoint, Stroke: import strokes;
+ readstrokes: Readstrokes;
+
+include "arg.m";
+
+Pen: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+debug := 0;
+stderr: ref Sys->FD;
+
+tkconfig := array[] of{
+ "canvas .c -borderwidth 0 -bg white -height 80 -width 80",
+ ".c create text 0 0 -anchor nw -width 5w -fill gray -tags mode",
+ ".c create text 30 0 -anchor nw -width 3w -fill blue -tags char",
+ "bind .c <Button-1> {grab set .c; send cmd push %x %y}",
+ "bind .c <Motion-Button-1> {send cmd move %x %y}",
+ "bind .c <ButtonRelease-1> {grab release .c; send cmd release %x %y}",
+ "bind .c <Enter> {send cmd move %x %y}", # does nothing if not previously down
+# "bind .c <Leave> {send cmd leave %x %y}", # ditto
+ "pack .c -expand 1 -fill both -padx 5 -pady 5",
+};
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "Usage: pen [-t] [-e] [classifier ...]\n");
+ raise "fail:usage";
+}
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "pen: no window context\n");
+ raise "fail:bad context";
+ }
+ stderr = sys->fildes(2);
+ draw = load Draw Draw->PATH;
+ bufio = load Bufio Bufio->PATH;
+ tk = load Tk Tk->PATH;
+ if(tk == nil)
+ nomod(Tk->PATH);
+ tkclient = load Tkclient Tkclient->PATH;
+ if(tkclient == nil)
+ nomod(Tkclient->PATH);
+ strokes = load Strokes Strokes->PATH;
+ if(strokes == nil)
+ nomod(Strokes->PATH);
+ strokes->init();
+ readstrokes = load Readstrokes Readstrokes->PATH;
+ if(readstrokes == nil)
+ nomod(Readstrokes->PATH);
+ readstrokes->init(strokes);
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ nomod(Arg->PATH);
+ arg->init(args);
+ taskbar := 0;
+ noexit := 0;
+ winopts := Tkclient->Appl;
+ corner := 1;
+ while((opt := arg->opt()) != 0)
+ case opt {
+ 't' =>
+ taskbar = 1;
+ 'e' =>
+ noexit = 1;
+ 'r' =>
+ winopts &= ~Tkclient->Resize;
+ 'c' =>
+ corner = 0;
+ * =>
+ usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ if(args == nil)
+ args = "/lib/strokes/letters.clx" :: "/lib/strokes/digits.clx" :: "/lib/strokes/punc.clx" :: nil;
+ csets := array[len args] of ref Classifier;
+ cs := 0;
+ for(; args != nil; args = tl args){
+ file := hd args;
+ (err, rc) := readstrokes->read_classifier(file, 1, 0);
+ if(rc == nil)
+ error(sys->sprint("can't read classifier %s: %s", file, err));
+ csets[cs++] = rc;
+ }
+ readstrokes = nil;
+
+ rec := csets[0];
+ digits: ref Classifier;
+ if(len csets > 1)
+ digits = csets[1]; # need not actually be digits
+
+ sys->pctl(Sys->NEWPGRP, nil);
+ tkclient->init();
+ (top, ctl) := tkclient->toplevel(ctxt, nil, "Pen", winopts);
+ cmd := chan of string;
+ tk->namechan(top, cmd, "cmd");
+ for (i1 := 0; i1 < len tkconfig; i1++)
+ tkcmd(top, tkconfig[i1]);
+ if(winopts & Tkclient->Resize)
+ tkcmd(top, "pack propagate . 0");
+
+
+ if(corner){
+ (w, h) := (int tk->cmd(top, ". cget -width"), int tk->cmd(top, ". cget -height"));
+ r := ctxt.display.image.r;
+ tkcmd(top, sys->sprint(". configure -x %d -y %d", r.max.x-w, r.max.y-h));
+ }
+
+
+ shift := 0;
+ punct := 0;
+ points := array[1000] of Penpoint;
+ npoint := 0;
+
+ tkclient->onscreen(top, nil);
+ tkclient->startinput(top, "ptr"::nil);
+ if(taskbar)
+ tkclient->wmctl(top, "task");
+ tk->cmd(top, "update");
+
+ for(;;){
+ if(punct)
+ drawmode(top, "#&*");
+ else if(rec == digits)
+ drawmode(top, "123");
+ else if(shift == 1)
+ drawmode(top, "Abc");
+ else if(shift == 2)
+ drawmode(top, "ABC");
+ else if(shift)
+ drawmode(top, "S "+string shift);
+ else
+ drawmode(top, "abc");
+ tk->cmd(top, "update");
+ alt{
+ s := <-top.ctxt.ptr =>
+ tk->pointer(top, *s);
+
+ s := <-top.ctxt.ctl or
+ s = <-top.wreq or
+ s = <-ctl =>
+ if(s == "exit" && noexit)
+ s = "task";
+ tkclient->wmctl(top, s);
+
+ s := <-cmd =>
+ (nf, flds) := sys->tokenize(s, " \t");
+ if(nf < 3)
+ break;
+ p := Penpoint(int hd tl flds, int hd tl tl flds, 0);
+ case hd flds {
+ "push" =>
+ tkcmd(top, "raise .");
+ tk->cmd(top, "update");
+ npoint = 0;
+ points[npoint++] = p;
+ "leave" =>
+ npoint = 0;
+ tkcmd(top, ".c delete stuff");
+ "release" =>
+ if(npoint == 0)
+ break;
+ points[npoint++] = p;
+ (n, tap) := recognize_stroke(top, rec, ref Stroke(npoint, points[0:npoint], 0, 0), debug);
+ drawchars(top, "");
+ name: string = nil;
+ if(n >= 0){
+ name = rec.cnames[n];
+ if(debug > 1){
+ ex: ref Stroke = nil;
+ if(rec.canonex != nil)
+ ex = rec.canonex[n];
+ drawshape(top, "stuff", ex, "blue", rec.dompts[n], "yellow");
+ sys->fprint(stderr, "match: %s\n", name);
+ }
+ case c := name[0] {
+ 'S' =>
+ shift = (shift+1)%3;
+ name = nil;
+ 'A' =>
+ name = " ";
+ 'B' =>
+ name = "\b";
+ 'R' =>
+ name = "\n";
+ 'T' =>
+ name = "\t";
+ 'N' =>
+ # num lock
+ if(rec == digits)
+ rec = csets[0];
+ else
+ rec = digits;
+ name = nil;
+ * =>
+ if(c >= 'A' && c <= 'Z'){ # other gestures, not yet implemented
+ shift = 0;
+ punct = 0;
+ rec = csets[0];
+ name = nil;
+ unknown(top);
+ break;
+ }
+ if(punct){
+ rec = csets[0];
+ punct = 0;
+ }
+ if(shift){
+ for(i := 0; i < len name; i++)
+ if((c = name[i]) >= 'a' && c <= 'z')
+ name[i] += 'A'-'a';
+ if(shift < 2)
+ shift = 0;
+ }
+ }
+ }else if(tap != nil){
+ if(punct == 0){
+ if(len csets > 2){
+ rec = csets[2];
+ punct = 1;
+ }
+ name = nil;
+ }else{
+ rec = csets[0];
+ punct = 0;
+ name = ".";
+ }
+ }else
+ unknown(top);
+ if(name != nil){
+ drawchars(top, name);
+ for(i := 0; i < len name; i++)
+ sys->fprint(top.ctxt.connfd, "key %d", name[i]);
+ # tk->keyboard(top, name[i]);
+ }
+ tkcmd(top, ".c delete stuff");
+ npoint = 0;
+ * =>
+ if(npoint){
+ q := points[npoint-1];
+ points[npoint++] = p;
+ tkcmd(top, sys->sprint(".c create line %d %d %d %d -tags stuff; update", q.x, q.y, p.x, p.y));
+ }
+ }
+ }
+ }
+}
+
+unknown(top: ref Tk->Toplevel)
+{
+ drawquery(top, (10, 10), 3);
+ tk->cmd(top, "update");
+ sys->sleep(300);
+ tkcmd(top, ".c delete query");
+ tk->cmd(top, "update");
+}
+
+drawchars(top: ref Tk->Toplevel, s: string)
+{
+ t := "";
+ for(i := 0; i < len s; i++){
+ c := s[i];
+ case c {
+ '\n' => t += "\\n";
+ '\b' => t += "\\b";
+ '\t' => t += "\\t";
+ 4 => t += "eot";
+ * =>
+ if(c < ' ')
+ t += sys->sprint("\\%3.3o", c);
+ else
+ t[len t] = c;
+ }
+ }
+ tkcmd(top, ".c itemconfigure char -text '"+t);
+}
+
+drawmode(top: ref Tk->Toplevel, mode: string)
+{
+ tkcmd(top, ".c itemconfigure mode -text '"+mode);
+}
+
+drawquery(top: ref Tk->Toplevel, p: Point, scale: int)
+{
+ width := 2;
+ size := 1<<scale;
+ if(size < 4)
+ width = 1;
+ o := Point(p.x-size/2, p.x+size/2);
+ if(o.x < 0)
+ o.x = 0;
+ if(o.y < 0)
+ o.y = 0;
+ c := o.add((size, size));
+ m := o.add(c).div(2);
+ b := c.add((0, size));
+ tkcmd(top, sys->sprint(".c create arc %d %d %d %d -start 150 -extent -240 -style arc -tags query -width %d -outline red", o.x, o.y, c.x, c.y, width));
+ tkcmd(top, sys->sprint(".c create line %d %d %d %d -fill red -width %d -tags query", m.x, c.y, m.x, b.y, width));
+ tkcmd(top, sys->sprint(".c create arc %d %d %d %d -start 0 -extent 360 -fill red -width %d -tags query -style arc -outline red", m.x-width, b.y+2*width, m.x+width, b.y+3*width, width));
+}
+
+tkcmd(top: ref Tk->Toplevel, s: string)
+{
+ e := tk->cmd(top, s);
+ if(e != nil && e[0]=='!')
+ sys->fprint(sys->fildes(2), "pen: tk error: %s in [%s]\n", e, s);
+}
+
+drawshape(top: ref Tk->Toplevel, tag: string, stroke: ref Stroke, colour: string, dompts: ref Stroke, domcol: string)
+{
+ if(top == nil)
+ return;
+ if(stroke != nil)
+ for(i := 1; i < stroke.npts; i++){
+ p := stroke.pts[i-1];
+ q := stroke.pts[i];
+ tkcmd(top, sys->sprint(".c create line %d %d %d %d -fill %s -tags %s", p.x, p.y, q.x, q.y, colour, tag));
+ }
+ if(dompts != nil)
+ for(i = 0; i < dompts.npts; i++){
+ p := dompts.pts[i];
+ tkcmd(top, sys->sprint(".c create oval %d %d %d %d -fill %s -tags %s", p.x-1, p.y-1, p.x+1, p.y+1, domcol, tag));
+ }
+ tk->cmd(top, "update");
+}
+
+#
+# duplicate function of strokes module temporarily
+# to allow for experiment
+#
+
+#DIST_THLD: con 3200; # x100
+DIST_THLD: con 3300; # x100
+
+# Tap-handling parameters
+TAP_TIME_THLD: con 150; # msec
+TAP_DIST_THLD: con 75; # dx*dx + dy*dy
+TAP_PATHLEN: con 10*100; # x100
+
+recognize_stroke(top: ref Tk->Toplevel, rec: ref Classifier, stroke: ref Stroke, debug: int): (int, string)
+{
+
+ if(stroke.npts < 1)
+ return (-1, nil);
+
+ stroke = stroke.filter(); # filter out close points
+
+ if(stroke.npts == 1 || stroke.length() < TAP_PATHLEN)
+ return (-1, "."); # considered a tap regardless of elapsed time
+
+ strokes->preprocess_stroke(stroke);
+
+ # Compute its dominant points.
+ dompts := stroke.interpolate().dominant();
+
+ if(debug)
+ drawshape(top, "stuff", stroke, "green", dompts, "red");
+
+ if(rec == nil)
+ return (-1, nil);
+
+ best_dist := Strokes->MAXDIST;
+ best_i := -1;
+
+ # Score input stroke against every class in classifier.
+ for(i := 0; i < rec.nclasses; i++){
+ name := rec.cnames[i];
+ (sim, dist) := strokes->score_stroke(dompts, rec.dompts[i]);
+ if(debug > 1 && dist < Strokes->MAXDIST)
+ sys->fprint(stderr, "(%s, %d, %d) ", name, sim, dist);
+ if(dist < DIST_THLD){
+ if(debug > 1)
+ sys->fprint(stderr, "(%s, %d, %d) ", name, sim, dist);
+ # Is it the best so far?
+ if(dist < best_dist){
+ best_dist = dist;
+ best_i = i;
+ }
+ }
+ }
+
+ if(debug > 1)
+ sys->fprint(stderr, "\n");
+
+ return (best_i, nil);
+}
+
+objrect(t: ref Tk->Toplevel, path: string, addbd: int): Rect
+{
+ r: Rect;
+ r.min.x = int tk->cmd(t, path+" cget -actx");
+ if(addbd)
+ r.min.x += int tk->cmd(t, path+" cget -bd");
+ r.min.y = int tk->cmd(t, ".f cget -acty");
+ if(addbd)
+ r.min.y += int tk->cmd(t, path+" cget -bd");
+ r.max.x = r.min.x + int tk->cmd(t, path+" cget -actwidth");
+ r.max.y = r.min.y + int tk->cmd(t, path+" cget -actheight");
+ return r;
+}
+
+nomod(s: string)
+{
+ error(sys->sprint("can't load %s: %r", s));
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "scribble: %s\n", s);
+ raise "fail:error";
+}
diff --git a/appl/wm/polyhedra.b b/appl/wm/polyhedra.b
new file mode 100644
index 00000000..b6d7088d
--- /dev/null
+++ b/appl/wm/polyhedra.b
@@ -0,0 +1,800 @@
+implement WmPolyhedra;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+ Point, Rect, Pointer, Image, Screen, Display: import draw;
+include "tk.m";
+ tk: Tk;
+ Toplevel: import tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "math.m";
+ math: Math;
+ sin, cos, tan, sqrt: import math;
+include "rand.m";
+ rand: Rand;
+include "daytime.m";
+ daytime: Daytime;
+include "math/polyhedra.m";
+ polyhedra: Polyhedra;
+ Polyhedron: import Polyhedra;
+ scanpolyhedra, getpolyhedron: import polyhedra;
+include "math/polyfill.m";
+ polyfill: Polyfill;
+ initzbuf, clearzbuf, fillpoly: import polyfill;
+include "smenu.m";
+ smenu: Smenu;
+ Scrollmenu: import smenu;
+
+WmPolyhedra : module
+{
+ init : fn(nil : ref Draw->Context, argv : list of string);
+};
+
+WIDTH, HEIGHT: con 400;
+
+mainwin: ref Toplevel;
+Disp, black, white, opaque: ref Image;
+Dispr: Rect;
+pinit := 40;
+
+init(ctxt : ref Draw->Context, argv : list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ bufio = load Bufio Bufio->PATH;
+ math = load Math Math->PATH;
+ rand = load Rand Rand->PATH;
+ daytime = load Daytime Daytime->PATH;
+ polyhedra = load Polyhedra Polyhedra->PATH;
+ polyfill = load Polyfill Polyfill->PATH;
+ smenu = load Smenu Smenu->PATH;
+ rand->init(daytime->now());
+ daytime = nil;
+ polyfill->init();
+ √2 = sqrt(2.0);
+ √3 = sqrt(3.0);
+ cursor := "";
+
+ tkclient->init();
+ if(ctxt == nil){
+ ctxt = tkclient->makedrawcontext();
+ # sys->fprint(sys->fildes(2), "wm not running\n");
+ # exit;
+ }
+ argv = tl argv;
+ while(argv != nil){
+ case hd argv{
+ "-p" =>
+ argv = tl argv;
+ if(argv != nil)
+ pinit = int hd argv;
+ "-r" =>
+ pinit = -1;
+ "-c" =>
+ argv = tl argv;
+ if(argv != nil)
+ cursor = hd argv;
+ }
+ if(argv != nil)
+ argv = tl argv;
+ }
+ (win, wmcmd) := tkclient->toplevel(ctxt, "", "Polyhedra", Tkclient->Resize | Tkclient->Hide);
+ mainwin = win;
+ sys->pctl(Sys->NEWPGRP, nil);
+ cmdch := chan of string;
+ tk->namechan(win, cmdch, "cmd");
+ for(i := 0; i < len win_config; i++)
+ cmd(win, win_config[i]);
+ if(cursor != nil)
+ cmd(win, "cursor -bitmap " + cursor);
+ tkclient->onscreen(win, nil);
+ tkclient->startinput(win, "kbd"::"ptr"::nil);
+ fittoscreen(win);
+ pid := -1;
+ sync := chan of int;
+ chanθ := chan of real;
+ geo := newgeom();
+ setimage(win, geo);
+ cmd(win, "update");
+ display := win.image.display;
+ white = display.color(Draw->White);
+ black = display.color(Draw->Black);
+ opaque = display.opaque;
+ shade = array[NSHADES] of ref Image;
+ for(i = 0; i < NSHADES; i++){
+ # v := (255*i)/(NSHADES-1); # NSHADES=17
+ v := (192*i)/(NSHADES-1)+32; # NSHADES=13
+ # v := (128*i)/(NSHADES-1)+64; # NSHADES=9
+ shade[i] = display.rgb(v, v, v);
+ # shade[i] = rgba(display, v, v, v, 16r7f);
+ }
+ (geo.npolyhedra, geo.polyhedra, geo.b) = scanpolyhedra("/lib/polyhedra.all");
+ if(geo.npolyhedra == 0){
+ sys->fprint(sys->fildes(2), "cannot open polyhedra database\n");
+ exit;
+ }
+ yieldc := chan of int;
+ # spawn yieldproc(yieldc);
+ # ypid := <- yieldc;
+ initgeom(geo);
+ sm := array[2] of ref Scrollmenu;
+ sm[0] = scrollmenu(win, ".f.menu", geo.polyhedra, geo.npolyhedra, 0);
+ sm[1] = scrollmenu(win, ".f.menud", geo.polyhedra, geo.npolyhedra, 1);
+ # createmenu(win, geo.polyhedra);
+ spawn drawpolyhedron(geo, sync, chanθ, yieldc);
+ pid = <- sync;
+ newproc := 0;
+
+ for(;;){
+ alt{
+ c := <-win.ctxt.kbd =>
+ tk->keyboard(win, c);
+ c := <-win.ctxt.ptr =>
+ tk->pointer(win, *c);
+ c := <-win.ctxt.ctl or
+ c = <-win.wreq =>
+ tkclient->wmctl(win, c);
+ c := <- wmcmd =>
+ case c{
+ "exit" =>
+ exits(pid, sm);
+ * =>
+ sync <-= 0;
+ tkclient->wmctl(win, c);
+ if(c[0] == '!'){
+ if(setimage(win, geo) <= 0)
+ exits(pid, sm);
+ }
+ sync <-= 1;
+ }
+ c := <- cmdch =>
+ (nil, toks) := sys->tokenize(c, " ");
+ case hd toks{
+ "prev" =>
+ geo.curpolyhedron = geo.curpolyhedron.prv;
+ getpoly(geo, -1);
+ newproc = 1;
+ "next" =>
+ geo.curpolyhedron = geo.curpolyhedron.nxt;
+ getpoly(geo, 1);
+ newproc = 1;
+ "dual" =>
+ geo.dual = !geo.dual;
+ newproc = 1;
+ "edges" =>
+ edges = !edges;
+ "faces" =>
+ faces = !faces;
+ "clear" =>
+ clear = !clear;
+ "slow" =>
+ if(geo.θ > ε){
+ if(geo.θ < 2.)
+ chanθ <-= geo.θ/2.;
+ else
+ chanθ <-= geo.θ-1.;
+ }
+ "fast" =>
+ if(geo.θ < 45.){
+ if(geo.θ < 1.)
+ chanθ <-= 2.*geo.θ;
+ else
+ chanθ <-= geo.θ+1.;
+ }
+ "axis" =>
+ setaxis(geo);
+ initmatrix(geo);
+ newproc = 1;
+ "menu" =>
+ x := int cmd(win, ".p cget actx");
+ y := int cmd(win, ".p cget acty");
+ w := int cmd(win, ".p cget -actwidth");
+ h := int cmd(win, ".p cget -actheight");
+ sm[geo.dual].post(x+w/8, y+h/8, cmdch, "");
+ # cmd(win, ".f.menu post " + x + " " + y);
+ * =>
+ i = int hd toks;
+ fp := geo.polyhedra;
+ for(p := fp; p != nil; p = p.nxt){
+ if(p.indx == i){
+ geo.curpolyhedron = p;
+ getpoly(geo, 1);
+ newproc = 1;
+ break;
+ }
+ if(p.nxt == fp)
+ break;
+ }
+ }
+ }
+ if(newproc){
+ sync <-= 0; # stop it first
+ kill(pid);
+ spawn drawpolyhedron(geo, sync, chanθ, yieldc);
+ pid = <- sync;
+ newproc = 0;
+ }
+ }
+}
+
+setimage(win: ref Toplevel, geo: ref Geom): int
+{
+ panelw := int tk->cmd(win, ".p cget -actwidth");
+ panelh := int tk->cmd(win, ".p cget -actheight");
+ if(panelw < 3)
+ panelw = 3;
+ if(panelh < 3)
+ panelh = 3;
+ Dispr = Rect((0,0), (panelw, panelh));
+ Disp = win.image.display.newimage(Dispr, win.image.chans, 0, Draw->Black);
+ if(Disp == nil){
+ sys->fprint(sys->fildes(2), "not enough image memory\n");
+ return 0;
+ }
+ tk->putimage(win, ".p", Disp, nil);
+ if(Dispr.dx() > Dispr.dy())
+ h := Dispr.dy();
+ else
+ h = Dispr.dx();
+ rr: Rect = ((0, 0), (h, h));
+ corner := ((Dispr.min.x+Dispr.max.x-rr.max.x)/2, (Dispr.min.y+Dispr.max.y-rr.max.y)/2);
+ geo.r = (rr.min.add(corner), rr.max.add(corner));
+ geo.h = h;
+ geo.sx = real ((3*h)/8);
+ geo.sy = - real ((3*h)/8);
+ geo.tx = h/2+geo.r.min.x;
+ geo.ty = h/2+geo.r.min.y;
+ geo.zstate = initzbuf(geo.r);
+ return 1;
+}
+
+# yieldcpu(c: chan of int)
+# {
+# c <-= 1;
+# <-c;
+# }
+
+# yieldproc(c: chan of int)
+# {
+# c <-= sys->pctl(0, nil);
+# for (;;) {
+# <-c;
+# c <-= 1;
+# }
+# }
+
+π: con Math->Pi;
+√2, √3: real;
+∞: con 1<<30;
+ε: con 0.001;
+
+Axis: adt{
+ λ, μ, ν: int;
+};
+
+Vector: adt{
+ x, y, z: real;
+};
+
+Geom: adt{
+ h: int; # length, breadth of r below
+ r: Rect; # area on screen to update
+ sx, sy: real; # x, y scale
+ tx, ty: int; # x, y translation
+ θ: real; # angle of rotation
+ TM: array of array of real; # rotation matrix
+ axis: Axis; # direction cosines of rotation
+ view: Vector;
+ light: Vector;
+ npolyhedra: int;
+ polyhedra: ref Polyhedron;
+ curpolyhedron: ref Polyhedron;
+ b: ref Iobuf; # of polyhedra file
+ dual: int;
+ zstate: ref Polyfill->Zstate;
+};
+
+NSHADES: con 13; # odd
+shade: array of ref Image;
+
+clear, faces: int = 1;
+edges: int = 0;
+
+setview(geo: ref Geom)
+{
+ geo.view = (0.0, 0.0, 1.0);
+ geo.light = (0.0, -1.0, 0.0);
+}
+
+map(v: Vector, geo: ref Geom): Point
+{
+ return (int (geo.sx*v.x)+geo.tx, int (geo.sy*v.y)+geo.ty);
+}
+
+minus(v1: Vector): Vector
+{
+ return (-v1.x, -v1.y, -v1.z);
+}
+
+add(v1, v2: Vector): Vector
+{
+ return (v1.x+v2.x, v1.y+v2.y, v1.z+v2.z);
+}
+
+sub(v1, v2: Vector): Vector
+{
+ return (v1.x-v2.x, v1.y-v2.y, v1.z-v2.z);
+}
+
+mul(v1: Vector, l: real): Vector
+{
+ return (l*v1.x, l*v1.y, l*v1.z);
+}
+
+div(v1: Vector, l: real): Vector
+{
+ return (v1.x/l, v1.y/l, v1.z/l);
+}
+
+normalize(v1: Vector): Vector
+{
+ return div(v1, sqrt(dot(v1, v1)));
+}
+
+dot(v1, v2: Vector): real
+{
+ return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
+}
+
+cross(v1, v2: Vector): Vector
+{
+ return (v1.y*v2.z-v2.y*v1.z, v1.z*v2.x-v2.z*v1.x, v1.x*v2.y-v2.x*v1.y);
+}
+
+drawpolyhedron(geo: ref Geom, sync: chan of int, chanθ: chan of real, yieldc: chan of int)
+{
+ s: string;
+
+ sync <-= sys->pctl(0, nil);
+ p := geo.curpolyhedron;
+ if(!geo.dual || p.anti){
+ s = p.name;
+ s += " (" + string p.indx + ")";
+ puts(s);
+ drawpolyhedron0(p.V, p.F, p.concave, p.allf || p.anti, p.v, p.f, p.fv, p.inc, geo, sync, chanθ, yieldc);
+ }
+ else{
+ s = p.dname;
+ s += " (" + string p.indx + ")";
+ puts(s);
+ drawpolyhedron0(p.F, p.V, p.concave, p.anti, p.f, p.v, p.vf, 0.0, geo, sync, chanθ, yieldc);
+ }
+}
+
+drawpolyhedron0(V, F, concave, allf: int, v, f: array of Vector, fv: array of array of int, inc: real, geo: ref Geom, sync: chan of int, chanθ: chan of real, yieldc: chan of int)
+{
+ norm : array of array of Vector;
+ newn, oldn : array of Vector;
+
+ yieldc = nil; # not used now
+ θ := geo.θ;
+ totθ := 0.;
+ if(θ != 0.)
+ n := int ((360.+θ/2.)/θ);
+ else
+ n = ∞;
+ p := n;
+ t := 0;
+ vec := array[2] of array of Vector;
+ vec[0] = array[V] of Vector;
+ vec[1] = array[V] of Vector;
+ if(concave){
+ norm = array[2] of array of Vector;
+ norm[0] = array[F] of Vector;
+ norm[1] = array[F] of Vector;
+ }
+ Disp.draw(geo.r, black, opaque, (0, 0));
+ reveal(geo.r);
+ for(i := 0; ; i = (i+1)%p){
+ alt{
+ <- sync =>
+ <- sync;
+ θ = <- chanθ =>
+ geo.θ = θ;
+ initmatrix(geo);
+ if(θ != 0.){
+ n = int ((360.+θ/2.)/θ);
+ p = int ((360.-totθ+θ/2.)/θ);
+ }
+ else
+ n = p = ∞;
+ if(p == 0)
+ i = 0;
+ else
+ i = 1;
+ * =>
+ # yieldcpu(yieldc);
+ sys->sleep(0);
+ }
+ if(concave)
+ clearzbuf(geo.zstate);
+ new := vec[t];
+ old := vec[!t];
+ if(concave){
+ newn = norm[t];
+ oldn = norm[!t];
+ }
+ t = !t;
+ if(i == 0){
+ for(j := 0; j < V; j++)
+ new[j] = v[j];
+ if(concave){
+ for(j = 0; j < F; j++)
+ newn[j] = f[j];
+ }
+ setview(geo);
+ totθ = 0.;
+ p = n;
+ }
+ else{
+ for(j := 0; j < V; j++)
+ new[j] = mulm(geo.TM, old[j]);
+ if(concave){
+ for(j = 0; j < F; j++)
+ newn[j] = mulm(geo.TM, oldn[j]);
+ }
+ else{
+ geo.view = mulmi(geo.TM, geo.view);
+ geo.light = mulmi(geo.TM, geo.light);
+ }
+ totθ += θ;
+ }
+ if(clear)
+ Disp.draw(geo.r, black, opaque, (0, 0));
+ for(j := 0; j < F; j++){
+ if(concave){
+ if(allf || dot(geo.view, newn[j]) < 0.0)
+ polyfilla(fv[j], new, newn[j], dot(geo.light, newn[j]), geo, concave, inc);
+ }
+ else{
+ if(dot(geo.view, f[j]) < 0.0)
+ polyfilla(fv[j], new, f[j], dot(geo.light, f[j]), geo, concave, 0.0);
+ }
+ }
+ reveal(geo.r);
+ }
+}
+
+ZSCALE: con real (1<<20);
+LIMIT: con real (1<<11);
+
+polyfilla(fv: array of int, v: array of Vector, f: Vector, ill: real, geo: ref Geom, concave: int, inc: real)
+{
+ dc, dx, dy: int;
+
+ d := 0.0;
+ n := fv[0];
+ ap := array[n+1] of Point;
+ for(j := 0; j < n; j++){
+ vtx := v[fv[j+1]];
+ # vtx = add(vtx, mul(f, 0.1)); # interesting effects with -/larger factors
+ ap[j] = map(vtx, geo);
+ d += dot(f, vtx);
+ }
+ ap[n] = ap[0];
+ d /= real n;
+ if(concave){
+ if(fv[n+1] != 1)
+ d += inc;
+ if(f.z > -ε && f.z < ε)
+ return;
+ α := geo.sx;
+ β := real geo.tx;
+ γ := geo.sy;
+ δ := real geo.ty;
+ c := f.z;
+ a := -f.x/(c*α);
+ if(a <= -LIMIT || a >= LIMIT)
+ return;
+ b := -f.y/(c*γ);
+ if(b <= -LIMIT || b >= LIMIT)
+ return;
+ d = d/c-β*a-δ*b;
+ if(d <= -LIMIT || d >= LIMIT)
+ return;
+ dx = int (a*ZSCALE);
+ dy = int (b*ZSCALE);
+ dc = int (d*ZSCALE);
+ }
+ edge := white;
+ face := shade[int ((real ((NSHADES-1)/2))*(1.0-ill))];
+ if(concave){
+ if(!faces)
+ face = black;
+ if(!edges)
+ edge = nil;
+ fillpoly(Disp, ap, ~0, face, (0, 0), geo.zstate, dc, dx, dy);
+ }
+ else{
+ if(faces)
+ Disp.fillpoly(ap, ~0, face, (0, 0));
+ if(edges)
+ Disp.poly(ap, Draw->Endsquare, Draw->Endsquare, 0, edge, (0, 0));
+ }
+}
+
+getpoly(geo: ref Geom, dir: int)
+{
+ p := geo.curpolyhedron;
+ if(0){
+ while(p.anti){
+ if(dir > 0)
+ p = p.nxt;
+ else
+ p = p.prv;
+ }
+ }
+ geo.curpolyhedron = p;
+ getpolyhedron(p, geo.b);
+}
+
+degtorad(α: real): real
+{
+ return α*π/180.0;
+}
+
+initmatrix(geo: ref Geom)
+{
+ TM := geo.TM;
+ φ := degtorad(geo.θ);
+ sinθ := sin(φ);
+ cosθ := cos(φ);
+ (l, m, n) := normalize((real geo.axis.λ, real geo.axis.μ, real geo.axis.ν));
+ f := 1.0-cosθ;
+ TM[1][1] = (1.0-l*l)*cosθ + l*l;
+ TM[1][2] = l*m*f-n*sinθ;
+ TM[1][3] = l*n*f+m*sinθ;
+ TM[2][1] = l*m*f+n*sinθ;
+ TM[2][2] = (1.0-m*m)*cosθ + m*m;
+ TM[2][3] = m*n*f-l*sinθ;
+ TM[3][1] = l*n*f-m*sinθ;
+ TM[3][2] = m*n*f+l*sinθ;
+ TM[3][3] = (1.0-n*n)*cosθ + n*n;
+}
+
+mulm(TM: array of array of real, v: Vector): Vector
+{
+ x := v.x;
+ y := v.y;
+ z := v.z;
+ v.x = TM[1][1]*x + TM[1][2]*y + TM[1][3]*z;
+ v.y = TM[2][1]*x + TM[2][2]*y + TM[2][3]*z;
+ v.z = TM[3][1]*x + TM[3][2]*y + TM[3][3]*z;
+ return v;
+}
+
+mulmi(TM: array of array of real, v: Vector): Vector
+{
+ x := v.x;
+ y := v.y;
+ z := v.z;
+ v.x = TM[1][1]*x + TM[2][1]*y + TM[3][1]*z;
+ v.y = TM[1][2]*x + TM[2][2]*y + TM[3][2]*z;
+ v.z = TM[1][3]*x + TM[2][3]*y + TM[3][3]*z;
+ return v;
+}
+
+reveal(r: Rect)
+{
+ cmd := sys->sprint(".p dirty %d %d %d %d", r.min.x, r.min.y, r.max.x, r.max.y);
+ tk->cmd(mainwin, cmd);
+ tk->cmd(mainwin, "update");
+}
+
+newgeom(): ref Geom
+{
+ geo := ref Geom;
+ TM := array[4] of array of real;
+ for(i := 0; i < 4; i++)
+ TM[i] = array[4] of real;
+ geo.θ = 10.;
+ geo.TM = TM;
+ geo.axis = (1, 1, 1);
+ geo.view = (1., 1., 1.);
+ geo.light = (1., 1., 1.);
+ geo.dual = 0;
+ return geo;
+}
+
+setaxis(geo: ref Geom)
+{
+ oaxis := geo.axis;
+ # while(geo.axis == Axis (0, 0, 0) || geo.axis = oaxis) not allowed
+ while((geo.axis.λ == 0 && geo.axis.μ == 0 && geo.axis.ν == 0) || (geo.axis.λ == oaxis.λ && geo.axis.μ == oaxis.μ && geo.axis.ν == oaxis.ν))
+ geo.axis = (rand->rand(5) - 2, rand->rand(5) - 2, rand->rand(5) - 2);
+}
+
+initgeom(geo: ref Geom)
+{
+ if(pinit < 0)
+ pn := rand->rand(geo.npolyhedra);
+ else
+ pn = pinit;
+ for(p := geo.polyhedra; --pn >= 0; p = p.nxt)
+ ;
+ geo.curpolyhedron = p;
+ getpoly(geo, 1);
+ setaxis(geo);
+ geo.θ = real (rand->rand(5)+1);
+ geo.dual = 0;
+ initmatrix(geo);
+ setview(geo);
+ Disp.draw(geo.r, black, opaque, (0, 0));
+ reveal(geo.r);
+}
+
+kill(pid: int): int
+{
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd == nil)
+ return -1;
+ if(sys->write(fd, array of byte "kill", 4) != 4)
+ return -1;
+ return 0;
+}
+
+exits(pid: int, sm: array of ref Scrollmenu)
+{
+ if(pid != -1)
+ kill(pid);
+ # kill(ypid);
+ sm[0].destroy();
+ sm[1].destroy();
+ exit;
+}
+
+cmd(top: ref Toplevel, s: string): string
+{
+ e := tk->cmd(top, s);
+ if (e != nil && e[0] == '!')
+ sys->fprint(sys->fildes(2), "polyhedra: tk error on '%s': %s\n", s, e);
+ return e;
+}
+
+puts(s: string)
+{
+ cmd(mainwin, ".f1.txt configure -text {" + s + "}");
+ cmd(mainwin, "update");
+}
+
+MENUMAX: con 10;
+
+scrollmenu(top: ref Tk->Toplevel, mname: string, p: ref Polyhedron, n: int, dual: int): ref Scrollmenu
+{
+ labs := array[n] of string;
+ i := 0;
+ for(q := p; q != nil && i < n; q = q.nxt){
+ if(dual)
+ name := q.dname;
+ else
+ name = q.name;
+ labs[i++] = string q.indx + " " + name;
+ }
+ sm := Scrollmenu.new(top, mname, labs, MENUMAX, (n-MENUMAX)/2);
+ cmd(top, mname + " configure -borderwidth 3");
+ return sm;
+}
+
+createmenu(top: ref Tk->Toplevel, p: ref Polyhedron)
+{
+ mn := ".f.menu";
+ cmd(top, "menu " + mn);
+ i := j := 0;
+ for(q := p ; q != nil; q = q.nxt){
+ cmd(top, mn + " add command -label {" + string q.indx + " " + q.name + "} -command {send cmd " + string q.indx + "}");
+ if(q.nxt == p)
+ break;
+ i++;
+ j++;
+ if(j == MENUMAX && q.nxt != nil){
+ cmd(top, mn + " add cascade -label MORE -menu " + mn + ".menu");
+ mn += ".menu";
+ cmd(top, "menu " + mn);
+ j = 0;
+ }
+ }
+}
+
+fittoscreen(win: ref Tk->Toplevel)
+{
+ Point: import draw;
+ if (win.image == nil || win.image.screen == nil)
+ return;
+ r := win.image.screen.image.r;
+ scrsize := Point((r.max.x - r.min.x), (r.max.y - r.min.y));
+ bd := int cmd(win, ". cget -bd");
+ winsize := Point(int cmd(win, ". cget -actwidth") + bd * 2, int cmd(win, ". cget -actheight") + bd * 2);
+ if (winsize.x > scrsize.x)
+ cmd(win, ". configure -width " + string (scrsize.x - bd * 2));
+ if (winsize.y > scrsize.y)
+ cmd(win, ". configure -height " + string (scrsize.y - bd * 2));
+ actr: Rect;
+ actr.min = Point(int cmd(win, ". cget -actx"), int cmd(win, ". cget -acty"));
+ actr.max = actr.min.add((int cmd(win, ". cget -actwidth") + bd*2,
+ int cmd(win, ". cget -actheight") + bd*2));
+ (dx, dy) := (actr.dx(), actr.dy());
+ if (actr.max.x > r.max.x)
+ (actr.min.x, actr.max.x) = (r.max.x - dx, r.max.x);
+ if (actr.max.y > r.max.y)
+ (actr.min.y, actr.max.y) = (r.max.y - dy, r.max.y);
+ if (actr.min.x < r.min.x)
+ (actr.min.x, actr.max.x) = (r.min.x, r.min.x + dx);
+ if (actr.min.y < r.min.y)
+ (actr.min.y, actr.max.y) = (r.min.y, r.min.y + dy);
+ cmd(win, ". configure -x " + string actr.min.x + " -y " + string actr.min.y);
+}
+
+win_config := array[] of {
+ "frame .f",
+ "button .f.prev -text {prev} -command {send cmd prev}",
+ "button .f.next -text {next} -command {send cmd next}",
+ "checkbutton .f.dual -text {dual} -command {send cmd dual} -variable dual",
+ ".f.dual deselect",
+ "pack .f.prev -side left",
+ "pack .f.next -side right",
+ "pack .f.dual -side top",
+
+ "frame .f0",
+ "checkbutton .f0.edges -text {edges} -command {send cmd edges} -variable edges",
+ ".f0.edges deselect",
+ "checkbutton .f0.faces -text {faces} -command {send cmd faces} -variable faces",
+ ".f0.faces select",
+ "checkbutton .f0.clear -text {clear} -command {send cmd clear} -variable clear",
+ ".f0.clear select",
+ "pack .f0.edges -side left",
+ "pack .f0.faces -side right",
+ "pack .f0.clear -side top",
+
+ "frame .f2",
+ "button .f2.slow -text {slow} -command {send cmd slow}",
+ "button .f2.fast -text {fast} -command {send cmd fast}",
+ "button .f2.axis -text {axis} -command {send cmd axis}",
+ "pack .f2.slow -side left",
+ "pack .f2.fast -side right",
+ "pack .f2.axis -side top",
+
+ "frame .f1",
+ "label .f1.txt -text { } -width " + string WIDTH,
+ "pack .f1.txt -side top -fill x",
+
+ "frame .f3",
+ "button .f3.menu -text {menu} -command {send cmd menu}",
+ "pack .f3.menu -side left",
+
+ "frame .pbd -bd 3",
+ "panel .p -width " + string WIDTH + " -height " + string HEIGHT,
+
+ "pack .f -side top -fill x",
+ "pack .f0 -side top -fill x",
+ "pack .f2 -side top -fill x",
+ "pack .f1 -side top -fill x",
+ "pack .f3 -side top -fill x",
+ "pack .p -in .pbd -fill both -expand 1",
+ "pack .pbd -side bottom -fill both -expand 1",
+ "pack propagate . 0",
+
+};
+
+rgba(d: ref Display, r: int, g: int, b: int, α: int): ref Image
+{
+ c := draw->setalpha((r<<24)|(g<<16)|(b<<8), α);
+ return d.newimage(((0, 0), (1, 1)), d.image.chans, 1, c);
+}
diff --git a/appl/wm/prof.b b/appl/wm/prof.b
new file mode 100644
index 00000000..71327e52
--- /dev/null
+++ b/appl/wm/prof.b
@@ -0,0 +1,323 @@
+implement Wmprof;
+
+include "sys.m";
+ sys: Sys;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "draw.m";
+ draw: Draw;
+include "tk.m";
+ tk: Tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+include "arg.m";
+ arg: Arg;
+include "profile.m";
+
+Prof: module{
+ init0: fn(ctxt: ref Draw->Context, argv: list of string): Profile->Prof;
+};
+
+prof: Prof;
+
+Wmprof: module{
+ init: fn(ctxt: ref Draw->Context, argl: list of string);
+};
+
+usage(s: string)
+{
+ sys->fprint(sys->fildes(2), "wm/prof: %s\n", s);
+ sys->fprint(sys->fildes(2), "usage: wm/prof [-e] [-m modname]... cmd [arg ... ]");
+ exit;
+}
+
+TXTBEGIN: con 3;
+
+init(ctxt: ref Draw->Context, argl: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ arg = load Arg Arg->PATH;
+
+ if(ctxt == nil)
+ fatal("wm not running");
+ sys->pctl(Sys->NEWPGRP, nil);
+
+ arg->init(argl);
+ while((o := arg->opt()) != 0){
+ case(o){
+ 'e' => ;
+ 'm' =>
+ if(arg->arg() == nil)
+ usage("missing module/file");
+ 's' =>
+ if(arg->arg() == nil)
+ usage("missing sample rate");
+ * =>
+ usage(sys->sprint("unknown option -%c", o));
+ }
+ }
+
+ stats := execprof(ctxt, argl);
+ if(stats.mods == nil)
+ exit;
+
+ tkclient->init();
+ (win, wmc) := tkclient->toplevel(ctxt, nil, hd argl, Tkclient->Resize|Tkclient->Hide);
+ tkc := chan of string;
+ tk->namechan(win, tkc, "tkc");
+ for(i := 0; i < len wincfg; i++)
+ cmd(win, wincfg[i]);
+ tkclient->onscreen(win, nil);
+ tkclient->startinput(win, "kbd"::"ptr"::nil);
+ createmenu(win, stats);
+ curc := 0;
+ cura := newprint(win, stats, curc);
+
+ for(;;){
+ alt{
+ c := <-win.ctxt.kbd =>
+ tk->keyboard(win, c);
+ c := <-win.ctxt.ptr =>
+ tk->pointer(win, *c);
+ c := <-win.ctxt.ctl or
+ c = <-win.wreq or
+ c = <-wmc =>
+ tkclient->wmctl(win, c);
+ c := <- tkc =>
+ (nil, toks) := sys->tokenize(c, " ");
+ case(hd toks){
+ "b" =>
+ if(curc > 0)
+ cura = newprint(win, stats, --curc);
+ "f" =>
+ if(curc < len stats.mods - 1)
+ cura = newprint(win, stats, ++curc);
+ "s" =>
+ if(cura != nil)
+ scroll(win, cura);
+ "m" =>
+ x := cmd(win, ".f cget actx");
+ y := cmd(win, ".f cget acty");
+ cmd(win, ".f.menu post " + x + " " + y);
+ * =>
+ curc = int hd toks;
+ cura = newprint(win, stats, curc);
+ }
+ }
+ }
+}
+
+execprof(ctxt: ref Draw->Context, argl: list of string): Profile->Prof
+{
+ {
+ prof = load Prof "/dis/prof.dis";
+ if(prof == nil)
+ fatal("cannot load profiler");
+ return prof->init0(ctxt, hd argl :: "-g" :: tl argl);
+ }
+ exception{
+ "fail:*" =>
+ return (nil, 0, nil);
+ }
+ return (nil, 0, nil);
+}
+
+newprint(win: ref Tk->Toplevel, p: Profile->Prof, i: int): array of int
+{
+ cmd(win, ".f.t delete 1.0 end");
+ cmd(win, "update");
+ m0, m1: list of Profile->Modprof;
+ for(m := p.mods; m != nil && --i >= 0; m = tl m)
+ m0 = m;
+ if(m == nil)
+ return nil;
+ m1 = tl m;
+ (name, nil, spath, nil, line, nil, nil, tot, nil, nil) := hd m;
+ name0 := name1 := "nil";
+ if(m0 != nil)
+ name0 = (hd m0).name;
+ if(m1 != nil)
+ name1 = (hd m1).name;
+ a := len name;
+ name += sys->sprint(" (%d%%) ", percent(tot, p.total));
+ cmd(win, ".f.t insert end {" + name + " <- " + name0 + " -> " + name1 + "}");
+ tag := gettag(win, tot, p.total);
+ cmd(win, ".f.t tag add " + tag + " " + "1.0" + " " + "1." + string a);
+ cmd(win, ".f.t insert end \n\n");
+ cmd(win, "update");
+ lineno := TXTBEGIN;
+ bio := bufio->open(spath, Bufio->OREAD);
+ if(bio == nil)
+ return nil;
+ i = 1;
+ ll := len line;
+ while((s := bio.gets('\n')) != nil){
+ f := 0;
+ if(i < ll)
+ f = line[i];
+ a = len s;
+ if(f > 0)
+ s = sys->sprint("%d%%\t%s", percent(f, tot), s);
+ else
+ s = sys->sprint("- \t%s", s);
+ b := len s;
+ cmd(win, ".f.t insert end " + tk->quote(s));
+ tag = gettag(win, f, tot);
+ cmd(win, ".f.t tag add " + tag + " " + string lineno + "." + string (b-a) + " " + string lineno + "." + string (b-1));
+ cmd(win, "update");
+ lineno++;
+ i++;
+ }
+ return line;
+}
+
+index(win: ref Tk->Toplevel, x: int, y: int): int
+{
+ t := cmd(win, ".f.t index @" + string x + "," + string y);
+ (nil, l) := sys->tokenize(t, ".");
+# sys->print("%d,%d -> %s\n", x, y, t);
+ return int hd l;
+}
+
+winextent(win: ref Tk->Toplevel): (int, int)
+{
+ w := int cmd(win, ".f.t cget -actwidth");
+ h := int cmd(win, ".f.t cget -actheight");
+ lw := index(win, 0, 0);
+ uw := index(win, w-1, h-1);
+ return (lw, uw);
+}
+
+see(win: ref Tk->Toplevel, line: int)
+{
+ cmd(win, ".f.t see " + string line + ".0");
+ cmd(win, "update");
+}
+
+scroll(win: ref Tk->Toplevel, line: array of int)
+{
+ (nil, uw) := winextent(win);
+ lno := TXTBEGIN;
+ ll := len line;
+ for(i := 1; i < ll; i++){
+ n := line[i];
+ if(n > 0 && lno > uw){
+ see(win, lno);
+ return;
+ }
+ lno++;
+ }
+ lno = TXTBEGIN;
+ ll = len line;
+ for(i = 1; i < ll; i++){
+ n := line[i];
+ if(n > 0){
+ see(win, lno);
+ return;
+ }
+ lno++;
+ }
+}
+
+cmd(top: ref Tk->Toplevel, s: string): string
+{
+ # sys->print("%s\n", s);
+ e := tk->cmd(top, s);
+ if (e != nil && e[0] == '!')
+ sys->fprint(sys->fildes(2), "tk error on '%s': %s\n", s, e);
+ return e;
+}
+
+fatal(s: string)
+{
+ sys->fprint(sys->fildes(2), "%s\n", s);
+ exit;
+}
+
+MENUMAX: con 20;
+
+createmenu(top: ref Tk->Toplevel, p: Profile->Prof )
+{
+ mn := ".f.menu";
+ cmd(top, "menu " + mn);
+ i := j := 0;
+ for(m := p.mods; m != nil; m = tl m){
+ name := (hd m).name;
+ cmd(top, mn + " add command -label " + name + " -command {send tkc " + string i + "}");
+ i++;
+ j++;
+ if(j == MENUMAX && tl m != nil){
+ cmd(top, mn + " add cascade -label MORE -menu " + mn + ".menu");
+ mn += ".menu";
+ cmd(top, "menu " + mn);
+ j = 0;
+ }
+ }
+}
+
+tags := array[256] of { * => byte 0 };
+
+gettag(win: ref Tk->Toplevel, n: int, d: int): string
+{
+ i := int ((real n/real d) * real 15);
+ if(i < 0 || i > 15)
+ i = 0;
+ s := "tag" + string i;
+ if(tags[i] == byte 0){
+ rgb := "#" + hex2(255-64*0)+hex2(255-64*(i/4))+hex2(255-64*(i%4));
+ cmd(win, ".f.t tag configure " + s + " -fg black -bg " + rgb);
+ tags[i] = byte 1;
+ }
+ return s;
+}
+
+percent(n: int, d: int): int
+{
+ return int ((real n/real d) * real 100);
+}
+
+hex(i: int): int
+{
+ if(i < 10)
+ return i+'0';
+ else
+ return i-10+'A';
+}
+
+hex2(i: int): string
+{
+ s := "00";
+ s[0] = hex(i/16);
+ s[1] = hex(i%16);
+ return s;
+}
+
+wincfg := array[] of {
+ "frame .f",
+ "text .f.t -width 809 -height 500 -state disabled -wrap char -bg white -yscrollcommand {.f.s set}",
+ "scrollbar .f.s -orient vertical -command {.f.t yview}",
+ "frame .i",
+ "button .i.b -bitmap small_color_left.bit -command {send tkc b}",
+ "button .i.f -bitmap small_color_right.bit -command {send tkc f}",
+ "button .i.s -bitmap small_find.bit -command {send tkc s}",
+ "button .i.m -bitmap small_reload.bit -command {send tkc m}",
+
+ "pack .i.b -side left",
+ "pack .i.f -side left",
+ "pack .i.s -side left",
+ "pack .i.m -side left",
+
+ "pack .f.s -fill y -side left",
+ "pack .f.t -fill both -expand 1",
+
+ "pack .i -fill x",
+ "pack .f -fill both -expand 1",
+ "pack propagate . 0",
+
+ "update",
+}; \ No newline at end of file
diff --git a/appl/wm/qt.b b/appl/wm/qt.b
new file mode 100644
index 00000000..de1cbcd8
--- /dev/null
+++ b/appl/wm/qt.b
@@ -0,0 +1,161 @@
+implement WmQt;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+
+include "tk.m";
+ tk: Tk;
+ Toplevel: import tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+ ctxt: ref Draw->Context;
+
+include "quicktime.m";
+ qt: QuickTime;
+
+WmQt: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Stopped, Playing: con iota;
+
+task_cfg := array[] of {
+ "canvas .c",
+ "frame .b",
+ "button .b.File -text File -command {send cmd file}",
+ "button .b.Stop -text Stop -command {send cmd stop}",
+ "button .b.Pause -text Pause -command {send cmd pause}",
+ "button .b.Play -text Play -command {send cmd play}",
+ "frame .f",
+ "label .f.file -text {File:}",
+ "label .f.name",
+ "pack .f.file .f.name -side left",
+ "pack .b.File .b.Stop .b.Pause .b.Play -side left",
+ "pack .f -fill x",
+ "pack .b -anchor w",
+ "pack .c -side bottom -fill both -expand 1",
+ "pack propagate . 0",
+};
+
+init(xctxt: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "qt: no window context\n");
+ raise "fail:bad context";
+ }
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient= load Tkclient Tkclient->PATH;
+
+ ctxt = xctxt;
+
+ tkclient->init();
+ (t, menubut) := tkclient->toplevel(ctxt.screen, "", "QuickTime Player", 0);
+
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+
+ tkclient->tkcmds(t, task_cfg);
+
+ tk->cmd(t, "bind . <Configure> {send cmd resize}");
+ tk->cmd(t, "update");
+
+ qt = load QuickTime QuickTime->PATH;
+ if(qt == nil) {
+ tkclient->dialog(t, "error -fg red", "Load Module",
+ "Failed to load the QuickTime interface:\n"+
+ sys->sprint("%r"),
+ 0, "Exit"::nil);
+ return;
+ }
+ qt->init();
+
+ fname := "";
+ ctl := chan of string;
+ state := Stopped;
+
+ for(;;) alt {
+ menu := <-menubut =>
+ if(menu == "exit")
+ return;
+ tkclient->wmctl(t, menu);
+ press := <-cmd =>
+ case press {
+ "file" =>
+ pat := list of {
+ "*.mov (Apple QuickTime Movie)"
+ };
+ fname = tkclient->filename(ctxt.screen, t, "Locate Movie", pat, "");
+ if(fname != nil) {
+ s := fname;
+ if(len s > 25)
+ s = "..."+fname[len s - 25:];
+ tk->cmd(t, ".f.name configure -text {"+s+"}");
+ tk->cmd(t, "update");
+ }
+ "play" =>
+ if(fname != nil)
+ spawn play(t, fname);
+ }
+ }
+}
+
+#
+# Parse the atoms describing a movie
+#
+moov(t: ref Toplevel, q: ref QuickTime->QD)
+{
+ for(;;) {
+ (h, l) := qt->q.atomhdr();
+ if(l < 0)
+ break;
+ case h {
+ * =>
+ qt->q.skipatom(l);
+ "mvhd" =>
+ err := qt->q.mvhd(l);
+ if(err == nil)
+ break;
+ tkclient->dialog(t, "error -fg red", "Parse Headers",
+ err,
+ 0, "Exit"::nil);
+ exit;
+ "trak" =>
+ err := qt->q.trak(l);
+ if(err == nil)
+ break;
+ tkclient->dialog(t, "error -fg red", "Parse Track",
+ err,
+ 0, "Exit"::nil);
+ exit;
+ }
+ }
+}
+
+play(t: ref Toplevel, file: string)
+{
+ (q, err) := qt->open(file);
+ if(err != nil) {
+ tkclient->dialog(t, "error -fg red", "Open Movie",
+ "Failed to open \""+file+"\"\n"+err,
+ 0, "Continue"::nil);
+ return;
+ }
+ for(;;) {
+ (h, l) := qt->q.atomhdr();
+ if(l < 0)
+ break;
+ case h {
+ * =>
+ qt->q.skipatom(l);
+ "moov" =>
+ moov(t, q);
+ }
+ }
+}
diff --git a/appl/wm/readmail.b b/appl/wm/readmail.b
new file mode 100644
index 00000000..e674bb4b
--- /dev/null
+++ b/appl/wm/readmail.b
@@ -0,0 +1,885 @@
+implement WmReadmail;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Context: import draw;
+
+include "tk.m";
+ tk: Tk;
+ Toplevel: import tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "dialog.m";
+ dialog: Dialog;
+
+include "selectfile.m";
+ selectfile: Selectfile;
+
+include "string.m";
+ str: String;
+
+include "keyring.m";
+ kr: Keyring;
+
+WmReadmail: module
+{
+ init: fn(ctxt: ref Draw->Context, args: list of string);
+};
+
+WmSendmail: module
+{
+ init: fn(ctxt: ref Draw->Context, args: list of string);
+};
+
+srv: Sys->Connection;
+main: ref Toplevel;
+ctxt: ref Context;
+nmesg: int;
+cmesg: int;
+map: array of byte;
+Ok, Deleted: con iota;
+username: string;
+
+mail_cfg := array[] of {
+ "frame .top",
+ "label .top.l -bitmap email.bit",
+ "frame .top.con",
+ "frame .top.con.b",
+ "button .top.con.b.con -bitmap mailcon -command {send msg connect}",
+ "bind .top.con.b.con <Enter> +{.top.status configure -text {connect/disconnect to mail server}}",
+ "button .top.con.b.next -bitmap mailnext -command {send msg next}",
+ "bind .top.con.b.next <Enter> +{.top.status configure -text {next message}}",
+ "button .top.con.b.prev -bitmap mailprev -command {send msg prev}",
+ "bind .top.con.b.prev <Enter> +{.top.status configure -text {previous message}}",
+ "button .top.con.b.del -bitmap maildel -command {send msg dele}",
+ "bind .top.con.b.del <Enter> +{.top.status configure -text {delete message}}",
+ "button .top.con.b.reply -bitmap mailreply -command {send msg reply}",
+ "bind .top.con.b.reply <Enter> +{.top.status configure -text {reply to message}}",
+ "button .top.con.b.fwd -bitmap mailforward",
+ "bind .top.con.b.fwd <Enter> +{.top.status configure -text {forward message}}",
+ "button .top.con.b.hdr -bitmap mailhdr -command {send msg hdrs}",
+ "bind .top.con.b.hdr <Enter> +{.top.status configure -text {fetch message headers}}",
+ "button .top.con.b.save -bitmap mailsave -command {send msg save}",
+ "bind .top.con.b.save <Enter> +{.top.status configure -text {save message}}",
+ "pack .top.con.b.con .top.con.b.prev .top.con.b.next .top.con.b.del .top.con.b.reply .top.con.b.fwd .top.con.b.hdr .top.con.b.save -padx 2 -side left",
+ "label .top.status -text {not connected ...} -anchor w",
+ "pack .top.l -side left",
+ "pack .top.con -side left -padx 10",
+ "pack .top.con.b .top.status -in .top.con -fill x -expand 1",
+ "frame .hdr",
+ "scrollbar .hdr.scroll -command {.hdr.t yview}",
+ "text .hdr.t -height 3c -yscrollcommand {.hdr.scroll set} -bg white",
+ "frame .hdr.pad -width 2c",
+ "pack .hdr.t -side left -fill x -expand 1",
+ "pack .hdr.scroll -side left -fill y",
+ "pack .hdr.pad",
+ "frame .body",
+ "scrollbar .body.scroll -command {.body.t yview}",
+ "text .body.t -width 15c -height 7c -yscrollcommand {.body.scroll set} -bg white",
+ "pack .body.t -side left -expand 1 -fill both",
+ "pack .body.scroll -side left -fill y",
+ "pack .top -anchor w -padx 5",
+ "pack .hdr -fill x -anchor w -padx 5 -pady 5",
+ "pack .body -expand 1 -fill both -padx 5 -pady 5",
+ "pack .b -padx 5 -pady 5 -fill x",
+ "pack propagate . 0",
+ "update"
+};
+
+con_cfg := array[] of {
+ "frame .b",
+ "button .b.ok -text {Connect} -command {send cmd ok}",
+ "button .b.can -text {Cancel} -command {send cmd can}",
+ "pack .b.ok .b.can -side left -fill x -padx 10 -pady 10 -expand 1",
+ "frame .l",
+ "label .l.h -text {Mail Server:} -anchor w",
+ "label .l.u -text {User Name:} -anchor w",
+ "label .l.s -text {Secret:} -anchor w",
+ "pack .l.h .l.u .l.s -fill both -expand 1",
+ "frame .e",
+ "entry .e.h",
+ "entry .e.u",
+ "entry .e.s -show •",
+ "pack .e.h .e.u .e.s -fill x",
+ "frame .f -borderwidth 2 -relief raised",
+ "pack .l .e -fill both -expand 1 -side left -in .f",
+ "pack .f",
+ "pack .b -fill x -expand 1",
+ "bind .e.h <Key-\n> {send cmd ok}",
+ "bind .e.u <Key-\n> {send cmd ok}",
+ "bind .e.s <Key-\n> {send cmd ok}",
+ "focus .e.s",
+};
+
+hdr_cfg := array[] of {
+ "scrollbar .sh -orient horizontal -command {.f.l xview}",
+ "scrollbar .f.sv -command {.f.l yview}",
+ "frame .f",
+ "listbox .f.l -width 80w -height 20h -yscrollcommand { .f.sv set} -xscrollcommand { .sh set}",
+ "pack .f.l -side left -fill both -expand 1",
+ "pack .f.sv -side left -fill y",
+ "pack .f -fill both -expand 1",
+ "pack .sh -fill x",
+ "pack propagate . 0",
+ "bind .f.l <Double-Button> { send tomain [.f.l get [.f.l curselection]] }",
+ "update",
+};
+
+init(xctxt: ref Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if (xctxt == nil) {
+ sys->fprint(sys->fildes(2), "readmail: no window context\n");
+ raise "fail:bad context";
+ }
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ dialog = load Dialog Dialog->PATH;
+ selectfile = load Selectfile Selectfile->PATH;
+ str = load String String->PATH;
+ kr = load Keyring Keyring->PATH;
+
+ ctxt = xctxt;
+
+ tkclient->init();
+ dialog->init();
+ selectfile->init();
+
+ tkargs := "";
+ argv = tl argv;
+ if(argv != nil) {
+ tkargs = hd argv;
+ argv = tl argv;
+ }
+
+ titlectl := chan of string;
+ (main, titlectl) = tkclient->toplevel(ctxt, tkargs, "Readmail: Reader", Tkclient->Appl);
+
+ msg := chan of string;
+ tk->namechan(main, msg, "msg");
+ hdr := chan of string;
+
+ for (c:=0; c<len mail_cfg; c++)
+ tk->cmd(main, mail_cfg[c]);
+ tkclient->onscreen(main, nil);
+ tkclient->startinput(main, "kbd"::"ptr"::nil);
+
+ for(;;) alt {
+ s := <-main.ctxt.kbd =>
+ tk->keyboard(main, s);
+ s := <-main.ctxt.ptr =>
+ tk->pointer(main, *s);
+ s := <-main.ctxt.ctl or
+ s = <-main.wreq or
+ s = <-titlectl =>
+ if(s == "exit") {
+ if(srv.dfd != nil) {
+ status("Updating mail box...");
+ pop3cmd("QUIT");
+ }
+ return;
+ }
+ tkclient->wmctl(main, s);
+ cmd := <-msg =>
+ case cmd {
+ "connect" =>
+ if(srv.dfd == nil) {
+ connect(main);
+ if(srv.dfd != nil)
+ initialize();
+ break;
+ }
+ disconnect();
+ "prev" =>
+ if(cmesg > nmesg) {
+ status("no more messages.");
+ break;
+ }
+ for(new := cmesg+1; new <= nmesg; new++) {
+ if(map[new] == byte Ok) {
+ cmesg = new;
+ loadmesg();
+ break;
+ }
+ }
+ "next" =>
+ for(new := cmesg-1; new >= 1; new--) {
+ if(map[new] == byte Ok) {
+ cmesg = new;
+ loadmesg();
+ break;
+ }
+ }
+ "dele" =>
+ delete();
+ if(cmesg > 0) {
+ cmesg--;
+ loadmesg();
+ }
+ "hdrs" =>
+ headers(hdr);
+ "save" =>
+ save();
+ "reply" =>
+ reply();
+ }
+ get := <-hdr =>
+ new := int get;
+ if(new < 1 || new > nmesg || map[new] != byte Ok)
+ break;
+ cmesg = new;
+ loadmesg();
+ }
+}
+
+headers(tomain: chan of string)
+{
+ (hdr, hdrctl) := tkclient->toplevel(ctxt, nil,
+ "Readmail: Headers", Tkclient->Appl);
+
+ tk->namechan(hdr, tomain, "tomain");
+
+ for (c:=0; c<len hdr_cfg; c++)
+ tk->cmd(hdr, hdr_cfg[c]);
+
+ for(i := 1; i <= nmesg; i++) {
+ if(map[i] == byte Deleted) {
+ info := sys->sprint("%4d ...Deleted...\n", i);
+ tk->cmd(hdr, ".f.l insert 0 '"+info);
+ continue;
+ }
+ if(topit(hdr, i) == 0)
+ break;
+ alt {
+ s := <-hdrctl =>
+ if(s == "exit")
+ return;
+ tkclient->wmctl(hdr, s);
+ * =>
+ ;
+ }
+ if((i%10) == 9)
+ tk->cmd(hdr, "update");
+ }
+ tk->cmd(hdr, "update");
+ tkclient->onscreen(hdr, nil);
+ tkclient->startinput(hdr, "kbd"::"ptr"::nil);
+
+ spawn hproc(hdrctl, hdr);
+}
+
+trunc(name: string): string
+{
+ for(i := 0; i < len name; i++)
+ if(name[i] == '<')
+ break;
+ i++;
+ if(i >= len name)
+ return name;
+ for(j := i; j < len name; j++)
+ if(name[j] == '>')
+ break;
+ return name[i:j];
+}
+
+topit(hdr: ref Toplevel, msg: int): int
+{
+ (err, s) := pop3cmd("TOP "+string msg+" 0");
+ if(err != nil) {
+ dialog->prompt(ctxt, hdr.image, "error -fg red", "POP3 Error",
+ "Ecountered a problem fetching headers\n"+err,
+ 0, "Dismiss"::nil);
+ return 0;
+ }
+
+ size := int s;
+ b := pop3body(size);
+ if(b == nil)
+ return 0;
+
+ from := getfield("from", b);
+ from = trunc(from);
+ date := getfield("date", b);
+ subj := getfield("subject", b);
+ if(len subj > 20)
+ subj = subj[0:19];
+
+ if(len subj > 0)
+ info := sys->sprint("%4d %5d %s \"%s\" %s", msg, size, from, subj, date);
+ else
+ info = sys->sprint("%4d %5d %s %s", msg, size, from, date);
+
+ tk->cmd(hdr, ".f.l insert 0 '"+info);
+ return 1;
+}
+
+mapdown(b: array of byte): string
+{
+ lb := len b;
+ l := array[lb] of byte;
+ for(i := 0; i < lb; i++) {
+ c := b[i];
+ if(c >= byte 'A' && c <= byte 'Z')
+ c += byte('a' - 'A');
+ l[i] = c;
+ }
+ return string l;
+}
+
+getfield(key: string, text: array of byte): string
+{
+ key[len key] = ':';
+ lk := len key;
+ cl := byte key[0];
+ cu := cl - byte ('a' - 'A');
+
+ lc: byte;
+ for(i := 0; i < len text - lk; i++) {
+ t := text[i];
+ if(t == byte '\n' && lc == byte '\n') # end header
+ break;
+ lc = t;
+ if(t != cu && t != cl)
+ continue;
+ if(key == mapdown(text[i:i+lk])) {
+ i += lk+1;
+ for(j := i+1; j < len text; j++) {
+ c := text[j];
+ if(c == byte '\r' || c == byte '\n')
+ break;
+ }
+ return string text[i:j];
+ }
+ }
+ return "";
+}
+
+hproc(wmctl: chan of string, top: ref Toplevel)
+{
+ for(;;) {
+ alt {
+ s := <-top.ctxt.kbd =>
+ tk->keyboard(top, s);
+ s := <-top.ctxt.ptr =>
+ tk->pointer(top, *s);
+ s := <-top.ctxt.ctl or
+ s = <-top.wreq or
+ s = <-wmctl =>
+ if(s == "exit")
+ return;
+ tkclient->wmctl(top, s);
+ }
+ }
+}
+
+reply()
+{
+ if(cmesg == 0) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Reply",
+ "No message to reply to",
+ 0, "Abort"::nil);
+ return;
+ }
+
+ hdr := tk->cmd(main, ".hdr.t get 1.0 end");
+ if(hdr == "") {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Reply",
+ "Mail has no header to reply to",
+ 0, "Abort"::nil);
+ return;
+ }
+
+ wmsender := load WmSendmail "/dis/wm/sendmail.dis";
+ if(wmsender == nil) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Reply",
+ "Failed to load mail sender:\n"+sys->sprint("%r"),
+ 0, "Abort"::nil);
+ return;
+ }
+
+ spawn wmsender->init(ctxt, "sendmail" :: hdr :: nil);
+}
+
+save()
+{
+ if(cmesg == 0) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Save",
+ "No current message",
+ 0, "Continue"::nil);
+ return;
+ }
+ pat := list of {
+ "*.let (Saved mail)",
+ "* (All files)"
+ };
+
+ fd: ref Sys->FD;
+ fname: string;
+ for(;;) {
+ fname = selectfile->filename(ctxt, main.image, "Save in Mailbox",
+ pat, "/usr/"+username+"/mail");
+ if(fname == nil)
+ return;
+
+ fd = sys->create(fname, sys->OWRITE, 8r660);
+ if(fd != nil)
+ break;
+
+ labs := list of {
+ "New name",
+ "Abort"
+ };
+
+ r := dialog->prompt(ctxt, main.image, "error -fg red", "Save",
+ "Failed to create "+sys->sprint("%s\n%r", fname),
+ 0, labs);
+ if(r == 1)
+ return;
+ }
+ s := tk->cmd(main, ".hdr.t get 1.0 end");
+ b := array of byte s;
+ r := sys->write(fd, b, len b);
+ if(r < 0) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Save",
+ "Error writing file"+sys->sprint("%s\n%r", fname),
+ 0, "Continue (not saved)":: nil);
+ return;
+ }
+ s = tk->cmd(main, ".body.t get 1.0 end");
+ b = array of byte s;
+ n := sys->write(fd, b, len b);
+ if(n < 0) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Save",
+ "Error writing file"+sys->sprint("%s\n%r", fname),
+ 0, "Continue (not saved)":: nil);
+ return;
+ }
+ status("wrote "+string(n+r)+" bytes.");
+}
+
+delete()
+{
+ if(srv.dfd == nil) {
+ dialog->prompt(ctxt, main.image, "warning -fg yellow", "Delete",
+ "You must be connected to delete messages",
+ 0, "Continue"::nil);
+ return;
+ }
+ (err, s) := pop3cmd("DELE "+string cmesg);
+ if(err != nil) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Delete",
+ "Encountered POP3 problem during delete\n"+err,
+ 0, "Continue"::nil);
+ return;
+ }
+ map[cmesg] = byte Deleted;
+ status(s);
+}
+
+status(msg: string)
+{
+ tk->cmd(main, ".top.status configure -text {"+msg+"}; update");
+}
+
+disconnect()
+{
+ (err, s) := pop3cmd("QUIT");
+ srv.dfd = nil;
+ tk->cmd(main, ".top.con configure -text Connect");
+ if(err != nil) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Disconnect",
+ "POP3 protocol problem\n"+err,
+ 0, "Proceed"::nil);
+ return;
+ }
+ status(s);
+}
+
+connect(parent: ref Toplevel)
+{
+ (t, conctl) := tkclient->toplevel(ctxt, postposn(parent),
+ "Connection Parameters", 0);
+
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+
+ for (c:=0; c<len con_cfg; c++)
+ tk->cmd(t, con_cfg[c]);
+
+ username = rf("/dev/user");
+ sv := rf("/usr/"+username+"/mail/popserver");
+ if(sv != "")
+ tk->cmd(t, ".e.h insert 0 '"+sv);
+
+ u := tk->cmd(t, ".e.u get");
+ if(u == "")
+ tk->cmd(t, ".e.u insert 0 '"+username);
+
+ tk->cmd(t, "update");
+ tkclient->onscreen(t, nil);
+ tkclient->startinput(t, "kbd"::"ptr"::nil);
+
+ for(;;) alt {
+ s := <-t.ctxt.kbd =>
+ tk->keyboard(t, s);
+ s := <-t.ctxt.ptr =>
+ tk->pointer(t, *s);
+ s := <-t.ctxt.ctl or
+ s = <-t.wreq or
+ s = <-conctl =>
+ if(s == "exit")
+ return;
+ tkclient->wmctl(t, s);
+ s := <-cmd =>
+ if(s == "can")
+ return;
+ server := tk->cmd(t, ".e.h get");
+ if(server == "") {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Connect",
+ "You must supply a server address",
+ 0, "Proceed"::nil);
+ break;
+ }
+ user := tk->cmd(t, ".e.u get");
+ if(user == "") {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Connect",
+ "You must supply a user name",
+ 0, "Proceed"::nil);
+ break;
+ }
+ pass := tk->cmd(t, ".e.s get");
+ if(pass == "") {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Connect",
+ "You must give a secret or password",
+ 0, "Proceed"::nil);
+ break;
+ }
+ if(dialer(t, server, user, pass) != 0)
+ return;
+ status("not connected");
+ }
+ srv.dfd = nil;
+}
+
+initialize()
+{
+ (err, s) := pop3cmd("STAT");
+ if(err != nil) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Mailbox Status",
+ "The following error occurred while "+
+ "checking your mailbox:\n"+err,
+ 0, "Dismiss"::nil);
+ srv.dfd = nil;
+ status("not connected");
+ return;
+ }
+
+ tk->cmd(main, ".top.con configure -text Disconnect; update");
+ nmesg = int s;
+ if(nmesg == 0) {
+ status("There are no messages.");
+ return;
+ }
+
+ map = array[nmesg+1] of byte;
+ for(i := 0; i <= nmesg; i++)
+ map[i] = byte Ok;
+
+ s = "";
+ if(nmesg > 1)
+ s = "s";
+ status("You have "+string nmesg+" message"+s);
+ cmesg = nmesg;
+ loadmesg();
+}
+
+loadmesg()
+{
+ if(srv.dfd == nil) {
+ dialog->prompt(ctxt, main.image, "warning -fg yellow", "Read",
+ "You must be connected to read messages",
+ 0, "Continue"::nil);
+ return;
+ }
+ (err, s) := pop3cmd("RETR "+sys->sprint("%d", cmesg));
+ if(err != nil) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Read",
+ "Error retrieving message:\n"+err,
+ 0, "Continue"::nil);
+ return;
+ }
+
+ tk->cmd(main, ".hdr.t delete 1.0 end; .body.t delete 1.0 end");
+ size := int s;
+
+ status("reading "+string size+" bytes ...");
+
+ b := pop3body(size);
+
+ (headr, body) := split(string b);
+ b = nil;
+ tk->cmd(main, ".hdr.t insert end '"+headr);
+ tk->cmd(main, ".body.t insert end '"+body);
+ tk->cmd(main, ".hdr.t see 1.0; .body.t see 1.0");
+ status("read message "+string cmesg+" of "+string nmesg+" , ready...");
+}
+
+split(text: string): (string, string)
+{
+ c, lc: int;
+ hdr, body: string;
+
+ hp := 0;
+ for(i := 0; i < len text; i++) {
+ c = text[i];
+ if(c == '\r')
+ continue;
+ hdr[hp++] = c;
+ if(lc == '\n' && c == '\n')
+ break;
+ lc = c;
+ }
+ bp := 0;
+ while(i < len text) {
+ c = text[i++];
+ if(c != '\r')
+ body[bp++] = c;
+ }
+ return (hdr, body);
+}
+
+dialer(t: ref Toplevel, server, user, pass: string): int
+{
+ ok: int;
+
+ for(;;) {
+ status("dialing server...");
+ (ok, srv) = sys->dial(netmkaddr(server, nil, "110"), nil);
+ if(ok >= 0)
+ break;
+
+ labs := list of {
+ "Retry",
+ "Cancel"
+ };
+ ok = dialog->prompt(ctxt, t.image, "error -fg", "Connect",
+ "The following error occurred while\n"+
+ "dialing the server: "+sys->sprint("%r"),
+ 0, labs);
+ if(ok != 0)
+ return 0;
+ }
+ status("connected...");
+ (err, s) := pop3resp();
+ if(err != nil) {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Connect",
+ "An error occurred during sign on.\n"+err,
+ 0, "Proceed"::nil);
+ return 0;
+ }
+ status(s);
+ (nil, s) = str->splitl(s, "<");
+ (chal, nil) := str->splitr(s, ">");
+ if(chal != nil){
+ ca := array of byte chal;
+ digest := array[kr->MD5dlen] of byte;
+ md5state := kr->md5(ca, len ca, nil, nil);
+ pa := array of byte pass;
+ kr->md5(pa, len pa, digest, md5state);
+ s = nil;
+ for(i := 0; i < kr->MD5dlen; i++)
+ s += sys->sprint("%2.2ux", int digest[i]);
+ (err, s) = pop3cmd("APOP "+user+" "+s);
+ if(err == nil) {
+ status("ready to serve...");
+ return 1;
+ } else {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Connect",
+ "Challenge/response failed.\n"+err,
+ 0, "Proceed"::nil);
+ return 0;
+ }
+ }
+ (err, s) = pop3cmd("USER "+user);
+ if(err != nil) {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Connect",
+ "An error occurred during login.\n"+err,
+ 0, "Proceed"::nil);
+ return 0;
+ }
+ (err, s) = pop3cmd("PASS "+pass);
+ if(err != nil) {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Connect",
+ "An error occurred during login.\n"+err,
+ 0, "Proceed"::nil);
+ return 0;
+ }
+ status("ready to serve...");
+ return 1;
+}
+
+rf(file: string): string
+{
+ fd := sys->open(file, sys->OREAD);
+ if(fd == nil)
+ return "";
+
+ buf := array[Sys->NAMEMAX] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return "";
+
+ return string buf[0:n];
+}
+
+postposn(parent: ref Toplevel): string
+{
+ x := int tk->cmd(parent, ".top.con cget -actx");
+ y := int tk->cmd(parent, ".top.con cget -acty");
+ h := int tk->cmd(parent, ".top.con cget -height");
+
+ return "-x "+string(x-2)+" -y "+string(y+h+2);
+}
+
+#
+# Talk POP3
+#
+pop3cmd(cmd: string): (string, string)
+{
+ cmd += "\r\n";
+# sys->print("->%s", cmd);
+ b := array of byte cmd;
+ l := len b;
+ n := sys->write(srv.dfd, b, l);
+ if(n != l)
+ return ("send to server:"+sys->sprint("%r"), nil);
+
+ return pop3resp();
+}
+
+pop3resp(): (string, string)
+{
+ s := "";
+ i := 0;
+ lastc := 0;
+ for(;;) {
+ c := pop3getc();
+ if(c == -1)
+ return ("read from server:"+sys->sprint("%r"), nil);
+ if(lastc == '\r' && c == '\n')
+ break;
+ s[i++] = c;
+ lastc = c;
+ }
+# sys->print("<-%s\n", s);
+ if(i < 3)
+ return ("short read from server", nil);
+ s = s[0:i-1];
+ if(s[0:3] == "+OK") {
+ i = 3;
+ while(i < len s && s[i] == ' ')
+ i++;
+ return (nil, s[i:]);
+ }
+ if(s[0:4] == "-ERR") {
+ i = 4;
+ while(s[i] == ' ' && i < len s)
+ i++;
+ return (s[i:], nil);
+ }
+ return ("invalid server response", nil);
+}
+
+pop3body(size: int): array of byte
+{
+ size += 512;
+ b := array[size] of byte;
+
+ cnt := emptypopbuf(b);
+ size -= cnt;
+
+ for(;;) {
+
+ if(cnt > 5 && string b[cnt-5:cnt] == "\r\n.\r\n") {
+ b = b[0:cnt-5];
+ break;
+ }
+ # resize buffer
+ if(size == 0) {
+ nb := array[len b + 4096] of byte;
+ nb[0:] = b;
+ size = len nb - len b;
+ b = nb;
+ nb = nil;
+ }
+ n := sys->read(srv.dfd, b[cnt:], len b - cnt);
+ if(n <= 0) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Read",
+ sys->sprint("Error retrieving message: %r"),
+ 0, "Continue"::nil);
+ return nil;
+ }
+ size -= n;
+ cnt += n;
+ }
+ return b;
+}
+
+Iob: adt
+{
+ nbyte: int;
+ posn: int;
+ buf: array of byte;
+};
+popbuf: Iob;
+
+pop3getc(): int
+{
+ if(popbuf.nbyte > 0) {
+ popbuf.nbyte--;
+ return int popbuf.buf[popbuf.posn++];
+ }
+ if(popbuf.buf == nil)
+ popbuf.buf = array[512] of byte;
+
+ popbuf.posn = 0;
+ n := sys->read(srv.dfd, popbuf.buf, len popbuf.buf);
+ if(n < 0)
+ return -1;
+
+ popbuf.nbyte = n-1;
+ return int popbuf.buf[popbuf.posn++];
+}
+
+emptypopbuf(a: array of byte) : int
+{
+ i := popbuf.nbyte;
+
+ if (i) {
+ a[0:] = popbuf.buf[popbuf.posn:(popbuf.posn+popbuf.nbyte)];
+ popbuf.nbyte = 0;
+ }
+
+ return i;
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, l) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}
diff --git a/appl/wm/remotelogon.b b/appl/wm/remotelogon.b
new file mode 100644
index 00000000..cb0be876
--- /dev/null
+++ b/appl/wm/remotelogon.b
@@ -0,0 +1,314 @@
+implement WmLogon;
+#
+# get a certificate to enable remote access.
+#
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Screen, Display, Image, Context, Point, Rect: import draw;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "arg.m";
+include "sh.m";
+include "newns.m";
+include "keyring.m";
+ keyring: Keyring;
+include "security.m";
+ login: Login;
+
+# XXX where to put the certificate: is the username already set to
+# something appropriate, with a home directory and keyring directory in that?
+
+# how do we find out the signer; presumably from the registry?
+# should do that before signing on; if we can't get it, then prompt for it.
+WmLogon: module {
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+cfg := array[] of {
+ "label .p -bitmap @/icons/inferno.bit -borderwidth 2 -relief raised",
+ "label .ul -text {User Name:} -anchor w",
+ "entry .ue -bg white",
+ "label .pl -text {Password:} -anchor w",
+ "entry .pe -bg white -show *",
+ "frame .f -borderwidth 2 -relief raised",
+ "grid .ul .ue -in .f",
+ "grid .pl .pe -in .f",
+ "pack .p .f -fill x",
+ "bind .ue <Key-\n> {focus next}",
+ "bind .ue {<Key-\t>} {focus next}",
+ "bind .pe <Key-\n> {send cmd ok}",
+ "bind .pe {<Key-\t>} {focus next}",
+ "focus .e",
+};
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ if(tkclient == nil){
+ sys->fprint(stderr(), "logon: cannot load %s: %r\n", Tkclient->PATH);
+ raise "fail:bad module";
+ }
+ login = load Login Login->PATH;
+ if(login == nil){
+ sys->fprint(stderr(), "logon: cannot load %s: %r\n", Login->PATH);
+ raise "fail:bad module";
+ }
+ keyring = load Keyring Keyring->PATH;
+ if(keyring == nil){
+ sys->fprint(stderr(), "logon: cannot load %s: %r\n", Keyring->PATH);
+ raise "fail:bad module";
+ }
+ sys->pctl(sys->NEWPGRP, nil);
+ tkclient->init();
+
+ (ctlwin, nil) := tkclient->toplevel(ctxt, nil, nil, Tkclient->Plain);
+ if(sys->fprint(ctlwin.ctxt.connfd, "request") == -1){
+ sys->fprint(stderr(), "logon: must be run as principal wm application\n");
+ raise "fail:lack of control";
+ }
+ addr: con "tcp!127.0.0.1!inflogin";
+ usr := "";
+ passwd := "";
+ arg := load Arg Arg->PATH;
+ if(arg != nil){
+ arg->init(args);
+ arg->setusage("usage: logon [-u user] [-p passwd] command [arg...]]\n");
+ while((opt := arg->opt()) != 0){
+ case opt{
+ 'u' =>
+ usr = arg->earg();
+ 'p' =>
+ passwd = arg->earg();
+ * =>
+ arg->usage();
+ }
+ }
+ args = arg->argv();
+ arg = nil;
+ } else
+ args = nil;
+ if(ctxt == nil)
+ sys->fprint(stderr(), "logon: must run under a window manager\n");
+
+ if (usr == nil || !logon(ctxt, usr, passwd, addr)) {
+ (panel, cmd) := makepanel(ctxt);
+ stop := chan of int;
+ spawn tkclient->handler(panel, stop);
+ for(;;) {
+ tk->cmd(panel, "focus .ue; update");
+ <-cmd;
+ usr = tk->cmd(panel, ".ue get");
+ if(usr == nil) {
+ notice(ctxt, "You must supply a user name to login");
+ continue;
+ }
+ passwd = tk->cmd(panel, ".pe get");
+
+ if(logon(ctxt, usr, passwd, addr)) {
+ panel = nil;
+ stop <-= 1;
+ break;
+ }
+ tk->cmd(panel, ".ue delete 0 end");
+ tk->cmd(panel, ".pe delete 0 end");
+ }
+ }
+ (ok, nil) := sys->stat("namespace");
+ if(ok >= 0) {
+ ns := load Newns Newns->PATH;
+ if(ns == nil)
+ notice(ctxt, "failed to load namespace builder");
+ else if ((nserr := ns->newns(nil, nil)) != nil)
+ notice(ctxt, "namespace error:\n"+nserr);
+ }
+ tkclient->wmctl(ctlwin, "endcontrol");
+ errch := chan of string;
+ spawn exec(ctxt, args, errch);
+ err := <-errch;
+ if (err != nil) {
+ sys->fprint(stderr(), "logon: %s\n", err);
+ raise "fail:exec failed";
+ }
+}
+
+makepanel(ctxt: ref Draw->Context): (ref Tk->Toplevel, chan of string)
+{
+ (t, nil) := tkclient->toplevel(ctxt, "-bg silver", nil, Tkclient->Plain);
+
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+
+ for(i := 0; i < len cfg; i++)
+ tk->cmd(t, cfg[i]);
+ err := tk->cmd(t, "variable lasterr");
+ if(err != nil) {
+ sys->fprint(stderr(), "logon: tk error: %s\n", err);
+ raise "fail:config error";
+ }
+ tk->cmd(t, "update");
+ org: Point;
+ ir := tk->rect(t, ".", Tk->Border|Tk->Required);
+ org.x = t.screenr.dx() / 2 - ir.dx() / 2;
+ org.y = t.screenr.dy() / 3 - ir.dy() / 2;
+ if (org.y < 0)
+ org.y = 0;
+ tk->cmd(t, ". configure -x " + string org.x + " -y " + string org.y);
+ tkclient->startinput(t, "kbd" :: "ptr" :: nil);
+ tkclient->onscreen(t, "onscreen");
+ return (t, cmd);
+}
+
+exec(ctxt: ref Draw->Context, argv: list of string, errch: chan of string)
+{
+ sys->pctl(sys->NEWFD, 0 :: 1 :: 2 :: nil);
+ if(argv == nil)
+ argv = "/dis/wm/toolbar.dis" :: nil;
+ else {
+ sh := load Sh Sh->PATH;
+ if(sh != nil){
+ sh->run(ctxt, "{$* &}" :: argv);
+ errch <-= nil;
+ exit;
+ }
+ }
+ {
+ cmd := load Command hd argv;
+ if (cmd == nil) {
+ errch <-= sys->sprint("cannot load %s: %r", hd argv);
+ } else {
+ errch <-= nil;
+ spawn cmd->init(ctxt, argv);
+ }
+ }exception{
+ "fail:*" =>
+ exit;
+ }
+}
+
+logon(ctxt: ref Draw->Context, uname, passwd, addr: string): int
+{
+ (err, info) := login->login(uname, passwd, addr);
+ if(err != nil){
+ notice(ctxt, "Login failed:\n" + err);
+ return 0;
+ }
+
+ keys := "/usr/" + user() + "/keyring";
+ if(sys->bind("#s", keys, Sys->MBEFORE) == -1){
+ notice(ctxt, sys->sprint("Cannot access keyring: %r"));
+ return 0;
+ }
+ fio := sys->file2chan(keys, "default");
+ if(fio == nil){
+ notice(ctxt, sys->sprint("Cannot create key file: %r"));
+ return 0;
+ }
+ sync := chan of int;
+ spawn infofile(fio, sync);
+ <-sync;
+
+ if(keyring->writeauthinfo(keys + "/default", info) == -1){
+ notice(ctxt, sys->sprint("Cannot write key file: %r"));
+ return 0;
+ }
+
+ return 1;
+}
+
+notecmd := array[] of {
+ "frame .f",
+ "label .f.l -bitmap error -foreground red",
+ "button .b -text Continue -command {send cmd done}",
+ "focus .f",
+ "bind .f <Key-\n> {send cmd done}",
+ "pack .f.l .f.m -side left -expand 1",
+ "pack .f .b",
+ "pack propagate . 0",
+};
+
+centre(t: ref Tk->Toplevel)
+{
+ sz := Point(int tk->cmd(t, ". cget -width"), int tk->cmd(t, ". cget -height"));
+ r := t.screenr;
+ if (sz.x > r.dx())
+ tk->cmd(t, ". configure -width " + string r.dx());
+ org: Point;
+ org.x = r.dx() / 2 - tk->rect(t, ".", 0).dx() / 2;
+ org.y = r.dy() / 3 - tk->rect(t, ".", 0).dy() / 2;
+ if (org.y < 0)
+ org.y = 0;
+ tk->cmd(t, ". configure -x " + string org.x + " -y " + string org.y);
+}
+
+notice(ctxt: ref Draw->Context, message: string)
+{
+ (t, nil) := tkclient->toplevel(ctxt, "-borderwidth 2 -relief raised", nil, Tkclient->Plain);
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+ tk->cmd(t, "label .f.m -anchor nw -text '"+message);
+ for(i := 0; i < len notecmd; i++)
+ tk->cmd(t, notecmd[i]);
+ centre(t);
+ tkclient->onscreen(t, "onscreen");
+ tkclient->startinput(t, "kbd"::"ptr"::nil);
+ stop := chan of int;
+ spawn tkclient->handler(t, stop);
+ tk->cmd(t, "update; cursor -default");
+ <-cmd;
+ stop <-= 1;
+}
+
+stderr(): ref Sys->FD
+{
+ return sys->fildes(2);
+}
+
+user(): string
+{
+ fd := sys->open("/dev/user", Sys->OREAD);
+ buf := array[8192] of byte;
+ if((n := sys->read(fd, buf, len buf)) > 0)
+ return string buf[0:n];
+ return "none";
+}
+
+infofile(fileio: ref Sys->FileIO, sync: chan of int)
+{
+ sys->pctl(Sys->NEWPGRP|Sys->NEWFD|Sys->NEWNS, nil);
+ sync <-= 1;
+
+ infodata: array of byte;
+ for(;;) alt {
+ (off, nbytes, fid, rc) := <-fileio.read =>
+ if(rc == nil)
+ break;
+ if(off > len infodata)
+ off = len infodata;
+ rc <-= (infodata[off:], nil);
+
+ (off, data, fid, wc) := <-fileio.write =>
+ if(wc == nil)
+ break;
+
+ if(off != len infodata){
+ wc <-= (0, "cannot be rewritten");
+ } else {
+ nid := array[len infodata+len data] of byte;
+ nid[0:] = infodata;
+ nid[len infodata:] = data;
+ infodata = nid;
+ wc <-= (len data, nil);
+ }
+ }
+}
diff --git a/appl/wm/reversi.b b/appl/wm/reversi.b
new file mode 100644
index 00000000..b1a85057
--- /dev/null
+++ b/appl/wm/reversi.b
@@ -0,0 +1,903 @@
+implement Reversi;
+
+#
+# Copyright © 2000 Vita Nuova Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+ Point, Rect, Image, Font, Context, Screen, Display: import draw;
+include "tk.m";
+ tk: Tk;
+ Toplevel: import tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+include "daytime.m";
+ daytime: Daytime;
+include "rand.m";
+ rand: Rand;
+
+# adtize and modularize
+
+stderr: ref Sys->FD;
+
+Reversi: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+nosleep, printout, auto: int;
+display: ref Draw->Display;
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ tkclient->init();
+ daytime = load Daytime Daytime->PATH;
+ rand = load Rand Rand->PATH;
+
+ argv = tl argv;
+ while(argv != nil){
+ s := hd argv;
+ if(s != nil && s[0] == '-'){
+ for(i := 1; i < len s; i++){
+ case s[i]{
+ 'a' => auto = 1;
+ 'p' => printout = 1;
+ 's' => nosleep = 1;
+ }
+ }
+ }
+ argv = tl argv;
+ }
+ stderr = sys->fildes(2);
+ rand->init(daytime->now());
+ daytime = nil;
+
+ if(ctxt == nil)
+ ctxt = tkclient->makedrawcontext();
+ display = ctxt.display;
+ (win, wmctl) := tkclient->toplevel(ctxt, "", "Reversi", Tkclient->Resize | Tkclient->Hide);
+ mainwin = win;
+ sys->pctl(Sys->NEWPGRP, nil);
+ cmdch := chan of string;
+ tk->namechan(win, cmdch, "cmd");
+ for(i := 0; i < len win_config; i++)
+ cmd(win, win_config[i]);
+ fittoscreen(win);
+ pid := -1;
+ sync := chan of int;
+ mvch := chan of (int, int, int);
+ initboard();
+ setimage();
+ spawn game(sync, mvch, 0);
+ pid = <- sync;
+ tkclient->onscreen(win, nil);
+ tkclient->startinput(win, "kbd"::"ptr"::nil);
+ lasts := 1;
+ for(;;){
+ alt{
+ s := <-win.ctxt.kbd =>
+ tk->keyboard(win, s);
+ s := <-win.ctxt.ptr =>
+ tk->pointer(win, *s);
+ s := <-win.ctxt.ctl or
+ s = <-win.wreq =>
+ tkclient->wmctl(win, s);
+ c := <- wmctl =>
+ case c{
+ "exit" =>
+ if(pid != -1)
+ kill(pid);
+ exit;
+ * =>
+ e := tkclient->wmctl(win, c);
+ if(e == nil && c[0] == '!'){
+ setimage();
+ drawboard();
+ }
+ }
+ c := <- cmdch =>
+ (nil, toks) := sys->tokenize(c, " ");
+ case hd toks{
+ "b1" or "b2" or "b3" =>
+ alt{
+ mvch <-= (SQUARE, int hd tl toks, int hd tl tl toks) => lasts = 1;
+ * => ;
+ }
+ "bh" or "bm" or "wh" or "wm" =>
+ col := BLACK;
+ knd := HUMAN;
+ if((hd toks)[0] == 'w')
+ col = WHITE;
+ if((hd toks)[1] == 'm')
+ knd = MACHINE;
+ kind[col] = knd;
+ "blev" or "wlev" =>
+ col := BLACK;
+ e := "be";
+ if((hd toks)[0] == 'w'){
+ col = WHITE;
+ e = "we";
+ }
+ sk := int cmd(win, ".f0." + e + " get");
+ if(sk > MAXPLIES)
+ sk = MAXPLIES;
+ if(sk >= 0)
+ skill[col] = sk;
+ "last" =>
+ alt{
+ mvch <-= (REPLAY, lasts, 0) => lasts++;
+ * => ;
+ }
+ * =>
+ ;
+ }
+ <- sync =>
+ pid = -1;
+ # exit;
+ spawn game(sync, mvch, 0);
+ pid = <- sync;
+ }
+ }
+}
+
+SQUARE, REPLAY: con iota;
+
+WIDTH: con 400;
+HEIGHT: con 400;
+
+SZB: con 8; # must be even
+SZF: con SZB+2;
+MC1: con SZB/2;
+MC2: con MC1+1;
+PIECES: con SZB*SZB;
+SQUARES: con PIECES-4;
+MAXMOVES: con 3*PIECES/2;
+NOMOVE: con SZF*SZF - 1;
+
+BLACK, WHITE, EMPTY, BORDER: con iota;
+MACHINE, HUMAN: con iota;
+SKILLB : con 6;
+SKILLW : con 0;
+MAXPLIES: con 6;
+
+moves: array of int;
+board: array of array of int; # for display
+brd: array of array of int; # for calculations
+val: array of array of int;
+order: array of (int, int);
+pieces: array of int;
+value: array of int;
+kind: array of int;
+skill: array of int;
+name: array of string;
+
+mainwin: ref Toplevel;
+brdimg: ref Image;
+brdr: Rect;
+brdx, brdy: int;
+
+black, white, green: ref Image;
+
+movech: chan of (int, int, int);
+
+setimage()
+{
+ brdw := int tk->cmd(mainwin, ".p cget -actwidth");
+ brdh := int tk->cmd(mainwin, ".p cget -actheight");
+# if (brdw > display.image.r.dx())
+# brdw = display.image.r.dx() - 4;
+# if (brdh > display.image.r.dy())
+# brdh = display.image.r.dy() - 40;
+
+ brdr = Rect((0,0), (brdw, brdh));
+ brdimg = display.newimage(brdr, display.image.chans, 0, Draw->White);
+ if(brdimg == nil)
+ fatal("not enough image memory");
+ tk->putimage(mainwin, ".p", brdimg, nil);
+}
+
+game(sync: chan of int, mvch: chan of (int, int, int), again: int)
+{
+ sync <-= sys->pctl(0, nil);
+ movech = mvch;
+ initbrd();
+ drawboard();
+ if(again)
+ replay(moves);
+ else
+ play();
+ sync <-= 0;
+}
+
+ordrect()
+{
+ i, j : int;
+
+ n := 0;
+ for(i = 1; i <= SZB; i++){
+ for(j = 1; j <= SZB; j++){
+ if(i < SZB/2 || j < SZB/2 || i > SZB/2+1 || j > SZB/2+1)
+ order[n++] = (i, j);
+ }
+ }
+ for(k := 0; k < SQUARES-1; k++){
+ for(l := k+1; l < SQUARES; l++){
+ (i, j) = order[k];
+ (a, b) := order[l];
+ if(val[i][j] > val[a][b])
+ (order[k], order[l]) = (order[l], order[k]);
+ }
+ }
+}
+
+initboard()
+{
+ i, j, k: int;
+
+ moves = array[MAXMOVES+1] of int;
+ board = array[SZF] of array of int;
+ brd = array[SZF] of array of int;
+ for(i = 0; i < SZF; i++){
+ board[i] = array[SZF] of int;
+ brd[i] = array[SZF] of int;
+ }
+ val = array[SZF] of array of int;
+ s := -pow(-1, SZB/2);
+ for(i = 0; i < SZF; i++){
+ val[i] = array[SZF] of int;
+ val[i][0] = val[i][SZF-1] = 0;
+ for(j = 1; j <= SZB; j++){
+ for(k = SZB/2; k > 0; k--){
+ if(i == k || i == SZB+1-k || j == k || j == SZB+1-k){
+ val[i][j] = s*pow(-7, SZB/2-k);
+ break;
+ }
+ }
+ }
+ }
+ order = array[SQUARES] of (int, int);
+ ordrect();
+ pieces = array[2] of int;
+ value = array[2] of int;
+ kind = array[2] of int;
+ kind[BLACK] = MACHINE;
+ if(auto)
+ kind[WHITE] = MACHINE;
+ else
+ kind[WHITE] = HUMAN;
+ skill = array[2] of int;
+ skill[BLACK] = SKILLB;
+ skill[WHITE] = SKILLW;
+ name = array[2] of string;
+ name[BLACK] = "black";
+ name[WHITE] = "white";
+ black = display.color(Draw->Black);
+ white = display.color(Draw->White);
+ green = display.color(Draw->Green);
+}
+
+initbrd()
+{
+ i, j: int;
+
+ for(i = 0; i < SZF; i++)
+ for(j = 0; j < SZF; j++)
+ brd[i][j] = EMPTY;
+ for(i = 0; i < SZF; i++)
+ brd[i][0] = brd[i][SZF-1] = BORDER;
+ for(j = 0; j< SZF; j++)
+ brd[0][j] = brd[SZF-1][j] = BORDER;
+ brd[MC1][MC1] = brd[MC2][MC2] = BLACK;
+ brd[MC1][MC2] = brd[MC2][MC1] = WHITE;
+ for(i = 0; i < SZF; i++)
+ for(j = 0; j < SZF; j++)
+ board[i][j] = brd[i][j];
+ pieces[BLACK] = pieces[WHITE] = 2;
+ value[BLACK] = value[WHITE] = -2;
+}
+
+plays := 0;
+bscore := 0;
+wscore := 0;
+bwins := 0;
+wwins := 0;
+
+play()
+{
+ n := 0;
+ for(i := 0; i <= MAXMOVES; i++)
+ moves[i] = NOMOVE;
+ if(plays&1)
+ (first, second) := (WHITE, BLACK);
+ else
+ (first, second) = (BLACK, WHITE);
+ if(printout)
+ sys->print("%d\n", first);
+ moves[n++] = first;
+ m1 := m2 := 1;
+ for(;;){
+ if(pieces[BLACK]+pieces[WHITE] == PIECES)
+ break;
+ m2 = m1;
+ m1 = move(first, second);
+ if(printout)
+ sys->print("%d\n", m1);
+ moves[n++] = m1;
+ if(!m1 && !m2)
+ break;
+ (first, second) = (second, first);
+ }
+ if(auto)
+ sys->print("score: %d-%d\n", pieces[BLACK], pieces[WHITE]);
+ bscore += pieces[BLACK];
+ wscore += pieces[WHITE];
+ if(pieces[BLACK] > pieces[WHITE])
+ bwins++;
+ else if(pieces[BLACK] < pieces[WHITE])
+ wwins++;
+ plays++;
+ if(auto)
+ sys->print(" black: %d white: %d draw: %d total: (%d-%d)\n", bwins, wwins, plays-bwins-wwins, bscore, wscore);
+ puts(sys->sprint("black %d:%d white", pieces[BLACK], pieces[WHITE]));
+ sleep(2000);
+ puts(sys->sprint("black %d:%d white", bwins, wwins));
+ sleep(2000);
+}
+
+replay(moves: array of int)
+{
+ n := 0;
+ first := moves[n++];
+ second := BLACK+WHITE-first;
+ m1 := m2 := 1;
+ while (pieces[BLACK]+pieces[WHITE] < PIECES){
+ m2 = m1;
+ m1 = moves[n++];
+ if(m1 == NOMOVE)
+ break;
+ if(m1 != 0)
+ makemove(m1/SZF, m1%SZF, first, second, 1, 0);
+ if(!m1 && !m2)
+ break;
+ (first, second) = (second, first);
+ }
+ # sys->print("score: %d-%d\n", pieces[BLACK], pieces[WHITE]);
+}
+
+lastmoves(p: int, moves: array of int)
+{
+ initbrd();
+ k := MAXMOVES+1;
+ for(i := 0; i <= MAXMOVES; i++){
+ if(moves[i] == NOMOVE){
+ k = i;
+ break;
+ }
+ }
+ if(k-p < 1)
+ p = k-1;
+ for(i = k-p; i < k; i++)
+ if(moves[i] == 0)
+ p++;
+ if(k-p < 1)
+ p = k-1;
+ n := 0;
+ me := moves[n++];
+ you := BLACK+WHITE-me;
+ while(n < k-p){
+ m := moves[n++];
+ if(m != 0)
+ makemove(m/SZF, m%SZF, me, you, 1, 1);
+ (me, you) = (you, me);
+ }
+ for(i = 0; i < SZF; i++)
+ for(j := 0; j < SZF; j++)
+ board[i][j] = brd[i][j];
+ drawboard();
+ sleep(1000);
+ while(n < k){
+ m := moves[n++];
+ if(m != 0)
+ makemove(m/SZF, m%SZF, me, you, 1, 0);
+ if(n < k)
+ sleep(500);
+ (me, you) = (you, me);
+ }
+}
+
+move(me: int, you: int): int
+{
+ if(kind[me] == MACHINE){
+ puts("machine " + name[me] + " move");
+ m := genmove(me, you);
+ if(!m){
+ puts("machine " + name[me] + " cannot go");
+ sleep(2000);
+ }
+ return m;
+ }
+ else{
+ m, n: int;
+
+ mvs := findmoves(me, you);
+ if(mvs == nil){
+ puts("human " + name[me] + " cannot go");
+ sleep(2000);
+ return 0;
+ }
+ for(;;){
+ puts("human " + name[me] + " move");
+ (m, n) = getmove();
+ if(m < 1 || n < 1 || m > SZB || n > SZB)
+ continue;
+ if(brd[m][n] == EMPTY)
+ (valid, nil) := makemove(m, n, me, you, 0, 0);
+ else
+ valid = 0;
+ if(valid)
+ break;
+ puts("illegal move");
+ sleep(2000);
+ }
+ makemove(m, n, me, you, 1, 0);
+ return m*SZF+n;
+ }
+}
+
+fullsrch: int;
+
+genmove(me: int, you: int): int
+{
+ m, n, v: int;
+
+ mvs := findmoves(me, you);
+ if(mvs == nil)
+ return 0;
+ if(skill[me] == 0){
+ l := len mvs;
+ r := rand->rand(l);
+ # r = 0;
+ while(--r >= 0)
+ mvs = tl mvs;
+ (m, n) = hd mvs;
+ }
+ else{
+ plies := skill[me];
+ left := PIECES-(pieces[BLACK]+pieces[WHITE]);
+ if(left < plies) # limit search
+ plies = left;
+ else if(left < 2*plies) # expand search to end
+ plies = left;
+ else{ # expand search nearer end of game
+ k := left/plies;
+ if(k < 3)
+ plies = ((k+2)*plies)/(k+1);
+ }
+ fullsrch = plies == left;
+ visits = leaves = 0;
+ (v, (m, n)) = minimax(me, you, plies, ∞, 1);
+ if(0){
+ # if((m==2&&n==2&&brd[1][1]!=BLACK) ||
+ # (m==2&&n==7&&brd[1][8]!=BLACK) ||
+ # (m==7&&n==2&&brd[8][1]!=BLACK) ||
+ # (m==7&&n==7&&brd[8][8]!=BLACK)){
+ while(mvs != nil){
+ (a, b) := hd mvs;
+ (nil, sqs) := makemove(a, b, me, you, 1, 1);
+ (v0, nil) := minimax(you, me, plies-1, ∞, 1);
+ sys->print(" (%d, %d): %d\n", a, b, v0);
+ undomove(a, b, me, you, sqs);
+ mvs = tl mvs;
+ }
+ if(!fullsrch){
+ sys->print("best move is %d, %d\n", m, n);
+ kind[WHITE] = HUMAN;
+ }
+ }
+ if(auto)
+ sys->print("eval = %d plies=%d goes=%d visits=%d\n", v, plies, len mvs, leaves);
+ }
+ makemove(m, n, me, you, 1, 0);
+ return m*SZF+n;
+}
+
+findmoves(me: int, you: int): list of (int, int)
+{
+ mvs: list of (int, int);
+
+ for(k := 0; k < SQUARES; k++){
+ (i, j) := order[k];
+ if(brd[i][j] == EMPTY){
+ (valid, nil) := makemove(i, j, me, you, 0, 0);
+ if(valid)
+ mvs = (i, j) :: mvs;
+ }
+ }
+ return mvs;
+}
+
+makemove(m: int, n: int, me: int, you: int, move: int, gen: int): (int, list of (int, int))
+{
+ sqs: list of (int, int);
+
+ if(move){
+ pieces[me]++;
+ value[me] += val[m][n];
+ brd[m][n] = me;
+ if(!gen){
+ board[m][n] = me;
+ drawpiece(m, n, me, 1);
+ panelupdate();
+ sleep(1000);
+ }
+ }
+ valid := 0;
+ for(i := -1; i < 2; i++){
+ for(j := -1; j < 2; j++){
+ if(i != 0 || j != 0){
+ v: int;
+
+ (v, sqs) = dirmove(m, n, i, j, me, you, move, gen, sqs);
+ valid |= v;
+ if (valid && !move)
+ return (1, sqs);
+ }
+ }
+ }
+ if(!valid && move)
+ fatal(sys->sprint("bad makemove call (%d, %d)", m, n));
+ return (valid, sqs);
+}
+
+dirmove(m: int, n: int, dx: int, dy: int, me: int, you: int, move: int, gen: int, sqs: list of (int, int)): (int, list of (int, int))
+{
+ p := 0;
+ m += dx;
+ n += dy;
+ while(brd[m][n] == you){
+ m += dx;
+ n += dy;
+ p++;
+ }
+ if(p > 0 && brd[m][n] == me){
+ if(move){
+ pieces[me] += p;
+ pieces[you] -= p;
+ m -= p*dx;
+ n -= p*dy;
+ while(--p >= 0){
+ brd[m][n] = me;
+ value[me] += val[m][n];
+ value[you] -= val[m][n];
+ if(gen)
+ sqs = (m, n) :: sqs;
+ else{
+ board[m][n] = me;
+ drawpiece(m, n, me, 0);
+ # sleep(500);
+ panelupdate();
+ }
+ m += dx;
+ n += dy;
+ }
+ }
+ return (1, sqs);
+ }
+ return (0, sqs);
+}
+
+undomove(m: int, n: int, me: int, you: int, sqs: list of (int, int))
+{
+ brd[m][n] = EMPTY;
+ pieces[me]--;
+ value[me] -= val[m][n];
+ for(; sqs != nil; sqs = tl sqs){
+ (x, y) := hd sqs;
+ brd[x][y] = you;
+ pieces[me]--;
+ pieces[you]++;
+ value[me] -= val[x][y];
+ value[you] += val[x][y];
+ }
+}
+
+getmove(): (int, int)
+{
+ k, x, y: int;
+
+ (k, x, y) = <- movech;
+ if(k == REPLAY){
+ lastmoves(x, moves);
+ return getmove();
+ }
+ return (x/brdx+1, y/brdy+1);
+}
+
+drawboard()
+{
+ brdx = brdr.dx()/SZB;
+ brdy = brdr.dy()/SZB;
+ brdimg.draw(brdr, green, nil, (0, 0));
+ for(i := 1; i < SZB; i++)
+ drawline(lmap(i, 0), lmap(i, SZB));
+ for(j := 1; j < SZB; j++)
+ drawline(lmap(0, j), lmap(SZB, j));
+ for(i = 1; i <= SZB; i++){
+ for(j = 1; j <= SZB; j++){
+ if (board[i][j] == BLACK || board[i][j] == WHITE)
+ drawpiece(i, j, board[i][j], 0);
+ }
+ }
+ panelupdate();
+}
+
+drawpiece(m, n, p, flash: int)
+{
+ if(p == BLACK)
+ src := black;
+ else
+ src = white;
+ if(0 && flash && kind[p] == MACHINE){
+ for(i := 0; i < 4; i++){
+ brdimg.fillellipse(cmap(m, n), 3*brdx/8, 3*brdy/8, src, (0, 0));
+ panelupdate();
+ sys->sleep(250);
+ brdimg.fillellipse(cmap(m, n), 3*brdx/8, 3*brdy/8, green, (0, 0));
+ panelupdate();
+ sys->sleep(250);
+ }
+ }
+ brdimg.fillellipse(cmap(m, n), 3*brdx/8, 3*brdy/8, src, (0, 0));
+}
+
+panelupdate()
+{
+ tk->cmd(mainwin, sys->sprint(".p dirty %d %d %d %d", brdr.min.x, brdr.min.y, brdr.max.x, brdr.max.y));
+ tk->cmd(mainwin, "update");
+}
+
+drawline(p0, p1: Point)
+{
+ brdimg.line(p0, p1, Draw->Endsquare, Draw->Endsquare, 0, brdimg.display.black, (0, 0));
+}
+
+cmap(m, n: int): Point
+{
+ return brdr.min.add((m*brdx-brdx/2, n*brdy-brdy/2));
+}
+
+lmap(m, n: int): Point
+{
+ return brdr.min.add((m*brdx, n*brdy));
+}
+
+∞: con (1<<30);
+MAXVISITS: con 1024;
+
+visits, leaves : int;
+
+minimax(me: int, you: int, plies: int, αβ: int, mv: int): (int, (int, int))
+{
+ if(plies == 0){
+ visits++;
+ leaves++;
+ if(visits == MAXVISITS){
+ visits = 0;
+ sys->sleep(0);
+ }
+ return (eval(me, you), (0, 0));
+ }
+ mvs := findmoves(me, you);
+ if(mvs == nil){
+ if(mv)
+ (v, nil) := minimax(you, me, plies, ∞, 0);
+ else
+ (v, nil) = minimax(you, me, plies-1, ∞, 0);
+ return (-v, (0, 0));
+ }
+ bestv := -∞;
+ bestm := (0, 0);
+ e := 0;
+ for(; mvs != nil; mvs = tl mvs){
+ (m, n) := hd mvs;
+ (nil, sqs) := makemove(m, n, me, you, 1, 1);
+ (v, nil) := minimax(you, me, plies-1, -bestv, 1);
+ v = -v;
+ undomove(m, n, me, you, sqs);
+ if(v > bestv || (v == bestv && rand->rand(++e) == 0)){
+ if(v > bestv)
+ e = 1;
+ bestv = v;
+ bestm = (m, n);
+ if(bestv >= αβ)
+ return (∞, (0, 0));
+ }
+ }
+ return (bestv, bestm);
+}
+
+eval(me: int, you: int): int
+{
+ d := pieces[me]-pieces[you];
+ if(fullsrch)
+ return d;
+ n := pieces[me]+pieces[you];
+ v := 0;
+ for(i := 1; i <= SZB; i += SZB-1)
+ for(j := 1; j <= SZB; j += SZB-1)
+ v += line(i, j, me, you);
+ return (PIECES-n)*(value[me]-value[you]+v) + n*d;
+}
+
+line(m: int, n: int, me: int, you: int): int
+{
+ if(brd[m][n] == EMPTY)
+ return 0;
+ dx := dy := -1;
+ if(m == 1)
+ dx = 1;
+ if(n == 1)
+ dy = 1;
+ return line0(m, n, 0, dy, me, you) +
+ line0(m, n, dx, 0, me, you) +
+ line0(m, n, dx, dy, me, you);
+}
+
+line0(m: int, n: int, dx: int, dy: int, me: int, you: int): int
+{
+ v := 0;
+ p := brd[m][n];
+ i := val[1][1];
+ while(brd[m][n] == p){
+ v += i;
+ m += dx;
+ n += dy;
+ }
+ if(p == you)
+ return -v;
+ if(p == me)
+ return v;
+ return v;
+}
+
+pow(n: int, m: int): int
+{
+ p := 1;
+ while(--m >= 0)
+ p *= n;
+ return p;
+}
+
+fatal(s: string)
+{
+ sys->fprint(stderr, "%s\n", s);
+ exit;
+}
+
+sleep(t: int)
+{
+ if(nosleep)
+ sys->sleep(0);
+ else
+ sys->sleep(t);
+}
+
+kill(pid: int): int
+{
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd == nil)
+ return -1;
+ if(sys->write(fd, array of byte "kill", 4) != 4)
+ return -1;
+ return 0;
+}
+
+cmd(top: ref Toplevel, s: string): string
+{
+ e := tk->cmd(top, s);
+ if (e != nil && e[0] == '!')
+ sys->fprint(stderr, "reversi: tk error on '%s': %s\n", s, e);
+ return e;
+}
+
+# swidth: int;
+# sfont: ref Font;
+
+# gettxtattrs()
+# {
+# swidth = int cmd(mainwin, ".f1.txt cget -width"); # always initial value ?
+# f := cmd(mainwin, ".f1.txt cget -font");
+# sfont = Font.open(brdimg.display, f);
+# }
+
+puts(s: string)
+{
+ # while(sfont.width(s) > swidth)
+ # s = s[0: len s -1];
+ cmd(mainwin, ".f1.txt configure -text {" + s + "}");
+ cmd(mainwin, "update");
+}
+
+fittoscreen(win: ref Tk->Toplevel)
+{
+ Point: import draw;
+ if (display.image == nil)
+ return;
+ r := display.image.r;
+ scrsize := Point(r.dx(), r.dy());
+ bd := int cmd(win, ". cget -bd");
+ winsize := Point(int cmd(win, ". cget -actwidth") + bd * 2, int cmd(win, ". cget -actheight") + bd * 2);
+ if (winsize.x > scrsize.x)
+ cmd(win, ". configure -width " + string (scrsize.x - bd * 2));
+ if (winsize.y > scrsize.y)
+ cmd(win, ". configure -height " + string (scrsize.y - bd * 2));
+ actr: Rect;
+ actr.min = Point(int cmd(win, ". cget -actx"), int cmd(win, ". cget -acty"));
+ actr.max = actr.min.add((int cmd(win, ". cget -actwidth") + bd*2,
+ int cmd(win, ". cget -actheight") + bd*2));
+ (dx, dy) := (actr.dx(), actr.dy());
+ if (actr.max.x > r.max.x)
+ (actr.min.x, actr.max.x) = (r.max.x - dx, r.max.x);
+ if (actr.max.y > r.max.y)
+ (actr.min.y, actr.max.y) = (r.max.y - dy, r.max.y);
+ if (actr.min.x < r.min.x)
+ (actr.min.x, actr.max.x) = (r.min.x, r.min.x + dx);
+ if (actr.min.y < r.min.y)
+ (actr.min.y, actr.max.y) = (r.min.y, r.min.y + dy);
+ cmd(win, ". configure -x " + string actr.min.x + " -y " + string actr.min.y);
+ cmd(win, "update");
+}
+
+win_config := array[] of {
+ "frame .f",
+ "button .f.last -text {last move} -command {send cmd last}",
+ "menubutton .f.bk -text Black -menu .f.bk.bm",
+ "menubutton .f.wk -text White -menu .f.wk.wm",
+ "menu .f.bk.bm",
+ ".f.bk.bm add command -label Human -command { send cmd bh }",
+ ".f.bk.bm add command -label Machine -command { send cmd bm }",
+ "menu .f.wk.wm",
+ ".f.wk.wm add command -label Human -command { send cmd wh }",
+ ".f.wk.wm add command -label Machine -command { send cmd wm }",
+ "pack .f.bk -side left",
+ "pack .f.wk -side right",
+ "pack .f.last -side top",
+
+ "frame .f0",
+ "label .f0.bl -text {Black level}",
+ "label .f0.wl -text {White level}",
+ "entry .f0.be -width 32",
+ "entry .f0.we -width 32",
+ ".f0.be insert 0 " + string SKILLB,
+ ".f0.we insert 0 " + string SKILLW,
+ "pack .f0.bl -side left",
+ "pack .f0.be -side left",
+ "pack .f0.wl -side right",
+ "pack .f0.we -side right",
+
+ "frame .f1",
+ "label .f1.txt -text { } -width " + string WIDTH,
+ "pack .f1.txt -side top -fill x",
+
+ "panel .p -width " + string WIDTH + " -height " + string HEIGHT,
+
+ "pack .f -side top -fill x",
+ "pack .f0 -side top -fill x",
+ "pack .f1 -side top -fill x",
+ "pack .p -side bottom -fill both -expand 1",
+ "pack propagate . 0",
+
+ "bind .p <Button-1> {send cmd b1 %x %y}",
+ "bind .p <Button-2> {send cmd b2 %x %y}",
+ "bind .p <Button-3> {send cmd b3 %x %y}",
+ "bind .f0.be <Key-\n> {send cmd blev}",
+ "bind .f0.we <Key-\n> {send cmd wlev}",
+ "update",
+};
diff --git a/appl/wm/rmtdir.b b/appl/wm/rmtdir.b
new file mode 100644
index 00000000..d63d409a
--- /dev/null
+++ b/appl/wm/rmtdir.b
@@ -0,0 +1,215 @@
+implement WmRmtdir;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+
+include "tk.m";
+ tk: Tk;
+ Toplevel: import tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "keyring.m";
+include "security.m";
+
+t: ref Toplevel;
+
+WmRmtdir: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Wm: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+rmt_config := array[] of {
+ "frame .f",
+ "label .f.l -text Address:",
+ "entry .f.e",
+ "pack .f.l .f.e -side left",
+ "label .status -text {Enter net!machine ...} -anchor w",
+ "pack .Wm_t .status .f -fill x",
+ "bind .f.e <Key-\n> {send cmd dial}",
+ "frame .b",
+ "radiobutton .b.none -variable alg -value none -anchor w -text '"+
+ "Authentication without SSL",
+ "radiobutton .b.clear -variable alg -value clear -anchor w -text '"+
+ "Authentication with SSL clear",
+ "radiobutton .b.sha -variable alg -value sha -anchor w -text '"+
+ "Authentication with SHA hash",
+ "radiobutton .b.md5 -variable alg -value md5 -anchor w -text '"+
+ "Authentication with MD5 hash",
+ "radiobutton .b.rc4 -variable alg -value rc4 -anchor w -text '"+
+ "Authentication with RC4 encryption",
+ "radiobutton .b.sharc4 -variable alg -value sha/rc4 -anchor w -text '"+
+ "Authentication with SHA and RC4",
+ "radiobutton .b.md5rc4 -variable alg -value md5/rc4 -anchor w -text '"+
+ "Authentication with MD5 and RC4",
+ "pack .b.none .b.clear .b.sha .b.md5 .b.rc4 .b.sharc4 .b.md5rc4 -fill x",
+ "pack .b -fill x",
+ ".b.none invoke",
+ "update",
+};
+
+init(ctxt: ref Draw->Context, nil: list of string)
+{
+ menubut : chan of string;
+
+ sys = load Sys Sys->PATH;
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "rmtdir: no window context\n");
+ raise "fail:bad context";
+ }
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient= load Tkclient Tkclient->PATH;
+
+ tkclient->init();
+
+ (t, menubut) = tkclient->toplevel(ctxt, "", sysname()+": Remote Connection", 0);
+
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+
+ for (i:=0; i<len rmt_config; i++)
+ tk->cmd(t, rmt_config[i]);
+ tkclient->onscreen(t, nil);
+ tkclient->startinput(t, "kbd"::"ptr"::nil);
+
+ for(;;) alt {
+ s := <-t.ctxt.kbd =>
+ tk->keyboard(t, s);
+ s := <-t.ctxt.ptr =>
+ tk->pointer(t, *s);
+ s := <-t.ctxt.ctl or
+ s = <-t.wreq or
+ s = <-menubut =>
+ tkclient->wmctl(t, s);
+ <-cmd =>
+ addr := tk->cmd(t, ".f.e get");
+ status("Dialing");
+ (ok, c) := sys->dial(netmkaddr(addr, "tcp", "styx"), nil);
+ if(ok < 0) {
+ tk->cmd(t, ".status configure -text {Failed: "+
+ sys->sprint("%r")+"}; update");
+ break;
+ }
+ status("Authenticate");
+ alg := tk->cmd(t, "variable alg");
+
+ kr := load Keyring Keyring->PATH;
+ if(kr == nil){
+ tk->cmd(t, ".status configure -text {Error: can't load module Keyring "+
+ sys->sprint("%r")+"}; update");
+ break;
+ }
+
+ user := user();
+ kd := "/usr/" + user + "/keyring/";
+ cert := kd + netmkaddr(addr, "tcp", "");
+ (ok, nil) = sys->stat(cert);
+ if(ok < 0)
+ cert = kd + "default";
+
+ ai := kr->readauthinfo(cert);
+ if(ai == nil){
+ tk->cmd(t, ".status configure -text {Error: certificate for "+
+ sys->sprint("%s",addr)+" not found}; update");
+ wmgetauthinfo := load Wm "/dis/wm/wmgetauthinfo.dis";
+ if(wmgetauthinfo == nil){
+ tk->cmd(t, ".status configure -text {Error: can't load module wmgetauthinfo.dis}; update");
+ exit;
+ }
+ spawn wmgetauthinfo->init(ctxt, nil);
+ break;
+ }
+
+ au := load Auth Auth->PATH;
+ if(au == nil){
+ tk->cmd(t, ".status configure -text {Error: can't load module Auth "+
+ sys->sprint("%r")+"; update");
+ break;
+ }
+
+ err := au->init();
+ if(err != nil){
+ tk->cmd(t, ".status configure -text {Error: "+
+ sys->sprint("%s", err)+"; update");
+ break;
+ }
+
+ fd: ref Sys->FD;
+ (fd, err) = au->client(alg, ai, c.dfd);
+ if(fd == nil){
+ tk->cmd(t, ".status configure -text {Error: authentication failed: "+
+ sys->sprint("%s",err)+"; update");
+ break;
+ }
+
+ status("Mount");
+ sys->pctl(sys->FORKNS, nil); # don't fork before authentication
+ n := sys->mount(fd, nil, "/n/remote", sys->MREPL, "");
+ if(n < 0) {
+ tk->cmd(t, ".status configure -text {Mount failed: "+
+ sys->sprint("%r")+"}; update");
+ break;
+ }
+ wmdir := load Wm "/dis/wm/dir.dis";
+ spawn wmdir->init(ctxt, "wm/dir" :: "/n/remote" :: nil);
+ return;
+ }
+}
+
+status(s: string)
+{
+ tk->cmd(t, ".status configure -text {"+s+"}; update");
+}
+
+sysname(): string
+{
+ fd := sys->open("#c/sysname", sys->OREAD);
+ if(fd == nil)
+ return "Anon";
+ buf := array[128] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return "Anon";
+ return string buf[0:n];
+}
+
+user(): string
+{
+ sys = load Sys Sys->PATH;
+
+ fd := sys->open("/dev/user", sys->OREAD);
+ if(fd == nil)
+ return "";
+
+ buf := array[128] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return "";
+
+ return string buf[0:n];
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, l) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}
diff --git a/appl/wm/rt.b b/appl/wm/rt.b
new file mode 100644
index 00000000..4bf26463
--- /dev/null
+++ b/appl/wm/rt.b
@@ -0,0 +1,701 @@
+implement WmRt;
+
+include "sys.m";
+ sys: Sys;
+ sprint: import sys;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "draw.m";
+
+include "tk.m";
+ tk: Tk;
+ Toplevel: import tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "dialog.m";
+ dialog: Dialog;
+
+include "selectfile.m";
+ selectfile: Selectfile;
+
+include "dis.m";
+ dis: Dis;
+ Inst, Type, Data, Link, Mod: import dis;
+ XMAGIC: import Dis;
+ MUSTCOMPILE, DONTCOMPILE: import Dis;
+ AMP, AFP, AIMM, AXXX, AIND, AMASK: import Dis;
+ ARM, AXNON, AXIMM, AXINF, AXINM: import Dis;
+ DEFB, DEFW, DEFS, DEFF, DEFA, DIND, DAPOP, DEFL: import Dis;
+
+WmRt: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+gctxt: ref Draw->Context;
+t: ref Toplevel;
+disfile: string;
+
+TK: con 1;
+
+m: ref Mod;
+rt := 0;
+ss := -1;
+
+rt_cfg := array[] of {
+ "frame .m",
+ "menubutton .m.open -text File -menu .file",
+ "menubutton .m.prop -text Properties -menu .prop",
+ "menubutton .m.view -text View -menu .view",
+ "label .m.l",
+ "pack .m.open .m.view .m.prop -side left",
+ "pack .m.l -side right",
+ "frame .b",
+ "text .b.t -width 12c -height 7c -yscrollcommand {.b.s set} -bg white",
+ "scrollbar .b.s -command {.b.t yview}",
+ "pack .b.s -fill y -side left",
+ "pack .b.t -fill both -expand 1",
+ "pack .m -anchor w -fill x",
+ "pack .b -fill both -expand 1",
+ "pack propagate . 0",
+ "update",
+
+ "menu .prop",
+ ".prop add checkbutton -text {Must compile} -command {send cmd must}",
+ ".prop add checkbutton -text {Don't compile} -command {send cmd dont}",
+ ".prop add separator",
+ ".prop add command -text {Set stack extent} -command {send cmd stack}",
+ ".prop add command -text {Sign module} -command {send cmd sign}",
+
+ "menu .view",
+ ".view add command -text {Header} -command {send cmd hdr}",
+ ".view add command -text {Code segment} -command {send cmd code}",
+ ".view add command -text {Data segment} -command {send cmd data}",
+ ".view add command -text {Type descriptors} -command {send cmd type}",
+ ".view add command -text {Link descriptors} -command {send cmd link}",
+ ".view add command -text {Import descriptors} -command {send cmd imports}",
+ ".view add command -text {Exception handlers} -command {send cmd handlers}",
+
+ "menu .file",
+ ".file add command -text {Open module} -command {send cmd open}",
+ ".file add separator",
+ ".file add command -text {Write .dis module} -command {send cmd save}",
+ ".file add command -text {Write .s file} -command {send cmd list}",
+};
+
+init(ctxt: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "rt: no window context\n");
+ raise "fail:bad context";
+ }
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ dialog = load Dialog Dialog->PATH;
+ selectfile = load Selectfile Selectfile->PATH;
+
+ sys->pctl(Sys->NEWPGRP, nil);
+
+ tkclient->init();
+ dialog->init();
+ selectfile->init();
+
+ gctxt = ctxt;
+
+ menubut: chan of string;
+ (t, menubut) = tkclient->toplevel(ctxt, "", "Dis Module Manager", Tkclient->Appl);
+
+ cmd := chan of string;
+
+ tk->namechan(t, cmd, "cmd");
+ tkcmds(t, rt_cfg);
+ tkclient->onscreen(t, nil);
+ tkclient->startinput(t, "kbd"::"ptr"::nil);
+
+ dis = load Dis Dis->PATH;
+ if(dis == nil) {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Load Module",
+ "wmrt requires Dis",
+ 0, "Exit"::nil);
+ return;
+ }
+ dis->init();
+
+ for(;;) alt {
+ s := <-t.ctxt.kbd =>
+ tk->keyboard(t, s);
+ s := <-t.ctxt.ptr =>
+ tk->pointer(t, *s);
+ s := <-t.ctxt.ctl or
+ s = <-t.wreq =>
+ tkclient->wmctl(t, s);
+ menu := <-menubut =>
+ if(menu == "exit")
+ return;
+ tkclient->wmctl(t, menu);
+ s := <-cmd =>
+ case s {
+ "open" =>
+ openfile(ctxt);
+ "save" =>
+ writedis();
+ "list" =>
+ writeasm();
+ "hdr" =>
+ hdr();
+ "code" =>
+ das(TK);
+ "data" =>
+ dat(TK);
+ "type" =>
+ desc(TK);
+ "link" =>
+ link(TK);
+ "imports" =>
+ imports(TK);
+ "handlers" =>
+ handlers(TK);
+ "must" =>
+ rt ^= MUSTCOMPILE;
+ "dont" =>
+ rt ^= DONTCOMPILE;
+ "stack" =>
+ spawn stack(ctxt);
+ "sign" =>
+ dialog->prompt(ctxt, t.image, "error -fg red", "Signed Modules",
+ "not implemented",
+ 0, "Continue"::nil);
+ }
+ }
+}
+
+stack_cfg := array[] of {
+ "scale .s -length 200 -to 32768 -resolution 128 -orient horizontal",
+ "frame .f",
+ "pack .s .f -pady 5 -fill x -expand 1",
+};
+
+stack(ctxt: ref Draw->Context)
+{
+ # (s, sbut) := tkclient->toplevel(ctxt, tkclient->geom(t), "Dis Stack", 0);
+ (s, sbut) := tkclient->toplevel(ctxt, "", "Dis Stack", 0);
+
+ cmd := chan of string;
+ tk->namechan(s, cmd, "cmd");
+ tkcmds(s, stack_cfg);
+ tk->cmd(s, ".s set " + string ss);
+ tk->cmd(s, "update");
+ tkclient->onscreen(s, nil);
+ tkclient->startinput(s, "kbd"::"ptr"::nil);
+
+ for(;;) alt {
+ c := <-s.ctxt.kbd =>
+ tk->keyboard(s, c);
+ c := <-s.ctxt.ptr =>
+ tk->pointer(s, *c);
+ c := <-s.ctxt.ctl or
+ c = <-s.wreq =>
+ tkclient->wmctl(s, c);
+ wmctl := <-sbut =>
+ if(wmctl == "exit") {
+ ss = int tk->cmd(s, ".s get");
+ return;
+ }
+ tkclient->wmctl(s, wmctl);
+ }
+}
+
+openfile(ctxt: ref Draw->Context)
+{
+ pattern := list of {
+ "*.dis (Dis VM module)",
+ "* (All files)"
+ };
+
+ for(;;) {
+ disfile = selectfile->filename(ctxt, t.image, "Dis file", pattern, nil);
+ if(disfile == "")
+ break;
+
+ s: string;
+ (m, s) = dis->loadobj(disfile);
+ if(s == nil) {
+ ss = m.ssize;
+ rt = m.rt;
+ tk->cmd(t, ".m.l configure -text {"+m.name+"}");
+ das(TK);
+ return;
+ }
+
+ r := dialog->prompt(ctxt, t.image, "error -fg red", "Open Dis File",
+ s,
+ 0, "Retry" :: "Abort" :: nil);
+ if(r == 1)
+ return;
+ }
+}
+
+writedis()
+{
+ if(m == nil || m.magic == 0) {
+ dialog->prompt(gctxt, t.image, "error -fg red", "Write .dis",
+ "no module loaded",
+ 0, "Continue"::nil);
+ return;
+ }
+ if(rt < 0)
+ rt = m.rt;
+ if(ss < 0)
+ ss = m.ssize;
+ if(rt == m.rt && ss == m.ssize)
+ return;
+ while((fd := sys->open(disfile, Sys->OREAD)) == nil){
+ if(dialog->prompt(gctxt, t.image, "error -fg red", "Open Dis File", "open failed: "+sprint("%r"),
+ 0, "Retry" :: "Abort" :: nil))
+ return;
+ }
+ if(len discona(rt) == len discona(m.rt) && len discona(ss) == len discona(m.ssize)){
+ sys->seek(fd, big 4, Sys->SEEKSTART); # skip magic
+ discon(fd, rt);
+ discon(fd, ss);
+ m.rt = rt;
+ m.ssize = ss;
+ return;
+ }
+ # rt and ss representations changed in length: read the file in,
+ # make a copy and update rt and ss when copying
+ (ok, d) := sys->fstat(fd);
+ if(ok < 0){
+ ioerror("Reading Dis file "+disfile, "can't find file length: "+sprint("%r"));
+ return;
+ }
+ length := int d.length;
+ disbuf := array[length] of byte;
+ if(sys->read(fd, disbuf, length) != length){
+ ioerror("Reading Dis file "+disfile, "read error: "+sprint("%r"));
+ return;
+ }
+ outbuf := array[length+2*4] of byte; # could avoid this buffer if required, by writing portions of disbuf
+ (magic, i) := operand(disbuf, 0);
+ o := putoperand(outbuf, magic);
+ if(magic == Dis->SMAGIC){
+ ns: int;
+ (ns, i) = operand(disbuf, i);
+ o += putoperand(outbuf[o:], ns);
+ sign := disbuf[i:i+ns];
+ i += ns;
+ outbuf[o:] = sign;
+ o += ns;
+ }
+ (nil, i) = operand(disbuf, i);
+ (nil, i) = operand(disbuf, i);
+ if(i < 0){
+ ioerror("Reading Dis file "+disfile, "Dis header too short");
+ return;
+ }
+ o += putoperand(outbuf[o:], rt);
+ o += putoperand(outbuf[o:], ss);
+ outbuf[o:] = disbuf[i:];
+ o += len disbuf - i;
+ fd = sys->create(disfile, Sys->OWRITE, 8r666);
+ if(fd == nil){
+ ioerror("Rewriting "+disfile, sys->sprint("can't create %s: %r",disfile));
+ return;
+ }
+ if(sys->write(fd, outbuf, o) != o)
+ ioerror("Rewriting "+disfile, "write error: "+sprint("%r"));
+ m.rt = rt;
+ m.ssize = ss;
+}
+
+ioerror(title: string, err: string)
+{
+ dialog->prompt(gctxt, t.image, "error -fg red", title, err, 0, "Dismiss" :: nil);
+}
+
+putoperand(out: array of byte, v: int): int
+{
+ a := discona(v);
+ out[0:] = a;
+ return len a;
+}
+
+discona(val: int): array of byte
+{
+ if(val >= -64 && val <= 63)
+ return array[] of { byte(val & ~16r80) };
+ else if(val >= -8192 && val <= 8191)
+ return array[] of { byte((val>>8) & ~16rC0 | 16r80), byte val };
+ else
+ return array[] of { byte(val>>24 | 16rC0), byte(val>>16), byte(val>>8), byte val };
+}
+
+discon(fd: ref Sys->FD, val: int)
+{
+ a := discona(val);
+ sys->write(fd, a, len a);
+}
+
+operand(disobj: array of byte, o: int): (int, int)
+{
+ if(o >= len disobj)
+ return (-1, -1);
+ b := int disobj[o++];
+ case b & 16rC0 {
+ 16r00 =>
+ return (b, o);
+ 16r40 =>
+ return (b | ~16r7F, o);
+ 16r80 =>
+ if(o >= len disobj)
+ return (-1, -1);
+ if(b & 16r20)
+ b |= ~16r3F;
+ else
+ b &= 16r3F;
+ b = (b<<8) | int disobj[o++];
+ return (b, o);
+ 16rC0 =>
+ if(o+2 >= len disobj)
+ return (-1, -1);
+ if(b & 16r20)
+ b |= ~16r3F;
+ else
+ b &= 16r3F;
+ b = b<<24 |
+ (int disobj[o]<<16) |
+ (int disobj[o+1]<<8)|
+ int disobj[o+2];
+ o += 3;
+ return (b, o);
+ }
+ return (0, -1); # can't happen
+}
+
+fasm: ref Iobuf;
+
+writeasm()
+{
+ if(m == nil || m.magic == 0) {
+ dialog->prompt(gctxt, t.image, "error -fg red", "Write .s",
+ "no module loaded",
+ 0, "Continue"::nil);
+ return;
+ }
+
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil) {
+ dialog->prompt(gctxt, t.image, "error -fg red", "Write .s",
+ "Bufio load failed: "+sprint("%r"),
+ 0, "Exit"::nil);
+ return;
+ }
+
+ for(;;) {
+ asmfile: string;
+ if(len disfile > 4 && disfile[len disfile-4:] == ".dis")
+ asmfile = disfile[0:len disfile-3] + "s";
+ else
+ asmfile = disfile + ".s";
+ fasm = bufio->create(asmfile, Sys->OWRITE|Sys->OTRUNC, 8r666);
+ if(fasm != nil)
+ break;
+ r := dialog->prompt(gctxt, t.image, "error -fg red", "Create .s file",
+ "open failed: "+sprint("%r"),
+ 0, "Retry" :: "Abort" :: nil);
+ if(r == 0)
+ continue;
+ else
+ return;
+ }
+ das(!TK);
+ fasm.puts("\tentry\t" + string m.entry + "," + string m.entryt + "\n");
+ desc(!TK);
+ dat(!TK);
+ fasm.puts("\tmodule\t" + m.name + "\n");
+ link(!TK);
+ imports(!TK);
+ handlers(!TK);
+ fasm.close();
+}
+
+link(flag: int)
+{
+ if(m == nil || m.magic == 0) {
+ dialog->prompt(gctxt, t.image, "error -fg red", "Link Descriptors",
+ "no module loaded",
+ 0, "Continue"::nil);
+ return;
+ }
+
+ if(flag == TK)
+ tk->cmd(t, ".b.t delete 1.0 end");
+
+ for(i := 0; i < m.lsize; i++) {
+ l := m.links[i];
+ s := sprint(" link %d,%d, 0x%ux, \"%s\"\n",
+ l.desc, l.pc, l.sig, l.name);
+ if(flag == TK)
+ tk->cmd(t, ".b.t insert end '"+s);
+ else
+ fasm.puts(s);
+ }
+ if(flag == TK)
+ tk->cmd(t, ".b.t see 1.0; update");
+}
+
+imports(flag: int)
+{
+ if(m == nil || m.magic == 0) {
+ dialog->prompt(gctxt, t.image, "error -fg red", "Import Descriptors",
+ "no module loaded",
+ 0, "Continue"::nil);
+ return;
+ }
+
+ if(flag == TK)
+ tk->cmd(t, ".b.t delete 1.0 end");
+
+ mi := m.imports;
+ for(i := 0; i < len mi; i++) {
+ a := mi[i];
+ for(j := 0; j < len a; j++) {
+ ai := a[j];
+ s := sprint(" import 0x%ux, \"%s\"\n", ai.sig, ai.name);
+ if(flag == TK)
+ tk->cmd(t, ".b.t insert end '"+s);
+ else
+ fasm.puts(s);
+ }
+ }
+ if(flag == TK)
+ tk->cmd(t, ".b.t see 1.0; update");
+}
+
+handlers(flag: int)
+{
+ if(m == nil || m.magic == 0) {
+ dialog->prompt(gctxt, t.image, "error -fg red", "Exception Handlers",
+ "no module loaded",
+ 0, "Continue"::nil);
+ return;
+ }
+
+ if(flag == TK)
+ tk->cmd(t, ".b.t delete 1.0 end");
+
+ hs := m.handlers;
+ for(i := 0; i < len hs; i++) {
+ h := hs[i];
+ tt := -1;
+ for(j := 0; j < len m.types; j++) {
+ if(h.t == m.types[j]) {
+ tt = j;
+ break;
+ }
+ }
+ s := sprint(" %d-%d, o=%d, e=%d t=%d\n", h.pc1, h.pc2, h.eoff, h.ne, tt);
+ if(flag == TK)
+ tk->cmd(t, ".b.t insert end '"+s);
+ else
+ fasm.puts(s);
+ et := h.etab;
+ for(j = 0; j < len et; j++) {
+ e := et[j];
+ if(e.s == nil)
+ s = sprint(" %d *\n", e.pc);
+ else
+ s = sprint(" %d \"%s\"\n", e.pc, e.s);
+ if(flag == TK)
+ tk->cmd(t, ".b.t insert end '"+s);
+ else
+ fasm.puts(s);
+ }
+ }
+ if(flag == TK)
+ tk->cmd(t, ".b.t see 1.0; update");
+}
+
+desc(flag: int)
+{
+ if(m == nil || m.magic == 0) {
+ dialog->prompt(gctxt, t.image, "error -fg red", "Type Descriptors",
+ "no module loaded",
+ 0, "Continue"::nil);
+ return;
+ }
+
+ if(flag == TK)
+ tk->cmd(t, ".b.t delete 1.0 end");
+
+ for(i := 0; i < m.tsize; i++) {
+ h := m.types[i];
+ s := sprint(" desc $%d, %d, \"", i, h.size);
+ for(j := 0; j < h.np; j++)
+ s += sprint("%.2ux", int h.map[j]);
+ s += "\"\n";
+ if(flag == TK)
+ tk->cmd(t, ".b.t insert end '"+s);
+ else
+ fasm.puts(s);
+ }
+ if(flag == TK)
+ tk->cmd(t, ".b.t see 1.0; update");
+}
+
+hdr()
+{
+ if(m == nil || m.magic == 0) {
+ dialog->prompt(gctxt, t.image, "error -fg red", "Header",
+ "no module loaded",
+ 0, "Continue"::nil);
+ return;
+ }
+
+ tk->cmd(t, ".b.t delete 1.0 end");
+
+ s := sprint("%.8ux Version %d Dis VM\n", m.magic, m.magic - XMAGIC + 1);
+ s += sprint("%.8ux Runtime flags %s\n", m.rt, rtflag(m.rt));
+ s += sprint("%8d bytes per stack extent\n\n", m.ssize);
+
+
+ s += sprint("%8d instructions\n", m.isize);
+ s += sprint("%8d data size\n", m.dsize);
+ s += sprint("%8d heap type descriptors\n", m.tsize);
+ s += sprint("%8d link directives\n", m.lsize);
+ s += sprint("%8d entry pc\n", m.entry);
+ s += sprint("%8d entry type descriptor\n\n", m.entryt);
+
+ if(m.sign == nil)
+ s += "Module is Insecure\n";
+
+ tk->cmd(t, ".b.t insert end '"+s);
+ tk->cmd(t, ".b.t see 1.0; update");
+}
+
+rtflag(flag: int): string
+{
+ if(flag == 0)
+ return "";
+
+ s := "[";
+
+ if(flag & MUSTCOMPILE)
+ s += "MustCompile";
+ if(flag & DONTCOMPILE) {
+ if(flag & MUSTCOMPILE)
+ s += "|";
+ s += "DontCompile";
+ }
+ s[len s] = ']';
+
+ return s;
+}
+
+das(flag: int)
+{
+ if(m == nil || m.magic == 0) {
+ dialog->prompt(gctxt, t.image, "error -fg red", "Assembly",
+ "no module loaded",
+ 0, "Continue"::nil);
+ return;
+ }
+
+ if(flag == TK)
+ tk->cmd(t, ".b.t delete 1.0 end");
+
+ for(i := 0; i < m.isize; i++) {
+ prefix := "";
+ if(flag == TK)
+ prefix = sprint(".b.t insert end '%4d ", i);
+ else {
+ if(i % 10 == 0)
+ fasm.puts("#" + string i + "\n");
+ prefix = sprint("\t");
+ }
+ s := prefix + dis->inst2s(m.inst[i]) + "\n";
+
+ if(flag == TK)
+ tk->cmd(t, s);
+ else
+ fasm.puts(s);
+ }
+ if(flag == TK)
+ tk->cmd(t, ".b.t see 1.0; update");
+}
+
+dat(flag: int)
+{
+ if(m == nil || m.magic == 0) {
+ dialog->prompt(gctxt, t.image, "error -fg red", "Module Data",
+ "no module loaded",
+ 0, "Continue"::nil);
+ return;
+ }
+ s := sprint(" var @mp, %d\n", m.types[0].size);
+ if(flag == TK) {
+ tk->cmd(t, ".b.t delete 1.0 end");
+ tk->cmd(t, ".b.t insert end '"+s);
+ } else
+ fasm.puts(s);
+
+ s = "";
+ for(d := m.data; d != nil; d = tl d) {
+ pick dat := hd d {
+ Bytes =>
+ s = sprint("\tbyte @mp+%d", dat.off);
+ for(n := 0; n < dat.n; n++)
+ s += sprint(",%d", int dat.bytes[n]);
+ Words =>
+ s = sprint("\tword @mp+%d", dat.off);
+ for(n := 0; n < dat.n; n++)
+ s += sprint(",%d", dat.words[n]);
+ String =>
+ s = sprint("\tstring @mp+%d, \"%s\"", dat.off, mapstr(dat.str));
+ Reals =>
+ s = sprint("\treal @mp+%d", dat.off);
+ for(n := 0; n < dat.n; n++)
+ s += sprint(", %g", dat.reals[n]);
+ break;
+ Array =>
+ s = sprint("\tarray @mp+%d,$%d,%d", dat.off, dat.typex, dat.length);
+ Aindex =>
+ s = sprint("\tindir @mp+%d,%d", dat.off, dat.index);
+ Arestore =>
+ s = "\tapop";
+ break;
+ Bigs =>
+ s = sprint("\tlong @mp+%d", dat.off);
+ for(n := 0; n < dat.n; n++)
+ s += sprint(", %bd", dat.bigs[n]);
+ }
+ if(flag == TK)
+ tk->cmd(t, ".b.t insert end '"+s+"\n");
+ else
+ fasm.puts(s+"\n");
+ }
+
+ if(flag == TK)
+ tk->cmd(t, ".b.t see 1.0; update");
+}
+
+mapstr(s: string): string
+{
+ for(i := 0; i < len s; i++) {
+ if(s[i] == '\n')
+ s = s[0:i] + "\\n" + s[i+1:];
+ }
+ return s;
+}
+
+tkcmds(t: ref Toplevel, cfg: array of string)
+{
+ for(i := 0; i < len cfg; i++)
+ tk->cmd(t, cfg[i]);
+}
diff --git a/appl/wm/sam.b b/appl/wm/sam.b
new file mode 100644
index 00000000..02f12f4d
--- /dev/null
+++ b/appl/wm/sam.b
@@ -0,0 +1,230 @@
+implement Samterm;
+
+include "sys.m";
+sys: Sys;
+fprint, sprint, FD: import sys;
+stderr, logfd: ref FD;
+
+include "draw.m";
+draw: Draw;
+
+include "samterm.m";
+
+include "samtk.m";
+samtk: Samtk;
+
+include "samstub.m";
+samstub: Samstub;
+Samio, Sammsg: import samstub;
+
+samio: ref Samio;
+
+ctxt: ref Context;
+
+init(context: ref draw->Context, nil: list of string)
+{
+ recvsam: chan of ref Sammsg;
+
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ stderr = sys->fildes(2);
+
+ logfd = sys->create("samterm.log", sys->OWRITE, 8r666);
+ if (logfd == nil) {
+ fprint(stderr, "Can't create samterm.log\n");
+ logfd = stderr;
+ }
+
+ fprint(logfd, "Samterm started\n");
+
+ pgrp := sys->pctl(sys->NEWPGRP, nil);
+
+ ctxt = ref Context(
+ context,
+ 1000, # initial tag
+
+ 0, # lock
+
+ nil, # keysel
+ nil, # scrollsel
+ nil, # buttonsel
+ nil, # menu2sel
+ nil, # menu3sel
+ nil, # titlesel
+ nil, # tags
+
+ nil, # menus
+ nil, # texts
+
+ nil, # cmd
+ nil, # which
+ nil, # work
+ pgrp, # pgrp
+ logfd # logging file descriptor
+ );
+
+ samtk = load Samtk Samtk->PATH;
+ if (samtk == nil) {
+ fprint(stderr, "Can't load %s\n", Samtk->PATH);
+ return;
+ }
+ samtk->init(ctxt);
+
+ samstub = load Samstub Samstub->PATH;
+ if (samstub == nil) {
+ fprint(stderr, "Can't load %s\n", Samstub->PATH);
+ return;
+ }
+ samstub->init(ctxt);
+
+ (samio, recvsam) = samstub->start();
+ if (samio == nil) {
+ fprint(stderr, "couldn't start samstub\n");
+ return;
+ }
+ samstub->outTs(samstub->Tversion, samstub->VERSION);
+
+ samstub->startcmdfile();
+
+ samstub->setlock();
+
+ for(;;) if (ctxt.lock == 0) alt {
+ (win, menu) := <-ctxt.titlesel =>
+ samstub->cleanout();
+ fl := ctxt.flayers[win];
+ tag := fl.tag;
+ if ((i := samtk->whichtext(tag)) < 0)
+ samtk->panic("samterm: whichtext");
+ t := ctxt.texts[i];
+ samtk->newcur(t, fl);
+ case menu {
+ "exit" =>
+ if (ctxt.flayers[win].tag == 0) {
+ samstub->outT0(samstub->Texit);
+ f := sprint("#p/%d/ctl", pgrp);
+ if ((fd := sys->open(f, sys->OWRITE)) != nil)
+ sys->write(fd, array of byte "killgrp\n", 8);
+ return;
+ }
+ samstub->close(win, tag);
+ "resize" =>
+ samtk->resize(fl);
+ samstub->scrollto(fl, fl.scope.first);
+ "task" =>
+ spawn samtk->titlectl(win, menu);
+ * =>
+ samtk->titlectl(win, menu);
+ }
+
+
+ (win, m1) := <-ctxt.buttonsel =>
+ samstub->cleanout();
+ fl := ctxt.flayers[win];
+ tag := fl.tag;
+ if (samtk->buttonselect(fl, m1)) {
+ samstub->outTsl(samstub->Tdclick, tag, fl.dot.first);
+ samstub->setlock();
+ }
+ (win, m2) := <-ctxt.menu2sel =>
+ samstub->cleanout();
+ fl := ctxt.flayers[win];
+ tag := fl.tag;
+ if ((i := samtk->whichtext(tag)) < 0)
+ samtk->panic("samterm: whichtext");
+ t := ctxt.texts[i];
+ samtk->newcur(t, fl);
+ case m2 {
+ "cut" =>
+ samstub->cut(t, fl);
+ "paste" =>
+ samstub->paste(t, fl);
+ "snarf" =>
+ samstub->snarf(t, fl);
+ "look" =>
+ samstub->look(t, fl);
+ "exch" =>
+ fprint(ctxt.logfd, "debug -- exch: %d, %s\n", win, m2);
+ "send" =>
+ samstub->send(t, fl);
+ "search" =>
+ samstub->search(t, fl);
+ * =>
+ samtk->panic("samterm: editmenu");
+ }
+ (win, m3) := <-ctxt.menu3sel =>
+ samstub->cleanout();
+ fl := ctxt.flayers[win];
+ tag := fl.tag;
+ if ((i := samtk->whichtext(tag)) < 0)
+ samtk->panic("samterm: whichtext");
+ t := ctxt.texts[i];
+ samtk->newcur(t, fl);
+ case m3 {
+ "new" =>
+ samstub->startnewfile();
+ "zerox" =>
+ samstub->zerox(t);
+ "close" =>
+ if (win != 0) {
+ samstub->close(win, tag);
+ }
+ "write" =>
+ samstub->outTs(samstub->Twrite, tag);
+ samstub->setlock();
+ * =>
+ for (i = 0; i < len ctxt.menus; i++) {
+ if (ctxt.menus[i].name == m3) {
+ break;
+ }
+ }
+ if (i == len ctxt.menus)
+ samtk->panic("init: can't find m3");
+ t = ctxt.menus[i].text;
+ t.flayers = samtk->append(tl t.flayers, hd t.flayers);
+ samtk->newcur(t, hd t.flayers);
+
+ }
+ (win, c) := <-ctxt.keysel =>
+ if (ctxt.which != ctxt.flayers[win]) {
+ fprint(ctxt.logfd, "probably can't happen\n");
+ samstub->cleanout();
+ tag := ctxt.flayers[win].tag;
+ if ((i := samtk->whichtext(tag)) < 0)
+ samtk->panic("samterm: whichtext");
+ samtk->newcur(ctxt.texts[i], ctxt.flayers[win]);
+ }
+ samstub->keypress(c[1:len c -1]);
+ (win, c) := <-ctxt.scrollsel =>
+ if (ctxt.which != ctxt.flayers[win]) {
+ samstub->cleanout();
+ tag := ctxt.flayers[win].tag;
+ if ((i := samtk->whichtext(tag)) < 0)
+ samtk->panic("samterm: whichtext");
+ samtk->newcur(ctxt.texts[i], ctxt.flayers[win]);
+ }
+ (pos, lines) := samtk->scroll(ctxt.which, c);
+ if (lines > 0) {
+ samstub->outTsll(samstub->Torigin,
+ ctxt.which.tag, pos, lines);
+ samstub->setlock();
+ } else if (pos != -1)
+ samstub->scrollto(ctxt.which, pos);
+ h := <-recvsam =>
+ if (samstub->inmesg(h)) {
+ samstub->outT0(samstub->Texit);
+ fname := sprint("#p/%d/ctl", pgrp);
+ if ((fdesc := sys->open(fname, sys->OWRITE)) != nil)
+ sys->write(fdesc, array of byte "killgrp\n", 8);
+ return;
+ }
+ } else {
+ h := <-recvsam;
+ if (samstub->inmesg(h)) {
+ samstub->outT0(samstub->Texit);
+ fname := sprint("#p/%d/ctl", pgrp);
+ if ((fdesc := sys->open(fname, sys->OWRITE)) != nil)
+ sys->write(fdesc, array of byte "killgrp\n", 8);
+ return;
+ }
+ }
+}
diff --git a/appl/wm/samstub.b b/appl/wm/samstub.b
new file mode 100644
index 00000000..bdf708ff
--- /dev/null
+++ b/appl/wm/samstub.b
@@ -0,0 +1,1338 @@
+implement Samstub;
+
+include "sys.m";
+sys: Sys;
+fprint, FD, fildes: import sys;
+
+stderr: ref FD;
+
+include "draw.m";
+draw: Draw;
+
+include "samterm.m";
+samterm: Samterm;
+Text, Menu, Context, Flayer, Section: import samterm;
+
+include "samtk.m";
+samtk: Samtk;
+panic, whichtext, whichmenu: import samtk;
+
+include "samstub.m";
+
+sendsam: chan of ref Sammsg;
+recvsam: chan of ref Sammsg;
+
+snarflen: int;
+
+ctxt: ref Context;
+
+requested: list of (int, int);
+
+tname := array [] of {
+ "Tversion",
+ "Tstartcmdfile",
+ "Tcheck",
+ "Trequest",
+ "Torigin",
+ "Tstartfile",
+ "Tworkfile",
+ "Ttype",
+ "Tcut",
+ "Tpaste",
+ "Tsnarf",
+ "Tstartnewfile",
+ "Twrite",
+ "Tclose",
+ "Tlook",
+ "Tsearch",
+ "Tsend",
+ "Tdclick",
+ "Tstartsnarf",
+ "Tsetsnarf",
+ "Tack",
+ "Texit",
+};
+
+hname := array [] of {
+ "Hversion",
+ "Hbindname",
+ "Hcurrent",
+ "Hnewname",
+ "Hmovname",
+ "Hgrow",
+ "Hcheck0",
+ "Hcheck",
+ "Hunlock",
+ "Hdata",
+ "Horigin",
+ "Hunlockfile",
+ "Hsetdot",
+ "Hgrowdata",
+ "Hmoveto",
+ "Hclean",
+ "Hdirty",
+ "Hcut",
+ "Hsetpat",
+ "Hdelname",
+ "Hclose",
+ "Hsetsnarf",
+ "Hsnarflen",
+ "Hack",
+ "Hexit",
+};
+
+init(c: ref Context)
+{
+ ctxt = c;
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+
+ stderr = fildes(2);
+
+ samterm = load Samterm Samterm->PATH;
+
+ samtk = load Samtk Samtk->PATH;
+ samtk->init(ctxt);
+
+ requested = nil;
+}
+
+start(): (ref Samio, chan of ref Sammsg)
+{
+ sys = load Sys Sys->PATH;
+
+ sys->bind("#C", "/", sys->MAFTER);
+
+ # Allocate a cmd device
+ ctl := sys->open("/cmd/clone", sys->ORDWR);
+ if(ctl == nil) {
+ fprint(stderr, "can't open /cmd/clone\n");
+ return (nil, nil);
+ }
+
+ # Find out which one
+ buf := array[32] of byte;
+ n := sys->read(ctl, buf, len buf);
+ if(n <= 0) {
+ fprint(stderr, "can't read cmd device\n");
+ return (nil, nil);
+ }
+
+ dir := "/cmd/"+string buf[0:n];
+
+ # Start the Command
+ n = sys->fprint(ctl, "exec "+ SAM);
+ if(n <= 0) {
+ fprint(stderr, "can't exec %s\n", SAM);
+ return (nil, nil);
+ }
+
+ data := sys->open(dir+"/data", sys->ORDWR);
+ if(data == nil) {
+ fprint(stderr, "can't open cmd data file\n");
+ return (nil, nil);
+ }
+
+ sendsam = chan of ref Sammsg;
+ recvsam = chan of ref Sammsg;
+
+ samio := ref Samio(ctl, data, array[1] of byte, 0, 0);
+
+ spawn sender(samio, sendsam);
+ spawn receiver(samio, recvsam);
+
+ return (samio, recvsam);
+}
+
+sender(samio: ref Samio, c: chan of ref Sammsg)
+{
+ fprint(ctxt.logfd, "sender started\n");
+ for (;;) {
+ h := <- c;
+ if (h == nil) return;
+ buf := array[3 + len h.mdata] of byte;
+ buf[0] = byte h.mtype;
+ buf[1] = byte h.mcount;
+ buf[2] = byte (h.mcount >> 8);
+ buf[3:] = h.mdata;
+ sys->write(samio.data, buf, len buf);
+ }
+}
+
+receiver(samio: ref Samio, msgchan: chan of ref Sammsg)
+{
+ c: int;
+
+ fprint(ctxt.logfd, "receiver started\n");
+
+ state := 0;
+ i := 0;
+ errs := 0;
+
+ h: ref Sammsg;
+
+ for (;;) {
+ if (samio.count == 0) {
+ n := sys->read(samio.data, samio.buffer, len samio.buffer);
+ if (n <= 0) {
+ fprint(stderr, "Read error on sam's pipe\n");
+ return;
+ }
+ samio.index = 0;
+ samio.count = n;
+ }
+ samio.count--;
+
+ c = int samio.buffer[samio.index++];
+
+ case state {
+ 0 =>
+ h = ref Sammsg(c, 0, nil);
+ state++;
+ continue;
+ 1 =>
+ h.mcount = c;
+ state++;
+ continue;
+ 2 =>
+ h.mcount = h.mcount|(c<<8);
+ if (h.mcount > DATASIZE || h.mcount < 0)
+ panic("receiver: count>DATASIZE");
+ if(h.mcount != 0) {
+ h.mdata = array[h.mcount] of byte;
+ i = 0;
+ state++;
+ continue;
+ }
+ 3 =>
+ h.mdata[i++] = byte c;
+ if(i < h.mcount){
+ continue;
+ }
+ }
+ msgchan <- = h;
+ h = nil;
+ state = 0;
+ }
+}
+
+inmesg(h: ref Sammsg): int
+{
+
+ case h.mtype {
+
+ Hversion =>
+ m := h.inshort(0);
+ fprint(ctxt.logfd, "Hversion: %d\n", m);
+
+ Hbindname =>
+ m := h.inshort(0);
+ vl := h.invlong(2);
+ fprint(ctxt.logfd, "Hbindname: %ux, %bux\n", m, vl);
+ bindname(m, int vl);
+
+ Hcurrent =>
+ m := h.inshort(0);
+ fprint(ctxt.logfd, "Hcurrent: %d\n", m);
+ hcurrent(m);
+
+ Hmovname =>
+ m := h.inshort(0);
+ fprint(ctxt.logfd, "Hmovname: %d, %s\n", m, string h.mdata[2:]);
+ movename(m, string h.mdata[2:]);
+
+ Hgrow =>
+ m := h.inshort(0);
+ l1 := h.inlong(2);
+ l2 := h.inlong(6);
+ fprint(ctxt.logfd, "Hgrow: %d, %d, %d\n", m, l1, l2);
+ hgrow(m, l1, l2);
+
+ Hnewname =>
+ m := h.inshort(0);
+ fprint(ctxt.logfd, "Hnewname: %d\n", m);
+ newname(m);
+
+ Hcheck0 =>
+ m := h.inshort(0);
+ fprint(ctxt.logfd, "Hcheck0: %d\n", m);
+ i := whichmenu(m);
+ if (i >= 0) {
+ t := ctxt.menus[i].text;
+ if (t != nil)
+ t.lock++;
+ outTs(Tcheck, m);
+ }
+
+ Hcheck =>
+ m := h.inshort(0);
+ fprint(ctxt.logfd, "Hcheck: %d\n", m);
+ i := whichmenu(m);
+ if (i >= 0) {
+ t := ctxt.menus[i].text;
+ if (t != nil && t.lock)
+ t.lock--;
+ hcheck(t);
+ }
+
+ Hunlock =>
+ fprint(ctxt.logfd, "Hunlock\n");
+ clrlock();
+
+ Hdata =>
+ m := h.inshort(0);
+ l := h.inlong(2);
+ fprint(ctxt.logfd, "Hdata: %d, %d, %s\n",
+ m, l, contract(string h.mdata[6:]));
+ hdata(m, l, string h.mdata[6:]);
+
+ Horigin =>
+ m := h.inshort(0);
+ l := h.inlong(2);
+ fprint(ctxt.logfd, "Horigin: %d, %d\n", m, l);
+ horigin(m, l);
+
+ Hunlockfile =>
+ m := h.inshort(0);
+ fprint(ctxt.logfd, "Hunlockfile: %d\n", m);
+ clrlock();
+
+ Hsetdot =>
+ m := h.inshort(0);
+ l1 := h.inlong(2);
+ l2 := h.inlong(6);
+ fprint(ctxt.logfd, "Hsetdot: %d, %d, %d\n", m, l1, l2);
+ hsetdot(m, l1, l2);
+
+ Hgrowdata =>
+ m := h.inshort(0);
+ l1 := h.inlong(2);
+ l2 := h.inlong(6);
+ fprint(ctxt.logfd, "Hgrowdata: %d, %d, %d, %s\n",
+ m, l1, l2, contract(string h.mdata[10:]));
+ hgrowdata(m, l1, l2, string h.mdata[10:]);
+
+ Hmoveto =>
+ m := h.inshort(0);
+ l := h.inlong(2);
+ fprint(ctxt.logfd, "Hmoveto: %d, %d\n", m, l);
+ hmoveto(m, l);
+
+ Hclean =>
+ m := h.inshort(0);
+ fprint(ctxt.logfd, "Hclean: %d\n", m);
+ hclean(m);
+
+ Hdirty =>
+ m := h.inshort(0);
+ fprint(ctxt.logfd, "Hdirty: %d\n", m);
+ hdirty(m);
+
+ Hdelname =>
+ m := h.inshort(0);
+ fprint(ctxt.logfd, "Hdelname: %d\n", m);
+ hdelname(m);
+
+ Hcut =>
+ m := h.inshort(0);
+ l1 := h.inlong(2);
+ l2 := h.inlong(6);
+ fprint(ctxt.logfd, "Hcut: %d, %d, %d\n",
+ m, l1, l2);
+ hcut(m, l1, l2);
+
+ Hclose =>
+ m := h.inshort(0);
+ fprint(ctxt.logfd, "Hclose: %d\n", m);
+ hclose(m);
+
+ Hsetpat =>
+ fprint(ctxt.logfd, "Hsetpat: %s\n", string h.mdata);
+ samtk->hsetpat(string h.mdata);
+
+ Hsetsnarf =>
+ m := h.inshort(0);
+ fprint(ctxt.logfd, "Hsetsnarf: %d\n", m);
+
+ Hsnarflen =>
+ snarflen = h.inlong(0);
+ fprint(ctxt.logfd, "Hsnarflen: %d\n", snarflen);
+
+ Hack =>
+ fprint(ctxt.logfd, "Hack\n");
+ outT0(Tack);
+
+ Hexit =>
+ fprint(ctxt.logfd, "Hexit\n");
+ return 1;
+
+ -1 =>
+ panic("rcv error");
+
+ * =>
+ fprint(ctxt.logfd, "type %d\n", h.mtype);
+ panic("rcv unknown");
+ }
+ return 0;
+}
+
+Sammsg.inshort(h: self ref Sammsg, n: int): int
+{
+ return ((int h.mdata[n+1])<<8) |
+ ((int h.mdata[n]));
+}
+
+Sammsg.inlong(h: self ref Sammsg, n: int): int
+{
+ return ((int h.mdata[n+3])<<24) |
+ ((int h.mdata[n+2])<<16) |
+ ((int h.mdata[n+1])<< 8) |
+ ((int h.mdata[n]));
+}
+
+Sammsg.invlong(h: self ref Sammsg, n: int): big
+{
+ return ((big h.mdata[n+7])<<56) |
+ ((big h.mdata[n+6])<<48) |
+ ((big h.mdata[n+5])<<40) |
+ ((big h.mdata[n+4])<<32) |
+ ((big h.mdata[n+3])<<24) |
+ ((big h.mdata[n+2])<<16) |
+ ((big h.mdata[n+1])<< 8) |
+ ((big h.mdata[n]));
+}
+
+Sammsg.outcopy(h: self ref Sammsg, pos: int, data: array of byte)
+{
+ h.mdata[pos:] = data;
+}
+
+Sammsg.outshort(h: self ref Sammsg, pos: int, s: int)
+{
+ h.mdata[pos++] = byte s;
+ h.mdata[pos] = byte (s >> 8);
+}
+
+Sammsg.outlong(h: self ref Sammsg, pos: int, s: int)
+{
+ h.mdata[pos++] = byte s;
+ h.mdata[pos++] = byte (s >> 8);
+ h.mdata[pos++] = byte (s >> 16);
+ h.mdata[pos] = byte (s >> 24);
+}
+
+Sammsg.outvlong(h: self ref Sammsg, pos: int, s: big)
+{
+ h.mdata[pos++] = byte s;
+ h.mdata[pos++] = byte (s >> 8);
+ h.mdata[pos++] = byte (s >> 16);
+ h.mdata[pos++] = byte (s >> 24);
+ h.mdata[pos++] = byte (s >> 32);
+ h.mdata[pos++] = byte (s >> 40);
+ h.mdata[pos++] = byte (s >> 48);
+ h.mdata[pos] = byte (s >> 56);
+}
+
+outT0(t: int)
+{
+ fprint(ctxt.logfd, "\t\t\t\t\t%s\n", tname[t]);
+ h := ref Sammsg(t, 0, nil);
+ sendsam <- = h;
+}
+
+outTs(t, s: int)
+{
+ fprint(ctxt.logfd, "\t\t\t\t\t%s %ux\n", tname[t], s);
+ a := array[2] of byte;
+ h := ref Sammsg(t, 2, a);
+ h.outshort(0, s);
+ sendsam <- = h;
+}
+
+outTv(t: int, i: big)
+{
+ fprint(ctxt.logfd, "\t\t\t\t\t%s %bux\n", tname[t], i);
+ a := array[8] of byte;
+ h := ref Sammsg(t, 8, a);
+ h.outvlong(0, i);
+ sendsam <- = h;
+}
+
+outTsll(t, m, l1, l2: int)
+{ fprint(ctxt.logfd, "\t\t\t\t\t%s %d %d %d\n", tname[t], m, l1, l2);
+ a := array[10] of byte;
+ h := ref Sammsg(t, 10, a);
+ h.outshort(0, m);
+ h.outlong(2, l1);
+ h.outlong(6, l2);
+ sendsam <- = h;
+}
+
+outTsl(t, m, l: int)
+{ fprint(ctxt.logfd, "\t\t\t\t\t%s %d %d\n", tname[t], m, l);
+ a := array[6] of byte;
+ h := ref Sammsg(t, 6, a);
+ h.outshort(0, m);
+ h.outlong(2, l);
+ sendsam <- = h;
+}
+
+outTsls(t, m, l1, l2: int)
+{ fprint(ctxt.logfd, "\t\t\t\t\t%s %d %d %d\n", tname[t], m, l1, l2);
+ a := array[8] of byte;
+ h := ref Sammsg(t, 8, a);
+ h.outshort(0, m);
+ h.outlong(2, l1);
+ h.outshort(6, l2);
+ sendsam <- = h;
+}
+
+outTslS(t, s1, l1: int, s: string)
+{
+ fprint(ctxt.logfd, "\t\t\t\t\t%s %d %d %s\n", tname[t], s1, l1, s);
+ a := array[6 + len array of byte s] of byte;
+ h := ref Sammsg(t, len a, a);
+ h.outshort(0, s1);
+ h.outlong(2, l1);
+ h.outcopy(6, array of byte s);
+ sendsam <- = h;
+}
+
+newname(tag: int)
+{
+ menuins(0, "dummy", nil, tag);
+}
+
+bindname(tag, l: int)
+{
+ if ((m := whichmenu(tag)) < 0) panic("bindname: whichmenu");
+ if ((l = whichtext(l)) < 0) panic("bindname: whichtext");
+ if (ctxt.menus[m].text != nil)
+ return; # Already bound
+ t := ctxt.texts[l];
+ t.tag = tag;
+ for (fls := t.flayers; fls != nil; fls = tl fls) (hd fls).tag = tag;
+ ctxt.menus[m].text = t;
+}
+
+menuins(m: int, s: string, t: ref Text, tag: int)
+{
+ newmenus := array [len ctxt.menus+1] of ref Menu;
+ menu := ref Menu(
+ tag, # tag
+ s, # name
+ t # text
+ );
+ if (m > 0)
+ newmenus[0:] = ctxt.menus[0:m];
+ newmenus[m] = menu;
+ if (m < len ctxt.menus)
+ newmenus[m+1:] = ctxt.menus[m:];
+ ctxt.menus = newmenus;
+
+ samtk->menuins(m, s);
+}
+
+menudel(m: int)
+{
+ if (len ctxt.menus == 0 || m >= len ctxt.menus || ctxt.menus[m].text != nil)
+ panic("menudel");
+ newmenus := array [len ctxt.menus - 1] of ref Menu;
+ newmenus[0:] = ctxt.menus[0:m];
+ newmenus[m:] = ctxt.menus[m+1:];
+ ctxt.menus = newmenus;
+ samtk->menudel(m);
+}
+
+outcmd() {
+ if(ctxt.work != nil) {
+ fl := ctxt.work;
+ outTsll(Tworkfile, fl.tag, fl.dot.first, fl.dot.last);
+ }
+}
+
+hclose(m: int)
+{
+ i: int;
+
+ # close LAST window of a file
+ if((m = whichmenu(m)) < 0) panic("hclose: whichmenu");
+ t := ctxt.menus[m].text;
+ if (tl t.flayers != nil) panic("hclose: flayers");
+ fl := hd t.flayers;
+ fl.t = nil;
+ for (i = 0; i< len ctxt.flayers; i++)
+ if (ctxt.flayers[i] == fl) break;
+ if (i == len ctxt.flayers) panic("hclose: ctxt.flayers");
+ samtk->chandel(i);
+ t.flayers = nil;
+ for (i = 0; i< len ctxt.texts; i++)
+ if (ctxt.texts[i] == ctxt.menus[m].text) break;
+ if (i == len ctxt.texts) panic("hclose: ctxt.texts");
+ ctxt.texts[i:] = ctxt.texts[i+1:];
+ ctxt.texts = ctxt.texts[:len ctxt.texts - 1];
+ ctxt.menus[m].text = nil;
+ ctxt.which = nil;
+ samtk->focus(hd ctxt.cmd.flayers);
+}
+
+close(win, tag: int)
+{
+ nfls: list of ref Flayer;
+
+ if ((m := whichtext(tag)) < 0) panic("close: text");
+ t := ctxt.texts[m];
+ if ((m = whichmenu(tag)) < 0) panic("close: menu");
+ if (len t.flayers == 1) {
+ outTs(Tclose, tag);
+ setlock();
+ return;
+ }
+ fl := ctxt.flayers[win];
+ nfls = nil;
+ for (fls := t.flayers; fls != nil; fls = tl fls)
+ if (hd fls != fl) nfls = hd fls :: nfls;
+ t.flayers = nfls;
+ samtk->chandel(win);
+ fl.t = nil;
+ samtk->settitle(t, ctxt.menus[m].name);
+ ctxt.which = nil;
+}
+
+hdelname(m: int)
+{
+ # close LAST window of a file
+ if((m = whichmenu(m)) < 0) panic("hdelname: whichmenu");
+ if (ctxt.menus[m].text != nil) panic("hdelname: text");
+ ctxt.menus[m:] = ctxt.menus[m+1:];
+ ctxt.menus = ctxt.menus[:len ctxt.menus - 1];
+ samtk->menudel(m);
+ ctxt.which = nil;
+}
+
+hdirty(m: int)
+{
+ if((m = whichmenu(m)) < 0) panic("hdirty: whichmenu");
+ if (ctxt.menus[m].text == nil) panic("hdirty: text");
+ ctxt.menus[m].text.state |= Samterm->Dirty;
+ samtk->settitle(ctxt.menus[m].text, ctxt.menus[m].name);
+}
+
+hclean(m: int)
+{
+ if((m = whichmenu(m)) < 0) panic("hclean: whichmenu");
+ if (ctxt.menus[m].text == nil) panic("hclean: text");
+ ctxt.menus[m].text.state &= ~Samterm->Dirty;
+ samtk->settitle(ctxt.menus[m].text, ctxt.menus[m].name);
+}
+
+movename(tag: int, s: string)
+{
+ i := whichmenu(tag);
+ if (i < 0) panic("movename: whichmenu");
+
+ t := ctxt.menus[i].text;
+
+ ctxt.menus[i].text = nil; # suppress panic in menudel
+ menudel(i);
+
+ if(t == ctxt.cmd)
+ i = 0;
+ else {
+ if (len ctxt.menus > 0 && ctxt.menus[0].text == ctxt.cmd)
+ i = 1;
+ else
+ i = 0;
+ for(; i < len ctxt.menus; i++) {
+ if (s < ctxt.menus[i].name)
+ break;
+ }
+ }
+ if (t != nil) samtk->settitle(t, s);
+ menuins(i, s, t, tag);
+}
+
+hcheck(t: ref Text)
+{
+ if (t == nil) {
+ fprint(ctxt.logfd, "hcheck: no text in menu entry\n");
+ return;
+ }
+ for (fls := t.flayers; fls != nil; fls = tl fls) {
+ fl := hd fls;
+ scrollto(fl, fl.scope.first);
+ }
+}
+
+setlock()
+{
+ ctxt.lock++;
+ samtk->allflayers("cursor -bitmap cursor.wait");
+}
+
+clrlock()
+{
+ if (ctxt.lock > 0)
+ ctxt.lock--;
+ else
+ fprint(ctxt.logfd, "lock: wasn't locked\n");
+ if (ctxt.lock == 0)
+ samtk->allflayers("cursor -default; update");
+}
+
+hcut(m, where, howmuch: int)
+{
+ if((m = whichmenu(m)) < 0) panic("hcut: whichmenu");
+ t := ctxt.menus[m].text;
+ if (t == nil) panic("hcut -- no text");
+
+# sctdump(t.sects, "Hcut, before");
+ t.nrunes -= howmuch;
+ t.sects = sctdelete(t.sects, where, howmuch);
+# sctdump(t.sects, "Hcut, after");
+ for (fls := t.flayers; fls != nil; fls = tl fls) {
+ fl := hd fls;
+ if (where < fl.scope.first) {
+ if (where + howmuch <= fl.scope.first)
+ fl.scope.first -= howmuch;
+ else
+ fl.scope.first = where;
+ }
+ if (where < fl.scope.last) {
+ if (where + howmuch <= fl.scope.last)
+ fl.scope.last -= howmuch;
+ else
+ fl.scope.last = where;
+ }
+ }
+}
+
+hgrow(tag, l1, l2: int)
+{
+ if((m := whichmenu(tag)) < 0) panic("hgrow: whichmenu");
+ t := ctxt.menus[m].text;
+ grow(t, l1, l2);
+}
+
+hdata(m, l: int, s: string)
+{
+ nr: list of (int, int);
+
+ if((m = whichmenu(m)) < 0) panic("hdata: whichmenu");
+ t := ctxt.menus[m].text;
+ if (t == nil) panic("hdata -- no text");
+ if (s != "") {
+ t.sects = sctput(t.sects, l, s);
+ updatefls(t, l, s);
+ }
+ for (nr = nil; requested != nil; requested = tl requested) {
+ (r1, r2) := hd requested;
+ if (r1 != m || r2 != l)
+ nr = (r1, r2) :: nr;
+ }
+ requested = nr;
+ clrlock();
+}
+
+hgrowdata(tag, l1, l2: int, s: string)
+{
+ if((m := whichmenu(tag)) < 0) panic("hgrow: whichmenu");
+ t := ctxt.menus[m].text;
+ if (t == nil) panic("hdata -- no text");
+ grow(t, l1, l2);
+ t.sects = sctput(t.sects, l1, s);
+ updatefls(t, l1, s);
+}
+
+hsetdot(m, l1, l2: int)
+{
+ if((m = whichmenu(m)) < 0) panic("hsetdot: whichmenu");
+ t := ctxt.menus[m].text;
+ if (t == nil || t.flayers == nil) panic("hsetdot -- no text");
+ samtk->setdot(hd t.flayers, l1, l2);
+}
+
+hcurrent(tag: int)
+{
+ if ((i := whichmenu(tag)) < 0) panic("hcurrent: whichmenu");
+ if (ctxt.menus[i].text == nil) {
+ n := startfile(tag);
+ ctxt.menus[i].text = ctxt.texts[n];
+ if (ctxt.menus[i].name != nil)
+ samtk->settitle(ctxt.texts[n], ctxt.menus[i].name);
+ }
+ ctxt.work = hd ctxt.menus[i].text.flayers;
+}
+
+hmoveto(m, l: int)
+{
+ if((m = whichmenu(m)) < 0) panic("hmoveto: whichmenu");
+ t := ctxt.menus[m].text;
+ fl := hd t.flayers;
+ if (fl.scope.first <= l &&
+ (l < fl.scope.last || fl.scope.last == fl.scope.first))
+ return;
+ (n, p) := sctrevcnt(t.sects, l, fl.lines/2);
+# fprint(ctxt.logfd, "hmoveto: (n, p) = (%d, %d)\n", n, p);
+ if (n < 0) {
+ outTsll(Torigin, t.tag, l, fl.lines/2);
+ setlock();
+ return;
+ }
+ scrollto(fl, p);
+}
+
+startcmdfile()
+{
+ t := ctxt.tag++;
+ n := newtext(t, 1);
+ ctxt.cmd = ctxt.texts[n];
+ outTv(Tstartcmdfile, big t);
+}
+
+startnewfile()
+{
+ t := ctxt.tag++;
+ n := newtext(t, 0);
+ outTv(Tstartnewfile, big t);
+}
+
+startfile(tag: int): int
+{
+ n := newtext(tag, 0);
+ outTv(Tstartfile, big tag);
+ setlock();
+ return n;
+}
+
+horigin(m, l: int)
+{
+ if((m = whichmenu(m)) < 0) panic("hmoveto: whichmenu");
+ t := ctxt.menus[m].text;
+ fl := hd t.flayers;
+ scrollto(fl, l);
+ clrlock();
+}
+
+scrollto(fl: ref Flayer, where: int)
+{
+ s: string;
+ n: int;
+
+ tag := fl.tag;
+ if ((i := whichtext(tag)) < 0) panic("scrollto: whichtext");
+ t := ctxt.texts[i];
+
+ samtk->flclear(fl);
+ (n, s) = sctgetlines(t.sects, where, fl.lines);
+ fl.scope.first = where;
+ fl.scope.last = where + len s;
+ if (s != "")
+ samtk->flinsert(fl, where, s);
+ if (n == 0) {
+ samtk->setscrollbar(t, fl);
+ } else {
+ (h, l) := scthole(t, fl.scope.last);
+ fl.scope.last = h;
+ if (l > 0)
+ outrequest(tag, h, l);
+ else
+ if (fl.scope.first > t.nrunes) {
+ fl.scope.first = t.nrunes;
+ fl.scope.last = t.nrunes;
+ samtk->setscrollbar(t, fl);
+ }
+ }
+}
+
+scthole(t: ref Text, f: int): (int, int)
+{
+ p := 0;
+ h := -1;
+ l := 0;
+ for (scts := t.sects; scts != nil; scts = tl scts) {
+ sct := hd scts;
+ nr := sct.nrunes;
+ nt := len sct.text;
+ if (h >= 0) {
+ if (sct.text == "") {
+ l += nr;
+ if (l >= 512) return (h,512);
+ } else
+ return (h,l);
+ }
+ if (h < 0 && f < nr) {
+ if (nt < nr) {
+ if (f < nt) {
+ h = p + nt;
+ l = nr - nt;
+ } else {
+ h = p + f;
+ l = nr - f;
+ }
+ if (l >= 512) return (h,512);
+ }
+ }
+ p += sct.nrunes;
+ f -= sct.nrunes;
+ }
+ if (h == -1) return (p, 0);
+ return (h, l);
+}
+
+# return (x, p): x = -1: p -> hole; x = 0: p -> line n; x > 0: p -> eof
+sctlinecount(t: ref Text, pos, n: int): (int, int)
+{
+ i: int;
+
+ p := 0;
+ for (scts := t.sects; scts != nil; scts = tl scts) {
+ sct := hd scts;
+ nr := sct.nrunes;
+ nt := len sct.text;
+ if (pos < nr) {
+ if (pos > 0) i = pos; else i = 0;
+ while (i < nt) {
+ if (sct.text[i++] == '\n') n--;
+ if (n == 0) return (0, p + i);
+ }
+ if (nt < nr) return (-1, p + nt);
+ }
+ p += sct.nrunes;
+ pos -= sct.nrunes;
+ }
+ return (n, p);
+}
+
+sctrevcnt(scts: list of ref Section, pos, n: int): (int, int)
+{
+ if (scts == nil) return (n, 0);
+ sct := hd scts;
+ scts = tl scts;
+ nt := len sct.text;
+ nr := sct.nrunes;
+ if (pos >= nr) {
+ (n, pos) = sctrevcnt(scts, pos - nr, n);
+ pos += nr;
+ }
+ if (n > 0) {
+ if (nt < nr && pos > nt)
+ return(-1, pos);
+ for (i := pos-1; i >= 0; i--) {
+ if (sct.text[i] == '\n') n--;
+ if (n == 0) break;
+ }
+ return (n, i + 1);
+ }
+ return (n, pos);
+}
+
+insertfls(t: ref Text, l: int, s: string)
+{
+ for (fls := t.flayers; fls != nil; fls = tl fls) {
+ fl := hd fls;
+ if (l < fl.scope.first || l > fl.scope.last) continue;
+ samtk->flinsert(fl, l, s);
+ samtk->setscrollbar(t, fl);
+ fl.scope.last += len s;
+ }
+}
+
+updatefls(t: ref Text, l: int, s: string)
+{
+ for (fls := t.flayers; fls != nil; fls = tl fls) {
+ fl := hd fls;
+ if (l < fl.scope.first || l > fl.scope.last) continue;
+ samtk->flinsert(fl, l, s);
+ (x, p) := sctlinecount(t, fl.scope.first, fl.lines);
+ fl.scope.last = p;
+ if (x >= 0) {
+ if (p > l + len s) {
+ samtk->flinsert(fl, l + len s,
+ sctget(t.sects, l + len s, p));
+ }
+ if (x == 0)
+ samtk->fldelexcess(fl);
+ } else {
+ (h1, h2) := scthole(t, l);
+ fl.scope.last = h1;
+ if (h2 > 0) {
+ outrequest(t.tag, h1, h2);
+ continue;
+ } else {
+ panic("Can't happen ??");
+ }
+ }
+ samtk->setscrollbar(t, fl);
+ }
+}
+
+outrequest(tag, h1, h2: int) {
+ for (l := requested; l != nil; l = tl l) {
+ (r1, r2) := hd l;
+ if (r1 == tag && r2 == h1) return;
+ }
+ outTsls(Trequest, tag, h1, h2);
+ requested = (tag, h1) :: requested;
+ setlock();
+}
+
+deletefls(t: ref Text, pos, nbytes: int)
+{
+ for (fls := t.flayers; fls != nil; fls = tl fls) {
+ fl := hd fls;
+ if (pos >= fl.scope.last) continue;
+ if (pos + nbytes <= fl.scope.first || pos >= fl.scope.last) {
+ fl.scope.first -= nbytes;
+ fl.scope.last -= nbytes;
+ continue;
+ }
+ samtk->fldelete(fl, pos, pos + nbytes);
+ (x, p) := sctlinecount(t, fl.scope.first, fl.lines);
+ if (x >= 0 && p > fl.scope.last) {
+ samtk->flinsert(fl, fl.scope.last,
+ sctget(t.sects, fl.scope.last, p));
+ fl.scope.last = p;
+ } else {
+ fl.scope.last = p;
+ (h1, h2) := scthole(t, fl.scope.last);
+ if (h2 > 0)
+ outrequest(t.tag, h1, h2);
+ }
+ samtk->setscrollbar(t, fl);
+ }
+}
+
+contract(s: string): string
+{
+ if (len s < 32)
+ cs := s;
+ else
+ cs = s[0:16] + " ... " + s[len s - 16:];
+ for (i := 0; i < len cs; i++)
+ if (cs[i] == '\n') cs[i] = '\u008a';
+ return cs;
+}
+
+cleanout()
+{
+ if ((fl := ctxt.which) == nil) return;
+ if ((i := whichtext(fl.tag)) < 0) panic("cleanout: whichtext");
+ t := ctxt.texts[i];
+
+ if (fl.typepoint >= 0 && fl.dot.first > fl.typepoint) {
+ s := sctget(t.sects, fl.typepoint, fl.dot.first);
+ outTslS(Samstub->Ttype, fl.tag, fl.typepoint, s);
+ t.state &= ~Samterm->LDirty;
+ }
+ fl.typepoint = -1;
+}
+
+newtext(tag, tp: int): int
+{
+ n := len ctxt.texts;
+ t := ref Text(
+ tag, # tag
+ 0, # lock
+ samtk->newflayer(tag, tp) :: nil, # flayers
+ 0, # nrunes
+ nil, # sects
+ 0 # state
+ );
+ texts := array [n + 1] of ref Text;
+ texts[0:] = ctxt.texts;
+ texts[n] = t;
+ ctxt.texts = texts;
+ samtk->newcur(t, hd t.flayers);
+ return n;
+}
+
+keypress(key: string)
+{
+ # Find text and flayer
+ fl := ctxt.which;
+ tag := fl.tag;
+ if ((i := whichtext(tag)) < 0) panic("keypress: whichtext");
+ t := ctxt.texts[i];
+
+ if (fl.dot.last != fl.dot.first) {
+ cut(t, fl);
+ }
+
+ case (key) {
+ "\b" =>
+ if (t.nrunes == 0 || fl.dot.first == 0)
+ return;
+ fl.dot.first--;
+ if (fl.typepoint >= 0 && fl.dot.first >= fl.typepoint) {
+ t.nrunes -= fl.dot.last - fl.dot.first;
+ t.sects = sctdelete(t.sects, fl.dot.first, fl.dot.last - fl.dot.first);
+ deletefls(t, fl.dot.first, fl.dot.last - fl.dot.first);
+ if (fl.dot.first == fl.typepoint) {
+ fl.typepoint = -1;
+ t.state &= ~Samterm->LDirty;
+ if ((i = whichmenu(tag)) < 0)
+ panic("keypress: whichmenu");
+ samtk->settitle(t, ctxt.menus[i].name);
+ }
+ } else {
+ cut(t, fl);
+ }
+ * =>
+ if (fl.typepoint < 0) {
+ fl.typepoint = fl.dot.first;
+ t.state |= Samterm->LDirty;
+ if ((i = whichmenu(tag)) < 0)
+ panic("keypress: whichmenu");
+ samtk->settitle(t, ctxt.menus[i].name);
+ }
+ if (fl.dot.first > t.nrunes)
+ panic("keypress -- cursor > file len");
+ t.sects = sctmakeroom(t.sects, fl.dot.first, len key);
+ t.nrunes += len key;
+ t.sects = sctput(t.sects, fl.dot.first, key);
+ insertfls(t, fl.dot.first, key);
+ f := fl.dot.first + len key;
+ samtk->setdot(fl, f, f);
+ if (key == "\n") {
+ if (f >= fl.scope.last) {
+ (n, p) := sctrevcnt(t.sects, f-1, 2*fl.lines/3);
+ if (n < 0) {
+ outTsll(Torigin, t.tag, f-1, 2*fl.lines/3);
+ setlock();
+ } else {
+ scrollto(fl, p);
+ }
+ }
+ if (t == ctxt.cmd && fl.dot.last == t.nrunes) {
+ outcmd();
+ setlock();
+ }
+ cleanout();
+ }
+ }
+ return;
+}
+
+cut(t: ref Text, fl: ref Flayer)
+{
+ if (fl.typepoint >= 0) panic("cut: typepoint");
+ outTsll(Tcut, fl.tag, fl.dot.first, fl.dot.last);
+ t.nrunes -= fl.dot.last - fl.dot.first;
+ t.sects = sctdelete(t.sects, fl.dot.first, fl.dot.last - fl.dot.first);
+ deletefls(t, fl.dot.first, fl.dot.last - fl.dot.first);
+}
+
+paste(t: ref Text, fl: ref Flayer)
+{
+ if (fl.typepoint >= 0) panic("paste: typepoint");
+ if (snarflen == 0) return;
+ if (fl.dot.first < fl.dot.last) cut(t, fl);
+ outTsl(Tpaste, fl.tag, fl.dot.first);
+}
+
+snarf(nil: ref Text, fl: ref Flayer)
+{
+ if (fl.typepoint >= 0) panic("snarf: typepoint");
+ if (fl.dot.first == fl.dot.last) return;
+ snarflen = fl.dot.last - fl.dot.first;
+ outTsll(Tsnarf, fl.tag, fl.dot.first, fl.dot.last);
+}
+
+look(nil: ref Text, fl: ref Flayer)
+{
+ if (fl.typepoint >= 0) panic("look: typepoint");
+ outTsll(Tlook, fl.tag, fl.dot.first, fl.dot.last);
+ setlock();
+}
+
+send(nil: ref Text, fl: ref Flayer)
+{
+ if (fl.typepoint >= 0) panic("send: typepoint");
+ outcmd();
+ outTsll(Tsend, fl.tag, fl.dot.first, fl.dot.last);
+ setlock();
+}
+
+search(nil: ref Text, fl: ref Flayer)
+{
+ if (fl.typepoint >= 0) panic("search: typepoint");
+ outcmd();
+ outT0(Tsearch);
+ setlock();
+}
+
+zerox(t: ref Text)
+{
+ fl := samtk->newflayer(t.tag, ctxt.cmd == t);
+ t.flayers = fl :: t.flayers;
+ m := whichmenu(t.tag);
+ samtk->settitle(t, ctxt.menus[m].name);
+ samtk->newcur(t, fl);
+ scrollto(fl, 0);
+}
+
+sctget(scts: list of ref Section, p1, p2: int): string
+{
+ while (scts != nil) {
+ sct := hd scts; scts = tl scts;
+ ln := len sct.text;
+ if (p1 < sct.nrunes) {
+ if (ln < sct.nrunes && p2 > ln) {
+ sctdump(scts, "panic");
+ panic("sctget - asking for a hole");
+ }
+ if (p2 > sct.nrunes) {
+ s := sct.text[p1:];
+ return s + sctget(scts, 0, p2 - ln);
+ }
+ return sct.text[p1:p2];
+ }
+ p1 -= sct.nrunes;
+ p2 -= sct.nrunes;
+ }
+ return "";
+}
+
+sctgetlines(scts: list of ref Section, p, n: int): (int, string)
+{
+ s := "";
+ while (scts != nil) {
+ sct := hd scts; scts = tl scts;
+ ln := len sct.text;
+ if (p < sct.nrunes) {
+ if (p > ln) return (n, s);
+ if (p > 0) b := p; else b = 0;
+ for (i := b; i < ln && n > 0; ) {
+ if (sct.text[i++] == '\n') n--;
+ }
+ if ( i > b)
+ s = s + sct.text[b:i];
+ if (n == 0 || ln < sct.nrunes) return (n, s);
+ }
+ p -= sct.nrunes;
+ }
+ return (n, s);
+}
+
+sctput(scts: list of ref Section, pos: int, s: string): list of ref Section
+{
+ # There should be a hole to receive text
+ if (scts == nil && s != "") panic("sctput: scts is nil\n");
+ sct := hd scts;
+ l := len sct.text;
+ if (sct.nrunes <= pos) {
+ return sct :: sctput(tl scts, pos-sct.nrunes, s);
+ }
+ if (pos < l) {
+ sctdump(scts, "panic");
+ panic("sctput: overwriting");
+ }
+ if (pos == l) {
+ if (sct.nrunes < l + len s) {
+ sct.text += s[:sct.nrunes-l];
+ return sct :: sctput(tl scts, 0, s[sct.nrunes-l:]);
+ }
+ sct.text += s;
+ return sct :: tl scts;
+ }
+ nrunes := sct.nrunes;
+ sct.nrunes = pos;
+ if (nrunes < pos + len s)
+ return sct ::
+ ref Section(nrunes-pos, s[:nrunes-pos]) ::
+ sctput(tl scts, 0, s[nrunes-pos:]);
+ return sct :: ref Section(nrunes-pos, s) :: tl scts;
+}
+
+sctmakeroom(scts: list of ref Section, pos: int, l: int): list of ref Section
+{
+ if (scts == nil) {
+ if (pos) panic("sctmakeroom: beyond end of sections");
+ return ref Section(l, nil) :: nil;
+ }
+ sct := hd scts;
+ if (sct.nrunes < pos)
+ return sct :: sctmakeroom(tl scts, pos-sct.nrunes, l);
+ if (len sct.text <= pos) {
+ # just add to the hole at end of section
+ sct.nrunes += l;
+ return sct :: tl scts;
+ }
+ if (pos == 0) {
+ # text is non-nil!
+ bsct := ref Section(l, nil);
+ return bsct :: scts;
+ }
+ bsct := ref Section(pos + l, sct.text[0:pos]);
+ esct := ref Section(sct.nrunes-pos, sct.text[pos:]);
+ return bsct :: esct :: tl scts;
+}
+
+sctdelete(scts: list of ref Section, start, nbytes: int): list of ref Section
+{
+ if (nbytes == 0) return scts;
+ if (scts == nil) panic("sctdelete: at eof");
+ sct := hd scts;
+ scts = tl scts;
+ nrunes := sct.nrunes;
+ if (start + nbytes < len sct.text) {
+ sct.text = sct.text[0:start] + sct.text[start+nbytes:];
+ sct.nrunes -= nbytes;
+ return sct :: scts;
+ }
+ if (start < nrunes) {
+ if (start > 0) {
+ if (start < len sct.text)
+ sct.text = sct.text[0:start];
+ if (start + nbytes <= nrunes) {
+ sct.nrunes -= nbytes;
+ return sct :: scts;
+ }
+ sct.nrunes = start;
+ return sct :: sctdelete(scts, 0, nbytes-nrunes+start);
+ }
+ if (nbytes < nrunes) {
+ sct.text = "";
+ sct.nrunes -= nbytes;
+ return sct :: scts;
+ }
+ return sctdelete(scts, 0, nbytes - nrunes);
+ }
+ return sct :: sctdelete(scts, start - nrunes, nbytes);
+}
+
+grow(t: ref Text, at, l: int)
+{
+# sctdump(t.sects, "grow, before");
+ t.sects = sctmakeroom(t.sects, at, l);
+ t.nrunes += l;
+# sctdump(t.sects, "grow, after");
+ for (fls := t.flayers; fls != nil; fls = tl fls) {
+ fl := hd fls;
+ if (at < fl.scope.first) fl.scope.first += l;
+ if (at < fl.scope.last) fl.scope.last += l;
+ }
+}
+
+findhole(t: ref Text): (int, int)
+{
+ for (fls := t.flayers; fls != nil; fls = tl fls) {
+ (h, l) := scthole(t, (hd fls).scope.first);
+ if (l > 0) return (h, l);
+ }
+ return (0, 0);
+}
+
+sctdump(scts: list of ref Section, s: string)
+{
+ fprint(ctxt.logfd, "Sctdump: %s\n", s);
+ p := 0;
+ while (scts != nil) {
+ sct := hd scts; scts = tl scts;
+ fprint(ctxt.logfd, "\tsct@%4d len=%4d len txt=%4d: %s\n",
+ p, sct.nrunes, len sct.text, contract(sct.text));
+ p += sct.nrunes;
+ }
+ fprint(ctxt.logfd, "\tend@%4d\n", p);
+}
diff --git a/appl/wm/samstub.m b/appl/wm/samstub.m
new file mode 100644
index 00000000..5bde16d8
--- /dev/null
+++ b/appl/wm/samstub.m
@@ -0,0 +1,132 @@
+Samstub: module
+{
+ PATH: con "/dis/wm/samstub.dis";
+ SAM: con "sam -R";
+
+ VERSION: con 0;
+ UTFmax: con 3;
+
+ TBLOCKSIZE: con 512; # largest piece of text sent to terminal ...
+ DATASIZE: con (UTFmax*TBLOCKSIZE+30);
+ # ... including protocol header stuff
+ SNARFSIZE: con 4096; # maximum length of exchanged snarf buffer
+
+ # Message types
+ Error, Status, Debug: con iota;
+
+ Sammsg: adt {
+ mtype: int;
+ mcount: int;
+ mdata: array of byte;
+
+ inshort: fn(h: self ref Sammsg, n: int): int;
+ inlong: fn(h: self ref Sammsg, n: int): int;
+ invlong: fn(h: self ref Sammsg, n: int): big;
+ outcopy: fn(h: self ref Sammsg, pos: int, data: array of byte);
+ outshort: fn(h: self ref Sammsg, pos: int, s: int);
+ outlong: fn(h: self ref Sammsg, pos: int, s: int);
+ outvlong: fn(h: self ref Sammsg, pos: int, s: big);
+ };
+
+ Samio: adt {
+ ctl: ref Sys->FD; # /cmd/nnn/ctl
+ data: ref Sys->FD; # /cmd/nnn/data
+ buffer: array of byte; # buffered data read from sam
+ index: int;
+ count: int; # pointers into buffer
+
+ };
+
+ init: fn(ctxt: ref Context);
+
+ start: fn(): (ref Samio, chan of ref Sammsg);
+ sender: fn(s: ref Samio, c: chan of ref Sammsg);
+ receiver: fn(s: ref Samio, c: chan of ref Sammsg);
+
+ outTs: fn(t, s: int);
+ outTv: fn(t: int, i: big);
+ outT0: fn(t: int);
+ outTsl: fn(t, m, l: int);
+ outTslS: fn(t, s1, l1: int, s: string);
+ outTsll: fn(t, m, l1, l2: int);
+
+ cleanout: fn();
+ close: fn(win, tag: int);
+ cut: fn(t: ref Text, fl: ref Flayer);
+ findhole: fn(t: ref Text): (int, int);
+ grow: fn(t: ref Text, l1, l2: int);
+ horigin: fn(m, l: int);
+ inmesg: fn(h: ref Sammsg): int;
+ keypress: fn(key: string);
+ look: fn(t: ref Text, fl: ref Flayer);
+ menuins: fn(p: int, s: string, t: ref Text, tg: int);
+ newtext: fn(tag, tp: int): int;
+ paste: fn(t: ref Text, fl: ref Flayer);
+ scrollto: fn(fl: ref Flayer, where: int);
+ sctget: fn(scts: list of ref Section, p1, p2: int): string;
+ sctgetlines: fn(scts: list of ref Section, p, n: int):
+ (int, string);
+ scthole: fn(t: ref Text, f: int): (int, int);
+ sctput: fn(scts: list of ref Section, pos: int, s: string):
+ list of ref Section;
+ search: fn(t: ref Text, fl: ref Flayer);
+ send: fn(t: ref Text, fl: ref Flayer);
+ setlock: fn();
+ snarf: fn(t: ref Text, fl: ref Flayer);
+ startcmdfile: fn();
+ startfile: fn(tag: int): int;
+ startnewfile: fn();
+ updatefls: fn(t: ref Text, l: int, s: string);
+ zerox: fn(t: ref Text);
+
+ Tversion, # version
+ Tstartcmdfile, # terminal just opened command frame
+ Tcheck, # ask host to poke with Hcheck
+ Trequest, # request data to fill a hole
+ Torigin, # gimme an Horigin near here
+ Tstartfile, # terminal just opened a file's frame
+ Tworkfile, # set file to which commands apply
+ Ttype, # add some characters, but terminal already knows
+ Tcut,
+ Tpaste,
+ Tsnarf,
+ Tstartnewfile, # terminal just opened a new frame
+ Twrite, # write file
+ Tclose, # terminal requests file close; check mod. status
+ Tlook, # search for literal current text
+ Tsearch, # search for last regular expression
+ Tsend, # pretend he typed stuff
+ Tdclick, # double click
+ Tstartsnarf, # initiate snarf buffer exchange
+ Tsetsnarf, # remember string in snarf buffer
+ Tack, # acknowledge Hack
+ Texit, # exit
+ TMAX: con iota;
+
+ Hversion, # version
+ Hbindname, # attach name[0] to text in terminal
+ Hcurrent, # make named file the typing file
+ Hnewname, # create "" name in menu
+ Hmovname, # move file name in menu
+ Hgrow, # insert space in rasp
+ Hcheck0, # see below
+ Hcheck, # ask terminal to check whether it needs more data
+ Hunlock, # command is finished; user can do things
+ Hdata, # store this data in previously allocated space
+ Horigin, # set origin of file/frame in terminal
+ Hunlockfile, # unlock file in terminal
+ Hsetdot, # set dot in terminal
+ Hgrowdata, # Hgrow + Hdata folded together
+ Hmoveto, # scrolling, context search, etc.
+ Hclean, # named file is now 'clean'
+ Hdirty, # named file is now 'dirty'
+ Hcut, # remove space from rasp
+ Hsetpat, # set remembered regular expression
+ Hdelname, # delete file name from menu
+ Hclose, # close file and remove from menu
+ Hsetsnarf, # remember string in snarf buffer
+ Hsnarflen, # report length of implicit snarf
+ Hack, # request acknowledgement
+ Hexit,
+ HMAX: con iota;
+};
diff --git a/appl/wm/samterm.m b/appl/wm/samterm.m
new file mode 100644
index 00000000..34c82095
--- /dev/null
+++ b/appl/wm/samterm.m
@@ -0,0 +1,75 @@
+include "tk.m";
+include "wmlib.m";
+
+Samterm: module
+{
+
+ PATH: con "/dis/wm/sam.dis";
+
+ Section: adt
+ {
+ nrunes: int;
+ text: string; # if null, we haven't got it
+ };
+
+ Range: adt {
+ first, last: int;
+ };
+
+ Flayer: adt {
+ tag: int;
+ t: ref Tk->Toplevel;
+ tkwin: string; # tk window name
+ scope: Range; # part of file in range
+ dot: Range; # cursor position wrt file, not scope
+ width: int; # window width (not used yet)
+ lineheigth: int; # height of a single line (for resize)
+ lines: int; # window height in lines
+ scrollbar: Range; # current position of scrollbar
+ typepoint: int; # -1, or pos of first unsent char typed
+ };
+
+ Text: adt {
+ tag: int;
+ lock: int;
+ flayers: list of ref Flayer; # hd flayers is current
+ nrunes: int;
+ sects: list of ref Section;
+ state: int;
+ };
+
+ Dirty: con 1;
+ LDirty: con 2;
+
+ Menu: adt {
+ tag: int;
+ name: string;
+ text: ref Text;
+ };
+
+ Context: adt {
+ ctxt: ref Draw->Context;
+ tag: int; # globally unique tag generator
+ lock: int; # global lock
+
+ keysel: array of chan of string;
+ scrollsel: array of chan of string;
+ buttonsel: array of chan of string;
+ menu2sel: array of chan of string;
+ menu3sel: array of chan of string;
+ titlesel: array of chan of string;
+ flayers: array of ref Flayer;
+
+ menus: array of ref Menu;
+ texts: array of ref Text;
+
+ cmd: ref Text; # sam command window
+ which: ref Flayer; # current flayer (sam or work)
+ work: ref Flayer; # current work flayer
+
+ pgrp: int; # process group
+ logfd: ref FD;
+ };
+
+ init: fn(ctxt: ref Draw->Context, args: list of string);
+};
diff --git a/appl/wm/samtk.b b/appl/wm/samtk.b
new file mode 100644
index 00000000..31eea9fb
--- /dev/null
+++ b/appl/wm/samtk.b
@@ -0,0 +1,688 @@
+implement Samtk;
+
+include "sys.m";
+sys: Sys;
+sprint, FD: import sys;
+
+include "draw.m";
+draw: Draw;
+
+include "samterm.m";
+Context, Flayer, Text, Section: import Samterm;
+
+include "tkclient.m";
+
+include "samtk.m";
+
+ctxt: ref Context;
+
+tk: Tk;
+tkclient: Tkclient;
+
+tksam1 := array[] of {
+ "frame .w",
+ "scrollbar .w.s -command {send scroll}",
+ "text .w.t -width 80w -height 8h",
+ "pack .w.s -side left -fill y",
+ "pack .w.t -fill both -expand 1",
+ "pack .Wm_t -fill x",
+ "pack .w -fill both -expand 1",
+ "pack propagate . 0",
+};
+
+tkwork1 := array[] of {
+ "frame .w",
+ "scrollbar .w.s -command {send scroll}",
+ "text .w.t -width 80w -height 20h",
+ "pack .w.s -side left -fill y",
+ "pack .w.t -fill both -expand 1",
+ "pack .Wm_t -fill x",
+ "pack .w -fill both -expand 1",
+ "pack propagate . 0",
+};
+
+tkcmdlist := array[] of {
+ "bind .w.t <Key> {send keys {%A}}",
+ "bind .w.t <Key-\b> {send keys {%A}}",
+ "bind .w.s <ButtonRelease-1> +{send scroll %s %b %y}",
+ "bind .w.t <ButtonPress-1> +{send button1 %s %b %x %y}",
+ "bind .w.t <ButtonRelease-1> +{send button1 %s %b %x %y}",
+ "bind .w.t <Double-ButtonPress-1> {send button1 2 %b %x %y}",
+ "bind .w.t <Double-ButtonRelease-1> {send button1 3 %b %x %y}",
+ "bind .w.t <ButtonPress-2> {.m2 post %x %y; grab set .m2}",
+ "bind .w.t <ButtonPress-3> {.m3 post %x %y; grab set .m3}",
+ "bind . <Configure> {send titlesel resize}",
+ "focus .w.t",
+ "update"
+};
+
+menuidx := array[2] of {"0","0"};
+
+init(c: ref Context)
+{
+ ctxt = c;
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+
+ tkclient = load Tkclient Tkclient->PATH;
+ tkclient->init();
+
+ scrollpos = scrolllines = 0;
+}
+
+x := 10;
+y := 10;
+
+newflayer(tag, tp: int): ref Flayer
+{
+ if (ctxt.which != nil) {
+ tk->cmd(ctxt.which.t,
+ ".Wm_t.title configure -background blue; update");
+ }
+ (t, cmdc) := tkclient->toplevel(ctxt.ctxt.screen, "-borderwidth 1 -relief raised", "SamTerm", Tkclient->Appl);
+ tk->cmd(t, ". configure -x "+string x+" -y "+string y+"; update");
+
+ if (x == 10 && y == 10) {
+ y = 200;
+ } else {
+ x += 40;
+ y += 40;
+ }
+
+ n := chanadd();
+ ctxt.titlesel[n] = cmdc;
+ tk->namechan(t, ctxt.menu3sel[n], "menu3");
+ tk->namechan(t, ctxt.menu2sel[n], "menu2");
+ tk->namechan(t, ctxt.buttonsel[n], "button1");
+ tk->namechan(t, ctxt.keysel[n], "keys");
+ tk->namechan(t, ctxt.scrollsel[n], "scroll");
+ tk->namechan(t, ctxt.titlesel[n], "titlesel");
+
+ lines: int;
+ if (tp) {
+ lines = 8;
+ tkclient->tkcmds(t, tksam1);
+ mkmenu2c(t);
+ } else {
+ lines = 20;
+ tkclient->tkcmds(t, tkwork1);
+ mkmenu2(t);
+ }
+ mkmenu3(t);
+ tkclient->tkcmds(t, tkcmdlist);
+
+ f := ref Flayer(
+ tag, # tag
+ t, # t
+ "SamTerm", # tkwin
+ (0, 0), # scope
+ (0, 0), # dot
+ int tk->cmd(t, ".w.t cget actwidth"), # screen width
+ int tk->cmd(t, ".w.t cget actheight") / lines, # lineheigth
+ lines, # lines
+ (0, 1), # scrollbar
+ -1 # typepoint
+ );
+ ctxt.flayers[n] = f;
+ return f;
+}
+
+menu2str := array [] of {
+ "cut",
+ "paste",
+ "snarf",
+ "look",
+# "exch",
+ "send", # storage for last pattern
+};
+
+menu3str := array [] of {
+ "new",
+ "zerox",
+ "close",
+ "write",
+};
+
+mkmenu2c(t: ref Tk->Toplevel)
+{
+ menus := array [NMENU2+1] of string;
+
+ menus[0] = "menu .m2";
+ for (i := 0; i < NMENU2; i++) {
+ menus[i+1] = addmenuitem(2, "menu2", menu2str[i]);
+ }
+ tkclient->tkcmds(t, menus);
+}
+
+mkmenu2(t: ref Tk->Toplevel)
+{
+ menus := array [NMENU2+1] of string;
+
+ menus[0] = "menu .m2";
+ for (i := 0; i < NMENU2-1; i++) {
+ menus[i+1] = addmenuitem(2, "menu2", menu2str[i]);
+ }
+ menus[NMENU2] = addmenuitem(2, "edit", "/");
+ tkclient->tkcmds(t, menus);
+}
+
+mkmenu3(t: ref Tk->Toplevel)
+{
+ menus := array [NMENU3+len ctxt.menus+1] of string;
+
+ menus[0] = "menu .m3";
+ for (i := 0; i < NMENU3; i++) {
+ menus[i+1] = addmenuitem(3, "menu3", menu3str[i]);
+ }
+ for (i = 0; i < len ctxt.menus; i++) {
+ menus[i+NMENU3+1] = addmenuitem(3, "menu3", ctxt.menus[i].name);
+ }
+ tkclient->tkcmds(t, menus);
+}
+
+addmenuitem(d: int, m, s: string): string
+{
+ return sprint(".m%d add command -text %s -command {send %s %s}",
+ d, s, m, s);
+}
+
+menuins(pos: int, s: string)
+{
+ for (i := 0; i < len ctxt.flayers; i++)
+ tk->cmd(ctxt.flayers[i].t,
+ sprint(".m3 insert %d command -text %s -command {send menu3 %s}",
+ pos + NMENU3, s, s));
+}
+
+menudel(pos: int)
+{
+ for (i := 0; i < len ctxt.flayers; i++)
+ tk->cmd(ctxt.flayers[i].t, sprint(".m3 delete %d", pos + NMENU3));
+}
+
+hsetpat(s: string)
+{
+ for (i := 0; i < len ctxt.flayers; i++) {
+ fl := ctxt.flayers[i];
+ if (fl.tag != ctxt.cmd.tag) {
+ tk->cmd(fl.t, ".m2 entryconfigure "
+ + string Search
+ + " -command {send menu2 search} -text '/" + s);
+ }
+ }
+}
+
+lastsearchstring := "//";
+
+setmenu(num : int,c : string){
+ fl := ctxt.flayers[num];
+ (nil, l) := sys->tokenize(c, " ");
+ x1 := int hd l - 50;
+ y1 := int hd tl l - int tk->cmd(fl.t, ".m"+string num+" yposition "+menuidx[num-2])
+ - 10;
+ tk->cmd(fl.t, ".m"+string num+" activate "+menuidx[num-2]+
+ "; .m"+string num+" post "+string x1+" "+string y1+
+ "; grab set .m"+string num+"; update");
+}
+
+titlectl(win: int, menu: string)
+{
+ tkclient->wmctl(ctxt.flayers[win].t, menu);
+}
+
+flraise(t: ref Text, fl: ref Flayer)
+{
+ nfls: list of ref Flayer;
+
+ nfls = nil;
+ t.flayers = fl :: dellist(t.flayers, fl);
+ tk->cmd(fl.t, "raise .; focus .w.t; update");
+}
+
+dellist(fls: list of ref Flayer, fl: ref Flayer): list of ref Flayer
+{
+ if (fls == nil) return nil;
+ if (hd fls == fl) return dellist(tl fls, fl);
+ return hd fls :: dellist(tl fls, fl);
+}
+
+append(fls: list of ref Flayer, fl: ref Flayer): list of ref Flayer
+{
+ if (fls == nil) return fl :: nil;
+ return hd fls :: append(tl fls, fl);
+}
+
+focus(fl: ref Flayer)
+{
+ tk->cmd(fl.t, "focus .w.t; update");
+}
+
+newcur(t: ref Text, fl: ref Flayer)
+{
+ if (ctxt.which == fl) return;
+ flraise(t, fl);
+ ctxt.which = fl;
+ if (t != ctxt.cmd)
+ ctxt.work = fl;
+}
+
+settitle(t: ref Text, s: string)
+{
+ sd := "";
+ sz := "";
+ if (t.state & Samterm->Dirty) sd = " (Dirty)";
+ if (t != ctxt.cmd && (t.state & Samterm->LDirty)) sd = " (Modified)";
+ if (len t.flayers > 1) sz = " (Zeroxed)";
+ for (fls := t.flayers; fls != nil; fls = tl fls) {
+ fl := hd fls;
+ fl.tkwin = s;
+ tkclient->settitle(fl.t, s + sd + sz);
+ tk->cmd(fl.t, "update");
+ }
+}
+
+resize(fl: ref Flayer)
+{
+ fl.lines = int tk->cmd(fl.t, ".w.t cget actheight") / fl.lineheigth;
+}
+
+allflayers(s: string)
+{
+ for (i := 0; i < len ctxt.texts; i++)
+ for (fls := ctxt.texts[i].flayers; fls != nil; fls = tl fls) {
+ fl := hd fls;
+ tk->cmd(fl.t, s);
+ }
+}
+
+setdot(fl: ref Flayer, l1, l2: int)
+{
+ tk->cmd(fl.t, ".w.t tag remove sel 0.0 end");
+
+ fl.dot.first = l1;
+ fl.dot.last = l2;
+ if (l2 <= fl.scope.first)
+ tk->cmd(fl.t, ".w.t mark set insert 0.0");
+ else if (fl.scope.last <= l1)
+ tk->cmd(fl.t, ".w.t mark set insert end");
+ else {
+ tk->cmd(fl.t, sprint(".w.t mark set insert 0.0+%dchars",
+ l1-fl.scope.first));
+ if (l1 != l2)
+ tk->cmd(fl.t, sprint(".w.t tag add sel 0.0+%dchars 0.0+%dchars",
+ l1-fl.scope.first,
+ l2-fl.scope.first));
+ }
+ tk->cmd(fl.t, "update");
+}
+
+panic(s: string)
+{
+ stderr := sys->fildes(2);
+ sys->fprint(stderr, "Panic: %s\n", s);
+ f := sys->sprint("#p/%d/ctl", ctxt.pgrp);
+ if ((fd := sys->open(f, sys->OWRITE)) != nil)
+ sys->write(fd, array of byte "killgrp\n", 8);
+ exit;
+}
+
+whichmenu(tag: int): int
+{
+ for (i := 0; i < len ctxt.menus; i++)
+ if (ctxt.menus[i].tag == tag)
+ return i;
+ return -1;
+}
+
+whichtext(tag: int): int
+{
+ for (i := 0; i < len ctxt.texts; i++)
+ if (ctxt.texts[i].tag == tag)
+ return i;
+ return -1;
+}
+
+setscrollbar(t: ref Text, fl: ref Flayer)
+{
+ ll := real t.nrunes;
+ f1 := 0.0; f2 := 1.0;
+ if (ll != 0.0) {
+ f1 = real fl.scope.first / ll;
+ if (fl.scope.last > t.nrunes)
+ f2 = 1.0;
+ else
+ f2 = real fl.scope.last / ll;
+ }
+ fl.scrollbar = fl.scope;
+ tk->cmd(fl.t, sprint(".w.s set %f %f; update", f1, f2));
+}
+
+buttonselect(fl: ref Flayer, s: string): int
+{
+ tag := fl.tag;
+ if ((i := whichtext(tag)) < 0) panic("buttonselect: whichtext");
+ t := ctxt.texts[i];
+
+ (n, l) := sys->tokenize(s, " ");
+ if (n != 4) panic("buttonselect");
+
+ # ignore mouse down -- wait for mouse up
+ if (hd l == "1" || hd l == "3") return 0;
+
+ if (ctxt.which != fl) {
+ if (ctxt.menus[i].text != ctxt.cmd)
+ ctxt.work = fl;
+ newcur(t, fl);
+# setdot(fl, fl.dot.first, fl.dot.first);
+ return 0;
+ }
+
+ if (hd l == "2") {
+ # Double click
+ l = tl tl l;
+ s = tk->cmd(fl.t, ".w.t index @" + hd l + "," + hd tl l);
+ fl.dot.first = fl.dot.last = coord2pos(t, fl, s);
+ return 1;
+ }
+
+ rg := tk->cmd(fl.t, ".w.t tag ranges sel");
+ if (rg == "") {
+ # Nothing selected, find insertion point
+ l = tl tl l;
+ s = tk->cmd(fl.t, ".w.t index @" + hd l + "," + hd tl l);
+ fl.dot.first = fl.dot.last = coord2pos(t, fl, s);
+ } else {
+ (n, l) = sys->tokenize(rg, " ");
+ #if (n == 4 && hd tl l == hd tl tl l)
+ # lst := hd tl tl tl l;
+ #else if (n != 2) panic("buttonselect: tag ranges");
+ #else lst = hd tl l;
+ # We only have one contiguous selection, so, take the
+ # first as dot.first and the last as dot.last
+ fst:=hd l;
+ lst:=fst;
+ while(l!=nil){
+ lst=hd l;
+ l = tl l;
+ }
+ fl.dot.first = coord2pos(t, fl, fst);
+ fl.dot.last = coord2pos(t, fl, lst);
+ tk->cmd(fl.t, ".w.t mark set insert " + fst);
+ tk->cmd(fl.t, "update");
+ }
+ return 0;
+}
+
+coord2pos(t: ref Text, fl: ref Flayer, s: string): int
+{
+ x, y: int;
+
+ (n, l) := sys->tokenize(s, ".");
+ if (n != 2) panic("coord2pos");
+ y = (int hd l) - 1;
+ x = int hd tl l;
+ if (x == 0 && y == 0) return fl.scope.first;
+ first := fl.scope.first;
+ for (scts := t.sects; scts != nil; scts = tl scts) {
+ sct := hd scts;
+ if (first >= sct.nrunes) {
+ first -= sct.nrunes;
+ continue;
+ }
+ if (first > 0) i := first; else i = 0;
+ while (i < len sct.text) {
+ if (y) {
+ if (sct.text[i++] == '\n') y--;
+ } else {
+ if (x <= 1)
+ return fl.scope.first - first + i + x;
+ if (sct.text[i++] == '\n') panic("coord2pos");
+ x--;
+ }
+ }
+ if (len sct.text < sct.nrunes) panic("coord2pos: hole");
+ first -= sct.nrunes;
+ }
+ if (x <= 0 && y == 0) return t.nrunes;
+ panic("coord2pos: can't find");
+ return(-1);
+}
+
+scrollpos, scrolllines: int;
+
+scroll(fl: ref Flayer, s: string): (int, int)
+{
+ tag := fl.tag;
+ if ((i := whichtext(tag)) < 0) panic("scroll: whichtext");
+ t := ctxt.texts[i];
+ (n, l) := sys->tokenize(s, " ");
+ height := fl.scrollbar.last - fl.scrollbar.first;
+ length := t.nrunes;
+ case (hd l) {
+ "0" =>
+ if (n != 3) panic("scroll: format");
+ return (scrollpos, scrolllines);
+ "moveto" =>
+ if (n != 2) panic("scroll: format");
+ f := real hd tl l;
+ if (f < 0.0) f = 0.0;
+ if (f > 1.0) f = 1.0;
+ scrollpos = int (f * real length) - height/2;
+ scrolllines = 1;
+ "scroll" =>
+ if (n != 3) panic("scroll: format");
+ l = tl l;
+ n = int hd l;
+ case(hd tl l) {
+ "page" =>
+ if (n < 0) {
+ scrollpos = fl.scrollbar.first;
+ scrolllines = fl.lines;
+ break;
+ }
+ scrollpos = fl.scrollbar.last;
+ scrolllines = 0;
+ "unit" =>
+ if (n < 0) {
+ scrollpos = fl.scrollbar.first - 1;
+ scrolllines = 1;
+ break;
+ }
+ (p, q) := rasplines(t.sects, fl.scrollbar.first, 1);
+ if (p > 0) {
+ scrollpos = p;
+ scrolllines = 0;
+ } else {
+ scrollpos = fl.scrollbar.first;
+ scrolllines = 0;
+ }
+ }
+ * =>
+ panic("scroll: input");
+ }
+ if (scrollpos > length)
+ scrollpos = length;
+ if (scrollpos < 0) {
+ scrollpos = 0;
+ scrolllines = 0;
+ }
+ if (length != 0)
+ tk->cmd(fl.t, sprint(".w.s set %f %f",
+ real scrollpos / real length,
+ real (scrollpos + height) / real length));
+ else
+ tk->cmd(fl.t, ".w.s set 0.0 1.0");
+ tk->cmd(fl.t, "update");
+ return (-1, -1);
+}
+
+flclear(fl: ref Flayer)
+{
+ tk->cmd(fl.t, ".w.t delete 0.0 end");
+ tk->cmd(fl.t, "update");
+}
+
+flinsert(fl: ref Flayer, l: int, s: string)
+{
+ offset := l-fl.scope.first;
+ tk->cmd(fl.t, ".w.t insert 0.0+" + string offset + "chars '" + s);
+ setdot(fl, fl.dot.first, fl.dot.last);
+}
+
+fldelexcess(fl: ref Flayer)
+{
+ tk->cmd(fl.t, ".w.t delete " + string (fl.lines+1) + ".0 end");
+}
+
+fldelete(fl: ref Flayer, l1, l2: int)
+{
+ s: string;
+ if (l1 <= fl.scope.first) {
+ if (l2 >= fl.scope.last) {
+ s = sprint(".w.t delete 0.0 end");
+ fl.scope.first = fl.scope.last = l1;
+ } else {
+ s = sprint(".w.t delete 0.0 0.0+%dchars",
+ l2 - fl.scope.first);
+ fl.scope.last -= l2 - l1;
+ fl.scope.first = l1;
+ }
+ } else {
+ if (l2 >= fl.scope.last) {
+ s = sprint(".w.t delete 0.0+%dchars end",
+ l1 - fl.scope.first);
+ fl.scope.last = l1;
+ } else {
+ s = sprint(".w.t delete 0.0+%dchars 0.0+%dchars",
+ l1 - fl.scope.first, l2 - fl.scope.first);
+ fl.scope.last -= l2 - l1;
+ }
+ }
+ if (fl.dot.first >= l2) fl.dot.first -= l2-l1;
+ else if (fl.dot.first > l1) fl.dot.first = l1;
+ if (fl.dot.last >= l2) fl.dot.last -= l2-l1;
+ else if (fl.dot.last > l1) fl.dot.last = l1;
+ tk->cmd(fl.t, s);
+ setdot(fl, fl.dot.first, fl.dot.last);
+ tk->cmd(fl.t, "update");
+}
+
+# Calculate position forward or backward nlines lines from pos.
+# If lines > 0 count forward, if lines < 0 count backward.\
+# Returns a pair, (position, nlines). Nlines is the remaining
+# number of lines to be found. If non-zero, beginning or end of
+# rasp was encountered while still counting, or a hole was
+# encountered. In the former case, position will be 0 or nrunes,
+# in the latter case, position will be set to -1.
+# To search to the beginning of the current line, set nlines to -1;
+
+rasplines(scts: list of ref Section, pos, nlines: int): (int, int)
+{
+ p, i: int;
+ if (nlines < 0) {
+ if (scts != nil) {
+ sct := hd scts; scts = tl scts;
+ if (pos > sct.nrunes) {
+ (p, nlines) =
+ rasplines(scts, pos - sct.nrunes, nlines);
+ if (p < 0) return (p, nlines);
+ pos = p + sct.nrunes;
+ if (nlines == 0) return (pos, 0);
+ }
+ if (pos > len sct.text) return (-1, nlines);
+ for (p = pos-1; p >= 0; p--) {
+ if (sct.text[p] == '\n') nlines++;
+ if (nlines == 0) return (p+1, 0);
+ }
+ }
+ return (0, nlines);
+ } else {
+ p = 0;
+ while (scts != nil) {
+ sct := hd scts; scts = tl scts;
+ if (pos < sct.nrunes) {
+ for (i = pos; i < len sct.text; i++) {
+ if (sct.text[i] == '\n') nlines--;
+ if (nlines == 0) return (p+i+1, 0);
+ }
+ if (i < sct.nrunes) return (-1, nlines);
+ }
+ pos -= sct.nrunes;
+ if (pos < 0) pos = 0;
+ p += sct.nrunes;
+ }
+ return (p, nlines);
+ }
+}
+
+chanadd(): int
+{
+ l := len ctxt.flayers;
+
+ keysel := array [l+1] of chan of string;
+ keysel[0:] = ctxt.keysel;
+ keysel[l] = chan of string;
+ ctxt.keysel = keysel;
+ scrollsel := array [l+1] of chan of string;
+ scrollsel[0:] = ctxt.scrollsel;
+ scrollsel[l] = chan of string;
+ ctxt.scrollsel = scrollsel;
+ buttonsel := array [l+1] of chan of string;
+ buttonsel[0:] = ctxt.buttonsel;
+ buttonsel[l] = chan of string;
+ ctxt.buttonsel = buttonsel;
+ menu2sel := array [l+1] of chan of string;
+ menu2sel[0:] = ctxt.menu2sel;
+ menu2sel[l] = chan of string;
+ ctxt.menu2sel = menu2sel;
+ menu3sel := array [l+1] of chan of string;
+ menu3sel[0:] = ctxt.menu3sel;
+ menu3sel[l] = chan of string;
+ ctxt.menu3sel = menu3sel;
+ titlesel := array [l+1] of chan of string;
+ titlesel[0:] = ctxt.titlesel;
+ titlesel[l] = chan of string;
+ ctxt.titlesel = titlesel;
+ flayers := array [l+1] of ref Flayer;
+ flayers[0:] = ctxt.flayers;
+ flayers[l] = nil;
+ ctxt.flayers = flayers;
+ return l;
+}
+
+chandel(n: int)
+{
+ l := len ctxt.flayers;
+ if (n >= l)
+ panic("chandel");
+
+ keysel := array [l-1] of chan of string;
+ keysel[0:] = ctxt.keysel[0:n];
+ keysel[n:] = ctxt.keysel[n+1:];
+ ctxt.keysel = keysel;
+ scrollsel := array [l-1] of chan of string;
+ scrollsel[0:] = ctxt.scrollsel[0:n];
+ scrollsel[n:] = ctxt.scrollsel[n+1:];
+ ctxt.scrollsel = scrollsel;
+ buttonsel := array [l-1] of chan of string;
+ buttonsel[0:] = ctxt.buttonsel[0:n];
+ buttonsel[n:] = ctxt.buttonsel[n+1:];
+ ctxt.buttonsel = buttonsel;
+ menu2sel := array [l-1] of chan of string;
+ menu2sel[0:] = ctxt.menu2sel[0:n];
+ menu2sel[n:] = ctxt.menu2sel[n+1:];
+ ctxt.menu2sel = menu2sel;
+ menu3sel := array [l-1] of chan of string;
+ menu3sel[0:] = ctxt.menu3sel[0:n];
+ menu3sel[n:] = ctxt.menu3sel[n+1:];
+ ctxt.menu3sel = menu3sel;
+ titlesel := array [l-1] of chan of string;
+ titlesel[0:] = ctxt.titlesel[0:n];
+ titlesel[n:] = ctxt.titlesel[n+1:];
+ ctxt.titlesel = titlesel;
+ flayers := array [l-1] of ref Flayer;
+ flayers[0:] = ctxt.flayers[0:n];
+ flayers[n:] = ctxt.flayers[n+1:];
+ ctxt.flayers = flayers;
+}
diff --git a/appl/wm/samtk.m b/appl/wm/samtk.m
new file mode 100644
index 00000000..cc3efe18
--- /dev/null
+++ b/appl/wm/samtk.m
@@ -0,0 +1,54 @@
+Samtk: module
+{
+
+ PATH: con "/dis/wm/samtk.dis";
+
+ Cut,
+ Paste,
+ Snarf,
+ Look,
+# Exch,
+ Send,
+ NMENU2: con iota;
+ Search: con Send;
+
+ New,
+ Zerox,
+ Close,
+ Write,
+ NMENU3: con iota;
+
+ None,
+ Some,
+ All: con iota; # visibility in flayer (`some' may not be used)
+
+ init: fn(ctxt: ref Context);
+
+ allflayers: fn(s: string);
+ append: fn(fls: list of ref Flayer, fl: ref Flayer):
+ list of ref Flayer;
+ buttonselect: fn(fl: ref Flayer, s: string): int;
+ chanadd: fn(): int;
+ chandel: fn(n: int);
+ coord2pos: fn(t: ref Text, fl: ref Flayer, s: string): int;
+ flclear: fn(fl: ref Flayer);
+ fldelete: fn(fl: ref Flayer, l1, l2: int);
+ fldelexcess: fn(fl: ref Flayer);
+ flinsert: fn(fl: ref Flayer, l: int, s: string);
+ flraise: fn(t: ref Text, fl: ref Flayer);
+ focus: fn(fl: ref Flayer);
+ hsetpat: fn(s: string);
+ menudel: fn(pos: int);
+ menuins: fn(pos: int, s: string);
+ newcur: fn(t: ref Text, fl: ref Flayer);
+ newflayer: fn(tag, tp: int): ref Flayer;
+ panic: fn(s: string);
+ resize: fn(fl: ref Flayer);
+ scroll: fn(fl: ref Flayer, s: string): (int, int);
+ setdot: fn(fl: ref Flayer, l1, l2: int);
+ setscrollbar: fn(t: ref Text, fl: ref Flayer);
+ settitle: fn(t: ref Text, s: string);
+ titlectl: fn(win: int, menu: string);
+ whichmenu: fn(tag: int): int;
+ whichtext: fn(tag: int): int;
+};
diff --git a/appl/wm/sendmail.b b/appl/wm/sendmail.b
new file mode 100644
index 00000000..da28eca2
--- /dev/null
+++ b/appl/wm/sendmail.b
@@ -0,0 +1,652 @@
+implement WmSendmail;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Context: import draw;
+
+include "tk.m";
+ tk: Tk;
+ Toplevel: import tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "dialog.m";
+ dialog: Dialog;
+
+include "selectfile.m";
+ selectfile: Selectfile;
+
+WmSendmail: module
+{
+ init: fn(ctxt: ref Draw->Context, args: list of string);
+};
+
+srv: Sys->Connection;
+main: ref Toplevel;
+ctxt: ref Context;
+username: string;
+
+mail_cfg := array[] of {
+ "frame .top",
+ "label .top.l -bitmap email.bit",
+ "frame .top.con",
+ "frame .top.con.b",
+ "button .top.con.b.con -bitmap mailcon -command {send msg connect}",
+ "bind .top.con.b.con <Enter> +{.top.status configure -text {connect/disconnect to mail server}}",
+ "button .top.con.b.send -bitmap maildeliver -command {send msg send}",
+ "bind .top.con.b.send <Enter> +{.top.status configure -text {deliver mail}}",
+
+ "button .top.con.b.nocc -bitmap mailnocc -command {.hdr.e.cc delete 0 end}",
+ "bind .top.con.b.nocc <Enter> +{.top.status configure -text {no carbon copy}}",
+
+ "button .top.con.b.new -bitmap mailnew -command {send msg new}",
+ "bind .top.con.b.new <Enter> +{.top.status configure -text {start a new message}}",
+ "button .top.con.b.save -bitmap mailsave -command {send msg save}",
+ "bind .top.con.b.save <Enter> +{.top.status configure -text {save message}}",
+ "pack .top.con.b.con .top.con.b.send .top.con.b.nocc .top.con.b.new .top.con.b.save -padx 2 -side left",
+ "label .top.status -text {not connected ...} -anchor w",
+ "pack .top.l -side left",
+ "pack .top.con -side left -padx 10",
+ "pack .top.con.b .top.status -in .top.con -fill x -expand 1",
+ "frame .hdr",
+ "frame .hdr.l",
+ "frame .hdr.e",
+ "label .hdr.l.mt -text {Mail To:}",
+ "label .hdr.l.cc -text {Mail CC:}",
+ "label .hdr.l.sb -text {Subject:}",
+ "pack .hdr.l.mt .hdr.l.cc .hdr.l.sb -fill y -expand 1",
+ "entry .hdr.e.mt -bg white",
+ "entry .hdr.e.cc -bg white",
+ "entry .hdr.e.sb -bg white",
+ "bind .hdr.e.mt <Key-\n> {}",
+ "bind .hdr.e.cc <Key-\n> {}",
+ "bind .hdr.e.sb <Key-\n> {}",
+ "pack .hdr.e.mt .hdr.e.cc .hdr.e.sb -fill x -expand 1",
+ "pack .hdr.l -side left -fill y",
+ "pack .hdr.e -side left -fill x -expand 1",
+ "frame .body",
+ "scrollbar .body.scroll -command {.body.t yview}",
+ "text .body.t -width 15c -height 7c -yscrollcommand {.body.scroll set} -bg white",
+ "pack .body.t -side left -expand 1 -fill both",
+ "pack .body.scroll -side left -fill y",
+ "pack .top -anchor w -padx 5",
+ "pack .hdr -fill x -anchor w -padx 5 -pady 5",
+ "pack .body -expand 1 -fill both -padx 5 -pady 5",
+ "pack .b -padx 5 -pady 5 -fill x",
+ "pack propagate . 0",
+ "update"
+};
+
+con_cfg := array[] of {
+ "frame .b",
+ "button .b.ok -text {Connect} -command {send cmd ok}",
+ "button .b.can -text {Cancel} -command {send cmd can}",
+ "pack .b.ok .b.can -side left -fill x -padx 10 -pady 10 -expand 1",
+ "frame .l",
+ "label .l.h -text {Mail Server:} -anchor w",
+ "label .l.u -text {User Name:} -anchor w",
+ "pack .l.h .l.u -fill both -expand 1",
+ "frame .e",
+ "entry .e.h -width 30w",
+ "entry .e.u -width 30w",
+ "pack .e.h .e.u -fill x",
+ "frame .f -borderwidth 2 -relief raised",
+ "pack .l .e -fill both -expand 1 -side left -in .f",
+ "bind .e.h <Key-\n> {send cmd ok}",
+ "bind .e.u <Key-\n> {send cmd ok}",
+};
+
+con_pack := array[] of {
+ "pack .f",
+ "pack .b -fill x -expand 1",
+ "focus .e.u",
+ "update",
+};
+
+new_cmd := array[] of {
+ ".hdr.e.mt delete 0 end",
+ ".hdr.e.cc delete 0 end",
+ ".hdr.e.sb delete 0 end",
+ ".body.t delete 1.0 end",
+ ".body.t see 1.0",
+ "update"
+};
+
+init(xctxt: ref Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if (xctxt == nil) {
+ sys->fprint(sys->fildes(2), "sendmail: no window context\n");
+ raise "fail:bad context";
+ }
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ dialog = load Dialog Dialog->PATH;
+ selectfile = load Selectfile Selectfile->PATH;
+
+ ctxt = xctxt;
+
+ tkclient->init();
+ dialog->init();
+ selectfile->init();
+
+ tkargs := "";
+ argv = tl argv;
+ if(argv != nil) {
+ tkargs = hd argv;
+ argv = tl argv;
+ }
+
+ titlectl: chan of string;
+ (main, titlectl) = tkclient->toplevel(ctxt, tkargs,
+ "MailStop: Sender", Tkclient->Appl);
+
+ msg := chan of string;
+ tk->namechan(main, msg, "msg");
+
+ for (c:=0; c<len mail_cfg; c++)
+ tk->cmd(main, mail_cfg[c]);
+ tkclient->onscreen(main, nil);
+ tkclient->startinput(main, "kbd"::"ptr"::nil);
+
+ if(argv != nil)
+ fromreadmail(hd argv);
+
+ for(;;) alt {
+ s := <-main.ctxt.kbd =>
+ tk->keyboard(main, s);
+ s := <-main.ctxt.ptr =>
+ tk->pointer(main, *s);
+ s := <-main.ctxt.ctl or
+ s = <-main.wreq or
+ s = <-titlectl =>
+ if(s == "exit") {
+ if(srv.dfd == nil)
+ return;
+ status("Closing connection...");
+ smtpcmd("QUIT");
+ return;
+ }
+ tkclient->wmctl(main, s);
+ cmd := <-msg =>
+ case cmd {
+ "connect" =>
+ if(srv.dfd == nil) {
+ connect(main, 1);
+ fixbutton();
+ break;
+ }
+ disconnect();
+ "save" =>
+ save();
+ "send" =>
+ sendmail();
+ "new" =>
+ for (c=0; c<len new_cmd; c++)
+ tk->cmd(main, new_cmd[c]);
+ }
+ }
+}
+
+fixbutton()
+{
+ s := "Connect";
+ if(srv.dfd != nil)
+ s = "Disconnect";
+
+ tk->cmd(main, ".top.con configure -text "+s+"; update");
+}
+
+sendmail()
+{
+ if(srv.dfd == nil) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Send",
+ "You must be connected to deliver mail",
+ 0, "Continue"::nil);
+ return;
+ }
+
+ mto := tk->cmd(main, ".hdr.e.mt get");
+ if(mto == "") {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Send",
+ "You must fill in the \"Mail To\" entry",
+ 0, "Continue (nothing sent)"::nil);
+ return;
+ }
+
+ if(tk->cmd(main, ".body.t index end") == "1.0") {
+ opt := "Cancel" :: "Send anyway" :: nil;
+ if(dialog->prompt(ctxt, main.image, "warning -fg yellow", "Send",
+ "The body of the mail is empty", 0, opt) == 0)
+ return;
+ }
+
+ (err, s) := smtpcmd("MAIL FROM:<"+username+">");
+ if(err != nil) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Send",
+ "Failed to specify FROM correctly:\n"+err,
+ 0, "Continue (nothing sent)"::nil);
+ return;
+ }
+ status(s);
+ (err, s) = smtpcmd("RCPT TO:<"+mto+">");
+ if(err != nil) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Send",
+ "Failed to specify TO correctly:\n"+err,
+ 0, "Continue (nothing sent)"::nil);
+ return;
+ }
+ status(s);
+ cc := tk->cmd(main, ".hdr.e.cc get");
+ if(cc != nil) {
+ (nil, l) := sys->tokenize(cc, "\t ,");
+ while(l != nil) {
+ copy := hd l;
+ (err, s) = smtpcmd("RCPT TO:<"+copy+">");
+ if(err != nil) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Send",
+ "Carbon copy to "+copy+"failed:\n"+err,
+ 0, "Continue (nothing sent)"::nil);
+ }
+ }
+ }
+ (err, s) = smtpcmd("DATA");
+ if(err != nil) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Send",
+ "Failed to enter DATA mode:\n"+err,
+ 0, "Continue (nothing sent)"::nil);
+ return;
+ }
+
+ sub := tk->cmd(main, ".hdr.e.sb get");
+ if(sub != nil)
+ sys->fprint(srv.dfd, "Subject: %s\n", sub);
+
+ b := array of byte tk->cmd(main, ".body.t get 1.0 end");
+ n := sys->write(srv.dfd, b, len b);
+ b = nil;
+ if(n < 0) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Send",
+ "Error writing server:\n"+sys->sprint("%r"),
+ 0, "Abort (partial send)"::nil);
+ return;
+ }
+ (err, s) = smtpcmd("\r\n.");
+ if(err != nil) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Send",
+ "Failed to terminate message:\n"+err,
+ 0, "Abort (partial send)"::nil);
+ return;
+ }
+ status(s);
+}
+
+save()
+{
+ mto := tk->cmd(main, ".hdr.e.to get");
+ if(mto == "") {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Save",
+ "No message to save",
+ 0, "Dismiss"::nil);
+ return;
+ }
+
+ pat := list of {
+ "*.letter (Saved mail)",
+ "* (All files)"
+ };
+
+ fname: string;
+ fd: ref Sys->FD;
+
+ for(;;) {
+ fname = selectfile->filename(ctxt, main.image, "Save in Mailbox", pat,
+ "/usr/"+rf("/dev/user")+"/mail");
+ if(fname == nil)
+ return;
+
+ fd = sys->create(fname, sys->OWRITE, 8r660);
+ if(fd != nil)
+ break;
+ r := dialog->prompt(ctxt, main.image, "error -fg red", "Save",
+ "Failed to create "+sys->sprint("%s\n%r", fname),
+ 0, "Retry"::"Cancel"::nil);
+ if(r > 0)
+ return;
+ }
+
+ r := sys->fprint(srv.dfd, "Mail To: %s\n", mto);
+ cc := tk->cmd(main, ".hdr.e.cc get");
+ if(cc != nil)
+ r += sys->fprint(srv.dfd, "Mail CC: %s\n", cc);
+ sb := tk->cmd(main, ".hdr.e.sb get");
+ if(sb != nil)
+ r += sys->fprint(srv.dfd, "Subject: %s\n\n", sb);
+
+ s := tk->cmd(main, ".body.t get 1.0 end");
+ b := array of byte s;
+ n := sys->write(fd, b, len b);
+ if(n < 0) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Save",
+ "Error writing file "+sys->sprint("%s\n%r", fname),
+ 0, "Continue"::nil);
+ return;
+ }
+ status("wrote "+string(n+r)+" bytes.");
+}
+
+status(msg: string)
+{
+ tk->cmd(main, ".top.status configure -text {"+msg+"}; update");
+}
+
+disconnect()
+{
+ (err, s) := smtpcmd("QUIT");
+ srv.dfd = nil;
+ fixbutton();
+ if(err != nil) {
+ dialog->prompt(ctxt, main.image, "error -fg red", "Disconnect",
+ "Server problem:\n"+err,
+ 0, "Dismiss"::nil);
+ return;
+ }
+ status(s);
+}
+
+connect(parent: ref Toplevel, interactive: int)
+{
+ (t, conctl) := tkclient->toplevel(ctxt, postposn(parent),
+ "Connection Parameters", 0);
+
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+
+ for (c:=0; c<len con_cfg; c++)
+ tk->cmd(t, con_cfg[c]);
+
+ username = rf("/dev/user");
+ s := rf("/usr/"+username+"/mail/smtpserver");
+ if(s != "")
+ tk->cmd(t, ".e.h insert 0 '"+s);
+
+ s = rf("/usr/"+username+"/mail/domain");
+ if(s != nil)
+ username += "@"+s;
+
+ u := tk->cmd(t, ".e.u get");
+ if(u == "")
+ tk->cmd(t, ".e.u insert 0 '"+username);
+
+ if(interactive == 0 && checkthendial(t) != 0)
+ return;
+
+ for (c=0; c<len con_pack; c++)
+ tk->cmd(t, con_pack[c]);
+ tkclient->onscreen(t, nil);
+ tkclient->startinput(t, "kbd"::"ptr"::nil);
+
+ for(;;) alt {
+ ss := <-t.ctxt.kbd =>
+ tk->keyboard(t, ss);
+ ss := <-t.ctxt.ptr =>
+ tk->pointer(t, *ss);
+ ss := <-t.ctxt.ctl or
+ ss = <-t.wreq or
+ ss = <-conctl =>
+ if (ss == "exit")
+ return;
+ tkclient->wmctl(t, ss);
+ s = <-cmd =>
+ if(s == "can")
+ return;
+ if(checkthendial(t) != 0)
+ return;
+ status("not connected");
+ }
+ srv.dfd = nil;
+}
+
+checkthendial(t: ref Toplevel): int
+{
+ server := tk->cmd(t, ".e.h get");
+ if(server == "") {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Connect",
+ "You must supply a server address",
+ 0, "Continue"::nil);
+ return 0;
+ }
+ user := tk->cmd(t, ".e.u get");
+ if(user == "") {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Connect",
+ "You must supply a user name",
+ 0, "Continue"::nil);
+ return 0;
+ }
+ if(dom(user) == "") {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Connect",
+ "The user name must contain an '@'",
+ 0, "Continue"::nil);
+ return 0;
+ }
+ return dialer(t, server, user);
+}
+
+dialer(t: ref Toplevel, server, user: string): int
+{
+ ok: int;
+
+ status("dialing server...");
+ (ok, srv) = sys->dial(netmkaddr(server, nil, "25"), nil);
+ if(ok < 0) {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Connect",
+ "The following error occurred while\n"+
+ "dialing the server: "+sys->sprint("%r"),
+ 0, "Continue"::nil);
+ return 0;
+ }
+ status("connected...");
+ (err, s) := smtpresp();
+ if(err != nil) {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Connect",
+ "An error occurred during sign on.\n"+err,
+ 0, "Continue"::nil);
+ return 0;
+ }
+ status(s);
+ (err, s) = smtpcmd("HELO "+dom(user));
+ if(err != nil) {
+ dialog->prompt(ctxt, t.image, "error -fg red", "Connect",
+ "An error occurred during login.\n"+err,
+ 0, "Continue"::nil);
+ return 0;
+ }
+ status("ready to send...");
+ return 1;
+}
+
+rf(file: string): string
+{
+ fd := sys->open(file, sys->OREAD);
+ if(fd == nil)
+ return "";
+
+ buf := array[128] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return "";
+
+ return string buf[0:n];
+}
+
+postposn(parent: ref Toplevel): string
+{
+ x := int tk->cmd(parent, ".top.con cget -actx");
+ y := int tk->cmd(parent, ".top.con cget -acty");
+ h := int tk->cmd(parent, ".top.con cget -height");
+
+ return "-x "+string(x-2)+" -y "+string(y+h+2);
+}
+
+dom(name: string): string
+{
+ for(i := 0; i < len name; i++)
+ if(name[i] == '@')
+ return name[i+1:];
+ return nil;
+}
+
+fromreadmail(hdr: string)
+{
+ (nil, l) := sys->tokenize(hdr, "\n");
+ while(l != nil) {
+ s := hd l;
+ l = tl l;
+ n := match(s, "subject: ");
+ if(n != nil) {
+ tk->cmd(main, ".hdr.e.sb insert end '"+n);
+ continue;
+ }
+ n = match(s, "cc: ");
+ if(n != nil) {
+ tk->cmd(main, ".hdr.e.cc insert end '"+n);
+ continue;
+ }
+ n = match(s, "from: ");
+ if(n != nil) {
+ n = extract(n);
+ tk->cmd(main, ".hdr.e.mt insert end '"+n);
+ }
+ }
+ connect(main, 0);
+}
+
+extract(name: string): string
+{
+ for(i := 0; i < len name; i++) {
+ if(name[i] == '<') {
+ for(j := i+1; j < len name; j++)
+ if(name[j] == '>')
+ break;
+ return name[i+1:j];
+ }
+ }
+ for(i = 0; i < len name; i++)
+ if(name[i] == ' ')
+ break;
+ return name[0:i];
+}
+
+lower(c: int): int
+{
+ if(c >= 'A' && c <= 'Z')
+ c = 'a' + (c - 'A');
+ return c;
+}
+
+match(text, pat: string): string
+{
+ for(i := 0; i < len pat; i++) {
+ c := text[i];
+ p := pat[i];
+ if(c != p && lower(c) != p)
+ return "";
+ }
+ return text[i:];
+}
+
+#
+# Talk SMTP
+#
+smtpcmd(cmd: string): (string, string)
+{
+ cmd += "\r\n";
+# sys->print("->%s", cmd);
+ b := array of byte cmd;
+ l := len b;
+ n := sys->write(srv.dfd, b, l);
+ if(n != l)
+ return ("send to server:"+sys->sprint("%r"), nil);
+
+ return smtpresp();
+}
+
+smtpresp(): (string, string)
+{
+ s := "";
+ i := 0;
+ lastc := 0;
+ for(;;) {
+ c := smtpgetc();
+ if(c == -1)
+ return ("read from server:"+sys->sprint("%r"), nil);
+ if(lastc == '\r' && c == '\n')
+ break;
+ s[i++] = c;
+ lastc = c;
+ }
+# sys->print("<-%s\n", s);
+ if(i < 3)
+ return ("short read from server", nil);
+ s = s[0:i-1];
+ case s[0] {
+ '1' or '2' or '3' =>
+ i = 3;
+ while(s[i] == ' ' && i < len s)
+ i++;
+ return (nil, s[i:]);
+ '4'or '5' =>
+ i = 3;
+ while(s[i] == ' ' && i < len s)
+ i++;
+ return (s[i:], nil);
+ * =>
+ return ("invalid server response", nil);
+ }
+}
+
+Iob: adt
+{
+ nbyte: int;
+ posn: int;
+ buf: array of byte;
+};
+smtpbuf: Iob;
+
+smtpgetc(): int
+{
+ if(smtpbuf.nbyte > 0) {
+ smtpbuf.nbyte--;
+ return int smtpbuf.buf[smtpbuf.posn++];
+ }
+ if(smtpbuf.buf == nil)
+ smtpbuf.buf = array[512] of byte;
+
+ smtpbuf.posn = 0;
+ n := sys->read(srv.dfd, smtpbuf.buf, len smtpbuf.buf);
+ if(n < 0)
+ return -1;
+
+ smtpbuf.nbyte = n-1;
+ return int smtpbuf.buf[smtpbuf.posn++];
+}
+
+netmkaddr(addr, net, svc: string): string
+{
+ if(net == nil)
+ net = "net";
+ (n, l) := sys->tokenize(addr, "!");
+ if(n <= 1){
+ if(svc== nil)
+ return sys->sprint("%s!%s", net, addr);
+ return sys->sprint("%s!%s!%s", net, addr, svc);
+ }
+ if(svc == nil || n > 2)
+ return addr;
+ return sys->sprint("%s!%s", addr, svc);
+}
diff --git a/appl/wm/sh.b b/appl/wm/sh.b
new file mode 100644
index 00000000..159ce6bc
--- /dev/null
+++ b/appl/wm/sh.b
@@ -0,0 +1,851 @@
+implement WmSh;
+
+include "sys.m";
+ sys: Sys;
+ FileIO: import sys;
+
+include "draw.m";
+ draw: Draw;
+ Context, Rect: import draw;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "plumbmsg.m";
+ plumbmsg: Plumbmsg;
+ Msg: import plumbmsg;
+
+include "workdir.m";
+
+include "string.m";
+ str: String;
+
+include "arg.m";
+
+WmSh: module
+{
+ init: fn(ctxt: ref Draw->Context, args: list of string);
+};
+
+Command: type WmSh;
+
+BSW: con 23; # ^w bacspace word
+BSL: con 21; # ^u backspace line
+EOT: con 4; # ^d end of file
+ESC: con 27; # hold mode
+
+# XXX line-based limits are inadequate - memory is still
+# blown if a client writes a very long line.
+HIWAT: con 2000; # maximum number of lines in transcript
+LOWAT: con 1500; # amount to reduce to after high water
+
+Name: con "Shell";
+
+Rdreq: adt
+{
+ off: int;
+ nbytes: int;
+ fid: int;
+ rc: chan of (array of byte, string);
+};
+
+shwin_cfg := array[] of {
+ "menu .m",
+ ".m add command -text noscroll -command {send edit noscroll}",
+ ".m add command -text cut -command {send edit cut}",
+ ".m add command -text paste -command {send edit paste}",
+ ".m add command -text snarf -command {send edit snarf}",
+ ".m add command -text send -command {send edit send}",
+ "frame .b -bd 1 -relief ridge",
+ "frame .ft -bd 0",
+ "scrollbar .ft.scroll -command {send scroll t}",
+ "text .ft.t -bd 1 -relief flat -yscrollcommand {send scroll s} -bg white -selectforeground black -selectbackground #CCCCCC",
+ ".ft.t tag configure sel -relief flat",
+ "pack .ft.scroll -side left -fill y",
+ "pack .ft.t -fill both -expand 1",
+ "pack .Wm_t -fill x",
+ "pack .b -anchor w -fill x",
+ "pack .ft -fill both -expand 1",
+ "focus .ft.t",
+ "bind .ft.t <Key> {send keys {%A}}",
+ "bind .ft.t <Control-d> {send keys {%A}}",
+ "bind .ft.t <Control-h> {send keys {%A}}",
+ "bind .ft.t <Control-w> {send keys {%A}}",
+ "bind .ft.t <Control-u> {send keys {%A}}",
+ "bind .ft.t <Button-1> +{send but1 pressed}",
+ "bind .ft.t <Double-Button-1> +{send but1 pressed}",
+ "bind .ft.t <ButtonRelease-1> +{send but1 released}",
+ "bind .ft.t <ButtonPress-2> {send but2 %X %Y}",
+ "bind .ft.t <Motion-Button-2-Button-1> {}",
+ "bind .ft.t <Motion-ButtonPress-2> {}",
+ "bind .ft.t <ButtonPress-3> {send but3 pressed}",
+ "bind .ft.t <ButtonRelease-3> {send but3 released %x %y}",
+ "bind .ft.t <Motion-Button-3> {}",
+ "bind .ft.t <Motion-Button-3-Button-1> {}",
+ "bind .ft.t <Double-Button-3> {}",
+ "bind .ft.t <Double-ButtonRelease-3> {}",
+};
+
+rdreq: list of Rdreq;
+menuindex := "0";
+holding := 0;
+plumbed := 0;
+rawon := 0;
+rawinput := "";
+scrolling := 1;
+partialread: array of byte;
+cwd := "";
+width, height, font: string;
+
+events: list of string;
+evrdreq: list of Rdreq;
+winname: string;
+
+badmod(p: string)
+{
+ sys->print("wm/sh: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init(ctxt: ref Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+
+ tkclient = load Tkclient Tkclient->PATH;
+ if (tkclient == nil)
+ badmod(Tkclient->PATH);
+
+ str = load String String->PATH;
+ if (str == nil)
+ badmod(String->PATH);
+
+ arg := load Arg Arg->PATH;
+ if (arg == nil)
+ badmod(Arg->PATH);
+ arg->init(argv);
+
+ plumbmsg = load Plumbmsg Plumbmsg->PATH;
+
+ sys->pctl(Sys->FORKNS | Sys->NEWPGRP | Sys->FORKENV, nil);
+
+ tkclient->init();
+ if (ctxt == nil)
+ ctxt = tkclient->makedrawcontext();
+ if(ctxt == nil){
+ sys->fprint(sys->fildes(2), "sh: no window context\n");
+ raise "fail:bad context";
+ }
+
+ if(plumbmsg != nil && plumbmsg->init(1, nil, 0) >= 0){
+ plumbed = 1;
+ workdir := load Workdir Workdir->PATH;
+ cwd = workdir->init();
+ }
+
+ shargs: list of string;
+ while ((opt := arg->opt()) != 0) {
+ case opt {
+ 'w' =>
+ width = arg->arg();
+ 'h' =>
+ height = arg->arg();
+ 'f' =>
+ font = arg->arg();
+ 'c' =>
+ a := arg->arg();
+ if (a == nil) {
+ sys->print("usage: wm/sh [-ilxvn] [-w width] [-h height] [-f font] [-c command] [file [args...]\n");
+ raise "fail:usage";
+ }
+ shargs = a :: "-c" :: shargs;
+ 'i' or 'l' or 'x' or 'v' or 'n' =>
+ shargs = sys->sprint("-%c", opt) :: shargs;
+ }
+ }
+ argv = arg->argv();
+ for (; shargs != nil; shargs = tl shargs)
+ argv = hd shargs :: argv;
+
+ winname = Name + " " + cwd;
+
+ spawn main(ctxt, argv);
+}
+
+task(t: ref Tk->Toplevel)
+{
+ tkclient->wmctl(t, "task");
+}
+
+atend(t: ref Tk->Toplevel, w: string): int
+{
+ s := cmd(t, w+" yview");
+ for(i := 0; i < len s; i++)
+ if(s[i] == ' ')
+ break;
+ return i == len s - 2 && s[i+1] == '1';
+}
+
+main(ctxt: ref Draw->Context, argv: list of string)
+{
+ (t, titlectl) := tkclient->toplevel(ctxt, "", winname, Tkclient->Appl);
+ wm := t.ctxt;
+
+ edit := chan of string;
+ tk->namechan(t, edit, "edit");
+
+ keys := chan of string;
+ tk->namechan(t, keys, "keys");
+
+ butcmd := chan of string;
+ tk->namechan(t, butcmd, "button");
+
+ event := chan of string;
+ tk->namechan(t, event, "action");
+
+ scroll := chan of string;
+ tk->namechan(t, scroll, "scroll");
+
+ but1 := chan of string;
+ tk->namechan(t, but1, "but1");
+ but2 := chan of string;
+ tk->namechan(t, but2, "but2");
+ but3 := chan of string;
+ tk->namechan(t, but3, "but3");
+ button1 := 0;
+ button3 := 0;
+
+ for (i := 0; i < len shwin_cfg; i++)
+ cmd(t, shwin_cfg[i]);
+ (menuw, nil) := itemsize(t, ".m");
+ if (font != nil) {
+ if (font[0] != '/' && (len font == 1 || font[0:2] != "./"))
+ font = "/fonts/" + font;
+ cmd(t, ".ft.t configure -font " + font);
+ }
+ cmd(t, ".ft.t configure -width 65w -height 20h");
+ cmd(t, "pack propagate . 0");
+ if(width != nil)
+ cmd(t, ". configure -width " + width);
+ if(height != nil)
+ cmd(t, ". configure -height " + height);
+ tkclient->onscreen(t, nil);
+ tkclient->startinput(t, "ptr" :: "kbd" :: nil);
+
+ ioc := chan of (int, ref FileIO, ref FileIO, string, ref FileIO);
+ spawn newsh(ctxt, ioc, argv);
+
+ (nil, file, filectl, consfile, shctl) := <-ioc;
+ if(file == nil || filectl == nil || shctl == nil) {
+ sys->print("newsh: shell cons creation failed\n");
+ return;
+ }
+ dummyfwrite := chan of (int, array of byte, int, Sys->Rwrite);
+ fwrite := file.write;
+
+ rdrpc: Rdreq;
+
+ # outpoint is place in text to insert characters printed by programs
+ cmd(t, ".ft.t mark set outpoint 1.0; .ft.t mark gravity outpoint left");
+
+ for(;;) alt {
+ c := <-wm.kbd =>
+ tk->keyboard(t, c);
+ m := <-wm.ptr =>
+ tk->pointer(t, *m);
+ c := <-wm.ctl or
+ c = <-t.wreq or
+ c = <-titlectl =>
+ tkclient->wmctl(t, c);
+ ecmd := <-edit =>
+ editor(t, ecmd);
+ sendinput(t);
+
+ c := <-keys =>
+ cut(t, 1);
+ char := c[1];
+ if(char == '\\')
+ char = c[2];
+ if(rawon){
+ if(int cmd(t, ".ft.t compare insert >= outpoint")){
+ rawinput[len rawinput] = char;
+ sendinput(t);
+ break;
+ }
+ }
+ case char {
+ * =>
+ cmd(t, ".ft.t insert insert "+c);
+ '\n' or
+ EOT =>
+ cmd(t, ".ft.t insert insert "+c);
+ sendinput(t);
+ '\b' =>
+ cmd(t, ".ft.t tkTextDelIns -c");
+ BSL =>
+ cmd(t, ".ft.t tkTextDelIns -l");
+ BSW =>
+ cmd(t, ".ft.t tkTextDelIns -w");
+ ESC =>
+ setholding(t, !holding);
+ }
+ cmd(t, ".ft.t see insert;update");
+
+ c := <-but1 =>
+ button1 = (c == "pressed");
+ button3 = 0; # abort any pending button 3 action
+
+ c := <-but2 =>
+ if(button1){
+ cut(t, 1);
+ cmd(t, "update");
+ break;
+ }
+ (nil, l) := sys->tokenize(c, " ");
+ x := int hd l - menuw/2;
+ y := int hd tl l - int cmd(t, ".m yposition "+menuindex) - 10;
+ cmd(t, ".m activate "+menuindex+"; .m post "+string x+" "+string y+
+ "; update");
+ button3 = 0; # abort any pending button 3 action
+
+ c := <-but3 =>
+ if(c == "pressed"){
+ button3 = 1;
+ if(button1){
+ paste(t);
+ sendinput(t);
+ cmd(t, "update");
+ }
+ break;
+ }
+ if(plumbed == 0 || button3 == 0 || button1 != 0)
+ break;
+ button3 = 0;
+ # plumb message triggered by release of button 3
+ (nil, l) := sys->tokenize(c, " ");
+ x := int hd tl l;
+ y := int hd tl tl l;
+ index := cmd(t, ".ft.t index @"+string x+","+string y);
+ selindex := cmd(t, ".ft.t tag ranges sel");
+ if(selindex != "")
+ insel := cmd(t, ".ft.t compare sel.first <= "+index)=="1" &&
+ cmd(t, ".ft.t compare sel.last >= "+index)=="1";
+ else
+ insel = 0;
+ attr := "";
+ if(insel)
+ text := tk->cmd(t, ".ft.t get sel.first sel.last");
+ else{
+ # have line with text in it
+ # now extract whitespace-bounded string around click
+ (nil, w) := sys->tokenize(index, ".");
+ charno := int hd tl w;
+ left := cmd(t, ".ft.t index {"+index+" linestart}");
+ right := cmd(t, ".ft.t index {"+index+" lineend}");
+ line := tk->cmd(t, ".ft.t get "+left+" "+right);
+ for(i=charno; i>0; --i)
+ if(line[i-1]==' ' || line[i-1]=='\t')
+ break;
+ for(j:=charno; j<len line; j++)
+ if(line[j]==' ' || line[j]=='\t')
+ break;
+ text = line[i:j];
+ attr = "click="+string (charno-i);
+ }
+ msg := ref Msg(
+ "WmSh",
+ "",
+ cwd,
+ "text",
+ attr,
+ array of byte text);
+ if(msg.send() < 0)
+ sys->fprint(sys->fildes(2), "sh: plumbing write error: %r\n");
+ c := <-butcmd =>
+ simulatetype(t, tkunquote(c));
+ sendinput(t);
+ cmd(t, "update");
+ c := <-event =>
+ events = str->append(tkunquote(c), events);
+ if (evrdreq != nil) {
+ rc := (hd evrdreq).rc;
+ rc <-= (array of byte hd events, nil);
+ evrdreq = tl evrdreq;
+ events = tl events;
+ }
+ rdrpc = <-shctl.read =>
+ if(rdrpc.rc == nil)
+ continue;
+ if (events != nil) {
+ rdrpc.rc <-= (array of byte hd events, nil);
+ events = tl events;
+ } else
+ evrdreq = rdrpc :: evrdreq;
+ (nil, data, nil, wc) := <-shctl.write =>
+ if (wc == nil)
+ break;
+ if ((err := shctlcmd(t, string data)) != nil)
+ wc <-= (0, err);
+ else
+ wc <-= (len data, nil);
+ rdrpc = <-filectl.read =>
+ if(rdrpc.rc == nil)
+ continue;
+ rdrpc.rc <-= (nil, "not allowed");
+ (nil, data, nil, wc) := <-filectl.write =>
+ if(wc == nil) {
+ # consctl closed - revert to cooked mode
+ # XXX should revert only on *last* close?
+ rawon = 0;
+ continue;
+ }
+ (nc, cmdlst) := sys->tokenize(string data, " \n");
+ if(nc == 1) {
+ case hd cmdlst {
+ "rawon" =>
+ rawon = 1;
+ rawinput = "";
+ # discard previous input
+ advance := string (len tk->cmd(t, ".ft.t get outpoint end") +1);
+ cmd(t, ".ft.t mark set outpoint outpoint+" + advance + "chars");
+ partialread = nil;
+ "rawoff" =>
+ rawon = 0;
+ partialread = nil;
+ "holdon" =>
+ setholding(t, 1);
+ cmd(t, "update");
+ "holdoff" =>
+ setholding(t, 0);
+ cmd(t, "update");
+ * =>
+ wc <-= (0, "unknown consctl request");
+ continue;
+ }
+ wc <-= (len data, nil);
+ continue;
+ }
+ wc <-= (0, "unknown consctl request");
+
+ rdrpc = <-file.read =>
+ if(rdrpc.rc == nil) {
+ (ok, nil) := sys->stat(consfile);
+ if (ok < 0)
+ return;
+ continue;
+ }
+ append(rdrpc);
+ sendinput(t);
+
+ c := <-scroll =>
+ if(c[0] == 't'){
+ cmd(t, ".ft.t yview "+c[1:]+";update");
+ if(scrolling)
+ fwrite = file.write;
+ else if(atend(t, ".ft.t"))
+ fwrite = file.write;
+ else
+ fwrite = dummyfwrite;
+ }else{
+ cmd(t, ".ft.scroll set "+c[1:]+";update");
+ if(atend(t, ".ft.t") && fwrite == dummyfwrite)
+ fwrite = file.write;
+ }
+ (nil, data, nil, wc) := <-fwrite =>
+ if(wc == nil) {
+ (ok, nil) := sys->stat(consfile);
+ if (ok < 0)
+ return;
+ continue;
+ }
+ needscroll := atend(t, ".ft.t");
+ cdata := cursorcontrol(t, string data);
+ ncdata := string len cdata + "chars;";
+ cmd(t, ".ft.t insert outpoint '"+ cdata);
+ wc <-= (len data, nil);
+ data = nil;
+ s := ".ft.t mark set outpoint outpoint+" + ncdata;
+ if(!atend(t, ".ft.t") && scrolling == 0)
+ fwrite = dummyfwrite;
+ else if(needscroll)
+ s += ".ft.t see outpoint;";
+ s += "update";
+ cmd(t, s);
+ nlines := int cmd(t, ".ft.t index end");
+ if(nlines > HIWAT){
+ s = ".ft.t delete 1.0 "+ string (nlines-LOWAT) +".0;update";
+ cmd(t, s);
+ }
+ }
+}
+
+setholding(t: ref Tk->Toplevel, hold: int)
+{
+ if(hold == holding)
+ return;
+ holding = hold;
+ color := "blue";
+ if(!holding){
+ color = "black";
+ tkclient->settitle(t, winname);
+ sendinput(t);
+ }else
+ tkclient->settitle(t, winname+" (holding)");
+ cmd(t, ".ft.t configure -foreground "+color);
+}
+
+tkunquote(s: string): string
+{
+ if (s == nil)
+ return nil;
+ t: string;
+ if (s[0] != '{' || s[len s - 1] != '}')
+ return s;
+ for (i := 1; i < len s - 1; i++) {
+ if (s[i] == '\\')
+ i++;
+ t[len t] = s[i];
+ }
+ return t;
+}
+
+buttonid := 0;
+shctlcmd(win: ref Tk->Toplevel, c: string): string
+{
+ toks := str->unquoted(c);
+ if (toks == nil)
+ return "null command";
+ n := len toks;
+ case hd toks {
+ "button" or
+ "action"=>
+ # (button|action) title sendtext
+ if (n != 3)
+ return "bad usage";
+ id := ".b.b" + string buttonid++;
+ cmd(win, "button " + id + " -text " + tk->quote(hd tl toks) +
+ " -command 'send " + hd toks + " " + tk->quote(hd tl tl toks));
+ cmd(win, "pack " + id + " -side left");
+ cmd(win, "pack propagate .b 0");
+ "clear" =>
+ cmd(win, "pack propagate .b 1");
+ for (i := 0; i < buttonid; i++)
+ cmd(win, "destroy .b.b" + string i);
+ buttonid = 0;
+ "cwd" =>
+ if (n != 2)
+ return "bad usage";
+ cwd = hd tl toks;
+ winname = Name + " " + cwd;
+ tkclient->settitle(win, winname);
+ * =>
+ return "bad command";
+ }
+ cmd(win, "update");
+ return nil;
+}
+
+
+RPCread: type (int, int, int, chan of (array of byte, string));
+
+append(r: RPCread)
+{
+ t := r :: nil;
+ while(rdreq != nil) {
+ t = hd rdreq :: t;
+ rdreq = tl rdreq;
+ }
+ rdreq = t;
+}
+
+insat(t: ref Tk->Toplevel, mark: string): int
+{
+ return cmd(t, ".ft.t compare insert == "+mark) == "1";
+}
+
+insininput(t: ref Tk->Toplevel): int
+{
+ if(cmd(t, ".ft.t compare insert >= outpoint") != "1")
+ return 0;
+ return cmd(t, ".ft.t compare {insert linestart} == {outpoint linestart}") == "1";
+}
+
+isalnum(s: string): int
+{
+ if(s == "")
+ return 0;
+ c := s[0];
+ if('a' <= c && c <= 'z')
+ return 1;
+ if('A' <= c && c <= 'Z')
+ return 1;
+ if('0' <= c && c <= '9')
+ return 1;
+ if(c == '_')
+ return 1;
+ if(c > 16rA0)
+ return 1;
+ return 0;
+}
+
+cursorcontrol(t: ref Tk->Toplevel, s: string): string
+{
+ l := len s;
+ for(i := 0; i < l; i++) {
+ case s[i] {
+ '\b' =>
+ pre := "";
+ rem := "";
+ if(i + 1 < l)
+ rem = s[i+1:];
+ if(i == 0) { # erase existing character in line
+ if(tk->cmd(t, ".ft.t get " +
+ "{outpoint linestart} outpoint") != "")
+ cmd(t, ".ft.t delete outpoint-1char");
+ } else {
+ if(s[i-1] != '\n') # don't erase newlines
+ i--;
+ if(i)
+ pre = s[:i];
+ }
+ s = pre + rem;
+ l = len s;
+ i = len pre - 1;
+ '\r' =>
+ s[i] = '\n';
+ if(i + 1 < l && s[i+1] == '\n') # \r\n
+ s = s[:i] + s[i+1:];
+ else if(i > 0 && s[i-1] == '\n') # \n\r
+ s = s[:i-1] + s[i:];
+ l = len s;
+ '\0' =>
+ s[i] = Sys->UTFerror;
+ }
+ }
+ return s;
+}
+
+editor(t: ref Tk->Toplevel, ecmd: string)
+{
+ s, snarf: string;
+
+ case ecmd {
+ "scroll" =>
+ menuindex = "0";
+ scrolling = 1;
+ cmd(t, ".m entryconfigure 0 -text noscroll -command {send edit noscroll}");
+ "noscroll" =>
+ menuindex = "0";
+ scrolling = 0;
+ cmd(t, ".m entryconfigure 0 -text scroll -command {send edit scroll}");
+ "cut" =>
+ menuindex = "1";
+ cut(t, 1);
+ "paste" =>
+ menuindex = "2";
+ paste(t);
+ "snarf" =>
+ menuindex = "3";
+ if(cmd(t, ".ft.t tag ranges sel") == "")
+ break;
+ snarf = tk->cmd(t, ".ft.t get sel.first sel.last");
+ tkclient->snarfput(snarf);
+ "send" =>
+ menuindex = "4";
+ if(cmd(t, ".ft.t tag ranges sel") != ""){
+ snarf = tk->cmd(t, ".ft.t get sel.first sel.last");
+ tkclient->snarfput(snarf);
+ }else{
+ snarf = tkclient->snarfget();
+ }
+ if(snarf != "")
+ s = snarf;
+ else
+ return;
+ if(s[len s-1] != '\n' && s[len s-1] != EOT)
+ s[len s] = '\n';
+ simulatetype(t, s);
+ }
+ cmd(t, "update");
+}
+
+simulatetype(t: ref Tk->Toplevel, s: string)
+{
+ if(rawon){
+ rawinput += s;
+ }else{
+ cmd(t, ".ft.t see end; .ft.t insert end '"+s);
+ cmd(t, ".ft.t mark set insert end");
+ tk->cmd(t, ".ft.t tag remove sel sel.first sel.last");
+ }
+}
+
+cut(t: ref Tk->Toplevel, snarfit: int)
+{
+ if(cmd(t, ".ft.t tag ranges sel") == "")
+ return;
+ if(snarfit)
+ tkclient->snarfput(tk->cmd(t, ".ft.t get sel.first sel.last"));
+ cmd(t, ".ft.t delete sel.first sel.last");
+}
+
+paste(t: ref Tk->Toplevel)
+{
+ snarf := tkclient->snarfget();
+ if(snarf == "")
+ return;
+ cut(t, 0);
+ if(rawon && int cmd(t, ".ft.t compare insert >= outpoint")){
+ rawinput += snarf;
+ }else{
+ cmd(t, ".ft.t insert insert '"+snarf);
+ cmd(t, ".ft.t tag add sel insert-"+string len snarf+"chars insert");
+ }
+}
+
+sendinput(t: ref Tk->Toplevel)
+{
+ input: string;
+ if(rawon)
+ input = rawinput;
+ else
+ input = tk->cmd(t, ".ft.t get outpoint end");
+ if(rdreq == nil || (input == nil && len partialread == 0))
+ return;
+ r := hd rdreq;
+ (chars, bytes, partial) := triminput(r.nbytes, input, partialread);
+ if(bytes == nil)
+ return; # no terminator yet
+ rdreq = tl rdreq;
+
+ alt {
+ r.rc <-= (bytes, nil) =>
+ # check that it really was sent
+ alt {
+ r.rc <-= (nil, nil) =>
+ ;
+ * =>
+ return;
+ }
+ * =>
+ return; # requester has disappeared; ignore his request and try another
+ }
+ if(rawon)
+ rawinput = rawinput[chars:];
+ else
+ cmd(t, ".ft.t mark set outpoint outpoint+" + string chars + "chars");
+ partialread = partial;
+}
+
+# read at most nr bytes from the input string, returning the number of characters
+# consumed, the bytes to be read, and any remaining bytes from a partially
+# read multibyte UTF character.
+triminput(nr: int, input: string, partial: array of byte): (int, array of byte, array of byte)
+{
+ if(nr <= len partial)
+ return (0, partial[0:nr], partial[nr:]);
+ if(holding)
+ return (0, nil, partial);
+
+ # keep the array bounds within sensible limits
+ if(nr > len input*Sys->UTFmax)
+ nr = len input*Sys->UTFmax;
+ buf := array[nr+Sys->UTFmax] of byte;
+ t := len partial;
+ buf[0:] = partial;
+
+ hold := !rawon;
+ i := 0;
+ while(i < len input){
+ c := input[i++];
+ # special case for ^D - don't read the actual ^D character
+ if(!rawon && c == EOT){
+ hold = 0;
+ break;
+ }
+
+ t += sys->char2byte(c, buf, t);
+ if(c == '\n' && !rawon){
+ hold = 0;
+ break;
+ }
+ if(t >= nr)
+ break;
+ }
+ if(hold){
+ for(j := i; j < len input; j++){
+ c := input[j];
+ if(c == '\n' || c == EOT)
+ break;
+ }
+ if(j == len input)
+ return (0, nil, partial);
+ # strip ^D when next read would read it, otherwise
+ # we'll give premature EOF.
+ if(i == j && input[i] == EOT)
+ i++;
+ }
+ partial = nil;
+ if(t > nr){
+ partial = buf[nr:t];
+ t = nr;
+ }
+ return (i, buf[0:t], partial);
+}
+
+newsh(ctxt: ref Context, ioc: chan of (int, ref FileIO, ref FileIO, string, ref FileIO),
+ args: list of string)
+{
+ pid := sys->pctl(sys->NEWFD, nil);
+
+ sh := load Command "/dis/sh.dis";
+ if(sh == nil) {
+ ioc <-= (0, nil, nil, nil, nil);
+ return;
+ }
+
+ tty := "cons."+string pid;
+
+ sys->bind("#s","/chan",sys->MBEFORE);
+ fio := sys->file2chan("/chan", tty);
+ fioctl := sys->file2chan("/chan", tty + "ctl");
+ shctl := sys->file2chan("/chan", "shctl");
+ ioc <-= (pid, fio, fioctl, "/chan/"+tty, shctl);
+ if(fio == nil || fioctl == nil || shctl == nil)
+ return;
+
+ sys->bind("/chan/"+tty, "/dev/cons", sys->MREPL);
+ sys->bind("/chan/"+tty+"ctl", "/dev/consctl", sys->MREPL);
+
+ fd0 := sys->open("/dev/cons", sys->OREAD|sys->ORCLOSE);
+ fd1 := sys->open("/dev/cons", sys->OWRITE);
+ fd2 := sys->open("/dev/cons", sys->OWRITE);
+
+ {
+ sh->init(ctxt, "sh" :: "-n" :: args);
+ }exception{
+ "fail:*" =>
+ exit;
+ }
+}
+
+cmd(top: ref Tk->Toplevel, c: string): string
+{
+ s:= tk->cmd(top, c);
+# sys->print("* %s\n", c);
+ if (s != nil && s[0] == '!')
+ sys->fprint(sys->fildes(2), "wmsh: tk error on '%s': %s\n", c, s);
+ return s;
+}
+
+itemsize(top: ref Tk->Toplevel, item: string): (int, int)
+{
+ w := int tk->cmd(top, item + " cget -actwidth");
+ h := int tk->cmd(top, item + " cget -actheight");
+ b := int tk->cmd(top, item + " cget -borderwidth");
+ return (w+b, h+b);
+}
diff --git a/appl/wm/smenu.b b/appl/wm/smenu.b
new file mode 100644
index 00000000..6b06754f
--- /dev/null
+++ b/appl/wm/smenu.b
@@ -0,0 +1,204 @@
+implement Smenu;
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+include "tk.m";
+ tk: Tk;
+include "smenu.m";
+
+Scrollmenu.new(t: ref Tk->Toplevel, name: string, labs: array of string, e: int, o: int): ref Scrollmenu
+{
+ if(sys == nil)
+ sys = load Sys Sys->PATH;
+ if(tk == nil)
+ tk = load Tk Tk->PATH;
+ m := ref Scrollmenu;
+ n := len labs;
+ if(n < e)
+ e = n;
+ if(o > n-e)
+ o = n-e;
+ l := 0;
+ for(i := 0; i < n; i++){
+ if(len labs[i] > l)
+ l = len labs[i];
+ i++;
+ }
+ nlabs := array[n] of string;
+ sp := string array[l] of { * => byte ' ' };
+ for(i = 0; i < n; i++)
+ nlabs[i] = labs[i] + sp[0: l - len labs[i]];
+ sch := cname(name);
+ cmd(t, "menu " + name);
+ for(i = 0; i < e; i++){
+ cmd(t, name + " add command -label {" + nlabs[o+i] + "} -command {send " + sch + " " + string i + "}");
+ }
+ # cmd(t, "bind " + name + " <ButtonPress-1> +{send " + sch + " b}");
+ # cmd(t, "bind " + name + " <ButtonRelease-1> +{send " + sch + " b}");
+ cmd(t, "bind " + name + " <Motion> +{send " + sch + " M %x %y}");
+ cmd(t, "bind " + name + " <Map> +{send " + sch + " m}");
+ cmd(t, "bind " + name + " <Unmap> +{send " + sch + " u}");
+ cmd(t, "update");
+ m.name = name;
+ m.labs = nlabs;
+ m.c = nil;
+ m.t = t;
+ m.m = e;
+ m.n = n;
+ m.o = o;
+ m.timer = 1;
+ return m;
+}
+
+Scrollmenu.post(m: self ref Scrollmenu, x: int, y: int, resc: chan of string, prefix: string)
+{
+ sync := chan of int;
+ spawn listen(m, sync, resc, prefix);
+ <- sync;
+ cmd(m.t, m.name + " post " + string x + " " + string y);
+ cmd(m.t, "update");
+}
+
+Scrollmenu.destroy(m: self ref Scrollmenu)
+{
+ if(m.c != nil){
+ m.c <-= "u"; # fake unmap message
+ m.c = nil;
+ }
+ m.name = nil;
+ m.labs = nil;
+ m.t = nil;
+}
+
+timer(t: int, sync: chan of int, c: chan of int)
+{
+ sync <-= 0;
+ for(;;){
+ alt{
+ c <-= 0 =>
+ sys->sleep(t);
+ <- sync =>
+ exit;
+ }
+ }
+}
+
+TINT: con 100;
+SEC: con 1000/TINT;
+
+listen(m: ref Scrollmenu, sync: chan of int, resc: chan of string, prefix: string)
+{
+ timerc := chan of int;
+ cmdc := chan of string;
+ m.c = cmdc;
+ tk->namechan(m.t, cmdc, cname(m.name));
+ sync <-= 0;
+ x := y := ly := w := h := -1;
+ for(;;){
+ alt{
+ <- timerc =>
+ if(x > 0 && x < w){
+ if(y < 0 && y > -h/m.m)
+ menudir(m, -1);
+ else if(y > 0+h && y < h+h/m.m)
+ menudir(m, 1);
+ }
+ s := <- cmdc =>
+ (nil, toks) := sys->tokenize(s, " ");
+ case hd toks{
+ "M" =>
+ x = int hd tl toks;
+ y = int hd tl tl toks;
+ if(!m.timer && x > 0 && x < w){
+ mv := 0;
+ if(y < ly && y < 0)
+ mv = y/(h/m.m)-1;
+ else if(y > ly && y > h)
+ mv = (y-h)/(h/m.m)+1;
+ if(mv != 0)
+ menudirs(m, mv);
+ ly = y;
+ }
+ "m" =>
+ w = int cmd(m.t, m.name + " cget -actwidth");
+ h = int cmd(m.t, m.name + " cget -actheight");
+ ly = -1;
+ if(m.timer){
+ spawn timer(TINT, sync, timerc);
+ <- sync;
+ }
+ "u" =>
+ if(m.timer)
+ sync <-= 0;
+ m.c = nil;
+ exit;
+ * =>
+ # do not block
+ res := prefix + string (int hd toks + m.o);
+ for(t := 0; t < SEC; ){
+ if(m.timer)
+ alt{
+ resc <-= res =>
+ t = SEC;
+ <- timerc =>
+ t++;
+ }
+ else
+ alt{
+ resc <-= res =>
+ t = SEC;
+ * =>
+ sys->sleep(TINT);
+ t++;
+ }
+ }
+ }
+ }
+ }
+}
+
+menudirs(sm: ref Scrollmenu, n: int)
+{
+ if(n < 0)
+ (a, d) := (-n, -1);
+ else
+ (a, d) = (n, 1);
+ for(i := 0; i < a; i++)
+ menudir(sm, d);
+}
+
+menudir(sm: ref Scrollmenu, d: int)
+{
+ o := sm.o;
+ n := sm.n;
+ m := sm.m;
+ if(d == -1){
+ if(o == 0)
+ return;
+ for(i := 0; i < m; i++)
+ cmd(sm.t, sm.name + " entryconfigure " + string i + " -label {" + sm.labs[o-1+i] + "}");
+ sm.o = o-1;
+ }
+ else{
+ if(o+m == n)
+ return;
+ for(i := 0; i < m; i++)
+ cmd(sm.t, sm.name + " entryconfigure " + string i + " -label {" + sm.labs[o+1+i] + "}");
+ sm.o = o+1;
+ }
+ cmd(sm.t, "update");
+}
+
+cname(s: string): string
+{
+ return "sm_" + s + "_sm";
+}
+
+cmd(top: ref Tk->Toplevel, s: string): string
+{
+ e := tk->cmd(top, s);
+ if (e != nil && e[0] == '!')
+ sys->fprint(sys->fildes(2), "Smenu: tk error on '%s': %s\n", s, e);
+ return e;
+}
diff --git a/appl/wm/smenu.m b/appl/wm/smenu.m
new file mode 100644
index 00000000..6002bde0
--- /dev/null
+++ b/appl/wm/smenu.m
@@ -0,0 +1,18 @@
+Smenu: module
+{
+ PATH: con "/dis/wm/smenu.dis";
+
+ Scrollmenu: adt{
+ # private data
+ m, n, o: int;
+ timer: int;
+ name: string;
+ labs: array of string;
+ c: chan of string;
+ t: ref Tk->Toplevel;
+
+ new: fn(t: ref Tk->Toplevel, name: string, labs: array of string, entries: int, origin: int): ref Scrollmenu;
+ post: fn(m: self ref Scrollmenu, x: int, y: int, resc: chan of string, prefix: string);
+ destroy: fn(m: self ref Scrollmenu);
+ };
+}; \ No newline at end of file
diff --git a/appl/wm/snake.b b/appl/wm/snake.b
new file mode 100644
index 00000000..a3c8c6a2
--- /dev/null
+++ b/appl/wm/snake.b
@@ -0,0 +1,373 @@
+implement Snake;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+ Display, Point, Screen, Image, Rect: import draw;
+include "tk.m";
+ tk: Tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+include "keyboard.m";
+include "rand.m";
+ rand: Rand;
+include "scoretable.m";
+ scoretable: Scoretable;
+
+Snake: module{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Tick: adt{
+ dt: int;
+};
+
+DX: con 30;
+DY: con 30;
+Size: int;
+
+EMPTY, SNAKE, FOOD, CRASH: con iota;
+HIGHSCOREFILE: con "/lib/scores/snake";
+
+board: array of array of int;
+win: ref Tk->Toplevel;
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "snake: no window context\n");
+ raise "fail:bad context";
+ }
+ draw = load Draw Draw->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ if(tkclient == nil){
+ sys->print("sys->fildes(2), couldn't load %s: %r\n", Tkclient->PATH);
+ raise "fail:bad module";
+ }
+ tkclient->init();
+ tk = load Tk Tk->PATH;
+ rand = load Rand Rand->PATH;
+ if(rand == nil){
+ sys->fprint(sys->fildes(2), "snake: cannot load %s: %r\n", Rand->PATH);
+ raise "fail:bad module";
+ }
+ scoretable = load Scoretable Scoretable->PATH;
+ if (scoretable != nil) {
+ (ok, err) := scoretable->init(-1, readfile("/dev/user"), "snake", HIGHSCOREFILE);
+ if (ok == -1) {
+ sys->fprint(sys->fildes(2), "snake: cannot init scoretable: %s\n", err);
+ scoretable = nil;
+ }
+ }
+
+ sys->pctl(Sys->NEWPGRP, nil);
+ ctlchan: chan of string;
+ (win, ctlchan) = tkclient->toplevel(ctxt, nil, "Snake", Tkclient->Hide);
+
+ tk->namechan(win, kch := chan of string, "kch");
+
+ cmd(win, "canvas .c -bd 2 -relief ridge");
+ cmd(win, "label .scoret -text Score:");
+ cmd(win, "label .score -text 0");
+ cmd(win, "frame .f");
+ if (scoretable != nil) {
+ cmd(win, "label .hight -text High:");
+ cmd(win, "label .high -text 0");
+ cmd(win, "pack .hight .high -in .f -side left");
+ }
+ cmd(win, "pack .score .scoret -in .f -side right");
+ cmd(win, "pack .f -side top -fill x");
+ cmd(win, "pack .c");
+ cmd(win, "bind .c <Key> {send kch %s}");
+ cmd(win, "bind . <ButtonRelease-1> {focus .c}");
+ cmd(win, "bind .Wm_t <ButtonRelease-1> +{focus .c}");
+ cmd(win, "focus .c");
+
+ Size = int cmd(win, ".c cget -actheight") / DY;
+ cmd(win, ".c configure -width " + string (Size * DX) + " -height " + string (Size * DY));
+
+ tkclient->onscreen(win, nil);
+ tkclient->startinput(win, "kbd"::"ptr"::nil);
+
+ spawn winctl(ctlchan);
+ if (len argv > 1)
+ game(kch, hd tl argv);
+
+ for(;;){
+ game(kch, nil);
+ cmd(win, ".c delete all");
+ }
+}
+
+winctl(ctlchan: chan of string)
+{
+ for(;;) alt {
+ s := <-win.ctxt.kbd =>
+ tk->keyboard(win, s);
+ s := <-win.ctxt.ptr =>
+ tk->pointer(win, *s);
+ s := <-win.ctxt.ctl or
+ s = <-win.wreq or
+ s = <-ctlchan =>
+ tkclient->wmctl(win, s);
+ }
+}
+
+board2s(board: array of array of int): string
+{
+ s := string DX + "." + string DY + ".";
+ for (y := 0; y < DY; y++)
+ for (x := 0; x < DX; x++)
+ s[len s] = board[x][y] + '0';
+ return s;
+}
+
+replayproc(replay: string, kch: chan of string, tick: chan of int, nil: ref Tick)
+{
+ i := 0;
+ while(i < len replay){
+ n := 0;
+ while(i < len replay && replay[i] >= '0' && replay[i] <= '9') {
+ n = n*10 + replay[i] - '0';
+ i++;
+ }
+ for (t := 0; t < n; t++) {
+ tick <-= 1;
+ sys->sleep(0);
+ }
+ if (i == len replay)
+ break;
+ kch <-= string replay[i];
+ i++;
+ }
+ tick <-= 1;
+ tick <-= 0;
+}
+
+game(realkch: chan of string, replay: string)
+{
+ scores := scoretable->scores();
+ if (scores != nil)
+ cmd(win, ".high configure -text " + string (hd scores).score);
+ cmd(win, ".score configure -text {0}");
+ board = array[DX] of { * => array[DY] of{* => EMPTY}};
+
+ seed := rand->rand(16r7fffffff);
+ if (replay != nil) {
+ seed = int replay;
+ for (i := 0; i < len replay; i++)
+ if (replay[i] == '.')
+ break;
+ if (i<len replay)
+ replay = replay[i+1:];
+ }
+ rand->init(seed);
+ p := Point(DX/2, DY/2);
+ dir := Point(1, 0);
+ lkey := 'r';
+ snake := array[5] of Point;
+ for(i := 0; i < len snake; i++){
+ snake[i] = p.add(dir.mul(i));
+ make(snake[i]);
+ }
+ placefood();
+ p = p.add(dir.mul(i));
+ ticki := ref Tick(100);
+ realtick := chan of int;
+
+ userkch: chan of string;
+ if(replay != nil) {
+ (userkch, realkch) = (realkch, chan of string);
+ spawn replayproc(replay, realkch, realtick, ticki);
+ } else {
+ userkch = chan of string;
+ spawn ticker(realtick, ticki);
+ }
+ cmd(win, "update");
+
+ score := 0;
+ leaveit := 0;
+ paused := 0;
+
+ log := "";
+ nticks := 0;
+ odir := dir;
+
+ dummykch := chan of string;
+ kch := realkch;
+
+ dummytick := chan of int;
+ tick := realtick;
+ for(;;){
+ alt{
+ c := <-kch =>
+ if(paused){
+ paused = 0;
+ tick = realtick;
+ }
+ kch = dummykch;
+ ndir := dir;
+ case int c{
+ Keyboard->Up =>
+ ndir = (0, -1);
+ Keyboard->Down =>
+ ndir = (0, 1);
+ Keyboard->Left =>
+ ndir = (-1, 0);
+ Keyboard->Right =>
+ ndir = (1, 0);
+ 'q' =>
+ tkclient->wmctl(win, "exit");
+ 'p' =>
+ paused = 1;
+ tick = dummytick;
+ kch = realkch;
+ }
+ if (!ndir.eq(dir) && !ndir.eq(dir.mul(-1))) { # don't allow 180° turn.
+ lkey = int c;
+ dir = ndir;
+ }
+ <-tick =>
+ if(!odir.eq(dir)) {
+ log += string nticks;
+ log[len log] = lkey;
+ nticks = 0;
+ odir = dir;
+ }
+ nticks++;
+ if(leaveit){
+ ns := array[len snake + 1] of Point;
+ ns[0:] = snake;
+ snake = ns;
+ leaveit = 0;
+ } else{
+ destroy(snake[0]);
+ snake[0:] = snake[1:];
+ }
+ np := snake[len snake - 2].add(dir);
+ np.x = (np.x + DX) % DX;
+ np.y = (np.y + DY) % DY;
+ snake[len snake - 1] = np;
+ wasfood := board[np.x][np.y] == FOOD;
+ if(!make(np)){
+ cmd(win, ".c create oval " + r2s(square(np).inset(-5)) + " -fill yellow");
+ cmd(win, "update");
+ if (scoretable != nil && replay == nil) {
+ board[np.x][np.y] = CRASH;
+ log += string nticks;
+ sys->print("%d.%s\n", seed, log);
+ scoretable->setscore(score, string seed + "." + log + " " + board2s(board));
+ }
+ ticki.dt = -1;
+ while(<-tick)
+ ;
+ sys->sleep(750);
+ absorb(realkch);
+ if(int <-realkch == 'q')
+ tkclient->wmctl(win, "exit");
+ return;
+ }
+ if(wasfood){
+ score++;
+ #if(score % 10 == 0){
+ # if(ticki.dt > 0)
+ # ticki.dt -= 5;
+ #}
+ cmd(win, ".score configure -text " + string score);
+ leaveit = 1;
+ placefood();
+ }
+ cmd(win, "update");
+ kch = realkch;
+ }
+ }
+}
+
+placefood()
+{
+ for(;;)
+ if(makefood((rand->rand(DX), rand->rand(DY))))
+ return;
+}
+
+make(p: Point): int
+{
+ # b := board[p.x][p.y];
+ if(board[p.x][p.y] == SNAKE)
+ return 0;
+ cmd(win, ".c create rectangle " + r2s(square(p)) +
+ " -fill blue -outline {} -tags b." + string p.x + "." + string p.y);
+ board[p.x][p.y] = SNAKE;
+ return 1;
+}
+
+makefood(p: Point): int
+{
+ b := board[p.x][p.y];
+ if(b == SNAKE)
+ return 0;
+ cmd(win, ".c create oval " + r2s(square(p).inset(-2)) +
+ " -fill red -tags b." + string p.x + "." + string p.y);
+ board[p.x][p.y] = FOOD;
+ return 1;
+}
+
+destroy(p: Point)
+{
+ board[p.x][p.y] = 0;
+ cmd(win, ".c delete b." + string p.x + "." + string p.y);
+}
+
+square(p: Point): Rect
+{
+ p = p.mul(Size);
+ return (p, p.add((Size, Size)));
+}
+
+ticker(tick: chan of int, ticki: ref Tick)
+{
+ while((dt := ticki.dt) >= 0){
+ sys->sleep(dt);
+ tick <-= 1;
+ }
+ tick <-= 0;
+}
+
+absorb(c: chan of string)
+{
+ for(;;){
+ alt{
+ <-c =>
+ ;
+ * =>
+ return;
+ }
+ }
+}
+
+r2s(r: Rect): string
+{
+ return sys->sprint("%d %d %d %d", r.min.x, r.min.y, r.max.x, r.max.y);
+}
+
+readfile(f: string): string
+{
+ fd := sys->open(f, Sys->OREAD);
+ if (fd == nil)
+ return nil;
+ buf := array[Sys->ATOMICIO] of byte;
+ n := sys->read(fd, buf, len buf);
+ if (n <= 0)
+ return nil;
+ return string buf[0:n];
+}
+
+cmd(win: ref Tk->Toplevel, s: string): string
+{
+ r := tk->cmd(win, s);
+ if(len r > 0 && r[0] == '!'){
+ sys->print("error executing '%s': %s\n", s, r[1:]);
+ }
+ return r;
+}
diff --git a/appl/wm/stopwatch.b b/appl/wm/stopwatch.b
new file mode 100644
index 00000000..7748bbe0
--- /dev/null
+++ b/appl/wm/stopwatch.b
@@ -0,0 +1,184 @@
+implement WmStopWatch;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "daytime.m";
+ daytime: Daytime;
+
+
+WmStopWatch: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+t: ref Tk->Toplevel;
+cmd: chan of string;
+
+tpid: int;
+
+hr,
+min,
+sec: int;
+
+sw_cfg := array[] of {
+ "frame .f",
+ "button .f.b1 -text Start -command {send cmd start}",
+ "button .f.b2 -text Stop -command {send cmd stop}",
+ "button .f.b3 -text Reset -command {send cmd reset}",
+ "pack .f.b1 .f.b2 .f.b3 -side left -fill x -expand 1",
+
+ "frame .ft",
+ "label .ft.d -label {0:00:00}",
+ "pack .ft.d -expand 1",
+
+ "frame .fs1",
+ "button .fs1.s -text Time1 -command {send cmd s1}",
+ "label .fs1.l -label {0:00:00}",
+ "pack .fs1.s .fs1.l -side left -expand 1",
+
+ "frame .fs2",
+ "button .fs2.s -text Time2 -command {send cmd s2}",
+ "label .fs2.l -label {0:00:00}",
+ "pack .fs2.s .fs2.l -side left -expand 1",
+
+ "frame .fs3",
+ "button .fs3.s -text Time3 -command {send cmd s3}",
+ "label .fs3.l -label {0:00:00}",
+ "pack .fs3.s .fs3.l -side left -expand 1",
+
+ "pack .Wm_t -fill x",
+ "pack .f .ft .fs1 .fs2 .fs3",
+ "pack propagate . 0",
+ "update",
+};
+
+init(ctxt: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "stopwatch: no window context\n");
+ raise "fail:bad context";
+ }
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient= load Tkclient Tkclient->PATH;
+ daytime = load Daytime Daytime->PATH;
+
+ if(draw==nil || tk==nil || tkclient==nil || daytime==nil){
+ sys->fprint(sys->fildes(2), "stopwatch: couldn't load modules\n");
+ return;
+ }
+
+ tkclient->init();
+
+ menubut := chan of string;
+ (t, menubut) = tkclient->toplevel(ctxt, "-borderwidth 2 -relief raised", "StopWatch", Tkclient->Appl);
+
+ hr = 0;
+ min = 0;
+ sec = 0;
+
+ cmd = chan of string;
+ tk->namechan(t, cmd, "cmd");
+ for (c:=0; c<len sw_cfg; c++)
+ tk->cmd(t, sw_cfg[c]);
+ tkclient->onscreen(t, nil);
+ tkclient->startinput(t, "kbd"::"ptr"::nil);
+ tpid = 0;
+
+ # keep the timerloop in a separate thread,
+ # so that wm events don't hold up the ticker
+ # i.e., titlebar click&hold would otherwise
+ # 'pause' the timer since the tick would not
+ # be processed.
+
+ pid := chan of int;
+ spawn timerloop(pid);
+ looppid := <- pid;
+
+ for(;;) alt {
+ s := <-t.ctxt.kbd =>
+ tk->keyboard(t, s);
+ s := <-t.ctxt.ptr =>
+ tk->pointer(t, *s);
+ s := <-t.ctxt.ctl or
+ s = <-t.wreq =>
+ tkclient->wmctl(t, s);
+ menu := <-menubut =>
+ if(menu == "exit") {
+ if(tpid)
+ kill(tpid);
+ kill(looppid);
+ return;
+ }
+ tkclient->wmctl(t, menu);
+ }
+}
+
+timerloop(pid: chan of int)
+{
+ pid <- = sys->pctl(0, nil);
+
+ tick := chan of int;
+ s: string;
+
+ for(;;) alt {
+ c := <-cmd =>
+ if(c == "stop"){
+ if(tpid != 0){
+ kill(tpid);
+ tpid = 0;
+ }
+ } else if(c == "reset"){
+ hr = min = sec = 0;
+ s = sys->sprint("%d:%2.2d:%2.2d", hr, min, sec);
+ tk->cmd(t, ".ft.d configure -label {"+s+"};update");
+ } else if(c == "start"){
+ if(tpid == 0){
+ spawn timer(tick);
+ tpid = <- tick;
+ }
+ } else if(c == "s1" || c == "s2" || c == "s3"){
+ s = sys->sprint("%d:%2.2d:%2.2d", hr, min, sec);
+ tk->cmd(t, ".f"+c+".l configure -label {"+s+"};update");
+ }
+ <-tick =>
+ sec++;
+ if(sec>=60){
+ sec = 0;
+ min++;
+ if(min>=60){
+ min = 0;
+ hr++;
+ }
+ }
+ s = sys->sprint("%d:%2.2d:%2.2d", hr, min, sec);
+ tk->cmd(t, ".ft.d configure -label {"+s+"};update");
+ }
+}
+
+timer(c: chan of int)
+{
+ pid := sys->pctl(0, nil);
+ for(;;) {
+ c <-= pid;
+ sys->sleep(1000);
+ }
+}
+
+kill(pid: int)
+{
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "kill");
+}
diff --git a/appl/wm/sweeper.b b/appl/wm/sweeper.b
new file mode 100644
index 00000000..f721ee9a
--- /dev/null
+++ b/appl/wm/sweeper.b
@@ -0,0 +1,330 @@
+implement Sweeper;
+
+#
+# michael@vitanuova.com
+#
+# Copyright © 2000 Vita Nuova Limited. All rights reserved.
+# Copyright © 2001 Vita Nuova Holdings Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+ Point, Rect, Image, Font, Context, Screen, Display: import draw;
+include "tk.m";
+ tk: Tk;
+ Toplevel: import tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+include "daytime.m";
+ daytime: Daytime;
+include "rand.m";
+ rand: Rand;
+
+stderr: ref Sys->FD;
+
+Sweeper: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+mainwin: ref Toplevel;
+score: int;
+mines: int;
+
+WIDTH: con 220;
+HEIGHT: con 220;
+
+EASY: con 20;
+SZB: con 10;
+SZI: con SZB+2; # internal board is 2 larger than visible board
+
+Cell: adt {
+ mine, state: int;
+};
+
+board: array of array of Cell;
+
+UNSELECTED, SELECTED, MARKED: con (1<<iota);
+
+
+init(ctxt: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ daytime = load Daytime Daytime->PATH;
+ rand = load Rand Rand->PATH;
+
+ stderr = sys->fildes(2);
+ rand->init(daytime->now());
+ daytime = nil;
+
+ tkclient->init();
+ if(ctxt == nil)
+ ctxt = tkclient->makedrawcontext();
+
+ (win, wmcmd) := tkclient->toplevel(ctxt, "", "Mine Sweeper", Tkclient->Hide);
+ mainwin = win;
+ sys->pctl(Sys->NEWPGRP, nil);
+ cmdch := chan of string;
+ tk->namechan(win, cmdch, "cmd");
+ display_board();
+ pid := -1;
+ finished := 0;
+ init_board();
+ tkclient->onscreen(win, nil);
+ tkclient->startinput(win, "kbd"::"ptr"::nil);
+ for (;;) {
+ alt {
+ s := <-win.ctxt.kbd =>
+ tk->keyboard(win, s);
+ s := <-win.ctxt.ptr =>
+ tk->pointer(win, *s);
+ c := <-win.ctxt.ctl or
+ c = <-win.wreq or
+ c = <- wmcmd => # wm commands
+ case c {
+ "exit" =>
+ if(pid != -1)
+ kill(pid);
+ exit;
+ * =>
+ tkclient->wmctl(win, c);
+ }
+ c := <- cmdch => # tk commands
+ (nil, toks) := sys->tokenize(c, " ");
+ case hd toks {
+ "b" =>
+ x := int hd tl toks;
+ y := int hd tl tl toks;
+ i := board_check(x, y);
+ case i {
+ -1 =>
+ display_mines();
+ display_lost();
+ finished = 1;
+ 0 to 8 =>
+ if (finished)
+ break;
+ score++;
+ board[x][y].state = SELECTED;
+ display_square(x, y, sys->sprint("%d", i), "olive");
+ if (i == 0) { # check all adjacent zeros
+ display_zeros(x, y);
+ }
+ display_score();
+ if (score+mines == SZB*SZB) {
+ display_mines();
+ display_win();
+ finished = 1;
+ }
+ * =>
+ ;
+ }
+ cmd(mainwin, "update");
+ "b3" =>
+ x := int hd tl toks;
+ y := int hd tl tl toks;
+ mark_square(x, y);
+ cmd(mainwin, "update");
+ "restart" =>
+ init_board();
+ display_score();
+ reset_display();
+ finished = 0;
+ * =>
+ sys->fprint(stderr, "%s\n", c);
+ }
+ }
+ }
+}
+
+display_board() {
+ i, j: int;
+ pack: string;
+
+ for(i = 0; i < len win_config; i++)
+ cmd(mainwin, win_config[i]);
+
+ for (i = 1; i <= SZB; i++) {
+ cmd(mainwin, sys->sprint("frame .f%d", i));
+ pack = "";
+ for (j = 1; j <= SZB; j++) {
+ pack += sys->sprint(" .f%d.b%dx%d", i, i, j);
+ cmd(mainwin, sys->sprint("button .f%d.b%dx%d -text { } -width 14 -command {send cmd b %d %d}", i, i, j, i, j));
+ cmd(mainwin, sys->sprint("bind .f%d.b%dx%d <ButtonRelease-3> {send cmd b3 %d %d}", i, i, j, i, j));
+ }
+ cmd(mainwin, sys->sprint("pack %s -side left", pack));
+ cmd(mainwin, sys->sprint("pack .f%d -side top -fill x", i));
+ }
+
+ for (i = 0; i < len win_config2; i++)
+ cmd (mainwin, win_config2[i]);
+}
+
+reset_display()
+{
+ for (i := 1; i <= SZB; i++) {
+ for (j := 1; j <= SZB; j++) {
+ s := sys->sprint(".f%d.b%dx%d configure -text { } -bg #dddddd -activebackground #eeeeee", i, i, j);
+ cmd(mainwin, s);
+ }
+ }
+ cmd(mainwin, "update");
+}
+
+
+init_board()
+{
+ i, j: int;
+
+ score = 0;
+ mines = 0;
+ board = array[SZI] of array of Cell;
+ for (i = 0; i < SZI; i++)
+ board[i] = array[SZI] of Cell;
+
+ # initialize board
+ for (i = 0; i < SZI; i++)
+ for (j =0; j < SZI; j++) {
+ board[i][j].mine = 0;
+ board[i][j].state = UNSELECTED;
+ }
+
+ # place mines
+ for (i = 0; i < EASY; i++) {
+ j = rand->rand(SZB*SZB);
+ if (board[(j/SZB)+1][(j%SZB)+1].mine == 0) { # rand could yield same result twice
+ board[(j/SZB)+1][(j%SZB)+1].mine = 1;
+ mines++;
+ }
+ }
+ cmd(mainwin, "update");
+}
+
+display_score()
+{
+ cmd(mainwin, ".f.l configure -text {Score: "+ sys->sprint("%d", score)+ "}");
+}
+
+display_win()
+{
+ cmd(mainwin, ".f.l configure -text {You have Won}");
+}
+
+display_lost()
+{
+ cmd(mainwin, ".f.l configure -text {You have Lost}");
+}
+
+display_mines()
+{
+ for (i := 1; i <= SZB; i++)
+ for (j := 1; j <= SZB; j++)
+ if (board[i][j].mine == 1)
+ display_square(i, j, "M", "red");
+}
+
+display_square(i, j: int, v: string, c: string) {
+ cmd(mainwin, sys->sprint(".f%d.b%dx%d configure -text {%s} -bg %s -activebackground %s", i, i, j, v, c, c));
+ cmd(mainwin, "update");
+}
+
+mark_square(i, j: int) {
+ case board[i][j].state {
+ UNSELECTED =>
+ board[i][j].state = MARKED;
+ display_square(i, j, "?", "orange");
+ MARKED =>
+ board[i][j].state = UNSELECTED;
+ display_square(i, j, " ", "#dddddd");
+ }
+}
+
+board_check(i, j: int) : int
+{
+ if (board[i][j].mine == 1)
+ return -1;
+ if (board[i][j].state&(SELECTED|MARKED))
+ return -2;
+ c := 0;
+ for (x := i-1; x <= i+1; x++)
+ for (y := j-1; y <= j+1; y++)
+ if (board[x][y].mine == 1)
+ c++;
+ return c;
+}
+
+display_zeros(i, j: int)
+{
+ for (x := i-1; x <= i+1; x++) {
+ for (y := j-1; y <= j+1; y++) {
+ if (x <1 || x>SZB || y<1 || y>SZB)
+ continue;
+ if (board_check(x, y) == 0) {
+ score++;
+ board[x][y].state = SELECTED;
+ display_square(x, y, "0", "olive");
+ display_zeros(x, y);
+ }
+ }
+ }
+}
+
+fatal(s: string)
+{
+ sys->fprint(stderr, "%s\n", s);
+ exit;
+}
+
+sleep(t: int)
+{
+ sys->sleep(t);
+}
+
+kill(pid: int): int
+{
+ fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd == nil)
+ return -1;
+ if(sys->write(fd, array of byte "kill", 4) != 4)
+ return -1;
+ return 0;
+}
+
+cmd(top: ref Toplevel, s: string): string
+{
+ e := tk->cmd(top, s);
+ if (e != nil && e[0] == '!')
+ sys->fprint(stderr, "sweeper: tk error on '%s': %s\n", s, e);
+ return e;
+}
+
+win_config := array[] of {
+ "frame .f -width 220 -height 220",
+
+ "menubutton .f.sz -text Options -menu .f.sz.sm",
+ "menu .f.sz.sm",
+ ".f.sz.sm add command -label restart -command { send cmd restart }",
+ "pack .f.sz -side left",
+
+ "label .f.l -text {Score: }",
+ "pack .f.l -side right",
+
+ "frame .ft",
+ "label .ft.l -text { }",
+ "pack .ft.l -side left",
+
+ "pack .f -side top -fill x",
+ "pack .ft -side top -fill x",
+
+};
+
+win_config2 := array[] of {
+
+ "pack propagate . 0",
+ "update",
+}; \ No newline at end of file
diff --git a/appl/wm/task.b b/appl/wm/task.b
new file mode 100644
index 00000000..762d1262
--- /dev/null
+++ b/appl/wm/task.b
@@ -0,0 +1,240 @@
+implement WmTask;
+
+include "sys.m";
+ sys: Sys;
+ Dir: import sys;
+
+include "draw.m";
+ draw: Draw;
+
+include "tk.m";
+ tk: Tk;
+ Toplevel: import tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "dialog.m";
+ dialog: Dialog;
+
+Prog: adt
+{
+ pid: int;
+ pgrp: int;
+ size: int;
+ state: string;
+ mod: string;
+};
+
+WmTask: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Wm: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+task_cfg := array[] of {
+ "frame .fl",
+ "scrollbar .fl.scroll -command {.fl.l yview}",
+ "listbox .fl.l -width 40w -yscrollcommand {.fl.scroll set}",
+ "frame .b",
+ "button .b.ref -text Refresh -command {send cmd r}",
+ "button .b.deb -text Debug -command {send cmd d}",
+ "button .b.files -text Files -command {send cmd f}",
+ "button .b.kill -text Kill -command {send cmd k}",
+ "button .b.killg -text {Kill Group} -command {send cmd kg}",
+ "pack .b.ref .b.deb .b.files .b.kill .b.killg -side left -padx 2 -pady 2",
+ "pack .b -fill x",
+ "pack .fl.scroll -side left -fill y",
+ "pack .fl.l -fill both -expand 1",
+ "pack .fl -fill both -expand 1",
+ "pack propagate . 0",
+};
+
+init(ctxt: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "task: no window context\n");
+ raise "fail:bad context";
+ }
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient= load Tkclient Tkclient->PATH;
+ dialog = load Dialog Dialog->PATH;
+
+ tkclient->init();
+ dialog->init();
+
+ sysnam := sysname();
+
+ (t, wmctl) := tkclient->toplevel(ctxt, "", sysnam, Tkclient->Appl);
+ if(t == nil)
+ return;
+
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+
+ for (c:=0; c<len task_cfg; c++)
+ tk->cmd(t, task_cfg[c]);
+
+ readprog(t);
+
+ tk->cmd(t, ".fl.l see end;update");
+ tkclient->onscreen(t, nil);
+ tkclient->startinput(t, "kbd"::"ptr"::nil);
+
+ for(;;) alt {
+ s := <-t.ctxt.kbd =>
+ tk->keyboard(t, s);
+ s := <-t.ctxt.ptr =>
+ tk->pointer(t, *s);
+ s := <-t.ctxt.ctl or
+ s = <-t.wreq =>
+ tkclient->wmctl(t, s);
+ menu := <-wmctl =>
+ case menu {
+ "exit" =>
+ return;
+ "task" =>
+ tkclient->wmctl(t, menu);
+ tk->cmd(t, ".fl.l delete 0 end");
+ readprog(t);
+ tk->cmd(t, ".fl.l see end;update");
+ * =>
+ tkclient->wmctl(t, menu);
+ }
+ bcmd := <-cmd =>
+ case bcmd {
+ "d" =>
+ sel := tk->cmd(t, ".fl.l curselection");
+ if(sel == "")
+ break;
+ pid := int tk->cmd(t, ".fl.l get "+sel);
+ stk := load Wm "/dis/wm/deb.dis";
+ if(stk == nil)
+ break;
+ spawn stk->init(ctxt, "wm/deb" :: "-p "+string pid :: nil);
+ stk = nil;
+ "k" or "kg" =>
+ sel := tk->cmd(t, ".fl.l curselection");
+ if(sel == "")
+ break;
+ pid := int tk->cmd(t, ".fl.l get "+sel);
+ what := "opening ctl file";
+ cfile := "/prog/"+string pid+"/ctl";
+ cfd := sys->open(cfile, sys->OWRITE);
+ if(cfd != nil) {
+ if(bcmd == "kg"){
+ if(sys->fprint(cfd, "killgrp") > 0){
+ cfd = nil;
+ refresh(t);
+ break;
+ }
+ }else if(sys->fprint(cfd, "kill") > 0){
+ tk->cmd(t, ".fl.l delete "+sel);
+ cfd = nil;
+ break;
+ }
+ cfd = nil;
+ what = "sending kill request";
+ }
+ if(bcmd == "k" && sys->sprint("%r") == "file does not exist") {
+ refresh(t);
+ break;
+ }
+ dialog->prompt(ctxt, t.image, "error -fg red", "Kill",
+ "Error "+what+"\n"+
+ "System: "+sys->sprint("%r"),
+ 0, "OK" :: nil);
+ "r" =>
+ refresh(t);
+ "f" =>
+ sel := tk->cmd(t, ".fl.l curselection");
+ if(sel == "")
+ break;
+ pid := int tk->cmd(t, ".fl.l get "+sel);
+ fi := load Wm "/dis/wm/edit.dis";
+ if(fi == nil)
+ break;
+ spawn fi->init(ctxt,
+ "edit" ::
+ "/prog/"+string pid+"/fd" :: nil);
+ fi = nil;
+ }
+ }
+}
+
+refresh(t: ref Tk->Toplevel)
+{
+ tk->cmd(t, ".fl.l delete 0 end");
+ readprog(t);
+ tk->cmd(t, ".fl.l see end;update");
+}
+
+mkprog(file: string): ref Prog
+{
+ fd := sys->open("/prog/"+file+"/status", sys->OREAD);
+ if(fd == nil)
+ return nil;
+
+ buf := array[256] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n <= 0)
+ return nil;
+
+ (v, l) := sys->tokenize(string buf[0:n], " ");
+ if(v < 6)
+ return nil;
+
+ prg := ref Prog;
+ prg.pid = int hd l;
+ l = tl l;
+ prg.pgrp = int hd l;
+ l = tl l;
+ l = tl l;
+ # eat blanks in user name
+ while(len l > 3)
+ l = tl l;
+ prg.state = hd l;
+ l = tl l;
+ prg.size = int hd l;
+ l = tl l;
+ prg.mod = hd l;
+
+ return prg;
+}
+
+readprog(t: ref Toplevel)
+{
+ fd := sys->open("/prog", sys->OREAD);
+ if(fd == nil)
+ return;
+ for(;;) {
+ (n, d) := sys->dirread(fd);
+ if(n <= 0)
+ break;
+ for(i := 0; i < n; i++) {
+ p := mkprog(d[i].name);
+ if(p != nil){
+ l := sys->sprint("%4d %4d %3dK %-7s %s", p.pid, p.pgrp, p.size, p.state, p.mod);
+ tk->cmd(t, ".fl.l insert end '"+l);
+ }
+ }
+ }
+}
+
+sysname(): string
+{
+ fd := sys->open("#c/sysname", sys->OREAD);
+ if(fd == nil)
+ return "Anon";
+ buf := array[128] of byte;
+ n := sys->read(fd, buf, len buf);
+ if(n < 0)
+ return "Anon";
+ return string buf[0:n];
+}
diff --git a/appl/wm/telnet.b b/appl/wm/telnet.b
new file mode 100644
index 00000000..077dd6aa
--- /dev/null
+++ b/appl/wm/telnet.b
@@ -0,0 +1,820 @@
+implement WmTelnet;
+
+include "sys.m";
+ sys: Sys;
+ Connection: import sys;
+
+include "draw.m";
+ draw: Draw;
+ Context: import draw;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "dialog.m";
+ dialog: Dialog;
+
+WmTelnet: module
+{
+ init: fn(ctxt: ref Draw->Context, args: list of string);
+};
+
+Iob: adt
+{
+ fd: ref Sys->FD;
+ t: ref Tk->Toplevel;
+ out: cyclic ref Iob;
+ buf: array of byte;
+ ptr: int;
+ nbyte: int;
+};
+
+BS: con 8; # ^h backspace character
+BSW: con 23; # ^w bacspace word
+BSL: con 21; # ^u backspace line
+EOT: con 4; # ^d end of file
+ESC: con 27; # hold mode
+
+HIWAT: con 2000; # maximum number of lines in transcript
+LOWAT: con 1500; # amount to reduce to after high water
+
+Name: con "Telnet";
+ctxt: ref Context;
+cmds: chan of string;
+net: Connection;
+stderr: ref Sys->FD;
+mcrlf: int;
+netinp: ref Iob;
+
+# control characters
+Se: con 240; # end subnegotiation
+NOP: con 241;
+Mark: con 242; # data mark
+Break: con 243;
+Interrupt: con 244;
+Abort: con 245; # TENEX ^O
+AreYouThere: con 246;
+Erasechar: con 247; # erase last character
+Eraseline: con 248; # erase line
+GoAhead: con 249; # half duplex clear to send
+Sb: con 250; # start subnegotiation
+Will: con 251;
+Wont: con 252;
+Do: con 253;
+Dont: con 254;
+Iac: con 255;
+
+# options
+Binary, Echo, SGA, Stat, Timing,
+Det, Term, EOR, Uid, Outmark,
+Ttyloc, M3270, Padx3, Window, Speed,
+Flow, Line, Xloc, Extend: con iota;
+
+Opt: adt
+{
+ name: string;
+ code: int;
+ noway: int;
+ remote: int; # remote value
+ local: int; # local value
+};
+
+opt := array[] of
+{
+ Binary => Opt("binary", 0, 0, 0, 0),
+ Echo => Opt("echo", 1, 0, 0, 0),
+ SGA => Opt("suppress Go Ahead", 3, 0, 0, 0),
+ Stat => Opt("status", 5, 1, 0, 0),
+ Timing => Opt("timing", 6, 1, 0, 0),
+ Det => Opt("det", 20, 1, 0, 0),
+ Term => Opt("terminal", 24, 0, 0, 0),
+ EOR => Opt("end of record", 25, 1, 0, 0),
+ Uid => Opt("uid", 26, 1, 0, 0),
+ Outmark => Opt("outmark", 27, 1, 0, 0),
+ Ttyloc => Opt("ttyloc", 28, 1, 0, 0),
+ M3270 => Opt("3270 mode", 29, 1, 0, 0),
+ Padx3 => Opt("pad x.3", 30, 1, 0, 0),
+ Window => Opt("window size", 31, 1, 0, 0),
+ Speed => Opt("speed", 32, 1, 0, 0),
+ Flow => Opt("flow control", 33, 1, 0, 0),
+ Line => Opt("line mode", 34, 0, 0, 0),
+ Xloc => Opt("X display loc", 35, 1, 0, 0),
+ Extend => Opt("Extended", 255, 1, 0, 0),
+};
+
+shwin_cfg := array[] of {
+ "menu .m",
+ ".m add command -text Cut -command {send edit cut}",
+ ".m add command -text Paste -command {send edit paste}",
+ ".m add command -text Snarf -command {send edit snarf}",
+ ".m add command -text Send -command {send edit send}",
+ "frame .ft",
+ "scrollbar .ft.scroll -command {.ft.t yview}",
+ "text .ft.t -width 70w -height 25h -yscrollcommand {.ft.scroll set}",
+ "frame .mb",
+ "menubutton .mb.c -text Connect -menu .mbc",
+ "menubutton .mb.t -text Terminal -menu .mbt",
+ "menu .mbc",
+ ".mbc add command -text {Remote System} -command {send cmd con}",
+ ".mbc add command -text {Disconnect} -state disabled -command {send cmd dis}",
+ ".mbc add command -text {Exit} -command {send cmd exit}",
+ ".mbc add separator",
+ "menu .mbt",
+ ".mbt add checkbutton -text {Line Mode} -command {send cmd line}",
+ ".mbt add checkbutton -text {Map CR to LF} -command {send cmd crlf}",
+ "pack .mb.c .mb.t -side left",
+ "pack .ft.scroll -side left -fill y",
+ "pack .ft.t -fill both -expand 1",
+ "pack .mb -fill x",
+ "pack .ft -fill both -expand 1",
+ "pack propagate . 0",
+ "focus .ft.t",
+ "bind .ft.t <Key> {send keys {%A}}",
+ "bind .ft.t <Control-d> {send keys {%A}}",
+ "bind .ft.t <Control-h> {send keys {%A}}",
+ "bind .ft.t <ButtonPress-3> {send but3 %X %Y}",
+ "bind .ft.t <ButtonRelease-3> {}",
+ "bind .ft.t <DoubleButton-3> {}",
+ "bind .ft.t <Double-ButtonRelease-3> {}",
+ "bind .ft.t <ButtonPress-2> {}",
+ "bind .ft.t <ButtonRelease-2> {}",
+ "update"
+};
+
+connect_cfg := array[] of {
+ "frame .fl",
+ "label .fl.h -text Host",
+ "label .fl.p -text Port",
+ "pack .fl.h .fl.p",
+ "frame .el",
+ "entry .el.h",
+ "entry .el.p",
+ ".el.p insert end 'telnet",
+ "pack .el.h .el.p",
+ "pack .Wm_t -fill x",
+ "pack .fl .el -side left",
+ "focus .el.h",
+ "bind .el.h <Key-\n> {send cmd ok}",
+ "bind .el.p <key-\n> {send cmd ok}",
+ "update"
+};
+
+connected_cfg := array[] of {
+ "focus .ft.t",
+ ".mbc entryconfigure 0 -state disabled",
+ ".mbc entryconfigure 1 -state normal"
+};
+
+menuindex := "0";
+holding := 0;
+
+init(C: ref Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if (C == nil) {
+ sys->fprint(sys->fildes(2), "telnet: no window context\n");
+ raise "fail:bad context";
+ }
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ dialog = load Dialog Dialog->PATH;
+
+ ctxt = C;
+ tkclient->init();
+ dialog->init();
+
+ sys->pctl(Sys->NEWPGRP, nil);
+ stderr = sys->fildes(2);
+
+ tkargs := "";
+ argv = tl argv;
+ if(argv != nil) {
+ tkargs = hd argv;
+ argv = tl argv;
+ }
+ (t, titlectl) := tkclient->toplevel(ctxt, tkargs, Name, Tkclient->Appl);
+
+ edit := chan of string;
+ tk->namechan(t, edit, "edit");
+ for (cc:=0; cc<len shwin_cfg; cc++)
+ tk->cmd(t, shwin_cfg[cc]);
+
+ keys := chan of string;
+ tk->namechan(t, keys, "keys");
+
+ but3 := chan of string;
+ tk->namechan(t, but3, "but3");
+
+ cmds = chan of string;
+ tk->namechan(t, cmds, "cmd");
+
+ # outpoint is place in text to insert characters printed by programs
+ tk->cmd(t, ".ft.t mark set outpoint end; .ft.t mark gravity outpoint left");
+ tkclient->onscreen(t, nil);
+ tkclient->startinput(t, "kbd"::"ptr"::nil);
+
+ for(;;) alt {
+ s := <-t.ctxt.kbd =>
+ tk->keyboard(t, s);
+ s := <-t.ctxt.ptr =>
+ tk->pointer(t, *s);
+ s := <-t.ctxt.ctl or
+ s = <-t.wreq or
+ s = <-titlectl =>
+ if(s == "exit") {
+ kill();
+ return;
+ }
+ tkclient->wmctl(t, s);
+ ecmd := <-edit =>
+ editor(t, ecmd);
+ sendinput(t);
+
+ c := <-keys =>
+ if(opt[Echo].local == 0) {
+ sys->fprint(net.dfd, "%c", c[1]);
+ break;
+ }
+ cut(t, 1);
+ char := c[1];
+ if(char == '\\')
+ char = c[2];
+ update := ";.ft.t see insert;update";
+ case char{
+ * =>
+ tk->cmd(t, ".ft.t insert insert "+c+update);
+ '\n' or EOT =>
+ tk->cmd(t, ".ft.t insert insert "+c+update);
+ sendinput(t);
+ BS =>
+ if(!insat(t, "outpoint"))
+ tk->cmd(t, ".ft.t delete insert-1chars"+update);
+ ESC =>
+ holding ^= 1;
+ color := "blue";
+ if(!holding){
+ color = "black";
+ tkclient->settitle(t, Name);
+ sendinput(t);
+ }else
+ tkclient->settitle(t, Name+" (holding)");
+ tk->cmd(t, ".ft.t configure -foreground "+color+update);
+ BSL =>
+ if(insininput(t))
+ tk->cmd(t, ".ft.t delete outpoint insert"+update);
+ else
+ tk->cmd(t, ".ft.t delete {insert linestart} insert"+update);
+ BSW =>
+ if(insat(t, "outpoint"))
+ break;
+ a0 := isalnum(tk->cmd(t, ".ft.t get insert-1chars"));
+ a1 := isalnum(tk->cmd(t, ".ft.t get insert"));
+ start: string;
+ if(a0 && a1) # middle of word
+ start = "{insert wordstart}";
+ else if(a0) # end of word
+ start = "{insert-1chars wordstart}";
+ else{ # beginning or not in word; must search
+ s: string;
+ for(n:=1; ;){
+ s = tk->cmd(t, ".ft.t get insert-"+ string n +"chars");
+ if(s=="" || s=="\n"){
+ start = "insert-"+ string n+"chars";
+ break;
+ }
+ n++;
+ if(isalnum(s)){
+ start = "{insert-"+ string n+"chars wordstart}";
+ break;
+ }
+ }
+
+ }
+ # don't ^w across outpoint
+ if(tk->cmd(t, ".ft.t compare insert >= outpoint") == "1"
+ && tk->cmd(t, ".ft.t compare "+start+" < outpoint") == "1")
+ start = "outpoint";
+ tk->cmd(t, ".ft.t delete " + start + " insert"+update);
+ }
+
+ c := <-but3 =>
+ (nil, l) := sys->tokenize(c, " ");
+ x := int hd l - 50;
+ y := int hd tl l - int tk->cmd(t, ".m yposition "+menuindex) - 10;
+ tk->cmd(t, ".m activate "+menuindex+"; .m post "+string x+" "+string y+
+ "; grab set .m; update");
+
+ c := <-cmds =>
+ case c {
+ "con" =>
+ tk->cmd(t, ".mb.c configure -state disabled");
+ connect(t);
+ tk->cmd(t, ".mb.c configure -state normal; update");
+ "dis" =>
+ tkclient->settitle(t, "Telnet");
+ tk->cmd(t, ".mbc entryconfigure 0 -state normal");
+ tk->cmd(t, ".mbc entryconfigure 1 -state disabled");
+ net.cfd = nil;
+ net.dfd = nil;
+ kill();
+ "exit" =>
+ kill();
+ return;
+ "crlf" =>
+ mcrlf = !mcrlf;
+ break;
+ "line" =>
+ if(opt[Line].local == 0)
+ send3(netinp, Iac, Will, opt[Line].code);
+ else
+ send3(netinp, Iac, Wont, opt[Line].code);
+ }
+ }
+}
+
+insat(t: ref Tk->Toplevel, mark: string): int
+{
+ return tk->cmd(t, ".ft.t compare insert == "+mark) == "1";
+}
+
+insininput(t: ref Tk->Toplevel): int
+{
+ if(tk->cmd(t, ".ft.t compare insert >= outpoint") != "1")
+ return 0;
+ return tk->cmd(t, ".ft.t compare {insert linestart} == {outpoint linestart}") == "1";
+}
+
+isalnum(s: string): int
+{
+ if(s == "")
+ return 0;
+ c := s[0];
+ if('a' <= c && c <= 'z')
+ return 1;
+ if('A' <= c && c <= 'Z')
+ return 1;
+ if('0' <= c && c <= '9')
+ return 1;
+ if(c == '_')
+ return 1;
+ if(c > 16rA0)
+ return 1;
+ return 0;
+}
+
+editor(t: ref Tk->Toplevel, ecmd: string)
+{
+ s, snarf: string;
+
+ case ecmd {
+ "cut" =>
+ menuindex = "0";
+ cut(t, 1);
+
+ "paste" =>
+ menuindex = "1";
+ snarf = tkclient->snarfget();
+ if(snarf == "")
+ break;
+ cut(t, 0);
+ tk->cmd(t, ".ft.t insert insert '"+snarf);
+ sendinput(t);
+
+ "snarf" =>
+ menuindex = "2";
+ if(tk->cmd(t, ".ft.t tag ranges sel") == "")
+ break;
+ snarf = tk->cmd(t, ".ft.t get sel.first sel.last");
+ tkclient->snarfput(snarf);
+
+ "send" =>
+ menuindex = "3";
+ if(tk->cmd(t, ".ft.t tag ranges sel") != ""){
+ snarf = tk->cmd(t, ".ft.t get sel.first sel.last");
+ tkclient->snarfput(snarf);
+ }else
+ snarf = tkclient->snarfget();
+ if(snarf != "")
+ s = snarf;
+ else
+ return;
+ if(s[len s-1] != '\n' && s[len s-1] != EOT)
+ s[len s] = '\n';
+ tk->cmd(t, ".ft.t see end; .ft.t insert end '"+s);
+ tk->cmd(t, ".ft.t mark set insert end");
+ tk->cmd(t, ".ft.t tag remove sel sel.first sel.last");
+ }
+ tk->cmd(t, "update");
+}
+
+cut(t: ref Tk->Toplevel, snarfit: int)
+{
+ if(tk->cmd(t, ".ft.t tag ranges sel") == "")
+ return;
+ if(snarfit)
+ tkclient->snarfput(tk->cmd(t, ".ft.t get sel.first sel.last"));
+ tk->cmd(t, ".ft.t delete sel.first sel.last");
+}
+
+sendinput(t: ref Tk->Toplevel)
+{
+ if(holding)
+ return;
+ input := tk->cmd(t, ".ft.t get outpoint end");
+ slen := len input;
+ if(slen == 0)
+ return;
+
+ for(i := 0; i < slen; i++)
+ if(input[i] == '\n' || input[i] == EOT)
+ break;
+
+ if(i >= slen)
+ return;
+
+ advance := string (i+1);
+ if(input[i] == EOT)
+ input = input[0:i];
+ else
+ input = input[0:i+1];
+
+ sys->fprint(net.dfd, "%s", input);
+ tk->cmd(t, ".ft.t mark set outpoint outpoint+" + advance + "chars");
+}
+
+kill()
+{
+ path := sys->sprint("#p/%d/ctl", sys->pctl(0, nil));
+ fd := sys->open(path, sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "killgrp");
+}
+
+connect(t: ref Tk->Toplevel)
+{
+ (b, titlectl) := tkclient->toplevel(ctxt, nil, "Connect", 0);
+ for (c:=0; c<len connect_cfg; c++)
+ tk->cmd(b, connect_cfg[c]);
+
+ cmd := chan of string;
+ tk->namechan(b, cmd, "cmd");
+ tkclient->onscreen(b, nil);
+ tkclient->startinput(b, "kbd"::"ptr"::nil);
+
+loop: for(;;) alt {
+ s := <-b.ctxt.kbd =>
+ tk->keyboard(b, s);
+ s := <-b.ctxt.ptr =>
+ tk->pointer(b, *s);
+ s := <-b.ctxt.ctl or
+ s = <-b.wreq or
+ s = <-titlectl =>
+ if(s == "exit")
+ return;
+ tkclient->wmctl(b, s);
+ <-cmd =>
+ break loop;
+ }
+
+ addr := sys->sprint("tcp!%s!%s",
+ tk->cmd(b, ".el.h get"),
+ tk->cmd(b, ".el.p get"));
+
+ tkclient->settitle(b, "Dialing");
+ tk->cmd(b, "update");
+
+ ok: int;
+ (ok, net) = sys->dial(addr, nil);
+ if(ok < 0) {
+ dialog->prompt(ctxt, b.image, "error -fg red",
+ "Connect", "Connection to host failed\n"+sys->sprint("%r"),
+ 0, "Stop connect" :: nil);
+ return;
+ }
+
+ tkclient->settitle(t, "Telnet - "+addr);
+ for (c=0; c<len connected_cfg; c++)
+ tk->cmd(b, connected_cfg[c]);
+
+ spawn fromnet(t);
+}
+
+flush(t: ref Tk->Toplevel, data: array of byte)
+{
+ cdata := string data;
+ ncdata := string len cdata + "chars;";
+ moveins := insat(t, "outpoint");
+ tk->cmd(t, ".ft.t insert outpoint '"+ cdata);
+ s := ".ft.t mark set outpoint outpoint+" + ncdata;
+ s += ".ft.t see outpoint;";
+ if(moveins)
+ s += ".ft.t mark set insert insert+" + ncdata;
+ s += "update";
+ tk->cmd(t, s);
+ nlines := int tk->cmd(t, ".ft.t index end");
+ if(nlines > HIWAT){
+ s = ".ft.t delete 1.0 "+ string (nlines-LOWAT) +".0;update";
+ tk->cmd(t, s);
+ }
+}
+
+iobnew(fd: ref Sys->FD, t: ref Tk->Toplevel, out: ref Iob, size: int): ref Iob
+{
+ iob := ref Iob;
+ iob.fd = fd;
+ iob.t = t;
+ iob.out = out;
+ iob.buf = array[size] of byte;
+ iob.nbyte = 0;
+ iob.ptr = 0;
+ return iob;
+}
+
+iobget(iob: ref Iob): int
+{
+ if(iob.nbyte == 0) {
+ if(iob.out != nil)
+ iobflush(iob.out);
+ iob.nbyte = sys->read(iob.fd, iob.buf, len iob.buf);
+ if(iob.nbyte <= 0)
+ return iob.nbyte;
+ iob.ptr = 0;
+ }
+ iob.nbyte--;
+ return int iob.buf[iob.ptr++];
+}
+
+iobput(iob: ref Iob, c: int)
+{
+ iob.buf[iob.ptr++] = byte c;
+ if(iob.ptr == len iob.buf)
+ iobflush(iob);
+}
+
+iobflush(iob: ref Iob)
+{
+ if(iob.fd == nil) {
+ flush(iob.t, iob.buf[0:iob.ptr]);
+ iob.ptr = 0;
+ }
+}
+
+fromnet(t: ref Tk->Toplevel)
+{
+ conout := iobnew(nil, t, nil, 2048);
+ netinp = iobnew(net.dfd, nil, conout, 2048);
+
+ crnls := 0;
+ freenl := 0;
+
+loop: for(;;) {
+ c := iobget(netinp);
+ case c {
+ -1 =>
+ cmds <-= "dis";
+ return;
+ '\n' => # skip nl after string of cr's */
+ if(!opt[Binary].local && !mcrlf) {
+ crnls++;
+ if(freenl == 0)
+ break;
+ freenl = 0;
+ continue loop;
+ }
+ '\r' =>
+ if(!opt[Binary].local && !mcrlf) {
+ if(crnls++ == 0){
+ freenl = 1;
+ c = '\n';
+ break;
+ }
+ continue loop;
+ }
+ Iac =>
+ c = iobget(netinp);
+ if(c == Iac)
+ break;
+ iobflush(conout);
+ if(control(netinp, c) < 0)
+ return;
+
+ continue loop;
+ }
+ iobput(conout, c);
+ }
+}
+
+control(bp: ref Iob, c: int): int
+{
+ case c {
+ AreYouThere =>
+ sys->fprint(net.dfd, "Inferno telnet V1.0\r\n");
+ Sb =>
+ return sub(bp);
+ Will =>
+ return will(bp);
+ Wont =>
+ return wont(bp);
+ Do =>
+ return doit(bp);
+ Dont =>
+ return dont(bp);
+ Se =>
+ sys->fprint(stderr, "telnet: SE without an SB\n");
+ -1 =>
+ return -1;
+ * =>
+ break;
+ }
+ return 0;
+}
+
+sub(bp: ref Iob): int
+{
+ subneg: string;
+ i := 0;
+ for(;;){
+ c := iobget(bp);
+ if(c == Iac) {
+ c = iobget(bp);
+ if(c == Se)
+ break;
+ subneg[i++] = Iac;
+ }
+ if(c < 0)
+ return -1;
+ subneg[i++] = c;
+ }
+ if(i == 0)
+ return 0;
+
+ sys->fprint(stderr, "sub %d %d n = %d\n", subneg[0], subneg[1], i);
+
+ for(i = 0; i < len opt; i++)
+ if(opt[i].code == subneg[0])
+ break;
+
+ if(i >= len opt)
+ return 0;
+
+ case i {
+ Term =>
+ sbsend(opt[Term].code, array of byte "dumb");
+ }
+
+ return 0;
+}
+
+sbsend(code: int, data: array of byte): int
+{
+ buf := array[4+len data+2] of byte;
+ o := 4+len data;
+
+ buf[0] = byte Iac;
+ buf[1] = byte Sb;
+ buf[2] = byte code;
+ buf[3] = byte 0;
+ buf[4:] = data;
+ buf[o] = byte Iac;
+ o++;
+ buf[o] = byte Se;
+
+ return sys->write(net.dfd, buf, len buf);
+}
+
+will(bp: ref Iob): int
+{
+ c := iobget(bp);
+ if(c < 0)
+ return -1;
+
+ sys->fprint(stderr, "will %d\n", c);
+
+ for(i := 0; i < len opt; i++)
+ if(opt[i].code == c)
+ break;
+
+ if(i >= len opt) {
+ send3(bp, Iac, Dont, c);
+ return 0;
+ }
+
+ rv := 0;
+ if(opt[i].noway)
+ send3(bp, Iac, Dont, c);
+ else
+ if(opt[i].remote == 0)
+ rv |= send3(bp, Iac, Do, c);
+
+ if(opt[i].remote == 0)
+ rv |= change(bp, i, Will);
+ opt[i].remote = 1;
+ return rv;
+}
+
+wont(bp: ref Iob): int
+{
+ c := iobget(bp);
+ if(c < 0)
+ return -1;
+
+ sys->fprint(stderr, "wont %d\n", c);
+
+ for(i := 0; i < len opt; i++)
+ if(opt[i].code == c)
+ break;
+
+ if(i >= len opt)
+ return 0;
+
+ rv := 0;
+ if(opt[i].remote) {
+ rv |= change(bp, i, Wont);
+ rv |= send3(bp, Iac, Dont, c);
+ }
+ opt[i].remote = 0;
+ return rv;
+}
+
+doit(bp: ref Iob): int
+{
+ c := iobget(bp);
+ if(c < 0)
+ return -1;
+
+ sys->fprint(stderr, "do %d\n", c);
+
+ for(i := 0; i < len opt; i++)
+ if(opt[i].code == c)
+ break;
+
+ if(i >= len opt || opt[i].noway) {
+ send3(bp, Iac, Wont, c);
+ return 0;
+ }
+ rv := 0;
+ if(opt[i].local == 0) {
+ rv |= change(bp, i, Do);
+ rv |= send3(bp, Iac, Will, c);
+ }
+ opt[i].local = 1;
+ return rv;
+}
+
+dont(bp: ref Iob): int
+{
+ c := iobget(bp);
+ if(c < 0)
+ return -1;
+
+ sys->fprint(stderr, "dont %d\n", c);
+
+ for(i := 0; i < len opt; i++)
+ if(opt[i].code == c)
+ break;
+
+ if(i >= len opt || opt[i].noway)
+ return 0;
+
+ rv := 0;
+ if(opt[i].local){
+ opt[i].local = 0;
+ rv |= change(bp, i, Dont);
+ rv |= send3(bp, Iac, Wont, c);
+ }
+ opt[i].local = 0;
+ return rv;
+}
+
+change(nil: ref Iob, nil: int, nil: int): int
+{
+ return 0;
+}
+
+send3(bp: ref Iob, c0: int, c1: int, c2: int): int
+{
+ buf := array[3] of byte;
+
+ buf[0] = byte c0;
+ buf[1] = byte c1;
+ buf[2] = byte c2;
+
+ t: string;
+ case c0 {
+ Will => t = "Will";
+ Wont => t = "Wont";
+ Do => t = "Do";
+ Dont => t = "Dont";
+ }
+ if(t != nil)
+ sys->fprint(stderr, "r %s %d\n", t, c1);
+
+ r := sys->write(bp.fd, buf, 3);
+ if(r != 3)
+ return -1;
+ return 0;
+}
diff --git a/appl/wm/tetris.b b/appl/wm/tetris.b
new file mode 100644
index 00000000..ddde1a17
--- /dev/null
+++ b/appl/wm/tetris.b
@@ -0,0 +1,806 @@
+# Copyright © 1999 Roger Peppe. All rights reserved.
+implement Tetris;
+
+include "sys.m";
+ sys: Sys;
+ stderr: ref Sys->FD;
+include "draw.m";
+ draw: Draw;
+ Point, Rect: import draw;
+include "tk.m";
+ tk: Tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+include "rand.m";
+ rand: Rand;
+include "scoretable.m";
+ scoretab: Scoretable;
+include "arg.m";
+include "keyboard.m";
+ Up, Down, Right, Left: import Keyboard;
+
+include "keyring.m";
+include "security.m"; # for random seed
+
+Tetris: module {
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+SCORETABLE: con "/lib/scores/tetris";
+LOCKPORT: con 18343;
+
+# number of pieces across and down board.
+BOARDWIDTH: con 10;
+BOARDHEIGHT: con 22;
+
+awaitingscore := 1;
+
+Row: adt {
+ tag: string;
+ delete: int;
+};
+
+Board: adt {
+ new: fn(top: ref Tk->Toplevel, w: string,
+ blocksize: int, maxsize: Point): ref Board;
+ makeblock: fn(bd: self ref Board, colour: string, p: Point): string;
+ moveblock: fn(bd: self ref Board, b: string, p: Point);
+ movecurr: fn(bd: self ref Board, delta: Point);
+ delrows: fn(bd: self ref Board, rows: list of int);
+ landedblock: fn(bd: self ref Board, b: string, p: Point);
+ setnextshape: fn(bd: self ref Board, colour: string, spec: array of Point);
+ setscore: fn(bd: self ref Board, score: int);
+ setlevel: fn(bd: self ref Board, level: int);
+ setnrows: fn(bd: self ref Board, level: int);
+ gameover: fn(bd: self ref Board);
+ update: fn(bd: self ref Board);
+
+ state: array of array of byte;
+ w: string;
+ dx: int;
+ win: ref Tk->Toplevel;
+ rows: array of Row;
+ maxid: int;
+};
+
+Piece: adt {
+ shape: int;
+ rot: int;
+};
+
+Shape: adt {
+ coords: array of array of Point;
+ colour: string;
+ score: array of int;
+};
+
+Game: adt {
+ new: fn(bd: ref Board): ref Game;
+ move: fn(g: self ref Game, dx: int);
+ rotate: fn(g: self ref Game, clockwise: int);
+ tick: fn(g: self ref Game): int;
+ drop: fn(g: self ref Game);
+
+ bd: ref Board;
+ level: int;
+ delay: int;
+ score: int;
+ nrows: int;
+ pieceids: array of string;
+ pos: Point;
+ next,
+ curr: Piece;
+};
+
+badmod(path: string)
+{
+ sys->fprint(stderr, "tetris: cannot load %s: %r\n", path);
+ raise "fail: bad module";
+}
+
+usage()
+{
+ sys->fprint(stderr, "usage: tetris [-b blocksize]\n");
+ raise "fail:usage";
+}
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stderr = sys->fildes(2);
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ if (tk == nil)
+ badmod(Tk->PATH);
+ tkclient = load Tkclient Tkclient->PATH;
+ if (tkclient == nil)
+ badmod(Tkclient->PATH);
+ tkclient->init();
+ rand = load Rand Rand->PATH;
+ if (rand == nil)
+ badmod(Rand->PATH);
+ arg := load Arg Arg->PATH;
+ if (arg == nil)
+ badmod(Arg->PATH);
+ if (ctxt == nil)
+ ctxt = tkclient->makedrawcontext();
+ blocksize := 17; # preferred block size
+ arg->init(argv);
+ while ((opt := arg->opt()) != 0) {
+ case opt {
+ 'b' =>
+ if ((b := arg->arg()) == nil || int b <= 0)
+ usage();
+ blocksize = int b;
+ * =>
+ usage();
+ }
+ }
+ if (arg->argv() != nil)
+ usage();
+
+ sys->pctl(Sys->NEWPGRP|Sys->FORKNS, nil);
+ scoretab = load Scoretable Scoretable->PATH;
+ scorech := chan of int;
+ spawn scoresrvwait(scorech);
+ (win, winctl) := tkclient->toplevel(ctxt, "", "Tetris",Tkclient->Hide);
+ seedrand();
+ fromuser := chan of string;
+ tk->namechan(win, fromuser, "user");
+ cmd(win, "bind . <Key> {send user k %s}");
+ cmd(win, "bind . <ButtonRelease-1> {focus .}");
+ cmd(win, "bind .Wm_t <ButtonRelease-1> +{focus .}");
+ cmd(win, "focus .");
+
+ maxsize := Point(10000, 10000);
+ if (ctxt.display.image != nil) {
+ img := ctxt.display.image;
+ wsz := wsize(win, ".");
+ maxsize.y = img.r.dy() - wsz.y;
+ maxsize.x = img.r.dx();
+ }
+
+ tkclient->onscreen(win, nil);
+ tkclient->startinput(win, "kbd"::"ptr"::nil);
+ for (;;) {
+ bd := Board.new(win, ".f", blocksize, maxsize);
+ if (bd == nil) {
+ sys->fprint(stderr, "tetris: couldn't make board\n");
+ return;
+ }
+ cmd(win, "bind .f.c <ButtonRelease-1> {send user m %x %y}");
+ cmd(win, "pack .f -side top");
+ cmd(win, "update");
+ g := Game.new(bd);
+ (finished, rank) := rungame(g, win, fromuser, winctl, scorech);
+ if (finished)
+ break;
+ cmd(win, "pack propagate . 0");
+ if (scoretab != nil) {
+ cmd(win, "destroy .f");
+ if (showhighscores(win, fromuser, winctl, rank) == 0)
+ break;
+ } else
+ cmd(win, "destroy .f");
+ }
+}
+
+wsize(win: ref Tk->Toplevel, w: string): Point
+{
+ bd := int cmd(win, w + " cget -bd");
+ return (int cmd(win, w + " cget -width") + bd * 2,
+ int cmd(win, w + " cget -height") + bd * 2);
+}
+
+rungame(g: ref Game, win: ref Tk->Toplevel, fromuser: chan of string, winctl: chan of string, scorech: chan of int): (int, int)
+{
+ tickchan := chan of int;
+ spawn ticker(g, tickchan);
+ paused := 0;
+ tch := chan of int;
+
+ gameover := 0;
+ rank := -1;
+ bdsize := wsize(win, ".f.c");
+ boundy := bdsize.y * 2 / 3;
+ id := cmd(win, ".f.c create line " + p2s((0, boundy)) + " " + p2s((bdsize.x, boundy)) +
+ " -fill white");
+ cmd(win, ".f.c lower " + id);
+ for (;;) alt {
+ s := <-win.ctxt.kbd =>
+ tk->keyboard(win, s);
+ s := <-win.ctxt.ptr =>
+ tk->pointer(win, *s);
+ s := <-fromuser =>
+ key: int;
+ if (s[0] == 'm') {
+ (nil, toks) := sys->tokenize(s, " ");
+ p := Point(int hd tl toks, int hd tl tl toks);
+ if (p.y > boundy)
+ key = ' ';
+ else {
+ x := p.x / (bdsize.x / 3);
+ case x {
+ 0 =>
+ key = '7';
+ 1 =>
+ key = '8';
+ 2 =>
+ key = '9';
+ * =>
+ break;
+ }
+ }
+ } else if (s[0] == 'k')
+ key = int s[1:];
+ else
+ sys->print("oops (%s)\n", s);
+ if (gameover)
+ return (key == 'q', rank);
+ if (paused) {
+ paused = 0;
+ (tickchan, tch) = (tch, tickchan);
+ if (key != 'q')
+ continue;
+ }
+ case key {
+ '9' or 'c' or Right =>
+ g.move(1);
+ '7' or 'z' or Left =>
+ g.move(-1);
+ '8' or 'x' or Up =>
+ g.rotate(0);
+ ' ' or Down =>
+ g.drop();
+ 'p' =>
+ paused = 1;
+ (tickchan, tch) = (tch, tickchan);
+ 'q' =>
+ g.delay = -1;
+ while (<-tickchan)
+ ;
+ return (1, rank);
+ }
+ s := <-win.ctxt.ctl or
+ s = <-win.wreq or
+ s = <-winctl =>
+ tkclient->wmctl(win, s);
+ n := <-tickchan =>
+ if (g.tick() == -1) {
+ while (n)
+ n = <-tickchan;
+ if (awaitingscore && !<-scorech) {
+ awaitingscore = 0;
+ scoretab = nil;
+ }
+ if (scoretab != nil)
+ rank = scoretab->setscore(g.score, sys->sprint("%d %d %bd", g.nrows, g.level,
+ big readfile("/dev/time") / big 1000000));
+ gameover = 1;
+ }
+ ok := <-scorech =>
+ awaitingscore = 0;
+ if (!ok)
+ scoretab = nil;
+ }
+}
+
+tablerow(win: ref Tk->Toplevel, w, bg: string, relief: string, vals: array of string, widths: array of string)
+{
+ cmd(win, "frame " + w + " -bd 2 -relief " + relief);
+ for (i := 0; i < len vals; i++) {
+ cw := cmd(win, "label " + w + "." + string i + " -text " + tk->quote(vals[i]) + " -width " + widths[i] + bg);
+ cmd(win, "pack " + cw + " -side left -anchor w");
+ }
+ cmd(win, "pack " + w + " -side top");
+}
+
+showhighscores(win: ref Tk->Toplevel, fromuser: chan of string, winctl: chan of string, rank: int): int
+{
+ widths := array[] of {"10w", "7w", "7w", "5w"}; # user, score, level, rows
+ cmd(win, "frame .f -bd 4 -relief raised");
+ cmd(win, "label .f.title -text {High Scores}");
+ cmd(win, "pack .f.title -side top -anchor n");
+ tablerow(win, ".f.h", nil, "raised", array[] of {"User", "Score", "Level", "Rows"}, widths);
+ sl := scoretab->scores();
+ n := 0;
+ while (sl != nil) {
+ s := hd sl;
+ bg := "";
+ if (n == rank)
+ bg = " -bg white";
+ f := ".f.f" + string n++;
+ nrows := level := "";
+ (nil, toks) := sys->tokenize(s.other, " ");
+ if (toks != nil)
+ (nrows, toks) = (hd toks, tl toks);
+ if (toks != nil)
+ level = hd toks;
+ tablerow(win, f, bg, "sunken", array[] of {s.user, string s.score, level, nrows}, widths);
+ sl = tl sl;
+ }
+ cmd(win, "button .f.b -text {New game} -command {send user s}");
+ cmd(win, "pack .f.b -side top");
+ cmd(win, "pack .f -side top");
+ cmd(win, "update");
+ for (;;) alt {
+ s := <-win.ctxt.kbd =>
+ tk->keyboard(win, s);
+ s := <-win.ctxt.ptr =>
+ tk->pointer(win, *s);
+ s := <-fromuser =>
+ if (s[0] == 'k') {
+ cmd(win, "destroy .f");
+ return int s[1:] != 'q';
+ } else if (s[0] == 's') {
+ cmd(win, "destroy .f");
+ return 1;
+ }
+ s := <-win.ctxt.ctl or
+ s = <-win.wreq or
+ s = <-winctl =>
+ tkclient->wmctl(win, s);
+ }
+}
+
+scoresrvwait(ch: chan of int)
+{
+ if (scoretab == nil) {
+ ch <-= 0;
+ return;
+ }
+ (ok, err) := scoretab->init(LOCKPORT, readfile("/dev/user"), "tetris", SCORETABLE);
+ if (ok != -1)
+ ch <-= 1;
+ else {
+ if (err != "timeout")
+ sys->fprint(stderr, "tetris: scoretable error: %s\n", err);
+ else
+ sys->fprint(stderr, "tetris: timed out trying to connect to score server\n");
+ ch <-= 0;
+ }
+}
+
+readfile(f: string): string
+{
+ fd := sys->open(f, Sys->OREAD);
+ if (fd == nil)
+ return nil;
+ buf := array[Sys->ATOMICIO] of byte;
+ n := sys->read(fd, buf, len buf);
+ if (n <= 0)
+ return nil;
+ return string buf[0:n];
+}
+
+ticker(g: ref Game, c: chan of int)
+{
+ c <-= 1;
+ while (g.delay >= 0) {
+ sys->sleep(g.delay);
+ c <-= 1;
+ }
+ c <-= 0;
+}
+
+seedrand()
+{
+ random := load Random Random->PATH;
+ if (random == nil) {
+ sys->fprint(stderr, "tetris: cannot load %s: %r\n", Random->PATH);
+ return;
+ }
+ seed := random->randomint(Random->ReallyRandom);
+ rand->init(seed);
+}
+
+Game.new(bd: ref Board): ref Game
+{
+ g := ref Game;
+ g.bd = bd;
+ g.level = 0;
+ g.pieceids = array[4] of string;
+ g.score = 0;
+ g.delay = delays[g.level];
+ g.nrows = 0;
+ g.next = randompiece();
+ newpiece(g);
+ bd.update();
+ return g;
+}
+
+randompiece(): Piece
+{
+ p: Piece;
+ p.shape = rand->rand(len shapes);
+ p.rot = rand->rand(len shapes[p.shape].coords);
+ return p;
+}
+
+Game.move(g: self ref Game, dx: int)
+{
+ np := g.pos.add((dx, 0));
+ if (canmove(g, g.curr, np)) {
+ g.bd.movecurr((dx, 0));
+ g.bd.update();
+ g.pos = np;
+ }
+}
+
+Game.rotate(g: self ref Game, clockwise: int)
+{
+ inc := 1;
+ if (!clockwise)
+ inc = -1;
+ npiece := g.curr;
+ coords := shapes[npiece.shape].coords;
+ nrots := len coords;
+ npiece.rot = (npiece.rot + inc + nrots) % nrots;
+ if (canmove(g, npiece, g.pos)) {
+ c := coords[npiece.rot];
+ for (i := 0; i < len c; i++) {
+ np := g.pos.add(c[i]);
+ g.bd.moveblock(g.pieceids[i], g.pos.add(c[i]));
+ }
+ g.curr = npiece;
+ g.bd.update();
+ }
+}
+
+Game.tick(g: self ref Game): int
+{
+ if (canmove(g, g.curr, g.pos.add((0, 1)))) {
+ g.bd.movecurr((0, 1));
+ g.pos.y++;
+ } else {
+ c := shapes[g.curr.shape].coords[g.curr.rot];
+ max := g.pos.y;
+ min := g.pos.y + 4;
+ for (i := 0; i < len c; i++) {
+ p := g.pos.add(c[i]);
+ if (p.y < 0) {
+ g.delay = -1;
+ g.bd.gameover();
+ g.bd.update();
+ return -1;
+ }
+ if (p.y > max)
+ max = p.y;
+ if (p.y < min)
+ min = p.y;
+ g.bd.landedblock(g.pieceids[i], p);
+ }
+ full: list of int;
+ for (i = min; i <= max; i++) {
+ for (x := 0; x < BOARDWIDTH; x++)
+ if (g.bd.state[i][x] == byte 0)
+ break;
+ if (x == BOARDWIDTH)
+ full = i :: full;
+ }
+ if (full != nil) {
+ g.bd.delrows(full);
+ g.nrows += len full;
+ g.bd.setnrows(g.nrows);
+ level := g.nrows / 10;
+ if (level != g.level) {
+ g.bd.setlevel(level);
+ g.level = level;
+ if (level >= len delays)
+ level = len delays - 1;
+ g.delay = delays[level];
+ }
+ }
+ g.score += shapes[g.curr.shape].score[g.curr.rot];
+ g.bd.setscore(g.score);
+ newpiece(g);
+ }
+ g.bd.update();
+ return 0;
+}
+
+Game.drop(g: self ref Game)
+{
+ p := g.pos.add((0, 1));
+ while (canmove(g, g.curr, p))
+ p.y++;
+ p.y--;
+ g.bd.movecurr((0, p.y - g.pos.y));
+ g.pos = p;
+ g.bd.update();
+}
+
+canmove(g: ref Game, piece: Piece, p: Point): int
+{
+ c := shapes[piece.shape].coords[piece.rot];
+ for (i := 0; i < len c; i++) {
+ q := p.add(c[i]);
+ if (q.x < 0 || q.x >= BOARDWIDTH || q.y >= BOARDHEIGHT)
+ return 0;
+ if (q.y >= 0 && int g.bd.state[q.y][q.x])
+ return 0;
+ }
+ return 1;
+}
+
+newpiece(g: ref Game)
+{
+ piece := g.curr = g.next;
+ g.next = randompiece();
+ g.bd.setnextshape(shapes[g.next.shape].colour, shapes[g.next.shape].coords[g.next.rot]);
+ shape := shapes[g.curr.shape];
+ coords := shape.coords[g.curr.rot];
+ g.pos = (3, -4);
+ for (i := 0; i < len coords; i++)
+ g.pieceids[i] = g.bd.makeblock(shape.colour, g.pos.add(coords[i]));
+}
+
+p2s(p: Point): string
+{
+ return string p.x + " " + string p.y;
+}
+
+Board.new(top: ref Tk->Toplevel, w: string, blocksize: int, maxsize: Point): ref Board
+{
+ cmd(top, "frame " + w);
+ cmd(top, "canvas " + w + ".c -borderwidth 2 -relief sunken -width 1 -height 1");
+ cmd(top, "frame " + w + ".f");
+ cmd(top, "canvas " + w + ".f.ns -width 1 -height 1");
+ makescorewidget(top, w + ".f.scoref", "Score");
+ makescorewidget(top, w + ".f.levelf", "Level");
+ makescorewidget(top, w + ".f.rowsf", "Rows");
+ cmd(top, "pack " + w + ".c -side left");
+ cmd(top, "pack " + w + ".f -side top");
+ cmd(top, "pack " + w + ".f.ns -side top");
+ cmd(top, "pack " + w + ".f.scoref -side top -fill x");
+ cmd(top, "pack " + w + ".f.levelf -side top -fill x");
+ cmd(top, "pack " + w + ".f.rowsf -side top -fill x");
+
+ sz := wsize(top, w);
+ avail := Point(maxsize.x - sz.x, maxsize.y);
+ avail.x /= BOARDWIDTH;
+ avail.y /= BOARDHEIGHT;
+ dx := avail.x;
+ if (avail.y < avail.x)
+ dx = avail.y;
+ if (dx <= 0)
+ return nil;
+ if (dx > blocksize)
+ dx = blocksize;
+ cmd(top, w + ".f.ns configure -width " + string(4 * dx + 1 - 2*2) +
+ " -height " + string(4 * dx + 1 - 2*2));
+ cmd(top, w + ".c configure -width " + string(dx * BOARDWIDTH + 1) +
+ " -height " + string(dx * BOARDHEIGHT + 1));
+ bd := ref Board(array[BOARDHEIGHT]
+ of {* => array[BOARDWIDTH] of {* => byte 0}},
+ w, dx, top, array[BOARDHEIGHT] of {* => Row(nil, 0)}, 1);
+ return bd;
+}
+
+makescorewidget(top: ref Tk->Toplevel, w, title: string)
+{
+ cmd(top, "frame " + w);
+ cmd(top, "label " + w + ".title -text " + tk->quote(title));
+ cmd(top, "label " + w +
+ ".val -bd 3 -relief sunken -width 5w -text 0 -anchor e");
+ cmd(top, "pack " + w + ".title -side left -anchor w");
+ cmd(top, "pack " + w + ".val -side right -anchor e");
+}
+
+blockrect(bd: ref Board, p: Point): string
+{
+ p = p.mul(bd.dx);
+ q := p.add((bd.dx, bd.dx));
+ return string p.x + " " + string p.y + " " + string q.x + " " + string q.y;
+}
+
+Board.makeblock(bd: self ref Board, colour: string, p: Point): string
+{
+ tag := cmd(bd.win, bd.w + ".c create rectangle " + blockrect(bd, p) + " -fill " + colour + " -tags curr");
+ if (tag != nil && tag[0] == '!')
+ return nil;
+ return tag;
+}
+
+Board.moveblock(bd: self ref Board, b: string, p: Point)
+{
+ cmd(bd.win, bd.w + ".c coords " + b + " " + blockrect(bd, p));
+}
+
+Board.movecurr(bd: self ref Board, delta: Point)
+{
+ delta = delta.mul(bd.dx);
+ cmd(bd.win, bd.w + ".c move curr " + string delta.x + " " + string delta.y);
+}
+
+Board.landedblock(bd: self ref Board, b: string, p: Point)
+{
+ cmd(bd.win, bd.w + ".c dtag " + b + " curr");
+ rs := cmd(bd.win, bd.w + ".c coords " + b);
+ if (rs != nil && rs[0] == '!')
+ return;
+ (n, toks) := sys->tokenize(rs, " ");
+ if (len toks != 4) {
+ sys->fprint(stderr, "bad coords for block %s\n", b);
+ return;
+ }
+ y := int hd tl toks / bd.dx;
+ if (y < 0)
+ return;
+ if (y >= BOARDHEIGHT) {
+ sys->fprint(stderr, "block '%s' too far down (coords %s)\n", b, rs);
+ return;
+ }
+ rtag := bd.rows[y].tag;
+ if (rtag == nil)
+ rtag = bd.rows[y].tag = "r" + string bd.maxid++;
+ cmd(bd.win, bd.w + ".c addtag " + rtag + " withtag " + b);
+ if (p.y >= 0)
+ bd.state[p.y][p.x] = byte 1;
+}
+
+Board.delrows(bd: self ref Board, rows: list of int)
+{
+ while (rows != nil) {
+ r := hd rows;
+ bd.rows[r].delete = 1;
+ rows = tl rows;
+ }
+ j := BOARDHEIGHT - 1;
+ for (i := BOARDHEIGHT - 1; i >= 0; i--) {
+ if (bd.rows[i].delete) {
+ cmd(bd.win, bd.w + ".c delete " + bd.rows[i].tag);
+ bd.rows[i] = (nil, 0);
+ bd.state[i] = nil;
+ } else {
+ if (i != j && bd.rows[i].tag != nil) {
+ dy := (j - i) * bd.dx;
+ cmd(bd.win, bd.w + ".c move " + bd.rows[i].tag + " 0 " + string dy);
+ bd.rows[j] = bd.rows[i];
+ bd.rows[i] = (nil, 0);
+ bd.state[j] = bd.state[i];
+ bd.state[i] = nil;
+ }
+ j--;
+ }
+ }
+ for (i = 0; i < BOARDHEIGHT; i++)
+ if (bd.state[i] == nil)
+ bd.state[i] = array[BOARDWIDTH] of {* => byte 0};
+}
+
+Board.update(bd: self ref Board)
+{
+ cmd(bd.win, "update");
+}
+
+Board.setnextshape(bd: self ref Board, colour: string, spec: array of Point)
+{
+ cmd(bd.win, bd.w + ".f.ns delete all");
+ min := Point(4,4);
+ max := Point(0,0);
+ for (i := 0; i < len spec; i++) {
+ if (spec[i].x > max.x) max.x = spec[i].x;
+ if (spec[i].x < min.x) min.x = spec[i].x;
+ if (spec[i].y > max.y) max.y = spec[i].y;
+ if (spec[i].y < min.y) min.y = spec[i].y;
+ }
+ o: Point;
+ o.x = (4 - (max.x - min.x + 1)) * bd.dx / 2 - min.x * bd.dx;
+ o.y = (4 - (max.y - min.y + 1)) * bd.dx / 2 - min.y * bd.dx;
+ for (i = 0; i < len spec; i++) {
+ br := Rect(o.add(spec[i].mul(bd.dx)), o.add(spec[i].add((1,1)).mul(bd.dx)));
+ cmd(bd.win, bd.w + ".f.ns create rectangle " +
+ string br.min.x + " " + string br.min.y + " " + string br.max.x + " " + string br.max.y +
+ " -fill " + colour);
+ }
+}
+
+Board.setscore(bd: self ref Board, score: int)
+{
+ cmd(bd.win, bd.w + ".f.scoref.val configure -text " + string score);
+}
+
+Board.setlevel(bd: self ref Board, level: int)
+{
+ cmd(bd.win, bd.w + ".f.levelf.val configure -text " + string level);
+}
+
+Board.setnrows(bd: self ref Board, nrows: int)
+{
+ cmd(bd.win, bd.w + ".f.rowsf.val configure -text " + string nrows);
+}
+
+Board.gameover(bd: self ref Board)
+{
+ cmd(bd.win, "label " + bd.w + ".gameover -text {Game over} -bd 4 -relief ridge");
+ p := Point(BOARDWIDTH * bd.dx / 2, BOARDHEIGHT * bd.dx / 3);
+ cmd(bd.win, bd.w + ".c create window " + string p.x + " " + string p.y + " -window " + bd.w + ".gameover");
+}
+
+cmd(top: ref Tk->Toplevel, s: string): string
+{
+ e := tk->cmd(top, s);
+# sys->print("%s\n", s);
+ if (e != nil && e[0] == '!')
+ sys->fprint(stderr, "tetris: tk error on '%s': %s\n", s, e);
+ return e;
+}
+
+VIOLET: con "#ffaaff";
+CYAN: con "#93ddf1";
+
+delays := array[] of {300, 250, 200, 150, 100, 80};
+
+shapes := array[] of {
+Shape(
+ # ####
+ array[] of {
+ array[] of {Point(0,1), Point(1,1), Point(2,1), Point(3,1)},
+ array[] of {Point(1,0), Point(1,1), Point(1,2), Point(1,3)},
+ },
+ "red",
+ array[] of {5, 8}),
+Shape(
+ # ##
+ # ##
+ array[] of {
+ array[] of {Point(0,0), Point(0,1), Point(1,0), Point(1,1)},
+ },
+ "orange",
+ array[] of {6}),
+Shape(
+ # #
+ # ##
+ # #
+ array[] of {
+ array[] of {Point(1,0), Point(0,1), Point(1,1), Point(2,1)},
+ array[] of {Point(1,0), Point(1,1), Point(2,1), Point(1,2)},
+ array[] of {Point(0,1), Point(1,1), Point(2,1), Point(1,2)},
+ array[] of {Point(1,0), Point(0,1), Point(1,1), Point(1,2)},
+ },
+ "yellow",
+ array[] of {5,5,6,5}),
+Shape(
+ # ##
+ # ##
+ array[] of {
+ array[] of {Point(0,0), Point(1,0), Point(1,1), Point(2,1)},
+ array[] of {Point(1,0), Point(0,1), Point(1,1), Point(0,2)},
+ },
+ "green",
+ array[] of {6,7}),
+Shape(
+ # ##
+ # ##
+ array[] of {
+ array[] of {Point(1,0), Point(2,0), Point(0,1), Point(1,1)},
+ array[] of {Point(0,0), Point(0,1), Point(1,1), Point(1,2)},
+ },
+ "blue",
+ array[] of {6,7}),
+Shape(
+ # ###
+ # #
+ array[] of {
+ array[] of {Point(2,0), Point(0,1), Point(1,1), Point(2,1)},
+ array[] of {Point(0,0), Point(0,1), Point(0,2), Point(1,2)},
+ array[] of {Point(0,0), Point(1,0), Point(2,0), Point(0,1)},
+ array[] of {Point(0,0), Point(1,0), Point(1,1), Point(1,2)},
+ },
+ CYAN,
+ array[] of {6,7,6,7}),
+Shape(
+ # #
+ # ###
+ array[] of {
+ array[] of {Point(0,0), Point(1,0), Point(2,0), Point(2,1)},
+ array[] of {Point(1,0), Point(1,1), Point(0,2), Point(1,2)},
+ array[] of {Point(0,0), Point(0,1), Point(1,1), Point(2,1)},
+ array[] of {Point(0,0), Point(1,0), Point(0,1), Point(0,2)},
+ },
+ VIOLET,
+ array[] of {6,7,6,7}
+),
+};
+
diff --git a/appl/wm/toolbar.b b/appl/wm/toolbar.b
new file mode 100644
index 00000000..a96f5ba4
--- /dev/null
+++ b/appl/wm/toolbar.b
@@ -0,0 +1,566 @@
+implement Toolbar;
+include "sys.m";
+ sys: Sys;
+include "draw.m";
+ draw: Draw;
+ Screen, Display, Image, Rect, Point, Wmcontext, Pointer: import draw;
+include "tk.m";
+ tk: Tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+include "sh.m";
+ shell: Sh;
+ Listnode, Context: import shell;
+include "string.m";
+ str: String;
+include "arg.m";
+
+myselfbuiltin: Shellbuiltin;
+
+Toolbar: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+ initbuiltin: fn(c: ref Context, sh: Sh): string;
+ runbuiltin: fn(c: ref Context, sh: Sh,
+ cmd: list of ref Listnode, last: int): string;
+ runsbuiltin: fn(c: ref Context, sh: Sh,
+ cmd: list of ref Listnode): list of ref Listnode;
+ whatis: fn(c: ref Sh->Context, sh: Sh, name: string, wtype: int): string;
+ getself: fn(): Shellbuiltin;
+};
+
+MAXCONSOLELINES: con 1024;
+
+# execute this if no menu items have been created
+# by the init script.
+defaultscript :=
+ "{menu shell " +
+ "{{autoload=std; load $autoload; pctl newpgrp; wm/sh}&}}";
+
+tbtop: ref Tk->Toplevel;
+screenr: Rect;
+
+badmodule(p: string)
+{
+ sys->fprint(stderr(), "toolbar: cannot load %s: %r\n", p);
+ raise "fail:bad module";
+}
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ if(draw == nil)
+ badmodule(Draw->PATH);
+ tk = load Tk Tk->PATH;
+ if(tk == nil)
+ badmodule(Tk->PATH);
+
+ str = load String String->PATH;
+ if(str == nil)
+ badmodule(String->PATH);
+
+ tkclient = load Tkclient Tkclient->PATH;
+ if(tkclient == nil)
+ badmodule(Tkclient->PATH);
+ tkclient->init();
+
+ shell = load Sh Sh->PATH;
+ if (shell == nil)
+ badmodule(Sh->PATH);
+ arg := load Arg Arg->PATH;
+ if (arg == nil)
+ badmodule(Arg->PATH);
+
+ myselfbuiltin = load Shellbuiltin "$self";
+ if (myselfbuiltin == nil)
+ badmodule("$self(Shellbuiltin)");
+
+ sys->pctl(Sys->NEWPGRP|Sys->FORKNS, nil);
+
+ sys->bind("#p", "/prog", sys->MREPL);
+ sys->bind("#s", "/chan", sys->MBEFORE);
+
+ arg->init(argv);
+ arg->setusage("toolbar [-s]");
+ startmenu := 1;
+ while((c := arg->opt()) != 0){
+ case c {
+ 's' =>
+ startmenu = 0;
+ * =>
+ arg->usage();
+ }
+ }
+ argv = arg->argv();
+ arg = nil;
+
+ if (ctxt == nil){
+ sys->fprint(sys->fildes(2), "toolbar: must run under a window manager\n");
+ raise "fail:no wm";
+ }
+
+ exec := chan of string;
+ task := chan of string;
+
+ tbtop = toolbar(ctxt, startmenu, exec, task);
+ tkclient->startinput(tbtop, "ptr" :: "control" :: nil);
+ layout(tbtop);
+
+ shctxt := Context.new(ctxt);
+ shctxt.addmodule("wm", myselfbuiltin);
+
+ snarfIO := sys->file2chan("/chan", "snarf");
+ if(snarfIO == nil)
+ fatal(sys->sprint("cannot make /chan/snarf: %r"));
+ sync := chan of string;
+ spawn consoleproc(ctxt, sync);
+ if ((err := <-sync) != nil)
+ fatal(err);
+
+ setupfinished := chan of int;
+ donesetup := 0;
+ spawn setup(shctxt, setupfinished);
+
+ snarf: array of byte;
+# write("/prog/"+string sys->pctl(0, nil)+"/ctl", "restricted"); # for testing
+ for(;;) alt{
+ s := <-tbtop.ctxt.kbd =>
+ tk->keyboard(tbtop, c);
+ m := <-tbtop.ctxt.ptr =>
+ tk->pointer(tbtop, *m);
+ s := <-tbtop.ctxt.ctl or
+ s = <-tbtop.wreq =>
+ wmctl(tbtop, s);
+ s := <-exec =>
+ # guard against parallel access to the shctxt environment
+ if (donesetup){
+ {
+ shctxt.run(ref Listnode(nil, s) :: nil, 0);
+ } exception e {"fail:*" =>;}
+ }
+ detask := <-task =>
+ deiconify(detask);
+ (off, data, fid, wc) := <-snarfIO.write =>
+ if(wc == nil)
+ break;
+ if (off == 0) # write at zero truncates
+ snarf = data;
+ else {
+ if (off + len data > len snarf) {
+ nsnarf := array[off + len data] of byte;
+ nsnarf[0:] = snarf;
+ snarf = nsnarf;
+ }
+ snarf[off:] = data;
+ }
+ wc <-= (len data, "");
+ (off, nbytes, nil, rc) := <-snarfIO.read =>
+ if(rc == nil)
+ break;
+ if (off >= len snarf) {
+ rc <-= (nil, ""); # XXX alt
+ break;
+ }
+ e := off + nbytes;
+ if (e > len snarf)
+ e = len snarf;
+ rc <-= (snarf[off:e], ""); # XXX alt
+ donesetup = <-setupfinished =>
+ ;
+ }
+}
+
+wmctl(top: ref Tk->Toplevel, c: string)
+{
+ args := str->unquoted(c);
+ if(args == nil)
+ return;
+ n := len args;
+
+ case hd args{
+ "request" =>
+ # request clientid args...
+ if(n < 3)
+ return;
+ args = tl args;
+ clientid := hd args;
+ args = tl args;
+ err := handlerequest(clientid, args);
+ if(err != nil)
+ sys->fprint(sys->fildes(2), "toolbar: bad wmctl request %#q: %s\n", c, err);
+ "newclient" =>
+ # newclient id
+ ;
+ "delclient" =>
+ # delclient id
+ deiconify(hd tl args);
+ "rect" =>
+ tkclient->wmctl(top, c);
+ layout(top);
+ * =>
+ tkclient->wmctl(top, c);
+ }
+}
+
+handlerequest(clientid: string, args: list of string): string
+{
+ n := len args;
+ case hd args {
+ "task" =>
+ # task name
+ if(n != 2)
+ return "no task label given";
+ iconify(clientid, hd tl args);
+ "untask" or
+ "unhide" =>
+ deiconify(clientid);
+ * =>
+ return "unknown request";
+ }
+ return nil;
+}
+
+iconify(id, label: string)
+{
+ label = condenselabel(label);
+ e := tk->cmd(tbtop, "button .toolbar." +id+" -command {send task "+id+"} -takefocus 0");
+ cmd(tbtop, ".toolbar." +id+" configure -text '" + label);
+ if(e[0] != '!')
+ cmd(tbtop, "pack .toolbar."+id+" -side left -fill y");
+ cmd(tbtop, "update");
+}
+
+deiconify(id: string)
+{
+ e := tk->cmd(tbtop, "destroy .toolbar."+id);
+ if(e == nil){
+ tkclient->wmctl(tbtop, sys->sprint("ctl %q untask", id));
+ tkclient->wmctl(tbtop, sys->sprint("ctl %q kbdfocus 1", id));
+ }
+ cmd(tbtop, "update");
+}
+
+layout(top: ref Tk->Toplevel)
+{
+ r := top.screenr;
+ h := 32;
+ if(r.dy() < 480)
+ h = tk->rect(top, ".b", Tk->Border|Tk->Required).dy();
+ cmd(top, ". configure -x " + string r.min.x +
+ " -y " + string (r.max.y - h) +
+ " -width " + string r.dx() +
+ " -height " + string h);
+ cmd(top, "update");
+ tkclient->onscreen(tbtop, "exact");
+}
+
+toolbar(ctxt: ref Draw->Context, startmenu: int,
+ exec, task: chan of string): ref Tk->Toplevel
+{
+ (tbtop, nil) = tkclient->toplevel(ctxt, nil, nil, Tkclient->Plain);
+ screenr = tbtop.screenr;
+
+ cmd(tbtop, "button .b -text {XXX}");
+ cmd(tbtop, "pack propagate . 0");
+
+ tk->namechan(tbtop, exec, "exec");
+ tk->namechan(tbtop, task, "task");
+ cmd(tbtop, "frame .toolbar");
+ if (startmenu) {
+ cmd(tbtop, "menubutton .toolbar.start -menu .m -borderwidth 0 -bitmap vitasmall.bit");
+ cmd(tbtop, "pack .toolbar.start -side left");
+ }
+ cmd(tbtop, "pack .toolbar -fill x");
+ cmd(tbtop, "menu .m");
+ return tbtop;
+}
+
+setup(shctxt: ref Context, finished: chan of int)
+{
+ ctxt := shctxt.copy(0);
+ ctxt.run(shell->stringlist2list("run"::"/lib/wmsetup"::nil), 0);
+ # if no items in menu, then create some.
+ if (tk->cmd(tbtop, ".m type 0")[0] == '!')
+ ctxt.run(shell->stringlist2list(defaultscript::nil), 0);
+ cmd(tbtop, "update");
+ finished <-= 1;
+}
+
+condenselabel(label: string): string
+{
+ if(len label > 15){
+ new := "";
+ l := 0;
+ while(len label > 15 && l < 3) {
+ new += label[0:15]+"\n";
+ label = label[15:];
+ for(v := 0; v < len label; v++)
+ if(label[v] != ' ')
+ break;
+ label = label[v:];
+ l++;
+ }
+ label = new + label;
+ }
+ return label;
+}
+
+initbuiltin(ctxt: ref Context, nil: Sh): string
+{
+ if (tbtop == nil) {
+ sys = load Sys Sys->PATH;
+ sys->fprint(sys->fildes(2), "wm: cannot load wm as a builtin\n");
+ raise "fail:usage";
+ }
+ ctxt.addbuiltin("menu", myselfbuiltin);
+ ctxt.addbuiltin("delmenu", myselfbuiltin);
+ ctxt.addbuiltin("error", myselfbuiltin);
+ return nil;
+}
+
+whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string
+{
+ return nil;
+}
+
+runbuiltin(c: ref Context, sh: Sh,
+ cmd: list of ref Listnode, nil: int): string
+{
+ case (hd cmd).word {
+ "menu" => return builtin_menu(c, sh, cmd);
+ "delmenu" => return builtin_delmenu(c, sh, cmd);
+ }
+ return nil;
+}
+
+runsbuiltin(nil: ref Context, nil: Sh,
+ nil: list of ref Listnode): list of ref Listnode
+{
+ return nil;
+}
+
+stderr(): ref Sys->FD
+{
+ return sys->fildes(2);
+}
+
+word(ln: ref Listnode): string
+{
+ if (ln.word != nil)
+ return ln.word;
+ if (ln.cmd != nil)
+ return shell->cmd2string(ln.cmd);
+ return nil;
+}
+
+menupath(title: string): string
+{
+ mpath := ".m."+title;
+ for(j := 0; j < len mpath; j++)
+ if(mpath[j] == ' ')
+ mpath[j] = '_';
+ return mpath;
+}
+
+builtin_menu(nil: ref Context, nil: Sh, argv: list of ref Listnode): string
+{
+ n := len argv;
+ if (n < 3 || n > 4) {
+ sys->fprint(stderr(), "usage: menu topmenu [ secondmenu ] command\n");
+ raise "fail:usage";
+ }
+ primary := (hd tl argv).word;
+ argv = tl tl argv;
+
+ if (n == 3) {
+ w := word(hd argv);
+ if (len w == 0)
+ cmd(tbtop, ".m insert 0 separator");
+ else
+ cmd(tbtop, ".m insert 0 command -label " + tk->quote(primary) +
+ " -command {send exec " + w + "}");
+ } else {
+ secondary := (hd argv).word;
+ argv = tl argv;
+
+ mpath := menupath(primary);
+ e := tk->cmd(tbtop, mpath+" cget -width");
+ if(e[0] == '!') {
+ cmd(tbtop, "menu "+mpath);
+ cmd(tbtop, ".m insert 0 cascade -label "+tk->quote(primary)+" -menu "+mpath);
+ }
+ w := word(hd argv);
+ if (len w == 0)
+ cmd(tbtop, mpath + " insert 0 separator");
+ else
+ cmd(tbtop, mpath+" insert 0 command -label "+tk->quote(secondary)+
+ " -command {send exec "+w+"}");
+ }
+ return nil;
+}
+
+builtin_delmenu(nil: ref Context, nil: Sh, nil: list of ref Listnode): string
+{
+ delmenu(".m");
+ cmd(tbtop, "menu .m");
+ return nil;
+}
+
+delmenu(m: string)
+{
+ for (i := int cmd(tbtop, m + " index end"); i >= 0; i--)
+ if (cmd(tbtop, m + " type " + string i) == "cascade")
+ delmenu(cmd(tbtop, m + " entrycget " + string i + " -menu"));
+ cmd(tbtop, "destroy " + m);
+}
+
+getself(): Shellbuiltin
+{
+ return myselfbuiltin;
+}
+
+cmd(top: ref Tk->Toplevel, c: string): string
+{
+ s := tk->cmd(top, c);
+ if (s != nil && s[0] == '!')
+ sys->fprint(stderr(), "tk error on %#q: %s\n", c, s);
+ return s;
+}
+
+kill(pid: int, note: string): int
+{
+ fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE);
+ if(fd == nil || sys->fprint(fd, "%s", note) < 0)
+ return -1;
+ return 0;
+}
+
+fatal(s: string)
+{
+ sys->fprint(sys->fildes(2), "wm: %s\n", s);
+ kill(sys->pctl(0, nil), "killgrp");
+ raise "fail:error";
+}
+
+bufferproc(in, out: chan of string)
+{
+ h, t: list of string;
+ dummyout := chan of string;
+ for(;;){
+ outc := dummyout;
+ s: string;
+ if(h != nil || t != nil){
+ outc = out;
+ if(h == nil)
+ for(; t != nil; t = tl t)
+ h = hd t :: h;
+ s = hd h;
+ }
+ alt{
+ x := <-in =>
+ t = x :: t;
+ outc <-= s =>
+ h = tl h;
+ }
+ }
+}
+
+con_cfg := array[] of
+{
+ "frame .cons",
+ "scrollbar .cons.scroll -command {.cons.t yview}",
+ "text .cons.t -width 60w -height 15w -bg white "+
+ "-fg black -font /fonts/misc/latin1.6x13.font "+
+ "-yscrollcommand {.cons.scroll set}",
+ "pack .cons.scroll -side left -fill y",
+ "pack .cons.t -fill both -expand 1",
+ "pack .cons -expand 1 -fill both",
+ "pack propagate . 0",
+ "update"
+};
+nlines := 0; # transcript length
+
+consoleproc(ctxt: ref Draw->Context, sync: chan of string)
+{
+ iostdout := sys->file2chan("/chan", "wmstdout");
+ if(iostdout == nil){
+ sync <-= sys->sprint("cannot make /chan/wmstdout: %r");
+ return;
+ }
+ iostderr := sys->file2chan("/chan", "wmstderr");
+ if(iostderr == nil){
+ sync <-= sys->sprint("cannot make /chan/wmstdout: %r");
+ return;
+ }
+
+ sync <-= nil;
+
+ (top, titlectl) := tkclient->toplevel(ctxt, "", "Log", tkclient->Appl);
+ for(i := 0; i < len con_cfg; i++)
+ cmd(top, con_cfg[i]);
+
+ r := tk->rect(top, ".", Tk->Border|Tk->Required);
+ cmd(top, ". configure -x " + string ((top.screenr.dx() - r.dx()) / 2 + top.screenr.min.x) +
+ " -y " + string (r.dy() / 3 + top.screenr.min.y));
+
+ tkclient->startinput(top, "ptr"::"kbd"::nil);
+ tkclient->onscreen(top, "onscreen");
+ tkclient->wmctl(top, "task");
+
+ for(;;) alt {
+ c := <-titlectl or
+ c = <-top.wreq or
+ c = <-top.ctxt.ctl =>
+ if(c == "exit")
+ c = "task";
+ tkclient->wmctl(top, c);
+ c := <-top.ctxt.kbd =>
+ tk->keyboard(top, c);
+ p := <-top.ctxt.ptr =>
+ tk->pointer(top, *p);
+ (off, nbytes, fid, rc) := <-iostdout.read =>
+ if(rc == nil)
+ break;
+ alt{
+ rc <-= (nil, "inappropriate use of file") =>;
+ * =>;
+ }
+ (off, nbytes, fid, rc) := <-iostderr.read =>
+ if(rc == nil)
+ break;
+ alt{
+ rc <-= (nil, "inappropriate use of file") =>;
+ * =>;
+ }
+ (off, data, fid, wc) := <-iostdout.write =>
+ conout(top, data, wc);
+ (off, data, fid, wc) := <-iostderr.write =>
+ conout(top, data, wc);
+ if(wc != nil)
+ tkclient->wmctl(top, "untask");
+ }
+}
+
+conout(top: ref Tk->Toplevel, data: array of byte, wc: Sys->Rwrite)
+{
+ if(wc == nil)
+ return;
+
+ s := string data;
+ tk->cmd(top, ".cons.t insert end '"+ s);
+ alt{
+ wc <-= (len data, nil) =>;
+ * =>;
+ }
+
+ for(i := 0; i < len s; i++)
+ if(s[i] == '\n')
+ nlines++;
+ if(nlines > MAXCONSOLELINES){
+ cmd(top, ".cons.t delete 1.0 " + string (nlines/4) + ".0; update");
+ nlines -= nlines / 4;
+ }
+
+ tk->cmd(top, ".cons.t see end; update");
+}
diff --git a/appl/wm/unibrowse.b b/appl/wm/unibrowse.b
new file mode 100644
index 00000000..50eefb40
--- /dev/null
+++ b/appl/wm/unibrowse.b
@@ -0,0 +1,966 @@
+implement Unibrowse;
+
+# unicode browser for inferno.
+# roger peppe (rog@ohm.york.ac.uk)
+
+include "sys.m";
+ sys: Sys;
+ stderr: ref Sys->FD;
+include "draw.m";
+ draw: Draw;
+include "tk.m";
+ tk: Tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+include "dialog.m";
+ dialog: Dialog;
+include "selectfile.m";
+ selectfile: Selectfile;
+include "string.m";
+ str: String;
+include "bufio.m";
+ bio: Bufio;
+
+Unibrowse: module
+{
+ init: fn(ctxt: ref Draw->Context, nil: list of string);
+};
+
+Widgetstack: adt {
+ stk: list of string; # list of widget names; bottom of list is left-most widget
+ name: string;
+
+ # init returns the widget name for the widgetstack;
+ # wn is the name of the frame holding the widget stack
+ new: fn(wn: string): ref Widgetstack;
+
+ push: fn(ws: self ref Widgetstack, w: string);
+ pop: fn(ws: self ref Widgetstack): string;
+ top: fn(ws: self ref Widgetstack): string;
+};
+
+Defaultwidth: con 30;
+Defaultheight: con 1;
+
+Tablerows: con 3;
+Tablecols: con 8;
+
+Element: adt {
+ name: string;
+ cmd: chan of string;
+ cmdname: string;
+ config: array of string;
+ doneinit: int;
+};
+
+# columns in unidata file
+ud_VAL, ud_CHARNAME, ud_CATEG, ud_COMBINE, ud_BIDIRECT,
+ud_DECOMP, ud_DECDIGIT, ud_DIGIT, ud_NUMERICVAL, ud_MIRRORED,
+ud_OLDNAME, ud_COMMENT, ud_UPCASE, ud_LOWCASE, ud_TITLECASE: con iota;
+
+# default font configurations within the application
+DEFAULTFONT: con "";
+UNICODEFONT: con "lucm/unicode.9";
+TITLEFONT: con "misc/latin1.8x13";
+DATAFONT: con "misc/latin1.8x13";
+BUTTONFONT: con "misc/latin1.8x13";
+
+currfont := "/fonts/" + UNICODEFONT + ".font";
+
+MAINMENU, BYSEARCH, BYNUMBER, BYCATEGORY, BYFONT, TABLE: con iota;
+elements := array[] of {
+MAINMENU => Element(".main", nil, "maincmd", array[] of {
+ "frame .main",
+ "$listbox data .main.menu -height 6h",
+ "$button button .main.insp -text {Inspector} -command {send maincmd inspect}",
+ "$button button .main.font -text {Font} -command {send maincmd font}",
+ "$label unicode .fontlabel", # .fontlabel's font is currently chosen font
+ "pack .main.menu -side top",
+ "pack .main.insp .main.font -side left",
+ "bind .main.menu <ButtonRelease-1> +{send maincmd newselect}"
+ }, 0),
+BYNUMBER => Element(".numfield", nil, "numcmd", array[] of {
+ "frame .numfield",
+ "$entry data .numfield.f -width 8w",
+ "bind .numfield.f <Key-\n> {send numcmd shownum}",
+ "$label title .numfield.l -text 'Hex unicode value",
+ "pack .numfield.l .numfield.f -side left"
+ }, 0),
+TABLE => Element(".tbl", nil, "tblcmd", array[] of {
+ "frame .tbl",
+ "frame .tbl.tf",
+ "frame .tbl.buts",
+ "$button button .tbl.buts.forw -text {Next} -command {send tblcmd forw}",
+ "$button button .tbl.buts.backw -text {Prev} -command {send tblcmd backw}",
+ "pack .tbl.buts.forw .tbl.buts.backw -side left",
+ "pack .tbl.tf -side top",
+ "pack .tbl.buts -side left"
+ }, 0),
+BYCATEGORY => Element(".cat", nil, "catcmd", array[] of {
+ "frame .cat",
+ "$listbox data .cat.menu -width 43w -height 130 -yscrollcommand {.cat.yscroll set}",
+ "scrollbar .cat.yscroll -width 18 -command {.cat.menu yview}",
+ "pack .cat.yscroll .cat.menu -side left -fill y",
+ "bind .cat.menu <ButtonRelease-1> +{send catcmd newselect}"
+ }, 0),
+BYSEARCH => Element(".srch", nil, "searchcmd", array[] of {
+ "frame .srch",
+ "$listbox data .srch.menu -width 43w -height 130 -yscrollcommand {.srch.yscroll set}",
+ "scrollbar .srch.yscroll -width 18 -command {.srch.menu yview}",
+ "pack .srch.yscroll .srch.menu -side left -fill y",
+ "bind .srch.menu <ButtonRelease-1> +{send searchcmd search}"
+ }, 0),
+BYFONT => Element(".font", nil, "fontcmd", array[] of {
+ "frame .font",
+ "$listbox data .font.menu -width 43w -height 130 -yscrollcommand {.font.yscroll set}",
+ "scrollbar .font.yscroll -width 18 -command {.font.menu yview}",
+ "pack .font.yscroll .font.menu -side left -fill y",
+ "bind .font.menu <ButtonRelease-1> +{send fontcmd newselect}"
+ }, 0),
+};
+
+entries := array[] of {
+("By Category", BYCATEGORY),
+("By number", BYNUMBER),
+("Symbol wordsearch", BYSEARCH),
+("Font information", BYFONT)
+};
+
+toplevelconfig := array[] of {
+"pack .Wm_t .display -side top -fill x",
+"image create bitmap waiting -file cursor.wait"
+};
+
+wmchan: chan of string; # from main window
+inspchan: chan of string; # to inspector
+
+ctxt: ref Draw->Context;
+displ: ref Widgetstack;
+top: ref Tk->Toplevel;
+unidata: ref bio->Iobuf;
+
+UNIDATA: con "/lib/unidata/unidata2.txt";
+UNIINDEX: con "/lib/unidata/index2.txt";
+UNIBLOCKS: con "/lib/unidata/blocks.txt";
+
+notice(msg: string)
+{
+ dialog->prompt(ctxt, top.image, "bomb.bit", "Notice", msg, 0, "OK"::nil);
+}
+
+init(drawctxt: ref Draw->Context, nil: list of string)
+{
+ entrychan := chan of string;
+
+ ctxt = drawctxt;
+ config();
+ if ((unidata = bio->open(UNIDATA, bio->OREAD)) == nil) {
+ notice("Couldn't open unicode data file");
+ inspchan <-= "exit";
+ exit;
+ }
+
+ push(MAINMENU);
+ tkclient->onscreen(top, nil);
+ tkclient->startinput(top, "kbd"::"ptr"::nil);
+ currpos := 0;
+
+ for (;;) alt {
+ c := <-top.ctxt.kbd =>
+ tk->keyboard(top, c);
+ p := <-top.ctxt.ptr =>
+ tk->pointer(top, *p);
+ c := <-top.ctxt.ctl or
+ c = <-top.wreq or
+ c = <-wmchan =>
+ tkclient->wmctl(top, c);
+ c := <-elements[MAINMENU].cmd =>
+ case c {
+ "font" =>
+ font := choosefont(ctxt);
+ if (font != nil) {
+ currfont = font;
+ updatefont();
+ update(top);
+ }
+ "newselect" =>
+ sel := int cmd(top, ".main.menu curselection");
+ (nil, el) := entries[sel];
+ if (el == BYSEARCH) {
+ spawn sendentry(top, "Enter search string", entrychan);
+ break;
+ }
+ pop(MAINMENU);
+ push(el);
+ update(top);
+
+ "inspect" =>
+ inspchan <-= "raise";
+ }
+ c := <-entrychan =>
+ if (c != nil) {
+ pop(MAINMENU);
+ push(BYSEARCH);
+ update(top);
+ keywordsearch(c);
+ }
+
+ c := <-elements[BYNUMBER].cmd =>
+ txt := cmd(top, ".numfield.f get");
+ (n, nil) := str->toint(txt, 16);
+
+ pop(BYNUMBER);
+ push(TABLE);
+ setchar(n);
+ currpos = filltable(n);
+ update(top);
+
+ c := <-elements[BYCATEGORY].cmd =>
+ sel := cmd(top, ".cat.menu curselection");
+ (currpos, nil) = str->toint(cmd(top, ".cat.menu get "+sel), 16);
+ pop(BYCATEGORY);
+ push(TABLE);
+ currpos = filltable(currpos);
+ update(top);
+
+ c := <-elements[TABLE].cmd =>
+ case c {
+ "forw" => currpos = filltable(currpos + Tablerows * Tablecols);
+ update(top);
+
+ "backw" => currpos = filltable(currpos - Tablerows * Tablecols);
+ update(top);
+
+ * => # must be set <col> <row>
+ (nil, args) := sys->tokenize(c, " ");
+ setchar(currpos + int hd tl args
+ + int hd tl tl args * Tablecols);
+ }
+
+ c := <-elements[BYSEARCH].cmd =>
+ sel := cmd(top, ".srch.menu curselection");
+ (n, nil) := str->toint(cmd(top, ".srch.menu get "+sel), 16);
+
+ pop(BYSEARCH);
+ push(TABLE);
+ setchar(n);
+ currpos = filltable(n);
+ update(top);
+
+ c := <-elements[BYFONT].cmd =>
+ sel := cmd(top, ".font.menu curselection");
+ (currpos, nil) = str->toint(cmd(top, ".font.menu get "+sel), 16);
+ pop(BYFONT);
+ push(TABLE);
+ currpos = filltable(currpos);
+ update(top);
+ }
+ inspchan <-= "exit";
+}
+
+sendentry(t: ref Tk->Toplevel, msg: string, where: chan of string)
+{
+ where <-= dialog->getstring(ctxt, t.image, msg);
+ exit;
+}
+
+setchar(c: int)
+{
+ s := ""; s[0] = c;
+ inspchan <-= s;
+}
+
+
+charconfig := array[] of {
+"frame .chdata -borderwidth 5 -relief ridge",
+"frame .chdata.f1",
+"frame .chdata.f2",
+"frame .chdata.chf -borderwidth 4 -relief raised",
+"frame .chdata.chcf -borderwidth 3 -relief ridge",
+"$label title .chdata.chf.title -text 'Glyph: ",
+"$label unicode .chdata.ch",
+"$label data .chdata.val -anchor e",
+"$label title .chdata.name -anchor w",
+"$label data .chdata.cat -anchor w",
+"$label data .chdata.comm -anchor w",
+"$button button .chdata.snarfbut -text {Snarf} -command {send charcmd snarf}",
+"$button button .chdata.pastebut -text {Paste} -command {send charcmd paste}",
+"pack .chdata.chf.title .chdata.chcf -in .chdata.chf -side left",
+"pack .chdata.ch -in .chdata.chcf",
+"pack .chdata.chf -in .chdata.f1 -side left -padx 1 -pady 1",
+"pack .chdata.val -in .chdata.f1 -side right",
+"pack .chdata.snarfbut .chdata.pastebut -in .chdata.f2 -side right",
+"pack .chdata.f1 .chdata.name .chdata.cat .chdata.comm .chdata.f2 -fill x -side top",
+"pack .Wm_t .chdata -side top -fill x",
+};
+
+inspector(ctxt: ref Draw->Context, cmdch: chan of string)
+{
+ chtop: ref Tk->Toplevel;
+
+ kbd := chan of int;
+ ptr := chan of ref Draw->Pointer;
+ wreq := chan of string;
+ iwmchan := chan of string;
+ ctl := chan of string;
+
+ charcmd := chan of string;
+ currc := 'A';
+
+ for (;;) alt {
+ c := <-kbd =>
+ tk->keyboard(chtop, c);
+ p := <-ptr =>
+ tk->pointer(chtop, *p);
+ c := <-ctl or
+ c = <-wreq or
+ c = <-iwmchan =>
+ if (c != "exit" && chtop != nil)
+ tkclient->wmctl(chtop, c);
+ else
+ chtop = nil;
+ c := <-cmdch =>
+ case c {
+ "raise" =>
+ if (chtop != nil) {
+ cmd(chtop, "raise .");
+ break;
+ }
+ org := winorg(top);
+ org.y += int cmd(top, ". cget -actheight");
+ (chtop, iwmchan) = tkclient->toplevel(ctxt,
+ "-x "+string org.x+" -y "+string org.y,
+ "Character inspector", 0);
+ tk->namechan(chtop, charcmd, "charcmd");
+
+ runconfig(chtop, charconfig);
+ inspector_setchar(chtop, currc);
+ tkclient->onscreen(chtop, "onscreen");
+ tkclient->startinput(chtop, "ptr"::nil);
+ kbd = chtop.ctxt.kbd;
+ ptr = chtop.ctxt.ptr;
+ ctl = chtop.ctxt.ctl;
+ wreq = chtop.wreq;
+ "font" =>
+ if (chtop != nil) {
+ cmd(chtop, ".chdata.ch configure -font "+currfont);
+ update(chtop);
+ }
+ "exit" =>
+ exit;
+ * =>
+ if (len c == 1) {
+ currc = c[0];
+ inspector_setchar(chtop, currc);
+ } else {
+ sys->fprint(stderr, "unknown inspector cmd: '%s'\n", c);
+ }
+ }
+ c := <-charcmd =>
+ case c {
+ "snarf" =>
+ tkclient->snarfput(cmd(chtop, ".chdata.ch cget -text"));
+ "paste" =>
+ buf := tkclient->snarfget();
+ if (len buf > 0)
+ inspector_setchar(chtop, buf[0]);
+ }
+ }
+}
+
+inspector_setchar(t: ref Tk->Toplevel, c: int)
+{
+ line := look(unidata, ';', sys->sprint("%4.4X", c));
+ labelset(t, ".chdata.ch", sys->sprint("%c", c));
+ labelset(t, ".chdata.val", sys->sprint("%4.4X", c));
+ if (line == nil) {
+ labelset(t, ".chdata.name", "No entry found in unicode table");
+ labelset(t, ".chdata.cat", "");
+ labelset(t, ".chdata.comm", "");
+ } else {
+ flds := fields(line, ';');
+ labelset(t, ".chdata.name", fieldindex(flds, ud_CHARNAME));
+ labelset(t, ".chdata.cat", categname(fieldindex(flds, ud_CATEG)));
+ labelset(t, ".chdata.comm", fieldindex(flds, ud_OLDNAME));
+ }
+ update(t);
+}
+
+keywordsearch(key: string): int
+{
+
+ data := bio->open(UNIINDEX, Sys->OREAD);
+
+ key = str->tolower(key);
+
+ busy();
+ cmd(top, ".srch.menu delete 0 end");
+ count := 0;
+ while ((l := bio->data.gets('\n')) != nil) {
+ l = str->tolower(l);
+ if (str->prefix(key, l)) {
+ if (len l > 1 && l[len l - 2] == '\r')
+ l = l[0:len l - 2];
+ else
+ l = l[0:len l - 1];
+ flds := fields(l, '\t');
+ cmd(top, ".srch.menu insert end '"
+ +fieldindex(flds, 1)+": "+fieldindex(flds, 0));
+ update(top);
+ count++;
+ }
+ }
+ notbusy();
+ if (count == 0) {
+ notice("No match");
+ return 0;
+ }
+ return 1;
+}
+
+nomodule(s: string)
+{
+ sys->fprint(stderr, "couldn't load modules %s: %r\n", s);
+ raise "could not load modules";
+}
+
+config()
+{
+ sys = load Sys Sys->PATH;
+ if(ctxt == nil){
+ sys->fprint(stderr, "unibrowse: window manager required\n");
+ raise "no wm";
+ }
+ sys->pctl(Sys->NEWPGRP, nil);
+ stderr = sys->fildes(2);
+
+ draw = load Draw Draw->PATH;
+ if (draw == nil) nomodule(Draw->PATH);
+
+ tk = load Tk Tk->PATH;
+ if (tk == nil) nomodule(Tk->PATH);
+
+ tkclient = load Tkclient Tkclient->PATH;
+ if (tkclient == nil) nomodule(Tkclient->PATH);
+
+ dialog = load Dialog Dialog->PATH;
+ if (dialog == nil) nomodule(Dialog->PATH);
+
+ selectfile = load Selectfile Selectfile->PATH;
+ if (selectfile == nil) nomodule(Selectfile->PATH);
+
+ str = load String String->PATH;
+ if (str == nil) nomodule(String->PATH);
+
+ bio = load Bufio Bufio->PATH;
+ if (bio == nil) nomodule(Bufio->PATH);
+
+ tkclient->init();
+ dialog->init();
+ selectfile->init();
+
+ ctxt = ctxt;
+
+ (top, wmchan) = tkclient->toplevel(ctxt, nil, "Unicode browser", Tkclient->Hide);
+
+ displ = Widgetstack.new(".display");
+ cmd(top, "pack .display");
+
+ for (i := 0; i < len elements; i++) {
+ elements[i].cmd = tkchan(elements[i].cmdname);
+ runconfig(top, elements[i].config);
+ }
+
+ runconfig(top, toplevelconfig);
+
+ inspchan = chan of string;
+ spawn inspector(ctxt, inspchan);
+}
+
+runconfig(top: ref Tk->Toplevel, cmds: array of string)
+{
+ for (i := 0; i < len cmds; i++) {
+ ent := tkexpand(cmds[i]);
+ if (ent != nil) {
+ err := cmd(top, ent);
+ if (len err > 0 && err[0] == '!')
+ sys->fprint(stderr, "config err: %s on '%s'\n", err, ent);
+ }
+ }
+}
+
+update(top: ref Tk->Toplevel)
+{ cmd(top, "update"); }
+
+busy()
+{ cmd(top, "cursor -image waiting"); }
+
+notbusy()
+{ cmd(top, "cursor -default"); }
+
+initelement(el: int): int
+# returns non-zero on success
+{
+ if (!elements[el].doneinit) {
+ elements[el].doneinit = 1;
+ case el {
+ MAINMENU =>
+ for (e := entries; len e > 0; e = e[1:]) {
+ (text, nil) := e[0];
+ cmd(top, ".main.menu insert end '" + text);
+ }
+
+ BYCATEGORY =>
+ cats := getcategories();
+ if (cats == nil) {
+ notice("No categories found");
+ elements[el].doneinit = 0;
+ return 0;
+ }
+ while (cats != nil) {
+ cmd(top, ".cat.menu insert 0 '" + hd cats);
+ cats = tl cats;
+ }
+ BYFONT =>
+ elements[el].doneinit = 0; # do it each time
+ fonts := getfonts(currfont);
+ if (fonts == nil) {
+ notice("Can't find font information file");
+ return 0;
+ }
+
+ cmd(top, ".font.menu delete 0 end");
+ while (fonts != nil) {
+ cmd(top, ".font.menu insert 0 '" + hd fonts);
+ fonts = tl fonts;
+ }
+ TABLE =>
+ inittable();
+ }
+
+ }
+ return 1;
+}
+
+tablecharpath(col, row: int): string
+{
+ return ".tbl.tf.c"+string row+"_"+string col;
+}
+
+inittable()
+{
+ i: int;
+ for (i = 0; i < Tablerows; i++) {
+ cmd(top, tkexpand("$label title .tbl.tf.num" + string i));
+ cmd(top, sys->sprint("grid .tbl.tf.num%d -row %d", i, i));
+
+ # >>> could put entry here
+ for (j := 0; j < Tablecols; j++) {
+ cname := ".tbl.tf.c" + string i +"_" +string j;
+ cmd(top, tkexpand("$label unicode "+cname
+ +" -borderwidth 1 -relief raised"));
+ cmd(top, "bind "+cname+" <ButtonRelease-1>"
+ +" {send tblcmd set "+string j +" "+string i+"}");
+ cmd(top, "grid "+cname+" -row "+string i+" -column "+string (j+1) +
+ " -sticky ews");
+ }
+ }
+}
+
+# fill table starting at n.
+# return actual starting value.
+filltable(n: int): int
+{
+ if (n < 0)
+ n = 0;
+ if (n + Tablerows * Tablecols > 16rffff)
+ n = 16rffff - Tablerows * Tablecols;
+ n -= n % Tablecols;
+ for (i := 0; i < Tablerows; i++) {
+ cmd(top, ".tbl.tf.num" + string i +" configure -text '"
+ + sys->sprint("%4.4X",n+i*Tablecols));
+ for (j := 0; j < Tablecols; j++) {
+ cname := tablecharpath(j, i);
+ cmd(top, cname + " configure -text '"
+ +sys->sprint("%c", n + i * Tablecols + j));
+ }
+ }
+ return n;
+}
+
+cnumtoint(s: string): int
+{
+ if (len s == 0)
+ return 0;
+ if (s[0] == '0' && len s > 1) {
+ n: int;
+ if (s[1] == 'x' || s[1] == 'X') {
+ if (len s < 3)
+ return 0;
+ (n, nil) = str->toint(s[2:], 16);
+ } else
+ (n, nil) = str->toint(s, 8);
+ return n;
+ }
+ return int s;
+}
+
+getfonts(font: string): list of string
+{
+ f := bio->open(font, bio->OREAD);
+ if (f == nil)
+ return nil;
+
+ # ignore header
+ if (bio->f.gets('\n') == nil)
+ return nil;
+
+ ret: list of string;
+ while ((s := bio->f.gets('\n')) != nil) {
+ (count, wds) := sys->tokenize(s, " \t");
+ if (count < 3 || count > 4)
+ continue; # ignore malformed lines
+ first := cnumtoint(hd wds);
+ wds = tl wds;
+ last := cnumtoint(hd wds);
+ wds = tl wds;
+ if (tl wds != nil) # if optional third field exists
+ wds = tl wds; # ignore it
+ name := hd wds;
+ if (name != "" && name[len name - 1] == '\n')
+ name = name[0:len name - 1];
+ ret = sys->sprint("%.4X-%.4X: %s", first, last, name) :: ret;
+ }
+ return ret;
+}
+
+getcategories(): list of string
+{
+ f := bio->open(UNIBLOCKS, bio->OREAD);
+ if (f == nil)
+ return nil;
+
+ ret: list of string;
+ while ((s := bio->f.gets('\n')) != nil) {
+ if (s[0] == '#')
+ continue;
+ (s, nil) = str->splitr(s, "^\n\r");
+ if (len s > 0) {
+ start, end: string;
+ (start, s) = str->splitl(s, ";");
+ s = str->drop(s, "; ");
+ (end, s) = str->splitl(s, ";");
+ s = str->drop(s, "; ");
+
+ ret = start+"-"+end+": "+s :: ret;
+ }
+ }
+ return ret;
+}
+
+
+tkexpand(s: string): string
+{
+ if (len s == 0 || s[0] != '$')
+ return s;
+
+ cmd, tp, name: string;
+ (cmd, s) = str->splitl(s, " \t");
+ cmd = cmd[1:];
+
+ s = str->drop(s, " \t");
+ (tp, s) = str->splitl(s, " \t");
+ s = str->drop(s, " \t");
+
+ (name, s) = str->splitl(s, " \t");
+ s = str->drop(s, " \t");
+
+ font := "";
+ case tp {
+ "deflt" => font = DEFAULTFONT;
+ "title" => font = TITLEFONT;
+ "data" => font = DATAFONT;
+ "button" => font = BUTTONFONT;
+ "unicode" => font = currfont;
+ }
+ if (font != nil) {
+ if (font[0] != '/')
+ font = "/fonts/"+font+".font";
+ font = "-font "+font;
+ }
+
+
+ ret := cmd+" "+name+" "+font+" "+s;
+ return ret;
+}
+
+categname(s: string): string
+{
+ r := "Unknown category";
+ case s {
+ "Mn" => r = "Mark, Non-Spacing ";
+ "Mc" => r = "Mark, Combining";
+ "Nd" => r = "Number, Decimal Digit";
+ "No" => r = "Number, Other";
+ "Zs" => r = "Separator, Space";
+ "Zl" => r = "Separator, Line";
+ "Zp" => r = "Separator, Paragraph";
+ "Cc" => r = "Other, Control or Format";
+ "Co" => r = "Other, Private Use";
+ "Cn" => r = "Other, Not Assigned";
+ "Lu" => r = "Letter, Uppercase";
+ "Ll" => r = "Letter, Lowercase";
+ "Lt" => r = "Letter, Titlecase ";
+ "Lm" => r = "Letter, Modifier";
+ "Lo" => r = "Letter, Other ";
+ "Pd" => r = "Punctuation, Dash";
+ "Ps" => r = "Punctuation, Open";
+ "Pe" => r = "Punctuation, Close";
+ "Po" => r = "Punctuation, Other";
+ "Sm" => r = "Symbol, Math";
+ "Sc" => r = "Symbol, Currency";
+ "So" => r = "Symbol, Other";
+ }
+ return r;
+}
+
+
+fields(s: string, sep: int): list of string
+# seperator can't be '^' (see string(2))
+{
+ cl := ""; cl[0] = sep;
+ ret: list of string;
+ do {
+ (l, r) := str->splitr(s, cl);
+ ret = r :: ret;
+ if (len l > 0)
+ s = l[0:len l - 1];
+ else
+ s = nil;
+ } while (s != nil);
+ return ret;
+}
+
+fieldindex(sl: list of string, n: int): string
+{
+ for (; sl != nil; sl = tl sl) {
+ if (n == 0)
+ return hd sl;
+ n--;
+ }
+ return nil;
+}
+
+push(el: int)
+{
+ if (initelement(el)) {
+ displ.push(elements[el].name);
+ }
+}
+
+pop(el: int)
+# pop elements until we encounter one matching el.
+{
+ while (displ.top() != elements[el].name)
+ displ.pop();
+}
+
+tkchan(nm: string): chan of string
+{
+ c := chan of string;
+ tk->namechan(top, c, nm);
+ return c;
+}
+
+cmd(top: ref Tk->Toplevel, s: string): string
+{
+ # sys->print("%s\n", s);
+ e := tk->cmd(top, s);
+ if (e != nil && e[0] == '!')
+ sys->fprint(sys->fildes(2), "tk error on '%s': %s\n", s, e);
+ return e;
+}
+
+labelset(t: ref Tk->Toplevel, name: string, val: string)
+{
+ cmd(t, name+" configure -text '"+val);
+}
+
+
+choosefont(ctxt: ref Draw->Context): string
+{
+ font := selectfile->filename(ctxt, top.image, "Select a font", "*.font" :: nil, "/fonts");
+ if (font != nil) {
+ ret := cmd(top, ".fontlabel configure"+" -font "+font);
+ if (len ret > 0 && ret[0] == '!') {
+ font = nil;
+ notice("Bad font: "+ret[1:]);
+ }
+ }
+ return font;
+}
+
+updatefont()
+{
+ if (elements[TABLE].doneinit) # only if table is being displayed
+ for (i := 0; i < Tablerows; i++)
+ for (j := 0; j < Tablecols; j++)
+ cmd(top, tablecharpath(j, i) + " configure -font "+currfont);
+ # update the font display table if it's being displayed
+ for (el := displ.stk; el != nil; el = tl el) {
+ if (hd el == elements[BYFONT].name) {
+ initelement(BYFONT);
+ }
+ }
+ inspchan <-= "font";
+}
+
+
+winorg(t: ref Tk->Toplevel): Draw->Point
+{
+ return Draw->Point(int cmd(t, ". cget -x"), int cmd(t, ". cget -y"));
+}
+
+Widgetstack.new(wn: string): ref Widgetstack
+{
+ cmd(top, "frame "+wn+" -borderwidth 4 -relief ridge");
+
+ return ref Widgetstack(nil, wn);
+}
+
+Widgetstack.push(ws: self ref Widgetstack, w: string)
+{
+ if (w == nil)
+ return;
+ opts: con " -fill y -side left";
+
+ if (ws.stk == nil) {
+ cmd(top, "pack "+w+" -in "+ws.name+" "+opts);
+ } else {
+ cmd(top, "pack "+w+" -after "+hd ws.stk+" "+opts);
+ }
+
+ ws.stk = w :: ws.stk;
+}
+
+Widgetstack.pop(ws: self ref Widgetstack): string
+{
+ if (ws.stk == nil) {
+ sys->fprint(stderr, "widget stack underflow!\n");
+ exit;
+ }
+ old := hd ws.stk;
+ ws.stk = tl ws.stk;
+ cmd(top, "pack forget "+old);
+ return old;
+}
+
+Widgetstack.top(ws: self ref Widgetstack): string
+{
+ if (ws.stk == nil)
+ return nil;
+ return hd ws.stk;
+}
+
+# binary search for key in f.
+# code converted from bsd source without permission.
+look(f: ref bio->Iobuf, sep: int, key: string): string
+{
+ bot := mid := big 0;
+ ktop := bio->f.seek(big 0, Sys->SEEKEND);
+ key = canon(key, sep);
+
+ for (;;) {
+ mid = (ktop + bot) / big 2;
+ bio->f.seek(mid, Sys->SEEKSTART);
+ c: int;
+ do {
+ c = bio->f.getb();
+ mid++;
+ } while (c != bio->EOF && c != bio->ERROR && c != '\n');
+ (entry, eof) := getword(f);
+ if (entry == nil && eof)
+ break;
+ entry = canon(entry, sep);
+ case comparewords(key, entry) {
+ -2 or -1 or 0 =>
+ if (ktop <= mid)
+ break;
+ ktop = mid;
+ continue;
+ 1 or 2 =>
+ bot = mid;
+ continue;
+ }
+ break;
+ }
+ bio->f.seek(bot, Sys->SEEKSTART);
+ while (bio->f.seek(big 0, Sys->SEEKRELA) < ktop) {
+ (entry, eof) := getword(f);
+ if (entry == nil && eof)
+ return nil;
+ word := canon(entry, sep);
+ case comparewords(key, word) {
+ -2 =>
+ return nil;
+ -1 or 0 =>
+ return entry;
+ 1 or 2 =>
+ continue;
+ }
+ break;
+ }
+ for (;;) {
+ (entry, eof) := getword(f);
+ if (entry == nil && eof)
+ return nil;
+ word := canon(entry, sep);
+ case comparewords(key, word) {
+ -1 or 0 =>
+ return entry;
+ }
+ break;
+ }
+ return nil;
+}
+
+comparewords(s, t: string): int
+{
+ if (s == t)
+ return 0;
+ i := 0;
+ for (; i < len s && i < len t && s[i] == t[i]; i++)
+ ;
+ if (i >= len s)
+ return -1;
+ if (i >= len t)
+ return 1;
+ if (s[i] < t[i])
+ return -2;
+ return 2;
+}
+
+getword(f: ref bio->Iobuf): (string, int)
+{
+ ret := "";
+ for (;;) {
+ c := bio->f.getc();
+ if (c == bio->EOF || c == bio->ERROR)
+ return (ret, 0);
+ if (c == '\n')
+ break;
+ ret[len ret] = c;
+ }
+ return (ret, 1);
+}
+
+canon(s: string, sep: int): string
+{
+ if (sep < 0)
+ return s;
+ i := 0;
+ for (; i < len s; i++)
+ if (s[i] == sep)
+ break;
+ return s[0:i];
+}
diff --git a/appl/wm/view.b b/appl/wm/view.b
new file mode 100644
index 00000000..c96ef87d
--- /dev/null
+++ b/appl/wm/view.b
@@ -0,0 +1,484 @@
+implement View;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Context, Rect, Point, Display, Screen, Image: import draw;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "imagefile.m";
+ imageremap: Imageremap;
+ readgif: RImagefile;
+ readjpg: RImagefile;
+ readxbitmap: RImagefile;
+ readpng: RImagefile;
+
+include "tk.m";
+ tk: Tk;
+ Toplevel: import tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "selectfile.m";
+ selectfile: Selectfile;
+
+include "arg.m";
+
+include "plumbmsg.m";
+ plumbmsg: Plumbmsg;
+ Msg: import plumbmsg;
+
+stderr: ref Sys->FD;
+display: ref Display;
+x := 25;
+y := 25;
+img_patterns: list of string;
+plumbed := 0;
+background: ref Image;
+
+View: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+init(ctxt: ref Draw->Context, argv: list of string)
+{
+ spawn realinit(ctxt, argv);
+}
+
+realinit(ctxt: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "view: no window context\n");
+ raise "fail:bad context";
+ }
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ selectfile = load Selectfile Selectfile->PATH;
+
+ sys->pctl(Sys->NEWPGRP, nil);
+ tkclient->init();
+ selectfile->init();
+
+ stderr = sys->fildes(2);
+ display = ctxt.display;
+ background = display.color(16r222222ff);
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ badload(Arg->PATH);
+
+ img_patterns = list of {
+ "*.bit (Compressed image files)",
+ "*.gif (GIF image files)",
+ "*.jpg (JPEG image files)",
+ "*.jpeg (JPEG image files)",
+ "*.png (PNG image files)",
+ "*.xbm (X Bitmap image files)"
+ };
+
+ imageremap = load Imageremap Imageremap->PATH;
+ if(imageremap == nil)
+ badload(Imageremap->PATH);
+
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ badload(Bufio->PATH);
+
+
+ arg->init(argv);
+ errdiff := 1;
+ while((c := arg->opt()) != 0)
+ case c {
+ 'f' =>
+ errdiff = 0;
+ 'i' =>
+ if(!plumbed){
+ plumbmsg = load Plumbmsg Plumbmsg->PATH;
+ if(plumbmsg != nil && plumbmsg->init(1, "view", 1000) >= 0)
+ plumbed = 1;
+ }
+ }
+ argv = arg->argv();
+ arg = nil;
+ if(argv == nil && !plumbed){
+ f := selectfile->filename(ctxt, nil, "View file name", img_patterns, nil);
+ if(f == "") {
+ #spawn view(nil, nil, "");
+ return;
+ }
+ argv = f :: nil;
+ }
+
+
+ for(;;){
+ file: string;
+ if(argv != nil){
+ file = hd argv;
+ argv = tl argv;
+ if(file == "-f"){
+ errdiff = 0;
+ continue;
+ }
+ }else if(plumbed){
+ file = plumbfile();
+ if(file == nil)
+ break;
+ errdiff = 1; # set this from attributes?
+ }else
+ break;
+
+ (ims, masks, err) := readimages(file, errdiff);
+
+ if(ims == nil)
+ sys->fprint(stderr, "view: can't read %s: %s\n", file, err);
+ else
+ spawn view(ctxt, ims, masks, file);
+ }
+}
+
+badload(s: string)
+{
+ sys->fprint(stderr, "view: can't load %s: %r\n", s);
+ raise "fail:load";
+}
+
+readimages(file: string, errdiff: int) : (array of ref Image, array of ref Image, string)
+{
+ im := display.open(file);
+
+ if(im != nil)
+ return (array[1] of {im}, array[1] of ref Image, nil);
+
+ fd := bufio->open(file, Sys->OREAD);
+ if(fd == nil)
+ return (nil, nil, sys->sprint("%r"));
+
+ (mod, err1) := filetype(file, fd);
+ if(mod == nil)
+ return (nil, nil, err1);
+
+ (ai, err2) := mod->readmulti(fd);
+ if(ai == nil)
+ return (nil, nil, err2);
+ if(err2 != "")
+ sys->fprint(stderr, "view: %s: %s\n", file, err2);
+ ims := array[len ai] of ref Image;
+ masks := array[len ai] of ref Image;
+ for(i := 0; i < len ai; i++){
+ masks[i] = transparency(ai[i], file);
+
+ # if transparency is enabled, errdiff==1 is probably a mistake,
+ # but there's no easy solution.
+ (ims[i], err2) = imageremap->remap(ai[i], display, errdiff);
+ if(ims[i] == nil)
+ return(nil, nil, err2);
+ }
+ return (ims, masks, nil);
+}
+
+viewcfg := array[] of {
+ "panel .p",
+ "menu .m",
+ ".m add command -label Open -command {send cmd open}",
+ ".m add command -label Grab -command {send cmd grab} -state disabled",
+ ".m add command -label Save -command {send cmd save}",
+ "pack .p -side bottom -fill both -expand 1",
+ "bind .p <Button-3> {send cmd but3 %X %Y}",
+ "bind .p <Motion-Button-3> {}",
+ "bind .p <ButtonRelease-3> {}",
+ "bind .p <Button-1> {send but1 %X %Y}",
+};
+
+DT: con 250;
+
+timer(dt: int, ticks, pidc: chan of int)
+{
+ pidc <-= sys->pctl(0, nil);
+ for(;;){
+ sys->sleep(dt);
+ ticks <-= 1;
+ }
+}
+
+view(ctxt: ref Context, ims, masks: array of ref Image, file: string)
+{
+ file = lastcomponent(file);
+ (t, titlechan) := tkclient->toplevel(ctxt, "", "view: "+file, Tkclient->Hide);
+
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+ but1 := chan of string;
+ tk->namechan(t, but1, "but1");
+
+ for (c:=0; c<len viewcfg; c++)
+ tk->cmd(t, viewcfg[c]);
+ tk->cmd(t, "update");
+
+ image := display.newimage(ims[0].r, ims[0].chans, 0, Draw->White);
+ if (image == nil) {
+ sys->fprint(stderr, "view: can't create image: %r\n");
+ return;
+ }
+ imconfig(t, image);
+ image.draw(image.r, ims[0], masks[0], ims[0].r.min);
+ tk->putimage(t, ".p", image, nil);
+ tk->cmd(t, "update");
+
+ pid := -1;
+ ticks := chan of int;
+ if(len ims > 1){
+ pidc := chan of int;
+ spawn timer(DT, ticks, pidc);
+ pid = <-pidc;
+ }
+ imno := 0;
+ grabbing := 0;
+ tkclient->onscreen(t, nil);
+ tkclient->startinput(t, "kbd"::"ptr"::nil);
+
+
+ for(;;) alt{
+ s := <-t.ctxt.kbd =>
+ tk->keyboard(t, s);
+ s := <-t.ctxt.ptr =>
+ tk->pointer(t, *s);
+ s := <-t.ctxt.ctl or
+ s = <-t.wreq or
+ s = <-titlechan =>
+ tkclient->wmctl(t, s);
+
+ <-ticks =>
+ if(masks[imno] != nil)
+ paneldraw(t, image, image.r, background, nil, image.r.min);
+ ++imno;
+ if(imno >= len ims)
+ imno = 0;
+ paneldraw(t, image, ims[imno].r, ims[imno], masks[imno], ims[imno].r.min);
+ tk->cmd(t, "update");
+
+ s := <-cmd =>
+ (nil, l) := sys->tokenize(s, " ");
+ case (hd l) {
+ "open" =>
+ spawn open(ctxt, t);
+ "grab" =>
+ tk->cmd(t, "cursor -bitmap cursor.drag; grab set .p");
+ grabbing = 1;
+ "save" =>
+ patterns := list of {
+ "*.bit (Inferno image files)",
+ "*.gif (GIF image files)",
+ "*.jpg (JPEG image files)",
+ "* (All files)"
+ };
+ f := selectfile->filename(ctxt, t.image, "Save file name",
+ patterns, nil);
+ if(f != "") {
+ fd := sys->create(f, Sys->OWRITE, 8r664);
+ if(fd != nil)
+ display.writeimage(fd, ims[0]);
+ }
+ "but3" =>
+ if(!grabbing) {
+ xx := int hd tl l - 50;
+ yy := int hd tl tl l - int tk->cmd(t, ".m yposition 0") - 10;
+ tk->cmd(t, ".m activate 0; .m post "+string xx+" "+string yy+
+ "; grab set .m; update");
+ }
+ }
+ s := <- but1 =>
+ if(grabbing) {
+ (nil, l) := sys->tokenize(s, " ");
+ xx := int hd l;
+ yy := int hd tl l;
+# grabtop := tk->intop(ctxt.screen, xx, yy);
+# if(grabtop != nil) {
+# cim := grabtop.image;
+# imr := Rect((0,0), (cim.r.dx(), cim.r.dy()));
+# image = display.newimage(imr, cim.chans, 0, draw->White);
+# if(image == nil){
+# sys->fprint(stderr, "view: can't allocate image\n");
+# exit;
+# }
+# image.draw(imr, cim, nil, cim.r.min);
+# tk->cmd(t, ".Wm_t.title configure -text {View: grabbed}");
+# imconfig(t, image);
+# tk->putimage(t, ".p", image, nil);
+# tk->cmd(t, "update");
+# # Would be nicer if this could be spun off cleanly
+# ims = array[1] of {image};
+# masks = array[1] of ref Image;
+# imno = 0;
+# grabtop = nil;
+# cim = nil;
+# }
+ tk->cmd(t, "cursor -default; grab release .p");
+ grabbing = 0;
+ }
+ }
+}
+
+open(ctxt: ref Context, t: ref tk->Toplevel)
+{
+ f := selectfile->filename(ctxt, t.image, "View file name", img_patterns, nil);
+ t = nil;
+ if(f != "") {
+ (ims, masks, err) := readimages(f, 1);
+ if(ims == nil)
+ sys->fprint(stderr, "view: can't read %s: %s\n", f, err);
+ else
+ view(ctxt, ims, masks, f);
+ }
+}
+
+lastcomponent(path: string) : string
+{
+ for(k:=len path-2; k>=0; k--)
+ if(path[k] == '/'){
+ path = path[k+1:];
+ break;
+ }
+ return path;
+}
+
+imconfig(t: ref Toplevel, im: ref Draw->Image)
+{
+ width := im.r.dx();
+ height := im.r.dy();
+ tk->cmd(t, ".p configure -width " + string width
+ + " -height " + string height + "; update");
+}
+
+plumbfile(): string
+{
+ if(!plumbed)
+ return nil;
+ for(;;){
+ msg := Msg.recv();
+ if(msg == nil){
+ sys->print("view: can't read /chan/plumb.view: %r\n");
+ return nil;
+ }
+ if(msg.kind != "text"){
+ sys->print("view: can't interpret '%s' kind of message\n", msg.kind);
+ continue;
+ }
+ file := string msg.data;
+ if(len file>0 && file[0]!='/' && len msg.dir>0){
+ if(msg.dir[len msg.dir-1] == '/')
+ file = msg.dir+file;
+ else
+ file = msg.dir+"/"+file;
+ }
+ return file;
+ }
+}
+
+Tab: adt
+{
+ suf: string;
+ path: string;
+ mod: RImagefile;
+};
+
+GIF, JPG, PIC, PNG, XBM: con iota;
+
+tab := array[] of
+{
+ GIF => Tab(".gif", RImagefile->READGIFPATH, nil),
+ JPG => Tab(".jpg", RImagefile->READJPGPATH, nil),
+ PIC => Tab(".pic", RImagefile->READPICPATH, nil),
+ XBM => Tab(".xbm", RImagefile->READXBMPATH, nil),
+ PNG => Tab(".png", RImagefile->READPNGPATH, nil),
+};
+
+filetype(file: string, fd: ref Iobuf): (RImagefile, string)
+{
+ for(i:=0; i<len tab; i++){
+ n := len tab[i].suf;
+ if(len file>n && file[len file-n:]==tab[i].suf)
+ return loadmod(i);
+ }
+
+ # sniff the header looking for a magic number
+ buf := array[20] of byte;
+ if(fd.read(buf, len buf) != len buf)
+ return (nil, sys->sprint("%r"));
+ fd.seek(big 0, 0);
+ if(string buf[0:6]=="GIF87a" || string buf[0:6]=="GIF89a")
+ return loadmod(GIF);
+ if(string buf[0:5] == "TYPE=")
+ return loadmod(PIC);
+ jpmagic := array[] of {byte 16rFF, byte 16rD8, byte 16rFF, byte 16rE0,
+ byte 0, byte 0, byte 'J', byte 'F', byte 'I', byte 'F', byte 0};
+ if(eqbytes(buf, jpmagic))
+ return loadmod(JPG);
+ pngmagic := array[] of {byte 137, byte 80, byte 78, byte 71, byte 13, byte 10, byte 26, byte 10};
+ if(eqbytes(buf, pngmagic))
+ return loadmod(PNG);
+ if(string buf[0:7] == "#define")
+ return loadmod(XBM);
+ return (nil, "can't recognize file type");
+}
+
+eqbytes(buf, magic: array of byte): int
+{
+ for(i:=0; i<len magic; i++)
+ if(magic[i]>byte 0 && buf[i]!=magic[i])
+ return 0;
+ return i == len magic;
+}
+
+loadmod(i: int): (RImagefile, string)
+{
+ if(tab[i].mod == nil){
+ tab[i].mod = load RImagefile tab[i].path;
+ if(tab[i].mod == nil)
+ sys->fprint(stderr, "view: can't find %s reader: %r\n", tab[i].suf);
+ else
+ tab[i].mod->init(bufio);
+ }
+ return (tab[i].mod, nil);
+}
+
+transparency(r: ref RImagefile->Rawimage, file: string): ref Image
+{
+ if(r.transp == 0)
+ return nil;
+ if(r.nchans != 1){
+ sys->fprint(stderr, "view: can't do transparency for multi-channel image %s\n", file);
+ return nil;
+ }
+ i := display.newimage(r.r, display.image.chans, 0, 0);
+ if(i == nil){
+ sys->fprint(stderr, "view: can't allocate mask for %s: %r\n", file);
+ exit;
+ }
+ pic := r.chans[0];
+ npic := len pic;
+ mpic := array[npic] of byte;
+ index := r.trindex;
+ for(j:=0; j<npic; j++)
+ if(pic[j] == index)
+ mpic[j] = byte 0;
+ else
+ mpic[j] = byte 16rFF;
+ i.writepixels(i.r, mpic);
+ return i;
+}
+
+paneldraw(t: ref Tk->Toplevel, dst: ref Image, r: Rect, src, mask: ref Image, p: Point)
+{
+ dst.draw(r, src, mask, p);
+ s := sys->sprint(".p dirty %d %d %d %d", r.min.x, r.min.y, r.max.x, r.max.y);
+ tk->cmd(t, s);
+}
diff --git a/appl/wm/vt.b b/appl/wm/vt.b
new file mode 100644
index 00000000..6813e226
--- /dev/null
+++ b/appl/wm/vt.b
@@ -0,0 +1,1007 @@
+implement WmVt;
+
+# note: this code was hacked together in a hurry from some decade-old C code
+# of mine, so don't expect it to be pretty...
+# Also, don't expect it to be finished... I had to rush to check this
+# in... it's just been worked on as a side-project from time to time
+# But it's good enough to be useful most of the time
+
+include "sys.m";
+ sys: Sys;
+ sprint: import sys;
+include "draw.m";
+ draw: Draw;
+ Display, Font, Black, Rect, Image, Point, Endsquare, Enddisc: import draw;
+include "tk.m";
+ tk: Tk;
+ Toplevel: import tk;
+include "tkclient.m";
+ tkclient: Tkclient;
+include "sh.m";
+
+CON_Maxnpts: con 1000;
+Maxnhits: con 5;
+
+
+WmVt: module {
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+
+
+VT_MAXPARAM: con 8;
+
+
+Vt: adt {
+ y1, y2: int;
+ mode: int; # misc mode parameters
+ qmode: int; # extended mode parameters
+ attr: int; # display attributes
+ fg: int; # foreground color
+ bg: int; # background color
+
+ # saved values:
+ save_x, save_y: int;
+ save_attr: int;
+ save_fg, save_bg: int;
+ save_mode: int;
+ save_qmode: int;
+
+ # escape code parsing:
+ esc: int; # escape mode
+ pcount: int; # parameter count
+ etype: int; # escape code type
+ ptype: int; # current parameter type
+ value: int; # current value
+ param: array of int;
+
+ # display info:
+ wid, hgt: int;
+ x, y: int;
+ dx, dy: int;
+ nlcr: int;
+ ccc: int;
+ scr: array of string;
+ cc: array of string;
+};
+
+
+display: ref Display;
+t: ref Toplevel;
+canvas: ref Image;
+canvrect: Rect;
+org: Point;
+font: ref Font;
+stderr: ref Sys->FD;
+vt: ref Vt;
+pad: string;
+vtc := array[16] of ref Image;
+raw := 0;
+echo := 1;
+reverse := 0;
+sq := "";
+
+inpchan: chan of string;
+
+
+shwin_cfg := array[] of {
+ "frame .f",
+ "pack .c .f -side top -fill x",
+ "pack propagate . 0",
+ "focus .f",
+ "bind .f <Key> {send keys {%A}}",
+ "bind . <Configure> {send cmd resize}",
+ "update"
+};
+
+
+titlebar()
+{
+ tk->cmd(t, "destroy .Wm_t.S");
+ tk->cmd(t, "button .Wm_t.S -bg #aaaaaa -fg white -text {" +
+ sprint("%d x %d", vt.wid, vt.hgt) + "}; " +
+ "pack .Wm_t.S -side right");
+ c := "green";
+ if(raw)
+ c = "red";
+ tk->cmd(t, "destroy .Wm_t.k");
+ tk->cmd(t, "button .Wm_t.k -bitmap keyboard.bit"+
+ " -background "+c+" -command {send wm_title raw}; " +
+ "pack .Wm_t.k -side right");
+ c = "red";
+ if(echo)
+ c = "green";
+ tk->cmd(t, "destroy .Wm_t.d");
+ tk->cmd(t, "button .Wm_t.d -bitmap display.bit"+
+ " -background "+c+" -command {send wm_title echo}; " +
+ "pack .Wm_t.d -side right");
+ c = "white";
+ if(reverse)
+ c = "black";
+ tk->cmd(t, "destroy .Wm_t.r");
+ tk->cmd(t, "button .Wm_t.r -width 24 -height 24 "+
+ " -background "+c+" -command {send wm_title reverse}; " +
+ "pack .Wm_t.r -side right");
+ tk->cmd(t, "update");
+}
+
+init(ctxt: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "vt: no window context\n");
+ raise "fail:bad context";
+ }
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+
+ stderr = sys->fildes(2);
+
+ sys->pctl(Sys->FORKNS, nil);
+ sys->pctl(Sys->NEWPGRP, nil);
+
+ menubut: chan of string;
+ tkclient->init();
+ (t, menubut) = tkclient->toplevel(ctxt, "", "WmVt", Tkclient->Appl);
+
+ display = ctxt.display;
+ font = Font.open(display, "*default*");
+
+ vt = ref Vt;
+ vt.hgt = 24;
+ vt.wid = 80;
+ vt.scr = array[vt.hgt] of string;
+ vt.cc = array[vt.hgt] of string;
+ vt_init(vt);
+
+ pad = "";
+ for(i:=0; i<vt.wid; i++)
+ pad[i] = ' ';
+
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+ tk->cmd(t, "canvas .c -height "
+ + string (vt.hgt*font.height) +
+ + " -width " + string (vt.wid*font.width("0")) +
+ " -background red");
+ tkcmds(t, shwin_cfg);
+ tkclient->onscreen(t, nil);
+ tkclient->startinput(t, "kbd"::"ptr"::nil);
+ titlebar();
+
+ keys := chan of string;
+ tk->namechan(t, keys, "keys");
+
+ canvas = t.image;
+ canvrect = canvposn(t);
+ org = canvrect.min;
+
+ npts := 0;
+ WasUp := 1;
+
+ for(i=0; i<16; i++) {
+ r := 0;
+ g := 0;
+ b := 0;
+ v := 192;
+ if(i&8)
+ v = 255;
+ if(i&1)
+ r = v;
+ if(i&2)
+ g = v;
+ if(i&4)
+ b = v;
+ vtc[i] = display.newimage(((0,0),(1,1)), t.image.chans,
+ 1, display.rgb2cmap(r, g, b));
+ if (vtc[i] == nil) {
+ sys->fprint(sys->fildes(2), "Failed to allocate image\n");
+ exit;
+ }
+ }
+
+ vt_write(vt, "\u001b[2J");
+
+ ioc := chan of (int, ref Sys->FileIO, ref Sys->FileIO);
+ spawn newsh(ctxt, ioc);
+
+ (pid, file, filectl) := <- ioc;
+ if((file == nil) || (filectl == nil)) {
+ sys->print("newsh: %r\n");
+ return;
+ }
+
+ # XXX - need to kill this later
+ ic := chan of string;
+ spawn consinp(ic, file.read);
+
+ inpchan = ic; # hack
+
+ for(;;) alt {
+ s := <-t.ctxt.kbd =>
+ tk->keyboard(t, s);
+ s := <-t.ctxt.ptr =>
+ tk->pointer(t, *s);
+ s := <-t.ctxt.ctl or
+ s = <-t.wreq =>
+ tkclient->wmctl(t, s);
+ menu := <- menubut =>
+ if(menu == "exit") {
+ kill(pid);
+ return;
+ }
+ else if(menu == "raw") {
+ raw = !raw;
+ titlebar();
+ redraw();
+ }
+ else if(menu == "echo") {
+ echo = !echo;
+ titlebar();
+ redraw();
+ }
+ else if(menu == "reverse") {
+ reverse = !reverse;
+ tmp := vtc[0];
+ vtc[0] = vtc[7];
+ vtc[7] = tmp;
+ titlebar();
+ redraw();
+ } else
+ tkclient->wmctl(t, menu);
+ tk->cmd(t, "focus .f");
+
+ s := <- cmd =>
+ (n, cmdstr) := sys->tokenize(s, " \t\n");
+ case hd cmdstr {
+ "quit" =>
+ exit;
+ "resize" =>
+ # sys->print("resize\n");
+ canvas = t.image;
+ canvrect = canvposn(t);
+ org = canvrect.min;
+ # sys->print("%d,%d %d,%d\n", canvrect.max.x, canvrect.min.x,
+ # canvas.r.max.x, canvas.r.min.x);
+ resize((canvrect.max.x-canvrect.min.x)/font.width("0"),
+ (canvrect.max.y-canvrect.min.y)/font.height);
+ titlebar();
+ redraw();
+ }
+
+ c := <- keys =>
+ ic <-= c[1:2];
+ if(echo)
+ scwrite(c[1:2]);
+
+ (off, data, fid, wc) := <- file.write =>
+ if(wc == nil)
+ return;
+ if(echo && !raw && sq != "") {
+ s := "";
+ for(i=0; i<len sq; i++)
+ s += "\b \b";
+ scwrite(s);
+ }
+ scwrite(string data);
+ if(echo && !raw && sq != "")
+ scwrite(sq);
+ wc <-= (len data, nil);
+ (off, data, fid, wc) := <- filectl.write =>
+ if(string data == "rawon") {
+ raw = 1;
+ echo = 0;
+ titlebar();
+ redraw();
+ }
+ if(string data == "rawoff") {
+ raw = 0;
+ echo = 1;
+ titlebar();
+ redraw();
+ }
+ wc <-= (len data, nil);
+ }
+}
+
+resize(wid,hgt: int)
+{
+ scr := array[hgt] of string;
+ cc := array[hgt] of string;
+ for(y :=0; y<hgt; y++) {
+ oy := y + hgt - vt.hgt;
+ if(oy < vt.hgt && oy >= 0) {
+ scr[y] = vt.scr[oy];
+ cc[y] = vt.cc[oy];
+ } else {
+ scr[y] = "";
+ cc[y] = "";
+ }
+ }
+ vt.x += wid - vt.wid;
+ vt.y += hgt - vt.hgt;
+ if(vt.x < 0)
+ vt.x = 0;
+ if(vt.x >= wid)
+ vt.x = wid;
+ if(vt.y < 0)
+ vt.y = 0;
+ if(vt.y >= hgt)
+ vt.y = hgt;
+ vt.wid = wid;
+ vt.hgt = hgt;
+ vt.scr = scr;
+ vt.cc = cc;
+}
+
+
+fixdx := 0;
+fixdy := 0;
+
+canvposn(t: ref Toplevel): Rect
+{
+ r: Rect;
+
+ r.min.x = int tk->cmd(t, ".c cget -actx") + int tk->cmd(t, ".dx get");
+ r.min.y = int tk->cmd(t, ".c cget -acty") + int tk->cmd(t, ".dy get");
+ r.max.x = r.min.x + int tk->cmd(t, ".c cget -width") + int tk->cmd(t, ".dw get");
+ r.max.y = r.min.y + int tk->cmd(t, ".c cget -height") + int tk->cmd(t, ".dh get");
+
+ # correction for Tk bug (width/height not correct):
+ dx := (t.image.r.max.x - t.image.r.min.x) - (r.max.x - r.min.x);
+ dy := (t.image.r.max.y - t.image.r.min.y) - (r.max.y - r.min.y);
+ if(fixdx == 0) {
+ fixdx = dx;
+ fixdy = dy;
+ } else {
+ r.max.x += dx-fixdx;
+ r.max.y += dy-fixdy;
+ }
+ return r;
+}
+
+
+redraw()
+{
+ # sys->print("redraw\n");
+ for(y:=0; y<vt.hgt; y++) {
+ xp := canvrect.min.x;
+ yp := canvrect.max.y-(vt.hgt-y)*font.height;
+ f := 0;
+ for(x:=0; x<=len vt.cc[y]; x++) {
+ if(x == len vt.cc[y] || (vt.cc[y][x]>>4) != (vt.cc[y][f]>>4)) {
+ if(x == len vt.cc[y])
+ w := canvrect.max.x-xp;
+ else
+ w = font.width(vt.scr[y][f:x]);
+ if(len vt.cc[y] == 0)
+ ccc := 7;
+ else
+ ccc = vt.cc[y][f];
+ canvas.draw(((xp,yp),(xp+w,yp+font.height)),
+ vtc[ccc>>4], nil, (0, 0));
+ xp += w;
+ f = x;
+ }
+ }
+ xp = canvrect.min.x;
+ f = 0;
+ for(x=1; x<=len vt.scr[y]; x++) {
+ if(x == len vt.scr[y] || (vt.cc[y][x]&15) != (vt.cc[y][f]&15)) {
+ canvas.text((xp,yp), vtc[vt.cc[y][f]&15],
+ (0, 0), font, vt.scr[y][f:x]);
+ xp += font.width(vt.scr[y][f:x]);
+ f = x;
+ }
+ }
+ }
+}
+
+
+
+scwrite(s: string)
+{
+ putchar(vt.x, vt.y, vtscr(vt.y, vt.x), vtcc(vt.y, vt.x));
+ vt_write(vt, s);
+ putchar(vt.x, vt.y, vtscr(vt.y, vt.x), vtcc(vt.y, vt.x) ^ 16rff);
+}
+
+putchar(x,y: int, ch: int, ccc: int)
+{
+ if(len vt.scr[y] < x) {
+ vt.scr[y] += pad[0:x-len vt.scr[y]];
+ vt.cc[y] += pad[0:x-len vt.cc[y]];
+ }
+ xp := canvrect.min.x+font.width(vt.scr[y][0:x]);
+ yp := canvrect.max.y-(vt.hgt-y)*font.height;
+ s: string;
+ s[0] = ch;
+ canvas.draw(((xp,yp),(xp+font.width(s),yp+font.height)),
+ vtc[ccc>>4], nil, (0, 0));
+ canvas.text((xp,yp), vtc[ccc&15], (0, 0), font, s);
+}
+
+VT_PUTCHAR(vt: ref Vt, x,y: int, ch: int)
+{
+ if(len vt.scr[y] < x) {
+ vt.scr[y] += pad[0:x-len vt.scr[y]];
+ vt.cc[y] += pad[0:x-len vt.cc[y]];
+ }
+ vt.scr[y][x] = ch;
+ vt.cc[y][x] = vt.ccc;
+ putchar(x, y, ch, int vt.ccc);
+}
+
+VT_SCROLL_UP(vt: ref Vt, x1,y1,x2,y2,n: int)
+{
+ # XXX - needs to handle vertical slices
+ for(i:=y1; i<=y2-n; i++) {
+ vt.scr[i] = vt.scr[i+n];
+ vt.cc[i] = vt.cc[i+n];
+ }
+ r: Rect;
+ r.min.x = canvrect.min.x;
+ r.max.x = r.min.x+(x2-x1+1)*font.width(" ");
+ r.min.y = canvrect.max.y-(vt.hgt-y1)*font.height;
+ r.max.y = r.min.y+(y2-y1-n+1)*font.height;
+ canvas.draw(r, canvas, nil, (r.min.x, r.min.y+font.height*n));
+ VT_CLEAR(vt, x1,y2-n+1,x2,y2);
+}
+
+VT_SCROLL_DOWN(vt: ref Vt, x1,y1,x2,y2,n: int)
+{
+ # XXX - needs to handle vertical slices
+ for(i:=y2; i>=y1+n; i--) {
+ vt.scr[i] = vt.scr[i-n];
+ vt.cc[i] = vt.cc[i-n];
+ }
+ VT_CLEAR(vt, x1,y1,x2,y1+n-1);
+ redraw();
+}
+
+VT_SCROLL_LEFT(vt: ref Vt, x1,y1,x2,y2,n: int)
+{
+ # XXX - shouldn't always scroll whole line
+ for(y:=y1; y<=y2; y++) {
+ if(len vt.scr[y] > n) {
+ vt.scr[y] = vt.scr[y][n:];
+ vt.cc[y] = vt.cc[y][n:];
+ } else {
+ vt.scr[y] = "";
+ vt.cc[y] = "";
+ }
+ }
+ redraw();
+}
+
+VT_SCROLL_RIGHT(vt: ref Vt, x1,y1,x2,y2,n: int)
+{
+ # XXX - shouldn't always scroll whole line
+ for(y:=y1; y<=y2; y++) {
+ vt.scr[y] = pad[0:n] + vt.scr[y];
+ vt.cc[y] = pad[0:n] + vt.cc[y];
+ }
+ redraw();
+}
+
+VT_CLEAR(vt: ref Vt, x1,y1,x2,y2: int)
+{
+ # XXX - needs to handle vertical slices
+ for(y:=y1; y<=y2; y++) {
+ vt.scr[y] = "";
+ vt.cc[y] = "";
+ }
+ r: Rect;
+ r.min.x = canvrect.min.x;
+ r.max.x = r.min.x + (x2-x1+1)*font.width(" ");
+ r.min.y = canvrect.max.y-(vt.hgt-y1)*font.height;
+ r.max.y = r.min.y + (y2-y1+1)*font.height;
+ canvas.draw(r, vtc[vt.ccc>>4], nil, (0, 0));
+}
+
+VT_SET_COLOR(vt: ref Vt)
+{
+ if(vt.attr & (1<<7))
+ vt.ccc = ((vt.fg<<4) | vt.bg);
+ else
+ vt.ccc = ((vt.bg<<4) | vt.fg);
+ if(vt.attr & (1<<1))
+ vt.ccc ^= (1<<3);
+}
+
+vtscr(y,x: int): int
+{
+ if(vt.scr[y] == nil)
+ return ' ';
+ if(x >= len vt.scr[y])
+ return ' ';
+ return vt.scr[y][x];
+}
+
+vtcc(y,x: int): int
+{
+ if(vt.cc[y] == nil)
+ return 7;
+ if(x >= len vt.cc[y])
+ return 7;
+ return vt.cc[y][x];
+}
+
+VT_SET_CURSOR(nil: ref Vt, x,y: int)
+{
+}
+
+VT_BEEP(nil: ref Vt)
+{
+ redraw();
+}
+
+# function for simulated typing (for returning status)
+VT_TYPE(vt: ref Vt, b: string)
+{
+ inpchan <-= b;
+}
+
+
+#############################################################################
+
+
+vt_save_state(vt: ref Vt)
+{
+ vt.save_x = vt.x;
+ vt.save_y = vt.y;
+ vt.save_attr = vt.attr;
+ vt.save_fg = vt.fg;
+ vt.save_bg = vt.bg;
+ vt.save_mode = vt.mode;
+ vt.save_qmode = vt.qmode;
+}
+
+vt_restore_state(vt: ref Vt)
+{
+ vt.x = vt.save_x;
+ vt.y = vt.save_y;
+ vt.attr = vt.save_attr;
+ vt.fg = vt.save_fg;
+ vt.bg = vt.save_bg;
+ vt.mode = vt.save_mode;
+ vt.qmode = vt.save_qmode;
+ VT_SET_COLOR(vt);
+}
+
+
+
+# expects vt.wid, vt.hgt and implementation
+# variables to be initialized first:
+
+vt_init(vt: ref Vt)
+{
+ vt.fg = 7;
+ vt.bg = 0;
+ vt.attr = 0;
+ vt.mode = 0;
+ vt.qmode = (1<<7);
+ vt.y1 = 0;
+ vt.y2 = vt.hgt-1;
+ vt.x = 0;
+ vt.y = 0;
+ vt.dx = 1;
+ vt.dy = 1;
+ vt.esc = 0;
+ vt.pcount = 0;
+ vt.param = array[VT_MAXPARAM] of int;
+ vt_save_state(vt);
+ VT_SET_COLOR(vt);
+}
+
+
+vt_checkscroll(vt: ref Vt, s: string)
+{
+ i := 0;
+ n: int;
+ if (vt.y == vt.y2+1 || vt.y >= vt.hgt) {
+ n = 1;
+ while(i < len s && n < (vt.y2-vt.y1)) {
+ c := s[i++];
+ if(c == 27 || c > 126 || c < 0)
+ break;
+ if(c == '\n')
+ n++;
+ }
+ vt.y = vt.y2-n+1;
+ VT_SCROLL_UP(vt,0,vt.y1,vt.wid-1,vt.y2,n);
+ } else if (vt.y == vt.y1-1) {
+ vt.y = vt.y1;
+ VT_SCROLL_DOWN(vt,0,vt.y1,vt.wid-1,vt.y2,1);
+ } else if (vt.y < 0)
+ vt.y = 0;
+}
+
+vt_write(vt: ref Vt, s: string)
+{
+ ch: int;
+ check_scroll: int;
+ n: int;
+ i := 0;
+
+ while(i < len s) {
+ check_scroll = 0;
+ ch = s[i++];
+ case vt.esc {
+ 1 =>
+ if(ch == '[') {
+ vt.etype = ch;
+ vt.esc++;
+ vt.value = 0;
+ vt.pcount = 0;
+ vt.ptype = 1;
+ for(n=0; n<VT_MAXPARAM; n++)
+ vt.param[n] = 0;
+ } else {
+ check_scroll = vt_call_ncsi(vt, ch);
+ vt.esc = 0;
+ }
+ 2 =>
+ if(ch >= '0' && ch <= '9')
+ vt.value=(vt.value)*10+(ch-'0');
+ else if(ch == '?')
+ vt.ptype = -1;
+ else {
+ vt.param[vt.pcount++] = vt.value*vt.ptype;
+ if(ch == ';') {
+ if(vt.pcount >= VT_MAXPARAM)
+ vt.pcount = VT_MAXPARAM-1;
+ vt.value = 0;
+ } else {
+ check_scroll = vt_call_csi(vt, ch);
+ vt.esc = 0;
+ }
+ }
+ * =>
+ case ch {
+ '\n' =>
+ vt.y += vt.dy;
+ check_scroll = 1;
+ if(vt.nlcr)
+ vt.x = 0;
+ '\r' =>
+ vt.x = 0;
+ '\b' =>
+ if (vt.x > 0)
+ vt.x -= vt.dx;
+ '\t' =>
+ n = (vt.x & ~7)+8;
+ if(vt.mode & (1<<4))
+ VT_SCROLL_RIGHT(vt, vt.x,vt.y,
+ vt.wid-1,vt.y, n - vt.x);
+ vt.x = n;
+ if(vt.x > vt.wid) {
+ vt.x = 0;
+ vt.y++;
+ check_scroll = 1;
+ }
+ 7 =>
+ VT_BEEP(vt);
+ 11 =>
+ vt.x = 0;
+ vt.y = vt.y1;
+ 12 =>
+ VT_CLEAR(vt,0,vt.y1,vt.wid-1,vt.y2);
+ 27 =>
+ vt.esc++;
+ 133 =>
+ vt.x = 0;
+ vt.y++;
+ check_scroll = 1;
+ 132 =>
+ vt.y++;
+ check_scroll = 1;
+ 136 => # XXX - set a tabstop
+ ;
+ 141 =>
+ vt.y--;
+ check_scroll = 1;
+ 142 => # XXX -- map G2 into GL for next char only
+ ;
+ 143 => # XXX -- map G3 into GL for next char
+ ;
+ 144 => # XXX -- device control string
+ ;
+ 145 => # XXX -- start of string - ignored
+ ;
+ 146 => # XXX -- device attribute request
+ ;
+ 147 =>
+ vt.esc = 2;
+ vt.etype = '[';
+ vt.esc++;
+ vt.value = 0;
+ vt.pcount = 0;
+ vt.ptype = 1;
+ for(n=0; n<VT_MAXPARAM; n++)
+ vt.param[n] = 0;
+ * =>
+ if(vt.mode & (1<<4))
+ VT_SCROLL_RIGHT(vt,vt.x,vt.y,
+ vt.wid-1,vt.y,1);
+ if(ch>=32 || ch <=126) {
+ if(vt.qmode & (1<<15)) {
+ if(vt.x >= vt.wid-1 && (vt.qmode & (1<<7))) {
+ vt.x = 0;
+ vt.y += vt.dy;
+ vt_checkscroll(vt, s[i:]);
+ }
+ vt.qmode &= ~(1<<15);
+ }
+ VT_PUTCHAR(vt,vt.x,vt.y,ch);
+ if((vt.x += vt.dx) >= vt.wid) {
+ vt.x = vt.wid-1;
+ vt.qmode |= (1<<15);
+ }
+ }
+ }
+ }
+ if(check_scroll)
+ vt_checkscroll(vt, s[i:]);
+ if(vt.x < 0)
+ vt.x = 0;
+ else if(vt.x >= vt.wid)
+ vt.x = vt.wid-1;
+ if(vt.y < 0)
+ vt.y = 0;
+ else if(vt.y >= vt.hgt)
+ vt.y = vt.hgt-1;
+ }
+ VT_SET_CURSOR(vt, vt.x, vt.y);
+}
+
+
+
+
+vt_call_csi(vt: ref Vt, ch: int): int
+{
+ i, n: int;
+ case ch {
+ 'A' =>
+ vt.y -= vt_param(vt, 1,1,1,vt.hgt);
+ 'B' =>
+ vt.y += vt_param(vt, 1,1,1,vt.hgt);
+ 'C' =>
+ vt.x += vt_param(vt, 1,1,1,vt.wid);
+ 'D' =>
+ vt.x -= vt_param(vt, 1,1,1,vt.wid);
+ 'f' or 'H' =>
+ vt.y = vt_param(vt, 0,1,1,vt.hgt)-1;
+ vt.x = vt_param(vt, 1,1,1,vt.wid)-1;
+ 'J' =>
+ case vt.param[0] {
+ 0 => VT_CLEAR(vt,vt.x,vt.y,vt.wid-1,vt.y);
+ VT_CLEAR(vt,0,vt.y+1,vt.wid-1,vt.y2);
+ 1 => VT_CLEAR(vt,0,0,vt.wid-1,vt.y-1);
+ VT_CLEAR(vt,0,vt.y,vt.x,vt.y);
+ 2 => VT_CLEAR(vt,0,vt.y1,vt.wid-1,vt.y2);
+ }
+ 'K' =>
+ case vt.param[0] {
+ 0 => VT_CLEAR(vt,vt.x,vt.y,vt.wid-1,vt.y);
+ 1 => VT_CLEAR(vt,0,vt.y,vt.x,vt.y);
+ 2 => VT_CLEAR(vt,0,vt.y,vt.wid-1,vt.y);
+ }
+ 'L' =>
+ n = vt_param(vt, 0,1,1,vt.hgt);
+ VT_SCROLL_DOWN(vt,0,vt.y,vt.wid-1,vt.y2,n);
+ 'M' =>
+ n = vt_param(vt,0,1,1,vt.hgt);
+ VT_SCROLL_UP(vt,0,vt.y,vt.wid-1,vt.y2,n);
+ '@' =>
+ n = vt_param(vt,0,1,1,vt.wid-1-vt.x);
+ VT_SCROLL_RIGHT(vt,vt.x,vt.y,vt.wid-1,vt.y,n);
+ 'P' =>
+ n = vt_param(vt,0,1,1,vt.wid-1-vt.x);
+ VT_SCROLL_LEFT(vt,vt.x,vt.y,vt.wid-1,vt.y,n);
+ 'X' =>
+ n = vt_param(vt,0,1,1,vt.wid-1-vt.x);
+ VT_CLEAR(vt,vt.x,vt.y,vt.x+n-1,vt.y);
+ 'm' =>
+ if(vt.pcount == 0)
+ vt.pcount++;
+ for(i=0; i<vt.pcount; i++) {
+ n = vt.param[i];
+ if(!n) {
+ vt.attr = 0;
+ vt.fg = 7;
+ vt.bg = 0;
+ } else if (n < 16)
+ vt.attr |= (1<<n);
+ else if (n < 28)
+ vt.attr &= ~(1<<(n-20));
+ else if (n < 38)
+ vt.fg = n-30;
+ else if (n < 48)
+ vt.bg = n-40;
+ else if (n < 58)
+ vt.fg = n-50+8;
+ else if (n < 68)
+ vt.bg = n-60+8;
+ }
+ VT_SET_COLOR(vt);
+ 'c' =>
+ if(vt.wid >= 132)
+ VT_TYPE(vt, "\u001b[?61;1;6c");
+ else
+ VT_TYPE(vt, "\u001b[?61;6c");
+ 'n' =>
+ n = vt_param(vt, 0,0,0,9);
+ if(n == 5)
+ VT_TYPE(vt, "\u001b[0n");
+ if(n == 5 || n == 6)
+ VT_TYPE(vt, sprint("\u001b[%d;%dR",vt.y+1,vt.x+1));
+ 'r' =>
+ vt.y1 = vt_param(vt, 0,1,1,vt.hgt)-1;
+ vt.y2 = vt_param(vt, 1,vt.hgt,1,vt.hgt)-1;
+ 's' =>
+ vt_save_state(vt);
+ 'u' =>
+ vt_restore_state(vt);
+ 'h' =>
+ for(i=0; i<vt.pcount; i++) {
+ n = vt.param[i];
+ if(n >= 0)
+ vt.mode |= (1<<n);
+ else
+ vt.qmode |= (1<<(-n));
+ }
+ 'l' =>
+ for(i=0; i<vt.pcount; i++) {
+ n = vt.param[i];
+ if(n >= 0)
+ vt.mode &= ~(1<<n);
+ else
+ vt.qmode &= ~(1<<(-n));
+ }
+ }
+
+ if(vt.y < 0)
+ vt.y = 0;
+ if(vt.y >= vt.hgt)
+ vt.y = vt.hgt-1;
+ if(vt.x < 0)
+ vt.x = 0;
+ if(vt.x >= vt.wid)
+ vt.x = vt.wid-1;
+ return 0;
+}
+
+vt_call_ncsi(vt: ref Vt, ch: int): int
+{
+ case ch {
+ 'E' =>
+ vt.x = 0;
+ '9' =>
+ ;
+ 'D' =>
+ vt.y++;
+ return 1;
+ 'H' => # XXX -- horizontal tab set
+ ;
+ '6' =>
+ ;
+ 'M' =>
+ vt.y--;
+ return 1;
+ '7' =>
+ vt_save_state(vt);
+ '8' =>
+ vt_restore_state(vt);
+ '=' =>
+ ;
+ '>' =>
+ ;
+ '#' =>
+ ;
+ '(' =>
+ ;
+ ')' =>
+ ;
+ }
+ return 0;
+}
+
+
+vt_param(vt: ref Vt, n: int, def: int, min, max: int): int
+{
+ param := vt.param[n];
+ if(param == 0)
+ param = def;
+ if(param < min)
+ param = min;
+ if(param > max)
+ param = max;
+ return param;
+}
+
+#############################################################################
+
+
+consinp(cs: chan of string, cr: chan of (int, int, int, Sys->Rread))
+{
+ for(;;) {
+ alt {
+ sq += <- cs => ;
+
+ (nil, nbytes, nil, rc) := <- cr =>
+ p := 0;
+ for(;;) {
+ if(raw)
+ p = len sq;
+ else
+ forloop:
+ for(i := 0; i < len sq; i++) {
+ case sq[i] {
+ '\b' =>
+ if(i > 0) {
+ sq = sq[0:i-1] + sq[i+1:];
+ --i;
+ }
+ '\n' =>
+ p = i+1;
+ break forloop;
+ }
+ }
+ if(p > 0)
+ break;
+ sq += <- cs;
+ }
+ if(nbytes > p)
+ nbytes = p;
+ alt {
+ rc <-= (array of byte sq[0:nbytes], "") =>
+ sq = sq[nbytes:];
+ * => ;
+ }
+ }
+ }
+}
+
+newsh(ctxt: ref Draw->Context, ioc: chan of (int, ref Sys->FileIO, ref Sys->FileIO))
+{
+ pid := sys->pctl(sys->NEWFD, nil);
+
+ sh := load Command "/dis/sh.dis";
+ if(sh == nil) {
+ ioc <-= (0, nil, nil);
+ return;
+ }
+
+ tty := "cons."+string pid;
+
+ sys->bind("#s","/chan",sys->MBEFORE);
+ fio := sys->file2chan("/chan", tty);
+ fioctl := sys->file2chan("/chan", tty + "ctl");
+ ioc <-= (pid, fio, fioctl);
+ if ((fio == nil) || (fioctl == nil))
+ return;
+
+ sys->bind("/chan/"+tty, "/dev/cons", sys->MREPL);
+ sys->bind("/chan/"+tty+"ctl", "/dev/consctl", sys->MREPL);
+
+ fd0 := sys->open("/dev/cons", sys->OREAD|sys->ORCLOSE);
+ fd1 := sys->open("/dev/cons", sys->OWRITE);
+ fd2 := sys->open("/dev/cons", sys->OWRITE);
+
+ sh->init(ctxt, "sh" :: "-n" :: nil);
+}
+
+kill(pid: int)
+{
+ fd := sys->open("#p/"+string pid+"/ctl", sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "killgrp");
+}
+
+tkcmds(t: ref Tk->Toplevel, cfg: array of string)
+{
+ for(i := 0; i < len cfg; i++)
+ tk->cmd(t, cfg[i]);
+}
diff --git a/appl/wm/wish.b b/appl/wm/wish.b
new file mode 100644
index 00000000..d5d2f353
--- /dev/null
+++ b/appl/wm/wish.b
@@ -0,0 +1,165 @@
+implement wish;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "bufio.m";
+ bufmod : Bufio;
+Iobuf : import bufmod;
+
+include "../lib/tcl.m";
+ tcl : Tcl_Core;
+
+wish : module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+
+
+menubut : chan of string;
+keyboard,mypid : int;
+
+Wwsh : ref Tk->Toplevel;
+
+init(ctxt: ref Draw->Context, argv: list of string) {
+ sys = load Sys Sys->PATH;
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "wish: no window context\n");
+ raise "fail:bad context";
+ }
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient= load Tkclient Tkclient->PATH;
+ bufmod = load Bufio Bufio->PATH;
+ if (tk==nil || tkclient==nil || bufmod==nil){
+ sys->print("Load Error: %r\n");
+ exit;
+ }
+ tcl=load Tcl_Core Tcl_Core->PATH;
+ if (tcl==nil){
+ sys->print("Cannot load Tcl (%r)\n");
+ exit;
+ }
+ keyboard=1;
+ argv = tl argv;
+ if (argv!=nil)
+ file:=parse_args(argv);
+ geom:="";
+ mypid=sys->pctl(sys->NEWPGRP, nil);
+ tkclient->init();
+ Wshinit(ctxt, geom);
+ tcl->init(ctxt,argv);
+ tcl->set_top(Wwsh);
+ shellit(file);
+}
+
+
+
+
+
+parse_args(argv : list of string) : string {
+ while (argv!=nil){
+ case (hd argv){
+ "-k" =>
+ keyboard=0;
+ "-f" =>
+ argv = tl argv;
+ return hd argv;
+ * =>
+ return nil;
+ }
+ argv = tl argv;
+ }
+ return nil;
+}
+
+shellit(file:string){
+ drag:=chan of string;
+ tk->namechan(Wwsh, drag, "Wm_drag");
+ lines:=chan of string;
+ Tcl_Chan:=chan of string;
+ tk->namechan(Wwsh, lines, "lines");
+ tk->namechan(Wwsh, Tcl_Chan, "Tcl_Chan");
+ new_inp:="wish%";
+ unfin:="wish>";
+ line : string;
+ loadfile(file);
+ quiet:=0;
+ if (keyboard)
+ spawn tcl->grab_lines(new_inp,unfin,lines);
+ for(;;){
+ alt{
+ s := <-drag =>
+ if(len s < 6 || s[0:5] != "path=")
+ break;
+ loadfile(s[5:]);
+ sys->print("%s ",new_inp);
+ line = <-lines =>
+ line = tcl->prepass(line);
+ msg:= tcl->evalcmd(line,0);
+ if (msg!=nil)
+ sys->print("%s\n",msg);
+ sys->print("%s ", new_inp);
+ tcl->clear_error();
+ rline := <-Tcl_Chan =>
+ rline = tcl->prepass(rline);
+ msg:= tcl->evalcmd(rline,0);
+ if (msg!=nil)
+ sys->print("%s\n",msg);
+ tcl->clear_error();
+ menu := <-menubut =>
+ if(menu == "exit"){
+ kfd := sys->open("#p/"+string mypid+"/ctl", sys->OWRITE);
+ if(kfd == nil)
+ sys->print("error opening pid %d (%r)\n",mypid);
+ sys->fprint(kfd, "killgrp");
+ exit;
+ }
+ tkclient->wmctl(Wwsh, menu);
+ }
+ }
+}
+
+
+
+loadfile(file :string) {
+ iob : ref Iobuf;
+ line,input : string;
+ line = "";
+ if (file==nil)
+ return;
+ iob = bufmod->open(file,bufmod->OREAD);
+ if (iob==nil){
+ sys->print("File %s cannot be opened for reading",file);
+ return;
+ }
+ while((input=iob.gets('\n'))!=nil){
+ line+=input;
+ if (tcl->finished(line,0)){
+ line = tcl->prepass(line);
+ msg:= tcl->evalcmd(line,0);
+ if (msg!=nil)
+ sys->print("%s\n",msg);
+ tcl->clear_error();
+ line=nil;
+ }
+ }
+}
+
+Wshinit(ctxt: ref Draw->Context, geom: string) {
+ (Wwsh, menubut) = tkclient->toplevel(ctxt, geom,
+ "WishPad",Tkclient->Appl);
+ cmd := chan of string;
+ tk->namechan(Wwsh, cmd, "wsh");
+ tk->cmd(Wwsh, "update");
+}
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);
+}
diff --git a/appl/wm/wmdeb.m b/appl/wm/wmdeb.m
new file mode 100644
index 00000000..378740e9
--- /dev/null
+++ b/appl/wm/wmdeb.m
@@ -0,0 +1,82 @@
+Diss: module {};
+
+DebSrc: module
+{
+ PATH: con "/dis/wm/debsrc.dis";
+
+ Mod: adt
+ {
+ src: string; # .b path
+ tk: string; # text widget
+ dis: string; # .dis path
+ sym: ref Sym; # debugger symbol table
+ srcask: int; # look for src file?
+ symask: int; # look for symbol file?
+ };
+
+ loadsrc: fn(src: string, addpath: int): ref Mod;
+ showstrsrc: fn(src: string);
+ search: fn(s: string): int;
+ snarf: fn(): string;
+ getsel: fn(): (ref Mod, int);
+ attachdis: fn(m: ref Mod): int;
+ attachsym: fn(m: ref Mod);
+ showmodsrc: fn(m: ref Mod, src: ref Src);
+ findmod: fn(m: ref Module): ref Mod;
+
+ init: fn(ctxt: ref Draw->Context, t: ref Tk->Toplevel,
+ tkclient: Tkclient, selectfile: Selectfile, dialog: Dialog,
+ str: String, debug: Debug, xscroll: int, remcr: int);
+ reinit: fn(xscroll: int, remcr: int);
+
+ packed: ref Mod;
+ searchpath: array of string;
+ opendir: string;
+};
+
+DebData: module
+{
+ PATH: con "/dis/wm/debdata.dis";
+
+ Datum: adt
+ {
+ tkid: string;
+ parent: string; # tkid of parent
+ vtk: string; # root tk name
+ e: ref Exp;
+ val: string; # value displayed on screen
+ canwalk: int; # can the variable be expanded?
+ kids: cyclic array of ref Datum; # list of expanded kids
+
+ expand: fn(d: self ref Datum, okids: array of ref Datum, who: string): ref Datum;
+ contract: fn(d: self ref Datum, who: string): ref Datum;
+ destroy: fn(d: self ref Datum);
+ showsrc: fn(d: self ref Datum);
+ };
+
+ Vars: adt
+ {
+ tk: string; # root tk widget
+ xbar: int; # x coord of var/val dividing line
+ d: array of ref Datum; # displayed variables
+
+ create: fn(): ref Vars;
+ delete: fn(v: self ref Vars);
+ show: fn(v: self ref Vars);
+ refresh: fn(v: self ref Vars, e: array of ref Debug->Exp);
+
+ expand: fn(v: self ref Vars, kid: string);
+ contract: fn(v: self ref Vars, kid: string);
+ showsrc: fn(v: self ref Vars, kid: string);
+ update: fn(v: self ref Vars);
+ scrolly: fn(v: self ref Vars, s: string);
+ };
+
+ ctl: fn(s: string);
+ wmctl: fn(s: string);
+ init: fn(ctxt: ref Draw->Context, geom: string,
+ debsrc: DebSrc,
+ str: String, debug: Debug):
+ (ref Tk->Toplevel, chan of string, chan of string);
+ raisex: fn();
+};
diff --git a/appl/wm/wmplay.b b/appl/wm/wmplay.b
new file mode 100644
index 00000000..83becacf
--- /dev/null
+++ b/appl/wm/wmplay.b
@@ -0,0 +1,176 @@
+implement WmPlay;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Context: import draw;
+ gctxt: ref Context;
+
+include "tk.m";
+ tk: Tk;
+
+include "tkclient.m";
+ tkclient: Tkclient;
+
+include "dialog.m";
+ dialog: Dialog;
+
+include "selectfile.m";
+ selectfile: Selectfile;
+
+tpid: int;
+ppid: int;
+Magic: con "rate";
+data: con "/dev/audio";
+ctl: con "/dev/audioctl";
+buffz: con Sys->ATOMICIO;
+top: ref Tk->Toplevel;
+
+WmPlay: module
+{
+ init: fn(ctxt: ref Context, argv: list of string);
+};
+
+notecmd := array[] of {
+ "frame .f",
+ "label .f.l -bitmap error -foreground red",
+ "button .b -text Continue -command {send cmd done}",
+ "focus .f",
+ "bind .f <Key-\n> {send cmd done}",
+ "pack .f.l .f.m -side left -expand 1 -padx 10 -pady 10",
+ "pack .f .b -padx 10 -pady 10",
+ "update; cursor -default"
+};
+
+notice(message: string)
+{
+ dialog->prompt(gctxt, top.image, "error -fg red", "Error", message, 0, "OK"::nil);
+}
+
+play(f: string)
+{
+ ppid = sys->pctl(0, nil);
+ buff := array[buffz] of byte;
+ inf := sys->open(f, Sys->OREAD);
+ if (inf == nil) {
+ notice(sys->sprint("could not open %s: %r", f));
+ return;
+ }
+ n := sys->read(inf, buff, buffz);
+ if (n < 0) {
+ notice(sys->sprint("could not read %s: %r", f));
+ return;
+ }
+ if (n < 10 || string buff[0:4] != Magic) {
+ notice(sys->sprint("%s: not an audio file", f));
+ return;
+ }
+ i := 0;
+ for (;;) {
+ if (i == n) {
+ notice(sys->sprint("%s: bad header", f));
+ return;
+ }
+ if (buff[i] == byte '\n') {
+ i++;
+ if (i == n) {
+ notice(sys->sprint("%s: bad header", f));
+ return;
+ }
+ if (buff[i] == byte '\n') {
+ i++;
+ if ((i % 4) != 0) {
+ notice(sys->sprint("%s: unpadded header", f));
+ return;
+ }
+ break;
+ }
+ }
+ else
+ i++;
+ }
+ df := sys->open(data, Sys->OWRITE);
+ if (df == nil) {
+ notice(sys->sprint("could not open %s: %r", data));
+ return;
+ }
+ cf := sys->open(ctl, Sys->OWRITE);
+ if (cf == nil) {
+ notice(sys->sprint("could not open %s: %r", ctl));
+ return;
+ }
+ if (sys->write(cf, buff, i - 1) < 0) {
+ notice(sys->sprint("could not write %s: %r", ctl));
+ return;
+ }
+ if (n > i && sys->write(df, buff[i:n], n - i) < 0) {
+ notice(sys->sprint("could not write %s: %r", data));
+ return;
+ }
+ if (sys->stream(inf, df, Sys->ATOMICIO) < 0) {
+ notice(sys->sprint("could not stream %s: %r", data));
+ return;
+ }
+}
+
+doplay(f: string)
+{
+ play(f);
+ kill(tpid);
+}
+
+init(ctxt: ref Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ if (ctxt == nil) {
+ sys->fprint(sys->fildes(2), "wmplay: no window context\n");
+ raise "fail:bad context";
+ }
+ draw = load Draw Draw->PATH;
+ tk = load Tk Tk->PATH;
+ tkclient = load Tkclient Tkclient->PATH;
+ dialog = load Dialog Dialog->PATH;
+ selectfile = load Selectfile Selectfile->PATH;
+
+ gctxt = ctxt;
+ sys->pctl(Sys->NEWPGRP, nil);
+ tkclient->init();
+ dialog->init();
+ selectfile->init();
+
+ file: string;
+ argv = tl argv;
+ if (argv != nil)
+ file = hd argv;
+ else {
+ file = selectfile->filename(ctxt, nil, "Locate Audio File", "*.iaf"::"*.wav"::nil, "");
+ if (file == "")
+ exit;
+ }
+
+ (t, menubut) := tkclient->toplevel(ctxt, "-borderwidth 2 -relief raised", "Play", 0);
+ tk->cmd(t, "label .d -label {" + file + "}");
+ tk->cmd(t, "pack .Wm_t -fill x; pack .d; pack propagate . 0");
+ tk->cmd(t, "update");
+ top = t;
+ tpid = sys->pctl(0, nil);
+ spawn doplay(file);
+
+ for(;;) {
+ menu := <- menubut;
+ if(menu == "exit") {
+ kill(ppid);
+ return;
+ }
+ tkclient->wmctl(t, menu);
+ }
+}
+
+kill(pid: int)
+{
+ fd := sys->open("/prog/" + string pid + "/ctl", sys->OWRITE);
+ if (fd != nil)
+ sys->fprint(fd, "kill");
+}