Only let an AppImage claim the rad: scheme

A dev build or an AppImage registered itself whenever it wasn't already
the handler, replacing the installed app as the target of rad: links.
Now installers own the scheme, and an AppImage only claims it when no
other handler is set. Dev builds never register; the README explains
how to test links in them instead.
This commit is contained in:
Rūdolfs Ošiņš
2026-10-08 10:35:46 +02:00
parent 344e10d967
commit 840f1b0c08
2 changed files with 50 additions and 16 deletions
+16
View File
@@ -48,6 +48,22 @@ to the browser console (visible at debug level):
VITE_LOG_INVOKE_TIMINGS=true npm run dev VITE_LOG_INVOKE_TIMINGS=true npm run dev
``` ```
A dev build uses its own identifier, so it runs next to an installed release,
and it never registers itself as the handler for `rad:` links. On Linux and
Windows, open a link in it by starting the dev binary a second time with the
link as its argument, while `npm run dev` is running:
```
target/dev-without-assertions/radicle-desktop rad:z4D5UCArafTzTQpDZNQRuqswh3ury
```
On macOS links only reach app bundles. Quit the installed app, then build and
open a debug bundle:
```
npm run tauri build -- --debug --bundles app
```
## Releasing ## Releasing
Releases are driven by an interactive, phased script. Start one with: Releases are driven by an interactive, phased script. Start one with:
+34 -16
View File
@@ -2,7 +2,6 @@ mod commands;
use radicle_types::AppState; use radicle_types::AppState;
use tauri::Manager; use tauri::Manager;
use tauri_plugin_deep_link::DeepLinkExt;
use tauri_plugin_log::{Target, TargetKind}; use tauri_plugin_log::{Target, TargetKind};
use commands::{auth, cob, diff, identity, inbox, profile, repo, startup, thread}; use commands::{auth, cob, diff, identity, inbox, profile, repo, startup, thread};
@@ -60,17 +59,7 @@ pub fn run() {
.plugin(tauri_plugin_window_state::Builder::default().build()) .plugin(tauri_plugin_window_state::Builder::default().build())
.plugin(tauri_plugin_deep_link::init()) .plugin(tauri_plugin_deep_link::init())
.setup(|app| { .setup(|app| {
// Installers register the scheme; an AppImage or a dev build has to register_rad_scheme(app);
// do it at runtime. It must never keep the app from starting, nor
// take the scheme back from a handler the user chose.
let unpackaged = cfg!(debug_assertions) || is_appimage(app);
if cfg!(any(target_os = "linux", windows))
&& unpackaged
&& !app.deep_link().is_registered("rad").unwrap_or(false)
&& let Err(e) = app.deep_link().register_all()
{
log::warn!("Unable to register the rad: scheme: {e}");
}
Ok(()) Ok(())
}) })
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
@@ -139,12 +128,41 @@ pub fn run() {
.expect("error while running tauri application"); .expect("error while running tauri application");
} }
// Installers register the scheme; an AppImage has to do it at runtime. It
// must never keep the app from starting, nor take the scheme from another
// handler, such as an installed package or one the user chose.
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn is_appimage(app: &tauri::App) -> bool { fn register_rad_scheme(app: &tauri::App) {
app.env().appimage.is_some() use tauri_plugin_deep_link::DeepLinkExt;
if app.env().appimage.is_none() || !rad_scheme_unclaimed() {
return;
}
if let Err(e) = app.deep_link().register_all() {
log::warn!("Unable to register the rad: scheme: {e}");
}
} }
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]
fn is_appimage(_app: &tauri::App) -> bool { fn register_rad_scheme(_app: &tauri::App) {}
false
// The scheme is ours to claim when no handler is set, or when the handler is
// the one an AppImage registered before, which may point to an old path.
#[cfg(target_os = "linux")]
fn rad_scheme_unclaimed() -> bool {
let Ok(exe) = tauri::utils::platform::current_exe() else {
return false;
};
let Some(exe) = exe.file_name() else {
return false;
};
let Ok(output) = std::process::Command::new("xdg-mime")
.args(["query", "default", "x-scheme-handler/rad"])
.output()
else {
return false;
};
let handler = String::from_utf8_lossy(&output.stdout);
let handler = handler.trim();
handler.is_empty() || handler == format!("{}-handler.desktop", exe.to_string_lossy())
} }