Skip to content
Velle Sinclair edited this page Jul 30, 2026 · 8 revisions

synui

The Wayland compositor, on wlroots 0.20 and rendering through scenefx 0.5's fx_renderer. Written for this system rather than adapted to it: it draws its own display-settings panel, wallpaper picker, dock, cursor picker, sound panel and control panel, renders an optional CRT post-process pass, and holds live subscriptions to synguard's verdict feed and synapd's activity.

Tiling + monocle layouts, per-output workspaces, XWayland, layer-shell. All shortcuts: Keybindings. Roadmap: synui/ROADMAP.md.

Deeper dives: The Desktop (bar, widgets, icons, themes) · Window Effects (glass, blur, shadows, CRT) · Cursor Themes · Sound Themes.


Features

Control panel (Super+C) — the front door. Lists every shortcut, generated from the live bind table, so it can't drift from the actual bindings.

The bar — a native quickshell bar, one per monitor, with per-monitor settings, a right-click menu and auto-hide. It replaced waybar in pkgrel 154, and the start button and menu moved into it in 163. See The Desktop.

Dock — auto-hide with hover-slide and a reveal dwell, right-click context menu, runtime pin/unpin, drag to any edge (vertical on left/right).

Desktop — optional ~/Desktop icons with drag-to-place, persistent placement and arrange-by name/type/size/date; a desktop right-click menu; and four optional widgets (visualiser, sysmon, big clock, quick-launch), all off by default.

Wallpapers (Super+W) — built-in Synapse (static) and Matrix (animated GLES2 rain, ported from the KDE matrix shader), a browser for ~/Pictures and friends, per-monitor choices and scaling modes on Tab (pkgrel 192), and Steam Workshop wallpapers through the optional linux-wallpaperengine package, driven by synui-wpengine.

Mixer — right-click the bar's volume module for per-device and per-application levels, drawn by the bar itself (pkgrel 197). See The Desktop.

Themes (Super+T) — SYNAPSE / Dark / XP / 95 plus six riced palettes, applied across synui's own chrome, the bar, GTK, Qt/KDE, the terminal (kitty, and foot where present) and Firefox.

Cursor themes (Super+Shift+P) — picker panel plus synui-cursor to install them. Event sounds (Super+S) — ten events, all silent by default, plus synui-sound to install sound themes.

Window effects — glass, backdrop blur, rounded corners, drop shadows with a shadow_spread knob, an optional glass_halo ring, and clip_csd_margin to crop clients that paint their own shadow. Plus the CRT pass (Super+E): scanline, curvature, aberration, glitch, and amber/green/white phosphor with bloom. Off by default as of pkgrel 183.

