refrain 0.5.2__tar.gz → 0.5.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {refrain-0.5.2 → refrain-0.5.4}/.github/ISSUE_TEMPLATE/bug_report.yml +12 -3
- {refrain-0.5.2 → refrain-0.5.4}/.github/codeql/codeql-config.yml +1 -1
- {refrain-0.5.2 → refrain-0.5.4}/.github/dependabot.yml +4 -0
- {refrain-0.5.2 → refrain-0.5.4}/.github/pull_request_template.md +1 -0
- {refrain-0.5.2 → refrain-0.5.4}/.github/workflows/codeql.yml +3 -3
- refrain-0.5.4/.github/workflows/release.yml +351 -0
- {refrain-0.5.2 → refrain-0.5.4}/.github/workflows/security.yml +15 -10
- refrain-0.5.4/.github/workflows/tests.yml +157 -0
- {refrain-0.5.2 → refrain-0.5.4}/CHANGELOG.md +464 -10
- {refrain-0.5.2 → refrain-0.5.4}/CONTRIBUTING.md +44 -14
- {refrain-0.5.2 → refrain-0.5.4}/LEGAL.md +42 -14
- refrain-0.5.4/LICENSE +109 -0
- {refrain-0.5.2 → refrain-0.5.4}/Makefile +7 -1
- refrain-0.5.4/PKG-INFO +283 -0
- {refrain-0.5.2 → refrain-0.5.4}/PRIVACY.md +67 -28
- refrain-0.5.4/README.md +247 -0
- refrain-0.5.4/SECURITY.md +119 -0
- {refrain-0.5.2 → refrain-0.5.4}/docs/architecture.md +89 -46
- refrain-0.5.4/docs/bluetooth.md +152 -0
- refrain-0.5.4/docs/configuration.md +70 -0
- refrain-0.5.4/docs/developer-mode.md +139 -0
- {refrain-0.5.2 → refrain-0.5.4}/docs/faq.md +114 -47
- refrain-0.5.4/docs/install.md +113 -0
- refrain-0.5.4/docs/interface.md +140 -0
- {refrain-0.5.2 → refrain-0.5.4}/docs/lastfm.md +25 -10
- refrain-0.5.4/docs/releases.md +40 -0
- refrain-0.5.2/ROADMAP.md → refrain-0.5.4/docs/roadmap.md +47 -7
- refrain-0.5.4/docs/settings.md +101 -0
- refrain-0.5.4/docs/stats.py +245 -0
- {refrain-0.5.2 → refrain-0.5.4}/docs/test-matrix.md +41 -22
- refrain-0.5.4/docs/troubleshooting.md +395 -0
- refrain-0.5.4/docs/updates.md +34 -0
- refrain-0.5.4/packaging/README.md +294 -0
- refrain-0.5.4/packaging/appimage/AppImageBuilder.yml +64 -0
- refrain-0.5.4/packaging/appimage/prune_qt.py +95 -0
- refrain-0.5.4/packaging/appimage/third_party_notices.py +240 -0
- {refrain-0.5.2 → refrain-0.5.4}/packaging/aur/refrain/.SRCINFO +8 -5
- {refrain-0.5.2 → refrain-0.5.4}/packaging/aur/refrain/PKGBUILD +15 -9
- {refrain-0.5.2 → refrain-0.5.4}/packaging/aur/refrain-git/.SRCINFO +5 -3
- {refrain-0.5.2 → refrain-0.5.4}/packaging/aur/refrain-git/PKGBUILD +7 -3
- {refrain-0.5.2 → refrain-0.5.4}/packaging/flatpak/io.github.Rockykln.Refrain.metainfo.xml +37 -26
- {refrain-0.5.2 → refrain-0.5.4}/packaging/flatpak/io.github.Rockykln.Refrain.yaml +8 -3
- {refrain-0.5.2 → refrain-0.5.4}/packaging/flatpak/python-deps.json +3 -3
- refrain-0.5.4/packaging/release_key.py +263 -0
- {refrain-0.5.2 → refrain-0.5.4}/pyproject.toml +61 -20
- refrain-0.5.4/requirements-dev.lock +1148 -0
- refrain-0.5.4/requirements-lowest.txt +4 -0
- refrain-0.5.4/requirements.txt +3 -0
- refrain-0.5.4/src/refrain/__init__.py +1 -0
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/app.py +436 -109
- refrain-0.5.4/src/refrain/assets/icons/chronometer.svg +6 -0
- refrain-0.5.4/src/refrain/assets/icons/tray-offline-dark.svg +4 -0
- refrain-0.5.4/src/refrain/assets/icons/tray-offline.svg +4 -0
- refrain-0.5.4/src/refrain/assets/icons/tray-paused-dark.svg +4 -0
- refrain-0.5.4/src/refrain/assets/icons/tray-paused.svg +4 -0
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/assets/icons/tray-playing-dark.svg +1 -1
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/assets/icons/tray-playing.svg +1 -1
- refrain-0.5.4/src/refrain/assets/icons/tray-private-dark.svg +8 -0
- refrain-0.5.4/src/refrain/assets/icons/tray-private.svg +8 -0
- refrain-0.5.4/src/refrain/assets/icons/tray-stopped-dark.svg +3 -0
- refrain-0.5.4/src/refrain/assets/icons/tray-stopped.svg +3 -0
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/autostart.py +19 -7
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/browser.py +2 -9
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/config.py +291 -50
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/cover_art.py +124 -45
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/cover_fetcher.py +165 -65
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/daemon.py +723 -221
- refrain-0.5.4/src/refrain/dev_metrics.py +559 -0
- refrain-0.5.4/src/refrain/diagnostics.py +170 -0
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/discord_app.py +44 -37
- refrain-0.5.4/src/refrain/discord_rpc.py +771 -0
- refrain-0.5.4/src/refrain/ed25519.py +106 -0
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/history.py +158 -69
- refrain-0.5.4/src/refrain/https_only.py +17 -0
- refrain-0.5.4/src/refrain/i18n/refrain_cs.qm +0 -0
- refrain-0.5.4/src/refrain/i18n/refrain_cs.ts +2237 -0
- refrain-0.5.4/src/refrain/i18n/refrain_de.qm +0 -0
- refrain-0.5.4/src/refrain/i18n/refrain_de.ts +2227 -0
- refrain-0.5.4/src/refrain/i18n/refrain_en.qm +0 -0
- refrain-0.5.4/src/refrain/i18n/refrain_en.ts +96 -0
- refrain-0.5.4/src/refrain/i18n/refrain_es.qm +0 -0
- refrain-0.5.4/src/refrain/i18n/refrain_es.ts +2227 -0
- refrain-0.5.4/src/refrain/i18n/refrain_fr.qm +0 -0
- refrain-0.5.4/src/refrain/i18n/refrain_fr.ts +2227 -0
- refrain-0.5.4/src/refrain/i18n/refrain_it.qm +0 -0
- refrain-0.5.4/src/refrain/i18n/refrain_it.ts +2227 -0
- refrain-0.5.4/src/refrain/i18n/refrain_ja.qm +0 -0
- refrain-0.5.4/src/refrain/i18n/refrain_ja.ts +2217 -0
- refrain-0.5.4/src/refrain/i18n/refrain_ko.qm +0 -0
- refrain-0.5.4/src/refrain/i18n/refrain_ko.ts +2217 -0
- refrain-0.5.4/src/refrain/i18n/refrain_nl.qm +0 -0
- refrain-0.5.4/src/refrain/i18n/refrain_nl.ts +2227 -0
- refrain-0.5.4/src/refrain/i18n/refrain_pl.qm +0 -0
- refrain-0.5.4/src/refrain/i18n/refrain_pl.ts +2237 -0
- refrain-0.5.4/src/refrain/i18n/refrain_pt.qm +0 -0
- refrain-0.5.4/src/refrain/i18n/refrain_pt.ts +2227 -0
- refrain-0.5.4/src/refrain/i18n/refrain_ru.qm +0 -0
- refrain-0.5.4/src/refrain/i18n/refrain_ru.ts +2237 -0
- refrain-0.5.4/src/refrain/i18n/refrain_sv.qm +0 -0
- refrain-0.5.4/src/refrain/i18n/refrain_sv.ts +2227 -0
- refrain-0.5.4/src/refrain/i18n/refrain_tr.qm +0 -0
- refrain-0.5.4/src/refrain/i18n/refrain_tr.ts +2217 -0
- refrain-0.5.4/src/refrain/i18n/refrain_uk.qm +0 -0
- refrain-0.5.4/src/refrain/i18n/refrain_uk.ts +2237 -0
- refrain-0.5.4/src/refrain/i18n/refrain_zh_CN.qm +0 -0
- refrain-0.5.4/src/refrain/i18n/refrain_zh_CN.ts +2217 -0
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/logging_setup.py +29 -3
- refrain-0.5.4/src/refrain/paths.py +104 -0
- refrain-0.5.4/src/refrain/qt_libraries.py +115 -0
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/scrobble.py +156 -71
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/scrobble_queue.py +78 -40
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/secrets_store.py +30 -41
- refrain-0.5.4/src/refrain/service_status.py +185 -0
- refrain-0.5.4/src/refrain/single_instance.py +114 -0
- refrain-0.5.4/src/refrain/song_lengths.py +222 -0
- refrain-0.5.4/src/refrain/sources/base.py +78 -0
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/sources/bluetooth.py +113 -83
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/sources/mpris.py +288 -120
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/sources/mpris_server.py +34 -32
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/startup_check.py +3 -17
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/timing.py +127 -72
- refrain-0.5.4/src/refrain/ui/clock.py +43 -0
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/ui/cursors.py +33 -20
- refrain-0.5.4/src/refrain/ui/external_link.py +33 -0
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/ui/history_window.py +222 -95
- refrain-0.5.4/src/refrain/ui/icons.py +52 -0
- refrain-0.5.4/src/refrain/ui/layout_check.py +280 -0
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/ui/legal_dialog.py +58 -36
- refrain-0.5.4/src/refrain/ui/log_window.py +494 -0
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/ui/settings_window.py +877 -341
- refrain-0.5.4/src/refrain/ui/status_window.py +999 -0
- refrain-0.5.4/src/refrain/ui/tooltips.py +57 -0
- refrain-0.5.4/src/refrain/ui/tray.py +525 -0
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/ui/update_dialog.py +51 -20
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/ui/welcome_dialog.py +131 -54
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/uninstall.py +11 -27
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/updater.py +250 -67
- refrain-0.5.4/tests/conftest.py +115 -0
- refrain-0.5.4/tests/daemon_fakes.py +364 -0
- refrain-0.5.4/tests/integration/__init__.py +0 -0
- refrain-0.5.4/tests/integration/conftest.py +241 -0
- refrain-0.5.4/tests/integration/fake_mpris_player.py +189 -0
- refrain-0.5.4/tests/integration/test_mpris_live_dbus.py +129 -0
- refrain-0.5.4/tests/release_signing.py +18 -0
- refrain-0.5.4/tests/test_album_fill_in.py +152 -0
- refrain-0.5.4/tests/test_app_cli.py +184 -0
- refrain-0.5.4/tests/test_app_edges.py +260 -0
- refrain-0.5.4/tests/test_app_helpers.py +247 -0
- refrain-0.5.4/tests/test_app_main.py +1326 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_autostart.py +31 -0
- refrain-0.5.4/tests/test_autostart_more.py +107 -0
- refrain-0.5.4/tests/test_bluetooth_apps.py +105 -0
- refrain-0.5.4/tests/test_bluetooth_source.py +518 -0
- refrain-0.5.4/tests/test_browser.py +128 -0
- refrain-0.5.4/tests/test_browser_edges.py +28 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_button_url.py +5 -6
- refrain-0.5.4/tests/test_clock.py +57 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_config.py +106 -23
- refrain-0.5.4/tests/test_config_edges.py +163 -0
- refrain-0.5.4/tests/test_config_notifications.py +40 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_config_preserve.py +2 -6
- refrain-0.5.4/tests/test_config_validation.py +230 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_cover_art.py +77 -39
- refrain-0.5.4/tests/test_cover_art_edges.py +79 -0
- refrain-0.5.4/tests/test_cover_art_more.py +214 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_cover_fetcher.py +18 -34
- refrain-0.5.4/tests/test_cover_fetcher_cache.py +225 -0
- refrain-0.5.4/tests/test_cover_fetcher_edges.py +105 -0
- refrain-0.5.4/tests/test_cover_history.py +150 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_crash_log.py +2 -2
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_daemon_album_format.py +3 -8
- refrain-0.5.4/tests/test_daemon_discord_artwork.py +108 -0
- refrain-0.5.4/tests/test_daemon_edges.py +131 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_daemon_idle.py +8 -26
- refrain-0.5.4/tests/test_daemon_lastfm_length.py +214 -0
- refrain-0.5.4/tests/test_daemon_length_rule.py +116 -0
- refrain-0.5.4/tests/test_daemon_lifecycle.py +513 -0
- refrain-0.5.4/tests/test_daemon_paused_rule.py +94 -0
- refrain-0.5.4/tests/test_daemon_pipeline.py +736 -0
- refrain-0.5.4/tests/test_daemon_restart_estimate.py +123 -0
- refrain-0.5.4/tests/test_daemon_service_states.py +143 -0
- refrain-0.5.4/tests/test_daemon_song_identity.py +136 -0
- refrain-0.5.4/tests/test_daemon_stop.py +45 -0
- refrain-0.5.4/tests/test_daemon_tick_lookups.py +120 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_dbus_dispatch.py +8 -35
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_desktop_entries.py +19 -0
- refrain-0.5.4/tests/test_dev_metrics.py +440 -0
- refrain-0.5.4/tests/test_developer_mode_ui.py +386 -0
- refrain-0.5.4/tests/test_diagnostics.py +247 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_discord_app.py +87 -50
- refrain-0.5.4/tests/test_discord_app_edges.py +26 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_discord_ipc_scan.py +2 -17
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_discord_listening.py +1 -6
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_discord_multi_client.py +15 -33
- refrain-0.5.4/tests/test_discord_rpc.py +285 -0
- refrain-0.5.4/tests/test_discord_rpc_connect.py +442 -0
- refrain-0.5.4/tests/test_discord_rpc_fd_leak.py +90 -0
- refrain-0.5.4/tests/test_discord_rpc_robustness.py +699 -0
- refrain-0.5.4/tests/test_ed25519.py +183 -0
- refrain-0.5.4/tests/test_ed25519_edge_cases.py +145 -0
- refrain-0.5.4/tests/test_external_link.py +58 -0
- refrain-0.5.4/tests/test_foreign_locales.py +88 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_history.py +148 -11
- refrain-0.5.4/tests/test_history_edges.py +261 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_history_window.py +236 -22
- refrain-0.5.4/tests/test_history_window_edges.py +218 -0
- refrain-0.5.4/tests/test_icons.py +273 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_imports.py +2 -7
- refrain-0.5.4/tests/test_layout_check.py +341 -0
- refrain-0.5.4/tests/test_layout_languages.py +113 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_legal_notice.py +6 -14
- refrain-0.5.4/tests/test_log_window_filter.py +47 -0
- refrain-0.5.4/tests/test_logging_setup.py +128 -0
- refrain-0.5.4/tests/test_logging_setup_edges.py +59 -0
- refrain-0.5.4/tests/test_mpris_controls.py +137 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_mpris_dispatch.py +28 -37
- refrain-0.5.4/tests/test_mpris_edges.py +120 -0
- refrain-0.5.4/tests/test_mpris_server_props.py +381 -0
- refrain-0.5.4/tests/test_mpris_source_more.py +700 -0
- refrain-0.5.4/tests/test_notify_argv.py +126 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_paths.py +21 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_position_resolver.py +159 -84
- refrain-0.5.4/tests/test_private_files.py +89 -0
- refrain-0.5.4/tests/test_private_writes.py +107 -0
- refrain-0.5.4/tests/test_qt_libraries.py +95 -0
- refrain-0.5.4/tests/test_qt_libraries_more.py +85 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_qt_plugin_path.py +5 -23
- refrain-0.5.4/tests/test_readme_stats.py +101 -0
- refrain-0.5.4/tests/test_release_key.py +214 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_scrobble.py +1 -5
- refrain-0.5.4/tests/test_scrobble_edges.py +50 -0
- refrain-0.5.4/tests/test_scrobble_more.py +309 -0
- refrain-0.5.4/tests/test_scrobble_queue_edges.py +75 -0
- refrain-0.5.4/tests/test_scrobble_queue_file.py +194 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_scrobbler.py +59 -32
- refrain-0.5.4/tests/test_sdist_contents.py +45 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_secrets_store.py +118 -20
- refrain-0.5.4/tests/test_secrets_store_edges.py +32 -0
- refrain-0.5.4/tests/test_secrets_store_more.py +234 -0
- refrain-0.5.4/tests/test_service_status.py +122 -0
- refrain-0.5.4/tests/test_settings_app_name.py +104 -0
- refrain-0.5.4/tests/test_settings_apply.py +352 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_settings_github_icon.py +18 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_settings_history.py +3 -6
- refrain-0.5.4/tests/test_settings_lastfm.py +226 -0
- refrain-0.5.4/tests/test_settings_more.py +395 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_settings_reset.py +5 -16
- refrain-0.5.4/tests/test_settings_unsaved.py +607 -0
- refrain-0.5.4/tests/test_settings_window_edges.py +273 -0
- refrain-0.5.4/tests/test_single_instance.py +117 -0
- refrain-0.5.4/tests/test_single_instance_errors.py +41 -0
- refrain-0.5.4/tests/test_song_lengths.py +320 -0
- refrain-0.5.4/tests/test_song_lengths_edges.py +33 -0
- refrain-0.5.4/tests/test_song_lengths_pins.py +410 -0
- refrain-0.5.4/tests/test_source_field_limits.py +56 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_source_selection.py +2 -7
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_sources_base.py +28 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_startup_check.py +6 -19
- refrain-0.5.4/tests/test_status_window.py +705 -0
- refrain-0.5.4/tests/test_system_certificates.py +57 -0
- refrain-0.5.4/tests/test_theme_colors.py +69 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_timing.py +33 -31
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_translations.py +17 -7
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_tray_init.py +18 -34
- refrain-0.5.4/tests/test_tray_states.py +510 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_ui_cursors.py +70 -21
- refrain-0.5.4/tests/test_ui_edges.py +279 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_ui_locale.py +6 -13
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_uninstall.py +41 -14
- refrain-0.5.4/tests/test_update_dialog_cancel.py +133 -0
- refrain-0.5.4/tests/test_update_dialog_flows.py +210 -0
- refrain-0.5.4/tests/test_update_orchestrator.py +212 -0
- refrain-0.5.4/tests/test_updater.py +791 -0
- refrain-0.5.4/tests/test_updater_download_limits.py +186 -0
- refrain-0.5.4/tests/test_updater_paths.py +495 -0
- refrain-0.5.4/tests/test_updater_subprocess_calls.py +149 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_welcome_dialog_growth.py +2 -13
- refrain-0.5.4/tests/test_welcome_probes.py +387 -0
- refrain-0.5.2/.github/workflows/release.yml +0 -181
- refrain-0.5.2/.github/workflows/tests.yml +0 -68
- refrain-0.5.2/LICENSE +0 -71
- refrain-0.5.2/PKG-INFO +0 -539
- refrain-0.5.2/README.md +0 -505
- refrain-0.5.2/SECURITY.md +0 -72
- refrain-0.5.2/docs/bluetooth.md +0 -141
- refrain-0.5.2/docs/screenshots/README.md +0 -75
- refrain-0.5.2/docs/screenshots/demo-cover.png +0 -0
- refrain-0.5.2/docs/screenshots/discord-rpc.png +0 -0
- refrain-0.5.2/docs/screenshots/history.png +0 -0
- refrain-0.5.2/docs/screenshots/legal.png +0 -0
- refrain-0.5.2/docs/screenshots/live-log.png +0 -0
- refrain-0.5.2/docs/screenshots/notification.png +0 -0
- refrain-0.5.2/docs/screenshots/settings-advanced.png +0 -0
- refrain-0.5.2/docs/screenshots/settings-general.png +0 -0
- refrain-0.5.2/docs/screenshots/settings-history.png +0 -0
- refrain-0.5.2/docs/screenshots/settings-lastfm.png +0 -0
- refrain-0.5.2/docs/screenshots/settings-sources.png +0 -0
- refrain-0.5.2/docs/screenshots/settings-updates.png +0 -0
- refrain-0.5.2/docs/screenshots/tray-menu.png +0 -0
- refrain-0.5.2/docs/screenshots/update-dialog.png +0 -0
- refrain-0.5.2/docs/screenshots/welcome.png +0 -0
- refrain-0.5.2/packaging/README.md +0 -192
- refrain-0.5.2/packaging/appimage/AppImageBuilder.yml +0 -80
- refrain-0.5.2/requirements.txt +0 -3
- refrain-0.5.2/src/refrain/__init__.py +0 -1
- refrain-0.5.2/src/refrain/assets/icons/tray-paused-dark.svg +0 -4
- refrain-0.5.2/src/refrain/assets/icons/tray-paused.svg +0 -4
- refrain-0.5.2/src/refrain/assets/icons/tray-stopped-dark.svg +0 -3
- refrain-0.5.2/src/refrain/assets/icons/tray-stopped.svg +0 -3
- refrain-0.5.2/src/refrain/discord_rpc.py +0 -399
- refrain-0.5.2/src/refrain/i18n/refrain_de.qm +0 -0
- refrain-0.5.2/src/refrain/i18n/refrain_de.ts +0 -1274
- refrain-0.5.2/src/refrain/i18n/refrain_en.qm +0 -0
- refrain-0.5.2/src/refrain/i18n/refrain_en.ts +0 -47
- refrain-0.5.2/src/refrain/i18n/refrain_es.qm +0 -0
- refrain-0.5.2/src/refrain/i18n/refrain_es.ts +0 -1274
- refrain-0.5.2/src/refrain/i18n/refrain_fr.qm +0 -0
- refrain-0.5.2/src/refrain/i18n/refrain_fr.ts +0 -1274
- refrain-0.5.2/src/refrain/i18n/refrain_it.qm +0 -0
- refrain-0.5.2/src/refrain/i18n/refrain_it.ts +0 -1274
- refrain-0.5.2/src/refrain/i18n/refrain_ja.qm +0 -0
- refrain-0.5.2/src/refrain/i18n/refrain_ja.ts +0 -1269
- refrain-0.5.2/src/refrain/i18n/refrain_pl.qm +0 -0
- refrain-0.5.2/src/refrain/i18n/refrain_pl.ts +0 -1279
- refrain-0.5.2/src/refrain/i18n/refrain_pt.qm +0 -0
- refrain-0.5.2/src/refrain/i18n/refrain_pt.ts +0 -1274
- refrain-0.5.2/src/refrain/i18n/refrain_ru.qm +0 -0
- refrain-0.5.2/src/refrain/i18n/refrain_ru.ts +0 -1279
- refrain-0.5.2/src/refrain/i18n/refrain_zh_CN.qm +0 -0
- refrain-0.5.2/src/refrain/i18n/refrain_zh_CN.ts +0 -1269
- refrain-0.5.2/src/refrain/paths.py +0 -46
- refrain-0.5.2/src/refrain/single_instance.py +0 -53
- refrain-0.5.2/src/refrain/song_lengths.py +0 -144
- refrain-0.5.2/src/refrain/sources/base.py +0 -43
- refrain-0.5.2/src/refrain/ui/log_window.py +0 -122
- refrain-0.5.2/src/refrain/ui/tray.py +0 -390
- refrain-0.5.2/tests/conftest.py +0 -69
- refrain-0.5.2/tests/test_bluetooth_apps.py +0 -70
- refrain-0.5.2/tests/test_browser.py +0 -57
- refrain-0.5.2/tests/test_discord_rpc.py +0 -180
- refrain-0.5.2/tests/test_mpris_controls.py +0 -57
- refrain-0.5.2/tests/test_notify_argv.py +0 -62
- refrain-0.5.2/tests/test_settings_lastfm.py +0 -133
- refrain-0.5.2/tests/test_song_lengths.py +0 -164
- refrain-0.5.2/tests/test_updater.py +0 -320
- {refrain-0.5.2 → refrain-0.5.4}/.editorconfig +0 -0
- {refrain-0.5.2 → refrain-0.5.4}/.gitattributes +0 -0
- {refrain-0.5.2 → refrain-0.5.4}/.github/FUNDING.yml +0 -0
- {refrain-0.5.2 → refrain-0.5.4}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {refrain-0.5.2 → refrain-0.5.4}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {refrain-0.5.2 → refrain-0.5.4}/.gitignore +0 -0
- {refrain-0.5.2 → refrain-0.5.4}/CODE_OF_CONDUCT.md +0 -0
- {refrain-0.5.2 → refrain-0.5.4}/packaging/flatpak/io.github.Rockykln.Refrain.desktop +0 -0
- {refrain-0.5.2 → refrain-0.5.4}/requirements-dev.txt +0 -0
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/__main__.py +0 -0
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/assets/icons/github-mark.svg +0 -0
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/assets/icons/menu-quit.svg +0 -0
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/assets/icons/menu-update.svg +0 -0
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/assets/icons/refrain.png +0 -0
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/assets/icons/refrain.svg +0 -0
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/assets/refrain.desktop +0 -0
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/sources/__init__.py +0 -0
- {refrain-0.5.2 → refrain-0.5.4}/src/refrain/ui/__init__.py +0 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/__init__.py +0 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_scrobble_queue.py +0 -0
- {refrain-0.5.2 → refrain-0.5.4}/tests/test_update_dialog_body.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.5.
|
|
16
|
+
placeholder: "0.5.4"
|
|
17
17
|
validations:
|
|
18
18
|
required: true
|
|
19
19
|
|
|
@@ -61,14 +61,23 @@ body:
|
|
|
61
61
|
validations:
|
|
62
62
|
required: true
|
|
63
63
|
|
|
64
|
+
- type: markdown
|
|
65
|
+
attributes:
|
|
66
|
+
value: |
|
|
67
|
+
**Before you paste a log:** issues are public. The log lists the songs you
|
|
68
|
+
played (your listening history) and, in debug mode, the address of your
|
|
69
|
+
Bluetooth device. Read it first and remove anything you don't want public,
|
|
70
|
+
or mail the log to [contact@rockykln.com](mailto:contact@rockykln.com) instead
|
|
71
|
+
and mention your issue number.
|
|
72
|
+
|
|
64
73
|
- type: textarea
|
|
65
74
|
id: logs
|
|
66
75
|
attributes:
|
|
67
76
|
label: Refrain log
|
|
68
77
|
description: |
|
|
69
78
|
Paste the relevant portion of `~/.local/state/refrain/refrain.log`.
|
|
70
|
-
|
|
71
|
-
|
|
79
|
+
It holds no passwords or tokens, but it does contain song titles and,
|
|
80
|
+
in debug mode, Bluetooth device addresses — check it before posting.
|
|
72
81
|
render: shell
|
|
73
82
|
validations:
|
|
74
83
|
required: false
|
|
@@ -9,7 +9,7 @@ queries:
|
|
|
9
9
|
- uses: security-and-quality
|
|
10
10
|
|
|
11
11
|
query-filters:
|
|
12
|
-
# Empty `except` blocks are deliberate in
|
|
12
|
+
# Empty `except` blocks are deliberate in cleanup paths
|
|
13
13
|
# (DBus disconnects, Discord IPC, tray icon teardown). Each site has
|
|
14
14
|
# context in surrounding code; the CodeQL note here would be repeated
|
|
15
15
|
# ~10× without adding signal.
|
|
@@ -4,6 +4,10 @@ updates:
|
|
|
4
4
|
# Python dependencies (pip — reads requirements*.txt + pyproject.toml)
|
|
5
5
|
- package-ecosystem: "pip"
|
|
6
6
|
directory: "/"
|
|
7
|
+
# The lowest supported versions, tested as such by the "lowest" job:
|
|
8
|
+
# raising them would stop testing the floor pyproject.toml promises.
|
|
9
|
+
exclude-paths:
|
|
10
|
+
- "requirements-lowest.txt"
|
|
7
11
|
schedule:
|
|
8
12
|
interval: "weekly"
|
|
9
13
|
day: "monday"
|
|
@@ -34,6 +34,7 @@ whatever's relevant and delete the rest.
|
|
|
34
34
|
|
|
35
35
|
## Checklist
|
|
36
36
|
|
|
37
|
+
- [ ] I agree that my contribution is licensed as described in the "Contributions" section of LICENSE (summary in CONTRIBUTING.md)
|
|
37
38
|
- [ ] `pytest` passes locally
|
|
38
39
|
- [ ] `ruff check .` and `ruff format --check .` are clean
|
|
39
40
|
- [ ] New / changed behavior is covered by tests (or unreachable from tests due to system integration — explain)
|
|
@@ -27,15 +27,15 @@ jobs:
|
|
|
27
27
|
language: [python]
|
|
28
28
|
|
|
29
29
|
steps:
|
|
30
|
-
- uses: actions/checkout@v7
|
|
30
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
31
31
|
|
|
32
32
|
- name: Initialize CodeQL
|
|
33
|
-
uses: github/codeql-action/init@v4
|
|
33
|
+
uses: github/codeql-action/init@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
|
|
34
34
|
with:
|
|
35
35
|
languages: ${{ matrix.language }}
|
|
36
36
|
config-file: ./.github/codeql/codeql-config.yml
|
|
37
37
|
|
|
38
38
|
- name: Perform CodeQL analysis
|
|
39
|
-
uses: github/codeql-action/analyze@v4
|
|
39
|
+
uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
|
|
40
40
|
with:
|
|
41
41
|
category: "/language:${{ matrix.language }}"
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
# Triggered when a semver tag is pushed: e.g. `git tag v0.2.0 && git push --tags`
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
tags:
|
|
7
|
+
- "v*.*.*"
|
|
8
|
+
|
|
9
|
+
# The AppImage build tools are installed unpinned, so the job that runs them
|
|
10
|
+
# only gets read access; the write and OIDC permissions sit on the small
|
|
11
|
+
# attestation and publishing jobs below.
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
build:
|
|
17
|
+
runs-on: ubuntu-24.04
|
|
18
|
+
|
|
19
|
+
steps:
|
|
20
|
+
- name: Checkout (full history for changelog)
|
|
21
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
22
|
+
with:
|
|
23
|
+
fetch-depth: 0
|
|
24
|
+
|
|
25
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
26
|
+
with:
|
|
27
|
+
python-version: "3.12"
|
|
28
|
+
cache: pip
|
|
29
|
+
cache-dependency-path: requirements-dev.lock
|
|
30
|
+
|
|
31
|
+
- name: Install + run tests (gate the release)
|
|
32
|
+
run: |
|
|
33
|
+
# System libs: dbus-python needs the *-dev headers at build
|
|
34
|
+
# time; PySide6.QtWidgets needs the EGL/xkbcommon/GL/fontconfig
|
|
35
|
+
# runtime libs to import (even under QT_QPA_PLATFORM=offscreen).
|
|
36
|
+
sudo apt-get update
|
|
37
|
+
sudo apt-get install -y \
|
|
38
|
+
libdbus-1-dev libglib2.0-dev pkg-config \
|
|
39
|
+
libegl1 libxkbcommon0 libfontconfig1 libgl1
|
|
40
|
+
# The pinned, hash-checked tool and runtime versions, then
|
|
41
|
+
# Refrain itself on top.
|
|
42
|
+
pip install --require-hashes -r requirements-dev.lock
|
|
43
|
+
pip install --no-deps -e .
|
|
44
|
+
pytest -q
|
|
45
|
+
|
|
46
|
+
- name: Verify tag matches package version
|
|
47
|
+
run: |
|
|
48
|
+
TAG="${GITHUB_REF#refs/tags/v}"
|
|
49
|
+
INIT_VERSION=$(python -c "import sys; sys.path.insert(0,'src'); import refrain; print(refrain.__version__)")
|
|
50
|
+
PYPROJECT_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
|
51
|
+
if [ "$TAG" != "$INIT_VERSION" ] || [ "$TAG" != "$PYPROJECT_VERSION" ]; then
|
|
52
|
+
echo "::error::Version mismatch — tag=v$TAG, __init__.py=$INIT_VERSION, pyproject.toml=$PYPROJECT_VERSION"
|
|
53
|
+
exit 1
|
|
54
|
+
fi
|
|
55
|
+
echo "Releasing v$TAG"
|
|
56
|
+
|
|
57
|
+
- name: Verify CHANGELOG has a matching entry for this tag
|
|
58
|
+
run: |
|
|
59
|
+
TAG="${GITHUB_REF#refs/tags/v}"
|
|
60
|
+
if ! grep -qE "^## \[${TAG}\]" CHANGELOG.md; then
|
|
61
|
+
echo "::error::CHANGELOG.md is missing a '## [${TAG}]' section"
|
|
62
|
+
exit 1
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
- name: Verify Flatpak manifest pins this tag
|
|
66
|
+
# The Flatpak build source is a pinned tag — it isn't
|
|
67
|
+
# rebuilt/published by this workflow (no live Flathub channel),
|
|
68
|
+
# so nothing else catches it going stale. It silently sat on
|
|
69
|
+
# v0.1.4 from then until v0.4.2. This gate makes a stale pin a
|
|
70
|
+
# hard release failure, like the version/CHANGELOG gates above.
|
|
71
|
+
run: |
|
|
72
|
+
TAG="${GITHUB_REF#refs/tags/v}"
|
|
73
|
+
MANIFEST=packaging/flatpak/io.github.Rockykln.Refrain.yaml
|
|
74
|
+
if ! grep -qE "^ tag: v${TAG}$" "$MANIFEST"; then
|
|
75
|
+
echo "::error::$MANIFEST is not pinned to tag v${TAG}"
|
|
76
|
+
exit 1
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
- name: Verify the metainfo lists this release
|
|
80
|
+
# Flathub's own CI rejects a build without it, but there is no live
|
|
81
|
+
# Flathub channel yet, so nothing else would notice this going stale.
|
|
82
|
+
run: |
|
|
83
|
+
TAG="${GITHUB_REF#refs/tags/v}"
|
|
84
|
+
METAINFO=packaging/flatpak/io.github.Rockykln.Refrain.metainfo.xml
|
|
85
|
+
if ! grep -qE "<release version=\"${TAG}\" date=" "$METAINFO"; then
|
|
86
|
+
echo "::error::$METAINFO has no <release> entry for ${TAG}"
|
|
87
|
+
exit 1
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
- name: Build wheel + sdist
|
|
91
|
+
run: |
|
|
92
|
+
python -m build
|
|
93
|
+
# Keep a clean copy of just the Python distributions for PyPI;
|
|
94
|
+
# the AppImage step writes additional files into dist/ later.
|
|
95
|
+
mkdir -p pypi-dist
|
|
96
|
+
cp dist/*.whl dist/*.tar.gz pypi-dist/
|
|
97
|
+
ls -la pypi-dist/
|
|
98
|
+
|
|
99
|
+
# pip-audit is already installed from requirements-dev.lock (dev extra) by
|
|
100
|
+
# the test step above, and doubles as a CycloneDX generator when pointed
|
|
101
|
+
# at a directory instead of the live environment — no new dependency.
|
|
102
|
+
# The target dir is a plain `pip install`, not the test venv, so the
|
|
103
|
+
# SBOM lists exactly what `pip install refrain` pulls in, not the dev
|
|
104
|
+
# tooling installed for the test run.
|
|
105
|
+
- name: Generate SBOM (CycloneDX) for the PyPI package
|
|
106
|
+
run: |
|
|
107
|
+
TAG="${GITHUB_REF#refs/tags/v}"
|
|
108
|
+
rm -rf /tmp/refrain-sbom-pypi
|
|
109
|
+
pip install --target /tmp/refrain-sbom-pypi dist/*.whl
|
|
110
|
+
sbom="dist/Refrain-${TAG}.cdx.json"
|
|
111
|
+
# Don't let a found (not induced) vulnerability fail the release build —
|
|
112
|
+
# that gate already exists as the pip-audit job in security.yml.
|
|
113
|
+
set +e
|
|
114
|
+
pip-audit --path /tmp/refrain-sbom-pypi --format=cyclonedx-json -o "$sbom"
|
|
115
|
+
rc=$?
|
|
116
|
+
set -e
|
|
117
|
+
if [ ! -s "$sbom" ]; then
|
|
118
|
+
echo "::error::SBOM generation produced no output"
|
|
119
|
+
exit 1
|
|
120
|
+
fi
|
|
121
|
+
if [ "$rc" -ne 0 ]; then
|
|
122
|
+
echo "::warning::pip-audit found known vulnerabilities while building the SBOM"
|
|
123
|
+
fi
|
|
124
|
+
rm -rf /tmp/refrain-sbom-pypi
|
|
125
|
+
|
|
126
|
+
# Single-file AppImage so end users can grab one binary from the
|
|
127
|
+
# Releases page and run it without installing anything else.
|
|
128
|
+
# The recipe installs Refrain with the runner's /usr/bin/python3,
|
|
129
|
+
# which must match the Python it bundles from Ubuntu 24.04.
|
|
130
|
+
- name: Install AppImage build deps
|
|
131
|
+
run: |
|
|
132
|
+
sudo apt-get update
|
|
133
|
+
sudo apt-get install -y \
|
|
134
|
+
python3-pip libfuse2t64 desktop-file-utils \
|
|
135
|
+
zsync appstream patchelf squashfs-tools file
|
|
136
|
+
# appimage-builder can't parse Ubuntu package versions with newer
|
|
137
|
+
# packaging. Both are pinned: whatever they produce is signed by
|
|
138
|
+
# hand afterwards, so the build must not float.
|
|
139
|
+
pip install "appimage-builder==1.1.0" "packaging==21.3"
|
|
140
|
+
|
|
141
|
+
- name: Build AppImage
|
|
142
|
+
id: appimage
|
|
143
|
+
run: |
|
|
144
|
+
TAG="${GITHUB_REF#refs/tags/v}"
|
|
145
|
+
# The recipe pins a literal `version:` value, which determines the
|
|
146
|
+
# output filename (Refrain-<version>-x86_64.AppImage). Rewrite it
|
|
147
|
+
# to match the tag so we don't hand-edit the recipe per release.
|
|
148
|
+
sed -i -E "s/^( version: ).*/\1${TAG}/" packaging/appimage/AppImageBuilder.yml
|
|
149
|
+
grep " version:" packaging/appimage/AppImageBuilder.yml
|
|
150
|
+
cd packaging/appimage
|
|
151
|
+
appimage-builder --recipe AppImageBuilder.yml --skip-test
|
|
152
|
+
mkdir -p ../../dist
|
|
153
|
+
mv *.AppImage ../../dist/ 2>/dev/null || true
|
|
154
|
+
mv *.AppImage.zsync ../../dist/ 2>/dev/null || true
|
|
155
|
+
ls -la ../../dist/
|
|
156
|
+
|
|
157
|
+
# Every AppImage up to 0.5.2 was published without ever starting.
|
|
158
|
+
- name: Start the AppImage once
|
|
159
|
+
run: |
|
|
160
|
+
TAG="${GITHUB_REF#refs/tags/v}"
|
|
161
|
+
out="$(./dist/Refrain-${TAG}-x86_64.AppImage --appimage-extract-and-run --version)"
|
|
162
|
+
echo "$out"
|
|
163
|
+
[ "$out" = "refrain ${TAG}" ]
|
|
164
|
+
|
|
165
|
+
- name: Check licence notices and Qt modules in the AppImage
|
|
166
|
+
run: |
|
|
167
|
+
TAG="${GITHUB_REF#refs/tags/v}"
|
|
168
|
+
tmp="$(mktemp -d)"
|
|
169
|
+
(cd "$tmp" && "$GITHUB_WORKSPACE/dist/Refrain-${TAG}-x86_64.AppImage" --appimage-extract >/dev/null)
|
|
170
|
+
doc="$tmp/squashfs-root/usr/share/doc/refrain"
|
|
171
|
+
grep -q "^ PySide6_Essentials " "$doc/THIRD-PARTY-NOTICES"
|
|
172
|
+
grep -q "^ python3.12 " "$doc/THIRD-PARTY-NOTICES"
|
|
173
|
+
test -s "$doc/LICENSE" && test -s "$doc/LICENSES/LGPL-3" && test -s "$doc/LICENSES/GPL-3"
|
|
174
|
+
gpl_only="$(find "$tmp/squashfs-root" -iregex '.*\(Lottie\|WaylandCompositor\|VirtualKeyboard\|Charts\|DataVisualization\|Quick3D\|pyside6_addons\).*')"
|
|
175
|
+
if [ -n "$gpl_only" ]; then
|
|
176
|
+
echo "::error::GPL-only Qt parts in the AppImage:"; echo "$gpl_only"; exit 1
|
|
177
|
+
fi
|
|
178
|
+
# Bonus SBOM for the pip-installed half of the AppImage (dbus-python
|
|
179
|
+
# and PyGObject come from apt and have no dist-info, so they won't
|
|
180
|
+
# show up here). A failure only warns: the PyPI package's SBOM above
|
|
181
|
+
# is the one of record.
|
|
182
|
+
sbom="$GITHUB_WORKSPACE/dist/Refrain-${TAG}-x86_64.AppImage.cdx.json"
|
|
183
|
+
set +e
|
|
184
|
+
pip-audit --path "$tmp/squashfs-root/usr/lib/python3/dist-packages" \
|
|
185
|
+
--format=cyclonedx-json -o "$sbom"
|
|
186
|
+
rc=$?
|
|
187
|
+
set -e
|
|
188
|
+
if [ ! -s "$sbom" ]; then
|
|
189
|
+
echo "::warning::AppImage SBOM generation produced no output — skipping this bonus asset"
|
|
190
|
+
rm -f "$sbom"
|
|
191
|
+
elif [ "$rc" -ne 0 ]; then
|
|
192
|
+
echo "::warning::pip-audit found known vulnerabilities while building the AppImage SBOM"
|
|
193
|
+
fi
|
|
194
|
+
rm -rf "$tmp"
|
|
195
|
+
|
|
196
|
+
# The in-app updater only installs an AppImage listed here, and only once
|
|
197
|
+
# the owner has signed this file (SHA256SUMS.sig, see packaging/README.md).
|
|
198
|
+
- name: Checksums for the in-app updater
|
|
199
|
+
# *.cdx.json (the SBOMs above) rides along in the same file: the
|
|
200
|
+
# updater only ever looks up the one line matching the AppImage's
|
|
201
|
+
# own name (see _expected_sha256 in src/refrain/updater.py), so
|
|
202
|
+
# extra lines for other assets are inert as far as it's concerned.
|
|
203
|
+
run: |
|
|
204
|
+
cd dist
|
|
205
|
+
sha256sum *.AppImage *.cdx.json > SHA256SUMS
|
|
206
|
+
cat SHA256SUMS
|
|
207
|
+
|
|
208
|
+
- name: Publish the AppImage's third-party notices next to it
|
|
209
|
+
run: |
|
|
210
|
+
cd dist
|
|
211
|
+
./Refrain-*-x86_64.AppImage --appimage-extract usr/share/doc/refrain/THIRD-PARTY-NOTICES > /dev/null
|
|
212
|
+
mv squashfs-root/usr/share/doc/refrain/THIRD-PARTY-NOTICES .
|
|
213
|
+
rm -rf squashfs-root
|
|
214
|
+
|
|
215
|
+
# Extract the matching `## [X.Y.Z]` section from CHANGELOG.md
|
|
216
|
+
# and feed it into the GitHub release body. Without this we'd
|
|
217
|
+
# fall back to GitHub's auto-generated "Full Changelog: …"
|
|
218
|
+
# one-liner — which is also what the in-app
|
|
219
|
+
# Settings → Updates → "Latest release notes" pane renders, so
|
|
220
|
+
# users would only ever see the bare compare-link there.
|
|
221
|
+
- name: Extract CHANGELOG section for the release body
|
|
222
|
+
run: |
|
|
223
|
+
TAG="${GITHUB_REF#refs/tags/v}"
|
|
224
|
+
# awk: print everything between `## [TAG]` and the next
|
|
225
|
+
# top-level ## section, excluding the heading lines themselves.
|
|
226
|
+
awk -v ver="${TAG}" '
|
|
227
|
+
$0 ~ "^## \\[" ver "\\]" { p=1; next }
|
|
228
|
+
p && /^## \[/ { exit }
|
|
229
|
+
p { print }
|
|
230
|
+
' CHANGELOG.md > release-notes.md
|
|
231
|
+
echo "::group::release-notes.md"
|
|
232
|
+
cat release-notes.md
|
|
233
|
+
echo "::endgroup::"
|
|
234
|
+
if [ ! -s release-notes.md ]; then
|
|
235
|
+
echo "::error::CHANGELOG section for v${TAG} extracted to nothing"
|
|
236
|
+
exit 1
|
|
237
|
+
fi
|
|
238
|
+
cat >> release-notes.md <<'NOTES'
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
The AppImage bundles third-party software under its own licences; the
|
|
243
|
+
list, with versions and where to get the source, is in `THIRD-PARTY-NOTICES`
|
|
244
|
+
below and inside the AppImage. Versions that were taken down, and why, are
|
|
245
|
+
listed in [docs/releases.md](https://github.com/Rockykln/refrain/blob/main/docs/releases.md).
|
|
246
|
+
NOTES
|
|
247
|
+
|
|
248
|
+
- name: Hand the release files to the publishing jobs
|
|
249
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
250
|
+
with:
|
|
251
|
+
name: release
|
|
252
|
+
path: |
|
|
253
|
+
dist/
|
|
254
|
+
release-notes.md
|
|
255
|
+
if-no-files-found: error
|
|
256
|
+
|
|
257
|
+
- name: Hand the PyPI files to the publishing job
|
|
258
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
259
|
+
with:
|
|
260
|
+
name: pypi-dist
|
|
261
|
+
path: pypi-dist/
|
|
262
|
+
if-no-files-found: error
|
|
263
|
+
|
|
264
|
+
attest:
|
|
265
|
+
needs: build
|
|
266
|
+
runs-on: ubuntu-24.04
|
|
267
|
+
permissions:
|
|
268
|
+
contents: read
|
|
269
|
+
id-token: write # Sigstore signing certificate for the attestation
|
|
270
|
+
attestations: write # store the attestation with the repository
|
|
271
|
+
|
|
272
|
+
steps:
|
|
273
|
+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
274
|
+
with:
|
|
275
|
+
name: release
|
|
276
|
+
|
|
277
|
+
# Verify with: gh attestation verify <file> --repo Rockykln/refrain
|
|
278
|
+
- name: Attest build provenance of the release files
|
|
279
|
+
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
|
|
280
|
+
with:
|
|
281
|
+
subject-path: |
|
|
282
|
+
dist/*.whl
|
|
283
|
+
dist/*.tar.gz
|
|
284
|
+
dist/*.AppImage
|
|
285
|
+
dist/*.AppImage.zsync
|
|
286
|
+
dist/*.cdx.json
|
|
287
|
+
dist/SHA256SUMS
|
|
288
|
+
dist/THIRD-PARTY-NOTICES
|
|
289
|
+
|
|
290
|
+
github-release:
|
|
291
|
+
needs: [build, attest]
|
|
292
|
+
runs-on: ubuntu-24.04
|
|
293
|
+
permissions:
|
|
294
|
+
contents: write # required to create the release
|
|
295
|
+
|
|
296
|
+
steps:
|
|
297
|
+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
298
|
+
with:
|
|
299
|
+
name: release
|
|
300
|
+
|
|
301
|
+
# A draft: the owner signs SHA256SUMS on their own machine and only then
|
|
302
|
+
# publishes it (packaging/release_key.py release <tag>).
|
|
303
|
+
- name: Create GitHub release (draft)
|
|
304
|
+
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
|
|
305
|
+
with:
|
|
306
|
+
# Use our CHANGELOG section (extracted above), not GitHub's
|
|
307
|
+
# auto-generated notes — the in-app update dialog renders
|
|
308
|
+
# this body verbatim so it has to actually be the changelog.
|
|
309
|
+
body_path: release-notes.md
|
|
310
|
+
generate_release_notes: false
|
|
311
|
+
draft: true
|
|
312
|
+
prerelease: ${{ contains(github.ref, '-') }} # e.g. v0.2.0-rc1
|
|
313
|
+
name: "Refrain ${{ github.ref_name }}"
|
|
314
|
+
files: |
|
|
315
|
+
dist/*.whl
|
|
316
|
+
dist/*.tar.gz
|
|
317
|
+
dist/*.AppImage
|
|
318
|
+
dist/*.AppImage.zsync
|
|
319
|
+
dist/*.cdx.json
|
|
320
|
+
dist/SHA256SUMS
|
|
321
|
+
dist/THIRD-PARTY-NOTICES
|
|
322
|
+
|
|
323
|
+
pypi:
|
|
324
|
+
# After the GitHub release, as before the jobs were split.
|
|
325
|
+
needs: github-release
|
|
326
|
+
runs-on: ubuntu-24.04
|
|
327
|
+
environment:
|
|
328
|
+
name: pypi
|
|
329
|
+
url: https://pypi.org/project/refrain/
|
|
330
|
+
permissions:
|
|
331
|
+
id-token: write # required by PyPA Trusted Publishers (OIDC) for PyPI upload
|
|
332
|
+
|
|
333
|
+
steps:
|
|
334
|
+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
335
|
+
with:
|
|
336
|
+
name: pypi-dist
|
|
337
|
+
path: pypi-dist/
|
|
338
|
+
|
|
339
|
+
# PyPI upload via Trusted Publishers (OIDC). No API token needed; the
|
|
340
|
+
# one-time setup is on pypi.org → Manage → Publishing → Add a new
|
|
341
|
+
# pending publisher (project: refrain, owner: Rockykln,
|
|
342
|
+
# repository: refrain, workflow: release.yml, environment: pypi).
|
|
343
|
+
# Skips silently if the version already exists on PyPI so re-running
|
|
344
|
+
# a release for AppImage-only fixes doesn't fail the workflow.
|
|
345
|
+
- name: Publish to PyPI
|
|
346
|
+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
|
347
|
+
with:
|
|
348
|
+
packages-dir: pypi-dist/
|
|
349
|
+
skip-existing: true
|
|
350
|
+
verbose: true
|
|
351
|
+
attestations: true
|
|
@@ -15,7 +15,6 @@ on:
|
|
|
15
15
|
|
|
16
16
|
permissions:
|
|
17
17
|
contents: read
|
|
18
|
-
security-events: write
|
|
19
18
|
|
|
20
19
|
jobs:
|
|
21
20
|
|
|
@@ -23,15 +22,16 @@ jobs:
|
|
|
23
22
|
name: bandit (python SAST)
|
|
24
23
|
runs-on: ubuntu-latest
|
|
25
24
|
steps:
|
|
26
|
-
- uses: actions/checkout@v7
|
|
25
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
27
26
|
|
|
28
|
-
- uses: actions/setup-python@v7
|
|
27
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
29
28
|
with:
|
|
30
29
|
python-version: "3.12"
|
|
31
30
|
cache: pip
|
|
31
|
+
cache-dependency-path: requirements-dev.lock
|
|
32
32
|
|
|
33
33
|
- name: Install bandit
|
|
34
|
-
run: pip install bandit
|
|
34
|
+
run: pip install --require-hashes -c requirements-dev.lock bandit
|
|
35
35
|
|
|
36
36
|
- name: Run bandit (medium + high severity only)
|
|
37
37
|
# Config (skips, exclude_dirs) lives in pyproject.toml [tool.bandit].
|
|
@@ -42,12 +42,13 @@ jobs:
|
|
|
42
42
|
name: pip-audit (dependency CVEs)
|
|
43
43
|
runs-on: ubuntu-latest
|
|
44
44
|
steps:
|
|
45
|
-
- uses: actions/checkout@v7
|
|
45
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
46
46
|
|
|
47
|
-
- uses: actions/setup-python@v7
|
|
47
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
48
48
|
with:
|
|
49
49
|
python-version: "3.12"
|
|
50
50
|
cache: pip
|
|
51
|
+
cache-dependency-path: requirements-dev.lock
|
|
51
52
|
|
|
52
53
|
- name: Install system headers for dbus-python build
|
|
53
54
|
# dbus-python is a C-extension module; pip-audit's resolver tries
|
|
@@ -60,8 +61,7 @@ jobs:
|
|
|
60
61
|
|
|
61
62
|
- name: Install pip-audit
|
|
62
63
|
run: |
|
|
63
|
-
|
|
64
|
-
pip install pip-audit
|
|
64
|
+
pip install --require-hashes -c requirements-dev.lock pip-audit
|
|
65
65
|
|
|
66
66
|
- name: Run pip-audit on requirements.txt
|
|
67
67
|
# --strict makes the job fail on any known vulnerability in the
|
|
@@ -69,16 +69,21 @@ jobs:
|
|
|
69
69
|
# noise is minimal.
|
|
70
70
|
run: pip-audit -r requirements.txt --strict
|
|
71
71
|
|
|
72
|
+
- name: Run pip-audit on the pinned dev/CI lock
|
|
73
|
+
# Nothing updates requirements-dev.lock on its own; a finding here
|
|
74
|
+
# means: regenerate it (command at the top of the file).
|
|
75
|
+
run: pip-audit -r requirements-dev.lock --require-hashes --disable-pip --strict
|
|
76
|
+
|
|
72
77
|
trufflehog:
|
|
73
78
|
name: trufflehog (secret detection)
|
|
74
79
|
runs-on: ubuntu-latest
|
|
75
80
|
steps:
|
|
76
|
-
- uses: actions/checkout@v7
|
|
81
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
77
82
|
with:
|
|
78
83
|
fetch-depth: 0
|
|
79
84
|
|
|
80
85
|
- name: Run TruffleHog
|
|
81
|
-
uses: trufflesecurity/trufflehog@
|
|
86
|
+
uses: trufflesecurity/trufflehog@f714bf454f350590f4a24c3ddb1aef02c35bf5b6 # v3.97.5
|
|
82
87
|
with:
|
|
83
88
|
# --only-verified: a secret only counts as flagged when TruffleHog
|
|
84
89
|
# successfully validates it against the live service. Drops the
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
name: tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, master]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
strategy:
|
|
16
|
+
matrix:
|
|
17
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
18
|
+
fail-fast: false
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
22
|
+
|
|
23
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
24
|
+
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
25
|
+
with:
|
|
26
|
+
python-version: ${{ matrix.python-version }}
|
|
27
|
+
cache: pip
|
|
28
|
+
cache-dependency-path: requirements-dev.lock
|
|
29
|
+
|
|
30
|
+
- name: Install runtime + test dependencies
|
|
31
|
+
# The newer tests import refrain modules that pull PySide6 /
|
|
32
|
+
# pypresence / dbus-python at module load, so the runtime deps
|
|
33
|
+
# have to be present in CI too. PySide6.QtWidgets requires the
|
|
34
|
+
# EGL / xkbcommon / GL / fontconfig system libs even when run
|
|
35
|
+
# under QT_QPA_PLATFORM=offscreen — without them the import
|
|
36
|
+
# itself fails before the test can opt out. dbus-daemon/dbus-bin
|
|
37
|
+
# give tests/integration a real dbus-run-session to spin up its
|
|
38
|
+
# own private bus; it skips itself if either is missing.
|
|
39
|
+
run: |
|
|
40
|
+
sudo apt-get update
|
|
41
|
+
sudo apt-get install -y \
|
|
42
|
+
libdbus-1-dev libglib2.0-dev pkg-config \
|
|
43
|
+
libegl1 libxkbcommon0 libfontconfig1 libgl1 \
|
|
44
|
+
dbus-daemon dbus-bin
|
|
45
|
+
pip install --require-hashes -r requirements-dev.lock
|
|
46
|
+
pip install --no-deps -e .
|
|
47
|
+
|
|
48
|
+
- name: Compile-check every module
|
|
49
|
+
run: python -m compileall -q src/
|
|
50
|
+
|
|
51
|
+
- name: Run tests with coverage
|
|
52
|
+
run: PYTHONPATH=src pytest --cov --cov-report=term-missing --cov-fail-under=99
|
|
53
|
+
|
|
54
|
+
- name: Coverage in the job summary
|
|
55
|
+
if: ${{ !cancelled() && hashFiles('.coverage') != '' }}
|
|
56
|
+
run: |
|
|
57
|
+
{
|
|
58
|
+
echo "### Coverage (Python ${{ matrix.python-version }}): $(coverage report --format=total)%"
|
|
59
|
+
echo
|
|
60
|
+
coverage report --format=markdown
|
|
61
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
62
|
+
|
|
63
|
+
lowest:
|
|
64
|
+
# The lower bounds in pyproject.toml, on the oldest supported Python.
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
steps:
|
|
67
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
68
|
+
|
|
69
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
70
|
+
with:
|
|
71
|
+
python-version: "3.11"
|
|
72
|
+
cache: pip
|
|
73
|
+
cache-dependency-path: |
|
|
74
|
+
requirements-dev.lock
|
|
75
|
+
requirements-lowest.txt
|
|
76
|
+
|
|
77
|
+
- name: Install the oldest supported dependencies
|
|
78
|
+
run: |
|
|
79
|
+
sudo apt-get update
|
|
80
|
+
sudo apt-get install -y \
|
|
81
|
+
libdbus-1-dev libglib2.0-dev pkg-config \
|
|
82
|
+
libegl1 libxkbcommon0 libfontconfig1 libgl1 \
|
|
83
|
+
dbus-daemon dbus-bin
|
|
84
|
+
pip install --require-hashes -c requirements-dev.lock pytest
|
|
85
|
+
pip install -r requirements-lowest.txt
|
|
86
|
+
pip install --no-deps -e .
|
|
87
|
+
pip check
|
|
88
|
+
|
|
89
|
+
- name: Run tests
|
|
90
|
+
run: PYTHONPATH=src pytest
|
|
91
|
+
|
|
92
|
+
mpris_integration:
|
|
93
|
+
# Real dbus-daemon, real dbus-python, no mocks: drives MPRISSource
|
|
94
|
+
# against a fake MPRIS player over a private session bus. Split out
|
|
95
|
+
# from `test` so its result is its own signal; it also runs as part
|
|
96
|
+
# of `test`/`lowest` above, which collect all of tests/.
|
|
97
|
+
runs-on: ubuntu-latest
|
|
98
|
+
steps:
|
|
99
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
100
|
+
|
|
101
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
102
|
+
with:
|
|
103
|
+
python-version: "3.12"
|
|
104
|
+
cache: pip
|
|
105
|
+
cache-dependency-path: requirements-dev.lock
|
|
106
|
+
|
|
107
|
+
- name: Install runtime + test dependencies
|
|
108
|
+
run: |
|
|
109
|
+
sudo apt-get update
|
|
110
|
+
sudo apt-get install -y \
|
|
111
|
+
libdbus-1-dev libglib2.0-dev pkg-config \
|
|
112
|
+
libegl1 libxkbcommon0 libfontconfig1 libgl1 \
|
|
113
|
+
dbus-daemon dbus-bin
|
|
114
|
+
pip install --require-hashes -r requirements-dev.lock
|
|
115
|
+
pip install --no-deps -e .
|
|
116
|
+
|
|
117
|
+
- name: Run the MPRIS D-Bus integration tests
|
|
118
|
+
run: QT_QPA_PLATFORM=offscreen PYTHONPATH=src pytest -q tests/integration
|
|
119
|
+
|
|
120
|
+
lint:
|
|
121
|
+
runs-on: ubuntu-latest
|
|
122
|
+
steps:
|
|
123
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
124
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
125
|
+
with:
|
|
126
|
+
python-version: "3.12"
|
|
127
|
+
cache: pip
|
|
128
|
+
cache-dependency-path: requirements-dev.lock
|
|
129
|
+
|
|
130
|
+
- name: Install ruff
|
|
131
|
+
# The version comes from requirements-dev.lock, like everywhere else.
|
|
132
|
+
run: pip install --require-hashes -c requirements-dev.lock ruff
|
|
133
|
+
|
|
134
|
+
- name: Lint with ruff
|
|
135
|
+
run: ruff check . --output-format=github
|
|
136
|
+
|
|
137
|
+
- name: Format check with ruff
|
|
138
|
+
run: ruff format --check .
|
|
139
|
+
|
|
140
|
+
typecheck:
|
|
141
|
+
# The oldest supported Python, the version [tool.mypy] checks against.
|
|
142
|
+
runs-on: ubuntu-latest
|
|
143
|
+
steps:
|
|
144
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
145
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
146
|
+
with:
|
|
147
|
+
python-version: "3.11"
|
|
148
|
+
cache: pip
|
|
149
|
+
cache-dependency-path: requirements-dev.lock
|
|
150
|
+
|
|
151
|
+
- name: Install mypy and the typed runtime dependencies
|
|
152
|
+
# PySide6 and pypresence ship their own type information and are
|
|
153
|
+
# checked against it; dbus-python has none (see [tool.mypy]).
|
|
154
|
+
run: pip install --require-hashes -c requirements-dev.lock mypy pypresence PySide6
|
|
155
|
+
|
|
156
|
+
- name: Type-check with mypy
|
|
157
|
+
run: mypy
|