mirror of
https://github.com/microsoft/PowerToys.git
synced 2024-11-23 19:49:17 +08:00
[Workspaces] Sequential launch (#35297)
This commit is contained in:
parent
89ec5be5ba
commit
9994fd7715
@ -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; }
|
||||
|
@ -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; }
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
|
@ -11,5 +11,6 @@ namespace WorkspacesLauncherUI.Data
|
||||
Launched,
|
||||
LaunchedAndMoved,
|
||||
Failed,
|
||||
Canceled,
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -6,5 +6,6 @@ enum class LaunchingState
|
||||
Waiting = 0,
|
||||
Launched,
|
||||
LaunchedAndMoved,
|
||||
Failed
|
||||
Failed,
|
||||
Canceled,
|
||||
};
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -25,6 +25,7 @@ namespace WorkspacesData
|
||||
auto operator<=>(const Position&) const = default;
|
||||
};
|
||||
|
||||
std::wstring id;
|
||||
std::wstring name;
|
||||
std::wstring title;
|
||||
std::wstring path;
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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");
|
||||
|
Loading…
Reference in New Issue
Block a user