2026-08-09 12:46:14 +10:00
|
|
|
# tabbed-wlroots
|
|
|
|
|
|
|
|
|
|
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/tabbed-wlroots`.
|
|
|
|
|
|
|
|
|
|
### Debian / Ubuntu
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
sudo apt install build-essential meson ninja-build pkg-config \
|
|
|
|
|
libwayland-dev libwayland-bin wayland-protocols \
|
2026-08-10 09:25:52 +10:00
|
|
|
libxkbcommon-dev libpixman-1-dev \
|
|
|
|
|
libegl1-mesa-dev libgles2-mesa-dev
|
2026-08-09 12:46:14 +10:00
|
|
|
```
|
|
|
|
|
|
2026-08-10 09:25:52 +10:00
|
|
|
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).
|
2026-08-09 12:46:14 +10:00
|
|
|
|
|
|
|
|
### 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
|
2026-08-10 09:25:52 +10:00
|
|
|
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.
|
2026-08-09 12:46:14 +10:00
|
|
|
|
|
|
|
|
## Usage
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
tabbed-wlroots <prog> [prog...]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Each `<prog>` is one shell command; quote it when it takes arguments, e.g.
|
2026-08-09 13:02:46 +10:00
|
|
|
`tabbed-wlroots "foot -e htop" "firefox -no-remote"`.
|
|
|
|
|
|
|
|
|
|
Every argument starts a new tab. There is no tab-count limit. Client apps run
|
|
|
|
|
as children of tabbed-wlroots and connect to its private Wayland socket, so
|
|
|
|
|
`DISPLAY` is unset for them and their `WAYLAND_DISPLAY` points at the tabbed
|
|
|
|
|
compositor (clients are launched with the GDK and Qt Wayland backends forced).
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
- Without any arguments a usage message is printed and the program exits.
|
|
|
|
|
- The client applications run inside the session, so closing their windows
|
|
|
|
|
only closes their tab; closing the host tabbed-wlroots window, or sending
|
|
|
|
|
SIGINT/SIGTERM, shuts the whole session down and kills all client children.
|
|
|
|
|
|
|
|
|
|
## Key bindings
|
|
|
|
|
|
|
|
|
|
| Key | Action |
|
|
|
|
|
|----------------------------|---------------------------------------------------|
|
|
|
|
|
| `Ctrl+PageUp` | Previous tab (hold to keep cycling) |
|
|
|
|
|
| `Ctrl+PageDown` | Next tab (hold to keep cycling) |
|
|
|
|
|
| `F11` | Toggle the host window fullscreen |
|
|
|
|
|
| `F9` | Dump composed scene buffer to `/tmp/scene_N.ppm` |
|
|
|
|
|
| `F10` | Dump host back buffer to `/tmp/host_N.ppm` |
|
|
|
|
|
|
|
|
|
|
The tab-cycling hotkey only emits a physical press for the first tab switch;
|
|
|
|
|
repeats are synthesized, so holding `Ctrl+PageUp/PageDown` keeps stepping
|
|
|
|
|
through tabs while any other key stops it.
|
|
|
|
|
|
|
|
|
|
## Logging (`TABBED_LOG`)
|
|
|
|
|
|
|
|
|
|
Quiet by default: a plain launch prints nothing to the launching terminal
|
|
|
|
|
except fatal errors. Set `TABBED_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 (F9 / F10 / SIGUSR1 / TABBED_AUTODUMP)
|
|
|
|
|
|
|
|
|
|
The frame-dump keys, `SIGUSR1`, and `TABBED_AUTODUMP` all write raw `P6`
|
|
|
|
|
portable-pixmap files to `/tmp` regardless of the log level:
|
|
|
|
|
|
|
|
|
|
- `F9` or `SIGUSR1` writes `/tmp/scene_N.ppm` — the composed scene (whatever
|
|
|
|
|
tabbed-wlroots rendered for the host).
|
|
|
|
|
- `F10` or `SIGUSR1` writes `/tmp/host_N.ppm` — the host compositor's back
|
|
|
|
|
buffer, so you can see what the host actually displayed.
|
|
|
|
|
- `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
|
|
|
|
|
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 `TABBED_LOG=2` the diagnostic that detects the client's own shm buffer
|
|
|
|
|
additionally writes that buffer to `/tmp/client_N.ppm`.
|