Эх сурвалжийг харах

refactor(core): global shortcut is now provided by `tao` (#2031)

Lucas Fernandes Nogueira 4 жил өмнө
parent
commit
3280c4aa91

+ 5 - 0
.changes/global-shortcut-refactor.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+**Breaking change**: The global shortcut API is now managed by `tao` so it cannot be accessed globally, the manager is now exposed on the `App` and `AppHandle` structs.

+ 6 - 0
.changes/runtime-global-shortcut.md

@@ -0,0 +1,6 @@
+---
+"tauri-runtime": patch
+"tauri-runtime-wry": patch
+---
+
+Adds global shortcut interfaces.

+ 1 - 1
Cargo.toml

@@ -25,7 +25,7 @@ members = [
 ]
 
 [patch.crates-io]
-tao = { git = "https://github.com/tauri-apps/tao", rev = "66360eea4ec6af8a52afcebb7700f486a0092168" }
+tao = { git = "https://github.com/tauri-apps/tao", rev = "01fc43b05ea41463d512c0e3497971edc543ac9d" }
 
 # default to small, optimized workspace release binaries
 [profile.release]

+ 211 - 26
core/tauri-runtime-wry/src/lib.rs

@@ -13,8 +13,8 @@ use tauri_runtime::{
     dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
     DetachedWindow, PendingWindow, WindowEvent,
   },
-  Dispatch, Error, Icon, Params, Result, RunEvent, RunIteration, Runtime, RuntimeHandle,
-  UserAttentionType,
+  Dispatch, Error, GlobalShortcutManager, Icon, Params, Result, RunEvent, RunIteration, Runtime,
+  RuntimeHandle, UserAttentionType,
 };
 
 #[cfg(feature = "menu")]
