detached 7738432

docs(readme): tighten wording and reorder sections

commit
773843275772a48e1d7e72ea77de9714d4c5e3ff
author
brice · · 2026-09-26 16:20 UTC
parent
d9218d6
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +73 -71
M README.md +73 -71
@@ -16,17 +16,17 @@
1616
1717 ## Why NeferWL
1818
19-NeferWL does a few things and does them fast. No animations, no themes, no built-in bar or wallpaper: those are choices, not missing features. Every frame budget goes to your apps.
19+NeferWL has no animations, themes, built-in bar or wallpaper, by design. The compositor stays small so the CPU and GPU go to your terminal and your games.
2020
21-- **A terminal, then out of the way.** One workspace opens your terminal at startup. Bars, launchers and notifications are external clients (Waybar, fuzzel, mako…).
22-- **Columns between tiling and scrolling.** Up to `max-columns` windows share the screen like a classic tiling WM (Sway); past that, columns scroll to the right like Niri, or split in a spiral with `overflow = fixed`. Each workspace picks its own rules.
23-- **Workspaces you design.** Numbered workspaces are dynamic. Named workspaces can be hidden behind a bind, and **slots** declare their columns (width + command): NeferWL starts the apps in the background and puts each window in its place.
24-- **Multi-monitor done right.** Each monitor owns its workspaces. Unplug one and its workspaces move to another monitor; plug it back and they return home, without stealing your focus.
25-- **Built for games.** Fullscreen games skip composition entirely (details below), with tearing and VRR when the game asks.
26-- **Light.** A running session uses about 40–80 MB of RAM with two 4K monitors, and near-zero CPU when nothing changes on screen.
27-- **Live config.** One flat `key = value` file. Every key applies on save, keyboard layout included; windows stay open.
21+- **Terminal first.** NeferWL opens your terminal at startup. Bars, launchers and notifications are external clients such as Waybar, fuzzel and mako.
22+- **Column tiling.** Up to `max-columns` windows share the screen, as in Sway. Past that, new columns scroll to the right as in Niri, or split in a spiral with `overflow = fixed`. Each workspace can set its own rules.
23+- **Custom workspaces.** Numbered workspaces appear and disappear as you use them. Named workspaces can be hidden behind a bind. **Slots** declare a workspace's columns (width and command): NeferWL starts the apps in the background and puts each window in its column.
24+- **Multi-monitor.** Each monitor has its own workspaces. When you unplug a monitor, its workspaces move to another one; when you plug it back, they return, and your focus stays where it was.
25+- **Games.** A fullscreen game is shown without composition, with tearing and VRR when the game asks for them. Details are in [the performance path](#the-performance-path).
26+- **Low footprint.** About 40–80 MB of RAM with two 4K monitors, and almost no CPU while the screen does not change.
27+- **Live config.** One `key = value` file. Every key applies when you save, keyboard layout included, and windows stay open.
2828
29-A complete desktop in a few lines:
29+An example config:
3030
3131 ```text
3232 keyboard.layout = fr
@@ -47,71 +47,49 @@ bind.cmd+d = workspace dev
4747
4848 ### The performance path
4949
50-Every Wayland protocol NeferWL implements is there because it saves work or latency:
50+These protocols and kernel features cut copies, waits and latency between the app and the screen:
5151
5252 - **Zero copy.** GPU clients (`zwp_linux_dmabuf_v1` v4) are sampled in place. Vulkan composes straight into exported scanout images, and the frame fence goes to KMS as `IN_FENCE_FD`: no CPU copy, no CPU wait before a flip.
5353 - **Direct scanout.** A fullscreen window's buffer goes straight to the display plane; dmabuf feedback sends the game a scanout-ready format first. A lone opaque window can use an overlay plane.
5454 - **Tearing and VRR.** `wp_tearing_control_v1` flips without waiting for vblank when the game requests it; variable refresh runs while a buffer is scanned out.
55-- **Explicit sync.** `wp_linux_drm_syncobj_v1` passes GPU fences end to end (needed by NVIDIA).
56-- **Frame pacing.** `wp_presentation` from kernel flip timestamps, `wp_fifo_v1`, `wp_commit_timing_v1` and `wp_content_type_v1`.
57-- **Only what changed.** Damage-limited redraws, occlusion culling of hidden surfaces, idle outputs never redraw. Atomic KMS: frame, cursor, overlay and VRR in one commit.
58-- **Input for games.** Relative pointer, pointer constraints, keyboard shortcuts inhibit, 1 kHz+ non-blocking input, hardware cursor plane.
55+- **Explicit sync.** `wp_linux_drm_syncobj_v1` passes GPU fences from the app to KMS. NVIDIA drivers need it.
56+- **Frame pacing.** Apps get real flip times through `wp_presentation`, and can queue frames with `wp_fifo_v1` and `wp_commit_timing_v1`.
57+- **Only what changed.** NeferWL redraws only damaged regions, skips surfaces hidden behind opaque ones, and leaves idle outputs alone. Frame, cursor, overlay and VRR go to the kernel in one atomic commit.
58+- **Input.** Relative pointer, pointer constraints and keyboard shortcuts inhibit for games. Input at 1 kHz and more never waits on rendering, and the cursor has its own hardware plane.
5959 - **X11 games.** Steam and Wine run through xwayland-satellite, started on the first X11 connection.
6060
61-Requires Linux and Go 1.27.
62-
6361 ## Hardware support
6462
65-NeferWL 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. Frames are composed on the GPU only: no CPU copy and no CPU wait for the GPU before a page flip.
66-
67-- **AMD GPUs:** developed and tested on AMD (radv).
68-- **NVIDIA GPUs:** untested. Explicit sync (`linux-drm-syncobj`), which NVIDIA drivers need, is implemented. Testers with NVIDIA hardware are welcome to report issues or contribute.
69-- **Intel GPUs:** untested.
70-
71-Build: `make build`.
63+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.
7264
73-Run: `neferwl --backend=headless --timeout 5s`
65+- **AMD:** developed and tested on radv.
66+- **NVIDIA:** untested. Explicit sync, which NVIDIA drivers need, is implemented. Reports from NVIDIA users are welcome.
67+- **Intel:** untested.
7468
7569 ## Install (Arch Linux)
7670
71+Building needs Go 1.27.
72+
7773 ```sh
7874 make pkg # builds dist/neferwl-*.pkg.tar.zst from HEAD
7975 sudo pacman -U dist/neferwl-*.pkg.tar.zst
8076 ```
8177
82-The package installs `neferwl` and a **NeferWL** session for display managers (Ly, GDM, SDDM). The session runs `neferwl --session`, which shares `WAYLAND_DISPLAY` and `DISPLAY` with D-Bus and systemd user services, so portals and notification daemons reach it.
83-
84-## Developing the bindings
85-
86-NeferWL depends on tagged releases of [purego-libwayland](https://github.com/bnema/purego-libwayland) and [purego-vulkan](https://github.com/bnema/purego-vulkan). To change them together with NeferWL, check them out next to it and use a Go workspace (not committed):
87-
88-```sh
89-go work init . ../purego-libwayland ../purego-vulkan
90-```
78+The package installs `neferwl` and a **NeferWL** session for display managers such as Ly, GDM and SDDM. The session runs `neferwl --session`, which passes `WAYLAND_DISPLAY` and `DISPLAY` to D-Bus and systemd user services so that portals and notification daemons can connect.
9179
92-`make pkg` ignores the workspace and builds the versions in `go.mod`: tag a binding change and update `go.mod` before packaging it.
80+On other distributions, `make build` builds `bin/neferwl`.
9381
9482 ## Configuration
9583
96-Configuration lives at `$XDG_CONFIG_HOME/neferwl/config` (or `~/.config/neferwl/config`). Use `neferwl --config path` to select another file; `neferwl validate-config [path]` checks it without starting the compositor.
97-
98-One `key = value` per line; `#` starts a comment:
84+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.
9985
100-```text
101-keyboard.layout = fr
102-terminal = foot
103-bind.cmd+return = spawn-terminal
104-bind.ctrl+cmd+space = spawn fuzzel
105-startup = wl-paste --watch cliphist store
106-```
86+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.
10787
108-Every key, value and default bind is listed in [docs/config.md](docs/config.md); `examples/config` is a commented copy of the defaults. A missing file means defaults. An invalid line logs a warning and keeps that key's default; the rest of the file still applies. Every key applies live when the file changes, including the keyboard layout; each reload logs the keys that changed.
88+[docs/config.md](docs/config.md) lists every key, action and default bind. `examples/config` is a commented copy of the defaults.
10989
11090 ## Running commands
11191
112-NeferWL starts a program, not a shell line: `spawn` runs a binary or a script. Anything more than one command with arguments, such as pipes or variables, goes in a script that you write.
113-
114-`spawn <command>` binds, `startup = <command>` lines and `terminal = <command>` run one program directly, without a shell. The line is split on spaces: the first word is the program (looked up in `PATH`, or a path such as `/opt/tool/run`), the other words are its arguments.
92+`spawn <command>` binds, `startup = <command>` lines and `terminal = <command>` run one program directly, without a shell. NeferWL splits the line on spaces: the first word is the program, found in `PATH` or given as a path such as `/opt/tool/run`, and the other words are its arguments.
11593
11694 ```text
11795 bind.ctrl+cmd+space = spawn fuzzel
@@ -119,9 +97,7 @@ startup = waybar
11997 startup = wl-paste --watch cliphist store
12098 ```
12199
122-There is no shell, so pipes (`|`), `&&`, redirections, variables (`$HOME`), `~` and quotes have no special meaning: they reach the program as plain arguments. What you write is what runs.
123-
124-For anything more complex, write a script and run the script:
100+Pipes (`|`), `&&`, redirections, variables (`$HOME`), `~` and quotes have no special meaning: they reach the program as plain arguments. For those, write a script and run it:
125101
126102 ```sh
127103 #!/bin/sh
@@ -134,13 +110,13 @@ grim -g "$(slurp)" "$file" && wl-copy < "$file"
134110 bind.cmd+shift+s = spawn screenshot-area
135111 ```
136112
137-Make it executable (`chmod +x`) and put it in a directory of your `PATH`, such as `~/.local/bin`. The script can use any shell, including fish (`#!/usr/bin/env fish`).
113+Make the script executable (`chmod +x`) and put it in a directory of your `PATH`, such as `~/.local/bin`. It can use any shell, including fish (`#!/usr/bin/env fish`).
138114
139-Programs started by NeferWL get `WAYLAND_DISPLAY`, `DISPLAY` (see [X11 apps](#x11-apps)) and `NEFERWL_STATE` (see below). `startup` commands run once when the session starts; editing them takes effect at the next start.
115+Programs started by NeferWL get `WAYLAND_DISPLAY`, `DISPLAY` (see [X11 apps](#x11-apps)) and `NEFERWL_STATE` (see below). `startup` commands run once when the session starts, so editing them takes effect at the next start.
140116
141117 ## Clipboard
142118
143-NeferWL supports the clipboard (`wl_data_device`), the primary selection (middle-click paste) and `ext_data_control_v1` for clipboard managers. With [cliphist](https://github.com/sentriz/cliphist), store every copy at startup and bind a picker script (see [Running commands](#running-commands)):
119+NeferWL supports the clipboard (`wl_data_device`), the primary selection (middle-click paste) and `ext_data_control_v1` for clipboard managers. To keep a history with [cliphist](https://github.com/sentriz/cliphist), store every copy at startup and bind a picker script:
144120
145121 ```text
146122 startup = wl-paste --watch cliphist store
@@ -158,7 +134,7 @@ Drag and drop is not supported yet.
158134
159135 ## X11 apps
160136
161-X11 apps such as Steam and Wine run through [xwayland-satellite](https://github.com/Supreeeme/xwayland-satellite) 0.7 or later, found in `PATH`. NeferWL opens an X11 display, sets `DISPLAY` for the programs it starts, and runs xwayland-satellite when the first X11 app connects. If xwayland-satellite exits, the next X11 app starts it again.
137+X11 apps such as Steam and Wine run through [xwayland-satellite](https://github.com/Supreeeme/xwayland-satellite) 0.7 or later, found in `PATH`. NeferWL opens an X11 display, sets `DISPLAY` for the programs it starts, and starts xwayland-satellite when the first X11 app connects. If xwayland-satellite exits, the next X11 app starts it again.
162138
163139 ```text
164140 xwayland = xwayland-satellite # the default; a path also works
@@ -169,42 +145,68 @@ Do not start xwayland-satellite yourself with `startup`. Changing `xwayland` tak
169145
170146 ## State for scripts
171147
172-While it runs, NeferWL keeps its state in `$XDG_RUNTIME_DIR/neferwl/<wayland socket>.json` and passes that path to the programs it starts as `NEFERWL_STATE`. The file lists every output (active numbered workspace, workspace count, name of the workspace on screen), the focused output and window, and every window with its app ID, PID, output and workspace.
148+While it runs, NeferWL writes its state to `$XDG_RUNTIME_DIR/neferwl/<wayland socket>.json` and passes that path to the programs it starts as `NEFERWL_STATE`. The file lists:
149+
150+- every output, with its active numbered workspace, workspace count and the name of the workspace on screen;
151+- the focused output and window;
152+- every window, with its app ID, PID, output and workspace.
173153
174154 ```sh
175155 neferwl state # the whole state as JSON
176156 neferwl state output-of "$PID" # the output of that process's window, or its nearest parent's
177157 ```
178158
179-`output-of` finds application windows only; bars have no single output.
159+`output-of` finds application windows only, because a bar has no single output.
180160
181161 ## Try it (headless)
182162
183-Run `neferwl --backend=headless --screenshot /tmp/neferwl-shots`, then connect with
184-`WAYLAND_DISPLAY=<logged name> foot`. `/tmp/neferwl-shots/latest.png` shows the current frame.
185-Use `--input path` (or `--input -` for stdin) to inject a headless script. Each line is
186-`type text`, `key Super+Return` (also Shift, Ctrl, Alt or a bare key), `sleep 1s`, `move x y` (absolute output coordinates), `click [left|right|middle]`,
187-`down button`, or `up button`. Cursor rendering is not implemented yet; pointer
188-focus after layout changes is updated on the next move.
163+The headless backend runs without a screen, for tests and quick checks:
164+
165+```sh
166+neferwl --backend=headless --screenshot /tmp/neferwl-shots
167+WAYLAND_DISPLAY=<name from the log> foot
168+```
169+
170+`/tmp/neferwl-shots/latest.png` shows the current frame. `--timeout 5s` stops NeferWL after 5 seconds.
171+
172+`--input <path>`, or `--input -` for stdin, plays an input script. One command per line:
173+
174+- `type <text>`
175+- `key Super+Return` (Shift, Ctrl and Alt also work, as does a bare key)
176+- `sleep 1s`
177+- `move <x> <y>` (output coordinates)
178+- `click [left|right|middle]`, `down <button>`, `up <button>`
179+
180+The headless backend does not draw the cursor. After a layout change, pointer focus updates on the next move.
189181
190182 ## Why not Rust?
191183
192-Most Wayland compositors are written in C (wlroots, Sway, Mutter), C++ (KWin, Hyprland, gamescope) or Rust (niri, COSMIC). NeferWL is written in Go, by choice:
184+Most Wayland compositors are written in C (wlroots, Sway, Mutter), C++ (KWin, Hyprland, gamescope) or Rust (niri, COSMIC). NeferWL uses Go on purpose:
193185
194-- **Simple and readable.** Go is small and explicit. Goroutines and channels fit a compositor where each part (input, outputs, clients) owns its own state.
195-- **Fast builds, easy tooling.** A full build takes seconds; tests, the race detector, profiling and formatting come with the language.
186+- **Readable.** Go is small and explicit. Goroutines and channels fit a compositor where input, each output and the clients own their own state.
187+- **Tooling.** A full build takes seconds, and tests, the race detector, profiling and formatting come with the language.
196188 - **No cgo.** NeferWL builds with `CGO_ENABLED=0`: libwayland, Vulkan and libinput are loaded at runtime through [purego](https://github.com/ebitengine/purego).
197-- **Fast enough.** With care for allocations and the garbage collector, the hot paths (input, rendering, buffer handling) perform close to native code.
189+- **Speed.** With care for allocations and the garbage collector, input, rendering and buffer handling run close to native code.
190+
191+I like bringing more tools to the Go ecosystem. NeferWL grew its own libraries along the way, [purego-libwayland](https://github.com/bnema/purego-libwayland), [purego-vulkan](https://github.com/bnema/purego-vulkan) and [wlturbo](https://github.com/bnema/wlturbo), and other Go projects can use them.
192+
193+Go has no borrow checker, so the compiler does not catch memory and concurrency mistakes as Rust's does. NeferWL compensates with strict ownership (one goroutine owns each piece of state), the race detector on every test run, and protocol tests against real Wayland clients.
194+
195+## Developing the bindings
198196
199-As a software engineer, my passion is bringing more tools to the Go ecosystem. NeferWL grew its own libraries along the way: purego-libwayland, purego-vulkan and wlturbo, to be published for others to reuse. Building them is part of the fun.
197+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:
198+
199+```sh
200+go work init . ../purego-libwayland ../purego-vulkan
201+```
200202
201-What Go does not give us is Rust's borrow checker: memory and concurrency mistakes are not caught at compile time. NeferWL makes up for it with strict ownership (one goroutine owns each piece of state), the race detector on every test run, and protocol tests against real Wayland clients.
203+`make pkg` ignores the workspace and builds the versions in `go.mod`. Tag the binding change and update `go.mod` before packaging.
202204
203205 ## Contributing
204206
205-Contributions are welcome. Open an issue first, for a bug fix or a feature, and wait until it is accepted before sending a pull request. Pull requests without an accepted issue will be closed.
207+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.
206208
207-Before a pull request, `make check`, `make race`, `make mocks-check` and `staticcheck ./...` must pass. Repository rules are in [AGENTS.md](AGENTS.md).
209+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).
208210
209211 ## License
210212
@@ -16,17 +16,17 @@
16 16
17 ## Why NeferWL17 ## Why NeferWL
18 18
19-NeferWL does a few things and does them fast. No animations, no themes, no built-in bar or wallpaper: those are choices, not missing features. Every frame budget goes to your apps.19+NeferWL has no animations, themes, built-in bar or wallpaper, by design. The compositor stays small so the CPU and GPU go to your terminal and your games.
20 20
21-- **A terminal, then out of the way.** One workspace opens your terminal at startup. Bars, launchers and notifications are external clients (Waybar, fuzzel, mako…).21+- **Terminal first.** NeferWL opens your terminal at startup. Bars, launchers and notifications are external clients such as Waybar, fuzzel and mako.
22-- **Columns between tiling and scrolling.** Up to `max-columns` windows share the screen like a classic tiling WM (Sway); past that, columns scroll to the right like Niri, or split in a spiral with `overflow = fixed`. Each workspace picks its own rules.22+- **Column tiling.** Up to `max-columns` windows share the screen, as in Sway. Past that, new columns scroll to the right as in Niri, or split in a spiral with `overflow = fixed`. Each workspace can set its own rules.
23-- **Workspaces you design.** Numbered workspaces are dynamic. Named workspaces can be hidden behind a bind, and **slots** declare their columns (width + command): NeferWL starts the apps in the background and puts each window in its place.23+- **Custom workspaces.** Numbered workspaces appear and disappear as you use them. Named workspaces can be hidden behind a bind. **Slots** declare a workspace's columns (width and command): NeferWL starts the apps in the background and puts each window in its column.
24-- **Multi-monitor done right.** Each monitor owns its workspaces. Unplug one and its workspaces move to another monitor; plug it back and they return home, without stealing your focus.24+- **Multi-monitor.** Each monitor has its own workspaces. When you unplug a monitor, its workspaces move to another one; when you plug it back, they return, and your focus stays where it was.
25-- **Built for games.** Fullscreen games skip composition entirely (details below), with tearing and VRR when the game asks.25+- **Games.** A fullscreen game is shown without composition, with tearing and VRR when the game asks for them. Details are in [the performance path](#the-performance-path).
26-- **Light.** A running session uses about 40–80 MB of RAM with two 4K monitors, and near-zero CPU when nothing changes on screen.26+- **Low footprint.** About 40–80 MB of RAM with two 4K monitors, and almost no CPU while the screen does not change.
27-- **Live config.** One flat `key = value` file. Every key applies on save, keyboard layout included; windows stay open.27+- **Live config.** One `key = value` file. Every key applies when you save, keyboard layout included, and windows stay open.
28 28
29-A complete desktop in a few lines:29+An example config:
30 30
31 ```text31 ```text
32 keyboard.layout = fr32 keyboard.layout = fr
@@ -47,71 +47,49 @@ bind.cmd+d = workspace dev
47 47
48 ### The performance path48 ### The performance path
49 49
50-Every Wayland protocol NeferWL implements is there because it saves work or latency:50+These protocols and kernel features cut copies, waits and latency between the app and the screen:
51 51
52 - **Zero copy.** GPU clients (`zwp_linux_dmabuf_v1` v4) are sampled in place. Vulkan composes straight into exported scanout images, and the frame fence goes to KMS as `IN_FENCE_FD`: no CPU copy, no CPU wait before a flip.52 - **Zero copy.** GPU clients (`zwp_linux_dmabuf_v1` v4) are sampled in place. Vulkan composes straight into exported scanout images, and the frame fence goes to KMS as `IN_FENCE_FD`: no CPU copy, no CPU wait before a flip.
53 - **Direct scanout.** A fullscreen window's buffer goes straight to the display plane; dmabuf feedback sends the game a scanout-ready format first. A lone opaque window can use an overlay plane.53 - **Direct scanout.** A fullscreen window's buffer goes straight to the display plane; dmabuf feedback sends the game a scanout-ready format first. A lone opaque window can use an overlay plane.
54 - **Tearing and VRR.** `wp_tearing_control_v1` flips without waiting for vblank when the game requests it; variable refresh runs while a buffer is scanned out.54 - **Tearing and VRR.** `wp_tearing_control_v1` flips without waiting for vblank when the game requests it; variable refresh runs while a buffer is scanned out.
55-- **Explicit sync.** `wp_linux_drm_syncobj_v1` passes GPU fences end to end (needed by NVIDIA).55+- **Explicit sync.** `wp_linux_drm_syncobj_v1` passes GPU fences from the app to KMS. NVIDIA drivers need it.
56-- **Frame pacing.** `wp_presentation` from kernel flip timestamps, `wp_fifo_v1`, `wp_commit_timing_v1` and `wp_content_type_v1`.56+- **Frame pacing.** Apps get real flip times through `wp_presentation`, and can queue frames with `wp_fifo_v1` and `wp_commit_timing_v1`.
57-- **Only what changed.** Damage-limited redraws, occlusion culling of hidden surfaces, idle outputs never redraw. Atomic KMS: frame, cursor, overlay and VRR in one commit.57+- **Only what changed.** NeferWL redraws only damaged regions, skips surfaces hidden behind opaque ones, and leaves idle outputs alone. Frame, cursor, overlay and VRR go to the kernel in one atomic commit.
58-- **Input for games.** Relative pointer, pointer constraints, keyboard shortcuts inhibit, 1 kHz+ non-blocking input, hardware cursor plane.58+- **Input.** Relative pointer, pointer constraints and keyboard shortcuts inhibit for games. Input at 1 kHz and more never waits on rendering, and the cursor has its own hardware plane.
59 - **X11 games.** Steam and Wine run through xwayland-satellite, started on the first X11 connection.59 - **X11 games.** Steam and Wine run through xwayland-satellite, started on the first X11 connection.
60 60
61-Requires Linux and Go 1.27.
62-
63 ## Hardware support61 ## Hardware support
64 62
65-NeferWL 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. Frames are composed on the GPU only: no CPU copy and no CPU wait for the GPU before a page flip.63+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.
66-
67-- **AMD GPUs:** developed and tested on AMD (radv).
68-- **NVIDIA GPUs:** untested. Explicit sync (`linux-drm-syncobj`), which NVIDIA drivers need, is implemented. Testers with NVIDIA hardware are welcome to report issues or contribute.
69-- **Intel GPUs:** untested.
70-
71-Build: `make build`.
72 64
73-Run: `neferwl --backend=headless --timeout 5s`65+- **AMD:** developed and tested on radv.
66+- **NVIDIA:** untested. Explicit sync, which NVIDIA drivers need, is implemented. Reports from NVIDIA users are welcome.
67+- **Intel:** untested.
74 68
75 ## Install (Arch Linux)69 ## Install (Arch Linux)
76 70
71+Building needs Go 1.27.
72+
77 ```sh73 ```sh
78 make pkg # builds dist/neferwl-*.pkg.tar.zst from HEAD74 make pkg # builds dist/neferwl-*.pkg.tar.zst from HEAD
79 sudo pacman -U dist/neferwl-*.pkg.tar.zst75 sudo pacman -U dist/neferwl-*.pkg.tar.zst
80 ```76 ```
81 77
82-The package installs `neferwl` and a **NeferWL** session for display managers (Ly, GDM, SDDM). The session runs `neferwl --session`, which shares `WAYLAND_DISPLAY` and `DISPLAY` with D-Bus and systemd user services, so portals and notification daemons reach it.78+The package installs `neferwl` and a **NeferWL** session for display managers such as Ly, GDM and SDDM. The session runs `neferwl --session`, which passes `WAYLAND_DISPLAY` and `DISPLAY` to D-Bus and systemd user services so that portals and notification daemons can connect.
83-
84-## Developing the bindings
85-
86-NeferWL depends on tagged releases of [purego-libwayland](https://github.com/bnema/purego-libwayland) and [purego-vulkan](https://github.com/bnema/purego-vulkan). To change them together with NeferWL, check them out next to it and use a Go workspace (not committed):
87-
88-```sh
89-go work init . ../purego-libwayland ../purego-vulkan
90-```
91 79
92-`make pkg` ignores the workspace and builds the versions in `go.mod`: tag a binding change and update `go.mod` before packaging it.80+On other distributions, `make build` builds `bin/neferwl`.
93 81
94 ## Configuration82 ## Configuration
95 83
96-Configuration lives at `$XDG_CONFIG_HOME/neferwl/config` (or `~/.config/neferwl/config`). Use `neferwl --config path` to select another file; `neferwl validate-config [path]` checks it without starting the compositor.84+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.
97-
98-One `key = value` per line; `#` starts a comment:
99 85
100-```text86+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.
101-keyboard.layout = fr
102-terminal = foot
103-bind.cmd+return = spawn-terminal
104-bind.ctrl+cmd+space = spawn fuzzel
105-startup = wl-paste --watch cliphist store
106-```
107 87
108-Every key, value and default bind is listed in [docs/config.md](docs/config.md); `examples/config` is a commented copy of the defaults. A missing file means defaults. An invalid line logs a warning and keeps that key's default; the rest of the file still applies. Every key applies live when the file changes, including the keyboard layout; each reload logs the keys that changed.88+[docs/config.md](docs/config.md) lists every key, action and default bind. `examples/config` is a commented copy of the defaults.
109 89
110 ## Running commands90 ## Running commands
111 91
112-NeferWL starts a program, not a shell line: `spawn` runs a binary or a script. Anything more than one command with arguments, such as pipes or variables, goes in a script that you write.92+`spawn <command>` binds, `startup = <command>` lines and `terminal = <command>` run one program directly, without a shell. NeferWL splits the line on spaces: the first word is the program, found in `PATH` or given as a path such as `/opt/tool/run`, and the other words are its arguments.
113-
114-`spawn <command>` binds, `startup = <command>` lines and `terminal = <command>` run one program directly, without a shell. The line is split on spaces: the first word is the program (looked up in `PATH`, or a path such as `/opt/tool/run`), the other words are its arguments.
115 93
116 ```text94 ```text
117 bind.ctrl+cmd+space = spawn fuzzel95 bind.ctrl+cmd+space = spawn fuzzel
@@ -119,9 +97,7 @@ startup = waybar
119 startup = wl-paste --watch cliphist store97 startup = wl-paste --watch cliphist store
120 ```98 ```
121 99
122-There is no shell, so pipes (`|`), `&&`, redirections, variables (`$HOME`), `~` and quotes have no special meaning: they reach the program as plain arguments. What you write is what runs.100+Pipes (`|`), `&&`, redirections, variables (`$HOME`), `~` and quotes have no special meaning: they reach the program as plain arguments. For those, write a script and run it:
123-
124-For anything more complex, write a script and run the script:
125 101
126 ```sh102 ```sh
127 #!/bin/sh103 #!/bin/sh
@@ -134,13 +110,13 @@ grim -g "$(slurp)" "$file" && wl-copy < "$file"
134 bind.cmd+shift+s = spawn screenshot-area110 bind.cmd+shift+s = spawn screenshot-area
135 ```111 ```
136 112
137-Make it executable (`chmod +x`) and put it in a directory of your `PATH`, such as `~/.local/bin`. The script can use any shell, including fish (`#!/usr/bin/env fish`).113+Make the script executable (`chmod +x`) and put it in a directory of your `PATH`, such as `~/.local/bin`. It can use any shell, including fish (`#!/usr/bin/env fish`).
138 114
139-Programs started by NeferWL get `WAYLAND_DISPLAY`, `DISPLAY` (see [X11 apps](#x11-apps)) and `NEFERWL_STATE` (see below). `startup` commands run once when the session starts; editing them takes effect at the next start.115+Programs started by NeferWL get `WAYLAND_DISPLAY`, `DISPLAY` (see [X11 apps](#x11-apps)) and `NEFERWL_STATE` (see below). `startup` commands run once when the session starts, so editing them takes effect at the next start.
140 116
141 ## Clipboard117 ## Clipboard
142 118
143-NeferWL supports the clipboard (`wl_data_device`), the primary selection (middle-click paste) and `ext_data_control_v1` for clipboard managers. With [cliphist](https://github.com/sentriz/cliphist), store every copy at startup and bind a picker script (see [Running commands](#running-commands)):119+NeferWL supports the clipboard (`wl_data_device`), the primary selection (middle-click paste) and `ext_data_control_v1` for clipboard managers. To keep a history with [cliphist](https://github.com/sentriz/cliphist), store every copy at startup and bind a picker script:
144 120
145 ```text121 ```text
146 startup = wl-paste --watch cliphist store122 startup = wl-paste --watch cliphist store
@@ -158,7 +134,7 @@ Drag and drop is not supported yet.
158 134
159 ## X11 apps135 ## X11 apps
160 136
161-X11 apps such as Steam and Wine run through [xwayland-satellite](https://github.com/Supreeeme/xwayland-satellite) 0.7 or later, found in `PATH`. NeferWL opens an X11 display, sets `DISPLAY` for the programs it starts, and runs xwayland-satellite when the first X11 app connects. If xwayland-satellite exits, the next X11 app starts it again.137+X11 apps such as Steam and Wine run through [xwayland-satellite](https://github.com/Supreeeme/xwayland-satellite) 0.7 or later, found in `PATH`. NeferWL opens an X11 display, sets `DISPLAY` for the programs it starts, and starts xwayland-satellite when the first X11 app connects. If xwayland-satellite exits, the next X11 app starts it again.
162 138
163 ```text139 ```text
164 xwayland = xwayland-satellite # the default; a path also works140 xwayland = xwayland-satellite # the default; a path also works
@@ -169,42 +145,68 @@ Do not start xwayland-satellite yourself with `startup`. Changing `xwayland` tak
169 145
170 ## State for scripts146 ## State for scripts
171 147
172-While it runs, NeferWL keeps its state in `$XDG_RUNTIME_DIR/neferwl/<wayland socket>.json` and passes that path to the programs it starts as `NEFERWL_STATE`. The file lists every output (active numbered workspace, workspace count, name of the workspace on screen), the focused output and window, and every window with its app ID, PID, output and workspace.148+While it runs, NeferWL writes its state to `$XDG_RUNTIME_DIR/neferwl/<wayland socket>.json` and passes that path to the programs it starts as `NEFERWL_STATE`. The file lists:
149+
150+- every output, with its active numbered workspace, workspace count and the name of the workspace on screen;
151+- the focused output and window;
152+- every window, with its app ID, PID, output and workspace.
173 153
174 ```sh154 ```sh
175 neferwl state # the whole state as JSON155 neferwl state # the whole state as JSON
176 neferwl state output-of "$PID" # the output of that process's window, or its nearest parent's156 neferwl state output-of "$PID" # the output of that process's window, or its nearest parent's
177 ```157 ```
178 158
179-`output-of` finds application windows only; bars have no single output.159+`output-of` finds application windows only, because a bar has no single output.
180 160
181 ## Try it (headless)161 ## Try it (headless)
182 162
183-Run `neferwl --backend=headless --screenshot /tmp/neferwl-shots`, then connect with163+The headless backend runs without a screen, for tests and quick checks:
184-`WAYLAND_DISPLAY=<logged name> foot`. `/tmp/neferwl-shots/latest.png` shows the current frame.164+
185-Use `--input path` (or `--input -` for stdin) to inject a headless script. Each line is165+```sh
186-`type text`, `key Super+Return` (also Shift, Ctrl, Alt or a bare key), `sleep 1s`, `move x y` (absolute output coordinates), `click [left|right|middle]`,166+neferwl --backend=headless --screenshot /tmp/neferwl-shots
187-`down button`, or `up button`. Cursor rendering is not implemented yet; pointer167+WAYLAND_DISPLAY=<name from the log> foot
188-focus after layout changes is updated on the next move.168+```
169+
170+`/tmp/neferwl-shots/latest.png` shows the current frame. `--timeout 5s` stops NeferWL after 5 seconds.
171+
172+`--input <path>`, or `--input -` for stdin, plays an input script. One command per line:
173+
174+- `type <text>`
175+- `key Super+Return` (Shift, Ctrl and Alt also work, as does a bare key)
176+- `sleep 1s`
177+- `move <x> <y>` (output coordinates)
178+- `click [left|right|middle]`, `down <button>`, `up <button>`
179+
180+The headless backend does not draw the cursor. After a layout change, pointer focus updates on the next move.
189 181
190 ## Why not Rust?182 ## Why not Rust?
191 183
192-Most Wayland compositors are written in C (wlroots, Sway, Mutter), C++ (KWin, Hyprland, gamescope) or Rust (niri, COSMIC). NeferWL is written in Go, by choice:184+Most Wayland compositors are written in C (wlroots, Sway, Mutter), C++ (KWin, Hyprland, gamescope) or Rust (niri, COSMIC). NeferWL uses Go on purpose:
193 185
194-- **Simple and readable.** Go is small and explicit. Goroutines and channels fit a compositor where each part (input, outputs, clients) owns its own state.186+- **Readable.** Go is small and explicit. Goroutines and channels fit a compositor where input, each output and the clients own their own state.
195-- **Fast builds, easy tooling.** A full build takes seconds; tests, the race detector, profiling and formatting come with the language.187+- **Tooling.** A full build takes seconds, and tests, the race detector, profiling and formatting come with the language.
196 - **No cgo.** NeferWL builds with `CGO_ENABLED=0`: libwayland, Vulkan and libinput are loaded at runtime through [purego](https://github.com/ebitengine/purego).188 - **No cgo.** NeferWL builds with `CGO_ENABLED=0`: libwayland, Vulkan and libinput are loaded at runtime through [purego](https://github.com/ebitengine/purego).
197-- **Fast enough.** With care for allocations and the garbage collector, the hot paths (input, rendering, buffer handling) perform close to native code.189+- **Speed.** With care for allocations and the garbage collector, input, rendering and buffer handling run close to native code.
190+
191+I like bringing more tools to the Go ecosystem. NeferWL grew its own libraries along the way, [purego-libwayland](https://github.com/bnema/purego-libwayland), [purego-vulkan](https://github.com/bnema/purego-vulkan) and [wlturbo](https://github.com/bnema/wlturbo), and other Go projects can use them.
192+
193+Go has no borrow checker, so the compiler does not catch memory and concurrency mistakes as Rust's does. NeferWL compensates with strict ownership (one goroutine owns each piece of state), the race detector on every test run, and protocol tests against real Wayland clients.
194+
195+## Developing the bindings
198 196
199-As a software engineer, my passion is bringing more tools to the Go ecosystem. NeferWL grew its own libraries along the way: purego-libwayland, purego-vulkan and wlturbo, to be published for others to reuse. Building them is part of the fun.197+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:
198+
199+```sh
200+go work init . ../purego-libwayland ../purego-vulkan
201+```
200 202
201-What Go does not give us is Rust's borrow checker: memory and concurrency mistakes are not caught at compile time. NeferWL makes up for it with strict ownership (one goroutine owns each piece of state), the race detector on every test run, and protocol tests against real Wayland clients.203+`make pkg` ignores the workspace and builds the versions in `go.mod`. Tag the binding change and update `go.mod` before packaging.
202 204
203 ## Contributing205 ## Contributing
204 206
205-Contributions are welcome. Open an issue first, for a bug fix or a feature, and wait until it is accepted before sending a pull request. Pull requests without an accepted issue will be closed.207+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.
206 208
207-Before a pull request, `make check`, `make race`, `make mocks-check` and `staticcheck ./...` must pass. Repository rules are in [AGENTS.md](AGENTS.md).209+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).
208 210
209 ## License211 ## License
210 212

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