Browse Source

Provide `HotkeyState` on Global Shortcut Events (#3822)

* Only react to hotkeys that are pressed

The global shortcuts are otherwise also triggering when the hotkey is
being released. This affects at least Windows.

* Provide `HotkeyState` on Global Shortcut Events

Instead of ignoring the events where the shortcut is released, the state
is now provided as an argument to the event handler.
Christopher Serr 2 months ago
parent
commit
3da821d184

+ 6 - 2
examples/overlay.rs

@@ -6,7 +6,7 @@
 //! We also add a global shortcut to toggle the overlay on and off, so you could build a raycast-type app with this.
 
 use dioxus::desktop::{
-    tao::dpi::PhysicalPosition, use_global_shortcut, LogicalSize, WindowBuilder,
+    tao::dpi::PhysicalPosition, use_global_shortcut, HotKeyState, LogicalSize, WindowBuilder,
 };
 use dioxus::prelude::*;
 
@@ -19,7 +19,11 @@ fn main() {
 fn app() -> Element {
     let mut show_overlay = use_signal(|| true);
 
-    _ = use_global_shortcut("cmd+g", move || show_overlay.toggle());
+    _ = use_global_shortcut("cmd+g", move |state| {
+        if state == HotKeyState::Pressed {
+            show_overlay.toggle();
+        }
+    });
 
     rsx! {
         document::Link {

+ 6 - 2
examples/shortcut.rs

@@ -5,7 +5,7 @@
 //!
 //! These are *global* shortcuts, so they will work even if your app is not in focus.
 
-use dioxus::desktop::use_global_shortcut;
+use dioxus::desktop::{use_global_shortcut, HotKeyState};
 use dioxus::prelude::*;
 
 fn main() {
@@ -15,7 +15,11 @@ fn main() {
 fn app() -> Element {
     let mut toggled = use_signal(|| false);
 
-    _ = use_global_shortcut("ctrl+s", move || toggled.toggle());
+    _ = use_global_shortcut("ctrl+s", move |state| {
+        if state == HotKeyState::Pressed {
+            toggled.toggle();
+        }
+    });
 
     rsx!("toggle: {toggled}")
 }

+ 2 - 2
packages/desktop/src/desktop_context.rs

@@ -4,7 +4,7 @@ use crate::{
     file_upload::NativeFileHover,
     ipc::UserWindowEvent,
     query::QueryEngine,
-    shortcut::{HotKey, ShortcutHandle, ShortcutRegistryError},
+    shortcut::{HotKey, HotKeyState, ShortcutHandle, ShortcutRegistryError},
     webview::WebviewInstance,
     AssetRequest, Config, WryEventHandler,
 };
@@ -214,7 +214,7 @@ impl DesktopService {
     pub fn create_shortcut(
         &self,
         hotkey: HotKey,
-        callback: impl FnMut() + 'static,
+        callback: impl FnMut(HotKeyState) + 'static,
     ) -> Result<ShortcutHandle, ShortcutRegistryError> {
         self.shared
             .shortcut_manager

+ 5 - 4
packages/desktop/src/hooks.rs

@@ -2,7 +2,7 @@ use std::rc::Rc;
 
 use crate::{
     assets::*, ipc::UserWindowEvent, shortcut::IntoAccelerator, window, DesktopContext,
-    ShortcutHandle, ShortcutRegistryError, WryEventHandler,
+    HotKeyState, ShortcutHandle, ShortcutRegistryError, WryEventHandler,
 };
 use dioxus_core::{
     prelude::{consume_context, use_hook_with_cleanup},
@@ -116,13 +116,14 @@ pub fn use_asset_handler(
 /// Get a closure that executes any JavaScript in the WebView context.
 pub fn use_global_shortcut(
     accelerator: impl IntoAccelerator,
-    mut handler: impl FnMut() + 'static,
+    handler: impl FnMut(HotKeyState) + 'static,
 ) -> Result<ShortcutHandle, ShortcutRegistryError> {
     // wrap the user's handler in something that keeps it up to date
-    let cb = use_callback(move |_| handler());
+    let cb = use_callback(handler);
 
     use_hook_with_cleanup(
-        move || window().create_shortcut(accelerator.accelerator(), move || cb(())),
+        #[allow(clippy::redundant_closure)]
+        move || window().create_shortcut(accelerator.accelerator(), move |state| cb(state)),
         |handle| {
             if let Ok(handle) = handle {
                 handle.remove();

+ 1 - 1
packages/desktop/src/lib.rs

@@ -51,5 +51,5 @@ pub use config::{Config, WindowCloseBehaviour};
 pub use desktop_context::{window, DesktopContext, DesktopService, WeakDesktopContext};
 pub use event_handlers::WryEventHandler;
 pub use hooks::*;
-pub use shortcut::{ShortcutHandle, ShortcutRegistryError};
+pub use shortcut::{HotKeyState, ShortcutHandle, ShortcutRegistryError};
 pub use wry::RequestAsyncResponder;

+ 10 - 0
packages/desktop/src/mobile_shortcut.rs

@@ -78,6 +78,16 @@ impl fmt::Display for HotkeyError {
 
 pub struct GlobalHotKeyEvent {
     pub id: u32,
+    pub state: HotKeyState,
+}
+
+/// Describes the state of the hotkey.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub enum HotKeyState {
+    /// The hotkey is pressed.
+    Pressed,
+    /// The hotkey is released.
+    Released,
 }
 
 pub(crate) type Code = dioxus_html::input_data::keyboard_types::Code;

+ 4 - 4
packages/desktop/src/shortcut.rs

@@ -9,7 +9,7 @@
 ))]
 pub use global_hotkey::{
     hotkey::{Code, HotKey},
-    Error as HotkeyError, GlobalHotKeyEvent, GlobalHotKeyManager,
+    Error as HotkeyError, GlobalHotKeyEvent, GlobalHotKeyManager, HotKeyState,
 };
 
 #[cfg(any(target_os = "ios", target_os = "android"))]
@@ -53,7 +53,7 @@ pub(crate) struct ShortcutRegistry {
 struct ShortcutInner {
     #[allow(unused)]
     shortcut: HotKey,
-    callbacks: Slab<Box<dyn FnMut()>>,
+    callbacks: Slab<Box<dyn FnMut(HotKeyState)>>,
 }
 
 impl ShortcutRegistry {
@@ -68,7 +68,7 @@ impl ShortcutRegistry {
     pub(crate) fn call_handlers(&self, id: GlobalHotKeyEvent) {
         if let Some(ShortcutInner { callbacks, .. }) = self.shortcuts.borrow_mut().get_mut(&id.id) {
             for (_, callback) in callbacks.iter_mut() {
-                (callback)();
+                (callback)(id.state);
             }
         }
     }
@@ -76,7 +76,7 @@ impl ShortcutRegistry {
     pub(crate) fn add_shortcut(
         &self,
         hotkey: HotKey,
-        callback: Box<dyn FnMut()>,
+        callback: Box<dyn FnMut(HotKeyState)>,
     ) -> Result<ShortcutHandle, ShortcutRegistryError> {
         let accelerator_id = hotkey.clone().id();