diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 78affbb16f..7e9445f893 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -76,6 +76,7 @@ body: - System tray interaction - TextExtractor - Video Conference Mute + - Workspaces - Welcome / PowerToys Tour window validations: required: true diff --git a/.github/ISSUE_TEMPLATE/translation_issue.yml b/.github/ISSUE_TEMPLATE/translation_issue.yml index 88114730c6..8a6cd56280 100644 --- a/.github/ISSUE_TEMPLATE/translation_issue.yml +++ b/.github/ISSUE_TEMPLATE/translation_issue.yml @@ -50,6 +50,7 @@ body: - System tray interaction - TextExtractor - Video Conference Mute + - Workspaces - Welcome / PowerToys Tour window validations: required: true diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 2bf5ff9492..f5bc0156f8 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -56,11 +56,14 @@ APPBARDATA appdata APPEXECLINK Appium +applayout Applicationcan +APPLICATIONFRAMEHOST appmanifest APPNAME appref appsettings +appsfolder appwindow appwiz APSTUDIO @@ -240,6 +243,7 @@ CONTEXTMENUHANDLER CONTROLL CONTROLPARENT copiedcolorrepresentation +COREWINDOW cotaskmem COULDNOT countof @@ -832,6 +836,7 @@ lpwcx lpwndpl LReader LRESULT +LSTATUS lstrcmp lstrcmpi lstrlen @@ -1205,6 +1210,8 @@ projectname PROPBAG PROPERTYKEY propkey +propsys +PROPVARIANT propvarutil prvpane psapi @@ -1370,6 +1377,7 @@ sddl SDKDDK sdns searchterm +SEARCHUI secpol SENDCHANGE sendinput @@ -1552,7 +1560,9 @@ SYSKEYUP SYSLIB SYSMENU SYSTEMAPPS +systemsettings SYSTEMTIME +SYSTEMWOW tapp TApplication TApplied @@ -1664,9 +1674,11 @@ urlmon Usb USEDEFAULT USEFILEATTRIBUTES +USEPOSITION USERDATA Userenv USESHOWWINDOW +USESIZE USESTDHANDLES USRDLL UType @@ -1734,6 +1746,7 @@ vswhere Vtbl WANTPALM wbem +Wbemidl wbemuuid WBounds Wca @@ -1821,6 +1834,9 @@ WNDCLASSEXW WNDCLASSW WNDPROC workarounds +WORKSPACESEDITOR +WORKSPACESLAUNCHER +WORKSPACESSNAPSHOTTOOL wox wparam wpf diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json index 03a30fbe8d..b873c42b90 100644 --- a/.pipelines/ESRPSigning_core.json +++ b/.pipelines/ESRPSigning_core.json @@ -189,6 +189,14 @@ "WinUI3Apps\\PowerToys.PowerRenameContextMenu.dll", "WinUI3Apps\\PowerRenameContextMenuPackage.msix", + "PowerToys.WorkspacesSnapshotTool.exe", + "PowerToys.WorkspacesLauncher.exe", + "PowerToys.WorkspacesEditor.exe", + "PowerToys.WorkspacesEditor.dll", + "PowerToys.WorkspacesLauncherUI.exe", + "PowerToys.WorkspacesLauncherUI.dll", + "PowerToys.WorkspacesModuleInterface.dll", + "WinUI3Apps\\PowerToys.RegistryPreviewExt.dll", "WinUI3Apps\\PowerToys.RegistryPreviewUILib.dll", "WinUI3Apps\\PowerToys.RegistryPreview.dll", diff --git a/PowerToys.sln b/PowerToys.sln index d2b3e1c00c..d6c33873e2 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -171,14 +171,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject src\.editorconfig = src\.editorconfig .vsconfig = .vsconfig + src\Common.Dotnet.CsWinRT.props = src\Common.Dotnet.CsWinRT.props + src\Common.SelfContained.props = src\Common.SelfContained.props Cpp.Build.props = Cpp.Build.props Directory.Build.props = Directory.Build.props Directory.Build.targets = Directory.Build.targets Directory.Packages.props = Directory.Packages.props Solution.props = Solution.props src\Version.props = src\Version.props - src\Common.SelfContained.props = src\Common.SelfContained.props - src\Common.Dotnet.CsWinRT.props = src\Common.Dotnet.CsWinRT.props EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Settings.UI.Library", "src\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj", "{B1BCC8C6-46B5-4BFA-8F22-20F32D99EC6A}" @@ -277,6 +277,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643 src\common\utils\modulesRegistry.h = src\common\utils\modulesRegistry.h src\common\utils\MsiUtils.h = src\common\utils\MsiUtils.h src\common\utils\MsWindowsSettings.h = src\common\utils\MsWindowsSettings.h + src\common\utils\OnThreadExecutor.h = src\common\utils\OnThreadExecutor.h src\common\utils\os-detect.h = src\common\utils\os-detect.h src\common\utils\package.h = src\common\utils\package.h src\common\utils\ProcessWaiter.h = src\common\utils\ProcessWaiter.h @@ -456,7 +457,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FileLocksmithExt", "src\mod EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileLocksmithUI", "src\modules\FileLocksmith\FileLocksmithUI\FileLocksmithUI.csproj", "{E69B044A-2F8A-45AA-AD0B-256C59421807}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FileLocksmithLibInterop", "src\modules\FileLocksmith\FileLocksmithLibInterop\FileLocksmithLibInterop.vcxproj", "{C604B37E-9D0E-4484-8778-E8B31B0E1B3A}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToys.FileLocksmithLib.Interop", "src\modules\FileLocksmith\FileLocksmithLibInterop\FileLocksmithLibInterop.vcxproj", "{C604B37E-9D0E-4484-8778-E8B31B0E1B3A}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GPOWrapper", "src\common\GPOWrapper\GPOWrapper.vcxproj", "{E599C30B-9DC8-4E5A-BF27-93D4CCEDE788}" EndProject @@ -583,6 +584,37 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerToys.Settings.DSC.Sche EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToys.Interop", "src\common\interop\PowerToys.Interop.vcxproj", "{F055103B-F80B-4D0C-BF48-057C55620033}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Workspaces", "Workspaces", "{A2221D7E-55E7-4BEA-90D1-4F162D670BBF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workspaces-common", "workspaces-common", "{BE126CBB-AE12-406A-9837-A05ACFCA57A7}" + ProjectSection(SolutionItems) = preProject + src\modules\Workspaces\workspaces-common\GuidUtils.h = src\modules\Workspaces\workspaces-common\GuidUtils.h + src\modules\Workspaces\workspaces-common\InvokePoint.h = src\modules\Workspaces\workspaces-common\InvokePoint.h + src\modules\Workspaces\workspaces-common\MonitorEnumerator.h = src\modules\Workspaces\workspaces-common\MonitorEnumerator.h + src\modules\Workspaces\workspaces-common\MonitorUtils.h = src\modules\Workspaces\workspaces-common\MonitorUtils.h + src\modules\Workspaces\workspaces-common\VirtualDesktop.h = src\modules\Workspaces\workspaces-common\VirtualDesktop.h + src\modules\Workspaces\workspaces-common\WindowEnumerator.h = src\modules\Workspaces\workspaces-common\WindowEnumerator.h + src\modules\Workspaces\workspaces-common\WindowFilter.h = src\modules\Workspaces\workspaces-common\WindowFilter.h + src\modules\Workspaces\workspaces-common\WindowUtils.h = src\modules\Workspaces\workspaces-common\WindowUtils.h + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WindowProperties", "WindowProperties", "{14CB58B7-D280-4A7A-95DE-4B2DF14EA000}" + ProjectSection(SolutionItems) = preProject + src\modules\Workspaces\WindowProperties\WorkspacesWindowPropertyUtils.h = src\modules\Workspaces\WindowProperties\WorkspacesWindowPropertyUtils.h + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesLib", "src\modules\Workspaces\WorkspacesLib\WorkspacesLib.vcxproj", "{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkspacesLauncherUI", "src\modules\Workspaces\WorkspacesLauncherUI\WorkspacesLauncherUI.csproj", "{9C53CC25-0623-4569-95BC-B05410675EE3}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesModuleInterface", "src\modules\Workspaces\WorkspacesModuleInterface\WorkspacesModuleInterface.vcxproj", "{45285DF2-9742-4ECA-9AC9-58951FC26489}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesSnapshotTool", "src\modules\Workspaces\WorkspacesSnapshotTool\WorkspacesSnapshotTool.vcxproj", "{3D63307B-9D27-44FD-B033-B26F39245B85}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkspacesEditor", "src\modules\Workspaces\WorkspacesEditor\WorkspacesEditor.csproj", "{367D7543-7DBA-4381-99F1-BF6142A996C4}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesLauncher", "src\modules\Workspaces\WorkspacesLauncher\WorkspacesLauncher.vcxproj", "{2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -2601,6 +2633,78 @@ Global {F055103B-F80B-4D0C-BF48-057C55620033}.Release|x64.Build.0 = Release|x64 {F055103B-F80B-4D0C-BF48-057C55620033}.Release|x86.ActiveCfg = Release|x64 {F055103B-F80B-4D0C-BF48-057C55620033}.Release|x86.Build.0 = Release|x64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|ARM64.Build.0 = Debug|ARM64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|x64.ActiveCfg = Debug|x64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|x64.Build.0 = Debug|x64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|x86.ActiveCfg = Debug|x64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Debug|x86.Build.0 = Debug|x64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|ARM64.ActiveCfg = Release|ARM64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|ARM64.Build.0 = Release|ARM64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|x64.ActiveCfg = Release|x64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|x64.Build.0 = Release|x64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|x86.ActiveCfg = Release|x64 + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}.Release|x86.Build.0 = Release|x64 + {9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|ARM64.Build.0 = Debug|ARM64 + {9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|x64.ActiveCfg = Debug|x64 + {9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|x64.Build.0 = Debug|x64 + {9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|x86.ActiveCfg = Debug|x64 + {9C53CC25-0623-4569-95BC-B05410675EE3}.Debug|x86.Build.0 = Debug|x64 + {9C53CC25-0623-4569-95BC-B05410675EE3}.Release|ARM64.ActiveCfg = Release|ARM64 + {9C53CC25-0623-4569-95BC-B05410675EE3}.Release|ARM64.Build.0 = Release|ARM64 + {9C53CC25-0623-4569-95BC-B05410675EE3}.Release|x64.ActiveCfg = Release|x64 + {9C53CC25-0623-4569-95BC-B05410675EE3}.Release|x64.Build.0 = Release|x64 + {9C53CC25-0623-4569-95BC-B05410675EE3}.Release|x86.ActiveCfg = Release|x64 + {9C53CC25-0623-4569-95BC-B05410675EE3}.Release|x86.Build.0 = Release|x64 + {45285DF2-9742-4ECA-9AC9-58951FC26489}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {45285DF2-9742-4ECA-9AC9-58951FC26489}.Debug|ARM64.Build.0 = Debug|ARM64 + {45285DF2-9742-4ECA-9AC9-58951FC26489}.Debug|x64.ActiveCfg = Debug|x64 + {45285DF2-9742-4ECA-9AC9-58951FC26489}.Debug|x64.Build.0 = Debug|x64 + {45285DF2-9742-4ECA-9AC9-58951FC26489}.Debug|x86.ActiveCfg = Debug|x64 + {45285DF2-9742-4ECA-9AC9-58951FC26489}.Debug|x86.Build.0 = Debug|x64 + {45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|ARM64.ActiveCfg = Release|ARM64 + {45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|ARM64.Build.0 = Release|ARM64 + {45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|x64.ActiveCfg = Release|x64 + {45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|x64.Build.0 = Release|x64 + {45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|x86.ActiveCfg = Release|x64 + {45285DF2-9742-4ECA-9AC9-58951FC26489}.Release|x86.Build.0 = Release|x64 + {3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|ARM64.Build.0 = Debug|ARM64 + {3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|x64.ActiveCfg = Debug|x64 + {3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|x64.Build.0 = Debug|x64 + {3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|x86.ActiveCfg = Debug|x64 + {3D63307B-9D27-44FD-B033-B26F39245B85}.Debug|x86.Build.0 = Debug|x64 + {3D63307B-9D27-44FD-B033-B26F39245B85}.Release|ARM64.ActiveCfg = Release|ARM64 + {3D63307B-9D27-44FD-B033-B26F39245B85}.Release|ARM64.Build.0 = Release|ARM64 + {3D63307B-9D27-44FD-B033-B26F39245B85}.Release|x64.ActiveCfg = Release|x64 + {3D63307B-9D27-44FD-B033-B26F39245B85}.Release|x64.Build.0 = Release|x64 + {3D63307B-9D27-44FD-B033-B26F39245B85}.Release|x86.ActiveCfg = Release|x64 + {3D63307B-9D27-44FD-B033-B26F39245B85}.Release|x86.Build.0 = Release|x64 + {367D7543-7DBA-4381-99F1-BF6142A996C4}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {367D7543-7DBA-4381-99F1-BF6142A996C4}.Debug|ARM64.Build.0 = Debug|ARM64 + {367D7543-7DBA-4381-99F1-BF6142A996C4}.Debug|x64.ActiveCfg = Debug|x64 + {367D7543-7DBA-4381-99F1-BF6142A996C4}.Debug|x64.Build.0 = Debug|x64 + {367D7543-7DBA-4381-99F1-BF6142A996C4}.Debug|x86.ActiveCfg = Debug|x64 + {367D7543-7DBA-4381-99F1-BF6142A996C4}.Debug|x86.Build.0 = Debug|x64 + {367D7543-7DBA-4381-99F1-BF6142A996C4}.Release|ARM64.ActiveCfg = Release|ARM64 + {367D7543-7DBA-4381-99F1-BF6142A996C4}.Release|ARM64.Build.0 = Release|ARM64 + {367D7543-7DBA-4381-99F1-BF6142A996C4}.Release|x64.ActiveCfg = Release|x64 + {367D7543-7DBA-4381-99F1-BF6142A996C4}.Release|x64.Build.0 = Release|x64 + {367D7543-7DBA-4381-99F1-BF6142A996C4}.Release|x86.ActiveCfg = Release|x64 + {367D7543-7DBA-4381-99F1-BF6142A996C4}.Release|x86.Build.0 = Release|x64 + {2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|ARM64.Build.0 = Debug|ARM64 + {2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|x64.ActiveCfg = Debug|x64 + {2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|x64.Build.0 = Debug|x64 + {2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|x86.ActiveCfg = Debug|x64 + {2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Debug|x86.Build.0 = Debug|x64 + {2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|ARM64.ActiveCfg = Release|ARM64 + {2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|ARM64.Build.0 = Release|ARM64 + {2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x64.ActiveCfg = Release|x64 + {2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x64.Build.0 = Release|x64 + {2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x86.ActiveCfg = Release|x64 + {2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96}.Release|x86.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2817,6 +2921,15 @@ Global {C0974915-8A1D-4BF0-977B-9587D3807AB7} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} {1D6893CB-BC0C-46A8-A76C-9728706CA51A} = {557C4636-D7E1-4838-A504-7D19B725EE95} {F055103B-F80B-4D0C-BF48-057C55620033} = {5A7818A8-109C-4E1C-850D-1A654E234B0E} + {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} + {BE126CBB-AE12-406A-9837-A05ACFCA57A7} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} + {14CB58B7-D280-4A7A-95DE-4B2DF14EA000} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} + {B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} + {9C53CC25-0623-4569-95BC-B05410675EE3} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} + {45285DF2-9742-4ECA-9AC9-58951FC26489} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} + {3D63307B-9D27-44FD-B033-B26F39245B85} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} + {367D7543-7DBA-4381-99F1-BF6142A996C4} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} + {2CAC093E-5FCF-4102-9C2C-AC7DD5D9EB96} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/doc/images/icons/Workspaces.png b/doc/images/icons/Workspaces.png new file mode 100644 index 0000000000..18f13f1c0c Binary files /dev/null and b/doc/images/icons/Workspaces.png differ diff --git a/installer/PowerToysSetup/Common.wxi b/installer/PowerToysSetup/Common.wxi index 4d64a0c63c..f036b3797b 100644 --- a/installer/PowerToysSetup/Common.wxi +++ b/installer/PowerToysSetup/Common.wxi @@ -18,6 +18,7 @@ + diff --git a/installer/PowerToysSetup/Resources.wxs b/installer/PowerToysSetup/Resources.wxs index c4bf93d59b..0f4e10f4a6 100644 --- a/installer/PowerToysSetup/Resources.wxs +++ b/installer/PowerToysSetup/Resources.wxs @@ -449,6 +449,15 @@ + + + + + + diff --git a/installer/PowerToysSetupCustomActions/CustomAction.cpp b/installer/PowerToysSetupCustomActions/CustomAction.cpp index a00b6bdad7..6e931e4749 100644 --- a/installer/PowerToysSetupCustomActions/CustomAction.cpp +++ b/installer/PowerToysSetupCustomActions/CustomAction.cpp @@ -1223,7 +1223,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall) } processes.resize(bytes / sizeof(processes[0])); - std::array processesToTerminate = { + std::array processesToTerminate = { L"PowerToys.PowerLauncher.exe", L"PowerToys.Settings.exe", L"PowerToys.AdvancedPaste.exe", @@ -1255,6 +1255,10 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall) L"PowerToys.MouseWithoutBordersService.exe", L"PowerToys.CropAndLock.exe", L"PowerToys.EnvironmentVariables.exe", + L"PowerToys.WorkspacesSnapshotTool.exe", + L"PowerToys.WorkspacesLauncher.exe", + L"PowerToys.WorkspacesLauncherUI.exe", + L"PowerToys.WorkspacesEditor.exe", L"PowerToys.exe", }; diff --git a/src/common/Common.UI/SettingsDeepLink.cs b/src/common/Common.UI/SettingsDeepLink.cs index c3a81a49a1..41bd2c012e 100644 --- a/src/common/Common.UI/SettingsDeepLink.cs +++ b/src/common/Common.UI/SettingsDeepLink.cs @@ -31,6 +31,7 @@ namespace Common.UI EnvironmentVariables, Dashboard, AdvancedPaste, + Workspaces, } private static string SettingsWindowNameToString(SettingsWindow value) @@ -77,6 +78,8 @@ namespace Common.UI return "Dashboard"; case SettingsWindow.AdvancedPaste: return "AdvancedPaste"; + case SettingsWindow.Workspaces: + return "Workspaces"; default: { return string.Empty; diff --git a/src/common/Display/Display.vcxproj b/src/common/Display/Display.vcxproj index e69a2ada2d..87b74ba534 100644 --- a/src/common/Display/Display.vcxproj +++ b/src/common/Display/Display.vcxproj @@ -24,15 +24,18 @@ NotUsing - ..\..\..\;%(AdditionalIncludeDirectories) + ..\..\..\;..\..\common;.\;%(AdditionalIncludeDirectories) _LIB;%(PreprocessorDefinitions) + + + diff --git a/src/common/Display/DisplayUtils.cpp b/src/common/Display/DisplayUtils.cpp new file mode 100644 index 0000000000..85f9c4b1fc --- /dev/null +++ b/src/common/Display/DisplayUtils.cpp @@ -0,0 +1,143 @@ +#include "DisplayUtils.h" + +#include +#include +#include + +#include +#include + +#include + +namespace DisplayUtils +{ + std::wstring remove_non_digits(const std::wstring& input) + { + std::wstring result; + std::copy_if(input.begin(), input.end(), std::back_inserter(result), [](wchar_t ch) { return std::iswdigit(ch); }); + return result; + } + + std::pair SplitDisplayDeviceId(const std::wstring& str) noexcept + { + // format: \\?\DISPLAY#{device id}#{instance id}#{some other id} + // example: \\?\DISPLAY#GSM1388#4&125707d6&0&UID8388688#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7} + // output: { GSM1388, 4&125707d6&0&UID8388688 } + + size_t nameStartPos = str.find_first_of('#'); + size_t uidStartPos = str.find('#', nameStartPos + 1); + size_t uidEndPos = str.find('#', uidStartPos + 1); + + if (nameStartPos == std::string::npos || uidStartPos == std::string::npos || uidEndPos == std::string::npos) + { + return { str, L"" }; + } + + return { str.substr(nameStartPos + 1, uidStartPos - nameStartPos - 1), str.substr(uidStartPos + 1, uidEndPos - uidStartPos - 1) }; + } + + std::pair> GetDisplays() + { + bool success = true; + std::vector result{}; + auto allMonitors = MonitorEnumerator::Enumerate(); + + OnThreadExecutor dpiUnawareThread; + dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] { + SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE); + SetThreadDpiHostingBehavior(DPI_HOSTING_BEHAVIOR_MIXED); + } }).wait(); + + for (auto& monitorData : allMonitors) + { + MONITORINFOEX monitorInfo = monitorData.second; + MONITORINFOEX dpiUnawareMonitorInfo{}; + + dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] { + dpiUnawareMonitorInfo.cbSize = sizeof(dpiUnawareMonitorInfo); + if (!GetMonitorInfo(monitorData.first, &dpiUnawareMonitorInfo)) + { + return; + } + } }).wait(); + + UINT dpi = 0; + if (DPIAware::GetScreenDPIForMonitor(monitorData.first, dpi) != S_OK) + { + success = false; + break; + } + + DisplayUtils::DisplayData data{ + .monitor = monitorData.first, + .dpi = dpi, + .monitorRectDpiAware = monitorInfo.rcMonitor, + .monitorRectDpiUnaware = dpiUnawareMonitorInfo.rcMonitor, + }; + + bool foundActiveMonitor = false; + DISPLAY_DEVICE displayDevice{ .cb = sizeof(displayDevice) }; + DWORD displayDeviceIndex = 0; + while (EnumDisplayDevicesW(monitorInfo.szDevice, displayDeviceIndex, &displayDevice, EDD_GET_DEVICE_INTERFACE_NAME)) + { + /* + * if (WI_IsFlagSet(displayDevice.StateFlags, DISPLAY_DEVICE_ACTIVE) && + WI_IsFlagClear(displayDevice.StateFlags, DISPLAY_DEVICE_MIRRORING_DRIVER)) + */ + if (((displayDevice.StateFlags & DISPLAY_DEVICE_ACTIVE) == DISPLAY_DEVICE_ACTIVE) && + (displayDevice.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) == 0) + { + // Find display devices associated with the display. + foundActiveMonitor = true; + break; + } + + displayDeviceIndex++; + } + + if (foundActiveMonitor) + { + auto deviceId = SplitDisplayDeviceId(displayDevice.DeviceID); + data.id = deviceId.first; + data.instanceId = deviceId.second; + try + { + std::wstring numberStr = displayDevice.DeviceName; // \\.\DISPLAY1\Monitor0 + numberStr = numberStr.substr(0, numberStr.find_last_of('\\')); // \\.\DISPLAY1 + numberStr = remove_non_digits(numberStr); + data.number = std::stoi(numberStr); + } + catch (...) + { + success = false; + break; + } + } + else + { + success = false; + + // Use the display name as a fallback value when no proper device was found. + data.id = monitorInfo.szDevice; + data.instanceId = L""; + + try + { + std::wstring numberStr = monitorInfo.szDevice; // \\.\DISPLAY1 + numberStr = remove_non_digits(numberStr); + data.number = std::stoi(numberStr); + } + catch (...) + { + success = false; + break; + } + } + + result.push_back(data); + } + + return { success, result }; + } + +} diff --git a/src/common/Display/DisplayUtils.h b/src/common/Display/DisplayUtils.h new file mode 100644 index 0000000000..0b0e86e227 --- /dev/null +++ b/src/common/Display/DisplayUtils.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include + +namespace DisplayUtils +{ + struct DisplayData + { + HMONITOR monitor{}; + std::wstring id; + std::wstring instanceId; + unsigned int number{}; + unsigned int dpi{}; + RECT monitorRectDpiAware{}; + RECT monitorRectDpiUnaware{}; + }; + + std::pair> GetDisplays(); +}; diff --git a/src/common/Display/MonitorEnumerator.h b/src/common/Display/MonitorEnumerator.h new file mode 100644 index 0000000000..c603bfee6d --- /dev/null +++ b/src/common/Display/MonitorEnumerator.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include + +class MonitorEnumerator +{ +public: + static std::vector> Enumerate() + { + MonitorEnumerator inst; + EnumDisplayMonitors(NULL, NULL, Callback, reinterpret_cast(&inst)); + return inst.m_monitors; + } + +private: + MonitorEnumerator() = default; + ~MonitorEnumerator() = default; + + static BOOL CALLBACK Callback(HMONITOR monitor, HDC /*hdc*/, LPRECT /*pRect*/, LPARAM param) + { + MonitorEnumerator* inst = reinterpret_cast(param); + MONITORINFOEX mi; + mi.cbSize = sizeof(mi); + if (GetMonitorInfo(monitor, &mi)) + { + inst->m_monitors.push_back({monitor, mi}); + } + + return TRUE; + } + + std::vector> m_monitors; +}; \ No newline at end of file diff --git a/src/common/Display/dpi_aware.cpp b/src/common/Display/dpi_aware.cpp index 2ed0228ae3..8397430c6d 100644 --- a/src/common/Display/dpi_aware.cpp +++ b/src/common/Display/dpi_aware.cpp @@ -1,7 +1,9 @@ #include "dpi_aware.h" + #include "monitors.h" #include #include +#include namespace DPIAware { @@ -60,6 +62,24 @@ namespace DPIAware } } + void Convert(HMONITOR monitor_handle, RECT& rect) + { + if (monitor_handle == NULL) + { + const POINT ptZero = { 0, 0 }; + monitor_handle = MonitorFromPoint(ptZero, MONITOR_DEFAULTTOPRIMARY); + } + + UINT dpi_x, dpi_y; + if (GetDpiForMonitor(monitor_handle, MDT_EFFECTIVE_DPI, &dpi_x, &dpi_y) == S_OK) + { + rect.left = static_cast(std::round(rect.left * static_cast(dpi_x) / DEFAULT_DPI)); + rect.right = static_cast(std::round(rect.right * static_cast(dpi_x) / DEFAULT_DPI)); + rect.top = static_cast(std::round(rect.top * static_cast(dpi_y) / DEFAULT_DPI)); + rect.bottom = static_cast(std::round(rect.bottom * static_cast(dpi_y) / DEFAULT_DPI)); + } + } + void ConvertByCursorPosition(float& width, float& height) { HMONITOR targetMonitor = nullptr; diff --git a/src/common/Display/dpi_aware.h b/src/common/Display/dpi_aware.h index f93e8f87ad..a63365aa2f 100644 --- a/src/common/Display/dpi_aware.h +++ b/src/common/Display/dpi_aware.h @@ -12,6 +12,7 @@ namespace DPIAware HRESULT GetScreenDPIForPoint(POINT p, UINT& dpi); HRESULT GetScreenDPIForCursor(UINT& dpi); void Convert(HMONITOR monitor_handle, float& width, float& height); + void Convert(HMONITOR monitor_handle, RECT& rect); void ConvertByCursorPosition(float& width, float& height); void InverseConvert(HMONITOR monitor_handle, float& width, float& height); void EnableDPIAwarenessForThisProcess(); diff --git a/src/common/GPOWrapper/GPOWrapper.cpp b/src/common/GPOWrapper/GPOWrapper.cpp index de987ed81e..08c896d0f3 100644 --- a/src/common/GPOWrapper/GPOWrapper.cpp +++ b/src/common/GPOWrapper/GPOWrapper.cpp @@ -1,4 +1,4 @@ -#include "pch.h" +#include "pch.h" #include "GPOWrapper.h" #include "GPOWrapper.g.cpp" @@ -176,6 +176,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation { return static_cast(powertoys_gpo::getAllowedAdvancedPasteOnlineAIModelsValue()); } + GpoRuleConfigured GPOWrapper::GetConfiguredWorkspacesEnabledValue() + { + return static_cast(powertoys_gpo::getConfiguredWorkspacesEnabledValue()); + } GpoRuleConfigured GPOWrapper::GetConfiguredMwbClipboardSharingEnabledValue() { return static_cast(powertoys_gpo::getConfiguredMwbClipboardSharingEnabledValue()); diff --git a/src/common/GPOWrapper/GPOWrapper.h b/src/common/GPOWrapper/GPOWrapper.h index 670124527b..b0392cea1e 100644 --- a/src/common/GPOWrapper/GPOWrapper.h +++ b/src/common/GPOWrapper/GPOWrapper.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "GPOWrapper.g.h" #include @@ -50,6 +50,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue(); static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue(); static GpoRuleConfigured GetAllowedAdvancedPasteOnlineAIModelsValue(); + static GpoRuleConfigured GetConfiguredWorkspacesEnabledValue(); static GpoRuleConfigured GetConfiguredMwbClipboardSharingEnabledValue(); static GpoRuleConfigured GetConfiguredMwbFileTransferEnabledValue(); static GpoRuleConfigured GetConfiguredMwbUseOriginalUserInterfaceValue(); diff --git a/src/common/GPOWrapper/GPOWrapper.idl b/src/common/GPOWrapper/GPOWrapper.idl index 27c2420b6e..81ff61121a 100644 --- a/src/common/GPOWrapper/GPOWrapper.idl +++ b/src/common/GPOWrapper/GPOWrapper.idl @@ -54,6 +54,7 @@ namespace PowerToys static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue(); static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue(); static GpoRuleConfigured GetAllowedAdvancedPasteOnlineAIModelsValue(); + static GpoRuleConfigured GetConfiguredWorkspacesEnabledValue(); static GpoRuleConfigured GetConfiguredMwbClipboardSharingEnabledValue(); static GpoRuleConfigured GetConfiguredMwbFileTransferEnabledValue(); static GpoRuleConfigured GetConfiguredMwbUseOriginalUserInterfaceValue(); diff --git a/src/common/GPOWrapperProjection/GPOWrapper.cs b/src/common/GPOWrapperProjection/GPOWrapper.cs index f0c0b8421c..6cb91a69ac 100644 --- a/src/common/GPOWrapperProjection/GPOWrapper.cs +++ b/src/common/GPOWrapperProjection/GPOWrapper.cs @@ -61,5 +61,10 @@ namespace PowerToys.GPOWrapperProjection { return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetRunPluginEnabledValue(pluginID); } + + public static GpoRuleConfigured GetConfiguredWorkspacesEnabledValue() + { + return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredWorkspacesEnabledValue(); + } } } diff --git a/src/common/ManagedCommon/ModuleType.cs b/src/common/ManagedCommon/ModuleType.cs index de57f5138c..6f15bc3a15 100644 --- a/src/common/ManagedCommon/ModuleType.cs +++ b/src/common/ManagedCommon/ModuleType.cs @@ -30,5 +30,6 @@ namespace ManagedCommon MeasureTool, ShortcutGuide, PowerOCR, + Workspaces, } } diff --git a/src/common/interop/Constants.cpp b/src/common/interop/Constants.cpp index 30969a75b4..63e9782346 100644 --- a/src/common/interop/Constants.cpp +++ b/src/common/interop/Constants.cpp @@ -147,4 +147,8 @@ namespace winrt::PowerToys::Interop::implementation { return CommonSharedConstants::SHOW_ENVIRONMENT_VARIABLES_ADMIN_EVENT; } + hstring Constants::WorkspacesLaunchEditorEvent() + { + return CommonSharedConstants::WORKSPACES_LAUNCH_EDITOR_EVENT; + } } diff --git a/src/common/interop/Constants.h b/src/common/interop/Constants.h index cc8ebc01b1..978ca8ab60 100644 --- a/src/common/interop/Constants.h +++ b/src/common/interop/Constants.h @@ -40,6 +40,7 @@ namespace winrt::PowerToys::Interop::implementation static hstring CropAndLockReparentEvent(); static hstring ShowEnvironmentVariablesSharedEvent(); static hstring ShowEnvironmentVariablesAdminSharedEvent(); + static hstring WorkspacesLaunchEditorEvent(); }; } diff --git a/src/common/interop/Constants.idl b/src/common/interop/Constants.idl index 72d9fc58a0..4c4125b7db 100644 --- a/src/common/interop/Constants.idl +++ b/src/common/interop/Constants.idl @@ -37,6 +37,7 @@ namespace PowerToys static String CropAndLockReparentEvent(); static String ShowEnvironmentVariablesSharedEvent(); static String ShowEnvironmentVariablesAdminSharedEvent(); + static String WorkspacesLaunchEditorEvent(); } } } \ No newline at end of file diff --git a/src/common/interop/shared_constants.h b/src/common/interop/shared_constants.h index c5d44524c9..5237c15737 100644 --- a/src/common/interop/shared_constants.h +++ b/src/common/interop/shared_constants.h @@ -43,6 +43,8 @@ namespace CommonSharedConstants const wchar_t FANCY_ZONES_EDITOR_TOGGLE_EVENT[] = L"Local\\FancyZones-ToggleEditorEvent-1e174338-06a3-472b-874d-073b21c62f14"; + const wchar_t WORKSPACES_LAUNCH_EDITOR_EVENT[] = L"Local\\Workspaces-LaunchEditorEvent-a55ff427-cf62-4994-a2cd-9f72139296bf"; + const wchar_t SHOW_HOSTS_EVENT[] = L"Local\\Hosts-ShowHostsEvent-5a0c0aae-5ff5-40f5-95c2-20e37ed671f0"; const wchar_t SHOW_HOSTS_ADMIN_EVENT[] = L"Local\\Hosts-ShowHostsAdminEvent-60ff44e2-efd3-43bf-928a-f4d269f98bec"; @@ -98,6 +100,8 @@ namespace CommonSharedConstants const wchar_t SHOW_ENVIRONMENT_VARIABLES_EVENT[] = L"Local\\PowerToysEnvironmentVariables-ShowEnvironmentVariablesEvent-1021f616-e951-4d64-b231-a8f972159978"; const wchar_t SHOW_ENVIRONMENT_VARIABLES_ADMIN_EVENT[] = L"Local\\PowerToysEnvironmentVariables-EnvironmentVariablesAdminEvent-8c95d2ad-047c-49a2-9e8b-b4656326cfb2"; + const wchar_t WORKSPACES_EXIT_EVENT[] = L"Local\\PowerToys-Workspaces-ExitEvent-29a1566f-f4f8-4d56-9435-d2a437f727c6"; + // Max DWORD for key code to disable keys. const DWORD VK_DISABLED = 0x100; } diff --git a/src/common/logger/logger_settings.h b/src/common/logger/logger_settings.h index cc1b3825ce..345286a38b 100644 --- a/src/common/logger/logger_settings.h +++ b/src/common/logger/logger_settings.h @@ -69,6 +69,10 @@ struct LogSettings inline const static std::string environmentVariablesLoggerName = "environment-variables"; inline const static std::wstring cmdNotFoundLogPath = L"Logs\\cmd-not-found-log.txt"; inline const static std::string cmdNotFoundLoggerName = "cmd-not-found"; + inline const static std::string workspacesLauncherLoggerName = "workspaces-launcher"; + inline const static std::wstring workspacesLauncherLogPath = L"workspaces-launcher-log.txt"; + inline const static std::string workspacesSnapshotToolLoggerName = "workspaces-snapshot-tool"; + inline const static std::wstring workspacesSnapshotToolLogPath = L"workspaces-snapshot-tool-log.txt"; inline const static int retention = 30; std::wstring logLevel; LogSettings(); diff --git a/src/common/utils/OnThreadExecutor.h b/src/common/utils/OnThreadExecutor.h new file mode 100644 index 0000000000..c361a33700 --- /dev/null +++ b/src/common/utils/OnThreadExecutor.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include +#include +#include + +// OnThreadExecutor allows its caller to off-load some work to a persistently running background thread. +// This might come in handy if you use the API which sets thread-wide global state and the state needs +// to be isolated. + +class OnThreadExecutor final +{ +public: + using task_t = std::packaged_task; + + OnThreadExecutor() : + _shutdown_request{ false }, + _worker_thread{ [this] { worker_thread(); } } + { + } + + ~OnThreadExecutor() + { + _shutdown_request = true; + _task_cv.notify_one(); + _worker_thread.join(); + } + + std::future submit(task_t task) + { + auto future = task.get_future(); + std::lock_guard lock{ _task_mutex }; + _task_queue.emplace(std::move(task)); + _task_cv.notify_one(); + return future; + } + + void cancel() + { + std::lock_guard lock{ _task_mutex }; + _task_queue = {}; + _task_cv.notify_one(); + } + +private: + void worker_thread() + { + while (!_shutdown_request) + { + task_t task; + { + std::unique_lock task_lock{ _task_mutex }; + _task_cv.wait(task_lock, [this] { return !_task_queue.empty() || _shutdown_request; }); + if (_shutdown_request) + { + return; + } + task = std::move(_task_queue.front()); + _task_queue.pop(); + } + task(); + } + } + + std::mutex _task_mutex; + std::condition_variable _task_cv; + std::atomic_bool _shutdown_request; + std::queue> _task_queue; + std::thread _worker_thread; +}; diff --git a/src/common/utils/gpo.h b/src/common/utils/gpo.h index cba300d8e5..8f71a21205 100644 --- a/src/common/utils/gpo.h +++ b/src/common/utils/gpo.h @@ -60,6 +60,7 @@ namespace powertoys_gpo { const std::wstring POLICY_CONFIGURE_ENABLED_ENVIRONMENT_VARIABLES = L"ConfigureEnabledUtilityEnvironmentVariables"; const std::wstring POLICY_CONFIGURE_ENABLED_QOI_PREVIEW = L"ConfigureEnabledUtilityFileExplorerQOIPreview"; const std::wstring POLICY_CONFIGURE_ENABLED_QOI_THUMBNAILS = L"ConfigureEnabledUtilityFileExplorerQOIThumbnails"; + const std::wstring POLICY_CONFIGURE_ENABLED_WORKSPACES = L"ConfigureEnabledUtilityWorkspaces"; // The registry value names for PowerToys installer and update policies. const std::wstring POLICY_DISABLE_PER_USER_INSTALLATION = L"PerUserInstallationDisabled"; @@ -399,6 +400,11 @@ namespace powertoys_gpo { return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_ADVANCED_PASTE); } + inline gpo_rule_configured_t getConfiguredWorkspacesEnabledValue() + { + return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_WORKSPACES); + } + inline gpo_rule_configured_t getConfiguredVideoConferenceMuteEnabledValue() { return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_VIDEO_CONFERENCE_MUTE); diff --git a/src/dsc/Microsoft.PowerToys.Configure/examples/disableAllModules.dsc.yaml b/src/dsc/Microsoft.PowerToys.Configure/examples/disableAllModules.dsc.yaml index 8fa39a1050..0cfc689316 100644 --- a/src/dsc/Microsoft.PowerToys.Configure/examples/disableAllModules.dsc.yaml +++ b/src/dsc/Microsoft.PowerToys.Configure/examples/disableAllModules.dsc.yaml @@ -57,6 +57,8 @@ properties: EnableQoiThumbnail: false PowerOcr: Enabled: false + Workspaces: + Enabled: false ShortcutGuide: Enabled: false VideoConference: diff --git a/src/dsc/Microsoft.PowerToys.Configure/examples/enableAllModules.dsc.yaml b/src/dsc/Microsoft.PowerToys.Configure/examples/enableAllModules.dsc.yaml index a7b69fd423..c3bd636177 100644 --- a/src/dsc/Microsoft.PowerToys.Configure/examples/enableAllModules.dsc.yaml +++ b/src/dsc/Microsoft.PowerToys.Configure/examples/enableAllModules.dsc.yaml @@ -57,6 +57,8 @@ properties: EnableQoiThumbnail: true PowerOcr: Enabled: true + Workspaces: + Enabled: true ShortcutGuide: Enabled: true VideoConference: diff --git a/src/gpo/assets/PowerToys.admx b/src/gpo/assets/PowerToys.admx index a0f70baa30..a79e6f627c 100644 --- a/src/gpo/assets/PowerToys.admx +++ b/src/gpo/assets/PowerToys.admx @@ -1,11 +1,11 @@ - + - + @@ -20,6 +20,7 @@ + @@ -364,6 +365,16 @@ + + + + + + + + + + diff --git a/src/gpo/assets/en-US/PowerToys.adml b/src/gpo/assets/en-US/PowerToys.adml index 761abde90f..37f8124bd1 100644 --- a/src/gpo/assets/en-US/PowerToys.adml +++ b/src/gpo/assets/en-US/PowerToys.adml @@ -10,6 +10,7 @@ Installer and Updates PowerToys Run Advanced Paste + Workspaces Mouse Without Borders General settings @@ -25,6 +26,7 @@ PowerToys version 0.81.0 or later PowerToys version 0.81.1 or later PowerToys version 0.83.0 or later + PowerToys version 0.84.0 or later This policy configures the enabled state for all PowerToys utilities. @@ -110,6 +112,12 @@ If you don't configure this setting, users are able to enable or disable the plu You can override this policy for individual plugins using the policy "Configure enabled state for individual plugins". Note: Changes require a restart of PowerToys Run. + + This policy configures the enabled disable state for the Workspaces utility. + +If you enable or don't configure this policy, the user takes control over the enabled state of the Workspaces utility. + +If you disable this policy, the user won't be able to enable Enable and use the Workspaces utility. With this policy you can configure an individual enabled state for each PowerToys Run plugin that you add to the list. @@ -219,6 +227,7 @@ If you disable or don't configure this policy, no predefined rules are applied. Peek: Configure enabled state Power Rename: Configure enabled state PowerToys Run: Configure enabled state + PowerToys Workspaces: Configure enabled state Quick Accent: Configure enabled state Registry Preview: Configure enabled state Screen Ruler: Configure enabled state diff --git a/src/modules/Workspaces/Assets/Workspaces.ico b/src/modules/Workspaces/Assets/Workspaces.ico new file mode 100644 index 0000000000..14292758fa Binary files /dev/null and b/src/modules/Workspaces/Assets/Workspaces.ico differ diff --git a/src/modules/Workspaces/WindowProperties/WorkspacesWindowPropertyUtils.h b/src/modules/Workspaces/WindowProperties/WorkspacesWindowPropertyUtils.h new file mode 100644 index 0000000000..1c1ddeb6f4 --- /dev/null +++ b/src/modules/Workspaces/WindowProperties/WorkspacesWindowPropertyUtils.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace WorkspacesWindowProperties +{ + namespace Properties + { + const wchar_t LaunchedByWorkspacesID[] = L"PowerToys_LaunchedByWorkspaces"; + } + + inline void StampWorkspacesLaunchedProperty(HWND window) + { + ::SetPropW(window, Properties::LaunchedByWorkspacesID, reinterpret_cast(1)); + } +} diff --git a/src/modules/Workspaces/WorkspacesEditor/App.config b/src/modules/Workspaces/WorkspacesEditor/App.config new file mode 100644 index 0000000000..e31368d227 --- /dev/null +++ b/src/modules/Workspaces/WorkspacesEditor/App.config @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/modules/Workspaces/WorkspacesEditor/App.xaml b/src/modules/Workspaces/WorkspacesEditor/App.xaml new file mode 100644 index 0000000000..567a577389 --- /dev/null +++ b/src/modules/Workspaces/WorkspacesEditor/App.xaml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/Workspaces/WorkspacesEditor/MainPage.xaml.cs b/src/modules/Workspaces/WorkspacesEditor/MainPage.xaml.cs new file mode 100644 index 0000000000..30e9ed70bb --- /dev/null +++ b/src/modules/Workspaces/WorkspacesEditor/MainPage.xaml.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Windows; +using System.Windows.Controls; +using WorkspacesEditor.Models; +using WorkspacesEditor.ViewModels; + +namespace WorkspacesEditor +{ + /// + /// Interaction logic for MainPage.xaml + /// + public partial class MainPage : Page + { + private MainViewModel _mainViewModel; + + public MainPage(MainViewModel mainViewModel) + { + InitializeComponent(); + _mainViewModel = mainViewModel; + this.DataContext = _mainViewModel; + } + + private /*async*/ void NewProjectButton_Click(object sender, RoutedEventArgs e) + { + _mainViewModel.EnterSnapshotMode(false); + } + + private void EditButtonClicked(object sender, RoutedEventArgs e) + { + _mainViewModel.CloseAllPopups(); + Button button = sender as Button; + Project selectedProject = button.DataContext as Project; + _mainViewModel.EditProject(selectedProject); + } + + private void DeleteButtonClicked(object sender, RoutedEventArgs e) + { + e.Handled = true; + Button button = sender as Button; + Project selectedProject = button.DataContext as Project; + _mainViewModel.DeleteProject(selectedProject); + } + + private void MoreButton_Click(object sender, RoutedEventArgs e) + { + e.Handled = true; + Button button = sender as Button; + Project project = button.DataContext as Project; + project.IsPopupVisible = true; + } + + private void LaunchButton_Click(object sender, RoutedEventArgs e) + { + e.Handled = true; + Button button = sender as Button; + Project project = button.DataContext as Project; + _mainViewModel.LaunchProject(project); + } + } +} diff --git a/src/modules/Workspaces/WorkspacesEditor/MainWindow.xaml b/src/modules/Workspaces/WorkspacesEditor/MainWindow.xaml new file mode 100644 index 0000000000..0c5eb55ccc --- /dev/null +++ b/src/modules/Workspaces/WorkspacesEditor/MainWindow.xaml @@ -0,0 +1,29 @@ + + + + + + + diff --git a/src/modules/Workspaces/WorkspacesEditor/MainWindow.xaml.cs b/src/modules/Workspaces/WorkspacesEditor/MainWindow.xaml.cs new file mode 100644 index 0000000000..adaf9c9a52 --- /dev/null +++ b/src/modules/Workspaces/WorkspacesEditor/MainWindow.xaml.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Windows; +using System.Windows.Interop; +using ManagedCommon; +using WorkspacesEditor.Utils; +using WorkspacesEditor.ViewModels; + +namespace WorkspacesEditor +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + public MainViewModel MainViewModel { get; set; } + + private static MainPage _mainPage; + + public MainWindow(MainViewModel mainViewModel) + { + MainViewModel = mainViewModel; + mainViewModel.SetMainWindow(this); + + WindowInteropHelper windowInteropHelper = new WindowInteropHelper(this); + System.Windows.Forms.Screen screen = System.Windows.Forms.Screen.FromHandle(windowInteropHelper.Handle); + double dpi = MonitorHelper.GetScreenDpiFromScreen(screen); + this.Height = screen.WorkingArea.Height / dpi * 0.90; + this.Width = screen.WorkingArea.Width / dpi * 0.75; + this.Top = screen.WorkingArea.Top + (int)(screen.WorkingArea.Height / dpi * 0.05); + this.Left = screen.WorkingArea.Left + (int)(screen.WorkingArea.Width / dpi * 0.125); + + InitializeComponent(); + + _mainPage = new MainPage(mainViewModel); + + ContentFrame.Navigate(_mainPage); + + MaxWidth = SystemParameters.PrimaryScreenWidth; + MaxHeight = SystemParameters.PrimaryScreenHeight; + } + + private void OnClosing(object sender, EventArgs e) + { + App.Current.Shutdown(); + } + + // This is required to fix a WPF rendering bug when using custom chrome + private void OnContentRendered(object sender, EventArgs e) + { + // Get the window handle of the Workspaces Editor window + IntPtr handle = new WindowInteropHelper(this).Handle; + WindowHelpers.BringToForeground(handle); + + InvalidateVisual(); + } + + public void ShowPage(ProjectEditor editPage) + { + ContentFrame.Navigate(editPage); + } + + public void SwitchToMainView() + { + ContentFrame.GoBack(); + } + } +} diff --git a/src/modules/Workspaces/WorkspacesEditor/Models/AppListDataTemplateSelector.cs b/src/modules/Workspaces/WorkspacesEditor/Models/AppListDataTemplateSelector.cs new file mode 100644 index 0000000000..660793c1c2 --- /dev/null +++ b/src/modules/Workspaces/WorkspacesEditor/Models/AppListDataTemplateSelector.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace WorkspacesEditor.Models +{ + public sealed class AppListDataTemplateSelector : System.Windows.Controls.DataTemplateSelector + { + public System.Windows.DataTemplate HeaderTemplate { get; set; } + + public System.Windows.DataTemplate AppTemplate { get; set; } + + public AppListDataTemplateSelector() + { + HeaderTemplate = new System.Windows.DataTemplate(); + AppTemplate = new System.Windows.DataTemplate(); + } + + public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container) + { + if (item is MonitorHeaderRow) + { + return HeaderTemplate; + } + else + { + return AppTemplate; + } + } + } +} diff --git a/src/modules/Workspaces/WorkspacesEditor/Models/Application.cs b/src/modules/Workspaces/WorkspacesEditor/Models/Application.cs new file mode 100644 index 0000000000..92790aef43 --- /dev/null +++ b/src/modules/Workspaces/WorkspacesEditor/Models/Application.cs @@ -0,0 +1,474 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.Json.Serialization; +using System.Text.RegularExpressions; +using System.Windows.Media.Imaging; +using ManagedCommon; +using Windows.Management.Deployment; + +namespace WorkspacesEditor.Models +{ + public class Application : INotifyPropertyChanged, IDisposable + { + private bool _isInitialized; + + public event PropertyChangedEventHandler PropertyChanged; + + public Application() + { + } + + public Application(Application other) + { + AppName = other.AppName; + AppPath = other.AppPath; + AppTitle = other.AppTitle; + PackageFullName = other.PackageFullName; + AppUserModelId = other.AppUserModelId; + CommandLineArguments = other.CommandLineArguments; + IsElevated = other.IsElevated; + CanLaunchElevated = other.CanLaunchElevated; + Minimized = other.Minimized; + Maximized = other.Maximized; + Position = other.Position; + MonitorNumber = other.MonitorNumber; + + Parent = other.Parent; + IsNotFound = other.IsNotFound; + IsHighlighted = other.IsHighlighted; + RepeatIndex = other.RepeatIndex; + PackagedId = other.PackagedId; + PackagedName = other.PackagedName; + PackagedPublisherID = other.PackagedPublisherID; + Aumid = other.Aumid; + IsExpanded = other.IsExpanded; + IsIncluded = other.IsIncluded; + } + + public Project Parent { get; set; } + + public struct WindowPosition + { + public int X { get; set; } + + public int Y { get; set; } + + public int Width { get; set; } + + public int Height { get; set; } + + public static bool operator ==(WindowPosition left, WindowPosition right) + { + return left.X == right.X && left.Y == right.Y && left.Width == right.Width && left.Height == right.Height; + } + + public static bool operator !=(WindowPosition left, WindowPosition right) + { + return left.X != right.X || left.Y != right.Y || left.Width != right.Width || left.Height != right.Height; + } + + public override bool Equals(object obj) + { + if (obj == null || GetType() != obj.GetType()) + { + return false; + } + + WindowPosition pos = (WindowPosition)obj; + return X == pos.X && Y == pos.Y && Width == pos.Width && Height == pos.Height; + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + } + + public string AppName { get; set; } + + public string AppPath { get; set; } + + public string AppTitle { get; set; } + + public string PackageFullName { get; set; } + + public string AppUserModelId { get; set; } + + public string CommandLineArguments { get; set; } + + private bool _isElevated; + + public bool IsElevated + { + get => _isElevated; + set + { + _isElevated = value; + OnPropertyChanged(new PropertyChangedEventArgs(nameof(AppMainParams))); + } + } + + public bool CanLaunchElevated { get; set; } + + internal void SwitchDeletion() + { + IsIncluded = !IsIncluded; + RedrawPreviewImage(); + } + + private void RedrawPreviewImage() + { + if (_isInitialized) + { + Parent.Initialize(App.ThemeManager.GetCurrentTheme()); + } + } + + private bool _minimized; + + public bool Minimized + { + get => _minimized; + set + { + _minimized = value; + OnPropertyChanged(new PropertyChangedEventArgs(nameof(Minimized))); + OnPropertyChanged(new PropertyChangedEventArgs(nameof(EditPositionEnabled))); + RedrawPreviewImage(); + } + } + + private bool _maximized; + + public bool Maximized + { + get => _maximized; + set + { + _maximized = value; + OnPropertyChanged(new PropertyChangedEventArgs(nameof(Maximized))); + OnPropertyChanged(new PropertyChangedEventArgs(nameof(EditPositionEnabled))); + RedrawPreviewImage(); + } + } + + public bool EditPositionEnabled { get => !Minimized && !Maximized; } + + private string _appMainParams; + + public string AppMainParams + { + get + { + _appMainParams = _isElevated ? Properties.Resources.Admin : string.Empty; + if (!string.IsNullOrWhiteSpace(CommandLineArguments)) + { + _appMainParams += (_appMainParams == string.Empty ? string.Empty : " | ") + Properties.Resources.Args + ": " + CommandLineArguments; + } + + OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsAppMainParamVisible))); + return _appMainParams; + } + } + + public bool IsAppMainParamVisible { get => !string.IsNullOrWhiteSpace(_appMainParams); } + + private bool _isNotFound; + + [JsonIgnore] + public bool IsNotFound + { + get + { + return _isNotFound; + } + + set + { + if (_isNotFound != value) + { + _isNotFound = value; + OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsNotFound))); + } + } + } + + [JsonIgnore] + public bool IsHighlighted { get; set; } + + [JsonIgnore] + public int RepeatIndex { get; set; } + + [JsonIgnore] + public string RepeatIndexString + { + get + { + return RepeatIndex <= 1 ? string.Empty : RepeatIndex.ToString(CultureInfo.InvariantCulture); + } + } + + [JsonIgnore] + private Icon _icon = null; + + [JsonIgnore] + public Icon Icon + { + get + { + if (_icon == null) + { + try + { + if (IsPackagedApp) + { + Uri uri = GetAppLogoByPackageFamilyName(); + var bitmap = new Bitmap(uri.LocalPath); + var iconHandle = bitmap.GetHicon(); + _icon = Icon.FromHandle(iconHandle); + } + else + { + _icon = Icon.ExtractAssociatedIcon(AppPath); + } + } + catch (Exception) + { + Logger.LogWarning($"Icon not found on app path: {AppPath}. Using default icon"); + IsNotFound = true; + _icon = new Icon(@"images\DefaultIcon.ico"); + } + } + + return _icon; + } + } + + public Uri GetAppLogoByPackageFamilyName() + { + var pkgManager = new PackageManager(); + var pkg = pkgManager.FindPackagesForUser(string.Empty, PackagedId).FirstOrDefault(); + + if (pkg == null) + { + return null; + } + + return pkg.Logo; + } + + private BitmapImage _iconBitmapImage; + + public BitmapImage IconBitmapImage + { + get + { + if (_iconBitmapImage == null) + { + try + { + Bitmap previewBitmap = new Bitmap(32, 32); + using (Graphics graphics = Graphics.FromImage(previewBitmap)) + { + graphics.SmoothingMode = SmoothingMode.AntiAlias; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + + graphics.DrawIcon(Icon, new Rectangle(0, 0, 32, 32)); + } + + using (var memory = new MemoryStream()) + { + previewBitmap.Save(memory, ImageFormat.Png); + memory.Position = 0; + + var bitmapImage = new BitmapImage(); + bitmapImage.BeginInit(); + bitmapImage.StreamSource = memory; + bitmapImage.CacheOption = BitmapCacheOption.OnLoad; + bitmapImage.EndInit(); + bitmapImage.Freeze(); + + _iconBitmapImage = bitmapImage; + } + } + catch (Exception e) + { + Logger.LogError($"Exception while drawing icon for app with path: {AppPath}. Exception message: {e.Message}"); + } + } + + return _iconBitmapImage; + } + } + + private WindowPosition _position; + + public WindowPosition Position + { + get => _position; + set + { + _position = value; + _scaledPosition = null; + } + } + + private WindowPosition? _scaledPosition; + + public WindowPosition ScaledPosition + { + get + { + if (_scaledPosition == null) + { + double scaleFactor = MonitorSetup.Dpi / 96.0; + _scaledPosition = new WindowPosition() + { + X = (int)(scaleFactor * Position.X), + Y = (int)(scaleFactor * Position.Y), + Height = (int)(scaleFactor * Position.Height), + Width = (int)(scaleFactor * Position.Width), + }; + } + + return _scaledPosition.Value; + } + } + + public int MonitorNumber { get; set; } + + private MonitorSetup _monitorSetup; + + public MonitorSetup MonitorSetup + { + get + { + if (_monitorSetup == null) + { + _monitorSetup = Parent.Monitors.Where(x => x.MonitorNumber == MonitorNumber).FirstOrDefault(); + } + + return _monitorSetup; + } + } + + public void OnPropertyChanged(PropertyChangedEventArgs e) + { + PropertyChanged?.Invoke(this, e); + } + + public void InitializationFinished() + { + _isInitialized = true; + } + + private bool? _isPackagedApp; + + public string PackagedId { get; set; } + + public string PackagedName { get; set; } + + public string PackagedPublisherID { get; set; } + + public string Aumid { get; set; } + + public bool IsPackagedApp + { + get + { + if (_isPackagedApp == null) + { + if (!AppPath.StartsWith("C:\\Program Files\\WindowsApps\\", StringComparison.InvariantCultureIgnoreCase)) + { + _isPackagedApp = false; + } + else + { + string appPath = AppPath.Replace("C:\\Program Files\\WindowsApps\\", string.Empty); + Regex packagedAppPathRegex = new Regex(@"(?[^_]*)_\d+.\d+.\d+.\d+_x64__(?[^\\]*)", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); + Match match = packagedAppPathRegex.Match(appPath); + _isPackagedApp = match.Success; + if (match.Success) + { + PackagedName = match.Groups["APPID"].Value; + PackagedPublisherID = match.Groups["PublisherID"].Value; + PackagedId = $"{PackagedName}_{PackagedPublisherID}"; + Aumid = $"{PackagedId}!App"; + } + } + } + + return _isPackagedApp.Value; + } + } + + private bool _isExpanded; + + public bool IsExpanded + { + get => _isExpanded; + set + { + if (_isExpanded != value) + { + _isExpanded = value; + OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsExpanded))); + } + } + } + + public string DeleteButtonContent { get => _isIncluded ? Properties.Resources.Delete : Properties.Resources.AddBack; } + + private bool _isIncluded = true; + + public bool IsIncluded + { + get => _isIncluded; + set + { + if (_isIncluded != value) + { + _isIncluded = value; + OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsIncluded))); + OnPropertyChanged(new PropertyChangedEventArgs(nameof(DeleteButtonContent))); + if (!_isIncluded) + { + IsExpanded = false; + } + } + } + } + + public void Dispose() + { + GC.SuppressFinalize(this); + } + + internal void CommandLineTextChanged(string newCommandLineValue) + { + CommandLineArguments = newCommandLineValue; + OnPropertyChanged(new PropertyChangedEventArgs(nameof(AppMainParams))); + } + + internal void MaximizedChecked() + { + Minimized = false; + } + + internal void MinimizedChecked() + { + Maximized = false; + } + } +} diff --git a/src/modules/Workspaces/WorkspacesEditor/Models/Monitor.cs b/src/modules/Workspaces/WorkspacesEditor/Models/Monitor.cs new file mode 100644 index 0000000000..2a911eb1fe --- /dev/null +++ b/src/modules/Workspaces/WorkspacesEditor/Models/Monitor.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Windows; + +namespace WorkspacesEditor.Models +{ + public class Monitor + { + public string MonitorName { get; private set; } + + public string MonitorInstanceId { get; private set; } + + public int MonitorNumber { get; private set; } + + public int Dpi { get; private set; } + + public Rect MonitorDpiUnawareBounds { get; private set; } + + public Rect MonitorDpiAwareBounds { get; private set; } + + public Monitor(string monitorName, string monitorInstanceId, int number, int dpi, Rect dpiAwareBounds, Rect dpiUnawareBounds) + { + MonitorName = monitorName; + MonitorInstanceId = monitorInstanceId; + MonitorNumber = number; + Dpi = dpi; + MonitorDpiAwareBounds = dpiAwareBounds; + MonitorDpiUnawareBounds = dpiUnawareBounds; + } + } +} diff --git a/src/modules/Workspaces/WorkspacesEditor/Models/MonitorHeaderRow.cs b/src/modules/Workspaces/WorkspacesEditor/Models/MonitorHeaderRow.cs new file mode 100644 index 0000000000..5a29ea146f --- /dev/null +++ b/src/modules/Workspaces/WorkspacesEditor/Models/MonitorHeaderRow.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace WorkspacesEditor.Models +{ + internal sealed class MonitorHeaderRow + { + public string MonitorName { get; set; } + + public string SelectString { get; set; } + } +} diff --git a/src/modules/Workspaces/WorkspacesEditor/Models/MonitorSetup.cs b/src/modules/Workspaces/WorkspacesEditor/Models/MonitorSetup.cs new file mode 100644 index 0000000000..e44365a988 --- /dev/null +++ b/src/modules/Workspaces/WorkspacesEditor/Models/MonitorSetup.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Windows; + +namespace WorkspacesEditor.Models +{ + public class MonitorSetup : Monitor, INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + public void OnPropertyChanged(PropertyChangedEventArgs e) + { + PropertyChanged?.Invoke(this, e); + } + + public string MonitorInfo { get => MonitorName; } + + public string MonitorInfoWithResolution { get => $"{MonitorName} {MonitorDpiAwareBounds.Width}x{MonitorDpiAwareBounds.Height}"; } + + public MonitorSetup(string monitorName, string monitorInstanceId, int number, int dpi, Rect dpiAwareBounds, Rect dpiUnawareBounds) + : base(monitorName, monitorInstanceId, number, dpi, dpiAwareBounds, dpiUnawareBounds) + { + } + + public MonitorSetup(MonitorSetup other) + : base(other.MonitorName, other.MonitorInstanceId, other.MonitorNumber, other.Dpi, other.MonitorDpiAwareBounds, other.MonitorDpiUnawareBounds) + { + } + } +} diff --git a/src/modules/Workspaces/WorkspacesEditor/Models/Project.cs b/src/modules/Workspaces/WorkspacesEditor/Models/Project.cs new file mode 100644 index 0000000000..b0065dd2ee --- /dev/null +++ b/src/modules/Workspaces/WorkspacesEditor/Models/Project.cs @@ -0,0 +1,376 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Globalization; +using System.Linq; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; +using ManagedCommon; +using WorkspacesEditor.Data; +using WorkspacesEditor.Utils; + +namespace WorkspacesEditor.Models +{ + public class Project : INotifyPropertyChanged + { + [JsonIgnore] + public string EditorWindowTitle { get; set; } + + public string Id { get; private set; } + + private string _name; + + public string Name + { + get + { + return _name; + } + + set + { + _name = value; + OnPropertyChanged(new PropertyChangedEventArgs(nameof(Name))); + OnPropertyChanged(new PropertyChangedEventArgs(nameof(CanBeSaved))); + } + } + + public long CreationTime { get; } // in seconds + + public long LastLaunchedTime { get; } // in seconds + + public bool IsShortcutNeeded { get; set; } + + public bool MoveExistingWindows { get; set; } + + public string LastLaunched + { + get + { + string lastLaunched = WorkspacesEditor.Properties.Resources.LastLaunched + ": "; + if (LastLaunchedTime == 0) + { + return lastLaunched + WorkspacesEditor.Properties.Resources.Never; + } + + const int SECOND = 1; + const int MINUTE = 60 * SECOND; + const int HOUR = 60 * MINUTE; + const int DAY = 24 * HOUR; + const int MONTH = 30 * DAY; + + DateTime lastLaunchDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(LastLaunchedTime); + + var now = DateTime.UtcNow.Ticks; + var ts = DateTime.UtcNow - lastLaunchDateTime; + double delta = Math.Abs(ts.TotalSeconds); + + if (delta < 1 * MINUTE) + { + return lastLaunched + WorkspacesEditor.Properties.Resources.Recently; + } + + if (delta < 2 * MINUTE) + { + return lastLaunched + WorkspacesEditor.Properties.Resources.OneMinuteAgo; + } + + if (delta < 45 * MINUTE) + { + return lastLaunched + ts.Minutes + " " + WorkspacesEditor.Properties.Resources.MinutesAgo; + } + + if (delta < 90 * MINUTE) + { + return lastLaunched + WorkspacesEditor.Properties.Resources.OneHourAgo; + } + + if (delta < 24 * HOUR) + { + return lastLaunched + ts.Hours + " " + WorkspacesEditor.Properties.Resources.HoursAgo; + } + + if (delta < 48 * HOUR) + { + return lastLaunched + WorkspacesEditor.Properties.Resources.Yesterday; + } + + if (delta < 30 * DAY) + { + return lastLaunched + ts.Days + " " + WorkspacesEditor.Properties.Resources.DaysAgo; + } + + if (delta < 12 * MONTH) + { + int months = Convert.ToInt32(Math.Floor((double)ts.Days / 30)); + return lastLaunched + (months <= 1 ? WorkspacesEditor.Properties.Resources.OneMonthAgo : months + " " + WorkspacesEditor.Properties.Resources.MonthsAgo); + } + else + { + int years = Convert.ToInt32(Math.Floor((double)ts.Days / 365)); + return lastLaunched + (years <= 1 ? WorkspacesEditor.Properties.Resources.OneYearAgo : years + " " + WorkspacesEditor.Properties.Resources.YearsAgo); + } + } + } + + public bool CanBeSaved + { + get => Name.Length > 0 && Applications.Count > 0; + } + + private bool _isRevertEnabled; + + public bool IsRevertEnabled + { + get => _isRevertEnabled; + set + { + if (_isRevertEnabled != value) + { + _isRevertEnabled = value; + OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsRevertEnabled))); + } + } + } + + private bool _isPopupVisible; + + [JsonIgnore] + public bool IsPopupVisible + { + get + { + return _isPopupVisible; + } + + set + { + _isPopupVisible = value; + OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsPopupVisible))); + } + } + + public List Applications { get; set; } + + public List ApplicationsListed + { + get + { + List applicationsListed = new List(); + ILookup apps = Applications.Where(x => !x.Minimized).ToLookup(x => x.MonitorSetup); + foreach (var appItem in apps.OrderBy(x => x.Key.MonitorDpiUnawareBounds.Left).ThenBy(x => x.Key.MonitorDpiUnawareBounds.Top)) + { + MonitorHeaderRow headerRow = new MonitorHeaderRow { MonitorName = "Screen " + appItem.Key.MonitorNumber, SelectString = Properties.Resources.SelectAllAppsOnMonitor + " " + appItem.Key.MonitorInfo }; + applicationsListed.Add(headerRow); + foreach (Application app in appItem) + { + applicationsListed.Add(app); + } + } + + var minimizedApps = Applications.Where(x => x.Minimized); + if (minimizedApps.Any()) + { + MonitorHeaderRow headerRow = new MonitorHeaderRow { MonitorName = Properties.Resources.Minimized_Apps, SelectString = Properties.Resources.SelectAllMinimizedApps }; + applicationsListed.Add(headerRow); + foreach (Application app in minimizedApps) + { + applicationsListed.Add(app); + } + } + + return applicationsListed; + } + } + + [JsonIgnore] + public string AppsCountString + { + get + { + int count = Applications.Count; + return count.ToString(CultureInfo.InvariantCulture) + " " + (count == 1 ? Properties.Resources.App : Properties.Resources.Apps); + } + } + + public List Monitors { get; } + + public bool IsPositionChangedManually { get; set; } // telemetry + + private BitmapImage _previewIcons; + private BitmapImage _previewImage; + private double _previewImageWidth; + + public Project(Project selectedProject) + { + Id = selectedProject.Id; + Name = selectedProject.Name; + PreviewIcons = selectedProject.PreviewIcons; + PreviewImage = selectedProject.PreviewImage; + IsShortcutNeeded = selectedProject.IsShortcutNeeded; + MoveExistingWindows = selectedProject.MoveExistingWindows; + + int screenIndex = 1; + + Monitors = new List(); + foreach (var item in selectedProject.Monitors.OrderBy(x => x.MonitorDpiAwareBounds.Left).ThenBy(x => x.MonitorDpiAwareBounds.Top)) + { + Monitors.Add(item); + screenIndex++; + } + + Applications = new List(); + foreach (var item in selectedProject.Applications) + { + Application newApp = new Application(item); + newApp.Parent = this; + newApp.InitializationFinished(); + Applications.Add(newApp); + } + } + + public Project(ProjectData.ProjectWrapper project) + { + Id = project.Id; + Name = project.Name; + CreationTime = project.CreationTime; + LastLaunchedTime = project.LastLaunchedTime; + IsShortcutNeeded = project.IsShortcutNeeded; + MoveExistingWindows = project.MoveExistingWindows; + Monitors = new List() { }; + Applications = new List { }; + + foreach (var app in project.Applications) + { + Models.Application newApp = new Models.Application() + { + AppName = app.Application, + AppPath = app.ApplicationPath, + AppTitle = app.Title, + PackageFullName = app.PackageFullName, + AppUserModelId = app.AppUserModelId, + Parent = this, + CommandLineArguments = app.CommandLineArguments, + IsElevated = app.IsElevated, + CanLaunchElevated = app.CanLaunchElevated, + Maximized = app.Maximized, + Minimized = app.Minimized, + IsNotFound = false, + Position = new Models.Application.WindowPosition() + { + Height = app.Position.Height, + Width = app.Position.Width, + X = app.Position.X, + Y = app.Position.Y, + }, + MonitorNumber = app.Monitor, + }; + newApp.InitializationFinished(); + Applications.Add(newApp); + } + + foreach (var monitor in project.MonitorConfiguration) + { + System.Windows.Rect dpiAware = new System.Windows.Rect(monitor.MonitorRectDpiAware.Left, monitor.MonitorRectDpiAware.Top, monitor.MonitorRectDpiAware.Width, monitor.MonitorRectDpiAware.Height); + System.Windows.Rect dpiUnaware = new System.Windows.Rect(monitor.MonitorRectDpiUnaware.Left, monitor.MonitorRectDpiUnaware.Top, monitor.MonitorRectDpiUnaware.Width, monitor.MonitorRectDpiUnaware.Height); + Monitors.Add(new MonitorSetup(monitor.Id, monitor.InstanceId, monitor.MonitorNumber, monitor.Dpi, dpiAware, dpiUnaware)); + } + } + + public BitmapImage PreviewIcons + { + get + { + return _previewIcons; + } + + set + { + _previewIcons = value; + OnPropertyChanged(new PropertyChangedEventArgs(nameof(PreviewIcons))); + } + } + + public BitmapImage PreviewImage + { + get + { + return _previewImage; + } + + set + { + _previewImage = value; + OnPropertyChanged(new PropertyChangedEventArgs(nameof(PreviewImage))); + } + } + + public double PreviewImageWidth + { + get + { + return _previewImageWidth; + } + + set + { + _previewImageWidth = value; + OnPropertyChanged(new PropertyChangedEventArgs(nameof(PreviewImageWidth))); + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + public void OnPropertyChanged(PropertyChangedEventArgs e) + { + PropertyChanged?.Invoke(this, e); + } + + public async void Initialize(Theme currentTheme) + { + PreviewIcons = await Task.Run(() => DrawHelper.DrawPreviewIcons(this)); + Rectangle commonBounds = GetCommonBounds(); + PreviewImage = await Task.Run(() => DrawHelper.DrawPreview(this, commonBounds, currentTheme)); + PreviewImageWidth = commonBounds.Width / (commonBounds.Height * 1.2 / 200); + } + + private Rectangle GetCommonBounds() + { + double minX = Monitors.First().MonitorDpiAwareBounds.Left; + double minY = Monitors.First().MonitorDpiAwareBounds.Top; + double maxX = Monitors.First().MonitorDpiAwareBounds.Right; + double maxY = Monitors.First().MonitorDpiAwareBounds.Bottom; + for (int monitorIndex = 1; monitorIndex < Monitors.Count; monitorIndex++) + { + Monitor monitor = Monitors[monitorIndex]; + minX = Math.Min(minX, monitor.MonitorDpiAwareBounds.Left); + minY = Math.Min(minY, monitor.MonitorDpiAwareBounds.Top); + maxX = Math.Max(maxX, monitor.MonitorDpiAwareBounds.Right); + maxY = Math.Max(maxY, monitor.MonitorDpiAwareBounds.Bottom); + } + + return new Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY)); + } + + public void UpdateAfterLaunchAndEdit(Project other) + { + Id = other.Id; + Name = other.Name; + IsRevertEnabled = true; + } + + internal void CloseExpanders() + { + foreach (Application app in Applications) + { + app.IsExpanded = false; + } + } + } +} diff --git a/src/modules/Workspaces/WorkspacesEditor/OverlayWindow.xaml b/src/modules/Workspaces/WorkspacesEditor/OverlayWindow.xaml new file mode 100644 index 0000000000..c10fb6233f --- /dev/null +++ b/src/modules/Workspaces/WorkspacesEditor/OverlayWindow.xaml @@ -0,0 +1,16 @@ + + + diff --git a/src/modules/Workspaces/WorkspacesEditor/OverlayWindow.xaml.cs b/src/modules/Workspaces/WorkspacesEditor/OverlayWindow.xaml.cs new file mode 100644 index 0000000000..54e892d9bd --- /dev/null +++ b/src/modules/Workspaces/WorkspacesEditor/OverlayWindow.xaml.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Windows; + +namespace WorkspacesEditor +{ + /// + /// Interaction logic for OverlayWindow.xaml + /// + public partial class OverlayWindow : Window + { + public OverlayWindow() + { + InitializeComponent(); + } + } +} diff --git a/src/modules/Workspaces/WorkspacesEditor/Properties/Resources.Designer.cs b/src/modules/Workspaces/WorkspacesEditor/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..ce81b9785d --- /dev/null +++ b/src/modules/Workspaces/WorkspacesEditor/Properties/Resources.Designer.cs @@ -0,0 +1,693 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace WorkspacesEditor.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WorkspacesEditor.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Add Back. + /// + public static string AddBack { + get { + return ResourceManager.GetString("AddBack", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Admin. + /// + public static string Admin { + get { + return ResourceManager.GetString("Admin", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Launch new app instances. + /// + public static string AlwaysLaunch { + get { + return ResourceManager.GetString("AlwaysLaunch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to app. + /// + public static string App { + get { + return ResourceManager.GetString("App", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to App name. + /// + public static string App_name { + get { + return ResourceManager.GetString("App_name", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to apps. + /// + public static string Apps { + get { + return ResourceManager.GetString("Apps", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Are you sure?. + /// + public static string Are_You_Sure { + get { + return ResourceManager.GetString("Are_You_Sure", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Are you sure you want to delete this Workspace?. + /// + public static string Are_You_Sure_Description { + get { + return ResourceManager.GetString("Are_You_Sure_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Args. + /// + public static string Args { + get { + return ResourceManager.GetString("Args", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cancel. + /// + public static string Cancel { + get { + return ResourceManager.GetString("Cancel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to CLI arguments. + /// + public static string CliArguments { + get { + return ResourceManager.GetString("CliArguments", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Created. + /// + public static string Created { + get { + return ResourceManager.GetString("Created", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Create Desktop Shortcut. + /// + public static string CreateShortcut { + get { + return ResourceManager.GetString("CreateShortcut", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Create Workspace. + /// + public static string CreateWorkspace { + get { + return ResourceManager.GetString("CreateWorkspace", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to days ago. + /// + public static string DaysAgo { + get { + return ResourceManager.GetString("DaysAgo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Workspace. + /// + public static string DefaultWorkspaceNamePrefix { + get { + return ResourceManager.GetString("DefaultWorkspaceNamePrefix", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Remove. + /// + public static string Delete { + get { + return ResourceManager.GetString("Delete", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Delete Workspace dialog.. + /// + public static string Delete_Workspace_Dialog_Announce { + get { + return ResourceManager.GetString("Delete_Workspace_Dialog_Announce", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Remove Selected Apps. + /// + public static string DeleteSelected { + get { + return ResourceManager.GetString("DeleteSelected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit. + /// + public static string Edit { + get { + return ResourceManager.GetString("Edit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to opened. + /// + public static string Edit_Project_Open_Announce { + get { + return ResourceManager.GetString("Edit_Project_Open_Announce", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit Workspace. + /// + public static string EditWorkspace { + get { + return ResourceManager.GetString("EditWorkspace", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error parsing Workspaces data.. + /// + public static string Error_Parsing_Message { + get { + return ResourceManager.GetString("Error_Parsing_Message", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Height. + /// + public static string Height { + get { + return ResourceManager.GetString("Height", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to hours ago. + /// + public static string HoursAgo { + get { + return ResourceManager.GetString("HoursAgo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Last launched. + /// + public static string LastLaunched { + get { + return ResourceManager.GetString("LastLaunched", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Launch. + /// + public static string Launch { + get { + return ResourceManager.GetString("Launch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Launch args. + /// + public static string Launch_args { + get { + return ResourceManager.GetString("Launch_args", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Launch as Admin. + /// + public static string LaunchAsAdmin { + get { + return ResourceManager.GetString("LaunchAsAdmin", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Launch & Edit. + /// + public static string LaunchEdit { + get { + return ResourceManager.GetString("LaunchEdit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Left. + /// + public static string Left { + get { + return ResourceManager.GetString("Left", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Workspaces Editor. + /// + public static string MainTitle { + get { + return ResourceManager.GetString("MainTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Maximized. + /// + public static string Maximized { + get { + return ResourceManager.GetString("Maximized", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Minimized. + /// + public static string Minimized { + get { + return ResourceManager.GetString("Minimized", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Minimized Apps. + /// + public static string Minimized_Apps { + get { + return ResourceManager.GetString("Minimized_Apps", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to minutes ago. + /// + public static string MinutesAgo { + get { + return ResourceManager.GetString("MinutesAgo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to months ago. + /// + public static string MonthsAgo { + get { + return ResourceManager.GetString("MonthsAgo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Move apps if present. + /// + public static string MoveIfExist { + get { + return ResourceManager.GetString("MoveIfExist", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Name. + /// + public static string Name { + get { + return ResourceManager.GetString("Name", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to never. + /// + public static string Never { + get { + return ResourceManager.GetString("Never", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to New Workspace. + /// + public static string New_Workspace { + get { + return ResourceManager.GetString("New_Workspace", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to There are no saved Workspaces.. + /// + public static string No_Workspaces_Message { + get { + return ResourceManager.GetString("No_Workspaces_Message", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The application cannot be found. + /// + public static string NotFoundTooltip { + get { + return ResourceManager.GetString("NotFoundTooltip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No Workspaces match the current search.. + /// + public static string NoWorkspacesMatch { + get { + return ResourceManager.GetString("NoWorkspacesMatch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to an hour ago. + /// + public static string OneHourAgo { + get { + return ResourceManager.GetString("OneHourAgo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to a minute ago. + /// + public static string OneMinuteAgo { + get { + return ResourceManager.GetString("OneMinuteAgo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to one month ago. + /// + public static string OneMonthAgo { + get { + return ResourceManager.GetString("OneMonthAgo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to one second ago. + /// + public static string OneSecondAgo { + get { + return ResourceManager.GetString("OneSecondAgo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to one year ago. + /// + public static string OneYearAgo { + get { + return ResourceManager.GetString("OneYearAgo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Pin Workspaces to Taskbar. + /// + public static string PinToTaskbar { + get { + return ResourceManager.GetString("PinToTaskbar", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to recently. + /// + public static string Recently { + get { + return ResourceManager.GetString("Recently", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Revert. + /// + public static string Revert { + get { + return ResourceManager.GetString("Revert", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Save Workspace. + /// + public static string Save_Workspace { + get { + return ResourceManager.GetString("Save_Workspace", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Search. + /// + public static string Search { + get { + return ResourceManager.GetString("Search", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Search for Workspaces or apps. + /// + public static string SearchExplanation { + get { + return ResourceManager.GetString("SearchExplanation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to seconds ago. + /// + public static string SecondsAgo { + get { + return ResourceManager.GetString("SecondsAgo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Select All Apps on. + /// + public static string SelectAllAppsOnMonitor { + get { + return ResourceManager.GetString("SelectAllAppsOnMonitor", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Select All Minimized Apps. + /// + public static string SelectAllMinimizedApps { + get { + return ResourceManager.GetString("SelectAllMinimizedApps", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Select All Apps in Workspace. + /// + public static string SelectedAllInWorkspace { + get { + return ResourceManager.GetString("SelectedAllInWorkspace", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit your layout and click "Capture" when finished.. + /// + public static string SnapshotDescription { + get { + return ResourceManager.GetString("SnapshotDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Snapshot Creator. + /// + public static string SnapshotWindowTitle { + get { + return ResourceManager.GetString("SnapshotWindowTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sort by. + /// + public static string SortBy { + get { + return ResourceManager.GetString("SortBy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Capture. + /// + public static string Take_Snapshot { + get { + return ResourceManager.GetString("Take_Snapshot", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Top. + /// + public static string Top { + get { + return ResourceManager.GetString("Top", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Width. + /// + public static string Width { + get { + return ResourceManager.GetString("Width", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Workspace name. + /// + public static string WorkspaceName { + get { + return ResourceManager.GetString("WorkspaceName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Workspaces. + /// + public static string Workspaces { + get { + return ResourceManager.GetString("Workspaces", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Write arguments here. + /// + public static string WriteArgs { + get { + return ResourceManager.GetString("WriteArgs", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to years ago. + /// + public static string YearsAgo { + get { + return ResourceManager.GetString("YearsAgo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to yesterday. + /// + public static string Yesterday { + get { + return ResourceManager.GetString("Yesterday", resourceCulture); + } + } + } +} diff --git a/src/modules/Workspaces/WorkspacesEditor/Properties/Resources.resx b/src/modules/Workspaces/WorkspacesEditor/Properties/Resources.resx new file mode 100644 index 0000000000..b768d6682a --- /dev/null +++ b/src/modules/Workspaces/WorkspacesEditor/Properties/Resources.resx @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Add back + + + Admin + + + Launch new app instances + + + app + + + apps + + + App name + + + Are you sure? + + + Are you sure you want to delete this Workspace? + + + Args + Arguments + + + Cancel + + + CLI arguments + + + Created + + + Create Workspace + + + Create desktop shortcut + + + days ago + + + Workspace + + + Remove + + + Remove selected apps + + + Delete Workspace dialog. + + + Edit + + + Edit Workspace + + + opened + + + Error parsing Workspaces data. + + + Height + + + hours ago + + + Last launched + + + Launch + + + Launch as Admin + + + Launch & edit + + + Launch args + + + Left + the left x coordinate + + + Workspaces Editor + + + Maximized + + + Minimized + + + Minimized apps + + + minutes ago + + + months ago + + + Move apps if present + + + Name + + + never + + + New Workspace + + + No Workspaces match the current search. + + + The application cannot be found + + + There are no saved Workspaces. + + + an hour ago + + + a minute ago + + + one month ago + + + one second ago + + + one year ago + + + Pin Workspaces to taskbar + + + Workspace name + + + Workspaces + + + recently + + + Revert + + + Save Workspace + + + Search + + + Search for Workspaces or apps + + + seconds ago + + + Select all apps on + + + Select all minimized apps + + + Select all apps in Workspace + + + Edit your layout and click "Capture" when finished. + + + Snapshot Creator + + + Sort by + + + Capture + + + Top + the top y coordinate + + + Width + + + Write arguments here + + + years ago + + + yesterday + + \ No newline at end of file diff --git a/src/modules/Workspaces/WorkspacesEditor/Properties/Settings.Designer.cs b/src/modules/Workspaces/WorkspacesEditor/Properties/Settings.Designer.cs new file mode 100644 index 0000000000..f598f04bae --- /dev/null +++ b/src/modules/Workspaces/WorkspacesEditor/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace WorkspacesEditor.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.1.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/src/modules/Workspaces/WorkspacesEditor/Properties/Settings.settings b/src/modules/Workspaces/WorkspacesEditor/Properties/Settings.settings new file mode 100644 index 0000000000..033d7a5e9e --- /dev/null +++ b/src/modules/Workspaces/WorkspacesEditor/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/modules/Workspaces/WorkspacesEditor/SnapshotWindow.xaml b/src/modules/Workspaces/WorkspacesEditor/SnapshotWindow.xaml new file mode 100644 index 0000000000..5dcceeb88b --- /dev/null +++ b/src/modules/Workspaces/WorkspacesEditor/SnapshotWindow.xaml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + +