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