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