mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-15 11:17:53 +01:00
[Workspaces] Handle admin windows repositioning. (#34965)
This commit is contained in:
1
.github/actions/spell-check/expect.txt
vendored
1
.github/actions/spell-check/expect.txt
vendored
@@ -1861,6 +1861,7 @@ workarounds
|
|||||||
WORKSPACESEDITOR
|
WORKSPACESEDITOR
|
||||||
WORKSPACESLAUNCHER
|
WORKSPACESLAUNCHER
|
||||||
WORKSPACESSNAPSHOTTOOL
|
WORKSPACESSNAPSHOTTOOL
|
||||||
|
WORKSPACESWINDOWARRANGER
|
||||||
wox
|
wox
|
||||||
wparam
|
wparam
|
||||||
wpf
|
wpf
|
||||||
|
|||||||
@@ -194,6 +194,7 @@
|
|||||||
|
|
||||||
"PowerToys.WorkspacesSnapshotTool.exe",
|
"PowerToys.WorkspacesSnapshotTool.exe",
|
||||||
"PowerToys.WorkspacesLauncher.exe",
|
"PowerToys.WorkspacesLauncher.exe",
|
||||||
|
"PowerToys.WorkspacesWindowArranger.exe",
|
||||||
"PowerToys.WorkspacesEditor.exe",
|
"PowerToys.WorkspacesEditor.exe",
|
||||||
"PowerToys.WorkspacesEditor.dll",
|
"PowerToys.WorkspacesEditor.dll",
|
||||||
"PowerToys.WorkspacesLauncherUI.exe",
|
"PowerToys.WorkspacesLauncherUI.exe",
|
||||||
|
|||||||
@@ -619,6 +619,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkspacesEditor", "src\mod
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesLauncher", "src\modules\Workspaces\WorkspacesLauncher\WorkspacesLauncher.vcxproj", "{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}"
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesLauncher", "src\modules\Workspaces\WorkspacesLauncher\WorkspacesLauncher.vcxproj", "{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesWindowArranger", "src\modules\Workspaces\WorkspacesWindowArranger\WorkspacesWindowArranger.vcxproj", "{37D07516-4185-43A4-924F-3C7A5D95ECF6}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|ARM64 = Debug|ARM64
|
Debug|ARM64 = Debug|ARM64
|
||||||
@@ -2719,6 +2721,18 @@ Global
|
|||||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x64.Build.0 = Release|x64
|
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x64.Build.0 = Release|x64
|
||||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x86.ActiveCfg = Release|x64
|
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x86.ActiveCfg = Release|x64
|
||||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x86.Build.0 = Release|x64
|
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x86.Build.0 = Release|x64
|
||||||
|
{37D07516-4185-43A4-924F-3C7A5D95ECF6}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
|
{37D07516-4185-43A4-924F-3C7A5D95ECF6}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
|
{37D07516-4185-43A4-924F-3C7A5D95ECF6}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{37D07516-4185-43A4-924F-3C7A5D95ECF6}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{37D07516-4185-43A4-924F-3C7A5D95ECF6}.Debug|x86.ActiveCfg = Debug|x64
|
||||||
|
{37D07516-4185-43A4-924F-3C7A5D95ECF6}.Debug|x86.Build.0 = Debug|x64
|
||||||
|
{37D07516-4185-43A4-924F-3C7A5D95ECF6}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
|
{37D07516-4185-43A4-924F-3C7A5D95ECF6}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
|
{37D07516-4185-43A4-924F-3C7A5D95ECF6}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{37D07516-4185-43A4-924F-3C7A5D95ECF6}.Release|x64.Build.0 = Release|x64
|
||||||
|
{37D07516-4185-43A4-924F-3C7A5D95ECF6}.Release|x86.ActiveCfg = Release|x64
|
||||||
|
{37D07516-4185-43A4-924F-3C7A5D95ECF6}.Release|x86.Build.0 = Release|x64
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -2946,6 +2960,7 @@ Global
|
|||||||
{3D63307B-9D27-44FD-B033-B26F39245B85} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
{3D63307B-9D27-44FD-B033-B26F39245B85} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||||
{367D7543-7DBA-4381-99F1-BF6142A996C4} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
{367D7543-7DBA-4381-99F1-BF6142A996C4} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||||
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||||
|
{37D07516-4185-43A4-924F-3C7A5D95ECF6} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
|
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
|
||||||
|
|||||||
@@ -1223,7 +1223,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
|
|||||||
}
|
}
|
||||||
processes.resize(bytes / sizeof(processes[0]));
|
processes.resize(bytes / sizeof(processes[0]));
|
||||||
|
|
||||||
std::array<std::wstring_view, 36> processesToTerminate = {
|
std::array<std::wstring_view, 37> processesToTerminate = {
|
||||||
L"PowerToys.PowerLauncher.exe",
|
L"PowerToys.PowerLauncher.exe",
|
||||||
L"PowerToys.Settings.exe",
|
L"PowerToys.Settings.exe",
|
||||||
L"PowerToys.AdvancedPaste.exe",
|
L"PowerToys.AdvancedPaste.exe",
|
||||||
@@ -1259,6 +1259,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
|
|||||||
L"PowerToys.WorkspacesLauncher.exe",
|
L"PowerToys.WorkspacesLauncher.exe",
|
||||||
L"PowerToys.WorkspacesLauncherUI.exe",
|
L"PowerToys.WorkspacesLauncherUI.exe",
|
||||||
L"PowerToys.WorkspacesEditor.exe",
|
L"PowerToys.WorkspacesEditor.exe",
|
||||||
|
L"PowerToys.WorkspacesWindowArranger.exe",
|
||||||
L"PowerToys.exe",
|
L"PowerToys.exe",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ struct LogSettings
|
|||||||
inline const static std::string newLoggerName = "NewPlus";
|
inline const static std::string newLoggerName = "NewPlus";
|
||||||
inline const static std::string workspacesLauncherLoggerName = "workspaces-launcher";
|
inline const static std::string workspacesLauncherLoggerName = "workspaces-launcher";
|
||||||
inline const static std::wstring workspacesLauncherLogPath = L"workspaces-launcher-log.txt";
|
inline const static std::wstring workspacesLauncherLogPath = L"workspaces-launcher-log.txt";
|
||||||
|
inline const static std::string workspacesWindowArrangerLoggerName = "workspaces-window-arranger";
|
||||||
|
inline const static std::wstring workspacesWindowArrangerLogPath = L"workspaces-window-arranger-log.txt";
|
||||||
inline const static std::string workspacesSnapshotToolLoggerName = "workspaces-snapshot-tool";
|
inline const static std::string workspacesSnapshotToolLoggerName = "workspaces-snapshot-tool";
|
||||||
inline const static std::wstring workspacesSnapshotToolLogPath = L"workspaces-snapshot-tool-log.txt";
|
inline const static std::wstring workspacesSnapshotToolLogPath = L"workspaces-snapshot-tool-log.txt";
|
||||||
inline const static int retention = 30;
|
inline const static int retention = 30;
|
||||||
|
|||||||
@@ -1,150 +1,27 @@
|
|||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "AppLauncher.h"
|
#include "AppLauncher.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include <shellapi.h>
|
||||||
|
|
||||||
#include <winrt/Windows.Management.Deployment.h>
|
#include <winrt/Windows.Management.Deployment.h>
|
||||||
#include <winrt/Windows.ApplicationModel.Core.h>
|
#include <winrt/Windows.ApplicationModel.Core.h>
|
||||||
|
|
||||||
#include <shellapi.h>
|
#include <common/utils/winapi_error.h>
|
||||||
#include <ShellScalingApi.h>
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
|
|
||||||
#include <workspaces-common/MonitorEnumerator.h>
|
|
||||||
#include <workspaces-common/WindowEnumerator.h>
|
|
||||||
#include <workspaces-common/WindowFilter.h>
|
|
||||||
|
|
||||||
#include <WorkspacesLib/AppUtils.h>
|
#include <WorkspacesLib/AppUtils.h>
|
||||||
|
|
||||||
#include <common/Display/dpi_aware.h>
|
|
||||||
#include <common/utils/winapi_error.h>
|
|
||||||
|
|
||||||
#include <LaunchingApp.h>
|
|
||||||
#include <LauncherUIHelper.h>
|
|
||||||
#include <RegistryUtils.h>
|
#include <RegistryUtils.h>
|
||||||
#include <WindowProperties/WorkspacesWindowPropertyUtils.h>
|
|
||||||
|
|
||||||
using namespace winrt;
|
using namespace winrt;
|
||||||
using namespace Windows::Foundation;
|
using namespace Windows::Foundation;
|
||||||
using namespace Windows::Management::Deployment;
|
using namespace Windows::Management::Deployment;
|
||||||
|
|
||||||
namespace FancyZones
|
namespace AppLauncher
|
||||||
{
|
{
|
||||||
inline bool allMonitorsHaveSameDpiScaling()
|
void UpdatePackagedApps(std::vector<WorkspacesData::WorkspacesProject::Application>& apps, const Utils::Apps::AppList& installedApps)
|
||||||
{
|
{
|
||||||
auto monitors = MonitorEnumerator::Enumerate();
|
|
||||||
if (monitors.size() < 2)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
UINT firstMonitorDpiX;
|
|
||||||
UINT firstMonitorDpiY;
|
|
||||||
|
|
||||||
if (S_OK != GetDpiForMonitor(monitors[0].first, MDT_EFFECTIVE_DPI, &firstMonitorDpiX, &firstMonitorDpiY))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 1; i < monitors.size(); i++)
|
|
||||||
{
|
|
||||||
UINT iteratedMonitorDpiX;
|
|
||||||
UINT iteratedMonitorDpiY;
|
|
||||||
|
|
||||||
if (S_OK != GetDpiForMonitor(monitors[i].first, MDT_EFFECTIVE_DPI, &iteratedMonitorDpiX, &iteratedMonitorDpiY) ||
|
|
||||||
iteratedMonitorDpiX != firstMonitorDpiX)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void ScreenToWorkAreaCoords(HWND window, HMONITOR monitor, RECT& rect)
|
|
||||||
{
|
|
||||||
MONITORINFOEXW monitorInfo{ sizeof(MONITORINFOEXW) };
|
|
||||||
GetMonitorInfoW(monitor, &monitorInfo);
|
|
||||||
|
|
||||||
auto xOffset = monitorInfo.rcWork.left - monitorInfo.rcMonitor.left;
|
|
||||||
auto yOffset = monitorInfo.rcWork.top - monitorInfo.rcMonitor.top;
|
|
||||||
|
|
||||||
DPIAware::Convert(monitor, rect);
|
|
||||||
|
|
||||||
auto referenceRect = RECT(rect.left - xOffset, rect.top - yOffset, rect.right - xOffset, rect.bottom - yOffset);
|
|
||||||
|
|
||||||
// Now, this rect should be used to determine the monitor and thus taskbar size. This fixes
|
|
||||||
// scenarios where the zone lies approximately between two monitors, and the taskbar is on the left.
|
|
||||||
monitor = MonitorFromRect(&referenceRect, MONITOR_DEFAULTTOPRIMARY);
|
|
||||||
GetMonitorInfoW(monitor, &monitorInfo);
|
|
||||||
|
|
||||||
xOffset = monitorInfo.rcWork.left - monitorInfo.rcMonitor.left;
|
|
||||||
yOffset = monitorInfo.rcWork.top - monitorInfo.rcMonitor.top;
|
|
||||||
|
|
||||||
rect.left -= xOffset;
|
|
||||||
rect.right -= xOffset;
|
|
||||||
rect.top -= yOffset;
|
|
||||||
rect.bottom -= yOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool SizeWindowToRect(HWND window, HMONITOR monitor, bool isMinimized, bool isMaximized, RECT rect) noexcept
|
|
||||||
{
|
|
||||||
WINDOWPLACEMENT placement{};
|
|
||||||
::GetWindowPlacement(window, &placement);
|
|
||||||
|
|
||||||
if (isMinimized)
|
|
||||||
{
|
|
||||||
placement.showCmd = SW_MINIMIZE;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if ((placement.showCmd != SW_SHOWMINIMIZED) &&
|
|
||||||
(placement.showCmd != SW_MINIMIZE))
|
|
||||||
{
|
|
||||||
if (placement.showCmd == SW_SHOWMAXIMIZED)
|
|
||||||
placement.flags &= ~WPF_RESTORETOMAXIMIZED;
|
|
||||||
|
|
||||||
placement.showCmd = SW_RESTORE;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScreenToWorkAreaCoords(window, monitor, rect);
|
|
||||||
placement.rcNormalPosition = rect;
|
|
||||||
}
|
|
||||||
|
|
||||||
placement.flags |= WPF_ASYNCWINDOWPLACEMENT;
|
|
||||||
|
|
||||||
auto result = ::SetWindowPlacement(window, &placement);
|
|
||||||
if (!result)
|
|
||||||
{
|
|
||||||
Logger::error(L"SetWindowPlacement failed, {}", get_last_error_or_default(GetLastError()));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure window is moved to the correct monitor before maximize.
|
|
||||||
if (isMaximized)
|
|
||||||
{
|
|
||||||
placement.showCmd = SW_SHOWMAXIMIZED;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do it again, allowing Windows to resize the window and set correct scaling
|
|
||||||
// This fixes Issue #365
|
|
||||||
result = ::SetWindowPlacement(window, &placement);
|
|
||||||
if (!result)
|
|
||||||
{
|
|
||||||
Logger::error(L"SetWindowPlacement failed, {}", get_last_error_or_default(GetLastError()));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
LaunchingApps Prepare(std::vector<WorkspacesData::WorkspacesProject::Application>& apps, const Utils::Apps::AppList& installedApps)
|
|
||||||
{
|
|
||||||
LaunchingApps launchedApps{};
|
|
||||||
launchedApps.reserve(apps.size());
|
|
||||||
|
|
||||||
for (auto& app : apps)
|
for (auto& app : apps)
|
||||||
{
|
{
|
||||||
// Packaged apps have version in the path, it will be outdated after update.
|
// Packaged apps have version in the path, it will be outdated after update.
|
||||||
@@ -160,322 +37,173 @@ namespace
|
|||||||
Logger::trace(L"Updated package full name for {}: {}", app.name, app.packageFullName);
|
Logger::trace(L"Updated package full name for {}: {}", app.name, app.packageFullName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
launchedApps.push_back({ app, nullptr, L"waiting" });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return launchedApps;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AllWindowsFound(const LaunchingApps& launchedApps)
|
Result<SHELLEXECUTEINFO, std::wstring> LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated)
|
||||||
{
|
{
|
||||||
return std::find_if(launchedApps.begin(), launchedApps.end(), [&](const LaunchingApp& val) {
|
std::wstring dir = std::filesystem::path(appPath).parent_path();
|
||||||
return val.window == nullptr;
|
|
||||||
}) == launchedApps.end();
|
|
||||||
};
|
|
||||||
|
|
||||||
bool AddOpenedWindows(LaunchingApps& launchedApps, const std::vector<HWND>& windows, const Utils::Apps::AppList& installedApps)
|
SHELLEXECUTEINFO sei = { 0 };
|
||||||
{
|
sei.cbSize = sizeof(SHELLEXECUTEINFO);
|
||||||
bool statusChanged = false;
|
sei.hwnd = nullptr;
|
||||||
for (HWND window : windows)
|
sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;
|
||||||
|
sei.lpVerb = elevated ? L"runas" : L"open";
|
||||||
|
sei.lpFile = appPath.c_str();
|
||||||
|
sei.lpParameters = commandLineArgs.c_str();
|
||||||
|
sei.lpDirectory = dir.c_str();
|
||||||
|
sei.nShow = SW_SHOWNORMAL;
|
||||||
|
|
||||||
|
if (!ShellExecuteEx(&sei))
|
||||||
{
|
{
|
||||||
auto installedAppData = Utils::Apps::GetApp(window, installedApps);
|
std::wstring error = get_last_error_or_default(GetLastError());
|
||||||
if (!installedAppData.has_value())
|
Logger::error(L"Failed to launch process. {}", error);
|
||||||
{
|
return Error(error);
|
||||||
continue;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
auto insertionIter = launchedApps.end();
|
return Ok(sei);
|
||||||
for (auto iter = launchedApps.begin(); iter != launchedApps.end(); ++iter)
|
}
|
||||||
|
|
||||||
|
bool LaunchPackagedApp(const std::wstring& packageFullName, ErrorList& launchErrors)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PackageManager packageManager;
|
||||||
|
for (const auto& package : packageManager.FindPackagesForUser({}))
|
||||||
{
|
{
|
||||||
if (iter->window == nullptr && installedAppData.value().name == iter->application.name)
|
if (package.Id().FullName() == packageFullName)
|
||||||
{
|
{
|
||||||
insertionIter = iter;
|
auto getAppListEntriesOperation = package.GetAppListEntriesAsync();
|
||||||
|
auto appEntries = getAppListEntriesOperation.get();
|
||||||
|
|
||||||
|
if (appEntries.Size() > 0)
|
||||||
|
{
|
||||||
|
IAsyncOperation<bool> launchOperation = appEntries.GetAt(0).LaunchAsync();
|
||||||
|
bool launchResult = launchOperation.get();
|
||||||
|
return launchResult;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::error(L"No app entries found for the package.");
|
||||||
|
launchErrors.push_back({ packageFullName, L"No app entries found for the package." });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// keep the window at the same position if it's already opened
|
|
||||||
WINDOWPLACEMENT placement{};
|
|
||||||
::GetWindowPlacement(window, &placement);
|
|
||||||
HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTOPRIMARY);
|
|
||||||
UINT dpi = DPIAware::DEFAULT_DPI;
|
|
||||||
DPIAware::GetScreenDPIForMonitor(monitor, dpi);
|
|
||||||
|
|
||||||
float x = static_cast<float>(placement.rcNormalPosition.left);
|
|
||||||
float y = static_cast<float>(placement.rcNormalPosition.top);
|
|
||||||
float width = static_cast<float>(placement.rcNormalPosition.right - placement.rcNormalPosition.left);
|
|
||||||
float height = static_cast<float>(placement.rcNormalPosition.bottom - placement.rcNormalPosition.top);
|
|
||||||
|
|
||||||
DPIAware::InverseConvert(monitor, x, y);
|
|
||||||
DPIAware::InverseConvert(monitor, width, height);
|
|
||||||
|
|
||||||
WorkspacesData::WorkspacesProject::Application::Position windowPosition{
|
|
||||||
.x = static_cast<int>(std::round(x)),
|
|
||||||
.y = static_cast<int>(std::round(y)),
|
|
||||||
.width = static_cast<int>(std::round(width)),
|
|
||||||
.height = static_cast<int>(std::round(height)),
|
|
||||||
};
|
|
||||||
if (iter->application.position == windowPosition)
|
|
||||||
{
|
|
||||||
Logger::debug(L"{} window already found at {} {}.", iter->application.name, iter->application.position.x, iter->application.position.y);
|
|
||||||
insertionIter = iter;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (insertionIter != launchedApps.end())
|
|
||||||
{
|
|
||||||
insertionIter->window = window;
|
|
||||||
insertionIter->state = L"launched";
|
|
||||||
statusChanged = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (AllWindowsFound(launchedApps))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return statusChanged;
|
catch (const hresult_error& ex)
|
||||||
}
|
{
|
||||||
}
|
Logger::error(L"Packaged app launching error: {}", ex.message());
|
||||||
|
launchErrors.push_back({ packageFullName, ex.message().c_str() });
|
||||||
|
}
|
||||||
|
|
||||||
bool LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated, ErrorList& launchErrors)
|
|
||||||
{
|
|
||||||
std::wstring dir = std::filesystem::path(appPath).parent_path();
|
|
||||||
|
|
||||||
SHELLEXECUTEINFO sei = { 0 };
|
|
||||||
sei.cbSize = sizeof(SHELLEXECUTEINFO);
|
|
||||||
sei.hwnd = nullptr;
|
|
||||||
sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;
|
|
||||||
sei.lpVerb = elevated ? L"runas" : L"open";
|
|
||||||
sei.lpFile = appPath.c_str();
|
|
||||||
sei.lpParameters = commandLineArgs.c_str();
|
|
||||||
sei.lpDirectory = dir.c_str();
|
|
||||||
sei.nShow = SW_SHOWNORMAL;
|
|
||||||
|
|
||||||
if (!ShellExecuteEx(&sei))
|
|
||||||
{
|
|
||||||
auto error = GetLastError();
|
|
||||||
Logger::error(L"Failed to launch process. {}", get_last_error_or_default(error));
|
|
||||||
launchErrors.push_back({ std::filesystem::path(appPath).filename(), get_last_error_or_default(error) });
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
bool Launch(const WorkspacesData::WorkspacesProject::Application& app, ErrorList& launchErrors)
|
||||||
}
|
|
||||||
|
|
||||||
bool LaunchPackagedApp(const std::wstring& packageFullName, ErrorList& launchErrors)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
PackageManager packageManager;
|
bool launched{ false };
|
||||||
for (const auto& package : packageManager.FindPackagesForUser({}))
|
|
||||||
{
|
|
||||||
if (package.Id().FullName() == packageFullName)
|
|
||||||
{
|
|
||||||
auto getAppListEntriesOperation = package.GetAppListEntriesAsync();
|
|
||||||
auto appEntries = getAppListEntriesOperation.get();
|
|
||||||
|
|
||||||
if (appEntries.Size() > 0)
|
// packaged apps: check protocol in registry
|
||||||
|
// usage example: Settings with cmd args
|
||||||
|
if (!app.packageFullName.empty())
|
||||||
|
{
|
||||||
|
auto names = RegistryUtils::GetUriProtocolNames(app.packageFullName);
|
||||||
|
if (!names.empty())
|
||||||
|
{
|
||||||
|
Logger::trace(L"Launching packaged by protocol with command line args {}", app.name);
|
||||||
|
|
||||||
|
std::wstring uriProtocolName = names[0];
|
||||||
|
std::wstring command = std::wstring(uriProtocolName + (app.commandLineArgs.starts_with(L":") ? L"" : L":") + app.commandLineArgs);
|
||||||
|
|
||||||
|
auto res = LaunchApp(command, L"", app.isElevated);
|
||||||
|
if (res.isOk())
|
||||||
{
|
{
|
||||||
IAsyncOperation<bool> launchOperation = appEntries.GetAt(0).LaunchAsync();
|
launched = true;
|
||||||
bool launchResult = launchOperation.get();
|
|
||||||
return launchResult;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger::error(L"No app entries found for the package.");
|
launchErrors.push_back({ std::filesystem::path(app.path).filename(), res.error() });
|
||||||
launchErrors.push_back({ packageFullName, L"No app entries found for the package." });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
}
|
|
||||||
catch (const hresult_error& ex)
|
|
||||||
{
|
|
||||||
Logger::error(L"Packaged app launching error: {}", ex.message());
|
|
||||||
launchErrors.push_back({ packageFullName, ex.message().c_str() });
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Launch(const WorkspacesData::WorkspacesProject::Application& app, ErrorList& launchErrors)
|
|
||||||
{
|
|
||||||
bool launched{ false };
|
|
||||||
|
|
||||||
// packaged apps: check protocol in registry
|
|
||||||
// usage example: Settings with cmd args
|
|
||||||
if (!app.packageFullName.empty())
|
|
||||||
{
|
|
||||||
auto names = RegistryUtils::GetUriProtocolNames(app.packageFullName);
|
|
||||||
if (!names.empty())
|
|
||||||
{
|
|
||||||
Logger::trace(L"Launching packaged by protocol with command line args {}", app.name);
|
|
||||||
|
|
||||||
std::wstring uriProtocolName = names[0];
|
|
||||||
std::wstring command = std::wstring(uriProtocolName + (app.commandLineArgs.starts_with(L":") ? L"" : L":") + app.commandLineArgs);
|
|
||||||
|
|
||||||
launched = LaunchApp(command, L"", app.isElevated, launchErrors);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger::info(L"Uri protocol names not found for {}", app.packageFullName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// packaged apps: try launching first by AppUserModel.ID
|
|
||||||
// usage example: elevated Terminal
|
|
||||||
if (!launched && !app.appUserModelId.empty() && !app.packageFullName.empty())
|
|
||||||
{
|
|
||||||
Logger::trace(L"Launching {} as {}", app.name, app.appUserModelId);
|
|
||||||
launched = LaunchApp(L"shell:AppsFolder\\" + app.appUserModelId, app.commandLineArgs, app.isElevated, launchErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
// packaged apps: try launching by package full name
|
|
||||||
// doesn't work for elevated apps or apps with command line args
|
|
||||||
if (!launched && !app.packageFullName.empty() && app.commandLineArgs.empty() && !app.isElevated)
|
|
||||||
{
|
|
||||||
Logger::trace(L"Launching packaged app {}", app.name);
|
|
||||||
launched = LaunchPackagedApp(app.packageFullName, launchErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!launched)
|
|
||||||
{
|
|
||||||
Logger::trace(L"Launching {} at {}", app.name, app.path);
|
|
||||||
|
|
||||||
DWORD dwAttrib = GetFileAttributesW(app.path.c_str());
|
|
||||||
if (dwAttrib == INVALID_FILE_ATTRIBUTES)
|
|
||||||
{
|
|
||||||
Logger::error(L"File not found at {}", app.path);
|
|
||||||
launchErrors.push_back({ std::filesystem::path(app.path).filename(), L"File not found" });
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
launched = LaunchApp(app.path, app.commandLineArgs, app.isElevated, launchErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger::trace(L"{} {} at {}", app.name, (launched ? L"launched" : L"not launched"), app.path);
|
|
||||||
return launched;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Launch(WorkspacesData::WorkspacesProject& project, const std::vector<WorkspacesData::WorkspacesProject::Monitor>& monitors, ErrorList& launchErrors)
|
|
||||||
{
|
|
||||||
bool launchedSuccessfully{ true };
|
|
||||||
|
|
||||||
LauncherUIHelper uiHelper;
|
|
||||||
uiHelper.LaunchUI();
|
|
||||||
|
|
||||||
// Get the set of windows before launching the app
|
|
||||||
std::vector<HWND> windowsBefore = WindowEnumerator::Enumerate(WindowFilter::Filter);
|
|
||||||
auto installedApps = Utils::Apps::GetAppsList();
|
|
||||||
auto launchedApps = Prepare(project.apps, installedApps);
|
|
||||||
|
|
||||||
uiHelper.UpdateLaunchStatus(launchedApps);
|
|
||||||
|
|
||||||
// Launch apps
|
|
||||||
for (auto& app : launchedApps)
|
|
||||||
{
|
|
||||||
if (!app.window)
|
|
||||||
{
|
|
||||||
if (!Launch(app.application, launchErrors))
|
|
||||||
{
|
{
|
||||||
Logger::error(L"Failed to launch {}", app.application.name);
|
Logger::info(L"Uri protocol names not found for {}", app.packageFullName);
|
||||||
app.state = L"failed";
|
|
||||||
uiHelper.UpdateLaunchStatus(launchedApps);
|
|
||||||
launchedSuccessfully = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// packaged apps: try launching first by AppUserModel.ID
|
||||||
|
// usage example: elevated Terminal
|
||||||
|
if (!launched && !app.appUserModelId.empty() && !app.packageFullName.empty())
|
||||||
|
{
|
||||||
|
Logger::trace(L"Launching {} as {}", app.name, app.appUserModelId);
|
||||||
|
auto res = LaunchApp(L"shell:AppsFolder\\" + app.appUserModelId, app.commandLineArgs, app.isElevated);
|
||||||
|
if (res.isOk())
|
||||||
|
{
|
||||||
|
launched = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
launchErrors.push_back({ std::filesystem::path(app.path).filename(), res.error() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// packaged apps: try launching by package full name
|
||||||
|
// doesn't work for elevated apps or apps with command line args
|
||||||
|
if (!launched && !app.packageFullName.empty() && app.commandLineArgs.empty() && !app.isElevated)
|
||||||
|
{
|
||||||
|
Logger::trace(L"Launching packaged app {}", app.name);
|
||||||
|
launched = LaunchPackagedApp(app.packageFullName, launchErrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!launched)
|
||||||
|
{
|
||||||
|
Logger::trace(L"Launching {} at {}", app.name, app.path);
|
||||||
|
|
||||||
|
DWORD dwAttrib = GetFileAttributesW(app.path.c_str());
|
||||||
|
if (dwAttrib == INVALID_FILE_ATTRIBUTES)
|
||||||
|
{
|
||||||
|
Logger::error(L"File not found at {}", app.path);
|
||||||
|
launchErrors.push_back({ std::filesystem::path(app.path).filename(), L"File not found" });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto res = LaunchApp(app.path, app.commandLineArgs, app.isElevated);
|
||||||
|
if (res.isOk())
|
||||||
|
{
|
||||||
|
launched = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
launchErrors.push_back({ std::filesystem::path(app.path).filename(), res.error() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::trace(L"{} {} at {}", app.name, (launched ? L"launched" : L"not launched"), app.path);
|
||||||
|
return launched;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get newly opened windows after launching apps, keep retrying for 5 seconds
|
bool Launch(WorkspacesData::WorkspacesProject& project, LaunchingStatus& launchingStatus, ErrorList& launchErrors)
|
||||||
Logger::trace(L"Find new windows");
|
|
||||||
for (int attempt = 0; attempt < 50 && !AllWindowsFound(launchedApps); attempt++)
|
|
||||||
{
|
{
|
||||||
std::vector<HWND> windowsAfter = WindowEnumerator::Enumerate(WindowFilter::Filter);
|
bool launchedSuccessfully{ true };
|
||||||
std::vector<HWND> windowsDiff{};
|
|
||||||
std::copy_if(windowsAfter.begin(), windowsAfter.end(), std::back_inserter(windowsDiff), [&](HWND window) { return std::find(windowsBefore.begin(), windowsBefore.end(), window) == windowsBefore.end(); });
|
auto installedApps = Utils::Apps::GetAppsList();
|
||||||
if (AddOpenedWindows(launchedApps, windowsDiff, installedApps))
|
UpdatePackagedApps(project.apps, installedApps);
|
||||||
|
|
||||||
|
// Launch apps
|
||||||
|
for (auto& app : project.apps)
|
||||||
{
|
{
|
||||||
uiHelper.UpdateLaunchStatus(launchedApps);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if all windows were found
|
return launchedSuccessfully;
|
||||||
if (AllWindowsFound(launchedApps))
|
|
||||||
{
|
|
||||||
Logger::trace(L"All windows found.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger::trace(L"Not all windows found, retry.");
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Check single-instance app windows
|
|
||||||
Logger::trace(L"Find single-instance app windows");
|
|
||||||
if (!AllWindowsFound(launchedApps))
|
|
||||||
{
|
|
||||||
if (AddOpenedWindows(launchedApps, WindowEnumerator::Enumerate(WindowFilter::Filter), installedApps))
|
|
||||||
{
|
|
||||||
uiHelper.UpdateLaunchStatus(launchedApps);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Place windows
|
|
||||||
for (const auto& [app, window, status] : launchedApps)
|
|
||||||
{
|
|
||||||
if (window == nullptr)
|
|
||||||
{
|
|
||||||
Logger::warn(L"{} window not found.", app.name);
|
|
||||||
launchedSuccessfully = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto snapMonitorIter = std::find_if(project.monitors.begin(), project.monitors.end(), [&](const WorkspacesData::WorkspacesProject::Monitor& val) { return val.number == app.monitor; });
|
|
||||||
if (snapMonitorIter == project.monitors.end())
|
|
||||||
{
|
|
||||||
Logger::error(L"No monitor saved for launching the app");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool launchMinimized = app.isMinimized;
|
|
||||||
bool launchMaximized = app.isMaximized;
|
|
||||||
|
|
||||||
HMONITOR currentMonitor{};
|
|
||||||
UINT currentDpi = DPIAware::DEFAULT_DPI;
|
|
||||||
auto currentMonitorIter = std::find_if(monitors.begin(), monitors.end(), [&](const WorkspacesData::WorkspacesProject::Monitor& val) { return val.number == app.monitor; });
|
|
||||||
if (currentMonitorIter != monitors.end())
|
|
||||||
{
|
|
||||||
currentMonitor = currentMonitorIter->monitor;
|
|
||||||
currentDpi = currentMonitorIter->dpi;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
currentMonitor = MonitorFromPoint(POINT{ 0, 0 }, MONITOR_DEFAULTTOPRIMARY);
|
|
||||||
DPIAware::GetScreenDPIForMonitor(currentMonitor, currentDpi);
|
|
||||||
launchMinimized = true;
|
|
||||||
launchMaximized = false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
RECT rect = app.position.toRect();
|
|
||||||
float mult = static_cast<float>(snapMonitorIter->dpi) / currentDpi;
|
|
||||||
rect.left = static_cast<long>(std::round(rect.left * mult));
|
|
||||||
rect.right = static_cast<long>(std::round(rect.right * mult));
|
|
||||||
rect.top = static_cast<long>(std::round(rect.top * mult));
|
|
||||||
rect.bottom = static_cast<long>(std::round(rect.bottom * mult));
|
|
||||||
|
|
||||||
if (FancyZones::SizeWindowToRect(window, currentMonitor, launchMinimized, launchMaximized, rect))
|
|
||||||
{
|
|
||||||
WorkspacesWindowProperties::StampWorkspacesLaunchedProperty(window);
|
|
||||||
Logger::trace(L"Placed {} to ({},{}) [{}x{}]", app.name, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger::error(L"Failed placing {}", app.name);
|
|
||||||
launchedSuccessfully = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return launchedSuccessfully;
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,16 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <shellapi.h>
|
||||||
|
|
||||||
|
#include <WorkspacesLib/LaunchingStatus.h>
|
||||||
|
#include <WorkspacesLib/Result.h>
|
||||||
#include <WorkspacesLib/WorkspacesData.h>
|
#include <WorkspacesLib/WorkspacesData.h>
|
||||||
|
|
||||||
using ErrorList = std::vector<std::pair<std::wstring, std::wstring>>;
|
namespace AppLauncher
|
||||||
|
{
|
||||||
|
using ErrorList = std::vector<std::pair<std::wstring, std::wstring>>;
|
||||||
|
|
||||||
bool Launch(WorkspacesData::WorkspacesProject& project, const std::vector<WorkspacesData::WorkspacesProject::Monitor>& monitors, 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);
|
||||||
|
}
|
||||||
|
|||||||
123
src/modules/Workspaces/WorkspacesLauncher/Launcher.cpp
Normal file
123
src/modules/Workspaces/WorkspacesLauncher/Launcher.cpp
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "Launcher.h"
|
||||||
|
|
||||||
|
#include <common/utils/json.h>
|
||||||
|
|
||||||
|
#include <workspaces-common/MonitorUtils.h>
|
||||||
|
|
||||||
|
#include <WorkspacesLib/trace.h>
|
||||||
|
|
||||||
|
#include <AppLauncher.h>
|
||||||
|
|
||||||
|
Launcher::Launcher(const WorkspacesData::WorkspacesProject& project,
|
||||||
|
std::vector<WorkspacesData::WorkspacesProject>& workspaces,
|
||||||
|
InvokePoint invokePoint) :
|
||||||
|
m_project(project),
|
||||||
|
m_workspaces(workspaces),
|
||||||
|
m_invokePoint(invokePoint),
|
||||||
|
m_start(std::chrono::high_resolution_clock::now()),
|
||||||
|
m_uiHelper(std::make_unique<LauncherUIHelper>()),
|
||||||
|
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_uiHelper->LaunchUI();
|
||||||
|
m_uiHelper->UpdateLaunchStatus(m_launchingStatus.Get());
|
||||||
|
|
||||||
|
bool launchElevated = std::find_if(m_project.apps.begin(), m_project.apps.end(), [](const WorkspacesData::WorkspacesProject::Application& app) { return app.isElevated; }) != m_project.apps.end();
|
||||||
|
m_windowArrangerHelper->Launch(m_project.id, launchElevated, [&]() -> bool
|
||||||
|
{
|
||||||
|
if (m_launchingStatus.AllLaunchedAndMoved())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_launchingStatus.AllLaunched())
|
||||||
|
{
|
||||||
|
static auto arrangerTimeDelay = std::chrono::high_resolution_clock::now();
|
||||||
|
auto currentTime = std::chrono::high_resolution_clock::now();
|
||||||
|
std::chrono::duration<double> timeDiff = currentTime - arrangerTimeDelay;
|
||||||
|
if (timeDiff.count() >= 5)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Launcher::~Launcher()
|
||||||
|
{
|
||||||
|
Logger::trace(L"Finalizing launch");
|
||||||
|
|
||||||
|
// update last-launched time
|
||||||
|
if (m_invokePoint != InvokePoint::LaunchAndEdit)
|
||||||
|
{
|
||||||
|
time_t launchedTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||||
|
m_project.lastLaunchedTime = launchedTime;
|
||||||
|
for (int i = 0; i < m_workspaces.size(); i++)
|
||||||
|
{
|
||||||
|
if (m_workspaces[i].id == m_project.id)
|
||||||
|
{
|
||||||
|
m_workspaces[i] = m_project;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
json::to_file(WorkspacesData::WorkspacesFile(), WorkspacesData::WorkspacesListJSON::ToJson(m_workspaces));
|
||||||
|
}
|
||||||
|
|
||||||
|
// telemetry
|
||||||
|
auto end = std::chrono::high_resolution_clock::now();
|
||||||
|
std::chrono::duration<double> duration = end - m_start;
|
||||||
|
Logger::trace(L"Launching time: {} s", duration.count());
|
||||||
|
|
||||||
|
auto monitors = MonitorUtils::IdentifyMonitors();
|
||||||
|
bool differentSetup = monitors.size() != m_project.monitors.size();
|
||||||
|
if (!differentSetup)
|
||||||
|
{
|
||||||
|
for (const auto& monitor : m_project.monitors)
|
||||||
|
{
|
||||||
|
auto setup = std::find_if(monitors.begin(), monitors.end(), [&](const WorkspacesData::WorkspacesProject::Monitor& val) { return val.dpi == monitor.dpi && val.monitorRectDpiAware == monitor.monitorRectDpiAware; });
|
||||||
|
if (setup == monitors.end())
|
||||||
|
{
|
||||||
|
differentSetup = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Trace::Workspaces::Launch(m_launchedSuccessfully, m_project, m_invokePoint, duration.count(), differentSetup, m_launchErrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Launcher::Launch()
|
||||||
|
{
|
||||||
|
Logger::info(L"Launch Workspace {} : {}", m_project.name, m_project.id);
|
||||||
|
m_launchedSuccessfully = AppLauncher::Launch(m_project, m_launchingStatus, m_launchErrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Launcher::handleWindowArrangerMessage(const std::wstring& msg)
|
||||||
|
{
|
||||||
|
if (msg == L"ready")
|
||||||
|
{
|
||||||
|
Launch();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto data = WorkspacesData::AppLaunchInfoJSON::FromJson(json::JsonValue::Parse(msg).GetObjectW());
|
||||||
|
if (data.has_value())
|
||||||
|
{
|
||||||
|
m_launchingStatus.Update(data.value().application, data.value().state);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::error(L"Failed to parse message from WorkspacesWindowArranger");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const winrt::hresult_error&)
|
||||||
|
{
|
||||||
|
Logger::error(L"Failed to parse message from WorkspacesWindowArranger");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/modules/Workspaces/WorkspacesLauncher/Launcher.h
Normal file
31
src/modules/Workspaces/WorkspacesLauncher/Launcher.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <WorkspacesLib/LaunchingStatus.h>
|
||||||
|
#include <WorkspacesLib/WorkspacesData.h>
|
||||||
|
|
||||||
|
#include <workspaces-common/InvokePoint.h>
|
||||||
|
|
||||||
|
#include <LauncherUIHelper.h>
|
||||||
|
#include <WindowArrangerHelper.h>
|
||||||
|
|
||||||
|
class Launcher
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
LaunchingStatus m_launchingStatus;
|
||||||
|
bool m_launchedSuccessfully{};
|
||||||
|
std::vector<std::pair<std::wstring, std::wstring>> m_launchErrors{};
|
||||||
|
|
||||||
|
void handleWindowArrangerMessage(const std::wstring& msg);
|
||||||
|
};
|
||||||
@@ -7,12 +7,22 @@
|
|||||||
#include <common/utils/OnThreadExecutor.h>
|
#include <common/utils/OnThreadExecutor.h>
|
||||||
#include <common/utils/winapi_error.h>
|
#include <common/utils/winapi_error.h>
|
||||||
|
|
||||||
|
#include <AppLauncher.h>
|
||||||
|
|
||||||
|
LauncherUIHelper::LauncherUIHelper() :
|
||||||
|
m_processId{},
|
||||||
|
m_ipcHelper(IPCHelperStrings::LauncherUIPipeName, IPCHelperStrings::UIPipeName, nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
LauncherUIHelper::~LauncherUIHelper()
|
LauncherUIHelper::~LauncherUIHelper()
|
||||||
{
|
{
|
||||||
OnThreadExecutor().submit(OnThreadExecutor::task_t{ [&] {
|
OnThreadExecutor().submit(OnThreadExecutor::task_t{ [&] {
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||||
|
|
||||||
HANDLE uiProcess = OpenProcess(PROCESS_ALL_ACCESS, false, uiProcessId);
|
Logger::info(L"Stopping WorkspacesLauncherUI with pid {}", m_processId);
|
||||||
|
|
||||||
|
HANDLE uiProcess = OpenProcess(PROCESS_ALL_ACCESS, false, m_processId);
|
||||||
if (uiProcess)
|
if (uiProcess)
|
||||||
{
|
{
|
||||||
bool res = TerminateProcess(uiProcess, 0);
|
bool res = TerminateProcess(uiProcess, 0);
|
||||||
@@ -25,54 +35,39 @@ LauncherUIHelper::~LauncherUIHelper()
|
|||||||
{
|
{
|
||||||
Logger::error(L"Unable to find UI process: {}", get_last_error_or_default(GetLastError()));
|
Logger::error(L"Unable to find UI process: {}", get_last_error_or_default(GetLastError()));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::filesystem::remove(WorkspacesData::LaunchWorkspacesFile());
|
|
||||||
} }).wait();
|
} }).wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherUIHelper::LaunchUI()
|
void LauncherUIHelper::LaunchUI()
|
||||||
{
|
{
|
||||||
Logger::trace(L"Starting WorkspacesLauncherUI");
|
Logger::trace(L"Starting WorkspacesLauncherUI");
|
||||||
|
|
||||||
STARTUPINFO info = { sizeof(info) };
|
|
||||||
PROCESS_INFORMATION pi = { 0 };
|
|
||||||
TCHAR buffer[MAX_PATH] = { 0 };
|
TCHAR buffer[MAX_PATH] = { 0 };
|
||||||
GetModuleFileName(NULL, buffer, MAX_PATH);
|
GetModuleFileName(NULL, buffer, MAX_PATH);
|
||||||
std::wstring path = std::filesystem::path(buffer).parent_path();
|
std::wstring path = std::filesystem::path(buffer).parent_path();
|
||||||
path.append(L"\\PowerToys.WorkspacesLauncherUI.exe");
|
|
||||||
auto succeeded = CreateProcessW(path.c_str(), nullptr, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &info, &pi);
|
auto res = AppLauncher::LaunchApp(path + L"\\PowerToys.WorkspacesLauncherUI.exe", L"", false);
|
||||||
if (succeeded)
|
if (res.isOk())
|
||||||
{
|
{
|
||||||
if (pi.hProcess)
|
auto value = res.value();
|
||||||
{
|
m_processId = GetProcessId(value.hProcess);
|
||||||
uiProcessId = pi.dwProcessId;
|
CloseHandle(value.hProcess);
|
||||||
CloseHandle(pi.hProcess);
|
Logger::info(L"WorkspacesLauncherUI started with pid {}", m_processId);
|
||||||
}
|
|
||||||
if (pi.hThread)
|
|
||||||
{
|
|
||||||
CloseHandle(pi.hThread);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger::error(L"CreateProcessW() failed. {}", get_last_error_or_default(GetLastError()));
|
Logger::error(L"Failed to launch PowerToys.WorkspacesLauncherUI: {}", res.error());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherUIHelper::UpdateLaunchStatus(LaunchingApps launchedApps)
|
void LauncherUIHelper::UpdateLaunchStatus(WorkspacesData::LaunchingAppStateMap launchedApps) const
|
||||||
{
|
{
|
||||||
WorkspacesData::AppLaunchData appData = WorkspacesData::AppLaunchData();
|
WorkspacesData::AppLaunchData appData;
|
||||||
appData.appLaunchInfoList.reserve(launchedApps.size());
|
|
||||||
appData.launcherProcessID = GetCurrentProcessId();
|
appData.launcherProcessID = GetCurrentProcessId();
|
||||||
for (auto& app : launchedApps)
|
for (auto& [app, data] : launchedApps)
|
||||||
{
|
{
|
||||||
WorkspacesData::AppLaunchInfo appLaunchInfo = WorkspacesData::AppLaunchInfo();
|
appData.appsStateList.insert({ app, { app, nullptr, data.state } });
|
||||||
appLaunchInfo.name = app.application.name;
|
|
||||||
appLaunchInfo.path = app.application.path;
|
|
||||||
appLaunchInfo.state = app.state;
|
|
||||||
|
|
||||||
appData.appLaunchInfoList.push_back(appLaunchInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
json::to_file(WorkspacesData::LaunchWorkspacesFile(), WorkspacesData::AppLaunchDataJSON::ToJson(appData));
|
m_ipcHelper.send(WorkspacesData::AppLaunchDataJSON::ToJson(appData).ToString().c_str());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <LaunchingApp.h>
|
#include <WorkspacesLib/WorkspacesData.h>
|
||||||
|
#include <WorkspacesLib/IPCHelper.h>
|
||||||
|
|
||||||
class LauncherUIHelper
|
class LauncherUIHelper
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LauncherUIHelper() = default;
|
LauncherUIHelper();
|
||||||
~LauncherUIHelper();
|
~LauncherUIHelper();
|
||||||
|
|
||||||
void LaunchUI();
|
void LaunchUI();
|
||||||
void UpdateLaunchStatus(LaunchingApps launchedApps);
|
void UpdateLaunchStatus(WorkspacesData::LaunchingAppStateMap launchedApps) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DWORD uiProcessId;
|
DWORD m_processId;
|
||||||
|
IPCHelper m_ipcHelper;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Windows.h>
|
|
||||||
#include <WorkspacesLib/WorkspacesData.h>
|
|
||||||
|
|
||||||
struct LaunchingApp
|
|
||||||
{
|
|
||||||
WorkspacesData::WorkspacesProject::Application application;
|
|
||||||
HWND window;
|
|
||||||
std::wstring state;
|
|
||||||
};
|
|
||||||
|
|
||||||
using LaunchingApps = std::vector<LaunchingApp>;
|
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "WindowArrangerHelper.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include <common/utils/OnThreadExecutor.h>
|
||||||
|
#include <common/utils/winapi_error.h>
|
||||||
|
|
||||||
|
#include <WorkspacesLib/WorkspacesData.h>
|
||||||
|
|
||||||
|
#include <AppLauncher.h>
|
||||||
|
|
||||||
|
WindowArrangerHelper::WindowArrangerHelper(std::function<void(const std::wstring&)> ipcCallback) :
|
||||||
|
m_processId{},
|
||||||
|
m_ipcHelper(IPCHelperStrings::LauncherArrangerPipeName, IPCHelperStrings::WindowArrangerPipeName, ipcCallback)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowArrangerHelper::~WindowArrangerHelper()
|
||||||
|
{
|
||||||
|
Logger::info(L"Stopping WorkspacesWindowArranger with pid {}", m_processId);
|
||||||
|
|
||||||
|
HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, false, m_processId);
|
||||||
|
if (process)
|
||||||
|
{
|
||||||
|
bool res = TerminateProcess(process, 0);
|
||||||
|
if (!res)
|
||||||
|
{
|
||||||
|
Logger::error(L"Unable to terminate PowerToys.WorkspacesWindowArranger process: {}", get_last_error_or_default(GetLastError()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::error(L"Unable to find PowerToys.WorkspacesWindowArranger process: {}", get_last_error_or_default(GetLastError()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowArrangerHelper::Launch(const std::wstring& projectId, bool elevated, std::function<bool()> keepWaitingCallback)
|
||||||
|
{
|
||||||
|
Logger::trace(L"Starting WorkspacesWindowArranger");
|
||||||
|
|
||||||
|
TCHAR buffer[MAX_PATH] = { 0 };
|
||||||
|
GetModuleFileName(NULL, buffer, MAX_PATH);
|
||||||
|
std::wstring path = std::filesystem::path(buffer).parent_path();
|
||||||
|
|
||||||
|
auto res = AppLauncher::LaunchApp(path + L"\\PowerToys.WorkspacesWindowArranger.exe", projectId, elevated);
|
||||||
|
if (res.isOk())
|
||||||
|
{
|
||||||
|
auto value = res.value();
|
||||||
|
m_processId = GetProcessId(value.hProcess);
|
||||||
|
Logger::info(L"WorkspacesWindowArranger started with pid {}", m_processId);
|
||||||
|
std::atomic_bool timeoutExpired = false;
|
||||||
|
m_threadExecutor.submit(OnThreadExecutor::task_t{
|
||||||
|
[&] {
|
||||||
|
HANDLE process = value.hProcess;
|
||||||
|
while (keepWaitingCallback())
|
||||||
|
{
|
||||||
|
WaitForSingleObject(process, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::trace(L"Finished waiting WorkspacesWindowArranger");
|
||||||
|
CloseHandle(process);
|
||||||
|
}}).wait();
|
||||||
|
|
||||||
|
timeoutExpired = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::error(L"Failed to launch PowerToys.WorkspacesWindowArranger: {}", res.error());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <WorkspacesLib/IPCHelper.h>
|
||||||
|
#include <WorkspacesLib/WorkspacesData.h>
|
||||||
|
|
||||||
|
#include <common/utils/OnThreadExecutor.h>
|
||||||
|
|
||||||
|
class WindowArrangerHelper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WindowArrangerHelper(std::function<void(const std::wstring&)> ipcCallback);
|
||||||
|
~WindowArrangerHelper();
|
||||||
|
|
||||||
|
void Launch(const std::wstring& projectId, bool elevated, std::function<bool()> keepWaitingCallback);
|
||||||
|
|
||||||
|
private:
|
||||||
|
DWORD m_processId;
|
||||||
|
IPCHelper m_ipcHelper;
|
||||||
|
OnThreadExecutor m_threadExecutor;
|
||||||
|
};
|
||||||
@@ -127,21 +127,23 @@
|
|||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="AppLauncher.cpp" />
|
<ClCompile Include="AppLauncher.cpp" />
|
||||||
|
<ClCompile Include="Launcher.cpp" />
|
||||||
<ClCompile Include="LauncherUIHelper.cpp" />
|
<ClCompile Include="LauncherUIHelper.cpp" />
|
||||||
<ClCompile Include="main.cpp" />
|
<ClCompile Include="main.cpp" />
|
||||||
<ClCompile Include="pch.cpp">
|
<ClCompile Include="pch.cpp">
|
||||||
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
|
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="RegistryUtils.cpp" />
|
<ClCompile Include="RegistryUtils.cpp" />
|
||||||
|
<ClCompile Include="WindowArrangerHelper.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="AppLauncher.h" />
|
<ClInclude Include="AppLauncher.h" />
|
||||||
|
<ClInclude Include="Launcher.h" />
|
||||||
<ClInclude Include="LauncherUIHelper.h" />
|
<ClInclude Include="LauncherUIHelper.h" />
|
||||||
<ClInclude Include="LaunchingApp.h" />
|
|
||||||
<ClInclude Include="pch.h" />
|
<ClInclude Include="pch.h" />
|
||||||
<ClInclude Include="RegistryUtils.h" />
|
<ClInclude Include="RegistryUtils.h" />
|
||||||
<ClInclude Include="resource.h" />
|
<ClInclude Include="resource.h" />
|
||||||
<ClInclude Include="utils.h" />
|
<ClInclude Include="WindowArrangerHelper.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
|
|||||||
@@ -27,13 +27,13 @@
|
|||||||
<ClInclude Include="RegistryUtils.h">
|
<ClInclude Include="RegistryUtils.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="utils.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="LauncherUIHelper.h">
|
<ClInclude Include="LauncherUIHelper.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="LaunchingApp.h">
|
<ClInclude Include="WindowArrangerHelper.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="Launcher.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -53,6 +53,12 @@
|
|||||||
<ClCompile Include="LauncherUIHelper.cpp">
|
<ClCompile Include="LauncherUIHelper.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="WindowArrangerHelper.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="Launcher.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
#include <WorkspacesLib/WorkspacesData.h>
|
#include <WorkspacesLib/JsonUtils.h>
|
||||||
#include <WorkspacesLib/trace.h>
|
#include <WorkspacesLib/utils.h>
|
||||||
|
|
||||||
#include <AppLauncher.h>
|
#include <Launcher.h>
|
||||||
#include <utils.h>
|
|
||||||
|
|
||||||
#include <Generated Files/resource.h>
|
#include <Generated Files/resource.h>
|
||||||
|
|
||||||
#include <workspaces-common/InvokePoint.h>
|
|
||||||
#include <workspaces-common/MonitorUtils.h>
|
|
||||||
|
|
||||||
#include <common/utils/elevation.h>
|
#include <common/utils/elevation.h>
|
||||||
#include <common/utils/gpo.h>
|
#include <common/utils/gpo.h>
|
||||||
#include <common/utils/logger_helper.h>
|
#include <common/utils/logger_helper.h>
|
||||||
@@ -58,16 +54,16 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cm
|
|||||||
|
|
||||||
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||||
|
|
||||||
std::string cmdLineStr(cmdline);
|
std::wstring cmdLineStr{ GetCommandLineW() };
|
||||||
auto cmdArgs = split(cmdLineStr, " ");
|
auto cmdArgs = split(cmdLineStr, L" ");
|
||||||
if (cmdArgs.size() < 1)
|
if (cmdArgs.size() < 2)
|
||||||
{
|
{
|
||||||
Logger::warn("Incorrect command line arguments");
|
Logger::warn("Incorrect command line arguments");
|
||||||
MessageBox(NULL, GET_RESOURCE_STRING(IDS_INCORRECT_ARGS).c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
MessageBox(NULL, GET_RESOURCE_STRING(IDS_INCORRECT_ARGS).c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::wstring id(cmdArgs[0].begin(), cmdArgs[0].end());
|
std::wstring id(cmdArgs[1].begin(), cmdArgs[1].end());
|
||||||
if (id.empty())
|
if (id.empty())
|
||||||
{
|
{
|
||||||
Logger::warn("Incorrect command line arguments: no workspace id");
|
Logger::warn("Incorrect command line arguments: no workspace id");
|
||||||
@@ -76,11 +72,11 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cm
|
|||||||
}
|
}
|
||||||
|
|
||||||
InvokePoint invokePoint = InvokePoint::EditorButton;
|
InvokePoint invokePoint = InvokePoint::EditorButton;
|
||||||
if (cmdArgs.size() > 1)
|
if (cmdArgs.size() > 2)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
invokePoint = static_cast<InvokePoint>(std::stoi(cmdArgs[1]));
|
invokePoint = static_cast<InvokePoint>(std::stoi(cmdArgs[2]));
|
||||||
}
|
}
|
||||||
catch (std::exception)
|
catch (std::exception)
|
||||||
{
|
{
|
||||||
@@ -95,79 +91,51 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cm
|
|||||||
if (invokePoint == InvokePoint::LaunchAndEdit)
|
if (invokePoint == InvokePoint::LaunchAndEdit)
|
||||||
{
|
{
|
||||||
// check the temp file in case the project is just created and not saved to the workspaces.json yet
|
// check the temp file in case the project is just created and not saved to the workspaces.json yet
|
||||||
if (std::filesystem::exists(WorkspacesData::TempWorkspacesFile()))
|
auto file = WorkspacesData::TempWorkspacesFile();
|
||||||
|
auto res = JsonUtils::ReadSingleWorkspace(file);
|
||||||
|
if (res.isOk() && projectToLaunch.id == id)
|
||||||
{
|
{
|
||||||
try
|
projectToLaunch = res.getValue();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::wstring formattedMessage{};
|
||||||
|
switch (res.error())
|
||||||
{
|
{
|
||||||
auto savedWorkspacesJson = json::from_file(WorkspacesData::TempWorkspacesFile());
|
case JsonUtils::WorkspacesFileError::FileReadingError:
|
||||||
if (savedWorkspacesJson.has_value())
|
formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_FILE_READING_ERROR), file);
|
||||||
{
|
break;
|
||||||
auto savedWorkspaces = WorkspacesData::WorkspacesProjectJSON::FromJson(savedWorkspacesJson.value());
|
case JsonUtils::WorkspacesFileError::IncorrectFileError:
|
||||||
if (savedWorkspaces.has_value())
|
formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_INCORRECT_FILE_ERROR), file);
|
||||||
{
|
break;
|
||||||
if (savedWorkspaces.value().id == id)
|
|
||||||
{
|
|
||||||
projectToLaunch = savedWorkspaces.value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger::critical("Incorrect Workspaces file");
|
|
||||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_INCORRECT_FILE_ERROR), WorkspacesData::TempWorkspacesFile());
|
|
||||||
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger::critical("Incorrect Workspaces file");
|
|
||||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_INCORRECT_FILE_ERROR), WorkspacesData::TempWorkspacesFile());
|
|
||||||
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (std::exception ex)
|
|
||||||
{
|
|
||||||
Logger::critical("Exception on reading Workspaces file: {}", ex.what());
|
|
||||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_FILE_READING_ERROR), WorkspacesData::TempWorkspacesFile());
|
|
||||||
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (projectToLaunch.id.empty())
|
if (projectToLaunch.id.empty())
|
||||||
{
|
{
|
||||||
try
|
auto file = WorkspacesData::WorkspacesFile();
|
||||||
|
auto res = JsonUtils::ReadWorkspaces(file);
|
||||||
|
if (res.isOk())
|
||||||
{
|
{
|
||||||
auto savedWorkspacesJson = json::from_file(WorkspacesData::WorkspacesFile());
|
workspaces = res.getValue();
|
||||||
if (savedWorkspacesJson.has_value())
|
|
||||||
{
|
|
||||||
auto savedWorkspaces = WorkspacesData::WorkspacesListJSON::FromJson(savedWorkspacesJson.value());
|
|
||||||
if (savedWorkspaces.has_value())
|
|
||||||
{
|
|
||||||
workspaces = savedWorkspaces.value();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger::critical("Incorrect Workspaces file");
|
|
||||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_INCORRECT_FILE_ERROR), WorkspacesData::WorkspacesFile());
|
|
||||||
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger::critical("Incorrect Workspaces file");
|
|
||||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_INCORRECT_FILE_ERROR), WorkspacesData::WorkspacesFile());
|
|
||||||
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (std::exception ex)
|
else
|
||||||
{
|
{
|
||||||
Logger::critical("Exception on reading Workspaces file: {}", ex.what());
|
std::wstring formattedMessage{};
|
||||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_FILE_READING_ERROR), WorkspacesData::WorkspacesFile());
|
switch (res.error())
|
||||||
|
{
|
||||||
|
case JsonUtils::WorkspacesFileError::FileReadingError:
|
||||||
|
formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_FILE_READING_ERROR), file);
|
||||||
|
break;
|
||||||
|
case JsonUtils::WorkspacesFileError::IncorrectFileError:
|
||||||
|
formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_INCORRECT_FILE_ERROR), file);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -175,7 +143,7 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cm
|
|||||||
if (workspaces.empty())
|
if (workspaces.empty())
|
||||||
{
|
{
|
||||||
Logger::warn("Workspaces file is empty");
|
Logger::warn("Workspaces file is empty");
|
||||||
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_EMPTY_FILE), WorkspacesData::WorkspacesFile());
|
std::wstring formattedMessage = fmt::format(GET_RESOURCE_STRING(IDS_EMPTY_FILE), file);
|
||||||
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
MessageBox(NULL, formattedMessage.c_str(), GET_RESOURCE_STRING(IDS_WORKSPACES).c_str(), MB_ICONERROR | MB_OK);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -198,50 +166,9 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cm
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// launch apps
|
Launcher launcher(projectToLaunch, workspaces, invokePoint);
|
||||||
Logger::info(L"Launch Workspace {} : {}", projectToLaunch.name, projectToLaunch.id);
|
|
||||||
auto monitors = MonitorUtils::IdentifyMonitors();
|
|
||||||
std::vector<std::pair<std::wstring, std::wstring>> launchErrors{};
|
|
||||||
auto start = std::chrono::high_resolution_clock::now();
|
|
||||||
bool launchedSuccessfully = Launch(projectToLaunch, monitors, launchErrors);
|
|
||||||
|
|
||||||
// update last-launched time
|
|
||||||
if (invokePoint != InvokePoint::LaunchAndEdit)
|
|
||||||
{
|
|
||||||
time_t launchedTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
|
||||||
projectToLaunch.lastLaunchedTime = launchedTime;
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
// telemetry
|
|
||||||
auto end = std::chrono::high_resolution_clock::now();
|
|
||||||
std::chrono::duration<double> duration = end - start;
|
|
||||||
Logger::trace(L"Launching time: {} s", duration.count());
|
|
||||||
|
|
||||||
bool differentSetup = monitors.size() != projectToLaunch.monitors.size();
|
|
||||||
if (!differentSetup)
|
|
||||||
{
|
|
||||||
for (const auto& monitor : projectToLaunch.monitors)
|
|
||||||
{
|
|
||||||
auto setup = std::find_if(monitors.begin(), monitors.end(), [&](const WorkspacesData::WorkspacesProject::Monitor& val) { return val.dpi == monitor.dpi && val.monitorRectDpiAware == monitor.monitorRectDpiAware; });
|
|
||||||
if (setup == monitors.end())
|
|
||||||
{
|
|
||||||
differentSetup = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace::Workspaces::Launch(launchedSuccessfully, projectToLaunch, invokePoint, duration.count(), differentSetup, launchErrors);
|
|
||||||
|
|
||||||
|
Logger::trace("Finished");
|
||||||
CoUninitialize();
|
CoUninitialize();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Forms.Design.Behavior;
|
|
||||||
|
|
||||||
using Common.UI;
|
using Common.UI;
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
using WorkspacesLauncherUI.Utils;
|
using PowerToys.Interop;
|
||||||
using WorkspacesLauncherUI.ViewModels;
|
using WorkspacesLauncherUI.ViewModels;
|
||||||
|
|
||||||
namespace WorkspacesLauncherUI
|
namespace WorkspacesLauncherUI
|
||||||
@@ -21,6 +19,9 @@ namespace WorkspacesLauncherUI
|
|||||||
{
|
{
|
||||||
private static Mutex _instanceMutex;
|
private static Mutex _instanceMutex;
|
||||||
|
|
||||||
|
// Create an instance of the IPC wrapper.
|
||||||
|
private static TwoWayPipeMessageIPCManaged ipcmanager;
|
||||||
|
|
||||||
private StatusWindow _mainWindow;
|
private StatusWindow _mainWindow;
|
||||||
|
|
||||||
private MainViewModel _mainViewModel;
|
private MainViewModel _mainViewModel;
|
||||||
@@ -29,21 +30,23 @@ namespace WorkspacesLauncherUI
|
|||||||
|
|
||||||
private bool _isDisposed;
|
private bool _isDisposed;
|
||||||
|
|
||||||
|
public static Action<string> IPCMessageReceivedCallback { get; set; }
|
||||||
|
|
||||||
public App()
|
public App()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnStartup(object sender, StartupEventArgs e)
|
private void OnStartup(object sender, StartupEventArgs e)
|
||||||
{
|
{
|
||||||
Logger.InitializeLogger("\\Workspaces\\Logs");
|
Logger.InitializeLogger("\\Workspaces\\WorkspacesLauncherUI");
|
||||||
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
||||||
|
|
||||||
const string appName = "Local\\PowerToys_Workspaces_Launcher_InstanceMutex";
|
const string appName = "Local\\PowerToys_Workspaces_LauncherUI_InstanceMutex";
|
||||||
bool createdNew;
|
bool createdNew;
|
||||||
_instanceMutex = new Mutex(true, appName, out createdNew);
|
_instanceMutex = new Mutex(true, appName, out createdNew);
|
||||||
if (!createdNew)
|
if (!createdNew)
|
||||||
{
|
{
|
||||||
Logger.LogWarning("Another instance of Workspaces Launcher is already running. Exiting this instance.");
|
Logger.LogWarning("Another instance of Workspaces Launcher UI is already running. Exiting this instance.");
|
||||||
_instanceMutex = null;
|
_instanceMutex = null;
|
||||||
Shutdown(0);
|
Shutdown(0);
|
||||||
return;
|
return;
|
||||||
@@ -56,6 +59,15 @@ namespace WorkspacesLauncherUI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipcmanager = new TwoWayPipeMessageIPCManaged("\\\\.\\pipe\\powertoys_workspaces_ui_", "\\\\.\\pipe\\powertoys_workspaces_launcher_ui_", (string message) =>
|
||||||
|
{
|
||||||
|
if (IPCMessageReceivedCallback != null && message.Length > 0)
|
||||||
|
{
|
||||||
|
IPCMessageReceivedCallback(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ipcmanager.Start();
|
||||||
|
|
||||||
ThemeManager = new ThemeManager(this);
|
ThemeManager = new ThemeManager(this);
|
||||||
|
|
||||||
if (_mainViewModel == null)
|
if (_mainViewModel == null)
|
||||||
@@ -97,6 +109,10 @@ namespace WorkspacesLauncherUI
|
|||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
ThemeManager?.Dispose();
|
ThemeManager?.Dispose();
|
||||||
|
|
||||||
|
ipcmanager?.End();
|
||||||
|
ipcmanager?.Dispose();
|
||||||
|
|
||||||
_instanceMutex?.Dispose();
|
_instanceMutex?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +1,16 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
// Copyright (c) Microsoft Corporation
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
using Workspaces.Data;
|
using Workspaces.Data;
|
||||||
using WorkspacesLauncherUI.Utils;
|
|
||||||
|
|
||||||
using static WorkspacesLauncherUI.Data.AppLaunchData;
|
using static WorkspacesLauncherUI.Data.AppLaunchData;
|
||||||
using static WorkspacesLauncherUI.Data.AppLaunchInfoData;
|
|
||||||
using static WorkspacesLauncherUI.Data.AppLaunchInfosData;
|
using static WorkspacesLauncherUI.Data.AppLaunchInfosData;
|
||||||
|
|
||||||
namespace WorkspacesLauncherUI.Data
|
namespace WorkspacesLauncherUI.Data
|
||||||
{
|
{
|
||||||
internal sealed class AppLaunchData : WorkspacesEditorData<AppLaunchDataWrapper>
|
public class AppLaunchData : WorkspacesUIData<AppLaunchDataWrapper>
|
||||||
{
|
{
|
||||||
public static string File
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return FolderUtils.DataFolder() + "\\launch-workspaces.json";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct AppLaunchDataWrapper
|
public struct AppLaunchDataWrapper
|
||||||
{
|
{
|
||||||
[JsonPropertyName("apps")]
|
[JsonPropertyName("apps")]
|
||||||
|
|||||||
@@ -1,32 +1,23 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
// Copyright (c) Microsoft Corporation
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
using Workspaces.Data;
|
using Workspaces.Data;
|
||||||
|
|
||||||
using static WorkspacesLauncherUI.Data.AppLaunchInfoData;
|
using static WorkspacesLauncherUI.Data.AppLaunchInfoData;
|
||||||
|
|
||||||
namespace WorkspacesLauncherUI.Data
|
namespace WorkspacesLauncherUI.Data
|
||||||
{
|
{
|
||||||
public class AppLaunchInfoData : WorkspacesEditorData<AppLaunchInfoWrapper>
|
public class AppLaunchInfoData : WorkspacesUIData<AppLaunchInfoWrapper>
|
||||||
{
|
{
|
||||||
public struct AppLaunchInfoWrapper
|
public struct AppLaunchInfoWrapper
|
||||||
{
|
{
|
||||||
[JsonPropertyName("name")]
|
[JsonPropertyName("application")]
|
||||||
public string Name { get; set; }
|
public ApplicationWrapper Application { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("path")]
|
|
||||||
public string Path { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("state")]
|
[JsonPropertyName("state")]
|
||||||
public string State { get; set; }
|
public LaunchingState State { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
@@ -13,7 +12,7 @@ using static WorkspacesLauncherUI.Data.AppLaunchInfosData;
|
|||||||
|
|
||||||
namespace WorkspacesLauncherUI.Data
|
namespace WorkspacesLauncherUI.Data
|
||||||
{
|
{
|
||||||
public class AppLaunchInfosData : WorkspacesEditorData<AppLaunchInfoListWrapper>
|
public class AppLaunchInfosData : WorkspacesUIData<AppLaunchInfoListWrapper>
|
||||||
{
|
{
|
||||||
public struct AppLaunchInfoListWrapper
|
public struct AppLaunchInfoListWrapper
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
namespace WorkspacesLauncherUI.Data
|
||||||
|
{
|
||||||
|
public struct ApplicationWrapper
|
||||||
|
{
|
||||||
|
public string Application { get; set; }
|
||||||
|
|
||||||
|
public string ApplicationPath { get; set; }
|
||||||
|
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
public string PackageFullName { get; set; }
|
||||||
|
|
||||||
|
public string AppUserModelId { get; set; }
|
||||||
|
|
||||||
|
public string CommandLineArguments { get; set; }
|
||||||
|
|
||||||
|
public bool IsElevated { get; set; }
|
||||||
|
|
||||||
|
public bool CanLaunchElevated { get; set; }
|
||||||
|
|
||||||
|
public bool Minimized { get; set; }
|
||||||
|
|
||||||
|
public bool Maximized { get; set; }
|
||||||
|
|
||||||
|
public PositionWrapper Position { get; set; }
|
||||||
|
|
||||||
|
public int Monitor { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
namespace WorkspacesLauncherUI.Data
|
||||||
|
{
|
||||||
|
// sync with WorkspacesLib : LaunchingStateEnum.h
|
||||||
|
public enum LaunchingState
|
||||||
|
{
|
||||||
|
Waiting = 0,
|
||||||
|
Launched,
|
||||||
|
LaunchedAndMoved,
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
namespace WorkspacesLauncherUI.Data
|
||||||
|
{
|
||||||
|
public struct PositionWrapper
|
||||||
|
{
|
||||||
|
public int X { get; set; }
|
||||||
|
|
||||||
|
public int Y { get; set; }
|
||||||
|
|
||||||
|
public int Width { get; set; }
|
||||||
|
|
||||||
|
public int Height { get; set; }
|
||||||
|
|
||||||
|
public static bool operator ==(PositionWrapper left, PositionWrapper right)
|
||||||
|
{
|
||||||
|
return left.X == right.X && left.Y == right.Y && left.Width == right.Width && left.Height == right.Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(PositionWrapper left, PositionWrapper right)
|
||||||
|
{
|
||||||
|
return left.X != right.X || left.Y != right.Y || left.Width != right.Width || left.Height != right.Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj == null || GetType() != obj.GetType())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PositionWrapper pos = (PositionWrapper)obj;
|
||||||
|
return X == pos.X && Y == pos.Y && Width == pos.Width && Height == pos.Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return base.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ using WorkspacesLauncherUI.Utils;
|
|||||||
|
|
||||||
namespace Workspaces.Data
|
namespace Workspaces.Data
|
||||||
{
|
{
|
||||||
public class WorkspacesEditorData<T>
|
public class WorkspacesUIData<T>
|
||||||
{
|
{
|
||||||
protected JsonSerializerOptions JsonOptions
|
protected JsonSerializerOptions JsonOptions
|
||||||
{
|
{
|
||||||
@@ -22,10 +22,8 @@ namespace Workspaces.Data
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public T Read(string file)
|
public T Deserialize(string data)
|
||||||
{
|
{
|
||||||
IOUtils ioUtils = new IOUtils();
|
|
||||||
string data = ioUtils.ReadFile(file);
|
|
||||||
return JsonSerializer.Deserialize<T>(data, JsonOptions);
|
return JsonSerializer.Deserialize<T>(data, JsonOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Automation.Peers;
|
using System.Windows.Automation.Peers;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
// Copyright (c) Microsoft Corporation
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
@@ -13,9 +13,9 @@ using System.Text.Json.Serialization;
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
using Windows.Management.Deployment;
|
using Windows.Management.Deployment;
|
||||||
|
using WorkspacesLauncherUI.Data;
|
||||||
|
|
||||||
namespace WorkspacesLauncherUI.Models
|
namespace WorkspacesLauncherUI.Models
|
||||||
{
|
{
|
||||||
@@ -28,9 +28,9 @@ namespace WorkspacesLauncherUI.Models
|
|||||||
PropertyChanged?.Invoke(this, e);
|
PropertyChanged?.Invoke(this, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string AppPath { get; set; }
|
public ApplicationWrapper Application { get; set; }
|
||||||
|
|
||||||
public bool Loading => LaunchState == "waiting";
|
public bool Loading => LaunchState == LaunchingState.Waiting || LaunchState == LaunchingState.Launched;
|
||||||
|
|
||||||
private Icon _icon;
|
private Icon _icon;
|
||||||
|
|
||||||
@@ -51,12 +51,12 @@ namespace WorkspacesLauncherUI.Models
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_icon = Icon.ExtractAssociatedIcon(AppPath);
|
_icon = Icon.ExtractAssociatedIcon(Application.ApplicationPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
Logger.LogWarning($"Icon not found on app path: {AppPath}. Using default icon");
|
Logger.LogWarning($"Icon not found on app path: {Application.ApplicationPath}. Using default icon");
|
||||||
IsNotFound = true;
|
IsNotFound = true;
|
||||||
_icon = new Icon(@"images\DefaultIcon.ico");
|
_icon = new Icon(@"images\DefaultIcon.ico");
|
||||||
}
|
}
|
||||||
@@ -66,16 +66,22 @@ namespace WorkspacesLauncherUI.Models
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Application.Application;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string LaunchState { get; set; }
|
public LaunchingState LaunchState { get; set; }
|
||||||
|
|
||||||
public string StateGlyph
|
public string StateGlyph
|
||||||
{
|
{
|
||||||
get => LaunchState switch
|
get => LaunchState switch
|
||||||
{
|
{
|
||||||
"launched" => "\U0000F78C",
|
LaunchingState.LaunchedAndMoved => "\U0000F78C",
|
||||||
"failed" => "\U0000EF2C",
|
LaunchingState.Failed => "\U0000EF2C",
|
||||||
_ => "\U0000EF2C",
|
_ => "\U0000EF2C",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -84,8 +90,8 @@ namespace WorkspacesLauncherUI.Models
|
|||||||
{
|
{
|
||||||
get => LaunchState switch
|
get => LaunchState switch
|
||||||
{
|
{
|
||||||
"launched" => new SolidColorBrush(System.Windows.Media.Color.FromArgb(255, 0, 128, 0)),
|
LaunchingState.LaunchedAndMoved => new SolidColorBrush(System.Windows.Media.Color.FromArgb(255, 0, 128, 0)),
|
||||||
"failed" => new SolidColorBrush(System.Windows.Media.Color.FromArgb(255, 254, 0, 0)),
|
LaunchingState.Failed => new SolidColorBrush(System.Windows.Media.Color.FromArgb(255, 254, 0, 0)),
|
||||||
_ => new SolidColorBrush(System.Windows.Media.Color.FromArgb(255, 254, 0, 0)),
|
_ => new SolidColorBrush(System.Windows.Media.Color.FromArgb(255, 254, 0, 0)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -139,13 +145,13 @@ namespace WorkspacesLauncherUI.Models
|
|||||||
{
|
{
|
||||||
if (_isPackagedApp == null)
|
if (_isPackagedApp == null)
|
||||||
{
|
{
|
||||||
if (!AppPath.StartsWith("C:\\Program Files\\WindowsApps\\", StringComparison.InvariantCultureIgnoreCase))
|
if (!Application.ApplicationPath.StartsWith("C:\\Program Files\\WindowsApps\\", StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
_isPackagedApp = false;
|
_isPackagedApp = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
string appPath = AppPath.Replace("C:\\Program Files\\WindowsApps\\", string.Empty);
|
string appPath = Application.ApplicationPath.Replace("C:\\Program Files\\WindowsApps\\", string.Empty);
|
||||||
Regex packagedAppPathRegex = new Regex(@"(?<APPID>[^_]*)_\d+.\d+.\d+.\d+_x64__(?<PublisherID>[^\\]*)", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
Regex packagedAppPathRegex = new Regex(@"(?<APPID>[^_]*)_\d+.\d+.\d+.\d+_x64__(?<PublisherID>[^\\]*)", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||||
Match match = packagedAppPathRegex.Match(appPath);
|
Match match = packagedAppPathRegex.Match(appPath);
|
||||||
_isPackagedApp = match.Success;
|
_isPackagedApp = match.Success;
|
||||||
@@ -200,7 +206,7 @@ namespace WorkspacesLauncherUI.Models
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.LogError($"Exception while drawing icon for app with path: {AppPath}. Exception message: {e.Message}");
|
Logger.LogError($"Exception while drawing icon for app with path: {Application.ApplicationPath}. Exception message: {e.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace WorkspacesLauncherUI.Utils
|
|
||||||
{
|
|
||||||
public class FolderUtils
|
|
||||||
{
|
|
||||||
public static string Desktop()
|
|
||||||
{
|
|
||||||
return Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string Temp()
|
|
||||||
{
|
|
||||||
return Path.GetTempPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: the same path should be used in SnapshotTool and Launcher
|
|
||||||
public static string DataFolder()
|
|
||||||
{
|
|
||||||
return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\Microsoft\\PowerToys\\Workspaces";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.IO.Abstractions;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace WorkspacesLauncherUI.Utils
|
|
||||||
{
|
|
||||||
public class IOUtils
|
|
||||||
{
|
|
||||||
private readonly IFileSystem _fileSystem = new FileSystem();
|
|
||||||
|
|
||||||
public IOUtils()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteFile(string fileName, string data)
|
|
||||||
{
|
|
||||||
_fileSystem.File.WriteAllText(fileName, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ReadFile(string fileName)
|
|
||||||
{
|
|
||||||
if (_fileSystem.File.Exists(fileName))
|
|
||||||
{
|
|
||||||
var attempts = 0;
|
|
||||||
while (attempts < 10)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using (Stream inputStream = _fileSystem.File.Open(fileName, FileMode.Open))
|
|
||||||
using (StreamReader reader = new StreamReader(inputStream))
|
|
||||||
{
|
|
||||||
string data = reader.ReadToEnd();
|
|
||||||
inputStream.Close();
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
Task.Delay(10).Wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
attempts++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,9 +7,6 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
|
||||||
using System.IO.Abstractions;
|
|
||||||
|
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
using WorkspacesLauncherUI.Data;
|
using WorkspacesLauncherUI.Data;
|
||||||
using WorkspacesLauncherUI.Models;
|
using WorkspacesLauncherUI.Models;
|
||||||
@@ -20,8 +17,6 @@ namespace WorkspacesLauncherUI.ViewModels
|
|||||||
{
|
{
|
||||||
public ObservableCollection<AppLaunching> AppsListed { get; set; } = new ObservableCollection<AppLaunching>();
|
public ObservableCollection<AppLaunching> AppsListed { get; set; } = new ObservableCollection<AppLaunching>();
|
||||||
|
|
||||||
private IFileSystemWatcher _watcher;
|
|
||||||
private System.Timers.Timer selfDestroyTimer;
|
|
||||||
private StatusWindow _snapshotWindow;
|
private StatusWindow _snapshotWindow;
|
||||||
private int launcherProcessID;
|
private int launcherProcessID;
|
||||||
private bool _exiting;
|
private bool _exiting;
|
||||||
@@ -36,60 +31,43 @@ namespace WorkspacesLauncherUI.ViewModels
|
|||||||
public MainViewModel()
|
public MainViewModel()
|
||||||
{
|
{
|
||||||
_exiting = false;
|
_exiting = false;
|
||||||
LoadAppLaunchInfos();
|
|
||||||
string fileName = Path.GetFileName(AppLaunchData.File);
|
// receive IPC Message
|
||||||
_watcher = Microsoft.PowerToys.Settings.UI.Library.Utilities.Helper.GetFileWatcher("Workspaces", fileName, () => AppLaunchInfoStateChanged());
|
App.IPCMessageReceivedCallback = (string msg) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AppLaunchData parser = new AppLaunchData();
|
||||||
|
AppLaunchData.AppLaunchDataWrapper appLaunchData = parser.Deserialize(msg);
|
||||||
|
HandleAppLaunchingState(appLaunchData);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex.Message);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AppLaunchInfoStateChanged()
|
private void HandleAppLaunchingState(AppLaunchData.AppLaunchDataWrapper appLaunchData)
|
||||||
{
|
|
||||||
LoadAppLaunchInfos();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadAppLaunchInfos()
|
|
||||||
{
|
{
|
||||||
if (_exiting)
|
if (_exiting)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AppLaunchData parser = new AppLaunchData();
|
|
||||||
if (!File.Exists(AppLaunchData.File))
|
|
||||||
{
|
|
||||||
Logger.LogWarning($"AppLaunchInfosData storage file not found: {AppLaunchData.File}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppLaunchData.AppLaunchDataWrapper appLaunchData = parser.Read(AppLaunchData.File);
|
|
||||||
|
|
||||||
launcherProcessID = appLaunchData.LauncherProcessID;
|
launcherProcessID = appLaunchData.LauncherProcessID;
|
||||||
|
|
||||||
List<AppLaunching> appLaunchingList = new List<AppLaunching>();
|
List<AppLaunching> appLaunchingList = new List<AppLaunching>();
|
||||||
bool allLaunched = true;
|
|
||||||
foreach (var app in appLaunchData.AppLaunchInfos.AppLaunchInfoList)
|
foreach (var app in appLaunchData.AppLaunchInfos.AppLaunchInfoList)
|
||||||
{
|
{
|
||||||
appLaunchingList.Add(new AppLaunching()
|
appLaunchingList.Add(new AppLaunching()
|
||||||
{
|
{
|
||||||
Name = app.Name,
|
Application = app.Application,
|
||||||
AppPath = app.Path,
|
|
||||||
LaunchState = app.State,
|
LaunchState = app.State,
|
||||||
});
|
});
|
||||||
if (app.State != "launched" && app.State != "failed")
|
|
||||||
{
|
|
||||||
allLaunched = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AppsListed = new ObservableCollection<AppLaunching>(appLaunchingList);
|
AppsListed = new ObservableCollection<AppLaunching>(appLaunchingList);
|
||||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(AppsListed)));
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(AppsListed)));
|
||||||
|
|
||||||
if (allLaunched)
|
|
||||||
{
|
|
||||||
selfDestroyTimer = new System.Timers.Timer();
|
|
||||||
selfDestroyTimer.Interval = 1000;
|
|
||||||
selfDestroyTimer.Elapsed += SelfDestroy;
|
|
||||||
selfDestroyTimer.Start();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SelfDestroy(object source, System.Timers.ElapsedEventArgs e)
|
private void SelfDestroy(object source, System.Timers.ElapsedEventArgs e)
|
||||||
@@ -113,7 +91,6 @@ namespace WorkspacesLauncherUI.ViewModels
|
|||||||
internal void CancelLaunch()
|
internal void CancelLaunch()
|
||||||
{
|
{
|
||||||
_exiting = true;
|
_exiting = true;
|
||||||
_watcher.Dispose();
|
|
||||||
Process proc = Process.GetProcessById(launcherProcessID);
|
Process proc = Process.GetProcessById(launcherProcessID);
|
||||||
proc.Kill();
|
proc.Kill();
|
||||||
}
|
}
|
||||||
|
|||||||
42
src/modules/Workspaces/WorkspacesLib/IPCHelper.cpp
Normal file
42
src/modules/Workspaces/WorkspacesLib/IPCHelper.cpp
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "IPCHelper.h"
|
||||||
|
|
||||||
|
#include <common/logger/logger.h>
|
||||||
|
|
||||||
|
IPCHelper::IPCHelper(const std::wstring& currentPipeName, const std::wstring receiverPipeName, std::function<void(const std::wstring&)> messageCallback) :
|
||||||
|
callback(messageCallback)
|
||||||
|
{
|
||||||
|
HANDLE hToken = nullptr;
|
||||||
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
|
||||||
|
{
|
||||||
|
Logger::error("Failed to get process token");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_lock lock{ ipcMutex };
|
||||||
|
ipc = make_unique<TwoWayPipeMessageIPC>(currentPipeName, receiverPipeName, std::bind(&IPCHelper::receive, this, std::placeholders::_1));
|
||||||
|
ipc->start(hToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
IPCHelper::~IPCHelper()
|
||||||
|
{
|
||||||
|
std::unique_lock lock{ ipcMutex };
|
||||||
|
if (ipc)
|
||||||
|
{
|
||||||
|
ipc->end();
|
||||||
|
ipc = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IPCHelper::send(const std::wstring& message) const
|
||||||
|
{
|
||||||
|
ipc->send(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IPCHelper::receive(const std::wstring& msg)
|
||||||
|
{
|
||||||
|
if (callback)
|
||||||
|
{
|
||||||
|
callback(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/modules/Workspaces/WorkspacesLib/IPCHelper.h
Normal file
29
src/modules/Workspaces/WorkspacesLib/IPCHelper.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <common/interop/two_way_pipe_message_ipc.h>
|
||||||
|
|
||||||
|
namespace IPCHelperStrings
|
||||||
|
{
|
||||||
|
static std::wstring LauncherUIPipeName(L"\\\\.\\pipe\\powertoys_workspaces_launcher_ui_");
|
||||||
|
static std::wstring UIPipeName(L"\\\\.\\pipe\\powertoys_workspaces_ui_");
|
||||||
|
|
||||||
|
static std::wstring LauncherArrangerPipeName(L"\\\\.\\pipe\\powertoys_workspaces_launcher_arranger_");
|
||||||
|
static std::wstring WindowArrangerPipeName(L"\\\\.\\pipe\\powertoys_workspaces_window_arranger_");
|
||||||
|
}
|
||||||
|
|
||||||
|
class IPCHelper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IPCHelper(const std::wstring& currentPipeName, const std::wstring receiverPipeName, std::function<void(const std::wstring&)> messageCallback);
|
||||||
|
~IPCHelper();
|
||||||
|
|
||||||
|
void send(const std::wstring& message) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void receive(const std::wstring& msg);
|
||||||
|
|
||||||
|
std::unique_ptr<TwoWayPipeMessageIPC> ipc;
|
||||||
|
std::mutex ipcMutex;
|
||||||
|
std::function<void(const std::wstring&)> callback;
|
||||||
|
};
|
||||||
106
src/modules/Workspaces/WorkspacesLib/JsonUtils.cpp
Normal file
106
src/modules/Workspaces/WorkspacesLib/JsonUtils.cpp
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "JsonUtils.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include <common/logger/logger.h>
|
||||||
|
|
||||||
|
namespace JsonUtils
|
||||||
|
{
|
||||||
|
Result<WorkspacesData::WorkspacesProject, WorkspacesFileError> ReadSingleWorkspace(const std::wstring& fileName)
|
||||||
|
{
|
||||||
|
if (std::filesystem::exists(fileName))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto tempWorkspacesJson = json::from_file(fileName);
|
||||||
|
if (tempWorkspacesJson.has_value())
|
||||||
|
{
|
||||||
|
auto tempWorkspace = WorkspacesData::WorkspacesProjectJSON::FromJson(tempWorkspacesJson.value());
|
||||||
|
if (tempWorkspace.has_value())
|
||||||
|
{
|
||||||
|
return Ok(tempWorkspace.value());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::critical("Incorrect Workspaces file");
|
||||||
|
return Error(WorkspacesFileError::IncorrectFileError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::critical("Incorrect Workspaces file");
|
||||||
|
return Error(WorkspacesFileError::IncorrectFileError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (std::exception ex)
|
||||||
|
{
|
||||||
|
Logger::critical("Exception on reading Workspaces file: {}", ex.what());
|
||||||
|
return Error(WorkspacesFileError::FileReadingError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(WorkspacesData::WorkspacesProject{});
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<std::vector<WorkspacesData::WorkspacesProject>, WorkspacesFileError> ReadWorkspaces(const std::wstring& fileName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto savedWorkspacesJson = json::from_file(fileName);
|
||||||
|
if (savedWorkspacesJson.has_value())
|
||||||
|
{
|
||||||
|
auto savedWorkspaces = WorkspacesData::WorkspacesListJSON::FromJson(savedWorkspacesJson.value());
|
||||||
|
if (savedWorkspaces.has_value())
|
||||||
|
{
|
||||||
|
return Ok(savedWorkspaces.value());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::critical("Incorrect Workspaces file");
|
||||||
|
return Error(WorkspacesFileError::IncorrectFileError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::critical("Incorrect Workspaces file");
|
||||||
|
return Error(WorkspacesFileError::IncorrectFileError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (std::exception ex)
|
||||||
|
{
|
||||||
|
Logger::critical("Exception on reading Workspaces file: {}", ex.what());
|
||||||
|
return Error(WorkspacesFileError::FileReadingError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Write(const std::wstring& fileName, const std::vector<WorkspacesData::WorkspacesProject>& projects)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
json::to_file(fileName, WorkspacesData::WorkspacesListJSON::ToJson(projects));
|
||||||
|
}
|
||||||
|
catch (std::exception ex)
|
||||||
|
{
|
||||||
|
Logger::error("Error writing workspaces file. {}", ex.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Write(const std::wstring& fileName, const WorkspacesData::WorkspacesProject& project)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
json::to_file(fileName, WorkspacesData::WorkspacesProjectJSON::ToJson(project));
|
||||||
|
}
|
||||||
|
catch (std::exception ex)
|
||||||
|
{
|
||||||
|
Logger::error("Error writing workspaces file. {}", ex.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/modules/Workspaces/WorkspacesLib/JsonUtils.h
Normal file
19
src/modules/Workspaces/WorkspacesLib/JsonUtils.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <WorkspacesLib/Result.h>
|
||||||
|
#include <WorkspacesLib/WorkspacesData.h>
|
||||||
|
|
||||||
|
namespace JsonUtils
|
||||||
|
{
|
||||||
|
enum class WorkspacesFileError
|
||||||
|
{
|
||||||
|
FileReadingError,
|
||||||
|
IncorrectFileError,
|
||||||
|
};
|
||||||
|
|
||||||
|
Result<WorkspacesData::WorkspacesProject, WorkspacesFileError> ReadSingleWorkspace(const std::wstring& fileName);
|
||||||
|
Result<std::vector<WorkspacesData::WorkspacesProject>, WorkspacesFileError> ReadWorkspaces(const std::wstring& fileName);
|
||||||
|
|
||||||
|
bool Write(const std::wstring& fileName, const std::vector<WorkspacesData::WorkspacesProject>& projects);
|
||||||
|
bool Write(const std::wstring& fileName, const WorkspacesData::WorkspacesProject& project);
|
||||||
|
}
|
||||||
10
src/modules/Workspaces/WorkspacesLib/LaunchingStateEnum.h
Normal file
10
src/modules/Workspaces/WorkspacesLib/LaunchingStateEnum.h
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// sync with WorkspacesLauncherUI : Data : LaunchingState.cs
|
||||||
|
enum class LaunchingState
|
||||||
|
{
|
||||||
|
Waiting = 0,
|
||||||
|
Launched,
|
||||||
|
LaunchedAndMoved,
|
||||||
|
Failed
|
||||||
|
};
|
||||||
66
src/modules/Workspaces/WorkspacesLib/LaunchingStatus.cpp
Normal file
66
src/modules/Workspaces/WorkspacesLib/LaunchingStatus.cpp
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "LaunchingStatus.h"
|
||||||
|
|
||||||
|
#include <common/logger/logger.h>
|
||||||
|
|
||||||
|
LaunchingStatus::LaunchingStatus(const WorkspacesData::WorkspacesProject& project, std::function<void(const WorkspacesData::LaunchingAppStateMap&)> updateCallback) :
|
||||||
|
m_updateCallback(updateCallback)
|
||||||
|
{
|
||||||
|
std::unique_lock lock(m_mutex);
|
||||||
|
for (const auto& app : project.apps)
|
||||||
|
{
|
||||||
|
m_appsState.insert({ app, { app, nullptr, LaunchingState::Waiting } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
Logger::debug(data.state);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LaunchingStatus::AllLaunched() noexcept
|
||||||
|
{
|
||||||
|
std::shared_lock lock(m_mutex);
|
||||||
|
for (const auto& [app, data] : m_appsState)
|
||||||
|
{
|
||||||
|
if (data.state == LaunchingState::Waiting)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LaunchingStatus::Update(const WorkspacesData::WorkspacesProject::Application& app, 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;
|
||||||
|
|
||||||
|
if (m_updateCallback)
|
||||||
|
{
|
||||||
|
m_updateCallback(m_appsState);
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/modules/Workspaces/WorkspacesLib/LaunchingStatus.h
Normal file
24
src/modules/Workspaces/WorkspacesLib/LaunchingStatus.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <shared_mutex>
|
||||||
|
|
||||||
|
#include <WorkspacesLib/WorkspacesData.h>
|
||||||
|
|
||||||
|
class LaunchingStatus
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LaunchingStatus(const WorkspacesData::WorkspacesProject& project, std::function<void(const WorkspacesData::LaunchingAppStateMap&)> updateCallback);
|
||||||
|
~LaunchingStatus() = default;
|
||||||
|
|
||||||
|
bool AllLaunchedAndMoved() noexcept;
|
||||||
|
bool AllLaunched() noexcept;
|
||||||
|
const WorkspacesData::LaunchingAppStateMap& Get() noexcept;
|
||||||
|
|
||||||
|
void Update(const WorkspacesData::WorkspacesProject::Application& app, LaunchingState state);
|
||||||
|
|
||||||
|
private:
|
||||||
|
WorkspacesData::LaunchingAppStateMap m_appsState;
|
||||||
|
std::function<void(const WorkspacesData::LaunchingAppStateMap&)> m_updateCallback;
|
||||||
|
std::shared_mutex m_mutex;
|
||||||
|
};
|
||||||
53
src/modules/Workspaces/WorkspacesLib/Result.h
Normal file
53
src/modules/Workspaces/WorkspacesLib/Result.h
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class Ok
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit constexpr Ok(T value) :
|
||||||
|
value(std::move(value)) {}
|
||||||
|
|
||||||
|
constexpr T&& get() { return std::move(value); }
|
||||||
|
|
||||||
|
T value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class Error
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit constexpr Error(T value) :
|
||||||
|
value(std::move(value)) {}
|
||||||
|
|
||||||
|
constexpr T&& get() { return std::move(value); }
|
||||||
|
|
||||||
|
T value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename OkT, typename ErrT>
|
||||||
|
class Result
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using VariantT = std::variant<Ok<OkT>, Error<ErrT>>;
|
||||||
|
|
||||||
|
constexpr Result(Ok<OkT> value) :
|
||||||
|
variant(std::move(value))
|
||||||
|
{}
|
||||||
|
|
||||||
|
constexpr Result(Error<ErrT> value) :
|
||||||
|
variant(std::move(value))
|
||||||
|
{}
|
||||||
|
|
||||||
|
constexpr bool isOk() const { return std::holds_alternative<Ok<OkT>>(variant); }
|
||||||
|
constexpr bool isError() const { return std::holds_alternative<Error<ErrT>>(variant); }
|
||||||
|
|
||||||
|
constexpr OkT value() const { return std::get<Ok<OkT>>(variant).value; }
|
||||||
|
constexpr ErrT error() const { return std::get<Error<ErrT>>(variant).value; }
|
||||||
|
|
||||||
|
constexpr OkT&& getValue() { return std::get<Ok<OkT>>(variant).get(); }
|
||||||
|
constexpr ErrT&& getError() { return std::get<Error<ErrT>>(variant).get(); }
|
||||||
|
|
||||||
|
VariantT variant;
|
||||||
|
};
|
||||||
@@ -21,12 +21,6 @@ namespace WorkspacesData
|
|||||||
std::wstring settingsFolderPath = PTSettingsHelper::get_module_save_folder_location(NonLocalizable::ModuleKey);
|
std::wstring settingsFolderPath = PTSettingsHelper::get_module_save_folder_location(NonLocalizable::ModuleKey);
|
||||||
return settingsFolderPath + L"\\temp-workspaces.json";
|
return settingsFolderPath + L"\\temp-workspaces.json";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::wstring LaunchWorkspacesFile()
|
|
||||||
{
|
|
||||||
std::wstring settingsFolderPath = PTSettingsHelper::get_module_save_folder_location(NonLocalizable::ModuleKey);
|
|
||||||
return settingsFolderPath + L"\\launch-workspaces.json";
|
|
||||||
}
|
|
||||||
|
|
||||||
RECT WorkspacesProject::Application::Position::toRect() const noexcept
|
RECT WorkspacesProject::Application::Position::toRect() const noexcept
|
||||||
{
|
{
|
||||||
@@ -420,19 +414,40 @@ namespace WorkspacesData
|
|||||||
{
|
{
|
||||||
namespace NonLocalizable
|
namespace NonLocalizable
|
||||||
{
|
{
|
||||||
const static wchar_t* NameID = L"name";
|
const static wchar_t* ApplicationID = L"application";
|
||||||
const static wchar_t* PathID = L"path";
|
|
||||||
const static wchar_t* StateID = L"state";
|
const static wchar_t* StateID = L"state";
|
||||||
}
|
}
|
||||||
|
|
||||||
json::JsonObject ToJson(const AppLaunchInfo& data)
|
json::JsonObject ToJson(const LaunchingAppState& data)
|
||||||
{
|
{
|
||||||
json::JsonObject json{};
|
json::JsonObject json{};
|
||||||
json.SetNamedValue(NonLocalizable::NameID, json::value(data.name));
|
json.SetNamedValue(NonLocalizable::ApplicationID, WorkspacesProjectJSON::ApplicationJSON::ToJson(data.application));
|
||||||
json.SetNamedValue(NonLocalizable::PathID, json::value(data.path));
|
json.SetNamedValue(NonLocalizable::StateID, json::value(static_cast<int>(data.state)));
|
||||||
json.SetNamedValue(NonLocalizable::StateID, json::value(data.state));
|
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<LaunchingAppState> FromJson(const json::JsonObject& json)
|
||||||
|
{
|
||||||
|
LaunchingAppState result{};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto app = WorkspacesProjectJSON::ApplicationJSON::FromJson(json.GetNamedObject(NonLocalizable::ApplicationID));
|
||||||
|
if (!app.has_value())
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.application = app.value();
|
||||||
|
result.state = static_cast<LaunchingState>(json.GetNamedNumber(NonLocalizable::StateID));
|
||||||
|
}
|
||||||
|
catch (const winrt::hresult_error&)
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace AppLaunchInfoListJSON
|
namespace AppLaunchInfoListJSON
|
||||||
@@ -442,18 +457,46 @@ namespace WorkspacesData
|
|||||||
const static wchar_t* AppLaunchInfoID = L"appLaunchInfos";
|
const static wchar_t* AppLaunchInfoID = L"appLaunchInfos";
|
||||||
}
|
}
|
||||||
|
|
||||||
json::JsonObject ToJson(const std::vector<AppLaunchInfo>& data)
|
json::JsonObject ToJson(const LaunchingAppStateMap& data)
|
||||||
{
|
{
|
||||||
json::JsonObject json{};
|
json::JsonObject json{};
|
||||||
json::JsonArray appLaunchInfoArray{};
|
json::JsonArray appLaunchInfoArray{};
|
||||||
for (const auto& appLaunchInfo : data)
|
for (const auto& appLaunchInfo : data)
|
||||||
{
|
{
|
||||||
appLaunchInfoArray.Append(AppLaunchInfoJSON::ToJson(appLaunchInfo));
|
appLaunchInfoArray.Append(AppLaunchInfoJSON::ToJson(appLaunchInfo.second));
|
||||||
}
|
}
|
||||||
|
|
||||||
json.SetNamedValue(NonLocalizable::AppLaunchInfoID, appLaunchInfoArray);
|
json.SetNamedValue(NonLocalizable::AppLaunchInfoID, appLaunchInfoArray);
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<LaunchingAppStateMap> FromJson(const json::JsonObject& json)
|
||||||
|
{
|
||||||
|
LaunchingAppStateMap result{};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto array = json.GetNamedArray(NonLocalizable::AppLaunchInfoID);
|
||||||
|
for (uint32_t i = 0; i < array.Size(); ++i)
|
||||||
|
{
|
||||||
|
auto obj = AppLaunchInfoJSON::FromJson(array.GetObjectAt(i));
|
||||||
|
if (obj.has_value())
|
||||||
|
{
|
||||||
|
result.insert({ obj.value().application, obj.value() });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const winrt::hresult_error&)
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace AppLaunchDataJSON
|
namespace AppLaunchDataJSON
|
||||||
@@ -467,7 +510,7 @@ namespace WorkspacesData
|
|||||||
json::JsonObject ToJson(const AppLaunchData& data)
|
json::JsonObject ToJson(const AppLaunchData& data)
|
||||||
{
|
{
|
||||||
json::JsonObject json{};
|
json::JsonObject json{};
|
||||||
json.SetNamedValue(NonLocalizable::AppsID, AppLaunchInfoListJSON::ToJson(data.appLaunchInfoList));
|
json.SetNamedValue(NonLocalizable::AppsID, AppLaunchInfoListJSON::ToJson(data.appsStateList));
|
||||||
json.SetNamedValue(NonLocalizable::ProcessID, json::value(data.launcherProcessID));
|
json.SetNamedValue(NonLocalizable::ProcessID, json::value(data.launcherProcessID));
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
#include <common/utils/json.h>
|
#include <common/utils/json.h>
|
||||||
|
|
||||||
|
#include <WorkspacesLib/LaunchingStateEnum.h>
|
||||||
|
|
||||||
namespace WorkspacesData
|
namespace WorkspacesData
|
||||||
{
|
{
|
||||||
std::wstring WorkspacesFile();
|
std::wstring WorkspacesFile();
|
||||||
std::wstring TempWorkspacesFile();
|
std::wstring TempWorkspacesFile();
|
||||||
std::wstring LaunchWorkspacesFile();
|
|
||||||
|
|
||||||
struct WorkspacesProject
|
struct WorkspacesProject
|
||||||
{
|
{
|
||||||
@@ -21,10 +22,7 @@ namespace WorkspacesData
|
|||||||
|
|
||||||
RECT toRect() const noexcept;
|
RECT toRect() const noexcept;
|
||||||
|
|
||||||
inline bool operator==(const Position& other) const noexcept
|
auto operator<=>(const Position&) const = default;
|
||||||
{
|
|
||||||
return x == other.x && y == other.y && width == other.width && height == other.height;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
std::wstring name;
|
std::wstring name;
|
||||||
@@ -39,6 +37,8 @@ namespace WorkspacesData
|
|||||||
bool isMaximized{};
|
bool isMaximized{};
|
||||||
Position position{};
|
Position position{};
|
||||||
unsigned int monitor{};
|
unsigned int monitor{};
|
||||||
|
|
||||||
|
auto operator<=>(const Application&) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Monitor
|
struct Monitor
|
||||||
@@ -80,34 +80,22 @@ namespace WorkspacesData
|
|||||||
std::vector<WorkspacesProject> projects;
|
std::vector<WorkspacesProject> projects;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AppLaunchInfo
|
struct LaunchingAppState
|
||||||
{
|
{
|
||||||
std::wstring name;
|
WorkspacesData::WorkspacesProject::Application application;
|
||||||
std::wstring path;
|
HWND window{};
|
||||||
std::wstring state;
|
LaunchingState state { LaunchingState::Waiting };
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace AppLaunchInfoJSON
|
using LaunchingAppStateMap = std::map<WorkspacesData::WorkspacesProject::Application, LaunchingAppState>;
|
||||||
{
|
using LaunchingAppStateList = std::vector<std::pair<WorkspacesData::WorkspacesProject::Application, LaunchingState>>;
|
||||||
json::JsonObject ToJson(const AppLaunchInfo& data);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace AppLaunchInfoListJSON
|
|
||||||
{
|
|
||||||
json::JsonObject ToJson(const std::vector<AppLaunchInfo>& data);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AppLaunchData
|
struct AppLaunchData
|
||||||
{
|
{
|
||||||
std::vector<AppLaunchInfo> appLaunchInfoList;
|
LaunchingAppStateMap appsStateList;
|
||||||
int launcherProcessID = 0;
|
int launcherProcessID = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace AppLaunchDataJSON
|
|
||||||
{
|
|
||||||
json::JsonObject ToJson(const AppLaunchData& data);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace WorkspacesProjectJSON
|
namespace WorkspacesProjectJSON
|
||||||
{
|
{
|
||||||
namespace ApplicationJSON
|
namespace ApplicationJSON
|
||||||
@@ -143,4 +131,22 @@ namespace WorkspacesData
|
|||||||
json::JsonObject ToJson(const std::vector<WorkspacesProject>& data);
|
json::JsonObject ToJson(const std::vector<WorkspacesProject>& data);
|
||||||
std::optional<std::vector<WorkspacesProject>> FromJson(const json::JsonObject& json);
|
std::optional<std::vector<WorkspacesProject>> FromJson(const json::JsonObject& json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace AppLaunchInfoJSON
|
||||||
|
{
|
||||||
|
json::JsonObject ToJson(const LaunchingAppState& data);
|
||||||
|
std::optional<LaunchingAppState> FromJson(const json::JsonObject& json);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace AppLaunchInfoListJSON
|
||||||
|
{
|
||||||
|
json::JsonObject ToJson(const LaunchingAppStateMap& data);
|
||||||
|
std::optional<LaunchingAppStateMap> FromJson(const json::JsonObject& json);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace AppLaunchDataJSON
|
||||||
|
{
|
||||||
|
json::JsonObject ToJson(const AppLaunchData& data);
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -33,19 +33,32 @@
|
|||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="AppUtils.h" />
|
<ClInclude Include="AppUtils.h" />
|
||||||
|
<ClInclude Include="IPCHelper.h" />
|
||||||
|
<ClInclude Include="JsonUtils.h" />
|
||||||
|
<ClInclude Include="LaunchingStateEnum.h" />
|
||||||
|
<ClInclude Include="LaunchingStatus.h" />
|
||||||
<ClInclude Include="pch.h" />
|
<ClInclude Include="pch.h" />
|
||||||
|
<ClInclude Include="Result.h" />
|
||||||
|
<ClInclude Include="utils.h" />
|
||||||
<ClInclude Include="WorkspacesData.h" />
|
<ClInclude Include="WorkspacesData.h" />
|
||||||
<ClInclude Include="trace.h" />
|
<ClInclude Include="trace.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="AppUtils.cpp" />
|
<ClCompile Include="AppUtils.cpp" />
|
||||||
|
<ClCompile Include="IPCHelper.cpp" />
|
||||||
|
<ClCompile Include="JsonUtils.cpp" />
|
||||||
|
<ClCompile Include="LaunchingStatus.cpp" />
|
||||||
<ClCompile Include="pch.cpp">
|
<ClCompile Include="pch.cpp">
|
||||||
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
|
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="two_way_pipe_message_ipc.cpp" />
|
||||||
<ClCompile Include="WorkspacesData.cpp" />
|
<ClCompile Include="WorkspacesData.cpp" />
|
||||||
<ClCompile Include="trace.cpp" />
|
<ClCompile Include="trace.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj">
|
||||||
|
<Project>{f055103b-f80b-4d0c-bf48-057c55620033}</Project>
|
||||||
|
</ProjectReference>
|
||||||
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
|
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
|
||||||
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
|
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
|
|||||||
@@ -23,6 +23,24 @@
|
|||||||
<ClInclude Include="AppUtils.h">
|
<ClInclude Include="AppUtils.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="Result.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="JsonUtils.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="IPCHelper.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="utils.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="LaunchingStateEnum.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="LaunchingStatus.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="pch.cpp">
|
<ClCompile Include="pch.cpp">
|
||||||
@@ -37,6 +55,18 @@
|
|||||||
<ClCompile Include="AppUtils.cpp">
|
<ClCompile Include="AppUtils.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="JsonUtils.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="IPCHelper.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="two_way_pipe_message_ipc.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="LaunchingStatus.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
|
|||||||
@@ -0,0 +1,469 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
#include <common/interop/two_way_pipe_message_ipc_impl.h>
|
||||||
|
|
||||||
|
#include <iterator>
|
||||||
|
|
||||||
|
constexpr DWORD BUFSIZE = 1024;
|
||||||
|
|
||||||
|
TwoWayPipeMessageIPC::TwoWayPipeMessageIPC(
|
||||||
|
std::wstring _input_pipe_name,
|
||||||
|
std::wstring _output_pipe_name,
|
||||||
|
callback_function p_func) :
|
||||||
|
impl(new TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl(
|
||||||
|
_input_pipe_name,
|
||||||
|
_output_pipe_name,
|
||||||
|
p_func))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TwoWayPipeMessageIPC::~TwoWayPipeMessageIPC()
|
||||||
|
{
|
||||||
|
delete impl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwoWayPipeMessageIPC::send(std::wstring msg)
|
||||||
|
{
|
||||||
|
impl->send(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwoWayPipeMessageIPC::start(HANDLE _restricted_pipe_token)
|
||||||
|
{
|
||||||
|
impl->start(_restricted_pipe_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwoWayPipeMessageIPC::end()
|
||||||
|
{
|
||||||
|
impl->end();
|
||||||
|
}
|
||||||
|
|
||||||
|
TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::TwoWayPipeMessageIPCImpl(
|
||||||
|
std::wstring _input_pipe_name,
|
||||||
|
std::wstring _output_pipe_name,
|
||||||
|
callback_function p_func)
|
||||||
|
{
|
||||||
|
input_pipe_name = _input_pipe_name;
|
||||||
|
output_pipe_name = _output_pipe_name;
|
||||||
|
dispatch_inc_message_function = p_func;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::send(std::wstring msg)
|
||||||
|
{
|
||||||
|
output_queue.queue_message(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::start(HANDLE _restricted_pipe_token)
|
||||||
|
{
|
||||||
|
output_queue_thread = std::thread(&TwoWayPipeMessageIPCImpl::consume_output_queue_thread, this);
|
||||||
|
input_queue_thread = std::thread(&TwoWayPipeMessageIPCImpl::consume_input_queue_thread, this);
|
||||||
|
input_pipe_thread = std::thread(&TwoWayPipeMessageIPCImpl::start_named_pipe_server, this, _restricted_pipe_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::end()
|
||||||
|
{
|
||||||
|
closed = true;
|
||||||
|
input_queue.interrupt();
|
||||||
|
input_queue_thread.join();
|
||||||
|
output_queue.interrupt();
|
||||||
|
output_queue_thread.join();
|
||||||
|
pipe_connect_handle_mutex.lock();
|
||||||
|
if (current_connect_pipe_handle != NULL)
|
||||||
|
{
|
||||||
|
//Cancels the Pipe currently waiting for a connection.
|
||||||
|
CancelIoEx(current_connect_pipe_handle, NULL);
|
||||||
|
}
|
||||||
|
pipe_connect_handle_mutex.unlock();
|
||||||
|
input_pipe_thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::send_pipe_message(std::wstring message)
|
||||||
|
{
|
||||||
|
// Adapted from https://learn.microsoft.com/windows/win32/ipc/named-pipe-client
|
||||||
|
HANDLE output_pipe_handle;
|
||||||
|
const wchar_t* message_send = message.c_str();
|
||||||
|
BOOL fSuccess = FALSE;
|
||||||
|
DWORD cbToWrite, cbWritten, dwMode;
|
||||||
|
const wchar_t* lpszPipename = output_pipe_name.c_str();
|
||||||
|
|
||||||
|
// Try to open a named pipe; wait for it, if necessary.
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
output_pipe_handle = CreateFile(
|
||||||
|
lpszPipename, // pipe name
|
||||||
|
GENERIC_READ | // read and write access
|
||||||
|
GENERIC_WRITE,
|
||||||
|
0, // no sharing
|
||||||
|
NULL, // default security attributes
|
||||||
|
OPEN_EXISTING, // opens existing pipe
|
||||||
|
0, // default attributes
|
||||||
|
NULL); // no template file
|
||||||
|
|
||||||
|
// Break if the pipe handle is valid.
|
||||||
|
|
||||||
|
if (output_pipe_handle != INVALID_HANDLE_VALUE)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Exit if an error other than ERROR_PIPE_BUSY occurs.
|
||||||
|
DWORD curr_error = 0;
|
||||||
|
if ((curr_error = GetLastError()) != ERROR_PIPE_BUSY)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All pipe instances are busy, so wait for 20 seconds.
|
||||||
|
|
||||||
|
if (!WaitNamedPipe(lpszPipename, 20000))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dwMode = PIPE_READMODE_MESSAGE;
|
||||||
|
fSuccess = SetNamedPipeHandleState(
|
||||||
|
output_pipe_handle, // pipe handle
|
||||||
|
&dwMode, // new pipe mode
|
||||||
|
NULL, // don't set maximum bytes
|
||||||
|
NULL); // don't set maximum time
|
||||||
|
if (!fSuccess)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a message to the pipe server.
|
||||||
|
|
||||||
|
cbToWrite = (lstrlen(message_send)) * sizeof(WCHAR); // no need to send final '\0'. Pipe is in message mode.
|
||||||
|
|
||||||
|
fSuccess = WriteFile(
|
||||||
|
output_pipe_handle, // pipe handle
|
||||||
|
message_send, // message
|
||||||
|
cbToWrite, // message length
|
||||||
|
&cbWritten, // bytes written
|
||||||
|
NULL); // not overlapped
|
||||||
|
if (!fSuccess)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CloseHandle(output_pipe_handle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::consume_output_queue_thread()
|
||||||
|
{
|
||||||
|
while (!closed)
|
||||||
|
{
|
||||||
|
std::wstring message = output_queue.pop_message();
|
||||||
|
if (message.length() == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
send_pipe_message(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::GetLogonSID(HANDLE hToken, PSID* ppsid)
|
||||||
|
{
|
||||||
|
// From https://learn.microsoft.com/previous-versions/aa446670(v=vs.85)
|
||||||
|
BOOL bSuccess = FALSE;
|
||||||
|
DWORD dwIndex;
|
||||||
|
DWORD dwLength = 0;
|
||||||
|
PTOKEN_GROUPS ptg = NULL;
|
||||||
|
|
||||||
|
// Verify the parameter passed in is not NULL.
|
||||||
|
if (NULL == ppsid)
|
||||||
|
goto Cleanup;
|
||||||
|
|
||||||
|
// Get required buffer size and allocate the TOKEN_GROUPS buffer.
|
||||||
|
|
||||||
|
if (!GetTokenInformation(
|
||||||
|
hToken, // handle to the access token
|
||||||
|
TokenGroups, // get information about the token's groups
|
||||||
|
static_cast<LPVOID>(ptg), // pointer to TOKEN_GROUPS buffer
|
||||||
|
0, // size of buffer
|
||||||
|
&dwLength // receives required buffer size
|
||||||
|
))
|
||||||
|
{
|
||||||
|
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
|
||||||
|
goto Cleanup;
|
||||||
|
|
||||||
|
ptg = static_cast<PTOKEN_GROUPS>(HeapAlloc(GetProcessHeap(),
|
||||||
|
HEAP_ZERO_MEMORY,
|
||||||
|
dwLength));
|
||||||
|
|
||||||
|
if (ptg == NULL)
|
||||||
|
goto Cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the token group information from the access token.
|
||||||
|
|
||||||
|
if (!GetTokenInformation(
|
||||||
|
hToken, // handle to the access token
|
||||||
|
TokenGroups, // get information about the token's groups
|
||||||
|
static_cast<LPVOID>(ptg), // pointer to TOKEN_GROUPS buffer
|
||||||
|
dwLength, // size of buffer
|
||||||
|
&dwLength // receives required buffer size
|
||||||
|
))
|
||||||
|
{
|
||||||
|
goto Cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through the groups to find the logon SID.
|
||||||
|
|
||||||
|
for (dwIndex = 0; dwIndex < ptg->GroupCount; dwIndex++)
|
||||||
|
if ((ptg->Groups[dwIndex].Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID)
|
||||||
|
{
|
||||||
|
// Found the logon SID; make a copy of it.
|
||||||
|
|
||||||
|
dwLength = GetLengthSid(ptg->Groups[dwIndex].Sid);
|
||||||
|
*ppsid = static_cast<PSID>(HeapAlloc(GetProcessHeap(),
|
||||||
|
HEAP_ZERO_MEMORY,
|
||||||
|
dwLength));
|
||||||
|
if (*ppsid == NULL)
|
||||||
|
goto Cleanup;
|
||||||
|
if (!CopySid(dwLength, *ppsid, ptg->Groups[dwIndex].Sid))
|
||||||
|
{
|
||||||
|
HeapFree(GetProcessHeap(), 0, static_cast<LPVOID>(*ppsid));
|
||||||
|
goto Cleanup;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bSuccess = TRUE;
|
||||||
|
|
||||||
|
Cleanup:
|
||||||
|
|
||||||
|
// Free the buffer for the token groups.
|
||||||
|
|
||||||
|
if (ptg != NULL)
|
||||||
|
HeapFree(GetProcessHeap(), 0, static_cast<LPVOID>(ptg));
|
||||||
|
|
||||||
|
return bSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::FreeLogonSID(PSID* ppsid)
|
||||||
|
{
|
||||||
|
// From https://learn.microsoft.com/previous-versions/aa446670(v=vs.85)
|
||||||
|
HeapFree(GetProcessHeap(), 0, static_cast<LPVOID>(*ppsid));
|
||||||
|
}
|
||||||
|
|
||||||
|
int TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::change_pipe_security_allow_restricted_token(HANDLE handle, HANDLE token)
|
||||||
|
{
|
||||||
|
PACL old_dacl, new_dacl;
|
||||||
|
PSECURITY_DESCRIPTOR sd;
|
||||||
|
EXPLICIT_ACCESS ea;
|
||||||
|
PSID user_restricted;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
if (!GetLogonSID(token, &user_restricted))
|
||||||
|
{
|
||||||
|
error = 5; // No access error.
|
||||||
|
goto Ldone;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetSecurityInfo(handle,
|
||||||
|
SE_KERNEL_OBJECT,
|
||||||
|
DACL_SECURITY_INFORMATION,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
&old_dacl,
|
||||||
|
NULL,
|
||||||
|
&sd))
|
||||||
|
{
|
||||||
|
error = GetLastError();
|
||||||
|
goto Lclean_sid;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&ea, 0, sizeof(EXPLICIT_ACCESS));
|
||||||
|
ea.grfAccessPermissions |= GENERIC_READ | FILE_WRITE_ATTRIBUTES;
|
||||||
|
ea.grfAccessPermissions |= GENERIC_WRITE | FILE_READ_ATTRIBUTES;
|
||||||
|
ea.grfAccessPermissions |= SYNCHRONIZE;
|
||||||
|
ea.grfAccessMode = SET_ACCESS;
|
||||||
|
ea.grfInheritance = NO_INHERITANCE;
|
||||||
|
ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||||
|
ea.Trustee.TrusteeType = TRUSTEE_IS_USER;
|
||||||
|
ea.Trustee.ptstrName = static_cast<LPTSTR>(user_restricted);
|
||||||
|
|
||||||
|
if (SetEntriesInAcl(1, &ea, old_dacl, &new_dacl))
|
||||||
|
{
|
||||||
|
error = GetLastError();
|
||||||
|
goto Lclean_sd;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SetSecurityInfo(handle,
|
||||||
|
SE_KERNEL_OBJECT,
|
||||||
|
DACL_SECURITY_INFORMATION,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
new_dacl,
|
||||||
|
NULL))
|
||||||
|
{
|
||||||
|
error = GetLastError();
|
||||||
|
goto Lclean_dacl;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = 0;
|
||||||
|
|
||||||
|
Lclean_dacl:
|
||||||
|
LocalFree(static_cast<HLOCAL>(new_dacl));
|
||||||
|
Lclean_sd:
|
||||||
|
LocalFree(static_cast<HLOCAL>(sd));
|
||||||
|
Lclean_sid:
|
||||||
|
FreeLogonSID(&user_restricted);
|
||||||
|
Ldone:
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
HANDLE TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::create_medium_integrity_token()
|
||||||
|
{
|
||||||
|
HANDLE restricted_token_handle;
|
||||||
|
SAFER_LEVEL_HANDLE level_handle = NULL;
|
||||||
|
DWORD sid_size = SECURITY_MAX_SID_SIZE;
|
||||||
|
BYTE medium_sid[SECURITY_MAX_SID_SIZE];
|
||||||
|
if (!SaferCreateLevel(SAFER_SCOPEID_USER, SAFER_LEVELID_NORMALUSER, SAFER_LEVEL_OPEN, &level_handle, NULL))
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (!SaferComputeTokenFromLevel(level_handle, NULL, &restricted_token_handle, 0, NULL))
|
||||||
|
{
|
||||||
|
SaferCloseLevel(level_handle);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
SaferCloseLevel(level_handle);
|
||||||
|
|
||||||
|
if (!CreateWellKnownSid(WinMediumLabelSid, nullptr, medium_sid, &sid_size))
|
||||||
|
{
|
||||||
|
CloseHandle(restricted_token_handle);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
TOKEN_MANDATORY_LABEL integrity_level = { 0 };
|
||||||
|
integrity_level.Label.Attributes = SE_GROUP_INTEGRITY;
|
||||||
|
integrity_level.Label.Sid = reinterpret_cast<PSID>(medium_sid);
|
||||||
|
|
||||||
|
if (!SetTokenInformation(restricted_token_handle, TokenIntegrityLevel, &integrity_level, sizeof(integrity_level)))
|
||||||
|
{
|
||||||
|
CloseHandle(restricted_token_handle);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return restricted_token_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::handle_pipe_connection(HANDLE input_pipe_handle)
|
||||||
|
{
|
||||||
|
if (!input_pipe_handle)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
constexpr DWORD readBlockBytes = BUFSIZE;
|
||||||
|
std::wstring message;
|
||||||
|
size_t iBlock = 0;
|
||||||
|
message.reserve(BUFSIZE);
|
||||||
|
bool ok;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
constexpr size_t charsPerBlock = readBlockBytes / sizeof(message[0]);
|
||||||
|
message.resize(message.size() + charsPerBlock);
|
||||||
|
DWORD bytesRead = 0;
|
||||||
|
ok = ReadFile(
|
||||||
|
input_pipe_handle,
|
||||||
|
// read the message directly into the string block by block simultaneously resizing it
|
||||||
|
message.data() + iBlock * charsPerBlock,
|
||||||
|
readBlockBytes,
|
||||||
|
&bytesRead,
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
if (!ok && GetLastError() != ERROR_MORE_DATA)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
iBlock++;
|
||||||
|
} while (!ok);
|
||||||
|
// trim the message's buffer
|
||||||
|
const auto nullCharPos = message.find_last_not_of(L'\0');
|
||||||
|
if (nullCharPos != std::wstring::npos)
|
||||||
|
{
|
||||||
|
message.resize(nullCharPos + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
input_queue.queue_message(std::move(message));
|
||||||
|
|
||||||
|
// Flush the pipe to allow the client to read the pipe's contents
|
||||||
|
// before disconnecting. Then disconnect the pipe, and close the
|
||||||
|
// handle to this pipe instance.
|
||||||
|
|
||||||
|
FlushFileBuffers(input_pipe_handle);
|
||||||
|
DisconnectNamedPipe(input_pipe_handle);
|
||||||
|
CloseHandle(input_pipe_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::start_named_pipe_server(HANDLE token)
|
||||||
|
{
|
||||||
|
// Adapted from https://learn.microsoft.com/windows/win32/ipc/multithreaded-pipe-server
|
||||||
|
const wchar_t* pipe_name = input_pipe_name.c_str();
|
||||||
|
BOOL connected = FALSE;
|
||||||
|
HANDLE connect_pipe_handle = INVALID_HANDLE_VALUE;
|
||||||
|
while (!closed)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::unique_lock lock(pipe_connect_handle_mutex);
|
||||||
|
connect_pipe_handle = CreateNamedPipe(
|
||||||
|
pipe_name,
|
||||||
|
PIPE_ACCESS_DUPLEX |
|
||||||
|
WRITE_DAC,
|
||||||
|
PIPE_TYPE_MESSAGE |
|
||||||
|
PIPE_READMODE_MESSAGE |
|
||||||
|
PIPE_WAIT,
|
||||||
|
PIPE_UNLIMITED_INSTANCES,
|
||||||
|
BUFSIZE,
|
||||||
|
BUFSIZE,
|
||||||
|
0,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
if (connect_pipe_handle == INVALID_HANDLE_VALUE)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token != NULL)
|
||||||
|
{
|
||||||
|
change_pipe_security_allow_restricted_token(connect_pipe_handle, token);
|
||||||
|
}
|
||||||
|
current_connect_pipe_handle = connect_pipe_handle;
|
||||||
|
}
|
||||||
|
connected = ConnectNamedPipe(connect_pipe_handle, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
|
||||||
|
{
|
||||||
|
std::unique_lock lock(pipe_connect_handle_mutex);
|
||||||
|
current_connect_pipe_handle = NULL;
|
||||||
|
}
|
||||||
|
if (connected)
|
||||||
|
{
|
||||||
|
std::thread(&TwoWayPipeMessageIPCImpl::handle_pipe_connection, this, connect_pipe_handle).detach();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Client could not connect.
|
||||||
|
CloseHandle(connect_pipe_handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwoWayPipeMessageIPC::TwoWayPipeMessageIPCImpl::consume_input_queue_thread()
|
||||||
|
{
|
||||||
|
while (!closed)
|
||||||
|
{
|
||||||
|
outgoing_message = L"";
|
||||||
|
std::wstring message = input_queue.pop_message();
|
||||||
|
if (message.length() == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if callback method exists first before trying to call it.
|
||||||
|
// otherwise just store the response message in a variable.
|
||||||
|
if (dispatch_inc_message_function != nullptr)
|
||||||
|
{
|
||||||
|
dispatch_inc_message_function(message);
|
||||||
|
}
|
||||||
|
outgoing_message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,12 +3,12 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
std::vector<std::string> split(std::string s, const std::string& delimiter)
|
std::vector<std::wstring> split(std::wstring s, const std::wstring& delimiter)
|
||||||
{
|
{
|
||||||
std::vector<std::string> tokens;
|
std::vector<std::wstring> tokens;
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
std::string token;
|
std::wstring token;
|
||||||
while ((pos = s.find(delimiter)) != std::string::npos)
|
while ((pos = s.find(delimiter)) != std::wstring::npos)
|
||||||
{
|
{
|
||||||
token = s.substr(0, pos);
|
token = s.substr(0, pos);
|
||||||
tokens.push_back(token);
|
tokens.push_back(token);
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
// Non-localizable
|
// Non-localizable
|
||||||
const std::wstring workspacesLauncherPath = L"PowerToys.WorkspacesLauncher.exe";
|
const std::wstring workspacesLauncherPath = L"PowerToys.WorkspacesLauncher.exe";
|
||||||
|
const std::wstring workspacesWindowArrangerPath = L"PowerToys.WorkspacesWindowArranger.exe";
|
||||||
const std::wstring workspacesSnapshotToolPath = L"PowerToys.WorkspacesSnapshotTool.exe";
|
const std::wstring workspacesSnapshotToolPath = L"PowerToys.WorkspacesSnapshotTool.exe";
|
||||||
const std::wstring workspacesEditorPath = L"PowerToys.WorkspacesEditor.exe";
|
const std::wstring workspacesEditorPath = L"PowerToys.WorkspacesEditor.exe";
|
||||||
|
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <WorkspacesLib/WorkspacesData.h>
|
|
||||||
|
|
||||||
#include <common/logger/logger.h>
|
|
||||||
|
|
||||||
namespace WorkspacesJsonUtils
|
|
||||||
{
|
|
||||||
inline std::vector<WorkspacesData::WorkspacesProject> Read(const std::wstring& fileName)
|
|
||||||
{
|
|
||||||
std::vector<WorkspacesData::WorkspacesProject> projects{};
|
|
||||||
try
|
|
||||||
{
|
|
||||||
auto savedProjectsJson = json::from_file(fileName);
|
|
||||||
if (savedProjectsJson.has_value())
|
|
||||||
{
|
|
||||||
auto savedProjects = WorkspacesData::WorkspacesListJSON::FromJson(savedProjectsJson.value());
|
|
||||||
if (savedProjects.has_value())
|
|
||||||
{
|
|
||||||
projects = savedProjects.value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (std::exception ex)
|
|
||||||
{
|
|
||||||
Logger::error("Error reading workspaces file. {}", ex.what());
|
|
||||||
}
|
|
||||||
|
|
||||||
return projects;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void Write(const std::wstring& fileName, const std::vector<WorkspacesData::WorkspacesProject>& projects)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json::to_file(fileName, WorkspacesData::WorkspacesListJSON::ToJson(projects));
|
|
||||||
}
|
|
||||||
catch (std::exception ex)
|
|
||||||
{
|
|
||||||
Logger::error("Error writing workspaces file. {}", ex.what());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void Write(const std::wstring& fileName, const WorkspacesData::WorkspacesProject& project)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
json::to_file(fileName, WorkspacesData::WorkspacesProjectJSON::ToJson(project));
|
|
||||||
}
|
|
||||||
catch (std::exception ex)
|
|
||||||
{
|
|
||||||
Logger::error("Error writing workspaces file. {}", ex.what());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -133,7 +133,6 @@
|
|||||||
<ClCompile Include="SnapshotUtils.cpp" />
|
<ClCompile Include="SnapshotUtils.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="JsonUtils.h" />
|
|
||||||
<ClInclude Include="pch.h" />
|
<ClInclude Include="pch.h" />
|
||||||
<ClInclude Include="resource.base.h" />
|
<ClInclude Include="resource.base.h" />
|
||||||
<ClInclude Include="SnapshotUtils.h" />
|
<ClInclude Include="SnapshotUtils.h" />
|
||||||
|
|||||||
@@ -18,9 +18,6 @@
|
|||||||
<ClInclude Include="pch.h">
|
<ClInclude Include="pch.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="JsonUtils.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="SnapshotUtils.h">
|
<ClInclude Include="SnapshotUtils.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
|||||||
@@ -5,21 +5,21 @@
|
|||||||
#include <workspaces-common/GuidUtils.h>
|
#include <workspaces-common/GuidUtils.h>
|
||||||
#include <workspaces-common/MonitorUtils.h>
|
#include <workspaces-common/MonitorUtils.h>
|
||||||
|
|
||||||
|
#include <WorkspacesLib/JsonUtils.h>
|
||||||
#include <WorkspacesLib/WorkspacesData.h>
|
#include <WorkspacesLib/WorkspacesData.h>
|
||||||
|
|
||||||
#include <JsonUtils.h>
|
|
||||||
#include <SnapshotUtils.h>
|
#include <SnapshotUtils.h>
|
||||||
|
|
||||||
#include <common/utils/gpo.h>
|
#include <common/utils/gpo.h>
|
||||||
#include <common/utils/logger_helper.h>
|
#include <common/utils/logger_helper.h>
|
||||||
#include <common/utils/UnhandledExceptionHandler.h>
|
#include <common/utils/UnhandledExceptionHandler.h>
|
||||||
|
|
||||||
const std::wstring moduleName = L"Workspaces\\ProjectsSnapshotTool";
|
const std::wstring moduleName = L"Workspaces\\WorkspacesSnapshotTool";
|
||||||
const std::wstring internalPath = L"";
|
const std::wstring internalPath = L"";
|
||||||
|
|
||||||
int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdLine, int cmdShow)
|
int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdLine, int cmdShow)
|
||||||
{
|
{
|
||||||
LoggerHelpers::init_logger(moduleName, internalPath, LogSettings::workspacesLauncherLoggerName);
|
LoggerHelpers::init_logger(moduleName, internalPath, LogSettings::workspacesSnapshotToolLoggerName);
|
||||||
InitUnhandledExceptionHandler();
|
InitUnhandledExceptionHandler();
|
||||||
|
|
||||||
if (powertoys_gpo::getConfiguredWorkspacesEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled)
|
if (powertoys_gpo::getConfiguredWorkspacesEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled)
|
||||||
@@ -46,14 +46,6 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdLine, int cm
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::wstring fileName = WorkspacesData::WorkspacesFile();
|
|
||||||
std::string cmdLineStr(cmdLine);
|
|
||||||
if (!cmdLineStr.empty())
|
|
||||||
{
|
|
||||||
std::wstring fileNameParam(cmdLineStr.begin(), cmdLineStr.end());
|
|
||||||
fileName = fileNameParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
// create new project
|
// create new project
|
||||||
time_t creationTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
time_t creationTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||||
WorkspacesData::WorkspacesProject project{ .id = CreateGuidString(), .creationTime = creationTime };
|
WorkspacesData::WorkspacesProject project{ .id = CreateGuidString(), .creationTime = creationTime };
|
||||||
@@ -75,7 +67,7 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdLine, int cm
|
|||||||
return monitorNumber;
|
return monitorNumber;
|
||||||
});
|
});
|
||||||
|
|
||||||
WorkspacesJsonUtils::Write(WorkspacesData::TempWorkspacesFile(), project);
|
JsonUtils::Write(WorkspacesData::TempWorkspacesFile(), project);
|
||||||
Logger::trace(L"WorkspacesProject {}:{} created", project.name, project.id);
|
Logger::trace(L"WorkspacesProject {}:{} created", project.name, project.id);
|
||||||
|
|
||||||
CoUninitialize();
|
CoUninitialize();
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ImportGroup Label="PropertySheets" />
|
||||||
|
<PropertyGroup Label="UserMacros" />
|
||||||
|
<!--
|
||||||
|
To customize common C++/WinRT project properties:
|
||||||
|
* right-click the project node
|
||||||
|
* expand the Common Properties item
|
||||||
|
* select the C++/WinRT property page
|
||||||
|
|
||||||
|
For more advanced scenarios, and complete documentation, please see:
|
||||||
|
https://github.com/Microsoft/cppwinrt/tree/master/nuget
|
||||||
|
-->
|
||||||
|
<PropertyGroup />
|
||||||
|
<ItemDefinitionGroup />
|
||||||
|
</Project>
|
||||||
120
src/modules/Workspaces/WorkspacesWindowArranger/Resource.resx
Normal file
120
src/modules/Workspaces/WorkspacesWindowArranger/Resource.resx
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
</root>
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "WindowArranger.h"
|
||||||
|
|
||||||
|
#include <common/logger/logger.h>
|
||||||
|
#include <common/utils/OnThreadExecutor.h>
|
||||||
|
#include <common/utils/process_path.h>
|
||||||
|
#include <common/utils/winapi_error.h>
|
||||||
|
|
||||||
|
#include <workspaces-common/MonitorUtils.h>
|
||||||
|
#include <workspaces-common/WindowEnumerator.h>
|
||||||
|
#include <workspaces-common/WindowFilter.h>
|
||||||
|
#include <workspaces-common/WindowUtils.h>
|
||||||
|
|
||||||
|
#include <WindowProperties/WorkspacesWindowPropertyUtils.h>
|
||||||
|
|
||||||
|
namespace FancyZones
|
||||||
|
{
|
||||||
|
inline void ScreenToWorkAreaCoords(HWND window, HMONITOR monitor, RECT& rect)
|
||||||
|
{
|
||||||
|
MONITORINFOEXW monitorInfo{ sizeof(MONITORINFOEXW) };
|
||||||
|
GetMonitorInfoW(monitor, &monitorInfo);
|
||||||
|
|
||||||
|
auto xOffset = monitorInfo.rcWork.left - monitorInfo.rcMonitor.left;
|
||||||
|
auto yOffset = monitorInfo.rcWork.top - monitorInfo.rcMonitor.top;
|
||||||
|
|
||||||
|
DPIAware::Convert(monitor, rect);
|
||||||
|
|
||||||
|
auto referenceRect = RECT(rect.left - xOffset, rect.top - yOffset, rect.right - xOffset, rect.bottom - yOffset);
|
||||||
|
|
||||||
|
// Now, this rect should be used to determine the monitor and thus taskbar size. This fixes
|
||||||
|
// scenarios where the zone lies approximately between two monitors, and the taskbar is on the left.
|
||||||
|
monitor = MonitorFromRect(&referenceRect, MONITOR_DEFAULTTOPRIMARY);
|
||||||
|
GetMonitorInfoW(monitor, &monitorInfo);
|
||||||
|
|
||||||
|
xOffset = monitorInfo.rcWork.left - monitorInfo.rcMonitor.left;
|
||||||
|
yOffset = monitorInfo.rcWork.top - monitorInfo.rcMonitor.top;
|
||||||
|
|
||||||
|
rect.left -= xOffset;
|
||||||
|
rect.right -= xOffset;
|
||||||
|
rect.top -= yOffset;
|
||||||
|
rect.bottom -= yOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool SizeWindowToRect(HWND window, HMONITOR monitor, bool isMinimized, bool isMaximized, RECT rect) noexcept
|
||||||
|
{
|
||||||
|
WINDOWPLACEMENT placement{};
|
||||||
|
::GetWindowPlacement(window, &placement);
|
||||||
|
|
||||||
|
if (isMinimized)
|
||||||
|
{
|
||||||
|
placement.showCmd = SW_MINIMIZE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ((placement.showCmd != SW_SHOWMINIMIZED) &&
|
||||||
|
(placement.showCmd != SW_MINIMIZE))
|
||||||
|
{
|
||||||
|
if (placement.showCmd == SW_SHOWMAXIMIZED)
|
||||||
|
placement.flags &= ~WPF_RESTORETOMAXIMIZED;
|
||||||
|
|
||||||
|
placement.showCmd = SW_RESTORE;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScreenToWorkAreaCoords(window, monitor, rect);
|
||||||
|
placement.rcNormalPosition = rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
placement.flags |= WPF_ASYNCWINDOWPLACEMENT;
|
||||||
|
|
||||||
|
auto result = ::SetWindowPlacement(window, &placement);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
Logger::error(L"SetWindowPlacement failed, {}", get_last_error_or_default(GetLastError()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure window is moved to the correct monitor before maximize.
|
||||||
|
if (isMaximized)
|
||||||
|
{
|
||||||
|
placement.showCmd = SW_SHOWMAXIMIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do it again, allowing Windows to resize the window and set correct scaling
|
||||||
|
// This fixes Issue #365
|
||||||
|
result = ::SetWindowPlacement(window, &placement);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
Logger::error(L"SetWindowPlacement failed, {}", get_last_error_or_default(GetLastError()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
WindowArranger::WindowArranger(WorkspacesData::WorkspacesProject project, const IPCHelper& ipcHelper) :
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool allFound = allWindowsFound();
|
||||||
|
Logger::info(L"Finished moving new windows, all windows found: {}", allFound);
|
||||||
|
|
||||||
|
if (!allFound)
|
||||||
|
{
|
||||||
|
std::vector<HWND> allWindows = WindowEnumerator::Enumerate(WindowFilter::Filter);
|
||||||
|
for (HWND window : allWindows)
|
||||||
|
{
|
||||||
|
processWindow(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//void WindowArranger::onWindowCreated(HWND window)
|
||||||
|
//{
|
||||||
|
// if (!WindowFilter::Filter(window))
|
||||||
|
// {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// 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())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RECT rect = WindowUtils::GetWindowRect(window);
|
||||||
|
if (rect.right - rect.left <= 0 || rect.bottom - rect.top <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring title = WindowUtils::GetWindowTitle(window);
|
||||||
|
if (title.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring processPath = get_process_path(window);
|
||||||
|
if (processPath.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto data = Utils::Apps::GetApp(processPath, m_installedApps);
|
||||||
|
if (!data.has_value() || data->name.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto iter = std::find_if(m_launchingApps.begin(), m_launchingApps.end(), [&](const auto& val)
|
||||||
|
{ return val.second.state == LaunchingState::Waiting && val.first.name == data.value().name; });
|
||||||
|
if (iter == m_launchingApps.end())
|
||||||
|
{
|
||||||
|
Logger::info(L"A window of {} is not in the project", processPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::debug(L"Move {}", title);
|
||||||
|
iter->second.window = window;
|
||||||
|
if (moveWindow(window, iter->first))
|
||||||
|
{
|
||||||
|
iter->second.state = LaunchingState::LaunchedAndMoved;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
iter->second.state = LaunchingState::Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ipcHelper.send(WorkspacesData::AppLaunchInfoJSON::ToJson({iter->first, nullptr, iter->second.state}).ToString().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowArranger::moveWindow(HWND window, const WorkspacesData::WorkspacesProject::Application& app)
|
||||||
|
{
|
||||||
|
auto snapMonitorIter = std::find_if(m_project.monitors.begin(), m_project.monitors.end(), [&](const WorkspacesData::WorkspacesProject::Monitor& val) { return val.number == app.monitor; });
|
||||||
|
if (snapMonitorIter == m_project.monitors.end())
|
||||||
|
{
|
||||||
|
Logger::error(L"No monitor saved for launching the app");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool launchMinimized = app.isMinimized;
|
||||||
|
bool launchMaximized = app.isMaximized;
|
||||||
|
|
||||||
|
HMONITOR currentMonitor{};
|
||||||
|
UINT currentDpi = DPIAware::DEFAULT_DPI;
|
||||||
|
auto currentMonitorIter = std::find_if(m_monitors.begin(), m_monitors.end(), [&](const WorkspacesData::WorkspacesProject::Monitor& val) { return val.number == app.monitor; });
|
||||||
|
if (currentMonitorIter != m_monitors.end())
|
||||||
|
{
|
||||||
|
currentMonitor = currentMonitorIter->monitor;
|
||||||
|
currentDpi = currentMonitorIter->dpi;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentMonitor = MonitorFromPoint(POINT{ 0, 0 }, MONITOR_DEFAULTTOPRIMARY);
|
||||||
|
DPIAware::GetScreenDPIForMonitor(currentMonitor, currentDpi);
|
||||||
|
launchMinimized = true;
|
||||||
|
launchMaximized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RECT rect = app.position.toRect();
|
||||||
|
float mult = static_cast<float>(snapMonitorIter->dpi) / currentDpi;
|
||||||
|
rect.left = static_cast<long>(std::round(rect.left * mult));
|
||||||
|
rect.right = static_cast<long>(std::round(rect.right * mult));
|
||||||
|
rect.top = static_cast<long>(std::round(rect.top * mult));
|
||||||
|
rect.bottom = static_cast<long>(std::round(rect.bottom * mult));
|
||||||
|
|
||||||
|
if (FancyZones::SizeWindowToRect(window, currentMonitor, launchMinimized, launchMaximized, rect))
|
||||||
|
{
|
||||||
|
WorkspacesWindowProperties::StampWorkspacesLaunchedProperty(window);
|
||||||
|
Logger::trace(L"Placed {} to ({},{}) [{}x{}]", app.name, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::error(L"Failed placing {}", app.name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowArranger::allWindowsFound() const
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <WindowCreationHandler.h>
|
||||||
|
|
||||||
|
#include <WorkspacesLib/AppUtils.h>
|
||||||
|
#include <WorkspacesLib/IPCHelper.h>
|
||||||
|
#include <WorkspacesLib/LaunchingStatus.h>
|
||||||
|
#include <WorkspacesLib/WorkspacesData.h>
|
||||||
|
|
||||||
|
class WindowArranger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WindowArranger(WorkspacesData::WorkspacesProject project, const IPCHelper& ipcHelper);
|
||||||
|
~WindowArranger() = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const WorkspacesData::WorkspacesProject m_project;
|
||||||
|
const std::vector<HWND> m_windowsBefore;
|
||||||
|
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{};
|
||||||
|
|
||||||
|
//void onWindowCreated(HWND window);
|
||||||
|
void processWindow(HWND window);
|
||||||
|
bool moveWindow(HWND window, const WorkspacesData::WorkspacesProject::Application& app);
|
||||||
|
|
||||||
|
bool allWindowsFound() const;
|
||||||
|
};
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "WindowCreationHandler.h"
|
||||||
|
|
||||||
|
WindowCreationHandler::WindowCreationHandler(std::function<void(HWND)> windowCreatedCallback) :
|
||||||
|
m_windowCreatedCallback(windowCreatedCallback)
|
||||||
|
{
|
||||||
|
s_instance = this;
|
||||||
|
InitHooks();
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowCreationHandler::~WindowCreationHandler()
|
||||||
|
{
|
||||||
|
m_staticWinEventHooks.erase(std::remove_if(begin(m_staticWinEventHooks),
|
||||||
|
end(m_staticWinEventHooks),
|
||||||
|
[](const HWINEVENTHOOK hook) {
|
||||||
|
return UnhookWinEvent(hook);
|
||||||
|
}),
|
||||||
|
end(m_staticWinEventHooks));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowCreationHandler::InitHooks()
|
||||||
|
{
|
||||||
|
std::array<DWORD, 3> events_to_subscribe = {
|
||||||
|
EVENT_OBJECT_UNCLOAKED,
|
||||||
|
EVENT_OBJECT_SHOW,
|
||||||
|
EVENT_OBJECT_CREATE
|
||||||
|
};
|
||||||
|
for (const auto event : events_to_subscribe)
|
||||||
|
{
|
||||||
|
auto hook = SetWinEventHook(event, event, nullptr, WinHookProc, 0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
|
||||||
|
if (hook)
|
||||||
|
{
|
||||||
|
m_staticWinEventHooks.emplace_back(hook);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::error(L"Failed to initialize win event hooks");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowCreationHandler::HandleWinHookEvent(DWORD event, HWND window) noexcept
|
||||||
|
{
|
||||||
|
switch (event)
|
||||||
|
{
|
||||||
|
//case EVENT_OBJECT_UNCLOAKED:
|
||||||
|
//case EVENT_OBJECT_SHOW:
|
||||||
|
case EVENT_OBJECT_CREATE:
|
||||||
|
{
|
||||||
|
if (m_windowCreatedCallback)
|
||||||
|
{
|
||||||
|
m_windowCreatedCallback(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class WindowCreationHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WindowCreationHandler(std::function<void(HWND)> windowCreatedCallback);
|
||||||
|
~WindowCreationHandler();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static inline WindowCreationHandler* s_instance = nullptr;
|
||||||
|
std::vector<HWINEVENTHOOK> m_staticWinEventHooks;
|
||||||
|
std::function<void(HWND)> m_windowCreatedCallback;
|
||||||
|
|
||||||
|
void InitHooks();
|
||||||
|
void HandleWinHookEvent(DWORD event, HWND window) noexcept;
|
||||||
|
|
||||||
|
static void CALLBACK WinHookProc(HWINEVENTHOOK winEventHook,
|
||||||
|
DWORD event,
|
||||||
|
HWND window,
|
||||||
|
LONG object,
|
||||||
|
LONG child,
|
||||||
|
DWORD eventThread,
|
||||||
|
DWORD eventTime)
|
||||||
|
{
|
||||||
|
if (s_instance)
|
||||||
|
{
|
||||||
|
s_instance->HandleWinHookEvent(event, window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<!-- Project configurations -->
|
||||||
|
<!-- Props that should be disabled while building on CI server -->
|
||||||
|
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||||
|
<Target Name="GenerateResourceFiles" BeforeTargets="PrepareForBuild">
|
||||||
|
<Exec Command="powershell -NonInteractive -executionpolicy Unrestricted $(SolutionDir)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h WorkspacesWindowArrangerResource.base.rc WorkspacesWindowArrangerResource.rc" />
|
||||||
|
</Target>
|
||||||
|
<!-- C++ source compile-specific things for all configurations -->
|
||||||
|
<ItemDefinitionGroup>
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<ConformanceMode>false</ConformanceMode>
|
||||||
|
<TreatWarningAsError>true</TreatWarningAsError>
|
||||||
|
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||||
|
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
|
||||||
|
<PreprocessorDefinitions>_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Windows</SubSystem>
|
||||||
|
</Link>
|
||||||
|
<Lib>
|
||||||
|
<TreatLibWarningAsErrors>true</TreatLibWarningAsErrors>
|
||||||
|
</Lib>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<!-- C++ source compile-specific things for Debug/Release configurations -->
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
|
||||||
|
<ClCompile>
|
||||||
|
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<Optimization>Disabled</Optimization>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||||
|
<ClCompile>
|
||||||
|
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<Optimization>MaxSpeed</Optimization>
|
||||||
|
<SDLCheck>false</SDLCheck>
|
||||||
|
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<!-- Global props -->
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<VCProjectVersion>16.0</VCProjectVersion>
|
||||||
|
<Keyword>Win32Proj</Keyword>
|
||||||
|
<ProjectGuid>{37D07516-4185-43A4-924F-3C7A5D95ECF6}</ProjectGuid>
|
||||||
|
<RootNamespace>WorkspacesWindowArranger</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
<!-- Props that are constant for both Debug and Release configurations -->
|
||||||
|
<PropertyGroup Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
<SpectreMitigation>Spectre</SpectreMitigation>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||||
|
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<LinkIncremental>true</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<LinkIncremental>false</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||||
|
<ImportGroup Label="ExtensionSettings">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="Shared">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<PropertyGroup Label="UserMacros" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetName>PowerToys.$(MSBuildProjectName)</TargetName>
|
||||||
|
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\</OutDir>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||||
|
<LinkIncremental>true</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||||
|
<LinkIncremental>false</LinkIncremental>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
<AdditionalIncludeDirectories>./../;$(SolutionDir)src\common\Telemetry;$(SolutionDir)src\common;$(SolutionDir)src\;./;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Windows</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
<AdditionalDependencies>shcore.lib;Shell32.lib;propsys.lib;DbgHelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
<AdditionalIncludeDirectories>./../;$(SolutionDir)src\common\Telemetry;$(SolutionDir)src\common;$(SolutionDir)src\;./;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Windows</SubSystem>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
<AdditionalDependencies>shcore.lib;Shell32.lib;propsys.lib;DbgHelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="main.cpp" />
|
||||||
|
<ClCompile Include="pch.cpp">
|
||||||
|
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="WindowArranger.cpp" />
|
||||||
|
<ClCompile Include="WindowCreationHandler.cpp" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="pch.h" />
|
||||||
|
<ClInclude Include="resource.h" />
|
||||||
|
<ClInclude Include="WindowArranger.h" />
|
||||||
|
<ClInclude Include="WindowCreationHandler.h" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="packages.config" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\common\Display\Display.vcxproj">
|
||||||
|
<Project>{caba8dfb-823b-4bf2-93ac-3f31984150d9}</Project>
|
||||||
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
|
||||||
|
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
|
||||||
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\WorkspacesLib\WorkspacesLib.vcxproj">
|
||||||
|
<Project>{b31fcc55-b5a4-4ea7-b414-2dceae6af332}</Project>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ResourceCompile Include="Generated Files/WorkspacesWindowArrangerResource.rc" />
|
||||||
|
<None Include="WorkspacesWindowArrangerResource.base.rc" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="Resource.resx">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</EmbeddedResource>
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
<Import Project="..\..\..\..\deps\spdlog.props" />
|
||||||
|
<ImportGroup Label="ExtensionTargets">
|
||||||
|
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||||
|
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
|
||||||
|
</ImportGroup>
|
||||||
|
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||||
|
<PropertyGroup>
|
||||||
|
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||||
|
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||||
|
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
|
||||||
|
</Target>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup>
|
||||||
|
<Filter Include="Source Files">
|
||||||
|
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||||
|
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="Header Files">
|
||||||
|
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||||
|
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="Resource Files">
|
||||||
|
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||||
|
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||||
|
</Filter>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="pch.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="resource.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="WindowCreationHandler.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="WindowArranger.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="pch.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="main.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="WindowCreationHandler.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="WindowArranger.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="packages.config" />
|
||||||
|
<None Include="WorkspacesWindowArrangerResource.base.rc">
|
||||||
|
<Filter>Resource Files</Filter>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ResourceCompile Include="Generated Files/WorkspacesWindowArrangerResource.rc">
|
||||||
|
<Filter>Resource Files</Filter>
|
||||||
|
</ResourceCompile>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="Resource.resx">
|
||||||
|
<Filter>Resource Files</Filter>
|
||||||
|
</EmbeddedResource>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
Binary file not shown.
115
src/modules/Workspaces/WorkspacesWindowArranger/main.cpp
Normal file
115
src/modules/Workspaces/WorkspacesWindowArranger/main.cpp
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
#include <WorkspacesLib/JsonUtils.h>
|
||||||
|
#include <WorkspacesLib/IPCHelper.h>
|
||||||
|
#include <WorkspacesLib/utils.h>
|
||||||
|
|
||||||
|
#include <common/utils/gpo.h>
|
||||||
|
#include <common/utils/logger_helper.h>
|
||||||
|
#include <common/utils/UnhandledExceptionHandler.h>
|
||||||
|
#include <common/utils/window.h>
|
||||||
|
|
||||||
|
#include <WindowArranger.h>
|
||||||
|
|
||||||
|
const std::wstring moduleName = L"Workspaces\\WorkspacesWindowArranger";
|
||||||
|
const std::wstring internalPath = L"";
|
||||||
|
|
||||||
|
int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdline, int cmdShow)
|
||||||
|
{
|
||||||
|
LoggerHelpers::init_logger(moduleName, internalPath, LogSettings::workspacesWindowArrangerLoggerName);
|
||||||
|
InitUnhandledExceptionHandler();
|
||||||
|
|
||||||
|
if (powertoys_gpo::getConfiguredWorkspacesEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled)
|
||||||
|
{
|
||||||
|
Logger::warn(L"Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||||
|
|
||||||
|
std::wstring commandLine{ GetCommandLineW() };
|
||||||
|
if (commandLine.empty())
|
||||||
|
{
|
||||||
|
Logger::warn("Empty command line arguments");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto args = split(commandLine, L" ");
|
||||||
|
std::wstring id{};
|
||||||
|
if (args.size() == 1)
|
||||||
|
{
|
||||||
|
id = args[0];
|
||||||
|
}
|
||||||
|
else if (args.size() == 2)
|
||||||
|
{
|
||||||
|
id = args[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.empty())
|
||||||
|
{
|
||||||
|
Logger::warn("Incorrect command line arguments: no workspace id");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read workspaces
|
||||||
|
std::vector<WorkspacesData::WorkspacesProject> workspaces;
|
||||||
|
WorkspacesData::WorkspacesProject projectToLaunch{};
|
||||||
|
|
||||||
|
// check the temp file in case the project is just created and not saved to the workspaces.json yet
|
||||||
|
if (std::filesystem::exists(WorkspacesData::TempWorkspacesFile()))
|
||||||
|
{
|
||||||
|
auto file = WorkspacesData::TempWorkspacesFile();
|
||||||
|
auto res = JsonUtils::ReadSingleWorkspace(file);
|
||||||
|
if (res.isOk() && res.value().id == id)
|
||||||
|
{
|
||||||
|
projectToLaunch = res.getValue();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::error(L"Error reading temp file");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projectToLaunch.id.empty())
|
||||||
|
{
|
||||||
|
auto file = WorkspacesData::WorkspacesFile();
|
||||||
|
auto res = JsonUtils::ReadWorkspaces(file);
|
||||||
|
if (res.isOk())
|
||||||
|
{
|
||||||
|
workspaces = res.getValue();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& proj : workspaces)
|
||||||
|
{
|
||||||
|
if (proj.id == id)
|
||||||
|
{
|
||||||
|
projectToLaunch = proj;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projectToLaunch.id.empty())
|
||||||
|
{
|
||||||
|
Logger::critical(L"Workspace {} not found", id);
|
||||||
|
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);
|
||||||
|
//run_message_loop();
|
||||||
|
|
||||||
|
Logger::debug(L"Arranger finished");
|
||||||
|
|
||||||
|
CoUninitialize();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<packages>
|
||||||
|
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
|
||||||
|
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
|
||||||
|
</packages>
|
||||||
1
src/modules/Workspaces/WorkspacesWindowArranger/pch.cpp
Normal file
1
src/modules/Workspaces/WorkspacesWindowArranger/pch.cpp
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#include "pch.h"
|
||||||
6
src/modules/Workspaces/WorkspacesWindowArranger/pch.h
Normal file
6
src/modules/Workspaces/WorkspacesWindowArranger/pch.h
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <windows.h>
|
||||||
|
#include <common/logger/logger.h>
|
||||||
|
#include <winrt/Windows.Foundation.h>
|
||||||
|
#include <winrt/Windows.Foundation.Collections.h>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
//{{NO_DEPENDENCIES}}
|
||||||
|
// Microsoft Visual C++ generated include file.
|
||||||
|
// Used by WorkspacesWindowArrangerResource.rc
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
// Non-localizable
|
||||||
|
|
||||||
|
#define FILE_DESCRIPTION "PowerToys Workspaces Window Arranger"
|
||||||
|
#define INTERNAL_NAME "PowerToys.WorkspacesWindowArranger"
|
||||||
|
#define ORIGINAL_FILENAME "PowerToys.WorkspacesWindowArranger.exe"
|
||||||
|
|
||||||
|
// Non-localizable
|
||||||
|
//////////////////////////////
|
||||||
@@ -21,6 +21,7 @@ namespace WindowUtils
|
|||||||
const wchar_t WorkspacesSnapshotTool[] = L"POWERTOYS.WORKSPACESSNAPSHOTTOOL";
|
const wchar_t WorkspacesSnapshotTool[] = L"POWERTOYS.WORKSPACESSNAPSHOTTOOL";
|
||||||
const wchar_t WorkspacesEditor[] = L"POWERTOYS.WORKSPACESEDITOR";
|
const wchar_t WorkspacesEditor[] = L"POWERTOYS.WORKSPACESEDITOR";
|
||||||
const wchar_t WorkspacesLauncher[] = L"POWERTOYS.WORKSPACESLAUNCHER";
|
const wchar_t WorkspacesLauncher[] = L"POWERTOYS.WORKSPACESLAUNCHER";
|
||||||
|
const wchar_t WorkspacesWindowArranger[] = L"POWERTOYS.WORKSPACESWINDOWARRANGER";
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool IsRoot(HWND window) noexcept
|
inline bool IsRoot(HWND window) noexcept
|
||||||
@@ -79,7 +80,8 @@ namespace WindowUtils
|
|||||||
NonLocalizable::SearchUI,
|
NonLocalizable::SearchUI,
|
||||||
NonLocalizable::HelpWindow,
|
NonLocalizable::HelpWindow,
|
||||||
NonLocalizable::WorkspacesEditor,
|
NonLocalizable::WorkspacesEditor,
|
||||||
NonLocalizable::WorkspacesLauncher,
|
NonLocalizable::WorkspacesLauncher,
|
||||||
|
NonLocalizable::WorkspacesWindowArranger,
|
||||||
NonLocalizable::WorkspacesSnapshotTool,
|
NonLocalizable::WorkspacesSnapshotTool,
|
||||||
};
|
};
|
||||||
return (check_excluded_app(window, processPathUpper, defaultExcludedApps));
|
return (check_excluded_app(window, processPathUpper, defaultExcludedApps));
|
||||||
|
|||||||
@@ -46,5 +46,6 @@ std::vector<std::wstring> processes =
|
|||||||
L"PowerToys.WorkspacesSnapshotTool.exe",
|
L"PowerToys.WorkspacesSnapshotTool.exe",
|
||||||
L"PowerToys.WorkspacesLauncher.exe",
|
L"PowerToys.WorkspacesLauncher.exe",
|
||||||
L"PowerToys.WorkspacesLauncherUI.exe",
|
L"PowerToys.WorkspacesLauncherUI.exe",
|
||||||
|
L"PowerToys.WorkspacesWindowArranger.exe",
|
||||||
L"PowerToys.WorkspacesEditor.exe",
|
L"PowerToys.WorkspacesEditor.exe",
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user