xref: /plan9/sys/src/cmd/page/view.c (revision f9e1cf08d3be51592e03e639fc848a68dc31a55e)
1 /*
2  * the actual viewer that handles screen stuff
3  */
4 
5 #include <u.h>
6 #include <libc.h>
7 #include <draw.h>
8 #include <cursor.h>
9 #include <event.h>
10 #include <bio.h>
11 #include <plumb.h>
12 #include <ctype.h>
13 #include <keyboard.h>
14 #include "page.h"
15 
16 Document *doc;
17 Image *im;
18 Image *tofree;
19 int page;
20 int angle = 0;
21 int showbottom = 0;		/* on the next showpage, move the image so the bottom is visible. */
22 
23 Rectangle ulrange;	/* the upper left corner of the image must be in this rectangle */
24 Point ul;			/* the upper left corner of the image is at this point on the screen */
25 
26 Point pclip(Point, Rectangle);
27 Rectangle mkrange(Rectangle screenr, Rectangle imr);
28 void redraw(Image*);
29 
30 Cursor reading={
31 	{-1, -1},
32 	{0xff, 0x80, 0xff, 0x80, 0xff, 0x00, 0xfe, 0x00,
33 	 0xff, 0x00, 0xff, 0x80, 0xff, 0xc0, 0xef, 0xe0,
34 	 0xc7, 0xf0, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0xc0,
35 	 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, },
36 	{0x00, 0x00, 0x7f, 0x00, 0x7e, 0x00, 0x7c, 0x00,
37 	 0x7e, 0x00, 0x7f, 0x00, 0x6f, 0x80, 0x47, 0xc0,
38 	 0x03, 0xe0, 0x01, 0xf0, 0x00, 0xe0, 0x00, 0x40,
39 	 0x00, 0x00, 0x01, 0xb6, 0x01, 0xb6, 0x00, 0x00, }
40 };
41 
42 Cursor query = {
43 	{-7,-7},
44 	{0x0f, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe,
45 	 0x7c, 0x7e, 0x78, 0x7e, 0x00, 0xfc, 0x01, 0xf8,
46 	 0x03, 0xf0, 0x07, 0xe0, 0x07, 0xc0, 0x07, 0xc0,
47 	 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, },
48 	{0x00, 0x00, 0x0f, 0xf0, 0x1f, 0xf8, 0x3c, 0x3c,
49 	 0x38, 0x1c, 0x00, 0x3c, 0x00, 0x78, 0x00, 0xf0,
50 	 0x01, 0xe0, 0x03, 0xc0, 0x03, 0x80, 0x03, 0x80,
51 	 0x00, 0x00, 0x03, 0x80, 0x03, 0x80, 0x00, 0x00, }
52 };
53 
54 enum {
55 	Left = 1,
56 	Middle = 2,
57 	Right = 4,
58 
59 	RMenu = 3,
60 };
61 
62 static void
63 delayfreeimage(Image *m)
64 {
65 	if(m == tofree)
66 		return;
67 	if(tofree)
68 		freeimage(tofree);
69 	tofree = m;
70 }
71 
72 void
73 unhide(void)
74 {
75 	static int wctl = -1;
76 
77 	if(wctl < 0)
78 		wctl = open("/dev/wctl", OWRITE);
79 	if(wctl < 0)
80 		return;
81 
82 	write(wctl, "unhide", 6);
83 }
84 
85 int
86 max(int a, int b)
87 {
88 	return a > b ? a : b;
89 }
90 
91 int
92 min(int a, int b)
93 {
94 	return a < b ? a : b;
95 }
96 
97 
98 char*
99 menugen(int n)
100 {
101 	static char menustr[32];
102 	char *p;
103 	int len;
104 
105 	if(n == doc->npage)
106 		return "exit";
107 	if(n > doc->npage)
108 		return nil;
109 
110 	if(reverse)
111 		n = doc->npage-1-n;
112 
113 	p = doc->pagename(doc, n);
114 	len = (sizeof menustr)-2;
115 
116 	if(strlen(p) > len && strrchr(p, '/'))
117 		p = strrchr(p, '/')+1;
118 	if(strlen(p) > len)
119 		p = p+strlen(p)-len;
120 
121 	strcpy(menustr+1, p);
122 	if(page == n)
123 		menustr[0] = '>';
124 	else
125 		menustr[0] = ' ';
126 	return menustr;
127 }
128 
129 void
130 showpage(int page, Menu *m)
131 {
132 	if(doc->fwdonly)
133 		m->lasthit = 0;	/* this page */
134 	else
135 		m->lasthit = reverse ? doc->npage-1-page : page;
136 
137 	esetcursor(&reading);
138 	delayfreeimage(nil);
139 	im = cachedpage(doc, angle, page);
140 	if(im == nil)
141 		wexits(0);
142 	if(resizing)
143 		resize(Dx(im->r), Dy(im->r));
144 
145 	esetcursor(nil);
146 	if(showbottom){
147 		ul.y = screen->r.max.y - Dy(im->r);
148 		showbottom = 0;
149 	}
150 
151 	redraw(screen);
152 	flushimage(display, 1);
153 }
154 
155 char*
156 writebitmap(void)
157 {
158 	char basename[64];
159 	char name[64+30];
160 	static char result[200];
161 	char *p, *q;
162 	int fd;
163 
164 	if(im == nil)
165 		return "no image";
166 
167 	memset(basename, 0, sizeof basename);
168 	if(doc->docname)
169 		strncpy(basename, doc->docname, sizeof(basename)-1);
170 	else if((p = menugen(page)) && p[0] != '\0')
171 		strncpy(basename, p+1, sizeof(basename)-1);
172 
173 	if(basename[0]) {
174 		if(q = strrchr(basename, '/'))
175 			q++;
176 		else
177 			q = basename;
178 		if(p = strchr(q, '.'))
179 			*p = 0;
180 
181 		memset(name, 0, sizeof name);
182 		snprint(name, sizeof(name)-1, "%s.%d.bit", q, page+1);
183 		if(access(name, 0) >= 0) {
184 			strcat(name, "XXXX");
185 			mktemp(name);
186 		}
187 		if(access(name, 0) >= 0)
188 			return "couldn't think of a name for bitmap";
189 	} else {
190 		strcpy(name, "bitXXXX");
191 		mktemp(name);
192 		if(access(name, 0) >= 0)
193 			return "couldn't think of a name for bitmap";
194 	}
195 
196 	if((fd = create(name, OWRITE, 0666)) < 0) {
197 		snprint(result, sizeof result, "cannot create %s: %r", name);
198 		return result;
199 	}
200 
201 	if(writeimage(fd, im, 0) < 0) {
202 		snprint(result, sizeof result, "cannot writeimage: %r");
203 		close(fd);
204 		return result;
205 	}
206 	close(fd);
207 
208 	snprint(result, sizeof result, "wrote %s", name);
209 	return result;
210 }
211 
212 static void translate(Point);
213 
214 static int
215 showdata(Plumbmsg *msg)
216 {
217 	char *s;
218 
219 	s = plumblookup(msg->attr, "action");
220 	return s && strcmp(s, "showdata")==0;
221 }
222 
223 /* correspond to entries in miditems[] below,
224  * changing one means you need to change
225  */
226 enum{
227 	Restore = 0,
228 	Zin,
229 	Fit,
230 	Rot,
231 	Upside,
232 	Empty1,
233 	Next,
234 	Prev,
235 	Zerox,
236 	Empty2,
237 	Reverse,
238 	Del,
239 	Write,
240 	Empty3,
241 	Exit,
242 };
243 
244 void
245 viewer(Document *dd)
246 {
247 	int i, fd, n, oldpage;
248 	int nxt;
249 	Menu menu, midmenu;
250 	Mouse m;
251 	Event e;
252 	Point dxy, oxy, xy0;
253 	Rectangle r;
254 	Image *tmp;
255 	static char *fwditems[] = { "this page", "next page", "exit", 0 };
256  	static char *miditems[] = {
257  		"orig size",
258  		"zoom in",
259  		"fit window",
260  		"rotate 90",
261  		"upside down",
262  		"",
263  		"next",
264  		"prev",
265 		"zerox",
266  		"",
267  		"reverse",
268  		"discard",
269  		"write",
270  		"",
271  		"quit",
272  		0
273  	};
274 	char *s;
275 	enum { Eplumb = 4 };
276 	Plumbmsg *pm;
277 
278 	doc = dd;    /* save global for menuhit */
279 	ul = screen->r.min;
280 	einit(Emouse|Ekeyboard);
281 	if(doc->addpage != nil)
282 		eplumb(Eplumb, "image");
283 
284 	esetcursor(&reading);
285 	r.min = ZP;
286 
287 	/*
288 	 * im is a global pointer to the current image.
289 	 * eventually, i think we will have a layer between
290 	 * the display routines and the ps/pdf/whatever routines
291 	 * to perhaps cache and handle images of different
292 	 * sizes, etc.
293 	 */
294 	im = 0;
295 	page = reverse ? doc->npage-1 : 0;
296 
297 	if(doc->fwdonly) {
298 		menu.item = fwditems;
299 		menu.gen = 0;
300 		menu.lasthit = 0;
301 	} else {
302 		menu.item = 0;
303 		menu.gen = menugen;
304 		menu.lasthit = 0;
305 	}
306 
307 	midmenu.item = miditems;
308 	midmenu.gen = 0;
309 	midmenu.lasthit = Next;
310 
311 	showpage(page, &menu);
312 	esetcursor(nil);
313 
314 	nxt = 0;
315 	for(;;) {
316 		/*
317 		 * throughout, if doc->fwdonly is set, we restrict the functionality
318 		 * a fair amount.  we don't care about doc->npage anymore, and
319 		 * all that can be done is select the next page.
320 		 */
321 		unlockdisplay(display);
322 		i = eread(Emouse|Ekeyboard|Eplumb, &e);
323 		lockdisplay(display);
324 		switch(i){
325 		case Ekeyboard:
326 			if(e.kbdc <= 0xFF && isdigit(e.kbdc)) {
327 				nxt = nxt*10+e.kbdc-'0';
328 				break;
329 			} else if(e.kbdc != '\n')
330 				nxt = 0;
331 			switch(e.kbdc) {
332 			case 'r':	/* reverse page order */
333 				if(doc->fwdonly)
334 					break;
335 				reverse = !reverse;
336 				menu.lasthit = doc->npage-1-menu.lasthit;
337 
338 				/*
339 				 * the theory is that if we are reversing the
340 				 * document order and are on the first or last
341 				 * page then we're just starting and really want
342 		 	 	 * to view the other end.  maybe the if
343 				 * should be dropped and this should happen always.
344 				 */
345 				if(page == 0 || page == doc->npage-1) {
346 					page = doc->npage-1-page;
347 					showpage(page, &menu);
348 				}
349 				break;
350 			case 'w':	/* write bitmap of current screen */
351 				esetcursor(&reading);
352 				s = writebitmap();
353 				if(s)
354 					string(screen, addpt(screen->r.min, Pt(5,5)), display->black, ZP,
355 						display->defaultfont, s);
356 				esetcursor(nil);
357 				flushimage(display, 1);
358 				break;
359 			case 'd':	/* remove image from working set */
360 				if(doc->rmpage && page < doc->npage) {
361 					if(doc->rmpage(doc, page) >= 0) {
362 						if(doc->npage < 0)
363 							wexits(0);
364 						if(page >= doc->npage)
365 							page = doc->npage-1;
366 						showpage(page, &menu);
367 					}
368 				}
369 				break;
370 			case 'q':
371 			case 0x04: /* ctrl-d */
372 				wexits(0);
373 			case 'u':
374 				if(im==nil)
375 					break;
376 				angle = (angle+180) % 360;
377 				showpage(page, &menu);
378 				break;
379 			case '-':
380 			case '\b':
381 			case Kleft:
382 				if(page > 0 && !doc->fwdonly) {
383 					--page;
384 					showpage(page, &menu);
385 				}
386 				break;
387 			case '\n':
388 				if(nxt) {
389 					nxt--;
390 					if(nxt >= 0 && nxt < doc->npage && !doc->fwdonly)
391 						showpage(page=nxt, &menu);
392 					nxt = 0;
393 					break;
394 				}
395 				goto Gotonext;
396 			case Kright:
397 			case ' ':
398 			Gotonext:
399 				if(doc->npage && ++page >= doc->npage && !doc->fwdonly)
400 					wexits(0);
401 				showpage(page, &menu);
402 				break;
403 
404 			/*
405 			 * The upper y coordinate of the image is at ul.y in screen->r.
406 			 * Panning up means moving the upper left corner down.  If the
407 			 * upper left corner is currently visible, we need to go back a page.
408 			 */
409 			case Kup:
410 				if(screen->r.min.y <= ul.y && ul.y < screen->r.max.y){
411 					if(page > 0 && !doc->fwdonly){
412 						--page;
413 						showbottom = 1;
414 						showpage(page, &menu);
415 					}
416 				} else {
417 					i = Dy(screen->r)/2;
418 					if(i > 10)
419 						i -= 10;
420 					if(i+ul.y > screen->r.min.y)
421 						i = screen->r.min.y - ul.y;
422 					translate(Pt(0, i));
423 				}
424 				break;
425 
426 			/*
427 			 * If the lower y coordinate is on the screen, we go to the next page.
428 			 * The lower y coordinate is at ul.y + Dy(im->r).
429 			 */
430 			case Kdown:
431 				i = ul.y + Dy(im->r);
432 				if(screen->r.min.y <= i && i <= screen->r.max.y){
433 					ul.y = screen->r.min.y;
434 					goto Gotonext;
435 				} else {
436 					i = -Dy(screen->r)/2;
437 					if(i < -10)
438 						i += 10;
439 					if(i+ul.y+Dy(im->r) <= screen->r.max.y)
440 						i = screen->r.max.y - Dy(im->r) - ul.y - 1;
441 					translate(Pt(0, i));
442 				}
443 				break;
444 			default:
445 				esetcursor(&query);
446 				sleep(1000);
447 				esetcursor(nil);
448 				break;
449 			}
450 			break;
451 
452 		case Emouse:
453 			m = e.mouse;
454 			switch(m.buttons){
455 			case Left:
456 				oxy = m.xy;
457 				xy0 = oxy;
458 				do {
459 					dxy = subpt(m.xy, oxy);
460 					oxy = m.xy;
461 					translate(dxy);
462 					unlockdisplay(display);
463 					m = emouse();
464 					lockdisplay(display);
465 				} while(m.buttons == Left);
466 				if(m.buttons) {
467 					dxy = subpt(xy0, oxy);
468 					translate(dxy);
469 				}
470 				break;
471 
472 			case Middle:
473 				if(doc->npage == 0)
474 					break;
475 
476 				unlockdisplay(display);
477 				n = emenuhit(Middle, &m, &midmenu);
478 				lockdisplay(display);
479 				if(n == -1)
480 					break;
481 				switch(n){
482 				case Next: 	/* next */
483 					if(reverse)
484 						page--;
485 					else
486 						page++;
487 					if(page < 0) {
488 						if(reverse) return;
489 						else page = 0;
490 					}
491 
492 					if((page >= doc->npage) && !doc->fwdonly)
493 						return;
494 
495 					showpage(page, &menu);
496 					nxt = 0;
497 					break;
498 				case Prev:	/* prev */
499 					if(reverse)
500 						page++;
501 					else
502 						page--;
503 					if(page < 0) {
504 						if(reverse) return;
505 						else page = 0;
506 					}
507 
508 					if((page >= doc->npage) && !doc->fwdonly && !reverse)
509 						return;
510 
511 					showpage(page, &menu);
512 					nxt = 0;
513 					break;
514 				case Zerox:	/* prev */
515 					zerox();
516 					break;
517 				case Zin:	/* zoom in */
518 					{
519 						double delta;
520 						Rectangle r;
521 
522 						r = egetrect(Middle, &m);
523 						if((rectclip(&r, rectaddpt(im->r, ul)) == 0) ||
524 							Dx(r) == 0 || Dy(r) == 0)
525 							break;
526 						/* use the smaller side to expand */
527 						if(Dx(r) < Dy(r))
528 							delta = (double)Dx(im->r)/(double)Dx(r);
529 						else
530 							delta = (double)Dy(im->r)/(double)Dy(r);
531 
532 						esetcursor(&reading);
533 						tmp = xallocimage(display,
534 								Rect(0, 0, (int)((double)Dx(im->r)*delta), (int)((double)Dy(im->r)*delta)),
535 								im->chan, 0, DBlack);
536 						if(tmp == nil) {
537 							fprint(2, "out of memory during zoom: %r\n");
538 							wexits("memory");
539 						}
540 						resample(im, tmp);
541 						im = tmp;
542 						delayfreeimage(tmp);
543 						esetcursor(nil);
544 						ul = screen->r.min;
545 						redraw(screen);
546 						flushimage(display, 1);
547 						break;
548 					}
549 				case Fit:	/* fit */
550 					{
551 						double delta;
552 						Rectangle r;
553 
554 						delta = (double)Dx(screen->r)/(double)Dx(im->r);
555 						if((double)Dy(im->r)*delta > Dy(screen->r))
556 							delta = (double)Dy(screen->r)/(double)Dy(im->r);
557 
558 						r = Rect(0, 0, (int)((double)Dx(im->r)*delta), (int)((double)Dy(im->r)*delta));
559 						esetcursor(&reading);
560 						tmp = xallocimage(display, r, im->chan, 0, DBlack);
561 						if(tmp == nil) {
562 							fprint(2, "out of memory during fit: %r\n");
563 							wexits("memory");
564 						}
565 						resample(im, tmp);
566 						im = tmp;
567 						delayfreeimage(tmp);
568 						esetcursor(nil);
569 						ul = screen->r.min;
570 						redraw(screen);
571 						flushimage(display, 1);
572 						break;
573 					}
574 				case Rot:	/* rotate 90 */
575 					angle = (angle+90) % 360;
576 					showpage(page, &menu);
577 					break;
578 				case Upside: 	/* upside-down */
579 					angle = (angle+180) % 360;
580 					showpage(page, &menu);
581 					break;
582 				case Restore:	/* restore */
583 					showpage(page, &menu);
584 					break;
585 				case Reverse:	/* reverse */
586 					if(doc->fwdonly)
587 						break;
588 					reverse = !reverse;
589 					menu.lasthit = doc->npage-1-menu.lasthit;
590 
591 					if(page == 0 || page == doc->npage-1) {
592 						page = doc->npage-1-page;
593 						showpage(page, &menu);
594 					}
595 					break;
596 				case Write: /* write */
597 					esetcursor(&reading);
598 					s = writebitmap();
599 					if(s)
600 						string(screen, addpt(screen->r.min, Pt(5,5)), display->black, ZP,
601 							display->defaultfont, s);
602 					esetcursor(nil);
603 					flushimage(display, 1);
604 					break;
605 				case Del: /* delete */
606 					if(doc->rmpage && page < doc->npage) {
607 						if(doc->rmpage(doc, page) >= 0) {
608 							if(doc->npage < 0)
609 								wexits(0);
610 							if(page >= doc->npage)
611 								page = doc->npage-1;
612 							showpage(page, &menu);
613 						}
614 					}
615 					break;
616 				case Exit:	/* exit */
617 					return;
618 				case Empty1:
619 				case Empty2:
620 				case Empty3:
621 					break;
622 
623 				};
624 
625 
626 
627 			case Right:
628 				if(doc->npage == 0)
629 					break;
630 
631 				oldpage = page;
632 				unlockdisplay(display);
633 				n = emenuhit(RMenu, &m, &menu);
634 				lockdisplay(display);
635 				if(n == -1)
636 					break;
637 
638 				if(doc->fwdonly) {
639 					switch(n){
640 					case 0:	/* this page */
641 						break;
642 					case 1:	/* next page */
643 						showpage(++page, &menu);
644 						break;
645 					case 2:	/* exit */
646 						return;
647 					}
648 					break;
649 				}
650 
651 				if(n == doc->npage)
652 					return;
653 				else
654 					page = reverse ? doc->npage-1-n : n;
655 
656 				if(oldpage != page)
657 					showpage(page, &menu);
658 				nxt = 0;
659 				break;
660 			}
661 			break;
662 
663 		case Eplumb:
664 			pm = e.v;
665 			if(pm->ndata <= 0){
666 				plumbfree(pm);
667 				break;
668 			}
669 			if(showdata(pm)) {
670 				s = estrdup("/tmp/pageplumbXXXXXXX");
671 				fd = opentemp(s);
672 				write(fd, pm->data, pm->ndata);
673 				/* lose fd reference on purpose; the file is open ORCLOSE */
674 			} else if(pm->data[0] == '/') {
675 				s = estrdup(pm->data);
676 			} else {
677 				s = emalloc(strlen(pm->wdir)+1+pm->ndata+1);
678 				sprint(s, "%s/%s", pm->wdir, pm->data);
679 				cleanname(s);
680 			}
681 			if((i = doc->addpage(doc, s)) >= 0) {
682 				page = i;
683 				unhide();
684 				showpage(page, &menu);
685 			}
686 			free(s);
687 			plumbfree(pm);
688 			break;
689 		}
690 	}
691 }
692 
693 Image *gray;
694 
695 /*
696  * A draw operation that touches only the area contained in bot but not in top.
697  * mp and sp get aligned with bot.min.
698  */
699 static void
700 gendrawdiff(Image *dst, Rectangle bot, Rectangle top,
701 	Image *src, Point sp, Image *mask, Point mp, int op)
702 {
703 	Rectangle r;
704 	Point origin;
705 	Point delta;
706 
707 	USED(op);
708 
709 	if(Dx(bot)*Dy(bot) == 0)
710 		return;
711 
712 	/* no points in bot - top */
713 	if(rectinrect(bot, top))
714 		return;
715 
716 	/* bot - top ≡ bot */
717 	if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){
718 		gendrawop(dst, bot, src, sp, mask, mp, op);
719 		return;
720 	}
721 
722 	origin = bot.min;
723 	/* split bot into rectangles that don't intersect top */
724 	/* left side */
725 	if(bot.min.x < top.min.x){
726 		r = Rect(bot.min.x, bot.min.y, top.min.x, bot.max.y);
727 		delta = subpt(r.min, origin);
728 		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
729 		bot.min.x = top.min.x;
730 	}
731 
732 	/* right side */
733 	if(bot.max.x > top.max.x){
734 		r = Rect(top.max.x, bot.min.y, bot.max.x, bot.max.y);
735 		delta = subpt(r.min, origin);
736 		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
737 		bot.max.x = top.max.x;
738 	}
739 
740 	/* top */
741 	if(bot.min.y < top.min.y){
742 		r = Rect(bot.min.x, bot.min.y, bot.max.x, top.min.y);
743 		delta = subpt(r.min, origin);
744 		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
745 		bot.min.y = top.min.y;
746 	}
747 
748 	/* bottom */
749 	if(bot.max.y > top.max.y){
750 		r = Rect(bot.min.x, top.max.y, bot.max.x, bot.max.y);
751 		delta = subpt(r.min, origin);
752 		gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
753 		bot.max.y = top.max.y;
754 	}
755 }
756 
757 static void
758 drawdiff(Image *dst, Rectangle bot, Rectangle top, Image *src, Image *mask, Point p, int op)
759 {
760 	gendrawdiff(dst, bot, top, src, p, mask, p, op);
761 }
762 
763 /*
764  * Translate the image in the window by delta.
765  */
766 static void
767 translate(Point delta)
768 {
769 	Point u;
770 	Rectangle r, or;
771 
772 	if(im == nil)
773 		return;
774 
775 	u = pclip(addpt(ul, delta), ulrange);
776 	delta = subpt(u, ul);
777 	if(delta.x == 0 && delta.y == 0)
778 		return;
779 
780 	/*
781 	 * The upper left corner of the image is currently at ul.
782 	 * We want to move it to u.
783 	 */
784 	or = rectaddpt(Rpt(ZP, Pt(Dx(im->r), Dy(im->r))), ul);
785 	r = rectaddpt(or, delta);
786 
787 	drawop(screen, r, screen, nil, ul, S);
788 	ul = u;
789 
790 	/* fill in gray where image used to be but isn't. */
791 	drawdiff(screen, insetrect(or, -2), insetrect(r, -2), gray, nil, ZP, S);
792 
793 	/* fill in black border */
794 	drawdiff(screen, insetrect(r, -2), r, display->black, nil, ZP, S);
795 
796 	/* fill in image where it used to be off the screen. */
797 	if(rectclip(&or, screen->r))
798 		drawdiff(screen, r, rectaddpt(or, delta), im, nil, im->r.min, S);
799 	else
800 		drawop(screen, r, im, nil, im->r.min, S);
801 	flushimage(display, 1);
802 }
803 
804 void
805 redraw(Image *screen)
806 {
807 	Rectangle r;
808 
809 	if(im == nil)
810 		return;
811 
812 	ulrange.max = screen->r.max;
813 	ulrange.min = subpt(screen->r.min, Pt(Dx(im->r), Dy(im->r)));
814 
815 	ul = pclip(ul, ulrange);
816 	drawop(screen, screen->r, im, nil, subpt(im->r.min, subpt(ul, screen->r.min)), S);
817 
818 	if(im->repl)
819 		return;
820 
821 	/* fill in any outer edges */
822 	/* black border */
823 	r = rectaddpt(im->r, subpt(ul, im->r.min));
824 	border(screen, r, -2, display->black, ZP);
825 	r.min = subpt(r.min, Pt(2,2));
826 	r.max = addpt(r.max, Pt(2,2));
827 
828 	/* gray for the rest */
829 	if(gray == nil) {
830 		gray = xallocimage(display, Rect(0,0,1,1), RGB24, 1, 0x888888FF);
831 		if(gray == nil) {
832 			fprint(2, "g out of memory: %r\n");
833 			wexits("mem");
834 		}
835 	}
836 	border(screen, r, -4000, gray, ZP);
837 //	flushimage(display, 0);
838 }
839 
840 void
841 eresized(int new)
842 {
843 	Rectangle r;
844 	r = screen->r;
845 	if(new && getwindow(display, Refnone) < 0)
846 		fprint(2,"can't reattach to window");
847 	ul = addpt(ul, subpt(screen->r.min, r.min));
848 	redraw(screen);
849 }
850 
851 /* clip p to be in r */
852 Point
853 pclip(Point p, Rectangle r)
854 {
855 	if(p.x < r.min.x)
856 		p.x = r.min.x;
857 	else if(p.x >= r.max.x)
858 		p.x = r.max.x-1;
859 
860 	if(p.y < r.min.y)
861 		p.y = r.min.y;
862 	else if(p.y >= r.max.y)
863 		p.y = r.max.y-1;
864 
865 	return p;
866 }
867 
868 /*
869  * resize is perhaps a misnomer.
870  * this really just grows the window to be at least dx across
871  * and dy high.  if the window hits the bottom or right edge,
872  * it is backed up until it hits the top or left edge.
873  */
874 void
875 resize(int dx, int dy)
876 {
877 	static Rectangle sr;
878 	Rectangle r, or;
879 
880 	dx += 2*Borderwidth;
881 	dy += 2*Borderwidth;
882 	if(wctlfd < 0){
883 		wctlfd = open("/dev/wctl", OWRITE);
884 		if(wctlfd < 0)
885 			return;
886 	}
887 
888 	r = insetrect(screen->r, -Borderwidth);
889 	if(Dx(r) >= dx && Dy(r) >= dy)
890 		return;
891 
892 	if(Dx(sr)*Dy(sr) == 0)
893 		sr = screenrect();
894 
895 	or = r;
896 
897 	r.max.x = max(r.min.x+dx, r.max.x);
898 	r.max.y = max(r.min.y+dy, r.max.y);
899 	if(r.max.x > sr.max.x){
900 		if(Dx(r) > Dx(sr)){
901 			r.min.x = 0;
902 			r.max.x = sr.max.x;
903 		}else
904 			r = rectaddpt(r, Pt(sr.max.x-r.max.x, 0));
905 	}
906 	if(r.max.y > sr.max.y){
907 		if(Dy(r) > Dy(sr)){
908 			r.min.y = 0;
909 			r.max.y = sr.max.y;
910 		}else
911 			r = rectaddpt(r, Pt(0, sr.max.y-r.max.y));
912 	}
913 
914 	/*
915 	 * Sometimes we can't actually grow the window big enough,
916 	 * and resizing it to the same shape makes it flash.
917 	 */
918 	if(Dx(r) == Dx(or) && Dy(r) == Dy(or))
919 		return;
920 
921 	fprint(wctlfd, "resize -minx %d -miny %d -maxx %d -maxy %d\n",
922 		r.min.x, r.min.y, r.max.x, r.max.y);
923 }
924 
925 /*
926  * If we allocimage after a resize but before flushing the draw buffer,
927  * we won't have seen the reshape event, and we won't have called
928  * getwindow, and allocimage will fail.  So we flushimage before every alloc.
929  */
930 Image*
931 xallocimage(Display *d, Rectangle r, ulong chan, int repl, ulong val)
932 {
933 	flushimage(display, 0);
934 	return allocimage(d, r, chan, repl, val);
935 }
936 
937 /* all code below this line should be in the library, but is stolen from colors instead */
938 static char*
939 rdenv(char *name)
940 {
941 	char *v;
942 	int fd, size;
943 
944 	fd = open(name, OREAD);
945 	if(fd < 0)
946 		return 0;
947 	size = seek(fd, 0, 2);
948 	v = malloc(size+1);
949 	if(v == 0){
950 		fprint(2, "page: can't malloc: %r\n");
951 		wexits("no mem");
952 	}
953 	seek(fd, 0, 0);
954 	read(fd, v, size);
955 	v[size] = 0;
956 	close(fd);
957 	return v;
958 }
959 
960 void
961 newwin(void)
962 {
963 	char *srv, *mntsrv;
964 	char spec[100];
965 	int srvfd, cons, pid;
966 
967 	switch(rfork(RFFDG|RFPROC|RFNAMEG|RFENVG|RFNOTEG|RFNOWAIT)){
968 	case -1:
969 		fprint(2, "page: can't fork: %r\n");
970 		wexits("no fork");
971 	case 0:
972 		break;
973 	default:
974 		wexits(0);
975 	}
976 
977 	srv = rdenv("/env/wsys");
978 	if(srv == 0){
979 		mntsrv = rdenv("/mnt/term/env/wsys");
980 		if(mntsrv == 0){
981 			fprint(2, "page: can't find $wsys\n");
982 			wexits("srv");
983 		}
984 		srv = malloc(strlen(mntsrv)+10);
985 		sprint(srv, "/mnt/term%s", mntsrv);
986 		free(mntsrv);
987 		pid  = 0;			/* can't send notes to remote processes! */
988 	}else
989 		pid = getpid();
990 	srvfd = open(srv, ORDWR);
991 	free(srv);
992 	if(srvfd == -1){
993 		fprint(2, "page: can't open %s: %r\n", srv);
994 		wexits("no srv");
995 	}
996 	sprint(spec, "new -pid %d", pid);
997 	if(mount(srvfd, -1, "/mnt/wsys", 0, spec) == -1){
998 		fprint(2, "page: can't mount /mnt/wsys: %r (spec=%s)\n", spec);
999 		wexits("no mount");
1000 	}
1001 	close(srvfd);
1002 	unmount("/mnt/acme", "/dev");
1003 	bind("/mnt/wsys", "/dev", MBEFORE);
1004 	cons = open("/dev/cons", OREAD);
1005 	if(cons==-1){
1006 	NoCons:
1007 		fprint(2, "page: can't open /dev/cons: %r");
1008 		wexits("no cons");
1009 	}
1010 	dup(cons, 0);
1011 	close(cons);
1012 	cons = open("/dev/cons", OWRITE);
1013 	if(cons==-1)
1014 		goto NoCons;
1015 	dup(cons, 1);
1016 	dup(cons, 2);
1017 	close(cons);
1018 //	wctlfd = open("/dev/wctl", OWRITE);
1019 }
1020 
1021 Rectangle
1022 screenrect(void)
1023 {
1024 	int fd;
1025 	char buf[12*5];
1026 
1027 	fd = open("/dev/screen", OREAD);
1028 	if(fd == -1)
1029 		fd=open("/mnt/term/dev/screen", OREAD);
1030 	if(fd == -1){
1031 		fprint(2, "page: can't open /dev/screen: %r\n");
1032 		wexits("window read");
1033 	}
1034 	if(read(fd, buf, sizeof buf) != sizeof buf){
1035 		fprint(2, "page: can't read /dev/screen: %r\n");
1036 		wexits("screen read");
1037 	}
1038 	close(fd);
1039 	return Rect(atoi(buf+12), atoi(buf+24), atoi(buf+36), atoi(buf+48));
1040 }
1041 
1042 void
1043 zerox(void)
1044 {
1045 	int pfd[2];
1046 
1047 	pipe(pfd);
1048 	switch(rfork(RFFDG|RFREND|RFPROC)) {
1049 		case -1:
1050 			wexits("cannot fork in zerox: %r");
1051 		case 0:
1052 			dup(pfd[1], 0);
1053 			close(pfd[0]);
1054 			execl("/bin/page", "page", "-w", nil);
1055 			wexits("cannot exec in zerox: %r\n");
1056 		default:
1057 			close(pfd[1]);
1058 			writeimage(pfd[0], im, 0);
1059 			close(pfd[0]);
1060 			break;
1061 	}
1062 }
1063