1

I have bitblt a screenshot of the entire screen, and its indeed a valid BITMAP since after writing it to a file, I can clearly see its there.

Lets say I have the window handle of Calculator. I want to be able to constantly BitBlt the BITMAP (containing the desktop screenshot) onto the window of Calculator so that whenever calculator is open, whether its the background window (visible or overlapped by other windows) or foreground - It will always show the latest screenshot instead of the default Calculator application & its buttons.

I took a screenshot (bitmap) of the desktop and expected to use StretchDIBits to cover the region of the target application with my bitmap. However, when calculator is opened, nothing happens to it/it doesnt get replaced by the screenshot of the desktop, although the StretchDIBits function still returns the number of scan lines copied to the destination.

void* calculator_hwnd = (void*) FindWindowA("ApplicationFrameWindow", "Calculator");
void* composition_window = GetWindowDC((HWND)GetDesktopWindow());
if (composition_window)
{
    void* composition_environment = 0;
    if ((composition_environment = CreateCompatibleDC((HDC)composition_window)))
    {
        int window_x_axis = GetSystemMetrics(SM_CXVIRTUALSCREEN);
        int window_y_axis = GetSystemMetrics(SM_CYVIRTUALSCREEN);
        void* bitmap = CreateCompatibleBitmap((HDC)composition_window, window_x_axis, window_y_axis);
        if (bitmap)
        {
            void* composition_bitmap = SelectObject((HDC)composition_environment, bitmap);
            int status = PrintWindow((HWND) GetDesktopWindow(), (HDC)composition_environment, 0x00000002);
            if (!status)
                BitBlt((HDC)composition_environment, 0, 0, window_x_axis, window_y_axis, (HDC)composition_window, 0, 0, SRCCOPY);

            BITMAPINFO* pbi = CreateBitmapInfoStruct((HBITMAP)bitmap);

            BITMAPINFOHEADER* pbih = (PBITMAPINFOHEADER)pbi;
            unsigned char* lpBits = (LPBYTE)GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);

            GetDIBits((HDC)composition_environment, (HBITMAP)bitmap, 0, (WORD)pbih->biHeight, lpBits, pbi,
                            DIB_RGB_COLORS);

            void* calculator_window_environment = GetDC((HWND)calculator_hwnd);
             
            StretchDIBits((HDC)calculator_window_environment, 0, 0, window_x_axis, window_y_axis, 0, 0, window_x_axis, window_y_axis, lpBits, pbi, DIB_RGB_COLORS, SRCCOPY);

            GlobalFree(lpBits);

            SelectObject((HDC)composition_environment, composition_bitmap);
            DeleteObject(bitmap);
        }
    }
    DeleteDC((HDC)composition_environment);
    ReleaseDC((HWND)GetDesktopWindow(), (HDC)composition_window);
}

This is where I got CreateBitmapInfoStruct

This question says what I am trying to do is possible, but doesn't explain how - can anyone answer that with respect to my code?

  • 3
    There's no reliable way of doing this. The entire window region is in full control of the window owner (the thread that called `CreateWindowEx`). Things get only more complicated with newer applications. Those that use Direct Composition for display generally do not even have a GDI redirection surface. Whatever DC you get from a window of that kind isn't going to be part of rendering the window. – IInspectable Feb 10 '23 at 09:09
  • 2
    What you could do instead is create a top-level window by your own application that covers the calculator window and draws the appropriate part of the screenshot. – zett42 Feb 10 '23 at 15:16

1 Answers1

2

This isn't supported by the OS. Window rendering is at the discretion of the window owner (i.e. the thread that called CreateWindowEx), and there aren't any customization points external threads or processes can hook into.

While you can render into a device context that belongs to a foreign window, there's no way for you to (reliably) find out when you need to update, nor is there any way to let the destination know that it shouldn't overwrite part or all of the window contents you just rendered.

That's as far as GDI rendering goes, which is really only supported for compatibility with applications that haven't been updated to take advantage of desktop composition (introduced in Windows Vista). With this model, applications can choose to render window contents without ever providing a redirection surface (which is required if you wish to BitBlt/StretchBlt into a device context).

So, in essence, there's no way for you to reliably render onto a window you did not create. The common workaround is to create a (partially transparent) overlay window, which you would then move around to keep it in sync with the window it is supposed to augment. This is undoubtedly going to create a pretty poor user experience, as your contents are inevitably going to lag behind as the user moves the window.

IInspectable
  • 46,945
  • 8
  • 85
  • 181