Revert "refact: privacy mdoe 1, multi-monitors (#15318)" (#15320)

This reverts commit 3cdf1cce54.
This commit is contained in:
RustDesk
2026-06-17 21:46:40 +08:00
committed by GitHub
parent 3cdf1cce54
commit 6665242edf
8 changed files with 79 additions and 405 deletions

View File

@@ -45,10 +45,10 @@ jobs:
run: | run: |
git clone https://github.com/rustdesk-org/RustDeskTempTopMostWindow RustDeskTempTopMostWindow git clone https://github.com/rustdesk-org/RustDeskTempTopMostWindow RustDeskTempTopMostWindow
# Build. commit 3b79772afb754a5a1111804864616c2e81513de8, support multiple monitors # Build. commit 53b548a5398624f7149a382000397993542ad796 is tag v0.3
- name: Build the project - name: Build the project
run: | run: |
cd RustDeskTempTopMostWindow && git checkout 3b79772afb754a5a1111804864616c2e81513de8 cd RustDeskTempTopMostWindow && git checkout 53b548a5398624f7149a382000397993542ad796
msbuild ${{ env.project_path }} -p:Configuration=${{ inputs.configuration }} -p:Platform=${{ inputs.platform }} /p:TargetVersion=${{ inputs.target_version }} msbuild ${{ env.project_path }} -p:Configuration=${{ inputs.configuration }} -p:Platform=${{ inputs.platform }} /p:TargetVersion=${{ inputs.target_version }}
- name: Archive build artifacts - name: Archive build artifacts

View File

@@ -72,24 +72,10 @@ Widget waylandKeyboardScopeChip(BuildContext context, String text) {
); );
} }
bool _isWindowsMode1PrivacyImpl(String privacyModeImpl) { // macOS privacy mode blacks out all online displays, so switching the remote
return privacyModeImpl == kPrivacyModeImplMag || // display does not weaken the local privacy protection.
privacyModeImpl == kPrivacyModeImplExcludeFromCapture; bool allowDisplaySwitchInPrivacyMode(PeerInfo pi) {
} return pi.platform == kPeerPlatformMacOS;
// macOS privacy mode blacks out all online displays. Windows Mode 1 also
// covers every local monitor with privacy overlay windows, so remote display
// switching does not weaken local privacy protection.
//
// Keep this separate from the capture backend capability. The legacy Windows
// magnifier capturer is not reliable for multi-monitor capture; WebRTC's
// screen_capturer_win_magnifier also disables it when SM_CMONITORS != 1:
// https://webrtc.googlesource.com/src/+/1845922d5a1bf9c27deeffb4a8c8daea124434c1/modules/desktop_capture/win/screen_capturer_win_magnifier.cc
bool allowDisplaySwitchInPrivacyMode(PeerInfo pi, String privacyModeImpl) {
return pi.platform == kPeerPlatformMacOS ||
(pi.platform == kPeerPlatformWindows &&
_isWindowsMode1PrivacyImpl(privacyModeImpl) &&
versionCmp(pi.version, '1.4.8') >= 0);
} }
class TTextMenu { class TTextMenu {
@@ -978,8 +964,7 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
final privacyModeState = PrivacyModeState.find(id); final privacyModeState = PrivacyModeState.find(id);
if (pi.isSupportMultiDisplay && if (pi.isSupportMultiDisplay &&
(privacyModeState.isEmpty || (privacyModeState.isEmpty || allowDisplaySwitchInPrivacyMode(pi)) &&
allowDisplaySwitchInPrivacyMode(pi, privacyModeState.value)) &&
pi.displaysCount.value > 1 && pi.displaysCount.value > 1 &&
bind.mainGetUserDefaultOption(key: kKeyShowMonitorsToolbar) == 'Y') { bind.mainGetUserDefaultOption(key: kKeyShowMonitorsToolbar) == 'Y') {
final value = final value =
@@ -1063,20 +1048,7 @@ List<TToggleMenu> toolbarPrivacyMode(
return []; // No permission and not active, hide options. return []; // No permission and not active, hide options.
} }
bool checkDisplayAllowedForPrivacyMode(String targetImplKey, bool turnOn) { getDefaultMenu(Future<void> Function(SessionID sid, String opt) toggleFunc) {
if (!turnOn ||
allowDisplaySwitchInPrivacyMode(pi, targetImplKey) ||
(ffiModel.pi.currentDisplay == 0 &&
!bind.sessionIsMultiUiSession(sessionId: sessionId))) {
return true;
}
msgBox(sessionId, 'custom-nook-nocancel-hasclose', 'info',
'Please switch to Display 1 first', '', ffi.dialogManager);
return false;
}
getDefaultMenu(Future<void> Function(SessionID sid, String opt) toggleFunc,
String targetImplKey) {
final enabled = !ffiModel.viewOnly && final enabled = !ffiModel.viewOnly &&
(hasPrivacyModePermission || privacyModeState.isNotEmpty); (hasPrivacyModePermission || privacyModeState.isNotEmpty);
return TToggleMenu( return TToggleMenu(
@@ -1084,7 +1056,16 @@ List<TToggleMenu> toolbarPrivacyMode(
onChanged: enabled onChanged: enabled
? (value) { ? (value) {
if (value == null) return; if (value == null) return;
if (!checkDisplayAllowedForPrivacyMode(targetImplKey, value)) { if (!allowDisplaySwitchInPrivacyMode(pi) &&
ffiModel.pi.currentDisplay != 0 &&
ffiModel.pi.currentDisplay != kAllDisplayValue) {
msgBox(
sessionId,
'custom-nook-nocancel-hasclose',
'info',
'Please switch to Display 1 first',
'',
ffi.dialogManager);
return; return;
} }
final option = 'privacy-mode'; final option = 'privacy-mode';
@@ -1102,7 +1083,7 @@ List<TToggleMenu> toolbarPrivacyMode(
getDefaultMenu((sid, opt) async { getDefaultMenu((sid, opt) async {
bind.sessionToggleOption(sessionId: sid, value: opt); bind.sessionToggleOption(sessionId: sid, value: opt);
togglePrivacyModeTime = DateTime.now(); togglePrivacyModeTime = DateTime.now();
}, kPrivacyModeImplMag) })
]; ];
} }
if (privacyModeImpls.isEmpty) { if (privacyModeImpls.isEmpty) {
@@ -1116,7 +1097,7 @@ List<TToggleMenu> toolbarPrivacyMode(
bind.sessionTogglePrivacyMode( bind.sessionTogglePrivacyMode(
sessionId: sid, implKey: implKey, on: privacyModeState.isEmpty); sessionId: sid, implKey: implKey, on: privacyModeState.isEmpty);
togglePrivacyModeTime = DateTime.now(); togglePrivacyModeTime = DateTime.now();
}, implKey) })
]; ];
} else { } else {
final visibleImpls = hasPrivacyModePermission final visibleImpls = hasPrivacyModePermission
@@ -1137,9 +1118,6 @@ List<TToggleMenu> toolbarPrivacyMode(
? (value) { ? (value) {
if (value == null) return; if (value == null) return;
if (value && !hasPrivacyModePermission) return; if (value && !hasPrivacyModePermission) return;
if (!checkDisplayAllowedForPrivacyMode(implKey, value)) {
return;
}
togglePrivacyModeTime = DateTime.now(); togglePrivacyModeTime = DateTime.now();
bind.sessionTogglePrivacyMode( bind.sessionTogglePrivacyMode(
sessionId: sessionId, implKey: implKey, on: value); sessionId: sessionId, implKey: implKey, on: value);

View File

@@ -29,10 +29,6 @@ const String kPlatformAdditionsHasFileClipboard = "has_file_clipboard";
const String kPlatformAdditionsSupportedPrivacyModeImpl = const String kPlatformAdditionsSupportedPrivacyModeImpl =
"supported_privacy_mode_impl"; "supported_privacy_mode_impl";
const String kPrivacyModeImplMag = 'privacy_mode_impl_mag';
const String kPrivacyModeImplExcludeFromCapture =
'privacy_mode_impl_exclude_from_capture';
const String kPeerPlatformWindows = "Windows"; const String kPeerPlatformWindows = "Windows";
const String kPeerPlatformLinux = "Linux"; const String kPeerPlatformLinux = "Linux";
const String kPeerPlatformMacOS = "Mac OS"; const String kPeerPlatformMacOS = "Mac OS";

View File

@@ -810,9 +810,8 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
} }
toolbarItems.add(Obx(() { toolbarItems.add(Obx(() {
final privacyModeState = PrivacyModeState.find(widget.id); if ((PrivacyModeState.find(widget.id).isEmpty ||
if ((privacyModeState.isEmpty || allowDisplaySwitchInPrivacyMode(pi)) &&
allowDisplaySwitchInPrivacyMode(pi, privacyModeState.value)) &&
pi.displaysCount.value > 1) { pi.displaysCount.value > 1) {
return _MonitorMenu( return _MonitorMenu(
id: widget.id, id: widget.id,

View File

@@ -1220,11 +1220,7 @@ void showOptions(
if (image != null) { if (image != null) {
displays.add(Padding(padding: const EdgeInsets.only(top: 8), child: image)); displays.add(Padding(padding: const EdgeInsets.only(top: 8), child: image));
} }
final privacyModeState = PrivacyModeState.find(id); if (pi.displays.length > 1 && pi.currentDisplay != kAllDisplayValue) {
if (pi.displays.length > 1 &&
pi.currentDisplay != kAllDisplayValue &&
(privacyModeState.isEmpty ||
allowDisplaySwitchInPrivacyMode(pi, privacyModeState.value))) {
final cur = pi.currentDisplay; final cur = pi.currentDisplay;
final children = <Widget>[]; final children = <Widget>[];
final isDarkTheme = MyTheme.currentThemeMode() == ThemeMode.dark; final isDarkTheme = MyTheme.currentThemeMode() == ThemeMode.dark;
@@ -1278,6 +1274,8 @@ void showOptions(
await toolbarDisplayToggle(context, id, gFFI); await toolbarDisplayToggle(context, id, gFFI);
List<TToggleMenu> privacyModeList = []; List<TToggleMenu> privacyModeList = [];
// privacy mode
final privacyModeState = PrivacyModeState.find(id);
if ((gFFI.ffiModel.pi.features.privacyMode && gFFI.ffiModel.keyboard) || if ((gFFI.ffiModel.pi.features.privacyMode && gFFI.ffiModel.keyboard) ||
privacyModeState.isNotEmpty) { privacyModeState.isNotEmpty) {
privacyModeList = toolbarPrivacyMode(privacyModeState, context, id, gFFI); privacyModeList = toolbarPrivacyMode(privacyModeState, context, id, gFFI);

View File

@@ -52,33 +52,6 @@ lazy_static::lazy_static! {
static ref MAG_BUFFER: Mutex<(bool, Vec<u8>)> = Default::default(); static ref MAG_BUFFER: Mutex<(bool, Vec<u8>)> = Default::default();
} }
fn find_windows(cls: &str, name: &str) -> Result<Vec<HWND>> {
let name_c = CString::new(name)?;
let cls_c = if cls.is_empty() {
None
} else {
Some(CString::new(cls)?)
};
let mut hwnds = Vec::new();
unsafe {
let mut after = NULL as _;
loop {
let hwnd = FindWindowExA(
NULL as _,
after,
cls_c.as_ref().map_or(NULL as _, |c| c.as_ptr()),
name_c.as_ptr(),
);
if hwnd.is_null() {
break;
}
hwnds.push(hwnd);
after = hwnd;
}
}
Ok(hwnds)
}
pub type REFWICPixelFormatGUID = *const GUID; pub type REFWICPixelFormatGUID = *const GUID;
pub type WICPixelFormatGUID = GUID; pub type WICPixelFormatGUID = GUID;
@@ -274,8 +247,6 @@ pub struct CapturerMag {
rect: RECT, rect: RECT,
width: usize, width: usize,
height: usize, height: usize,
excluded_window_target: Option<(String, String)>,
excluded_windows: Vec<HWND>,
} }
impl Drop for CapturerMag { impl Drop for CapturerMag {
@@ -290,10 +261,6 @@ impl CapturerMag {
MagInterface::new().is_ok() MagInterface::new().is_ok()
} }
// This captures through the legacy Windows Magnification API. Do not infer
// multi-monitor capture support from privacy overlay coverage: WebRTC also
// disables its magnifier capturer when SM_CMONITORS != 1.
// https://webrtc.googlesource.com/src/+/1845922d5a1bf9c27deeffb4a8c8daea124434c1/modules/desktop_capture/win/screen_capturer_win_magnifier.cc
pub(crate) fn new(origin: (i32, i32), width: usize, height: usize) -> Result<Self> { pub(crate) fn new(origin: (i32, i32), width: usize, height: usize) -> Result<Self> {
unsafe { unsafe {
let x = GetSystemMetrics(SM_XVIRTUALSCREEN); let x = GetSystemMetrics(SM_XVIRTUALSCREEN);
@@ -338,8 +305,6 @@ impl CapturerMag {
}, },
width, width,
height, height,
excluded_window_target: None,
excluded_windows: Vec::new(),
}; };
unsafe { unsafe {
@@ -471,36 +436,19 @@ impl CapturerMag {
} }
pub(crate) fn exclude(&mut self, cls: &str, name: &str) -> Result<bool> { pub(crate) fn exclude(&mut self, cls: &str, name: &str) -> Result<bool> {
let mut hwnds = find_windows(cls, name)?; let name_c = CString::new(name)?;
self.excluded_window_target = Some((cls.to_owned(), name.to_owned()));
if hwnds.is_empty() {
self.excluded_windows.clear();
return Ok(false);
}
self.exclude_windows(&mut hwnds)?;
self.excluded_windows = hwnds;
Ok(true)
}
fn refresh_excluded_windows(&mut self) -> Result<()> {
let Some((cls, name)) = self.excluded_window_target.as_ref() else {
return Ok(());
};
let mut hwnds = find_windows(cls, name)?;
// Keep the previous filter list while privacy windows are being recreated.
if hwnds.is_empty() || hwnds == self.excluded_windows {
return Ok(());
}
self.exclude_windows(&mut hwnds)?;
self.excluded_windows = hwnds;
Ok(())
}
fn exclude_windows(&mut self, hwnds: &mut [HWND]) -> Result<bool> {
let count = hwnds.len() as _;
unsafe { unsafe {
let mut hwnd = if cls.len() == 0 {
FindWindowExA(NULL as _, NULL as _, NULL as _, name_c.as_ptr())
} else {
let cls_c = CString::new(cls).unwrap();
FindWindowExA(NULL as _, NULL as _, cls_c.as_ptr(), name_c.as_ptr())
};
if hwnd.is_null() {
return Ok(false);
}
if let Some(set_window_filter_list_func) = if let Some(set_window_filter_list_func) =
self.mag_interface.set_window_filter_list_func self.mag_interface.set_window_filter_list_func
{ {
@@ -508,15 +456,16 @@ impl CapturerMag {
== set_window_filter_list_func( == set_window_filter_list_func(
self.magnifier_window, self.magnifier_window,
MW_FILTERMODE_EXCLUDE, MW_FILTERMODE_EXCLUDE,
count, 1,
hwnds.as_mut_ptr(), &mut hwnd,
) )
{ {
return Err(Error::new( return Err(Error::new(
ErrorKind::Other, ErrorKind::Other,
format!( format!(
"Failed MagSetWindowFilterList for {} windows, error {}", "Failed MagSetWindowFilterList for cls {} name {}, error {}",
count, cls,
name,
Error::last_os_error() Error::last_os_error()
), ),
)); ));
@@ -547,7 +496,6 @@ impl CapturerMag {
} }
pub(crate) fn frame(&mut self, data: &mut Vec<u8>) -> Result<()> { pub(crate) fn frame(&mut self, data: &mut Vec<u8>) -> Result<()> {
self.refresh_excluded_windows()?;
Self::clear_data(); Self::clear_data();
unsafe { unsafe {

View File

@@ -4,14 +4,13 @@ use hbb_common::{allow_err, bail, log, ResultType};
use std::{ use std::{
ffi::CString, ffi::CString,
io::Error, io::Error,
mem::size_of,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use winapi::{ use winapi::{
shared::{ shared::{
minwindef::{BOOL, FALSE, LPARAM, TRUE}, minwindef::FALSE,
ntdef::{HANDLE, NULL}, ntdef::{HANDLE, NULL},
windef::{HDC, HMONITOR, HWND, RECT}, windef::HWND,
}, },
um::{ um::{
handleapi::CloseHandle, handleapi::CloseHandle,
@@ -32,13 +31,7 @@ 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 ORIGIN_PROCESS_EXE: &'static str = "C:\\Windows\\System32\\RuntimeBroker.exe";
pub const WIN_TOPMOST_INJECTED_PROCESS_EXE: &'static str = "RuntimeBroker_rustdesk.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 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"; 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 { struct WindowHandlers {
hthread: u64, hthread: u64,
@@ -109,17 +102,22 @@ impl PrivacyMode for PrivacyModeImpl {
); );
} }
let should_start_broker = self.handlers.is_default(); if self.handlers.is_default() {
if should_start_broker { log::info!("turn_on_privacy, dll not found when started, try start");
log::info!("turn_on_privacy, broker not running, try start");
self.start()?; self.start()?;
std::thread::sleep(std::time::Duration::from_millis(1_000)); std::thread::sleep(std::time::Duration::from_millis(1_000));
} }
if let Err(e) = self.show_privacy_windows(conn_id, true) { let hwnd = wait_find_privacy_hwnd(0)?;
self.stop(); if hwnd.is_null() {
return Err(e); bail!("No privacy window created");
} }
super::win_input::hook()?;
unsafe {
ShowWindow(hwnd as _, SW_SHOW);
}
self.conn_id = conn_id;
self.hwnd = hwnd as _;
Ok(true) Ok(true)
} }
@@ -130,10 +128,12 @@ impl PrivacyMode for PrivacyModeImpl {
) -> ResultType<()> { ) -> ResultType<()> {
self.check_off_conn_id(conn_id)?; self.check_off_conn_id(conn_id)?;
super::win_input::unhook()?; super::win_input::unhook()?;
let hwnds = find_privacy_hwnds()?;
let hide_result = set_privacy_windows_visible(&hwnds, false); unsafe {
if hide_result.is_err() { let hwnd = wait_find_privacy_hwnd(0)?;
self.stop(); if !hwnd.is_null() {
ShowWindow(hwnd, SW_HIDE);
}
} }
if self.conn_id != INVALID_PRIVACY_MODE_CONN_ID { if self.conn_id != INVALID_PRIVACY_MODE_CONN_ID {
@@ -146,10 +146,9 @@ impl PrivacyMode for PrivacyModeImpl {
)); ));
} }
self.conn_id = INVALID_PRIVACY_MODE_CONN_ID.to_owned(); self.conn_id = INVALID_PRIVACY_MODE_CONN_ID.to_owned();
self.hwnd = 0;
} }
hide_result.map(|_| ()) Ok(())
} }
#[inline] #[inline]
@@ -207,7 +206,8 @@ impl PrivacyModeImpl {
); );
} }
if wait_find_privacy_hwnds(PRIVACY_WINDOW_WAIT_MILLIS).is_ok() { let hwnd = wait_find_privacy_hwnd(1_000)?;
if !hwnd.is_null() {
log::info!("Privacy window is ready"); log::info!("Privacy window is ready");
return Ok(()); return Ok(());
} }
@@ -276,19 +276,14 @@ impl PrivacyModeImpl {
); );
}; };
if let Err(e) = inject_dll( inject_dll(
proc_info.hProcess, proc_info.hProcess,
proc_info.hThread, proc_info.hThread,
dll_file.to_string_lossy().as_ref(), 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) { if 0xffffffff == ResumeThread(proc_info.hThread) {
TerminateProcess(proc_info.hProcess, 0); // CloseHandle
CloseHandle(proc_info.hThread); CloseHandle(proc_info.hThread);
CloseHandle(proc_info.hProcess); CloseHandle(proc_info.hProcess);
@@ -301,9 +296,9 @@ impl PrivacyModeImpl {
self.handlers.hthread = proc_info.hThread as _; self.handlers.hthread = proc_info.hThread as _;
self.handlers.hprocess = proc_info.hProcess as _; self.handlers.hprocess = proc_info.hProcess as _;
if let Err(e) = wait_find_privacy_hwnds(PRIVACY_WINDOW_WAIT_MILLIS) { let hwnd = wait_find_privacy_hwnd(1_000)?;
self.handlers.reset(); if hwnd.is_null() {
return Err(e); bail!("Failed to get hwnd after started");
} }
} }
@@ -314,49 +309,6 @@ impl PrivacyModeImpl {
pub fn stop(&mut self) { pub fn stop(&mut self) {
self.handlers.reset(); 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 { impl Drop for PrivacyModeImpl {
@@ -411,214 +363,21 @@ unsafe fn inject_dll<'a>(hproc: HANDLE, hthread: HANDLE, dll_file: &'a str) -> R
Ok(()) Ok(())
} }
fn wait_find_privacy_hwnds(msecs: u128) -> ResultType<Vec<HWND>> { pub(super) fn wait_find_privacy_hwnd(msecs: u128) -> ResultType<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>> {
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 tm_begin = Instant::now();
let wndname = CString::new(PRIVACY_WINDOW_NAME)?;
loop { loop {
let hwnds = find_privacy_hwnds()?; unsafe {
let visible_hwnds = if require_visible { let hwnd = FindWindowA(NULL as _, wndname.as_ptr() as _);
filter_visible_hwnds(&hwnds) if !hwnd.is_null() {
} else { return Ok(hwnd);
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 { if msecs == 0 || tm_begin.elapsed().as_millis() > msecs {
let visible = if require_visible { "visible " } else { "" }; return Ok(NULL as _);
bail!(
"Expected {}privacy windows to cover {} monitors, covered {}, found {}",
visible,
monitor_rects.len(),
covered,
hwnds.len(),
);
} }
std::thread::sleep(Duration::from_millis(PRIVACY_WINDOW_POLL_INTERVAL_MILLIS)); std::thread::sleep(Duration::from_millis(100));
} }
} }
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
}

View File

@@ -272,10 +272,6 @@ fn create_capturer(
if privacy_mode_id > 0 { if privacy_mode_id > 0 {
#[cfg(windows)] #[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( if let Some(c1) = crate::privacy_mode::win_mag::create_capturer(
privacy_mode_id, privacy_mode_id,
display.origin(), display.origin(),