-
-
Notifications
You must be signed in to change notification settings - Fork 0
Window Effects
Glass, blur, drop shadows, rounded corners and the CRT post-process pass. All of
it is synuirc configuration, and the shipped default is conservative: as of
pkgrel 183 a fresh install boots with window effects off and the SYNAPSE
wallpaper on, because effects on a machine that cannot afford them is a bad first
impression that looks like a broken system.
A default change is invisible on an existing machine. Your
synuircis yours; new defaults only reach the ISO and fresh installs.
Rendering is scenefx 0.5 on wlroots 0.20. scenefx is a fork of wlroots' scene graph, which matters — see the HDR note below.
All of these go in ~/.config/synui/synuirc (or /etc/synui/synuirc).
| Key | Default | Range | What it does |
|---|---|---|---|
transparency |
off |
on/off | Master switch for translucent windows |
active_opacity |
— | 0–1 | Focused window opacity |
inactive_opacity |
— | 0–1 | Unfocused window opacity |
foot_alpha |
— | 0–1 |
foot's alpha, decoupled from the slider above |
corner_radius |
12 |
0–48 | Rounded window corners |
| Key | Default | Range | What it does |
|---|---|---|---|
blur |
on |
on/off | Backdrop blur behind translucent windows |
blur_passes |
3 |
1–5 | Dual-kawase passes |
blur_radius |
5 |
1–20 | Per-pass radius |
blur_noise |
0.02 |
0–1 | Dither, to break up banding |
blur_brightness |
0.90 |
Post-blur brightness | |
blur_contrast |
1.00 |
Post-blur contrast | |
blur_saturation |
1.15 |
Post-blur saturation | |
glass_halo |
0 |
0–64 px | Let the blur reach past the window as a ring |
| Key | Default | Range | What it does |
|---|---|---|---|
shadow |
on |
on/off | Drop shadow |
shadow_blur_sigma |
18 |
0–80 | Gaussian sigma |
shadow_spread |
0 |
0–64 px | Solid ring outside the window before the tail |
shadow_offset_x / _y
|
0 / 6
|
px | Offset |
shadow_opacity |
0.45 |
0–1 | |
shadow_color |
black | ||
clip_csd_margin |
on |
on/off | Crop clients to their declared xdg geometry |
| Key | Default | What it does |
|---|---|---|
effects |
off |
Master switch for the post-process pass |
effect_scanline |
0.35 |
|
effect_curvature |
0.25 |
|
effect_aberration |
0.40 |
Chromatic aberration |
effect_glitch |
0.60 |
|
effect_phosphor |
off |
Monochrome tint: amber / green / white |
effect_mono |
0.90 |
How strongly the phosphor tint desaturates |
effect_bloom |
0.55 |
Phosphor glow |
Persisted to filters.state, adjustable live with a slider per effect.
night_light / night_light_temp, or Super+Shift+B.
This is the single most useful skill for debugging "the shadow looks wrong on this app", and it took a measurement pass to establish.
synui's shadow is a gaussian. No hard edge, reach = sigma + spread, and
shadow_offset_y makes the top band differ from the sides. The shader clips at
2·σ_eff, where coverage is about 2% — never a visible step.
A hard cliff back to the wallpaper is a client margin. GTK crops its own shadow to a rectangular margin that differs per side, so the tell is a hard edge at an asymmetric distance — measured 37 px left, 9 px top on Firefox.
That is exactly what "Firefox's shadow is overgrown and square-cornered" was. It was never synui's shadow.
Firefox never binds xdg-decoration. synui declares SERVER_SIDE, but a
client only obeys if it binds the protocol, so Firefox keeps its GTK frame and an
invisible CSD shadow margin outside the window geometry it declares — and
paints a heavy shadow into it, on top of synui's own.
Proven by logging geometry against wlr_surface_get_extents:
firefox geo = 26,23 896x490 inside ext 948x542
foot geo == ext exactly
Measured, that margin was a ~36 px near-black slab at ~85% darkening with a hard step, against Dolphin's 12 px soft ring.
clip_csd_margin (default on) clips the client tree to
wlr_xdg_surface.geometry, so every window's ring is the one synui draws — same
width, same rounded corners. It also takes the margin's input region off the grab
ring, which is the documented cause of "Firefox can't be resized by its edges".
Three things that are easy to get wrong there:
-
Clip the client tree, not
view->scene_tree.wlr_scene_subsurface_tree_set_clip()recurses into every subsurface tree below the node it is given, and xdg popups are parented beside the client tree — so clipping the parent crops the menus. synui capturesview->client_treeright afterwlr_scene_xdg_surface_create(), when the tree has exactly one child. -
Skip the no-op case. A clip that covers everything is not free: it puts a
source box and dest size on the scene buffer, and that alone shifted the
blurred backdrop around
footby ~7/255. Guarded on extents ⊄ geometry, so every SSD client keeps its exact pixels. -
wlr_box_equalis NULL/empty-safe, soset_clip(NULL)on an unclipped tree early-returns — cheap enough for the per-commit path.
box_shadow.frag insets the solid rectangle by sigma, so only the gaussian
tail lands outside the window. Peak darkening at the border is therefore about
half of shadow_opacity — 50% is a hard ceiling even at opacity 1.0,
against Firefox's measured ~85%.
shadow_spread lifts that ceiling without patching scenefx: grow the node by
sigma + S instead of sigma, and the shader's own inset now lands S px
outside the window. Full alpha for S px, then the tail. Node corners grow
with it; a square window stays square; S = 0 is byte-identical to the old
geometry, which is why it is the default.
Measured: the 12 px band outside Dolphin's border went 60.0 → 46.1 luminance at
shadow_spread = 8.
A maximized or fullscreen window has no shadow, and the Win95 theme's
SYN_CHROME_BEVELchrome returns a shadow of 0. Both are deliberate.
glass_halo = 14 puts a blurred ring outside the window — the backdrop blur
extends past the frame instead of stopping at it.
The history is instructive, because the first two attempts both looked like config problems and were not.
pkgrel 181 — it only ever affected Firefox. The blur companion node was built
only for a translucent view, and transparency defaults to off. On a desktop
with no transparency line in its rc, the only window with a blur node at all
was Firefox (which is glass-native). No value of glass_halo could give Dolphin
or foot a ring, and the nested test rig disagreed because that rig runs with
transparency on.
Any "window effect" verdict from a desktop whose rc has no
transparencyline is a Firefox-only verdict.
pkgrel 182 widened the gate to (translucent || halo > 0), so SSD apps got
the ring for the first time.
pkgrel 184 — the ring had a square top corner. The cause was the per-buffer construction. A decorated window is two stacked buffers, and their corner radii carry the seam rule: the titlebar rounds the top, the content rounds the bottom, and the join between them is straight. Growing those boxes outward inherits the seam — so the content buffer's ring covered the whole left edge with square top corners, and the titlebar contributed no ring at all. The seam rule is right inside the window and wrong outside it.
view_halo_update() now owns it: one wlr_scene_blur node on view->frame,
grown glass_halo px on every side, with corner_radii_all(radius + halo) and
the window's own rounded rect cut out via clipped_region so it is a pure ring.
Lowered under the shadow, with the same maximize/fullscreen suppression the
shadow has.
Two things fell out of moving it to the frame: the per-buffer path got its transparency mask back, and an opaque window no longer runs a full-window blur pass just to show a 14 px ring.
The halo is drawn above the shadow, so it blurs the spread into a smooth ramp rather than compounding it.
glass_halo = 14alone actually brightens the band (60 → 80.7) by covering the shadow; withshadow_spread = 8it lands back at 61.6, but smooth.
synui-firefox-glass toggles it. Firefox is treated as app-native glass: it
needs browser.tabs.allow_transparent_browser (FF 128+), which the helper sets.
Two rules:
-
offmust revert the prefs. Leaving them set means Firefox stays translucent with no glass behind it. - Page content is opaque by design. Only the chrome goes glass; a transparent web page would be unreadable.
synui implements org_kde_kwin_blur, so KDE apps can go glass at all — but note
that Dolphin never binds it. If you are testing KDE blur, confirm with
WAYLAND_DEBUG that the client actually binds the interface before concluding
anything about the compositor.
The display panel tells an HDR monitor from a merely 10-bit one by reading the EDID, not by asking the plane. Every 10-bit plane used to be labelled "HDR-ready"; on this desk exactly one output actually is.
Real HDR10 output is blocked on scenefx, and scenefx is a fork of wlroots'
scene graph — so the work is not "wait for wlroots", it is "port or patch the
fork". wp_color_management_v1 is exported as of the 0.20 port.
Two things the night-light work established, which apply to anything touching output colour:
- Night light goes on the output state. Setting it anywhere else is ignored while window effects are on.
-
A NULL gamma LUT must be committed, or it never turns off. The witness is
modetest'sGAMMA_LUTproperty, not the compositor's own log.
scenefx's fx_renderer ignores WLR_RENDERER — it is GLES2-only. The only
headless path that works is WLR_RENDERER_FORCE_SOFTWARE=1 plus
WLR_RENDERER_ALLOW_SOFTWARE=1, which is also the only configuration grim can
copy from.
The A/B rig, ~2 minutes:
S=/tmp/effects-ab
cp -a ~/.config/synui $S/config # edit the COPY, not SYNUI_CONFIG:
# SYNUI_CONFIG would change layout too
# stub synui-apply-theme on PATH first — see Troubleshooting
env -u SYNUI_SOCKET XDG_CONFIG_HOME=$S/config \
WAYLAND_DISPLAY=wayland-0 WLR_BACKENDS=headless WLR_HEADLESS_OUTPUTS=1 \
WLR_RENDERER_FORCE_SOFTWARE=1 WLR_RENDERER_ALLOW_SOFTWARE=1 \
/usr/bin/synui
# app on wayland-1, then grim, then compare column meansFour traps that cost real time:
-
SIGHUPis not a clean A/B. The border colour flipped between two shots with the same rc, so a whole-image pixel diff is worthless. Restart the compositor per arm, or compare only the profile you care about. -
pkill -f wayland-1kills the running shell command itself (exit 144), and the rest of the compound command silently never runs. Kill by PID. -
Geometry restore only lands if the view is floating —
synctl dispatch float_toggleafter map is what makes it take. - The nested compositor takes the next free Wayland name, not the one you
passed. Confirm
synctl outputssaysHEADLESS-1before you trust anything, or you are driving the live desktop.
Because the installed binary is stripped, the screenshot is the package
verification — running /usr/bin/synui from two pkgrels in the same rig is a
true A/B.
See also: The Desktop, synui, Troubleshooting.
Using it
- Installation
- Updating
- Software
- Files
- Keybindings
- Commands
- Nix
- Gaming
- DaVinci Resolve
- Secure Boot
- Troubleshooting
Customising it
Components
Apps
Hacking on it