refrain 0.5.2__tar.gz → 0.5.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 (348) hide show
  1. {refrain-0.5.2 → refrain-0.5.3}/.github/ISSUE_TEMPLATE/bug_report.yml +12 -3
  2. {refrain-0.5.2 → refrain-0.5.3}/.github/codeql/codeql-config.yml +1 -1
  3. {refrain-0.5.2 → refrain-0.5.3}/.github/pull_request_template.md +1 -0
  4. {refrain-0.5.2 → refrain-0.5.3}/.github/workflows/codeql.yml +3 -3
  5. {refrain-0.5.2 → refrain-0.5.3}/.github/workflows/release.yml +142 -34
  6. {refrain-0.5.2 → refrain-0.5.3}/.github/workflows/security.yml +14 -9
  7. refrain-0.5.3/.github/workflows/tests.yml +106 -0
  8. {refrain-0.5.2 → refrain-0.5.3}/CHANGELOG.md +296 -6
  9. {refrain-0.5.2 → refrain-0.5.3}/CONTRIBUTING.md +18 -9
  10. {refrain-0.5.2 → refrain-0.5.3}/LEGAL.md +39 -13
  11. refrain-0.5.3/LICENSE +109 -0
  12. {refrain-0.5.2 → refrain-0.5.3}/PKG-INFO +255 -79
  13. {refrain-0.5.2 → refrain-0.5.3}/PRIVACY.md +58 -27
  14. {refrain-0.5.2 → refrain-0.5.3}/README.md +250 -74
  15. {refrain-0.5.2 → refrain-0.5.3}/SECURITY.md +42 -6
  16. {refrain-0.5.2 → refrain-0.5.3}/docs/architecture.md +11 -7
  17. refrain-0.5.3/docs/bluetooth.md +152 -0
  18. refrain-0.5.3/docs/developer-mode.md +114 -0
  19. {refrain-0.5.2 → refrain-0.5.3}/docs/faq.md +43 -23
  20. {refrain-0.5.2 → refrain-0.5.3}/docs/lastfm.md +19 -10
  21. refrain-0.5.3/docs/releases.md +40 -0
  22. refrain-0.5.2/ROADMAP.md → refrain-0.5.3/docs/roadmap.md +47 -2
  23. {refrain-0.5.2 → refrain-0.5.3}/docs/screenshots/README.md +40 -27
  24. refrain-0.5.3/docs/screenshots/discord-rpc-light.png +0 -0
  25. refrain-0.5.3/docs/screenshots/discord-rpc.png +0 -0
  26. refrain-0.5.3/docs/screenshots/history-light.png +0 -0
  27. refrain-0.5.3/docs/screenshots/history.png +0 -0
  28. refrain-0.5.3/docs/screenshots/legal-light.png +0 -0
  29. refrain-0.5.3/docs/screenshots/legal.png +0 -0
  30. refrain-0.5.3/docs/screenshots/live-log-light.png +0 -0
  31. refrain-0.5.3/docs/screenshots/live-log.png +0 -0
  32. refrain-0.5.3/docs/screenshots/notification-light.png +0 -0
  33. refrain-0.5.3/docs/screenshots/notification.png +0 -0
  34. refrain-0.5.3/docs/screenshots/render.py +411 -0
  35. refrain-0.5.3/docs/screenshots/settings-advanced-light.png +0 -0
  36. refrain-0.5.3/docs/screenshots/settings-advanced.png +0 -0
  37. refrain-0.5.3/docs/screenshots/settings-general-light.png +0 -0
  38. refrain-0.5.3/docs/screenshots/settings-general.png +0 -0
  39. refrain-0.5.3/docs/screenshots/settings-history-light.png +0 -0
  40. refrain-0.5.3/docs/screenshots/settings-history.png +0 -0
  41. refrain-0.5.3/docs/screenshots/settings-lastfm-light.png +0 -0
  42. refrain-0.5.3/docs/screenshots/settings-lastfm.png +0 -0
  43. refrain-0.5.3/docs/screenshots/settings-sources-light.png +0 -0
  44. refrain-0.5.3/docs/screenshots/settings-sources.png +0 -0
  45. refrain-0.5.3/docs/screenshots/settings-updates-light.png +0 -0
  46. refrain-0.5.3/docs/screenshots/settings-updates.png +0 -0
  47. refrain-0.5.3/docs/screenshots/social.py +100 -0
  48. refrain-0.5.3/docs/screenshots/status-light.png +0 -0
  49. refrain-0.5.3/docs/screenshots/status.png +0 -0
  50. refrain-0.5.3/docs/screenshots/tray-menu-light.png +0 -0
  51. refrain-0.5.3/docs/screenshots/tray-menu.png +0 -0
  52. refrain-0.5.3/docs/screenshots/update-dialog-light.png +0 -0
  53. refrain-0.5.3/docs/screenshots/update-dialog.png +0 -0
  54. refrain-0.5.3/docs/screenshots/welcome-light.png +0 -0
  55. refrain-0.5.3/docs/screenshots/welcome.png +0 -0
  56. refrain-0.5.3/docs/social-preview.png +0 -0
  57. refrain-0.5.3/docs/stats.py +245 -0
  58. {refrain-0.5.2 → refrain-0.5.3}/docs/test-matrix.md +18 -14
  59. refrain-0.5.3/docs/troubleshooting.md +368 -0
  60. {refrain-0.5.2 → refrain-0.5.3}/packaging/README.md +94 -13
  61. refrain-0.5.3/packaging/appimage/AppImageBuilder.yml +64 -0
  62. refrain-0.5.3/packaging/appimage/prune_qt.py +95 -0
  63. refrain-0.5.3/packaging/appimage/third_party_notices.py +240 -0
  64. {refrain-0.5.2 → refrain-0.5.3}/packaging/aur/refrain/.SRCINFO +2 -2
  65. {refrain-0.5.2 → refrain-0.5.3}/packaging/aur/refrain/PKGBUILD +2 -4
  66. {refrain-0.5.2 → refrain-0.5.3}/packaging/aur/refrain-git/.SRCINFO +2 -1
  67. {refrain-0.5.2 → refrain-0.5.3}/packaging/aur/refrain-git/PKGBUILD +5 -2
  68. {refrain-0.5.2 → refrain-0.5.3}/packaging/flatpak/io.github.Rockykln.Refrain.metainfo.xml +22 -26
  69. {refrain-0.5.2 → refrain-0.5.3}/packaging/flatpak/io.github.Rockykln.Refrain.yaml +8 -3
  70. refrain-0.5.3/packaging/release_key.py +263 -0
  71. {refrain-0.5.2 → refrain-0.5.3}/pyproject.toml +7 -16
  72. refrain-0.5.3/requirements-dev.lock +732 -0
  73. refrain-0.5.3/requirements-lowest.txt +4 -0
  74. refrain-0.5.3/requirements.txt +3 -0
  75. refrain-0.5.3/src/refrain/__init__.py +1 -0
  76. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/app.py +392 -97
  77. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/autostart.py +19 -7
  78. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/browser.py +2 -9
  79. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/config.py +259 -27
  80. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/cover_art.py +121 -34
  81. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/cover_fetcher.py +164 -64
  82. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/daemon.py +292 -129
  83. refrain-0.5.3/src/refrain/dev_metrics.py +547 -0
  84. refrain-0.5.3/src/refrain/diagnostics.py +110 -0
  85. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/discord_app.py +5 -18
  86. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/discord_rpc.py +337 -60
  87. refrain-0.5.3/src/refrain/ed25519.py +101 -0
  88. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/history.py +33 -30
  89. refrain-0.5.3/src/refrain/i18n/refrain_cs.qm +0 -0
  90. refrain-0.5.3/src/refrain/i18n/refrain_cs.ts +2151 -0
  91. refrain-0.5.3/src/refrain/i18n/refrain_de.qm +0 -0
  92. refrain-0.5.3/src/refrain/i18n/refrain_de.ts +2144 -0
  93. refrain-0.5.3/src/refrain/i18n/refrain_en.qm +0 -0
  94. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/i18n/refrain_en.ts +27 -5
  95. refrain-0.5.3/src/refrain/i18n/refrain_es.qm +0 -0
  96. refrain-0.5.3/src/refrain/i18n/refrain_es.ts +2144 -0
  97. refrain-0.5.3/src/refrain/i18n/refrain_fr.qm +0 -0
  98. refrain-0.5.3/src/refrain/i18n/refrain_fr.ts +2144 -0
  99. refrain-0.5.3/src/refrain/i18n/refrain_it.qm +0 -0
  100. refrain-0.5.3/src/refrain/i18n/refrain_it.ts +2144 -0
  101. refrain-0.5.3/src/refrain/i18n/refrain_ja.qm +0 -0
  102. refrain-0.5.3/src/refrain/i18n/refrain_ja.ts +2137 -0
  103. refrain-0.5.3/src/refrain/i18n/refrain_ko.qm +0 -0
  104. refrain-0.5.3/src/refrain/i18n/refrain_ko.ts +2137 -0
  105. refrain-0.5.3/src/refrain/i18n/refrain_nl.qm +0 -0
  106. refrain-0.5.3/src/refrain/i18n/refrain_nl.ts +2144 -0
  107. refrain-0.5.3/src/refrain/i18n/refrain_pl.qm +0 -0
  108. refrain-0.5.3/src/refrain/i18n/refrain_pl.ts +2151 -0
  109. refrain-0.5.3/src/refrain/i18n/refrain_pt.qm +0 -0
  110. refrain-0.5.3/src/refrain/i18n/refrain_pt.ts +2144 -0
  111. refrain-0.5.3/src/refrain/i18n/refrain_ru.qm +0 -0
  112. refrain-0.5.3/src/refrain/i18n/refrain_ru.ts +2151 -0
  113. refrain-0.5.3/src/refrain/i18n/refrain_sv.qm +0 -0
  114. refrain-0.5.3/src/refrain/i18n/refrain_sv.ts +2144 -0
  115. refrain-0.5.3/src/refrain/i18n/refrain_tr.qm +0 -0
  116. refrain-0.5.3/src/refrain/i18n/refrain_tr.ts +2137 -0
  117. refrain-0.5.3/src/refrain/i18n/refrain_uk.qm +0 -0
  118. refrain-0.5.3/src/refrain/i18n/refrain_uk.ts +2151 -0
  119. refrain-0.5.3/src/refrain/i18n/refrain_zh_CN.qm +0 -0
  120. refrain-0.5.3/src/refrain/i18n/refrain_zh_CN.ts +2137 -0
  121. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/logging_setup.py +27 -1
  122. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/paths.py +7 -1
  123. refrain-0.5.3/src/refrain/qt_libraries.py +115 -0
  124. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/scrobble.py +75 -45
  125. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/scrobble_queue.py +75 -25
  126. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/secrets_store.py +28 -39
  127. refrain-0.5.3/src/refrain/service_status.py +139 -0
  128. refrain-0.5.3/src/refrain/single_instance.py +114 -0
  129. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/song_lengths.py +45 -23
  130. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/sources/bluetooth.py +69 -61
  131. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/sources/mpris.py +203 -102
  132. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/sources/mpris_server.py +34 -32
  133. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/startup_check.py +3 -17
  134. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/timing.py +60 -48
  135. refrain-0.5.3/src/refrain/ui/clock.py +43 -0
  136. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/ui/cursors.py +29 -16
  137. refrain-0.5.3/src/refrain/ui/external_link.py +33 -0
  138. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/ui/history_window.py +201 -83
  139. refrain-0.5.3/src/refrain/ui/layout_check.py +281 -0
  140. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/ui/legal_dialog.py +50 -31
  141. refrain-0.5.3/src/refrain/ui/log_window.py +473 -0
  142. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/ui/settings_window.py +800 -304
  143. refrain-0.5.3/src/refrain/ui/status_window.py +908 -0
  144. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/ui/tray.py +148 -79
  145. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/ui/update_dialog.py +35 -15
  146. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/ui/welcome_dialog.py +40 -40
  147. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/uninstall.py +11 -27
  148. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/updater.py +215 -67
  149. refrain-0.5.3/tests/conftest.py +110 -0
  150. refrain-0.5.3/tests/daemon_fakes.py +359 -0
  151. refrain-0.5.3/tests/release_signing.py +18 -0
  152. refrain-0.5.3/tests/test_app_cli.py +184 -0
  153. refrain-0.5.3/tests/test_app_edges.py +225 -0
  154. refrain-0.5.3/tests/test_app_helpers.py +241 -0
  155. refrain-0.5.3/tests/test_app_main.py +1287 -0
  156. {refrain-0.5.2 → refrain-0.5.3}/tests/test_autostart.py +31 -0
  157. refrain-0.5.3/tests/test_autostart_more.py +107 -0
  158. refrain-0.5.3/tests/test_bluetooth_apps.py +105 -0
  159. refrain-0.5.3/tests/test_bluetooth_source.py +473 -0
  160. refrain-0.5.3/tests/test_browser.py +128 -0
  161. refrain-0.5.3/tests/test_browser_edges.py +28 -0
  162. {refrain-0.5.2 → refrain-0.5.3}/tests/test_button_url.py +5 -6
  163. refrain-0.5.3/tests/test_clock.py +57 -0
  164. {refrain-0.5.2 → refrain-0.5.3}/tests/test_config.py +97 -20
  165. refrain-0.5.3/tests/test_config_edges.py +143 -0
  166. refrain-0.5.3/tests/test_config_notifications.py +40 -0
  167. {refrain-0.5.2 → refrain-0.5.3}/tests/test_config_preserve.py +2 -6
  168. refrain-0.5.3/tests/test_config_validation.py +227 -0
  169. {refrain-0.5.2 → refrain-0.5.3}/tests/test_cover_art.py +77 -39
  170. refrain-0.5.3/tests/test_cover_art_edges.py +79 -0
  171. refrain-0.5.3/tests/test_cover_art_more.py +214 -0
  172. {refrain-0.5.2 → refrain-0.5.3}/tests/test_cover_fetcher.py +18 -34
  173. refrain-0.5.3/tests/test_cover_fetcher_cache.py +225 -0
  174. refrain-0.5.3/tests/test_cover_fetcher_edges.py +105 -0
  175. refrain-0.5.3/tests/test_cover_history.py +150 -0
  176. {refrain-0.5.2 → refrain-0.5.3}/tests/test_crash_log.py +2 -2
  177. {refrain-0.5.2 → refrain-0.5.3}/tests/test_daemon_album_format.py +3 -8
  178. refrain-0.5.3/tests/test_daemon_edges.py +131 -0
  179. {refrain-0.5.2 → refrain-0.5.3}/tests/test_daemon_idle.py +8 -26
  180. refrain-0.5.3/tests/test_daemon_lifecycle.py +496 -0
  181. refrain-0.5.3/tests/test_daemon_pipeline.py +609 -0
  182. refrain-0.5.3/tests/test_daemon_service_states.py +143 -0
  183. refrain-0.5.3/tests/test_daemon_stop.py +45 -0
  184. refrain-0.5.3/tests/test_daemon_tick_lookups.py +120 -0
  185. {refrain-0.5.2 → refrain-0.5.3}/tests/test_dbus_dispatch.py +8 -35
  186. {refrain-0.5.2 → refrain-0.5.3}/tests/test_desktop_entries.py +19 -0
  187. refrain-0.5.3/tests/test_dev_metrics.py +425 -0
  188. refrain-0.5.3/tests/test_developer_mode_ui.py +334 -0
  189. refrain-0.5.3/tests/test_diagnostics.py +96 -0
  190. {refrain-0.5.2 → refrain-0.5.3}/tests/test_discord_app.py +8 -32
  191. refrain-0.5.3/tests/test_discord_app_edges.py +26 -0
  192. {refrain-0.5.2 → refrain-0.5.3}/tests/test_discord_ipc_scan.py +1 -16
  193. {refrain-0.5.2 → refrain-0.5.3}/tests/test_discord_listening.py +1 -6
  194. {refrain-0.5.2 → refrain-0.5.3}/tests/test_discord_multi_client.py +14 -32
  195. {refrain-0.5.2 → refrain-0.5.3}/tests/test_discord_rpc.py +87 -25
  196. refrain-0.5.3/tests/test_discord_rpc_connect.py +367 -0
  197. refrain-0.5.3/tests/test_discord_rpc_robustness.py +617 -0
  198. refrain-0.5.3/tests/test_ed25519.py +183 -0
  199. refrain-0.5.3/tests/test_external_link.py +58 -0
  200. refrain-0.5.3/tests/test_foreign_locales.py +88 -0
  201. {refrain-0.5.2 → refrain-0.5.3}/tests/test_history.py +40 -11
  202. refrain-0.5.3/tests/test_history_edges.py +261 -0
  203. {refrain-0.5.2 → refrain-0.5.3}/tests/test_history_window.py +205 -21
  204. refrain-0.5.3/tests/test_history_window_edges.py +212 -0
  205. {refrain-0.5.2 → refrain-0.5.3}/tests/test_imports.py +2 -7
  206. refrain-0.5.3/tests/test_layout_check.py +341 -0
  207. refrain-0.5.3/tests/test_layout_languages.py +113 -0
  208. {refrain-0.5.2 → refrain-0.5.3}/tests/test_legal_notice.py +6 -14
  209. refrain-0.5.3/tests/test_log_window_filter.py +47 -0
  210. refrain-0.5.3/tests/test_logging_setup.py +128 -0
  211. refrain-0.5.3/tests/test_logging_setup_edges.py +59 -0
  212. refrain-0.5.3/tests/test_mpris_controls.py +137 -0
  213. {refrain-0.5.2 → refrain-0.5.3}/tests/test_mpris_dispatch.py +16 -26
  214. refrain-0.5.3/tests/test_mpris_edges.py +117 -0
  215. refrain-0.5.3/tests/test_mpris_server_props.py +381 -0
  216. refrain-0.5.3/tests/test_mpris_source_more.py +603 -0
  217. {refrain-0.5.2 → refrain-0.5.3}/tests/test_notify_argv.py +6 -0
  218. {refrain-0.5.2 → refrain-0.5.3}/tests/test_paths.py +21 -0
  219. {refrain-0.5.2 → refrain-0.5.3}/tests/test_position_resolver.py +79 -84
  220. refrain-0.5.3/tests/test_private_files.py +89 -0
  221. refrain-0.5.3/tests/test_qt_libraries.py +95 -0
  222. refrain-0.5.3/tests/test_qt_libraries_more.py +85 -0
  223. {refrain-0.5.2 → refrain-0.5.3}/tests/test_qt_plugin_path.py +5 -23
  224. refrain-0.5.3/tests/test_readme_stats.py +101 -0
  225. refrain-0.5.3/tests/test_release_key.py +214 -0
  226. {refrain-0.5.2 → refrain-0.5.3}/tests/test_scrobble.py +1 -5
  227. refrain-0.5.3/tests/test_scrobble_edges.py +50 -0
  228. refrain-0.5.3/tests/test_scrobble_more.py +305 -0
  229. refrain-0.5.3/tests/test_scrobble_queue_edges.py +75 -0
  230. refrain-0.5.3/tests/test_scrobble_queue_file.py +194 -0
  231. {refrain-0.5.2 → refrain-0.5.3}/tests/test_scrobbler.py +59 -32
  232. {refrain-0.5.2 → refrain-0.5.3}/tests/test_secrets_store.py +118 -20
  233. refrain-0.5.3/tests/test_secrets_store_edges.py +32 -0
  234. refrain-0.5.3/tests/test_secrets_store_more.py +234 -0
  235. refrain-0.5.3/tests/test_service_status.py +120 -0
  236. refrain-0.5.3/tests/test_settings_app_name.py +74 -0
  237. refrain-0.5.3/tests/test_settings_apply.py +321 -0
  238. {refrain-0.5.2 → refrain-0.5.3}/tests/test_settings_history.py +3 -6
  239. refrain-0.5.3/tests/test_settings_lastfm.py +226 -0
  240. refrain-0.5.3/tests/test_settings_more.py +366 -0
  241. {refrain-0.5.2 → refrain-0.5.3}/tests/test_settings_reset.py +5 -16
  242. refrain-0.5.3/tests/test_settings_unsaved.py +581 -0
  243. refrain-0.5.3/tests/test_settings_window_edges.py +273 -0
  244. refrain-0.5.3/tests/test_single_instance.py +117 -0
  245. refrain-0.5.3/tests/test_single_instance_errors.py +41 -0
  246. {refrain-0.5.2 → refrain-0.5.3}/tests/test_song_lengths.py +96 -6
  247. refrain-0.5.3/tests/test_song_lengths_edges.py +33 -0
  248. {refrain-0.5.2 → refrain-0.5.3}/tests/test_source_selection.py +2 -7
  249. {refrain-0.5.2 → refrain-0.5.3}/tests/test_startup_check.py +5 -18
  250. refrain-0.5.3/tests/test_status_window.py +461 -0
  251. refrain-0.5.3/tests/test_system_certificates.py +57 -0
  252. refrain-0.5.3/tests/test_theme_colors.py +69 -0
  253. {refrain-0.5.2 → refrain-0.5.3}/tests/test_timing.py +7 -18
  254. {refrain-0.5.2 → refrain-0.5.3}/tests/test_translations.py +17 -7
  255. {refrain-0.5.2 → refrain-0.5.3}/tests/test_tray_init.py +18 -34
  256. refrain-0.5.3/tests/test_tray_states.py +295 -0
  257. {refrain-0.5.2 → refrain-0.5.3}/tests/test_ui_cursors.py +70 -21
  258. refrain-0.5.3/tests/test_ui_edges.py +217 -0
  259. {refrain-0.5.2 → refrain-0.5.3}/tests/test_ui_locale.py +6 -13
  260. {refrain-0.5.2 → refrain-0.5.3}/tests/test_uninstall.py +41 -14
  261. refrain-0.5.3/tests/test_update_dialog_cancel.py +133 -0
  262. refrain-0.5.3/tests/test_update_dialog_flows.py +208 -0
  263. refrain-0.5.3/tests/test_update_orchestrator.py +212 -0
  264. refrain-0.5.3/tests/test_updater.py +757 -0
  265. refrain-0.5.3/tests/test_updater_paths.py +484 -0
  266. {refrain-0.5.2 → refrain-0.5.3}/tests/test_welcome_dialog_growth.py +2 -13
  267. refrain-0.5.3/tests/test_welcome_probes.py +300 -0
  268. refrain-0.5.2/.github/workflows/tests.yml +0 -68
  269. refrain-0.5.2/LICENSE +0 -71
  270. refrain-0.5.2/docs/bluetooth.md +0 -141
  271. refrain-0.5.2/docs/screenshots/discord-rpc.png +0 -0
  272. refrain-0.5.2/docs/screenshots/history.png +0 -0
  273. refrain-0.5.2/docs/screenshots/legal.png +0 -0
  274. refrain-0.5.2/docs/screenshots/live-log.png +0 -0
  275. refrain-0.5.2/docs/screenshots/notification.png +0 -0
  276. refrain-0.5.2/docs/screenshots/settings-advanced.png +0 -0
  277. refrain-0.5.2/docs/screenshots/settings-general.png +0 -0
  278. refrain-0.5.2/docs/screenshots/settings-history.png +0 -0
  279. refrain-0.5.2/docs/screenshots/settings-lastfm.png +0 -0
  280. refrain-0.5.2/docs/screenshots/settings-sources.png +0 -0
  281. refrain-0.5.2/docs/screenshots/settings-updates.png +0 -0
  282. refrain-0.5.2/docs/screenshots/tray-menu.png +0 -0
  283. refrain-0.5.2/docs/screenshots/update-dialog.png +0 -0
  284. refrain-0.5.2/docs/screenshots/welcome.png +0 -0
  285. refrain-0.5.2/packaging/appimage/AppImageBuilder.yml +0 -80
  286. refrain-0.5.2/requirements.txt +0 -3
  287. refrain-0.5.2/src/refrain/__init__.py +0 -1
  288. refrain-0.5.2/src/refrain/i18n/refrain_de.qm +0 -0
  289. refrain-0.5.2/src/refrain/i18n/refrain_de.ts +0 -1274
  290. refrain-0.5.2/src/refrain/i18n/refrain_en.qm +0 -0
  291. refrain-0.5.2/src/refrain/i18n/refrain_es.qm +0 -0
  292. refrain-0.5.2/src/refrain/i18n/refrain_es.ts +0 -1274
  293. refrain-0.5.2/src/refrain/i18n/refrain_fr.qm +0 -0
  294. refrain-0.5.2/src/refrain/i18n/refrain_fr.ts +0 -1274
  295. refrain-0.5.2/src/refrain/i18n/refrain_it.qm +0 -0
  296. refrain-0.5.2/src/refrain/i18n/refrain_it.ts +0 -1274
  297. refrain-0.5.2/src/refrain/i18n/refrain_ja.qm +0 -0
  298. refrain-0.5.2/src/refrain/i18n/refrain_ja.ts +0 -1269
  299. refrain-0.5.2/src/refrain/i18n/refrain_pl.qm +0 -0
  300. refrain-0.5.2/src/refrain/i18n/refrain_pl.ts +0 -1279
  301. refrain-0.5.2/src/refrain/i18n/refrain_pt.qm +0 -0
  302. refrain-0.5.2/src/refrain/i18n/refrain_pt.ts +0 -1274
  303. refrain-0.5.2/src/refrain/i18n/refrain_ru.qm +0 -0
  304. refrain-0.5.2/src/refrain/i18n/refrain_ru.ts +0 -1279
  305. refrain-0.5.2/src/refrain/i18n/refrain_zh_CN.qm +0 -0
  306. refrain-0.5.2/src/refrain/i18n/refrain_zh_CN.ts +0 -1269
  307. refrain-0.5.2/src/refrain/single_instance.py +0 -53
  308. refrain-0.5.2/src/refrain/ui/log_window.py +0 -122
  309. refrain-0.5.2/tests/conftest.py +0 -69
  310. refrain-0.5.2/tests/test_bluetooth_apps.py +0 -70
  311. refrain-0.5.2/tests/test_browser.py +0 -57
  312. refrain-0.5.2/tests/test_mpris_controls.py +0 -57
  313. refrain-0.5.2/tests/test_settings_lastfm.py +0 -133
  314. refrain-0.5.2/tests/test_updater.py +0 -320
  315. {refrain-0.5.2 → refrain-0.5.3}/.editorconfig +0 -0
  316. {refrain-0.5.2 → refrain-0.5.3}/.gitattributes +0 -0
  317. {refrain-0.5.2 → refrain-0.5.3}/.github/FUNDING.yml +0 -0
  318. {refrain-0.5.2 → refrain-0.5.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  319. {refrain-0.5.2 → refrain-0.5.3}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  320. {refrain-0.5.2 → refrain-0.5.3}/.github/dependabot.yml +0 -0
  321. {refrain-0.5.2 → refrain-0.5.3}/.gitignore +0 -0
  322. {refrain-0.5.2 → refrain-0.5.3}/CODE_OF_CONDUCT.md +0 -0
  323. {refrain-0.5.2 → refrain-0.5.3}/Makefile +0 -0
  324. {refrain-0.5.2 → refrain-0.5.3}/docs/screenshots/demo-cover.png +0 -0
  325. {refrain-0.5.2 → refrain-0.5.3}/packaging/flatpak/io.github.Rockykln.Refrain.desktop +0 -0
  326. {refrain-0.5.2 → refrain-0.5.3}/packaging/flatpak/python-deps.json +0 -0
  327. {refrain-0.5.2 → refrain-0.5.3}/requirements-dev.txt +0 -0
  328. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/__main__.py +0 -0
  329. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/assets/icons/github-mark.svg +0 -0
  330. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/assets/icons/menu-quit.svg +0 -0
  331. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/assets/icons/menu-update.svg +0 -0
  332. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/assets/icons/refrain.png +0 -0
  333. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/assets/icons/refrain.svg +0 -0
  334. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/assets/icons/tray-paused-dark.svg +0 -0
  335. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/assets/icons/tray-paused.svg +0 -0
  336. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/assets/icons/tray-playing-dark.svg +0 -0
  337. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/assets/icons/tray-playing.svg +0 -0
  338. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/assets/icons/tray-stopped-dark.svg +0 -0
  339. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/assets/icons/tray-stopped.svg +0 -0
  340. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/assets/refrain.desktop +0 -0
  341. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/sources/__init__.py +0 -0
  342. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/sources/base.py +0 -0
  343. {refrain-0.5.2 → refrain-0.5.3}/src/refrain/ui/__init__.py +0 -0
  344. {refrain-0.5.2 → refrain-0.5.3}/tests/__init__.py +0 -0
  345. {refrain-0.5.2 → refrain-0.5.3}/tests/test_scrobble_queue.py +0 -0
  346. {refrain-0.5.2 → refrain-0.5.3}/tests/test_settings_github_icon.py +0 -0
  347. {refrain-0.5.2 → refrain-0.5.3}/tests/test_sources_base.py +0 -0
  348. {refrain-0.5.2 → refrain-0.5.3}/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.2"
16
+ placeholder: "0.5.3"
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
- Tracks playing at the time of the bug are useful context but no secrets
71
- live in the log.
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 best-effort cleanup paths
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.
@@ -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 }}"
@@ -6,31 +6,30 @@ on:
6
6
  tags:
