wmii

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

ewmh.c (13111B)


      1 /* Copyright ©2007-2010 Kris Maglione <maglione.k at Gmail>
      2  * See LICENSE file for license details.
      3  */
      4 #include "dat.h"
      5 #include <limits.h>
      6 #include "fns.h"
      7 
      8 Window *ewmhwin;
      9 
     10 static void	ewmh_getwinstate(Client*);
     11 static void	ewmh_setstate(Client*, Atom, int);
     12 static void	tick(long, void*);
     13 
     14 static Handlers	client_handlers;
     15 static Handlers	root_handlers;
     16 
     17 static void
     18 clientprop_long(Client *c, int cache, char *prop, char *type, long *data, int l) {
     19 	if(l != c->proplen[cache] || memcmp(&c->propcache[cache], data, l * sizeof *data)) {
     20 		c->proplen[cache] = l;
     21 		memcpy(&c->propcache[cache], data, l * sizeof *data);
     22 		changeprop_long(&c->w, prop, type, data, l);
     23 	}
     24 }
     25 static void
     26 clientprop_del(Client *c, int cache, char *prop) {
     27 	c->proplen[cache] = 0;
     28 	delproperty(&c->w, prop);
     29 }
     30 
     31 void
     32 ewmh_init(void) {
     33 	char myname[] = "wmii";
     34 	long win;
     35 
     36 	ewmhwin = createwindow(&scr.root,
     37 		Rect(0, 0, 1, 1), 0 /*depth*/,
     38 		InputOnly, nil, 0);
     39 
     40 	win = ewmhwin->xid;
     41 	changeprop_long(&scr.root, Net("SUPPORTING_WM_CHECK"), "WINDOW", &win, 1);
     42 	changeprop_long(ewmhwin, Net("SUPPORTING_WM_CHECK"), "WINDOW", &win, 1);
     43 	changeprop_string(ewmhwin, Net("WM_NAME"), myname);
     44 
     45 	changeprop_long(&scr.root, Net("DESKTOP_VIEWPORT"), "CARDINAL",
     46 			(long[2]){0, 0}, 2);
     47 
     48 	pushhandler(&scr.root, &root_handlers, nil);
     49 
     50 	tick(0L, nil);
     51 
     52 	long supported[] = {
     53 		/* Misc */
     54 		NET("SUPPORTED"),
     55 		/* Root Properties/Messages */
     56 		NET("ACTIVE_WINDOW"),
     57 		NET("CLOSE_WINDOW"),
     58 		NET("CURRENT_DESKTOP"),
     59 		/* Client Properties */
     60 		NET("FRAME_EXTENTS"),
     61 		NET("WM_DESKTOP"),
     62 		NET("WM_FULLSCREEN_MONITORS"),
     63 		NET("WM_NAME"),
     64 		NET("WM_PID"),
     65 		NET("WM_STRUT"),
     66 		NET("WM_STRUT_PARTIAL"),
     67 		/* Set this so clients don't update Net("USER_TIME") */
     68 		NET("USER_TIME_WINDOW"),
     69 		/* States */
     70 		NET("WM_STATE"),
     71 		STATE("DEMANDS_ATTENTION"),
     72 		STATE("FULLSCREEN"),
     73 		STATE("SHADED"),
     74 		/* Window Types */
     75 		NET("WM_WINDOW_TYPE"),
     76 		TYPE("DIALOG"),
     77 		TYPE("DOCK"),
     78 		TYPE("NORMAL"),
     79 		TYPE("MENU"),
     80 		TYPE("SPLASH"),
     81 		TYPE("TOOLBAR"),
     82 		/* Actions */
     83 		NET("WM_ALLOWED_ACTIONS"),
     84 		ACTION("FULLSCREEN"),
     85 		/* Desktops */
     86 		NET("DESKTOP_NAMES"),
     87 		NET("NUMBER_OF_DESKTOPS"),
     88 		/* Client List */
     89 		NET("CLIENT_LIST"),
     90 		NET("CLIENT_LIST_STACKING"),
     91 	};
     92 	changeprop_long(&scr.root, Net("SUPPORTED"), "ATOM", supported, nelem(supported));
     93 }
     94 
     95 inline bool
     96 ewmh_responsive_p(Client *c) {
     97 	return c->w.ewmh.ping == 0 || (ulong)(nsec() / 1000000) - c->w.ewmh.ping < PingTime;
     98 }
     99 
    100 void
    101 ewmh_checkresponsive(Client *c) {
    102 
    103 	if(!ewmh_responsive_p(c))
    104 		if(!c->dead)
    105 			frame_draw(c->sel);
    106 		else if(c->dead++ == 1)
    107 			event("Unresponsive %#C\n", c);
    108 }
    109 
    110 static void
    111 tick(long id, void *v) {
    112 	static int count;
    113 	Client *c;
    114 	ulong time;
    115 	int mod, i;
    116 
    117 	time = nsec() / 1000000;
    118 	count++;
    119 	mod = count % PingPartition;
    120 	for(i=0, c=client; c; c=c->next, i++)
    121 		if(c->proto & ProtoPing) {
    122 			if(!ewmh_responsive_p(c))
    123 				ewmh_checkresponsive(c);
    124 			if(i % PingPartition == mod)
    125 				sendmessage(&c->w, "WM_PROTOCOLS", NET("WM_PING"), time, c->w.xid, 0, 0);
    126 			if(i % PingPartition == mod)
    127 				Dprint(DEwmh, "_NET_WM_PING %#C %,uld\n", c, time);
    128 		}
    129 
    130 	ixp_settimer(&srv, PingPeriod / PingPartition, tick, nil);
    131 }
    132 
    133 void
    134 ewmh_updateclientlist(void) {
    135 	Vector_long vec;
    136 	Client *c;
    137 
    138 	vector_linit(&vec);
    139 	for(c=client; c; c=c->next)
    140 		vector_lpush(&vec, c->w.xid);
    141 	changeprop_long(&scr.root, Net("CLIENT_LIST"), "WINDOW", vec.ary, vec.n);
    142 	free(vec.ary);
    143 }
    144 
    145 void
    146 ewmh_updatestacking(void) {
    147 	Vector_long vec;
    148 	Frame *f;
    149 	Area *a;
    150 	View *v;
    151 	int s;
    152 
    153 	vector_linit(&vec);
    154 
    155 	for(v=view; v; v=v->next) {
    156 		foreach_column(v, s, a)
    157 			for(f=a->frame; f; f=f->anext)
    158 				if(f->client->sel == f)
    159 					vector_lpush(&vec, f->client->w.xid);
    160 	}
    161 	for(v=view; v; v=v->next) {
    162 		for(f=v->floating->stack; f; f=f->snext)
    163 			if(!f->snext) break;
    164 		for(; f; f=f->sprev)
    165 			if(f->client->sel == f)
    166 				vector_lpush(&vec, f->client->w.xid);
    167 	}
    168 
    169 	changeprop_long(&scr.root, Net("CLIENT_LIST_STACKING"), "WINDOW", vec.ary, vec.n);
    170 	vector_lfree(&vec);
    171 }
    172 
    173 void
    174 ewmh_initclient(Client *c) {
    175 	long allowed[] = {
    176 		ACTION("FULLSCREEN"),
    177 	};
    178 
    179 	changeprop_long(&c->w, Net("WM_ALLOWED_ACTIONS"), "ATOM",
    180 		allowed, nelem(allowed));
    181 	ewmh_getwintype(c);
    182 	ewmh_getwinstate(c);
    183 	ewmh_getstrut(c);
    184 	ewmh_framesize(c);
    185 	ewmh_updateclientlist();
    186 	pushhandler(&c->w, &client_handlers, c);
    187 }
    188 
    189 void
    190 ewmh_destroyclient(Client *c) {
    191 
    192 	ewmh_updateclientlist();
    193 
    194 	free(c->strut);
    195 }
    196 
    197 #ifdef notdef
    198 static ulong
    199 usertime(Window *w) {
    200 	long *l;
    201 	long ret;
    202 
    203 	ret = 0;
    204 	if(getprop_long(w, Net("WM_USER_TIME_WINDOW"), "CARDINAL", 0, &l, 1)) {
    205 		w = window(*l);
    206 		free(l);
    207 	}
    208 	if(getprop_long(w, Net("WM_USER_TIME"), "CARDINAL", 0, &l, 1)) {
    209 		ret = *l;
    210 		free(l);
    211 	}
    212 	return ret;
    213 }
    214 #endif
    215 
    216 static bool
    217 event_client_clientmessage(Window *w, void *aux, XClientMessageEvent *e) {
    218 	Client *c;
    219 	ulong *l;
    220 	ulong msg;
    221 	int action;
    222 
    223 	c = aux;
    224 	l = (ulong*)e->data.l;
    225 	msg = e->message_type;
    226 	Dprint(DEwmh, "ClientMessage: %A\n", msg);
    227 
    228 	if(msg == NET("WM_STATE")) {
    229 		enum {
    230 			StateUnset,
    231 			StateSet,
    232 			StateToggle,
    233 		};
    234 		if(e->format != 32)
    235 			return false;
    236 
    237 		switch(l[0]) {
    238 		case StateUnset:  action = Off;    break;
    239 		case StateSet:    action = On;     break;
    240 		case StateToggle: action = Toggle; break;
    241 		default: return false;
    242 		}
    243 
    244 		Dprint(DEwmh, "\tAction: %s\n", TOGGLE(action));
    245 		ewmh_setstate(c, l[1], action);
    246 		ewmh_setstate(c, l[2], action);
    247 		return false;
    248 	}else
    249 	if(msg == NET("ACTIVE_WINDOW")) {
    250 		if(e->format != 32)
    251 			return false;
    252 		Dprint(DEwmh, "\tsource:    %uld\n", l[0]);
    253 		Dprint(DEwmh, "\ttimestamp: %,uld\n", l[1]);
    254 		Dprint(DEwmh, "\tactive:    %#ulx\n", l[2]);
    255 		Dprint(DEwmh, "\twindow:    %#ulx\n", e->window);
    256 		Dprint(DEwmh, "\tclient:    %C\n", c);
    257 
    258 		if(l[0] == SourceClient && !(c->permission & PermActivate))
    259 			return false;
    260 		if(l[0] == SourceClient || l[0] == SourcePager)
    261 			focus(c, true);
    262 		return false;
    263 	}else
    264 	if(msg == NET("CLOSE_WINDOW")) {
    265 		if(e->format != 32)
    266 			return false;
    267 		Dprint(DEwmh, "\tsource: %ld\n", l[0]);
    268 		Dprint(DEwmh, "\twindow: %#ulx\n", e->window);
    269 		client_kill(c, true);
    270 		return false;
    271 	}
    272 
    273 	return false;
    274 }
    275 
    276 static bool
    277 event_client_property(Window *w, void *aux, XPropertyEvent *e) {
    278 	return ewmh_prop(aux, e->atom);
    279 }
    280 
    281 static Handlers client_handlers = {
    282 	.message = event_client_clientmessage,
    283 	.property = event_client_property,
    284 };
    285 
    286 bool
    287 ewmh_prop(Client *c, Atom a) {
    288 	if(a == NET("WM_WINDOW_TYPE"))
    289 		ewmh_getwintype(c);
    290 	else
    291 	if(a == NET("WM_STRUT_PARTIAL"))
    292 		ewmh_getstrut(c);
    293 	else
    294 		return true;
    295 	return false;
    296 }
    297 
    298 typedef struct Prop Prop;
    299 struct Prop {
    300 	char*	name;
    301 	long	mask;
    302 	Atom	atom;
    303 };
    304 
    305 static long
    306 getmask(Prop *props, ulong *vals, int n) {
    307 	Prop *p;
    308 	long ret;
    309 
    310 	if(props[0].atom == 0)
    311 		for(p=props; p->name; p++)
    312 			p->atom = xatom(p->name);
    313 
    314 	ret = 0;
    315 	while(n--) {
    316 		Dprint(DEwmh, "\tvals[%d] = \"%A\"\n", n, vals[n]);
    317 		for(p=props; p->name; p++)
    318 			if(p->atom == vals[n]) {
    319 				ret |= p->mask;
    320 				break;
    321 			}
    322 	}
    323 	return ret;
    324 }
    325 
    326 static long
    327 getprop_mask(Window *w, char *prop, Prop *props) {
    328 	ulong *vals;
    329 	long n, mask;
    330 
    331 	n = getprop_ulong(w, prop, "ATOM",
    332 		0L, &vals, 16);
    333 	mask = getmask(props, vals, n);
    334 	free(vals);
    335 	return mask;
    336 }
    337 
    338 void
    339 ewmh_getwintype(Client *c) {
    340 	static Prop props[] = {
    341 		{Type("DESKTOP"), TypeDesktop},
    342 		{Type("DOCK"),    TypeDock},
    343 		{Type("TOOLBAR"), TypeToolbar},
    344 		{Type("MENU"),    TypeMenu},
    345 		{Type("UTILITY"), TypeUtility},
    346 		{Type("SPLASH"),  TypeSplash},
    347 		{Type("DIALOG"),  TypeDialog},
    348 		{Type("NORMAL"),  TypeNormal},
    349 		{0, }
    350 	};
    351 	long mask;
    352 
    353 	mask = getprop_mask(&c->w, Net("WM_WINDOW_TYPE"), props);
    354 
    355 	c->w.ewmh.type = mask;
    356 	if(mask & (TypeDock|TypeMenu|TypeToolbar)) {
    357 		c->borderless = true;
    358 		c->titleless = true;
    359 	}
    360 	if(mask & (TypeSplash|TypeDock))
    361 		c->nofocus = true;
    362 }
    363 
    364 static void
    365 ewmh_getwinstate(Client *c) {
    366 	ulong *vals;
    367 	long n;
    368 
    369 	n = getprop_ulong(&c->w, Net("WM_STATE"), "ATOM",
    370 		0L, &vals, 16);
    371 	while(--n >= 0)
    372 		ewmh_setstate(c, vals[n], On);
    373 	free(vals);
    374 }
    375 
    376 long
    377 ewmh_protocols(Window *w) {
    378 	static Prop props[] = {
    379 		{"WM_DELETE_WINDOW", ProtoDelete},
    380 		{"WM_TAKE_FOCUS", ProtoTakeFocus},
    381 		{Net("WM_PING"), ProtoPing},
    382 		{0, }
    383 	};
    384 
    385 	return getprop_mask(w, "WM_PROTOCOLS", props);
    386 }
    387 
    388 void
    389 ewmh_getstrut(Client *c) {
    390 	enum {
    391 		Left, Right, Top, Bottom,
    392 		LeftMin, LeftMax,
    393 		RightMin, RightMax,
    394 		TopMin, TopMax,
    395 		BottomMin, BottomMax,
    396 		Last
    397 	};
    398 	long *strut;
    399 	ulong n;
    400 
    401 	if(c->strut != nil)
    402 		free(c->strut);
    403 	c->strut = nil;
    404 
    405 	n = getprop_long(&c->w, Net("WM_STRUT_PARTIAL"), "CARDINAL",
    406 		0L, &strut, Last);
    407 	if(n != Last) {
    408 		free(strut);
    409 		n = getprop_long(&c->w, Net("WM_STRUT"), "CARDINAL",
    410 			0L, &strut, 4L);
    411 		if(n != 4) {
    412 			free(strut);
    413 			return;
    414 		}
    415 		Dprint(DEwmh, "ewmh_getstrut(%#C[%C]) Using WM_STRUT\n", c, c);
    416 		strut = erealloc(strut, Last * sizeof *strut);
    417 		strut[LeftMin] = strut[RightMin] = 0;
    418 		strut[LeftMax] = strut[RightMax] = INT_MAX;
    419 		strut[TopMin] = strut[BottomMin] = 0;
    420 		strut[TopMax] = strut[BottomMax] = INT_MAX;
    421 	}
    422 	c->strut = emalloc(sizeof *c->strut);
    423 	c->strut->left =   Rect(0,                strut[LeftMin],  strut[Left],      strut[LeftMax]);
    424 	c->strut->right =  Rect(-strut[Right],    strut[RightMin], 0,                strut[RightMax]);
    425 	c->strut->top =    Rect(strut[TopMin],    0,               strut[TopMax],    strut[Top]);
    426 	c->strut->bottom = Rect(strut[BottomMin], -strut[Bottom],  strut[BottomMax], 0);
    427 	Dprint(DEwmh, "ewmh_getstrut(%#C[%C])\n", c, c);
    428 	Dprint(DEwmh, "\ttop: %R\n", c->strut->top);
    429 	Dprint(DEwmh, "\tleft: %R\n", c->strut->left);
    430 	Dprint(DEwmh, "\tright: %R\n", c->strut->right);
    431 	Dprint(DEwmh, "\tbottom: %R\n", c->strut->bottom);
    432 	free(strut);
    433 	view_update(selview);
    434 }
    435 
    436 static void
    437 ewmh_setstate(Client *c, Atom state, int action) {
    438 
    439 	Dprint(DEwmh, "\tSTATE = %A\n", state);
    440 	if(state == 0)
    441 		return;
    442 
    443 	if(state == STATE("FULLSCREEN"))
    444 		fullscreen(c, action, -1);
    445 	else
    446 	if(state == STATE("DEMANDS_ATTENTION"))
    447 		client_seturgent(c, action, UrgClient);
    448 }
    449 
    450 static bool
    451 event_root_clientmessage(Window *w, void *aux, XClientMessageEvent *e) {
    452 	Client *c;
    453 	View *v;
    454 	ulong *l;
    455 	ulong msg;
    456 	int i;
    457 
    458 	l = (ulong*)e->data.l;
    459 	msg = e->message_type;
    460 	Debug(DEwmh)
    461 		if(msg != xatom("WM_PROTOCOLS") && l[0] != NET("WM_PING"))
    462 			Dprint(DEwmh, "ClientMessage: %A\n", msg);
    463 
    464 	if(msg == NET("CURRENT_DESKTOP")) {
    465 		if(e->format != 32)
    466 			return false;
    467 		for(v=view, i=l[0]; v; v=v->next, i--)
    468 			if(i == 0)
    469 				break;
    470 		Dprint(DEwmh, "\t%s\n", v->name);
    471 		if(i == 0)
    472 			view_select(v->name);
    473 		return 1;
    474 	}
    475 	if(msg == xatom("WM_PROTOCOLS")) {
    476 		if(e->format != 32)
    477 			return false;
    478 		if(l[0] == NET("WM_PING")) {
    479 			if(e->window != scr.root.xid)
    480 				return false;
    481 			if(!(c = win2client(l[2])))
    482 				return false;
    483 			i = ewmh_responsive_p(c);
    484 			c->w.ewmh.ping = nsec() / 1000000;
    485 			c->w.ewmh.lag = (c->w.ewmh.ping & 0xffffffff) - (l[1] & 0xffffffff);
    486 			if(i == false)
    487 				frame_draw(c->sel);
    488 			return false;
    489 		}
    490 		return false;
    491 	}
    492 
    493 	return false;
    494 }
    495 
    496 static Handlers root_handlers = {
    497 	.message = event_root_clientmessage,
    498 };
    499 
    500 
    501 void
    502 ewmh_framesize(Client *c) {
    503 	Rectangle rc, rf;
    504 	Frame *f;
    505 
    506 	if((f = c->sel)) {
    507 		rc = f->crect;
    508 		rf = f->r;
    509 	}
    510 	else {
    511 		rf = frame_client2rect(c, ZR, c->floating);
    512 		rc = rectsubpt(ZR, rf.min);
    513 	}
    514 
    515 	long extents[] = {
    516 		rc.min.x, Dx(rf) - rc.max.x,
    517 		rc.min.y, Dy(rf) - rc.max.y,
    518 	};
    519 	clientprop_long(c, PExtents, Net("FRAME_EXTENTS"), "CARDINAL",
    520 			extents, nelem(extents));
    521 }
    522 
    523 void
    524 ewmh_updatestate(Client *c) {
    525 	long state[16];
    526 	Frame *f;
    527 	int i;
    528 
    529 	f = c->sel;
    530 	if(f == nil || f->view != selview)
    531 		return;
    532 
    533 	i = 0;
    534 	if(f->collapsed)
    535 		state[i++] = STATE("SHADED");
    536 	if(c->fullscreen >= 0)
    537 		state[i++] = STATE("FULLSCREEN");
    538 	if(c->urgent)
    539 		state[i++] = STATE("DEMANDS_ATTENTION");
    540 
    541 	if(i > 0)
    542 		clientprop_long(c, PState, Net("WM_STATE"), "ATOM", state, i);
    543 	else
    544 		clientprop_del(c, PState, Net("WM_STATE"));
    545 
    546 	if(c->fullscreen >= 0)
    547 		clientprop_long(c, PMonitors, Net("WM_FULLSCREEN_MONITORS"), "CARDINAL",
    548 				(long[]) { c->fullscreen, c->fullscreen,
    549 					   c->fullscreen, c->fullscreen },
    550 				4);
    551 	else
    552 		clientprop_del(c, PMonitors, Net("WM_FULLSCREEN_MONITORS"));
    553 }
    554 
    555 /* Views */
    556 void
    557 ewmh_updateviews(void) {
    558 	View *v;
    559 	Vector_ptr tags;
    560 	long i;
    561 
    562 	if(starting)
    563 		return;
    564 
    565 	vector_pinit(&tags);
    566 	for(v=view, i=0; v; v=v->next, i++)
    567 		vector_ppush(&tags, v->name);
    568 	vector_ppush(&tags, nil);
    569 	changeprop_textlist(&scr.root, Net("DESKTOP_NAMES"), "UTF8_STRING", (char**)tags.ary);
    570 	changeprop_long(&scr.root, Net("NUMBER_OF_DESKTOPS"), "CARDINAL", &i, 1);
    571 	vector_pfree(&tags);
    572 	ewmh_updateview();
    573 	ewmh_updateclients();
    574 }
    575 
    576 static int
    577 viewidx(View *v) {
    578 	View *vp;
    579 	int i;
    580 
    581 	for(vp=view, i=0; vp; vp=vp->next, i++)
    582 		if(vp == v)
    583 			break;
    584 	assert(vp);
    585 	return i;
    586 }
    587 
    588 void
    589 ewmh_updateview(void) {
    590 	long i;
    591 
    592 	if(starting)
    593 		return;
    594 
    595 	i = viewidx(selview);
    596 	changeprop_long(&scr.root, Net("CURRENT_DESKTOP"), "CARDINAL", &i, 1);
    597 }
    598 
    599 void
    600 ewmh_updateclient(Client *c) {
    601 	long i;
    602 
    603 	i = -1;
    604 	if(c->sel)
    605 		i = viewidx(c->sel->view);
    606 	clientprop_long(c, PDesktop, Net("WM_DESKTOP"), "CARDINAL", &i, 1);
    607 }
    608 
    609 void
    610 ewmh_updateclients(void) {
    611 	Client *c;
    612 
    613 	if(starting)
    614 		return;
    615 
    616 	for(c=client; c; c=c->next)
    617 		ewmh_updateclient(c);
    618 }
    619