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

3.3k Views Asked by At

I'd like to be able to show a window containing a message that is displayed to the user but which is not captured by Desktop Duplication. Is that possible?

Alternatively, is there a way I can draw over the top of the desktop surface before it gets shown to the user? (ideally without massively stalling the GPU)

Background: I'm writing a remote viewing / support application, and want to allow the remote user to work in privacy - blanking the user's screen while not interfering with capture.

I'd like to avoid falling back to the dark days of WM_PRINT and BitBlt, but I'm not sure DXGI allows what I want to do.

5

There are 5 best solutions below

2
On BEST ANSWER

Desktop Duplication copies composed image delivered to video output and your idea is to have it working not just excluding specific region(s) but also have operating system rendering/composition activity for the windows behind the window in question, the composition which is not necessary for the normal desktop operation. Such composition is not actually happening in first place and Desktop Duplication does not offer services to force it or otherwise separate image data on per-window basis.

0
On

One option would be to implement the RDP protocol yourself, and take advantage of the features already available in windows to lock out the local session when a remote session is started. You can look into mstsclib if you wish to do this yourself, or you can check out mRemoteNG which is a fully featured C# remote desktop client which also implements the protocol.

Another option, is to capture windows which are partially (or fully) hidden to a bitmap context of your choosing, you can use user32.dll PrintWindow and the little known PW_RENDERFULLCONTENT flag. This flag is only available on Windows 8.1 or newer, and it will capture even windows which are rendered with the Composition API's or directly with DirectX. A simple example of how you might use this is below:

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

You could enumerate all the windows on the desktop using the approach, but it will not be fast, as PrintWindow asks each window to redraw to the hdc you've provided. Even one misbehaving window can slow this down by hundreds or thousands of milliseconds.

0
On

I dug a little bit in the web and I came across the Magnification API. I discovered that a callback can be registered for the magnification thread using the MagSetImageScalingCallback API.

As far as I understand this callback is called whenever a new frame need to be drawn into the magnifier window registered with the MagSetWindowSource API. The raw screen bitmap and all related information are passed to the callback whose goal is to transform the bitmap that will be drawn to the window upon the callback returns.

In my opinion the name "ImageScalingCallback" can lead to a misunderstanding of the real usage. Anyway I finally realized how this can be used in my application:

1) The magnifier window is created and set as fullscreen-topmost.

2) The callback is called as soon as the first frame need to be drawn

3) The original bitmap is copied to another buffer

4) The original bitmap content is replaced with a flat-black bitmap

5) The callback returns and the modified bitmap is drawn to the magnifier window

These step can be iterated without loosing the "capture" capability. In fact even if the screen is covered with a black image this doesn't prevent the Magnification API from capturing the screen.

That's because the window registered as magnifier window is never included in the capture (even in case of a fullscreen window).

This is exactly the behavior I was looking for. I slightly modified the sample Screenshot using the Magnification library on the CodeProject web site to implemented this behavior. The captured images contained into the srcdata pointer are dumped to a set of file to demonstrate that the capture is working and that each image contains the updated capture.

Unfortunately these API are deprecated and no replacement has been provided yet.

0
On

It's an old thread, but still if you want to hide a Window from Desktop Duplication or Windows Graphics Capture API, you can use this function

[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);
}
0
On

NOTE: This answer address the problem only partialy.

What @DanGroom and me wanted to achieve is capturing the content of the screen while the screen keep showing a fixed image or bitmap which cover the screen content.

As suggested by @RomanR. I took a look at the UWP screen capture API however I realized that they only apply to UWP applications. I didn't try them but it seems like they are based on DirectX just like the desktop duplication 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 to dump a frame into a bmp and that was the behaviour). So these API led to a blind alley in my case.

I had a little success instead with the DWM Thumbnail API (DwmRegisterThumbnail() etc ...). They are quite simple compared to the screen capture API.

  1. You register a window you want to monitor and set the destination window where you want to receive the image of the monitored window.
  2. Ask periodically for an updated image of the monitored window using the DwmUpdateThumbnailProperties() call.

There is a good sample here

The big benefit of these API is that they work also with windows which are not currently showed or covered by other windows. The image quality and frame rate is acceptable and you can even get a FullHD thumbnail of a window. So you can just create a topmost fullscreen window and register it to receive the "frames" of another window. The result is a fullscreen window which display the content of another window.

Since the thumbnail is sent to a window this reduces to the problem of using BitBlt to capture the image of a window which receive the frames of another window. That's because the screen updates are sent directly to the destination window and apparently you can't just receive the frame updates in buffer or something like this. If you make the destination window transparent and try to capture the frames you get a black bitmap. Also you might be stuck with BitBlt capturing problems like this.

The problem might be solved in this way:

  1. Intercept the message (WM_...something) sent by the DwmUpdateThumbnailProperties call which should send also the frame to the destination window.
  2. Save the frame in whatever format and memory just before this is disaplyed on the window
  3. Send to the destination window a different frame (the bitmap used to cover the screen)
  4. Go to point 1

Unfortunately I don't know how to achieve that by using the windows API. Especially for point 2 how can I get the frame of a window without capturing the already displayed image ?