7
7
  - "v*.*.*"
8
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.
9
12
  permissions:
10
- contents: write # required to create the release
11
- id-token: write # required by PyPA Trusted Publishers (OIDC) for PyPI upload
13
+ contents: read
12
14
 
13
15
  jobs:
14
- release:
15
- runs-on: ubuntu-latest
16
- environment:
17
- name: pypi
18
- url: https://pypi.org/project/refrain/
16
+ build:
17
+ runs-on: ubuntu-24.04
19
18
 
20
19
  steps:
21
20
  - name: Checkout (full history for changelog)
22
- uses: actions/checkout@v7
21
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
23
22
  with:
24
23
  fetch-depth: 0
25
24
 
26
- - uses: actions/setup-python@v7
25
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
27
26
  with:
28
27
  python-version: "3.12"
29
28
  cache: pip
29
+ cache-dependency-path: requirements-dev.lock
30
30
 
31
31
  - name: Install + run tests (gate the release)
32
32
  run: |
33
- python -m pip install --upgrade pip
34
33
  # System libs: dbus-python needs the *-dev headers at build
35
34
  # time; PySide6.QtWidgets needs the EGL/xkbcommon/GL/fontconfig
36
35
  # runtime libs to import (even under QT_QPA_PLATFORM=offscreen).
