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