]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/rio/rio.c
rio: make sure flush replies are send only *after* the request got flushed or was...
[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.max.x = r.min.x+300;
227                         r.max.y = r.min.y+80;
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 *sn, buf[1024];
270
271         if(snarffd < 0)
272                 return;
273         sn = nil;
274         i = 0;
275         seek(snarffd, 0, 0);
276         while((n = read(snarffd, buf, sizeof buf)) > 0){
277                 sn = erealloc(sn, i+n+1);
278                 memmove(sn+i, buf, n);
279                 i += n;
280                 sn[i] = 0;
281         }
282         if(i > 0){
283                 snarf = runerealloc(snarf, i+1);
284                 cvttorunes(sn, i, snarf, &nb, &nsnarf, &nulls);
285                 free(sn);
286         }
287 }
288
289 void
290 initcmd(void *arg)
291 {
292         char *cmd;
293
294         cmd = arg;
295         rfork(RFENVG|RFFDG|RFNOTEG|RFNAMEG);
296         procexecl(nil, "/bin/rc", "rc", "-c", cmd, nil);
297         fprint(2, "rio: exec failed: %r\n");
298         exits("exec");
299 }
300
301 char *oknotes[] =
302 {
303         "delete",
304         "hangup",
305         "kill",
306         "exit",
307         nil
308 };
309
310 int
311 shutdown(void *, char *msg)
312 {
313         int i;
314         static Lock shutdownlk;
315         
316         killprocs();
317         for(i=0; oknotes[i]; i++)
318                 if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0){
319                         lock(&shutdownlk);      /* only one can threadexitsall */
320                         threadexitsall(msg);
321                 }
322         fprint(2, "rio %d: abort: %s\n", getpid(), msg);
323         abort();
324         exits(msg);
325         return 0;
326 }
327
328 void
329 killprocs(void)
330 {
331         int i;
332
333         for(i=0; i<nwindow; i++)
334                 if(window[i]->notefd >= 0)
335                         write(window[i]->notefd, "hangup", 6); 
336 }
337
338 void
339 keyboardthread(void*)
340 {
341         char *s;
342
343         threadsetname("keyboardthread");
344
345         while(s = recvp(kbdchan)){
346                 if(*s == 'k' || *s == 'K')
347                         shiftdown = utfrune(s+1, Kshift) != nil;
348                 if(input == nil || sendp(input->ck, s) <= 0)
349                         free(s);
350         }
351 }
352
353 /*
354  * Used by /dev/kbdin
355  */
356 void
357 keyboardsend(char *s, int cnt)
358 {
359         if(cnt <= 0)
360                 return;
361         if(s[cnt-1] == 0)
362                 chanprint(kbdchan, "%s", s);
363         else {
364                 Rune *r;
365                 int i, nb, nr;
366
367                 r = runemalloc(cnt);
368                 cvttorunes(s, cnt, r, &nb, &nr, nil);
369                 for(i=0; i<nr; i++){
370                         if(r[i])
371                                 chanprint(kbdchan, "c%C", r[i]);
372                 }
373                 free(r);
374         }
375 }
376
377 int
378 portion(int x, int lo, int hi)
379 {
380         x -= lo;
381         hi -= lo;
382         if(x < 20)
383                 return 0;
384         if(x > hi-20)
385                 return 2;
386         return 1;
387 }
388
389 int
390 whichcorner(Window *w, Point p)
391 {
392         int i, j;
393         
394         i = portion(p.x, w->screenr.min.x, w->screenr.max.x);
395         j = portion(p.y, w->screenr.min.y, w->screenr.max.y);
396         return 3*j+i;
397 }
398
399 void
400 cornercursor(Window *w, Point p, int force)
401 {
402         if(w!=nil && winborder(w, p))
403                 riosetcursor(corners[whichcorner(w, p)], force);
404         else
405                 wsetcursor(w, force);
406 }
407
408 /* thread to allow fsysproc to synchronize window closing with main proc */
409 void
410 winclosethread(void*)
411 {
412         Window *w;
413
414         threadsetname("winclosethread");
415         for(;;){
416                 w = recvp(winclosechan);
417                 wclose(w);
418         }
419 }
420
421 /* thread to make Deleted windows that the client still holds disappear offscreen after an interval */
422 void
423 deletethread(void*)
424 {
425         char *s;
426         Image *i;
427
428         threadsetname("deletethread");
429         for(;;){
430                 s = recvp(deletechan);
431                 i = namedimage(display, s);
432                 if(i != nil){
433                         /* move it off-screen to hide it, since client is slow in letting it go */
434                         originwindow(i, i->r.min, view->r.max);
435                 }
436                 freeimage(i);
437                 free(s);
438         }
439 }
440
441 void
442 deletetimeoutproc(void *v)
443 {
444         char *s;
445
446         s = v;
447         sleep(750);     /* remove window from screen after 3/4 of a second */
448         sendp(deletechan, s);
449 }
450
451 /*
452  * Button 6 - keyboard toggle - has been pressed.
453  * Send event to keyboard, wait for button up, send that.
454  * Note: there is no coordinate translation done here; this
455  * is just about getting button 6 to the keyboard simulator.
456  */
457 void
458 keyboardhide(void)
459 {
460         send(wkeyboard->mc.c, mouse);
461         do
462                 readmouse(mousectl);
463         while(mouse->buttons & (1<<5));
464         send(wkeyboard->mc.c, mouse);
465 }
466
467 void
468 mousethread(void*)
469 {
470         int sending, inside, scrolling, moving, band;
471         Window *w, *winput;
472         Image *i;
473         Rectangle r;
474         Point xy;
475         Mouse tmp;
476         enum {
477                 MReshape,
478                 MMouse,
479                 NALT
480         };
481         static Alt alts[NALT+1];
482
483         threadsetname("mousethread");
484         sending = FALSE;
485         scrolling = FALSE;
486         moving = FALSE;
487
488         alts[MReshape].c = mousectl->resizec;
489         alts[MReshape].v = nil;
490         alts[MReshape].op = CHANRCV;
491         alts[MMouse].c = mousectl->c;
492         alts[MMouse].v = &mousectl->Mouse;
493         alts[MMouse].op = CHANRCV;
494         alts[NALT].op = CHANEND;
495
496         for(;;)
497             switch(alt(alts)){
498                 case MReshape:
499                         resized();
500                         break;
501                 case MMouse:
502                         if(wkeyboard!=nil && (mouse->buttons & (1<<5))){
503                                 keyboardhide();
504                                 break;
505                         }
506                 Again:
507                         winput = input;
508                         /* override everything for the keyboard window */
509                         if(wkeyboard!=nil && ptinrect(mouse->xy, wkeyboard->screenr)){
510                                 /* make sure it's on top; this call is free if it is */
511                                 wtopme(wkeyboard);
512                                 winput = wkeyboard;
513                         }
514                         if(winput!=nil && !winput->deleted && winput->i!=nil){
515                                 /* convert to logical coordinates */
516                                 xy.x = mouse->xy.x + (winput->i->r.min.x-winput->screenr.min.x);
517                                 xy.y = mouse->xy.y + (winput->i->r.min.y-winput->screenr.min.y);
518
519                                 /* the up and down scroll buttons are not subject to the usual rules */
520                                 if((mouse->buttons&(8|16)) && !winput->mouseopen)
521                                         goto Sending;
522
523                                 inside = ptinrect(mouse->xy, insetrect(winput->screenr, Selborder));
524                                 if(winput->mouseopen)
525                                         scrolling = FALSE;
526                                 else if(scrolling)
527                                         scrolling = mouse->buttons;
528                                 else
529                                         scrolling = mouse->buttons && ptinrect(xy, winput->scrollr);
530                                 /* topped will be zero or less if window has been bottomed */
531                                 if(sending == FALSE && !scrolling && winborder(winput, mouse->xy) && winput->topped>0){
532                                         moving = TRUE;
533                                 }else if(inside && (scrolling || winput->mouseopen || (mouse->buttons&1)))
534                                         sending = TRUE;
535                         }else
536                                 sending = FALSE;
537                         if(sending){
538                         Sending:
539                                 if(mouse->buttons == 0){
540                                         cornercursor(winput, mouse->xy, 0);
541                                         sending = FALSE;
542                                 }else
543                                         wsetcursor(winput, 0);
544                                 tmp = mousectl->Mouse;
545                                 tmp.xy = xy;
546                                 send(winput->mc.c, &tmp);
547                                 continue;
548                         }
549                         w = wpointto(mouse->xy);
550                         /* change cursor if over anyone's border */
551                         if(w != nil)
552                                 cornercursor(w, mouse->xy, 0);
553                         else
554                                 riosetcursor(nil, 0);
555                         if(moving && (mouse->buttons&7)){
556                                 incref(winput);
557                                 band = mouse->buttons & 3;
558                                 sweeping = 1;
559                                 if(band)
560                                         i = bandsize(winput);
561                                 else
562                                         i = drag(winput, &r);
563                                 sweeping = 0;
564                                 if(i != nil){
565                                         if(band)
566                                                 wsendctlmesg(winput, Reshaped, i->r, i);
567                                         else
568                                                 wsendctlmesg(winput, Moved, r, i);
569                                         cornercursor(winput, mouse->xy, 1);
570                                 }
571                                 if(wclose(winput) == 0)
572                                         w = winput;
573                                 else {
574                                         riosetcursor(nil, 0);
575                                         w = nil;
576                                 }
577                         }
578                         /* we're not sending the event, but if button is down maybe we should */
579                         if(mouse->buttons){
580                                 /* w->topped will be zero or less if window has been bottomed */
581                                 if(w==nil || (w==winput && w->topped>0)){
582                                         if(mouse->buttons & 1){
583                                                 ;
584                                         }else if(mouse->buttons & 2){
585                                                 if(winput && !winput->deleted && !winput->mouseopen){
586                                                         incref(winput);
587                                                         button2menu(winput);
588                                                         wclose(winput);
589                                                 }
590                                         }else if(mouse->buttons & 4)
591                                                 button3menu();
592                                 }else{
593                                         /* if button 1 event in the window, top the window and wait for button up. */
594                                         /* otherwise, top the window and pass the event on */
595                                         if(wtop(mouse->xy) && (mouse->buttons!=1 || winborder(w, mouse->xy)))
596                                                 goto Again;
597                                         goto Drain;
598                                 }
599                         }
600                         moving = FALSE;
601                         break;
602
603                 Drain:
604                         do
605                                 readmouse(mousectl);
606                         while(mousectl->buttons);
607                         moving = FALSE;
608                         goto Again;     /* recalculate mouse position, cursor */
609                 }
610 }
611
612 int
613 wtopcmp(void *a, void *b)
614 {
615         return (*(Window**)a)->topped - (*(Window**)b)->topped;
616 }
617
618 void
619 resized(void)
620 {
621         Image *im;
622         int i, j;
623         Rectangle r;
624         Point o, n;
625         Window *w;
626
627         if(getwindow(display, Refnone) < 0)
628                 error("failed to re-attach window");
629         freescrtemps();
630         view = screen;
631         freescreen(wscreen);
632         wscreen = allocscreen(screen, background, 0);
633         if(wscreen == nil)
634                 error("can't re-allocate screen");
635         draw(view, view->r, background, nil, ZP);
636         o = subpt(viewr.max, viewr.min);
637         n = subpt(view->clipr.max, view->clipr.min);
638         qsort(window, nwindow, sizeof(window[0]), wtopcmp);
639         for(i=0; i<nwindow; i++){
640                 w = window[i];
641                 r = rectsubpt(w->i->r, viewr.min);
642                 r.min.x = (r.min.x*n.x)/o.x;
643                 r.min.y = (r.min.y*n.y)/o.y;
644                 r.max.x = (r.max.x*n.x)/o.x;
645                 r.max.y = (r.max.y*n.y)/o.y;
646                 if(!goodrect(r))
647                         r = rectsubpt(w->i->r, viewr.min);
648                 r = rectaddpt(r, screen->clipr.min);
649                 for(j=0; j<nhidden; j++)
650                         if(w == hidden[j])
651                                 break;
652                 incref(w);
653                 if(j < nhidden){
654                         im = allocimage(display, r, screen->chan, 0, DNofill);
655                         r = ZR;
656                 } else
657                         im = allocwindow(wscreen, r, Refbackup, DNofill);
658                 if(im)
659                         wsendctlmesg(w, Reshaped, r, im);
660                 wclose(w);
661         }
662         viewr = screen->r;
663         flushimage(display, 1);
664 }
665
666 int
667 obscured(Window *w, Rectangle r, int i)
668 {
669         Window *t;
670
671         if(Dx(r) < font->height || Dy(r) < font->height)
672                 return 1;
673         if(!rectclip(&r, screen->r))
674                 return 1;
675         for(; i<nwindow; i++){
676                 t = window[i];
677                 if(t == w || t->topped <= w->topped)
678                         continue;
679                 if(Dx(t->screenr) == 0 || Dy(t->screenr) == 0 || rectXrect(r, t->screenr) == 0)
680                         continue;
681                 if(r.min.y < t->screenr.min.y)
682                         if(!obscured(w, Rect(r.min.x, r.min.y, r.max.x, t->screenr.min.y), i))
683                                 return 0;
684                 if(r.min.x < t->screenr.min.x)
685                         if(!obscured(w, Rect(r.min.x, r.min.y, t->screenr.min.x, r.max.y), i))
686                                 return 0;
687                 if(r.max.y > t->screenr.max.y)
688                         if(!obscured(w, Rect(r.min.x, t->screenr.max.y, r.max.x, r.max.y), i))
689                                 return 0;
690                 if(r.max.x > t->screenr.max.x)
691                         if(!obscured(w, Rect(t->screenr.max.x, r.min.y, r.max.x, r.max.y), i))
692                                 return 0;
693                 return 1;
694         }
695         return 0;
696 }
697
698 void
699 button3menu(void)
700 {
701         int i, j, n;
702
703         n = nhidden;
704         for(i=0; i<nwindow; i++){
705                 for(j=0; j<n; j++)
706                         if(window[i] == hidden[j])
707                                 break;
708                 if(j == n)
709                         if(obscured(window[i], window[i]->screenr, 0)){
710                                 hidden[n++] = window[i];
711                                 if(n >= nelem(hidden))
712                                         break;
713                         }
714         }
715         if(n >= nelem(menu3str)-Hidden)
716                 n = nelem(menu3str)-Hidden-1;
717         for(i=0; i<n; i++){
718                 free(menu3str[i+Hidden]);
719                 menu3str[i+Hidden] = estrdup(hidden[i]->label);
720         }
721         for(i+=Hidden; menu3str[i]; i++){
722                 free(menu3str[i]);
723                 menu3str[i] = nil;
724         }
725         sweeping = 1;
726         switch(i = menuhit(3, mousectl, &menu3, wscreen)){
727         case -1:
728                 break;
729         case New:
730                 new(sweep(), FALSE, scrolling, 0, nil, "/bin/rc", nil);
731                 break;
732         case Reshape:
733                 resize();
734                 break;
735         case Move:
736                 move();
737                 break;
738         case Delete:
739                 delete();
740                 break;
741         case Hide:
742                 hide();
743                 break;
744         case Exit:
745                 if(Hidden > Exit){
746                         send(exitchan, nil);
747                         break;
748                 }
749                 /* else fall through */
750         default:
751                 unhide(i);
752                 break;
753         }
754         sweeping = 0;
755 }
756
757 void
758 button2menu(Window *w)
759 {
760         if(w->scrolling)
761                 menu2str[Scroll] = "noscroll";
762         else
763                 menu2str[Scroll] = "scroll";
764         switch(menuhit(2, mousectl, &menu2, wscreen)){
765         case Cut:
766                 wsnarf(w);
767                 wcut(w);
768                 wscrdraw(w);
769                 break;
770
771         case Snarf:
772                 wsnarf(w);
773                 break;
774
775         case Paste:
776                 getsnarf();
777                 wpaste(w);
778                 wscrdraw(w);
779                 break;
780
781         case Plumb:
782                 wplumb(w);
783                 break;
784
785         case Send:
786                 getsnarf();
787                 wsnarf(w);
788                 if(nsnarf == 0)
789                         break;
790                 if(w->rawing){
791                         waddraw(w, snarf, nsnarf);
792                         if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
793                                 waddraw(w, L"\n", 1);
794                 }else{
795                         winsert(w, snarf, nsnarf, w->nr);
796                         if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
797                                 winsert(w, L"\n", 1, w->nr);
798                 }
799                 wsetselect(w, w->nr, w->nr);
800                 wshow(w, w->nr);
801                 break;
802
803         case Scroll:
804                 if(w->scrolling ^= 1)
805                         wshow(w, w->nr);
806                 break;
807         }
808         wsendctlmesg(w, Wakeup, ZR, nil);
809         flushimage(display, 1);
810 }
811
812 Point
813 onscreen(Point p)
814 {
815         p.x = max(screen->clipr.min.x, p.x);
816         p.x = min(screen->clipr.max.x, p.x);
817         p.y = max(screen->clipr.min.y, p.y);
818         p.y = min(screen->clipr.max.y, p.y);
819         return p;
820 }
821
822 Image*
823 sweep(void)
824 {
825         Image *i, *oi;
826         Rectangle r;
827         Point p0, p;
828
829         i = nil;
830         menuing = TRUE;
831         riosetcursor(&crosscursor, 1);
832         while(mouse->buttons == 0)
833                 readmouse(mousectl);
834         p0 = onscreen(mouse->xy);
835         p = p0;
836         r.min = p;
837         r.max = p;
838         oi = nil;
839         while(mouse->buttons == 4){
840                 readmouse(mousectl);
841                 if(mouse->buttons != 4 && mouse->buttons != 0)
842                         break;
843                 if(!eqpt(mouse->xy, p)){
844                         p = onscreen(mouse->xy);
845                         r = canonrect(Rpt(p0, p));
846                         if(Dx(r)>5 && Dy(r)>5){
847                                 i = allocwindow(wscreen, r, Refnone, DNofill);
848                                 freeimage(oi);
849                                 if(i == nil)
850                                         goto Rescue;
851                                 oi = i;
852                                 border(i, r, Selborder, sizecol, ZP);
853                                 draw(i, insetrect(r, Selborder), cols[BACK], nil, ZP);
854                                 flushimage(display, 1);
855                         }
856                 }
857         }
858         if(mouse->buttons != 0)
859                 goto Rescue;
860         if(i==nil || Dx(i->r)<100 || Dy(i->r)<3*font->height)
861                 goto Rescue;
862         oi = i;
863         i = allocwindow(wscreen, oi->r, Refbackup, DNofill);
864         freeimage(oi);
865         if(i == nil)
866                 goto Rescue;
867         cornercursor(input, mouse->xy, 1);
868         goto Return;
869
870  Rescue:
871         freeimage(i);
872         i = nil;
873         cornercursor(input, mouse->xy, 1);
874         while(mouse->buttons)
875                 readmouse(mousectl);
876
877  Return:
878         moveto(mousectl, mouse->xy);    /* force cursor update; ugly */
879         menuing = FALSE;
880         return i;
881 }
882
883 void
884 drawedge(Image **bp, Rectangle r)
885 {
886         Image *b = *bp;
887         if(b != nil && Dx(b->r) == Dx(r) && Dy(b->r) == Dy(r))
888                 originwindow(b, r.min, r.min);
889         else{
890                 freeimage(b);
891                 b = allocwindow(wscreen, r, Refbackup, DNofill);
892                 if(b != nil) draw(b, r, sizecol, nil, ZP);
893                 *bp = b;
894         }
895 }
896
897 void
898 drawborder(Rectangle r, int show)
899 {
900         static Image *b[4];
901         int i;
902         if(show == 0){
903                 for(i = 0; i < 4; i++){
904                         freeimage(b[i]);
905                         b[i] = nil;
906                 }
907         }else{
908                 r = canonrect(r);
909                 drawedge(&b[0], Rect(r.min.x, r.min.y, r.min.x+Borderwidth, r.max.y));
910                 drawedge(&b[1], Rect(r.min.x+Borderwidth, r.min.y, r.max.x-Borderwidth, r.min.y+Borderwidth));
911                 drawedge(&b[2], Rect(r.max.x-Borderwidth, r.min.y, r.max.x, r.max.y));
912                 drawedge(&b[3], Rect(r.min.x+Borderwidth, r.max.y-Borderwidth, r.max.x-Borderwidth, r.max.y));
913         }
914 }
915
916 Image*
917 drag(Window *w, Rectangle *rp)
918 {
919         Image *i, *ni;
920         Point p, op, d, dm, om;
921         Rectangle r;
922
923         i = w->i;
924         menuing = TRUE;
925         om = mouse->xy;
926         riosetcursor(&boxcursor, 1);
927         dm = subpt(mouse->xy, w->screenr.min);
928         d = subpt(i->r.max, i->r.min);
929         op = subpt(mouse->xy, dm);
930         drawborder(Rect(op.x, op.y, op.x+d.x, op.y+d.y), 1);
931         flushimage(display, 1);
932         while(mouse->buttons == 4){
933                 p = subpt(mouse->xy, dm);
934                 if(!eqpt(p, op)){
935                         drawborder(Rect(p.x, p.y, p.x+d.x, p.y+d.y), 1);
936                         flushimage(display, 1);
937                         op = p;
938                 }
939                 readmouse(mousectl);
940         }
941         r = Rect(op.x, op.y, op.x+d.x, op.y+d.y);
942         drawborder(r, 0);
943         cornercursor(w, mouse->xy, 1);
944         moveto(mousectl, mouse->xy);    /* force cursor update; ugly */
945         menuing = FALSE;
946         flushimage(display, 1);
947         if(mouse->buttons!=0 || (ni=allocwindow(wscreen, r, Refbackup, DNofill))==nil){
948                 moveto(mousectl, om);
949                 while(mouse->buttons)
950                         readmouse(mousectl);
951                 *rp = Rect(0, 0, 0, 0);
952                 return nil;
953         }
954         draw(ni, ni->r, i, nil, i->r.min);
955         *rp = r;
956         return ni;
957 }
958
959 Point
960 cornerpt(Rectangle r, Point p, int which)
961 {
962         switch(which){
963         case 0: /* top left */
964                 p = Pt(r.min.x, r.min.y);
965                 break;
966         case 2: /* top right */
967                 p = Pt(r.max.x,r.min.y);
968                 break;
969         case 6: /* bottom left */
970                 p = Pt(r.min.x, r.max.y);
971                 break;
972         case 8: /* bottom right */
973                 p = Pt(r.max.x, r.max.y);
974                 break;
975         case 1: /* top edge */
976                 p = Pt(p.x,r.min.y);
977                 break;
978         case 5: /* right edge */
979                 p = Pt(r.max.x, p.y);
980                 break;
981         case 7: /* bottom edge */
982                 p = Pt(p.x, r.max.y);
983                 break;
984         case 3:         /* left edge */
985                 p = Pt(r.min.x, p.y);
986                 break;
987         }
988         return p;
989 }
990
991 Rectangle
992 whichrect(Rectangle r, Point p, int which)
993 {
994         switch(which){
995         case 0: /* top left */
996                 r = Rect(p.x, p.y, r.max.x, r.max.y);
997                 break;
998         case 2: /* top right */
999                 r = Rect(r.min.x, p.y, p.x, r.max.y);
1000                 break;
1001         case 6: /* bottom left */
1002                 r = Rect(p.x, r.min.y, r.max.x, p.y);
1003                 break;
1004         case 8: /* bottom right */
1005                 r = Rect(r.min.x, r.min.y, p.x, p.y);
1006                 break;
1007         case 1: /* top edge */
1008                 r = Rect(r.min.x, p.y, r.max.x, r.max.y);
1009                 break;
1010         case 5: /* right edge */
1011                 r = Rect(r.min.x, r.min.y, p.x, r.max.y);
1012                 break;
1013         case 7: /* bottom edge */
1014                 r = Rect(r.min.x, r.min.y, r.max.x, p.y);
1015                 break;
1016         case 3:         /* left edge */
1017                 r = Rect(p.x, r.min.y, r.max.x, r.max.y);
1018                 break;
1019         }
1020         return canonrect(r);
1021 }
1022
1023 Image*
1024 bandsize(Window *w)
1025 {
1026         Rectangle r, or;
1027         Point p, startp;
1028         int which, but;
1029
1030         p = mouse->xy;
1031         but = mouse->buttons;
1032         which = whichcorner(w, p);
1033         p = cornerpt(w->screenr, p, which);
1034         wmovemouse(w, p);
1035         readmouse(mousectl);
1036         r = whichrect(w->screenr, p, which);
1037         drawborder(r, 1);
1038         or = r;
1039         startp = p;
1040         
1041         while(mouse->buttons == but){
1042                 p = onscreen(mouse->xy);
1043                 r = whichrect(w->screenr, p, which);
1044                 if(!eqrect(r, or) && goodrect(r)){
1045                         drawborder(r, 1);
1046                         flushimage(display, 1);
1047                         or = r;
1048                 }
1049                 readmouse(mousectl);
1050         }
1051         p = mouse->xy;
1052         drawborder(or, 0);
1053         flushimage(display, 1);
1054         wsetcursor(w, 1);
1055         if(mouse->buttons!=0 || Dx(or)<100 || Dy(or)<3*font->height){
1056                 while(mouse->buttons)
1057                         readmouse(mousectl);
1058                 return nil;
1059         }
1060         if(abs(p.x-startp.x)+abs(p.y-startp.y) <= 1)
1061                 return nil;
1062         return allocwindow(wscreen, or, Refbackup, DNofill);
1063 }
1064
1065 Window*
1066 pointto(int wait)
1067 {
1068         Window *w;
1069
1070         menuing = TRUE;
1071         riosetcursor(&sightcursor, 1);
1072         while(mouse->buttons == 0)
1073                 readmouse(mousectl);
1074         if(mouse->buttons == 4)
1075                 w = wpointto(mouse->xy);
1076         else
1077                 w = nil;
1078         if(wait){
1079                 while(mouse->buttons){
1080                         if(mouse->buttons!=4 && w !=nil){       /* cancel */
1081                                 cornercursor(input, mouse->xy, 0);
1082                                 w = nil;
1083                         }
1084                         readmouse(mousectl);
1085                 }
1086                 if(w != nil && wpointto(mouse->xy) != w)
1087                         w = nil;
1088         }
1089         cornercursor(input, mouse->xy, 0);
1090         moveto(mousectl, mouse->xy);    /* force cursor update; ugly */
1091         menuing = FALSE;
1092         return w;
1093 }
1094
1095 void
1096 delete(void)
1097 {
1098         Window *w;
1099
1100         w = pointto(TRUE);
1101         if(w)
1102                 wsendctlmesg(w, Deleted, ZR, nil);
1103 }
1104
1105 void
1106 resize(void)
1107 {
1108         Window *w;
1109         Image *i;
1110
1111         w = pointto(TRUE);
1112         if(w == nil)
1113                 return;
1114         incref(w);
1115         i = sweep();
1116         if(i)
1117                 wsendctlmesg(w, Reshaped, i->r, i);
1118         wclose(w);
1119 }
1120
1121 void
1122 move(void)
1123 {
1124         Window *w;
1125         Image *i;
1126         Rectangle r;
1127
1128         w = pointto(FALSE);
1129         if(w == nil)
1130                 return;
1131         incref(w);
1132         i = drag(w, &r);
1133         if(i)
1134                 wsendctlmesg(w, Moved, r, i);
1135         cornercursor(w, mouse->xy, 1);
1136         wclose(w);
1137 }
1138
1139 int
1140 whide(Window *w)
1141 {
1142         Image *i;
1143         int j;
1144
1145         for(j=0; j<nhidden; j++)
1146                 if(hidden[j] == w)      /* already hidden */
1147                         return -1;
1148         if(nhidden >= nelem(hidden))
1149                 return 0;
1150         incref(w);
1151         i = allocimage(display, w->screenr, w->i->chan, 0, DNofill);
1152         if(i){
1153                 hidden[nhidden++] = w;
1154                 wsendctlmesg(w, Reshaped, ZR, i);
1155         }
1156         wclose(w);
1157         return i!=0;
1158 }
1159
1160 int
1161 wunhide(Window *w)
1162 {
1163         int j;
1164         Image *i;
1165
1166         for(j=0; j<nhidden; j++)
1167                 if(hidden[j] == w)
1168                         break;
1169         if(j == nhidden)
1170                 return -1;      /* not hidden */
1171         incref(w);
1172         i = allocwindow(wscreen, w->i->r, Refbackup, DNofill);
1173         if(i){
1174                 --nhidden;
1175                 memmove(hidden+j, hidden+j+1, (nhidden-j)*sizeof(Window*));
1176                 wsendctlmesg(w, Reshaped, w->i->r, i);
1177         }
1178         wclose(w);
1179         return i!=0;
1180 }
1181
1182 void
1183 hide(void)
1184 {
1185         Window *w;
1186
1187         w = pointto(TRUE);
1188         if(w)
1189                 whide(w);
1190 }
1191
1192 void
1193 unhide(int j)
1194 {
1195         Window *w;
1196
1197         if(j < Hidden)
1198                 return;
1199         j -= Hidden;
1200         w = hidden[j];
1201         if(w == nil)
1202                 return;
1203         if(j < nhidden){
1204                 wunhide(w);
1205                 return;
1206         }
1207         /* uncover obscured window */
1208         for(j=0; j<nwindow; j++)
1209                 if(window[j] == w){
1210                         incref(w);
1211                         wtopme(w);
1212                         wcurrent(w);
1213                         wclose(w);
1214                         return;
1215                 }
1216 }
1217
1218 Window*
1219 new(Image *i, int hideit, int scrollit, int pid, char *dir, char *cmd, char **argv)
1220 {
1221         Window *w;
1222         Mousectl *mc;
1223         Channel *cm, *ck, *cctl, *cpid;
1224         void **arg;
1225
1226         if(i == nil)
1227                 return nil;
1228         if(hideit && nhidden >= nelem(hidden)){
1229                 freeimage(i);
1230                 return nil;
1231         }
1232         cm = chancreate(sizeof(Mouse), 0);
1233         ck = chancreate(sizeof(char*), 0);
1234         cctl = chancreate(sizeof(Wctlmesg), 4);
1235         cpid = chancreate(sizeof(int), 0);
1236         if(cm==nil || ck==nil || cctl==nil)
1237                 error("new: channel alloc failed");
1238         mc = emalloc(sizeof(Mousectl));
1239         *mc = *mousectl;
1240         mc->image = i;
1241         mc->c = cm;
1242         w = wmk(i, mc, ck, cctl, scrollit);
1243         free(mc);       /* wmk copies *mc */
1244         window = erealloc(window, ++nwindow*sizeof(Window*));
1245         window[nwindow-1] = w;
1246         if(hideit){
1247                 hidden[nhidden++] = w;
1248                 w->screenr = ZR;
1249         }
1250         threadcreate(winctl, w, 8192);
1251         if(!hideit)
1252                 wcurrent(w);
1253         if(pid == 0){
1254                 arg = emalloc(5*sizeof(void*));
1255                 arg[0] = w;
1256                 arg[1] = cpid;
1257                 arg[2] = cmd;
1258                 if(argv == nil)
1259                         arg[3] = rcargv;
1260                 else
1261                         arg[3] = argv;
1262                 arg[4] = dir;
1263                 proccreate(winshell, arg, 8192);
1264                 pid = recvul(cpid);
1265                 free(arg);
1266         }
1267         if(pid == 0){
1268                 /* window creation failed */
1269                 wsendctlmesg(w, Deleted, ZR, nil);
1270                 chanfree(cpid);
1271                 return nil;
1272         }
1273         wsetpid(w, pid, 1);
1274         wsetname(w);
1275         if(dir){
1276                 free(w->dir);
1277                 w->dir = estrdup(dir);
1278         }
1279         chanfree(cpid);
1280         return w;
1281 }
1282
1283 static void
1284 kbdproc(void *arg)
1285 {
1286         Channel *c = arg;
1287         char buf[128], *p, *e;
1288         int fd, cfd, kfd, n;
1289
1290         threadsetname("kbdproc");
1291
1292         if((fd = open("/dev/cons", OREAD)) < 0){
1293                 chanprint(c, "%r");
1294                 return;
1295         }
1296         if((cfd = open("/dev/consctl", OWRITE)) < 0){
1297                 chanprint(c, "%r");
1298                 return;
1299         }
1300         fprint(cfd, "rawon");
1301
1302         if(sendp(c, nil) <= 0)
1303                 return;
1304
1305         if((kfd = open("/dev/kbd", OREAD)) >= 0){
1306                 close(fd);
1307
1308                 /* read kbd state */
1309                 while((n = read(kfd, buf, sizeof(buf))) > 0)
1310                         chanprint(c, "%.*s", n, buf);
1311                 close(kfd);
1312         } else {
1313                 /* read single characters */
1314                 p = buf;
1315                 for(;;){
1316                         Rune r;
1317
1318                         e = buf + sizeof(buf);
1319                         if((n = read(fd, p, e-p)) <= 0)
1320                                 break;
1321                         e = p + n;
1322                         while(p < e && fullrune(p, e - p)){
1323                                 p += chartorune(&r, p);
1324                                 if(r)
1325                                         chanprint(c, "c%C", r);
1326                         }
1327                         n = e - p;
1328                         memmove(buf, p, n);
1329                         p = buf + n;
1330                 }
1331         }
1332 }
1333
1334 Channel*
1335 initkbd(void)
1336 {
1337         Channel *c;
1338         char *e;
1339
1340         c = chancreate(sizeof(char*), 16);
1341         procrfork(kbdproc, c, STACK, RFCFDG);
1342         if(e = recvp(c)){
1343                 chanfree(c);
1344                 c = nil;
1345                 werrstr(e);
1346                 free(e);
1347         }
1348         return c;
1349 }