@@ -38,10 +37,10 @@ jobs:
38
37
  sudo apt-get install -y \
39
38
  libdbus-1-dev libglib2.0-dev pkg-config \
40
39
  libegl1 libxkbcommon0 libfontconfig1 libgl1
41
- # Install the package itself so the test imports get every
42
- # runtime dep (PySide6, pypresence, dbus-python) — without
43
- # this the new test_daemon_*.py files fail at collection.
44
- pip install -e ".[dev]"
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 .
45
44
  pytest -q
46
45
 
47
46
  - name: Verify tag matches package version
@@ -64,7 +63,7 @@ jobs:
64
63
  fi
65
64
 
66
65
  - name: Verify Flatpak manifest pins this tag
67
- # The Flatpak build source is a pinned (tag, commit) — it isn't
66
+ # The Flatpak build source is a pinned tag — it isn't
68
67
  # rebuilt/published by this workflow (no live Flathub channel),
69
68
  # so nothing else catches it going stale. It silently sat on
70
69
  # v0.1.4 from then until v0.4.2. This gate makes a stale pin a
@@ -76,14 +75,9 @@ jobs:
76
75
  echo "::error::$MANIFEST is not pinned to tag v${TAG}"
77
76
  exit 1
78
77
  fi
79
- if ! grep -qE "^ commit: [0-9a-f]{40}$" "$MANIFEST"; then
80
- echo "::error::$MANIFEST has no valid 40-character commit pin"
81
- exit 1
82
- fi
83
78
 
