Skip to content

Instantly share code, notes, and snippets.

@dscharrer
Last active July 10, 2023 00:05
Show Gist options
  • Save dscharrer/06d6b6a5370c4f6979f3 to your computer and use it in GitHub Desktop.
Save dscharrer/06d6b6a5370c4f6979f3 to your computer and use it in GitHub Desktop.
Various window management fixes for the Linux Steam client

Various window management fixes for the Linux Steam client

DISCLAIMER: Use at your own risk! This is in no way endorsed by VALVE.

These files are mirrored on both GitHub and Gist - use whichever you prefer.

Feature request for these changes to be implemented in Steam: issue #1040

steamwm.cpp

The window management fixes:

  • Force borders on non-menu windows.
  • Let the WM position non-menu/tooltip 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).
  • Tell the WM which Steam windows are dialogs. This lets the window manager place them more intelligently. For example, the WM might center dialogs.
  • Steam sets error dialogs as unmanaged windows - fix that.

Obsolete fixes (now disabled by default):

  • Set _NET_WM_NAME to the WM_NAME value to get better window titles.
  • Set fixed size hints for windows with a fixed layout.

Fixes can be individually enabled or disabled - for details see the comments in the source file.

This file compiles to a library that can be LD_PRELOADed into the Steam process. For your convenience it is also its own build and wrapper script.

Requires: g++ with support for x86 targets, Xlib + headers

Use:

$ chmod +x steamwm.cpp
$ ./steamwm.cpp steam

noframe.patch

This is a Steam skin that complements steamwm.cpp: It is exactly the same as the default skin, but with the window borders and controls removed.

To install it use:

$ chmod +x noframe.patch
$ ./noframe.patch

and then select the noframe skin in the Steam settings.

