1 /* Skippy - Seduces Kids Into Perversion
2  *
3  * Copyright (C) 2004 Hyriand <hyriand@thegraveyard.org>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19 
20 #include "skippy.h"
21 
22 Atom
23 	/* Root pixmap / wallpaper atoms */
24 	_XROOTPMAP_ID,
25 	ESETROOT_PMAP_ID,
26 
27 	// ICCWM atoms
28 	WM_PROTOCOLS,
29 	WM_DELETE_WINDOW,
30 
31 	// Window type atoms
32 	_NET_WM_WINDOW_TYPE_DESKTOP,
33 	_NET_WM_WINDOW_TYPE_DOCK,
34 	_NET_WM_WINDOW_TYPE_NORMAL,
35 	_NET_WM_WINDOW_TYPE_TOOLTIP,
36 
37 	// EWMH atoms
38 	_NET_CLOSE_WINDOW,
39 	_NET_WM_STATE,
40 	_NET_WM_STATE_SHADED,
41 	_NET_ACTIVE_WINDOW,
42 	_NET_WM_ICON,
43 	_NET_CURRENT_DESKTOP,
44 
45 	// Other atoms
46 	KWM_WIN_ICON;
47 
48 static Atom
49 	/* Generic atoms */
50 	XA_WM_STATE,
51 	WM_CLIENT_LEADER,
52 	XA_UTF8_STRING,
53 
54 	/* EWMH atoms */
55 	_NET_SUPPORTING_WM_CHECK,
56 	_NET_SUPPORTED,
57 	_NET_NUMBER_OF_DESKTOPS,
58 	_NET_CLIENT_LIST,
59 	_NET_CLIENT_LIST_STACKING,
60 	_NET_WM_DESKTOP,
61 	_NET_WM_STATE_HIDDEN,
62 	_NET_WM_STATE_SKIP_TASKBAR,
63 	_NET_WM_STATE_SKIP_PAGER,
64 	_NET_WM_STATE_FULLSCREEN,
65 	_NET_WM_STATE_ABOVE,
66 	_NET_WM_STATE_STICKY,
67 	_NET_WM_WINDOW_TYPE,
68 	_NET_WM_VISIBLE_NAME,
69 	_NET_WM_NAME,
70 
71 	/* Old gnome atoms */
72 	_WIN_SUPPORTING_WM_CHECK,
73 	_WIN_WORKSPACE,
74 	_WIN_WORKSPACE_COUNT,
75 	_WIN_PROTOCOLS,
76 	_WIN_CLIENT_LIST,
77 	_WIN_STATE,
78 	_WIN_HINTS;
79 
80 /* From WindowMaker's gnome.c */
81 #define WIN_HINTS_SKIP_FOCUS      (1<<0) /*"alt-tab" skips this win*/
82 #define WIN_HINTS_SKIP_WINLIST    (1<<1) /*do not show in window list*/
83 #define WIN_HINTS_SKIP_TASKBAR    (1<<2) /*do not show on taskbar*/
84 #define WIN_HINTS_GROUP_TRANSIENT (1<<3) /*Reserved - definition is unclear*/
85 #define WIN_HINTS_FOCUS_ON_CLICK  (1<<4) /*app only accepts focus if clicked*/
86 #define WIN_HINTS_DO_NOT_COVER    (1<<5) /* attempt to not cover this window */
87 
88 
89 #define WIN_STATE_STICKY          (1<<0) /*everyone knows sticky*/
90 #define WIN_STATE_MINIMIZED       (1<<1) /*Reserved - definition is unclear*/
91 #define WIN_STATE_MAXIMIZED_VERT  (1<<2) /*window in maximized V state*/
92 #define WIN_STATE_MAXIMIZED_HORIZ (1<<3) /*window in maximized H state*/
93 #define WIN_STATE_HIDDEN          (1<<4) /*not on taskbar but window visible*/
94 #define WIN_STATE_SHADED          (1<<5) /*shaded (MacOS / Afterstep style)*/
95 /* these are bogus states defined in "the spec" */
96 #define WIN_STATE_HID_WORKSPACE   (1<<6) /*not on current desktop*/
97 #define WIN_STATE_HID_TRANSIENT   (1<<7) /*owner of transient is hidden*/
98 #define WIN_STATE_FIXED_POSITION  (1<<8) /*window is fixed in position even*/
99 #define WIN_STATE_ARRANGE_IGNORE  (1<<9) /*ignore for auto arranging*/
100 
101 /**
102  * @brief Wrapper of XInternAtom().
103  */
104 static inline Atom
get_atom(session_t * ps,const char * name)105 get_atom(session_t *ps, const char *name) {
106 	return XInternAtom(ps->dpy, name, False);
107 }
108 
109 /**
110  * @brief Initialize X atoms.
111  */
112 void
wm_get_atoms(session_t * ps)113 wm_get_atoms(session_t *ps) {
114 #define T_GETATOM(name) name = get_atom(ps, # name)
115 	XA_WM_STATE = get_atom(ps, "WM_STATE");
116 	T_GETATOM(WM_CLIENT_LEADER);
117 	XA_UTF8_STRING = get_atom(ps, "UTF8_STRING");
118 
119 	T_GETATOM(_XROOTPMAP_ID);
120 	T_GETATOM(ESETROOT_PMAP_ID);
121 
122 	T_GETATOM(WM_PROTOCOLS),
123 	T_GETATOM(WM_DELETE_WINDOW),
124 
125 	T_GETATOM(_NET_SUPPORTING_WM_CHECK);
126 	T_GETATOM(_NET_SUPPORTED);
127 	T_GETATOM(_NET_NUMBER_OF_DESKTOPS);
128 	T_GETATOM(_NET_CLIENT_LIST);
129 	T_GETATOM(_NET_CLIENT_LIST_STACKING);
130 	T_GETATOM(_NET_CURRENT_DESKTOP);
131 	T_GETATOM(_NET_WM_DESKTOP);
132 	T_GETATOM(_NET_WM_STATE);
133 	T_GETATOM(_NET_WM_STATE_HIDDEN);
134 	T_GETATOM(_NET_WM_STATE_SKIP_TASKBAR);
135 	T_GETATOM(_NET_WM_STATE_SKIP_PAGER);
136 	T_GETATOM(_NET_WM_STATE_FULLSCREEN);
137 	T_GETATOM(_NET_WM_STATE_ABOVE);
138 	T_GETATOM(_NET_WM_STATE_STICKY);
139 	T_GETATOM(_NET_WM_WINDOW_TYPE);
140 	T_GETATOM(_NET_WM_WINDOW_TYPE_DESKTOP);
141 	T_GETATOM(_NET_WM_WINDOW_TYPE_DOCK);
142 	T_GETATOM(_NET_WM_WINDOW_TYPE_NORMAL);
143 	T_GETATOM(_NET_WM_WINDOW_TYPE_TOOLTIP);
144 	T_GETATOM(_NET_WM_VISIBLE_NAME);
145 	T_GETATOM(_NET_WM_NAME);
146 	T_GETATOM(_NET_ACTIVE_WINDOW);
147 	T_GETATOM(_NET_CLOSE_WINDOW);
148 	T_GETATOM(_NET_WM_STATE_SHADED);
149 	T_GETATOM(_NET_WM_ICON);
150 
151 	T_GETATOM(KWM_WIN_ICON);
152 
153 	T_GETATOM(_WIN_SUPPORTING_WM_CHECK);
154 	T_GETATOM(_WIN_WORKSPACE);
155 	T_GETATOM(_WIN_WORKSPACE_COUNT);
156 	T_GETATOM(_WIN_PROTOCOLS);
157 	T_GETATOM(_WIN_CLIENT_LIST);
158 	T_GETATOM(_WIN_STATE);
159 	T_GETATOM(_WIN_HINTS);
160 #undef T_GETATOM
161 }
162 
163 bool
wm_check_netwm(session_t * ps)164 wm_check_netwm(session_t *ps) {
165 	Display *dpy = ps->dpy;
166 
167 	Window wm_check = None;
168 	unsigned char *data = NULL;
169 
170 	int real_format = 0;
171 	Atom real_type = None;
172 	unsigned long items_read = 0, items_left = 0;
173 
174 	bool success = (Success == XGetWindowProperty(dpy, ps->root,
175 				_NET_SUPPORTING_WM_CHECK,
176 				0L, 1L, False, XA_WINDOW, &real_type, &real_format,
177 				&items_read, &items_left, &data)
178 			&& items_read && data && 32 == real_format);
179 	if (success)
180 		wm_check = *((long *)data);
181 	spxfree(&data);
182 	if (!success) return false;
183 
184 	success = (Success == XGetWindowProperty(dpy, wm_check,
185 				_NET_SUPPORTING_WM_CHECK,
186 				0L, 1L, False, XA_WINDOW, &real_type, &real_format,
187 				&items_read, &items_left, &data)
188 			&& items_read && data && 32 == real_format
189 			&& wm_check == *((long *)data));
190 	spxfree(&data);
191 	if (!success) return false;
192 
193 	success = (Success == XGetWindowProperty(dpy, ps->root, _NET_SUPPORTED,
194 	                  0L, 8192L, False, XA_ATOM, &real_type, &real_format,
195 	                  &items_read, &items_left, &data)
196 			&& items_read && data && 32 == real_format);
197 	if (!success)
198 		items_read = 0;
199 
200 	long *ldata = (long *) data;
201 	int req = 0;
202 	for (int i = 0; i < items_read; i++) {
203 		if (_NET_NUMBER_OF_DESKTOPS == ldata[i])
204 			req |= 1;
205 		else if (_NET_CURRENT_DESKTOP == ldata[i])
206 			req |= 2;
207 		else if (_NET_WM_STATE == ldata[i])
208 			req |= 4;
209 		else if (_NET_CLIENT_LIST == ldata[i])
210 			req |= 8;
211 		else if (_NET_CLIENT_LIST_STACKING == ldata[i]) {
212 			req |= 8;
213 			_NET_CLIENT_LIST = _NET_CLIENT_LIST_STACKING;
214 		}
215 		else if (_NET_WM_STATE_FULLSCREEN == ldata[i])
216 			ps->has_ewmh_fullscreen = true;
217 	}
218 
219 	spxfree(&data);
220 
221 	return ((req & 15) == 15);
222 }
223 
224 bool
wm_check_gnome(session_t * ps)225 wm_check_gnome(session_t *ps) {
226 	Display *dpy = ps->dpy;
227 	unsigned char *data = NULL;
228 
229 	Window wm_check = None;
230 	int real_format = 0;
231 	Atom real_type = None;
232 	unsigned long items_read = 0, items_left = 0;
233 
234 	// Make sure _WIN_SUPPORTING_WM_CHECK is present on root window
235 	bool success = (Success ==
236 			XGetWindowProperty(dpy, ps->root, _WIN_SUPPORTING_WM_CHECK,
237 				0L, 1L, False, XA_CARDINAL, &real_type, &real_format,
238 				&items_read, &items_left, &data)
239 			&& items_read && data && 32 == real_format);
240 
241 	if (success)
242 		wm_check = ((long *)data)[0];
243 	spxfree(&data);
244 	if (!success)
245 		return success;
246 
247 	/*
248 	// Make sure _WIN_SUPPORTING_WM_CHECK is present on the WM check window
249 	// as well
250 	success = (Success == XGetWindowProperty(dpy, wm_check, _WIN_SUPPORTING_WM_CHECK,
251 				0L, 1L, False, XA_CARDINAL, &real_type, &real_format,
252 				&items_read, &items_left, &data) && items_real
253 			&& wm_check == *((long *) data));
254 	spxfree(&data);
255 	if (!success)
256 		return success;
257 	*/
258 
259 	// Check supported protocols
260 	success = (Success == XGetWindowProperty(dpy, ps->root, _WIN_PROTOCOLS,
261 				0L, 8192L, False, XA_ATOM, &real_type, &real_format,
262 				&items_read, &items_left, &data)
263 			&& items_read && data && 32 == real_format);
264 	if (!success)
265 		items_read = 0;
266 
267 	long *ldata = (long *) data;
268 	int req = 0;
269 	for (int i = 0; i < items_read; i++) {
270 		if (_WIN_WORKSPACE == ldata[i])
271 			req |= 1;
272 		else if (_WIN_WORKSPACE_COUNT == ldata[i])
273 			req |= 2;
274 		else if (_WIN_STATE == ldata[i])
275 			req |= 4;
276 		else if (_WIN_CLIENT_LIST == ldata[i])
277 			req |= 8;
278 	}
279 	spxfree(&data);
280 
281 	return ((req & 15) == 15);
282 }
283 
284 /**
285  * @brief Find the client window under a specific frame window.
286  *
287  * Using a depth-first search.
288  */
289 static Window
wm_find_client(session_t * ps,Window wid)290 wm_find_client(session_t *ps, Window wid) {
291 	dlist *stack = dlist_add(NULL, (void *) wid);
292 	Window result = None;
293 	while (stack) {
294 		dlist *stack2 = NULL;
295 		foreach_dlist (stack) {
296 			Window cur = (Window) iter->data;
297 			if (wid_has_prop(ps, cur, XA_WM_STATE)) {
298 				result = cur;
299 				break;
300 			}
301 			Window *children = NULL;
302 			unsigned nchildren = 0;
303 			Window rroot = None, rparent = None;
304 			if (XQueryTree(ps->dpy, cur, &rroot, &rparent,
305 						&children, &nchildren) && nchildren && children)
306 				for (int i = 0; i < nchildren; ++i)
307 					stack2 = dlist_add(stack2, (void *) children[i]);
308 			sxfree(children);
309 		}
310 		dlist_free(stack);
311 		if (result) {
312 			free(stack2);
313 			break;
314 		}
315 		else {
316 			stack = stack2;
317 		}
318 	}
319 
320 	return result;
321 }
322 
323 static inline dlist *
wm_get_stack_fromprop(session_t * ps,Window root,Atom a)324 wm_get_stack_fromprop(session_t *ps, Window root, Atom a) {
325 	dlist *l = NULL;
326 	unsigned char *data = NULL;
327 	int real_format = 0;
328 	Atom real_type = None;
329 	unsigned long items_read = 0, items_left = 0;
330 	int status = XGetWindowProperty(ps->dpy, root, a,
331 			0L, 8192L, False, XA_WINDOW, &real_type, &real_format,
332 			&items_read, &items_left, &data);
333 	if (Success == status && 32 == real_format && data)
334 		for (int i = 0; i < items_read; i++) {
335 			l = dlist_add(l, (void *) ((long *) data)[i]);
336 		}
337 
338 	sxfree(data);
339 	return l;
340 }
341 
342 static inline dlist *
wm_get_stack_sub(session_t * ps,Window root)343 wm_get_stack_sub(session_t *ps, Window root) {
344 	dlist *l = NULL;
345 
346 	if (!(ps->o.acceptOvRedir || ps->o.acceptWMWin)) {
347 		// EWMH
348 		l = wm_get_stack_fromprop(ps, root, _NET_CLIENT_LIST);
349 		if (l) {
350 			printfdf("(): Retrieved window stack from _NET_CLIENT_LIST.");
351 			return l;
352 		}
353 
354 		// GNOME WM
355 		l = wm_get_stack_fromprop(ps, root, _WIN_CLIENT_LIST);
356 		if (l) {
357 			printfdf("(): Retrieved window stack from _WIN_CLIENT_LIST.");
358 			return l;
359 		}
360 	}
361 
362 	// Stupid method
363 	{
364 		Window *children = NULL;
365 		unsigned nchildren = 0;
366 		Window rroot = None, rparent = None;
367 		if (XQueryTree(ps->dpy, root, &rroot, &rparent,
368 					&children, &nchildren) && nchildren && children) {
369 			// Fluxbox sets override-redirect on its frame windows,
370 			// so we can't skip override-redirect windows.
371 			for (int i = 0; i < nchildren; ++i) {
372 				Window wid = children[i];
373 				Window client = wm_find_client(ps, wid);
374 				if (!client && (ps->o.acceptOvRedir || ps->o.acceptWMWin)) {
375 					XWindowAttributes attr = { };
376 					if (XGetWindowAttributes(ps->dpy, wid, &attr)
377 						&& ((attr.override_redirect && ps->o.acceptOvRedir)
378 								|| (!attr.override_redirect && ps->o.acceptWMWin))) {
379 						client = wid;
380 					}
381 				}
382 				if (client)
383 					l = dlist_add(l, (void *) client);
384 			}
385 		}
386 		sxfree(children);
387 		printfdf("(): Retrieved window stack by querying all children.");
388 	}
389 
390 	return l;
391 }
392 
393 dlist *
wm_get_stack(session_t * ps)394 wm_get_stack(session_t *ps) {
395 	if (ps->o.includeAllScreens) {
396 		dlist *l = NULL;
397 		for (int i = 0; i < ScreenCount(ps->dpy); ++i)
398 			l = dlist_join(l, wm_get_stack_sub(ps, RootWindow(ps->dpy, i)));
399 		return l;
400 	}
401 	else return wm_get_stack_sub(ps, ps->root);
402 }
403 
404 Pixmap
wm_get_root_pmap(Display * dpy)405 wm_get_root_pmap(Display *dpy)
406 {
407 	Pixmap rootpmap = None;
408 	unsigned char *data;
409 	int status, real_format;
410 	Atom real_type;
411 	unsigned long items_read, items_left;
412 
413 	status = XGetWindowProperty(dpy, DefaultRootWindow(dpy), _XROOTPMAP_ID,
414 	                  0L, 1L, False, XA_PIXMAP, &real_type, &real_format,
415 	                  &items_read, &items_left, &data);
416 	if(status != Success) {
417 		status = XGetWindowProperty(dpy, DefaultRootWindow(dpy), ESETROOT_PMAP_ID,
418 		                  0L, 1L, False, XA_PIXMAP, &real_type, &real_format,
419 		                  &items_read, &items_left, &data);
420 		if(status != Success)
421 			return None;
422 	}
423 
424 	if(items_read)
425 		rootpmap = ((Pixmap*)data)[0];
426 
427 	XFree(data);
428 
429 	return rootpmap;
430 }
431 
432 long
wm_get_current_desktop(session_t * ps)433 wm_get_current_desktop(session_t *ps) {
434 	winprop_t prop = { };
435 	long desktop = 0;
436 
437 	prop = wid_get_prop(ps, ps->root, _NET_CURRENT_DESKTOP,
438 			1, XA_CARDINAL, 0);
439 	desktop = winprop_get_int(&prop);
440 	free_winprop(&prop);
441 	if (!desktop) {
442 		prop = wid_get_prop(ps, ps->root, _WIN_WORKSPACE, 1, XA_CARDINAL, 0);
443 		desktop = winprop_get_int(&prop);
444 		free_winprop(&prop);
445 	}
446 
447 	return desktop;
448 }
449 
450 /**
451  * @brief Retrieve the title of a window.
452  *
453  * Must be a UTF-8 string.
454  */
455 FcChar8 *
wm_get_window_title(session_t * ps,Window wid,int * length_return)456 wm_get_window_title(session_t *ps, Window wid, int *length_return) {
457 	char *ret = NULL;
458 
459 	// wm_wid_get_prop_utf8() is certainly more appropriate, yet
460 	// I found Xlib failing to interpret CJK characters in WM_NAME with
461 	// type STRING, so we have to keep using the old way here.
462 	ret = wm_wid_get_prop_rstr(ps, wid, _NET_WM_VISIBLE_NAME);
463 	if (!ret)
464 		ret = wm_wid_get_prop_rstr(ps, wid, _NET_WM_NAME);
465 	if (!ret)
466 		ret = wm_wid_get_prop_rstr(ps, wid, XA_WM_NAME);
467 	if (ret && length_return)
468 		*length_return = strlen(ret);
469 
470 	return (FcChar8 *) ret;
471 }
472 
473 Window
wm_get_group_leader(Display * dpy,Window window)474 wm_get_group_leader(Display *dpy, Window window)
475 {
476 	unsigned char *data;
477 	int status, real_format;
478 	Atom real_type;
479 	unsigned long items_read, items_left;
480 	Window leader = None;
481 
482 	status = XGetWindowProperty(dpy, window, WM_CLIENT_LEADER,
483 	                  0, 1, False, XA_WINDOW, &real_type, &real_format,
484 	                  &items_read, &items_left, &data);
485 
486 	if(status != Success)
487 	{
488 		XWMHints *hints = XGetWMHints(dpy, window);
489 		if(! hints)
490 			return None;
491 
492 		if(hints->flags & WindowGroupHint)
493 			leader = hints->window_group;
494 
495 		return leader;
496 	}
497 
498 	if(items_read)
499 		leader = ((Window*)data)[0];
500 
501 	XFree(data);
502 
503 	return leader;
504 }
505 
506 void
wm_set_fullscreen(session_t * ps,Window window,int x,int y,unsigned width,unsigned height)507 wm_set_fullscreen(session_t *ps, Window window,
508 		int x, int y, unsigned width, unsigned height) {
509 	Display *dpy = ps->dpy;
510 	if (ps->o.useNetWMFullscreen && ps->has_ewmh_fullscreen) {
511 		Atom props[] = {
512 			_NET_WM_STATE_FULLSCREEN,
513 			_NET_WM_STATE_SKIP_TASKBAR,
514 			_NET_WM_STATE_SKIP_PAGER,
515 			_NET_WM_STATE_ABOVE,
516 			_NET_WM_STATE_STICKY,
517 			0,
518 		};
519 		long desktop = -1L;
520 
521 		XChangeProperty(dpy, window, _NET_WM_STATE, XA_ATOM, 32,
522 				PropModeReplace, (unsigned char *) props,
523 				sizeof(props) / sizeof(props[0]) - 1);
524 		XChangeProperty(dpy, window, _NET_WM_DESKTOP, XA_CARDINAL, 32,
525 				PropModeReplace, (unsigned char *) &desktop, 1);
526 	}
527 	else {
528 		XSetWindowAttributes wattr;
529 		wattr.override_redirect = True;
530 		XChangeWindowAttributes(dpy, window, CWOverrideRedirect, &wattr);
531 		XMoveResizeWindow(dpy, window, x, y, width, height);
532 	}
533 }
534 
535 bool
wm_validate_window(session_t * ps,Window wid)536 wm_validate_window(session_t *ps, Window wid) {
537 	winprop_t prop = { };
538 	bool result = true;
539 
540 	// Check _NET_WM_WINDOW_TYPE
541 	prop = wid_get_prop(ps, wid, _NET_WM_WINDOW_TYPE, 1, XA_ATOM, 32);
542 	{
543 		long v = winprop_get_int(&prop);
544 		if ((_NET_WM_WINDOW_TYPE_DESKTOP == v
545 					|| _NET_WM_WINDOW_TYPE_DOCK == v))
546 			result = false;
547 	}
548 	free_winprop(&prop);
549 
550 	if (!result) return result;
551 
552 	if (WMPSN_EWMH == ps->wmpsn) {
553 		// Check _NET_WM_STATE
554 		prop = wid_get_prop(ps, wid, _NET_WM_STATE, 8192, XA_ATOM, 32);
555 		for (int i = 0; result && i < prop.nitems; i++) {
556 			long v = prop.data32[i];
557 			if (!ps->o.showUnmapped && _NET_WM_STATE_HIDDEN == v)
558 				result = false;
559 			else if (ps->o.ignoreSkipTaskbar
560 					&& _NET_WM_STATE_SKIP_TASKBAR == v)
561 				result = false;
562 			else if (_NET_WM_STATE_SHADED == v)
563 				result = false;
564 		}
565 		free_winprop(&prop);
566 
567 	}
568 	else if (WMPSN_GNOME == ps->wmpsn) {
569 		// Check _WIN_STATE
570 		prop = wid_get_prop(ps, wid, _WIN_STATE, 1, XA_CARDINAL, 0);
571 		if (!ps->o.showUnmapped && winprop_get_int(&prop)
572 				& (WIN_STATE_MINIMIZED | WIN_STATE_SHADED | WIN_STATE_HIDDEN))
573 			result = false;
574 		free_winprop(&prop);
575 
576 		if (result && ps->o.ignoreSkipTaskbar) {
577 			prop = wid_get_prop(ps, wid, _WIN_HINTS, 1, XA_CARDINAL, 0);
578 			if (winprop_get_int(&prop) & WIN_HINTS_SKIP_TASKBAR)
579 				result = false;
580 			free_winprop(&prop);
581 		}
582 	}
583 
584 	return result;
585 }
586 
587 long
wm_get_window_desktop(session_t * ps,Window wid)588 wm_get_window_desktop(session_t *ps, Window wid) {
589 	long desktop = LONG_MIN;
590 	winprop_t prop = { };
591 
592 	// Check for sticky window
593 	if (WMPSN_GNOME == ps->wmpsn) {
594 		prop = wid_get_prop(ps, wid, _WIN_STATE, 1, XA_CARDINAL, 0);
595 		if (WIN_STATE_STICKY & winprop_get_int(&prop))
596 			desktop = -1;
597 		free_winprop(&prop);
598 		if (LONG_MIN != desktop)
599 			return desktop;
600 	}
601 
602 	prop = wid_get_prop(ps, wid, _NET_WM_DESKTOP, 1, XA_CARDINAL, 0);
603 	if (prop.nitems)
604 		desktop = winprop_get_int(&prop);
605 	if ((long) 0xFFFFFFFFL == desktop)
606 		desktop = -1;
607 	free_winprop(&prop);
608 	if (LONG_MIN != desktop) return desktop;
609 
610 	prop = wid_get_prop(ps, wid, _WIN_WORKSPACE, 1, XA_CARDINAL, 0);
611 	if (prop.nitems)
612 		desktop = winprop_get_int(&prop);
613 	free_winprop(&prop);
614 	if (LONG_MIN != desktop) return desktop;
615 
616 	return wm_get_current_desktop(ps);
617 }
618 
619 /* Get focused window and traverse towards the root window until a window with WM_STATE is found */
620 Window
wm_get_focused(session_t * ps)621 wm_get_focused(session_t *ps) {
622 	Window focused = None;
623 
624 	{
625 		int revert_to = 0;
626 		if (!XGetInputFocus(ps->dpy, &focused, &revert_to)) {
627 			printfef("(): Failed to get current focused window.");
628 			return None;
629 		}
630 		// printfdf("(): Focused window is %#010lx.", focused);
631 	}
632 
633 	while (focused) {
634 		// Discard insane values
635 		if (ps->root == focused || PointerRoot == focused)
636 			return None;
637 
638 		// Check for WM_STATE
639 		if (wid_has_prop(ps, focused, XA_WM_STATE))
640 			return focused;
641 
642 		// Query window parent
643 		{
644 			Window rroot = None, parent = None;
645 			Window *children = NULL;
646 			unsigned int nchildren = 0;
647 
648 			Status status =
649 				XQueryTree(ps->dpy, focused, &rroot, &parent, &children, &nchildren);
650 			sxfree(children);
651 			if (!status) {
652 				printfef("(): Failed to get parent window of %#010lx.", focused);
653 				return None;
654 			}
655 			// printfdf("(): Parent window of %#010lx is %#010lx.", focused, parent);
656 			focused = parent;
657 			assert(ps->root == rroot);
658 		}
659 	}
660 
661 	return focused;
662 }
663 
664 /**
665  * @brief Get the raw string from a string property.
666  */
667 char *
wm_wid_get_prop_rstr(session_t * ps,Window wid,Atom prop)668 wm_wid_get_prop_rstr(session_t *ps, Window wid, Atom prop) {
669 	Atom type_ret = None;
670 	int fmt_ret = 0;
671 	unsigned long nitems = 0;
672 	unsigned long bytes_after_ret = 0;
673 	unsigned char *data = NULL;
674 	char *ret = NULL;
675 	if (Success == XGetWindowProperty(ps->dpy, wid, prop, 0, BUF_LEN,
676 				False, AnyPropertyType, &type_ret, &fmt_ret, &nitems,
677 				&bytes_after_ret, &data) && nitems && 8 == fmt_ret)
678 		ret = mstrdup((char *) data);
679 	sxfree(data);
680 	return ret;
681 }
682 
683 /**
684  * @brief Get the first string in a UTF-8 string property on a window.
685  */
686 char *
wm_wid_get_prop_utf8(session_t * ps,Window wid,Atom prop)687 wm_wid_get_prop_utf8(session_t *ps, Window wid, Atom prop) {
688 	XTextProperty text_prop = { };
689 	char *ret = NULL;
690 	if (XGetTextProperty(ps->dpy, wid, &text_prop, prop)) {
691 		char **strlst = NULL;
692 		int cstr = 0;
693 		Xutf8TextPropertyToTextList(ps->dpy, &text_prop, &strlst, &cstr);
694 		if (cstr) ret = mstrdup(strlst[0]);
695 		if (strlst) XFreeStringList(strlst);
696 	}
697 	sxfree(text_prop.value);
698 	return ret;
699 }
700 
701 /**
702  * @brief Set a UTF-8 string property on a window.
703  */
704 bool
wm_wid_set_prop_utf8(session_t * ps,Window wid,Atom prop,char * text)705 wm_wid_set_prop_utf8(session_t *ps, Window wid, Atom prop, char *text) {
706 	XTextProperty text_prop = { };
707 	bool success = (Success == XmbTextListToTextProperty(ps->dpy, &text, 1,
708 				XUTF8StringStyle, &text_prop));
709 	if (success)
710 		XSetTextProperty(ps->dpy, wid, &text_prop, prop);
711 	sxfree(text_prop.value);
712 	return success;
713 }
714 
715 /**
716  * @brief Set basic properties on a window.
717  */
718 void
wm_wid_set_info(session_t * ps,Window wid,const char * name,Atom window_type)719 wm_wid_set_info(session_t *ps, Window wid, const char *name,
720 		Atom window_type) {
721 	// Set window name
722 	{
723 		char *textcpy = mstrjoin("skippy-xd ", name);
724 		{
725 			XTextProperty text_prop = { };
726 			if (Success == XmbTextListToTextProperty(ps->dpy, &textcpy, 1,
727 						XStdICCTextStyle, &text_prop))
728 				XSetWMName(ps->dpy, wid, &text_prop);
729 			sxfree(text_prop.value);
730 		}
731 		wm_wid_set_prop_utf8(ps, wid, _NET_WM_NAME, textcpy);
732 		free(textcpy);
733 	}
734 
735 	// Set window class
736 	{
737 		XClassHint *classh = allocchk(XAllocClassHint());
738 		classh->res_name = "skippy-xd";
739 		classh->res_class = "skippy-xd";
740 		XSetClassHint(ps->dpy, wid, classh);
741 		XFree(classh);
742 	}
743 
744 	// Set window type
745 	{
746 		if (!window_type)
747 			window_type = _NET_WM_WINDOW_TYPE_NORMAL;
748 		long val = window_type;
749 		XChangeProperty(ps->dpy, wid, _NET_WM_WINDOW_TYPE, XA_ATOM, 32,
750 				PropModeReplace, (unsigned char *) &val, 1);
751 	}
752 }
753 
754 /**
755  * @brief Send a X client messsage.
756  */
757 void
wm_send_clientmsg(session_t * ps,Window twid,Window wid,Atom msg_type,int fmt,long event_mask,int len,const unsigned char * data)758 wm_send_clientmsg(session_t *ps, Window twid, Window wid, Atom msg_type,
759 		int fmt, long event_mask, int len, const unsigned char *data) {
760 	assert(twid);
761 	assert(8 == fmt || 16 == fmt || 32 == fmt);
762 	assert(len * fmt <= 20 * 8);
763 	XClientMessageEvent ev = {
764 		.type = ClientMessage,
765 		.window = wid,
766 		.message_type = msg_type,
767 		.format = fmt,
768 	};
769 	int seglen = 0;
770 	switch (fmt) {
771 		case 32: seglen = sizeof(long); break;
772 		case 16: seglen = sizeof(short); break;
773 		case 8: seglen = sizeof(char); break;
774 	}
775 	memcpy(ev.data.l, data, seglen * len);
776 	XSendEvent(ps->dpy, twid, False, event_mask, (XEvent *) &ev);
777 }
778 
779 /**
780  * @brief Find out the WM frame of a client window by querying X.
781  *
782  * @param ps current session
783  * @param wid window ID
784  * @return window ID of the frame window
785  */
786 Window
wm_find_frame(session_t * ps,Window wid)787 wm_find_frame(session_t *ps, Window wid) {
788   // We traverse through its ancestors to find out the frame
789   for (Window cwid = wid; cwid && cwid != ps->root; ) {
790     Window rroot = None;
791     Window *children = NULL;
792     unsigned nchildren = 0;
793 	wid = cwid;
794     if (!XQueryTree(ps->dpy, cwid, &rroot, &cwid, &children,
795           &nchildren))
796 			cwid = 0;
797     sxfree(children);
798   }
799 
800   return wid;
801 }
802 
803 /**
804  * Get a specific attribute of a window.
805  *
806  * Returns a blank structure if the returned type and format does not
807  * match the requested type and format.
808  *
809  * @param ps current session
810  * @param w window
811  * @param atom atom of attribute to fetch
812  * @param length length to read
813  * @param rtype atom of the requested type
814  * @param rformat requested format
815  * @return a <code>winprop_t</code> structure containing the attribute
816  *    and number of items. A blank one on failure.
817  */
818 winprop_t
wid_get_prop_adv(const session_t * ps,Window w,Atom atom,long offset,long length,Atom rtype,int rformat)819 wid_get_prop_adv(const session_t *ps, Window w, Atom atom, long offset,
820     long length, Atom rtype, int rformat) {
821   Atom type = None;
822   int format = 0;
823   unsigned long nitems = 0, after = 0;
824   unsigned char *data = NULL;
825 
826 	if (Success == XGetWindowProperty(ps->dpy, w, atom, offset, length,
827 				False, rtype, &type, &format, &nitems, &after, &data)
828 			&& nitems && (AnyPropertyType == type || type == rtype)
829 			&& (!rformat || format == rformat)
830 			&& (8 == format || 16 == format || 32 == format)) {
831 		return (winprop_t) {
832 			.data8 = data,
833 			.nitems = nitems,
834 			.type = type,
835 			.format = format,
836 		};
837 	}
838 
839   sxfree(data);
840 
841   return (winprop_t) {
842     .data8 = NULL,
843     .nitems = 0,
844     .type = AnyPropertyType,
845     .format = 0
846   };
847 }
848 
849