mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-23 19:49:43 +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>
243 lines
8.7 KiB
C++
243 lines
8.7 KiB
C++
#include "pch.h"
|
|
#include "AppLauncher.h"
|
|
|
|
#include <filesystem>
|
|
|
|
#include <shellapi.h>
|
|
|
|
#include <winrt/Windows.Management.Deployment.h>
|
|
#include <winrt/Windows.ApplicationModel.Core.h>
|
|
|
|
#include <common/utils/winapi_error.h>
|
|
|
|
#include <RegistryUtils.h>
|
|
|
|
using namespace winrt;
|
|
using namespace Windows::Foundation;
|
|
using namespace Windows::Management::Deployment;
|
|
|
|
namespace AppLauncher
|
|
{
|
|
namespace NonLocalizable
|
|
{
|
|
const std::wstring EdgeFilename = L"msedge.exe";
|
|
const std::wstring EdgePwaFilename = L"msedge_proxy.exe";
|
|
const std::wstring ChromeFilename = L"chrome.exe";
|
|
const std::wstring ChromePwaFilename = L"chrome_proxy.exe";
|
|
const std::wstring PwaCommandLineAddition = L"--profile-directory=Default --app-id=";
|
|
const std::wstring SteamProtocolPrefix = L"steam:";
|
|
}
|
|
|
|
Result<SHELLEXECUTEINFO, std::wstring> LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated)
|
|
{
|
|
std::wstring dir = std::filesystem::path(appPath).parent_path();
|
|
|
|
SHELLEXECUTEINFO sei = { 0 };
|
|
sei.cbSize = sizeof(SHELLEXECUTEINFO);
|
|
sei.hwnd = nullptr;
|
|
sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;
|
|
sei.lpVerb = elevated ? L"runas" : L"open";
|
|
sei.lpFile = appPath.c_str();
|
|
sei.lpParameters = commandLineArgs.c_str();
|
|
sei.lpDirectory = dir.c_str();
|
|
sei.nShow = SW_SHOWNORMAL;
|
|
|
|
if (!ShellExecuteEx(&sei))
|
|
{
|
|
std::wstring error = get_last_error_or_default(GetLastError());
|
|
Logger::error(L"Failed to launch process. {}", error);
|
|
return Error(error);
|
|
}
|
|
|
|
return Ok(sei);
|
|
}
|
|
|
|
bool LaunchPackagedApp(const std::wstring& packageFullName, ErrorList& launchErrors)
|
|
{
|
|
try
|
|
{
|
|
PackageManager packageManager;
|
|
for (const auto& package : packageManager.FindPackagesForUser({}))
|
|
{
|
|
if (package.Id().FullName() == packageFullName)
|
|
{
|
|
auto getAppListEntriesOperation = package.GetAppListEntriesAsync();
|
|
auto appEntries = getAppListEntriesOperation.get();
|
|
|
|
if (appEntries.Size() > 0)
|
|
{
|
|
IAsyncOperation<bool> launchOperation = appEntries.GetAt(0).LaunchAsync();
|
|
bool launchResult = launchOperation.get();
|
|
return launchResult;
|
|
}
|
|
else
|
|
{
|
|
Logger::error(L"No app entries found for the package.");
|
|
launchErrors.push_back({ packageFullName, L"No app entries found for the package." });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (const hresult_error& ex)
|
|
{
|
|
Logger::error(L"Packaged app launching error: {}", ex.message());
|
|
launchErrors.push_back({ packageFullName, ex.message().c_str() });
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Launch(const WorkspacesData::WorkspacesProject::Application& app, ErrorList& launchErrors)
|
|
{
|
|
bool launched{ false };
|
|
|
|
// packaged apps: check protocol in registry
|
|
// usage example: Settings with cmd args
|
|
if (!app.packageFullName.empty())
|
|
{
|
|
auto names = RegistryUtils::GetUriProtocolNames(app.packageFullName);
|
|
if (!names.empty())
|
|
{
|
|
Logger::trace(L"Launching packaged by protocol with command line args {}", app.name);
|
|
|
|
std::wstring uriProtocolName = names[0];
|
|
std::wstring command = std::wstring(uriProtocolName + (app.commandLineArgs.starts_with(L":") ? L"" : L":") + app.commandLineArgs);
|
|
|
|
auto res = LaunchApp(command, L"", app.isElevated);
|
|
if (res.isOk())
|
|
{
|
|
launched = true;
|
|
}
|
|
else
|
|
{
|
|
launchErrors.push_back({ std::filesystem::path(app.path).filename(), res.error() });
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Logger::info(L"Uri protocol names not found for {}", app.packageFullName);
|
|
}
|
|
}
|
|
|
|
// packaged apps: try launching first by AppUserModel.ID
|
|
// usage example: elevated Terminal
|
|
if (!launched && !app.appUserModelId.empty() && !app.packageFullName.empty())
|
|
{
|
|
Logger::trace(L"Launching {} as {} - {app.packageFullName}", app.name, app.appUserModelId, app.packageFullName);
|
|
auto res = LaunchApp(L"shell:AppsFolder\\" + app.appUserModelId, app.commandLineArgs, app.isElevated);
|
|
if (res.isOk())
|
|
{
|
|
launched = true;
|
|
}
|
|
else
|
|
{
|
|
launchErrors.push_back({ std::filesystem::path(app.path).filename(), res.error() });
|
|
}
|
|
}
|
|
|
|
// protocol launch for steam
|
|
if (!launched && !app.appUserModelId.empty() && app.appUserModelId.contains(NonLocalizable::SteamProtocolPrefix))
|
|
{
|
|
Logger::trace(L"Launching {} as {}", app.name, app.appUserModelId);
|
|
auto res = LaunchApp(app.appUserModelId, app.commandLineArgs, app.isElevated);
|
|
if (res.isOk())
|
|
{
|
|
launched = true;
|
|
}
|
|
else
|
|
{
|
|
launchErrors.push_back({ std::filesystem::path(app.path).filename(), res.error() });
|
|
}
|
|
}
|
|
|
|
// packaged apps: try launching by package full name
|
|
// doesn't work for elevated apps or apps with command line args
|
|
if (!launched && !app.packageFullName.empty() && app.commandLineArgs.empty() && !app.isElevated)
|
|
{
|
|
Logger::trace(L"Launching packaged app {}", app.name);
|
|
launched = LaunchPackagedApp(app.packageFullName, launchErrors);
|
|
}
|
|
|
|
std::wstring appPathFinal;
|
|
std::wstring commandLineArgsFinal;
|
|
appPathFinal = app.path;
|
|
commandLineArgsFinal = app.commandLineArgs;
|
|
|
|
if (!launched && !app.pwaAppId.empty())
|
|
{
|
|
int version = 0;
|
|
|
|
if (app.version != L"")
|
|
{
|
|
try
|
|
{
|
|
version = std::stoi(app.version);
|
|
}
|
|
catch (const std::invalid_argument&)
|
|
{
|
|
Logger::error(L"Invalid version format: {}", app.version);
|
|
version = 0;
|
|
}
|
|
catch (const std::out_of_range&)
|
|
{
|
|
Logger::error(L"Version out of range: {}", app.version);
|
|
version = 0;
|
|
}
|
|
}
|
|
|
|
if (version >= 1)
|
|
{
|
|
auto res = LaunchApp(L"shell:AppsFolder\\" + app.appUserModelId, app.commandLineArgs, app.isElevated);
|
|
if (res.isOk())
|
|
{
|
|
launched = true;
|
|
}
|
|
else
|
|
{
|
|
launchErrors.push_back({ app.appUserModelId, res.error() });
|
|
}
|
|
}
|
|
|
|
if (!launched)
|
|
{
|
|
std::filesystem::path appPath(app.path);
|
|
if (appPath.filename() == NonLocalizable::EdgeFilename)
|
|
{
|
|
appPathFinal = appPath.parent_path() / NonLocalizable::EdgePwaFilename;
|
|
commandLineArgsFinal = NonLocalizable::PwaCommandLineAddition + app.pwaAppId + L" " + app.commandLineArgs;
|
|
}
|
|
if (appPath.filename() == NonLocalizable::ChromeFilename)
|
|
{
|
|
appPathFinal = appPath.parent_path() / NonLocalizable::ChromePwaFilename;
|
|
commandLineArgsFinal = NonLocalizable::PwaCommandLineAddition + app.pwaAppId + L" " + app.commandLineArgs;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!launched)
|
|
{
|
|
Logger::trace(L"Launching {} at {}", app.name, appPathFinal);
|
|
|
|
DWORD dwAttrib = GetFileAttributesW(appPathFinal.c_str());
|
|
if (dwAttrib == INVALID_FILE_ATTRIBUTES)
|
|
{
|
|
Logger::error(L"File not found at {}", appPathFinal);
|
|
launchErrors.push_back({ std::filesystem::path(appPathFinal).filename(), L"File not found" });
|
|
return false;
|
|
}
|
|
|
|
auto res = LaunchApp(appPathFinal, commandLineArgsFinal, app.isElevated);
|
|
if (res.isOk())
|
|
{
|
|
launched = true;
|
|
}
|
|
else
|
|
{
|
|
launchErrors.push_back({ std::filesystem::path(appPathFinal).filename(), res.error() });
|
|
}
|
|
}
|
|
|
|
Logger::trace(L"{} {} at {}", app.name, (launched ? L"launched" : L"not launched"), appPathFinal);
|
|
return launched;
|
|
}
|
|
} |