progman.exe^H^H^H^H
1/*
2 * Copyright 2020 joshua stein <jcs@jcs.org>
3 * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to
7 * deal in the Software without restriction, including without limitation the
8 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 * sell copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 */
22
23#include <err.h>
24#include <stdlib.h>
25#include <string.h>
26#include <limits.h>
27#include <X11/Xutil.h>
28#include "progman.h"
29
30Atom kde_net_wm_window_type_override;
31Atom net_active_window;
32Atom net_client_list;
33Atom net_client_stack;
34Atom net_close_window;
35Atom net_cur_desk;
36Atom net_num_desks;
37Atom net_supported;
38Atom net_supporting_wm;
39Atom net_wm_desk;
40Atom net_wm_icon;
41Atom net_wm_icon_name;
42Atom net_wm_name;
43Atom net_wm_state;
44Atom net_wm_state_above;
45Atom net_wm_state_add;
46Atom net_wm_state_below;
47Atom net_wm_state_fs;
48Atom net_wm_state_mh;
49Atom net_wm_state_mv;
50Atom net_wm_state_rm;
51Atom net_wm_state_shaded;
52Atom net_wm_state_skipp;
53Atom net_wm_state_skipt;
54Atom net_wm_state_toggle;
55Atom net_wm_strut;
56Atom net_wm_strut_partial;
57Atom net_wm_type_desk;
58Atom net_wm_type_dock;
59Atom net_wm_type_menu;
60Atom net_wm_type_notif;
61Atom net_wm_type_splash;
62Atom net_wm_type_utility;
63Atom net_wm_wintype;
64Atom utf8_string;
65Atom wm_change_state;
66Atom wm_delete;
67Atom wm_protos;
68Atom wm_state;
69Atom xrootpmap_id;
70
71static char *get_string_atom(Window, Atom, Atom);
72static char *_get_wm_name(Window, int);
73
74void
75find_supported_atoms(void)
76{
77 net_supported = XInternAtom(dpy, "_NET_SUPPORTED", False);
78 utf8_string = XInternAtom(dpy, "UTF8_STRING", False);
79 wm_change_state = XInternAtom(dpy, "WM_CHANGE_STATE", False);
80 wm_delete = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
81 wm_protos = XInternAtom(dpy, "WM_PROTOCOLS", False);
82 wm_state = XInternAtom(dpy, "WM_STATE", False);
83 net_wm_state_rm = 0;
84 net_wm_state_add = 1;
85 net_wm_state_toggle = 2;
86
87 xrootpmap_id = XInternAtom(dpy, "_XROOTPMAP_ID", False);
88
89 kde_net_wm_window_type_override = XInternAtom(dpy,
90 "_KDE_NET_WM_WINDOW_TYPE_OVERRIDE", False);
91 append_atoms(root, net_supported, XA_ATOM,
92 &kde_net_wm_window_type_override, 1);
93
94 net_active_window = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False);
95 append_atoms(root, net_supported, XA_ATOM, &net_active_window, 1);
96
97 net_client_list = XInternAtom(dpy, "_NET_CLIENT_LIST", False);
98 append_atoms(root, net_supported, XA_ATOM, &net_client_list, 1);
99
100 net_client_stack = XInternAtom(dpy, "_NET_CLIENT_LIST_STACKING", False);
101 append_atoms(root, net_supported, XA_ATOM, &net_client_stack, 1);
102
103 net_close_window = XInternAtom(dpy, "_NET_CLOSE_WINDOW", False);
104 append_atoms(root, net_supported, XA_ATOM, &net_close_window, 1);
105
106 net_cur_desk = XInternAtom(dpy, "_NET_CURRENT_DESKTOP", False);
107 append_atoms(root, net_supported, XA_ATOM, &net_cur_desk, 1);
108
109 net_num_desks = XInternAtom(dpy, "_NET_NUMBER_OF_DESKTOPS", False);
110 append_atoms(root, net_supported, XA_ATOM, &net_num_desks, 1);
111
112 net_supporting_wm = XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False);
113 append_atoms(root, net_supported, XA_ATOM, &net_supporting_wm, 1);
114
115 net_wm_desk = XInternAtom(dpy, "_NET_WM_DESKTOP", False);
116 append_atoms(root, net_supported, XA_ATOM, &net_wm_desk, 1);
117
118 net_wm_icon = XInternAtom(dpy, "_NET_WM_ICON", False);
119 append_atoms(root, net_supported, XA_ATOM, &net_wm_icon, 1);
120
121 net_wm_icon_name = XInternAtom(dpy, "_NET_WM_ICON_NAME", False);
122 append_atoms(root, net_supported, XA_ATOM, &net_wm_icon_name, 1);
123
124 net_wm_name = XInternAtom(dpy, "_NET_WM_NAME", False);
125 append_atoms(root, net_supported, XA_ATOM, &net_wm_name, 1);
126
127 net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
128 append_atoms(root, net_supported, XA_ATOM, &net_wm_state, 1);
129
130 net_wm_state_above = XInternAtom(dpy, "_NET_WM_STATE_ABOVE", False);
131 append_atoms(root, net_supported, XA_ATOM, &net_wm_state_above, 1);
132
133 net_wm_state_below = XInternAtom(dpy, "_NET_WM_STATE_BELOW", False);
134 append_atoms(root, net_supported, XA_ATOM, &net_wm_state_below, 1);
135
136 net_wm_state_fs = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
137 append_atoms(root, net_supported, XA_ATOM, &net_wm_state_fs, 1);
138
139 net_wm_state_mh = XInternAtom(dpy, "_NET_WM_STATE_MAXIMIZED_HORZ",
140 False);
141 append_atoms(root, net_supported, XA_ATOM, &net_wm_state_mh, 1);
142
143 net_wm_state_mv = XInternAtom(dpy, "_NET_WM_STATE_MAXIMIZED_VERT",
144 False);
145 append_atoms(root, net_supported, XA_ATOM, &net_wm_state_mv, 1);
146
147 net_wm_state_shaded = XInternAtom(dpy, "_NET_WM_STATE_SHADED", False);
148 append_atoms(root, net_supported, XA_ATOM, &net_wm_state_shaded, 1);
149
150 net_wm_strut = XInternAtom(dpy, "_NET_WM_STRUT", False);
151 append_atoms(root, net_supported, XA_ATOM, &net_wm_strut, 1);
152
153 net_wm_strut_partial = XInternAtom(dpy, "_NET_WM_STRUT_PARTIAL", False);
154 append_atoms(root, net_supported, XA_ATOM, &net_wm_strut_partial, 1);
155
156 net_wm_type_desk = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DESKTOP",
157 False);
158 append_atoms(root, net_supported, XA_ATOM, &net_wm_type_desk, 1);
159
160 net_wm_type_dock = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DOCK", False);
161 append_atoms(root, net_supported, XA_ATOM, &net_wm_type_dock, 1);
162
163 net_wm_type_menu = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_MENU", False);
164 append_atoms(root, net_supported, XA_ATOM, &net_wm_type_menu, 1);
165
166 net_wm_type_notif = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_NOTIFICATION",
167 False);
168 append_atoms(root, net_supported, XA_ATOM, &net_wm_type_utility, 1);
169
170 net_wm_type_splash = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_SPLASH",
171 False);
172 append_atoms(root, net_supported, XA_ATOM, &net_wm_type_splash, 1);
173
174 net_wm_type_utility = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_UTILITY",
175 False);
176 append_atoms(root, net_supported, XA_ATOM, &net_wm_type_utility, 1);
177
178 net_wm_wintype = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
179 append_atoms(root, net_supported, XA_ATOM, &net_wm_wintype, 1);
180}
181
182/*
183 * Despite the fact that all these are 32 bits on the wire, libX11 really does
184 * stuff an array of longs into *data, so you get 64 bits on 64-bit archs. So
185 * we gotta be careful here.
186 */
187
188unsigned long
189get_atoms(Window w, Atom a, Atom type, unsigned long off, unsigned long *ret,
190 unsigned long nitems, unsigned long *left)
191{
192 Atom real_type;
193 int i, real_format = 0;
194 unsigned long items_read = 0;
195 unsigned long bytes_left = 0;
196 unsigned long *p;
197 unsigned char *data = NULL;
198
199 ignore_xerrors++;
200 XGetWindowProperty(dpy, w, a, off, nitems, False, type, &real_type,
201 &real_format, &items_read, &bytes_left, &data);
202 ignore_xerrors--;
203
204 if (real_format == 32 && items_read) {
205 p = (unsigned long *)data;
206 for (i = 0; i < items_read; i++)
207 *ret++ = *p++;
208 if (left)
209 *left = bytes_left;
210 } else {
211 items_read = 0;
212 if (left)
213 *left = 0;
214 }
215
216 if (data != NULL)
217 XFree(data);
218
219 return items_read;
220}
221
222unsigned long
223set_atoms(Window w, Atom a, Atom type, unsigned long *val,
224 unsigned long nitems)
225{
226 return (XChangeProperty(dpy, w, a, type, 32, PropModeReplace,
227 (unsigned char *) val, nitems) == Success);
228}
229
230unsigned long
231append_atoms(Window w, Atom a, Atom type, unsigned long *val,
232 unsigned long nitems)
233{
234 return (XChangeProperty(dpy, w, a, type, 32, PropModeAppend,
235 (unsigned char *) val, nitems) == Success);
236}
237
238void
239remove_atom(Window w, Atom a, Atom type, unsigned long remove)
240{
241 unsigned long tmp, read, left, *new;
242 int i, j = 0;
243
244 read = get_atoms(w, a, type, 0, &tmp, 1, &left);
245 if (!read)
246 return;
247
248 new = malloc((read + left) * sizeof(*new));
249 if (new == NULL)
250 err(1, "malloc");
251 if (read && tmp != remove)
252 new[j++] = tmp;
253
254 for (i = 1, read = left = 1; read && left; i += read) {
255 read = get_atoms(w, a, type, i, &tmp, 1, &left);
256 if (!read)
257 break;
258 if (tmp != remove)
259 new[j++] = tmp;
260 }
261
262 if (j)
263 XChangeProperty(dpy, w, a, type, 32, PropModeReplace,
264 (unsigned char *)new, j);
265 else
266 XDeleteProperty(dpy, w, a);
267
268 free(new);
269}
270
271/*
272 * Get the window-manager name (aka human-readable "title") for a given window.
273 * There are two ways a client can set this:
274 *
275 * 1. _NET_WM_STRING, which has type UTF8_STRING.
276 * This is preferred and is always used if available.
277 *
278 * 2. WM_NAME, which has type COMPOUND_STRING or STRING.
279 * This is the old ICCCM way, which we fall back to in the absence of
280 * _NET_WM_STRING. In this case, we ask X to convert the value of the property
281 * to UTF-8 for us. N.b.: STRING is Latin-1 whatever the locale.
282 * COMPOUND_STRING is the most hideous abomination ever created. Thankfully we
283 * do not have to worry about any of this.
284 *
285 * If UTF-8 conversion is not available (XFree86 < 4.0.2, or any older X
286 * implementation), only WM_NAME will be checked, and, at least for XFree86 and
287 * X.Org, it will only be returned if it has type STRING. This is due to an
288 * inherent limitation in their implementation of XFetchName. If you have a
289 * different X vendor, YMMV.
290 *
291 * In all cases, this function asks X to allocate the returned string, so it
292 * must be freed with XFree.
293 */
294char *
295_get_wm_name(Window w, int icon)
296{
297 char *name;
298 XTextProperty name_prop;
299 XTextProperty name_prop_converted;
300 char **name_list;
301 int nitems;
302
303 if (icon) {
304 if ((name = get_string_atom(w, net_wm_icon_name, utf8_string)))
305 return name;
306 if (!XGetWMIconName(dpy, w, &name_prop))
307 return NULL;
308 } else {
309 if ((name = get_string_atom(w, net_wm_name, utf8_string)))
310 return name;
311 if (!XGetWMName(dpy, w, &name_prop))
312 return NULL;
313 }
314
315 if (Xutf8TextPropertyToTextList(dpy, &name_prop, &name_list,
316 &nitems) == Success && nitems >= 1) {
317 /*
318 * Now we've got a freshly allocated XTextList. Since
319 * it might have multiple items that need to be joined,
320 * and we need to return something that can be freed by
321 * XFree, we roll it back up into an XTextProperty.
322 */
323 if (Xutf8TextListToTextProperty(dpy, name_list, nitems,
324 XUTF8StringStyle, &name_prop_converted) == Success) {
325 XFreeStringList(name_list);
326 return (char *)name_prop_converted.value;
327 }
328
329 /*
330 * Not much we can do here. This should never
331 * happen anyway. Famous last words.
332 */
333 XFreeStringList(name_list);
334 return NULL;
335 }
336
337 return (char *)name_prop.value;
338}
339
340char *
341get_wm_name(Window w)
342{
343 return _get_wm_name(w, 0);
344}
345
346char *
347get_wm_icon_name(Window w)
348{
349 return _get_wm_name(w, 1);
350}
351
352/*
353 * I give up on trying to do this the right way. We'll just request as many
354 * elements as possible. If that's not the entire string, we're fucked. In
355 * reality this should never happen. (That's the second time I get to say "this
356 * should never happen" in this file!)
357 *
358 * Standard gripe about casting nonsense applies.
359 */
360static char *
361get_string_atom(Window w, Atom a, Atom type)
362{
363 Atom real_type;
364 int real_format = 0;
365 unsigned long items_read = 0;
366 unsigned long bytes_left = 0;
367 unsigned char *data;
368
369 XGetWindowProperty(dpy, w, a, 0, LONG_MAX, False, type,
370 &real_type, &real_format, &items_read, &bytes_left, &data);
371
372 /* XXX: should check bytes_left here and bail if nonzero, in case
373 * someone wants to store a >4gb string on the server */
374
375 if (real_format == 8 && items_read >= 1)
376 return (char *)data;
377
378 return NULL;
379}
380
381void
382set_string_atom(Window w, Atom a, unsigned char *str, unsigned long len)
383{
384 XChangeProperty(dpy, w, a, utf8_string, 8, PropModeReplace, str, len);
385}
386
387/*
388 * Reads the _NET_WM_STRUT_PARTIAL or _NET_WM_STRUT hint into the args, if it
389 * exists. In the case of _NET_WM_STRUT_PARTIAL we cheat and only take the
390 * first 4 values, because that's all we care about. This means we can use the
391 * same code for both (_NET_WM_STRUT only specifies 4 elements). Each number is
392 * the margin in pixels on that side of the display where we don't want to
393 * place clients. If there is no hint, we act as if it was all zeros (no
394 * margin).
395 */
396int
397get_strut(Window w, strut_t *s)
398{
399 Atom real_type;
400 int real_format = 0;
401 unsigned long items_read = 0;
402 unsigned long bytes_left = 0;
403 unsigned char *data;
404 unsigned long *strut_data;
405
406 XGetWindowProperty(dpy, w, net_wm_strut_partial, 0, 12, False,
407 XA_CARDINAL, &real_type, &real_format, &items_read, &bytes_left,
408 &data);
409
410 if (!(real_format == 32 && items_read >= 12))
411 XGetWindowProperty(dpy, w, net_wm_strut, 0, 4, False,
412 XA_CARDINAL, &real_type, &real_format, &items_read,
413 &bytes_left, &data);
414
415 if (real_format == 32 && items_read >= 4) {
416 strut_data = (unsigned long *) data;
417 s->left = strut_data[0];
418 s->right = strut_data[1];
419 s->top = strut_data[2];
420 s->bottom = strut_data[3];
421 XFree(data);
422 return 1;
423 }
424
425 s->left = 0;
426 s->right = 0;
427 s->top = 0;
428 s->bottom = 0;
429
430 return 0;
431}
432
433unsigned long
434get_wm_state(Window w)
435{
436 unsigned long state;
437
438 if (get_atoms(w, wm_state, wm_state, 0, &state, 1, NULL))
439 return state;
440
441 return WithdrawnState;
442}