refrain 0.2.2__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.
Files changed (104) hide show
  1. {refrain-0.2.2 → refrain-0.2.3}/.github/ISSUE_TEMPLATE/bug_report.yml +1 -1
  2. {refrain-0.2.2 → refrain-0.2.3}/.gitignore +1 -0
  3. {refrain-0.2.2 → refrain-0.2.3}/CHANGELOG.md +124 -1
  4. {refrain-0.2.2 → refrain-0.2.3}/PKG-INFO +5 -4
  5. {refrain-0.2.2 → refrain-0.2.3}/README.md +4 -3
  6. {refrain-0.2.2 → refrain-0.2.3}/ROADMAP.md +50 -0
  7. refrain-0.2.3/docs/test-matrix.md +169 -0
  8. {refrain-0.2.2 → refrain-0.2.3}/packaging/appimage/AppImageBuilder.yml +1 -1
  9. refrain-0.2.3/packaging/aur/refrain/.SRCINFO +19 -0
  10. {refrain-0.2.2 → refrain-0.2.3}/packaging/aur/refrain/PKGBUILD +8 -2
  11. {refrain-0.2.2 → refrain-0.2.3}/packaging/flatpak/io.github.Rockykln.Refrain.metainfo.xml +21 -0
  12. {refrain-0.2.2 → refrain-0.2.3}/pyproject.toml +1 -1
  13. refrain-0.2.3/src/refrain/__init__.py +1 -0
  14. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/app.py +163 -9
  15. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/autostart.py +25 -15
  16. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/config.py +14 -1
  17. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/daemon.py +30 -23
  18. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/discord_rpc.py +65 -2
  19. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/logging_setup.py +19 -11
  20. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/sources/bluetooth.py +31 -2
  21. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/sources/mpris.py +10 -2
  22. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/sources/mpris_server.py +11 -3
  23. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/timing.py +36 -0
  24. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/ui/log_window.py +6 -1
  25. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/ui/settings_window.py +17 -0
  26. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/ui/update_dialog.py +54 -4
  27. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/ui/welcome_dialog.py +12 -4
  28. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/updater.py +57 -4
  29. {refrain-0.2.2 → refrain-0.2.3}/tests/test_autostart.py +28 -0
  30. {refrain-0.2.2 → refrain-0.2.3}/tests/test_timing.py +47 -1
  31. refrain-0.2.2/src/refrain/__init__.py +0 -1
  32. {refrain-0.2.2 → refrain-0.2.3}/.editorconfig +0 -0
  33. {refrain-0.2.2 → refrain-0.2.3}/.gitattributes +0 -0
  34. {refrain-0.2.2 → refrain-0.2.3}/.github/FUNDING.yml +0 -0
  35. {refrain-0.2.2 → refrain-0.2.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  36. {refrain-0.2.2 → refrain-0.2.3}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  37. {refrain-0.2.2 → refrain-0.2.3}/.github/codeql/codeql-config.yml +0 -0
  38. {refrain-0.2.2 → refrain-0.2.3}/.github/dependabot.yml +0 -0
  39. {refrain-0.2.2 → refrain-0.2.3}/.github/pull_request_template.md +0 -0
  40. {refrain-0.2.2 → refrain-0.2.3}/.github/workflows/codeql.yml +0 -0
  41. {refrain-0.2.2 → refrain-0.2.3}/.github/workflows/release.yml +0 -0
  42. {refrain-0.2.2 → refrain-0.2.3}/.github/workflows/security.yml +0 -0
  43. {refrain-0.2.2 → refrain-0.2.3}/.github/workflows/tests.yml +0 -0
  44. {refrain-0.2.2 → refrain-0.2.3}/CODE_OF_CONDUCT.md +0 -0
  45. {refrain-0.2.2 → refrain-0.2.3}/CONTRIBUTING.md +0 -0
  46. {refrain-0.2.2 → refrain-0.2.3}/LICENSE +0 -0
  47. {refrain-0.2.2 → refrain-0.2.3}/Makefile +0 -0
  48. {refrain-0.2.2 → refrain-0.2.3}/SECURITY.md +0 -0
  49. {refrain-0.2.2 → refrain-0.2.3}/docs/architecture.md +0 -0
  50. {refrain-0.2.2 → refrain-0.2.3}/docs/faq.md +0 -0
  51. {refrain-0.2.2 → refrain-0.2.3}/docs/screenshots/README.md +0 -0
  52. {refrain-0.2.2 → refrain-0.2.3}/docs/screenshots/discord-rpc.png +0 -0
  53. {refrain-0.2.2 → refrain-0.2.3}/docs/screenshots/live-log.png +0 -0
  54. {refrain-0.2.2 → refrain-0.2.3}/docs/screenshots/notification.png +0 -0
  55. {refrain-0.2.2 → refrain-0.2.3}/docs/screenshots/settings-advanced.png +0 -0
  56. {refrain-0.2.2 → refrain-0.2.3}/docs/screenshots/settings-general.png +0 -0
  57. {refrain-0.2.2 → refrain-0.2.3}/docs/screenshots/settings-sources.png +0 -0
  58. {refrain-0.2.2 → refrain-0.2.3}/docs/screenshots/settings-updates.png +0 -0
  59. {refrain-0.2.2 → refrain-0.2.3}/docs/screenshots/tray-menu.png +0 -0
  60. {refrain-0.2.2 → refrain-0.2.3}/docs/screenshots/update-dialog.png +0 -0
  61. {refrain-0.2.2 → refrain-0.2.3}/docs/screenshots/welcome.png +0 -0
  62. {refrain-0.2.2 → refrain-0.2.3}/packaging/README.md +0 -0
  63. {refrain-0.2.2 → refrain-0.2.3}/packaging/aur/refrain-git/PKGBUILD +0 -0
  64. {refrain-0.2.2 → refrain-0.2.3}/packaging/flatpak/io.github.Rockykln.Refrain.desktop +0 -0
  65. {refrain-0.2.2 → refrain-0.2.3}/packaging/flatpak/io.github.Rockykln.Refrain.yaml +0 -0
  66. {refrain-0.2.2 → refrain-0.2.3}/packaging/flatpak/python-deps.json +0 -0
  67. {refrain-0.2.2 → refrain-0.2.3}/requirements-dev.txt +0 -0
  68. {refrain-0.2.2 → refrain-0.2.3}/requirements.txt +0 -0
  69. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/__main__.py +0 -0
  70. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/assets/icons/github-mark.svg +0 -0
  71. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/assets/icons/refrain.png +0 -0
  72. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/assets/icons/refrain.svg +0 -0
  73. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/assets/icons/tray-paused-dark.svg +0 -0
  74. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/assets/icons/tray-paused.svg +0 -0
  75. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/assets/icons/tray-playing-dark.svg +0 -0
  76. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/assets/icons/tray-playing.svg +0 -0
  77. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/assets/icons/tray-stopped-dark.svg +0 -0
  78. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/assets/icons/tray-stopped.svg +0 -0
  79. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/assets/refrain.desktop +0 -0
  80. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/cover_art.py +0 -0
  81. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/cover_fetcher.py +0 -0
  82. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/i18n/refrain_de.qm +0 -0
  83. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/i18n/refrain_de.ts +0 -0
  84. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/paths.py +0 -0
  85. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/single_instance.py +0 -0
  86. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/sources/__init__.py +0 -0
  87. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/sources/base.py +0 -0
  88. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/ui/__init__.py +0 -0
  89. {refrain-0.2.2 → refrain-0.2.3}/src/refrain/ui/tray.py +0 -0
  90. {refrain-0.2.2 → refrain-0.2.3}/tests/__init__.py +0 -0
  91. {refrain-0.2.2 → refrain-0.2.3}/tests/conftest.py +0 -0
  92. {refrain-0.2.2 → refrain-0.2.3}/tests/test_config.py +0 -0
  93. {refrain-0.2.2 → refrain-0.2.3}/tests/test_cover_art.py +0 -0
  94. {refrain-0.2.2 → refrain-0.2.3}/tests/test_cover_fetcher.py +0 -0
  95. {refrain-0.2.2 → refrain-0.2.3}/tests/test_daemon_album_format.py +0 -0
  96. {refrain-0.2.2 → refrain-0.2.3}/tests/test_daemon_idle.py +0 -0
  97. {refrain-0.2.2 → refrain-0.2.3}/tests/test_discord_listening.py +0 -0
  98. {refrain-0.2.2 → refrain-0.2.3}/tests/test_imports.py +0 -0
  99. {refrain-0.2.2 → refrain-0.2.3}/tests/test_mpris_dispatch.py +0 -0
  100. {refrain-0.2.2 → refrain-0.2.3}/tests/test_paths.py +0 -0
  101. {refrain-0.2.2 → refrain-0.2.3}/tests/test_sources_base.py +0 -0
  102. {refrain-0.2.2 → refrain-0.2.3}/tests/test_tray_init.py +0 -0
  103. {refrain-0.2.2 → refrain-0.2.3}/tests/test_update_dialog_body.py +0 -0
  104. {refrain-0.2.2 → refrain-0.2.3}/tests/test_updater.py +0 -0
@@ -13,7 +13,7 @@ body:
13
13
  attributes:
14
14
  label: Refrain version
15
15
  description: Run `refrain --version` or check the bottom-right of the settings window.
16
- placeholder: "0.1.4"
16
+ placeholder: "0.2.3"
17
17
  validations:
18
18
  required: true
19
19
 
@@ -39,6 +39,7 @@ env/
39
39
  # Local config / runtime
40
40
  *.log
41
41
  .refrain.local.toml
42
+ *.local.md
42
43
 
43
44
  # Build artifacts (Flatpak / packaging)
44
45
  .flatpak-builder/
@@ -7,6 +7,122 @@ 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
+
10
126
  ## [0.2.2] - 2026-05-06
11
127
 
12
128
  Two roadmap features pulled forward from v0.3 plus a substantial batch
@@ -568,7 +684,14 @@ with a proper, installable Linux app.
568
684
  pip-audit, trufflehog, release), Dependabot, issue + PR templates,
