refrain 0.2.2__tar.gz → 0.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {refrain-0.2.2 → refrain-0.2.4}/.github/ISSUE_TEMPLATE/bug_report.yml +1 -1
- {refrain-0.2.2 → refrain-0.2.4}/.gitignore +1 -0
- {refrain-0.2.2 → refrain-0.2.4}/CHANGELOG.md +252 -1
- {refrain-0.2.2 → refrain-0.2.4}/PKG-INFO +7 -4
- {refrain-0.2.2 → refrain-0.2.4}/README.md +6 -3
- {refrain-0.2.2 → refrain-0.2.4}/ROADMAP.md +114 -0
- {refrain-0.2.2 → refrain-0.2.4}/docs/architecture.md +41 -10
- refrain-0.2.4/docs/bluetooth.md +136 -0
- {refrain-0.2.2 → refrain-0.2.4}/docs/faq.md +44 -0
- refrain-0.2.4/docs/screenshots/live-log.png +0 -0
- refrain-0.2.4/docs/screenshots/settings-advanced.png +0 -0
- refrain-0.2.4/docs/screenshots/settings-general.png +0 -0
- refrain-0.2.4/docs/screenshots/settings-sources.png +0 -0
- refrain-0.2.4/docs/screenshots/settings-updates.png +0 -0
- refrain-0.2.4/docs/screenshots/update-dialog.png +0 -0
- refrain-0.2.4/docs/screenshots/welcome.png +0 -0
- refrain-0.2.4/docs/test-matrix.md +169 -0
- {refrain-0.2.2 → refrain-0.2.4}/packaging/appimage/AppImageBuilder.yml +1 -1
- refrain-0.2.4/packaging/aur/refrain/.SRCINFO +20 -0
- {refrain-0.2.2 → refrain-0.2.4}/packaging/aur/refrain/PKGBUILD +5 -2
- {refrain-0.2.2 → refrain-0.2.4}/packaging/flatpak/io.github.Rockykln.Refrain.metainfo.xml +43 -0
- {refrain-0.2.2 → refrain-0.2.4}/pyproject.toml +1 -1
- refrain-0.2.4/src/refrain/__init__.py +1 -0
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/app.py +197 -17
- refrain-0.2.4/src/refrain/assets/icons/menu-update.svg +3 -0
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/autostart.py +25 -15
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/config.py +47 -7
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/daemon.py +108 -56
- refrain-0.2.4/src/refrain/discord_rpc.py +207 -0
- refrain-0.2.4/src/refrain/i18n/refrain_de.qm +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/i18n/refrain_de.ts +274 -84
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/logging_setup.py +19 -11
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/sources/bluetooth.py +31 -2
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/sources/mpris.py +15 -7
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/sources/mpris_server.py +48 -11
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/timing.py +36 -0
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/ui/log_window.py +11 -6
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/ui/settings_window.py +72 -1
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/ui/tray.py +20 -4
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/ui/update_dialog.py +69 -18
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/ui/welcome_dialog.py +12 -4
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/updater.py +70 -8
- {refrain-0.2.2 → refrain-0.2.4}/tests/test_autostart.py +28 -0
- {refrain-0.2.2 → refrain-0.2.4}/tests/test_config.py +24 -0
- {refrain-0.2.2 → refrain-0.2.4}/tests/test_daemon_idle.py +60 -0
- refrain-0.2.4/tests/test_discord_rpc.py +180 -0
- {refrain-0.2.2 → refrain-0.2.4}/tests/test_timing.py +47 -1
- {refrain-0.2.2 → refrain-0.2.4}/tests/test_updater.py +38 -0
- refrain-0.2.2/docs/screenshots/live-log.png +0 -0
- refrain-0.2.2/docs/screenshots/settings-advanced.png +0 -0
- refrain-0.2.2/docs/screenshots/settings-general.png +0 -0
- refrain-0.2.2/docs/screenshots/settings-sources.png +0 -0
- refrain-0.2.2/docs/screenshots/settings-updates.png +0 -0
- refrain-0.2.2/docs/screenshots/update-dialog.png +0 -0
- refrain-0.2.2/docs/screenshots/welcome.png +0 -0
- refrain-0.2.2/src/refrain/__init__.py +0 -1
- refrain-0.2.2/src/refrain/discord_rpc.py +0 -107
- refrain-0.2.2/src/refrain/i18n/refrain_de.qm +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/.editorconfig +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/.gitattributes +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/.github/FUNDING.yml +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/.github/codeql/codeql-config.yml +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/.github/dependabot.yml +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/.github/pull_request_template.md +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/.github/workflows/codeql.yml +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/.github/workflows/release.yml +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/.github/workflows/security.yml +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/.github/workflows/tests.yml +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/CODE_OF_CONDUCT.md +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/CONTRIBUTING.md +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/LICENSE +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/Makefile +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/SECURITY.md +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/docs/screenshots/README.md +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/docs/screenshots/discord-rpc.png +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/docs/screenshots/notification.png +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/docs/screenshots/tray-menu.png +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/packaging/README.md +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/packaging/aur/refrain-git/PKGBUILD +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/packaging/flatpak/io.github.Rockykln.Refrain.desktop +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/packaging/flatpak/io.github.Rockykln.Refrain.yaml +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/packaging/flatpak/python-deps.json +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/requirements-dev.txt +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/requirements.txt +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/__main__.py +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/assets/icons/github-mark.svg +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/assets/icons/refrain.png +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/assets/icons/refrain.svg +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/assets/icons/tray-paused-dark.svg +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/assets/icons/tray-paused.svg +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/assets/icons/tray-playing-dark.svg +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/assets/icons/tray-playing.svg +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/assets/icons/tray-stopped-dark.svg +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/assets/icons/tray-stopped.svg +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/assets/refrain.desktop +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/cover_art.py +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/cover_fetcher.py +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/paths.py +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/single_instance.py +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/sources/__init__.py +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/sources/base.py +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/src/refrain/ui/__init__.py +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/tests/__init__.py +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/tests/conftest.py +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/tests/test_cover_art.py +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/tests/test_cover_fetcher.py +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/tests/test_daemon_album_format.py +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/tests/test_discord_listening.py +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/tests/test_imports.py +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/tests/test_mpris_dispatch.py +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/tests/test_paths.py +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/tests/test_sources_base.py +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/tests/test_tray_init.py +0 -0
- {refrain-0.2.2 → refrain-0.2.4}/tests/test_update_dialog_body.py +0 -0
|
@@ -7,6 +7,250 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.4] - 2026-05-07
|
|
11
|
+
|
|
12
|
+
A polish + reliability release built on the v0.2.3 distro-portability
|
|
13
|
+
work. The headline items are time-display consistency across all
|
|
14
|
+
three surfaces (tray / Discord / Plasma panel), Snap/Flatpak Discord
|
|
15
|
+
support out of the box, a full inline changelog in Settings, and
|
|
16
|
+
~20 % more test coverage to lock the new behaviour in. No breaking
|
|
17
|
+
changes — same config schema, same Python floor, same UI layout.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **Inline release notes in Settings → Updates tab.** The tab
|
|
22
|
+
previously only had an auto-check toggle, last-checked label and a
|
|
23
|
+
"Check now" button — the actual release notes only showed up in
|
|
24
|
+
the popup when an update was available. The tab now carries a
|
|
25
|
+
QTextBrowser that renders the same Markdown the popup uses, plus
|
|
26
|
+
"Current version" and "Latest known" labels. `UpdateOrchestrator`
|
|
27
|
+
gained a `releaseInfoFetched(release | None)` signal that fires
|
|
28
|
+
after every check (auto or manual, success or failure) so the tab
|
|
29
|
+
refreshes its contents regardless of the result.
|
|
30
|
+
- **`docs/bluetooth.md`** — first-time-setup walkthrough covering
|
|
31
|
+
bluez install per distro, pairing recipe, AVRCP verification,
|
|
32
|
+
per-source Discord profile setup, and a Troubleshooting section
|
|
33
|
+
for the common breakage modes (no AVRCP exposed, missing
|
|
34
|
+
control, multiple paired devices, empty dropdown).
|
|
35
|
+
- **FAQ entries** for "Refrain isn't picking up my browser"
|
|
36
|
+
(covers the `playerctl` diagnostic, the Snap-confined-browser
|
|
37
|
+
workaround, and the Firefox `about:config` toggle), and
|
|
38
|
+
"How do I add a browser that isn't in the list".
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- **Tray progress + published MPRIS metadata + Discord activity
|
|
43
|
+
payload all use the iTunes-corrected duration.** v0.2.3 already
|
|
44
|
+
fixed Discord; the tray's "0:42 / 7:21 (-6:39)" ticker and the
|
|
45
|
+
`mpris:length` we publish to Plasma's panel were still using the
|
|
46
|
+
raw `mpris:length` and showed inconsistent numbers when MPRIS
|
|
47
|
+
briefly lied about a song's duration. Hoisted
|
|
48
|
+
`pick_effective_duration_ms` into `_dispatch` so all three
|
|
49
|
+
surfaces see the same value every tick. Tray position is also
|
|
50
|
+
now clamped to duration so a "2:30 / 0:14 (-0:00)" display can't
|
|
51
|
+
happen during a brief MPRIS preview-clip glitch on a longer song.
|
|
52
|
+
- **`os.chmod(tmp, 0o755)` on AppImage update replaced by mode
|
|
53
|
+
preservation** — read the running AppImage's mode and mirror it,
|
|
54
|
+
with `0o700` as fallback. CodeQL's `py/overly-permissive-file`
|
|
55
|
+
warning is gone, and an AppImage that the user installed at
|
|
56
|
+
`0o700` stays `0o700` across upgrades.
|
|
57
|
+
- **DiscordRPC dedupes identical consecutive payloads** instead of
|
|
58
|
+
hammering the IPC channel on every poll. The daemon ticks every
|
|
59
|
+
500 ms but Discord rate-limits presence updates to 5 per 20 s, so
|
|
60
|
+
~75 % of those ticks were silently being dropped on the Discord
|
|
61
|
+
side anyway. Now the second-and-on identical payload is a no-op
|
|
62
|
+
on our side too.
|
|
63
|
+
- **Cover-wait defer back to 3 polls (~1.5 s)**. v0.2.2 lowered it
|
|
64
|
+
to 1 poll to feel more "live", but that meant Discord briefly
|
|
65
|
+
rendered the `refrain` brand fallback as the *large* image while
|
|
66
|
+
iTunes search returned, then flipped to the cover — visible
|
|
67
|
+
flicker on every track change. With 3 polls Discord typically
|
|
68
|
+
transitions straight from "no activity" to the cover with no
|
|
69
|
+
flash. Bounded: songs that have no iTunes match still update
|
|
70
|
+
after 1.5 s with the brand fallback.
|
|
71
|
+
- **Tray menu's "Update available" line now carries a coloured
|
|
72
|
+
icon** (Breeze accent blue, `assets/icons/menu-update.svg`)
|
|
73
|
+
instead of just a unicode `⬆` arrow in the same white as every
|
|
74
|
+
other line. Visually distinguishes the update notification from
|
|
75
|
+
Settings / Live log / Restart in the menu's icon column.
|
|
76
|
+
- **Browser hint defaults expanded** with Floorp, Waterfox, Mullvad
|
|
77
|
+
Browser, Tor Browser and ungoogled-chromium. Existing configs
|
|
78
|
+
keep their saved list (no auto-migration); the new entries appear
|
|
79
|
+
unticked in Settings → Sources until the user toggles them.
|
|
80
|
+
- **~20 English-only UI strings wrapped in `tr()`** —
|
|
81
|
+
`update_dialog.py` (status labels, button labels, header HTML),
|
|
82
|
+
`log_window.py` (toolbar Level/Auto-scroll/Copy/Clear/Close), and
|
|
83
|
+
`app.py` module-level QMessageBox calls (now via
|
|
84
|
+
`QCoreApplication.translate`). Re-ran `lupdate6` + `lrelease6`
|
|
85
|
+
against `refrain_de.ts`: 127/127 finished German translations.
|
|
86
|
+
- **`docs/architecture.md` refreshed** to v0.2.x reality — the
|
|
87
|
+
diagram showed a "1 Hz tick" (default has been 500 ms since
|
|
88
|
+
v0.2.2), the worker-thread block was missing `MPRISServer`, the
|
|
89
|
+
GLib thread for dbus-python signal dispatch was undocumented, and
|
|
90
|
+
the "does not export any custom interfaces" line was wrong since
|
|
91
|
+
the v0.2.2 MPRIS-server publication. Plus a new section on
|
|
92
|
+
Discord IPC sandbox bridging (Snap/Flatpak).
|
|
93
|
+
|
|
94
|
+
### Fixed
|
|
95
|
+
|
|
96
|
+
- **Tray tooltip cleared on track change.** After a paused-to-paused
|
|
97
|
+
track switch, the tooltip briefly showed "Song B • 1:30 / 2:11"
|
|
98
|
+
using Song A's elapsed counter while the new track waited for its
|
|
99
|
+
first `progressTick`. `set_track` now drops the stale progress
|
|
100
|
+
line when the title text actually changed.
|
|
101
|
+
- **`compute_idle_state` honours the iTunes-corrected duration.**
|
|
102
|
+
Previously the deadline keyed off `track.duration_ms` so a
|
|
103
|
+
7:21-playlist-total-on-a-2:11-song lie made dangling-tab cleanup
|
|
104
|
+
fire 5 minutes too late. New optional `effective_duration_ms`
|
|
105
|
+
parameter; the daemon passes the same value the RPC payload
|
|
106
|
+
uses.
|
|
107
|
+
- **Snap and Flatpak Discord builds reachable out of the box.**
|
|
108
|
+
Their IPC socket lives inside the sandbox tree
|
|
109
|
+
(`$XDG_RUNTIME_DIR/app/com.discordapp.Discord/`,
|
|
110
|
+
`~/.var/app/com.discordapp.Discord/config/discord/`,
|
|
111
|
+
`~/snap/discord/current/.config/discord/`), so pypresence's stock
|
|
112
|
+
discovery never finds it. `_bridge_sandboxed_ipc_socket` symlinks
|
|
113
|
+
the first sandbox socket it finds into `$XDG_RUNTIME_DIR` before
|
|
114
|
+
each connect attempt, and sweeps stale symlinks left behind when
|
|
115
|
+
the sandbox path's target is removed (Snap uninstall, Flatpak
|
|
116
|
+
remove, host reboot).
|
|
117
|
+
- **Config drops unknown TOML keys instead of nuking the whole
|
|
118
|
+
file.** A single typo or a key written by a *newer* Refrain that
|
|
119
|
+
the user has since downgraded from used to make
|
|
120
|
+
`Config.from_dict` raise `TypeError`, caught by the surrounding
|
|
121
|
+
`except` and falling back to defaults for *every* setting.
|
|
122
|
+
`_construct()` now filters the payload through dataclass fields
|
|
123
|
+
before `**`-splatting; the offending keys get a single WARNING in
|
|
124
|
+
the log naming the section, the rest of the section survives.
|
|
125
|
+
- **Stale comments + doc references cleaned up.** Four "1 Hz" call-
|
|
126
|
+
outs in `mpris.py` / `daemon.py` (default has been 500 ms since
|
|
127
|
+
v0.2.2), plus one reference to the AppImage runtime that no
|
|
128
|
+
longer matches the AppRun layout.
|
|
129
|
+
|
|
130
|
+
### Tests
|
|
131
|
+
|
|
132
|
+
- **+13 unit tests** covering the new helpers — `DiscordRPC` payload
|
|
133
|
+
dedup (4), `_bridge_sandboxed_ipc_socket` (4), `compute_idle_state`
|
|
134
|
+
with `effective_duration_ms` (2), `cleanup_orphan_downloads` (3).
|
|
135
|
+
Total: 113 → 125, all green, ruff + bandit + pip-audit clean,
|
|
136
|
+
Dependabot 0 outstanding alerts.
|
|
137
|
+
|
|
138
|
+
## [0.2.3] - 2026-05-07
|
|
139
|
+
|
|
140
|
+
A reliability + portability pass driven by hands-on testing across
|
|
141
|
+
Ubuntu 25.04, CentOS Stream 10, and the existing CachyOS daily driver.
|
|
142
|
+
No breaking changes — same config, same UI, same Python floor; the
|
|
143
|
+
release just makes Refrain behave correctly on more distros and tightens
|
|
144
|
+
a handful of long-standing rough edges.
|
|
145
|
+
|
|
146
|
+
### Added
|
|
147
|
+
|
|
148
|
+
- **Sandboxed Discord IPC bridge** — Snap and Flatpak Discord builds
|
|
149
|
+
publish their `discord-ipc-N` socket inside the sandbox tree instead
|
|
150
|
+
of `$XDG_RUNTIME_DIR`, so pypresence's stock discovery never finds it.
|
|
151
|
+
`_bridge_sandboxed_ipc_socket` probes the three known sandbox paths
|
|
152
|
+
(Flatpak instance dir, Flatpak config-dir layout, Snap path) and
|
|
153
|
+
symlinks the first hit into `$XDG_RUNTIME_DIR` before each connect
|
|
154
|
+
attempt. The welcome-dialog probe walks the same paths so first-run
|
|
155
|
+
diagnostics turn green on those installs.
|
|
156
|
+
- **Cancel button in the update dialog** — `_apply_appimage` now
|
|
157
|
+
reads in 64 KiB chunks and polls a cancellation callback between
|
|
158
|
+
blocks. The dialog repurposes its "Later" button as "Cancel" while a
|
|
159
|
+
download is running, treats window-close as cancel, and removes the
|
|
160
|
+
partial `.AppImage.new` on abort. `UpdateResult.cancelled` lets the
|
|
161
|
+
dialog suppress the failure popup for user-initiated aborts.
|
|
162
|
+
- **Orphan-download self-heal** — `cleanup_orphan_downloads` runs once
|
|
163
|
+
at startup so a `*.AppImage.new` left behind by a SIGKILL or power
|
|
164
|
+
loss mid-download doesn't sit next to the binary forever.
|
|
165
|
+
- **Browser hints expanded** — Floorp, Waterfox, Mullvad Browser, Tor
|
|
166
|
+
Browser, and ungoogled-chromium are now in `DEFAULT_BROWSER_HINTS`
|
|
167
|
+
and surface as Settings checkboxes. Existing configs keep their
|
|
168
|
+
saved list (no auto-migration); the new entries appear unticked
|
|
169
|
+
until the user toggles them.
|
|
170
|
+
- **iTunes-catalog duration as MPRIS override** — when MPRIS reports
|
|
171
|
+
an obviously-wrong `mpris:length` (Apple Music's preview-clip 14 s,
|
|
172
|
+
the playlist-total instead of the track length, or a stale value
|
|
173
|
+
carried over from the previous track), `pick_effective_duration_ms`
|
|
174
|
+
prefers the iTunes value if the two disagree by more than 15 % or
|
|
175
|
+
if MPRIS sits in the preview-clip band on a song iTunes considers
|
|
176
|
+
full-length. 8 new unit tests in `test_timing.py`. Discord no
|
|
177
|
+
longer briefly flips a 2:11 song's total to 0:14 / 7:21 mid-track.
|
|
178
|
+
- **`docs/test-matrix.md`** — eight Tier-1 distros that together
|
|
179
|
+
cover ≈ 95 % of the realistic user base, plus Tier-2 spot-checks,
|
|
180
|
+
Tier-3 desktop / compositor combinations, and a six-step smoke
|
|
181
|
+
check ("tray + theme parity", "settings round-trip", "MPRIS to
|
|
182
|
+
Discord", "Bluetooth", "update-dialog cleanup", "restart cycle")
|
|
183
|
+
per row.
|
|
184
|
+
|
|
185
|
+
### Changed
|
|
186
|
+
|
|
187
|
+
- **Qt style plugin path augmentation also walks Debian / Ubuntu and
|
|
188
|
+
Fedora / RHEL / openSUSE layouts** — the v0.2.2 fix only checked
|
|
189
|
+
`/usr/lib/qt6/plugins` (Arch). pip / pipx installs on those distros
|
|
190
|
+
now also pick up the system Breeze plugin instead of falling back
|
|
191
|
+
to Fusion. Detection picks the first existing path among
|
|
192
|
+
`/usr/lib/<arch-triple>/qt6/plugins`, `/usr/lib64/qt6/plugins`,
|
|
193
|
+
`/usr/lib/qt6/plugins`.
|
|
194
|
+
- **"No system tray" error** rewritten to cover GNOME, MATE, XFCE,
|
|
195
|
+
Hyprland / Sway / i3 / river, plus a "should work, try logging
|
|
196
|
+
out" hint for Plasma / Cinnamon / LXQt / Budgie. Previously the
|
|
197
|
+
fix advice was GNOME-AppIndicator-only, leaving everyone else to
|
|
198
|
+
guess.
|
|
199
|
+
- **`refrain --install-desktop`** rewrites the installed
|
|
200
|
+
`Exec=` line to point at the actual launcher path of the running
|
|
201
|
+
install. Previously the bundled `Exec=refrain` only worked when
|
|
202
|
+
`refrain` was on `$PATH` (distro packages, pipx); AppImage and
|
|
203
|
+
source-checkout users got a broken menu entry. Resolution order
|
|
204
|
+
matches the autostart logic — extracted into the new
|
|
205
|
+
`autostart.resolve_exec_line(extra_args)` helper.
|
|
206
|
+
- **MPRIS source duration handling decouples drift-resync from the
|
|
207
|
+
payload's preview-clip flag** — the drift-skip still keys off the
|
|
208
|
+
raw MPRIS-reported duration (because the position-field looping is
|
|
209
|
+
a property of MPRIS' preview-clip mode), but the Discord activity
|
|
210
|
+
payload's start/end fields key off the *effective* duration so a
|
|
211
|
+
brief MPRIS preview-clip glitch on a full-length song doesn't kill
|
|
212
|
+
the progress bar.
|
|
213
|
+
- **PyGObject "not installed" warning fires once per startup**
|
|
214
|
+
instead of twice (eager init from `app.py` plus lazy fallback in
|
|
215
|
+
`MPRISServer.start` both used to log). The install hint also lists
|
|
216
|
+
the Fedora / RHEL / openSUSE package name (`python3-gobject`)
|
|
217
|
+
alongside the Arch and Debian ones.
|
|
218
|
+
|
|
219
|
+
### Fixed
|
|
220
|
+
|
|
221
|
+
- **Settings / Log / Update windows now carry the Refrain icon
|
|
222
|
+
explicitly** — on GNOME Wayland the title-based heuristic was
|
|
223
|
+
matching "Settings" against `org.gnome.Settings` and rendering the
|
|
224
|
+
gnome-control-center gear icon in the dock. The WelcomeDialog
|
|
225
|
+
already set its own icon; the other three top-level windows did
|
|
226
|
+
not.
|
|
227
|
+
- **Bluetooth fast-fail when `org.bluez` is not present** — every
|
|
228
|
+
poll cycle on a VM / minimal install previously triggered a
|
|
229
|
+
`service_start_timeout=25000ms` D-Bus activation timeout, blocking
|
|
230
|
+
the worker thread for the full 25 s. `NameHasOwner` returns
|
|
231
|
+
instantly without triggering activation; the three entry points
|
|
232
|
+
(`read`, `_call_method`, `list_paired_devices`) now short-circuit
|
|
233
|
+
when the owner check returns False.
|
|
234
|
+
- **Logging gaps closed in six places** — `setup_logging` runs
|
|
235
|
+
before `Config.load` so the "created default config" /
|
|
236
|
+
"config unreadable" messages reach the file log; toggling the log
|
|
237
|
+
level in Settings now applies to the running root logger via
|
|
238
|
+
`_apply_log_level` on `settings.applied`; the "no system tray"
|
|
239
|
+
early-exit gets a `log.error()`; `os.execvp`-based restarts call
|
|
240
|
+
`logging.shutdown()` first so the file handler's last buffered
|
|
241
|
+
line isn't lost; `Config.load` exception path adds `exc_info=True`;
|
|
242
|
+
`setup_logging` degrades gracefully when the XDG state dir or log
|
|
243
|
+
file can't be opened (console handler always attaches first).
|
|
244
|
+
- **Catch-all exception branches in `DiscordRPC._ensure_connected`,
|
|
245
|
+
`MPRISSource._call_method_on`, and `BluetoothSource._call_method`**
|
|
246
|
+
now use `log.exception()` so the traceback lands in `refrain.log`
|
|
247
|
+
for unexpected errors. Previously they only logged `str(exc)`,
|
|
248
|
+
losing the stack exactly when it would matter most.
|
|
249
|
+
- **Qt-internal log records routed through `refrain.qt`** instead
|
|
250
|
+
of the bare `qt` logger, so they parent under the project
|
|
251
|
+
namespace and show up alongside everything else when the user
|
|
252
|
+
filters by name.
|
|
253
|
+
|
|
10
254
|
## [0.2.2] - 2026-05-06
|
|
11
255
|
|
|
12
256
|
Two roadmap features pulled forward from v0.3 plus a substantial batch
|
|
@@ -568,7 +812,14 @@ with a proper, installable Linux app.
|
|
|
568
812
|
pip-audit, trufflehog, release), Dependabot, issue + PR templates,
|
|
569
813
|
`CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`.
|
|
570
814
|
|
|
571
|
-
[Unreleased]: https://github.com/Rockykln/refrain/compare/v0.
|
|
815
|
+
[Unreleased]: https://github.com/Rockykln/refrain/compare/v0.2.3...HEAD
|
|
816
|
+
[0.2.3]: https://github.com/Rockykln/refrain/compare/v0.2.2...v0.2.3
|
|
817
|
+
[0.2.2]: https://github.com/Rockykln/refrain/compare/v0.2.1...v0.2.2
|
|
818
|
+
[0.2.1]: https://github.com/Rockykln/refrain/compare/v0.2.0...v0.2.1
|
|
819
|
+
[0.2.0]: https://github.com/Rockykln/refrain/compare/v0.1.5...v0.2.0
|
|
820
|
+
[0.1.5]: https://github.com/Rockykln/refrain/compare/v0.1.4...v0.1.5
|
|
821
|
+
[0.1.4]: https://github.com/Rockykln/refrain/compare/v0.1.3...v0.1.4
|
|
822
|
+
[0.1.3]: https://github.com/Rockykln/refrain/compare/v0.1.2...v0.1.3
|
|
572
823
|
[0.1.2]: https://github.com/Rockykln/refrain/compare/v0.1.1...v0.1.2
|
|
573
824
|
[0.1.1]: https://github.com/Rockykln/refrain/compare/v0.1.0...v0.1.1
|
|
574
825
|
[0.1.0]: https://github.com/Rockykln/refrain/releases/tag/v0.1.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: refrain
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Discord Rich Presence for Apple Music on Linux
|
|
5
5
|
Project-URL: Homepage, https://github.com/Rockykln/refrain
|
|
6
6
|
Project-URL: Repository, https://github.com/Rockykln/refrain
|
|
@@ -134,9 +134,10 @@ and `~/.local/share/icons/`. To undo: `refrain --uninstall-desktop`.
|
|
|
134
134
|
|
|
135
135
|
### Tested on
|
|
136
136
|
|
|
137
|
-
-
|
|
138
|
-
|
|
139
|
-
-
|
|
137
|
+
See [`docs/test-matrix.md`](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 (Python or glibc floor too low).
|
|
138
|
+
|
|
139
|
+
- **Tier 1 (must pass every release):** CachyOS, Arch Linux, Fedora 42, Ubuntu 24.04 LTS, Debian 13, openSUSE Tumbleweed, Linux Mint 22, Manjaro Stable.
|
|
140
|
+
- **Desktops:** KDE Plasma 6 (Wayland is the primary target, X11 also covered), GNOME with the [AppIndicator and KStatusNotifierItem](https://extensions.gnome.org/extension/615/appindicator-support/) 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.
|
|
140
141
|
|
|
141
142
|
## First-time setup
|
|
142
143
|
|
|
@@ -318,6 +319,8 @@ tray + controls running.
|
|
|
318
319
|
|
|
319
320
|
- [Architecture overview](docs/architecture.md) — threads, D-Bus surface, file paths
|
|
320
321
|
- [FAQ](docs/faq.md)
|
|
322
|
+
- [Bluetooth quick-start](docs/bluetooth.md) — pair + AVRCP setup walkthrough
|
|
323
|
+
- [Test matrix](docs/test-matrix.md) — supported distros, smoke-check checklist
|
|
321
324
|
- [Roadmap](ROADMAP.md)
|
|
322
325
|
- [Changelog](CHANGELOG.md)
|
|
323
326
|
- [Contributing](CONTRIBUTING.md) — dev setup, testing, code style
|
|
@@ -100,9 +100,10 @@ and `~/.local/share/icons/`. To undo: `refrain --uninstall-desktop`.
|
|
|
100
100
|
|
|
101
101
|
### Tested on
|
|
102
102
|
|
|
103
|
-
-
|
|
104
|
-
|
|
105
|
-
-
|
|
103
|
+
See [`docs/test-matrix.md`](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 (Python or glibc floor too low).
|
|
104
|
+
|
|
105
|
+
- **Tier 1 (must pass every release):** CachyOS, Arch Linux, Fedora 42, Ubuntu 24.04 LTS, Debian 13, openSUSE Tumbleweed, Linux Mint 22, Manjaro Stable.
|
|
106
|
+
- **Desktops:** KDE Plasma 6 (Wayland is the primary target, X11 also covered), GNOME with the [AppIndicator and KStatusNotifierItem](https://extensions.gnome.org/extension/615/appindicator-support/) 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.
|
|
106
107
|
|
|
107
108
|
## First-time setup
|
|
108
109
|
|
|
@@ -284,6 +285,8 @@ tray + controls running.
|
|
|
284
285
|
|
|
285
286
|
- [Architecture overview](docs/architecture.md) — threads, D-Bus surface, file paths
|
|
286
287
|
- [FAQ](docs/faq.md)
|
|
288
|
+
- [Bluetooth quick-start](docs/bluetooth.md) — pair + AVRCP setup walkthrough
|
|
289
|
+
- [Test matrix](docs/test-matrix.md) — supported distros, smoke-check checklist
|
|
287
290
|
- [Roadmap](ROADMAP.md)
|
|
288
291
|
- [Changelog](CHANGELOG.md)
|
|
289
292
|
- [Contributing](CONTRIBUTING.md) — dev setup, testing, code style
|
|
@@ -169,6 +169,120 @@ What's done, what's next, what's deliberately not in scope.
|
|
|
169
169
|
in the wheel**, **MPRIS dispatch logging at INFO** so
|
|
170
170
|
Next/Previous routing is visible in the live log.
|
|
171
171
|
|
|
172
|
+
## Done — v0.2.3
|
|
173
|
+
|
|
174
|
+
- **Distro-portability sweep** driven by hands-on testing.
|
|
175
|
+
- Qt style plugin path augmentation extended to Debian / Ubuntu
|
|
176
|
+
(`/usr/lib/<arch-triple>/qt6/plugins`) and Fedora / RHEL / openSUSE
|
|
177
|
+
(`/usr/lib64/qt6/plugins`); v0.2.2 only handled the Arch layout.
|
|
178
|
+
- Snap and Flatpak Discord builds now reachable out of the box —
|
|
179
|
+
`_bridge_sandboxed_ipc_socket` symlinks the sandboxed
|
|
180
|
+
`discord-ipc-N` into `$XDG_RUNTIME_DIR` before each pypresence
|
|
181
|
+
connect attempt.
|
|
182
|
+
- Bluetooth fast-fail via `NameHasOwner('org.bluez')` so VMs and
|
|
183
|
+
minimal installs without bluez no longer block the daemon's
|
|
184
|
+
poll cycle for 25 s waiting on D-Bus service activation.
|
|
185
|
+
- "No system tray" error covers GNOME, MATE, XFCE, and the
|
|
186
|
+
tiling-WM crowd (Hyprland / Sway / i3 / river); was
|
|
187
|
+
GNOME-AppIndicator-only.
|
|
188
|
+
- `--install-desktop` rewrites `Exec=` in the installed `.desktop`
|
|
189
|
+
to match the running install (AppImage path / venv shim / system
|
|
190
|
+
binary), so AppImage and source-checkout users get a working
|
|
191
|
+
menu entry.
|
|
192
|
+
- Browser hints expanded to cover Floorp, Waterfox, Mullvad
|
|
193
|
+
Browser, Tor Browser, ungoogled-chromium.
|
|
194
|
+
- **iTunes-catalog duration as MPRIS override** —
|
|
195
|
+
`pick_effective_duration_ms` chooses iTunes when MPRIS and iTunes
|
|
196
|
+
disagree by more than 15 % or when MPRIS sits in the preview-clip
|
|
197
|
+
band on a song iTunes considers full-length. Apple Music's MPRIS
|
|
198
|
+
surface no longer makes Discord briefly flip a 2:11 song's total
|
|
199
|
+
to 0:14 / 7:21 mid-track.
|
|
200
|
+
- **Update-dialog Cancel + orphan-download self-heal** — `Cancel`
|
|
201
|
+
button + window-close handler abort an in-flight AppImage
|
|
202
|
+
download cleanly and remove the partial `.new`. A separate
|
|
203
|
+
`cleanup_orphan_downloads` runs at startup so a SIGKILL or power
|
|
204
|
+
loss mid-download doesn't leave a stale `*.AppImage.new` next to
|
|
205
|
+
the binary forever.
|
|
206
|
+
- **Per-window icons** on Settings / Log / Update dialogs so GNOME
|
|
207
|
+
Shell's title-based heuristic on Wayland doesn't render them with
|
|
208
|
+
the gnome-control-center gear icon in the dock.
|
|
209
|
+
- **Logging audit** closed six gaps: `setup_logging` runs before
|
|
210
|
+
`Config.load` so config-load messages reach the file log; the
|
|
211
|
+
log-level toggle in Settings applies live; `setup_logging` degrades
|
|
212
|
+
gracefully when the XDG state dir is unwritable; `os.execvp`-based
|
|
213
|
+
restarts flush log handlers first; catch-all exception branches in
|
|
214
|
+
`DiscordRPC._ensure_connected`, `MPRISSource._call_method_on` and
|
|
215
|
+
`BluetoothSource._call_method` switched to `log.exception()` so
|
|
216
|
+
tracebacks land in `refrain.log`.
|
|
217
|
+
- **`docs/test-matrix.md`** — eight Tier-1 distros, Tier-2 spot-check
|
|
218
|
+
list, Tier-3 desktop / compositor sweep, six-step smoke checks,
|
|
219
|
+
out-of-scope reasons for the floor of unsupported distros (RHEL 9
|
|
220
|
+
/ Rocky 9 / Alma 9 / Debian 11 / Ubuntu 22.04 / Alpine).
|
|
221
|
+
|
|
222
|
+
## Done — v0.2.4
|
|
223
|
+
|
|
224
|
+
- **Time-display consistency across tray + Plasma panel + Discord**.
|
|
225
|
+
v0.2.3 already fixed Discord; the tray's progress label and the
|
|
226
|
+
published MPRIS Metadata still keyed off the raw `mpris:length`
|
|
227
|
+
and showed inconsistent numbers when MPRIS briefly lied about a
|
|
228
|
+
song's duration. `pick_effective_duration_ms` is now hoisted into
|
|
229
|
+
`_dispatch` so all three surfaces see the same iTunes-corrected
|
|
230
|
+
value every tick. Plus position is clamped to duration so a
|
|
231
|
+
"2:30 / 0:14 (-0:00)" line can't render during a brief MPRIS
|
|
232
|
+
preview-clip glitch.
|
|
233
|
+
- **Snap and Flatpak Discord builds reachable out of the box**.
|
|
234
|
+
Their IPC socket lives inside the sandbox tree
|
|
235
|
+
(`xdg-run/app/com.discordapp.Discord/`,
|
|
236
|
+
`~/.var/app/com.discordapp.Discord/.../`,
|
|
237
|
+
`~/snap/discord/current/.config/discord/`) which pypresence
|
|
238
|
+
doesn't probe. `_bridge_sandboxed_ipc_socket` symlinks the first
|
|
239
|
+
sandbox socket it finds into `$XDG_RUNTIME_DIR` before each
|
|
240
|
+
connect attempt + sweeps stale symlinks left behind when the
|
|
241
|
+
Discord install is removed.
|
|
242
|
+
- **Inline release notes in Settings → Updates tab**. The tab
|
|
243
|
+
carries a QTextBrowser that renders the same Markdown as the
|
|
244
|
+
popup, plus "Current version" + "Latest known" labels.
|
|
245
|
+
`releaseInfoFetched(release | None)` signal fires after every
|
|
246
|
+
check (auto / manual, success / failure) so the tab refreshes
|
|
247
|
+
regardless of the result.
|
|
248
|
+
- **Discord activity card always shows the Refrain brand badge as
|
|
249
|
+
small_image**. Previously the badge was only in `large_image` as
|
|
250
|
+
a fallback when cover-art lookup failed; now it's the small-icon
|
|
251
|
+
corner of every payload, so the cover gets the visual focus and
|
|
252
|
+
hovering reveals "Refrain" as the source app. Plus the
|
|
253
|
+
cover-wait defer is back to 3 polls (~1.5 s at 500 ms tick) so
|
|
254
|
+
Discord usually goes straight from "no activity" to "cover" with
|
|
255
|
+
no brand flash on the way.
|
|
256
|
+
- **Config drops unknown TOML keys** instead of nuking the whole
|
|
257
|
+
file. A typo or a key written by a newer Refrain that the user
|
|
258
|
+
has since downgraded from used to make `Config.from_dict` raise,
|
|
259
|
+
caught by the surrounding except, and fall back to defaults for
|
|
260
|
+
every setting. Now the offending key gets a single warning, the
|
|
261
|
+
rest of the section survives.
|
|
262
|
+
- **DiscordRPC dedupes identical consecutive payloads**. The
|
|
263
|
+
daemon ticks every 500 ms but Discord rate-limits presence
|
|
264
|
+
updates to 5/20 s — most ticks were silently dropped on the
|
|
265
|
+
Discord side anyway. Now the second-and-on identical payload
|
|
266
|
+
is a no-op on our side too.
|
|
267
|
+
- **Browser hints expanded**: Floorp, Waterfox, Mullvad Browser,
|
|
268
|
+
Tor Browser, ungoogled-chromium.
|
|
269
|
+
- **`docs/bluetooth.md`** — first-time-setup walkthrough covering
|
|
270
|
+
bluez install per distro, pairing recipe, AVRCP verification,
|
|
271
|
+
per-source Discord profile setup, and Troubleshooting.
|
|
272
|
+
- **FAQ entries** for "Refrain isn't picking up my browser"
|
|
273
|
+
(covers `playerctl` diagnostic + Snap-confined-browser workaround
|
|
274
|
+
+ Firefox `about:config` toggle) and "How do I add a browser
|
|
275
|
+
that isn't in the list".
|
|
276
|
+
- **`docs/architecture.md` refreshed** to v0.2.x reality — diagram
|
|
277
|
+
no longer claims "1 Hz tick" (default has been 500 ms since
|
|
278
|
+
v0.2.2), `MPRISServer` block added, `org.mpris.MediaPlayer2.refrain`
|
|
279
|
+
publish documented, GLib thread for dbus-python signal dispatch
|
|
280
|
+
documented, new section on Discord IPC sandbox bridging.
|
|
281
|
+
- **+13 unit tests**: `DiscordRPC` payload dedup (4),
|
|
282
|
+
`_bridge_sandboxed_ipc_socket` (4), `compute_idle_state` with
|
|
283
|
+
`effective_duration_ms` (2), `cleanup_orphan_downloads` (3).
|
|
284
|
+
Total: 113 → 125, all green.
|
|
285
|
+
|
|
172
286
|
## Up next — v0.3
|
|
173
287
|
|
|
174
288
|
- **Polishing the wrapped i18n surface** — wrap the remaining
|
|
@@ -11,26 +11,33 @@ Refrain is one process with two threads and one external IPC socket.
|
|
|
11
11
|
│ ├─ TrayIcon (QSystemTrayIcon) ←─── status / track │
|
|
12
12
|
│ ├─ SettingsWindow (QDialog, hidden after Apply) │
|
|
13
13
|
│ ├─ LogWindow (QDialog, on-demand) │
|
|
14
|
+
│ ├─ WelcomeDialog (first-run only) │
|
|
14
15
|
│ ├─ UpdateOrchestrator │
|
|
15
16
|
│ └─ UpdateDialog │
|
|
16
17
|
│ │
|
|
17
18
|
│ Worker thread (Qt event loop, QThread) │
|
|
18
19
|
│ ├─ DaemonWorker │
|
|
19
|
-
│ │ ├─ MPRISSource ──► Session DBus
|
|
20
|
+
│ │ ├─ MPRISSource ──► Session DBus (read) │
|
|
20
21
|
│ │ ├─ BluetoothSource ──► System DBus (org.bluez) │
|
|
21
22
|
│ │ ├─ CoverFetcher ──► iTunes Search (HTTPS) │
|
|
22
|
-
│ │
|
|
23
|
-
│ └─
|
|
23
|
+
│ │ ├─ DiscordRPC ──► Discord IPC socket │
|
|
24
|
+
│ │ └─ MPRISServer ──► Session DBus (publish own) │
|
|
25
|
+
│ └─ Timer (QTimer, 500 ms default poll, configurable) │
|
|
26
|
+
│ │
|
|
27
|
+
│ GLib thread (only when PyGObject is available) │
|
|
28
|
+
│ └─ GLib.MainLoop — pumps dbus-python signals so Plasma's panel │
|
|
29
|
+
│ PlayPause / Next / Previous reach our MPRISServer methods. │
|
|
24
30
|
└─────────────────────────────────────────────────────────────────┘
|
|
25
31
|
```
|
|
26
32
|
|
|
27
33
|
## Why a worker thread?
|
|
28
34
|
|
|
29
|
-
The polling loop (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
The polling loop (default 500 ms, configurable via
|
|
36
|
+
`advanced.poll_interval_ms`) reads MPRIS / BlueZ properties via D-Bus.
|
|
37
|
+
Some of those calls block briefly. iTunes Search lookups can take up
|
|
38
|
+
to 5 s. Discord IPC writes can stall when Discord is restarting.
|
|
39
|
+
Doing any of that on the GUI thread would make the tray icon and
|
|
40
|
+
settings window freeze every poll.
|
|
34
41
|
|
|
35
42
|
The worker thread runs a Qt event loop (driven by `QTimer`, *not* a
|
|
36
43
|
`while/sleep` loop — that detail matters: a sleep loop blocks the event
|
|
@@ -84,6 +91,30 @@ to `/usr/share/applications/refrain.desktop` instead.
|
|
|
84
91
|
| `org.mpris.MediaPlayer2.Player` | session| Track metadata + Play/Next/Prev |
|
|
85
92
|
| `org.bluez.MediaPlayer1` | system | AVRCP track + Play/Pause/Next/Prev|
|
|
86
93
|
| `org.bluez.Device1` (via ObjectManager) | system | Paired-device enumeration |
|
|
94
|
+
| `org.freedesktop.DBus.NameHasOwner` | system | Fast-fail check before activating `org.bluez` (avoids a 25 s service-activation timeout on hosts without bluez)|
|
|
95
|
+
|
|
96
|
+
## D-Bus interfaces published
|
|
97
|
+
|
|
98
|
+
Refrain publishes two well-known names on the **session** bus:
|
|
99
|
+
|
|
100
|
+
| Bus name | Purpose |
|
|
101
|
+
|---------------------------------------|------------------------------------------------|
|
|
102
|
+
| `io.github.Rockykln.Refrain` | Single-instance lock. No object path; just the name reservation. |
|
|
103
|
+
| `org.mpris.MediaPlayer2.refrain` | Refrain itself as an MPRIS player. KDE Plasma's panel media controls applet, KDE Connect, GNOME Shell etc. drive the same Play/Pause/Next/Previous as the tray, and render the same track Discord renders. Implements the standard MPRIS root + Player interfaces. |
|
|
104
|
+
|
|
105
|
+
The MPRIS-server publication needs PyGObject (`gi`) for a GLib main loop
|
|
106
|
+
to pump dbus-python signal dispatch. When PyGObject isn't installed,
|
|
107
|
+
Refrain logs a warning at startup and falls back to read-only mode —
|
|
108
|
+
the rest of the app works, but Plasma's panel can't drive playback.
|
|
109
|
+
|
|
110
|
+
## Discord IPC discovery
|
|
87
111
|
|
|
88
|
-
|
|
89
|
-
|
|
112
|
+
The standard path is `$XDG_RUNTIME_DIR/discord-ipc-N` (N=0..9). Snap
|
|
113
|
+
and Flatpak Discord builds put their socket inside the sandbox tree
|
|
114
|
+
instead (`$XDG_RUNTIME_DIR/app/com.discordapp.Discord/discord-ipc-N`,
|
|
115
|
+
`~/.var/app/com.discordapp.Discord/config/discord/discord-ipc-N`,
|
|
116
|
+
`~/snap/discord/current/.config/discord/discord-ipc-N`).
|
|
117
|
+
`DiscordRPC._bridge_sandboxed_ipc_socket` symlinks the first sandbox
|
|
118
|
+
socket it finds into `$XDG_RUNTIME_DIR` before each connect attempt,
|
|
119
|
+
and sweeps stale symlinks left behind by previously-uninstalled
|
|
120
|
+
Discord builds.
|