4

我希望能够显示一个包含向用户显示但桌面复制未捕获的消息的窗口。那可能吗?

或者,有没有一种方法可以在向用户显示之前在桌面表面的顶部进行绘制?(理想情况下不会大量停止 GPU)

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

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

4

4 回答 4

4

桌面复制将合成图像复制到视频输出,您的想法是让它不仅可以排除特定区域,而且还可以为相关窗口后面的窗口提供操作系统渲染/合成活动,合成不是必需的正常的桌面操作。这种组合实际上并没有首先发生,并且桌面复制不提供服务来强制它或以其他方式在每个窗口的基础上分离图像数据。

于 2017-07-25T09:41:38.273 回答
1

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

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

正如@RomanR 所建议的那样。我查看了 UWP 屏幕捕获 API,但我意识到它们仅适用于 UWP 应用程序。我没有尝试过,但它们似乎是基于 DirectX 的,就像这里的桌面复制示例一样。我不知道 DirectX,但据我了解,无法使用这些 API 监视特定窗口。您可以获得整个屏幕的框架。如果屏幕被全屏窗口覆盖,那就是在每个帧上捕获的窗口(我添加了一段代码将帧转储到 bmp 中,这就是行为)。所以这些 API 在我的案例中导致了一条死胡同。

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

  1. 您注册要监视的窗口并设置要接收监视窗口图像的目标窗口。
  2. 使用DwmUpdateThumbnailProperties () 调用定期询问受监视窗口的更新图像。

这里有一个很好的样本

这些 API 的最大好处是它们也适用于当前没有被其他窗口显示或覆盖的窗口。图像质量和帧速率是可以接受的,您甚至可以获得窗口的全高清缩略图。因此,您可以创建一个最顶层的全屏窗口并将其注册为接收另一个窗口的“帧”。结果是一个全屏窗口,它显示另一个窗口的内容。

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

问题可能通过以下方式解决:

  1. 拦截 DwmUpdateThumbnailProperties 调用发送的消息(WM_...something),该调用也应该将帧发送到目标窗口。
  2. 在窗口显示之前以任何格式和内存保存框架
  3. 向目标窗口发送不同的帧(用于覆盖屏幕的位图)
  4. 转到第 1 点

不幸的是,我不知道如何通过使用 windows API 来实现这一点。特别是对于第 2 点,如何在不捕获已显示图像的情况下获得窗口框架?

于 2019-04-19T16:28:42.640 回答
1

我在网上挖了一点,发现了Magnification API。我发现可以使用MagSetImageScalingCallback API 为放大线程注册回调。

据我了解,只要需要将新框架绘制到使用MagSetWindowSource API 注册的放大镜窗口中,就会调用此回调。原始屏幕位图和所有相关信息被传递给回调,其目标是在回调返回时转换将绘制到窗口的位图。

在我看来,“图像缩放回调”这个名称可能会导致对实际用法的误解。无论如何,我终于意识到如何在我的应用程序中使用它:

1)放大镜窗口被创建并设置为全屏最顶部。

2) 一旦需要绘制第一帧,就会调用回调

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

4) 将原来的位图内容替换为纯黑色位图

5) 回调返回,修改后的位图被绘制到放大镜窗口

这些步骤可以在不丢失“捕获”能力的情况下进行迭代。事实上,即使屏幕被黑色图像覆盖,这也不会阻止 Magnification API 捕获屏幕。

这是因为注册为放大镜窗口的窗口永远不会包含在捕获中(即使是全屏窗口)

这正是我正在寻找的行为。我使用CodeProject 网站上的放大库稍微修改了示例屏幕截图以实现此行为。包含在 srcdata 指针中的捕获图像被转储到一组文件中,以证明捕获正在工作并且每个图像都包含更新的捕获。

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

于 2019-11-06T14:25:11.053 回答
0

一种选择是自己实现 RDP 协议,并利用 Windows 中已有的功能在远程会话启动时锁定本地会话。如果您希望自己执行此操作,可以查看mstsclib,或者您可以查看mRemoteNG,它是一个功能齐全的 C# 远程桌面客户端,它也实现了该协议。

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

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;
}

您可以使用该方法枚举桌面上的所有窗口,但它不会很快,因为 PrintWindow 要求每个窗口重绘到您提供的 hdc。即使是一个行为不端的窗口也可以将其减慢数百或数千毫秒。

于 2020-07-29T22:47:09.157 回答