]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/mothra/mothra.c
17306d5506f6b00c38eb03629469508ef5e77e05
[plan9front.git] / sys / src / cmd / mothra / mothra.c
1 /*
2  * Trivial web browser
3  */
4 #include <u.h>
5 #include <libc.h>
6 #include <draw.h>
7 #include <event.h>
8 #include <keyboard.h>
9 #include <plumb.h>
10 #include <cursor.h>
11 #include <panel.h>
12 #include "mothra.h"
13 #include "rtext.h"
14 int debug=0;            /* -d flag causes debug messages to appear in mothra.err */
15 int verbose=0;          /* -v flag causes html errors to appear in mothra.err */
16 int defdisplay=1;       /* is the default (initial) display visible? */
17 Panel *root;    /* the whole display */
18 Panel *alt;     /* the alternate display */
19 Panel *alttext; /* the alternate text window */
20 Panel *cmd;     /* command entry */
21 Panel *cururl;  /* label giving the url of the visible text */
22 Panel *list;    /* list of previously acquired www pages */
23 Panel *msg;     /* message display */
24 Panel *menu3;   /* button 3 menu */
25 char mothra[] = "mothra!";
26 Cursor patientcurs={
27         0, 0,
28         0x01, 0x80, 0x03, 0xC0, 0x07, 0xE0, 0x07, 0xe0,
29         0x07, 0xe0, 0x07, 0xe0, 0x03, 0xc0, 0x0F, 0xF0,
30         0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8,
31         0x0F, 0xF0, 0x1F, 0xF8, 0x3F, 0xFC, 0x3F, 0xFC,
32
33         0x01, 0x80, 0x03, 0xC0, 0x07, 0xE0, 0x04, 0x20,
34         0x04, 0x20, 0x06, 0x60, 0x02, 0x40, 0x0C, 0x30,
35         0x10, 0x08, 0x14, 0x08, 0x14, 0x28, 0x12, 0x28,
36         0x0A, 0x50, 0x16, 0x68, 0x20, 0x04, 0x3F, 0xFC,
37 };
38 Cursor confirmcurs={
39         0, 0,
40         0x0F, 0xBF, 0x0F, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF,
41         0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFE, 0xFF, 0xFE,
42         0xFF, 0xFE, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xFF,
43         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC,
44
45         0x00, 0x0E, 0x07, 0x1F, 0x03, 0x17, 0x73, 0x6F,
46         0xFB, 0xCE, 0xDB, 0x8C, 0xDB, 0xC0, 0xFB, 0x6C,
47         0x77, 0xFC, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03,
48         0x94, 0xA6, 0x63, 0x3C, 0x63, 0x18, 0x94, 0x90
49 };
50 Cursor readingcurs={
51         -10, -3,
52         0x00, 0x00, 0x00, 0x00, 0x0F, 0xF0, 0x0F, 0xF0,
53         0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x1F, 0xF0,
54         0x3F, 0xF0, 0x7F, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF,
55         0xFB, 0xFF, 0xF3, 0xFF, 0x00, 0x00, 0x00, 0x00,
56
57         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xE0,
58         0x07, 0xE0, 0x01, 0xE0, 0x03, 0xE0, 0x07, 0x60,
59         0x0E, 0x60, 0x1C, 0x00, 0x38, 0x00, 0x71, 0xB6,
60         0x61, 0xB6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
61 };
62 Cursor mothcurs={
63         {-7, -7},
64         {0x00, 0x00, 0x60, 0x06, 0xf8, 0x1f, 0xfc, 0x3f, 
65          0xfe, 0x7f, 0xff, 0xff, 0x7f, 0xfe, 0x7f, 0xfe, 
66          0x7f, 0xfe, 0x3f, 0xfc, 0x3f, 0xfc, 0x1f, 0xf8, 
67          0x1f, 0xf8, 0x0e, 0x70, 0x0c, 0x30, 0x00, 0x00, },
68         {0x00, 0x00, 0x00, 0x00, 0x60, 0x06, 0x58, 0x1a, 
69          0x5c, 0x3a, 0x64, 0x26, 0x27, 0xe4, 0x37, 0xec, 
70          0x37, 0xec, 0x17, 0xe8, 0x1b, 0xd8, 0x0e, 0x70, 
71          0x0c, 0x30, 0x04, 0x20, 0x00, 0x00, 0x00, 0x00, }
72 };
73
74 Www *current=0;
75 Url *selection=0;
76 int mothmode;
77 int kickpipe[2];
78
79 void docmd(Panel *, char *);
80 void doprev(Panel *, int, int);
81 char *urlstr(Url *);
82 void setcurrent(int, char *);
83 char *genwww(Panel *, int);
84 void updtext(Www *);
85 void dolink(Panel *, int, Rtext *);
86 void hit3(int, int);
87 void mothon(Www *, int);
88 void killpix(Www *w);
89 char *buttons[]={
90         "alt display",
91         "moth mode",
92         "snarf",
93         "paste",
94         "save hit",
95         "hit list",
96         "exit",
97         0
98 };
99
100 int wwwtop=0;
101 Www *www(int index){
102         static Www a[NWWW];
103         return &a[index % NWWW];
104 }
105 int nwww(void){
106         return wwwtop<NWWW ? wwwtop : NWWW;
107 }
108
109 int subpanel(Panel *obj, Panel *subj){
110         if(obj==0) return 0;
111         if(obj==subj) return 1;
112         for(obj=obj->child;obj;obj=obj->next)
113                 if(subpanel(obj, subj)) return 1;
114         return 0;
115 }
116 /*
117  * Make sure that the keyboard focus is on-screen, by adjusting it to
118  * be the cmd entry if necessary.
119  */
120 int adjkb(void){
121         Rtext *t;
122         int yoffs;
123         if(current){
124                 yoffs=text->r.min.y-plgetpostextview(text);
125                 for(t=current->text;t;t=t->next) if(!eqrect(t->r, Rect(0,0,0,0))){
126                         if(t->r.max.y+yoffs>=text->r.min.y
127                         && t->r.min.y+yoffs<text->r.max.y
128                         && t->b==0
129                         && subpanel(t->p, plkbfocus))
130                                 return 1;
131                 }
132         }
133         plgrabkb(cmd);
134         return 0;
135 }
136
137 void scrolltext(int dy, int whence)
138 {
139         Scroll s;
140
141         s = plgetscroll(text);
142         switch(whence){
143         case 0:
144                 s.pos.y = dy;
145                 break;
146         case 1:
147                 s.pos.y += dy;
148                 break;
149         case 2:
150                 s.pos.y = s.size.y+dy;
151                 break;
152         }
153         if(s.pos.y < 0)
154                 s.pos.y = 0;
155         if(s.pos.y > s.size.y)
156                 s.pos.y = s.size.y;
157         plsetscroll(text, s);
158 }
159
160 void mkpanels(void){
161         Panel *p, *bar;
162         menu3=plmenu(0, 0, buttons, PACKN|FILLX, hit3);
163         root=plpopup(root, EXPAND, 0, 0, menu3);
164                 p=plgroup(root, PACKN|FILLX);
165                         msg=pllabel(p, PACKN|FILLX, mothra);
166                         plplacelabel(msg, PLACEW);
167                         pllabel(p, PACKW, "Go:");
168                         cmd=plentry(p, PACKN|FILLX, 0, "", docmd);
169                 p=plgroup(root, PACKN|FILLX);
170                         bar=plscrollbar(p, PACKW);
171                         list=pllist(p, PACKN|FILLX, genwww, 8, doprev);
172                         plscroll(list, 0, bar);
173                 p=plgroup(root, PACKN|FILLX);
174                         pllabel(p, PACKW, "Url:");
175                         cururl=pllabel(p, PACKE|EXPAND, "---");
176                         plplacelabel(cururl, PLACEW);
177                 p=plgroup(root, PACKN|EXPAND);
178                         bar=plscrollbar(p, PACKW|USERFL);
179                         text=pltextview(p, PACKE|EXPAND, Pt(0, 0), 0, dolink);
180                         plscroll(text, 0, bar);
181         plgrabkb(cmd);
182         alt=plpopup(0, PACKE|EXPAND, 0, 0, menu3);
183                 bar=plscrollbar(alt, PACKW|USERFL);
184                 alttext=pltextview(alt, PACKE|EXPAND, Pt(0, 0), 0, dolink);
185                 plscroll(alttext, 0, bar);
186 }
187 int cohort = -1;
188 void killcohort(void){
189         int i;
190         for(i=0;i!=3;i++){      /* It's a long way to the kitchen */
191                 postnote(PNGROUP, cohort, "kill\n");
192                 sleep(1);
193         }
194 }
195 void catch(void*, char*){
196         noted(NCONT);
197 }
198 void dienow(void*, char*){
199         noted(NDFLT);
200 }
201 int mkmfile(char *stem, int mode){
202         char *henv;
203         char filename[NNAME];
204         int f;
205         if(home[0]=='\0'){
206                 henv=getenv("home");
207                 if(henv){
208                         sprint(home, "%s/lib", henv);
209                         f=create(home, OREAD, DMDIR|0777);
210                         if(f!=-1) close(f);
211                         sprint(home, "%s/lib/mothra", henv);
212                         f=create(home, OREAD, DMDIR|0777);
213                         if(f!=-1) close(f);
214                         free(henv);
215                 }
216                 else
217                         strcpy(home, "/tmp");
218         }
219         snprint(filename, sizeof(filename), "%s/%s", home, stem);
220         f=create(filename, OWRITE, mode);
221         if(f==-1)
222                 f=create(stem, OWRITE, mode);
223         return f;
224 }
225
226 void donecurs(void){
227         if(current && current->alldone==0)
228                 esetcursor(&readingcurs);
229         else if(mothmode)
230                 esetcursor(&mothcurs);
231         else
232                 esetcursor(0);
233 }
234
235 void drawlock(int dolock){
236         static int ref = 0;
237         if(dolock){
238                 if(ref++ == 0)
239                         lockdisplay(display);
240         } else {
241                 if(--ref == 0)
242                         unlockdisplay(display);
243         }
244 }
245
246 void scrollto(char *tag);
247 extern char *mtpt; /* url */
248
249 void main(int argc, char *argv[]){
250         Event e;
251         enum { Eplumb = 128, Ekick = 256 };
252         Plumbmsg *pm;
253         Www *new;
254         Action *a;
255         char *url;
256         int errfile;
257         int i;
258
259         quotefmtinstall();
260         fmtinstall('U', Ufmt);
261
262         ARGBEGIN{
263         case 'd': debug=1; break;
264         case 'v': verbose=1; break;
265         case 'm':
266                 if(mtpt = ARGF())
267                         break;
268         default:  goto Usage;
269         }ARGEND
270
271         /*
272          * so that we can stop all subprocesses with a note,
273          * and to isolate rendezvous from other processes
274          */
275         if(cohort=rfork(RFPROC|RFNOTEG|RFNAMEG|RFREND)){
276                 atexit(killcohort);
277                 notify(catch);
278                 waitpid();
279                 exits(0);
280         }
281         cohort = getpid();
282         atexit(killcohort);
283
284         switch(argc){
285         default:
286         Usage:
287                 fprint(2, "Usage: %s [-d] [-m mtpt] [url]\n", argv[0]);
288                 exits("usage");
289         case 0:
290                 url=getenv("url");
291                 break;
292         case 1: url=argv[0]; break;
293         }
294         errfile=mkmfile("mothra.err", 0666);
295         if(errfile!=-1){
296                 dup(errfile, 2);
297                 close(errfile);
298         }
299         if(initdraw(0, 0, mothra) < 0)
300                 sysfatal("initdraw: %r");
301         display->locking = 1;
302         chrwidth=stringwidth(font, "0");
303         pltabsize(chrwidth, 8*chrwidth);
304         einit(Emouse|Ekeyboard);
305         eplumb(Eplumb, "web");
306         if(pipe(kickpipe) < 0)
307                 sysfatal("pipe: %r");
308         estart(Ekick, kickpipe[0], 256);
309         plinit(screen->depth);
310         if(debug) notify(dienow);
311         getfonts();
312         hrule=allocimage(display, Rect(0, 0, 2048, 5), screen->chan, 0, DWhite);
313         if(hrule==0)
314                 sysfatal("can't allocimage!");
315         draw(hrule, Rect(0,1,1280,3), display->black, 0, ZP);
316         linespace=allocimage(display, Rect(0, 0, 2048, 5), screen->chan, 0, DWhite);
317         if(linespace==0)
318                 sysfatal("can't allocimage!");
319         bullet=allocimage(display, Rect(0,0,25, 8), screen->chan, 0, DWhite);
320         fillellipse(bullet, Pt(4,4), 3, 3, display->black, ZP);
321         mkpanels();
322
323         unlockdisplay(display);
324         eresized(0);
325         drawlock(1);
326
327         if(url && url[0])
328                 geturl(url, -1, 1, 0);
329
330         mouse.buttons=0;
331         for(;;){
332                 if(mouse.buttons==0 && current){
333                         if(current->finished){
334                                 updtext(current);
335                                 if(current->url->tag[0])
336                                         scrollto(current->url->tag);
337                                 current->finished=0;
338                                 current->changed=0;
339                                 current->alldone=1;
340                                 message(mothra);
341                                 donecurs();
342                         }
343                 }
344
345                 flushimage(display, 1);
346                 drawlock(0);
347                 i=event(&e);
348                 drawlock(1);
349
350                 switch(i){
351                 case Ekick:
352                         if(mouse.buttons==0 && current && current->changed){
353                                 if(!current->finished)
354                                         updtext(current);
355                                 current->changed=0;
356                         }
357                         break;
358                 case Ekeyboard:
359                         switch(e.kbdc){
360                         default:
361                                 adjkb();
362                                 plkeyboard(e.kbdc);
363                                 break;
364                         case Khome:
365                                 scrolltext(0, 0);
366                                 break;
367                         case Kup:
368                                 scrolltext(-text->size.y/4, 1);
369                                 break;
370                         case Kpgup:
371                                 scrolltext(-text->size.y/2, 1);
372                                 break;
373                         case Kdown:
374                                 scrolltext(text->size.y/4, 1);
375                                 break;
376                         case Kpgdown:
377                                 scrolltext(text->size.y/2, 1);
378                                 break;
379                         case Kend:
380                                 scrolltext(-text->size.y, 2);
381                                 break;
382                         }
383                         break;
384                 case Emouse:
385                         mouse=e.mouse;
386                         if(mouse.buttons & (8|16)){
387                                 if(mouse.buttons & 8)
388                                         scrolltext(-text->size.y/24, 1);
389                                 else
390                                         scrolltext(text->size.y/24, 1);
391                                 break;
392                         }
393                         plmouse(root, &mouse);
394                         break;
395                 case Eplumb:
396                         pm=e.v;
397                         if(pm->ndata > 0)
398                                 geturl(pm->data, -1, 1, 0);
399                         plumbfree(pm);
400                         break;
401                 }
402         }
403 }
404 Cursor confirmcursor={
405         0, 0,
406         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
407         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
408         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
409         0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
410
411         0x00, 0x0E, 0x07, 0x1F, 0x03, 0x17, 0x73, 0x6F,
412         0xFB, 0xCE, 0xDB, 0x8C, 0xDB, 0xC0, 0xFB, 0x6C,
413         0x77, 0xFC, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03,
414         0x94, 0xA6, 0x63, 0x3C, 0x63, 0x18, 0x94, 0x90,
415 };
416 int confirm(int b){
417         Mouse down, up;
418         esetcursor(&confirmcursor);
419         do down=emouse(); while(!down.buttons);
420         do up=emouse(); while(up.buttons);
421         donecurs();
422         return down.buttons==(1<<(b-1));
423 }
424 void message(char *s, ...){
425         static char buf[1024];
426         char *out;
427         va_list args;
428         va_start(args, s);
429         out = buf + vsnprint(buf, sizeof(buf), s, args);
430         va_end(args);
431         *out='\0';
432         plinitlabel(msg, PACKN|FILLX, buf);
433         if(defdisplay) pldraw(msg, screen);
434 }
435 void htmlerror(char *name, int line, char *m, ...){
436         static char buf[1024];
437         char *out;
438         va_list args;
439         if(verbose){
440                 va_start(args, m);
441                 out=buf+snprint(buf, sizeof(buf), "%s: line %d: ", name, line);
442                 out+=vsnprint(out, sizeof(buf)-(out-buf)-1, m, args);
443                 va_end(args);
444                 *out='\0';
445                 fprint(2, "%s\n", buf);
446         }
447 }
448 void eresized(int new){
449         Rectangle r;
450
451         drawlock(1);
452         if(new && getwindow(display, Refnone) == -1) {
453                 fprint(2, "getwindow: %r\n");
454                 exits("getwindow");
455         }
456         r=screen->r;
457         plpack(root, r);
458         plpack(alt, r);
459         draw(screen, r, display->white, 0, ZP);
460         pldraw(root, screen);
461         flushimage(display, 1);
462         drawlock(0);
463 }
464 void *emalloc(int n){
465         void *v;
466         v=malloc(n);
467         if(v==0)
468                 sysfatal("out of memory");
469         memset(v, 0, n);
470         setmalloctag(v, getcallerpc(&n));
471         return v;
472 }
473 void nstrcpy(char *to, char *from, int len){
474         strncpy(to, from, len);
475         to[len-1] = 0;
476 }
477
478 char *genwww(Panel *, int index){
479         static char buf[1024];
480         Www *w;
481         int i;
482
483         if(index >= nwww())
484                 return 0;
485         i = wwwtop-index-1;
486         w = www(i);
487         if(w->title[0]!='\0')
488                 w->gottitle=1;
489         snprint(buf, sizeof(buf), "%2d %s", i+1, w->title);
490         return buf;
491 }
492
493 void scrollto(char *tag){
494         Rtext *tp;
495         Action *ap;
496         if(current == nil || text == nil)
497                 return;
498         if(tag && tag[0]){
499                 for(tp=current->text;tp;tp=tp->next){
500                         ap=tp->user;
501                         if(ap && ap->name && strcmp(ap->name, tag)==0){
502                                 current->yoffs=tp->topy;
503                                 break;
504                         }
505                 }
506         }
507         plsetpostextview(text, current->yoffs);
508         flushimage(display, 1);
509 }
510
511 /*
512  * selected text should be a url.
513  */
514 void setcurrent(int index, char *tag){
515         Www *new;
516         int i;
517         new=www(index);
518         if(new==current && (tag==0 || tag[0]==0)) return;
519         if(current)
520                 current->yoffs=plgetpostextview(text);
521         current=new;
522         plinitlabel(cururl, PACKE|EXPAND, current->url->fullname);
523         if(defdisplay) pldraw(cururl, screen);
524         plinittextview(text, PACKE|EXPAND, Pt(0, 0), current->text, dolink);
525         scrollto(tag);
526         if((i = open("/dev/label", OWRITE)) >= 0){
527                 fprint(i, "%s %s", mothra, current->url->fullname);
528                 close(i);
529         }
530         donecurs();
531 }
532 char *arg(char *s){
533         do ++s; while(*s==' ' || *s=='\t');
534         return s;
535 }
536 void save(int ifd, char *name){
537         char buf[NNAME+64];
538         int cfd, ofd;
539         if(ifd < 0){
540                 message("save: %s: %r", name);
541                 return;
542         }
543         ofd=create(name, OWRITE, 0666);
544         if(ofd < 0){
545                 message("save: %s: %r", name);
546                 return;
547         }
548         switch(rfork(RFNOTEG|RFNAMEG|RFFDG|RFMEM|RFPROC|RFNOWAIT)){
549         case -1:
550                 message("Can't fork: %r");
551                 break;
552         case 0:
553                 dup(ifd, 0);
554                 close(ifd);
555                 dup(ofd, 1);
556                 close(ofd);
557
558                 snprint(buf, sizeof(buf),
559                         "{tput -p || cat} |[2] {aux/statusmsg -k %q >/dev/null || cat >/dev/null}", name);
560                 execl("/bin/rc", "rc", "-c", buf, nil);
561                 exits("exec");
562         }
563         close(ifd);
564         close(ofd);
565         donecurs();
566 }
567 void screendump(char *name, int full){
568         Image *b;
569         int fd;
570         fd=create(name, OWRITE, 0666);
571         if(fd==-1){
572                 message("can't create %s", name);
573                 return;
574         }
575         if(full){
576                 writeimage(fd, screen, 0);
577         } else {
578                 if((b=allocimage(display, text->r, screen->chan, 0, DNofill)) == nil){
579                         message("can't allocate image");
580                         close(fd);
581                         return;
582                 }
583                 draw(b, b->r, screen, 0, b->r.min);
584                 writeimage(fd, b, 0);
585                 freeimage(b);
586         }
587         close(fd);
588 }
589
590 /*
591  * convert a url into a local file name.
592  */
593 char *urltofile(Url *url){
594         char *name, *slash;
595         if(url == nil)
596                 return nil;
597         if(url->fullname[0] || url->reltext[0])
598                 name = urlstr(url);
599         else
600                 name = "/";
601         if(slash = strrchr(name, '/'))
602                 name = slash+1;
603         if(name[0] == 0)
604                 name = "index";
605         return name;
606 }
607
608 /*
609  * user typed a command.
610  */
611 void docmd(Panel *p, char *s){
612         char buf[NNAME];
613         int c;
614
615         USED(p);
616         while(*s==' ' || *s=='\t') s++;
617         /*
618          * Non-command does a get on the url
619          */
620         if(s[0]!='\0' && s[1]!='\0' && s[1]!=' ')
621                 geturl(s, -1, 0, 0);
622         else switch(c = s[0]){
623         default:
624                 message("Unknown command %s", s);
625                 break;
626         case 'a':
627                 s = arg(s);
628                 if(*s=='\0' && selection)
629                         hit3(3, 0);
630                 break;
631         case 'g':
632                 s = arg(s);
633                 if(*s=='\0'){
634         case 'r':
635                         if(selection)
636                                 s = urlstr(selection);
637                         else
638                                 message("no url selected");
639                 }
640                 geturl(s, -1, 0, 0);
641                 break;
642         case 'j':
643                 s = arg(s);
644                 if(*s)
645                         doprev(nil, 1, wwwtop-atoi(s));
646                 else
647                         message("Usage: j index");
648                 break;
649         case 'm':
650                 mothon(current, !mothmode);
651                 break;
652         case 'k':
653                 killpix(current);
654                 break;
655         case 'w':
656         case 'W':
657                 s = arg(s);
658                 if(s==0 || *s=='\0'){
659                         snprint(buf, sizeof(buf), "dump.bit");
660                         if(eenter("Screendump to", buf, sizeof(buf), &mouse) <= 0)
661                                 break;
662                         s = buf;
663                 }
664                 screendump(s, c == 'W');
665                 break;
666         case 's':
667                 s = arg(s);
668                 if(!selection){
669                         message("no url selected");
670                         break;
671                 }
672                 if(s==0 || *s=='\0'){
673                         snprint(buf, sizeof(buf), "%s", urltofile(selection));
674                         if(eenter("Save to", buf, sizeof(buf), &mouse) <= 0)
675                                 break;
676                         s = buf;
677                 }
678                 save(urlget(selection, -1), s);
679                 break;
680         case 'q':
681                 exits(0);
682         }
683         plinitentry(cmd, EXPAND, 0, "", docmd);
684         if(defdisplay) pldraw(cmd, screen);
685 }
686
687 void hiturl(int buttons, char *url, int map){
688         switch(buttons){
689         case 1: geturl(url, -1, 0, map); break;
690         case 2: selurl(url); break;
691         case 4: message("Button 3 hit on url can't happen!"); break;
692         }
693 }
694
695 /*
696  * user selected from the list of available pages
697  */
698 void doprev(Panel *p, int buttons, int index){
699         int i;
700         USED(p);
701         if(index < 0 || index >= nwww())
702                 return;
703         i = wwwtop-index-1;
704         switch(buttons){
705         case 1: setcurrent(i, 0);       /* no break ... */
706         case 2: selurl(www(i)->url->fullname); break;
707         case 4: message("Button 3 hit on page can't happen!"); break;
708         }
709 }
710
711 /*
712  * Follow an html link
713  */
714 void dolink(Panel *p, int buttons, Rtext *word){
715         char *file, mapurl[NNAME];
716         Point coord;
717         int yoffs;
718         Action *a;
719
720         a=word->user;
721         if(a == nil || a->image == nil && a->link == nil)
722                 return;
723         if(mothmode)
724                 hiturl(buttons, a->image ? a->image : a->link, 0);
725         else if(a->ismap){
726                 yoffs=plgetpostextview(p);
727                 coord=subpt(subpt(mouse.xy, word->r.min), p->r.min);
728                 snprint(mapurl, sizeof(mapurl), "%s?%d,%d", a->link, coord.x, coord.y+yoffs);
729                 hiturl(buttons, mapurl, 1);
730         } else
731                 hiturl(buttons, a->link ? a->link : a->image, 0);
732 }
733
734 void filter(char *cmd, int fd){
735         switch(rfork(RFFDG|RFPROC|RFMEM|RFNOWAIT)){
736         case -1:
737                 message("Can't fork!");
738                 break;
739         case 0:
740                 close(0);
741                 dup(fd, 0);
742                 close(fd);
743                 execl("/bin/rc", "rc", "-c", cmd, 0);
744                 _exits(0);
745         }
746         close(fd);
747 }
748 void gettext(Www *w, int fd, int type){
749         switch(rfork(RFFDG|RFPROC|RFMEM|RFNOWAIT)){
750         case -1:
751                 message("Can't fork, please wait");
752                 break;
753         case 0:
754                 if(type==HTML)
755                         plrdhtml(w->url->fullname, fd, w);
756                 else
757                         plrdplain(w->url->fullname, fd, w);
758                 _exits(0);
759         }
760         close(fd);
761 }
762
763 void freetext(Rtext *t){
764         Rtext *tt;
765         Action *a;
766
767         tt = t;
768         for(; t!=0; t = t->next){
769                 t->b=0;
770                 free(t->text);
771                 t->text=0;
772                 if(a = t->user){
773                         t->user=0;
774                         free(a->image);
775                         free(a->link);
776                         free(a->name);
777                         free(a);
778                 }
779         }
780         plrtfree(tt);
781 }
782
783 int pipeline(char *cmd, int fd)
784 {
785         int pfd[2];
786
787         if(pipe(pfd)==-1){
788 Err:
789                 close(fd);
790                 werrstr("pipeline for %s failed: %r", cmd);
791                 return -1;
792         }
793         switch(fork()){
794         case -1:
795                 close(pfd[0]);
796                 close(pfd[1]);
797                 goto Err;
798         case 0:
799                 dup(fd, 0);
800                 dup(pfd[0], 1);
801                 close(pfd[0]);
802                 close(pfd[1]);
803                 execl("/bin/rc", "rc", "-c", cmd, 0);
804                 _exits(0);
805         }
806         close(pfd[0]);
807         close(fd);
808         return pfd[1];
809 }
810
811 char*
812 urlstr(Url *url){
813         if(url->fullname[0])
814                 return url->fullname;
815         return url->reltext;
816 }
817 Url* selurl(char *urlname){
818         static Url url;
819         seturl(&url, urlname, current ? current->url->fullname : "");
820         selection=&url;
821         message("selected: %s", urlstr(selection));
822         return selection;
823 }
824 void seturl(Url *url, char *urlname, char *base){
825         nstrcpy(url->reltext, urlname, sizeof(url->reltext));
826         nstrcpy(url->basename, base, sizeof(url->basename));
827         url->fullname[0] = 0;
828         url->tag[0] = 0;
829         url->map = 0;
830 }
831 Url *copyurl(Url *u){
832         Url *v;
833         v=emalloc(sizeof(Url));
834         *v=*u;
835         return v;
836 }
837 void freeurl(Url *u){
838         free(u);
839 }
840
841 /*
842  * get the file at the given url
843  */
844 void geturl(char *urlname, int post, int plumb, int map){
845         int i, fd, typ, pfd[2];
846         char cmd[NNAME];
847         ulong n;
848         Www *w;
849
850         if(*urlname == '#' && post < 0){
851                 scrollto(urlname+1);
852                 return;
853         }
854
855         selurl(urlname);
856         selection->map=map;
857
858         message("getting %s", selection->reltext);
859         esetcursor(&patientcurs);
860         for(;;){
861                 if((fd=urlget(selection, post)) < 0){
862                         message("%r");
863                         break;
864                 }
865                 message("getting %s", selection->fullname);
866                 if(mothmode && !plumb)
867                         typ = -1;
868                 else
869                         typ = snooptype(fd);
870                 switch(typ){
871                 default:
872                         if(plumb){
873                                 message("unknown file type");
874                                 close(fd);
875                                 break;
876                         }
877                         snprint(cmd, sizeof(cmd), "%s", urltofile(selection));
878                         if(eenter("Save to", cmd, sizeof(cmd), &mouse) <= 0){
879                                 close(fd);
880                                 break;
881                         }
882                         save(fd, cmd);
883                         break;
884                 case HTML:
885                         fd = pipeline("/bin/uhtml", fd);
886                 case PLAIN:
887                         n=0; 
888                         for(i=wwwtop-1; i>=0 && i!=(wwwtop-NWWW-1); i--){
889                                 w = www(i);
890                                 n += countpix(w->pix);
891                                 if(n >= NPIXMB*1024*1024)
892                                         killpix(w);
893                         }
894                         w = www(i = wwwtop++);
895                         if(i >= NWWW){
896                                 /* wait for the reader to finish the document */
897                                 while(!w->finished && !w->alldone){
898                                         drawlock(0);
899                                         sleep(10);
900                                         drawlock(1);
901                                 }
902                                 freetext(w->text);
903                                 freeform(w->form);
904                                 freepix(w->pix);
905                                 freeurl(w->url);
906                                 memset(w, 0, sizeof(*w));
907                         }
908                         if(selection->map)
909                                 w->url=copyurl(current->url);
910                         else
911                                 w->url=copyurl(selection);
912                         w->finished = 0;
913                         w->alldone = 0;
914                         gettext(w, fd, typ);
915                         if(rfork(RFPROC|RFMEM|RFNOWAIT) == 0){
916                                 for(;;){
917                                         sleep(1000);
918                                         if(w->finished || w->alldone)
919                                                 break;
920                                         if(w->changed)
921                                                 write(kickpipe[1], "C", 1);
922                                 }
923                                 _exits(0);
924                         }
925                         plinitlist(list, PACKN|FILLX, genwww, 8, doprev);
926                         if(defdisplay) pldraw(list, screen);
927                         setcurrent(i, selection->tag);
928                         break;
929                 case GIF:
930                 case JPEG:
931                 case PNG:
932                 case BMP:
933                 case PAGE:
934                         filter("page -w", fd);
935                         break;
936                 }
937                 break;
938         }
939         donecurs();
940 }
941 void updtext(Www *w){
942         Rtext *t;
943         Action *a;
944         if(defdisplay && w->gottitle==0 && w->title[0]!='\0')
945                 pldraw(list, screen);
946         for(t=w->text;t;t=t->next){
947                 a=t->user;
948                 if(a){
949                         if(a->field)
950                                 mkfieldpanel(t);
951                         a->field=0;
952                 }
953         }
954         if(w != current)
955                 return;
956         w->yoffs=plgetpostextview(text);
957         plinittextview(text, PACKE|EXPAND, Pt(0, 0), w->text, dolink);
958         plsetpostextview(text, w->yoffs);
959         pldraw(text, screen);
960 }
961
962 void finish(Www *w){
963         w->finished = 1;
964         write(kickpipe[1], "F", 1);
965 }
966
967 void
968 mothon(Www *w, int on)
969 {
970         Rtext *t, *x;
971         Action *a, *ap;
972
973         if(w==0 || mothmode==on)
974                 return;
975         if(mothmode = on)
976                 message("moth mode!");
977         else
978                 message(mothra);
979         /*
980          * insert or remove artificial links to the href for 
981          * images that are also links
982          */
983         for(t=w->text;t;t=t->next){
984                 a=t->user;
985                 if(a == nil || a->image == nil || a->link == nil)
986                         continue;
987                 x = t->next;
988                 if(on){
989                         t->next = nil;
990                         ap=emalloc(sizeof(Action));
991                         ap->link = strdup(a->link);
992                         plrtstr(&t->next, 0, 0, t->font, strdup("->"), PL_HOT, ap);
993                         t->next->next = x;
994                 } else {
995                         t->next = x->next;
996                         x->next = nil;
997                         freetext(x);
998                 }
999         }
1000         updtext(w);
1001         donecurs();
1002 }
1003
1004 void killpix(Www *w){
1005         Rtext *t;
1006
1007         if(w==0 || !w->finished && !w->alldone)
1008                 return;
1009         for(t=w->text; t; t=t->next)
1010                 if(t->b && t->user)
1011                         t->b=0;
1012         freepix(w->pix);
1013         w->pix=0;
1014         updtext(w);
1015 }
1016 void snarf(Panel *p){
1017         if(p==0) p=cmd;
1018         plputsnarf(urlstr(selection));
1019         /* non-ops if nothing selected */
1020         plsnarf(p);
1021         plsnarf(text);
1022 }
1023 void paste(Panel *p){
1024         if(p==0) p=cmd;
1025         plpaste(p);
1026 }
1027 void hit3(int button, int item){
1028         char name[NNAME];
1029         char file[128];
1030         Panel *swap;
1031         int fd;
1032         USED(button);
1033         switch(item){
1034         case 0:
1035                 swap=root;
1036                 root=alt;
1037                 alt=swap;
1038                 if(current)
1039                         current->yoffs=plgetpostextview(text);
1040                 swap=text;
1041                 text=alttext;
1042                 alttext=swap;
1043                 defdisplay=!defdisplay;
1044                 plpack(root, screen->r);
1045                 if(current){
1046                         plinittextview(text, PACKE|EXPAND, Pt(0, 0), current->text, dolink);
1047                         plsetpostextview(text, current->yoffs);
1048                 }
1049                 pldraw(root, screen);
1050                 break;
1051         case 1:
1052                 mothon(current, !mothmode);
1053                 break;
1054         case 2:
1055                 snarf(plkbfocus);
1056                 break;
1057         case 3:
1058                 paste(plkbfocus);
1059                 break;
1060         case 4:
1061                 snprint(name, sizeof(name), "%s/hit.html", home);
1062                 fd=open(name, OWRITE);
1063                 if(fd==-1){
1064                         fd=create(name, OWRITE, 0666);
1065                         if(fd==-1){
1066                                 message("can't open %s", name);
1067                                 return;
1068                         }
1069                         fprint(fd, "<html><head><title>Hit List</title></head>\n");
1070                         fprint(fd, "<body><h1>Hit list</h1>\n");
1071                 }
1072                 seek(fd, 0, 2);
1073                 fprint(fd, "<p><a href=\"%s\">%s</a>\n", urlstr(selection), urlstr(selection));
1074                 close(fd);
1075                 break;
1076         case 5:
1077                 snprint(name, sizeof(name), "file:%s/hit.html", home);
1078                 geturl(name, -1, 1, 0);
1079                 break;
1080         case 6:
1081                 if(confirm(3))
1082                         exits(0);
1083                 break;
1084         }
1085 }