应用如何防止截屏:Windows、macOS 和 Linux 详解
当你尝试截取 Netflix 或 Spotify 的截图时,看到的不是视频画面,而是黑色矩形——这不是 bug,而是通过系统 API 故意实现的保护机制。开发者使用 Windows 上的 SetWindowDisplayAffinity 或 macOS 上的 sharingType 等标志,将窗口从屏幕捕获流中排除。但在 macOS 15 Sequoia 中,Apple 破坏了兼容性,而在 Linux 上,一切取决于显示服务器。让我们详细分析每个平台上的保护机制、失效情况以及可能的变通方法。
Windows 中的屏幕捕获机制:三种主要 API
当 Zoom 等应用请求屏幕共享权限时,它会订阅来自操作系统的帧流。Windows 有三种主要方式获取此流,每种都有其独特特性:
- GDI BitBlt from desktop DC——最古老的方法,从 Windows 2000 开始可用。它使用
GetDC(NULL)中的BitBlt调用,将像素从 DWM(Desktop Window Manager)表面复制到任意 HDC 中。速度慢、无硬件加速,但通用性强。常用于旧版应用和监控工具。 - Desktop Duplication API (DXGI)——Windows 8 引入。通过
IDXGIOutputDuplication::AcquireNextFrame,返回包含完整合成桌面帧的 GPU 纹理。速度快、硬件加速,但只能捕获整个显示器。经典版 Zoom、TeamViewer、AnyDesk 和 Teams 均使用此 API。 - Windows.Graphics.Capture (WGC)——现代标准,从 Windows 10 1803 开始引入。支持捕获整个显示器或特定 HWND 窗口。当前版 OBS、Microsoft Teams、Chromium、Zoom 和内置的 Snipping Tool 均使用它。
这三种方法最终都从 DWM 读取数据——这是合成最终屏幕图像的唯一场所。保护机制正是在此处发挥作用。
SetWindowDisplayAffinity:模式及其行为
DWM 允许每个窗口通过 SetWindowDisplayAffinity 函数设置 display affinity 属性。此属性决定窗口是否出现在捕获流中。有三种可用值:
WDA_NONE(0x00)——窗口在所有捕获中可见(默认)。WDA_MONITOR(0x01)——窗口在显示器上显示,但在捕获中被黑色矩形替换。从 Windows Vista 开始支持。Netflix 使用此模式保护内容。WDA_EXCLUDEFROMCAPTURE(0x11)——窗口完全从捕获流中排除,仿佛不存在。从 Windows 10 2020 年 5 月更新(build 19041)开始引入。下层窗口内容会透出——更干净的解决方案。
C++ 示例:
#include <windows.h>
#include <cstdio>
int main() {
HWND hwnd = GetConsoleWindow();
if (!SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE)) {
printf("SetWindowDisplayAffinity failed: %lu\n", GetLastError());
return 1;
}
printf("Okno teper nevidimo for zakhvata. Poprobuy sdelat skrinshot.\n");
Sleep(60000);
return 0;
}
在 C# 中通过 P/Invoke、在 Electron 中通过 setContentProtection(true)、在 Tauri 中通过 with_content_protection(true),效果类似。
在底层,DWM 分离了合成管道:带有 WDA_EXCLUDEFROMCAPTURE 的窗口仅进入物理显示器管道,而不进入捕获客户端管道。你无法在像素读取层面绕过它。
Windows 中的兼容性和限制
在 Windows 11 23H2 上的测试显示,WDA_EXCLUDEFROMCAPTURE 对所有主要捕获方法均正常工作:
- GDI BitBlt——窗口不存在。
- Desktop Duplication (DXGI)——窗口不存在(在 Windows 11 24H2 中,有个小问题:
AcquireNextFrame会因隐藏窗口更新而唤醒,但不传递内容)。 - Windows.Graphics.Capture (WGC)——窗口不存在。
唯一例外是 PrintWindow。此方法绕过 DWM,直接要求窗口渲染到指定 DC,忽略该标志。不过,大多数屏幕共享应用(Zoom、Teams、Discord、OBS)不使用 PrintWindow。
关键限制:WDA_EXCLUDEFROMCAPTURE 仅从 Windows 10 20H1(2020 年 5 月更新)开始支持。在旧版(包括 LTSC)中,该函数返回 ERROR_INVALID_PARAMETER。此时,回退到 WDA_MONITOR,它会在捕获中显示黑色矩形。
还有一个未文档化的属性 WCA_EXCLUDED_FROM_DDA,通过 SetWindowCompositionAttribute 设置,仅将窗口从 Desktop Duplication API 中排除。实际中无需它,因为 WDA_EXCLUDEFROMCAPTURE 覆盖更全面。
macOS 至 14 版:sharingType 作为可靠机制
在 macOS 14 版及更早版本中,使用 NSWindow 属性 sharingType:
window.sharingType = .none
可用值:
.readWrite——窗口可被其他进程读取和修改(很少使用)。.readOnly——窗口可被捕获(默认)。.none——窗口从捕获中排除。
此标志通过 WindowServer 生效,后者使用 Core Graphics 处理合成。直至 macOS 14 Sonoma(含),sharingType = .none 有效阻挡了旧版 API(CGWindowListCreateImage)和新版 ScreenCaptureKit(macOS 12.3 引入)的捕获。
Electron 和 Tauri 的 setContentProtection(true) 方法在底层使用了此机制。
macOS 15 Sequoia:ScreenCaptureKit 的倒退
在 macOS 15 Sequoia 中,Apple 改变了 WindowServer 架构:现在合成将所有可见窗口组装到一个单一帧缓冲区,ScreenCaptureKit 直接捕获它。因此,sharingType = .none 不再影响 SCStream。
Apple 官方代表在开发者论坛确认:“目前没有公开 API 可防止屏幕捕获”。
关键细节:旧版 API(CGWindowListCreateImage)仍尊重 sharingType = .none。这导致应用行为分裂:
- Zoom(当前版)、Teams、QuickTime、系统截图(Cmd+Shift+3/4/5)、较新 OBS 版——使用 ScreenCaptureKit → 窗口可见。
- Google Chrome(
getDisplayMedia)、Chrome 中的 Google Meet、较旧 OBS 版——使用 CoreGraphics → 窗口隐藏。
这是需要屏幕捕获保护的产品面临的重大倒退。没有私有 API,就无解——使用它们保证 App Store 审核被拒且 OS 更新后失效。备选方案包括限制支持 macOS 14 及更早版本,或行为方法:屏幕共享开始时自动最小化窗口,通过系统信号检测 SCStream 活动。
Linux:X11 与 Wayland——两个不同世界
在 Linux 上,情况因显示服务器而异。
X11——从架构上不支持窗口防捕获保护。任何有显示访问权限的客户端均可调用 XGetImage 或 XCompositeNameWindowPixmap 抓取任意窗口像素。合成扩展(Picom、KWin、Mutter)可能提供部分保护,但无跨窗口管理器的解决方案。
Wayland——基于隔离原则:客户端无法读取他人表面。屏幕捕获仅通过 xdg-desktop-portal + PipeWire 可能,且需明确用户同意。窗口保护在协议层面处理——如果窗口不授予访问,它不会出现在捕获中。这使 Wayland 远比 X11 更安全,但要求应用正确实现协议。
关键要点
- 使用
SetWindowDisplayAffinity的WDA_EXCLUDEFROMCAPTURE——Windows 上完全排除窗口捕获的事实标准。从 Windows 10 2020 年 5 月更新开始支持。 - 在 macOS 15 Sequoia 上,
sharingType = .none不再阻挡 ScreenCaptureKit——这是 Apple 架构变更,无公开变通方法。 - 在 Linux 上,X11 协议设计使窗口保护不可能;Wayland 在协议层面处理。
- Windows 上的
PrintWindow绕过保护,但流行屏幕共享应用不使用它。 - Electron 和 Tauri 在底层利用这些 API——行为取决于平台和 OS 版本。
— Editorial Team
暂无评论。