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