Plan 9 from Bell Labs’s /usr/web/sources/extra/i/i.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#include "i.h"

enum {
	ALERTLINELEN = 80,	// max number of characters in an alert line
	SP = 8,			// a spacer for between controls
	SP2 = 4			// half of SP
};


Channel*		gochan;
AuthInfo*		auths = nil;
Channel*		popupans;
int			popupactive = 0;
History		history;
CtlLayout		ctllay;
ProgLayout	proglay;
PopupLayout	popuplay;
Loc*			keyfocus;
int			pgrp = 0;
int			dbgres = 0;
Frame*		top;
Frame*		curframe;
Frame*		ctlframe;
Frame*		progframe;
Frame*		popupframe;
Image*		popupwin;

static void	start(void);
static void	redrawctl(int resized);
static void	redrawmain(int resized);
static void	redrawprog(int resized);
static void	dopopup(int kind, Rune* s);
static void	redrawpopup(void);
static void	finishpopup(int code);
static void	freectls(Control** a, int n);
static Rune*	ctlentrytext(Control* c);
static Loc*	frameloc(Control* c, Frame* f);
static void	resetkeyfocus(Frame* oldf);
static GoSpec*	handlemouse(EV e);
static GoSpec*	handlekey(EV e);
static void	go(void* arg);
static void	goproc(void* arg);
static int	get(GoSpec* g, Frame* f, int origkind, HistNode* hn);
static void	go_local(Frame* f, Rune* loc);
static void	checkrefresh(Frame* f);
static Frame*	findnamedframe(Frame* f, Rune* name);
static Frame*	findframe(Frame* f, int id);
static GoSpec*	anchorgospec(Item* it, Anchor* a, Point p);
static GoSpec*	pushaction(Control* c, Loc* loc);
static GoSpec*	form_submit(Frame* fr, Form* frm, Point p, Control* submitctl);
static Rune*	ucvt(Rune* s);
static int	hexdigit(int v);
static void	form_reset(Frame* fr, Form* frm);
static GoSpec*	formaction(int frameid, int formid, int ftype);
static ParsedUrl*	findhit(Map* map, Point p, int w, int h, int* ptarget);
static int	d2pix(Dimen d, int tot);
static GoSpec*	newget(ParsedUrl* url, int target);
static GoSpec*	newpost(ParsedUrl* url, Rune* body, int target);
static GoSpec*	newspecial(int kind, HistNode* hn);
static GoSpec*	copygospec(GoSpec* g);
static int	gospecequal(GoSpec* a, GoSpec* b);
static int	docconfigequal(DocConfig* a, DocConfig* b);
static int	docconfigequalarray(DocConfig** a1, int n1, DocConfig** a2, int n2);
static DocConfig*	newdocconfig(Rune* fname, Rune* title, int initconfig, GoSpec* g);
static DocConfig*	copydocconfig(DocConfig* d);
static HistNode_list*	newhistnodelist(HistNode* hn, HistNode_list* l);
static void	histaddedge(HistNode* a, HistNode* b, int atob);
static HistNode*	histnodecopy(HistNode* a);
static HistNode*	newhistnode(DocConfig* top, DocConfig** kids, int nkids,
					HistNode_list* preds, HistNode_list* succs);
static void	histadd(Frame* f, GoSpec* g, int navkind);
static void	histupdate(Frame* f);
static HistNode*	histfind(int gokind);
static void	histprint();
static HistNode_list*	remhnode(HistNode_list* l, HistNode* hn);
static void	printhnodeindices(char* label, HistNode_list* l);
static void	dumphistory(void);
static AuthInfo*	getauth(Rune* chal);
static Rune*	tobase64(Rune* a);
static int	c64(int c);
static void	dosaveas(ByteSource* bsmain);
static void	handleprogress(EV e);
static void	showstatus(Rune* msg);
static void	alert(void *arg);
static void	showurl(Rune* u);

void
threadmain(int argc, char* argv[])
{
	int	showprog;
	ResourceState	curres;
	ResourceState	newres;
	int	v;
	GoSpec*	g;
	void*	alertargs[2];
	int	dfile;
	EV	ev;
	uchar*	fname;

	meminit(GRmain);
	if(initdraw(nil, nil, "i") < 0)
		fatalerror("initdraw failed");
	iutilsinit(argc, argv);
	dbgres = config.dbg['r'];
	showprog = config.showprogress;
	if(dbg && config.dbgfile != nil) {
		fname = fromStr(config.dbgfile, Strlen(config.dbgfile), UTF_8);
		dfile = create((char*)fname, OWRITE, 0644);
		if(dfile >= 0) {
			dup(dfile, 1);
			trace("debug output\n");
		}
	}
	curres = curresstate();
	if(dbgres) {
		resstateprint(startres, "starting resources");
		curres = curresstate();
	}
	guiinit();
	if(dbgres) {
		newres = curresstate();
		resstateprint(resstatesince(newres, curres), "difference after made screen windows");
		curres = newres;
	}
	start();
	gochan = chancreate(sizeof(GoSpec*), 1);
	if(gochan == nil)
		fatalerror("Can't make go channel");
	g = newget(config.starturl, FTtop);
	if(dbgres) {
		newres = curresstate();
		resstateprint(resstatesince(newres, curres), "difference after initial configure");
		curres = newres;
	}
	if(threadcreate(netget, nil, STACKSIZE) < 0)
		fatalerror("Can't make netget thread!");
	if(threadcreate(go, g, STACKSIZE) < 0)
		fatalerror("Can't make go thread!");
	while(1) {
		if(recv(evchan, &ev) == -1)
			goto maindone;
		if(dbg > 1) {
			if(ev.tag == EVmousetag) {
				if(dbg > 2 || ev.u.mouse.mtype != Mmove)
					trace("Ev: %M\n", &ev);
			}
			else
				trace("Ev: %M\n", &ev);
		}
		switch(ev.tag) {
		case EVkeytag:
			switch(ev.u.keychar) {
			case Kdown:
				yscroll(curframe, CAscrollpage, 1);
				break;
			case Kup:
				yscroll(curframe, CAscrollpage, -1);
				break;
			case Khome:
				yscroll(curframe, CAscrollpage, -10000);
				break;
			default:
				g = handlekey(ev);
				break;
			}
			break;

		case EVmousetag:
			g = handlemouse(ev);
			break;

		case EVresizetag:
			redrawctl(1);
			redrawmain(1);
			redrawprog(1);
			curframe = top;
			g = newspecial(GoHistnode, histfind(0));
			break;

		case EVexposetag:
			g = nil;
			break;

		case EVhidetag:
			g = nil;
			break;

		case EVquittag:
			goto maindone;
			break;

		case EValerttag:
			alertargs[0] = ev.u.alert.msg;
			alertargs[1] = ev.u.alert.sync;
			if(proccreate(alert, (void*)alertargs, STACKSIZESMALL) < 0) {
				trace("can't create alert proc\n");
				if(sendul((Channel*)ev.u.alert.sync, 1) < 0)
					finish();
			}
			g = nil;
			break;

		case EVformtag:
			g = formaction(ev.u.form.frameid, ev.u.form.formid, ev.u.form.ftype);
			break;

		case EVgotag:
			if(ev.u.go.url == nil)
				g = newspecial(GoHistnode, histfind(0));
			else
				g = newget(ev.u.go.url, ev.u.go.target);
			break;

		case EVprogresstag:
			if(showprog)
				handleprogress(ev);
			g = nil;
			break;

		default:
			trace("unknown event tag %d\n", ev.tag);
			g = nil;
		}
		if(g != nil) {
			abortgo();
			v = nbsend(gochan, &g);
			if(v < 0)
				goto maindone;
			if(v == 0) {
				// discard the gospec: last one hasn't been
				// acted on yet.
				// (We do this to avoid having events pile up
				// while waiting for abort to have effect)
				trace("go pileup; discard\n");
				g = nil;
			}
		}
	}

maindone:
	finish();
}