569
685
  `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`.
570
686
 
571
- [Unreleased]: https://github.com/Rockykln/refrain/compare/v0.1.2...HEAD
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
572
695
  [0.1.2]: https://github.com/Rockykln/refrain/compare/v0.1.1...v0.1.2
573
696
  [0.1.1]: https://github.com/Rockykln/refrain/compare/v0.1.0...v0.1.1
574
697
  [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.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
- - KDE Plasma 6 (Wayland) — primary target
138
- - KDE Plasma 6 (X11)
139
- - GNOME (with the [AppIndicator and KStatusNotifierItem](https://extensions.gnome.org/extension/615/appindicator-support/) extension)
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
 
@@ -100,9 +100,10 @@ and `~/.local/share/icons/`. To undo: `refrain --uninstall-desktop`.
100
100
 
101
101
  ### Tested on
102
102
 
103
- - KDE Plasma 6 (Wayland) — primary target
104
- - KDE Plasma 6 (X11)
105
- - GNOME (with the [AppIndicator and KStatusNotifierItem](https://extensions.gnome.org/extension/615/appindicator-support/) extension)
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
 
@@ -169,6 +169,56 @@ 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
+
172
222
  ## Up next — v0.3
173
223
 
174
224
  - **Polishing the wrapped i18n surface** — wrap the remaining
@@ -0,0 +1,169 @@
1
+ # Test matrix
2
+
3
+ The set of distros + desktop combinations Refrain is expected to work on,
4
+ and a six-step smoke test to run on each before declaring a release good
5
+ to ship. Tick boxes as you verify; leave a date + Refrain version in the
6
+ "Last verified" column.
7
+
8
+ ## How to use this document
9
+
10
+ For each row in the matrix:
11
+
12
+ 1. Run through the six checks under [Smoke checks](#smoke-checks).
13
+ 2. If everything passes, tick the row's checkbox and write the date +
14
+ Refrain version into "Last verified".
15
+ 3. If something fails, file an issue and leave the row unticked. Note
16
+ the failing check in "Notes".
17
+
18
+ A row failing one minor optional check (e.g. notifications missing
19
+ because `notify-send` isn't installed) is fine — that's documented
20
+ graceful-degradation, not a regression.
21
+
22
+ ## Tier 1 — primary support, must pass every release
23
+
24
+ These are the eight systems that together cover ≈ 95 % of the realistic
25
+ user base. CachyOS / KDE Plasma 6 / Wayland is the maintainer's daily
26
+ driver and should be tested on every commit; the rest before each tag.
27
+
28
+ | ✓ | Distro | Desktop | Display server | Channel | Last verified | Notes |
29
+ |---|---|---|---|---|---|---|
30
+ | [ ] | **CachyOS** (rolling) | KDE Plasma 6 | Wayland | AUR `refrain` | | maintainer daily |
31
+ | [ ] | **Arch Linux** (rolling) | KDE Plasma 6 | X11 | AUR `refrain` | | alternate display server |
32
+ | [ ] | **Fedora 42 Workstation** | GNOME 47 | Wayland | AppImage + PyPI | | RPM world + GNOME tray ext. |
33
+ | [ ] | **Ubuntu 24.04 LTS** | GNOME 46 | Wayland | AppImage | | LTS, glibc floor |
34
+ | ❌ | **Ubuntu 25.04** | GNOME 48 | Wayland | PyPI | 2026-05-07 / v0.2.2 | Tested + **failed**. Settings window came up with the gnome-control-center icon (Wayland app-id matching heuristic; fixed in `1e759ba` by setting per-window icon explicitly). Snap-sandboxed Firefox + Chrome both refused to publish MPRIS so Refrain saw no players — distro-external, workaround is a deb-channel browser (Brave / Mozilla apt repo). 25 s `org.bluez` activation timeout per poll when bluez isn't present (fast-fail via `NameHasOwner` added in follow-up). Re-test with v0.2.3+ once those fixes ship. |
35
+ | [ ] | **Debian 13** (Trixie) | KDE Plasma 6 | Wayland | AppImage | | Plasma outside Arch |
36
+ | [ ] | **openSUSE Tumbleweed** | KDE Plasma 6 | Wayland | PyPI in venv | | rolling non-Arch |
37
+ | [ ] | **Linux Mint 22** | Cinnamon 6 | X11 | AppImage | | Cinnamon tray, X11 |
38
+ | [ ] | **Manjaro** Stable | KDE Plasma 6 | Wayland | AUR `refrain` | | delayed Arch mirror |
39
+
40
+ ## Tier 2 — supported, spot-check
41
+
42
+ Less frequent verification — once per minor release is enough.
43
+
44
+ | ✓ | Distro | Desktop | Channel | Notes |
45
+ |---|---|---|---|---|
46
+ | [ ] | **EndeavourOS** | KDE | AUR `refrain` | Arch derivative |
47
+ | [ ] | **Garuda Linux** | KDE | AUR `refrain` | Arch derivative |
48
+ | [ ] | **Pop!_OS 24.04** | GNOME (or COSMIC) | AppImage | COSMIC tray TBD |
49
+ | [ ] | **KDE Neon** (User) | KDE Plasma 6 | PyPI / AppImage | Plasma testing target |
50
+ | [ ] | **Ubuntu 25.04** | GNOME | PyPI in venv | Python 3.13 |
51
+ | [ ] | **Debian 12** (Bookworm) | KDE / GNOME | AppImage | Python 3.11, Qt 5 — AppImage required |
52
+ | [ ] | **Fedora 41 KDE Spin** | KDE Plasma 6 | Wayland | AppImage / PyPI | Fedora KDE flavour |
53
+ | [ ] | **NixOS** unstable | KDE / GNOME | PyPI in nix-shell | flake.nix would be nice-to-have |
54
+
55
+ ## Tier 3 — desktop / compositor edge cases
56
+
57
+ Not full distro entries — just verify the tray + tray icon rendering
58
+ on these specifically.
59
+
60
+ | ✓ | Compositor / DE | Tray status | Notes |
61
+ |---|---|---|---|
62
+ | [ ] | KDE Plasma 6 — Wayland | native ✓ | primary target |
63
+ | [ ] | KDE Plasma 6 — X11 | native ✓ | |
64
+ | [ ] | GNOME 45+ | needs [AppIndicator extension](https://extensions.gnome.org/extension/615/appindicator-support/) | refuses start without it |
65
+ | [ ] | XFCE 4.18+ | OK with `xfce4-statusnotifier-plugin` | |
66
+ | [ ] | Cinnamon 6 | native ✓ | |
67
+ | [ ] | LXQt 2 | native ✓ | |
68
+ | [ ] | Budgie | OK via AppIndicator | |
69
+ | [ ] | MATE | needs `mate-applet-statusnotifier` | |
70
+ | [ ] | Hyprland / Sway / i3 | needs SNI-capable bar (waybar `tray` module, polybar) | |
71
+ | [ ] | Pantheon (elementary) | unofficial SNI support | untested |
72
+ | [ ] | COSMIC (Pop!_OS 24.04+) | own protocol | untested |
73
+
74
+ ## Out of scope (won't work)
75
+
76
+ These are documented as **unsupported** — don't open issues for them.
77
+
78
+ | Distro | Reason |
79
+ |---|---|
80
+ | Debian 11 (Bullseye) | Python 3.9 — too old |
81
+ | Ubuntu 20.04 / 22.04 LTS | Python 3.8 / 3.10 too old; AppImage glibc floor too high for 22.04 |
82
+ | Linux Mint 21.x | Python 3.10 — too old |
83
+ | **CentOS Stream 10** | Tested 2026-05-07 against v0.2.2 — **failed**. AppImage in mounted mode needs `fuse` (not just `fuse-libs`) which the docs didn't surface; in `--appimage-extract-and-run` mode raises `No module named refrain` on a fresh extract. PyPI install starts but GNOME default ships no AppIndicator package out of the box — Refrain refuses to launch with "No system tray". Reachable only with manual Extension-Manager dance. Not Tier-1/2 worth. |
84
+ | CentOS Stream 9 | glibc 2.34 < 2.35 (AppImage breaks); Python 3.9 default |
85
+ | RHEL 9 / Rocky 9 / AlmaLinux 9 | same family as CentOS Stream 9 |
86
+ | RHEL 8 / Rocky 8 / AlmaLinux 8 | glibc 2.28; Python 3.6 default |
87
+ | Alpine Linux | musl libc — PySide6 wheels are glibc-only |
88
+ | openSUSE Leap 15.5 | Python 3.6 default |
89
+
90
+ ## Smoke checks
91
+
92
+ Run all six on every Tier 1 row, in order. Each should take under a
93
+ minute; the whole sweep is ≈ 5 minutes per system.
94
+
95
+ ### 1. Tray + theme parity
96
+
97
+ - Refrain icon appears in the system tray on launch.
98
+ - Icon style matches the system theme (light glyph on dark panel,
99
+ dark glyph on light panel).
100
+ - Settings window styling matches the rest of the desktop (Breeze
101
+ on KDE, Adwaita-via-Qt on GNOME, etc.). The pip / pipx / AppImage
102
+ builds should look the same as the AUR / system build because of
103
+ the Qt-plugin-path augmentation in `app.py`.
104
+
105
+ ### 2. Settings round-trip
106
+
107
+ - Tray → *Settings…* opens the window.
108
+ - Toggle any setting (e.g. *Notifications*), hit *Apply*.
109
+ - Window closes; setting persists in `~/.config/refrain/config.toml`.
110
+ - No console errors, no Qt crashes.
111
+
112
+ ### 3. MPRIS / Apple Music — Discord push
113
+
114
+ - Open `https://music.apple.com` in any major Linux browser.
115
+ - Start playing a track.
116
+ - Within ≈ 1 s, the tray menu shows the track title + artist.
117
+ - Within ≈ 2 s, Discord status shows "Listening to ..." with the
118
+ cover art.
119
+ - **Duration check**: progress bar in Discord shows the *correct*
120
+ total length for the track (e.g. a 2:11 song shows 2:11, not 0:14
121
+ or 7:21).
122
+
123
+ ### 4. Bluetooth source (optional, hardware-dependent)
124
+
125
+ Skip if no AVRCP-capable Bluetooth source is available. Otherwise:
126
+
127
+ - Pair + connect a phone playing music via Bluetooth.
128
+ - Tray menu shows the track within ≈ 1 s.
129
+ - Discord status pushes the metadata.
130
+ - Per-source `client_id_bluetooth` (if set) routes to the right
131
+ Discord application.
132
+
133
+ ### 5. Update dialog cleanup
134
+
135
+ Triggered automatically when a newer version exists; or set
136
+ `update.last_check_ts = 0` in the config and restart.
137
+
138
+ - Update dialog opens with a working *Cancel* button while
139
+ downloading.
140
+ - Hitting Cancel mid-download removes the `*.AppImage.new` file
141
+ next to the binary (only relevant for AppImage installs).
142
+ - Closing the dialog window via the X button while downloading
143
+ also cancels cleanly.
144
+ - After cancel, no orphan `.new` file is left behind.
145
+
146
+ ### 6. Restart cycle
147
+
148
+ - Tray → *Restart Refrain*.
149
+ - The process exits cleanly, releases the D-Bus name
150
+ `io.github.Rockykln.Refrain`, and re-execs itself.
151
+ - Tray icon disappears and reappears within ≈ 2 s.
152
+ - Discord RPC reconnects and the same track shows up again.
153
+ - `refrain.log` has a `Re-execing for restart` line followed by a
154
+ fresh `Refrain ... starting` line — no duplicate D-Bus name
155
+ errors.
156
+
157
+ ## Failure-mode reference
158
+
159
+ If a check fails, the most common causes:
160
+
161
+ | Symptom | Likely cause |
162
+ |---|---|
163
+ | Refrain refuses to start with "No system tray" | Missing AppIndicator extension on GNOME, or bar with no tray on tiling WM |
164
+ | Tray icon visible but no track shown when playing in browser | `plasma-browser-integration` not enabled in browser, or `mpris_enabled = false` |
165
+ | Discord status never appears | No `client_id` set, or Discord IPC socket not reachable (Snap/Flatpak Discord can hide it) |
166
+ | Cover art missing | iTunes Search has no match for that artist / title — fallback brand icon shown |
167
+ | Settings window has no icons / wrong style on pip/pipx install | Qt plugin-path augmentation didn't fire — check log for `Augmenting Qt plugin path` |
168
+ | Duration shows as 0:14 or some too-short value | Pre-`pick_effective_duration_ms` build, or iTunes lookup hasn't resolved yet for that track |
169
+ | AppImage fails with `failed to exec fusermount` (RHEL family) | `fuse-libs` alone is not enough — install the `fuse` package too, or run with `--appimage-extract-and-run` |
@@ -30,7 +30,7 @@ AppDir:
30
30
  id: io.github.Rockykln.Refrain
31
31
  name: Refrain
32
32
  icon: refrain
33
- version: 0.2.2
33
+ version: 0.2.3
34
34
  exec: usr/bin/python3
35
35
  exec_args: -m refrain $@
36
36
 
@@ -0,0 +1,19 @@
1
+ pkgbase = refrain
2
+ pkgdesc = Discord Rich Presence for Apple Music on Linux
3
+ pkgver = 0.2.2
4
+ pkgrel = 1
5
+ url = https://github.com/Rockykln/refrain
6
+ arch = any
7
+ license = custom:Refrain
8
+ makedepends = python-build
9
+ makedepends = python-installer
10
+ makedepends = python-hatchling
11
+ makedepends = python-wheel
12
+ depends = python>=3.11
13
+ depends = python-pypresence
14
+ depends = python-dbus
15
+ depends = pyside6
16
+ source = refrain-0.2.2.tar.gz::https://github.com/Rockykln/refrain/archive/v0.2.2.tar.gz
17
+ sha256sums = db07e0783b3f44a7c54d4b8ee2ebe33f3dc577707a03bd42f5958ddae3d5ac12
18
+
19
+ pkgname = refrain
@@ -1,7 +1,7 @@
1
1
  # Maintainer: Rockykln <contact@rockykln.com>
2
2
 
3
3
  pkgname=refrain
4
- pkgver=0.2.2
4
+ pkgver=0.2.3
5
5
  pkgrel=1
6
6
  pkgdesc="Discord Rich Presence for Apple Music on Linux"
7
7
  arch=('any')
@@ -13,6 +13,9 @@ depends=(
13
13
  'python-dbus'
14
14
  'pyside6'
15
15
  )
16
+ optdepends=(
17
+ 'python-gobject: enables MPRIS-server publication so Plasma media controls reach Refrain'
18
+ )
16
19
  makedepends=(
17
20
  'python-build'
18
21
  'python-installer'
@@ -20,7 +23,10 @@ makedepends=(
20
23
  'python-wheel'
21
24
  )
22
25
  source=("$pkgname-$pkgver.tar.gz::https://github.com/Rockykln/refrain/archive/v$pkgver.tar.gz")
23
- sha256sums=('SKIP') # set after release tag is pushed: updpkgsums
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
+ sha256sums=('SKIP')
24
30
 
25
31
  build() {
26
32
  cd "$pkgname-$pkgver"
@@ -51,6 +51,27 @@
51
51
  <content_rating type="oars-1.1" />
52
52
 
53
53
  <releases>
54
+ <release version="0.2.3" date="2026-05-07">
55
+ <description>
56
+ <p>
57
+ Reliability + portability pass. Sandbox-aware Discord IPC
58
+ discovery so Snap and Flatpak Discord builds are reachable
59
+ out of the box, Qt style plugin path augmentation extended
60
+ to Debian / Ubuntu / Fedora / RHEL / openSUSE layouts so
61
+ pip / pipx installs render in the system theme on those
62
+ distros (was Arch-only in 0.2.2), Bluetooth fast-fail so
63
+ VMs without bluez don't stall the poll cycle for 25 s, and
64
+ "No system tray" advice extended past the GNOME
65
+ AppIndicator hint to cover MATE, XFCE, and tiling
66
+ window managers. Plus a Cancel button on the update
67
+ dialog, orphan-download self-heal, per-window icons (no
68
+ more gnome-control-center gear icon on Wayland), an
69
+ iTunes-catalog duration override that fixes Discord briefly
70
+ flipping a 2:11 song's total to 0:14 mid-track, and a
71
+ six-step distro test matrix in docs/test-matrix.md.
72
+ </p>
73
+ </description>
74
+ </release>
54
75
  <release version="0.2.2" date="2026-05-06">
55
76
  <description>
56
77
  <p>
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "refrain"
7
- version = "0.2.2"
7
+ version = "0.2.3"
8
8
  description = "Discord Rich Presence for Apple Music on Linux"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -0,0 +1 @@
1
+ __version__ = "0.2.3"