From 4a9435638663d08a67282dbc540d603d180d2841 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Tue, 22 Sep 2026 22:03:02 -0300 Subject: [PATCH] fix(deep-link): unregister must refresh the database --- .changes/fix-deep-link-linux-unregister.md | 5 ++ packages/api-e2e/test/specs/deep-link.spec.ts | 37 +++++------- plugins/deep-link/src/lib.rs | 58 +++++++++++++++---- 3 files changed, 67 insertions(+), 33 deletions(-) create mode 100644 .changes/fix-deep-link-linux-unregister.md diff --git a/.changes/fix-deep-link-linux-unregister.md b/.changes/fix-deep-link-linux-unregister.md new file mode 100644 index 000000000..ab542f7ae --- /dev/null +++ b/.changes/fix-deep-link-linux-unregister.md @@ -0,0 +1,5 @@ +--- +deep-link: patch +--- + +Fixed `unregister` on Linux leaving the app as the scheme's handler: it now also removes the scheme from the `MimeType` of the handler's `.desktop` file and refreshes the desktop database, which `xdg-mime` falls back to. It also no longer fails when `mimeapps.list` does not exist. diff --git a/packages/api-e2e/test/specs/deep-link.spec.ts b/packages/api-e2e/test/specs/deep-link.spec.ts index a818e8f17..1deb711b5 100644 --- a/packages/api-e2e/test/specs/deep-link.spec.ts +++ b/packages/api-e2e/test/specs/deep-link.spec.ts @@ -3,13 +3,7 @@ // SPDX-License-Identifier: MIT import { expect } from '@wdio/globals' -import { - tauri, - tauriError, - describePlugin, - itOn, - platform -} from '../helpers/index.js' +import { tauri, tauriError, describePlugin, itOn } from '../helpers/index.js' // The suite launches the app without a URL, so there is no current deep link, // and it cannot open one through the OS either. `onOpenUrl` is exercised by @@ -48,7 +42,7 @@ describePlugin('deep-link', () => { itOn( ['linux', 'win32'], - 'register makes the app the scheme handler', + 'register and unregister toggle the scheme handler', async () => { const result = await tauri(async (api, scheme) => { await api.deepLink.register(scheme) @@ -59,23 +53,22 @@ describePlugin('deep-link', () => { afterUnregister: await api.deepLink.isRegistered(scheme) } }, scheme) - expect(result.registered).toBe(true) - // On Linux `xdg-mime` falls back to the desktop database, which still lists - // the handler after its `mimeapps.list` default is removed. - if (platform === 'win32') { - expect(result.afterUnregister).toBe(false) - } + expect(result).toEqual({ registered: true, afterUnregister: false }) } ) - itOn('win32', 'isRegistered is false for an unknown scheme', async () => { - expect( - await tauri( - (api, scheme) => api.deepLink.isRegistered(scheme), - `${scheme}-unknown` - ) - ).toBe(false) - }) + itOn( + ['linux', 'win32'], + 'isRegistered is false for an unknown scheme', + async () => { + expect( + await tauri( + (api, scheme) => api.deepLink.isRegistered(scheme), + `${scheme}-unknown` + ) + ).toBe(false) + } + ) itOn( ['darwin', 'android', 'ios'], diff --git a/plugins/deep-link/src/lib.rs b/plugins/deep-link/src/lib.rs index eb5443159..9796060a6 100644 --- a/plugins/deep-link/src/lib.rs +++ b/plugins/deep-link/src/lib.rs @@ -148,7 +148,7 @@ mod imp { /// /// ## Platform-specific: /// - /// - **Linux**: Can only unregister the scheme if it was initially registered with [`register`](`Self::register`). May not work on older distros. + /// - **Linux**: Can only unregister the scheme if it was initially registered with [`register`](`Self::register`). Needs the `update-desktop-database` command available on the system. May not work on older distros. /// - **macOS / Android / iOS**: Unsupported, will return [`Error::UnsupportedPlatform`](`crate::Error::UnsupportedPlatform`). pub fn unregister>(&self, _protocol: S) -> crate::Result<()> { Err(crate::Error::UnsupportedPlatform) @@ -383,7 +383,7 @@ mod imp { /// /// - **Windows**: Requires admin rights if the protocol is registered on local machine /// (this can happen when registered from the NSIS installer when the install mode is set to both or per machine) - /// - **Linux**: Can only unregister the scheme if it was initially registered with [`register`](`Self::register`). May not work on older distros. + /// - **Linux**: Can only unregister the scheme if it was initially registered with [`register`](`Self::register`). Refreshes the desktop database with the `update-desktop-database` command; without it, [`is_registered`](`Self::is_registered`) may keep returning `true`. May not work on older distros. /// - **macOS / Android / iOS**: Unsupported, will return [`Error::UnsupportedPlatform`](`crate::Error::UnsupportedPlatform`). pub fn unregister>(&self, _protocol: S) -> crate::Result<()> { #[cfg(windows)] @@ -401,9 +401,6 @@ mod imp { #[cfg(target_os = "linux")] { - let mimeapps_path = self.app.path().config_dir()?.join("mimeapps.list"); - let mut mimeapps = ini::Ini::load_from_file(&mimeapps_path)?; - let file_name = format!( "{}-handler.desktop", tauri::utils::platform::current_exe()? @@ -411,16 +408,55 @@ mod imp { .unwrap() .to_string_lossy() ); + let mime_type = format!("x-scheme-handler/{}", _protocol.as_ref()); - if let Some(section) = mimeapps.section_mut(Some("Default Applications")) { - let scheme = format!("x-scheme-handler/{}", _protocol.as_ref()); - - if section.get(&scheme).unwrap_or_default() == file_name { - section.remove(scheme); + // stop being the default handler + let mimeapps_path = self.app.path().config_dir()?.join("mimeapps.list"); + if mimeapps_path.exists() { + let mut mimeapps = ini::Ini::load_from_file(&mimeapps_path)?; + if let Some(section) = mimeapps.section_mut(Some("Default Applications")) { + if section.get(&mime_type).unwrap_or_default() == file_name { + section.remove(&mime_type); + } } + mimeapps.write_to_file(&mimeapps_path)?; } - mimeapps.write_to_file(mimeapps_path)?; + // Stop declaring the scheme in the handler's `.desktop` file too: the desktop + // database indexes it, and with no default set `xdg-mime` falls back to that + // index, so the app would otherwise still be the handler. + let applications = self.app.path().data_dir()?.join("applications"); + let desktop_file_path = applications.join(&file_name); + // Only the `MimeType` key is touched: the file may carry other changes. + if let Ok(mut desktop_file) = ini::Ini::load_from_file(&desktop_file_path) { + if let Some(section) = desktop_file.section_mut(Some("Desktop Entry")) { + let mime_types = section + .get("MimeType") + .unwrap_or_default() + .split(';') + .filter(|mime| !mime.is_empty() && *mime != mime_type) + .map(ToString::to_string) + .collect::>(); + if mime_types.is_empty() { + section.remove("MimeType"); + } else { + section.insert("MimeType", mime_types.join(";")); + } + } + desktop_file.write_to_file(&desktop_file_path)?; + + // Without the refreshed index `xdg-mime` may keep reporting the app as the + // handler, but the scheme is unregistered as far as the app can tell, so a + // missing command is not an error. + if let Err(e) = Command::new("update-desktop-database") + .arg(&applications) + .status() + { + tracing::warn!( + "Failed to run OS command `update-desktop-database`, the desktop database may still list the app as the `{mime_type}` handler: {e}" + ); + } + } Ok(()) }