spacepaste

  1.  
  2. #if 0
  3. #// Various window management fixes for the Linux Steam client.
  4. #//
  5. #// You can set the following environment variables to 0 to disable individual features:
  6. #//
  7. #// STEAMWM_FORCE_BORDERS Force borders on non-menu windows.
  8. #//
  9. #// STEAMWM_PREVENT_MOVE Let the WM position non-menu/tooltip windows.
  10. #//
  11. #// STEAMWM_GROUP_WINDOWS Group all steam windows.
  12. #// This helps WMs with their focus stealing preventions,
  13. #// and also prevents all Steam windows from being dimmed
  14. #// (by KWin) if any Steam window has focus (is a KWin setting).
  15. #// NOTE: Window is still dimmed when showing menus/tooltips :(
  16. #//
  17. #// STEAMWM_SET_WINDOW_TYPE Tell the WM which Steam windows are dialogs.
  18. #// This lets the window manager place them more intelligently.
  19. #// For example, the WM might center dialogs.
  20. #// NOTE: We simply treat every window with a title other than
  21. #// "Steam" or "Friends" as a dialog window.
  22. #// The startup window is also marked as a dialog.
  23. #//
  24. #// STEAMWM_MANAGE_ERRORS Steam sets error dialogs as unmanaged windows - fix that.
  25. #//
  26. #//
  27. #// Obsolete fixes (now disabled by default):
  28. #//
  29. #// STEAMWM_FIX_NET_WM_NAME Set _NET_WM_NAME to the WM_NAME value to get better window
  30. #// titles (and add " - Steam" suffix if needed).
  31. #// Steam now doesn't set WM_ICON_NAME, _NET_WM_NAME or
  32. #// _NET_WM_ICON_NAME anymore - while it would be better to set
  33. #// them, their absence is unlikely to cause problems.
  34. #//
  35. #// STEAMWM_SET_FIXED_SIZE Set fixed size hints for windows with a fixed layout.
  36. #//
  37. #//
  38. #// Requires: g++ with support for x86 targets, Xlib + headers
  39. #//
  40. #//
  41. #// Use:
  42. #// $ chmod +x steamwm.cpp
  43. #// and then
  44. #//
  45. #//
  46. #// $ DEBUGGER="$(pwd)/steamwm.cpp" steam
  47. #//
  48. #// *or*
  49. #//
  50. #// $ ./steamwm.cpp steam // Prints ld.so errors on 64-bit systems
  51. #//
  52. #// *or*
  53. #//
  54. #// $ ./steamwm.cpp // Compile
  55. #// $ LD_PRELOAD="$(pwd)/steamwm.so" steam // Prints ld.so errors on 64-bit systems
  56. #//
  57. #//
  58. #// DISCLAIMER: Use at your own risk! This is in no way endorsed by VALVE.
  59. #//
  60. #// This program is free software. It comes without any warranty, to
  61. #// the extent permitted by applicable law. You can redistribute it
  62. #// and/or modify it under the terms of the Do What The Fuck You Want
  63. #// To Public License, Version 2, as published by Sam Hocevar. See
  64. #// http://sam.zoy.org/wtfpl/COPYING for more details.
  65. #//
  66. [ -z $STEAMWM_FORCE_BORDERS ] && export STEAMWM_FORCE_BORDERS=1
  67. [ -z $STEAMWM_PREVENT_MOVE ] && export STEAMWM_PREVENT_MOVE=1
  68. [ -z $STEAMWM_FIX_NET_WM_NAME ] && export STEAMWM_FIX_NET_WM_NAME=0
  69. [ -z $STEAMWM_GROUP_WINDOWS ] && export STEAMWM_GROUP_WINDOWS=1
  70. [ -z $STEAMWM_SET_WINDOW_TYPE ] && export STEAMWM_SET_WINDOW_TYPE=1
  71. [ -z $STEAMWM_SET_FIXED_SIZE ] && export STEAMWM_SET_FIXED_SIZE=0
  72. [ -z $STEAMWM_MANAGE_ERRORS ] && export STEAMWM_MANAGE_ERRORS=1
  73. self="$(readlink -f "$(which "$0")")"
  74. name="$(basename "$self" .cpp)"
  75. out="$(dirname "$self")/$name"
  76. soname="$name.so"
  77. #// On amd64 platforms, compile a dummy 64-bit steamwm.so,
  78. #// so that native 64-bit tools invoked by Steam and its
  79. #// launch script won't spam (harmless) ld.so errors.
  80. if [ -f '/lib64/ld-linux-x86-64.so.2' ] ; then
  81. dout="$out/64"
  82. mkdir -p "$dout"
  83. if ! [ -f "$dout/$soname" ] ; then
  84. echo -e "\n" | gcc -shared -fPIC -m64 -x c - -o "$dout/$soname" &> /dev/null
  85. strip "$dout/$soname" &> /dev/null
  86. #// ignore all errors - this may at worst cause warnings later
  87. fi
  88. export LD_LIBRARY_PATH="$dout:$LD_LIBRARY_PATH"
  89. fi
  90. #// Compile the LD_PRELOAD library
  91. mkdir -p "$out"
  92. if [ "$self" -nt "$out/$soname" ] ; then
  93. echo "Compiling $soname..."
  94. g++ -shared -fPIC -m32 -x c++ "$self" -o "$out/$soname" \
  95. -lX11 -static-libgcc -static-libstdc++ \
  96. -O3 -Wall -Wextra -DSONAME="$soname" \
  97. || exit 1
  98. fi
  99. #// Run the executable
  100. export LD_PRELOAD="$soname:$LD_PRELOAD"
  101. export LD_LIBRARY_PATH="$out:$LD_LIBRARY_PATH"
  102. [ -z "$1" ] || exec "$@"
  103. exit
  104. #endif // 0
  105. #ifndef _GNU_SOURCE
  106. #define _GNU_SOURCE
  107. #endif
  108. #include <stdio.h>
  109. #include <stdlib.h>
  110. #include <string.h>
  111. #include <dlfcn.h>
  112. #include <X11/Xlib.h>
  113. #include <X11/Xatom.h>
  114. #include <X11/Xutil.h>
  115. #define STR_(x) # x
  116. #define STR(x) STR_(x)
  117. // List of window titles for windows that should not be marked as dialogs
  118. static const char * main_windows[] = {
  119. "Steam",
  120. "Friends",
  121. };
  122. // List of window titles for windows that should be dialogs even if unmanaged
  123. static const char * dialog_windows[] = {
  124. "Untitled",
  125. "Steam - Go Online",
  126. };
  127. static const char * fixed_size_windows[] = {
  128. "Settings",
  129. "About Steam",
  130. "Backup and Restore Programs",
  131. "Steam - Error",
  132. "Steam - Self Updater",
  133. };
  134. static const char * fixed_size_suffixes[] = {
  135. " - Properties",
  136. " - Category",
  137. };
  138. static bool force_borders = false;
  139. static bool prevent_move = false;
  140. static bool fix_net_wm_name = false;
  141. static bool group_windows = false;
  142. static bool set_window_type = false;
  143. static bool set_fixed_size = false;
  144. static bool manage_errors = false;
  145. extern "C" {
  146. extern char * program_invocation_short_name; // provided by glibc
  147. }
  148. static bool get_setting(const char * name) {
  149. char * setting = getenv(name);
  150. return (setting && setting[0] != '\0' && setting[0] != '0');
  151. }
  152. void steamwm_init(void) __attribute__((constructor));
  153. void steamwm_init(void) {
  154. // Only attach to steam!
  155. if(strcmp(program_invocation_short_name, "steam") != 0) {
  156. return;
  157. }
  158. // Prevent steamwm.so from being attached to processes started by steam
  159. const char * envname = "LD_PRELOAD";
  160. const char * oldenv = getenv(envname);
  161. if(oldenv) {
  162. char * env = strdup(oldenv);
  163. char * pos = strstr(env, STR(SONAME));
  164. if(pos) {
  165. size_t len1 = strlen(STR(SONAME));
  166. size_t len2 = strlen(pos + len1);
  167. memmove(pos, pos + len1, len2);
  168. *(pos + len2) = '\0';
  169. setenv(envname, env, 1);
  170. }
  171. free(env);
  172. }
  173. force_borders = get_setting("STEAMWM_FORCE_BORDERS");
  174. prevent_move = get_setting("STEAMWM_PREVENT_MOVE");
  175. fix_net_wm_name = get_setting("STEAMWM_FIX_NET_WM_NAME");
  176. group_windows = get_setting("STEAMWM_GROUP_WINDOWS");
  177. set_window_type = get_setting("STEAMWM_SET_WINDOW_TYPE");
  178. set_fixed_size = get_setting("STEAMWM_SET_FIXED_SIZE");
  179. manage_errors = get_setting("STEAMWM_MANAGE_ERRORS");
  180. fprintf(stderr, "\n[steamwm] attached to steam:\n force_borders %d\n"
  181. " prevent_move %d\n fix_net_wm_name %d\n"
  182. " group_windows %d\n set_window_type %d\n"
  183. " set_fixed_size %d\n manage_errors %d\n\n",
  184. int(force_borders), int(prevent_move), int(fix_net_wm_name),
  185. int(group_windows), int(set_window_type), int(set_fixed_size),
  186. int(manage_errors));
  187. }
  188. /* helper functions */
  189. #define BASE_NAME(SymbolName) base_ ## SymbolName
  190. #define TYPE_NAME(SymbolName) SymbolName ## _t
  191. #define INTERCEPT(ReturnType, SymbolName, ...) \
  192. typedef ReturnType (*TYPE_NAME(SymbolName))(__VA_ARGS__); \
  193. static void * const BASE_NAME(SymbolName) = dlsym(RTLD_NEXT, STR(SymbolName)); \
  194. ReturnType SymbolName(__VA_ARGS__)
  195. #define BASE(SymbolName) ((TYPE_NAME(SymbolName))BASE_NAME(SymbolName))
  196. static bool is_unmanaged_window(Display * dpy, Window w);
  197. static void set_is_unmanaged_window(Display * dpy, Window w, bool is_unmanaged);
  198. static bool is_fixed_size_window_name(const char * name);
  199. static bool is_main_window_name(const char * name);
  200. static bool is_dialog_window_name(const char * name);
  201. static void set_window_desired_size(Display * dpy, Window w, int width, int height,
  202. bool set_fixed);
  203. static void set_window_property(Display * dpy, Window w, Atom property, Atom type,
  204. long value);
  205. static void set_window_group_hint(Display * dpy, Window w, XID window_group);
  206. static void set_window_is_dialog(Display * dpy, Window w, bool is_dialog);
  207. static void set_window_modal(Display * dpy, Window w);
  208. /* fix window titles and types, and add window borders & title bars */
  209. static Window first_window = None, second_window = None;
  210. static void name_changed(Display * dpy, Window w, const unsigned char * data, int n);
  211. INTERCEPT(void, XSetWMName,
  212. Display * dpy,
  213. Window w,
  214. XTextProperty * prop
  215. ) {
  216. if(prop->format == 8) {
  217. // The libX11 pulled in with STEAM_RUNTIME=1 has XSetWMName (or XSetTextProperty?)
  218. // liked/optimized to use internal functions and not the global XChangeProperty,
  219. // so our override below won't work -> also intercept XSetWMName().
  220. name_changed(dpy, w, prop->value, prop->nitems);
  221. }
  222. return BASE(XSetWMName)(dpy, w, prop);
  223. }
  224. INTERCEPT(int, XChangeProperty,
  225. Display * dpy,
  226. Window w,
  227. Atom property,
  228. Atom type,
  229. int format,
  230. int mode,
  231. const unsigned char * data,
  232. int n
  233. ) {
  234. if(property == XA_WM_NAME && format == 8) {
  235. name_changed(dpy, w, data, n);
  236. }
  237. if(manage_errors && property == XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False)) {
  238. if(!is_unmanaged_window(dpy, w) && type == XA_ATOM && format == 32 && n > 0) {
  239. Atom menu = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_MENU", False);
  240. Atom type = reinterpret_cast<const long * /* sic */>(data)[0];
  241. if(type == menu) {
  242. // Ignore the window type Steam sets on error dialogs.
  243. return 1;
  244. }
  245. }
  246. }
  247. if(force_borders && property == XInternAtom(dpy, "_MOTIF_WM_HINTS", False)) {
  248. // Don't suppress window borders!
  249. return 1;
  250. }
  251. return BASE(XChangeProperty)(dpy, w, property, type, format, mode, data, n);
  252. }
  253. static void name_changed(Display * dpy, Window w, const unsigned char * data, int n) {
  254. char * value = (char *)data;
  255. if(fix_net_wm_name) {
  256. // Use the XA_WM_NAME as both XA_WM_NAME and _NET_WM_NAME.
  257. // Steam sets _NET_WM_NAME to just "Steam" for all windows.
  258. const unsigned char * name = data;
  259. unsigned char * buffer = NULL;
  260. int nn = n;
  261. if(n > 0 && strstr((char *)data, "Steam") == 0) {
  262. // Make sure "Steam" is in all window titles.
  263. char suffix[] = " - Steam";
  264. nn = n + sizeof(suffix) - 1;
  265. name = buffer = (unsigned char *)malloc(nn + 1);
  266. memcpy(buffer, data, n);
  267. memcpy(buffer + n, suffix, sizeof(suffix));
  268. }
  269. Atom net_wm_name = XInternAtom(dpy, "_NET_WM_NAME", False);
  270. Atom utf8_string = XInternAtom(dpy, "UTF8_STRING", False);
  271. BASE(XChangeProperty)(dpy, w, net_wm_name, utf8_string, 8, PropModeReplace, name, nn);
  272. if(buffer) {
  273. free(buffer);
  274. }
  275. }
  276. if(manage_errors && is_unmanaged_window(dpy, w) && is_dialog_window_name(value)) {
  277. // Error dialogs should be managed by the window manager.
  278. set_is_unmanaged_window(dpy, w, false);
  279. set_window_modal(dpy, w);
  280. set_window_desired_size(dpy, w, -1, -1, true);
  281. }
  282. if(set_window_type && !is_unmanaged_window(dpy, w)
  283. && w != first_window && w != second_window) {
  284. // Set the window type for non-menu windows.
  285. // This should probably be done *before* mapping the windows,
  286. // but then we don't have a title yet.
  287. // Try to guess the window type from the title.
  288. set_window_is_dialog(dpy, w, !is_main_window_name(value));
  289. }
  290. if(set_fixed_size && is_fixed_size_window_name(value)) {
  291. // Set fixed size hints for windows with static layouts.
  292. set_window_desired_size(dpy, w, -1, -1, true);
  293. }
  294. }
  295. /* ignore window move requests for non-menu windows */
  296. INTERCEPT(int, XResizeWindow,
  297. Display * dpy,
  298. Window w,
  299. unsigned int width,
  300. unsigned int height
  301. ) {
  302. if(set_fixed_size || manage_errors) {
  303. // Set fixed size hints for windows with static layouts.
  304. set_window_desired_size(dpy, w, width, height, false);
  305. }
  306. return BASE(XResizeWindow)(dpy, w, width, height);
  307. }
  308. INTERCEPT(int, XMoveResizeWindow,
  309. Display * dpy,
  310. Window w,
  311. int x,
  312. int y,
  313. unsigned int width,
  314. unsigned int height
  315. ) {
  316. if(set_fixed_size || manage_errors) {
  317. // Set fixed size hints for windows with static layouts.
  318. set_window_desired_size(dpy, w, width, height, false);
  319. }
  320. if(prevent_move && !is_unmanaged_window(dpy, w)) {
  321. // Ignore the position request for non-menu windows.
  322. return BASE(XResizeWindow)(dpy, w, width, height);
  323. }
  324. return BASE(XMoveResizeWindow)(dpy, w, x, y, width, height);
  325. }
  326. INTERCEPT(int, XMoveWindow,
  327. Display * dpy,
  328. Window w,
  329. int x,
  330. int y
  331. ) {
  332. if(prevent_move && !is_unmanaged_window(dpy, w)) {
  333. // Ignore the position request for non-menu windows.
  334. return 1;
  335. }
  336. return BASE(XMoveWindow)(dpy, w, x, y);
  337. }
  338. /* group windows and force the first and second window to be dialogs */
  339. INTERCEPT(int, XMapWindow,
  340. Display * dpy,
  341. Window w
  342. ) {
  343. if(first_window == None) {
  344. first_window = w;
  345. }
  346. if(group_windows) {
  347. // Group all steam windows.
  348. Atom leader = XInternAtom(dpy, "WM_CLIENT_LEADER", False);
  349. set_window_property(dpy, w, leader, XA_WINDOW, first_window);
  350. set_window_group_hint(dpy, w, first_window);
  351. }
  352. if(set_window_type && (w == first_window || second_window == None)) {
  353. // Force the first and second windows to be marked as dialogs.
  354. set_window_is_dialog(dpy, w, true);
  355. if(w != first_window) {
  356. // Give the second window a proper size *now* so that the WM can center it.
  357. second_window = w;
  358. XResizeWindow(dpy, w, 384, 107);
  359. }
  360. }
  361. return BASE(XMapWindow)(dpy, w);
  362. }
  363. INTERCEPT(void, XSetWMNormalHints,
  364. Display * dpy,
  365. Window w,
  366. XSizeHints * hints
  367. ) {
  368. XSizeHints old_hints;
  369. long supplied;
  370. // Prevent steam from overriding the max size hints
  371. if((set_fixed_size || manage_errors)
  372. && XGetWMNormalHints(dpy, w, &old_hints, &supplied)) {
  373. if(!(hints->flags & PSize) && (old_hints.flags & PSize)) {
  374. hints->flags |= PSize;
  375. hints->width = old_hints.width;
  376. hints->height = old_hints.height;
  377. }
  378. if((old_hints.flags & PMinSize) && (old_hints.flags & PMaxSize)
  379. && old_hints.min_width == old_hints.max_width
  380. && old_hints.min_height == old_hints.max_height) {
  381. hints->flags |= PMinSize | PMaxSize;
  382. hints->min_width = hints->max_width = old_hints.max_width;
  383. hints->min_height = hints->max_height = old_hints.max_height;
  384. }
  385. }
  386. BASE(XSetWMNormalHints)(dpy, w, hints);
  387. }
  388. /* helper function implementations */
  389. static bool is_unmanaged_window(Display * dpy, Window w) {
  390. XWindowAttributes xwa;
  391. if(!XGetWindowAttributes(dpy, w, &xwa)) {
  392. return false;
  393. }
  394. return xwa.override_redirect;
  395. }
  396. static void set_is_unmanaged_window(Display * dpy, Window w, bool is_unmanaged) {
  397. XSetWindowAttributes xswa;
  398. xswa.override_redirect = is_unmanaged;
  399. XChangeWindowAttributes(dpy, w, CWOverrideRedirect, &xswa);
  400. }
  401. template <size_t N>
  402. static bool is_in_array(const char * (&array)[N], const char * needle) {
  403. for(unsigned i = 0; i < N; i++) {
  404. if(strcmp(needle, array[i]) == 0) {
  405. return true;
  406. }
  407. }
  408. return false;
  409. }
  410. static bool is_main_window_name(const char * name) {
  411. return is_in_array(main_windows, name);
  412. }
  413. static bool is_dialog_window_name(const char * name) {
  414. return is_in_array(dialog_windows, name);
  415. }
  416. static bool is_fixed_size_window_name(const char * name) {
  417. if(is_in_array(fixed_size_windows, name)) {
  418. return true;
  419. }
  420. int len = strlen(name);
  421. for(unsigned i = 0; i < sizeof(fixed_size_suffixes)/sizeof(*fixed_size_suffixes); i++) {
  422. int plen = strlen(fixed_size_suffixes[i]);
  423. if(len > plen && strcmp(name + len - plen, fixed_size_suffixes[i]) == 0) {
  424. return true;
  425. }
  426. }
  427. return false;
  428. }
  429. static void set_window_desired_size(Display * dpy, Window w, int width, int height,
  430. bool set_fixed) {
  431. XSizeHints xsh;
  432. long supplied;
  433. if(!XGetWMNormalHints(dpy, w, &xsh, &supplied)) {
  434. xsh.flags = 0;
  435. }
  436. if(width > 0 && height > 0) {
  437. // Store the desired size.
  438. // PBaseSize (base_width and base_height) sounds more appropriate
  439. // (and is not obsolete), but some window managers won't let the window
  440. // get smaller than the base size.
  441. xsh.flags |= PSize;
  442. xsh.width = width, xsh.height = height;
  443. } else if(xsh.flags & PSize) {
  444. // Retrieve the desired size.
  445. width = xsh.width, height = xsh.height;
  446. } else {
  447. Window root;
  448. int x, y;
  449. unsigned int cwidth, cheight, border_width, depth;
  450. if(!XGetGeometry(dpy, w, &root, &x, &y, &cwidth, &cheight, &border_width, &depth)) {
  451. return;
  452. }
  453. width = cwidth, height = cheight;
  454. }
  455. if(set_fixed || ((xsh.flags & PMinSize) && (xsh.flags & PMaxSize)
  456. && xsh.min_width == xsh.max_width && xsh.min_height == xsh.max_height)) {
  457. xsh.flags |= PMinSize | PMaxSize;
  458. xsh.min_width = xsh.max_width = width;
  459. xsh.min_height = xsh.max_height = height;
  460. }
  461. BASE(XSetWMNormalHints)(dpy, w, &xsh);
  462. }
  463. static void set_window_property(Display * dpy, Window w, Atom property, Atom type,
  464. long value) {
  465. unsigned char data[sizeof(long)];
  466. memcpy(&data, &value, sizeof(long));
  467. BASE(XChangeProperty)(dpy, w, property, type, 32, PropModeReplace, data, 1);
  468. }
  469. static void set_window_group_hint(Display * dpy, Window w, XID window_group) {
  470. XWMHints base_hints;
  471. XWMHints * h = XGetWMHints(dpy, w);
  472. if(!h) {
  473. h = &base_hints;
  474. h->flags = 0;
  475. }
  476. h->flags |= WindowGroupHint;
  477. h->window_group = window_group;
  478. XSetWMHints(dpy, w, h);
  479. if(h != &base_hints) {
  480. XFree(h);
  481. }
  482. }
  483. static void set_window_is_dialog(Display * dpy, Window w, bool is_dialog) {
  484. Atom window_type = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
  485. if(is_dialog) {
  486. Atom dialog = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False);
  487. set_window_property(dpy, w, window_type, XA_ATOM, dialog);
  488. } else {
  489. Atom normal = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_NORMAL", False);
  490. set_window_property(dpy, w, window_type, XA_ATOM, normal);
  491. }
  492. }
  493. static void set_window_modal(Display * dpy, Window w) {
  494. XWindowAttributes xwa;
  495. if(!XGetWindowAttributes(dpy, w, &xwa)) {
  496. return;
  497. }
  498. Atom state = XInternAtom(dpy, "_NET_WM_STATE", False);
  499. Atom state_modal = XInternAtom(dpy, "_NET_WM_STATE_MODAL", False);
  500. if(xwa.map_state == IsUnmapped) {
  501. set_window_property(dpy, w, state, XA_ATOM, state_modal);
  502. } else {
  503. XEvent event;
  504. event.type = ClientMessage;
  505. event.xclient.message_type = state;
  506. event.xclient.window = w;
  507. event.xclient.format = 32;
  508. event.xclient.data.l[0] = 1; // add
  509. event.xclient.data.l[1] = state_modal;
  510. event.xclient.data.l[2] = 0;
  511. event.xclient.data.l[3] = 1;
  512. event.xclient.data.l[4] = 0;
  513. XSendEvent(dpy, DefaultRootWindow(dpy), False,
  514. (SubstructureNotifyMask | SubstructureRedirectMask), &event);
  515. }
  516. }
  517.