[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
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 419 additions and 158 deletions

View File

@ -25,6 +25,8 @@ namespace WorkspacesEditor.Data
public int Height { get; set; }
}
public string Id { get; set; }
public string Application { get; set; }
public string ApplicationPath { get; set; }

View File

@ -31,6 +31,7 @@ namespace WorkspacesEditor.Models
public Application(Application other)
{
Id = other.Id;
AppName = other.AppName;
AppPath = other.AppPath;
AppTitle = other.AppTitle;
@ -95,6 +96,8 @@ namespace WorkspacesEditor.Models
}
}
public string Id { get; set; }
public string AppName { get; set; }
public string AppPath { get; set; }

View File

@ -251,6 +251,7 @@ namespace WorkspacesEditor.Models
{
Models.Application newApp = new Models.Application()
{
Id = app.Id != null ? app.Id : $"{{{Guid.NewGuid().ToString()}}}",
AppName = app.Application,
AppPath = app.ApplicationPath,
AppTitle = app.Title,

View File

@ -97,6 +97,7 @@ namespace WorkspacesEditor.Utils
{
wrapper.Applications.Add(new ProjectData.ApplicationWrapper
{
Id = app.Id,
Application = app.AppName,
ApplicationPath = app.AppPath,
Title = app.AppTitle,

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

View File

@ -37,6 +37,14 @@ namespace WorkspacesLauncherUI
{
}
public static void SendIPCMessage(string message)
{
if (ipcmanager != null)
{
ipcmanager.Send(message);
}
}
private void OnStartup(object sender, StartupEventArgs e)
{
Logger.InitializeLogger("\\Workspaces\\WorkspacesLauncherUI");

View File

@ -11,5 +11,6 @@ namespace WorkspacesLauncherUI.Data
Launched,
LaunchedAndMoved,
Failed,
Canceled,
}
}

View File

@ -19,7 +19,6 @@ namespace WorkspacesLauncherUI.ViewModels
private StatusWindow _snapshotWindow;
private int launcherProcessID;
private bool _exiting;
public event PropertyChangedEventHandler PropertyChanged;
@ -30,8 +29,6 @@ namespace WorkspacesLauncherUI.ViewModels
public MainViewModel()
{
_exiting = false;
// receive IPC Message
App.IPCMessageReceivedCallback = (string msg) =>
{
@ -50,11 +47,6 @@ namespace WorkspacesLauncherUI.ViewModels
private void HandleAppLaunchingState(AppLaunchData.AppLaunchDataWrapper appLaunchData)
{
if (_exiting)
{
return;
}
launcherProcessID = appLaunchData.LauncherProcessID;
List<AppLaunching> appLaunchingList = new List<AppLaunching>();
foreach (var app in appLaunchData.AppLaunchInfos.AppLaunchInfoList)
@ -90,9 +82,7 @@ namespace WorkspacesLauncherUI.ViewModels
internal void CancelLaunch()
{
_exiting = true;
Process proc = Process.GetProcessById(launcherProcessID);
proc.Kill();
App.SendIPCMessage("cancel");
}
}
}

View File

@ -341,5 +341,42 @@ namespace Utils
return Utils::Apps::GetApp(processPath, pid, apps);
}
bool UpdateAppVersion(WorkspacesData::WorkspacesProject::Application& app, const AppList& installedApps)
{
auto installedApp = std::find_if(installedApps.begin(), installedApps.end(), [&](const AppData& val) { return val.name == app.name; });
if (installedApp == installedApps.end())
{
return false;
}
// 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())
{
if (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);
return true;
}
}
return false;
}
bool UpdateWorkspacesApps(WorkspacesData::WorkspacesProject& workspace, const AppList& installedApps)
{
bool updated = false;
for (auto& app : workspace.apps)
{
updated |= UpdateAppVersion(app, installedApps);
}
return updated;
}
}
}

View File

@ -1,5 +1,7 @@
#pragma once
#include <WorkspacesLib/WorkspacesData.h>
namespace Utils
{
namespace Apps
@ -21,5 +23,8 @@ namespace Utils
AppList GetAppsList();
std::optional<AppData> GetApp(const std::wstring& appPath, DWORD pid, const AppList& apps);
std::optional<AppData> GetApp(HWND window, const AppList& apps);
bool UpdateAppVersion(WorkspacesData::WorkspacesProject::Application& app, const AppList& installedApps);
bool UpdateWorkspacesApps(WorkspacesData::WorkspacesProject& workspace, const AppList& installedApps);
}
}

