code: plan9front

ref: 5622b0bbd878dbc34045cc6fd37cffa64461eabe
dir: /sys/src/cmd/mothra/mothra.c/

View raw version
/*
 * Trivial web browser
 */
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>
#include <keyboard.h>
#include <plumb.h>
#include <cursor.h>
#include <panel.h>
#include <regexp.h>
#include "mothra.h"
#include "rtext.h"
int debug=0;
int verbose=0;		/* -v flag causes html errors to be written to file-descriptor 2 */
int killimgs=0;	/* should mothra kill images? */
int defdisplay=1;	/* is the default (initial) display visible? */
int visxbar=0;	/* horizontal scrollbar visible? */
int topxbar=0;	/* horizontal scrollbar at top? */
Panel *root;	/* the whole display */
Panel *alt;	/* the alternate display */
Panel *alttext;	/* the alternate text window */
Panel *cmd;	/* command entry */
Panel *cururl;	/* label giving the url of the visible text */
Panel *list;	/* list of previously acquired www pages */
Panel *msg;	/* message display */
Panel *menu3;	/* button 3 menu */
char mothra[] = "mothra!";
Cursor patientcurs={
	0, 0,
	0x01, 0x80, 0x03, 0xC0, 0x07, 0xE0, 0x07, 0xe0,
	0x07, 0xe0, 0x07, 0xe0, 0x03, 0xc0, 0x0F, 0xF0,
	0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8,
	0x0F, 0xF0, 0x1F, 0xF8, 0x3F, 0xFC, 0x3F, 0xFC,

	0x01, 0x80, 0x03, 0xC0, 0x07, 0xE0, 0x04, 0x20,
	0x04, 0x20, 0x06, 0x60, 0x02, 0x40, 0x0C, 0x30,
	0x10, 0x08, 0x14, 0x08, 0x14, 0x28, 0x12, 0x28,
	0x0A, 0x50, 0x16, 0x68, 0x20, 0x04, 0x3F, 0xFC,
};
Cursor confirmcursor={
	0, 0,
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,

	0x00, 0x0E, 0x07, 0x1F, 0x03, 0x17, 0x73, 0x6F,
	0xFB, 0xCE, 0xDB, 0x8C, 0xDB, 0xC0, 0xFB, 0x6C,
	0x77, 0xFC, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03,
	0x94, 0xA6, 0x63, 0x3C, 0x63, 0x18, 0x94, 0x90,
};
Cursor readingcurs={
	-10, -3,
	0x00, 0x00, 0x00, 0x00, 0x0F, 0xF0, 0x0F, 0xF0,
	0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x1F, 0xF0,
	0x3F, 0xF0, 0x7F, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF,
	0xFB, 0xFF, 0xF3, 0xFF, 0x00, 0x00, 0x00, 0x00,

	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xE0,
	0x07, 0xE0, 0x01, 0xE0, 0x03, 0xE0, 0x07, 0x60,
	0x0E, 0x60, 0x1C, 0x00, 0x38, 0x00, 0x71, 0xB6,
	0x61, 0xB6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
Cursor mothcurs={
	{-7, -7},
	{0x00, 0x00, 0x60, 0x06, 0xf8, 0x1f, 0xfc, 0x3f, 
	 0xfe, 0x7f, 0xff, 0xff, 0x7f, 0xfe, 0x7f, 0xfe, 
	 0x7f, 0xfe, 0x3f, 0xfc, 0x3f, 0xfc, 0x1f, 0xf8, 
	 0x1f, 0xf8, 0x0e, 0x70, 0x0c, 0x30, 0x00, 0x00, },
	{0x00, 0x00, 0x00, 0x00, 0x60, 0x06, 0x58, 0x1a, 
	 0x5c, 0x3a, 0x64, 0x26, 0x27, 0xe4, 0x37, 0xec, 
	 0x37, 0xec, 0x17, 0xe8, 0x1b, 0xd8, 0x0e, 0x70, 
	 0x0c, 0x30, 0x04, 0x20, 0x00, 0x00, 0x00, 0x00, }
};

Www *current=0;
Url *selection=0;
int mothmode;
int kickpipe[2];

void docmd(Panel *, char *);
void doprev(Panel *, int, int);
char *urlstr(Url *);
void setcurrent(int, char *);
char *genwww(Panel *, int);
void updtext(Www *);
void dolink(Panel *, int, Rtext *);
void hit3(int, int);
void mothon(Www *, int);
void killpix(Www *w);
char *buttons[]={
	"alt display",
	"moth mode",
	"snarf",
	"paste",
	"plumb",
	"search",
	"save hit",
	"hit list",
	"exit",
	0
};

int wwwtop=0;
Www *www(int index){
	static Www a[NWWW];
	return &a[index % NWWW];
}
int nwww(void){
	return wwwtop<NWWW ? wwwtop : NWWW;
}

int subpanel(Panel *obj, Panel *subj){
	if(obj==0) return 0;
	if(obj==subj) return 1;
	for(obj=obj->child;obj;obj=obj->next)
		if(subpanel(obj, subj)) return 1;
	return 0;
}
/*
 * Make sure that the keyboard focus is on-screen, by adjusting it to
 * be the cmd entry if necessary.
 */
int adjkb(void){
	Rtext *t;
	int yoffs;
	if(current){
		yoffs=text->r.min.y-plgetpostextview(text);
		for(t=current->text;t;t=t->next) if(!eqrect(t->r, Rect(0,0,0,0))){
			if(t->r.max.y+yoffs>=text->r.min.y
			&& t->r.min.y+yoffs<text->r.max.y
			&& t->b==0
			&& subpanel(t->p, plkbfocus))
				return 1;
		}
	}
	plgrabkb(cmd);
	return 0;
}

void scrollpanel(Panel *p, int dy, int whence)
{
	Scroll s;

	s = plgetscroll(p);
	switch(whence){
	case 0:
		s.pos.y = dy;
		break;
	case 1:
		s.pos.y += dy;
		break;
	case 2:
		s.pos.y = s.size.y+dy;
		break;
	}
	if(s.pos.y > s.size.y)
		s.pos.y = s.size.y;
	if(s.pos.y < 0)
		s.pos.y = 0;
	plsetscroll(p, s);
}

void sidescroll(int dx, int whence)
{
	Scroll s;

	s = plgetscroll(text);
	switch(whence){
	case 0:
		s.pos.x = dx;
		break;
	case 1:
		s.pos.x += dx;
		break;
	case 2:
		s.pos.x = s.size.x+dx;
		break;
	}
	if(s.pos.x > s.size.x - text->size.x + 5)
		s.pos.x = s.size.x - text->size.x + 5;
	if(s.pos.x < 0)
		s.pos.x = 0;
	plsetscroll(text, s);
}

void mkpanels(void){
	Panel *p, *xbar, *ybar, *swap;
	int xflags;

	if(topxbar)
		xflags=PACKN|USERFL;
	else
		xflags=PACKS|USERFL;
	if(!visxbar)
		xflags|=IGNORE;
	menu3=plmenu(0, 0, buttons, PACKN|FILLX, hit3);
	root=plpopup(root, EXPAND, 0, 0, menu3);
		p=plgroup(root, PACKN|FILLX);
			msg=pllabel(p, PACKN|FILLX, mothra);
			plplacelabel(msg, PLACEW);
			pllabel(p, PACKW, "Go:");
			cmd=plentry(p, PACKN|FILLX, 0, "", docmd);
		p=plgroup(root, PACKN|FILLX);
			ybar=plscrollbar(p, PACKW);
			list=pllist(p, PACKN|FILLX, genwww, 8, doprev);
			plscroll(list, 0, ybar);
		p=plgroup(root, PACKN|FILLX);
			pllabel(p, PACKW, "Url:");
			cururl=pllabel(p, PACKE|EXPAND, "---");
			plplacelabel(cururl, PLACEW);
		p=plgroup(root, PACKN|EXPAND);
			ybar=plscrollbar(p, PACKW|USERFL);
			xbar=plscrollbar(p, xflags);
			text=pltextview(p, PACKE|EXPAND, Pt(0, 0), 0, dolink);
			plscroll(text, xbar, ybar);
	plgrabkb(cmd);
	alt=plpopup(0, PACKE|EXPAND, 0, 0, menu3);
		ybar=plscrollbar(alt, PACKW|USERFL);
		xbar=plscrollbar(alt, xflags);
		alttext=pltextview(alt, PACKE|EXPAND, Pt(0, 0), 0, dolink);
		plscroll(alttext, xbar, ybar);
	if(!defdisplay){
		swap=root;
		root=alt;
		alt=swap;
		swap=text;
		text=alttext;
		alttext=swap;
	}
}
int cohort = -1;
void killcohort(void){
	int i;
	for(i=0;i!=3;i++){	/* It's a long way to the kitchen */
		postnote(PNGROUP, cohort, "kill\n");
		sleep(1);
	}
}
void catch(void*, char*){
	noted(NCONT);
}
void dienow(void*, char*){
	noted(NDFLT);
}

char* mkhome(void){
	static char *home;		/* where to put files */
	char *henv, *tmp;
	int f;

	if(home == nil){
		henv=getenv("home");
		if(henv){
			tmp = smprint("%s/lib", henv);
			f=create(tmp, OREAD, DMDIR|0777);
			if(f!=-1) close(f);
			free(tmp);

			home = smprint("%s/lib/mothra", henv);
			f=create(home, OREAD, DMDIR|0777);
			if(f!=-1) close(f);
			free(henv);
		}
		else
			home = strdup("/tmp");
	}
	return home;
}

void donecurs(void){
	if(current && current->alldone==0)
		esetcursor(&readingcurs);
	else if(mothmode)
		esetcursor(&mothcurs);
	else
		esetcursor(0);
}

void drawlock(int dolock){
	static int ref = 0;
	if(dolock){
		if(ref++ == 0)
			lockdisplay(display);
	} else {
		if(--ref == 0)
			unlockdisplay(display);
	}
}

void scrollto(char *tag);
void search(void);

extern char *mtpt; /* url */

void main(int argc, char *argv[]){
	Event e;
	enum { Eplumb = 128, Ekick = 256 };
	Plumbmsg *pm;
	char *url;
	int i;

	quotefmtinstall();
	fmtinstall('U', Ufmt);

	ARGBEGIN{
	case 'd': debug=1; break;
	case 'v': verbose=1; break;
	case 'k': killimgs=1; break;
	case 'm':
		if(mtpt = ARGF())
			break;
	case 'a': defdisplay=0; break;
	default:  goto Usage;
	}ARGEND

	/*
	 * so that we can stop all subprocesses with a note,
	 * and to isolate rendezvous from other processes
	 */
	if(cohort=rfork(RFPROC|RFNOTEG|RFNAMEG|RFREND)){
		atexit(killcohort);
		notify(catch);
		waitpid();
		exits(0);
	}
	cohort = getpid();
	atexit(killcohort);

	switch(argc){
	default:
	Usage:
		fprint(2, "usage: %s [-dvak] [-m mtpt] [url]\n", argv0);
		exits("usage");
	case 0:
		url=getenv("url");
		break;
	case 1: url=argv[0]; break;
	}
	if(initdraw(0, 0, mothra) < 0)
		sysfatal("initdraw: %r");
	display->locking = 1;
	chrwidth=stringwidth(font, "0");
	pltabsize(chrwidth, 8*chrwidth);
	einit(Emouse|Ekeyboard);
	eplumb(Eplumb, "web");
	if(pipe(kickpipe) < 0)
		sysfatal("pipe: %r");
	estart(Ekick, kickpipe[0], 256);
	plinit();
	if(debug) notify(dienow);
	getfonts();
	hrule=allocimage(display, Rect(0, 0, 1, 5), screen->chan, 1, DWhite);
	if(hrule==0)
		sysfatal("can't allocimage!");
	draw(hrule, Rect(0,1,1,3), display->black, 0, ZP);
	linespace=allocimage(display, Rect(0, 0, 1, 5), screen->chan, 1, DWhite);
	if(linespace==0)
		sysfatal("can't allocimage!");
	bullet=allocimage(display, Rect(0,0,25, 8), screen->chan, 0, DWhite);
	fillellipse(bullet, Pt(4,4), 3, 3, display->black, ZP);
	mkpanels();
	unlockdisplay(display);
	eresized(0);
	drawlock(1);

	if(url && url[0])
		geturl(url, -1, 1, 0);

	mouse.buttons=0;
	for(;;){
		if(mouse.buttons==0 && current){
			if(current->finished){
				updtext(current);
				if(current->url->tag[0])
					scrollto(current->url->tag);
				current->finished=0;
				current->changed=0;
				current->alldone=1;
				message(mothra);
				donecurs();
			}
		}

		drawlock(0);
		i=event(&e);
		drawlock(1);

		switch(i){
		case Ekick:
			if(mouse.buttons==0 && current && current->changed){
				if(!current->finished)
					updtext(current);
				current->changed=0;
			}
			break;
		case Ekeyboard:
			switch(e.kbdc){
			default:
Plkey:
				adjkb();
				plkeyboard(e.kbdc);
				break;
			case Khome:
				scrollpanel(text, 0, 0);
				break;
			case Kup:
				scrollpanel(text, -text->size.y/4, 1);
				break;
			case Kpgup:
				scrollpanel(text, -text->size.y/2, 1);
				break;
			case Kdown:
				scrollpanel(text, text->size.y/4, 1);
				break;
			case Kpgdown:
				scrollpanel(text, text->size.y/2, 1);
				break;
			case Kend:
				scrollpanel(text, -text->size.y, 2);
				break;
			case Kack:
				search();
				break;
			case Kright:
				if(plkbfocus)
					goto Plkey;
				sidescroll(text->size.x/4, 1);
				break;
			case Kleft:
				if(plkbfocus)
					goto Plkey;
				sidescroll(-text->size.x/4, 1);
				break;
			}
			break;
		case Emouse:
			mouse=e.mouse;
			if(mouse.buttons & (8|16) && ptinrect(mouse.xy, list->r) && defdisplay){
				if(mouse.buttons & 8)
					scrollpanel(list, list->r.min.y - mouse.xy.y, 1);
				else
					scrollpanel(list, mouse.xy.y - list->r.min.y, 1);
				break;
			}
			if(mouse.buttons & (8|16) && ptinrect(mouse.xy, text->r)){
				if(mouse.buttons & 8)
					scrollpanel(text, text->r.min.y - mouse.xy.y, 1);
				else
					scrollpanel(text, mouse.xy.y - text->r.min.y, 1);
				break;
			}
			plmouse(root, &mouse);
			if(mouse.buttons == 1 && root->lastmouse == root)
				plgrabkb(nil);
			break;
		case Eplumb:
			pm=e.v;
			if(pm->ndata > 0)
				geturl(pm->data, -1, 1, 0);
			plumbfree(pm);
			break;
		}
	}
}
int confirm(int b){
	Mouse down, up;
	esetcursor(&confirmcursor);
	do down=emouse(); while(!down.buttons);
	do up=emouse(); while(up.buttons);
	donecurs();
	return down.buttons==(1<<(b-1));
}
void message(char *s, ...){
	static char buf[1024];
	char *out;
	va_list args;
	va_start(args, s);
	out = buf + vsnprint(buf, sizeof(buf), s, args);
	va_end(args);
	*out='\0';
	plinitlabel(msg, PACKN|FILLX, buf);
	if(defdisplay) pldraw(msg, screen);
}
void htmlerror(char *name, int line, char *m, ...){
	static char buf[1024];
	char *out;
	va_list args;
	if(verbose){
		va_start(args, m);
		out=buf+snprint(buf, sizeof(buf), "%s: line %d: ", name, line);
		out+=vsnprint(out, sizeof(buf)-(out-buf)-1, m, args);
		va_end(args);
		*out='\0';
		fprint(2, "%s\n", buf);
	}
}
void eresized(int new){
	Rectangle r;

	drawlock(1);
	if(new && getwindow(display, Refnone) == -1) {
		fprint(2, "getwindow: %r\n");
		exits("getwindow");
	}
	r=screen->r;
	plpack(root, r);
	plpack(alt, r);
	pldraw(cmd, screen);	/* put cmd box on screen for alt display */
	pldraw(root, screen);
	flushimage(display, 1);
	drawlock(0);
}
void *emalloc(int n){
	void *v;
	v=malloc(n);
	if(v==0)
		sysfatal("out of memory");
	memset(v, 0, n);
	setmalloctag(v, getcallerpc(&n));
	return v;
}
void nstrcpy(char *to, char *from, int len){
	strncpy(to, from, len);
	to[len-1] = 0;
}

char *genwww(Panel *, int index){
	static char buf[1024];
	Www *w;
	int i;

	if(index >= nwww())
		return 0;
	i = wwwtop-index-1;
	w = www(i);
	if(!w->url)
		return 0;
	if(w->title[0]!='\0'){
		w->gottitle=1;
		snprint(buf, sizeof(buf), "%2d %s", i+1, w->title);
	} else
		snprint(buf, sizeof(buf), "%2d %s", i+1, urlstr(w->url));
	return buf;
}

void scrollto(char *tag){
	Rtext *tp;
	Action *ap;
	if(current == nil || text == nil)
		return;
	if(tag && tag[0]){
		for(tp=current->text;tp;tp=tp->next){
			ap=tp->user;
			if(ap && ap->name && strcmp(ap->name, tag)==0){
				current->yoffs=tp->topy;
				break;
			}
		}
	}
	plsetpostextview(text, current->yoffs);
}

/*
 * selected text should be a url.
 */
void setcurrent(int index, char *tag){
	Www *new;
	int i;
	new=www(index);
	if(new==current && (tag==0 || tag[0]==0)) return;
	if(current)
		current->yoffs=plgetpostextview(text);
	current=new;
	plinitlabel(cururl, PACKE|EXPAND, current->url->fullname);
	if(defdisplay) pldraw(cururl, screen);
	plinittextview(text, PACKE|EXPAND, Pt(0, 0), current->text, dolink);
	scrollto(tag);
	if((i = open("/dev/label", OWRITE)) >= 0){
		fprint(i, "%s %s", mothra, current->url->fullname);
		close(i);
	}
	donecurs();
}
char *arg(char *s){
	do ++s; while(*s==' ' || *s=='\t');
	return s;
}
void save(int ifd, char *name){
	char buf[NNAME+64];
	int ofd;
	if(ifd < 0){
		message("save: %s: %r", name);
		return;
	}
	ofd=create(name, OWRITE, 0666);
	if(ofd < 0){
		message("save: %s: %r", name);
		return;
	}
	switch(rfork(RFNOTEG|RFNAMEG|RFFDG|RFMEM|RFPROC|RFNOWAIT)){
	case -1:
		message("Can't fork: %r");
		break;
	case 0:
		dup(ifd, 0);
		close(ifd);
		dup(ofd, 1);
		close(ofd);

		snprint(buf, sizeof(buf),
			"{tput -p || cat} |[2] {aux/statusmsg -k %q >/dev/null || cat >/dev/null}", name);
		execl("/bin/rc", "rc", "-c", buf, nil);
		exits("exec");
	}
	close(ifd);
	close(ofd);
	donecurs();
}
void screendump(char *name, int full){
	Image *b;
	int fd;
	fd=create(name, OWRITE, 0666);
	if(fd==-1){
		message("can't create %s", name);
		return;
	}
	if(full){
		writeimage(fd, screen, 0);
	} else {
		if((b=allocimage(display, text->r, screen->chan, 0, DNofill)) == nil){
			message("can't allocate image");
			close(fd);
			return;
		}
		draw(b, b->r, screen, 0, b->r.min);
		writeimage(fd, b, 0);
		freeimage(b);
	}
	close(fd);
}

/*
 * convert a url into a local file name.
 */
char *urltofile(Url *url){
	char *name, *slash;
	if(url == nil)
		return nil;
	name = urlstr(url);
	if(name == nil || name[0] == 0)
		name = "/";
	if(slash = strrchr(name, '/'))
		name = slash+1;
	if(name[0] == 0)
		name = "index";
	return name;
}

/*
 * user typed a command.
 */
void docmd(Panel *p, char *s){
	char buf[NNAME];
	int c;

	USED(p);
	while(*s==' ' || *s=='\t') s++;
	/*
	 * Non-command does a get on the url
	 */
	if(s[0]!='\0' && s[1]!='\0' && s[1]!=' ')
		geturl(s, -1, 0, 0);
	else switch(c = s[0]){
	default:
		message("Unknown command %s", s);
		break;
	case 'a':
		s = arg(s);
		if(*s=='\0' && selection)
			hit3(3, 0);
		break;
	case 'd':
		s = arg(s);
		if(*s){
			s = smprint("https://lite.duckduckgo.com/lite/?q=%U&kd=-1", s);
			if(s != nil)
				geturl(s, -1, 0, 0);
			free(s);
		}else
			message("Usage: d text");
		break;
	case 'g':
		s = arg(s);
		if(*s=='\0'){
	case 'r':
			if(selection)
				s = urlstr(selection);
			else
				message("no url selected");
		}
		geturl(s, -1, 0, 0);
		break;
	case 'j':
		s = arg(s);
		if(*s)
			doprev(nil, 1, wwwtop-atoi(s));
		else
			message("Usage: j index");
		break;
	case 'm':
		mothon(current, !mothmode);
		break;
	case 'k':
		killimgs = !killimgs;
		if (killimgs)
			killpix(current);
		break;
	case 'w':
	case 'W':
		s = arg(s);
		if(s==0 || *s=='\0'){
			snprint(buf, sizeof(buf), "dump.bit");
			if(eenter("Screendump to", buf, sizeof(buf), &mouse) <= 0)
				break;
			s = buf;
		}
		screendump(s, c == 'W');
		break;
	case 's':
		s = arg(s);
		if(!selection){
			message("no url selected");
			break;
		}
		if(s==0 || *s=='\0'){
			snprint(buf, sizeof(buf), "%s", urltofile(selection));
			if(eenter("Save to", buf, sizeof(buf), &mouse) <= 0)
				break;
			s = buf;
		}
		save(urlget(selection, -1), s);
		break;
	case 'q':
		exits(0);
	}
	plinitentry(cmd, EXPAND, 0, "", docmd);
	pldraw(root, screen);
}

void regerror(char *msg)
{
	werrstr("regerror: %s", msg);
}

void search(void){
	static char last[256];
	char buf[256];
	Reprog *re;
	Rtext *tp;

	for(;;){
		if(current == nil || current->text == nil || text == nil)
			return;
		strncpy(buf, last, sizeof(buf)-1);
		if(eenter("Search for", buf, sizeof(buf), &mouse) <= 0)
			return;
		strncpy(last, buf, sizeof(buf)-1);
		re = regcompnl(buf);
		if(re == nil){
			message("%r");
			continue;
		}
		for(tp=current->text;tp;tp=tp->next)
			if(tp->flags & PL_SEL)
				break;
		if(tp == nil)
			tp = current->text;
		else {
			tp->flags &= ~PL_SEL;
			tp = tp->next;
		}
		while(tp != nil){
			tp->flags &= ~PL_SEL;
			if(tp->text && *tp->text)
			if(regexec(re, tp->text, nil, 0)){
				tp->flags |= PL_SEL;
				plsetpostextview(text, tp->topy);
				break;
			}
			tp = tp->next;
		}
		free(re);
		updtext(current);
	}
}

void hiturl(int buttons, char *url, int map){
	switch(buttons){
	case 1: geturl(url, -1, 0, map); break;
	case 2: urlresolve(selurl(url)); break;
	case 4: message("Button 3 hit on url can't happen!"); break;
	}
}

/*
 * user selected from the list of available pages
 */
void doprev(Panel *p, int buttons, int index){
	int i;
	USED(p);
	if(index < 0 || index >= nwww())
		return;
	i = wwwtop-index-1;
	switch(buttons){
	case 1: setcurrent(i, 0);	/* no break ... */
	case 2: selurl(www(i)->url->fullname); break;
	case 4: message("Button 3 hit on page can't happen!"); break;
	}
}

/*
 * Follow an html link
 */
void dolink(Panel *p, int buttons, Rtext *word){
	Action *a;

	a=word->user;
	if(a == nil || (a->link == nil && a->image == nil))
		return;
	if(mothmode)
		hiturl(buttons, a->image ? a->image : a->link, 0);
	else if(a->link){
		if(a->ismap){
			char mapurl[NNAME];
			Point coord;
			int yoffs;

			yoffs=plgetpostextview(p);
			coord=subpt(subpt(mouse.xy, word->r.min), p->r.min);
			snprint(mapurl, sizeof(mapurl), "%s?%d,%d", a->link, coord.x, coord.y+yoffs);
			hiturl(buttons, mapurl, 1);
		} else
			hiturl(buttons, a->link, 0);
	}
}

void filter(int fd, char *cmd){
	switch(rfork(RFFDG|RFPROC|RFMEM|RFREND|RFNOWAIT|RFNOTEG)){
	case -1:
		message("Can't fork!");
		break;
	case 0:
		dupfds(fd, 1, 2, -1);
		execl("/bin/rc", "rc", "-c", cmd, nil);
		_exits(0);
	}
	close(fd);
}
void gettext(Www *w, int fd, int type){
	switch(rfork(RFFDG|RFPROC|RFMEM|RFNOWAIT)){
	case -1:
		message("Can't fork, please wait");
		break;
	case 0:
		if(type==HTML)
			plrdhtml(w->url->fullname, fd, w, killimgs);
		else
			plrdplain(w->url->fullname, fd, w);
		_exits(0);
	}
	close(fd);
}

void freetext(Rtext *t){
	Rtext *tt;
	Action *a;

	tt = t;
	for(; t!=0; t = t->next){
		t->b=0;
		free(t->text);
		t->text=0;
		if(a = t->user){
			t->user=0;
			free(a->image);
			free(a->link);
			free(a->name);
			free(a);
		}
	}
	plrtfree(tt);
}

void
dupfds(int fd, ...)
{
	int mfd, n, i;
	va_list arg;
	Dir *dir;

	va_start(arg, fd);
	for(mfd = 0; fd >= 0; fd = va_arg(arg, int), mfd++)
		if(fd != mfd)
			if(dup(fd, mfd) < 0)
				sysfatal("dup: %r");
	va_end(arg);
	if((fd = open("/fd", OREAD)) < 0)
		sysfatal("open: %r");
	n = dirreadall(fd, &dir);
	for(i=0; i<n; i++){
		if(strstr(dir[i].name, "ctl"))
			continue;
		fd = atoi(dir[i].name);
		if(fd >= mfd)
			close(fd);
	}
	free(dir);
}

int pipeline(int fd, char *fmt, ...)
{
	char buf[80], *argv[4];
	va_list arg;
	int pfd[2];

	va_start(arg, fmt);
	vsnprint(buf, sizeof buf, fmt, arg);
	va_end(arg);

	if(pipe(pfd) < 0){
	Err:
		close(fd);
		werrstr("pipeline for %s failed: %r", buf);
		return -1;
	}
	switch(rfork(RFPROC|RFMEM|RFFDG|RFREND|RFNOWAIT)){
	case -1:
		close(pfd[0]);
		close(pfd[1]);
		goto Err;
	case 0:
		dupfds(fd, pfd[1], 2, -1);
		argv[0] = "rc";
		argv[1] = "-c";
		argv[2] = buf;
		argv[3] = nil;
		exec("/bin/rc", argv);
		_exits(0);
	}
	close(fd);
	close(pfd[1]);
	return pfd[0];
}

char*
urlstr(Url *url){
	if(url->fullname[0])
		return url->fullname;
	return url->reltext;
}

Url *copyurl(Url *u){
	Url *v;
	v=emalloc(sizeof(Url));
	*v=*u;
	v->reltext = strdup(u->reltext);
	v->basename = strdup(u->basename);
	return v;
}

void freeurl(Url *u){
	free(u->reltext);
	free(u->basename);
	free(u);
}

void seturl(Url *url, char *urlname, char *base){
	url->reltext = strdup(urlname);
	url->basename = strdup(base);
	url->fullname[0] = 0;
	url->tag[0] = 0;
	url->map = 0;
}

Url* selurl(char *urlname){
	Url *last;

	last=selection;
	selection=emalloc(sizeof(Url));
	seturl(selection, urlname, current ? current->url->fullname : "");
	if(last) freeurl(last);
	message("selected: %s", urlstr(selection));
	plgrabkb(cmd);		/* for snarf */
	return selection;
}

/*
 * get the file at the given url
 */
void geturl(char *urlname, int post, int plumb, int map){
	int i, fd, typ;
	char cmd[NNAME];
	ulong n;
	Www *w;

	if(*urlname == '#' && post < 0){
		scrollto(urlname+1);
		return;
	}

	selurl(urlname);
	selection->map=map;

	message("getting %s", urlstr(selection));
	esetcursor(&patientcurs);
	for(;;){
		if((fd=urlget(selection, post)) < 0){
			message("%r");
			break;
		}
		message("getting %s", selection->fullname);
		if(mothmode && !plumb)
			typ = -1;
		else if((typ = mimetotype(selection->contenttype)) < 0)
			typ = snooptype(fd);

		switch(typ){
		default:
			if(plumb){
				message("unknown file type");
				close(fd);
				break;
			}
			snprint(cmd, sizeof(cmd), "%s", urltofile(selection));
			if(eenter("Save to", cmd, sizeof(cmd), &mouse) <= 0){
				close(fd);
				break;
			}
			save(fd, cmd);
			break;
		case HTML:
			fd = pipeline(fd, "exec uhtml");
		case PLAIN:
			n=0; 
			for(i=wwwtop-1; i>=0 && i!=(wwwtop-NWWW-1); i--){
				w = www(i);
				n += countpix(w->pix);
				if(n >= NPIXMB*1024*1024)
					killpix(w);
			}
			w = www(i = wwwtop++);
			if(i >= NWWW){
				/* wait for the reader to finish the document */
				while(!w->finished && !w->alldone){
					drawlock(0);
					sleep(10);
					drawlock(1);
				}
				freetext(w->text);
				freeform(w->form);
				freepix(w->pix);
				freeurl(w->url);
				memset(w, 0, sizeof(*w));
			}
			if(selection->map)
				w->url=copyurl(current->url);
			else
				w->url=copyurl(selection);
			w->finished = 0;
			w->alldone = 0;
			gettext(w, fd, typ);
			if(rfork(RFPROC|RFMEM|RFNOWAIT) == 0){
				for(;;){
					sleep(1000);
					if(w->finished || w->alldone)
						break;
					if(w->changed)
						write(kickpipe[1], "C", 1);
				}
				_exits(0);
			}
			plinitlist(list, PACKN|FILLX, genwww, 8, doprev);
			if(defdisplay) pldraw(list, screen);
			setcurrent(i, selection->tag);
			break;
		case GIF:
		case JPEG:
		case PNG:
		case BMP:
		case PAGE:
			filter(fd, "exec page -w");
			break;
		}
		break;
	}
	donecurs();
}
void updtext(Www *w){
	Rtext *t;
	Action *a;
	if(defdisplay && w->gottitle==0 && w->title[0]!='\0')
		pldraw(list, screen);
	for(t=w->text;t;t=t->next){
		a=t->user;
		if(a){
			if(a->field)
				mkfieldpanel(t);
			a->field=0;
		}
	}
	if(w != current)
		return;
	w->yoffs=plgetpostextview(text);
	plinittextview(text, PACKE|EXPAND, Pt(0, 0), w->text, dolink);
	plsetpostextview(text, w->yoffs);
	pldraw(text, screen);
}

void finish(Www *w){
	w->finished = 1;
	write(kickpipe[1], "F", 1);
}

void
mothon(Www *w, int on)
{
	Rtext *t, *x;
	Action *a, *ap;

	if(w==0 || mothmode==on)
		return;
	if(mothmode = on)
		message("moth mode!");
	else
		message(mothra);
	/*
	 * insert or remove artificial links to the href for 
	 * images that are also links
	 */
	for(t=w->text;t;t=t->next){
		a=t->user;
		if(a == nil || a->image == nil)
			continue;
		if(a->link == nil){
			if(on)
				t->flags |= PL_HOT;
			else
				t->flags &= ~PL_HOT;
			continue;
		}
		x = t->next;
		if(on){
			t->next = nil;
			ap=emalloc(sizeof(Action));
			ap->link = strdup(a->link);
			plrtstr(&t->next, 0, 0, 0, t->font, strdup("->"), PL_HOT, ap);
			t->next->next = x;
		} else {
			if(x) {
				t->next = x->next;
				x->next = nil;
				freetext(x);
			}
		}
	}
	updtext(w);
	donecurs();
}

void killpix(Www *w){
	Rtext *t;

	if(w==0 || !w->finished && !w->alldone)
		return;
	for(t=w->text; t; t=t->next)
		if(t->b && t->user)
			t->b=0;
	freepix(w->pix);
	w->pix=0;
	updtext(w);
}
void snarf(Panel *p){
	if(p==0 || p==cmd){
		if(selection){
			plputsnarf(urlstr(selection));
			plsnarf(text);
		}else
			message("no url selected");
	}else
		plsnarf(p);
}
void paste(Panel *p){
	if(p==0) p=cmd;
	plpaste(p);
}
void hit3(int button, int item){
	char buf[1024];
	char name[NNAME];
	char *s;
	Panel *swap;
	int fd;
	USED(button);
	switch(item){
	case 0:
		swap=root;
		root=alt;
		alt=swap;
		if(current)
			current->yoffs=plgetpostextview(text);
		swap=text;
		text=alttext;
		alttext=swap;
		defdisplay=!defdisplay;
		plpack(root, screen->r);
		if(current){
			plinittextview(text, PACKE|EXPAND, Pt(0, 0), current->text, dolink);
			plsetpostextview(text, current->yoffs);
		}
		pldraw(root, screen);
		break;
	case 1:
		mothon(current, !mothmode);
		break;
	case 2:
		snarf(plkbfocus);
		break;
	case 3:
		paste(plkbfocus);
		break;
	case 4:
		if(plkbfocus==nil || plkbfocus==cmd){
			if(text==nil || text->snarf==nil || selection==nil)
				return;
			if((s=text->snarf(text))==nil)
				s=smprint("%s", urlstr(selection));
		}else
			if((s=plkbfocus->snarf(plkbfocus))==nil)
				return;
		if((fd=plumbopen("send", OWRITE))<0){
			message("can't plumb");
			free(s);
			return;
		}
		plumbsendtext(fd, "mothra", nil, getwd(buf, sizeof buf), s);
		close(fd);
		free(s);
		break;
	case 5:
		search();
		break;
	case 6:
		if(!selection){
			message("no url selected");
			break;
		}
		snprint(name, sizeof(name), "%s/hit.html", mkhome());
		fd=open(name, OWRITE);
		if(fd==-1){
			fd=create(name, OWRITE, 0666);
			if(fd==-1){
				message("can't open %s", name);
				return;
			}
			fprint(fd, "<html><head><title>Hit List</title></head>\n");
			fprint(fd, "<body><h1>Hit list</h1>\n");
		}
		seek(fd, 0, 2);
		fprint(fd, "<p><a href=\"%s\">%s</a>\n", urlstr(selection), urlstr(selection));
		close(fd);
		break;
	case 7:
		snprint(name, sizeof(name), "file:%s/hit.html", mkhome());
		geturl(name, -1, 1, 0);
		break;
	case 8:
		if(confirm(3))
			exits(0);
		break;
	}
}