/ tools / StylesReportTool / StylesReportTool.cpp
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  }