refrain 0.2.0__tar.gz → 0.2.2__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.2}/.github/workflows/release.yml +6 -2
- {refrain-0.2.0 → refrain-0.2.2}/.github/workflows/tests.yml +7 -2
- {refrain-0.2.0 → refrain-0.2.2}/.gitignore +1 -1
- {refrain-0.2.0 → refrain-0.2.2}/CHANGELOG.md +203 -0
- {refrain-0.2.0 → refrain-0.2.2}/CONTRIBUTING.md +21 -18
- {refrain-0.2.0 → refrain-0.2.2}/PKG-INFO +21 -3
- {refrain-0.2.0 → refrain-0.2.2}/README.md +20 -2
- {refrain-0.2.0 → refrain-0.2.2}/ROADMAP.md +66 -5
- refrain-0.2.2/docs/screenshots/settings-advanced.png +0 -0
- refrain-0.2.2/docs/screenshots/settings-general.png +0 -0
- refrain-0.2.2/docs/screenshots/settings-sources.png +0 -0
- refrain-0.2.2/docs/screenshots/settings-updates.png +0 -0
- refrain-0.2.2/docs/screenshots/welcome.png +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/packaging/appimage/AppImageBuilder.yml +1 -1
- {refrain-0.2.0 → refrain-0.2.2}/packaging/aur/refrain/PKGBUILD +2 -2
- {refrain-0.2.0 → refrain-0.2.2}/packaging/flatpak/io.github.Rockykln.Refrain.metainfo.xml +35 -0
- {refrain-0.2.0 → refrain-0.2.2}/pyproject.toml +4 -5
- refrain-0.2.2/src/refrain/__init__.py +1 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/app.py +115 -24
- refrain-0.2.2/src/refrain/assets/icons/refrain.png +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/config.py +37 -5
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/cover_art.py +42 -4
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/cover_fetcher.py +21 -4
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/daemon.py +284 -116
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/discord_rpc.py +7 -1
- refrain-0.2.2/src/refrain/i18n/refrain_de.qm +0 -0
- refrain-0.2.2/src/refrain/i18n/refrain_de.ts +483 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/logging_setup.py +8 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/sources/bluetooth.py +5 -5
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/sources/mpris.py +149 -52
- refrain-0.2.2/src/refrain/sources/mpris_server.py +360 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/timing.py +12 -1
- refrain-0.2.2/src/refrain/ui/settings_window.py +683 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/ui/tray.py +40 -30
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/ui/welcome_dialog.py +127 -29
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/updater.py +46 -7
- {refrain-0.2.0 → refrain-0.2.2}/tests/test_config.py +10 -4
- {refrain-0.2.0 → refrain-0.2.2}/tests/test_cover_art.py +6 -3
- {refrain-0.2.0 → refrain-0.2.2}/tests/test_daemon_album_format.py +22 -5
- {refrain-0.2.0 → refrain-0.2.2}/tests/test_daemon_idle.py +26 -3
- {refrain-0.2.0 → refrain-0.2.2}/tests/test_mpris_dispatch.py +5 -5
- refrain-0.2.2/tests/test_tray_init.py +86 -0
- {refrain-0.2.0 → refrain-0.2.2}/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/ui/settings_window.py +0 -342
- {refrain-0.2.0 → refrain-0.2.2}/.editorconfig +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/.gitattributes +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/.github/FUNDING.yml +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/.github/codeql/codeql-config.yml +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/.github/dependabot.yml +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/.github/pull_request_template.md +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/.github/workflows/codeql.yml +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/.github/workflows/security.yml +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/CODE_OF_CONDUCT.md +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/LICENSE +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/Makefile +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/SECURITY.md +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/docs/architecture.md +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/docs/faq.md +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/docs/screenshots/README.md +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/docs/screenshots/discord-rpc.png +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/docs/screenshots/live-log.png +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/docs/screenshots/notification.png +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/docs/screenshots/tray-menu.png +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/docs/screenshots/update-dialog.png +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/packaging/README.md +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/packaging/aur/refrain-git/PKGBUILD +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/packaging/flatpak/io.github.Rockykln.Refrain.desktop +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/packaging/flatpak/io.github.Rockykln.Refrain.yaml +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/packaging/flatpak/python-deps.json +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/requirements-dev.txt +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/requirements.txt +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/__main__.py +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/assets/icons/github-mark.svg +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/assets/icons/refrain.svg +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/assets/icons/tray-paused-dark.svg +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/assets/icons/tray-paused.svg +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/assets/icons/tray-playing-dark.svg +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/assets/icons/tray-playing.svg +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/assets/icons/tray-stopped-dark.svg +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/assets/icons/tray-stopped.svg +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/assets/refrain.desktop +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/autostart.py +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/paths.py +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/single_instance.py +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/sources/__init__.py +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/sources/base.py +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/ui/__init__.py +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/ui/log_window.py +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/src/refrain/ui/update_dialog.py +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/tests/__init__.py +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/tests/conftest.py +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/tests/test_autostart.py +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/tests/test_cover_fetcher.py +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/tests/test_discord_listening.py +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/tests/test_imports.py +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/tests/test_paths.py +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/tests/test_sources_base.py +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/tests/test_timing.py +0 -0
- {refrain-0.2.0 → refrain-0.2.2}/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,209 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.2] - 2026-05-06
|
|
11
|
+
|
|
12
|
+
Two roadmap features pulled forward from v0.3 plus a substantial batch
|
|
13
|
+
of reliability + UX work driven by live-testing the v0.2.1 build.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **Multiple Discord profiles** — per-source `client_id_mpris` and
|
|
18
|
+
`client_id_bluetooth` fields in `Config.discord` plus matching UI in
|
|
19
|
+
Settings → General → Discord. Empty falls back to the default
|
|
20
|
+
Client ID. The daemon reconnects RPC under the source-specific ID
|
|
21
|
+
the moment the active source flips, so each source can render with
|
|
22
|
+
its own application name + uploaded artwork in the user's profile.
|
|
23
|
+
- **MPRIS-server mode** — Refrain registers itself as
|
|
24
|
+
`org.mpris.MediaPlayer2.refrain` on the session bus so KDE Plasma's
|
|
25
|
+
panel media-controls applet (and KDE Connect, GNOME Shell, Mako
|
|
26
|
+
notifications, …) drive the same Play/Pause/Next/Previous as the
|
|
27
|
+
tray, and render the same track Discord renders. Implemented with
|
|
28
|
+
`dbus-python` + a daemon GLib main loop running in its own thread
|
|
29
|
+
so it doesn't compete with Qt's event loop. Falls back gracefully
|
|
30
|
+
when PyGObject isn't installed.
|
|
31
|
+
- **iTunes track duration in the cover-fetcher cache** —
|
|
32
|
+
`trackTimeMillis` from the iTunes Search API rides along with
|
|
33
|
+
`cover_url` / `song_url` in the on-disk cache and exposed via
|
|
34
|
+
`CoverFetcher.get_duration_ms()`. Used as a defensive fallback when
|
|
35
|
+
MPRIS reports an obviously-wrong preview-clip length.
|
|
36
|
+
- **Per-player MPRIS timeout blacklist** — if a property `Get` times
|
|
37
|
+
out we banish that bus name for 5 s so a hung player (typically
|
|
38
|
+
plasma-browser-integration on a frozen Apple Music tab) can't keep
|
|
39
|
+
eating the daemon's poll cycle one tick at a time.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- **Welcome wizard** redesigned: 56×56 icon-badge + title + subtitle
|
|
44
|
+
header, intro line about tray-driven controls, dedicated diagnostics
|
|
45
|
+
card with two clearly-labelled rows (Discord / iTunes), Discord
|
|
46
|
+
Application ID block with helper text + readable italic styling
|
|
47
|
+
(the previous `palette(mid)` tone went invisible on Plasma Breeze
|
|
48
|
+
dark themes), Skip ghost-style on the left + Apply primary on the
|
|
49
|
+
right.
|
|
50
|
+
- **Welcome wizard now opens Settings on confirmation** instead of
|
|
51
|
+
silently dropping into the tray, and reloads the Settings form so
|
|
52
|
+
the just-saved Discord Client ID appears in the input field.
|
|
53
|
+
- **`compute_rpc_start_ts` accepts `is_preview_clip=True`** to skip
|
|
54
|
+
the drift-resync path on tracks whose MPRIS Position field loops
|
|
55
|
+
0→8s instead of advancing monotonically. Discord's elapsed counter
|
|
56
|
+
no longer resets every ~8 s on preview-mode playback.
|
|
57
|
+
- **Reset-all-settings preserves every Discord Client ID** (default +
|
|
58
|
+
per-source mpris + per-source bluetooth) and the dialog text now
|
|
59
|
+
states this explicitly. Reset-dialog button labels are localised
|
|
60
|
+
(`Reset` / `Zurücksetzen` / `Cancel` / `Abbrechen`) instead of the
|
|
61
|
+
generic Yes / No.
|
|
62
|
+
- **Default `notify_delay_ms` lowered from 600 ms to 0 ms** — cover
|
|
63
|
+
cache hits fire instantly, cache misses still get the existing
|
|
64
|
+
retry loop up to 2 s for the iTunes download.
|
|
65
|
+
- **Default `poll_interval_ms` lowered from 1 s to 500 ms** — track
|
|
66
|
+
changes, position updates, and tray controls feel noticeably more
|
|
67
|
+
responsive without measurable CPU impact.
|
|
68
|
+
- **Idle detection skips preview-clip-length tracks** (duration <
|
|
69
|
+
30 s) — Apple Music keeps reporting the same metadata while a
|
|
70
|
+
preview replays, and clearing Discord under those conditions hid
|
|
71
|
+
the activity mid-listen.
|
|
72
|
+
- **Idle-detection log fires once per dangling-track instance**
|
|
73
|
+
instead of every poll tick.
|
|
74
|
+
|
|
75
|
+
### Fixed
|
|
76
|
+
|
|
77
|
+
- **Discord RPC connect now happens within ~1 s of the first track
|
|
78
|
+
detection** (was: up to 51 s). Root cause: dbus-python's default
|
|
79
|
+
25 s reply timeout combined with plasma-browser-integration
|
|
80
|
+
introspection hangs blocked the daemon's poll. Fix: pass
|
|
81
|
+
`introspect=False` on every `bus.get_object` and `timeout=0.5` on
|
|
82
|
+
every property `Get` and method dispatch.
|
|
83
|
+
- **Discord RPC reconnects eagerly when the user changes their
|
|
84
|
+
Application ID** (welcome wizard or Settings → General). Previously
|
|
85
|
+
the new pipe wasn't established until the next *playing-state*
|
|
86
|
+
poll, so paused users sat there waiting forever.
|
|
87
|
+
- **Skip / Previous reach Apple Music reliably**: dispatch tries
|
|
88
|
+
the browser-native MPRIS player (chromium / firefox) first, then
|
|
89
|
+
falls back to plasma-browser-integration. Plasma's wrapper claims
|
|
90
|
+
`CanGoNext=True` on Apple Music but its Next routes through a
|
|
91
|
+
flaky mediaSession path; the browser-native player reaches the
|
|
92
|
+
same handler via a more reliable path.
|
|
93
|
+
- **Per-property `_safe_get` inside MPRIS read** — chromium MPRIS
|
|
94
|
+
rejecting one optional property (notably `DesktopEntry`, with a
|
|
95
|
+
generic `Error.Failed`) no longer drops the whole player from our
|
|
96
|
+
candidate list, which previously hid the chromium player entirely
|
|
97
|
+
from the skip-fallback chain.
|
|
98
|
+
- **All apple-music candidates** beyond the metadata winner join the
|
|
99
|
+
control-fallback list, so the chromium player is reachable for
|
|
100
|
+
skip dispatches even when plasma wins the metadata pick.
|
|
101
|
+
- **Notification fires immediately on cover-cache hits** (50 ms vs.
|
|
102
|
+
the previous unconditional 600-1500 ms `notify_delay_ms` wait).
|
|
103
|
+
- **Discord progress bar dropped entirely on preview-clip mode**
|
|
104
|
+
(MPRIS duration < 30 s) — no `start`, no `end` in the activity
|
|
105
|
+
payload — so the elapsed timer doesn't loop nonsensically with
|
|
106
|
+
the 8-second preview.
|
|
107
|
+
- **`update_config` invocation from the welcome wizard** now goes
|
|
108
|
+
through `settings.applied.emit(config)` (already QueuedConnection-
|
|
109
|
+
wired to the worker thread) instead of `QMetaObject.invokeMethod
|
|
110
|
+
(… Q_ARG(object, …))`, which PySide6 rejects with "Unable to find
|
|
111
|
+
a QMetaType for 'object'" — that exception was silently breaking
|
|
112
|
+
the post-wizard handshake on first-run.
|
|
113
|
+
- **Welcome wizard X / Esc** marks `first_run_complete=True` (via
|
|
114
|
+
`reject()` override) so the wizard doesn't re-appear on the next
|
|
115
|
+
launch when the user dismisses without confirming.
|
|
116
|
+
- **Welcome wizard Apply with empty ID** asks for confirmation
|
|
117
|
+
before silently saving "no Discord".
|
|
118
|
+
- **Refrain's own published MPRIS bus name skipped** during the
|
|
119
|
+
source read loop — reading our own publish path back was
|
|
120
|
+
circular and added noticeable latency.
|
|
121
|
+
- **`_control` exception handling** — a TypeError in
|
|
122
|
+
`getattr(src, action)()` no longer kills the entire dispatch
|
|
123
|
+
before the follow-up polls fire.
|
|
124
|
+
- **Reset preserves per-source Discord IDs** (`client_id_mpris` /
|
|
125
|
+
`client_id_bluetooth`), not just the default `client_id`.
|
|
126
|
+
- **dbus.proxies / dbus.connection log spam silenced** — their
|
|
127
|
+
Introspect-error tracebacks against unrelated MPRIS players
|
|
128
|
+
drowned the live log in noise that has nothing to do with
|
|
129
|
+
refrain.
|
|
130
|
+
|
|
131
|
+
### Removed
|
|
132
|
+
|
|
133
|
+
- **Dead `_diag_label` shim** in `WelcomeDialog` that was only there
|
|
134
|
+
for a backwards-compat path no longer in use.
|
|
135
|
+
|
|
136
|
+
## [0.2.1] - 2026-05-06
|
|
137
|
+
|
|
138
|
+
Polish release built around the v0.2.0 surface. The Settings window got
|
|
139
|
+
a UI overhaul, two notification cover-art races are gone, and the
|
|
140
|
+
external code-review punch list landed in full (atomic config writes,
|
|
141
|
+
AppImage size validation, `.ts` source files no longer ship in the
|
|
142
|
+
wheel, dead D-Bus watcher module deleted).
|
|
143
|
+
|
|
144
|
+
### Added
|
|
145
|
+
|
|
146
|
+
- **Auto-restart on Discord client_id change** — Apply now triggers an
|
|
147
|
+
in-process re-exec when the client_id field has changed, same
|
|
148
|
+
pattern as the language switch. pypresence binds to the client_id
|
|
149
|
+
at connect time, so a fresh process is the simplest way to re-init
|
|
150
|
+
cleanly.
|
|
151
|
+
- **AppImage update size validation** — `_apply_appimage` now compares
|
|
152
|
+
the downloaded file against the GitHub Releases manifest's
|
|
153
|
+
`appimage_size` before replacing the live binary. A truncated
|
|
154
|
+
download is rejected and the tmp file cleaned up instead of
|
|
155
|
+
bricking the next launch.
|
|
156
|
+
- **MPRIS dispatch logging** — `Next` / `Previous` / `PlayPause` log
|
|
157
|
+
which player actually received the call (INFO level). Diagnoses
|
|
158
|
+
"Next pauses instead of skipping" type bugs where a fallback
|
|
159
|
+
player handles the action wrong.
|
|
160
|
+
- **RPC track-change diagnostics** — log raw `pos`, `dur`, `start_ts`
|
|
161
|
+
on every recompute so duration mismatches (browser MPRIS reporting
|
|
162
|
+
preview-clip lengths, etc.) are visible in the live log.
|
|
163
|
+
|
|
164
|
+
### Changed
|
|
165
|
+
|
|
166
|
+
- **Settings window UI overhaul** — every tab uses a consistent
|
|
167
|
+
`QGroupBox` + form-layout with left-aligned titles via stylesheet
|
|
168
|
+
(overrides Plasma Breeze's centered default). Inputs are
|
|
169
|
+
fixed-width: 220 px default, 360 px for the Discord group whose
|
|
170
|
+
Client ID placeholder + privacy-mode labels need more room.
|
|
171
|
+
`FieldsStayAtSizeHint` + per-widget `setFixedWidth` is the only
|
|
172
|
+
combo that holds across both Fusion (offscreen tests) and Breeze
|
|
173
|
+
(Plasma) — `AllNonFixedFieldsGrow` was ignoring fixed-width caps.
|
|
174
|
+
- **Discord RPC reconnect cap lowered to 15 s** — autostart launches
|
|
175
|
+
refrain before Discord is ready; the previous 60 s ceiling meant
|
|
176
|
+
the user could sit there for almost a minute after Discord
|
|
177
|
+
finished loading before refrain noticed and connected.
|
|
178
|
+
- **Atomic config writes** — `Config.save` now writes to a `.tmp`
|
|
179
|
+
sibling and `os.replace`s into place. A crash mid-write would
|
|
180
|
+
otherwise leave an empty / half-written TOML and refrain
|
|
181
|
+
silently falls back to defaults on parse error, losing every
|
|
182
|
+
setting the user picked.
|
|
183
|
+
- **Discord client_id Apply re-init** — wired `restartRequested`
|
|
184
|
+
emission so picking up a new client_id no longer requires a
|
|
185
|
+
manual restart.
|
|
186
|
+
|
|
187
|
+
### Fixed
|
|
188
|
+
|
|
189
|
+
- **Notification cover flicker** — `notify-send -i` now carries the
|
|
190
|
+
same image as the `--hint string:image-path:...` payload. KDE
|
|
191
|
+
Plasma briefly rendered the `-i refrain` brand badge (~50–100 ms)
|
|
192
|
+
while it loaded the image-path file from disk; using the same
|
|
193
|
+
file for both makes the transition invisible.
|
|
194
|
+
- **Discord activity-card cover flicker** — defer the first RPC
|
|
195
|
+
update for a new track until the iTunes-search cover URL is in
|
|
196
|
+
cache, capped at 3 polls (~3 s). Without this, Discord briefly
|
|
197
|
+
rendered the `refrain` brand fallback before the real cover
|
|
198
|
+
paint, exactly the issue cover-fetcher was supposed to fix.
|
|
199
|
+
- **Group titles centered on Plasma Breeze** — explicit stylesheet
|
|
200
|
+
overrides Breeze's default centered `QGroupBox::title`.
|
|
201
|
+
|
|
202
|
+
### Removed
|
|
203
|
+
|
|
204
|
+
- **Dead `dbus_watcher` module** — 155 lines of `MPRISWatcher` /
|
|
205
|
+
`BluetoothWatcher` machinery that was never wired up after the
|
|
206
|
+
signal-driven path was abandoned for v0.2.x. The companion
|
|
207
|
+
`_refresh_watchers` and `_on_external_change` slots in
|
|
208
|
+
`Daemon` went with it.
|
|
209
|
+
- **`.ts` translation sources from the wheel** — only the compiled
|
|
210
|
+
`.qm` files ship now. Linguist sources stay in the source tree
|
|
211
|
+
for translator PRs.
|
|
212
|
+
|
|
10
213
|
## [0.2.0] - 2026-05-06
|
|
11
214
|
|
|
12
215
|
First minor-version bump. Five planned v0.2 features land together
|
|
@@ -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.2
|
|
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
|
|
@@ -162,7 +162,9 @@ same file; you almost never need to touch it by hand.
|
|
|
162
162
|
|
|
163
163
|
```toml
|
|
164
164
|
[discord]
|
|
165
|
-
client_id = "" #
|
|
165
|
+
client_id = "" # default Application ID — paste yours here
|
|
166
|
+
client_id_mpris = "" # optional per-source override (browser / Apple Music)
|
|
167
|
+
client_id_bluetooth = "" # optional per-source override (Bluetooth headphones)
|
|
166
168
|
|
|
167
169
|
[sources]
|
|
168
170
|
mpris_enabled = true
|
|
@@ -177,12 +179,28 @@ autostart = false
|
|
|
177
179
|
notifications = true
|
|
178
180
|
cover_art = true
|
|
179
181
|
show_buttons = true
|
|
182
|
+
notify_delay_ms = 0 # 0 = fire ASAP; the cover-art retry loop still waits up to ~2 s
|
|
180
183
|
|
|
181
184
|
[advanced]
|
|
182
|
-
poll_interval_ms =
|
|
185
|
+
poll_interval_ms = 500
|
|
183
186
|
log_level = "INFO"
|
|
187
|
+
cover_cache_size = 200 # disk cap for cached covers
|
|
188
|
+
idle_grace_s = 30 # clear status when same track plays past duration + grace; 0 disables
|
|
189
|
+
language = "system" # "system" follows QLocale; "en" / "de" / … force a translation
|
|
184
190
|
```
|
|
185
191
|
|
|
192
|
+
Per-source `client_id_*` fields let Apple Music render under one Discord
|
|
193
|
+
application (with the album-grid as artwork) and Bluetooth headphones under
|
|
194
|
+
another (with a generic Bluetooth glyph). Empty falls back to the default
|
|
195
|
+
`client_id`.
|
|
196
|
+
|
|
197
|
+
## MPRIS server
|
|
198
|
+
|
|
199
|
+
Refrain publishes itself as `org.mpris.MediaPlayer2.refrain` on the session
|
|
200
|
+
bus, so KDE Plasma's panel media-controls applet (and KDE Connect, GNOME
|
|
201
|
+
Shell, Mako, …) drive the same Play/Pause/Next/Previous as the tray and
|
|
202
|
+
render the same track Discord renders.
|
|
203
|
+
|
|
186
204
|
## Tray
|
|
187
205
|
|
|
188
206
|
<p align="center">
|
|
@@ -128,7 +128,9 @@ same file; you almost never need to touch it by hand.
|
|
|
128
128
|
|
|
129
129
|
```toml
|
|
130
130
|
[discord]
|
|
131
|
-
client_id = "" #
|
|
131
|
+
client_id = "" # default Application ID — paste yours here
|
|
132
|
+
client_id_mpris = "" # optional per-source override (browser / Apple Music)
|
|
133
|
+
client_id_bluetooth = "" # optional per-source override (Bluetooth headphones)
|
|
132
134
|
|
|
133
135
|
[sources]
|
|
134
136
|
mpris_enabled = true
|
|
@@ -143,12 +145,28 @@ autostart = false
|
|
|
143
145
|
notifications = true
|
|
144
146
|
cover_art = true
|
|
145
147
|
show_buttons = true
|
|
148
|
+
notify_delay_ms = 0 # 0 = fire ASAP; the cover-art retry loop still waits up to ~2 s
|
|
146
149
|
|
|
147
150
|
[advanced]
|
|
148
|
-
poll_interval_ms =
|
|
151
|
+
poll_interval_ms = 500
|
|
149
152
|
log_level = "INFO"
|
|
153
|
+
cover_cache_size = 200 # disk cap for cached covers
|
|
154
|
+
idle_grace_s = 30 # clear status when same track plays past duration + grace; 0 disables
|
|
155
|
+
language = "system" # "system" follows QLocale; "en" / "de" / … force a translation
|
|
150
156
|
```
|
|
151
157
|
|
|
158
|
+
Per-source `client_id_*` fields let Apple Music render under one Discord
|
|
159
|
+
application (with the album-grid as artwork) and Bluetooth headphones under
|
|
160
|
+
another (with a generic Bluetooth glyph). Empty falls back to the default
|
|
161
|
+
`client_id`.
|
|
162
|
+
|
|
163
|
+
## MPRIS server
|
|
164
|
+
|
|
165
|
+
Refrain publishes itself as `org.mpris.MediaPlayer2.refrain` on the session
|
|
166
|
+
bus, so KDE Plasma's panel media-controls applet (and KDE Connect, GNOME
|
|
167
|
+
Shell, Mako, …) drive the same Play/Pause/Next/Previous as the tray and
|
|
168
|
+
render the same track Discord renders.
|
|
169
|
+
|
|
152
170
|
## Tray
|
|
153
171
|
|
|
154
172
|
<p align="center">
|
|
@@ -145,6 +145,30 @@ What's done, what's next, what's deliberately not in scope.
|
|
|
145
145
|
- **Window-title cleanup** — no more "Refrain — Settings — Refrain"
|
|
146
146
|
triple; Qt's auto-suffix is the only place "Refrain" appears.
|
|
147
147
|
|
|
148
|
+
## Done — v0.2.1
|
|
149
|
+
|
|
150
|
+
- **Settings UI overhaul** — consistent `QGroupBox` + form-layout
|
|
151
|
+
across every tab, left-aligned group titles via stylesheet
|
|
152
|
+
(Plasma Breeze centers them by default), fixed-width inputs
|
|
153
|
+
(220 px default, 360 px in the Discord group). Switched from
|
|
154
|
+
`AllNonFixedFieldsGrow` to `FieldsStayAtSizeHint` because the
|
|
155
|
+
former silently ignored fixed-width caps on Plasma Breeze.
|
|
156
|
+
- **Notification cover-flicker fix** — `notify-send -i` now
|
|
157
|
+
carries the same image as the `--hint string:image-path:`
|
|
158
|
+
payload so KDE Plasma can't briefly render the brand badge.
|
|
159
|
+
- **Discord RPC cover flicker fix** — defer the first activity
|
|
160
|
+
update for a new track until the iTunes cover URL is in cache,
|
|
161
|
+
capped at ~3 polls so cover-less songs still update.
|
|
162
|
+
- **Auto-restart on Discord client_id Apply** — same pattern as
|
|
163
|
+
the language switch, since pypresence binds to the client_id
|
|
164
|
+
at connect time.
|
|
165
|
+
- **AppImage update size validation** + tmp cleanup, **atomic
|
|
166
|
+
config writes** (tmp + os.replace), **dead `dbus_watcher`
|
|
167
|
+
module removed** (155 lines), **RPC reconnect backoff capped
|
|
168
|
+
at 15 s** instead of 60 s, **`.ts` source files no longer ship
|
|
169
|
+
in the wheel**, **MPRIS dispatch logging at INFO** so
|
|
170
|
+
Next/Previous routing is visible in the live log.
|
|
171
|
+
|
|
148
172
|
## Up next — v0.3
|
|
149
173
|
|
|
150
174
|
- **Polishing the wrapped i18n surface** — wrap the remaining
|
|
@@ -156,6 +180,48 @@ What's done, what's next, what's deliberately not in scope.
|
|
|
156
180
|
signed tag, so AUR users get the exact same commit the release
|
|
157
181
|
workflow ships.
|
|
158
182
|
|
|
183
|
+
## Done — v0.2.2
|
|
184
|
+
|
|
185
|
+
- **Multiple Discord profiles** — per-source `client_id_mpris` and
|
|
186
|
+
`client_id_bluetooth` overrides on top of the default `client_id`,
|
|
187
|
+
so Apple Music can render under one Discord application (with the
|
|
188
|
+
album-grid as artwork) and Bluetooth headphones under another (with
|
|
189
|
+
a generic Bluetooth glyph). Daemon reconnects RPC the moment the
|
|
190
|
+
active source flips.
|
|
191
|
+
- **MPRIS-server mode** — Refrain publishes itself as
|
|
192
|
+
`org.mpris.MediaPlayer2.refrain` so KDE Plasma's panel media-controls
|
|
193
|
+
applet, KDE Connect, GNOME Shell, etc. drive the same Play/Pause/
|
|
194
|
+
Next/Previous as the tray and render the same track. Built on
|
|
195
|
+
`dbus-python` with a GLib main loop in its own thread so it
|
|
196
|
+
doesn't conflict with Qt's event loop.
|
|
197
|
+
- **Welcome wizard redesign** — icon-badge header, subtitle, dedicated
|
|
198
|
+
diagnostics card with two clearly-labelled probe rows, readable
|
|
199
|
+
helper text on Plasma Breeze Dark (was invisible with `palette(mid)`).
|
|
200
|
+
Apply now opens Settings automatically with the just-saved Discord
|
|
201
|
+
ID pre-filled.
|
|
202
|
+
- **Live-tier responsiveness** — default `poll_interval_ms` 1 s → 500 ms,
|
|
203
|
+
`notify_delay_ms` 600 ms → 0 ms (cache hits fire instantly), Discord
|
|
204
|
+
RPC connect happens within ~1 s of first track detection (was up to
|
|
205
|
+
51 s due to dbus-python's 25 s default reply timeout combined with
|
|
206
|
+
plasma-browser-integration introspection hangs — fixed via
|
|
207
|
+
`introspect=False` + per-property `timeout=0.5` + per-player
|
|
208
|
+
blacklist when a player times out).
|
|
209
|
+
- **iTunes `trackTimeMillis` fallback** for Apple Music preview-clip
|
|
210
|
+
durations (< 30 s). Discord drops `start`/`end` entirely on preview
|
|
211
|
+
mode so the elapsed counter doesn't reset every 8 s.
|
|
212
|
+
- **Skip / Previous reach Apple Music reliably** — chromium-native
|
|
213
|
+
MPRIS dispatched first, plasma-browser-integration as fallback,
|
|
214
|
+
cascade follow-up polls (0/50/150/350/750 ms) so the track-change
|
|
215
|
+
reflects in Discord + tray within ~250 ms of detection.
|
|
216
|
+
- **Reset preserves all three Discord IDs**, dialog button labels
|
|
217
|
+
localised (`Reset` / `Zurücksetzen`), text states what's preserved
|
|
218
|
+
explicitly.
|
|
219
|
+
- **Welcome wizard X / Esc** marks `first_run_complete=True` so the
|
|
220
|
+
wizard doesn't re-appear; Apply with empty ID asks for confirmation.
|
|
221
|
+
- **Theme-aware text colors** — every helper-text label switched from
|
|
222
|
+
`palette(mid)` to `palette(text)` after Plasma Breeze Dark
|
|
223
|
+
rendered them invisible.
|
|
224
|
+
|
|
159
225
|
## Maybe — v0.3+
|
|
160
226
|
|
|
161
227
|
- **Flathub submission**, second attempt. The manifest under
|
|
@@ -167,11 +233,6 @@ What's done, what's next, what's deliberately not in scope.
|
|
|
167
233
|
- **Cover-art replacement notifications**: re-send the desktop notification
|
|
168
234
|
via `--replace-id` once the cover finishes downloading, so the embed
|
|
169
235
|
swaps in even when the initial retry window times out.
|
|
170
|
-
- **Multiple Discord profiles**: per-source client IDs so Bluetooth and
|
|
171
|
-
Apple Music Web can render with different artwork in the user's profile.
|
|
172
|
-
- **MPRIS-server mode**: expose Refrain as an MPRIS player itself, so KDE
|
|
173
|
-
Plasma's media controls in the panel show the same track Refrain shows
|
|
174
|
-
in Discord.
|
|
175
236
|
|
|
176
237
|
## Deliberately not in scope
|
|
177
238
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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.2
|
|
5
5
|
pkgrel=1
|
|
6
6
|
pkgdesc="Discord Rich Presence for Apple Music on Linux"
|
|
7
7
|
arch=('any')
|
|
@@ -20,7 +20,7 @@ makedepends=(
|
|
|
20
20
|
'python-wheel'
|
|
21
21
|
)
|
|
22
22
|
source=("$pkgname-$pkgver.tar.gz::https://github.com/Rockykln/refrain/archive/v$pkgver.tar.gz")
|
|
23
|
-
sha256sums=('
|
|
23
|
+
sha256sums=('SKIP') # set after release tag is pushed: updpkgsums
|
|
24
24
|
|
|
25
25
|
build() {
|
|
26
26
|
cd "$pkgname-$pkgver"
|
|
@@ -51,6 +51,41 @@
|
|
|
51
51
|
<content_rating type="oars-1.1" />
|
|
52
52
|
|
|
53
53
|
<releases>
|
|
54
|
+
<release version="0.2.2" date="2026-05-06">
|
|
55
|
+
<description>
|
|
56
|
+
<p>
|
|
57
|
+
Two roadmap features pulled forward: per-source Discord
|
|
58
|
+
client IDs (so Apple Music and Bluetooth can render under
|
|
59
|
+
different Discord applications, with their own artwork in
|
|
60
|
+
the user's profile), and MPRIS-server mode (Refrain
|
|
61
|
+
publishes itself as org.mpris.MediaPlayer2.refrain so KDE
|
|
62
|
+
Plasma's panel media-controls applet, KDE Connect, GNOME
|
|
63
|
+
Shell, etc. drive the same Play / Pause / Next / Previous
|
|
64
|
+
as the tray and render the same track Discord renders).
|
|
65
|
+
</p>
|
|
66
|
+
</description>
|
|
67
|
+
</release>
|
|
68
|
+
<release version="0.2.1" date="2026-05-06">
|
|
69
|
+
<description>
|
|
70
|
+
<p>
|
|
71
|
+
Settings UI overhaul: every tab now uses a consistent
|
|
72
|
+
GroupBox + form-layout with left-aligned titles, fixed-width
|
|
73
|
+
inputs that hold across Plasma Breeze and other Qt styles,
|
|
74
|
+
and a compact 220 px field default (360 px for the Discord
|
|
75
|
+
group whose values need more room). Notification cover-art
|
|
76
|
+
flicker is gone — `notify-send -i` and `image-path` now
|
|
77
|
+
carry the same image so KDE Plasma can't briefly render
|
|
78
|
+
the brand badge. Discord RPC defers the first update for
|
|
79
|
+
a new track until the cover URL is in cache so the brand
|
|
80
|
+
fallback never flashes in the activity card. Apply now
|
|
81
|
+
auto-restarts when the Discord client_id changes (same
|
|
82
|
+
pattern as the language switch). Dead D-Bus watcher code
|
|
83
|
+
removed (155 lines), atomic config writes, AppImage
|
|
84
|
+
updater validates downloaded size, RPC reconnect backoff
|
|
85
|
+
capped at 15 s instead of 60 s.
|
|
86
|
+
</p>
|
|
87
|
+
</description>
|
|
88
|
+
</release>
|
|
54
89
|
<release version="0.2.0" date="2026-05-06">
|
|
55
90
|
<description>
|
|
56
91
|
<p>
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "refrain"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
8
8
|
description = "Discord Rich Presence for Apple Music on Linux"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -50,12 +50,11 @@ Changelog = "https://github.com/Rockykln/refrain/blob/main/CHANGELOG.md"
|
|
|
50
50
|
|
|
51
51
|
[tool.hatch.build.targets.wheel]
|
|
52
52
|
packages = ["src/refrain"]
|
|
53
|
-
# Include compiled translations + bundled assets in the wheel.
|
|
54
|
-
# .qm files
|
|
55
|
-
#
|
|
53
|
+
# Include compiled translations + bundled assets in the wheel. Only the
|
|
54
|
+
# pre-compiled .qm files ship — .ts (Linguist source) files belong in
|
|
55
|
+
# the source tree for translators to edit, not in the runtime wheel.
|
|
56
56
|
artifacts = [
|
|
57
57
|
"src/refrain/i18n/*.qm",
|
|
58
|
-
"src/refrain/i18n/*.ts",
|
|
59
58
|
"src/refrain/assets/**/*",
|
|
60
59
|
]
|
|
61
60
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.2"
|