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