ironrdp_displaycontrol/
client.rs

1use ironrdp_core::{impl_as_any, Decode as _, EncodeResult, ReadCursor};
2use ironrdp_dvc::{encode_dvc_messages, DvcClientProcessor, DvcMessage, DvcProcessor};
3use ironrdp_pdu::{decode_err, PduResult};
4use ironrdp_svc::{ChannelFlags, SvcMessage};
5use tracing::debug;
6
7use crate::pdu::{DisplayControlCapabilities, DisplayControlMonitorLayout, DisplayControlPdu};
8use crate::CHANNEL_NAME;
9
10/// A client for the Display Control Virtual Channel.
11pub struct DisplayControlClient {
12    /// A callback that will be called when capabilities are received from the server.
13    on_capabilities_received: OnCapabilitiesReceived,
14    /// Indicates whether the capabilities have been received from the server.
15    ready: bool,
16}
17
18impl DisplayControlClient {
19    /// Creates a new [`DisplayControlClient`] with the given `callback`.
20    ///
21    /// The `callback` will be called when capabilities are received from the server.
22    /// It is important to note that the channel will not be fully operational until the capabilities are received.
23    /// Attempting to send messages before the capabilities are received will result in an error or a silent failure.
24    pub fn new<F>(callback: F) -> Self
25    where
26        F: Fn(DisplayControlCapabilities) -> PduResult<Vec<DvcMessage>> + Send + 'static,
27    {
28        Self {
29            on_capabilities_received: Box::new(callback),
30            ready: false,
31        }
32    }
33
34    pub fn ready(&self) -> bool {
35        self.ready
36    }
37
38    /// Builds a [`DisplayControlPdu::MonitorLayout`] with a single primary monitor
39    /// with the given `width` and `height`, and wraps it as an [`SvcMessage`].
40    ///
41    /// Per [2.2.2.2.1]:
42    /// - The `width` MUST be greater than or equal to 200 pixels and less than or equal to 8192 pixels, and MUST NOT be an odd value.
43    /// - The `height` MUST be greater than or equal to 200 pixels and less than or equal to 8192 pixels.
44    /// - The `scale_factor` MUST be ignored if it is less than 100 percent or greater than 500 percent.
45    /// - The `physical_dims` (width, height) MUST be ignored if either is less than 10 mm or greater than 10,000 mm.
46    ///
47    /// Use [`crate::pdu::MonitorLayoutEntry::adjust_display_size`] to adjust `width` and `height` before calling this function
48    /// to ensure the display size is within the valid range.
49    ///
50    /// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c
51    pub fn encode_single_primary_monitor(
52        &self,
53        channel_id: u32,
54        width: u32,
55        height: u32,
56        scale_factor: Option<u32>,
57        physical_dims: Option<(u32, u32)>,
58    ) -> EncodeResult<Vec<SvcMessage>> {
59        // TODO: prevent resolution with values greater than max monitor area received in caps.
60        let pdu: DisplayControlPdu =
61            DisplayControlMonitorLayout::new_single_primary_monitor(width, height, scale_factor, physical_dims)?.into();
62        debug!(?pdu, "Sending monitor layout");
63        encode_dvc_messages(channel_id, vec![Box::new(pdu)], ChannelFlags::empty())
64    }
65}
66
67impl_as_any!(DisplayControlClient);
68
69impl DvcProcessor for DisplayControlClient {
70    fn channel_name(&self) -> &str {
71        CHANNEL_NAME
72    }
73
74    fn start(&mut self, _channel_id: u32) -> PduResult<Vec<DvcMessage>> {
75        Ok(Vec::new())
76    }
77
78    fn process(&mut self, _channel_id: u32, payload: &[u8]) -> PduResult<Vec<DvcMessage>> {
79        let caps = DisplayControlCapabilities::decode(&mut ReadCursor::new(payload)).map_err(|e| decode_err!(e))?;
80        debug!("Received {:?}", caps);
81        self.ready = true;
82        (self.on_capabilities_received)(caps)
83    }
84}
85
86impl DvcClientProcessor for DisplayControlClient {}
87
88type OnCapabilitiesReceived = Box<dyn Fn(DisplayControlCapabilities) -> PduResult<Vec<DvcMessage>> + Send>;