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