]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/mothra/mothra.c
mothra: never snarf the "Go:" box
[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->link == nil && a->image == nil))
722                 return;
723         if(mothmode)
724                 hiturl(buttons, a->image ? a->image : a->link, 0);
725         else if(a->link){
726                 if(a->ismap){
727                         yoffs=plgetpostextview(p);
728                         coord=subpt(subpt(mouse.xy, word->r.min), p->r.min);
729                         snprint(mapurl, sizeof(mapurl), "%s?%d,%d", a->link, coord.x, coord.y+yoffs);
730                         hiturl(buttons, mapurl, 1);
731                 } else
732                         hiturl(buttons, a->link, 0);
733         }
734 }
735
736 void filter(char *cmd, int fd){
737         switch(rfork(RFFDG|RFPROC|RFMEM|RFNOWAIT)){
738         case -1:
739                 message("Can't fork!");
740                 break;
741         case 0:
742                 close(0);
743                 dup(fd, 0);
744                 close(fd);
745                 execl("/bin/rc", "rc", "-c", cmd, 0);
746                 _exits(0);
747         }
748         close(fd);
749 }
750 void gettext(Www *w, int fd, int type){
751         switch(rfork(RFFDG|RFPROC|RFMEM|RFNOWAIT)){
752         case -1:
753                 message("Can't fork, please wait");
754                 break;
755         case 0:
756                 if(type==HTML)
757                         plrdhtml(w->url->fullname, fd, w);
758                 else
759                         plrdplain(w->url->fullname, fd, w);
760                 _exits(0);
761         }
762         close(fd);
763 }
764
765 void freetext(Rtext *t){
766         Rtext *tt;
767         Action *a;
768
769         tt = t;
770         for(; t!=0; t = t->next){
771                 t->b=0;
772                 free(t->text);
773                 t->text=0;
774                 if(a = t->user){
775                         t->user=0;
776                         free(a->image);
777                         free(a->link);
778                         free(a->name);
779                         free(a);
780                 }
781         }
782         plrtfree(tt);
783 }
784
785 int pipeline(char *cmd, int fd)
786 {
787         int pfd[2];
788
789         if(pipe(pfd)==-1){
790 Err:
791                 close(fd);
792                 werrstr("pipeline for %s failed: %r", cmd);
793                 return -1;
794         }
795         switch(fork()){
796         case -1:
797                 close(pfd[0]);
798                 close(pfd[1]);
799                 goto Err;
800         case 0:
801                 dup(fd, 0);
802                 dup(pfd[0], 1);
803                 close(pfd[0]);
804                 close(pfd[1]);
805                 execl("/bin/rc", "rc", "-c", cmd, 0);
806                 _exits(0);
807         }
808         close(pfd[0]);
809         close(fd);
810         return pfd[1];
811 }
812
813 char*
814 urlstr(Url *url){
815         if(url->fullname[0])
816                 return url->fullname;
817         return url->reltext;
818 }
819 Url* selurl(char *urlname){
820         static Url url;
821         seturl(&url, urlname, current ? current->url->fullname : "");
822         selection=&url;
823         message("selected: %s", urlstr(selection));
824         return selection;
825 }
826 void seturl(Url *url, char *urlname, char *base){
827         nstrcpy(url->reltext, urlname, sizeof(url->reltext));
828         nstrcpy(url->basename, base, sizeof(url->basename));
829         url->fullname[0] = 0;
830         url->tag[0] = 0;
831         url->map = 0;
832 }
833 Url *copyurl(Url *u){
834         Url *v;
835         v=emalloc(sizeof(Url));
836         *v=*u;
837         return v;
838 }
839 void freeurl(Url *u){
840         free(u);
841 }
842
843 /*
844  * get the file at the given url
845  */
846 void geturl(char *urlname, int post, int plumb, int map){
847         int i, fd, typ, pfd[2];
848         char cmd[NNAME];
849         ulong n;
850         Www *w;
851
852         if(*urlname == '#' && post < 0){
853                 scrollto(urlname+1);
854                 return;
855         }
856
857         selurl(urlname);
858         selection->map=map;
859
860         message("getting %s", selection->reltext);
861         esetcursor(&patientcurs);
862         for(;;){
863                 if((fd=urlget(selection, post)) < 0){
864                         message("%r");
865                         break;
866                 }
867                 message("getting %s", selection->fullname);
868                 if(mothmode && !plumb)
869                         typ = -1;
870                 else
871                         typ = snooptype(fd);
872                 switch(typ){
873                 default:
874                         if(plumb){
875                                 message("unknown file type");
876                                 close(fd);
877                                 break;
878                         }
879                         snprint(cmd, sizeof(cmd), "%s", urltofile(selection));
880                         if(eenter("Save to", cmd, sizeof(cmd), &mouse) <= 0){
881                                 close(fd);
882                                 break;
883                         }
884                         save(fd, cmd);
885                         break;
886                 case HTML:
887                         fd = pipeline("/bin/uhtml", fd);
888                 case PLAIN:
889                         n=0; 
890                         for(i=wwwtop-1; i>=0 && i!=(wwwtop-NWWW-1); i--){
891                                 w = www(i);
892                                 n += countpix(w->pix);
893                                 if(n >= NPIXMB*1024*1024)
894                                         killpix(w);
895                         }
896                         w = www(i = wwwtop++);
897                         if(i >= NWWW){
898                                 /* wait for the reader to finish the document */
899                                 while(!w->finished && !w->alldone){
900                                         drawlock(0);
901                                         sleep(10);
902                                         drawlock(1);
903                                 }
904                                 freetext(w->text);
905                                 freeform(w->form);
906                                 freepix(w->pix);
907                                 freeurl(w->url);
908                                 memset(w, 0, sizeof(*w));
909                         }
910                         if(selection->map)
911                                 w->url=copyurl(current->url);
912                         else
913                                 w->url=copyurl(selection);
914                         w->finished = 0;
915                         w->alldone = 0;
916                         gettext(w, fd, typ);
917                         if(rfork(RFPROC|RFMEM|RFNOWAIT) == 0){
918                                 for(;;){
919                                         sleep(1000);
920                                         if(w->finished || w->alldone)
921                                                 break;
922                                         if(w->changed)
923                                                 write(kickpipe[1], "C", 1);
924                                 }
925                                 _exits(0);
926                         }
927                         plinitlist(list, PACKN|FILLX, genwww, 8, doprev);
928                         if(defdisplay) pldraw(list, screen);
929                         setcurrent(i, selection->tag);
930                         break;
931                 case GIF:
932                 case JPEG:
933                 case PNG:
934                 case BMP:
935                 case PAGE:
936                         filter("page -w", fd);
937                         break;
938                 }
939                 break;
940         }
941         donecurs();
942 }
943 void updtext(Www *w){
944         Rtext *t;
945         Action *a;
946         if(defdisplay && w->gottitle==0 && w->title[0]!='\0')
947                 pldraw(list, screen);
948         for(t=w->text;t;t=t->next){
949                 a=t->user;
950                 if(a){
951                         if(a->field)
952                                 mkfieldpanel(t);
953                         a->field=0;
954                 }
955         }
956         if(w != current)
957                 return;
958         w->yoffs=plgetpostextview(text);
959         plinittextview(text, PACKE|EXPAND, Pt(0, 0), w->text, dolink);
960         plsetpostextview(text, w->yoffs);
961         pldraw(text, screen);
962 }
963
964 void finish(Www *w){
965         w->finished = 1;
966         write(kickpipe[1], "F", 1);
967 }
968
969 void
970 mothon(Www *w, int on)
971 {
972         Rtext *t, *x;
973         Action *a, *ap;
974
975         if(w==0 || mothmode==on)
976                 return;
977         if(mothmode = on)
978                 message("moth mode!");
979         else
980                 message(mothra);
981         /*
982          * insert or remove artificial links to the href for 
983          * images that are also links
984          */
985         for(t=w->text;t;t=t->next){
986                 a=t->user;
987                 if(a == nil || a->image == nil)
988                         continue;
989                 if(a->link == nil){
990                         if(on)
991                                 t->flags |= PL_HOT;
992                         else
993                                 t->flags &= ~PL_HOT;
994                         continue;
995                 }
996                 x = t->next;
997                 if(on){
998                         t->next = nil;
999                         ap=emalloc(sizeof(Action));
1000                         ap->link = strdup(a->link);
1001                         plrtstr(&t->next, 0, 0, t->font, strdup("->"), PL_HOT, ap);
1002                         t->next->next = x;
1003                 } else {
1004                         t->next = x->next;
1005                         x->next = nil;
1006                         freetext(x);
1007                 }
1008         }
1009         updtext(w);
1010         donecurs();
1011 }
1012
1013 void killpix(Www *w){
1014         Rtext *t;
1015
1016         if(w==0 || !w->finished && !w->alldone)
1017                 return;
1018         for(t=w->text; t; t=t->next)
1019                 if(t->b && t->user)
1020                         t->b=0;
1021         freepix(w->pix);
1022         w->pix=0;
1023         updtext(w);
1024 }
1025 void snarf(Panel *p){
1026         if(p==0 || p==cmd){
1027                 plputsnarf(urlstr(selection));
1028                 plsnarf(text);
1029         }else
1030                 plsnarf(p);
1031 }
1032 void paste(Panel *p){
1033         if(p==0) p=cmd;
1034         plpaste(p);
1035 }
1036 void hit3(int button, int item){
1037         char name[NNAME];
1038         char file[128];
1039         Panel *swap;
1040         int fd;
1041         USED(button);
1042         switch(item){
1043         case 0:
1044                 swap=root;
1045                 root=alt;
1046                 alt=swap;
1047                 if(current)
1048                         current->yoffs=plgetpostextview(text);
1049                 swap=text;
1050                 text=alttext;
1051                 alttext=swap;
1052                 defdisplay=!defdisplay;
1053                 plpack(root, screen->r);
1054                 if(current){
1055                         plinittextview(text, PACKE|EXPAND, Pt(0, 0), current->text, dolink);
1056                         plsetpostextview(text, current->yoffs);
1057                 }
1058                 pldraw(root, screen);
1059                 break;
1060         case 1:
1061                 mothon(current, !mothmode);
1062                 break;
1063         case 2:
1064                 snarf(plkbfocus);
1065                 break;
1066         case 3:
1067                 paste(plkbfocus);
1068                 break;
1069         case 4:
1070                 snprint(name, sizeof(name), "%s/hit.html", home);
1071                 fd=open(name, OWRITE);
1072                 if(fd==-1){
1073                         fd=create(name, OWRITE, 0666);
1074                         if(fd==-1){
1075                                 message("can't open %s", name);
1076                                 return;
1077                         }
1078                         fprint(fd, "<html><head><title>Hit List</title></head>\n");
1079                         fprint(fd, "<body><h1>Hit list</h1>\n");
1080                 }
1081                 seek(fd, 0, 2);
1082                 fprint(fd, "<p><a href=\"%s\">%s</a>\n", urlstr(selection), urlstr(selection));
1083                 close(fd);
1084                 break;
1085         case 5:
1086                 snprint(name, sizeof(name), "file:%s/hit.html", home);
1087                 geturl(name, -1, 1, 0);
1088                 break;
1089         case 6:
1090                 if(confirm(3))
1091                         exits(0);
1092                 break;
1093         }
1094 }