summaryrefslogtreecommitdiff
path: root/appl/collab/servers
diff options
context:
space:
mode:
Diffstat (limited to 'appl/collab/servers')
-rw-r--r--appl/collab/servers/chatsrv.b263
-rw-r--r--appl/collab/servers/memfssrv.b20
-rw-r--r--appl/collab/servers/mpx.b301
-rw-r--r--appl/collab/servers/wbsrv.b226
4 files changed, 810 insertions, 0 deletions
diff --git a/appl/collab/servers/chatsrv.b b/appl/collab/servers/chatsrv.b
new file mode 100644
index 00000000..072c0617
--- /dev/null
+++ b/appl/collab/servers/chatsrv.b
@@ -0,0 +1,263 @@
+implement Service;
+
+#
+# simple text-based chat service
+#
+
+include "sys.m";
+ sys: Sys;
+ Qid: import Sys;
+
+include "styx.m";
+ styx: Styx;
+ Tmsg, Rmsg: import Styx;
+
+include "styxservers.m";
+ styxservers: Styxservers;
+ Styxserver, Navigator: import styxservers;
+ nametree: Nametree;
+ Tree: import nametree;
+
+include "../service.m";
+
+Qdir, Qusers, Qmsgs: con iota;
+
+tc: chan of ref Tmsg;
+srv: ref Styxserver;
+
+user := "inferno";
+
+dir(name: string, perm: int, path: int): Sys->Dir
+{
+ d := sys->zerodir;
+ d.name = name;
+ d.uid = user;
+ d.gid = user;
+ d.qid.path = big path;
+ if(perm & Sys->DMDIR)
+ d.qid.qtype = Sys->QTDIR;
+ else
+ d.qid.qtype = Sys->QTFILE;
+ d.mode = perm;
+ return d;
+}
+
+init(nil: list of string): (string, string, ref Sys->FD)
+{
+ sys = load Sys Sys->PATH;
+ styx = load Styx Styx->PATH;
+ if(styx == nil)
+ return (sys->sprint("can't load %s: %r", Styx->PATH), nil, nil);
+ styxservers = load Styxservers Styxservers->PATH;
+ if(styxservers == nil)
+ return (sys->sprint("can't load %s: %r", Styxservers->PATH), nil, nil);
+ nametree = load Nametree Nametree->PATH;
+ if(nametree == nil)
+ return (sys->sprint("can't load %s: %r", Nametree->PATH), nil, nil);
+ styx->init();
+ styxservers->init(styx);
+ nametree->init();
+
+ (tree, treeop) := nametree->start();
+ tree.create(big Qdir, dir(".", Sys->DMDIR|8r555, Qdir));
+ tree.create(big Qdir, dir("users", 8r444, Qusers));
+ tree.create(big Qdir, dir("msgs", 8r666, Qmsgs));
+
+ p := array [2] of ref Sys->FD;
+ if (sys->pipe(p) < 0){
+ tree.quit();
+ return (sys->sprint("cannot create pipe: %r"), nil, nil);
+ }
+
+ nextmsg = ref Msg (0, nil, nil, nil);
+
+ (tc, srv) = Styxserver.new(p[1], Navigator.new(treeop), big Qdir);
+ spawn chatsrv(tree);
+
+ return (nil, "/", p[0]);
+}
+
+chatsrv(tree: ref Tree)
+{
+ while((tmsg := <-tc) != nil){
+ pick tm := tmsg {
+ Readerror =>
+ break;
+ Flush =>
+ cancelpending(tm.tag);
+ srv.reply(ref Rmsg.Flush(tm.tag));
+ Open =>
+ c := srv.open(tm);
+ if (c == nil)
+ break;
+ if (int c.path == Qmsgs){
+ newmsgclient(tm.fid, c.uname);
+ #root[0].qid.vers++; # TO DO
+ }
+ Read =>
+ c := srv.getfid(tm.fid);
+ if (c == nil || !c.isopen) {
+ srv.reply(ref Rmsg.Error(tm.tag, Styxservers->Ebadfid));
+ break;
+ }
+ case int c.path {
+ Qdir =>
+ srv.read(tm);
+ Qmsgs =>
+ mc := getmsgclient(tm.fid);
+ if (mc == nil) {
+ srv.reply(ref Rmsg.Error(tm.tag, "internal error -- lost client"));
+ continue;
+ }
+ tm.offset = big 0;
+ msg := getnextmsg(mc);
+ if (msg == nil) {
+ if(mc.pending != nil)
+ srv.reply(ref Rmsg.Error(tm.tag, "read already pending"));
+ else
+ mc.pending = tm;
+ continue;
+ }
+ srv.reply(styxservers->readstr(tm, msg));
+ Qusers =>
+ srv.reply(styxservers->readstr(tm, usernames()));
+ * =>
+ srv.reply(ref Rmsg.Error(tm.tag, "phase error -- bad path"));
+ }
+ Write =>
+ c := srv.getfid(tm.fid);
+ if (c == nil || !c.isopen) {
+ srv.reply(ref Rmsg.Error(tm.tag, Styxservers->Ebadfid));
+ continue;
+ }
+ if (int c.path != Qmsgs) {
+ srv.reply(ref Rmsg.Error(tm.tag, Styxservers->Eperm));
+ continue;
+ }
+ writemsgclients(tm.fid, c.uname, string tm.data);
+ srv.reply(ref Rmsg.Write(tm.tag, len tm.data));
+ Clunk =>
+ c := srv.clunk(tm);
+ if (c != nil && int c.path == Qmsgs){
+ closemsgclient(tm.fid);
+ # root[0].qid.vers++; # TO DO
+ }
+ * =>
+ srv.default(tmsg);
+ }
+ }
+ tree.quit();
+ sys->print("chatsrv exit\n");
+}
+
+Msg: adt {
+ fromfid: int;
+ from: string;
+ msg: string;
+ next: cyclic ref Msg;
+};
+
+Msgclient: adt {
+ fid: int;
+ name: string;
+ nextmsg: ref Msg;
+ pending: ref Tmsg.Read;
+ next: cyclic ref Msgclient;
+};
+
+nextmsg: ref Msg;
+msgclients: ref Msgclient;
+
+usernames(): string
+{
+ s := "";
+ for (c := msgclients; c != nil; c = c.next)
+ s += c.name+"\n";
+ return s;
+}
+
+newmsgclient(fid: int, name: string)
+{
+ writemsgclients(fid, nil, "+++ " + name + " has arrived");
+ msgclients = ref Msgclient(fid, name, nextmsg, nil, msgclients);
+}
+
+getmsgclient(fid: int): ref Msgclient
+{
+ for (c := msgclients; c != nil; c = c.next)
+ if (c.fid == fid)
+ return c;
+ return nil;
+}
+
+cancelpending(tag: int)
+{
+ for (c := msgclients; c != nil; c = c.next)
+ if((tm := c.pending) != nil && tm.tag == tag){
+ c.pending = nil;
+ break;
+ }
+}
+
+closemsgclient(fid: int)
+{
+ prev: ref Msgclient;
+ s := "";
+ for (c := msgclients; c != nil; c = c.next) {
+ if (c.fid == fid) {
+ if (prev == nil)
+ msgclients = c.next;
+ else
+ prev.next = c.next;
+ s = "--- " + c.name + " has left";
+ break;
+ }
+ prev = c;
+ }
+ if (s != nil)
+ writemsgclients(fid, nil, s);
+}
+
+writemsgclients(fromfid: int, from: string, msg: string)
+{
+ nm := ref Msg(0, nil, nil, nil);
+ nextmsg.fromfid = fromfid;
+ nextmsg.from = from;
+ nextmsg.msg = msg;
+ nextmsg.next = nm;
+
+ for (c := msgclients; c != nil; c = c.next) {
+ if (c.pending != nil) {
+ s := msgtext(c, nextmsg);
+ srv.reply(styxservers->readstr(c.pending, s));
+ c.pending = nil;
+ c.nextmsg = nm;
+ }
+ }
+ nextmsg = nm;
+}
+
+getnextmsg(mc: ref Msgclient): string
+{
+# uncomment next two lines to eliminate queued messages to self
+# while(mc.nextmsg.next != nil && mc.nextmsg.fromfid == mc.fid)
+# mc.nextmsg = mc.nextmsg.next;
+ if ((m := mc.nextmsg).next != nil){
+ mc.nextmsg = m.next;
+ return msgtext(mc, m);
+ }
+ return nil;
+}
+
+msgtext(mc: ref Msgclient, m: ref Msg): string
+{
+ prefix := "";
+ if (m.from != nil) {
+ # not a system message
+ if (mc.fid == m.fromfid)
+ prefix = "<you>: ";
+ else
+ prefix = m.from + ": ";
+ }
+ return prefix + m.msg;
+}
diff --git a/appl/collab/servers/memfssrv.b b/appl/collab/servers/memfssrv.b
new file mode 100644
index 00000000..8c44cd5d
--- /dev/null
+++ b/appl/collab/servers/memfssrv.b
@@ -0,0 +1,20 @@
+implement Service;
+
+include "sys.m";
+include "../service.m";
+include "memfs.m";
+
+init(nil : list of string) : (string, string, ref Sys->FD)
+{
+ sys := load Sys Sys->PATH;
+ memfs := load MemFS MemFS->PATH;
+ if (memfs == nil) {
+ err := sys->sprint("cannot load %s: %r", MemFS->PATH);
+ return (err, nil, nil);
+ }
+ err := memfs->init();
+ if (err != nil)
+ return (err, nil, nil);
+ fd := memfs->newfs(1024 * 512);
+ return (nil, "/", fd);
+}
diff --git a/appl/collab/servers/mpx.b b/appl/collab/servers/mpx.b
new file mode 100644
index 00000000..ea0c2c1c
--- /dev/null
+++ b/appl/collab/servers/mpx.b
@@ -0,0 +1,301 @@
+implement Service;
+
+#
+# 1 to many and many to 1 multiplexor
+#
+
+include "sys.m";
+ sys: Sys;
+ Qid: import Sys;
+
+include "styx.m";
+ styx: Styx;
+ Tmsg, Rmsg: import styx;
+
+include "styxservers.m";
+ styxservers: Styxservers;
+ Styxserver, Navigator: import styxservers;
+ nametree: Nametree;
+ Tree: import nametree;
+
+include "service.m";
+
+include "messages.m";
+ messages: Messages;
+ Msg, Msglist, Readreq, User: import messages;
+
+Qdir, Qroot, Qusers, Qleaf: con iota;
+
+srv: ref Styxserver;
+clientidgen := 0;
+
+Einactive: con "not currently active";
+
+toleaf: ref Msglist;
+toroot: ref Msglist;
+userlist: list of ref User;
+
+user := "inferno";
+
+dir(name: string, perm: int, path: int): Sys->Dir
+{
+ d := sys->zerodir;
+ d.name = name;
+ d.uid = user;
+ d.gid = user;
+ d.qid.path = big path;
+ if(perm & Sys->DMDIR)
+ d.qid.qtype = Sys->QTDIR;
+ else
+ d.qid.qtype = Sys->QTFILE;
+ d.mode = perm;
+ return d;
+}
+
+init(nil: list of string): (string, string, ref Sys->FD)
+{
+ sys = load Sys Sys->PATH;
+ styx = load Styx Styx->PATH;
+ if(styx == nil)
+ return (sys->sprint("can't load %s: %r", Styx->PATH), nil, nil);
+ styxservers = load Styxservers Styxservers->PATH;
+ if(styxservers == nil)
+ return (sys->sprint("can't load %s: %r", Styxservers->PATH), nil, nil);
+ nametree = load Nametree Nametree->PATH;
+ if(nametree == nil)
+ return (sys->sprint("can't load %s: %r", Nametree->PATH), nil, nil);
+ styx->init();
+ styxservers->init(styx);
+styxservers->traceset(1);
+ nametree->init();
+ messages = load Messages Messages->PATH;
+ if(messages == nil)
+ return (sys->sprint("can't load %s: %r", Messages->PATH), nil, nil);
+
+ (tree, treeop) := nametree->start();
+ tree.create(big Qdir, dir(".", Sys->DMDIR|8r555, Qdir));
+ tree.create(big Qdir, dir("leaf", 8r666, Qleaf));
+ tree.create(big Qdir, dir("root", 8r666, Qroot));
+ tree.create(big Qdir, dir("users", 8r444, Qusers));
+
+ p := array [2] of ref Sys->FD;
+ if (sys->pipe(p) < 0){
+ tree.quit();
+ return (sys->sprint("can't create pipe: %r"), nil, nil);
+ }
+
+ toleaf = Msglist.new();
+ toroot = Msglist.new();
+
+ tc: chan of ref Tmsg;
+ (tc, srv) = Styxserver.new(p[1], Navigator.new(treeop), big Qdir);
+ spawn mpx(tc, tree);
+
+ return (nil, "/", p[0]);
+}
+
+mpx(tc: chan of ref Tmsg, tree: ref Tree)
+{
+ root: ref User;
+ while((tmsg := <-tc) != nil){
+ pick tm := tmsg {
+ Readerror =>
+ break;
+ Open =>
+ c := srv.getfid(tm.fid);
+ if(c == nil || c.isopen){
+ srv.reply(ref Rmsg.Error(tm.tag, Styxservers->Ebadfid));
+ continue;
+ }
+ case int c.path {
+ Qroot =>
+ if(root != nil){
+ srv.reply(ref Rmsg.Error(tm.tag, sys->sprint("interaction already directed by %s", root.name)));
+ continue;
+ }
+ c = srv.open(tm);
+ if (c == nil)
+ continue;
+ root = ref User(0, tm.fid, c.uname, nil);
+ root.initqueue(toroot);
+ Qleaf =>
+ if(root == nil){
+ srv.reply(ref Rmsg.Error(tm.tag, Einactive));
+ continue;
+ }
+ c = srv.open(tm);
+ if (c == nil)
+ continue;
+ userarrives(tm.fid, c.uname);
+ # mpxdir[1].qid.vers++; # TO DO
+ * =>
+ srv.open(tm);
+ }
+ Read =>
+ c := srv.getfid(tm.fid);
+ if (c == nil || !c.isopen) {
+ srv.reply(ref Rmsg.Error(tm.tag, Styxservers->Ebadfid));
+ continue;
+ }
+ case int c.path {
+ Qdir =>
+ srv.read(tm);
+ Qroot =>
+ tm.offset = big 0;
+ m := qread(toroot, root, tm, 1);
+ if(m != nil)
+ srv.reply(ref Rmsg.Read(tm.tag, m.data));
+ Qleaf =>
+ u := fid2user(tm.fid);
+ if (u == nil) {
+ srv.reply(ref Rmsg.Error(tm.tag, "internal error -- lost user"));
+ continue;
+ }
+ tm.offset = big 0;
+ m := qread(toleaf, u, tm, 0);
+ if(m == nil){
+ if(root == nil)
+ srv.reply(ref Rmsg.Read(tm.tag, nil));
+ else
+ qread(toleaf, u, tm, 1); # put us on the wait queue
+ }else
+ srv.reply(ref Rmsg.Read(tm.tag, m.data));
+ Qusers =>
+ srv.reply(styxservers->readstr(tm, usernames()));
+ * =>
+ srv.reply(ref Rmsg.Error(tm.tag, "phase error -- bad path"));
+ }
+ Write =>
+ c := srv.getfid(tm.fid);
+ if (c == nil || !c.isopen) {
+ srv.reply(ref Rmsg.Error(tm.tag, Styxservers->Ebadfid));
+ continue;
+ }
+ case int c.path {
+ Qroot =>
+ qwrite(toleaf, msg(root, 'M', tm.data));
+ srv.reply(ref Rmsg.Write(tm.tag, len tm.data));
+ Qleaf =>
+ u := fid2user(tm.fid);
+ if(u == nil) {
+ srv.reply(ref Rmsg.Error(tm.tag, "internal error -- lost user"));
+ continue;
+ }
+ if(root == nil){
+ srv.reply(ref Rmsg.Error(tm.tag, Einactive));
+ continue;
+ }
+ qwrite(toroot, msg(u, 'm', tm.data));
+ srv.reply(ref Rmsg.Write(tm.tag, len tm.data));
+ * =>
+ srv.reply(ref Rmsg.Error(tm.tag, Styxservers->Eperm));
+ }
+ Flush =>
+ cancelpending(tm.tag);
+ srv.reply(ref Rmsg.Flush(tm.tag));
+ Clunk =>
+ c := srv.getfid(tm.fid);
+ if(c.isopen){
+ case int c.path {
+ Qroot =>
+ # shut down?
+ qwrite(toleaf, msg(root, 'L', nil));
+ root = nil;
+ Qleaf =>
+ userleaves(tm.fid);
+ # mpxdir[1].qid.vers++; # TO DO
+ }
+ }
+ * =>
+ srv.default(tmsg);
+ }
+ }
+ tree.quit();
+ sys->print("mpx exit\n");
+}
+
+mpxseqgen := 0;
+
+time(): int
+{
+ return ++mpxseqgen; # server time; assumes 2^31-1 is large enough
+}
+
+userarrives(fid: int, name: string)
+{
+ u := User.new(fid, name);
+ qwrite(toroot, msg(u, 'a', nil));
+ u.initqueue(toleaf); # sees leaf messages from now on
+ userlist = u :: userlist;
+}
+
+fid2user(fid: int): ref User
+{
+ for(ul := userlist; ul != nil; ul = tl ul)
+ if((u := hd ul).fid == fid)
+ return u;
+ return nil;
+}
+
+userleaves(fid: int)
+{
+ ul := userlist;
+ userlist = nil;
+ u: ref User;
+ for(; ul != nil; ul = tl ul)
+ if((hd ul).fid != fid)
+ userlist = hd ul :: userlist;
+ else
+ u = hd ul;
+ if(u != nil)
+ qwrite(toroot, msg(u, 'l', nil));
+}
+
+usernames(): string
+{
+ s := "";
+ for(ul := userlist; ul != nil; ul = tl ul){
+ u := hd ul;
+ s += string u.id+" "+u.name+"\n";
+ }
+ return s;
+}
+
+qwrite(msgs: ref Msglist, m: ref Msg)
+{
+ pending := msgs.write(m);
+ for(; pending != nil; pending = tl pending){
+ (u, req) := hd pending;
+ m = u.read(); # must succeed, or the code is wrong
+ data := m.data;
+ if(req.count < len data)
+ data = data[0:req.count];
+ srv.reply(ref Rmsg.Read(req.tag, data));
+ }
+}
+
+qread(msgs: ref Msglist, u: ref User, tm: ref Tmsg.Read, wait: int): ref Msg
+{
+ m := u.read();
+ if(m != nil){
+ if(tm.count < len m.data)
+ m.data = m.data[0:tm.count];
+ }else if(wait)
+ msgs.wait(u, ref Readreq(tm.tag, tm.fid, tm.count, tm.offset));
+ return m;
+}
+
+cancelpending(tag: int)
+{
+ toroot.flushtag(tag);
+ toleaf.flushtag(tag);
+}
+
+msg(u: ref User, op: int, data: array of byte): ref Msg
+{
+ a := sys->aprint("%ud %d %c %s ", time(), u.id, op, u.name);
+ m := ref Msg(u, array[len a + len data] of byte, nil);
+ m.data[0:] = a;
+ m.data[len a:] = data;
+ return m;
+}
diff --git a/appl/collab/servers/wbsrv.b b/appl/collab/servers/wbsrv.b
new file mode 100644
index 00000000..2492db7e
--- /dev/null
+++ b/appl/collab/servers/wbsrv.b
@@ -0,0 +1,226 @@
+implement Service;
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+ draw: Draw;
+ Chans, Display, Image, Rect, Point : import draw;
+
+include "../service.m";
+
+WBW : con 234;
+WBH : con 279;
+
+init(nil : list of string) : (string, string, ref Sys->FD)
+{
+ sys = load Sys Sys->PATH;
+ draw = load Draw Draw->PATH;
+ if (draw == nil)
+ return ("cannot load Draw module", nil, nil);
+
+ p := array [2] of ref Sys->FD;
+ if (sys->pipe(p) == -1)
+ return (sys->sprint("cannot create pipe: %r"), nil, nil);
+
+ display := Display.allocate(nil);
+ if (display == nil)
+ return (sys->sprint("cannot allocate display: %r"), nil, nil);
+
+ r := Rect(Point(0,0), Point(WBW, WBH));
+ wb := display.newimage(r, Draw->CMAP8, 0, Draw->White);
+ if (wb == nil)
+ return (sys->sprint("cannot allocate whiteboard image: %r"), nil, nil);
+
+ nextmsg = ref Msg (nil, nil);
+ spawn wbsrv(p[1], wb);
+ return (nil, "/chan", p[0]);
+}
+
+wbsrv(fd : ref Sys->FD, wb: ref Image)
+{
+ sys->pctl(Sys->FORKNS, nil);
+ sys->unmount(nil, "/chan");
+ sys->bind("#s", "/chan", Sys->MREPL);
+
+ bit := sys->file2chan("/chan", "wb.bit");
+ strokes := sys->file2chan("/chan", "strokes");
+
+ hangup := chan of int;
+ spawn export(fd, hangup);
+
+ nwbbytes := draw->bytesperline(wb.r, wb.depth) * wb.r.dy();
+ bithdr := sys->aprint("%11s %11d %11d %11d %11d ", wb.chans.text(), 0, 0, WBW, WBH);
+
+ for (;;) alt {
+ <-hangup =>
+ sys->print("whiteboard:hangup\n");
+ return;
+
+ (offset, count, fid, r) := <-bit.read =>
+ if (r == nil) {
+ closeclient(fid);
+ continue;
+ }
+ c := getclient(fid);
+ if (c == nil) {
+ # new client
+ c = newclient(fid);
+ data := array [len bithdr + nwbbytes] of byte;
+ data[0:] = bithdr;
+ wb.readpixels(wb.r, data[len bithdr:]);
+ c.bitdata = data;
+ }
+ if (offset >= len c.bitdata) {
+ rreply(r, (nil, nil));
+ continue;
+ }
+ rreply(r, (c.bitdata[offset:], nil));
+
+ (offset, data, fid, w) := <-bit.write =>
+ if (w != nil)
+ wreply(w, (0, "permission denied"));
+
+ (offset, count, fid, r) := <-strokes.read =>
+ if (r == nil) {
+ closeclient(fid);
+ continue;
+ }
+ c := getclient(fid);
+ if (c == nil) {
+ c = newclient(fid);
+ c.nextmsg = nextmsg;
+ }
+ d := c.nextmsg.data;
+ if (d == nil) {
+ c.pending = r;
+ c.pendlen = count;
+ continue;
+ }
+ c.nextmsg = c.nextmsg.next;
+ rreply(r, (d, nil));
+
+ (offset, data, fid, w) := <-strokes.write =>
+ if (w == nil) {
+ closeclient(fid);
+ continue;
+ }
+ err := drawstrokes(wb, data);
+ if (err != nil) {
+ wreply(w, (0, err));
+ continue;
+ }
+ wreply(w, (len data, nil));
+ writeclients(data);
+ }
+}
+
+rreply(rc: chan of (array of byte, string), reply: (array of byte, string))
+{
+ alt {
+ rc <-= reply =>;
+ * =>;
+ }
+}
+
+wreply(wc: chan of (int, string), reply: (int, string))
+{
+ alt {
+ wc <-= reply=>;
+ * =>;
+ }
+}
+
+export(fd : ref Sys->FD, done : chan of int)
+{
+ sys->export(fd, "/", Sys->EXPWAIT);
+ done <-= 1;
+}
+
+Msg : adt {
+ data : array of byte;
+ next : cyclic ref Msg;
+};
+
+Client : adt {
+ fid : int;
+ bitdata : array of byte; # bit file client
+ nextmsg : ref Msg; # strokes file client
+ pending : Sys->Rread;
+ pendlen : int;
+};
+
+nextmsg : ref Msg;
+clients : list of ref Client;
+
+newclient(fid : int) : ref Client
+{
+ c := ref Client(fid, nil, nil, nil, 0);
+ clients = c :: clients;
+ return c;
+}
+
+getclient(fid : int) : ref Client
+{
+ for(cl := clients; cl != nil; cl = tl cl)
+ if((c := hd cl).fid == fid)
+ return c;
+ return nil;
+}
+
+closeclient(fid : int)
+{
+ nl: list of ref Client;
+ for(cl := clients; cl != nil; cl = tl cl)
+ if((hd cl).fid != fid)
+ nl = hd cl :: nl;
+ clients = nl;
+}
+
+writeclients(data : array of byte)
+{
+ nm := ref Msg(nil, nil);
+ nextmsg.data = data;
+ nextmsg.next = nm;
+
+ for(cl := clients; cl != nil; cl = tl cl){
+ if ((c := hd cl).pending != nil) {
+ n := c.pendlen;
+ if (n > len data)
+ n = len data;
+ alt{
+ c.pending <-= (data[0:n], nil) => ;
+ * => ;
+ }
+ c.pending = nil;
+ c.nextmsg = nm;
+ }
+ }
+ nextmsg = nm;
+}
+
+# data: colour width p0 p1 pn*
+
+drawstrokes(wb: ref Image, data : array of byte) : string
+{
+ (n, toks) := sys->tokenize(string data, " ");
+ if (n < 6 || n & 1)
+ return "bad data";
+
+ colour, width, x, y : int;
+ (colour, toks) = (int hd toks, tl toks);
+ (width, toks) = (int hd toks, tl toks);
+ (x, toks) = (int hd toks, tl toks);
+ (y, toks) = (int hd toks, tl toks);
+ pen := wb.display.newimage(Rect(Point(0,0), Point(1,1)), Draw->CMAP8, 1, colour);
+ p0 := Point(x, y);
+ while (toks != nil) {
+ (x, toks) = (int hd toks, tl toks);
+ (y, toks) = (int hd toks, tl toks);
+ p1 := Point(x, y);
+ # could use poly() instead of line()
+ wb.line(p0, p1, Draw->Endsquare, Draw->Endsquare, width, pen, pen.r.min);
+ p0 = p1;
+ }
+ return nil;
+}