refrain 0.2.4__tar.gz → 0.2.5__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 (107) hide show
  1. {refrain-0.2.4 → refrain-0.2.5}/CHANGELOG.md +35 -1
  2. {refrain-0.2.4 → refrain-0.2.5}/PKG-INFO +1 -1
  3. {refrain-0.2.4 → refrain-0.2.5}/packaging/appimage/AppImageBuilder.yml +1 -1
  4. {refrain-0.2.4 → refrain-0.2.5}/packaging/aur/refrain/.SRCINFO +3 -3
  5. {refrain-0.2.4 → refrain-0.2.5}/packaging/aur/refrain/PKGBUILD +1 -1
  6. {refrain-0.2.4 → refrain-0.2.5}/packaging/flatpak/io.github.Rockykln.Refrain.metainfo.xml +13 -0
  7. {refrain-0.2.4 → refrain-0.2.5}/pyproject.toml +1 -1
  8. refrain-0.2.5/src/refrain/__init__.py +1 -0
  9. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/ui/update_dialog.py +2 -0
  10. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/updater.py +82 -4
  11. {refrain-0.2.4 → refrain-0.2.5}/tests/test_updater.py +48 -2
  12. refrain-0.2.4/src/refrain/__init__.py +0 -1
  13. {refrain-0.2.4 → refrain-0.2.5}/.editorconfig +0 -0
  14. {refrain-0.2.4 → refrain-0.2.5}/.gitattributes +0 -0
  15. {refrain-0.2.4 → refrain-0.2.5}/.github/FUNDING.yml +0 -0
  16. {refrain-0.2.4 → refrain-0.2.5}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  17. {refrain-0.2.4 → refrain-0.2.5}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  18. {refrain-0.2.4 → refrain-0.2.5}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  19. {refrain-0.2.4 → refrain-0.2.5}/.github/codeql/codeql-config.yml +0 -0
  20. {refrain-0.2.4 → refrain-0.2.5}/.github/dependabot.yml +0 -0
  21. {refrain-0.2.4 → refrain-0.2.5}/.github/pull_request_template.md +0 -0
  22. {refrain-0.2.4 → refrain-0.2.5}/.github/workflows/codeql.yml +0 -0
  23. {refrain-0.2.4 → refrain-0.2.5}/.github/workflows/release.yml +0 -0
  24. {refrain-0.2.4 → refrain-0.2.5}/.github/workflows/security.yml +0 -0
  25. {refrain-0.2.4 → refrain-0.2.5}/.github/workflows/tests.yml +0 -0
  26. {refrain-0.2.4 → refrain-0.2.5}/.gitignore +0 -0
  27. {refrain-0.2.4 → refrain-0.2.5}/CODE_OF_CONDUCT.md +0 -0
  28. {refrain-0.2.4 → refrain-0.2.5}/CONTRIBUTING.md +0 -0
  29. {refrain-0.2.4 → refrain-0.2.5}/LICENSE +0 -0
  30. {refrain-0.2.4 → refrain-0.2.5}/Makefile +0 -0
  31. {refrain-0.2.4 → refrain-0.2.5}/README.md +0 -0
  32. {refrain-0.2.4 → refrain-0.2.5}/ROADMAP.md +0 -0
  33. {refrain-0.2.4 → refrain-0.2.5}/SECURITY.md +0 -0
  34. {refrain-0.2.4 → refrain-0.2.5}/docs/architecture.md +0 -0
  35. {refrain-0.2.4 → refrain-0.2.5}/docs/bluetooth.md +0 -0
  36. {refrain-0.2.4 → refrain-0.2.5}/docs/faq.md +0 -0
  37. {refrain-0.2.4 → refrain-0.2.5}/docs/screenshots/README.md +0 -0
  38. {refrain-0.2.4 → refrain-0.2.5}/docs/screenshots/discord-rpc.png +0 -0
  39. {refrain-0.2.4 → refrain-0.2.5}/docs/screenshots/live-log.png +0 -0
  40. {refrain-0.2.4 → refrain-0.2.5}/docs/screenshots/notification.png +0 -0
  41. {refrain-0.2.4 → refrain-0.2.5}/docs/screenshots/settings-advanced.png +0 -0
  42. {refrain-0.2.4 → refrain-0.2.5}/docs/screenshots/settings-general.png +0 -0
  43. {refrain-0.2.4 → refrain-0.2.5}/docs/screenshots/settings-sources.png +0 -0
  44. {refrain-0.2.4 → refrain-0.2.5}/docs/screenshots/settings-updates.png +0 -0
  45. {refrain-0.2.4 → refrain-0.2.5}/docs/screenshots/tray-menu.png +0 -0
  46. {refrain-0.2.4 → refrain-0.2.5}/docs/screenshots/update-dialog.png +0 -0
  47. {refrain-0.2.4 → refrain-0.2.5}/docs/screenshots/welcome.png +0 -0
  48. {refrain-0.2.4 → refrain-0.2.5}/docs/test-matrix.md +0 -0
  49. {refrain-0.2.4 → refrain-0.2.5}/packaging/README.md +0 -0
  50. {refrain-0.2.4 → refrain-0.2.5}/packaging/aur/refrain-git/PKGBUILD +0 -0
  51. {refrain-0.2.4 → refrain-0.2.5}/packaging/flatpak/io.github.Rockykln.Refrain.desktop +0 -0
  52. {refrain-0.2.4 → refrain-0.2.5}/packaging/flatpak/io.github.Rockykln.Refrain.yaml +0 -0
  53. {refrain-0.2.4 → refrain-0.2.5}/packaging/flatpak/python-deps.json +0 -0
  54. {refrain-0.2.4 → refrain-0.2.5}/requirements-dev.txt +0 -0
  55. {refrain-0.2.4 → refrain-0.2.5}/requirements.txt +0 -0
  56. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/__main__.py +0 -0
  57. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/app.py +0 -0
  58. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/assets/icons/github-mark.svg +0 -0
  59. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/assets/icons/menu-update.svg +0 -0
  60. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/assets/icons/refrain.png +0 -0
  61. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/assets/icons/refrain.svg +0 -0
  62. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/assets/icons/tray-paused-dark.svg +0 -0
  63. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/assets/icons/tray-paused.svg +0 -0
  64. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/assets/icons/tray-playing-dark.svg +0 -0
  65. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/assets/icons/tray-playing.svg +0 -0
  66. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/assets/icons/tray-stopped-dark.svg +0 -0
  67. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/assets/icons/tray-stopped.svg +0 -0
  68. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/assets/refrain.desktop +0 -0
  69. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/autostart.py +0 -0
  70. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/config.py +0 -0
  71. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/cover_art.py +0 -0
  72. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/cover_fetcher.py +0 -0
  73. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/daemon.py +0 -0
  74. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/discord_rpc.py +0 -0
  75. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/i18n/refrain_de.qm +0 -0
  76. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/i18n/refrain_de.ts +0 -0
  77. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/logging_setup.py +0 -0
  78. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/paths.py +0 -0
  79. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/single_instance.py +0 -0
  80. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/sources/__init__.py +0 -0
  81. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/sources/base.py +0 -0
  82. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/sources/bluetooth.py +0 -0
  83. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/sources/mpris.py +0 -0
  84. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/sources/mpris_server.py +0 -0
  85. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/timing.py +0 -0
  86. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/ui/__init__.py +0 -0
  87. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/ui/log_window.py +0 -0
  88. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/ui/settings_window.py +0 -0
  89. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/ui/tray.py +0 -0
  90. {refrain-0.2.4 → refrain-0.2.5}/src/refrain/ui/welcome_dialog.py +0 -0
  91. {refrain-0.2.4 → refrain-0.2.5}/tests/__init__.py +0 -0
  92. {refrain-0.2.4 → refrain-0.2.5}/tests/conftest.py +0 -0
  93. {refrain-0.2.4 → refrain-0.2.5}/tests/test_autostart.py +0 -0
  94. {refrain-0.2.4 → refrain-0.2.5}/tests/test_config.py +0 -0
  95. {refrain-0.2.4 → refrain-0.2.5}/tests/test_cover_art.py +0 -0
  96. {refrain-0.2.4 → refrain-0.2.5}/tests/test_cover_fetcher.py +0 -0
  97. {refrain-0.2.4 → refrain-0.2.5}/tests/test_daemon_album_format.py +0 -0
  98. {refrain-0.2.4 → refrain-0.2.5}/tests/test_daemon_idle.py +0 -0
  99. {refrain-0.2.4 → refrain-0.2.5}/tests/test_discord_listening.py +0 -0
  100. {refrain-0.2.4 → refrain-0.2.5}/tests/test_discord_rpc.py +0 -0
  101. {refrain-0.2.4 → refrain-0.2.5}/tests/test_imports.py +0 -0
  102. {refrain-0.2.4 → refrain-0.2.5}/tests/test_mpris_dispatch.py +0 -0
  103. {refrain-0.2.4 → refrain-0.2.5}/tests/test_paths.py +0 -0
  104. {refrain-0.2.4 → refrain-0.2.5}/tests/test_sources_base.py +0 -0
  105. {refrain-0.2.4 → refrain-0.2.5}/tests/test_timing.py +0 -0
  106. {refrain-0.2.4 → refrain-0.2.5}/tests/test_tray_init.py +0 -0
  107. {refrain-0.2.4 → refrain-0.2.5}/tests/test_update_dialog_body.py +0 -0