static void
start(void)
{
	Image*	i;
	Image*	m;

	top = newframe();
	ctlframe = newframe();		// not really a frame, but need cim pointer
	progframe = newframe();	// not really a frame, but need cim pointer
	popupframe = newframe();	// not really a frame, but need cim pointer
	curframe = top;
	ctllay.logoicon = geticon(IClogo, nil);
	i = geticon(ICback, &m);
	ctllay.controls[CLCbackbut] = newbutton(ctlframe, i, m, L"Go back", nil, 1, 1);
	i = geticon(ICfwd, &m);
	ctllay.controls[CLCfwdbut] = newbutton(ctlframe, i, m, L"Go forward", nil, 1, 1);
	i = geticon(ICreload, &m);
	ctllay.controls[CLCreloadbut] = newbutton(ctlframe, i, m, L"Reload current page", nil, 1, 1);
	i = geticon(ICstop, &m);
	ctllay.controls[CLCstopbut] = newbutton(ctlframe, i, m, L"Stop", nil, 1, 1);
	i = geticon(IChist, &m);
	ctllay.controls[CLChistbut] = newbutton(ctlframe, i, m, L"Show history", nil, 1, 1);
	i = geticon(ICbmark, &m);
	ctllay.controls[CLCbmarkbut] = newbutton(ctlframe, i, m, L"Show bookmarks", nil, 1, 1);
	i = geticon(ICexit, &m);
	ctllay.controls[CLCexitbut] = newbutton(ctlframe, i, m, L"Exit", nil, 1, 1);
	ctllay.controls[CLCentry] = newentry(ctlframe, 30, 1, 0);
	disable(ctllay.controls[CLCbackbut]);
	disable(ctllay.controls[CLCfwdbut]);
	disable(ctllay.controls[CLCstopbut]);
	ctllay.status = nil;
	keyfocus = frameloc(ctllay.controls[CLCentry], ctlframe);
	gainfocus(ctllay.controls[CLCentry]);
	popuplay.kind = PopupNone;
	popupans = chancreate(sizeof(PopupAns), 0);
	redrawctl(1);
	redrawmain(1);
	redrawprog(1);
}

static void
redrawctl(int resized)
{
	Rectangle	r;
	Point	p;
	Image*	li;
	int	lw;
	int	lh;
	int 	i;
	Control*	b;
	int	x;
	int	y;

	pushclipr(rctl);
	r = insetrect(rctl, ReliefBd);
	drawfill(screen, r, Grey);
	drawrelief(screen, r, ReliefRaised);
	replaceclipr(r);
	p = ctllay.logopos;
	li = ctllay.logoicon;
	lw = Dx(li->r);
	lh = Dy(li->r);
	if(resized) {
		ctlframe->r = insetrect(rctl, 2*ReliefBd);
		ctlframe->cr = r;
		ctlframe->cim = screen;
		p = addpt(r.min, Pt(7, 7));
		ctllay.logopos = p;
		x = p.x + lw;
		y = p.y;
		x += SP;
		for(i = 0; i < NUMCLCS; i++) {
			b = ctllay.controls[i];
			b->r = rectsubpt(b->r, b->r.min);
			if(i == CLCentry || i == CLCexitbut)
				continue;
			b->r = rectaddpt(b->r, Pt(x, y));
			x += Dx(b->r) + SP2;
		}
		x += SP2;
		ctllay.entrypos = Pt(x, y);
		ctllay.controls[CLCentry]->r = Rpt(ctllay.entrypos, Pt(r.max.x - 7, y + 22));
		ctllay.controls[CLCentry]->r.max.x -= Dx(ctllay.controls[CLCexitbut]->r) + SP2;
		ctllay.controls[CLCexitbut]->r = rectaddpt(ctllay.controls[CLCexitbut]->r,
				Pt(ctllay.controls[CLCentry]->r.max.x + SP2, y));
	}
	ctllay.statuspos = Pt(p.x + lw + SP, p.y + Dy((ctllay.controls[CLCbackbut])->r) + SP);
	draw(screen, Rpt(p, addpt(p, Pt(lw, lh))), li, nil, ZP);
	for(i = 0; i < NUMCLCS; i++)
		drawctl(ctllay.controls[i], 0);
	showstatus(ctllay.status);
	popclipr();
	flushimage(display, 1);
}

static void
redrawmain(int resized)
{
	if(resized) {
		top->r = insetrect(rmain, 2*ReliefBd);
		top->cr = top->r;
		top->cim = screen;
		resetframe(top);
		imcacheresetlimits();
	}
	pushclipr(rmain);
	drawrelief(screen, insetrect(top->r, -ReliefBd), ReliefRaised);
	drawrelief(screen, top->r, ReliefSunk);
	replaceclipr(top->r);
	drawfill(screen, top->r, White);
	popclipr();
	flushimage(display, 1);
}

static void
redrawprog(int resized)
{
	Rectangle	r;
	int	i;
	Control**	newbox;
	Point	p;
	int	nbox;
	int	nboxold;
	Control*	c;
	int	vo;

	if(!config.showprogress)
		return;
	pushclipr(rprog);
	r = insetrect(rprog, ReliefBd);
	drawfill(screen, r, Grey);
	drawrelief(screen, r, ReliefRaised);
	replaceclipr(r);
	if(resized) {
		p = proglay.first;
		nbox = proglay.boxlen;
		progframe->r = insetrect(rprog, 2*ReliefBd);
		progframe->cr = progframe->r;
		progframe->cim = screen;
		nboxold = nbox;
		c = newprogbox(progframe);
		vo = (Dy(r) - Dy(c->r))/2;
		p = addpt(r.min, Pt(7, vo));
		proglay.first = p;
		proglay.dx = Dx(c->r) + SP2;
		nbox = (Dx(r) - 2*7 - SP2)/proglay.dx;
		if(nboxold != nbox) {
			newbox = (Control**)erealloc(proglay.box, nbox * sizeof(Control*));
			proglay.box = newbox;
			proglay.boxlen = nbox;
			for(i = nboxold; i < nbox; i++)
				proglay.box[i] = newprogbox(progframe);
		}
		for(i = 0; i < nbox; i++) {
			c = proglay.box[i];
			c->r = rectsubpt(c->r, c->r.min);
			c->r = rectaddpt(c->r, Pt(p.x + i*proglay.dx, p.y));
		}
	}
	for(i = 0; i < proglay.nused; i++)
		drawctl(proglay.box[i], 0);
	popclipr();
	flushimage(display, 1);
}

