Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions crates/ironrdp-displaycontrol/src/pdu/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,17 @@ impl DisplayControlCapabilities {
pub fn max_monitor_area(&self) -> u64 {
self.max_monitor_area
}

/// One 4K-area (3840x2400) monitor: the single-monitor capabilities every
/// existing server advertised before per-display monitor counts existed,
/// and the safe fallback for a monitor count that turns out to be invalid.
///
/// # Panics
///
/// Never: `(1, 3840, 2400)` is always within [`new`](Self::new)'s valid range.
pub fn single_monitor() -> Self {
Self::new(1, 3840, 2400).expect("(1, 3840, 2400) are always within the valid range")
}
}

impl Encode for DisplayControlCapabilities {
Expand Down
14 changes: 11 additions & 3 deletions crates/ironrdp-displaycontrol/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ pub trait DisplayControlHandler: Send {
fn monitor_layout(&self, layout: DisplayControlMonitorLayout) {
debug!(?layout);
}

/// Capabilities advertised to the client when the channel starts.
///
/// Defaults to [`DisplayControlCapabilities::single_monitor()`], so any
/// existing handler that doesn't override this keeps its current
/// behavior. A handler serving more than one monitor should override
/// this to return `DisplayControlCapabilities::new(monitor_count, 3840, 2400)`.
fn capabilities(&self) -> DisplayControlCapabilities {
DisplayControlCapabilities::single_monitor()
}
}

/// A server for the Display Control Virtual Channel.
Expand All @@ -32,9 +42,7 @@ impl DvcProcessor for DisplayControlServer {
}

fn start(&mut self, _channel_id: u32) -> PduResult<Vec<DvcMessage>> {
let pdu: DisplayControlPdu = DisplayControlCapabilities::new(1, 3840, 2400)
.map_err(|e| decode_err!(e))?
.into();
let pdu: DisplayControlPdu = self.handler.capabilities().into();

Ok(vec![Box::new(pdu)])
}
Expand Down
15 changes: 15 additions & 0 deletions crates/ironrdp-server/src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,21 @@ pub trait RdpServerDisplay: Send {
fn request_layout(&mut self, layout: DisplayControlMonitorLayout) {
debug!(?layout, "Requesting layout")
}

/// The maximum number of monitors this display will honor in a client's
/// `request_layout()` call for the rest of the session.
///
/// This is a capacity ceiling (MS-RDPEDISP `MaxNumMonitors`), not a report
/// of the current topology: the client may request any layout up to this
/// many monitors, and it is validated against this number, not the other
/// way around. Called once, before the Display Control Virtual Channel
/// opens, to build the capabilities the server advertises; the display's
/// actual monitor count may later grow or shrink within that ceiling
/// without a way to advertise a new one mid-session. Defaults to `1`,
/// matching every existing implementation's current behavior.
async fn monitor_count(&mut self) -> u32 {
1
}
}

#[cfg(test)]
Expand Down
33 changes: 26 additions & 7 deletions crates/ironrdp-server/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use ironrdp_async::Framed;
use ironrdp_cliprdr::CliprdrServer;
use ironrdp_cliprdr::backend::ClipboardMessage;
use ironrdp_core::{decode, encode_vec, impl_as_any};
use ironrdp_displaycontrol::pdu::DisplayControlMonitorLayout;
use ironrdp_displaycontrol::pdu::{DisplayControlCapabilities, DisplayControlMonitorLayout};
use ironrdp_displaycontrol::server::{DisplayControlHandler, DisplayControlServer};
use ironrdp_dvc as dvc;
#[cfg(feature = "usb")]
Expand Down Expand Up @@ -530,11 +530,12 @@ impl dvc::DvcServerProcessor for AInputHandler {}

struct DisplayControlBackend {
display: Arc<Mutex<Box<dyn RdpServerDisplay>>>,
monitor_count: u32,
}

impl DisplayControlBackend {
fn new(display: Arc<Mutex<Box<dyn RdpServerDisplay>>>) -> Self {
Self { display }
fn new(display: Arc<Mutex<Box<dyn RdpServerDisplay>>>, monitor_count: u32) -> Self {
Self { display, monitor_count }
}
}

Expand All @@ -543,6 +544,22 @@ impl DisplayControlHandler for DisplayControlBackend {
let display = Arc::clone(&self.display);
task::spawn_blocking(move || display.blocking_lock().request_layout(layout));
}

fn capabilities(&self) -> DisplayControlCapabilities {
// `DisplayControlCapabilities::new` only rejects `monitor_count > 1024`; 0 passes its
// validation (0 * 3840 * 2400 does not overflow) but would advertise a server that
// supports no monitors, so it is folded into the same out-of-range fallback below.
let monitor_count = if self.monitor_count == 0 {
warn!("RdpServerDisplay::monitor_count() returned 0, falling back to 1");
1
} else {
self.monitor_count
};
DisplayControlCapabilities::new(monitor_count, 3840, 2400).unwrap_or_else(|e| {
warn!(monitor_count, error = %e, "RdpServerDisplay::monitor_count() out of range, falling back to 1");
DisplayControlCapabilities::single_monitor()
})
}
Comment thread
glamberson marked this conversation as resolved.
Comment thread
glamberson marked this conversation as resolved.
Comment thread
glamberson marked this conversation as resolved.
}

#[cfg(feature = "usb")]
Expand Down Expand Up @@ -1792,7 +1809,7 @@ impl RdpServer {
self.gfx_handle.as_ref()
}

fn attach_channels(&mut self, acceptor: &mut Acceptor) {
fn attach_channels(&mut self, acceptor: &mut Acceptor, monitor_count: u32) {
if let Some(cliprdr_factory) = self.cliprdr_factory.as_deref() {
let backend = cliprdr_factory.build_cliprdr_backend();

Expand All @@ -1813,7 +1830,7 @@ impl RdpServer {
acceptor.attach_static_channel(RdpdrServer::new(backend));
}

let dcs_backend = DisplayControlBackend::new(Arc::clone(&self.display));
let dcs_backend = DisplayControlBackend::new(Arc::clone(&self.display), monitor_count);
let dvc = dvc::DrdynvcServer::new()
.with_dynamic_channel(AInputHandler {
handler: Arc::clone(&self.handler),
Expand Down Expand Up @@ -1948,7 +1965,8 @@ impl RdpServer {
// `accept_finalize`, which is where the acceptor first consumes the
// static channel set (the MCS Connect Initial); `accept_begin`, already
// done, stops at the security-upgrade gate before that.
self.attach_channels(&mut candidate.acceptor);
let monitor_count = self.display.lock().await.monitor_count().await;
self.attach_channels(&mut candidate.acceptor, monitor_count);

self.finalize_negotiated(*candidate).await
}
Expand Down Expand Up @@ -2076,6 +2094,7 @@ impl RdpServer {
self.display_suppressed.store(false, Ordering::Relaxed);

let size = self.display.lock().await.size().await;
let monitor_count = self.display.lock().await.monitor_count().await;
let capabilities = capabilities::capabilities(&self.opts, size);
let mut pending = PendingConnection::new(
self.opts.security.clone(),
Expand All @@ -2085,7 +2104,7 @@ impl RdpServer {
self.opts.honor_client_desktop_size,
);

self.attach_channels(pending.acceptor_mut());
self.attach_channels(pending.acceptor_mut(), monitor_count);

let Some(negotiated) = pending.negotiate_and_authenticate(stream, tls).await? else {
return Ok(());
Expand Down
Loading