diff --git a/crates/ironrdp-client/src/config.rs b/crates/ironrdp-client/src/config.rs index ffc040b7c..564a22fef 100644 --- a/crates/ironrdp-client/src/config.rs +++ b/crates/ironrdp-client/src/config.rs @@ -1,3 +1,4 @@ +#![allow(clippy::print_stdout)] use core::num::ParseIntError; use core::str::FromStr; @@ -233,6 +234,10 @@ struct Args { /// The clipboard type #[clap(long, value_enum, value_parser, default_value_t = ClipboardType::Default)] clipboard_type: ClipboardType, + + /// The bitmap codecs to use (remotefx:on, ...) + #[clap(long, value_parser, num_args = 1.., value_delimiter = ',')] + codecs: Vec, } impl Config { @@ -263,18 +268,25 @@ impl Config { .context("Password prompt")? }; - let bitmap = if let Some(color_depth) = args.color_depth { + let codecs: Vec<_> = args.codecs.iter().map(|s| s.as_str()).collect(); + let codecs = match client_codecs_capabilities(&codecs) { + Ok(codecs) => codecs, + Err(help) => { + print!("{}", help); + std::process::exit(0); + } + }; + let mut bitmap = connector::BitmapConfig { + color_depth: 32, + lossy_compression: true, + codecs, + }; + + if let Some(color_depth) = args.color_depth { if color_depth != 16 && color_depth != 32 { anyhow::bail!("Invalid color depth. Only 16 and 32 bit color depths are supported."); } - - Some(connector::BitmapConfig { - color_depth, - lossy_compression: true, - codecs: client_codecs_capabilities(), - }) - } else { - None + bitmap.color_depth = color_depth; }; let clipboard_type = if args.clipboard_type == ClipboardType::Default { @@ -306,7 +318,7 @@ impl Config { height: DEFAULT_HEIGHT, }, desktop_scale_factor: 0, // Default to 0 per FreeRDP - bitmap, + bitmap: Some(bitmap), client_build: semver::Version::parse(env!("CARGO_PKG_VERSION")) .map(|version| version.major * 100 + version.minor * 10 + version.patch) .unwrap_or(0) diff --git a/crates/ironrdp-connector/src/connection_activation.rs b/crates/ironrdp-connector/src/connection_activation.rs index c3ecb0a3b..22fd814eb 100644 --- a/crates/ironrdp-connector/src/connection_activation.rs +++ b/crates/ironrdp-connector/src/connection_activation.rs @@ -360,7 +360,7 @@ fn create_client_confirm_active( .bitmap .as_ref() .map(|b| b.codecs.clone()) - .unwrap_or_else(client_codecs_capabilities), + .unwrap_or_else(|| client_codecs_capabilities(&[]).unwrap()), ), CapabilitySet::FrameAcknowledge(FrameAcknowledge { // FIXME(#447): Revert this to 2 per FreeRDP. diff --git a/crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_codecs.rs b/crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_codecs.rs index ad2e8e74e..07c28b325 100644 --- a/crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_codecs.rs +++ b/crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_codecs.rs @@ -641,17 +641,77 @@ impl CodecId { } } -pub fn client_codecs_capabilities() -> BitmapCodecs { - let codecs = vec![Codec { - id: CODEC_ID_REMOTEFX.0, - property: CodecProperty::RemoteFx(RemoteFxContainer::ClientContainer(RfxClientCapsContainer { - capture_flags: CaptureFlags::empty(), - caps_data: RfxCaps(RfxCapset(vec![RfxICap { - flags: RfxICapFlags::empty(), - entropy_bits: EntropyBits::Rlgr3, - }])), - })), - }]; - - BitmapCodecs(codecs) +/// This function generates a list of client codec capabilities based on the +/// provided configuration. +/// +/// # Arguments +/// +/// * `config` - A slice of string slices that specifies which codecs to include +/// in the capabilities. Codecs can be explicitly turned on ("codec:on") or +/// off ("codec:off"). +/// +/// # List of codecs +/// +/// * `remotefx` (on by default) +/// +/// # Returns +/// +/// A vector of `Codec` structs representing the codec capabilities, or an error +/// suitable for CLI. +pub fn client_codecs_capabilities(config: &[&str]) -> Result { + use std::collections::HashMap; + + fn parse_codecs_config<'a>(codecs: &'a [&'a str]) -> Result, String> { + let mut result = HashMap::new(); + + for &codec_str in codecs { + if let Some(colon_index) = codec_str.find(':') { + let codec_name = &codec_str[0..colon_index]; + let state_str = &codec_str[colon_index + 1..]; + + let state = match state_str { + "on" => true, + "off" => false, + _ => return Err(format!("Unhandled configuration: {}", state_str)), + }; + + result.insert(codec_name, state); + } else { + // No colon found, assume it's "on" + result.insert(codec_str, true); + } + } + + Ok(result) + } + + if config.contains(&"help") { + return Err(r#" +List of codecs: +- `remotefx` (on by default) +"# + .to_owned()); + } + let mut config = parse_codecs_config(config)?; + let mut codecs = vec![]; + + if config.remove("remotefx").unwrap_or(true) { + codecs.push(Codec { + id: CODEC_ID_REMOTEFX.0, + property: CodecProperty::RemoteFx(RemoteFxContainer::ClientContainer(RfxClientCapsContainer { + capture_flags: CaptureFlags::empty(), + caps_data: RfxCaps(RfxCapset(vec![RfxICap { + flags: RfxICapFlags::empty(), + entropy_bits: EntropyBits::Rlgr3, + }])), + })), + }); + } + + let codec_names = config.keys().copied().collect::>().join(", "); + if !codec_names.is_empty() { + return Err(format!("Unknown codecs: {}", codec_names)); + } + + Ok(BitmapCodecs(codecs)) } diff --git a/crates/ironrdp-testsuite-core/tests/session/mod.rs b/crates/ironrdp-testsuite-core/tests/session/mod.rs index 23765d8f9..fd03457bf 100644 --- a/crates/ironrdp-testsuite-core/tests/session/mod.rs +++ b/crates/ironrdp-testsuite-core/tests/session/mod.rs @@ -1 +1,24 @@ mod rfx; + +#[cfg(test)] +mod tests { + use ironrdp_pdu::rdp::capability_sets::{client_codecs_capabilities, CodecProperty}; + + #[test] + fn test_codecs_capabilities() { + let config = &[]; + let _capabilities = client_codecs_capabilities(config).unwrap(); + + let config = &["badcodec"]; + assert!(client_codecs_capabilities(config).is_err()); + + let config = &["remotefx:on"]; + let capabilities = client_codecs_capabilities(config).unwrap(); + assert_eq!(capabilities.0.len(), 1); + assert!(matches!(capabilities.0[0].property, CodecProperty::RemoteFx(_))); + + let config = &["remotefx:off"]; + let capabilities = client_codecs_capabilities(config).unwrap(); + assert_eq!(capabilities.0.len(), 0); + } +} diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index 78fd331ba..39b773a60 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -839,7 +839,7 @@ fn build_config( bitmap: Some(connector::BitmapConfig { color_depth: 16, lossy_compression: true, - codecs: client_codecs_capabilities(), + codecs: client_codecs_capabilities(&[]).unwrap(), }), #[allow(clippy::arithmetic_side_effects)] // fine unless we end up with an insanely big version client_build: semver::Version::parse(env!("CARGO_PKG_VERSION"))