[!WARNING]
Early alpha, but stable enough for daily use. Developed and tested mostly on AMD CPUs and GPUs. NVIDIA support is incomplete and untested (contribs welcome). Expect bugs and breaking config changes.
Why NeferWL
I wanted a compositor I would never have to think about. It takes the newest Wayland protocols and kernel features as they land, gives games the whole GPU, and otherwise stays out of sight.
It is small on purpose, and it will stay small. No blur, no shadows, no rounded corners, no themes. No built-in bar or wallpaper either: bring your own, any layer-shell client works.
Animations are short, cheap and off by default (experimental, animations = on).
About 40–80 MB of RAM with two 4K monitors, and almost no CPU while the screen does not change. See Performance.
OK, but why Go?
Most Wayland compositors are written in C (wlroots, Sway, Mutter), C++ (KWin, Hyprland, gamescope) or Rust (niri, COSMIC). The usual objection to Go is the garbage collector. It is a fair one, so here is how NeferWL deals with it, and why Go turns out to fit a compositor well.
A compositor is a concurrency problem
A compositor sits in the middle of many independent streams that never stop: keyboard and pointer events, client requests and buffer commits, one vblank and page-flip cycle per monitor, frame fences from the GPU, presentation feedback, screen captures, config reloads, hotplug, idle and lock state. None of them should wait on another. A slow client must not delay a flip, and rendering must not delay input.
Go has this built in. Goroutines are cheap, channels carry messages between them, and the runtime spreads them over every CPU core. NeferWL follows one idiom everywhere: each piece of state has one owner goroutine, and others talk to it over channels.
- Input runs on its own thread, reads libinput and sends events to core.
- Core owns windows, workspaces and focus. It turns input and client events into layouts and scenes.
- The Wayland server owns client connections. It sends client events and surface content, and receives commands back.
- Each output owns its DRM state, renders its scenes with Vulkan, commits frames and reports flips and presentation times.
- Capture, config, idle, sessions and leases each run in their own goroutine.
About 35 channels connect them, all created in one place, so the full data flow is easy to read. There is no mutex on window state, no lock ordering to get wrong, and the race detector checks every test. When CAP_SYS_NICE is granted, the input and output goroutines lock their OS thread and request real-time scheduling, so a busy CPU cannot delay a cursor move or a flip.
Keeping the garbage collector out of the way
- The hot path does not allocate. Frame callbacks, presentation reports, DRM flips, Vulkan submissions and surface updates reuse memory owned by the goroutine that handles them.
make check runs allocation guards that fail the build when one of these paths starts allocating.
- The GC has almost nothing to collect. Client buffers live in shared and GPU memory, not on the Go heap, so the live heap is a few megabytes. Playing a 4K HDR video with 65 tiled subsurfaces committing every frame, GC takes under 9 % of a process that itself takes 13 % of a core. See Performance.
- No cgo. NeferWL builds with
CGO_ENABLED=0. libwayland, Vulkan and libinput are loaded at runtime through purego with fixed-arity calls: no reflection, no allocation at the boundary.
- Work only when something changes. Damage tracking redraws only what changed, surfaces hidden behind opaque ones are skipped, and an idle output does nothing.
- Tuned on real hardware. Under VRR, cursor moves ride on the game's next frame instead of forcing their own refresh, and game frames keep a short gap after each flip so the panel does not fall back to its slowest rate.
I also like bringing more tools to the Go ecosystem. NeferWL grew its own libraries along the way, and other Go projects can use them:
Features
Display and games
- Direct scanout: eligible fullscreen buffers go straight to the display, without composition.
- VRR and tearing: enabled by default for supported fullscreen use (
render.vrr, render.tearing). Tearing requires direct scanout and a client request.
- Explicit sync: client fences go to the GPU and KMS. Enabled by default, with no flag to set.
- HDR: HDR10 on capable displays, with configurable brightness for SDR content.
- Multi-monitor: separate workspaces and fractional scale per output. Workspaces move when a monitor is unplugged and return when it reconnects.
- Hidden apps rest: a window you cannot see (another workspace, a hidden stash, scrolled off screen, under a fullscreen window) gets frame callbacks once per second and the suspended state. Browsers and games stop drawing frames nobody sees. A window being captured keeps its output's refresh rate.
- X11 apps: Steam and other X11 clients run through xwayland-satellite. Wine can run natively on Wayland.
Windows and workspaces
- Cascade tiling (default): an original layout. Columns fill the screen side by side, then continue in a new band below; scroll bands vertically and switch workspaces sideways. See Cascade.
- Other layouts: scroll horizontally, as in PaperWM and niri, or keep columns on screen in a fixed spiral. Choose per output or workspace.
- Overview:
cmd+o or a four-finger swipe up shows live workspace previews. Select a window with the keyboard, touchpad or mouse.
- Stash:
cmd+s toggles a floating strip of windows set aside for the current workspace. cmd+shift+s moves a window into or out of it.
- Workspaces: numbered workspaces appear as needed. Named workspaces can start commands and place their windows in predefined columns.
Desktop integration
Bring your own desktop UI: any layer-shell client, such as Noctalia or Waybar, works. For capture and idle management, see nefercap and neferafk.
Configuration is a flat key = value file that reloads on save. A JSON state file exposes outputs, workspaces and windows to scripts. See Desktop integration.
Install
Arch Linux
Choose an AUR package:
paru -S neferwl-bin # latest release, pre-built for x86_64 and aarch64
paru -S neferwl-git # latest main, built from source
Or install from a checkout with Go 1.27:
make install # build a package from HEAD and install it with pacman
make pkg builds the package without installing it.
Ubuntu 24.04 and later
Download the .deb for your architecture (amd64 or arm64) and checksums.txt from Releases, then install it with apt, which pulls the runtime dependencies:
sha256sum -c --ignore-missing checksums.txt
sudo apt install ./neferwl_<version>_amd64.deb
Optional tools:
sudo apt install foot fuzzel xdg-desktop-portal-gtk xdg-desktop-portal-gnome
X11 apps need xwayland-satellite 0.7 or later, which you may have to build. The package is tested on Ubuntu 24.04 and 26.04; Debian with the same library packages should work but is untested.
What the packages provide
The Arch and Debian packages provide:
neferwl with CAP_SYS_NICE for real-time scheduling.
- A NeferWL session for display managers such as Ly, GDM and SDDM.
neferwl-session, which starts neferwl.service and manages graphical-session.target so desktop services start and stop with the session.
Running neferwl --session directly also exports the display environment to D-Bus and systemd user services. See Desktop integration for session setup.
Copy /usr/share/doc/neferwl/config.example to ~/.config/neferwl/config to start from the example config.
Other distributions
Download a linux_amd64 or linux_arm64 archive from Releases and verify it against checksums.txt. To build locally, run make bin; the binary is bin/neferwl.
Hardware support
NeferWL requires Linux, atomic KMS and a Vulkan 1.3 device that can export images as dmabufs. It reports an error at startup if these requirements are not met.
| GPU |
Status |
| AMD |
Developed and tested on radv |
| NVIDIA |
Untested; explicit sync is implemented, but support is incomplete |
| Intel |
Untested |
Reports from NVIDIA and Intel users are welcome.
Configuration
The default file is $XDG_CONFIG_HOME/neferwl/config, or ~/.config/neferwl/config.
keyboard.layout = fr
terminal.auto-open = first
startup = waybar
output.DP-1 = 3840x2160@144
output.DP-1.scale = 1.5
layout.max-columns = 3
# Start and place an editor and terminal when this workspace is opened.
workspace.dev.monitor = DP-1
workspace.dev.column.1 = 67%, code --new-window
workspace.dev.column.2 = 33%, foot
bind.cmd+d = workspace dev
See Configuration for all keys, actions and default bindings, or examples/config for commented defaults.
- Use one
key = value per line; # starts a comment.
- Missing keys use defaults. Invalid lines warn and keep that key's default; other lines still apply.
- Save to reload. The log lists changed keys.
- Use
neferwl --config <path> to select a file, or neferwl validate-config [path] to check it without starting the compositor.
Default configuration
These values apply without a config file. Empty values use the system setting; terminal uses $TERMINAL, then foot. No startup commands or named workspaces are configured by default. cmd means Super.
# Keyboard and commands
keyboard.layout =
keyboard.variant =
keyboard.options =
keyboard.repeat-rate = 25
keyboard.repeat-delay = 600
keyboard.cmd = super
terminal =
terminal.auto-open = first
xwayland = xwayland-satellite
# Appearance and layout
background = #111111
border.width = 2
border.active = #808080
border.inactive = #111111
floating.dim = 0.3
layout.gaps = 0
layout.max-columns = 2
layout.overflow = cascade
stash.width = 80
stash.gap = 2
stash.dim = 0.5
stash.capture = on
stash.spawn-terminal = on
# Input
touchpad.natural-scroll = off
touchpad.tap = on
touchpad.accel-speed = 0
touchpad.accel-profile = adaptive
touchpad.left-handed = off
touchpad.scroll-factor = 1
mouse.natural-scroll = off
mouse.accel-speed = 0
mouse.accel-profile = adaptive
mouse.left-handed = off
cursor.hide-after = 5s
# Focus and animations
focus.follow-move = off
animations = off
animations.speed = normal
focus.flash = on
focus.flash-width = 2
focus.flash-opacity = 0.6
focus.flash-duration = 200ms
# Rendering and logging
render.direct-scanout = on
render.tearing = on
render.vrr = on
render.vrr-flip-gap = 1ms
performance.realtime = on
log.level = info
log.debug =
# Launch, close and session
bind.cmd+return = spawn-terminal
bind.ctrl+cmd+space = spawn fuzzel
bind.cmd+q = close-window
bind.ctrl+alt+backspace = quit
# Focus and move
bind.cmd+left = focus-column-left
bind.cmd+right = focus-column-right
bind.cmd+up = focus-window-up
bind.cmd+down = focus-window-down
bind.cmd+h = focus-column-left
bind.cmd+l = focus-column-right
bind.cmd+k = focus-window-up
bind.cmd+j = focus-window-down
bind.cmd+shift+left = move-column-left
bind.cmd+shift+right = move-column-right
bind.cmd+shift+h = move-column-left
bind.cmd+shift+l = move-column-right
bind.cmd+shift+up = move-window-up
bind.cmd+shift+down = move-window-down
bind.cmd+shift+k = move-window-up
bind.cmd+shift+j = move-window-down
bind.cmd+bracketleft = consume-or-expel-window-left
bind.cmd+bracketright = consume-or-expel-window-right
# Resize and window modes
bind.cmd+alt+left = set-column-width -1
bind.cmd+alt+right = set-column-width +1
bind.cmd+alt+up = set-window-height -10%
bind.cmd+alt+down = set-window-height +10%
bind.cmd+alt+h = set-column-width -1
bind.cmd+alt+l = set-column-width +1
bind.cmd+alt+k = set-window-height -10%
bind.cmd+alt+j = set-window-height +10%
bind.cmd+shift+space = toggle-floating
bind.cmd+r = cycle-column-width
bind.cmd+f = maximize-column
bind.cmd+shift+f = toggle-fullscreen
bind.cmd+s = toggle-stash-visible
bind.cmd+shift+s = toggle-window-stash
bind.cmd+o = toggle-overview
# bind.cmd+g = hint
bind.cmd+tab = switch-column-next
bind.cmd+shift+tab = switch-column-prev
# Workspaces
bind.cmd+pageup = focus-workspace-prev
bind.cmd+pagedown = focus-workspace-next
bind.cmd+shift+pageup = move-column-to-workspace-prev
bind.cmd+shift+pagedown = move-column-to-workspace-next
bind.cmd+ctrl+shift+up = move-workspace-prev
bind.cmd+ctrl+shift+down = move-workspace-next
bind.cmd+ctrl+shift+k = move-workspace-prev
bind.cmd+ctrl+shift+j = move-workspace-next
bind.cmd+code:2 = focus-workspace 1
bind.cmd+code:3 = focus-workspace 2
bind.cmd+code:4 = focus-workspace 3
bind.cmd+code:5 = focus-workspace 4
bind.cmd+code:6 = focus-workspace 5
bind.cmd+code:7 = focus-workspace 6
bind.cmd+code:8 = focus-workspace 7
bind.cmd+code:9 = focus-workspace 8
bind.cmd+code:10 = focus-workspace 9
bind.cmd+shift+code:2 = move-column-to-workspace 1
bind.cmd+shift+code:3 = move-column-to-workspace 2
bind.cmd+shift+code:4 = move-column-to-workspace 3
bind.cmd+shift+code:5 = move-column-to-workspace 4
bind.cmd+shift+code:6 = move-column-to-workspace 5
bind.cmd+shift+code:7 = move-column-to-workspace 6
bind.cmd+shift+code:8 = move-column-to-workspace 7
bind.cmd+shift+code:9 = move-column-to-workspace 8
bind.cmd+shift+code:10 = move-column-to-workspace 9
# Monitors and scale
bind.cmd+ctrl+left = focus-monitor-left
bind.cmd+ctrl+right = focus-monitor-right
bind.cmd+ctrl+h = focus-monitor-left
bind.cmd+ctrl+l = focus-monitor-right
bind.cmd+ctrl+shift+left = move-workspace-to-monitor-left
bind.cmd+ctrl+shift+right = move-workspace-to-monitor-right
bind.cmd+ctrl+shift+h = move-workspace-to-monitor-left
bind.cmd+ctrl+shift+l = move-workspace-to-monitor-right
bind.cmd+code:13 = scale-up
bind.cmd+code:12 = scale-down
The focus flash marks the window, column or output an action changes with a brief
inner border. Workspace switches and mouse focus do not trigger a flash.
It runs when animations = on.
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.
See Configuration for accepted values and workspace options.
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.
Documentation
- Configuration: keys, actions, bindings and HDR.
- Desktop integration: sessions, clipboard, input methods, X11 apps and scripting.
- Session locking: external lockers, recovery and security limits.
- Screen capture: protocols, indicator, executable allowlist and portals.
- Headless mode: screenshots, input scripts and input-method testing without a screen.
- Performance: VRR, real-time priority, profiling and frame stats.
Contributing
Open an issue and wait for acceptance before sending a pull request, including for bug fixes. Pull requests without an accepted issue are closed.
Before submitting, run make check, make race, make mocks-check and staticcheck ./.... See AGENTS.md for repository rules.
License
GNU General Public License v3.0.
source
1<h1 align="center">NeferWL</h1>
2
3<p align="center">
4 <a href="LICENSE"><img src="https://img.shields.io/badge/license-GPLv3-blue?style=flat-square" alt="License: GPLv3"></a>
5 <a href="https://github.com/bnema/neferwl"><img src="https://img.shields.io/badge/platform-Linux-blue?style=flat-square" alt="Platform: Linux"></a>
6 <a href="https://github.com/bnema/neferwl/commits/main"><img src="https://img.shields.io/github/last-commit/bnema/neferwl/main?style=flat-square&logo=github" alt="Last commit"></a>
7 <a href="https://github.com/bnema/neferwl/stargazers"><img src="https://img.shields.io/github/stars/bnema/neferwl?style=flat-square&logo=github" alt="GitHub stars"></a>
8</p>
9
10<p align="center">A Wayland compositor that spends its frames on your apps, not on itself.</p>
11
12> [!WARNING]
13> **Early alpha, but stable enough for daily use.** Developed and tested mostly on AMD CPUs and GPUs. NVIDIA support is incomplete and untested (contribs welcome). Expect bugs and breaking config changes.
14
15## Why NeferWL
16
17I wanted a compositor I would never have to think about. It takes the newest Wayland protocols and kernel features as they land, gives games the whole GPU, and otherwise stays out of sight.
18
19It is small on purpose, and it will stay small. No blur, no shadows, no rounded corners, no themes. No built-in bar or wallpaper either: bring your own, any layer-shell client works.
20
21Animations are short, cheap and off by default (experimental, `animations = on`).
22
23About 40–80 MB of RAM with two 4K monitors, and almost no CPU while the screen does not change. See [Performance](docs/performance.md).
24
25## OK, but why Go?
26
27Most Wayland compositors are written in C (wlroots, Sway, Mutter), C++ (KWin, Hyprland, gamescope) or Rust (niri, COSMIC). The usual objection to Go is the garbage collector. It is a fair one, so here is how NeferWL deals with it, and why Go turns out to fit a compositor well.
28
29### A compositor is a concurrency problem
30
31A compositor sits in the middle of many independent streams that never stop: keyboard and pointer events, client requests and buffer commits, one vblank and page-flip cycle per monitor, frame fences from the GPU, presentation feedback, screen captures, config reloads, hotplug, idle and lock state. None of them should wait on another. A slow client must not delay a flip, and rendering must not delay input.
32
33Go has this built in. Goroutines are cheap, channels carry messages between them, and the runtime spreads them over every CPU core. NeferWL follows one idiom everywhere: **each piece of state has one owner goroutine, and others talk to it over channels.**
34
35- **Input** runs on its own thread, reads libinput and sends events to core.
36- **Core** owns windows, workspaces and focus. It turns input and client events into layouts and scenes.
37- **The Wayland server** owns client connections. It sends client events and surface content, and receives commands back.
38- **Each output** owns its DRM state, renders its scenes with Vulkan, commits frames and reports flips and presentation times.
39- **Capture, config, idle, sessions and leases** each run in their own goroutine.
40
41About 35 channels connect them, all created in one place, so the full data flow is easy to read. There is no mutex on window state, no lock ordering to get wrong, and the race detector checks every test. When CAP_SYS_NICE is granted, the input and output goroutines lock their OS thread and request real-time scheduling, so a busy CPU cannot delay a cursor move or a flip.
42
43### Keeping the garbage collector out of the way
44
45- **The hot path does not allocate.** Frame callbacks, presentation reports, DRM flips, Vulkan submissions and surface updates reuse memory owned by the goroutine that handles them. `make check` runs allocation guards that fail the build when one of these paths starts allocating.
46- **The GC has almost nothing to collect.** Client buffers live in shared and GPU memory, not on the Go heap, so the live heap is a few megabytes. Playing a 4K HDR video with 65 tiled subsurfaces committing every frame, GC takes under 9 % of a process that itself takes 13 % of a core. See [Performance](docs/performance.md).
47- **No cgo.** NeferWL builds with `CGO_ENABLED=0`. libwayland, Vulkan and libinput are loaded at runtime through [purego](https://github.com/ebitengine/purego) with fixed-arity calls: no reflection, no allocation at the boundary.
48- **Work only when something changes.** Damage tracking redraws only what changed, surfaces hidden behind opaque ones are skipped, and an idle output does nothing.
49- **Tuned on real hardware.** Under VRR, cursor moves ride on the game's next frame instead of forcing their own refresh, and game frames keep a short gap after each flip so the panel does not fall back to its slowest rate.
50
51I also like bringing more tools to the Go ecosystem. NeferWL grew its own libraries along the way, and other Go projects can use them:
52
53- [purego-libwayland](https://github.com/bnema/purego-libwayland): libwayland-server without cgo.
54- [purego-vulkan](https://github.com/bnema/purego-vulkan): Vulkan without cgo, generated from `vk.xml`.
55- [go-wayland-bindings](https://github.com/bnema/go-wayland-bindings): Wayland protocol bindings, generated from the upstream XML.
56- [wlturbo](https://github.com/bnema/wlturbo): a fast Wayland client.
57- [neferclient](https://github.com/bnema/neferclient): a client toolkit on top of them: connection, outputs, surface roles, seat and dmabuf presentation.
58
59## Features
60
61### Display and games
62
63- **Direct scanout:** eligible fullscreen buffers go straight to the display, without composition.
64- **VRR and tearing:** enabled by default for supported fullscreen use (`render.vrr`, `render.tearing`). Tearing requires direct scanout and a client request.
65- **Explicit sync:** client fences go to the GPU and KMS. Enabled by default, with no flag to set.
66- **HDR:** HDR10 on capable displays, with configurable brightness for SDR content.
67- **Multi-monitor:** separate workspaces and fractional scale per output. Workspaces move when a monitor is unplugged and return when it reconnects.
68- **Hidden apps rest:** a window you cannot see (another workspace, a hidden stash, scrolled off screen, under a fullscreen window) gets frame callbacks once per second and the suspended state. Browsers and games stop drawing frames nobody sees. A window being captured keeps its output's refresh rate.
69- **X11 apps:** Steam and other X11 clients run through xwayland-satellite. Wine can run natively on Wayland.
70
71### Windows and workspaces
72
73- **Cascade tiling (default):** an original layout. Columns fill the screen side by side, then continue in a new band below; scroll bands vertically and switch workspaces sideways. See [Cascade](docs/config.md#cascade).
74- **Other layouts:** scroll horizontally, as in PaperWM and niri, or keep columns on screen in a fixed spiral. Choose per output or workspace.
75- **Overview:** `cmd+o` or a four-finger swipe up shows live workspace previews. Select a window with the keyboard, touchpad or mouse.
76- **Stash:** `cmd+s` toggles a floating strip of windows set aside for the current workspace. `cmd+shift+s` moves a window into or out of it.
77- **Workspaces:** numbered workspaces appear as needed. Named workspaces can start commands and place their windows in predefined columns.
78
79### Desktop integration
80
81Bring your own desktop UI: any layer-shell client, such as Noctalia or Waybar, works. For capture and idle management, see [nefercap](https://github.com/bnema/nefercap) and [neferafk](https://github.com/bnema/neferafk).
82
83Configuration is a flat `key = value` file that reloads on save. A JSON state file exposes outputs, workspaces and windows to scripts. See [Desktop integration](docs/desktop.md).
84
85## Install
86
87### Arch Linux
88
89Choose an AUR package:
90
91```sh
92paru -S neferwl-bin # latest release, pre-built for x86_64 and aarch64
93paru -S neferwl-git # latest main, built from source
94```
95
96Or install from a checkout with Go 1.27:
97
98```sh
99make install # build a package from HEAD and install it with pacman
100```
101
102`make pkg` builds the package without installing it.
103
104### Ubuntu 24.04 and later
105
106Download the `.deb` for your architecture (`amd64` or `arm64`) and `checksums.txt` from [Releases](https://github.com/bnema/neferwl/releases), then install it with apt, which pulls the runtime dependencies:
107
108```sh
109sha256sum -c --ignore-missing checksums.txt
110sudo apt install ./neferwl_<version>_amd64.deb
111```
112
113Optional tools:
114
115```sh
116sudo apt install foot fuzzel xdg-desktop-portal-gtk xdg-desktop-portal-gnome
117```
118
119X11 apps need [xwayland-satellite](https://github.com/Supreeeme/xwayland-satellite) 0.7 or later, which you may have to build. The package is tested on Ubuntu 24.04 and 26.04; Debian with the same library packages should work but is untested.
120
121### What the packages provide
122
123The Arch and Debian packages provide:
124
125- `neferwl` with CAP_SYS_NICE for real-time scheduling.
126- A **NeferWL** session for display managers such as Ly, GDM and SDDM.
127- `neferwl-session`, which starts `neferwl.service` and manages `graphical-session.target` so desktop services start and stop with the session.
128
129Running `neferwl --session` directly also exports the display environment to D-Bus and systemd user services. See [Desktop integration](docs/desktop.md) for session setup.
130
131Copy `/usr/share/doc/neferwl/config.example` to `~/.config/neferwl/config` to start from the example config.
132
133### Other distributions
134
135Download a `linux_amd64` or `linux_arm64` archive from [Releases](https://github.com/bnema/neferwl/releases) and verify it against `checksums.txt`. To build locally, run `make bin`; the binary is `bin/neferwl`.
136
137## Hardware support
138
139NeferWL requires Linux, atomic KMS and a Vulkan 1.3 device that can export images as dmabufs. It reports an error at startup if these requirements are not met.
140
141| GPU | Status |
142| --- | --- |
143| AMD | Developed and tested on radv |
144| NVIDIA | Untested; explicit sync is implemented, but support is incomplete |
145| Intel | Untested |
146
147Reports from NVIDIA and Intel users are welcome.
148
149## Configuration
150
151The default file is `$XDG_CONFIG_HOME/neferwl/config`, or `~/.config/neferwl/config`.
152
153```text
154keyboard.layout = fr
155terminal.auto-open = first
156startup = waybar
157
158output.DP-1 = 3840x2160@144
159output.DP-1.scale = 1.5
160
161layout.max-columns = 3
162
163# Start and place an editor and terminal when this workspace is opened.
164workspace.dev.monitor = DP-1
165workspace.dev.column.1 = 67%, code --new-window
166workspace.dev.column.2 = 33%, foot
167bind.cmd+d = workspace dev
168```
169
170See [Configuration](docs/config.md) for all keys, actions and default bindings, or [examples/config](examples/config) for commented defaults.
171
172- Use one `key = value` per line; `#` starts a comment.
173- Missing keys use defaults. Invalid lines warn and keep that key's default; other lines still apply.
174- Save to reload. The log lists changed keys.
175- Use `neferwl --config <path>` to select a file, or `neferwl validate-config [path]` to check it without starting the compositor.
176
177### Default configuration
178
179<details>
180<summary>All default settings and key bindings</summary>
181
182These values apply without a config file. Empty values use the system setting; `terminal` uses `$TERMINAL`, then `foot`. No startup commands or named workspaces are configured by default. `cmd` means Super.
183
184```text
185# Keyboard and commands
186keyboard.layout =
187keyboard.variant =
188keyboard.options =
189keyboard.repeat-rate = 25
190keyboard.repeat-delay = 600
191keyboard.cmd = super
192terminal =
193terminal.auto-open = first
194xwayland = xwayland-satellite
195
196# Appearance and layout
197background = #111111
198border.width = 2
199border.active = #808080
200border.inactive = #111111
201floating.dim = 0.3
202layout.gaps = 0
203layout.max-columns = 2
204layout.overflow = cascade
205stash.width = 80
206stash.gap = 2
207stash.dim = 0.5
208stash.capture = on
209stash.spawn-terminal = on
210
211# Input
212touchpad.natural-scroll = off
213touchpad.tap = on
214touchpad.accel-speed = 0
215touchpad.accel-profile = adaptive
216touchpad.left-handed = off
217touchpad.scroll-factor = 1
218mouse.natural-scroll = off
219mouse.accel-speed = 0
220mouse.accel-profile = adaptive
221mouse.left-handed = off
222cursor.hide-after = 5s
223
224# Focus and animations
225focus.follow-move = off
226animations = off
227animations.speed = normal
228focus.flash = on
229focus.flash-width = 2
230focus.flash-opacity = 0.6
231focus.flash-duration = 200ms
232
233# Rendering and logging
234render.direct-scanout = on
235render.tearing = on
236render.vrr = on
237render.vrr-flip-gap = 1ms
238performance.realtime = on
239log.level = info
240log.debug =
241
242# Launch, close and session
243bind.cmd+return = spawn-terminal
244bind.ctrl+cmd+space = spawn fuzzel
245bind.cmd+q = close-window
246bind.ctrl+alt+backspace = quit
247
248# Focus and move
249bind.cmd+left = focus-column-left
250bind.cmd+right = focus-column-right
251bind.cmd+up = focus-window-up
252bind.cmd+down = focus-window-down
253bind.cmd+h = focus-column-left
254bind.cmd+l = focus-column-right
255bind.cmd+k = focus-window-up
256bind.cmd+j = focus-window-down
257bind.cmd+shift+left = move-column-left
258bind.cmd+shift+right = move-column-right
259bind.cmd+shift+h = move-column-left
260bind.cmd+shift+l = move-column-right
261bind.cmd+shift+up = move-window-up
262bind.cmd+shift+down = move-window-down
263bind.cmd+shift+k = move-window-up
264bind.cmd+shift+j = move-window-down
265bind.cmd+bracketleft = consume-or-expel-window-left
266bind.cmd+bracketright = consume-or-expel-window-right
267
268# Resize and window modes
269bind.cmd+alt+left = set-column-width -1
270bind.cmd+alt+right = set-column-width +1
271bind.cmd+alt+up = set-window-height -10%
272bind.cmd+alt+down = set-window-height +10%
273bind.cmd+alt+h = set-column-width -1
274bind.cmd+alt+l = set-column-width +1
275bind.cmd+alt+k = set-window-height -10%
276bind.cmd+alt+j = set-window-height +10%
277bind.cmd+shift+space = toggle-floating
278bind.cmd+r = cycle-column-width
279bind.cmd+f = maximize-column
280bind.cmd+shift+f = toggle-fullscreen
281bind.cmd+s = toggle-stash-visible
282bind.cmd+shift+s = toggle-window-stash
283bind.cmd+o = toggle-overview
284# bind.cmd+g = hint
285bind.cmd+tab = switch-column-next
286bind.cmd+shift+tab = switch-column-prev
287
288# Workspaces
289bind.cmd+pageup = focus-workspace-prev
290bind.cmd+pagedown = focus-workspace-next
291bind.cmd+shift+pageup = move-column-to-workspace-prev
292bind.cmd+shift+pagedown = move-column-to-workspace-next
293bind.cmd+ctrl+shift+up = move-workspace-prev
294bind.cmd+ctrl+shift+down = move-workspace-next
295bind.cmd+ctrl+shift+k = move-workspace-prev
296bind.cmd+ctrl+shift+j = move-workspace-next
297bind.cmd+code:2 = focus-workspace 1
298bind.cmd+code:3 = focus-workspace 2
299bind.cmd+code:4 = focus-workspace 3
300bind.cmd+code:5 = focus-workspace 4
301bind.cmd+code:6 = focus-workspace 5
302bind.cmd+code:7 = focus-workspace 6
303bind.cmd+code:8 = focus-workspace 7
304bind.cmd+code:9 = focus-workspace 8
305bind.cmd+code:10 = focus-workspace 9
306bind.cmd+shift+code:2 = move-column-to-workspace 1
307bind.cmd+shift+code:3 = move-column-to-workspace 2
308bind.cmd+shift+code:4 = move-column-to-workspace 3
309bind.cmd+shift+code:5 = move-column-to-workspace 4
310bind.cmd+shift+code:6 = move-column-to-workspace 5
311bind.cmd+shift+code:7 = move-column-to-workspace 6
312bind.cmd+shift+code:8 = move-column-to-workspace 7
313bind.cmd+shift+code:9 = move-column-to-workspace 8
314bind.cmd+shift+code:10 = move-column-to-workspace 9
315
316# Monitors and scale
317bind.cmd+ctrl+left = focus-monitor-left
318bind.cmd+ctrl+right = focus-monitor-right
319bind.cmd+ctrl+h = focus-monitor-left
320bind.cmd+ctrl+l = focus-monitor-right
321bind.cmd+ctrl+shift+left = move-workspace-to-monitor-left
322bind.cmd+ctrl+shift+right = move-workspace-to-monitor-right
323bind.cmd+ctrl+shift+h = move-workspace-to-monitor-left
324bind.cmd+ctrl+shift+l = move-workspace-to-monitor-right
325bind.cmd+code:13 = scale-up
326bind.cmd+code:12 = scale-down
327```
328
329The focus flash marks the window, column or output an action changes with a brief
330inner border. Workspace switches and mouse focus do not trigger a flash.
331It runs when `animations = on`.
332
333Per-output defaults (replace `<name>` with a connector such as `DP-1`):
334
335```text
336output.<name> = preferred
337output.<name>.scale = 1
338output.<name>.transform = normal
339output.<name>.primary = off
340output.<name>.offset = 0
341output.<name>.hdr = off
342output.<name>.sdr-brightness = 203
343```
344
345Outputs 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.
346
347See [Configuration](docs/config.md) for accepted values and workspace options.
348
349</details>
350
351### Automatic terminal
352
353The command comes from `terminal`, then `$TERMINAL`, then `foot`.
354
355| `terminal.auto-open` | Behaviour |
356| --- | --- |
357| `first` | Open once on the initial numbered workspace |
358| `all` | Open on each visible empty workspace without predefined slots |
359| `off` | Do not open automatically |
360
361`--no-terminal` disables automatic opening for one run; `spawn-terminal` still works. Launched programs inherit `$SHELL`, `$EDITOR` and `$VISUAL`.
362
363## Documentation
364
365- [Configuration](docs/config.md): keys, actions, bindings and HDR.
366- [Desktop integration](docs/desktop.md): sessions, clipboard, input methods, X11 apps and scripting.
367- [Session locking](docs/session-lock.md): external lockers, recovery and security limits.
368- [Screen capture](docs/capture.md): protocols, indicator, executable allowlist and portals.
369- [Headless mode](docs/headless.md): screenshots, input scripts and input-method testing without a screen.
370- [Performance](docs/performance.md): VRR, real-time priority, profiling and frame stats.
371
372## Contributing
373
374Open an issue and wait for acceptance before sending a pull request, including for bug fixes. Pull requests without an accepted issue are closed.
375
376Before submitting, run `make check`, `make race`, `make mocks-check` and `staticcheck ./...`. See [AGENTS.md](AGENTS.md) for repository rules.
377
378## License
379
380[GNU General Public License v3.0](LICENSE).