Skip to content

Effects

Effects are GLSL programs Umbriel runs on animation events, on the focused window’s border, on windows, on whole outputs, and around the pointer. Every effect is off until you select one. Umbriel ships a small set; each is a preset you include and then name where it should apply.

Bundled presets install under share/umbriel/effects/<kind>/<name>/. Include a preset’s effect.toml, then select its name. For an installation under /usr:

[include]
files = [
"/usr/share/umbriel/effects/border/pulse/effect.toml",
"/usr/share/umbriel/effects/animation/reveal/effect.toml",
]
[effects]
border = "pulse"
[animation.windows_in]
effect = "reveal"

Including a file only makes its preset available. The border and effect selectors are what turn it on. If your configuration already has an [include] table, append the paths to its files array and add the selectors to your existing [effects] and [animation.windows_in] tables rather than repeating the tables.

Bundled presets:

PresetKindSelector
revealanimation[animation.windows_in] effect = "reveal" (also windows_out)
squashanimation[animation.windows_move] effect = "squash"
pulseborder[effects] border = "pulse"
scanlineswindow[effects] window = "scanlines"
vignettescreen[effects] screen = "vignette"
glowcursor[effects] cursor = "glow"

Turn a default off for one window or output

Section titled “Turn a default off for one window or output”

Set a selector to "" to select nothing. A window rule or an output table can replace the default by name or switch it off with "off":

[effects]
border = "pulse"
screen = "vignette"
[[window_rule]]
match.app_id = "^mpv$"
border_effect = "off"
window_effect = "scanlines"
[output."HDMI-A-1"]
screen_effect = "off"

border_effect and window_effect follow the usual window-rule merging: the last matching rule that sets a key wins. The cursor effect has no per-window or per-output override.

[effects]:

KeyDefaultDescription
border""Border preset or pool for the focused window.
window""Window preset or pool applied to every window.
screen""Screen preset or pool applied to every output.
cursor""Cursor preset or pool, shared across outputs.
max_fps0Cap, 0 to 240, for frames drawn only because an effect animates. 0 follows each output’s refresh rate.
in_capturefalseInclude window, screen, and cursor effects in screencopy and image-copy captures. Border effects always appear, and an export-dmabuf capture always sees the same frame as the display, regardless of this setting.

Where each kind draws:

  • animation binds to an animation event through [animation.<event>] effect = "<name>". The event’s enabled, duration_ms, and curve still own its timeline; see Animation.
  • border draws on the focused window’s border ring while the window is decorated, not fullscreen, and not urgent. padding reserves transparent space around the ring for the effect to paint into.
  • window draws over each window in place, regardless of focus, including undecorated and fullscreen windows. It follows the window through opening, closing, moving, workspace switches, and the overview. A floating window with client-side decorations and corner_radius = 0 has no rounding to mask against, so the effect also shades the transparent margin the client draws around such a window.
  • screen draws over the whole output after everything else, except the cursor effect and the software cursor.
  • cursor draws in a square of radius logical pixels around the pointer (0 covers the whole output), after the screen effect, only on the output currently holding the pointer, clipped at that output’s edges. It runs before the software cursor is drawn and never shades the cursor image, and never affects a hardware cursor. It hides when the compositor hides the pointer; a client that hides its own cursor image does not by itself turn the effect off.

The session lock detaches screen and cursor effects and never shades the lock surface.

[effects.preset.<name>] defines one preset. kind is required; shader is a GLSL file relative to the TOML file that names it. The file is watched and reloads with the configuration; a missing or unreadable file reports a diagnostic and leaves the preset inert until the file appears, and a preset without shader is inert as well.

Presets and pools share a namespace. Names must be non-empty, cannot be off, and cannot contain /, which separates an action selector from its target. A colliding pool is rejected while the preset remains valid, with diagnostics pointing to both declarations.

KeyKindsDefaultDescription
kindallrequiredanimation, border, window, screen, or cursor.
shaderallnonePath to the GLSL source, at most 256 KiB. Without it the preset is inert.
paletteallfalseSupply [colors] accent and status colors to the program.
paddingborder0Transparent space around the ring the effect may paint, 0 to 1024.
speedborder1.0Multiplier on umbriel_time, 0 to 10. 0 holds umbriel_time at zero.
animatedbordertruefalse freezes umbriel_time at zero.
overlayborder""A window preset drawn on the window while the border effect applies.
light.spreadborder80How far light from the ring spills, 1 to 256 logical pixels. Defining [effects.preset.<name>.light] enables light.
light.intensityborder1.0Light gain, 0 to 4.
light.thresholdborder0.5Brightness a ring pixel needs before it emits, 0 to 1.
radiuscursor0Half-size of the square around the pointer, 0 to 4096; 0 covers the output.

