mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-10-10 22:38:44 +02:00
fix(global-shortcut): release the shortcuts lock before running handlers (#3683)
This commit is contained in:
1 parent
42ae8b4501
commit
fc18d05513
3 files changed
+97
-8
No files matched your search
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
global-shortcut: patch
|
||||||
|
global-shortcut-js: patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Release the shortcuts lock before running shortcut handlers, so a handler that calls back into the plugin, or pumps messages that dispatch another shortcut event, no longer deadlocks the app.
|
||||||
@@ -33,5 +33,8 @@ tauri = { workspace = true }
|
|||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tauri = { workspace = true, features = ["test"] }
|
||||||
|
|
||||||
[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies]
|
[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies]
|
||||||
global-hotkey = { version = "0.8", features = ["serde"] }
|
global-hotkey = { version = "0.8", features = ["serde"] }
|
||||||
@@ -63,6 +63,32 @@ struct RegisteredShortcut<R: Runtime> {
|
|||||||
handler: Option<Arc<HandlerFn<R>>>,
|
handler: Option<Arc<HandlerFn<R>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Runs the handlers registered for the shortcut behind `event`.
|
||||||
|
///
|
||||||
|
/// The shortcut and its handler are copied out of the map and the lock is released before any
|
||||||
|
/// user code runs, so a handler can call back into the plugin (or pump messages that dispatch
|
||||||
|
/// another shortcut event) without deadlocking on `shortcuts`.
|
||||||
|
fn dispatch_shortcut_event<R: Runtime>(
|
||||||
|
shortcuts: &Mutex<HashMap<HotKeyId, RegisteredShortcut<R>>>,
|
||||||
|
app: &AppHandle<R>,
|
||||||
|
global_handler: Option<&HandlerFn<R>>,
|
||||||
|
event: GlobalHotKeyEvent,
|
||||||
|
) {
|
||||||
|
let registered = shortcuts
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.get(&event.id)
|
||||||
|
.map(|s| (s.shortcut, s.handler.clone()));
|
||||||
|
if let Some((shortcut, shortcut_handler)) = registered {
|
||||||
|
if let Some(handler) = &shortcut_handler {
|
||||||
|
handler(app, &shortcut, event);
|
||||||
|
}
|
||||||
|
if let Some(handler) = global_handler {
|
||||||
|
handler(app, &shortcut, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct GlobalHotKeyManager(global_hotkey::GlobalHotKeyManager);
|
struct GlobalHotKeyManager(global_hotkey::GlobalHotKeyManager);
|
||||||
|
|
||||||
/// SAFETY: we ensure it is run on main thread only
|
/// SAFETY: we ensure it is run on main thread only
|
||||||
@@ -475,14 +501,7 @@ impl<R: Runtime> Builder<R> {
|
|||||||
|
|
||||||
let app_handle = app.clone();
|
let app_handle = app.clone();
|
||||||
GlobalHotKeyEvent::set_event_handler(Some(move |e: GlobalHotKeyEvent| {
|
GlobalHotKeyEvent::set_event_handler(Some(move |e: GlobalHotKeyEvent| {
|
||||||
if let Some(shortcut) = shortcuts_.lock().unwrap().get(&e.id) {
|
dispatch_shortcut_event(&shortcuts_, &app_handle, handler.as_ref(), e);
|
||||||
if let Some(handler) = &shortcut.handler {
|
|
||||||
handler(&app_handle, &shortcut.shortcut, e);
|
|
||||||
}
|
|
||||||
if let Some(handler) = &handler {
|
|
||||||
handler(&app_handle, &shortcut.shortcut, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
app.manage(GlobalShortcut {
|
app.manage(GlobalShortcut {
|
||||||
@@ -495,3 +514,64 @@ impl<R: Runtime> Builder<R> {
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
use tauri::test::{MockRuntime, mock_app};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn handlers_run_without_holding_the_shortcuts_lock() {
|
||||||
|
let app = mock_app();
|
||||||
|
let shortcut = Shortcut::new(Some(Modifiers::ALT), Code::KeyW);
|
||||||
|
let shortcuts = Arc::new(Mutex::new(HashMap::<
|
||||||
|
HotKeyId,
|
||||||
|
RegisteredShortcut<MockRuntime>,
|
||||||
|
>::new()));
|
||||||
|
let calls = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
let shortcut_handler: HandlerFn<MockRuntime> = {
|
||||||
|
let shortcuts = shortcuts.clone();
|
||||||
|
let calls = calls.clone();
|
||||||
|
Box::new(move |_, _, _| {
|
||||||
|
assert!(
|
||||||
|
shortcuts.try_lock().is_ok(),
|
||||||
|
"the shortcuts lock must not be held while a handler runs"
|
||||||
|
);
|
||||||
|
calls.fetch_add(1, Ordering::SeqCst);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
let global_handler: HandlerFn<MockRuntime> = {
|
||||||
|
let shortcuts = shortcuts.clone();
|
||||||
|
let calls = calls.clone();
|
||||||
|
Box::new(move |_, _, _| {
|
||||||
|
assert!(
|
||||||
|
shortcuts.try_lock().is_ok(),
|
||||||
|
"the shortcuts lock must not be held while a handler runs"
|
||||||
|
);
|
||||||
|
calls.fetch_add(1, Ordering::SeqCst);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
shortcuts.lock().unwrap().insert(
|
||||||
|
shortcut.id(),
|
||||||
|
RegisteredShortcut {
|
||||||
|
shortcut,
|
||||||
|
handler: Some(Arc::new(shortcut_handler)),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch_shortcut_event(
|
||||||
|
&shortcuts,
|
||||||
|
app.handle(),
|
||||||
|
Some(&global_handler),
|
||||||
|
GlobalHotKeyEvent {
|
||||||
|
id: shortcut.id(),
|
||||||
|
state: ShortcutState::Pressed,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(calls.load(Ordering::SeqCst), 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user