-
- #if 0
-
- #// Various window management fixes for the Linux Steam client.
- #//
- #// You can set the following environment variables to 0 to disable individual features:
- #//
- #// STEAMWM_FORCE_BORDERS Force borders on non-menu windows.
- #//
- #// STEAMWM_PREVENT_MOVE Let the WM position non-menu/tooltip windows.
- #//
- #// STEAMWM_GROUP_WINDOWS Group all steam windows.
- #// This helps WMs with their focus stealing preventions,
- #// and also prevents all Steam windows from being dimmed
- #// (by KWin) if any Steam window has focus (is a KWin setting).
- #// NOTE: Window is still dimmed when showing menus/tooltips :(
- #//
- #// STEAMWM_SET_WINDOW_TYPE Tell the WM which Steam windows are dialogs.
- #// This lets the window manager place them more intelligently.
- #// For example, the WM might center dialogs.
- #// NOTE: We simply treat every window with a title other than
- #// "Steam" or "Friends" as a dialog window.
- #// The startup window is also marked as a dialog.
- #//
- #// STEAMWM_MANAGE_ERRORS Steam sets error dialogs as unmanaged windows - fix that.
- #//
- #//
- #// Obsolete fixes (now disabled by default):
- #//
- #// STEAMWM_FIX_NET_WM_NAME Set _NET_WM_NAME to the WM_NAME value to get better window
- #// titles (and add " - Steam" suffix if needed).
- #// Steam now doesn't set WM_ICON_NAME, _NET_WM_NAME or
- #// _NET_WM_ICON_NAME anymore - while it would be better to set
- #// them, their absence is unlikely to cause problems.
- #//
- #// STEAMWM_SET_FIXED_SIZE Set fixed size hints for windows with a fixed layout.
- #//
- #//
- #// Requires: g++ with support for x86 targets, Xlib + headers
- #//
- #//
- #// Use:
- #// $ chmod +x steamwm.cpp
- #// and then
- #//
- #//
- #// $ DEBUGGER="$(pwd)/steamwm.cpp" steam
- #//
- #// *or*
- #//
- #// $ ./steamwm.cpp steam // Prints ld.so errors on 64-bit systems
- #//
- #// *or*
- #//
- #// $ ./steamwm.cpp // Compile
- #// $ LD_PRELOAD="$(pwd)/steamwm.so" steam // Prints ld.so errors on 64-bit systems
- #//
- #//
- #// DISCLAIMER: Use at your own risk! This is in no way endorsed by VALVE.
- #//
- #// This program is free software. It comes without any warranty, to
- #// the extent permitted by applicable law. You can redistribute it
- #// and/or modify it under the terms of the Do What The Fuck You Want
- #// To Public License, Version 2, as published by Sam Hocevar. See
- #// http://sam.zoy.org/wtfpl/COPYING for more details.
- #//
-
-
- [ -z $STEAMWM_FORCE_BORDERS ] && export STEAMWM_FORCE_BORDERS=1
- [ -z $STEAMWM_PREVENT_MOVE ] && export STEAMWM_PREVENT_MOVE=1
- [ -z $STEAMWM_FIX_NET_WM_NAME ] && export STEAMWM_FIX_NET_WM_NAME=0
- [ -z $STEAMWM_GROUP_WINDOWS ] && export STEAMWM_GROUP_WINDOWS=1
- [ -z $STEAMWM_SET_WINDOW_TYPE ] && export STEAMWM_SET_WINDOW_TYPE=1
- [ -z $STEAMWM_SET_FIXED_SIZE ] && export STEAMWM_SET_FIXED_SIZE=0
- [ -z $STEAMWM_MANAGE_ERRORS ] && export STEAMWM_MANAGE_ERRORS=1
-
-
- self="$(readlink -f "$(which "$0")")"
- name="$(basename "$self" .cpp)"
- out="$(dirname "$self")/$name"
- soname="$name.so"
-
-
- #// On amd64 platforms, compile a dummy 64-bit steamwm.so,
- #// so that native 64-bit tools invoked by Steam and its
- #// launch script won't spam (harmless) ld.so errors.
- if [ -f '/lib64/ld-linux-x86-64.so.2' ] ; then
- dout="$out/64"
- mkdir -p "$dout"
- if ! [ -f "$dout/$soname" ] ; then
- echo -e "\n" | gcc -shared -fPIC -m64 -x c - -o "$dout/$soname" &> /dev/null
- strip "$dout/$soname" &> /dev/null
- #// ignore all errors - this may at worst cause warnings later
- fi
- export LD_LIBRARY_PATH="$dout:$LD_LIBRARY_PATH"
- fi
-
-
- #// Compile the LD_PRELOAD library
- mkdir -p "$out"
- if [ "$self" -nt "$out/$soname" ] ; then
- echo "Compiling $soname..."
- g++ -shared -fPIC -m32 -x c++ "$self" -o "$out/$soname" \
- -lX11 -static-libgcc -static-libstdc++ \
- -O3 -Wall -Wextra -DSONAME="$soname" \
- || exit 1
- fi
-
-
- #// Run the executable
- export LD_PRELOAD="$soname:$LD_PRELOAD"
- export LD_LIBRARY_PATH="$out:$LD_LIBRARY_PATH"
- [ -z "$1" ] || exec "$@"
-
-
- exit
-
- #endif // 0
-
-
- #ifndef _GNU_SOURCE
- #define _GNU_SOURCE
- #endif
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <dlfcn.h>
- #include <X11/Xlib.h>
- #include <X11/Xatom.h>
- #include <X11/Xutil.h>
-
- #define STR_(x) # x
- #define STR(x) STR_(x)
-
-
- // List of window titles for windows that should not be marked as dialogs
- static const char * main_windows[] = {
- "Steam",
- "Friends",
- };
- // List of window titles for windows that should be dialogs even if unmanaged
- static const char * dialog_windows[] = {
- "Untitled",
- "Steam - Go Online",
- };
- static const char * fixed_size_windows[] = {
- "Settings",
- "About Steam",
- "Backup and Restore Programs",
- "Steam - Error",
- "Steam - Self Updater",
- };
- static const char * fixed_size_suffixes[] = {
- " - Properties",
- " - Category",
- };
-
-
- static bool force_borders = false;
- static bool prevent_move = false;
- static bool fix_net_wm_name = false;
- static bool group_windows = false;
- static bool set_window_type = false;
- static bool set_fixed_size = false;
- static bool manage_errors = false;
-
- extern "C" {
- extern char * program_invocation_short_name; // provided by glibc
- }
-
- static bool get_setting(const char * name) {
- char * setting = getenv(name);
- return (setting && setting[0] != '\0' && setting[0] != '0');
- }
-
- void steamwm_init(void) __attribute__((constructor));
- void steamwm_init(void) {
-
- // Only attach to steam!
- if(strcmp(program_invocation_short_name, "steam") != 0) {
- return;
- }
-
- // Prevent steamwm.so from being attached to processes started by steam
- const char * envname = "LD_PRELOAD";
- const char * oldenv = getenv(envname);
- if(oldenv) {
- char * env = strdup(oldenv);
- char * pos = strstr(env, STR(SONAME));
- if(pos) {
- size_t len1 = strlen(STR(SONAME));
- size_t len2 = strlen(pos + len1);
- memmove(pos, pos + len1, len2);
- *(pos + len2) = '\0';
- setenv(envname, env, 1);
- }
- free(env);
- }
-
- force_borders = get_setting("STEAMWM_FORCE_BORDERS");
- prevent_move = get_setting("STEAMWM_PREVENT_MOVE");
- fix_net_wm_name = get_setting("STEAMWM_FIX_NET_WM_NAME");
- group_windows = get_setting("STEAMWM_GROUP_WINDOWS");
- set_window_type = get_setting("STEAMWM_SET_WINDOW_TYPE");
- set_fixed_size = get_setting("STEAMWM_SET_FIXED_SIZE");
- manage_errors = get_setting("STEAMWM_MANAGE_ERRORS");
-
-
- fprintf(stderr, "\n[steamwm] attached to steam:\n force_borders %d\n"
- " prevent_move %d\n fix_net_wm_name %d\n"
- " group_windows %d\n set_window_type %d\n"
- " set_fixed_size %d\n manage_errors %d\n\n",
- int(force_borders), int(prevent_move), int(fix_net_wm_name),
- int(group_windows), int(set_window_type), int(set_fixed_size),
- int(manage_errors));
-
- }
-
-
- /* helper functions */
-
- #define BASE_NAME(SymbolName) base_ ## SymbolName
- #define TYPE_NAME(SymbolName) SymbolName ## _t
- #define INTERCEPT(ReturnType, SymbolName, ...) \
- typedef ReturnType (*TYPE_NAME(SymbolName))(__VA_ARGS__); \
- static void * const BASE_NAME(SymbolName) = dlsym(RTLD_NEXT, STR(SymbolName)); \
- ReturnType SymbolName(__VA_ARGS__)
- #define BASE(SymbolName) ((TYPE_NAME(SymbolName))BASE_NAME(SymbolName))
-
- static bool is_unmanaged_window(Display * dpy, Window w);
- static void set_is_unmanaged_window(Display * dpy, Window w, bool is_unmanaged);
- static bool is_fixed_size_window_name(const char * name);
- static bool is_main_window_name(const char * name);
- static bool is_dialog_window_name(const char * name);
- static void set_window_desired_size(Display * dpy, Window w, int width, int height,
- bool set_fixed);
- static void set_window_property(Display * dpy, Window w, Atom property, Atom type,
- long value);
- static void set_window_group_hint(Display * dpy, Window w, XID window_group);
- static void set_window_is_dialog(Display * dpy, Window w, bool is_dialog);
- static void set_window_modal(Display * dpy, Window w);
-
-
- /* fix window titles and types, and add window borders & title bars */
-
- static Window first_window = None, second_window = None;
-
- static void name_changed(Display * dpy, Window w, const unsigned char * data, int n);
-
- INTERCEPT(void, XSetWMName,
- Display * dpy,
- Window w,
- XTextProperty * prop
- ) {
-
- if(prop->format == 8) {
- // The libX11 pulled in with STEAM_RUNTIME=1 has XSetWMName (or XSetTextProperty?)
- // liked/optimized to use internal functions and not the global XChangeProperty,
- // so our override below won't work -> also intercept XSetWMName().
- name_changed(dpy, w, prop->value, prop->nitems);
- }
-
- return BASE(XSetWMName)(dpy, w, prop);
- }
-
- INTERCEPT(int, XChangeProperty,
- Display * dpy,
- Window w,
- Atom property,
- Atom type,
- int format,
- int mode,
- const unsigned char * data,
- int n
- ) {
-
- if(property == XA_WM_NAME && format == 8) {
- name_changed(dpy, w, data, n);
- }
-
- if(manage_errors && property == XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False)) {
- if(!is_unmanaged_window(dpy, w) && type == XA_ATOM && format == 32 && n > 0) {
- Atom menu = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_MENU", False);
- Atom type = reinterpret_cast<const long * /* sic */>(data)[0];
- if(type == menu) {
- // Ignore the window type Steam sets on error dialogs.
- return 1;
- }
- }
- }
-
- if(force_borders && property == XInternAtom(dpy, "_MOTIF_WM_HINTS", False)) {
- // Don't suppress window borders!
- return 1;
- }
-
- return BASE(XChangeProperty)(dpy, w, property, type, format, mode, data, n);
- }
-
- static void name_changed(Display * dpy, Window w, const unsigned char * data, int n) {
-
- char * value = (char *)data;
-
- if(fix_net_wm_name) {
- // Use the XA_WM_NAME as both XA_WM_NAME and _NET_WM_NAME.
- // Steam sets _NET_WM_NAME to just "Steam" for all windows.
- const unsigned char * name = data;
- unsigned char * buffer = NULL;
- int nn = n;
- if(n > 0 && strstr((char *)data, "Steam") == 0) {
- // Make sure "Steam" is in all window titles.
- char suffix[] = " - Steam";
- nn = n + sizeof(suffix) - 1;
- name = buffer = (unsigned char *)malloc(nn + 1);
- memcpy(buffer, data, n);
- memcpy(buffer + n, suffix, sizeof(suffix));
- }
- Atom net_wm_name = XInternAtom(dpy, "_NET_WM_NAME", False);
- Atom utf8_string = XInternAtom(dpy, "UTF8_STRING", False);
- BASE(XChangeProperty)(dpy, w, net_wm_name, utf8_string, 8, PropModeReplace, name, nn);
- if(buffer) {
- free(buffer);
- }
- }
-
- if(manage_errors && is_unmanaged_window(dpy, w) && is_dialog_window_name(value)) {
- // Error dialogs should be managed by the window manager.
- set_is_unmanaged_window(dpy, w, false);
- set_window_modal(dpy, w);
- set_window_desired_size(dpy, w, -1, -1, true);
- }
-
- if(set_window_type && !is_unmanaged_window(dpy, w)
- && w != first_window && w != second_window) {
- // Set the window type for non-menu windows.
- // This should probably be done *before* mapping the windows,
- // but then we don't have a title yet.
- // Try to guess the window type from the title.
- set_window_is_dialog(dpy, w, !is_main_window_name(value));
- }
-
- if(set_fixed_size && is_fixed_size_window_name(value)) {
- // Set fixed size hints for windows with static layouts.
- set_window_desired_size(dpy, w, -1, -1, true);
- }
-
- }
-
-
- /* ignore window move requests for non-menu windows */
-
- INTERCEPT(int, XResizeWindow,
- Display * dpy,
- Window w,
- unsigned int width,
- unsigned int height
- ) {
-
- if(set_fixed_size || manage_errors) {
- // Set fixed size hints for windows with static layouts.
- set_window_desired_size(dpy, w, width, height, false);
- }
-
- return BASE(XResizeWindow)(dpy, w, width, height);
- }
-
- INTERCEPT(int, XMoveResizeWindow,
- Display * dpy,
- Window w,
- int x,
- int y,
- unsigned int width,
- unsigned int height
- ) {
-
- if(set_fixed_size || manage_errors) {
- // Set fixed size hints for windows with static layouts.
- set_window_desired_size(dpy, w, width, height, false);
- }
-
- if(prevent_move && !is_unmanaged_window(dpy, w)) {
- // Ignore the position request for non-menu windows.
- return BASE(XResizeWindow)(dpy, w, width, height);
- }
-
- return BASE(XMoveResizeWindow)(dpy, w, x, y, width, height);
- }
-
- INTERCEPT(int, XMoveWindow,
- Display * dpy,
- Window w,
- int x,
- int y
- ) {
-
- if(prevent_move && !is_unmanaged_window(dpy, w)) {
- // Ignore the position request for non-menu windows.
- return 1;
- }
-
- return BASE(XMoveWindow)(dpy, w, x, y);
- }
-
-
- /* group windows and force the first and second window to be dialogs */
-
- INTERCEPT(int, XMapWindow,
- Display * dpy,
- Window w
- ) {
-
- if(first_window == None) {
- first_window = w;
- }
-
- if(group_windows) {
- // Group all steam windows.
- Atom leader = XInternAtom(dpy, "WM_CLIENT_LEADER", False);
- set_window_property(dpy, w, leader, XA_WINDOW, first_window);
- set_window_group_hint(dpy, w, first_window);
- }
-
- if(set_window_type && (w == first_window || second_window == None)) {
- // Force the first and second windows to be marked as dialogs.
- set_window_is_dialog(dpy, w, true);
- if(w != first_window) {
- // Give the second window a proper size *now* so that the WM can center it.
- second_window = w;
- XResizeWindow(dpy, w, 384, 107);
- }
- }
-
- return BASE(XMapWindow)(dpy, w);
- }
-
- INTERCEPT(void, XSetWMNormalHints,
- Display * dpy,
- Window w,
- XSizeHints * hints
- ) {
- XSizeHints old_hints;
- long supplied;
- // Prevent steam from overriding the max size hints
- if((set_fixed_size || manage_errors)
- && XGetWMNormalHints(dpy, w, &old_hints, &supplied)) {
- if(!(hints->flags & PSize) && (old_hints.flags & PSize)) {
- hints->flags |= PSize;
- hints->width = old_hints.width;
- hints->height = old_hints.height;
- }
- if((old_hints.flags & PMinSize) && (old_hints.flags & PMaxSize)
- && old_hints.min_width == old_hints.max_width
- && old_hints.min_height == old_hints.max_height) {
- hints->flags |= PMinSize | PMaxSize;
- hints->min_width = hints->max_width = old_hints.max_width;
- hints->min_height = hints->max_height = old_hints.max_height;
- }
- }
- BASE(XSetWMNormalHints)(dpy, w, hints);
- }
-
-
- /* helper function implementations */
-
- static bool is_unmanaged_window(Display * dpy, Window w) {
- XWindowAttributes xwa;
- if(!XGetWindowAttributes(dpy, w, &xwa)) {
- return false;
- }
- return xwa.override_redirect;
- }
-
- static void set_is_unmanaged_window(Display * dpy, Window w, bool is_unmanaged) {
- XSetWindowAttributes xswa;
- xswa.override_redirect = is_unmanaged;
- XChangeWindowAttributes(dpy, w, CWOverrideRedirect, &xswa);
- }
-
- template <size_t N>
- static bool is_in_array(const char * (&array)[N], const char * needle) {
-
- for(unsigned i = 0; i < N; i++) {
- if(strcmp(needle, array[i]) == 0) {
- return true;
- }
- }
-
- return false;
- }
-
- static bool is_main_window_name(const char * name) {
- return is_in_array(main_windows, name);
- }
-
- static bool is_dialog_window_name(const char * name) {
- return is_in_array(dialog_windows, name);
- }
-
- static bool is_fixed_size_window_name(const char * name) {
-
- if(is_in_array(fixed_size_windows, name)) {
- return true;
- }
-
- int len = strlen(name);
- for(unsigned i = 0; i < sizeof(fixed_size_suffixes)/sizeof(*fixed_size_suffixes); i++) {
- int plen = strlen(fixed_size_suffixes[i]);
- if(len > plen && strcmp(name + len - plen, fixed_size_suffixes[i]) == 0) {
- return true;
- }
- }
-
- return false;
- }
-
- static void set_window_desired_size(Display * dpy, Window w, int width, int height,
- bool set_fixed) {
- XSizeHints xsh;
- long supplied;
- if(!XGetWMNormalHints(dpy, w, &xsh, &supplied)) {
- xsh.flags = 0;
- }
- if(width > 0 && height > 0) {
- // Store the desired size.
- // PBaseSize (base_width and base_height) sounds more appropriate
- // (and is not obsolete), but some window managers won't let the window
- // get smaller than the base size.
- xsh.flags |= PSize;
- xsh.width = width, xsh.height = height;
- } else if(xsh.flags & PSize) {
- // Retrieve the desired size.
- width = xsh.width, height = xsh.height;
- } else {
- Window root;
- int x, y;
- unsigned int cwidth, cheight, border_width, depth;
- if(!XGetGeometry(dpy, w, &root, &x, &y, &cwidth, &cheight, &border_width, &depth)) {
- return;
- }
- width = cwidth, height = cheight;
- }
- if(set_fixed || ((xsh.flags & PMinSize) && (xsh.flags & PMaxSize)
- && xsh.min_width == xsh.max_width && xsh.min_height == xsh.max_height)) {
- xsh.flags |= PMinSize | PMaxSize;
- xsh.min_width = xsh.max_width = width;
- xsh.min_height = xsh.max_height = height;
- }
- BASE(XSetWMNormalHints)(dpy, w, &xsh);
- }
-
- static void set_window_property(Display * dpy, Window w, Atom property, Atom type,
- long value) {
- unsigned char data[sizeof(long)];
- memcpy(&data, &value, sizeof(long));
- BASE(XChangeProperty)(dpy, w, property, type, 32, PropModeReplace, data, 1);
- }
-
- static void set_window_group_hint(Display * dpy, Window w, XID window_group) {
- XWMHints base_hints;
- XWMHints * h = XGetWMHints(dpy, w);
- if(!h) {
- h = &base_hints;
- h->flags = 0;
- }
- h->flags |= WindowGroupHint;
- h->window_group = window_group;
- XSetWMHints(dpy, w, h);
- if(h != &base_hints) {
- XFree(h);
- }
- }
-
- static void set_window_is_dialog(Display * dpy, Window w, bool is_dialog) {
- Atom window_type = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
- if(is_dialog) {
- Atom dialog = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False);
- set_window_property(dpy, w, window_type, XA_ATOM, dialog);
- } else {
- Atom normal = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_NORMAL", False);
- set_window_property(dpy, w, window_type, XA_ATOM, normal);
- }
- }
-
- static void set_window_modal(Display * dpy, Window w) {
- XWindowAttributes xwa;
- if(!XGetWindowAttributes(dpy, w, &xwa)) {
- return;
- }
- Atom state = XInternAtom(dpy, "_NET_WM_STATE", False);
- Atom state_modal = XInternAtom(dpy, "_NET_WM_STATE_MODAL", False);
- if(xwa.map_state == IsUnmapped) {
- set_window_property(dpy, w, state, XA_ATOM, state_modal);
- } else {
- XEvent event;
- event.type = ClientMessage;
- event.xclient.message_type = state;
- event.xclient.window = w;
- event.xclient.format = 32;
- event.xclient.data.l[0] = 1; // add
- event.xclient.data.l[1] = state_modal;
- event.xclient.data.l[2] = 0;
- event.xclient.data.l[3] = 1;
- event.xclient.data.l[4] = 0;
- XSendEvent(dpy, DefaultRootWindow(dpy), False,
- (SubstructureNotifyMask | SubstructureRedirectMask), &event);
- }
- }
-