diff options
Diffstat (limited to 'appl/spree/clients/lobby.b')
| -rw-r--r-- | appl/spree/clients/lobby.b | 562 |
1 files changed, 562 insertions, 0 deletions
diff --git a/appl/spree/clients/lobby.b b/appl/spree/clients/lobby.b new file mode 100644 index 00000000..1af52827 --- /dev/null +++ b/appl/spree/clients/lobby.b @@ -0,0 +1,562 @@ +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 <Configure> {.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 <Button-1> {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; +} |
