Skip to main content

rdesktop_core/
input.rs

1//! Global input hooks — raw keyboard and mouse events captured system-wide,
2//! regardless of which window is focused.
3//!
4//! This is the foundation for "keyboard driver / mouse driver" scenarios
5//! (e.g. a Logitech G-Hub-style remapper): rdesktop owns the low-level hook and
6//! forwards every event to a [`GlobalInputHandler`], where application code can
7//! observe, transform, or suppress it.
8//!
9//! - **Windows**: `SetWindowsHookExW` with `WH_KEYBOARD_LL` + `WH_MOUSE_LL` on a
10//!   dedicated message-pump thread.
11//! - **macOS**: a `CGEventTap` on `kCGHIDEventTap` (see `input_mac`).
12
13use crate::error::Result;
14use crate::hotkeys::{Key, Modifiers};
15use serde::{Deserialize, Serialize};
16use std::sync::{Arc, Mutex};
17
18/// Pressed / released state for a key or mouse button.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
20pub enum KeyState {
21    Pressed,
22    Released,
23}
24
25/// Mouse buttons reported by the low-level hook.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
27pub enum MouseButton {
28    Left,
29    Right,
30    Middle,
31    /// X1 (typically "back")
32    X1,
33    /// X2 (typically "forward")
34    X2,
35}
36
37/// A single global input event.
38#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
39pub enum GlobalInputEvent {
40    Keyboard {
41        key: Key,
42        state: KeyState,
43        modifiers: Modifiers,
44    },
45    Mouse {
46        button: MouseButton,
47        state: KeyState,
48        x: i32,
49        y: i32,
50    },
51    MouseMove {
52        x: i32,
53        y: i32,
54    },
55}
56
57/// Callback invoked for every global input event.
58pub trait GlobalInputHandler: Send + Sync {
59    fn on_event(&self, event: GlobalInputEvent);
60}
61
62/// Platform-independent manager for global input hooks.
63pub struct GlobalInput {
64    handler: Arc<dyn GlobalInputHandler>,
65    inner: Mutex<Option<PlatformInput>>,
66    /// When false, mouse-move spam is suppressed (movement events are high-frequency).
67    include_mouse_move: bool,
68}
69
70impl GlobalInput {
71    pub fn new(handler: Arc<dyn GlobalInputHandler>) -> Self {
72        Self {
73            handler,
74            inner: Mutex::new(None),
75            include_mouse_move: false,
76        }
77    }
78
79    /// Include `MouseMove` events (high frequency — off by default).
80    pub fn with_mouse_move(mut self, on: bool) -> Self {
81        self.include_mouse_move = on;
82        self
83    }
84
85    /// Begin listening. Spawns the platform hook machinery.
86    pub fn start(&self) -> Result<()> {
87        let mut guard = self.inner.lock().unwrap();
88        if guard.is_some() {
89            return Ok(());
90        }
91        let p = PlatformInput::start(self.handler.clone(), self.include_mouse_move)?;
92        *guard = Some(p);
93        Ok(())
94    }
95
96    /// Stop listening (also happens on drop).
97    pub fn stop(&self) {
98        if let Some(p) = self.inner.lock().unwrap().take() {
99            p.stop();
100        }
101    }
102}
103
104impl Drop for GlobalInput {
105    fn drop(&mut self) {
106        if let Some(p) = self.inner.lock().unwrap().take() {
107            p.stop();
108        }
109    }
110}
111
112enum PlatformInput {
113    #[cfg(windows)]
114    Windows(crate::input_win::WinInput),
115    #[cfg(target_os = "macos")]
116    Macos(crate::input_mac::MacInput),
117    #[cfg(not(any(windows, target_os = "macos")))]
118    Unsupported,
119}
120
121impl PlatformInput {
122    fn start(handler: Arc<dyn GlobalInputHandler>, include_mouse_move: bool) -> Result<Self> {
123        #[cfg(windows)]
124        {
125            Ok(PlatformInput::Windows(crate::input_win::WinInput::start(
126                handler,
127                include_mouse_move,
128            )?))
129        }
130        #[cfg(target_os = "macos")]
131        {
132            Ok(PlatformInput::Macos(crate::input_mac::MacInput::start(
133                handler,
134                include_mouse_move,
135            )?))
136        }
137        #[cfg(not(any(windows, target_os = "macos")))]
138        {
139            let _ = (handler, include_mouse_move);
140            Err(crate::error::RdesktopError::UnsupportedPlatform(
141                "global input hooks are only supported on Windows and macOS".into(),
142            ))
143        }
144    }
145
146    fn stop(self) {
147        match self {
148            #[cfg(windows)]
149            PlatformInput::Windows(w) => w.stop(),
150            #[cfg(target_os = "macos")]
151            PlatformInput::Macos(m) => m.stop(),
152            #[cfg(not(any(windows, target_os = "macos")))]
153            PlatformInput::Unsupported => {}
154        }
155    }
156}