driftwm reads its configuration from ~/.config/driftwm/config.toml (respecting
XDG_CONFIG_HOME). Every field is optional — anything you omit uses the built-in
default shown below. Copy config.reference.toml to
get started, then uncomment and edit only the lines you want to change. Validate a
config with driftwm --check-config.
Default: "super"
Window manager modifier key: "super" (default) or "alt"
Default: false
Sloppy focus: keyboard focus follows the pointer to windows. Moving to empty canvas keeps focus; click empty canvas to unfocus.
Default: "center"
Where new windows spawn when no window rule positions them:
"center"— viewport center; camera animates to the new window."cursor"— centered on the cursor (clamped to the active output's usable area); camera stays put unless zoomed out and zoom.reset_on_new_window is true."auto"— snap-place adjacent to the focused window's cluster: try focused's edges (clockwise from the one nearest the viewport center), then BFS to neighbors. Falls back to "center" when no focused window or no valid placement was found.
Default: []
Commands to run at startup (after WAYLAND_DISPLAY is set). Each entry is passed to sh -c, so full shell syntax works (pipes, &&, env vars).
Running driftwm as a systemd session (via driftwm-session / a display manager) also launches XDG autostart entries (~/.config/autostart, /etc/xdg/autostart), in addition to the autostart list above. To opt out:
systemctl --user mask xdg-desktop-autostart.target
Example:
autostart = ["waybar", "swaync"]Environment variables set before any clients launch. Child processes (autostart, exec bindings) inherit these. These override the compositor's built-in toolkit defaults (MOZ_ENABLE_WAYLAND, QT_QPA_PLATFORM, SDL_VIDEODRIVER, GDK_BACKEND, ELECTRON_OZONE_PLATFORM_HINT).
Example:
QT_WAYLAND_DISABLE_WINDOWDECORATION = "1"
MOZ_ENABLE_WAYLAND = "1"Default: "us"
XKB layout (e.g., "us,ru" for multi-layout)
Default: false
for multi-layout setups
Default: ""
XKB variant (e.g., "dvorak", or "," for two defaults)
Default: ""
XKB options (e.g., "grp:win_space_toggle" for Super+Space layout switch) grp:alt_shift_toggle/grp:ctrl_shift_toggle break those chord bindings — use a tap binding (see [keybindings]) instead
Default: ""
XKB model (e.g., "pc105")
Default: 25
keys/sec
Default: 200
ms before repeat starts
Default: true
match bindings by physical key position across layouts
Default: true
num lock state on startup
Default: false
caps lock state on startup
Default: true
enable tap-to-click
Default: true
reverse scroll direction (content follows fingers)
Default: true
double-tap-hold to drag
Default: 0.0
pointer acceleration (-1.0 to 1.0)
Default: "adaptive"
"flat" or "adaptive"
Default: "none"
none = device default; clickfinger = finger count (1=left, 2=right, 3=middle); button_areas = position on trackpad
Default: true
ignore trackpad input shortly after a key press (palm rejection)
Default: 0.0
pointer acceleration (-1.0 to 1.0)
Default: "flat"
"flat" or "adaptive"
Default: false
reverse scroll direction
Default: false
swap left/right mouse buttons (skipped on devices without left-handed support)
Default: true
enable touchscreen support
Default: "none"
pin touch to an output by connector name; "none" (default) auto-detects the touchscreen by physical size + connector. Only set this to tell apart two identically-sized touchscreens.
Example:
map_to_output = "DP-2"Default: "none"
XCURSOR_THEME; "none" = inherit from environment (e.g. "Adwaita")
Default: 0
XCURSOR_SIZE; 0 = inherit from environment (e.g. 24)
Default: 0.5
cursor opacity on non-active outputs (0.0–1.0)
Default: 1.5
trackpad (scroll/gestures) pan multiplier
Default: 1.0
mouse (drag) pan multiplier (1.0 = direct)
Default: 1.0
touchscreen gesture pan speed multiplier
Default: 0.5
momentum coast: 0 = off, 0.5 = default, 1 = floatiest
Default: 0.3
camera lerp factor (higher = faster)
Default: true
on close, pan to the newly focused window if off-screen false = camera stays put; focus only moves to a visible window
Default: false
completed click on a partially off-screen window also pans it in fully visible → focus only
Default: 20
px per nudge-window action (mod-shift-arrow by default)
Default: 100.0
px per pan-viewport action (mod-ctrl-arrow by default)
Default: [[0, 0]]
Anchors: canvas points discoverable by center-nearest (4-finger swipe / Mod+Arrow) even when no window is there. Uses Y-up coordinate system.
Example: 4 corners
anchors = [[0, 0], [-1750, 1750], [1750, 1750], [1750, -1750], [-1750, -1750]]Default: 100.0
activation zone width (px from viewport edge)
Default: 4.0
px/frame at zone boundary
Default: 10.0
px/frame at viewport edge
Default: false
pan when the bare cursor touches a screen edge (not just while dragging). Toggle: "toggle-cursor-pan".
Default: 20.0
cursor edge-pan activation zone (px) — kept small so it doesn't trigger by accident. Pans at a constant speed_max within the zone (steady, push-speed independent); speed_min is unused here.
Default: 1.1
multiplier per keypress (1.1 = 10% per press)
Default: 1.0
trackpad pinch-zoom speed multiplier
Default: 1.0
mouse-wheel zoom speed multiplier
Default: 1.0
touchscreen gesture zoom speed multiplier
Default: 80.0
viewport px padding for zoom-to-fit (screen space)
Default: true
animate zoom to 1.0 when a new window is mapped (false = keep current zoom, pan only)
Default: true
animate zoom to 1.0 when an off-screen window requests focus (false = keep current zoom, pan only)
Default: true
magnetic edge snapping during window drag
Default: 12.0
gap between snapped windows (canvas px)
Default: 24.0
activation threshold (screen px from edge)
Default: 32.0
screen px past snap to break free
Default: false
also align corners (parallel edges line up: top-to-top, left-to-left)
Default: false
also align centers (midpoints line up along the moved axis)
Default: "#303030"
title bar background (default: dark gray)
Default: "#FFFFFF"
title text + close button × color (default: white)
Default: 10
clip window corners to this radius
Default: true
drop shadow under window chrome
Default: 25
SSD title bar text. The font is resolved via fontconfig — install the adwaita-fonts package for the default look; otherwise a generic sans is substituted.
SSD title bar height in px (default: 25)
Default: "Adwaita Sans"
title text font family
Default: 11
title text size in points (default: 11)
Default: "medium"
thin/extralight/light/regular/medium/ semibold/bold/extrabold/black (default: medium)
Default: "center"
"left" or "center" (default: center). "center" centers short titles and left-aligns + ellipsizes long ones
Default: "client"
Decoration mode for windows without a rule:
"client"— CSD: client draws its own titlebar (default)"minimal"— SSD: no titlebar; shadow, corners, and border still apply via [decorations] + per-window rules"none"— bare client surface: compositor adds zero chrome; per-window border/corner/shadow rules are ignored
"server" (driftwm titlebar) is intentionally not allowed as a global default: many toolkits (GTK, Electron) ignore xdg-decoration and keep drawing CSD, producing a misaligned double titlebar. Use it per-app via [[window_rules]] instead.
Default: 0
Borders apply to "client", "server", and "minimal" modes. "none" mode has no border unless one is set per-app in [[window_rules]].
px; 0 disables the border
Default: "#303030"
unfocused border (default: dark gray)
Default: "#303030"
focused border. Same default as above — set this to a different color to get a focus indicator.
Default: 2
number of Kawase down+up passes (default: 2)
Default: 1.1
per-pass texel spread (default: 1.1)
Default: 20
refresh rate of blur under an animated wallpaper (0-144, default 20; 0 = off, freezing the frost so it stops re-sampling the wallpaper). The background is blurred once into a shared full-output texture and each window slices its rect from it, so cost stays flat as windows are added; a window stacked over other windows falls back to an exact per-window blur at the same cadence. Animated wallpapers evolve slowly, so well below the output rate still looks continuous through frosted glass. Camera moves force a refresh.
Default: "default"
Five types: "default" (built-in dot-grid — the default), "shader", "tile", "wallpaper", "none". For shader/tile/wallpaper, path is the source.
Example:
type = "shader" # procedural GLSL (scrolls with canvas)
path = "/usr/local/share/driftwm/wallpapers/animated/fast_smoke.glsl"
type = "tile" # image tiled across the canvas
path = "~/Pictures/Wallpapers/tile.png"
type = "wallpaper" # single image fixed to viewport (does not scroll/zoom)
path = "~/Pictures/Wallpapers/wallpaper.jpg"
type = "none" # render no built-in background (path ignored), so a
# wlr-layer-shell wallpaper daemon (swaybg, swww, mpvpaper for live video)
# is the wallpaper. Launch it yourself; see docs/shaders.md.A "shader" can also sample an image via texture (bound to the shader's tex sampler) — a procedural effect on your image. See docs/shaders.md.
Example: shader sampling an image
type = "shader"
path = "/usr/local/share/driftwm/wallpapers/textured/ripple.glsl"
texture = "~/Pictures/Wallpapers/photo.jpg"Default: false
tile mode only: mirror-fold the image (2×2 reflected block) so a non-seamless image's edges always meet a reflection — no visible tile seams. Also gives a kaleidoscope/symmetry look. No-op for other background types. Single images only; gigapixel pyramidal TIFFs aren't mirrored (pre-mirror the source).
Default: false
Bake a heavy static shader to a texture once, then pan that — so it pans as cheaply as an image instead of recomputing every frame. ONLY correct for shaders that slide rigidly with the camera: u_camera used once, at full scale, as the only camera term. In GLSL:
vec2 canvas = v_coords * size + u_camera; // pan shifts the image 1:1
Parallax (u_camera * factor) renders WRONG; animated (u_time) and zoom-dependent (u_zoom) shaders are never cached and render live.
Default: false
Honor a shader's output alpha so transparent pixels reveal whatever sits below the background — e.g. a wlr-layer-shell Background surface from an external wallpaper engine. Forces the shader onto the live path, so cache_shader has no effect while this is on. Image backgrounds ignore it: tile/wallpaper PNGs with an alpha channel become see-through automatically. See docs/shaders.md.
Default: 128
Memory ceiling (MB) shared by cache_shader and gigapixel-TIFF wallpapers, with LRU eviction. Raise it for sharper revisits on large / HiDPI displays; lower it on memory-constrained machines (too low just keeps the background blurrier).
Default: 0
Frame-rate cap for animated (u_time) shader backgrounds. 0 = every output frame (default). Slow-moving shaders look identical well below the refresh rate; between ticks the compositor reuses the composited result instead of re-evaluating the shader, so this directly scales the background's GPU cost.
Default: []
Opt out of built-in default bindings by category, for a clean slate. Normally your [keybindings]/[mouse]/[gestures] entries merge with the built-ins (use = "none" to drop a single default). Listing a category here removes ALL of that category's defaults, leaving only your own entries. Categories: "keys", "mouse", "gestures".
Example: bring your own keyboard scheme, keep mouse + gesture defaults
disable_defaults = ["keys"]Keyboard bindings: "Modifier+...+Keysym" = "action [arg]" Merges with defaults. Use "none" to unbind a default binding. "mod" expands to mod_key. Literal modifiers: alt, super, ctrl, shift. Keysyms are XKB names (case-insensitive): return, tab, up, a, equal, etc. A bare modifier combo (e.g. "alt+shift") is a tap binding (fires on chord release; see [input.keyboard] options).
Actions:
exec <cmd>— launch an app (shows loading cursor until window appears, exits fullscreen)exec-terminal— launch the auto-detected terminal (see [keybindings] below; override with $TERMINAL)exec-launcher— launch the auto-detected app launcher (see [keybindings] below; override with $LAUNCHER)spawn <cmd>— run a command without loading cursor and exiting fullscreen (toggles, OSD, screenshots)close-window— close the focused windownudge-window <dir>— move focused window by nudge_step pxpan-viewport <dir>— pan camera by pan_step pxcenter-window— center viewport on focused window + reset zoomfocus-center— focus + center on the window under the pointer + reset zoomcenter-nearest <dir>— navigate to nearest window in directioncycle-windows forward— Alt-Tab style window cyclingcycle-windows backward— reverse cyclehome-toggle— toggle between current position and originzoom-in / zoom-out— step zoomzoom-reset— zoom to 1.0go-to <x> <y>— jump camera to canvas position (bookmarks, Y-up)zoom-to-fit— fit all windows in viewportzoom-to-fit-snapped— fit only the focused window's snap clustertoggle-fullscreen— toggle focused window fullscreenfit-window— toggle maximize: centers + resets zoom + fills viewport; restore only resizes backfit-window-snapped— fit-window for the focused window's whole snap clusterfill-window— grow in place to fill free space; edges outside the usable area or overlapping another window pull back to a gap; press again to restoretoggle-pin-to-screen— pin/unpin the focused window to the screen (ignores pan/zoom, floats above)reload-config— hot-reload config filetoggle-cursor-pan— toggle cursor edge-pan (see [navigation.edge_pan])quit— exit the compositorsend-to-output <dir>— move focused window to adjacent outputsend-cursor-to-output <dir>— move the cursor to adjacent outputswitch-layout <target>— cycle keyboard layout: next, prev, or a 0-based indexnone— unbind this key combo
Directions: up, down, left, right, up-left, up-right, down-left, down-right
| Binding | Action | Notes |
|---|---|---|
"mod+return" |
exec-terminal |
$TERMINAL, else first of: foot, alacritty, ptyxis, kitty, wezterm, gnome-terminal, konsole |
"mod+d" |
exec-launcher |
$LAUNCHER, else first of: fuzzel, wofi, rofi, bemenu-run, wmenu-run, tofi-drun, mew-run |
"mod+q" |
close-window |
|
"mod+e" |
toggle-cursor-pan |
toggle cursor edge-pan (see [navigation.edge_pan]) |
"mod+f" |
toggle-fullscreen |
|
"mod+m" |
fit-window |
|
"mod+shift+m" |
fit-window-snapped |
|
"mod+t" |
toggle-pin-to-screen |
|
"mod+c" |
center-window |
|
"mod+x" |
focus-center |
|
"mod+a" |
home-toggle |
|
"mod+up" |
center-nearest up |
|
"mod+down" |
center-nearest down |
|
"mod+left" |
center-nearest left |
|
"mod+right" |
center-nearest right |
|
"mod+shift+up" |
nudge-window up |
|
"mod+shift+down" |
nudge-window down |
|
"mod+shift+left" |
nudge-window left |
|
"mod+shift+right" |
nudge-window right |
|
"mod+ctrl+up" |
pan-viewport up |
|
"mod+ctrl+down" |
pan-viewport down |
|
"mod+ctrl+left" |
pan-viewport left |
|
"mod+ctrl+right" |
pan-viewport right |
|
"alt+tab" |
cycle-windows forward |
|
"alt+shift+tab" |
cycle-windows backward |
|
"mod+equal" |
zoom-in |
|
"mod+minus" |
zoom-out |
|
"mod+0" |
zoom-reset |
|
"mod+z" |
zoom-reset |
|
"mod+w" |
zoom-to-fit |
|
"mod+shift+w" |
zoom-to-fit-snapped |
|
"mod+1" |
go-to -1750 1750 |
top-left bookmark |
"mod+2" |
go-to 1750 1750 |
top-right bookmark |
"mod+3" |
go-to 1750 -1750 |
bottom-right bookmark |
"mod+4" |
go-to -1750 -1750 |
bottom-left bookmark |
"mod+alt+up" |
send-to-output up |
move window to output above |
"mod+alt+down" |
send-to-output down |
|
"mod+alt+left" |
send-to-output left |
|
"mod+alt+right" |
send-to-output right |
|
"mod+l" |
spawn swaylock -f -c 000000 -kl |
|
"mod+ctrl+shift+q" |
quit |
|
"XF86AudioRaiseVolume" |
spawn wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+ |
|
"XF86AudioLowerVolume" |
spawn wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%- |
|
"XF86AudioMute" |
spawn wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle |
|
"XF86MonBrightnessUp" |
spawn brightnessctl set +5% |
|
"XF86MonBrightnessDown" |
spawn brightnessctl set 5%- |
|
"XF86AudioPlay" |
spawn playerctl play-pause |
|
"XF86AudioPause" |
spawn playerctl play-pause |
|
"XF86AudioNext" |
spawn playerctl next |
|
"XF86AudioPrev" |
spawn playerctl previous |
|
"XF86AudioStop" |
spawn playerctl stop |
|
"Print" |
spawn grim - | wl-copy |
|
"shift+Print" |
spawn grim -g \"$(slurp -d)\" - | wl-copy |
Example: built-in window capture (isolated window + shadow) to clipboard
"ctrl+Print" = "spawn driftwm msg screenshot window -o - | wl-copy"Example: tap binding — bare modifier chord, fires on release with no key on top
"alt+shift" = "switch-layout next"Example: fill-window (unbound by default)
"mod+g" = "fill-window"Default: true
When true (default), dragging a window's edge or corner resizes it via the invisible resize border (SSD frame or CSD margin). Set false to make that border inert — resize only through explicit bindings (e.g. alt+right) or gestures.
Default: false
When true, resizing a window by dragging its edge (SSD or CSD border) propagates to every window connected to it via snap adjacency. Keybinding/gesture resize is unaffected — bind resize-window-snapped explicitly if you want cluster-aware resize there too.
Default: false
When true, maximize/unmaximize initiated via window decoration (CSD maximize button, SSD title-bar double-click, or xdg/foreign-toplevel set_maximized) propagates to every window connected via snap adjacency. Keybinding/gesture fit is unaffected — bind fit-window-snapped explicitly if you want cluster-aware fit there too.
Mouse bindings: "Modifier+...+Trigger" = "action" Context-aware: on-window, on-canvas, anywhere. Specific context checked first, then "anywhere" as fallback. Click-to-focus and SSD decoration clicks are always hardcoded. Triggers: left, right, middle (buttons), trackpad-scroll, wheel-scroll, wheel-up, wheel-down. The wheel-up/wheel-down triggers fire once per discrete wheel notch and can run any action, e.g. volume on mod+shift+scroll. Merges with defaults. Use "none" to unbind.
Mouse actions: move-window, move-snapped-windows, resize-window, resize-window-snapped, pan-viewport, zoom, center-nearest Any keyboard action also works for button triggers: exec, close-window, toggle-fullscreen, etc.
move-window / resize-window act on the focused window only. move-snapped-windows / resize-window-snapped also translate every window connected to the focused one via snap adjacency
| Binding | Action | Notes |
|---|---|---|
"alt+left" |
move-window |
|
"alt+shift+left" |
move-snapped-windows |
|
"alt+right" |
resize-window |
|
"alt+shift+right" |
resize-window-snapped |
|
"alt+middle" |
fit-window |
|
"alt+shift+middle" |
fit-window-snapped |
|
"mod+middle" |
toggle-fullscreen |
| Binding | Action | Notes |
|---|---|---|
"left" |
pan-viewport |
unmodified left-click on empty canvas → pan |
"trackpad-scroll" |
pan-viewport |
trackpad scroll on empty canvas → pan |
"wheel-scroll" |
zoom |
mouse wheel on empty canvas → zoom |
| Binding | Action | Notes |
|---|---|---|
"mod+left" |
pan-viewport |
|
"mod+ctrl+left" |
center-nearest |
direction from drag delta |
"mod+trackpad-scroll" |
pan-viewport |
|
"mod+wheel-scroll" |
zoom |
Default: 12.0
Gesture thresholds — tune for your touchpad size.
px cumulative distance before directional swipe fires
Default: 0.85
scale below which pinch-in fires (1.0 = no pinch)
Default: 1.15
scale above which pinch-out fires (1.0 = no pinch)
Gesture bindings: "Modifier+N-finger-" = "action" Context-aware: on-window, on-canvas, anywhere. Unbound gestures are forwarded to the focused app. "none" unbinds (prevents anywhere fallback, still forwards).
Gesture types:
N-finger-swipe— continuous OR threshold (action determines behavior)N-finger-swipe-up/down/left/right— threshold only, checked before swipe fallback3-finger-doubletap-swipe— continuous OR threshold (3-finger tap then swipe)N-finger-pinch— continuous only (use pinch-in/out for discrete)N-finger-pinch-in/out— threshold onlyN-finger-hold— threshold only (fires on release)
Continuous actions: pan-viewport, zoom, move-window, move-snapped-windows, resize-window, resize-window-snapped Threshold actions: center-nearest, center-window, home-toggle, zoom-to-fit, zoom-to-fit-snapped, fit-window, fit-window-snapped, fill-window, exec , etc.
| Binding | Action | Notes |
|---|---|---|
"alt+3-finger-swipe" |
resize-window |
|
"alt+shift+3-finger-swipe" |
resize-window-snapped |
|
"3-finger-doubletap-swipe" |
move-window |
|
"alt+2-finger-pinch-in" |
fit-window |
|
"alt+2-finger-pinch-out" |
fit-window |
|
"alt+shift+2-finger-pinch-in" |
fit-window-snapped |
|
"alt+shift+2-finger-pinch-out" |
fit-window-snapped |
|
"alt+3-finger-pinch-in" |
toggle-fullscreen |
|
"alt+3-finger-pinch-out" |
toggle-fullscreen |
| Binding | Action | Notes |
|---|---|---|
"2-finger-pinch" |
zoom |
| Binding | Action | Notes |
|---|---|---|
"3-finger-swipe" |
pan-viewport |
continuous (per-frame dx/dy) |
"4-finger-swipe" |
center-nearest |
threshold (accumulate, detect direction, fire once) |
"mod+3-finger-swipe" |
center-nearest |
mod makes 3-finger swipe navigate too |
Example: per-direction overrides (threshold only, checked before swipe fallback)
"4-finger-swipe-up" = "exec brightnessctl set +5%"
"4-finger-swipe-down" = "exec brightnessctl set 5%-"| Binding | Action | Notes |
|---|---|---|
"mod+2-finger-pinch" |
zoom |
mod overrides app forwarding |
"3-finger-pinch" |
zoom |
continuous |
"4-finger-pinch-in" |
zoom-to-fit |
threshold |
"mod+4-finger-pinch-in" |
zoom-to-fit-snapped |
|
"4-finger-pinch-out" |
home-toggle |
threshold |
"mod+3-finger-pinch-in" |
zoom-to-fit |
|
"mod+3-finger-pinch-out" |
home-toggle |
|
"4-finger-hold" |
center-window |
fires on release |
"mod+3-finger-hold" |
center-window |
Touchscreen gesture BINDINGS. Distinct from [input.touch], which holds the touch device settings (enable, map_to_output) — put those there, not here (same split as [gestures] bindings vs [input.trackpad] device settings).
Bindings: "N-finger-" = "action" (touch has no keyboard modifiers) Context-aware: on-window, on-canvas, anywhere. Unbound gestures are forwarded to the focused app. "none" removes a binding in its context (under [touch.anywhere] it also drops the anywhere fallback). A fully unbound gesture forwards to the app.
Touch gesture types (1–5 fingers):
N-finger-swipe— continuous OR threshold (action determines behavior)N-finger-swipe-up/down/left/right— threshold only, checked before swipe fallbackN-finger-pinch— continuous only (use pinch-in/out for discrete)N-finger-pinch-in/out— threshold onlyN-finger-tap— threshold only (quick touch, no movement)N-finger-doubletap— threshold only (two quick taps)N-finger-doubletap-swipe— continuous only (tap then drag)N-finger-hold-swipe— continuous only (dwell then drag)
Continuous actions: pan-viewport (swipe), zoom (pinch), and the window grabs — move-window / move-snapped-windows / resize-window / resize-window-snapped (doubletap-swipe / hold-swipe). A held move-window also extends to the snap-cluster. Threshold actions: center-nearest, center-window, home-toggle, zoom-to-fit, fit-window, fill-window, exec , etc.
Note: within one physical gesture, a continuous translation (pan) and a threshold pinch on the same finger count don't combine — either bind both axes continuous (pan + zoom) or drive discrete actions from a threshold swipe/pinch.
1–2 fingers starting on a window forward to the app, and the 3+ finger pan/zoom/navigate gestures are bound "anywhere" so they apply over windows too. The window-targeted gestures live here: they act on the window the fingers land on (a gesture starting on empty canvas just pans).
| Binding | Action | Notes |
|---|---|---|
"3-finger-doubletap" |
fit-window |
|
"3-finger-doubletap-swipe" |
move-window |
continuous (hold to move the cluster) |
"3-finger-hold-swipe" |
resize-window |
continuous (dwell then drag) |
| Binding | Action | Notes |
|---|---|---|
"1-finger-swipe" |
pan-viewport |
|
"2-finger-swipe" |
pan-viewport |
|
"2-finger-pinch" |
zoom |
5-finger navigation mirrors 4-finger by default, so a stray 5th contact doesn't abort a navigation gesture.
| Binding | Action | Notes |
|---|---|---|
"3-finger-swipe" |
pan-viewport |
continuous |
"3-finger-pinch" |
zoom |
continuous |
"4-finger-swipe" |
center-nearest |
threshold (direction from drag) |
"4-finger-pinch-in" |
zoom-to-fit |
threshold |
"4-finger-pinch-out" |
home-toggle |
threshold |
"3-finger-tap" |
center-window |
Default: true
X11 support via xwayland-satellite. driftwm spawns satellite eagerly at startup, exports DISPLAY=:N, and X11 apps connect transparently. If the binary isn't found, X11 support is disabled with a warning; everything else still runs.
default: true
Default: "xwayland-satellite"
path to xwayland-satellite binary ($PATH lookup works)
Default: false
Hardware stability quirks. All default to false (opt-in). Enable these if you experience flickering, crashes, or rendering issues. Particularly useful on NVIDIA GPUs with proprietary drivers. Note: These flags must be set before launching driftwm. Changing them requires a restart. For additional NVIDIA-specific settings, set these environment variables in your session wrapper script or shell profile before starting driftwm:
export SMITHAY_USE_LEGACY=1 # Use legacy DRM API instead of atomic modesetting
export __GL_GSYNC_ALLOWED=0
export __GL_VRR_ALLOWED=0
export __GL_MaxFramesAllowed=1
export NVD_BACKEND=direct
Force GPU-fence wait before every page flip (already done automatically when smithay reports needs_sync — typical case on NVIDIA. Set true only if you still see flicker after defaults.)
Default: false
Force EGL composition (disable direct scanout)
Default: false
Composite the cursor into the frame instead of using the KMS cursor plane, while keeping direct scanout for fullscreen apps. Fixes a stuttering / tearing hardware cursor on discrete NVIDIA GPUs.
Default: 0
Cap FPS delivered to continuous screen-capture clients (recorders / casts). 0 = unlimited. Each captured frame forces a full-scene re-composite that competes with a fullscreen app behind it; a cap (e.g. 30 or 60) trims that overhead while gaming + streaming. One-shot screenshots (grim) are never throttled.
Default: "#ffffff"
outline color for other monitors' viewports
Default: 1
pixels (0 to disable)
Default: 0.5
0.0–1.0
Per-output configuration. Each [[outputs]] entry matches by connector name. Find connector names with wlr-randr or check driftwm logs at startup. Outputs without a matching entry default to scale 1.0. Winit backend ignores [[outputs]] entries.
name = "*" is a wildcard entry: it applies to any connected output that has no exact-name entry (exact entries always win). A fixed position makes no sense on the wildcard — it's ignored (falls back to "auto").
mode accepts "preferred", "max", "WxH", or "WxH@Hz". "preferred" (the default, and the safe choice) uses the monitor's advertised preferred mode. "max" picks the highest resolution, then highest refresh. A bare "WxH" only selects a mode the monitor already advertises — if none matches, it keeps the preferred mode (logged as a warning, not an error). "WxH@Hz" forces that exact mode, synthesizing a CVT modeline when the monitor doesn't advertise it (intended for CRTs or forcing non-standard modes; may be rejected by some panels).
Example:
[[outputs]]
name = "eDP-1" # connector name, or "*" for a fallback entry (required)
scale = 1.5 # fractional scale (default: 1.0)
transform = "normal" # normal, 90, 180, 270, flipped, flipped-90, flipped-180, flipped-270
position = "auto" # "auto" (left-to-right) or [x, y] in layout coords
mode = "preferred" # "preferred", "max", "1920x1080", or "2560x1440@144"
[[outputs]]
name = "HDMI-A-1"
scale = 1.0
mode = "1920x1080@60"Window rules: match windows and apply per-window overrides. ALL matching rules are merged in config order (later rules override earlier ones for scalar fields; boolean flags are sticky-on). This lets you compose rules — e.g. one rule sets blur=true, a later one adds opacity=0.85.
Match criteria (at least one required; all specified must match):
app_id— Wayland app_id. X11 apps proxied via xwayland-satellite arrive with app_id set from WM_CLASS instance (typically lowercase).title— window title
Pattern syntax (applies to all match fields):
Plain string— exact match: "kitty"Glob— * wildcard: "steam_app_*"Regex— wrap in /…/: "/^steam_app_\d+$/"
To find a window's identifiers, run while the window is open:
`driftwm msg state`
Effect fields:
position— [x, y] coordinates (window center, Y-up). Canvas coords, or output-relative (origin = output center) when pinned_to_screen.size— [width, height] initial window dimensions (one-shot; user/app can resize after)fullscreen— true: force this window to open in fullscreen modewidget— true: pinned (immovable), below normal windows, excluded from navigation and alt-tab (default: false)pinned_to_screen— true: lock the window to the output's screen space — ignores pan/zoom, floats above normal windows (PiP, toolbars).positionbecomes output-relative; movable unless widget = true. Toggle live withtoggle-pin-to-screen(Mod+T). (default: false)decoration— overrides [decorations] default_mode for matched windows. Omit to inherit default_mode. Values:- "client": CSD — client's own titlebar
- "server": SSD — driftwm's titlebar
- "minimal": SSD — no titlebar, just shadow + corners + border (this is the mode for chrome-on-borderless widgets; border_width / corner_radius / shadow rules apply)
- "none": bare client surface — compositor adds zero chrome, and per-window border_width / corner_radius / shadow rules are ignored. Use "minimal" if you want chrome without a titlebar.
blur— true: blur background behind this window (default: false)opacity— 0.0–1.0: window transparency (default: 1.0, fully opaque)border_width— per-window border width override (px). Set to 0 to disable border on a window even when [decorations] border_width > 0. Ignored for decoration = "none".border_color— per-window unfocused border color hex (e.g. "#5c5c5c").border_color_focused— per-window focused border color hex.corner_radius— per-window corner radius override (px). Affects content clip, border shape, and shadow. Ignored for decoration = "none".shadow— per-window shadow toggle. Overrides [decorations] shadow. Ignored for decoration = "none".output— output name (e.g. "DP-1") this window fullscreens onto. Takes precedence over the output the client itself requests. Omit to honor the client's request, then the active output. Find names underoutputs.*indriftwm msg state. (default: unset)pass_keys— controls which compositor keybindings are forwarded to the app:- pass_keys = true — forward ALL keys (game-friendly)
- pass_keys = ["mod+q", "ctrl+q"] — forward ONLY these combos; all other compositor shortcuts stay active
- pass_keys = false / omit — compositor handles everything (default)
- VT switching (Ctrl+Alt+F1–F12) — always stays in the compositor
Layer-shell surfaces (panels, notifications, bars like waybar): matched by their namespace against app_id. decoration is ignored — layers have no decoration mode. Chrome (border_width, corner_radius, shadow) is field-by-field opt-in on the rule and does NOT inherit from [decorations]. Without explicit values on the rule, a layer surface has no border, no shadow, and no corner clip.
layer_order— stacking among layer surfaces sharing the same wlr-layer (higher = on top; ties stack by map order, newest on top). The protocol has no z-index within a layer, so two overlay clients (e.g. an on-screen keyboard and a touch visualizer) otherwise stack by launch order. Also orders canvas-positioned layers among themselves. Ignored for regular windows.
Example: keep the on-screen keyboard above other overlay surfaces
[[window_rules]]
app_id = "wvkbd"
layer_order = 10Example: Desktop widget (pinned clock/calendar)
[[window_rules]]
app_id = "my-widget"
position = [0, 0]
widget = true
decoration = "none"Example: Picture-in-Picture, pinned to the screen, stays put while you pan/zoom
[[window_rules]]
title = "Picture-in-Picture"
pinned_to_screen = true
position = [0, -300] # output-relative: 300px below center; movable, drop to center on the outputExample: Blurred transparent terminal
[[window_rules]]
app_id = "kitty"
opacity = 0.85
blur = trueExample: Game, pass ALL keys to the app (mod+q, ctrl+q etc. reach the game)
[[window_rules]]
app_id = "steam_app_*"
pass_keys = trueExample: Game, only let ctrl+q through, keep everything else (mod+q still closes)
[[window_rules]]
app_id = "factorio"
pass_keys = ["ctrl+q"]Example: Regex match, any Steam game app
[[window_rules]]
app_id = "/^steam_app_\\d+$/"
pass_keys = trueExample: Always open this game fullscreen on a specific monitor
[[window_rules]]
app_id = "steam_app_*"
output = "DP-1"Example: Compose rules, blur from first rule, opacity from second (both apply)
[[window_rules]]
app_id = "Alacritty"
blur = true
[[window_rules]]
app_id = "Alacritty"
opacity = 0.9Example: Iced/libcosmic utility windows that share the main window's app_id
[[window_rules]]
title = "winit window"
widget = true