main.cpp
  1  #include <Windows.h>
  2  #include <iostream>
  3  #include <array>
  4  #include <vector>
  5  
  6  std::wstring get_process_path(DWORD pid) noexcept
  7  {
  8    auto process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, TRUE, pid);
  9    std::wstring name;
 10    if (process != INVALID_HANDLE_VALUE)
 11    {
 12      name.resize(MAX_PATH);
 13      DWORD name_length = static_cast<DWORD>(name.length());
 14      if (QueryFullProcessImageNameW(process, 0, static_cast<LPWSTR>(name.data()), &name_length) == 0)
 15      {
 16        name_length = 0;
 17      }
 18      name.resize(name_length);
 19      CloseHandle(process);
 20    }
 21    return name;
 22  }
 23  
 24  std::wstring get_process_path(HWND window) noexcept
 25  {
 26    const static std::wstring app_frame_host = L"ApplicationFrameHost.exe";
 27    DWORD pid{};
 28    GetWindowThreadProcessId(window, &pid);
 29    auto name = get_process_path(pid);
 30    if (name.length() >= app_frame_host.length() &&
 31      name.compare(name.length() - app_frame_host.length(), app_frame_host.length(), app_frame_host) == 0)
 32    {
 33      // It is a UWP app. We will enumerate the windows and look for one created
 34      // by something with a different PID
 35      DWORD new_pid = pid;
 36      EnumChildWindows(window, [](HWND hwnd, LPARAM param) -> BOOL {
 37        auto new_pid_ptr = reinterpret_cast<DWORD*>(param);
 38        DWORD pid;
 39        GetWindowThreadProcessId(hwnd, &pid);
 40        if (pid != *new_pid_ptr)
 41        {
 42          *new_pid_ptr = pid;
 43          return FALSE;
 44        }
 45        else
 46        {
 47          return TRUE;
 48        }
 49      }, reinterpret_cast<LPARAM>(&new_pid));
 50      // If we have a new pid, get the new name.
 51      if (new_pid != pid)
 52      {
 53        return get_process_path(new_pid);
 54      }
 55    }
 56    return name;
 57  }
 58  
 59  std::string window_styles(LONG style)
 60  {
 61    std::string result;
 62    if (style == 0)
 63      result = "WS_OVERLAPPED ";
 64  #define TEST_STYLE(x) if ((style & x) == x) result += #x " ";
 65    TEST_STYLE(WS_POPUP);
 66    TEST_STYLE(WS_CHILD);
 67    TEST_STYLE(WS_MINIMIZE);
 68    TEST_STYLE(WS_VISIBLE);
 69    TEST_STYLE(WS_DISABLED);
 70    TEST_STYLE(WS_CLIPSIBLINGS);
 71    TEST_STYLE(WS_CLIPCHILDREN);
 72    TEST_STYLE(WS_MAXIMIZE);
 73    TEST_STYLE(WS_CAPTION);
 74    TEST_STYLE(WS_BORDER);
 75    TEST_STYLE(WS_DLGFRAME);
 76    TEST_STYLE(WS_VSCROLL);
 77    TEST_STYLE(WS_HSCROLL);
 78    TEST_STYLE(WS_SYSMENU);
 79    TEST_STYLE(WS_THICKFRAME);
 80    TEST_STYLE(WS_GROUP);
 81    TEST_STYLE(WS_TABSTOP);
 82    TEST_STYLE(WS_MINIMIZEBOX);
 83    TEST_STYLE(WS_MAXIMIZEBOX);
 84    TEST_STYLE(WS_ICONIC);
 85    TEST_STYLE(WS_SIZEBOX);
 86    TEST_STYLE(WS_TILEDWINDOW);
 87    TEST_STYLE(WS_OVERLAPPEDWINDOW);
 88    TEST_STYLE(WS_POPUPWINDOW);
 89    TEST_STYLE(WS_CHILDWINDOW);
 90  #undef TEST_STYLE
 91    if (result.size() > 0)
 92      result.pop_back();
 93    return result;
 94  }
 95  
 96  std::string window_exstyles(LONG style)
 97  {
 98    std::string result;
 99  #define TEST_STYLE(x) if ((style & x) == x) result += #x " ";
100    TEST_STYLE(WS_EX_DLGMODALFRAME);
101    TEST_STYLE(WS_EX_NOPARENTNOTIFY);
102    TEST_STYLE(WS_EX_TOPMOST);
103    TEST_STYLE(WS_EX_ACCEPTFILES);
104    TEST_STYLE(WS_EX_TRANSPARENT);
105    TEST_STYLE(WS_EX_MDICHILD);
106    TEST_STYLE(WS_EX_TOOLWINDOW);
107    TEST_STYLE(WS_EX_WINDOWEDGE);
108    TEST_STYLE(WS_EX_CLIENTEDGE);
109    TEST_STYLE(WS_EX_CONTEXTHELP);
110    TEST_STYLE(WS_EX_RIGHT);
111    TEST_STYLE(WS_EX_LEFT);
112    TEST_STYLE(WS_EX_RTLREADING);
113    TEST_STYLE(WS_EX_LTRREADING);
114    TEST_STYLE(WS_EX_LEFTSCROLLBAR);
115    TEST_STYLE(WS_EX_RIGHTSCROLLBAR);
116    TEST_STYLE(WS_EX_CONTROLPARENT);
117    TEST_STYLE(WS_EX_STATICEDGE);
118    TEST_STYLE(WS_EX_APPWINDOW);
119    TEST_STYLE(WS_EX_OVERLAPPEDWINDOW);
120    TEST_STYLE(WS_EX_PALETTEWINDOW);
121    TEST_STYLE(WS_EX_LAYERED);
122    TEST_STYLE(WS_EX_NOINHERITLAYOUT);
123    TEST_STYLE(WS_EX_NOREDIRECTIONBITMAP);
124    TEST_STYLE(WS_EX_LAYOUTRTL);
125    TEST_STYLE(WS_EX_COMPOSITED);
126  #undef TEST_STYLE
127    if (result.size() > 0)
128      result.pop_back();
129    return result;
130  }
131  
132  
133  bool is_system_window(HWND hwnd, const char* class_name)
134  {
135    static auto system_classes = { "SysListView32", "WorkerW", "Shell_TrayWnd", "Shell_SecondaryTrayWnd", "Progman" };
136    static auto system_hwnds = { GetDesktopWindow(), GetShellWindow() };
137    for (auto system_hwnd : system_hwnds)
138    {
139      if (hwnd == system_hwnd)
140      {
141        return true;
142      }
143    }
144    for (const auto& system_class : system_classes)
145    {
146      if (strcmp(system_class, class_name) == 0)
147      {
148        return true;
149      }
150    }
151    return false;
152  }
153  
154  static bool no_visible_owner(HWND window) noexcept
155  {
156    auto owner = GetWindow(window, GW_OWNER);
157    if (owner == nullptr)
158    {
159      return true; // There is no owner at all
160    }
161    if (!IsWindowVisible(owner))
162    {
163      return true; // Owner is invisible
164    }
165    RECT rect;
166    if (!GetWindowRect(owner, &rect))
167    {
168      return false; // Could not get the rect, return true (and filter out the window) just in case
169    }
170    // Return false (and allow the window to be zonable) if the owner window size is zero
171    // It is enough that the window is zero-sized in one dimension only.
172    return rect.top == rect.bottom || rect.left == rect.right;
173  }
174  
175  #define TEST_IF(condition) std::cout<<"\t" << #condition <<": " <<((condition) ? "true, window not zonable" : "false")<<"\n"; if (condition) rv = false;
176  
177  bool test_window(HWND window)
178  {
179    std::cout << "\n";
180    std::cout << "HWND:         0x" << window << "\n";
181    DWORD pid;
182    GetWindowThreadProcessId(window, &pid);
183    std::cout << "PID:          0x" << std::hex << pid << "\n";
184    std::cout << "FOREGROUND:   0x" << GetForegroundWindow() << "\n";
185  
186    auto style = GetWindowLongPtr(window, GWL_STYLE);
187    auto exStyle = GetWindowLongPtr(window, GWL_EXSTYLE);
188    std::cout << "style:        0x" << std::hex << style << ": " << window_styles(static_cast<LONG>(style)) << "\n";
189    std::cout << "exStyle:      0x" << std::hex << exStyle << ": " << window_exstyles(static_cast<LONG>(exStyle)) << " \n";
190    std::array<char, 256> class_name;
191    GetClassNameA(window, class_name.data(), static_cast<int>(class_name.size()));
192    std::cout << "Window class: '" << class_name.data() << "' equals:\n";
193    auto process_path = get_process_path(window);
194    std::wcout<< L"Process path: " << process_path << L"\n";
195    bool rv = true;
196    std::cout << "Testing if the window is zonable:\n";
197    TEST_IF(GetAncestor(window, GA_ROOT) != window);
198    TEST_IF(!IsWindowVisible(window));
199    if ((style & WS_POPUP) == WS_POPUP &&
200        (style & WS_THICKFRAME) == 0 &&
201        (style & WS_MINIMIZEBOX) == 0 &&
202        (style & WS_MAXIMIZEBOX) == 0)
203    {
204      std::cout << "\t(style & WS_POPUP) && no frame nor max/min buttons: true, window not zonable\n";
205    }
206    else
207    {
208      std::cout << "\t(style & WS_POPUP) && no frame nor max/min buttons: false\n";
209    }
210    TEST_IF((style & WS_CHILD) == WS_CHILD);
211    TEST_IF((style & WS_DISABLED) == WS_DISABLED);
212    TEST_IF((exStyle & WS_EX_TOOLWINDOW) == WS_EX_TOOLWINDOW);
213    TEST_IF((exStyle & WS_EX_NOACTIVATE) == WS_EX_NOACTIVATE);
214    TEST_IF(is_system_window(window, class_name.data()));
215    if (strcmp(class_name.data(), "Windows.UI.Core.CoreWindow") == 0 &&
216        process_path.ends_with(L"SearchUI.exe"))
217    {
218      std::cout << "\tapp is Cortana: true, window not zonable\n";
219    }
220    else
221    {
222      std::cout << "\tapp is Cortana: false\n";
223    }
224    TEST_IF(!no_visible_owner(window));
225    return rv;
226  }
227  
228  LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam)
229  {
230    static HWND hwnd = nullptr;
231    if (nCode == HC_ACTION)
232    {
233      POINT point;
234      GetCursorPos(&point);
235      auto new_hwnd = WindowFromPoint(point);
236      if (hwnd != new_hwnd) {
237        hwnd = new_hwnd;
238        if (test_window(hwnd))
239        {
240          std::cout << "Window is zonable\n";
241        }
242        else
243        {
244          std::cout << "Window is NOT zonable\n";
245        }
246      }
247    }
248    return CallNextHookEx(NULL, nCode, wParam, lParam);
249  }
250  
251  int main()
252  {
253    HHOOK hhkLowLevelKybd = SetWindowsHookEx(WH_MOUSE_LL, LowLevelMouseProc, 0, 0);
254    MSG msg;
255    while (!GetMessage(&msg, NULL, NULL, NULL))
256    {
257      TranslateMessage(&msg);
258      DispatchMessage(&msg);
259    }
260    UnhookWindowsHookEx(hhkLowLevelKybd);
261    return(0);
262  }