有什么方法可以从桌面复制中省略 window 吗?

Is there any way to omit a window from Desktop Duplication?

我希望能够显示 window,其中包含向用户显示但桌面复制未捕获的消息。这可能吗?

或者,有没有一种方法可以在向用户显示之前在桌面表面上绘制? (理想情况下不会大量拖延 GPU)

背景:我正在编写一个远程查看/支持应用程序,并希望允许远程用户在隐私中工作 - 在不干扰捕获的同时消隐用户的屏幕。

我想避免回到 WM_PRINT 和 BitBlt 的黑暗日子,但我不确定 DXGI 是否允许我做我想做的事情。

Desktop Duplication 将组合图像复制到视频输出,您的想法是让它工作,不仅排除特定区域,而且还有操作系统 rendering/composition activity windows 后面的 window 是正常桌面操作所不需要的组成。这种组合实际上并没有首先发生,桌面复制不提供强制它或以其他方式在每个 window 基础上分离图像数据的服务。

注意:此答案仅部分解决了问题。

@DanGroom 和我想要实现的是捕获屏幕的内容,同时屏幕不断显示覆盖屏幕内容的固定图像或位图。

如@RomanR 所建议。我查看了 UWP 屏幕截图 API,但我意识到它们仅适用于 UWP 应用程序。我没有尝试过它们,但它们似乎是基于 DirectX 的,就像桌面复制 sample here. I don't know DirectX but from my understanding there is no way to monitor a specific window using these API. You can just get a frame of the whole screen. If the screen is covered with a full screen window that's the window which is captured on each frame (I added a piece of code 将帧转储到 bmp 中一样,这就是行为)。 所以这些 API 导致了我的死胡同。

我用 DWM Thumbnail API DwmRegisterThumbnail() 等...)取得了一些成功。与屏幕截图 API 相比,它们非常简单。

  1. 您注册了一个您想要监控的 window 并设置了您想要接收被监控 window 图像的目的地 window。
  2. 使用 DwmUpdateThumbnailProperties() 调用定期询问受监视 window 的更新图像。

有一个很好的样本here

这些 API 的最大好处是 它们还可以与 windows 一起使用,而其他 windows 当前未显示或涵盖。图像质量和帧率是 可以接受,您甚至可以获得 window 的全高清缩略图。 所以你可以创建一个最顶层的全屏 window 并注册它来接收另一个 window 的 "frames"。结果是全屏 window,显示另一个 window 的内容。

由于缩略图被发送到 window,这就减少了使用 BitBlt 捕获接收另一个 window 帧的 window 图像的问题。那是因为屏幕更新直接发送到目的地 window 并且显然你不能只在缓冲区或类似的东西中接收帧更新。如果您使目标 window 透明并尝试捕获帧,您将获得黑色位图。此外,您可能会遇到像 this.

这样的 BitBlt 捕获问题

问题可以这样解决:

  1. 拦截 DwmUpdateThumbnailProperties 调用发送的消息(WM_...某物),该调用还应将帧发送到目的地 window。
  2. 在 window
  3. 上禁用之前以任何格式和内存保存帧
  4. 发送到目的地window一个不同的帧(用于覆盖屏幕的位图)
  5. 转到第 1 点

不幸的是,我不知道如何使用 windows API 来实现。特别是对于第 2 点,如何在不捕获已经显示的图像的情况下获得 window 的帧?

我在网上搜索了一下,发现了 Magnification API。 我发现可以使用 MagSetImageScalingCallback API.

为放大线程注册回调

据我所知,只要需要将新帧绘制到使用 MagSetWindowSource API 注册的放大镜 window 中,就会调用此回调。 原始屏幕位图和所有相关信息都传递给回调,其目标是在回调 returns.

时将绘制的位图转换为 window

在我看来 "ImageScalingCallback" 这个名字可能会导致对实际用法的误解。 无论如何,我终于意识到如何在我的应用程序中使用它:

1) 放大镜 window 已创建并设置为全屏最顶层。

2) 需要绘制第一帧时立即调用回调

3) 原始位图复制到另一个缓冲区

4) 原位图内容替换为纯黑位图

5)回调returns并将修改后的位图绘制到放大镜window

可以在不失去 "capture" 能力的情况下重复这些步骤。 事实上,即使屏幕被黑色图像覆盖,也不会阻止放大 API 捕获屏幕。

那是因为注册为放大镜 window 的 window 从未包含在捕获中(即使是全屏 window) .

这正是我要找的行为。 我稍微修改了 CodeProject 网站上的示例 Screenshot using the Magnification library 以实现此行为。包含在 srcdata 指针中的捕获图像被转储到一组文件以证明捕获正在工作并且每个图像都包含更新的捕获。

不幸的是,这些 API 已被弃用,尚未提供替代品。

一种选择是自己实施 RDP 协议,并利用 windows 中已有的功能在启动远程会话时锁定本地会话。您可以查看 mstsclib if you wish to do this yourself, or you can check out mRemoteNG,这是一个功能齐全的 C# 远程桌面客户端,它也实现了该协议。

另一种选择是捕获部分(或完全)隐藏到您选择的位图上下文的 windows,您可以使用 user32.dll PrintWindow 和鲜为人知的 PW_RENDERFULLCONTENT标志。此标志仅在 Windows 8.1 或更高版本上可用,它甚至会捕获使用 Composition API 或直接使用 DirectX 渲染的 windows。下面是您如何使用它的一个简单示例:

Bitmap GetWindowBitmap(IntPtr hWnd) {
    RECT bounds;
    if (!GetWindowRect(hWnd, out bounds))
        throw new Win32Exception();

    var bmp = new Bitmap(bounds.right - bounds.left, bounds.bottom - bounds.top);
    using (var g = Graphics.FromImage(bmp))
    {
        IntPtr dc = IntPtr.Zero; 
        try
        {
            dc = g.GetHdc();
            bool success = PrintWindow(hWnd, dc, PrintWindowDrawingOptions.PW_RENDERFULLCONTENT /* 0x00000002 */);
            if (!success)
                throw new Win32Exception();
        }
        finally
        {
            if (dc != IntPtr.Zero)
                g.ReleaseHdc(dc);
        }
    }
    return bmp;
}

您可以使用该方法枚举桌面上的所有 windows,但速度不会很快,因为 PrintWindow 要求每个 window 重绘到您提供的 hdc。即使是一个不当行为 window 也会使这一过程减慢数百或数千毫秒。