View File

@ -6,5 +6,6 @@ enum class LaunchingState
Waiting = 0,
Launched,
LaunchedAndMoved,
Failed
Failed,
Canceled,
};

View File

@ -3,8 +3,7 @@
#include <common/logger/logger.h>
LaunchingStatus::LaunchingStatus(const WorkspacesData::WorkspacesProject& project, std::function<void(const WorkspacesData::LaunchingAppStateMap&)> updateCallback) :
m_updateCallback(updateCallback)
LaunchingStatus::LaunchingStatus(const WorkspacesData::WorkspacesProject& project)
{
std::unique_lock lock(m_mutex);
for (const auto& app : project.apps)
@ -13,26 +12,6 @@ LaunchingStatus::LaunchingStatus(const WorkspacesData::WorkspacesProject& projec
}
}
const WorkspacesData::LaunchingAppStateMap& LaunchingStatus::Get() noexcept
{
std::shared_lock lock(m_mutex);
return m_appsState;
}
bool LaunchingStatus::AllLaunchedAndMoved() noexcept
{
std::shared_lock lock(m_mutex);
for (const auto& [app, data] : m_appsState)
{
if (data.state != LaunchingState::Failed && data.state != LaunchingState::LaunchedAndMoved)
{
return false;
}
}
return true;
}
bool LaunchingStatus::AllLaunched() noexcept
{
std::shared_lock lock(m_mutex);
@ -47,6 +26,86 @@ bool LaunchingStatus::AllLaunched() noexcept
return true;
}
bool LaunchingStatus::AllLaunchedAndMoved() noexcept
{
std::shared_lock lock(m_mutex);
for (const auto& [app, data] : m_appsState)
{
if (data.state != LaunchingState::Failed &&
data.state != LaunchingState::Canceled &&
data.state != LaunchingState::LaunchedAndMoved)
{
return false;
}
}
return true;
}
bool LaunchingStatus::AllInstancesOfTheAppLaunchedAndMoved(const WorkspacesData::WorkspacesProject::Application& application) noexcept
{
std::shared_lock lock(m_mutex);
for (const auto& [app, state] : m_appsState)
{
if (app.name == application.name || app.path == application.path)
{
if (state.state == LaunchingState::Launched)
{
return false;
}
}
}
return true;
}
const WorkspacesData::LaunchingAppStateMap& LaunchingStatus::Get() noexcept
{
std::shared_lock lock(m_mutex);
return m_appsState;
}
std::optional<WorkspacesData::LaunchingAppState> LaunchingStatus::Get(const WorkspacesData::WorkspacesProject::Application& app) noexcept
{
std::shared_lock lock(m_mutex);
if (m_appsState.contains(app))
{
return m_appsState.at(app);
}
return std::nullopt;
}
std::optional<WorkspacesData::LaunchingAppState> LaunchingStatus::GetNext(LaunchingState state) noexcept
{
std::shared_lock lock(m_mutex);
for (const auto& [app, appState] : m_appsState)
{
if (appState.state == state)
{
return appState;
}
}
return std::nullopt;
}
bool LaunchingStatus::IsWindowProcessed(HWND window) noexcept
{
std::shared_lock lock(m_mutex);
for (const auto& [app, state] : m_appsState)
{
if (state.window == window)
{
return true;
}
}
return false;
}
void LaunchingStatus::Update(const WorkspacesData::WorkspacesProject::Application& app, LaunchingState state)
{
std::unique_lock lock(m_mutex);
@ -57,9 +116,29 @@ void LaunchingStatus::Update(const WorkspacesData::WorkspacesProject::Applicatio
}
m_appsState[app].state = state;
if (m_updateCallback)
{
m_updateCallback(m_appsState);
}
}
void LaunchingStatus::Update(const WorkspacesData::WorkspacesProject::Application& app, HWND window, LaunchingState state)
{
std::unique_lock lock(m_mutex);
if (!m_appsState.contains(app))
{
Logger::error(L"Error updating state: app {} is not tracked in the project", app.name);
return;
}
m_appsState[app].state = state;
m_appsState[app].window = window;
}
void LaunchingStatus::Cancel()
{
std::unique_lock lock(m_mutex);
for (auto& [app, state] : m_appsState)
{
if (state.state == LaunchingState::Waiting)
{
state.state = LaunchingState::Canceled;
}
}
}