// Display popup of given kind (s is more info for drawing, depending on kind),
// and return user's answer on popupans as (code, string), where code will
// be -1 for error, 0 when the user hit cancel, and 1 when the user hit OK.
static void
dopopup(int kind, Rune* s)
{
	int	w;
	int	h;
	int	n;
	int	nw;
	int	i;
	int	k;
	int	nline;
	int	curlen;
	int	curlinestart;
	Control*	cok;
	Control*	ccancel;
	Point	p;
	Control*	oldc;
	Control*	newc;
	Control*	chead;
	Control*	crealm;
	Control*	cunlab;
	Control*	cpwlab;
	Control*	cuser;
	Control*	cpass;
	Control*	clab;
	Control*	cfile;
	Rune*	l;
	Control*	c;
	PopupAns	pans;
	Rune*	words[BIGBUFSIZE];
	int	wordlens[BIGBUFSIZE];
	Rune*	lines[SMALLBUFSIZE];

	cok = newbutton(popupframe, nil, nil, L"Ok", nil, 1, 1);
	ccancel = newbutton(popupframe, nil, nil, L"Cancel", nil, 1, 1);
	p = Pt(0, 0);
	switch(kind) {
	case PopupAuth:
		popuplay.ncontrols = 8;
		popuplay.controls = (Control**)emalloc(popuplay.ncontrols*sizeof(Control*));
		popuplay.controls[0] = cuser = newentry(popupframe, 30, 1, 0);
		popuplay.controls[1] = cpass = newentry(popupframe, 30, 1, 0);
		popuplay.controls[2] = chead = newlabel(popupframe, L"Type your user name and password");
		popuplay.controls[3] = crealm = newlabel(popupframe, Strdup2(L"Resource: ", s));
		popuplay.controls[4] = cunlab = newlabel(popupframe, Strdup(L"User Name: "));
		popuplay.controls[5] = cpwlab = newlabel(popupframe, Strdup(L"Password: "));
		popuplay.controls[6] = cok;
		popuplay.controls[7] = ccancel;
		w = SP +
			max(chead->r.max.x, max(crealm->r.max.x, cunlab->r.max.x + cuser->r.max.x)) +
			SP;
		w = min(w, Dx(rmain) - 2*SP);
		h = SP + chead->r.max.y + SP + crealm->r.max.y + SP + cuser->r.max.y + SP +
			cpass->r.max.y + 2*SP + cok->r.max.y + SP;
		popupwin = makepopup(w, h);
		if(popupwin == nil) {
			pans.code = -1;
			pans.s = nil;
			if(send(popupans, &pans) < 0)
				finish();
			freectls(popuplay.controls, popuplay.ncontrols);
			return;
		}
		p = addpt(popupwin->r.min, Pt(SP, SP));
		chead->r = rectaddpt(chead->r, p);
		p.y += Dy(chead->r) + SP;
		crealm->r = rectaddpt(crealm->r, p);
		p.y += Dy(crealm->r) + SP;
		cunlab->r = rectaddpt(cunlab->r, p);
		cuser->r = rectaddpt(cuser->r, addpt(p, Pt(Dx(cunlab->r), 0)));
		p.y += Dy(cuser->r) + SP;
		cpwlab->r = rectaddpt(cpwlab->r, p);
		cpass->r = rectaddpt(cuser->r, Pt(0, Dy(cuser->r) + SP));
		p.y += Dy(cpass->r) + 2*SP;
		break;

	case PopupSaveAs:
		popuplay.ncontrols = 4;
		popuplay.controls = (Control**)emalloc(popuplay.ncontrols*sizeof(Control*));
		popuplay.controls[0] = cfile = newentry(popupframe, 40, 1, 0);
		popuplay.controls[1] = clab = newlabel(popupframe, L"Save As: ");
		popuplay.controls[2] = cok;
		popuplay.controls[3] = ccancel;
		((Centry*)cfile)->s = s;
		w = SP + clab->r.max.x + cfile->r.max.x + SP;
		h = SP + cfile->r.max.y + 2*SP + cok->r.max.y + SP;
		popupwin = makepopup(w, h);
		if(popupwin == nil) {
			pans.code = -1;
			pans.s = nil;
			if(send(popupans, &pans) < 0)
				finish();
			freectls(popuplay.controls, popuplay.ncontrols);
			return;
		}
		p = addpt(popupwin->r.min, Pt(SP, SP));
		clab->r = rectaddpt(clab->r, p);
		cfile->r = rectaddpt(cfile->r, addpt(p, Pt(Dx(clab->r) + SP, 0)));
		p.y += Dy(clab->r) + 2*SP;
		break;

	case PopupAlert:
		ccancel = nil;

		// split s up into lines of at most ALERTLINELEN characters each
		n = splitall(s, Strlen(s), L" \t\n\r", words, wordlens, BIGBUFSIZE);
		curlen = 0;
		curlinestart = 0;
		nline = 0;
		for(i = 0; i < n; i++) {
			nw = wordlens[i];
			if(curlen + nw >= ALERTLINELEN) {
				l = newstr(curlen);
				if(nline < SMALLBUFSIZE)
					lines[nline++] = l;
				for(k = curlinestart; k < i; k++) {
					l = Stradd(l, words[k], wordlens[k]);
					if(k < i-1)
						*l++ = ' ';
				}
				*l = 0;
				curlinestart = i;
				curlen = nw;
			}
			else
				curlen += nw + (curlen > 0);
		}
		if(curlen != 0) {
			l = newstr(curlen);
			if(nline < SMALLBUFSIZE)
				lines[nline++] = l;
			for(k = curlinestart; k < i; k++) {
				l = Stradd(l, words[k], wordlens[k]);
				if(k < i-1)
					*l++ = ' ';
			}
			*l = 0;
		}
		popuplay.ncontrols = nline+1;
		popuplay.controls = (Control**)emalloc(popuplay.ncontrols*sizeof(Control*));
		popuplay.controls[0] = cok;
		w = SP + cok->r.max.x + SP;
		h = SP + 2*SP + cok->r.max.y + SP;
		for(k = 0; k < nline; k++) {
			l = lines[k];
			c = newlabel(popupframe, l);
			popuplay.controls[k+1] = c;
			w = max(w, SP + Dx(c->r) + SP);
			h += Dy(c->r);
		}
		popupwin = makepopup(w, h);
		if(popupwin == nil) {
			pans.code = -1;
			pans.s = nil;
			if(send(popupans, &pans) < 0)
				finish();
			freectls(popuplay.controls, popuplay.ncontrols);
			return;
		}
		p = addpt(popupwin->r.min, Pt(SP, SP));
		for(k = 1; k <= nline; k++) {
			c = popuplay.controls[k];
			c->r = rectaddpt(c->r, p);
			p.y += Dy(c->r);
		}
		p.y += 2*SP;
		break;

	default:
		assert(0);
		break;
	}
	if(ccancel == nil) {
		p.x += (Dx(popupwin->r) - cok->r.max.x)/2 - SP;
		cok->r = rectaddpt(cok->r, p);
	}
	else {
		p.x += (Dx(popupwin->r) - (cok->r.max.x + SP + ccancel->r.max.x))/2 - SP;
		cok->r = rectaddpt(cok->r, p);
		p.x += Dx(cok->r) + SP;
		ccancel->r = rectaddpt(ccancel->r, p);
	}
	popupframe->cim = popupwin;
	popuplay.kind = kind;
	popuplay.okbut = cok;
	popuplay.cancelbut = ccancel;
	popupactive = 1;
	oldc = keyfocus->le[keyfocus->n - 1].control;
	if(oldc != nil)
		losefocus(oldc);
	newc = popuplay.controls[0];
	keyfocus = frameloc(newc, popupframe);
	gainfocus(newc);
	redrawpopup();
}

static void
redrawpopup(void)
{
	Rectangle	r;
	int i;

	if(popuplay.kind == PopupNone)
		return ;
	assert(popupwin != nil);
	popupwin->clipr = popupwin->r;
	r = insetrect(popupwin->r, ReliefBd);
	drawfill(popupwin, r, Grey);
	drawrelief(popupwin, r, ReliefRaised);
	popupwin->clipr = r;
	for(i = 0; i < popuplay.ncontrols; i++)
		drawctl(popuplay.controls[i], 0);
	flushimage(display, 1);
}

static void
finishpopup(int code)
{
	PopupAns	pans;

	pans.code = code;
	pans.s = nil;
	switch(popuplay.kind) {
	case PopupAuth:
		pans.s = Strdup3(ctlentrytext(popuplay.controls[0]), L":",
				ctlentrytext(popuplay.controls[1]));
		break;

	case PopupSaveAs:
		pans.s = ctlentrytext(popuplay.controls[0]);
		break;
	}
	popuplay.kind = PopupNone;
	popuplay.controls = nil;
	popuplay.okbut = nil;
	popuplay.cancelbut = nil;
	popupframe->cim = nil;
	popupwin = nil;
	popupactive = 0;
	freectls(popuplay.controls, popuplay.ncontrols);
	if(send(popupans, &pans) < 0)
		finish();
}

// assuming c is an entry control, return its contents
// (caller must dup, if it wants to save the result)
static Rune*
ctlentrytext(Control* c)
{
	assert(c->tag == Centrytag);
	return ((Centry*)c)->s;
}

// Return a Loc representing a control in the frame f
static Loc*
frameloc(Control* c, Frame* f)
{
	Loc*	loc;

	loc = newloc();
	addloc(loc, LEframe, f->r.min);
	loc->le[loc->n - 1].frame = f;
	addloc(loc, LEcontrol, c->r.min);
	loc->le[loc->n - 1].control = c;
	return loc;
}

// Frame oldf is being reset, so change keyfocus back to ctllay.entry
static void
resetkeyfocus(Frame* oldf)
{
	USED(oldf);
	keyfocus = frameloc(ctllay.controls[CLCentry], ctlframe);
}

