diff --git a/apps/desktop/src/platform/windows/privacy/privacy_backend_win.cpp b/apps/desktop/src/platform/windows/privacy/privacy_backend_win.cpp index a0921a5c..a0d38114 100644 --- a/apps/desktop/src/platform/windows/privacy/privacy_backend_win.cpp +++ b/apps/desktop/src/platform/windows/privacy/privacy_backend_win.cpp @@ -21,6 +21,7 @@ #include "privacy_input_guard.h" #include "privacy_probe_pattern.h" #include "privacy_probe_renderer.h" +#include "privacy_window_visibility.h" #include "rd_log.h" #ifndef WDA_EXCLUDEFROMCAPTURE @@ -379,6 +380,7 @@ class WindowsPrivacyBackend final : public PrivacyBackend { continue; if (GetWindowRect(above, &bounds) && IntersectRect(&intersection, &bounds, &w.rect)) { + if (IsNonObstructingPrivacyWindow(above, bounds)) continue; char window_class[128]{}; DWORD process_id = 0; GetClassNameA(above, window_class, sizeof(window_class)); @@ -387,16 +389,19 @@ class WindowsPrivacyBackend final : public PrivacyBackend { if (above != last_obstruction_ || now - last_obstruction_log_ >= 1000) { LOG_WARN( - "Privacy cover obstructed: class={}, process={}; " + "Potential privacy cover overlap: class={}, process={}, " + "band={}, bounds=({}, {})-({}, {}), exstyle=0x{:x}; " "remote operation paused", - window_class, process_id); + window_class, process_id, above_band, bounds.left, bounds.top, + bounds.right, bounds.bottom, + GetWindowLongPtrW(above, GWL_EXSTYLE)); last_obstruction_ = above; last_obstruction_log_ = now; } - // A higher-band/system window cannot be repaired by raising an - // ordinary cover. Retain protection and require explicit recovery. + // HWND order cannot prove compositor order, but this window may + // display content. Retain protection and require explicit recovery. return { - "A system or higher-band window overrides privacy coverage; " + "A visible window may overlap privacy coverage; " "remote operation paused. Turn privacy off to recover.", false}; } diff --git a/apps/desktop/src/platform/windows/privacy/privacy_window_visibility.h b/apps/desktop/src/platform/windows/privacy/privacy_window_visibility.h new file mode 100644 index 00000000..c47b3e1b --- /dev/null +++ b/apps/desktop/src/platform/windows/privacy/privacy_window_visibility.h @@ -0,0 +1,42 @@ +/* + * @Author: DI JUNKUN + * @Date: 2026-09-11 + * Copyright (c) 2026 by DI JUNKUN, All Rights Reserved. + */ + +#ifndef _PRIVACY_WINDOW_VISIBILITY_H_ +#define _PRIVACY_WINDOW_VISIBILITY_H_ + +#include + +#include + +namespace crossdesk { + +// Called for a visible foreign window whose full bounds intersect a cover. +// Inspect it on every check: an OSD helper can grow into a content window. +inline bool IsNonObstructingPrivacyWindow(HWND window, const RECT& bounds) { + const auto styles = GetWindowLongPtrW(window, GWL_EXSTYLE); + if (!(styles & WS_EX_LAYERED)) return false; + + BYTE alpha = 0; + DWORD flags = 0; + // WS_EX_TRANSPARENT alone does not imply invisible pixels. Likewise, + // UpdateLayeredWindow windows can reject this query; failure proves nothing. + if (GetLayeredWindowAttributes(window, nullptr, &alpha, &flags) && + (flags & LWA_ALPHA) && alpha == 0) + return true; + + constexpr LONG_PTR helper_styles = + WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE | WS_EX_TRANSPARENT; + const auto width = static_cast(bounds.right) - bounds.left; + const auto height = static_cast(bounds.bottom) - bounds.top; + // Single-pixel, nonactivating OSD placeholders cannot display meaningful + // desktop content. Use the whole window, never its clipped intersection; + // larger OSDs and windows with unknown opacity still require coverage checks. + return (styles & helper_styles) == helper_styles && width == 1 && height == 1; +} + +} // namespace crossdesk + +#endif // _PRIVACY_WINDOW_VISIBILITY_H_