]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/rio/rio.c
rio: move the test if w is allowed to change cursor into wsetcursor()
[plan9front.git] / sys / src / cmd / rio / rio.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 /*
15  *  WASHINGTON (AP) - The Food and Drug Administration warned
16  * consumers Wednesday not to use ``Rio'' hair relaxer products
17  * because they may cause severe hair loss or turn hair green....
18  *    The FDA urged consumers who have experienced problems with Rio
19  * to notify their local FDA office, local health department or the
20  * company at 1‑800‑543‑3002.
21  */
22
23 void            resize(void);
24 void            move(void);
25 void            delete(void);
26 void            hide(void);
27 void            unhide(int);
28 void            newtile(int);
29 Image*  sweep(void);
30 Image*  bandsize(Window*);
31 Image*  drag(Window*);
32 void            resized(void);
33 Channel *exitchan;      /* chan(int) */
34 Channel *winclosechan; /* chan(Window*); */
35 Channel *kbdchan;       /* chan(char*); */
36 Rectangle       viewr;
37 int             threadrforkflag = 0;    /* should be RFENVG but that hides rio from plumber */
38
39 void    mousethread(void*);
40 void    keyboardthread(void*);
41 void    winclosethread(void*);
42 void    deletethread(void*);
43 void    initcmd(void*);
44 Channel* initkbd(void);
45
46 char            *fontname;
47
48 enum
49 {
50         New,
51         Reshape,
52         Move,
53         Delete,
54         Hide,
55         Exit,
56 };
57
58 enum
59 {
60         Cut,
61         Paste,
62         Snarf,
63         Plumb,
64         Look,
65         Send,
66         Scroll,
67 };
68
69 char            *menu2str[] = {
70  [Cut]          "cut",
71  [Paste]                "paste",
72  [Snarf]                "snarf",
73  [Plumb]                "plumb",
74  [Look]         "look",
75  [Send]         "send",
76  [Scroll]               "scroll",
77                         nil
78 };
79
80 Menu menu2 =
81 {
82         menu2str
83 };
84
85 int     Hidden = Exit+1;
86
87 char            *menu3str[100] = {
88  [New]          "New",
89  [Reshape]      "Resize",
90  [Move]         "Move",
91  [Delete]               "Delete",
92  [Hide]         "Hide",
93  [Exit]         "Exit",
94                         nil
95 };
96
97 Menu menu3 =
98 {
99         menu3str
100 };
101
102 char *rcargv[] = { "rc", "-i", nil };
103 char *kbdargv[] = { "rc", "-c", nil, nil };
104
105 int errorshouldabort = 0;
106
107 void
108 derror(Display*, char *errorstr)
109 {
110         error(errorstr);
111 }
112
113 void
114 usage(void)
115 {
116         fprint(2, "usage: rio [-b] [-f font] [-i initcmd] [-k kbdcmd] [-s]\n");
117         exits("usage");
118 }
119
120 void
121 threadmain(int argc, char *argv[])
122 {
123         char *initstr, *kbdin, *s;
124         static void *arg[1];
125         char buf[256];
126         Image *i;
127         Rectangle r;
128
129         if(strstr(argv[0], ".out") == nil){
130                 menu3str[Exit] = nil;
131                 Hidden--;
132         }
133         initstr = nil;
134         kbdin = nil;
135         maxtab = 0;
136         ARGBEGIN{
137         case 'b':
138                 reverse = ~0xFF;
139                 break;
140         case 'f':
141                 fontname = EARGF(usage());
142                 break;
143         case 'i':
144                 initstr = EARGF(usage());
145                 break;
146         case 'k':
147                 if(kbdin != nil)
148                         usage();
149                 kbdin = EARGF(usage());
150                 break;
151         case 's':
152                 scrolling = TRUE;
153                 break;
154         case 'D':
155                 debug++;
156                 break;
157         default:
158                 usage();
159         }ARGEND
160
161         if(getwd(buf, sizeof buf) == nil)
162                 startdir = estrdup(".");
163         else
164                 startdir = estrdup(buf);
165         if(fontname == nil)
166                 fontname = getenv("font");
167         s = getenv("tabstop");
168         if(s != nil)
169                 maxtab = strtol(s, nil, 0);
170         if(maxtab == 0)
171                 maxtab = 4;
172         free(s);
173
174         if(fontname){
175                 /* check font before barging ahead */
176                 if(access(fontname, 0) < 0){
177                         fprint(2, "rio: can't access %s: %r\n", fontname);
178                         exits("font open");
179                 }
180                 putenv("font", fontname);
181         }
182
183         snarffd = open("/dev/snarf", OREAD|OCEXEC);
184         gotscreen = access("/dev/screen", AEXIST)==0;
185
186         if(geninitdraw(nil, derror, nil, "rio", nil, Refnone) < 0){
187                 fprint(2, "rio: can't open display: %r\n");
188                 exits("display open");
189         }
190         iconinit();
191
192         exitchan = chancreate(sizeof(int), 0);
193         winclosechan = chancreate(sizeof(Window*), 0);
194         deletechan = chancreate(sizeof(char*), 0);
195
196         view = screen;
197         viewr = view->r;
198         mousectl = initmouse(nil, screen);
199         if(mousectl == nil)
200                 error("can't find mouse");
201         mouse = mousectl;
202         kbdchan = initkbd();
203         if(kbdchan == nil)
204                 error("can't find keyboard");
205         wscreen = allocscreen(screen, background, 0);
206         if(wscreen == nil)
207                 error("can't allocate screen");
208         draw(view, viewr, background, nil, ZP);
209         flushimage(display, 1);
210
211         timerinit();
212         threadcreate(keyboardthread, nil, STACK);
213         threadcreate(mousethread, nil, STACK);
214         threadcreate(winclosethread, nil, STACK);
215         threadcreate(deletethread, nil, STACK);
216         filsys = filsysinit(xfidinit());
217
218         if(filsys == nil)
219                 fprint(2, "rio: can't create file system server: %r\n");
220         else{
221                 errorshouldabort = 1;   /* suicide if there's trouble after this */
222                 if(initstr)
223                         proccreate(initcmd, initstr, STACK);
224                 if(kbdin){
225                         kbdargv[2] = kbdin;
226                         r = screen->r;
227                         r.min.y = r.max.y-Dy(r)/3;
228                         i = allocwindow(wscreen, r, Refbackup, DNofill);
229                         wkeyboard = new(i, FALSE, scrolling, 0, nil, "/bin/rc", kbdargv);
230                         if(wkeyboard == nil)
231                                 error("can't create keyboard window");
232                 }
233                 threadnotify(shutdown, 1);
234                 recv(exitchan, nil);
235         }
236         killprocs();
237         threadexitsall(nil);
238 }
239
240 /*
241  * /dev/snarf updates when the file is closed, so we must open our own
242  * fd here rather than use snarffd
243  */
244 void
245 putsnarf(void)
246 {
247         int fd, i, n;
248
249         if(snarffd<0 || nsnarf==0)
250                 return;
251         fd = open("/dev/snarf", OWRITE);
252         if(fd < 0)
253                 return;
254         /* snarf buffer could be huge, so fprint will truncate; do it in blocks */
255         for(i=0; i<nsnarf; i+=n){
256                 n = nsnarf-i;
257                 if(n >= 256)
258                         n = 256;
259                 if(fprint(fd, "%.*S", n, snarf+i) < 0)
260                         break;
261         }
262         close(fd);
263 }
264
265 void
266 getsnarf(void)
267 {
268         int i, n, nb, nulls;
269         char *s, *sn;
270
271         if(snarffd < 0)
272                 return;
273         sn = nil;
274         i = 0;
275         seek(snarffd, 0, 0);
276         for(;;){
277                 if(i > MAXSNARF)
278                         break;
279                 if((s = realloc(sn, i+1024+1)) == nil)
280                         break;
281                 sn = s;
282                 if((n = read(snarffd, sn+i, 1024)) <= 0)
283                         break;
284                 i += n;
285         }
286         if(i == 0)
287                 return;
288         sn[i] = 0;
289         if((snarf = runerealloc(snarf, i+1)) != nil)
290                 cvttorunes(sn, i, snarf, &nb, &nsnarf, &nulls);
291         free(sn);
292 }
293
294 void
295 initcmd(void *arg)
296 {
297         char *cmd;
298
299         cmd = arg;
300         rfork(RFENVG|RFFDG|RFNOTEG|RFNAMEG);
301         procexecl(nil, "/bin/rc", "rc", "-c", cmd, nil);
302         fprint(2, "rio: exec failed: %r\n");
303         exits("exec");
304 }
305
306 char *oknotes[] =
307 {
308         "delete",
309         "hangup",
310         "kill",
311         "exit",
312         nil
313 };
314
315 int
316 shutdown(void *, char *msg)
317 {
318         int i;
319         static Lock shutdownlk;
320         
321         killprocs();
322         for(i=0; oknotes[i]; i++)
323                 if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0){
324                         lock(&shutdownlk);      /* only one can threadexitsall */
325                         threadexitsall(msg);
326                 }
327         fprint(2, "rio %d: abort: %s\n", getpid(), msg);
328         abort();
329         exits(msg);
330         return 0;
331 }
332
333 void
334 killprocs(void)
335 {
336         int i;
337
338         for(i=0; i<nwindow; i++)
339                 if(window[i]->notefd >= 0)
340                         write(window[i]->notefd, "hangup", 6); 
341 }
342
343 void
344 keyboardthread(void*)
345 {
346         char *s;
347
348         threadsetname("keyboardthread");
349
350         while(s = recvp(kbdchan)){
351                 if(*s == 'k' || *s == 'K')
352                         shiftdown = utfrune(s+1, Kshift) != nil;
353                 if(input == nil || sendp(input->ck, s) <= 0)
354                         free(s);
355         }
356 }
357
358 int
359 inborder(Rectangle r, Point xy)
360 {
361         return ptinrect(xy, r) && !ptinrect(xy, insetrect(r, Selborder));
362 }
363
364 Rectangle
365 whichrect(Rectangle r, Point p, int which)
366 {
367         switch(which){
368         case 0: /* top left */
369                 r = Rect(p.x, p.y, r.max.x, r.max.y);
370                 break;
371         case 2: /* top right */
372                 r = Rect(r.min.x, p.y, p.x+1, r.max.y);
373                 break;
374         case 6: /* bottom left */
375                 r = Rect(p.x, r.min.y, r.max.x, p.y+1);
376                 break;
377         case 8: /* bottom right */
378                 r = Rect(r.min.x, r.min.y, p.x+1, p.y+1);
379                 break;
380         case 1: /* top edge */
381                 r = Rect(r.min.x, p.y, r.max.x, r.max.y);
382                 break;
383         case 5: /* right edge */
384                 r = Rect(r.min.x, r.min.y, p.x+1, r.max.y);
385                 break;
386         case 7: /* bottom edge */
387                 r = Rect(r.min.x, r.min.y, r.max.x, p.y+1);
388                 break;
389         case 3:         /* left edge */
390                 r = Rect(p.x, r.min.y, r.max.x, r.max.y);
391                 break;
392         }
393         return canonrect(r);
394 }
395
396 int
397 portion(int x, int lo, int hi)
398 {
399         x -= lo;
400         hi -= lo;
401         if(hi < 20)
402                 return x > 0 ? 2 : 0;
403         if(x < 20)
404                 return 0;
405         if(x > hi-20)
406                 return 2;
407         return 1;
408 }
409
410 int
411 whichcorner(Rectangle r, Point p)
412 {
413         int i, j;
414         
415         i = portion(p.x, r.min.x, r.max.x);
416         j = portion(p.y, r.min.y, r.max.y);
417         return 3*j+i;
418 }
419
420 /* thread to allow fsysproc to synchronize window closing with main proc */
421 void
422 winclosethread(void*)
423 {
424         Window *w;
425
426         threadsetname("winclosethread");
427         for(;;){
428                 w = recvp(winclosechan);
429                 wclose(w);
430         }
431 }
432
433 /* thread to make Deleted windows that the client still holds disappear offscreen after an interval */
434 void
435 deletethread(void*)
436 {
437         char *s;
438         Image *i;
439
440         threadsetname("deletethread");
441         for(;;){
442                 s = recvp(deletechan);
443                 i = namedimage(display, s);
444                 if(i != nil){
445                         /* move it off-screen to hide it, since client is slow in letting it go */
446                         originwindow(i, i->r.min, view->r.max);
447                         freeimage(i);
448                         flushimage(display, 1);
449                 }
450                 free(s);
451         }
452 }
453
454 void
455 deletetimeoutproc(void *v)
456 {
457         char *s;
458
459         s = v;
460         sleep(750);     /* remove window from screen after 3/4 of a second */
461         sendp(deletechan, s);
462 }
463
464 /*
465  * Button 6 - keyboard toggle - has been pressed.
466  * Send event to keyboard, wait for button up, send that.
467  * Note: there is no coordinate translation done here; this
468  * is just about getting button 6 to the keyboard simulator.
469  */
470 void
471 keyboardhide(void)
472 {
473         send(wkeyboard->mc.c, mouse);
474         do
475                 readmouse(mousectl);
476         while(mouse->buttons & (1<<5));
477         send(wkeyboard->mc.c, mouse);
478 }
479
480 void
481 mousethread(void*)
482 {
483         int sending, inside, scrolling, moving;
484         Window *w, *winput;
485         Image *i;
486         Point xy;
487         Mouse tmp;
488         enum {
489                 MReshape,
490                 MMouse,
491                 NALT
492         };
493         static Alt alts[NALT+1];
494
495         threadsetname("mousethread");
496         sending = FALSE;
497         scrolling = FALSE;
498
499         alts[MReshape].c = mousectl->resizec;
500         alts[MReshape].v = nil;
501         alts[MReshape].op = CHANRCV;
502         alts[MMouse].c = mousectl->c;
503         alts[MMouse].v = &mousectl->Mouse;
504         alts[MMouse].op = CHANRCV;
505         alts[NALT].op = CHANEND;
506
507         for(;;)
508             switch(alt(alts)){
509                 case MReshape:
510                         resized();
511                         break;
512                 case MMouse:
513                         if(wkeyboard!=nil && (mouse->buttons & (1<<5))){
514                                 keyboardhide();
515                                 break;
516                         }
517                 Again:
518                         moving = FALSE;
519                         winput = input;
520                         /* override everything for the keyboard window */
521                         if(wkeyboard!=nil && ptinrect(mouse->xy, wkeyboard->screenr)){
522                                 /* make sure it's on top; this call is free if it is */
523                                 wtopme(wkeyboard);
524                                 winput = wkeyboard;
525                         }
526                         if(winput!=nil && !winput->deleted && winput->i!=nil){
527                                 /* convert to logical coordinates */
528                                 xy.x = mouse->xy.x + (winput->i->r.min.x-winput->screenr.min.x);
529                                 xy.y = mouse->xy.y + (winput->i->r.min.y-winput->screenr.min.y);
530
531                                 /* the up and down scroll buttons are not subject to the usual rules */
532                                 if((mouse->buttons&(8|16)) && !winput->mouseopen)
533                                         goto Sending;
534
535                                 inside = ptinrect(mouse->xy, insetrect(winput->screenr, Selborder));
536                                 if(winput->mouseopen)
537                                         scrolling = FALSE;
538                                 else if(scrolling)
539                                         scrolling = mouse->buttons;
540                                 else
541                                         scrolling = mouse->buttons && ptinrect(xy, winput->scrollr);
542                                 /* topped will be zero or less if window has been bottomed */
543                                 if(sending == FALSE && !scrolling && inborder(winput->screenr, mouse->xy) && winput->topped>0)
544                                         moving = TRUE;
545                                 else if(inside && (scrolling || winput->mouseopen || (mouse->buttons&1)))
546                                         sending = TRUE;
547                         }else
548                                 sending = FALSE;
549                         if(sending){
550                         Sending:
551                                 wsetcursor(winput, FALSE);
552                                 if(mouse->buttons == 0)
553                                         sending = FALSE;
554                                 tmp = mousectl->Mouse;
555                                 tmp.xy = xy;
556                                 send(winput->mc.c, &tmp);
557                                 continue;
558                         }
559                         if(moving && (mouse->buttons&7)){
560                                 incref(winput);
561                                 sweeping = TRUE;
562                                 if(mouse->buttons & 3)
563                                         i = bandsize(winput);
564                                 else
565                                         i = drag(winput);
566                                 sweeping = FALSE;
567                                 if(i != nil)
568                                         wsendctlmesg(winput, Reshaped, i->r, i);
569                                 wclose(winput);
570                                 continue;
571                         }
572                         w = wpointto(mouse->xy);
573                         if(w!=nil && inborder(w->screenr, mouse->xy))
574                                 riosetcursor(corners[whichcorner(w->screenr, mouse->xy)]);
575                         else
576                                 wsetcursor(w, FALSE);
577                         /* we're not sending the event, but if button is down maybe we should */
578                         if(mouse->buttons){
579                                 /* w->topped will be zero or less if window has been bottomed */
580                                 if(w==nil || (w==winput && w->topped>0)){
581                                         if(mouse->buttons & 1){
582                                                 ;
583                                         }else if(mouse->buttons & 2){
584                                                 if(winput && !winput->deleted && !winput->mouseopen){
585                                                         incref(winput);
586                                                         button2menu(winput);
587                                                         wclose(winput);
588                                                 }
589                                         }else if(mouse->buttons & 4)
590                                                 button3menu();
591                                 }else{
592                                         /* if button 1 event in the window, top the window and wait for button up. */
593                                         /* otherwise, top the window and pass the event on */
594                                         if(wtop(mouse->xy) && (mouse->buttons!=1 || inborder(w->screenr, mouse->xy)))
595                                                 goto Again;
596                                         goto Drain;
597                                 }
598                         }
599                         break;
600
601                 Drain:
602                         do
603                                 readmouse(mousectl);
604                         while(mousectl->buttons);
605                         goto Again;     /* recalculate mouse position, cursor */
606                 }
607 }
608
609 int
610 wtopcmp(void *a, void *b)
611 {
612         return (*(Window**)a)->topped - (*(Window**)b)->topped;
613 }
614
615 void
616 resized(void)
617 {
618         Image *im;
619         int i, j;
620         Rectangle r;
621         Point o, n;
622         Window *w;
623
624         if(getwindow(display, Refnone) < 0)
625                 error("failed to re-attach window");
626         freescrtemps();
627         view = screen;
628         freescreen(wscreen);
629         wscreen = allocscreen(screen, background, 0);
630         if(wscreen == nil)
631                 error("can't re-allocate screen");
632         draw(view, view->r, background, nil, ZP);
633         o = subpt(viewr.max, viewr.min);
634         n = subpt(view->clipr.max, view->clipr.min);
635         qsort(window, nwindow, sizeof(window[0]), wtopcmp);
636         for(i=0; i<nwindow; i++){
637                 w = window[i];
638                 r = rectsubpt(w->i->r, viewr.min);
639                 r.min.x = (r.min.x*n.x)/o.x;
640                 r.min.y = (r.min.y*n.y)/o.y;
641                 r.max.x = (r.max.x*n.x)/o.x;
642                 r.max.y = (r.max.y*n.y)/o.y;
643                 r = rectaddpt(r, view->clipr.min);
644                 if(!goodrect(r))
645                         r = rectsubpt(w->i->r, subpt(w->i->r.min, r.min));
646                 for(j=0; j<nhidden; j++)
647                         if(w == hidden[j])
648                                 break;
649                 incref(w);
650                 if(j < nhidden){
651                         im = allocimage(display, r, screen->chan, 0, DNofill);
652                         r = ZR;
653                 } else
654                         im = allocwindow(wscreen, r, Refbackup, DNofill);
655                 if(im)
656                         wsendctlmesg(w, Reshaped, r, im);
657                 wclose(w);
658         }
659         viewr = view->r;
660         flushimage(display, 1);
661 }
662
663 int
664 obscured(Window *w, Rectangle r, int i)
665 {
666         Window *t;
667
668         if(Dx(r) < font->height || Dy(r) < font->height)
669                 return 1;
670         if(!rectclip(&r, screen->r))
671                 return 1;
672         for(; i<nwindow; i++){
673                 t = window[i];
674                 if(t == w || t->topped <= w->topped)
675                         continue;
676                 if(Dx(t->screenr) == 0 || Dy(t->screenr) == 0 || rectXrect(r, t->screenr) == 0)
677                         continue;
678                 if(r.min.y < t->screenr.min.y)
679                         if(!obscured(w, Rect(r.min.x, r.min.y, r.max.x, t->screenr.min.y), i))
680                                 return 0;
681                 if(r.min.x < t->screenr.min.x)
682                         if(!obscured(w, Rect(r.min.x, r.min.y, t->screenr.min.x, r.max.y), i))
683                                 return 0;
684                 if(r.max.y > t->screenr.max.y)
685                         if(!obscured(w, Rect(r.min.x, t->screenr.max.y, r.max.x, r.max.y), i))
686                                 return 0;
687                 if(r.max.x > t->screenr.max.x)
688                         if(!obscured(w, Rect(t->screenr.max.x, r.min.y, r.max.x, r.max.y), i))
689                                 return 0;
690                 return 1;
691         }
692         return 0;
693 }
694
695 static char*
696 shortlabel(char *s)
697 {
698         enum { NBUF=60 };
699         static char buf[NBUF*UTFmax];
700         int i, k, l;
701         Rune r;
702
703         l = utflen(s);
704         if(l < NBUF-2)
705                 return estrdup(s);
706         k = i = 0;
707         while(i < NBUF/2){
708                 k += chartorune(&r, s+k);
709                 i++;
710         }
711         strncpy(buf, s, k);
712         strcpy(buf+k, "...");
713         while((l-i) >= NBUF/2-4){
714                 k += chartorune(&r, s+k);
715                 i++;
716         }
717         strcat(buf, s+k);
718         return estrdup(buf);
719 }
720
721 void
722 button3menu(void)
723 {
724         int i, j, n;
725
726         n = nhidden;
727         for(i=0; i<nwindow; i++){
728                 for(j=0; j<n; j++)
729                         if(window[i] == hidden[j])
730                                 break;
731                 if(j == n)
732                         if(obscured(window[i], window[i]->screenr, 0)){
733                                 hidden[n++] = window[i];
734                                 if(n >= nelem(hidden))
735                                         break;
736                         }
737         }
738         if(n >= nelem(menu3str)-Hidden)
739                 n = nelem(menu3str)-Hidden-1;
740         for(i=0; i<n; i++){
741                 free(menu3str[i+Hidden]);
742                 menu3str[i+Hidden] = shortlabel(hidden[i]->label);
743         }
744         for(i+=Hidden; menu3str[i]; i++){
745                 free(menu3str[i]);
746                 menu3str[i] = nil;
747         }
748         sweeping = TRUE;
749         switch(i = menuhit(3, mousectl, &menu3, wscreen)){
750         case -1:
751                 break;
752         case New:
753                 new(sweep(), FALSE, scrolling, 0, nil, "/bin/rc", nil);
754                 break;
755         case Reshape:
756                 resize();
757                 break;
758         case Move:
759                 move();
760                 break;
761         case Delete:
762                 delete();
763                 break;
764         case Hide:
765                 hide();
766                 break;
767         case Exit:
768                 if(Hidden > Exit){
769                         send(exitchan, nil);
770                         break;
771                 }
772                 /* else fall through */
773         default:
774                 unhide(i);
775                 break;
776         }
777         sweeping = FALSE;
778 }
779
780 void
781 button2menu(Window *w)
782 {
783         if(w->scrolling)
784                 menu2str[Scroll] = "noscroll";
785         else
786                 menu2str[Scroll] = "scroll";
787         switch(menuhit(2, mousectl, &menu2, wscreen)){
788         case Cut:
789                 wsnarf(w);
790                 wcut(w);
791                 wscrdraw(w);
792                 break;
793
794         case Snarf:
795                 wsnarf(w);
796                 break;
797
798         case Paste:
799                 getsnarf();
800                 wpaste(w);
801                 wscrdraw(w);
802                 break;
803
804         case Plumb:
805                 wplumb(w);
806                 break;
807
808         case Look:
809                 wlook(w);
810                 break;
811
812         case Send:
813                 getsnarf();
814                 wsnarf(w);
815                 if(nsnarf == 0)
816                         break;
817                 if(w->rawing){
818                         waddraw(w, snarf, nsnarf);
819                         if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
820                                 waddraw(w, L"\n", 1);
821                 }else{
822                         winsert(w, snarf, nsnarf, w->nr);
823                         if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
824                                 winsert(w, L"\n", 1, w->nr);
825                 }
826                 wsetselect(w, w->nr, w->nr);
827                 wshow(w, w->nr);
828                 break;
829
830         case Scroll:
831                 if(w->scrolling ^= 1)
832                         wshow(w, w->nr);
833                 break;
834         }
835         flushimage(display, 1);
836         wsendctlmesg(w, Wakeup, ZR, nil);
837 }
838
839 Point
840 onscreen(Point p)
841 {
842         p.x = max(screen->clipr.min.x, p.x);
843         p.x = min(screen->clipr.max.x, p.x);
844         p.y = max(screen->clipr.min.y, p.y);
845         p.y = min(screen->clipr.max.y, p.y);
846         return p;
847 }
848
849 Image*
850 sweep(void)
851 {
852         Image *i, *oi;
853         Rectangle r;
854         Point p0, p;
855
856         i = nil;
857         menuing = TRUE;
858         riosetcursor(&crosscursor);
859         while(mouse->buttons == 0)
860                 readmouse(mousectl);
861         p0 = onscreen(mouse->xy);
862         p = p0;
863         r.min = p;
864         r.max = p;
865         oi = nil;
866         while(mouse->buttons == 4){
867                 if(!eqpt(mouse->xy, p)){
868                         p = onscreen(mouse->xy);
869                         r = canonrect(Rpt(p0, p));
870                         r = whichrect(r, p, whichcorner(r, p));
871                         if(Dx(r)>5 && Dy(r)>5){
872                                 i = allocwindow(wscreen, r, Refnone, DNofill);
873                                 freeimage(oi);
874                                 if(i == nil)
875                                         goto Rescue;
876                                 oi = i;
877                                 border(i, r, Selborder, sizecol, ZP);
878                                 draw(i, insetrect(r, Selborder), cols[BACK], nil, ZP);
879                         }
880                 }
881                 readmouse(mousectl);
882         }
883         if(mouse->buttons != 0)
884                 goto Rescue;
885         if(i==nil || !goodrect(r))
886                 goto Rescue;
887         oi = i;
888         i = allocwindow(wscreen, oi->r, Refbackup, DNofill);
889         freeimage(oi);
890         if(i == nil)
891                 goto Rescue;
892         riosetcursor(corners[whichcorner(i->r, mouse->xy)]);
893         goto Return;
894
895  Rescue:
896         riosetcursor(nil);
897         freeimage(i);
898         i = nil;
899         flushimage(display, 1);
900         while(mouse->buttons)
901                 readmouse(mousectl);
902
903  Return:
904         menuing = FALSE;
905         return i;
906 }
907
908 void
909 drawedge(Image **bp, Rectangle r)
910 {
911         Image *b = *bp;
912         if(b != nil && Dx(b->r) == Dx(r) && Dy(b->r) == Dy(r))
913                 originwindow(b, r.min, r.min);
914         else{
915                 freeimage(b);
916                 b = allocwindow(wscreen, r, Refbackup, DNofill);
917                 if(b != nil) draw(b, r, sizecol, nil, ZP);
918                 *bp = b;
919         }
920 }
921
922 void
923 drawborder(Rectangle r, int show)
924 {
925         static Image *b[4];
926         int i;
927         if(show == 0){
928                 for(i = 0; i < 4; i++){
929                         freeimage(b[i]);
930                         b[i] = nil;
931                 }
932         }else{
933                 r = canonrect(r);
934                 drawedge(&b[0], Rect(r.min.x, r.min.y, r.min.x+Borderwidth, r.max.y));
935                 drawedge(&b[1], Rect(r.min.x+Borderwidth, r.min.y, r.max.x-Borderwidth, r.min.y+Borderwidth));
936                 drawedge(&b[2], Rect(r.max.x-Borderwidth, r.min.y, r.max.x, r.max.y));
937                 drawedge(&b[3], Rect(r.min.x+Borderwidth, r.max.y-Borderwidth, r.max.x-Borderwidth, r.max.y));
938         }
939 }
940
941 Image*
942 drag(Window *w)
943 {
944         Point p, op, d, dm, om;
945         Rectangle r;
946
947         menuing = TRUE;
948         riosetcursor(&boxcursor);
949         om = mouse->xy;
950         dm = subpt(om, w->screenr.min);
951         d = subpt(w->screenr.max, w->screenr.min);
952         op = subpt(om, dm);
953         drawborder(Rect(op.x, op.y, op.x+d.x, op.y+d.y), 1);
954         while(mouse->buttons==4){
955                 p = subpt(mouse->xy, dm);
956                 if(!eqpt(p, op)){
957                         drawborder(Rect(p.x, p.y, p.x+d.x, p.y+d.y), 1);
958                         op = p;
959                 }
960                 readmouse(mousectl);
961         }
962         r = Rect(op.x, op.y, op.x+d.x, op.y+d.y);
963         drawborder(r, 0);
964         p = mouse->xy;
965         riosetcursor(inborder(r, p) ? corners[whichcorner(r, p)] : nil);
966         menuing = FALSE;
967         if(mouse->buttons!=0 || !goodrect(r) || eqrect(r, w->screenr)){
968                 flushimage(display, 1);
969                 while(mouse->buttons)
970                         readmouse(mousectl);
971                 return nil;
972         }
973         return allocwindow(wscreen, r, Refbackup, DNofill);
974 }
975
976 Image*
977 bandsize(Window *w)
978 {
979         Rectangle r, or;
980         Point p, startp;
981         int which, but;
982
983         p = mouse->xy;
984         but = mouse->buttons;
985         which = whichcorner(w->screenr, p);
986         riosetcursor(corners[which]);
987         r = whichrect(w->screenr, p, which);
988         drawborder(r, 1);
989         or = r;
990         startp = p;
991         
992         while(mouse->buttons==but){
993                 p = onscreen(mouse->xy);
994                 r = whichrect(w->screenr, p, which);
995                 if(!eqrect(r, or) && goodrect(r)){
996                         drawborder(r, 1);
997                         or = r;
998                 }
999                 readmouse(mousectl);
1000         }
1001         p = mouse->xy;
1002         drawborder(or, 0);
1003         if(mouse->buttons!=0 || !goodrect(or) || eqrect(or, w->screenr)
1004         || abs(p.x-startp.x)+abs(p.y-startp.y) <= 1){
1005                 flushimage(display, 1);
1006                 while(mouse->buttons)
1007                         readmouse(mousectl);
1008                 return nil;
1009         }
1010         return allocwindow(wscreen, or, Refbackup, DNofill);
1011 }
1012
1013 Window*
1014 pointto(int wait)
1015 {
1016         Window *w;
1017
1018         menuing = TRUE;
1019         riosetcursor(&sightcursor);
1020         while(mouse->buttons == 0)
1021                 readmouse(mousectl);
1022         if(mouse->buttons == 4)
1023                 w = wpointto(mouse->xy);
1024         else
1025                 w = nil;
1026         if(wait){
1027                 while(mouse->buttons){
1028                         if(mouse->buttons!=4 && w !=nil){       /* cancel */
1029                                 riosetcursor(nil);
1030                                 w = nil;
1031                         }
1032                         readmouse(mousectl);
1033                 }
1034                 if(w != nil && wpointto(mouse->xy) != w)
1035                         w = nil;
1036         }
1037         riosetcursor(nil);
1038         menuing = FALSE;
1039         return w;
1040 }
1041
1042 void
1043 delete(void)
1044 {
1045         Window *w;
1046
1047         w = pointto(TRUE);
1048         if(w)
1049                 wsendctlmesg(w, Deleted, ZR, nil);
1050 }
1051
1052 void
1053 resize(void)
1054 {
1055         Window *w;
1056         Image *i;
1057
1058         w = pointto(TRUE);
1059         if(w == nil)
1060                 return;
1061         incref(w);
1062         i = sweep();
1063         if(i)
1064                 wsendctlmesg(w, Reshaped, i->r, i);
1065         wclose(w);
1066 }
1067
1068 void
1069 move(void)
1070 {
1071         Window *w;
1072         Image *i;
1073
1074         w = pointto(FALSE);
1075         if(w == nil)
1076                 return;
1077         incref(w);
1078         i = drag(w);
1079         if(i)
1080                 wsendctlmesg(w, Reshaped, i->r, i);
1081         wclose(w);
1082 }
1083
1084 int
1085 whide(Window *w)
1086 {
1087         Image *i;
1088         int j;
1089
1090         for(j=0; j<nhidden; j++)
1091                 if(hidden[j] == w)      /* already hidden */
1092                         return -1;
1093         if(nhidden >= nelem(hidden))
1094                 return 0;
1095         incref(w);
1096         i = allocimage(display, w->screenr, w->i->chan, 0, DNofill);
1097         if(i){
1098                 hidden[nhidden++] = w;
1099                 wsendctlmesg(w, Reshaped, ZR, i);
1100         }
1101         wclose(w);
1102         return i!=0;
1103 }
1104
1105 int
1106 wunhide(Window *w)
1107 {
1108         int j;
1109         Image *i;
1110
1111         for(j=0; j<nhidden; j++)
1112                 if(hidden[j] == w)
1113                         break;
1114         if(j == nhidden)
1115                 return -1;      /* not hidden */
1116         incref(w);
1117         i = allocwindow(wscreen, w->i->r, Refbackup, DNofill);
1118         if(i){
1119                 --nhidden;
1120                 memmove(hidden+j, hidden+j+1, (nhidden-j)*sizeof(Window*));
1121                 wsendctlmesg(w, Reshaped, w->i->r, i);
1122         }
1123         wclose(w);
1124         return i!=0;
1125 }
1126
1127 void
1128 hide(void)
1129 {
1130         Window *w;
1131
1132         w = pointto(TRUE);
1133         if(w)
1134                 whide(w);
1135 }
1136
1137 void
1138 unhide(int j)
1139 {
1140         Window *w;
1141
1142         if(j < Hidden)
1143                 return;
1144         j -= Hidden;
1145         w = hidden[j];
1146         if(w == nil)
1147                 return;
1148         if(j < nhidden){
1149                 wunhide(w);
1150                 return;
1151         }
1152         /* uncover obscured window */
1153         for(j=0; j<nwindow; j++)
1154                 if(window[j] == w){
1155                         incref(w);
1156                         wtopme(w);
1157                         wcurrent(w);
1158                         wclose(w);
1159                         return;
1160                 }
1161 }
1162
1163 Window*
1164 new(Image *i, int hideit, int scrollit, int pid, char *dir, char *cmd, char **argv)
1165 {
1166         Window *w;
1167         Mousectl *mc;
1168         Channel *cm, *ck, *cctl, *cpid;
1169         void **arg;
1170
1171         if(i == nil)
1172                 return nil;
1173         if(hideit && nhidden >= nelem(hidden)){
1174                 freeimage(i);
1175                 return nil;
1176         }
1177         cm = chancreate(sizeof(Mouse), 0);
1178         ck = chancreate(sizeof(char*), 0);
1179         cctl = chancreate(sizeof(Wctlmesg), 4);
1180         cpid = chancreate(sizeof(int), 0);
1181         if(cm==nil || ck==nil || cctl==nil)
1182                 error("new: channel alloc failed");
1183         mc = emalloc(sizeof(Mousectl));
1184         *mc = *mousectl;
1185         mc->image = i;
1186         mc->c = cm;
1187         w = wmk(i, mc, ck, cctl, scrollit);
1188         free(mc);       /* wmk copies *mc */
1189         window = erealloc(window, ++nwindow*sizeof(Window*));
1190         window[nwindow-1] = w;
1191         if(hideit){
1192                 hidden[nhidden++] = w;
1193                 w->screenr = ZR;
1194         }
1195         threadcreate(winctl, w, 8192);
1196         if(!hideit)
1197                 wcurrent(w);
1198         if(pid == 0){
1199                 arg = emalloc(5*sizeof(void*));
1200                 arg[0] = w;
1201                 arg[1] = cpid;
1202                 arg[2] = cmd;
1203                 if(argv == nil)
1204                         arg[3] = rcargv;
1205                 else
1206                         arg[3] = argv;
1207                 arg[4] = dir;
1208                 proccreate(winshell, arg, 8192);
1209                 pid = recvul(cpid);
1210                 free(arg);
1211         }
1212         if(pid == 0){
1213                 /* window creation failed */
1214                 wsendctlmesg(w, Deleted, ZR, nil);
1215                 chanfree(cpid);
1216                 return nil;
1217         }
1218         wsetpid(w, pid, 1);
1219         wsetname(w);
1220         if(dir){
1221                 free(w->dir);
1222                 w->dir = estrdup(dir);
1223         }
1224         chanfree(cpid);
1225         return w;
1226 }
1227
1228 static void
1229 kbdproc(void *arg)
1230 {
1231         Channel *c = arg;
1232         char buf[128], *p, *e;
1233         int fd, cfd, kfd, n;
1234
1235         threadsetname("kbdproc");
1236
1237         if((fd = open("/dev/cons", OREAD)) < 0){
1238                 chanprint(c, "%r");
1239                 return;
1240         }
1241         if((cfd = open("/dev/consctl", OWRITE)) < 0){
1242                 chanprint(c, "%r");
1243                 return;
1244         }
1245         fprint(cfd, "rawon");
1246
1247         if(sendp(c, nil) <= 0)
1248                 return;
1249
1250         if((kfd = open("/dev/kbd", OREAD)) >= 0){
1251                 close(fd);
1252
1253                 /* only serve a kbd file per window when we got one */
1254                 servekbd = 1;
1255
1256                 /* read kbd state */
1257                 while((n = read(kfd, buf, sizeof(buf)-1)) > 0){
1258                         e = buf+n;
1259                         e[-1] = 0;
1260                         e[0] = 0;
1261                         for(p = buf; p < e; p += strlen(p)+1)
1262                                 chanprint(c, "%s", p);
1263                 }
1264         } else {
1265                 /* read single characters */
1266                 p = buf;
1267                 for(;;){
1268                         Rune r;
1269
1270                         e = buf + sizeof(buf);
1271                         if((n = read(fd, p, e-p)) <= 0)
1272                                 break;
1273                         e = p + n;
1274                         while(p < e && fullrune(p, e - p)){
1275                                 p += chartorune(&r, p);
1276                                 if(r)
1277                                         chanprint(c, "c%C", r);
1278                         }
1279                         n = e - p;
1280                         memmove(buf, p, n);
1281                         p = buf + n;
1282                 }
1283         }
1284         send(exitchan, nil);
1285 }
1286
1287 Channel*
1288 initkbd(void)
1289 {
1290         Channel *c;
1291         char *e;
1292
1293         c = chancreate(sizeof(char*), 16);
1294         procrfork(kbdproc, c, STACK, RFCFDG);
1295         if(e = recvp(c)){
1296                 chanfree(c);
1297                 c = nil;
1298                 werrstr("%s", e);
1299                 free(e);
1300         }
1301         return c;
1302 }