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 X1 = 3,
22 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 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#[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)] 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
122pub struct MousePosition {
123 pub x: u16,
124 pub y: u16,
125}
126
127#[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
149pub 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 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 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
398pub 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}