Support standalone (native-output) mode alongside nested hosting

TABBED_MODE=standalone (or a visible WLR_BACKENDS) makes tabbed-wlroots its
own Wayland compositor rendering onto the first native output instead of a
nested host window. DRM+libinput is the default backend set; a headless
backend (created directly, since wlr_backend_autocreate wraps backends in a
multi backend that wlr_headless_add_output rejects) keeps the mode testable
in CI over the existing stdin feed.

Standalone:
- server_init creates a headless or autocreated backend and attaches a
  new_input listener; skips seat bridging (no host client to mirror).
- handle_new_output keeps the first output only, drives it at its preferred
  mode, and sizes the swapchain to the output instead of a host window.
- Font/icon overlays build CPU wlr_textures (premultiplied ARGB) alongside
  the existing GL textures so the tab overlay can be drawn by a second
  wlr_renderer pass over the committed output buffer.
- draw_tab_overlay_pass mirrors draw_tab_overlay's geometry with pass
  rects/textures; render_standalone builds scene state, applies the overlay,
  commits the buffer, and sends frame done. SIGUSR1 now also readbacks the
  composited frame (GLES2 FBO, or CPU data-ptr fallback) as /tmp/host_N.ppm.
- Native keyboards/pointers are attached to the seat and share the existing
  process_key_event/process_modifiers hotkey handling and the main loop's
  standalone branch (blocking dispatch + render on frame/redraw).

The nested mode is unchanged. Adds standalone smoke tests (boot/cycle and
Alt overlay readback under WLR_BACKENDS=headless); full suite 21 passing.
README documents the new mode.
This commit is contained in:
Andrew Hamilton 2026-08-15 18:06:55 +10:00
parent 33f8626bde
commit 72b95f1204
3 changed files with 775 additions and 36 deletions

View file

@ -134,6 +134,30 @@ Fullscreen mirrors between the host window and the clients:
fullscreens the tabbed host window.
- Leaving fullscreen returns every tab to maximized.
## Standalone mode (`TABBED_MODE=standalone`)
By default tabbed-wlroots runs **nested**: it opens a window on the host
Wayland compositor and draws every tab inside that single window. Set
`TABBED_MODE=standalone` to instead become its own Wayland compositor,
rendering directly onto a native display (DRM via wlroots) — the first output
only; extra monitors are ignored. In this mode tabbed-wlroots takes over the
screen, and the tab overlay is composited onto the output in a second render
pass instead of being drawn into a host window.
- `TABBED_MODE=standalone WLR_BACKENDS=...` selects the wlroots backends
(wlroots' own `WLR_BACKENDS` variable is passed through). With no
`WLR_BACKENDS` it defaults to `drm,libinput`.
- `WAYLAND_DISPLAY` is ignored in standalone mode (it never connects to an
outer compositor), except as part of the requested `WLR_BACKENDS`.
- Real keyboard/pointer devices are attached to the tabbed seat directly
(native input); the `Ctrl-Shift-T` and `Alt`-overlay hotkeys still work off
those devices.
- The stdin event feed used by the test harness works identically in both
modes, so the same synthesized-input tests cover nested and standalone runs.
- Headless testing: `TABBED_MODE=standalone WLR_BACKENDS=headless` runs
standalone on wlroots' headless backend (no DRM needed), useful in CI or
when no TTY is available.
## Logging (`TABBED_LOG`)
Quiet by default: a plain launch prints nothing to the launching terminal
@ -155,8 +179,10 @@ files to `/tmp` regardless of the log level:
- `SIGUSR1` writes `/tmp/scene_N.ppm` — the composed scene (whatever
tabbed-wlroots rendered for the host).
- `SIGUSR1` also writes `/tmp/host_N.ppm` — the host compositor's back
buffer, so you can see what the host actually displayed.
- `SIGUSR1` writes `/tmp/host_N.ppm` — the host compositor's back
buffer, so you can see what the host actually displayed. In standalone
mode this is the composited frame being presented to the output (scene
plus the `Alt` overlay).
- `N` is an incrementing per-run counter; a single `SIGUSR1` produces a
matching scene/host pair.
- With `TABBED_AUTODUMP` (any value, e.g. `TABBED_AUTODUMP=1`), a scene and