xref: /plan9/sys/src/cmd/rio/xfid.c (revision 2906cf0c8250511ba5ce896f8e5505ea9c265e35)
1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 #include <thread.h>
5 #include <cursor.h>
6 #include <mouse.h>
7 #include <keyboard.h>
8 #include <frame.h>
9 #include <fcall.h>
10 #include <plumb.h>
11 #include "dat.h"
12 #include "fns.h"
13 
14 #define	MAXSNARF	100*1024
15 
16 char Einuse[] =		"file in use";
17 char Edeleted[] =	"window deleted";
18 char Ebadreq[] =	"bad graphics request";
19 char Etooshort[] =	"buffer too small";
20 char Ebadtile[] =	"unknown tile";
21 char Eshort[] =		"short i/o request";
22 char Elong[] = 		"snarf buffer too long";
23 char Eunkid[] = 	"unknown id in attach";
24 char Ebadrect[] = 	"bad rectangle in attach";
25 char Ewindow[] = 	"cannot make window";
26 char Enowindow[] = 	"window has no image";
27 char Ebadmouse[] = 	"bad format on /dev/mouse";
28 char Ebadwrect[] = 	"rectangle outside screen";
29 char Ebadoffset[] = 	"window read not on scan line boundary";
30 extern char Eperm[];
31 
32 static	Xfid	*xfidfree;
33 static	Xfid	*xfid;
34 static	Channel	*cxfidalloc;	/* chan(Xfid*) */
35 static	Channel	*cxfidfree;	/* chan(Xfid*) */
36 
37 static	char	*tsnarf;
38 static	int	ntsnarf;
39 
40 void
xfidallocthread(void *)41 xfidallocthread(void*)
42 {
43 	Xfid *x;
44 	enum { Alloc, Free, N };
45 	static Alt alts[N+1];
46 
47 	alts[Alloc].c = cxfidalloc;
48 	alts[Alloc].v = nil;
49 	alts[Alloc].op = CHANRCV;
50 	alts[Free].c = cxfidfree;
51 	alts[Free].v = &x;
52 	alts[Free].op = CHANRCV;
53 	alts[N].op = CHANEND;
54 	for(;;){
55 		switch(alt(alts)){
56 		case Alloc:
57 			x = xfidfree;
58 			if(x)
59 				xfidfree = x->free;
60 			else{
61 				x = emalloc(sizeof(Xfid));
62 				x->c = chancreate(sizeof(void(*)(Xfid*)), 0);
63 				x->flushc = chancreate(sizeof(int), 0);	/* notification only; no data */
64 				x->flushtag = -1;
65 				x->next = xfid;
66 				xfid = x;
67 				threadcreate(xfidctl, x, 16384);
68 			}
69 			if(x->ref != 0){
70 				fprint(2, "%p incref %ld\n", x, x->ref);
71 				error("incref");
72 			}
73 			if(x->flushtag != -1)
74 				error("flushtag in allocate");
75 			incref(x);
76 			sendp(cxfidalloc, x);
77 			break;
78 		case Free:
79 			if(x->ref != 0){
80 				fprint(2, "%p decref %ld\n", x, x->ref);
81 				error("decref");
82 			}
83 			if(x->flushtag != -1)
84 				error("flushtag in free");
85 			x->free = xfidfree;
86 			xfidfree = x;
87 			break;
88 		}
89 	}
90 }
91 
92 Channel*
xfidinit(void)93 xfidinit(void)
94 {
95 	cxfidalloc = chancreate(sizeof(Xfid*), 0);
96 	cxfidfree = chancreate(sizeof(Xfid*), 0);
97 	threadcreate(xfidallocthread, nil, STACK);
98 	return cxfidalloc;
99 }
100 
101 void
xfidctl(void * arg)102 xfidctl(void *arg)
103 {
104 	Xfid *x;
105 	void (*f)(Xfid*);
106 	char buf[64];
107 
108 	x = arg;
109 	snprint(buf, sizeof buf, "xfid.%p", x);
110 	threadsetname(buf);
111 	for(;;){
112 		f = recvp(x->c);
113 		(*f)(x);
114 		if(decref(x) == 0)
115 			sendp(cxfidfree, x);
116 	}
117 }
118 
119 void
xfidflush(Xfid * x)120 xfidflush(Xfid *x)
121 {
122 	Fcall t;
123 	Xfid *xf;
124 
125 	for(xf=xfid; xf; xf=xf->next)
126 		if(xf->flushtag == x->oldtag){
127 			xf->flushtag = -1;
128 			xf->flushing = TRUE;
129 			incref(xf);	/* to hold data structures up at tail of synchronization */
130 			if(xf->ref == 1)
131 				error("ref 1 in flush");
132 			if(canqlock(&xf->active)){
133 				qunlock(&xf->active);
134 				sendul(xf->flushc, 0);
135 			}else{
136 				qlock(&xf->active);	/* wait for him to finish */
137 				qunlock(&xf->active);
138 			}
139 			xf->flushing = FALSE;
140 			if(decref(xf) == 0)
141 				sendp(cxfidfree, xf);
142 			break;
143 		}
144 	filsysrespond(x->fs, x, &t, nil);
145 }
146 
147 void
xfidattach(Xfid * x)148 xfidattach(Xfid *x)
149 {
150 	Fcall t;
151 	int id, hideit, scrollit;
152 	Window *w;
153 	char *err, *n, *dir, errbuf[ERRMAX];
154 	int pid, newlymade;
155 	Rectangle r;
156 	Image *i;
157 
158 	t.qid = x->f->qid;
159 	qlock(&all);
160 	w = nil;
161 	err = Eunkid;
162 	newlymade = FALSE;
163 	hideit = 0;
164 
165 	if(x->aname[0] == 'N'){	/* N 100,100, 200, 200 - old syntax */
166 		n = x->aname+1;
167 		pid = strtoul(n, &n, 0);
168 		if(*n == ',')
169 			n++;
170 		r.min.x = strtoul(n, &n, 0);
171 		if(*n == ',')
172 			n++;
173 		r.min.y = strtoul(n, &n, 0);
174 		if(*n == ',')
175 			n++;
176 		r.max.x = strtoul(n, &n, 0);
177 		if(*n == ',')
178 			n++;
179 		r.max.y = strtoul(n, &n, 0);
180   Allocate:
181 		if(!goodrect(r))
182 			err = Ebadrect;
183 		else{
184 			if(hideit)
185 				i = allocimage(display, r, screen->chan, 0, DWhite);
186 			else
187 				i = allocwindow(wscreen, r, Refbackup, DWhite);
188 			if(i){
189 				border(i, r, Selborder, display->black, ZP);
190 				if(pid == 0)
191 					pid = -1;	/* make sure we don't pop a shell! - UGH */
192 				w = new(i, hideit, scrolling, pid, nil, nil, nil);
193 				flushimage(display, 1);
194 				newlymade = TRUE;
195 			}else
196 				err = Ewindow;
197 		}
198 	}else if(strncmp(x->aname, "new", 3) == 0){	/* new -dx -dy - new syntax, as in wctl */
199 		pid = 0;
200 		if(parsewctl(nil, ZR, &r, &pid, nil, &hideit, &scrollit, &dir, x->aname, errbuf) < 0)
201 			err = errbuf;
202 		else
203 			goto Allocate;
204 	}else{
205 		id = atoi(x->aname);
206 		w = wlookid(id);
207 	}
208 	x->f->w = w;
209 	if(w == nil){
210 		qunlock(&all);
211 		x->f->busy = FALSE;
212 		filsysrespond(x->fs, x, &t, err);
213 		return;
214 	}
215 	if(!newlymade)	/* counteract dec() in winshell() */
216 		incref(w);
217 	qunlock(&all);
218 	filsysrespond(x->fs, x, &t, nil);
219 }
220 
221 void
xfidopen(Xfid * x)222 xfidopen(Xfid *x)
223 {
224 	Fcall t;
225 	Window *w;
226 
227 	w = x->f->w;
228 	if(w->deleted){
229 		filsysrespond(x->fs, x, &t, Edeleted);
230 		return;
231 	}
232 	switch(FILE(x->f->qid)){
233 	case Qconsctl:
234 		if(w->ctlopen){
235 			filsysrespond(x->fs, x, &t, Einuse);
236 			return;
237 		}
238 		w->ctlopen = TRUE;
239 		break;
240 	case Qkbdin:
241 		if(w !=  wkeyboard){
242 			filsysrespond(x->fs, x, &t, Eperm);
243 			return;
244 		}
245 		break;
246 	case Qmouse:
247 		if(w->mouseopen){
248 			filsysrespond(x->fs, x, &t, Einuse);
249 			return;
250 		}
251 		/*
252 		 * Reshaped: there's a race if the appl. opens the
253 		 * window, is resized, and then opens the mouse,
254 		 * but that's rare.  The alternative is to generate
255 		 * a resized event every time a new program starts
256 		 * up in a window that has been resized since the
257 		 * dawn of time.  We choose the lesser evil.
258 		 */
259 		w->resized = FALSE;
260 		w->mouseopen = TRUE;
261 		break;
262 	case Qsnarf:
263 		if(x->mode==ORDWR || x->mode==OWRITE){
264 			if(tsnarf)
265 				free(tsnarf);	/* collision, but OK */
266 			ntsnarf = 0;
267 			tsnarf = malloc(1);
268 		}
269 		break;
270 	case Qwctl:
271 		if(x->mode==OREAD || x->mode==ORDWR){
272 			/*
273 			 * It would be much nicer to implement fan-out for wctl reads,
274 			 * so multiple people can see the resizings, but rio just isn't
275 			 * structured for that.  It's structured for /dev/cons, which gives
276 			 * alternate data to alternate readers.  So to keep things sane for
277 			 * wctl, we compromise and give an error if two people try to
278 			 * open it.  Apologies.
279 			 */
280 			if(w->wctlopen){
281 				filsysrespond(x->fs, x, &t, Einuse);
282 				return;
283 			}
284 			w->wctlopen = TRUE;
285 			w->wctlready = 1;
286 			wsendctlmesg(w, Wakeup, ZR, nil);
287 		}
288 		break;
289 	}
290 	t.qid = x->f->qid;
291 	t.iounit = messagesize-IOHDRSZ;
292 	x->f->open = TRUE;
293 	x->f->mode = x->mode;
294 	filsysrespond(x->fs, x, &t, nil);
295 }
296 
297 void
xfidclose(Xfid * x)298 xfidclose(Xfid *x)
299 {
300 	Fcall t;
301 	Window *w;
302 	int nb, nulls;
303 
304 	w = x->f->w;
305 	switch(FILE(x->f->qid)){
306 	case Qconsctl:
307 		if(w->rawing){
308 			w->rawing = FALSE;
309 			wsendctlmesg(w, Rawoff, ZR, nil);
310 		}
311 		if(w->holding){
312 			w->holding = FALSE;
313 			wsendctlmesg(w, Holdoff, ZR, nil);
314 		}
315 		w->ctlopen = FALSE;
316 		break;
317 	case Qcursor:
318 		w->cursorp = nil;
319 		wsetcursor(w, FALSE);
320 		break;
321 	case Qmouse:
322 		w->resized = FALSE;
323 		w->mouseopen = FALSE;
324 		if(w->i != nil)
325 			wsendctlmesg(w, Refresh, w->i->r, nil);
326 		break;
327 	/* odd behavior but really ok: replace snarf buffer when /dev/snarf is closed */
328 	case Qsnarf:
329 		if(x->f->mode==ORDWR || x->f->mode==OWRITE){
330 			snarf = runerealloc(snarf, ntsnarf+1);
331 			cvttorunes(tsnarf, ntsnarf, snarf, &nb, &nsnarf, &nulls);
332 			free(tsnarf);
333 			tsnarf = nil;
334 			ntsnarf = 0;
335 		}
336 		break;
337 	case Qwctl:
338 		if(x->f->mode==OREAD || x->f->mode==ORDWR)
339 			w->wctlopen = FALSE;
340 		break;
341 	}
342 	wclose(w);
343 	filsysrespond(x->fs, x, &t, nil);
344 }
345 
346 void
xfidwrite(Xfid * x)347 xfidwrite(Xfid *x)
348 {
349 	Fcall fc;
350 	int c, cnt, qid, nb, off, nr;
351 	char buf[256], *p;
352 	Point pt;
353 	Window *w;
354 	Rune *r;
355 	Conswritemesg cwm;
356 	Stringpair pair;
357 	enum { CWdata, CWflush, NCW };
358 	Alt alts[NCW+1];
359 
360 	w = x->f->w;
361 	if(w->deleted){
362 		filsysrespond(x->fs, x, &fc, Edeleted);
363 		return;
364 	}
365 	qid = FILE(x->f->qid);
366 	cnt = x->count;
367 	off = x->offset;
368 	x->data[cnt] = 0;
369 	switch(qid){
370 	case Qcons:
371 		nr = x->f->nrpart;
372 		if(nr > 0){
373 			memmove(x->data+nr, x->data, cnt);	/* there's room: see malloc in filsysproc */
374 			memmove(x->data, x->f->rpart, nr);
375 			cnt += nr;
376 			x->f->nrpart = 0;
377 		}
378 		r = runemalloc(cnt);
379 		cvttorunes(x->data, cnt-UTFmax, r, &nb, &nr, nil);
380 		/* approach end of buffer */
381 		while(fullrune(x->data+nb, cnt-nb)){
382 			c = nb;
383 			nb += chartorune(&r[nr], x->data+c);
384 			if(r[nr])
385 				nr++;
386 		}
387 		if(nb < cnt){
388 			memmove(x->f->rpart, x->data+nb, cnt-nb);
389 			x->f->nrpart = cnt-nb;
390 		}
391 		x->flushtag = x->tag;
392 
393 		alts[CWdata].c = w->conswrite;
394 		alts[CWdata].v = &cwm;
395 		alts[CWdata].op = CHANRCV;
396 		alts[CWflush].c = x->flushc;
397 		alts[CWflush].v = nil;
398 		alts[CWflush].op = CHANRCV;
399 		alts[NCW].op = CHANEND;
400 
401 		switch(alt(alts)){
402 		case CWdata:
403 			break;
404 		case CWflush:
405 			filsyscancel(x);
406 			return;
407 		}
408 
409 		/* received data */
410 		x->flushtag = -1;
411 		if(x->flushing){
412 			recv(x->flushc, nil);	/* wake up flushing xfid */
413 			pair.s = runemalloc(1);
414 			pair.ns = 0;
415 			send(cwm.cw, &pair);		/* wake up window with empty data */
416 			filsyscancel(x);
417 			return;
418 		}
419 		qlock(&x->active);
420 		pair.s = r;
421 		pair.ns = nr;
422 		send(cwm.cw, &pair);
423 		fc.count = x->count;
424 		filsysrespond(x->fs, x, &fc, nil);
425 		qunlock(&x->active);
426 		return;
427 
428 	case Qconsctl:
429 		if(strncmp(x->data, "holdon", 6)==0){
430 			if(w->holding++ == 0)
431 				wsendctlmesg(w, Holdon, ZR, nil);
432 			break;
433 		}
434 		if(strncmp(x->data, "holdoff", 7)==0 && w->holding){
435 			if(--w->holding == FALSE)
436 				wsendctlmesg(w, Holdoff, ZR, nil);
437 			break;
438 		}
439 		if(strncmp(x->data, "rawon", 5)==0){
440 			if(w->holding){
441 				w->holding = FALSE;
442 				wsendctlmesg(w, Holdoff, ZR, nil);
443 			}
444 			if(w->rawing++ == 0)
445 				wsendctlmesg(w, Rawon, ZR, nil);
446 			break;
447 		}
448 		if(strncmp(x->data, "rawoff", 6)==0 && w->rawing){
449 			if(--w->rawing == 0)
450 				wsendctlmesg(w, Rawoff, ZR, nil);
451 			break;
452 		}
453 		filsysrespond(x->fs, x, &fc, "unknown control message");
454 		return;
455 
456 	case Qcursor:
457 		if(cnt < 2*4+2*2*16)
458 			w->cursorp = nil;
459 		else{
460 			w->cursor.offset.x = BGLONG(x->data+0*4);
461 			w->cursor.offset.y = BGLONG(x->data+1*4);
462 			memmove(w->cursor.clr, x->data+2*4, 2*2*16);
463 			w->cursorp = &w->cursor;
464 		}
465 		wsetcursor(w, !sweeping);
466 		break;
467 
468 	case Qlabel:
469 		if(off != 0){
470 			filsysrespond(x->fs, x, &fc, "non-zero offset writing label");
471 			return;
472 		}
473 		free(w->label);
474 		w->label = emalloc(cnt+1);
475 		memmove(w->label, x->data, cnt);
476 		w->label[cnt] = 0;
477 		break;
478 
479 	case Qmouse:
480 		if(w!=input || Dx(w->screenr)<=0)
481 			break;
482 		if(x->data[0] != 'm'){
483 			filsysrespond(x->fs, x, &fc, Ebadmouse);
484 			return;
485 		}
486 		p = nil;
487 		pt.x = strtoul(x->data+1, &p, 0);
488 		if(p == nil){
489 			filsysrespond(x->fs, x, &fc, Eshort);
490 			return;
491 		}
492 		pt.y = strtoul(p, nil, 0);
493 		if(w==input && wpointto(mouse->xy)==w)
494 			wsendctlmesg(w, Movemouse, Rpt(pt, pt), nil);
495 		break;
496 
497 	case Qsnarf:
498 		/* always append only */
499 		if(ntsnarf > MAXSNARF){	/* avoid thrashing when people cut huge text */
500 			filsysrespond(x->fs, x, &fc, Elong);
501 			return;
502 		}
503 		tsnarf = erealloc(tsnarf, ntsnarf+cnt+1);	/* room for NUL */
504 		memmove(tsnarf+ntsnarf, x->data, cnt);
505 		ntsnarf += cnt;
506 		snarfversion++;
507 		break;
508 
509 	case Qwdir:
510 		if(cnt == 0)
511 			break;
512 		if(x->data[cnt-1] == '\n'){
513 			if(cnt == 1)
514 				break;
515 			x->data[cnt-1] = '\0';
516 		}
517 		/* assume data comes in a single write */
518 		/*
519 		  * Problem: programs like dossrv, ftp produce illegal UTF;
520 		  * we must cope by converting it first.
521 		  */
522 		snprint(buf, sizeof buf, "%.*s", cnt, x->data);
523 		if(buf[0] == '/'){
524 			free(w->dir);
525 			w->dir = estrdup(buf);
526 		}else{
527 			p = emalloc(strlen(w->dir) + 1 + strlen(buf) + 1);
528 			sprint(p, "%s/%s", w->dir, buf);
529 			free(w->dir);
530 			w->dir = cleanname(p);
531 		}
532 		break;
533 
534 	case Qkbdin:
535 		keyboardsend(x->data, cnt);
536 		break;
537 
538 	case Qwctl:
539 		if(writewctl(x, buf) < 0){
540 			filsysrespond(x->fs, x, &fc, buf);
541 			return;
542 		}
543 		flushimage(display, 1);
544 		break;
545 
546 	default:
547 		fprint(2, buf, "unknown qid %d in write\n", qid);
548 		sprint(buf, "unknown qid in write");
549 		filsysrespond(x->fs, x, &fc, buf);
550 		return;
551 	}
552 	fc.count = cnt;
553 	filsysrespond(x->fs, x, &fc, nil);
554 }
555 
556 int
readwindow(Image * i,char * t,Rectangle r,int offset,int n)557 readwindow(Image *i, char *t, Rectangle r, int offset, int n)
558 {
559 	int ww, y;
560 
561 	offset -= 5*12;
562 	ww = bytesperline(r, screen->depth);
563 	r.min.y += offset/ww;
564 	if(r.min.y >= r.max.y)
565 		return 0;
566 	y = r.min.y + n/ww;
567 	if(y < r.max.y)
568 		r.max.y = y;
569 	if(r.max.y <= r.min.y)
570 		return 0;
571 	return unloadimage(i, r, (uchar*)t, n);
572 }
573 
574 void
xfidread(Xfid * x)575 xfidread(Xfid *x)
576 {
577 	Fcall fc;
578 	int n, off, cnt, c;
579 	uint qid;
580 	char buf[128], *t;
581 	char cbuf[30];
582 	Window *w;
583 	Mouse ms;
584 	Rectangle r;
585 	Image *i;
586 	Channel *c1, *c2;	/* chan (tuple(char*, int)) */
587 	Consreadmesg crm;
588 	Mousereadmesg mrm;
589 	Consreadmesg cwrm;
590 	Stringpair pair;
591 	enum { CRdata, CRflush, NCR };
592 	enum { MRdata, MRflush, NMR };
593 	enum { WCRdata, WCRflush, NWCR };
594 	Alt alts[NCR+1];
595 
596 	w = x->f->w;
597 	if(w->deleted){
598 		filsysrespond(x->fs, x, &fc, Edeleted);
599 		return;
600 	}
601 	qid = FILE(x->f->qid);
602 	off = x->offset;
603 	cnt = x->count;
604 	switch(qid){
605 	case Qcons:
606 		x->flushtag = x->tag;
607 
608 		alts[CRdata].c = w->consread;
609 		alts[CRdata].v = &crm;
610 		alts[CRdata].op = CHANRCV;
611 		alts[CRflush].c = x->flushc;
612 		alts[CRflush].v = nil;
613 		alts[CRflush].op = CHANRCV;
614 		alts[NMR].op = CHANEND;
615 
616 		switch(alt(alts)){
617 		case CRdata:
618 			break;
619 		case CRflush:
620 			filsyscancel(x);
621 			return;
622 		}
623 
624 		/* received data */
625 		x->flushtag = -1;
626 		c1 = crm.c1;
627 		c2 = crm.c2;
628 		t = malloc(cnt+UTFmax+1);	/* room to unpack partial rune plus */
629 		pair.s = t;
630 		pair.ns = cnt;
631 		send(c1, &pair);
632 		if(x->flushing){
633 			recv(x->flushc, nil);	/* wake up flushing xfid */
634 			recv(c2, nil);			/* wake up window and toss data */
635 			free(t);
636 			filsyscancel(x);
637 			return;
638 		}
639 		qlock(&x->active);
640 		recv(c2, &pair);
641 		fc.data = pair.s;
642 		fc.count = pair.ns;
643 		filsysrespond(x->fs, x, &fc, nil);
644 		free(t);
645 		qunlock(&x->active);
646 		break;
647 
648 	case Qlabel:
649 		n = strlen(w->label);
650 		if(off > n)
651 			off = n;
652 		if(off+cnt > n)
653 			cnt = n-off;
654 		fc.data = w->label+off;
655 		fc.count = cnt;
656 		filsysrespond(x->fs, x, &fc, nil);
657 		break;
658 
659 	case Qmouse:
660 		x->flushtag = x->tag;
661 
662 		alts[MRdata].c = w->mouseread;
663 		alts[MRdata].v = &mrm;
664 		alts[MRdata].op = CHANRCV;
665 		alts[MRflush].c = x->flushc;
666 		alts[MRflush].v = nil;
667 		alts[MRflush].op = CHANRCV;
668 		alts[NMR].op = CHANEND;
669 
670 		switch(alt(alts)){
671 		case MRdata:
672 			break;
673 		case MRflush:
674 			filsyscancel(x);
675 			return;
676 		}
677 
678 		/* received data */
679 		x->flushtag = -1;
680 		if(x->flushing){
681 			recv(x->flushc, nil);		/* wake up flushing xfid */
682 			recv(mrm.cm, nil);			/* wake up window and toss data */
683 			filsyscancel(x);
684 			return;
685 		}
686 		qlock(&x->active);
687 		recv(mrm.cm, &ms);
688 		c = 'm';
689 		if(w->resized)
690 			c = 'r';
691 		n = sprint(buf, "%c%11d %11d %11d %11ld ", c, ms.xy.x, ms.xy.y, ms.buttons, ms.msec);
692 		w->resized = 0;
693 		fc.data = buf;
694 		fc.count = min(n, cnt);
695 		filsysrespond(x->fs, x, &fc, nil);
696 		qunlock(&x->active);
697 		break;
698 
699 	case Qcursor:
700 		filsysrespond(x->fs, x, &fc, "cursor read not implemented");
701 		break;
702 
703 	/* The algorithm for snarf and text is expensive but easy and rarely used */
704 	case Qsnarf:
705 		getsnarf();
706 		if(nsnarf)
707 			t = runetobyte(snarf, nsnarf, &n);
708 		else {
709 			t = nil;
710 			n = 0;
711 		}
712 		goto Text;
713 
714 	case Qtext:
715 		t = wcontents(w, &n);
716 		goto Text;
717 
718 	Text:
719 		if(off > n){
720 			off = n;
721 			cnt = 0;
722 		}
723 		if(off+cnt > n)
724 			cnt = n-off;
725 		fc.data = t+off;
726 		fc.count = cnt;
727 		filsysrespond(x->fs, x, &fc, nil);
728 		free(t);
729 		break;
730 
731 	case Qwdir:
732 		t = estrdup(w->dir);
733 		n = strlen(t);
734 		goto Text;
735 
736 	case Qwinid:
737 		n = sprint(buf, "%11d ", w->id);
738 		t = estrdup(buf);
739 		goto Text;
740 
741 
742 	case Qwinname:
743 		n = strlen(w->name);
744 		if(n == 0){
745 			filsysrespond(x->fs, x, &fc, "window has no name");
746 			break;
747 		}
748 		t = estrdup(w->name);
749 		goto Text;
750 
751 	case Qwindow:
752 		i = w->i;
753 		if(i == nil || Dx(w->screenr)<=0){
754 			filsysrespond(x->fs, x, &fc, Enowindow);
755 			return;
756 		}
757 		r = w->screenr;
758 		goto caseImage;
759 
760 	case Qscreen:
761 		i = display->image;
762 		if(i == nil){
763 			filsysrespond(x->fs, x, &fc, "no top-level screen");
764 			break;
765 		}
766 		r = i->r;
767 		/* fall through */
768 
769 	caseImage:
770 		if(off < 5*12){
771 			n = sprint(buf, "%11s %11d %11d %11d %11d ",
772 				chantostr(cbuf, screen->chan),
773 				i->r.min.x, i->r.min.y, i->r.max.x, i->r.max.y);
774 			t = estrdup(buf);
775 			goto Text;
776 		}
777 		t = malloc(cnt);
778 		fc.data = t;
779 		n = readwindow(i, t, r, off, cnt);	/* careful; fc.count is unsigned */
780 		if(n < 0){
781 			buf[0] = 0;
782 			errstr(buf, sizeof buf);
783 			filsysrespond(x->fs, x, &fc, buf);
784 		}else{
785 			fc.count = n;
786 			filsysrespond(x->fs, x, &fc, nil);
787 		}
788 		free(t);
789 		return;
790 
791 	case Qwctl:	/* read returns rectangle, hangs if not resized */
792 		if(cnt < 4*12){
793 			filsysrespond(x->fs, x, &fc, Etooshort);
794 			break;
795 		}
796 		x->flushtag = x->tag;
797 
798 		alts[WCRdata].c = w->wctlread;
799 		alts[WCRdata].v = &cwrm;
800 		alts[WCRdata].op = CHANRCV;
801 		alts[WCRflush].c = x->flushc;
802 		alts[WCRflush].v = nil;
803 		alts[WCRflush].op = CHANRCV;
804 		alts[NMR].op = CHANEND;
805 
806 		switch(alt(alts)){
807 		case WCRdata:
808 			break;
809 		case WCRflush:
810 			filsyscancel(x);
811 			return;
812 		}
813 
814 		/* received data */
815 		x->flushtag = -1;
816 		c1 = cwrm.c1;
817 		c2 = cwrm.c2;
818 		t = malloc(cnt+1);	/* be sure to have room for NUL */
819 		pair.s = t;
820 		pair.ns = cnt+1;
821 		send(c1, &pair);
822 		if(x->flushing){
823 			recv(x->flushc, nil);	/* wake up flushing xfid */
824 			recv(c2, nil);			/* wake up window and toss data */
825 			free(t);
826 			filsyscancel(x);
827 			return;
828 		}
829 		qlock(&x->active);
830 		recv(c2, &pair);
831 		fc.data = pair.s;
832 		if(pair.ns > cnt)
833 			pair.ns = cnt;
834 		fc.count = pair.ns;
835 		filsysrespond(x->fs, x, &fc, nil);
836 		free(t);
837 		qunlock(&x->active);
838 		break;
839 
840 	default:
841 		fprint(2, "unknown qid %d in read\n", qid);
842 		sprint(buf, "unknown qid in read");
843 		filsysrespond(x->fs, x, &fc, buf);
844 		break;
845 	}
846 }
847