diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 5253895dd..e32313987 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -2642,6 +2642,91 @@ pub fn wide_string(s: &str) -> Vec { .collect() } +// This only changes mstsc's top-level window title. The full-screen connection +// bar is rendered separately and cannot be customized when mstsc.exe is +// launched as an independent process. +pub fn set_rdp_window_title(mut child: std::process::Child, name: String) { + let name: String = name.chars().filter(|c| !c.is_control()).take(120).collect(); + if name.is_empty() { + return; + } + let process_id = child.id(); + // mstsc owns the title and can restore "localhost" while connecting or + // reconnecting. Follow only the process we launched and reapply the peer + // name until it exits, so concurrent RDP sessions cannot rename each other. + if let Err(err) = std::thread::Builder::new() + .name("rdp-window-title".to_owned()) + .spawn(move || { + let mut warned = false; + loop { + match child.try_wait() { + Ok(Some(_)) => break, + Err(err) => { + log::warn!("Failed to query mstsc process: {}", err); + break; + } + Ok(None) => match set_process_rdp_window_title(process_id, &name) { + Ok(()) => warned = false, + Err(err) if !warned => { + log::warn!("Failed to set RDP window title: {}", err); + warned = true; + } + Err(_) => {} + }, + } + std::thread::sleep(Duration::from_millis(500)); + } + }) + { + log::warn!("Failed to start RDP window title thread: {}", err); + } +} + +fn set_process_rdp_window_title(process_id: DWORD, name: &str) -> io::Result<()> { + struct Context { + process_id: DWORD, + title: Vec, + error: Option, + } + + unsafe extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL { + let context = &mut *(lparam as *mut Context); + let mut window_process_id = 0; + GetWindowThreadProcessId(hwnd, &mut window_process_id); + if window_process_id != context.process_id || IsWindowVisible(hwnd) == FALSE { + return TRUE; + } + let len = GetWindowTextLengthW(hwnd); + if len <= 0 { + return TRUE; + } + let mut title = vec![0u16; len as usize + 1]; + let len = GetWindowTextW(hwnd, title.as_mut_ptr(), title.len() as _); + if len > 0 && String::from_utf16_lossy(&title[..len as usize]).contains("localhost") { + if SetWindowTextW(hwnd, context.title.as_ptr()) == FALSE { + context.error = Some(io::Error::last_os_error()); + return FALSE; + } + } + TRUE + } + + let mut context = Context { + process_id, + title: wide_string(name), + error: None, + }; + let enumerated = + unsafe { EnumWindows(Some(enum_window), &mut context as *mut Context as LPARAM) }; + if let Some(err) = context.error { + return Err(err); + } + if enumerated == FALSE { + return Err(io::Error::last_os_error()); + } + Ok(()) +} + /// send message to currently shown window pub fn send_message_to_hnwd( class_name: &str, diff --git a/src/port_forward.rs b/src/port_forward.rs index 7a3f8715c..392ed3c67 100644 --- a/src/port_forward.rs +++ b/src/port_forward.rs @@ -15,7 +15,7 @@ use hbb_common::{ ResultType, Stream, }; -fn run_rdp(port: u16) { +fn run_rdp(port: u16, name: &str) { std::process::Command::new("cmdkey") .arg("/delete:localhost") .output() @@ -35,10 +35,37 @@ fn run_rdp(port: u16) { .output() .ok(); } - std::process::Command::new("mstsc") + // Keep using /v instead of a generated .rdp file: mstsc then preserves the + // user's Default.rdp settings and avoids unsigned-file warnings or policies. + match std::process::Command::new("mstsc") .arg(format!("/v:localhost:{}", port)) .spawn() - .ok(); + { + Ok(child) => { + #[cfg(windows)] + crate::platform::set_rdp_window_title(child, name.to_owned()); + #[cfg(not(windows))] + let _ = (child, name); + } + Err(err) => log::warn!("Failed to launch mstsc: {}", err), + } +} + +// Show the peer identity with its hostname, using the ID when no alias exists. +fn rdp_display_name(lc: &Arc>, id: &str) -> String { + let lc = lc.read().unwrap(); + let alias = lc + .options + .get("alias") + .map(|s| s.trim()) + .unwrap_or_default(); + let hostname = lc.info.hostname.trim(); + let identity = if !alias.is_empty() { alias } else { id }; + if hostname.is_empty() || hostname == identity { + identity.to_owned() + } else { + format!("{} ({})", identity, hostname) + } } pub async fn listen( @@ -58,7 +85,7 @@ pub async fn listen( log::info!("listening on port {:?}", addr); let is_rdp = port == 0; if is_rdp { - run_rdp(addr.port()); + run_rdp(addr.port(), &rdp_display_name(&lc, &id)); } let mut ui_receiver = ui_receiver; loop { @@ -96,7 +123,7 @@ pub async fn listen( } Some(Data::NewRDP) => { println!("receive run_rdp from ui_receiver"); - run_rdp(addr.port()); + run_rdp(addr.port(), &rdp_display_name(&lc, &id)); } _ => {} }