View File

@ -1,6 +1,5 @@
#pragma once
#include <functional>
#include <shared_mutex>
#include <WorkspacesLib/WorkspacesData.h>
@ -8,17 +7,24 @@
class LaunchingStatus
{
public:
LaunchingStatus(const WorkspacesData::WorkspacesProject& project, std::function<void(const WorkspacesData::LaunchingAppStateMap&)> updateCallback);
LaunchingStatus(const WorkspacesData::WorkspacesProject& project);
~LaunchingStatus() = default;
bool AllLaunchedAndMoved() noexcept;
bool AllLaunched() noexcept;
bool AllLaunchedAndMoved() noexcept;
bool AllInstancesOfTheAppLaunchedAndMoved(const WorkspacesData::WorkspacesProject::Application& app) noexcept;
const WorkspacesData::LaunchingAppStateMap& Get() noexcept;
std::optional<WorkspacesData::LaunchingAppState> Get(const WorkspacesData::WorkspacesProject::Application& app) noexcept;
std::optional<WorkspacesData::LaunchingAppState> GetNext(LaunchingState state) noexcept;
bool IsWindowProcessed(HWND window) noexcept;
void Update(const WorkspacesData::WorkspacesProject::Application& app, LaunchingState state);
void Update(const WorkspacesData::WorkspacesProject::Application& app, HWND window, LaunchingState state);
void Cancel();
private:
WorkspacesData::LaunchingAppStateMap m_appsState;
std::function<void(const WorkspacesData::LaunchingAppStateMap&)> m_updateCallback;
std::shared_mutex m_mutex;
};

View File

