返回

WDA_EXCLUDEFROMCAPTURE失效?原因及解决方法

windows

WDA_EXCLUDEFROMCAPTURE 失效:行为如同 WDA_MONITOR 的原因及解决

在Windows环境下,开发者有时需要控制窗口是否被屏幕截图捕获。SetWindowDisplayAffinity API 提供这种能力, 其中 WDA_EXCLUDEFROMCAPTURE 常被期望隐藏窗口内容,使其不出现在截图里。 然而,实际应用中可能遇到 WDA_EXCLUDEFROMCAPTURE 行为像 WDA_MONITOR (窗口内容显示为黑色) 的情况。 这篇文章将深入探讨这类问题的原因,并提供切实可行的解决方案。

问题的根源:层叠窗口的交互影响

WDA_EXCLUDEFROMCAPTURE 期望阻止窗口出现在截图或其他捕获机制里,而 WDA_MONITOR 确实能将窗口内容渲染成黑色, 达到内容保护的目的。当设置窗口的 WS_EX_LAYERED 样式时,通常是需要实现透明或半透明效果,这会引发与 WDA_EXCLUDEFROMCAPTURE 之间的冲突。 根本原因在于系统如何处理带有 WS_EX_LAYERED 样式和 WDA_EXCLUDEFROMCAPTURE 属性的窗口截图。

层叠窗口需要特殊的渲染处理, 它们并不是传统的窗口绘制,而是以复合方式呈现。 当窗口既是层叠窗口又是尝试排除截图时,系统处理逻辑可能出现偏差。 它会将排除捕获的行为误解成将整个窗口都视为受保护区域,从而变成类似 WDA_MONITOR 的效果,使窗口显示为黑色。

解决方案

以下几种方法可以帮助解决这个问题,使 WDA_EXCLUDEFROMCAPTURE 正常工作:

方案一:移除 Layered 样式

如果窗口的透明不是必需的,最直接的方案是移除 WS_EX_LAYERED 样式。通过此调整,窗口将以标准方式绘制, 并配合 WDA_EXCLUDEFROMCAPTURE 使用。这将能够避免不正常的显示行为。

代码示例:

import tkinter as tk
import ctypes
from ctypes import wintypes

GWL_EXSTYLE = -20
WS_EX_LAYERED = 0x00080000
WS_EX_TRANSPARENT = 0x00000020
LWA_COLORKEY = 0x00000001
LWA_ALPHA = 0x00000002


def apply_exclude_capture(root):
    user32 = ctypes.WinDLL('user32', use_last_error=True)
    user32.SetWindowDisplayAffinity.argtypes = wintypes.HWND, wintypes.DWORD
    user32.SetWindowDisplayAffinity.restype = wintypes.BOOL
    
    WDA_EXCLUDEFROMCAPTURE = 0x00000011
    result = user32.SetWindowDisplayAffinity(root.winfo_id(), WDA_EXCLUDEFROMCAPTURE)
    if not result:
        raise ctypes.WinError(ctypes.get_last_error())
    else:
        print("SetWindowDisplayAffinity applied with exclusion!")
        
        

root = tk.Tk()
root.title("App")
root.geometry('1500x750')
root.resizable(False, False)
canvas = tk.Canvas(root, width=1500, height=750, bg='white', highlightthickness=0)
canvas.pack()
apply_exclude_capture(root)


# 将层叠属性移除
hwnd = canvas.winfo_id()
styles = ctypes.windll.user32.GetWindowLongW(hwnd, GWL_EXSTYLE)
styles &= ~(WS_EX_LAYERED | WS_EX_TRANSPARENT)
ctypes.windll.user32.SetWindowLongW(hwnd, GWL_EXSTYLE, styles)
#不再需要使用透明相关的设置
# SetLayeredWindowAttributes(hwnd, 16777215, 255, LWA_COLORKEY | LWA_ALPHA)
root.mainloop()

操作步骤:

  1. 创建一个基础的 tkinter 窗口。
  2. 注释掉原有的设置 WS_EX_LAYEREDWS_EX_TRANSPARENT 样式以及透明属性设置代码,或者,也可以删除,确保窗口不再是层叠窗口。
  3. 使用 SetWindowDisplayAffinity 应用 WDA_EXCLUDEFROMCAPTURE

