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