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 }