Display (Super+D) — per-output mode, position, scale, 10-bit colour and HDR detection from the EDID (not from the plane's bit depth). Monitor grid with aligned columns. Night light on the output state.

Power management (Super+P) — compositor-native dim / blank / lock / suspend. Retired swayidle entirely. Includes media idle-inhibit: it won't blank the screen while audio is playing. Explicit laptop lid policy, brightness keys, battery.

Lock screen (Super+L) and a graphical greeter over greetd IPC, drawn in-process and mirroring the lock screen.

Task manager (Ctrl+Alt+Delete) — process table with CPU/RAM/GPU overview and per-process VRAM via dlopen'd NVML (no CUDA headers needed at build time).

Game mode (Super+G) — detects a fullscreen Xwayland client, stops synapd to free its VRAM, and holds off idle. The bar shows an indicator ($XDG_RUNTIME_DIR/synui-game).

Screenshots and recordingPrint and friends (Keybindings); Super+Shift+R records. Clipboard historySuper+V.

Security feed from synguard, neural overlay (Super+A) fed live from synapd, system tray (SNI), news reader (Super+R), ISO mounter (udisks2, no root), and cat mode (Super+Shift+C) — a procedurally-drawn cairo kitty that wanders your monitors on top of everything, because why not.


Hard-won rules for working on synui

Compositor-specific traps that cost real days. The cross-cutting ones — signal inheritance, silent staleness, shadowed units — are in Development Notes.

Never block the wl_event_loop on X

Xwayland is itself a client of that event loop, and it starts lazily. A blocking xcb round-trip made from the event loop deadlocked the entire compositor at login — black screen, dead input, no core dump. Use a ready-flag plus a worker thread.

Reset signal dispositions in every child you spawn

SIG_IGN and the blocked mask survive exec(). This burned us three separate times: synui's SIGCHLD=SIG_IGN leaked into Xwayland and made it abort itself; and synui's signalfd blocked mask was inherited by every child, making every app synui launched immune to SIGTERM.

Decorative scene buffers must set point_accepts_input = false

Or they swallow clicks. (Discovered via cat mode, of all things.) The QML equivalent is mask: Region {} on a desktop widget — without it the widget is an invisible rectangle that eats every click over it.

Keyboard focus takes view_surface(view), never the raw surface

Two click sites passed the raw (sub)surface to focus_view, sending wl_keyboard.enter to a render subsurface. Firefox therefore never made its persistent-storage doorhanger the active modal, so the panel was unresponsive and Tab skipped it. A doorhanger is a subsurface, not a popup — none of the popup/grab machinery applies to it.

Don't call focus_view on popup clicks

It disrupts the xdg_popup grab, which broke menu activation in Firefox.

Unconstrain nested popups too

parent_view is only set for toplevel-parented popups, so nested ones were never unconstrained and every submenu got its full requested size — an application list ran off-screen instead of scrolling.

Pointer motion must be delivered during an implicit grab

Without wl_pointer.motion while a button is held, region-select (slurp) and every client-side drag are broken.

An X11 view has a NULL xdg_surface

Anything on the unmap or decoration path that reaches for xdg_surface unconditionally will SEGV the moment an X11 window is involved. And when you need to crop something, crop the buffer, not the treewlr_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.

A layer surface's exclusiveZone before its first configure is dropped

The bar reserved its strip while auto-hidden and lost the reservation when it should have had it, because the zone was set too early. The same ordering is why desktop-icon layout pass 1 saw the whole output: the bar reserves its strip after startup, so anything laid out at startup must re-run once the zone lands.

Effects: whole-damage before build_state, or you get a stale swapchain

Committing whole-output damage after the state is built shows the previous frame's buffer for one frame — the "flash on focus change" bug.

Re-seat the blur node per commit

A blur node that keeps its old size after a resize is the "shadow ghost" people report as a rendering artifact. It is a scenefx blur node that was never resized.

The seam rule is right inside a window and wrong outside it

A decorated window is two stacked buffers whose corner radii encode the titlebar/content seam. Anything built by growing those per-buffer boxes inherits the seam and comes out square where it should be round. Frame-level effects belong on view->frame, not on the buffers. See Window Effects.


Config

~/.config/synui/synuirc (or /etc/synui/synuirc), plus outputs.conf for monitor layout — where primary=1 marks the primary output for Xwayland, which is what stops SDL games opening on the wrong monitor. outputs.conf x/y are taken verbatim; synui does not repack them into rows.

State files live beside it: wallpaper.state, wpengine.state, dock.state, power.state, filters.state, welcome.state, cursor.state, cursor.env, sounds.state, widgets.state, theme.json.

A state file wins over synuirc — that is what makes a choice made in a panel survive both a restart and a packaged config update. See Wallpapers for the one people meet first.

The file is synuirc, not synui.conf. Every state file resolves through one helper (syn_config_path) so they all honour XDG_CONFIG_HOME — they used to hardcode ~/.config/synui while synuirc honoured the env var, so pointing XDG_CONFIG_HOME elsewhere read settings from one directory and persisted choices from another.

Session env vars live in three places: the live /usr/local/bin/synui-session and two blocks in syn-install.sh. Change one, change all three. (XCURSOR_SIZE lives here — unset, libXcursor sizes the cursor from the virtual screen width, which on a multi-monitor setup is thousands of pixels and gives Xwayland clients a huge pointer. The wrapper sources cursor.env after the defaults.)


Testing synui without a seat

Most rendering work is verified in a nested headless compositor rather than on a real seat. Three rules that are not optional — scenefx's renderer is GLES2-only so software rendering has to be forced, synui-apply-theme must be stubbed or a nested session re-themes the live desktop, and you have to confirm which compositor you are actually talking to before trusting any result.

Full recipe and traps: Development Notes.

See also: Keybindings, The Desktop, Window Effects, Architecture.

Clone this wiki locally