xref: /plan9/sys/src/cmd/abaco/page.c (revision 7ab27030036b6c877a6f81728daeda263d1ca3cf)
1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 #include <memdraw.h>
5 #include <thread.h>
6 #include <cursor.h>
7 #include <mouse.h>
8 #include <keyboard.h>
9 #include <frame.h>
10 #include <plumb.h>
11 #include <html.h>
12 #include "dat.h"
13 #include "fns.h"
14 
15 static void pageload1(Page *, Url *, int);
16 
17 static
18 void
addchild(Page * p,Page * c)19 addchild(Page *p, Page *c)
20 {
21 	Page *t;
22 
23 	c->parent = p;
24 	c->w = p->w;
25 	c->b = p->b;
26 	c->col = p->col;
27 	c->row = p->row;
28 	if(p->child == nil)
29 		p->child = c;
30 	else{
31 		for(t=p->child; t->next!=nil; t=t->next)
32 			;
33 		t->next = c;
34 	}
35 }
36 
37 static
38 void
loadchilds(Page * p,Kidinfo * k)39 loadchilds(Page *p, Kidinfo *k)
40 {
41 	Runestr rs;
42 	Kidinfo *t;
43 	Page *c;
44 
45 	addrefresh(p, "loading frames...");
46 	p->kidinfo = k;
47 	for(t=k->kidinfos; t!=nil; t=t->next){
48 		c = emalloc(sizeof(Page));
49 		addchild(p, c);
50 		if(t->isframeset){
51 			c->url = urldup(p->url);
52 			loadchilds(c, t);
53 		}else{
54 			c->kidinfo = t;
55 			/* this check shouldn't be necessary, but... */
56 			if(t->src){
57 				rs.r = urlcombine(p->url->act.r, t->src);
58 				rs.nr = runestrlen(rs.r);
59 				pageload1(c, urlalloc(&rs, nil, HGet), FALSE);
60 				closerunestr(&rs);
61 			}
62 		}
63 	}
64 }
65 
66 static struct {
67 	char *mime;
68 	char *filter;
69 }filtertab[] = {
70 	"image/gif",	"gif -t9",
71 	"image/jpeg",	"jpg -t9",
72 	"image/jpg",	"jpg -t9",
73 	"image/pjpeg",	"jpg -t9",
74 	"image/png",	"png -t9",
75 	"image/ppm",	"ppm -t9",
76 	nil,	nil,
77 };
78 
79 char *
getfilter(Rune * r,int x,int y)80 getfilter(Rune *r, int x, int y)
81 {
82 	char buf[128];
83 	int i;
84 
85 	snprint(buf, sizeof(buf), "%S", r);
86 	for(i=0; filtertab[i].mime!=nil; i++)
87 		if(cistrncmp(buf, filtertab[i].mime, strlen(filtertab[i].mime)) == 0)
88 			break;
89 
90 	if(filtertab[i].filter == nil)
91 		return nil;
92 
93 	if(x==0 && y==0)
94 		return smprint("%s", filtertab[i].filter);
95 	if(x!=0 && y!=0)
96 		return smprint("%s | resample -x %d -y %d", filtertab[i].filter, x, y);
97 	if(x != 0)
98 		return smprint("%s | resample -x %d", filtertab[i].filter, x);
99 	/* y != 0 */
100 	return smprint("%s | resample -y %d", filtertab[i].filter, y);
101 }
102 
103 static Cimage *cimages = nil;
104 static QLock cimagelock;
105 
106 static
107 void
freecimage(Cimage * ci)108 freecimage(Cimage *ci)
109 {
110 	Cimage *ci1;
111 
112 	qlock(&cimagelock);
113 	if(decref(ci) == 0){
114 		if(ci->i)
115 			freeimage(ci->i);
116 		else if(ci->mi)
117 			freememimage(ci->mi);
118 		urlfree(ci->url);
119 		ci1 = cimages;
120 		if(ci1 == ci)
121 			cimages = ci->next;
122 		else{
123 			while(ci1){
124 				if(ci1->next == ci){
125 					ci1->next = ci->next;
126 					break;
127 				}
128 				ci1 = ci1->next;
129 			}
130 		}
131 		free(ci);
132 	}
133 	qunlock(&cimagelock);
134 }
135 
136 static
137 void
closeimages(Page * p)138 closeimages(Page *p)
139 {
140 	int i;
141 
142 	for(i=0; i<p->ncimage; i++)
143 		freecimage(p->cimage[i]);
144 	free(p->cimage);
145 	p->cimage =nil;
146 	p->ncimage = 0;
147 }
148 
149 static
150 Cimage *
loadimg(Rune * src,int x,int y)151 loadimg(Rune *src, int x , int y)
152 {
153 	Channel *sync;
154 	Cimage *ci;
155 	Runestr rs;
156 	Exec *e;
157 	char *filter;
158 	int fd, p[2], q[2];
159 
160 	ci = emalloc(sizeof(Cimage));
161 	rs. r = src;
162 	rs.nr = runestrlen(rs.r);
163 	ci->url = urlalloc(&rs, nil, HGet);
164 	fd = urlopen(ci->url);
165 	if(fd < 0){
166     Err1:
167 		return ci;
168 	}
169 	filter = getfilter(ci->url->ctype.r, x, y);
170 	if(filter == nil){
171 		werrstr("%S unsupported: %S", ci->url->ctype.r, ci->url->act.r);
172     Err2:
173 		close(fd);
174 		goto Err1;
175 	}
176 
177 	if(pipe(p)<0 || pipe(q)<0)
178 		error("can't create pipe");
179 	close(p[0]);
180 	p[0] = fd;
181 	sync = chancreate(sizeof(ulong), 0);
182 	if(sync == nil)
183 		error("can't create channel");
184 	e = emalloc(sizeof(Exec));
185 	e->p[0] = p[0];
186 	e->p[1] = p[1];
187 	e->q[0] = q[0];
188 	e->q[1] = q[1];
189 	e->cmd = filter;
190 	e->sync = sync;
191 	proccreate(execproc, e, STACK);
192 	recvul(sync);
193 	chanfree(sync);
194 	close(p[0]);
195 	close(p[1]);
196 	close(q[1]);
197 
198 	ci->mi = readmemimage(q[0]);
199 	close(q[0]);
200 	if(ci->mi == nil){
201 		werrstr("can't read image");
202 		goto Err2;
203 	}
204 	free(filter);
205 	return ci;
206 }
207 
208 static
209 Cimage *
findimg(Rune * s)210 findimg(Rune *s)
211 {
212 	Cimage *ci;
213 
214 	qlock(&cimagelock);
215 	for(ci=cimages; ci!=nil; ci=ci->next)
216 		if(runestrcmp(ci->url->src.r, s) == 0)
217 			break;
218 
219 	qunlock(&cimagelock);
220 	return ci;
221 }
222 
223 void
loadimages(Page * p)224 loadimages(Page *p)
225 {
226 	Cimage *ci;
227 	Iimage *i;
228 	Rune *src;
229 
230 	addrefresh(p, "loading images...");
231 	reverseimages(&p->doc->images);
232 	for(i=p->doc->images; i!=nil; i=i->nextimage){
233 		if(p->aborting)
234 			break;
235 		src = urlcombine(getbase(p), i->imsrc);
236 		ci = findimg(src);
237 		if(ci == nil){
238 			ci = loadimg(src, i->imwidth, i->imheight);
239 			qlock(&cimagelock);
240 			ci->next = cimages;
241 			cimages = ci;
242 			qunlock(&cimagelock);
243 		}
244 		free(src);
245 		incref(ci);
246 		i->aux = ci;
247 		p->cimage = erealloc(p->cimage, ++p->ncimage*sizeof(Cimage *));
248 		p->cimage[p->ncimage-1] = ci;
249 		p->changed = TRUE;
250 		addrefresh(p, "");
251 	}
252 }
253 
254 static char *mimetab[] = {
255 	"text/html",
256 	"application/xhtml",
257 	nil,
258 };
259 
260 static
261 void
pageloadproc(void * v)262 pageloadproc(void *v)
263 {
264 	Page *p;
265 	char buf[BUFSIZE], *s;
266 	long n, l;
267 	int fd, i, ctype;
268 
269 	threadsetname("pageloadproc");
270 	rfork(RFFDG);
271 
272 	p = v;
273 	addrefresh(p, "opening: %S...", p->url->src.r);
274 	fd = urlopen(p->url);
275 	if(fd < 0){
276 		addrefresh(p, "%S: %r", p->url->src.r);
277     Err:
278 		p->loading = FALSE;
279 		return;
280 	}
281 	if(runestrlen(p->url->ctype.r) == 0) /* assume .html when headers don't say anyting */
282 		goto Html;
283 
284 	snprint(buf, sizeof(buf), "%S", p->url->ctype.r);
285 	for(i=0; mimetab[i]!=nil; i++)
286 		if(cistrncmp(buf, mimetab[i], strlen(mimetab[i])) == 0)
287 			break;
288 
289 	if(mimetab[i]){
290     Html:
291 		ctype = TextHtml;
292 	}else if(cistrncmp(buf, "text/", 5) == 0)
293 		ctype = TextPlain;
294 	else{
295 		close(fd);
296 		addrefresh(p, "%S: unsupported mime type: '%S'", p->url->act.r, p->url->ctype.r);
297 		goto Err;
298 	}
299 	addrefresh(p, "loading: %S...", p->url->src.r);
300 	s = nil;
301 	l = 0;
302 	while((n=read(fd, buf, sizeof(buf))) > 0){
303 		if(p->aborting){
304 			if(s){
305 				free(s);
306 				s = nil;
307 			}
308 			break;
309 		}
310 		s = erealloc(s, l+n+1);
311 		memmove(s+l, buf, n);
312 		l += n;
313 		s[l] = '\0';
314 	}
315 	close(fd);
316 	n = l;
317 	if(s){
318 		s = convert(p->url->ctype, s, &n);
319 		p->items = parsehtml((uchar *)s, n, p->url->act.r, ctype, UTF_8, &p->doc);
320 		free(s);
321 		fixtext(p);
322 		if(ctype==TextHtml && p->aborting==FALSE){
323 			p->changed = TRUE;
324 			addrefresh(p, "");
325 			if(p->doc->doctitle){
326 				p->title.r = erunestrdup(p->doc->doctitle);
327 				p->title.nr = runestrlen(p->title.r);
328 			}
329 			p->loading = XXX;
330 			if(p->doc->kidinfo)
331 				loadchilds(p, p->doc->kidinfo);
332 			else if(p->doc->images)
333 				loadimages(p);
334 		}
335 	}
336 	p->changed = TRUE;
337 	p->loading = FALSE;
338 	addrefresh(p, "");
339 }
340 
341 static
342 void
pageload1(Page * p,Url * u,int dohist)343 pageload1(Page *p, Url *u, int dohist)
344 {
345 	pageclose(p);
346 	p->loading = TRUE;
347 	p->url = u;
348 	if(dohist)
349 		winaddhist(p->w, p->url);
350 	proccreate(pageloadproc, p, STACK);
351 }
352 
353 void
pageload(Page * p,Url * u,int dohist)354 pageload(Page *p, Url *u, int dohist)
355 {
356 	if(p->parent == nil)
357 		textset(&p->w->url, u->src.r, u->src.nr);
358 	draw(p->b, p->all, display->white, nil, ZP);
359 	pageload1(p, u, dohist);
360 }
361 
362 void
pageget(Page * p,Runestr * src,Runestr * post,int m,int dohist)363 pageget(Page *p, Runestr *src, Runestr *post,  int m, int dohist)
364 {
365 	pageload(p, urlalloc(src, post, m), dohist);
366 }
367 
368 void
pageclose(Page * p)369 pageclose(Page *p)
370 {
371 	Page *c, *nc;
372 
373 	if(p == selpage)
374 		selpage = nil;
375 	pageabort(p);
376 	closeimages(p);
377 	urlfree(p->url);
378 	p->url = nil;
379 	if(p->doc){
380 		freedocinfo(p->doc);
381 		p->doc = nil;
382 	}
383 	layfree(p->lay);
384 	p->lay = nil;
385 	freeitems(p->items);
386 	p->items = nil;
387 	for(c=p->child; c!=nil; c=nc){
388 		nc = c->next;
389 		pageclose(c);
390 		free(c);
391 	}
392 	p->child = nil;
393 	closerunestr(&p->title);
394 	closerunestr(&p->refresh.rs);
395 	p->refresh.t = 0;
396 	p->pos = ZP;
397 	p->top = ZP;
398 	p->bot = ZP;
399 	p->loading = p->aborting = FALSE;
400 }
401 
402 int
pageabort(Page * p)403 pageabort(Page *p)
404 {
405 	Page *c;
406 
407 	for(c=p->child; c!=nil; c=c->next)
408 		pageabort(c);
409 
410 	p->aborting = TRUE;
411 	while(p->loading)
412 		sleep(100);
413 
414 	p->aborting = FALSE;
415 	return TRUE;
416 }
417 
418 
419 static Image *tmp;
420 
421 void
tmpresize(void)422 tmpresize(void)
423 {
424 	if(tmp)
425 		freeimage(tmp);
426 
427 	tmp = eallocimage(display, Rect(0,0,Dx(screen->r),Dy(screen->r)), screen->chan, 0, -1);
428 }
429 
430 static
431 void
renderchilds(Page * p)432 renderchilds(Page *p)
433 {
434 	Rectangle r;
435 	Kidinfo *k;
436 	Page *c;
437 	int i, j, x, y, *w, *h;
438 
439 	draw(p->b, p->all, display->white, nil, ZP);
440 	r = p->all;
441 	y = r.min.y;
442 	c = p->child;
443 	k = p->kidinfo;
444 	frdims(k->rows, k->nrows, Dy(r), &h);
445 	frdims(k->cols, k->ncols, Dx(r), &w);
446 	for(i=0; i<k->nrows; i++){
447 		x = r.min.x;
448 		for(j=0; j<k->ncols; j++){
449 			if(c->aborting)
450 				return;
451 			c->b = p->b;
452 			c->all = Rect(x,y,x+w[j],y+h[i]);
453 			c->w = p->w;
454 			pagerender(c);
455 			c = c->next;
456 			x += w[j];
457 		}
458 		y += h[i];
459 	}
460 	free(w);
461 	free(h);
462 }
463 
464 static
465 void
pagerender1(Page * p)466 pagerender1(Page *p)
467 {
468 	Rectangle r;
469 
470 	r = p->all;
471 	p->hscrollr = r;
472 	p->hscrollr.min.y = r.max.y-Scrollsize;
473 	p->vscrollr = r;
474 	p->vscrollr.max.x = r.min.x+Scrollsize;
475 	r.max.y -= Scrollsize;
476 	r.min.x += Scrollsize;
477 	p->r = r;
478 	p->vscrollr.max.y = r.max.y;
479 	p->hscrollr.min.x = r.min.x;
480 	laypage(p);
481 	pageredraw(p);
482 }
483 
484 void
pagerender(Page * p)485 pagerender(Page *p)
486 {
487 	if(p->child && p->loading==FALSE)
488 		renderchilds(p);
489 	else if(p->doc)
490 		pagerender1(p);
491 }
492 
493 void
pageredraw(Page * p)494 pageredraw(Page *p)
495 {
496 	Rectangle r;
497 
498 	r = p->lay->r;
499 	if(Dx(r)==0 || Dy(r)==0){
500 		draw(p->b, p->r, display->white, nil, ZP);
501 		return;
502 	}
503 	if(tmp == nil)
504 		tmpresize();
505 
506 	p->selecting = FALSE;
507 	draw(tmp, tmp->r, getbg(p), nil, ZP);
508 	laydraw(p, tmp, p->lay);
509 	draw(p->b, p->r, tmp, nil, tmp->r.min);
510 	r = p->vscrollr;
511 	r.min.y = r.max.y;
512 	r.max.y += Scrollsize;
513 	draw(p->b, r, tagcols[HIGH], nil, ZP);
514 	draw(p->b, insetrect(r, 1), tagcols[BACK], nil, ZP);
515 	pagescrldraw(p);
516 }
517 
518 static
519 void
pageselect1(Page * p)520 pageselect1(Page *p)	/* when called, button 1 is down */
521 {
522 	Point mp, npos, opos;
523 	int b, scrled, x, y;
524 
525 	b = mouse->buttons;
526 	mp = mousectl->xy;
527 	opos = getpt(p, mp);
528 	do{
529 		x = y = 0;
530 		if(mp.x < p->r.min.x)
531 			x -= p->r.min.x-mp.x;
532 		else if(mp.x > p->r.max.x)
533 			x += mp.x-p->r.max.x;
534 		if(mp.y < p->r.min.y)
535 			y -= (p->r.min.y-mp.y)*Panspeed;
536 		else if(mp.y > p->r.max.y)
537 			y += (mp.y-p->r.max.y)*Panspeed;
538 
539 		scrled = pagescrollxy(p, x, y);
540 		npos = getpt(p, mp);
541 		if(opos.y <  npos.y){
542 			p->top = opos;
543 			p->bot = npos;
544 		}else{
545 			p->top = npos;
546 			p->bot = opos;
547 		}
548 		pageredraw(p);
549 		if(scrled == TRUE)
550 			scrsleep(100);
551 		else
552 			readmouse(mousectl);
553 
554 		mp = mousectl->xy;
555 	}while(mousectl->buttons == b);
556 }
557 
558 static Rune left1[] =  { L'{', L'[', L'(', L'<', L'«', 0 };
559 static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 };
560 static Rune left2[] =  { L'\'', L'"', L'`', 0 };
561 
562 static
563 Rune *left[] = {
564 	left1,
565 	left2,
566 	nil
567 };
568 static
569 Rune *right[] = {
570 	right1,
571 	left2,
572 	nil
573 };
574 
575 void
pagedoubleclick(Page * p)576 pagedoubleclick(Page *p)
577 {
578 	Point xy;
579 	Line *l;
580 	Box *b;
581 
582 	xy = getpt(p, mouse->xy);
583 	l = linewhich(p->lay, xy);
584 	if(l==nil || l->hastext==FALSE)
585 		return;
586 
587 	if(xy.x<l->boxes->r.min.x && hasbrk(l->state)){	/* beginning of line? */
588 		p->top = l->boxes->r.min;
589 		if(l->next && !hasbrk(l->next->state)){
590 			for(l=l->next; l->next!=nil; l=l->next)
591 				if(hasbrk(l->next->state))
592 					break;
593 		}
594 		p->bot = l->lastbox->r.max;;
595 	}else if(xy.x>l->lastbox->r.max.x && hasbrk(l->next->state)){	/* end of line? */
596 		p->bot = l->lastbox->r.max;
597 		if(!hasbrk(l->state) && l->prev!=nil){
598 			for(l=l->prev; l->prev!=nil; l=l->prev)
599 				if(hasbrk(l->state))
600 					break;
601 		}
602 		p->top = l->boxes->r.min;
603 	}else{
604 		b = pttobox(l, xy);
605 		if(b!=nil && b->i->tag==Itexttag){
606 			p->top = b->r.min;
607 			p->bot = b->r.max;
608 		}
609 	}
610 	p->top.y += 2;
611 	p->bot.y -= 2;
612 	pageredraw(p);
613 }
614 
615 static uint clickmsec;
616 
617 void
pageselect(Page * p)618 pageselect(Page *p)
619 {
620 	int b, x, y;
621 
622 
623 	selpage = p;
624 	/*
625 	 * To have double-clicking and chording, we double-click
626 	 * immediately if it might make sense.
627 	 */
628 	b = mouse->buttons;
629 	if(mouse->msec-clickmsec<500){
630 		pagedoubleclick(p);
631 		x = mouse->xy.x;
632 		y = mouse->xy.y;
633 		/* stay here until something interesting happens */
634 		do
635 			readmouse(mousectl);
636 		while(mouse->buttons==b && abs(mouse->xy.x-x)<3 && abs(mouse->xy.y-y)<3);
637 		mouse->xy.x = x;	/* in case we're calling pageselect1 */
638 		mouse->xy.y = y;
639 	}
640 	if(mousectl->buttons == b)
641 		pageselect1(p);
642 
643 	if(eqpt(p->top, p->bot)){
644 		if(mouse->msec-clickmsec<500)
645 			pagedoubleclick(p);
646 		else
647 			clickmsec = mouse->msec;
648 	}
649 	while(mouse->buttons){
650 		mouse->msec = 0;
651 		b = mouse->buttons;
652 		if(b & 2)	/* snarf only */
653 			cut(nil, nil, TRUE, FALSE, nil, 0);
654 		while(mouse->buttons == b)
655 			readmouse(mousectl);
656 	}
657 }
658 
659 Page *
pagewhich(Page * p,Point xy)660 pagewhich(Page *p, Point xy)
661 {
662 	Page *c;
663 
664 	if(p->child == nil)
665 		return p;
666 
667 	for(c=p->child; c!=nil; c=c->next)
668 		if(ptinrect(xy, c->all))
669 			return pagewhich(c, xy);
670 
671 	return nil;
672 }
673 
674 void
pagemouse(Page * p,Point xy,int but)675 pagemouse(Page *p, Point xy, int but)
676 {
677 	Box *b;
678 
679 	p = pagewhich(p, xy);
680 	if(p == nil)
681 		return;
682 
683 	if(pagerefresh(p))
684 		return;
685 
686 	if(p->lay == nil)
687 		return;
688 
689 	if(ptinrect(xy, p->vscrollr)){
690 		pagescroll(p, but, FALSE);
691 		return;
692 	}
693 	if(ptinrect(xy, p->hscrollr)){
694 		pagescroll(p, but, TRUE);
695 		return;
696 	}
697 	xy = getpt(p, xy);
698 	b = boxwhich(p->lay, xy);
699 	if(b && b->mouse)
700 		b->mouse(b, p, but);
701 	else if(but == 1)
702 		pageselect(p);
703 }
704 
705 void
pagetype(Page * p,Rune r,Point xy)706 pagetype(Page *p, Rune r, Point xy)
707 {
708 	Box *b;
709 	int x, y;
710 
711 	p = pagewhich(p, xy);
712 	if(p == nil)
713 		return;
714 
715 	if(pagerefresh(p))
716 		return;
717 
718 	if(p->lay == nil)
719 		return;
720 
721 	/* text field? */
722 	xy = getpt(p, xy);
723 	b = boxwhich(p->lay, xy);
724 	if(b && b->key){
725 		b->key(b, p, r);
726 		return;
727 	}
728 	/* ^H: same as 'Back' */
729 	if(r == 0x08){
730 		wingohist(p->w, FALSE);
731 		return;
732 	}
733 
734 	x = 0;
735 	y = 0;
736 	switch(r){
737 	case Kleft:
738 		x -= Dx(p->r)/2;
739 		break;
740 	case Kright:
741 		x += Dx(p->r)/2;
742 		break;
743 	case Kdown:
744 	case Kscrollonedown:
745 		y += Dy(p->r)/2;
746 		break;
747 	case Kpgdown:
748 		y += Dy(p->r);
749 		break;
750 	case Kup:
751 	case Kscrolloneup:
752 		y -= Dy(p->r)/2;
753 		break;
754 	case Kpgup:
755 		y -= Dy(p->r);
756 		break;
757 	case Khome:
758 		y -= Dy(p->lay->r);	/* force p->pos.y = 0 */
759 		break;
760 	case Kend:
761 		y = Dy(p->lay->r) - Dy(p->r);
762 		break;
763 	default:
764 		return;
765 	}
766 	if(pagescrollxy(p, x, y))
767 		pageredraw(p);
768 }
769 
770 void
pagesnarf(Page * p)771 pagesnarf(Page *p)
772 {
773 	Runestr rs;
774 
775 	memset(&rs, 0, sizeof(Runestr));
776 	laysnarf(p, p->lay, &rs);
777 	putsnarf(&rs);
778 	closerunestr(&rs);
779 }
780 
781 void
pagesetrefresh(Page * p)782 pagesetrefresh(Page *p)
783 {
784 	Runestr rs;
785 	Rune *s, *q, *t;
786 	char *v;
787 	int n;
788 
789 	if(!p->doc || !p->doc->refresh)
790 		return;
791 
792 	s = p->doc->refresh;
793 	q = runestrchr(s, L'=');
794 	if(q == nil)
795 		return;
796 	q++;
797 	if(!q)
798 		return;
799 	n = runestrlen(q);
800 	if(*q == L'''){
801 		q++;
802 		n -= 2;
803 	}
804 	if(n <= 0)
805 		return;
806 	t = runesmprint("%.*S", n, q);
807 	rs.r = urlcombine(getbase(p), t);
808 	rs.nr = runestrlen(rs.r);
809 	copyrunestr(&p->refresh.rs, &rs);
810 	closerunestr(&rs);
811 	free(t);
812 
813 	/* now the time */
814 	q = runestrchr(s, L';');
815 	if(q){
816 		v = smprint("%.*S", (int)(q-s),  s);
817 		p->refresh.t = atoi(v);
818 		free(v);
819 	}else
820 		p->refresh.t = 1;
821 
822 	p->refresh.t += time(0);
823 }
824 
825 int
826 pagerefresh(Page *p)
827 {
828 	int t;
829 
830 	if(!p->refresh.t)
831 		return 0;
832 
833 	t = p->refresh.t - time(0);
834 	if(t > 0)
835 		return 0;
836 
837 	pageget(p, &p->refresh.rs, nil, HGet, FALSE);
838 	return 1;
839 }
840