headless.md RawHistoryFolder

Headless mode

Runs NeferWL without a screen, for tests and quick checks:

neferwl --backend=headless --screenshot /tmp/neferwl-shots
WAYLAND_DISPLAY=<name from the log> foot
  • latest.png shows the current frame; frames.ffconcat records frame timing. Every frame is also saved as frame-NNNNNN.png, so the directory grows. latest.png is a hard link to the newest frame, so replace it rather than editing it in place.
  • --size 640x360,640x360 creates two outputs, arranged left to right, for testing focus-monitor-left/right and moving columns between outputs.
  • With several outputs, each writes to <dir>/HEADLESS-<n>/.
  • --timeout 5s stops NeferWL after 5 seconds.
  • grim and recorders work too; see Screen capture.

Recording a video

Convert the screenshots and their real-time timing index into a video:

ffmpeg -f concat -safe 0 -i /tmp/shots/frames.ffconcat -vf "fps=60,format=yuv420p" -c:v libx264 -crf 18 demo.mp4

For a GIF, convert demo.mp4 with ffmpeg's palettegen and paletteuse filters.

Demo recording

Run tools/demo/record.sh. It requires Go 1.27, rootless Docker, ffmpeg, network access and an AMD GPU. It builds the Arch image (apps, tesseract, AT-SPI) and the compositor binary, then records the 1080p README demo. The compositor runs headless inside a disposable container, with the binary and a work directory bind-mounted and /dev/dri passed for the GPU. The image only carries Mesa's radeon Vulkan driver, and hint scans need the sync files that a hardware driver exports; the script stops early without an AMD render node. Firefox opens a live DuckDuckGo page, so the recording needs the network and its content varies. A FIFO feeds input.txt while the script launches the apps on the same clock. The hint step makes Firefox fullscreen and labels the page with hint; hint-click <words> then types the label of the link that shows those words, found from the scan's dump and an OCR read of the still page (hint-read). The recording only counts if the compositor stops on the quit key that ends input.txt; reaching its --timeout first fails the run as truncated. Temporary captures are removed by default; set KEEP_CAPTURE=1 to keep successful runs. The recording and poster replace docs/media/demo.mp4 and docs/media/demo.png.

Input scripts

--input <file>, or --input - for stdin, plays one command per line:

Command Effect
type <text> Type text
key Super+Return Press a key, with optional Shift, Ctrl, Alt or Super
hold Super+h 1s Press a key like key, hold it, then release it
press Super, release Super Hold or release a modifier across mouse actions (Super, Shift, Ctrl, Alt)
sleep 1s Wait
move <x> <y> Move the pointer, in output coordinates
click [left|right|middle], down <button>, up <button> Mouse buttons
swipe up|down|left|right Three-finger flick
swipe4 up|down Four-finger flick

Testing input methods

examples/testime sends a preedit string, then commits text, each time an app enables text input:

WAYLAND_DISPLAY=<name> go run ./examples/testime -preedit nihon -commit 日本

-count <n> exits after n commits; -delay sets the time between preedit and commit.

X11 check

make e2e-xwayland runs the end-to-end test of the built-in window manager (xwayland = builtin). It is behind the xwayland build tag, so go test ./... and make check only compile it (go vet -tags xwayland). It needs Xwayland on PATH and a GPU, and skips without Xwayland. The clipboard test also needs wl-copy and wl-paste (wl-clipboard) and skips without them; they run with the headless WAYLAND_DISPLAY and need no extra focus step, since the focused probe window is enough for wl-copy --foreground to set the selection.

