Skip to content

Commit

Permalink
[Workspaces] Saving app properties on launch and recapture (#36751)
Browse files Browse the repository at this point in the history
* [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
  • Loading branch information
donlaci authored Jan 16, 2025
1 parent 603379a commit f5f332c
Show file tree
Hide file tree
Showing 11 changed files with 107 additions and 26 deletions.
1 change: 1 addition & 0 deletions .github/actions/spell-check/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,7 @@ LOWORD
lparam
LPBITMAPINFOHEADER
LPCITEMIDLIST
LPCLSID
lpcmi
LPCMINVOKECOMMANDINFO
LPCREATESTRUCT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
18 changes: 14 additions & 4 deletions src/modules/Workspaces/WorkspacesEditor/Models/Project.cs
Original file line number Diff line number Diff line change
Expand Up @@ -342,12 +342,22 @@ private Rectangle GetCommonBounds()
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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class MainViewModel : INotifyPropertyChanged, IDisposable
private Timer lastUpdatedTimer;
private WorkspacesSettings settings;
private PwaHelper _pwaHelper;
private bool _isExistingProjectLaunched;

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

Expand Down Expand Up @@ -256,12 +257,12 @@ public async void SnapWorkspace()
{
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;
Expand Down Expand Up @@ -431,15 +432,12 @@ private void LastUpdatedTimerElapsed(object sender, ElapsedEventArgs e)
}
}

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
{
Expand Down Expand Up @@ -484,6 +482,7 @@ internal void CloseAllPopups()

internal void EnterSnapshotMode(bool isExistingProjectLaunched)
{
_isExistingProjectLaunched = isExistingProjectLaunched;
_mainWindow.WindowState = System.Windows.WindowState.Minimized;
_overlayWindows.Clear();
foreach (var screen in MonitorHelper.GetDpiUnawareScreens())
Expand Down
1 change: 0 additions & 1 deletion src/modules/Workspaces/WorkspacesLib/AppUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,5 @@ namespace Utils
{
return installPath.ends_with(NonLocalizable::ChromeFilename);
}

}
}
22 changes: 11 additions & 11 deletions src/modules/Workspaces/WorkspacesLib/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,24 +40,24 @@ CommandLineArgs split(std::wstring s, const std::wstring& delimiter)
{
cmdArgs.isRestarted = true;
}
else if (!cmdArgs.workspaceId.empty())
{
try
{
auto invokePoint = static_cast<InvokePoint>(std::stoi(token));
cmdArgs.invokePoint = invokePoint;
}
catch (std::exception)
{
}
}
else
{
auto guid = GuidFromString(token);
if (guid.has_value())
{
cmdArgs.workspaceId = token;
}
else
{
try
{
auto invokePoint = static_cast<InvokePoint>(std::stoi(token));
cmdArgs.invokePoint = invokePoint;
}
catch (std::exception)
{
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include <WorkspacesLib/AppUtils.h>
#include <WorkspacesLib/PwaHelper.h>
#include <WindowProperties/WorkspacesWindowPropertyUtils.h>

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

Expand Down Expand Up @@ -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{};
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
7 changes: 6 additions & 1 deletion src/modules/Workspaces/WorkspacesSnapshotTool/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"";
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
2 changes: 2 additions & 0 deletions src/modules/Workspaces/workspaces-common/GuidUtils.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#pragma once

#include <shlobj.h>

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

0 comments on commit f5f332c

Please sign in to comment.