Merge branch 'docs/readme-refocus' into feat/presentation
11 files · +224 -331
M AGENTS.md +2 -2
| @@ -26,10 +26,10 @@ Test doubles are always generated by Mockery v3 (`testify` template). Never hand | |||
| 26 | 26 | ||
| 27 | 27 | ## End-to-end tests (headless backend) | |
| 28 | 28 | ||
| 29 | - | Unit tests are not enough for user-facing behavior (binds, gestures, window placement, config keys). Test it end to end on the headless backend before declaring it done; see [docs/headless.md](docs/headless.md). | |
| 29 | + | Unit tests are not enough for user-facing behavior (binds, gestures, window placement, config keys). Test it end to end on the headless backend before declaring it done; see [docs/dev/headless.md](docs/dev/headless.md). | |
| 30 | 30 | ||
| 31 | 31 | - Build the branch binary (`CGO_ENABLED=0 go build -o <tmp>/neferwl ./cmd/neferwl`) and run it with a throwaway config: `<tmp>/neferwl --backend=headless --config <tmp>/config --no-xwayland --screenshot <tmp>/shots --input <script> --timeout 10s`. | |
| 32 | - | - Input scripts drive keys and gestures (`key Super+s`, `swipe4 down`, `sleep 1s`); see the table in docs/headless.md. | |
| 32 | + | - Input scripts drive keys and gestures (`key Super+s`, `swipe4 down`, `sleep 1s`); see the table in docs/dev/headless.md. | |
| 33 | 33 | - Logs go to `$XDG_STATE_HOME/neferwl/runs/headless/latest.log`, not stderr. Read the headless `WAYLAND_DISPLAY` from its `listening` entry. | |
| 34 | 34 | - Isolate from the user's live session: run the headless compositor, its clients and `neferwl state` with `env -u NEFERWL_STATE -u NEFERWL_SLOT WAYLAND_DISPLAY=<headless>`. `NEFERWL_STATE` wins over `WAYLAND_DISPLAY` and would read the live session's state. | |
| 35 | 35 | - Check results with `WAYLAND_DISPLAY=<headless> neferwl state` (windows, stash index/count, visibility) and the `latest.png` screenshot. | |
| @@ -26,10 +26,10 @@ Test doubles are always generated by Mockery v3 (`testify` template). Never hand | |||||
| 26 | 26 | ||||
| 27 | ## End-to-end tests (headless backend) | 27 | ## End-to-end tests (headless backend) | ||
| 28 | 28 | ||||
| 29 | - | Unit tests are not enough for user-facing behavior (binds, gestures, window placement, config keys). Test it end to end on the headless backend before declaring it done; see [docs/headless.md](docs/headless.md). | 29 | + | Unit tests are not enough for user-facing behavior (binds, gestures, window placement, config keys). Test it end to end on the headless backend before declaring it done; see [docs/dev/headless.md](docs/dev/headless.md). |
| 30 | 30 | ||||
| 31 | - Build the branch binary (`CGO_ENABLED=0 go build -o <tmp>/neferwl ./cmd/neferwl`) and run it with a throwaway config: `<tmp>/neferwl --backend=headless --config <tmp>/config --no-xwayland --screenshot <tmp>/shots --input <script> --timeout 10s`. | 31 | - Build the branch binary (`CGO_ENABLED=0 go build -o <tmp>/neferwl ./cmd/neferwl`) and run it with a throwaway config: `<tmp>/neferwl --backend=headless --config <tmp>/config --no-xwayland --screenshot <tmp>/shots --input <script> --timeout 10s`. | ||
| 32 | - | - Input scripts drive keys and gestures (`key Super+s`, `swipe4 down`, `sleep 1s`); see the table in docs/headless.md. | 32 | + | - Input scripts drive keys and gestures (`key Super+s`, `swipe4 down`, `sleep 1s`); see the table in docs/dev/headless.md. |
| 33 | - Logs go to `$XDG_STATE_HOME/neferwl/runs/headless/latest.log`, not stderr. Read the headless `WAYLAND_DISPLAY` from its `listening` entry. | 33 | - Logs go to `$XDG_STATE_HOME/neferwl/runs/headless/latest.log`, not stderr. Read the headless `WAYLAND_DISPLAY` from its `listening` entry. | ||
| 34 | - Isolate from the user's live session: run the headless compositor, its clients and `neferwl state` with `env -u NEFERWL_STATE -u NEFERWL_SLOT WAYLAND_DISPLAY=<headless>`. `NEFERWL_STATE` wins over `WAYLAND_DISPLAY` and would read the live session's state. | 34 | - Isolate from the user's live session: run the headless compositor, its clients and `neferwl state` with `env -u NEFERWL_STATE -u NEFERWL_SLOT WAYLAND_DISPLAY=<headless>`. `NEFERWL_STATE` wins over `WAYLAND_DISPLAY` and would read the live session's state. | ||
| 35 | - Check results with `WAYLAND_DISPLAY=<headless> neferwl state` (windows, stash index/count, visibility) and the `latest.png` screenshot. | 35 | - Check results with `WAYLAND_DISPLAY=<headless> neferwl state` (windows, stash index/count, visibility) and the `latest.png` screenshot. | ||
M README.md +78 -265
| @@ -4,90 +4,33 @@ | |||
| 4 | 4 | <a href="LICENSE"><img src="https://img.shields.io/badge/license-GPLv3-blue?style=flat-square" alt="License: GPLv3"></a> | |
| 5 | 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 | 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 | 7 | </p> | |
| 9 | 8 | ||
| 10 | 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 | 14 | > [!WARNING] | |
| 13 | 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 | 17 | ## 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. | |
| 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/dev/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). | |
| 58 | 25 | ||
| 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 | + | Written in Go, without cgo. Each piece of state has one owner goroutine, connected to the others by channels; hot paths reuse memory and client buffers stay off the Go heap. See [Architecture](docs/dev/architecture.md) for the concurrency model, the GC strategy and the reusable Go libraries. | |
| 70 | 27 | ||
| 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). | |
| 28 | + | 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. | |
| 84 | 29 | ||
| 85 | 30 | ## Install | |
| 86 | 31 | ||
| 87 | 32 | ### Arch Linux | |
| 88 | 33 | ||
| 89 | - | Choose an AUR package: | |
| 90 | - | ||
| 91 | 34 | ```sh | |
| 92 | 35 | paru -S neferwl-bin # latest release, pre-built for x86_64 and aarch64 | |
| 93 | 36 | paru -S neferwl-git # latest main, built from source | |
| @@ -110,17 +53,13 @@ sha256sum -c --ignore-missing checksums.txt | |||
| 110 | 53 | sudo apt install ./neferwl_<version>_amd64.deb | |
| 111 | 54 | ``` | |
| 112 | 55 | ||
| 113 | - | Optional tools: | |
| 114 | - | ||
| 115 | 56 | ```sh | |
| 116 | 57 | sudo apt install foot fuzzel xdg-desktop-portal-gtk xdg-desktop-portal-gnome | |
| 117 | 58 | ``` | |
| 118 | 59 | ||
| 119 | 60 | 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 | 61 | ||
| 121 | - | ### What the packages provide | |
| 122 | - | ||
| 123 | - | The Arch and Debian packages provide: | |
| 62 | + | The packages provide: | |
| 124 | 63 | ||
| 125 | 64 | - `neferwl` with CAP_SYS_NICE for real-time scheduling. | |
| 126 | 65 | - A **NeferWL** session for display managers such as Ly, GDM and SDDM. | |
| @@ -134,18 +73,6 @@ Copy `/usr/share/doc/neferwl/config.example` to `~/.config/neferwl/config` to st | |||
| 134 | 73 | ||
| 135 | 74 | 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 | 75 | ||
| 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 | 76 | ## Configuration | |
| 150 | 77 | ||
| 151 | 78 | The default file is `$XDG_CONFIG_HOME/neferwl/config`, or `~/.config/neferwl/config`. | |
| @@ -167,211 +94,97 @@ workspace.dev.column.2 = 33%, foot | |||
| 167 | 94 | bind.cmd+d = workspace dev | |
| 168 | 95 | ``` | |
| 169 | 96 | ||
| 170 | - | See [Configuration](docs/config.md) for all keys, actions and default bindings, or [examples/config](examples/config) for commented defaults. | |
| 171 | - | ||
| 172 | 97 | - Use one `key = value` per line; `#` starts a comment. | |
| 173 | 98 | - Missing keys use defaults. Invalid lines warn and keep that key's default; other lines still apply. | |
| 174 | 99 | - Save to reload. The log lists changed keys. | |
| 175 | 100 | - Use `neferwl --config <path>` to select a file, or `neferwl validate-config [path]` to check it without starting the compositor. | |
| 176 | 101 | ||
| 177 | - | ### Default configuration | |
| 178 | - | ||
| 179 | - | <details> | |
| 180 | - | <summary>All default settings and key bindings</summary> | |
| 102 | + | ### Common settings | |
| 103 | + | ||
| 104 | + | | Key | Default | Meaning | | |
| 105 | + | | --- | --- | --- | | |
| 106 | + | | `keyboard.layout` | system | xkb layout, e.g. `fr` | | |
| 107 | + | | `keyboard.cmd` | `super` | Modifier used as `cmd` | | |
| 108 | + | | `terminal` | `$TERMINAL`, then `foot` | Terminal command | | |
| 109 | + | | `terminal.auto-open` | `first` | Open on initial numbered workspace; also `all` or `off` | | |
| 110 | + | | `startup` | none | Session-start commands, separated by `;` | | |
| 111 | + | | `layout.max-columns` | `2` | Columns sharing the screen | | |
| 112 | + | | `layout.overflow` | `cascade` | Overflow layout: `cascade`, `scroll`, `fixed` | | |
| 113 | + | | `layout.gaps` | `0` | Space between windows | | |
| 114 | + | | `output.<name>` | `preferred` | Mode: `WxH@Hz`, `preferred` or `off` | | |
| 115 | + | | `output.<name>.scale` | `1` | Fractional scale, 1 to 4 | | |
| 116 | + | | `output.<name>.hdr` | `off` | HDR on capable displays | | |
| 117 | + | | `touchpad.natural-scroll` | `off` | Content follows fingers | | |
| 118 | + | | `stash.width` | `80` | Selected stash window width, percent | | |
| 119 | + | | `hint.enable` | `off` | Experimental keyboard clicking; restart required | | |
| 120 | + | | `animations` | `off` | Experimental layout and swipe animations | | |
| 121 | + | ||
| 122 | + | ### Default key bindings | |
| 123 | + | ||
| 124 | + | `cmd` means Super by default. Hint mode has no default binding. | |
| 125 | + | ||
| 126 | + | | Keys | Action | | |
| 127 | + | | --- | --- | | |
| 128 | + | | `cmd+return` | Open terminal | | |
| 129 | + | | `ctrl+cmd+space` | Launch fuzzel | | |
| 130 | + | | `cmd+q` | Close window | | |
| 131 | + | | `cmd+arrows` / `cmd+h/j/k/l` | Focus columns and windows | | |
| 132 | + | | `cmd+shift+arrows` | Move columns and windows | | |
| 133 | + | | `cmd+o` | Toggle overview | | |
| 134 | + | | `cmd+s` | Show/hide stash | | |
| 135 | + | | `cmd+shift+s` | Move window into/out of stash | | |
| 136 | + | | `cmd+shift+f` | Toggle fullscreen | | |
| 137 | + | | `cmd+shift+space` | Toggle floating | | |
| 138 | + | | `cmd+1`…`9` (physical digit keys) | Focus numbered workspace | | |
| 139 | + | | `cmd+pageup/pagedown` | Previous/next workspace | | |
| 140 | + | ||
| 141 | + | See [Configuration](docs/config.md) for all keys, actions, bindings and automatic-terminal behavior, or [examples/config](examples/config) for commented defaults. | |
| 181 | 142 | ||
| 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. | |
| 143 | + | ## Features | |
| 183 | 144 | ||
| 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 | - | ``` | |
| 145 | + | ### Display and games | |
| 328 | 146 | ||
| 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`. | |
| 147 | + | - 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. | |
| 148 | + | - Explicit sync carries client fences to the GPU and KMS, enabled by default with no flag. HDR10 on capable displays includes configurable SDR brightness. | |
| 149 | + | - Separate workspaces and fractional scale per output; workspaces move when a monitor is unplugged and return when it reconnects. | |
| 150 | + | - 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. | |
| 151 | + | - Steam and other X11 clients run through xwayland-satellite; Wine can run natively on Wayland. | |
| 332 | 152 | ||
| 333 | - | Per-output defaults (replace `<name>` with a connector such as `DP-1`): | |
| 153 | + | ### Windows and workspaces | |
| 334 | 154 | ||
| 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 | - | ``` | |
| 155 | + | - [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. | |
| 156 | + | - Numbered workspaces appear as needed; named workspaces can start commands and place windows in predefined columns. | |
| 157 | + | - Overview and stash keep workspaces and set-aside windows close at hand, with keyboard, pointer and touchpad navigation. | |
| 344 | 158 | ||
| 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. | |
| 159 | + | ### Desktop integration | |
| 346 | 160 | ||
| 347 | - | See [Configuration](docs/config.md) for accepted values and workspace options. | |
| 161 | + | 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 | 162 | ||
| 349 | - | </details> | |
| 163 | + | 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 | 164 | ||
| 351 | - | ### Automatic terminal | |
| 165 | + | ## Hardware support | |
| 352 | 166 | ||
| 353 | - | The command comes from `terminal`, then `$TERMINAL`, then `foot`. | |
| 167 | + | 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 | 168 | ||
| 355 | - | | `terminal.auto-open` | Behaviour | | |
| 169 | + | | GPU | Status | | |
| 356 | 170 | | --- | --- | | |
| 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 | | |
| 171 | + | | AMD | Developed and tested on radv | | |
| 172 | + | | NVIDIA | Untested; explicit sync is implemented, but support is incomplete | | |
| 173 | + | | Intel | Untested | | |
| 360 | 174 | ||
| 361 | - | `--no-terminal` disables automatic opening for one run; `spawn-terminal` still works. Launched programs inherit `$SHELL`, `$EDITOR` and `$VISUAL`. | |
| 175 | + | Reports from NVIDIA and Intel users are welcome. | |
| 362 | 176 | ||
| 363 | 177 | ## Documentation | |
| 364 | 178 | ||
| 365 | - | - [Configuration](docs/config.md): keys, actions, bindings and HDR. | |
| 366 | - | - [Desktop integration](docs/desktop.md): sessions, clipboard, input methods, X11 apps and scripting. | |
| 367 | - | - [Session locking](docs/session-lock.md): external lockers, recovery and security limits. | |
| 179 | + | - [Configuration](docs/config.md): keys, actions, bindings, layouts, overview, stash, hint mode and HDR. | |
| 180 | + | - [Desktop integration](docs/desktop.md): sessions, clipboard, input methods, X11 apps, session locking and scripting. | |
| 368 | 181 | - [Screen capture](docs/capture.md): protocols, indicator, executable allowlist and portals. | |
| 369 | - | - [Headless mode](docs/headless.md): screenshots, input scripts and input-method testing without a screen. | |
| 370 | - | - [Performance](docs/performance.md): VRR, real-time priority, profiling and frame stats. | |
| 182 | + | - [Performance](docs/performance.md): VRR and real-time priority. | |
| 183 | + | - [Developer documentation](docs/dev/index.md): architecture, headless testing, profiling and frame stats. | |
| 371 | 184 | ||
| 372 | 185 | ## Contributing | |
| 373 | 186 | ||
| 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. | |
| 187 | + | Issues are welcome, including NVIDIA and Intel reports. Please open an issue before a large pull request; small fixes are fine directly. | |
| 375 | 188 | ||
| 376 | 189 | Before submitting, run `make check`, `make race`, `make mocks-check` and `staticcheck ./...`. See [AGENTS.md](AGENTS.md) for repository rules. | |
| 377 | 190 | ||
| @@ -4,90 +4,33 @@ | |||||
| 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 NeferWL | 17 | ## 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/dev/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 | - | ## Features | 26 | + | Written in Go, without cgo. Each piece of state has one owner goroutine, connected to the others by channels; hot paths reuse memory and client buffers stay off the Go heap. See [Architecture](docs/dev/architecture.md) for the concurrency model, the GC strategy and the reusable Go libraries. |
| 60 | - | ||||
| 61 | - | ### Display and games | |||
| 62 | - | ||||
| 63 | - | - **Direct scanout:** eligible fullscreen buffers go straight to the display, without composition. | |||
| 64 | - | - **VRR and tearing:** enabled by default for supported fullscreen use (`render.vrr`, `render.tearing`). Tearing requires direct scanout and a client request. | |||
| 65 | - | - **Explicit sync:** client fences go to the GPU and KMS. Enabled by default, with no flag to set. | |||
| 66 | - | - **HDR:** HDR10 on capable displays, with configurable brightness for SDR content. | |||
| 67 | - | - **Multi-monitor:** separate workspaces and fractional scale per output. Workspaces move when a monitor is unplugged and return when it reconnects. | |||
| 68 | - | - **Hidden apps rest:** a window you cannot see (another workspace, a hidden stash, scrolled off screen, under a fullscreen window) gets frame callbacks once per second and the suspended state. Browsers and games stop drawing frames nobody sees. A window being captured keeps its output's refresh rate. | |||
| 69 | - | - **X11 apps:** Steam and other X11 clients run through xwayland-satellite. Wine can run natively on Wayland. | |||
| 70 | 27 | ||||
| 71 | - | ### Windows and workspaces | 28 | + | 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 | 29 | ||||
| 85 | ## Install | 30 | ## Install | ||
| 86 | 31 | ||||
| 87 | ### Arch Linux | 32 | ### Arch Linux | ||
| 88 | 33 | ||||
| 89 | - | Choose an AUR package: | |||
| 90 | - | ||||
| 91 | ```sh | 34 | ```sh | ||
| 92 | paru -S neferwl-bin # latest release, pre-built for x86_64 and aarch64 | 35 | paru -S neferwl-bin # latest release, pre-built for x86_64 and aarch64 | ||
| 93 | paru -S neferwl-git # latest main, built from source | 36 | paru -S neferwl-git # latest main, built from source | ||
| @@ -110,17 +53,13 @@ sha256sum -c --ignore-missing checksums.txt | |||||
| 110 | sudo apt install ./neferwl_<version>_amd64.deb | 53 | sudo apt install ./neferwl_<version>_amd64.deb | ||
| 111 | ``` | 54 | ``` | ||
| 112 | 55 | ||||
| 113 | - | Optional tools: | |||
| 114 | - | ||||
| 115 | ```sh | 56 | ```sh | ||
| 116 | sudo apt install foot fuzzel xdg-desktop-portal-gtk xdg-desktop-portal-gnome | 57 | sudo apt install foot fuzzel xdg-desktop-portal-gtk xdg-desktop-portal-gnome | ||
| 117 | ``` | 58 | ``` | ||
| 118 | 59 | ||||
| 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. | 60 | 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 | 61 | ||||
| 121 | - | ### What the packages provide | 62 | + | The packages provide: |
| 122 | - | ||||
| 123 | - | The Arch and Debian packages provide: | |||
| 124 | 63 | ||||
| 125 | - `neferwl` with CAP_SYS_NICE for real-time scheduling. | 64 | - `neferwl` with CAP_SYS_NICE for real-time scheduling. | ||
| 126 | - A **NeferWL** session for display managers such as Ly, GDM and SDDM. | 65 | - A **NeferWL** session for display managers such as Ly, GDM and SDDM. | ||
| @@ -134,18 +73,6 @@ Copy `/usr/share/doc/neferwl/config.example` to `~/.config/neferwl/config` to st | |||||
| 134 | 73 | ||||
| 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`. | 74 | 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 | 75 | ||||
| 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 | ## Configuration | 76 | ## Configuration | ||
| 150 | 77 | ||||
| 151 | The default file is `$XDG_CONFIG_HOME/neferwl/config`, or `~/.config/neferwl/config`. | 78 | The default file is `$XDG_CONFIG_HOME/neferwl/config`, or `~/.config/neferwl/config`. | ||
| @@ -167,211 +94,97 @@ workspace.dev.column.2 = 33%, foot | |||||
| 167 | bind.cmd+d = workspace dev | 94 | bind.cmd+d = workspace dev | ||
| 168 | ``` | 95 | ``` | ||
| 169 | 96 | ||||
| 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. | 97 | - 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. | 98 | - 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. | 99 | - 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. | 100 | - Use `neferwl --config <path>` to select a file, or `neferwl validate-config [path]` to check it without starting the compositor. | ||
| 176 | 101 | ||||
| 177 | - | ### Default configuration | 102 | + | ### Common settings |
| 178 | - | 103 | + | ||
| 179 | - | <details> | 104 | + | | Key | Default | Meaning | |
| 180 | - | <summary>All default settings and key bindings</summary> | 105 | + | | --- | --- | --- | |
| 106 | + | | `keyboard.layout` | system | xkb layout, e.g. `fr` | | |||
| 107 | + | | `keyboard.cmd` | `super` | Modifier used as `cmd` | | |||
| 108 | + | | `terminal` | `$TERMINAL`, then `foot` | Terminal command | | |||
| 109 | + | | `terminal.auto-open` | `first` | Open on initial numbered workspace; also `all` or `off` | | |||
| 110 | + | | `startup` | none | Session-start commands, separated by `;` | | |||
| 111 | + | | `layout.max-columns` | `2` | Columns sharing the screen | | |||
| 112 | + | | `layout.overflow` | `cascade` | Overflow layout: `cascade`, `scroll`, `fixed` | | |||
| 113 | + | | `layout.gaps` | `0` | Space between windows | | |||
| 114 | + | | `output.<name>` | `preferred` | Mode: `WxH@Hz`, `preferred` or `off` | | |||
| 115 | + | | `output.<name>.scale` | `1` | Fractional scale, 1 to 4 | | |||
| 116 | + | | `output.<name>.hdr` | `off` | HDR on capable displays | | |||
| 117 | + | | `touchpad.natural-scroll` | `off` | Content follows fingers | | |||
| 118 | + | | `stash.width` | `80` | Selected stash window width, percent | | |||
| 119 | + | | `hint.enable` | `off` | Experimental keyboard clicking; restart required | | |||
| 120 | + | | `animations` | `off` | Experimental layout and swipe animations | | |||
| 121 | + | ||||
| 122 | + | ### Default key bindings | |||
| 123 | + | ||||
| 124 | + | `cmd` means Super by default. Hint mode has no default binding. | |||
| 125 | + | ||||
| 126 | + | | Keys | Action | | |||
| 127 | + | | --- | --- | | |||
| 128 | + | | `cmd+return` | Open terminal | | |||
| 129 | + | | `ctrl+cmd+space` | Launch fuzzel | | |||
| 130 | + | | `cmd+q` | Close window | | |||
| 131 | + | | `cmd+arrows` / `cmd+h/j/k/l` | Focus columns and windows | | |||
| 132 | + | | `cmd+shift+arrows` | Move columns and windows | | |||
| 133 | + | | `cmd+o` | Toggle overview | | |||
| 134 | + | | `cmd+s` | Show/hide stash | | |||
| 135 | + | | `cmd+shift+s` | Move window into/out of stash | | |||
| 136 | + | | `cmd+shift+f` | Toggle fullscreen | | |||
| 137 | + | | `cmd+shift+space` | Toggle floating | | |||
| 138 | + | | `cmd+1`…`9` (physical digit keys) | Focus numbered workspace | | |||
| 139 | + | | `cmd+pageup/pagedown` | Previous/next workspace | | |||
| 140 | + | ||||
| 141 | + | See [Configuration](docs/config.md) for all keys, actions, bindings and automatic-terminal behavior, or [examples/config](examples/config) for commented defaults. | |||
| 181 | 142 | ||||
| 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. | 143 | + | ## Features |
| 183 | 144 | ||||
| 184 | - | ```text | 145 | + | ### 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 | 146 | ||||
| 329 | - | The focus flash marks the window, column or output an action changes with a brief | 147 | + | - 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. | 148 | + | - 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`. | 149 | + | - Separate workspaces and fractional scale per output; workspaces move when a monitor is unplugged and return when it reconnects. |
| 150 | + | - 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. | |||
| 151 | + | - Steam and other X11 clients run through xwayland-satellite; Wine can run natively on Wayland. | |||
| 332 | 152 | ||||
| 333 | - | Per-output defaults (replace `<name>` with a connector such as `DP-1`): | 153 | + | ### Windows and workspaces |
| 334 | 154 | ||||
| 335 | - | ```text | 155 | + | - [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> = preferred | 156 | + | - Numbered workspaces appear as needed; named workspaces can start commands and place windows in predefined columns. |
| 337 | - | output.<name>.scale = 1 | 157 | + | - 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 | 158 | ||||
| 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. | 159 | + | ### Desktop integration |
| 346 | 160 | ||||
| 347 | - | See [Configuration](docs/config.md) for accepted values and workspace options. | 161 | + | 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 | 162 | ||||
| 349 | - | </details> | 163 | + | 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 | 164 | ||||
| 351 | - | ### Automatic terminal | 165 | + | ## Hardware support |
| 352 | 166 | ||||
| 353 | - | The command comes from `terminal`, then `$TERMINAL`, then `foot`. | 167 | + | 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 | 168 | ||||
| 355 | - | | `terminal.auto-open` | Behaviour | | 169 | + | | GPU | Status | |
| 356 | | --- | --- | | 170 | | --- | --- | | ||
| 357 | - | | `first` | Open once on the initial numbered workspace | | 171 | + | | AMD | Developed and tested on radv | |
| 358 | - | | `all` | Open on each visible empty workspace without predefined slots | | 172 | + | | NVIDIA | Untested; explicit sync is implemented, but support is incomplete | |
| 359 | - | | `off` | Do not open automatically | | 173 | + | | Intel | Untested | |
| 360 | 174 | ||||
| 361 | - | `--no-terminal` disables automatic opening for one run; `spawn-terminal` still works. Launched programs inherit `$SHELL`, `$EDITOR` and `$VISUAL`. | 175 | + | Reports from NVIDIA and Intel users are welcome. |
| 362 | 176 | ||||
| 363 | ## Documentation | 177 | ## Documentation | ||
| 364 | 178 | ||||
| 365 | - | - [Configuration](docs/config.md): keys, actions, bindings and HDR. | 179 | + | - [Configuration](docs/config.md): keys, actions, bindings, layouts, overview, stash, hint mode and HDR. |
| 366 | - | - [Desktop integration](docs/desktop.md): sessions, clipboard, input methods, X11 apps and scripting. | 180 | + | - [Desktop integration](docs/desktop.md): sessions, clipboard, input methods, X11 apps, session locking and scripting. |
| 367 | - | - [Session locking](docs/session-lock.md): external lockers, recovery and security limits. | |||
| 368 | - [Screen capture](docs/capture.md): protocols, indicator, executable allowlist and portals. | 181 | - [Screen capture](docs/capture.md): protocols, indicator, executable allowlist and portals. | ||
| 369 | - | - [Headless mode](docs/headless.md): screenshots, input scripts and input-method testing without a screen. | 182 | + | - [Performance](docs/performance.md): VRR and real-time priority. |
| 370 | - | - [Performance](docs/performance.md): VRR, real-time priority, profiling and frame stats. | 183 | + | - [Developer documentation](docs/dev/index.md): architecture, headless testing, profiling and frame stats. |
| 371 | 184 | ||||
| 372 | ## Contributing | 185 | ## Contributing | ||
| 373 | 186 | ||||
| 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. | 187 | + | Issues are welcome, including NVIDIA and Intel reports. Please open an issue before a large pull request; small fixes are fine directly. |
| 375 | 188 | ||||
| 376 | Before submitting, run `make check`, `make race`, `make mocks-check` and `staticcheck ./...`. See [AGENTS.md](AGENTS.md) for repository rules. | 189 | Before submitting, run `make check`, `make race`, `make mocks-check` and `staticcheck ./...`. See [AGENTS.md](AGENTS.md) for repository rules. | ||
| 377 | 190 | ||||
M docs/config.md +30 -0
| @@ -7,6 +7,8 @@ neferwl --config <path> # use another file | |||
| 7 | 7 | 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 | 12 | ## Format | |
| 11 | 13 | ||
| 12 | 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 | 99 | switches and mouse focus do not trigger a flash. | |
| 98 | 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 | 130 | ## Window rules | |
| 101 | 131 | ||
| 102 | 132 | 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 file | 7 | 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 | ## Format | 12 | ## 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 rules | 130 | ## 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. | ||
M docs/desktop.md +24 -1
| @@ -60,7 +60,30 @@ xwayland = off # no X11 support | |||
| 60 | 60 | ||
| 61 | 61 | ## Session locking | |
| 62 | 62 | ||
| 63 | - | Use an external locker such as swaylock or hyprlock. See [Session locking](session-lock.md). | |
| 63 | + | NeferWL supports `ext-session-lock-v1` with an external locker such as swaylock or hyprlock. The locker checks the password; NeferWL hides the desktop and blocks input. | |
| 64 | + | ||
| 65 | + | ### While locked | |
| 66 | + | ||
| 67 | + | - Only the locker gets input. Binds, clipboard requests and new captures are refused. | |
| 68 | + | - Outputs show black and the locker's surfaces only. | |
| 69 | + | - Running captures stop. | |
| 70 | + | - `Ctrl+Alt+Backspace` is disabled. VT switching still works. | |
| 71 | + | - A locker that uses a screenshot as background (hyprlock `screenshot`, swaylock-effects `--screenshots`) must be in the [capture allowlist](capture.md#the-allowlist). | |
| 72 | + | ||
| 73 | + | The locker is told `locked` only once every output shows a protected frame or is off. | |
| 74 | + | ||
| 75 | + | ### If the locker dies | |
| 76 | + | ||
| 77 | + | The session stays locked and black. Start a new locker to take over. There is no emergency unlock key and no timeout. | |
| 78 | + | ||
| 79 | + | A locker that exits right after unlocking must do a Wayland roundtrip first, or its exit counts as a crash and the session stays locked. | |
| 80 | + | ||
| 81 | + | ### Limits | |
| 82 | + | ||
| 83 | + | - NeferWL does not authenticate: no PAM, no stored credentials. | |
| 84 | + | - Any process running as your user can start its own locker and unlock. | |
| 85 | + | - Clipboard data and window contents a client received before the lock stay with that client. | |
| 86 | + | - The [state file](desktop.md#state-for-scripts) is not cleared while locked. | |
| 64 | 87 | ||
| 65 | 88 | ## Screen capture | |
| 66 | 89 | ||
| @@ -60,7 +60,30 @@ xwayland = off # no X11 support | |||||
| 60 | 60 | ||||
| 61 | ## Session locking | 61 | ## Session locking | ||
| 62 | 62 | ||||
| 63 | - | Use an external locker such as swaylock or hyprlock. See [Session locking](session-lock.md). | 63 | + | NeferWL supports `ext-session-lock-v1` with an external locker such as swaylock or hyprlock. The locker checks the password; NeferWL hides the desktop and blocks input. |
| 64 | + | ||||
| 65 | + | ### While locked | |||
| 66 | + | ||||
| 67 | + | - Only the locker gets input. Binds, clipboard requests and new captures are refused. | |||
| 68 | + | - Outputs show black and the locker's surfaces only. | |||
| 69 | + | - Running captures stop. | |||
| 70 | + | - `Ctrl+Alt+Backspace` is disabled. VT switching still works. | |||
| 71 | + | - A locker that uses a screenshot as background (hyprlock `screenshot`, swaylock-effects `--screenshots`) must be in the [capture allowlist](capture.md#the-allowlist). | |||
| 72 | + | ||||
| 73 | + | The locker is told `locked` only once every output shows a protected frame or is off. | |||
| 74 | + | ||||
| 75 | + | ### If the locker dies | |||
| 76 | + | ||||
| 77 | + | The session stays locked and black. Start a new locker to take over. There is no emergency unlock key and no timeout. | |||
| 78 | + | ||||
| 79 | + | A locker that exits right after unlocking must do a Wayland roundtrip first, or its exit counts as a crash and the session stays locked. | |||
| 80 | + | ||||
| 81 | + | ### Limits | |||
| 82 | + | ||||
| 83 | + | - NeferWL does not authenticate: no PAM, no stored credentials. | |||
| 84 | + | - Any process running as your user can start its own locker and unlock. | |||
| 85 | + | - Clipboard data and window contents a client received before the lock stay with that client. | |||
| 86 | + | - The [state file](desktop.md#state-for-scripts) is not cleared while locked. | |||
| 64 | 87 | ||||
| 65 | ## Screen capture | 88 | ## Screen capture | ||
| 66 | 89 | ||||
A docs/dev/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 | + | ||||
R docs/headless.md → docs/dev/headless.md +1 -1 renamed, 96% similar
| @@ -12,7 +12,7 @@ WAYLAND_DISPLAY=<name from the log> foot | |||
| 12 | 12 | for testing `focus-monitor-left/right` and moving columns between outputs. | |
| 13 | 13 | - With several outputs, each writes to `<dir>/HEADLESS-<n>/`. | |
| 14 | 14 | - `--timeout 5s` stops NeferWL after 5 seconds. | |
| 15 | - | - `grim` and recorders work too; see [Screen capture](capture.md). | |
| 15 | + | - `grim` and recorders work too; see [Screen capture](../capture.md). | |
| 16 | 16 | ||
| 17 | 17 | ## Recording a video | |
| 18 | 18 | ||
| @@ -12,7 +12,7 @@ WAYLAND_DISPLAY=<name from the log> foot | |||||
| 12 | for testing `focus-monitor-left/right` and moving columns between outputs. | 12 | for testing `focus-monitor-left/right` and moving columns between outputs. | ||
| 13 | - With several outputs, each writes to `<dir>/HEADLESS-<n>/`. | 13 | - With several outputs, each writes to `<dir>/HEADLESS-<n>/`. | ||
| 14 | - `--timeout 5s` stops NeferWL after 5 seconds. | 14 | - `--timeout 5s` stops NeferWL after 5 seconds. | ||
| 15 | - | - `grim` and recorders work too; see [Screen capture](capture.md). | 15 | + | - `grim` and recorders work too; see [Screen capture](../capture.md). |
| 16 | 16 | ||||
| 17 | ## Recording a video | 17 | ## Recording a video | ||
| 18 | 18 | ||||
A docs/dev/index.md +8 -0
| @@ -0,0 +1,8 @@ | |||
| 1 | + | # Developer documentation | |
| 2 | + | ||
| 3 | + | These guides cover NeferWL's internals and tools for development, testing and performance investigation. For configuration and desktop setup, see the [user documentation](../index.md). | |
| 4 | + | ||
| 5 | + | - [Architecture](architecture.md): owner goroutines, GC strategy and reusable purego libraries. | |
| 6 | + | - [Headless mode](headless.md): screenshots, scripted input and testing without a screen. | |
| 7 | + | - [Profiling](profiling.md): CPU and allocation profiling, logging and frame statistics. | |
| 8 | + | - [Repository rules](../../AGENTS.md): architecture constraints, test requirements and checks before merge. | |
| @@ -0,0 +1,8 @@ | |||||
| 1 | + | # Developer documentation | |||
| 2 | + | ||||
| 3 | + | These guides cover NeferWL's internals and tools for development, testing and performance investigation. For configuration and desktop setup, see the [user documentation](../index.md). | |||
| 4 | + | ||||
| 5 | + | - [Architecture](architecture.md): owner goroutines, GC strategy and reusable purego libraries. | |||
| 6 | + | - [Headless mode](headless.md): screenshots, scripted input and testing without a screen. | |||
| 7 | + | - [Profiling](profiling.md): CPU and allocation profiling, logging and frame statistics. | |||
| 8 | + | - [Repository rules](../../AGENTS.md): architecture constraints, test requirements and checks before merge. | |||
A docs/dev/profiling.md +36 -0
| @@ -0,0 +1,36 @@ | |||
| 1 | + | # Profiling | |
| 2 | + | ||
| 3 | + | Run these from another TTY or over SSH: | |
| 4 | + | ||
| 5 | + | ```sh | |
| 6 | + | # CPU, Go and C frames | |
| 7 | + | perf record -g -p (pgrep -f bin/neferwl) -- sleep 8 | |
| 8 | + | perf report --children --percent-limit 1 | |
| 9 | + | ||
| 10 | + | # Allocations | |
| 11 | + | neferwl --backend=drm --pprof=localhost:6060 | |
| 12 | + | go tool pprof -sample_index=alloc_objects -top http://localhost:6060/debug/pprof/heap | |
| 13 | + | ||
| 14 | + | # One line per GC cycle | |
| 15 | + | GODEBUG=gctrace=1 neferwl --backend=drm | |
| 16 | + | ``` | |
| 17 | + | ||
| 18 | + | `make tty` serves pprof on `localhost:6060` by default (`make tty PPROF=` turns it off). | |
| 19 | + | ||
| 20 | + | Logs are in `~/.local/state/neferwl/runs/<backend>/`, with `latest.log` pointing at the newest run. `--debug=all` enables every category except the noisy `drm-flip`, `input-motion`, `input-keys`, `wayland-damage` (one `client damage` entry per client commit) and `render-damage` (one `frame damage` entry per rendered frame); name them to turn them on. | |
| 21 | + | ||
| 22 | + | ## Frame stats | |
| 23 | + | ||
| 24 | + | Every 10 s, each output logs a `stats` entry: | |
| 25 | + | ||
| 26 | + | | Field | Meaning | | |
| 27 | + | |---|---| | |
| 28 | + | | `missed_vblanks` | Frames that flipped more than 1.5 refresh periods late (not counted under VRR) | | |
| 29 | + | | `max_flip_interval_ms` | Longest gap between two flips | | |
| 30 | + | | `max_commit_delay_ms` | Longest wait from "frame due" to commit | | |
| 31 | + | | `late_fences` | Missed vblanks where the GPU finished too late (needs `--debug=drm-flip`) | | |
| 32 | + | | `redrawn_pixels` | Pixels drawn | | |
| 33 | + | ||
| 34 | + | If outputs report flips faster than the display loop reads them, wayland merges them and logs a warning with `merged` at most every 10 s. | |
| 35 | + | ||
| 36 | + | Missed vblanks with `late_fences` mean the GPU was late. Without `late_fences` but with a large `max_commit_delay_ms`, the CPU was late. | |
| @@ -0,0 +1,36 @@ | |||||
| 1 | + | # Profiling | |||
| 2 | + | ||||
| 3 | + | Run these from another TTY or over SSH: | |||
| 4 | + | ||||
| 5 | + | ```sh | |||
| 6 | + | # CPU, Go and C frames | |||
| 7 | + | perf record -g -p (pgrep -f bin/neferwl) -- sleep 8 | |||
| 8 | + | perf report --children --percent-limit 1 | |||
| 9 | + | ||||
| 10 | + | # Allocations | |||
| 11 | + | neferwl --backend=drm --pprof=localhost:6060 | |||
| 12 | + | go tool pprof -sample_index=alloc_objects -top http://localhost:6060/debug/pprof/heap | |||
| 13 | + | ||||
| 14 | + | # One line per GC cycle | |||
| 15 | + | GODEBUG=gctrace=1 neferwl --backend=drm | |||
| 16 | + | ``` | |||
| 17 | + | ||||
| 18 | + | `make tty` serves pprof on `localhost:6060` by default (`make tty PPROF=` turns it off). | |||
| 19 | + | ||||
| 20 | + | Logs are in `~/.local/state/neferwl/runs/<backend>/`, with `latest.log` pointing at the newest run. `--debug=all` enables every category except the noisy `drm-flip`, `input-motion`, `input-keys`, `wayland-damage` (one `client damage` entry per client commit) and `render-damage` (one `frame damage` entry per rendered frame); name them to turn them on. | |||
| 21 | + | ||||
| 22 | + | ## Frame stats | |||
| 23 | + | ||||
| 24 | + | Every 10 s, each output logs a `stats` entry: | |||
| 25 | + | ||||
| 26 | + | | Field | Meaning | | |||
| 27 | + | |---|---| | |||
| 28 | + | | `missed_vblanks` | Frames that flipped more than 1.5 refresh periods late (not counted under VRR) | | |||
| 29 | + | | `max_flip_interval_ms` | Longest gap between two flips | | |||
| 30 | + | | `max_commit_delay_ms` | Longest wait from "frame due" to commit | | |||
| 31 | + | | `late_fences` | Missed vblanks where the GPU finished too late (needs `--debug=drm-flip`) | | |||
| 32 | + | | `redrawn_pixels` | Pixels drawn | | |||
| 33 | + | ||||
| 34 | + | If outputs report flips faster than the display loop reads them, wayland merges them and logs a warning with `merged` at most every 10 s. | |||
| 35 | + | ||||
| 36 | + | Missed vblanks with `late_fences` mean the GPU was late. Without `late_fences` but with a large `max_commit_delay_ms`, the CPU was late. | |||
A docs/index.md +10 -0
| @@ -0,0 +1,10 @@ | |||
| 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, screen capture and performance. | |
| 4 | + | ||
| 5 | + | - [Configuration](config.md): keys, actions, default bindings, layouts and HDR. | |
| 6 | + | - [Desktop integration](desktop.md): sessions, clipboard, input methods, X11 apps, session locking and scripting. | |
| 7 | + | - [Screen capture](capture.md): protocols, indicator, executable allowlist and portals. | |
| 8 | + | - [Performance](performance.md): resource use, VRR and real-time priority. | |
| 9 | + | ||
| 10 | + | Developer documentation: [docs/dev](dev/index.md) | |
| @@ -0,0 +1,10 @@ | |||||
| 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, screen capture and performance. | |||
| 4 | + | ||||
| 5 | + | - [Configuration](config.md): keys, actions, default bindings, layouts and HDR. | |||
| 6 | + | - [Desktop integration](desktop.md): sessions, clipboard, input methods, X11 apps, session locking and scripting. | |||
| 7 | + | - [Screen capture](capture.md): protocols, indicator, executable allowlist and portals. | |||
| 8 | + | - [Performance](performance.md): resource use, VRR and real-time priority. | |||
| 9 | + | ||||
| 10 | + | Developer documentation: [docs/dev](dev/index.md) | |||
M docs/performance.md +1 -36
| @@ -22,39 +22,4 @@ sudo setcap cap_sys_nice+ep bin/neferwl | |||
| 22 | 22 | ||
| 23 | 23 | `performance.realtime = off` turns it off. With the capability set, the loader ignores `LD_PRELOAD` and `LD_LIBRARY_PATH`, and debuggers need root to attach. | |
| 24 | 24 | ||
| 25 | - | ## Profiling | |
| 26 | - | ||
| 27 | - | Run these from another TTY or over SSH: | |
| 28 | - | ||
| 29 | - | ```sh | |
| 30 | - | # CPU, Go and C frames | |
| 31 | - | perf record -g -p (pgrep -f bin/neferwl) -- sleep 8 | |
| 32 | - | perf report --children --percent-limit 1 | |
| 33 | - | ||
| 34 | - | # Allocations | |
| 35 | - | neferwl --backend=drm --pprof=localhost:6060 | |
| 36 | - | go tool pprof -sample_index=alloc_objects -top http://localhost:6060/debug/pprof/heap | |
| 37 | - | ||
| 38 | - | # One line per GC cycle | |
| 39 | - | GODEBUG=gctrace=1 neferwl --backend=drm | |
| 40 | - | ``` | |
| 41 | - | ||
| 42 | - | `make tty` serves pprof on `localhost:6060` by default (`make tty PPROF=` turns it off). | |
| 43 | - | ||
| 44 | - | Logs are in `~/.local/state/neferwl/runs/<backend>/`, with `latest.log` pointing at the newest run. `--debug=all` enables every category except the noisy `drm-flip`, `input-motion`, `input-keys`, `wayland-damage` (one `client damage` entry per client commit) and `render-damage` (one `frame damage` entry per rendered frame); name them to turn them on. | |
| 45 | - | ||
| 46 | - | ## Frame stats | |
| 47 | - | ||
| 48 | - | Every 10 s, each output logs a `stats` entry: | |
| 49 | - | ||
| 50 | - | | Field | Meaning | | |
| 51 | - | |---|---| | |
| 52 | - | | `missed_vblanks` | Frames that flipped more than 1.5 refresh periods late (not counted under VRR) | | |
| 53 | - | | `max_flip_interval_ms` | Longest gap between two flips | | |
| 54 | - | | `max_commit_delay_ms` | Longest wait from "frame due" to commit | | |
| 55 | - | | `late_fences` | Missed vblanks where the GPU finished too late (needs `--debug=drm-flip`) | | |
| 56 | - | | `redrawn_pixels` | Pixels drawn | | |
| 57 | - | ||
| 58 | - | If outputs report flips faster than the display loop reads them, wayland merges them and logs a warning with `merged` at most every 10 s. | |
| 59 | - | ||
| 60 | - | Missed vblanks with `late_fences` mean the GPU was late. Without `late_fences` but with a large `max_commit_delay_ms`, the CPU was late. | |
| 25 | + | For profiling tools and frame statistics, see [Profiling](dev/profiling.md). | |
| @@ -22,39 +22,4 @@ sudo setcap cap_sys_nice+ep bin/neferwl | |||||
| 22 | 22 | ||||
| 23 | `performance.realtime = off` turns it off. With the capability set, the loader ignores `LD_PRELOAD` and `LD_LIBRARY_PATH`, and debuggers need root to attach. | 23 | `performance.realtime = off` turns it off. With the capability set, the loader ignores `LD_PRELOAD` and `LD_LIBRARY_PATH`, and debuggers need root to attach. | ||
| 24 | 24 | ||||
| 25 | - | ## Profiling | 25 | + | For profiling tools and frame statistics, see [Profiling](dev/profiling.md). |
| 26 | - | ||||
| 27 | - | Run these from another TTY or over SSH: | |||
| 28 | - | ||||
| 29 | - | ```sh | |||
| 30 | - | # CPU, Go and C frames | |||
| 31 | - | perf record -g -p (pgrep -f bin/neferwl) -- sleep 8 | |||
| 32 | - | perf report --children --percent-limit 1 | |||
| 33 | - | ||||
| 34 | - | # Allocations | |||
| 35 | - | neferwl --backend=drm --pprof=localhost:6060 | |||
| 36 | - | go tool pprof -sample_index=alloc_objects -top http://localhost:6060/debug/pprof/heap | |||
| 37 | - | ||||
| 38 | - | # One line per GC cycle | |||
| 39 | - | GODEBUG=gctrace=1 neferwl --backend=drm | |||
| 40 | - | ``` | |||
| 41 | - | ||||
| 42 | - | `make tty` serves pprof on `localhost:6060` by default (`make tty PPROF=` turns it off). | |||
| 43 | - | ||||
| 44 | - | Logs are in `~/.local/state/neferwl/runs/<backend>/`, with `latest.log` pointing at the newest run. `--debug=all` enables every category except the noisy `drm-flip`, `input-motion`, `input-keys`, `wayland-damage` (one `client damage` entry per client commit) and `render-damage` (one `frame damage` entry per rendered frame); name them to turn them on. | |||
| 45 | - | ||||
| 46 | - | ## Frame stats | |||
| 47 | - | ||||
| 48 | - | Every 10 s, each output logs a `stats` entry: | |||
| 49 | - | ||||
| 50 | - | | Field | Meaning | | |||
| 51 | - | |---|---| | |||
| 52 | - | | `missed_vblanks` | Frames that flipped more than 1.5 refresh periods late (not counted under VRR) | | |||
| 53 | - | | `max_flip_interval_ms` | Longest gap between two flips | | |||
| 54 | - | | `max_commit_delay_ms` | Longest wait from "frame due" to commit | | |||
| 55 | - | | `late_fences` | Missed vblanks where the GPU finished too late (needs `--debug=drm-flip`) | | |||
| 56 | - | | `redrawn_pixels` | Pixels drawn | | |||
| 57 | - | ||||
| 58 | - | If outputs report flips faster than the display loop reads them, wayland merges them and logs a warning with `merged` at most every 10 s. | |||
| 59 | - | ||||
| 60 | - | Missed vblanks with `late_fences` mean the GPU was late. Without `late_fences` but with a large `max_commit_delay_ms`, the CPU was late. | |||
D docs/session-lock.md +0 -26
| @@ -1,26 +0,0 @@ | |||
| 1 | - | # Session locking | |
| 2 | - | ||
| 3 | - | NeferWL supports `ext-session-lock-v1` with an external locker such as swaylock or hyprlock. The locker checks the password; NeferWL hides the desktop and blocks input. | |
| 4 | - | ||
| 5 | - | ## While locked | |
| 6 | - | ||
| 7 | - | - Only the locker gets input. Binds, clipboard requests and new captures are refused. | |
| 8 | - | - Outputs show black and the locker's surfaces only. | |
| 9 | - | - Running captures stop. | |
| 10 | - | - `Ctrl+Alt+Backspace` is disabled. VT switching still works. | |
| 11 | - | - A locker that uses a screenshot as background (hyprlock `screenshot`, swaylock-effects `--screenshots`) must be in the [capture allowlist](capture.md#the-allowlist). | |
| 12 | - | ||
| 13 | - | The locker is told `locked` only once every output shows a protected frame or is off. | |
| 14 | - | ||
| 15 | - | ## If the locker dies | |
| 16 | - | ||
| 17 | - | The session stays locked and black. Start a new locker to take over. There is no emergency unlock key and no timeout. | |
| 18 | - | ||
| 19 | - | A locker that exits right after unlocking must do a Wayland roundtrip first, or its exit counts as a crash and the session stays locked. | |
| 20 | - | ||
| 21 | - | ## Limits | |
| 22 | - | ||
| 23 | - | - NeferWL does not authenticate: no PAM, no stored credentials. | |
| 24 | - | - Any process running as your user can start its own locker and unlock. | |
| 25 | - | - Clipboard data and window contents a client received before the lock stay with that client. | |
| 26 | - | - The [state file](desktop.md#state-for-scripts) is not cleared while locked. | |
| @@ -1,26 +0,0 @@ | |||||
| 1 | - | # Session locking | |||
| 2 | - | ||||
| 3 | - | NeferWL supports `ext-session-lock-v1` with an external locker such as swaylock or hyprlock. The locker checks the password; NeferWL hides the desktop and blocks input. | |||
| 4 | - | ||||
| 5 | - | ## While locked | |||
| 6 | - | ||||
| 7 | - | - Only the locker gets input. Binds, clipboard requests and new captures are refused. | |||
| 8 | - | - Outputs show black and the locker's surfaces only. | |||
| 9 | - | - Running captures stop. | |||
| 10 | - | - `Ctrl+Alt+Backspace` is disabled. VT switching still works. | |||
| 11 | - | - A locker that uses a screenshot as background (hyprlock `screenshot`, swaylock-effects `--screenshots`) must be in the [capture allowlist](capture.md#the-allowlist). | |||
| 12 | - | ||||
| 13 | - | The locker is told `locked` only once every output shows a protected frame or is off. | |||
| 14 | - | ||||
| 15 | - | ## If the locker dies | |||
| 16 | - | ||||
| 17 | - | The session stays locked and black. Start a new locker to take over. There is no emergency unlock key and no timeout. | |||
| 18 | - | ||||
| 19 | - | A locker that exits right after unlocking must do a Wayland roundtrip first, or its exit counts as a crash and the session stays locked. | |||
| 20 | - | ||||
| 21 | - | ## Limits | |||
| 22 | - | ||||
| 23 | - | - NeferWL does not authenticate: no PAM, no stored credentials. | |||
| 24 | - | - Any process running as your user can start its own locker and unlock. | |||
| 25 | - | - Clipboard data and window contents a client received before the lock stay with that client. | |||
| 26 | - | - The [state file](desktop.md#state-for-scripts) is not cleared while locked. | |||