Capture HWND with context menu

I am using WinAPI to capture windows in Java.

public static BufferedImage capture(Pointer hWnd2, String wText) {
   HWND hWnd = new HWND(hWnd2);
   boolean success;
   if(User32Extra.INSTANCE.IsIconic(hWnd))
   {
       success= User32Extra.INSTANCE.ShowWindow(hWnd, 4);
       System.err.println(success);
       System.err.println(wText+Native.getLastError());
   }
   HDC hdcWindow2 = User32.INSTANCE.GetDC(hWnd);
   HDC hdcMemDC = GDI32.INSTANCE.CreateCompatibleDC(hdcWindow2);
   success = User32Extra.INSTANCE.PrintWindow(hWnd,hdcWindow2,1);
   RECT bounds = new RECT();
   RECT bounds1 = new RECT();
   User32Extra.INSTANCE.GetWindowRect(hWnd, bounds);
   User32Extra.INSTANCE.GetClientRect(hWnd, bounds1);

   int extraGap = (bounds.right-bounds.left-bounds1.right);
   int width = bounds.right-bounds.left-extraGap;
   int height = bounds.bottom-bounds.top-extraGap ;
   HBITMAP hBitmap = GDI32.INSTANCE.CreateCompatibleBitmap(hdcWindow2, width, height);

   HANDLE hOld = GDI32.INSTANCE.SelectObject(hdcMemDC, hBitmap);
   GDI32Extra.INSTANCE.BitBlt(hdcMemDC,0, 0, width, height, hdcWindow2, bounds.left+bounds1.right-bounds.right+extraGap, bounds.top+bounds1.bottom-bounds.bottom+extraGap, WinGDIExtra.SRCCOPY);
   GDI32.INSTANCE.SelectObject(hdcMemDC, hOld);

   BITMAPINFO bmi = new BITMAPINFO();
   bmi.bmiHeader.biWidth = width;
   bmi.bmiHeader.biHeight = -height;
   bmi.bmiHeader.biPlanes = 1;
   bmi.bmiHeader.biBitCount = 32;
   bmi.bmiHeader.biCompression = WinGDI.BI_RGB;
   Memory buffer = new Memory(width * height * 4);
   GDI32.INSTANCE.GetDIBits(hdcWindow2, hBitmap, 0, height, buffer, bmi, WinGDI.DIB_RGB_COLORS);

   BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

   GDI32.INSTANCE.DeleteObject(hBitmap);
   GDI32.INSTANCE.DeleteDC(hdcMemDC);
   User32.INSTANCE.ReleaseDC(hWnd, hdcWindow2);

   return image;
}

      

This code fixes the specified HWND window. But if the user opens the context menu or menu options. They are not captured.

How to grab the context menu of a window?

+3


source to share


1 answer


I would use GetDesktopWindow()

to capture the entire screen and then pass the window coordinates to BitBlt

to copy only the target window with all overlays (including the context menu)

public static BufferedImage capture(Pointer hWnd2, String wText) {
   HWND hWnd = new HWND(hWnd2);
   boolean success;
   if(User32Extra.INSTANCE.IsIconic(hWnd))
   {
       success= User32Extra.INSTANCE.ShowWindow(hWnd, 4);
       System.err.println(success);
       System.err.println(wText+Native.getLastError());
   }
   // Pass GetDesktopWindow instead of target HWND to GetDC
   HDC hdcWindow2 = User32.INSTANCE.GetDC(User32.INSTANCE.GetDesktopWindow());
   HDC hdcMemDC = GDI32.INSTANCE.CreateCompatibleDC(hdcWindow2);

   RECT bounds = new RECT();
   //RECT bounds1 = new RECT();
   User32Extra.INSTANCE.GetWindowRect(hWnd, bounds);
   //User32Extra.INSTANCE.GetClientRect(hWnd, bounds1);

   //int extraGap = (bounds.right-bounds.left-bounds1.right);
   int width = bounds.right-bounds.left;
   int height = bounds.bottom-bounds.top;
   HBITMAP hBitmap = GDI32.INSTANCE.CreateCompatibleBitmap(hdcWindow2, width, height);

   HANDLE hOld = GDI32.INSTANCE.SelectObject(hdcMemDC, hBitmap);
   GDI32Extra.INSTANCE.BitBlt(hdcMemDC,0, 0, width, height, hdcWindow2, bounds.left, bounds.top, WinGDIExtra.SRCCOPY);
   GDI32.INSTANCE.SelectObject(hdcMemDC, hOld);

   BITMAPINFO bmi = new BITMAPINFO();
   bmi.bmiHeader.biWidth = width;
   bmi.bmiHeader.biHeight = -height;
   bmi.bmiHeader.biPlanes = 1;
   bmi.bmiHeader.biBitCount = 32;
   bmi.bmiHeader.biCompression = WinGDI.BI_RGB;
   Memory buffer = new Memory(width * height * 4);
   GDI32.INSTANCE.GetDIBits(hdcWindow2, hBitmap, 0, height, buffer, bmi, WinGDI.DIB_RGB_COLORS);

   BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

   GDI32.INSTANCE.DeleteObject(hBitmap);
   GDI32.INSTANCE.DeleteDC(hdcMemDC);
   User32.INSTANCE.ReleaseDC(hWnd, hdcWindow2);

   return image;
}

      

