diff --git a/.changes/global-shortcut-refactor.md b/.changes/global-shortcut-refactor.md new file mode 100644 index 00000000..295c8f6f --- /dev/null +++ b/.changes/global-shortcut-refactor.md @@ -0,0 +1,9 @@ +--- +"global-shortcut": "patch" +--- + +**Breaking change** Refactored the plugin Rust APIs for better DX and flexibility: + +- Changed `Builder::with_handler` to be a method instead of a static method, it will also be triggered for any and all shortcuts even if the shortcut is registered through JS. +- Added `Builder::with_shortcut` and `Builder::with_shortcuts` to register shortcuts on the plugin builder. +- Added `on_shortcut` and `on_all_shortcuts` to register shortcuts with a handler. diff --git a/plugins/global-shortcut/README.md b/plugins/global-shortcut/README.md index 4bdb24f4..f3288258 100644 --- a/plugins/global-shortcut/README.md +++ b/plugins/global-shortcut/README.md @@ -57,20 +57,22 @@ fn main() { .setup(|app| { #[cfg(desktop)] { - use tauri_plugin_global_shortcut::{Code, GlobalShortcutExt, Modifiers, Shortcut}; + use tauri::Manager; + use tauri_plugin_global_shortcut::{Code, Modifiers}; - let ctrl_n_shortcut = Shortcut::new(Some(Modifiers::CONTROL), Code::KeyN); app.handle().plugin( - tauri_plugin_global_shortcut::Builder::with_handler(move |_app, shortcut| { - println!("{:?}", shortcut); - if shortcut == &ctrl_n_shortcut { - println!("Ctrl-N Detected!"); - } - }) - .build(), + tauri_plugin_global_shortcut::Builder::new() + .with_shortcuts(["ctrl+d", "alt+space"])? + .with_handler(|app, shortcut| { + if shortcut.matches(Modifiers::CONTROL, Code::KeyD) { + let _ = app.emit("shortcut-event", "Ctrl+D triggered"); + } + if shortcut.matches(Modifiers::ALT, Code::Space) { + let _ = app.emit("shortcut-event", "Alt+Space triggered"); + } + }) + .build(), )?; - - app.global_shortcut().register(ctrl_n_shortcut)?; } Ok(()) diff --git a/plugins/global-shortcut/src/lib.rs b/plugins/global-shortcut/src/lib.rs index eed72a99..b1f3b83e 100644 --- a/plugins/global-shortcut/src/lib.rs +++ b/plugins/global-shortcut/src/lib.rs @@ -35,20 +35,6 @@ type Result = std::result::Result; type HotKeyId = u32; type HandlerFn = Box, &Shortcut) + Send + Sync + 'static>; -enum ShortcutSource { - Ipc(Channel), - Rust, -} - -impl Clone for ShortcutSource { - fn clone(&self) -> Self { - match self { - Self::Ipc(channel) => Self::Ipc(channel.clone()), - Self::Rust => Self::Rust, - } - } -} - pub struct ShortcutWrapper(Shortcut); impl From for ShortcutWrapper { @@ -64,52 +50,52 @@ impl TryFrom<&str> for ShortcutWrapper { } } -struct RegisteredShortcut { - source: ShortcutSource, - shortcut: (Shortcut, Option), +struct RegisteredShortcut { + shortcut: Shortcut, + handler: Option>>, } pub struct GlobalShortcut { #[allow(dead_code)] app: AppHandle, - manager: std::result::Result, - shortcuts: Arc>>, + manager: GlobalHotKeyManager, + shortcuts: Arc>>>, } impl GlobalShortcut { - fn register_internal( + fn register_internal, &Shortcut) + Send + Sync + 'static>( &self, - shortcut: (Shortcut, Option), - source: ShortcutSource, + shortcut: Shortcut, + handler: Option, ) -> Result<()> { - let id = shortcut.0.id(); - acquire_manager(&self.manager)?.register(shortcut.0)?; + let id = shortcut.id(); + let handler = handler.map(|h| Arc::new(Box::new(h) as HandlerFn)); + + self.manager.register(shortcut)?; self.shortcuts .lock() .unwrap() - .insert(id, RegisteredShortcut { source, shortcut }); + .insert(id, RegisteredShortcut { shortcut, handler }); Ok(()) } - fn register_all_internal)>>( - &self, - shortcuts: S, - source: ShortcutSource, - ) -> Result<()> { - let hotkeys = shortcuts - .into_iter() - .collect::)>>(); + fn register_all_internal(&self, shortcuts: S, handler: Option) -> Result<()> + where + S: IntoIterator, + F: Fn(&AppHandle, &Shortcut) + Send + Sync + 'static, + { + let handler = handler.map(|h| Arc::new(Box::new(h) as HandlerFn)); - let manager = acquire_manager(&self.manager)?; - let mut shortcuts = self.shortcuts.lock().unwrap(); - for hotkey in hotkeys { - manager.register(hotkey.0)?; + let hotkeys = shortcuts.into_iter().collect::>(); + let mut shortcuts = self.shortcuts.lock().unwrap(); + for shortcut in hotkeys { + self.manager.register(shortcut)?; shortcuts.insert( - hotkey.0.id(), + shortcut.id(), RegisteredShortcut { - source: source.clone(), - shortcut: hotkey, + shortcut, + handler: handler.clone(), }, ); } @@ -117,34 +103,65 @@ impl GlobalShortcut { Ok(()) } - pub fn register>(&self, shortcut: S) -> Result<()> + /// Register a shortcut. + pub fn register(&self, shortcut: S) -> Result<()> where + S: TryInto, S::Error: std::error::Error, { - self.register_internal((try_into_shortcut(shortcut)?, None), ShortcutSource::Rust) + self.register_internal( + try_into_shortcut(shortcut)?, + None::, &Shortcut)>, + ) } - pub fn register_all, S: IntoIterator>( - &self, - shortcuts: S, - ) -> Result<()> + /// Register a shortcut with a handler. + pub fn on_shortcut(&self, shortcut: S, handler: F) -> Result<()> + where + S: TryInto, + S::Error: std::error::Error, + F: Fn(&AppHandle, &Shortcut) + Send + Sync + 'static, + { + self.register_internal(try_into_shortcut(shortcut)?, Some(handler)) + } + + /// Register multiple shortcuts. + pub fn register_all(&self, shortcuts: S) -> Result<()> where + S: IntoIterator, + T: TryInto, T::Error: std::error::Error, { let mut s = Vec::new(); for shortcut in shortcuts { - s.push((try_into_shortcut(shortcut)?, None)); + s.push(try_into_shortcut(shortcut)?); } - self.register_all_internal(s, ShortcutSource::Rust) + self.register_all_internal(s, None::, &Shortcut)>) + } + + /// Register multiple shortcuts with a handler. + pub fn on_all_shortcuts(&self, shortcuts: S, handler: F) -> Result<()> + where + S: IntoIterator, + T: TryInto, + T::Error: std::error::Error, + F: Fn(&AppHandle, &Shortcut) + Send + Sync + 'static, + { + let mut s = Vec::new(); + for shortcut in shortcuts { + s.push(try_into_shortcut(shortcut)?); + } + self.register_all_internal(s, Some(handler)) } pub fn unregister>(&self, shortcut: S) -> Result<()> where S::Error: std::error::Error, { - acquire_manager(&self.manager)? - .unregister(try_into_shortcut(shortcut)?) - .map_err(Into::into) + let shortcut = try_into_shortcut(shortcut)?; + self.manager.unregister(shortcut)?; + self.shortcuts.lock().unwrap().remove(&shortcut.id()); + Ok(()) } pub fn unregister_all, S: IntoIterator>( @@ -158,9 +175,15 @@ impl GlobalShortcut { for shortcut in shortcuts { s.push(try_into_shortcut(shortcut)?); } - acquire_manager(&self.manager)? - .unregister_all(&s) - .map_err(Into::into) + + self.manager.unregister_all(&s)?; + + let mut shortcuts = self.shortcuts.lock().unwrap(); + for s in s { + shortcuts.remove(&s.id()); + } + + Ok(()) } /// Determines whether the given shortcut is registered by this application or not. @@ -188,14 +211,6 @@ impl> GlobalShortcutExt for T { } } -fn acquire_manager( - manager: &std::result::Result, -) -> Result<&GlobalHotKeyManager> { - manager - .as_ref() - .map_err(|e| Error::GlobalHotkey(e.to_string())) -} - fn parse_shortcut>(shortcut: S) -> Result { shortcut.as_ref().parse().map_err(Into::into) } @@ -218,8 +233,10 @@ fn register( handler: Channel, ) -> Result<()> { global_shortcut.register_internal( - (parse_shortcut(&shortcut)?, Some(shortcut)), - ShortcutSource::Ipc(handler), + parse_shortcut(&shortcut)?, + Some(move |_app: &AppHandle, _shortcut: &Shortcut| { + let _ = handler.send(&shortcut); + }), ) } @@ -231,10 +248,22 @@ fn register_all( handler: Channel, ) -> Result<()> { let mut hotkeys = Vec::new(); + + let mut shortcut_map = HashMap::new(); for shortcut in shortcuts { - hotkeys.push((parse_shortcut(&shortcut)?, Some(shortcut))); + let hotkey = parse_shortcut(&shortcut)?; + shortcut_map.insert(hotkey.id(), shortcut); + hotkeys.push(hotkey); } - global_shortcut.register_all_internal(hotkeys, ShortcutSource::Ipc(handler)) + + global_shortcut.register_all_internal( + hotkeys, + Some(move |_app: &AppHandle, shortcut: &Shortcut| { + if let Some(shortcut_str) = shortcut_map.get(&shortcut.id()) { + let _ = handler.send(shortcut_str); + } + }), + ) } #[tauri::command] @@ -269,12 +298,14 @@ fn is_registered( } pub struct Builder { + shortcuts: Vec, handler: Option>, } impl Default for Builder { fn default() -> Self { Self { + shortcuts: Vec::new(), handler: Default::default(), } } @@ -285,16 +316,42 @@ impl Builder { Self::default() } + /// Add a shortcut to be registerd. + pub fn with_shortcut(mut self, shortcut: T) -> Result + where + T: TryInto, + T::Error: std::error::Error, + { + self.shortcuts.push(try_into_shortcut(shortcut)?); + Ok(self) + } + + /// Add multiple shortcuts to be registerd. + pub fn with_shortcuts(mut self, shortcuts: S) -> Result + where + S: IntoIterator, + T: TryInto, + T::Error: std::error::Error, + { + for shortcut in shortcuts { + self.shortcuts.push(try_into_shortcut(shortcut)?); + } + + Ok(self) + } + + /// Specify a global shortcut handler that will be triggered for any and all shortcuts. pub fn with_handler, &Shortcut) + Send + Sync + 'static>( + mut self, handler: F, ) -> Self { - Self { - handler: Some(Box::new(handler)), - } + self.handler.replace(Box::new(handler)); + self } pub fn build(self) -> TauriPlugin { let handler = self.handler; + let shortcuts = self.shortcuts; PluginBuilder::new("global-shortcut") .js_init_script(include_str!("api-iife.js").to_string()) .invoke_handler(tauri::generate_handler![ @@ -305,29 +362,37 @@ impl Builder { is_registered ]) .setup(move |app, _api| { - let shortcuts = - Arc::new(Mutex::new(HashMap::::new())); + let manager = GlobalHotKeyManager::new()?; + let mut store = HashMap::>::new(); + for shortcut in shortcuts { + manager.register(shortcut)?; + store.insert( + shortcut.id(), + RegisteredShortcut { + shortcut, + handler: None, + }, + ); + } + + let shortcuts = Arc::new(Mutex::new(store)); let shortcuts_ = shortcuts.clone(); let app_handle = app.clone(); GlobalHotKeyEvent::set_event_handler(Some(move |e: GlobalHotKeyEvent| { if let Some(shortcut) = shortcuts_.lock().unwrap().get(&e.id) { - match &shortcut.source { - ShortcutSource::Ipc(channel) => { - let _ = channel.send(&shortcut.shortcut.1); - } - ShortcutSource::Rust => { - if let Some(handler) = &handler { - handler(&app_handle, &shortcut.shortcut.0); - } - } + if let Some(handler) = &shortcut.handler { + handler(&app_handle, &shortcut.shortcut); + } + if let Some(handler) = &handler { + handler(&app_handle, &shortcut.shortcut); } } })); app.manage(GlobalShortcut { app: app.clone(), - manager: GlobalHotKeyManager::new(), + manager, shortcuts, }); Ok(())