我希望能够显示一个窗口,其中包含一条显示给用户但桌面复制未捕获的消息。这可能吗?
或者,在向用户显示桌面之前,有没有一种方法可以在桌面顶部绘制?(理想情况下不需要大规模拖延GPU)
背景:我正在编写一个远程查看/支持应用程序,希望允许远程用户私下工作——在不干扰捕获的情况下屏蔽用户的屏幕。
我想避免回到WM_PRINT和BitBlt的黑暗日子,但我不确定DXGI是否允许我想做的事情。
桌面复制将合成的图像复制到视频输出中,您的想法是不仅要排除特定区域,还要为有问题的窗口后面的窗口进行操作系统渲染/合成活动,这种合成对于正常的桌面操作来说是不必要的。这种合成最初并没有真正发生,桌面复制也不提供强制它或以其他方式按窗口分离图像数据的服务。
注意:这个答案只是部分地解决了问题。
@DanGroom和我想要实现的是捕捉屏幕的内容,同时屏幕不断显示覆盖屏幕内容的固定图像或位图。
正如@RomanR所建议的。我看了一下UWP截屏API,但我意识到它们只适用于UWP应用程序。我没有尝试过,但看起来它们是基于DirectX的,就像这里的桌面复制示例一样。我不知道DirectX,但据我所知,没有办法使用这些API来监控特定的窗口。你可以得到整个屏幕的一帧。如果屏幕上覆盖着一个全屏窗口,那就是每个帧上捕获的窗口(我添加了一段代码,将帧转储到bmp中,这就是行为)。所以这些API在我的情况下导致了一条死胡同。
我使用DWM Thumbnail API取得了一些成功(DwmRegisterThumbnail()等等)。与屏幕截图API相比,它们非常简单。
- 注册要监视的窗口,并设置要接收监视窗口图像的目标窗口
- 使用DwmUpdateThumbnailProperties()调用定期询问被监视窗口的更新图像
这里有一个很好的样品
这些API的最大好处是它们还可以使用当前未显示或未被其他窗口覆盖的窗口。图像质量和帧速率为可以接受,您甚至可以获得窗口的FullHD缩略图。因此,您只需创建一个最顶部的全屏窗口并注册它即可接收另一个窗口的"帧"。结果是一个全屏窗口,显示另一个窗口的内容。
由于缩略图被发送到窗口,这减少了使用BitBlt来捕获接收另一窗口的帧的窗口的图像的问题。这是因为屏幕更新直接发送到目标窗口,显然你不能只在缓冲区或类似的地方接收帧更新。如果您使目标窗口透明并尝试捕获帧,则会得到一个黑色位图。此外,您可能会被BitBlt捕获这样的问题所困扰。
这个问题可以通过以下方式解决:
- 拦截DwmUpdateThumbnailProperties调用发送的消息(WM_…something),该调用还应将帧发送到目标窗口
- 在窗口上禁用之前,以任何格式和内存保存帧
- 向目标窗口发送不同的帧(用于覆盖屏幕的位图)
- 转到第1点
遗憾的是,我不知道如何通过使用windows API来实现这一点。特别是对于第2点,如何在不捕获已显示图像的情况下获得窗口的边框?
我在网上挖了一点,发现了Magnification API。我发现可以使用MagSetImageScalingCallbackneneneba API为放大线程注册回调。
据我所知,每当需要将新帧绘制到使用MagSetWindowSourceneneneba API注册的放大镜窗口中时,都会调用此回调。原始屏幕位图和所有相关信息将传递给回调,回调的目标是在回调返回时转换将绘制到窗口的位图。
在我看来,"图像缩放回调"这个名称可能会导致对实际用法的误解。无论如何,我终于意识到这可以在我的应用程序中使用:
1) 此时会创建放大镜窗口,并将其设置为全屏顶部。
2) 一旦需要绘制第一帧,就会调用回调
3) 原始位图被复制到另一个缓冲区
4) 原始位图内容被替换为平面黑色位图
5) 回调返回,修改后的位图被绘制到放大镜窗口
这些步骤可以在不失去"捕获"能力的情况下重复执行。事实上,即使屏幕被黑色图像覆盖,这也不会阻止放大API捕获屏幕。
这是因为注册为放大镜窗口的窗口从未包含在捕获中(即使是全屏窗口)。
这正是我想要的行为。我使用CodeProject网站上的放大库对示例屏幕截图进行了轻微修改,以实现这种行为。srcdata指针中包含的捕获图像被转储到一组文件中,以证明捕获正在工作,并且每个图像都包含更新的捕获。
遗憾的是,这些API已被弃用,尚未提供替代品。
这是一个旧线程,但如果您想从桌面复制或Windows图形捕获API隐藏窗口,您可以使用此功能
[DllImport("user32.dll")]
private static extern uint SetWindowDisplayAffinity(IntPtr hwnd, uint dwAffinity);
public void HideWindowContent(IntPtr hwnd)
{
uint WDA_EXCLUDEFROMCAPTURE = 0x00000011;
var result = SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE);
}
一种选择是自己实现RDP协议,并利用windows中已有的功能在远程会话启动时锁定本地会话。如果你想自己做这件事,你可以查看mstslib,或者你可以查看mRemoteNG,它是一个功能齐全的C#远程桌面客户端,也实现了协议。
另一种选择是捕获部分(或完全)隐藏在您选择的位图上下文中的窗口,您可以使用user32.dllPrintWindow
和鲜为人知的PW_RENDERFULLCONTENT
标志。此标志仅在Windows 8.1或更高版本上可用,它甚至可以捕获使用Composition 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。即使是一个行为不端的窗口也可以将速度减慢数百或数千毫秒。