progman.exe^H^H^H^H
at master 442 lines 14 kB view raw
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}