Get all visible windows' bounding box on Windows

24 Sep, 2020C++, Windows API

I was tasked to build a screenshot utility for a Electron desktop app, and one of the requirements was to allow users to hover on each visible window and crop the screenshot image to it, similar to how the Snipping Tool's "Window Snip" mode works.

GATSBY_EMPTY_ALT

But the desktopCapturer module of the Electron is only able to capture the whole desktop of each monitor, unable to get the information of each visible window.

So I'll need to build a native programme that calls the Windows API to gather all the info and my JavaScript part of the Electron app can render it on the screenshot editor web app.

Windows APIs

  • EnumWindows: Enumerates all top-level windows on the screen by passing the handle to each window, in turn, to an application-defined callback function.

  • DwmGetWindowAttribute: Retrieves the current value of a specified Desktop Window Manager (DWM) attribute applied to a window.

    And by setting the 2nd argument dwAttribute to DWMWA_EXTENDED_FRAME_BOUNDS, I should be able to get the window's frame bounds rectangle in screen space.

Another problem here is that the "in screen space" means the coordinates of the bounding rectangle is relative to the Windows's virtual screen when there're more than 1 monitor.

GATSBY_EMPTY_ALT

But Electron's desktopCapturer API doesn't support such virtual screen concept, thus I'll need to convert the "in screen space" coordinates to relative-to-each-monitor coordinates in order to render the visible windows' bounding boxes on the BrowserWindow on each monitor. To achieve such conversion, I'll need call another 2 functions:

  • MonitorFromWindow: Retrieves a handle to the display monitor that has the largest area of intersection with the bounding rectangle of a specified window.

  • GetMonitorInfoA: The GetMonitorInfo function retrieves information about a display monitor.

Besides, to allow the Electron app to match the monitor with the output, I also need to get the device (monitor/display) id that matches the id from the Electron's Display object.

After a bit of digging into the Electron's source code, I found that the Device.id is generated by calling a display::win::DisplayInfo::DeviceIdFromDeviceName, which comes from Chromium's ui/display/win/display_info.cc. And in the ui/display/win/screen_win.cc the DisplayInfo::DeviceIdFromDeviceName function is being called with a monitor_info.szDevice argument.

Looking back to the DisplayInfo::DeviceIdFromDeviceName's implementation, it is clear that the Electron's Device.id is the result of Chromium's internal FastHash of the monInfo.szDevice and converted to a number. But I reckon it's too troublesome to port the Chromium's Hash to my code, so I just return the device name and do the fast-hash in JavaScript outside of this programme.

Source code

#define NOMINMAX
#include <string>
#include <algorithm>
#include <iostream>
#include <windows.h>
#include <dwmapi.h>

static BOOL CALLBACK enumWindowCallback(HWND hWnd, LPARAM lparam) {
  int length = GetWindowTextLength(hWnd);
  char* buffer = new char[length + 1];
  GetWindowText(hWnd, buffer, length + 1);
  std::string title(buffer);

  RECT rect;
  int nCloaked;
  DwmGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, &rect, sizeof(rect));
  DwmGetWindowAttribute(hWnd, DWMWA_CLOAKED, &nCloaked, sizeof(int));
  bool bCloaked = nCloaked != 0;

  TITLEBARINFO ti;
  ti.cbSize = sizeof(ti);
  GetTitleBarInfo(hWnd, &ti);
  bool bVisible = !(ti.rgstate[0] & STATE_SYSTEM_INVISIBLE);

  WINDOWINFO winInfo;
  GetWindowInfo(hWnd, &winInfo);

  MONITORINFOEX monInfo;
  monInfo.cbSize = sizeof(monInfo);
  HMONITOR hMon = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONULL);
  GetMonitorInfoA(hMon, &monInfo);

  // Get the relative position of the window
  int x1 = std::max(rect.left, monInfo.rcMonitor.left);
  int y1 = std::max(rect.top, monInfo.rcMonitor.top);
  int x2 = std::min(rect.right, monInfo.rcMonitor.right);
  int y2 = std::min(rect.bottom, monInfo.rcMonitor.bottom);

  int x = x1 - monInfo.rcMonitor.left;
  int y = y1 - monInfo.rcMonitor.top;
  int width = x2 - x1;
  int height = y2 - y1;

  if (IsWindowVisible(hWnd) && bVisible && !bCloaked && length != 0 && hMon) {
    std::cout << monInfo.szDevice << std::endl;
    std::cout << title << ',' << std::endl;
    std::cout << x << "," << y << "," << width << "," << height << std::endl;
  }

  delete buffer;
  return true;
}

int main() {
  EnumWindows(enumWindowCallback, NULL);
  return 0;
}

And to get the programme work correctly on high-DPI PCs, I need to apply dpiAware and dpiAwareness application manifests as below with mt:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
  <asmv3:application>
    <asmv3:windowsSettings>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
    </asmv3:windowsSettings>
  </asmv3:application>
</assembly>

Finally I created a bat script to compile and mt my programme:

call cl^
  /EHsc^
    sources\win32\visible-windows.cpp^
    User32.lib^
    Dwmapi.lib^
  /Fe: bin\win32\visible-windows.exe

call mt^
  -manifest sources\win32\visible-windows.manifest^
  -outputresource:bin\win32\visible-windows.exe

Powered by Gatsby. Theme inspired by end2end.

© 2014-2022. Made withby mdluo.