mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-06-27 10:44:53 +03:00
Refact/privacy mode 1 multi monitors (#15321)
* refact: privacy mdoe 1, multi-monitors Signed-off-by: fufesou <linlong1266@gmail.com> * fix: harden privacy mode overlay & capture cleanup Signed-off-by: fufesou <linlong1266@gmail.com> * Fix privacy mode edge cases after multi-monitor overlay changes Signed-off-by: fufesou <linlong1266@gmail.com> * Add missing changes Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
@@ -4,13 +4,14 @@ use hbb_common::{allow_err, bail, log, ResultType};
|
||||
use std::{
|
||||
ffi::CString,
|
||||
io::Error,
|
||||
mem::size_of,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use winapi::{
|
||||
shared::{
|
||||
minwindef::FALSE,
|
||||
minwindef::{BOOL, FALSE, LPARAM, TRUE},
|
||||
ntdef::{HANDLE, NULL},
|
||||
windef::HWND,
|
||||
windef::{HDC, HMONITOR, HWND, RECT},
|
||||
},
|
||||
um::{
|
||||
handleapi::CloseHandle,
|
||||
@@ -31,7 +32,13 @@ pub(super) const PRIVACY_MODE_IMPL: &str = "privacy_mode_impl_mag";
|
||||
pub const ORIGIN_PROCESS_EXE: &'static str = "C:\\Windows\\System32\\RuntimeBroker.exe";
|
||||
pub const WIN_TOPMOST_INJECTED_PROCESS_EXE: &'static str = "RuntimeBroker_rustdesk.exe";
|
||||
pub const INJECTED_PROCESS_EXE: &'static str = WIN_TOPMOST_INJECTED_PROCESS_EXE;
|
||||
pub(super) const PRIVACY_WINDOW_CLASS: &'static str = "RustDeskPrivacyWindowClass";
|
||||
pub(super) const PRIVACY_WINDOW_NAME: &'static str = "RustDeskPrivacyWindow";
|
||||
const PRIVACY_WINDOW_WAIT_MILLIS: u128 = 1_000;
|
||||
const PRIVACY_WINDOW_WAIT_EXTRA_MONITOR_MILLIS: u128 = 500;
|
||||
const PRIVACY_WINDOW_POLL_INTERVAL_MILLIS: u64 = 100;
|
||||
const WM_RUSTDESK_SHOW_WINDOWS: u32 = WM_APP + 3;
|
||||
const WM_RUSTDESK_HIDE_WINDOWS: u32 = WM_APP + 4;
|
||||
|
||||
struct WindowHandlers {
|
||||
hthread: u64,
|
||||
@@ -102,22 +109,17 @@ impl PrivacyMode for PrivacyModeImpl {
|
||||
);
|
||||
}
|
||||
|
||||
if self.handlers.is_default() {
|
||||
log::info!("turn_on_privacy, dll not found when started, try start");
|
||||
let should_start_broker = self.handlers.is_default();
|
||||
if should_start_broker {
|
||||
log::info!("turn_on_privacy, broker not running, try start");
|
||||
self.start()?;
|
||||
std::thread::sleep(std::time::Duration::from_millis(1_000));
|
||||
}
|
||||
|
||||
let hwnd = wait_find_privacy_hwnd(0)?;
|
||||
if hwnd.is_null() {
|
||||
bail!("No privacy window created");
|
||||
if let Err(e) = self.show_privacy_windows(conn_id, true) {
|
||||
self.stop();
|
||||
return Err(e);
|
||||
}
|
||||
super::win_input::hook()?;
|
||||
unsafe {
|
||||
ShowWindow(hwnd as _, SW_SHOW);
|
||||
}
|
||||
self.conn_id = conn_id;
|
||||
self.hwnd = hwnd as _;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@@ -128,27 +130,33 @@ impl PrivacyMode for PrivacyModeImpl {
|
||||
) -> ResultType<()> {
|
||||
self.check_off_conn_id(conn_id)?;
|
||||
super::win_input::unhook()?;
|
||||
|
||||
unsafe {
|
||||
let hwnd = wait_find_privacy_hwnd(0)?;
|
||||
if !hwnd.is_null() {
|
||||
ShowWindow(hwnd, SW_HIDE);
|
||||
}
|
||||
let hwnds = find_privacy_hwnds()?;
|
||||
let hide_result = set_privacy_windows_visible(&hwnds, false);
|
||||
if hide_result.is_err() {
|
||||
self.stop();
|
||||
}
|
||||
|
||||
// Continue local state cleanup even after stop(); the broker has
|
||||
// been torn down, so keeping conn_id/hwnd would leave stale state.
|
||||
if self.conn_id != INVALID_PRIVACY_MODE_CONN_ID {
|
||||
if let Some(state) = state {
|
||||
allow_err!(super::set_privacy_mode_state(
|
||||
conn_id,
|
||||
state,
|
||||
PRIVACY_MODE_IMPL.to_string(),
|
||||
1_000
|
||||
));
|
||||
// Only publish the off state after the hide message was posted.
|
||||
// Otherwise the peer may receive a success-like state and then a
|
||||
// failed turn-off response for the same request.
|
||||
if hide_result.is_ok() {
|
||||
if let Some(state) = state {
|
||||
allow_err!(super::set_privacy_mode_state(
|
||||
conn_id,
|
||||
state,
|
||||
PRIVACY_MODE_IMPL.to_string(),
|
||||
1_000
|
||||
));
|
||||
}
|
||||
}
|
||||
self.conn_id = INVALID_PRIVACY_MODE_CONN_ID.to_owned();
|
||||
self.hwnd = 0;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
hide_result.map(|_| ())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -206,8 +214,7 @@ impl PrivacyModeImpl {
|
||||
);
|
||||
}
|
||||
|
||||
let hwnd = wait_find_privacy_hwnd(1_000)?;
|
||||
if !hwnd.is_null() {
|
||||
if wait_find_privacy_hwnds(PRIVACY_WINDOW_WAIT_MILLIS).is_ok() {
|
||||
log::info!("Privacy window is ready");
|
||||
return Ok(());
|
||||
}
|
||||
@@ -276,14 +283,19 @@ impl PrivacyModeImpl {
|
||||
);
|
||||
};
|
||||
|
||||
inject_dll(
|
||||
if let Err(e) = inject_dll(
|
||||
proc_info.hProcess,
|
||||
proc_info.hThread,
|
||||
dll_file.to_string_lossy().as_ref(),
|
||||
)?;
|
||||
) {
|
||||
TerminateProcess(proc_info.hProcess, 0);
|
||||
CloseHandle(proc_info.hThread);
|
||||
CloseHandle(proc_info.hProcess);
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
if 0xffffffff == ResumeThread(proc_info.hThread) {
|
||||
// CloseHandle
|
||||
TerminateProcess(proc_info.hProcess, 0);
|
||||
CloseHandle(proc_info.hThread);
|
||||
CloseHandle(proc_info.hProcess);
|
||||
|
||||
@@ -296,9 +308,9 @@ impl PrivacyModeImpl {
|
||||
self.handlers.hthread = proc_info.hThread as _;
|
||||
self.handlers.hprocess = proc_info.hProcess as _;
|
||||
|
||||
let hwnd = wait_find_privacy_hwnd(1_000)?;
|
||||
if hwnd.is_null() {
|
||||
bail!("Failed to get hwnd after started");
|
||||
if let Err(e) = wait_find_privacy_hwnds(PRIVACY_WINDOW_WAIT_MILLIS) {
|
||||
self.handlers.reset();
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,6 +321,49 @@ impl PrivacyModeImpl {
|
||||
pub fn stop(&mut self) {
|
||||
self.handlers.reset();
|
||||
}
|
||||
|
||||
fn show_privacy_windows(&mut self, conn_id: i32, hook_input: bool) -> ResultType<()> {
|
||||
let hwnds = wait_find_privacy_hwnds(PRIVACY_WINDOW_WAIT_MILLIS)?;
|
||||
if hwnds.is_empty() {
|
||||
bail!("No privacy window created");
|
||||
}
|
||||
|
||||
if hook_input {
|
||||
super::win_input::hook()?;
|
||||
}
|
||||
match set_privacy_windows_visible(&hwnds, true) {
|
||||
Ok(_) => {
|
||||
let visible_hwnds =
|
||||
match wait_find_visible_privacy_hwnds(PRIVACY_WINDOW_WAIT_MILLIS) {
|
||||
Ok(hwnds) => hwnds,
|
||||
Err(e) => {
|
||||
allow_err!(set_privacy_windows_visible(&hwnds, false));
|
||||
if hook_input {
|
||||
allow_err!(super::win_input::unhook());
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
let Some(hwnd) = visible_hwnds.first() else {
|
||||
allow_err!(set_privacy_windows_visible(&hwnds, false));
|
||||
if hook_input {
|
||||
allow_err!(super::win_input::unhook());
|
||||
}
|
||||
bail!("No visible privacy window created");
|
||||
};
|
||||
self.conn_id = conn_id;
|
||||
self.hwnd = *hwnd as _;
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
allow_err!(set_privacy_windows_visible(&hwnds, false));
|
||||
if hook_input {
|
||||
allow_err!(super::win_input::unhook());
|
||||
}
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PrivacyModeImpl {
|
||||
@@ -363,21 +418,217 @@ unsafe fn inject_dll<'a>(hproc: HANDLE, hthread: HANDLE, dll_file: &'a str) -> R
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn wait_find_privacy_hwnd(msecs: u128) -> ResultType<HWND> {
|
||||
fn wait_find_privacy_hwnds(msecs: u128) -> ResultType<Vec<HWND>> {
|
||||
wait_find_privacy_hwnds_impl(msecs, false)
|
||||
}
|
||||
|
||||
fn wait_find_visible_privacy_hwnds(msecs: u128) -> ResultType<Vec<HWND>> {
|
||||
wait_find_privacy_hwnds_impl(msecs, true)
|
||||
}
|
||||
|
||||
fn privacy_window_wait_millis(base_millis: u128, monitor_count: usize) -> u128 {
|
||||
if base_millis == 0 {
|
||||
return 0;
|
||||
}
|
||||
// Privacy Mode 1 creates one overlay per monitor. Keep the single-monitor
|
||||
// wait as the base and add time for each extra overlay before coverage
|
||||
// verification times out.
|
||||
base_millis
|
||||
+ (monitor_count.saturating_sub(1) as u128) * PRIVACY_WINDOW_WAIT_EXTRA_MONITOR_MILLIS
|
||||
}
|
||||
|
||||
fn wait_find_privacy_hwnds_impl(msecs: u128, require_visible: bool) -> ResultType<Vec<HWND>> {
|
||||
// This verifies initial turn-on coverage. If displays change during this
|
||||
// short poll window, the DLL refreshes overlays asynchronously, while this
|
||||
// check may still time out against the geometry sampled here.
|
||||
let monitor_rects = get_monitor_rects()?;
|
||||
if monitor_rects.is_empty() {
|
||||
bail!("No privacy monitor found");
|
||||
}
|
||||
let msecs = privacy_window_wait_millis(msecs, monitor_rects.len());
|
||||
|
||||
let tm_begin = Instant::now();
|
||||
let wndname = CString::new(PRIVACY_WINDOW_NAME)?;
|
||||
loop {
|
||||
unsafe {
|
||||
let hwnd = FindWindowA(NULL as _, wndname.as_ptr() as _);
|
||||
if !hwnd.is_null() {
|
||||
return Ok(hwnd);
|
||||
}
|
||||
let hwnds = find_privacy_hwnds()?;
|
||||
let visible_hwnds = if require_visible {
|
||||
filter_visible_hwnds(&hwnds)
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let covered_hwnds = if require_visible {
|
||||
visible_hwnds.as_slice()
|
||||
} else {
|
||||
hwnds.as_slice()
|
||||
};
|
||||
let covered = count_covered_monitors(covered_hwnds, &monitor_rects);
|
||||
if covered == monitor_rects.len() {
|
||||
return Ok(if require_visible {
|
||||
visible_hwnds
|
||||
} else {
|
||||
hwnds
|
||||
});
|
||||
}
|
||||
|
||||
if msecs == 0 || tm_begin.elapsed().as_millis() > msecs {
|
||||
return Ok(NULL as _);
|
||||
let visible = if require_visible { "visible " } else { "" };
|
||||
bail!(
|
||||
"Expected {}privacy windows to cover {} monitors, covered {}, found {}",
|
||||
visible,
|
||||
monitor_rects.len(),
|
||||
covered,
|
||||
hwnds.len(),
|
||||
);
|
||||
}
|
||||
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
std::thread::sleep(Duration::from_millis(PRIVACY_WINDOW_POLL_INTERVAL_MILLIS));
|
||||
}
|
||||
}
|
||||
|
||||
fn find_privacy_hwnds() -> ResultType<Vec<HWND>> {
|
||||
let class_name = CString::new(PRIVACY_WINDOW_CLASS)?;
|
||||
let wndname = CString::new(PRIVACY_WINDOW_NAME)?;
|
||||
let mut hwnds = Vec::new();
|
||||
unsafe {
|
||||
let mut after = NULL as _;
|
||||
loop {
|
||||
let hwnd = FindWindowExA(
|
||||
NULL as _,
|
||||
after,
|
||||
class_name.as_ptr() as _,
|
||||
wndname.as_ptr() as _,
|
||||
);
|
||||
if hwnd.is_null() {
|
||||
break;
|
||||
}
|
||||
hwnds.push(hwnd);
|
||||
after = hwnd;
|
||||
}
|
||||
}
|
||||
Ok(hwnds)
|
||||
}
|
||||
|
||||
fn filter_visible_hwnds(hwnds: &[HWND]) -> Vec<HWND> {
|
||||
hwnds
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|hwnd| unsafe { FALSE != IsWindowVisible(*hwnd) })
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn set_privacy_windows_visible(hwnds: &[HWND], show: bool) -> ResultType<usize> {
|
||||
if hwnds.is_empty() {
|
||||
return Ok(0);
|
||||
};
|
||||
let message = if show {
|
||||
WM_RUSTDESK_SHOW_WINDOWS
|
||||
} else {
|
||||
WM_RUSTDESK_HIDE_WINDOWS
|
||||
};
|
||||
let mut posted = 0;
|
||||
let mut first_error = None;
|
||||
for &hwnd in hwnds {
|
||||
unsafe {
|
||||
if FALSE == PostMessageA(hwnd, message, 0, 0) {
|
||||
if first_error.is_none() {
|
||||
first_error = Some(Error::last_os_error());
|
||||
}
|
||||
} else {
|
||||
posted += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(error) = first_error {
|
||||
bail!(
|
||||
"Failed to post privacy window visibility message to all privacy windows, posted {}/{}, first error {}",
|
||||
posted,
|
||||
hwnds.len(),
|
||||
error,
|
||||
);
|
||||
}
|
||||
Ok(posted)
|
||||
}
|
||||
|
||||
fn get_monitor_rects() -> ResultType<Vec<RECT>> {
|
||||
let mut rects = Vec::new();
|
||||
unsafe {
|
||||
if FALSE
|
||||
== EnumDisplayMonitors(
|
||||
NULL as _,
|
||||
NULL as _,
|
||||
Some(enum_monitor_rect_proc),
|
||||
&mut rects as *mut Vec<RECT> as LPARAM,
|
||||
)
|
||||
{
|
||||
bail!(
|
||||
"Failed EnumDisplayMonitors, error {}",
|
||||
Error::last_os_error()
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(rects)
|
||||
}
|
||||
|
||||
unsafe extern "system" fn enum_monitor_rect_proc(
|
||||
hmon: HMONITOR,
|
||||
_hdc: HDC,
|
||||
_rect: *mut RECT,
|
||||
lparam: LPARAM,
|
||||
) -> BOOL {
|
||||
let rects = &mut *(lparam as *mut Vec<RECT>);
|
||||
let mut monitor_info = MONITORINFO {
|
||||
cbSize: size_of::<MONITORINFO>() as _,
|
||||
rcMonitor: RECT {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
rcWork: RECT {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
dwFlags: 0,
|
||||
};
|
||||
if FALSE == GetMonitorInfoA(hmon, &mut monitor_info) {
|
||||
return FALSE;
|
||||
}
|
||||
rects.push(monitor_info.rcMonitor);
|
||||
TRUE
|
||||
}
|
||||
|
||||
fn count_covered_monitors(hwnds: &[HWND], monitor_rects: &[RECT]) -> usize {
|
||||
let mut covered = 0;
|
||||
for monitor_rect in monitor_rects {
|
||||
for hwnd in hwnds {
|
||||
let mut window_rect = RECT {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
};
|
||||
unsafe {
|
||||
if FALSE == GetWindowRect(*hwnd, &mut window_rect) {
|
||||
log::warn!(
|
||||
"Failed GetWindowRect for privacy window, error {}",
|
||||
Error::last_os_error()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if rect_covers(&window_rect, monitor_rect) {
|
||||
covered += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
covered
|
||||
}
|
||||
|
||||
fn rect_covers(window_rect: &RECT, monitor_rect: &RECT) -> bool {
|
||||
window_rect.left <= monitor_rect.left
|
||||
&& window_rect.top <= monitor_rect.top
|
||||
&& window_rect.right >= monitor_rect.right
|
||||
&& window_rect.bottom >= monitor_rect.bottom
|
||||
}
|
||||
|
||||
@@ -272,6 +272,10 @@ fn create_capturer(
|
||||
if privacy_mode_id > 0 {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// Windows Mode 1 can cover every local monitor with overlay windows,
|
||||
// but the legacy magnifier capture backend is still single-monitor
|
||||
// constrained. Keep display-switch gating aligned with that backend
|
||||
// limit, not just the overlay coverage.
|
||||
if let Some(c1) = crate::privacy_mode::win_mag::create_capturer(
|
||||
privacy_mode_id,
|
||||
display.origin(),
|
||||
|
||||
Reference in New Issue
Block a user