fix(launcher): report leftover upstream launchers that no longer start #54

Merged
xavierk merged 1 commits from fix/stale-upstream-launcher into main 2026-11-06 21:03:29 +00:00
Owner

Description

A previous install can leave ~/.local/share/applications/vocalinux.desktop (under $XDG_DATA_HOME), ~/.local/bin/vocalinux and ~/.local/bin/vocalinux-gui behind. When their target is gone they start nothing, yet they sit beside Voculinux in launchers, and picking the stale one fails silently.

This adds a check to the one-time legacy import (src/vocalinux/legacy_import.py), which runs after the tray is up.

Detection. A launcher is stale only on positive evidence:

  • Desktop entry: the Exec= in the [Desktop Entry] group names a target that does not exist. The target is the first word after any env and VAR=... prefix. A bare name is resolved on PATH. An empty or unparseable Exec= is not flagged.
  • Wrapper files: a dangling symlink, or a #! script whose shebang interpreter or an absolute .../bin/python* or .../bin/vocalinux(-gui) path does not exist. A compiled binary is never read as a wrapper.
  • A working install is never flagged.

Behaviour:

  • Runs as a GLib idle callback after the tray is built, next to the model load. It does stat calls and reads small files only: no subprocess, no network.
  • Logs a warning per stale file and shows one notification, "Old Vocalinux launcher found", naming the files to remove. The notification goes through the existing ui/notifications.py helper.
  • Records $XDG_DATA_HOME/voculinux/stale-upstream-launcher-notified, so the notice shows once. A clean run writes nothing.
  • Never deletes or modifies a file.

README gets a "Migrating from a previous Vocalinux install" section with the removal commands. docs/INSTALL.md has no migration section, so it is unchanged.

Fixes #52

Related issue

Fixes #52. Spec context: #16 (legacy import from #18).

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Refactor
  • Tests
  • Packaging / CI

Checklist

  • Code follows project style (Black, isort, flake8). The critical flake8 gate, black --check, isort --check and mypy src/ are clean.
  • Documentation updated when user-facing behavior or install steps change
  • Tests added or updated for the change
  • Local tests pass (pytest). Not ticked: I ran the related files one at a time (legacy import, rename sweep, app identity, custom dictionary, dbus activation, main, notifications, host process, and the packaging tests that read README). The full suite was not run because of the pre-existing order-dependent gi-mock failures on main.
  • Conventional commit message style

Screenshots (if applicable)

N/A. The notice is a desktop popup and was not captured.

Notes for reviewers

  • Seams under test, all offline with a fake HOME, XDG roots and PATH under tmp_path: missing target; env-prefixed Exec into a wrapper whose venv is gone (the reported case); working install left alone; both wrappers named; dangling symlink; compiled binary not read as a script; bare name resolved on PATH; empty, env-only and unparseable Exec; Desktop Action Exec ignored; marker suppresses a repeat; empty XDG_DATA_HOME falls back to ~/.local/share; the idle callback returns False so GLib runs it once; a failing check is swallowed.
  • The marker is new. The legacy import writes no marker. It is exactly-once only through existence checks. A stale launcher can exist without any legacy config, so this check cannot reuse that existence test. It writes its own marker.
  • The rename sweep allowlist is untouched. Legacy names in src/ come from LEGACY_APP_DIR_NAME, and the notice text says "previous Vocalinux", which the sweep accepts.
  • The notification body is not markup-escaped. A home path containing & or < may render oddly on a server that parses markup. Known, not handled here.
