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