This method, however, will grab another window if it overlaps our target window. Probably enough if you know the window is active or the context menu is open (because it will be enforced for it).

Another approach is a little hacky, but it works for other scenarios as well. We can check if there are windows with class name #32768

(class for popup menus). If so, we check if our target window is in the foreground, and then we can copy it to the target bitmap on top of our window.

public static BufferedImage capture(Pointer hWnd2, String wText) {
    HWND hWnd = new HWND(hWnd2);
    boolean success;
    if(User32Extra.INSTANCE.IsIconic(hWnd))
    {
        success= User32Extra.INSTANCE.ShowWindow(hWnd, 4);
        System.err.println(success);
        System.err.println(wText+Native.getLastError());
    }
    HDC hdcWindow2 = User32.INSTANCE.GetDC(hWnd);
    final HDC hdcMemDC = GDI32.INSTANCE.CreateCompatibleDC(hdcWindow2);

    RECT bounds = new RECT();
    RECT bounds1 = new RECT();
    User32Extra.INSTANCE.GetWindowRect(hWnd, bounds);
    User32Extra.INSTANCE.GetClientRect(hWnd, bounds1);

    int extraGap = (bounds.right-bounds.left-bounds1.right);
    int width = bounds.right-bounds.left-extraGap;
    int height = bounds.bottom-bounds.top-extraGap ;
    HBITMAP hBitmap = GDI32.INSTANCE.CreateCompatibleBitmap(hdcWindow2, width, height);

    HANDLE hOld = GDI32.INSTANCE.SelectObject(hdcMemDC, hBitmap);
    GDI32Extra.INSTANCE.BitBlt(hdcMemDC,0, 0, width, height, hdcWindow2, bounds.left+bounds1.right-bounds.right+extraGap, bounds.top+bounds1.bottom-bounds.bottom+extraGap, WinGDIExtra.SRCCOPY);

    // Popup menu doesn't have parent, so we could encounter situation when context menu from another window is overlapping our target window
    // Since context menu enforces our window to focus, we can check here if our window is foreground window (otherwise it is not possible to have opened context menu)
    if (User32.INSTANCE.GetForegroundWindow() == hWnd)
    {
        final RECT windowBounds = bounds;
        // Enum all windows and check if they have class #32768
        User32.INSTANCE.EnumWindows(new WNDENUMPROC() {
            @Override
            public boolean callback(HWND hMenuWnd, Pointer arg1) {
                byte[] windowClass = new byte[8];
                User32.INSTANCE.GetClassNameA(hMenuWnd, windowClass, 8);
                String wClass = Native.toString(windowClass);
                // #32768 is class name for popup menus.
                if (wClass.equals("#32768"))
                {
                    HDC hdcWindowMenu = User32.INSTANCE.GetDC(hMenuWnd);
                    RECT menuBounds = new RECT();
                    User32Extra.INSTANCE.GetWindowRect(hMenuWnd, menuBounds);
                    GDI32Extra.INSTANCE.BitBlt(hdcMemDC, menuBounds.left - windowBounds.left, menuBounds.top - windowBounds.top, menuBounds.right-menuBounds.left, menuBounds.bottom-menuBounds.top, hdcWindowMenu, 0, 0, WinGDIExtra.SRCCOPY);
                    User32.INSTANCE.ReleaseDC(hMenuWnd, hdcWindowMenu);
                }
                return true;
            }
        }, null);
    }

    GDI32.INSTANCE.SelectObject(hdcMemDC, hOld);

    BITMAPINFO bmi = new BITMAPINFO();
    bmi.bmiHeader.biWidth = width;
    bmi.bmiHeader.biHeight = -height;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 32;
    bmi.bmiHeader.biCompression = WinGDI.BI_RGB;
    Memory buffer = new Memory(width * height * 4);
    GDI32.INSTANCE.GetDIBits(hdcWindow2, hBitmap, 0, height, buffer, bmi, WinGDI.DIB_RGB_COLORS);

    BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

    GDI32.INSTANCE.DeleteObject(hBitmap);
    GDI32.INSTANCE.DeleteDC(hdcMemDC);
    User32.INSTANCE.ReleaseDC(hWnd, hdcWindow2);

    return image;
}

      



For EnumWindows, you need to add the following imports:

import com.sun.jna.platform.win32.WinUser.WNDENUMPROC;

      

and definition:

boolean EnumWindows(WinUser.WNDENUMPROC lpEnumFunc, Pointer arg);
int GetClassNameA(HWND hWnd, byte[] lpString, int nMaxCount);

      

+1


source







All Articles