Customization
Tessa aims to be highly customizable without embedding a JavaScript/TypeScript runtime or a native plugin VM. Customization has three layers:
- Declarative configuration — key bindings, window rules, layout tuning, colors, includes, and live reload.
- A process extension surface — hooks, structured events, status entries,
the launcher, and the Unix-socket IPC used by
tessa-ctl. - Protocols for external policy engines — the long-lived layout-helper and effect-helper protocols are available now, including per-window tint and animation overrides. The design goal is that a policy engine can live in any language without loading code into the compositor.
The flat key = value format is intentional: it keeps the parser small,
supports positional includes, and makes effective values and parsed rules
inspectable with tessa-ctl config.
Window rules
Section titled “Window rules”A window rule is one line. Match clauses and actions are separated by ;:
window-rule = app-id=pavucontrol; float=truewindow-rule = title=Picture-in-Picture; float=true; position=40,40window-rule = app-id=firefox; workspace=2window-rule = title=GIMP*; float=falsewindow-rule = match=app ~ "^firefox$" && title !~ "Private|Incognito"; workspace=2window-rule = title=Chat*; border-active=#4cc9f0; border-inactive=#1b2a41window-rule = app-id=mpv; opacity=0.9; border-width=0; corner-radius=0window-rule = title=Picture-in-Picture; opacity=0.85; corner-radius=14; blur=trueMatch clauses:
| Key | Meaning |
|---|---|
app-id, app_id, class |
xdg application id |
title |
window title |
output, monitor |
output name |
match, expr |
boolean expression using POSIX extended regexes |
Expression fields are app / app-id / class, title, and output /
monitor. Operators are ==, !=, ~, !~, && / and, || / or,
! / not, and parentheses. Values may be quoted to include spaces. Simple
match keys and match are combined with AND.
Patterns support shell-style * and ? wildcards and are case-sensitive.
* matches any sequence, including an empty one:
window-rule = title=*Picture-in-Picture*; float=truewindow-rule = app-id=org.example.*; workspace=3Action clauses:
| Key | Meaning |
|---|---|
float=true/false |
open floating or tiled |
tile=true/false |
inverse of float |
fullscreen=true/false |
open fullscreen |
workspace=1..9 |
assign the window to a workspace |
size=WxH |
floating size, for example size=900x600 |
width=N, height=N |
floating size, one axis at a time |
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 alias) |
blur=true/false |
per-window backdrop blur |
shadow=true/false |
per-window layered shadow |
border-width=0..16 |
per-window border thickness; 0 hides it |
tint=#RRGGBBAA / tint=none |
composited tint overlay (blocks direct scanout while visible) |
tearing=true/false |
per-window tearing preference |
scratchpad=true/false |
place the window on the scratchpad |
special=NAME / workspace=special:NAME |
place the window on a special workspace |
animation=true/false |
enable or suppress open/workspace/close motion |
animation-ms, animation-open-ms, animation-workspace-ms, animation-close-ms |
per-window durations (-1 inherits) |
animation-curve, animation-open-curve, animation-workspace-curve, animation-close-curve |
per-window curves |
size and position imply floating unless the same rule says float=false.
Invalid expressions or regular expressions are config errors with file/line
diagnostics. Border overrides, opacity, rounding, border width, and shadow state
are also used by close snapshots. corner-radius and blur require a scenefx
build (the default; disabled with -Dscenefx=false); the other effects work
everywhere.
Rules are evaluated in file order; a later matching rule can override an earlier
one. Structural actions (float, workspace, size, position,
fullscreen) run when a window maps and are re-evaluated for existing windows
on Super+R / tessa-ctl dispatch reload. Decorative actions (border-*,
opacity, corner-radius, blur, shadow, border-width) also react
immediately when the title or app-id changes.
Values may be quoted when they contain spaces. A ; inside a value is not
supported in the flat format.
Layer-shell rules
Section titled “Layer-shell rules”Layer-shell clients such as bars, notification daemons, and wallpaper providers
are configured with layer-rule:
layer-rule = namespace=tessa-bar; opacity=0.94; corner-radius=12; blur=truelayer-rule = namespace=tessa-notifications; layer=overlay; opacity=1; blur=falselayer-rule = namespace=my-wallpaper; layer=background; opacity=1; blur=false| Clause | Meaning |
|---|---|
namespace= / ns= |
layer namespace pattern |
output= / monitor= |
output name pattern |
layer= |
background, bottom, top, overlay, or 0..3 |
opacity=0.0..1.0 |
layer surface opacity |
corner-radius=0..64 |
layer surface rounding |
blur=true/false |
backdrop blur override |
Later matches win. Without a rule, opacity is 1, corners are square, and blur
follows the global setting for top/overlay layers only. Layer rules apply on map
and on config reload. Effective values are visible in tessa-ctl state /
state --json; parsed rules are listed by tessa-ctl config / config --json.
Opacity works in every build; rounding and blur require a scenefx build (the
default; disabled with -Dscenefx=false).
Layout tuning
Section titled “Layout tuning”| Key | Default | Meaning |
|---|---|---|
gaps |
10 |
gap between tiles |
outer_gaps |
16 |
gap around the tiling area |
master_ratio |
0.58 |
master-column width ratio |
master_count |
1 |
windows stacked in the master column |
mosaic_ratio |
0.62 |
slice ratio for the mosaic spiral |
mosaic_min_size |
160 |
minimum tile before mosaic falls back to grid |
grid_columns |
0 |
fixed grid columns; 0 uses ceil(sqrt(n)) |
layout |
master |
default layout (master, mosaic, grid, columns, scrolling, monocle, stacking, or custom) |
layout_1 … layout_9 |
inherited | initial layout per workspace |
Runtime layout choices are stored per output and workspace in outputs.conf.
A config reload updates workspaces that have no runtime override. layout_N
only seeds workspaces without a saved or runtime choice.
workspace-rule overrides layout and spacing per workspace:
workspace-rule = ws=2; layout=grid; gaps=4; outer-gaps=8; master-ratio=0.65| Clause | Meaning |
|---|---|
ws=1..9 |
target workspace (required) |
layout=master/mosaic/grid/columns/scrolling/monocle/stacking/custom |
initial layout seed |
gaps=N |
gap between tiles on this workspace |
outer-gaps=N |
outer gap on this workspace |
master-ratio=0.15..0.9 |
master-column width on this workspace |
output-rule overrides layout tuning for one monitor, identified by connector
name or EDID metadata, optionally narrowed to a single workspace:
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 = make=Dell Inc.; model=DELL*; adaptive-sync=onoutput-rule = serial=ABC123; adaptive-sync=off| Clause | Meaning |
|---|---|
output= / monitor= |
connector name pattern |
make= / vendor= |
EDID manufacturer pattern |
model= |
EDID model pattern |
serial= / edid-serial= |
EDID serial pattern |
ws=1..9 |
optional workspace; omitted means every workspace |
layout=master/mosaic/grid/columns/scrolling/monocle/stacking/custom |
layout seed |
gaps=N, outer-gaps=N |
tile and outer spacing |
master-ratio=0.15..0.9, master-count=1..8 |
master tuning |
mosaic-ratio=0.25..0.85, mosaic-min-size=N |
mosaic tuning |
grid-columns=0..8 |
fixed grid columns; 0 is automatic |
profile=NAME |
apply a named tuning profile first |
adaptive-sync=true/false / vrr= |
request VRR for this output |
At least one match clause (output, make, model, or serial) is required.
Any number may be combined; every field that is present must match. Patterns
support * and ?, which is useful when a connector name changes between
docks or boots.
Output rules also accept theme keys: accent, bg, border-active,
border-inactive, panel-bg, panel-fg, bg-top, bg-bottom, bar-height,
bar-top-margin, and bar-side-margin. The panel applies the rule matching its
own output, so per-monitor colors and dimensions stay in sync. Use hyphens or
underscores; both are accepted.
adaptive-sync is applied only to rules without ws=. Tessa checks whether the
output advertises adaptive sync and logs the commit result. Removing the rule
restores the state the output had before Tessa applied a policy. Some monitors
need a VRR-capable mode and connector; check the Tessa log if enabling VRR has
no effect.
Rules are applied in file order. Precedence is global defaults < output rule
(all workspaces) < workspace-rule < output rule with ws=. A runtime layout
choice still wins over the seed and is persisted.
Named profiles group layout/tuning values:
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 may be declared before or after the rules that reference them.
Explicit rule keys override profile values. Profiles cover layout/spacing, not
theme colors. Unknown profile references reject the reload. Workspace profiles
support layout, gaps, outer-gaps, master-ratio, master-count,
mosaic-ratio, mosaic-min-size, and grid-columns.
The Super+Ctrl+H/L ratio binding adjusts the active output/workspace ratio
for the current session; config reload restores configured ratio values.
Workspaces can be bound to monitors so the shortcut always brings that monitor forward:
workspace_pin = eDP-1 1workspace_pin = HDMI-A-1 3Keyboard, gesture, IPC, and bar switches to a pinned workspace redirect to its
output. Super+Shift+<n> moves the focused window to that workspace, changing
its output when the workspace is pinned elsewhere. A missing output releases the
pin until that output appears. tessa-ctl workspace <n> <output> still names the
requested monitor, but a pin takes precedence.
Workspaces can also have names for the bar and for commands:
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 global, unique, single tokens, and cannot be numeric. They resolve
after the full config is parsed, so declaration order is flexible. The bar pill
shows the name (long names are shortened), while tessa-ctl workspace Code and
tessa-ctl dispatch move-workspace Code use the same alias. Windows still
store a numeric workspace internally; modes, pins, gestures, and persistence
keep working by number. Window-rule workspace= accepts either form.
Each output has a hidden floating scratchpad:
window-rule = app-id=org.example.notes; scratchpad=true# window-rule = app-id=org.example.notes; workspace=specialbind = SUPER+grave toggle-scratchpadbind = SUPER+SHIFT+grave move-to-scratchpadtoggle-scratchpad shows or hides the focused output’s scratchpad and focuses
its first window when opening. move-to-scratchpad sends the focused window
into the scratchpad, or returns a scratchpad window to normal tiling. Switching
workspaces hides the scratchpad. The scratchpad is an overlay of floating
windows, not an extra numbered workspace, so it does not affect workspace
occupancy or Super+1..9.
Window groups turn the tiled windows on a workspace into one tabbed tile:
bind = SUPER+SHIFT+g toggle-groupbind = SUPER+ALT+g group-addbind = SUPER+ALT+SHIFT+g group-removetoggle-group groups all tiled windows on the active workspace, or ungroups the
whole group when a member is focused. While grouped, only the active member is
visible and Super+Tab / Super+Shift+Tab cycle tabs instead of moving to
another tile. group-add joins the focused window to a group on the same
workspace; group-remove detaches it. Groups are runtime state, are not
persisted, and are dropped when a member moves to another workspace or the
scratchpad. IPC commands mirror the actions: toggle-group, group-next,
group-prev, group-add, and group-remove.
Mosaic is deliberately bounded: when the configured minimum tile size cannot be maintained, the whole workspace uses the grid layout instead of overlapping or producing unusable slivers.
External layout helper
Section titled “External layout helper”Custom tiling lives in long-lived helper processes, one per output. Select it
with layout = custom, point layout_helper at a command, and bind it if you
want it outside the Super+T cycle:
layout = customlayout_helper = python3 ~/.config/tessa/layout.pylayout_helper_timeout = 100bind = SUPER+SHIFT+T set-layout customTessa sends HELLO<TAB>2 once, then asynchronous requests describing the
usable area and the tiled windows. It never blocks the compositor waiting for
the reply. After a helper announces VERSION<TAB>2, requests look like this
( means a literal tab):
HELLO 2LAYOUT 2OUTPUT HEADLESS-1AREA 0 0 1920 1080OUTER_GAP 16GAP 10BORDER 2SCALE 1.000TRANSFORM 0COUNT 2WINDOW 7 foot notes 1 0WINDOW 8 kitty shell 0 1ENDThe last two WINDOW fields are focused and urgent (1 or 0). Urgency
can be set from a script with tessa-ctl dispatch urgent <id> on (or off);
focusing a window clears it. State JSON exposes it as "urgent".
The helper replies with one rectangle per requested window:
VERSION 2RECT 7 16 16 940 1048RECT 8 966 16 938 1048ENDVERSION 2 is optional. Without it the helper is treated as protocol 1;
old helpers that ignore unknown lines keep working. The first request uses the
protocol-1 three-field WINDOW form; focused/urgent fields are added once the
helper has announced VERSION 2. Coordinates are absolute logical compositor
coordinates. Rectangle width and
height are frame sizes including the border. Floating and fullscreen windows
are not sent. A response must contain exactly one rectangle for every request
window and may not contain unknown ids. Return ERR instead of rectangles to
reject the frame.
Each output gets its own process and rectangle cache. Requests are sent when the
layout, focus, or urgency changes. While a reply is outstanding, the previous
rectangles stay in place and further changes coalesce into the next request.
Until the first successful reply, Tessa uses the built-in layout. A missing
command, timeout, crash, malformed response, or rectangle mismatch falls back
to the master layout for that output. The helper is retried lazily on the next
request after one second; there is no dedicated retry timer. Restarting or
changing layout_helper restarts the helpers; a config
reload with the same command keeps them alive.
The request is limited to 64 tiled windows. The default timeout is 100 ms and
the allowed range is 10–1000 ms. stdout is reserved for the protocol; write
diagnostics to stderr. The command runs as your user through /bin/sh, like
a hook. See examples/layout-cascade.py for a
minimal working helper.
Input modes
Section titled “Input modes”Modes group bindings and are entered with the enter-mode action:
mode = resizebind = h resize-width -30bind = l resize-width 30bind = Escape enter-mode default
mode = defaultbind = SUPER+SHIFT+r enter-mode resizemode = <name> changes the parse context for following bind lines until the
next mode line. Names may contain letters, digits, _, and -. Includes
restore the caller’s mode after returning.
Mode bindings are matched even when no modifiers are held; global (default)
bindings still work, and keys with no match reach the focused client. This makes
modal resize, navigation, or launcher-like keymaps possible without a separate
compositor. tessa-ctl mode <name> switches from a script, and the active mode
is exposed as MODE <name> in tessa-ctl state, "mode" in state JSON,
"modes" in config JSON, and the mode-change event.
Leader keys and per-mode timeouts extend named modes:
leader = SUPER+SPACEleader_timeout = 1000
mode = leaderbind = f spawn firefoxbind = t spawn kittybind = Escape enter-mode default
mode = resizemode_timeout = 300bind = h resize-width -30bind = l resize-width 30Press the leader in the default mode to enter leader; a matching binding runs
one action and returns to default unless it enters another mode. Pressing the
leader again, Escape, or leader_timeout exits. leader_timeout = 0 keeps
leader mode active. mode_timeout applies to the mode section it appears in and
returns to default; 0 disables it.
Event rules
Section titled “Event rules”Event rules filter lifecycle events with the same expression language and run a shell command:
event-rule = window-open; match=app ~ "^firefox$" && ws == "2"; run=notify-send "Firefox" "Opened on 2"event-rule = window-open; match=id ~ "^[0-9]+$" && title == "Mail"; run=touch ~/.local/state/mailevent-rule = workspace-change; match=ws == "3"; run=~/bin/on-workspace-3The first clause is the event name (or use event=). match/expr is optional;
run/command is required. Expressions additionally expose event, ws, and
id, compared as strings. Commands receive the hook environment
(TESSA_EVENT, TESSA_WINDOW_ID, TESSA_WORKSPACE, TESSA_TITLE,
TESSA_OUTPUT, TESSA_ARG, …). Matching event rules run in config order
before hook commands. Like hooks, they run through /bin/sh -c with the
session user’s privileges.
Validation and JSON
Section titled “Validation and JSON”Configuration is parsed with file and line tracking. Unknown keys, invalid numbers/booleans/colors, malformed rules, and missing includes are reported instead of being silently ignored:
tessa-ctl config-checkSuper+R / tessa-ctl dispatch reload rejects a configuration with errors and
keeps the previously active one. tessa-ctl config shows the effective values
plus diagnostic error <path>:<line>: <message> lines. config-check exits
non-zero when errors remain.
For tools, both snapshots have a schema version and are available as JSON:
tessa-ctl state --jsontessa-ctl config --jsonState JSON contains schema: 1, outputs, occupancy, focused id, statuses, and
window geometry. Config JSON contains scalar settings, per-workspace layouts and
tuning, rules, hooks, event rules, profiles, theme roles, and diagnostics. The
full wire format is in EXTENDING.md.
The same socket supports JSON-RPC 2.0 for read access and event subscriptions:
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"}'hello negotiates capabilities (state, config, events, subscriptions,
request-ids, extensions, effects2, protocol version 1) and reports
tessa, extension_api, layout_api, and effect_api. subscribe streams
lifecycle events as JSON notifications with the request id in
params.subscription. Mutation still uses the line commands.
Per-device input and cursor
Section titled “Per-device input and cursor”libinput settings are configured with repeatable input (or input-rule)
lines. Match by device type and/or a wildcard device name; matching rules are
merged in file order:
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=true| Clause | Meaning |
|---|---|
type=any/keyboard/pointer/touchpad/mouse/touch/tablet/tablet-pad/switch |
device class; default any |
name= / device= |
libinput device-name pattern; * and ? supported |
tap=, tap-drag=, tap-drag-lock= |
touchpad tap and tap-drag behavior |
natural-scroll=, left-handed=, middle-emulation= |
pointer scroll/direction/emulation |
dwt= / disable-while-typing= |
disable the touchpad while typing |
accel-profile=adaptive/flat, accel-speed=-1.0..1.0 |
pointer acceleration |
scroll-method=none/two-finger/edge/button/circular |
pointer/touchpad scrolling |
scroll-button=1..767 |
evdev button used by scroll-method=button |
click-method=none/button-areas/clickfinger |
touchpad click behavior |
tap-button-map=lrm/lmr, clickfinger-button-map=lrm/lmr |
button order |
A pointer that supports tap gestures is classified as a touchpad; other
pointers are mice. Touchpad-only clauses are ignored on a mouse, and pointer
clauses that the device does not support are skipped. Every device’s original
libinput state is captured before Tessa applies a rule, so config reload can
remove an override and return to the original state. Device names are shown by
libinput list-devices.
Cursor appearance uses two global keys:
cursor_theme = Adwaitacursor_size = 24cursor_theme is an Xcursor theme name; an empty value uses the system default.
cursor_size is clamped to 16–96 logical pixels. Reload applies both
immediately. If the named theme fails to load, Tessa logs a warning and keeps the
previous cursor.
Input, gestures, and XIM
Section titled “Input, gestures, and XIM”| Key | Meaning |
|---|---|
gestures |
enable compositor swipe gestures (default true) |
gesture_fingers |
finger count for shell swipes, 2–5 (default 3) |
gesture_workspace_threshold |
horizontal distance needed for a workspace switch (default 400) |
gesture_launcher_threshold |
upward distance needed for the launcher (default 300) |
touch |
accept touch input devices (default true) |
tablet |
enable tablet-v2 tools and pads (default true) |
pointer_constraints |
enable pointer-constraints-v1 (default true) |
xim |
export XMODIFIERS for X11 clients (default true) |
xim_server |
XIM server name; empty disables the variable (default fcitx) |
xim_start |
optional XIM server command started after the X display appears |
A swipe with exactly gesture_fingers fingers is handled by the shell when no
keyboard-focused layer surface is open. Swiping left goes to the next
workspace, swiping right to the previous one, and swiping up opens the launcher.
Set gestures = false to forward every gesture to clients. Gestures with other
finger counts, plus pinch and hold, are always sent to the focused client through
pointer-gestures-v1. Tune the thresholds in 20–1000; smaller values react
sooner.
Touch devices are attached to the shared cursor and routed through scene hit-testing. Tapping a client focuses it; the panel accepts touch directly. Tablet-v2 tools receive proximity, tip, pressure, distance, tilt, rotation, slider, wheel, and button events, and fall back to pointer clicks on surfaces that do not support tablet-v2. Tablet pads forward buttons, rings, strips, and mode changes to the keyboard-focused surface.
Pointer constraints are activated when their surface receives keyboard focus
and deactivated on a focus change. Locked pointers stop cursor movement while
relative motion continues to reach the client; confined pointers are clipped to
the constraint region. Set pointer_constraints = false to ignore constraint
requests.
For X11 input methods, Tessa exports XMODIFIERS=@im=<xim_server> once XWayland
is running. xim_start is optional and is started only after the X socket
exists, which avoids starting Fcitx before its display is ready:
xim = truexim_server = fcitxxim_start = fcitx5 -dIf Fcitx or IBus already starts from startup, leave xim_start empty. XIM
settings are applied at compositor startup; restart Tessa after changing them.
Use xim = false when another input method stack manages XMODIFIERS.
Appearance
Section titled “Appearance”| Key | Meaning |
|---|---|
accent |
accent used by borders, panel, and launcher |
bg |
wallpaper/background color |
border_active |
focused-window border color; empty means use accent |
border_inactive |
unfocused border color |
border_width |
border thickness in logical pixels |
font |
panel/launcher family |
bar_height |
bar height in logical pixels (32–96) |
bar_top_margin |
distance from the top edge (0–64) |
bar_side_margin |
left/right margin (0–128) |
panel_bg |
bar background color |
panel_fg |
bar primary text color |
bg_top |
top color of the wallpaper gradient; unset derives it from bg |
bg_bottom |
bottom color of the wallpaper gradient; unset derives it from bg |
wallpaper_art |
draw the decorative wallpaper art (default true) |
wallpaper_image |
PNG wallpaper path (absolute or ~/...); replaces gradient/art |
wallpaper_fit |
cover (default), contain, or stretch |
animations |
animate window open, close, and workspace transitions (default true) |
animation_ms |
default motion duration in milliseconds, 0–1000 (default 160) |
animation_open_ms, animation_workspace_ms, animation_close_ms |
per-kind duration; -1 inherits (default -1) |
reduce_motion |
disable movement animations (default false) |
reduce_motion_animations |
none or fade for a close-only fade (default none) |
reduce_motion_fade_ms |
reduced-motion close fade duration (default 120) |
shadows |
draw layered window drop shadows (default true) |
corner_radius |
rounded window radius in logical pixels, 0–64 (default 10; scenefx builds) |
blur |
backdrop blur for translucent clients and shell layers (default true; scenefx builds) |
blur_radius |
blur radius, 1–32 (default 6) |
blur_passes |
blur quality passes, 1–5 (default 3) |
power_lock |
command for the power menu’s Lock action (default loginctl lock-session) |
power_suspend |
command for Suspend (default systemctl suspend) |
power_reboot |
command for Reboot (default systemctl reboot) |
power_off |
command for Power Off (default systemctl poweroff) |
screenshot_dir |
screenshot PNG directory (default ~/Pictures/Screenshots) |
volume_status |
optional status command (prints a percentage); empty auto-detects wpctl/pactl/amixer |
volume_up |
optional volume-up command override |
volume_down |
optional volume-down command override |
volume_mute |
optional mute-toggle command override |
The panel rereads the palette, launcher entries, bar geometry, and power
commands when the compositor emits config-reload. A geometry change recreates
the bar layer surface, so the reserved area follows immediately. bg_top,
bg_bottom, wallpaper_art, and wallpaper_image repaint the panel background
without a restart. Theme packages are loaded through theme; see below.
Bar modules, priority, and overflow
Section titled “Bar modules, priority, and overflow”bar_layout selects and orders the right-hand cluster (the first name is
closest to the right edge). The logo, workspace pills, and window chips keep
their anchored positions. tessa-panel --list-modules prints the registry,
including whether a module exposes compositor context.
bar_layout = logo, workspaces, chips, layout, groups, mode, effects, clock, tray, statusWhen the right cluster does not fit, the panel hides modules from the lowest
bar_priority upward and draws a +N chip. Clicking the chip lists the hidden
modules and their priorities. Defaults put the clock, status widgets, and
workspaces first, and stats/media/tray last:
# Higher values stay visible longer. Names match bar_layout.bar_priority = clock=100, status=80, layout=60, groups=55, mode=65, effects=30bar_priority = stats=20, media=40, tray=35, battery=50, network=50, volume=60# bar_overflow = false # disable priority hiding and the +N chipThe planner uses approximate module widths and also reserves room for the
+N chip; those hard minimum widths (for example stats below 1180 px and
media below 1250 px) still apply. To preview decisions for a given bar width
without opening a session at that size:
# width, then the left-side space reserved for the logo/chipstessa-panel --bar-plan 850 200The command prints visible, hidden, and skip ... reason=minimum-width
lines with each module’s priority and estimated cost. External status widgets
have their own --priority and +N overflow inside the status module; the
bar planner treats the whole module as one unit.
wallpaper_image is loaded with Cairo’s PNG support. cover fills the output
and crops the excess, contain letterboxes onto bg, and stretch ignores the
aspect ratio. A missing or invalid image logs an error and falls back to the
gradient/art.
Semantic roles are available for colors shared by 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 = #070a12Roles are applied after direct keys and override them; a matching per-output theme rule still wins for that output. The panel reads the same roles.
A theme package is a normal include, addressed with the theme alias:
theme = ~/.config/tessa/themes/vermilion-inktheme behaves exactly like include; the compositor and panel share the same
file. A directory package loads <dir>/theme.conf (other files are ignored for
forward compatibility). The config watcher reloads the active package when it
changes, follows a theme change to a different package, and Super+R reloads
it manually. See examples/themes/vermilion-ink/ for a complete palette, and
tessa-panel --theme-json to inspect the resolved tokens.
Motion, shadows, and wallpaper
Section titled “Motion, shadows, and wallpaper”Windows slide in from a configurable offset when mapped, and workspace
switches slide the incoming workspace horizontally. Both use named
cubic-bezier or spring curves (built-ins, bezier = name, x1, y1, x2, y2, or
spring = name, damping, frequency) over their per-kind duration
(animation_open_ms / animation_workspace_ms, falling back to
animation_ms). A window closed through the compositor (a key binding, bar
chip, IPC, or another client) captures its surface and scales/fades that
snapshot with animation_close_curve over animation_close_ms. A client that
closes itself disappears immediately because its buffer is already gone.
reduce_motion = true disables open/workspace movement while leaving other
settings intact; animations = false disables everything.
animation_ms = 0 sets the fallback duration to zero, but explicit per-kind or
per-window durations still apply.
With reduced motion, reduce_motion_animations = fade keeps only a close fade
without scale or slide, and none disables close effects. Popover animation is
controlled separately by the panel.
Window rules can opt individual windows in or out and override the duration or curve:
spring = bounce, 0.35, 1.4window-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=bounceanimation-curve covers open and workspace transitions; close uses
animation-close-curve. A spring’s damping is 0.05–2 and its frequency is
0.25–4 oscillations over the animation; lower damping overshoots more, and the
curve always ends at 1.
Shadows are three translucent scene rectangles behind each client frame. They
follow the border geometry and are disabled with shadows = false.
In a scenefx build (the default), corner_radius rounds client content,
the border, and shadow layers; fullscreen windows stay square. blur enables
backdrop blur on translucent windows and on the top/overlay/popup layer
surfaces, using blur_radius and blur_passes. Both apply immediately on
config reload. These keys are accepted but have no visual effect in the default
wlroots-scene build.
The wallpaper is a vertical gradient. Set bg_top and bg_bottom to override
the derived default; either key can be omitted independently. Decorative shapes
(the accent wash, ink blots, sun, and tessellation) are drawn only when
wallpaper_art = true.
Calendar and power menu
Section titled “Calendar and power menu”Click the clock to open the month calendar. Left/Right, the arrow buttons,
or the scroll wheel change the month; Home returns to today. The power menu
opens by right-clicking the TESSA mark. It offers Lock, Suspend, Reboot,
Power Off, and Quit Tessa; the first four run the power_* commands above.
Up/Down and Enter work from the keyboard, and clicking outside closes
either popover.
Both popovers can also be opened or toggled by an extension:
tessa-ctl panel calendartessa-ctl panel powertessa-ctl panel power DP-1Opening a popover emits the panel event with arg=calendar or arg=power
and the output name, so hooks can observe or drive the shell as well.
Clipboard history
Section titled “Clipboard history”Super+V opens the clipboard history, and tessa-ctl panel clipboard can open
it from an extension or another binding. The panel uses the compositor’s
wlr-data-control-v1 global to observe selections without requiring keyboard
focus. History keeps the latest eight distinct text entries (up to 16 KiB each)
in memory only; it is never written to disk. Selecting an entry promotes it to
the top and makes the panel the new selection source, so any application can
paste it with its normal paste command.
clipboard is also available as a key-binding action:
bind = SUPER+v clipboardbind = SUPER+SHIFT+v clipboardScreenshots
Section titled “Screenshots”Super+Shift+S (or tessa-ctl panel screenshot) captures the focused output
with wlr-screencopy. Drag to select a region, click for the full output, or
press Enter for the full output; Esc or right-click cancels. The panel draws
the selection on an overlay, punches the selected area out of the dim layer, and
shows the physical pixel size while dragging.
PNGs are written to screenshot_dir:
screenshot_dir = ~/Pictures/ScreenshotsThat is the default. Subdirectories are created as needed. Files are named
tessa-YYYYMMDD-HHMMSS-<ms>.png, where the millisecond field is unpadded
(for example tessa-20261103-020304-42.png). The capture is taken before the
overlay is
shown, so the selection UI and dim layer are not included. The bar is part of
the output and is included unless you select a region below it. screenshot is
also a key-binding action:
bind = SUPER+SHIFT+s screenshotSystem indicators and media
Section titled “System indicators and media”When a battery is present under /sys/class/power_supply, the bar shows its
charge and colors the fill while charging. The network indicator reads the
default route from /proc/net/route and the interface state from
/sys/class/net; it shows Wi-Fi when the interface has a wireless attribute
and LAN otherwise. Both refresh every five seconds and need no configuration.
Tessa watches MPRIS on the session bus. The first player discovered contributes
a bar chip with the current title and play/pause state. Click the chip, press
Super+M, or run tessa-ctl panel media to open the controls: previous,
play/pause, and next. In the popover, Space toggles playback and Left/Right
skip. The MPRIS client is read-only for metadata; album art is not displayed.
Use media as a key-binding action, for example:
bind = SUPER+M mediabind = SUPER+SHIFT+m mediaStatusNotifier tray
Section titled “StatusNotifier tray”The panel owns org.kde.StatusNotifierWatcher and hosts registered tray items.
Icons from IconPixmap or IconName appear in the bar. Left-click activates an
item; right-click or Super+Shift+T opens a popover with the item titles and
statuses. Up/Down selects, Enter activates, and Esc closes. Items can
also be opened with:
tessa-ctl panel traytray is available as a key-binding action:
bind = SUPER+SHIFT+t trayDBusMenu context menus and secondary activation are not implemented yet. If a different watcher already owns the D-Bus name, the tray service logs the failure and the rest of the panel keeps running.
Volume
Section titled “Volume”When wpctl, pactl, or amixer is available, the panel shows the default
output volume in the bar. Click it or press Super+Shift+A to open a compact
volume bar just below the volume indicator without dimming the desktop.
Up/Down changes the volume, m toggles mute, and Esc closes.
tessa-ctl panel audio opens the same bar, and audio is a key-binding
action:
bind = SUPER+SHIFT+a audioThe default commands are:
wpctl get-volume @DEFAULT_AUDIO_SINK@,wpctl set-volume ... 5%+/-, andwpctl set-mute ... togglefor PipeWirepactl get-sink-volume,pactl set-sink-volume, andpactl set-sink-mutefor PulseAudioamixer get Masterandamixer sset Master ...for ALSA
Override any part with panel settings. A custom status command should print a
percentage and may contain MUTED/[off]; custom commands run through
/bin/sh -c:
volume_status = ~/.local/bin/my-volume-statusvolume_up = ~/.local/bin/my-volume upvolume_down = ~/.local/bin/my-volume downvolume_mute = ~/.local/bin/my-volume muteScreen sharing and portals
Section titled “Screen sharing and portals”The compositor exposes the protocols xdg-desktop-portal-wlr needs:
zwlr_screencopy_manager_v1, linux-dmabuf when the renderer supports it,
ext-output-image-capture-source-manager-v1, ext-image-copy-capture-v1, and
both foreign-toplevel list protocols. The repository ships
packaging/portal/tessa.portal; system packages install it under
/usr/share/xdg-desktop-portal/portals/. It selects the wlr backend for
XDG_CURRENT_DESKTOP=Tessa.
Install xdg-desktop-portal, xdg-desktop-portal-wlr, and a running PipeWire
server. If xdpw’s automatic chooser is inconvenient, select an output directly:
[screencast]chooser_type = noneoutput_name = HDMI-A-1max_fps = 30Monitor screencasting is covered end to end by tests/portal.sh. Window
sources depend on the portal backend’s support for the image-capture-source
protocols; Tessa currently advertises output capture sources.
Notifications
Section titled “Notifications”The panel implements the org.freedesktop.Notifications D-Bus service on the
session bus, so applications can use it without a separate daemon. Up to four
toasts stack below the bar. They support replacement by notification id, timeout
(0 keeps a notification until dismissed), CloseNotification, and
click-to-dismiss. Notifications marked with an expiry time remove themselves and
emit the corresponding signal.
Advertised capabilities are actions, body, body-markup, icon-static, and
persistence:
- Actions. Non-default action pairs become buttons at the bottom of the
toast. Clicking one emits
ActionInvokedand closes the notification. Adefaultaction is triggered by clicking the card body. Notification actions can also be invoked from an extension withtessa-ctl notify-action <id> <key>, which is useful for scripted tests and keyboard-driven automation. - Icons.
image-data/image_data,image-path/image_path, theicon-namehint, and theapp_iconargument are supported. PNG theme icons are searched under the usual XDG icon directories; otherwise a colored monogram is shown. - Markup. The body is rendered with Pango markup when it parses cleanly (bold, italic, underline, spans, and links). Invalid markup falls back to plain text.
- Routing. New toasts are created on the currently focused output. When focus moves while toasts are visible, the stack moves to the new output. Actions still work because a single panel owns the D-Bus name and renders the surface on the chosen output.
If another notification daemon is already running, Tessa cannot own the bus name and leaves notifications to that daemon. Notification sounds and HTML beyond Pango’s supported subset are not implemented.
You can exercise the service with:
gdbus call --session --dest org.freedesktop.Notifications \ --object-path /org/freedesktop/Notifications \ --method org.freedesktop.Notifications.Notify \ test-app 0 "" "Build finished" "All targets are up to date" "[]" "{}" 5000Keys and commands
Section titled “Keys and commands”See ../README.md for the defaults. Additional bindings use XKB keysym names:
bind = SUPER+Return terminalbind = SUPER+g set-layout gridbind = SUPER+ALT+Left resize-width -30bind = SUPER+ALT+Right resize-width 30Available actions include spawn, terminal, close, quit, workspace,
move-to-workspace, focus, move, toggle-float, toggle-fullscreen,
focus-next, focus-prev, launcher, clipboard, screenshot, media,
tray, audio, reload, ratio, toggle-layout, set-layout,
resize-width, and resize-height.
Live inspection and automation
Section titled “Live inspection and automation”tessa-ctl config prints the effective configuration, including parsed rules
and tuning values:
tessa-ctl configtessa-ctl statetessa-ctl events window-openHooks receive the same structured fields as the event stream. Status entries, custom launcher entries, runtime layout changes, and window resizing are all available through IPC. See EXTENDING.md for the protocol.
Customization status
Section titled “Customization status”The 0.7 customization-parity items are implemented:
effect_helperreceives per-window state from a long-lived process and returns opacity, corner radius, blur/shadow, and border-width overrides; see EXTENDING.md. There is no in-process plugin VM.tessa-ctl config-check FILE,config-diff FILE, andconfig-editprovide candidate validation and effective-config diff previews.- Shell v2 adds selectable/ordered bar modules, per-surface fonts and foreground/background colors, PNG or PPM-shader wallpapers, and layer rules for the bar, popovers, and toasts.
- The bar module registry is exposed by
tessa-panel --list-modules(add--jsonfor tools). Built-in context modules includelayout,groups,mode, andeffects; they read compositor state rather than spawning a separate shell process.tessa-panel --theme-jsonexports resolved semantic color, font, geometry, and effect tokens for external shells. tessa-ctl shellreports which shell roles Tessa Panel owns and lists external layer-shell clients such as Waybar or Quickshell.tessa-ctl perfandframe_time_budgetexpose render-commit timings; the headless benchmark istests/performance.sh.- The built-in panel is supervised with pidfd exit detection and bounded
exponential restart backoff;
tests/panel-recovery.shcovers crash recovery and config-driven enable/disable. tests/fuzz-parsers.shfuzzes config and IPC parsing, andtests/fuzz-asan.shrepeats it with the ASan runtime preloaded. Helper timeout contracts are covered bytests/helper-timeouts.sh.
Remaining validation is hardware-specific (DRM/libinput, physical touchpads, VRR, and a target-laptop frame-time budget). CI-built signed packages and release artifacts are still on the 0.1.x roadmap. The roadmap is in ROADMAP.md. A TypeScript/TSX configuration runtime remains a non-goal; Tessa’s answer to ShojiWM-style composition is a process protocol plus declarative rules, not an embedded JS VM.