@ -3,6 +3,8 @@
#include <common/SettingsAPI/settings_helpers.h>
#include <workspaces-common/GuidUtils.h>
namespace NonLocalizable
{
const inline wchar_t ModuleKey[] = L"Workspaces";
@ -72,6 +74,7 @@ namespace WorkspacesData
namespace NonLocalizable
{
const static wchar_t* AppIdID = L"id";
const static wchar_t* AppNameID = L"application";
const static wchar_t* AppPathID = L"application-path";
const static wchar_t* AppPackageFullNameID = L"package-full-name";
@ -89,6 +92,7 @@ namespace WorkspacesData
json::JsonObject ToJson(const WorkspacesProject::Application& data)
{
json::JsonObject json{};
json.SetNamedValue(NonLocalizable::AppIdID, json::value(data.id));
json.SetNamedValue(NonLocalizable::AppNameID, json::value(data.name));
json.SetNamedValue(NonLocalizable::AppPathID, json::value(data.path));
json.SetNamedValue(NonLocalizable::AppTitleID, json::value(data.title));
@ -110,6 +114,11 @@ namespace WorkspacesData
WorkspacesProject::Application result;
try
{
if (json.HasKey(NonLocalizable::AppIdID))
{
result.id = json.GetNamedString(NonLocalizable::AppIdID);
}
if (json.HasKey(NonLocalizable::AppNameID))
{
result.name = json.GetNamedString(NonLocalizable::AppNameID);

View File

@ -25,6 +25,7 @@ namespace WorkspacesData
auto operator<=>(const Position&) const = default;
};
std::wstring id;
std::wstring name;
std::wstring title;
std::wstring path;

View File

@ -94,45 +94,47 @@ namespace FancyZones
}
WindowArranger::WindowArranger(WorkspacesData::WorkspacesProject project, const IPCHelper& ipcHelper) :
WindowArranger::WindowArranger(WorkspacesData::WorkspacesProject project) :
m_project(project),
m_windowsBefore(WindowEnumerator::Enumerate(WindowFilter::Filter)),
m_monitors(MonitorUtils::IdentifyMonitors()),
m_installedApps(Utils::Apps::GetAppsList()),
//m_windowCreationHandler(std::bind(&WindowArranger::onWindowCreated, this, std::placeholders::_1)),
m_ipcHelper(ipcHelper)
m_ipcHelper(IPCHelperStrings::WindowArrangerPipeName, IPCHelperStrings::LauncherArrangerPipeName, std::bind(&WindowArranger::receiveIpcMessage, this, std::placeholders::_1)),
m_launchingStatus(m_project)
{
for (auto& app : project.apps)
{
m_launchingApps.insert({ app, { app, nullptr } });
}
m_ipcHelper.send(L"ready");
for (int attempt = 0; attempt < 50 && !allWindowsFound(); attempt++)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
const long maxLaunchingWaitingTime = 10000, maxRepositionWaitingTime = 3000, ms = 300;
long waitingTime{ 0 };
std::vector<HWND> windowsAfter = WindowEnumerator::Enumerate(WindowFilter::Filter);
std::vector<HWND> windowsDiff{};
std::copy_if(windowsAfter.begin(), windowsAfter.end(), std::back_inserter(windowsDiff), [&](HWND window) { return std::find(m_windowsBefore.begin(), m_windowsBefore.end(), window) == m_windowsBefore.end(); });
for (HWND window : windowsDiff)
{
processWindow(window);
}
// process launching windows
while (!m_launchingStatus.AllLaunched() && waitingTime < maxLaunchingWaitingTime)
{
processWindows(false);
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
waitingTime += ms;
}
bool allFound = allWindowsFound();
Logger::info(L"Finished moving new windows, all windows found: {}", allFound);
if (!allFound)
if (waitingTime >= maxLaunchingWaitingTime)
{
std::vector<HWND> allWindows = WindowEnumerator::Enumerate(WindowFilter::Filter);
for (HWND window : allWindows)
{
processWindow(window);
}
Logger::info(L"Launching timeout expired");
}
Logger::info(L"Finished moving new windows");
// wait for 3 seconds after all apps launched
waitingTime = 0;
while (!m_launchingStatus.AllLaunchedAndMoved() && waitingTime < maxRepositionWaitingTime)
{
processWindows(true);
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
waitingTime += ms;
}
if (waitingTime >= maxRepositionWaitingTime)
{
Logger::info(L"Repositioning timeout expired");
}
}
@ -146,11 +148,26 @@ WindowArranger::WindowArranger(WorkspacesData::WorkspacesProject project, const
// processWindow(window);
//}
void WindowArranger::processWindows(bool processAll)
{
std::vector<HWND> windows = WindowEnumerator::Enumerate(WindowFilter::Filter);
if (!processAll)
{
std::vector<HWND> windowsDiff{};
std::copy_if(windows.begin(), windows.end(), std::back_inserter(windowsDiff), [&](HWND window) { return std::find(m_windowsBefore.begin(), m_windowsBefore.end(), window) == m_windowsBefore.end(); });
windows = windowsDiff;
}
for (HWND window : windows)
{
processWindow(window);
}
}
void WindowArranger::processWindow(HWND window)
{
// check if this window is already handled
auto windowIter = std::find_if(m_launchingApps.begin(), m_launchingApps.end(), [&](const auto& val) { return val.second.window == window; });
if (windowIter != m_launchingApps.end())
if (m_launchingStatus.IsWindowProcessed(window))
{
return;
}
@ -176,27 +193,34 @@ void WindowArranger::processWindow(HWND window)
return;
}
auto iter = std::find_if(m_launchingApps.begin(), m_launchingApps.end(), [&](const auto& val)
const auto& apps = m_launchingStatus.Get();
auto iter = std::find_if(apps.begin(), apps.end(), [&](const auto& val)
{
return val.second.state == LaunchingState::Waiting && !val.second.window && (val.first.name == data.value().name || val.first.path == data.value().installPath);
return val.second.state == LaunchingState::Launched &&
!val.second.window &&
(val.first.name == data.value().name || val.first.path == data.value().installPath);
});
if (iter == m_launchingApps.end())
if (iter == apps.end())
{
Logger::info(L"A window of {} is not in the project", processPath);
Logger::info(L"Skip {}", processPath);
return;
}
iter->second.window = window;
if (moveWindow(window, iter->first))
{
iter->second.state = LaunchingState::LaunchedAndMoved;
m_launchingStatus.Update(iter->first, window, LaunchingState::LaunchedAndMoved);
}
else
{
iter->second.state = LaunchingState::Failed;
m_launchingStatus.Update(iter->first, window, LaunchingState::Failed);
}
m_ipcHelper.send(WorkspacesData::AppLaunchInfoJSON::ToJson({iter->first, nullptr, iter->second.state}).ToString().c_str());
auto state = m_launchingStatus.Get(iter->first);
if (state.has_value())
{
sendUpdatedState(state.value());
}
}
bool WindowArranger::moveWindow(HWND window, const WorkspacesData::WorkspacesProject::Application& app)
@ -247,9 +271,27 @@ bool WindowArranger::moveWindow(HWND window, const WorkspacesData::WorkspacesPro
}
}
bool WindowArranger::allWindowsFound() const
void WindowArranger::receiveIpcMessage(const std::wstring& message)
{
return std::find_if(m_launchingApps.begin(), m_launchingApps.end(), [&](const std::pair<WorkspacesData::WorkspacesProject::Application, WorkspacesData::LaunchingAppState>& val) {
return val.second.window == nullptr;
}) == m_launchingApps.end();
try
{
auto data = WorkspacesData::AppLaunchInfoJSON::FromJson(json::JsonValue::Parse(message).GetObjectW());
if (data.has_value())
{
m_launchingStatus.Update(data.value().application, data.value().state);
}
else
{
Logger::error(L"Failed to parse message from WorkspacesLauncher");
}
}
catch (const winrt::hresult_error&)
{
Logger::error(L"Failed to parse message from WorkspacesLauncher");
}
}
void WindowArranger::sendUpdatedState(const WorkspacesData::LaunchingAppState& data) const
{
m_ipcHelper.send(WorkspacesData::AppLaunchInfoJSON::ToJson({ data.application, nullptr, data.state }).ToString().c_str());
}

View File

@ -10,7 +10,7 @@
class WindowArranger
{
public:
WindowArranger(WorkspacesData::WorkspacesProject project, const IPCHelper& ipcHelper);
WindowArranger(WorkspacesData::WorkspacesProject project);
~WindowArranger() = default;
private:
@ -19,12 +19,14 @@ private:
const std::vector<WorkspacesData::WorkspacesProject::Monitor> m_monitors;
const Utils::Apps::AppList m_installedApps;
//const WindowCreationHandler m_windowCreationHandler;
const IPCHelper& m_ipcHelper;
WorkspacesData::LaunchingAppStateMap m_launchingApps{};
IPCHelper m_ipcHelper;
LaunchingStatus m_launchingStatus;
//void onWindowCreated(HWND window);
void processWindows(bool processAll);
void processWindow(HWND window);
bool moveWindow(HWND window, const WorkspacesData::WorkspacesProject::Application& app);
bool allWindowsFound() const;
void receiveIpcMessage(const std::wstring& message);
void sendUpdatedState(const WorkspacesData::LaunchingAppState& data) const;
};

View File

@ -90,12 +90,9 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cm
return 1;
}
// IPC
IPCHelper ipc(IPCHelperStrings::WindowArrangerPipeName, IPCHelperStrings::LauncherArrangerPipeName, nullptr);
// arrange windows
Logger::info(L"Arrange windows from Workspace {} : {}", projectToLaunch.name, projectToLaunch.id);
WindowArranger windowArranger(projectToLaunch, ipc);
WindowArranger windowArranger(projectToLaunch);
//run_message_loop();
Logger::debug(L"Arranger finished");