Skip to content

Commit 3652726

Browse files
committed
.
1 parent 3299db4 commit 3652726

2 files changed

Lines changed: 48 additions & 41 deletions

File tree

crates/ironrdp-client/src/rdp.rs

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,13 @@ use smallvec::SmallVec;
4444
use tokio::io::{AsyncRead, AsyncWrite};
4545
use tokio::net::TcpStream;
4646
use tokio::sync::mpsc;
47-
#[cfg(all(windows, feature = "dvc-com-plugin"))]
48-
use tracing::error;
49-
use tracing::{debug, info, trace, warn};
47+
// `error` and `warn` are used only by feature-gated code paths (clipboard, dvc-com-plugin); keep
48+
// the imports unconditional so the macros are always in scope regardless of features.
49+
#[cfg_attr(
50+
not(any(feature = "clipboard", all(windows, feature = "dvc-com-plugin"))),
51+
expect(unused_imports, reason = "error/warn used only under feature-gated code paths")
52+
)]
53+
use tracing::{debug, error, info, trace, warn};
5054

5155
use crate::config::{Config, RDCleanPathConfig};
5256

@@ -133,8 +137,14 @@ pub struct RdpClient {
133137
impl RdpClient {
134138
pub async fn run(mut self) {
135139
loop {
136-
let (connection_result, framed) = if let Some(rdcleanpath) = self.config.rdcleanpath.as_ref() {
137-
match connect_ws(&self.config, rdcleanpath, &self.dvc_pipe_proxy_factory).await {
140+
// Split the borrow: `connect_ws` needs `&mut self.config` (to consume optional
141+
// backends), but we also need to inspect `self.config.rdcleanpath`. Take the
142+
// `RDCleanPathConfig` out for the duration of the call and put it back afterwards
143+
// so reconnect attempts continue to use the same path.
144+
let (connection_result, framed) = if let Some(rdcleanpath) = self.config.rdcleanpath.take() {
145+
let result = connect_ws(&mut self.config, &rdcleanpath, &self.dvc_pipe_proxy_factory).await;
146+
self.config.rdcleanpath = Some(rdcleanpath);
147+
match result {
138148
Ok(result) => result,
139149
Err(e) => {
140150
let _ = self
@@ -145,7 +155,7 @@ impl RdpClient {
145155
}
146156
}
147157
} else {
148-
match connect(&self.config, &self.dvc_pipe_proxy_factory).await {
158+
match connect(&mut self.config, &self.dvc_pipe_proxy_factory).await {
149159
Ok(result) => result,
150160
Err(e) => {
151161
let _ = self
@@ -197,7 +207,7 @@ impl<T> AsyncReadWrite for T where T: AsyncRead + AsyncWrite {}
197207
type UpgradedFramed = ironrdp_tokio::TokioFramed<Box<dyn AsyncReadWrite + Unpin + Send + Sync>>;
198208

199209
async fn connect(
200-
config: &Config,
210+
config: &mut Config,
201211
dvc_pipe_proxy_factory: &DvcPipeProxyFactory,
202212
) -> ConnectorResult<(ConnectionResult, UpgradedFramed)> {
203213
let dest = config.destination.to_string();
@@ -271,7 +281,7 @@ async fn connect(
271281
}
272282

273283
async fn connect_ws(
274-
config: &Config,
284+
config: &mut Config,
275285
rdcleanpath: &RDCleanPathConfig,
276286
dvc_pipe_proxy_factory: &DvcPipeProxyFactory,
277287
) -> ConnectorResult<(ConnectionResult, UpgradedFramed)> {
@@ -397,24 +407,26 @@ fn build_drdynvc(config: &Config, dvc_pipe_proxy_factory: &DvcPipeProxyFactory)
397407
}
398408

399409
/// Attach the optional static channels (sound, rdpdr, clipboard) based on Cargo + runtime features.
400-
fn attach_optional_channels(connector: &mut connector::ClientConnector, _config: &Config) {
410+
#[cfg_attr(
411+
not(any(feature = "sound", feature = "rdpdr", feature = "clipboard")),
412+
expect(unused_variables, reason = "no feature-gated channel uses the connector")
413+
)]
414+
fn attach_optional_channels(connector: &mut connector::ClientConnector, _config: &mut Config) {
401415
#[cfg(feature = "sound")]
402416
if _config.features.sound {
403-
let backend: Box<dyn rdpsnd::client::RdpsndClientHandler> = if let Some(b) = clone_sound_backend(_config) {
404-
b
405-
} else {
406-
Box::new(ironrdp_rdpsnd_native::cpal::RdpsndBackend::new())
407-
};
417+
let backend: Box<dyn rdpsnd::client::RdpsndClientHandler> = _config
418+
.sound_backend
419+
.take()
420+
.unwrap_or_else(|| Box::new(ironrdp_rdpsnd_native::cpal::RdpsndBackend::new()));
408421
connector.attach_static_channel(rdpsnd::client::Rdpsnd::new(backend));
409422
}
410423

411424
#[cfg(feature = "rdpdr")]
412425
if _config.features.rdpdr {
413-
let backend: Box<dyn rdpdr::backend::RdpdrBackend> = if let Some(b) = clone_rdpdr_backend(_config) {
414-
b
415-
} else {
416-
Box::new(NoopRdpdrBackend {})
417-
};
426+
let backend: Box<dyn rdpdr::backend::RdpdrBackend> = _config
427+
.rdpdr_backend
428+
.take()
429+
.unwrap_or_else(|| Box::new(NoopRdpdrBackend {}));
418430
let mut rdpdr_channel = rdpdr::Rdpdr::new(backend, "IronRDP".to_owned());
419431
if _config.features.smartcard {
420432
rdpdr_channel = rdpdr_channel.with_smartcard(0);
@@ -423,31 +435,21 @@ fn attach_optional_channels(connector: &mut connector::ClientConnector, _config:
423435
}
424436

425437
#[cfg(feature = "clipboard")]
426-
if _config.features.clipboard {
438+
if _config.features.clipboard && _config.clipboard_type != crate::config::ClipboardType::Disable {
427439
if let Some(factory) = _config.clipboard_backend.as_deref() {
428440
let backend = factory.build_cliprdr_backend();
429441
connector.attach_static_channel(cliprdr::Cliprdr::new(backend));
442+
} else {
443+
warn!(
444+
"Clipboard feature is enabled but no `clipboard_backend` factory was supplied via \
445+
`ConfigBuilder::with_clipboard_backend`; the CLIPRDR static channel will not be attached. \
446+
This is expected when `clipboard_type = Disable`; otherwise embedders must provide a backend \
447+
factory (the library does not pull in a native clipboard backend on its own)."
448+
);
430449
}
431450
}
432451
}
433452

434-
/// Sound backends are not Clone; we transfer ownership only if it has been explicitly set
435-
/// via the builder. (Default cpal backend is constructed inline.)
436-
#[cfg(feature = "sound")]
437-
fn clone_sound_backend(_config: &Config) -> Option<Box<dyn rdpsnd::client::RdpsndClientHandler>> {
438-
// The Config's sound_backend slot is consumed during build for now (None for V1).
439-
// To allow a custom backend to be honored, callers may pass it via ConfigBuilder which moves
440-
// it into Config; we do not support reconnect-with-custom-backend at this stage.
441-
let _ = _config;
442-
None
443-
}
444-
445-
#[cfg(feature = "rdpdr")]
446-
fn clone_rdpdr_backend(_config: &Config) -> Option<Box<dyn rdpdr::backend::RdpdrBackend>> {
447-
let _ = _config;
448-
None
449-
}
450-
451453
#[cfg(feature = "gateway")]
452454
fn network_client() -> ReqwestNetworkClient {
453455
ReqwestNetworkClient::new()

crates/ironrdp-viewer/src/main.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,15 @@ fn main() -> anyhow::Result<()> {
2828
let (input_event_sender, input_event_receiver) = RdpInputEvent::create_channel();
2929
let (output_event_sender, mut output_event_receiver) = mpsc::channel::<RdpOutputEvent>(64);
3030

31-
// NOTE: we need to keep `win_clipboard` alive, otherwise it will be dropped before IronRDP
32-
// starts and clipboard functionality will not be available.
31+
// NOTE: we need to keep `_win_clipboard` alive, otherwise it will be dropped before IronRDP
32+
// starts and clipboard functionality will not be available. The binding is intentionally
33+
// write-only (we only rely on its `Drop` happening at end of scope).
3334
#[cfg(windows)]
34-
let _win_clipboard;
35+
#[expect(
36+
clippy::collection_is_never_read,
37+
reason = "keeps WinClipboard alive until end of scope"
38+
)]
39+
let mut _win_clipboard: Option<ironrdp_cliprdr_native::WinClipboard> = None;
3540

3641
let mut config = build_config(parsed).context("configuration")?;
3742

@@ -51,7 +56,7 @@ fn main() -> anyhow::Result<()> {
5156

5257
let cliprdr = WinClipboard::new(ClientClipboardMessageProxy::new(input_event_sender.clone()))?;
5358
let factory = cliprdr.backend_factory();
54-
_win_clipboard = cliprdr;
59+
_win_clipboard = Some(cliprdr);
5560
Some(factory)
5661
}
5762
#[cfg(not(windows))]

0 commit comments

Comments
 (0)