@@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.5] - 2026-05-07
11
+
12
+ A same-day patch on top of v0.2.4 driven by user feedback that the
13
+ AUR / Flatpak update path was effectively unusable from the GUI — the
14
+ dialog only displayed the package-manager command in a popup and
15
+ expected the user to copy + paste it into a terminal themselves.
16
+
17
+ ### Changed
18
+
19
+ - **AUR + Flatpak updates now spawn a terminal automatically** with
20
+ the package-manager command pre-typed. The user confirms any sudo
21
+ prompt in the terminal, the package manager retains state, and
22
+ Refrain just needs a restart afterwards. The
23
+ "Refrain never modifies system files directly" guarantee is
24
+ preserved — we only spawn the user's own AUR helper / `flatpak`,
25
+ and only inside a terminal the user can read / cancel.
26
+ - `_run_in_terminal()` walks a probe list (konsole, gnome-terminal,
27
+ xfce4-terminal, kitty, alacritty, foot, xterm, wezterm) and
28
+ spawns the first hit. Falls back to the previous "show command
29
+ in a popup" behaviour when no terminal is on PATH.
30
+ - `_aur_helper()` picks `yay` → `paru` → `trizen` → `pikaur` in
31
+ that order; falls back to `sudo pacman -Syu refrain` when no
32
+ helper is installed.
33
+ - Update dialog button label flips from "Show update command" to
34
+ "Run update in terminal" when the install is AUR or Flatpak.
35
+
36
+ ### Tests
37
+
38
+ - 4 new tests in `tests/test_updater.py` covering the fallback
39
+ message-box path, terminal-spawn for both AUR + Flatpak, and the
40
+ `_aur_helper` pacman fallback when no helper is installed.
41
+
10
42
  ## [0.2.4] - 2026-05-07
