[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
|
||||
LPBITMAPINFOHEADER
|
||||
LPCITEMIDLIST
|
||||
LPCLSID
|
||||
lpcmi
|
||||
LPCMINVOKECOMMANDINFO
|
||||
LPCREATESTRUCT
|
||||
|
|
|
@ -7,10 +7,70 @@ namespace WorkspacesWindowProperties
|
|||
namespace Properties
|
||||
{
|
||||
const wchar_t LaunchedByWorkspacesID[] = L"PowerToys_LaunchedByWorkspaces";
|
||||
const wchar_t WorkspacesAppID[] = L"PowerToys_WorkspacesAppId";
|
||||
}
|
||||
|
||||
inline void StampWorkspacesLaunchedProperty(HWND window)
|
||||
{
|
||||
::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));
|
||||
}
|
||||
|
||||
public void UpdateAfterLaunchAndEdit(Project other)
|
||||
public void UpdateAfterLaunchAndEdit(Project projectBeforeLaunch)
|
||||
{
|
||||
Id = other.Id;
|
||||
Name = other.Name;
|
||||
Id = projectBeforeLaunch.Id;
|
||||
Name = projectBeforeLaunch.Name;
|
||||
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()
|
||||
|
|
|
@ -40,6 +40,7 @@ namespace WorkspacesEditor.ViewModels
|
|||
private Timer lastUpdatedTimer;
|
||||
private WorkspacesSettings settings;
|
||||
private PwaHelper _pwaHelper;
|
||||
private bool _isExistingProjectLaunched;
|
||||
|
||||
public ObservableCollection<Project> Workspaces { get; set; } = new ObservableCollection<Project>();
|
||||
|
||||
|
@ -256,12 +257,12 @@ namespace WorkspacesEditor.ViewModels
|
|||
{
|
||||
CancelSnapshot();
|
||||
|
||||
await Task.Run(() => RunSnapshotTool());
|
||||
await Task.Run(() => RunSnapshotTool(_isExistingProjectLaunched));
|
||||
|
||||
Project project = _workspacesEditorIO.ParseTempProject();
|
||||
if (project != null)
|
||||
{
|
||||
if (editedProject != null)
|
||||
if (_isExistingProjectLaunched)
|
||||
{
|
||||
project.UpdateAfterLaunchAndEdit(projectBeforeLaunch);
|
||||
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.StartInfo = new ProcessStartInfo(@".\PowerToys.WorkspacesSnapshotTool.exe");
|
||||
process.StartInfo.CreateNoWindow = true;
|
||||
if (!string.IsNullOrEmpty(filename))
|
||||
{
|
||||
process.StartInfo.Arguments = filename;
|
||||
}
|
||||
process.StartInfo.Arguments = isExistingProjectLaunched ? $"{(int)InvokePoint.LaunchAndEdit}" : string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -484,6 +482,7 @@ namespace WorkspacesEditor.ViewModels
|
|||
|
||||
internal void EnterSnapshotMode(bool isExistingProjectLaunched)
|
||||
{
|
||||
_isExistingProjectLaunched = isExistingProjectLaunched;
|
||||
_mainWindow.WindowState = System.Windows.WindowState.Minimized;
|
||||
_overlayWindows.Clear();
|
||||
foreach (var screen in MonitorHelper.GetDpiUnawareScreens())
|
||||
|
|
|
@ -397,6 +397,5 @@ namespace Utils
|
|||
{
|
||||
return installPath.ends_with(NonLocalizable::ChromeFilename);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -40,7 +40,14 @@ CommandLineArgs split(std::wstring s, const std::wstring& delimiter)
|
|||
{
|
||||
cmdArgs.isRestarted = true;
|
||||
}
|
||||
else if (!cmdArgs.workspaceId.empty())
|
||||
else
|
||||
{
|
||||
auto guid = GuidFromString(token);
|
||||
if (guid.has_value())
|
||||
{
|
||||
cmdArgs.workspaceId = token;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -51,13 +58,6 @@ CommandLineArgs split(std::wstring s, const std::wstring& delimiter)
|
|||
{
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto guid = GuidFromString(token);
|
||||
if (guid.has_value())
|
||||
{
|
||||
cmdArgs.workspaceId = token;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#include <WorkspacesLib/AppUtils.h>
|
||||
#include <WorkspacesLib/PwaHelper.h>
|
||||
#include <WindowProperties/WorkspacesWindowPropertyUtils.h>
|
||||
|
||||
#pragma comment(lib, "ntdll.lib")
|
||||
|
||||
|
@ -38,7 +39,7 @@ namespace SnapshotUtils
|
|||
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{};
|
||||
std::vector<WorkspacesData::WorkspacesProject::Application> apps{};
|
||||
|
@ -157,7 +158,10 @@ namespace SnapshotUtils
|
|||
rect.bottom = monitorRect.top + monitorRect.height;
|
||||
}
|
||||
|
||||
std::wstring guid = isGuidNeeded ? WorkspacesWindowProperties::GetGuidFromHwnd(window) : L"";
|
||||
|
||||
WorkspacesData::WorkspacesProject::Application app{
|
||||
.id = guid,
|
||||
.name = appData.name,
|
||||
.title = title,
|
||||
.path = appData.installPath,
|
||||
|
|
|
@ -4,5 +4,5 @@
|
|||
|
||||
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/logger_helper.h>
|
||||
#include <common/utils/UnhandledExceptionHandler.h>
|
||||
#include <WorkspacesLib/utils.h>
|
||||
|
||||
const std::wstring moduleName = L"Workspaces\\WorkspacesSnapshotTool";
|
||||
const std::wstring internalPath = L"";
|
||||
|
@ -46,13 +47,17 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdLine, int cm
|
|||
return -1;
|
||||
}
|
||||
|
||||
std::wstring cmdLineStr{ GetCommandLineW() };
|
||||
auto cmdArgs = split(cmdLineStr, L" ");
|
||||
|
||||
// create new project
|
||||
time_t creationTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
WorkspacesData::WorkspacesProject project{ .id = CreateGuidString(), .creationTime = creationTime };
|
||||
Logger::trace(L"Creating workspace {}:{}", project.name, project.id);
|
||||
|
||||
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);
|
||||
unsigned int monitorNumber = 0;
|
||||
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))
|
||||
{
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include <shlobj.h>
|
||||
|
||||
inline std::optional<GUID> GuidFromString(const std::wstring& str) noexcept
|
||||
|
|
Загрузка…
Ссылка в новой задаче