84
79
  - name: Build wheel + sdist
85
80
  run: |
86
- pip install build
87
81
  python -m build
88
82
  # Keep a clean copy of just the Python distributions for PyPI;
89
83
  # the AppImage step writes additional files into dist/ later.
@@ -93,21 +87,19 @@ jobs:
93
87
 
94
88
  # Single-file AppImage so end users can grab one binary from the
95
89
  # Releases page and run it without installing anything else.
96
- # Marked continue-on-error so a recipe glitch never blocks shipping
97
- # the wheel + sdist; we re-attempt by deleting + re-pushing the tag.
90
+ # The recipe installs Refrain with the runner's /usr/bin/python3,
91
+ # which must match the Python it bundles from Ubuntu 24.04.
98
92
  - name: Install AppImage build deps
99
- continue-on-error: true
100
93
  run: |
101
94
  sudo apt-get update
102
95
  sudo apt-get install -y \
103
- libdbus-1-dev libglib2.0-dev pkg-config \
104
- libfuse2 desktop-file-utils \
96
+ python3-pip libfuse2t64 desktop-file-utils \
105
97
  zsync appstream patchelf squashfs-tools file
106
- pip install appimage-builder
98
+ # appimage-builder can't parse Ubuntu package versions with newer packaging.
99
+ pip install appimage-builder "packaging<22"
107
100
 
