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