mirror of
https://github.com/netbirdio/IronRDP.git
synced 2026-05-22 18:43:12 -07:00
feat(displaycontrol): hook up resize for ironrdp-client crate (#430)
This commit is contained in:
Generated
+2
@@ -1702,6 +1702,7 @@ dependencies = [
|
||||
"ironrdp-cliprdr",
|
||||
"ironrdp-cliprdr-native",
|
||||
"ironrdp-connector",
|
||||
"ironrdp-displaycontrol",
|
||||
"ironrdp-dvc",
|
||||
"ironrdp-graphics",
|
||||
"ironrdp-input",
|
||||
@@ -1990,6 +1991,7 @@ name = "ironrdp-session"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ironrdp-connector",
|
||||
"ironrdp-displaycontrol",
|
||||
"ironrdp-dvc",
|
||||
"ironrdp-error",
|
||||
"ironrdp-graphics",
|
||||
|
||||
@@ -28,7 +28,7 @@ native-tls = ["ironrdp-tls/native-tls"]
|
||||
[dependencies]
|
||||
|
||||
# Protocols
|
||||
ironrdp = { workspace = true, features = ["input", "graphics", "dvc", "rdpdr", "rdpsnd", "cliprdr"] }
|
||||
ironrdp = { workspace = true, features = ["input", "graphics", "dvc", "svc", "rdpdr", "rdpsnd", "cliprdr", "displaycontrol"] }
|
||||
ironrdp-cliprdr-native.workspace = true
|
||||
ironrdp-tls.workspace = true
|
||||
ironrdp-tokio.workspace = true
|
||||
|
||||
@@ -296,6 +296,7 @@ impl Config {
|
||||
width: DEFAULT_WIDTH,
|
||||
height: DEFAULT_HEIGHT,
|
||||
},
|
||||
desktop_scale_factor: 0, // Default to 0 per FreeRDP
|
||||
bitmap,
|
||||
client_build: semver::Version::parse(env!("CARGO_PKG_VERSION"))
|
||||
.map(|version| version.major * 100 + version.minor * 10 + version.patch)
|
||||
|
||||
@@ -63,9 +63,17 @@ impl GuiContext {
|
||||
match event {
|
||||
Event::WindowEvent { window_id, event } if window_id == window.id() => match event {
|
||||
WindowEvent::Resized(size) => {
|
||||
let scale_factor = (window.scale_factor() * 100.0) as u32;
|
||||
// TODO: it should be possible to get the physical size here, however winit doesn't make it straightforward.
|
||||
// FreeRDP does it based on DPI reading grabbed via [`SDL_GetDisplayDPI`](https://wiki.libsdl.org/SDL2/SDL_GetDisplayDPI):
|
||||
// https://github.com/FreeRDP/FreeRDP/blob/ba8cf8cf2158018fb7abbedb51ab245f369be813/client/SDL/sdl_monitor.cpp#L250-L262
|
||||
let (physical_width, physical_height) = (0, 0);
|
||||
let _ = input_event_sender.send(RdpInputEvent::Resize {
|
||||
width: u16::try_from(size.width).unwrap(),
|
||||
height: u16::try_from(size.height).unwrap(),
|
||||
scale_factor,
|
||||
physical_width,
|
||||
physical_height,
|
||||
});
|
||||
}
|
||||
WindowEvent::CloseRequested => {
|
||||
@@ -225,6 +233,8 @@ impl GuiContext {
|
||||
// TODO: is there something we should handle here?
|
||||
}
|
||||
Event::UserEvent(RdpOutputEvent::Image { buffer, width, height }) => {
|
||||
trace!(width = ?width, height = ?height, "Received image with size");
|
||||
trace!(window_physical_size = ?window.inner_size(), "Drawing image to the window with size");
|
||||
surface
|
||||
.resize(
|
||||
NonZeroU32::new(u32::from(width)).unwrap(),
|
||||
|
||||
@@ -19,6 +19,7 @@ fn main() -> anyhow::Result<()> {
|
||||
debug!("GUI context initialized");
|
||||
|
||||
let window_size = gui.window().inner_size();
|
||||
config.connector.desktop_scale_factor = 0; // TODO: should this be `(gui.window().scale_factor() * 100.0) as u32`?
|
||||
config.connector.desktop_size.width = u16::try_from(window_size.width).unwrap();
|
||||
config.connector.desktop_size.height = u16::try_from(window_size.height).unwrap();
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use ironrdp::cliprdr::backend::{ClipboardMessage, CliprdrBackendFactory};
|
||||
use ironrdp::connector::connection_activation::ConnectionActivationState;
|
||||
use ironrdp::connector::{ConnectionResult, ConnectorResult};
|
||||
use ironrdp::displaycontrol::client::DisplayControlClient;
|
||||
use ironrdp::displaycontrol::pdu::MonitorLayoutEntry;
|
||||
use ironrdp::graphics::image_processing::PixelFormat;
|
||||
use ironrdp::pdu::input::fast_path::FastPathInputEvent;
|
||||
use ironrdp::pdu::write_buf::WriteBuf;
|
||||
@@ -28,7 +30,13 @@ pub enum RdpOutputEvent {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RdpInputEvent {
|
||||
Resize { width: u16, height: u16 },
|
||||
Resize {
|
||||
width: u16,
|
||||
height: u16,
|
||||
scale_factor: u32,
|
||||
physical_width: u32,
|
||||
physical_height: u32,
|
||||
},
|
||||
FastPath(SmallVec<[FastPathInputEvent; 2]>),
|
||||
Close,
|
||||
Clipboard(ClipboardMessage),
|
||||
@@ -107,7 +115,9 @@ async fn connect(
|
||||
|
||||
let mut connector = connector::ClientConnector::new(config.connector.clone())
|
||||
.with_server_addr(server_addr)
|
||||
.with_static_channel(ironrdp::dvc::DrdynvcClient::new())
|
||||
.with_static_channel(
|
||||
ironrdp::dvc::DrdynvcClient::new().with_dynamic_channel(DisplayControlClient::new(|_| Ok(Vec::new()))),
|
||||
)
|
||||
.with_static_channel(rdpsnd::Rdpsnd::new())
|
||||
.with_static_channel(rdpdr::Rdpdr::new(Box::new(NoopRdpdrBackend {}), "IronRDP".to_owned()).with_smartcard(0));
|
||||
|
||||
@@ -177,24 +187,30 @@ async fn active_session(
|
||||
let input_event = input_event.ok_or_else(|| session::general_err!("GUI is stopped"))?;
|
||||
|
||||
match input_event {
|
||||
RdpInputEvent::Resize { mut width, mut height } => {
|
||||
// TODO(#105): Add support for Display Update Virtual Channel Extension
|
||||
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/d2954508-f487-48bc-8731-39743e0854a9
|
||||
// One approach when this extension is not available is to perform a connection from scratch again.
|
||||
|
||||
RdpInputEvent::Resize { mut width, mut height, .. } => {
|
||||
// Find the last resize event
|
||||
while let Ok(newer_event) = input_event_receiver.try_recv() {
|
||||
if let RdpInputEvent::Resize { width: newer_width, height: newer_height } = newer_event {
|
||||
if let RdpInputEvent::Resize {
|
||||
width: newer_width,
|
||||
height: newer_height,
|
||||
..
|
||||
} = newer_event {
|
||||
width = newer_width;
|
||||
height = newer_height;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(#271): use the "auto-reconnect cookie": https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/15b0d1c9-2891-4adb-a45e-deb4aeeeab7c
|
||||
|
||||
info!(width, height, "resize event");
|
||||
let (width, height) = MonitorLayoutEntry::adjust_display_size(width.into(), height.into());
|
||||
debug!(width, height, "Adjusted display size");
|
||||
|
||||
return Ok(RdpControlFlow::ReconnectWithNewSize { width, height })
|
||||
if let Some(response_frame) = active_stage.encode_resize(width, height, None, Some((width, height))) { // Set physical width and height to the same as the pixel width and heighbbt per FreeRDP
|
||||
vec![ActiveStageOutput::ResponseFrame(response_frame?)]
|
||||
} else {
|
||||
// TODO(#271): use the "auto-reconnect cookie": https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/15b0d1c9-2891-4adb-a45e-deb4aeeeab7c
|
||||
debug!("Reconnecting with new size");
|
||||
return Ok(RdpControlFlow::ReconnectWithNewSize { width: width.try_into().unwrap(), height: height.try_into().unwrap() })
|
||||
}
|
||||
},
|
||||
RdpInputEvent::FastPath(events) => {
|
||||
trace!(?events);
|
||||
@@ -307,10 +323,10 @@ async fn active_session(
|
||||
pointer_software_rendering,
|
||||
} = connection_activation.state
|
||||
{
|
||||
debug!("Deactivation-Reactivation Sequence completed");
|
||||
debug!(?desktop_size, "Deactivation-Reactivation Sequence completed");
|
||||
// Update image size with the new desktop size.
|
||||
image = DecodedImage::new(PixelFormat::RgbA32, desktop_size.width, desktop_size.height);
|
||||
// Create a new [`FastPathProcessor`] with potentially updated
|
||||
// io/user channel ids.
|
||||
// Update the active stage with the new channel IDs and pointer settings.
|
||||
active_stage.set_fastpath_processor(
|
||||
fast_path::ProcessorBuilder {
|
||||
io_channel_id,
|
||||
@@ -320,6 +336,7 @@ async fn active_session(
|
||||
}
|
||||
.build(),
|
||||
);
|
||||
active_stage.set_no_server_pointer(no_server_pointer);
|
||||
break 'activation_seq;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -660,11 +660,19 @@ fn create_gcc_blocks<'a>(
|
||||
dig_product_id: Some(config.dig_product_id.clone()),
|
||||
connection_type: Some(ConnectionType::Lan),
|
||||
server_selected_protocol: Some(selected_protocol),
|
||||
desktop_physical_width: None,
|
||||
desktop_physical_height: None,
|
||||
desktop_orientation: None,
|
||||
desktop_scale_factor: None,
|
||||
device_scale_factor: None,
|
||||
desktop_physical_width: Some(0), // 0 per FreeRDP
|
||||
desktop_physical_height: Some(0), // 0 per FreeRDP
|
||||
desktop_orientation: if config.desktop_size.width > config.desktop_size.height {
|
||||
Some(MonitorOrientation::Landscape as u16)
|
||||
} else {
|
||||
Some(MonitorOrientation::Portrait as u16)
|
||||
},
|
||||
desktop_scale_factor: Some(config.desktop_scale_factor),
|
||||
device_scale_factor: if config.desktop_scale_factor >= 100 && config.desktop_scale_factor <= 500 {
|
||||
Some(100)
|
||||
} else {
|
||||
Some(0)
|
||||
},
|
||||
},
|
||||
},
|
||||
security: ClientSecurityData {
|
||||
|
||||
@@ -19,7 +19,7 @@ use crate::{legacy, Config, ConnectionFinalizationSequence, ConnectorResult, Des
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConnectionActivationSequence {
|
||||
pub state: ConnectionActivationState,
|
||||
pub config: Config,
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl ConnectionActivationSequence {
|
||||
@@ -127,6 +127,15 @@ impl Sequence for ConnectionActivationSequence {
|
||||
}
|
||||
}
|
||||
|
||||
// At this point we have already sent a requested desktop size to the server -- either as a part of the
|
||||
// [`TS_UD_CS_CORE`] (on initial connection) or the [`DISPLAYCONTROL_MONITOR_LAYOUT`] (on resize event).
|
||||
//
|
||||
// The server is therefore responding with a desktop size here, which will be close to the requested size but
|
||||
// may be slightly different due to server-side constraints. We should use this negotiated size for the rest of
|
||||
// the session.
|
||||
//
|
||||
// [TS_UD_CS_CORE]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/00f1da4a-ee9c-421a-852f-c19f92343d73
|
||||
// [DISPLAYCONTROL_MONITOR_LAYOUT]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c
|
||||
let desktop_size = capability_sets
|
||||
.iter()
|
||||
.find_map(|c| match c {
|
||||
@@ -142,7 +151,7 @@ impl Sequence for ConnectionActivationSequence {
|
||||
});
|
||||
|
||||
let client_confirm_active = rdp::headers::ShareControlPdu::ClientConfirmActive(
|
||||
create_client_confirm_active(&self.config, capability_sets),
|
||||
create_client_confirm_active(&self.config, capability_sets, desktop_size),
|
||||
);
|
||||
|
||||
debug!(message = ?client_confirm_active, "Send");
|
||||
@@ -249,6 +258,7 @@ const DEFAULT_POINTER_CACHE_SIZE: u16 = 32;
|
||||
fn create_client_confirm_active(
|
||||
config: &Config,
|
||||
mut server_capability_sets: Vec<CapabilitySet>,
|
||||
desktop_size: DesktopSize,
|
||||
) -> rdp::capability_sets::ClientConfirmActive {
|
||||
use ironrdp_pdu::rdp::capability_sets::*;
|
||||
|
||||
@@ -276,8 +286,8 @@ fn create_client_confirm_active(
|
||||
}),
|
||||
CapabilitySet::Bitmap(Bitmap {
|
||||
pref_bits_per_pix: 32,
|
||||
desktop_width: config.desktop_size.width,
|
||||
desktop_height: config.desktop_size.height,
|
||||
desktop_width: desktop_size.width,
|
||||
desktop_height: desktop_size.height,
|
||||
// This is required to be true in order for the Microsoft::Windows::RDS::DisplayControl DVC to work.
|
||||
desktop_resize_flag: true,
|
||||
drawing_flags,
|
||||
@@ -355,7 +365,10 @@ fn create_client_confirm_active(
|
||||
})),
|
||||
}])),
|
||||
CapabilitySet::FrameAcknowledge(FrameAcknowledge {
|
||||
max_unacknowledged_frame_count: 2,
|
||||
// FIXME(#447): Revert this to 2 per FreeRDP.
|
||||
// This is a temporary hack to fix a resize bug, see:
|
||||
// https://github.com/Devolutions/IronRDP/issues/447
|
||||
max_unacknowledged_frame_count: 20,
|
||||
}),
|
||||
]);
|
||||
|
||||
@@ -364,7 +377,7 @@ fn create_client_confirm_active(
|
||||
.any(|c| matches!(&c, CapabilitySet::MultiFragmentUpdate(_)))
|
||||
{
|
||||
server_capability_sets.push(CapabilitySet::MultiFragmentUpdate(MultifragmentUpdate {
|
||||
max_request_size: 1024,
|
||||
max_request_size: 8 * 1024 * 1024, // 8 MB
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,10 @@ impl Credentials {
|
||||
pub struct Config {
|
||||
/// The initial desktop size to request
|
||||
pub desktop_size: DesktopSize,
|
||||
/// The initial desktop scale factor to request.
|
||||
///
|
||||
/// This becomes the `desktop_scale_factor` in the [`TS_UD_CS_CORE`](gcc::ClientCoreOptionalData) structure.
|
||||
pub desktop_scale_factor: u32,
|
||||
/// TLS + Graphical login (legacy)
|
||||
///
|
||||
/// Also called SSL or TLS security protocol.
|
||||
|
||||
@@ -37,13 +37,29 @@ impl DisplayControlClient {
|
||||
|
||||
/// Builds a [`DisplayControlPdu::MonitorLayout`] with a single primary monitor
|
||||
/// with the given `width` and `height`, and wraps it as an [`SvcMessage`].
|
||||
///
|
||||
/// Per [2.2.2.2.1]:
|
||||
/// - 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.
|
||||
/// - The `height` MUST be greater than or equal to 200 pixels and less than or equal to 8192 pixels.
|
||||
/// - The `scale_factor` MUST be ignored if it is less than 100 percent or greater than 500 percent.
|
||||
/// - The `physical_dims` (width, height) MUST be ignored if either is less than 10 mm or greater than 10,000 mm.
|
||||
///
|
||||
/// Use [`crate::pdu::MonitorLayoutEntry::adjust_display_size`] to adjust `width` and `height` before calling this function
|
||||
/// to ensure the display size is within the valid range.
|
||||
///
|
||||
/// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c
|
||||
pub fn encode_single_primary_monitor(
|
||||
&self,
|
||||
channel_id: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
scale_factor: Option<u32>,
|
||||
physical_dims: Option<(u32, u32)>,
|
||||
) -> PduResult<Vec<SvcMessage>> {
|
||||
let pdu: DisplayControlPdu = DisplayControlMonitorLayout::new_single_primary_monitor(width, height)?.into();
|
||||
// TODO: prevent resolution with values greater than max monitor area received in caps.
|
||||
let pdu: DisplayControlPdu =
|
||||
DisplayControlMonitorLayout::new_single_primary_monitor(width, height, scale_factor, physical_dims)?.into();
|
||||
debug!(?pdu, "Sending monitor layout");
|
||||
encode_dvc_messages(channel_id, vec![Box::new(pdu)], ChannelFlags::empty())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,16 +236,44 @@ impl DisplayControlMonitorLayout {
|
||||
}
|
||||
|
||||
/// Creates a new [`DisplayControlMonitorLayout`] with a single primary monitor
|
||||
/// with the given `width` and `height`.
|
||||
pub fn new_single_primary_monitor(width: u32, height: u32) -> PduResult<Self> {
|
||||
let monitors = vec![
|
||||
MonitorLayoutEntry::new_primary(width, height)?.with_orientation(if width > height {
|
||||
MonitorOrientation::Landscape
|
||||
} else {
|
||||
MonitorOrientation::Portrait
|
||||
}),
|
||||
];
|
||||
Ok(DisplayControlMonitorLayout::new(&monitors).unwrap())
|
||||
///
|
||||
/// Per [2.2.2.2.1]:
|
||||
/// - 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.
|
||||
/// - The `height` MUST be greater than or equal to 200 pixels and less than or equal to 8192 pixels.
|
||||
/// - The `scale_factor` MUST be ignored if it is less than 100 percent or greater than 500 percent.
|
||||
/// - The `physical_dims` (width, height) MUST be ignored if either is less than 10 mm or greater than 10,000 mm.
|
||||
///
|
||||
/// Use [`MonitorLayoutEntry::adjust_display_size`] to adjust `width` and `height` before calling this function
|
||||
/// to ensure the display size is within the valid range.
|
||||
///
|
||||
/// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c
|
||||
pub fn new_single_primary_monitor(
|
||||
width: u32,
|
||||
height: u32,
|
||||
scale_factor: Option<u32>,
|
||||
physical_dims: Option<(u32, u32)>,
|
||||
) -> PduResult<Self> {
|
||||
let entry = MonitorLayoutEntry::new_primary(width, height)?.with_orientation(if width > height {
|
||||
MonitorOrientation::Landscape
|
||||
} else {
|
||||
MonitorOrientation::Portrait
|
||||
});
|
||||
|
||||
let entry = if let Some(scale_factor) = scale_factor {
|
||||
entry
|
||||
.with_desktop_scale_factor(scale_factor)?
|
||||
.with_device_scale_factor(DeviceScaleFactor::Scale100Percent)
|
||||
} else {
|
||||
entry
|
||||
};
|
||||
|
||||
let entry = if let Some((physical_width, physical_height)) = physical_dims {
|
||||
entry.with_physical_dimensions(physical_width, physical_height)?
|
||||
} else {
|
||||
entry
|
||||
};
|
||||
|
||||
Ok(DisplayControlMonitorLayout::new(&[entry]).unwrap())
|
||||
}
|
||||
|
||||
pub fn monitors(&self) -> &[MonitorLayoutEntry] {
|
||||
@@ -350,18 +378,21 @@ impl MonitorLayoutEntry {
|
||||
|
||||
/// Creates a new [`MonitorLayoutEntry`].
|
||||
///
|
||||
/// - `width` and `height` MUST be >= 200 and <= 8192.
|
||||
/// - `width` SHOULD be even. If it is odd, it will be adjusted
|
||||
/// to the nearest even number by subtracting 1.
|
||||
/// Per [2.2.2.2.1]:
|
||||
/// - 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.
|
||||
/// - The `height` MUST be greater than or equal to 200 pixels and less than or equal to 8192 pixels.
|
||||
///
|
||||
/// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c
|
||||
fn new_impl(mut width: u32, height: u32) -> PduResult<Self> {
|
||||
if width % 2 != 0 {
|
||||
let prev_width = width;
|
||||
width = width.saturating_sub(1);
|
||||
warn!(
|
||||
"Monitor width cannot be odd, adjusting from {} to {}",
|
||||
"Monitor width cannot be odd, adjusting from [{}] to [{}]",
|
||||
prev_width, width
|
||||
)
|
||||
}
|
||||
|
||||
validate_dimensions(width, height)?;
|
||||
|
||||
Ok(Self {
|
||||
@@ -373,19 +404,63 @@ impl MonitorLayoutEntry {
|
||||
physical_width: 0,
|
||||
physical_height: 0,
|
||||
orientation: 0,
|
||||
desktop_scale_factor: 100,
|
||||
device_scale_factor: 100,
|
||||
desktop_scale_factor: 0,
|
||||
device_scale_factor: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new primary monitor layout entry.
|
||||
/// Adjusts the display size to be within the valid range.
|
||||
///
|
||||
/// Per [2.2.2.2.1]:
|
||||
/// - 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.
|
||||
/// - The `height` MUST be greater than or equal to 200 pixels and less than or equal to 8192 pixels.
|
||||
///
|
||||
/// Functions that create [`MonitorLayoutEntry`] should typically use this function to adjust the display size first.
|
||||
///
|
||||
/// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c
|
||||
pub fn adjust_display_size(width: u32, height: u32) -> (u32, u32) {
|
||||
fn constrain(value: u32) -> u32 {
|
||||
if value < 200 {
|
||||
200
|
||||
} else if value > 8192 {
|
||||
8192
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
let mut width = width;
|
||||
if width % 2 != 0 {
|
||||
width = width.saturating_sub(1);
|
||||
}
|
||||
|
||||
(constrain(width), constrain(height))
|
||||
}
|
||||
|
||||
/// Creates a new primary [`MonitorLayoutEntry`].
|
||||
///
|
||||
/// Per [2.2.2.2.1]:
|
||||
/// - 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.
|
||||
/// - The `height` MUST be greater than or equal to 200 pixels and less than or equal to 8192 pixels.
|
||||
///
|
||||
/// Use [`MonitorLayoutEntry::adjust_display_size`] before calling this function to ensure the display size is within the valid range.
|
||||
///
|
||||
/// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c
|
||||
pub fn new_primary(width: u32, height: u32) -> PduResult<Self> {
|
||||
let mut entry = Self::new_impl(width, height)?;
|
||||
entry.is_primary = true;
|
||||
Ok(entry)
|
||||
}
|
||||
|
||||
/// Creates a new secondary monitor layout entry.
|
||||
/// Creates a new primary [`MonitorLayoutEntry`].
|
||||
///
|
||||
/// Per [2.2.2.2.1]:
|
||||
/// - 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.
|
||||
/// - The `height` MUST be greater than or equal to 200 pixels and less than or equal to 8192 pixels.
|
||||
///
|
||||
/// Use [`MonitorLayoutEntry::adjust_display_size`] before calling this function to ensure the display size is within the valid range.
|
||||
///
|
||||
/// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c
|
||||
pub fn new_secondary(width: u32, height: u32) -> PduResult<Self> {
|
||||
Self::new_impl(width, height)
|
||||
}
|
||||
@@ -416,7 +491,7 @@ impl MonitorLayoutEntry {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the monitor's desktop scale factor in percent. (Default is `100`)
|
||||
/// Sets the monitor's desktop scale factor in percent.
|
||||
///
|
||||
/// NOTE: As specified in [MS-RDPEDISP], if the desktop scale factor is not in the valid range
|
||||
/// (100..=500 percent), the monitor desktop scale factor is considered invalid and should be ignored.
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::pdu::{
|
||||
CapabilitiesResponsePdu, CapsVersion, ClosePdu, CreateResponsePdu, CreationStatus, DrdynvcClientPdu,
|
||||
DrdynvcServerPdu,
|
||||
};
|
||||
use crate::{encode_dvc_messages, DvcProcessor, DynamicChannelId, DynamicChannelSet};
|
||||
use crate::{encode_dvc_messages, DvcProcessor, DynamicChannelSet, DynamicVirtualChannel};
|
||||
use alloc::vec::Vec;
|
||||
use core::any::TypeId;
|
||||
use core::fmt;
|
||||
@@ -61,17 +61,11 @@ impl DrdynvcClient {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_dynamic_channel_by_type_id<T>(&self) -> Option<(&T, Option<DynamicChannelId>)>
|
||||
pub fn get_dvc_by_type_id<T>(&self) -> Option<&DynamicVirtualChannel>
|
||||
where
|
||||
T: DvcProcessor,
|
||||
{
|
||||
self.dynamic_channels
|
||||
.get_by_type_id(TypeId::of::<T>())
|
||||
.and_then(|(channel, channel_id)| {
|
||||
channel
|
||||
.channel_processor_downcast_ref()
|
||||
.map(|channel| (channel as &T, channel_id))
|
||||
})
|
||||
self.dynamic_channels.get_by_type_id(TypeId::of::<T>())
|
||||
}
|
||||
|
||||
fn create_capabilities_response(&mut self) -> SvcMessage {
|
||||
@@ -128,7 +122,7 @@ impl SvcProcessor for DrdynvcClient {
|
||||
self.dynamic_channels
|
||||
.attach_channel_id(channel_name.clone(), channel_id);
|
||||
let dynamic_channel = self.dynamic_channels.get_by_channel_name_mut(&channel_name).unwrap();
|
||||
(CreationStatus::OK, dynamic_channel.start(channel_id)?)
|
||||
(CreationStatus::OK, dynamic_channel.start()?)
|
||||
} else {
|
||||
(CreationStatus::NO_LISTENER, Vec::new())
|
||||
};
|
||||
|
||||
@@ -101,6 +101,10 @@ pub fn encode_dvc_messages(
|
||||
pub struct DynamicVirtualChannel {
|
||||
channel_processor: Box<dyn DvcProcessor + Send>,
|
||||
complete_data: CompleteData,
|
||||
/// The channel ID assigned by the server.
|
||||
///
|
||||
/// This field is `None` until the server assigns a channel ID.
|
||||
channel_id: Option<DynamicChannelId>,
|
||||
}
|
||||
|
||||
impl DynamicVirtualChannel {
|
||||
@@ -108,11 +112,28 @@ impl DynamicVirtualChannel {
|
||||
Self {
|
||||
channel_processor: Box::new(handler),
|
||||
complete_data: CompleteData::new(),
|
||||
channel_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn start(&mut self, channel_id: DynamicChannelId) -> PduResult<Vec<DvcMessage>> {
|
||||
self.channel_processor.start(channel_id)
|
||||
pub fn is_open(&self) -> bool {
|
||||
self.channel_id.is_some()
|
||||
}
|
||||
|
||||
pub fn channel_id(&self) -> Option<DynamicChannelId> {
|
||||
self.channel_id
|
||||
}
|
||||
|
||||
pub fn channel_processor_downcast_ref<T: DvcProcessor>(&self) -> Option<&T> {
|
||||
self.channel_processor.as_any().downcast_ref()
|
||||
}
|
||||
|
||||
fn start(&mut self) -> PduResult<Vec<DvcMessage>> {
|
||||
if let Some(channel_id) = self.channel_id {
|
||||
self.channel_processor.start(channel_id)
|
||||
} else {
|
||||
Err(other_err!("DynamicVirtualChannel::start", "channel ID not set"))
|
||||
}
|
||||
}
|
||||
|
||||
fn process(&mut self, pdu: DrdynvcDataPdu) -> PduResult<Vec<DvcMessage>> {
|
||||
@@ -128,10 +149,6 @@ impl DynamicVirtualChannel {
|
||||
fn channel_name(&self) -> &str {
|
||||
self.channel_processor.channel_name()
|
||||
}
|
||||
|
||||
fn channel_processor_downcast_ref<T: DvcProcessor>(&self) -> Option<&T> {
|
||||
self.channel_processor.as_any().downcast_ref()
|
||||
}
|
||||
}
|
||||
|
||||
struct DynamicChannelSet {
|
||||
@@ -160,15 +177,17 @@ impl DynamicChannelSet {
|
||||
|
||||
fn attach_channel_id(&mut self, name: DynamicChannelName, id: DynamicChannelId) -> Option<DynamicChannelId> {
|
||||
self.channel_id_to_name.insert(id, name.clone());
|
||||
self.name_to_channel_id.insert(name, id)
|
||||
self.name_to_channel_id.insert(name.clone(), id);
|
||||
let dvc = self.get_by_channel_name_mut(&name)?;
|
||||
let old_id = dvc.channel_id;
|
||||
dvc.channel_id = Some(id);
|
||||
old_id
|
||||
}
|
||||
|
||||
fn get_by_type_id(&self, type_id: TypeId) -> Option<(&DynamicVirtualChannel, Option<DynamicChannelId>)> {
|
||||
self.type_id_to_name.get(&type_id).and_then(|name| {
|
||||
self.channels
|
||||
.get(name)
|
||||
.map(|channel| (channel, self.name_to_channel_id.get(name).copied()))
|
||||
})
|
||||
fn get_by_type_id(&self, type_id: TypeId) -> Option<&DynamicVirtualChannel> {
|
||||
self.type_id_to_name
|
||||
.get(&type_id)
|
||||
.and_then(|name| self.channels.get(name))
|
||||
}
|
||||
|
||||
fn get_by_channel_name(&self, name: &DynamicChannelName) -> Option<&DynamicVirtualChannel> {
|
||||
|
||||
@@ -170,11 +170,8 @@ pub struct RfxChannel {
|
||||
pub struct RfxChannelWidth(i16);
|
||||
|
||||
impl RfxChannelWidth {
|
||||
pub fn new(value: i16) -> Result<Self, RfxError> {
|
||||
(1..=4096)
|
||||
.contains(&value)
|
||||
.then_some(Self(value))
|
||||
.ok_or(RfxError::InvalidChannelWidth(value))
|
||||
pub fn new(value: i16) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
|
||||
pub fn as_u16(self) -> u16 {
|
||||
@@ -192,11 +189,8 @@ impl RfxChannelWidth {
|
||||
pub struct RfxChannelHeight(i16);
|
||||
|
||||
impl RfxChannelHeight {
|
||||
pub fn new(value: i16) -> Result<Self, RfxError> {
|
||||
(1..=2048)
|
||||
.contains(&value)
|
||||
.then_some(Self(value))
|
||||
.ok_or(RfxError::InvalidChannelWidth(value))
|
||||
pub fn new(value: i16) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
|
||||
pub fn as_u16(self) -> u16 {
|
||||
@@ -218,10 +212,10 @@ impl PduBufferParsing<'_> for RfxChannel {
|
||||
}
|
||||
|
||||
let width = buffer.read_i16::<LittleEndian>()?;
|
||||
let width = RfxChannelWidth::new(width)?;
|
||||
let width = RfxChannelWidth::new(width);
|
||||
|
||||
let height = buffer.read_i16::<LittleEndian>()?;
|
||||
let height = RfxChannelHeight::new(height)?;
|
||||
let height = RfxChannelHeight::new(height);
|
||||
|
||||
Ok(Self { width, height })
|
||||
}
|
||||
|
||||
@@ -51,6 +51,9 @@ macro_rules! user_header_try {
|
||||
|
||||
const USER_DATA_HEADER_SIZE: usize = 4;
|
||||
|
||||
/// 2.2.1.3 Client MCS Connect Initial PDU with GCC Conference Create Request
|
||||
///
|
||||
/// [2.2.1.3]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/db6713ee-1c0e-4064-a3b3-0fac30b4037b
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ClientGccBlocks {
|
||||
pub core: ClientCoreData,
|
||||
|
||||
@@ -37,7 +37,9 @@ const DESKTOP_ORIENTATION_SIZE: usize = 2;
|
||||
const DESKTOP_SCALE_FACTOR_SIZE: usize = 4;
|
||||
const DEVICE_SCALE_FACTOR_SIZE: usize = 4;
|
||||
|
||||
/// TS_UD_CS_CORE (required part)
|
||||
/// 2.2.1.3.2 Client Core Data (TS_UD_CS_CORE) (required part)
|
||||
///
|
||||
/// [2.2.1.3.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/00f1da4a-ee9c-421a-852f-c19f92343d73
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ClientCoreData {
|
||||
pub version: RdpVersion,
|
||||
@@ -182,7 +184,12 @@ impl<'de> PduDecode<'de> for ClientCoreData {
|
||||
}
|
||||
}
|
||||
|
||||
/// TS_UD_CS_CORE (optional part)
|
||||
/// 2.2.1.3.2 Client Core Data (TS_UD_CS_CORE) (optional part)
|
||||
///
|
||||
/// For every field in this structure, the previous fields MUST be present in order to be a valid structure.
|
||||
/// It is incumbent on the user of this structure to ensure that the structure is valid.
|
||||
///
|
||||
/// [2.2.1.3.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/00f1da4a-ee9c-421a-852f-c19f92343d73
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct ClientCoreOptionalData {
|
||||
/// The requested color depth. Values in this field MUST be ignored if the highColorDepth field is present.
|
||||
@@ -217,26 +224,56 @@ impl PduEncode for ClientCoreOptionalData {
|
||||
}
|
||||
|
||||
if let Some(value) = self.client_product_id {
|
||||
if self.post_beta2_color_depth.is_none() {
|
||||
return Err(invalid_message_err!(
|
||||
"postBeta2ColorDepth",
|
||||
"postBeta2ColorDepth must be present"
|
||||
));
|
||||
}
|
||||
dst.write_u16(value);
|
||||
}
|
||||
|
||||
if let Some(value) = self.serial_number {
|
||||
if self.client_product_id.is_none() {
|
||||
return Err(invalid_message_err!(
|
||||
"clientProductId",
|
||||
"clientProductId must be present"
|
||||
));
|
||||
}
|
||||
dst.write_u32(value);
|
||||
}
|
||||
|
||||
if let Some(value) = self.high_color_depth {
|
||||
if self.serial_number.is_none() {
|
||||
return Err(invalid_message_err!("serialNumber", "serialNumber must be present"));
|
||||
}
|
||||
dst.write_u16(value.to_u16().unwrap());
|
||||
}
|
||||
|
||||
if let Some(value) = self.supported_color_depths {
|
||||
if self.high_color_depth.is_none() {
|
||||
return Err(invalid_message_err!("highColorDepth", "highColorDepth must be present"));
|
||||
}
|
||||
dst.write_u16(value.bits());
|
||||
}
|
||||
|
||||
if let Some(value) = self.early_capability_flags {
|
||||
if self.supported_color_depths.is_none() {
|
||||
return Err(invalid_message_err!(
|
||||
"supportedColorDepths",
|
||||
"supportedColorDepths must be present"
|
||||
));
|
||||
}
|
||||
dst.write_u16(value.bits());
|
||||
}
|
||||
|
||||
if let Some(ref value) = self.dig_product_id {
|
||||
if self.early_capability_flags.is_none() {
|
||||
return Err(invalid_message_err!(
|
||||
"earlyCapabilityFlags",
|
||||
"earlyCapabilityFlags must be present"
|
||||
));
|
||||
}
|
||||
let mut dig_product_id_buffer = utils::to_utf16_bytes(value);
|
||||
dig_product_id_buffer.resize(DIG_PRODUCT_ID_SIZE - 2, 0);
|
||||
dig_product_id_buffer.extend_from_slice([0; 2].as_ref()); // UTF-16 null terminator
|
||||
@@ -245,31 +282,67 @@ impl PduEncode for ClientCoreOptionalData {
|
||||
}
|
||||
|
||||
if let Some(value) = self.connection_type {
|
||||
if self.dig_product_id.is_none() {
|
||||
return Err(invalid_message_err!("digProductId", "digProductId must be present"));
|
||||
}
|
||||
dst.write_u8(value.to_u8().unwrap());
|
||||
write_padding!(dst, 1);
|
||||
}
|
||||
|
||||
if let Some(value) = self.server_selected_protocol {
|
||||
if self.connection_type.is_none() {
|
||||
return Err(invalid_message_err!("connectionType", "connectionType must be present"));
|
||||
}
|
||||
dst.write_u32(value.bits())
|
||||
}
|
||||
|
||||
if let Some(value) = self.desktop_physical_width {
|
||||
if self.server_selected_protocol.is_none() {
|
||||
return Err(invalid_message_err!(
|
||||
"serverSelectedProtocol",
|
||||
"serverSelectedProtocol must be present"
|
||||
));
|
||||
}
|
||||
dst.write_u32(value);
|
||||
}
|
||||
|
||||
if let Some(value) = self.desktop_physical_height {
|
||||
if self.desktop_physical_width.is_none() {
|
||||
return Err(invalid_message_err!(
|
||||
"desktopPhysicalWidth",
|
||||
"desktopPhysicalWidth must be present"
|
||||
));
|
||||
}
|
||||
dst.write_u32(value);
|
||||
}
|
||||
|
||||
if let Some(value) = self.desktop_orientation {
|
||||
if self.desktop_physical_height.is_none() {
|
||||
return Err(invalid_message_err!(
|
||||
"desktopPhysicalHeight",
|
||||
"desktopPhysicalHeight must be present"
|
||||
));
|
||||
}
|
||||
dst.write_u16(value);
|
||||
}
|
||||
|
||||
if let Some(value) = self.desktop_scale_factor {
|
||||
if self.desktop_orientation.is_none() {
|
||||
return Err(invalid_message_err!(
|
||||
"desktopOrientation",
|
||||
"desktopOrientation must be present"
|
||||
));
|
||||
}
|
||||
dst.write_u32(value);
|
||||
}
|
||||
|
||||
if let Some(value) = self.device_scale_factor {
|
||||
if self.desktop_scale_factor.is_none() {
|
||||
return Err(invalid_message_err!(
|
||||
"desktopScaleFactor",
|
||||
"desktopScaleFactor must be present"
|
||||
));
|
||||
}
|
||||
dst.write_u32(value);
|
||||
}
|
||||
|
||||
|
||||
@@ -122,6 +122,10 @@ pub(crate) fn sizeof_length(length: u16) -> usize {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn sizeof_long_length() -> usize {
|
||||
2
|
||||
}
|
||||
|
||||
pub(crate) fn sizeof_u32(value: u32) -> usize {
|
||||
if value <= 0xff {
|
||||
2
|
||||
|
||||
@@ -37,8 +37,8 @@ impl RfxEncoder {
|
||||
};
|
||||
let context = rfx::Headers::Context(context);
|
||||
let channels = rfx::ChannelsPdu(vec![RfxChannel {
|
||||
width: RfxChannelWidth::new(width).map_err(|e| custom_err!("width", e))?,
|
||||
height: RfxChannelHeight::new(height).map_err(|e| custom_err!("height", e))?,
|
||||
width: RfxChannelWidth::new(width),
|
||||
height: RfxChannelHeight::new(height),
|
||||
}]);
|
||||
let channels = rfx::Headers::Channels(channels);
|
||||
let version = rfx::CodecVersionsPdu;
|
||||
|
||||
@@ -22,4 +22,5 @@ ironrdp-dvc.workspace = true
|
||||
ironrdp-error.workspace = true
|
||||
ironrdp-graphics.workspace = true
|
||||
ironrdp-pdu = { workspace = true, features = ["std"] }
|
||||
ironrdp-displaycontrol.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
@@ -2,6 +2,8 @@ use std::rc::Rc;
|
||||
|
||||
use ironrdp_connector::connection_activation::ConnectionActivationSequence;
|
||||
use ironrdp_connector::ConnectionResult;
|
||||
use ironrdp_displaycontrol::client::DisplayControlClient;
|
||||
use ironrdp_dvc::{DrdynvcClient, DvcProcessor, DynamicVirtualChannel};
|
||||
use ironrdp_graphics::pointer::DecodedPointer;
|
||||
use ironrdp_pdu::geometry::InclusiveRectangle;
|
||||
use ironrdp_pdu::input::fast_path::{FastPathInput, FastPathInputEvent};
|
||||
@@ -150,6 +152,10 @@ impl ActiveStage {
|
||||
self.fast_path_processor = processor;
|
||||
}
|
||||
|
||||
pub fn set_no_server_pointer(&mut self, no_server_pointer: bool) {
|
||||
self.no_server_pointer = no_server_pointer;
|
||||
}
|
||||
|
||||
/// Encodes client-side graceful shutdown request. Note that upon sending this request,
|
||||
/// client should wait for server's ShutdownDenied PDU before closing the connection.
|
||||
///
|
||||
@@ -177,6 +183,10 @@ impl ActiveStage {
|
||||
self.x224_processor.get_svc_processor_mut()
|
||||
}
|
||||
|
||||
pub fn get_dvc<T: DvcProcessor + 'static>(&mut self) -> Option<&DynamicVirtualChannel> {
|
||||
self.x224_processor.get_dvc::<T>()
|
||||
}
|
||||
|
||||
/// Completes user's SVC request with data, required to sent it over the network and returns
|
||||
/// a buffer with encoded data.
|
||||
pub fn process_svc_processor_messages<C: SvcProcessor + 'static>(
|
||||
@@ -185,6 +195,56 @@ impl ActiveStage {
|
||||
) -> SessionResult<Vec<u8>> {
|
||||
self.x224_processor.process_svc_processor_messages(messages)
|
||||
}
|
||||
|
||||
/// Fully encodes a resize request for sending over the Display Control Virtual Channel.
|
||||
///
|
||||
/// If the Display Control Virtual Channel is not available, or not yet connected, this method
|
||||
/// will return `None`.
|
||||
///
|
||||
/// Per [2.2.2.2.1]:
|
||||
/// - 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.
|
||||
/// - The `height` MUST be greater than or equal to 200 pixels and less than or equal to 8192 pixels.
|
||||
/// - The `scale_factor` MUST be ignored if it is less than 100 percent or greater than 500 percent.
|
||||
/// - The `physical_dims` (width, height) MUST be ignored if either is less than 10 mm or greater than 10,000 mm.
|
||||
///
|
||||
/// Use [`ironrdp_displaycontrol::pdu::MonitorLayoutEntry::adjust_display_size`] to adjust `width` and `height` before calling this function
|
||||
/// to ensure the display size is within the valid range.
|
||||
///
|
||||
/// [2.2.2.2.2]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpedisp/ea2de591-9203-42cd-9908-be7a55237d1c
|
||||
pub fn encode_resize(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
scale_factor: Option<u32>,
|
||||
physical_dims: Option<(u32, u32)>,
|
||||
) -> Option<SessionResult<Vec<u8>>> {
|
||||
if let Some(dvc) = self.get_dvc::<DisplayControlClient>() {
|
||||
if dvc.is_open() {
|
||||
let display_control = dvc.channel_processor_downcast_ref::<DisplayControlClient>()?;
|
||||
let channel_id = dvc.channel_id().unwrap(); // Safe to unwrap, as we checked if the channel is open
|
||||
let svc_messages = match display_control.encode_single_primary_monitor(
|
||||
channel_id,
|
||||
width,
|
||||
height,
|
||||
scale_factor,
|
||||
physical_dims,
|
||||
) {
|
||||
Ok(messages) => messages,
|
||||
Err(e) => return Some(Err(SessionError::pdu(e))),
|
||||
};
|
||||
|
||||
return Some(
|
||||
self.process_svc_processor_messages(SvcProcessorMessages::<DrdynvcClient>::new(svc_messages)),
|
||||
);
|
||||
} else {
|
||||
debug!("Could not encode a resize: Display Control Virtual Channel is not yet connected");
|
||||
}
|
||||
} else {
|
||||
debug!("Could not encode a resize: Display Control Virtual Channel is not available");
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user