kick/README.md

341 lines
17 KiB
Markdown
Raw Normal View History

# kick
A nested Wayland compositor (built on wlroots) that presents several
client apps as tabs in a single window on the host Wayland compositor.
Each command-line argument launches one tab.
## Building
The project builds with [Meson](https://mesonbuild.com/) + Ninja. On most
distributions the non-wlroots dependencies are available in the standard
repositories; `wlroots` is used from the system where one is installed, and
otherwise is fetched and built automatically from a pinned source tarball
(`subprojects/wlroots.wrap`).
```sh
meson setup build
meson compile -C build
```
The resulting binary is `build/kick`.
The Wayland protocol stubs (`xdg-shell`, `xdg-decoration`,
`cursor-shape-v1` and `tablet-v2`, `wlr-data-control-unstable-v1`) are not
committed; they are generated into the build directory by `wayland-scanner`
from the system `wayland-protocols` (and the bundled
`wlr-data-control-unstable-v1.xml`), so they always match the installed
protocol headers. This adds no extra dependency beyond what the wlroots
fallback already requires. (`tablet-v2` is generated only because
cursor-shape's implementation references `wl_tablet_tool_v2_interface`.)
### Debian / Ubuntu
```sh
sudo apt install build-essential meson ninja-build pkg-config \
libwayland-dev libwayland-bin wayland-protocols \
libxkbcommon-dev libpixman-1-dev \
libegl1-mesa-dev libgles2-mesa-dev
```
No wlroots package is needed: this project targets wlroots 0.19, while
Debian/Ubuntu ship another version as a versioned package (e.g.
`libwlroots-0.20-dev` on Debian >= trixie, pkg-config name `wlroots-0.20`),
so Meson builds the bundled 0.19.1 instead (see below).
### Arch
```sh
sudo pacman -S base-devel meson ninja pkgconf wayland wayland-protocols \
wlroots xkbcommon mesa pixman
```
### Fedora
```sh
sudo dnf install gcc meson ninja-build pkgconf-pkg-config \
wayland-devel wayland-protocols-devel wlroots-devel \
libxkbcommon-devel mesa-libEGL-devel mesa-libGLES-devel pixman-devel
```
### When the system wlroots dep is missing / the fallback can't find one
The Meson setup searches for `wlroots-0.19` then `wlroots`; if neither is
installed it builds the pinned 0.19.1 from `subprojects/wlroots.wrap`, which
only needs the wayland, wayland-protocols, xkbcommon and EGL/GLES2 dev
packages listed above. On newer Debian/Ubuntu the packaged wlroots is a
versioned library (e.g. `libwlroots-0.20-dev`, pkg-config `wlroots-0.20`)
whose version falls outside this project's `>=0.19,<0.20` pin, so the
fallback build is used automatically and no wlroots package needs to be
installed. Optional wlroots features (Vulkan, X11, libinput, etc.) are
auto-detected and skipped when their build deps aren't present, so a minimal
install works too.
## Usage
```
kick [--start-cmd-index N] [prog...]
```
Each `<prog>` is one shell command; quote it when it takes arguments, e.g.
`kick "foot -e htop" "firefox -no-remote"`. With no arguments, a
single terminal tab is opened (see `$TERMINAL` below). `--start-cmd-index
N` starts focused on command `N` (1-based; the last command by default).
`kick -h` prints this usage.
Every argument starts a new tab. There is no tab-count limit. Client apps run
as children of kick and connect to its private Wayland socket, so
`DISPLAY` is unset for them and their `WAYLAND_DISPLAY` points at the kick
compositor (clients are launched with the GDK and Qt Wayland backends forced).
`Alt+Shift+T` opens the terminal named by the `$TERMINAL` environment variable
when it is set (see Key bindings); otherwise it falls back to `alacritty`. The
value is used verbatim, so it may carry arguments, e.g.
`TERMINAL="foot -e htop" kick ...`.
Notes:
- An argument written bare (starting with `-`) is taken as a tab launcher too
(unless quoted); a warning is printed when it looks like a stray option.
- Each command (an argument or the default `$TERMINAL`) is resolved on `PATH`
before the session starts: if its program isn't found, kick prints
an error and exits non-zero without opening any window.
- The client applications run inside the session, so closing their windows
only closes their tab; closing the host kick window, or sending
SIGINT/SIGTERM, shuts the whole session down and kills all client children.
### Startup order
The command-line commands are all launched at the same time, in the order
you wrote them. Each command runs in its own process group, and when its
client connects it claims its own slot by that group — so every tab keeps
its command-line position even with wrapped commands like `sh -c "foot -e
htop"`, no matter which program takes longest to open its window.
Until a client connects, its tab is black and listed in the overlay under
its **command line** instead of a window title. From the very first frame
the **last** command's tab is the active one, so you wait only on that
single tab: when it maps, the window reveals it and nothing steals focus
before that, so you don't flicker through every intermediate tab. This
"last command wins" rule also means the tab you see on startup is always
the one from the last argument. Pass `--start-cmd-index N` to start focused
on a different command instead: `N` is 1-based (the first command is 1),
and that command's tab is black-and-active from the first frame just like
the default last one.
If a command never connects — e.g. `sleep 999` — the rest of the command
line still starts (they all launched together) and the window shows the
last tab as soon as it is ready. A command whose whole process group exits
without ever connecting (e.g. `kick /bin/false`) drops its tab
entirely instead of leaving a permanent black hole.
Closing a tab takes it out of the list for good: its slot is removed and
the remaining tabs reflow into place — there are no holes for later clients
to fall into, so externally started windows are always appended, not
absorbed into a dead tab's position.
Later tab openings follow the same no-steal rule: a brand-new tab never yanks
the active surface on its own. `Alt+Shift+T` still focuses its fresh
terminal, and a sub-window of the tab you are currently looking at becomes
active; every other new window or tab just appears in the list, hidden, until
you cycle to it.
### Sub-windows: `--sub-windows tabs|stack`
A client's second window (a dialog, palette, or another main window from the
same process) is a *sub-window* of that client's tab. `--sub-windows` picks
how it appears:
- `tabs` (default): each sub-window gets its own tab glued immediately to the
right of its parent tab. It activates only if the parent tab is the one
being shown; otherwise it waits hidden until you cycle to it.
- `stack`: sub-windows never get a tab. They render stacked above their
parent tab's surface, share the tab's visibility, and hold the keyboard
while that tab is shown. They are invisible to cycling and to the
Alt-held overlay, and closing the tab closes them with it (same process).
When a stacked window closes, focus falls back to the tab's own surface.
`--sub-windows stack` suits apps whose dialogs feel like part of the window;
`tabs` keeps every surface individually reachable, which is kick's original
model.
## Key bindings
| Key | Action |
|----------------------------|---------------------------------------------------|
| `Alt` (hold) | Show the tab-title overlay (centered) |
| `Alt+Up` | Previous tab (hold to keep cycling) |
| `Alt+Down` | Next tab (hold to keep cycling) |
| `Alt+Shift+Up` | Move the active tab earlier in the list |
| `Alt+Shift+Down` | Move the active tab later in the list |
| `Alt+Delete` | Close the active tab (SIGTERM, SIGKILL fallback) |
| `Alt+Shift+T` | Open a new terminal tab next to the current one |
The tab-management prefix is `Alt` by default. Pass `--modifier super` to
use `Super` (Windows/Meta key, Mod4) instead: `Super` hold shows the
overlay, `Super+Up/Down` cycle, `Super+Shift+Up/Down` reorder,
`Super+Delete` closes, and `Super+Shift+T` opens a new terminal -- the
new-tab chord follows the prefix like every other binding.
The `Super` variant is meant for standalone/DRM and labwc-style hosts
(including the Raspberry Pi, where nothing reserves it); under GNOME the
host grabs most `Super` chords before the nested compositor ever sees them
(e.g. `Super+Up` maximizes the kick window), so keep the default `Alt`
there. labwc's own defaults also leave `Super+Shift+arrow` and
`Super+Delete` free even when kick is nested inside labwc.
Holding the tab-management modifier (`Alt`, or `Super` with `--modifier
super`) shows a passive overlay listing every tab's title with the
active tab highlighted; the list updates live as you cycle with `Up`/`Down`
and disappears on release. It is drawn by kick itself, so it works
on hosts with or without a title bar (the overlay text uses a system font
via FreeType; set `KICK_FONT` to pick one). Each row also shows the
tab's app icon, resolved from its Wayland `app_id` via the desktop
files and icon themes in `XDG_DATA_DIRS` (PNG via libpng, SVG via
librsvg); tabs without a resolvable icon get a generic placeholder.
`Alt+Shift+T` launches a fresh terminal (`$TERMINAL`, default
`alacritty`) as a new tab placed immediately to the right of the current tab,
and it becomes the active tab once it maps (the one exception to the
no-map-steal rule at startup, along with sub-windows of the shown tab).
The tab-cycling hotkey only emits a physical press for the first tab switch;
repeats are synthesized, so holding `Alt+Up/Down` keeps stepping through
tabs while any other key stops it.
`Alt+Shift+Up/Down` reorder the overlay list instead of switching: the
active tab stays active, it just moves one position earlier or later in the
order. Moving off either end wraps around and the whole list shifts — a
rightmost tab moved past the end lands first, with every other tab pushed
one position, not merely swapped with the first. Holding the chord keeps
moving it (same synthesized repeat as cycling). An `Alt+Shift+T` inside the
moved tab opens exactly where the reordered list says it should — immediately
right of the tab's new position.
Both `Alt+Up/Down` and `Alt+Shift+Up/Down` are grabbed: kick consumes them
so the focused client never receives the chord (e.g. a terminal can't act on
`Shift`+arrow while you reorder).
`Alt+Delete` closes the active tab. The tab disappears immediately (the tab
to its right becomes active, or the left neighbor if it was the rightmost);
the client's process group gets a `SIGTERM`, escalated to `SIGKILL` after a
short grace if it ignores the signal. A tab that closes on its own (its
program exits) instead hands active off to the left neighbor.
Function keys are **not** grabbed: `F9`/`F10`/`F11` (and anything else) are
forwarded to the focused client, so apps keep their own bindings (e.g. a
browser's `F11`).
## Fullscreen
Fullscreen mirrors between the host window and the clients:
- Fullscreen the kick window (use your host compositor's fullscreen
keybinding, or an app's own `F11`) and every tab is told it is fullscreen,
so clients drop their decorations and fill the screen.
- A client that requests fullscreen itself (e.g. a video player) also
fullscreens the kick host window.
- Leaving fullscreen returns every tab to maximized.
## Standalone mode (`KICK_MODE=standalone`)
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.
2026-08-15 18:06:55 +10:00
By default kick runs **nested**: it opens a window on the host
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.
2026-08-15 18:06:55 +10:00
Wayland compositor and draws every tab inside that single window. Set
`KICK_MODE=standalone` to instead become its own Wayland compositor,
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.
2026-08-15 18:06:55 +10:00
rendering directly onto a native display (DRM via wlroots) — the first output
only; extra monitors are ignored. In this mode kick takes over the
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.
2026-08-15 18:06:55 +10:00
screen, and the tab overlay is composited onto the output in a second render
pass instead of being drawn into a host window.
- `KICK_MODE=standalone WLR_BACKENDS=...` selects the wlroots backends
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.
2026-08-15 18:06:55 +10:00
(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 kick seat directly
(native input); the `Alt+Shift+T` and `Alt`-overlay hotkeys still work off
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.
2026-08-15 18:06:55 +10:00
those devices.
- `Ctrl-Alt-F1..F12` switch virtual terminals while running on the DRM
backend (via libseat/logind); the terminal sees nothing of the chord.
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.
2026-08-15 18:06:55 +10:00
- 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: `KICK_MODE=standalone WLR_BACKENDS=headless` runs
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.
2026-08-15 18:06:55 +10:00
standalone on wlroots' headless backend (no DRM needed), useful in CI or
when no TTY is available.
### Running the nested tests without a visible window
`./test.sh` runs the nested suite against the desktop, so each test briefly
opens the compositor's host window. `./test-headless.sh` re-hosts those
windows inside an invisible compositor: it boots a second, standalone
instance of the same binary (`KICK_MODE=standalone WLR_BACKENDS=headless`,
output shown nowhere) and points `WAYLAND_DISPLAY` at it for the pytest run,
so nothing flashes on the desktop. No extra dependencies.
The headless host exposes `wlr-data-control`, so the clipboard tests run
through the (preferred) data-control bridge instead of the focus-based one
that the desktop session uses. Standalone mode needs that data-control
`set_selection` request handled too, which is the case regardless of which
runner you use.
`test_nested_host_primary_to_kick` exercises the host->nested primary
selection path, which only exists over the data-control v2 bridge; it skips
on hosts without it (e.g. GNOME), so use `./test-headless.sh` or a
data-control host (labwc, sway) for it.
Known-flaky tests are marked `@pytest.mark.flaky`. `pytest.ini` defaults to
`-m "not flaky"`, so they are excluded from every plain run — `./test.sh`,
`./test-headless.sh`, or direct `pytest`. Run them on demand with
`./test.sh -m flaky`.
## Cursor shapes (`cursor-shape-v1`)
Kick advertises the `cursor-shape-v1` protocol (as `wp_cursor_shape_manager_v1`,
version 1). Nested clients that switch the pointer's cursor shape are honoured:
- **Standalone** mode applies the requested shape to the compositor's own
cursor, looked up in the current `XCURSOR_THEME` (so e.g. GTK's text cursor
works over a galculator entry).
- **Nested** mode has no cursor of its own: the shape is forwarded to the host
compositor's pointer via `wp_cursor_shape_device_v1.set_shape`, and the host
(which draws the cursor over our window) applies it.
The shape is remembered per tab and the cursor is reset to `default` when you
switch to a tab whose client last asked for nothing (or asked for `default`),
so a text cursor never leaks from one app into the next.
Legacy `wl_pointer.set_cursor` surfaces are **not** bridged: a nested client
that still uses the old protocol (some older toolkits) keeps no visible cursor
over the window, and a host without `cursor-shape-v1` simply draws its default
cursor (logged as `[cursor] host shape n/a` at debug level).
## Logging (`KICK_LOG`)
Quiet by default: a plain launch prints nothing to the launching terminal
except fatal errors. Set `KICK_LOG` before starting:
| Value | Meaning |
|-------|------------------------------------------------------------------|
| unset/`0` | Quiet. Only fatal errors are shown. |
| `1` | Info. Status lines, dump summaries, host notes (e.g. socket). |
| `2` | Debug. Adds a per-frame `[diag]` trace and EGL/render/import messages, and client stdout/stderr flows through. |
At debug level (`2`) client programs also inherit the launching terminal's
stdout/stderr instead of being silenced, which makes client errors visible.
## Frame dumps (SIGUSR1 / KICK_AUTODUMP)
The `SIGUSR1` signal and `KICK_AUTODUMP` write raw `P6` portable-pixmap
files to `/tmp` regardless of the log level:
- `SIGUSR1` writes `/tmp/scene_N.ppm` — the composed scene (whatever
kick rendered for the host).
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.
2026-08-15 18:06:55 +10:00
- `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 `KICK_AUTODUMP` (any value, e.g. `KICK_AUTODUMP=1`), a scene and
host dump are taken automatically once the first frame in which a client
committed a CPU-readable (shared-memory) buffer — handy for capturing
exactly what a newly launched client drew.
- At `KICK_LOG=2` the diagnostic that detects the client's own shm buffer
additionally writes that buffer to `/tmp/client_N.ppm`.