implement Lobby; include "sys.m"; sys: Sys; include "draw.m"; draw: Draw; Point, Rect, Display, Image, Font: import draw; include "tk.m"; tk: Tk; include "tkclient.m"; tkclient: Tkclient; include "../join.m"; join: Join; include "dividers.m"; dividers: Dividers; Divider: import dividers; include "commandline.m"; commandline: Commandline; Cmdline: import commandline; include "sh.m"; Lobby: module { init: fn(ctxt: ref Draw->Context, argv: list of string); }; CLIENTDIR: con "/dis/spree/clients"; NAMEFONT: con "/fonts/charon/plain.small.font"; TITLEFONT: con "/fonts/charon/bold.normal.font"; HEADERFONT: con "/fonts/charon/italic.normal.font"; Object: adt { id: int; pick { Session => filename: string; owner: string; invitations: list of string; members: list of string; invited: int; Sessiontype => start: string; name: string; title: string; clienttype: string; Invite => session: ref Object.Session; name: string; Member => parentid: int; name: string; Archive => Other => } }; drawctxt: ref Draw->Context; cliquefd: ref Sys->FD; objects: array of ref Object; myname: string; maxid := 0; badmodule(m: string) { sys->fprint(sys->fildes(2), "lobby: cannot load %s: %r\n", m); raise "fail:bad module"; } 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; if (tkclient == nil) badmodule(Tkclient->PATH); tkclient->init(); commandline = load Commandline Commandline->PATH; if(commandline == nil) badmodule(Commandline->PATH); commandline->init(); dividers = load Dividers Dividers->PATH; if (dividers == nil) badmodule(Dividers->PATH); dividers->init(); join = load Join Join->PATH; if (join == nil) badmodule(Join->PATH); drawctxt = ctxt; cliquefd = sys->fildes(0); sys->pctl(Sys->NEWPGRP, nil); client1(); } columns := array[] of {("name", ""), ("members", ""), ("watch", "Watch"), ("join", "Join"), ("invite", "Invite")}; reqwidth(win: ref Tk->Toplevel, w: string): int { return 2 * int cmd(win, w + " cget -bd") + int cmd(win, w + " cget -width"); } client1() { (win, winctl) := tkclient->toplevel(drawctxt, nil, "Lobby", Tkclient->Appl); ech := chan of string; tk->namechan(win, ech, "e"); (chat, chatevent) := Cmdline.new(win, ".d2", nil); updatech := chan of list of string; spawn readproc(updatech); cmd(win, "frame .buts"); cmd(win, "menubutton .buts.start -text New -menu .buts.start.m"); cmd(win, "menu .buts.start.m"); cmd(win, "pack .buts.start -side left"); cmd(win, "button .buts.kick -text Kick -command {send e kick}"); cmd(win, "pack .buts.kick -side left"); cmd(win, "pack .buts -side top -fill x"); cmd(win, "frame .d1"); cmd(win, "scrollbar .d1.s -orient vertical -command {.d1.c yview}"); cmd(win, "canvas .d1.c -yscrollcommand {.d1.s set}"); cmd(win, "pack .d1.s -side left -fill y"); cmd(win, "pack .d1.c -side top -fill both -expand 1"); cmd(win, "frame .t"); cmd(win, ".d1.c create window 0 0 -anchor nw -window .t"); cmd(win, "frame .t.f1 -bd 2 -relief sunken"); cmd(win, "pack .t.f1 -side top -fill both -expand 1"); cmd(win, "label .t.f1.sessionlabel -text Sessions -font " + TITLEFONT); cmd(win, "pack .t.f1.sessionlabel"); cmd(win, "frame .t.s"); cmd(win, "pack .t.s -in .t.f1 -side top -fill both -expand 1"); cmd(win, "frame .t.f2 -bd 2 -relief sunken"); cmd(win, "label .t.archiveslabel -text Archives -font " + TITLEFONT); cmd(win, "pack .t.archiveslabel"); cmd(win, "frame .t.a"); cmd(win, "pack .t.a -in .t.f2 -side top -fill both -expand 1 -anchor w"); cmd(win, "pack .t.f2 -side top -fill both -expand 1"); cmd(win, "label .t.a.title0 -text Title -font " + HEADERFONT); cmd(win, "label .t.a.title1 -text Members -font " + HEADERFONT); cmd(win, "grid .t.a.title0 .t.a.title1 -sticky w"); cmd(win, "grid columnconfigure .t.a 1 -weight 1"); cmd(win, "bind .t {.d1.c configure -scrollregion {0 0 [.t cget -width] [.t cget -height]}}"); cmd(win, "button .tmp"); for (i := 0; i < len columns; i++) { (name, mintext) := columns[i]; cmd(win, ".tmp configure -text '" + mintext); cmd(win, "grid columnconfigure .t.s " + string i + " -name " + name + " -minsize " + string reqwidth(win, ".tmp")); } cmd(win, "grid columnconfigure .t.s members -weight 1"); cmd(win, "destroy .tmp"); cmd(win, "menu .invite"); (divider, dividerevent) := Divider.new(win, ".d", ".d1" :: ".d2" :: nil, Dividers->NS); cmd(win, "pack .d -side top -fill both"); cmd(win, "pack propagate . 0"); 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); s := <-win.ctxt.ctl or s = <-win.wreq or s = <-winctl => tkclient->wmctl(win, s); c := <-dividerevent => divider.event(c); c := <-chatevent => lines := chat.event(c); for (; lines != nil; lines = tl lines) { line := hd lines; if (len line > 0 && line[len line-1]=='\n') line = line[0:len line-1]; cliquecmd("chat " + line); } lines := <-updatech => #sys->print("++\n"); for (; lines != nil; lines = tl lines) { #sys->print("+%s\n", hd lines); doupdate(win, chat, hd lines); } cmd(win, "update"); c := <-ech => (n, toks) := sys->tokenize(c, " "); case hd toks { "watch" => joinclique(win, chat, int hd tl toks, "watch"); "join" => joinclique(win, chat, int hd tl toks, "join"); "start" => start(win, chat, int hd tl toks); "postinvite" => postinvite(win, int hd tl toks, hd tl tl toks); "unarchive" => e := cliquecmd("unarchive " + hd tl toks); if (e != nil) chat.addtext("failed to unarchive: " + e + "\n"); "invite" => # invite sessionid name (id, name) := (hd tl toks, hd tl tl toks); vname := "inv." + name; v := int cmd(win, "variable " + vname); s := "invite"; if (!v) s = "uninvite"; e := cliquecmd(s + " " + string id + " " + name); if (e != nil) { chat.addtext("invite failed: " + e + "\n"); cmd(win, "variable " + vname + " " + string !v); } "kick" => e := cliquecmd("kick"); if (e != nil) chat.addtext("kick failed: " + e + "\n"); * => sys->print("unknown msg %s\n", c); } cmd(win, "update"); } } } joinclique(nil: ref Tk->Toplevel, chat: ref Cmdline, id: int, how: string) { pick o := objects[id] { Session => e := join->join(drawctxt, "/n/remote", o.filename, how); if (e != nil) chat.addtext("couldn't join clique: " + e + "\n"); else chat.addtext("joined clique ok\n"); * => sys->print("join bad id %d (type %d)\n", id, tagof objects[id]); } } start(nil: ref Tk->Toplevel, chat: ref Cmdline, id: int) { pick o := objects[id] { Sessiontype => e := cliquecmd("start " + o.start); if (e != nil) chat.addtext("failed to start clique: " + e + "\n"); * => sys->print("start bad id %d (type %d)\n", id, tagof objects[id]); } } postinvite(win: ref Tk->Toplevel, id: int, widget: string) { pick o := objects[id] { Session => cmd(win, ".invite delete 0 end"); cmd(win, ".invite add checkbutton -text All -variable inv.all -command {send e invite " + string id + " all}"); for (invites := o.invitations; invites != nil; invites = tl invites) if (hd invites == "all") break; cmd(win, "variable inv.all " + string (invites != nil)); for (i := 0; i < len objects; i++) { if (objects[i] == nil) continue; pick p := objects[i] { Member => if (tagof(objects[p.parentid]) != tagof(Object.Session) && p.name != o.owner) { for (invites = o.invitations; invites != nil; invites = tl invites) if (hd invites == p.name) break; invited := invites != nil; cmd(win, "variable inv." + p.name + " " + string invited); cmd(win, ".invite add checkbutton -variable inv." + p.name + " -command {send e invite " + string id + " " + p.name + "}" + " -text '" + p.name); } } } x := int cmd(win, widget + " cget -actx"); y := int cmd(win, widget + " cget -acty"); h := 2 * int cmd(win, widget + " cget -bd") + int cmd(win, widget + " cget -actheight"); cmd(win, ".invite post " + string x + " " + string (y + h)); * => sys->print("bad invited id %d (type %d)\n", id, tagof objects[id]); } } panic(s: string) { sys->print("lobby panic: %s\n", s); raise "panic"; } doupdate(win: ref Tk->Toplevel, chat: ref Cmdline, line: string) { (n, toks) := sys->tokenize(line, " "); if (n == 0) return; case hd toks { "chat" => chat.addtext(sys->sprint("%s: %s\n", hd tl toks, concat(tl tl toks))); "create" => # create id parentid vis type id := int hd tl toks; if (id >= len objects) objects = (array[len objects + 10] of ref Object)[0:] = objects; if (objects[id] != nil) panic(sys->sprint("object %d already exists!", id)); parentid := int hd tl tl toks; objtype := tl tl tl tl toks; o: ref Object; case hd objtype { "sessiontype" => o = ref Object.Sessiontype(id, nil, nil, nil, nil); "session" => cmd(win, "grid rowinsert .t.s 0"); cmd(win, "grid rowconfigure .t.s 0 -name id" + string id); f := ".t.s.f" + string id; cmd(win, "frame " + f); # dummy, so we can destroy row easily cmd(win, "label "+f+".name"); cmd(win, "grid "+f+".name -row id" + string id + " -column name -in .t.s"); cmd(win, "button "+f+".watch -text Watch -command {send e watch " + string id + "}"); cmd(win, "grid "+f+".watch -row id" + string id + " -column watch -in .t.s"); cmd(win, "label "+f+".members -font " + NAMEFONT); cmd(win, "grid "+f+".members -row id" + string id + " -column members -in .t.s"); o = ref Object.Session(id, nil, nil, nil, nil, 0); "member" => o = ref Object.Member(id, parentid, nil); "invite" => pick parent := objects[parentid] { Session => o = ref Object.Invite(id, parent, nil); * => panic("invite not under session"); } "archive" => cmd(win, "grid rowinsert .t.a 1"); cmd(win, "grid rowconfigure .t.a 1 -name id" + string id); f := ".t.a.f" + string id; cmd(win, "frame " + f); cmd(win, "label "+f+".name"); cmd(win, "grid "+f+".name -row id" + string id + " -column 0 -in .t.a -sticky w"); cmd(win, "label "+f+".members -anchor w -font " + NAMEFONT); cmd(win, "grid "+f+".members -row id" + string id + " -column 1 -in .t.a -sticky ew"); cmd(win, "button "+f+".unarchive -text Unarchive -command {send e unarchive " + string id + "}"); cmd(win, "grid "+f+".unarchive -row id" + string id + " -column 2 -in .t.a"); o = ref Object.Archive(id); * => o = ref Object.Other(id); } objects[id] = o; "del" => # del parent start end objs... for (objs := tl tl tl tl toks; objs != nil; objs = tl objs) { id := int hd objs; pick o := objects[id] { Session => cmd(win, "grid rowdelete .t.s id" + string id); cmd(win, "destroy .t.s.f" + string id); Archive => cmd(win, "grid rowdelete .t.a id" + string id); cmd(win, "destroy .t.a.f" + string id); Sessiontype => sys->print("cannot destroy sessiontypes yet\n"); Member => pick parent := objects[o.parentid] { Session => parent.members = removeitem(parent.members, o.name); cmd(win, sys->sprint(".t.s.f%d.members configure -text '%s", o.parentid, concat(parent.members))); * => chat.addtext(o.name + " has left\n"); } Invite => s := o.session; invites := s.invitations; invited := 0; for (s.invitations = nil; invites != nil; invites = tl invites) { inv := hd invites; if (inv != o.name) { s.invitations = inv :: s.invitations; if (inv == "all" || inv == myname) invited = 1; } } if (!invited && s.invited) { cmd(win, "destroy .t.s.f" + hd tl toks + ".join"); s.invited = 0; } } objects[id] = nil; } "name" => myname = hd tl toks; tkclient->settitle(win, "Lobby (" + myname + ")"); "set" => # set obj attr val id := int hd tl toks; (attr, val) := (hd tl tl toks, tl tl tl toks); pick o := objects[id] { Session => f := ".t.s.f" + string id; case attr { "filename" => o.filename = hd val; "owner" => if (hd val == myname) { cmd(win, "label "+f+".invite -text Invite -bd 2 -relief raised"); cmd(win, "bind "+f+".invite {send e postinvite " + string id + " %W}"); cmd(win, "grid "+f+".invite -row id" + string id + " -column invite -in .t.s"); } o.owner = hd val; "title" => cmd(win, f + ".name configure -text '" + concat(val)); } Archive => f := ".t.a.f" + string id; case attr { "name" => cmd(win, f + ".name configure -text '" + concat(val)); "members" => cmd(win, f + ".members configure -text '" + concat(val)); } Sessiontype => case attr { "start" => o.start = concat(val); "clienttype" => o.clienttype = hd val; "title" => if (o.title != nil) panic("can't change sessiontype name!"); else { o.title = concat(val); cmd(win, ".buts.start.m add command" + " -command {send e start " + string id + "}" + " -text '" + o.title); } "name" => o.name = hd val; } Member => case attr { "name" => if (o.name != nil) panic("cannot change member name!"); o.name = hd val; pick parent := objects[o.parentid] { Session => parent.members = o.name :: parent.members; cmd(win, sys->sprint(".t.s.f%d.members configure -text '%s", o.parentid, concat(parent.members))); * => chat.addtext(o.name + " has arrived\n"); } } Invite => case attr { "name" => o.name = hd val; s := o.session; sid := string s.id; f := ".t.s.f" + sid; invited := o.name == myname || o.name == "all"; s.invitations = o.name :: s.invitations; if (invited && !s.invited) { cmd(win, "button "+f+".join -text Join -command {send e join " + sid + "}"); cmd(win, "grid "+f+".join -row id" + sid + " -column join -in .t.s"); s.invited = 1; } } } } } removeitem(l: list of string, i: string): list of string { rl: list of string; for (; l != nil; l = tl l) if (hd l != i) rl = hd l :: rl; return rl; } numsplit(s: string): (string, int) { for (i := len s - 1; i >= 0; i--) if (s[i] < '0' || s[i] > '9') break; if (i == len s -1) return (s, 0); return (s[0:i+1], int s[i+1:]); } cliquecmd(s: string): string { if (sys->fprint(cliquefd, "%s", s) == -1) { e := sys->sprint("%r"); sys->print("error on '%s': %s\n", s, e); return e; } return nil; } prefixed(s: string, prefix: string): int { return len s >= len prefix && s[0:len prefix] == prefix; } readproc(updatech: chan of list of string) { buf := array[Sys->ATOMICIO] of byte; while ((n := sys->read(cliquefd, buf, Sys->ATOMICIO)) > 0) { (nil, lines) := sys->tokenize(string buf[0:n], "\n"); if (lines != nil) updatech <-= lines; } updatech <-= nil; } startclient(mod: Command, argv: list of string) { { mod->init(drawctxt, argv); } exception e { "*" => sys->print("client %s broken: %s\n", hd argv, e); exit; } mod->init(drawctxt, argv); } 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; } concat(l: list of string): string { if (l == nil) return nil; s := hd l; for (l = tl l; l != nil; l = tl l) s += " " + hd l; return s; }