11
43
 
12
44
  A polish + reliability release built on the v0.2.3 distro-portability
@@ -812,7 +844,9 @@ with a proper, installable Linux app.
812
844
  pip-audit, trufflehog, release), Dependabot, issue + PR templates,
813
845
  `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`.
814
846
 
815
- [Unreleased]: https://github.com/Rockykln/refrain/compare/v0.2.3...HEAD
847
+ [Unreleased]: https://github.com/Rockykln/refrain/compare/v0.2.5...HEAD
848
+ [0.2.5]: https://github.com/Rockykln/refrain/compare/v0.2.4...v0.2.5
849
+ [0.2.4]: https://github.com/Rockykln/refrain/compare/v0.2.3...v0.2.4
816
850
  [0.2.3]: https://github.com/Rockykln/refrain/compare/v0.2.2...v0.2.3
817
851
  [0.2.2]: https://github.com/Rockykln/refrain/compare/v0.2.1...v0.2.2
818
852
  [0.2.1]: https://github.com/Rockykln/refrain/compare/v0.2.0...v0.2.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: refrain
3
- Version: 0.2.4
3
+ Version: 0.2.5
4
4
  Summary: Discord Rich Presence for Apple Music on Linux
5
5
  Project-URL: Homepage, https://github.com/Rockykln/refrain
6
6
  Project-URL: Repository, https://github.com/Rockykln/refrain
