Skip to content

Sound Themes

Velle Sinclair edited this page Jul 26, 2026 · 2 revisions

Sound Themes

SynapseOS boots silent. Every event sound is off, and ~/.config/synui/sounds.state does not exist until you turn one on. That is deliberate: a system that starts making noises after an upgrade is a system that gets its speakers muted, and then the notification you actually wanted is gone too.

Turning them on is Super+S (the panel) or synui-sound (the command line). Both write the same file, and the file is the only state there is.


Quick start

sudo pacman -S sound-theme-freedesktop libcanberra   # samples + the player
synui-sound                                          # what is on right now
synui-sound all on                                   # every event
synui-sound login on                                 # or just one
synui-sound test login                               # hear it, regardless of switches

synui-sound with no arguments prints the whole state — master switch, theme, volume, and one row per event showing the sample it would actually play:

  master           on
  theme            freedesktop
  volume           70
  login            on    service-login            (Login)
  device_added     off   device-added             (Device plugged in)
  lock             on    bell                     (Screen locked)
  screenshot       on    (not in freedesktop)     (Screenshot)

That third column is the point of the tool. Read on for why.


The events

Ten of them, hooked into the compositor at the point the thing actually happens:

Event Fires when Hook
login the session starts synui_main
logout the session ends synui_destroy — so SIGTERM and ^C get it too, not just the quit action
device_added / device_removed USB or block device plugged / unplugged the libudev monitor
lock / unlock screen locked / unlocked lock.c
notify a notification arrives notif.c, only when !replaces
screenshot a screenshot is taken input.c
volume_change the volume OSD moves input.cnot on mute
error an alert wherever one is raised

notify fires only on a genuinely new notification. A client updating an existing one (replaces_id set) is a progress bar, and playing a chime per update turns a file copy into a machine gun.


Commands

synui-sound                          show the current state
synui-sound <event> [on|off|toggle]  one event
synui-sound all [on|off|toggle]      every event at once
synui-sound master [on|off|toggle]   the master switch alone
synui-sound test <event>             play it once, ignoring every switch
synui-sound volume [0-100]           master level
synui-sound theme [<name>]           show or set the sound theme
synui-sound themes                   list installed themes
synui-sound samples [<theme>]        list the sample ids a theme ships
synui-sound sound <event> [<id>|default]   pick the sample for one event
synui-sound install <archive|dir>    install a theme
synui-sound remove <name>            remove one you installed

Two behaviours worth knowing:

  • all toggle turns everything OFF if anything is on. "Make it stop" is always one predictable command, never a coin flip.
  • Turning any event on turns the master switch on. Otherwise all on is ten switches flipped and still silence.

Getting more sounds

From the repos

Each of these adds one theme to the picker:

sudo pacman -S sound-theme-freedesktop ocean-sound-theme \
               sound-theme-elementary pop-sound-theme \
               deepin-sound-theme cosmic-sound-theme

Then synui-sound theme ocean, or the Sound theme row of the Super+S panel.

From a downloaded pack

synui-sound install ~/Downloads/some-sound-theme.tar.gz
synui-sound install ~/my-sounds            # a plain folder works too

Accepted: .zip, .tar.gz, .tar.xz, .tar.bz2, .tar.zst, or a directory. It lands in ~/.local/share/sounds/<name>/ and appears in the theme row immediately. The installer prints the sample count, and warns if it is zero — a theme that ships no samples installs fine and is silent, which is otherwise indistinguishable from a broken switch.

A pack that ships a light and a dark variant side by side installs both.

A folder of loose files is enough

The most likely thing you have is a folder of .oga/.ogg/.wav files with no theme structure at all. Rather than refusing it over a missing index.theme, the installer stages a real theme around it: samples into stereo/, a generated index.theme naming it, and from there it is an ordinary install.

Hand-rolling one

A theme is a directory:

~/.local/share/sounds/mine/stereo/desktop-login.oga
~/.local/share/sounds/mine/index.theme     # [Sound Theme] / Directories=stereo

The file names are XDG sound ids (freedesktop.org sound-naming spec), so a theme you make here works in GNOME and KDE unchanged. A theme need not be complete — pick any sample it does have for any event with synui-sound sound <event> <id>.

Removing one

synui-sound remove mine

Only ever from ~/.local/share/sounds. A theme under /usr/share/sounds came from a package and belongs to pacman — removing it here would leave the package database claiming files that are gone. If the theme you remove was the selected one, the selection moves to another installed theme, because a desktop that has silently gone quiet is worse than one that sounds different.


Picking the sample for one event

By default each event resolves through an automatic chain: the name the sound-naming spec gives the event first, then a fallback the freedesktop theme actually ships.