The test starts a headless NeferWL with a real Xwayland on the first free X display (a live session's :0 stays untouched) and runs internal/app/testdata/x11probe, a small X11 client (-class, -title, -size, -sleep, -at X,Y) that paints its window green, prints mapped WxH and focus, and exits 0 on WM_DELETE_WINDOW. With -override it also maps a magenta override-redirect window (-override-at, -override-size, -override-type menu|tooltip, -override-for, -override-shape, -override-dismiss-on-focusout) and prints override mapped, override unmapped or dismissed. -fullscreen (with -fullscreen-delay, -fullscreen-off) requests _NET_WM_STATE_FULLSCREEN by client message and prints fullscreen WxH / fullscreen off WxH when the window manager publishes the state; -keyboard-grab calls XGrabKeyboard and prints grab. Windowless modes: -copy TEXT|@file (with -copy-sel CLIPBOARD|PRIMARY, -copy-for) owns a selection and serves TARGETS, UTF8_STRING, STRING and TEXT, with INCR above 64 KiB, and prints copy owned; -paste CLIPBOARD|PRIMARY prints paste "<text>", paste len=N sha256=H above 4 KiB, or paste none; -xsettings prints xsettings serial=N Xft/DPI=… Gdk/WindowScalingFactor=… Gdk/UnscaledDPI=… Gtk/CursorThemeName=… Gtk/CursorThemeSize=… or xsettings none; -root-cursor prints cursor WxH from XFixes GetCursorImage. -at also sends a ConfigureRequest with the window rect before the map, like games do. It checks:

  • the state lists one visible X11Probe window with the probe's pid, and the screenshot is mostly green inside its tile;
  • the probe receives focus;
  • a second probe tiles beside the first;
  • key Super+q closes the focused probe within 2 seconds and the state drops to one window;
  • an override-redirect menu is drawn magenta above the green toplevel at its X position, is not listed in the state, leaves focus on the toplevel and disappears from the screenshot once the probe unmaps it;
  • a menu is dismissed when its owner gets FocusOut because another X11 window took the focus;
  • a menu with an empty input shape is still drawn and still not a window;
  • a fullscreen request 1.5 s after the map fills the output (all green, no gap) and Super+Shift+f leaves it; the client can also leave it itself; a request at the map stays tiled (the 1 s grace);
  • with the keyboard grab, Super+q does not close a fullscreen focused client and works again once it leaves fullscreen; on a windowed client it closes it;
  • on two outputs, a window created at the second output's origin opens on HEADLESS-2 and fullscreens there;
  • clipboard: wl-copy text of 4 KiB and 1 MiB (INCR) is read by x11probe -paste, and an x11probe -copy of the same sizes is read byte-exact by wl-paste, for CLIPBOARD and PRIMARY; wl-copy --clear gives paste none;
  • XSETTINGS: serial, Xft/DPI=98304 and Gdk/WindowScalingFactor=1 at any output scale (also with output.HEADLESS-1.scale = 2), and the cursor theme and size from XCURSOR_THEME/XCURSOR_SIZE; xwayland.xsettings = off leaves no owner;
  • root cursor: GetCursorImage over the root is the theme arrow at the theme size (a throwaway theme in XCURSOR_PATH);
  • a window rule rule.games.app-id = steam_app_\d+ floats an X11 window of class steam_app_413150, and not another class.

Not covered headless: relative pointer lock (needs a real pointer device) and Xwayland output hotplug (--size is fixed); these are unit-tested in the wayland adapter.

Run it by hand with go test -tags xwayland -race ./internal/app -run TestXwayland -count=1 -v.

Colour check

make color-check renders examples/testpattern on a headless output and checks the colours, in SDR and in HDR (--headless-hdr). It needs a GPU and /dev/udmabuf, and is not part of make check.

On a real screen, compare by eye:

WAYLAND_DISPLAY=<name> go run ./examples/testpattern -mode hdr -fullscreen   # or -mode sdr
source
1# Headless mode
2
3Runs NeferWL without a screen, for tests and quick checks:
4
5```sh
6neferwl --backend=headless --screenshot /tmp/neferwl-shots
7WAYLAND_DISPLAY=<name from the log> foot
8```
9
10- `latest.png` shows the current frame; `frames.ffconcat` records frame timing. Every frame is also saved as `frame-NNNNNN.png`, so the directory grows. `latest.png` is a hard link to the newest frame, so replace it rather than editing it in place.
11- `--size 640x360,640x360` creates two outputs, arranged left to right,
12  for testing `focus-monitor-left/right` and moving columns between outputs.
13- With several outputs, each writes to `<dir>/HEADLESS-<n>/`.
14- `--timeout 5s` stops NeferWL after 5 seconds.
15- `grim` and recorders work too; see [Screen capture](../capture.md).
16
17## Recording a video
18
19Convert the screenshots and their real-time timing index into a video:
20
21```sh
22ffmpeg -f concat -safe 0 -i /tmp/shots/frames.ffconcat -vf "fps=60,format=yuv420p" -c:v libx264 -crf 18 demo.mp4
23```
24
25For a GIF, convert `demo.mp4` with ffmpeg's `palettegen` and `paletteuse` filters.
26
27### Demo recording
28Run `tools/demo/record.sh`. It requires Go 1.27, rootless Docker, ffmpeg, network access and an AMD GPU.
29It builds the Arch image (apps, tesseract, AT-SPI) and the compositor binary, then records the 1080p README demo.
30The compositor runs headless inside a disposable container, with the binary and a work directory bind-mounted and `/dev/dri` passed for the GPU.
31The image only carries Mesa's radeon Vulkan driver, and hint scans need the sync files that a hardware driver exports; the script stops early without an AMD render node.
32Firefox opens a live DuckDuckGo page, so the recording needs the network and its content varies.
33A FIFO feeds `input.txt` while the script launches the apps on the same clock.
34The hint step makes Firefox fullscreen and labels the page with `hint`; `hint-click <words>` then types the label of the link that shows those words, found from the scan's dump and an OCR read of the still page (`hint-read`).
35The recording only counts if the compositor stops on the quit key that ends `input.txt`; reaching its `--timeout` first fails the run as truncated.
36Temporary captures are removed by default; set `KEEP_CAPTURE=1` to keep successful runs.
37The recording and poster replace `docs/media/demo.mp4` and `docs/media/demo.png`.
38
39## Input scripts
40
41`--input <file>`, or `--input -` for stdin, plays one command per line:
42
43| Command | Effect |
44|---|---|
45| `type <text>` | Type text |
46| `key Super+Return` | Press a key, with optional Shift, Ctrl, Alt or Super |
47| `hold Super+h 1s` | Press a key like `key`, hold it, then release it |
48| `press Super`, `release Super` | Hold or release a modifier across mouse actions (`Super`, `Shift`, `Ctrl`, `Alt`) |
49| `sleep 1s` | Wait |
50| `move <x> <y>` | Move the pointer, in output coordinates |
51| `click [left\|right\|middle]`, `down <button>`, `up <button>` | Mouse buttons |
52| `swipe up\|down\|left\|right` | Three-finger flick |
53| `swipe4 up\|down` | Four-finger flick |
54
55## Testing input methods
56
57`examples/testime` sends a preedit string, then commits text, each time an app enables text input:
58
59```sh
60WAYLAND_DISPLAY=<name> go run ./examples/testime -preedit nihon -commit 日本
61```
62
63`-count <n>` exits after n commits; `-delay` sets the time between preedit and commit.
64
65## X11 check
66
67`make e2e-xwayland` runs the end-to-end test of the built-in window manager (`xwayland = builtin`). It is behind the `xwayland` build tag, so `go test ./...` and `make check` only compile it (`go vet -tags xwayland`). It needs `Xwayland` on `PATH` and a GPU, and skips without Xwayland. The clipboard test also needs `wl-copy` and `wl-paste` (wl-clipboard) and skips without them; they run with the headless `WAYLAND_DISPLAY` and need no extra focus step, since the focused probe window is enough for `wl-copy --foreground` to set the selection.
68
69The test starts a headless NeferWL with a real `Xwayland` on the first free X display (a live session's `:0` stays untouched) and runs `internal/app/testdata/x11probe`, a small X11 client (`-class`, `-title`, `-size`, `-sleep`, `-at X,Y`) that paints its window green, prints `mapped WxH` and `focus`, and exits 0 on `WM_DELETE_WINDOW`. With `-override` it also maps a magenta override-redirect window (`-override-at`, `-override-size`, `-override-type menu|tooltip`, `-override-for`, `-override-shape`, `-override-dismiss-on-focusout`) and prints `override mapped`, `override unmapped` or `dismissed`. `-fullscreen` (with `-fullscreen-delay`, `-fullscreen-off`) requests `_NET_WM_STATE_FULLSCREEN` by client message and prints `fullscreen WxH` / `fullscreen off WxH` when the window manager publishes the state; `-keyboard-grab` calls `XGrabKeyboard` and prints `grab`. Windowless modes: `-copy TEXT|@file` (with `-copy-sel CLIPBOARD|PRIMARY`, `-copy-for`) owns a selection and serves `TARGETS`, `UTF8_STRING`, `STRING` and `TEXT`, with INCR above 64 KiB, and prints `copy owned`; `-paste CLIPBOARD|PRIMARY` prints `paste "<text>"`, `paste len=N sha256=H` above 4 KiB, or `paste none`; `-xsettings` prints `xsettings serial=N Xft/DPI=… Gdk/WindowScalingFactor=… Gdk/UnscaledDPI=… Gtk/CursorThemeName=… Gtk/CursorThemeSize=…` or `xsettings none`; `-root-cursor` prints `cursor WxH` from XFixes `GetCursorImage`. `-at` also sends a ConfigureRequest with the window rect before the map, like games do. It checks:
70
71- the state lists one visible `X11Probe` window with the probe's pid, and the screenshot is mostly green inside its tile;
72- the probe receives focus;
73- a second probe tiles beside the first;
74- `key Super+q` closes the focused probe within 2 seconds and the state drops to one window;
75- an override-redirect menu is drawn magenta above the green toplevel at its X position, is not listed in the state, leaves focus on the toplevel and disappears from the screenshot once the probe unmaps it;
76- a menu is dismissed when its owner gets `FocusOut` because another X11 window took the focus;
77- a menu with an empty input shape is still drawn and still not a window;
78- a fullscreen request 1.5 s after the map fills the output (all green, no gap) and `Super+Shift+f` leaves it; the client can also leave it itself; a request at the map stays tiled (the 1 s grace);
79- with the keyboard grab, `Super+q` does not close a fullscreen focused client and works again once it leaves fullscreen; on a windowed client it closes it;
80- on two outputs, a window created at the second output's origin opens on `HEADLESS-2` and fullscreens there;
81- clipboard: `wl-copy` text of 4 KiB and 1 MiB (INCR) is read by `x11probe -paste`, and an `x11probe -copy` of the same sizes is read byte-exact by `wl-paste`, for CLIPBOARD and PRIMARY; `wl-copy --clear` gives `paste none`;
82- XSETTINGS: serial, `Xft/DPI=98304` and `Gdk/WindowScalingFactor=1` at any output scale (also with `output.HEADLESS-1.scale = 2`), and the cursor theme and size from `XCURSOR_THEME`/`XCURSOR_SIZE`; `xwayland.xsettings = off` leaves no owner;
83- root cursor: `GetCursorImage` over the root is the theme arrow at the theme size (a throwaway theme in `XCURSOR_PATH`);
84- a window rule `rule.games.app-id = steam_app_\d+` floats an X11 window of class `steam_app_413150`, and not another class.
85
86Not covered headless: relative pointer lock (needs a real pointer device) and Xwayland output hotplug (`--size` is fixed); these are unit-tested in the `wayland` adapter.
87
88Run it by hand with `go test -tags xwayland -race ./internal/app -run TestXwayland -count=1 -v`.
89
90## Colour check
91
92`make color-check` renders `examples/testpattern` on a headless output and checks the colours, in SDR and in HDR (`--headless-hdr`). It needs a GPU and `/dev/udmabuf`, and is not part of `make check`.
93
94On a real screen, compare by eye:
95
96```sh
97WAYLAND_DISPLAY=<name> go run ./examples/testpattern -mode hdr -fullscreen   # or -mode sdr
98```

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 23

Tags 10

↑↓ moveenter openesc close