Border light is built from the ring in buffer pixels, so the same preset’s brightness differs across output scales. The light itself stacks below panels and pinned windows, above a window being dragged.

Keys that do not belong to a preset’s kind are reported as unknown. Defining the same preset name in two files is an error.

[effects.preset.tint]
kind = "window"
shader = "tint.glsl"
palette = true
[effects]
window = "tint"

A pool chooses a stable preset for each owner before its first rendered frame:

[effects.pool.borders]
kind = "border"
choose = ["pulse", "quiet"]
selection = "unused_first"
[effects]
border = "borders"

Define or include the pulse and quiet border presets separately.

KeyRequired/defaultMeaning
kindRequiredborder, window, screen, or cursor.
chooseRequired arrayOrdered names of presets of that kind.
selectionunused_firstunused_first, round_robin, or random.

unused_first chooses the least-held member, breaking ties in list order. round_robin hands out members in order and wraps. random makes a uniform random choice. Invalid or duplicate members are dropped individually, keeping the first valid occurrence. A missing/non-array choose, invalid policy, or animation kind rejects the pool. An empty pool is inert and can be selected, but cannot be cycled. Pools cannot contain pools.

Pools are accepted by the four [effects] selectors, window rules’ border_effect/window_effect, output screen_effect, and matching runtime actions. Animation bindings and border overlay remain preset-only. Definitions and references may be in different included files. Inspection keeps declaration order: included files before their including file, and source order within each file. Members keep choose order.

Each mapped window owns independent border and window slots; each present output owns a screen slot; the session owns one cursor slot. Holdings count only unsuppressed owners assigned from that same pool. Hidden/scratchpad windows, unfocused borders, disabled outputs, and failed/inert programs still hold members. Plain presets, other pools, overlays, and closing snapshots do not count.

Visibility and drawing gates do not allocate. A focus rule that changes the winning selector resolves that selector. An unchanged selector retains its member. On a pool change, a valid remembered member for the destination wins, then the current member if it belongs to that pool, then a new policy pick. Returning to a pool can therefore share a member with a newer window. Releasing a holding never redistributes other owners.

The sixteen effect actions operate independently on window, border, screen, and cursor slots:

Terminal window
umbriel msg effect-window-set:scanlines
umbriel msg effect-border-cycle:borders
umbriel msg effect-window-toggle
umbriel msg effect-window-reset
umbriel msg effect-screen-set:vignette/HDMI-A-1
umbriel msg effect-cursor-set:glow

Window/border actions default to the focused window; screen actions use the preferred output. Set and cycle accept a target after the first /, preserving any further slashes in output names. An unnamed targeted cycle is effect-window-cycle:/<window-id>. Toggle/reset take an optional target directly. Cursor actions have no target. Explicit screen targets can address present disabled outputs; their cached selection is visible when they are enabled again.

set <name> installs a runtime override, clears suppression, and makes a fresh policy pick for a pool. cycle [pool] advances through that pool (or the underlying current pool), installs an override, and clears suppression. If the current member is absent from the requested pool, cycle uses its policy. One-member pools cycle successfully; empty pools report pool is empty.

set off suppresses without replacing the selector or losing history; repeating it does nothing further. Toggle flips suppression when a member exists and can always unsuppress, even if a reload made the selection empty. Toggling an empty, unsuppressed slot succeeds without changing it. Suppression releases the holding and also hides a border’s overlay. Rules/reloads continue updating the underlying selection while suppressed. reset clears the override, suppression, history, and cached assignment, then resolves configuration afresh.

Runtime overrides take precedence over rules and defaults. Invalid names, kinds, targets, or payloads fail without changing selection or policy state. A valid inert or failed program remains selected and renders plainly.

Unrelated reloads and renderer recovery retain assignments. Effects reloads prune invalid history, retain still-valid members, and drop deleted/wrong-kind runtime overrides with an owner-specific diagnostic, preserving suppression. Unmap clears the window’s state after copying closing visuals; remap starts fresh. Output disable retains state, but disconnect/reconnect starts fresh. Runtime state is not written to disk.

