Skip to content

Commit

Permalink
[Workspaces] Sequential launch (#35297)
Browse files Browse the repository at this point in the history
  • Loading branch information
SeraphimaZykova authored Oct 14, 2024
1 parent 89ec5be commit 9994fd7
Show file tree
Hide file tree
Showing 26 changed files with 399 additions and 138 deletions.
2 changes: 2 additions & 0 deletions src/modules/Workspaces/WorkspacesEditor/Data/ProjectData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public struct WindowPositionWrapper
public int Height { get; set; }
}

public string Id { get; set; }

public string Application { get; set; }

public string ApplicationPath { get; set; }
Expand Down
3 changes: 3 additions & 0 deletions src/modules/Workspaces/WorkspacesEditor/Models/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public Application()

public Application(Application other)
{
Id = other.Id;
AppName = other.AppName;
AppPath = other.AppPath;
AppTitle = other.AppTitle;
Expand Down Expand Up @@ -95,6 +96,8 @@ public override int GetHashCode()
}
}

public string Id { get; set; }

public string AppName { get; set; }

public string AppPath { get; set; }
Expand Down
1 change: 1 addition & 0 deletions src/modules/Workspaces/WorkspacesEditor/Models/Project.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ public Project(ProjectData.ProjectWrapper project)
{
Models.Application newApp = new Models.Application()
{
Id = app.Id != null ? app.Id : $"{{{Guid.NewGuid().ToString()}}}",
AppName = app.Application,
AppPath = app.ApplicationPath,
AppTitle = app.Title,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public void SerializeWorkspaces(List<Project> workspaces, bool useTempFile = fal
{
wrapper.Applications.Add(new ProjectData.ApplicationWrapper
{
Id = app.Id,
Application = app.AppName,
ApplicationPath = app.AppPath,
Title = app.AppTitle,
Expand Down
47 changes: 0 additions & 47 deletions src/modules/Workspaces/WorkspacesLauncher/AppLauncher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@

#include <common/utils/winapi_error.h>

#include <WorkspacesLib/AppUtils.h>

#include <RegistryUtils.h>

using namespace winrt;
Expand All @@ -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();
Expand Down Expand Up @@ -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;
}
}
4 changes: 2 additions & 2 deletions src/modules/Workspaces/WorkspacesLauncher/AppLauncher.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <shellapi.h>

#include <WorkspacesLib/AppUtils.h>
#include <WorkspacesLib/LaunchingStatus.h>
#include <WorkspacesLib/Result.h>
#include <WorkspacesLib/WorkspacesData.h>
Expand All @@ -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);
}
93 changes: 86 additions & 7 deletions src/modules/Workspaces/WorkspacesLauncher/Launcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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());

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
{
Expand All @@ -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
{
Expand All @@ -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 changes: 10 additions & 4 deletions src/modules/Workspaces/WorkspacesLauncher/Launcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,24 @@ class Launcher
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::atomic<bool> m_launchedSuccessfully{};
LaunchingStatus m_launchingStatus;

std::unique_ptr<LauncherUIHelper> m_uiHelper;
std::mutex m_uiHelperMutex;

std::unique_ptr<WindowArrangerHelper> m_windowArrangerHelper;
LaunchingStatus m_launchingStatus;
bool m_launchedSuccessfully{};
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);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
class LauncherUIHelper
{
public:
LauncherUIHelper();
LauncherUIHelper(std::function<void(const std::wstring&)> ipcCallback);
~LauncherUIHelper();

void LaunchUI();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class WindowArrangerHelper
~WindowArrangerHelper();

void Launch(const std::wstring& projectId, bool elevated, std::function<bool()> keepWaitingCallback);
void UpdateLaunchStatus(const WorkspacesData::LaunchingAppState& appState) const;

private:
DWORD m_processId;
Expand Down
32 changes: 32 additions & 0 deletions src/modules/Workspaces/WorkspacesLauncher/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"";
Expand Down Expand Up @@ -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");
Expand Down
8 changes: 8 additions & 0 deletions src/modules/Workspaces/WorkspacesLauncherUI/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ public App()
{
}

public static void SendIPCMessage(string message)
{
if (ipcmanager != null)
{
ipcmanager.Send(message);
}
}

private void OnStartup(object sender, StartupEventArgs e)
{
Logger.InitializeLogger("\\Workspaces\\WorkspacesLauncherUI");
Expand Down
Loading

0 comments on commit 9994fd7

Please sign in to comment.