@@ -30,7 +30,7 @@ AppDir:
30
30
  id: io.github.Rockykln.Refrain
31
31
  name: Refrain
32
32
  icon: refrain
33
- version: 0.2.4
33
+ version: 0.2.5
34
34
  exec: usr/bin/python3
35
35
  exec_args: -m refrain $@
36
36
 
@@ -1,6 +1,6 @@
1
1
  pkgbase = refrain
2
2
  pkgdesc = Discord Rich Presence for Apple Music on Linux
3
- pkgver = 0.2.3
3
+ pkgver = 0.2.4
4
4
  pkgrel = 1
5
5
  url = https://github.com/Rockykln/refrain
6
6
  arch = any
@@ -14,7 +14,7 @@ pkgbase = refrain
14
14
  depends = python-dbus
15
15
  depends = pyside6
16
16
  optdepends = python-gobject: enables MPRIS-server publication so Plasma media controls reach Refrain
17
- source = refrain-0.2.3.tar.gz::https://github.com/Rockykln/refrain/archive/v0.2.3.tar.gz
18
- sha256sums = 29f2371393d855f508730238a096cbdc85219616a9fab240745c1072a005b23d
17
+ source = refrain-0.2.4.tar.gz::https://github.com/Rockykln/refrain/archive/v0.2.4.tar.gz
18
+ sha256sums = cb4e712a2809bd237c4ccaebd2cc81afa28286b4d260072f74bf94ce530a0ea3
19
19
 
20
20
  pkgname = refrain
@@ -1,7 +1,7 @@
1
1
  # Maintainer: Rockykln <contact@rockykln.com>
2
2
 
3
3
  pkgname=refrain
4
- pkgver=0.2.4
4
+ pkgver=0.2.5
5
5
  pkgrel=1
6
6
  pkgdesc="Discord Rich Presence for Apple Music on Linux"
7
7
  arch=('any')
@@ -51,6 +51,19 @@
51
51
  <content_rating type="oars-1.1" />
52
52
 
53
53
  <releases>
54
+ <release version="0.2.5" date="2026-05-07">
55
+ <description>
56
+ <p>
57
+ Same-day patch over v0.2.4. AUR and Flatpak updates now
58
+ spawn a terminal emulator running the package-manager
59
+ command (yay -Syu / flatpak update) instead of just
60
+ displaying the command in a popup for the user to retype.
61
+ The user confirms any sudo prompt in the terminal, the
62
+ package manager retains state. Falls back to the previous
63
+ message-box hint when no terminal is on PATH.
64
+ </p>
65
+ </description>
66
+ </release>
54
67
  <release version="0.2.4" date="2026-05-07">
55
68
  <description>
56
69
  <p>
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "refrain"
7
- version = "0.2.4"
7
+ version = "0.2.5"
8
8
  description = "Discord Rich Presence for Apple Music on Linux"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -0,0 +1 @@
1
+ __version__ = "0.2.5"
@@ -127,6 +127,8 @@ class UpdateDialog(QDialog):
127
127
  return self.tr("Download && replace")
128
128
  if self._install_type == "pip":
129
129
  return self.tr("Run pip upgrade")
130
+ if self._install_type in ("aur", "flatpak"):
131
+ return self.tr("Run update in terminal")
130
132
  return self.tr("Show update command")
131
133
 
132
134
  def _open_release_page(self) -> None:
@@ -254,12 +254,67 @@ def cleanup_orphan_downloads() -> None:
254
254
  log.debug("Could not remove orphan update download %s: %s", tmp, e)
255
255
 
256
256
 
