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 _;
11const 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; const BUTTON2 = 0x0000_2000; const BUTTON3 = 0x0000_4000; 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 + 4 ;
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 ;
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 + 8 + 4 + 4 ;
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 ;
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}