detached 245c4bb

docs(readme): shorten prose and list configuration defaults (#136)

* docs(readme): shorten prose and list configuration defaults

* docs(readme): preserve original project motivation

* docs(readme): link companion tools and list Noctalia

* docs(readme): restore Go rationale position and heading

* docs(readme): describe layer-shell support without a client catalog

* docs(readme): restore personal tone in project rationale

* docs(readme): use a full stop in project rationale

* docs(readme): separate animations from resource usage

* docs(readme): ground Go rationale in input and frame constraints

* docs(readme): restore voice in intro, animations and Go rationale
commit
245c4bbb26d9b4f996f894f0fb3cbcbe985cd9b3
author
bnema · · 2026-10-05 10:25 UTC
committer
GitHub · · 2026-10-05 10:25 UTC
parent
b6be3a8
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
1 file · +268 -95
M README.md +268 -95
@@ -10,68 +10,116 @@
1010 <p align="center">A Wayland compositor that spends its frames on your apps, not on itself.</p>
1111
1212 > [!WARNING]
13-> **Early alpha, but stable enough for daily use.** NeferWL is developed and tested mostly on AMD CPUs and GPUs. NVIDIA support is incomplete and untested; expect bugs and breaking config changes.
14-
15----
13+> **Early alpha, but I use it every day.** Developed and tested mostly on AMD CPUs and GPUs. NVIDIA support is incomplete and untested. Expect bugs and breaking config changes.
1614
1715 ## Why NeferWL
1816
1917 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.
2018
21-It is small on purpose, and it will stay small. Drawing is kept to simple primitives: no blur, no shadows, no rounded corners, no themes. There is no built-in bar or wallpaper either; for those, bring your own: any layer-shell client works. Animations are experimental and off by default (`animations = on` turns them on); off, every change is instant and touchpad swipes switch on release. With them, three-finger swipes follow the fingers, windows and the view move with short springs (about 180 ms) when you scroll, switch workspaces, move columns or windows, resize them, drop a dragged tile or send a workspace to another monitor; windows fade in and out when they open, close or leave with the stash, the overview zooms its cards, and a brief pulse marks the window you just focused. They stay cheap: nothing runs while the screen is still, frames follow the display, only the region that moves is redrawn, apps are resized once, to their final size, and do not redraw during a transition, and nothing is copied or blurred. About 40–80 MB of RAM with two 4K monitors, and almost no CPU while the screen does not change.
19+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.
2220
23-## OK, but why Go?
21+Animations are experimental and off by default (`animations = on`). Off, every change is instant. On, three-finger swipes follow your fingers, and windows and the view glide on short springs (about 180 ms). Windows fade in and out, the overview zooms its cards, and a brief pulse marks the window you just focused. They stay cheap: nothing runs while the screen is still, only the region that moves is redrawn, and apps are resized once, to their final size.
2422
25-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.
23+About 40–80 MB of RAM with two 4K monitors, and almost no CPU while the screen does not change. See [Performance](docs/performance.md).
2624
27-### A compositor is a concurrency problem
25+## OK, but why Go?
2826
29-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.
27+Most Wayland compositors are written in C, C++ or Rust. The usual objection to Go is the garbage collector. Fair, so here is how NeferWL deals with it.
3028
31-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.**
29+A compositor is a pile of streams that never stop: input, client commits, one vblank per monitor, GPU fences, captures, hotplug. A slow client must not delay a flip, and rendering must not delay input. Go gives me cheap goroutines and channels to keep them apart. NeferWL follows one rule throughout: **each piece of state has one owner goroutine, and others talk to it over channels.**
3230
33-- **Input** runs on its own thread, reads libinput and sends events to core.
34-- **Core** owns windows, workspaces and focus. It turns input and client events into layouts and scenes.
35-- **The Wayland server** owns client connections. It sends client events and surface content, and receives commands back.
36-- **Each output** owns its DRM state, renders its scenes with Vulkan, commits frames and reports flips and presentation times.
37-- **Capture, config, idle, sessions and leases** each run in their own goroutine.
31+Input, core, the Wayland server and each output run independently. No mutex on window state, no lock ordering to get wrong, and the race detector checks every test. With CAP_SYS_NICE, input and output threads request real-time scheduling, so a busy CPU cannot delay a cursor move or a flip.
3832
39-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.
33+### Keeping GC away from frames
4034
41-### Keeping the garbage collector out of the way
35+- **Reuse memory on hot paths.** `make check` includes allocation guards for frame callbacks, presentation reports, DRM flips, Vulkan submissions and surface updates.
36+- **Keep buffers off the Go heap.** Client buffers live in shared or GPU memory. The live Go heap is only a few megabytes: playing 4K HDR video with 65 tiled subsurfaces, GC takes under 9 % of a process that itself uses 13 % of a core.
37+- **Build without cgo.** libwayland, Vulkan and libinput load at runtime through [purego](https://github.com/ebitengine/purego).
38+- **Render only changes.** Damage tracking limits redraws, opaque surfaces hide work behind them, and idle outputs do nothing.
39+- **Tune on real hardware.** Under VRR, cursor moves ride on the game's next frame, and game frames keep a short gap after each flip so the panel does not drop to its slowest rate.
4240
43-- **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.
44-- **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).
45-- **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.
46-- **Work only when something changes.** Damage tracking redraws only what changed, surfaces hidden behind opaque ones are skipped, and an idle output does nothing.
47-- **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.
41+A full build takes seconds, and testing, race detection, profiling and formatting come with Go. That makes it a pleasure to work on. For measurements and the rendering pipeline, see [Performance](docs/performance.md).
4842
49-### A pleasure to work on
43+### Reusable libraries
5044
51-A full build takes seconds, and tests, race detection, profiling and formatting come with the language. 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:
45+I like bringing more tools to the Go ecosystem, so NeferWL grew its own libraries along the way. Other Go projects can use them:
5246
5347 - [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.
48+- [purego-vulkan](https://github.com/bnema/purego-vulkan): Vulkan bindings generated from `vk.xml`, without cgo.
49+- [go-wayland-bindings](https://github.com/bnema/go-wayland-bindings): protocol bindings generated from upstream XML.
50+- [wlturbo](https://github.com/bnema/wlturbo): a Wayland client library.
51+- [neferclient](https://github.com/bnema/neferclient): a client toolkit for connections, outputs, surface roles, seats and dmabuf presentation.
5852
5953 ## Features
6054
61-- **Games.** A fullscreen window is scanned out directly: its buffer goes to the display without a composition pass. Wine runs natively on Wayland; Steam and other X11 clients run through xwayland-satellite. See [the performance path](#the-performance-path).
62-- **VRR.** Variable refresh rate turns on while a fullscreen window covers the output, so the display follows the game's frame rate. On by default (`render.vrr`).
63-- **Screen tearing.** A game in direct scanout that asks for tearing gets it, for the lowest input latency. On by default (`render.tearing`).
64-- **Explicit sync.** On by default for every client, with no flag to set. Client fences go straight to the GPU and to KMS, so frames are shown when they are ready, without implicit-sync stalls.
65-- **HDR.** HDR10 output on capable displays. HDR clients (games, browsers, video players) are shown at full range; SDR content is shown at a configured brightness.
66-- **Column tiling.** Columns scroll to the right, as in PaperWM and Niri, or stay on screen and split, as in Sway. Per output or per workspace.
67-- **Overview.** `cmd+o` by default (or a four-finger swipe up) shows the workspaces of a monitor as live, scaled previews. In `fixed` mode, windows hidden behind a maximized column show as a stack of cards behind it, so nothing is lost out of sight. Pick a window with the keyboard, the touchpad or a click.
68-- **Stash.** Set aside a window you do not need right now. Each workspace has its own stash, with no limit on the number of windows. By default, `cmd+s` (or a four-finger swipe down) shows it as a floating strip over your tiles, or hides it again (a four-finger swipe up hides it too, while it is shown); `cmd+shift+s` sends a window into the stash or back to its column.
69-- **Workspaces.** Numbered workspaces are created and removed as needed. Named workspaces declare their columns (width and command); NeferWL starts the commands and places each window.
70-- **Multi-monitor.** Each output has its own workspaces. When an output is unplugged its workspaces move to another one, and return when it is plugged back. Fractional scale per output.
71-- **External clients.** No built-in bar, launcher or notifications. Waybar, fuzzel, mako, nefercap, cliphist, swayidle, wlr-randr and input methods such as fcitx5 work through their standard protocols. See [Desktop integration](docs/desktop.md).
72-- **Simple config.** One flat `key = value` file, reloaded on save without closing windows. A bad line only warns and keeps its default, and `neferwl validate-config` checks a file before you use it. A JSON state file describes outputs, workspaces and windows for scripts.
73-
74-Config example:
55+### Display and games
56+
57+- **Direct scanout:** eligible fullscreen buffers go straight to the display, without composition.
58+- **VRR and tearing:** enabled by default for supported fullscreen use (`render.vrr`, `render.tearing`). Tearing requires direct scanout and a client request.
59+- **Explicit sync:** client fences go to the GPU and KMS. Enabled by default, with no flag to set.
60+- **HDR:** HDR10 on capable displays, with configurable brightness for SDR content.
61+- **Multi-monitor:** separate workspaces and fractional scale per output. Workspaces move when a monitor is unplugged and return when it reconnects.
62+- **X11 apps:** Steam and other X11 clients run through xwayland-satellite. Wine can run natively on Wayland.
63+
64+### Windows and workspaces
65+
66+- **Column tiling:** scroll horizontally, as in PaperWM and niri, or keep columns on screen in a fixed layout. Choose per output or workspace.
67+- **Overview:** `cmd+o` or a four-finger swipe up shows live workspace previews. Select a window with the keyboard, touchpad or mouse.
68+- **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.
69+- **Workspaces:** numbered workspaces appear as needed. Named workspaces can start commands and place their windows in predefined columns.
70+
71+### Desktop integration
72+
73+Bring your own desktop UI: any layer-shell client, such as Noctalia or Waybar, works. No built-in bar, launcher or notifications; input methods like fcitx5 work through their standard protocols. For capture and idle management, see [nefercap](https://github.com/bnema/nefercap) and [neferafk](https://github.com/bnema/neferafk).
74+
75+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](docs/desktop.md).
76+
77+## Install
78+
79+### Arch Linux
80+
81+Choose an AUR package:
82+
83+```sh
84+paru -S neferwl-bin # latest release, pre-built for x86_64 and aarch64
85+paru -S neferwl-git # latest main, built from source
86+```
87+
88+Or install from a checkout with Go 1.27:
89+
90+```sh
91+make install # build a package from HEAD and install it with pacman
92+```
93+
94+`make pkg` builds the package without installing it.
95+
96+The package provides:
97+
98+- `neferwl` with CAP_SYS_NICE for real-time scheduling.
99+- A **NeferWL** session for display managers such as Ly, GDM and SDDM.
100+- `neferwl-session`, which starts `neferwl.service` and manages `graphical-session.target` so desktop services start and stop with the session.
101+
102+Running `neferwl --session` directly also exports the display environment to D-Bus and systemd user services. See [Desktop integration](docs/desktop.md) for session setup.
103+
104+### Other distributions
105+
106+Download 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`.
107+
108+## Hardware support
109+
110+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.
111+
112+| GPU | Status |
113+| --- | --- |
114+| AMD | Developed and tested on radv |
115+| NVIDIA | Untested; explicit sync is implemented, but support is incomplete |
116+| Intel | Untested |
117+
118+Reports from NVIDIA and Intel users are welcome.
119+
120+## Configuration
121+
122+The default file is `$XDG_CONFIG_HOME/neferwl/config`, or `~/.config/neferwl/config`.
75123
76124 ```text
77125 keyboard.layout = fr
@@ -83,88 +131,213 @@ output.DP-1.scale = 1.5
83131
84132 layout.max-columns = 3
85133
86-# A bind-only dev workspace: editor and terminal, started and placed for you.
134+# Start and place an editor and terminal when this workspace is opened.
87135 workspace.dev.monitor = DP-1
88136 workspace.dev.column.1 = 67%, code --new-window
89137 workspace.dev.column.2 = 33%, foot
90138 bind.cmd+d = workspace dev
91139 ```
92140
93-The terminal command comes from `terminal`, then `$TERMINAL`, then `foot`. `terminal.auto-open = first` opens it once on the initial numbered workspace; `all` opens it on each visible empty workspace without slots, and `off` disables automatic opening. `--no-terminal` overrides automatic opening for one run; `spawn-terminal` still works. Launched programs inherit `$SHELL`, `$EDITOR`, and `$VISUAL`.
94-
95-## The performance path
141+See [Configuration](docs/config.md) for all keys, actions and default bindings, or [examples/config](examples/config) for commented defaults.
96142
97-- `zwp_linux_dmabuf_v1` v6: client buffers are sampled in place. Vulkan composes into exported scanout images and the frame fence goes to KMS as `IN_FENCE_FD`. No CPU copy, no CPU wait.
98-- `wp_single_pixel_buffer_v1`: solid backgrounds and letterbox bars are drawn as fills, without client buffers.
99-- Direct scanout of a fullscreen window's buffer; dmabuf feedback gives it a scanout-ready format first. A lone opaque window can use an overlay plane. On HDR outputs, SDR windows also bypass composition when the plane has a colour pipeline.
100-- `wp_tearing_control_v1` and VRR while a fullscreen window covers the output.
101-- `wp_linux_drm_syncobj_v1`: client fences go to KMS.
102-- `wp_presentation`, `wp_fifo_v1`, `wp_commit_timing_v1` for frame pacing.
103-- `wp_fractional_scale_v1` and `wp_viewporter`: clients render at the output scale, nothing is rescaled.
104-- Damage tracking, occlusion of surfaces behind opaque ones, no work on idle outputs. Frame, cursor, overlay and VRR in one atomic commit.
105-- Input is read on its own thread and never waits on rendering; the cursor has a hardware plane. With CAP_SYS_NICE, input and output threads request real-time scheduling. Relative pointer, pointer constraints and keyboard shortcuts inhibit for games. `zwp_input_timestamps_v1` gives clients microsecond input timestamps from libinput.
106-- `wp_drm_lease_v1` leases headset connectors to SteamVR and Monado.
107-
108-## Hardware support
143+- Use one `key = value` per line; `#` starts a comment.
144+- Missing keys use defaults. Invalid lines warn and keep that key's default; other lines still apply.
145+- Save to reload. The log lists changed keys.
146+- Use `neferwl --config <path>` to select a file, or `neferwl validate-config [path]` to check it without starting the compositor.
109147
110-NeferWL runs on Linux. It needs a GPU and kernel driver with atomic KMS, and a Vulkan 1.3 device that can export images as dmabufs. Without them, it stops at startup with an error.
148+### Default configuration
111149
112-- **AMD:** developed and tested on radv.
113-- **NVIDIA:** untested. Explicit sync, which NVIDIA drivers need, is implemented. Reports from NVIDIA users are welcome.
114-- **Intel:** untested.
150+<details>
151+<summary>All default settings and key bindings</summary>
115152
116-## Install (Arch Linux)
153+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.
117154
118-From the AUR:
119-
120-```sh
121-paru -S neferwl-bin # latest release, pre-built for x86_64 and aarch64
122-paru -S neferwl-git # latest main, built from source
155+```text
156+# Keyboard and commands
157+keyboard.layout =
158+keyboard.variant =
159+keyboard.options =
160+keyboard.repeat-rate = 25
161+keyboard.repeat-delay = 600
162+keyboard.cmd = super
163+terminal =
164+terminal.auto-open = first
165+xwayland = xwayland-satellite
166+
167+# Appearance and layout
168+background = #111111
169+border.width = 2
170+border.active = #808080
171+border.inactive = #111111
172+floating.dim = 0.3
173+layout.gaps = 0
174+layout.max-columns = 2
175+layout.overflow = scroll
176+layout.presets = 1/3, 1/2, 2/3, 1
177+stash.width = 80
178+stash.gap = 2
179+stash.dim = 0.5
180+stash.capture = on
181+
182+# Input
183+touchpad.natural-scroll = off
184+touchpad.tap = on
185+touchpad.accel-speed = 0
186+touchpad.accel-profile = adaptive
187+touchpad.left-handed = off
188+touchpad.scroll-factor = 1
189+mouse.natural-scroll = off
190+mouse.accel-speed = 0
191+mouse.accel-profile = adaptive
192+mouse.left-handed = off
193+cursor.hide-after = 5s
194+
195+# Focus and animations
196+focus.follow-move = off
197+animations = off
198+animations.speed = normal
199+focus.animation = pulse
200+focus.effect = screen
201+focus.strength = 0.04
202+
203+# Rendering and logging
204+render.direct-scanout = on
205+render.tearing = on
206+render.vrr = on
207+render.vrr-flip-gap = 1ms
208+performance.realtime = on
209+log.level = info
210+log.debug =
211+
212+# Launch, close and session
213+bind.cmd+return = spawn-terminal
214+bind.ctrl+cmd+space = spawn fuzzel
215+bind.cmd+q = close-window
216+bind.ctrl+alt+backspace = quit
217+
218+# Focus and move
219+bind.cmd+left = focus-column-left
220+bind.cmd+right = focus-column-right
221+bind.cmd+up = focus-window-up
222+bind.cmd+down = focus-window-down
223+bind.cmd+h = focus-column-left
224+bind.cmd+l = focus-column-right
225+bind.cmd+k = focus-window-up
226+bind.cmd+j = focus-window-down
227+bind.cmd+shift+left = move-column-left
228+bind.cmd+shift+right = move-column-right
229+bind.cmd+shift+h = move-column-left
230+bind.cmd+shift+l = move-column-right
231+bind.cmd+shift+up = move-window-up
232+bind.cmd+shift+down = move-window-down
233+bind.cmd+shift+k = move-window-up
234+bind.cmd+shift+j = move-window-down
235+bind.cmd+bracketleft = consume-or-expel-window-left
236+bind.cmd+bracketright = consume-or-expel-window-right
237+
238+# Resize and window modes
239+bind.cmd+alt+left = set-column-width -10%
240+bind.cmd+alt+right = set-column-width +10%
241+bind.cmd+alt+up = set-window-height -10%
242+bind.cmd+alt+down = set-window-height +10%
243+bind.cmd+alt+h = set-column-width -10%
244+bind.cmd+alt+l = set-column-width +10%
245+bind.cmd+alt+k = set-window-height -10%
246+bind.cmd+alt+j = set-window-height +10%
247+bind.cmd+shift+space = toggle-floating
248+bind.cmd+r = cycle-column-width
249+bind.cmd+f = maximize-column
250+bind.cmd+shift+f = toggle-fullscreen
251+bind.cmd+s = toggle-stash-visible
252+bind.cmd+shift+s = toggle-window-stash
253+bind.cmd+o = toggle-overview
254+
255+# Workspaces
256+bind.cmd+pageup = focus-workspace-up
257+bind.cmd+pagedown = focus-workspace-down
258+bind.cmd+shift+pageup = move-column-to-workspace-up
259+bind.cmd+shift+pagedown = move-column-to-workspace-down
260+bind.cmd+ctrl+shift+up = move-workspace-up
261+bind.cmd+ctrl+shift+down = move-workspace-down
262+bind.cmd+ctrl+shift+k = move-workspace-up
263+bind.cmd+ctrl+shift+j = move-workspace-down
264+bind.cmd+code:2 = focus-workspace 1
265+bind.cmd+code:3 = focus-workspace 2
266+bind.cmd+code:4 = focus-workspace 3
267+bind.cmd+code:5 = focus-workspace 4
268+bind.cmd+code:6 = focus-workspace 5
269+bind.cmd+code:7 = focus-workspace 6
270+bind.cmd+code:8 = focus-workspace 7
271+bind.cmd+code:9 = focus-workspace 8
272+bind.cmd+code:10 = focus-workspace 9
273+bind.cmd+shift+code:2 = move-column-to-workspace 1
274+bind.cmd+shift+code:3 = move-column-to-workspace 2
275+bind.cmd+shift+code:4 = move-column-to-workspace 3
276+bind.cmd+shift+code:5 = move-column-to-workspace 4
277+bind.cmd+shift+code:6 = move-column-to-workspace 5
278+bind.cmd+shift+code:7 = move-column-to-workspace 6
279+bind.cmd+shift+code:8 = move-column-to-workspace 7
280+bind.cmd+shift+code:9 = move-column-to-workspace 8
281+bind.cmd+shift+code:10 = move-column-to-workspace 9
282+
283+# Monitors and scale
284+bind.cmd+ctrl+left = focus-monitor-left
285+bind.cmd+ctrl+right = focus-monitor-right
286+bind.cmd+ctrl+h = focus-monitor-left
287+bind.cmd+ctrl+l = focus-monitor-right
288+bind.cmd+ctrl+shift+left = move-workspace-to-monitor-left
289+bind.cmd+ctrl+shift+right = move-workspace-to-monitor-right
290+bind.cmd+ctrl+shift+h = move-workspace-to-monitor-left
291+bind.cmd+ctrl+shift+l = move-workspace-to-monitor-right
292+bind.cmd+code:13 = scale-up
293+bind.cmd+code:12 = scale-down
123294 ```
124295
125-From a checkout (needs Go 1.27):
296+Per-output defaults (replace `<name>` with a connector such as `DP-1`):
126297
127-```sh
128-make install # builds dist/neferwl-*.pkg.tar.zst from HEAD, then installs it with pacman
298+```text
299+output.<name> = preferred
300+output.<name>.scale = 1
301+output.<name>.transform = normal
302+output.<name>.primary = off
303+output.<name>.offset = 0
304+output.<name>.hdr = off
305+output.<name>.sdr-brightness = 203
129306 ```
130307
131-`make pkg` only builds the package. The package installs `neferwl` with CAP_SYS_NICE (real-time scheduling, see [performance](docs/performance.md)) and a **NeferWL** session for display managers such as Ly, GDM and SDDM. The session runs `neferwl-session`, which starts `neferwl.service` under systemd. This starts `graphical-session.target` and services bound to it (bars, notification daemons, portals, and XDG autostart) with NeferWL and stops them with it. Running `neferwl --session` alone still exports the display environment to D-Bus and systemd user services.
308+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.
132309
133-On other distributions, download a `linux_amd64` or `linux_arm64` archive from [Releases](https://github.com/bnema/neferwl/releases) and check it against `checksums.txt`, or run `make bin` to build `bin/neferwl`.
310+See [Configuration](docs/config.md) for accepted values and workspace options.
134311
135-## Configuration
312+</details>
136313
137-NeferWL reads `$XDG_CONFIG_HOME/neferwl/config`, or `~/.config/neferwl/config`. `neferwl --config <path>` selects another file, and `neferwl validate-config [path]` checks a file without starting the compositor.
314+### Automatic terminal
138315
139-The file has one `key = value` per line, and `#` starts a comment. A missing file or key means the default. An invalid line logs a warning and keeps that key's default; the rest of the file still applies. Changes apply when you save, and each reload logs the keys that changed.
316+The command comes from `terminal`, then `$TERMINAL`, then `foot`.
140317
141-[docs/config.md](docs/config.md) lists every key, action and default bind. `examples/config` is a commented copy of the defaults.
318+| `terminal.auto-open` | Behaviour |
319+| --- | --- |
320+| `first` | Open once on the initial numbered workspace |
321+| `all` | Open on each visible empty workspace without predefined slots |
322+| `off` | Do not open automatically |
142323
143-## Documentation
324+`--no-terminal` disables automatic opening for one run; `spawn-terminal` still works. Launched programs inherit `$SHELL`, `$EDITOR` and `$VISUAL`.
144325
145-- [Configuration](docs/config.md): every key, action and default bind, and HDR.
146-- [Desktop integration](docs/desktop.md): running commands, clipboard, input methods, X11 apps, idle and screen off, bars, and the state file for scripts.
147-- [Session locking](docs/session-lock.md): external lockers, output confirmation, owner recovery and security limits.
148-- [Screen capture](docs/capture.md): capture protocols, the indicator, the executable allowlist, sandboxed clients and portals.
149-- [Headless mode](docs/headless.md): run without a screen, take screenshots, play input scripts and test input methods.
150-- [Performance](docs/performance.md): how to profile, the allocation guards, and reference numbers.
151-
152-## Developing the bindings
153-
154-NeferWL uses tagged releases of [purego-libwayland](https://github.com/bnema/purego-libwayland) and [purego-vulkan](https://github.com/bnema/purego-vulkan). To change a binding and NeferWL together, clone the binding next to NeferWL and use a Go workspace, which is not committed:
155-
156-```sh
157-go work init . ../purego-libwayland ../purego-vulkan
158-```
326+## Documentation
159327
160-`make pkg` ignores the workspace and builds the versions in `go.mod`. Tag the binding change and update `go.mod` before packaging.
328+- [Configuration](docs/config.md): keys, actions, bindings and HDR.
329+- [Desktop integration](docs/desktop.md): sessions, clipboard, input methods, X11 apps and scripting.
330+- [Session locking](docs/session-lock.md): external lockers, recovery and security limits.
331+- [Screen capture](docs/capture.md): protocols, indicator, executable allowlist and portals.
332+- [Headless mode](docs/headless.md): screenshots, input scripts and input-method testing without a screen.
333+- [Performance](docs/performance.md): rendering, frame pacing, profiling and reference measurements.
161334
162335 ## Contributing
163336
164-Contributions are welcome. Open an issue first, for a bug fix or a feature, and wait until it is accepted before you send a pull request. Pull requests without an accepted issue are closed.
337+Open an issue and wait for acceptance before sending a pull request, including for bug fixes. Pull requests without an accepted issue are closed.
165338
166-Before you open a pull request, `make check`, `make race`, `make mocks-check` and `staticcheck ./...` must pass. Repository rules are in [AGENTS.md](AGENTS.md).
339+Before submitting, run `make check`, `make race`, `make mocks-check` and `staticcheck ./...`. See [AGENTS.md](AGENTS.md) for repository rules.
167340
168341 ## License
169342
170-NeferWL is licensed under the [GNU General Public License v3.0](LICENSE).
343+[GNU General Public License v3.0](LICENSE).
@@ -10,68 +10,116 @@
10 <p align="center">A Wayland compositor that spends its frames on your apps, not on itself.</p>10 <p align="center">A Wayland compositor that spends its frames on your apps, not on itself.</p>
11 11
12 > [!WARNING]12 > [!WARNING]
13-> **Early alpha, but stable enough for daily use.** NeferWL is developed and tested mostly on AMD CPUs and GPUs. NVIDIA support is incomplete and untested; expect bugs and breaking config changes.13+> **Early alpha, but I use it every day.** Developed and tested mostly on AMD CPUs and GPUs. NVIDIA support is incomplete and untested. Expect bugs and breaking config changes.
14-
15----
16 14
17 ## Why NeferWL15 ## Why NeferWL
18 16
19 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.17 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.
20 18
21-It is small on purpose, and it will stay small. Drawing is kept to simple primitives: no blur, no shadows, no rounded corners, no themes. There is no built-in bar or wallpaper either; for those, bring your own: any layer-shell client works. Animations are experimental and off by default (`animations = on` turns them on); off, every change is instant and touchpad swipes switch on release. With them, three-finger swipes follow the fingers, windows and the view move with short springs (about 180 ms) when you scroll, switch workspaces, move columns or windows, resize them, drop a dragged tile or send a workspace to another monitor; windows fade in and out when they open, close or leave with the stash, the overview zooms its cards, and a brief pulse marks the window you just focused. They stay cheap: nothing runs while the screen is still, frames follow the display, only the region that moves is redrawn, apps are resized once, to their final size, and do not redraw during a transition, and nothing is copied or blurred. About 40–80 MB of RAM with two 4K monitors, and almost no CPU while the screen does not change.19+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.
22 20
23-## OK, but why Go?21+Animations are experimental and off by default (`animations = on`). Off, every change is instant. On, three-finger swipes follow your fingers, and windows and the view glide on short springs (about 180 ms). Windows fade in and out, the overview zooms its cards, and a brief pulse marks the window you just focused. They stay cheap: nothing runs while the screen is still, only the region that moves is redrawn, and apps are resized once, to their final size.
24 22
25-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.23+About 40–80 MB of RAM with two 4K monitors, and almost no CPU while the screen does not change. See [Performance](docs/performance.md).
26 24
27-### A compositor is a concurrency problem25+## OK, but why Go?
28 26
29-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.27+Most Wayland compositors are written in C, C++ or Rust. The usual objection to Go is the garbage collector. Fair, so here is how NeferWL deals with it.
30 28
31-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.**29+A compositor is a pile of streams that never stop: input, client commits, one vblank per monitor, GPU fences, captures, hotplug. A slow client must not delay a flip, and rendering must not delay input. Go gives me cheap goroutines and channels to keep them apart. NeferWL follows one rule throughout: **each piece of state has one owner goroutine, and others talk to it over channels.**
32 30
33-- **Input** runs on its own thread, reads libinput and sends events to core.31+Input, core, the Wayland server and each output run independently. No mutex on window state, no lock ordering to get wrong, and the race detector checks every test. With CAP_SYS_NICE, input and output threads request real-time scheduling, so a busy CPU cannot delay a cursor move or a flip.
34-- **Core** owns windows, workspaces and focus. It turns input and client events into layouts and scenes.
35-- **The Wayland server** owns client connections. It sends client events and surface content, and receives commands back.
36-- **Each output** owns its DRM state, renders its scenes with Vulkan, commits frames and reports flips and presentation times.
37-- **Capture, config, idle, sessions and leases** each run in their own goroutine.
38 32
39-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.33+### Keeping GC away from frames
40 34
41-### Keeping the garbage collector out of the way35+- **Reuse memory on hot paths.** `make check` includes allocation guards for frame callbacks, presentation reports, DRM flips, Vulkan submissions and surface updates.
36+- **Keep buffers off the Go heap.** Client buffers live in shared or GPU memory. The live Go heap is only a few megabytes: playing 4K HDR video with 65 tiled subsurfaces, GC takes under 9 % of a process that itself uses 13 % of a core.
37+- **Build without cgo.** libwayland, Vulkan and libinput load at runtime through [purego](https://github.com/ebitengine/purego).
38+- **Render only changes.** Damage tracking limits redraws, opaque surfaces hide work behind them, and idle outputs do nothing.
39+- **Tune on real hardware.** Under VRR, cursor moves ride on the game's next frame, and game frames keep a short gap after each flip so the panel does not drop to its slowest rate.
42 40
43-- **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.41+A full build takes seconds, and testing, race detection, profiling and formatting come with Go. That makes it a pleasure to work on. For measurements and the rendering pipeline, see [Performance](docs/performance.md).
44-- **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).
45-- **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.
46-- **Work only when something changes.** Damage tracking redraws only what changed, surfaces hidden behind opaque ones are skipped, and an idle output does nothing.
47-- **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.
48 42
49-### A pleasure to work on43+### Reusable libraries
50 44
51-A full build takes seconds, and tests, race detection, profiling and formatting come with the language. 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:45+I like bringing more tools to the Go ecosystem, so NeferWL grew its own libraries along the way. Other Go projects can use them:
52 46
53 - [purego-libwayland](https://github.com/bnema/purego-libwayland): libwayland-server without cgo.47 - [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`.48+- [purego-vulkan](https://github.com/bnema/purego-vulkan): Vulkan bindings generated from `vk.xml`, without cgo.
55-- [go-wayland-bindings](https://github.com/bnema/go-wayland-bindings): Wayland protocol bindings, generated from the upstream XML.49+- [go-wayland-bindings](https://github.com/bnema/go-wayland-bindings): protocol bindings generated from upstream XML.
56-- [wlturbo](https://github.com/bnema/wlturbo): a fast Wayland client.50+- [wlturbo](https://github.com/bnema/wlturbo): a Wayland client library.
57-- [neferclient](https://github.com/bnema/neferclient): a client toolkit on top of them: connection, outputs, surface roles, seat and dmabuf presentation.51+- [neferclient](https://github.com/bnema/neferclient): a client toolkit for connections, outputs, surface roles, seats and dmabuf presentation.
58 52
59 ## Features53 ## Features
60 54
61-- **Games.** A fullscreen window is scanned out directly: its buffer goes to the display without a composition pass. Wine runs natively on Wayland; Steam and other X11 clients run through xwayland-satellite. See [the performance path](#the-performance-path).55+### Display and games
62-- **VRR.** Variable refresh rate turns on while a fullscreen window covers the output, so the display follows the game's frame rate. On by default (`render.vrr`).56+
63-- **Screen tearing.** A game in direct scanout that asks for tearing gets it, for the lowest input latency. On by default (`render.tearing`).57+- **Direct scanout:** eligible fullscreen buffers go straight to the display, without composition.
64-- **Explicit sync.** On by default for every client, with no flag to set. Client fences go straight to the GPU and to KMS, so frames are shown when they are ready, without implicit-sync stalls.58+- **VRR and tearing:** enabled by default for supported fullscreen use (`render.vrr`, `render.tearing`). Tearing requires direct scanout and a client request.
65-- **HDR.** HDR10 output on capable displays. HDR clients (games, browsers, video players) are shown at full range; SDR content is shown at a configured brightness.59+- **Explicit sync:** client fences go to the GPU and KMS. Enabled by default, with no flag to set.
66-- **Column tiling.** Columns scroll to the right, as in PaperWM and Niri, or stay on screen and split, as in Sway. Per output or per workspace.60+- **HDR:** HDR10 on capable displays, with configurable brightness for SDR content.
67-- **Overview.** `cmd+o` by default (or a four-finger swipe up) shows the workspaces of a monitor as live, scaled previews. In `fixed` mode, windows hidden behind a maximized column show as a stack of cards behind it, so nothing is lost out of sight. Pick a window with the keyboard, the touchpad or a click.61+- **Multi-monitor:** separate workspaces and fractional scale per output. Workspaces move when a monitor is unplugged and return when it reconnects.
68-- **Stash.** Set aside a window you do not need right now. Each workspace has its own stash, with no limit on the number of windows. By default, `cmd+s` (or a four-finger swipe down) shows it as a floating strip over your tiles, or hides it again (a four-finger swipe up hides it too, while it is shown); `cmd+shift+s` sends a window into the stash or back to its column.62+- **X11 apps:** Steam and other X11 clients run through xwayland-satellite. Wine can run natively on Wayland.
69-- **Workspaces.** Numbered workspaces are created and removed as needed. Named workspaces declare their columns (width and command); NeferWL starts the commands and places each window.63+
70-- **Multi-monitor.** Each output has its own workspaces. When an output is unplugged its workspaces move to another one, and return when it is plugged back. Fractional scale per output.64+### Windows and workspaces
71-- **External clients.** No built-in bar, launcher or notifications. Waybar, fuzzel, mako, nefercap, cliphist, swayidle, wlr-randr and input methods such as fcitx5 work through their standard protocols. See [Desktop integration](docs/desktop.md).65+
72-- **Simple config.** One flat `key = value` file, reloaded on save without closing windows. A bad line only warns and keeps its default, and `neferwl validate-config` checks a file before you use it. A JSON state file describes outputs, workspaces and windows for scripts.66+- **Column tiling:** scroll horizontally, as in PaperWM and niri, or keep columns on screen in a fixed layout. Choose per output or workspace.
73-67+- **Overview:** `cmd+o` or a four-finger swipe up shows live workspace previews. Select a window with the keyboard, touchpad or mouse.
74-Config example:68+- **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.
69+- **Workspaces:** numbered workspaces appear as needed. Named workspaces can start commands and place their windows in predefined columns.
70+
71+### Desktop integration
72+
73+Bring your own desktop UI: any layer-shell client, such as Noctalia or Waybar, works. No built-in bar, launcher or notifications; input methods like fcitx5 work through their standard protocols. For capture and idle management, see [nefercap](https://github.com/bnema/nefercap) and [neferafk](https://github.com/bnema/neferafk).
74+
75+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](docs/desktop.md).
76+
77+## Install
78+
79+### Arch Linux
80+
81+Choose an AUR package:
82+
83+```sh
84+paru -S neferwl-bin # latest release, pre-built for x86_64 and aarch64
85+paru -S neferwl-git # latest main, built from source
86+```
87+
88+Or install from a checkout with Go 1.27:
89+
90+```sh
91+make install # build a package from HEAD and install it with pacman
92+```
93+
94+`make pkg` builds the package without installing it.
95+
96+The package provides:
97+
98+- `neferwl` with CAP_SYS_NICE for real-time scheduling.
99+- A **NeferWL** session for display managers such as Ly, GDM and SDDM.
100+- `neferwl-session`, which starts `neferwl.service` and manages `graphical-session.target` so desktop services start and stop with the session.
101+
102+Running `neferwl --session` directly also exports the display environment to D-Bus and systemd user services. See [Desktop integration](docs/desktop.md) for session setup.
103+
104+### Other distributions
105+
106+Download 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`.
107+
108+## Hardware support
109+
110+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.
111+
112+| GPU | Status |
113+| --- | --- |
114+| AMD | Developed and tested on radv |
115+| NVIDIA | Untested; explicit sync is implemented, but support is incomplete |
116+| Intel | Untested |
117+
118+Reports from NVIDIA and Intel users are welcome.
119+
120+## Configuration
121+
122+The default file is `$XDG_CONFIG_HOME/neferwl/config`, or `~/.config/neferwl/config`.
75 123
76 ```text124 ```text
77 keyboard.layout = fr125 keyboard.layout = fr
@@ -83,88 +131,213 @@ output.DP-1.scale = 1.5
83 131
84 layout.max-columns = 3132 layout.max-columns = 3
85 133
86-# A bind-only dev workspace: editor and terminal, started and placed for you.134+# Start and place an editor and terminal when this workspace is opened.
87 workspace.dev.monitor = DP-1135 workspace.dev.monitor = DP-1
88 workspace.dev.column.1 = 67%, code --new-window136 workspace.dev.column.1 = 67%, code --new-window
89 workspace.dev.column.2 = 33%, foot137 workspace.dev.column.2 = 33%, foot
90 bind.cmd+d = workspace dev138 bind.cmd+d = workspace dev
91 ```139 ```
92 140
93-The terminal command comes from `terminal`, then `$TERMINAL`, then `foot`. `terminal.auto-open = first` opens it once on the initial numbered workspace; `all` opens it on each visible empty workspace without slots, and `off` disables automatic opening. `--no-terminal` overrides automatic opening for one run; `spawn-terminal` still works. Launched programs inherit `$SHELL`, `$EDITOR`, and `$VISUAL`.141+See [Configuration](docs/config.md) for all keys, actions and default bindings, or [examples/config](examples/config) for commented defaults.
94-
95-## The performance path
96 142
97-- `zwp_linux_dmabuf_v1` v6: client buffers are sampled in place. Vulkan composes into exported scanout images and the frame fence goes to KMS as `IN_FENCE_FD`. No CPU copy, no CPU wait.143+- Use one `key = value` per line; `#` starts a comment.
98-- `wp_single_pixel_buffer_v1`: solid backgrounds and letterbox bars are drawn as fills, without client buffers.144+- Missing keys use defaults. Invalid lines warn and keep that key's default; other lines still apply.
99-- Direct scanout of a fullscreen window's buffer; dmabuf feedback gives it a scanout-ready format first. A lone opaque window can use an overlay plane. On HDR outputs, SDR windows also bypass composition when the plane has a colour pipeline.145+- Save to reload. The log lists changed keys.
100-- `wp_tearing_control_v1` and VRR while a fullscreen window covers the output.146+- Use `neferwl --config <path>` to select a file, or `neferwl validate-config [path]` to check it without starting the compositor.
101-- `wp_linux_drm_syncobj_v1`: client fences go to KMS.
102-- `wp_presentation`, `wp_fifo_v1`, `wp_commit_timing_v1` for frame pacing.
103-- `wp_fractional_scale_v1` and `wp_viewporter`: clients render at the output scale, nothing is rescaled.
104-- Damage tracking, occlusion of surfaces behind opaque ones, no work on idle outputs. Frame, cursor, overlay and VRR in one atomic commit.
105-- Input is read on its own thread and never waits on rendering; the cursor has a hardware plane. With CAP_SYS_NICE, input and output threads request real-time scheduling. Relative pointer, pointer constraints and keyboard shortcuts inhibit for games. `zwp_input_timestamps_v1` gives clients microsecond input timestamps from libinput.
106-- `wp_drm_lease_v1` leases headset connectors to SteamVR and Monado.
107-
108-## Hardware support
109 147
110-NeferWL runs on Linux. It needs a GPU and kernel driver with atomic KMS, and a Vulkan 1.3 device that can export images as dmabufs. Without them, it stops at startup with an error.148+### Default configuration
111 149
112-- **AMD:** developed and tested on radv.150+<details>
113-- **NVIDIA:** untested. Explicit sync, which NVIDIA drivers need, is implemented. Reports from NVIDIA users are welcome.151+<summary>All default settings and key bindings</summary>
114-- **Intel:** untested.
115 152
116-## Install (Arch Linux)153+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.
117 154
118-From the AUR:155+```text
119-156+# Keyboard and commands
120-```sh157+keyboard.layout =
121-paru -S neferwl-bin # latest release, pre-built for x86_64 and aarch64158+keyboard.variant =
122-paru -S neferwl-git # latest main, built from source159+keyboard.options =
160+keyboard.repeat-rate = 25
161+keyboard.repeat-delay = 600
162+keyboard.cmd = super
163+terminal =
164+terminal.auto-open = first
165+xwayland = xwayland-satellite
166+
167+# Appearance and layout
168+background = #111111
169+border.width = 2
170+border.active = #808080
171+border.inactive = #111111
172+floating.dim = 0.3
173+layout.gaps = 0
174+layout.max-columns = 2
175+layout.overflow = scroll
176+layout.presets = 1/3, 1/2, 2/3, 1
177+stash.width = 80
178+stash.gap = 2
179+stash.dim = 0.5
180+stash.capture = on
181+
182+# Input
183+touchpad.natural-scroll = off
184+touchpad.tap = on
185+touchpad.accel-speed = 0
186+touchpad.accel-profile = adaptive
187+touchpad.left-handed = off
188+touchpad.scroll-factor = 1
189+mouse.natural-scroll = off
190+mouse.accel-speed = 0
191+mouse.accel-profile = adaptive
192+mouse.left-handed = off
193+cursor.hide-after = 5s
194+
195+# Focus and animations
196+focus.follow-move = off
197+animations = off
198+animations.speed = normal
199+focus.animation = pulse
200+focus.effect = screen
201+focus.strength = 0.04
202+
203+# Rendering and logging
204+render.direct-scanout = on
205+render.tearing = on
206+render.vrr = on
207+render.vrr-flip-gap = 1ms
208+performance.realtime = on
209+log.level = info
210+log.debug =
211+
212+# Launch, close and session
213+bind.cmd+return = spawn-terminal
214+bind.ctrl+cmd+space = spawn fuzzel
215+bind.cmd+q = close-window
216+bind.ctrl+alt+backspace = quit
217+
218+# Focus and move
219+bind.cmd+left = focus-column-left
220+bind.cmd+right = focus-column-right
221+bind.cmd+up = focus-window-up
222+bind.cmd+down = focus-window-down
223+bind.cmd+h = focus-column-left
224+bind.cmd+l = focus-column-right
225+bind.cmd+k = focus-window-up
226+bind.cmd+j = focus-window-down
227+bind.cmd+shift+left = move-column-left
228+bind.cmd+shift+right = move-column-right
229+bind.cmd+shift+h = move-column-left
230+bind.cmd+shift+l = move-column-right
231+bind.cmd+shift+up = move-window-up
232+bind.cmd+shift+down = move-window-down
233+bind.cmd+shift+k = move-window-up
234+bind.cmd+shift+j = move-window-down
235+bind.cmd+bracketleft = consume-or-expel-window-left
236+bind.cmd+bracketright = consume-or-expel-window-right
237+
238+# Resize and window modes
239+bind.cmd+alt+left = set-column-width -10%
240+bind.cmd+alt+right = set-column-width +10%
241+bind.cmd+alt+up = set-window-height -10%
242+bind.cmd+alt+down = set-window-height +10%
243+bind.cmd+alt+h = set-column-width -10%
244+bind.cmd+alt+l = set-column-width +10%
245+bind.cmd+alt+k = set-window-height -10%
246+bind.cmd+alt+j = set-window-height +10%
247+bind.cmd+shift+space = toggle-floating
248+bind.cmd+r = cycle-column-width
249+bind.cmd+f = maximize-column
250+bind.cmd+shift+f = toggle-fullscreen
251+bind.cmd+s = toggle-stash-visible
252+bind.cmd+shift+s = toggle-window-stash
253+bind.cmd+o = toggle-overview
254+
255+# Workspaces
256+bind.cmd+pageup = focus-workspace-up
257+bind.cmd+pagedown = focus-workspace-down
258+bind.cmd+shift+pageup = move-column-to-workspace-up
259+bind.cmd+shift+pagedown = move-column-to-workspace-down
260+bind.cmd+ctrl+shift+up = move-workspace-up
261+bind.cmd+ctrl+shift+down = move-workspace-down
262+bind.cmd+ctrl+shift+k = move-workspace-up
263+bind.cmd+ctrl+shift+j = move-workspace-down
264+bind.cmd+code:2 = focus-workspace 1
265+bind.cmd+code:3 = focus-workspace 2
266+bind.cmd+code:4 = focus-workspace 3
267+bind.cmd+code:5 = focus-workspace 4
268+bind.cmd+code:6 = focus-workspace 5
269+bind.cmd+code:7 = focus-workspace 6
270+bind.cmd+code:8 = focus-workspace 7
271+bind.cmd+code:9 = focus-workspace 8
272+bind.cmd+code:10 = focus-workspace 9
273+bind.cmd+shift+code:2 = move-column-to-workspace 1
274+bind.cmd+shift+code:3 = move-column-to-workspace 2
275+bind.cmd+shift+code:4 = move-column-to-workspace 3
276+bind.cmd+shift+code:5 = move-column-to-workspace 4
277+bind.cmd+shift+code:6 = move-column-to-workspace 5
278+bind.cmd+shift+code:7 = move-column-to-workspace 6
279+bind.cmd+shift+code:8 = move-column-to-workspace 7
280+bind.cmd+shift+code:9 = move-column-to-workspace 8
281+bind.cmd+shift+code:10 = move-column-to-workspace 9
282+
283+# Monitors and scale
284+bind.cmd+ctrl+left = focus-monitor-left
285+bind.cmd+ctrl+right = focus-monitor-right
286+bind.cmd+ctrl+h = focus-monitor-left
287+bind.cmd+ctrl+l = focus-monitor-right
288+bind.cmd+ctrl+shift+left = move-workspace-to-monitor-left
289+bind.cmd+ctrl+shift+right = move-workspace-to-monitor-right
290+bind.cmd+ctrl+shift+h = move-workspace-to-monitor-left
291+bind.cmd+ctrl+shift+l = move-workspace-to-monitor-right
292+bind.cmd+code:13 = scale-up
293+bind.cmd+code:12 = scale-down
123 ```294 ```
124 295
125-From a checkout (needs Go 1.27):296+Per-output defaults (replace `<name>` with a connector such as `DP-1`):
126 297
127-```sh298+```text
128-make install # builds dist/neferwl-*.pkg.tar.zst from HEAD, then installs it with pacman299+output.<name> = preferred
300+output.<name>.scale = 1
301+output.<name>.transform = normal
302+output.<name>.primary = off
303+output.<name>.offset = 0
304+output.<name>.hdr = off
305+output.<name>.sdr-brightness = 203
129 ```306 ```
130 307
131-`make pkg` only builds the package. The package installs `neferwl` with CAP_SYS_NICE (real-time scheduling, see [performance](docs/performance.md)) and a **NeferWL** session for display managers such as Ly, GDM and SDDM. The session runs `neferwl-session`, which starts `neferwl.service` under systemd. This starts `graphical-session.target` and services bound to it (bars, notification daemons, portals, and XDG autostart) with NeferWL and stops them with it. Running `neferwl --session` alone still exports the display environment to D-Bus and systemd user services.308+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.
132 309
133-On other distributions, download a `linux_amd64` or `linux_arm64` archive from [Releases](https://github.com/bnema/neferwl/releases) and check it against `checksums.txt`, or run `make bin` to build `bin/neferwl`.310+See [Configuration](docs/config.md) for accepted values and workspace options.
134 311
135-## Configuration312+</details>
136 313
137-NeferWL reads `$XDG_CONFIG_HOME/neferwl/config`, or `~/.config/neferwl/config`. `neferwl --config <path>` selects another file, and `neferwl validate-config [path]` checks a file without starting the compositor.314+### Automatic terminal
138 315
139-The file has one `key = value` per line, and `#` starts a comment. A missing file or key means the default. An invalid line logs a warning and keeps that key's default; the rest of the file still applies. Changes apply when you save, and each reload logs the keys that changed.316+The command comes from `terminal`, then `$TERMINAL`, then `foot`.
140 317
141-[docs/config.md](docs/config.md) lists every key, action and default bind. `examples/config` is a commented copy of the defaults.318+| `terminal.auto-open` | Behaviour |
319+| --- | --- |
320+| `first` | Open once on the initial numbered workspace |
321+| `all` | Open on each visible empty workspace without predefined slots |
322+| `off` | Do not open automatically |
142 323
143-## Documentation324+`--no-terminal` disables automatic opening for one run; `spawn-terminal` still works. Launched programs inherit `$SHELL`, `$EDITOR` and `$VISUAL`.
144 325
145-- [Configuration](docs/config.md): every key, action and default bind, and HDR.326+## Documentation
146-- [Desktop integration](docs/desktop.md): running commands, clipboard, input methods, X11 apps, idle and screen off, bars, and the state file for scripts.
147-- [Session locking](docs/session-lock.md): external lockers, output confirmation, owner recovery and security limits.
148-- [Screen capture](docs/capture.md): capture protocols, the indicator, the executable allowlist, sandboxed clients and portals.
149-- [Headless mode](docs/headless.md): run without a screen, take screenshots, play input scripts and test input methods.
150-- [Performance](docs/performance.md): how to profile, the allocation guards, and reference numbers.
151-
152-## Developing the bindings
153-
154-NeferWL uses tagged releases of [purego-libwayland](https://github.com/bnema/purego-libwayland) and [purego-vulkan](https://github.com/bnema/purego-vulkan). To change a binding and NeferWL together, clone the binding next to NeferWL and use a Go workspace, which is not committed:
155-
156-```sh
157-go work init . ../purego-libwayland ../purego-vulkan
158-```
159 327
160-`make pkg` ignores the workspace and builds the versions in `go.mod`. Tag the binding change and update `go.mod` before packaging.328+- [Configuration](docs/config.md): keys, actions, bindings and HDR.
329+- [Desktop integration](docs/desktop.md): sessions, clipboard, input methods, X11 apps and scripting.
330+- [Session locking](docs/session-lock.md): external lockers, recovery and security limits.
331+- [Screen capture](docs/capture.md): protocols, indicator, executable allowlist and portals.
332+- [Headless mode](docs/headless.md): screenshots, input scripts and input-method testing without a screen.
333+- [Performance](docs/performance.md): rendering, frame pacing, profiling and reference measurements.
161 334
162 ## Contributing335 ## Contributing
163 336
164-Contributions are welcome. Open an issue first, for a bug fix or a feature, and wait until it is accepted before you send a pull request. Pull requests without an accepted issue are closed.337+Open an issue and wait for acceptance before sending a pull request, including for bug fixes. Pull requests without an accepted issue are closed.
165 338
166-Before you open a pull request, `make check`, `make race`, `make mocks-check` and `staticcheck ./...` must pass. Repository rules are in [AGENTS.md](AGENTS.md).339+Before submitting, run `make check`, `make race`, `make mocks-check` and `staticcheck ./...`. See [AGENTS.md](AGENTS.md) for repository rules.
167 340
168 ## License341 ## License
169 342
170-NeferWL is licensed under the [GNU General Public License v3.0](LICENSE).343+[GNU General Public License v3.0](LICENSE).

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