A Wayland compositor that spends its frames on your apps, not on itself.
> [!WARNING] > **Early alpha, but I use it every day.** Developed and tested mostly on AMD CPUs and GPUs. NVIDIA support is incomplete and untested. Expect bugs and breaking config changes. ## Why NeferWL I wanted a compositor I would never have to think about. It takes the newest Wayland protocols and kernel features as they land, gives games the whole GPU, and otherwise stays out of sight. It is small on purpose, and it will stay small. No blur, no shadows, no rounded corners, no themes. No built-in bar or wallpaper either: bring your own, any layer-shell client works. Animations are short, cheap and off by default (experimental, `animations = on`). About 40–80 MB of RAM with two 4K monitors, and almost no CPU while the screen does not change. See [Performance](docs/performance.md). ## OK, but why Go? Most Wayland compositors are written in C (wlroots, Sway, Mutter), C++ (KWin, Hyprland, gamescope) or Rust (niri, COSMIC). The usual objection to Go is the garbage collector. It is a fair one, so here is how NeferWL deals with it, and why Go turns out to fit a compositor well. ### A compositor is a concurrency problem A compositor sits in the middle of many independent streams that never stop: keyboard and pointer events, client requests and buffer commits, one vblank and page-flip cycle per monitor, frame fences from the GPU, presentation feedback, screen captures, config reloads, hotplug, idle and lock state. None of them should wait on another. A slow client must not delay a flip, and rendering must not delay input. Go has this built in. Goroutines are cheap, channels carry messages between them, and the runtime spreads them over every CPU core. NeferWL follows one idiom everywhere: **each piece of state has one owner goroutine, and others talk to it over channels.** - **Input** runs on its own thread, reads libinput and sends events to core. - **Core** owns windows, workspaces and focus. It turns input and client events into layouts and scenes. - **The Wayland server** owns client connections. It sends client events and surface content, and receives commands back. - **Each output** owns its DRM state, renders its scenes with Vulkan, commits frames and reports flips and presentation times. - **Capture, config, idle, sessions and leases** each run in their own goroutine. About 35 channels connect them, all created in one place, so the full data flow is easy to read. There is no mutex on window state, no lock ordering to get wrong, and the race detector checks every test. When CAP_SYS_NICE is granted, the input and output goroutines lock their OS thread and request real-time scheduling, so a busy CPU cannot delay a cursor move or a flip. ### Keeping the garbage collector out of the way - **The hot path does not allocate.** Frame callbacks, presentation reports, DRM flips, Vulkan submissions and surface updates reuse memory owned by the goroutine that handles them. `make check` runs allocation guards that fail the build when one of these paths starts allocating. - **The GC has almost nothing to collect.** Client buffers live in shared and GPU memory, not on the Go heap, so the live heap is a few megabytes. Playing a 4K HDR video with 65 tiled subsurfaces committing every frame, GC takes under 9 % of a process that itself takes 13 % of a core. See [Performance](docs/performance.md). - **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. - **Work only when something changes.** Damage tracking redraws only what changed, surfaces hidden behind opaque ones are skipped, and an idle output does nothing. - **Tuned on real hardware.** Under VRR, cursor moves ride on the game's next frame instead of forcing their own refresh, and game frames keep a short gap after each flip so the panel does not fall back to its slowest rate. ### A pleasure to work on A full build takes seconds, and tests, race detection, profiling and formatting come with the language. I also like bringing more tools to the Go ecosystem. NeferWL grew its own libraries along the way, and other Go projects can use them: - [purego-libwayland](https://github.com/bnema/purego-libwayland): libwayland-server without cgo. - [purego-vulkan](https://github.com/bnema/purego-vulkan): Vulkan without cgo, generated from `vk.xml`. - [go-wayland-bindings](https://github.com/bnema/go-wayland-bindings): Wayland protocol bindings, generated from the upstream XML. - [wlturbo](https://github.com/bnema/wlturbo): a fast Wayland client. - [neferclient](https://github.com/bnema/neferclient): a client toolkit on top of them: connection, outputs, surface roles, seat and dmabuf presentation. ## Features ### Display and games - **Direct scanout:** eligible fullscreen buffers go straight to the display, without composition. - **VRR and tearing:** enabled by default for supported fullscreen use (`render.vrr`, `render.tearing`). Tearing requires direct scanout and a client request. - **Explicit sync:** client fences go to the GPU and KMS. Enabled by default, with no flag to set. - **HDR:** HDR10 on capable displays, with configurable brightness for SDR content. - **Multi-monitor:** separate workspaces and fractional scale per output. Workspaces move when a monitor is unplugged and return when it reconnects. - **X11 apps:** Steam and other X11 clients run through xwayland-satellite. Wine can run natively on Wayland. ### Windows and workspaces - **Column tiling:** scroll horizontally, as in PaperWM and niri, or keep columns on screen in a fixed layout. Choose per output or workspace. - **Overview:** `cmd+o` or a four-finger swipe up shows live workspace previews. Select a window with the keyboard, touchpad or mouse. - **Stash:** `cmd+s` toggles a floating strip of windows set aside for the current workspace. `cmd+shift+s` moves a window into or out of it. - **Workspaces:** numbered workspaces appear as needed. Named workspaces can start commands and place their windows in predefined columns. ### Desktop integration Bring your own desktop UI: any layer-shell client, such as Noctalia or Waybar, works. No built-in bar, launcher or notifications; input methods like fcitx5 work through their standard protocols. For capture and idle management, see [nefercap](https://github.com/bnema/nefercap) and [neferafk](https://github.com/bnema/neferafk). 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). ## Install ### Arch Linux Choose an AUR package: ```sh paru -S neferwl-bin # latest release, pre-built for x86_64 and aarch64 paru -S neferwl-git # latest main, built from source ``` Or install from a checkout with Go 1.27: ```sh make install # build a package from HEAD and install it with pacman ``` `make pkg` builds the package without installing it. The package provides: - `neferwl` with CAP_SYS_NICE for real-time scheduling. - A **NeferWL** session for display managers such as Ly, GDM and SDDM. - `neferwl-session`, which starts `neferwl.service` and manages `graphical-session.target` so desktop services start and stop with the session. Running `neferwl --session` directly also exports the display environment to D-Bus and systemd user services. See [Desktop integration](docs/desktop.md) for session setup. ### Other distributions 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`. ## Hardware support NeferWL requires Linux, atomic KMS and a Vulkan 1.3 device that can export images as dmabufs. It reports an error at startup if these requirements are not met. | GPU | Status | | --- | --- | | AMD | Developed and tested on radv | | NVIDIA | Untested; explicit sync is implemented, but support is incomplete | | Intel | Untested | Reports from NVIDIA and Intel users are welcome. ## Configuration The default file is `$XDG_CONFIG_HOME/neferwl/config`, or `~/.config/neferwl/config`. ```text keyboard.layout = fr terminal.auto-open = first startup = waybar output.DP-1 = 3840x2160@144 output.DP-1.scale = 1.5 layout.max-columns = 3 # Start and place an editor and terminal when this workspace is opened. workspace.dev.monitor = DP-1 workspace.dev.column.1 = 67%, code --new-window workspace.dev.column.2 = 33%, foot bind.cmd+d = workspace dev ``` See [Configuration](docs/config.md) for all keys, actions and default bindings, or [examples/config](examples/config) for commented defaults. - Use one `key = value` per line; `#` starts a comment. - Missing keys use defaults. Invalid lines warn and keep that key's default; other lines still apply. - Save to reload. The log lists changed keys. - Use `neferwl --config