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