@@ -32,6 +32,7 @@ use tauri_utils::config::WindowConfig;
 use uuid::Uuid;
 use wry::{
   application::{
+    accelerator::{Accelerator, AcceleratorId},
     dpi::{
       LogicalPosition as WryLogicalPosition, LogicalSize as WryLogicalSize,
       PhysicalPosition as WryPhysicalPosition, PhysicalSize as WryPhysicalSize,
@@ -40,6 +41,7 @@ use wry::{
     event::{Event, WindowEvent as WryWindowEvent},
     event_loop::{ControlFlow, EventLoop, EventLoopProxy, EventLoopWindowTarget},
     monitor::MonitorHandle,
+    platform::global_shortcut::{GlobalShortcut, ShortcutManager as WryShortcutManager},
     window::{
       Fullscreen, Icon as WindowIcon, UserAttentionType as WryUserAttentionType, Window,
       WindowBuilder as WryWindowBuilder, WindowId,
@@ -76,6 +78,118 @@ type CreateWebviewHandler =
   Box<dyn FnOnce(&EventLoopWindowTarget<Message>) -> Result<WebviewWrapper> + Send>;
 type WindowEventHandler = Box<dyn Fn(&WindowEvent) + Send>;
 type WindowEventListeners = Arc<Mutex<HashMap<Uuid, WindowEventHandler>>>;
+type GlobalShortcutListeners = Arc<Mutex<HashMap<AcceleratorId, Box<dyn Fn() + Send>>>>;
+
+macro_rules! dispatcher_getter {
+  ($self: ident, $message: expr) => {{
+    if current_thread().id() == $self.context.main_thread_id
+      && !$self.context.is_event_loop_running.load(Ordering::Relaxed)
+    {
+      panic!("This API cannot be called when the event loop is not running");
+    }
+    let (tx, rx) = channel();
+    $self
+      .context
+      .proxy
+      .send_event(Message::Window($self.window_id, $message(tx)))
+      .map_err(|_| Error::FailedToSendMessage)?;
+    rx.recv().unwrap()
+  }};
+}
+
+macro_rules! shortcut_getter {
+  ($self: ident, $rx: expr, $message: expr) => {{
+    if current_thread().id() == $self.context.main_thread_id
+      && !$self.context.is_event_loop_running.load(Ordering::Relaxed)
+    {
+      panic!("This API cannot be called when the event loop is not running");
+    }
+    $self
+      .context
+      .proxy
+      .send_event($message)
+      .map_err(|_| Error::FailedToSendMessage)?;
+    $rx.recv().unwrap()
+  }};
+}
+
+#[derive(Debug, Clone)]
+struct GlobalShortcutWrapper(GlobalShortcut);
+
+unsafe impl Send for GlobalShortcutWrapper {}
+
+#[derive(Clone)]
+struct GlobalShortcutManagerContext {
+  main_thread_id: ThreadId,
+  is_event_loop_running: Arc<AtomicBool>,
+  proxy: EventLoopProxy<Message>,
+}
+
+/// Wrapper around [`WryShortcutManager`].
+#[derive(Clone)]
+pub struct GlobalShortcutManagerHandle {
+  context: GlobalShortcutManagerContext,
+  shortcuts: HashMap<String, (AcceleratorId, GlobalShortcutWrapper)>,
+  listeners: GlobalShortcutListeners,
+}
+
+impl GlobalShortcutManager for GlobalShortcutManagerHandle {
+  fn is_registered(&self, accelerator: &str) -> Result<bool> {
+    let (tx, rx) = channel();
+    Ok(shortcut_getter!(
+      self,
+      rx,
+      Message::GlobalShortcut(GlobalShortcutMessage::IsRegistered(
+        accelerator.parse().expect("invalid accelerator"),
+        tx
+      ))
+    ))
+  }
+
+  fn register<F: Fn() + Send + 'static>(&mut self, accelerator: &str, handler: F) -> Result<()> {
+    let wry_accelerator: Accelerator = accelerator.parse().expect("invalid accelerator");
+    let id = wry_accelerator.clone().id();
+    let (tx, rx) = channel();
+    let shortcut = shortcut_getter!(
+      self,
+      rx,
+      Message::GlobalShortcut(GlobalShortcutMessage::Register(wry_accelerator, tx))
+    )?;
+
+    self.listeners.lock().unwrap().insert(id, Box::new(handler));
+    self.shortcuts.insert(accelerator.into(), (id, shortcut));
+
+    Ok(())
+  }
+
+  fn unregister_all(&mut self) -> Result<()> {
+    let (tx, rx) = channel();
+    shortcut_getter!(
+      self,
+      rx,
+      Message::GlobalShortcut(GlobalShortcutMessage::UnregisterAll(tx))
+    )?;
+    self.listeners.lock().unwrap().clear();
+    self.shortcuts.clear();
+    Ok(())
+  }
+
+  fn unregister(&mut self, accelerator: &str) -> Result<()> {
+    if let Some((accelerator_id, shortcut)) = self.shortcuts.remove(accelerator) {
+      let (tx, rx) = channel();
+      shortcut_getter!(
+        self,
+        rx,
+        Message::GlobalShortcut(GlobalShortcutMessage::Unregister(
+          GlobalShortcutWrapper(shortcut.0),
+          tx
+        ))
+      )?;
+      self.listeners.lock().unwrap().remove(&accelerator_id);
+    }
+    Ok(())
+  }
+}
 
 /// Wrapper around a [`wry::application::window::Icon`] that can be created from an [`Icon`].
 pub struct WryIcon(WindowIcon);
@@ -267,7 +381,7 @@ pub struct WindowBuilderWrapper {
   inner: WryWindowBuilder,
   center: bool,
   #[cfg(feature = "menu")]
-  menu_items: HashMap<u32, WryCustomMenuItem>,
+  menu_items: HashMap<u16, WryCustomMenuItem>,
 }
 
 // safe since `menu_items` are read only here
@@ -510,7 +624,7 @@ enum WindowMessage {
   SetSkipTaskbar(bool),
   DragWindow,
   #[cfg(feature = "menu")]
-  UpdateMenuItem(u32, menu::MenuUpdate),
+  UpdateMenuItem(u16, menu::MenuUpdate),
 }
 
 #[derive(Debug, Clone)]
@@ -522,12 +636,20 @@ enum WebviewMessage {
 #[cfg(feature = "system-tray")]
 #[derive(Clone)]
 pub(crate) enum TrayMessage {
-  UpdateItem(u32, menu::MenuUpdate),
+  UpdateItem(u16, menu::MenuUpdate),
   UpdateIcon(Icon),
   #[cfg(windows)]
   Remove,
 }
 
+#[derive(Clone)]
+pub(crate) enum GlobalShortcutMessage {
+  IsRegistered(Accelerator, Sender<bool>),
+  Register(Accelerator, Sender<Result<GlobalShortcutWrapper>>),
+  Unregister(GlobalShortcutWrapper, Sender<Result<()>>),
+  UnregisterAll(Sender<Result<()>>),
+}
+
 #[derive(Clone)]
 pub(crate) enum Message {
   Task(MainTask),
@@ -536,6 +658,7 @@ pub(crate) enum Message {
   #[cfg(feature = "system-tray")]
   Tray(TrayMessage),
   CreateWebview(Arc<Mutex<Option<CreateWebviewHandler>>>, Sender<WindowId>),
+  GlobalShortcut(GlobalShortcutMessage),
 }
 
 #[derive(Clone)]
@@ -555,23 +678,6 @@ pub struct WryDispatcher {
   context: DispatcherContext,
 }
 
-macro_rules! dispatcher_getter {
-  ($self: ident, $message: expr) => {{
-    if current_thread().id() == $self.context.main_thread_id
-      && !$self.context.is_event_loop_running.load(Ordering::Relaxed)
-    {
-      panic!("This API cannot be called when the event loop is not running");
-    }
-    let (tx, rx) = channel();
-    $self
-      .context
-      .proxy
-      .send_event(Message::Window($self.window_id, $message(tx)))
-      .map_err(|_| Error::FailedToSendMessage)?;
-    rx.recv().unwrap()
-  }};
-}
-
 impl Dispatch for WryDispatcher {
   type Runtime = Wry;
   type WindowBuilder = WindowBuilderWrapper;
@@ -959,7 +1065,7 @@ impl Dispatch for WryDispatcher {
   }
 
   #[cfg(feature = "menu")]
-  fn update_menu_item(&self, id: u32, update: menu::MenuUpdate) -> Result<()> {
+  fn update_menu_item(&self, id: u16, update: menu::MenuUpdate) -> Result<()> {
     self
       .context
       .proxy
@@ -983,7 +1089,7 @@ struct WebviewWrapper {
   label: String,
   inner: WebView,
   #[cfg(feature = "menu")]
-  menu_items: HashMap<u32, WryCustomMenuItem>,
+  menu_items: HashMap<u16, WryCustomMenuItem>,
   #[cfg(feature = "menu")]
   is_menu_visible: AtomicBool,
 }
@@ -991,6 +1097,8 @@ struct WebviewWrapper {
 /// A Tauri [`Runtime`] wrapper around wry.
 pub struct Wry {
   main_thread_id: ThreadId,
+  global_shortcut_manager: Arc<Mutex<WryShortcutManager>>,
+  global_shortcut_manager_handle: GlobalShortcutManagerHandle,
   is_event_loop_running: Arc<AtomicBool>,
   event_loop: EventLoop<Message>,
   webviews: Arc<Mutex<HashMap<WindowId, WebviewWrapper>>>,
@@ -1050,14 +1158,30 @@ impl RuntimeHandle for WryHandle {
 impl Runtime for Wry {
   type Dispatcher = WryDispatcher;
   type Handle = WryHandle;
+  type GlobalShortcutManager = GlobalShortcutManagerHandle;
   #[cfg(feature = "system-tray")]
   type TrayHandler = SystemTrayHandle;
 
   fn new() -> Result<Self> {
     let event_loop = EventLoop::<Message>::with_user_event();
+    let global_shortcut_manager = WryShortcutManager::new(&event_loop);
+    let global_shortcut_listeners = GlobalShortcutListeners::default();
+    let proxy = event_loop.create_proxy();
+    let main_thread_id = current_thread().id();
+    let is_event_loop_running = Arc::new(AtomicBool::default());
     Ok(Self {
-      main_thread_id: current_thread().id(),
-      is_event_loop_running: Default::default(),
+      main_thread_id,
+      global_shortcut_manager: Arc::new(Mutex::new(global_shortcut_manager)),
+      global_shortcut_manager_handle: GlobalShortcutManagerHandle {
+        context: GlobalShortcutManagerContext {
+          main_thread_id,
+          is_event_loop_running: is_event_loop_running.clone(),
+          proxy,
+        },
+        shortcuts: Default::default(),
+        listeners: global_shortcut_listeners,
+      },
+      is_event_loop_running,
       event_loop,
       webviews: Default::default(),
       window_event_listeners: Default::default(),
@@ -1081,6 +1205,10 @@ impl Runtime for Wry {
     }
   }
 
+  fn global_shortcut_manager(&self) -> Self::GlobalShortcutManager {
+    self.global_shortcut_manager_handle.clone()
+  }
+
   fn create_window<P: Params<Runtime = Self>>(
     &self,
     pending: PendingWindow<P>,
@@ -1168,6 +1296,8 @@ impl Runtime for Wry {
     let menu_event_listeners = self.menu_event_listeners.clone();
     #[cfg(feature = "system-tray")]
     let tray_context = self.tray_context.clone();
+    let global_shortcut_manager = self.global_shortcut_manager.clone();
+    let global_shortcut_manager_handle = self.global_shortcut_manager_handle.clone();
 
     let mut iteration = RunIteration::default();
 
@@ -1186,6 +1316,8 @@ impl Runtime for Wry {
             callback: &callback,
             webviews: webviews.lock().expect("poisoned webview collection"),
             window_event_listeners: window_event_listeners.clone(),
+            global_shortcut_manager: global_shortcut_manager.clone(),
+            global_shortcut_manager_handle: global_shortcut_manager_handle.clone(),
             #[cfg(feature = "menu")]
             menu_event_listeners: menu_event_listeners.clone(),
             #[cfg(feature = "system-tray")]
@@ -1206,6 +1338,8 @@ impl Runtime for Wry {
     let menu_event_listeners = self.menu_event_listeners.clone();
     #[cfg(feature = "system-tray")]
     let tray_context = self.tray_context;
+    let global_shortcut_manager = self.global_shortcut_manager.clone();
+    let global_shortcut_manager_handle = self.global_shortcut_manager_handle.clone();
 
     self.event_loop.run(move |event, event_loop, control_flow| {
       handle_event_loop(
@@ -1216,6 +1350,8 @@ impl Runtime for Wry {
           callback: &callback,
           webviews: webviews.lock().expect("poisoned webview collection"),
           window_event_listeners: window_event_listeners.clone(),
+          global_shortcut_manager: global_shortcut_manager.clone(),
+          global_shortcut_manager_handle: global_shortcut_manager_handle.clone(),
           #[cfg(feature = "menu")]
           menu_event_listeners: menu_event_listeners.clone(),
           #[cfg(feature = "system-tray")]
@@ -1230,6 +1366,8 @@ struct EventLoopIterationContext<'a> {
   callback: &'a (dyn Fn(RunEvent) + 'static),
   webviews: MutexGuard<'a, HashMap<WindowId, WebviewWrapper>>,
   window_event_listeners: WindowEventListeners,
+  global_shortcut_manager: Arc<Mutex<WryShortcutManager>>,
+  global_shortcut_manager_handle: GlobalShortcutManagerHandle,
   #[cfg(feature = "menu")]
   menu_event_listeners: MenuEventListeners,
   #[cfg(feature = "system-tray")]
@@ -1246,6 +1384,8 @@ fn handle_event_loop(
     callback,
     mut webviews,
     window_event_listeners,
+    global_shortcut_manager,
+    global_shortcut_manager_handle,
     #[cfg(feature = "menu")]
     menu_event_listeners,
     #[cfg(feature = "system-tray")]
@@ -1254,6 +1394,13 @@ fn handle_event_loop(
   *control_flow = ControlFlow::Wait;
 
   match event {
+    Event::GlobalShortcutEvent(accelerator_id) => {
+      for (id, handler) in &*global_shortcut_manager_handle.listeners.lock().unwrap() {
+        if accelerator_id == *id {
+          handler();
+        }
+      }
+    }
     #[cfg(feature = "menu")]
     Event::MenuEvent {
       menu_id,
@@ -1504,6 +1651,44 @@ fn handle_event_loop(
           }
         }
       },
+      Message::GlobalShortcut(message) => match message {
+        GlobalShortcutMessage::IsRegistered(accelerator, tx) => tx
+          .send(
+            global_shortcut_manager
+              .lock()
+              .unwrap()
+              .is_registered(&accelerator),
+          )
+          .unwrap(),
+        GlobalShortcutMessage::Register(accelerator, tx) => tx
+          .send(
+            global_shortcut_manager
+              .lock()
+              .unwrap()
+              .register(accelerator)
+              .map(GlobalShortcutWrapper)
+              .map_err(|e| Error::GlobalShortcut(Box::new(e))),
+          )
+          .unwrap(),
+        GlobalShortcutMessage::Unregister(shortcut, tx) => tx
+          .send(
+            global_shortcut_manager
+              .lock()
+              .unwrap()
+              .unregister(shortcut.0)
+              .map_err(|e| Error::GlobalShortcut(Box::new(e))),
+          )
+          .unwrap(),
+        GlobalShortcutMessage::UnregisterAll(tx) => tx
+          .send(
+            global_shortcut_manager
+              .lock()
+              .unwrap()
+              .unregister_all()
+              .map_err(|e| Error::GlobalShortcut(Box::new(e))),
+          )
+          .unwrap(),
+      },
     },
     _ => (),
   }

+ 5 - 5
core/tauri-runtime-wry/src/menu.rs

@@ -45,7 +45,7 @@ pub type SystemTrayEventHandler = Box<dyn Fn(&SystemTrayEvent) + Send>;
 #[cfg(feature = "system-tray")]
 pub type SystemTrayEventListeners = Arc<Mutex<HashMap<Uuid, SystemTrayEventHandler>>>;
 #[cfg(feature = "system-tray")]
-pub type SystemTrayItems = Arc<Mutex<HashMap<u32, WryCustomMenuItem>>>;
+pub type SystemTrayItems = Arc<Mutex<HashMap<u16, WryCustomMenuItem>>>;
 
 #[cfg(feature = "system-tray")]
 #[derive(Clone)]
@@ -61,7 +61,7 @@ impl TrayHandle for SystemTrayHandle {
       .send_event(Message::Tray(TrayMessage::UpdateIcon(icon)))
       .map_err(|_| Error::FailedToSendMessage)
   }
-  fn update_item(&self, id: u32, update: MenuUpdate) -> Result<()> {
+  fn update_item(&self, id: u16, update: MenuUpdate) -> Result<()> {
     self
       .proxy
       .send_event(Message::Tray(TrayMessage::UpdateItem(id, update)))
@@ -147,7 +147,7 @@ impl<'a, I: MenuId> From<&'a CustomMenuItem<I>> for MenuItemAttributesWrapper<'a
       .with_selected(item.selected)
       .with_id(WryMenuId(item.id_value()));
     if let Some(accelerator) = item.keyboard_accelerator.as_ref() {
-      attributes = attributes.with_accelerators(accelerator);
+      attributes = attributes.with_accelerators(&accelerator.parse().expect("invalid accelerator"));
     }
     Self(attributes)
   }
@@ -191,7 +191,7 @@ impl From<SystemTrayMenuItem> for MenuItemWrapper {
 
 #[cfg(feature = "menu")]
 pub fn to_wry_menu<I: MenuId>(
-  custom_menu_items: &mut HashMap<u32, WryCustomMenuItem>,
+  custom_menu_items: &mut HashMap<u16, WryCustomMenuItem>,
   menu: Menu<I>,
 ) -> MenuBar {
   let mut wry_menu = MenuBar::new();
@@ -224,7 +224,7 @@ pub fn to_wry_menu<I: MenuId>(
 
 #[cfg(feature = "system-tray")]
 pub fn to_wry_context_menu<I: MenuId>(
-  custom_menu_items: &mut HashMap<u32, WryCustomMenuItem>,
+  custom_menu_items: &mut HashMap<u16, WryCustomMenuItem>,
   menu: SystemTrayMenu<I>,
 ) -> WryContextMenu {
   let mut tray_menu = WryContextMenu::new();

+ 49 - 2
core/tauri-runtime/src/lib.rs

@@ -116,6 +116,9 @@ pub enum Error {
   /// Failed to get monitor on window operation.
   #[error("failed to get monitor")]
   FailedToGetMonitor,
+  /// Global shortcut error.
+  #[error(transparent)]
+  GlobalShortcut(Box<dyn std::error::Error + Send>),
 }
 
 /// Result type.
@@ -193,7 +196,7 @@ pub enum RunEvent {
 
 /// A system tray event.
 pub enum SystemTrayEvent {
-  MenuItemClick(u32),
+  MenuItemClick(u16),
   LeftClick {
     position: PhysicalPosition<f64>,
     size: PhysicalSize<f64>,
@@ -228,12 +231,53 @@ pub trait RuntimeHandle: Send + Sized + Clone + 'static {
   fn remove_system_tray(&self) -> crate::Result<()>;
 }
 
+/// A global shortcut manager.
+pub trait GlobalShortcutManager {
+  /// Whether the application has registered the given `accelerator`.
+  ///
+  /// # Panics
+  ///
+  /// Panics if the app is not running yet, usually when called on the `tauri::Builder#setup` closure.
+  /// You can spawn a task to use the API using the `tauri::async_runtime` to prevent the panic.
+  fn is_registered(&self, accelerator: &str) -> crate::Result<bool>;
+
+  /// Register a global shortcut of `accelerator`.
+  ///
+  /// # Panics
+  ///
+  /// Panics if the app is not running yet, usually when called on the `tauri::Builder#setup` closure.
+  /// You can spawn a task to use the API using the `tauri::async_runtime` to prevent the panic.
+  fn register<F: Fn() + Send + 'static>(
+    &mut self,
+    accelerator: &str,
+    handler: F,
+  ) -> crate::Result<()>;
+
+  /// Unregister all accelerators registered by the manager instance.
+  ///
+  /// # Panics
+  ///
+  /// Panics if the app is not running yet, usually when called on the `tauri::Builder#setup` closure.
+  /// You can spawn a task to use the API using the `tauri::async_runtime` to prevent the panic.
+  fn unregister_all(&mut self) -> crate::Result<()>;
+
+  /// Unregister the provided `accelerator`.
+  ///
+  /// # Panics
+  ///
+  /// Panics if the app is not running yet, usually when called on the `tauri::Builder#setup` closure.
+  /// You can spawn a task to use the API using the `tauri::async_runtime` to prevent the panic.
+  fn unregister(&mut self, accelerator: &str) -> crate::Result<()>;
+}
+
 /// The webview runtime interface.
 pub trait Runtime: Sized + 'static {
   /// The message dispatcher.
   type Dispatcher: Dispatch<Runtime = Self>;
   /// The runtime handle type.
   type Handle: RuntimeHandle<Runtime = Self>;
+  /// The global shortcut manager type.
+  type GlobalShortcutManager: GlobalShortcutManager + Clone + Send;
   /// The tray handler type.
   #[cfg(feature = "system-tray")]
   type TrayHandler: menu::TrayHandle + Clone + Send;
@@ -244,6 +288,9 @@ pub trait Runtime: Sized + 'static {
   /// Gets a runtime handle.
   fn handle(&self) -> Self::Handle;
 
+  /// Gets the global shortcut manager.
+  fn global_shortcut_manager(&self) -> Self::GlobalShortcutManager;
+
   /// Create a new webview window.
   fn create_window<P: Params<Runtime = Self>>(
     &self,
@@ -436,5 +483,5 @@ pub trait Dispatch: Clone + Send + Sized + 'static {
 
   /// Applies the specified `update` to the menu item associated with the given `id`.
   #[cfg(feature = "menu")]
-  fn update_menu_item(&self, id: u32, update: menu::MenuUpdate) -> crate::Result<()>;
+  fn update_menu_item(&self, id: u16, update: menu::MenuUpdate) -> crate::Result<()>;
 }

+ 3 - 3
core/tauri-runtime/src/menu.rs

@@ -142,7 +142,7 @@ pub enum MenuUpdate {
 
 pub trait TrayHandle {
   fn set_icon(&self, icon: crate::Icon) -> crate::Result<()>;
-  fn update_item(&self, id: u32, update: MenuUpdate) -> crate::Result<()>;
+  fn update_item(&self, id: u16, update: MenuUpdate) -> crate::Result<()>;
 }
 
 /// A window menu.
@@ -248,10 +248,10 @@ impl<I: MenuId> CustomMenuItem<I> {
   }
 
   #[doc(hidden)]
-  pub fn id_value(&self) -> u32 {
+  pub fn id_value(&self) -> u16 {
     let mut s = DefaultHasher::new();
     self.id.hash(&mut s);
-    s.finish() as u32
+    s.finish() as u16
   }
 }
 

+ 1 - 1
core/tauri-runtime/src/window.rs

@@ -51,7 +51,7 @@ pub enum WindowEvent {
 #[derive(Serialize)]
 #[serde(rename_all = "camelCase")]
 pub struct MenuEvent {
-  pub menu_item_id: u32,
+  pub menu_item_id: u16,
 }
 
 /// A webview window that has yet to be built.

+ 1 - 4
core/tauri/Cargo.toml

@@ -66,9 +66,6 @@ clap = { version = "=3.0.0-beta.2", optional = true }
 # Notifications
 notify-rust = { version = "4.5", optional = true }
 
-# Global shortcut
-tauri-hotkey = { version = "0.1.2", optional = true }
-
 # HTTP
 reqwest = { version = "0.11", features = [ "json", "multipart" ], optional = true }
 bytes = { version = "1", features = [ "serde" ], optional = true }
@@ -133,4 +130,4 @@ dialog-save = [ "raw-window-handle" ]
 http-all = [ ]
 http-request = [ ]
 notification-all = [ "notify-rust" ]
-global-shortcut-all = [ "tauri-hotkey" ]
+global-shortcut-all = [ ]

+ 0 - 4
core/tauri/src/api/error.rs

@@ -76,10 +76,6 @@ pub enum Error {
   #[cfg(feature = "cli")]
   #[error("failed to parse CLI arguments: {0}")]
   ParseCliArguments(#[from] clap::Error),
-  /// Shortcut error.
-  #[cfg(global_shortcut_all)]
-  #[error("shortcut error: {0}")]
-  Shortcut(#[from] tauri_hotkey::Error),
   /// Shell error.
   #[error("shell error: {0}")]
   Shell(String),

+ 0 - 4
core/tauri/src/api/mod.rs

@@ -36,10 +36,6 @@ pub mod cli;
 #[cfg(feature = "cli")]
 pub use clap;
 
-/// Global shortcuts interface.
-#[cfg(global_shortcut_all)]
-pub mod shortcuts;
-
 /// The desktop notifications API module.
 #[cfg(notification_all)]
 pub mod notification;

+ 0 - 46
core/tauri/src/api/shortcuts.rs

@@ -1,46 +0,0 @@
-// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
-// SPDX-License-Identifier: Apache-2.0
-// SPDX-License-Identifier: MIT
-
-use tauri_hotkey::{parse_hotkey, HotkeyManager};
-
-/// The shortcut manager builder.
-#[derive(Default)]
-pub struct ShortcutManager(HotkeyManager);
-
-impl ShortcutManager {
-  /// Initializes a new instance of the shortcut manager.
-  pub fn new() -> Self {
-    Default::default()
-  }
-
-  /// Determines whether the given hotkey is registered or not.
-  pub fn is_registered(&self, shortcut: String) -> crate::api::Result<bool> {
-    let hotkey = parse_hotkey(&shortcut)?;
-    Ok(self.0.is_registered(&hotkey))
-  }
-
-  /// Registers a new shortcut handler.
-  pub fn register<H: FnMut() + Send + 'static>(
-    &mut self,
-    shortcut: String,
-    handler: H,
-  ) -> crate::api::Result<()> {
-    let hotkey = parse_hotkey(&shortcut)?;
-    self.0.register(hotkey, handler)?;
-    Ok(())
-  }
-
-  /// Unregister a previously registered shortcut handler.
-  pub fn unregister(&mut self, shortcut: String) -> crate::api::Result<()> {
-    let hotkey = parse_hotkey(&shortcut)?;
-    self.0.unregister(&hotkey)?;
-    Ok(())
-  }
-
-  /// Unregisters all shortcuts registered by this application.
-  pub fn unregister_all(&mut self) -> crate::api::Result<()> {
-    self.0.unregister_all()?;
-    Ok(())
-  }
-}

+ 23 - 2
core/tauri/src/app.rs

@@ -113,6 +113,7 @@ crate::manager::default_args! {
   pub struct AppHandle<P: Params> {
     runtime_handle: <P::Runtime as Runtime>::Handle,
     manager: WindowManager<P>,
+    global_shortcut_manager: <P::Runtime as Runtime>::GlobalShortcutManager,
     #[cfg(feature = "system-tray")]
     tray_handle: Option<tray::SystemTrayHandle<P>>,
   }
@@ -123,6 +124,7 @@ impl<P: Params> Clone for AppHandle<P> {
     Self {
       runtime_handle: self.runtime_handle.clone(),
       manager: self.manager.clone(),
+      global_shortcut_manager: self.global_shortcut_manager.clone(),
       #[cfg(feature = "system-tray")]
       tray_handle: self.tray_handle.clone(),
     }
@@ -147,6 +149,10 @@ impl<P: Params> ManagerBase<P> for AppHandle<P> {
   fn runtime(&self) -> RuntimeOrDispatch<'_, P> {
     RuntimeOrDispatch::RuntimeHandle(self.runtime_handle.clone())
   }
+
+  fn app_handle(&self) -> AppHandle<P> {
+    self.clone()
+  }
 }
 
 crate::manager::default_args! {
@@ -156,6 +162,7 @@ crate::manager::default_args! {
   pub struct App<P: Params> {
     runtime: Option<P::Runtime>,
     manager: WindowManager<P>,
+    global_shortcut_manager: <P::Runtime as Runtime>::GlobalShortcutManager,
     #[cfg(feature = "system-tray")]
     tray_handle: Option<tray::SystemTrayHandle<P>>,
     handle: AppHandle<P>,
@@ -171,6 +178,10 @@ impl<P: Params> ManagerBase<P> for App<P> {
   fn runtime(&self) -> RuntimeOrDispatch<'_, P> {
     RuntimeOrDispatch::Runtime(self.runtime.as_ref().unwrap())
   }
+
+  fn app_handle(&self) -> AppHandle<P> {
+    self.handle()
+  }
 }
 
 macro_rules! shared_app_impl {
@@ -209,6 +220,11 @@ macro_rules! shared_app_impl {
           .expect("tray not configured; use the `Builder#system_tray` API first.")
       }
 
+      /// Gets a copy of the global shortcut manager instance.
+      pub fn global_shortcut_manager(&self) -> <P::Runtime as Runtime>::GlobalShortcutManager {
+        self.global_shortcut_manager.clone()
+      }
+
       /// The path resolver for the application.
       pub fn path_resolver(&self) -> PathResolver {
         PathResolver {
@@ -700,15 +716,18 @@ where
 
     let runtime = R::new()?;
     let runtime_handle = runtime.handle();
+    let global_shortcut_manager = runtime.global_shortcut_manager();
 
     let mut app = App {
       runtime: Some(runtime),
       manager: manager.clone(),
+      global_shortcut_manager: global_shortcut_manager.clone(),
       #[cfg(feature = "system-tray")]
       tray_handle: None,
       handle: AppHandle {
         runtime_handle,
         manager,
+        global_shortcut_manager,
         #[cfg(feature = "system-tray")]
         tray_handle: None,
       },
@@ -726,9 +745,11 @@ where
     let mut main_window = None;
 
     for pending in self.pending_windows {
-      let pending = app.manager.prepare_window(pending, &pending_labels)?;
+      let pending = app
+        .manager
+        .prepare_window(app.handle.clone(), pending, &pending_labels)?;
       let detached = app.runtime.as_ref().unwrap().create_window(pending)?;
-      let _window = app.manager.attach_window(detached);
+      let _window = app.manager.attach_window(app.handle(), detached);
       #[cfg(feature = "updater")]
       if main_window.is_none() {
         main_window = Some(_window);

+ 3 - 3
core/tauri/src/app/tray.rs

@@ -13,7 +13,7 @@ pub use crate::{
 
 use std::{collections::HashMap, sync::Arc};
 
-pub(crate) fn get_menu_ids<I: MenuId>(map: &mut HashMap<u32, I>, menu: &SystemTrayMenu<I>) {
+pub(crate) fn get_menu_ids<I: MenuId>(map: &mut HashMap<u16, I>, menu: &SystemTrayMenu<I>) {
   for item in &menu.items {
     match item {
       SystemTrayMenuEntry::CustomItem(c) => {
@@ -78,7 +78,7 @@ pub enum SystemTrayEvent<I: MenuId> {
 crate::manager::default_args! {
   /// A handle to a system tray. Allows updating the context menu items.
   pub struct SystemTrayHandle<P: Params> {
-    pub(crate) ids: Arc<HashMap<u32, P::SystemTrayMenuId>>,
+    pub(crate) ids: Arc<HashMap<u16, P::SystemTrayMenuId>>,
     pub(crate) inner: <P::Runtime as Runtime>::TrayHandler,
   }
 }
@@ -95,7 +95,7 @@ impl<P: Params> Clone for SystemTrayHandle<P> {
 crate::manager::default_args! {
   /// A handle to a system tray menu item.
   pub struct SystemTrayMenuItemHandle<P: Params> {
-    id: u32,
+    id: u16,
     tray_handler: <P::Runtime as Runtime>::TrayHandler,
   }
 }

+ 27 - 32
core/tauri/src/endpoints/global_shortcut.rs

@@ -7,17 +7,7 @@ use crate::{Params, Window};
 use serde::Deserialize;
 
 #[cfg(global_shortcut_all)]
-use crate::{api::shortcuts::ShortcutManager, runtime::Dispatch};
-
-#[cfg(global_shortcut_all)]
-type ShortcutManagerHandle = std::sync::Arc<std::sync::Mutex<ShortcutManager>>;
-
-#[cfg(global_shortcut_all)]
-pub fn manager_handle() -> &'static ShortcutManagerHandle {
-  use once_cell::sync::Lazy;
-  static MANAGER: Lazy<ShortcutManagerHandle> = Lazy::new(Default::default);
-  &MANAGER
-}
+use crate::runtime::{GlobalShortcutManager, Runtime};
 
 /// The API descriptor.
 #[derive(Deserialize)]
@@ -39,16 +29,17 @@ pub enum Cmd {
 }
 
 #[cfg(global_shortcut_all)]
-fn register_shortcut<D: Dispatch>(
-  dispatcher: D,
-  manager: &mut ShortcutManager,
+fn register_shortcut<P: Params>(
+  window: Window<P>,
+  manager: &mut <P::Runtime as Runtime>::GlobalShortcutManager,
   shortcut: String,
   handler: String,
 ) -> crate::Result<()> {
-  manager.register(shortcut.clone(), move || {
-    let callback_string = crate::api::rpc::format_callback(handler.to_string(), &shortcut)
+  let accelerator = shortcut.clone();
+  manager.register(&shortcut, move || {
+    let callback_string = crate::api::rpc::format_callback(handler.to_string(), &accelerator)
       .expect("unable to serialize shortcut string to json");
-    let _ = dispatcher.eval_script(callback_string.as_str());
+    let _ = window.eval(callback_string.as_str());
   })?;
   Ok(())
 }
@@ -67,34 +58,38 @@ impl Cmd {
   pub fn run<P: Params>(self, window: Window<P>) -> crate::Result<InvokeResponse> {
     match self {
       Self::Register { shortcut, handler } => {
-        let dispatcher = window.dispatcher();
-        let mut manager = manager_handle().lock().unwrap();
-        register_shortcut(dispatcher, &mut manager, shortcut, handler)?;
+        let mut manager = window.app_handle.global_shortcut_manager();
+        register_shortcut(window, &mut manager, shortcut, handler)?;
         Ok(().into())
       }
       Self::RegisterAll { shortcuts, handler } => {
-        let dispatcher = window.dispatcher();
-        let mut manager = manager_handle().lock().unwrap();
+        let mut manager = window.app_handle.global_shortcut_manager();
         for shortcut in shortcuts {
-          let dispatch = dispatcher.clone();
-          register_shortcut(dispatch, &mut manager, shortcut, handler.clone())?;
+          register_shortcut(window.clone(), &mut manager, shortcut, handler.clone())?;
         }
         Ok(().into())
       }
       Self::Unregister { shortcut } => {
-        let mut manager = manager_handle().lock().unwrap();
-        manager.unregister(shortcut)?;
+        window
+          .app_handle
+          .global_shortcut_manager()
+          .unregister(&shortcut)?;
         Ok(().into())
       }
       Self::UnregisterAll => {
-        let mut manager = manager_handle().lock().unwrap();
-        manager.unregister_all()?;
+        window
+          .app_handle
+          .global_shortcut_manager()
+          .unregister_all()?;
         Ok(().into())
       }
-      Self::IsRegistered { shortcut } => {
-        let manager = manager_handle().lock().unwrap();
-        Ok(manager.is_registered(shortcut)?.into())
-      }
+      Self::IsRegistered { shortcut } => Ok(
+        window
+          .app_handle
+          .global_shortcut_manager()
+          .is_registered(&shortcut)?
+          .into(),
+      ),
     }
   }
 }

+ 6 - 3
core/tauri/src/lib.rs

@@ -340,7 +340,7 @@ pub trait Manager<P: Params>: sealed::ManagerBase<P> {
 
 /// Prevent implementation details from leaking out of the [`Manager`] trait.
 pub(crate) mod sealed {
-  use crate::manager::WindowManager;
+  use crate::{app::AppHandle, manager::WindowManager};
   use tauri_runtime::{Params, Runtime, RuntimeHandle};
 
   /// A running [`Runtime`] or a dispatcher to it.
@@ -361,6 +361,7 @@ pub(crate) mod sealed {
     fn manager(&self) -> &WindowManager<P>;
 
     fn runtime(&self) -> RuntimeOrDispatch<'_, P>;
+    fn app_handle(&self) -> AppHandle<P>;
 
     /// Creates a new [`Window`] on the [`Runtime`] and attaches it to the [`Manager`].
     fn create_new_window(
@@ -369,7 +370,9 @@ pub(crate) mod sealed {
     ) -> crate::Result<crate::Window<P>> {
       use crate::runtime::Dispatch;
       let labels = self.manager().labels().into_iter().collect::<Vec<_>>();
-      let pending = self.manager().prepare_window(pending, &labels)?;
+      let pending = self
+        .manager()
+        .prepare_window(self.app_handle(), pending, &labels)?;
       match self.runtime() {
         RuntimeOrDispatch::Runtime(runtime) => runtime.create_window(pending).map_err(Into::into),
         RuntimeOrDispatch::RuntimeHandle(handle) => {
@@ -379,7 +382,7 @@ pub(crate) mod sealed {
           dispatcher.create_window(pending).map_err(Into::into)
         }
       }
-      .map(|window| self.manager().attach_window(window))
+      .map(|window| self.manager().attach_window(self.app_handle(), window))
     }
   }
 }

+ 14 - 12
core/tauri/src/manager.rs

@@ -12,7 +12,7 @@ use crate::{
     path::{resolve_path, BaseDirectory},
     PackageInfo,
   },
-  app::{GlobalWindowEvent, GlobalWindowEventListener},
+  app::{AppHandle, GlobalWindowEvent, GlobalWindowEventListener},
   event::{Event, EventHandler, Listeners},
   hooks::{InvokeHandler, OnPageLoad, PageLoadPayload},
   plugin::PluginStore,
@@ -101,7 +101,7 @@ crate::manager::default_args! {
     menu: Option<Menu<P::MenuId>>,
     /// Maps runtime id to a strongly typed menu id.
     #[cfg(feature = "menu")]
-    menu_ids: HashMap<u32, P::MenuId>,
+    menu_ids: HashMap<u16, P::MenuId>,
     /// Menu event listeners to all windows.
     #[cfg(feature = "menu")]
     menu_event_listeners: Arc<Vec<GlobalMenuEventListener<P>>>,
@@ -209,7 +209,7 @@ impl<P: Params> Clone for WindowManager<P> {
 }
 
 #[cfg(feature = "menu")]
-fn get_menu_ids<I: MenuId>(map: &mut HashMap<u32, I>, menu: &Menu<I>) {
+fn get_menu_ids<I: MenuId>(map: &mut HashMap<u16, I>, menu: &Menu<I>) {
   for item in &menu.items {
     match item {
       MenuEntry::CustomItem(c) => {
@@ -280,7 +280,7 @@ impl<P: Params> WindowManager<P> {
 
   /// Get the menu ids mapper.
   #[cfg(feature = "menu")]
-  pub(crate) fn menu_ids(&self) -> HashMap<u32, P::MenuId> {
+  pub(crate) fn menu_ids(&self) -> HashMap<u16, P::MenuId> {
     self.inner.menu_ids.clone()
   }
 
@@ -371,10 +371,10 @@ impl<P: Params> WindowManager<P> {
     Ok(pending)
   }
 
-  fn prepare_rpc_handler(&self) -> WebviewRpcHandler<P> {
+  fn prepare_rpc_handler(&self, app_handle: AppHandle<P>) -> WebviewRpcHandler<P> {
     let manager = self.clone();
     Box::new(move |window, request| {
-      let window = Window::new(manager.clone(), window);
+      let window = Window::new(manager.clone(), window, app_handle.clone());
       let command = request.command.clone();
 
       let arg = request
@@ -446,12 +446,13 @@ impl<P: Params> WindowManager<P> {
     }
   }
 
-  fn prepare_file_drop(&self) -> FileDropHandler<P> {
+  fn prepare_file_drop(&self, app_handle: AppHandle<P>) -> FileDropHandler<P> {
     let manager = self.clone();
     Box::new(move |event, window| {
       let manager = manager.clone();
+      let app_handle = app_handle.clone();
       crate::async_runtime::block_on(async move {
-        let window = Window::new(manager.clone(), window);
+        let window = Window::new(manager.clone(), window, app_handle);
         let _ = match event {
           FileDropEvent::Hovered(paths) => {
             window.emit(&tauri_event::<P::Event>("tauri://file-drop"), Some(paths))
@@ -604,6 +605,7 @@ impl<P: Params> WindowManager<P> {
 
   pub fn prepare_window(
     &self,
+    app_handle: AppHandle<P>,
     mut pending: PendingWindow<P>,
     pending_labels: &[P::Label],
   ) -> crate::Result<PendingWindow<P>> {
@@ -627,19 +629,19 @@ impl<P: Params> WindowManager<P> {
     if is_local {
       let label = pending.label.clone();
       pending = self.prepare_pending_window(pending, label, pending_labels)?;
-      pending.rpc_handler = Some(self.prepare_rpc_handler());
+      pending.rpc_handler = Some(self.prepare_rpc_handler(app_handle.clone()));
     }
 
     if pending.webview_attributes.file_drop_handler_enabled {
-      pending.file_drop_handler = Some(self.prepare_file_drop());
+      pending.file_drop_handler = Some(self.prepare_file_drop(app_handle));
     }
     pending.url = url;
 
     Ok(pending)
   }
 
-  pub fn attach_window(&self, window: DetachedWindow<P>) -> Window<P> {
-    let window = Window::new(self.clone(), window);
+  pub fn attach_window(&self, app_handle: AppHandle<P>, window: DetachedWindow<P>) -> Window<P> {
+    let window = Window::new(self.clone(), window, app_handle);
 
     let window_ = window.clone();
     let window_event_listeners = self.inner.window_event_listeners.clone();

+ 17 - 4
core/tauri/src/window.rs

@@ -8,6 +8,7 @@ pub(crate) mod menu;
 
 use crate::{
   api::config::WindowUrl,
+  app::AppHandle,
   command::{CommandArg, CommandItem},
   event::{Event, EventHandler},
   manager::WindowManager,
@@ -85,11 +86,10 @@ crate::manager::default_args! {
   /// the same application.
   pub struct Window<P: Params> {
     /// The webview window created by the runtime.
-    /// ok
     window: DetachedWindow<P>,
-
     /// The manager to associate this webview window with.
     manager: WindowManager<P>,
+    pub(crate) app_handle: AppHandle<P>,
   }
 }
 
@@ -98,6 +98,7 @@ impl<P: Params> Clone for Window<P> {
     Self {
       window: self.window.clone(),
       manager: self.manager.clone(),
+      app_handle: self.app_handle.clone(),
     }
   }
 }
@@ -123,6 +124,10 @@ impl<P: Params> ManagerBase<P> for Window<P> {
     &self.manager
   }
 
+  fn app_handle(&self) -> AppHandle<P> {
+    self.app_handle.clone()
+  }
+
   fn runtime(&self) -> RuntimeOrDispatch<'_, P> {
     RuntimeOrDispatch::Dispatch(self.dispatcher())
   }
@@ -137,8 +142,16 @@ impl<'de, P: Params> CommandArg<'de, P> for Window<P> {
 
 impl<P: Params> Window<P> {
   /// Create a new window that is attached to the manager.
-  pub(crate) fn new(manager: WindowManager<P>, window: DetachedWindow<P>) -> Self {
-    Self { window, manager }
+  pub(crate) fn new(
+    manager: WindowManager<P>,
+    window: DetachedWindow<P>,
+    app_handle: AppHandle<P>,
+  ) -> Self {
+    Self {
+      window,
+      manager,
+      app_handle,
+    }
   }
 
   /// Creates a new webview window.

+ 2 - 2
core/tauri/src/window/menu.rs

@@ -27,7 +27,7 @@ impl<I: MenuId> MenuEvent<I> {
 crate::manager::default_args! {
   /// A handle to a system tray. Allows updating the context menu items.
   pub struct MenuHandle<P: Params> {
-    pub(crate) ids: HashMap<u32, P::MenuId>,
+    pub(crate) ids: HashMap<u16, P::MenuId>,
     pub(crate) dispatcher: <P::Runtime as Runtime>::Dispatcher,
   }
 }
@@ -44,7 +44,7 @@ impl<P: Params> Clone for MenuHandle<P> {
 crate::manager::default_args! {
   /// A handle to a system tray menu item.
   pub struct MenuItemHandle<P: Params> {
-    id: u32,
+    id: u16,
     dispatcher: <P::Runtime as Runtime>::Dispatcher,
   }
 }

+ 1 - 1
tooling/cli.js/test/jest/__tests__/template.spec.js

@@ -27,7 +27,7 @@ describe('[CLI] cli.js template', () => {
     const manifestFile = readFileSync(manifestPath).toString()
     writeFileSync(
       manifestPath,
-      `workspace = { }\n[patch.crates-io]\ntao = { git = "https://github.com/tauri-apps/tao", rev = "66360eea4ec6af8a52afcebb7700f486a0092168" }\n\n${manifestFile}`
+      `workspace = { }\n[patch.crates-io]\ntao = { git = "https://github.com/tauri-apps/tao", rev = "01fc43b05ea41463d512c0e3497971edc543ac9d" }\n\n${manifestFile}`
     )
 
     const { promise: buildPromise } = await build()