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
|
|
|
|
|
|
|
|
|
|
```
|
2026-08-15 11:29:53 +10:00
|
|
|
tabbed-wlroots [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-15 11:29:53 +10:00
|
|
|
`tabbed-wlroots "foot -e htop" "firefox -no-remote"`. With no arguments, a
|
|
|
|
|
single terminal tab is opened (see `$TERMINAL` below). `tabbed-wlroots -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
|
|
|
|
|
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).
|
|
|
|
|
|
2026-08-15 09:16:09 +10:00
|
|
|
`Ctrl+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" tabbed-wlroots ...`.
|
|
|
|
|
|
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`
|
|
|
|
|
before the session starts: if its program isn't found, tabbed-wlroots prints
|
|
|
|
|
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
|
|
|
|
|
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 |
|
|
|
|
|
|----------------------------|---------------------------------------------------|
|
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-08-15 09:16:09 +10:00
|
|
|
| `Ctrl+Shift+T` | Open a new terminal tab next to the current one |
|
2026-08-09 13:02:46 +10:00
|
|
|
|
2026-08-14 12:39:51 +10:00
|
|
|
Holding `Alt` 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 `Alt` release. It is drawn by tabbed itself, so it works
|
|
|
|
|
on hosts with or without a title bar (the overlay text uses a system font
|
2026-08-14 12:55:42 +10:00
|
|
|
via FreeType; set `TABBED_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.
|
2026-08-14 12:39:51 +10:00
|
|
|
|
2026-08-15 09:16:09 +10:00
|
|
|
`Ctrl+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 — exactly as if its command had been
|
|
|
|
|
given on the command line.
|
|
|
|
|
|
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-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
|
|
|
|
|
|
|
|
|
|
Fullscreen mirrors between the host window and the clients:
|
|
|
|
|
|
|
|
|
|
- Fullscreen the tabbed 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 tabbed host window.
|
|
|
|
|
- Leaving fullscreen returns every tab to maximized.
|
|
|
|
|
|
2026-08-09 13:02:46 +10:00
|
|
|
## 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.
|
|
|
|
|
|
2026-08-14 11:31:32 +10:00
|
|
|
## Frame dumps (SIGUSR1 / TABBED_AUTODUMP)
|
2026-08-09 13:02:46 +10:00
|
|
|
|
2026-08-14 11:31:32 +10:00
|
|
|
The `SIGUSR1` signal and `TABBED_AUTODUMP` write raw `P6` portable-pixmap
|
|
|
|
|
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-09 13:02:46 +10:00
|
|
|
tabbed-wlroots rendered for the host).
|
2026-08-14 11:31:32 +10:00
|
|
|
- `SIGUSR1` also writes `/tmp/host_N.ppm` — the host compositor's back
|
2026-08-09 13:02:46 +10:00
|
|
|
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`.
|