ref: 51bf59e016b43f16ec91943829af30d99a886cc1
dir: /appl/charon/charon.b/
implement Charon; include "common.m"; include "debug.m"; sys: Sys; CU: CharonUtils; ByteSource, MaskedImage, CImage, ImageCache, ReqInfo, Header, ResourceState, config, max, min, X: import CU; D: Draw; Point, Rect, Font, Image, Display, Screen: import D; S: String; U: Url; Parsedurl: import U; L: Layout; Frame, Loc, Control: import L; I: Img; ImageSource: import I; B: Build; Item, Dimen: import B; E: Events; Event: import E; J: Script; G: Gui; C : Ctype; include "sh.m"; # package up info related to a navigation command GoSpec: adt { kind: int; # GoNormal, etc. url: ref Parsedurl; # destination (absolute) meth: int; # HGet or HPost body: string; # used if HPost target: string; # name of target frame auth: string; # optional auth info histnode: ref HistNode; # if kind is GoHistnode newget: fn(kind: int, url: ref Parsedurl, target: string) : ref GoSpec; newpost: fn(url: ref Parsedurl, body, target: string) : ref GoSpec; newspecial: fn(kind: int, histnode: ref HistNode) : ref GoSpec; equal: fn(a: self ref GoSpec, b: ref GoSpec) : int; }; GoNormal, GoReplace, GoLink, GoHistnode, GoSettext: con iota; # Information about a set of frames making up the screen DocConfig: adt { framename: string; # nonempty, except possibly for topconfig title: string; initconfig: int; # true unless this is a frameset and some subframe changed gospec: cyclic ref GoSpec; # TODO: add current y pos and form field values equal: fn(a: self ref DocConfig, b: ref DocConfig) : int; equalarray: fn(a1: array of ref DocConfig, a2: array of ref DocConfig) : int; }; # Information about a particular screen configuration HistNode: adt { topconfig: cyclic ref DocConfig; # config of top (whole doc, or frameset root) kidconfigs: cyclic array of ref DocConfig; # configs for kid frames (if a frameset) preds: cyclic list of ref HistNode; # edges in (via normal navigation) succs: cyclic list of ref HistNode; # edges out (via normal navigation) findid : int; findchain : cyclic list of ref HistNode; addedge: fn(a: self ref HistNode, b: ref HistNode, atob: int); copy: fn(a: self ref HistNode) : ref HistNode; }; History: adt { h: array of ref HistNode; # all visited HistNodes, in LRU order n: int; # h[0:n] is valid part of h findid : int; add: fn(h: self ref History, f: ref Frame, g: ref GoSpec, navkind: int); update: fn(h: self ref History, f: ref Frame); find: fn(h: self ref History, k: int) : ref HistNode; print: fn(h: self ref History); histinfo: fn(h: self ref History) : (int, string, string, string); findurl: fn(h: self ref History, s: string) : ref HistNode; }; # Authentication strings AuthInfo: adt { realm: string; credentials: string; }; auths: list of ref AuthInfo = nil; history : ref History; keyfocus: ref Control; mouseover: ref B->Anchor; mouseoverfr: ref Frame; grabctl: ref Control; popupctl: ref Control; SP : con 8; # a spacer for between controls SP2 : con 4; # half of SP SP3 : con 2; pgrp := 0; gopgrp := 0; dbg := 0; warn := 0; dbgres := 0; doscripts := 0; top, curframe: ref Frame; mainwin: ref Image; p0 := Point(0,0); context: ref Draw->Context; opener: chan of string; sendopener(s: string) { if(opener != nil){ alt{ opener <- = s => ; * => ; } } } hasopener(): int { return opener != nil; } init(ctxt: ref Draw->Context, argl: list of string) { chctxt := ref Context(ctxt, argl, nil, nil, nil); initc(chctxt); } initc(ctxt: ref Context) { sys = load Sys Sys->PATH; if (ctxt == nil) fatalerror("bad args\n"); opener = ctxt.c; argl := ctxt.args; context = ctxt.ctxt; (retval, nil) := sys->stat("/net/tcp"); if(retval < 0) sys->bind("#I", "/net", sys->MREPL); (retval, nil) = sys->stat("/net/cs"); if(retval < 0) startcs(); pgrp = sys->pctl(sys->NEWPGRP, nil); CU = load CharonUtils CharonUtils->PATH; if(CU == nil) fatalerror(sys->sprint("Couldn't load %s\n", CharonUtils->PATH)); ech := chan of ref Event; errpath := CU->init(load Charon SELF, CU, argl, ech, ctxt.cksrv, ctxt.ckclient); if(errpath != "") fatalerror(sys->sprint("Couldn't load %s\n", errpath)); ctxt = nil; sys = load Sys Sys->PATH; D = load Draw Draw->PATH; S = load String String->PATH; U = load Url Url->PATH; if (U != nil) U->init(); E = CU->E; L = CU->L; I = CU->I; B = CU->B; J = CU->J; G = CU->G; C = CU->C; dbg = int (CU->config).dbg['d']; warn = dbg || int (CU->config).dbg['w']; dbgres = int (CU->config).dbg['r']; doscripts = (CU->config).doscripts && J != nil; if(dbg && (CU->config).dbgfile != "") { dfile := sys->create((CU->config).dbgfile, sys->OWRITE, 8r666); if(dfile != nil) { sys->dup(dfile.fd, 1); } } curres := ResourceState.cur(); newres: ResourceState; if(dbgres) { (CU->startres).print("starting resources"); curres = ResourceState.cur(); } context = G->init(context, CU); if(dbgres) { newres = ResourceState.cur(); newres.since(curres).print("difference after G->init (made screen windows)"); curres = newres; } mainwin = G->mainwin; # L->init() was deferred until after G was inited L->init(CU); if(dbgres) { newres = ResourceState.cur(); newres.since(curres).print("difference after L->init (loaded Build, Lex)"); curres = newres; } (CU->imcache).init(); if(dbgres) { newres = ResourceState.cur(); newres.since(curres).print("difference after (CU->imcache).init"); curres = newres; } start(); if(J != nil) J->frametreechanged(top); startpage := config.starturl; g := GoSpec.newget(GoNormal, CU->makeabsurl(startpage), "_top"); if(dbgres) { newres = ResourceState.cur(); newres.since(curres).print("difference after initial configure"); curres = newres; } spawn plumbwatch(); spawn go(g); sendopener("B"); Forloop: for(;;) { ev := <- ech; if(dbg > 1) { pick de := ev { Emouse => if(dbg > 2 || de.mtype != E->Mmove) sys->print("%s\n", ev.tostring()); * => sys->print("%s\n", ev.tostring()); } } pick e := ev { Ekey => g = nil; case e.keychar { E->Kdown => curframe.yscroll(L->CAscrollpage, -1); E->Kup => curframe.yscroll(L->CAscrollpage, 1); E->Khome => curframe.yscroll(L->CAscrollpage, -10000); E->Kend => curframe.yscroll(L->CAscrollpage, 10000); E->Kaup => curframe.yscroll(L->CAscrollline, -1); E->Kadown => curframe.yscroll(L->CAscrollline, 1); * => handlekey(e); } Emouse => g = handlemouse(e); Ereshape => mainwin = G->mainwin; redraw(1); curframe = top; g = GoSpec.newspecial(GoHistnode, history.find(0)); Equit => break Forloop; Estop => if(gopgrp != 0) stop(); g = nil; Eback => g = GoSpec.newspecial(GoHistnode, history.find(-1)); Efwd => g = GoSpec.newspecial(GoHistnode, history.find(1)); Eform => formaction(e.frameid, e.formid, e.ftype, 0); g = nil; Eformfield => formfieldaction(e.frameid, e.formid, e.fieldid, e.fftype); g = nil; Ego => case e.gtype { E->EGnormal => url := CU->makeabsurl(e.url); if (url != nil) g = GoSpec.newget(GoNormal,url, e.target); else g = nil; E->EGreplace => g = GoSpec.newget(GoReplace, U->parse(e.url), e.target); E->EGreload => g = GoSpec.newspecial(GoHistnode, history.find(0)); E->EGforward => g = GoSpec.newspecial(GoHistnode, history.find(1)); E->EGback => g = GoSpec.newspecial(GoHistnode, history.find(-1)); E->EGdelta => g = GoSpec.newspecial(GoHistnode, history.find(e.delta)); E->EGlocation => g = GoSpec.newspecial(GoHistnode, history.findurl(e.url)); } Esubmit => if(e.subkind == CU->HGet) g = GoSpec.newget(GoNormal, e.action, e.target); else { g = GoSpec.newpost(e.action, e.data, e.target); } Escroll => f := findframe(top, e.frameid); if (f != nil) f.scrollabs(e.pt); g = nil; Escrollr => f := findframe(top, e.frameid); if (f != nil) f.scrollrel(e.pt); g = nil; Esettext => f := findframe(top, e.frameid); if (f != nil) g = ref GoSpec (GoSettext, e.url, 0, e.text, f.name, "", nil); Elostfocus => setfocus(nil); g = nil; Edismisspopup => if (popupctl != nil) setfocus(popupctl.donepopup()); popupctl = nil; grabctl = nil; } if (g == nil) continue; if (g.kind != GoSettext) { if (g.url != nil) { scheme := g.url.scheme; if (scheme == "javascript") { if (doscripts) spawn dojsurl(g); continue; } if (!CU->schemeok(scheme)) { url := g.url.tostring(); if (plumbsend(url, "url") == -1) G->setstatus(X("bad URL", "gui")+": "+url); continue; } } } if(gopgrp != 0) stop(); spawn go(g); } finish(); } mkprog(c: Command, ctxt: ref Draw->Context, args: list of string) { sys->pctl(Sys->NEWPGRP|Sys->NEWFD, list of {0, 1, 2}); c->init(ctxt, args); } start() { top = Frame.new(); curframe = top; history = ref History(nil, 0, 0); keyfocus = nil; mouseover = nil; redraw(1); } redraw(resized: int) { im := mainwin; if(resized) { # top.r = im.r.inset(2*L->ReliefBd); top.r = im.r; top.cim = mainwin; top.reset(); (CU->imcache).resetlimits(); } im.clipr = im.r; # L->drawrelief(im, top.r.inset(-L->ReliefBd), L->ReliefRaised); # L->drawrelief(im, top.r, L->ReliefSunk); L->drawfill(im, top.r, CU->White); G->flush(im.r); # im.clipr = top.r; } # Return a Loc representing a control in the frame f frameloc(c: ref Control, f: ref Frame) : ref Loc { loc := Loc.new(); loc.add(L->LEframe, f.r.min); loc.le[loc.n-1].frame = f; if (c != nil) { loc.add(L->LEcontrol, c.r.min); loc.le[loc.n-1].control = c; } return loc; } resetkeyfocus(f: ref Frame) { # determine if focus is in frame f or one of its sub-frames if (keyfocus == nil) return; for (focusf := keyfocus.f; focusf != nil; focusf = focusf.parent) { if (focusf == f) { keyfocus = nil; break; } } # current focus not in frameset being modified - leave as is } ctlmouse(e: ref Event.Emouse, ctl, grab: ref Control): ref Control { ev := E->SEnone; (action, newgrab) := ctl.domouse(e.p, e.mtype, grab); case (action) { L->CAbuttonpush => if(doscripts && ctl.ff != nil && ctl.ff.evmask) ev = E->SEonclick; else pushaction(ctl, e.p.sub(ctl.r.min)); L->CAkeyfocus => setfocus(ctl); L->CAchanged => # Select Formfield - selection has changed ev = E->SEonchange; L->CAselected => # text input Formfield - text selection has changed ev = E->SEonselect; L->CAdopopup => popupctl = ctl.dopopup(); if (popupctl != nil) setfocus(popupctl); L->CAdonepopup => setfocus(ctl.donepopup()); ev = E->SEonchange; popupctl = nil; } if (doscripts && ctl.ff != nil && (ctl.ff.evmask & ev)) { se := ref E->ScriptEvent(ev, ctl.f.id, ctl.ff.form.formid, ctl.ff.fieldid, -1, -1, e.p.x, e.p.y, 1, nil, nil, 0); J->jevchan <-= se; } return newgrab; } mainwinmouse(e: ref Event.Emouse) : (ref GoSpec, ref Control) { p := e.p; g : ref GoSpec; ctl : ref Control; newgrab : ref Control; domouseout := 0; loc : ref Loc; if(mouseover != nil) domouseout = 1; loc = top.find(p, nil); if(loc != nil) { if(dbg > 1) loc.print("mouse loc"); f := loc.lastframe(); hasscripts := f.doc.hasscripts; if(e.mtype != E->Mmove) curframe = f; n1 := loc.n-1; case loc.le[n1].kind { L->LEitem => it := loc.le[n1].item; if (it.anchorid < 0) break; a : ref Build->Anchor = nil; for(al := f.doc.anchors; al != nil; al = tl al) { a = hd al; if(a.index == it.anchorid) break; } if (al == nil) break; if(dbg > 1) sys->print("in anchor %d, href=%s\n", a.index, a.href.tostring()); if(doscripts && a.evmask) { if(a == mouseover) { domouseout = 0; # still over same anchor } else if(e.mtype == E->Mmove) { if(domouseout) { if(mouseover.evmask & E->SEonmouseout) { se := ref E->ScriptEvent(E->SEonmouseout, mouseoverfr.id, -1, -1, mouseover.index, -1, 0, 0, 0, nil, nil, 0); J->jevchan <-= se; } domouseout = 0; } mouseover = a; mouseoverfr = f; if(a.evmask & E->SEonmouseover) { se := ref E->ScriptEvent(E->SEonmouseover, f.id, -1, -1, a.index, -1, e.p.x, e.p.y, 0, nil, nil, 0); J->jevchan <-= se; } } if (e.mtype == E->Mlbuttonup || e.mtype == E->Mldrop) { if(a.evmask & E->SEonclick) { se := ref E->ScriptEvent(E->SEonclick, f.id, -1, -1, a.index, -1, 0, 0, 0, nil, nil, 0); J->jevchan <-= se; break; } ctl = nil; } } if(e.mtype == E->Mlbuttonup || e.mtype == E->Mldrop) { g = anchorgospec(it, a, loc.pos); if (g == nil) break; } else if(e.mtype == E->Mmbuttonup) { g = anchorgospec(it, a, loc.pos); if (g == nil) break; url := g.url.tostring(); G->setstatus(url); G->snarfput(url); g = nil; } L->LEcontrol => ctl = loc.le[n1].control; } } if (ctl != nil) newgrab = ctlmouse(e, ctl, nil); if(newgrab == nil && domouseout && doscripts) { if(mouseover.evmask & E->SEonmouseout) { se := ref E->ScriptEvent(E->SEonmouseout, mouseoverfr.id, -1, -1, mouseover.index, -1, 0, 0, 0, nil, nil, 0); J->jevchan <-= se; } mouseoverfr = nil; mouseover = nil; } return (g, newgrab); } dojsurl(g : ref GoSpec) { f := curframe; case g.target { "_top" => f = top; "_self" => ; # curframe is already OK "_parent" => if(f.parent != nil) f = f.parent; "_blank" => f = top; # we don't create new browsers... * => # this is recommended "current practice" f = findnamedframe(f, g.target); if(f == nil) { f = findnamedframe(top, g.target); if(f == nil) f = top; } } jev := ref E->ScriptEvent (E->SEscript, f.id, -1, -1, -1, -1, 0, 0, 0, g.url.path, chan of string, 0); J->jevchan <-= jev; v := <- jev.reply; if (v != nil) { ev := ref Event.Esettext(f.id, g.url, v); E->evchan <-= ev; } } # If mouse event results in command to navigate somewhere else, # return a GoSpec ref, else nil. handlemouse(e: ref Event.Emouse): ref GoSpec { g: ref GoSpec; ctl := grabctl; if (popupctl != nil) ctl = popupctl; if (ctl != nil) grabctl = ctlmouse(e, ctl, grabctl); else if (e.p.in(mainwin.r)) (g, grabctl) = mainwinmouse(e); return g; } setfocus(newc : ref Control) { newf, oldf: ref Frame; if (newc != nil) newf = newc.f; oldc := keyfocus; if (oldc != nil) oldf = oldc.f; if (oldc != nil && oldc != newc) oldc.losefocus(1); if (oldf != nil && oldf != newf) oldf.focus(0, 1); if (newf != nil && newf != oldf) newf.focus(1,1); if (newc != nil && newc != oldc) newc.gainfocus(1); keyfocus = newc; } handlekey(e: ref Event.Ekey) { c := keyfocus; if (c == nil) return; pick ce := c { Centry => case c.dokey(e.keychar) { L->CAreturnkey => if(c.ff != nil) { spawn form_submit(c.f, c.ff.form, p0, c, 1); return; } L->CAtabkey => # if control in a form - move focus to next focus-able control if (c.ff != nil) { found := 0; form := c.ff.form; nextff : ref B->Formfield; for (ffl := form.fields; ffl != nil; ffl = tl ffl) { ff := hd ffl; if (ff == c.ff) { found = 1; continue; } if (ff.ftype == B->Ftext || ff.ftype == B->Fpassword) { if (nextff == nil || found) nextff = ff; if (found) break; } } if (nextff != nil) formfield_focus(c.f, nextff); } } } return; } fileexist(file: string) :int { fd := sys->open(file, sys->OREAD); if (fd == nil) return 0; else return 1; } go(g: ref GoSpec) { gopgrp = sys->pctl(sys->NEWPGRP, nil); spawn goproc(g); # got to make netget the thread with the gopgrp thread, # since it runs until killed, and killing a pgrp needs an active # thread CU->netget(); } goproc(g: ref GoSpec) { origkind := g.kind; hn : ref HistNode = nil; doctext := ""; case origkind { GoNormal or GoReplace or GoSettext => ; GoHistnode => hn = g.histnode; if(hn == nil) return; g = hn.topconfig.gospec; } case g.target { "_top" => curframe = top; "_self" => ; # curframe is already OK "_parent" => if(curframe.parent != nil) curframe = curframe.parent; "_blank" => curframe = top; # we don't create new browsers... * => # this is recommended "current practice" curframe = findnamedframe(curframe, g.target); if(curframe == nil) { curframe = findnamedframe(top, g.target); if(curframe == nil) curframe = top; } } f := curframe; if(dbg) { sys->print("\n\nGO TO %s\n", g.url.tostring()); if(g.target != "_top") sys->print("target frame name=%s\n", f.name); } G->progress <-= (-1, G->Pstart, 0, ""); err := ""; status := "Done"; if((origkind == GoNormal || origkind == GoReplace || origkind == GoLink) && g.url.frag != "" && f.doc != nil && f.doc.src != nil && CU->urlequal(g.url, f.doc.src)) go_local(f, g.url.frag); else { if (g.kind == GoSettext) settext(g, f, g.body); else err = get(g, f, origkind, hn); if(doscripts && J->defaultStatus != "") status = J->defaultStatus; } if(err != nil) { status = err; G->progress <-= (-1, G->Perr, 100, err); } else G->progress <-= (-1, G->Pdone, 0, nil); G->setstatus(status); checkrefresh(f); } settext(g : ref GoSpec, f : ref Frame, text : string) : string { sdest := g.url.tostring(); G->setstatus(X("Fetching", "gui") + " " + sdest); bs := CU->stringreq(text); G->seturl(sdest); history.add(f, g, GoNormal); resetkeyfocus(f); L->layout(f, bs, 0); if (J != nil) J->framedone(f, f.doc.hasscripts); history.update(f); error := ""; if(f.kids != nil) { if(J != nil) J->frametreechanged(f); nkids := len f.kids; kdone := chan of (ref Frame, string); for(kl := f.kids; kl != nil; kl = tl kl) { k := hd kl; if(k.src != nil) { gs := GoSpec.newget(GoNormal, k.src, "_self"); if(dbg) sys->print("get child frame %s\n", gs.url.tostring()); spawn getproc(gs, k, GoNormal, nil, kdone); } } while (nkids--) { (k, e) := <- kdone; if (error != nil) error = e; checkrefresh(k); } } if (J != nil) { #this code should be split off as it is duplicated from get() # at this point all sub-frames and images have been loaded # Optimise this! so as only do it if a doc in the frameset # has script/event code J->jevchan <-= ref E->ScriptEvent(E->SEonload, f.id, -1, -1, -1, -1, -1, -1, -1, nil, nil, 0); if (doscripts && f.doc.hasscripts) { for(itl := f.doc.images; itl != nil; itl = tl itl) { it := hd itl; if(it.genattr == nil || !it.genattr.evmask) continue; ev := E->SEnone; pick im := it { Iimage => case im.ci.complete { # correct to equate these two ? Img->Mimnone or Img->Mimerror => ev = E->SEonerror; Img->Mimdone => ev = E->SEonload; } if(im.genattr.evmask & ev) J->jevchan <-= ref E->ScriptEvent(ev, f.id, -1, -1, -1, im.imageid, -1, -1, -1, nil, nil, 0); } } } } return error; } getproc(g: ref GoSpec, f: ref Frame, origkind: int, hn: ref HistNode, done : chan of (ref Frame, string)) { done <-= (f, get(g, f, origkind, hn)); } get(g: ref GoSpec, f: ref Frame, origkind: int, hn: ref HistNode) : string { curres, newres: ResourceState; if(dbgres) { (CU->imcache).clear(); curres = ResourceState.cur(); } sdest := g.url.tostring(); G->setstatus(X("Fetching", "gui") + " " + sdest); bsmain : ref ByteSource; hdr : ref Header; ri := ref ReqInfo(g.url, g.meth, array of byte g.body, g.auth, g.target); authtried := 0; realm := ""; auth := ""; error := ""; for(nredirs := 0; ; nredirs++) { bsmain = CU->startreq(ri); error = bsmain.err; if(error != "") { CU->freebs(bsmain); return error; } CU->waitreq(bsmain::nil); error = bsmain.err; if(error != "") { CU->freebs(bsmain); return error; } hdr = bsmain.hdr; (use, e, challenge, newurl) := CU->hdraction(bsmain, 1, nredirs); error = e; if(challenge != nil) { if(authtried) { # we already tried once; give up error = "Need authorization"; use = 1; } else { (realm, auth) = getauth(challenge); if(auth != "") { ri.auth = auth; authtried = 1; CU->freebs(bsmain); continue; } else { error = "Need authorization"; use = 1; } } } if (error == nil) { if (hdr.code != CU->HCOk) error = CU->hcphrase(hdr.code); if(authtried) { # it succeeded; add to auths list so don't have to ask again auths = ref AuthInfo(realm, auth) :: auths; } } if(newurl != nil) { ri.url = newurl; # some sites (e.g., amazon.com) assume that POST turns into # GET on redirect (maybe this is just http 1.0?) ri.method = CU->HGet; CU->freebs(bsmain); continue; } if(use == 0) { CU->freebs(bsmain); return error; } break; } if(dbgres > 1) { newres = ResourceState.cur(); newres.since(curres).print("resources to get header"); curres = newres; } if(hdr.mtype == CU->TextHtml || hdr.mtype == CU->TextPlain || I->supported(hdr.mtype)) { G->seturl(sdest); history.add(f, g, origkind); resetkeyfocus(f); srcdata := L->layout(f, bsmain, origkind == GoLink); if (J != nil) J->framedone(f, f.doc.hasscripts); history.update(f); if(dbgres > 1) { newres = ResourceState.cur(); newres.since(curres).print("resources to get page and do layout"); curres = newres; } if(f.kids != nil) { if(J != nil) J->frametreechanged(f); i := 0; nkids := len f.kids; kdone := chan of (ref Frame, string); for(kl := f.kids; kl != nil; kl = tl kl) { k := hd kl; if(k.src != nil) { if(hn != nil) gs := hn.kidconfigs[i].gospec; else gs = GoSpec.newget(GoNormal, k.src, "_self"); if(dbg) sys->print("get child frame %s\n", gs.url.tostring()); gokind := GoLink; if (origkind != GoLink) gokind = GoNormal; spawn getproc(gs, k, gokind, nil, kdone); } i++; } while (nkids--) { (k, err) := <- kdone; if (error == nil) # we currently only capture the first error # as we only have one palce to report it error = err; checkrefresh(k); } } if (J != nil) { # at this point all sub-frames and images have been loaded J->jevchan <-= ref E->ScriptEvent(E->SEonload, f.id, -1, -1, -1, -1, -1, -1, -1, nil, nil, 0); if (doscripts && f.doc.hasscripts) { for(itl := f.doc.images; itl != nil; itl = tl itl) { it := hd itl; if(it.genattr == nil || !it.genattr.evmask) continue; ev := E->SEnone; pick im := it { Iimage => case im.ci.complete { # correct to equate these two ? Img->Mimnone or Img->Mimerror => ev = E->SEonerror; Img->Mimdone => ev = E->SEonload; } if(im.genattr.evmask & ev) J->jevchan <-= ref E->ScriptEvent(ev, f.id, -1, -1, -1, im.imageid, -1, -1, -1, nil, nil, 0); } } } } if(g.url.frag != "") go_local(f, g.url.frag); } else { error = X("Unsupported media type", "gui")+ " "+CU->mnames[hdr.mtype]; # Optionally put a save-as dialog up here. if((CU->config).offersave) dosaveas(bsmain); CU->freebs(bsmain); } if(dbgres == 1) { newres = ResourceState.cur(); newres.since(curres).print("resources to do page"); curres = newres; } return error; } # Scroll frame f so that destination hyperlink loc is at top of view go_local(f: ref Frame, loc: string) { if(dbg) sys->print("go to local destination %s\n", loc); for(ld := f.doc.dests; ld != nil; ld = tl ld) { d := hd ld; if(d.name == loc) { dloc := f.find(p0, d.item); if(dloc == nil) { if(warn) sys->print("couldn't find item for destination anchor %s\n", loc); return; } p := f.sptolp(dloc.le[dloc.n-1].pos); f.yscroll(L->CAscrollabs, p.y); return; } } # special location names... l := S->tolower(loc); if(l == "top" || l == "home"){ f.yscroll(L->CAscrollabs, 0); return; } if(l == "end" || l=="bottom"){ f.yscroll(L->CAscrollabs, f.totalr.max.y); return; } if(warn) sys->print("couldn't find destination anchor %s\n", loc); } stripwhite(s: string) : string { j := 0; n := len s; for(i := 0; i < n; i++) { c := s[i]; if(c < C->NCTYPE && C->ctype[c]==C->W) continue; s[j++] = c; } if(j < n) s = s[0:j]; return s; } # If refresh has been set in f (i.e., client pull), # pause the appropriate amount of time and then go to new place checkrefresh(f: ref Frame) { if(f.doc != nil && f.doc.refresh != "") { seconds := 0; url : ref Parsedurl = nil; refresh := stripwhite(f.doc.refresh); (n, l) := sys->tokenize(refresh, ";"); if(n > 0) { seconds = int hd l; if(n > 1) { s := hd tl l; if(len s > 4 && S->tolower(s[0:4]) == "url=") { url = U->mkabs(U->parse(s[4:]), f.doc.base); } } } spawn dorefresh(f, seconds, url); } } dorefresh(f: ref Frame, seconds: int, url: ref Parsedurl) { sys->sleep(seconds * 1000); e : ref Event; if(url == nil) e = ref Event.Ego(nil, f.name, 0, E->EGreload); else e = ref Event.Ego(url.tostring(), f.name, 0, E->EGnormal); E->evchan <-= e; } # Do depth first search from f, looking for frame with given name. findnamedframe(f: ref Frame, name: string) : ref Frame { if(f.name == name) return f; for(l := f.kids; l != nil; l = tl l) { k := hd l; a := findnamedframe(k, name); if(a != nil) return a; } return nil; } # Similar, but look for frame id, starting from f findframe(f: ref Frame, id: int) : ref Frame { if(f.id == id) return f; for(l := f.kids; l != nil; l = tl l) { k := hd l; a := findframe(k, id); if(a != nil) return a; } return nil; } # Return Gospec resulting from button up in anchor a, at offset pos inside item it. anchorgospec(it: ref Item, a: ref B->Anchor, p: Point) : ref GoSpec { g : ref GoSpec; u := a.href; target := a.target; pick i := it { Iimage => ci := i.ci; if(ci.mims != nil) { if(i.map != nil) { (u, target) = findhit(i.map, p, ci.width, ci.height); } else if(u != nil && u.scheme != "javascript" && (it.state&B->IFsmap)) { # copy u, add ?x,y x := min(max(p.x-(int i.hspace + int i.border),0),ci.width-1); y := min(max(p.y-(int i.vspace + int i.border),0),ci.height-1); u = ref *a.href; u.query = string x + "," + string y; } } Ifloat => return anchorgospec(i.item, a, p); } if(u != nil) g = GoSpec.newget(GoLink, u, target); return g; } # Control c has been pushed. # Find the form it is in and perform required action (reset, or submit). pushaction(c: ref Control, pt: Point) { pick b := c { Cbutton => ff := b.ff; f := b.f; if(ff != nil) { case ff.ftype { B->Fsubmit or B->Fimage => spawn form_submit(c.f, ff.form, pt, c, 1); B->Freset => spawn form_reset(f, ff.form); } } } } # if onsubmit==1, then raise onsubmit event (if handler present) form_submit(fr: ref Frame, frm: ref B->Form, p: Point, submitctl: ref Control, onsubmit: int) { submitfield : ref B->Formfield; if (submitctl != nil) submitfield = submitctl.ff; if(submitctl != nil && tagof(submitctl) == tagof(Control.Centry)) { # Via CR, so only submit if there is a submit button (first one is the default) firstsubmit : ref B->Formfield; for(l := frm.fields; l != nil; l = tl l) { f := hd l; if (f.ftype == B->Fsubmit) { firstsubmit = f; break; } } if (firstsubmit == nil) return; submitfield = firstsubmit; } if(doscripts && fr.doc.hasscripts && onsubmit && (frm.evmask & E->SEonsubmit)) { c := chan of string; J->jevchan <-= ref E->ScriptEvent(E->SEonsubmit, fr.id, frm.formid, -1, -1, -1, -1, -1, -1, nil, c, 0); if(<-c == nil) return; } v := ""; sep := ""; radiodone : list of string = nil; floop: for(l := frm.fields; l != nil; l = tl l) { f := hd l; if(f.name == "") continue; val := ""; c: ref Control; if(f.ctlid >= 0) c = fr.controls[f.ctlid]; case f.ftype { B->Ftext or B->Fpassword or B->Ftextarea => if(c != nil) pick e := c { Centry => val = e.s; } if(val != "" && f.name == "_ISINDEX_") { # just the index terms after the "?" if(sep != "") v = v + sep; sep = "&"; v = v + ucvt(val); break floop; } B->Fcheckbox or B->Fradio => if(f.ftype == B->Fradio) { # Need the following to catch case where there # is more than one radiobutton with the same name # and value. for(rl := radiodone; rl != nil; rl = tl rl) if(hd rl == f.name) continue floop; } checked := 0; if(c != nil) pick cb := c { Ccheckbox => checked = cb.flags & L->CFactive; } if(checked) { val = f.value; if(f.ftype == B->Fradio) radiodone = f.name :: radiodone; } else continue; B->Fhidden => val = f.value; B->Fsubmit => if(submitctl != nil && f == submitctl.ff && f.name != "_no_name_submit_") val = f.value; else continue; B->Fselect => if(c != nil) pick s := c { Cselect => for(i := 0; i < len s.options; i++) { if(s.options[i].selected) { if(sep != "") v = v + sep; sep = "&"; v = v + ucvt(f.name) + "=" + ucvt(s.options[i].value); } } continue; } B->Fimage => if(submitctl != nil && f == submitctl.ff) { if(sep != "") v = v + sep; sep = "&"; v = v + ucvt(f.name + ".x") + "=" + ucvt(string max(p.x,0)) + sep + ucvt(f.name + ".y") + "=" + ucvt(string max(p.y,0)); continue; } } # if(val != "") { if(sep != "") v = v + sep; sep = "&"; v = v + ucvt(f.name) + "=" + ucvt(val); # } } action := ref *frm.action; if (frm.method == CU->HGet) { if (action.query != "" && v != "") action.query += "&"; action.query += v; v = ""; } # action.query = v; E->evchan <-= ref Event.Esubmit(frm.method, action, v, frm.target); } hexdigit := "0123456789ABCDEF"; urlchars := array [128] of { 'a' to 'z' => byte 1, 'A' to 'Z' => byte 1, '0' to '9' => byte 1, '-' or '/' or '$' or '_' or '@' or '.' or '!' or '*' or '\'' or '(' or ')' => byte 1, * => byte 0 }; ucvt(s: string): string { b := array of byte s; u := ""; for(i := 0; i < len b; i++) { c := int b[i]; if (c < len urlchars && int urlchars[c]) u[len u] = c; else if(c == ' ') u[len u] = '+'; else { u[len u] = '%'; u[len u] = hexdigit[(c>>4)&15]; u[len u] = hexdigit[c&15]; } } return u; } form_reset(fr: ref Frame, frm: ref B->Form) { if(doscripts && fr.doc.hasscripts && (frm.evmask & E->SEonreset)) { c := chan of string; J->jevchan <-= ref E->ScriptEvent(E->SEonreset, fr.id, frm.formid, -1, -1, -1, -1, -1, -1, nil, c, 0); if(<-c == nil) return; } for(fl := frm.fields; fl != nil; fl = tl fl) { a := hd fl; if(a.ctlid >= 0) fr.controls[a.ctlid].reset(); } # fr.cim.flush(D->Flushnow); } formaction(frameid, formid, ftype, onsubmit: int) { if(dbg > 1) sys->print("formaction %d %d %d %d\n", frameid, formid, ftype, onsubmit); f := findframe(top, frameid); if(f != nil) { d := f.doc; if(d != nil) { for(fl := d.forms; fl != nil; fl = tl fl) { frm := hd fl; if(frm.formid == formid) { if(ftype == E->EFsubmit) spawn form_submit(f, frm, Point(0,0), nil, onsubmit); else spawn form_reset(f, frm); } } } } } formfield_blur(f: ref Frame, ff: ref B->Formfield) { if(ff.ftype != B->Fhidden) { c := f.controls[ff.ctlid]; if(!(c.flags & L->CFhasfocus)) return; # lose focus quietly - don't raise "onblur" event for the given control c.losefocus(0); setfocus(nil); } } formfield_focus(f: ref Frame, ff: ref B->Formfield) { if(ff.ftype != B->Fhidden) { c := f.controls[ff.ctlid]; if(c.flags & L->CFhasfocus) return; # gain focus quietly - don't raise "onfocus" event for the given control c.gainfocus(0); setfocus(c); } } # simulate a mouse click, but don't trigger onclick event formfield_click(f: ref Frame, frm: ref B->Form, ff: ref B->Formfield) { c := f.controls[ff.ctlid]; case ff.ftype { B->Fcheckbox or B->Fradio or B->Fbutton => c.domouse(p0, E->Mlbuttonup, nil); B->Fsubmit => spawn form_submit(f, frm, p0, c, 1); B->Freset => spawn form_reset(f, frm); } } formfield_select(f: ref Frame, ff: ref B->Formfield) { case ff.ftype { B->Ftext or B->Fselect or B->Ftextarea => ctl := f.controls[ff.ctlid]; pick c := ctl { Centry => c.sel = (0, len c.s); ctl.draw(1); } } } formfieldaction(frameid, formid, fieldid, fftype: int) { if(dbg > 1) sys->print("formfieldaction %d %d %d %d\n", frameid, formid, fieldid, fftype); f := findframe(top, frameid); if(f == nil || f.doc == nil) return; # find form in frame frm : ref B->Form; for(fl := f.doc.forms; fl != nil; fl = tl fl) { if((hd fl).formid == formid) { frm = hd fl; break; } } if(frm == nil) return; # find formfield in form ff : ref B->Formfield; for(ffl := frm.fields; ffl != nil; ffl = tl ffl) { if((hd ffl).fieldid == fieldid) { ff = hd ffl; break; } } if(ff == nil || ff.ctlid < 0) return; # perform action case fftype { E->EFFblur => formfield_blur(f, ff); E->EFFfocus => formfield_focus(f, ff); E->EFFclick => formfield_click(f, frm, ff); E->EFFselect => formfield_select(f, ff); E->EFFredraw => c := f.controls[ff.ctlid]; pick ctl := c { Cselect => sel := 0; for (i := 0; i < len ctl.options; i++) { if (ctl.options[i].selected) { sel = i; break; } } if (sel > len ctl.options - ctl.nvis) sel = len ctl.options - ctl.nvis; ctl.first = sel; } c.draw(1); } } # Find hit in a local map findhit(map: ref B->Map, p: Point, w, h: int) : (ref Parsedurl, string) { x := p.x; y := p.y; dflt : ref Parsedurl = nil; dflttarg := ""; for(al := map.areas; al != nil; al = tl al) { a := hd al; c := a.coords; nc := len c; x1 := 0; y1 := 0; x2 := 0; y2 := 0; if(nc >= 2) { x1 = d2pix(c[0], w); y1= d2pix(c[1], h); if(nc > 2) { x2 = d2pix(c[2], w); if(nc > 3) y2 = d2pix(c[3], h); } } hit := 0; case a.shape { "rect" or "rectangle" => if(nc == 4) hit = x1 <= x && x <= x2 && y1 <= y && y <= y2; "circ" or "circle" => if(nc == 3) { xd := x - x1; yd := y - y1; hit = xd*xd + yd*yd <= x2*x2; } "poly" or "polygon" => np := nc / 2; hit = 0; xr := real x; yr := real y; j := np - 1; for(i := 0; i < np; j = i++) { xi := real d2pix(c[2*i], w); yi := real d2pix(c[2*i+1], h); xj := real d2pix(c[2*j], w); yj := real d2pix(c[2*j+1], h); if ((((yi<=yr) && (yr<yj)) || ((yj<=yr) && (yr<yi))) && (xr < (xj - xi) * (yr - yi) / (yj - yi) + xi)) hit = !hit; } "def" or "default" => dflt = a.href; dflttarg = a.target; } if(hit) return (a.href, a.target); } return (dflt, dflttarg); } d2pix(d: B->Dimen, tot: int) : int { ans := d.spec(); if(d.kind() == B->Dpercent) ans = (ans * tot) / 100; return ans; } GoSpec.newget(kind: int, url: ref Parsedurl, target: string) : ref GoSpec { return ref GoSpec(kind, url, CU->HGet, "", target, "", nil); } GoSpec.newpost(url: ref Parsedurl, body, target: string) : ref GoSpec { return ref GoSpec(GoNormal, url, CU->HPost, body, target, "", nil); } GoSpec.newspecial(kind: int, hn: ref HistNode) : ref GoSpec { return ref GoSpec(kind, nil, 0, "", "", "", hn); } GoSpec.equal(a: self ref GoSpec, b: ref GoSpec) : int { if(a.url == nil || b.url == nil) return 0; return CU->urlequal(a.url, b.url) && a.meth == b.meth && a.body == b.body; } DocConfig.equal(a: self ref DocConfig, b: ref DocConfig) : int { return a.framename == b.framename && a.gospec.equal(b.gospec); } DocConfig.equalarray(a1: array of ref DocConfig, a2: array of ref DocConfig) : int { n := len a1; if(n != len a2) return 0; for(i := 0; i < n; i++) { if(a1[i] == nil || a2[i] == nil) continue; if(!(a1[i]).equal(a2[i])) return 0; } return 1; } # Put b in a.succs (if atob is true) or a.preds (if atob is false) # at front of list. # If it is already in the list, move it to the front. HistNode.addedge(a: self ref HistNode, b: ref HistNode, atob: int) { if(atob) oldl := a.succs; else oldl = a.preds; there := 0; for(l := oldl; l != nil; l = tl l) if(hd l == b) { there = 1; break; } if(there) newl := b :: remhnode(oldl, b); else newl = b :: oldl; if(atob) a.succs = newl; else a.preds = newl; } # return copy of l with hn removed (known that hn # occurs at most once) remhnode(l: list of ref HistNode, hn: ref HistNode) : list of ref HistNode { if(l == nil) return nil; hdl := hd l; if(hdl == hn) return tl l; return hdl :: remhnode(tl l, hn); } # Copy of a, with new kidconfigs array (so that it can be changed independent # of a), and clear the preds and succs. HistNode.copy(a: self ref HistNode) : ref HistNode { n := len a.kidconfigs; kc : array of ref DocConfig = nil; if(n > 0) { kc = array[n] of ref DocConfig; for(i := 0; i < n; i++) kc[i] = a.kidconfigs[i]; } return ref HistNode(a.topconfig, kc, nil, nil, -1, nil); } # This is called just before layout of f with result of getting g. # (we don't yet know doctitle and whether this is a frameset). # If navkind is not GoHistnode, update the history graph; but if # navkind is GoReplace, replace oldcur with the new HistNode. # In any case reorder the history array to put latest last in array. History.add(h: self ref History, f: ref Frame, g: ref GoSpec, navkind: int) { if(len h.h <= h.n) { newh := array[len h.h + 20] of ref HistNode; newh[0:] = h.h; h.h = newh; } oldcur : ref HistNode; if(h.n > 0) oldcur = h.h[h.n-1]; dc := ref DocConfig(f.name, g.url.tostring(), navkind != GoHistnode, g); hnode := ref HistNode(dc, nil, nil, nil, -1, nil); if(f == top) { g.target = "_top"; } else if(oldcur != nil) { # oldcur should be a frameset and f should be a kid in it kidpos := -1; for(i := 0; i < len oldcur.kidconfigs; i++) { kc := oldcur.kidconfigs[i]; if(kc != nil && kc.framename == f.name) { kidpos = i; break; } } if(kidpos == -1) { if(dbg) sys->print("history botch\n"); } else { hnode = oldcur.copy(); hnode.kidconfigs[kidpos] = dc; } } # see if equivalent node to hnode is already in history hnodepos := -1; for(i := 0; i < h.n; i++) { if(hnode.topconfig.equal(h.h[i].topconfig)) { if((hnode.kidconfigs==nil && h.h[i].topconfig.initconfig) || DocConfig.equalarray(hnode.kidconfigs, h.h[i].kidconfigs)) { hnodepos = i; hnode = h.h[i]; break; } } } if(hnodepos == -1) { if(navkind == GoReplace && h.n > 0) h.n--; hnodepos = h.n; h.h[h.n++] = hnode; } if(oldcur != nil && hnode != oldcur && navkind != GoHistnode) { oldcur.addedge(hnode, 1); if(navkind != GoReplace) hnode.addedge(oldcur, 0); else if(oldcur.preds != nil) hnode.addedge(hd oldcur.preds, 0); } if(hnodepos != h.n-1) { # move hnode to h.n-1, and shift rest back for(k := hnodepos; k < h.n-1; k++) h.h[k] = h.h[k+1]; h.h[h.n-1] = hnode; } G->backbutton(hnode.preds != nil); G->fwdbutton(hnode.succs != nil); } # This is called just after layout of f. # Now we can put in correct doctitle, and make kids array if necessary. History.update(h: self ref History, f: ref Frame) { hnode := h.h[h.n-1]; if(f == top) { hnode.topconfig.title = f.doc.doctitle; if(f.kids != nil && hnode.kidconfigs == nil) { kc := array[len f.kids] of ref DocConfig; i := 0; for(l := f.kids; l != nil; l = tl l) { kf := hd l; if(kf.src != nil) kc[i] = ref DocConfig(kf.name, kf.src.tostring(), 1, GoSpec.newget(GoNormal, kf.src, "_self")); i++; } hnode.kidconfigs = kc; } } else { # hnode should be a frameset and f should be a kid in it for(i := 0; i < len hnode.kidconfigs; i++) { kc := hnode.kidconfigs[i]; if(kc != nil && kc.framename == f.name) { hnode.kidconfigs[i].title = f.doc.doctitle; return; } } if(dbg) sys->print("history update botch\n"); } } # Find the gokind node (-1==Back, 0==Same, +1==Forward) # other gokind values come from JavaScript's History.go(delta) History.find(h: self ref History, gokind: int) : ref HistNode { if(h.n > 0) { cur := h.h[h.n-1]; case gokind { 1 => if(cur.succs != nil) return hd cur.succs; -1 => if(cur.preds != nil) return hd cur.preds; 0 => return cur; * => # BUG: follows circularities: gives rise to different behaviour to other # browsers but maintains the property of find(n) being equivalent to # the user pressing the (forward/back) button n times h.findid++; while (gokind != 0 && cur != nil) { hn : list of ref HistNode; if (gokind > 0) { gokind--; hn = cur.succs; } else { gokind++; hn = cur.preds; } if (cur.findid == h.findid) hn = cur.findchain; else cur.findid = h.findid; if (hn != nil) { cur.findchain = tl hn; cur = hd hn; } else cur = nil; } return cur; } } return nil; } # for debugging History.print(h: self ref History) { sys->print("History\n"); for(i := 0; i < h.n; i++) { hn := history.h[i]; sys->print("Node %d:\n", i); dc := hn.topconfig; sys->print("\tframe=%s, target=%s, url=%s\n", dc.framename, dc.gospec.target, dc.gospec.url.tostring()); if(hn.kidconfigs != nil) { for(j := 0; j < len hn.kidconfigs; j++) { dc = hn.kidconfigs[j]; if(dc != nil) sys->print("\t\t%d: frame=%s, target=%s, url=%s\n", j, dc.framename, dc.gospec.target, dc.gospec.url.tostring()); } } if(hn.preds != nil) printhnodeindices(h, "Preds", hn.preds); if(hn.succs != nil) printhnodeindices(h, "Succs", hn.succs); } sys->print("\n"); } # helpers for JavaScript's History object History.histinfo(h: self ref History) : (int, string, string, string) { length := 0; current, next, previous : string; if(h.n > 0) { hn := h.h[h.n-1]; length = len hn.succs + len hn.preds + 1; current = hn.topconfig.gospec.url.tostring(); if(hn.succs != nil) { fwd := hd hn.succs; next = fwd.topconfig.gospec.url.tostring(); } if(hn.preds != nil) { back := hd hn.preds; previous = back.topconfig.gospec.url.tostring(); } } return (length, current, next, previous); } histinfo() : (int, string, string, string) { return history.histinfo(); } # does URL in hn contain s as a substring? isurlsubstring(hn: ref HistNode, s: string) : int { url := hn.topconfig.gospec.url.tostring(); (l, r) := S->splitstrl(url, s); if(r != nil) return 1; return 0; } # for JavaScript's History.go(location) # find nearest history entry whose URL contains s as a substring # (search forward and backward from current "in parallel"?) History.findurl(h: self ref History, s: string) : ref HistNode { if(h.n > 0) { hn := h.h[h.n-1]; if(isurlsubstring(hn, s)) return hn; fwd := hn.succs; back := hn.preds; while(fwd != nil && back != nil) { if(fwd != nil) { if(isurlsubstring(hd fwd, s)) return hd fwd; fwd = tl fwd; } if(back != nil) { if(isurlsubstring(hd back, s)) return hd back; back = tl back; } } } return nil; } printhnodeindices(h: ref History, label: string, l: list of ref HistNode) { sys->print("\t%s:", label); for( ; l != nil; l = tl l) { hn := hd l; for(i := 0; i < h.n; i++) { if(hn == h.h[i]) { sys->print(" %d", i); break; } } if(i == h.n) sys->print(" ?"); } sys->print("\n"); } dumphistory() { fname := config.userdir + "/history.html"; fd := sys->create(fname, sys->OWRITE, 8r600); if(fd == nil) { if(warn) sys->print("can't create history file\n"); return; } line := "<HEAD><TITLE>History</TITLE>\n<META HTTP-EQUIV=\"content-type\" CONTENT=\"text/html; charset=utf8\">\n</HEAD>\n<BODY>\n"; buf := array[Sys->ATOMICIO] of byte; aline := array of byte line; buf[0:] = aline; bufpos := len aline; for(i := history.n-1; i >= 0; i--) { hn := history.h[i]; dc := hn.topconfig; line = "<A HREF=" + dc.gospec.url.tostring() + " TARGET=\"_top\">" + dc.title + "</A><BR>\n"; if(hn.kidconfigs != nil) { line += "<UL>"; for(j := 0; j < len hn.kidconfigs; j++) { dc = hn.kidconfigs[j]; if(dc != nil) { line += "<LI><A HREF=" + dc.gospec.url.tostring() + " TARGET=\"" + dc.framename + "\">" + dc.title + "</A>\n"; } } line += "</UL>"; } aline = array of byte line; if(bufpos + len aline > Sys->ATOMICIO) { sys->write(fd, buf, bufpos); bufpos = 0; } buf[bufpos:] = aline; bufpos += len aline; } if(bufpos > 0) sys->write(fd, buf, bufpos); } # getauth returns the (realm, credentials), with "" for the credentials # if we fail in getting authorization for some reason getauth(chal: string) : (string, string) { if(len chal < 12 || S->tolower(chal[0:12]) != "basic realm=") { if(dbg || warn) sys->print("unrecognized authorization challenge: %s\n", chal); return ("", ""); } realm := chal[12:]; if(realm[0] == '"') realm = realm[1:len realm - 1]; for(al := auths; al != nil; al = tl al) { a := hd al; if(realm == a.realm) return (realm, a.credentials); } c := chan of (int, string); (code, uname, pword) := G->auth(realm); if(code != 1) return (nil, nil); cred := uname + ":" + pword; cred = tobase64(cred); return (realm, cred); } # Convert string to the base64 encoding tobase64(a: string) : string { n := len a; if(n == 0) return ""; out := ""; j := 0; i := 0; while(i < n) { x := a[i++] << 16; if(i < n) x |= (a[i++]&255) << 8; if(i < n) x |= (a[i++]&255); out[j++] = c64(x>>18); out[j++] = c64(x>>12); out[j++] = c64(x>> 6); out[j++] = c64(x); } nmod3 := n % 3; if(nmod3 != 0) { out[j-1] = '='; if(nmod3 == 1) out[j-2] = '='; } return out; } c64(c: int) : int { v : con "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; return v[c&63]; } dosaveas(bsmain: ref ByteSource) { (code, ans) := G->prompt("Save as", nil); if (code == -1) return; if(code == 1 && ans != "") { if(ans[0] != '/') ans = config.userdir + "/" + ans; fd := sys->create(ans, sys->OWRITE, 8r644); if(fd == nil) { G->alert(X("Couldn't create", "gui") + " " + ans); return; } G->setstatus(X("Saving", "gui") + " " + bsmain.hdr.actual.tostring()); # TODO: should really use a different protocol that # doesn't require getting whole file before proceeding s := ""; while(!bsmain.eof) { CU->waitreq(bsmain::nil); if(bsmain.err != "") { s = bsmain.err; break; } } if(s == "") { flen := bsmain.edata; for(i := 0; i < bsmain.edata; ) { n := sys->write(fd, bsmain.data[i:flen], flen-i); if(n <= 0) break; i += n; } if(i != flen) s = "whole file not written"; } if(s == "") s = X("Created", "gui") + " " + ans; G->setstatus(X("Created", "gui") + " " + ans); # G->alert(s); } CU->freebs(bsmain); } fatalerror(msg: string) { sys->print("Fatal error: %s\n", msg); finish(); } pctoloc(mod: string, pc: int) : string { ans := sys->sprint("pc=%d", pc); db := load Debug Debug->PATH; if(db == nil) return ans; Sym : import db; db->init(); modname := mod; for(i := 0; i < len mod; i++) if(mod[i] == '[') { modname = mod[0:i]; break; } sblname := ""; case modname { "Build" => sblname = "build.sbl"; "CharonUtils" => sblname = "chutils.sbl"; "Gui" => sblname = "gui.sbl"; "Img" => sblname = "img.sbl"; "Layout" => sblname = "layout.sbl"; "Lex" => sblname = "lex.sbl"; "Test" => sblname = "test.sbl"; } if(sblname == "") return ans; (sym, nil) := db->sym(sblname); if(sym == nil) return ans; src := sym.pctosrc(pc); if(src == nil) return ans; return sys->sprint("%s:%d", src.start.file, src.start.line); } startcs() { cs := load Command "/dis/ndb/cs.dis"; if (cs == nil) { sys->print("failed to start cs\n"); return; } spawn cs->init(nil, nil); sys->sleep(1000); } startcharon(url: string, c: chan of string) { ctxt := ref Context; ctxt.ctxt = context; ctxt.args = "charon" :: url :: nil; ctxt.c = c; ctxt.cksrv = CU->CK; ctxt.ckclient = CU->ckclient; ch := load Charon "/dis/charon.dis"; fdl := list of {0, 1, 2}; if (CU->ckclient != nil) fdl = (CU->ckclient).fd.fd :: fdl; if(ch != nil){ sys->pctl(Sys->NEWPGRP|Sys->NEWFD, fdl); ch->initc(ctxt); } } # Kill all processes spawned by us, and exit finish() { if (CU != nil) { CU->kill(pgrp, 1); if(gopgrp != 0) CU->kill(gopgrp, 1); } if(plumb != nil) plumb->shutdown(); sendopener("E"); exit; } include "plumbmsg.m"; plumb: Plumbmsg; Msg: import plumb; plumbwatch() { plumb = load Plumbmsg Plumbmsg->PATH; if (plumb == nil) return; if (plumb->init(1, (CU->config).plumbport, 0) == -1) { # try to set up plumbing for sending only if (plumb->init(1, nil, 0) == -1) plumb = nil; return; } while ((m := Msg.recv()) != nil) { if (m.kind == "text") { u := CU->makeabsurl(string m.data); if (u != nil) E->evchan <-= ref Event.Ego(u.tostring(), "_top", 0, E->EGnormal); } } } plumbsend(s, dest: string): int { if (plumb == nil) return -1; if (dest != nil) dest = "type="+dest; msg := ref Msg((CU->config).plumbport, nil, "", "text", dest, array of byte s); if (msg.send() < 0) return -1; return 0; } stop() { stopped := X("Stopped", "gui"); G->progress <-= (-1, G->Paborted, 0, stopped); G->setstatus(stopped); CU->abortgo(gopgrp); } gettop(): ref Layout->Frame { return top; }