mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-24 04:00:02 +01:00
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Workspace support for pwa is now limited, it is tight to specific Profile launch. If you create a pwa app with a profile other than "Default", launch will fail. Then you have to manually configure that profile to launch. This pr fix it by launching with shell:appsfolder\appusermodelId <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [X] **Closes:** #36384 - [ ] **Communication:** I've discussed this with core contributors already. If work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end user facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed - [X] Create a new workspace with a pwa app(Other than default profile) should be no problem. - [X] Existing workspace with a pwa(default profile and other profile) should launch successfully without problem 1. with pt version 91.1, create a loop pwa with "Profile 1" instead of "Default" in edge. 2. capture and launch actually launch the edge instead of loop 3. Create profile with this impl and launch 4. Launch pwa successfully --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
260 lines
8.4 KiB
C++
260 lines
8.4 KiB
C++
#include "pch.h"
|
|
#include "PwaHelper.h"
|
|
#include "WindowUtils.h"
|
|
|
|
#include <filesystem>
|
|
|
|
#include <appmodel.h>
|
|
#include <shellapi.h>
|
|
#include <ShlObj.h>
|
|
#include <shobjidl.h>
|
|
#include <tlhelp32.h>
|
|
#include <wrl.h>
|
|
#include <propkey.h>
|
|
|
|
#include <wil/com.h>
|
|
|
|
#include <common/logger/logger.h>
|
|
#include <common/utils/winapi_error.h>
|
|
|
|
#include <WorkspacesLib/AppUtils.h>
|
|
#include <WorkspacesLib/CommandLineArgsHelper.h>
|
|
#include <WorkspacesLib/StringUtils.h>
|
|
|
|
namespace Utils
|
|
{
|
|
namespace NonLocalizable
|
|
{
|
|
const std::wstring EdgeAppIdIdentifier = L"--app-id=";
|
|
const std::wstring ChromeAppIdIdentifier = L"Chrome._crx_";
|
|
const std::wstring ChromeBase = L"Google\\Chrome\\User Data\\Default\\Web Applications";
|
|
const std::wstring EdgeBase = L"Microsoft\\Edge\\User Data\\Default\\Web Applications";
|
|
const std::wstring ChromeDirPrefix = L"_crx_";
|
|
const std::wstring EdgeDirPrefix = L"_crx__";
|
|
const std::wstring IcoExtension = L".ico";
|
|
}
|
|
|
|
static const std::wstring& GetLocalAppDataFolder()
|
|
{
|
|
static std::wstring localFolder{};
|
|
|
|
if (localFolder.empty())
|
|
{
|
|
wil::unique_cotaskmem_string folderPath;
|
|
HRESULT hres = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &folderPath);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
localFolder = folderPath.get();
|
|
}
|
|
else
|
|
{
|
|
Logger::error(L"Failed to get the local app data folder path: {}", get_last_error_or_default(hres));
|
|
localFolder = L""; // Ensure it is explicitly set to empty on failure
|
|
}
|
|
}
|
|
|
|
return localFolder;
|
|
}
|
|
|
|
// Finds all PwaHelper.exe processes with the specified parent process ID
|
|
std::vector<DWORD> FindPwaHelperProcessIds()
|
|
{
|
|
std::vector<DWORD> pwaHelperProcessIds;
|
|
const HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
if (hSnapshot == INVALID_HANDLE_VALUE)
|
|
{
|
|
Logger::info(L"Invalid handle when creating snapshot for the search for PwaHelper processes");
|
|
return pwaHelperProcessIds;
|
|
}
|
|
|
|
PROCESSENTRY32 pe;
|
|
pe.dwSize = sizeof(PROCESSENTRY32);
|
|
|
|
if (Process32First(hSnapshot, &pe))
|
|
{
|
|
do
|
|
{
|
|
if (_wcsicmp(pe.szExeFile, L"PwaHelper.exe") == 0)
|
|
{
|
|
Logger::info(L"Found a PWA process with id {}", pe.th32ProcessID);
|
|
pwaHelperProcessIds.push_back(pe.th32ProcessID);
|
|
}
|
|
} while (Process32Next(hSnapshot, &pe));
|
|
}
|
|
|
|
CloseHandle(hSnapshot);
|
|
return pwaHelperProcessIds;
|
|
}
|
|
|
|
PwaHelper::PwaHelper()
|
|
{
|
|
InitChromeAppIds();
|
|
InitEdgeAppIds();
|
|
}
|
|
|
|
void PwaHelper::InitAppIds(const std::wstring& browserDataFolder, const std::wstring& browserDirPrefix, const std::function<void(const std::wstring&)>& addingAppIdCallback)
|
|
{
|
|
std::filesystem::path folderPath(GetLocalAppDataFolder());
|
|
folderPath.append(browserDataFolder);
|
|
if (!std::filesystem::exists(folderPath))
|
|
{
|
|
Logger::info(L"Edge base path does not exist: {}", folderPath.wstring());
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
for (const auto& directory : std::filesystem::directory_iterator(folderPath))
|
|
{
|
|
if (!directory.is_directory())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const std::wstring directoryName = directory.path().filename();
|
|
if (directoryName.find(browserDirPrefix) != 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const std::wstring appId = directoryName.substr(browserDirPrefix.length());
|
|
if (addingAppIdCallback)
|
|
{
|
|
addingAppIdCallback(appId);
|
|
}
|
|
|
|
for (const auto& filename : std::filesystem::directory_iterator(directory))
|
|
{
|
|
if (!filename.is_directory())
|
|
{
|
|
const std::filesystem::path filenameString = filename.path().filename();
|
|
if (StringUtils::CaseInsensitiveEquals(filenameString.extension(), NonLocalizable::IcoExtension))
|
|
{
|
|
const auto stem = filenameString.stem().wstring();
|
|
m_pwaAppIdsToAppNames.insert({ appId, stem });
|
|
Logger::info(L"Found an installed Pwa app {} with PwaAppId {}", stem, appId);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (std::exception& ex)
|
|
{
|
|
Logger::error("Failed to iterate over the directory: {}", ex.what());
|
|
}
|
|
}
|
|
|
|
void PwaHelper::InitEdgeAppIds()
|
|
{
|
|
if (!m_edgeAppIds.empty())
|
|
{
|
|
// already initialized
|
|
return;
|
|
}
|
|
|
|
CommandLineArgsHelper commandLineArgsHelper{};
|
|
|
|
const auto pwaHelperProcessIds = FindPwaHelperProcessIds();
|
|
Logger::info(L"Found {} edge Pwa helper processes", pwaHelperProcessIds.size());
|
|
|
|
for (const auto subProcessID : pwaHelperProcessIds)
|
|
{
|
|
std::wstring aumidID = GetAUMIDFromProcessId(subProcessID);
|
|
std::wstring commandLineArg = commandLineArgsHelper.GetCommandLineArgs(subProcessID);
|
|
std::wstring appId = GetAppIdFromCommandLineArgs(commandLineArg);
|
|
|
|
m_edgeAppIds.insert({ aumidID, appId });
|
|
Logger::info(L"Found an edge Pwa helper process with AumidID {} and PwaAppId {}", aumidID, appId);
|
|
}
|
|
|
|
InitAppIds(NonLocalizable::EdgeBase, NonLocalizable::EdgeDirPrefix, [&](const std::wstring&) {});
|
|
}
|
|
|
|
void PwaHelper::InitChromeAppIds()
|
|
{
|
|
if (!m_chromeAppIds.empty())
|
|
{
|
|
// already initialized
|
|
return;
|
|
}
|
|
|
|
InitAppIds(NonLocalizable::ChromeBase, NonLocalizable::ChromeDirPrefix, [&](const std::wstring& appId) {
|
|
m_chromeAppIds.push_back(appId);
|
|
});
|
|
}
|
|
|
|
std::optional<std::wstring> PwaHelper::GetEdgeAppId(const std::wstring& windowAumid) const
|
|
{
|
|
const auto pwaIndex = m_edgeAppIds.find(windowAumid);
|
|
if (pwaIndex != m_edgeAppIds.end())
|
|
{
|
|
return pwaIndex->second;
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<std::wstring> PwaHelper::GetChromeAppId(const std::wstring& windowAumid) const
|
|
{
|
|
const auto appIdIndexStart = windowAumid.find(NonLocalizable::ChromeAppIdIdentifier);
|
|
if (appIdIndexStart != std::wstring::npos)
|
|
{
|
|
std::wstring windowAumidSub = windowAumid.substr(appIdIndexStart + NonLocalizable::ChromeAppIdIdentifier.size());
|
|
const auto appIdIndexEnd = windowAumidSub.find(L" ");
|
|
if (appIdIndexEnd != std::wstring::npos)
|
|
{
|
|
windowAumidSub = windowAumidSub.substr(0, appIdIndexEnd);
|
|
}
|
|
|
|
const std::wstring windowAumidBegin = windowAumidSub.substr(0, 10);
|
|
for (const auto chromeAppId : m_chromeAppIds)
|
|
{
|
|
if (chromeAppId.find(windowAumidBegin) == 0)
|
|
{
|
|
return chromeAppId;
|
|
}
|
|
}
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::wstring PwaHelper::SearchPwaName(const std::wstring& pwaAppId, const std::wstring& windowAumid) const
|
|
{
|
|
const auto index = m_pwaAppIdsToAppNames.find(pwaAppId);
|
|
if (index != m_pwaAppIdsToAppNames.end())
|
|
{
|
|
return index->second;
|
|
}
|
|
|
|
std::wstring nameFromAumid{ windowAumid };
|
|
const std::size_t delimiterPos = nameFromAumid.find(L"-");
|
|
if (delimiterPos != std::string::npos)
|
|
{
|
|
return nameFromAumid.substr(0, delimiterPos);
|
|
}
|
|
|
|
return nameFromAumid;
|
|
}
|
|
|
|
std::wstring PwaHelper::GetAppIdFromCommandLineArgs(const std::wstring& commandLineArgs) const
|
|
{
|
|
auto result = commandLineArgs;
|
|
|
|
// remove the prefix
|
|
if (result.find(NonLocalizable::EdgeAppIdIdentifier) == 0)
|
|
{
|
|
result.erase(0, NonLocalizable::EdgeAppIdIdentifier.length());
|
|
}
|
|
|
|
// remove the suffix
|
|
auto appIdIndexEnd = result.find(L" ");
|
|
if (appIdIndexEnd != std::wstring::npos)
|
|
{
|
|
result = result.substr(0, appIdIndexEnd);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
} |