umbriel effects prints presets with program states, pools with exact hold counts, the cursor selection, and window/output owners. --json provides the same data for scripts. States are inert, unreferenced, failed, and compiled. Only reached pools compile their members. Named keybinds and enabled hot corners are prepared before their first trigger; an IPC-only name compiles synchronously on first selection. Suppressed runtime overrides remain roots; inactive pool history does not.

umbriel windows --json and subscribe windows include border_effect and window_effect with name, pool, source (default, rule, or runtime), and suppressed; borders also expose overlay. Inspection never picks, compiles, or binds. Screen assignments are in effects, while outputs --json keeps its existing output-management contract. See IPC.

Sources are GLSL ES 1.00 fragment code without #version, main, or precision qualifiers. Each kind defines one entry point that receives uv, normalized over the drawn rectangle with (0, 0) at the top left, and returns premultiplied RGBA:

KindEntry point
animationvec4 animation(vec2 uv)
bordervec4 border(vec2 uv)
windowvec4 window(vec2 uv)
screenvec4 screen(vec2 uv)
cursorvec4 cursor(vec2 uv)

Every kind sees:

NameMeaning
umbriel_sample(vec2 uv)The input under the drawn rectangle: the captured window for animations, the native ring for borders, the pixels already on screen for window, screen, and cursor effects.
umbriel_sample_previous(vec2 uv)This effect’s previous result. Using it allocates two extra buffers for each window or output it runs on.
umbriel_sizeDrawn width and height in effect logical pixels, before overview zoom.
umbriel_scaleBuffer pixels per effect logical pixel, including overview zoom.
umbriel_expandHow far the drawn rectangle extends past the window on each side, as a fraction of its width and height. (0, 0) except for an animation running while drag physics deforms the window.
umbriel_timeSeconds on the animation clock, times the border’s speed. Held as a single-precision float that is never wrapped, so fine time-based motion loses precision after long uptimes. sin and cos reduce their argument to one revolution, so they stay correct at large angles.
umbriel_palette_count4 for palette presets, 0 otherwise.
umbriel_palette_at(float t)The palette color at t, blended between neighboring colors from the wrapping sequence accent_primary, accent_secondary, warning, error. Transparent black when there is no palette.

Animations add umbriel_progress, umbriel_clamped_progress, umbriel_linear_progress, umbriel_direction, and umbriel_random_seed (Animation). Borders add umbriel_border_hole (the client rectangle in uv), umbriel_border_radius (its corner radii in logical pixels), and umbriel_border_distance(vec2 uv), the signed distance in logical pixels to the client rectangle, negative inside it; the client hole is always cut out of a border’s result. Cursor effects add umbriel_pointer, the pointer position in uv.

At rest, a window effect reads the output framebuffer after the window has been drawn, so through a translucent window it sees and may rewrite the desktop behind it. While an animation encloses the window (opening, closing, moving, a workspace switch, the overview, or drag physics) it reads that animation’s capture instead: it shades the window’s own content, and the result is composited over the live desktop. A shader that depends on the backdrop must tolerate that change when an animation begins or ends. A border’s overlay follows the same rule.

Referenced presets compile at startup, on effects reload, and during runtime action preparation. A compile error is logged with the preset’s name and the driver’s message, whose line numbers count from the top of the shader file; that preset renders plainly (opening and closing animations keep their built-in animation, style and scale included) until a reload fixes it. Unknown names, or a preset of the wrong kind for a selector, report a diagnostic and are dropped: a top-level [effects] selector selects nothing, and a window rule’s or output’s own override falls back to an earlier matching rule or the [effects] default. Shaders are trusted local GPU code; keep them small and side-effect free.

Unreferenced presets and pools add no rendering work. Named keybinds and enabled hot corners prepare programs before use; selections retain bookkeeping state. A border effect renders the ring through a capture and one program pass per frame on the focused window, and requests extra frames only while its program reads umbriel_time and its clock advances, capped by max_fps. Light adds a second program pass and a blurred pyramid where the ring draws, and a blend on every output its light reaches. A window effect copies the pixels under the window and runs one pass per window per frame. Screen and cursor effects each run one pass over the output or the radius square and disable direct scanout on that output. Drag physics costs only while a window is held or settling. With in_capture = false, a pending screencopy or image-copy capture of an output composes its frame twice whenever any window, screen, or cursor effect is visible on that output.