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