client.c (23235B)
1 /* Copyright ©2004-2006 Anselm R. Garbe <garbeam at gmail dot com> 2 * Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail> 3 * See LICENSE file for license details. 4 */ 5 #include "dat.h" 6 #include <ctype.h> 7 #include <strings.h> 8 #include <signal.h> 9 #include <X11/Xatom.h> 10 #include "fns.h" 11 12 #define Mbsearch(k, l, cmp) bsearch(k, l, nelem(l), sizeof(*l), cmp) 13 14 static Handlers handlers; 15 16 enum { 17 ClientMask = StructureNotifyMask 18 | PropertyChangeMask 19 | EnterWindowMask 20 | FocusChangeMask, 21 ButtonMask = ButtonPressMask 22 | ButtonReleaseMask, 23 }; 24 25 static Group* group; 26 27 void 28 group_init(Client *c) { 29 Group *g; 30 long *ret; 31 XWindow w; 32 long n; 33 34 w = c->w.hints->group; 35 if(w == 0) { 36 /* Not quite ICCCM compliant, but it seems to work. */ 37 n = getprop_long(&c->w, "WM_CLIENT_LEADER", "WINDOW", 0L, &ret, 1L); 38 if(n == 0) 39 return; 40 w = *ret; 41 free(ret); 42 } 43 44 for(g=group; g; g=g->next) 45 if(g->leader == w) 46 break; 47 if(g == nil) { 48 g = emallocz(sizeof *g); 49 g->leader = w; 50 g->next = group; 51 group = g; 52 } 53 c->group = g; 54 g->ref++; 55 } 56 57 void 58 group_remove(Client *c) { 59 Group **gp; 60 Group *g; 61 62 g = c->group; 63 c->group = nil; 64 if(g == nil) 65 return; 66 if(g->client == c) 67 g->client = nil; 68 g->ref--; 69 if(g->ref == 0) { 70 for(gp=&group; *gp; gp=&gp[0]->next) 71 if(*gp == g) break; 72 assert(*gp == g); 73 gp[0] = gp[0]->next; 74 free(g); 75 } 76 } 77 78 Client* 79 group_leader(Group *g) { 80 Client *c; 81 82 c = win2client(g->leader); 83 if(c) 84 return c; 85 if(g->client) 86 return g->client; 87 /* Could do better. */ 88 for(c=client; c; c=c->next) 89 if(c->frame && c->group == g) 90 break; 91 return c; 92 } 93 94 Client* 95 client_create(XWindow w, XWindowAttributes *wa) { 96 Client **t, *c; 97 char **host = nil; 98 ulong *pid = nil; 99 100 c = emallocz(sizeof *c); 101 c->fullscreen = -1; 102 c->border = wa->border_width; 103 104 c->r = rectsetorigin(Rect(0, 0, wa->width, wa->height), 105 Pt(wa->x, wa->y)); 106 107 c->w.type = WWindow; 108 c->w.visual = wa->visual; 109 c->w.xid = w; 110 c->w.r = c->r; 111 c->w.aux = c; 112 113 setborder(&c->w, 0, &(Color){0}); 114 115 client_prop(c, xatom("WM_PROTOCOLS")); 116 client_prop(c, xatom("WM_TRANSIENT_FOR")); 117 client_prop(c, xatom("WM_NORMAL_HINTS")); 118 client_prop(c, xatom("WM_HINTS")); 119 client_prop(c, xatom("WM_CLASS")); 120 client_prop(c, xatom("WM_NAME")); 121 client_prop(c, xatom("_MOTIF_WM_HINTS")); 122 123 gethostname(hostname, sizeof(hostname) - 1); 124 if(getprop_textlist(&c->w, "WM_CLIENT_MACHINE", &host) && 125 getprop_ulong(&c->w, Net("WM_PID"), "CARDINAL", 0, &pid, 1) && 126 !strcmp(hostname, *host)) 127 c->pid = (int)*pid; 128 freestringlist(host); 129 free(pid); 130 131 c->rgba = render_argb_p(c->w.visual); 132 client_reparent(c); 133 134 sethandler(&c->w, &handlers); 135 136 selectinput(&c->w, ClientMask); 137 138 group_init(c); 139 140 grab_button(c->framewin->xid, AnyButton, AnyModifier); 141 142 for(t=&client ;; t=&t[0]->next) 143 if(!*t) { 144 c->next = *t; 145 *t = c; 146 break; 147 } 148 149 150 /* 151 * It's actually possible for a window to be destroyed 152 * before we get a chance to reparent it. Check for that 153 * now, because otherwise we'll wind up mapping a 154 * perceptibly empty frame before it's destroyed. 155 */ 156 traperrors(true); 157 XAddToSaveSet(display, w); 158 if(traperrors(false)) { 159 client_destroy(c); 160 return nil; 161 } 162 163 ewmh_initclient(c); 164 165 event("CreateClient %#C\n", c); 166 client_manage(c); 167 return c; 168 } 169 170 void 171 client_reparent(Client *c) { 172 Window *fw; 173 WinAttr wa; 174 bool rgba; 175 176 rgba = c->rgba | RGBA_P(def.normcolor) | RGBA_P(def.focuscolor); 177 178 fw = c->framewin; 179 if(fw && (fw->depth == 32) == rgba) 180 return; 181 182 wa.background_pixmap = None; 183 wa.bit_gravity = NorthWestGravity; 184 wa.event_mask = ButtonPressMask 185 | ButtonReleaseMask 186 | EnterWindowMask 187 | ExposureMask 188 | PointerMotionMask 189 | StructureNotifyMask 190 | SubstructureNotifyMask 191 | SubstructureRedirectMask; 192 wa.override_redirect = true; 193 if(rgba) 194 c->framewin = createwindow_rgba(&scr.root, c->r, 195 &wa, CWBackPixmap 196 | CWBitGravity 197 | CWEventMask 198 | CWOverrideRedirect); 199 else 200 c->framewin = createwindow(&scr.root, c->r, scr.depth, InputOutput, 201 &wa, CWBackPixmap 202 | CWBitGravity 203 | CWEventMask 204 | CWOverrideRedirect); 205 206 c->framewin->aux = c; 207 sethandler(c->framewin, &framehandler); 208 reparentwindow(&c->w, c->framewin, ZP); 209 if(fw) 210 destroywindow(fw); 211 } 212 213 static bool 214 apply_rules(Client *c) { 215 IxpMsg m; 216 Rule *r; 217 Ruleval *rv; 218 bool ret, more; 219 220 ret = true; 221 more = true; 222 for(r=def.rules.rule; r && more; r=r->next) 223 if(regexec(r->regex, c->props, nil, 0)) { 224 more = false; 225 for(rv=r->values; rv; rv=rv->next) { 226 if(!strcmp(rv->key, "continue")) 227 more = true; 228 else if(!strcmp(rv->key, "tags")) 229 utflcpy(c->tags, rv->value, sizeof c->tags); 230 else if(!strcmp(rv->key, "force-tags")) { 231 utflcpy(c->tags, rv->value, sizeof c->tags); 232 ret = false; 233 }else { 234 bufclear(); 235 bufprint("%s %s", rv->key, rv->value); 236 m = ixp_message(buffer, _buf_end - buffer, MsgPack); 237 if(waserror()) 238 warning("processing rule %q=%q: %r", rv->key, rv->value); 239 else { 240 message_client(c, &m); 241 poperror(); 242 } 243 } 244 } 245 } 246 return ret; 247 } 248 249 void 250 client_manage(Client *c) { 251 Client *leader; 252 char *tags; 253 bool dotags; 254 255 if(Dx(c->r) == Dx(screen->r)) 256 if(Dy(c->r) == Dy(screen->r)) 257 if(c->w.ewmh.type == 0) 258 fullscreen(c, true, -1); 259 260 dotags = apply_rules(c); 261 262 if(!c->tags[0] || dotags) { 263 leader = win2client(c->trans); 264 if(leader == nil && c->group) 265 leader = group_leader(c->group); 266 267 tags = getprop_string(&c->w, "_WMII_TAGS"); 268 if(tags) 269 utflcpy(c->tags, tags, sizeof c->tags); 270 else if(leader) 271 utflcpy(c->tags, leader->tags, sizeof c->tags); 272 free(tags); 273 } 274 275 if(c->tags[0]) 276 client_applytags(c, c->tags); 277 else 278 client_applytags(c, "sel"); 279 280 if(!starting) 281 view_update_all(); 282 283 bool newgroup = !c->group 284 || c->group->ref == 1 285 || selclient() && (selclient()->group == c->group) 286 || group_leader(c->group) 287 && !client_viewframe(group_leader(c->group), 288 c->sel->view); 289 290 /* f = c->sel; */ 291 if(!(c->w.ewmh.type & TypeSplash)) 292 if(newgroup) { 293 /* XXX: Look over this. 294 if(f->area != f->view->sel) 295 f->view->oldsel = f->view->sel; 296 */ 297 }else { 298 frame_restack(c->sel, c->sel->area->sel); 299 view_restack(c->sel->view); 300 } 301 } 302 303 void 304 client_destroy(Client *c) { 305 Rectangle r; 306 char *none; 307 Client **tc; 308 bool hide; 309 310 unmapwin(c->framewin); 311 client_seturgent(c, false, UrgClient); 312 313 for(tc=&client; *tc; tc=&tc[0]->next) 314 if(*tc == c) { 315 *tc = c->next; 316 break; 317 } 318 319 r = client_grav(c, ZR); 320 321 hide = (!c->sel || c->sel->view != selview); 322 323 /* In case the client is already destroyed. */ 324 traperrors(true); 325 326 sethandler(&c->w, nil); 327 if(hide) 328 reparentwindow(&c->w, &scr.root, screen->r.max); 329 else 330 reparentwindow(&c->w, &scr.root, r.min); 331 332 if(starting >= 0) 333 XRemoveFromSaveSet(display, c->w.xid); 334 335 none = nil; 336 client_setviews(c, &none); 337 if(starting >= 0) { 338 client_unmap(c, WithdrawnState); 339 delproperty(&c->w, "_WMII_TAGS"); 340 } 341 refree(&c->tagre); 342 refree(&c->tagvre); 343 free(c->retags); 344 345 traperrors(false); 346 347 destroywindow(c->framewin); 348 349 ewmh_destroyclient(c); 350 group_remove(c); 351 if(starting >= 0) 352 event("DestroyClient %#C\n", c); 353 354 event_flush(FocusChangeMask, true); 355 cleanupwindow(&c->w); 356 free(c); 357 } 358 359 /* Convenience functions */ 360 Frame* 361 client_viewframe(Client *c, View *v) { 362 Frame *f; 363 364 for(f=c->frame; f; f=f->cnext) 365 if(f->view == v) 366 break; 367 return f; 368 } 369 370 Client* 371 selclient(void) { 372 if(selview->sel->sel) 373 return selview->sel->sel->client; 374 return nil; 375 } 376 377 Client* 378 win2client(XWindow w) { 379 Client *c; 380 for(c=client; c; c=c->next) 381 if(c->w.xid == w) break; 382 return c; 383 } 384 385 int 386 Cfmt(Fmt *f) { 387 Client *c; 388 389 c = va_arg(f->args, Client*); 390 if(c) 391 if(f->flags & FmtSharp) 392 return fmtprint(f, "%W", &c->w); 393 else 394 return fmtprint(f, "%s", c->name); 395 return fmtprint(f, "<nil>"); 396 } 397 398 Rectangle 399 client_grav(Client *c, Rectangle rd) { 400 Rectangle r, cr; 401 Point sp; 402 WinHints *h; 403 404 h = c->w.hints; 405 406 if(eqrect(rd, ZR)) { 407 if(c->sel) { 408 r = c->sel->floatr; 409 cr = frame_rect2client(c, r, true); 410 }else { 411 cr = c->r; 412 r = frame_client2rect(c, cr, true); 413 r = rectsetorigin(r, cr.min); 414 } 415 sp = subpt(cr.min, r.min); 416 r = gravitate(r, cr, h->grav); 417 if(!h->gravstatic) 418 r = rectsubpt(r, sp); 419 return frame_rect2client(c, r, true); 420 }else { 421 r = frame_client2rect(c, rd, true); 422 sp = subpt(rd.min, r.min); 423 r = gravitate(rd, r, h->grav); 424 if(!h->gravstatic) 425 r = rectaddpt(r, sp); 426 return frame_client2rect(c, r, true); 427 } 428 } 429 430 bool 431 client_floats_p(Client *c) { 432 if(c->floating == Never) 433 return false; 434 return c->trans 435 || c->floating 436 || c->fixedsize 437 || c->titleless 438 || c->borderless 439 || c->fullscreen >= 0 440 || (c->w.ewmh.type & (TypeDialog|TypeSplash|TypeDock|TypeMenu|TypeToolbar)); 441 } 442 443 Frame* 444 client_groupframe(Client *c, View *v) { 445 if(c->group && c->group->client) 446 return client_viewframe(c->group->client, v); 447 return nil; 448 } 449 450 Rectangle 451 frame_hints(Frame *f, Rectangle r, Align sticky) { 452 Rectangle or; 453 WinHints h; 454 Point p; 455 Client *c; 456 457 c = f->client; 458 if(c->w.hints == nil) 459 return r; 460 461 or = r; 462 h = frame_gethints(f); 463 r = sizehint(&h, r); 464 465 if(!f->area->floating) { 466 /* Not allowed to grow */ 467 if(Dx(r) > Dx(or)) 468 r.max.x = r.min.x+Dx(or); 469 if(Dy(r) > Dy(or)) 470 r.max.y = r.min.y+Dy(or); 471 } 472 473 p = ZP; 474 if((sticky&(East|West)) == East) 475 p.x = Dx(or) - Dx(r); 476 if((sticky&(North|South)) == South) 477 p.y = Dy(or) - Dy(r); 478 return rectaddpt(r, p); 479 } 480 481 static void 482 client_setstate(Client * c, int state) { 483 long data[] = { state, None }; 484 485 changeprop_long(&c->w, "WM_STATE", "WM_STATE", data, nelem(data)); 486 } 487 488 void 489 client_map(Client *c) { 490 if(!c->w.mapped) { 491 mapwin(&c->w); 492 client_setstate(c, NormalState); 493 } 494 } 495 496 void 497 client_unmap(Client *c, int state) { 498 if(c->w.mapped) 499 unmapwin(&c->w); 500 client_setstate(c, state); 501 } 502 503 int 504 client_mapframe(Client *c) { 505 return mapwin(c->framewin); 506 } 507 508 int 509 client_unmapframe(Client *c) { 510 return unmapwin(c->framewin); 511 } 512 513 void 514 focus(Client *c, bool user) { 515 View *v; 516 Frame *f; 517 518 Dprint(DFocus, "focus(%#C, %d)\n", c, user); 519 if(!c->nofocus || user) 520 if((f = c->sel)) { 521 v = f->view; 522 if(v != selview) 523 view_focus(screen, v); 524 frame_focus(c->sel); 525 view_restack(c->sel->view); 526 } 527 } 528 529 void 530 client_focus(Client *c) { 531 /* Round trip. */ 532 533 if(c && c->group) 534 c->group->client = c; 535 536 sync(); 537 event_flush(FocusChangeMask, true); 538 539 Dprint(DFocus, "client_focus([%#C]%C) collapsed=%s\n", 540 c, c, c && c->sel->collapsed ? "true" : "false"); 541 Dprint(DFocus, "\t[%#C]%C\n\t=> [%#C]%C\n", 542 disp.focus, disp.focus, c, c); 543 544 if(disp.focus != c) { 545 if(c && !c->sel->collapsed) { 546 if(!c->noinput) 547 setfocus(&c->w, RevertToParent); 548 else if(c->proto & ProtoTakeFocus) { 549 event_updatextime(); 550 client_message(c, "WM_TAKE_FOCUS", 0); 551 } 552 }else 553 setfocus(screen->barwin, RevertToParent); 554 555 sync(); 556 event_flush(FocusChangeMask, true); 557 } 558 } 559 560 void 561 client_resize(Client *c, Rectangle r) { 562 Frame *f; 563 564 f = c->sel; 565 frame_resize(f, r); 566 567 if(f->view != selview) { 568 client_unmap(c, IconicState); 569 client_unmapframe(c); 570 return; 571 } 572 573 c->r = rectaddpt(f->crect, f->r.min); 574 575 if(f->collapsed) { 576 if(f->area->max && !resizing) 577 client_unmapframe(c); 578 else { 579 reshapewin(c->framewin, f->r); 580 movewin(&c->w, f->crect.min); 581 client_mapframe(c); 582 } 583 client_unmap(c, IconicState); 584 }else { 585 client_map(c); 586 reshapewin(c->framewin, f->r); 587 reshapewin(&c->w, f->crect); 588 client_mapframe(c); 589 if(!eqrect(c->r, c->configr)) 590 client_configure(c); 591 ewmh_framesize(c); 592 } 593 } 594 595 void 596 client_setcursor(Client *c, Cursor cur) { 597 WinAttr wa; 598 599 if(c->cursor != cur) { 600 c->cursor = cur; 601 wa.cursor = cur; 602 setwinattr(c->framewin, &wa, CWCursor); 603 } 604 } 605 606 void 607 client_configure(Client *c) { 608 Rectangle r; 609 610 c->configr = c->r; 611 r = rectsubpt(c->r, Pt(c->border, c->border)); 612 613 sendevent(&c->w, false, StructureNotifyMask, 614 &(XConfigureEvent) { 615 .type = ConfigureNotify, 616 .event = c->w.xid, 617 .window = c->w.xid, 618 619 .x = r.min.x, 620 .y = r.min.y, 621 .width = Dx(r), 622 .height = Dy(r), 623 .border_width = c->border, 624 }); 625 } 626 627 void 628 client_message(Client *c, char *msg, long l2) { 629 sendmessage(&c->w, "WM_PROTOCOLS", xatom(msg), event_xtime, l2, 0, 0); 630 } 631 632 void 633 client_kill(Client *c, bool nice) { 634 635 if(!nice) { 636 if(c->pid) 637 kill(c->pid, SIGKILL); 638 XKillClient(display, c->w.xid); 639 } 640 else if(c->proto & ProtoDelete) { 641 c->dead = 1; 642 client_message(c, "WM_DELETE_WINDOW", 0); 643 ewmh_checkresponsive(c); 644 } 645 else 646 XKillClient(display, c->w.xid); 647 } 648 649 void 650 fullscreen(Client *c, int fullscreen, long screen) { 651 Client *leader; 652 Frame *f; 653 bool wassel; 654 655 if(fullscreen == Toggle) 656 fullscreen = (c->fullscreen >= 0) ^ On; 657 if(fullscreen == (c->fullscreen >= 0)) 658 return; 659 660 event("Fullscreen %#C %s\n", c, (fullscreen ? "on" : "off")); 661 662 c->fullscreen = -1; 663 if(!fullscreen) 664 for(f=c->frame; f; f=f->cnext) { 665 if(f->oldarea == 0) { 666 frame_resize(f, f->floatr); 667 if(f->view == selview) /* FIXME */ 668 client_resize(f->client, f->r); 669 670 } 671 else if(f->oldarea > 0) { 672 wassel = (f == f->area->sel); 673 area_moveto(view_findarea(f->view, f->oldscreen, f->oldarea, true), 674 f); 675 if(wassel) 676 frame_focus(f); 677 } 678 } 679 else { 680 c->fullscreen = 0; 681 if(screen >= 0) 682 c->fullscreen = screen; 683 else if(c->sel) 684 c->fullscreen = ownerscreen(c->r); 685 else if(c->group && (leader = group_leader(c->group)) && leader->sel) 686 c->fullscreen = ownerscreen(leader->r); 687 else if(selclient()) 688 c->fullscreen = ownerscreen(selclient()->r); 689 690 for(f=c->frame; f; f=f->cnext) 691 f->oldarea = -1; 692 if((f = c->sel)) 693 view_update(f->view); 694 } 695 ewmh_updatestate(c); 696 } 697 698 void 699 client_seturgent(Client *c, int urgent, int from) { 700 XWMHints *wmh; 701 char *cfrom, *cnot; 702 Frame *f; 703 704 if(urgent == Toggle) 705 urgent = c->urgent ^ On; 706 707 cfrom = (from == UrgManager ? "Manager" : "Client"); 708 cnot = (urgent ? "" : "Not"); 709 710 if(urgent != c->urgent) { 711 event("%sUrgent %#C %s\n", cnot, c, cfrom); 712 c->urgent = urgent; 713 ewmh_updatestate(c); 714 if(c->sel) 715 frame_draw(c->sel); 716 717 for(f=c->frame; f; f=f->cnext) 718 view_update_urgency(f->view, cfrom); 719 } 720 721 if(from == UrgManager) { 722 wmh = XGetWMHints(display, c->w.xid); 723 if(wmh == nil) 724 wmh = emallocz(sizeof *wmh); 725 726 wmh->flags &= ~XUrgencyHint; 727 if(urgent) 728 wmh->flags |= XUrgencyHint; 729 XSetWMHints(display, c->w.xid, wmh); 730 XFree(wmh); 731 } 732 } 733 734 /* X11 stuff */ 735 void 736 update_class(Client *c) { 737 738 snprint(c->props, sizeof c->props, "%s:%s", c->class, c->name); 739 } 740 741 static void 742 client_updatename(Client *c) { 743 char *str; 744 745 c->name[0] = '\0'; 746 if((str = windowname(&c->w))) { 747 utflcpy(c->name, str, sizeof c->name); 748 free(str); 749 } 750 751 update_class(c); 752 if(c->sel) 753 frame_draw(c->sel); 754 } 755 756 static void 757 updatemwm(Client *c) { 758 enum { 759 All = 0x1, 760 Border = 0x2, 761 Title = 0x8, 762 FlagDecor = 0x2, 763 Flags = 0, 764 Decor = 2, 765 }; 766 Rectangle r; 767 ulong *ret; 768 int n; 769 770 /* To quote Metacity, or KWin quoting Metacity: 771 * 772 * We support MWM hints deemed non-stupid 773 * 774 * Our definition of non-stupid is a bit less lenient than 775 * theirs, though. In fact, we don't really even support the 776 * idea of supporting the hints that we support, but apps 777 * like xmms (which no one should use) break if we don't. 778 */ 779 780 n = getprop_ulong(&c->w, "_MOTIF_WM_HINTS", "_MOTIF_WM_HINTS", 781 0L, &ret, 3L); 782 783 /* FIXME: Should somehow handle all frames of a client. */ 784 if(c->sel) 785 r = client_grav(c, ZR); 786 787 c->borderless = 0; 788 c->titleless = 0; 789 if(n >= 3 && (ret[Flags] & FlagDecor)) { 790 if(ret[Decor] & All) 791 ret[Decor] ^= ~0; 792 c->borderless = !(ret[Decor] & Border); 793 c->titleless = !(ret[Decor] & Title); 794 } 795 free(ret); 796 797 if(false && c->sel) { 798 c->sel->floatr = client_grav(c, r); 799 if(c->sel->area->floating) { 800 client_resize(c, c->sel->floatr); 801 frame_draw(c->sel); 802 } 803 } 804 } 805 806 bool 807 client_prop(Client *c, Atom a) { 808 WinHints h; 809 XWMHints *wmh; 810 char **class; 811 int n; 812 813 if(a == xatom("WM_PROTOCOLS")) 814 c->proto = ewmh_protocols(&c->w); 815 else 816 if(a == xatom("_NET_WM_NAME")) 817 goto wmname; 818 else 819 if(a == xatom("_MOTIF_WM_HINTS")) 820 updatemwm(c); 821 else 822 switch (a) { 823 default: 824 return true; 825 case XA_WM_TRANSIENT_FOR: 826 XGetTransientForHint(display, c->w.xid, &c->trans); 827 break; 828 case XA_WM_NORMAL_HINTS: 829 memset(&h, 0, sizeof h); 830 if(c->w.hints) 831 bcopy(c->w.hints, &h, sizeof h); 832 gethints(&c->w); 833 if(c->w.hints) 834 c->fixedsize = eqpt(c->w.hints->min, c->w.hints->max); 835 if(memcmp(&h, c->w.hints, sizeof h)) 836 if(c->sel) 837 view_update(c->sel->view); 838 break; 839 case XA_WM_HINTS: 840 wmh = XGetWMHints(display, c->w.xid); 841 if(wmh) { 842 c->noinput = (wmh->flags&InputFocus) && !wmh->input; 843 client_seturgent(c, (wmh->flags & XUrgencyHint) != 0, UrgClient); 844 XFree(wmh); 845 } 846 break; 847 case XA_WM_CLASS: 848 n = getprop_textlist(&c->w, "WM_CLASS", &class); 849 snprint(c->class, sizeof c->class, "%s:%s", 850 (n > 0 ? class[0] : "<nil>"), 851 (n > 1 ? class[1] : "<nil>")); 852 freestringlist(class); 853 update_class(c); 854 break; 855 case XA_WM_NAME: 856 wmname: 857 client_updatename(c); 858 break; 859 } 860 return false; 861 } 862 863 /* Handlers */ 864 static bool 865 configreq_event(Window *w, void *aux, XConfigureRequestEvent *e) { 866 Rectangle r; 867 Client *c; 868 869 c = aux; 870 871 r = client_grav(c, ZR); 872 r.max = subpt(r.max, r.min); 873 874 if(e->value_mask & CWX) 875 r.min.x = e->x; 876 if(e->value_mask & CWY) 877 r.min.y = e->y; 878 if(e->value_mask & CWWidth) 879 r.max.x = e->width; 880 if(e->value_mask & CWHeight) 881 r.max.y = e->height; 882 883 if(e->value_mask & CWBorderWidth) 884 c->border = e->border_width; 885 886 r.max = addpt(r.min, r.max); 887 r = client_grav(c, r); 888 889 if(c->sel->area->floating) 890 client_resize(c, r); 891 else { 892 c->sel->floatr = r; 893 client_configure(c); 894 } 895 return false; 896 } 897 898 static bool 899 destroy_event(Window *w, void *aux, XDestroyWindowEvent *e) { 900 USED(w, e); 901 902 client_destroy(aux); 903 return false; 904 } 905 906 static bool 907 enter_event(Window *w, void *aux, XCrossingEvent *e) { 908 Client *c; 909 910 c = aux; 911 if(e->detail != NotifyInferior) { 912 if(e->detail != NotifyVirtual) 913 if(e->serial > event_lastconfigure && disp.focus != c) { 914 Dprint(DFocus, "enter_notify([%#C]%s)\n", c, c->name); 915 focus(c, false); 916 } 917 client_setcursor(c, cursor[CurNormal]); 918 }else 919 Dprint(DFocus, "enter_notify(%#C[NotifyInferior]%s)\n", c, c->name); 920 return false; 921 } 922 923 static bool 924 focusin_event(Window *w, void *aux, XFocusChangeEvent *e) { 925 Client *c, *old; 926 927 c = aux; 928 929 print_focus("focusin_event", c, c->name); 930 931 if(e->mode == NotifyGrab) 932 disp.hasgrab = c; 933 934 old = disp.focus; 935 disp.focus = c; 936 if(c != old) { 937 event("ClientFocus %#C\n", c); 938 if(c->sel) 939 frame_draw(c->sel); 940 } 941 return false; 942 } 943 944 static bool 945 focusout_event(Window *w, void *aux, XFocusChangeEvent *e) { 946 Client *c; 947 948 c = aux; 949 if((e->mode == NotifyWhileGrabbed) && (disp.hasgrab != &c_root)) { 950 if(disp.focus) 951 disp.hasgrab = disp.focus; 952 }else if(disp.focus == c) { 953 print_focus("focusout_event", &c_magic, "<magic>"); 954 disp.focus = &c_magic; 955 if(c->sel) 956 frame_draw(c->sel); 957 } 958 return false; 959 } 960 961 static bool 962 unmap_event(Window *w, void *aux, XUnmapEvent *e) { 963 Client *c; 964 965 c = aux; 966 if(!e->send_event && w->parent != c->framewin) 967 c->w.unmapped++; 968 if(e->send_event || c->w.unmapped < 0) 969 client_destroy(c); 970 return false; 971 } 972 973 static bool 974 map_event(Window *w, void *aux, XMapEvent *e) { 975 Client *c; 976 977 USED(e); 978 979 c = aux; 980 if(c == selclient()) 981 client_focus(c); 982 return true; 983 } 984 985 static bool 986 property_event(Window *w, void *aux, XPropertyEvent *e) { 987 988 if(e->state == PropertyDelete) /* FIXME */ 989 return true; 990 return client_prop(aux, e->atom); 991 } 992 993 static Handlers handlers = { 994 .configreq = configreq_event, 995 .destroy = destroy_event, 996 .enter = enter_event, 997 .focusin = focusin_event, 998 .focusout = focusout_event, 999 .map = map_event, 1000 .unmap = unmap_event, 1001 .property = property_event, 1002 }; 1003 1004 /* Other */ 1005 void 1006 client_setviews(Client *c, char **tags) { 1007 Frame **fp, *f; 1008 int cmp; 1009 1010 fp = &c->frame; 1011 while(*fp || *tags) { 1012 SET(cmp); 1013 while(*fp) { 1014 if(*tags) { 1015 cmp = strcmp(fp[0]->view->name, *tags); 1016 if(cmp >= 0) 1017 break; 1018 } 1019 1020 f = *fp; 1021 view_detach(f); 1022 *fp = f->cnext; 1023 if(c->sel == f) 1024 c->sel = *fp; 1025 free(f); 1026 } 1027 if(*tags) { 1028 if(!*fp || cmp > 0) { 1029 f = frame_create(c, view_create(*tags)); 1030 Dprint(DGeneric, "%#C %p %R %R %R %C\n", c, c->sel, c->r, f->floatr, c->sel ? c->sel->floatr : ZR, c); 1031 if(f->view == selview || !c->sel) 1032 c->sel = f; 1033 kludge = c; /* FIXME */ 1034 view_attach(f->view, f); 1035 kludge = nil; 1036 f->cnext = *fp; 1037 *fp = f; 1038 } 1039 if(fp[0]) fp=&fp[0]->cnext; 1040 tags++; 1041 } 1042 } 1043 if(c->sel == nil) 1044 c->sel = c->frame; 1045 if(c->sel) 1046 frame_draw(c->sel); 1047 } 1048 1049 static int 1050 bsstrcmp(const void *a, const void *b) { 1051 return strcmp((char*)a, *(char**)b); 1052 } 1053 1054 static int 1055 strpcmp(const void *ap, const void *bp) { 1056 char **a, **b; 1057 1058 a = (char**)ap; 1059 b = (char**)bp; 1060 return strcmp(*a, *b); 1061 } 1062 1063 static char *badtags[] = { 1064 ".", 1065 "..", 1066 "sel", 1067 }; 1068 1069 char* 1070 client_extratags(Client *c) { 1071 Fmt fmt; 1072 Frame *f; 1073 char *toks[32]; 1074 char **tags; 1075 int i; 1076 1077 i = 0; 1078 toks[i++] = ""; 1079 for(f=c->frame; f && i < nelem(toks)-1; f=f->cnext) 1080 if(f != c->sel) 1081 toks[i++] = f->view->name; 1082 toks[i] = nil; 1083 tags = comm(CLeft, toks, c->retags); 1084 1085 if(i == 1 && !c->tagre.regex && !c->tagvre.regex) { 1086 free(tags); 1087 return nil; 1088 } 1089 1090 fmtstrinit(&fmt); 1091 if(i > 1) 1092 join(tags, "+", &fmt); 1093 free(tags); 1094 1095 if(c->tagre.regex) 1096 fmtprint(&fmt, "+/%s/", c->tagre.regex); 1097 if(c->tagvre.regex) 1098 fmtprint(&fmt, "-/%s/", c->tagvre.regex); 1099 return fmtstrflush(&fmt); 1100 } 1101 1102 bool 1103 client_applytags(Client *c, const char *tags) { 1104 Fmt fmt; 1105 uint i, j, k; 1106 char buf[512]; 1107 char *toks[32]; 1108 char **p; 1109 char *cur, *s; 1110 int add, old; 1111 1112 buf[0] = 0; 1113 if(memchr("+-^", tags[0], 4)) 1114 utflcpy(buf, c->tags, sizeof c->tags); 1115 else { 1116 refree(&c->tagre); 1117 refree(&c->tagvre); 1118 } 1119 strlcat(buf, tags, sizeof buf); 1120 1121 j = 0; 1122 s = buf; 1123 old = '+'; 1124 while((cur = mask(&s, &add, &old))) { 1125 /* Check for regex. */ 1126 if(cur[0] == '/') { 1127 cur++; 1128 *strrchr(cur, '/') = '\0'; 1129 if(add == '+') 1130 reinit(&c->tagre, cur); 1131 else if(add == '-') 1132 reinit(&c->tagvre, cur); 1133 } 1134 else if(!strcmp(cur, "~")) 1135 c->floating = add ? On : Never; 1136 else { 1137 trim(cur, " \t\r\n"); 1138 if(!strcmp(cur, "sel")) 1139 cur = selview->name; 1140 else if(Mbsearch(cur, badtags, bsstrcmp)) 1141 continue; 1142 1143 if(j < nelem(toks)-1) { 1144 if(add == '^') 1145 add = bsearch(cur, toks, j, sizeof *toks, bsstrcmp) ? '-' : '+'; 1146 if(add == '+') 1147 toks[j++] = cur; 1148 else { 1149 for(i = 0, k = 0; i < j; i++) 1150 if(strcmp(toks[i], cur)) 1151 toks[k++] = toks[i]; 1152 j = k; 1153 } 1154 } 1155 } 1156 } 1157 1158 toks[j] = nil; 1159 qsort(toks, j, sizeof *toks, strpcmp); 1160 uniq(toks); 1161 1162 fmtstrinit(&fmt); 1163 join(toks, "+", &fmt); 1164 if(c->tagre.regex) 1165 fmtprint(&fmt, "+/%s/", c->tagre.regex); 1166 if(c->tagvre.regex) 1167 fmtprint(&fmt, "-/%s/", c->tagvre.regex); 1168 1169 s = fmtstrflush(&fmt); 1170 utflcpy(c->tags, s, sizeof c->tags); 1171 changeprop_string(&c->w, "_WMII_TAGS", c->tags); 1172 free(s); 1173 1174 free(c->retags); 1175 p = view_names(); 1176 grep(p, c->tagre.regc, 0); 1177 grep(p, c->tagvre.regc, GInvert); 1178 c->retags = comm(CRight, toks, p); 1179 free(p); 1180 1181 if(c->retags[0] == nil && toks[0] == nil) { 1182 toks[0] = "orphans"; 1183 toks[1] = nil; 1184 } 1185 1186 p = comm(~0, c->retags, toks); 1187 client_setviews(c, p); 1188 free(p); 1189 return true; 1190 } 1191