Skip to content

fix(unix): restore launcher fallback and support Plasma 6 - #135

Merged
Byron merged 3 commits into
mainfrom
fix-linux-launchers
Oct 22, 2026
Merged

Byron merged 3 commits into
mainfrom
fix-linux-launchers

Conversation

@Byron

@Byron Byron commented Oct 21, 2026 •

Copy link
Copy Markdown
Owner

Tasks

  • refackiew

When xdg-open starts but exits unsuccessfully, that() and the CLI
previously stopped before trying another installed opener. On Plasma 6,
the fallback list also omitted the versioned KDE launchers.

Launcher behavior

  • Retry reported failures from UNIX desktop launchers and retain the first
    unsuccessful exit when later candidates are missing.
  • Add kde-open6 and kde-open5 before the existing kde-open fallback.
  • Keep launcher ordering, argument boundaries, and exit-status policy
    together so compatibility decisions are explicit.
  • Preserve Windows' stop-on-failure policy and the existing protections
    for option-shaped and non-UTF-8 paths.

Bounded detached checks

Add that_detached_with_timeout(path, timeout) to opt into checking the
actual launcher status for a configurable interval per candidate. Retry
confirmed UNIX launcher failures; consider successful exits or launchers
still running at the deadline handed off. A zero timeout polls once.

On UNIX, use setsid() while retaining the launcher as a child. Reserve a
background waiter before spawning and transfer ownership after a timeout
so the launcher can outlive its caller and is eventually reaped. Stop
fallback if observing an already-started launcher fails, avoiding a second
launch based only on a wait error. Existing detached calls retain their
timing behavior. The new API uses command-based launchers on Windows,
including when shellexecute-on-windows is enabled.

Historical constraints

55dc10d relaxed exit checks for argumentless availability probes, where
usage errors are expected. It did not establish that failed real opening
operations should stop fallback. 245c95e introduced that behavior;
the earlier implementation retried unsuccessful operations.

Keep stderr redirected to null: 30a144a and 0bdc6d6 document previous
pipe deadlocks and hangs. Do not interpret the successful double-fork
intermediate child's exit as launcher success (a78c0ca, 43e601d).

Document these constraints and upstream launcher behavior in
docs/launchers.md. Exclude kioclient exec, which enables execution
beyond kde-open's behavior, and arbitrary file-manager or AppImage
environment fallbacks that could override configured handlers.

Validation and limits

  • Default and all-feature Linux tests pass, covering fallback, error
    selection, Plasma 6 reachability, timeouts, child reaping, survival after
    caller exit, large stderr output, argument protection, and CLI behavior.
  • Formatting, git diff --check, and all-target, all-feature Linux clippy
    pass. Windows and macOS compile checks and clippy also pass.
  • Actual desktop and AppImage behavior still needs testing. A handler
    that opens a target before reporting failure can cause a duplicate open
    when retried. Failures after the timeout cannot trigger fallback.

Refs: #134

<!-- agent -->
When `xdg-open` starts but exits unsuccessfully, `that()` and the CLI
previously stopped before trying another installed opener. On Plasma 6,
the fallback list also omitted the versioned KDE launchers.

### Launcher behavior

- Retry reported failures from UNIX desktop launchers and retain the first
  unsuccessful exit when later candidates are missing.
- Add `kde-open6` and `kde-open5` before the existing `kde-open` fallback.
- Keep launcher ordering, argument boundaries, and exit-status policy
  together so compatibility decisions are explicit.
- Preserve Windows' stop-on-failure policy and the existing protections
  for option-shaped and non-UTF-8 paths.

### Bounded detached checks

Add `that_detached_with_timeout(path, timeout)` to opt into checking the
actual launcher status for a configurable interval per candidate. Retry
confirmed UNIX launcher failures; consider successful exits or launchers
still running at the deadline handed off. A zero timeout polls once.

On UNIX, use `setsid()` while retaining the launcher as a child. Reserve a
background waiter before spawning and transfer ownership after a timeout
so the launcher can outlive its caller and is eventually reaped. Stop
fallback if observing an already-started launcher fails, avoiding a second
launch based only on a wait error. Existing detached calls retain their
timing behavior. The new API uses command-based launchers on Windows,
including when `shellexecute-on-windows` is enabled.

### Historical constraints

`55dc10d` relaxed exit checks for argumentless availability probes, where
usage errors are expected. It did not establish that failed real opening
operations should stop fallback. `245c95e` introduced that behavior;
the earlier implementation retried unsuccessful operations.

Keep stderr redirected to null: `30a144a` and `0bdc6d6` document previous
pipe deadlocks and hangs. Do not interpret the successful double-fork
intermediate child's exit as launcher success (`a78c0ca`, `43e601d`).

Document these constraints and upstream launcher behavior in
`docs/launchers.md`. Exclude `kioclient exec`, which enables execution
beyond `kde-open`'s behavior, and arbitrary file-manager or AppImage
environment fallbacks that could override configured handlers.

### Validation and limits

- Default and all-feature Linux tests pass, covering fallback, error
  selection, Plasma 6 reachability, timeouts, child reaping, survival after
  caller exit, large stderr output, argument protection, and CLI behavior.
- Formatting, `git diff --check`, and all-target, all-feature Linux clippy
  pass. Windows and macOS compile checks and clippy also pass.
