From 6665242edfe6b1887d7287b8065ad5fc2640f777 Mon Sep 17 00:00:00 2001 From: RustDesk <71636191+rustdesk@users.noreply.github.com> Date: Wed, 17 Jun 2026 21:46:40 +0800 Subject: [PATCH] Revert "refact: privacy mdoe 1, multi-monitors (#15318)" (#15320) This reverts commit 3cdf1cce540a5c75a921812d5307177a4351ba07. --- .../third-party-RustDeskTempTopMostWindow.yml | 4 +- flutter/lib/common/widgets/toolbar.dart | 58 +--- flutter/lib/consts.dart | 4 - .../lib/desktop/widgets/remote_toolbar.dart | 5 +- flutter/lib/mobile/pages/remote_page.dart | 8 +- libs/scrap/src/dxgi/mag.rs | 86 +---- src/privacy_mode/win_topmost_window.rs | 315 ++---------------- src/server/video_service.rs | 4 - 8 files changed, 79 insertions(+), 405 deletions(-) diff --git a/.github/workflows/third-party-RustDeskTempTopMostWindow.yml b/.github/workflows/third-party-RustDeskTempTopMostWindow.yml index d19b80d9e..4b36b54f2 100644 --- a/.github/workflows/third-party-RustDeskTempTopMostWindow.yml +++ b/.github/workflows/third-party-RustDeskTempTopMostWindow.yml @@ -45,10 +45,10 @@ jobs: run: | 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 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 }} - name: Archive build artifacts diff --git a/flutter/lib/common/widgets/toolbar.dart b/flutter/lib/common/widgets/toolbar.dart index 9653b5478..637904c2c 100644 --- a/flutter/lib/common/widgets/toolbar.dart +++ b/flutter/lib/common/widgets/toolbar.dart @@ -72,24 +72,10 @@ Widget waylandKeyboardScopeChip(BuildContext context, String text) { ); } -bool _isWindowsMode1PrivacyImpl(String privacyModeImpl) { - return privacyModeImpl == kPrivacyModeImplMag || - privacyModeImpl == kPrivacyModeImplExcludeFromCapture; -} - -// 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); +// macOS privacy mode blacks out all online displays, so switching the remote +// display does not weaken the local privacy protection. +bool allowDisplaySwitchInPrivacyMode(PeerInfo pi) { + return pi.platform == kPeerPlatformMacOS; } class TTextMenu { @@ -978,8 +964,7 @@ Future> toolbarDisplayToggle( final privacyModeState = PrivacyModeState.find(id); if (pi.isSupportMultiDisplay && - (privacyModeState.isEmpty || - allowDisplaySwitchInPrivacyMode(pi, privacyModeState.value)) && + (privacyModeState.isEmpty || allowDisplaySwitchInPrivacyMode(pi)) && pi.displaysCount.value > 1 && bind.mainGetUserDefaultOption(key: kKeyShowMonitorsToolbar) == 'Y') { final value = @@ -1063,20 +1048,7 @@ List toolbarPrivacyMode( return []; // No permission and not active, hide options. } - bool checkDisplayAllowedForPrivacyMode(String targetImplKey, bool turnOn) { - 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 Function(SessionID sid, String opt) toggleFunc, - String targetImplKey) { + getDefaultMenu(Future Function(SessionID sid, String opt) toggleFunc) { final enabled = !ffiModel.viewOnly && (hasPrivacyModePermission || privacyModeState.isNotEmpty); return TToggleMenu( @@ -1084,7 +1056,16 @@ List toolbarPrivacyMode( onChanged: enabled ? (value) { 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; } final option = 'privacy-mode'; @@ -1102,7 +1083,7 @@ List toolbarPrivacyMode( getDefaultMenu((sid, opt) async { bind.sessionToggleOption(sessionId: sid, value: opt); togglePrivacyModeTime = DateTime.now(); - }, kPrivacyModeImplMag) + }) ]; } if (privacyModeImpls.isEmpty) { @@ -1116,7 +1097,7 @@ List toolbarPrivacyMode( bind.sessionTogglePrivacyMode( sessionId: sid, implKey: implKey, on: privacyModeState.isEmpty); togglePrivacyModeTime = DateTime.now(); - }, implKey) + }) ]; } else { final visibleImpls = hasPrivacyModePermission @@ -1137,9 +1118,6 @@ List toolbarPrivacyMode( ? (value) { if (value == null) return; if (value && !hasPrivacyModePermission) return; - if (!checkDisplayAllowedForPrivacyMode(implKey, value)) { - return; - } togglePrivacyModeTime = DateTime.now(); bind.sessionTogglePrivacyMode( sessionId: sessionId, implKey: implKey, on: value); diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 8caf6ee11..adf7b1d45 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -29,10 +29,6 @@ const String kPlatformAdditionsHasFileClipboard = "has_file_clipboard"; const String kPlatformAdditionsSupportedPrivacyModeImpl = "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 kPeerPlatformLinux = "Linux"; const String kPeerPlatformMacOS = "Mac OS"; diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index 686120be5..210cc8571 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -810,9 +810,8 @@ class _RemoteToolbarState extends State { } toolbarItems.add(Obx(() { - final privacyModeState = PrivacyModeState.find(widget.id); - if ((privacyModeState.isEmpty || - allowDisplaySwitchInPrivacyMode(pi, privacyModeState.value)) && + if ((PrivacyModeState.find(widget.id).isEmpty || + allowDisplaySwitchInPrivacyMode(pi)) && pi.displaysCount.value > 1) { return _MonitorMenu( id: widget.id, diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index 8395f4540..6f3831072 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -1220,11 +1220,7 @@ void showOptions( if (image != null) { displays.add(Padding(padding: const EdgeInsets.only(top: 8), child: image)); } - final privacyModeState = PrivacyModeState.find(id); - if (pi.displays.length > 1 && - pi.currentDisplay != kAllDisplayValue && - (privacyModeState.isEmpty || - allowDisplaySwitchInPrivacyMode(pi, privacyModeState.value))) { + if (pi.displays.length > 1 && pi.currentDisplay != kAllDisplayValue) { final cur = pi.currentDisplay; final children = []; final isDarkTheme = MyTheme.currentThemeMode() == ThemeMode.dark; @@ -1278,6 +1274,8 @@ void showOptions( await toolbarDisplayToggle(context, id, gFFI); List privacyModeList = []; + // privacy mode + final privacyModeState = PrivacyModeState.find(id); if ((gFFI.ffiModel.pi.features.privacyMode && gFFI.ffiModel.keyboard) || privacyModeState.isNotEmpty) { privacyModeList = toolbarPrivacyMode(privacyModeState, context, id, gFFI); diff --git a/libs/scrap/src/dxgi/mag.rs b/libs/scrap/src/dxgi/mag.rs index 20321383e..75fc892cf 100644 --- a/libs/scrap/src/dxgi/mag.rs +++ b/libs/scrap/src/dxgi/mag.rs @@ -52,33 +52,6 @@ lazy_static::lazy_static! { static ref MAG_BUFFER: Mutex<(bool, Vec)> = Default::default(); } -fn find_windows(cls: &str, name: &str) -> Result> { - 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 WICPixelFormatGUID = GUID; @@ -274,8 +247,6 @@ pub struct CapturerMag { rect: RECT, width: usize, height: usize, - excluded_window_target: Option<(String, String)>, - excluded_windows: Vec, } impl Drop for CapturerMag { @@ -290,10 +261,6 @@ impl CapturerMag { 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 { unsafe { let x = GetSystemMetrics(SM_XVIRTUALSCREEN); @@ -338,8 +305,6 @@ impl CapturerMag { }, width, height, - excluded_window_target: None, - excluded_windows: Vec::new(), }; unsafe { @@ -471,36 +436,19 @@ impl CapturerMag { } pub(crate) fn exclude(&mut self, cls: &str, name: &str) -> Result { - let mut hwnds = find_windows(cls, 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 { - let count = hwnds.len() as _; + let name_c = CString::new(name)?; 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) = self.mag_interface.set_window_filter_list_func { @@ -508,15 +456,16 @@ impl CapturerMag { == set_window_filter_list_func( self.magnifier_window, MW_FILTERMODE_EXCLUDE, - count, - hwnds.as_mut_ptr(), + 1, + &mut hwnd, ) { return Err(Error::new( ErrorKind::Other, format!( - "Failed MagSetWindowFilterList for {} windows, error {}", - count, + "Failed MagSetWindowFilterList for cls {} name {}, error {}", + cls, + name, Error::last_os_error() ), )); @@ -547,7 +496,6 @@ impl CapturerMag { } pub(crate) fn frame(&mut self, data: &mut Vec) -> Result<()> { - self.refresh_excluded_windows()?; Self::clear_data(); unsafe { diff --git a/src/privacy_mode/win_topmost_window.rs b/src/privacy_mode/win_topmost_window.rs index 547be02e7..a7f80a02d 100644 --- a/src/privacy_mode/win_topmost_window.rs +++ b/src/privacy_mode/win_topmost_window.rs @@ -4,14 +4,13 @@ use hbb_common::{allow_err, bail, log, ResultType}; use std::{ ffi::CString, io::Error, - mem::size_of, time::{Duration, Instant}, }; use winapi::{ shared::{ - minwindef::{BOOL, FALSE, LPARAM, TRUE}, + minwindef::FALSE, ntdef::{HANDLE, NULL}, - windef::{HDC, HMONITOR, HWND, RECT}, + windef::HWND, }, um::{ 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 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, @@ -109,17 +102,22 @@ impl PrivacyMode for PrivacyModeImpl { ); } - let should_start_broker = self.handlers.is_default(); - if should_start_broker { - log::info!("turn_on_privacy, broker not running, try start"); + if self.handlers.is_default() { + log::info!("turn_on_privacy, dll not found when started, try start"); self.start()?; std::thread::sleep(std::time::Duration::from_millis(1_000)); } - if let Err(e) = self.show_privacy_windows(conn_id, true) { - self.stop(); - return Err(e); + let hwnd = wait_find_privacy_hwnd(0)?; + if hwnd.is_null() { + 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) } @@ -130,10 +128,12 @@ impl PrivacyMode for PrivacyModeImpl { ) -> ResultType<()> { self.check_off_conn_id(conn_id)?; super::win_input::unhook()?; - let hwnds = find_privacy_hwnds()?; - let hide_result = set_privacy_windows_visible(&hwnds, false); - if hide_result.is_err() { - self.stop(); + + unsafe { + let hwnd = wait_find_privacy_hwnd(0)?; + if !hwnd.is_null() { + ShowWindow(hwnd, SW_HIDE); + } } 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.hwnd = 0; } - hide_result.map(|_| ()) + Ok(()) } #[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"); return Ok(()); } @@ -276,19 +276,14 @@ impl PrivacyModeImpl { ); }; - if let Err(e) = inject_dll( + 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) { - TerminateProcess(proc_info.hProcess, 0); + // CloseHandle CloseHandle(proc_info.hThread); CloseHandle(proc_info.hProcess); @@ -301,9 +296,9 @@ impl PrivacyModeImpl { self.handlers.hthread = proc_info.hThread as _; self.handlers.hprocess = proc_info.hProcess as _; - if let Err(e) = wait_find_privacy_hwnds(PRIVACY_WINDOW_WAIT_MILLIS) { - self.handlers.reset(); - return Err(e); + let hwnd = wait_find_privacy_hwnd(1_000)?; + if hwnd.is_null() { + bail!("Failed to get hwnd after started"); } } @@ -314,49 +309,6 @@ 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 { @@ -411,214 +363,21 @@ unsafe fn inject_dll<'a>(hproc: HANDLE, hthread: HANDLE, dll_file: &'a str) -> R Ok(()) } -fn wait_find_privacy_hwnds(msecs: u128) -> ResultType> { - wait_find_privacy_hwnds_impl(msecs, false) -} - -fn wait_find_visible_privacy_hwnds(msecs: u128) -> ResultType> { - 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> { - 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()); - +pub(super) fn wait_find_privacy_hwnd(msecs: u128) -> ResultType { let tm_begin = Instant::now(); + let wndname = CString::new(PRIVACY_WINDOW_NAME)?; loop { - 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 - }); + unsafe { + let hwnd = FindWindowA(NULL as _, wndname.as_ptr() as _); + if !hwnd.is_null() { + return Ok(hwnd); + } } if msecs == 0 || tm_begin.elapsed().as_millis() > msecs { - let visible = if require_visible { "visible " } else { "" }; - bail!( - "Expected {}privacy windows to cover {} monitors, covered {}, found {}", - visible, - monitor_rects.len(), - covered, - hwnds.len(), - ); + return Ok(NULL as _); } - std::thread::sleep(Duration::from_millis(PRIVACY_WINDOW_POLL_INTERVAL_MILLIS)); + std::thread::sleep(Duration::from_millis(100)); } } - -fn find_privacy_hwnds() -> ResultType> { - 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 { - hwnds - .iter() - .copied() - .filter(|hwnd| unsafe { FALSE != IsWindowVisible(*hwnd) }) - .collect() -} - -fn set_privacy_windows_visible(hwnds: &[HWND], show: bool) -> ResultType { - 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> { - let mut rects = Vec::new(); - unsafe { - if FALSE - == EnumDisplayMonitors( - NULL as _, - NULL as _, - Some(enum_monitor_rect_proc), - &mut rects as *mut Vec 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); - let mut monitor_info = MONITORINFO { - cbSize: size_of::() 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 -} diff --git a/src/server/video_service.rs b/src/server/video_service.rs index 15f0ef893..13a781c28 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -272,10 +272,6 @@ 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(),