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