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