108
101
  - name: Build AppImage
109
102
  id: appimage
110
- continue-on-error: true
111
103
  run: |
112
104
  TAG="${GITHUB_REF#refs/tags/v}"
113
105
  # The recipe pins a literal `version:` value, which determines the
@@ -122,6 +114,44 @@ jobs:
122
114
  mv *.AppImage.zsync ../../dist/ 2>/dev/null || true
123
115
  ls -la ../../dist/
124
116
 
117
+ # Every AppImage up to 0.5.2 was published without ever starting.
118
+ - name: Start the AppImage once
119
+ run: |
120
+ TAG="${GITHUB_REF#refs/tags/v}"
121
+ out="$(./dist/Refrain-${TAG}-x86_64.AppImage --appimage-extract-and-run --version)"
122
+ echo "$out"
123
+ [ "$out" = "refrain ${TAG}" ]
124
+
125
+ - name: Check licence notices and Qt modules in the AppImage
126
+ run: |
127
+ TAG="${GITHUB_REF#refs/tags/v}"
128
+ tmp="$(mktemp -d)"
129
+ (cd "$tmp" && "$GITHUB_WORKSPACE/dist/Refrain-${TAG}-x86_64.AppImage" --appimage-extract >/dev/null)
130
+ doc="$tmp/squashfs-root/usr/share/doc/refrain"
131
+ grep -q "^ PySide6_Essentials " "$doc/THIRD-PARTY-NOTICES"
132
+ grep -q "^ python3.12 " "$doc/THIRD-PARTY-NOTICES"
133
+ test -s "$doc/LICENSE" && test -s "$doc/LICENSES/LGPL-3" && test -s "$doc/LICENSES/GPL-3"
134
+ gpl_only="$(find "$tmp/squashfs-root" -iregex '.*\(Lottie\|WaylandCompositor\|VirtualKeyboard\|Charts\|DataVisualization\|Quick3D\|pyside6_addons\).*')"
135
+ if [ -n "$gpl_only" ]; then
136
+ echo "::error::GPL-only Qt parts in the AppImage:"; echo "$gpl_only"; exit 1
137
+ fi
138
+ rm -rf "$tmp"
139
+
140
+ # The in-app updater only installs an AppImage listed here, and only once
141
+ # the owner has signed this file (SHA256SUMS.sig, see packaging/README.md).
142
+ - name: Checksums for the in-app updater
143
+ run: |
144
+ cd dist
145
+ sha256sum *.AppImage > SHA256SUMS
146
+ cat SHA256SUMS
147
+
148
+ - name: Publish the AppImage's third-party notices next to it
149
+ run: |
150
+ cd dist
151
+ ./Refrain-*-x86_64.AppImage --appimage-extract usr/share/doc/refrain/THIRD-PARTY-NOTICES > /dev/null
152
+ mv squashfs-root/usr/share/doc/refrain/THIRD-PARTY-NOTICES .
153
+ rm -rf squashfs-root
154
+
125
155
  # Extract the matching `## [X.Y.Z]` section from CHANGELOG.md