257
+ # Terminal emulators we know how to launch a command inside, in
258
+ # preference order. Each entry is ``(binary, argv_template)`` where
259
+ # ``argv_template[i] == "{cmd}"`` gets replaced with the shell command
260
+ # string. The shell wrapper pauses at the end so the user can read
261
+ # output before the window closes.
262
+ _TERMINAL_PROBES: tuple[tuple[str, list[str]], ...] = (
263
+ ("konsole", ["konsole", "-e", "bash", "-c", "{cmd}"]),
264
+ ("gnome-terminal", ["gnome-terminal", "--", "bash", "-c", "{cmd}"]),
265
+ ("xfce4-terminal", ["xfce4-terminal", "-x", "bash", "-c", "{cmd}"]),
266
+ ("kitty", ["kitty", "bash", "-c", "{cmd}"]),
267
+ ("alacritty", ["alacritty", "-e", "bash", "-c", "{cmd}"]),
268
+ ("foot", ["foot", "bash", "-c", "{cmd}"]),
269
+ ("xterm", ["xterm", "-e", "bash", "-c", "{cmd}"]),
270
+ ("wezterm", ["wezterm", "start", "--", "bash", "-c", "{cmd}"]),
271
+ )
272
+
273
+
274
+ def _run_in_terminal(cmd: str) -> bool:
275
+ """Pop up a terminal emulator running ``cmd`` (a shell string).
276
+
277
+ Wraps the command so the terminal stays open after it exits — the
278
+ user can read the package-manager output and any error before the
279
+ window vanishes. Returns True iff we successfully spawned a
280
+ terminal; False (silently) if no known emulator is on PATH.
281
+ """
282
+ wrapped = f"{cmd}; echo; read -rp 'Press Enter to close…'"
283
+ for binary, template in _TERMINAL_PROBES:
284
+ if not shutil.which(binary):
285
+ continue
286
+ argv = [arg.format(cmd=wrapped) for arg in template]
287
+ try:
288
+ subprocess.Popen(argv, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
289
+ log.info("Spawned %s for update command", binary)
290
+ return True
291
+ except Exception as e:
292
+ log.debug("Failed to spawn %s: %s", binary, e)
293
+ log.info("No known terminal emulator found — falling back to message-box hint")
294
+ return False
295
+
296
+
297
+ def _aur_helper() -> str:
298
+ """Pick the user's AUR helper. Falls back to bare ``pacman -Syu``
299
+ when no helper is on PATH (won't update AUR packages, but at least
300
+ won't be wrong — the user gets to see the issue + run their own
301
+ helper manually).
302
+ """
303
+ for helper in ("yay", "paru", "trizen", "pikaur"):
304
+ if shutil.which(helper):
305
+ return f"{helper} -Syu refrain"
306
+ return "sudo pacman -Syu refrain"
307
+
308
+
257
309
  def apply_update(
258
310
  release: ReleaseInfo,
259
311
  install_type: str | None = None,
260
312
  cancelled: Callable[[], bool] | None = None,
261
313
  ) -> UpdateResult:
262
- """Type-aware update. Never modifies system files."""
314
+ """Type-aware update. Never modifies system files directly — for
315
+ AUR / Flatpak / system installs we shell out to the user's package
316
+ manager via a terminal emulator so the user confirms any sudo
317
+ prompt themselves and the package manager retains state."""
263
318
  install_type = install_type or detect_install_type()
264
319
  log.info("apply_update: target=%s install_type=%s", release.version, install_type)
265
320
 
@@ -274,15 +329,38 @@ def apply_update(
274
329
  "`git pull` and reinstall with `pip install -e .`.",
275
330
  )
276
331
  if install_type == "flatpak":
332
+ cmd = "flatpak update -y io.github.Rockykln.Refrain"
333
+ if _run_in_terminal(cmd):
334
+ return UpdateResult(
335
+ success=True,
336
+ message=(
337
+ "Launched the update in a new terminal:\n\n"
338
+ f" {cmd}\n\n"
339
+ "Confirm any prompts there. Restart Refrain afterwards "
340
+ "to load the new version."
341
+ ),
342
+ needs_restart=True,
343
+ )
277
344
  return UpdateResult(
278
345
  success=False,
279
- message="Flatpak install detected. Update via:\n\n"
280
- " flatpak update io.github.Rockykln.Refrain",
346
+ message=f"Flatpak install detected. Update via:\n\n {cmd}",
281
347
  )
282
348
  if install_type == "aur":
349
+ cmd = _aur_helper()
350
+ if _run_in_terminal(cmd):
351
+ return UpdateResult(
352
+ success=True,
353
+ message=(
354
+ "Launched the update in a new terminal:\n\n"
355
+ f" {cmd}\n\n"
356
+ "Confirm any sudo prompt there. Restart Refrain "
357
+ "afterwards to load the new version."
358
+ ),
359
+ needs_restart=True,
360
+ )
283
361
  return UpdateResult(
284
362
  success=False,
285
- message="AUR install detected. Update via your AUR helper:\n\n yay -Syu refrain",
363
+ message=f"AUR install detected. Update via your AUR helper:\n\n {cmd}",
286
364
  )
287
365
  return UpdateResult(
288
366
  success=False,
@@ -146,7 +146,7 @@ def test_check_latest_release_handles_empty_assets(monkeypatch, updater):
146
146
  assert info.appimage_size == 0
147
147
 
148
148
 
149
- def test_apply_update_dispatches_per_install_type(updater):
149
+ def test_apply_update_dispatches_per_install_type(updater, monkeypatch):
150
150
  info = updater.ReleaseInfo(
151
151
  tag="v0.2.0",
152
152
  version="0.2.0",
@@ -156,19 +156,65 @@ def test_apply_update_dispatches_per_install_type(updater):
156
156
  )
157
157
  # Without an APPIMAGE env var, "appimage" path errors out cleanly
158
158
  os.environ.pop("APPIMAGE", None)
159
+ # Force the fallback path (no terminal available) so the
160
+ # assertions test the offline-friendly message-box behaviour.
161
+ # The terminal-spawn path is exercised separately below.
162
+ monkeypatch.setattr(updater, "_run_in_terminal", lambda _cmd: False)
163
+
159
164
  r = updater.apply_update(info, install_type="flatpak")
160
165
  assert r.success is False
161
166
  assert "flatpak update" in r.message.lower()
162
167
 
163
168
  r = updater.apply_update(info, install_type="aur")
164
169
  assert r.success is False
165
- assert "yay" in r.message.lower()
170
+ # The hint message names whatever helper detect produced
171
+ # (yay/paru/trizen/pikaur or the bare-pacman fallback) — assert
172
+ # we mention the canonical keyword "syu" which all of them use.
173
+ assert "syu" in r.message.lower()
166
174
 
167
175
  r = updater.apply_update(info, install_type="dev")
168
176
  assert r.success is False
169
177
  assert "git pull" in r.message.lower()
170
178
 
171
179
 
180
+ def test_apply_update_aur_launches_terminal_when_available(updater, monkeypatch):
181
+ """AUR install + a usable terminal → spawn it, mark needs_restart."""
182
+ spawned: list[str] = []
183
+
184
+ def fake_terminal(cmd: str) -> bool:
185
+ spawned.append(cmd)
186
+ return True
187
+
188
+ monkeypatch.setattr(updater, "_run_in_terminal", fake_terminal)
189
+
190
+ info = updater.ReleaseInfo(tag="v0.2.0", version="0.2.0", name="x", body="", html_url="")
191
+ r = updater.apply_update(info, install_type="aur")
192
+ assert r.success is True
193
+ assert r.needs_restart is True
194
+ assert spawned and "syu refrain" in spawned[0].lower()
195
+
196
+
197
+ def test_apply_update_flatpak_launches_terminal_when_available(updater, monkeypatch):
198
+ spawned: list[str] = []
199
+ monkeypatch.setattr(updater, "_run_in_terminal", lambda cmd: spawned.append(cmd) or True)
200
+
201
+ info = updater.ReleaseInfo(tag="v0.2.0", version="0.2.0", name="x", body="", html_url="")
202
+ r = updater.apply_update(info, install_type="flatpak")
203
+ assert r.success is True
204
+ assert r.needs_restart is True
205
+ assert spawned and "flatpak update" in spawned[0].lower()
206
+
207
+
208
+ def test_aur_helper_falls_back_to_pacman(updater, monkeypatch):
209
+ """When no AUR helper is installed, _aur_helper falls back to a
210
+ bare pacman command. Update dialog will still surface this so
211
+ the user can swap in their preferred helper if they prefer."""
212
+ monkeypatch.setattr(updater.shutil, "which", lambda _name: None)
213
+ cmd = updater._aur_helper()
214
+ assert "pacman" in cmd
215
+ assert "refrain" in cmd
216
+
217
+
172
218
  # ---------------------------------------------------------------------------
173
219
  # cleanup_orphan_downloads — self-heal of *.AppImage.new files left behind
174
220
  # by a SIGKILL / power-loss mid-download.
@@ -1 +0,0 @@
1
- __version__ = "0.2.4"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes