Skip to content

Commit f5f332c

Browse files
authored
[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
1 parent 603379a commit f5f332c

File tree

11 files changed

+107
-26
lines changed

11 files changed

+107
-26
lines changed

.github/actions/spell-check/expect.txt

+1
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,7 @@ LOWORD
769769
lparam
770770
LPBITMAPINFOHEADER
771771
LPCITEMIDLIST
772+
LPCLSID
772773
lpcmi
773774
LPCMINVOKECOMMANDINFO
774775
LPCREATESTRUCT

src/modules/Workspaces/WindowProperties/WorkspacesWindowPropertyUtils.h

+60
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,70 @@ namespace WorkspacesWindowProperties
77
namespace Properties
88
{
99
const wchar_t LaunchedByWorkspacesID[] = L"PowerToys_LaunchedByWorkspaces";
10+
const wchar_t WorkspacesAppID[] = L"PowerToys_WorkspacesAppId";
1011
}
1112

1213
inline void StampWorkspacesLaunchedProperty(HWND window)
1314
{
1415
::SetPropW(window, Properties::LaunchedByWorkspacesID, reinterpret_cast<HANDLE>(1));
1516
}
17+
18+
inline void StampWorkspacesGuidProperty(HWND window, const std::wstring& appId)
19+
{
20+
GUID guid;
21+
HRESULT hr = CLSIDFromString(appId.c_str(), static_cast<LPCLSID> (&guid));
22+
if (hr != S_OK)
23+
{
24+
return;
25+
}
26+
27+
const size_t workspacesAppIDLength = wcslen(Properties::WorkspacesAppID);
28+
wchar_t* workspacesAppIDPart = new wchar_t[workspacesAppIDLength + 2];
29+
std::memcpy(&workspacesAppIDPart[0], &Properties::WorkspacesAppID, workspacesAppIDLength * sizeof(wchar_t));
30+
workspacesAppIDPart[workspacesAppIDLength + 1] = 0;
31+
32+
// 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)
33+
const int numberOfProperties = sizeof(GUID) / sizeof(HANDLE);
34+
35+
uint64_t parts[numberOfProperties];
36+
std::memcpy(&parts[0], &guid, sizeof(GUID));
37+
for (unsigned char partIndex = 0; partIndex < numberOfProperties; partIndex++)
38+
{
39+
workspacesAppIDPart[workspacesAppIDLength] = '0' + partIndex;
40+
::SetPropW(window, workspacesAppIDPart, reinterpret_cast<HANDLE>(parts[partIndex]));
41+
}
42+
}
43+
44+
inline const std::wstring GetGuidFromHwnd(HWND window)
45+
{
46+
const size_t workspacesAppIDLength = wcslen(Properties::WorkspacesAppID);
47+
wchar_t* workspacesAppIDPart = new wchar_t[workspacesAppIDLength + 2];
48+
std::memcpy(&workspacesAppIDPart[0], &Properties::WorkspacesAppID, workspacesAppIDLength * sizeof(wchar_t));
49+
workspacesAppIDPart[workspacesAppIDLength + 1] = 0;
50+
51+
// 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)
52+
const int numberOfProperties = sizeof(GUID) / sizeof(HANDLE);
53+
54+
uint64_t parts[numberOfProperties];
55+
for (unsigned char partIndex = 0; partIndex < numberOfProperties; partIndex++)
56+
{
57+
workspacesAppIDPart[workspacesAppIDLength] = '0' + partIndex;
58+
HANDLE rawData = GetPropW(window, workspacesAppIDPart);
59+
if (rawData)
60+
{
61+
parts[partIndex] = reinterpret_cast<uint64_t>(rawData);
62+
}
63+
else
64+
{
65+
return L"";
66+
}
67+
}
68+
69+
GUID guid;
70+
std::memcpy(&guid, &parts[0], sizeof(GUID));
71+
WCHAR* guidString;
72+
StringFromCLSID(guid, &guidString);
73+
74+
return guidString;
75+
}
1676
}

src/modules/Workspaces/WorkspacesEditor/Models/Project.cs

+14-4
Original file line numberDiff line numberDiff line change
@@ -342,12 +342,22 @@ private Rectangle GetCommonBounds()
342342
return new Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY));
343343
}
344344

345-
public void UpdateAfterLaunchAndEdit(Project other)
345+
public void UpdateAfterLaunchAndEdit(Project projectBeforeLaunch)
346346
{
347-
Id = other.Id;
348-
Name = other.Name;
347+
Id = projectBeforeLaunch.Id;
348+
Name = projectBeforeLaunch.Name;
349349
IsRevertEnabled = true;
350-
MoveExistingWindows = other.MoveExistingWindows;
350+
MoveExistingWindows = projectBeforeLaunch.MoveExistingWindows;
351+
foreach (Application app in Applications)
352+
{
353+
var sameAppBefore = projectBeforeLaunch.Applications.Where(x => x.Id.Equals(app.Id, StringComparison.OrdinalIgnoreCase));
354+
if (sameAppBefore.Any())
355+
{
356+
var appBefore = sameAppBefore.FirstOrDefault();
357+
app.CommandLineArguments = appBefore.CommandLineArguments;
358+
app.IsElevated = appBefore.IsElevated;
359+
}
360+
}
351361
}
352362

353363
internal void CloseExpanders()

src/modules/Workspaces/WorkspacesEditor/ViewModels/MainViewModel.cs

+6-7
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public class MainViewModel : INotifyPropertyChanged, IDisposable
4040
private Timer lastUpdatedTimer;
4141
private WorkspacesSettings settings;
4242
private PwaHelper _pwaHelper;
43+
private bool _isExistingProjectLaunched;
4344

4445
public ObservableCollection<Project> Workspaces { get; set; } = new ObservableCollection<Project>();
4546

@@ -256,12 +257,12 @@ public async void SnapWorkspace()
256257
{
257258
CancelSnapshot();
258259

259-
await Task.Run(() => RunSnapshotTool());
260+
await Task.Run(() => RunSnapshotTool(_isExistingProjectLaunched));
260261

261262
Project project = _workspacesEditorIO.ParseTempProject();
262263
if (project != null)
263264
{
264-
if (editedProject != null)
265+
if (_isExistingProjectLaunched)
265266
{
266267
project.UpdateAfterLaunchAndEdit(projectBeforeLaunch);
267268
project.EditorWindowTitle = Properties.Resources.EditWorkspace;
@@ -431,15 +432,12 @@ private void LastUpdatedTimerElapsed(object sender, ElapsedEventArgs e)
431432
}
432433
}
433434

434-
private void RunSnapshotTool(string filename = null)
435+
private void RunSnapshotTool(bool isExistingProjectLaunched)
435436
{
436437
Process process = new Process();
437438
process.StartInfo = new ProcessStartInfo(@".\PowerToys.WorkspacesSnapshotTool.exe");
438439
process.StartInfo.CreateNoWindow = true;
439-
if (!string.IsNullOrEmpty(filename))
440-
{
441-
process.StartInfo.Arguments = filename;
442-
}
440+
process.StartInfo.Arguments = isExistingProjectLaunched ? $"{(int)InvokePoint.LaunchAndEdit}" : string.Empty;
443441

444442
try
445443
{
@@ -484,6 +482,7 @@ internal void CloseAllPopups()
484482

485483
internal void EnterSnapshotMode(bool isExistingProjectLaunched)
486484
{
485+
_isExistingProjectLaunched = isExistingProjectLaunched;
487486
_mainWindow.WindowState = System.Windows.WindowState.Minimized;
488487
_overlayWindows.Clear();
489488
foreach (var screen in MonitorHelper.GetDpiUnawareScreens())

src/modules/Workspaces/WorkspacesLib/AppUtils.cpp

-1
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,5 @@ namespace Utils
397397
{
398398
return installPath.ends_with(NonLocalizable::ChromeFilename);
399399
}
400-
401400
}
402401
}

src/modules/Workspaces/WorkspacesLib/utils.h

+11-11
Original file line numberDiff line numberDiff line change
@@ -40,24 +40,24 @@ CommandLineArgs split(std::wstring s, const std::wstring& delimiter)
4040
{
4141
cmdArgs.isRestarted = true;
4242
}
43-
else if (!cmdArgs.workspaceId.empty())
44-
{
45-
try
46-
{
47-
auto invokePoint = static_cast<InvokePoint>(std::stoi(token));
48-
cmdArgs.invokePoint = invokePoint;
49-
}
50-
catch (std::exception)
51-
{
52-
}
53-
}
5443
else
5544
{
5645
auto guid = GuidFromString(token);
5746
if (guid.has_value())
5847
{
5948
cmdArgs.workspaceId = token;
6049
}
50+
else
51+
{
52+
try
53+
{
54+
auto invokePoint = static_cast<InvokePoint>(std::stoi(token));
55+
cmdArgs.invokePoint = invokePoint;
56+
}
57+
catch (std::exception)
58+
{
59+
}
60+
}
6161
}
6262
}
6363

src/modules/Workspaces/WorkspacesSnapshotTool/SnapshotUtils.cpp

+5-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#include <WorkspacesLib/AppUtils.h>
1212
#include <WorkspacesLib/PwaHelper.h>
13+
#include <WindowProperties/WorkspacesWindowPropertyUtils.h>
1314

1415
#pragma comment(lib, "ntdll.lib")
1516

@@ -38,7 +39,7 @@ namespace SnapshotUtils
3839
return false;
3940
}
4041

