global_shortcut.rs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. // Copyright 2019-2023 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. //! Global shortcut implementation.
  5. use std::{
  6. collections::HashMap,
  7. error::Error as StdError,
  8. fmt,
  9. rc::Rc,
  10. sync::{
  11. mpsc::{channel, Sender},
  12. Arc, Mutex,
  13. },
  14. };
  15. use crate::{getter, Context, Message};
  16. use tauri_runtime::{Error, GlobalShortcutManager, Result, UserEvent};
  17. #[cfg(desktop)]
  18. pub use wry::application::{
  19. accelerator::{Accelerator, AcceleratorId, AcceleratorParseError},
  20. global_shortcut::{GlobalShortcut, ShortcutManager as WryShortcutManager},
  21. };
  22. pub type GlobalShortcutListeners = Arc<Mutex<HashMap<AcceleratorId, Box<dyn Fn() + Send>>>>;
  23. #[derive(Debug, Clone)]
  24. pub enum GlobalShortcutMessage {
  25. IsRegistered(Accelerator, Sender<bool>),
  26. Register(Accelerator, Sender<Result<GlobalShortcutWrapper>>),
  27. Unregister(GlobalShortcutWrapper, Sender<Result<()>>),
  28. UnregisterAll(Sender<Result<()>>),
  29. }
  30. #[derive(Debug, Clone)]
  31. pub struct GlobalShortcutWrapper(GlobalShortcut);
  32. // SAFETY: usage outside of main thread is guarded, we use the event loop on such cases.
  33. #[allow(clippy::non_send_fields_in_send_ty)]
  34. unsafe impl Send for GlobalShortcutWrapper {}
  35. #[derive(Debug, Clone)]
  36. struct AcceleratorParseErrorWrapper(AcceleratorParseError);
  37. impl fmt::Display for AcceleratorParseErrorWrapper {
  38. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  39. write!(f, "{}", self.0)
  40. }
  41. }
  42. impl StdError for AcceleratorParseErrorWrapper {}
  43. /// Wrapper around [`WryShortcutManager`].
  44. #[derive(Clone)]
  45. pub struct GlobalShortcutManagerHandle<T: UserEvent> {
  46. pub context: Context<T>,
  47. pub shortcuts: Arc<Mutex<HashMap<String, (AcceleratorId, GlobalShortcutWrapper)>>>,
  48. pub listeners: GlobalShortcutListeners,
  49. }
  50. // SAFETY: this is safe since the `Context` usage is guarded on `send_user_message`.
  51. #[allow(clippy::non_send_fields_in_send_ty)]
  52. unsafe impl<T: UserEvent> Sync for GlobalShortcutManagerHandle<T> {}
  53. impl<T: UserEvent> fmt::Debug for GlobalShortcutManagerHandle<T> {
  54. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  55. f.debug_struct("GlobalShortcutManagerHandle")
  56. .field("context", &self.context)
  57. .field("shortcuts", &self.shortcuts)
  58. .finish()
  59. }
  60. }
  61. impl<T: UserEvent> GlobalShortcutManager for GlobalShortcutManagerHandle<T> {
  62. fn is_registered(&self, accelerator: &str) -> Result<bool> {
  63. let (tx, rx) = channel();
  64. getter!(
  65. self,
  66. rx,
  67. Message::GlobalShortcut(GlobalShortcutMessage::IsRegistered(
  68. accelerator
  69. .parse()
  70. .map_err(|e: AcceleratorParseError| Error::GlobalShortcut(Box::new(
  71. AcceleratorParseErrorWrapper(e)
  72. )))?,
  73. tx
  74. ))
  75. )
  76. }
  77. fn register<F: Fn() + Send + 'static>(&mut self, accelerator: &str, handler: F) -> Result<()> {
  78. let wry_accelerator: Accelerator =
  79. accelerator.parse().map_err(|e: AcceleratorParseError| {
  80. Error::GlobalShortcut(Box::new(AcceleratorParseErrorWrapper(e)))
  81. })?;
  82. let id = wry_accelerator.clone().id();
  83. let (tx, rx) = channel();
  84. let shortcut = getter!(
  85. self,
  86. rx,
  87. Message::GlobalShortcut(GlobalShortcutMessage::Register(wry_accelerator, tx))
  88. )??;
  89. self.listeners.lock().unwrap().insert(id, Box::new(handler));
  90. self
  91. .shortcuts
  92. .lock()
  93. .unwrap()
  94. .insert(accelerator.into(), (id, shortcut));
  95. Ok(())
  96. }
  97. fn unregister_all(&mut self) -> Result<()> {
  98. let (tx, rx) = channel();
  99. getter!(
  100. self,
  101. rx,
  102. Message::GlobalShortcut(GlobalShortcutMessage::UnregisterAll(tx))
  103. )??;
  104. self.listeners.lock().unwrap().clear();
  105. self.shortcuts.lock().unwrap().clear();
  106. Ok(())
  107. }
  108. fn unregister(&mut self, accelerator: &str) -> Result<()> {
  109. if let Some((accelerator_id, shortcut)) = self.shortcuts.lock().unwrap().remove(accelerator) {
  110. let (tx, rx) = channel();
  111. getter!(
  112. self,
  113. rx,
  114. Message::GlobalShortcut(GlobalShortcutMessage::Unregister(shortcut, tx))
  115. )??;
  116. self.listeners.lock().unwrap().remove(&accelerator_id);
  117. }
  118. Ok(())
  119. }
  120. }
  121. pub fn handle_global_shortcut_message(
  122. message: GlobalShortcutMessage,
  123. global_shortcut_manager: &Rc<Mutex<WryShortcutManager>>,
  124. ) {
  125. match message {
  126. GlobalShortcutMessage::IsRegistered(accelerator, tx) => tx
  127. .send(
  128. global_shortcut_manager
  129. .lock()
  130. .unwrap()
  131. .is_registered(&accelerator),
  132. )
  133. .unwrap(),
  134. GlobalShortcutMessage::Register(accelerator, tx) => tx
  135. .send(
  136. global_shortcut_manager
  137. .lock()
  138. .unwrap()
  139. .register(accelerator)
  140. .map(GlobalShortcutWrapper)
  141. .map_err(|e| Error::GlobalShortcut(Box::new(e))),
  142. )
  143. .unwrap(),
  144. GlobalShortcutMessage::Unregister(shortcut, tx) => tx
  145. .send(
  146. global_shortcut_manager
  147. .lock()
  148. .unwrap()
  149. .unregister(shortcut.0)
  150. .map_err(|e| Error::GlobalShortcut(Box::new(e))),
  151. )
  152. .unwrap(),
  153. GlobalShortcutMessage::UnregisterAll(tx) => tx
  154. .send(
  155. global_shortcut_manager
  156. .lock()
  157. .unwrap()
  158. .unregister_all()
  159. .map_err(|e| Error::GlobalShortcut(Box::new(e))),
  160. )
  161. .unwrap(),
  162. }
  163. }