xref: /plan9-contrib/sys/src/cmd/page/view.c (revision 7dd7cddf99dd7472612f1413b4da293630e6b1bc)
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 upside = 0;
20 
21 Rectangle ulrange;	/* the upper left corner of the image must be in this rectangle */
22 Point ul;			/* the upper left corner of the image is at this point on the screen */
23 
24 Point pclip(Point, Rectangle);
25 Rectangle mkrange(Rectangle screenr, Rectangle imr);
26 void redraw(Image*);
27 
28 Cursor reading={
29 	{-1, -1},
30 	{0xff, 0x80, 0xff, 0x80, 0xff, 0x00, 0xfe, 0x00,
31 	 0xff, 0x00, 0xff, 0x80, 0xff, 0xc0, 0xef, 0xe0,
32 	 0xc7, 0xf0, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0xc0,
33 	 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, },
34 	{0x00, 0x00, 0x7f, 0x00, 0x7e, 0x00, 0x7c, 0x00,
35 	 0x7e, 0x00, 0x7f, 0x00, 0x6f, 0x80, 0x47, 0xc0,
36 	 0x03, 0xe0, 0x01, 0xf0, 0x00, 0xe0, 0x00, 0x40,
37 	 0x00, 0x00, 0x01, 0xb6, 0x01, 0xb6, 0x00, 0x00, }
38 };
39 
40 enum {
41 	Left = 1,
42 	Middle = 2,
43 	Right = 4,
44 
45 	RMenu = 3,
46 };
47 
48 int
49 max(int a, int b)
50 {
51 	return a > b ? a : b;
52 }
53 
54 int
55 min(int a, int b)
56 {
57 	return a < b ? a : b;
58 }
59 
60 
61 char*
62 menugen(int n)
63 {
64 	static char menustr[32];
65 	char *p;
66 	int len;
67 
68 	if(n == doc->npage)
69 		return "exit";
70 	if(n > doc->npage)
71 		return nil;
72 
73 	if(reverse)
74 		n = doc->npage-1-n;
75 
76 	p = doc->pagename(doc, n);
77 	len = (sizeof menustr)-2;
78 
79 	if(strlen(p) > len && strrchr(p, '/'))
80 		p = strrchr(p, '/')+1;
81 	if(strlen(p) > len)
82 		p = p+strlen(p)-len;
83 
84 	strcpy(menustr+1, p);
85 	if(page == n)
86 		menustr[0] = '>';
87 	else
88 		menustr[0] = ' ';
89 	return menustr;
90 }
91 
92 void
93 showpage(int page, Menu *m)
94 {
95 	if(doc->fwdonly)
96 		m->lasthit = 0;	/* this page */
97 	else
98 		m->lasthit = reverse ? doc->npage-1-page : page;
99 
100 	esetcursor(&reading);
101 	freeimage(im);
102 	if((page < 0 || page >= doc->npage) && !doc->fwdonly){
103 		im = nil;
104 		return;
105 	}
106 	im = doc->drawpage(doc, page);
107 	if(im == nil) {
108 		if(doc->fwdonly)	/* this is how we know we're out of pages */
109 			wexits(0);
110 
111 		im = xallocimage(display, Rect(0,0,50,50), GREY1, 1, DBlack);
112 		if(im == nil) {
113 			fprint(2, "out of memory: %r\n");
114 			wexits("memory");
115 		}
116 		string(im, ZP, display->white, ZP, display->defaultfont, "?");
117 	}else if(resizing){
118 		resize(Dx(im->r), Dy(im->r));
119 	}
120 	if(upside)
121 		rot180(im);
122 
123 	esetcursor(nil);
124 	// ul = ulrange.min;
125 
126 	redraw(screen);
127 	flushimage(display, 1);
128 }
129 
130 char*
131 writebitmap(void)
132 {
133 	char basename[64];
134 	char name[64+30];
135 	static char result[200];
136 	char *p, *q;
137 	int fd;
138 
139 	if(im == nil)
140 		return "no image";
141 
142 	memset(basename, 0, sizeof basename);
143 	if(doc->docname)
144 		strncpy(basename, doc->docname, sizeof(basename)-1);
145 	else if((p = menugen(page)) && p[0] != '\0')
146 		strncpy(basename, p+1, sizeof(basename)-1);
147 
148 	if(basename[0]) {
149 		if(q = strrchr(basename, '/'))
150 			q++;
151 		else
152 			q = basename;
153 		if(p = strchr(q, '.'))
154 			*p = 0;
155 
156 		memset(name, 0, sizeof name);
157 		snprint(name, sizeof(name)-1, "%s.%d.bit", q, page+1);
158 		if(access(name, 0) >= 0) {
159 			strcat(name, "XXXX");
160 			mktemp(name);
161 		}
162 		if(access(name, 0) >= 0)
163 			return "couldn't think of a name for bitmap";
164 	} else {
165 		strcpy(name, "bitXXXX");
166 		mktemp(name);
167 		if(access(name, 0) >= 0)
168 			return "couldn't think of a name for bitmap";
169 	}
170 
171 	if((fd = create(name, OWRITE, 0666)) < 0) {
172 		snprint(result, sizeof result, "cannot create %s: %r", name);
173 		return result;
174 	}
175 
176 	if(writeimage(fd, im, 0) < 0) {
177 		snprint(result, sizeof result, "cannot writeimage: %r");
178 		close(fd);
179 		return result;
180 	}
181 	close(fd);
182 
183 	snprint(result, sizeof result, "wrote %s", name);
184 	return result;
185 }
186 
187 static void translate(Point);
188 
189 static int
190 showdata(Plumbmsg *msg)
191 {
192 	char *s;
193 
194 	s = plumblookup(msg->attr, "action");
195 	return s && strcmp(s, "showdata")==0;
196 }
197 
198 void
199 viewer(Document *dd)
200 {
201 	int i, fd, n, oldpage;
202 	int nxt;
203 	Menu menu;
204 	Mouse m;
205 	Event e;
206 	Point dxy, oxy, xy0;
207 	Rectangle r;
208 	char *fwditems[] = { "this page", "next page", "exit", 0 };
209 	char *s;
210 	enum { Eplumb = 4 };
211 	Plumbmsg *pm;
212 
213 	doc = dd;    /* save global for menuhit */
214 	ul = screen->r.min;
215 	einit(Emouse|Ekeyboard);
216 	if(doc->addpage != nil)
217 		eplumb(Eplumb, "image");
218 
219 	esetcursor(&reading);
220 	r.min = ZP;
221 
222 	/*
223 	 * im is a global pointer to the current image.
224 	 * eventually, i think we will have a layer between
225 	 * the display routines and the ps/pdf/whatever routines
226 	 * to perhaps cache and handle images of different
227 	 * sizes, etc.
228 	 */
229 	im = 0;
230 	page = reverse ? doc->npage-1 : 0;
231 
232 	if(doc->fwdonly) {
233 		menu.item = fwditems;
234 		menu.gen = 0;
235 		menu.lasthit = 0;
236 	} else {
237 		menu.item = 0;
238 		menu.gen = menugen;
239 		menu.lasthit = 0;
240 	}
241 
242 	showpage(page, &menu);
243 	esetcursor(nil);
244 
245 	nxt = 0;
246 	for(;;) {
247 		/*
248 		 * throughout, if doc->fwdonly is set, we restrict the functionality
249 		 * a fair amount.  we don't care about doc->npage anymore, and
250 		 * all that can be done is select the next page.
251 		 */
252 		switch(eread(Emouse|Ekeyboard|Eplumb, &e)){
253 		case Ekeyboard:
254 			if(e.kbdc <= 0xFF && isdigit(e.kbdc)) {
255 				nxt = nxt*10+e.kbdc-'0';
256 				break;
257 			} else if(e.kbdc != '\n')
258 				nxt = 0;
259 			switch(e.kbdc) {
260 			case 'r':	/* reverse page order */
261 				if(doc->fwdonly)
262 					break;
263 				reverse = !reverse;
264 				menu.lasthit = doc->npage-1-menu.lasthit;
265 
266 				/*
267 				 * the theory is that if we are reversing the
268 				 * document order and are on the first or last
269 				 * page then we're just starting and really want
270 		 	 	 * to view the other end.  maybe the if
271 				 * should be dropped and this should happen always.
272 				 */
273 				if(page == 0 || page == doc->npage-1) {
274 					page = doc->npage-1-page;
275 					showpage(page, &menu);
276 				}
277 				break;
278 			case 'w':	/* write bitmap of current screen */
279 				esetcursor(&reading);
280 				s = writebitmap();
281 				if(s)
282 					string(screen, addpt(screen->r.min, Pt(5,5)), display->black, ZP,
283 						display->defaultfont, s);
284 				esetcursor(nil);
285 				flushimage(display, 1);
286 				break;
287 			case 'd':	/* remove image from working set */
288 				if(doc->rmpage && page < doc->npage) {
289 					if(doc->rmpage(doc, page) >= 0) {
290 						if(doc->npage < 0)
291 							wexits(0);
292 						if(page >= doc->npage)
293 							page = doc->npage-1;
294 						showpage(page, &menu);
295 					}
296 				}
297 				break;
298 			case 'q':
299 			case 0x04: /* ctrl-d */
300 				wexits(0);
301 			case 'u':
302 				if(im==nil)
303 					break;
304 				esetcursor(&reading);
305 				rot180(im);
306 				esetcursor(nil);
307 				upside = !upside;
308 				redraw(screen);
309 				flushimage(display, 1);
310 				break;
311 			case '-':
312 			case '\b':
313 			case Kup:	/* up arrow */
314 				if(page > 0 && !doc->fwdonly) {
315 					--page;
316 					showpage(page, &menu);
317 				}
318 				break;
319 			case '\n':
320 				if(nxt) {
321 					nxt--;
322 					if(nxt >= 0 && nxt < doc->npage && !doc->fwdonly)
323 						showpage(page=nxt, &menu);
324 					nxt = 0;
325 					break;
326 				}
327 				/* fall through */
328 			default:
329 				if(doc->npage && ++page >= doc->npage && !doc->fwdonly)
330 					wexits(0);
331 				showpage(page, &menu);
332 				break;
333 			}
334 			break;
335 
336 		case Emouse:
337 			m = e.mouse;
338 			switch(m.buttons){
339 			case Left:
340 				oxy = m.xy;
341 				xy0 = oxy;
342 				do {
343 					dxy = subpt(m.xy, oxy);
344 					oxy = m.xy;
345 					translate(dxy);
346 					m = emouse();
347 				} while(m.buttons == Left);
348 				if(m.buttons) {
349 					dxy = subpt(xy0, oxy);
350 					translate(dxy);
351 				}
352 				break;
353 
354 			case Middle:
355 				do
356 					m = emouse();
357 				while(m.buttons == Middle);
358 				if(m.buttons)
359 					break;
360 
361 				if(doc->npage == 0)
362 					break;
363 
364 				if(reverse)
365 					page--;
366 				else
367 					page++;
368 
369 				if((page >= doc->npage || page < 0) && !doc->fwdonly)
370 					return;
371 
372 				showpage(page, &menu);
373 				nxt = 0;
374 				break;
375 
376 			case Right:
377 				if(doc->npage == 0)
378 					break;
379 
380 				oldpage = page;
381 				n = emenuhit(RMenu, &m, &menu);
382 				if(n == -1)
383 					break;
384 
385 				if(doc->fwdonly) {
386 					switch(n){
387 					case 0:	/* this page */
388 						break;
389 					case 1:	/* next page */
390 						showpage(++page, &menu);
391 						break;
392 					case 2:	/* exit */
393 						return;
394 					}
395 					break;
396 				}
397 
398 				if(n == doc->npage)
399 					return;
400 				else
401 					page = reverse ? doc->npage-1-n : n;
402 
403 				if(oldpage != page)
404 					showpage(page, &menu);
405 				nxt = 0;
406 				break;
407 			}
408 			break;
409 
410 		case Eplumb:
411 			pm = e.v;
412 			if(pm->ndata <= 0){
413 				plumbfree(pm);
414 				break;
415 			}
416 			if(showdata(pm)) {
417 				s = estrdup("/tmp/pageplumbXXXXXXX");
418 				fd = opentemp(s);
419 				write(fd, pm->data, pm->ndata);
420 				/* lose fd reference on purpose; the file is open ORCLOSE */
421 			} else if(pm->data[0] == '/') {
422 				s = estrdup(pm->data);
423 			} else {
424 				s = emalloc(strlen(pm->wdir)+1+pm->ndata+1);
425 				sprint(s, "%s/%s", pm->wdir, pm->data);
426 				cleanname(s);
427 			}
428 			if((i = doc->addpage(doc, s)) >= 0) {
429 				page = i;
430 				showpage(page, &menu);
431 			}
432 			free(s);
433 			plumbfree(pm);
434 			break;
435 		}
436 	}
437 }
438 
439 Image *gray;
440 
441 /*
442  * A draw operation that touches only the area contained in bot but not in top.
443  * mp and sp get aligned with bot.min.
444  */
445 static void
446 gendrawdiff(Image *dst, Rectangle bot, Rectangle top,
447 	Image *src, Point sp, Image *mask, Point mp)
448 {
449 	Rectangle r;
450 	Point origin;
451 	Point delta;
452 
453 	if(Dx(bot)*Dy(bot) == 0)
454 		return;
455 
456 	/* no points in bot - top */
457 	if(rectinrect(bot, top))
458 		return;
459 
460 	/* bot - top ≡ bot */
461 	if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){
462 		gendraw(dst, bot, src, sp, mask, mp);
463 		return;
464 	}
465 
466 	origin = bot.min;
467 	/* split bot into rectangles that don't intersect top */
468 	/* left side */
469 	if(bot.min.x < top.min.x){
470 		r = Rect(bot.min.x, bot.min.y, top.min.x, bot.max.y);
471 		delta = subpt(r.min, origin);
472 		gendraw(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta));
473 		bot.min.x = top.min.x;
474 	}
475 
476 	/* right side */
477 	if(bot.max.x > top.max.x){
478 		r = Rect(top.max.x, bot.min.y, bot.max.x, bot.max.y);
479 		delta = subpt(r.min, origin);
480 		gendraw(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta));
481 		bot.max.x = top.max.x;
482 	}
483 
484 	/* top */
485 	if(bot.min.y < top.min.y){
486 		r = Rect(bot.min.x, bot.min.y, bot.max.x, top.min.y);
487 		delta = subpt(r.min, origin);
488 		gendraw(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta));
489 		bot.min.y = top.min.y;
490 	}
491 
492 	/* bottom */
493 	if(bot.max.y > top.max.y){
494 		r = Rect(bot.min.x, top.max.y, bot.max.x, bot.max.y);
495 		delta = subpt(r.min, origin);
496 		gendraw(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta));
497 		bot.max.y = top.max.y;
498 	}
499 }
500 
501 static void
502 drawdiff(Image *dst, Rectangle bot, Rectangle top, Image *src, Image *mask, Point p)
503 {
504 	gendrawdiff(dst, bot, top, src, p, mask, p);
505 }
506 
507 /*
508  * Translate the image in the window by delta.
509  */
510 static void
511 translate(Point delta)
512 {
513 	Point u;
514 	Rectangle r, or;
515 
516 	if(im == nil)
517 		return;
518 
519 	u = pclip(addpt(ul, delta), ulrange);
520 	delta = subpt(u, ul);
521 	if(delta.x == 0 && delta.y == 0)
522 		return;
523 
524 	/*
525 	 * The upper left corner of the image is currently at ul.
526 	 * We want to move it to u.
527 	 */
528 	or = rectaddpt(Rpt(ZP, Pt(Dx(im->r), Dy(im->r))), ul);
529 	r = rectaddpt(or, delta);
530 
531 	draw(screen, r, screen, nil, ul);
532 	ul = u;
533 
534 	/* fill in gray where image used to be but isn't. */
535 	drawdiff(screen, insetrect(or, -2), insetrect(r, -2), gray, nil, ZP);
536 
537 	/* fill in black border */
538 	drawdiff(screen, insetrect(r, -2), r, display->black, nil, ZP);
539 
540 	/* fill in image where it used to be off the screen. */
541 	if(rectclip(&or, screen->r))
542 		drawdiff(screen, r, rectaddpt(or, delta), im, nil, im->r.min);
543 	else
544 		draw(screen, r, im, nil, im->r.min);
545 	flushimage(display, 1);
546 }
547 
548 void
549 redraw(Image *screen)
550 {
551 	Rectangle r;
552 
553 	if(im == nil)
554 		return;
555 
556 	ulrange.max = screen->r.max;
557 	ulrange.min = subpt(screen->r.min, Pt(Dx(im->r), Dy(im->r)));
558 
559 	ul = pclip(ul, ulrange);
560 	draw(screen, screen->r, im, nil, subpt(im->r.min, subpt(ul, screen->r.min)));
561 
562 	if(im->repl)
563 		return;
564 
565 	/* fill in any outer edges */
566 	/* black border */
567 	r = rectaddpt(im->r, subpt(ul, im->r.min));
568 	border(screen, r, -2, display->black, ZP);
569 	r.min = subpt(r.min, Pt(2,2));
570 	r.max = addpt(r.max, Pt(2,2));
571 
572 	/* gray for the rest */
573 	if(gray == nil) {
574 		gray = xallocimage(display, Rect(0,0,1,1), RGB24, 1, 0x888888FF);
575 		if(gray == nil) {
576 			fprint(2, "g out of memory: %r\n");
577 			wexits("mem");
578 		}
579 	}
580 	border(screen, r, -4000, gray, ZP);
581 //	flushimage(display, 0);
582 }
583 
584 void
585 eresized(int new)
586 {
587 	Rectangle r;
588 	r = screen->r;
589 	if(new && getwindow(display, Refnone) < 0)
590 		fprint(2,"can't reattach to window");
591 	ul = addpt(ul, subpt(screen->r.min, r.min));
592 	redraw(screen);
593 }
594 
595 /* clip p to be in r */
596 Point
597 pclip(Point p, Rectangle r)
598 {
599 	if(p.x < r.min.x)
600 		p.x = r.min.x;
601 	else if(p.x >= r.max.x)
602 		p.x = r.max.x-1;
603 
604 	if(p.y < r.min.y)
605 		p.y = r.min.y;
606 	else if(p.y >= r.max.y)
607 		p.y = r.max.y-1;
608 
609 	return p;
610 }
611 
612 /*
613  * resize is perhaps a misnomer.
614  * this really just grows the window to be at least dx across
615  * and dy high.  if the window hits the bottom or right edge,
616  * it is backed up until it hits the top or left edge.
617  */
618 void
619 resize(int dx, int dy)
620 {
621 	static Rectangle sr;
622 	Rectangle r, or;
623 
624 	dx += 2*Borderwidth;
625 	dy += 2*Borderwidth;
626 	if(wctlfd < 0){
627 		wctlfd = open("/dev/wctl", OWRITE);
628 		if(wctlfd < 0)
629 			return;
630 	}
631 
632 	r = insetrect(screen->r, -Borderwidth);
633 	if(Dx(r) >= dx && Dy(r) >= dy)
634 		return;
635 
636 	if(Dx(sr)*Dy(sr) == 0)
637 		sr = screenrect();
638 
639 	or = r;
640 
641 	r.max.x = max(r.min.x+dx, r.max.x);
642 	r.max.y = max(r.min.y+dy, r.max.y);
643 	if(r.max.x > sr.max.x){
644 		if(Dx(r) > Dx(sr)){
645 			r.min.x = 0;
646 			r.max.x = sr.max.x;
647 		}else
648 			r = rectaddpt(r, Pt(sr.max.x-r.max.x, 0));
649 	}
650 	if(r.max.y > sr.max.y){
651 		if(Dy(r) > Dy(sr)){
652 			r.min.y = 0;
653 			r.max.y = sr.max.y;
654 		}else
655 			r = rectaddpt(r, Pt(0, sr.max.y-r.max.y));
656 	}
657 
658 	/*
659 	 * Sometimes we can't actually grow the window big enough,
660 	 * and resizing it to the same shape makes it flash.
661 	 */
662 	if(Dx(r) == Dx(or) && Dy(r) == Dy(or))
663 		return;
664 
665 	fprint(wctlfd, "resize -minx %d -miny %d -maxx %d -maxy %d\n",
666 		r.min.x, r.min.y, r.max.x, r.max.y);
667 }
668 
669 /*
670  * If we allocimage after a resize but before flushing the draw buffer,
671  * we won't have seen the reshape event, and we won't have called
672  * getwindow, and allocimage will fail.  So we flushimage before every alloc.
673  */
674 Image*
675 xallocimage(Display *d, Rectangle r, ulong chan, int repl, ulong val)
676 {
677 	flushimage(display, 0);
678 	return allocimage(d, r, chan, repl, val);
679 }
680 
681 /* all code below this line should be in the library, but is stolen from colors instead */
682 static char*
683 rdenv(char *name)
684 {
685 	char *v;
686 	int fd, size;
687 
688 	fd = open(name, OREAD);
689 	if(fd < 0)
690 		return 0;
691 	size = seek(fd, 0, 2);
692 	v = malloc(size+1);
693 	if(v == 0){
694 		fprint(2, "page: can't malloc: %r\n");
695 		wexits("no mem");
696 	}
697 	seek(fd, 0, 0);
698 	read(fd, v, size);
699 	v[size] = 0;
700 	close(fd);
701 	return v;
702 }
703 
704 void
705 newwin(void)
706 {
707 	char *srv, *mntsrv;
708 	char spec[100];
709 	int srvfd, cons, pid;
710 
711 	switch(rfork(RFFDG|RFPROC|RFNAMEG|RFENVG|RFNOTEG|RFNOWAIT)){
712 	case -1:
713 		fprint(2, "page: can't fork: %r\n");
714 		wexits("no fork");
715 	case 0:
716 		break;
717 	default:
718 		wexits(0);
719 	}
720 
721 	srv = rdenv("/env/wsys");
722 	if(srv == 0){
723 		mntsrv = rdenv("/mnt/term/env/wsys");
724 		if(mntsrv == 0){
725 			fprint(2, "page: can't find $wsys\n");
726 			wexits("srv");
727 		}
728 		srv = malloc(strlen(mntsrv)+10);
729 		sprint(srv, "/mnt/term%s", mntsrv);
730 		free(mntsrv);
731 		pid  = 0;			/* can't send notes to remote processes! */
732 	}else
733 		pid = getpid();
734 	srvfd = open(srv, ORDWR);
735 	free(srv);
736 	if(srvfd == -1){
737 		fprint(2, "page: can't open %s: %r\n", srv);
738 		wexits("no srv");
739 	}
740 	sprint(spec, "new -pid %d", pid);
741 	if(mount(srvfd, "/mnt/wsys", 0, spec) == -1){
742 		fprint(2, "page: can't mount /mnt/wsys: %r (spec=%s)\n", spec);
743 		wexits("no mount");
744 	}
745 	close(srvfd);
746 	unmount("/mnt/acme", "/dev");
747 	bind("/mnt/wsys", "/dev", MBEFORE);
748 	cons = open("/dev/cons", OREAD);
749 	if(cons==-1){
750 	NoCons:
751 		fprint(2, "page: can't open /dev/cons: %r");
752 		wexits("no cons");
753 	}
754 	dup(cons, 0);
755 	close(cons);
756 	cons = open("/dev/cons", OWRITE);
757 	if(cons==-1)
758 		goto NoCons;
759 	dup(cons, 1);
760 	dup(cons, 2);
761 	close(cons);
762 //	wctlfd = open("/dev/wctl", OWRITE);
763 }
764 
765 Rectangle
766 screenrect(void)
767 {
768 	int fd;
769 	char buf[12*5];
770 
771 	fd = open("/dev/screen", OREAD);
772 	if(fd == -1)
773 		fd=open("/mnt/term/dev/screen", OREAD);
774 	if(fd == -1){
775 		fprint(2, "page: can't open /dev/screen: %r\n");
776 		wexits("window read");
777 	}
778 	if(read(fd, buf, sizeof buf) != sizeof buf){
779 		fprint(2, "page: can't read /dev/screen: %r\n");
780 		wexits("screen read");
781 	}
782 	close(fd);
783 	return Rect(atoi(buf+12), atoi(buf+24), atoi(buf+36), atoi(buf+48));
784 }
785 
786