diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 0e8491dc44..ba07b47d3a 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -769,6 +769,7 @@ LOWORD lparam LPBITMAPINFOHEADER LPCITEMIDLIST +LPCLSID lpcmi LPCMINVOKECOMMANDINFO LPCREATESTRUCT diff --git a/src/modules/Workspaces/WindowProperties/WorkspacesWindowPropertyUtils.h b/src/modules/Workspaces/WindowProperties/WorkspacesWindowPropertyUtils.h index 1c1ddeb6f4..2cd1d5488d 100644 --- a/src/modules/Workspaces/WindowProperties/WorkspacesWindowPropertyUtils.h +++ b/src/modules/Workspaces/WindowProperties/WorkspacesWindowPropertyUtils.h @@ -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(1)); } + + inline void StampWorkspacesGuidProperty(HWND window, const std::wstring& appId) + { + GUID guid; + HRESULT hr = CLSIDFromString(appId.c_str(), static_cast (&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(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(rawData); + } + else + { + return L""; + } + } + + GUID guid; + std::memcpy(&guid, &parts[0], sizeof(GUID)); + WCHAR* guidString; + StringFromCLSID(guid, &guidString); + + return guidString; + } } diff --git a/src/modules/Workspaces/WorkspacesEditor/Models/Project.cs b/src/modules/Workspaces/WorkspacesEditor/Models/Project.cs index 0c44440d39..860f78c9d8 100644 --- a/src/modules/Workspaces/WorkspacesEditor/Models/Project.cs +++ b/src/modules/Workspaces/WorkspacesEditor/Models/Project.cs @@ -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() diff --git a/src/modules/Workspaces/WorkspacesEditor/ViewModels/MainViewModel.cs b/src/modules/Workspaces/WorkspacesEditor/ViewModels/MainViewModel.cs index ccee84c9c5..b0974e1241 100644 --- a/src/modules/Workspaces/WorkspacesEditor/ViewModels/MainViewModel.cs +++ b/src/modules/Workspaces/WorkspacesEditor/ViewModels/MainViewModel.cs @@ -40,6 +40,7 @@ namespace WorkspacesEditor.ViewModels private Timer lastUpdatedTimer; private WorkspacesSettings settings; private PwaHelper _pwaHelper; + private bool _isExistingProjectLaunched; public ObservableCollection Workspaces { get; set; } = new ObservableCollection(); @@ -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()) diff --git a/src/modules/Workspaces/WorkspacesLib/AppUtils.cpp b/src/modules/Workspaces/WorkspacesLib/AppUtils.cpp index 9805dd3fe8..19b33214b7 100644 --- a/src/modules/Workspaces/WorkspacesLib/AppUtils.cpp +++ b/src/modules/Workspaces/WorkspacesLib/AppUtils.cpp @@ -397,6 +397,5 @@ namespace Utils { return installPath.ends_with(NonLocalizable::ChromeFilename); } - } } \ No newline at end of file diff --git a/src/modules/Workspaces/WorkspacesLib/utils.h b/src/modules/Workspaces/WorkspacesLib/utils.h index 971900d8ec..da836ccabc 100644 --- a/src/modules/Workspaces/WorkspacesLib/utils.h +++ b/src/modules/Workspaces/WorkspacesLib/utils.h @@ -40,17 +40,6 @@ CommandLineArgs split(std::wstring s, const std::wstring& delimiter) { cmdArgs.isRestarted = true; } - else if (!cmdArgs.workspaceId.empty()) - { - try - { - auto invokePoint = static_cast(std::stoi(token)); - cmdArgs.invokePoint = invokePoint; - } - catch (std::exception) - { - } - } else { auto guid = GuidFromString(token); @@ -58,6 +47,17 @@ CommandLineArgs split(std::wstring s, const std::wstring& delimiter) { cmdArgs.workspaceId = token; } + else + { + try + { + auto invokePoint = static_cast(std::stoi(token)); + cmdArgs.invokePoint = invokePoint; + } + catch (std::exception) + { + } + } } } diff --git a/src/modules/Workspaces/WorkspacesSnapshotTool/SnapshotUtils.cpp b/src/modules/Workspaces/WorkspacesSnapshotTool/SnapshotUtils.cpp index ddbf0f3cf7..ccc8601219 100644 --- a/src/modules/Workspaces/WorkspacesSnapshotTool/SnapshotUtils.cpp +++ b/src/modules/Workspaces/WorkspacesSnapshotTool/SnapshotUtils.cpp @@ -10,6 +10,7 @@ #include #include +#include #pragma comment(lib, "ntdll.lib") @@ -38,7 +39,7 @@ namespace SnapshotUtils return false; } - std::vector GetApps(const std::function getMonitorNumberFromWindowHandle, const std::function getMonitorRect) + std::vector GetApps(bool isGuidNeeded, const std::function getMonitorNumberFromWindowHandle, const std::function getMonitorRect) { Utils::PwaHelper pwaHelper{}; std::vector 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, diff --git a/src/modules/Workspaces/WorkspacesSnapshotTool/SnapshotUtils.h b/src/modules/Workspaces/WorkspacesSnapshotTool/SnapshotUtils.h index df95bbadf3..ad69e1e7e4 100644 --- a/src/modules/Workspaces/WorkspacesSnapshotTool/SnapshotUtils.h +++ b/src/modules/Workspaces/WorkspacesSnapshotTool/SnapshotUtils.h @@ -4,5 +4,5 @@ namespace SnapshotUtils { - std::vector GetApps(const std::function getMonitorNumberFromWindowHandle, const std::function getMonitorRect); + std::vector GetApps(bool isGuidNeeded, const std::function getMonitorNumberFromWindowHandle, const std::function getMonitorRect); }; diff --git a/src/modules/Workspaces/WorkspacesSnapshotTool/main.cpp b/src/modules/Workspaces/WorkspacesSnapshotTool/main.cpp index 8a99ea6ef5..44456be307 100644 --- a/src/modules/Workspaces/WorkspacesSnapshotTool/main.cpp +++ b/src/modules/Workspaces/WorkspacesSnapshotTool/main.cpp @@ -13,6 +13,7 @@ #include #include #include +#include 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) diff --git a/src/modules/Workspaces/WorkspacesWindowArranger/WindowArranger.cpp b/src/modules/Workspaces/WorkspacesWindowArranger/WindowArranger.cpp index 06d4fba780..2726022889 100644 --- a/src/modules/Workspaces/WorkspacesWindowArranger/WindowArranger.cpp +++ b/src/modules/Workspaces/WorkspacesWindowArranger/WindowArranger.cpp @@ -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; } diff --git a/src/modules/Workspaces/workspaces-common/GuidUtils.h b/src/modules/Workspaces/workspaces-common/GuidUtils.h index acab157938..f7b1cff768 100644 --- a/src/modules/Workspaces/workspaces-common/GuidUtils.h +++ b/src/modules/Workspaces/workspaces-common/GuidUtils.h @@ -1,3 +1,5 @@ +#pragma once + #include inline std::optional GuidFromString(const std::wstring& str) noexcept