config.md RawHistoryFolder

Configuration

NeferWL reads ~/.config/neferwl/config (or $XDG_CONFIG_HOME/neferwl/config). Changes apply when you save, except startup and xwayland, which apply at the next start.

neferwl --config <path>            # use another file
neferwl validate-config [path]     # check a file

Missing keys use defaults. Empty keyboard values use the system setting. No startup commands or named workspaces are configured by default. The log lists changed keys after a reload.

Format

  • One key = value per line. # starts a comment.
  • Lists are comma-separated; booleans are on / off.
  • An invalid line logs a warning and keeps the default. If a key appears twice, the last one wins.

See examples/config for every key with its default.

Keys

Key Default Meaning
keyboard.layout, .variant, .options empty xkb names, e.g. fr, caps:escape
keyboard.repeat-rate / .repeat-delay 25 / 600 Repeats per second / delay in ms
keyboard.cmd super Modifier meant by cmd in binds: super, alt or ctrl
terminal $TERMINAL or foot Terminal command
terminal.auto-open first Open a terminal on the first workspace (first), on every empty one (all), or never (off)
startup none Commands run at session start, separated by ;
xwayland xwayland-satellite X11 support; off disables it
background #111111 Background color
border.radius 0 Corner radius in logical pixels, 0 to 64; floats always, tiles only with gaps
border.width 2 Border width in pixels; 0 hides borders
border.active / .inactive #808080 / #111111 Border colors of the focused / other windows
floating.dim 0.3 Darkening behind floating windows and the overview, 0 to 1
layout.gaps 0 Space between windows
layout.max-columns 2 Columns that share the screen
layout.overflow cascade What happens past max-columns: cascade into bands below, scroll right, or fixed spiral split
layout.<output>.max-columns, .overflow layout values Per-output layout
stash.width 80 Width of the selected stashed window, in % of the screen (10–90)
stash.gap 2 Gap around the selected stashed window, in % (0–10)
stash.dim 0.5 Darkening of the stash neighbors, 0 to 1
stash.capture on A window opened while the stash shows joins the stash
stash.spawn-terminal on toggle-stash-visible on an empty stash opens terminal in it
touchpad.natural-scroll off Content follows the fingers
touchpad.tap on Tap to click
touchpad.accel-speed 0 Pointer speed, -1 to 1
touchpad.accel-profile adaptive adaptive or flat
touchpad.left-handed off Swap left and right buttons
touchpad.scroll-factor 1 Scroll speed multiplier, up to 10
mouse.natural-scroll, .accel-speed, .accel-profile, .left-handed as touchpad Same, for mice, trackballs and trackpoints
cursor.hide-after 5s Hide the cursor after this long without motion, or off
animations off Experimental. Animate layout changes and make swipes follow the fingers
animations.speed normal slow, normal, fast, or a factor from 0.1 to 10
focus.follow-move off Follow a column moved to another workspace
focus.position-hint on Show workspace/band dots near the output bottom for 600ms after a position change, then fade for 150ms (no fade with animations off); hidden in overview, fullscreen and session lock; never captured
focus.flash on Briefly draw an inner border, inside the window border, on the window, column or output an action changed
focus.flash-width 2 Flash border width in logical pixels, 1 to 8
focus.flash-opacity 0.6 Starting opacity of the flash, 0.1 to 1
focus.flash-duration 200ms Fade duration, 50ms to 1s; not scaled by animations.speed
focus.flash-color border.active Flash color #rrggbb; empty uses border.active
hint.enable off Turn on the experimental hint mode (restart required)
hint.detector nefer-hints The hint mode detector binary: a path, or a name looked up in PATH (restart required)
hint.model $XDG_DATA_HOME/nefer-hints/models Directory of the detector's model files; ~/.local/share/nefer-hints/models when XDG_DATA_HOME is unset; a leading ~/ is expanded (restart required)
hint.threshold model default Optional minimum detection score override, 0.01 to 0.99; unset or empty uses the model’s own threshold (model.json), else the runner default. Lower finds more targets and more false ones (restart required)
hint.capture off Show hint labels in captures requested by Wayland clients (screenshots, screencasts); the compositor's own hint scans never see them; applies on reload
hint.size auto Label text size in logical pixels (6 to 72), or auto for screen pixel density
hint.background empty Label background (#rrggbb); empty uses border.active
hint.foreground empty Label text (#rrggbb); empty chooses a contrasting colour
workspace.<name>.monitor focused output Home monitor of a named workspace
workspace.<name>.size inherit Smaller viewport, e.g. 1920x1080
workspace.<name>.max-columns, .overflow output values Per-workspace layout
workspace.<name>.column.<N> none Column slot: <cells>, <command>
rule.<name>.* none Window rules
output.<name> preferred WxH, WxH@Hz, preferred or off
output.<name>.scale 1 1 to 4, e.g. 1.5 or 4/3
output.<name>.transform normal 90, 180, 270, flipped, flipped-90, …
output.<name>.primary off Focused at startup
output.<name>.right-of, .left-of, .above, .below, .offset automatic Output placement
output.<name>.hdr off HDR on capable displays
output.<name>.sdr-brightness 203 Brightness of SDR content in HDR, in nits (80–1000)
render.direct-scanout on Send fullscreen windows straight to the display
render.tearing on Allow tearing for fullscreen games
render.vrr on Variable refresh rate for fullscreen windows
render.vrr-flip-gap 1ms See Performance
performance.realtime on Real-time scheduling
log.level / log.debug info / empty Log level / debug categories or all
bind.<keys> see Default binds Binds

With layout.gaps = 0, borders are tmux-style lines between tiles. With gaps, every tile gets a full border, even alone. border.radius rounds floating windows always and tiles with gaps; fullscreen windows, popups and layers stay square.

<output> is a connector such as DP-2, or a monitor key (make model serial).

The position hint follows actual workspace/band changes and monitor focus, regardless of input method. It lists all ext-workspace entries in monitor order: numbered workspaces (including the empty spare), then configured hidden named workspaces, even when empty. Cascade workspaces use top-aligned band columns; scroll/fixed workspaces use one vertical workspace column. Large lists show at most 16 entries with the active entry kept visible; band rows are capped at 16.

The focus flash marks the window, column or output an action changes with a brief inner border. When a flash-triggering action crosses screens, it marks the whole destination output, even if that output has no windows. Same-screen workspace switches and mouse focus do not trigger a flash. It runs when animations = on.

Automatic terminal

The command comes from terminal, then $TERMINAL, then foot.

terminal.auto-open Behaviour
first Open once on the initial numbered workspace
all Open on each visible empty workspace without predefined slots
off Do not open automatically

--no-terminal disables automatic opening for one run; spawn-terminal still works. Launched programs inherit $SHELL, $EDITOR and $VISUAL.

Per-output defaults

Per-output defaults (replace <name> with a connector such as DP-1):

output.<name> = preferred
output.<name>.scale = 1
output.<name>.transform = normal
output.<name>.primary = off
output.<name>.offset = 0
output.<name>.hdr = off
output.<name>.sdr-brightness = 203

Outputs are arranged automatically unless .right-of, .left-of, .above or .below is set. Per-output layouts inherit layout.*; named workspaces inherit their output's layout and size, use the focused output unless .monitor is set, and have no predefined columns. Media keys have no default bindings.

Window rules

Rules place an app's windows when they first open.

Key Meaning
rule.<name>.app-id Required. Regular expression matching the whole app ID
rule.<name>.floating on to float, off to tile
rule.<name>.workspace Workspace number or name
rule.<name>.monitor Output
rule.<name>.width Column width in cells, 1 to 16
rule.games.app-id = steam_app_.*
rule.games.workspace = 3
rule.chat.app-id = discord|org\.telegram\..*
rule.chat.workspace = chat
rule.chat.width = 1
rule.calc.app-id = org\.gnome\.Calculator
rule.calc.floating = on
  • Every matching rule applies, in file order; later rules win.
  • A rule never moves your focus or view.
  • To find an app ID: neferwl state | jq '.windows[].app_id'.

Output placement

By default, outputs sit left to right in config order. Place one against another, and use offset (logical pixels) to align the edges:

output.DP-2.right-of = DP-1
output.DP-2.offset = 360    # DP-2 starts 360 px lower than DP-1
┌────────────┐
│            │┌─────────┐
│    DP-1    ││  DP-2   │
│ 2560x1440  ││1920x1080│
│            │└─────────┘
└────────────┘

offset moves down for right-of/left-of, and right for above/below. kanshi, wdisplays and wlr-randr override these settings until the next reload.

Workspace size

workspace.<name>.size gives a named workspace a smaller centered viewport, for example to present at 1080p on a 4K screen:

workspace.presentation.size = 1920x1080
bind.cmd+p = workspace presentation

Touchpad

Gesture Action
Three fingers left/right Scroll columns (switch workspace in cascade)
Three fingers up/down Switch workspace (scroll bands in cascade)
Four fingers up Open the overview, or hide the stash
Four fingers down Close the overview, or show/hide the stash

A swipe moves one step at most. Other gestures (pinch, hold, two-finger scroll) go to the app under the pointer.

HDR

output.<name>.hdr = on sends HDR10 to displays that support it. If the display, driver or GPU cannot, NeferWL falls back to SDR and logs why. NVIDIA is not supported yet.

  • HDR-aware apps (games, browsers, video players) can show HDR content. Some need HDR turned on in their own settings. X11 apps stay SDR.
  • The desktop stays SDR, shown at sdr-brightness nits.
  • Fullscreen HDR windows go straight to the display.
  • Screenshots and captures are SDR (tone-mapped).
  • If NeferWL crashes, the next compositor may show distorted colours until it sets its own colour pipeline.

Binds

bind.<keys> = <action>. Modifiers: cmd (see keyboard.cmd), shift, ctrl, alt, super.

  • Keys are what your layout prints: cmd+é on AZERTY. Write =, # and space by name: cmd+equal, cmd+numbersign, cmd+space.
  • code:N is a physical key, the same on every layout: code:2 to code:10 are the digits 1 to 9.
  • Other keys use their xkb name (wev shows it).
  • none removes a default bind.
  • spawn <command> runs a program without a shell. See Running commands.
  • Holding a move bind repeats it: focus, move, resize and workspace steps, first after 250 ms, then 8 times a second. Toggles, spawns and closes run once. The overview's arrows and h/j/k/l repeat the same way.
bind.XF86AudioRaiseVolume = spawn wpctl set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ 5%+
bind.XF86AudioLowerVolume = spawn wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
bind.XF86AudioMute = spawn wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
bind.XF86MonBrightnessUp = spawn brightnessctl set +5%
bind.XF86MonBrightnessDown = spawn brightnessctl set 5%-

Actions

Action Effect
spawn <command> Run a program
spawn-terminal Run terminal
stash <command> Run a program and open its window in the stash
close-window Close the focused window
toggle-fullscreen Fullscreen the focused window. Nothing shows above it except its own dialogs
toggle-floating Make the focused window float, or put it back in its column
toggle-window-stash Move the focused window to the stash, or back to its column
toggle-stash-visible Show or hide the stash
toggle-overview Open or close the overview
hint Label the clickable elements on the focused screen, then click the one you type (hint mode)
switch-column-next / -prev Switch to the previously used column. Hold cmd and press again to go further back
maximize-column Toggle full width for the focused column
cycle-column-width Grow the focused column by one cell, then back to equal widths
set-column-width <+N|-N> Widen or narrow the focused column by N cells
set-window-height <+N%|-N%> Change the focused window's share of its column
focus-column-left/right Focus the neighbor column, then the neighbor monitor
focus-window-up/down Focus the neighbor window in the column, then the neighbor workspace (band in cascade)
move-column-left/right Move the focused column, then to the neighbor monitor
move-window-up/down Move the focused window in its column
consume-or-expel-window-left/right Join the neighbor column, or leave the current one
focus-workspace <N> / -prev/-next Show a workspace
workspace <name> Show or hide a named workspace
move-column-to-workspace <N> / -prev/-next Move the focused column to a workspace
move-window-to-workspace <N> / -prev/-next Move the focused window to a workspace
move-workspace-prev/next Swap the workspace with its neighbor
focus-monitor-left/right/up/down Focus the neighbor monitor
move-workspace-to-monitor-left/right/up/down Move the workspace to the neighbor monitor
scale-up / scale-down Zoom the display; the scale is saved to the config
quit Exit NeferWL

Default binds

Keys Action
cmd+return spawn-terminal
ctrl+cmd+space spawn fuzzel
cmd+q close-window
cmd+left/right, cmd+h/l focus-column-left/right
cmd+up/down, cmd+k/j focus-window-up/down
cmd+shift+left/right, cmd+shift+h/l move-column-left/right
cmd+shift+up/down, cmd+shift+k/j move-window-up/down
cmd+bracketleft/bracketright consume-or-expel-window-left/right
cmd+alt+left/right, cmd+alt+h/l set-column-width -1/+1
cmd+alt+up/down, cmd+alt+k/j set-window-height -10%/+10%
cmd+r cycle-column-width
cmd+f maximize-column
cmd+shift+f toggle-fullscreen
cmd+shift+space toggle-floating
cmd+s toggle-stash-visible
cmd+shift+s toggle-window-stash
cmd+o toggle-overview
cmd+tab / cmd+shift+tab switch-column-next/prev
cmd+1…9 (code:2…10) focus-workspace 1…9
cmd+shift+1…9 move-column-to-workspace 1…9
cmd+pageup/pagedown focus-workspace-prev/next
cmd+shift+pageup/pagedown move-column-to-workspace-prev/next
cmd+ctrl+arrows, cmd+ctrl+h/j/k/l focus-monitor-*
cmd+ctrl+shift+left/right, cmd+ctrl+shift+h/l move-workspace-to-monitor-left/right
cmd+ctrl+shift+up/down, cmd+ctrl+shift+k/j move-workspace-prev/next
cmd+code:12 / cmd+code:13 (- / = on US) scale-down / scale-up
ctrl+alt+backspace quit

With keyboard.cmd = alt or ctrl, the defaults that would combine cmd with that same modifier are dropped. Bind those actions to other keys.

Layout

Cascade

The default layout. Columns sit side by side in bands of up to max-columns, stacked one below the other. Each band fills the screen height and keeps its columns until you move them.

  • Left/right focus moves within a band, then to the neighbor monitor, then to the neighbor workspace.
  • move-column-left/right stays in the band; at its edge the column goes to the neighbor monitor, if any.
  • Up/down moves through the windows of a column, then to the band above or below.
  • move-window-up/down past the column's top or bottom sends the whole column to the band above or below. It joins that band when it has room, else it gets a new band between the two.
  • A new window joins the focused band when it has room, else it gets a new band right below.
  • cycle-column-width in a full band makes room: the rightmost column goes to the band below (from the last column, the leftmost goes to the band above). The end of the cycle brings it back.
  • maximize-column gives the column a band of its own: the columns on its left go to the band above, the ones on its right to the band below. Maximizing again forms the band back.
  • A band left empty goes away. A smaller max-columns moves the extra columns of a band to a new band below it.

Column widths

A cell is 1/max-columns of the screen width. A column is 1 to 16 cells wide. Columns without a set width share the space equally.

Stash

Each workspace has a stash: a strip of windows set aside with toggle-window-stash. The selected one shows centered over your columns, with its neighbors dimmed on the sides. focus-column-left/right or a three-finger swipe browse it. toggle-stash-visible or a four-finger swipe hides it.

stash <command> runs a program and opens its window straight in the stash of the workspace you were on, selected and focused, even if you switch workspace before it appears. With stash.spawn-terminal, toggle-stash-visible or the four-finger swipe down on an empty stash does the same with terminal.

bind.cmd+shift+f = stash firefox

The window must appear within 5 seconds, otherwise it opens normally. Apps that hand a launch to an already running instance (single-instance browsers, footclient) open their window normally.

Free floating windows

toggle-floating turns the focused window into a floating window that stays where you put it. On a floating window, move binds move it by 50 pixels and resize binds resize it.

Moving windows with the pointer

  • cmd + left drag moves a window. Drop it on the top or bottom of another window to stack it there, or in the middle to swap.
  • cmd + right drag resizes a floating window.
  • cmd + right drag on the line between two columns or two stacked windows resizes them.
  • cmd + double right-click on that line resets the lane's column widths or the column's row heights. Column widths reset when a column enters or leaves the lane.
  • Escape cancels the drag and restores the previous sizes.

Overview

toggle-overview (or a four-finger swipe up) shows previews of your workspaces. Arrows, h/j/k/l or three-finger swipes move the selection; Return or a click opens it; Escape goes back. close-window and the move-to-workspace binds work on the selected preview.

A workspace's stash shows as a pile of cards. Selecting the pile unfolds it into a carousel of every stashed window: a row under the workspace with layout.overflow = cascade, a column on its left otherwise. The selected card stays in the middle, the earlier cards before it and the later ones after it, wrapping round; a wider gap separates the newest card from the oldest. Moves along the carousel pick a card. With fewer than three cards, or on a workspace holding only stashed windows, it does not wrap round. Leaving it folds it again. Moving to another workspace selects its windows rather than a carousel that wraps round, even when its stash has the focus, so browsing workspaces never stops on one.

With layout.overflow = cascade, workspaces sit side by side, each band of columns is a lane, and the stash sits under the lanes. Down (or fingers down on the touchpad, with natural scroll off) moves to the lane below; past the last lane it unfolds the stash. Left/right walk the cards; where the carousel does not wrap round, they go to the neighbor workspace past either end. Up leaves the stash for the last lane, at the column you came from.

Otherwise, left from the first column unfolds the stash. Up/down walk the cards, the focus-workspace binds change workspace, and right goes back to the columns.

Hint mode

Hint mode is experimental and off by default: set hint.enable = on and restart NeferWL. While it is off, nothing of hint mode runs or uses memory, and the hint bind logs a notice once and does nothing. A reload does not change it.

hint labels the clickable elements (buttons, links, icons, fields) on the focused output. Type a label to click its target; BackSpace erases a letter, Escape cancels. No bind is set by default; for example bind.cmd+g = hint.

Labels use the border.active colour with a contrasting text colour, and their size follows the screen's pixel density. Each label sits just outside the top-left corner of its target. The click is a plain left click.

Labels are not part of screenshots and screencasts by default. With hint.capture = on they appear in the captures that Wayland clients request (including Exclude captures); the detector never sees them. The capture indicator and the position hint stay out of every capture. A reload applies hint.capture to the next frame.

Where targets come from

Targets come from nefer-hints, a separate program that looks at the pixels of the whole output with a neural network and returns a box for each clickable element. It works the same for every app, toolkit and terminal. It sees what is drawn: it does not know what an element does, and it can miss or invent boxes (hint.threshold trades one for the other). Boxes under 4×4 logical pixels are dropped.

NeferWL starts nefer-hints at the first hint scan (loading takes about half a second), keeps it running, and stops it on exit. If it dies, fails or takes more than 2 seconds on a scan, that scan is cancelled and a new process starts at the next one. Its device (GPU or CPU) is logged at startup, at info level.

Requirements

  • The nefer-hints binary, found in PATH or named by hint.detector.
  • Its model files, in hint.model.
  • A Vulkan GPU for speed; without one the detector falls back to the CPU, which is slower.

Debugging hint mode

With hint.enable = on and NEFERWL_HINT_DEBUG=1 set when NeferWL starts (for example NEFERWL_HINT_DEBUG=1 make tty, or the variable in the launch environment), every successful scan writes three files to $XDG_STATE_HOME/neferwl/hints/ (~/.local/state/neferwl/hints/ when unset): <time>-<output>.png, the frame as scanned; <time>-<output>.annotated.png, the same with the detector boxes outlined; and <time>-<output>.json, the targets (rectangle and score) and the capture, detection and total timings. NeferWL keeps the 50 most recent scans and logs a warning at startup. The detector's own messages are logged at debug level (log.debug = hints). The files contain the screen in clear: delete them after use.

source
1# Configuration
2
3NeferWL reads `~/.config/neferwl/config` (or `$XDG_CONFIG_HOME/neferwl/config`). Changes apply when you save, except `startup` and `xwayland`, which apply at the next start.
4
5```sh
6neferwl --config <path>            # use another file
7neferwl validate-config [path]     # check a file
8```
9
10Missing keys use defaults. Empty keyboard values use the system setting. No startup commands or named workspaces are configured by default. The log lists changed keys after a reload.
11
12## Format
13
14- One `key = value` per line. `#` starts a comment.
15- Lists are comma-separated; booleans are `on` / `off`.
16- An invalid line logs a warning and keeps the default. If a key appears twice, the last one wins.
17
18See [examples/config](../examples/config) for every key with its default.
19
20## Keys
21
22| Key | Default | Meaning |
23|---|---|---|
24| `keyboard.layout`, `.variant`, `.options` | empty | xkb names, e.g. `fr`, `caps:escape` |
25| `keyboard.repeat-rate` / `.repeat-delay` | `25` / `600` | Repeats per second / delay in ms |
26| `keyboard.cmd` | `super` | Modifier meant by `cmd` in binds: `super`, `alt` or `ctrl` |
27| `terminal` | `$TERMINAL` or `foot` | Terminal command |
28| `terminal.auto-open` | `first` | Open a terminal on the first workspace (`first`), on every empty one (`all`), or never (`off`) |
29| `startup` | none | Commands run at session start, separated by `;` |
30| `xwayland` | `xwayland-satellite` | X11 support; `off` disables it |
31| `background` | `#111111` | Background color |
32| `border.radius` | `0` | Corner radius in logical pixels, `0` to `64`; floats always, tiles only with gaps |
33| `border.width` | `2` | Border width in pixels; `0` hides borders |
34| `border.active` / `.inactive` | `#808080` / `#111111` | Border colors of the focused / other windows |
35| `floating.dim` | `0.3` | Darkening behind floating windows and the overview, 0 to 1 |
36| `layout.gaps` | `0` | Space between windows |
37| `layout.max-columns` | `2` | Columns that share the screen |
38| `layout.overflow` | `cascade` | What happens past `max-columns`: [`cascade`](#cascade) into bands below, `scroll` right, or `fixed` spiral split |
39| `layout.<output>.max-columns`, `.overflow` | layout values | Per-output layout |
40| `stash.width` | `80` | Width of the selected [stashed](#stash) window, in % of the screen (10–90) |
41| `stash.gap` | `2` | Gap around the selected stashed window, in % (0–10) |
42| `stash.dim` | `0.5` | Darkening of the stash neighbors, 0 to 1 |
43| `stash.capture` | `on` | A window opened while the stash shows joins the stash |
44| `stash.spawn-terminal` | `on` | `toggle-stash-visible` on an empty stash opens `terminal` in it |
45| `touchpad.natural-scroll` | `off` | Content follows the fingers |
46| `touchpad.tap` | `on` | Tap to click |
47| `touchpad.accel-speed` | `0` | Pointer speed, `-1` to `1` |
48| `touchpad.accel-profile` | `adaptive` | `adaptive` or `flat` |
49| `touchpad.left-handed` | `off` | Swap left and right buttons |
50| `touchpad.scroll-factor` | `1` | Scroll speed multiplier, up to `10` |
51| `mouse.natural-scroll`, `.accel-speed`, `.accel-profile`, `.left-handed` | as touchpad | Same, for mice, trackballs and trackpoints |
52| `cursor.hide-after` | `5s` | Hide the cursor after this long without motion, or `off` |
53| `animations` | `off` | **Experimental.** Animate layout changes and make swipes follow the fingers |
54| `animations.speed` | `normal` | `slow`, `normal`, `fast`, or a factor from `0.1` to `10` |
55| `focus.follow-move` | `off` | Follow a column moved to another workspace |
56| `focus.position-hint` | `on` | Show workspace/band dots near the output bottom for 600ms after a position change, then fade for 150ms (no fade with animations off); hidden in overview, fullscreen and session lock; never captured |
57| `focus.flash` | `on` | Briefly draw an inner border, inside the window border, on the window, column or output an action changed |
58| `focus.flash-width` | `2` | Flash border width in logical pixels, `1` to `8` |
59| `focus.flash-opacity` | `0.6` | Starting opacity of the flash, `0.1` to `1` |
60| `focus.flash-duration` | `200ms` | Fade duration, `50ms` to `1s`; not scaled by `animations.speed` |
61| `focus.flash-color` | `border.active` | Flash color `#rrggbb`; empty uses `border.active` |
62| `hint.enable` | `off` | Turn on the experimental [hint mode](#hint-mode) (restart required) |
63| `hint.detector` | `nefer-hints` | The [hint mode](#hint-mode) detector binary: a path, or a name looked up in `PATH` (restart required) |
64| `hint.model` | `$XDG_DATA_HOME/nefer-hints/models` | Directory of the detector's model files; `~/.local/share/nefer-hints/models` when `XDG_DATA_HOME` is unset; a leading `~/` is expanded (restart required) |
65| `hint.threshold` | model default | Optional minimum detection score override, 0.01 to 0.99; unset or empty uses the model’s own threshold (`model.json`), else the runner default. Lower finds more targets and more false ones (restart required) |
66| `hint.capture` | `off` | Show hint labels in captures requested by Wayland clients (screenshots, screencasts); the compositor's own hint scans never see them; applies on reload |
67| `hint.size` | `auto` | Label text size in logical pixels (6 to 72), or `auto` for screen pixel density |
68| `hint.background` | empty | Label background (`#rrggbb`); empty uses `border.active` |
69| `hint.foreground` | empty | Label text (`#rrggbb`); empty chooses a contrasting colour |
70| `workspace.<name>.monitor` | focused output | Home monitor of a named workspace |
71| `workspace.<name>.size` | `inherit` | [Smaller viewport](#workspace-size), e.g. `1920x1080` |
72| `workspace.<name>.max-columns`, `.overflow` | output values | Per-workspace layout |
73| `workspace.<name>.column.<N>` | none | Column slot: `<cells>, <command>` |
74| `rule.<name>.*` | none | [Window rules](#window-rules) |
75| `output.<name>` | `preferred` | `WxH`, `WxH@Hz`, `preferred` or `off` |
76| `output.<name>.scale` | `1` | 1 to 4, e.g. `1.5` or `4/3` |
77| `output.<name>.transform` | `normal` | `90`, `180`, `270`, `flipped`, `flipped-90`, … |
78| `output.<name>.primary` | `off` | Focused at startup |
79| `output.<name>.right-of`, `.left-of`, `.above`, `.below`, `.offset` | automatic | [Output placement](#output-placement) |
80| `output.<name>.hdr` | `off` | [HDR](#hdr) on capable displays |
81| `output.<name>.sdr-brightness` | `203` | Brightness of SDR content in HDR, in nits (80–1000) |
82| `render.direct-scanout` | `on` | Send fullscreen windows straight to the display |
83| `render.tearing` | `on` | Allow tearing for fullscreen games |
84| `render.vrr` | `on` | Variable refresh rate for fullscreen windows |
85| `render.vrr-flip-gap` | `1ms` | See [Performance](performance.md#vrr) |
86| `performance.realtime` | `on` | Real-time scheduling |
87| `log.level` / `log.debug` | `info` / empty | Log level / debug categories or `all` |
88| `bind.<keys>` | see [Default binds](#default-binds) | [Binds](#binds) |
89
90With `layout.gaps = 0`, borders are tmux-style lines between tiles. With gaps, every tile gets a full border, even alone. `border.radius` rounds floating windows always and tiles with gaps; fullscreen windows, popups and layers stay square.
91
92`<output>` is a connector such as `DP-2`, or a monitor key (`make model serial`).
93
94The position hint follows actual workspace/band changes and monitor focus, regardless
95of input method. It lists all ext-workspace entries in monitor order: numbered
96workspaces (including the empty spare), then configured hidden named workspaces,
97even when empty. Cascade workspaces use top-aligned band columns; scroll/fixed
98workspaces use one vertical workspace column. Large lists show at most 16 entries
99with the active entry kept visible; band rows are capped at 16.
100
101The focus flash marks the window, column or output an action changes with a brief
102inner border. When a flash-triggering action crosses screens, it marks the whole
103destination output, even if that output has no windows. Same-screen workspace
104switches and mouse focus do not trigger a flash.
105It runs when `animations = on`.
106
107## Automatic terminal
108
109The command comes from `terminal`, then `$TERMINAL`, then `foot`.
110
111| `terminal.auto-open` | Behaviour |
112| --- | --- |
113| `first` | Open once on the initial numbered workspace |
114| `all` | Open on each visible empty workspace without predefined slots |
115| `off` | Do not open automatically |
116
117`--no-terminal` disables automatic opening for one run; `spawn-terminal` still works. Launched programs inherit `$SHELL`, `$EDITOR` and `$VISUAL`.
118
119## Per-output defaults
120
121Per-output defaults (replace `<name>` with a connector such as `DP-1`):
122
123```text
124output.<name> = preferred
125output.<name>.scale = 1
126output.<name>.transform = normal
127output.<name>.primary = off
128output.<name>.offset = 0
129output.<name>.hdr = off
130output.<name>.sdr-brightness = 203
131```
132
133Outputs are arranged automatically unless `.right-of`, `.left-of`, `.above` or `.below` is set. Per-output layouts inherit `layout.*`; named workspaces inherit their output's layout and size, use the focused output unless `.monitor` is set, and have no predefined columns. Media keys have no default bindings.
134
135## Window rules
136
137Rules place an app's windows when they first open.
138
139| Key | Meaning |
140|---|---|
141| `rule.<name>.app-id` | Required. [Regular expression](https://pkg.go.dev/regexp/syntax) matching the whole app ID |
142| `rule.<name>.floating` | `on` to float, `off` to tile |
143| `rule.<name>.workspace` | Workspace number or name |
144| `rule.<name>.monitor` | Output |
145| `rule.<name>.width` | Column width in [cells](#column-widths), 1 to 16 |
146
147```ini
148rule.games.app-id = steam_app_.*
149rule.games.workspace = 3
150rule.chat.app-id = discord|org\.telegram\..*
151rule.chat.workspace = chat
152rule.chat.width = 1
153rule.calc.app-id = org\.gnome\.Calculator
154rule.calc.floating = on
155```
156
157- Every matching rule applies, in file order; later rules win.
158- A rule never moves your focus or view.
159- To find an app ID: `neferwl state | jq '.windows[].app_id'`.
160
161## Output placement
162
163By default, outputs sit left to right in config order. Place one against another, and use `offset` (logical pixels) to align the edges:
164
165```ini
166output.DP-2.right-of = DP-1
167output.DP-2.offset = 360    # DP-2 starts 360 px lower than DP-1
168```
169
170```
171┌────────────┐
172│            │┌─────────┐
173│    DP-1    ││  DP-2   │
174│ 2560x1440  ││1920x1080│
175│            │└─────────┘
176└────────────┘
177```
178
179`offset` moves down for `right-of`/`left-of`, and right for `above`/`below`. kanshi, wdisplays and wlr-randr override these settings until the next reload.
180
181## Workspace size
182
183`workspace.<name>.size` gives a named workspace a smaller centered viewport, for example to present at 1080p on a 4K screen:
184
185```ini
186workspace.presentation.size = 1920x1080
187bind.cmd+p = workspace presentation
188```
189
190## Touchpad
191
192| Gesture | Action |
193|---|---|
194| Three fingers left/right | Scroll columns (switch workspace in cascade) |
195| Three fingers up/down | Switch workspace (scroll bands in cascade) |
196| Four fingers up | Open the overview, or hide the stash |
197| Four fingers down | Close the overview, or show/hide the stash |
198
199A swipe moves one step at most. Other gestures (pinch, hold, two-finger scroll) go to the app under the pointer.
200
201## HDR
202
203`output.<name>.hdr = on` sends HDR10 to displays that support it. If the display, driver or GPU cannot, NeferWL falls back to SDR and logs why. NVIDIA is not supported yet.
204
205- HDR-aware apps (games, browsers, video players) can show HDR content. Some need HDR turned on in their own settings. X11 apps stay SDR.
206- The desktop stays SDR, shown at `sdr-brightness` nits.
207- Fullscreen HDR windows go straight to the display.
208- Screenshots and captures are SDR (tone-mapped).
209- If NeferWL crashes, the next compositor may show distorted colours until it sets its own colour pipeline.
210
211## Binds
212
213`bind.<keys> = <action>`. Modifiers: `cmd` (see `keyboard.cmd`), `shift`, `ctrl`, `alt`, `super`.
214
215- Keys are what your layout prints: `cmd+é` on AZERTY. Write `=`, `#` and space by name: `cmd+equal`, `cmd+numbersign`, `cmd+space`.
216- `code:N` is a physical key, the same on every layout: `code:2` to `code:10` are the digits 1 to 9.
217- Other keys use their xkb name (`wev` shows it).
218- `none` removes a default bind.
219- `spawn <command>` runs a program without a shell. See [Running commands](desktop.md#running-commands).
220- Holding a move bind repeats it: focus, move, resize and workspace steps, first after 250 ms, then 8 times a second. Toggles, spawns and closes run once. The overview's arrows and `h/j/k/l` repeat the same way.
221
222```ini
223bind.XF86AudioRaiseVolume = spawn wpctl set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ 5%+
224bind.XF86AudioLowerVolume = spawn wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
225bind.XF86AudioMute = spawn wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
226bind.XF86MonBrightnessUp = spawn brightnessctl set +5%
227bind.XF86MonBrightnessDown = spawn brightnessctl set 5%-
228```
229
230### Actions
231
232| Action | Effect |
233|---|---|
234| `spawn <command>` | Run a program |
235| `spawn-terminal` | Run `terminal` |
236| `stash <command>` | Run a program and open its window in the [stash](#stash) |
237| `close-window` | Close the focused window |
238| `toggle-fullscreen` | Fullscreen the focused window. Nothing shows above it except its own dialogs |
239| `toggle-floating` | Make the focused window [float](#free-floating-windows), or put it back in its column |
240| `toggle-window-stash` | Move the focused window to the [stash](#stash), or back to its column |
241| `toggle-stash-visible` | Show or hide the stash |
242| `toggle-overview` | Open or close the [overview](#overview) |
243| `hint` | Label the clickable elements on the focused screen, then click the one you type ([hint mode](#hint-mode)) |
244| `switch-column-next` / `-prev` | Switch to the previously used column. Hold `cmd` and press again to go further back |
245| `maximize-column` | Toggle full width for the focused column |
246| `cycle-column-width` | Grow the focused column by one [cell](#column-widths), then back to equal widths |
247| `set-column-width <+N\|-N>` | Widen or narrow the focused column by N cells |
248| `set-window-height <+N%\|-N%>` | Change the focused window's share of its column |
249| `focus-column-left/right` | Focus the neighbor column, then the neighbor monitor |
250| `focus-window-up/down` | Focus the neighbor window in the column, then the neighbor workspace (band in cascade) |
251| `move-column-left/right` | Move the focused column, then to the neighbor monitor |
252| `move-window-up/down` | Move the focused window in its column |
253| `consume-or-expel-window-left/right` | Join the neighbor column, or leave the current one |
254| `focus-workspace <N>` / `-prev/-next` | Show a workspace |
255| `workspace <name>` | Show or hide a named workspace |
256| `move-column-to-workspace <N>` / `-prev/-next` | Move the focused column to a workspace |
257| `move-window-to-workspace <N>` / `-prev/-next` | Move the focused window to a workspace |
258| `move-workspace-prev/next` | Swap the workspace with its neighbor |
259| `focus-monitor-left/right/up/down` | Focus the neighbor monitor |
260| `move-workspace-to-monitor-left/right/up/down` | Move the workspace to the neighbor monitor |
261| `scale-up` / `scale-down` | Zoom the display; the scale is saved to the config |
262| `quit` | Exit NeferWL |
263
264### Default binds
265
266| Keys | Action |
267|---|---|
268| `cmd+return` | `spawn-terminal` |
269| `ctrl+cmd+space` | `spawn fuzzel` |
270| `cmd+q` | `close-window` |
271| `cmd+left/right`, `cmd+h/l` | `focus-column-left/right` |
272| `cmd+up/down`, `cmd+k/j` | `focus-window-up/down` |
273| `cmd+shift+left/right`, `cmd+shift+h/l` | `move-column-left/right` |
274| `cmd+shift+up/down`, `cmd+shift+k/j` | `move-window-up/down` |
275| `cmd+bracketleft/bracketright` | `consume-or-expel-window-left/right` |
276| `cmd+alt+left/right`, `cmd+alt+h/l` | `set-column-width -1/+1` |
277| `cmd+alt+up/down`, `cmd+alt+k/j` | `set-window-height -10%/+10%` |
278| `cmd+r` | `cycle-column-width` |
279| `cmd+f` | `maximize-column` |
280| `cmd+shift+f` | `toggle-fullscreen` |
281| `cmd+shift+space` | `toggle-floating` |
282| `cmd+s` | `toggle-stash-visible` |
283| `cmd+shift+s` | `toggle-window-stash` |
284| `cmd+o` | `toggle-overview` |
285| `cmd+tab` / `cmd+shift+tab` | `switch-column-next/prev` |
286| `cmd+1`…`9` (`code:2`…`10`) | `focus-workspace 1`…`9` |
287| `cmd+shift+1`…`9` | `move-column-to-workspace 1`…`9` |
288| `cmd+pageup/pagedown` | `focus-workspace-prev/next` |
289| `cmd+shift+pageup/pagedown` | `move-column-to-workspace-prev/next` |
290| `cmd+ctrl+arrows`, `cmd+ctrl+h/j/k/l` | `focus-monitor-*` |
291| `cmd+ctrl+shift+left/right`, `cmd+ctrl+shift+h/l` | `move-workspace-to-monitor-left/right` |
292| `cmd+ctrl+shift+up/down`, `cmd+ctrl+shift+k/j` | `move-workspace-prev/next` |
293| `cmd+code:12` / `cmd+code:13` (`-` / `=` on US) | `scale-down` / `scale-up` |
294| `ctrl+alt+backspace` | `quit` |
295
296With `keyboard.cmd = alt` or `ctrl`, the defaults that would combine `cmd` with that same modifier are dropped. Bind those actions to other keys.
297
298## Layout
299
300### Cascade
301
302The default layout. Columns sit side by side in bands of up to `max-columns`, stacked one below the other. Each band fills the screen height and keeps its columns until you move them.
303
304- Left/right focus moves within a band, then to the neighbor monitor, then to the neighbor workspace.
305- `move-column-left/right` stays in the band; at its edge the column goes to the neighbor monitor, if any.
306- Up/down moves through the windows of a column, then to the band above or below.
307- `move-window-up/down` past the column's top or bottom sends the whole column to the band above or below. It joins that band when it has room, else it gets a new band between the two.
308- A new window joins the focused band when it has room, else it gets a new band right below.
309- `cycle-column-width` in a full band makes room: the rightmost column goes to the band below (from the last column, the leftmost goes to the band above). The end of the cycle brings it back.
310- `maximize-column` gives the column a band of its own: the columns on its left go to the band above, the ones on its right to the band below. Maximizing again forms the band back.
311- A band left empty goes away. A smaller `max-columns` moves the extra columns of a band to a new band below it.
312
313### Column widths
314
315A cell is `1/max-columns` of the screen width. A column is 1 to 16 cells wide. Columns without a set width share the space equally.
316
317### Stash
318
319Each workspace has a stash: a strip of windows set aside with `toggle-window-stash`. The selected one shows centered over your columns, with its neighbors dimmed on the sides. `focus-column-left/right` or a three-finger swipe browse it. `toggle-stash-visible` or a four-finger swipe hides it.
320
321`stash <command>` runs a program and opens its window straight in the stash of the workspace you were on, selected and focused, even if you switch workspace before it appears. With `stash.spawn-terminal`, `toggle-stash-visible` or the four-finger swipe down on an empty stash does the same with `terminal`.
322
323```ini
324bind.cmd+shift+f = stash firefox
325```
326
327The window must appear within 5 seconds, otherwise it opens normally. Apps that hand a launch to an already running instance (single-instance browsers, `footclient`) open their window normally.
328
329### Free floating windows
330
331`toggle-floating` turns the focused window into a floating window that stays where you put it. On a floating window, move binds move it by 50 pixels and resize binds resize it.
332
333### Moving windows with the pointer
334
335- `cmd` + left drag moves a window. Drop it on the top or bottom of another window to stack it there, or in the middle to swap.
336- `cmd` + right drag resizes a floating window.
337- `cmd` + right drag on the line between two columns or two stacked windows resizes them.
338- `cmd` + double right-click on that line resets the lane's column widths or the column's row heights. Column widths reset when a column enters or leaves the lane.
339- `Escape` cancels the drag and restores the previous sizes.
340
341### Overview
342
343`toggle-overview` (or a four-finger swipe up) shows previews of your workspaces. Arrows, `h/j/k/l` or three-finger swipes move the selection; `Return` or a click opens it; `Escape` goes back. `close-window` and the move-to-workspace binds work on the selected preview.
344
345A workspace's stash shows as a pile of cards. Selecting the pile unfolds it into a carousel of every stashed window: a row under the workspace with `layout.overflow = cascade`, a column on its left otherwise. The selected card stays in the middle, the earlier cards before it and the later ones after it, wrapping round; a wider gap separates the newest card from the oldest. Moves along the carousel pick a card. With fewer than three cards, or on a workspace holding only stashed windows, it does not wrap round. Leaving it folds it again. Moving to another workspace selects its windows rather than a carousel that wraps round, even when its stash has the focus, so browsing workspaces never stops on one.
346
347With `layout.overflow = cascade`, workspaces sit side by side, each band of columns is a lane, and the stash sits under the lanes. Down (or fingers down on the touchpad, with natural scroll off) moves to the lane below; past the last lane it unfolds the stash. Left/right walk the cards; where the carousel does not wrap round, they go to the neighbor workspace past either end. Up leaves the stash for the last lane, at the column you came from.
348
349Otherwise, left from the first column unfolds the stash. Up/down walk the cards, the focus-workspace binds change workspace, and right goes back to the columns.
350
351## Hint mode
352
353Hint mode is experimental and off by default: set `hint.enable = on` and restart NeferWL.
354While it is off, nothing of hint mode runs or uses memory, and the `hint` bind
355logs a notice once and does nothing. A reload does not change it.
356
357`hint` labels the clickable elements (buttons, links, icons, fields) on the
358focused output. Type a label to click its target; BackSpace erases a letter,
359Escape cancels. No bind is set by default; for example `bind.cmd+g = hint`.
360
361Labels use the `border.active` colour with a contrasting text colour, and
362their size follows the screen's pixel density. Each label sits just outside
363the top-left corner of its target. The click is a plain left click.
364
365Labels are not part of screenshots and screencasts by default. With
366`hint.capture = on` they appear in the captures that Wayland clients request
367(including Exclude captures); the detector never sees them.
368The capture indicator and the position hint stay out of every capture.
369A reload applies `hint.capture` to the next frame.
370
371### Where targets come from
372
373Targets come from `nefer-hints`, a separate program that looks at the pixels of
374the whole output with a neural network and returns a box for each clickable
375element. It works the same for every app, toolkit and terminal. It sees what is
376drawn: it does not know what an element does, and it can miss or invent boxes
377(`hint.threshold` trades one for the other). Boxes under 4×4 logical pixels
378are dropped.
379
380NeferWL starts `nefer-hints` at the first hint scan (loading takes about half a
381second), keeps it running, and stops it on exit. If it dies, fails or takes
382more than 2 seconds on a scan, that scan is cancelled and a new process starts
383at the next one. Its device (GPU or CPU) is logged at startup, at info level.
384
385### Requirements
386
387- The `nefer-hints` binary, found in `PATH` or named by `hint.detector`.
388- Its model files, in `hint.model`.
389- A Vulkan GPU for speed; without one the detector falls back to the CPU, which is slower.
390
391### Debugging hint mode
392
393With `hint.enable = on` and `NEFERWL_HINT_DEBUG=1` set when NeferWL starts (for example `NEFERWL_HINT_DEBUG=1 make tty`, or the variable in the launch environment), every successful scan writes three files to `$XDG_STATE_HOME/neferwl/hints/` (`~/.local/state/neferwl/hints/` when unset): `<time>-<output>.png`, the frame as scanned; `<time>-<output>.annotated.png`, the same with the detector boxes outlined; and `<time>-<output>.json`, the targets (rectangle and score) and the capture, detection and total timings. NeferWL keeps the 50 most recent scans and logs a warning at startup. The detector's own messages are logged at debug level (`log.debug = hints`). The files contain the screen in clear: delete them after use.

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 25

Tags 10

↑↓ moveenter openesc close