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