/ src / modules / Workspaces / WorkspacesSnapshotTool / SnapshotUtils.cpp
SnapshotUtils.cpp
  1  #include "pch.h"
  2  #include "SnapshotUtils.h"
  3  
  4  #include <common/utils/elevation.h>
  5  #include <common/utils/process_path.h>
  6  #include <common/utils/resources.h>
  7  #include <common/notifications/NotificationUtil.h>
  8  
  9  #include <workspaces-common/WindowEnumerator.h>
 10  #include <workspaces-common/WindowFilter.h>
 11  
 12  #include <WorkspacesLib/AppUtils.h>
 13  #include <WorkspacesLib/PwaHelper.h>
 14  #include <WorkspacesLib/WindowUtils.h>
 15  #include <WindowProperties/WorkspacesWindowPropertyUtils.h>
 16  
 17  #include "Generated Files/resource.h"
 18  
 19  #pragma comment(lib, "ntdll.lib")
 20  
 21  namespace SnapshotUtils
 22  {
 23      namespace NonLocalizable
 24      {
 25          const std::wstring ApplicationFrameHost = L"ApplicationFrameHost.exe";
 26      }
 27  
 28      bool IsProcessElevated(DWORD processID)
 29      {
 30          wil::unique_handle hProcess{ OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, processID) };
 31          wil::unique_handle token;
 32  
 33          if (OpenProcessToken(hProcess.get(), TOKEN_QUERY, &token))
 34          {
 35              TOKEN_ELEVATION elevation;
 36              DWORD size;
 37              if (GetTokenInformation(token.get(), TokenElevation, &elevation, sizeof(elevation), &size))
 38              {
 39                  return elevation.TokenIsElevated != 0;
 40              }
 41          }
 42  
 43          return false;
 44      }
 45  
 46      std::vector<WorkspacesData::WorkspacesProject::Application> GetApps(bool isGuidNeeded, const std::function<unsigned int(HWND)> getMonitorNumberFromWindowHandle, const std::function<WorkspacesData::WorkspacesProject::Monitor::MonitorRect(unsigned int)> getMonitorRect)
 47      {
 48          Utils::PwaHelper pwaHelper{};
 49          std::vector<WorkspacesData::WorkspacesProject::Application> apps{};
 50  
 51          auto installedApps = Utils::Apps::GetAppsList();
 52          auto windows = WindowEnumerator::Enumerate(WindowFilter::Filter);
 53  
 54          for (const auto window : windows)
 55          {
 56              if (WindowFilter::FilterPopup(window))
 57              {
 58                  continue;
 59              }
 60  
 61              // filter by window rect size
 62              RECT rect = WindowUtils::GetWindowRect(window);
 63              if (rect.right - rect.left <= 0 || rect.bottom - rect.top <= 0)
 64              {
 65                  continue;
 66              }
 67  
 68              // filter by window title
 69              std::wstring title = WindowUtils::GetWindowTitle(window);
 70              if (title.empty())
 71              {
 72                  continue;
 73              }
 74  
 75              Logger::info("Try to get window app:{}", reinterpret_cast<void*>(window));
 76  
 77              DWORD pid{};
 78              GetWindowThreadProcessId(window, &pid);
 79  
 80              // filter by app path
 81              std::wstring processPath = get_process_path(window);
 82              if (processPath.empty())
 83              {
 84                  // When PT runs not as admin, it can't get the process path of the window of the elevated process.
 85                  // Notify the user that running as admin is required to process elevated windows.
 86                  if (!is_process_elevated() && IsProcessElevated(pid))
 87                  {
 88                      auto notificationUtil = std::make_unique<notifications::NotificationUtil>();
 89  
 90                      notificationUtil->WarnIfElevationIsRequired(GET_RESOURCE_STRING(IDS_PROJECTS),
 91                                                                  GET_RESOURCE_STRING(IDS_SYSTEM_FOREGROUND_ELEVATED),
 92                                                                  GET_RESOURCE_STRING(IDS_SYSTEM_FOREGROUND_ELEVATED_LEARN_MORE),
 93                                                                  GET_RESOURCE_STRING(IDS_SYSTEM_FOREGROUND_ELEVATED_DIALOG_DONT_SHOW_AGAIN));
 94                  }
 95  
 96                  continue;
 97              }
 98  
 99              if (WindowUtils::IsExcludedByDefault(window, processPath))
100              {
101                  continue;
102              }
103  
104              // fix for the packaged apps that are not caught when minimized, e.g. Settings, Microsoft ToDo, ...
105              if (processPath.ends_with(NonLocalizable::ApplicationFrameHost))
106              {
107                  for (auto otherWindow : windows)
108                  {
109                      DWORD otherPid{};
110                      GetWindowThreadProcessId(otherWindow, &otherPid);
111  
112                      // searching for the window with the same title but different PID
113                      if (pid != otherPid && title == WindowUtils::GetWindowTitle(otherWindow))
114                      {
115                          processPath = get_process_path(otherPid);
116                          break;
117                      }
118                  }
119              }
120  
121              auto data = Utils::Apps::GetApp(processPath, pid, installedApps);
122              if (!data.has_value() || data->name.empty())
123              {
124                  Logger::info(L"Installed app not found:{},{}", reinterpret_cast<void*>(window), processPath);
125                  continue;
126              }
127  
128              if (!data->IsSteamGame() && !WindowUtils::HasThickFrame(window))
129              {
130                  // Only care about steam games if it has no thick frame to remain consistent with
131                  // the behavior as before.
132                  continue;
133              }
134  
135              Logger::info(L"Found app for window:{},{}", reinterpret_cast<void*>(window), processPath);
136  
137              auto appData = data.value();
138  
139              bool isEdge = appData.IsEdge();
140              bool isChrome = appData.IsChrome();
141              if (isEdge || isChrome)
142              {
143                  auto windowAumid = Utils::GetAUMIDFromWindow(window);
144                  std::optional<std::wstring> pwaAppId{};
145  
146                  if (isEdge)
147                  {
148                      pwaAppId = pwaHelper.GetEdgeAppId(windowAumid);
149                  }
150                  else if (isChrome)
151                  {
152                      pwaAppId = pwaHelper.GetChromeAppId(windowAumid);
153                  }
154  
155                  if (pwaAppId.has_value())
156                  {
157                      auto pwaName = pwaHelper.SearchPwaName(pwaAppId.value(), windowAumid);
158                      Logger::info(L"Found {} PWA app with name {}, appId: {}", (isEdge ? L"Edge" : (isChrome ? L"Chrome" : L"unknown")), pwaName, pwaAppId.value());
159  
160                      appData.pwaAppId = pwaAppId.value();
161                      appData.name = pwaName + L" (" + appData.name + L")";
162                      // If it's pwa app, appUserModelId should be their own pwa id.
163                      appData.appUserModelId = windowAumid;
164                  }
165              }
166  
167              bool isMinimized = WindowUtils::IsMinimized(window);
168              unsigned int monitorNumber = getMonitorNumberFromWindowHandle(window);
169  
170              if (isMinimized)
171              {
172                  // set the screen area as position, the values we get for the minimized windows are out of the screens' area
173                  WorkspacesData::WorkspacesProject::Monitor::MonitorRect monitorRect = getMonitorRect(monitorNumber);
174                  rect.left = monitorRect.left;
175                  rect.top = monitorRect.top;
176                  rect.right = monitorRect.left + monitorRect.width;
177                  rect.bottom = monitorRect.top + monitorRect.height;
178              }
179  
180              std::wstring guid = isGuidNeeded ? WorkspacesWindowProperties::GetGuidFromHwnd(window) : L"";
181  
182              WorkspacesData::WorkspacesProject::Application app{
183                  .id = guid,
184                  .name = appData.name,
185                  .title = title,
186                  .path = appData.installPath,
187                  .packageFullName = appData.packageFullName,
188                  .appUserModelId = appData.appUserModelId,
189                  .pwaAppId = appData.pwaAppId,
190                  .commandLineArgs = L"",
191                  .version = L"1",
192                  .isElevated = IsProcessElevated(pid),
193                  .canLaunchElevated = appData.canLaunchElevated,
194                  .isMinimized = isMinimized,
195                  .isMaximized = WindowUtils::IsMaximized(window),
196                  .position = WorkspacesData::WorkspacesProject::Application::Position{
197                      .x = rect.left,
198                      .y = rect.top,
199                      .width = rect.right - rect.left,
200                      .height = rect.bottom - rect.top,
201                  },
202                  .monitor = monitorNumber,
203              };
204  
205              apps.push_back(app);
206          }
207  
208          return apps;
209      }
210  }