synui-sound sound login bell        # play "bell" for login instead
synui-sound sound login default     # back to the automatic chain
synui-sound samples                 # what the current theme has to offer

In the Super+S panel, [ and ] cycle the selected event's sample. (Brackets rather than Shift+arrows because the panel's key handler returns 0 for any modifier — Shift+Left never arrives at all.)

A deliberate pick is never silently replaced by a fallback. Once you name an id, only that id is tried. The fallback chain exists to make the defaults audible, not to second-guess you.

synui-sound sound validates the id against the theme on disk and refuses a typo. That is the only place it can be caught: libcanberra answering "no such sample" is indistinguishable from a broken toggle everywhere downstream.


Technical notes

Why several spec names resolve to something else

The sound-naming spec defines desktop-login, desktop-screen-lock and others that no theme on disk actually ships. Defaulting to them would mean every event named a file nobody hears. So each event has a chain — spec name first, so a theme that does ship the proper name wins, then one the freedesktop theme really has:

Event Chain
login desktop-loginservice-login
lock desktop-screen-lockbell
unlock desktop-unlockcomplete
notify message-new-instantmessage
screenshot screen-capturecamera-shutter

The state report and the panel both show the resolved id, checked against the theme on disk — not the chain's first entry. Three states, three colours in the panel: picked (bright), automatic (dim), and not in this theme (warning). That last one is the whole reason the column exists.

/usr/share/sounds/alsa is a ghost theme

/usr/share/sounds/alsa is nine channel-test .wav files (Front_Left.wav, …) dropped there by alsa-utils. No index.theme, no stereo/, not one XDG sample id. Select it and every event is silent — every switch says on, and every lookup misses.

An earlier picker offered it, because it listed any directory under share/sounds.

A theme is now index.theme or stereo/ or mono/, enforced in both the shell (is_theme) and the panel (sound_dir_is_theme). Filtering alone would leave anyone already on such a directory silent and none the wiser, so both also name it:

  theme            alsa  <-- NOT a sound theme, nothing will play
                   pick one of: freedesktop ocean

Lesson: when you tighten a validity check, handle the people already sitting on the invalid value. A filter fixes new choices and abandons existing ones.

The volume key collision

The master level and the volume-OSD event were both called volume in one flat state file. get volume read the event's line (off) into the level, and every sound went silent. The event is now volume_change.

The same shape of bug is why the per-event sample override is keyed <event>_sound and matched by building the exact key, not by prefix — an event added later whose name extends another's would otherwise steal its line.

Lesson: one flat namespace means every name in it must be distinct from every other, including names of a different kind.

Two writers, one file, no drift

synui-sound is the single writer of sounds.state. The Super+S panel, the Super+S keybind and the command line all run the same script, so "is this event enabled?" has exactly one answer and one format.

The compositor caches the file to skip a fork() when everything is off — but it is a cache, and the script re-checks on every invocation. A sound is never played because the compositor's copy was stale.

Writes are temp-file + rename(), so the panel (which re-reads after every change) can never see a half-written file.

The panel's cache must refresh before acting, not after

Both the sound and widget panels keep an optimistic copy of the state, because spawning the helper is asynchronous and re-reading immediately after draws the previous value under the cursor.

But the volume row adjusts relatively. A copy left stale by a synui-sound volume typed in a terminal gets written back over the new value. The fix is an mtime-keyed refresh at the top of the key handler — and deliberately not zeroing the mtime after your own write: an unchanged mtime means the child has not landed yet, and the optimistic value is the better answer.

The panel does not re-read while open. Changing sounds.state under an open panel shows nothing — close and reopen it.

The udev filter

Device events come from a libudev monitor filtered to usb/usb_device and block/disk. Unfiltered, plugging in one USB stick fires once per USB interface and once per partition — four chimes for one stick.

Playback path

canberra-gtk-play when it is present, with --property=canberra.xdg-theme.name=<theme> so the setting applies to this one sample without touching your global GTK sound-theme-name. Without libcanberra it falls back to resolving the file itself and handing it to pw-play or paplay.

Volume is 0–100 as a percentage of full scale, converted to the dB libcanberra wants (100 = 0 dB, the sample as recorded). 0 is treated as mute and never reaches the player.

A theme with no such sample is silence, not an errorplay always exits 0. This is why the report has to do the checking.

Checking it without listening

synui-sound with no arguments resolves every event against the theme on disk, so it answers "what would play?" without playing anything. synui-sound test <event> is the other half: it ignores every switch, so it separates "the sound is off" from "the audio stack is broken".


See also: Cursor Themes, The Desktop, synui, Keybindings, Commands.

Clone this wiki locally