detached b2b939a

docs(readme): lead with performance and configuration

commit
b2b939a1f52ffc924a788109db6b383370c2911f
author
brice · · 2026-10-21 04:37 UTC
parent
ba1d5fd
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
4 files · +153 -260
M README.md +78 -260
@@ -4,90 +4,35 @@
44 <a href="LICENSE"><img src="https://img.shields.io/badge/license-GPLv3-blue?style=flat-square" alt="License: GPLv3"></a>
55 <a href="https://github.com/bnema/neferwl"><img src="https://img.shields.io/badge/platform-Linux-blue?style=flat-square" alt="Platform: Linux"></a>
66 <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>
87 </p>
98
109 <p align="center">A Wayland compositor that spends its frames on your apps, not on itself.</p>
1110
11+<!-- demo: docs/media/demo.mp4 -->
12+_Demo video coming soon._
13+
1214 > [!WARNING]
1315 > **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.
1416
1517 ## Why NeferWL
1618
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.
18-
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.
20-
21-Animations are short, cheap and off by default (experimental, `animations = on`).
22-
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).
24-
25-## OK, but why Go?
26-
27-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.
28-
29-### A compositor is a concurrency problem
30-
31-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.
32-
33-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.**
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-
41-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.
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-
51-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:
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.
19+- **Performance:** allocation-free hot paths, no wasted CPU passes and no cgo; about 40–80 MB of RAM with two 4K monitors. On 4K HDR video, GC takes under 9% of a process using 13% of a core; see [Architecture](docs/architecture.md#keeping-the-garbage-collector-out-of-the-way) and [Performance](docs/performance.md).
20+- **Games:** direct scanout, VRR, tearing, explicit sync and [HDR](docs/config.md#hdr) give games the whole GPU instead of spending it on composition; see [Performance](docs/performance.md).
21+- **Overview:** `cmd+o` or a four-finger swipe up shows live workspace previews. Select a window with the keyboard, touchpad or mouse; see [Overview](docs/config.md#overview).
22+- **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. See [Stash](docs/config.md#stash).
23+- **Hint mode:** click links and buttons by typing their labels, without reaching for the mouse. Enable experimental `hint.enable` and add a bind; see [Hint mode](docs/config.md#hint-mode).
24+- **Animations:** short, cheap and off by default. Opt in with experimental `animations = on`; see [Configuration](docs/config.md#keys).
5825
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.
26+Go fits a compositor's independent event streams: each piece of state has one owner goroutine, connected to others by channels.<br>
27+Hot paths reuse memory, client buffers stay off the Go heap, and runtime library calls use purego rather than cgo.<br>
28+See [Architecture](docs/architecture.md) for the concurrency model, GC strategy and reusable Go libraries.
7029
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-
81-Bring 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-
83-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).
30+Small on purpose, and staying small: no blur, shadows, rounded corners or themes. No built-in bar or wallpaper either: bring your own, any layer-shell client works.
8431
8532 ## Install
8633
8734 ### Arch Linux
8835
89-Choose an AUR package:
90-
9136 ```sh
9237 paru -S neferwl-bin # latest release, pre-built for x86_64 and aarch64
9338 paru -S neferwl-git # latest main, built from source
@@ -110,17 +55,13 @@ sha256sum -c --ignore-missing checksums.txt
11055 sudo apt install ./neferwl_<version>_amd64.deb
11156 ```
11257
113-Optional tools:
114-
11558 ```sh
11659 sudo apt install foot fuzzel xdg-desktop-portal-gtk xdg-desktop-portal-gnome
11760 ```
11861
11962 X11 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.
12063
121-### What the packages provide
122-
123-The Arch and Debian packages provide:
64+The packages provide:
12465
12566 - `neferwl` with CAP_SYS_NICE for real-time scheduling.
12667 - A **NeferWL** session for display managers such as Ly, GDM and SDDM.
@@ -134,18 +75,6 @@ Copy `/usr/share/doc/neferwl/config.example` to `~/.config/neferwl/config` to st
13475
13576 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`.
13677
137-## Hardware support
138-
139-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.
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-
147-Reports from NVIDIA and Intel users are welcome.
148-
14978 ## Configuration
15079
15180 The default file is `$XDG_CONFIG_HOME/neferwl/config`, or `~/.config/neferwl/config`.
@@ -167,201 +96,90 @@ workspace.dev.column.2 = 33%, foot
16796 bind.cmd+d = workspace dev
16897 ```
16998
170-See [Configuration](docs/config.md) for all keys, actions and default bindings, or [examples/config](examples/config) for commented defaults.
171-
17299 - Use one `key = value` per line; `#` starts a comment.
173100 - Missing keys use defaults. Invalid lines warn and keep that key's default; other lines still apply.
174101 - Save to reload. The log lists changed keys.
175102 - Use `neferwl --config <path>` to select a file, or `neferwl validate-config [path]` to check it without starting the compositor.
176103
177-### Default configuration
178-
179-<details>
180-<summary>All default settings and key bindings</summary>
104+### Common settings
105+
106+| Key | Default | Meaning |
107+| --- | --- | --- |
108+| `keyboard.layout` | system | xkb layout, e.g. `fr` |
109+| `keyboard.cmd` | `super` | Modifier used as `cmd` |
110+| `terminal` | `$TERMINAL`, then `foot` | Terminal command |
111+| `terminal.auto-open` | `first` | Open on initial numbered workspace; also `all` or `off` |
112+| `startup` | none | Session-start commands, separated by `;` |
113+| `layout.max-columns` | `2` | Columns sharing the screen |
114+| `layout.overflow` | `cascade` | Overflow layout: `cascade`, `scroll`, `fixed` |
115+| `layout.gaps` | `0` | Space between windows |
116+| `output.<name>` | `preferred` | Mode: `WxH@Hz`, `preferred` or `off` |
117+| `output.<name>.scale` | `1` | Fractional scale, 1 to 4 |
118+| `output.<name>.hdr` | `off` | HDR on capable displays |
119+| `touchpad.natural-scroll` | `off` | Content follows fingers |
120+| `stash.width` | `80` | Selected stash window width, percent |
121+| `hint.enable` | `off` | Experimental keyboard clicking; restart required |
122+| `animations` | `off` | Experimental layout and swipe animations |
123+
124+### Default key bindings
125+
126+`cmd` means Super by default. Hint mode has no default binding.
127+
128+| Keys | Action |
129+| --- | --- |
130+| `cmd+return` | Open terminal |
131+| `ctrl+cmd+space` | Launch fuzzel |
132+| `cmd+q` | Close window |
133+| `cmd+arrows` / `cmd+h/j/k/l` | Focus columns and windows |
134+| `cmd+shift+arrows` | Move columns and windows |
135+| `cmd+o` | Toggle overview |
136+| `cmd+s` | Show/hide stash |
137+| `cmd+shift+s` | Move window into/out of stash |
138+| `cmd+shift+f` | Toggle fullscreen |
139+| `cmd+shift+space` | Toggle floating |
140+| `cmd+1`…`9` (physical digit keys) | Focus numbered workspace |
141+| `cmd+pageup/pagedown` | Previous/next workspace |
142+
143+See [Configuration](docs/config.md) for all keys, actions, bindings and automatic-terminal behavior, or [examples/config](examples/config) for commented defaults.
181144
182-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.
145+## Features
183146
184-```text
185-# Keyboard and commands
186-keyboard.layout =
187-keyboard.variant =
188-keyboard.options =
189-keyboard.repeat-rate = 25
190-keyboard.repeat-delay = 600
191-keyboard.cmd = super
192-terminal =
193-terminal.auto-open = first
194-xwayland = xwayland-satellite
195-
196-# Appearance and layout
197-background = #111111
198-border.width = 2
199-border.active = #808080
200-border.inactive = #111111
201-floating.dim = 0.3
202-layout.gaps = 0
203-layout.max-columns = 2
204-layout.overflow = cascade
205-stash.width = 80
206-stash.gap = 2
207-stash.dim = 0.5
208-stash.capture = on
209-stash.spawn-terminal = on
210-
211-# Input
212-touchpad.natural-scroll = off
213-touchpad.tap = on
214-touchpad.accel-speed = 0
215-touchpad.accel-profile = adaptive
216-touchpad.left-handed = off
217-touchpad.scroll-factor = 1
218-mouse.natural-scroll = off
219-mouse.accel-speed = 0
220-mouse.accel-profile = adaptive
221-mouse.left-handed = off
222-cursor.hide-after = 5s
223-
224-# Focus and animations
225-focus.follow-move = off
226-animations = off
227-animations.speed = normal
228-focus.flash = on
229-focus.flash-width = 2
230-focus.flash-opacity = 0.6
231-focus.flash-duration = 200ms
232-
233-# Rendering and logging
234-render.direct-scanout = on
235-render.tearing = on
236-render.vrr = on
237-render.vrr-flip-gap = 1ms
238-performance.realtime = on
239-log.level = info
240-log.debug =
241-
242-# Launch, close and session
243-bind.cmd+return = spawn-terminal
244-bind.ctrl+cmd+space = spawn fuzzel
245-bind.cmd+q = close-window
246-bind.ctrl+alt+backspace = quit
247-
248-# Focus and move
249-bind.cmd+left = focus-column-left
250-bind.cmd+right = focus-column-right
251-bind.cmd+up = focus-window-up
252-bind.cmd+down = focus-window-down
253-bind.cmd+h = focus-column-left
254-bind.cmd+l = focus-column-right
255-bind.cmd+k = focus-window-up
256-bind.cmd+j = focus-window-down
257-bind.cmd+shift+left = move-column-left
258-bind.cmd+shift+right = move-column-right
259-bind.cmd+shift+h = move-column-left
260-bind.cmd+shift+l = move-column-right
261-bind.cmd+shift+up = move-window-up
262-bind.cmd+shift+down = move-window-down
263-bind.cmd+shift+k = move-window-up
264-bind.cmd+shift+j = move-window-down
265-bind.cmd+bracketleft = consume-or-expel-window-left
266-bind.cmd+bracketright = consume-or-expel-window-right
267-
268-# Resize and window modes
269-bind.cmd+alt+left = set-column-width -1
270-bind.cmd+alt+right = set-column-width +1
271-bind.cmd+alt+up = set-window-height -10%
272-bind.cmd+alt+down = set-window-height +10%
273-bind.cmd+alt+h = set-column-width -1
274-bind.cmd+alt+l = set-column-width +1
275-bind.cmd+alt+k = set-window-height -10%
276-bind.cmd+alt+j = set-window-height +10%
277-bind.cmd+shift+space = toggle-floating
278-bind.cmd+r = cycle-column-width
279-bind.cmd+f = maximize-column
280-bind.cmd+shift+f = toggle-fullscreen
281-bind.cmd+s = toggle-stash-visible
282-bind.cmd+shift+s = toggle-window-stash
283-bind.cmd+o = toggle-overview
284-# bind.cmd+g = hint
285-bind.cmd+tab = switch-column-next
286-bind.cmd+shift+tab = switch-column-prev
287-
288-# Workspaces
289-bind.cmd+pageup = focus-workspace-prev
290-bind.cmd+pagedown = focus-workspace-next
291-bind.cmd+shift+pageup = move-column-to-workspace-prev
292-bind.cmd+shift+pagedown = move-column-to-workspace-next
293-bind.cmd+ctrl+shift+up = move-workspace-prev
294-bind.cmd+ctrl+shift+down = move-workspace-next
295-bind.cmd+ctrl+shift+k = move-workspace-prev
296-bind.cmd+ctrl+shift+j = move-workspace-next
297-bind.cmd+code:2 = focus-workspace 1
298-bind.cmd+code:3 = focus-workspace 2
299-bind.cmd+code:4 = focus-workspace 3
300-bind.cmd+code:5 = focus-workspace 4
301-bind.cmd+code:6 = focus-workspace 5
302-bind.cmd+code:7 = focus-workspace 6
303-bind.cmd+code:8 = focus-workspace 7
304-bind.cmd+code:9 = focus-workspace 8
305-bind.cmd+code:10 = focus-workspace 9
306-bind.cmd+shift+code:2 = move-column-to-workspace 1
307-bind.cmd+shift+code:3 = move-column-to-workspace 2
308-bind.cmd+shift+code:4 = move-column-to-workspace 3
309-bind.cmd+shift+code:5 = move-column-to-workspace 4
310-bind.cmd+shift+code:6 = move-column-to-workspace 5
311-bind.cmd+shift+code:7 = move-column-to-workspace 6
312-bind.cmd+shift+code:8 = move-column-to-workspace 7
313-bind.cmd+shift+code:9 = move-column-to-workspace 8
314-bind.cmd+shift+code:10 = move-column-to-workspace 9
315-
316-# Monitors and scale
317-bind.cmd+ctrl+left = focus-monitor-left
318-bind.cmd+ctrl+right = focus-monitor-right
319-bind.cmd+ctrl+h = focus-monitor-left
320-bind.cmd+ctrl+l = focus-monitor-right
321-bind.cmd+ctrl+shift+left = move-workspace-to-monitor-left
322-bind.cmd+ctrl+shift+right = move-workspace-to-monitor-right
323-bind.cmd+ctrl+shift+h = move-workspace-to-monitor-left
324-bind.cmd+ctrl+shift+l = move-workspace-to-monitor-right
325-bind.cmd+code:13 = scale-up
326-bind.cmd+code:12 = scale-down
327-```
147+### Display and games
328148
329-The focus flash marks the window, column or output an action changes with a brief
330-inner border. Workspace switches and mouse focus do not trigger a flash.
331-It runs when `animations = on`.
149+- Eligible fullscreen buffers use direct scanout; VRR and tearing are on by default for supported fullscreen use (`render.vrr`, `render.tearing`). Tearing requires direct scanout and a client request.
150+- Explicit sync carries client fences to the GPU and KMS, enabled by default with no flag. HDR10 on capable displays includes configurable SDR brightness.
151+- Separate workspaces and fractional scale per output; workspaces move when a monitor is unplugged and return when it reconnects.
152+- Hidden apps get frame callbacks once per second and the suspended state, whether on another workspace, in a hidden stash, off screen or under fullscreen. Captured windows keep their output's refresh rate.
153+- Steam and other X11 clients run through xwayland-satellite; Wine can run natively on Wayland.
332154
333-Per-output defaults (replace `<name>` with a connector such as `DP-1`):
155+### Windows and workspaces
334156
335-```text
336-output.<name> = preferred
337-output.<name>.scale = 1
338-output.<name>.transform = normal
339-output.<name>.primary = off
340-output.<name>.offset = 0
341-output.<name>.hdr = off
342-output.<name>.sdr-brightness = 203
343-```
157+- [Cascade tiling](docs/config.md#cascade) is the default; horizontal scrolling (as in PaperWM and niri) and fixed spiral layouts are available per output or workspace.
158+- Numbered workspaces appear as needed; named workspaces can start commands and place windows in predefined columns.
159+- Overview and stash keep workspaces and set-aside windows close at hand, with keyboard, pointer and touchpad navigation.
344160
345-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.
161+### Desktop integration
346162
347-See [Configuration](docs/config.md) for accepted values and workspace options.
163+Bring your own layer-shell UI, such as Noctalia or Waybar. For capture and idle management, see [nefercap](https://github.com/bnema/nefercap) and [neferafk](https://github.com/bnema/neferafk).
348164
349-</details>
165+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).
350166
351-### Automatic terminal
167+## Hardware support
352168
353-The command comes from `terminal`, then `$TERMINAL`, then `foot`.
169+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.
354170
355-| `terminal.auto-open` | Behaviour |
171+| GPU | Status |
356172 | --- | --- |
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 |
173+| AMD | Developed and tested on radv |
174+| NVIDIA | Untested; explicit sync is implemented, but support is incomplete |
175+| Intel | Untested |
360176
361-`--no-terminal` disables automatic opening for one run; `spawn-terminal` still works. Launched programs inherit `$SHELL`, `$EDITOR` and `$VISUAL`.
177+Reports from NVIDIA and Intel users are welcome.
362178
363179 ## Documentation
364180
181+- [Documentation index](docs/index.md): start here.
182+- [Architecture](docs/architecture.md): concurrency, GC strategy and purego libraries.
365183 - [Configuration](docs/config.md): keys, actions, bindings and HDR.
366184 - [Desktop integration](docs/desktop.md): sessions, clipboard, input methods, X11 apps and scripting.
367185 - [Session locking](docs/session-lock.md): external lockers, recovery and security limits.
@@ -371,7 +189,7 @@ The command comes from `terminal`, then `$TERMINAL`, then `foot`.
371189
372190 ## Contributing
373191
374-Open an issue and wait for acceptance before sending a pull request, including for bug fixes. Pull requests without an accepted issue are closed.
192+Issues are welcome, including NVIDIA and Intel reports. Please open an issue before a large pull request; small fixes are fine directly.
375193
376194 Before submitting, run `make check`, `make race`, `make mocks-check` and `staticcheck ./...`. See [AGENTS.md](AGENTS.md) for repository rules.
377195
@@ -4,90 +4,35 @@
4 <a href="LICENSE"><img src="https://img.shields.io/badge/license-GPLv3-blue?style=flat-square" alt="License: GPLv3"></a>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>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>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>7 </p>
9 8
10 <p align="center">A Wayland compositor that spends its frames on your apps, not on itself.</p>9 <p align="center">A Wayland compositor that spends its frames on your apps, not on itself.</p>
11 10
11+<!-- demo: docs/media/demo.mp4 -->
12+_Demo video coming soon._
13+
12 > [!WARNING]14 > [!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.15 > **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 16
15 ## Why NeferWL17 ## Why NeferWL
16 18
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.19+- **Performance:** allocation-free hot paths, no wasted CPU passes and no cgo; about 40–80 MB of RAM with two 4K monitors. On 4K HDR video, GC takes under 9% of a process using 13% of a core; see [Architecture](docs/architecture.md#keeping-the-garbage-collector-out-of-the-way) and [Performance](docs/performance.md).
18-20+- **Games:** direct scanout, VRR, tearing, explicit sync and [HDR](docs/config.md#hdr) give games the whole GPU instead of spending it on composition; see [Performance](docs/performance.md).
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.21+- **Overview:** `cmd+o` or a four-finger swipe up shows live workspace previews. Select a window with the keyboard, touchpad or mouse; see [Overview](docs/config.md#overview).
20-22+- **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. See [Stash](docs/config.md#stash).
21-Animations are short, cheap and off by default (experimental, `animations = on`).23+- **Hint mode:** click links and buttons by typing their labels, without reaching for the mouse. Enable experimental `hint.enable` and add a bind; see [Hint mode](docs/config.md#hint-mode).
22-24+- **Animations:** short, cheap and off by default. Opt in with experimental `animations = on`; see [Configuration](docs/config.md#keys).
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).
24-
25-## OK, but why Go?
26-
27-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.
28-
29-### A compositor is a concurrency problem
30-
31-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.
32-
33-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.**
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-
41-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.
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-
51-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:
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 25
59-## Features26+Go fits a compositor's independent event streams: each piece of state has one owner goroutine, connected to others by channels.<br>
60-27+Hot paths reuse memory, client buffers stay off the Go heap, and runtime library calls use purego rather than cgo.<br>
61-### Display and games28+See [Architecture](docs/architecture.md) for the concurrency model, GC strategy and reusable Go libraries.
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 29
71-### Windows and workspaces30+Small on purpose, and staying small: no blur, shadows, rounded corners or themes. No built-in bar or wallpaper either: bring your own, any layer-shell client works.
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-
81-Bring 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-
83-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).
84 31
85 ## Install32 ## Install
86 33
87 ### Arch Linux34 ### Arch Linux
88 35
89-Choose an AUR package:
90-
91 ```sh36 ```sh
92 paru -S neferwl-bin # latest release, pre-built for x86_64 and aarch6437 paru -S neferwl-bin # latest release, pre-built for x86_64 and aarch64
93 paru -S neferwl-git # latest main, built from source38 paru -S neferwl-git # latest main, built from source
@@ -110,17 +55,13 @@ sha256sum -c --ignore-missing checksums.txt
110 sudo apt install ./neferwl_<version>_amd64.deb55 sudo apt install ./neferwl_<version>_amd64.deb
111 ```56 ```
112 57
113-Optional tools:
114-
115 ```sh58 ```sh
116 sudo apt install foot fuzzel xdg-desktop-portal-gtk xdg-desktop-portal-gnome59 sudo apt install foot fuzzel xdg-desktop-portal-gtk xdg-desktop-portal-gnome
117 ```60 ```
118 61
119 X11 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.62 X11 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 63
121-### What the packages provide64+The packages provide:
122-
123-The Arch and Debian packages provide:
124 65
125 - `neferwl` with CAP_SYS_NICE for real-time scheduling.66 - `neferwl` with CAP_SYS_NICE for real-time scheduling.
126 - A **NeferWL** session for display managers such as Ly, GDM and SDDM.67 - A **NeferWL** session for display managers such as Ly, GDM and SDDM.
@@ -134,18 +75,6 @@ Copy `/usr/share/doc/neferwl/config.example` to `~/.config/neferwl/config` to st
134 75
135 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`.76 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`.
136 77
137-## Hardware support
138-
139-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.
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-
147-Reports from NVIDIA and Intel users are welcome.
148-
149 ## Configuration78 ## Configuration
150 79
151 The default file is `$XDG_CONFIG_HOME/neferwl/config`, or `~/.config/neferwl/config`.80 The default file is `$XDG_CONFIG_HOME/neferwl/config`, or `~/.config/neferwl/config`.
@@ -167,201 +96,90 @@ workspace.dev.column.2 = 33%, foot
167 bind.cmd+d = workspace dev96 bind.cmd+d = workspace dev
168 ```97 ```
169 98
170-See [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.99 - 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.100 - 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.101 - 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.102 - Use `neferwl --config <path>` to select a file, or `neferwl validate-config [path]` to check it without starting the compositor.
176 103
177-### Default configuration104+### Common settings
178-105+
179-<details>106+| Key | Default | Meaning |
180-<summary>All default settings and key bindings</summary>107+| --- | --- | --- |
108+| `keyboard.layout` | system | xkb layout, e.g. `fr` |
109+| `keyboard.cmd` | `super` | Modifier used as `cmd` |
110+| `terminal` | `$TERMINAL`, then `foot` | Terminal command |
111+| `terminal.auto-open` | `first` | Open on initial numbered workspace; also `all` or `off` |
112+| `startup` | none | Session-start commands, separated by `;` |
113+| `layout.max-columns` | `2` | Columns sharing the screen |
114+| `layout.overflow` | `cascade` | Overflow layout: `cascade`, `scroll`, `fixed` |
115+| `layout.gaps` | `0` | Space between windows |
116+| `output.<name>` | `preferred` | Mode: `WxH@Hz`, `preferred` or `off` |
117+| `output.<name>.scale` | `1` | Fractional scale, 1 to 4 |
118+| `output.<name>.hdr` | `off` | HDR on capable displays |
119+| `touchpad.natural-scroll` | `off` | Content follows fingers |
120+| `stash.width` | `80` | Selected stash window width, percent |
121+| `hint.enable` | `off` | Experimental keyboard clicking; restart required |
122+| `animations` | `off` | Experimental layout and swipe animations |
123+
124+### Default key bindings
125+
126+`cmd` means Super by default. Hint mode has no default binding.
127+
128+| Keys | Action |
129+| --- | --- |
130+| `cmd+return` | Open terminal |
131+| `ctrl+cmd+space` | Launch fuzzel |
132+| `cmd+q` | Close window |
133+| `cmd+arrows` / `cmd+h/j/k/l` | Focus columns and windows |
134+| `cmd+shift+arrows` | Move columns and windows |
135+| `cmd+o` | Toggle overview |
136+| `cmd+s` | Show/hide stash |
137+| `cmd+shift+s` | Move window into/out of stash |
138+| `cmd+shift+f` | Toggle fullscreen |
139+| `cmd+shift+space` | Toggle floating |
140+| `cmd+1`…`9` (physical digit keys) | Focus numbered workspace |
141+| `cmd+pageup/pagedown` | Previous/next workspace |
142+
143+See [Configuration](docs/config.md) for all keys, actions, bindings and automatic-terminal behavior, or [examples/config](examples/config) for commented defaults.
181 144
182-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.145+## Features
183 146
184-```text147+### Display and games
185-# Keyboard and commands
186-keyboard.layout =
187-keyboard.variant =
188-keyboard.options =
189-keyboard.repeat-rate = 25
190-keyboard.repeat-delay = 600
191-keyboard.cmd = super
192-terminal =
193-terminal.auto-open = first
194-xwayland = xwayland-satellite
195-
196-# Appearance and layout
197-background = #111111
198-border.width = 2
199-border.active = #808080
200-border.inactive = #111111
201-floating.dim = 0.3
202-layout.gaps = 0
203-layout.max-columns = 2
204-layout.overflow = cascade
205-stash.width = 80
206-stash.gap = 2
207-stash.dim = 0.5
208-stash.capture = on
209-stash.spawn-terminal = on
210-
211-# Input
212-touchpad.natural-scroll = off
213-touchpad.tap = on
214-touchpad.accel-speed = 0
215-touchpad.accel-profile = adaptive
216-touchpad.left-handed = off
217-touchpad.scroll-factor = 1
218-mouse.natural-scroll = off
219-mouse.accel-speed = 0
220-mouse.accel-profile = adaptive
221-mouse.left-handed = off
222-cursor.hide-after = 5s
223-
224-# Focus and animations
225-focus.follow-move = off
226-animations = off
227-animations.speed = normal
228-focus.flash = on
229-focus.flash-width = 2
230-focus.flash-opacity = 0.6
231-focus.flash-duration = 200ms
232-
233-# Rendering and logging
234-render.direct-scanout = on
235-render.tearing = on
236-render.vrr = on
237-render.vrr-flip-gap = 1ms
238-performance.realtime = on
239-log.level = info
240-log.debug =
241-
242-# Launch, close and session
243-bind.cmd+return = spawn-terminal
244-bind.ctrl+cmd+space = spawn fuzzel
245-bind.cmd+q = close-window
246-bind.ctrl+alt+backspace = quit
247-
248-# Focus and move
249-bind.cmd+left = focus-column-left
250-bind.cmd+right = focus-column-right
251-bind.cmd+up = focus-window-up
252-bind.cmd+down = focus-window-down
253-bind.cmd+h = focus-column-left
254-bind.cmd+l = focus-column-right
255-bind.cmd+k = focus-window-up
256-bind.cmd+j = focus-window-down
257-bind.cmd+shift+left = move-column-left
258-bind.cmd+shift+right = move-column-right
259-bind.cmd+shift+h = move-column-left
260-bind.cmd+shift+l = move-column-right
261-bind.cmd+shift+up = move-window-up
262-bind.cmd+shift+down = move-window-down
263-bind.cmd+shift+k = move-window-up
264-bind.cmd+shift+j = move-window-down
265-bind.cmd+bracketleft = consume-or-expel-window-left
266-bind.cmd+bracketright = consume-or-expel-window-right
267-
268-# Resize and window modes
269-bind.cmd+alt+left = set-column-width -1
270-bind.cmd+alt+right = set-column-width +1
271-bind.cmd+alt+up = set-window-height -10%
272-bind.cmd+alt+down = set-window-height +10%
273-bind.cmd+alt+h = set-column-width -1
274-bind.cmd+alt+l = set-column-width +1
275-bind.cmd+alt+k = set-window-height -10%
276-bind.cmd+alt+j = set-window-height +10%
277-bind.cmd+shift+space = toggle-floating
278-bind.cmd+r = cycle-column-width
279-bind.cmd+f = maximize-column
280-bind.cmd+shift+f = toggle-fullscreen
281-bind.cmd+s = toggle-stash-visible
282-bind.cmd+shift+s = toggle-window-stash
283-bind.cmd+o = toggle-overview
284-# bind.cmd+g = hint
285-bind.cmd+tab = switch-column-next
286-bind.cmd+shift+tab = switch-column-prev
287-
288-# Workspaces
289-bind.cmd+pageup = focus-workspace-prev
290-bind.cmd+pagedown = focus-workspace-next
291-bind.cmd+shift+pageup = move-column-to-workspace-prev
292-bind.cmd+shift+pagedown = move-column-to-workspace-next
293-bind.cmd+ctrl+shift+up = move-workspace-prev
294-bind.cmd+ctrl+shift+down = move-workspace-next
295-bind.cmd+ctrl+shift+k = move-workspace-prev
296-bind.cmd+ctrl+shift+j = move-workspace-next
297-bind.cmd+code:2 = focus-workspace 1
298-bind.cmd+code:3 = focus-workspace 2
299-bind.cmd+code:4 = focus-workspace 3
300-bind.cmd+code:5 = focus-workspace 4
301-bind.cmd+code:6 = focus-workspace 5
302-bind.cmd+code:7 = focus-workspace 6
303-bind.cmd+code:8 = focus-workspace 7
304-bind.cmd+code:9 = focus-workspace 8
305-bind.cmd+code:10 = focus-workspace 9
306-bind.cmd+shift+code:2 = move-column-to-workspace 1
307-bind.cmd+shift+code:3 = move-column-to-workspace 2
308-bind.cmd+shift+code:4 = move-column-to-workspace 3
309-bind.cmd+shift+code:5 = move-column-to-workspace 4
310-bind.cmd+shift+code:6 = move-column-to-workspace 5
311-bind.cmd+shift+code:7 = move-column-to-workspace 6
312-bind.cmd+shift+code:8 = move-column-to-workspace 7
313-bind.cmd+shift+code:9 = move-column-to-workspace 8
314-bind.cmd+shift+code:10 = move-column-to-workspace 9
315-
316-# Monitors and scale
317-bind.cmd+ctrl+left = focus-monitor-left
318-bind.cmd+ctrl+right = focus-monitor-right
319-bind.cmd+ctrl+h = focus-monitor-left
320-bind.cmd+ctrl+l = focus-monitor-right
321-bind.cmd+ctrl+shift+left = move-workspace-to-monitor-left
322-bind.cmd+ctrl+shift+right = move-workspace-to-monitor-right
323-bind.cmd+ctrl+shift+h = move-workspace-to-monitor-left
324-bind.cmd+ctrl+shift+l = move-workspace-to-monitor-right
325-bind.cmd+code:13 = scale-up
326-bind.cmd+code:12 = scale-down
327-```
328 148
329-The focus flash marks the window, column or output an action changes with a brief149+- Eligible fullscreen buffers use direct scanout; VRR and tearing are on by default for supported fullscreen use (`render.vrr`, `render.tearing`). Tearing requires direct scanout and a client request.
330-inner border. Workspace switches and mouse focus do not trigger a flash.150+- Explicit sync carries client fences to the GPU and KMS, enabled by default with no flag. HDR10 on capable displays includes configurable SDR brightness.
331-It runs when `animations = on`.151+- Separate workspaces and fractional scale per output; workspaces move when a monitor is unplugged and return when it reconnects.
152+- Hidden apps get frame callbacks once per second and the suspended state, whether on another workspace, in a hidden stash, off screen or under fullscreen. Captured windows keep their output's refresh rate.
153+- Steam and other X11 clients run through xwayland-satellite; Wine can run natively on Wayland.
332 154
333-Per-output defaults (replace `<name>` with a connector such as `DP-1`):155+### Windows and workspaces
334 156
335-```text157+- [Cascade tiling](docs/config.md#cascade) is the default; horizontal scrolling (as in PaperWM and niri) and fixed spiral layouts are available per output or workspace.
336-output.<name> = preferred158+- Numbered workspaces appear as needed; named workspaces can start commands and place windows in predefined columns.
337-output.<name>.scale = 1159+- Overview and stash keep workspaces and set-aside windows close at hand, with keyboard, pointer and touchpad navigation.
338-output.<name>.transform = normal
339-output.<name>.primary = off
340-output.<name>.offset = 0
341-output.<name>.hdr = off
342-output.<name>.sdr-brightness = 203
343-```
344 160
345-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.161+### Desktop integration
346 162
347-See [Configuration](docs/config.md) for accepted values and workspace options.163+Bring your own layer-shell UI, such as Noctalia or Waybar. For capture and idle management, see [nefercap](https://github.com/bnema/nefercap) and [neferafk](https://github.com/bnema/neferafk).
348 164
349-</details>165+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).
350 166
351-### Automatic terminal167+## Hardware support
352 168
353-The command comes from `terminal`, then `$TERMINAL`, then `foot`.169+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.
354 170
355-| `terminal.auto-open` | Behaviour |171+| GPU | Status |
356 | --- | --- |172 | --- | --- |
357-| `first` | Open once on the initial numbered workspace |173+| AMD | Developed and tested on radv |
358-| `all` | Open on each visible empty workspace without predefined slots |174+| NVIDIA | Untested; explicit sync is implemented, but support is incomplete |
359-| `off` | Do not open automatically |175+| Intel | Untested |
360 176
361-`--no-terminal` disables automatic opening for one run; `spawn-terminal` still works. Launched programs inherit `$SHELL`, `$EDITOR` and `$VISUAL`.177+Reports from NVIDIA and Intel users are welcome.
362 178
363 ## Documentation179 ## Documentation
364 180
181+- [Documentation index](docs/index.md): start here.
182+- [Architecture](docs/architecture.md): concurrency, GC strategy and purego libraries.
365 - [Configuration](docs/config.md): keys, actions, bindings and HDR.183 - [Configuration](docs/config.md): keys, actions, bindings and HDR.
366 - [Desktop integration](docs/desktop.md): sessions, clipboard, input methods, X11 apps and scripting.184 - [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.185 - [Session locking](docs/session-lock.md): external lockers, recovery and security limits.
@@ -371,7 +189,7 @@ The command comes from `terminal`, then `$TERMINAL`, then `foot`.
371 189
372 ## Contributing190 ## Contributing
373 191
374-Open an issue and wait for acceptance before sending a pull request, including for bug fixes. Pull requests without an accepted issue are closed.192+Issues are welcome, including NVIDIA and Intel reports. Please open an issue before a large pull request; small fixes are fine directly.
375 193
376 Before submitting, run `make check`, `make race`, `make mocks-check` and `staticcheck ./...`. See [AGENTS.md](AGENTS.md) for repository rules.194 Before submitting, run `make check`, `make race`, `make mocks-check` and `staticcheck ./...`. See [AGENTS.md](AGENTS.md) for repository rules.
377 195
A docs/architecture.md +34 -0
@@ -0,0 +1,34 @@
1+# Architecture
2+
3+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.
4+
5+### A compositor is a concurrency problem
6+
7+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.
8+
9+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.**
10+
11+- **Input** runs on its own thread, reads libinput and sends events to core.
12+- **Core** owns windows, workspaces and focus. It turns input and client events into layouts and scenes.
13+- **The Wayland server** owns client connections. It sends client events and surface content, and receives commands back.
14+- **Each output** owns its DRM state, renders its scenes with Vulkan, commits frames and reports flips and presentation times.
15+- **Capture, config, idle, sessions and leases** each run in their own goroutine.
16+
17+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.
18+
19+### Keeping the garbage collector out of the way
20+
21+- **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.
22+- **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](performance.md).
23+- **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.
24+- **Work only when something changes.** Damage tracking redraws only what changed, surfaces hidden behind opaque ones are skipped, and an idle output does nothing.
25+- **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.
26+
27+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:
28+
29+- [purego-libwayland](https://github.com/bnema/purego-libwayland): libwayland-server without cgo.
30+- [purego-vulkan](https://github.com/bnema/purego-vulkan): Vulkan without cgo, generated from `vk.xml`.
31+- [go-wayland-bindings](https://github.com/bnema/go-wayland-bindings): Wayland protocol bindings, generated from the upstream XML.
32+- [wlturbo](https://github.com/bnema/wlturbo): a fast Wayland client.
33+- [neferclient](https://github.com/bnema/neferclient): a client toolkit on top of them: connection, outputs, surface roles, seat and dmabuf presentation.
34+
@@ -0,0 +1,34 @@
1+# Architecture
2+
3+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.
4+
5+### A compositor is a concurrency problem
6+
7+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.
8+
9+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.**
10+
11+- **Input** runs on its own thread, reads libinput and sends events to core.
12+- **Core** owns windows, workspaces and focus. It turns input and client events into layouts and scenes.
13+- **The Wayland server** owns client connections. It sends client events and surface content, and receives commands back.
14+- **Each output** owns its DRM state, renders its scenes with Vulkan, commits frames and reports flips and presentation times.
15+- **Capture, config, idle, sessions and leases** each run in their own goroutine.
16+
17+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.
18+
19+### Keeping the garbage collector out of the way
20+
21+- **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.
22+- **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](performance.md).
23+- **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.
24+- **Work only when something changes.** Damage tracking redraws only what changed, surfaces hidden behind opaque ones are skipped, and an idle output does nothing.
25+- **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.
26+
27+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:
28+
29+- [purego-libwayland](https://github.com/bnema/purego-libwayland): libwayland-server without cgo.
30+- [purego-vulkan](https://github.com/bnema/purego-vulkan): Vulkan without cgo, generated from `vk.xml`.
31+- [go-wayland-bindings](https://github.com/bnema/go-wayland-bindings): Wayland protocol bindings, generated from the upstream XML.
32+- [wlturbo](https://github.com/bnema/wlturbo): a fast Wayland client.
33+- [neferclient](https://github.com/bnema/neferclient): a client toolkit on top of them: connection, outputs, surface roles, seat and dmabuf presentation.
34+
M docs/config.md +30 -0
@@ -7,6 +7,8 @@ neferwl --config <path> # use another file
77 neferwl validate-config [path] # check a file
88 ```
99
10+Missing keys use defaults. Empty keyboard values use the system setting. No startup commands or named workspaces are configured by default. The log lists changed keys after a reload.
11+
1012 ## Format
1113
1214 - One `key = value` per line. `#` starts a comment.
@@ -97,6 +99,34 @@ destination output, even if that output has no windows. Same-screen workspace
9799 switches and mouse focus do not trigger a flash.
98100 It runs when `animations = on`.
99101
102+## Automatic terminal
103+
104+The command comes from `terminal`, then `$TERMINAL`, then `foot`.
105+
106+| `terminal.auto-open` | Behaviour |
107+| --- | --- |
108+| `first` | Open once on the initial numbered workspace |
109+| `all` | Open on each visible empty workspace without predefined slots |
110+| `off` | Do not open automatically |
111+
112+`--no-terminal` disables automatic opening for one run; `spawn-terminal` still works. Launched programs inherit `$SHELL`, `$EDITOR` and `$VISUAL`.
113+
114+## Per-output defaults
115+
116+Per-output defaults (replace `<name>` with a connector such as `DP-1`):
117+
118+```text
119+output.<name> = preferred
120+output.<name>.scale = 1
121+output.<name>.transform = normal
122+output.<name>.primary = off
123+output.<name>.offset = 0
124+output.<name>.hdr = off
125+output.<name>.sdr-brightness = 203
126+```
127+
128+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.
129+
100130 ## Window rules
101131
102132 Rules place an app's windows when they first open.
@@ -7,6 +7,8 @@ neferwl --config <path> # use another file
7 neferwl validate-config [path] # check a file7 neferwl validate-config [path] # check a file
8 ```8 ```
9 9
10+Missing keys use defaults. Empty keyboard values use the system setting. No startup commands or named workspaces are configured by default. The log lists changed keys after a reload.
11+
10 ## Format12 ## Format
11 13
12 - One `key = value` per line. `#` starts a comment.14 - One `key = value` per line. `#` starts a comment.
@@ -97,6 +99,34 @@ destination output, even if that output has no windows. Same-screen workspace
97 switches and mouse focus do not trigger a flash.99 switches and mouse focus do not trigger a flash.
98 It runs when `animations = on`.100 It runs when `animations = on`.
99 101
102+## Automatic terminal
103+
104+The command comes from `terminal`, then `$TERMINAL`, then `foot`.
105+
106+| `terminal.auto-open` | Behaviour |
107+| --- | --- |
108+| `first` | Open once on the initial numbered workspace |
109+| `all` | Open on each visible empty workspace without predefined slots |
110+| `off` | Do not open automatically |
111+
112+`--no-terminal` disables automatic opening for one run; `spawn-terminal` still works. Launched programs inherit `$SHELL`, `$EDITOR` and `$VISUAL`.
113+
114+## Per-output defaults
115+
116+Per-output defaults (replace `<name>` with a connector such as `DP-1`):
117+
118+```text
119+output.<name> = preferred
120+output.<name>.scale = 1
121+output.<name>.transform = normal
122+output.<name>.primary = off
123+output.<name>.offset = 0
124+output.<name>.hdr = off
125+output.<name>.sdr-brightness = 203
126+```
127+
128+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.
129+
100 ## Window rules130 ## Window rules
101 131
102 Rules place an app's windows when they first open.132 Rules place an app's windows when they first open.
A docs/index.md +11 -0
@@ -0,0 +1,11 @@
1+# NeferWL documentation
2+
3+NeferWL is a small Wayland compositor in Go. Start with Configuration to set up your keyboard, outputs and workflows, then use the guides below for desktop integration, architecture and troubleshooting.
4+
5+- [Configuration](config.md): keys, actions, default bindings, layouts and HDR.
6+- [Architecture](architecture.md): owner goroutines, GC strategy and reusable purego libraries.
7+- [Desktop integration](desktop.md): sessions, clipboard, input methods, X11 apps and scripting.
8+- [Session locking](session-lock.md): external lockers, recovery and security limits.
9+- [Screen capture](capture.md): protocols, indicator, executable allowlist and portals.
10+- [Headless mode](headless.md): screenshots, scripted input and testing without a screen.
11+- [Performance](performance.md): VRR, real-time priority, profiling and frame stats.
@@ -0,0 +1,11 @@
1+# NeferWL documentation
2+
3+NeferWL is a small Wayland compositor in Go. Start with Configuration to set up your keyboard, outputs and workflows, then use the guides below for desktop integration, architecture and troubleshooting.
4+
5+- [Configuration](config.md): keys, actions, default bindings, layouts and HDR.
6+- [Architecture](architecture.md): owner goroutines, GC strategy and reusable purego libraries.
7+- [Desktop integration](desktop.md): sessions, clipboard, input methods, X11 apps and scripting.
8+- [Session locking](session-lock.md): external lockers, recovery and security limits.
9+- [Screen capture](capture.md): protocols, indicator, executable allowlist and portals.
10+- [Headless mode](headless.md): screenshots, scripted input and testing without a screen.
11+- [Performance](performance.md): VRR, real-time priority, profiling and frame stats.

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