StylesReportTool.cpp
1 #include "pch.h" 2 #include "StylesReportTool.h" 3 4 #include <dwmapi.h> 5 #include <shlobj.h> 6 7 #include <filesystem> 8 #include <fstream> 9 #include <map> 10 #include <format> 11 12 inline std::optional<std::wstring> get_last_error_message(const DWORD dw) 13 { 14 std::optional<std::wstring> message; 15 try 16 { 17 const auto msg = std::system_category().message(dw); 18 message.emplace(begin(msg), end(msg)); 19 } 20 catch (...) 21 { 22 } 23 return message; 24 } 25 26 inline std::wstring get_last_error_or_default(const DWORD dw) 27 { 28 auto message = get_last_error_message(dw); 29 return message.has_value() ? message.value() : L""; 30 } 31 32 std::filesystem::path get_desktop_path() 33 { 34 wchar_t* p; 35 if (S_OK != SHGetKnownFolderPath(FOLDERID_Desktop, 0, NULL, &p)) return ""; 36 37 std::filesystem::path result = p; 38 CoTaskMemFree(p); 39 40 return result; 41 } 42 43 // Get the executable path or module name for modern apps 44 inline std::wstring get_process_path(DWORD pid) noexcept 45 { 46 auto process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, TRUE, pid); 47 std::wstring name; 48 if (process != INVALID_HANDLE_VALUE) 49 { 50 name.resize(MAX_PATH); 51 DWORD name_length = static_cast<DWORD>(name.length()); 52 if (QueryFullProcessImageNameW(process, 0, static_cast<LPWSTR>(name.data()), &name_length) == 0) 53 { 54 name_length = 0; 55 } 56 name.resize(name_length); 57 CloseHandle(process); 58 } 59 return name; 60 } 61 62 // Get the executable path or module name for modern apps 63 inline std::wstring get_process_path(HWND window) noexcept 64 { 65 const static std::wstring app_frame_host = L"ApplicationFrameHost.exe"; 66 67 DWORD pid{}; 68 GetWindowThreadProcessId(window, &pid); 69 auto name = get_process_path(pid); 70 71 if (name.length() >= app_frame_host.length() && 72 name.compare(name.length() - app_frame_host.length(), app_frame_host.length(), app_frame_host) == 0) 73 { 74 // It is a UWP app. We will enumerate the windows and look for one created 75 // by something with a different PID 76 DWORD new_pid = pid; 77 78 EnumChildWindows( 79 window, [](HWND hwnd, LPARAM param) -> BOOL { 80 auto new_pid_ptr = reinterpret_cast<DWORD*>(param); 81 DWORD pid; 82 GetWindowThreadProcessId(hwnd, &pid); 83 if (pid != *new_pid_ptr) 84 { 85 *new_pid_ptr = pid; 86 return FALSE; 87 } 88 else 89 { 90 return TRUE; 91 } 92 }, 93 reinterpret_cast<LPARAM>(&new_pid)); 94 95 // If we have a new pid, get the new name. 96 if (new_pid != pid) 97 { 98 return get_process_path(new_pid); 99 } 100 } 101 102 return name; 103 } 104 105 class Logger 106 { 107 private: 108 inline static std::wofstream logger; 109 110 public: 111 ~Logger() 112 { 113 logger.close(); 114 } 115 116 static void init(std::string loggerName) 117 { 118 std::filesystem::path rootFolder(get_desktop_path()); 119 120 auto logsPath = rootFolder; 121 logsPath.append(L"window_styles.txt"); 122 123 logger.open(logsPath.string(), std::ios_base::out | std::ios_base::app); 124 } 125 126 template<typename FormatString, typename... Args> 127 static void log(FormatString fmt, Args&&... args) 128 { 129 logger << std::vformat(fmt, std::make_wformat_args(args...)) << std::endl; 130 } 131 }; 132 133 std::map<DWMWINDOWATTRIBUTE, std::wstring> dwmAttributesReadable = { 134 {DWMWINDOWATTRIBUTE::DWMWA_NCRENDERING_ENABLED, L"DWMWA_NCRENDERING_ENABLED"}, 135 {DWMWINDOWATTRIBUTE::DWMWA_CAPTION_BUTTON_BOUNDS, L"DWMWA_CAPTION_BUTTON_BOUNDS"}, 136 {DWMWINDOWATTRIBUTE::DWMWA_EXTENDED_FRAME_BOUNDS, L"DWMWA_EXTENDED_FRAME_BOUNDS"}, 137 {DWMWINDOWATTRIBUTE::DWMWA_CLOAKED, L"DWMWA_CLOAKED"}, 138 }; 139 140 template <typename T> 141 void LogDwmInfo(HWND window, DWMWINDOWATTRIBUTE attr, T& value) 142 { 143 if (DwmGetWindowAttribute(window, attr, &value, sizeof(value)) == S_OK) 144 { 145 Logger::log(L"{}: {} ", dwmAttributesReadable[attr], value); 146 } 147 else 148 { 149 Logger::log(L"Failed to get {}", dwmAttributesReadable[attr]); 150 } 151 } 152 153 void LogDwmRect(HWND window, DWMWINDOWATTRIBUTE attr, RECT& value) 154 { 155 if (DwmGetWindowAttribute(window, attr, &value, sizeof(value)) >= 0) 156 { 157 Logger::log(L"{}: LT({},{}), RB({},{}), [{} x {}] ", dwmAttributesReadable[attr], value.left, value.top, value.right, value.bottom, value.right - value.left, value.bottom - value.top); 158 } 159 else 160 { 161 Logger::log(L"Failed to get {}", dwmAttributesReadable[attr]); 162 } 163 } 164 165 void LogStyles(HWND window) 166 { 167 auto style = GetWindowLong(window, GWL_STYLE); 168 169 Logger::log(L"------------------ Style --------------------- "); 170 Logger::log(L""); 171 172 Logger::log(L"WS_BORDER {}", ((style & WS_BORDER) == WS_BORDER)); 173 Logger::log(L"WS_CAPTION {}", ((style & WS_CAPTION) == WS_CAPTION)); 174 Logger::log(L"WS_CHILD {}", ((style & WS_CHILD) == WS_CHILD)); 175 Logger::log(L"WS_CHILDWINDOW {}", ((style & WS_CHILDWINDOW) == WS_CHILDWINDOW)); 176 Logger::log(L"WS_CLIPCHILDREN {}", ((style & WS_CLIPCHILDREN) == WS_CLIPCHILDREN)); 177 Logger::log(L"WS_CLIPSIBLINGS {}", ((style & WS_CLIPSIBLINGS) == WS_CLIPSIBLINGS)); 178 Logger::log(L"WS_DISABLED {}", ((style & WS_DISABLED) == WS_DISABLED)); 179 Logger::log(L"WS_DLGFRAME {}", ((style & WS_DLGFRAME) == WS_DLGFRAME)); 180 Logger::log(L"WS_GROUP {}", ((style & WS_GROUP) == WS_GROUP)); 181 Logger::log(L"WS_HSCROLL {}", ((style & WS_HSCROLL) == WS_HSCROLL)); 182 Logger::log(L"WS_ICONIC {}", ((style & WS_ICONIC) == WS_ICONIC)); 183 Logger::log(L"WS_MAXIMIZE {}", ((style & WS_MAXIMIZE) == WS_MAXIMIZE)); 184 Logger::log(L"WS_MAXIMIZEBOX {}", ((style & WS_MAXIMIZEBOX) == WS_MAXIMIZEBOX)); 185 Logger::log(L"WS_MINIMIZE {}", ((style & WS_MINIMIZE) == WS_MINIMIZE)); 186 Logger::log(L"WS_MINIMIZEBOX {}", ((style & WS_MINIMIZEBOX) == WS_MINIMIZEBOX)); 187 Logger::log(L"WS_OVERLAPPED {}", ((style & WS_OVERLAPPED) == WS_OVERLAPPED)); 188 Logger::log(L"WS_OVERLAPPEDWINDOW {}", ((style & WS_OVERLAPPEDWINDOW) == WS_OVERLAPPEDWINDOW)); 189 Logger::log(L"WS_POPUP {}", ((style & WS_POPUP) == WS_POPUP)); 190 Logger::log(L"WS_POPUPWINDOW {}", ((style & WS_POPUPWINDOW) == WS_POPUPWINDOW)); 191 Logger::log(L"WS_SIZEBOX {}", ((style & WS_SIZEBOX) == WS_SIZEBOX)); 192 Logger::log(L"WS_SYSMENU {}", ((style & WS_SYSMENU) == WS_SYSMENU)); 193 Logger::log(L"WS_TABSTOP {}", ((style & WS_TABSTOP) == WS_TABSTOP)); 194 Logger::log(L"WS_THICKFRAME {}", ((style & WS_THICKFRAME) == WS_THICKFRAME)); 195 Logger::log(L"WS_TILED {}", ((style & WS_TILED) == WS_TILED)); 196 Logger::log(L"WS_TILEDWINDOW {}", ((style & WS_TILEDWINDOW) == WS_TILEDWINDOW)); 197 Logger::log(L"WS_VISIBLE {}", ((style & WS_VISIBLE) == WS_VISIBLE)); 198 Logger::log(L"WS_VSCROLL {}", ((style & WS_VSCROLL) == WS_VSCROLL)); 199 200 Logger::log(L""); 201 } 202 203 void LogExStyles(HWND window) 204 { 205 auto exStyle = GetWindowLong(window, GWL_EXSTYLE); 206 Logger::log(L"------------------ Exstyle --------------------- "); 207 Logger::log(L""); 208 209 Logger::log(L"WS_EX_ACCEPTFILES {}", (exStyle & WS_EX_ACCEPTFILES) == WS_EX_ACCEPTFILES); 210 Logger::log(L"WS_EX_APPWINDOW {}", (exStyle & WS_EX_APPWINDOW) == WS_EX_APPWINDOW); 211 Logger::log(L"WS_EX_CLIENTEDGE {}", (exStyle & WS_EX_CLIENTEDGE) == WS_EX_CLIENTEDGE); 212 Logger::log(L"WS_EX_COMPOSITED {}", (exStyle & WS_EX_COMPOSITED) == WS_EX_COMPOSITED); 213 Logger::log(L"WS_EX_CONTEXTHELP {}", (exStyle & WS_EX_CONTEXTHELP) == WS_EX_CONTEXTHELP); 214 Logger::log(L"WS_EX_CONTROLPARENT {}", (exStyle & WS_EX_CONTROLPARENT) == WS_EX_CONTROLPARENT); 215 Logger::log(L"WS_EX_DLGMODALFRAME {}", (exStyle & WS_EX_DLGMODALFRAME) == WS_EX_DLGMODALFRAME); 216 Logger::log(L"WS_EX_LAYERED {}", (exStyle & WS_EX_LAYERED) == WS_EX_LAYERED); 217 Logger::log(L"WS_EX_LAYOUTRTL {}", (exStyle & WS_EX_LAYOUTRTL) == WS_EX_LAYOUTRTL); 218 Logger::log(L"WS_EX_LEFT {}", (exStyle & WS_EX_LEFT) == WS_EX_LEFT); 219 Logger::log(L"WS_EX_LEFTSCROLLBAR {}", (exStyle & WS_EX_LEFTSCROLLBAR) == WS_EX_LEFTSCROLLBAR); 220 Logger::log(L"WS_EX_LTRREADING {}", (exStyle & WS_EX_LTRREADING) == WS_EX_LTRREADING); 221 Logger::log(L"WS_EX_MDICHILD {}", (exStyle & WS_EX_MDICHILD) == WS_EX_MDICHILD); 222 Logger::log(L"WS_EX_NOACTIVATE {}", (exStyle & WS_EX_NOACTIVATE) == WS_EX_NOACTIVATE); 223 Logger::log(L"WS_EX_NOINHERITLAYOUT {}", (exStyle & WS_EX_NOINHERITLAYOUT) == WS_EX_NOINHERITLAYOUT); 224 Logger::log(L"WS_EX_NOPARENTNOTIFY {}", (exStyle & WS_EX_NOPARENTNOTIFY) == WS_EX_NOPARENTNOTIFY); 225 Logger::log(L"WS_EX_NOREDIRECTIONBITMAP {}", (exStyle & WS_EX_NOREDIRECTIONBITMAP) == WS_EX_NOREDIRECTIONBITMAP); 226 Logger::log(L"WS_EX_OVERLAPPEDWINDOW {}", (exStyle & WS_EX_OVERLAPPEDWINDOW) == WS_EX_OVERLAPPEDWINDOW); 227 Logger::log(L"WS_EX_PALETTEWINDOW {}", (exStyle & WS_EX_PALETTEWINDOW) == WS_EX_PALETTEWINDOW); 228 Logger::log(L"WS_EX_RIGHT {}", (exStyle & WS_EX_RIGHT) == WS_EX_RIGHT); 229 Logger::log(L"WS_EX_RIGHTSCROLLBAR {}", (exStyle & WS_EX_RIGHTSCROLLBAR) == WS_EX_RIGHTSCROLLBAR); 230 Logger::log(L"WS_EX_RTLREADING {}", (exStyle & WS_EX_RTLREADING) == WS_EX_RTLREADING); 231 Logger::log(L"WS_EX_STATICEDGE {}", (exStyle & WS_EX_STATICEDGE) == WS_EX_STATICEDGE); 232 Logger::log(L"WS_EX_TOOLWINDOW {}", (exStyle & WS_EX_TOOLWINDOW) == WS_EX_TOOLWINDOW); 233 Logger::log(L"WS_EX_TOPMOST {}", (exStyle & WS_EX_TOPMOST) == WS_EX_TOPMOST); 234 Logger::log(L"WS_EX_TRANSPARENT {}", (exStyle & WS_EX_TRANSPARENT) == WS_EX_TRANSPARENT); 235 Logger::log(L"WS_EX_WINDOWEDGE {}", (exStyle & WS_EX_WINDOWEDGE) == WS_EX_WINDOWEDGE); 236 237 Logger::log(L""); 238 } 239 240 void LogDwmAttributes(HWND window) 241 { 242 Logger::log(L"------------------ DwmAttributes --------------------- "); 243 Logger::log(L""); 244 245 int intValue{}; 246 unsigned int uintValue{}; 247 248 LogDwmInfo(window, DWMWINDOWATTRIBUTE::DWMWA_NCRENDERING_ENABLED, intValue); 249 LogDwmInfo(window, DWMWINDOWATTRIBUTE::DWMWA_CLOAKED, intValue); 250 251 RECT rectValue{}; 252 LogDwmRect(window, DWMWINDOWATTRIBUTE::DWMWA_CAPTION_BUTTON_BOUNDS, rectValue); 253 LogDwmRect(window, DWMWINDOWATTRIBUTE::DWMWA_EXTENDED_FRAME_BOUNDS, rectValue); 254 255 Logger::log(L""); 256 } 257 258 void LogVirtualDesktopInfo(HWND window) 259 { 260 Logger::log(L"------------------ VirtualDesktop info --------------------- "); 261 Logger::log(L""); 262 263 IVirtualDesktopManager* vdManager = nullptr; 264 auto res = CoCreateInstance(CLSID_VirtualDesktopManager, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&vdManager)); 265 if (FAILED(res)) 266 { 267 Logger::log(L"Failed to create VirtualDesktopManager instance"); 268 Logger::log(L""); 269 return; 270 } 271 272 BOOL isWindowOnCurrentDesktop = false; 273 if (vdManager->IsWindowOnCurrentVirtualDesktop(window, &isWindowOnCurrentDesktop) == S_OK) 274 { 275 Logger::log(L"Window is on current virtual desktop: {}", isWindowOnCurrentDesktop); 276 } 277 278 GUID id{}; 279 auto vdIdRes = vdManager->GetWindowDesktopId(window, &id); 280 if (vdIdRes == S_OK) 281 { 282 OLECHAR* guidString; 283 if (StringFromCLSID(id, &guidString) == S_OK) 284 { 285 Logger::log(L"Virtual desktop id: {}", guidString); 286 } 287 288 CoTaskMemFree(guidString); 289 } 290 else 291 { 292 Logger::log(L"GetWindowDesktopId error: {}", get_last_error_or_default(vdIdRes)); 293 } 294 295 if (vdManager) 296 { 297 vdManager->Release(); 298 } 299 300 Logger::log(L""); 301 } 302 303 void LogInfo(HWND window) 304 { 305 auto processPath = get_process_path(window); 306 auto app = processPath; 307 auto pos = processPath.find_last_of('\\'); 308 if (pos != std::string::npos && pos + 1 < processPath.length()) 309 { 310 app = processPath.substr(pos + 1); 311 } 312 313 Logger::log(L"Timestamp: {}", std::chrono::system_clock::now()); 314 Logger::log(L"Window: {}", app); 315 316 WCHAR className[256]; 317 auto classNameLength = GetClassName(window, className, sizeof(className)); 318 if (classNameLength > 0) 319 { 320 Logger::log(L"Class: {}", className); 321 } 322 else 323 { 324 Logger::log(L"GetClassName error: {}", get_last_error_or_default(GetLastError())); 325 } 326 327 Logger::log(L""); 328 329 LogStyles(window); 330 LogExStyles(window); 331 LogDwmAttributes(window); 332 LogVirtualDesktopInfo(window); 333 334 Logger::log(L"======================================="); 335 Logger::log(L""); 336 } 337 338 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); 339 340 int APIENTRY wWinMain(_In_ HINSTANCE hInstance, 341 _In_opt_ HINSTANCE hPrevInstance, 342 _In_ LPWSTR lpCmdLine, 343 _In_ int nCmdShow) 344 { 345 UNREFERENCED_PARAMETER(hPrevInstance); 346 UNREFERENCED_PARAMETER(lpCmdLine); 347 348 Logger::init("StylesReportTool"); 349 350 WNDCLASSEXW wcex; 351 wcex.cbSize = sizeof(WNDCLASSEX); 352 wcex.style = {}; 353 wcex.lpfnWndProc = WndProc; 354 wcex.cbClsExtra = 0; 355 wcex.cbWndExtra = 0; 356 wcex.hInstance = hInstance; 357 wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSTYLESICON)); 358 wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); 359 wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); 360 wcex.lpszMenuName = L""; 361 wcex.lpszClassName = L"StylesReportTool"; 362 wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALLICON)); 363 364 if (!RegisterClassExW(&wcex)) 365 { 366 Logger::log(L"Register class error: {}", get_last_error_or_default(GetLastError())); 367 return FALSE; 368 } 369 370 HWND hWnd = CreateWindowW(L"StylesReportTool", L"Window Style Report Tool", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 600, 200, nullptr, nullptr, hInstance, nullptr); 371 if (!hWnd) 372 { 373 Logger::log(L"Window creation error: {}", get_last_error_or_default(GetLastError())); 374 return FALSE; 375 } 376 377 if (!RegisterHotKey(hWnd, 1, MOD_ALT | MOD_CONTROL | MOD_NOREPEAT, 0x53)) // ctrl + alt + s 378 { 379 Logger::log(L"Failed to register hotkey: {}", get_last_error_or_default(GetLastError())); 380 return FALSE; 381 } 382 383 ShowWindow(hWnd, nCmdShow); 384 UpdateWindow(hWnd); 385 386 MSG msg{}; 387 while (GetMessage(&msg, nullptr, 0, 0)) 388 { 389 TranslateMessage(&msg); 390 DispatchMessage(&msg); 391 } 392 393 return (int) msg.wParam; 394 } 395 396 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 397 { 398 switch (message) 399 { 400 case WM_HOTKEY: 401 { 402 LogInfo(GetForegroundWindow()); 403 PostQuitMessage(0); 404 } 405 break; 406 case WM_PAINT: 407 { 408 PAINTSTRUCT ps; 409 HDC hdc = BeginPaint(hWnd, &ps); 410 411 LPCWSTR text = L"Please select the target window (using a mouse or Alt+Tab), \r\nand press Ctrl+Alt+S to capture its styles. \r\nYou can find the output file \"window_styles.txt\" on your desktop."; 412 RECT rc{0,50,600,200}; 413 DrawText(hdc, text, static_cast<int>(wcslen(text)), &rc, DT_CENTER | DT_WORDBREAK); 414 415 EndPaint(hWnd, &ps); 416 } 417 break; 418 case WM_DESTROY: 419 PostQuitMessage(0); 420 break; 421 default: 422 return DefWindowProc(hWnd, message, wParam, lParam); 423 } 424 return 0; 425 }