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