mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-24 04:00:02 +01:00
[Workspaces] Sequential launch (#35297)
This commit is contained in:
@@ -10,8 +10,6 @@
|
||||
|
||||
#include <common/utils/winapi_error.h>
|
||||
|
||||
#include <WorkspacesLib/AppUtils.h>
|
||||
|
||||
#include <RegistryUtils.h>
|
||||
|
||||
using namespace winrt;
|
||||
@@ -20,26 +18,6 @@ using namespace Windows::Management::Deployment;
|
||||
|
||||
namespace AppLauncher
|
||||
{
|
||||
void UpdatePackagedApps(std::vector<WorkspacesData::WorkspacesProject::Application>& apps, const Utils::Apps::AppList& installedApps)
|
||||
{
|
||||
for (auto& app : apps)
|
||||
{
|
||||
// Packaged apps have version in the path, it will be outdated after update.
|
||||
// We need make sure the current package is up to date.
|
||||
if (!app.packageFullName.empty())
|
||||
{
|
||||
auto installedApp = std::find_if(installedApps.begin(), installedApps.end(), [&](const Utils::Apps::AppData& val) { return val.name == app.name; });
|
||||
if (installedApp != installedApps.end() && app.packageFullName != installedApp->packageFullName)
|
||||
{
|
||||
std::wstring exeFileName = app.path.substr(app.path.find_last_of(L"\\") + 1);
|
||||
app.packageFullName = installedApp->packageFullName;
|
||||
app.path = installedApp->installPath + L"\\" + exeFileName;
|
||||
Logger::trace(L"Updated package full name for {}: {}", app.name, app.packageFullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result<SHELLEXECUTEINFO, std::wstring> LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated)
|
||||
{
|
||||
std::wstring dir = std::filesystem::path(appPath).parent_path();
|
||||
@@ -181,29 +159,4 @@ namespace AppLauncher
|
||||
Logger::trace(L"{} {} at {}", app.name, (launched ? L"launched" : L"not launched"), app.path);
|
||||
return launched;
|
||||
}
|
||||
|
||||
bool Launch(WorkspacesData::WorkspacesProject& project, LaunchingStatus& launchingStatus, ErrorList& launchErrors)
|
||||
{
|
||||
bool launchedSuccessfully{ true };
|
||||
|
||||
auto installedApps = Utils::Apps::GetAppsList();
|
||||
UpdatePackagedApps(project.apps, installedApps);
|
||||
|
||||
// Launch apps
|
||||
for (auto& app : project.apps)
|
||||
{
|
||||
if (!Launch(app, launchErrors))
|
||||
{
|
||||
Logger::error(L"Failed to launch {}", app.name);
|
||||
launchingStatus.Update(app, LaunchingState::Failed);
|
||||
launchedSuccessfully = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
launchingStatus.Update(app, LaunchingState::Launched);
|
||||
}
|
||||
}
|
||||
|
||||
return launchedSuccessfully;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <WorkspacesLib/AppUtils.h>
|
||||
#include <WorkspacesLib/LaunchingStatus.h>
|
||||
#include <WorkspacesLib/Result.h>
|
||||
#include <WorkspacesLib/WorkspacesData.h>
|
||||
@@ -10,7 +11,6 @@ namespace AppLauncher
|
||||
{
|
||||
using ErrorList = std::vector<std::pair<std::wstring, std::wstring>>;
|
||||
|
||||
bool Launch(const WorkspacesData::WorkspacesProject::Application& app, ErrorList& launchErrors);
|
||||
Result<SHELLEXECUTEINFO, std::wstring> LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated);
|
||||
|
||||
bool Launch(WorkspacesData::WorkspacesProject& project, LaunchingStatus& launchingStatus, ErrorList& launchErrors);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <WorkspacesLib/trace.h>
|
||||
|
||||
#include <AppLauncher.h>
|
||||
#include <WorkspacesLib/AppUtils.h>
|
||||
|
||||
Launcher::Launcher(const WorkspacesData::WorkspacesProject& project,
|
||||
std::vector<WorkspacesData::WorkspacesProject>& workspaces,
|
||||
@@ -16,10 +17,13 @@ Launcher::Launcher(const WorkspacesData::WorkspacesProject& project,
|
||||
m_workspaces(workspaces),
|
||||
m_invokePoint(invokePoint),
|
||||
m_start(std::chrono::high_resolution_clock::now()),
|
||||
m_uiHelper(std::make_unique<LauncherUIHelper>()),
|
||||
m_uiHelper(std::make_unique<LauncherUIHelper>(std::bind(&Launcher::handleUIMessage, this, std::placeholders::_1))),
|
||||
m_windowArrangerHelper(std::make_unique<WindowArrangerHelper>(std::bind(&Launcher::handleWindowArrangerMessage, this, std::placeholders::_1))),
|
||||
m_launchingStatus(m_project, std::bind(&LauncherUIHelper::UpdateLaunchStatus, m_uiHelper.get(), std::placeholders::_1))
|
||||
m_launchingStatus(m_project)
|
||||
{
|
||||
// main thread
|
||||
Logger::info(L"Launch Workspace {} : {}", m_project.name, m_project.id);
|
||||
|
||||
m_uiHelper->LaunchUI();
|
||||
m_uiHelper->UpdateLaunchStatus(m_launchingStatus.Get());
|
||||
|
||||
@@ -48,6 +52,7 @@ Launcher::Launcher(const WorkspacesData::WorkspacesProject& project,
|
||||
|
||||
Launcher::~Launcher()
|
||||
{
|
||||
// main thread, will wait until arranger is finished
|
||||
Logger::trace(L"Finalizing launch");
|
||||
|
||||
// update last-launched time
|
||||
@@ -86,20 +91,81 @@ Launcher::~Launcher()
|
||||
}
|
||||
}
|
||||
|
||||
std::lock_guard lock(m_launchErrorsMutex);
|
||||
Trace::Workspaces::Launch(m_launchedSuccessfully, m_project, m_invokePoint, duration.count(), differentSetup, m_launchErrors);
|
||||
}
|
||||
|
||||
void Launcher::Launch()
|
||||
void Launcher::Launch() // Launching thread
|
||||
{
|
||||
Logger::info(L"Launch Workspace {} : {}", m_project.name, m_project.id);
|
||||
m_launchedSuccessfully = AppLauncher::Launch(m_project, m_launchingStatus, m_launchErrors);
|
||||
const long maxWaitTimeMs = 3000;
|
||||
const long ms = 100;
|
||||
|
||||
// Launch apps
|
||||
for (auto appState = m_launchingStatus.GetNext(LaunchingState::Waiting); appState.has_value(); appState = m_launchingStatus.GetNext(LaunchingState::Waiting))
|
||||
{
|
||||
auto app = appState.value().application;
|
||||
|
||||
long waitingTime = 0;
|
||||
bool additionalWait = false;
|
||||
while (!m_launchingStatus.AllInstancesOfTheAppLaunchedAndMoved(app) && waitingTime < maxWaitTimeMs)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
|
||||
waitingTime += ms;
|
||||
additionalWait = true;
|
||||
}
|
||||
|
||||
if (additionalWait)
|
||||
{
|
||||
// Resolves an issue when Outlook does not launch when launching one after another.
|
||||
// Launching Outlook instances right one after another causes error message.
|
||||
// Launching Outlook instances with less than 1-second delay causes the second window not to appear
|
||||
// even though there wasn't a launch error.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
}
|
||||
|
||||
if (waitingTime >= maxWaitTimeMs)
|
||||
{
|
||||
Logger::info(L"Waiting time for launching next {} instance expired", app.name);
|
||||
}
|
||||
|
||||
bool launched{ false };
|
||||
{
|
||||
std::lock_guard lock(m_launchErrorsMutex);
|
||||
launched = AppLauncher::Launch(app, m_launchErrors);
|
||||
}
|
||||
|
||||
if (launched)
|
||||
{
|
||||
m_launchingStatus.Update(app, LaunchingState::Launched);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error(L"Failed to launch {}", app.name);
|
||||
m_launchingStatus.Update(app, LaunchingState::Failed);
|
||||
m_launchedSuccessfully = false;
|
||||
}
|
||||
|
||||
auto status = m_launchingStatus.Get(app); // updated after launch status
|
||||
if (status.has_value())
|
||||
{
|
||||
{
|
||||
std::lock_guard lock(m_windowArrangerHelperMutex);
|
||||
m_windowArrangerHelper->UpdateLaunchStatus(status.value());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard lock(m_uiHelperMutex);
|
||||
m_uiHelper->UpdateLaunchStatus(m_launchingStatus.Get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Launcher::handleWindowArrangerMessage(const std::wstring& msg)
|
||||
void Launcher::handleWindowArrangerMessage(const std::wstring& msg) // WorkspacesArranger IPC thread
|
||||
{
|
||||
if (msg == L"ready")
|
||||
{
|
||||
Launch();
|
||||
std::thread([&]() { Launch(); }).detach();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -109,6 +175,11 @@ void Launcher::handleWindowArrangerMessage(const std::wstring& msg)
|
||||
if (data.has_value())
|
||||
{
|
||||
m_launchingStatus.Update(data.value().application, data.value().state);
|
||||
|
||||
{
|
||||
std::lock_guard lock(m_uiHelperMutex);
|
||||
m_uiHelper->UpdateLaunchStatus(m_launchingStatus.Get());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -121,3 +192,11 @@ void Launcher::handleWindowArrangerMessage(const std::wstring& msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Launcher::handleUIMessage(const std::wstring& msg) // UI IPC thread
|
||||
{
|
||||
if (msg == L"cancel")
|
||||
{
|
||||
m_launchingStatus.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,18 +14,24 @@ public:
|
||||
Launcher(const WorkspacesData::WorkspacesProject& project, std::vector<WorkspacesData::WorkspacesProject>& workspaces, InvokePoint invokePoint);
|
||||
~Launcher();
|
||||
|
||||
void Launch();
|
||||
|
||||
private:
|
||||
WorkspacesData::WorkspacesProject m_project;
|
||||
std::vector<WorkspacesData::WorkspacesProject>& m_workspaces;
|
||||
const InvokePoint m_invokePoint;
|
||||
const std::chrono::steady_clock::time_point m_start;
|
||||
std::unique_ptr<LauncherUIHelper> m_uiHelper;
|
||||
std::unique_ptr<WindowArrangerHelper> m_windowArrangerHelper;
|
||||
std::atomic<bool> m_launchedSuccessfully{};
|
||||
LaunchingStatus m_launchingStatus;
|
||||
bool m_launchedSuccessfully{};
|
||||
std::vector<std::pair<std::wstring, std::wstring>> m_launchErrors{};
|
||||
|
||||
std::unique_ptr<LauncherUIHelper> m_uiHelper;
|
||||
std::mutex m_uiHelperMutex;
|
||||
|
||||
std::unique_ptr<WindowArrangerHelper> m_windowArrangerHelper;
|
||||
std::mutex m_windowArrangerHelperMutex;
|
||||
|
||||
std::vector<std::pair<std::wstring, std::wstring>> m_launchErrors{};
|
||||
std::mutex m_launchErrorsMutex;
|
||||
|
||||
void Launch();
|
||||
void handleWindowArrangerMessage(const std::wstring& msg);
|
||||
void handleUIMessage(const std::wstring& msg);
|
||||
};
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
|
||||
#include <AppLauncher.h>
|
||||
|
||||
LauncherUIHelper::LauncherUIHelper() :
|
||||
LauncherUIHelper::LauncherUIHelper(std::function<void(const std::wstring&)> ipcCallback) :
|
||||
m_processId{},
|
||||
m_ipcHelper(IPCHelperStrings::LauncherUIPipeName, IPCHelperStrings::UIPipeName, nullptr)
|
||||
m_ipcHelper(IPCHelperStrings::LauncherUIPipeName, IPCHelperStrings::UIPipeName, ipcCallback)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
class LauncherUIHelper
|
||||
{
|
||||
public:
|
||||
LauncherUIHelper();
|
||||
LauncherUIHelper(std::function<void(const std::wstring&)> ipcCallback);
|
||||
~LauncherUIHelper();
|
||||
|
||||
void LaunchUI();
|
||||
|
||||
@@ -69,3 +69,8 @@ void WindowArrangerHelper::Launch(const std::wstring& projectId, bool elevated,
|
||||
Logger::error(L"Failed to launch PowerToys.WorkspacesWindowArranger: {}", res.error());
|
||||
}
|
||||
}
|
||||
|
||||
void WindowArrangerHelper::UpdateLaunchStatus(const WorkspacesData::LaunchingAppState& appState) const
|
||||
{
|
||||
m_ipcHelper.send(WorkspacesData::AppLaunchInfoJSON::ToJson({ appState.application, nullptr, appState.state }).ToString().c_str());
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ public:
|
||||
~WindowArrangerHelper();
|
||||
|
||||
void Launch(const std::wstring& projectId, bool elevated, std::function<bool()> keepWaitingCallback);
|
||||
void UpdateLaunchStatus(const WorkspacesData::LaunchingAppState& appState) const;
|
||||
|
||||
private:
|
||||
DWORD m_processId;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <Launcher.h>
|
||||
|
||||
#include <Generated Files/resource.h>
|
||||
#include <WorkspacesLib/AppUtils.h>
|
||||
|
||||
const std::wstring moduleName = L"Workspaces\\WorkspacesLauncher";
|
||||
const std::wstring internalPath = L"";
|
||||
@@ -161,6 +162,37 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cm
|
||||
return 1;
|
||||
}
|
||||
|
||||
// prepare project in advance
|
||||
auto installedApps = Utils::Apps::GetAppsList();
|
||||
bool updatedApps = Utils::Apps::UpdateWorkspacesApps(projectToLaunch, installedApps);
|
||||
bool updatedIds = false;
|
||||
|
||||
// verify apps have ids
|
||||
for (auto& app : projectToLaunch.apps)
|
||||
{
|
||||
if (app.id.empty())
|
||||
{
|
||||
app.id = CreateGuidString();
|
||||
updatedIds = true;
|
||||
}
|
||||
}
|
||||
|
||||
// update the file before launching, so WorkspacesWindowArranger and WorkspacesLauncherUI could get updated app paths
|
||||
if (updatedApps || updatedIds)
|
||||
{
|
||||
for (int i = 0; i < workspaces.size(); i++)
|
||||
{
|
||||
if (workspaces[i].id == projectToLaunch.id)
|
||||
{
|
||||
workspaces[i] = projectToLaunch;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
json::to_file(WorkspacesData::WorkspacesFile(), WorkspacesData::WorkspacesListJSON::ToJson(workspaces));
|
||||
}
|
||||
|
||||
// launch
|
||||
Launcher launcher(projectToLaunch, workspaces, cmdArgs.invokePoint);
|
||||
|
||||
Logger::trace("Finished");
|
||||
|
||||
Reference in New Issue
Block a user