diff --git a/src/modules/Workspaces/WorkspacesEditor/Data/ProjectData.cs b/src/modules/Workspaces/WorkspacesEditor/Data/ProjectData.cs index 281ac67abca8..7eb0a63831de 100644 --- a/src/modules/Workspaces/WorkspacesEditor/Data/ProjectData.cs +++ b/src/modules/Workspaces/WorkspacesEditor/Data/ProjectData.cs @@ -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; } diff --git a/src/modules/Workspaces/WorkspacesEditor/Models/Application.cs b/src/modules/Workspaces/WorkspacesEditor/Models/Application.cs index c31087c20c70..294d59854d8b 100644 --- a/src/modules/Workspaces/WorkspacesEditor/Models/Application.cs +++ b/src/modules/Workspaces/WorkspacesEditor/Models/Application.cs @@ -31,6 +31,7 @@ public Application() public Application(Application other) { + Id = other.Id; AppName = other.AppName; AppPath = other.AppPath; AppTitle = other.AppTitle; @@ -95,6 +96,8 @@ public override int GetHashCode() } } + public string Id { get; set; } + public string AppName { get; set; } public string AppPath { get; set; } diff --git a/src/modules/Workspaces/WorkspacesEditor/Models/Project.cs b/src/modules/Workspaces/WorkspacesEditor/Models/Project.cs index a9a6f407ba3c..07d7ad40a138 100644 --- a/src/modules/Workspaces/WorkspacesEditor/Models/Project.cs +++ b/src/modules/Workspaces/WorkspacesEditor/Models/Project.cs @@ -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, diff --git a/src/modules/Workspaces/WorkspacesEditor/Utils/WorkspacesEditorIO.cs b/src/modules/Workspaces/WorkspacesEditor/Utils/WorkspacesEditorIO.cs index da128f2ee4e0..a3a4bc418daf 100644 --- a/src/modules/Workspaces/WorkspacesEditor/Utils/WorkspacesEditorIO.cs +++ b/src/modules/Workspaces/WorkspacesEditor/Utils/WorkspacesEditorIO.cs @@ -97,6 +97,7 @@ public void SerializeWorkspaces(List workspaces, bool useTempFile = fal { wrapper.Applications.Add(new ProjectData.ApplicationWrapper { + Id = app.Id, Application = app.AppName, ApplicationPath = app.AppPath, Title = app.AppTitle, diff --git a/src/modules/Workspaces/WorkspacesLauncher/AppLauncher.cpp b/src/modules/Workspaces/WorkspacesLauncher/AppLauncher.cpp index f9aed31fe66f..3c58c267f9b2 100644 --- a/src/modules/Workspaces/WorkspacesLauncher/AppLauncher.cpp +++ b/src/modules/Workspaces/WorkspacesLauncher/AppLauncher.cpp @@ -10,8 +10,6 @@ #include -#include - #include using namespace winrt; @@ -20,26 +18,6 @@ using namespace Windows::Management::Deployment; namespace AppLauncher { - void UpdatePackagedApps(std::vector& 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 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; - } } \ No newline at end of file diff --git a/src/modules/Workspaces/WorkspacesLauncher/AppLauncher.h b/src/modules/Workspaces/WorkspacesLauncher/AppLauncher.h index 0827afc4c93c..99ddeab6c81b 100644 --- a/src/modules/Workspaces/WorkspacesLauncher/AppLauncher.h +++ b/src/modules/Workspaces/WorkspacesLauncher/AppLauncher.h @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -10,7 +11,6 @@ namespace AppLauncher { using ErrorList = std::vector>; + bool Launch(const WorkspacesData::WorkspacesProject::Application& app, ErrorList& launchErrors); Result LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated); - - bool Launch(WorkspacesData::WorkspacesProject& project, LaunchingStatus& launchingStatus, ErrorList& launchErrors); } diff --git a/src/modules/Workspaces/WorkspacesLauncher/Launcher.cpp b/src/modules/Workspaces/WorkspacesLauncher/Launcher.cpp index b660d0d939c5..6483c8f477c5 100644 --- a/src/modules/Workspaces/WorkspacesLauncher/Launcher.cpp +++ b/src/modules/Workspaces/WorkspacesLauncher/Launcher.cpp @@ -8,6 +8,7 @@ #include #include +#include Launcher::Launcher(const WorkspacesData::WorkspacesProject& project, std::vector& 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()), + m_uiHelper(std::make_unique(std::bind(&Launcher::handleUIMessage, this, std::placeholders::_1))), m_windowArrangerHelper(std::make_unique(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(); + } +} diff --git a/src/modules/Workspaces/WorkspacesLauncher/Launcher.h b/src/modules/Workspaces/WorkspacesLauncher/Launcher.h index 36f17329d27c..9811be25c5bc 100644 --- a/src/modules/Workspaces/WorkspacesLauncher/Launcher.h +++ b/src/modules/Workspaces/WorkspacesLauncher/Launcher.h @@ -14,18 +14,24 @@ class Launcher Launcher(const WorkspacesData::WorkspacesProject& project, std::vector& workspaces, InvokePoint invokePoint); ~Launcher(); - void Launch(); - private: WorkspacesData::WorkspacesProject m_project; std::vector& m_workspaces; const InvokePoint m_invokePoint; const std::chrono::steady_clock::time_point m_start; + std::atomic m_launchedSuccessfully{}; + LaunchingStatus m_launchingStatus; + std::unique_ptr m_uiHelper; + std::mutex m_uiHelperMutex; + std::unique_ptr m_windowArrangerHelper; - LaunchingStatus m_launchingStatus; - bool m_launchedSuccessfully{}; + std::mutex m_windowArrangerHelperMutex; + std::vector> m_launchErrors{}; + std::mutex m_launchErrorsMutex; + void Launch(); void handleWindowArrangerMessage(const std::wstring& msg); + void handleUIMessage(const std::wstring& msg); }; diff --git a/src/modules/Workspaces/WorkspacesLauncher/LauncherUIHelper.cpp b/src/modules/Workspaces/WorkspacesLauncher/LauncherUIHelper.cpp index b35c6d3657f3..8bcccaec9199 100644 --- a/src/modules/Workspaces/WorkspacesLauncher/LauncherUIHelper.cpp +++ b/src/modules/Workspaces/WorkspacesLauncher/LauncherUIHelper.cpp @@ -9,9 +9,9 @@ #include -LauncherUIHelper::LauncherUIHelper() : +LauncherUIHelper::LauncherUIHelper(std::function ipcCallback) : m_processId{}, - m_ipcHelper(IPCHelperStrings::LauncherUIPipeName, IPCHelperStrings::UIPipeName, nullptr) + m_ipcHelper(IPCHelperStrings::LauncherUIPipeName, IPCHelperStrings::UIPipeName, ipcCallback) { } diff --git a/src/modules/Workspaces/WorkspacesLauncher/LauncherUIHelper.h b/src/modules/Workspaces/WorkspacesLauncher/LauncherUIHelper.h index 20704f13a2d6..bb43543e3f12 100644 --- a/src/modules/Workspaces/WorkspacesLauncher/LauncherUIHelper.h +++ b/src/modules/Workspaces/WorkspacesLauncher/LauncherUIHelper.h @@ -6,7 +6,7 @@ class LauncherUIHelper { public: - LauncherUIHelper(); + LauncherUIHelper(std::function ipcCallback); ~LauncherUIHelper(); void LaunchUI(); diff --git a/src/modules/Workspaces/WorkspacesLauncher/WindowArrangerHelper.cpp b/src/modules/Workspaces/WorkspacesLauncher/WindowArrangerHelper.cpp index 600038ea617d..cb2075aba98f 100644 --- a/src/modules/Workspaces/WorkspacesLauncher/WindowArrangerHelper.cpp +++ b/src/modules/Workspaces/WorkspacesLauncher/WindowArrangerHelper.cpp @@ -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()); +} diff --git a/src/modules/Workspaces/WorkspacesLauncher/WindowArrangerHelper.h b/src/modules/Workspaces/WorkspacesLauncher/WindowArrangerHelper.h index 22d7e3ea1cea..8e0ee5d473a0 100644 --- a/src/modules/Workspaces/WorkspacesLauncher/WindowArrangerHelper.h +++ b/src/modules/Workspaces/WorkspacesLauncher/WindowArrangerHelper.h @@ -12,6 +12,7 @@ class WindowArrangerHelper ~WindowArrangerHelper(); void Launch(const std::wstring& projectId, bool elevated, std::function keepWaitingCallback); + void UpdateLaunchStatus(const WorkspacesData::LaunchingAppState& appState) const; private: DWORD m_processId; diff --git a/src/modules/Workspaces/WorkspacesLauncher/main.cpp b/src/modules/Workspaces/WorkspacesLauncher/main.cpp index 9f3d15109796..3e7c8c7db1b6 100644 --- a/src/modules/Workspaces/WorkspacesLauncher/main.cpp +++ b/src/modules/Workspaces/WorkspacesLauncher/main.cpp @@ -13,6 +13,7 @@ #include #include +#include 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"); diff --git a/src/modules/Workspaces/WorkspacesLauncherUI/App.xaml.cs b/src/modules/Workspaces/WorkspacesLauncherUI/App.xaml.cs index a06591852345..64686fc139ec 100644 --- a/src/modules/Workspaces/WorkspacesLauncherUI/App.xaml.cs +++ b/src/modules/Workspaces/WorkspacesLauncherUI/App.xaml.cs @@ -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"); diff --git a/src/modules/Workspaces/WorkspacesLauncherUI/Data/LaunchingState.cs b/src/modules/Workspaces/WorkspacesLauncherUI/Data/LaunchingState.cs index 9ad8c958d25a..67cd951b8b00 100644 --- a/src/modules/Workspaces/WorkspacesLauncherUI/Data/LaunchingState.cs +++ b/src/modules/Workspaces/WorkspacesLauncherUI/Data/LaunchingState.cs @@ -11,5 +11,6 @@ public enum LaunchingState Launched, LaunchedAndMoved, Failed, + Canceled, } } diff --git a/src/modules/Workspaces/WorkspacesLauncherUI/ViewModels/MainViewModel.cs b/src/modules/Workspaces/WorkspacesLauncherUI/ViewModels/MainViewModel.cs index ed6cdd8f3153..7002c9ef0590 100644 --- a/src/modules/Workspaces/WorkspacesLauncherUI/ViewModels/MainViewModel.cs +++ b/src/modules/Workspaces/WorkspacesLauncherUI/ViewModels/MainViewModel.cs @@ -19,7 +19,6 @@ public class MainViewModel : INotifyPropertyChanged, IDisposable private StatusWindow _snapshotWindow; private int launcherProcessID; - private bool _exiting; public event PropertyChangedEventHandler PropertyChanged; @@ -30,8 +29,6 @@ public void OnPropertyChanged(PropertyChangedEventArgs e) public MainViewModel() { - _exiting = false; - // receive IPC Message App.IPCMessageReceivedCallback = (string msg) => { @@ -50,11 +47,6 @@ public MainViewModel() private void HandleAppLaunchingState(AppLaunchData.AppLaunchDataWrapper appLaunchData) { - if (_exiting) - { - return; - } - launcherProcessID = appLaunchData.LauncherProcessID; List appLaunchingList = new List(); foreach (var app in appLaunchData.AppLaunchInfos.AppLaunchInfoList) @@ -90,9 +82,7 @@ internal void SetSnapshotWindow(StatusWindow snapshotWindow) internal void CancelLaunch() { - _exiting = true; - Process proc = Process.GetProcessById(launcherProcessID); - proc.Kill(); + App.SendIPCMessage("cancel"); } } } diff --git a/src/modules/Workspaces/WorkspacesLib/AppUtils.cpp b/src/modules/Workspaces/WorkspacesLib/AppUtils.cpp index 2d69d6f86ffe..9b65b64b296c 100644 --- a/src/modules/Workspaces/WorkspacesLib/AppUtils.cpp +++ b/src/modules/Workspaces/WorkspacesLib/AppUtils.cpp @@ -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; + } + } } \ No newline at end of file diff --git a/src/modules/Workspaces/WorkspacesLib/AppUtils.h b/src/modules/Workspaces/WorkspacesLib/AppUtils.h index 596d7d0a8511..80c88f2eddba 100644 --- a/src/modules/Workspaces/WorkspacesLib/AppUtils.h +++ b/src/modules/Workspaces/WorkspacesLib/AppUtils.h @@ -1,5 +1,7 @@ #pragma once +#include + namespace Utils { namespace Apps @@ -21,5 +23,8 @@ namespace Utils AppList GetAppsList(); std::optional GetApp(const std::wstring& appPath, DWORD pid, const AppList& apps); std::optional GetApp(HWND window, const AppList& apps); + + bool UpdateAppVersion(WorkspacesData::WorkspacesProject::Application& app, const AppList& installedApps); + bool UpdateWorkspacesApps(WorkspacesData::WorkspacesProject& workspace, const AppList& installedApps); } } \ No newline at end of file diff --git a/src/modules/Workspaces/WorkspacesLib/LaunchingStateEnum.h b/src/modules/Workspaces/WorkspacesLib/LaunchingStateEnum.h index 2fbaf2fe9724..ab229a08f2c1 100644 --- a/src/modules/Workspaces/WorkspacesLib/LaunchingStateEnum.h +++ b/src/modules/Workspaces/WorkspacesLib/LaunchingStateEnum.h @@ -6,5 +6,6 @@ enum class LaunchingState Waiting = 0, Launched, LaunchedAndMoved, - Failed + Failed, + Canceled, }; \ No newline at end of file diff --git a/src/modules/Workspaces/WorkspacesLib/LaunchingStatus.cpp b/src/modules/Workspaces/WorkspacesLib/LaunchingStatus.cpp index 46c4bb6e3f4c..f41a872313b8 100644 --- a/src/modules/Workspaces/WorkspacesLib/LaunchingStatus.cpp +++ b/src/modules/Workspaces/WorkspacesLib/LaunchingStatus.cpp @@ -3,8 +3,7 @@ #include -LaunchingStatus::LaunchingStatus(const WorkspacesData::WorkspacesProject& project, std::function updateCallback) : - m_updateCallback(updateCallback) +LaunchingStatus::LaunchingStatus(const WorkspacesData::WorkspacesProject& project) { std::unique_lock lock(m_mutex); for (const auto& app : project.apps) @@ -13,10 +12,18 @@ LaunchingStatus::LaunchingStatus(const WorkspacesData::WorkspacesProject& projec } } -const WorkspacesData::LaunchingAppStateMap& LaunchingStatus::Get() noexcept +bool LaunchingStatus::AllLaunched() noexcept { std::shared_lock lock(m_mutex); - return m_appsState; + for (const auto& [app, data] : m_appsState) + { + if (data.state == LaunchingState::Waiting) + { + return false; + } + } + + return true; } bool LaunchingStatus::AllLaunchedAndMoved() noexcept @@ -24,7 +31,9 @@ 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) + if (data.state != LaunchingState::Failed && + data.state != LaunchingState::Canceled && + data.state != LaunchingState::LaunchedAndMoved) { return false; } @@ -33,20 +42,70 @@ bool LaunchingStatus::AllLaunchedAndMoved() noexcept return true; } -bool LaunchingStatus::AllLaunched() noexcept +bool LaunchingStatus::AllInstancesOfTheAppLaunchedAndMoved(const WorkspacesData::WorkspacesProject::Application& application) noexcept { std::shared_lock lock(m_mutex); - for (const auto& [app, data] : m_appsState) + + for (const auto& [app, state] : m_appsState) { - if (data.state == LaunchingState::Waiting) + if (app.name == application.name || app.path == application.path) { - return false; + 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 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 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) +void LaunchingStatus::Update(const WorkspacesData::WorkspacesProject::Application& app, HWND window, LaunchingState state) +{ + std::unique_lock lock(m_mutex); + if (!m_appsState.contains(app)) { - m_updateCallback(m_appsState); + 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; + } + } +} \ No newline at end of file diff --git a/src/modules/Workspaces/WorkspacesLib/LaunchingStatus.h b/src/modules/Workspaces/WorkspacesLib/LaunchingStatus.h index eec0b1b0f62d..2a0b679f0870 100644 --- a/src/modules/Workspaces/WorkspacesLib/LaunchingStatus.h +++ b/src/modules/Workspaces/WorkspacesLib/LaunchingStatus.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -8,17 +7,24 @@ class LaunchingStatus { public: - LaunchingStatus(const WorkspacesData::WorkspacesProject& project, std::function 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 Get(const WorkspacesData::WorkspacesProject::Application& app) noexcept; + std::optional 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 m_updateCallback; std::shared_mutex m_mutex; }; diff --git a/src/modules/Workspaces/WorkspacesLib/WorkspacesData.cpp b/src/modules/Workspaces/WorkspacesLib/WorkspacesData.cpp index d71619f90b32..8a15001eacc5 100644 --- a/src/modules/Workspaces/WorkspacesLib/WorkspacesData.cpp +++ b/src/modules/Workspaces/WorkspacesLib/WorkspacesData.cpp @@ -3,6 +3,8 @@ #include +#include + 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); diff --git a/src/modules/Workspaces/WorkspacesLib/WorkspacesData.h b/src/modules/Workspaces/WorkspacesLib/WorkspacesData.h index 40252850a33a..d72833799611 100644 --- a/src/modules/Workspaces/WorkspacesLib/WorkspacesData.h +++ b/src/modules/Workspaces/WorkspacesLib/WorkspacesData.h @@ -25,6 +25,7 @@ namespace WorkspacesData auto operator<=>(const Position&) const = default; }; + std::wstring id; std::wstring name; std::wstring title; std::wstring path; diff --git a/src/modules/Workspaces/WorkspacesWindowArranger/WindowArranger.cpp b/src/modules/Workspaces/WorkspacesWindowArranger/WindowArranger.cpp index 18bd2a049f82..5c176c173fc9 100644 --- a/src/modules/Workspaces/WorkspacesWindowArranger/WindowArranger.cpp +++ b/src/modules/Workspaces/WorkspacesWindowArranger/WindowArranger.cpp @@ -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++) + const long maxLaunchingWaitingTime = 10000, maxRepositionWaitingTime = 3000, ms = 300; + long waitingTime{ 0 }; + + // process launching windows + while (!m_launchingStatus.AllLaunched() && waitingTime < maxLaunchingWaitingTime) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + processWindows(false); + std::this_thread::sleep_for(std::chrono::milliseconds(ms)); + waitingTime += ms; + } - std::vector windowsAfter = WindowEnumerator::Enumerate(WindowFilter::Filter); - std::vector 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); - } + if (waitingTime >= maxLaunchingWaitingTime) + { + Logger::info(L"Launching timeout expired"); } - bool allFound = allWindowsFound(); - Logger::info(L"Finished moving new windows, all windows found: {}", allFound); + Logger::info(L"Finished moving new windows"); - if (!allFound) + // wait for 3 seconds after all apps launched + waitingTime = 0; + while (!m_launchingStatus.AllLaunchedAndMoved() && waitingTime < maxRepositionWaitingTime) { - std::vector allWindows = WindowEnumerator::Enumerate(WindowFilter::Filter); - for (HWND window : allWindows) - { - processWindow(window); - } + 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 windows = WindowEnumerator::Enumerate(WindowFilter::Filter); + + if (!processAll) + { + std::vector 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) +{ + 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 { - return std::find_if(m_launchingApps.begin(), m_launchingApps.end(), [&](const std::pair& val) { - return val.second.window == nullptr; - }) == m_launchingApps.end(); + m_ipcHelper.send(WorkspacesData::AppLaunchInfoJSON::ToJson({ data.application, nullptr, data.state }).ToString().c_str()); } diff --git a/src/modules/Workspaces/WorkspacesWindowArranger/WindowArranger.h b/src/modules/Workspaces/WorkspacesWindowArranger/WindowArranger.h index e18b52829b6a..da708e550f9f 100644 --- a/src/modules/Workspaces/WorkspacesWindowArranger/WindowArranger.h +++ b/src/modules/Workspaces/WorkspacesWindowArranger/WindowArranger.h @@ -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 @@ class WindowArranger const std::vector 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; }; diff --git a/src/modules/Workspaces/WorkspacesWindowArranger/main.cpp b/src/modules/Workspaces/WorkspacesWindowArranger/main.cpp index 1c6a72dae5ad..0d952436946a 100644 --- a/src/modules/Workspaces/WorkspacesWindowArranger/main.cpp +++ b/src/modules/Workspaces/WorkspacesWindowArranger/main.cpp @@ -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");