// If mouse event results in command to navigate somewhere else,
// return a GoSpec ref, else nil.
// TODO: deactivate activated controls if mouse leaves the area;
// perhaps do grabs?
static GoSpec*
handlemouse(EV e)
{
	Point	p;
	GoSpec*	g;
	Control*	oldc;
	Control*	c;
	Anchor*	a;
	Item*	it;
//	ScriptEvent*	se;
//	int	hasscripts;
	Frame*	f;
	int	n1;
	Loc*	loc;
	Rune*	msg;
	int	i;
	Cprogbox* pc;
	int	ns;
	char	buf[SMALLBUFSIZE];

	p = e.u.mouse.p;
	g = nil;
	if(popupactive) {
		for(i = 0; i < popuplay.ncontrols; i++) {
			c = popuplay.controls[i];
			if(ptinrect(p, c->r)) {
				if(dbg > 1)
					trace("mouse in popup control\n");
				switch(domouse(c, p, e.u.mouse.mtype)) {
				case CAbuttonpush:
					if(c == popuplay.okbut)
						finishpopup(1);
					else if(c == popuplay.cancelbut)
						finishpopup(0);
					break;

				case CAkeyfocus:
					if(dbg > 1)
						printloc(keyfocus, "old focus");
					oldc = keyfocus->le[keyfocus->n - 1].control;
					if(oldc != nil)
						losefocus(oldc);
					keyfocus = frameloc(c, popupframe);
					gainfocus(c);
					if(dbg > 1)
						printloc(keyfocus, "new focus");
					break;
				}
			}
		}
	}
	else if(ptinrect(p, rctl)) {
		for(i = 0; i < NUMCLCS; i++) {
			c = ctllay.controls[i];
			if(ptinrect(p, c->r)) {
				if(dbg > 1)
					trace("mouse in controlwin control\n");
				switch(domouse(c, p, e.u.mouse.mtype)) {
				case CAbuttonpush:
					switch(i) {
					case CLCbackbut:
						g = newspecial(GoHistnode, histfind(-1));
						break;
					case CLCfwdbut:
						g = newspecial(GoHistnode, histfind(1));
						break;
					case CLCreloadbut:
						g = newspecial(GoHistnode, histfind(0));
						break;
					case CLChistbut:
						g = newspecial(GoHistory, nil);
						break;
					case CLCbmarkbut:
						g = newspecial(GoBookmarks, nil);
						break;
					case CLCstopbut:
						g = newspecial(GoStop, nil);
						break;
					case CLCexitbut:
						finish();
					}
					break;

				case CAflyover:
					if(c->tag == Cbuttontag)
						showstatus(((Cbutton*)c)->label);
					break;

				case CAkeyfocus:
					if(dbg > 1)
						printloc(keyfocus, "old focus");
					oldc = keyfocus->le[keyfocus->n - 1].control;
					if(oldc != nil)
						losefocus(oldc);
					keyfocus = frameloc(c, ctlframe);
					gainfocus(c);
					if(dbg > 1)
						printloc(keyfocus, "new focus");
					break;
				}
				break;
			}
		}
	}
	else if(ptinrect(p, rmain)) {
		loc = findloc(top, p, nil);
		if(loc != nil) {
			if(dbg > 1)
				printloc(loc, "mouse loc");
			f = lastframe(loc);
//			hasscripts = f->doc->hasscripts;
			if(e.u.mouse.mtype != Mmove)
				curframe = f;
			n1 = loc->n - 1;
			switch(loc->le[n1].kind) {
			case LEitem:
				it = loc->le[n1].item;
				if(it->anchorid >= 0) {
					for(a = f->doc->anchors; a != nil; a = a->next) {
						if(a->index == it->anchorid) {
							if(dbg > 1)
								trace("in anchor %d, href=%U\n", a->index, a->href);
							if(e.u.mouse.mtype == Mlbuttonup)
								g = anchorgospec(it, a, loc->pos);
							else if(e.u.mouse.mtype == Mmbuttonup) {
								showstatus(a->href->url);
							}
						}
					}
				}
				break;

			case LEcontrol:
				c = loc->le[n1].control;
				switch(domouse(c, p, e.u.mouse.mtype)) {
				case CAbuttonpush:
//					if(hasscripts && c->ff != nil && c->ff->events != nil) {
//						se = copyScriptEvent((ScriptEvent(Aonclick, f->id,
//							c->ff->form->formid, c->ff->fieldid, -1, e->p.x, e->p.y, 1));
//						/* TODO spawn */do_on(se);
//						return nil;
//					}
					g = pushaction(c, loc);
					break;

				case CAkeyfocus:
					if(dbg > 1)
						printloc(keyfocus, "old focus");
					oldc = keyfocus->le[keyfocus->n - 1].control;
					if(oldc != nil)
						losefocus(oldc);
					keyfocus = frameloc(c, ctlframe);
					gainfocus(c);
					if(dbg > 1)
						printloc(keyfocus, "new focus");
					break;
				}
				break;
			}
		}
	}
	else if(ptinrect(p, rprog)) {
		for(i = 0; i < proglay.boxlen; i++) {
			c = proglay.box[i];
			if(ptinrect(p, c->r)) {
				if(dbg > 1)
					trace("mouse in progbox control %d\n", i);
				switch(domouse(c, p, e.u.mouse.mtype)) {
				case CAbuttonpush:
					switch(c->tag) {
					case Cprogboxtag:
						assert(c->tag == Cprogboxtag);
						pc = (Cprogbox*)c;
						ns = snprint(buf, sizeof(buf), "%S, %d%% done", pc->src, pc->pcnt);
						if(pc->err != nil)
							ns += snprint(buf+ns, sizeof(buf)-ns, ", %S", pc->err);
						if(dbg)
							ns += snprint(buf+ns, sizeof(buf)-ns, ", bsid=%d", pc->bsid);
						msg = toStr((uchar*)buf, ns, UTF_8);
						showstatus(msg);
						break;
					}
					break;
				}
			}
		}
	}
	return g;
}

// If key event results in command to navigate somewhere else,
// return a GoSpec ref, else nil.
static GoSpec*
handlekey(EV e)
{
	Loc*	loc;
	int	n1;
	ParsedUrl*	u;
	Rune*	s;
	Centry*	ce;
	Control*	c;

	loc = keyfocus;
	if(dbg > 1)
		printloc(loc, "key focus loc");
	n1 = loc->n - 1;
	switch(loc->le[n1].kind) {
	case LEcontrol:
		c = loc->le[n1].control;
		switch(c->tag) {
		case Centrytag:
			ce = (Centry*)c;
			switch(dokey(c, e.u.keychar)) {
			case CAreturnkey:
				if(c == ctllay.controls[CLCentry]) {
					s = ce->s;
					if(s != nil) {
						u = makeurl(s, 1);
						return newget(u, FTtop);
					}
				}
				else if(popupactive) {
					finishpopup(1);
					return nil;
				}
				else if(c->ff != nil)
					return form_submit(c->f, c->ff->form, ZP, c);
				break;

			case CAtabkey:
				break;
			}
			break;
		}
		break;
	}
	return nil;
}

// Run as separate thread
// Arg has first GoSpec* as arg
static void
go(void* arg)
{
	GoSpec*	g;
	int	origkind;
	int	rv;
	HistNode*	hn;
	Frame*	f;
	ParsedUrl*	url;
	Rune*	s;
	EV prog;

	g = (GoSpec*)arg;
	while(1) {
		origkind = g->kind;
		hn = nil;
		switch(g->kind) {
		case GoNormal:
			break;
	
		case GoHistnode:
			hn = g->histnode;
			if(hn == nil)
				return;
			g = hn->topconfig->gospec;
			break;
	
		case GoBookmarks:
			s = Strdup3(L"file:", config.userdir, L"/bookmarks.html");
			url = makeurl(s, 0);
			g = newget(url, FTtop);
			break;
	
		case GoHistory:
			s = Strdup3(L"file:", config.userdir, L"/history.html");
			url = makeurl(s, 0);
			dumphistory();
			g = newget(url, FTtop);
			break;
		}
		switch(g->target) {
		case FTtop:
			curframe = top;
			break;
		case FTself:
			break;	// curframe is already OK
		case FTparent:
			if(curframe->parent != nil)
				curframe = curframe->parent;
			break;
		case FTblank:
			curframe = top;	// we don't create new browsers...
			break;
		default:
			// this is recommended "current practice"
			curframe = findnamedframe(curframe, targetname(g->target));
			if(curframe == nil) {
				curframe = findnamedframe(top, targetname(g->target));
				if(curframe == nil)
					curframe = top;
			}
		}
		if(g->kind == GoStop) {
			if(dbg)
				trace("\n\nSTOP\n");
			showstatus(L"Stopped");
		}
		else {
			f = curframe;
			if(dbg) {
				trace("\n\nGO TO %U\n", g->url);
				if(g->target != FTtop)
					trace("target frame name=%S\n", f->name);
			}
			if(g->url->nfrag != 0 && origkind == GoNormal
					&& f->doc != nil && f->doc->src != nil && urlequal(g->url, f->doc->src)) {
				go_local(f, g->url->frag);
				return ;
			}
			if(config.showprogress) {
				prog.tag = EVprogresstag;
				prog.u.progress.bsid = -1;
				prog.u.progress.state = 0;
				prog.u.progress.pcnt = 0;
				prog.u.progress.s = nil;
				if(send(evchan, &prog) < 0)
					finish();
			}
			enable(ctllay.controls[CLCstopbut]);
			rv = get(g, f, origkind, hn);
			disable(ctllay.controls[CLCstopbut]);
			if(rv) {
				showstatus(L"Done");
				checkrefresh(f);
			}
		}
		if(recv(gochan, &g) < 0)
			finish();
	}
}

