Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Workspaces] Sequential launch #35297

Merged
merged 14 commits into from
Oct 14, 2024
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,
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
19 changes: 19 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,24 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cm
return 1;
}

// prepare project in advance
auto installedApps = Utils::Apps::GetAppsList();
if (Utils::Apps::UpdateWorkspacesApps(projectToLaunch, installedApps))
{
// update the file before launching, so WorkspacesWindowArranger and WorkspacesLauncherUI could get updated app paths
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ public enum LaunchingState
Launched,
LaunchedAndMoved,
Failed,
Canceled,
}
}
Loading
Loading