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