41-
std::vector<WorkspacesData::WorkspacesProject::Application> GetApps(const std::function<unsigned int(HWND)> getMonitorNumberFromWindowHandle, const std::function<WorkspacesData::WorkspacesProject::Monitor::MonitorRect(unsigned int)> getMonitorRect)
42+
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)
4243
{
4344
Utils::PwaHelper pwaHelper{};
4445
std::vector<WorkspacesData::WorkspacesProject::Application> apps{};
@@ -157,7 +158,10 @@ namespace SnapshotUtils
157158
rect.bottom = monitorRect.top + monitorRect.height;
158159
}
159160

161+
std::wstring guid = isGuidNeeded ? WorkspacesWindowProperties::GetGuidFromHwnd(window) : L"";
162+
160163
WorkspacesData::WorkspacesProject::Application app{
164+
.id = guid,
161165
.name = appData.name,
162166
.title = title,
163167
.path = appData.installPath,

src/modules/Workspaces/WorkspacesSnapshotTool/SnapshotUtils.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44

55
namespace SnapshotUtils
66
{
7-
std::vector<WorkspacesData::WorkspacesProject::Application> GetApps(const std::function<unsigned int(HWND)> getMonitorNumberFromWindowHandle, const std::function<WorkspacesData::WorkspacesProject::Monitor::MonitorRect(unsigned int)> getMonitorRect);
7+
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);
88
};

src/modules/Workspaces/WorkspacesSnapshotTool/main.cpp

+6-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <common/utils/gpo.h>
1414
#include <common/utils/logger_helper.h>
1515
#include <common/utils/UnhandledExceptionHandler.h>
16+
#include <WorkspacesLib/utils.h>
1617

1718
const std::wstring moduleName = L"Workspaces\\WorkspacesSnapshotTool";
1819
const std::wstring internalPath = L"";
@@ -46,13 +47,17 @@ int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, LPSTR cmdLine, int cm
4647
return -1;
4748
}
4849

50+
std::wstring cmdLineStr{ GetCommandLineW() };
51+
auto cmdArgs = split(cmdLineStr, L" ");
52+
4953
// create new project
5054
time_t creationTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
5155
WorkspacesData::WorkspacesProject project{ .id = CreateGuidString(), .creationTime = creationTime };
5256
Logger::trace(L"Creating workspace {}:{}", project.name, project.id);
5357

5458
project.monitors = MonitorUtils::IdentifyMonitors();
55-
project.apps = SnapshotUtils::GetApps([&](HWND window) -> unsigned int {
59+
bool isGuidNeeded = cmdArgs.invokePoint == InvokePoint::LaunchAndEdit;
60+
project.apps = SnapshotUtils::GetApps(isGuidNeeded, [&](HWND window) -> unsigned int {
5661
auto windowMonitor = MonitorFromWindow(window, MONITOR_DEFAULTTOPRIMARY);
5762
unsigned int monitorNumber = 0;
5863
for (const auto& monitor : project.monitors)

src/modules/Workspaces/WorkspacesWindowArranger/WindowArranger.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,7 @@ bool WindowArranger::moveWindow(HWND window, const WorkspacesData::WorkspacesPro
460460
if (PlacementHelper::SizeWindowToRect(window, currentMonitor, launchMinimized, launchMaximized, rect))
461461
{
462462
WorkspacesWindowProperties::StampWorkspacesLaunchedProperty(window);
463+
WorkspacesWindowProperties::StampWorkspacesGuidProperty(window, app.id);
463464
Logger::trace(L"Placed {} to ({},{}) [{}x{}]", app.name, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
464465
return true;
465466
}

src/modules/Workspaces/workspaces-common/GuidUtils.h

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#pragma once
2+
13
#include <shlobj.h>
24

35
inline std::optional<GUID> GuidFromString(const std::wstring& str) noexcept

0 commit comments

Comments
 (0)