#!/bin/sh
# Steam skin without any window decorations.
# Same as the default skin, but with the window borders and controls removed.
# To in stall it just run ./noframe.patch
self="$(readlink -f "$(which "$0")")"
name="$(basename "$self" .patch)"
skin="skins/$name"
cd ~/.steam/root/
if [ -e "$skin" ] ; then
echo "Skin '$name' already exists!"
echo "Remove \"$(readlink -f "$skin")\" first."
exit 1
fi
mkdir -p "$skin/resource/styles"
cp resource/styles/steam.styles "$skin/resource/styles/"
cp resource/steamscheme.res "$skin/resource/"
dos2unix -f "$skin/resource/styles/steam.styles"
dos2unix -f "$skin/resource/steamscheme.res"
patch -ld "$skin" -p0 < "$self"
exit
diff -ru resource/steamscheme.res skins/noframe/resource/steamscheme.res
--- resource/steamscheme.res 2011-08-02 14:14:04.000000000 +0200
+++ skins/noframe/resource/steamscheme.res 2012-11-17 06:01:22.762356738 +0100
@@ -94,56 +94,27 @@
frame_title
{
- xpos 0
- ypos 1
- wide max
- tall 28
- AutoResize 1
+ visible 0 // hidden
}
frame_captiongrip
{
- xpos 4
- ypos 4
- wide r20
- tall 60
- AutoResize 1
+ visible 0 // hidden
}
frame_minimize
{
- xpos r45
- xpos 22 [$OSX]
- ypos 7
- ypos 3 [$OSX]
- wide 20
- tall 20
- PinCorner 1
- PinCorner 0 [$OSX]
+ visible 0 // hidden
}
frame_maximize
{
- xpos r59
- xpos 43 [$OSX]
- ypos 7
- ypos 3 [$OSX]
- wide 20
- tall 20
- visible 0 [!$OSX]
- PinCorner 1
- PinCorner 0 [$OSX]
- }
+ visible 0 // hidden
+ }
+
frame_close
{
- xpos r29
- xpos 1 [$OSX]
- ypos 7
- ypos 3 [$OSX]
- wide 20
- tall 20
- PinCorner 1
- PinCorner 0 [$OSX]
+ visible 0 // hidden
}
frame_brGrip
@@ -161,7 +132,7 @@
sheet
{
xpos 9
- ypos 26
+ ypos 9
wide r9
tall r48
}
@@ -198,7 +169,7 @@
subpanel
{
xpos 10
- ypos 28
+ ypos 10
wide r10
tall r48
AutoResize 3
diff -ru resource/styles/steam.styles skins/noframe/resource/styles/steam.styles
--- resource/styles/steam.styles 2015-07-29 10:23:54.131952610 +0200
+++ skins/noframe/resource/styles/steam.styles 2015-07-29 10:25:08.610957742 +0200
@@ -806,25 +806,11 @@
Frame
{
bgcolor="DialogBG"
- render
- {
- 0="gradient_horizontal( x0, y1 - 1, x1 + 1, y1, ClientBG, focus )"
- 1="gradient( x1 - 1, y0, x1, y1, ClientBG, focus )"
- 2="gradient_horizontal( x0, y0, x1, y0 + 1, focus0, ClientBG )"
- 3="gradient( x0, y0, x0 + 1, y1, focus0, ClientBG )"
- }
}
Frame:FrameFocus
{
bgcolor="DialogBG"
- render
- {
- 0="gradient_horizontal( x0, y1 - 1, x1 + 1, y1, ClientBG, focus2 )"
- 1="gradient( x1 - 1, y0, x1, y1, ClientBG, focus2 )"
- 2="gradient_horizontal( x0, y0, x1, y0 + 1, focus, ClientBG )"
- 3="gradient( x0, y0, x0 + 1, y1, focus, ClientBG )"
- }
}
FrameBRGripPanel
@@ -839,161 +825,88 @@
FrameMinimizeButton
{
- render_bg {}
- image="graphics/win32_win_min"
- image="graphics/osx_win_dis" [$OSX]
}
FrameMinimizeButton:hover
{
- render_bg {}
- bgcolor="none"
- image="graphics/win32_win_min_hover"
- image="graphics/osx_min_hov" [$OSX]
}
FrameMinimizeButton:framefocus [$OSX]
{
- render_bg {}
- bgcolor="none"
- image="graphics/osx_min_def"
}
FrameMinimizeButton:framefocus:hover [$OSX]
{
- render_bg {}
- bgcolor="none"
- image="graphics/osx_min_hov"
}
FrameMinimizeButton:active [$OSX]
{
- render_bg {}
- bgcolor="none"
- image="graphics/osx_min_down"
}
// need the maximize button to have different styles for OSX & win32
FrameMaximizeButton
{
- render_bg {}
- image="graphics/win32_win_max"
- image="graphics/osx_win_dis" [$OSX]
}
FrameMaximizeButton:hover
{
- render_bg {}
- bgcolor="none"
- image="graphics/win32_win_max_hover"
- image="graphics/osx_max_hov" [$OSX]
}
FrameMaximizeButton:framefocus
{
- render_bg {}
- bgcolor="none"
- image="graphics/win32_win_max"
- image="graphics/osx_max_def" [$OSX]
}
FrameMaximizeButton:framefocus:hover
{
- render_bg {}
- bgcolor="none"
- image="graphics/win32_win_max_hover"
- image="graphics/osx_max_hov" [$OSX]
}
FrameMaximizeButton:active
{
- render_bg {}
- bgcolor="none"
- image="graphics/win32_win_max_hover"
- image="graphics/osx_max_down" [$OSX]
}
// these are for when the maximize button becomes the restore button
FrameRestoreButton
{
- render_bg {}
- image="graphics/win32_win_restore"
- image="graphics/osx_win_dis" [$OSX]
}
FrameRestoreButton:hover
{
- render_bg {}
- bgcolor="none"
- image="graphics/win32_win_restore_hover"
- image="graphics/osx_max_hov" [$OSX]
}
FrameRestoreButton:framefocus
{
- render_bg {}
- bgcolor="none"
- image="graphics/win32_win_restore"
- image="graphics/osx_max_def" [$OSX]
}
FrameRestoreButton:framefocus:hover
{
- render_bg {}
- bgcolor="none"
- image="graphics/win32_win_restore_hover"
- image="graphics/osx_max_hov" [$OSX]
}
FrameRestoreButton:active
{
- render_bg {}
- bgcolor="none"
- image="graphics/win32_win_restore_hover"
- image="graphics/osx_max_down" [$OSX]
}
FrameCloseButton
{
- render_bg {}
- image="graphics/win32_win_close"
- image="graphics/osx_win_dis" [$OSX]
}
FrameCloseButton:hover
{
- render_bg {}
- image="graphics/win32_win_close_hover"
- image="graphics/osx_close_hov" [$OSX]
}
FrameCloseButton:framefocus [$OSX]
{
- render_bg {}
- bgcolor="none"
- image="graphics/osx_close_def"
}
FrameCloseButton:framefocus:hover [$OSX]
{
- render_bg {}
- bgcolor="none"
- image="graphics/osx_close_hov"
}
FrameCloseButton:active [$OSX]
{
- render_bg {}
- bgcolor="none"
- image="graphics/osx_close_down"
}
FrameCloseButton:disabled
{
- render_bg {}
- bgcolor="none"
- image="graphics/win32_win_close_disabled"
- image="graphics/osx_win_dis" [$OSX]
}
FrameTitle
#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);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment