Added focused-window shortcut fallback on Linux (#882)
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-07-21 14:43:33 +02:00
1 parent db508edc28
commit 1e5f07cb01
11 files changed
+350 -17

No files matched your search

+48 -9
View File
@@ -91,6 +91,9 @@ pub enum ShortcutBackend {
/// The Tauri global-shortcut plugin manages the shortcut.
Tauri,
/// The focused application window handles the shortcut.
Local,
}
/// Response for shortcut registration and processing state changes.
@@ -151,6 +154,12 @@ enum ActiveBinding {
shortcut: String,
},
/// Stores a shortcut handled within the focused application window.
Local {
/// Contains the registered shortcut in Tauri syntax.
shortcut: String,
},
#[cfg(target_os = "linux")]
/// Stores a shortcut and its live XDG portal session.
Portal {
@@ -170,6 +179,7 @@ impl ActiveBinding {
fn shortcut(&self) -> &str {
match self {
Self::Tauri { shortcut } => shortcut,
Self::Local { shortcut } => shortcut,
#[cfg(target_os = "linux")]
Self::Portal { shortcut, .. } => shortcut,
}
@@ -179,6 +189,7 @@ impl ActiveBinding {
fn backend(&self) -> ShortcutBackend {
match self {
Self::Tauri { .. } => ShortcutBackend::Tauri,
Self::Local { .. } => ShortcutBackend::Local,
#[cfg(target_os = "linux")]
Self::Portal { .. } => ShortcutBackend::Portal,
}
@@ -188,6 +199,7 @@ impl ActiveBinding {
fn effective_display_name(&self) -> String {
match self {
Self::Tauri { shortcut } => shortcut.clone(),
Self::Local { shortcut } => shortcut.clone(),
#[cfg(target_os = "linux")]
Self::Portal { effective_display_name, .. } => effective_display_name.clone(),
}
@@ -246,8 +258,15 @@ pub async fn register(
Err(error) => {
let current_backend = manager.bindings.get(&request.id).map(ActiveBinding::backend);
if may_fallback_to_tauri(error.kind, current_backend) {
warn!(Source = "XDG portal"; "Global shortcut registration failed; using the Tauri X11 backend: {}", error.message);
if may_fallback_to_local(error.kind, current_backend) {
warn!(Source = "XDG portal"; "Global shortcut registration failed; using the focused-window fallback: {}", error.message);
if let Some(old_binding) = manager.bindings.remove(&request.id) {
close_binding(&app_handle, request.id, old_binding).await;
}
manager.bindings.insert(request.id, ActiveBinding::Local { shortcut: request.shortcut.clone() });
return ShortcutResponse::success(ShortcutBackend::Local, request.shortcut);
} else {
let cancelled = error.kind == PortalFailureKind::Cancelled;
if cancelled {
@@ -264,6 +283,7 @@ pub async fn register(
}
}
#[cfg(not(target_os = "linux"))]
match register_tauri_binding(&app_handle, &request.shortcut, request.id, event_sender) {
Ok(()) => {
if let Some(old_binding) = manager.bindings.remove(&request.id) {
@@ -329,6 +349,8 @@ async fn close_binding(app_handle: &tauri::AppHandle, id: Shortcut, binding: Act
}
},
ActiveBinding::Local { .. } => {},
#[cfg(target_os = "linux")]
ActiveBinding::Portal { generation, session, .. } => {
let is_still_active = ACTIVE_PORTAL_GENERATIONS.lock().unwrap().get(&id) == Some(&generation);
@@ -447,8 +469,8 @@ enum PortalFailureKind {
Technical,
}
/// Determines whether a failed portal attempt may safely fall back to Tauri.
fn may_fallback_to_tauri(_failure: PortalFailureKind, current_backend: Option<ShortcutBackend>) -> bool {
/// Determines whether a failed portal attempt may safely use the focused-window fallback.
fn may_fallback_to_local(_failure: PortalFailureKind, current_backend: Option<ShortcutBackend>) -> bool {
current_backend != Some(ShortcutBackend::Portal)
}
@@ -946,29 +968,46 @@ mod tests {
}
#[test]
/// Verifies that all initial portal failures use the Tauri fallback.
fn all_initial_portal_failures_use_tauri_fallback() {
/// Verifies that all initial portal failures use the focused-window fallback.
fn all_initial_portal_failures_use_local_fallback() {
for failure in [
PortalFailureKind::Unavailable,
PortalFailureKind::Cancelled,
PortalFailureKind::Denied,
PortalFailureKind::Technical,
] {
assert!(may_fallback_to_tauri(failure, None));
assert!(may_fallback_to_local(failure, None));
}
}
#[test]
/// Verifies that a failed reconfiguration never replaces an active portal binding.
fn failed_reconfiguration_preserves_portal_binding() {
assert!(!may_fallback_to_tauri(PortalFailureKind::Cancelled, Some(ShortcutBackend::Portal)));
assert!(!may_fallback_to_tauri(PortalFailureKind::Denied, Some(ShortcutBackend::Portal)));
for failure in [
PortalFailureKind::Unavailable,
PortalFailureKind::Cancelled,
PortalFailureKind::Denied,
PortalFailureKind::Technical,
] {
assert!(!may_fallback_to_local(failure, Some(ShortcutBackend::Portal)));
}
}
#[test]
/// Verifies that focused-window bindings expose their shortcut and backend consistently.
fn local_binding_reports_runtime_state() {
let binding = ActiveBinding::Local { shortcut: "CmdOrControl+3".to_string() };
assert_eq!(binding.shortcut(), "CmdOrControl+3");
assert_eq!(binding.backend(), ShortcutBackend::Local);
assert_eq!(binding.effective_display_name(), "CmdOrControl+3");
}
#[test]
/// Verifies that suspend keeps portal sessions while unregistering Tauri bindings.
fn suspend_keeps_portal_session_registered() {
assert!(!unregister_backend_during_suspend(ShortcutBackend::Portal));
assert!(!unregister_backend_during_suspend(ShortcutBackend::Local));
assert!(unregister_backend_during_suspend(ShortcutBackend::Tauri));
}