Window Rules
Window rules allow you to automatically configure how specific applications behave when they open. You can control which desktop a window appears on, whether it floats, its layer, and much more.
Understanding Window Rules
When a window opens, bspwm checks its class name and instance name against your rules. If there's a match, the rule's settings are applied automatically.
Finding Window Class and Instance Names
Before creating rules, you need to identify windows. Use xprop:
xprop | grep WM_CLASS
Click on a window, and you'll see output like:
WM_CLASS(STRING) = "Navigator", "firefox"
The format is "instance_name", "class_name". In this example:
- Instance name: Navigator
- Class name: firefox
Alternative methods:
# Using xdotool
xdotool selectwindow getwindowclassname
# Using bspwm's query (for existing windows)
bspc query -T -n focused | jq '.client.className'
Creating Rules
Basic Syntax
bspc rule -a CLASS_NAME[:INSTANCE_NAME] [CONSEQUENCES...]
Examples:
# Match by class name only
bspc rule -a Firefox desktop='^2'
# Match by class and instance
bspc rule -a Firefox:Navigator desktop='^2'
# Wildcard matching
bspc rule -a '*:*:Picture-in-Picture' state=floating
Rule Matching
- Class only:
Firefox- Matches any window with class "Firefox" - Class:Instance:
Firefox:Navigator- Matches class "Firefox" AND instance "Navigator" - Wildcards:
*matches anything - Title matching:
CLASS:INSTANCE:TITLE- Some special windows need title matching
Rule Consequences
Desktop Placement
Control which desktop windows appear on.
# Send to desktop by index
bspc rule -a Firefox desktop='^2'
# Send to desktop by name
bspc rule -a Firefox desktop='Web'
# Send to specific monitor's desktop
bspc rule -a Slack desktop='HDMI-1:^3'
Window State
Control the window's tiling state.
# Always float
bspc rule -a Gimp state=floating
# Always tile (override floating defaults)
bspc rule -a Steam state=tiled
# Pseudo-tiled (maintains preferred size)
bspc rule -a Pavucontrol state=pseudo_tiled
# Fullscreen
bspc rule -a mpv state=fullscreen
| State | Effect |
|---|---|
tiled |
Window is managed by the tiling tree |
pseudo_tiled |
Window maintains its size, centered in tile space |
floating |
Window floats freely above tiled windows |
fullscreen |
Window fills the screen, no borders |
Stacking Layer
Control the window's stacking order.
# Always on top
bspc rule -a 'Picture-in-Picture' layer=above
# Always below other windows
bspc rule -a Conky layer=below
# Normal layer (default)
bspc rule -a Firefox layer=normal
Flags
Set special window flags.
# Sticky (follows you to all desktops)
bspc rule -a Spotify sticky=on
# Hidden (starts hidden, for scratchpads)
bspc rule -a scratchterm hidden=on
# Locked (can't be closed with bspc node -c)
bspc rule -a important locked=on
# Private (maintains position)
bspc rule -a terminal private=on
Focus Behavior
Control focus when the window opens.
# Follow focus to the new window (default)
bspc rule -a Firefox follow=on
# Don't steal focus when opening
bspc rule -a Slack follow=off
# Focus the window (bring it to front)
bspc rule -a urgent_app focus=on
Centering Floating Windows
# Center a floating window on the screen
bspc rule -a Pavucontrol state=floating center=on
Split Direction and Ratio
Control how the tree splits when the window is inserted.
# Split to the right
bspc rule -a terminal split_dir=east
# Split below
bspc rule -a terminal split_dir=south
# Custom split ratio (0-1)
bspc rule -a sidebar split_ratio=0.3 split_dir=west
Border Control
# Borderless window
bspc rule -a Conky border=off
# Force border
bspc rule -a Firefox border=on
Management Control
# Don't manage this window (bspwm ignores it)
bspc rule -a Conky manage=off
# Manage it (useful for override rules)
bspc rule -a special manage=on
One-Shot Rules
One-shot rules apply only to the next window matching the pattern, then automatically delete themselves.
# The next Firefox window will float, then this rule is removed
bspc rule -a Firefox --one-shot state=floating
# Short form
bspc rule -a Firefox -o state=floating
Use cases:
- Temporarily opening a window differently
- Scripted window placement
- Dialogs from scripts
Managing Rules
List All Rules
bspc rule -l
Output shows rule index and configuration:
Navigator:firefox => desktop=^2 follow=on
Gimp:* => state=floating
*:*:Picture-in-Picture => state=floating sticky=on layer=above
Remove Rules
# Remove by class/instance pattern
bspc rule -r Firefox
bspc rule -r Firefox:Navigator
# Remove by index (from rule -l output)
bspc rule -r head # First rule
bspc rule -r tail # Last rule
bspc rule -r '^3' # Third rule
# Remove all rules
bspc rule -r '*'
External Rules Command
For complex, dynamic rules, use an external script instead of static rules.
Setup
bspc config external_rules_command "$HOME/.config/bspwm/external_rules"
Script Structure
The script receives these arguments:
- Window ID
- Class name
- Instance name
- Monitor, desktop, and node selector consequences (from matching rules)
It outputs rule consequences in key=value format.
Example External Rules Script
#!/bin/bash
# ~/.config/bspwm/external_rules
wid=$1
class=$2
instance=$3
# Get window title for more specific matching
title=$(xdotool getwindowname "$wid" 2>/dev/null)
# Debug: uncomment to log window info
# echo "Window: $wid, Class: $class, Instance: $instance, Title: $title" >> /tmp/bspwm_rules.log
# Float dialogs and popups
case "$instance" in
*[Dd]ialog*|*[Pp]opup*|*[Pp]references*)
echo "state=floating center=on"
;;
esac
# Float windows with specific titles
case "$title" in
"Save As"*|"Open File"*|"Confirm"*)
echo "state=floating center=on"
;;
"Picture-in-Picture"|"Picture in picture")
echo "state=floating sticky=on layer=above"
;;
esac
# Application-specific rules
case "$class" in
Spotify)
echo "desktop=^10 follow=off"
;;
Slack)
echo "desktop=^9 follow=off"
;;
Gimp)
echo "state=floating follow=on"
;;
Steam)
case "$title" in
"Steam"|"Steam - News"*)
echo "state=floating"
;;
*)
echo "state=tiled"
;;
esac
;;
esac
# Float small windows (requires xdotool)
geometry=$(xdotool getwindowgeometry "$wid" 2>/dev/null)
if [[ "$geometry" =~ ([0-9]+)x([0-9]+) ]]; then
width="${BASH_REMATCH[1]}"
height="${BASH_REMATCH[2]}"
if (( width < 400 && height < 400 )); then
echo "state=floating center=on"
fi
fi
Make it executable:
chmod +x ~/.config/bspwm/external_rules
Practical Rule Collections
Development Setup
# IDE on desktop 1
bspc rule -a jetbrains-idea desktop='^1' follow=on
# Terminal floats with half-screen size (pseudo-tiled)
bspc rule -a Alacritty:dev-terminal state=pseudo_tiled
# Browser for documentation on desktop 2
bspc rule -a Firefox desktop='^2'
# Database tools on desktop 3
bspc rule -a DBeaver desktop='^3'
Media and Entertainment
# Video player fullscreen
bspc rule -a mpv state=fullscreen
# Picture-in-picture always visible
bspc rule -a '*:*:Picture-in-Picture' state=floating sticky=on layer=above
# Music player on last desktop
bspc rule -a Spotify desktop='^10' follow=off
# Volume control floats and centers
bspc rule -a Pavucontrol state=floating center=on
Communication
# Chat apps on desktop 9, don't steal focus
bspc rule -a Slack desktop='^9' follow=off
bspc rule -a discord desktop='^9' follow=off
bspc rule -a TelegramDesktop desktop='^9' follow=off
# Email on desktop 8
bspc rule -a thunderbird desktop='^8'
Utility Windows
# File manager floats
bspc rule -a Pcmanfm state=floating
bspc rule -a Thunar state=floating
# System monitor floats
bspc rule -a Lxappearance state=floating
bspc rule -a Nitrogen state=floating
# Password manager floats
bspc rule -a KeePassXC state=floating center=on
Scratchpad Implementation
Scratchpads are hidden windows you can toggle on/off. Here's how to implement them:
1. Create a Rule for the Scratchpad Window
bspc rule -a Alacritty:scratchpad sticky=on state=floating hidden=on
2. Create a Toggle Script
#!/bin/bash
# ~/.local/bin/scratchpad
id=$(xdotool search --classname scratchpad)
if [ -z "$id" ]; then
# Scratchpad doesn't exist, create it
alacritty --class Alacritty,scratchpad &
sleep 0.2
id=$(xdotool search --classname scratchpad)
# Position it (centered, 80% width, 60% height)
eval $(xdotool getdisplaygeometry --shell)
w=$((WIDTH * 80 / 100))
h=$((HEIGHT * 60 / 100))
x=$((WIDTH / 2 - w / 2))
y=$((HEIGHT / 2 - h / 2))
xdotool windowsize "$id" $w $h
xdotool windowmove "$id" $x $y
fi
# Toggle visibility
bspc node "$id" -g hidden -f
3. Add a Keybinding
In sxhkdrc:
super + grave
~/.local/bin/scratchpad
Troubleshooting Rules
Rule Not Working?
-
Check the class/instance name:
bash xprop | grep WM_CLASS -
List current rules:
bash bspc rule -l -
Check for typos - Class names are case-sensitive!
-
Order matters - Later rules can override earlier ones
-
Use external rules for debugging:
bash #!/bin/bash echo "$(date): $@" >> /tmp/bspwm_rules.log
Window Opens on Wrong Desktop?
- Check if multiple rules match
- External rules run after static rules
- Some applications spawn multiple windows (check all their classes)
Floating Windows Not Centered?
- Add
center=onto the rule - Some windows set their own geometry after opening
- Try using a one-shot rule or external rules script