// The important invariant in get/layout is that we keep
// track of every ByteSource returned by startreq, and
// ensure that we freebs each of them.
// And we can't start a new get() until all the ones started
// for the current get() have been freed.
static int
get(GoSpec* g, Frame* f, int origkind, HistNode* hn)
{
	ResourceState	curres;
	ResourceState	newres;
	Rune*	sdest;
	ByteSource*	bsmain;
	Header*	hdr;
	ReqInfo*	ri;
	int	authtried;
	AuthInfo*	auth;
	int	nredirs;
	ByteSource*	bs;
	int	use;
	int	error;
	Rune*	challenge;
	ParsedUrl*	newurl;
	GoSpec*	gs;
	Framelist*	kl;
	Frame*	k;
	int	i;
	Rune*	msg;
	uchar*	body;
	int		bodylen;

	if(dbgres) {
		imcacheclear();
		curres = curresstate();
	}
	sdest = g->url->url;
	msg = Strdup2(L"Fetching ", sdest);
	showstatus(msg);
	if(g->body != nil) {
		body = fromStr(g->body, Strlen(g->body), UTF_8);
		bodylen = strlen((char*)body);
	}
	else {
		body = nil;
		bodylen = 0;
	}
	ri = newreqinfo(g->url, g->meth, body, bodylen, g->auth, g->target);
	authtried = 0;
	auth = nil;
	for(nredirs = 0; ; nredirs++) {
		bsmain = startreq(ri);
		if(bsmain->err) {
			showstatus(errphrase(bsmain->err));
			freebs(bsmain);
			goto errret;
		}
		bs = waitreq();
		if(bs == nil)
			goto errret;
		assert(bs == bsmain);
		if(bsmain->err != 0) {
			showstatus(errphrase(bsmain->err));
			freebs(bsmain);
			goto errret;
		}
		hdr = bsmain->hdr;
		use = hdraction(bsmain, 1, nredirs, &error, &challenge, &newurl);
		if(challenge != nil) {
			if(authtried) {
				error = ERRauthfailed;
				use = 1;
			}
			else {
				auth = getauth(challenge);
				if(auth != nil) {
					ri->auth = auth->credentials;
					authtried = 1;
					freebs(bsmain);
					continue;
				}
				else {
					error = ERRauthfailed;
					use = 1;
				}
			}
		}
		if(error)
			showstatus(errphrase(error));
		else {
			showstatus(hcphrase(hdr->code));
			if(authtried) {
				auth->next = auths;
				auths = auth;
			}
		}
		if(newurl != nil) {
			ri->url = newurl;
			ri->method = HGet;
			freebs(bsmain);
			continue;
		}
		if(use == 0) {
			freebs(bsmain);
			goto errret;
		}
		break;
	}
	if(dbgres > 1) {
		newres = curresstate();
		resstateprint(resstatesince(newres, curres), "resources to get header");
		curres = newres;
	}
	if(hdr->length > 0 && (hdr->mtype == TextHtml || hdr->mtype == TextPlain || supported(hdr->mtype))) {
		showurl(sdest);
		histadd(f, g, origkind);
		resetkeyfocus(f);
		layout(f, bsmain, nil);
		histupdate(f);
		if(dbgres > 1) {
			newres = curresstate();
			resstateprint(resstatesince(newres, curres), "resources to get page and do layout");
			curres = newres;
		}
		if(f->kids != nil) {
			i = 0;
			for(kl = f->kids; kl != nil; kl = kl->next) {
				k = kl->val;
				if(k->src != nil) {
					if(hn != nil)
						gs = hn->kidconfigs[i]->gospec;
					else
						gs = newget(copyurl(k->src), FTself);
					if(dbg)
						trace("get child frame %U\n", gs->url);
					if(!get(gs, k, GoNormal, nil))
						goto errret;
				}
				i++;
			}
		}
		if(g->url->nfrag != 0)
			go_local(f, g->url->frag);
	}
	else {
		if(hdr->length == 0) {
			showstatus(L"Empty page");
			freebs(bsmain);
		}
		else {
			msg = Strdup2(L"Unsupported media type: ", mnames[hdr->mtype]);
			showstatus(msg);
			dosaveas(bsmain);	// frees bsmain when done
		}
	}
	if(dbgres == 1) {
		newres = curresstate();
		resstateprint(resstatesince(newres, curres), "resources to do page");
		curres = newres;
	}
	return 1;

errret:
	return 0;
}

static void
go_local(Frame* f, Rune* loc)
{
	Loc*	dloc;
	Point	p;
	DestAnchor*	d;

	if(dbg)
		trace("go to local destination %S\n", loc);
	for(d = f->doc->dests; d != nil; d = d->next) {
		if(!Strcmp(d->name, loc)) {
			dloc = findloc(f, ZP, d->item);
			if(dloc == nil) {
				if(warn)
					trace("couldn't find item for destination anchor %S\n", loc);
				return ;
			}
			p = sptolp(f, dloc->le[dloc->n - 1].pos);
			yscroll(f, CAscrollabs, p.y);
			return;
		}
	}
	if(warn)
		trace("couldn't find destination anchor %S\n", loc);
}

// If refresh has been set in f (i.e., client pull),
// pause the appropriate amount of time and then go to new place
static void
checkrefresh(Frame* f)
{
	Rune*	s;
	int	seconds;
	ParsedUrl*	url;
	int	n;
	EV	e;
	EV*	goe;
	Rune*	a[2];
	int		na[2];

	if(f->doc != nil && f->doc->refresh != nil) {
		seconds = 0;
		url = nil;
		n = splitall(f->doc->refresh, Strlen(f->doc->refresh), L"; ", a, na, 2);
		if(n > 0) {
			seconds = Strtol(a[0], nil, 10);
			if(n > 1) {
				s = a[1];
				if(na[1] > 4 && !Strncmpci(s, 4, L"url=")) {
					s = Strndup(s+4, na[1]-4);
					url = makeurl(s, 0);
					url = makeabsoluteurl(url, f->doc->base);
				}
			}
		}
		goe = (EV*)emalloc(sizeof(EV));
		if(url == nil)
			*goe = evgo(nil, FTtop, EGreload, f->id);
		else
			*goe = evgo(copyurl(url), targetid(f->name), EGnormal, f->id);
		e.tag = EVdelaytag;
		e.genframeid = f->id;
		e.u.delay.millisecs = seconds*1000;
		e.u.delay.ev = goe;
		if(send(evchan, &e) < 0)
			finish();
	}
}

// Do depth first search from f, looking for frame with given name.
static Frame*
findnamedframe(Frame* f, Rune* name)
{
	Framelist*	kl;
	Frame*	a;

	if(!Strcmp(f->name, name))
		return f;
	for(kl = f->kids; kl != nil; kl = kl->next) {
		a = findnamedframe(kl->val, name);
		if(a != nil)
			return a;
	}
	return nil;
}

// Similar, but look for frame id, starting from f
static Frame*
findframe(Frame* f, int id)
{
	Framelist*	kl;
	Frame*	a;

	if(f->id == id)
		return f;
	for(kl = f->kids; kl != nil; kl = kl->next) {
		a = findframe(kl->val, id);
		if(a != nil)
			return a;
	}
	return nil;
}

// Return Gospec resulting from button up in anchor a, at offset pos inside item it.
static GoSpec*
anchorgospec(Item* it, Anchor* a, Point p)
{
	GoSpec*	g;
	ParsedUrl*	u;
	int	target;
	int	x;
	int	y;
	Rune*	sx;
	Rune*	sy;
	Rune*	q;
	CImage*	ci;
	Iimage*	i;
	Ifloat*	f;

	g = nil;
	target = a->target;
	u = nil;
	switch(it->tag) {
	case Iimagetag:
		i = (Iimage*)it;
		ci = i->ci;
		if(ci->mims != nil) {
			if(i->map != nil) {
				u = findhit(i->map, p, ci->width, ci->height, &target);
			}
			else if(a->href != nil && (it->state&IFsmap)) {
				x = min(max(p.x - (i->hspace + i->border), 0), ci->width - 1);
				y = min(max(p.y - (i->vspace + i->border), 0), ci->height - 1);
				sx = ltoStr(x);
				sy = ltoStr(y);
				q = Strdup3(sx, L",", sy);
				u = makequeryurl(a->href, q);
			}
		}
		break;

	case Ifloattag:
		f = (Ifloat*)it;
		return anchorgospec(f->item, a, p);

	default:
		u = copyurl(a->href);
	}
	if(u != nil)
		g = newget(u, target);
	return g;
}

// Control c has been pushed.
// Find the form it is in and perform required action (reset, or submit).
// If a submit, the return value is the place to go to.
static GoSpec*
pushaction(Control* c, Loc* loc)
{
	Formfield*	ff;
	Frame*	f;
	Cbutton*	b;

	if(c->tag == Cbuttontag) {
		b = (Cbutton*)c;
		ff = b->ff;
		f = b->f;
		if(ff != nil) {
			switch(ff->ftype) {
			case Fsubmit:
			case Fimage:
				return form_submit(c->f, ff->form, loc->pos, c);
				break;

			case Freset:
				form_reset(f, ff->form);
				break;
			}
		}
	}
	return nil;
}

