Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Workspaces] Saving app properties on launch and recapture #36751

Merged
merged 8 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/spell-check/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,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
Loading