ironrdp_input/
lib.rs

1#![doc = include_str!("../README.md")]
2#![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")]
3
4use std::collections::BTreeSet;
5
6use bitvec::array::BitArray;
7use bitvec::BitArr;
8use ironrdp_pdu::input::fast_path::{FastPathInputEvent, KeyboardFlags};
9use ironrdp_pdu::input::mouse::PointerFlags;
10use ironrdp_pdu::input::mouse_x::PointerXFlags;
11use ironrdp_pdu::input::{MousePdu, MouseXPdu};
12use smallvec::SmallVec;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15#[repr(u8)]
16pub enum MouseButton {
17    Left = 0,
18    Middle = 1,
19    Right = 2,
20    /// Typically Browser Back button
21    X1 = 3,
22    /// Typically Browser Forward button
23    X2 = 4,
24}
25
26impl MouseButton {
27    pub fn as_idx(self) -> usize {
28        self as usize
29    }
30
31    pub fn from_idx(idx: usize) -> Option<Self> {
32        match idx {
33            0 => Some(Self::Left),
34            1 => Some(Self::Middle),
35            2 => Some(Self::Right),
36            3 => Some(Self::X1),
37            4 => Some(Self::X2),
38            _ => None,
39        }
40    }
41
42    pub fn from_web_button(value: u8) -> Option<Self> {
43        // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button#value
44        match value {
45            0 => Some(Self::Left),
46            1 => Some(Self::Middle),
47            2 => Some(Self::Right),
48            3 => Some(Self::X1),
49            4 => Some(Self::X2),
50            _ => None,
51        }
52    }
53
54    pub fn from_native_button(value: u16) -> Option<Self> {
55        match value {
56            1 => Some(Self::Left),
57            2 => Some(Self::Middle),
58            3 => Some(Self::Right),
59            8 => Some(Self::X1),
60            9 => Some(Self::X2),
61            _ => None,
62        }
63    }
64}
65
66/// Keyboard scan code.
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
68pub struct Scancode {
69    code: u8,
70    extended: bool,
71}
72
73impl Scancode {
74    pub const fn from_u8(extended: bool, code: u8) -> Self {
75        Self { code, extended }
76    }
77
78    pub const fn from_u16(scancode: u16) -> Self {
79        let extended = scancode & 0xE000 == 0xE000;
80
81        #[allow(clippy::cast_possible_truncation)] // truncating on purpose
82        let code = scancode as u8;
83
84        Self { code, extended }
85    }
86
87    pub fn as_idx(self) -> usize {
88        if self.extended {
89            usize::from(self.code).checked_add(256).expect("never overflow")
90        } else {
91            usize::from(self.code)
92        }
93    }
94
95    pub fn as_u8(self) -> (bool, u8) {
96        (self.extended, self.code)
97    }
98
99    pub fn as_u16(self) -> u16 {
100        if self.extended {
101            u16::from(self.code) | 0xE000
102        } else {
103            u16::from(self.code)
104        }
105    }
106}
107
108impl From<(bool, u8)> for Scancode {
109    fn from((extended, code): (bool, u8)) -> Self {
110        Self::from_u8(extended, code)
111    }
112}
113
114impl From<u16> for Scancode {
115    fn from(code: u16) -> Self {
116        Self::from_u16(code)
117    }
118}
119
120/// Cursor position for a mouse device.
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
122pub struct MousePosition {
123    pub x: u16,
124    pub y: u16,
125}
126
127/// Mouse wheel rotations.
128#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
129pub struct WheelRotations {
130    pub is_vertical: bool,
131    pub rotation_units: i16,
132}
133
134#[derive(Debug, Clone)]
135pub enum Operation {
136    MouseButtonPressed(MouseButton),
137    MouseButtonReleased(MouseButton),
138    MouseMove(MousePosition),
139    WheelRotations(WheelRotations),
140    KeyPressed(Scancode),
141    KeyReleased(Scancode),
142    UnicodeKeyPressed(char),
143    UnicodeKeyReleased(char),
144}
145
146pub type KeyboardState = BitArr!(for 512);
147pub type MouseButtonsState = BitArr!(for 5);
148
149/// In-memory database for maintaining the current keyboard and mouse state.
150pub struct Database {
151    unicode_keyboard_state: BTreeSet<char>,
152    keyboard: KeyboardState,
153    mouse_buttons: MouseButtonsState,
154    mouse_position: MousePosition,
155}
156
157impl Default for Database {
158    fn default() -> Self {
159        Self::new()
160    }
161}
162
163impl Database {
164    pub fn new() -> Self {
165        Self {
166            keyboard: BitArray::ZERO,
167            mouse_buttons: BitArray::ZERO,
168            mouse_position: MousePosition { x: 0, y: 0 },
169            unicode_keyboard_state: BTreeSet::new(),
170        }
171    }
172
173    pub fn is_unicode_key_pressed(&self, character: char) -> bool {
174        self.unicode_keyboard_state.contains(&character)
175    }
176
177    pub fn is_key_pressed(&self, scancode: Scancode) -> bool {
178        self.keyboard
179            .get(scancode.as_idx())
180            .as_deref()
181            .copied()
182            .unwrap_or(false)
183    }
184
185    pub fn is_mouse_button_pressed(&self, button: MouseButton) -> bool {
186        self.mouse_buttons
187            .get(button.as_idx())
188            .as_deref()
189            .copied()
190            .unwrap_or(false)
191    }
192
193    pub fn mouse_position(&self) -> MousePosition {
194        self.mouse_position
195    }
196
197    pub fn keyboard_state(&self) -> &KeyboardState {
198        &self.keyboard
199    }
200
201    pub fn mouse_buttons_state(&self) -> &MouseButtonsState {
202        &self.mouse_buttons
203    }
204
205    /// Apply a transaction (list of operations) and returns a list of RDP input events to send.
206    ///
207    /// Operations that would cause no state change are ignored.
208    pub fn apply(&mut self, transaction: impl IntoIterator<Item = Operation>) -> SmallVec<[FastPathInputEvent; 2]> {
209        let mut events = SmallVec::new();
210
211        for operation in transaction {
212            match operation {
213                Operation::MouseButtonPressed(button) => {
214                    let was_pressed = self.mouse_buttons.replace(button.as_idx(), true);
215
216                    if !was_pressed {
217                        let event = match MouseButtonFlags::from(button) {
218                            MouseButtonFlags::Button(flags) => FastPathInputEvent::MouseEvent(MousePdu {
219                                flags: PointerFlags::DOWN | flags,
220                                number_of_wheel_rotation_units: 0,
221                                x_position: self.mouse_position.x,
222                                y_position: self.mouse_position.y,
223                            }),
224                            MouseButtonFlags::Pointer(flags) => FastPathInputEvent::MouseEventEx(MouseXPdu {
225                                flags: PointerXFlags::DOWN | flags,
226                                x_position: self.mouse_position.x,
227                                y_position: self.mouse_position.y,
228                            }),
229                        };
230
231                        events.push(event)
232                    }
233                }
234                Operation::MouseButtonReleased(button) => {
235                    let was_pressed = self.mouse_buttons.replace(button.as_idx(), false);
236
237                    if was_pressed {
238                        let event = match MouseButtonFlags::from(button) {
239                            MouseButtonFlags::Button(flags) => FastPathInputEvent::MouseEvent(MousePdu {
240                                flags,
241                                number_of_wheel_rotation_units: 0,
242                                x_position: self.mouse_position.x,
243                                y_position: self.mouse_position.y,
244                            }),
245                            MouseButtonFlags::Pointer(flags) => FastPathInputEvent::MouseEventEx(MouseXPdu {
246                                flags,
247                                x_position: self.mouse_position.x,
248                                y_position: self.mouse_position.y,
249                            }),
250                        };
251
252                        events.push(event)
253                    }
254                }
255                Operation::MouseMove(position) => {
256                    if position != self.mouse_position {
257                        self.mouse_position = position;
258                        events.push(FastPathInputEvent::MouseEvent(MousePdu {
259                            flags: PointerFlags::MOVE,
260                            number_of_wheel_rotation_units: 0,
261                            x_position: position.x,
262                            y_position: position.y,
263                        }))
264                    }
265                }
266                Operation::WheelRotations(rotations) => events.push(FastPathInputEvent::MouseEvent(MousePdu {
267                    flags: if rotations.is_vertical {
268                        PointerFlags::VERTICAL_WHEEL
269                    } else {
270                        PointerFlags::HORIZONTAL_WHEEL
271                    },
272                    number_of_wheel_rotation_units: rotations.rotation_units,
273                    x_position: self.mouse_position.x,
274                    y_position: self.mouse_position.y,
275                })),
276                Operation::KeyPressed(scancode) => {
277                    let was_pressed = self.keyboard.replace(scancode.as_idx(), true);
278
279                    let mut flags = KeyboardFlags::empty();
280
281                    if scancode.extended {
282                        flags |= KeyboardFlags::EXTENDED
283                    };
284
285                    if was_pressed {
286                        events.push(FastPathInputEvent::KeyboardEvent(
287                            flags | KeyboardFlags::RELEASE,
288                            scancode.code,
289                        ));
290                    }
291
292                    events.push(FastPathInputEvent::KeyboardEvent(flags, scancode.code));
293                }
294                Operation::KeyReleased(scancode) => {
295                    let was_pressed = self.keyboard.replace(scancode.as_idx(), false);
296
297                    let mut flags = KeyboardFlags::RELEASE;
298
299                    if scancode.extended {
300                        flags |= KeyboardFlags::EXTENDED
301                    };
302
303                    if was_pressed {
304                        events.push(FastPathInputEvent::KeyboardEvent(flags, scancode.code));
305                    }
306                }
307                Operation::UnicodeKeyPressed(character) => {
308                    let was_pressed = !self.unicode_keyboard_state.insert(character);
309
310                    let mut utf16_buffer = [0u16; 2];
311                    let utf16_code_units = character.encode_utf16(&mut utf16_buffer);
312
313                    if was_pressed {
314                        for code in utf16_code_units.iter() {
315                            events.push(FastPathInputEvent::UnicodeKeyboardEvent(KeyboardFlags::RELEASE, *code));
316                        }
317                    }
318
319                    for code in utf16_code_units {
320                        events.push(FastPathInputEvent::UnicodeKeyboardEvent(KeyboardFlags::empty(), *code));
321                    }
322                }
323                Operation::UnicodeKeyReleased(character) => {
324                    let was_pressed = self.unicode_keyboard_state.remove(&character);
325
326                    let mut utf16_buffer = [0u16; 2];
327                    let utf16_code_units = character.encode_utf16(&mut utf16_buffer);
328
329                    if was_pressed {
330                        for code in utf16_code_units {
331                            events.push(FastPathInputEvent::UnicodeKeyboardEvent(KeyboardFlags::RELEASE, *code));
332                        }
333                    }
334                }
335            }
336        }
337
338        events
339    }
340
341    /// Releases all keys and buttons. Returns a list of RDP input events to send.
342    pub fn release_all(&mut self) -> SmallVec<[FastPathInputEvent; 2]> {
343        let mut events = SmallVec::new();
344
345        for idx in self.mouse_buttons.iter_ones() {
346            let button = MouseButton::from_idx(idx).expect("in-range index");
347
348            let event = match MouseButtonFlags::from(button) {
349                MouseButtonFlags::Button(flags) => FastPathInputEvent::MouseEvent(MousePdu {
350                    flags,
351                    number_of_wheel_rotation_units: 0,
352                    x_position: self.mouse_position.x,
353                    y_position: self.mouse_position.y,
354                }),
355                MouseButtonFlags::Pointer(flags) => FastPathInputEvent::MouseEventEx(MouseXPdu {
356                    flags,
357                    x_position: self.mouse_position.x,
358                    y_position: self.mouse_position.y,
359                }),
360            };
361
362            events.push(event)
363        }
364
365        for idx in self.keyboard.iter_ones() {
366            let (scancode, extended) = if idx >= 256 {
367                let extended_code = idx.checked_sub(256).expect("never underflow");
368                (u8::try_from(extended_code).unwrap(), true)
369            } else {
370                (u8::try_from(idx).unwrap(), false)
371            };
372
373            let mut flags = KeyboardFlags::RELEASE;
374
375            if extended {
376                flags |= KeyboardFlags::EXTENDED
377            };
378
379            events.push(FastPathInputEvent::KeyboardEvent(flags, scancode));
380        }
381
382        for character in core::mem::take(&mut self.unicode_keyboard_state).into_iter() {
383            let mut utf16_buffer = [0u16; 2];
384            let utf16_code_units = character.encode_utf16(&mut utf16_buffer);
385
386            for code in utf16_code_units {
387                events.push(FastPathInputEvent::UnicodeKeyboardEvent(KeyboardFlags::RELEASE, *code));
388            }
389        }
390
391        self.mouse_buttons = BitArray::ZERO;
392        self.keyboard = BitArray::ZERO;
393
394        events
395    }
396}
397
398/// Returns the RDP input event to send in order to synchronize lock keys.
399pub fn synchronize_event(scroll_lock: bool, num_lock: bool, caps_lock: bool, kana_lock: bool) -> FastPathInputEvent {
400    use ironrdp_pdu::input::fast_path::SynchronizeFlags;
401
402    let mut flags = SynchronizeFlags::empty();
403
404    if scroll_lock {
405        flags |= SynchronizeFlags::SCROLL_LOCK;
406    }
407
408    if num_lock {
409        flags |= SynchronizeFlags::NUM_LOCK;
410    }
411
412    if caps_lock {
413        flags |= SynchronizeFlags::CAPS_LOCK;
414    }
415
416    if kana_lock {
417        flags |= SynchronizeFlags::KANA_LOCK;
418    }
419
420    FastPathInputEvent::SyncEvent(flags)
421}
422
423enum MouseButtonFlags {
424    Button(PointerFlags),
425    Pointer(PointerXFlags),
426}
427
428impl From<MouseButton> for MouseButtonFlags {
429    fn from(value: MouseButton) -> Self {
430        match value {
431            MouseButton::Left => Self::Button(PointerFlags::LEFT_BUTTON),
432            MouseButton::Middle => Self::Button(PointerFlags::MIDDLE_BUTTON_OR_WHEEL),
433            MouseButton::Right => Self::Button(PointerFlags::RIGHT_BUTTON),
434            MouseButton::X1 => Self::Pointer(PointerXFlags::BUTTON1),
435            MouseButton::X2 => Self::Pointer(PointerXFlags::BUTTON2),
436        }
437    }
438}