WPF 透過 SetWindowDisplayAffinity 配置禁止對視窗進行截圖或錄屏

lindexi發表於2024-06-16

有些應用程式比較機密或隱私,不期望被其他截圖軟體截圖到應用的視窗,或者被錄屏軟體錄製到。簡單的方法是透過 SetWindowDisplayAffinity 方法進行配置視窗阻止截圖軟體對其截圖

開始之前必須說明的是對抗截圖錄屏是一個矛和盾的事情,截圖和錄屏技術方向在千方百計嘗試對所有視窗進行截圖和錄屏。而某些機密或隱私等軟體又在對抗截圖和錄屏。本文使用的 SetWindowDisplayAffinity 只是一個非常基礎的禁止視窗被截圖的方法,能防住的截圖工具和錄屏軟體有限,只能做簡單的保護視窗不被基礎截圖工具所獲取介面

按照使用 Win32 方法的慣例,先定義出來 SetWindowDisplayAffinity 方法,程式碼如下

    private const uint WDA_NONE = 0x00000000;
    private const uint WDA_MONITOR = 0x00000001;

    [DllImport("user32.dll")]
    public static extern uint SetWindowDisplayAffinity(IntPtr hWnd, uint dwAffinity);

在 .NET 7 之後,還可以使用 LibraryImportAttribute 這個原始碼生成器輔助的定義 Win32 方法,對比 DllImport 的優勢在於能夠透過原始碼生成器最佳化呼叫的效能。更新之後的定義程式碼如下,核心是將 extern 換成 partial 關鍵詞和更換標記的特性

    [LibraryImport("user32.dll")]
    public static partial uint SetWindowDisplayAffinity(IntPtr hWnd, uint dwAffinity);

對於本文使用的如此簡單的 SetWindowDisplayAffinity 方法,使用 LibraryImportAttribute 是沒有帶來什麼好處的

且使用此特性需要給當前的專案開啟不安全程式碼的允許開關。更多請參考 P/Invoke source generation - .NET Microsoft Learn

為了方便本文描述,我新建了一個例子專案,可以在本文末尾找到本文所有程式碼的下載方法

在 MainWindow.xaml 放一個按鈕,用於控制設定視窗允許和禁止截圖的狀態

        <ToggleButton x:Name="TakeSnapshotToggleButton" HorizontalAlignment="Center" VerticalAlignment="Center" Padding="10,10,10,10" Content="禁止截圖" Checked="TakeSnapshotToggleButton_OnChecked"/>

後臺程式碼編寫實現邏輯

    private void TakeSnapshotToggleButton_OnChecked(object sender, RoutedEventArgs e)
    {
        if (TakeSnapshotToggleButton.IsChecked is true)
        {
            // 禁止截圖模式
            SetWindowDisplayAffinity(new WindowInteropHelper(this).Handle, WDA_MONITOR);

            // 修改內容為再點選就是允許截圖
            TakeSnapshotToggleButton.Content = "允許截圖";
        }
        else
        {
            // 允許截圖模式
            SetWindowDisplayAffinity(new WindowInteropHelper(this).Handle, WDA_NONE);

            // 修改內容為再點選就是禁止截圖
            TakeSnapshotToggleButton.Content = "禁止截圖";
        }
    }

如此即可實現此按鈕功能,嘗試執行程式碼,點選按鈕,進入禁止截圖狀態。然後使用截圖軟體,如 QQ 截圖等工具嘗試進行截圖,可以看到視窗是黑的不能被截圖

接著再點選按鈕,進入允許截圖狀態,此時可以看到截圖軟體可以對視窗進行截圖可以看到視窗的內容

透過本文的方法只能防禦有限的截圖軟體。有些從驅動級進行獲取介面影像的,或者 Hook 掉 DWM 的,甚至更徹底的從 HDMI 級硬體捕獲的,這些都統統無法防禦

在 Windows 10 的 2004 版本,對 SetWindowDisplayAffinity 方法進行了擴充套件,新增了只允許在顯示器顯示而不在任何截圖或錄屏工具顯示的引數。在原先的 SetWindowDisplayAffinity 使用 WDA_MONITOR 禁止截圖時,使用截圖工具將看到一個黑色的視窗,看不到任何內容。但是對於一些錄屏軟體來說,會影響其體驗。有時候期望做一個錄屏輔助工具,卻要麼發現錄屏輔助工具被錄屏工具錄製進去,要麼就是黑色一片影響互動。透過新的 WDA_EXCLUDEFROMCAPTURE 引數,可以有效進行最佳化

使用 WDA_EXCLUDEFROMCAPTURE 引數,可以配置應用視窗只允許在顯示器顯示而不在任何截圖或錄屏工具顯示,這就意味著視窗對於截圖軟體錄屏軟體來說是隱藏的,從截圖軟體裡面不再可以看到應用視窗,截圖軟體不會看到黑色的視窗而是完全不知道有這樣的視窗的存在

使用方法也非常簡單,如以下程式碼

SetWindowDisplayAffinity(new WindowInteropHelper(this).Handle, WDA_EXCLUDEFROMCAPTURE);

    private const uint WDA_EXCLUDEFROMCAPTURE = 0x00000011;

    [DllImport("user32.dll")]
    public static extern uint SetWindowDisplayAffinity(IntPtr hWnd, uint dwAffinity);

大家可以執行程式碼,測試一些分別設定 WDA_MONITOR 和 WDA_EXCLUDEFROMCAPTURE 引數,對截圖軟體的影響

特別說明的是,只有在 Windows 10 的 2004 和更高版本,才能支援 WDA_EXCLUDEFROMCAPTURE 引數。如果在更低的版本執行,則 WDA_EXCLUDEFROMCAPTURE 引數的功能和 WDA_MONITOR 相同

本文程式碼放在 githubgitee 上,可以使用如下命令列拉取程式碼

先建立一個空資料夾,接著使用命令列 cd 命令進入此空資料夾,在命令列裡面輸入以下程式碼,即可獲取到本文的程式碼

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 21c1500cd1ca8bffe892644425235de7eac24f92

以上使用的是 gitee 的源,如果 gitee 不能訪問導致拉取失敗,請替換為 github 的源。請在命令列繼續輸入以下程式碼,將 gitee 源換成 github 源進行拉取程式碼

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 21c1500cd1ca8bffe892644425235de7eac24f92

獲取程式碼之後,進入 WPFDemo/LecacheniJequchaferenal 資料夾,即可獲取到原始碼

參考文件

P/Invoke source generation - .NET Microsoft Learn

https://github.com/akinbicer/screen-capture-protector

SetWindowDisplayAffinity function (winuser.h) - Win32 apps Microsoft Learn

相關文章