## Description A previous install can leave `~/.local/share/applications/vocalinux.desktop` (under `$XDG_DATA_HOME`), `~/.local/bin/vocalinux` and `~/.local/bin/vocalinux-gui` behind. When their target is gone they start nothing, yet they sit beside Voculinux in launchers, and picking the stale one fails silently. This adds a check to the one-time legacy import (`src/vocalinux/legacy_import.py`), which runs after the tray is up. Detection. A launcher is stale only on positive evidence: - Desktop entry: the `Exec=` in the `[Desktop Entry]` group names a target that does not exist. The target is the first word after any `env` and `VAR=...` prefix. A bare name is resolved on `PATH`. An empty or unparseable `Exec=` is not flagged. - Wrapper files: a dangling symlink, or a `#!` script whose shebang interpreter or an absolute `.../bin/python*` or `.../bin/vocalinux(-gui)` path does not exist. A compiled binary is never read as a wrapper. - A working install is never flagged. Behaviour: - Runs as a GLib idle callback after the tray is built, next to the model load. It does stat calls and reads small files only: no subprocess, no network. - Logs a warning per stale file and shows one notification, "Old Vocalinux launcher found", naming the files to remove. The notification goes through the existing `ui/notifications.py` helper. - Records `$XDG_DATA_HOME/voculinux/stale-upstream-launcher-notified`, so the notice shows once. A clean run writes nothing. - Never deletes or modifies a file. README gets a "Migrating from a previous Vocalinux install" section with the removal commands. `docs/INSTALL.md` has no migration section, so it is unchanged. Fixes #52 ## Related issue Fixes #52. Spec context: #16 (legacy import from #18). ## Type of change - [x] Bug fix - [ ] New feature - [ ] Breaking change - [x] Documentation - [ ] Refactor - [x] Tests - [ ] Packaging / CI ## Checklist - [x] Code follows project style (Black, isort, flake8). The critical flake8 gate, `black --check`, `isort --check` and `mypy src/` are clean. - [x] Documentation updated when user-facing behavior or install steps change - [x] Tests added or updated for the change - [ ] Local tests pass (`pytest`). Not ticked: I ran the related files one at a time (legacy import, rename sweep, app identity, custom dictionary, dbus activation, main, notifications, host process, and the packaging tests that read README). The full suite was not run because of the pre-existing order-dependent gi-mock failures on main. - [x] Conventional commit message style ## Screenshots (if applicable) N/A. The notice is a desktop popup and was not captured. ## Notes for reviewers - Seams under test, all offline with a fake HOME, XDG roots and PATH under `tmp_path`: missing target; env-prefixed Exec into a wrapper whose venv is gone (the reported case); working install left alone; both wrappers named; dangling symlink; compiled binary not read as a script; bare name resolved on PATH; empty, env-only and unparseable Exec; Desktop Action Exec ignored; marker suppresses a repeat; empty `XDG_DATA_HOME` falls back to `~/.local/share`; the idle callback returns False so GLib runs it once; a failing check is swallowed. - The marker is new. The legacy import writes no marker. It is exactly-once only through existence checks. A stale launcher can exist without any legacy config, so this check cannot reuse that existence test. It writes its own marker. - The rename sweep allowlist is untouched. Legacy names in `src/` come from `LEGACY_APP_DIR_NAME`, and the notice text says "previous Vocalinux", which the sweep accepts. - The notification body is not markup-escaped. A home path containing `&` or `<` may render oddly on a server that parses markup. Known, not handled here.
xavierk added 1 commit 2026-11-06 21:01:39 +00:00
fix(launcher): report leftover upstream launchers that no longer start
CI / security (pull_request) Successful in 1m14s
CI / lint (pull_request) Successful in 23s
CI / ai-review (pull_request) Successful in 7s
1cf5423d39
A previous Vocalinux install can leave ~/.local/share/applications/vocalinux.desktop
and ~/.local/bin/vocalinux(-gui) behind. When their target or venv is gone they
start nothing, yet they sit next to Voculinux in launchers.

The one-time legacy import now also checks those three paths, after the tray is
up, with stat calls and small reads only. A launcher is stale when its target is
missing, a link is dangling, or a script names a missing interpreter or venv
entry. A working install is never reported. One notice names the stale files and
a marker in the new data directory keeps it to once. No file is removed or
modified.

Fixes #52
xavierk merged commit 8b842e5712 into main 2026-11-06 21:03:29 +00:00
Sign in to join this conversation.