feat(displaycontrol): hook up resize for ironrdp-client crate (#430)

This commit is contained in:
Isaiah Becker-Mayer
2024-04-29 22:20:02 +03:00
committed by GitHub
parent 5c42ade597
commit fd105e4b56
27 changed files with 393 additions and 91 deletions
Generated
+2
View File
@@ -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",
+1 -1
View File
@@ -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
+1
View File
@@ -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)
+10
View File
@@ -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(),
+1
View File
@@ -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();
+31 -14
View File
@@ -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;
}
}
+13 -5
View File
@@ -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
}));
}
+4
View File
@@ -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.
+17 -1
View File
@@ -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())
}
}
+94 -19
View File
@@ -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.
+4 -10
View File
@@ -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())
};
+32 -13
View File
@@ -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 })
}
+3
View File
@@ -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,
+75 -2
View File
@@ -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);
}
+4
View File
@@ -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
+2 -2
View File
@@ -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;
+1
View File
@@ -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