Quick Settings tile
Title and artist; click to play or pause. The arrow opens cover art, album, previous / play-pause / next, “Open ‹player›” and a player picker.
GNOME Shell extension
What is playing, in Quick Settings and the top bar.
Shows the current track from any MPRIS player — Spotify (including the Flatpak), browsers, Rhythmbox, VLC and the rest — with play/pause, previous, next and a button to bring the player to the front.
01
QuickMusic adds one tile to Quick Settings and, while something plays or is paused, one item to the top bar. It reads the standard MPRIS interface players already publish on the session bus, so there is nothing else to install.
Title and artist; click to play or pause. The arrow opens cover art, album, previous / play-pause / next, “Open ‹player›” and a player picker.
“Artist – Title” while something is playing or paused, hidden otherwise. Left-click plays and pauses; right-click opens the same controls.
Whichever started playing most recently, and the last one used once nothing is. Pin one from the picker to stay on it.
Spotify (including the Flatpak), Firefox, Chromium, Rhythmbox, VLC and most others. Nothing extra to install.
02
org.mpris.MediaPlayer2.<name> on the
session bus; Spotify’s does.
$ curl -LO https://github.com/Ghost-Assembly/quickmusic/releases/latest/download/[email protected] $ gnome-extensions install --force [email protected] $ gnome-extensions enable [email protected]
$ just setup $ just install $ just enable
Log out and back in. A newly installed extension is only picked up when the Shell next starts. On Wayland that means a new session.
03
QuickMusic speaks MPRIS, the D-Bus interface desktop media players publish. With several running, the tile shows the first that applies:
A pin is stored without any instance suffix —
chromium, not chromium.instance2 — so
it survives the player restarting. A pinned player that is not
running stays in the picker, marked “(not running)”, so the pin
can be seen and undone.
$ just mpris-check chromium (Chrome) Stopped (no track) can: (nothing) * spotify (Spotify) Playing Foo Fighters – My Hero album: The Colour And The Shape can: canPlay canPause canGoPrevious canGoNext canRaise
04
Open them from the Extensions app, or with
just prefs from a clone.
| Setting | Default | Note |
|---|---|---|
| Show in the top bar | On | Hidden while nothing is playing or paused, either way. |
| Top bar label width | 40 characters | 10 to 120. Longer text is cut with an ellipsis. |
| Pinned player | None | Set from the Quick Settings picker; “Forget” clears it. |
05
The top bar item works without a mouse once it has focus.
| Input | Does |
|---|---|
| Left-click | Play or pause |
| Right-click | Open the controls |
| Enter or Space | Play or pause |
| Menu or Shift+F10 | Open the controls |
A screen reader hears the whole track and what activating the item would do — “Pause Foo Fighters – My Hero” — even when the visible label is cut short.
06
extension.js is deliberately thin: it builds the
MPRIS watcher and the panel on enable, and tears both down on
disable. Every decision lives in one pure module; the files
around it only move data between it and the bus or the Shell, so
the suite can test the decisions on plain Node.
Players are untrusted. Text is set as plain
text, flattened to one line and capped; cover art loads only
from https:// or file://. See the
security policy.
07
The unit suite runs on Node with the Shell’s modules replaced by
recording stubs. just test-live adds what CI cannot
have: it boots a headless GNOME Shell with a fake player on a
private bus, and checks that the tile picks up a new player,
follows a pause, drops a player that quits, and survives a
disable and re-enable with no JavaScript error. It then checks
the shell log for two kinds of leak: signal or object lifetime
warnings, and a GSource that outlived the disable
(a Source ID … was not found or
GSource … still active line). It then runs the
packaging check below. just test-docs holds this
site to its rules in Chromium and Firefox: accessibility in both
color schemes, no JavaScript, no other origin, and no sideways
scrolling on a phone.
prefs.js has no coverage, by
design: it holds only Adw and Gtk widget construction, and the
key list and wording it shows live in
modules/settings.js, tested on plain Node. The
exclusion is written in both vitest.config.js and
sonar-project.properties so the two agree.
modules/mpris.js’s race logic — a player
connecting, quitting or being dropped mid-load — is under test
with a fake bus. The wire format itself is still checked against
the real session bus, by just mpris-check, and
against scripts/fake-player.js by
just test-live.
08
An extension ships as a plain zip, conventionally named
<uuid>.shell-extension.zip, with
metadata.json at the archive root.
just build makes it with zip rather
than GNOME’s gnome-extensions pack, because that
tool comes with the gnome-shell package and CI has
no GNOME.
The official packer stays the authority all the same:
just pack-check runs it and fails on any difference
from the built zip, so the two cannot drift.
Every shipped icon is decoded, not just packed.
An icon that fails to load is silent — the Shell draws nothing
and logs nothing. A comment placed before the
<svg> element is enough to defeat
gdk-pixbuf’s format sniffing, so the pack check loads each icon
and fails if it does not decode to something visible.
09
Set version-name in metadata.json and
version in package.json, commit, then
tag and push. Run just test-live first: neither the
headless check nor the packer check runs in CI.
$ git tag -a v0.1.0 -m 'release v0.1.0' $ git push origin v0.1.0
The release workflow checks the tag against both files and stops
before building if they disagree — a release whose zip tells
GNOME it is another version is worse than none. It then runs
just ci and attaches the zip to a GitHub release.
Uploading to extensions.gnome.org is deliberately left out of
CI. gnome-extensions upload authenticates with the
account password rather than a scoped token, and every upload
goes to human review anyway.
10
just # list every recipe just test # unit suite just test-docs # this site, in Chromium and Firefox just lint # eslint, prettier, gschema, shellcheck just ci # what CI runs: lint, test, test-docs, security, build just test-live # headless gnome-shell, bundle and live-bus checks just mpris-check # list the players on this session bus just fake-player # put a test player on the bus just docs # serve this site on localhost:8000