方案二:使用 SetLayeredWindowAttributes 进行色彩键设置

如果需要保持透明, SetLayeredWindowAttributes 和颜色键是一个备选项。 可以通过创建一个指定颜色(通常是纯色)的透明窗口来实现。
WDA_EXCLUDEFROMCAPTURE 在此设置下可能可以按照预期工作,不过实际表现因系统和驱动而异。

代码示例:

import tkinter as tk
import ctypes
from ctypes import wintypes

GWL_EXSTYLE = -20
WS_EX_LAYERED = 0x00080000
WS_EX_TRANSPARENT = 0x00000020
LWA_COLORKEY = 0x00000001
LWA_ALPHA = 0x00000002


def apply_exclude_capture(root):
    user32 = ctypes.WinDLL('user32', use_last_error=True)
    user32.SetWindowDisplayAffinity.argtypes = wintypes.HWND, wintypes.DWORD
    user32.SetWindowDisplayAffinity.restype = wintypes.BOOL
    
    WDA_EXCLUDEFROMCAPTURE = 0x00000011
    result = user32.SetWindowDisplayAffinity(root.winfo_id(), WDA_EXCLUDEFROMCAPTURE)
    if not result:
        raise ctypes.WinError(ctypes.get_last_error())
    else:
        print("SetWindowDisplayAffinity applied with exclusion!")
        

root = tk.Tk()
root.title("App")
root.geometry('1500x750')
root.resizable(False, False)
canvas = tk.Canvas(root, width=1500, height=750, bg='red', highlightthickness=0)
canvas.pack()
apply_exclude_capture(root)
hwnd = canvas.winfo_id()
styles = ctypes.windll.user32.GetWindowLongW(hwnd, GWL_EXSTYLE)
styles |= WS_EX_LAYERED | WS_EX_TRANSPARENT
ctypes.windll.user32.SetWindowLongW(hwnd, GWL_EXSTYLE, styles)

#设置color key
SetLayeredWindowAttributes = ctypes.windll.user32.SetLayeredWindowAttributes
SetLayeredWindowAttributes.argtypes = [wintypes.HWND, wintypes.COLORREF, wintypes.BYTE, wintypes.DWORD]
SetLayeredWindowAttributes.restype = wintypes.BOOL

SetLayeredWindowAttributes(hwnd, 0x000000ff, 0, LWA_COLORKEY)

root.mainloop()

操作步骤:

  1. 设置 WS_EX_LAYEREDWS_EX_TRANSPARENT 窗口扩展样式。
  2. 创建带有纯色背景(如红色)的 Canvas。
  3. 调用 SetLayeredWindowAttributes 使用颜色键将指定背景色透明。
  4. 必须在设置 Layered 样式之后 应用 SetWindowDisplayAffinity

方案三:使用 DWM 复合(需要谨慎测试)

Direct Composition Manager (DWM) 也影响窗口如何捕获。可以尝试禁用或调整DWM的窗口合成, 但这种操作可能影响其他程序的视觉呈现效果,需小心使用并测试,以保证应用兼容性。 这项设置可以考虑通过配置应用的方式实现,而不修改用户全局系统设置,但操作上相对比较复杂。具体方案因操作系统和实际需求而异。

操作步骤

此方案较为复杂,并不适合直接提供代码,以下仅做说明:

  1. 可以使用Windows提供的DWM API对窗口进行特定设置。
  2. 利用DwmSetWindowAttribute 可以调整窗口的合成方式。
  3. 验证这种方法是否会影响WDA_EXCLUDEFROMCAPTURE。

重要提示: 此方案可能会导致其他视觉问题,建议在充分测试和理解DWM影响的情况下采用。

安全提示

在实际应用 WDA_EXCLUDEFROMCAPTURE 时,需谨慎处理窗口内容保护,防止安全风险:

  1. 验证所使用的API版本,确保在目标系统上能按预期运行。
  2. 除了截图捕获,也要注意其他的捕获方式,例如录屏,摄像头等。
  3. 充分测试各个解决方案,找到与自己应用场景最为匹配的方案。

总之, 解决 WDA_EXCLUDEFROMCAPTURE 行为异常问题的关键是理解层叠窗口的机制,并且明确需要使用的窗口特性。 选择合适的方案能有效的实现预期结果,并在最大程度上规避安全隐患。