2026-08-19 11:46:25 +10:00
|
|
|
|
# kick
|
2026-08-09 12:46:14 +10:00
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-19 11:46:25 +10:00
|
|
|
|
The resulting binary is `build/kick`.
|
2026-08-09 12:46:14 +10:00
|
|
|
|
|
2026-08-19 09:54:11 +10:00
|
|
|
|
The Wayland protocol stubs (`xdg-shell`, `xdg-decoration`,
|
2026-09-08 00:37:52 +10:00
|
|
|
|
`cursor-shape-v1` and `tablet-v2`, `ext-data-control-v1`,
|
2026-09-08 06:49:39 +10:00
|
|
|
|
`ext-image-copy-capture-v1`, `ext-image-capture-source-v1`) are not
|
|
|
|
|
|
committed; they are generated into the build directory by `wayland-scanner`
|
|
|
|
|
|
from the system `wayland-protocols` (and the bundled
|
|
|
|
|
|
`wlr-layer-shell-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`.)
|
2026-08-19 09:54:11 +10:00
|
|
|
|
|
2026-08-09 12:46:14 +10:00
|
|
|
|
### Debian / Ubuntu
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
sudo apt install build-essential meson ninja-build pkg-config \
|
|
|
|
|
|
libwayland-dev libwayland-bin wayland-protocols \
|
2026-09-13 11:59:07 +10:00
|
|
|
|
libxkbcommon-dev libpixman-1-dev libpango1.0-dev \
|
2026-08-10 09:25:52 +10:00
|
|
|
|
libegl1-mesa-dev libgles2-mesa-dev
|
2026-08-09 12:46:14 +10:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-09-07 23:37:45 +10:00
|
|
|
|
No wlroots package is needed: this project targets wlroots 0.20, and
|
|
|
|
|
|
where the distribution ships it as a versioned package (e.g.
|
|
|
|
|
|
`libwlroots-0.20-dev` on Debian >= trixie, pkg-config name `wlroots-0.20`)
|
|
|
|
|
|
Meson uses the system copy directly; otherwise it builds the bundled
|
|
|
|
|
|
0.20.2 instead (see below).
|
2026-08-09 12:46:14 +10:00
|
|
|
|
|
|
|
|
|
|
### Arch
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
sudo pacman -S base-devel meson ninja pkgconf wayland wayland-protocols \
|
2026-09-13 11:59:07 +10:00
|
|
|
|
wlroots xkbcommon mesa pixman pango
|
2026-08-09 12:46:14 +10:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Fedora
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
sudo dnf install gcc meson ninja-build pkgconf-pkg-config \
|
2026-09-13 11:59:07 +10:00
|
|
|
|
wayland-devel wayland-protocols-devel wlroots-devel pango-devel \
|
2026-08-09 12:46:14 +10:00
|
|
|
|
libxkbcommon-devel mesa-libEGL-devel mesa-libGLES-devel pixman-devel
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### When the system wlroots dep is missing / the fallback can't find one
|
|
|
|
|
|
|
2026-09-07 23:37:45 +10:00
|
|
|
|
The Meson setup searches for `wlroots-0.20` then `wlroots`; if neither is
|
|
|
|
|
|
installed it builds the pinned 0.20.2 from `subprojects/wlroots.wrap`, which
|
2026-08-09 12:46:14 +10:00
|
|
|
|
only needs the wayland, wayland-protocols, xkbcommon and EGL/GLES2 dev
|
2026-09-07 23:37:45 +10:00
|
|
|
|
packages listed above. On older Debian/Ubuntu the packaged wlroots is a
|
|
|
|
|
|
versioned library for another series (e.g. `libwlroots-0.19-dev`, pkg-config
|
|
|
|
|
|
`wlroots-0.19`) whose version falls outside this project's `>=0.20,<0.21`
|
|
|
|
|
|
pin, so the fallback build is used automatically and no wlroots package
|
|
|
|
|
|
needs to be installed. Optional wlroots features (Vulkan, X11, libinput, etc.) are
|
2026-08-10 09:25:52 +10:00
|
|
|
|
auto-detected and skipped when their build deps aren't present, so a minimal
|
|
|
|
|
|
install works too.
|
2026-08-09 12:46:14 +10:00
|
|
|
|
|
|
|
|
|
|
## Usage
|
|
|
|
|
|
|
|
|
|
|
|
```
|
2026-09-14 21:33:32 +10:00
|
|
|
|
kick [--start-cmd-index N] [--focus-priority] [prog...]
|
2026-08-09 12:46:14 +10:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Each `<prog>` is one shell command; quote it when it takes arguments, e.g.
|
2026-08-19 11:46:25 +10:00
|
|
|
|
`kick "foot -e htop" "firefox -no-remote"`. With no arguments, a
|
2026-08-19 09:21:46 +10:00
|
|
|
|
single terminal tab is opened (see `$TERMINAL` below). `--start-cmd-index
|
|
|
|
|
|
N` starts focused on command `N` (1-based; the last command by default).
|
2026-09-14 21:33:32 +10:00
|
|
|
|
`--focus-priority` favors the active window: it runs at normal CPU/IO priority
|
|
|
|
|
|
(nice 0, best-effort/4) while every other window's process group is pushed
|
|
|
|
|
|
down (nice +10, best-effort/7). Off by default; needs no sudo because
|
|
|
|
|
|
background groups are only ever deprioritized. Rebalanced on every focus
|
|
|
|
|
|
change, including each Alt+Tab preview step. Works nested and standalone
|
|
|
|
|
|
(strongest standalone, where no host scheduler competes).
|
2026-08-19 11:46:25 +10:00
|
|
|
|
`kick -h` prints this usage.
|
2026-08-09 13:02:46 +10:00
|
|
|
|
|
|
|
|
|
|
Every argument starts a new tab. There is no tab-count limit. Client apps run
|
2026-08-24 10:43:24 +10:00
|
|
|
|
as children of kick with `WAYLAND_DISPLAY` pointed at the kick compositor and
|
2026-09-14 11:42:30 +10:00
|
|
|
|
`XAUTHORITY` cleared; `KICK_MODE`/`WLR_BACKENDS` are scrubbed too, since they
|
|
|
|
|
|
are kick's own config -- a nested kick started from a tab must auto-detect
|
|
|
|
|
|
instead of inheriting standalone. Toolkits then self-select — Wayland-capable
|
|
|
|
|
|
apps connect to kick directly, X11-only ones (emacs-gtk, xmessage) use
|
|
|
|
|
|
`DISPLAY` at the embedded Xwayland when XWayland support is available
|
|
|
|
|
|
(see below).
|
2026-08-24 10:43:24 +10:00
|
|
|
|
kick never forces `GDK_BACKEND`/`QT_QPA_PLATFORM`: an explicit backend value
|
|
|
|
|
|
removes a toolkit's fallback and breaks X11-only builds. If a particular app
|
|
|
|
|
|
misbehaves under native Wayland, wrap its command, e.g.
|
|
|
|
|
|
`"GDK_BACKEND=x11 myapp"`.
|
2026-08-21 12:37:28 +10:00
|
|
|
|
|
|
|
|
|
|
### XWayland
|
|
|
|
|
|
|
|
|
|
|
|
X11 applications work as tabs alongside Wayland clients: `kick xmessage hi`
|
|
|
|
|
|
opens xmessage in a tab like any other command. kick embeds an Xwayland
|
|
|
|
|
|
server (started lazily on first use) and claims each X11 window by the
|
|
|
|
|
|
process group of its `_NET_WM_PID`, using the same pgid matching as Wayland
|
|
|
|
|
|
clients — so an X11 program's windows land in the tab reserved for its
|
|
|
|
|
|
command-line argument.
|
|
|
|
|
|
|
|
|
|
|
|
- Managed X11 windows are resized to fill the output and follow every tab
|
|
|
|
|
|
rule (focus, cycling, Alt+Delete close via WM_DELETE_WINDOW with the usual
|
|
|
|
|
|
SIGTERM/SIGKILL escalation).
|
|
|
|
|
|
- Override-redirect windows (menus, tooltips, popups) float above the tabs
|
|
|
|
|
|
at their requested position; they take keyboard focus only when they ask
|
|
|
|
|
|
for it.
|
|
|
|
|
|
- Transient/dialog X11 windows follow the same rules as Wayland subsurface
|
2026-08-22 13:14:18 +10:00
|
|
|
|
dialogs (`--tab-groups on|off`).
|
2026-08-21 12:37:28 +10:00
|
|
|
|
- Set `KICK_NO_XWAYLAND=1` to disable the embedded X server entirely
|
|
|
|
|
|
(`DISPLAY` is then unset again).
|
|
|
|
|
|
|
|
|
|
|
|
Requirements: wlroots built with XWayland support plus `xcb`, `xcb-icccm`
|
|
|
|
|
|
and `xcb-ewmh` dev packages; if any are missing, kick builds without X11
|
|
|
|
|
|
support and behaves exactly as before. Known limitation: X11 cursor themes
|
|
|
|
|
|
are not applied inside X11 windows (kick does not bridge the legacy
|
|
|
|
|
|
`wl_pointer.set_cursor` path).
|
2026-08-09 13:02:46 +10:00
|
|
|
|
|
2026-08-18 13:47:00 +10:00
|
|
|
|
`Alt+Shift+T` opens the terminal named by the `$TERMINAL` environment variable
|
2026-08-15 09:16:09 +10:00
|
|
|
|
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.
|
2026-08-19 11:46:25 +10:00
|
|
|
|
`TERMINAL="foot -e htop" kick ...`.
|
2026-08-15 09:16:09 +10:00
|
|
|
|
|
2026-08-09 13:02:46 +10:00
|
|
|
|
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.
|
2026-08-15 11:29:53 +10:00
|
|
|
|
- Each command (an argument or the default `$TERMINAL`) is resolved on `PATH`
|
2026-08-19 11:46:25 +10:00
|
|
|
|
before the session starts: if its program isn't found, kick prints
|
2026-08-15 11:29:53 +10:00
|
|
|
|
an error and exits non-zero without opening any window.
|
2026-08-09 13:02:46 +10:00
|
|
|
|
- The client applications run inside the session, so closing their windows
|
2026-08-19 11:46:25 +10:00
|
|
|
|
only closes their tab; closing the host kick window, or sending
|
2026-08-09 13:02:46 +10:00
|
|
|
|
SIGINT/SIGTERM, shuts the whole session down and kills all client children.
|
|
|
|
|
|
|
2026-08-18 11:20:29 +10:00
|
|
|
|
### Startup order
|
|
|
|
|
|
|
Launch CLI tabs in parallel; slot-claim by process group
- All command-line tabs fork at startup; the last one is active and shown
black until its first frame maps, matching the existing README contract
that the last command's window appears immediately.
- Claim incoming toplevels by process group, not exact pid, so wrapped
commands (sh -c "foot -e htop") keep their own slot even though the
sh wrapper forks the real client.
- Closed tabs are fully removed: remove_app() compacts the array, frees the
slot, clears parent refs, and prefers the left neighbor as the successor
(right on explicit close, mirroring the old behavior on destroy).
- Black (pending) tabs are live slots: cycle/reorder walk them, activation
logs '[diag] active slot=N (black, awaiting map)', and Alt+Delete closes
one outright. A SIGCHLD-latched sweep drops black tabs whose whole
process group exits before ever connecting (e.g. /bin/false).
- The map-reveal rule keys on the scene node still being disabled, since a
slot's toplevel is set at claim time (before its map event).
- Update the standalone/headless and nested tests to wait for a mapped
frame (the startup tab is active before it maps, so the old 'no tab was
ever activated' wait no longer fires), and add two tests covering
Alt+Delete on a black active tab and the never-connecting-command sweep.
2026-08-18 22:41:27 +10:00
|
|
|
|
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
|
2026-08-19 09:21:46 +10:00
|
|
|
|
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.
|
Launch CLI tabs in parallel; slot-claim by process group
- All command-line tabs fork at startup; the last one is active and shown
black until its first frame maps, matching the existing README contract
that the last command's window appears immediately.
- Claim incoming toplevels by process group, not exact pid, so wrapped
commands (sh -c "foot -e htop") keep their own slot even though the
sh wrapper forks the real client.
- Closed tabs are fully removed: remove_app() compacts the array, frees the
slot, clears parent refs, and prefers the left neighbor as the successor
(right on explicit close, mirroring the old behavior on destroy).
- Black (pending) tabs are live slots: cycle/reorder walk them, activation
logs '[diag] active slot=N (black, awaiting map)', and Alt+Delete closes
one outright. A SIGCHLD-latched sweep drops black tabs whose whole
process group exits before ever connecting (e.g. /bin/false).
- The map-reveal rule keys on the scene node still being disabled, since a
slot's toplevel is set at claim time (before its map event).
- Update the standalone/headless and nested tests to wait for a mapped
frame (the startup tab is active before it maps, so the old 'no tab was
ever activated' wait no longer fires), and add two tests covering
Alt+Delete on a black active tab and the never-connecting-command sweep.
2026-08-18 22:41:27 +10:00
|
|
|
|
|
|
|
|
|
|
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
|
2026-08-19 11:46:25 +10:00
|
|
|
|
without ever connecting (e.g. `kick /bin/false`) drops its tab
|
Launch CLI tabs in parallel; slot-claim by process group
- All command-line tabs fork at startup; the last one is active and shown
black until its first frame maps, matching the existing README contract
that the last command's window appears immediately.
- Claim incoming toplevels by process group, not exact pid, so wrapped
commands (sh -c "foot -e htop") keep their own slot even though the
sh wrapper forks the real client.
- Closed tabs are fully removed: remove_app() compacts the array, frees the
slot, clears parent refs, and prefers the left neighbor as the successor
(right on explicit close, mirroring the old behavior on destroy).
- Black (pending) tabs are live slots: cycle/reorder walk them, activation
logs '[diag] active slot=N (black, awaiting map)', and Alt+Delete closes
one outright. A SIGCHLD-latched sweep drops black tabs whose whole
process group exits before ever connecting (e.g. /bin/false).
- The map-reveal rule keys on the scene node still being disabled, since a
slot's toplevel is set at claim time (before its map event).
- Update the standalone/headless and nested tests to wait for a mapped
frame (the startup tab is active before it maps, so the old 'no tab was
ever activated' wait no longer fires), and add two tests covering
Alt+Delete on a black active tab and the never-connecting-command sweep.
2026-08-18 22:41:27 +10:00
|
|
|
|
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.
|
2026-08-18 11:20:29 +10:00
|
|
|
|
|
|
|
|
|
|
Later tab openings follow the same no-steal rule: a brand-new tab never yanks
|
2026-08-18 13:47:00 +10:00
|
|
|
|
the active surface on its own. `Alt+Shift+T` still focuses its fresh
|
2026-08-22 13:14:18 +10:00
|
|
|
|
terminal, and a tab-group member of the tab you are currently looking at
|
|
|
|
|
|
becomes active; every other new window or tab just appears in the list, hidden, until
|
2026-08-18 11:20:29 +10:00
|
|
|
|
you cycle to it.
|
|
|
|
|
|
|
2026-08-22 13:14:18 +10:00
|
|
|
|
### Tab groups: `--tab-groups on|off`
|
2026-08-21 11:35:21 +10:00
|
|
|
|
|
2026-08-22 13:14:18 +10:00
|
|
|
|
A window is a *tab-group member* of an existing tab when its client process is
|
|
|
|
|
|
a (direct or indirect) child of that tab's client process — kick walks the
|
|
|
|
|
|
parent-PID chain (`/proc`) up to a few levels to find the owning tab. So when a
|
|
|
|
|
|
terminal tab's shell launches `gnome-calculator`, or an app opens a dialog or
|
|
|
|
|
|
palette, those windows are "related" to the tab they came from (the whole
|
|
|
|
|
|
forked process tree, not just a single process). `--tab-groups` picks how they
|
|
|
|
|
|
appear:
|
2026-08-21 11:35:21 +10:00
|
|
|
|
|
2026-08-22 13:14:18 +10:00
|
|
|
|
- `on` (default): related 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
|
|
|
|
|
|
tree). When a stacked window closes, focus falls back to the tab's own
|
|
|
|
|
|
surface.
|
|
|
|
|
|
- `off`: each related window gets its own tab glued immediately to the
|
2026-08-21 11:35:21 +10:00
|
|
|
|
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.
|
|
|
|
|
|
|
2026-08-22 13:14:18 +10:00
|
|
|
|
`--tab-groups on` groups a tab with its related windows the way a browser groups
|
|
|
|
|
|
tabs: the dialogs and auxiliaries feel like part of the one window. `off` keeps
|
|
|
|
|
|
every surface individually reachable, which is kick's original model.
|
2026-08-21 11:35:21 +10:00
|
|
|
|
|
2026-09-15 23:20:56 +10:00
|
|
|
|
### Panel (`--panel`)
|
|
|
|
|
|
|
|
|
|
|
|
`--panel CMD` launches a wlr-layer-shell panel (e.g. waybar) as an
|
|
|
|
|
|
always-on-top bar that sizes the tabs underneath it. The panel is launched
|
|
|
|
|
|
with a plain exec and shares the login session's D-Bus bus, like every other
|
|
|
|
|
|
compositor's panels. A single-instance panel therefore cannot nest: if the
|
|
|
|
|
|
host session already runs one (e.g. `wf-panel-pi` >= 1.31, which owns
|
|
|
|
|
|
`com.raspberrypi.wfpanelpi`), the nested copy defers to it and exits without
|
|
|
|
|
|
ever mapping, so no bar appears. Use a multi-instance panel, stop the host
|
|
|
|
|
|
copy, or run kick as the session itself (standalone), where no competitor
|
|
|
|
|
|
exists.
|
|
|
|
|
|
|
2026-08-09 13:02:46 +10:00
|
|
|
|
## Key bindings
|
|
|
|
|
|
|
|
|
|
|
|
| Key | Action |
|
|
|
|
|
|
|----------------------------|---------------------------------------------------|
|
2026-08-15 09:16:09 +10:00
|
|
|
|
| `Alt` (hold) | Show the tab-title overlay (centered) |
|
2026-08-14 08:48:28 +10:00
|
|
|
|
| `Alt+Up` | Previous tab (hold to keep cycling) |
|
|
|
|
|
|
| `Alt+Down` | Next tab (hold to keep cycling) |
|
2026-09-06 23:52:03 +10:00
|
|
|
|
| `Alt+Tab` / `Alt+1` | Walk windows most-recently-used first (hold to keep walking; the stack commits on release) |
|
2026-08-17 10:45:21 +10:00
|
|
|
|
| `Alt+Shift+Up` | Move the active tab earlier in the list |
|
|
|
|
|
|
| `Alt+Shift+Down` | Move the active tab later in the list |
|
2026-08-22 22:03:34 +10:00
|
|
|
|
| `Alt+Delete` | Close the active window (or the tab when it's the only window) |
|
2026-08-18 13:47:00 +10:00
|
|
|
|
| `Alt+Shift+T` | Open a new terminal tab next to the current one |
|
2026-08-22 22:16:51 +10:00
|
|
|
|
| `Alt+Left` / `Alt+Right` | Cycle the active tab's tab-group windows (Left = previous, Right = next; hold to keep cycling) |
|
2026-09-11 21:59:44 +10:00
|
|
|
|
| `Alt+F11` | Toggle fullscreen: the kick window in the host when nested, every client directly when standalone |
|
2026-08-09 13:02:46 +10:00
|
|
|
|
|
2026-08-17 11:44:27 +10:00
|
|
|
|
The tab-management prefix is `Alt` by default. Pass `--modifier super` to
|
|
|
|
|
|
use `Super` (Windows/Meta key, Mod4) instead: `Super` hold shows the
|
2026-09-06 23:52:03 +10:00
|
|
|
|
overlay, `Super+Up/Down` cycle, `Super+Tab`/`Super+1` walk recent windows,
|
|
|
|
|
|
`Super+Shift+Up/Down` reorder,
|
2026-08-18 13:47:00 +10:00
|
|
|
|
`Super+Delete` closes, and `Super+Shift+T` opens a new terminal -- the
|
|
|
|
|
|
new-tab chord follows the prefix like every other binding.
|
2026-08-17 11:44:27 +10:00
|
|
|
|
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
|
2026-08-19 11:46:25 +10:00
|
|
|
|
(e.g. `Super+Up` maximizes the kick window), so keep the default `Alt`
|
2026-08-17 11:44:27 +10:00
|
|
|
|
there. labwc's own defaults also leave `Super+Shift+arrow` and
|
2026-08-19 11:46:25 +10:00
|
|
|
|
`Super+Delete` free even when kick is nested inside labwc.
|
2026-08-17 11:44:27 +10:00
|
|
|
|
|
|
|
|
|
|
Holding the tab-management modifier (`Alt`, or `Super` with `--modifier
|
2026-08-22 23:32:03 +10:00
|
|
|
|
super`) shows a passive overlay listing every tab; the list updates live as
|
|
|
|
|
|
you move the focus and disappears on release. It is drawn by kick itself, so
|
2026-09-13 11:59:07 +10:00
|
|
|
|
it works on hosts with or without a title bar (the overlay text is rendered
|
|
|
|
|
|
with Pango from a system font family; set `KICK_FONT` to pick one, e.g.
|
|
|
|
|
|
`KICK_FONT="DejaVu Sans"`, default `Sans`). Each tab row lists that
|
2026-08-22 23:32:03 +10:00
|
|
|
|
tab's windows -- its main surface plus any tab-group members -- each shown
|
|
|
|
|
|
with its own icon (resolved from the window's `app_id` via the desktop files
|
|
|
|
|
|
and icon themes in `XDG_DATA_DIRS`, PNG via libpng, SVG via librsvg; a window
|
|
|
|
|
|
without a resolvable icon gets a generic placeholder). The focused window is
|
|
|
|
|
|
highlighted like a cursor: `Alt+Up`/`Alt+Down` switch tabs and
|
|
|
|
|
|
`Alt+Left`/`Alt+Right` move between windows within the active tab, so the
|
|
|
|
|
|
highlight walks through every window in the session.
|
2026-08-14 12:39:51 +10:00
|
|
|
|
|
2026-09-06 23:52:03 +10:00
|
|
|
|
`Alt+Tab` (and its synonym `Alt+1`, the chord that survives hosts like
|
|
|
|
|
|
GNOME which grab `Alt+Tab`, `Alt+Esc` *and* `Alt+Backtick` -- the last from
|
|
|
|
|
|
the metacity-era `switch_group` default) walk the session's
|
|
|
|
|
|
windows most-recently-used first: each press previews the next window after
|
|
|
|
|
|
the one you are looking at, wrapping at the bottom, and holding the chord
|
|
|
|
|
|
keeps stepping via the same synthesized repeat as tab cycling. While the
|
|
|
|
|
|
modifier is held the ordering does not change — every focus change is only a
|
|
|
|
|
|
preview — so the stack itself updates on release: releasing after moving
|
|
|
|
|
|
commits the shown window to the front of the MRU order. A bare hold that
|
|
|
|
|
|
never navigates leaves the order untouched. With the modifier released,
|
|
|
|
|
|
focus changes (e.g. switching to a window) promote that window to the front
|
|
|
|
|
|
immediately. Like every navigation chord, `Alt+Tab`/`Alt+1` are grabbed
|
|
|
|
|
|
so the focused client never sees them; `Shift` is ignored (there is no
|
|
|
|
|
|
reverse direction).
|
|
|
|
|
|
|
2026-08-18 13:47:00 +10:00
|
|
|
|
`Alt+Shift+T` launches a fresh terminal (`$TERMINAL`, default
|
2026-08-15 09:16:09 +10:00
|
|
|
|
`alacritty`) as a new tab placed immediately to the right of the current tab,
|
2026-08-18 11:20:29 +10:00
|
|
|
|
and it becomes the active tab once it maps (the one exception to the
|
2026-08-22 13:14:18 +10:00
|
|
|
|
no-map-steal rule at startup, along with tab-group members of the shown tab).
|
2026-08-15 09:16:09 +10:00
|
|
|
|
|
2026-08-09 13:02:46 +10:00
|
|
|
|
The tab-cycling hotkey only emits a physical press for the first tab switch;
|
2026-08-14 08:48:28 +10:00
|
|
|
|
repeats are synthesized, so holding `Alt+Up/Down` keeps stepping through
|
|
|
|
|
|
tabs while any other key stops it.
|
2026-08-09 13:02:46 +10:00
|
|
|
|
|
2026-08-17 10:45:21 +10:00
|
|
|
|
`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
|
Grab Alt+arrows and rotate tabs when reorder wraps past an end
A focused terminal acted on Alt+Shift+Up/Down (selection/typing) because
the reorder chord was forwarded like plain Alt+Up/Down cycling was. Both
are now grabbed: tabbed consumes them so the focused client never sees
them.
Reordering off an end previously swapped the moved tab with the far-end
tab, leaving the middle of the list in place ([A,B,C,D] moving D off the
end gave [D,B,C,A]). reorder_active_tab now shifts every slot between the
two positions, so the whole list rotates: [D,A,B,C]. Adjacent moves are
unchanged (rotation is a swap there).
The reorder test now runs three tabs to distinguish swap from rotation
([R,G,B] moving B off the end must land [B,R,G]) and keeps the
Ctrl+Shift+T anchor-slot check. The clipboard tests got more patience
(retry on stalled wl-paste; longer socket-start wait) since a stalled host
GNOME display was flaking them.
Full suite 5/5 green.
2026-08-17 11:12:28 +10:00
|
|
|
|
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
|
2026-08-18 13:47:00 +10:00
|
|
|
|
moving it (same synthesized repeat as cycling). An `Alt+Shift+T` inside the
|
Grab Alt+arrows and rotate tabs when reorder wraps past an end
A focused terminal acted on Alt+Shift+Up/Down (selection/typing) because
the reorder chord was forwarded like plain Alt+Up/Down cycling was. Both
are now grabbed: tabbed consumes them so the focused client never sees
them.
Reordering off an end previously swapped the moved tab with the far-end
tab, leaving the middle of the list in place ([A,B,C,D] moving D off the
end gave [D,B,C,A]). reorder_active_tab now shifts every slot between the
two positions, so the whole list rotates: [D,A,B,C]. Adjacent moves are
unchanged (rotation is a swap there).
The reorder test now runs three tabs to distinguish swap from rotation
([R,G,B] moving B off the end must land [B,R,G]) and keeps the
Ctrl+Shift+T anchor-slot check. The clipboard tests got more patience
(retry on stalled wl-paste; longer socket-start wait) since a stalled host
GNOME display was flaking them.
Full suite 5/5 green.
2026-08-17 11:12:28 +10:00
|
|
|
|
moved tab opens exactly where the reordered list says it should — immediately
|
|
|
|
|
|
right of the tab's new position.
|
|
|
|
|
|
|
2026-08-19 11:46:25 +10:00
|
|
|
|
Both `Alt+Up/Down` and `Alt+Shift+Up/Down` are grabbed: kick consumes them
|
Grab Alt+arrows and rotate tabs when reorder wraps past an end
A focused terminal acted on Alt+Shift+Up/Down (selection/typing) because
the reorder chord was forwarded like plain Alt+Up/Down cycling was. Both
are now grabbed: tabbed consumes them so the focused client never sees
them.
Reordering off an end previously swapped the moved tab with the far-end
tab, leaving the middle of the list in place ([A,B,C,D] moving D off the
end gave [D,B,C,A]). reorder_active_tab now shifts every slot between the
two positions, so the whole list rotates: [D,A,B,C]. Adjacent moves are
unchanged (rotation is a swap there).
The reorder test now runs three tabs to distinguish swap from rotation
([R,G,B] moving B off the end must land [B,R,G]) and keeps the
Ctrl+Shift+T anchor-slot check. The clipboard tests got more patience
(retry on stalled wl-paste; longer socket-start wait) since a stalled host
GNOME display was flaking them.
Full suite 5/5 green.
2026-08-17 11:12:28 +10:00
|
|
|
|
so the focused client never receives the chord (e.g. a terminal can't act on
|
|
|
|
|
|
`Shift`+arrow while you reorder).
|
2026-08-17 10:45:21 +10:00
|
|
|
|
|
2026-08-22 22:03:34 +10:00
|
|
|
|
`Alt+Delete` closes the active window. When the tab holds more than one window
|
|
|
|
|
|
(a tab group), only the focused window closes and the tab stays up; when it is
|
|
|
|
|
|
the tab's only window, the whole 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 window or tab that closes on its own (its
|
2026-08-17 10:26:48 +10:00
|
|
|
|
program exits) instead hands active off to the left neighbor.
|
|
|
|
|
|
|
2026-08-14 11:31:32 +10:00
|
|
|
|
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
|
|
|
|
|
|
|
2026-08-26 10:11:18 +10:00
|
|
|
|
## Fullscreen
|
|
|
|
|
|
|
2026-09-12 12:20:25 +10:00
|
|
|
|
Fullscreen is per overlay entry:
|
2026-08-14 11:31:32 +10:00
|
|
|
|
|
2026-08-24 23:25:31 +10:00
|
|
|
|
- Fullscreen the kick window (your host compositor's fullscreen keybinding,
|
2026-09-12 12:20:25 +10:00
|
|
|
|
or `Alt+F11`) and only the focused overlay entry is told it is
|
2026-09-13 09:19:21 +10:00
|
|
|
|
fullscreen, so it drops decorations and fills the screen. Layout and
|
|
|
|
|
|
panels follow the shown entry: while the fullscreen entry is shown the
|
|
|
|
|
|
bar is hidden and tabs take the whole output; switch to a normal entry
|
|
|
|
|
|
and the bar returns with the panel-respecting usable layout, even while
|
|
|
|
|
|
the fullscreen entry waits in the background. A black tab (client not
|
|
|
|
|
|
yet mapped) picks fullscreen up when it maps.
|
2026-09-12 12:20:25 +10:00
|
|
|
|
- A shown client that requests fullscreen itself (e.g. Firefox F11) takes
|
|
|
|
|
|
the host window with it: one action fills the whole monitor, still for
|
|
|
|
|
|
that entry only. Exiting (second F11) drops the host chrome again. A
|
|
|
|
|
|
hidden tab's request is remembered via the protocol and live when you
|
|
|
|
|
|
cycle to it, without touching the host.
|
2026-09-14 09:43:23 +10:00
|
|
|
|
- Client chrome buttons follow the same rule, since every tab lives
|
|
|
|
|
|
maximized: an app's maximize/restore toggle fullscreens its entry (a
|
|
|
|
|
|
request arriving before first display stays plain maximize, so fresh
|
|
|
|
|
|
windows never open fullscreened), and minimize steps a fullscreen entry
|
|
|
|
|
|
back down to maximized. Taskbar buttons behave the same way.
|
2026-08-26 18:19:59 +10:00
|
|
|
|
- Leaving fullscreen returns every window to maximized (X11 tab-group
|
|
|
|
|
|
members keep their own geometry).
|
2026-08-14 11:31:32 +10:00
|
|
|
|
|
2026-08-24 08:51:51 +10:00
|
|
|
|
## Standalone mode
|
|
|
|
|
|
|
|
|
|
|
|
At a Linux virtual console, plain `kick` automatically runs **standalone**:
|
|
|
|
|
|
it becomes its own Wayland compositor, rendering directly onto the native
|
|
|
|
|
|
display (DRM via wlroots) — the first output only; extra monitors are
|
|
|
|
|
|
ignored. In this mode kick 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.
|
|
|
|
|
|
|
|
|
|
|
|
The auto-detection fires only when none of `KICK_MODE`, `WLR_BACKENDS`,
|
2026-09-14 10:58:03 +10:00
|
|
|
|
`WAYLAND_DISPLAY` or `DISPLAY` is set **and** either kick's stdin is a real
|
|
|
|
|
|
VT (`/dev/tty<digits>`) or the login advertises a Wayland session
|
|
|
|
|
|
(`XDG_SESSION_TYPE=wayland`, as every display manager/greeter sets): there
|
|
|
|
|
|
must be nothing to nest into, and SSH sessions, terminal emulators and
|
|
|
|
|
|
pipes match neither test, so they keep the nested default. The chosen mode
|
2026-08-24 08:51:51 +10:00
|
|
|
|
is logged at startup as `[mode] standalone|nested (reason)`. Escape hatches,
|
|
|
|
|
|
in precedence order:
|
|
|
|
|
|
|
|
|
|
|
|
- `KICK_MODE=standalone` forces standalone everywhere.
|
|
|
|
|
|
- `KICK_MODE=nested` pins nested even at a console.
|
|
|
|
|
|
- Any non-empty `WLR_BACKENDS` forces standalone and selects the backends.
|
|
|
|
|
|
|
|
|
|
|
|
Otherwise kick runs **nested** by default: it opens a window on the host
|
|
|
|
|
|
Wayland compositor and draws every tab inside that single window.
|
2026-08-15 18:06:55 +10:00
|
|
|
|
- `WAYLAND_DISPLAY` is ignored in standalone mode (it never connects to an
|
|
|
|
|
|
outer compositor), except as part of the requested `WLR_BACKENDS`.
|
2026-08-19 11:46:25 +10:00
|
|
|
|
- Real keyboard/pointer devices are attached to the kick seat directly
|
2026-08-18 13:47:00 +10:00
|
|
|
|
(native input); the `Alt+Shift+T` and `Alt`-overlay hotkeys still work off
|
2026-08-25 09:36:17 +10:00
|
|
|
|
those devices. Touchscreens work the same way: standalone mode delivers
|
|
|
|
|
|
native touch events to the focused client, and nested mode forwards touch
|
|
|
|
|
|
from the host seat. Multi-touch slots are preserved; touches do not move
|
|
|
|
|
|
the mouse pointer.
|
2026-08-16 07:39:37 +10:00
|
|
|
|
- `Ctrl-Alt-F1..F12` switch virtual terminals while running on the DRM
|
|
|
|
|
|
backend (via libseat/logind); the terminal sees nothing of the chord.
|
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.
|
2026-08-19 11:46:25 +10:00
|
|
|
|
- Headless testing: `KICK_MODE=standalone WLR_BACKENDS=headless` runs
|
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.
|
|
|
|
|
|
|
2026-08-20 17:28:59 +10:00
|
|
|
|
`test_nested_host_primary_to_kick` exercises the host->nested primary
|
2026-09-08 06:49:39 +10:00
|
|
|
|
selection path, which only exists over the ext-data-control bridge; it skips
|
|
|
|
|
|
on hosts without it (e.g. GNOME), so use an ext-data-control host (labwc,
|
|
|
|
|
|
sway) for it.
|
2026-08-20 17:28:59 +10:00
|
|
|
|
|
2026-08-21 07:44:00 +10:00
|
|
|
|
Known-flaky tests are marked `@pytest.mark.flaky`. `pytest.ini` defaults to
|
2026-09-01 13:35:02 +10:00
|
|
|
|
`-m "not flaky"`, so they are excluded from every plain run (`./test.sh` or
|
|
|
|
|
|
direct `pytest`). Run them on demand with `./test.sh -m flaky`.
|
2026-08-20 18:00:48 +10:00
|
|
|
|
|
2026-09-08 18:08:58 +10:00
|
|
|
|
Parallel runs: `./test.sh -n4` splits the suite across workers
|
|
|
|
|
|
(pytest-xdist), cutting wall time ~3x (156s to ~47s here). Every spawn
|
|
|
|
|
|
uses unique log paths and per-session dump directories (`KICK_DUMP_DIR`),
|
|
|
|
|
|
and sockets are pid-unique, so workers stay isolated. The serial run
|
|
|
|
|
|
remains the deterministic gate: a few timing-sensitive tests can flake
|
|
|
|
|
|
under parallel load (they pass alone and serially).
|
2026-09-08 11:50:45 +10:00
|
|
|
|
|
2026-08-19 15:08:31 +10:00
|
|
|
|
## 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).
|
|
|
|
|
|
|
2026-09-04 09:37:13 +10:00
|
|
|
|
## Text input / input method (`text-input-v3`, `input-method-v2`)
|
|
|
|
|
|
|
|
|
|
|
|
Kick advertises `zwp_text_input_manager_v3` and `zwp_input_method_manager_v2`,
|
|
|
|
|
|
so in-session clients (toolkit text entries) and in-session input methods
|
|
|
|
|
|
(e.g. fcitx5, on-screen keyboards) work with each other in both nested and
|
|
|
|
|
|
standalone mode. The focused tab's field state (surrounding text, content
|
|
|
|
|
|
type) is relayed to the input method, and its preedit/commit strings back to
|
|
|
|
|
|
the field; focus follows tab switches, and the IME's candidate popup floats
|
|
|
|
|
|
above the tabs. While the IME holds its keyboard grab, keys go to it instead
|
|
|
|
|
|
of the focused client (the `Alt` tab-management chords still run first).
|
|
|
|
|
|
Relay activity is logged with the `[im]` prefix at `KICK_LOG=2`.
|
|
|
|
|
|
|
|
|
|
|
|
## Content type / single-pixel buffers
|
|
|
|
|
|
|
|
|
|
|
|
Kick advertises `wp_content_type_manager_v1`, so video players and games can
|
|
|
|
|
|
tag their surfaces (`photo`/`video`/`game`; the type is recorded in the
|
|
|
|
|
|
`[diag] map` line at `KICK_LOG=2`), and
|
|
|
|
|
|
`wp_single_pixel_buffer_manager_v1`, so clients can back a solid-color
|
|
|
|
|
|
surface with a 1×1 buffer (scaled up via `wp_viewporter`, which kick also
|
|
|
|
|
|
advertises) instead of a shared-memory pool.
|
|
|
|
|
|
|
2026-09-04 09:43:04 +10:00
|
|
|
|
## Gamma control (`wlr-gamma-control`)
|
|
|
|
|
|
|
|
|
|
|
|
Kick advertises `zwlr_gamma_control_manager_v1`, so night-light tools
|
|
|
|
|
|
(wlsunset, gammastep) can set the output's gamma ramp. Ramps are committed
|
|
|
|
|
|
to the single output via the standard wlroots apply path (a departing
|
|
|
|
|
|
client resets it); outputs with no gamma LUT report `failed` instead, so
|
|
|
|
|
|
on headless/nested outputs such tools learn the output is unsupported
|
|
|
|
|
|
rather than hanging. Like all hardware LUTs, this only takes visible
|
|
|
|
|
|
effect on DRM in standalone mode.
|
|
|
|
|
|
|
2026-09-04 10:00:34 +10:00
|
|
|
|
## Modal dialogs (`xdg-dialog`)
|
|
|
|
|
|
|
|
|
|
|
|
Kick advertises `xdg_wm_dialog_v1`, so toolkits can mark a toplevel modal
|
|
|
|
|
|
instead of relying on process ancestry alone. A modal dialog demands
|
|
|
|
|
|
attention: when it maps (or is set modal while mapped) its tab is shown
|
|
|
|
|
|
and the dialog takes window focus -- the deliberate exception to the
|
|
|
|
|
|
no-steal rule, alongside `Alt+Shift+T`. Placement is unchanged
|
|
|
|
|
|
(`--tab-groups on` still stacks it with its tab; `off` still gives it its
|
|
|
|
|
|
own reachable tab), and modality works identically nested and standalone
|
|
|
|
|
|
(it never depends on the host compositor). Session lock still wins over a
|
|
|
|
|
|
modal.
|
|
|
|
|
|
|
2026-09-04 10:13:13 +10:00
|
|
|
|
## Pointer gestures (`pointer-gestures`)
|
|
|
|
|
|
|
|
|
|
|
|
Kick advertises `zwp_pointer_gestures_v1`, so touchpad clients (browsers,
|
|
|
|
|
|
drawing apps, map viewers) receive swipe/pinch/hold gestures. Gestures fan
|
|
|
|
|
|
out to whichever client holds seat pointer focus. In standalone mode real
|
|
|
|
|
|
libinput touchpad gestures feed this; in nested mode the host only offers
|
|
|
|
|
|
`wl_pointer` (no gesture source), so gestures there come from clients that
|
|
|
|
|
|
synthesize them. Relay activity is logged with the `[gesture]` prefix at
|
|
|
|
|
|
`KICK_LOG=2`.
|
|
|
|
|
|
|
2026-09-04 10:21:30 +10:00
|
|
|
|
## Tearing control / fractional scale
|
|
|
|
|
|
|
|
|
|
|
|
A surface opting into `wp_tearing_control_v1` async presentation drives
|
|
|
|
|
|
the output commit's async-flip flag from the shown tab's hint (logged as
|
|
|
|
|
|
`[tearing] async|vsync` at `KICK_LOG=2`, transitions only): on DRM this
|
|
|
|
|
|
becomes a real tearing page flip for game tabs, while headless and nested
|
|
|
|
|
|
outputs correctly ignore it. Every surface is also told the output scale
|
|
|
|
|
|
via `wp_fractional_scale_v1` when claimed, so clients never have to guess
|
|
|
|
|
|
(120 = 1.0).
|
|
|
|
|
|
|
2026-09-04 10:35:34 +10:00
|
|
|
|
## Window icons (`xdg-toplevel-icon`)
|
|
|
|
|
|
|
|
|
|
|
|
Kick advertises `xdg_toplevel_icon_manager_v1` (preferring 28 px, the
|
|
|
|
|
|
overlay's draw size). A client-supplied icon beats the desktop-file guess:
|
|
|
|
|
|
the largest uploaded buffer becomes the tab's overlay texture, so exact
|
|
|
|
|
|
artwork shows where clients provide it. The guesser stays as the fallback
|
|
|
|
|
|
-- and the only source for X11 windows -- so every window still gets an
|
|
|
|
|
|
icon: clearing the icon drops back to guessing, and windows that never
|
|
|
|
|
|
set one behave exactly as before.
|
|
|
|
|
|
|
2026-09-04 10:52:16 +10:00
|
|
|
|
## Output management (`wlr-output-management`)
|
|
|
|
|
|
|
|
|
|
|
|
Kick advertises `zwlr_output_manager_v1`, so display tools (kanshi,
|
|
|
|
|
|
wdisplays) can list the single head and request sizes. Standalone honors
|
|
|
|
|
|
custom sizes through the normal resize path (tabs reflow, clients learn
|
|
|
|
|
|
the new size); nested refuses anything but the live size, because the
|
|
|
|
|
|
host window owns the geometry there. Disabling, scale and transform
|
|
|
|
|
|
changes are refused everywhere: one fixed output per session.
|
|
|
|
|
|
|
feat: implement wl_fixes, xdg-toplevel-tag, color-representation (wlroots 0.20)
Three of the four protocols new in wlroots 0.20 (ext-workspace has no
meaningful mapping onto kick's tab model, so it stays out):
- wl_fixes (core, v1): advertise the global; wlroots serves
destroy_registry itself. Clients like Firefox use it to tear down
registries without tripping protocol errors.
- xdg-toplevel-tag-v1: record each window's tag + translated
description ([tag] log line per change), show 'title [tag]' in the
overlay, and with --tab-groups on stack an unmapped external window
onto the tab whose window carries the same tag (browser-style tag
groups). Tags arriving before claim wait in a pending list keyed by
toplevel; mapped windows never move.
- color-representation-v1: advertise an explicit SDR sRGB set
(premultiplied-electrical alpha, identity coefficients in full
range) rather than the renderer's query, which reports empty on
GLES2; wlroots validates client values against it.
Tests: wltest gains --tag-set/--tag-desc and --color-probe/--color-out
(bind-time listener, since the manager bursts on bind); the globals
test covers all three new globals; new tag, tag-grouping and color
round-trip tests; README documents all three.
2026-09-08 10:22:50 +10:00
|
|
|
|
## Core fixes (`wl_fixes`)
|
|
|
|
|
|
|
|
|
|
|
|
Kick advertises `wl_fixes` (version 1), the core-protocol repair global:
|
|
|
|
|
|
clients such as Firefox use it to destroy registries cleanly instead of
|
|
|
|
|
|
tripping protocol errors on teardown. wlroots serves the requests; the
|
|
|
|
|
|
compositor only advertises the global.
|
|
|
|
|
|
|
|
|
|
|
|
## Window tags (`xdg-toplevel-tag`)
|
|
|
|
|
|
|
|
|
|
|
|
Kick advertises `xdg_toplevel_tag_manager_v1`, so toolkits can label a
|
|
|
|
|
|
toplevel with an untranslated tag ("settings", ...) plus a translated
|
|
|
|
|
|
description. Tags show in the overlay row ("title [tag]") and, with
|
|
|
|
|
|
`--tab-groups on`, windows carrying the same tag as an existing tab's
|
|
|
|
|
|
window stack onto that tab instead of opening their own (browser-style
|
|
|
|
|
|
tag groups). A tag set in the initial commit is picked up at claim; later
|
|
|
|
|
|
updates rewrite the live window in place without moving it between tabs.
|
|
|
|
|
|
|
|
|
|
|
|
## Color representation (`color-representation-v1`)
|
|
|
|
|
|
|
|
|
|
|
|
Kick advertises `wp_color_representation_manager_v1`, so clients learn
|
|
|
|
|
|
the alpha modes and matrix-coefficient/range pairs the session supports
|
|
|
|
|
|
(premultiplied-electrical alpha, identity coefficients in full range --
|
|
|
|
|
|
honest SDR sRGB) and can declare their surface's representation (validated
|
|
|
|
|
|
by wlroots).
|
|
|
|
|
|
The GLES2 renderer speaks SDR sRGB, so this is mostly a correctness
|
|
|
|
|
|
handshake for color-aware clients rather than visible behavior today.
|
|
|
|
|
|
|
2026-09-09 22:47:44 +10:00
|
|
|
|
## Color management (`color-management-v1`)
|
|
|
|
|
|
|
|
|
|
|
|
Kick advertises `wp_color_manager_v1`, so color-aware clients can
|
|
|
|
|
|
negotiate image descriptions with the session (perceptual render
|
|
|
|
|
|
intent, extended sRGB transfer characteristics and sRGB primaries -- honest SDR
|
|
|
|
|
|
sRGB, no ICC/parametric features). The scene is linked to the manager
|
|
|
|
|
|
so per-surface feedback flows; like color representation, this is a
|
|
|
|
|
|
correctness handshake on the GLES2 renderer rather than visible
|
|
|
|
|
|
color work today.
|
|
|
|
|
|
|
2026-09-09 22:49:32 +10:00
|
|
|
|
## System bell (`xdg-system-bell-v1`)
|
|
|
|
|
|
|
|
|
|
|
|
Kick advertises `xdg_system_bell_v1`, so clients can ring the system
|
|
|
|
|
|
bell on their behalf. The protocol leaves the rendering up to the
|
|
|
|
|
|
compositor (sound, visual flash, or nothing); kick logs each ring as
|
|
|
|
|
|
`[bell] ring` at `KICK_LOG=1` and otherwise renders nothing, keeping
|
|
|
|
|
|
the quiet-by-default contract.
|
|
|
|
|
|
|
2026-09-10 02:30:55 +10:00
|
|
|
|
## Alpha modifier (`alpha-modifier-v1`)
|
|
|
|
|
|
|
|
|
|
|
|
Kick advertises `wp_alpha_modifier_v1`, so clients can multiply their
|
|
|
|
|
|
surface's alpha (fades, translucent video) without repainting.
|
|
|
|
|
|
wlroots applies the multiplier when rendering, so like color
|
|
|
|
|
|
representation this is a passive correctness handshake rather than
|
|
|
|
|
|
visible compositor work.
|
|
|
|
|
|
|
2026-09-10 03:08:18 +10:00
|
|
|
|
## Window decorations (`xdg-decoration`)
|
|
|
|
|
|
|
|
|
|
|
|
Kick advertises `zxdg_decoration_manager_v1`, so toolkit clients
|
|
|
|
|
|
(libdecor, Qt) can negotiate who draws window frames. Every tab is
|
|
|
|
|
|
maximized and kick draws no frames of its own, so new toplevels are
|
|
|
|
|
|
pinned to client-side and later server-side requests are ignored:
|
|
|
|
|
|
client headerbars keep working, and nothing wastes tab space on
|
|
|
|
|
|
compositor-drawn borders.
|
|
|
|
|
|
|
2026-08-19 11:46:25 +10:00
|
|
|
|
## Logging (`KICK_LOG`)
|
2026-08-09 13:02:46 +10:00
|
|
|
|
|
|
|
|
|
|
Quiet by default: a plain launch prints nothing to the launching terminal
|
2026-08-19 11:46:25 +10:00
|
|
|
|
except fatal errors. Set `KICK_LOG` before starting:
|
2026-08-09 13:02:46 +10:00
|
|
|
|
|
|
|
|
|
|
| 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.
|
|
|
|
|
|
|
2026-08-19 11:46:25 +10:00
|
|
|
|
## Frame dumps (SIGUSR1 / KICK_AUTODUMP)
|
2026-08-09 13:02:46 +10:00
|
|
|
|
|
2026-08-19 11:46:25 +10:00
|
|
|
|
The `SIGUSR1` signal and `KICK_AUTODUMP` write raw `P6` portable-pixmap
|
2026-08-14 11:31:32 +10:00
|
|
|
|
files to `/tmp` regardless of the log level:
|
2026-08-09 13:02:46 +10:00
|
|
|
|
|
2026-08-14 11:31:32 +10:00
|
|
|
|
- `SIGUSR1` writes `/tmp/scene_N.ppm` — the composed scene (whatever
|
2026-08-19 11:46:25 +10:00
|
|
|
|
kick rendered for the host).
|
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).
|
2026-08-09 13:02:46 +10:00
|
|
|
|
- `N` is an incrementing per-run counter; a single `SIGUSR1` produces a
|
|
|
|
|
|
matching scene/host pair.
|
2026-08-19 11:46:25 +10:00
|
|
|
|
- With `KICK_AUTODUMP` (any value, e.g. `KICK_AUTODUMP=1`), a scene and
|
2026-08-09 13:02:46 +10:00
|
|
|
|
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.
|
2026-08-19 11:46:25 +10:00
|
|
|
|
- At `KICK_LOG=2` the diagnostic that detects the client's own shm buffer
|
2026-08-09 13:02:46 +10:00
|
|
|
|
additionally writes that buffer to `/tmp/client_N.ppm`.
|