Skip to main content

ironrdp_ainput/
lib.rs

1#![cfg_attr(doc, doc = include_str!("../README.md"))]
2#![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")]
3
4use bitflags::bitflags;
5use ironrdp_core::{
6    Decode, DecodeResult, Encode, EncodeResult, ReadCursor, WriteCursor, ensure_fixed_part_size, invalid_field_err,
7};
8use ironrdp_dvc::DvcEncode;
9use num_derive::FromPrimitive;
10use num_traits::FromPrimitive as _;
11// Advanced Input channel as defined from Freerdp, [here]:
12//
13// [here]: https://github.com/FreeRDP/FreeRDP/blob/master/include/freerdp/channels/ainput.h
14
15const VERSION_MAJOR: u32 = 1;
16const VERSION_MINOR: u32 = 0;
17
18pub const CHANNEL_NAME: &str = "FreeRDP::Advanced::Input";
19
20bitflags! {
21    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
22    pub struct MouseEventFlags: u64 {
23        const WHEEL = 0x0000_0001;
24        const MOVE = 0x0000_0004;
25        const DOWN = 0x0000_0008;
26
27        const REL = 0x0000_0010;
28        const HAVE_REL = 0x0000_0020;
29        const BUTTON1 = 0x0000_1000; /* left */
30        const BUTTON2 = 0x0000_2000; /* right */
31        const BUTTON3 = 0x0000_4000; /* middle */
32
33        const XBUTTON1 = 0x0000_0100;
34        const XBUTTON2 = 0x0000_0200;
35
36        const _ = !0;
37    }
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct VersionPdu {
42    major_version: u32,
43    minor_version: u32,
44}
45
46impl VersionPdu {
47    const NAME: &'static str = "AInputVersionPdu";
48
49    const FIXED_PART_SIZE: usize = 4 /* MajorVersion */ + 4 /* MinorVersion */;
50
51    pub fn new() -> Self {
52        Self {
53            major_version: VERSION_MAJOR,
54            minor_version: VERSION_MINOR,
55        }
56    }
57}
58
59impl Default for VersionPdu {
60    fn default() -> Self {
61        Self::new()
62    }
63}
64
65impl Encode for VersionPdu {
66    fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
67        ensure_fixed_part_size!(in: dst);
68
69        dst.write_u32(self.major_version);
70        dst.write_u32(self.minor_version);
71
72        Ok(())
73    }
74
75    fn name(&self) -> &'static str {
76        Self::NAME
77    }
78
79    fn size(&self) -> usize {
80        Self::FIXED_PART_SIZE
81    }
82}
83
84impl<'de> Decode<'de> for VersionPdu {
85    fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
86        ensure_fixed_part_size!(in: src);
87
88        let major_version = src.read_u32();
89        let minor_version = src.read_u32();
90
91        Ok(Self {
92            major_version,
93            minor_version,
94        })
95    }
96}
97
98#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive)]
99#[repr(u16)]
100pub enum ServerPduType {
101    Version = 0x01,
102}
103
104impl ServerPduType {
105    #[expect(
106        clippy::as_conversions,
107        reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive"
108    )]
109    fn as_u16(&self) -> u16 {
110        *self as u16
111    }
112}
113
114impl<'a> From<&'a ServerPdu> for ServerPduType {
115    fn from(s: &'a ServerPdu) -> Self {
116        match s {
117            ServerPdu::Version(_) => Self::Version,
118        }
119    }
120}
121
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub enum ServerPdu {
124    Version(VersionPdu),
125}
126
127impl ServerPdu {
128    const NAME: &'static str = "AInputServerPdu";
129
130    const FIXED_PART_SIZE: usize = 2 /* PduType */;
131}
132
133impl Encode for ServerPdu {
134    fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
135        ensure_fixed_part_size!(in: dst);
136
137        dst.write_u16(ServerPduType::from(self).as_u16());
138        match self {
139            ServerPdu::Version(pdu) => pdu.encode(dst),
140        }
141    }
142
143    fn name(&self) -> &'static str {
144        Self::NAME
145    }
146
147    fn size(&self) -> usize {
148        Self::FIXED_PART_SIZE
149            .checked_add(match self {
150                ServerPdu::Version(pdu) => pdu.size(),
151            })
152            .expect("never overflow")
153    }
154}
155
156impl DvcEncode for ServerPdu {}
157
158impl<'de> Decode<'de> for ServerPdu {
159    fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
160        ensure_fixed_part_size!(in: src);
161
162        let pdu_type =
163            ServerPduType::from_u16(src.read_u16()).ok_or_else(|| invalid_field_err!("pduType", "invalid pdu type"))?;
164
165        let server_pdu = match pdu_type {
166            ServerPduType::Version => ServerPdu::Version(VersionPdu::decode(src)?),
167        };
168
169        Ok(server_pdu)
170    }
171}
172
173#[derive(Debug, Clone, PartialEq, Eq)]
174pub struct MousePdu {
175    pub time: u64,
176    pub flags: MouseEventFlags,
177    pub x: i32,
178    pub y: i32,
179}
180
181impl MousePdu {
182    const NAME: &'static str = "AInputMousePdu";
183
184    const FIXED_PART_SIZE: usize = 8 /* Time */ + 8 /* Flags */ + 4 /* X */ + 4 /* Y */;
185}
186
187impl Encode for MousePdu {
188    fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
189        ensure_fixed_part_size!(in: dst);
190
191        dst.write_u64(self.time);
192        dst.write_u64(self.flags.bits());
193        dst.write_i32(self.x);
194        dst.write_i32(self.y);
195
196        Ok(())
197    }
198
199    fn name(&self) -> &'static str {
200        Self::NAME
201    }
202
203    fn size(&self) -> usize {
204        Self::FIXED_PART_SIZE
205    }
206}
207
208impl<'de> Decode<'de> for MousePdu {
209    fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
210        ensure_fixed_part_size!(in: src);
211
212        let time = src.read_u64();
213        let flags = MouseEventFlags::from_bits_retain(src.read_u64());
214        let x = src.read_i32();
215        let y = src.read_i32();
216
217        Ok(Self { time, flags, x, y })
218    }
219}
220
221#[derive(Debug, Clone, PartialEq, Eq)]
222pub enum ClientPdu {
223    Mouse(MousePdu),
224}
225
226impl ClientPdu {
227    const NAME: &'static str = "AInputClientPdu";
228
229    const FIXED_PART_SIZE: usize = 2 /* PduType */;
230}
231
232impl Encode for ClientPdu {
233    fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
234        ensure_fixed_part_size!(in: dst);
235
236        dst.write_u16(ClientPduType::from(self).as_u16());
237        match self {
238            ClientPdu::Mouse(pdu) => pdu.encode(dst),
239        }
240    }
241
242    fn name(&self) -> &'static str {
243        Self::NAME
244    }
245
246    fn size(&self) -> usize {
247        Self::FIXED_PART_SIZE
248            .checked_add(match self {
249                ClientPdu::Mouse(pdu) => pdu.size(),
250            })
251            .expect("never overflow")
252    }
253}
254
255impl<'de> Decode<'de> for ClientPdu {
256    fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
257        ensure_fixed_part_size!(in: src);
258
259        let pdu_type =
260            ClientPduType::from_u16(src.read_u16()).ok_or_else(|| invalid_field_err!("pduType", "invalid pdu type"))?;
261
262        let client_pdu = match pdu_type {
263            ClientPduType::Mouse => ClientPdu::Mouse(MousePdu::decode(src)?),
264        };
265
266        Ok(client_pdu)
267    }
268}
269
270#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive)]
271#[repr(u16)]
272pub enum ClientPduType {
273    Mouse = 0x02,
274}
275
276impl ClientPduType {
277    #[expect(
278        clippy::as_conversions,
279        reason = "guarantees discriminant layout, and as is the only way to cast enum -> primitive"
280    )]
281    fn as_u16(self) -> u16 {
282        self as u16
283    }
284}
285
286impl<'a> From<&'a ClientPdu> for ClientPduType {
287    fn from(s: &'a ClientPdu) -> Self {
288        match s {
289            ClientPdu::Mouse(_) => Self::Mouse,
290        }
291    }
292}