static GoSpec*
form_submit(Frame* fr, Form* frm, Point p, Control* submitctl)
{
	Rune*	v;
	Rune*	sep;
	Rune*	t;
	Rune*	z;
	Strlist*	radiodone;
	Formfield*	f;
	int	nnonhidden;
	Strlist*	rl;
	int	checked;
	int	i;
	int	n;
	Cselect*	cs;
	Rune*	val;
	Control*	c;
	ParsedUrl*	action;

	if(submitctl != nil && submitctl->tag == Centrytag) {
		nnonhidden = 0;
		for(f = frm->fields; f != nil; f = f->next) {
			if(f->ftype != Fhidden)
				nnonhidden++;
		}
		if(nnonhidden > 1)
			return nil;
	}
	v = nil;
	sep = nil;
	radiodone = nil;
	for(f = frm->fields; f != nil; f = f->next) {
		if(f->name == nil)
			continue;
		val = nil;
		if(f->ctlid >= 0)
			c = fr->controls[f->ctlid];
		else
			c = nil;
		switch(f->ftype) {
		case Ftext:
		case Fpassword:
		case Ftextarea:
			if(c != nil)
				val = ((Centry*)c)->s;
			if(val != nil && !Strcmp(f->name, L"_ISINDEX_")) {
				if(sep != nil)
					v = Strdup2(v, sep);
				t = ucvt(val);
				v = Strdup2(v, t);
				goto floop_done;
			}
			break;

		case Fcheckbox:
		case Fradio:
			if(f->ftype == 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 = rl->next)
					if(!Strcmp(rl->val, f->name))
						goto floop_continue;
			}
			checked = 0;
			if(c != nil)
				if(c->tag == Ccheckboxtag)
					checked = ((Ccheckbox*)c)->flags&CFactive;
			if(checked) {
				val = f->value;
				if(f->ftype == Fradio)
					radiodone = newstrlist(f->name, radiodone);
			}
			else
				goto floop_continue;
			break;

		case Fhidden:
			val = f->value;
			break;

		case Fsubmit:
			if(submitctl != nil && f == submitctl->ff && Strcmp(f->name, L"_no_name_submit_"))
				val = f->value;
			else
				goto floop_continue;
			break;

		case Fselect:
			if(c != nil) {
				assert(c->tag == Cselecttag);
				cs = (Cselect*)c;
				n = listlen((List*)cs->options);
				for(i = 0; i < n; i++) {
					if(cs->options[i].selected) {
						if(sep != nil)
							v = Strdup2(v, sep);
						sep = L"&";
						t = ucvt(f->name);
						v = Strdup3(v, t, L"=");
						t = ucvt(cs->options[i].value);
						v = Strdup2(v, t);
					}
				}
				goto floop_continue;
			}
			break;

		case Fimage:
			if(submitctl != nil && f == submitctl->ff) {
				if(sep != nil)
					v = Strdup2(v, sep);
				sep = L"&";
				z = Strdup2(f->name, L".x");
				t = ucvt(z);
				v = Strdup3(v, t, L"=");
				z = ltoStr(max(p.x, 0));
				t = ucvt(z);
				v = Strdup3(v,  t, sep);
				z = Strdup2(f->name, L".y");
				t = ucvt(z);
				v = Strdup3(v, t, L"=");
				z = ltoStr(max(p.y, 0));
				t = ucvt(z);
				v = Strdup2(v,  t);
				goto floop_continue;
			}
			break;
		}
		if(val != nil) {
			if(sep != nil)
				v = Strdup2(v, sep);
			sep = L"&";
			t = ucvt(f->name);
			v = Strdup3(v, t, L"=");
			if(val != nil) {
				t = ucvt(val);
				v = Strdup2(v, t);
			}
		}
floop_continue:
		;
	}

floop_done:
	if(frm->method == HPost)
		return newpost(copyurl(frm->action), v, frm->target);
	else {
		action = makequeryurl(frm->action, v);
		return newget(action, frm->target);
	}
}

static Rune*
ucvt(Rune* s)
{
	Rune*	u;
	int	i;
	int	c;
	int	n;
	int	j;
	int	len;

	n = Strlen(s);
	len = 0;
	for(i = 0; i < n; i++) {
		c = s[i];
		if(inclass(c, L"- /$_@.!*'(),a-zA-Z0-9"))
			len++;
		else
			len += 3;
	}
	u = newstr(len);
	j = 0;
	for(i = 0; i < n; i++) {
		c = s[i];
		if(inclass(c, L"-/$_@.!*'(),a-zA-Z0-9"))
			u[j++] = c;
		else if(c == ' ')
			u[j++] = '+';
		else {
			u[j++] = '%';
			u[j++] = hexdigit((c >> 4)&15);
			u[j++] = hexdigit(c&15);
		}
	}
	u[j] = 0;
	return u;
}

static int
hexdigit(int v)
{
	if(0 <= v && v <= 9)
		return '0' + v;
	else
		return 'A' + v - 10;
}

static void
form_reset(Frame* fr, Form* frm)
{
	Formfield*	a;

	for(a = frm->fields; a != nil; a = a->next) {
		if(a->ctlid >= 0)
			resetctl(fr->controls[a->ctlid]);
	}
	flushimage(display, 1);
}

static GoSpec*
formaction(int frameid, int formid, int ftype)
{
	Frame*	f;
	Form*	frm;
	Docinfo*	d;

	trace("formaction %d %d %d\n", frameid, formid, ftype);
	f = findframe(top, frameid);
	if(f != nil) {
		d = f->doc;
		if(d != nil) {
			for(frm = d->forms; frm != nil; frm = frm->next) {
				if(frm->formid == formid) {
					if(ftype == EFsubmit) {
						return form_submit(f, frm, Pt(0, 0), nil);
					}
					else {
						form_reset(f, frm);
						return nil;
					}
				}
			}
		}
	}
	return nil;
}

// Find hit in a local map.
// If found, return action url, and target frame in *ptarget.
static ParsedUrl*
findhit(Map* map, Point p, int w, int h, int* ptarget)
{
	int	x;
	int	y;
	ParsedUrl*	dflt;
	int	dflttarg;
	int	xd;
	int	yd;
	double	xi;
	double	yi;
	double	xj;
	double	yj;
	int	np;
	double	xr;
	double	yr;
	int	j;
	int	i;
	Area	*a;
	Dimen*	c;
	int	nc;
	int	x1;
	int	y1;
	int	x2;
	int	y2;
	int	hit;

	x = p.x;
	y = p.y;
	dflt = nil;
	dflttarg = FTself;
	for(a = map->areas; a != nil; a = a->next) {
		c = a->coords;
		nc = a->ncoords;
		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;
		switch(a->shape) {
		case SHrect:
			if(nc == 4)
				hit = x1 <= x && x <= x2 && y1 <= y && y <= y2;
			break;

		case SHcircle:
			if(nc == 3) {
				xd = x - x1;
				yd = y - y1;
				hit = xd*xd + yd*yd <= x2*x2;
			}
			break;

		case SHpoly:
			np = nc/2;
			hit = 0;
			xr = (double)x;
			yr = (double)y;
			j = np - 1;
			for(i = 0; i < np; j = i++) {
				xi = (double)d2pix(c[2*i], w);
				yi = (double)d2pix(c[2*i + 1], h);
				xj = (double)d2pix(c[2*j], w);
				yj = (double)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;
			}
			break;

		default:
			dflt = a->href;
			dflttarg = a->target;
		}
		if(hit) {
			*ptarget = a->target;
			return copyurl(a->href);
		}
	}
	*ptarget = dflttarg;
	return copyurl(dflt);
}

static int
d2pix(Dimen d, int tot)
{
	int	ans;

	ans = dimenspec(d);
	if(dimenkind(d) == Dpercent)
		ans = (ans*tot)/100;
	return ans;
}

static GoSpec*
newget(ParsedUrl* url, int target)
{
	GoSpec* g;

	g = (GoSpec*)emallocz(sizeof(GoSpec));
	g->kind = GoNormal;
	g->url = url;
	g->meth = HGet;
	g->target = target;
	return g;
}

static GoSpec*
newpost(ParsedUrl* url, Rune* body, int target)
{
	GoSpec* g;

	g = (GoSpec*)emallocz(sizeof(GoSpec));
	g->kind = GoNormal;
	g->url = url;
	g->meth = HPost;
	g->body = body;
	g->target = target;
	return g;
}

static GoSpec*
newspecial(int kind, HistNode* hn)
{
	GoSpec* g;

	g = (GoSpec*)emallocz(sizeof(GoSpec));
	g->kind = kind;
	g->histnode = hn;
	return g;
}

static GoSpec*
copygospec(GoSpec* g)
{
	GoSpec* ans;

	ans = (GoSpec*)emalloc(sizeof(GoSpec));
	ans->kind = g->kind;
	ans->url = copyurl(g->url);
	ans->meth = g->meth;
	ans->body = Strdup(g->body);
	ans->auth = g->auth;
	ans->histnode = g->histnode;
	return ans;
}

static int
gospecequal(GoSpec* a, GoSpec* b)
{
	if(a->url == nil || b->url == nil)
		return 0;
	return urlequal(a->url, b->url) && a->meth == b->meth && !Strcmp(a->body, b->body);
}

static DocConfig*
newdocconfig(Rune* fname, Rune* title, int initconfig, GoSpec* g)
{
	DocConfig* d;

	d = (DocConfig*)emalloc(sizeof(DocConfig));
	d->framename = fname;
	d->title = title;
	d->initconfig = initconfig;
	d->gospec = g;
	return d;
}

