Tessa Setup & Customization Guide
New to Tessa? Start with the Quickstart for installation, first launch, and the essential keys.
This guide collects the setup and customization surface of the current Tessa tree. It covers the implementation through the 0.7 roadmap, including special workspaces, shell v2, the effect helper, performance instrumentation, and the interactive config editor.
Tessa is three programs:
tessa— the wlroots compositor/window manager.tessa-panel— the wallpaper, floating taskbar, launcher, and shell UI.tessa-ctl— the state/event/automation client.
There is no Waybar, Rofi, or wofi dependency. Configuration is a flat
key = value file. Super+R reloads it, tessa-ctl config-check diagnoses it,
and tessa-ctl config prints the effective values and parsed rules.
1. Requirements
Section titled “1. Requirements”Required
Section titled “Required”For a normal install on Ubuntu 26.04, use the prebuilt .deb in section 2.1.
On Arch Linux, use the AUR-backed PKGBUILD in section 2.2. The Zig/wlroots
development requirements below apply only to source and package builds.
- Linux (development is tested on Ubuntu 26.04)
- Zig 0.17.0
- wlroots 0.20 development files
- Wayland, wayland-protocols >= 1.47, xkbcommon, pixman, libdrm, libinput, libseat, libudev, EGL/GLES
- Cairo, Pango, GLib/GIO for the panel and notifications
- Fonts: Inter, JetBrains Mono, Noto Sans CJK JP (or equivalent)
- A terminal; the default command is
kitty(terminal = footis fine)
Ubuntu 26.04 example (install Zig 0.17 from upstream if the distribution package is older):
sudo apt install build-essential pkg-config git \ libwayland-dev wayland-protocols libwlroots-0.20-dev \ libxkbcommon-dev libpixman-1-dev libdrm-dev libinput-dev \ libseat-dev libudev-dev libegl-dev libgles2-mesa-dev \ libcairo2-dev libpango1.0-dev libglib2.0-dev libfontconfig1-dev \ fonts-inter fonts-jetbrains-mono fonts-noto-cjk \ xwayland xauth xterm fcitx5 fcitx5-mozcOn Arch Linux, the official zig (0.17) and wlroots0.20 match this tree.
Install them as shown in section 2.2.
If wlroots is installed under /usr/local, verify that pkg-config and the
shared library are visible:
pkg-config --modversion wlroots-0.20ldconfig -p | grep wlrootsOptional
Section titled “Optional”xwayland-satellitefor X11 applications (xwayland = trueauto-start)wlr-randrfor runtime output mode/position/scale/transform changesgrimfor screenshots and testswtypefor keyboard injection in headless testsswaylockfor the defaultSuper+Escapelock actionswayidleandwlopmfor idle/locking and output powerwpctl(PipeWire),pactl(PulseAudio), oramixer(ALSA) for volumexdg-desktop-portal,xdg-desktop-portal-wlr, and PipeWire for screen sharing- Fcitx5 + Mozc for Wayland IME and XIM
- scenefx 0.5 for the default rounded corners and backdrop blur
(
-Dscenefx=falseomits it)
2. Install or Build
Section titled “2. Install or Build”2.1 Binary package (Ubuntu 26.04)
Section titled “2.1 Binary package (Ubuntu 26.04)”The recommended install uses the v0.2.4 amd64 package:
sudo apt updatesudo apt install curlcurl -fLO https://github.com/Keychrom/Tessa/releases/download/v0.2.4/tessa_0.2.4_amd64.debcurl -fLO https://github.com/Keychrom/Tessa/releases/download/v0.2.4/SHA256SUMSgrep ' tessa_0.2.4_amd64.deb$' SHA256SUMS | sha256sum -c -sudo apt install ./tessa_0.2.4_amd64.deb0.2.4 may be the final
.debrelease. It is the last package planned against Zig 0.16 and wlroots 0.19. The current source tree (0.2.5) targets Zig 0.17, wlroots 0.20, and scenefx 0.5; use a source build or the Arch package for the new toolchain.
The package installs:
/usr/bin/tessa,/usr/bin/tessa-panel,/usr/bin/tessa-ctl/usr/bin/tessa-session/usr/share/wayland-sessions/tessa.desktop/usr/share/tessa/config.example/usr/share/xdg-desktop-portal/portals/tessa.portal- man pages and documentation
Log out and select Tessa in GDM, SDDM, greetd, or another display manager. The session entry starts the installed compositor and panel directly; the source session installer in section 3.2 is only for development.
Useful optional packages:
sudo apt install kitty wlr-randr grim dbus \ fcitx5 fcitx5-mozc \ xdg-desktop-portal xdg-desktop-portal-wlr pipewirexwayland-satellite is not packaged in Ubuntu 26.04. Install it from upstream
if X11 applications are needed; otherwise xwayland = true will log a warning
and X11 clients will not start.
Verify the package and configuration:
dpkg -s tessacommand -v tessa tessa-panel tessa-ctltessa-ctl config-checkIf Tessa was previously installed manually under /usr/local, keep only one
installation. /usr/local/bin normally precedes /usr/bin in PATH, so the
manual binaries would otherwise win.
2.2 Arch Linux
Section titled “2.2 Arch Linux”Arch’s official repositories provide zig 0.17, wlroots0.20, and scenefx
0.5, which match this tree. Install the build dependencies:
sudo pacman -Syu --needed base-devel git zig wlroots0.20 scenefx \ wayland wayland-protocols libxkbcommon libinput pixman libdrm \ libseat libudev libegl libgles2 mesa cairo pango glib2Then build and install Tessa:
git clone https://github.com/Keychrom/Tessa.gitcd Tessa/packaging/archmakepkg -siThe resulting tessa-git package installs the same files as the .deb
(binaries, session entry, config example, portal selector, man pages, and
documentation) under /usr.
Verify:
tessa-ctl config-checkcommand -v tessa tessa-panel tessa-ctlUseful optional packages:
sudo pacman -S --needed kitty wlr-randr grim dbus fcitx5 fcitx5-mozc \ xdg-desktop-portal xdg-desktop-portal-wlr pipewireFor X11 applications, use AUR xwayland-satellite-git. The PKGBUILD depends on
scenefx and enables rounded corners and blur by default; pass
-Dscenefx=false to a source build for the plain wlroots scene. The 0.2.4 Arch
package was built and exercised with the 0.16/0.19 AUR toolchain. The 0.2.5
PKGBUILD has been built and installed on Arch Linux with the official zig and
wlroots0.20, and the full headless suite passes in plain and scenefx builds.
If makepkg reports an ABI or dependency problem, confirm zig version is
0.17.x and pkg-config --modversion wlroots-0.20 is 0.20.x.
2.3 Build from source (development)
Section titled “2.3 Build from source (development)”From the repository root:
chmod +x scripts/*.sh scripts/tessa-session./scripts/build.shRelease build:
./scripts/build.sh -Doptimize=ReleaseSafescripts/build.sh requires Zig 0.17.x; set ZIG=/path/to/zig to choose an
explicit toolchain. On Arch, the official zig package is the supported
compiler.
Artifacts:
zig-out/bin/tessazig-out/bin/tessa-panelzig-out/bin/tessa-ctl
Run tests with:
sh tests/smoke.shsh tests/animations.shsh tests/output-rules.shsh tests/config-watch.shThe full set is sh tests/*.sh. Tests skip optional dependencies that are not
installed (foot, grim, wlr-randr, PipeWire, D-Bus, etc.).
tests/effects.sh runs only in a scenefx build.
scenefx effects build
Section titled “scenefx effects build”The default build uses scenefx 0.5 for rounded corners and backdrop blur and requires its GLES2 renderer. Install scenefx 0.5 and build normally:
./scripts/build.sh -Doptimize=ReleaseSafeFor the plain wlroots scene, which supports both GL and pixman, disable scenefx:
./scripts/build.sh -Doptimize=ReleaseSafe -Dscenefx=falseIf scenefx is installed under a non-system prefix, set its pkg-config and library paths for both build and run:
export PKG_CONFIG_PATH=$HOME/.local/lib/x86_64-linux-gnu/pkgconfig:$PKG_CONFIG_PATHexport LD_LIBRARY_PATH=$HOME/.local/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH./scripts/build.sh -Dscenefx=trueThe corner_radius and blur keys are accepted in a -Dscenefx=false build
but have no visual effect.
3. Run and Install
Section titled “3. Run and Install”3.1 Directly from a TTY
Section titled “3.1 Directly from a TTY”Run from the active physical console or a TTY, not over SSH. For a package install:
tessaFor a source build:
cd /path/to/TessaPATH="$PWD/zig-out/bin:$PATH" ./zig-out/bin/tessawlroots autodetects DRM and libinput. Tessa starts one tessa-panel per output
from PATH. The DRM backend needs an active logind/seat session and access to
input devices; run it as your normal user from a logged-in TTY, not as root.
An SSH session cannot use DRM, so use headless or nested Wayland there.
Quit with Super+Shift+E or tessa-ctl quit.
3.2 Display-manager session
Section titled “3.2 Display-manager session”The .deb and Arch tessa-git packages already install the session entry.
Use this only for a source build:
# Per-user install./scripts/install-session.sh
# System-wide installsudo PREFIX=/usr/local ./scripts/install-session.shAdd -Dscenefx=false to either command for a plain build without scenefx,
for example on Ubuntu 26.04:
./scripts/install-session.sh -Dscenefx=falseThe installer places:
tessa,tessa-panel, andtessa-ctltessa-sessionwayland-sessions/tessa.desktopxdg-desktop-portal/portals/tessa.portal
Log out and choose Tessa in GDM, SDDM, greetd, or another display manager.
tessa-session prepends its directory to PATH, sets
XDG_CURRENT_DESKTOP=Tessa, and starts tessa.
Distribution packaging definitions live in packaging/arch/PKGBUILD and
debian/. See packaging/README.md for installed
files and dependencies.
3.3 Headless development
Section titled “3.3 Headless development”./scripts/run-headless.shIn another shell:
./scripts/screenshot.sh /tmp/tessa.pngThe script sets:
XDG_RUNTIME_DIR=/tmp/tessa-headless(override withTESSA_RUNTIME_DIR)WLR_BACKENDS=headlessWLR_RENDERER=pixmanWLR_HEADLESS_OUTPUTS=1(override in the environment)PATHcontainingzig-out/bin
For two headless outputs:
WLR_HEADLESS_OUTPUTS=2 ./scripts/run-headless.sh3.4 Nested Wayland
Section titled “3.4 Nested Wayland”To run Tessa as a window on another Wayland compositor:
WLR_BACKENDS=wayland WLR_RENDERER=gles2 \ PATH="$PWD/zig-out/bin:$PATH" ./zig-out/bin/tessa3.5 XWayland / X11
Section titled “3.5 XWayland / X11”Tessa uses the external
xwayland-satellite, not
wlroots’ built-in XWayland. Put it in PATH or set TESSA_XWAYLAND_BIN.
xwayland = truexwayland_display = autoauto scans /tmp/.X11-unix/X<n> and /tmp/.X<n>-lock for the first free
display. Use :1 etc. to force one. TESSA_NO_XWAYLAND=1 disables XWayland.
XIM:
xim = truexim_server = fcitx# Optional: start after the X display appearsxim_start = fcitx5 -dXMODIFIERS=@im=fcitx is exported to X11 clients. Leave xim_start empty if
Fcitx5 is already started by the session.
3.6 Screen-sharing portal
Section titled “3.6 Screen-sharing portal”install-session.sh installs tessa.portal. For a manual install, copy
packaging/portal/tessa.portal into
/usr/share/xdg-desktop-portal/portals/.
Required: xdg-desktop-portal, xdg-desktop-portal-wlr, and PipeWire.
Example xdpw setup for one output:
[screencast]chooser_type=noneoutput_name=HEADLESS-14. Environment Variables
Section titled “4. Environment Variables”| Variable | Meaning |
|---|---|
TESSA_CONFIG |
Config path; defaults to $XDG_CONFIG_HOME/tessa/config or ~/.config/tessa/config |
TESSA_IPC |
IPC socket path; defaults to $XDG_RUNTIME_DIR/tessa.sock |
TESSA_OUTPUT_CONFIG |
Output/layout state file; defaults to $XDG_CONFIG_HOME/tessa/outputs.conf |
TESSA_DEBUG |
Verbose Tessa/wlroots logging |
TESSA_PANEL_DEBUG |
Verbose panel logging |
TESSA_CTL_DEBUG |
Verbose tessa-ctl logging |
TESSA_NO_PANEL |
Disable automatic panel start |
TESSA_PANEL_BIN |
Override the panel executable |
TESSA_PANEL_OUTPUT |
Output name for a manually started panel |
TESSA_NO_XWAYLAND |
Disable XWayland start |
TESSA_ALLOW_LINUX_DMABUF |
Re-enable linux-dmabuf on a vmwgfx session where it is auto-disabled |
TESSA_XWAYLAND_BIN |
Path to xwayland-satellite |
TESSA_SOCKET_NAME |
Explicit Wayland socket name |
TESSA_STARTUP |
Extra startup command (prefer the startup config key) |
WLR_BACKENDS |
drm, headless, wayland, etc. |
WLR_RENDERER |
gles2 or pixman (scenefx builds use GLES2) |
WLR_HEADLESS_OUTPUTS |
Number of headless outputs |
TESSA_RUNTIME_DIR |
Runtime directory used by the headless/screenshot scripts |
5. Configuration Basics
Section titled “5. Configuration Basics”Copy the example:
mkdir -p ~/.config/tessacp config/config.example ~/.config/tessa/configFormat rules:
key = value- Lines beginning with
#are comments include = pathloads another file; relative paths are resolved next to the including file- Include depth is limited to 8
- Flat values are taken verbatim; whitespace is preserved after trimming, and quote characters are not stripped
- Rule/extension clauses support quoted values; a
;cannot be used inside a flat value
Reload and inspect:
tessa --check-config [file] # offline validation; non-zero on errorstessa-ctl config # effective settings and parsed rulestessa-ctl config --json # schema-1 JSONtessa-ctl config-check # non-zero on errorstessa-ctl dispatch reload # same as Super+Rtessa --check-config loads the config into a temporary parser state without
starting the compositor. It prints the same file/line diagnostics and is safe
to use from editors, CI, or a pre-login TTY. A reload with errors is rejected
and the previous configuration remains active.
Diagnostics include the file name and line number.
Tessa also watches the config directory. Atomic saves of the main config and
.conf includes in that directory reload automatically after a short debounce;
manual Super+R / tessa-ctl dispatch reload is still available. Invalid
edits are rejected and keep the previous configuration.
6. Configuration Keys
Section titled “6. Configuration Keys”6.1 Core and layout
Section titled “6.1 Core and layout”| Key | Default | Meaning |
|---|---|---|
terminal |
kitty |
Command for Super+Return and Terminal=true apps |
gaps |
10 |
Gap between tiles |
outer_gaps |
16 |
Gap around the tiling area |
border_width |
2 |
Window border thickness (0–16) |
master_ratio |
0.58 |
Master width ratio (0.15–0.9) |
master_count |
1 |
Windows stacked in the master column (1–8) |
mosaic_ratio |
0.62 |
Mosaic slice ratio (0.25–0.85) |
mosaic_min_size |
160 |
Minimum tile before mosaic falls back to grid (80–600) |
grid_columns |
0 |
Fixed grid columns; 0 is automatic (max 8) |
layout |
master |
Default: master, mosaic, grid, columns, scrolling, monocle, stacking, or custom |
layout_cycle |
master,mosaic,grid,columns |
Comma-separated layouts cycled by Super+T |
default_column_width |
50% |
Scrolling column width (50%, 0.5, or fixed 640) |
column_width_presets |
33.333%,50%,66.667% |
Presets cycled by column-width-cycle |
center_focused_column |
never |
Scrolling focus policy: never, always, on-overflow |
animation_view_ms |
180 |
Scrolling viewport animation duration |
animation_view_curve |
ease-out-cubic |
Curve for the scrolling viewport |
layout_1 … layout_9 |
inherited | Initial layout for a workspace without a saved/runtime override |
layout_helper |
empty | One asynchronous custom layout helper per output |
layout_helper_timeout |
100 |
Helper timeout in ms (10–1000) |
effect_helper |
empty | Long-lived per-window visual effect helper |
effect_helper_timeout |
200 |
Effect-helper timeout in ms (20–2000) |
extension_ready_timeout |
10000 |
Time to wait for a supervised extension’s token handshake in ms (0 disables; 1000–60000) |
frame_time_budget |
16 |
Render-commit budget in ms; frames above it are counted by tessa-ctl perf |
direct_scanout |
true |
Hide compositor nodes for an opaque fullscreen client so wlroots can scan it out directly |
tearing |
off |
off, fullscreen, or force; always requires the client’s async hint |
explicit_sync |
true |
Advertise linux-drm-syncobj-v1 when renderer/backend timelines and a DRM fd are available; restart to change |
output_config |
~/.config/tessa/outputs.conf |
Output and layout persistence file |
workspace_pin |
— | OUTPUT WORKSPACE pin; repeatable |
workspace-name |
— | WORKSPACE NAME display/command alias; repeatable |
special-workspace |
— | Declare a named special (hidden overlay) workspace; repeatable |
workspace-rule |
— | Per-workspace layout/spacing rule |
output-rule |
— | Per-output layout, spacing, theme, and VRR rule matched by connector or EDID |
input / input-rule |
— | Per-device libinput policy; repeatable |
profile |
— | Named layout/tuning profile referenced by output/workspace rules |
window-rule |
— | Declarative window rule |
layer-rule |
— | Declarative layer-shell rule |
theme-role |
— | Semantic color role (accent, borders, wallpaper, panel) |
mode |
default |
Start a named input-mode section for following bind lines |
bind |
— | Add or override a key binding |
include |
— | Include another config file |
6.2 Appearance and animation
Section titled “6.2 Appearance and animation”| Key | Default | Meaning |
|---|---|---|
accent |
#e2684f |
Accent for borders, bar, launcher |
bg |
#0a0f1a |
Base wallpaper color |
bg_top |
unset | Top wallpaper gradient color; derived from bg if omitted |
bg_bottom |
unset | Bottom wallpaper gradient color |
wallpaper_art |
true |
Draw ink/sun/water decorative art |
wallpaper_image |
empty | PNG wallpaper path (absolute or ~/...); replaces gradient/art |
wallpaper_shader |
empty | Command that prints a binary PPM (P6) frame; takes precedence over the image |
wallpaper_fit |
cover |
Image fit: cover, contain, or stretch |
border_active |
empty | Focused border; empty uses accent |
border_inactive |
#293347 |
Unfocused border |
font |
Inter |
Panel UI font |
animations |
true |
Window open/close and workspace motion |
reduce_motion |
false |
Disable movement animations |
reduce_motion_animations |
none |
none, or fade for a closing-window fade |
reduce_motion_fade_ms |
120 |
Fade duration when reduced motion is fade |
animation_ms |
160 |
Default animation duration (0–1000 ms) |
animation_open_ms |
inherited | Open-animation duration (-1 inherits) |
animation_workspace_ms |
inherited | Workspace-animation duration (-1 inherits) |
animation_close_ms |
inherited | Close-effect duration (-1 inherits) |
animation_curve |
ease-out-cubic |
Global timing curve for open/workspace |
animation_open_curve |
inherited | Open timing curve |
animation_workspace_curve |
inherited | Workspace timing curve |
animation_close_curve |
smoothstep |
Close timing curve |
bezier |
— | Named cubic-bezier: name, x1, y1, x2, y2 |
spring |
— | Named spring: name, damping, frequency |
animation_open_x / _y |
0 / 18 |
Open-animation start offset in px |
animation_workspace_offset |
54 |
Workspace-slide start offset in px |
animation_close_scale |
0.92 |
Close snapshot final scale (0–1) |
animation_close_fade |
true |
Fade the close snapshot |
shadows |
true |
Layered window shadows |
corner_radius |
10 |
Rounded window radius (0–64; scenefx build) |
blur |
true |
Backdrop blur for translucent surfaces (scenefx build) |
blur_radius |
6 |
Blur radius (1–32) |
blur_passes |
3 |
Blur quality passes (1–5) |
animations = false, reduce_motion = true, or animation_ms = 0 disables
motion.
6.3 Panel and shell
Section titled “6.3 Panel and shell”| Key | Default | Meaning |
|---|---|---|
panel / autostart_panel |
true |
Auto-start tessa-panel per output |
panel_modules |
all | Enabled panel parts: background, bar, launcher, popovers, notifications, toasts, tray, media, audio; also all/none |
bar_height |
44 |
Bar height (32–96) |
bar_top_margin |
12 |
Distance from the top (0–64) |
bar_side_margin |
16 |
Left/right margin (0–128) |
panel_bg |
#0e131d |
Base panel background used by panel.background and per-surface defaults |
panel_fg |
#edf2fa |
Base panel foreground used by panel.foreground and per-surface defaults |
bar_layout |
all modules | Comma/space-separated module list; selects and orders the right cluster |
bar-font, launcher-font, popover-font, toast-font |
font |
Per-surface fonts |
bar-fg, launcher-fg, popover-fg, toast-fg |
panel_fg |
Per-surface foreground colors |
bar-bg, launcher-bg, popover-bg, toast-bg |
panel_bg |
Per-surface background colors |
launcher_entry |
— | Extra launcher item Name | command; repeatable |
power_lock |
loginctl lock-session |
Power-menu Lock |
power_suspend |
systemctl suspend |
Suspend |
power_reboot |
systemctl reboot |
Reboot |
power_off |
systemctl poweroff |
Power Off |
screenshot_dir |
~/Pictures/Screenshots |
PNG output directory |
volume_status |
empty | Volume status command; auto-detects wpctl/pactl/amixer |
volume_up |
empty | Volume-up command |
volume_down |
empty | Volume-down command |
volume_mute |
empty | Mute-toggle command |
Changing bar geometry recreates the bar layer surface and updates its exclusive zone.
6.4 Input and gestures
Section titled “6.4 Input and gestures”| Key | Default | Meaning |
|---|---|---|
kb_layout |
environment | XKB layout (e.g. us,de) |
kb_variant |
empty | XKB variant |
kb_options |
empty | XKB options (e.g. grp:alt_shift_toggle) |
kb_model |
empty | XKB model |
kb_rules |
empty | XKB rules |
repeat_rate |
25 |
Repeat rate |
repeat_delay |
600 |
Delay before repeat (ms) |
leader |
unset | Leader chord such as SUPER+SPACE |
leader_timeout |
1000 |
Leader-mode timeout in ms; 0 keeps it active |
mode_timeout |
0 |
Timeout for the current named mode in ms |
gestures |
true |
Touchpad gestures |
gesture_fingers |
3 |
Fingers for workspace/launcher swipes (2–5) |
gesture_workspace_threshold |
400 |
Horizontal swipe threshold (20–1000) |
gesture_launcher_threshold |
300 |
Upward swipe threshold (20–1000) |
touch |
true |
Accept touch devices |
tablet |
true |
tablet-v2 tools and pads |
pointer_constraints |
true |
pointer-constraints-v1 |
cursor_theme |
system default | Xcursor theme name; empty uses the system default |
cursor_size |
24 |
Cursor size in logical pixels (16–96) |
input / input-rule |
— | Per-device libinput rule; repeatable |
input rules are described in section 11. Cursor theme and size reload live;
an unknown theme leaves the previous cursor active.
Super+R also rebuilds keymaps.
6.5 X11 and IME
Section titled “6.5 X11 and IME”| Key | Default | Meaning |
|---|---|---|
xwayland |
true |
Start xwayland-satellite and export DISPLAY |
xwayland_display |
auto |
auto or an explicit :0 etc. |
xim |
true |
Export XMODIFIERS for X11 clients |
xim_server |
fcitx |
XIM server name |
xim_start |
empty | Command started after the X display appears |
linux_dmabuf |
true |
Advertise zwp_linux_dmabuf_v1; auto-disabled on vmwgfx |
6.6 Startup, hooks, and automation
Section titled “6.6 Startup, hooks, and automation”| Key | Meaning |
|---|---|
startup |
Command run once at startup |
hook |
<event> <command> lifecycle hook; repeatable |
event-rule |
Conditional event command; see section 8.8 |
include |
Split configuration across files |
See the event table in section 13.
7. Default Bindings and Mouse
Section titled “7. Default Bindings and Mouse”Keyboard
Section titled “Keyboard”| Binding | Action |
|---|---|
Super+Return |
Terminal |
Super+D |
Launcher |
Super+V |
Clipboard history |
Super+Shift+S |
Screenshot UI |
Super+M |
MPRIS media controls |
Super+Shift+T |
StatusNotifier tray |
Super+Shift+A |
Volume popover |
Super+T |
Cycle through layout_cycle (four original layouts by default) |
Super+Q |
Close focused window |
Super+Shift+E |
Quit Tessa |
Super+R |
Reload config |
Super+1 … Super+9 |
Switch workspace |
Super+Shift+1 … 9 |
Move window to workspace |
Super+H/J/K/L |
Focus left/down/up/right |
Super+left-drag |
Reorder a tiled window; inserts a column in scrolling |
Super+wheel |
Scroll the viewport in a scrolling layout |
Super+Shift+H/J/K/L |
Swap/move in that direction, or to an adjacent output |
Super+Space |
Toggle floating |
Super+F |
Toggle fullscreen |
Super+grave |
Toggle the scratchpad |
Super+Shift+grave |
Move the focused window to/from the scratchpad |
Super+Shift+G |
Group/ungroup tiled windows on the workspace |
Super+Escape |
Spawn swaylock |
Super+Tab / Super+Shift+Tab |
Cycle windows; cycles tabs when a group is focused |
Super+Ctrl+H/L |
Shrink/grow master ratio |
Super+Alt+H/L |
Shrink/grow floating width |
Super+Alt+J/K |
Grow/shrink floating height |
Use XKB keysym names. Super, Logo, and Win are equivalent modifiers.
bind = SUPER+Return terminalbind = SUPER+d launcherbind = SUPER+1 workspace 1bind = SUPER+SHIFT+1 move-to-workspace 1bind = SUPER+h focus leftbind = SUPER+SHIFT+h move leftbind = SUPER+ctrl+h ratio -0.03bind = SUPER+SHIFT+r enter-mode resizeActions: terminal, spawn, close, quit, workspace,
move-to-workspace, focus, move, toggle-float, toggle-fullscreen,
toggle-scratchpad, move-to-scratchpad, special/toggle-special,
move-to-special, toggle-group, group-next,
group-prev, group-add, group-remove,
focus-next, focus-prev, launcher, clipboard, screenshot, media,
tray, audio, reload, ratio, toggle-layout, set-layout,
resize-width, resize-height, focus-column-*, move-column-*,
move-window-up/down, consume-window, expel-window,
column-width-cycle, column-width-cycle-back, column-width-set,
column-width-maximize, center-column, and enter-mode.
- Click a window to focus; click a bar chip to focus it
- Right-click a bar chip to close the window
- Scroll over the bar to cycle workspaces
Super+left-dragmoves a floating window; on a tiled window it reorders tilesSuper+right-dragresizes a floating window- In scrolling mode,
Super+wheelscrolls the viewport - Client-side titlebar drags work for floating windows
- Click
TESSAfor the launcher, right-click for the power menu - Click the clock for the calendar
8. Layouts and Rules
Section titled “8. Layouts and Rules”8.1 Built-in layouts
Section titled “8.1 Built-in layouts”- master: master area plus a vertical stack;
master_ratio,master_count - mosaic (
spiral): recursive spiral for up to eight windows; falls back to grid when the configured minimum tile cannot be maintained, and to master above eight windows - grid:
ceil(sqrt(n))columns; the last row is centered;grid_columnscan fix the column count - columns (
column): equal-width full-height columns - scrolling (
scroll,scroller): Niri-style horizontal column strip - monocle: the focused tiled window fills the workspace
- stacking (
deck): master area plus overlapping stack cards - custom (
helper): external layout helper
Super+T cycles the built-in layouts. The default cycle is the original four;
add new modes explicitly with layout_cycle. custom is not in the default
cycle; select it explicitly with set-layout custom. layout_N only seeds
workspaces with no saved/runtime choice. Runtime choices are persisted in
outputs.conf.
8.1.1 Scrolling, monocle, and stacking
Section titled “8.1.1 Scrolling, monocle, and stacking”- scrolling: Niri-style horizontal columns. New windows become a column
next to the focused column. Each column has a proportion or fixed width; a
column can contain multiple windows via
consume. - monocle: the focused tiled window fills the usable area; other tiled windows are hidden until focused.
- stacking (alias
deck): the firstmaster_countwindows use the master area, and the remaining windows overlap in the right stack area. The focused card is raised.
Scrolling keys:
layout = scrollingdefault_column_width = 50% # 50%, 0.5, or a fixed 640column_width_presets = 33.333%,50%,66.667%center_focused_column = never # never, always, on-overflowanimation_view_ms = 180layout_cycle = master,scrolling,monocle,stackingIn scrolling mode, focus left/right and move left/right operate on columns;
focus up/down and move up/down operate within a column. The explicit
actions are focus-column-*, move-column-*, move-window-up/down,
consume-window, expel-window, column-width-cycle,
column-width-cycle-back, column-width-set, column-width-maximize, and
center-column.
consume-window appends the active window of the right column to the focused
column. expel-window moves the bottom window of the focused column into a new
column on the right. Grouped windows behave as a single tabbed column;
consume/expel is refused while a group is involved.
swap and reorder work on the flat tiled order and are refused while the
workspace is scrolling; use column-move, move-window-*, or a tile drag
there.
Column state (order, width, viewport) is session state. Switching away from scrolling flattens the column order into the normal tiled order so the next layout preserves it, but multi-window columns and per-column widths are not restored when scrolling is selected again. The layout mode itself is persisted normally.
layout_cycle accepts a comma-separated list of layout names. The default is
master,mosaic,grid,columns; add scrolling, monocle, or stacking to make
them reachable with Super+T.
8.2 workspace-rule
Section titled “8.2 workspace-rule”workspace-rule = ws=2; layout=grid; gaps=4; outer-gaps=8; master-ratio=0.65Keys: required ws=1..9, plus layout, gaps, outer-gaps,
master-ratio=0.15..0.9, master-count=1..8, mosaic-ratio=0.25..0.85,
mosaic-min-size=80..600, grid-columns=0..8,
default-column-width=50%|0.5|640, center-focused-column=never|always|on-overflow,
and profile=NAME.
8.3 output-rule
Section titled “8.3 output-rule”output-rule = output=HDMI-A-1; layout=grid; gaps=8; outer-gaps=12output-rule = output=HDMI-A-1; ws=2; layout=master; master-ratio=0.62output-rule = output=HDMI-A-1; profile=compact; gaps=4output-rule = output=HEADLESS-*; accent=#8fd3ff; bg=#0c1118output-rule = output=HEADLESS-*; border-active=#8fd3ff; border-inactive=#243040output-rule = output=HEADLESS-*; panel-bg=#111827; panel-fg=#eef2f8output-rule = output=HEADLESS-*; bg-top=#162038; bg-bottom=#070a12output-rule = output=HEADLESS-*; bar-height=52; bar-top-margin=8; bar-side-margin=20output-rule = make=Dell Inc.; model=DELL*; adaptive-sync=onoutput-rule = serial=ABC123; adaptive-sync=offMatch clauses:
output=/monitor=: connector name, for exampleHDMI-A-1make=/vendor=: EDID manufacturer stringmodel=: EDID model stringserial=/edid-serial=: EDID serial number- Any combination of the above; at least one is required. Every field that is
present must match.
*and?wildcards are supported.
Layout/tuning keys (optionally narrowed with ws=):
ws=1..9: optional; omitted means all workspaceslayout=...gaps=N,outer-gaps=Nmaster-ratio=0.15..0.9,master-count=1..8mosaic-ratio=0.25..0.85,mosaic-min-size=80..600grid-columns=0..8default-column-width=50%|0.5|640center-focused-column=never|always|on-overflowprofile=NAME: apply a named profile before this rule’s explicit keys
Theme keys (all workspaces only):
accent=#RRGGBB,bg=#RRGGBBborder-active=#RRGGBB,border-inactive=#RRGGBBpanel-bg=#RRGGBB,panel-fg=#RRGGBBbg-top=#RRGGBB,bg-bottom=#RRGGBBbar-height=0..200,bar-top-margin=0..128,bar-side-margin=0..256; the panel clamps them to 32–96, 0–64, and 0–128 at runtime
Output policy keys (all workspaces only):
adaptive-sync=on|off/vrr=on|off: request VRR on the matched output. Tessa commits the change immediately and logs whether it succeeded. Outputs that do not advertise adaptive-sync support ignore the request. Removing the rule restores the output’s state from before Tessa applied a policy; startup defaults are normally off.
The compositor applies the output background and border colors. The panel
re-reads the same rule for its own output, so bar height, margins, wallpaper,
and panel colors stay in sync. Theme and adaptive-sync fields are ignored when
ws= is present.
Named profiles
Section titled “Named profiles”profile = compact; gaps=0; outer-gaps=0; master-ratio=0.5; master-count=2profile = grid3; layout=grid; grid-columns=3; gaps=6; outer-gaps=2output-rule = output=DP-1; profile=compact; gaps=4workspace-rule = ws=2; profile=grid3Profiles can be declared before or after the rules that use them. Explicit
keys in the rule override the profile. Profiles cover layout and spacing;
theme keys remain on output-rule. An unknown profile is a config error and
the reload is rejected. Workspace profiles can set layout, gaps,
outer-gaps, master-ratio, master-count, mosaic-ratio,
mosaic-min-size, and grid-columns.
Precedence:
global defaults < output rule (all workspaces) < workspace-rule < output rule with ws= < runtime layout choiceSuper+Ctrl+H/L changes the active output/workspace ratio for the current
session. Config reload restores configured values.
8.4 window-rule
Section titled “8.4 window-rule”window-rule = app-id=pavucontrol; float=truewindow-rule = title=*Picture-in-Picture*; float=true; position=40,40window-rule = app-id=firefox; workspace=2window-rule = app-id=org.example.*; fullscreen=truewindow-rule = match=app ~ "^firefox$" && title !~ "Private|Incognito"; workspace=2window-rule = match=(class == "foot" || class == "Alacritty") && output ~ "^DP-"; float=truewindow-rule = title=Chat*; border-active=#4cc9f0; border-inactive=#1b2a41window-rule = app-id=mpv; opacity=0.9; corner-radius=0; border-width=0window-rule = title=Picture-in-Picture; opacity=0.85; corner-radius=14; blur=trueMatch keys:
| Key | Meaning |
|---|---|
app-id, app_id, class |
xdg app-id |
title |
Window title |
output, monitor |
Output name |
match, expr |
Boolean expression with POSIX ERE regexes |
Expression fields are app / app-id / class, title, and output /
monitor. Operators are ==, !=, ~ (regex match), !~ (regex
non-match), && / and, || / or, ! / not, and parentheses. Quoted
values may contain spaces. Regexes use POSIX extended syntax and are
case-sensitive.
Actions:
| Key | Meaning |
|---|---|
float=true/false |
Floating or tiled |
tile=true/false |
Inverse of float |
fullscreen=true/false |
Fullscreen |
workspace=1..9|name |
Assign workspace |
workspace=special |
Assign to the scratchpad (alias for scratchpad=true) |
workspace=special:NAME |
Assign to a declared special workspace |
special=NAME |
Assign to a declared special workspace (shorthand) |
scratchpad=true/false |
Hidden floating scratchpad assignment |
size=WxH |
Floating size |
width=N, height=N |
One floating axis |
position=X,Y |
Floating position |
border-active=#RRGGBB |
Per-window focused border |
border-inactive=#RRGGBB |
Per-window unfocused border |
opacity=0.0..1.0 |
Window and decoration opacity |
corner-radius=0..64 |
Per-window rounding (rounding is an alias) |
blur=true/false |
Per-window backdrop blur |
shadow=true/false |
Per-window layered shadow |
tearing=true/false |
Allow async tearing for this client when tearing is fullscreen or force (or opt in while the global mode is off) |
border-width=0..16 |
Per-window border thickness; 0 hides it |
tint=#RRGGBBAA |
Translucent colour overlay above the content; none clears it |
animation=true/false |
Enable or disable motion for this window |
animation-ms=N |
Base animation duration override (0–1000) |
animation-open-ms, animation-workspace-ms, animation-close-ms |
Per-kind duration override |
animation-curve=NAME |
Open/workspace curve override |
animation-open-curve, animation-workspace-curve, animation-close-curve |
Per-kind curve override |
Patterns use * and ?. Simple keys and match are combined with AND. Rules
run in file order and later matches win, including border colors and visual
effects. Invalid expressions or regexes are reported with file/line diagnostics
and reject the reload. Structural actions (float, workspace, size,
position, fullscreen) run at map time and on config reload. Decorative
actions (border-*, opacity, corner-radius, blur, shadow,
tearing, border-width) also re-evaluate immediately when the title or
app-id changes.
Motion rule keys (animation*) use the same pass and apply to the next
transition. Per-rule colors, border width, opacity, rounding, and shadow state
are carried into the close snapshot effect. corner-radius and blur require a
scenefx build (the default; disabled with -Dscenefx=false); opacity, shadows,
and border width work in every build.
8.5 layer-rule
Section titled “8.5 layer-rule”Layer-shell surfaces (bars, notifications, wallpaper providers, and other
zwlr_layer_shell_v1 clients) use a separate rule list:
layer-rule = namespace=tessa-bar; opacity=0.94; corner-radius=12; blur=truelayer-rule = namespace=tessa-notifications; layer=top; opacity=1; blur=falselayer-rule = namespace=my-wallpaper; layer=background; opacity=1; blur=falseMatch keys:
| Key | Meaning |
|---|---|
namespace, ns |
Layer namespace with */? patterns |
output, monitor |
Output name with */? patterns |
layer |
background, bottom, top, overlay, or 0..3 |
Actions:
| Key | Meaning |
|---|---|
opacity=0.0..1.0 |
Layer surface opacity |
corner-radius=0..64 |
Layer surface rounding (rounding is an alias) |
blur=true/false |
Backdrop blur override |
Layer rules are evaluated in file order and later matches win. With no rule,
layer opacity is 1 and blur follows the global blur setting for the top and
overlay layers (background and bottom layers stay unblurred); rounded corners
default to 0. The panel’s surfaces use the namespaces tessa-bar,
tessa-overlay (launcher, calendar, power, clipboard, media, tray, audio,
screenshot, and extensions popovers), and tessa-notifications (toasts). A layer rule with
layer=top styles bar and toasts; popovers are on the overlay layer. Rules
re-apply on Super+R / config reload. corner-radius and blur require a
scenefx build (the default; disabled with -Dscenefx=false). tessa-ctl state
and tessa-ctl state --json expose
the effective layer values, and tessa-ctl config / config --json list the
parsed rules.
8.6 External layout helper
Section titled “8.6 External layout helper”layout = customlayout_helper = python3 ~/.config/tessa/layout.pylayout_helper_timeout = 100bind = SUPER+SHIFT+T set-layout customTessa starts one helper per output and sends requests asynchronously; the
compositor never blocks waiting for a reply. It advertises HELLO 2 and sends
tab-separated LAYOUT\t2, OUTPUT, AREA, OUTER_GAP, GAP, BORDER,
SCALE, TRANSFORM, COUNT, and WINDOW lines ending in END. A protocol-2
helper announces VERSION 2; after that each WINDOW carries id, app-id,
title, focused, and urgent fields. The first request uses the three-field
protocol-1 form. Replies contain an optional VERSION 2, tab-separated
RECT\tid\tx\ty\tw\th lines, and END. Coordinates are absolute logical
coordinates; rectangles include the border. A helper that ignores the HELLO
and version lines remains compatible.
The previous rectangles stay visible while a reply is pending; the first reply
falls back to the built-in layout until it arrives. Timeout, crash, malformed
reply, ID mismatch, or missing rectangles fall back to master for that output.
Helpers are retried lazily on the next request after one second (there is no
dedicated retry timer). stdout is reserved for the protocol;
diagnostics go to stderr. tessa-ctl dispatch urgent <id> on|off updates the
urgent flag sent to helpers. See
CUSTOMIZATION.md.
8.7 Workspace pins
Section titled “8.7 Workspace pins”workspace_pin = eDP-1 1workspace_pin = HDMI-A-1 3Keyboard, gesture, IPC, bar, and foreign-activation switches redirect to the
pinned output. Super+Shift+<n> and CMD move-workspace <n> move a window to
that output. A missing output ignores the pin. A later pin for the same
workspace wins.
Named workspaces
Section titled “Named workspaces”Workspaces 1–9 can have global names:
workspace-name = 2 Codeworkspace-name = 3 Chatbind = SUPER+2 workspace Codebind = SUPER+SHIFT+2 move-to-workspace Codewindow-rule = app-id=org.mozilla.firefox; workspace=ChatNames are single tokens (no spaces), cannot be numeric, and must be unique.
They are resolved after the whole config is parsed, so a workspace-name line
may appear after the bindings and rules that use it. The bar uses the name as
the workspace pill label (very long names are shortened); tessa-ctl workspace Code, CMD move-workspace Code, and window-rule workspace= all accept the
name. Numeric indices continue to work. Names are presentation and command
aliases: windows remain assigned to the numeric workspace internally, and
names apply to that number on every output.
Scratchpad
Section titled “Scratchpad”A scratchpad is a hidden floating overlay per output:
window-rule = app-id=org.example.notes; scratchpad=truebind = SUPER+grave toggle-scratchpadbind = SUPER+SHIFT+grave move-to-scratchpadSuper+grave shows or hides the focused output’s scratchpad windows; when
shown, the first one is focused. Super+Shift+grave moves the focused window
into the scratchpad, or returns a scratchpad window to normal tiling. A window
rule can use scratchpad=true or workspace=special. Changing workspaces hides
the scratchpad. Scratchpad windows are floating, do not count as workspace
occupancy, and remain hidden from the bar while closed. IPC equivalents are
CMD toggle-scratchpad and CMD move-to-scratchpad.
Special workspaces
Section titled “Special workspaces”Named special workspaces are declared once and act as hidden overlays on each output. They are separate from the scratchpad and can hold tiled or floating windows:
special-workspace = notesspecial-workspace = toolswindow-rule = app-id=org.example.notes; special=notes; size=520x360bind = SUPER+ALT+n special notesbind = SUPER+ALT+SHIFT+n move-to-special notesworkspace=special:tools is accepted as an equivalent rule form. Toggle with
tessa-ctl special tools; move the focused window with tessa-ctl move-to-special tools. While a special workspace is visible, normal workspace
windows on that output are hidden. Toggling it off restores the normal workspace
view and focuses the first visible normal window. Special clients do not count
toward workspace occupancy.
Window groups
Section titled “Window groups”Super+Shift+G groups the tiled windows on the active workspace into one
tabbed tile and shows the focused member. Super+Tab and Super+Shift+Tab
cycle the group’s tabs while a group member is focused; the bar lists only the
visible member. Pressing Super+Shift+G again on a grouped window ungroups the
whole group.
bind = SUPER+ALT+g group-addbind = SUPER+ALT+SHIFT+g group-removegroup-add adds the focused tiled window to an existing group on the same
workspace. group-remove detaches it and restores normal tiling. Moving a
member to another workspace or scratchpad removes it from the group. Groups are
runtime state and are not persisted across restart. IPC commands are
toggle-group, group-next, group-prev, group-add, and group-remove.
8.8 Event rules
Section titled “8.8 Event rules”Event rules run a command when a lifecycle event matches an expression:
event-rule = window-open; match=app ~ "^firefox$" && ws == "2"; run=notify-send "Firefox" "Opened on workspace 2"event-rule = window-open; match=title == "Mail" && id ~ "^[0-9]+$"; run=touch ~/.local/state/mail-openedevent-rule = workspace-change; match=ws == "3"; run=~/bin/on-workspace-3The event name is the first clause or event=<name>. match / expr is
optional; without it every event of that name runs the command. run /
command is required. Event rules use the same boolean/regex syntax as window
rules and additionally expose event, ws, and id; numeric values are
compared as strings (ws == "3").
Commands receive the normal hook environment (TESSA_EVENT, TESSA_WINDOW_ID,
TESSA_WORKSPACE, TESSA_TITLE, TESSA_OUTPUT, TESSA_ARG, and so on).
Matching rules run in config order before unconditional hook entries. Like
hooks, commands run through /bin/sh -c with the compositor’s privileges.
8.9 Long-lived effect helper
Section titled “8.9 Long-lived effect helper”effect_helper = python3 ~/.config/tessa/effects.pyeffect_helper_timeout = 200The effect helper is one long-lived process for all outputs. Tessa sends a
coalesced request whenever window metadata or focus changes. The current
protocol is v2; helpers that reply VERSION 1 keep using the v1 positional
format.
HELLO\t2EFFECT\t2WINDOW\t<id>\t<app-id>\t<title>\t<output>\t<ws>\t<focused>\t<urgent>\t<fullscreen>\t<floating>\t<visible>\t<opacity>\t<radius>\t<blur>\t<shadow>\t<border-width>\t<x>\t<y>\t<w>\t<h>\t[<tint>]ENDEvery field is tab-separated. tint is appended only after the
helper has negotiated v2. Reply:
VERSION\t2WINDOW\t<id>\topacity=0.9\tradius=16\tblur=on\tshadow=off\tborder-width=2\ttint=#ff8844aa\topen-ms=240\topen-curve=spring-softENDEvery v2 field is optional; omitted fields keep the rule value. tint is
#RRGGBBAA or none; animation fields accept -1–1000 ms and any curve
registered by bezier/spring or the built-ins. Omitting a window removes its
override. Tessa validates IDs, ranges, and curve names; a timeout, crash,
malformed reply, unknown curve, or bad value drops all overrides and returns to
rule visuals. The helper is retried on the next refresh after one second.
stdout is reserved for the protocol. See
EXTENDING.md. The
protocol never injects code into the compositor.
9. Monitors and Outputs
Section titled “9. Monitors and Outputs”Basics
Section titled “Basics”- Each output has its own wallpaper, bar, and nine workspaces
- One panel process per output
Super+1…9applies to the focused output unless pinnedSuper+Shift+H/J/K/Lcan move a window to an adjacent output
wlr-randr
Section titled “wlr-randr”wlr-randrwlr-randr --output HDMI-A-1 --mode 2560x1440@144wlr-randr --output HDMI-A-1 --pos 1920,0wlr-randr --output eDP-1 --scale 1.5wlr-randr --output eDP-1 --transform 90To place a left monitor and a center monitor explicitly, use logical layout
coordinates (take the connector names from wlr-randr):
wlr-randr --output DP-1 --pos 0,0wlr-randr --output HDMI-A-1 --pos 2560,0Settings are saved to outputs.conf and restored on the next start. Override
the path with output_config or TESSA_OUTPUT_CONFIG. The file is mode 0600.
Disabled-output state is not persisted.
Scaling
Section titled “Scaling”- Integer scales render the panel with integer buffers
- Fractional-scale-aware clients receive the exact scale, including runtime changes
- Unsupported clients fall back to the ceiling
- The panel uses an integer Cairo buffer scale, so fractional scales are downsampled
- XWayland scaling depends on
xwayland-satellite - Fractional X11 behavior has not been validated on hardware
External bars and launchers
Section titled “External bars and launchers”Tessa Panel stays the default (panel = true). For a layer-shell bar or launcher
from another desktop, disable the integrated panel and start the program from
startup:
panel = falsestartup = waybarpanel = false also turns off Tessa’s background, notifications, and built-in
launcher. To keep selected parts of Tessa Panel, leave panel = true and
choose the pieces instead:
# Keep Tessa's launcher and notifications; let Waybar own the bar.panel = truepanel_modules = launcher,notifications,toastsstartup = waybarpanel_modules accepts background (wallpaper), bar, launcher,
popovers (popover), notifications (notification), toasts (toast),
tray, media, and audio (volume); all/none are aliases. The key also
accepts panel-modules. The supervisor restarts the panel when the mask changes. If both
Tessa’s bar and an external bar reserve the same edge, both exclusive zones are
honored and the desktop becomes shorter; disable one of them. Run
tessa-ctl shell to see which roles Tessa Panel owns and which external
layer-shell clients are present. tests/external-shell.sh covers Waybar and
Wofi; tests/shell-coexistence.sh covers Quickshell and Noctalia when their
binaries are installed.
Waybar 0.15 uses layer-shell directly and can use the standard workspace and toplevel protocols:
{ "layer": "top", "position": "top", "height": 34, "modules-left": ["ext/workspaces"], "modules-center": ["wlr/taskbar"]}Tessa exposes one ext-workspace-v1 group per output with nine workspaces.
Names come from workspace-name (or 1…9); active and urgent state are
updated live, and activating a workspace switches that output. Only the
workspace activate capability is advertised because Tessa’s workspace set is
fixed. wlr-foreign-toplevel-management also follows windows when they move
between outputs.
Wofi and other layer-shell launchers need no compositor-specific setup. To replace the built-in launcher binding:
bind = SUPER+D spawn wofi --show drunUse layer=overlay for Wofi if fullscreen windows should not cover it. A
Quickshell PanelWindow uses layer-shell; Quickshell.WindowManager consumes
the ext-workspace groups, and ToplevelManager consumes foreign-toplevel.
focusable: true panels request ON_DEMAND keyboard focus and are focused on
click.
If Tessa Panel and an external bar use the same edge, both reserve space; disable
one. Their trays also compete for the single org.kde.StatusNotifierWatcher, so
only one tray is populated. Waybar minimize/maximize requests are no-ops because
Tessa has no such window state.
Output loss
Section titled “Output loss”Clients move to the first remaining output. Removing the last output terminates the compositor.
10. Appearance
Section titled “10. Appearance”Colors and wallpaper
Section titled “Colors and wallpaper”accent = #e2684fbg = #0a0f1abg_top = #162038bg_bottom = #070a12wallpaper_art = truebg_top and bg_bottom derive from bg when omitted. wallpaper_art = false
removes the ink/sun artwork.
A PNG can replace the generated wallpaper:
wallpaper_image = ~/Pictures/wallpaper.pngwallpaper_fit = coverwallpaper_image accepts an absolute path or a leading ~/. The panel loads
the PNG through Cairo and reloads it when the config reloads. wallpaper_fit
is cover (fill and crop, the default), contain (letterbox using bg), or
stretch. Without an image, the gradient and art above are used. Image load
errors are logged and fall back to the gradient.
A command can render a procedural wallpaper instead:
wallpaper_shader = ~/.config/tessa/sunset.shwallpaper_fit = stretchThe command must print a binary PPM header (P6, width, height, maxval 255)
followed by exactly one whitespace byte and width * height * 3 RGB bytes. It
runs on config load/reload; the frame is cached until the next reload. The
shader takes precedence over wallpaper_image. Because the command is invoked
synchronously, keep render time short.
Bar and fonts
Section titled “Bar and fonts”bar_height = 44bar_top_margin = 12bar_side_margin = 16panel_bg = #0e1320panel_fg = #eef2f8font = Interbar_layout = logo,workspaces,chips,clock,media,stats,volume,network,battery,tray,statusbar_font = Interlauncher_font = Interpopover_font = Intertoast_font = Interbar_fg = #eef2f8bar_bg = #0e1320bar_layout selects visible modules and orders the right-hand cluster from
right to left (the first listed module is closest to the right edge). Recognized
names are logo (mark), workspaces (workspace), chips
(title/windows), clock (date), battery, network, volume (audio),
stats (cpu/memory), media, tray, and status; unknown names are
ignored. Logo, workspace pills, and window chips stay anchored.
bar-font, launcher-font, popover-font, and toast-font fall back to
font. The *-fg and *-bg keys override foreground/background per surface.
panel_bg/panel_fg themselves are the base for the panel.background and
panel.foreground theme roles and the per-surface defaults; the bar’s actual
colors are bar-bg/bar-fg (or those theme roles). Bar background and
foreground output-rule keys still apply per output. The right cluster is
responsive: battery hides at width ≤900, tray at ≤980, network at ≤1000, volume
at ≤1080, stats at ≤1180, and media at ≤1250. Window chips show at most six;
an overflow +N badge counts only chips skipped for width before the six-chip
cap, so windows beyond the cap are not necessarily counted.
Semantic theme roles let one value drive the compositor and panel:
theme-role = accent = #e2684ftheme-role = window.border.active = #8fd3fftheme-role = window.border.inactive = #243040theme-role = window.background = #0a0f1atheme-role = panel.background = #0e1320theme-role = panel.foreground = #eef2f8theme-role = wallpaper.top = #162038theme-role = wallpaper.bottom = #070a12Supported roles are accent, window.border.active,
window.border.inactive, window.background, panel.background,
panel.foreground, bar.background, bar.foreground, launcher.background,
launcher.foreground, popover.background, popover.foreground,
toast.background, toast.foreground, wallpaper.top, and
wallpaper.bottom. Roles are applied after direct keys, so a role wins over
accent, border_active, border_inactive, bg, and the corresponding
panel keys. panel.background/panel.foreground also fill any per-surface
background/foreground that was not set explicitly. A matching output-rule
theme still overrides the global role for that output.
Borders and shadows
Section titled “Borders and shadows”border_width = 2border_active = #e2684fborder_inactive = #293347shadows = trueAn empty border_active uses accent. Fullscreen windows have no border or
rounded corners.
Animation
Section titled “Animation”animations = trueanimation_ms = 160animation_open_ms = 160animation_workspace_ms = 220animation_close_ms = 180animation_curve = ease-out-cubicanimation_open_curve = ease-out-quintanimation_workspace_curve = ease-in-out-cubicanimation_close_curve = smoothstepbezier = snap, 0.16, 1.0, 0.3, 1.0spring = bounce, 0.35, 1.4animation_open_x = 0animation_open_y = 18animation_workspace_offset = 54animation_close_scale = 0.92animation_close_fade = truereduce_motion = falsereduce_motion_animations = fadereduce_motion_fade_ms = 120- Built-in curves:
linear,ease-out-cubic,ease-in-out-cubic,ease-out-quint,ease-in-out-quint,smoothstep,overshoot,spring,spring-soft, andspring-bouncy bezier = name, x1, y1, x2, y2defines a custom cubic-bezier curvespring = name, damping, frequencydefines a normalized spring curve.dampingis 0.05–2 andfrequencyis 0.25–4 oscillations over the animation; lower damping overshoots more, and the curve always ends at 1- Open: configurable x/y offset (default 0/18 px) with the open curve
- Workspace: configurable ±offset (default 54 px) with the workspace curve
- Close: compositor-initiated closes keep the surface buffer, scale to
animation_close_scale, and optionally fade with the close curve - Per-kind durations override
animation_ms;-1inherits it - Client-initiated closes are immediate because the buffer is gone at unmap
reduce_motion = truedisables open/workspace movementreduce_motion_animations = fadekeeps only a close fade, with no scale or slide, forreduce_motion_fade_msreduce_motion_animations = none(default) disables close effects too- Durations are 0–1000 ms;
0disables that animation
Window rules can override motion per window:
window-rule = app-id=kitty; animation=falsewindow-rule = title=Picture-in-Picture; animation-ms=260; animation-curve=spring-soft; animation-close-ms=180; animation-close-curve=bounce| Rule key | Meaning |
|---|---|
animation=true/false |
Enable or disable all motion for the window |
animation-ms=N |
Base duration override (0–1000) |
animation-open-ms, animation-workspace-ms, animation-close-ms |
Per-kind duration override |
animation-curve=NAME |
Open/workspace curve override |
animation-open-curve, animation-workspace-curve, animation-close-curve |
Per-kind curve override |
animation-curve applies to open and workspace transitions; close uses
animation-close-curve (or the global close curve). These overrides are
re-evaluated with the other decorative window-rule fields. The global
animations = false and reduce_motion = true switches still take precedence.
Lifecycle hooks: animation-start, animation-done,
close-animation-start, and close-animation-done.
scenefx corners and blur
Section titled “scenefx corners and blur”Only in scenefx builds (the default):
corner_radius = 10blur = trueblur_radius = 6blur_passes = 3corner_radius applies to client content, border, and shadow. blur applies
to translucent clients and top/overlay/popup layers; opaque windows show no
blur. This build requires GLES2 and does not support pixman.
11. Input
Section titled “11. Input”Keyboard
Section titled “Keyboard”kb_layout = us,dekb_variant =kb_options = grp:alt_shift_togglekb_model =kb_rules =repeat_rate = 25repeat_delay = 600Super+R rebuilds keymaps. Empty kb_* values use the environment defaults.
Cursor theme
Section titled “Cursor theme”cursor_theme = Adwaitacursor_size = 24The theme is an Xcursor name resolved through the usual XCURSOR_THEME,
XCURSOR_PATH, $XDG_DATA_HOME/icons, and /usr/share/icons search path.
Leave cursor_theme empty to use the system default. cursor_size is clamped
to 16–96 logical pixels. Config reload recreates the cursor manager immediately;
if the new theme cannot be loaded, Tessa logs a warning and keeps the previous
cursor instead of leaving the pointer invisible.
Per-device input policy
Section titled “Per-device input policy”Tessa applies libinput settings with repeatable input (or input-rule)
lines. Rules are matched against each physical libinput device, merged in file
order, and the last value for each field wins:
input = type=touchpad; tap=true; tap-drag=true; natural-scroll=trueinput = type=touchpad; name=*Precision*; accel-profile=flat; accel-speed=-0.3input = type=mouse; name=*Mouse*; natural-scroll=false; scroll-method=button; scroll-button=275input = type=touchpad; dwt=false; click-method=clickfingerinput = type=pointer; left-handed=trueMatch clauses:
| Key | Meaning |
|---|---|
type= / device-type= / kind= |
any, keyboard, pointer, touchpad, mouse, touch, tablet, tablet-pad, or switch; default any |
name= / device= |
libinput device name; * and ? wildcards; omitted matches every device |
Settings:
| Key | Applies to | Values |
|---|---|---|
tap= |
touchpad | true/false |
tap-drag= |
touchpad | true/false |
tap-drag-lock= |
touchpad | true/false |
natural-scroll= |
pointer/touchpad | true/false |
left-handed= |
pointer | true/false |
middle-emulation= |
pointer | true/false |
dwt= / disable-while-typing= |
touchpad | true/false |
accel-profile= |
pointer | adaptive or flat |
accel-speed= |
pointer | -1.0 … 1.0 |
scroll-method= |
pointer/touchpad | none, two-finger, edge, button, or circular |
scroll-button= |
pointer | evdev button code, 1–767 |
click-method= |
touchpad | none, button-areas, or clickfinger |
tap-button-map= |
touchpad | lrm or lmr |
clickfinger-button-map= |
touchpad | lrm or lmr |
The type classification uses libinput: a pointer with tap-finger support is a touchpad, otherwise it is a mouse. Touchpad-specific clauses have no effect on a mouse, and vice versa. Settings are applied when a device is added and again on every config reload. Tessa snapshots each device’s original libinput state, so removing a rule restores that state instead of leaving the old override behind. Only libinput devices are configurable; virtual devices and devices owned by another backend are left alone.
Headless tests can validate parsing and JSON output but cannot exercise tap or
scroll behavior. Verify touchpad and mouse tuning on real hardware with
libinput list-devices for the exact device name.
Input modes
Section titled “Input modes”mode = resizebind = h resize-width -30bind = l resize-width 30bind = Escape enter-mode defaultmode = defaultbind = SUPER+SHIFT+r enter-mode resizeMode bindings match without modifiers and take priority over global bindings.
Set mode_timeout = 300 inside a mode section to return to default after
300 ms without a matching key.
A leader chord enters the leader mode:
leader = SUPER+SPACEleader_timeout = 1000
mode = leaderbind = f spawn firefoxbind = t spawn kittybind = Escape enter-mode defaultThe leader is recognized only in the default mode. Pressing it again, binding
Escape, or waiting leader_timeout exits. A matching leader-mode binding
runs one action and returns to default unless the action enters another mode.
leader_timeout = 0 keeps leader mode active until an explicit exit.
Gestures
Section titled “Gestures”gestures = truegesture_fingers = 3gesture_workspace_threshold = 400gesture_launcher_threshold = 300By default, a horizontal three-finger swipe changes workspace and an upward swipe opens the launcher. Other finger counts and pinch/hold gestures are forwarded to the focused client.
Touch and tablet
Section titled “Touch and tablet”touch = truetablet = truepointer_constraints = trueTouch includes panel UI. tablet-v2 supports pressure, tilt, rotation, tool buttons, rings, and strips. pointer-constraints-v1 activates locked/confined pointers based on focus.
IME / XIM
Section titled “IME / XIM”Wayland IME uses text-input-v3 and input-method-v2; Fcitx5 + Mozc is
verified in the launcher. For X11, set XMODIFIERS and optionally start the
XIM server:
xim = truexim_server = fcitxxim_start = fcitx5 -dxim_start runs after the X display socket appears.
Fcitx5 keeps a keyboard grab while it runs. Tessa passes the input method’s
virtual-keyboard output back to the focused client, including clients that do
not use text-input-v3 (for example Firefox), and does not re-enter keyboard
focus when that virtual keyboard is created or destroyed.
12. Shell Features
Section titled “12. Shell Features”Launcher
Section titled “Launcher”Super+D, theTESSAmark, ortessa-ctl launcher- Scans normal
.desktopentries - Type to filter;
Up/Down,Enter,Esc, or click - IME input works
launcher_entry = Name | commandadds custom entries- Config reload updates launcher entries
Clipboard history
Section titled “Clipboard history”Super+Vor the bar- Up to eight text entries, 16 KiB each, memory only
- Select to put an entry back on the clipboard
wlr-data-control-v1 is exposed to the session, so any trusted client can read
the selection without keyboard focus. Treat session applications as trusted.
Screenshot
Section titled “Screenshot”Super+Shift+S- Drag for a region, click or
Enterfor the full output Escor right-click cancels- Captures the focused output with
wlr-screencopy - Saves PNGs to
screenshot_dir
Calendar and power
Section titled “Calendar and power”- Click the clock for the month calendar
Left/Right, arrows, or wheel change months;Homereturns to today- Right-click
TESSAfor the power menu Up/Down,Enter,Esc, or click- Lock/Suspend/Reboot/Power Off commands are configurable
Notifications
Section titled “Notifications”The panel owns org.freedesktop.Notifications:
- Replacement, close, expiry
- Action buttons and the
defaultaction image-data,image-path,icon-name- Pango markup
- Routing to the focused output
tessa-ctl notify-action <id> <key>
Not supported: rich HTML beyond Pango, notification sounds, per-notification output pinning.
- Workspace pills
- Window chips (click focus, right-click close)
- CPU/RAM
- Clock/calendar
- Battery
- Default-route network state
- MPRIS media (first player, no album art)
- StatusNotifier tray (no DBusMenu)
- Volume (
wpctl/pactl/amixer) - Up to eight custom status entries
The panel is supervised by the compositor. Tessa watches the panel process with
a Linux pidfd and restarts it with bounded exponential backoff after a crash
(1 s, doubling to 8 s; after a stable ≥10 s run the failure counter resets and
the first retry can use 500 ms). The notification-owner panel keeps that role across
restarts. Deliberate shutdown and output removal do not restart it. This is
covered by tests/panel-recovery.sh.
Volume
Section titled “Volume”volume_status = wpctl get-volume @DEFAULT_AUDIO_SINK@volume_up = wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+volume_down = wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-volume_mute = wpctl set-mute @DEFAULT_AUDIO_SINK@ toggleEmpty commands auto-detect wpctl, pactl, then amixer.
Lock and idle
Section titled “Lock and idle”ext-session-lock-v1- Default
Super+Escaperunsswaylock - While locked, desktop trees are hidden and virtual-keyboard input is ignored
- If the lock client dies, Tessa terminates the session
- Idle timing is external; use
swayidle idle-inhibit-v1,ext-idle-notify-v1, andwlr-output-power-management-v1are exposedwlopmcan power outputs on/off
swayidle -w \ timeout 300 'swaylock -f' \ timeout 600 'wlopm --off "*"' \ resume 'wlopm --on "*"'Screen sharing
Section titled “Screen sharing”Install tessa.portal, xdg-desktop-portal-wlr, and PipeWire. Screenshot and
ScreenCast requests route to the wlr backend when
XDG_CURRENT_DESKTOP=Tessa.
13. Extension API
Section titled “13. Extension API”13.1 Socket and commands
Section titled “13.1 Socket and commands”The socket is $TESSA_IPC or $XDG_RUNTIME_DIR/tessa.sock, mode 0600. A
connection receives nothing until it subscribes.
HELLO <version> [<extension-token>]CMD subscribe state|events|allCMD stateCMD state-jsonCMD configCMD config-jsonCMD extensionsCMD extensions-jsonCMD extension-enable <id> on|offCMD config-compare <path>CMD perfCMD perf-jsonCMD perf-resetCMD renderingCMD rendering-jsonCMD workspace <1..9|name> [output]CMD move-workspace <1..9|name>CMD toggle-scratchpadCMD move-to-scratchpadCMD special <name>CMD move-to-special <name>CMD toggle-groupCMD group-nextCMD group-prevCMD group-addCMD group-removeCMD focus <id>CMD close <id>CMD spawn <shell command>CMD launcher [query]CMD panel <calendar|power|clipboard|screenshot|media|tray|audio|extensions> [output]CMD layout [name] [output]CMD column-focus <left|right|first|last>CMD column-move <left|right|first|last>CMD column-width <set|cycle|cycle-back|maximize|center> [value]CMD column-consumeCMD column-expelCMD window-move <up|down>CMD swap <id> <id>CMD reorder <id> <before|after> <target-id>CMD resize <dx> <dy>CMD float <id>CMD status <id> <text> (or tab-separated for a click command)CMD reloadCMD notification-action <id> <key>CMD quitHELLO <version> [<token>] is optional and may be sent first. Tessa replies
VERSION\t1 and a tab-separated CAPABILITIES line, then keeps the connection
open for commands. The capability list includes extensions, effects2, and
shell-modules. Unknown/newer versions negotiate down to 1; a non-numeric
version gets ERR\tinvalid-version. A supervised extension appends its
TESSA_EXTENSION_TOKEN; Tessa replies with EXTENSION\t<id>\t<permissions>
after the capability line. An unknown token gets ERR\tunauthorized; a
malformed HELLO that contains a token is fail-closed and keeps no
permissions. A token handshake clears subscriptions made before authentication,
so re-subscribe afterwards. Scoped clients get
ERR\tunknown-command\t<command> for unknown commands.
Clients that send commands directly remain compatible (line protocol 0) and
keep full access. tessa-ctl hello prints the negotiated version and
capability list.
STATE/CONFIG snapshots end with END. OUT is
name workspace focused layout. WIN is tab-separated:
id, workspace, focused, floating, fullscreen, app_id, title, output, x, y, w, h, scratchpad, visible, group-id, group-active, on-screen, column, tile.
The trailing fields describe scrolling state and are also present in other
layouts: on-screen is 1 when the window is currently placed on screen (inside
the scrolling viewport, or the visible monocle/stacking card), column is its
scrolling column id (0 when it has none), and tile is its 0-based position
inside that column.
PANEL is output, pid, supervised, notify-owner for the built-in panel
supervisor. The same snapshot also contains:
ACTIVE <workspace>WORKSPACE <n> <name>MODE <name>SPECIAL <output> <name>OCC <workspace> <count>FOCUSED <window-id>STATUS<TAB><id><TAB><text><TAB><click-command>LAYER<TAB><namespace><TAB><output><TAB><layer><TAB><opacity><TAB><radius><TAB><blur>LAYER reports mapped layer-shell surfaces (tessa-background, tessa-bar,
tessa-overlay, tessa-notifications, or external namespaces). layer is
0=background, 1=bottom, 2=top, 3=overlay. STATUS and LAYER are tab-separated
because their text fields may contain spaces. Raw line clients can set a click
command with CMD<TAB>status<TAB><id><TAB><text><TAB><command>;
tessa-ctl status ... --exec <command> uses that form.
Scrolling outputs additionally emit one VIEW line and one COL line per
column. Both use 0-based column indexes and pixel coordinates:
VIEW <output> <workspace> <offset> <content-width> <viewport-width> <column-count> <focus-index>COL <output> <workspace> <index> <x> <width> <focused> <maximized> <tabbed> <window-count>offset is the horizontal scroll position of the column strip, x is the
column’s screen position (already offset), and tabbed marks a column that
holds a window group.
EXT lines describe supervised extensions:
EXT <id> <state> <api> <pid> <ready> <restarts> <enabled> <permissions>.
States are disabled, blocked, stopped, starting, running, ready,
and failed; starting is reserved and not currently emitted. A ready flag
means the process authenticated with its token; a running process that has not
done so is still running.
tessa-ctl wraps the common operations:
tessa-ctl statetessa-ctl hellotessa-ctl extensions [--json]tessa-ctl extension-enable weather offtessa-ctl configtessa-ctl config-check [file]tessa-ctl config-diff filetessa-ctl config-edit [file]tessa-ctl perf [--json] [--reset]tessa-ctl perf --watch=2tessa-ctl rendering [--json]tessa-ctl events [topic]tessa-ctl workspace 3 [output]tessa-ctl special notestessa-ctl move-to-special notestessa-ctl spawn foot -T notestessa-ctl launcher firetessa-ctl panel calendartessa-ctl panel extensionstessa-ctl layout gridtessa-ctl column-focus righttessa-ctl column-move lefttessa-ctl column-width cycletessa-ctl consumetessa-ctl expeltessa-ctl window-move downtessa-ctl swap 3 7tessa-ctl reorder 7 before 3tessa-ctl mode resizetessa-ctl resize 40 30tessa-ctl dispatch reloadtessa-ctl quitconfig-check <file> validates a candidate without applying it. config-diff
loads the candidate transactionally, reports diagnostics, and prints lines from
the compositor’s effective configuration that would change; panel-only settings
and comments do not appear in that diff. config-edit copies the running config
to a temporary file, opens $VISUAL/$EDITOR, shows the check/diff, and writes
it back and reloads only after confirmation. If only panel settings or comments
changed, it still offers to save and reload.
perf reports render-commit timing from the compositor (avg_us, p50_us,
p95_us, max_us, over-budget frames, and whether scenefx is active). The
budget comes from frame_time_budget; --reset clears the sample window.
--json includes an outputs array, and the text form appends one OUTPUT
line per output. Per-output counters include presented, zero_copy, vsync,
missed, scanout, tearing, and tearing_fail. perf --watch[=SECS]
repeats the snapshot every second (or the requested interval). --reset
clears both the global and per-output windows. tests/performance.sh uses this
for the headless animation and full-effect benchmarks.
rendering [--json] reports static and runtime rendering capabilities:
direct-scanout mode, tearing mode and whether wp-tearing-control-v1 is
advertised, whether linux-drm-syncobj-v1 is active, renderer/backend timeline
support, output color-transform support, scenefx, DRM fds, and per-output
scanout/zero-copy/tearing counters. Use zero_copy and scanout_frames to
confirm that direct scanout actually happened on real DRM hardware; headless
outputs exercise the state machine but cannot scan out. HDR color management is
not advertised yet; color_management 0 is expected, and the reason and future
requirements are documented in RENDERING.md.
To disable a feature for troubleshooting:
direct_scanout = falsetearing = offexplicit_sync = false # requires a compositor restart13.2 JSON and JSON-RPC
Section titled “13.2 JSON and JSON-RPC”state --json: schema 1 plustessa,line_protocol,extension_api,layout_api,effect_api, outputs, occupancy, focus, statuses, windows, layers, and anextensionsarray. Window objects includetint, ananimationoverride object,tearing,on_screen,column, andtile(the textWINline does not include these).config --json: schema 1 plus the same API versions, effective config, layouts and cycle, workspace tuning, rules, output rules, hooks, event rules, profiles, theme roles,panel_modules,animation_view_ms/_curve, extension manifests, and diagnostics.- Reject unknown schema versions
The same socket accepts JSON-RPC 2.0 requests as one-line JSON objects:
tessa-ctl rpc '{"jsonrpc":"2.0","id":1,"method":"hello"}'tessa-ctl rpc '{"jsonrpc":"2.0","id":7,"method":"state"}'tessa-ctl rpc '{"jsonrpc":"2.0","id":"cfg","method":"config"}'tessa-ctl rpc '{"jsonrpc":"2.0","id":"sub-1","method":"subscribe"}'Methods: hello/capabilities, ping, state, config, subscribe, and
unsubscribe. Every response echoes the request id. hello reports
protocol: "tessa-jsonrpc", version: 1, line_protocol: 1, tessa,
extension_api, layout_api, effect_api, and the capabilities state,
config, events, subscriptions, request-ids, extensions, effects2.
Errors use standard JSON-RPC codes (-32700 parse error, -32601 unknown
method).
After subscribe, lifecycle events arrive as notifications:
{"jsonrpc":"2.0","method":"event","params":{"name":"workspace-change","ws":2,"previous-ws":1,"output":"eDP-1","subscription":"sub-1"}}The event plane remains line-oriented for existing clients; JSON-RPC subscribers receive JSON notifications with the same fields.
13.3 Events
Section titled “13.3 Events”Events are EVENT\t<topic>\tkey=value..., delivered only to subscribers and
best-effort.
| Topic | When | Fields |
|---|---|---|
startup |
Startup complete | — |
shutdown |
Shutdown | — |
window-open |
Toplevel mapped | id, ws, app, title, output |
window-close |
Mapped toplevel destroyed | id, ws, app, title, output |
window-focus |
Focus changed/cleared | id, previous, ws, app, title, output |
workspace-change |
Output workspace changed | ws, previous-ws, output |
special-change |
Special workspace shown/hidden or window moved | ws, output, arg (name; omitted when restoring) |
window-moved |
Window moved workspace | id, ws, previous-ws, app, title, output |
layout-change |
Layout changed | ws, output, arg |
mode-change |
Input mode changed | arg |
config-reload |
Successful reload | — |
launcher |
Launcher requested | arg, output |
panel |
Panel popover requested | arg, output |
notification-action |
Notification action | id, action |
output-added |
Output initialized | output, arg |
output-removed |
Output removed | output, arg |
animation-start |
Open/workspace slide starts | id, ws, output, arg (open/workspace) |
animation-done |
Open/workspace slide finishes | id, ws, output, arg |
close-animation-start |
Close snapshot starts | id, ws, output, arg (close) |
close-animation-done |
Close snapshot finishes | id, ws, output, arg |
extension-start |
Supervised extension process started | arg (extension id) |
extension-ready |
Extension authenticated with its token | arg |
extension-failed |
Extension exited unexpectedly | arg |
extension-stop |
Extension disabled or stopped deliberately | arg |
13.4 Hooks
Section titled “13.4 Hooks”hook = window-open notify-send "Window opened" "$TESSA_TITLE"hook = animation-done echo "$TESSA_ARG $TESSA_WINDOW_ID" >> ~/.cache/tessa-anim.logHooks run asynchronously through /bin/sh with the session user’s privileges.
Multiple hooks per event are allowed but not ordered.
Environment: TESSA_EVENT, TESSA_WINDOW_ID, TESSA_PREVIOUS_ID,
TESSA_WORKSPACE, TESSA_PREVIOUS_WORKSPACE, TESSA_APP_ID, TESSA_TITLE,
TESSA_OUTPUT, TESSA_ARG, TESSA_FOCUSED_ID, TESSA_ACTIVE_WORKSPACE, and
TESSA_FOCUSED_OUTPUT.
13.5 Status entries
Section titled “13.5 Status entries”tessa-ctl status weather "Tokyo 18C"tessa-ctl status weather "Tokyo 18C" --exec "weather-app"tessa-ctl status weather --clearAn id is stable; updates replace in place. Max eight entries, up to 159 bytes,
shared across outputs. Clicking sends CMD spawn <command>.
13.6 Supervised extensions
Section titled “13.6 Supervised extensions”An extension is a config-declared, supervised process. It speaks the same control socket as other clients:
extension = id=weather; command=weatherd; api=1; permissions=read,statusextension = id=theme; command=tessa-theme; api=1; requires=weather; after=weatherapi must be supported by the compositor (currently 1). permissions is a
comma-separated subset of read, events, status, window, workspace,
layout, spawn, panel, control, extensions; use all or none for the
extremes. The default is read,status. requires and after are dependency
ids; unknown ids, duplicates, self-dependencies, and cycles are config errors.
requires waits until the dependency is ready, while after only sets the
startup order. The supervisor uses pidfd exit detection and exponential restart
backoff (1 s, 2 s, 4 s, then 8 s), and passes TESSA_EXTENSION_ID,
TESSA_EXTENSION_TOKEN, TESSA_EXTENSION_API, and
TESSA_EXTENSION_PERMISSIONS. extension_ready_timeout (default 10000 ms,
0 disables) kills a process that never authenticates. Tokens rotate on every
start; stopping, disabling, crashing, or reloading an extension revokes its
authenticated connections.
The process identifies itself before sending commands:
HELLO 1 $TESSA_EXTENSION_TOKENVERSION 1CAPABILITIES ...EXTENSION weather read,statusA missing or wrong permission gets ERR forbidden <command>. Unknown commands
from a scoped extension return ERR unknown-command <command>. A malformed
HELLO that contains a token is fail-closed and does not regain full access.
This is a cooperative scope check, not a sandbox: the extension runs as the same
user and can read files, the token, or the socket directly. Clients that do not
send a token keep the legacy full-access behavior, including tessa-panel and
tessa-ctl.
CMD extensions and CMD extensions-json list the runtime state. EXT state
lines use
EXT <id> <state> <api> <pid> <ready> <restarts> <enabled> <permissions>;
states are disabled, blocked, stopped, starting, running, ready,
and failed (starting is reserved and not currently emitted). The JSON
extensions array also includes last_error, failures, and
next_restart_ms. CMD extension-enable <id> on|off changes the current session
only. The
panel’s Extensions popover is opened with tessa-ctl panel extensions and
toggles the selected entry with Enter. The full compatibility contract is in
EXTENSION-API.md.
13.7 Launcher entries
Section titled “13.7 Launcher entries”launcher_entry = Notes | foot -e nvim ~/notes.mdlauncher_entry = Lock screen | swaylockThe part before | is the display name; the rest is a /bin/sh command.
14. Example Configs
Section titled “14. Example Configs”Minimal
Section titled “Minimal”terminal = footpanel = truexwayland = trueanimations = trueTwo monitors and pinned workspaces
Section titled “Two monitors and pinned workspaces”terminal = footgaps = 8outer_gaps = 12layout = master
workspace_pin = eDP-1 1workspace_pin = HDMI-A-1 3
output-rule = output=eDP-1; layout=master; master-ratio=0.6output-rule = output=HDMI-A-1; layout=grid; gaps=15; outer-gaps=20output-rule = output=HDMI-A-1; ws=3; layout=columns; gaps=6
window-rule = app-id=pavucontrol; float=true; size=800x600window-rule = title=Picture-in-Picture; float=true; position=1300,80Appearance-focused
Section titled “Appearance-focused”accent = #e2684fbg_top = #162038bg_bottom = #070a12wallpaper_art = true
bar_height = 46bar_top_margin = 12bar_side_margin = 18panel_bg = #0d1422panel_fg = #f1f4fafont = JetBrains Mono
border_width = 2border_active = #e2684fborder_inactive = #293347shadows = trueanimation_ms = 220corner_radius = 14blur = trueblur_radius = 8blur_passes = 3
hook = animation-done echo "$TESSA_ARG $TESSA_WINDOW_ID" >> ~/.cache/tessa-anim.loghook = close-animation-done echo "closed $TESSA_WINDOW_ID" >> ~/.cache/tessa-anim.logAutomation
Section titled “Automation”startup = fcitx5 -dhook = workspace-change ~/.config/tessa/on-workspace.shhook = window-open [ "$TESSA_APP_ID" = firefox ] && echo "$TESSA_TITLE"
bind = SUPER+SHIFT+r enter-mode resizemode = resizebind = h resize-width -30bind = l resize-width 30bind = Escape enter-mode defaultmode = default15. Troubleshooting
Section titled “15. Troubleshooting”DRM will not start / screen stays black
Section titled “DRM will not start / screen stays black”- Run from a TTY as your normal user, not root
- Confirm the logind/seat session is active
- Confirm access to input devices
- Over SSH, use headless or nested Wayland
- Try
WLR_RENDERER=pixmanon a system without working GL
Configuration does not apply
Section titled “Configuration does not apply”tessa-ctl config-checktessa-ctl configA reload with errors is rejected. config-check checks the last loaded
configuration, so reload after editing.
Arch build fails: wrong Zig or wlroots
Section titled “Arch build fails: wrong Zig or wlroots”Tessa requires Zig 0.17 and wlroots 0.20. Both are in the official Arch
repositories as zig and wlroots0.20; build with the PKGBUILD from section
2.2.
zig versionpkg-config --modversion wlroots-0.20The first command must print 0.17.x; the second must print 0.20.x. If
pkg-config finds /usr/local, remove that prefix from the build environment or
point PKG_CONFIG_PATH at the official installation.
Arch Debug build fails with R_X86_64_PC64
Section titled “Arch Debug build fails with R_X86_64_PC64”fatal linker error: unhandled relocation type R_X86_64_PC64note: in .../Scrt1.o:.sframeOld Zig self-hosted linking could not handle the .sframe relocations emitted
by glibc 2.43 and GCC 16. The current build.zig forces LLVM/LLD for all three
programs, so this tree is not affected. On an older clone, use a release build
(-Doptimize=ReleaseSafe), which already selects LLVM/LLD, or apply the
use_llvm = true and use_lld = true settings to each executable.
Panel missing
Section titled “Panel missing”- Is
tessa-panelinPATH? - Is
panel = true? - Is
TESSA_NO_PANELset? - Check
TESSA_PANEL_BIN - Use
TESSA_PANEL_DEBUG=1
Manual start:
TESSA_PANEL_OUTPUT=HEADLESS-1 tessa-panelCursor is missing
Section titled “Cursor is missing”Tessa 0.1.0 did not set a default cursor before a client requested a shape,
so the pointer could stay invisible over the wallpaper or bar. 0.2.0 sets the
default cursor at startup. 0.2.2 also restores it when the pointer enters a
layer surface such as the panel or wallpaper after a client has cleared its
cursor. No wlr_cursor_warp_closest or manual output-init refresh is needed;
wlroots applies the cursor image when an output is added.
Install an Xcursor theme on minimal systems:
sudo apt install xcursor-themes# Optional modern theme:sudo apt install adwaita-icon-themeThe tessa package depends on xcursor-themes. Some legacy themes only provide
left_ptr; Tessa 0.2.3 checks default, left_ptr, and arrow and logs which
name it selected. If a configured cursor_theme cannot be loaded, Tessa logs a
warning and falls back to the system cursor; check
~/.local/state/tessa/tessa.log or the compositor output.
On a virtual machine, a broken DRM cursor plane can hide a correctly loaded
cursor. Tessa 0.2.3 detects vmwgfx, vboxvideo, bochs, qxl, cirrus, and
virtio_gpu and sets WLR_NO_HARDWARE_CURSORS=1 automatically, including when
launched directly. tessa-session also enables it for other detected VMs. To
force it manually:
WLR_NO_HARDWARE_CURSORS=1 tessaTerminal does not start
Section titled “Terminal does not start”The default terminal is kitty, but older packages only suggested it and did
not install it. Install a terminal:
sudo apt install kitty# or, especially on a VM without working 3D acceleration:sudo apt install footTessa 0.2.3 resolves a missing default terminal through foot, kitty,
alacritty, wezterm, then xterm, and logs which command it uses. An
explicitly configured terminal = ... is never replaced. Kitty needs a working
EGL/GL stack; on virtual machines without GPU acceleration use foot or set
terminal = foot. Check the terminal’s stderr and Tessa’s log before assuming
the compositor failed to spawn it.
Kitty appears and then exits
Section titled “Kitty appears and then exits”A window that maps and immediately disappears is normally Kitty (or Kitty’s
shell) exiting, not a compositor unmap. Check it from a foot terminal inside
the same session:
tessa-ctl spawn footWAYLAND_DEBUG=1 kitty- No
new toplevelline in Tessa’s log: the command never connected. Checkterminal,PATH, and the launcher entry. new toplevelfollowed by the window closing: Kitty exited. Look for EGL, GBM, DBus, or shell-startup errors in Kitty’s output. On a VM without working 3D, usefoot; forcing software rendering withLIBGL_ALWAYS_SOFTWARE=1works only when Mesa’s software EGL stack is installed.importing the supplied dmabufs failed/ broken pipe on vmwgfx: this is a vmwgfx linux-dmabuf failure, not a Kitty bug. Tessa 0.2.3 disableslinux-dmabuf-v1on vmwgfx so clients usewl_shm. SetTESSA_ALLOW_LINUX_DMABUF=1only for debugging if you need to re-enable it.- A
Terminal=truedesktop entry closes after its command finishes: this is expected. Tessa runs it asterminal sh -c '<command>', so a short-lived command produces a short-lived terminal.kitty.desktopitself isTerminal=false.
X11 apps do not start
Section titled “X11 apps do not start”- Is
xwayland-satelliteinPATHor set viaTESSA_XWAYLAND_BIN? - Is
xwayland = true? - Is
TESSA_NO_XWAYLANDset? - Does the log contain
xwayland: '...' not found; X11 apps disabled? - Does the log contain
xwayland: connected on :N? Tessa 0.2.3 setsDISPLAYonly after this point.
Tessa does not invent a DISPLAY value when the satellite cannot start, so an
X11 app should fail clearly rather than connect to a nonexistent server.
XMODIFIERS is also exported only with a ready X display and xim = true.
When XWayland is enabled, startup hooks and TESSA_STARTUP run after that
point, so commands such as fcitx5 -d see the X environment. Without a
satellite they run immediately.
IME does not work
Section titled “IME does not work”- Is an
input-method-v2IME running? - Is
XMODIFIERS=@im=fcitxexported to X11 clients? - Is
xim_startrunning after the display appears? - Ensure Fcitx5 is not started twice.
- With XWayland disabled there is no X display, so
XMODIFIERSis intentionally not exported. - If keys stop working in Firefox or another client after Fcitx5 starts, check
the log for repeated
virtual keyboard added/input method grabbed keyboardlines. Current builds avoid that text-input focus loop; restart the session after updating.
External bar or launcher does not work
Section titled “External bar or launcher does not work”- Check that the surface exists:
tessa-ctl state | grep LAYERshould show its namespace (for examplewaybarorwofi). - If both Tessa Panel and an external bar are anchored to the same edge, both
reserve space. Set
panel = falsewhen the external bar should be the only bar. - On Waybar 0.14+ use
ext/workspaces; the oldwlr/workspacesmodule was removed.wlr/taskbarneedswlr-foreign-toplevel-management, which Tessa provides. - Wofi uses layer-shell by default.
--normal-windowis supported but keyboard focus depends on the compositor’s normal toplevel focus; layer mode is recommended. - Layer surfaces that request
EXCLUSIVEkeyboard focus are focused on map;ON_DEMANDsurfaces are focused when clicked. A client that never requests keyboard interactivity cannot receive typed input. - Tessa Panel, Waybar, and Quickshell all try to own
org.kde.StatusNotifierWatcher; only one tray will be populated.
Screen sharing fails
Section titled “Screen sharing fails”- Install
xdg-desktop-portal,xdg-desktop-portal-wlr, and PipeWire - Install
tessa.portal - Check xdpw
chooser_typeandoutput_name - Set
XDG_CURRENT_DESKTOP=Tessa
Corners/blur missing
Section titled “Corners/blur missing”- Use the default scenefx build (do not pass
-Dscenefx=false) - Confirm
scenefx 1intessa-ctl config - Make the scenefx library visible at runtime
- Do not use
WLR_RENDERER=pixman - Blur is invisible on opaque windows
Output settings reset
Section titled “Output settings reset”- Check
TESSA_OUTPUT_CONFIG - Check
outputs.confpermissions/content - Confirm output names are stable
- Disabled-output state is not persisted
Headless inspection
Section titled “Headless inspection”./scripts/run-headless.sh# another shellexport PATH="$PWD/zig-out/bin:$PATH"export XDG_RUNTIME_DIR=/tmp/tessa-headlesstessa-ctl state./scripts/screenshot.sh /tmp/tessa.pngTESSA_DEBUG=1 ./zig-out/bin/tessaTESSA_PANEL_DEBUG=1 tessa-panelTESSA_CTL_DEBUG=1 tessa-ctl state16. Known Limitations
Section titled “16. Known Limitations”- Physical DRM/libinput is exercised on Arch Linux, with device-specific gaps. A real Arch Linux session (including display-manager startup), DRM output, keyboard, mouse, IME, and XWayland clients have been validated on 0.2.4. The 0.2.5 headless suite (78 test scripts) passes on Arch Linux with Zig 0.17, wlroots 0.20, and scenefx 0.5 in both plain and scenefx builds, including portal ScreenCast, tray, notifications, and pointer interaction. A real DRM/libinput session on the new toolchain is still pending. Per-device touchpad tuning, VRR/adaptive-sync commits, and unusual multi-GPU/monitor combinations still need validation on the matching hardware.
- Arch packaging uses the official toolchain. The PKGBUILD targets the
official
zig0.17,wlroots0.20, andscenefxpackages; the effects build is the default. Reportmakepkgfailures with the compiler and pkg-config versions from the troubleshooting section. - Fractional scaling is partial. Fractional-scale-aware Wayland clients get
the exact scale, but the panel uses integer Cairo buffers and unsupported
clients use the ceiling. XWayland depends on
xwayland-satelliteand is not hardware-validated. - Disabled outputs are not persisted.
wlr-randr --offis runtime-only. - One panel per output. This isolates crashes but uses more memory.
- Lock testing is basic.
ext-session-lock-v1is implemented; a real swaylock/PAM setup still needs a physical-session test. - Idle timing is external. Use
swayidle,wlopm, etc. - Clipboard history is text-only and in memory. Eight entries, 16 KiB each.
- Notifications are basic. No rich HTML beyond Pango, sounds, or per-notification output pinning.
- No tray menus. StatusNotifier registration/icons/activation/status/removal work; DBusMenu does not.
- Media controls the first MPRIS player only. No album art.
- System indicators are basic. Battery reads
/sys/class/power_supply; network uses the default route andoperstate. - Corners/blur are in the default build. Requires scenefx 0.5 and GLES2;
use
-Dscenefx=falsefor the plain wlroots scene and pixman. - Window effects are simple. Close snapshots only apply to compositor-initiated closes; client self-closes are immediate.
- Profiles cover layout/tuning, not theme values. Use per-output
output-ruletheme keys for output-specific colors and panel dimensions. - Structural window rules still need a map or reload. Decorative rule fields
(colors, opacity, rounding, blur, shadow, border width) react immediately to
title/app-id changes, but
float,workspace,size,position, andfullscreenare not re-applied on metadata changes. - Layout-helper replies are complete snapshots. Per-output asynchronous helpers, focus/urgent metadata, and version negotiation are implemented; partial streaming rectangle updates are not.
- JSON-RPC is read-only plus event subscriptions. Mutation still uses the line commands.
- Config editing is terminal-based.
tessa-ctl config-check FILE,config-diff FILE, andconfig-editprovide validation, effective-config diff previews, and confirmed apply/reload while the compositor is running.config-edituses$VISUAL/$EDITOR; there is no separate GUI editor. Validation and diff preview require a running compositor. - Output
enabledstate, notification output pinning, and custom notification sounds are not implemented.
17. Related Documentation
Section titled “17. Related Documentation”- README.md — overview, build, screenshots, default bindings
- CUSTOMIZATION.md — rules, layouts, themes, panel, helper protocol
- EXTENDING.md — IPC, events, hooks, JSON, status, examples
- ARCHITECTURE.md — scene graph, IPC, input, rendering, panel internals
- ROADMAP.md — implemented and planned items
- DEVELOPMENT.md — headless development, tests, screenshots, XWayland/IME checks