@@ -44,9 +44,13 @@ use smallvec::SmallVec;
4444use tokio:: io:: { AsyncRead , AsyncWrite } ;
4545use tokio:: net:: TcpStream ;
4646use 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
5155use crate :: config:: { Config , RDCleanPathConfig } ;
5256
@@ -133,8 +137,14 @@ pub struct RdpClient {
133137impl 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 {}
197207type UpgradedFramed = ironrdp_tokio:: TokioFramed < Box < dyn AsyncReadWrite + Unpin + Send + Sync > > ;
198208
199209async 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
273283async 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" ) ]
452454fn network_client ( ) -> ReqwestNetworkClient {
453455 ReqwestNetworkClient :: new ( )
0 commit comments