126
156
  # and feed it into the GitHub release body. Without this we'd
127
157
  # fall back to GitHub's auto-generated "Full Changelog: …"
@@ -145,16 +175,79 @@ jobs:
145
175
  echo "::error::CHANGELOG section for v${TAG} extracted to nothing"
146
176
  exit 1
147
177
  fi
178
+ cat >> release-notes.md <<'NOTES'
179
+
180
+ ---
181
+
182
+ The AppImage bundles third-party software under its own licences; the
183
+ list, with versions and where to get the source, is in `THIRD-PARTY-NOTICES`
184
+ below and inside the AppImage. Versions that were taken down, and why, are
185
+ listed in [docs/releases.md](https://github.com/Rockykln/refrain/blob/main/docs/releases.md).
186
+ NOTES
187
+
188
+ - name: Hand the release files to the publishing jobs
189
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
190
+ with:
191
+ name: release
192
+ path: |
193
+ dist/
194
+ release-notes.md
195
+ if-no-files-found: error
196
+
197
+ - name: Hand the PyPI files to the publishing job
198
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
199
+ with:
200
+ name: pypi-dist
201
+ path: pypi-dist/
202
+ if-no-files-found: error
203
+
204
+ attest:
205
+ needs: build
206
+ runs-on: ubuntu-24.04
207
+ permissions:
208
+ contents: read
209
+ id-token: write # Sigstore signing certificate for the attestation
210
+ attestations: write # store the attestation with the repository
211
+
212
+ steps:
213
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
214
+ with:
215
+ name: release
216
+
217
+ # Verify with: gh attestation verify <file> --repo Rockykln/refrain
218
+ - name: Attest build provenance of the release files
219
+ uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
220
+ with:
221
+ subject-path: |
222
+ dist/*.whl
223
+ dist/*.tar.gz
224
+ dist/*.AppImage
225
+ dist/*.AppImage.zsync
226
+ dist/SHA256SUMS
227
+ dist/THIRD-PARTY-NOTICES
148
228
 
149
- - name: Create GitHub release
150
- uses: softprops/action-gh-release@v3
229
+ github-release:
230
+ needs: [build, attest]
231
+ runs-on: ubuntu-24.04
232
+ permissions:
233
+ contents: write # required to create the release
234
+
235
+ steps:
236
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
237
+ with:
238
+ name: release
239
+
240
+ # A draft: the owner signs SHA256SUMS on their own machine and only then
241
+ # publishes it (packaging/release_key.py release <tag>).
242
+ - name: Create GitHub release (draft)
243
+ uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
151
244
  with:
152
245
  # Use our CHANGELOG section (extracted above), not GitHub's
153
246
  # auto-generated notes — the in-app update dialog renders
154
247
  # this body verbatim so it has to actually be the changelog.
155
248
  body_path: release-notes.md
156
249
  generate_release_notes: false
157
- draft: false
250
+ draft: true
158
251
  prerelease: ${{ contains(github.ref, '-') }} # e.g. v0.2.0-rc1
159
252
  name: "Refrain ${{ github.ref_name }}"
160
253
  files: |
@@ -162,6 +255,24 @@ jobs:
162
255
  dist/*.tar.gz
163
256
  dist/*.AppImage
164
257
  dist/*.AppImage.zsync
258
+ dist/SHA256SUMS
259
+ dist/THIRD-PARTY-NOTICES
260
+
261
+ pypi:
262
+ # After the GitHub release, as before the jobs were split.
263
+ needs: github-release
264
+ runs-on: ubuntu-24.04
265
+ environment:
266
+ name: pypi
267
+ url: https://pypi.org/project/refrain/
268
+ permissions:
269
+ id-token: write # required by PyPA Trusted Publishers (OIDC) for PyPI upload
270
+
271
+ steps:
272
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
273
+ with:
274
+ name: pypi-dist
275
+ path: pypi-dist/
165
276
 
166
277
  # PyPI upload via Trusted Publishers (OIDC). No API token needed; the
167
278
  # one-time setup is on pypi.org → Manage → Publishing → Add a new
@@ -170,12 +281,9 @@ jobs:
170
281
  # Skips silently if the version already exists on PyPI so re-running
171
282
  # a release for AppImage-only fixes doesn't fail the workflow.
172
283
  - name: Publish to PyPI
173
- uses: pypa/gh-action-pypi-publish@release/v1
284
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
174
285
  with:
175
286
  packages-dir: pypi-dist/
176
287
  skip-existing: true
177
288
  verbose: true
178
289
  attestations: true
179
- # Don't let an upload hiccup wipe out the GitHub release that
180
- # already succeeded above — reruns can fix PyPI later.
181
- continue-on-error: 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
- python -m pip install --upgrade pip
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,11 +69,16 @@ 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
 
@@ -0,0 +1,106 @@
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.
37
+ run: |
38
+ sudo apt-get update
39
+ sudo apt-get install -y \
40
+ libdbus-1-dev libglib2.0-dev pkg-config \
41
+ libegl1 libxkbcommon0 libfontconfig1 libgl1
42
+ pip install --require-hashes -r requirements-dev.lock
43
+ pip install --no-deps -e .
44
+
45
+ - name: Compile-check every module
46
+ run: python -m compileall -q src/
47
+
48
+ - name: Run tests with coverage
49
+ run: PYTHONPATH=src pytest --cov --cov-report=term-missing --cov-fail-under=99
50
+
51
+ - name: Coverage in the job summary
52
+ if: ${{ !cancelled() && hashFiles('.coverage') != '' }}
53
+ run: |
54
+ {
55
+ echo "### Coverage (Python ${{ matrix.python-version }}): $(coverage report --format=total)%"
56
+ echo
57
+ coverage report --format=markdown
58
+ } >> "$GITHUB_STEP_SUMMARY"
59
+
60
+ lowest:
61
+ # The lower bounds in pyproject.toml, on the oldest supported Python.
62
+ runs-on: ubuntu-latest
63
+ steps:
64
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
65
+
66
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
67
+ with:
68
+ python-version: "3.11"
69
+ cache: pip
70
+ cache-dependency-path: |
71
+ requirements-dev.lock
72
+ requirements-lowest.txt
73
+
74
+ - name: Install the oldest supported dependencies
75
+ run: |
76
+ sudo apt-get update
77
+ sudo apt-get install -y \
78
+ libdbus-1-dev libglib2.0-dev pkg-config \
79
+ libegl1 libxkbcommon0 libfontconfig1 libgl1
80
+ pip install --require-hashes -c requirements-dev.lock pytest
81
+ pip install -r requirements-lowest.txt
82
+ pip install --no-deps -e .
83
+ pip check
84
+
85
+ - name: Run tests
86
+ run: PYTHONPATH=src pytest
87
+
88
+ lint:
89
+ runs-on: ubuntu-latest
90
+ steps:
91
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
92
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
93
+ with:
94
+ python-version: "3.12"
95
+ cache: pip
96
+ cache-dependency-path: requirements-dev.lock
97
+
98
+ - name: Install ruff
99
+ # The version comes from requirements-dev.lock, like everywhere else.
100
+ run: pip install --require-hashes -c requirements-dev.lock ruff
101
+
102
+ - name: Lint with ruff
103
+ run: ruff check . --output-format=github
104
+
105
+ - name: Format check with ruff
106
+ run: ruff format --check .