1 /* Copyright ©2004-2006 Anselm R. Garbe <garbeam at gmail dot com>
2 * Copyright ©2006-2007 Kris Maglione <fbsdaemon@gmail.com>
3 * See LICENSE file for license details.
4 */
5 #include "dat.h"
6 #include <ctype.h>
7 #include <stdlib.h>
8 #include <stdio.h>
9 #include <string.h>
10 #include <X11/Xatom.h>
11 #include "fns.h"
12
13 #define Mbsearch(k, l, cmp) bsearch(k, l, nelem(l), sizeof(*l), cmp)
14
15 static Handlers handlers;
16
17 enum {
18 ClientMask =
19 StructureNotifyMask
20 | PropertyChangeMask
21 | EnterWindowMask
22 | FocusChangeMask,
23 ButtonMask =
24 ButtonPressMask
25 | ButtonReleaseMask
26 };
27
28 Client *
create_client(XWindow w,XWindowAttributes * wa)29 create_client(XWindow w, XWindowAttributes *wa) {
30 Client **t, *c;
31 WinAttr fwa;
32
33 c = emallocz(sizeof(Client));
34 c->border = wa->border_width;
35
36 c->r.min = Pt(wa->x, wa->y);
37 c->r.max = addpt(c->r.min, Pt(wa->width, wa->height));
38
39 c->w.type = WWindow;
40 c->w.w = w;
41 c->w.r = c->r;
42
43 if((Dx(c->r) == Dx(screen->r)) && (Dy(c->r) == Dy(screen->r)))
44 fullscreen(c, True);
45
46 prop_client(c, xatom("WM_PROTOCOLS"));
47 prop_client(c, xatom("WM_TRANSIENT_FOR"));
48 prop_client(c, xatom("WM_NORMAL_HINTS"));
49 prop_client(c, xatom("WM_HINTS"));
50 prop_client(c, xatom("WM_CLASS"));
51 prop_client(c, xatom("WM_NAME"));
52 prop_client(c, xatom("_MOTIF_WM_HINTS"));
53
54 XSetWindowBorderWidth(display, w, 0);
55 XAddToSaveSet(display, w);
56 XSelectInput(display, c->w.w, ClientMask);
57
58 fwa.override_redirect = True;
59 fwa.background_pixmap = None;
60 fwa.event_mask =
61 SubstructureRedirectMask
62 | SubstructureNotifyMask
63 | ExposureMask
64 | EnterWindowMask
65 | PointerMotionMask
66 | ButtonPressMask
67 | ButtonReleaseMask;
68 c->framewin = createwindow(&scr.root, c->r, scr.depth, InputOutput, &fwa,
69 CWOverrideRedirect
70 | CWEventMask
71 | CWBackPixmap);
72 c->framewin->aux = c;
73 c->w.aux = c;
74 sethandler(c->framewin, &framehandler);
75 sethandler(&c->w, &handlers);
76
77 grab_button(c->framewin->w, AnyButton, AnyModifier);
78
79 for(t=&client ;; t=&(*t)->next)
80 if(!*t) {
81 c->next = *t;
82 *t = c;
83 break;
84 }
85
86 write_event("CreateClient %C\n", c);
87 manage_client(c);
88 return c;
89 }
90
91 void
manage_client(Client * c)92 manage_client(Client *c) {
93 Point p;
94 Client *trans;
95 char *tags;
96
97 tags = gettextproperty(&c->w, "_WMII_TAGS");
98 if(tags == nil)
99 tags = gettextproperty(&c->w, "_WIN_TAGS");
100
101 if((trans = win2client(c->trans)))
102 utflcpy(c->tags, trans->tags, sizeof(c->tags));
103 else if(tags)
104 utflcpy(c->tags, tags, sizeof(c->tags));
105
106 free(tags);
107
108 p.x = def.border;
109 p.y = labelh(def.font);
110
111 reparentwindow(&c->w, c->framewin, p);
112
113 if(c->tags[0])
114 apply_tags(c, c->tags);
115 else
116 apply_rules(c);
117
118 if(!starting)
119 update_views();
120
121 if(c->sel->view == screen->sel)
122 focus(c, True);
123 flushevents(EnterWindowMask, False);
124 }
125
126 static int /* Temporary Xlib error handler */
ignoreerrors(Display * d,XErrorEvent * e)127 ignoreerrors(Display *d, XErrorEvent *e) {
128 USED(d);
129 USED(e);
130 return 0;
131 }
132
133 void
destroy_client(Client * c)134 destroy_client(Client *c) {
135 int (*handler)(Display*, XErrorEvent*);
136 Rectangle r;
137 char *dummy;
138 Client **tc;
139 Bool hide;
140
141 Dprint("client.c:destroy_client(%p) %s\n", c, c->name);
142
143 unmapwin(c->framewin);
144
145 for(tc=&client; *tc; tc=&(*tc)->next)
146 if(*tc == c) {
147 *tc = c->next;
148 break;
149 }
150
151 r = gravclient(c, ZR);
152
153 hide = False;
154 if(!c->sel || c->sel->view != screen->sel)
155 hide = True;
156
157 XGrabServer(display);
158
159 /* In case the client is already unmapped */
160 handler = XSetErrorHandler(ignoreerrors);
161
162 dummy = nil;
163 update_client_views(c, &dummy);
164 unmap_client(c, IconicState);
165 sethandler(&c->w, nil);
166
167 if(hide)
168 reparentwindow(&c->w, &scr.root, screen->r.max);
169 else
170 reparentwindow(&c->w, &scr.root, r.min);
171 destroywindow(c->framewin);
172
173 XSync(display, False);
174 XSetErrorHandler(handler);
175 XUngrabServer(display);
176
177 write_event("DestroyClient %C\n", c);
178
179 flushevents(EnterWindowMask, False);
180 free(c->w.hints);
181 free(c);
182 }
183
184 /* Convenience functions */
185 Client *
selclient(void)186 selclient(void) {
187 if(screen->sel->sel->sel)
188 return screen->sel->sel->sel->client;
189 return nil;
190 }
191
192 Client *
win2client(XWindow w)193 win2client(XWindow w) {
194 Client *c;
195 for(c=client; c; c=c->next)
196 if(c->w.w == w) break;
197 return c;
198 }
199
200 int
Cfmt(Fmt * f)201 Cfmt(Fmt *f) {
202 Client *c;
203
204 c = va_arg(f->args, Client*);
205 if(c)
206 return fmtprint(f, "%W", &c->w);
207 return fmtprint(f, "<nil>");
208 }
209
210 char *
clientname(Client * c)211 clientname(Client *c) {
212 if(c)
213 return c->name;
214 return "<nil>";
215 }
216
217 Rectangle
gravclient(Client * c,Rectangle rd)218 gravclient(Client *c, Rectangle rd) {
219 Rectangle r;
220 Point sp;
221 WinHints *h;
222
223 h = c->w.hints;
224 sp = Pt(def.border, labelh(def.font));
225
226 if(eqrect(rd, ZR)) {
227 if(c->sel) {
228 if(c->sel->area->floating)
229 r = c->sel->r;
230 else
231 r = c->sel->revert;
232 }else
233 r = client2frame(nil, c->r);
234 r = gravitate(r, c->r, h->grav);
235 if(h->gravstatic)
236 r = rectaddpt(r, sp);
237 return frame2client(nil, r);
238 }else {
239 r = client2frame(nil, rd);
240 r = gravitate(rd, r, h->grav);
241 if(h->gravstatic)
242 r = rectsubpt(r, sp);
243 return client2frame(nil, r);
244 }
245 }
246
247 Rectangle
frame_hints(Frame * f,Rectangle r,Align sticky)248 frame_hints(Frame *f, Rectangle r, Align sticky) {
249 Rectangle or;
250 Point p;
251 Client *c;
252
253 c = f->client;
254 if(c->w.hints == nil)
255 return r;
256
257 or = r;
258 r = frame2client(f, r);
259 r = sizehint(c->w.hints, r);
260 r = client2frame(f, r);
261
262 if(!f->area->floating) {
263 /* Not allowed to grow */
264 if(Dx(r) > Dx(or))
265 r.max.x =r.min.x+Dx(or);
266 if(Dy(r) > Dy(or))
267 r.max.y = r.min.y+Dy(or);
268 }
269
270 p = ZP;
271 if((sticky&(EAST|WEST)) == EAST)
272 p.x = Dx(or) - Dx(r);
273 if((sticky&(NORTH|SOUTH)) == SOUTH)
274 p.y = Dy(or) - Dy(r);
275
276 return rectaddpt(r, p);
277 }
278
279 static void
set_client_state(Client * c,int state)280 set_client_state(Client * c, int state) {
281 long data[] = { state, None };
282
283 changeprop_long(&c->w, "WM_STATE", "WM_STATE", data, nelem(data));
284 }
285
286 void
map_client(Client * c)287 map_client(Client *c) {
288 if(!c->w.mapped) {
289 mapwin(&c->w);
290 set_client_state(c, NormalState);
291 }
292 }
293
294 void
unmap_client(Client * c,int state)295 unmap_client(Client *c, int state) {
296 if(c->w.mapped) {
297 unmapwin(&c->w);
298 set_client_state(c, state);
299 }
300 }
301
302 int
map_frame(Client * c)303 map_frame(Client *c) {
304 return mapwin(c->framewin);
305 }
306
307 int
unmap_frame(Client * c)308 unmap_frame(Client *c) {
309 return unmapwin(c->framewin);
310 }
311
312 void
focus(Client * c,Bool restack)313 focus(Client *c, Bool restack) {
314 View *v;
315 Frame *f;
316
317 f = c->sel;
318 if(!f)
319 return;
320
321 v = f->area->view;
322 if(v != screen->sel)
323 focus_view(screen, v);
324 focus_frame(c->sel, restack);
325 }
326
327 void
focus_client(Client * c)328 focus_client(Client *c) {
329 flushevents(FocusChangeMask, True);
330
331 Dprint("focus_client(%p[%C]) => %s\n", c, c, clientname(c));
332
333 if (screen->focus == c)
334 return;
335
336 if(c == nil || !c->noinput) {
337 Dprint("\t%s => %s\n", clientname(screen->focus), clientname(c));
338
339 if(c)
340 setfocus(&c->w, RevertToPointerRoot);
341 else
342 setfocus(screen->barwin, RevertToPointerRoot);
343
344 write_event("ClientFocus %C\n", c);
345
346 XSync(display, False);
347 flushevents(FocusChangeMask, True);
348 } else if(c && c->noinput) {
349 setfocus(nil, RevertToPointerRoot);
350 }
351 }
352
353 void
resize_client(Client * c,Rectangle * r)354 resize_client(Client *c, Rectangle *r) {
355 Frame *f;
356
357 f = c->sel;
358 resize_frame(f, *r);
359
360 if(f->area->view != screen->sel) {
361 unmap_client(c, IconicState);
362 unmap_frame(c);
363 return;
364 }
365
366 c->r = rectaddpt(f->crect, f->r.min);
367
368 if((f->area->mode == Colmax) && (f->area->sel != f)) {
369 unmap_frame(c);
370 unmap_client(c, IconicState);
371 }else if(f->collapsed) {
372 reshapewin(c->framewin, f->r);
373 map_frame(c);
374 unmap_client(c, IconicState);
375 }else {
376 map_client(c);
377 reshapewin(c->framewin, f->r);
378 reshapewin(&c->w, f->crect);
379 map_frame(c);
380 configure_client(c);
381 }
382
383 flushevents(FocusChangeMask|ExposureMask, True);
384 }
385
386 void
set_cursor(Client * c,Cursor cur)387 set_cursor(Client *c, Cursor cur) {
388 WinAttr wa;
389
390 if(c->cursor != cur) {
391 c->cursor = cur;
392 wa.cursor = cur;
393 setwinattr(c->framewin, &wa, CWCursor);
394 }
395 }
396
397 void
configure_client(Client * c)398 configure_client(Client *c) {
399 XConfigureEvent e;
400 Rectangle r;
401
402 r = rectsubpt(c->r, Pt(c->border, c->border));
403
404 e.type = ConfigureNotify;
405 e.event = c->w.w;
406 e.window = c->w.w;
407 e.above = None;
408 e.override_redirect = False;
409
410 e.x = r.min.x;
411 e.y = r.min.y;
412 e.width = Dx(r);
413 e.height = Dy(r);
414 e.border_width = c->border;
415
416 XSendEvent(display, c->w.w,
417 /*propegate*/ False,
418 StructureNotifyMask,
419 (XEvent*)&e);
420 }
421
422 static void
send_client_message(Client * c,char * name,char * value)423 send_client_message(Client *c, char *name, char *value) {
424 XEvent e;
425
426 e.type = ClientMessage;
427 e.xclient.window = c->w.w;
428 e.xclient.message_type = xatom(name);
429 e.xclient.format = 32;
430 e.xclient.data.l[0] = xatom(value);
431 e.xclient.data.l[1] = CurrentTime;
432 XSendEvent(display, c->w.w, False, NoEventMask, &e);
433 XSync(display, False);
434 }
435
436 void
kill_client(Client * c)437 kill_client(Client * c) {
438 if(c->proto & WM_PROTOCOL_DELWIN)
439 send_client_message(c, "WM_PROTOCOLS", "WM_DELETE_WINDOW");
440 else
441 XKillClient(display, c->w.w);
442 }
443
444 void
fullscreen(Client * c,int fullscreen)445 fullscreen(Client *c, int fullscreen) {
446 Frame *f;
447
448 if(fullscreen == Toggle)
449 fullscreen = c->fullscreen ^ On;
450 if(fullscreen == c->fullscreen)
451 return;
452
453 write_event("Fullscreen %C %s\n", c, (fullscreen ? "on" : "off"));
454 c->fullscreen = fullscreen;
455
456 if((f = c->sel)) {
457 if(fullscreen) {
458 /* we lose information here if the client was just moved to
459 * the floating area, but it's worth it */
460 c->revert = f->area;
461
462 if(f->area->floating)
463 f->revert = f->r;
464 else {
465 f->r = f->revert;
466 send_to_area(f->view->area, f);
467 }
468 focus_client(c);
469 }else {
470 resize_frame(f, f->revert);
471 if (c->revert) {
472 send_to_area(c->revert, f);
473 c->revert = nil;
474 }
475 }
476 if(f->view == screen->sel)
477 focus_view(screen, f->view);
478 }
479 }
480
481 void
set_urgent(Client * c,int urgent,Bool write)482 set_urgent(Client *c, int urgent, Bool write) {
483 XWMHints *wmh;
484 char *cwrite, *cnot;
485 Frame *f, *ff;
486 Area *a;
487
488 if(urgent == Toggle)
489 urgent = c->urgent ^ On;
490
491 cwrite = (write ? "Manager" : "Client");
492 cnot = (urgent ? "" : "Not");
493
494 if(urgent != c->urgent) {
495 write_event("%sUrgent %C %s\n", cnot, c, cwrite);
496 c->urgent = urgent;
497 if(c->sel) {
498 if(c->sel->view == screen->sel)
499 draw_frame(c->sel);
500 for(f=c->frame; f; f=f->cnext) {
501 SET(ff);
502 if(!urgent)
503 for(a=f->view->area; a; a=a->next)
504 for(ff=a->frame; ff; ff=ff->anext)
505 if(ff->client->urgent) break;
506 if(urgent || ff == nil)
507 write_event("%sUrgentTag %s %s\n", cnot, cwrite, f->view->name);
508 }
509 }
510 }
511
512 if(write) {
513 wmh = XGetWMHints(display, c->w.w);
514 if(wmh == nil)
515 wmh = emallocz(sizeof *wmh);
516
517 wmh->flags &= ~XUrgencyHint;
518 if(urgent)
519 wmh->flags |= XUrgencyHint;
520 XSetWMHints(display, c->w.w, wmh);
521 XFree(wmh);
522 }
523 }
524
525 /* X11 stuff */
526 void
update_class(Client * c)527 update_class(Client *c) {
528 char *str;
529
530 str = utfrune(c->props, L':');
531 if(str)
532 str = utfrune(str+1, L':');
533 if(str == nil) {
534 strcpy(c->props, "::");
535 str = c->props + 1;
536 }
537 utflcpy(str+1, c->name, sizeof(c->props));
538 }
539
540 static void
update_client_name(Client * c)541 update_client_name(Client *c) {
542 char *str;
543
544 c->name[0] = '\0';
545
546 str = gettextproperty(&c->w, "_NET_WM_NAME");
547 if(str == nil)
548 str = gettextproperty(&c->w, "WM_NAME");
549 if(str)
550 utflcpy(c->name, str, sizeof(c->name));
551 free(str);
552
553 update_class(c);
554 if(c->sel)
555 draw_frame(c->sel);
556 }
557
558 static void
updatemwm(Client * c)559 updatemwm(Client *c) {
560 enum {
561 All = 0x1,
562 Border = 0x2,
563 Title = 0x8,
564 FlagDecor = 0x2,
565 Flags = 0,
566 Decor = 2,
567 };
568 Rectangle r;
569 ulong *ret;
570 Atom real;
571 int n;
572
573 n = getproperty(&c->w, "_MOTIF_WM_HINTS", "_MOTIF_WM_HINTS", &real,
574 0L, (void*)&ret, 3L);
575
576 if(c->sel)
577 r = frame2client(c->sel, c->sel->r);
578
579 if(n >= 3 && (ret[Flags]&FlagDecor)) {
580 if(ret[Decor]&All)
581 ret[Decor] ^= ~0;
582 c->borderless = ((ret[Decor]&Border)==0);
583 c->titleless = ((ret[Decor]&Title)==0);
584 }else {
585 c->borderless = 0;
586 c->titleless = 0;
587 }
588 free(ret);
589
590 if(c->sel) {
591 r = client2frame(c->sel, r);
592 resize_client(c, &r);
593 draw_frame(c->sel);
594 }
595 }
596
597 void
prop_client(Client * c,Atom a)598 prop_client(Client *c, Atom a) {
599 XWMHints *wmh;
600 char **class;
601 int n;
602
603 if(a == xatom("WM_PROTOCOLS")) {
604 c->proto = winprotocols(&c->w);
605 }
606 else if(a == xatom("_NET_WM_NAME")) {
607 goto wmname;
608 }
609 else if(a == xatom("_MOTIF_WM_HINTS")) {
610 updatemwm(c);
611 }
612 else switch (a) {
613 case XA_WM_TRANSIENT_FOR:
614 XGetTransientForHint(display, c->w.w, &c->trans);
615 break;
616 case XA_WM_NORMAL_HINTS:
617 sethints(&c->w);
618 if(c->w.hints)
619 c->fixedsize = eqpt(c->w.hints->min, c->w.hints->max);
620 break;
621 case XA_WM_HINTS:
622 wmh = XGetWMHints(display, c->w.w);
623 if(wmh) {
624 c->noinput = !((wmh->flags&InputFocus) && wmh->input);
625 set_urgent(c, (wmh->flags & XUrgencyHint) != 0, False);
626 XFree(wmh);
627 }
628 break;
629 case XA_WM_CLASS:
630 n = gettextlistproperty(&c->w, "WM_CLASS", &class);
631 snprint(c->props, sizeof(c->props), "%s:%s:",
632 (n > 0 ? class[0] : "<nil>"),
633 (n > 1 ? class[1] : "<nil>"));
634 freestringlist(class);
635 update_class(c);
636 break;
637 case XA_WM_NAME:
638 wmname:
639 update_client_name(c);
640 break;
641 }
642 }
643
644 /* Handlers */
645 static void
configreq_event(Window * w,XConfigureRequestEvent * e)646 configreq_event(Window *w, XConfigureRequestEvent *e) {
647 Rectangle r, cr;
648 Client *c;
649
650 c = w->aux;
651
652 r = gravclient(c, ZR);
653 r.max = subpt(r.max, r.min);
654
655 if(e->value_mask&CWX)
656 r.min.x = e->x;
657 if(e->value_mask&CWY)
658 r.min.y = e->y;
659 if(e->value_mask&CWWidth)
660 r.max.x = e->width;
661 if(e->value_mask&CWHeight)
662 r.max.y = e->height;
663
664 if(e->value_mask&CWBorderWidth)
665 c->border = e->border_width;
666
667 r.max = addpt(r.min, r.max);
668 cr = r;
669 r = gravclient(c, r);
670
671 if((Dx(cr) == Dx(screen->r)) && (Dy(cr) == Dy(screen->r)))
672 fullscreen(c, True);
673
674 if(c->sel->area->floating)
675 resize_client(c, &r);
676 else {
677 c->sel->revert = r;
678 configure_client(c);
679 }
680 }
681
682 static void
destroy_event(Window * w,XDestroyWindowEvent * e)683 destroy_event(Window *w, XDestroyWindowEvent *e) {
684 USED(w);
685 USED(e);
686
687 Dprint("client.c:destroy_event(%W)\n", w);
688 destroy_client(w->aux);
689 }
690
691 static void
enter_event(Window * w,XCrossingEvent * e)692 enter_event(Window *w, XCrossingEvent *e) {
693 Client *c;
694
695 c = w->aux;
696 if(e->detail != NotifyInferior) {
697 if(screen->focus != c) {
698 Dprint("enter_notify(c) => %s\n", c->name);
699 focus(c, False);
700 }
701 set_cursor(c, cursor[CurNormal]);
702 }else Dprint("enter_notify(c[NotifyInferior]) => %s\n", c->name);
703 }
704
705 static void
focusin_event(Window * w,XFocusChangeEvent * e)706 focusin_event(Window *w, XFocusChangeEvent *e) {
707 Client *c, *old;
708
709 c = w->aux;
710
711 print_focus(c, c->name);
712
713 if(e->mode == NotifyGrab)
714 screen->hasgrab = c;
715
716 old = screen->focus;
717 screen->focus = c;
718 if(c != old) {
719 if(c->sel)
720 draw_frame(c->sel);
721 }
722 }
723
724 static void
focusout_event(Window * w,XFocusChangeEvent * e)725 focusout_event(Window *w, XFocusChangeEvent *e) {
726 Client *c;
727
728 c = w->aux;
729 if((e->mode == NotifyWhileGrabbed) && (screen->hasgrab != &c_root)) {
730 if(screen->focus)
731 screen->hasgrab = screen->focus;
732 }else if(screen->focus == c) {
733 print_focus(&c_magic, "<magic>");
734 screen->focus = &c_magic;
735 if(c->sel)
736 draw_frame(c->sel);
737 }
738 }
739
740 static void
unmap_event(Window * w,XUnmapEvent * e)741 unmap_event(Window *w, XUnmapEvent *e) {
742 Client *c;
743
744 c = w->aux;
745 if(!e->send_event)
746 c->unmapped--;
747 destroy_client(c);
748 }
749
750 static void
map_event(Window * w,XMapEvent * e)751 map_event(Window *w, XMapEvent *e) {
752 Client *c;
753
754 USED(e);
755
756 c = w->aux;
757 if(c == selclient())
758 focus_client(c);
759 }
760
761 static void
property_event(Window * w,XPropertyEvent * e)762 property_event(Window *w, XPropertyEvent *e) {
763 Client *c;
764
765 if(e->state == PropertyDelete)
766 return;
767
768 c = w->aux;
769 prop_client(c, e->atom);
770 }
771
772 static Handlers handlers = {
773 .configreq = configreq_event,
774 .destroy = destroy_event,
775 .enter = enter_event,
776 .focusin = focusin_event,
777 .focusout = focusout_event,
778 .map = map_event,
779 .unmap = unmap_event,
780 .property = property_event,
781 };
782
783 /* Other */
784 #if 0 /* Not used at the moment */
785 void
786 newcol_client(Client *c, char *arg) {
787 Frame *f;
788 Area *to, *a;
789 View *v;
790
791 f = c->sel;
792 a = f->area;
793 v = f->view;
794
795 if(a->floating)
796 return;
797 if((f->anext == nil) && (f->aprev == nil))
798 return;
799
800 if(!strncmp(arg, "prev", 5)) {
801 for(to=v->area; to; to=to->next)
802 if(to->next == a) break;
803 to = new_column(v, to, 0);
804 send_to_area(to, f);
805 }
806 else if(!strncmp(arg, "next", 5)) {
807 to = new_column(v, a, 0);
808 send_to_area(to, f);
809 }
810 else
811 return;
812 flushevents(EnterWindowMask, False);
813 }
814 #endif
815
816 void
update_client_views(Client * c,char ** tags)817 update_client_views(Client *c, char **tags) {
818 Frame **fp, *f;
819 int cmp;
820
821 fp = &c->frame;
822 while(*fp || *tags) {
823 SET(cmp);
824 while(*fp) {
825 if(*tags) {
826 cmp = strcmp((*fp)->view->name, *tags);
827 if(cmp >= 0)
828 break;
829 }
830
831 f = *fp;
832 detach_from_area(f);
833 *fp = f->cnext;
834 if(c->sel == f)
835 c->sel = *fp;
836 free(f);
837 }
838 if(*tags) {
839 if(!*fp || cmp > 0) {
840 f = create_frame(c, get_view(*tags));
841 if(f->view == screen->sel || !c->sel)
842 c->sel = f;
843 attach_to_view(f->view, f);
844 f->cnext = *fp;
845 *fp = f;
846 }
847 if(*fp) fp=&(*fp)->cnext;
848 tags++;
849 }
850 }
851 update_views();
852 }
853
854 static int
bsstrcmp(const void * a,const void * b)855 bsstrcmp(const void *a, const void *b) {
856 return strcmp((char*)a, (char*)b);
857 }
858
859 static int
strpcmp(const void * a,const void * b)860 strpcmp(const void *a, const void *b) {
861 return strcmp(*(char **)a, *(char **)b);
862 }
863
864 static char *badtags[] = {
865 ".",
866 "..",
867 "sel",
868 };
869
870 void
apply_tags(Client * c,const char * tags)871 apply_tags(Client *c, const char *tags) {
872 uint i, j, k, n;
873 Bool add;
874 char buf[512], last;
875 char *toks[32], *cur;
876
877 buf[0] = 0;
878
879 for(n = 0; tags[n]; n++)
880 if(!isspace(tags[n]))
881 break;
882
883 if(tags[n] == '+' || tags[n] == '-')
884 utflcpy(buf, c->tags, sizeof(c->tags));
885
886 wmii_strlcat(buf, &tags[n], sizeof(buf));
887 trim(buf, " \t/");
888
889 n = 0;
890 add = True;
891 if(buf[0] == '+')
892 n++;
893 else if(buf[0] == '-') {
894 n++;
895 add = False;
896 }
897
898 j = 0;
899 while(buf[n] && n < sizeof(buf) && j < 32) {
900 for(i = n; i < sizeof(buf) - 1; i++)
901 if(buf[i] == '+' || buf[i] == '-' || buf[i] == '\0')
902 break;
903 last = buf[i];
904 buf[i] = '\0';
905
906 cur = nil;
907 if(!strcmp(buf+n, "~"))
908 c->floating = add;
909 else if(!strcmp(buf+n, "!") || !strcmp(buf+n, "sel"))
910 cur = screen->sel->name;
911 else if(!Mbsearch(buf+n, badtags, bsstrcmp))
912 cur = buf+n;
913
914 n = i + 1;
915 if(cur) {
916 if(add)
917 toks[j++] = cur;
918 else {
919 for(i = 0, k = 0; i < j; i++)
920 if(strcmp(toks[i], cur))
921 toks[k++] = toks[i];
922 j = k;
923 }
924 }
925
926 switch(last) {
927 case '+':
928 add = True;
929 break;
930 case '-':
931 add = False;
932 break;
933 case '\0':
934 buf[n] = '\0';
935 break;
936 }
937 }
938
939 if(!j)
940 return;
941
942 qsort(toks, j, sizeof(char *), strpcmp);
943
944 c->tags[0] = '\0';
945 for(i=0, n=0; i < j; i++)
946 if(n == 0 || strcmp(toks[i], toks[n-1])) {
947 if(i > 0)
948 wmii_strlcat(c->tags, "+", sizeof(c->tags));
949 wmii_strlcat(c->tags, toks[i], sizeof(c->tags));
950 toks[n++] = toks[i];
951 }
952 toks[n] = nil;
953
954 update_client_views(c, toks);
955
956 changeprop_char(&c->w, "_WMII_TAGS", "UTF8_STRING", c->tags, strlen(c->tags));
957 }
958
959 void
apply_rules(Client * c)960 apply_rules(Client *c) {
961 Rule *r;
962
963 if(strlen(c->tags))
964 return;
965
966 if(def.tagrules.string)
967 for(r=def.tagrules.rule; r; r=r->next)
968 if(regexec(r->regex, c->props, nil, 0)) {
969 apply_tags(c, r->value);
970 if(c->tags[0] && strcmp(c->tags, "nil"))
971 break;
972 }
973 if(c->tags[0] == '\0')
974 apply_tags(c, "nil");
975 }
976
977