[Workspaces] Saving app properties on launch and recapture (#36751)
* [Workspaces] Implementing set and get GUID to/from HWND to distinguish windows moved by the Workspaces tool * After launch and capture copy the CLI args from the "original" project * Fix getting GUID * spell check * modification to be able to handle different data sizes on different systems * code optimisation * Replacing string parameter by InvokePoint * renaming variable
This commit is contained in:
Родитель
603379a1ad
Коммит
f5f332cbba
|
@ -769,6 +769,7 @@ LOWORD
|
||||||
lparam
|
lparam
|
||||||
LPBITMAPINFOHEADER
|
LPBITMAPINFOHEADER
|
||||||
LPCITEMIDLIST
|
LPCITEMIDLIST
|
||||||
|
LPCLSID
|
||||||
lpcmi
|
lpcmi
|
||||||
LPCMINVOKECOMMANDINFO
|
LPCMINVOKECOMMANDINFO
|
||||||
LPCREATESTRUCT
|
LPCREATESTRUCT
|
||||||
|
|
|
@ -7,10 +7,70 @@ namespace WorkspacesWindowProperties
|
||||||
namespace Properties
|
namespace Properties
|
||||||
{
|
{
|
||||||
const wchar_t LaunchedByWorkspacesID[] = L"PowerToys_LaunchedByWorkspaces";
|
const wchar_t LaunchedByWorkspacesID[] = L"PowerToys_LaunchedByWorkspaces";
|
||||||
|
const wchar_t WorkspacesAppID[] = L"PowerToys_WorkspacesAppId";
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void StampWorkspacesLaunchedProperty(HWND window)
|
inline void StampWorkspacesLaunchedProperty(HWND window)
|
||||||
{
|
{
|
||||||
::SetPropW(window, Properties::LaunchedByWorkspacesID, reinterpret_cast<HANDLE>(1));
|
::SetPropW(window, Properties::LaunchedByWorkspacesID, reinterpret_cast<HANDLE>(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void StampWorkspacesGuidProperty(HWND window, const std::wstring& appId)
|
||||||
|
{
|
||||||
|
GUID guid;
|
||||||
|
HRESULT hr = CLSIDFromString(appId.c_str(), static_cast<LPCLSID> (&guid));
|
||||||
|
if (hr != S_OK)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t workspacesAppIDLength = wcslen(Properties::WorkspacesAppID);
|
||||||
|
wchar_t* workspacesAppIDPart = new wchar_t[workspacesAppIDLength + 2];
|
||||||
|
std::memcpy(&workspacesAppIDPart[0], &Properties::WorkspacesAppID, workspacesAppIDLength * sizeof(wchar_t));
|
||||||
|
workspacesAppIDPart[workspacesAppIDLength + 1] = 0;
|
||||||
|
|
||||||
|
// the size of the HANDLE type can vary on different systems: 4 or 8 bytes. As we can set only a HANDLE as a property, we need more properties (2 or 4) to be able to store a GUID (16 bytes)
|
||||||
|
const int numberOfProperties = sizeof(GUID) / sizeof(HANDLE);
|
||||||
|
|
||||||
|
uint64_t parts[numberOfProperties];
|
||||||
|
std::memcpy(&parts[0], &guid, sizeof(GUID));
|
||||||
|
for (unsigned char partIndex = 0; partIndex < numberOfProperties; partIndex++)
|
||||||
|
{
|
||||||
|
workspacesAppIDPart[workspacesAppIDLength] = '0' + partIndex;
|
||||||
|
::SetPropW(window, workspacesAppIDPart, reinterpret_cast<HANDLE>(parts[partIndex]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const std::wstring GetGuidFromHwnd(HWND window)
|
||||||
|
{
|
||||||
|
const size_t workspacesAppIDLength = wcslen(Properties::WorkspacesAppID);
|
||||||
|
wchar_t* workspacesAppIDPart = new wchar_t[workspacesAppIDLength + 2];
|
||||||
|
std::memcpy(&workspacesAppIDPart[0], &Properties::WorkspacesAppID, workspacesAppIDLength * sizeof(wchar_t));
|
||||||
|
workspacesAppIDPart[workspacesAppIDLength + 1] = 0;
|
||||||
|
|
||||||
|
// the size of the HANDLE type can vary on different systems: 4 or 8 bytes. As we can set only a HANDLE as a property, we need more properties (2 or 4) to be able to store a GUID (16 bytes)
|
||||||
|
const int numberOfProperties = sizeof(GUID) / sizeof(HANDLE);
|
||||||
|
|
||||||
|
uint64_t parts[numberOfProperties];
|
||||||
|
for (unsigned char partIndex = 0; partIndex < numberOfProperties; partIndex++)
|
||||||
|
{
|
||||||
|
workspacesAppIDPart[workspacesAppIDLength] = '0' + partIndex;
|
||||||
|
HANDLE rawData = GetPropW(window, workspacesAppIDPart);
|
||||||
|
if (rawData)
|
||||||
|
{
|
||||||
|
parts[partIndex] = reinterpret_cast<uint64_t>(rawData);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return L"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GUID guid;
|
||||||
|
std::memcpy(&guid, &parts[0], sizeof(GUID));
|
||||||
|
WCHAR* guidString;
|
||||||
|
StringFromCLSID(guid, &guidString);
|
||||||
|
|
||||||
|
return guidString;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -342,12 +342,22 @@ namespace WorkspacesEditor.Models
|
||||||
return new Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY));
|
return new Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateAfterLaunchAndEdit(Project other)
|
public void UpdateAfterLaunchAndEdit(Project projectBeforeLaunch)
|
||||||
{
|
{
|
||||||
Id = other.Id;
|
Id = projectBeforeLaunch.Id;
|
||||||
Name = other.Name;
|
Name = projectBeforeLaunch.Name;
|
||||||
IsRevertEnabled = true;
|
IsRevertEnabled = true;
|
||||||
MoveExistingWindows = other.MoveExistingWindows;
|
MoveExistingWindows = projectBeforeLaunch.MoveExistingWindows;
|
||||||
|
foreach (Application app in Applications)
|
||||||
|
{
|
||||||
|
var sameAppBefore = projectBeforeLaunch.Applications.Where(x => x.Id.Equals(app.Id, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (sameAppBefore.Any())
|
||||||
|
{
|
||||||
|
var appBefore = sameAppBefore.FirstOrDefault();
|
||||||
|
app.CommandLineArguments = appBefore.CommandLineArguments;
|
||||||
|
app.IsElevated = appBefore.IsElevated;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void CloseExpanders()
|
internal void CloseExpanders()
|
||||||
|
|
|
@ -40,6 +40,7 @@ namespace WorkspacesEditor.ViewModels
|
||||||
private Timer lastUpdatedTimer;
|
private Timer lastUpdatedTimer;
|
||||||
private WorkspacesSettings settings;
|
private WorkspacesSettings settings;
|
||||||
private PwaHelper _pwaHelper;
|
private PwaHelper _pwaHelper;
|
||||||
|
private bool _isExistingProjectLaunched;
|
||||||
|
|
||||||
public ObservableCollection<Project> Workspaces { get; set; } = new ObservableCollection<Project>();
|
public ObservableCollection<Project> Workspaces { get; set; } = new ObservableCollection<Project>();
|
||||||
|
|
||||||
|
@ -256,12 +257,12 @@ namespace WorkspacesEditor.ViewModels
|
||||||
{
|
{
|
||||||
CancelSnapshot();
|
CancelSnapshot();
|
||||||
|
|
||||||
await Task.Run(() => RunSnapshotTool());
|
await Task.Run(() => RunSnapshotTool(_isExistingProjectLaunched));
|
||||||
|
|
||||||
Project project = _workspacesEditorIO.ParseTempProject();
|
Project project = _workspacesEditorIO.ParseTempProject();
|
||||||
if (project != null)
|
if (project != null)
|
||||||
{
|
{
|
||||||
if (editedProject != null)
|
if (_isExistingProjectLaunched)
|
||||||
{
|
{
|
||||||
project.UpdateAfterLaunchAndEdit(projectBeforeLaunch);
|
project.UpdateAfterLaunchAndEdit(projectBeforeLaunch);
|
||||||
project.EditorWindowTitle = Properties.Resources.EditWorkspace;
|
project.EditorWindowTitle = Properties.Resources.EditWorkspace;
|
||||||
|
@ -431,15 +432,12 @@ namespace WorkspacesEditor.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RunSnapshotTool(string filename = null)
|
private void RunSnapshotTool(bool isExistingProjectLaunched)
|
||||||
{
|
{
|
||||||
Process process = new Process();
|
Process process = new Process();
|
||||||
process.StartInfo = new ProcessStartInfo(@".\PowerToys.WorkspacesSnapshotTool.exe");
|
process.StartInfo = new ProcessStartInfo(@".\PowerToys.WorkspacesSnapshotTool.exe");
|
||||||
process.StartInfo.CreateNoWindow = true;
|
process.StartInfo.CreateNoWindow = true;
|
||||||
if (!string.IsNullOrEmpty(filename))
|
process.StartInfo.Arguments = isExistingProjectLaunched ? $"{(int)InvokePoint.LaunchAndEdit}" : string.Empty;
|
||||||
{
|
|
||||||
process.StartInfo.Arguments = filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -484,6 +482,7 @@ namespace WorkspacesEditor.ViewModels
|
||||||
|
|
||||||
internal void EnterSnapshotMode(bool isExistingProjectLaunched)
|
internal void EnterSnapshotMode(bool isExistingProjectLaunched)
|
||||||
{
|
{
|
||||||
|
_isExistingProjectLaunched = isExistingProjectLaunched;
|
||||||
_mainWindow.WindowState = System.Windows.WindowState.Minimized;
|
_mainWindow.WindowState = System.Windows.WindowState.Minimized;
|
||||||
_overlayWindows.Clear();
|
_overlayWindows.Clear();
|
||||||
foreach (var screen in MonitorHelper.GetDpiUnawareScreens())
|
foreach (var screen in MonitorHelper.GetDpiUnawareScreens())
|
||||||
|
|
|
@ -397,6 +397,5 @@ namespace Utils
|
||||||
{
|
{
|
||||||
return installPath.ends_with(NonLocalizable::ChromeFilename);
|
return installPath.ends_with(NonLocalizable::ChromeFilename);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -40,17 +40,6 @@ CommandLineArgs split(std::wstring s, const std::wstring& delimiter)
|
||||||
{
|
{
|
||||||
cmdArgs.isRestarted = true;
|
cmdArgs.isRestarted = true;
|
||||||
}
|
}
|
||||||
else if (!cmdArgs.workspaceId.empty())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
auto invokePoint = static_cast<InvokePoint>(std::stoi(token));
|
|
||||||
cmdArgs.invokePoint = invokePoint;
|
|
||||||
}
|
|
||||||
catch (std::exception)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto guid = GuidFromString(token);
|
auto guid = GuidFromString(token);
|
||||||
|
@ -58,6 +47,17 @@ CommandLineArgs split(std::wstring s, const std::wstring& delimiter)
|
||||||
{
|
{
|
||||||
cmdArgs.workspaceId = token;
|
cmdArgs.workspaceId = token;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto invokePoint = static_cast<InvokePoint>(std::stoi(token));
|
||||||
|
cmdArgs.invokePoint = invokePoint;
|
||||||
|
}
|
||||||
|
catch (std::exception)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
#include <WorkspacesLib/AppUtils.h>
|
#include <WorkspacesLib/AppUtils.h>
|
||||||
#include <WorkspacesLib/PwaHelper.h>
|
#include <WorkspacesLib/PwaHelper.h>
|
||||||
|
#include <WindowProperties/WorkspacesWindowPropertyUtils.h>
|
||||||
|
|
||||||
#pragma comment(lib, "ntdll.lib")
|
#pragma comment(lib, "ntdll.lib")
|
||||||
|
|
||||||
|
@ -38,7 +39,7 @@ namespace SnapshotUtils
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<WorkspacesData::WorkspacesProject::Application> GetApps(const std::function<unsigned int(HWND)> getMonitorNumberFromWindowHandle, const std::function<WorkspacesData::WorkspacesProject::Monitor::MonitorRect(unsigned int)> getMonitorRect)
|
std::vector<WorkspacesData::WorkspacesProject::Application> GetApps(bool isGuidNeeded, const std::function<unsigned int(HWND)> getMonitorNumberFromWindowHandle, const std::function<WorkspacesData::WorkspacesProject::Monitor::MonitorRect(unsigned int)> getMonitorRect)
|
||||||
{
|
{
|
||||||
Utils::PwaHelper pwaHelper{};
|
Utils::PwaHelper pwaHelper{};
|
||||||
std::vector<WorkspacesData::WorkspacesProject::Application> apps{};
|
std::vector<WorkspacesData::WorkspacesProject::Application> apps{};
|
||||||
|
@ -157,7 +158,10 @@ namespace SnapshotUtils
|
||||||
rect.bottom = monitorRect.top + monitorRect.height;
|
rect.bottom = monitorRect.top + monitorRect.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::wstring guid = isGuidNeeded ? WorkspacesWindowProperties::GetGuidFromHwnd(window) : L"";
|
||||||
|
|
||||||
WorkspacesData::WorkspacesProject::Application app{
|
WorkspacesData::WorkspacesProject::Application app{
|
||||||
|
.id = guid,
|
||||||
.name = appData.name,
|
.name = appData.name,
|
||||||
.title = title,
|
.title = title,
|
||||||
.path = appData.installPath,
|
.path = appData.installPath,
|
||||||
|
|
|
@ -4,5 +4,5 @@
|
||||||
|
|
||||||
namespace SnapshotUtils
|
namespace SnapshotUtils
|
||||||
{
|
{
|
||||||
std::vector<WorkspacesData::WorkspacesProject::Application> GetApps(const std::function<unsigned int(HWND)> getMonitorNumberFromWindowHandle, const std::function<WorkspacesData::WorkspacesProject::Monitor::MonitorRect(unsigned int)> getMonitorRect);
|
std::vector<WorkspacesData::WorkspacesProject::Application> GetApps(bool isGuidNeeded, const std::function<unsigned int(HWND)> getMonitorNumberFromWindowHandle, const std::function<WorkspacesData::WorkspacesProject::Monitor::MonitorRect(unsigned int)> getMonitorRect);
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#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>
|
||||||
|
#include <WorkspacesLib/utils.h>
|
||||||
|
|
||||||
const std::wstring moduleName = L"Workspaces\\WorkspacesSnapshotTool";
|
const std::wstring moduleName = L"Workspaces\\WorkspacesSnapshotTool";
|
||||||
const std::wstring internalPath = L"";
|
const std::wstring internalPath = L"";
|
||||||
|
@ -46,13 +47,17 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdLine, int cm
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::wstring cmdLineStr{ GetCommandLineW() };
|
||||||
|
auto cmdArgs = split(cmdLineStr, L" ");
|
||||||
|
|
||||||
// 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 };
|
||||||
Logger::trace(L"Creating workspace {}:{}", project.name, project.id);
|
Logger::trace(L"Creating workspace {}:{}", project.name, project.id);
|
||||||
|
|
||||||
project.monitors = MonitorUtils::IdentifyMonitors();
|
project.monitors = MonitorUtils::IdentifyMonitors();
|
||||||
project.apps = SnapshotUtils::GetApps([&](HWND window) -> unsigned int {
|
bool isGuidNeeded = cmdArgs.invokePoint == InvokePoint::LaunchAndEdit;
|
||||||
|
project.apps = SnapshotUtils::GetApps(isGuidNeeded, [&](HWND window) -> unsigned int {
|
||||||
auto windowMonitor = MonitorFromWindow(window, MONITOR_DEFAULTTOPRIMARY);
|
auto windowMonitor = MonitorFromWindow(window, MONITOR_DEFAULTTOPRIMARY);
|
||||||
unsigned int monitorNumber = 0;
|
unsigned int monitorNumber = 0;
|
||||||
for (const auto& monitor : project.monitors)
|
for (const auto& monitor : project.monitors)
|
||||||
|
|
|
@ -460,6 +460,7 @@ bool WindowArranger::moveWindow(HWND window, const WorkspacesData::WorkspacesPro
|
||||||
if (PlacementHelper::SizeWindowToRect(window, currentMonitor, launchMinimized, launchMaximized, rect))
|
if (PlacementHelper::SizeWindowToRect(window, currentMonitor, launchMinimized, launchMaximized, rect))
|
||||||
{
|
{
|
||||||
WorkspacesWindowProperties::StampWorkspacesLaunchedProperty(window);
|
WorkspacesWindowProperties::StampWorkspacesLaunchedProperty(window);
|
||||||
|
WorkspacesWindowProperties::StampWorkspacesGuidProperty(window, app.id);
|
||||||
Logger::trace(L"Placed {} to ({},{}) [{}x{}]", app.name, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
|
Logger::trace(L"Placed {} to ({},{}) [{}x{}]", app.name, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
#include <shlobj.h>
|
#include <shlobj.h>
|
||||||
|
|
||||||
inline std::optional<GUID> GuidFromString(const std::wstring& str) noexcept
|
inline std::optional<GUID> GuidFromString(const std::wstring& str) noexcept
|
||||||
|
|
Загрузка…
Ссылка в новой задаче