7.3 KiB
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 + 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).
meson setup build
meson compile -C build
The resulting binary is build/tabbed-wlroots.
Debian / Ubuntu
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
sudo pacman -S base-devel meson ninja pkgconf wayland wayland-protocols \
wlroots xkbcommon mesa pixman
Fedora
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
tabbed-wlroots [prog...]
Each <prog> is one shell command; quote it when it takes arguments, e.g.
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.
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).
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 ....
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 onPATHbefore the session starts: if its program isn't found, tabbed-wlroots 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 tabbed-wlroots window, or sending SIGINT/SIGTERM, shuts the whole session down and kills all client children.
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) |
Ctrl+Shift+T |
Open a new terminal tab next to the current one |
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
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.
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.
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.
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.
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 (SIGUSR1 / TABBED_AUTODUMP)
The SIGUSR1 signal and TABBED_AUTODUMP write raw P6 portable-pixmap
files to /tmp regardless of the log level:
SIGUSR1writes/tmp/scene_N.ppm— the composed scene (whatever tabbed-wlroots rendered for the host).SIGUSR1also writes/tmp/host_N.ppm— the host compositor's back buffer, so you can see what the host actually displayed.Nis an incrementing per-run counter; a singleSIGUSR1produces 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=2the diagnostic that detects the client's own shm buffer additionally writes that buffer to/tmp/client_N.ppm.