wmii

git clone git://oldgit.suckless.org/wmii/
Log | Files | Refs | README | LICENSE

client.c (23235B)


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