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