Skip to main content

rdesktop_core/
simulate.rs

1//! System-wide input simulation — programmatically inject keyboard and mouse
2//! events into the OS input stream, as if typed or clicked by a physical user.
3//!
4//! This is the "output" counterpart to [`crate::input`] (capture). Together
5//! they enable device-remapping / macro / automation scenarios — the
6//! "keyboard driver / mouse driver" side of a Logitech G-Hub-style tool:
7//! observe raw input via [`crate::input::GlobalInput`], then re-emit (or
8//! transform) it via [`InputSimulator`].
9//!
10//! - **Windows**: `SendInput` with `KEYBDINPUT` / `MOUSEINPUT` structures.
11//! - **macOS**: a `CGEvent`-based implementation (pending real macOS build;
12//!   the current target returns [`crate::error::RdesktopError::UnsupportedPlatform`]).
13
14use crate::error::Result;
15#[cfg(not(any(windows, target_os = "macos")))]
16use crate::error::RdesktopError;
17use crate::hotkeys::Key;
18use crate::input::MouseButton;
19
20/// Platform-independent input simulator.
21///
22/// Construct with [`InputSimulator::new`]; on unsupported platforms it returns
23/// [`crate::error::RdesktopError::UnsupportedPlatform`]. The simulator holds no
24/// threads and is cheap to keep around — create one and reuse it.
25pub struct InputSimulator {
26    inner: PlatformSim,
27}
28
29enum PlatformSim {
30    #[cfg(windows)]
31    Windows(crate::simulate_win::WinSim),
32    #[cfg(target_os = "macos")]
33    Macos(crate::simulate_mac::MacSim),
34    #[cfg(not(any(windows, target_os = "macos")))]
35    Unsupported,
36}
37
38impl InputSimulator {
39    /// Create a platform simulator. Errors on unsupported platforms.
40    pub fn new() -> Result<Self> {
41        Ok(Self {
42            inner: PlatformSim::new()?,
43        })
44    }
45
46    /// Hold a key down (no auto release).
47    pub fn press_key(&self, key: Key) -> Result<()> {
48        self.inner.press_key(key)
49    }
50
51    /// Release a held key.
52    pub fn release_key(&self, key: Key) -> Result<()> {
53        self.inner.release_key(key)
54    }
55
56    /// Press then immediately release a key.
57    pub fn tap_key(&self, key: Key) -> Result<()> {
58        self.press_key(key)?;
59        self.release_key(key)
60    }
61
62    /// Press every key in `keys`, then release them in reverse order. Use this
63    /// for chords such as `Ctrl+C`: pass `[Key::Letter('c')]` after holding Ctrl
64    /// via [`InputSimulator::press_key`], or build the whole chord from raw
65    /// keys and let this method do press/release ordering.
66    pub fn tap_combo(&self, keys: &[Key]) -> Result<()> {
67        self.inner.tap_combo(keys)
68    }
69
70    /// Type arbitrary text as if entered on the keyboard. Uses Unicode input
71    /// events, so it works for any character (including non-ASCII).
72    pub fn type_text(&self, text: &str) -> Result<()> {
73        self.inner.type_text(text)
74    }
75
76    /// Move the mouse. When `absolute` is true, `(x, y)` are screen coordinates;
77    /// otherwise they are relative mickeys (pixels).
78    pub fn move_mouse(&self, x: i32, y: i32, absolute: bool) -> Result<()> {
79        self.inner.move_mouse(x, y, absolute)
80    }
81
82    /// Hold a mouse button down.
83    pub fn press_mouse(&self, button: MouseButton) -> Result<()> {
84        self.inner.press_mouse(button)
85    }
86
87    /// Release a held mouse button.
88    pub fn release_mouse(&self, button: MouseButton) -> Result<()> {
89        self.inner.release_mouse(button)
90    }
91
92    /// Click (press + release) a mouse button at the current position.
93    pub fn click(&self, button: MouseButton) -> Result<()> {
94        self.press_mouse(button)?;
95        self.release_mouse(button)
96    }
97
98    /// Scroll the wheel by `delta` units (positive = away from the user).
99    pub fn scroll(&self, delta: i32) -> Result<()> {
100        self.inner.scroll(delta)
101    }
102}
103
104impl PlatformSim {
105    fn new() -> Result<Self> {
106        #[cfg(windows)]
107        {
108            Ok(PlatformSim::Windows(crate::simulate_win::WinSim::new()?))
109        }
110        #[cfg(target_os = "macos")]
111        {
112            Ok(PlatformSim::Macos(crate::simulate_mac::MacSim::new()?))
113        }
114        #[cfg(not(any(windows, target_os = "macos")))]
115        {
116            Err(RdesktopError::UnsupportedPlatform(
117                "system-wide input simulation is only supported on Windows and macOS".into(),
118            ))
119        }
120    }
121
122    fn press_key(&self, key: Key) -> Result<()> {
123        match self {
124            #[cfg(windows)]
125            PlatformSim::Windows(w) => w.press_key(key),
126            #[cfg(target_os = "macos")]
127            PlatformSim::Macos(m) => m.press_key(key),
128            #[cfg(not(any(windows, target_os = "macos")))]
129            PlatformSim::Unsupported => Err(RdesktopError::UnsupportedPlatform(
130                "system-wide input simulation is only supported on Windows and macOS".into(),
131            )),
132        }
133    }
134
135    fn release_key(&self, key: Key) -> Result<()> {
136        match self {
137            #[cfg(windows)]
138            PlatformSim::Windows(w) => w.release_key(key),
139            #[cfg(target_os = "macos")]
140            PlatformSim::Macos(m) => m.release_key(key),
141            #[cfg(not(any(windows, target_os = "macos")))]
142            PlatformSim::Unsupported => Err(RdesktopError::UnsupportedPlatform(
143                "system-wide input simulation is only supported on Windows and macOS".into(),
144            )),
145        }
146    }
147
148    fn tap_combo(&self, keys: &[Key]) -> Result<()> {
149        match self {
150            #[cfg(windows)]
151            PlatformSim::Windows(w) => w.tap_combo(keys),
152            #[cfg(target_os = "macos")]
153            PlatformSim::Macos(m) => m.tap_combo(keys),
154            #[cfg(not(any(windows, target_os = "macos")))]
155            PlatformSim::Unsupported => Err(RdesktopError::UnsupportedPlatform(
156                "system-wide input simulation is only supported on Windows and macOS".into(),
157            )),
158        }
159    }
160
161    fn type_text(&self, text: &str) -> Result<()> {
162        match self {
163            #[cfg(windows)]
164            PlatformSim::Windows(w) => w.type_text(text),
165            #[cfg(target_os = "macos")]
166            PlatformSim::Macos(m) => m.type_text(text),
167            #[cfg(not(any(windows, target_os = "macos")))]
168            PlatformSim::Unsupported => Err(RdesktopError::UnsupportedPlatform(
169                "system-wide input simulation is only supported on Windows and macOS".into(),
170            )),
171        }
172    }
173
174    fn move_mouse(&self, x: i32, y: i32, absolute: bool) -> Result<()> {
175        match self {
176            #[cfg(windows)]
177            PlatformSim::Windows(w) => w.move_mouse(x, y, absolute),
178            #[cfg(target_os = "macos")]
179            PlatformSim::Macos(m) => m.move_mouse(x, y, absolute),
180            #[cfg(not(any(windows, target_os = "macos")))]
181            PlatformSim::Unsupported => Err(RdesktopError::UnsupportedPlatform(
182                "system-wide input simulation is only supported on Windows and macOS".into(),
183            )),
184        }
185    }
186
187    fn press_mouse(&self, button: MouseButton) -> Result<()> {
188        match self {
189            #[cfg(windows)]
190            PlatformSim::Windows(w) => w.press_mouse(button),
191            #[cfg(target_os = "macos")]
192            PlatformSim::Macos(m) => m.press_mouse(button),
193            #[cfg(not(any(windows, target_os = "macos")))]
194            PlatformSim::Unsupported => Err(RdesktopError::UnsupportedPlatform(
195                "system-wide input simulation is only supported on Windows and macOS".into(),
196            )),
197        }
198    }
199
200    fn release_mouse(&self, button: MouseButton) -> Result<()> {
201        match self {
202            #[cfg(windows)]
203            PlatformSim::Windows(w) => w.release_mouse(button),
204            #[cfg(target_os = "macos")]
205            PlatformSim::Macos(m) => m.release_mouse(button),
206            #[cfg(not(any(windows, target_os = "macos")))]
207            PlatformSim::Unsupported => Err(RdesktopError::UnsupportedPlatform(
208                "system-wide input simulation is only supported on Windows and macOS".into(),
209            )),
210        }
211    }
212
213    fn scroll(&self, delta: i32) -> Result<()> {
214        match self {
215            #[cfg(windows)]
216            PlatformSim::Windows(w) => w.scroll(delta),
217            #[cfg(target_os = "macos")]
218            PlatformSim::Macos(m) => m.scroll(delta),
219            #[cfg(not(any(windows, target_os = "macos")))]
220            PlatformSim::Unsupported => Err(RdesktopError::UnsupportedPlatform(
221                "system-wide input simulation is only supported on Windows and macOS".into(),
222            )),
223        }
224    }
225}