Discord Rich Presence for Apple Music on Linux.
Refrain shows what you're listening to on Apple Music as your Discord status
— whether the audio is playing in a browser tab on music.apple.com or
streaming from your phone over Bluetooth.
- Reads playback metadata from MPRIS (Apple Music in any major Linux browser) and BlueZ AVRCP (any AVRCP-capable Bluetooth source). Firefox and its relatives report the page they play; Chromium-based browsers need KDE's Plasma Browser Integration, which works outside KDE too — see troubleshooting.
- Forwards track + cover art to Discord via the local IPC socket.
- Optionally scrobbles to Last.fm alongside Discord (opt-in, with a crash-safe offline queue).
- Lives in your system tray with Play/Pause/Next/Previous controls.
- Keeps a recently played list — cover, time, source — of the last 30 songs by default, on your machine only.
- Provides a settings window (PySide6) for everything users typically want to tweak — privacy mode, sources, autostart, Bluetooth device picker.
- Linux with D-Bus
- Python ≥ 3.11
- Discord desktop client running (Refrain talks to its IPC socket)
- For Bluetooth: BlueZ with AVRCP enabled
| Channel | Install |
|---|---|
| PyPI (any distro with Python ≥ 3.11) | pipx install --system-site-packages refrain — see below |
| AUR (Arch / CachyOS / Manjaro / EndeavourOS) | yay -S refrain (stable) or yay -S refrain-git (latest main) |
| AppImage (portable single-file, any glibc-based distro) | Download from the latest release, chmod +x, run it |
| From source | See below |
The AppImage needs FUSE 2, which current distros no longer install by
default: libfuse2t64 on Debian and Ubuntu, fuse-libs on Fedora,
libfuse2 on openSUSE, fuse2 on Arch. Without it, start the AppImage
with --appimage-extract-and-run.
A Flatpak manifest exists under packaging/flatpak/ for users who want to
build it themselves; a Flathub submission is on the roadmap but not
currently active. Build files for the live channels live under
packaging/.
See packaging/README.md for build instructions.
Which versions can still be downloaded, and why the others were taken
down: docs/releases.md.
Current distros no longer let pip install into the system Python, so
install with pipx. dbus-python and PyGObject come
from your distro; --system-site-packages lets Refrain use them instead of
compiling its own. On Debian and Ubuntu:
sudo apt update && sudo apt install pipx python3-dbus python3-gi libxcb-cursor0
pipx install --system-site-packages refrain
pipx ensurepath # once, if ~/.local/bin isn't on your PATH yet
refrain --install-desktopCommands for Fedora and openSUSE, the source build, where the files land
and how to remove them again: docs/install.md.
See docs/test-matrix.md for the full Tier-1 / Tier-2
list, the per-row smoke checks, and which distros are explicitly out-of-scope.
- Tier 1 (must pass every release): CachyOS, Arch Linux, Fedora 42, Ubuntu 24.04 LTS, Ubuntu 25.04, Ubuntu 26.04 LTS, Debian 13 (Plasma and GNOME), openSUSE Tumbleweed, Linux Mint 22, Manjaro Stable.
- Desktops: KDE Plasma 6 (Wayland is the primary target, X11 also
covered), GNOME with the AppIndicator and KStatusNotifierItem
extension, XFCE / Cinnamon / LXQt / Budgie via their native or
AppIndicator-bridged tray, MATE with
mate-applet-statusnotifier, tiling WMs (Hyprland / Sway / i3 / river) via a SNI-capable status bar.
Refrain needs a Discord Application ID to push status updates. Each user registers their own (free, takes 30 seconds):
- Open https://discord.com/developers/applications and click New Application.
- Give it a name you're entitled to use — that name is what shows up under "Listening to ..." in your Discord status. You can also upload a square image as the application icon; Discord uses it as the fallback when there's no album cover.
- Copy the Application ID from the General Information page.
- Launch Refrain → Settings → General → Application ID → paste, OK.
- In Discord: Settings → Activity privacy → Share your detected activities with others. A fresh Discord install has this switch off, and with it off nobody sees your status — Refrain looks connected either way. The Discord desktop app is required; Discord in a browser can't receive a status.
The first time you launch Refrain without a configured ID, the welcome wizard pops up with the setup steps + a live diagnostics panel that probes your D-Bus session and Discord IPC socket so you know up front whether your environment can host the RPC at all.
After the wizard, the Status window says You're all set. Play a song and it shows up in Discord within a few seconds.
Refrain can scrobble to Last.fm alongside the
Discord status — a second, independent channel, never a replacement.
It's opt-in and off by default. Register your own free
API account, then fill in
Settings → Last.fm. The shared secret and session token are stored in
your OS keyring, never in config.toml.
Full walkthrough, the scrobble rules and troubleshooting:
docs/lastfm.md.
Refrain is local-first: no Refrain server, no account, no telemetry, and the author receives nothing. Two lookups are on by default and can be switched off — cover art at Apple and the update check at GitHub. Everything else only runs once you set it up: Discord needs an Application ID, Last.fm is opt-in. Data always goes directly to that provider, never through anything of ours.
Privacy → Off is the global kill switch (no Discord status, no
scrobbling) while keeping the tray + controls running. The Recently
played list never leaves your machine.
Every data flow, what is kept and for how long, and how to erase it —
written to GDPR transparency expectations — is in
PRIVACY.md.
Using Refrain
- Installing — per-distro commands, source build, uninstall, file locations
- Interface — tray, Status window, Recently played, notifications
- Settings — every tab, field by field
- Configuration file — what
config.tomlholds - Updates — update check and AppImage self-update
- Bluetooth quick-start — pair + AVRCP setup walkthrough
- Last.fm scrobbling — API account + connect walkthrough
When something is wrong
- FAQ
- Known limitations and troubleshooting — what doesn't work, and what to check when something fails
- Developer mode — local timing and usage metrics, and the live log. Never sent
- Test matrix — supported distros, smoke-check checklist
About the project
- Architecture overview — threads, D-Bus surface, file paths
- Privacy & data protection — every data flow, retention, erasure
- Security policy
- Changelog · Releases — every version, and why some were removed
- Roadmap
- Contributing — dev setup, testing, code style
- Packaging guide — AUR, Flatpak, AppImage build steps
See CONTRIBUTING.md for dev setup, testing, and the
source/UI architecture. PRs welcome — especially for distribution packaging
(Flatpak, AUR, AppImage) and for additional Bluetooth device shapes.
- General questions, feedback, feature ideas → contact@rockykln.com
- Security reports → report@rockykln.com (also: GitHub private advisory)
- Bugs → please use the issue tracker
Refrain License (Use-Only) — see LICENSE.
Refrain is source-available but not open source. In short:
- Anyone may use, copy, and redistribute the unmodified Software.
- Anyone may read, study, and reference the source code.
- Modifications and derivative works may not be redistributed. Forks on GitHub are fine only for preparing a pull request.
- The "Refrain" name and logo may not be used to imply endorsement of or affiliation with modified versions.
Third-party dependencies (PySide6, pypresence, dbus-python) retain
their original licenses (LGPL / MIT).
Refrain is an independent project. It is not affiliated with, sponsored by, or endorsed by Apple, Discord, Last.fm or KDE, and "Refrain" is not a registered trademark. All product names and trademarks belong to their respective owners and are used only to describe what Refrain interoperates with.
Full notice — trademarks, licence, third-party components, and what data
leaves your machine — in LEGAL.md. The same text is reachable
inside the app under Settings → Legal.
| Stat | Value |
|---|---|
| Lines of code | 13,038 |
| Lines in the repository | 87,382 |
| Words in the repository | 306,980 |
| Words of documentation | 46,967 |
| Automated tests | 2,133 |
| Test coverage | 100 % |
| Days since the first release | 138 |
| Versions released | 24 |
| Downloads | 2,676 |
| Commits | 213 |
| Languages | 16 |
| Runtime dependencies | 3 |
| Browsers tested | 6 (Chrome, Chromium, Brave, Vivaldi, Firefox, Zen) |
| Ways to install | 3 (PyPI, AUR, AppImage) |
As of v0.5.3.