1use ironrdp_core::{
6 ensure_fixed_part_size, invalid_field_err, Decode, DecodeResult, Encode, EncodeResult, ReadCursor, WriteCursor,
7};
8use ironrdp_dvc::DvcEncode;
9use tracing::warn;
10
11const DISPLAYCONTROL_PDU_TYPE_CAPS: u32 = 0x00000005;
12const DISPLAYCONTROL_PDU_TYPE_MONITOR_LAYOUT: u32 = 0x00000002;
13
14const DISPLAYCONTROL_MONITOR_PRIMARY: u32 = 0x00000001;
15
16const MAX_SUPPORTED_MONITORS: u16 = 1024;
20const MAX_MONITOR_AREA_FACTOR: u16 = 1024 * 16;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
27pub enum DisplayControlPdu {
28 Caps(DisplayControlCapabilities),
29 MonitorLayout(DisplayControlMonitorLayout),
30}
31
32impl DisplayControlPdu {
33 const NAME: &'static str = "DISPLAYCONTROL_HEADER";
34 const FIXED_PART_SIZE: usize = 4 + 4 ;
35}
36
37impl Encode for DisplayControlPdu {
38 fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
39 ensure_fixed_part_size!(in: dst);
40
41 let (kind, payload_length) = match self {
42 DisplayControlPdu::Caps(caps) => (DISPLAYCONTROL_PDU_TYPE_CAPS, caps.size()),
43 DisplayControlPdu::MonitorLayout(layout) => (DISPLAYCONTROL_PDU_TYPE_MONITOR_LAYOUT, layout.size()),
44 };
45
46 #[expect(clippy::arithmetic_side_effects)]
48 let pdu_size = payload_length + Self::FIXED_PART_SIZE;
49
50 dst.write_u32(kind);
52 dst.write_u32(pdu_size.try_into().unwrap());
53
54 match self {
55 DisplayControlPdu::Caps(caps) => caps.encode(dst),
56 DisplayControlPdu::MonitorLayout(layout) => layout.encode(dst),
57 }?;
58
59 Ok(())
60 }
61
62 fn name(&self) -> &'static str {
63 Self::NAME
64 }
65
66 fn size(&self) -> usize {
67 #[expect(clippy::arithmetic_side_effects)]
69 let size = Self::FIXED_PART_SIZE
70 + match self {
71 DisplayControlPdu::Caps(caps) => caps.size(),
72 DisplayControlPdu::MonitorLayout(layout) => layout.size(),
73 };
74
75 size
76 }
77}
78
79impl DvcEncode for DisplayControlPdu {}
80
81impl<'de> Decode<'de> for DisplayControlPdu {
82 fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
83 ensure_fixed_part_size!(in: src);
84
85 let kind = src.read_u32();
87 let pdu_length = src.read_u32();
88
89 let _payload_length = pdu_length
90 .checked_sub(Self::FIXED_PART_SIZE.try_into().unwrap())
91 .ok_or_else(|| invalid_field_err!("Length", "Display control PDU length is too small"))?;
92
93 match kind {
94 DISPLAYCONTROL_PDU_TYPE_CAPS => {
95 let caps = DisplayControlCapabilities::decode(src)?;
96 Ok(DisplayControlPdu::Caps(caps))
97 }
98 DISPLAYCONTROL_PDU_TYPE_MONITOR_LAYOUT => {
99 let layout = DisplayControlMonitorLayout::decode(src)?;
100 Ok(DisplayControlPdu::MonitorLayout(layout))
101 }
102 _ => Err(invalid_field_err!("Type", "Unknown display control PDU type")),
103 }
104 }
105}
106
107impl From<DisplayControlCapabilities> for DisplayControlPdu {
108 fn from(caps: DisplayControlCapabilities) -> Self {
109 Self::Caps(caps)
110 }
111}
112
113impl From<DisplayControlMonitorLayout> for DisplayControlPdu {
114 fn from(layout: DisplayControlMonitorLayout) -> Self {
115 Self::MonitorLayout(layout)
116 }
117}
118
119#[derive(Debug, Clone, PartialEq, Eq)]
130pub struct DisplayControlCapabilities {
131 max_num_monitors: u32,
132 max_monitor_area_factor_a: u32,
133 max_monitor_area_factor_b: u32,
134 max_monitor_area: u64,
135}
136
137impl DisplayControlCapabilities {
138 const NAME: &'static str = "DISPLAYCONTROL_CAPS_PDU";
139 const FIXED_PART_SIZE: usize = 4 + 4 + 4 ;
142
143 pub fn new(
144 max_num_monitors: u32,
145 max_monitor_area_factor_a: u32,
146 max_monitor_area_factor_b: u32,
147 ) -> DecodeResult<Self> {
148 let max_monitor_area =
149 calculate_monitor_area(max_num_monitors, max_monitor_area_factor_a, max_monitor_area_factor_b)?;
150
151 Ok(Self {
152 max_num_monitors,
153 max_monitor_area_factor_a,
154 max_monitor_area_factor_b,
155 max_monitor_area,
156 })
157 }
158
159 pub fn max_monitor_area(&self) -> u64 {
160 self.max_monitor_area
161 }
162}
163
164impl Encode for DisplayControlCapabilities {
165 fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
166 ensure_fixed_part_size!(in: dst);
167 dst.write_u32(self.max_num_monitors);
168 dst.write_u32(self.max_monitor_area_factor_a);
169 dst.write_u32(self.max_monitor_area_factor_b);
170
171 Ok(())
172 }
173
174 fn name(&self) -> &'static str {
175 Self::NAME
176 }
177
178 fn size(&self) -> usize {
179 Self::FIXED_PART_SIZE
180 }
181}
182
183impl<'de> Decode<'de> for DisplayControlCapabilities {
184 fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
185 ensure_fixed_part_size!(in: src);
186
187 let max_num_monitors = src.read_u32();
188 let max_monitor_area_factor_a = src.read_u32();
189 let max_monitor_area_factor_b = src.read_u32();
190
191 let max_monitor_area =
192 calculate_monitor_area(max_num_monitors, max_monitor_area_factor_a, max_monitor_area_factor_b)?;
193
194 Ok(Self {
195 max_num_monitors,
196 max_monitor_area_factor_a,
197 max_monitor_area_factor_b,
198 max_monitor_area,
199 })
200 }
201}
202
203#[derive(Debug, Clone, PartialEq, Eq)]
212pub struct DisplayControlMonitorLayout {
213 monitors: Vec<MonitorLayoutEntry>,
214}
215
216impl DisplayControlMonitorLayout {
217 const NAME: &'static str = "DISPLAYCONTROL_MONITOR_LAYOUT_PDU";
218 const FIXED_PART_SIZE: usize = 4 + 4 ;
219
220 pub fn new(monitors: &[MonitorLayoutEntry]) -> EncodeResult<Self> {
221 if monitors.len() > MAX_SUPPORTED_MONITORS.into() {
222 return Err(invalid_field_err!("NumMonitors", "Too many monitors",));
223 }
224
225 let primary_monitors_count = monitors.iter().filter(|monitor| monitor.is_primary()).count();
226
227 if primary_monitors_count != 1 {
228 return Err(invalid_field_err!(
229 "PrimaryMonitor",
230 "There must be exactly one primary monitor"
231 ));
232 }
233
234 Ok(Self {
235 monitors: monitors.to_vec(),
236 })
237 }
238
239 pub fn new_single_primary_monitor(
252 width: u32,
253 height: u32,
254 scale_factor: Option<u32>,
255 physical_dims: Option<(u32, u32)>,
256 ) -> EncodeResult<Self> {
257 let entry = MonitorLayoutEntry::new_primary(width, height)?.with_orientation(if width > height {
258 MonitorOrientation::Landscape
259 } else {
260 MonitorOrientation::Portrait
261 });
262
263 let entry = if let Some(scale_factor) = scale_factor {
264 entry
265 .with_desktop_scale_factor(scale_factor)?
266 .with_device_scale_factor(DeviceScaleFactor::Scale100Percent)
267 } else {
268 entry
269 };
270
271 let entry = if let Some((physical_width, physical_height)) = physical_dims {
272 entry.with_physical_dimensions(physical_width, physical_height)?
273 } else {
274 entry
275 };
276
277 Ok(DisplayControlMonitorLayout::new(&[entry]).unwrap())
278 }
279
280 pub fn monitors(&self) -> &[MonitorLayoutEntry] {
281 &self.monitors
282 }
283}
284
285impl Encode for DisplayControlMonitorLayout {
286 fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
287 ensure_fixed_part_size!(in: dst);
288
289 dst.write_u32(MonitorLayoutEntry::FIXED_PART_SIZE.try_into().unwrap());
290
291 let monitors_count: u32 = self
292 .monitors
293 .len()
294 .try_into()
295 .map_err(|_| invalid_field_err!("NumMonitors", "Number of monitors is too big"))?;
296
297 dst.write_u32(monitors_count);
298
299 for monitor in &self.monitors {
300 monitor.encode(dst)?;
301 }
302
303 Ok(())
304 }
305
306 fn name(&self) -> &'static str {
307 Self::NAME
308 }
309
310 fn size(&self) -> usize {
311 #[expect(clippy::arithmetic_side_effects)]
314 let size = Self::FIXED_PART_SIZE + self.monitors.iter().map(|monitor| monitor.size()).sum::<usize>();
315
316 size
317 }
318}
319
320impl<'de> Decode<'de> for DisplayControlMonitorLayout {
321 fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
322 ensure_fixed_part_size!(in: src);
323
324 let monitor_layout_size = src.read_u32();
325
326 if monitor_layout_size != MonitorLayoutEntry::FIXED_PART_SIZE.try_into().unwrap() {
327 return Err(invalid_field_err!(
328 "MonitorLayoutSize",
329 "Monitor layout size is invalid"
330 ));
331 }
332
333 let num_monitors = src.read_u32();
334
335 if num_monitors > MAX_SUPPORTED_MONITORS.into() {
336 return Err(invalid_field_err!("NumMonitors", "Too many monitors"));
337 }
338
339 let mut monitors = Vec::with_capacity(usize::try_from(num_monitors).unwrap());
340 for _ in 0..num_monitors {
341 let monitor = MonitorLayoutEntry::decode(src)?;
342 monitors.push(monitor);
343 }
344
345 Ok(Self { monitors })
346 }
347}
348
349#[derive(Debug, Clone, PartialEq, Eq)]
353pub struct MonitorLayoutEntry {
354 is_primary: bool,
355 left: i32,
356 top: i32,
357 width: u32,
358 height: u32,
359 physical_width: u32,
360 physical_height: u32,
361 orientation: u32,
362 desktop_scale_factor: u32,
363 device_scale_factor: u32,
364}
365
366macro_rules! validate_dimensions {
367 ($width:expr, $height:expr) => {{
368 if !(200..=8192).contains(&$width) {
369 return Err(invalid_field_err!("Width", "Monitor width is out of range"));
370 }
371 if $width % 2 != 0 {
372 return Err(invalid_field_err!("Width", "Monitor width cannot be odd"));
373 }
374 if !(200..=8192).contains(&$height) {
375 return Err(invalid_field_err!("Height", "Monitor height is out of range"));
376 }
377 Ok(())
378 }};
379}
380
381impl MonitorLayoutEntry {
382 const FIXED_PART_SIZE: usize = 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 ;
392
393 const NAME: &'static str = "DISPLAYCONTROL_MONITOR_LAYOUT";
394
395 fn new_impl(mut width: u32, height: u32) -> EncodeResult<Self> {
403 if width % 2 != 0 {
404 let prev_width = width;
405 width = width.saturating_sub(1);
406 warn!(
407 "Monitor width cannot be odd, adjusting from [{}] to [{}]",
408 prev_width, width
409 )
410 }
411
412 validate_dimensions!(width, height)?;
413
414 Ok(Self {
415 is_primary: false,
416 left: 0,
417 top: 0,
418 width,
419 height,
420 physical_width: 0,
421 physical_height: 0,
422 orientation: 0,
423 desktop_scale_factor: 0,
424 device_scale_factor: 0,
425 })
426 }
427
428 pub fn adjust_display_size(width: u32, height: u32) -> (u32, u32) {
438 fn constrain(value: u32) -> u32 {
439 value.clamp(200, 8192)
440 }
441
442 let mut width = width;
443 if width % 2 != 0 {
444 width = width.saturating_sub(1);
445 }
446
447 (constrain(width), constrain(height))
448 }
449
450 pub fn new_primary(width: u32, height: u32) -> EncodeResult<Self> {
460 let mut entry = Self::new_impl(width, height)?;
461 entry.is_primary = true;
462 Ok(entry)
463 }
464
465 pub fn new_secondary(width: u32, height: u32) -> EncodeResult<Self> {
475 Self::new_impl(width, height)
476 }
477
478 #[must_use]
480 pub fn with_orientation(mut self, orientation: MonitorOrientation) -> Self {
481 self.orientation = orientation.angle();
482 self
483 }
484
485 pub fn with_position(mut self, left: i32, top: i32) -> EncodeResult<Self> {
489 validate_position(left, top, self.is_primary)?;
490
491 self.left = left;
492 self.top = top;
493
494 Ok(self)
495 }
496
497 #[must_use]
499 pub fn with_device_scale_factor(mut self, device_scale_factor: DeviceScaleFactor) -> Self {
500 self.device_scale_factor = device_scale_factor.value();
501 self
502 }
503
504 pub fn with_desktop_scale_factor(mut self, desktop_scale_factor: u32) -> EncodeResult<Self> {
509 validate_desktop_scale_factor(desktop_scale_factor)?;
510
511 self.desktop_scale_factor = desktop_scale_factor;
512 Ok(self)
513 }
514
515 pub fn with_physical_dimensions(mut self, physical_width: u32, physical_height: u32) -> EncodeResult<Self> {
521 validate_physical_dimensions(physical_width, physical_height)?;
522
523 self.physical_width = physical_width;
524 self.physical_height = physical_height;
525 Ok(self)
526 }
527
528 pub fn is_primary(&self) -> bool {
529 self.is_primary
530 }
531
532 pub fn position(&self) -> Option<(i32, i32)> {
534 validate_position(self.left, self.top, self.is_primary).ok()?;
535
536 Some((self.left, self.top))
537 }
538
539 pub fn dimensions(&self) -> (u32, u32) {
541 (self.width, self.height)
542 }
543
544 pub fn orientation(&self) -> Option<MonitorOrientation> {
549 MonitorOrientation::from_angle(self.orientation)
550 }
551
552 pub fn physical_dimensions(&self) -> Option<(u32, u32)> {
558 validate_physical_dimensions(self.physical_width, self.physical_height).ok()?;
559 Some((self.physical_width, self.physical_height))
560 }
561
562 pub fn desktop_scale_factor(&self) -> Option<u32> {
570 validate_desktop_scale_factor(self.desktop_scale_factor).ok()?;
571
572 Some(self.desktop_scale_factor)
573 }
574
575 pub fn device_scale_factor(&self) -> Option<DeviceScaleFactor> {
580 match self.device_scale_factor {
581 100 => Some(DeviceScaleFactor::Scale100Percent),
582 140 => Some(DeviceScaleFactor::Scale140Percent),
583 180 => Some(DeviceScaleFactor::Scale180Percent),
584 _ => None,
585 }
586 }
587}
588
589impl Encode for MonitorLayoutEntry {
590 fn encode(&self, dst: &mut WriteCursor<'_>) -> EncodeResult<()> {
591 ensure_fixed_part_size!(in: dst);
592
593 let flags = if self.is_primary {
594 DISPLAYCONTROL_MONITOR_PRIMARY
595 } else {
596 0
597 };
598 dst.write_u32(flags);
599 dst.write_i32(self.left);
600 dst.write_i32(self.top);
601 dst.write_u32(self.width);
602 dst.write_u32(self.height);
603 dst.write_u32(self.physical_width);
604 dst.write_u32(self.physical_height);
605 dst.write_u32(self.orientation);
606 dst.write_u32(self.desktop_scale_factor);
607 dst.write_u32(self.device_scale_factor);
608
609 Ok(())
610 }
611
612 fn name(&self) -> &'static str {
613 Self::NAME
614 }
615
616 fn size(&self) -> usize {
617 Self::FIXED_PART_SIZE
618 }
619}
620
621impl<'de> Decode<'de> for MonitorLayoutEntry {
622 fn decode(src: &mut ReadCursor<'de>) -> DecodeResult<Self> {
623 ensure_fixed_part_size!(in: src);
624
625 let flags = src.read_u32();
626 let left = src.read_i32();
627 let top = src.read_i32();
628 let width = src.read_u32();
629 let height = src.read_u32();
630 let physical_width = src.read_u32();
631 let physical_height = src.read_u32();
632 let orientation = src.read_u32();
633 let desktop_scale_factor = src.read_u32();
634 let device_scale_factor = src.read_u32();
635
636 validate_dimensions!(width, height)?;
637
638 Ok(Self {
639 is_primary: flags & DISPLAYCONTROL_MONITOR_PRIMARY != 0,
640 left,
641 top,
642 width,
643 height,
644 physical_width,
645 physical_height,
646 orientation,
647 desktop_scale_factor,
648 device_scale_factor,
649 })
650 }
651}
652
653#[derive(Debug, Clone, Copy, PartialEq, Eq)]
655pub enum MonitorOrientation {
656 Landscape,
657 Portrait,
658 LandscapeFlipped,
659 PortraitFlipped,
660}
661
662impl MonitorOrientation {
663 pub fn from_angle(angle: u32) -> Option<Self> {
664 match angle {
665 0 => Some(Self::Landscape),
666 90 => Some(Self::Portrait),
667 180 => Some(Self::LandscapeFlipped),
668 270 => Some(Self::PortraitFlipped),
669 _ => None,
670 }
671 }
672
673 pub fn angle(&self) -> u32 {
674 match self {
675 Self::Landscape => 0,
676 Self::Portrait => 90,
677 Self::LandscapeFlipped => 180,
678 Self::PortraitFlipped => 270,
679 }
680 }
681}
682
683#[derive(Debug, Clone, Copy, PartialEq, Eq)]
685pub enum DeviceScaleFactor {
686 Scale100Percent,
687 Scale140Percent,
688 Scale180Percent,
689}
690
691impl DeviceScaleFactor {
692 pub fn value(&self) -> u32 {
693 match self {
694 Self::Scale100Percent => 100,
695 Self::Scale140Percent => 140,
696 Self::Scale180Percent => 180,
697 }
698 }
699}
700
701fn validate_position(left: i32, top: i32, is_primary: bool) -> EncodeResult<()> {
702 if is_primary && (left != 0 || top != 0) {
703 return Err(invalid_field_err!(
704 "Position",
705 "Primary monitor position must be (0, 0)"
706 ));
707 }
708
709 Ok(())
710}
711
712fn validate_desktop_scale_factor(desktop_scale_factor: u32) -> EncodeResult<()> {
713 if !(100..=500).contains(&desktop_scale_factor) {
714 return Err(invalid_field_err!(
715 "DesktopScaleFactor",
716 "Desktop scale factor is out of range"
717 ));
718 }
719
720 Ok(())
721}
722
723fn validate_physical_dimensions(physical_width: u32, physical_height: u32) -> EncodeResult<()> {
724 if !(10..=10000).contains(&physical_width) {
725 return Err(invalid_field_err!("PhysicalWidth", "Physical width is out of range"));
726 }
727 if !(10..=10000).contains(&physical_height) {
728 return Err(invalid_field_err!("PhysicalHeight", "Physical height is out of range"));
729 }
730
731 Ok(())
732}
733
734fn calculate_monitor_area(
735 max_num_monitors: u32,
736 max_monitor_area_factor_a: u32,
737 max_monitor_area_factor_b: u32,
738) -> DecodeResult<u64> {
739 if max_num_monitors > MAX_SUPPORTED_MONITORS.into() {
740 return Err(invalid_field_err!("NumMonitors", "Too many monitors"));
741 }
742
743 if max_monitor_area_factor_a > MAX_MONITOR_AREA_FACTOR.into()
744 || max_monitor_area_factor_b > MAX_MONITOR_AREA_FACTOR.into()
745 {
746 return Err(invalid_field_err!(
747 "MaxMonitorAreaFactor",
748 "Invalid monitor area factor"
749 ));
750 }
751
752 #[expect(clippy::arithmetic_side_effects)]
755 Ok(u64::from(max_monitor_area_factor_a) * u64::from(max_monitor_area_factor_b) * u64::from(max_num_monitors))
756}