refrain 0.2.3__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.3 → refrain-0.2.4}/CHANGELOG.md +128 -0
- {refrain-0.2.3 → refrain-0.2.4}/PKG-INFO +3 -1
- {refrain-0.2.3 → refrain-0.2.4}/README.md +2 -0
- {refrain-0.2.3 → refrain-0.2.4}/ROADMAP.md +64 -0
- {refrain-0.2.3 → refrain-0.2.4}/docs/architecture.md +41 -10
- refrain-0.2.4/docs/bluetooth.md +136 -0
- {refrain-0.2.3 → 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.3 → refrain-0.2.4}/packaging/appimage/AppImageBuilder.yml +1 -1
- {refrain-0.2.3 → refrain-0.2.4}/packaging/aur/refrain/.SRCINFO +4 -3
- {refrain-0.2.3 → refrain-0.2.4}/packaging/aur/refrain/PKGBUILD +1 -4
- {refrain-0.2.3 → refrain-0.2.4}/packaging/flatpak/io.github.Rockykln.Refrain.metainfo.xml +22 -0
- {refrain-0.2.3 → refrain-0.2.4}/pyproject.toml +1 -1
- refrain-0.2.4/src/refrain/__init__.py +1 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/app.py +46 -20
- refrain-0.2.4/src/refrain/assets/icons/menu-update.svg +3 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/config.py +33 -6
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/daemon.py +87 -42
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/discord_rpc.py +40 -3
- refrain-0.2.4/src/refrain/i18n/refrain_de.qm +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/i18n/refrain_de.ts +274 -84
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/sources/mpris.py +5 -5
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/sources/mpris_server.py +37 -8
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/ui/log_window.py +5 -5
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/ui/settings_window.py +55 -1
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/ui/tray.py +20 -4
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/ui/update_dialog.py +19 -18
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/updater.py +13 -4
- {refrain-0.2.3 → refrain-0.2.4}/tests/test_config.py +24 -0
- {refrain-0.2.3 → 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.3 → refrain-0.2.4}/tests/test_updater.py +38 -0
- refrain-0.2.3/docs/screenshots/live-log.png +0 -0
- refrain-0.2.3/docs/screenshots/settings-advanced.png +0 -0
- refrain-0.2.3/docs/screenshots/settings-general.png +0 -0
- refrain-0.2.3/docs/screenshots/settings-sources.png +0 -0
- refrain-0.2.3/docs/screenshots/settings-updates.png +0 -0
- refrain-0.2.3/docs/screenshots/update-dialog.png +0 -0
- refrain-0.2.3/docs/screenshots/welcome.png +0 -0
- refrain-0.2.3/src/refrain/__init__.py +0 -1
- refrain-0.2.3/src/refrain/i18n/refrain_de.qm +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/.editorconfig +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/.gitattributes +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/.github/FUNDING.yml +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/.github/codeql/codeql-config.yml +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/.github/dependabot.yml +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/.github/pull_request_template.md +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/.github/workflows/codeql.yml +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/.github/workflows/release.yml +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/.github/workflows/security.yml +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/.github/workflows/tests.yml +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/.gitignore +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/CODE_OF_CONDUCT.md +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/CONTRIBUTING.md +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/LICENSE +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/Makefile +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/SECURITY.md +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/docs/screenshots/README.md +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/docs/screenshots/discord-rpc.png +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/docs/screenshots/notification.png +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/docs/screenshots/tray-menu.png +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/docs/test-matrix.md +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/packaging/README.md +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/packaging/aur/refrain-git/PKGBUILD +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/packaging/flatpak/io.github.Rockykln.Refrain.desktop +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/packaging/flatpak/io.github.Rockykln.Refrain.yaml +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/packaging/flatpak/python-deps.json +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/requirements-dev.txt +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/requirements.txt +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/__main__.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/assets/icons/github-mark.svg +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/assets/icons/refrain.png +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/assets/icons/refrain.svg +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/assets/icons/tray-paused-dark.svg +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/assets/icons/tray-paused.svg +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/assets/icons/tray-playing-dark.svg +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/assets/icons/tray-playing.svg +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/assets/icons/tray-stopped-dark.svg +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/assets/icons/tray-stopped.svg +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/assets/refrain.desktop +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/autostart.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/cover_art.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/cover_fetcher.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/logging_setup.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/paths.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/single_instance.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/sources/__init__.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/sources/base.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/sources/bluetooth.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/timing.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/ui/__init__.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/src/refrain/ui/welcome_dialog.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/tests/__init__.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/tests/conftest.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/tests/test_autostart.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/tests/test_cover_art.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/tests/test_cover_fetcher.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/tests/test_daemon_album_format.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/tests/test_discord_listening.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/tests/test_imports.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/tests/test_mpris_dispatch.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/tests/test_paths.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/tests/test_sources_base.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/tests/test_timing.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/tests/test_tray_init.py +0 -0
- {refrain-0.2.3 → refrain-0.2.4}/tests/test_update_dialog_body.py +0 -0
|
@@ -7,6 +7,134 @@ 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
|
+
|
|
10
138
|
## [0.2.3] - 2026-05-07
|
|
11
139
|
|
|
12
140
|
A reliability + portability pass driven by hands-on testing across
|
|
@@ -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
|
|
@@ -319,6 +319,8 @@ tray + controls running.
|
|
|
319
319
|
|
|
320
320
|
- [Architecture overview](docs/architecture.md) — threads, D-Bus surface, file paths
|
|
321
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
|
|
322
324
|
- [Roadmap](ROADMAP.md)
|
|
323
325
|
- [Changelog](CHANGELOG.md)
|
|
324
326
|
- [Contributing](CONTRIBUTING.md) — dev setup, testing, code style
|
|
@@ -285,6 +285,8 @@ tray + controls running.
|
|
|
285
285
|
|
|
286
286
|
- [Architecture overview](docs/architecture.md) — threads, D-Bus surface, file paths
|
|
287
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
|
|
288
290
|
- [Roadmap](ROADMAP.md)
|
|
289
291
|
- [Changelog](CHANGELOG.md)
|
|
290
292
|
- [Contributing](CONTRIBUTING.md) — dev setup, testing, code style
|
|
@@ -219,6 +219,70 @@ What's done, what's next, what's deliberately not in scope.
|
|
|
219
219
|
out-of-scope reasons for the floor of unsupported distros (RHEL 9
|
|
220
220
|
/ Rocky 9 / Alma 9 / Debian 11 / Ubuntu 22.04 / Alpine).
|
|
221
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
|
+
|
|
222
286
|
## Up next — v0.3
|
|
223
287
|
|
|
224
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.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Bluetooth quick-start
|
|
2
|
+
|
|
3
|
+
Refrain's Bluetooth source reads playback metadata via BlueZ AVRCP —
|
|
4
|
+
the standard Bluetooth audio metadata profile. Anything that pairs as
|
|
5
|
+
an A2DP audio source and exposes `org.bluez.MediaPlayer1` over D-Bus
|
|
6
|
+
will work: iPhones, Android phones, dedicated music players, even
|
|
7
|
+
some car head-units.
|
|
8
|
+
|
|
9
|
+
This guide walks through getting it set up the first time on KDE
|
|
10
|
+
Plasma, GNOME, or any other desktop with `bluez` running. If
|
|
11
|
+
something is missing on your distro, the
|
|
12
|
+
[Troubleshooting](#troubleshooting) section at the end covers the
|
|
13
|
+
usual suspects.
|
|
14
|
+
|
|
15
|
+
## Prerequisites
|
|
16
|
+
|
|
17
|
+
- BlueZ ≥ 5 with the AVRCP profile enabled (default on every modern
|
|
18
|
+
desktop distro).
|
|
19
|
+
- A Bluetooth adapter that's powered on. `bluetoothctl show` should
|
|
20
|
+
list at least one controller.
|
|
21
|
+
- Your desktop's Bluetooth applet running (KDE's
|
|
22
|
+
bluedevil-applet, GNOME's bluetooth-panel, blueman, …). Refrain
|
|
23
|
+
doesn't manage pairing itself — it only reads metadata from
|
|
24
|
+
already-connected devices.
|
|
25
|
+
|
|
26
|
+
If `bluetoothctl` isn't installed, install it via your package
|
|
27
|
+
manager:
|
|
28
|
+
|
|
29
|
+
| Distro | Command |
|
|
30
|
+
|---|---|
|
|
31
|
+
| Arch / CachyOS / Manjaro | `sudo pacman -S bluez bluez-utils` |
|
|
32
|
+
| Debian / Ubuntu / Mint | `sudo apt install bluez bluez-tools` |
|
|
33
|
+
| Fedora / RHEL / Rocky | `sudo dnf install bluez bluez-tools` |
|
|
34
|
+
| openSUSE | `sudo zypper install bluez` |
|
|
35
|
+
|
|
36
|
+
## Pairing
|
|
37
|
+
|
|
38
|
+
1. Put the phone (or other source) into pairing mode. On iOS this is
|
|
39
|
+
*Settings → Bluetooth*; on Android it's *Settings → Connected
|
|
40
|
+
devices*.
|
|
41
|
+
2. Open your desktop's Bluetooth applet, scan for new devices, click
|
|
42
|
+
*Pair* on the phone entry, confirm the matching PIN on both ends.
|
|
43
|
+
3. Enable *Audio*. Some applets surface this as a toggle after
|
|
44
|
+
pairing; others auto-enable it. Verify with:
|
|
45
|
+
```sh
|
|
46
|
+
bluetoothctl info <MAC>
|
|
47
|
+
```
|
|
48
|
+
Look for `UUIDs: ... A/V Remote Control Target ... AudioSource ...`
|
|
49
|
+
in the output. If those are missing, the phone is paired but not
|
|
50
|
+
advertising AVRCP — disconnect, repair, and tick the *Audio
|
|
51
|
+
profile* box this time.
|
|
52
|
+
|
|
53
|
+
## First playback
|
|
54
|
+
|
|
55
|
+
1. Make sure the phone is **connected** (not just paired). The
|
|
56
|
+
applet shows a connected indicator; CLI:
|
|
57
|
+
```sh
|
|
58
|
+
bluetoothctl info <MAC> | grep "Connected:"
|
|
59
|
+
```
|
|
60
|
+
should show `Connected: yes`.
|
|
61
|
+
2. Start music on the phone. Spotify, Apple Music, the iOS Music
|
|
62
|
+
app, anything that publishes track metadata via the AVRCP
|
|
63
|
+
profile.
|
|
64
|
+
3. In Refrain, open *Settings → Sources → Bluetooth*:
|
|
65
|
+
- Toggle **Enable Bluetooth source** on.
|
|
66
|
+
- Pick the device from the dropdown. It should show the phone's
|
|
67
|
+
Bluetooth name (e.g. *Rocky's iPhone*) and its MAC address.
|
|
68
|
+
- Hit **Apply**.
|
|
69
|
+
4. Within ~1 s, the tray menu shows the track title + artist.
|
|
70
|
+
Within ~2 s, Discord renders the listening status.
|
|
71
|
+
|
|
72
|
+
The dropdown's `(auto-detect)` entry picks whichever device is
|
|
73
|
+
currently exposing AVRCP — useful if you switch between phone and
|
|
74
|
+
headphones with the same Refrain config. The MAC-pinned variant is
|
|
75
|
+
stricter but stable when multiple sources are connected at once.
|
|
76
|
+
|
|
77
|
+
## Per-source Discord profile (optional)
|
|
78
|
+
|
|
79
|
+
You can give Bluetooth its own Discord application so the status
|
|
80
|
+
renders with a different icon than the browser's Apple Music
|
|
81
|
+
playback:
|
|
82
|
+
|
|
83
|
+
1. Register a second Discord application at
|
|
84
|
+
<https://discord.com/developers/applications> — call it e.g.
|
|
85
|
+
"Refrain (Bluetooth)" and upload a Bluetooth glyph as the icon.
|
|
86
|
+
2. Copy the new Client ID.
|
|
87
|
+
3. *Settings → General → Bluetooth Client ID* — paste, *Apply*.
|
|
88
|
+
|
|
89
|
+
Refrain reconnects RPC under the per-source ID the moment a track
|
|
90
|
+
arrives from Bluetooth.
|
|
91
|
+
|
|
92
|
+
## Troubleshooting
|
|
93
|
+
|
|
94
|
+
### Refrain says "no track" while music is clearly playing on the phone
|
|
95
|
+
|
|
96
|
+
- Confirm AVRCP is actually working:
|
|
97
|
+
```sh
|
|
98
|
+
busctl --system call org.bluez /org/bluez/hci0/dev_<MAC_with_underscores> \
|
|
99
|
+
org.freedesktop.DBus.Properties Get ss org.bluez.MediaPlayer1 Track
|
|
100
|
+
```
|
|
101
|
+
This should return the current track. If it errors with
|
|
102
|
+
`org.bluez.Error.DoesNotExist`, the phone isn't exposing AVRCP — try
|
|
103
|
+
disconnect / reconnect, and verify the *Audio profile* box on the
|
|
104
|
+
pairing.
|
|
105
|
+
- Check the tray-menu source label or the live log
|
|
106
|
+
(tray → *Live log…*). Look for `Track change [bluetooth]: …` lines.
|
|
107
|
+
If you see `[mpris]` instead, the browser is winning the source
|
|
108
|
+
race — close the music tab so Bluetooth becomes the only candidate.
|
|
109
|
+
|
|
110
|
+
### `bluetoothd` D-Bus activation timeout warning in the log
|
|
111
|
+
|
|
112
|
+
You'll see something like
|
|
113
|
+
`Bluetooth: GetManagedObjects failed: ... service_start_timeout=25000ms`
|
|
114
|
+
on a system without `bluez` installed (typical of VMs and minimal
|
|
115
|
+
installs). v0.2.3+ fast-fails before the activation timeout fires;
|
|
116
|
+
on older builds you'd want to disable the Bluetooth source toggle.
|
|
117
|
+
|
|
118
|
+
### The dropdown is empty
|
|
119
|
+
|
|
120
|
+
That means BlueZ is running but no `org.bluez.Device1` entries exist.
|
|
121
|
+
Pair at least one device first (Section "Pairing" above), then click
|
|
122
|
+
**Apply** in Refrain to refresh the dropdown.
|
|
123
|
+
|
|
124
|
+
### Track shows but Play/Pause/Next/Previous don't work
|
|
125
|
+
|
|
126
|
+
AVRCP control depends on the phone's app supporting
|
|
127
|
+
`AVRCP-CT 1.4` or higher. iOS Music, Apple Music, and Spotify all
|
|
128
|
+
do. Some Android battery-saver settings disable AVRCP control —
|
|
129
|
+
check the per-app battery / background settings on the phone.
|
|
130
|
+
|
|
131
|
+
### Multiple paired devices, wrong one gets picked
|
|
132
|
+
|
|
133
|
+
Set *Settings → Sources → Bluetooth → Device* to the specific MAC.
|
|
134
|
+
The `(auto-detect)` mode picks the first eligible AVRCP player from
|
|
135
|
+
BlueZ's enumeration order, which isn't stable when several phones
|
|
136
|
+
are paired.
|
|
@@ -37,6 +37,50 @@ Install the **AppIndicator and KStatusNotifierItem** GNOME Shell
|
|
|
37
37
|
extension. Refrain's tray uses `QSystemTrayIcon`, which on GNOME requires
|
|
38
38
|
that extension to be visible.
|
|
39
39
|
|
|
40
|
+
## Refrain isn't picking up my browser.
|
|
41
|
+
|
|
42
|
+
Refrain reads track metadata from the browser's MPRIS publication. If
|
|
43
|
+
nothing shows up while Apple Music is playing, run `playerctl -l` in a
|
|
44
|
+
terminal — it lists every MPRIS player on your session bus.
|
|
45
|
+
|
|
46
|
+
- **Empty list (or no Firefox / Chromium entry):** the browser isn't
|
|
47
|
+
publishing MPRIS at all. Common causes:
|
|
48
|
+
- **Firefox**: MPRIS is off by default on some installs. Open
|
|
49
|
+
`about:config` → set `media.hardwaremediakeys.enabled = true` →
|
|
50
|
+
fully quit Firefox (close all windows + wait for `pgrep firefox`
|
|
51
|
+
to be empty) → relaunch.
|
|
52
|
+
- **Snap-confined browsers** (Snap Firefox, Snap Chromium on
|
|
53
|
+
Ubuntu): the snap sandbox blocks D-Bus session-bus access for
|
|
54
|
+
MPRIS. Switch to a deb-channel browser:
|
|
55
|
+
- Mozilla's official Firefox deb:
|
|
56
|
+
<https://support.mozilla.org/en-US/kb/install-firefox-linux>
|
|
57
|
+
- Brave deb: <https://brave.com/linux/>
|
|
58
|
+
- Chromium from Debian/Ubuntu deb (not the Snap).
|
|
59
|
+
- **Flatpak browsers** without `--talk-name=org.mpris.MediaPlayer2.*`:
|
|
60
|
+
Flathub builds usually have it; self-built ones may not.
|
|
61
|
+
- **Browser shows but track isn't picked up:** check that `xesam:url`
|
|
62
|
+
contains `music.apple.com`:
|
|
63
|
+
```sh
|
|
64
|
+
playerctl --player=firefox metadata | grep xesam:url
|
|
65
|
+
```
|
|
66
|
+
If the URL field is empty or points elsewhere, Apple Music's tab
|
|
67
|
+
isn't the active media tab — switch to it and start playback.
|
|
68
|
+
|
|
69
|
+
## How do I add a browser that isn't in the Settings list?
|
|
70
|
+
|
|
71
|
+
*Settings → Sources → Detected browsers → Other (comma-separated)*.
|
|
72
|
+
Enter a substring of the browser's MPRIS bus name. Find it via
|
|
73
|
+
`playerctl -l` while a media tab is playing — e.g. for Floorp the
|
|
74
|
+
substring is `floorp`. Save with *Apply*.
|
|
75
|
+
|
|
76
|
+
## Bluetooth: how do I get my iPhone / phone showing up?
|
|
77
|
+
|
|
78
|
+
See the dedicated walkthrough at [`docs/bluetooth.md`](bluetooth.md).
|
|
79
|
+
Quick version: pair the phone in your desktop's Bluetooth manager,
|
|
80
|
+
connect it, start music on the phone, then in Refrain
|
|
81
|
+
*Settings → Sources → Bluetooth* turn the toggle on and pick the
|
|
82
|
+
device from the dropdown.
|
|
83
|
+
|
|
40
84
|
## Refrain is already running but the settings window won't reopen.
|
|
41
85
|
|
|
42
86
|
Click the tray icon. The settings window is normally hidden, not closed —
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
pkgbase = refrain
|
|
2
2
|
pkgdesc = Discord Rich Presence for Apple Music on Linux
|
|
3
|
-
pkgver = 0.2.
|
|
3
|
+
pkgver = 0.2.3
|
|
4
4
|
pkgrel = 1
|
|
5
5
|
url = https://github.com/Rockykln/refrain
|
|
6
6
|
arch = any
|
|
@@ -13,7 +13,8 @@ pkgbase = refrain
|
|
|
13
13
|
depends = python-pypresence
|
|
14
14
|
depends = python-dbus
|
|
15
15
|
depends = pyside6
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
optdepends = python-gobject: enables MPRIS-server publication so Plasma media controls reach Refrain
|
|
17
|
+
source = refrain-0.2.3.tar.gz::https://github.com/Rockykln/refrain/archive/v0.2.3.tar.gz
|
|
18
|
+
sha256sums = 29f2371393d855f508730238a096cbdc85219616a9fab240745c1072a005b23d
|
|
18
19
|
|
|
19
20
|
pkgname = refrain
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Maintainer: Rockykln <contact@rockykln.com>
|
|
2
2
|
|
|
3
3
|
pkgname=refrain
|
|
4
|
-
pkgver=0.2.
|
|
4
|
+
pkgver=0.2.4
|
|
5
5
|
pkgrel=1
|
|
6
6
|
pkgdesc="Discord Rich Presence for Apple Music on Linux"
|
|
7
7
|
arch=('any')
|
|
@@ -23,9 +23,6 @@ makedepends=(
|
|
|
23
23
|
'python-wheel'
|
|
24
24
|
)
|
|
25
25
|
source=("$pkgname-$pkgver.tar.gz::https://github.com/Rockykln/refrain/archive/v$pkgver.tar.gz")
|
|
26
|
-
# Placeholder — recompute after the GitHub release exists:
|
|
27
|
-
# curl -sLO "https://github.com/Rockykln/refrain/archive/v${pkgver}.tar.gz"
|
|
28
|
-
# sha256sum "v${pkgver}.tar.gz"
|
|
29
26
|
sha256sums=('SKIP')
|
|
30
27
|
|
|
31
28
|
build() {
|
|
@@ -51,6 +51,28 @@
|
|
|
51
51
|
<content_rating type="oars-1.1" />
|
|
52
52
|
|
|
53
53
|
<releases>
|
|
54
|
+
<release version="0.2.4" date="2026-05-07">
|
|
55
|
+
<description>
|
|
56
|
+
<p>
|
|
57
|
+
Polish + reliability release on top of v0.2.3. Time-display
|
|
58
|
+
consistency across tray, Plasma panel media-controls applet
|
|
59
|
+
and Discord (all three now read the same iTunes-corrected
|
|
60
|
+
duration). Snap and Flatpak Discord builds reachable out of
|
|
61
|
+
the box via sandbox-aware IPC-socket bridging. Inline
|
|
62
|
+
release notes in Settings → Updates. Discord activity card
|
|
63
|
+
always carries the Refrain brand as small_image so the
|
|
64
|
+
cover gets the visual focus. Config drops unknown TOML
|
|
65
|
+
keys instead of nuking the whole file when a newer-version
|
|
66
|
+
key is encountered. DiscordRPC dedupes identical
|
|
67
|
+
consecutive payloads. Browser hints expanded with Floorp,
|
|
68
|
+
Waterfox, Mullvad Browser, Tor Browser, ungoogled-chromium.
|
|
69
|
+
New docs/bluetooth.md walkthrough and FAQ entries on
|
|
70
|
+
browser MPRIS troubleshooting. +13 unit tests, total now
|
|
71
|
+
125, all green; ruff + bandit + pip-audit + Dependabot all
|
|
72
|
+
clean.
|
|
73
|
+
</p>
|
|
74
|
+
</description>
|
|
75
|
+
</release>
|
|
54
76
|
<release version="0.2.3" date="2026-05-07">
|
|
55
77
|
<description>
|
|
56
78
|
<p>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.4"
|