[Workspaces] Sequential launch (#35297)

This commit is contained in:
Seraphima Zykova
2024-10-14 16:51:02 +03:00
committed by GitHub
parent 89ec5be5ba
commit 9994fd7715
26 changed files with 419 additions and 158 deletions

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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);
};

View File

@@ -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)
{
}

View File

@@ -6,7 +6,7 @@
class LauncherUIHelper
{
public:
LauncherUIHelper();
LauncherUIHelper(std::function<void(const std::wstring&)> ipcCallback);
~LauncherUIHelper();
void LaunchUI();

View File

@@ -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());
}

View File

@@ -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;

View File

@@ -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");