refrain 0.2.0__tar.gz → 0.2.3__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.0 → refrain-0.2.3}/.github/ISSUE_TEMPLATE/bug_report.yml +1 -1
- {refrain-0.2.0 → refrain-0.2.3}/.github/workflows/release.yml +6 -2
- {refrain-0.2.0 → refrain-0.2.3}/.github/workflows/tests.yml +7 -2
- {refrain-0.2.0 → refrain-0.2.3}/.gitignore +2 -1
- {refrain-0.2.0 → refrain-0.2.3}/CHANGELOG.md +327 -1
- {refrain-0.2.0 → refrain-0.2.3}/CONTRIBUTING.md +21 -18
- {refrain-0.2.0 → refrain-0.2.3}/PKG-INFO +25 -6
- {refrain-0.2.0 → refrain-0.2.3}/README.md +24 -5
- {refrain-0.2.0 → refrain-0.2.3}/ROADMAP.md +116 -5
- 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/welcome.png +0 -0
- refrain-0.2.3/docs/test-matrix.md +169 -0
- {refrain-0.2.0 → refrain-0.2.3}/packaging/appimage/AppImageBuilder.yml +1 -1
- refrain-0.2.3/packaging/aur/refrain/.SRCINFO +19 -0
- {refrain-0.2.0 → refrain-0.2.3}/packaging/aur/refrain/PKGBUILD +8 -2
- {refrain-0.2.0 → refrain-0.2.3}/packaging/flatpak/io.github.Rockykln.Refrain.metainfo.xml +56 -0
- {refrain-0.2.0 → refrain-0.2.3}/pyproject.toml +4 -5
- refrain-0.2.3/src/refrain/__init__.py +1 -0
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/app.py +278 -33
- refrain-0.2.3/src/refrain/assets/icons/refrain.png +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/autostart.py +25 -15
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/config.py +51 -6
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/cover_art.py +42 -4
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/cover_fetcher.py +21 -4
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/daemon.py +293 -118
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/discord_rpc.py +72 -3
- refrain-0.2.3/src/refrain/i18n/refrain_de.qm +0 -0
- refrain-0.2.3/src/refrain/i18n/refrain_de.ts +483 -0
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/logging_setup.py +27 -11
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/sources/bluetooth.py +36 -7
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/sources/mpris.py +159 -54
- refrain-0.2.3/src/refrain/sources/mpris_server.py +368 -0
- refrain-0.2.3/src/refrain/timing.py +89 -0
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/ui/log_window.py +6 -1
- refrain-0.2.3/src/refrain/ui/settings_window.py +700 -0
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/ui/tray.py +40 -30
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/ui/update_dialog.py +54 -4
- refrain-0.2.3/src/refrain/ui/welcome_dialog.py +327 -0
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/updater.py +103 -11
- {refrain-0.2.0 → refrain-0.2.3}/tests/test_autostart.py +28 -0
- {refrain-0.2.0 → refrain-0.2.3}/tests/test_config.py +10 -4
- {refrain-0.2.0 → refrain-0.2.3}/tests/test_cover_art.py +6 -3
- {refrain-0.2.0 → refrain-0.2.3}/tests/test_daemon_album_format.py +22 -5
- {refrain-0.2.0 → refrain-0.2.3}/tests/test_daemon_idle.py +26 -3
- {refrain-0.2.0 → refrain-0.2.3}/tests/test_mpris_dispatch.py +5 -5
- {refrain-0.2.0 → refrain-0.2.3}/tests/test_timing.py +47 -1
- refrain-0.2.3/tests/test_tray_init.py +86 -0
- {refrain-0.2.0 → refrain-0.2.3}/tests/test_update_dialog_body.py +15 -9
- refrain-0.2.0/docs/screenshots/settings-advanced.png +0 -0
- refrain-0.2.0/docs/screenshots/settings-general.png +0 -0
- refrain-0.2.0/docs/screenshots/settings-sources.png +0 -0
- refrain-0.2.0/docs/screenshots/settings-updates.png +0 -0
- refrain-0.2.0/src/refrain/__init__.py +0 -1
- refrain-0.2.0/src/refrain/i18n/refrain_de.qm +0 -0
- refrain-0.2.0/src/refrain/i18n/refrain_de.ts +0 -342
- refrain-0.2.0/src/refrain/i18n/refrain_en.qm +0 -0
- refrain-0.2.0/src/refrain/i18n/refrain_en.ts +0 -340
- refrain-0.2.0/src/refrain/i18n/refrain_es.qm +0 -0
- refrain-0.2.0/src/refrain/i18n/refrain_es.ts +0 -340
- refrain-0.2.0/src/refrain/i18n/refrain_fr.qm +0 -0
- refrain-0.2.0/src/refrain/i18n/refrain_fr.ts +0 -340
- refrain-0.2.0/src/refrain/i18n/refrain_it.qm +0 -0
- refrain-0.2.0/src/refrain/i18n/refrain_it.ts +0 -340
- refrain-0.2.0/src/refrain/i18n/refrain_ja.qm +0 -0
- refrain-0.2.0/src/refrain/i18n/refrain_ja.ts +0 -340
- refrain-0.2.0/src/refrain/i18n/refrain_nl.qm +0 -0
- refrain-0.2.0/src/refrain/i18n/refrain_nl.ts +0 -340
- refrain-0.2.0/src/refrain/i18n/refrain_pl.qm +0 -0
- refrain-0.2.0/src/refrain/i18n/refrain_pl.ts +0 -340
- refrain-0.2.0/src/refrain/i18n/refrain_pt.qm +0 -0
- refrain-0.2.0/src/refrain/i18n/refrain_pt.ts +0 -340
- refrain-0.2.0/src/refrain/i18n/refrain_zh_CN.qm +0 -0
- refrain-0.2.0/src/refrain/i18n/refrain_zh_CN.ts +0 -340
- refrain-0.2.0/src/refrain/sources/dbus_watcher.py +0 -136
- refrain-0.2.0/src/refrain/timing.py +0 -42
- refrain-0.2.0/src/refrain/ui/settings_window.py +0 -342
- refrain-0.2.0/src/refrain/ui/welcome_dialog.py +0 -221
- {refrain-0.2.0 → refrain-0.2.3}/.editorconfig +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/.gitattributes +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/.github/FUNDING.yml +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/.github/codeql/codeql-config.yml +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/.github/dependabot.yml +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/.github/pull_request_template.md +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/.github/workflows/codeql.yml +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/.github/workflows/security.yml +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/CODE_OF_CONDUCT.md +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/LICENSE +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/Makefile +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/SECURITY.md +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/docs/architecture.md +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/docs/faq.md +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/docs/screenshots/README.md +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/docs/screenshots/discord-rpc.png +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/docs/screenshots/live-log.png +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/docs/screenshots/notification.png +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/docs/screenshots/tray-menu.png +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/docs/screenshots/update-dialog.png +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/packaging/README.md +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/packaging/aur/refrain-git/PKGBUILD +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/packaging/flatpak/io.github.Rockykln.Refrain.desktop +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/packaging/flatpak/io.github.Rockykln.Refrain.yaml +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/packaging/flatpak/python-deps.json +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/requirements-dev.txt +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/requirements.txt +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/__main__.py +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/assets/icons/github-mark.svg +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/assets/icons/refrain.svg +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/assets/icons/tray-paused-dark.svg +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/assets/icons/tray-paused.svg +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/assets/icons/tray-playing-dark.svg +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/assets/icons/tray-playing.svg +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/assets/icons/tray-stopped-dark.svg +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/assets/icons/tray-stopped.svg +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/assets/refrain.desktop +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/paths.py +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/single_instance.py +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/sources/__init__.py +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/sources/base.py +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/src/refrain/ui/__init__.py +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/tests/__init__.py +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/tests/conftest.py +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/tests/test_cover_fetcher.py +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/tests/test_discord_listening.py +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/tests/test_imports.py +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/tests/test_paths.py +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/tests/test_sources_base.py +0 -0
- {refrain-0.2.0 → refrain-0.2.3}/tests/test_updater.py +0 -0
|
@@ -31,9 +31,13 @@ jobs:
|
|
|
31
31
|
- name: Install + run tests (gate the release)
|
|
32
32
|
run: |
|
|
33
33
|
python -m pip install --upgrade pip
|
|
34
|
-
# System libs
|
|
34
|
+
# System libs: dbus-python needs the *-dev headers at build
|
|
35
|
+
# time; PySide6.QtWidgets needs the EGL/xkbcommon/GL/fontconfig
|
|
36
|
+
# runtime libs to import (even under QT_QPA_PLATFORM=offscreen).
|
|
35
37
|
sudo apt-get update
|
|
36
|
-
sudo apt-get install -y
|
|
38
|
+
sudo apt-get install -y \
|
|
39
|
+
libdbus-1-dev libglib2.0-dev pkg-config \
|
|
40
|
+
libegl1 libxkbcommon0 libfontconfig1 libgl1
|
|
37
41
|
# Install the package itself so the test imports get every
|
|
38
42
|
# runtime dep (PySide6, pypresence, dbus-python) — without
|
|
39
43
|
# this the new test_daemon_*.py files fail at collection.
|
|
@@ -26,11 +26,16 @@ jobs:
|
|
|
26
26
|
- name: Install runtime + test dependencies
|
|
27
27
|
# The newer tests import refrain modules that pull PySide6 /
|
|
28
28
|
# pypresence / dbus-python at module load, so the runtime deps
|
|
29
|
-
# have to be present in CI too.
|
|
29
|
+
# have to be present in CI too. PySide6.QtWidgets requires the
|
|
30
|
+
# EGL / xkbcommon / GL / fontconfig system libs even when run
|
|
31
|
+
# under QT_QPA_PLATFORM=offscreen — without them the import
|
|
32
|
+
# itself fails before the test can opt out.
|
|
30
33
|
run: |
|
|
31
34
|
python -m pip install --upgrade pip
|
|
32
35
|
sudo apt-get update
|
|
33
|
-
sudo apt-get install -y
|
|
36
|
+
sudo apt-get install -y \
|
|
37
|
+
libdbus-1-dev libglib2.0-dev pkg-config \
|
|
38
|
+
libegl1 libxkbcommon0 libfontconfig1 libgl1
|
|
34
39
|
pip install -e ".[dev]"
|
|
35
40
|
|
|
36
41
|
- name: Compile-check every module
|
|
@@ -7,6 +7,325 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.3] - 2026-05-07
|
|
11
|
+
|
|
12
|
+
A reliability + portability pass driven by hands-on testing across
|
|
13
|
+
Ubuntu 25.04, CentOS Stream 10, and the existing CachyOS daily driver.
|
|
14
|
+
No breaking changes — same config, same UI, same Python floor; the
|
|
15
|
+
release just makes Refrain behave correctly on more distros and tightens
|
|
16
|
+
a handful of long-standing rough edges.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **Sandboxed Discord IPC bridge** — Snap and Flatpak Discord builds
|
|
21
|
+
publish their `discord-ipc-N` socket inside the sandbox tree instead
|
|
22
|
+
of `$XDG_RUNTIME_DIR`, so pypresence's stock discovery never finds it.
|
|
23
|
+
`_bridge_sandboxed_ipc_socket` probes the three known sandbox paths
|
|
24
|
+
(Flatpak instance dir, Flatpak config-dir layout, Snap path) and
|
|
25
|
+
symlinks the first hit into `$XDG_RUNTIME_DIR` before each connect
|
|
26
|
+
attempt. The welcome-dialog probe walks the same paths so first-run
|
|
27
|
+
diagnostics turn green on those installs.
|
|
28
|
+
- **Cancel button in the update dialog** — `_apply_appimage` now
|
|
29
|
+
reads in 64 KiB chunks and polls a cancellation callback between
|
|
30
|
+
blocks. The dialog repurposes its "Later" button as "Cancel" while a
|
|
31
|
+
download is running, treats window-close as cancel, and removes the
|
|
32
|
+
partial `.AppImage.new` on abort. `UpdateResult.cancelled` lets the
|
|
33
|
+
dialog suppress the failure popup for user-initiated aborts.
|
|
34
|
+
- **Orphan-download self-heal** — `cleanup_orphan_downloads` runs once
|
|
35
|
+
at startup so a `*.AppImage.new` left behind by a SIGKILL or power
|
|
36
|
+
loss mid-download doesn't sit next to the binary forever.
|
|
37
|
+
- **Browser hints expanded** — Floorp, Waterfox, Mullvad Browser, Tor
|
|
38
|
+
Browser, and ungoogled-chromium are now in `DEFAULT_BROWSER_HINTS`
|
|
39
|
+
and surface as Settings checkboxes. Existing configs keep their
|
|
40
|
+
saved list (no auto-migration); the new entries appear unticked
|
|
41
|
+
until the user toggles them.
|
|
42
|
+
- **iTunes-catalog duration as MPRIS override** — when MPRIS reports
|
|
43
|
+
an obviously-wrong `mpris:length` (Apple Music's preview-clip 14 s,
|
|
44
|
+
the playlist-total instead of the track length, or a stale value
|
|
45
|
+
carried over from the previous track), `pick_effective_duration_ms`
|
|
46
|
+
prefers the iTunes value if the two disagree by more than 15 % or
|
|
47
|
+
if MPRIS sits in the preview-clip band on a song iTunes considers
|
|
48
|
+
full-length. 8 new unit tests in `test_timing.py`. Discord no
|
|
49
|
+
longer briefly flips a 2:11 song's total to 0:14 / 7:21 mid-track.
|
|
50
|
+
- **`docs/test-matrix.md`** — eight Tier-1 distros that together
|
|
51
|
+
cover ≈ 95 % of the realistic user base, plus Tier-2 spot-checks,
|
|
52
|
+
Tier-3 desktop / compositor combinations, and a six-step smoke
|
|
53
|
+
check ("tray + theme parity", "settings round-trip", "MPRIS to
|
|
54
|
+
Discord", "Bluetooth", "update-dialog cleanup", "restart cycle")
|
|
55
|
+
per row.
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
|
|
59
|
+
- **Qt style plugin path augmentation also walks Debian / Ubuntu and
|
|
60
|
+
Fedora / RHEL / openSUSE layouts** — the v0.2.2 fix only checked
|
|
61
|
+
`/usr/lib/qt6/plugins` (Arch). pip / pipx installs on those distros
|
|
62
|
+
now also pick up the system Breeze plugin instead of falling back
|
|
63
|
+
to Fusion. Detection picks the first existing path among
|
|
64
|
+
`/usr/lib/<arch-triple>/qt6/plugins`, `/usr/lib64/qt6/plugins`,
|
|
65
|
+
`/usr/lib/qt6/plugins`.
|
|
66
|
+
- **"No system tray" error** rewritten to cover GNOME, MATE, XFCE,
|
|
67
|
+
Hyprland / Sway / i3 / river, plus a "should work, try logging
|
|
68
|
+
out" hint for Plasma / Cinnamon / LXQt / Budgie. Previously the
|
|
69
|
+
fix advice was GNOME-AppIndicator-only, leaving everyone else to
|
|
70
|
+
guess.
|
|
71
|
+
- **`refrain --install-desktop`** rewrites the installed
|
|
72
|
+
`Exec=` line to point at the actual launcher path of the running
|
|
73
|
+
install. Previously the bundled `Exec=refrain` only worked when
|
|
74
|
+
`refrain` was on `$PATH` (distro packages, pipx); AppImage and
|
|
75
|
+
source-checkout users got a broken menu entry. Resolution order
|
|
76
|
+
matches the autostart logic — extracted into the new
|
|
77
|
+
`autostart.resolve_exec_line(extra_args)` helper.
|
|
78
|
+
- **MPRIS source duration handling decouples drift-resync from the
|
|
79
|
+
payload's preview-clip flag** — the drift-skip still keys off the
|
|
80
|
+
raw MPRIS-reported duration (because the position-field looping is
|
|
81
|
+
a property of MPRIS' preview-clip mode), but the Discord activity
|
|
82
|
+
payload's start/end fields key off the *effective* duration so a
|
|
83
|
+
brief MPRIS preview-clip glitch on a full-length song doesn't kill
|
|
84
|
+
the progress bar.
|
|
85
|
+
- **PyGObject "not installed" warning fires once per startup**
|
|
86
|
+
instead of twice (eager init from `app.py` plus lazy fallback in
|
|
87
|
+
`MPRISServer.start` both used to log). The install hint also lists
|
|
88
|
+
the Fedora / RHEL / openSUSE package name (`python3-gobject`)
|
|
89
|
+
alongside the Arch and Debian ones.
|
|
90
|
+
|
|
91
|
+
### Fixed
|
|
92
|
+
|
|
93
|
+
- **Settings / Log / Update windows now carry the Refrain icon
|
|
94
|
+
explicitly** — on GNOME Wayland the title-based heuristic was
|
|
95
|
+
matching "Settings" against `org.gnome.Settings` and rendering the
|
|
96
|
+
gnome-control-center gear icon in the dock. The WelcomeDialog
|
|
97
|
+
already set its own icon; the other three top-level windows did
|
|
98
|
+
not.
|
|
99
|
+
- **Bluetooth fast-fail when `org.bluez` is not present** — every
|
|
100
|
+
poll cycle on a VM / minimal install previously triggered a
|
|
101
|
+
`service_start_timeout=25000ms` D-Bus activation timeout, blocking
|
|
102
|
+
the worker thread for the full 25 s. `NameHasOwner` returns
|
|
103
|
+
instantly without triggering activation; the three entry points
|
|
104
|
+
(`read`, `_call_method`, `list_paired_devices`) now short-circuit
|
|
105
|
+
when the owner check returns False.
|
|
106
|
+
- **Logging gaps closed in six places** — `setup_logging` runs
|
|
107
|
+
before `Config.load` so the "created default config" /
|
|
108
|
+
"config unreadable" messages reach the file log; toggling the log
|
|
109
|
+
level in Settings now applies to the running root logger via
|
|
110
|
+
`_apply_log_level` on `settings.applied`; the "no system tray"
|
|
111
|
+
early-exit gets a `log.error()`; `os.execvp`-based restarts call
|
|
112
|
+
`logging.shutdown()` first so the file handler's last buffered
|
|
113
|
+
line isn't lost; `Config.load` exception path adds `exc_info=True`;
|
|
114
|
+
`setup_logging` degrades gracefully when the XDG state dir or log
|
|
115
|
+
file can't be opened (console handler always attaches first).
|
|
116
|
+
- **Catch-all exception branches in `DiscordRPC._ensure_connected`,
|
|
117
|
+
`MPRISSource._call_method_on`, and `BluetoothSource._call_method`**
|
|
118
|
+
now use `log.exception()` so the traceback lands in `refrain.log`
|
|
119
|
+
for unexpected errors. Previously they only logged `str(exc)`,
|
|
120
|
+
losing the stack exactly when it would matter most.
|
|
121
|
+
- **Qt-internal log records routed through `refrain.qt`** instead
|
|
122
|
+
of the bare `qt` logger, so they parent under the project
|
|
123
|
+
namespace and show up alongside everything else when the user
|
|
124
|
+
filters by name.
|
|
125
|
+
|
|
126
|
+
## [0.2.2] - 2026-05-06
|
|
127
|
+
|
|
128
|
+
Two roadmap features pulled forward from v0.3 plus a substantial batch
|
|
129
|
+
of reliability + UX work driven by live-testing the v0.2.1 build.
|
|
130
|
+
|
|
131
|
+
### Added
|
|
132
|
+
|
|
133
|
+
- **Multiple Discord profiles** — per-source `client_id_mpris` and
|
|
134
|
+
`client_id_bluetooth` fields in `Config.discord` plus matching UI in
|
|
135
|
+
Settings → General → Discord. Empty falls back to the default
|
|
136
|
+
Client ID. The daemon reconnects RPC under the source-specific ID
|
|
137
|
+
the moment the active source flips, so each source can render with
|
|
138
|
+
its own application name + uploaded artwork in the user's profile.
|
|
139
|
+
- **MPRIS-server mode** — Refrain registers itself as
|
|
140
|
+
`org.mpris.MediaPlayer2.refrain` on the session bus so KDE Plasma's
|
|
141
|
+
panel media-controls applet (and KDE Connect, GNOME Shell, Mako
|
|
142
|
+
notifications, …) drive the same Play/Pause/Next/Previous as the
|
|
143
|
+
tray, and render the same track Discord renders. Implemented with
|
|
144
|
+
`dbus-python` + a daemon GLib main loop running in its own thread
|
|
145
|
+
so it doesn't compete with Qt's event loop. Falls back gracefully
|
|
146
|
+
when PyGObject isn't installed.
|
|
147
|
+
- **iTunes track duration in the cover-fetcher cache** —
|
|
148
|
+
`trackTimeMillis` from the iTunes Search API rides along with
|
|
149
|
+
`cover_url` / `song_url` in the on-disk cache and exposed via
|
|
150
|
+
`CoverFetcher.get_duration_ms()`. Used as a defensive fallback when
|
|
151
|
+
MPRIS reports an obviously-wrong preview-clip length.
|
|
152
|
+
- **Per-player MPRIS timeout blacklist** — if a property `Get` times
|
|
153
|
+
out we banish that bus name for 5 s so a hung player (typically
|
|
154
|
+
plasma-browser-integration on a frozen Apple Music tab) can't keep
|
|
155
|
+
eating the daemon's poll cycle one tick at a time.
|
|
156
|
+
|
|
157
|
+
### Changed
|
|
158
|
+
|
|
159
|
+
- **Welcome wizard** redesigned: 56×56 icon-badge + title + subtitle
|
|
160
|
+
header, intro line about tray-driven controls, dedicated diagnostics
|
|
161
|
+
card with two clearly-labelled rows (Discord / iTunes), Discord
|
|
162
|
+
Application ID block with helper text + readable italic styling
|
|
163
|
+
(the previous `palette(mid)` tone went invisible on Plasma Breeze
|
|
164
|
+
dark themes), Skip ghost-style on the left + Apply primary on the
|
|
165
|
+
right.
|
|
166
|
+
- **Welcome wizard now opens Settings on confirmation** instead of
|
|
167
|
+
silently dropping into the tray, and reloads the Settings form so
|
|
168
|
+
the just-saved Discord Client ID appears in the input field.
|
|
169
|
+
- **`compute_rpc_start_ts` accepts `is_preview_clip=True`** to skip
|
|
170
|
+
the drift-resync path on tracks whose MPRIS Position field loops
|
|
171
|
+
0→8s instead of advancing monotonically. Discord's elapsed counter
|
|
172
|
+
no longer resets every ~8 s on preview-mode playback.
|
|
173
|
+
- **Reset-all-settings preserves every Discord Client ID** (default +
|
|
174
|
+
per-source mpris + per-source bluetooth) and the dialog text now
|
|
175
|
+
states this explicitly. Reset-dialog button labels are localised
|
|
176
|
+
(`Reset` / `Zurücksetzen` / `Cancel` / `Abbrechen`) instead of the
|
|
177
|
+
generic Yes / No.
|
|
178
|
+
- **Default `notify_delay_ms` lowered from 600 ms to 0 ms** — cover
|
|
179
|
+
cache hits fire instantly, cache misses still get the existing
|
|
180
|
+
retry loop up to 2 s for the iTunes download.
|
|
181
|
+
- **Default `poll_interval_ms` lowered from 1 s to 500 ms** — track
|
|
182
|
+
changes, position updates, and tray controls feel noticeably more
|
|
183
|
+
responsive without measurable CPU impact.
|
|
184
|
+
- **Idle detection skips preview-clip-length tracks** (duration <
|
|
185
|
+
30 s) — Apple Music keeps reporting the same metadata while a
|
|
186
|
+
preview replays, and clearing Discord under those conditions hid
|
|
187
|
+
the activity mid-listen.
|
|
188
|
+
- **Idle-detection log fires once per dangling-track instance**
|
|
189
|
+
instead of every poll tick.
|
|
190
|
+
|
|
191
|
+
### Fixed
|
|
192
|
+
|
|
193
|
+
- **Discord RPC connect now happens within ~1 s of the first track
|
|
194
|
+
detection** (was: up to 51 s). Root cause: dbus-python's default
|
|
195
|
+
25 s reply timeout combined with plasma-browser-integration
|
|
196
|
+
introspection hangs blocked the daemon's poll. Fix: pass
|
|
197
|
+
`introspect=False` on every `bus.get_object` and `timeout=0.5` on
|
|
198
|
+
every property `Get` and method dispatch.
|
|
199
|
+
- **Discord RPC reconnects eagerly when the user changes their
|
|
200
|
+
Application ID** (welcome wizard or Settings → General). Previously
|
|
201
|
+
the new pipe wasn't established until the next *playing-state*
|
|
202
|
+
poll, so paused users sat there waiting forever.
|
|
203
|
+
- **Skip / Previous reach Apple Music reliably**: dispatch tries
|
|
204
|
+
the browser-native MPRIS player (chromium / firefox) first, then
|
|
205
|
+
falls back to plasma-browser-integration. Plasma's wrapper claims
|
|
206
|
+
`CanGoNext=True` on Apple Music but its Next routes through a
|
|
207
|
+
flaky mediaSession path; the browser-native player reaches the
|
|
208
|
+
same handler via a more reliable path.
|
|
209
|
+
- **Per-property `_safe_get` inside MPRIS read** — chromium MPRIS
|
|
210
|
+
rejecting one optional property (notably `DesktopEntry`, with a
|
|
211
|
+
generic `Error.Failed`) no longer drops the whole player from our
|
|
212
|
+
candidate list, which previously hid the chromium player entirely
|
|
213
|
+
from the skip-fallback chain.
|
|
214
|
+
- **All apple-music candidates** beyond the metadata winner join the
|
|
215
|
+
control-fallback list, so the chromium player is reachable for
|
|
216
|
+
skip dispatches even when plasma wins the metadata pick.
|
|
217
|
+
- **Notification fires immediately on cover-cache hits** (50 ms vs.
|
|
218
|
+
the previous unconditional 600-1500 ms `notify_delay_ms` wait).
|
|
219
|
+
- **Discord progress bar dropped entirely on preview-clip mode**
|
|
220
|
+
(MPRIS duration < 30 s) — no `start`, no `end` in the activity
|
|
221
|
+
payload — so the elapsed timer doesn't loop nonsensically with
|
|
222
|
+
the 8-second preview.
|
|
223
|
+
- **`update_config` invocation from the welcome wizard** now goes
|
|
224
|
+
through `settings.applied.emit(config)` (already QueuedConnection-
|
|
225
|
+
wired to the worker thread) instead of `QMetaObject.invokeMethod
|
|
226
|
+
(… Q_ARG(object, …))`, which PySide6 rejects with "Unable to find
|
|
227
|
+
a QMetaType for 'object'" — that exception was silently breaking
|
|
228
|
+
the post-wizard handshake on first-run.
|
|
229
|
+
- **Welcome wizard X / Esc** marks `first_run_complete=True` (via
|
|
230
|
+
`reject()` override) so the wizard doesn't re-appear on the next
|
|
231
|
+
launch when the user dismisses without confirming.
|
|
232
|
+
- **Welcome wizard Apply with empty ID** asks for confirmation
|
|
233
|
+
before silently saving "no Discord".
|
|
234
|
+
- **Refrain's own published MPRIS bus name skipped** during the
|
|
235
|
+
source read loop — reading our own publish path back was
|
|
236
|
+
circular and added noticeable latency.
|
|
237
|
+
- **`_control` exception handling** — a TypeError in
|
|
238
|
+
`getattr(src, action)()` no longer kills the entire dispatch
|
|
239
|
+
before the follow-up polls fire.
|
|
240
|
+
- **Reset preserves per-source Discord IDs** (`client_id_mpris` /
|
|
241
|
+
`client_id_bluetooth`), not just the default `client_id`.
|
|
242
|
+
- **dbus.proxies / dbus.connection log spam silenced** — their
|
|
243
|
+
Introspect-error tracebacks against unrelated MPRIS players
|
|
244
|
+
drowned the live log in noise that has nothing to do with
|
|
245
|
+
refrain.
|
|
246
|
+
|
|
247
|
+
### Removed
|
|
248
|
+
|
|
249
|
+
- **Dead `_diag_label` shim** in `WelcomeDialog` that was only there
|
|
250
|
+
for a backwards-compat path no longer in use.
|
|
251
|
+
|
|
252
|
+
## [0.2.1] - 2026-05-06
|
|
253
|
+
|
|
254
|
+
Polish release built around the v0.2.0 surface. The Settings window got
|
|
255
|
+
a UI overhaul, two notification cover-art races are gone, and the
|
|
256
|
+
external code-review punch list landed in full (atomic config writes,
|
|
257
|
+
AppImage size validation, `.ts` source files no longer ship in the
|
|
258
|
+
wheel, dead D-Bus watcher module deleted).
|
|
259
|
+
|
|
260
|
+
### Added
|
|
261
|
+
|
|
262
|
+
- **Auto-restart on Discord client_id change** — Apply now triggers an
|
|
263
|
+
in-process re-exec when the client_id field has changed, same
|
|
264
|
+
pattern as the language switch. pypresence binds to the client_id
|
|
265
|
+
at connect time, so a fresh process is the simplest way to re-init
|
|
266
|
+
cleanly.
|
|
267
|
+
- **AppImage update size validation** — `_apply_appimage` now compares
|
|
268
|
+
the downloaded file against the GitHub Releases manifest's
|
|
269
|
+
`appimage_size` before replacing the live binary. A truncated
|
|
270
|
+
download is rejected and the tmp file cleaned up instead of
|
|
271
|
+
bricking the next launch.
|
|
272
|
+
- **MPRIS dispatch logging** — `Next` / `Previous` / `PlayPause` log
|
|
273
|
+
which player actually received the call (INFO level). Diagnoses
|
|
274
|
+
"Next pauses instead of skipping" type bugs where a fallback
|
|
275
|
+
player handles the action wrong.
|
|
276
|
+
- **RPC track-change diagnostics** — log raw `pos`, `dur`, `start_ts`
|
|
277
|
+
on every recompute so duration mismatches (browser MPRIS reporting
|
|
278
|
+
preview-clip lengths, etc.) are visible in the live log.
|
|
279
|
+
|
|
280
|
+
### Changed
|
|
281
|
+
|
|
282
|
+
- **Settings window UI overhaul** — every tab uses a consistent
|
|
283
|
+
`QGroupBox` + form-layout with left-aligned titles via stylesheet
|
|
284
|
+
(overrides Plasma Breeze's centered default). Inputs are
|
|
285
|
+
fixed-width: 220 px default, 360 px for the Discord group whose
|
|
286
|
+
Client ID placeholder + privacy-mode labels need more room.
|
|
287
|
+
`FieldsStayAtSizeHint` + per-widget `setFixedWidth` is the only
|
|
288
|
+
combo that holds across both Fusion (offscreen tests) and Breeze
|
|
289
|
+
(Plasma) — `AllNonFixedFieldsGrow` was ignoring fixed-width caps.
|
|
290
|
+
- **Discord RPC reconnect cap lowered to 15 s** — autostart launches
|
|
291
|
+
refrain before Discord is ready; the previous 60 s ceiling meant
|
|
292
|
+
the user could sit there for almost a minute after Discord
|
|
293
|
+
finished loading before refrain noticed and connected.
|
|
294
|
+
- **Atomic config writes** — `Config.save` now writes to a `.tmp`
|
|
295
|
+
sibling and `os.replace`s into place. A crash mid-write would
|
|
296
|
+
otherwise leave an empty / half-written TOML and refrain
|
|
297
|
+
silently falls back to defaults on parse error, losing every
|
|
298
|
+
setting the user picked.
|
|
299
|
+
- **Discord client_id Apply re-init** — wired `restartRequested`
|
|
300
|
+
emission so picking up a new client_id no longer requires a
|
|
301
|
+
manual restart.
|
|
302
|
+
|
|
303
|
+
### Fixed
|
|
304
|
+
|
|
305
|
+
- **Notification cover flicker** — `notify-send -i` now carries the
|
|
306
|
+
same image as the `--hint string:image-path:...` payload. KDE
|
|
307
|
+
Plasma briefly rendered the `-i refrain` brand badge (~50–100 ms)
|
|
308
|
+
while it loaded the image-path file from disk; using the same
|
|
309
|
+
file for both makes the transition invisible.
|
|
310
|
+
- **Discord activity-card cover flicker** — defer the first RPC
|
|
311
|
+
update for a new track until the iTunes-search cover URL is in
|
|
312
|
+
cache, capped at 3 polls (~3 s). Without this, Discord briefly
|
|
313
|
+
rendered the `refrain` brand fallback before the real cover
|
|
314
|
+
paint, exactly the issue cover-fetcher was supposed to fix.
|
|
315
|
+
- **Group titles centered on Plasma Breeze** — explicit stylesheet
|
|
316
|
+
overrides Breeze's default centered `QGroupBox::title`.
|
|
317
|
+
|
|
318
|
+
### Removed
|
|
319
|
+
|
|
320
|
+
- **Dead `dbus_watcher` module** — 155 lines of `MPRISWatcher` /
|
|
321
|
+
`BluetoothWatcher` machinery that was never wired up after the
|
|
322
|
+
signal-driven path was abandoned for v0.2.x. The companion
|
|
323
|
+
`_refresh_watchers` and `_on_external_change` slots in
|
|
324
|
+
`Daemon` went with it.
|
|
325
|
+
- **`.ts` translation sources from the wheel** — only the compiled
|
|
326
|
+
`.qm` files ship now. Linguist sources stay in the source tree
|
|
327
|
+
for translator PRs.
|
|
328
|
+
|
|
10
329
|
## [0.2.0] - 2026-05-06
|
|
11
330
|
|
|
12
331
|
First minor-version bump. Five planned v0.2 features land together
|
|
@@ -365,7 +684,14 @@ with a proper, installable Linux app.
|
|
|
365
684
|
pip-audit, trufflehog, release), Dependabot, issue + PR templates,
|
|
366
685
|
`CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`.
|
|
367
686
|
|
|
368
|
-
[Unreleased]: https://github.com/Rockykln/refrain/compare/v0.
|
|
687
|
+
[Unreleased]: https://github.com/Rockykln/refrain/compare/v0.2.3...HEAD
|
|
688
|
+
[0.2.3]: https://github.com/Rockykln/refrain/compare/v0.2.2...v0.2.3
|
|
689
|
+
[0.2.2]: https://github.com/Rockykln/refrain/compare/v0.2.1...v0.2.2
|
|
690
|
+
[0.2.1]: https://github.com/Rockykln/refrain/compare/v0.2.0...v0.2.1
|
|
691
|
+
[0.2.0]: https://github.com/Rockykln/refrain/compare/v0.1.5...v0.2.0
|
|
692
|
+
[0.1.5]: https://github.com/Rockykln/refrain/compare/v0.1.4...v0.1.5
|
|
693
|
+
[0.1.4]: https://github.com/Rockykln/refrain/compare/v0.1.3...v0.1.4
|
|
694
|
+
[0.1.3]: https://github.com/Rockykln/refrain/compare/v0.1.2...v0.1.3
|
|
369
695
|
[0.1.2]: https://github.com/Rockykln/refrain/compare/v0.1.1...v0.1.2
|
|
370
696
|
[0.1.1]: https://github.com/Rockykln/refrain/compare/v0.1.0...v0.1.1
|
|
371
697
|
[0.1.0]: https://github.com/Rockykln/refrain/releases/tag/v0.1.0
|
|
@@ -122,31 +122,34 @@ descriptions instead.
|
|
|
122
122
|
|
|
123
123
|
## Translations
|
|
124
124
|
|
|
125
|
-
User-visible strings live wrapped in `tr()` calls;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
source strings already extracted, marked
|
|
130
|
-
`<translation type="unfinished"></translation>` — those are the ones we'd
|
|
131
|
-
love community PRs for.
|
|
125
|
+
User-visible strings live wrapped in `tr()` calls; translations are
|
|
126
|
+
kept in `src/refrain/i18n/refrain_<lang>.ts` (Qt Linguist format).
|
|
127
|
+
English is the source language. German is the only translation we
|
|
128
|
+
ship complete right now.
|
|
132
129
|
|
|
133
|
-
To
|
|
130
|
+
To add a new language end-to-end:
|
|
134
131
|
|
|
135
132
|
```sh
|
|
136
|
-
# 1.
|
|
137
|
-
#
|
|
138
|
-
|
|
133
|
+
# 1. Copy the German translation as a starting point (gives you all
|
|
134
|
+
# the strings already extracted).
|
|
135
|
+
cp src/refrain/i18n/refrain_de.ts src/refrain/i18n/refrain_fr.ts
|
|
139
136
|
|
|
140
|
-
# 2.
|
|
137
|
+
# 2. Open it in Qt Linguist (friendlier than editing XML by hand) and
|
|
138
|
+
# replace each German line with your translation.
|
|
139
|
+
pyside6-linguist src/refrain/i18n/refrain_fr.ts
|
|
141
140
|
|
|
142
|
-
# 3. Compile to .qm
|
|
141
|
+
# 3. Compile to .qm.
|
|
143
142
|
make i18n
|
|
143
|
+
|
|
144
|
+
# 4. Add your code to settings_window.py's language combo so users can
|
|
145
|
+
# pick it from the Localization group, then run Refrain locally to
|
|
146
|
+
# smoke-test.
|
|
144
147
|
LANG=fr_FR.UTF-8 refrain
|
|
145
148
|
```
|
|
146
149
|
|
|
147
|
-
If you've wrapped any new strings on the source side, run
|
|
148
|
-
`make i18n-update` first — it refreshes
|
|
149
|
-
|
|
150
|
+
If you've wrapped any new English strings on the source side, run
|
|
151
|
+
`make i18n-update` first — it refreshes the `.ts` file's source-string
|
|
152
|
+
list while preserving existing translations.
|
|
150
153
|
|
|
151
|
-
PRs that
|
|
152
|
-
|
|
154
|
+
PRs that add a new language (or fix typos in an existing one) are
|
|
155
|
+
very welcome.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: refrain
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
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
|
|
|
@@ -162,7 +163,9 @@ same file; you almost never need to touch it by hand.
|
|
|
162
163
|
|
|
163
164
|
```toml
|
|
164
165
|
[discord]
|
|
165
|
-
client_id = "" #
|
|
166
|
+
client_id = "" # default Application ID — paste yours here
|
|
167
|
+
client_id_mpris = "" # optional per-source override (browser / Apple Music)
|
|
168
|
+
client_id_bluetooth = "" # optional per-source override (Bluetooth headphones)
|
|
166
169
|
|
|
167
170
|
[sources]
|
|
168
171
|
mpris_enabled = true
|
|
@@ -177,12 +180,28 @@ autostart = false
|
|
|
177
180
|
notifications = true
|
|
178
181
|
cover_art = true
|
|
179
182
|
show_buttons = true
|
|
183
|
+
notify_delay_ms = 0 # 0 = fire ASAP; the cover-art retry loop still waits up to ~2 s
|
|
180
184
|
|
|
181
185
|
[advanced]
|
|
182
|
-
poll_interval_ms =
|
|
186
|
+
poll_interval_ms = 500
|
|
183
187
|
log_level = "INFO"
|
|
188
|
+
cover_cache_size = 200 # disk cap for cached covers
|
|
189
|
+
idle_grace_s = 30 # clear status when same track plays past duration + grace; 0 disables
|
|
190
|
+
language = "system" # "system" follows QLocale; "en" / "de" / … force a translation
|
|
184
191
|
```
|
|
185
192
|
|
|
193
|
+
Per-source `client_id_*` fields let Apple Music render under one Discord
|
|
194
|
+
application (with the album-grid as artwork) and Bluetooth headphones under
|
|
195
|
+
another (with a generic Bluetooth glyph). Empty falls back to the default
|
|
196
|
+
`client_id`.
|
|
197
|
+
|
|
198
|
+
## MPRIS server
|
|
199
|
+
|
|
200
|
+
Refrain publishes itself as `org.mpris.MediaPlayer2.refrain` on the session
|
|
201
|
+
bus, so KDE Plasma's panel media-controls applet (and KDE Connect, GNOME
|
|
202
|
+
Shell, Mako, …) drive the same Play/Pause/Next/Previous as the tray and
|
|
203
|
+
render the same track Discord renders.
|
|
204
|
+
|
|
186
205
|
## Tray
|
|
187
206
|
|
|
188
207
|
<p align="center">
|
|
@@ -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
|
|
|
@@ -128,7 +129,9 @@ same file; you almost never need to touch it by hand.
|
|
|
128
129
|
|
|
129
130
|
```toml
|
|
130
131
|
[discord]
|
|
131
|
-
client_id = "" #
|
|
132
|
+
client_id = "" # default Application ID — paste yours here
|
|
133
|
+
client_id_mpris = "" # optional per-source override (browser / Apple Music)
|
|
134
|
+
client_id_bluetooth = "" # optional per-source override (Bluetooth headphones)
|
|
132
135
|
|
|
133
136
|
[sources]
|
|
134
137
|
mpris_enabled = true
|
|
@@ -143,12 +146,28 @@ autostart = false
|
|
|
143
146
|
notifications = true
|
|
144
147
|
cover_art = true
|
|
145
148
|
show_buttons = true
|
|
149
|
+
notify_delay_ms = 0 # 0 = fire ASAP; the cover-art retry loop still waits up to ~2 s
|
|
146
150
|
|
|
147
151
|
[advanced]
|
|
148
|
-
poll_interval_ms =
|
|
152
|
+
poll_interval_ms = 500
|
|
149
153
|
log_level = "INFO"
|
|
154
|
+
cover_cache_size = 200 # disk cap for cached covers
|
|
155
|
+
idle_grace_s = 30 # clear status when same track plays past duration + grace; 0 disables
|
|
156
|
+
language = "system" # "system" follows QLocale; "en" / "de" / … force a translation
|
|
150
157
|
```
|
|
151
158
|
|
|
159
|
+
Per-source `client_id_*` fields let Apple Music render under one Discord
|
|
160
|
+
application (with the album-grid as artwork) and Bluetooth headphones under
|
|
161
|
+
another (with a generic Bluetooth glyph). Empty falls back to the default
|
|
162
|
+
`client_id`.
|
|
163
|
+
|
|
164
|
+
## MPRIS server
|
|
165
|
+
|
|
166
|
+
Refrain publishes itself as `org.mpris.MediaPlayer2.refrain` on the session
|
|
167
|
+
bus, so KDE Plasma's panel media-controls applet (and KDE Connect, GNOME
|
|
168
|
+
Shell, Mako, …) drive the same Play/Pause/Next/Previous as the tray and
|
|
169
|
+
render the same track Discord renders.
|
|
170
|
+
|
|
152
171
|
## Tray
|
|
153
172
|
|
|
154
173
|
<p align="center">
|