[fix] avoid false Windows privacy screen obstruction failures

This commit is contained in:
kunkundi
2026-09-11 17:43:04 +08:00
parent 9af7417c46
commit 99ae0fc811
2 changed files with 52 additions and 5 deletions
@@ -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};
}
@@ -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 <Windows.h>
#include <cstdint>
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<int64_t>(bounds.right) - bounds.left;
const auto height = static_cast<int64_t>(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_