static DocConfig*
copydocconfig(DocConfig* d)
{
	DocConfig* ans;

	ans = (DocConfig*)emalloc(sizeof(DocConfig));
	ans->framename = d->framename;
	ans->title = d->title;
	ans->initconfig = d->initconfig;
	ans->gospec = d->gospec;
	return ans;
}

static int
docconfigequal(DocConfig* a, DocConfig* b)
{
	return !Strcmp(a->framename, b->framename) && gospecequal(a->gospec, b->gospec);
}

static int
docconfigequalarray(DocConfig** a1, int n1, DocConfig** a2, int n2)
{
	int	i;

	if(n1 != n2)
		return 0;
	for(i = 0; i < n1; i++) {
		if(a1[i] == nil || a2[i] == nil)
			continue;
		if(!docconfigequal((a1[i]), a2[i]))
			return 0;
	}
	return 1;
}

static HistNode_list*
newhistnodelist(HistNode* hn, HistNode_list* l)
{
	HistNode_list* ans;

	ans = (HistNode_list*)emalloc(sizeof(HistNode_list));
	ans->histnode = hn;
	ans->next = l;
	return ans;
}

// 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.
static void
histaddedge(HistNode* a, HistNode* b, int atob)
{
	HistNode_list*	oldl;
	int	there;
	HistNode_list*	l;
	HistNode_list*	newl;

	if(atob)
		oldl = a->succs;
	else
		oldl = a->preds;
	there = 0;
	for(l = oldl; l != nil; l = l->next)
		if(l->histnode == b) {
			there = 1;
			break;
		}
	if(there)
		newl = newhistnodelist(b, remhnode(oldl, b));
	else
		newl = newhistnodelist(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).
static HistNode_list*
remhnode(HistNode_list* l, HistNode* hn)
{
	HistNode*	hdl;
	HistNode_list* ans;

	if(l == nil)
		return nil;
	hdl = l->histnode;
	if(hdl == hn) {
		ans = l->next;
		return ans;
	}
	return newhistnodelist(hdl, remhnode(l->next, hn));
}

static HistNode*
newhistnode(DocConfig* top, DocConfig** kids, int nkids,
					HistNode_list* preds, HistNode_list* succs)
{
	HistNode* h;

	h = (HistNode*)emalloc(sizeof(HistNode));
	h->topconfig = top;
	h->kidconfigs = kids;
	h->nkids = nkids;
	h->preds = preds;
	h->succs = succs;
	return h;
}

// Copy of a, with new kidconfigs array (so that it can be changed independent
// of a), and clear the preds and succs.
static HistNode*
histnodecopy(HistNode* a)
{
	int	n;
	DocConfig**	kc;
	int	i;

	n = a->nkids;
	kc = nil;
	if(n > 0) {
		kc = (DocConfig**)emalloc(n * sizeof(DocConfig*));
		for(i = 0; i < n; i++)
			kc[i] = copydocconfig(a->kidconfigs[i]);
	}
	return newhistnode(a->topconfig, kc, n, nil, 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.
// In any case reorder the history array to put latest last in array.
static void
histadd(Frame* f, GoSpec* g, int navkind)
{
	HistNode*	oldcur;
	DocConfig*	dc;
	HistNode*	hnode;
	int	hnodepos;
	int	i;
	DocConfig*	kc;
	int	kidpos;
	int	k;

	if(history.hlen <= history.n) {
		history.hlen += 20;
		history.h = (HistNode**)erealloc(history.h, history.hlen * sizeof(HistNode*));
	}
	if(history.n > 0)
		oldcur = history.h[history.n - 1];
	else
		oldcur = nil;
	dc = newdocconfig(Strdup(f->name), Strdup(g->url->url), navkind != GoHistnode, copygospec(g));
	hnode = newhistnode(dc, nil, 0, nil, nil);
	if(f == top)
		g->target = FTtop;
	else if(oldcur != nil) {
		kidpos = -1;
		for(i = 0; i < oldcur->nkids; i++) {
			kc = oldcur->kidconfigs[i];
			if(kc != nil && !Strcmp(kc->framename, f->name)) {
				kidpos = i;
				break;
			}
		}
		if(kidpos == -1) {
			if(dbg)
				trace("history botch\n");
		}
		else {
			hnode = histnodecopy(oldcur);
			hnode->kidconfigs[kidpos] = dc;
		}
	}
	// see if equivalent node to hnode is already in history
	hnodepos = -1;
	for(i = 0; i < history.n; i++) {
		if(docconfigequal(hnode->topconfig, history.h[i]->topconfig)) {
			if((hnode->kidconfigs == nil && history.h[i]->topconfig->initconfig)
			   || docconfigequalarray(hnode->kidconfigs, hnode->nkids,
							history.h[i]->kidconfigs, history.h[i]->nkids)) {
				hnodepos = i;
				hnode = history.h[i];
				break;
			}
		}
	}
	if(hnodepos == -1) {
		hnodepos = history.n;
		history.h[history.n++] = hnode;
	}
	if(oldcur != nil && hnode != oldcur && navkind != GoHistnode) {
		histaddedge(oldcur, hnode, 1);
		histaddedge(hnode, oldcur, 0);
	}
	if(hnodepos != history.n - 1) {
		for(k = hnodepos; k < history.n - 1; k++)
			history.h[k] = history.h[k + 1];
		history.h[history.n - 1] = hnode;
	}
	if(hnode->preds != nil)
		enable(ctllay.controls[CLCbackbut]);
	else
		disable(ctllay.controls[CLCbackbut]);
	if(hnode->succs != nil)
		enable(ctllay.controls[CLCfwdbut]);
	else
		disable(ctllay.controls[CLCfwdbut]);
}

// This is called just after layout of f.
// Now we can put in correct doctitle, and make kids array if necessary.
static void
histupdate(Frame* f)
{
	HistNode*	hnode;
	Framelist*	kl;
	Frame*	kf;
	DocConfig**	kc;
	DocConfig*	dc;
	int	i;

	hnode = history.h[history.n - 1];
	if(f == top) {
		hnode->topconfig->title = Strdup(f->doc->doctitle);
		if(f->kids != nil && hnode->kidconfigs == nil) {
			kc = (DocConfig**)emalloc(listlen((List*)f->kids) * sizeof(DocConfig*));
			i = 0;
			for(kl = f->kids; kl != nil; kl = kl->next) {
				kf = kl->val;
				if(kf->src != nil)
					kc[i] = newdocconfig(Strdup(kf->name), Strdup(kf->src->url), 1,
							newget(copyurl(kf->src), FTself));
				i++;
			}
			hnode->kidconfigs = kc;
		}
	}
	else {
		for(i = 0; i < hnode->nkids; i++) {
			dc = hnode->kidconfigs[i];
			if(dc != nil && !Strcmp(dc->framename, f->name)) {
				hnode->kidconfigs[i]->title = Strdup(f->doc->doctitle);
				return;
			}
		}
		if(dbg)
			trace("history update botch\n");
	}
}

// Find the gokind node (-1==Back, 0==Same, +1==Forward)
static HistNode*
histfind(int gokind)
{
	HistNode*	cur;

	if(history.n > 0) {
		cur = history.h[history.n - 1];
		switch(gokind) {
		case 1:
			if(cur->succs != nil)
				return cur->succs->histnode;
			break;
		case -1:
			if(cur->preds != nil)
				return cur->preds->histnode;
			break;
		case 0:
			return cur;
			break;
		}
	}
	return nil;
}

// for debugging
static void
histprint(void)
{
	int	i;
	int	j;
	HistNode*	hn;
	DocConfig*	dc;

	trace("History\n");
	for(i = 0; i < history.n; i++) {
		hn = history.h[i];
		trace("Node %d:\n", i);
		dc = hn->topconfig;
		trace("\tframe=%S, target=%S, url=%U\n",
				dc->framename, targetname(dc->gospec->target), dc->gospec->url);
		if(hn->kidconfigs != nil) {
			for(j = 0; j < hn->nkids; j++) {
				dc = hn->kidconfigs[j];
				if(dc != nil)
					trace("\t\t%d: frame=%S, target=%S, url=%U\n",
						j, dc->framename, targetname(dc->gospec->target), dc->gospec->url);
			}
		}
		if(hn->preds != nil)
			printhnodeindices("Preds", hn->preds);
		if(hn->succs != nil)
			printhnodeindices("Succs", hn->succs);
	}
	trace("\n");
}

static void
printhnodeindices(char* label, HistNode_list* l)
{
	HistNode*	hn;
	int	i;

	trace("\t%s:", label);
	for(; l != nil; l = l->next) {
		hn = l->histnode;
		for(i = 0; i < history.n; i++) {
			if(hn == history.h[i]) {
				trace(" %d", i);
				break;
			}
		}
		if(i == history.n)
			trace(" ?");
	}
	trace("\n");
}

// Create HTML representation of history in "history.html" in user's config directory.
// This is called from go thread group.
static void
dumphistory(void)
{
	// TODO
/*
	Rune*	fname;
	FD*	fd;
	Rune*	line;
	uchar*	buf;
	uchar*	aline;
	int	bufpos;
	int	i;
	int	j;
	int	n;
	int	nl;
	HistNode*	hn;
	DocConfig*	dc;
	char buf[ATOMICIO];

	snprint(buf, sizeof(buf), 
	fname = Strdup2(config.userdir, L"/history.html", 0);
	fd = create(fname, OWRITE, 384);
	if(fd == nil) {
		if(warn)
			trace("can't create history file\n");
		return ;
	}
	line = L"<HEAD> <TITLE>History</TITLE></HEAD>\n<BODY>\n";
	nl = Strlen(line);
	buf = (uchar*)emalloc(ATOMICIO * sizeof(uchar), 0);
	n = 0;
	aline = fromStr(line, nl, UTF_8, 0);
	memmove(buf, aline, nl);
	bufpos = nl;
	for(i = history->n - 1; i >= 0; i--) {
		hn = history->h[i];
		dc = hn->topconfig;
		line = Strdup2(Strdup2(Strdup2(Strdup2(L"<A HREF=", tostring(dc->gospec->url)), L" TARGET=\"_top\">"), dc->title), L"</A><BR>\n");
		if(hn->kidconfigs != nil) {
			Stradd(line, L"<UL>", FIXME);
			for(j = 0; j < arraylen(hn->kidconfigs); j++) {
				dc = hn->kidconfigs[j];
				if(dc != nil) {
					Stradd(line, Strdup2(Strdup2(Strdup2(Strdup2(Strdup2(Strdup2(L"<LI><A HREF=", tostring(dc->gospec->url)), L" TARGET=\""), dc->framename), L"\">"), dc->title), L"</A>\n"), FIXME);
				}
			}
			Stradd(line, L"</UL>", FIXME);
		}
		aline = fromStr(line, arraylen(line), UTF_8);
		if(bufpos + arraylen(aline) > ATOMICIO) {
			write(fd, buf, bufpos);
			bufpos = 0;
		}
		buf[bufpos:...]  = aline;
		bufpos += arraylen(aline);
	}
	if(bufpos > 0)
		write(fd, buf, bufpos);
*/
}

static void
freectls(Control** a, int n)
{
	// TODO: free the memory
	USED(a);
	USED(n);
}

static AuthInfo*
getauth(Rune* chal)
{
	// TODO
	USED(chal);
	return nil;
/*
	Rune*	realm;
	AuthInfop_list*	al;
	int	code;
	Rune*	ans;
	AuthInfo*	a;

	if(Strcmp(tolower(chal[0:12]), L"basic realm=")) {
		if(dbg || warn)
			trace("unrecognized authorization challenge: %s\n", chal);
		return makeRunepRunep_pair(L"", L"");
	}
	realm = chal[12:];
	if(realm[0] == 34)
		realm = realm[1:arraylen(realm) - 1];
	for(al = auths; al != nil; al = al->tl) {
		a = al->hd;
		if(!Strcmp(realm, a->realm))
			return makeRunepRunep_pair(realm, a->credentials);
	}
	dopopup(PopupAuth, realm);
	tmp_24 = (recv(popupans, &tmp_25);
	code = tmp_24.t0;
	ans = tmp_24.t1;
	if(code == -1)
		trace("couldn't create popup window\n");
	else if(code == 1)
		ans = tobase64(ans);
	return makeRunepRunep_pair(realm, ans);
*/
}

static Rune*
tobase64(Rune* a)
{
	int	n;
	int	nout;
	Rune*	out;
	int	j;
	int	i;
	int	nmod3;
	int	x;

	n = Strlen(a);
	if(n == 0)
		return nil;
	j = 0;
	i = 0;
	nout = n*4;
	out = newstr(nout);
	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);
	}
	out[j] = 0;
	nmod3 = n%3;
	if(nmod3 != 0) {
		out[j - 1] = 61;
		if(nmod3 == 1)
			out[j - 2] = 61;
	}
	return out;
}

static int
c64(int c)
{
	static Rune* v = L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	return v[c&63];
}

static void
dosaveas(ByteSource* bsmain)
{
// TODO
/*
	int	code;
	Rune*	ans;
	ByteSource*	bs;
	int	n;
	int	i;
	Rune*	err;
	int	flen;
	FD*	fd;

	dopopup(PopupSaveAs, L"");
	tmp_26 = (recv(popupans, &tmp_27);
	code = tmp_26.t0;
	ans = tmp_26.t1;
	if(code == -1)
		trace("couldn't create popup window\n");
	else if(code == 1 && ans != nil) {
		if(ans[0] != 47)
			ans = Strdup2(Strdup2(config.userdir, L"/"), ans);
		fd = create(ans, OWRITE, 420);
		if(fd == nil) {
			dopopup(PopupAlert, Strdup2(L"Couldn't create ", ans));
			tmp_28 =(recv(popupans, &tmp_29);
		}
		else {
			showstatus(Strdup2(L"Saving ", tostring(bsmain->hdr->actual)), 1);
			err = L"";
			flen = bsmain->hdr->length;
			while(bsmain->edata < flen) {
				bs = waitreq();
				if(bs->refgo == 0)
					continue;
				assert(bs == bsmain);
				if(bs->err != nil) {
					err = bs->err;
					break;
				}
			}
			if(err == nil) {
				i = 0;
				while(i < flen) {
					n = write(fd, bsmain->data[slice i, flen], flen - i);
					if(n <= 0)
						break;
					i += n;
				}
				if(i != flen)
					err = L"whole file not written";
			}
			if(err != nil)
				dopopup(PopupAlert, err);
			else
				dopopup(PopupAlert, Strdup2(L"Created ", ans));
			tmp_30 = (recv(popupans, &tmp_31);
		}
	}
*/
	freebs(bsmain);
}

static void
handleprogress(EV ev)
{
	int	i;
	Cprogbox*	pb;
	int	bsid;
	int	state;
	int	pcnt;
	Rune*	s;

	bsid = ev.u.progress.bsid;
	state = ev.u.progress.state;
	pcnt = ev.u.progress.pcnt;
	s = ev.u.progress.s;
	if(bsid == -1) {
		for(i = 0; i < proglay.nused; i++) {
			pb = (Cprogbox*)proglay.box[i];
			pb->state = Punused;
			pb->pcnt = 0;
			pb->bsid = -1;
			pb->src = nil;
			pb->err = nil;
		}
		proglay.nused = 0;
		redrawprog(0);
	}
	else {
		if(state == Pstart) {
			if(proglay.nused < proglay.boxlen) {
				i = proglay.nused;
				proglay.nused++;
			}
			else
				i = 0;
			pb = (Cprogbox*)proglay.box[i];
			pb->state = state;
			pb->bsid = bsid;
			pb->src = s;
		}
		else {
			for(i = 0; i < proglay.nused; i++) {
				pb = (Cprogbox*)proglay.box[i];
				if(pb->bsid == bsid)
					break;
			}
			if(i == proglay.nused)
				return;
			pb = (Cprogbox*)proglay.box[i];
			if(pb->state != Perr) {
				pb->state = state;
				pb->pcnt = pcnt;
				pb->err = s;
			}
		}
		drawctl(proglay.box[i], 1);
	}
}

static void
showstatus(Rune* msg)
{
	Rune*	ostatus;
	Point	p;
	Point	sp;

	ostatus = ctllay.status;
	p = ctllay.statuspos;
	pushclipr(ctlframe->cr);
	if(ostatus != nil && Strcmp(ostatus, msg)) {
		sp = measurestring(ostatus);
		drawfill(screen, Rpt(p, addpt(p, sp)), Grey);
	}
	ctllay.status = Strdup(msg);
	drawstring(screen, p, msg);
	popclipr();
	flushimage(display, 1);
}

// Run as separate proc to put up alert popup
static void
alert(void* arg)
{
	Rune* msg;
	Channel* sync;
	PopupAns ans;
	void** args;

	meminit(GRalert);
	args = (void**)arg;
	msg = (Rune*)args[0];
	sync = (Channel*)args[1];
	dopopup(PopupAlert, msg);
	if(recv(popupans, &ans) < 0)
		finish();
	if(sendul(sync, 1) < 0)
		finish();
}

static void
showurl(Rune* u)
{
	entryset(ctllay.controls[CLCentry], Strdup(u));
}

void
fatalerror(char* msg)
{
	trace("Fatal error: %s\n", msg);
	finish();
}

void
trace(char* fmt, ...)
{
	va_list arg;

	va_start(arg, fmt);
	vfprint(1, fmt, arg);
	va_end(arg);
}

void
finish(void)
{
	if(dbg)
		trace("finish called\n");
	threadexitsall("");
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to [email protected].