/ src / modules / Workspaces / WorkspacesLib / PwaHelper.cpp
PwaHelper.cpp
  1  #include "pch.h"
  2  #include "PwaHelper.h"
  3  #include "WindowUtils.h"
  4  
  5  #include <filesystem>
  6  
  7  #include <appmodel.h>
  8  #include <shellapi.h>
  9  #include <ShlObj.h>
 10  #include <shobjidl.h>
 11  #include <tlhelp32.h>
 12  #include <wrl.h>
 13  #include <propkey.h>
 14  
 15  #include <wil/com.h>
 16  
 17  #include <common/logger/logger.h>
 18  #include <common/utils/winapi_error.h>
 19  
 20  #include <WorkspacesLib/AppUtils.h>
 21  #include <WorkspacesLib/CommandLineArgsHelper.h>
 22  #include <WorkspacesLib/StringUtils.h>
 23  
 24  namespace Utils
 25  {
 26      namespace NonLocalizable
 27      {
 28          const std::wstring EdgeAppIdIdentifier = L"--app-id=";
 29          const std::wstring ChromeAppIdIdentifier = L"Chrome._crx_";
 30          const std::wstring ChromeBase = L"Google\\Chrome\\User Data\\Default\\Web Applications";
 31          const std::wstring EdgeBase = L"Microsoft\\Edge\\User Data\\Default\\Web Applications";
 32          const std::wstring ChromeDirPrefix = L"_crx_";
 33          const std::wstring EdgeDirPrefix = L"_crx__";
 34          const std::wstring IcoExtension = L".ico";
 35      }
 36  
 37      static const std::wstring& GetLocalAppDataFolder()
 38      {
 39          static std::wstring localFolder{};
 40  
 41          if (localFolder.empty())
 42          {
 43              wil::unique_cotaskmem_string folderPath;
 44              HRESULT hres = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &folderPath);
 45              if (SUCCEEDED(hres))
 46              {
 47                  localFolder = folderPath.get();
 48              }
 49              else
 50              {
 51                  Logger::error(L"Failed to get the local app data folder path: {}", get_last_error_or_default(hres));
 52                  localFolder = L""; // Ensure it is explicitly set to empty on failure
 53              }
 54          }
 55  
 56          return localFolder;
 57      }
 58  
 59      // Finds all PwaHelper.exe processes with the specified parent process ID
 60      std::vector<DWORD> FindPwaHelperProcessIds()
 61      {
 62          std::vector<DWORD> pwaHelperProcessIds;
 63          const HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
 64          if (hSnapshot == INVALID_HANDLE_VALUE)
 65          {
 66              Logger::info(L"Invalid handle when creating snapshot for the search for PwaHelper processes");
 67              return pwaHelperProcessIds;
 68          }
 69  
 70          PROCESSENTRY32 pe;
 71          pe.dwSize = sizeof(PROCESSENTRY32);
 72  
 73          if (Process32First(hSnapshot, &pe))
 74          {
 75              do
 76              {
 77                  if (_wcsicmp(pe.szExeFile, L"PwaHelper.exe") == 0)
 78                  {
 79                      Logger::info(L"Found a PWA process with id {}", pe.th32ProcessID);
 80                      pwaHelperProcessIds.push_back(pe.th32ProcessID);
 81                  }
 82              } while (Process32Next(hSnapshot, &pe));
 83          }
 84  
 85          CloseHandle(hSnapshot);
 86          return pwaHelperProcessIds;
 87      }
 88  
 89      PwaHelper::PwaHelper()
 90      {
 91          InitChromeAppIds();
 92          InitEdgeAppIds();
 93      }
 94  
 95      void PwaHelper::InitAppIds(const std::wstring& browserDataFolder, const std::wstring& browserDirPrefix, const std::function<void(const std::wstring&)>& addingAppIdCallback)
 96      {
 97          std::filesystem::path folderPath(GetLocalAppDataFolder());
 98          folderPath.append(browserDataFolder);
 99          if (!std::filesystem::exists(folderPath))
100          {
101              Logger::info(L"Edge base path does not exist: {}", folderPath.wstring());
102              return;
103          }
104  
105          try
106          {
107              for (const auto& directory : std::filesystem::directory_iterator(folderPath))
108              {
109                  if (!directory.is_directory())
110                  {
111                      continue;
112                  }
113  
114                  const std::wstring directoryName = directory.path().filename();
115                  if (directoryName.find(browserDirPrefix) != 0)
116                  {
117                      continue;
118                  }
119  
120                  const std::wstring appId = directoryName.substr(browserDirPrefix.length());
121                  if (addingAppIdCallback)
122                  {
123                      addingAppIdCallback(appId);
124                  }
125  
126                  for (const auto& filename : std::filesystem::directory_iterator(directory))
127                  {
128                      if (!filename.is_directory())
129                      {
130                          const std::filesystem::path filenameString = filename.path().filename();
131                          if (StringUtils::CaseInsensitiveEquals(filenameString.extension(), NonLocalizable::IcoExtension))
132                          {
133                              const auto stem = filenameString.stem().wstring();
134                              m_pwaAppIdsToAppNames.insert({ appId, stem });
135                              Logger::info(L"Found an installed Pwa app {} with PwaAppId {}", stem, appId);
136                              break;
137                          }
138                      }
139                  }
140              }
141          }
142          catch (std::exception& ex)
143          {
144              Logger::error("Failed to iterate over the directory: {}", ex.what());
145          }
146      }
147  
148      void PwaHelper::InitEdgeAppIds()
149      {
150          if (!m_edgeAppIds.empty())
151          {
152              // already initialized
153              return;
154          }
155  
156          CommandLineArgsHelper commandLineArgsHelper{};
157  
158          const auto pwaHelperProcessIds = FindPwaHelperProcessIds();
159          Logger::info(L"Found {} edge Pwa helper processes", pwaHelperProcessIds.size());
160  
161          for (const auto subProcessID : pwaHelperProcessIds)
162          {
163              std::wstring aumidID = GetAUMIDFromProcessId(subProcessID);
164              std::wstring commandLineArg = commandLineArgsHelper.GetCommandLineArgs(subProcessID);
165              std::wstring appId = GetAppIdFromCommandLineArgs(commandLineArg);
166  
167              m_edgeAppIds.insert({ aumidID, appId });
168              Logger::info(L"Found an edge Pwa helper process with AumidID {} and PwaAppId {}", aumidID, appId);
169          }
170  
171          InitAppIds(NonLocalizable::EdgeBase, NonLocalizable::EdgeDirPrefix, [&](const std::wstring&) {});
172      }
173  
174      void PwaHelper::InitChromeAppIds()
175      {
176          if (!m_chromeAppIds.empty())
177          {
178              // already initialized
179              return;
180          }
181  
182          InitAppIds(NonLocalizable::ChromeBase, NonLocalizable::ChromeDirPrefix, [&](const std::wstring& appId) {
183              m_chromeAppIds.push_back(appId);
184          });
185      }
186  
187      std::optional<std::wstring> PwaHelper::GetEdgeAppId(const std::wstring& windowAumid) const
188      {
189          const auto pwaIndex = m_edgeAppIds.find(windowAumid);
190          if (pwaIndex != m_edgeAppIds.end())
191          {
192              return pwaIndex->second;
193          }
194  
195          return std::nullopt;
196      }
197  
198      std::optional<std::wstring> PwaHelper::GetChromeAppId(const std::wstring& windowAumid) const
199      {
200          const auto appIdIndexStart = windowAumid.find(NonLocalizable::ChromeAppIdIdentifier);
201          if (appIdIndexStart != std::wstring::npos)
202          {
203              std::wstring windowAumidSub = windowAumid.substr(appIdIndexStart + NonLocalizable::ChromeAppIdIdentifier.size());
204              const auto appIdIndexEnd = windowAumidSub.find(L" ");
205              if (appIdIndexEnd != std::wstring::npos)
206              {
207                  windowAumidSub = windowAumidSub.substr(0, appIdIndexEnd);
208              }
209  
210              const std::wstring windowAumidBegin = windowAumidSub.substr(0, 10);
211              for (const auto chromeAppId : m_chromeAppIds)
212              {
213                  if (chromeAppId.find(windowAumidBegin) == 0)
214                  {
215                      return chromeAppId;
216                  }
217              }
218          }
219  
220          return std::nullopt;
221      }
222  
223      std::wstring PwaHelper::SearchPwaName(const std::wstring& pwaAppId, const std::wstring& windowAumid) const
224      {
225          const auto index = m_pwaAppIdsToAppNames.find(pwaAppId);
226          if (index != m_pwaAppIdsToAppNames.end())
227          {
228              return index->second;
229          }
230  
231          std::wstring nameFromAumid{ windowAumid };
232          const std::size_t delimiterPos = nameFromAumid.find(L"-");
233          if (delimiterPos != std::string::npos)
234          {
235              return nameFromAumid.substr(0, delimiterPos);
236          }
237  
238          return nameFromAumid;
239      }
240  
241      std::wstring PwaHelper::GetAppIdFromCommandLineArgs(const std::wstring& commandLineArgs) const
242      {
243          auto result = commandLineArgs;
244  
245          // remove the prefix
246          if (result.find(NonLocalizable::EdgeAppIdIdentifier) == 0)
247          {
248              result.erase(0, NonLocalizable::EdgeAppIdIdentifier.length());
249          }
250  
251          // remove the suffix
252          auto appIdIndexEnd = result.find(L" ");
253          if (appIdIndexEnd != std::wstring::npos)
254          {
255              result = result.substr(0, appIdIndexEnd);
256          }
257  
258          return result;
259      }
260  }