- Actual desktop and AppImage behavior still needs testing. A handler
  that opens a target before reporting failure can cause a duplicate open
  when retried. Failures after the timeout cannot trigger fallback.

Refs: #134

Assisted-by: GPT 6.0 Astra
Co-authored-by: GPT 6.0 Astra <codex@openai.com>
@Byron
Byron force-pushed the fix-linux-launchers branch from 12db1c3 to 279c1b2 Compare October 21, 2026 19:15
@Byron
Byron marked this pull request as ready for review October 21, 2026 19:15
@Byron
Byron requested a balanced review from Copilot October 21, 2026 19:15
@Byron

Byron commented Oct 21, 2026

Copy link
Copy Markdown
Owner Author

Will merge once @Alfagun74 could give it a shot.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Timeout polling can retry failures after the deadline, and the reaping assertion checks the wrong thread.

2 open issues
What changed in this PR

Restores UNIX launcher fallback and adds Plasma 6 support, addressing #134 while preserving Windows’ exit-status policy.

Changes:

  • Adds versioned KDE launchers and retries unsuccessful UNIX launches.
  • Introduces bounded detached status checks with background child reaping.
  • Adds fallback, timeout, argument-safety, and CLI regression tests.
File Description
tests/​unix_launchers.rs Adds launcher integration tests.
src/​unix.rs Centralizes launcher policies and adds KDE candidates.
src/​main.rs Retries failed UNIX launchers in the CLI.
src/​lib.rs Adds shared fallback handling and the detached timeout API.

Review effort: Balanced

Comment thread src/lib.rs
Comment thread tests/unix_launchers.rs Outdated
@Alfagun74

Alfagun74 commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Tested 279c1b2 locally with a simulated Plasma 6 setup (xdg-open exits 4, only kde-open6 present):

API Launchers attempted Result
that() xdg-open → kde-open6 Ok(())
that_detached_with_timeout(200ms) xdg-open → kde-open6 Ok(())
that_detached() xdg-open only Ok(()), nothing opened

Integration tests, unit tests, doc tests and cargo clippy --all-targets pass here.

The remaining gap: tauri-plugin-opener calls that_detached(), so Tauri apps still get Ok(()) when xdg-open spawns and fails. The new candidate list only kicks in when a launcher is absent. Either the plugin switches to that_detached_with_timeout(), or that_detached() needs a small bounded default; I'll follow up in the plugin repo.

Both Copilot findings look right. If the deadline is only checked while the child is still running, a launcher that fails just after the timeout can trigger a second launch and a duplicate open. And the reaping test has to read /proc/thread-self/children; the launcher is spawned on a worker thread, so checking /proc/self/task/<pid>/children can mask a zombie.

AppImage: keeping PATH/LD_LIBRARY_PATH untouched is reasonable, but the bundled $APPDIR/usr/bin/xdg-open caveat should be documented somewhere user-visible. The PR text mentions docs/launchers.md, which doesn't exist in the branch; the constraints live in the unix.rs doc comment instead.

Skipping kioclient exec and hard-coded file managers makes sense given allowExec and handler bypassing. We keep that fallback app-side.

I'll test the GameVault build on a real Plasma 6 machine and report back.

@Byron

Byron commented Oct 21, 2026

Copy link
Copy Markdown
Owner Author

Thanks for testing. Can you provide a patch for this? I can't easily test it.
Thanks.

`that_detached()` returned as soon as a launcher spawned, so a launcher that
failed right after starting was reported as success and no fallback ran.

Observe each launcher for 250 ms by delegating to
`that_detached_with_timeout()`. A launcher that exits unsuccessfully within
that window falls through to the next candidate; one still running is treated
as handed off. The macOS and `shellexecute-on-windows` paths are unchanged.

Extend the fixture tests to call `that_detached()` directly.

Refs: #134
@Alfagun74

Alfagun74 commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Opened #136 against fix-linux-launchers with the that_detached() part.

that_detached() now checks each launcher for 250 ms, so tauri-plugin-opener gets the fallback too. cargo test, cargo fmt --check and cargo clippy --all-targets pass; the fake Plasma 6 setup reaches kde-open6.

<!-- agent -->
- The deadline is checked only after `try_wait()` reports a running child. With a 50 ms timeout, if the polling thread resumes at 60 ms after the launcher failed at 55 ms, this returns `Exited` and triggers fallback. That contradicts the documented policy that failures after the timeout cannot trigger fallback and can cause a duplicate open. Check the deadline before subsequent polls, preserving the initial poll for a zero timeout.
- Linux tracks children per thread. `std::process::id()` selects the main thread, but the test harness runs `launcher_fixture` on a worker thread that spawns the launcher. This assertion can therefore pass even if that launcher remains an unreaped zombie. Read `/proc/thread-self/children` to check the spawning thread instead.

Assisted-by: GPT 6.1 Sol
Co-authored-by: GPT 6.1 Sol <codex@openai.com>
@Byron
Byron force-pushed the fix-linux-launchers branch from 817de63 to c88759b Compare October 22, 2026 03:50
@Byron
Byron merged commit 4e64f6b into main Oct 22, 2026
7 checks passed
@Byron
Byron deleted the fix-linux-launchers branch October 22, 2026 03:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Linux: failing launchers are treated as success and KDE Plasma 6 launchers are never tried

3 participants