diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index bc5ba04289da..c5a58b670d3b 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -1145,6 +1145,7 @@ OUTOFCONTEXT Outptr outputtype outsettings +outsourced OVERLAPPEDWINDOW Oversampling OVERWRITEPROMPT @@ -1280,6 +1281,7 @@ projectname PROPERTYKEY Propset PROPVARIANT +Prt PRTL prvpane psapi @@ -1918,6 +1920,7 @@ WNDCLASSW WNDPROC wnode wom +workerw WORKSPACESEDITOR WORKSPACESLAUNCHER WORKSPACESSNAPSHOTTOOL diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json index 6f8f817492cf..74b5cea14565 100644 --- a/.pipelines/ESRPSigning_core.json +++ b/.pipelines/ESRPSigning_core.json @@ -216,8 +216,12 @@ "WinUI3Apps\\PowerToys.RegistryPreview.dll", "WinUI3Apps\\PowerToys.RegistryPreview.exe", - "PowerToys.ShortcutGuide.exe", - "PowerToys.ShortcutGuideModuleInterface.dll", + "WinUI3Apps\\PowerToys.ShortcutGuide.exe", + "WinUI3Apps\\PowerToys.ShortcutGuide.dll", + "WinUI3Apps\\PowerToys.ShortcutGuideModuleInterface.dll", + "WinUI3Apps\\PowerToys.ShortcutGuide.IndexYmlGenerator.dll", + "WinUI3Apps\\PowerToys.ShortcutGuide.IndexYmlGenerator.exe", + "WinUI3Apps\\ShortcutGuide.CPPProject.dll", "PowerToys.ZoomIt.exe", "PowerToys.ZoomItModuleInterface.dll", diff --git a/Directory.Packages.props b/Directory.Packages.props index 71bbda504296..61cb71ec8e3e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -107,6 +107,7 @@ + diff --git a/NOTICE.md b/NOTICE.md index d75fe9952271..626db2b11b6e 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -1581,3 +1581,4 @@ SOFTWARE. - WinUIEx 2.2.0 - WPF-UI 3.0.5 - WyHash 1.0.5 +- YamlDotNet 16.3.0 diff --git a/PowerToys.sln b/PowerToys.sln index 00986aae2904..507566045697 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -39,14 +39,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fancyzones", "fancyzones", EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FancyZonesLib", "src\modules\fancyzones\FancyZonesLib\FancyZonesLib.vcxproj", "{F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FancyZones.UnitTests", "src\modules\fancyzones\FancyZonesTests\UnitTests\UnitTests.vcxproj", "{9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests", "src\modules\fancyzones\FancyZonesTests\UnitTests\UnitTests.vcxproj", "{9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}" ProjectSection(ProjectDependencies) = postProject {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} = {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{1AFB6476-670D-4E80-A464-657E01DFF482}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common.Lib.UnitTests", "src\common\UnitTests-CommonLib\UnitTests-CommonLib.vcxproj", "{1A066C63-64B3-45F8-92FE-664E1CCE8077}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests-CommonLib", "src\common\UnitTests-CommonLib\UnitTests-CommonLib.vcxproj", "{1A066C63-64B3-45F8-92FE-664E1CCE8077}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FancyZonesEditor", "src\modules\fancyzones\editor\FancyZonesEditor\FancyZonesEditor.csproj", "{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}" EndProject @@ -61,7 +61,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameLib", "src\modul EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameTest", "src\modules\powerrename\testapp\PowerRenameTest.vcxproj", "{A3935CF4-46C5-4A88-84D3-6B12E16E6BA2}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRename.UnitTests", "src\modules\powerrename\unittests\PowerRenameLibUnitTests.vcxproj", "{2151F984-E006-4A9F-92EF-C6DDE3DC8413}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameLibUnitTests", "src\modules\powerrename\unittests\PowerRenameLibUnitTests.vcxproj", "{2151F984-E006-4A9F-92EF-C6DDE3DC8413}" ProjectSection(ProjectDependencies) = postProject {51920F1F-C28C-4ADF-8660-4238766796C2} = {51920F1F-C28C-4ADF-8660-4238766796C2} {B25AC7A5-FB9F-4789-B392-D5C85E948670} = {B25AC7A5-FB9F-4789-B392-D5C85E948670} @@ -79,12 +79,12 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ImageResizerExt", "src\modu EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageResizer.UnitTests", "src\modules\imageresizer\tests\ImageResizer.UnitTests.csproj", "{E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToys.ActionRunner", "src\ActionRunner\ActionRunner.vcxproj", "{D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ActionRunner", "src\ActionRunner\ActionRunner.vcxproj", "{D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}" ProjectSection(ProjectDependencies) = postProject {17DA04DF-E393-4397-9CF0-84DABE11032E} = {17DA04DF-E393-4397-9CF0-84DABE11032E} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ApplicationUpdate", "src\common\updating\updating.vcxproj", "{17DA04DF-E393-4397-9CF0-84DABE11032E}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "updating", "src\common\updating\updating.vcxproj", "{17DA04DF-E393-4397-9CF0-84DABE11032E}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "keyboardmanager", "keyboardmanager", "{38BDB927-829B-4C65-9CD9-93FB05D66D65}" EndProject @@ -213,7 +213,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Settings.UI.UnitTests", "sr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.Calculator.UnitTest", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.Calculator.UnitTest\Microsoft.PowerToys.Run.Plugin.Calculator.UnitTest.csproj", "{632BBE62-5421-49EA-835A-7FFA4F499BD6}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "spdlog", "src\logging\logging.vcxproj", "{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "logging", "src\logging\logging.vcxproj", "{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.System", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.System\Microsoft.PowerToys.Run.Plugin.System.csproj", "{FD8EB419-FF9C-4D88-BB6F-BF6CED37747B}" EndProject @@ -232,7 +232,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common.Interop.UnitTests", EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "notifications", "notifications", "{D92131D6-7610-4D60-A7DB-1C169783F83B}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Notifications", "src\common\notifications\notifications.vcxproj", "{1D5BE09D-78C0-4FD7-AF00-AE7C1AF7C525}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "notifications", "src\common\notifications\notifications.vcxproj", "{1D5BE09D-78C0-4FD7-AF00-AE7C1AF7C525}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BackgroundActivatorDLL", "src\common\notifications\BackgroundActivatorDLL\BackgroundActivatorDLL.vcxproj", "{031AC72E-FA28-4AB7-B690-6F7B9C28AA73}" ProjectSection(ProjectDependencies) = postProject @@ -241,7 +241,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BackgroundActivatorDLL", "s EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BackgroundActivator", "src\common\notifications\BackgroundActivator\BackgroundActivator.vcxproj", "{0B593A6C-4143-4337-860E-DB5710FB87DB}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Version", "src\common\version\version.vcxproj", "{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "version", "src\common\version\version.vcxproj", "{CC6E41AC-8174-4E8A-8D22-85DD7F4851DF}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "interop", "interop", "{5A7818A8-109C-4E1C-850D-1A654E234B0E}" EndProject @@ -310,13 +310,13 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManagerEngine", "sr EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManagerEngineLibrary", "src\modules\keyboardmanager\KeyboardManagerEngineLibrary\KeyboardManagerEngineLibrary.vcxproj", "{E496B7FC-1E99-4BAB-849B-0E8367040B02}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManager.Engine.UnitTests", "src\modules\keyboardmanager\KeyboardManagerEngineTest\KeyboardManagerEngineTest.vcxproj", "{7F4B3A60-BC27-45A7-8000-68B0B6EA7466}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManagerEngineTest", "src\modules\keyboardmanager\KeyboardManagerEngineTest\KeyboardManagerEngineTest.vcxproj", "{7F4B3A60-BC27-45A7-8000-68B0B6EA7466}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManagerEditor", "src\modules\keyboardmanager\KeyboardManagerEditor\KeyboardManagerEditor.vcxproj", "{8DF78B53-200E-451F-9328-01EB907193AE}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManagerEditorLibrary", "src\modules\keyboardmanager\KeyboardManagerEditorLibrary\KeyboardManagerEditorLibrary.vcxproj", "{23D2070D-E4AD-4ADD-85A7-083D9C76AD49}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManager.Editor.UnitTests", "src\modules\keyboardmanager\KeyboardManagerEditorTest\KeyboardManagerEditorTest.vcxproj", "{62173D9A-6724-4C00-A1C8-FB646480A9EC}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManagerEditorTest", "src\modules\keyboardmanager\KeyboardManagerEditorTest\KeyboardManagerEditorTest.vcxproj", "{62173D9A-6724-4C00-A1C8-FB646480A9EC}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "awake", "awake", "{127F38E0-40AA-4594-B955-5616BF206882}" EndProject @@ -328,12 +328,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plu EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plugin.UnitConverter.UnitTest", "src\modules\launcher\Plugins\Community.PowerToys.Run.Plugin.UnitConverter.UnitTest\Community.PowerToys.Run.Plugin.UnitConverter.UnitTest.csproj", "{3E424AD2-19E5-4AE6-B833-F53963EB5FC1}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shortcutguide", "shortcutguide", "{106CBECA-0701-4FC3-838C-9DF816A19AE2}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ShortcutGuide", "ShortcutGuide", "{106CBECA-0701-4FC3-838C-9DF816A19AE2}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ShortcutGuideModuleInterface", "src\modules\ShortcutGuide\ShortcutGuideModuleInterface\ShortcutGuideModuleInterface.vcxproj", "{2D604C07-51FC-46BB-9EB7-75AECC7F5E81}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ShortcutGuide", "src\modules\ShortcutGuide\ShortcutGuide\ShortcutGuide.vcxproj", "{2EDB3EB4-FA92-4BFF-B2D8-566584837231}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FancyZonesModuleInterface", "src\modules\fancyzones\FancyZonesModuleInterface\FancyZonesModuleInterface.vcxproj", "{48804216-2A0E-4168-A6D8-9CD068D14227}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FancyZones", "src\modules\fancyzones\FancyZones\FancyZones.vcxproj", "{FF1D7936-842A-4BBB-8BEA-E9FE796DE700}" @@ -445,7 +443,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}") = "PowerToys.FileLocksmithLib.Interop", "src\modules\FileLocksmith\FileLocksmithLibInterop\FileLocksmithLibInterop.vcxproj", "{C604B37E-9D0E-4484-8778-E8B31B0E1B3A}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FileLocksmithLibInterop", "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 @@ -453,7 +451,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GPOWrapperProjection", "src EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Peek", "Peek", "{17B4FA70-001E-4D33-BBBB-0D142DBC2E20}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Peek", "src\modules\peek\peek\peek.vcxproj", "{A1425B53-3D61-4679-8623-E64A0D3D0A48}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "peek", "src\modules\peek\peek\peek.vcxproj", "{A1425B53-3D61-4679-8623-E64A0D3D0A48}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Peek.UI", "src\modules\peek\Peek.UI\Peek.UI.csproj", "{9D7A6DE0-7D27-424D-ABAE-41B2161F9A03}" EndProject @@ -568,7 +566,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerToys.Settings.DSC.Sche {020A7474-3601-4160-A159-D7B70B77B15F} = {020A7474-3601-4160-A159-D7B70B77B15F} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NewPlus.ShellExtension", "src\modules\NewPlus\NewShellExtensionContextMenu\NewShellExtensionContextMenu.vcxproj", "{8ACB33D9-C95B-47D4-8363-9731EE0930A0}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NewShellExtensionContextMenu", "src\modules\NewPlus\NewShellExtensionContextMenu\NewShellExtensionContextMenu.vcxproj", "{8ACB33D9-C95B-47D4-8363-9731EE0930A0}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "New+", "New+", "{CA716AE6-FE5C-40AC-BB8F-2C87912687AC}" EndProject @@ -595,7 +593,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WindowProperties", "WindowP EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesLib", "src\modules\Workspaces\WorkspacesLib\WorkspacesLib.vcxproj", "{B31FCC55-B5A4-4EA7-B414-2DCEAE6AF332}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Workspaces.Lib.UnitTests", "src\modules\Workspaces\WorkspacesLib.UnitTests\WorkspacesLibUnitTests.vcxproj", "{A85D4D9F-9A39-4B5D-8B5A-9F2D5C9A8B4C}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WorkspacesLibUnitTests", "src\modules\Workspaces\WorkspacesLib.UnitTests\WorkspacesLibUnitTests.vcxproj", "{A85D4D9F-9A39-4B5D-8B5A-9F2D5C9A8B4C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkspacesLauncherUI", "src\modules\Workspaces\WorkspacesLauncherUI\WorkspacesLauncherUI.csproj", "{9C53CC25-0623-4569-95BC-B05410675EE3}" EndProject @@ -697,7 +695,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.System EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CmdPalKeyboardService", "src\modules\cmdpal\CmdPalKeyboardService\CmdPalKeyboardService.vcxproj", "{5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRename.FuzzTests", "src\modules\powerrename\PowerRename.FuzzingTest\PowerRename.FuzzingTest.vcxproj", "{2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRename.FuzzingTest", "src\modules\powerrename\PowerRename.FuzzingTest\PowerRename.FuzzingTest.vcxproj", "{2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BgcodePreviewHandler", "src\modules\previewpane\BgcodePreviewHandler\BgcodePreviewHandler.csproj", "{9E0CBC06-F29A-4810-B93C-97D53863B95E}" EndProject @@ -786,8 +784,20 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.TimeDa EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.WindowWalker.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.WindowWalker.UnitTests\Microsoft.CmdPal.Ext.WindowWalker.UnitTests.csproj", "{E816D7B0-4688-4ECB-97CC-3D8E798F3829}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ShortcutGuide.CPPProject", "src\modules\ShortcutGuide\ShortcutGuide.CPPProject\ShortcutGuide.CPPProject.vcxproj", "{C992FD2C-83B8-4941-9FC1-09730068D8EC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShortcutGuide.IndexYmlGenerator", "src\modules\ShortcutGuide\ShortcutGuide.IndexYmlGenerator\ShortcutGuide.IndexYmlGenerator.csproj", "{30F57201-9B54-5253-8033-8A28ECD3F1CE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShortcutGuide.Ui", "src\modules\ShortcutGuide\ShortcutGuide.Ui\ShortcutGuide.Ui.csproj", "{D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.UnitTestBase", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj", "{00D8659C-2068-40B6-8B86-759CD6284BBB}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ShortcutGuide.CPPProject", "src\modules\ShortcutGuide\ShortcutGuide.CPPProject\ShortcutGuide.CPPProject.vcxproj", "{C992FD2C-83B8-4941-9FC1-09730068D8EC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShortcutGuide.IndexYmlGenerator", "src\modules\ShortcutGuide\ShortcutGuide.IndexYmlGenerator\ShortcutGuide.IndexYmlGenerator.csproj", "{30F57201-9B54-5253-8033-8A28ECD3F1CE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShortcutGuide.Ui", "src\modules\ShortcutGuide\ShortcutGuide.Ui\ShortcutGuide.Ui.csproj", "{D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -1428,14 +1438,6 @@ Global {2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Release|ARM64.Build.0 = Release|ARM64 {2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Release|x64.ActiveCfg = Release|x64 {2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Release|x64.Build.0 = Release|x64 - {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Debug|ARM64.Build.0 = Debug|ARM64 - {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Debug|x64.ActiveCfg = Debug|x64 - {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Debug|x64.Build.0 = Debug|x64 - {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Release|ARM64.ActiveCfg = Release|ARM64 - {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Release|ARM64.Build.0 = Release|ARM64 - {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Release|x64.ActiveCfg = Release|x64 - {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Release|x64.Build.0 = Release|x64 {48804216-2A0E-4168-A6D8-9CD068D14227}.Debug|ARM64.ActiveCfg = Debug|ARM64 {48804216-2A0E-4168-A6D8-9CD068D14227}.Debug|ARM64.Build.0 = Debug|ARM64 {48804216-2A0E-4168-A6D8-9CD068D14227}.Debug|x64.ActiveCfg = Debug|x64 @@ -2842,6 +2844,30 @@ Global {E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Release|ARM64.Build.0 = Release|ARM64 {E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Release|x64.ActiveCfg = Release|x64 {E816D7B0-4688-4ECB-97CC-3D8E798F3829}.Release|x64.Build.0 = Release|x64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Debug|ARM64.Build.0 = Debug|ARM64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Debug|x64.ActiveCfg = Debug|x64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Debug|x64.Build.0 = Debug|x64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Release|ARM64.ActiveCfg = Release|ARM64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Release|ARM64.Build.0 = Release|ARM64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Release|x64.ActiveCfg = Release|x64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Release|x64.Build.0 = Release|x64 + {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Debug|ARM64.Build.0 = Debug|ARM64 + {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Debug|x64.ActiveCfg = Debug|x64 + {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Debug|x64.Build.0 = Debug|x64 + {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Release|ARM64.ActiveCfg = Release|ARM64 + {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Release|ARM64.Build.0 = Release|ARM64 + {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Release|x64.ActiveCfg = Release|x64 + {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Release|x64.Build.0 = Release|x64 + {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Debug|ARM64.Build.0 = Debug|ARM64 + {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Debug|x64.ActiveCfg = Debug|x64 + {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Debug|x64.Build.0 = Debug|x64 + {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Release|ARM64.ActiveCfg = Release|ARM64 + {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Release|ARM64.Build.0 = Release|ARM64 + {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Release|x64.ActiveCfg = Release|x64 + {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Release|x64.Build.0 = Release|x64 {00D8659C-2068-40B6-8B86-759CD6284BBB}.Debug|ARM64.ActiveCfg = Debug|ARM64 {00D8659C-2068-40B6-8B86-759CD6284BBB}.Debug|ARM64.Build.0 = Debug|ARM64 {00D8659C-2068-40B6-8B86-759CD6284BBB}.Debug|x64.ActiveCfg = Debug|x64 @@ -2850,6 +2876,30 @@ Global {00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|ARM64.Build.0 = Release|ARM64 {00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.ActiveCfg = Release|x64 {00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.Build.0 = Release|x64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Debug|ARM64.Build.0 = Debug|ARM64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Debug|x64.ActiveCfg = Debug|x64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Debug|x64.Build.0 = Debug|x64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Release|ARM64.ActiveCfg = Release|ARM64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Release|ARM64.Build.0 = Release|ARM64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Release|x64.ActiveCfg = Release|x64 + {C992FD2C-83B8-4941-9FC1-09730068D8EC}.Release|x64.Build.0 = Release|x64 + {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Debug|ARM64.Build.0 = Debug|ARM64 + {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Debug|x64.ActiveCfg = Debug|x64 + {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Debug|x64.Build.0 = Debug|x64 + {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Release|ARM64.ActiveCfg = Release|ARM64 + {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Release|ARM64.Build.0 = Release|ARM64 + {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Release|x64.ActiveCfg = Release|x64 + {30F57201-9B54-5253-8033-8A28ECD3F1CE}.Release|x64.Build.0 = Release|x64 + {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Debug|ARM64.Build.0 = Debug|ARM64 + {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Debug|x64.ActiveCfg = Debug|x64 + {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Debug|x64.Build.0 = Debug|x64 + {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Release|ARM64.ActiveCfg = Release|ARM64 + {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Release|ARM64.Build.0 = Release|ARM64 + {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Release|x64.ActiveCfg = Release|x64 + {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2947,7 +2997,6 @@ Global {3E424AD2-19E5-4AE6-B833-F53963EB5FC1} = {B9617A31-0F0A-4397-851D-BF2FBEE32D7F} {106CBECA-0701-4FC3-838C-9DF816A19AE2} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {2D604C07-51FC-46BB-9EB7-75AECC7F5E81} = {106CBECA-0701-4FC3-838C-9DF816A19AE2} - {2EDB3EB4-FA92-4BFF-B2D8-566584837231} = {106CBECA-0701-4FC3-838C-9DF816A19AE2} {48804216-2A0E-4168-A6D8-9CD068D14227} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} {FF1D7936-842A-4BBB-8BEA-E9FE796DE700} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} {5043CECE-E6A7-4867-9CBE-02D27D83747A} = {4AFC9975-2456-4C70-94A4-84073C1CED93} @@ -3160,9 +3209,15 @@ Global {E816D7AE-4688-4ECB-97CC-3D8E798F3827} = {8EF25507-2575-4ADE-BF7E-D23376903AB8} {E816D7AF-4688-4ECB-97CC-3D8E798F3828} = {8EF25507-2575-4ADE-BF7E-D23376903AB8} {E816D7B0-4688-4ECB-97CC-3D8E798F3829} = {8EF25507-2575-4ADE-BF7E-D23376903AB8} + {C992FD2C-83B8-4941-9FC1-09730068D8EC} = {106CBECA-0701-4FC3-838C-9DF816A19AE2} + {30F57201-9B54-5253-8033-8A28ECD3F1CE} = {106CBECA-0701-4FC3-838C-9DF816A19AE2} + {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D} = {106CBECA-0701-4FC3-838C-9DF816A19AE2} {00D8659C-2068-40B6-8B86-759CD6284BBB} = {8EF25507-2575-4ADE-BF7E-D23376903AB8} + {C992FD2C-83B8-4941-9FC1-09730068D8EC} = {106CBECA-0701-4FC3-838C-9DF816A19AE2} + {30F57201-9B54-5253-8033-8A28ECD3F1CE} = {106CBECA-0701-4FC3-838C-9DF816A19AE2} + {D5BD72DD-B461-FDD4-FD7D-AF0B620AE75D} = {106CBECA-0701-4FC3-838C-9DF816A19AE2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} EndGlobalSection -EndGlobal +EndGlobal \ No newline at end of file diff --git a/deps/cziplib b/deps/cziplib index 81314fff0a88..7a5741426136 160000 --- a/deps/cziplib +++ b/deps/cziplib @@ -1 +1 @@ -Subproject commit 81314fff0a882b72a9ad321e7a3311660125b56e +Subproject commit 7a57414261361ca991ff8053881343eb6bb6f205 diff --git a/doc/devdocs/images/shortcutguide/diagram.png b/doc/devdocs/images/shortcutguide/diagram.png deleted file mode 100644 index 12d7256828a8..000000000000 Binary files a/doc/devdocs/images/shortcutguide/diagram.png and /dev/null differ diff --git a/doc/devdocs/modules/shortcut_guide.md b/doc/devdocs/modules/shortcut_guide.md index c8cefbc3f3a6..64bc483b0998 100644 --- a/doc/devdocs/modules/shortcut_guide.md +++ b/doc/devdocs/modules/shortcut_guide.md @@ -9,12 +9,14 @@ [Pull Requests](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+is%3Aopen+label%3A%22Product-Shortcut+Guide%22+) ## Overview -Shortcut Guide is a PowerToy that displays an overlay of available keyboard shortcuts when the Windows key is pressed and held. It provides a visual reference for Windows key combinations, helping users discover and utilize built-in Windows shortcuts. +Shortcut Guide is a PowerToy that displays an overlay of available keyboard shortcuts when a user-set keyboard shortcut is pressed. It helps users discover and remember keyboard shortcuts for Windows and apps. + +> [!NOTE] +> The spec for the manifest files is in development and will be linked here once available. ## Usage -- Press and hold the Windows key to display the overlay of available shortcuts -- Press the hotkey again to dismiss the overlay -- The overlay displays Windows shortcuts with their corresponding actions +- Press the user-defined hotkey to display the overlay +- Press the hotkey again or press ESC to dismiss the overlay ## Build and Debug Instructions @@ -25,67 +27,83 @@ Shortcut Guide is a PowerToy that displays an overlay of available keyboard shor 4. The executable is named PowerToys.ShortcutGuide.exe ### Debug -1. Right-click the ShortcutGuide project and select 'Set as Startup Project' +1. Right-click the ShortcutGuide.Ui project and select 'Set as Startup Project' 2. Right-click the project again and select 'Debug' -## Code Structure +> [!NOTE] +> When run in debug mode, the window behaves differently than in release mode. It will not automatically close when loosing focus, it will be displayed on top of all other windows, and it is not hidden from the taskbar. + +## Project Structure + +The Shortcut Guide module consists of the following 4 projects: + +### [`ShortcutGuide.Ui`](/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj + +This is the main UI project for the Shortcut Guide module. Upon startup it does the following tasks: + +1. Copies the built-in manifest files to the users manifest directory (overwriting existing files). +2. Generate the `index.yml` manifest file. +3. Populate the PowerToys shortcut manifest with the user-defined shortcuts. +4. Starts the UI. + +### [`ShortcutGuide.CPPProject`](/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/ShortcutGuide.CPPProject.vcxproj) + +This project exports certain functions to be used by the Shortcut Guide module, that were not able to be implemented in C#. + +#### [`excluded_app.cpp`](/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/excluded_app.cpp) + +This file contains one function with the following signature: -![Diagram](../images/shortcutguide/diagram.png) +```cpp +__declspec(dllexport) bool IsCurrentWindowExcludedFromShortcutGuide() +``` -### Core Files +This function checks if the current window is excluded from the Shortcut Guide overlay. It returns `true` if the current window is excluded otherwise it returns `false`. -#### [`dllmain.cpp`](/src/modules/shortcut_guide/dllmain.cpp) -Contains DLL boilerplate code. Implements the PowertoyModuleIface, including enable/disable functionality and GPO policy handling. Captures hotkey events and starts the PowerToys.ShortcutGuide.exe process to display the shortcut guide window. +#### [`tasklist_positions.cpp`](/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/tasklist_positions.cpp) -#### [`shortcut_guide.cpp`](/src/modules/shortcut_guide/shortcut_guide.cpp) -Contains the module interface code. It initializes the settings values and the keyboard event listener. Defines the OverlayWindow class, which manages the overall logic and event handling for the PowerToys Shortcut Guide. +This file contains helper functions to retrieve the positions of the taskbar buttons. It exports the following function: -#### [`overlay_window.cpp`](/src/modules/shortcut_guide/overlay_window.cpp) -Contains the code for loading the SVGs, creating and rendering of the overlay window. Manages and displays overlay windows with SVG graphics through two main classes: -- D2DOverlaySVG: Handles loading, resizing, and manipulation of SVG graphics -- D2DOverlayWindow: Manages the display and behavior of the overlay window +```cpp +__declspec(dllexport) TasklistButton* get_buttons(HMONITOR monitor, int* size) +``` -#### [`keyboard_state.cpp`](/src/modules/shortcut_guide/keyboard_state.cpp) -Contains helper methods for checking the current state of the keyboard. +This function retrieves the positions of the taskbar buttons for a given monitor. It returns an array of `TasklistButton` structures (max 10), which contain the position and size of each button. -#### [`target_state.cpp`](/src/modules/shortcut_guide/target_state.cpp) -State machine that handles the keyboard events. It's responsible for deciding when to show the overlay, when to suppress the Start menu (if the overlay is displayed long enough), etc. Handles state transitions and synchronization to ensure the overlay is shown or hidden appropriately based on user interactions. +`monitor` must be the monitor handle of the monitor containing the taskbar instance of which the buttons should be retrieved. -#### [`trace.cpp`](/src/modules/shortcut_guide/trace.cpp) -Contains code for telemetry. +`size` will contain the resulting array size. -### Supporting Files +It determines the positions through Windows `FindWindowEx` function. +For the primary taskbar it searches for: +* A window called "Shell_TrayWnd" +* that contains a window called "ReBarWindow32" +* that contains a window called "MSTaskSwWClass" +* that contains a window called "MSTaskListWClass" -#### [`animation.cpp`](/src/modules/shortcut_guide/animation.cpp) -Handles the timing and interpolation of animations. Calculates the current value of an animation based on elapsed time and a specified easing function. +For any secondary taskbar it searches for: +* A window called "Shell_SecondaryTrayWnd" +* that contains a window called "WorkerW" +* that contains a window called "MSTaskListWClass" -#### [`d2d_svg.cpp`](/src/modules/shortcut_guide/d2d_svg.cpp) -Provides functionality for loading, resizing, recoloring, rendering, and manipulating SVG images using Direct2D. +It then enumerates all the button elements inside "MSTaskListWClass" while skipping such with a same name (which implies the user does not use combining taskbar buttons) -#### [`d2d_text.cpp`](/src/modules/shortcut_guide/d2d_text.cpp) -Handles creation, resizing, alignment, and rendering of text using Direct2D and DirectWrite. +### [`ShortcutGuide.IndexYmlGenerator`](/src/modules/ShortcutGuide/ShortcutGuide.IndexYmlGenerator/) -#### [`d2d_window.cpp`](/src/modules/shortcut_guide/d2d_window.cpp) -Manages a window using Direct2D and Direct3D for rendering. Handles window creation, resizing, rendering, and destruction. +This application generates the `index.yml` manifest file. -#### [`native_event_waiter.cpp`](/src/modules/shortcut_guide/native_event_waiter.cpp) -Waits for a named event and executes a specified action when the event is triggered. Uses a separate thread to handle event waiting and action execution. +It is a separate project so that its code can be easier ported to WinGet in the future. -#### [`tasklist_positions.cpp`](/src/modules/shortcut_guide/tasklist_positions.cpp) -Handles retrieving and updating the positions and information of taskbar buttons in Windows. +### [`ShortcutGuideModuleInterface`](/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj) -#### [`main.cpp`](/src/modules/shortcut_guide/main.cpp) -The entry point for the PowerToys Shortcut Guide application. Handles initialization, ensures single instance execution, manages parent process termination, creates and displays the overlay window, and runs the main event loop. +The module interface that handles opening and closing the user interface. ## Features and Limitations -- The overlay displays Windows shortcuts (Windows key combinations) -- The module supports localization, but only for the Windows controls on the left side of the overlay +- Currently the displayed shortcuts (Except the ones from PowerToys) are not localized. - It's currently rated as a P3 (lower priority) module ## Future Development -A community-contributed version 2 is in development that will support: -- Application-specific shortcuts based on the active application -- Additional shortcuts beyond Windows key combinations -- PowerToys shortcuts +- Implementing with WinGet to get new shortcut manifest files +- Adding localization support for the built-in manifest files \ No newline at end of file diff --git a/installer/PowerToysSetup/ShortcutGuide.wxs b/installer/PowerToysSetup/ShortcutGuide.wxs index 729a80586101..ed03201f5076 100644 --- a/installer/PowerToysSetup/ShortcutGuide.wxs +++ b/installer/PowerToysSetup/ShortcutGuide.wxs @@ -4,26 +4,25 @@ - - + + - - - + + - + - + - + - + diff --git a/installer/PowerToysSetup/generateAllFileComponents.ps1 b/installer/PowerToysSetup/generateAllFileComponents.ps1 index a8365d8649e8..207fb8bb38c3 100644 --- a/installer/PowerToysSetup/generateAllFileComponents.ps1 +++ b/installer/PowerToysSetup/generateAllFileComponents.ps1 @@ -305,8 +305,8 @@ Generate-FileComponents -fileListName "ValueGeneratorImagesCmpFiles" -wxsFilePat ## Plugins #ShortcutGuide -Generate-FileList -fileDepsJson "" -fileListName ShortcutGuideSvgFiles -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\ShortcutGuide\" -Generate-FileComponents -fileListName "ShortcutGuideSvgFiles" -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs -regroot $registryroot +Generate-FileList -fileDepsJson "" -fileListName ShortcutGuideAssetsFiles -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\ShortcutGuide\" +Generate-FileComponents -fileListName "ShortcutGuideAssetsFiles" -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs -regroot $registryroot #Settings Generate-FileList -fileDepsJson "" -fileListName SettingsV2AssetsFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\" diff --git a/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/ShortcutGuide.CPPProject.vcxproj b/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/ShortcutGuide.CPPProject.vcxproj new file mode 100644 index 000000000000..612b81decb24 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/ShortcutGuide.CPPProject.vcxproj @@ -0,0 +1,161 @@ + + + + + 15.0 + Win32Proj + {2d604c07-51fc-46bb-9eb7-75aecc7f5e81} + ShortcutGuide.CPPProject + ShortcutGuide.CPPProject + 10.0.26100.0 + + + + DynamicLibrary + true + v143 + Unicode + Spectre + + + DynamicLibrary + false + v143 + true + Unicode + Spectre + + + + + + + + + + + + 17.0 + Win32Proj + {c992fd2c-83b8-4941-9fc1-09730068d8ec} + ShortcutGuideCPPProject + 10.0.22621.0 + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\ + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + ..\..\..\common\utils;..\..\..;%(AdditionalIncludeDirectories) + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + ..\..\..\common\utils;..\..\..;%(AdditionalIncludeDirectories) + + + Console + true + + + + + ..\..\..\common\utils;..\..\..;%(AdditionalIncludeDirectories) + + + + + ..\..\..\common\utils;..\..\..;%(AdditionalIncludeDirectories) + + + + + + Create + Create + Create + Create + + + + + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + + {6955446d-23f7-4023-9bb3-8657f904af99} + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/ShortcutGuide.CPPProject.vcxproj.filters b/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/ShortcutGuide.CPPProject.vcxproj.filters new file mode 100644 index 000000000000..76e726ffc627 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/ShortcutGuide.CPPProject.vcxproj.filters @@ -0,0 +1,42 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + + + + \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/excluded_app.cpp b/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/excluded_app.cpp new file mode 100644 index 000000000000..0f7fe4a33675 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/excluded_app.cpp @@ -0,0 +1,39 @@ +#include "pch.h" +#include "excluded_app.h" +#include + +extern "C" +{ + __declspec(dllexport) bool IsCurrentWindowExcludedFromShortcutGuide() + { + PowerToysSettings::PowerToyValues settings = PowerToysSettings::PowerToyValues::load_from_settings_file(L"Shortcut Guide"); + auto settingsObject = settings.get_raw_json(); + std::wstring apps = settingsObject.GetNamedObject(L"properties").GetNamedObject(L"disabled_apps").GetNamedString(L"value").c_str(); + auto excludedUppercase = apps; + CharUpperBuffW(excludedUppercase.data(), static_cast(excludedUppercase.length())); + std::wstring_view view(excludedUppercase); + view = left_trim(trim(view)); + + while (!view.empty()) + { + auto pos = (std::min)(view.find_first_of(L"\r\n"), view.length()); + m_excludedApps.emplace_back(view.substr(0, pos)); + view.remove_prefix(pos); + view = left_trim(trim(view)); + } + + if (m_excludedApps.empty()) + { + return false; + } + + if (HWND foregroundApp{ GetForegroundWindow() }) + { + auto processPath = get_process_path(foregroundApp); + CharUpperBuffW(processPath.data(), static_cast(processPath.length())); + + return check_excluded_app(foregroundApp, processPath, m_excludedApps); + } + return false; + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/excluded_app.h b/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/excluded_app.h new file mode 100644 index 000000000000..652449a138a1 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/excluded_app.h @@ -0,0 +1,7 @@ +#pragma once + +extern "C" +{ + std::vector m_excludedApps; + __declspec(dllexport) bool IsCurrentWindowExcludedFromShortcutGuide(); +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/pch.cpp b/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/pch.cpp similarity index 100% rename from src/modules/ShortcutGuide/ShortcutGuide/pch.cpp rename to src/modules/ShortcutGuide/ShortcutGuide.CPPProject/pch.cpp diff --git a/src/modules/ShortcutGuide/ShortcutGuide/pch.h b/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/pch.h similarity index 73% rename from src/modules/ShortcutGuide/ShortcutGuide/pch.h rename to src/modules/ShortcutGuide/ShortcutGuide.CPPProject/pch.h index 9ec2bbcb4184..9e16b802560f 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/pch.h +++ b/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/pch.h @@ -1,9 +1,11 @@ -#pragma once +#pragma once #define NOMINMAX +#include #include +#include +#include #include #include -#include #include #include #include @@ -24,6 +26,7 @@ #include #include #include -#include #include -#include \ No newline at end of file +#include +#include +#include <../SettingsAPI/settings_objects.h> diff --git a/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/tasklist_positions.cpp b/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/tasklist_positions.cpp new file mode 100644 index 000000000000..23cfd37e3b0c --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/tasklist_positions.cpp @@ -0,0 +1,185 @@ +#include "pch.h" +#include "tasklist_positions.h" + +// Tried my hardest adapting this to C#, but FindWindowW didn't work properly in C#. ~Noraa Junker + +extern "C" +{ + HWND GetTaskbarHwndForCursorMonitor(HMONITOR monitor) + { + POINT pt; + if (!GetCursorPos(&pt)) + return nullptr; + + // Find the primary taskbar + HWND primaryTaskbar = FindWindowW(L"Shell_TrayWnd", nullptr); + if (primaryTaskbar) + { + MONITORINFO mi = { sizeof(mi) }; + if (GetWindowRect(primaryTaskbar, &mi.rcMonitor)) + { + HMONITOR primaryMonitor = MonitorFromRect(&mi.rcMonitor, MONITOR_DEFAULTTONEAREST); + if (primaryMonitor == monitor) + return primaryTaskbar; + } + } + + // Find the secondary taskbar(s) + HWND secondaryTaskbar = nullptr; + while ((secondaryTaskbar = FindWindowExW(nullptr, secondaryTaskbar, L"Shell_SecondaryTrayWnd", nullptr)) != nullptr) + { + MONITORINFO mi = { sizeof(mi) }; + RECT rc; + if (GetWindowRect(secondaryTaskbar, &rc)) + { + HMONITOR taskbarMonitor = MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST); + if (monitor == taskbarMonitor) + return secondaryTaskbar; + } + } + + return nullptr; + } + + void update(HMONITOR monitor) + { + // Get HWND of the tasklist for the monitor under the cursor + auto taskbar_hwnd = GetTaskbarHwndForCursorMonitor(monitor); + if (!taskbar_hwnd) + return; + + wchar_t class_name[64] = {}; + GetClassNameW(taskbar_hwnd, class_name, 64); + + HWND tasklist_hwnd = nullptr; + + if (wcscmp(class_name, L"Shell_TrayWnd") == 0) + { + // Primary taskbar structure + tasklist_hwnd = FindWindowExW(taskbar_hwnd, 0, L"ReBarWindow32", nullptr); + if (!tasklist_hwnd) + return; + tasklist_hwnd = FindWindowExW(tasklist_hwnd, 0, L"MSTaskSwWClass", nullptr); + if (!tasklist_hwnd) + return; + tasklist_hwnd = FindWindowExW(tasklist_hwnd, 0, L"MSTaskListWClass", nullptr); + if (!tasklist_hwnd) + return; + } + else if (wcscmp(class_name, L"Shell_SecondaryTrayWnd") == 0) + { + // Secondary taskbar structure + HWND workerw = FindWindowExW(taskbar_hwnd, 0, L"WorkerW", nullptr); + if (!workerw) + return; + tasklist_hwnd = FindWindowExW(workerw, 0, L"MSTaskListWClass", nullptr); + if (!tasklist_hwnd) + return; + } + else + { + // Unknown taskbar type + return; + } + + if (!automation) + { + winrt::check_hresult(CoCreateInstance(CLSID_CUIAutomation, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IUIAutomation, + automation.put_void())); + winrt::check_hresult(automation->CreateTrueCondition(true_condition.put())); + } + element = nullptr; + winrt::check_hresult(automation->ElementFromHandle(tasklist_hwnd, element.put())); + } + + bool update_buttons(std::vector& buttons) + { + if (!automation || !element) + { + return false; + } + winrt::com_ptr elements; + if (element->FindAll(TreeScope_Children, true_condition.get(), elements.put()) < 0) + return false; + if (!elements) + return false; + int count; + if (elements->get_Length(&count) < 0) + return false; + winrt::com_ptr child; + std::vector found_buttons; + found_buttons.reserve(count); + for (int i = 0; i < count; ++i) + { + child = nullptr; + if (elements->GetElement(i, child.put()) < 0) + return false; + TasklistButton button = {}; + if (VARIANT var_rect; child->GetCurrentPropertyValue(UIA_BoundingRectanglePropertyId, &var_rect) >= 0) + { + if (var_rect.vt == (VT_R8 | VT_ARRAY)) + { + LONG pos; + double value; + pos = 0; + SafeArrayGetElement(var_rect.parray, &pos, &value); + button.x = static_cast(value); + pos = 1; + SafeArrayGetElement(var_rect.parray, &pos, &value); + button.y = static_cast(value); + pos = 2; + SafeArrayGetElement(var_rect.parray, &pos, &value); + button.width = static_cast(value); + pos = 3; + SafeArrayGetElement(var_rect.parray, &pos, &value); + button.height = static_cast(value); + } + VariantClear(&var_rect); + } + else + { + return false; + } + if (BSTR automation_id; child->get_CurrentAutomationId(&automation_id) >= 0) + { + wcsncpy_s(button.name, automation_id, _countof(button.name)); + SysFreeString(automation_id); + } + found_buttons.push_back(button); + } + // assign keynums + buttons.clear(); + for (auto& button : found_buttons) + { + if (buttons.empty()) + { + button.keynum = 1; + buttons.push_back(std::move(button)); + } + else + { + if (button.x < buttons.back().x || button.y < buttons.back().y) // skip 2nd row + break; + if (wcsncmp(button.name, buttons.back().name, _countof(button.name)) == 0) + continue; // skip buttons from the same app + button.keynum = buttons.back().keynum + 1; + buttons.push_back(std::move(button)); + if (buttons.back().keynum == 10) + break; // no more than 10 buttons + } + } + return true; + } + + __declspec(dllexport) TasklistButton* get_buttons(HMONITOR monitor, int* size) + { + update(monitor); + static std::vector buttons; + update_buttons(buttons); + *size = static_cast(buttons.size()); + return buttons.data(); + } +} \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/tasklist_positions.h b/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/tasklist_positions.h new file mode 100644 index 000000000000..0f8c4ca35aa4 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/tasklist_positions.h @@ -0,0 +1,23 @@ +#pragma once + +struct TasklistButton +{ + wchar_t name[256]; + int x; + int y; + int width; + int height; + int keynum; +}; + +extern "C" +{ + winrt::com_ptr automation; + winrt::com_ptr element; + winrt::com_ptr true_condition; + + // Helper to get the taskbar HWND for the monitor under the cursor + HWND GetTaskbarHwndForCursorMonitor(HMONITOR monitor); + bool update_buttons(std::vector& buttons); + __declspec(dllexport) TasklistButton* get_buttons(HMONITOR monitor, int* size); +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.IndexYmlGenerator/IndexYmlGenerator.cs b/src/modules/ShortcutGuide/ShortcutGuide.IndexYmlGenerator/IndexYmlGenerator.cs new file mode 100644 index 000000000000..1997bc102397 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.IndexYmlGenerator/IndexYmlGenerator.cs @@ -0,0 +1,76 @@ +// 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.Collections.Generic; +using System.IO; +using ShortcutGuide.Helpers; +using ShortcutGuide.Models; +using YamlDotNet.Serialization; + +// This should all be outsourced to WinGet in the future +namespace ShortcutGuide.IndexYmlGenerator +{ + public class IndexYmlGenerator + { + public static void Main() + { + CreateIndexYmlFile(); + } + + // Todo: Exception handling + public static void CreateIndexYmlFile() + { + string path = ManifestInterpreter.PathOfManifestFiles; + if (File.Exists(Path.Combine(path, "index.yml"))) + { + File.Delete(Path.Combine(path, "index.yml")); + } + + IndexFile indexFile = new() { }; + Dictionary<(string WindowFilter, bool BackgroundProcess), List> processes = []; + + foreach (string file in Directory.EnumerateFiles(path, "*.yml")) + { + string content = File.ReadAllText(file); + Deserializer deserializer = new(); + ShortcutFile shortcutFile = deserializer.Deserialize(content); + if (processes.TryGetValue((shortcutFile.WindowFilter, shortcutFile.BackgroundProcess), out List? apps)) + { + if (apps.Contains(shortcutFile.PackageName)) + { + continue; + } + + apps.Add(shortcutFile.PackageName); + continue; + } + + processes[(shortcutFile.WindowFilter, shortcutFile.BackgroundProcess)] = [shortcutFile.PackageName]; + } + + indexFile.Index = []; + + foreach (var item in processes) + { + indexFile.Index = + [ + .. indexFile.Index, + new IndexFile.IndexItem + { + WindowFilter = item.Key.WindowFilter, + BackgroundProcess = item.Key.BackgroundProcess, + Apps = [.. item.Value], + }, + ]; + } + + // Todo: Take the default shell name from the settings or environment variable, default to "+WindowsNT.Shell" + indexFile.DefaultShellName = "+WindowsNT.Shell"; + + Serializer serializer = new(); + string yamlContent = serializer.Serialize(indexFile); + File.WriteAllText(Path.Combine(path, "index.yml"), yamlContent); + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.IndexYmlGenerator/ShortcutGuide.IndexYmlGenerator.csproj b/src/modules/ShortcutGuide/ShortcutGuide.IndexYmlGenerator/ShortcutGuide.IndexYmlGenerator.csproj new file mode 100644 index 000000000000..571ca9de0363 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.IndexYmlGenerator/ShortcutGuide.IndexYmlGenerator.csproj @@ -0,0 +1,24 @@ + + + + + + + WinExe + ShortcutGuide.IndexYmlGenerator + enable + false + false + ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps + PowerToys.ShortcutGuide.IndexYmlGenerator + + + + + + + + + + + diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/+WindowsNT.Notepad.en-US.yml b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/+WindowsNT.Notepad.en-US.yml new file mode 100644 index 000000000000..d0d607253c36 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/+WindowsNT.Notepad.en-US.yml @@ -0,0 +1,247 @@ +PackageName: +WindowsNT.Notepad +Name: Notepad +WindowFilter: "Notepad.exe" +BackgroundProcess: false +Shortcuts: + - SectionName: File + Properties: + - Name: New tab + Recommended: true + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - N + - Name: New window + Shortcut: + - Win: false + Ctrl: true + Shift: true + Alt: false + Keys: + - N + - Name: Open + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - O + - Name: Save + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - S + - Name: Save As + Shortcut: + - Win: false + Ctrl: true + Shift: true + Alt: false + Keys: + - S + - Name: Save all + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: true + Keys: + - S + - Name: Print + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - P + - Name: Close tab + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - W + - Name: Close window + Shortcut: + - Win: false + Ctrl: true + Shift: true + Alt: false + Keys: + - W + - SectionName: Edit + Properties: + - Name: Undo + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - Z + - Name: Redo + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: true + Keys: + - Z + - Name: Cut + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - X + - Name: Copy + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - C + - Name: Paste + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - V + - Name: Search with Bing + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - E + - Name: Find + Recommended: true + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - F + - Name: Find next + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - F3 + - Name: Find previous + Shortcut: + - Win: false + Ctrl: false + Shift: true + Alt: false + Keys: + - F3 + - Name: Replace + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - H + - Name: Go to + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - G + - Name: Select all + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - A + - Name: Time/Date + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - F5 + - SectionName: View + Properties: + - Name: Zoom in + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - Plus + - Name: Zoom out + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - Minus + - Name: Reset zoom + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - 0 + - SectionName: Formatting + Properties: + - Name: Bold + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - B + - Name: Italic + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - I + - Name: Insert link + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - K + - Name: Clear formatting + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - Space \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/+WindowsNT.Shell.en-US.yml b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/+WindowsNT.Shell.en-US.yml new file mode 100644 index 000000000000..e845324580da --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/+WindowsNT.Shell.en-US.yml @@ -0,0 +1,773 @@ +PackageName: +WindowsNT.Shell +WindowFilter: "*" +BackgroundProcess: true +Shortcuts: + - SectionName: Desktop Shortcuts + Properties: + - Name: Close active window + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - F4 + - Name: Open shutdown box + Description: When no windows are open + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - F4 + - Name: Cycle through open Windows + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - Esc + - Name: Reveal typed password + Description: On sign-in screen + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - F8 + - Name: Go back + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - "" + - Name: Go forward + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - "" + - Name: Move up one screen + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - "" + - Name: Move down one screen + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - "" + - Name: Window context menu + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - Space + - Name: Switch between open apps + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - Tab + Description: While pressing Tab multiple times + - Name: Run command + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - "" + Description: for the underlined letter in the app + - Name: View open apps + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: true + Keys: + - Tab + - Name: Change start menu size + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "" + - Name: Move cursor + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "" + Description: To the beginning or end of a word + - Name: Switch keyboard layout + Shortcut: + - Win: false + Ctrl: true + Shift: true + Alt: false + Keys: + - "" + - Name: Select block of text + Shortcut: + - Win: false + Ctrl: true + Shift: true + Alt: false + Keys: + - "" + - Name: Open Task Manager + Shortcut: + - Win: false + Ctrl: true + Shift: true + Alt: false + Keys: + - Esc + - Name: Enable/Disable Chinese IME + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - Space + - Name: Open context menu + Shortcut: + - Win: false + Ctrl: false + Shift: true + Alt: false + Keys: + - F10 + Description: For the selected item + - SectionName: Virtual desktop + Properties: + - Name: Open task view + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - Tab + - Name: Add a virtual desktop + Shortcut: + - Win: true + Ctrl: true + Shift: false + Alt: false + Keys: + - D + - Name: Close current desktop + Shortcut: + - Win: true + Ctrl: true + Shift: false + Alt: false + Keys: + - F4 + - Name: Switch desktop + Shortcut: + - Win: true + Ctrl: true + Shift: false + Alt: false + Keys: + - "" + Recommended: true + - SectionName: "Windows key" + Properties: + - Name: Open start menu + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - Name: Open Action Center + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "A" + - Name: Open Date and Time + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: true + Keys: + - "D" + - Name: Focus on the notification area + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "B" + - Name: Open narrator + Shortcut: + - Win: true + Ctrl: true + Shift: false + Alt: false + Keys: + - "Enter" + - Name: Open domain search + Shortcut: + - Win: true + Ctrl: true + Shift: false + Alt: false + Keys: + - "F" + - Name: Open Quick Assist + Shortcut: + - Win: true + Ctrl: true + Shift: false + Alt: false + Keys: + - "Q" + - Name: Wake up device + Shortcut: + - Win: true + Ctrl: true + Shift: true + Alt: false + Keys: + - "B" + Description: When black or a blank screen. + - Name: Change input option + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "Space" + Description: To next option + - Name: Change input option + Shortcut: + - Win: true + Ctrl: true + Shift: false + Alt: false + Keys: + - "Space" + Description: To previous option + - Name: Display/Hide desktop + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "D" + - Name: Minimize the active window + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "Down" + Recommended: true + - Name: Open file Explorer + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "E" + - Name: Close Magnifier + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "Esc" + - Name: Open Feedback Hub + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "F" + Recommended: true + - Name: Start IME reconversion + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "/" + - Name: Open Game Bar + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "G" + - Name: Open voice dictation + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "H" + - Name: Minimize or restore all other windows + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - Name: Open Settings + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "I" + - Name: Set focus to a Windows tip + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "J" + - Name: Open Cast + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "K" + - Name: Lock the device + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "L" + - Name: Snap the window + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - Name: Minimize all windows + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "M" + - Name: Zoom out Magnifier + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "-" + - Name: Zoom in Magnifier + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "=" + - Name: Open notification center + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "N" + - Name: Lock the device orientation + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "O" + - Name: Open project Settings + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "P" + - Name: Open Settings about Page + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - Name: Open the emoji panel + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "." + - Name: Open the emoji panel + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - ";" + - Name: Capture a screenshot + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + Description: Save to the pictures folder + - Name: Open search + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "Q" + - Name: Open search + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "S" + - Name: Open Run dialog + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "R" + - Name: Restore window + Description: If a window is snapped or maximized + Shortcut: + - Win: true + Ctrl: false + Shift: true + Alt: false + Keys: + - "" + - Name: Make UWP app full screen + Shortcut: + - Win: true + Ctrl: false + Shift: true + Alt: false + Keys: + - "" + - Name: Move window to monitor + Shortcut: + - Win: true + Ctrl: false + Shift: true + Alt: false + Keys: + - "" + - Name: Open Snipping Tool + Shortcut: + - Win: true + Ctrl: false + Shift: true + Alt: false + Keys: + - "S" + - Name: Stretch window + Description: To the top and bottom of the screen + Shortcut: + - Win: true + Ctrl: false + Shift: true + Alt: false + Keys: + - "" + - Name: Open task view + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "Tab" + - Name: Open Accessibility Settings + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "U" + - Name: Maximize the active window + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - Name: Open the clipboard history + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "V" + - Name: Open widgets + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "W" + - Name: Open Quick Link menu + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "X" + - Name: Open snap layouts + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "Z" + - SectionName: Clipboard + Properties: + - Name: Copy + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "C" + - Name: Cut + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "X" + - Name: Paste + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "V" + - Name: Paste + Shortcut: + - Win: false + Ctrl: false + Shift: true + Alt: false + Keys: + - "" + Description: Paste as plain text + - SectionName: Taskbar Shortcuts + Properties: + - Name: Open app in Taskbar + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - Name: Open jump list + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: true + Keys: + - "" + - Name: Switch to last active window + Shortcut: + - Win: true + Ctrl: true + Shift: false + Alt: false + Keys: + - "" + - Name: Open as administrator + Shortcut: + - Win: true + Ctrl: true + Shift: true + Alt: false + Keys: + - "" + - SectionName: Copilot key + Properties: + - Name: Open Copilot + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - '' + Description: When copilot is available + - Name: Open Windows search + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - '' + Description: When copilot is not available + - SectionName: Office key + Properties: + - Name: Open Word + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - "W" + - Name: Open Excel + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - "X" + - Name: Open PowerPoint + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - "P" + - Name: Open Outlook + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - "O" + - Name: Open Microsoft Teams + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - "T" + - Name: Open OneNote + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - "N" + - Name: Open OneDrive + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - "D" + - Name: Open Yammer + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - "Y" + - Name: Open LinkedIn + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - "L" \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/+WindowsNT.WindowsExplorer.en-US.yml b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/+WindowsNT.WindowsExplorer.en-US.yml new file mode 100644 index 000000000000..ca134ea7926d --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/+WindowsNT.WindowsExplorer.en-US.yml @@ -0,0 +1,266 @@ +PackageName: +WindowsNT.WindowsExplorer +WindowFilter: "explorer.exe" +Name: File Explorer +Shortcuts: + - SectionName: General + Properties: + - Name: Open File Explorer + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "E" + Recommended: true + - Name: Select the address bar + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - "D" + - Name: Select the address bar + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "L" + - Name: Select the address bar + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "F4" + - Name: Select the search box + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "E" + - Name: Select the search box + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "F3" + - Name: Select the search box + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "F" + - Name: Refresh the window + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "F5" + - Name: Cycle through elements in the active window + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "F6" + - Name: Maximize or restore the active window + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "F11" + - SectionName: Navigation + Properties: + - Name: Navigate to the previous folder + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - "" + - Name: Navigate to the previous folder + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - Name: Navigate to the next folder + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - "" + - Name: Move up a level in the folder path + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - "" + - SectionName: "Window management" + Properties: + - Name: Open a new window + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "N" + - Name: Open a new tab + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "T" + Recommended: true + - Name: Close the current active tab + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "W" + - Name: Move to the next tab + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "Tab" + - Name: Move to the previous tab + Shortcut: + - Win: false + Ctrl: true + Shift: true + Alt: false + Keys: + - "Tab" + - Name: Move to that tab number + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "Number (1-9)" + - Name: Show/Hide the preview pane + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - "P" + - Name: Show/Hide the details pane + Shortcut: + - Win: false + Ctrl: false + Shift: true + Alt: true + Keys: + - "P" + - Name: Resize all columns to fit text + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "+" + - Name: Expand all folders + Description: In the navigation pane + Shortcut: + - Win: false + Ctrl: true + Shift: true + Alt: false + Keys: + - "E" + - SectionName: "File management" + Properties: + - Name: Display properties for the selected item + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - "Enter" + - Name: Delete the selected item + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - Name: Delete the selected item + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "D" + - Name: Delete the selected item permanently + Description: "This removes the item without sending it to the Recycle Bin" + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "D" + - Name: Create a new folder + Shortcut: + - Win: false + Ctrl: true + Shift: true + Alt: false + Keys: + - "N" + Recommended: true + - Name: Rename the selected item + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "F2" + - Name: Select multiple items + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "" + \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/CopilotKey.png b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/CopilotKey.png new file mode 100644 index 000000000000..ccb59a6075af Binary files /dev/null and b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/CopilotKey.png differ diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/HeroImage-dark.png b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/HeroImage-dark.png new file mode 100644 index 000000000000..aa1a055744b5 Binary files /dev/null and b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/HeroImage-dark.png differ diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/HeroImage.png b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/HeroImage.png new file mode 100644 index 000000000000..54c028ec5467 Binary files /dev/null and b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/HeroImage.png differ diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/Microsoft.PowerToys.en-US.yml b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/Microsoft.PowerToys.en-US.yml new file mode 100644 index 000000000000..c768b4bb5ebe --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/Microsoft.PowerToys.en-US.yml @@ -0,0 +1,9 @@ +PackageName: Microsoft.PowerToys +Name: PowerToys +BackgroundProcess: True +WindowFilter: "powertoys.exe" +Shortcuts: + - SectionName: General + Properties: +# +# \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/OfficeKey.png b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/OfficeKey.png new file mode 100644 index 000000000000..ab524b1793be Binary files /dev/null and b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/OfficeKey.png differ diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Shortcut-Guide.ico b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/ShortcutGuide.ico similarity index 100% rename from src/modules/ShortcutGuide/ShortcutGuide/Shortcut-Guide.ico rename to src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/ShortcutGuide.ico diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/DisplayHelper.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/DisplayHelper.cs new file mode 100644 index 000000000000..e638ff1c7e2d --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/DisplayHelper.cs @@ -0,0 +1,50 @@ +// 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 Windows.Foundation; +using WinUIEx; + +namespace ShortcutGuide.Helpers +{ + public static class DisplayHelper + { + /// + /// Returns the display work area for the monitor that contains the specified window. + /// + /// The window handle + /// A element containing the display area + public static Rect GetWorkAreaForDisplayWithWindow(nint hwnd) + { + _foundMonitorIndex = -1; + _monitorIndex = 0; + var monitor = NativeMethods.MonitorFromWindow(hwnd, (int)NativeMethods.MonitorFromWindowDwFlags.MONITOR_DEFAULTTONEAREST); + NativeMethods.EnumDisplayMonitors(nint.Zero, nint.Zero, MonitorEnumProc, new NativeMethods.LPARAM(monitor)); + return MonitorInfo.GetDisplayMonitors()[_foundMonitorIndex].RectWork; + } + + /// + /// The index of the monitor that contains the specified window. -1 indicates that no monitor was found (yet). + /// + private static int _foundMonitorIndex = -1; + + /// + /// The index of the monitor in the enumeration. This is used to find the correct monitor in the list of monitors. + /// + private static int _monitorIndex; + + private static bool MonitorEnumProc(nint hMonitor, nint hdcMonitor, ref NativeMethods.RECT lprcMonitor, nint dwData) + { + nint targetMonitor = dwData; + + if (hMonitor == targetMonitor) + { + _foundMonitorIndex = _monitorIndex; + return false; + } + + _monitorIndex++; + return true; + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/DpiHelper.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/DpiHelper.cs new file mode 100644 index 000000000000..78f5fa8b12b7 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/DpiHelper.cs @@ -0,0 +1,43 @@ +// 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 ShortcutGuide.Helpers +{ + // This class is rewritten from C++ to C# from the measure tool project + internal static class DpiHelper + { +#pragma warning disable SA1310 // Field names should not contain underscore + private const int DEFAULT_DPI = 96; + private const int MONITOR_DEFAULTTONEAREST = 2; + private const int MDT_EFFECTIVE_DPI = 0; +#pragma warning restore SA1310 // Field names should not contain underscore + + public static float GetDPIScaleForWindow(int hwnd) + { + int dpi = DEFAULT_DPI; + GetScreenDPIForWindow(hwnd, ref dpi); + return (float)dpi / DEFAULT_DPI; + } + + private static long GetScreenDPIForWindow(int hwnd, ref int dpi) + { + var targetMonitor = NativeMethods.MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + return GetScreenDPIForMonitor(targetMonitor.ToInt32(), ref dpi); + } + + private static long GetScreenDPIForMonitor(int targetMonitor, ref int dpi) + { + if (targetMonitor != 0) + { + int dummy = 0; + return NativeMethods.GetDpiForMonitor(targetMonitor, MDT_EFFECTIVE_DPI, ref dpi, ref dummy); + } + else + { + dpi = DEFAULT_DPI; + return 0x80004005L; + } + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/ManifestInterpreter.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/ManifestInterpreter.cs new file mode 100644 index 000000000000..e45ccb58d1be --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/ManifestInterpreter.cs @@ -0,0 +1,150 @@ +// 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.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using ShortcutGuide.Models; +using YamlDotNet.Serialization; + +namespace ShortcutGuide.Helpers +{ + /// + /// Helps to interpret the manifest files for the Shortcut Guide. + /// + public class ManifestInterpreter + { + // Todo: Get language from settings or environment variable, default to "en-US" + + /// + /// Gets the language used for the manifest files. + /// + public static string Language => "en-US"; + + /// + /// Returns the shortcuts for a specific application. + /// + /// + /// The method should only be called if the application is known to have a shortcuts file. + /// + /// The manifest id. + /// The deserialized shortcuts file. + /// The requested file was not found. + public static ShortcutFile GetShortcutsOfApplication(string applicationName) + { + string path = PathOfManifestFiles; + IEnumerable files = Directory.EnumerateFiles(path, applicationName + ".*.yml") ?? + throw new FileNotFoundException($"The file for the application '{applicationName}' was not found in '{path}'."); + + IEnumerable filesEnumerable = files as string[] ?? [.. files]; + return filesEnumerable.Any(f => f.EndsWith($".{Language}.yml", StringComparison.InvariantCulture)) + ? YamlToShortcutList(File.ReadAllText(Path.Combine(path, applicationName + $".{Language}.yml"))) + : filesEnumerable.Any(f => f.EndsWith(".en-US.yml", StringComparison.InvariantCulture)) + ? YamlToShortcutList(File.ReadAllText(filesEnumerable.First(f => f.EndsWith(".en-US.yml", StringComparison.InvariantCulture)))) + : throw new FileNotFoundException($"The file for the application '{applicationName}' was not found in '{path}' with the language '{Language}' or 'en-US'."); + } + + /// + /// Deserializes the content of a YAML file to a . + /// + /// The content of the YAML file. + /// A deserialized object. + private static ShortcutFile YamlToShortcutList(string content) + { + Deserializer deserializer = new(); + return deserializer.Deserialize(content); + } + + /// + /// Gets the path to the directory where the manifest files are stored. + /// + public static string PathOfManifestFiles => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft", "WinGet", "KeyboardShortcuts"); + + /// + /// Retrieves the index YAML file that contains the list of all applications and their shortcuts. + /// + /// A deserialized object. + public static IndexFile GetIndexYamlFile() + { + string path = PathOfManifestFiles; + string content = File.ReadAllText(Path.Combine(path, "index.yml")); + Deserializer deserializer = new(); + return deserializer.Deserialize(content); + } + + /// + /// Retrieves all application IDs that should be displayed, based on the foreground window and background processes. + /// + /// An array of all application IDs. + public static string[] GetAllCurrentApplicationIds() + { + nint handle = NativeMethods.GetForegroundWindow(); + + List applicationIds = []; + + Process[] processes = Process.GetProcesses(); + + if (NativeMethods.GetWindowThreadProcessId(handle, out uint processId) > 0) + { + string? name = Process.GetProcessById((int)processId).MainModule?.ModuleName; + + if (name is not null) + { + try + { + foreach (var item in GetIndexYamlFile().Index.First((s) => !s.BackgroundProcess && IsMatch(name, s.WindowFilter)).Apps) + { + applicationIds.Add(item); + } + } + catch (InvalidOperationException) + { + } + } + } + + foreach (var item in GetIndexYamlFile().Index.Where((s) => s.BackgroundProcess)) + { + try + { + if (processes.Any((p) => + { + try + { + return IsMatch(p.MainModule!.ModuleName, item.WindowFilter); + } + catch (Win32Exception) + { + return false; + } + })) + { + foreach (var app in item.Apps) + { + applicationIds.Add(app); + } + } + } + catch (InvalidOperationException) + { + } + } + + return [.. applicationIds]; + + static bool IsMatch(string input, string filter) + { + input = input.ToLower(CultureInfo.InvariantCulture); + filter = filter.ToLower(CultureInfo.InvariantCulture); + string regexPattern = "^" + Regex.Escape(filter).Replace("\\*", ".*") + "$"; + return Regex.IsMatch(input, regexPattern); + } + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/PowerToysShortcutsPopulator.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/PowerToysShortcutsPopulator.cs new file mode 100644 index 000000000000..b96bcfa8b90b --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/PowerToysShortcutsPopulator.cs @@ -0,0 +1,174 @@ +// 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.Globalization; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using Microsoft.PowerToys.Settings.UI.Library; +using static ShortcutGuide.Helpers.ResourceLoaderInstance; + +namespace ShortcutGuide.Helpers +{ + /// + /// Populates the PowerToys shortcuts in the manifest files. + /// + internal sealed partial class PowerToysShortcutsPopulator + { + /// + /// Populates the PowerToys shortcuts in the manifest files. + /// + public static void Populate() + { + string path = Path.Combine(ManifestInterpreter.PathOfManifestFiles, $"Microsoft.PowerToys.{ManifestInterpreter.Language}.yml"); + + StringBuilder content = new(File.ReadAllText(path)); + + const string populateStartString = "# "; + const string populateEndString = "# "; + + content = new(PopulateRegex().Replace(content.ToString(), populateStartString + Environment.NewLine)); + + ISettingsUtils settingsUtils = new SettingsUtils(); + EnabledModules enabledModules = SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Enabled; + if (enabledModules.AdvancedPaste) + { + AdvancedPasteProperties advancedPasteProperties = SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties; + content.Append(HotkeySettingsToYaml(advancedPasteProperties.AdvancedPasteUIShortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("AdvancedPasteUI_Shortcut/Header"))); + content.Append(HotkeySettingsToYaml(advancedPasteProperties.PasteAsPlainTextShortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("PasteAsPlainText_Shortcut/Header"))); + content.Append(HotkeySettingsToYaml(advancedPasteProperties.PasteAsMarkdownShortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("PasteAsMarkdown_Shortcut/Header"))); + content.Append(HotkeySettingsToYaml(advancedPasteProperties.PasteAsJsonShortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("PasteAsJson_Shortcut/Header"))); + if (advancedPasteProperties.AdditionalActions.ImageToText.IsShown) + { + content.Append(HotkeySettingsToYaml(advancedPasteProperties.AdditionalActions.ImageToText.Shortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("ImageToText/Header"))); + } + + if (advancedPasteProperties.AdditionalActions.PasteAsFile.IsShown) + { + content.Append(HotkeySettingsToYaml(advancedPasteProperties.AdditionalActions.PasteAsFile.PasteAsTxtFile.Shortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("PasteAsTxtFile/Header"))); + content.Append(HotkeySettingsToYaml(advancedPasteProperties.AdditionalActions.PasteAsFile.PasteAsPngFile.Shortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("PasteAsPngFile/Header"))); + content.Append(HotkeySettingsToYaml(advancedPasteProperties.AdditionalActions.PasteAsFile.PasteAsHtmlFile.Shortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("PasteAsHtmlFile/Header"))); + } + + if (advancedPasteProperties.AdditionalActions.Transcode.IsShown) + { + content.Append(HotkeySettingsToYaml(advancedPasteProperties.AdditionalActions.Transcode.TranscodeToMp3.Shortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("TranscodeToMp3/Header"))); + content.Append(HotkeySettingsToYaml(advancedPasteProperties.AdditionalActions.Transcode.TranscodeToMp4.Shortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("TranscodeToMp4/Header"))); + } + } + + if (enabledModules.AlwaysOnTop) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.Hotkey, SettingsResourceLoader.GetString("AlwaysOnTop/ModuleTitle"), SettingsResourceLoader.GetString("AlwaysOnTop_ShortDescription"))); + } + + if (enabledModules.ColorPicker) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.ActivationShortcut, SettingsResourceLoader.GetString("ColorPicker/ModuleTitle"), SettingsResourceLoader.GetString("ColorPicker_ShortDescription"))); + } + + if (enabledModules.CmdPal) + { + content.Append(HotkeySettingsToYaml(new CmdPalProperties().Hotkey, SettingsResourceLoader.GetString("CmdPal/ModuleTitle"))); + } + + if (enabledModules.CropAndLock) + { + CropAndLockProperties cropAndLockProperties = SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties; + content.Append(HotkeySettingsToYaml(cropAndLockProperties.ThumbnailHotkey, SettingsResourceLoader.GetString("CropAndLock/ModuleTitle"), SettingsResourceLoader.GetString("CropAndLock_Thumbnail"))); + content.Append(HotkeySettingsToYaml(cropAndLockProperties.ReparentHotkey, SettingsResourceLoader.GetString("CropAndLock/ModuleTitle"), SettingsResourceLoader.GetString("CropAndLock_Reparent"))); + } + + if (enabledModules.FancyZones) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.FancyzonesEditorHotkey, SettingsResourceLoader.GetString("FancyZones/ModuleTitle"), SettingsResourceLoader.GetString("FancyZones_OpenEditor"))); + } + + if (enabledModules.MouseHighlighter) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.ActivationShortcut, SettingsResourceLoader.GetString("MouseUtils_MouseHighlighter/Header"), SettingsResourceLoader.GetString("MouseHighlighter_ShortDescription"))); + } + + if (enabledModules.MouseJump) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.ActivationShortcut, SettingsResourceLoader.GetString("MouseUtils_MouseJump/Header"), SettingsResourceLoader.GetString("MouseJump_ShortDescription"))); + } + + if (enabledModules.MousePointerCrosshairs) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.ActivationShortcut, SettingsResourceLoader.GetString("MouseUtils_MousePointerCrosshairs/Header"), SettingsResourceLoader.GetString("MouseCrosshairs_ShortDescription"))); + } + + if (enabledModules.Peek) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.ActivationShortcut, SettingsResourceLoader.GetString("Peek/ModuleTitle"))); + } + + if (enabledModules.PowerLauncher) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.OpenPowerLauncher, SettingsResourceLoader.GetString("PowerLauncher/ModuleTitle"))); + } + + if (enabledModules.MeasureTool) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.ActivationShortcut, SettingsResourceLoader.GetString("MeasureTool/ModuleTitle"), SettingsResourceLoader.GetString("ScreenRuler_ShortDescription"))); + } + + if (enabledModules.ShortcutGuide) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.DefaultOpenShortcutGuide, SettingsResourceLoader.GetString("ShortcutGuide/ModuleTitle"), SettingsResourceLoader.GetString("ShortcutGuide_ShortDescription"))); + } + + if (enabledModules.PowerOcr) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.ActivationShortcut, SettingsResourceLoader.GetString("TextExtractor/ModuleTitle"), SettingsResourceLoader.GetString("PowerOcr_ShortDescription"))); + } + + if (enabledModules.Workspaces) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.Hotkey, SettingsResourceLoader.GetString("Workspaces/ModuleTitle"), SettingsResourceLoader.GetString("Workspaces_ShortDescription"))); + } + + content.Append(populateEndString); + + File.WriteAllText(path, content.ToString()); + } + + /// + /// Converts the hotkey settings to a YAML format string for the manifest file. + /// + /// Object containing a hotkey from the settings. + /// The name of the PowerToys module. + /// Description of the action. + /// Yaml code for the manifest file. + private static string HotkeySettingsToYaml(HotkeySettings hotkeySettings, string moduleName, string? description = null) + { + string content = string.Empty; + content += " - Name: " + moduleName + Environment.NewLine; + content += " Shortcut: " + Environment.NewLine; + content += " - Win: " + hotkeySettings.Win.ToString() + Environment.NewLine; + content += " Ctrl: " + hotkeySettings.Ctrl.ToString() + Environment.NewLine; + content += " Alt: " + hotkeySettings.Alt.ToString() + Environment.NewLine; + content += " Shift: " + hotkeySettings.Shift.ToString() + Environment.NewLine; + content += " Keys:" + Environment.NewLine; + content += " - " + hotkeySettings.Code.ToString(CultureInfo.InvariantCulture) + Environment.NewLine; + if (description != null) + { + content += " Description: " + description + Environment.NewLine; + } + + return content; + } + + /// + private static string HotkeySettingsToYaml(KeyboardKeysProperty hotkeySettings, string moduleName, string? description = null) + { + return HotkeySettingsToYaml(hotkeySettings.Value, moduleName, description); + } + + [GeneratedRegex(@"# [\s\S\n\r]*# ")] + private static partial Regex PopulateRegex(); + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/ResourceLoaderInstance.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/ResourceLoaderInstance.cs new file mode 100644 index 000000000000..f4e18161e9db --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/ResourceLoaderInstance.cs @@ -0,0 +1,27 @@ +// 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 Microsoft.Windows.ApplicationModel.Resources; + +namespace ShortcutGuide.Helpers +{ + internal static class ResourceLoaderInstance + { + /// + /// Gets the resource loader for the Shortcut Guide module. + /// + internal static ResourceLoader ResourceLoader { get; private set; } + + /// + /// Gets the resource loader for the Settings module. + /// + internal static ResourceLoader SettingsResourceLoader { get; private set; } + + static ResourceLoaderInstance() + { + ResourceLoader = new ResourceLoader("PowerToys.ShortcutGuide.pri"); + SettingsResourceLoader = new ResourceLoader("PowerToys.Settings.pri"); + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/TasklistPositions.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/TasklistPositions.cs new file mode 100644 index 000000000000..efbf4448411f --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/TasklistPositions.cs @@ -0,0 +1,44 @@ +// 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.Runtime.InteropServices; +using TasklistButton = ShortcutGuide.NativeMethods.TasklistButton; + +namespace ShortcutGuide.Helpers +{ + /// + /// Provides methods to retrieve the positions of taskbar buttons on the current monitor. + /// + internal static class TasklistPositions + { + /// + /// Retrieves the taskbar buttons for the current monitor. + /// + /// An array of the taskbar buttons. + public static TasklistButton[] GetButtons() + { + var monitor = NativeMethods.MonitorFromWindow(MainWindow.WindowHwnd, 0); + nint ptr = NativeMethods.GetTasklistButtons(monitor, out int size); + if (ptr == nint.Zero) + { + return []; + } + + if (size <= 0) + { + return []; + } + + TasklistButton[] buttons = new TasklistButton[size]; + nint currentPtr = ptr; + for (int i = 0; i < size; i++) + { + buttons[i] = Marshal.PtrToStructure(currentPtr); + currentPtr += Marshal.SizeOf(); + } + + return buttons; + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/IndexFile.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/IndexFile.cs new file mode 100644 index 000000000000..e8c64d813b82 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/IndexFile.cs @@ -0,0 +1,22 @@ +// 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 ShortcutGuide.Models +{ + public struct IndexFile + { + public struct IndexItem + { + public string WindowFilter { get; set; } + + public bool BackgroundProcess { get; set; } + + public string[] Apps { get; set; } + } + + public string DefaultShellName { get; set; } + + public IndexItem[] Index { get; set; } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutCategory.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutCategory.cs new file mode 100644 index 000000000000..0dcd3d1323af --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutCategory.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 ShortcutGuide.Models +{ + public struct ShortcutCategory + { + public string SectionName { get; set; } + + public ShortcutEntry[] Properties { get; set; } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutEntry.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutEntry.cs new file mode 100644 index 000000000000..911bb535b2bd --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutEntry.cs @@ -0,0 +1,378 @@ +// 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.Linq; +using System.Text.Json.Serialization; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.PowerToys.Settings.UI.Library.Utilities; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Markup; +using Windows.UI.Text; +using static ShortcutGuide.Models.ShortcutEntry; +using Orientation = Microsoft.UI.Xaml.Controls.Orientation; + +namespace ShortcutGuide.Models +{ + public class ShortcutEntry(string name, string? description, bool recommended, ShortcutDescription[] shortcutDescriptions) + { + public override bool Equals(object? obj) + { + return obj is ShortcutEntry other && Name == other.Name && + Description == other.Description && + Shortcut.Length == other.Shortcut.Length && + Shortcut.SequenceEqual(other.Shortcut); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public static bool operator ==(ShortcutEntry? left, ShortcutEntry? right) + { + return (left is null && right is null) || (left is not null && right is not null && left.Equals(right)); + } + + public static bool operator !=(ShortcutEntry? left, ShortcutEntry? right) + { + return !(left == right); + } + + public ShortcutEntry() + : this(string.Empty, string.Empty, false, []) + { + } + + [JsonPropertyName(nameof(Name))] + public string Name { get; set; } = name; + + [JsonPropertyName(nameof(Description))] + public string? Description { get; set; } = description; + + [JsonPropertyName(nameof(Recommended))] + public bool Recommended { get; set; } = recommended; + + [JsonPropertyName(nameof(Shortcut))] + public ShortcutDescription[] Shortcut { get; set; } = shortcutDescriptions; + + public class ShortcutDescription(bool ctrl, bool shift, bool alt, bool win, string[] keys) + { + public ShortcutDescription() + : this(false, false, false, false, []) + { + } + + [JsonPropertyName(nameof(Ctrl))] + public bool Ctrl { get; set; } = ctrl; + + [JsonPropertyName(nameof(Shift))] + public bool Shift { get; set; } = shift; + + [JsonPropertyName(nameof(Alt))] + public bool Alt { get; set; } = alt; + + [JsonPropertyName(nameof(Win))] + public bool Win { get; set; } = win; + + [JsonPropertyName(nameof(Keys))] + public string[] Keys { get; set; } = keys; + + public override bool Equals(object? obj) + { + return obj is ShortcutDescription other && Ctrl == other.Ctrl && + Shift == other.Shift && + Alt == other.Alt && + Win == other.Win && + Keys.SequenceEqual(other.Keys); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public static bool operator ==(ShortcutDescription? left, ShortcutDescription? right) + { + return (left is null && right is null) || (left is not null && right is not null && left.Equals(right)); + } + + public static bool operator !=(ShortcutDescription? left, ShortcutDescription? right) + { + return !(left == right); + } + } + + public static implicit operator ShortcutTemplateDataObject(ShortcutEntry shortcut) + { + List shortcutStackPanels = []; + + for (int i = 0; i < shortcut.Shortcut.Length; i++) + { + ShortcutDescription shortcutEntry = shortcut.Shortcut[i]; + StackPanel shortcutStackPanel = new() { Orientation = Orientation.Horizontal }; + shortcutStackPanels.Add(shortcutStackPanel); + + // If any entry is blank, we skip the whole shortcut + if (shortcutEntry is { Ctrl: false, Alt: false, Shift: false, Win: false, Keys.Length: 0 }) + { + return new ShortcutTemplateDataObject(shortcut.Name, shortcut.Description ?? string.Empty, shortcutStackPanel, shortcut); + } + + if (shortcut.Shortcut.Length > 1) + { + TextBlock shortcutIndexTextBlock = new() + { + Text = $"{i + 1}.", + Margin = new Thickness(3), + VerticalAlignment = VerticalAlignment.Center, + FontWeight = new FontWeight { Weight = 600 }, + }; + shortcutStackPanel.Children.Add(shortcutIndexTextBlock); + } + + if (shortcutEntry.Win) + { + PathIcon winIcon = (XamlReader.Load(@"") as PathIcon)!; + Viewbox winIconContainer = new() + { + Child = winIcon, + Height = 20, + Width = 20, + Margin = new Thickness(3, -1, 3, 2), + }; + shortcutStackPanel.Children.Add(winIconContainer); + } + + if (shortcutEntry.Ctrl) + { + AddNewTextToStackPanel("Ctrl"); + } + + if (shortcutEntry.Alt) + { + AddNewTextToStackPanel("Alt"); + } + + if (shortcutEntry.Shift) + { + AddNewTextToStackPanel("Shift"); + } + + foreach (string key in shortcutEntry.Keys) + { + AddKeyToStackPanel(key, shortcutStackPanel); + } + + continue; + + void AddNewTextToStackPanel(string text) + { + shortcutStackPanel.Children.Add(new TextBlock { Text = text, Margin = new Thickness(3), VerticalAlignment = VerticalAlignment.Center }); + } + } + + StackPanel stackPanelToReturn = shortcutStackPanels[0]; + + switch (shortcutStackPanels.Count) + { + case 0: + return new ShortcutTemplateDataObject(shortcut.Name, shortcut.Description ?? string.Empty, new StackPanel(), shortcut); + case <= 1: + return new ShortcutTemplateDataObject(shortcut.Name, shortcut.Description ?? string.Empty, stackPanelToReturn, shortcut); + default: + stackPanelToReturn = new StackPanel + { + Orientation = Orientation.Vertical, + }; + + foreach (StackPanel panel in shortcutStackPanels) + { + panel.Visibility = Visibility.Collapsed; + stackPanelToReturn.Children.Add(panel); + } + + shortcutStackPanels[0].Visibility = Visibility.Visible; + for (int i = 1; i < shortcutStackPanels.Count; i++) + { + shortcutStackPanels[i].Visibility = Visibility.Collapsed; + } + + AnimateStackPanels([.. shortcutStackPanels]); + + return new ShortcutTemplateDataObject(shortcut.Name, shortcut.Description ?? string.Empty, stackPanelToReturn, shortcut); + } + } + + /// + /// Transforms a key string into a visual representation in the stack panel. + /// + /// The string representation of the key. + /// The StackPanel to add the key to. + private static void AddKeyToStackPanel(string key, StackPanel shortcutStackPanel) + { + Microsoft.UI.Xaml.Media.FontFamily monospaceFont = new("Courier New"); + + switch (key) + { + case "": + shortcutStackPanel.Children.Add(new BitmapIcon() { UriSource = new("ms-appx:///Assets/ShortcutGuide/CopilotKey.png") }); + break; + case "": + shortcutStackPanel.Children.Add(new BitmapIcon() { UriSource = new("ms-appx:///Assets/ShortcutGuide/OfficeKey.png"), Height = 20, Width = 20 }); + break; + case "": + AddNewTextToStackPanel("←"); + break; + case "": + AddNewTextToStackPanel("→"); + break; + case "": + AddNewTextToStackPanel("↑"); + break; + case "": + AddNewTextToStackPanel("↓"); + break; + case "": + AddNewTextToStackPanel("..."); + break; + case "": + TextBlock animatedTextBlock = new() + { + Text = "A", + Margin = new Thickness(3, 3, 3, 1), + VerticalAlignment = VerticalAlignment.Center, + + // Use monospaced font to ensure the text doesn't move + FontFamily = monospaceFont, + TextDecorations = TextDecorations.Underline, + }; + + shortcutStackPanel.Children.Add(animatedTextBlock); + + AnimateTextBlock(animatedTextBlock, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + break; + case "": + TextBlock arrowTextBlock = new() + { + Text = "→", + Margin = new Thickness(3), + FontFamily = monospaceFont, + VerticalAlignment = VerticalAlignment.Center, + }; + + shortcutStackPanel.Children.Add(arrowTextBlock); + + AnimateTextBlock(arrowTextBlock, "→↓←↑", 1000); + break; + case "": + TextBlock arrowLRTextBlock = new() + { + Text = "→", + Margin = new Thickness(3), + FontFamily = monospaceFont, + VerticalAlignment = VerticalAlignment.Center, + }; + shortcutStackPanel.Children.Add(arrowLRTextBlock); + AnimateTextBlock(arrowLRTextBlock, "→←", 1000); + break; + case "": + TextBlock arrowUDTextBlock = new() + { + Text = "↑", + Margin = new Thickness(3), + FontFamily = monospaceFont, + VerticalAlignment = VerticalAlignment.Center, + }; + shortcutStackPanel.Children.Add(arrowUDTextBlock); + AnimateTextBlock(arrowUDTextBlock, "↑↓", 1000); + break; + case { } name when name.StartsWith('<') && name.EndsWith('>'): + AddNewTextToStackPanel(name[1..^1]); + break; + case { } num when int.TryParse(num, out int parsedNum): + if (parsedNum == 0) + { + break; + } + + AddNewTextToStackPanel(Helper.GetKeyName((uint)parsedNum)); + break; + default: + AddNewTextToStackPanel(key); + break; + } + + void AddNewTextToStackPanel(string text) + { + shortcutStackPanel.Children.Add(new TextBlock { Text = text, Margin = new Thickness(3), VerticalAlignment = VerticalAlignment.Center }); + } + } + + /// + /// Animates the text of a TextBlock by cycling through the characters of a given string. + /// + /// + /// This function runs asynchronously and will not block the UI thread. Exceptions that occur during the animation will be caught and ignored to prevent crashes. + /// + /// The textblock to animate. + /// The characters to cycle through. + /// The delay to the next animation frame. + private static async void AnimateTextBlock(TextBlock animatedTextBlock, string text, int delay = 500) + { + try + { + int index = 0; + CancellationToken cancellationToken = ShortcutView.AnimationCancellationTokenSource.Token; + + while (!cancellationToken.IsCancellationRequested) + { + animatedTextBlock.Text = text[index].ToString(); + index = (index + 1) % text.Length; + await Task.Delay(delay); + } + } + catch + { + // ignored + } + } + + /// + /// Animates the visibility of the stack panels one after another. + /// + /// + /// This function runs asynchronously and will not block the UI thread. Exceptions that occur during the animation will be caught and ignored to prevent crashes. + /// + /// The panels to animate. + /// The delay to the next animation frame. + private static async void AnimateStackPanels(StackPanel[] panels, int delay = 2000) + { + try + { + int index = 0; + CancellationToken cancellationToken = ShortcutView.AnimationCancellationTokenSource.Token; + + while (!cancellationToken.IsCancellationRequested) + { + foreach (StackPanel panel in panels) + { + panel.Visibility = Visibility.Collapsed; + } + + panels[index].Visibility = Visibility.Visible; + index = (index + 1) % panels.Length; + await Task.Delay(delay); + } + } + catch + { + // ignored + } + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutFile.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutFile.cs new file mode 100644 index 000000000000..46c114d6453c --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutFile.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. + +namespace ShortcutGuide.Models +{ + public struct ShortcutFile + { + public string PackageName { get; set; } + + public ShortcutCategory[] Shortcuts { get; set; } + + public string WindowFilter { get; set; } + + public bool BackgroundProcess { get; set; } + + public string Name { get; set; } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutPageParameters.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutPageParameters.cs new file mode 100644 index 000000000000..d88c4cda9134 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutPageParameters.cs @@ -0,0 +1,60 @@ +// 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; + +namespace ShortcutGuide.Models +{ + /// + /// Represents the parameters for the shortcut page in the Shortcut Guide module. + /// + internal struct ShortcutPageParameters + { + /// + /// Gets or sets the content of the search box. + /// + public static SearchFilterObservable SearchFilter = new(); + + /// + /// Gets or sets the pinned shortcuts for the Shortcut Guide. + /// + public static Dictionary> PinnedShortcuts = []; + + /// + /// Gets or sets the name of the current page being displayed in the Shortcut Guide. + /// + public static string CurrentPageName = string.Empty; + + /// + /// The height of the frame that displays the shortcuts. + /// + public static FrameHeightObservable FrameHeight = new(); + + internal sealed class SearchFilterObservable + { + public event EventHandler? FilterChanged; + + public void OnFilterChanged(string filter) + { + FilterChanged?.Invoke(this, filter); + } + } + + internal sealed class FrameHeightObservable + { + public event EventHandler? FrameHeightChanged; + + public void OnFrameHeightChanged(double height) + { + if (height <= 0) + { + return; + } + + FrameHeightChanged?.Invoke(this, height); + } + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/NativeMethods.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/NativeMethods.cs new file mode 100644 index 000000000000..e1d830dcc9c6 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/NativeMethods.cs @@ -0,0 +1,127 @@ +// 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.Runtime.InteropServices; +using Windows.Graphics; + +namespace ShortcutGuide; + +internal static partial class NativeMethods +{ + internal const int GWL_STYLE = -16; + internal const int WS_CAPTION = 0x00C00000; + + [LibraryImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags); + + [LibraryImport("user32.dll", SetLastError = true)] + internal static partial int GetWindowLongW(IntPtr hWnd, int nIndex); + + [LibraryImport("user32.dll")] + internal static partial int SetWindowLongW(IntPtr hWnd, int nIndex, int dwNewLong); + + [LibraryImport("user32.dll", StringMarshalling = StringMarshalling.Utf16)] + internal static partial IntPtr FindWindowA(in string lpClassName, in string? lpWindowName); + + [LibraryImport("User32.dll")] + internal static partial IntPtr MonitorFromWindow(IntPtr hwnd, int dwFlags); + + [LibraryImport("Shcore.dll")] + internal static partial long GetDpiForMonitor(IntPtr hmonitor, int dpiType, ref int dpiX, ref int dpiY); + + [LibraryImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool GetCursorPos(out POINT lpPoint); + + [LibraryImport("user32.dll")] + internal static partial IntPtr GetForegroundWindow(); + + [LibraryImport("user32.dll", SetLastError = true)] + internal static partial uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId); + + [LibraryImport("user32.dll")] + internal static partial short GetAsyncKeyState(int vKey); + + [LibraryImport("ShortcutGuide.CPPProject.dll", EntryPoint = "get_buttons")] + internal static partial IntPtr GetTasklistButtons(IntPtr monitor, out int size); + + [LibraryImport("ShortcutGuide.CPPProject.dll", EntryPoint = "IsCurrentWindowExcludedFromShortcutGuide")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool IsCurrentWindowExcludedFromShortcutGuide(); + + [LibraryImport("User32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumDelegate lpfnEnum, IntPtr dwData); + + internal delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData); + + internal struct LPARAM(IntPtr value) + { + internal IntPtr Value = value; + + public static implicit operator IntPtr(LPARAM lParam) + { + return lParam.Value; + } + + public static implicit operator LPARAM(IntPtr value) + { + return new LPARAM(value); + } + + public static implicit operator LPARAM(int value) + { + return new LPARAM(new IntPtr(value)); + } + + public static implicit operator int(LPARAM lParam) + { + return lParam.Value.ToInt32(); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct RECT + { + public int Left; + public int Top; + public int Right; + public int Bottom; + } + + internal struct POINT + { + internal int X; + internal int Y; + + public static implicit operator PointInt32(POINT point) + { + return new PointInt32(point.X, point.Y); + } + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct TasklistButton + { + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] + public string Name; + + public int X; + + public int Y; + + public int Width; + + public int Height; + + public int Keynum; + } + + public enum MonitorFromWindowDwFlags + { + MONITOR_DEFAULTTONEAREST = 2, + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Program.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Program.cs new file mode 100644 index 000000000000..61a61883b9d2 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Program.cs @@ -0,0 +1,84 @@ +// 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.Diagnostics; +using System.IO; +using System.Threading; +using System.Windows; +using ManagedCommon; +using Microsoft.UI.Dispatching; +using Microsoft.Windows.AppLifecycle; +using ShortcutGuide.Helpers; +using Application = Microsoft.UI.Xaml.Application; + +namespace ShortcutGuide +{ + public sealed class Program + { + private static readonly string[] InbuiltManifestFiles = [ + "+WindowsNT.Shell.en-US.yml", + "+WindowsNT.WindowsExplorer.en-US.yml", + "+WindowsNT.Notepad.en-US.yml", + "Microsoft.PowerToys.en-US.yml", + ]; + + [STAThread] + public static void Main() + { + if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredShortcutGuideEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled) + { + Logger.LogWarning("Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator."); + return; + } + + Directory.CreateDirectory(ManifestInterpreter.PathOfManifestFiles); + + if (NativeMethods.IsCurrentWindowExcludedFromShortcutGuide()) + { + return; + } + + // Todo: Only copy files after an update. + // Todo: Handle error + foreach (var file in InbuiltManifestFiles) + { + File.Copy(Path.GetDirectoryName(Environment.ProcessPath) + "\\Assets\\ShortcutGuide\\" + file, ManifestInterpreter.PathOfManifestFiles + "\\" + file, true); + } + + Process indexGeneration = Process.Start(Path.GetDirectoryName(Environment.ProcessPath) + "\\PowerToys.ShortcutGuide.IndexYmlGenerator.exe"); + indexGeneration.WaitForExit(); + if (indexGeneration.ExitCode != 0) + { + Logger.LogError("Index generation failed with exit code: " + indexGeneration.ExitCode); + MessageBox.Show($"Shortcut Guide encountered an error while generating the index file. There is likely a corrupt shortcuts file in \"{ManifestInterpreter.PathOfManifestFiles}\". Try deleting this directory.", "Error displaying shortcuts", MessageBoxButton.OK, MessageBoxImage.Error); + return; + } + + PowerToysShortcutsPopulator.Populate(); + + Logger.InitializeLogger("\\ShortcutGuide\\Logs"); + WinRT.ComWrappersSupport.InitializeComWrappers(); + + var instanceKey = AppInstance.FindOrRegisterForKey("PowerToys_ShortcutGuide_Instance"); + + if (instanceKey.IsCurrent) + { + Application.Start((p) => + { + var context = new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread()); + SynchronizationContext.SetSynchronizationContext(context); + _ = new App(); + }); + } + else + { + Logger.LogWarning("Another instance of ShortcutGuide is running. Exiting ShortcutGuide"); + } + + // Something prevents the process from exiting, so we need to kill it manually. + Process.GetCurrentProcess().Kill(); + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj new file mode 100644 index 000000000000..ef7b310e0ee1 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj @@ -0,0 +1,130 @@ + + + + + + + WinExe + ShortcutGuide + app.manifest + true + true + None + enable + false + false + true + ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps + PowerToys.ShortcutGuide + DISABLE_XAML_GENERATED_MAIN,TRACE + Assets\ShortcutGuide\ShortcutGuide.ico + + PowerToys.ShortcutGuide.pri + + + + + + + + + + + + + + + + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + tlbimp + 0 + 1 + 944de083-8fb8-45cf-bcb7-c477acb2f897 + 0 + false + true + + + + + + PowerToys.GPOWrapper + $(OutDir) + false + + + + + + + + + + + + + + + + + + + + + + + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + true + + diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/App.xaml b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/App.xaml new file mode 100644 index 000000000000..7856e3635637 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/App.xaml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/App.xaml.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/App.xaml.cs new file mode 100644 index 000000000000..923d8da42647 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/App.xaml.cs @@ -0,0 +1,29 @@ +// 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 Microsoft.UI.Xaml; + +namespace ShortcutGuide +{ + public partial class App + { + public App() + { + InitializeComponent(); + } + + protected override void OnLaunched(LaunchActivatedEventArgs args) + { + _window = new MainWindow(); + _window.Activate(); + _window.Closed += (_, _) => + { + _window = null; + Current.Exit(); + }; + } + + private Window? _window; + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/MainWindow.xaml b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/MainWindow.xaml new file mode 100644 index 000000000000..01bbfd2ea7ff --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/MainWindow.xaml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/MainWindow.xaml.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/MainWindow.xaml.cs new file mode 100644 index 000000000000..1f0c02791d48 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/MainWindow.xaml.cs @@ -0,0 +1,253 @@ +// 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.Collections.Generic; +using System.IO; +using System.Text.Json; +using Common.UI; +using ManagedCommon; +using Microsoft.PowerToys.Settings.UI.Library; +using Microsoft.UI; +using Microsoft.UI.Windowing; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Input; +using ShortcutGuide.Helpers; +using ShortcutGuide.Models; +using Windows.Foundation; +using Windows.Graphics; +using Windows.System; +using WinUIEx; +using static ShortcutGuide.NativeMethods; + +namespace ShortcutGuide +{ + /// + /// An empty window that can be used on its own or navigated to within a Frame. + /// + public sealed partial class MainWindow + { + private readonly string[] _currentApplicationIds; + private readonly bool _firstRun; + + public static nint WindowHwnd { get; set; } + + private AppWindow _appWindow; + private bool _setPosition; + + public MainWindow() + { + _currentApplicationIds = ManifestInterpreter.GetAllCurrentApplicationIds(); + + InitializeComponent(); + + Title = ResourceLoaderInstance.ResourceLoader.GetString("Title")!; + + var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this); + WindowHwnd = hwnd; + WindowId windowId = Win32Interop.GetWindowIdFromWindow(hwnd); + _appWindow = AppWindow.GetFromWindowId(windowId); +#if !DEBUG + this.SetIsAlwaysOnTop(true); + this.SetIsShownInSwitchers(false); +#endif + this.SetIsResizable(false); + this.SetIsMinimizable(false); + this.SetIsMaximizable(false); + IsTitleBarVisible = false; + + // Remove the caption style from the window style. Windows App SDK 1.6 added it, which made the title bar and borders appear. This code removes it. + var windowStyle = GetWindowLongW(hwnd, GWL_STYLE); + windowStyle &= ~WS_CAPTION; + _ = SetWindowLongW(hwnd, GWL_STYLE, windowStyle); + + Activated += Window_Activated; + + SettingsUtils settingsUtils = new(); + + if (settingsUtils.SettingsExists(ShortcutGuideSettings.ModuleName, "Pinned.json")) + { + string pinnedPath = settingsUtils.GetSettingsFilePath(ShortcutGuideSettings.ModuleName, "Pinned.json"); + ShortcutPageParameters.PinnedShortcuts = JsonSerializer.Deserialize>>(File.ReadAllText(pinnedPath))!; + } + + Content.KeyUp += (_, e) => + { + if (e.Key == VirtualKey.Escape) + { + Close(); + } + }; + + ShortcutGuideSettings shortcutGuideSettings = SettingsRepository.GetInstance(settingsUtils).SettingsConfig; + ShortcutGuideProperties shortcutGuideProperties = shortcutGuideSettings.Properties; + + switch (shortcutGuideProperties.Theme.Value) + { + case "dark": + ((FrameworkElement)Content).RequestedTheme = ElementTheme.Dark; + MainPage.RequestedTheme = ElementTheme.Dark; + MainPage.Background = new Microsoft.UI.Xaml.Media.SolidColorBrush(Windows.UI.Color.FromArgb(255, 0, 0, 0)); + break; + case "light": + ((FrameworkElement)Content).RequestedTheme = ElementTheme.Light; + MainPage.RequestedTheme = ElementTheme.Light; + MainPage.Background = new Microsoft.UI.Xaml.Media.SolidColorBrush(Windows.UI.Color.FromArgb(255, 255, 255, 255)); + break; + case "system": + // Ignore, as the theme will be set by the system. + break; + default: + Logger.LogError("Invalid theme value in settings: " + shortcutGuideProperties.Theme.Value); + break; + } + + _firstRun = shortcutGuideProperties.FirstRun.Value; + shortcutGuideProperties.FirstRun = new BoolProperty(false); +#pragma warning disable CA1869 // Cache and reuse 'JsonSerializerOptions' instances + settingsUtils.SaveSettings(JsonSerializer.Serialize(shortcutGuideSettings, new JsonSerializerOptions { WriteIndented = true }), "Shortcut Guide"); +#pragma warning restore CA1869 // Cache and reuse 'JsonSerializerOptions' instances + } + + private void Window_Activated(object sender, WindowActivatedEventArgs e) + { + if (e.WindowActivationState == WindowActivationState.Deactivated) + { +#if !DEBUG + Close(); +#endif + } + + // The code below sets the position of the window to the center of the monitor, but only if it hasn't been set before. + if (!_setPosition) + { + var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this); + WindowId windowId = Win32Interop.GetWindowIdFromWindow(hwnd); + + _appWindow = AppWindow.GetFromWindowId(windowId); + + GetCursorPos(out POINT lpPoint); + _appWindow.Move(new POINT { Y = lpPoint.Y - ((int)Height / 2), X = lpPoint.X - ((int)Width / 2) }); + + float dpiScale = DpiHelper.GetDPIScaleForWindow((int)hwnd); + + Rect monitorRect = DisplayHelper.GetWorkAreaForDisplayWithWindow(hwnd); + this.SetWindowSize(monitorRect.Width / dpiScale, monitorRect.Height / dpiScale / 2); + + // Move top of the window to the center of the monitor + _appWindow.Move(new PointInt32((int)monitorRect.X, (int)(monitorRect.Y + (int)(monitorRect.Height / 2)))); + _setPosition = true; + AppWindow.Changed += (_, a) => + { + if (!a.DidPresenterChange) + { + return; + } + + Rect monitorRect = DisplayHelper.GetWorkAreaForDisplayWithWindow(hwnd); + float dpiScale = DpiHelper.GetDPIScaleForWindow((int)hwnd); + this.SetWindowSize(monitorRect.Width / dpiScale, monitorRect.Height / dpiScale / 2); + _appWindow.Move(new PointInt32((int)monitorRect.X, (int)(monitorRect.Y + (int)(monitorRect.Height / 2)))); + }; + } + + // Populate the window selector with the current application IDs if it is empty. + if (WindowSelector.Items.Count == 0) + { + foreach (var item in _currentApplicationIds) + { + if (item == ManifestInterpreter.GetIndexYamlFile().DefaultShellName) + { + WindowSelector.Items.Add(new SelectorBarItem { Name = item, Text = "Windows", Icon = new FontIcon() { Glyph = "\xE770" } }); + } + else + { + try + { + WindowSelector.Items.Add(new SelectorBarItem { Name = item, Text = ManifestInterpreter.GetShortcutsOfApplication(item).Name, Icon = new FontIcon { Glyph = "\uEB91" } }); + } + catch (IOException) + { + } + } + } + + if (_firstRun) + { + CreateAndOpenWelcomePage(); + } + + WindowSelector.SelectedItem = WindowSelector.Items[0]; + } + } + + public void WindowSelectionChanged(object sender, SelectorBarSelectionChangedEventArgs e) + { + string newPageName = ((SelectorBar)sender).SelectedItem.Name; + + if (newPageName == "") + { + ContentFrame.Navigate(typeof(OOBEView)); + return; + } + + ShortcutPageParameters.CurrentPageName = newPageName; + + ContentFrame.Loaded += (_, _) => ShortcutPageParameters.FrameHeight.OnFrameHeightChanged(ContentFrame.ActualHeight); + + ContentFrame.Navigate(typeof(ShortcutView)); + + // I don't know why this has to be called again, but it does. + ShortcutPageParameters.FrameHeight.OnFrameHeightChanged(ContentFrame.ActualHeight); + } + + public void CloseButton_Clicked(object sender, RoutedEventArgs e) + { + ShortcutView.AnimationCancellationTokenSource.Cancel(); + Close(); + } + + private void SearchBox_TextChanged(object sender, TextChangedEventArgs e) + { + ShortcutPageParameters.SearchFilter.OnFilterChanged(SearchBox.Text); + } + + private void SearchBox_KeyboardAcceleratorInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) + { + SearchBox.Focus(FocusState.Programmatic); + } + + private void SettingsButton_Clicked(object sender, RoutedEventArgs e) + { + SettingsDeepLink.OpenSettings(SettingsDeepLink.SettingsWindow.ShortcutGuide, true); + } + + private void InformationButton_Click(object sender, RoutedEventArgs e) + { + InformationTip.IsOpen = !InformationTip.IsOpen; + } + + private void HyperlinkButton_Click(object sender, RoutedEventArgs e) + { + InformationTip.IsOpen = false; + if (WindowSelector.Items[0].Name == "") + { + WindowSelector.SelectedItem = WindowSelector.Items[0]; + } + else + { + CreateAndOpenWelcomePage(); + } + } + + /// + /// Adds the welcome page to the window selector and opens it. + /// + private void CreateAndOpenWelcomePage() + { + WindowSelector.Items.Insert(0, new SelectorBarItem { Name = "", Text = ResourceLoaderInstance.ResourceLoader.GetString("Welcome"), Icon = new FontIcon { Glyph = "\uE789" } }); + WindowSelector.SelectedItem = WindowSelector.Items[0]; + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/OOBEView.xaml b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/OOBEView.xaml new file mode 100644 index 000000000000..429cb8fe70ae --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/OOBEView.xaml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/OOBEView.xaml.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/OOBEView.xaml.cs new file mode 100644 index 000000000000..51fa26216f6b --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/OOBEView.xaml.cs @@ -0,0 +1,102 @@ +// 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 Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Media.Animation; + +namespace ShortcutGuide +{ + public sealed partial class OOBEView : Page + { + public OOBEView() + { + InitializeComponent(); + + /*SizeChanged += (_, _) => + { + HeroImageCompositeTransform.TranslateX = ActualWidth - 1350; + };*/ + + HeroImage.ImageSource = ActualTheme == ElementTheme.Dark + ? new Microsoft.UI.Xaml.Media.Imaging.BitmapImage(new Uri("ms-appx:///Assets/ShortcutGuide/HeroImage-dark.png")) + : new Microsoft.UI.Xaml.Media.Imaging.BitmapImage(new Uri("ms-appx:///Assets/ShortcutGuide/HeroImage.png")); + + ActualThemeChanged += (_, _) => + { + HeroImage.ImageSource = ActualTheme == ElementTheme.Dark + ? new Microsoft.UI.Xaml.Media.Imaging.BitmapImage(new Uri("ms-appx:///Assets/ShortcutGuide/HeroImage-dark.png")) + : new Microsoft.UI.Xaml.Media.Imaging.BitmapImage(new Uri("ms-appx:///Assets/ShortcutGuide/HeroImage.png")); + }; + + Loaded += (_, _) => AnimateStackPanelChildren(MainStackPanel); + } + + /// + /// Animates the children of a StackPanel by fading them in and translating them from the left. + /// + /// The StackPanel to animate. + private void AnimateStackPanelChildren(StackPanel panel) + { + Storyboard storyboard = new(); + double delay = 0.0; + Duration duration = new(TimeSpan.FromSeconds(0.3)); + + foreach (UIElement child in panel.Children) + { + if (child is not FrameworkElement childFrameworkElement || string.IsNullOrEmpty(childFrameworkElement.Name)) + { + continue; + } + + if (child.RenderTransform is null or not CompositeTransform) + { + child.RenderTransform = new CompositeTransform { TranslateX = -30 }; + child.RenderTransformOrigin = new Windows.Foundation.Point(0.5, 0.5); + } + + child.Opacity = 0; + + DoubleAnimation opacityAnimation = new() + { + From = 0, + To = 1, + Duration = duration, + BeginTime = TimeSpan.FromSeconds(delay), + }; + Storyboard.SetTarget(opacityAnimation, childFrameworkElement); + Storyboard.SetTargetProperty(opacityAnimation, "Opacity"); + storyboard.Children.Add(opacityAnimation); + + DoubleAnimation translateAnimation = new() + { + From = -30, + To = 0, + Duration = duration, + BeginTime = TimeSpan.FromSeconds(delay), + }; + Storyboard.SetTarget(translateAnimation, childFrameworkElement); + Storyboard.SetTargetProperty(translateAnimation, "(UIElement.RenderTransform).(CompositeTransform.TranslateX)"); + storyboard.Children.Add(translateAnimation); + + delay += 0.2; + } + + DoubleAnimation heroImageTranslateAnimation = new() + { + From = ActualWidth, + To = ActualWidth - 1350, + Duration = duration, + BeginTime = TimeSpan.FromSeconds(delay), + }; + Storyboard.SetTarget(heroImageTranslateAnimation, HeroImage); + Storyboard.SetTargetProperty(heroImageTranslateAnimation, "(ImageBrush.Transform).(CompositeTransform.TranslateX)"); + storyboard.Children.Add(heroImageTranslateAnimation); + + storyboard.Begin(); + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/ShortcutTemplateDataObject.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/ShortcutTemplateDataObject.cs new file mode 100644 index 000000000000..6186b7172769 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/ShortcutTemplateDataObject.cs @@ -0,0 +1,35 @@ +// 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 Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using ShortcutGuide.Models; + +namespace ShortcutGuide +{ + public sealed class ShortcutTemplateDataObject + { + public string Name { get; set; } + + public string Description { get; set; } + + public StackPanel Shortcut { get; set; } + + public ShortcutEntry OriginalShortcutObject { get; set; } + + public Visibility DescriptionVisible { get; set; } + + public ShortcutTemplateDataObject(string name, string description, StackPanel shortcut, ShortcutEntry originalShortcutObject) + { + Name = name; + Description = description; + OriginalShortcutObject = originalShortcutObject; + + DescriptionVisible = string.IsNullOrWhiteSpace(description) ? Visibility.Collapsed : Visibility.Visible; + + shortcut.Orientation = Orientation.Horizontal; + Shortcut = shortcut; + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/ShortcutView.xaml b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/ShortcutView.xaml new file mode 100644 index 000000000000..844dcc146a93 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/ShortcutView.xaml @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/ShortcutView.xaml.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/ShortcutView.xaml.cs new file mode 100644 index 000000000000..2808d03a4efa --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/ShortcutView.xaml.cs @@ -0,0 +1,383 @@ +// 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.Globalization; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text.Json; +using System.Threading; +using System.Windows.Controls; +using Microsoft.PowerToys.Settings.UI.Library; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media.Animation; +using ShortcutGuide.Helpers; +using ShortcutGuide.Models; +using Windows.Foundation; +using Grid = Microsoft.UI.Xaml.Controls.Grid; + +namespace ShortcutGuide +{ + public sealed partial class ShortcutView : INotifyPropertyChanged + { + private readonly DispatcherTimer _taskbarIconsUpdateTimer = new() { Interval = TimeSpan.FromMilliseconds(500) }; + private readonly ShortcutFile _shortcutList = ManifestInterpreter.GetShortcutsOfApplication(ShortcutPageParameters.CurrentPageName); + private bool _showTaskbarShortcuts; + private static CancellationTokenSource _animationCancellationTokenSource = new(); + + /// + /// Gets or sets a cancellation token source for animations in shortcut view. + /// When setting a new token source, the previous one is cancelled to stop ongoing animations. + /// + public static CancellationTokenSource AnimationCancellationTokenSource + { + get => _animationCancellationTokenSource; + set + { + _animationCancellationTokenSource?.Cancel(); + _animationCancellationTokenSource = value; + } + } + + public ShortcutView() + { + InitializeComponent(); + DataContext = this; + + // Stop any ongoing animations by cancelling the previous token source + AnimationCancellationTokenSource = new(); + + try + { + PopulateCategorySelector(); + + CategorySelector.SelectedItem = CategorySelector.Items[0]; + CategorySelector.SelectionChanged += CategorySelector_SelectionChanged; + + foreach (var shortcut in _shortcutList.Shortcuts[0].Properties) + { + ShortcutListElement.Items.Add((ShortcutTemplateDataObject)shortcut); + } + + ShortcutPageParameters.FrameHeight.FrameHeightChanged += ContentHeightChanged; + ShortcutPageParameters.SearchFilter.FilterChanged += SearchFilter_FilterChanged; + + if (!ShortcutPageParameters.PinnedShortcuts.TryGetValue(ShortcutPageParameters.CurrentPageName, out var _)) + { + ShortcutPageParameters.PinnedShortcuts.Add(ShortcutPageParameters.CurrentPageName, []); + } + + if (_showTaskbarShortcuts) + { + TaskbarIndicators.Visibility = Visibility.Visible; + ShortcutsScrollViewer.Margin = new Thickness(0, 0, 0, 20); + _taskbarIconsUpdateTimer.Tick += UpdateTaskbarIndicators; + _taskbarIconsUpdateTimer.Start(); + } + + OpenOverview(); + } + catch (Exception) + { + OverviewStackPanel.Visibility = Visibility.Collapsed; + ErrorMessage.Visibility = Visibility.Visible; + ErrorMessage.Text = ResourceLoaderInstance.ResourceLoader.GetString("ErrorInAppParsing"); + } + + Unloaded += (_, _) => + { + _taskbarIconsUpdateTimer.Tick -= UpdateTaskbarIndicators; + _taskbarIconsUpdateTimer.Stop(); + }; + } + + /// + /// Populates the selector and sets . + /// + private void PopulateCategorySelector() + { + int i = -1; + CategorySelector.Items.Add(new SelectorBarItem() + { + Text = ResourceLoaderInstance.ResourceLoader.GetString("Overview"), + Name = i.ToString(CultureInfo.InvariantCulture), + }); + + i++; + + foreach (var category in _shortcutList.Shortcuts) + { + switch (category.SectionName) + { + case { } name when name.StartsWith("", StringComparison.Ordinal): + _showTaskbarShortcuts = true; + break; + case { } name when name.StartsWith('<') && name.EndsWith('>'): + break; + default: + CategorySelector.Items.Add(new SelectorBarItem() { Text = category.SectionName, Name = i.ToString(CultureInfo.InvariantCulture) }); + break; + } + + i++; + } + } + + /// + /// Updates the taskbar indicators. + /// + private void UpdateTaskbarIndicators(object? sender, object? e) + { + NativeMethods.TasklistButton[] buttons = TasklistPositions.GetButtons(); + + for (int i = 0; i < TaskbarIndicators.Children.Count; i++) + { + if (i < buttons.Length) + { + TaskbarIndicators.Children[i].Visibility = Visibility.Visible; + Rect workArea = DisplayHelper.GetWorkAreaForDisplayWithWindow(MainWindow.WindowHwnd); + DoubleAnimation animation = new() + { + To = (buttons[i].X - workArea.Left) / DpiHelper.GetDPIScaleForWindow(MainWindow.WindowHwnd.ToInt32()), + Duration = TimeSpan.FromMilliseconds(500), + }; + + // Create the storyboard + Storyboard storyboard = new(); + storyboard.Children.Add(animation); + + // Set the target and property + Storyboard.SetTarget(animation, TaskbarIndicators.Children[i]); + Storyboard.SetTargetProperty(animation, "(Canvas.Left)"); + + // Start the animation + storyboard.Begin(); + + ((TaskbarIndicator)TaskbarIndicators.Children[i]).Width = buttons[i].Width / DpiHelper.GetDPIScaleForWindow(MainWindow.WindowHwnd.ToInt32()); + ((TaskbarIndicator)TaskbarIndicators.Children[i]).Height = buttons[i].Height / DpiHelper.GetDPIScaleForWindow(MainWindow.WindowHwnd.ToInt32()); + + continue; + } + + TaskbarIndicators.Children[i].Visibility = Visibility.Collapsed; + } + } + + private void OnPropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + private double _contentHeight; + + public event PropertyChangedEventHandler? PropertyChanged; + + public double ContentHeight + { + get => _contentHeight - CategorySelector.ActualHeight; + set + { + _contentHeight = value; + OnPropertyChanged(); + } + } + + public void ContentHeightChanged(object? sender, double e) + { + ContentHeight = e; + } + + private void OpenOverview() + { + RecommendedListElement.Items.Clear(); + PinnedListElement.Items.Clear(); + OverviewStackPanel.Visibility = Visibility.Visible; + RecommendedListElement.Visibility = Visibility.Visible; + PinnedListElement.Visibility = Visibility.Visible; + RecommendedListTitle.Visibility = Visibility.Visible; + PinnedListTitle.Visibility = Visibility.Visible; + ShortcutListElement.Visibility = Visibility.Collapsed; + + foreach (var shortcut in _shortcutList.Shortcuts.SelectMany(list => list.Properties.Where(s => s.Recommended))) + { + RecommendedListElement.Items.Add((ShortcutTemplateDataObject)shortcut); + } + + if (RecommendedListElement.Items.Count == 0) + { + RecommendedListTitle.Visibility = Visibility.Collapsed; + RecommendedListElement.Visibility = Visibility.Collapsed; + } + + foreach (var shortcut in ShortcutPageParameters.PinnedShortcuts[ShortcutPageParameters.CurrentPageName]) + { + PinnedListElement.Items.Add((ShortcutTemplateDataObject)shortcut); + } + + if (PinnedListElement.Items.Count == 0) + { + PinnedListTitle.Visibility = Visibility.Collapsed; + PinnedListElement.Visibility = Visibility.Collapsed; + } + + if (RecommendedListElement.Items.Count == 0 && PinnedListElement.Items.Count == 0) + { + OverviewStackPanel.Visibility = Visibility.Collapsed; + ErrorMessage.Visibility = Visibility.Visible; + ErrorMessage.Text = ResourceLoaderInstance.ResourceLoader.GetString("NoShortcutsInOverview"); + } + + if (_showTaskbarShortcuts) + { + TaskbarIndicators.Visibility = Visibility.Visible; + ShortcutsScrollViewer.Margin = new Thickness(0, 0, 0, 20); + TaskbarLaunchShortcutsListElement.Visibility = Visibility.Visible; + TaskbarLaunchShortcutsListElement.Items.Clear(); + TaskbarLaunchShortcutsTitle.Visibility = Visibility.Visible; + foreach (var item in _shortcutList.Shortcuts.First(x => x.SectionName.StartsWith("", StringComparison.InvariantCulture)).Properties) + { + TaskbarLaunchShortcutsListElement.Items.Add((ShortcutTemplateDataObject)item); + } + + return; + } + + TaskbarLaunchShortcutsListElement.Visibility = Visibility.Collapsed; + TaskbarLaunchShortcutsTitle.Visibility = Visibility.Collapsed; + TaskbarLaunchShortcutsDescription.Visibility = Visibility.Collapsed; + } + + private string _searchFilter = string.Empty; + + private void SearchFilter_FilterChanged(object? sender, string e) + { + FilterBy(e); + _searchFilter = e; + } + + public void FilterBy(string filter) + { + TaskbarIndicators.Visibility = Visibility.Collapsed; + ShortcutsScrollViewer.Margin = new Thickness(0); + ShortcutListElement.Items.Clear(); + ShortcutListElement.Visibility = Visibility.Visible; + ErrorMessage.Visibility = Visibility.Collapsed; + + if (int.Parse(CategorySelector.SelectedItem.Name, CultureInfo.InvariantCulture) == -1) + { + if (string.IsNullOrWhiteSpace(filter)) + { + OpenOverview(); + return; + } + + OverviewStackPanel.Visibility = Visibility.Collapsed; + foreach (var shortcut in _shortcutList.Shortcuts.SelectMany(list => list.Properties.Where(s => s.Name.Contains(filter, StringComparison.InvariantCultureIgnoreCase)))) + { + ShortcutListElement.Items.Add((ShortcutTemplateDataObject)shortcut); + } + } + else + { + foreach (var shortcut in _shortcutList.Shortcuts[int.Parse(CategorySelector.SelectedItem.Name, CultureInfo.InvariantCulture)].Properties.Where(s => s.Name.Contains(filter, StringComparison.InvariantCultureIgnoreCase))) + { + ShortcutListElement.Items.Add((ShortcutTemplateDataObject)shortcut); + } + } + + if (ShortcutListElement.Items.Count != 0) + { + return; + } + + ShortcutListElement.Visibility = Visibility.Collapsed; + ErrorMessage.Visibility = Visibility.Visible; + ErrorMessage.Text = ResourceLoaderInstance.ResourceLoader.GetString("SearchBlank"); + } + + public void CategorySelector_SelectionChanged(SelectorBar sender, SelectorBarSelectionChangedEventArgs e) + { + ShortcutListElement.Items.Clear(); + RecommendedListElement.Items.Clear(); + PinnedListElement.Items.Clear(); + TaskbarLaunchShortcutsListElement.Items.Clear(); + ErrorMessage.Visibility = Visibility.Collapsed; + RecommendedListElement.Visibility = Visibility.Collapsed; + PinnedListElement.Visibility = Visibility.Collapsed; + OverviewStackPanel.Visibility = Visibility.Collapsed; + TaskbarLaunchShortcutsListElement.Visibility = Visibility.Collapsed; + TaskbarLaunchShortcutsTitle.Visibility = Visibility.Collapsed; + TaskbarLaunchShortcutsDescription.Visibility = Visibility.Collapsed; + ShortcutListElement.Visibility = Visibility.Visible; + TaskbarIndicators.Visibility = Visibility.Collapsed; + ShortcutsScrollViewer.Margin = new Thickness(0); + + try + { + if (int.Parse(sender.SelectedItem.Name, CultureInfo.InvariantCulture) == -1) + { + OpenOverview(); + FilterBy(_searchFilter); + return; + } + + foreach (var shortcut in _shortcutList.Shortcuts[int.Parse(sender.SelectedItem.Name, CultureInfo.InvariantCulture)].Properties) + { + ShortcutListElement.Items.Add((ShortcutTemplateDataObject)shortcut); + } + } + catch (NullReferenceException) + { + ErrorMessage.Visibility = Visibility.Visible; + ErrorMessage.Text = ResourceLoaderInstance.ResourceLoader.GetString("ErrorInCategoryParsing"); + } + + FilterBy(_searchFilter); + } + + private void PinShortcut(object sender, RoutedEventArgs e) + { + if (ShortcutPageParameters.PinnedShortcuts[ShortcutPageParameters.CurrentPageName].Contains(((ShortcutTemplateDataObject)((MenuFlyoutItem)sender).DataContext).OriginalShortcutObject)) + { + ShortcutPageParameters.PinnedShortcuts[ShortcutPageParameters.CurrentPageName].Remove(((ShortcutTemplateDataObject)((MenuFlyoutItem)sender).DataContext).OriginalShortcutObject); + } + else + { + ShortcutPageParameters.PinnedShortcuts[ShortcutPageParameters.CurrentPageName].Add(((ShortcutTemplateDataObject)((MenuFlyoutItem)sender).DataContext).OriginalShortcutObject); + } + + if (int.Parse(CategorySelector.SelectedItem.Name, CultureInfo.InvariantCulture) == -1) + { + OpenOverview(); + } + + string serialized = JsonSerializer.Serialize(ShortcutPageParameters.PinnedShortcuts); + + SettingsUtils settingsUtils = new(); + string pinnedPath = settingsUtils.GetSettingsFilePath(ShortcutGuideSettings.ModuleName, "Pinned.json"); + File.WriteAllText(pinnedPath, serialized); + } + + private void MenuFlyout_Opening(object sender, object e) + { + if (sender is not MenuFlyout menu || + menu.Target is not Grid parentGrid || + parentGrid.DataContext is not ShortcutTemplateDataObject dataObject || + menu.Items[0] is not MenuFlyoutItem pinItem) + { + return; + } + + ShortcutEntry originalObject = dataObject.OriginalShortcutObject; + + bool isItemPinned = ShortcutPageParameters.PinnedShortcuts[ShortcutPageParameters.CurrentPageName].Any(x => x.Equals(originalObject)); + + pinItem.Text = isItemPinned ? ResourceLoaderInstance.ResourceLoader.GetString("UnpinShortcut") : ResourceLoaderInstance.ResourceLoader.GetString("PinShortcut"); + pinItem.Icon = new SymbolIcon(isItemPinned ? Symbol.UnPin : Symbol.Pin); + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/TaskbarIndicator.xaml b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/TaskbarIndicator.xaml new file mode 100644 index 000000000000..f7e8d56bb7a8 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/TaskbarIndicator.xaml @@ -0,0 +1,28 @@ + + + + + + + + + + diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/TaskbarIndicator.xaml.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/TaskbarIndicator.xaml.cs new file mode 100644 index 000000000000..0945c848fcaa --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/TaskbarIndicator.xaml.cs @@ -0,0 +1,52 @@ +// 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.Globalization; +using Microsoft.UI.Xaml.Controls; + +namespace ShortcutGuide; + +public sealed partial class TaskbarIndicator : UserControl +{ + private int _indicatorNumber; + + public int IndicatorNumber + { + get => _indicatorNumber; + set + { + _indicatorNumber = value; + IndicatorText.Text = value > 9 ? "0" : value.ToString(CultureInfo.InvariantCulture); + } + } + + public new double Width + { + get => (double)GetValue(WidthProperty); + set + { + SetValue(WidthProperty, value); + IndicatorText.Width = Width; + IndicatorCanvas.Width = Width; + IndicatorRectangle.Width = Width; + } + } + + public new double Height + { + get => (double)GetValue(HeightProperty); + set + { + SetValue(HeightProperty, value); + IndicatorText.Height = Height; + IndicatorCanvas.Height = Height; + IndicatorRectangle.Height = Height; + } + } + + public TaskbarIndicator() + { + InitializeComponent(); + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Resources.resx b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Strings/en-us/Resources.resw similarity index 56% rename from src/modules/ShortcutGuide/ShortcutGuide/Resources.resx rename to src/modules/ShortcutGuide/ShortcutGuide.Ui/Strings/en-us/Resources.resw index 34942e7a5295..6514f775bef3 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/Resources.resx +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Strings/en-us/Resources.resw @@ -1,4 +1,4 @@ - + + true/PM + PerMonitorV2, PerMonitor + + + + + + + + + diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/0.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/0.svg deleted file mode 100644 index b797f8b7ef73..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/0.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/1.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/1.svg deleted file mode 100644 index 6e1e3d28f146..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/1.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/2.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/2.svg deleted file mode 100644 index 5183242c1b2e..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/2.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/3.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/3.svg deleted file mode 100644 index 63ad68f061df..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/3.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/4.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/4.svg deleted file mode 100644 index 4f7e36f8bd7b..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/4.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/5.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/5.svg deleted file mode 100644 index 0fd52f658589..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/5.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/6.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/6.svg deleted file mode 100644 index 00303cdb3208..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/6.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/7.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/7.svg deleted file mode 100644 index cce8aedbaa61..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/7.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/8.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/8.svg deleted file mode 100644 index 57f4856b6b90..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/8.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/9.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/9.svg deleted file mode 100644 index 2e0f33c493ff..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/9.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/no_active_window.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/no_active_window.svg deleted file mode 100644 index 69d390bd081b..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/no_active_window.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/overlay.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/overlay.svg deleted file mode 100644 index fff83ab395ee..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/overlay.svg +++ /dev/null @@ -1,205 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/overlay_portrait.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/overlay_portrait.svg deleted file mode 100644 index 82c03149733b..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/overlay_portrait.svg +++ /dev/null @@ -1,208 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/PropertySheet.props b/src/modules/ShortcutGuide/ShortcutGuide/PropertySheet.props deleted file mode 100644 index b0c622690fed..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/PropertySheet.props +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.base.rc b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.base.rc deleted file mode 100644 index 1f88309dd2dc..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.base.rc +++ /dev/null @@ -1,54 +0,0 @@ -#include -#include "Generated Files/resource.h" -#include "../../../../common/version/version.h" - -#define APSTUDIO_READONLY_SYMBOLS -#include "winres.h" -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// -// Icon -// - -// Icon with lowest ID value placed first to ensure application icon -// remains consistent on all systems. -IDI_ICON1 ICON "Shortcut-Guide.ico" - - ///////////////////////////////////////////////////////////////////////////// -// -// Version -// - -VS_VERSION_INFO VERSIONINFO - FILEVERSION FILE_VERSION - PRODUCTVERSION PRODUCT_VERSION - FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -#ifdef _DEBUG - FILEFLAGS VS_FF_DEBUG -#else - FILEFLAGS 0x0L -#endif - FILEOS VOS_NT_WINDOWS32 - FILETYPE VFT_DLL - FILESUBTYPE VFT2_UNKNOWN -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904b0" - BEGIN - VALUE "CompanyName", COMPANY_NAME - VALUE "FileDescription", FILE_DESCRIPTION - VALUE "FileVersion", FILE_VERSION_STRING - VALUE "InternalName", INTERNAL_NAME - VALUE "LegalCopyright", COPYRIGHT_NOTE - VALUE "OriginalFilename", ORIGINAL_FILENAME - VALUE "ProductName", PRODUCT_NAME - VALUE "ProductVersion", PRODUCT_VERSION_STRING - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1200 - END -END \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.exe.manifest b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.exe.manifest deleted file mode 100644 index 4747d3bd23dd..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.exe.manifest +++ /dev/null @@ -1,9 +0,0 @@ - - - - - true/PM - PerMonitorV2 - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj deleted file mode 100644 index 045be94f2bda..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj +++ /dev/null @@ -1,193 +0,0 @@ - - - - - - - - true - true - true - true - 15.0 - {2edb3eb4-fa92-4bff-b2d8-566584837231} - Win32Proj - ShortcutGuide - - - - Application - v143 - v141 - v142 - Unicode - - - true - true - - - false - true - false - - - - - - - - - - - - - - - PowerToys.$(MSBuildProjectName) - - - ..\..\..\..\$(Platform)\$(Configuration)\ - - - - ;..\..\..\common\inc;..\..\..\common\Telemetry;..\..\..\;..\;%(AdditionalIncludeDirectories) - - - ole32.lib;Shell32.lib;OleAut32.lib;Dbghelp.lib;Dwmapi.lib;Dcomp.lib;Shlwapi.lib;%(AdditionalDependencies) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create - - - - - - - - - - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - - - {caba8dfb-823b-4bf2-93ac-3f31984150d9} - - - {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} - - - {6955446d-23f7-4023-9bb3-8657f904af99} - - - {8f021b46-362b-485c-bfba-ccf83e820cbd} - - - {98537082-0fdb-40de-abd8-0dc5a4269bab} - - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj.filters b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj.filters deleted file mode 100644 index 006a6196ef35..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj.filters +++ /dev/null @@ -1,141 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;svg;tiff;tif;png;wav;mfcribbon-ms - - - {cb917ac7-30da-494b-81f1-cbe4415e91f4} - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Resource Files - - - Generated Files - - - Header Files - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - - Resource Files - - - - - - Resource Files - - - - - Resource Files - - - - - Generated Files - - - - - Resource Files - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuideConstants.h b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuideConstants.h deleted file mode 100644 index 3d0e434abfb2..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuideConstants.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once -#include - -namespace ShortcutGuideConstants -{ - // Name of the powertoy module. - inline const std::wstring ModuleKey = L"Shortcut Guide"; -} \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuideSettings.h b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuideSettings.h deleted file mode 100644 index a39f2f25114e..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuideSettings.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once -#include - -struct ShortcutGuideSettings -{ - std::wstring hotkey = L"shift+win+/"; - int overlayOpacity = 90; - std::wstring theme = L"system"; - std::wstring disabledApps = L""; - bool shouldReactToPressedWinKey = false; - int windowsKeyPressTimeForGlobalWindowsShortcuts = 900; - int windowsKeyPressTimeForTaskbarIconShortcuts = 900; -}; diff --git a/src/modules/ShortcutGuide/ShortcutGuide/animation.cpp b/src/modules/ShortcutGuide/ShortcutGuide/animation.cpp deleted file mode 100644 index 4bc039a3e279..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/animation.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "pch.h" -#include "animation.h" - -Animation::Animation(double duration, double start, double stop) : - duration(duration), start_value(start), end_value(stop), start(std::chrono::high_resolution_clock::now()) {} - -void Animation::reset() -{ - start = std::chrono::high_resolution_clock::now(); -} -void Animation::reset(double animation_duration) -{ - duration = animation_duration; - reset(); -} -void Animation::reset(double animation_duration, double animation_start, double animation_stop) -{ - start_value = animation_start; - end_value = animation_stop; - reset(animation_duration); -} - -static double ease_out_expo(double t) -{ - return 1 - pow(2, -8 * t); -} - -double Animation::apply_animation_function(double t, AnimFunctions apply_function) -{ - switch (apply_function) - { - case EASE_OUT_EXPO: - return ease_out_expo(t); - case LINEAR: - default: - return t; - } -} - -double Animation::value(AnimFunctions apply_function) const -{ - auto anim_duration = std::chrono::high_resolution_clock::now() - start; - double t = std::chrono::duration(anim_duration).count() / duration; - if (t >= 1) - return end_value; - return start_value + (end_value - start_value) * apply_animation_function(t, apply_function); -} -bool Animation::done() const -{ - return std::chrono::high_resolution_clock::now() - start >= std::chrono::duration(duration); -} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/animation.h b/src/modules/ShortcutGuide/ShortcutGuide/animation.h deleted file mode 100644 index 61ae59b661ad..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/animation.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once -#include - -/* - Usage: - When creating animation constructor takes one parameter - how long - should the animation take in seconds. - - Call reset() when starting animation. - - When rendering, call value() to get value from 0 to 1 - depending on animation - progress. -*/ -class Animation -{ -public: - enum AnimFunctions - { - LINEAR = 0, - EASE_OUT_EXPO - }; - - Animation(double duration = 1, double start = 0, double stop = 1); - void reset(); - void reset(double animation_duration); - void reset(double animation_duration, double animation_start, double animation_stop); - double value(AnimFunctions apply_function) const; - bool done() const; - -private: - static double apply_animation_function(double t, AnimFunctions apply_function); - std::chrono::high_resolution_clock::time_point start; - double start_value, end_value, duration; -}; diff --git a/src/modules/ShortcutGuide/ShortcutGuide/d2d_svg.cpp b/src/modules/ShortcutGuide/ShortcutGuide/d2d_svg.cpp deleted file mode 100644 index 0f01aaf403d3..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/d2d_svg.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include "pch.h" -#include "d2d_svg.h" - -D2DSVG& D2DSVG::load(const std::wstring& filename, ID2D1DeviceContext5* d2d_dc) -{ - svg = nullptr; - winrt::com_ptr svg_stream; - auto h = SHCreateStreamOnFileEx(filename.c_str(), - STGM_READ, - FILE_ATTRIBUTE_NORMAL, - FALSE, - nullptr, - svg_stream.put()); - winrt::check_hresult(h); - - auto h1 = d2d_dc->CreateSvgDocument( - svg_stream.get(), - D2D1::SizeF(1, 1), - svg.put()); - - winrt::check_hresult(h1); - - winrt::com_ptr root; - svg->GetRoot(root.put()); - float tmp; - winrt::check_hresult(root->GetAttributeValue(L"width", &tmp)); - svg_width = static_cast(tmp); - winrt::check_hresult(root->GetAttributeValue(L"height", &tmp)); - svg_height = static_cast(tmp); - return *this; -} - -D2DSVG& D2DSVG::resize(int x, int y, int width, int height, float fill, float max_scale) -{ - // Center - transform = D2D1::Matrix3x2F::Identity(); - transform = transform * D2D1::Matrix3x2F::Translation((width - svg_width) / 2.0f, (height - svg_height) / 2.0f); - float h_scale = fill * height / svg_height; - float v_scale = fill * width / svg_width; - used_scale = std::min(h_scale, v_scale); - if (max_scale > 0) - { - used_scale = std::min(used_scale, max_scale); - } - transform = transform * D2D1::Matrix3x2F::Scale(used_scale, used_scale, D2D1::Point2F(width / 2.0f, height / 2.0f)); - transform = transform * D2D1::Matrix3x2F::Translation(static_cast(x), static_cast(y)); - return *this; -} - -D2DSVG& D2DSVG::recolor(uint32_t oldcolor, uint32_t newcolor) -{ - auto new_color = D2D1::ColorF(newcolor & 0xFFFFFF, 1); - auto old_color = D2D1::ColorF(oldcolor & 0xFFFFFF, 1); - std::function recurse = [&](ID2D1SvgElement* element) { - if (!element) - return; - if (element->IsAttributeSpecified(L"fill")) - { - D2D1_COLOR_F elem_fill; - winrt::com_ptr paint; - element->GetAttributeValue(L"fill", paint.put()); - paint->GetColor(&elem_fill); - if (elem_fill.r == old_color.r && elem_fill.g == old_color.g && elem_fill.b == old_color.b) - { - winrt::check_hresult(element->SetAttributeValue(L"fill", new_color)); - } - } - winrt::com_ptr sub; - element->GetFirstChild(sub.put()); - while (sub) - { - recurse(sub.get()); - winrt::com_ptr next; - element->GetNextChild(sub.get(), next.put()); - sub = next; - } - }; - winrt::com_ptr root; - svg->GetRoot(root.put()); - recurse(root.get()); - return *this; -} - -D2DSVG& D2DSVG::render(ID2D1DeviceContext5* d2d_dc) -{ - D2D1_MATRIX_3X2_F current; - d2d_dc->GetTransform(¤t); - d2d_dc->SetTransform(transform * current); - d2d_dc->DrawSvgDocument(svg.get()); - d2d_dc->SetTransform(current); - return *this; -} - -D2DSVG& D2DSVG::toggle_element(const wchar_t* id, bool visible) -{ - winrt::com_ptr element; - if (svg->FindElementById(id, element.put()) != S_OK) - return *this; - if (!element) - return *this; - element->SetAttributeValue(L"display", visible ? D2D1_SVG_DISPLAY::D2D1_SVG_DISPLAY_INLINE : D2D1_SVG_DISPLAY::D2D1_SVG_DISPLAY_NONE); - return *this; -} - -winrt::com_ptr D2DSVG::find_element(const std::wstring& id) -{ - winrt::com_ptr element; - winrt::check_hresult(svg->FindElementById(id.c_str(), element.put())); - return element; -} - -D2D1_RECT_F D2DSVG::rescale(D2D1_RECT_F rect) -{ - D2D1_RECT_F result; - auto src = reinterpret_cast(&rect); - auto dst = reinterpret_cast(&result); - dst[0] = src[0] * transform; - dst[1] = src[1] * transform; - return result; -} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/d2d_svg.h b/src/modules/ShortcutGuide/ShortcutGuide/d2d_svg.h deleted file mode 100644 index 08869b2de193..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/d2d_svg.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include -#include -#include -#include - -class D2DSVG -{ -public: - D2DSVG& load(const std::wstring& filename, ID2D1DeviceContext5* d2d_dc); - D2DSVG& resize(int x, int y, int width, int height, float fill, float max_scale = -1.0f); - D2DSVG& render(ID2D1DeviceContext5* d2d_dc); - D2DSVG& recolor(uint32_t oldcolor, uint32_t newcolor); - float get_scale() const { return used_scale; } - int width() const { return svg_width; } - int height() const { return svg_height; } - D2DSVG& toggle_element(const wchar_t* id, bool visible); - winrt::com_ptr find_element(const std::wstring& id); - D2D1_RECT_F rescale(D2D1_RECT_F rect); - -protected: - float used_scale = 1.0f; - winrt::com_ptr svg; - int svg_width = -1, svg_height = -1; - D2D1::Matrix3x2F transform; -}; diff --git a/src/modules/ShortcutGuide/ShortcutGuide/d2d_text.cpp b/src/modules/ShortcutGuide/ShortcutGuide/d2d_text.cpp deleted file mode 100644 index 7f25c4e32c13..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/d2d_text.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include "pch.h" -#include "d2d_text.h" - -D2DText::D2DText(float text_size, float scale) -{ - winrt::check_hresult(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(factory), reinterpret_cast(factory.put_void()))); - resize(text_size, scale); - winrt::check_hresult(format->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER)); - winrt::check_hresult(format->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER)); -} - -D2DText& D2DText::resize(float text_size, float scale) -{ - format = nullptr; - winrt::check_hresult(factory->CreateTextFormat(L"Segoe UI", - nullptr, - DWRITE_FONT_WEIGHT_NORMAL, - DWRITE_FONT_STYLE_NORMAL, - DWRITE_FONT_STRETCH_NORMAL, - text_size * scale, - L"en-us", - format.put())); - winrt::check_hresult(format->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER)); - return *this; -} - -D2DText& D2DText::set_alignment_left() -{ - winrt::check_hresult(format->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING)); - return *this; -} - -D2DText& D2DText::set_alignment_center() -{ - winrt::check_hresult(format->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER)); - return *this; -} - -D2DText& D2DText::set_alignment_right() -{ - winrt::check_hresult(format->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_TRAILING)); - return *this; -} - -void D2DText::write(ID2D1DeviceContext5* d2d_dc, D2D1_COLOR_F color, D2D1_RECT_F rect, std::wstring text) -{ - winrt::com_ptr brush; - d2d_dc->CreateSolidColorBrush(color, brush.put()); - d2d_dc->DrawText(text.c_str(), - static_cast(text.length()), - format.get(), - rect, - brush.get()); -} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/d2d_text.h b/src/modules/ShortcutGuide/ShortcutGuide/d2d_text.h deleted file mode 100644 index 513dc120e58b..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/d2d_text.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once -#include -#include - -class D2DText -{ -public: - D2DText(float text_size = 15.0f, float scale = 1.0f); - D2DText& resize(float text_size, float scale); - D2DText& set_alignment_left(); - D2DText& set_alignment_center(); - D2DText& set_alignment_right(); - void write(ID2D1DeviceContext5* d2d_dc, D2D1_COLOR_F color, D2D1_RECT_F rect, std::wstring text); - -private: - winrt::com_ptr factory; - winrt::com_ptr format; -}; diff --git a/src/modules/ShortcutGuide/ShortcutGuide/d2d_window.cpp b/src/modules/ShortcutGuide/ShortcutGuide/d2d_window.cpp deleted file mode 100644 index f4bf7e9cf607..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/d2d_window.cpp +++ /dev/null @@ -1,211 +0,0 @@ -#include "pch.h" -#include "d2d_window.h" - -#include - -D2DWindow::D2DWindow() -{ - static const WCHAR* class_name = L"PToyD2DPopup"; - WNDCLASS wc = {}; - wc.hCursor = LoadCursor(nullptr, IDC_ARROW); - wc.hInstance = reinterpret_cast(&__ImageBase); - wc.lpszClassName = class_name; - wc.style = CS_HREDRAW | CS_VREDRAW; - wc.lpfnWndProc = d2d_window_proc; - RegisterClass(&wc); - hwnd = CreateWindowExW(WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_NOREDIRECTIONBITMAP | WS_EX_LAYERED, - wc.lpszClassName, - L"PToyD2DPopup", - WS_POPUP | WS_VISIBLE, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - nullptr, - nullptr, - wc.hInstance, - this); - WINRT_VERIFY(hwnd); -} - -void D2DWindow::show(UINT x, UINT y, UINT width, UINT height) -{ - if (!initialized) - { - base_init(); - } - base_resize(width, height); - render_empty(); - hidden = false; - on_show(); - SetWindowPos(hwnd, HWND_TOPMOST, x, y, width, height, 0); - ShowWindow(hwnd, SW_SHOWNORMAL); - SetForegroundWindow(hwnd); - UpdateWindow(hwnd); -} - -void D2DWindow::hide() -{ - hidden = true; - ShowWindow(hwnd, SW_HIDE); - on_hide(); -} - -void D2DWindow::initialize() -{ - base_init(); -} - -void D2DWindow::base_init() -{ - std::unique_lock lock(mutex); - // D2D1Factory is independent from the device, no need to recreate it if we need to recreate the device. - if (!d2d_factory) - { -#ifdef _DEBUG - D2D1_FACTORY_OPTIONS options = { D2D1_DEBUG_LEVEL_INFORMATION }; -#else - D2D1_FACTORY_OPTIONS options = {}; -#endif - winrt::check_hresult(D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, - __uuidof(d2d_factory), - &options, - d2d_factory.put_void())); - } - // For all other stuff - assign nullptr first to release the object, to reset the com_ptr. - d2d_dc = nullptr; - d2d_device = nullptr; - dxgi_factory = nullptr; - dxgi_device = nullptr; - d3d_device = nullptr; - winrt::check_hresult(D3D11CreateDevice(nullptr, - D3D_DRIVER_TYPE_HARDWARE, - nullptr, - D3D11_CREATE_DEVICE_BGRA_SUPPORT, - nullptr, - 0, - D3D11_SDK_VERSION, - d3d_device.put(), - nullptr, - nullptr)); - winrt::check_hresult(d3d_device->QueryInterface(__uuidof(dxgi_device), dxgi_device.put_void())); - winrt::check_hresult(CreateDXGIFactory2(0, __uuidof(dxgi_factory), dxgi_factory.put_void())); - winrt::check_hresult(d2d_factory->CreateDevice(dxgi_device.get(), d2d_device.put())); - winrt::check_hresult(d2d_device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, d2d_dc.put())); - init(); - initialized = true; -} - -void D2DWindow::base_resize(UINT width, UINT height) -{ - std::unique_lock lock(mutex); - if (!initialized) - { - return; - } - window_width = width; - window_height = height; - if (window_width == 0 || window_height == 0) - { - return; - } - DXGI_SWAP_CHAIN_DESC1 sc_description = {}; - sc_description.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - sc_description.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - sc_description.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; - sc_description.BufferCount = 2; - sc_description.SampleDesc.Count = 1; - sc_description.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED; - sc_description.Width = window_width; - sc_description.Height = window_height; - dxgi_swap_chain = nullptr; - winrt::check_hresult(dxgi_factory->CreateSwapChainForComposition(dxgi_device.get(), - &sc_description, - nullptr, - dxgi_swap_chain.put())); - composition_device = nullptr; - winrt::check_hresult(DCompositionCreateDevice(dxgi_device.get(), - __uuidof(composition_device), - composition_device.put_void())); - - composition_target = nullptr; - winrt::check_hresult(composition_device->CreateTargetForHwnd(hwnd, true, composition_target.put())); - - composition_visual = nullptr; - winrt::check_hresult(composition_device->CreateVisual(composition_visual.put())); - winrt::check_hresult(composition_visual->SetContent(dxgi_swap_chain.get())); - winrt::check_hresult(composition_target->SetRoot(composition_visual.get())); - - dxgi_surface = nullptr; - winrt::check_hresult(dxgi_swap_chain->GetBuffer(0, __uuidof(dxgi_surface), dxgi_surface.put_void())); - D2D1_BITMAP_PROPERTIES1 properties = {}; - properties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; - properties.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM; - properties.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW; - - d2d_bitmap = nullptr; - winrt::check_hresult(d2d_dc->CreateBitmapFromDxgiSurface(dxgi_surface.get(), - properties, - d2d_bitmap.put())); - d2d_dc->SetTarget(d2d_bitmap.get()); - resize(); -} - -void D2DWindow::base_render() -{ - std::unique_lock lock(mutex); - if (!initialized || !d2d_dc || !d2d_bitmap) - return; - d2d_dc->BeginDraw(); - render(d2d_dc.get()); - winrt::check_hresult(d2d_dc->EndDraw()); - winrt::check_hresult(dxgi_swap_chain->Present(1, 0)); - winrt::check_hresult(composition_device->Commit()); -} - -void D2DWindow::render_empty() -{ - std::unique_lock lock(mutex); - if (!initialized || !d2d_dc || !d2d_bitmap) - return; - d2d_dc->BeginDraw(); - d2d_dc->Clear(); - winrt::check_hresult(d2d_dc->EndDraw()); - winrt::check_hresult(dxgi_swap_chain->Present(1, 0)); - winrt::check_hresult(composition_device->Commit()); -} - -D2DWindow::~D2DWindow() -{ - ShowWindow(hwnd, SW_HIDE); - DestroyWindow(hwnd); -} - -D2DWindow* D2DWindow::this_from_hwnd(HWND window) -{ - return reinterpret_cast(GetWindowLongPtr(window, GWLP_USERDATA)); -} - -LRESULT __stdcall D2DWindow::d2d_window_proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - auto self = this_from_hwnd(window); - switch (message) - { - case WM_NCCREATE: - { - auto create_struct = reinterpret_cast(lparam); - SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(create_struct->lpCreateParams)); - return TRUE; - } - case WM_MOVE: - case WM_SIZE: - self->base_resize(static_cast(lparam) & 0xFFFF, static_cast(lparam) >> 16); - [[fallthrough]]; - case WM_PAINT: - self->base_render(); - return 0; - - default: - return DefWindowProc(window, message, wparam, lparam); - } -} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/d2d_window.h b/src/modules/ShortcutGuide/ShortcutGuide/d2d_window.h deleted file mode 100644 index b062962d281f..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/d2d_window.h +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "d2d_svg.h" - -#include -#include - -class D2DWindow -{ -public: - D2DWindow(); - void show(UINT x, UINT y, UINT width, UINT height); - void hide(); - void initialize(); - virtual ~D2DWindow(); - -protected: - // Implement this: - - // Initialization - called when D2D device needs to be created. - // When called all D2DWindow members will be initialized, including d2d_dc - virtual void init() = 0; - // resize - when called, window_width and window_height will have current window size - virtual void resize() = 0; - // render - called on WM_PAINT, BeginPaint/EndPaint is handled by D2DWindow - virtual void render(ID2D1DeviceContext5* d2d_dc) = 0; - // on_show, on_hide - called when the window is about to be shown or about to be hidden - virtual void on_show() = 0; - virtual void on_hide() = 0; - - static LRESULT __stdcall d2d_window_proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); - static D2DWindow* this_from_hwnd(HWND window); - - void base_init(); - void base_resize(UINT width, UINT height); - void base_render(); - void render_empty(); - - std::recursive_mutex mutex; - bool hidden = true; - bool initialized = false; - HWND hwnd; - UINT window_width{}; - UINT window_height{}; - winrt::com_ptr d3d_device; - winrt::com_ptr dxgi_device; - winrt::com_ptr dxgi_factory; - winrt::com_ptr dxgi_swap_chain; - winrt::com_ptr composition_device; - winrt::com_ptr composition_target; - winrt::com_ptr composition_visual; - winrt::com_ptr dxgi_surface; - winrt::com_ptr d2d_bitmap; - winrt::com_ptr d2d_factory; - winrt::com_ptr d2d_device; - winrt::com_ptr d2d_dc; -}; diff --git a/src/modules/ShortcutGuide/ShortcutGuide/main.cpp b/src/modules/ShortcutGuide/ShortcutGuide/main.cpp deleted file mode 100644 index 713446403bd3..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/main.cpp +++ /dev/null @@ -1,144 +0,0 @@ -#include "pch.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "shortcut_guide.h" -#include "target_state.h" -#include "ShortcutGuideConstants.h" -#include "trace.h" - -const std::wstring instanceMutexName = L"Local\\PowerToys_ShortcutGuide_InstanceMutex"; - -// set current path to the executable path -bool SetCurrentPath() -{ - TCHAR buffer[MAX_PATH] = { 0 }; - if (!GetModuleFileName(NULL, buffer, MAX_PATH)) - { - Logger::error(L"Failed to get module path. {}", get_last_error_or_default(GetLastError())); - return false; - } - - if (!PathRemoveFileSpec(buffer)) - { - Logger::error(L"Failed to remove file from module path. {}", get_last_error_or_default(GetLastError())); - return false; - } - - std::error_code err; - std::filesystem::current_path(buffer, err); - if (err.value()) - { - Logger::error("Failed to set current path. {}", err.message()); - return false; - } - - return true; -} - -int WINAPI wWinMain(_In_ HINSTANCE /*hInstance*/, _In_opt_ HINSTANCE /*hPrevInstance*/, _In_ PWSTR lpCmdLine, _In_ int /*nCmdShow*/) -{ - winrt::init_apartment(); - LoggerHelpers::init_logger(ShortcutGuideConstants::ModuleKey, L"ShortcutGuide", LogSettings::shortcutGuideLoggerName); - - Shared::Trace::ETWTrace trace; - trace.UpdateState(true); - - if (powertoys_gpo::getConfiguredShortcutGuideEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled) - { - Logger::warn(L"Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator."); - return 0; - } - - InitUnhandledExceptionHandler(); - Logger::trace("Starting Shortcut Guide"); - - if (!SetCurrentPath()) - { - return false; - } - - Trace::RegisterProvider(); - if (std::wstring(lpCmdLine).find(L' ') != std::wstring::npos) - { - Logger::trace("Sending settings telemetry"); - auto settings = OverlayWindow::GetSettings(); - Trace::SendSettings(settings); - Trace::UnregisterProvider(); - return 0; - } - - auto mutex = CreateMutex(nullptr, true, instanceMutexName.c_str()); - if (mutex == nullptr) - { - Logger::error(L"Failed to create mutex. {}", get_last_error_or_default(GetLastError())); - } - - if (GetLastError() == ERROR_ALREADY_EXISTS) - { - Logger::warn(L"Shortcut Guide instance is already running"); - Trace::UnregisterProvider(); - return 0; - } - - std::wstring pid = std::wstring(lpCmdLine); - if (!pid.empty()) - { - auto mainThreadId = GetCurrentThreadId(); - ProcessWaiter::OnProcessTerminate(pid, [mainThreadId](int err) { - if (err != ERROR_SUCCESS) - { - Logger::error(L"Failed to wait for parent process exit. {}", get_last_error_or_default(err)); - } - else - { - Logger::trace(L"PowerToys runner exited."); - } - - Logger::trace(L"Exiting Shortcut Guide"); - PostThreadMessage(mainThreadId, WM_QUIT, 0, 0); - }); - } - - auto hwnd = GetForegroundWindow(); - auto window = OverlayWindow(hwnd); - EventWaiter exitEventWaiter; - if (window.IsDisabled()) - { - Logger::trace("SG is disabled for the current foreground app. Exiting SG"); - Trace::UnregisterProvider(); - return 0; - } - else - { - auto mainThreadId = GetCurrentThreadId(); - exitEventWaiter = EventWaiter(CommonSharedConstants::SHORTCUT_GUIDE_EXIT_EVENT, [mainThreadId, &window](int err) { - if (err != ERROR_SUCCESS) - { - Logger::error(L"Failed to wait for {} event. {}", CommonSharedConstants::SHORTCUT_GUIDE_EXIT_EVENT, get_last_error_or_default(err)); - } - else - { - Logger::trace(L"{} event was signaled", CommonSharedConstants::SHORTCUT_GUIDE_EXIT_EVENT); - } - - window.CloseWindow(HideWindowType::THE_SHORTCUT_PRESSED, mainThreadId); - }); - } - - window.ShowWindow(); - run_message_loop(); - - trace.Flush(); - Trace::UnregisterProvider(); - return 0; -} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/native_event_waiter.cpp b/src/modules/ShortcutGuide/ShortcutGuide/native_event_waiter.cpp deleted file mode 100644 index b038ea4ffda8..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/native_event_waiter.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "pch.h" -#include "native_event_waiter.h" - -void NativeEventWaiter::run() -{ - while (!aborting) - { - auto result = WaitForSingleObject(event_handle, timeout); - if (!aborting && result == WAIT_OBJECT_0) - { - action(); - } - } -} - -NativeEventWaiter::NativeEventWaiter(const std::wstring& event_name, std::function action) -{ - event_handle = CreateEventW(NULL, FALSE, FALSE, event_name.c_str()); - this->action = action; - running_thread = std::thread([&]() { run(); }); -} - -NativeEventWaiter::~NativeEventWaiter() -{ - aborting = true; - SetEvent(event_handle); - running_thread.join(); - CloseHandle(event_handle); -} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/native_event_waiter.h b/src/modules/ShortcutGuide/ShortcutGuide/native_event_waiter.h deleted file mode 100644 index d7dcaa603eca..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/native_event_waiter.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once -#include "pch.h" -#include "common/interop/shared_constants.h" - -class NativeEventWaiter -{ - static const int timeout = 1000; - - HANDLE event_handle = nullptr; - std::function action = nullptr; - std::atomic aborting = false; - - void run(); - std::thread running_thread; - -public: - NativeEventWaiter(const std::wstring& event_name, std::function action); - ~NativeEventWaiter(); -}; diff --git a/src/modules/ShortcutGuide/ShortcutGuide/overlay_window.cpp b/src/modules/ShortcutGuide/ShortcutGuide/overlay_window.cpp deleted file mode 100644 index 825bd10a1f7a..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/overlay_window.cpp +++ /dev/null @@ -1,947 +0,0 @@ -#include "pch.h" -#include "overlay_window.h" -#include -#include "tasklist_positions.h" -#include "start_visible.h" -#include -#include -#include - -#include "shortcut_guide.h" -#include "trace.h" -#include "Generated Files/resource.h" - -namespace -{ - // Gets position of given window. - std::optional get_window_pos(HWND hwnd) - { - RECT window; - if (DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &window, sizeof(window)) == S_OK) - { - return window; - } - else - { - return {}; - } - } - - enum WindowState - { - UNKNOWN, - MINIMIZED, - MAXIMIZED, - SNAPPED_TOP_LEFT, - SNAPPED_LEFT, - SNAPPED_BOTTOM_LEFT, - SNAPPED_TOP_RIGHT, - SNAPPED_RIGHT, - SNAPPED_BOTTOM_RIGHT, - RESTORED - }; - - inline WindowState get_window_state(HWND hwnd) - { - WINDOWPLACEMENT placement; - placement.length = sizeof(WINDOWPLACEMENT); - - if (GetWindowPlacement(hwnd, &placement) == 0) - { - return UNKNOWN; - } - - if (placement.showCmd == SW_MINIMIZE || placement.showCmd == SW_SHOWMINIMIZED || IsIconic(hwnd)) - { - return MINIMIZED; - } - - if (placement.showCmd == SW_MAXIMIZE || placement.showCmd == SW_SHOWMAXIMIZED) - { - return MAXIMIZED; - } - - auto rectp = get_window_pos(hwnd); - if (!rectp) - { - return UNKNOWN; - } - - auto rect = *rectp; - MONITORINFO monitor; - monitor.cbSize = sizeof(MONITORINFO); - auto h_monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); - GetMonitorInfo(h_monitor, &monitor); - bool top_left = monitor.rcWork.top == rect.top && monitor.rcWork.left == rect.left; - bool bottom_left = monitor.rcWork.bottom == rect.bottom && monitor.rcWork.left == rect.left; - bool top_right = monitor.rcWork.top == rect.top && monitor.rcWork.right == rect.right; - bool bottom_right = monitor.rcWork.bottom == rect.bottom && monitor.rcWork.right == rect.right; - - if (top_left && bottom_left) - return SNAPPED_LEFT; - if (top_left) - return SNAPPED_TOP_LEFT; - if (bottom_left) - return SNAPPED_BOTTOM_LEFT; - if (top_right && bottom_right) - return SNAPPED_RIGHT; - if (top_right) - return SNAPPED_TOP_RIGHT; - if (bottom_right) - return SNAPPED_BOTTOM_RIGHT; - - return RESTORED; - } - -} - -D2DOverlaySVG& D2DOverlaySVG::load(const std::wstring& filename, ID2D1DeviceContext5* d2d_dc) -{ - D2DSVG::load(filename, d2d_dc); - window_group = nullptr; - thumbnail_top_left = {}; - thumbnail_bottom_right = {}; - thumbnail_scaled_rect = {}; - return *this; -} - -D2DOverlaySVG& D2DOverlaySVG::resize(int x, int y, int width, int height, float fill, float max_scale) -{ - D2DSVG::resize(x, y, width, height, fill, max_scale); - if (thumbnail_bottom_right.x != 0 && thumbnail_bottom_right.y != 0) - { - auto scaled_top_left = transform.TransformPoint(thumbnail_top_left); - auto scanled_bottom_right = transform.TransformPoint(thumbnail_bottom_right); - thumbnail_scaled_rect.left = static_cast(scaled_top_left.x); - thumbnail_scaled_rect.top = static_cast(scaled_top_left.y); - thumbnail_scaled_rect.right = static_cast(scanled_bottom_right.x); - thumbnail_scaled_rect.bottom = static_cast(scanled_bottom_right.y); - } - return *this; -} - -D2DOverlaySVG& D2DOverlaySVG::find_thumbnail(const std::wstring& id) -{ - winrt::com_ptr thumbnail_box; - winrt::check_hresult(svg->FindElementById(id.c_str(), thumbnail_box.put())); - winrt::check_hresult(thumbnail_box->GetAttributeValue(L"x", &thumbnail_top_left.x)); - winrt::check_hresult(thumbnail_box->GetAttributeValue(L"y", &thumbnail_top_left.y)); - winrt::check_hresult(thumbnail_box->GetAttributeValue(L"width", &thumbnail_bottom_right.x)); - thumbnail_bottom_right.x += thumbnail_top_left.x; - winrt::check_hresult(thumbnail_box->GetAttributeValue(L"height", &thumbnail_bottom_right.y)); - thumbnail_bottom_right.y += thumbnail_top_left.y; - return *this; -} - -D2DOverlaySVG& D2DOverlaySVG::find_window_group(const std::wstring& id) -{ - window_group = nullptr; - winrt::check_hresult(svg->FindElementById(id.c_str(), window_group.put())); - return *this; -} - -ScaleResult D2DOverlaySVG::get_thumbnail_rect_and_scale(int x_offset, int y_offset, int window_cx, int window_cy, float fill) -{ - if (thumbnail_bottom_right.x == 0 && thumbnail_bottom_right.y == 0) - { - return {}; - } - int thumbnail_scaled_rect_width = thumbnail_scaled_rect.right - thumbnail_scaled_rect.left; - int thumbnail_scaled_rect_heigh = thumbnail_scaled_rect.bottom - thumbnail_scaled_rect.top; - if (thumbnail_scaled_rect_heigh == 0 || thumbnail_scaled_rect_width == 0 || - window_cx == 0 || window_cy == 0) - { - return {}; - } - float scale_h = fill * thumbnail_scaled_rect_width / window_cx; - float scale_v = fill * thumbnail_scaled_rect_heigh / window_cy; - float use_scale = std::min(scale_h, scale_v); - RECT thumb_rect; - thumb_rect.left = thumbnail_scaled_rect.left + static_cast(thumbnail_scaled_rect_width - use_scale * window_cx) / 2 + x_offset; - thumb_rect.right = thumbnail_scaled_rect.right - static_cast(thumbnail_scaled_rect_width - use_scale * window_cx) / 2 + x_offset; - thumb_rect.top = thumbnail_scaled_rect.top + static_cast(thumbnail_scaled_rect_heigh - use_scale * window_cy) / 2 + y_offset; - thumb_rect.bottom = thumbnail_scaled_rect.bottom - static_cast(thumbnail_scaled_rect_heigh - use_scale * window_cy) / 2 + y_offset; - ScaleResult result; - result.scale = use_scale; - result.rect = thumb_rect; - return result; -} - -winrt::com_ptr D2DOverlaySVG::find_element(const std::wstring& id) -{ - winrt::com_ptr element; - winrt::check_hresult(svg->FindElementById(id.c_str(), element.put())); - return element; -} - -D2DOverlaySVG& D2DOverlaySVG::toggle_window_group(bool active) -{ - if (window_group) - { - window_group->SetAttributeValue(L"fill-opacity", active ? 1.0f : 0.3f); - } - return *this; -} - -D2D1_RECT_F D2DOverlaySVG::get_maximize_label() const -{ - D2D1_RECT_F result; - auto height = thumbnail_scaled_rect.bottom - thumbnail_scaled_rect.top; - auto width = thumbnail_scaled_rect.right - thumbnail_scaled_rect.left; - if (width >= height) - { - result.top = thumbnail_scaled_rect.bottom + height * 0.210f; - result.bottom = thumbnail_scaled_rect.bottom + height * 0.310f; - result.left = thumbnail_scaled_rect.left + width * 0.009f; - result.right = thumbnail_scaled_rect.right + width * 0.009f; - } - else - { - result.top = thumbnail_scaled_rect.top + height * 0.323f; - result.bottom = thumbnail_scaled_rect.top + height * 0.398f; - result.left = static_cast(thumbnail_scaled_rect.right); - result.right = thumbnail_scaled_rect.right + width * 1.45f; - } - return result; -} -D2D1_RECT_F D2DOverlaySVG::get_minimize_label() const -{ - D2D1_RECT_F result; - auto height = thumbnail_scaled_rect.bottom - thumbnail_scaled_rect.top; - auto width = thumbnail_scaled_rect.right - thumbnail_scaled_rect.left; - if (width >= height) - { - result.top = thumbnail_scaled_rect.bottom + height * 0.8f; - result.bottom = thumbnail_scaled_rect.bottom + height * 0.9f; - result.left = thumbnail_scaled_rect.left + width * 0.009f; - result.right = thumbnail_scaled_rect.right + width * 0.009f; - } - else - { - result.top = thumbnail_scaled_rect.top + height * 0.725f; - result.bottom = thumbnail_scaled_rect.top + height * 0.800f; - result.left =static_cast(thumbnail_scaled_rect.right); - result.right = thumbnail_scaled_rect.right + width * 1.45f; - } - return result; -} -D2D1_RECT_F D2DOverlaySVG::get_snap_left() const -{ - D2D1_RECT_F result; - auto height = thumbnail_scaled_rect.bottom - thumbnail_scaled_rect.top; - auto width = thumbnail_scaled_rect.right - thumbnail_scaled_rect.left; - if (width >= height) - { - result.top = thumbnail_scaled_rect.bottom + height * 0.5f; - result.bottom = thumbnail_scaled_rect.bottom + height * 0.6f; - result.left = thumbnail_scaled_rect.left + width * 0.009f; - result.right = thumbnail_scaled_rect.left + width * 0.339f; - } - else - { - result.top = thumbnail_scaled_rect.top + height * 0.523f; - result.bottom = thumbnail_scaled_rect.top + height * 0.598f; - result.left = static_cast(thumbnail_scaled_rect.right); - result.right = thumbnail_scaled_rect.right + width * 0.450f; - } - return result; -} -D2D1_RECT_F D2DOverlaySVG::get_snap_right() const -{ - D2D1_RECT_F result; - auto height = thumbnail_scaled_rect.bottom - thumbnail_scaled_rect.top; - auto width = thumbnail_scaled_rect.right - thumbnail_scaled_rect.left; - if (width >= height) - { - result.top = thumbnail_scaled_rect.bottom + height * 0.5f; - result.bottom = thumbnail_scaled_rect.bottom + height * 0.6f; - result.left = thumbnail_scaled_rect.left + width * 0.679f; - result.right = thumbnail_scaled_rect.right + width * 1.009f; - } - else - { - result.top = thumbnail_scaled_rect.top + height * 0.523f; - result.bottom = thumbnail_scaled_rect.top + height * 0.598f; - result.left = static_cast(thumbnail_scaled_rect.right + width); - result.right = thumbnail_scaled_rect.right + width * 1.45f; - } - return result; -} - -D2DOverlayWindow::D2DOverlayWindow() : - total_screen({}), - D2DWindow() -{ - BOOL isEnabledAnimations = GetAnimationsEnabled(); - background_animation = isEnabledAnimations? 0.3f : 0.f; - global_windows_shortcuts_animation = isEnabledAnimations ? 0.3f : 0.f; - taskbar_icon_shortcuts_animation = isEnabledAnimations ? 0.3f : 0.f; - tasklist_thread = std::thread([&] { - while (running) - { - // Removing causes C3538 on std::unique_lock lock(mutex); in show(..) - std::unique_lock task_list_lock(tasklist_cv_mutex); - tasklist_cv.wait(task_list_lock, [&] { return !running || tasklist_update; }); - if (!running) - return; - task_list_lock.unlock(); - while (running && tasklist_update) - { - std::vector buttons; - if (tasklist.update_buttons(buttons)) - { - std::unique_lock lock(mutex); - tasklist_buttons.swap(buttons); - } - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - } - } - }); -} - -void D2DOverlayWindow::show(HWND window, bool snappable) -{ - std::unique_lock lock(mutex); - hidden = false; - tasklist_buttons.clear(); - active_window = window; - active_window_snappable = snappable; - auto old_bck = colors.start_color_menu; - auto colors_updated = colors.update(); - auto new_light_mode = (theme_setting == Light) || (theme_setting == System && colors.light_mode); - if (initialized && (colors_updated || light_mode != new_light_mode)) - { - // update background colors - landscape.recolor(old_bck, colors.start_color_menu); - portrait.recolor(old_bck, colors.start_color_menu); - for (auto& arrow : arrows) - { - arrow.recolor(old_bck, colors.start_color_menu); - } - light_mode = new_light_mode; - if (light_mode) - { - landscape.recolor(0xDDDDDD, 0x222222); - portrait.recolor(0xDDDDDD, 0x222222); - for (auto& arrow : arrows) - { - arrow.recolor(0xDDDDDD, 0x222222); - } - } - else - { - landscape.recolor(0x222222, 0xDDDDDD); - portrait.recolor(0x222222, 0xDDDDDD); - for (auto& arrow : arrows) - { - arrow.recolor(0x222222, 0xDDDDDD); - } - } - } - monitors = MonitorInfo::GetMonitors(true); - // calculate the rect covering all the screens - total_screen = monitors[0].GetScreenSize(true); - for (auto& monitor : monitors) - { - const auto monitorSize = monitor.GetScreenSize(true); - total_screen.rect.left = std::min(total_screen.left(), monitorSize.left()); - total_screen.rect.top = std::min(total_screen.top(), monitorSize.top()); - total_screen.rect.right = std::max(total_screen.right(), monitorSize.right()); - total_screen.rect.bottom = std::max(total_screen.bottom(), monitorSize.bottom()); - } - // make sure top-right corner of all the monitor rects is (0,0) - monitor_dx = -total_screen.left(); - monitor_dy = -total_screen.top(); - total_screen.rect.left += monitor_dx; - total_screen.rect.right += monitor_dx; - total_screen.rect.top += monitor_dy; - total_screen.rect.bottom += monitor_dy; - tasklist.update(); - if (window) - { - // Ignore errors, if this fails we will just not show the thumbnail - DwmRegisterThumbnail(hwnd, window, &thumbnail); - } - - background_animation.reset(); - - if (milliseconds_press_time_for_global_windows_shortcuts < milliseconds_press_time_for_taskbar_icon_shortcuts) - { - global_windows_shortcuts_shown = true; - taskbar_icon_shortcuts_shown = false; - global_windows_shortcuts_animation.reset(); - } - else if (milliseconds_press_time_for_global_windows_shortcuts > milliseconds_press_time_for_taskbar_icon_shortcuts) - { - global_windows_shortcuts_shown = false; - taskbar_icon_shortcuts_shown = true; - taskbar_icon_shortcuts_animation.reset(); - } - else - { - global_windows_shortcuts_shown = true; - taskbar_icon_shortcuts_shown = true; - global_windows_shortcuts_animation.reset(); - taskbar_icon_shortcuts_animation.reset(); - } - - auto primary_size = MonitorInfo::GetPrimaryMonitor().GetScreenSize(false); - shown_start_time = std::chrono::steady_clock::now(); - lock.unlock(); - D2DWindow::show(primary_size.left(), primary_size.top(), primary_size.width(), primary_size.height()); - // Check if taskbar is auto-hidden. If so, don't display the number arrows - APPBARDATA param = {}; - param.cbSize = sizeof(APPBARDATA); - if (static_cast(SHAppBarMessage(ABM_GETSTATE, ¶m)) != ABS_AUTOHIDE) - { - tasklist_cv_mutex.lock(); - tasklist_update = true; - tasklist_cv_mutex.unlock(); - tasklist_cv.notify_one(); - } -} - -void D2DOverlayWindow::on_show() -{ - // show override does everything -} - -void D2DOverlayWindow::on_hide() -{ - Logger::trace("D2DOverlayWindow::on_hide()"); - tasklist_cv_mutex.lock(); - tasklist_update = false; - tasklist_cv_mutex.unlock(); - tasklist_cv.notify_one(); - if (thumbnail) - { - DwmUnregisterThumbnail(thumbnail); - } - std::chrono::steady_clock::time_point shown_end_time = std::chrono::steady_clock::now(); - // Trace the event only if the overlay window was visible. - if (shown_start_time.time_since_epoch().count() > 0) - { - auto duration = std::chrono::duration_cast(shown_end_time - shown_start_time).count(); - Logger::trace(L"Duration: {}. Close Type: {}", duration, windowCloseType); - Trace::SendGuideSession(duration, windowCloseType.c_str()); - shown_start_time = {}; - } -} - -D2DOverlayWindow::~D2DOverlayWindow() -{ - tasklist_cv_mutex.lock(); - running = false; - tasklist_cv_mutex.unlock(); - tasklist_cv.notify_one(); - tasklist_thread.join(); -} - -void D2DOverlayWindow::apply_overlay_opacity(float opacity) -{ - if (opacity <= 0.0f) - { - opacity = 0.0f; - } - if (opacity >= 1.0f) - { - opacity = 1.0f; - } - overlay_opacity = opacity; -} - -void D2DOverlayWindow::apply_press_time_for_global_windows_shortcuts(int press_time) -{ - milliseconds_press_time_for_global_windows_shortcuts = std::max(press_time, 0); -} - -void D2DOverlayWindow::apply_press_time_for_taskbar_icon_shortcuts(int press_time) -{ - milliseconds_press_time_for_taskbar_icon_shortcuts = std::max(press_time, 0); -} - -void D2DOverlayWindow::set_theme(const std::wstring& theme) -{ - if (theme == L"light") - { - theme_setting = Light; - } - else if (theme == L"dark") - { - theme_setting = Dark; - } - else - { - theme_setting = System; - } -} - -/* Hide the window but do not call on_hide(). Use this to quickly hide the window when needed. - Note, that a proper hide should be made after this before showing the window again. -*/ -void D2DOverlayWindow::quick_hide() -{ - ShowWindow(hwnd, SW_HIDE); - if (thumbnail) - { - DwmUnregisterThumbnail(thumbnail); - } -} - -HWND D2DOverlayWindow::get_window_handle() -{ - return hwnd; -} - -float D2DOverlayWindow::get_overlay_opacity() -{ - return overlay_opacity; -} - -void D2DOverlayWindow::init() -{ - colors.update(); - landscape.load(L"Assets\\ShortcutGuide\\overlay.svg", d2d_dc.get()) - .find_thumbnail(L"monitorRect") - .find_window_group(L"WindowControlsGroup") - .recolor(0x2582FB, colors.start_color_menu); - portrait.load(L"Assets\\ShortcutGuide\\overlay_portrait.svg", d2d_dc.get()) - .find_thumbnail(L"monitorRect") - .find_window_group(L"WindowControlsGroup") - .recolor(0x2582FB, colors.start_color_menu); - no_active.load(L"Assets\\ShortcutGuide\\no_active_window.svg", d2d_dc.get()); - arrows.resize(10); - for (unsigned i = 0; i < arrows.size(); ++i) - { - arrows[i].load(L"Assets\\ShortcutGuide\\" + std::to_wstring((i + 1) % 10) + L".svg", d2d_dc.get()).recolor(0x2582FB, colors.start_color_menu); - } - light_mode = (theme_setting == Light) || (theme_setting == System && colors.light_mode); - if (light_mode) - { - landscape.recolor(0x2E17FC, 0x000000); - portrait.recolor(0x2E17FC, 0x000000); - for (auto& arrow : arrows) - { - arrow.recolor(0x222222, 0x000000); - } - } - else - { - landscape.recolor(0x2E17FC, 0xFFFFFF); - portrait.recolor(0x2E17FC, 0xFFFFFF); - for (auto& arrow : arrows) - { - arrow.recolor(0x222222, 0xFFFFFF); - } - } -} - -void D2DOverlayWindow::resize() -{ - window_rect = *get_window_pos(hwnd); - float no_active_scale, font; - if (window_width >= window_height) - { // portrait is broke right now - use_overlay = &landscape; - no_active_scale = 0.3f; - font = 12.0f; - } - else - { - use_overlay = &portrait; - no_active_scale = 0.5f; - font = 13.0f; - } - use_overlay->resize(0, 0, window_width, window_height, 0.8f); - auto thumb_no_active_rect = use_overlay->get_thumbnail_rect_and_scale(0, 0, no_active.width(), no_active.height(), no_active_scale).rect; - no_active.resize(thumb_no_active_rect.left, - thumb_no_active_rect.top, - thumb_no_active_rect.right - thumb_no_active_rect.left, - thumb_no_active_rect.bottom - thumb_no_active_rect.top, - 1.0f); - text.resize(font, use_overlay->get_scale()); -} - -void render_arrow(D2DSVG& arrow, TasklistButton& button, RECT window, float max_scale, ID2D1DeviceContext5* d2d_dc, int x_offset, int y_offset) -{ - int dx = 0, dy = 0; - // Calculate taskbar orientation - arrow.toggle_element(L"left", false); - arrow.toggle_element(L"right", false); - arrow.toggle_element(L"top", false); - arrow.toggle_element(L"bottom", false); - if (button.x <= window.left) - { // taskbar on left - dx = 1; - arrow.toggle_element(L"left", true); - } - if (button.x >= window.right) - { // taskbar on right - dx = -1; - arrow.toggle_element(L"right", true); - } - if (button.y <= window.top) - { // taskbar on top - dy = 1; - arrow.toggle_element(L"top", true); - } - if (button.y >= window.bottom) - { // taskbar on bottom - dy = -1; - arrow.toggle_element(L"bottom", true); - } - double arrow_ratio = static_cast(arrow.height()) / arrow.width(); - if (dy != 0) - { - // assume button is 25% wider than taller, +10% to make room for each of the arrows that are hidden - auto render_arrow_width = static_cast(button.height * 1.25f * 1.2f); - auto render_arrow_height = static_cast(render_arrow_width * arrow_ratio); - arrow.resize((button.x + (button.width - render_arrow_width) / 2) + x_offset, - (dy == -1 ? button.y - render_arrow_height : 0) + y_offset, - render_arrow_width, - render_arrow_height, - 0.95f, - max_scale) - .render(d2d_dc); - } - else - { - // same as above - make room for the hidden arrow - auto render_arrow_height = static_cast(button.height * 1.2f); - auto render_arrow_width = static_cast(render_arrow_height / arrow_ratio); - arrow.resize((dx == -1 ? button.x - render_arrow_width : 0) + x_offset, - (button.y + (button.height - render_arrow_height) / 2) + y_offset, - render_arrow_width, - render_arrow_height, - 0.95f, - max_scale) - .render(d2d_dc); - } -} - -bool D2DOverlayWindow::show_thumbnail(const RECT& rect, double alpha) -{ - if (!thumbnail) - { - return false; - } - DWM_THUMBNAIL_PROPERTIES thumb_properties; - thumb_properties.dwFlags = DWM_TNP_SOURCECLIENTAREAONLY | DWM_TNP_VISIBLE | DWM_TNP_RECTDESTINATION | DWM_TNP_OPACITY; - thumb_properties.fSourceClientAreaOnly = FALSE; - thumb_properties.fVisible = TRUE; - thumb_properties.opacity = static_cast(255 * alpha); - thumb_properties.rcDestination = rect; - if (DwmUpdateThumbnailProperties(thumbnail, &thumb_properties) != S_OK) - { - return false; - } - return true; -} - -void D2DOverlayWindow::hide_thumbnail() -{ - DWM_THUMBNAIL_PROPERTIES thumb_properties; - thumb_properties.dwFlags = DWM_TNP_VISIBLE; - thumb_properties.fVisible = FALSE; - DwmUpdateThumbnailProperties(thumbnail, &thumb_properties); -} - -void D2DOverlayWindow::render(ID2D1DeviceContext5* d2d_device_context) -{ - if (!hidden && !overlay_window_instance->overlay_visible()) - { - hide(); - return; - } - - d2d_device_context->Clear(); - int taskbar_icon_shortcuts_x_offset = 0, taskbar_icon_shortcuts_y_offset = 0; - - double current_background_anim_value = background_animation.value(Animation::AnimFunctions::LINEAR); - double current_global_windows_shortcuts_anim_value = global_windows_shortcuts_animation.value(Animation::AnimFunctions::LINEAR); - double pos_global_windows_shortcuts_anim_value = 1 - global_windows_shortcuts_animation.value(Animation::AnimFunctions::EASE_OUT_EXPO); - double pos_taskbar_icon_shortcuts_anim_value = 1 - taskbar_icon_shortcuts_animation.value(Animation::AnimFunctions::EASE_OUT_EXPO); - - // Draw background - SetLayeredWindowAttributes(hwnd, 0, static_cast(255 * current_background_anim_value), LWA_ALPHA); - winrt::com_ptr brush; - float brush_opacity = get_overlay_opacity(); - D2D1_COLOR_F brushColor = light_mode ? D2D1::ColorF(1.0f, 1.0f, 1.0f, brush_opacity) : D2D1::ColorF(0, 0, 0, brush_opacity); - winrt::check_hresult(d2d_device_context->CreateSolidColorBrush(brushColor, brush.put())); - D2D1_RECT_F background_rect = {}; - background_rect.bottom = static_cast(window_height); - background_rect.right = static_cast(window_width); - d2d_device_context->SetTransform(D2D1::Matrix3x2F::Identity()); - d2d_device_context->FillRectangle(background_rect, brush.get()); - - // Draw the taskbar shortcuts (the arrows with numbers) - if (taskbar_icon_shortcuts_shown) - { - if (!tasklist_buttons.empty()) - { - if (tasklist_buttons[0].x <= window_rect.left) - { - // taskbar on left - taskbar_icon_shortcuts_x_offset = static_cast(-pos_taskbar_icon_shortcuts_anim_value * use_overlay->width() * use_overlay->get_scale()); - } - if (tasklist_buttons[0].x >= window_rect.right) - { - // taskbar on right - taskbar_icon_shortcuts_x_offset = static_cast(pos_taskbar_icon_shortcuts_anim_value * use_overlay->width() * use_overlay->get_scale()); - } - if (tasklist_buttons[0].y <= window_rect.top) - { - // taskbar on top - taskbar_icon_shortcuts_y_offset = static_cast(-pos_taskbar_icon_shortcuts_anim_value * use_overlay->height() * use_overlay->get_scale()); - } - if (tasklist_buttons[0].y >= window_rect.bottom) - { - // taskbar on bottom - taskbar_icon_shortcuts_y_offset = static_cast(pos_taskbar_icon_shortcuts_anim_value * use_overlay->height() * use_overlay->get_scale()); - } - for (auto&& button : tasklist_buttons) - { - if (static_cast(button.keynum) - 1 >= arrows.size()) - { - continue; - } - render_arrow(arrows[static_cast(button.keynum) - 1], button, window_rect, use_overlay->get_scale(), d2d_device_context, taskbar_icon_shortcuts_x_offset, taskbar_icon_shortcuts_y_offset); - } - } - } - else - { - auto time_since_start = std::chrono::high_resolution_clock::now() - shown_start_time; - if (time_since_start.count() / 1000000 > milliseconds_press_time_for_taskbar_icon_shortcuts - milliseconds_press_time_for_global_windows_shortcuts) - { - taskbar_icon_shortcuts_shown = true; - taskbar_icon_shortcuts_animation.reset(); - } - } - - if (global_windows_shortcuts_shown) - { - // Thumbnail logic: - auto window_state = get_window_state(active_window); - auto thumb_window = get_window_pos(active_window); - if (!thumb_window.has_value()) - { - thumb_window = RECT(); - } - - bool miniature_shown = active_window != nullptr && thumbnail != nullptr && thumb_window && window_state != MINIMIZED; - RECT client_rect; - if (thumb_window && GetClientRect(active_window, &client_rect)) - { - int dx = ((thumb_window->right - thumb_window->left) - (client_rect.right - client_rect.left)) / 2; - int dy = ((thumb_window->bottom - thumb_window->top) - (client_rect.bottom - client_rect.top)) / 2; - thumb_window->left += dx; - thumb_window->right -= dx; - thumb_window->top += dy; - thumb_window->bottom -= dy; - } - if (miniature_shown && thumb_window->right - thumb_window->left <= 0 || thumb_window->bottom - thumb_window->top <= 0) - { - miniature_shown = false; - } - bool render_monitors = true; - auto total_monitor_with_screen = total_screen; - if (thumb_window) - { - total_monitor_with_screen.rect.left = std::min(total_monitor_with_screen.rect.left, thumb_window->left + monitor_dx); - total_monitor_with_screen.rect.top = std::min(total_monitor_with_screen.rect.top, thumb_window->top + monitor_dy); - total_monitor_with_screen.rect.right = std::max(total_monitor_with_screen.rect.right, thumb_window->right + monitor_dx); - total_monitor_with_screen.rect.bottom = std::max(total_monitor_with_screen.rect.bottom, thumb_window->bottom + monitor_dy); - } - // Only allow the new rect being slight bigger. - if (total_monitor_with_screen.width() - total_screen.width() > (thumb_window->right - thumb_window->left) / 2 || - total_monitor_with_screen.height() - total_screen.height() > (thumb_window->bottom - thumb_window->top) / 2) - { - render_monitors = false; - } - if (window_state == MINIMIZED) - { - total_monitor_with_screen = total_screen; - } - auto rect_and_scale = use_overlay->get_thumbnail_rect_and_scale(0, 0, total_monitor_with_screen.width(), total_monitor_with_screen.height(), 1); - if (miniature_shown) - { - RECT thumbnail_pos; - if (render_monitors) - { - thumbnail_pos.left = static_cast((thumb_window->left + monitor_dx) * rect_and_scale.scale + rect_and_scale.rect.left); - thumbnail_pos.top = static_cast((thumb_window->top + monitor_dy) * rect_and_scale.scale + rect_and_scale.rect.top); - thumbnail_pos.right = static_cast((thumb_window->right + monitor_dx) * rect_and_scale.scale + rect_and_scale.rect.left); - thumbnail_pos.bottom = static_cast((thumb_window->bottom + monitor_dy) * rect_and_scale.scale + rect_and_scale.rect.top); - } - else - { - thumbnail_pos = use_overlay->get_thumbnail_rect_and_scale(0, 0, thumb_window->right - thumb_window->left, thumb_window->bottom - thumb_window->top, 1).rect; - } - // If the animation is done show the thumbnail - // we cannot animate the thumbnail, the animation lags behind - miniature_shown = show_thumbnail(thumbnail_pos, current_global_windows_shortcuts_anim_value); - } - else - { - hide_thumbnail(); - } - if (window_state == MINIMIZED) - { - render_monitors = true; - } - // render the monitors - if (render_monitors) - { - brushColor = D2D1::ColorF(colors.start_color_menu, miniature_shown ? static_cast(current_global_windows_shortcuts_anim_value * 0.9) : static_cast(current_global_windows_shortcuts_anim_value * 0.3)); - brush = nullptr; - winrt::check_hresult(d2d_device_context->CreateSolidColorBrush(brushColor, brush.put())); - for (auto& monitor : monitors) - { - D2D1_RECT_F monitor_rect; - const auto monitor_size = monitor.GetScreenSize(true); - monitor_rect.left = static_cast((monitor_size.left() + monitor_dx) * rect_and_scale.scale + rect_and_scale.rect.left); - monitor_rect.top = static_cast((monitor_size.top() + monitor_dy) * rect_and_scale.scale + rect_and_scale.rect.top); - monitor_rect.right = static_cast((monitor_size.right() + monitor_dx) * rect_and_scale.scale + rect_and_scale.rect.left); - monitor_rect.bottom = static_cast((monitor_size.bottom() + monitor_dy) * rect_and_scale.scale + rect_and_scale.rect.top); - d2d_device_context->SetTransform(D2D1::Matrix3x2F::Identity()); - d2d_device_context->FillRectangle(monitor_rect, brush.get()); - } - } - // Finalize the overlay - dim the buttons if no thumbnail is present and show "No active window" - use_overlay->toggle_window_group(miniature_shown || window_state == MINIMIZED); - if (!miniature_shown && window_state != MINIMIZED) - { - no_active.render(d2d_device_context); - window_state = UNKNOWN; - } - - // Set the animation - move the draw window according to animation step - int global_windows_shortcuts_y_offset = static_cast(pos_global_windows_shortcuts_anim_value * use_overlay->height() * use_overlay->get_scale()); - auto popIn = D2D1::Matrix3x2F::Translation(0, static_cast(global_windows_shortcuts_y_offset)); - d2d_device_context->SetTransform(popIn); - - // Animate keys - for (unsigned id = 0; id < key_animations.size();) - { - auto& animation = key_animations[id]; - D2D1_COLOR_F color; - auto value = static_cast(animation.animation.value(Animation::AnimFunctions::EASE_OUT_EXPO)); - color.a = 1.0f; - color.r = animation.original.r + (1.0f - animation.original.r) * value; - color.g = animation.original.g + (1.0f - animation.original.g) * value; - color.b = animation.original.b + (1.0f - animation.original.b) * value; - animation.button->SetAttributeValue(L"fill", color); - if (animation.animation.done()) - { - if (value == 1) - { - animation.animation.reset(0.05, 1, 0); - animation.animation.value(Animation::AnimFunctions::EASE_OUT_EXPO); - } - else - { - key_animations.erase(key_animations.begin() + id); - continue; - } - } - ++id; - } - // Finally: render the overlay... - use_overlay->render(d2d_device_context); - // ... window arrows texts ... - std::wstring left, right, up, down; - bool left_disabled = false; - bool right_disabled = false; - bool up_disabled = false; - bool down_disabled = false; - switch (window_state) - { - case MINIMIZED: - left = GET_RESOURCE_STRING(IDS_NO_ACTION); - left_disabled = true; - right = GET_RESOURCE_STRING(IDS_NO_ACTION); - right_disabled = true; - up = GET_RESOURCE_STRING(IDS_RESTORE); - down = GET_RESOURCE_STRING(IDS_NO_ACTION); - down_disabled = true; - break; - case MAXIMIZED: - left = GET_RESOURCE_STRING(IDS_SNAP_LEFT); - right = GET_RESOURCE_STRING(IDS_SNAP_RIGHT); - up = GET_RESOURCE_STRING(IDS_NO_ACTION); - up_disabled = true; - down = GET_RESOURCE_STRING(IDS_RESTORE); - break; - case SNAPPED_TOP_LEFT: - left = GET_RESOURCE_STRING(IDS_SNAP_UPPER_RIGHT); - right = GET_RESOURCE_STRING(IDS_SNAP_UPPER_RIGHT); - up = GET_RESOURCE_STRING(IDS_MAXIMIZE); - down = GET_RESOURCE_STRING(IDS_SNAP_LEFT); - break; - case SNAPPED_LEFT: - left = GET_RESOURCE_STRING(IDS_SNAP_RIGHT); - right = GET_RESOURCE_STRING(IDS_RESTORE); - up = GET_RESOURCE_STRING(IDS_SNAP_UPPER_LEFT); - down = GET_RESOURCE_STRING(IDS_SNAP_LOWER_LEFT); - break; - case SNAPPED_BOTTOM_LEFT: - left = GET_RESOURCE_STRING(IDS_SNAP_LOWER_RIGHT); - right = GET_RESOURCE_STRING(IDS_SNAP_LOWER_RIGHT); - up = GET_RESOURCE_STRING(IDS_SNAP_LEFT); - down = GET_RESOURCE_STRING(IDS_MINIMIZE); - break; - case SNAPPED_TOP_RIGHT: - left = GET_RESOURCE_STRING(IDS_SNAP_UPPER_LEFT); - right = GET_RESOURCE_STRING(IDS_SNAP_UPPER_LEFT); - up = GET_RESOURCE_STRING(IDS_MAXIMIZE); - down = GET_RESOURCE_STRING(IDS_SNAP_RIGHT); - break; - case SNAPPED_RIGHT: - left = GET_RESOURCE_STRING(IDS_RESTORE); - right = GET_RESOURCE_STRING(IDS_SNAP_LEFT); - up = GET_RESOURCE_STRING(IDS_SNAP_UPPER_RIGHT); - down = GET_RESOURCE_STRING(IDS_SNAP_LOWER_RIGHT); - break; - case SNAPPED_BOTTOM_RIGHT: - left = GET_RESOURCE_STRING(IDS_SNAP_LOWER_LEFT); - right = GET_RESOURCE_STRING(IDS_SNAP_LOWER_LEFT); - up = GET_RESOURCE_STRING(IDS_SNAP_RIGHT); - down = GET_RESOURCE_STRING(IDS_MINIMIZE); - break; - case RESTORED: - left = GET_RESOURCE_STRING(IDS_SNAP_LEFT); - right = GET_RESOURCE_STRING(IDS_SNAP_RIGHT); - up = GET_RESOURCE_STRING(IDS_MAXIMIZE); - down = GET_RESOURCE_STRING(IDS_MINIMIZE); - break; - default: - left = GET_RESOURCE_STRING(IDS_NO_ACTION); - left_disabled = true; - right = GET_RESOURCE_STRING(IDS_NO_ACTION); - right_disabled = true; - up = GET_RESOURCE_STRING(IDS_NO_ACTION); - up_disabled = true; - down = GET_RESOURCE_STRING(IDS_NO_ACTION); - down_disabled = true; - } - auto text_color = D2D1::ColorF(light_mode ? 0x222222 : 0xDDDDDD, active_window_snappable && (miniature_shown || window_state == MINIMIZED) ? 1.0f : 0.3f); - use_overlay->find_element(L"KeyUpGroup")->SetAttributeValue(L"fill-opacity", up_disabled ? 0.3f : 1.0f); - text.set_alignment_center().write(d2d_device_context, text_color, use_overlay->get_maximize_label(), up); - use_overlay->find_element(L"KeyDownGroup")->SetAttributeValue(L"fill-opacity", down_disabled ? 0.3f : 1.0f); - text.write(d2d_device_context, text_color, use_overlay->get_minimize_label(), down); - use_overlay->find_element(L"KeyLeftGroup")->SetAttributeValue(L"fill-opacity", left_disabled ? 0.3f : 1.0f); - text.set_alignment_right().write(d2d_device_context, text_color, use_overlay->get_snap_left(), left); - use_overlay->find_element(L"KeyRightGroup")->SetAttributeValue(L"fill-opacity", right_disabled ? 0.3f : 1.0f); - text.set_alignment_left().write(d2d_device_context, text_color, use_overlay->get_snap_right(), right); - } - else - { - auto time_since_start = std::chrono::high_resolution_clock::now() - shown_start_time; - if (time_since_start.count() / 1000000 > milliseconds_press_time_for_global_windows_shortcuts - milliseconds_press_time_for_taskbar_icon_shortcuts) - { - global_windows_shortcuts_shown = true; - global_windows_shortcuts_animation.reset(); - } - } -} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/overlay_window.h b/src/modules/ShortcutGuide/ShortcutGuide/overlay_window.h deleted file mode 100644 index 00f05db56526..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/overlay_window.h +++ /dev/null @@ -1,114 +0,0 @@ -#pragma once -#include "animation.h" -#include "d2d_svg.h" -#include "d2d_window.h" -#include "d2d_text.h" - -#include -#include -#include "tasklist_positions.h" - -struct ScaleResult -{ - double scale; - RECT rect; -}; - -class D2DOverlaySVG : public D2DSVG -{ -public: - D2DOverlaySVG& load(const std::wstring& filename, ID2D1DeviceContext5* d2d_dc); - D2DOverlaySVG& resize(int x, int y, int width, int height, float fill, float max_scale = -1.0f); - D2DOverlaySVG& find_thumbnail(const std::wstring& id); - D2DOverlaySVG& find_window_group(const std::wstring& id); - ScaleResult get_thumbnail_rect_and_scale(int x_offset, int y_offset, int window_cx, int window_cy, float fill); - D2DOverlaySVG& toggle_window_group(bool active); - winrt::com_ptr find_element(const std::wstring& id); - D2D1_RECT_F get_maximize_label() const; - D2D1_RECT_F get_minimize_label() const; - D2D1_RECT_F get_snap_left() const; - D2D1_RECT_F get_snap_right() const; - -private: - D2D1_POINT_2F thumbnail_top_left = {}; - D2D1_POINT_2F thumbnail_bottom_right = {}; - RECT thumbnail_scaled_rect = {}; - winrt::com_ptr window_group; -}; - -struct AnimateKeys -{ - Animation animation; - D2D1_COLOR_F original; - winrt::com_ptr button; - int vk_code; -}; - -class D2DOverlayWindow : public D2DWindow -{ -public: - D2DOverlayWindow(); - void show(HWND window, bool snappable); - ~D2DOverlayWindow(); - void apply_overlay_opacity(float opacity); - void apply_press_time_for_global_windows_shortcuts(int press_time); - void apply_press_time_for_taskbar_icon_shortcuts(int press_time); - void set_theme(const std::wstring& theme); - void quick_hide(); - - HWND get_window_handle(); - void SetWindowCloseType(std::wstring wCloseType) - { - windowCloseType = wCloseType; - } - -private: - std::wstring windowCloseType; - bool show_thumbnail(const RECT& rect, double alpha); - void hide_thumbnail(); - virtual void init() override; - virtual void resize() override; - virtual void render(ID2D1DeviceContext5* d2dd2d_device_context_dc) override; - virtual void on_show() override; - virtual void on_hide() override; - float get_overlay_opacity(); - - bool running = true; - std::vector key_animations; - std::vector monitors; - Box total_screen; - int monitor_dx = 0, monitor_dy = 0; - D2DText text; - WindowsColors colors; - Animation background_animation; - Animation global_windows_shortcuts_animation; - Animation taskbar_icon_shortcuts_animation; - bool global_windows_shortcuts_shown = false; - bool taskbar_icon_shortcuts_shown = false; - RECT window_rect = {}; - Tasklist tasklist; - std::vector tasklist_buttons; - std::thread tasklist_thread; - bool tasklist_update = false; - std::mutex tasklist_cv_mutex; - std::condition_variable tasklist_cv; - - HTHUMBNAIL thumbnail = nullptr; - HWND active_window = nullptr; - bool active_window_snappable = false; - D2DOverlaySVG landscape, portrait; - D2DOverlaySVG* use_overlay = nullptr; - D2DSVG no_active; - std::vector arrows; - std::chrono::steady_clock::time_point shown_start_time; - float overlay_opacity = 0.9f; - enum - { - Light, - Dark, - System - } theme_setting = System; - bool light_mode = true; - UINT milliseconds_press_time_for_global_windows_shortcuts = 900; - UINT milliseconds_press_time_for_taskbar_icon_shortcuts = 900; -}; diff --git a/src/modules/ShortcutGuide/ShortcutGuide/packages.config b/src/modules/ShortcutGuide/ShortcutGuide/packages.config deleted file mode 100644 index 09bfc449e21c..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/resource.base.h b/src/modules/ShortcutGuide/ShortcutGuide/resource.base.h deleted file mode 100644 index 75f509067cb7..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/resource.base.h +++ /dev/null @@ -1,13 +0,0 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by ShortcutGuide.rc - -////////////////////////////// -// Non-localizable - -#define FILE_DESCRIPTION "PowerToys ShortcutGuide" -#define INTERNAL_NAME "ShortcutGuide" -#define ORIGINAL_FILENAME "PowerToys.ShortcutGuide.exe" - -// Non-localizable -////////////////////////////// diff --git a/src/modules/ShortcutGuide/ShortcutGuide/shortcut_guide.cpp b/src/modules/ShortcutGuide/ShortcutGuide/shortcut_guide.cpp deleted file mode 100644 index 719f713e7933..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/shortcut_guide.cpp +++ /dev/null @@ -1,505 +0,0 @@ -#include "pch.h" -#include "shortcut_guide.h" -#include "target_state.h" -#include "trace.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// TODO: refactor singleton -OverlayWindow* overlay_window_instance = nullptr; - -namespace -{ - // Window properties relevant to ShortcutGuide - struct ShortcutGuideWindowInfo - { - HWND hwnd = nullptr; // Handle to the top-level foreground window or nullptr if there is no such window - bool snappable = false; // True, if the window can react to Windows Snap keys - bool disabled = false; - }; - - ShortcutGuideWindowInfo GetShortcutGuideWindowInfo(HWND active_window) - { - ShortcutGuideWindowInfo result; - active_window = GetAncestor(active_window, GA_ROOT); - if (!IsWindowVisible(active_window)) - { - return result; - } - - auto style = GetWindowLong(active_window, GWL_STYLE); - auto exStyle = GetWindowLong(active_window, GWL_EXSTYLE); - if ((style & WS_CHILD) == WS_CHILD || - (style & WS_DISABLED) == WS_DISABLED || - (exStyle & WS_EX_TOOLWINDOW) == WS_EX_TOOLWINDOW || - (exStyle & WS_EX_NOACTIVATE) == WS_EX_NOACTIVATE) - { - return result; - } - std::array class_name; - GetClassNameA(active_window, class_name.data(), static_cast(class_name.size())); - if (is_system_window(active_window, class_name.data())) - { - return result; - } - static HWND cortana_hwnd = nullptr; - if (cortana_hwnd == nullptr) - { - if (strcmp(class_name.data(), "Windows.UI.Core.CoreWindow") == 0 && - get_process_path(active_window).ends_with(L"SearchUI.exe")) - { - cortana_hwnd = active_window; - return result; - } - } - else if (cortana_hwnd == active_window) - { - return result; - } - result.hwnd = active_window; - // In reality, Windows Snap works if even one of those styles is set - // for a window, it is just limited. If there is no WS_MAXIMIZEBOX using - // WinKey + Up just won't maximize the window. Similarly, without - // WS_MINIMIZEBOX the window will not get minimized. A "Save As..." dialog - // is a example of such window - it can be snapped to both sides and to - // all screen corners, but will not get maximized nor minimized. - // For now, since ShortcutGuide can only disable entire "Windows Controls" - // group, we require that the window supports all the options. - result.snappable = ((style & WS_MAXIMIZEBOX) == WS_MAXIMIZEBOX) && - ((style & WS_MINIMIZEBOX) == WS_MINIMIZEBOX) && - ((style & WS_THICKFRAME) == WS_THICKFRAME); - return result; - } - - const LPARAM eventActivateWindow = 1; - - bool wasWinPressed = false; - bool isWinPressed() - { - return (GetAsyncKeyState(VK_LWIN) & 0x8000) || (GetAsyncKeyState(VK_RWIN) & 0x8000); - } - - // all modifiers without win key - std::vector modifierKeys = { VK_SHIFT, VK_LSHIFT, VK_RSHIFT, VK_CONTROL, VK_LCONTROL, VK_RCONTROL, VK_MENU, VK_LMENU, VK_RMENU }; - - // returns false if there are other modifiers pressed or win key isn' pressed - bool onlyWinPressed() - { - if (!isWinPressed()) - { - return false; - } - - for (auto key : modifierKeys) - { - if (GetAsyncKeyState(key) & 0x8000) - { - return false; - } - } - - return true; - } - - constexpr bool isWin(int key) - { - return key == VK_LWIN || key == VK_RWIN; - } - - constexpr bool isKeyDown(LowlevelKeyboardEvent event) - { - return event.wParam == WM_KEYDOWN || event.wParam == WM_SYSKEYDOWN; - } - - LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) - { - LowlevelKeyboardEvent event; - if (nCode == HC_ACTION) - { - event.lParam = reinterpret_cast(lParam); - event.wParam = wParam; - - if (event.lParam->vkCode == VK_ESCAPE) - { - Logger::trace(L"ESC key was pressed"); - overlay_window_instance->CloseWindow(HideWindowType::ESC_PRESSED); - } - - if (wasWinPressed && !isKeyDown(event) && isWin(event.lParam->vkCode)) - { - Logger::trace(L"Win key was released"); - overlay_window_instance->CloseWindow(HideWindowType::WIN_RELEASED); - } - - if (isKeyDown(event) && isWin(event.lParam->vkCode)) - { - wasWinPressed = true; - } - - if (onlyWinPressed() && isKeyDown(event) && !isWin(event.lParam->vkCode)) - { - Logger::trace(L"Shortcut with win key was pressed"); - overlay_window_instance->CloseWindow(HideWindowType::WIN_SHORTCUT_PRESSED); - } - } - - return CallNextHookEx(NULL, nCode, wParam, lParam); - } - - LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam) - { - if (nCode >= 0) - { - switch (wParam) - { - case WM_LBUTTONUP: - case WM_RBUTTONUP: - case WM_MBUTTONUP: - case WM_XBUTTONUP: - // Don't close with mouse click if activation is windows key and the key is pressed - if (!overlay_window_instance->win_key_activation() || !isWinPressed()) - { - overlay_window_instance->CloseWindow(HideWindowType::MOUSE_BUTTONUP); - } - break; - default: - break; - } - } - - return CallNextHookEx(0, nCode, wParam, lParam); - } - - std::wstring ToWstring(HideWindowType type) - { - switch (type) - { - case HideWindowType::ESC_PRESSED: - return L"ESC_PRESSED"; - case HideWindowType::WIN_RELEASED: - return L"WIN_RELEASED"; - case HideWindowType::WIN_SHORTCUT_PRESSED: - return L"WIN_SHORTCUT_PRESSED"; - case HideWindowType::THE_SHORTCUT_PRESSED: - return L"THE_SHORTCUT_PRESSED"; - case HideWindowType::MOUSE_BUTTONUP: - return L"MOUSE_BUTTONUP"; - } - - return L""; - } -} - -OverlayWindow::OverlayWindow(HWND activeWindow) -{ - overlay_window_instance = this; - this->activeWindow = activeWindow; - app_name = GET_RESOURCE_STRING(IDS_SHORTCUT_GUIDE); - - Logger::info("Overlay Window is creating"); - init_settings(); - keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), NULL); - if (!keyboardHook) - { - Logger::warn(L"Failed to create low level keyboard hook. {}", get_last_error_or_default(GetLastError())); - } - - mouseHook = SetWindowsHookEx(WH_MOUSE_LL, LowLevelMouseProc, GetModuleHandle(NULL), NULL); - if (!mouseHook) - { - Logger::warn(L"Failed to create low level mouse hook. {}", get_last_error_or_default(GetLastError())); - } -} - -void OverlayWindow::ShowWindow() -{ - winkey_popup = std::make_unique(); - winkey_popup->apply_overlay_opacity(overlayOpacity.value / 100.0f); - winkey_popup->set_theme(theme.value); - - // The press time only takes effect when the shortcut guide is activated by pressing the win key. - if (shouldReactToPressedWinKey.value) - { - winkey_popup->apply_press_time_for_global_windows_shortcuts(windowsKeyPressTimeForGlobalWindowsShortcuts.value); - winkey_popup->apply_press_time_for_taskbar_icon_shortcuts(windowsKeyPressTimeForTaskbarIconShortcuts.value); - } - else - { - winkey_popup->apply_press_time_for_global_windows_shortcuts(0); - winkey_popup->apply_press_time_for_taskbar_icon_shortcuts(0); - } - - target_state = std::make_unique(); - try - { - winkey_popup->initialize(); - } - catch (...) - { - Logger::critical("Winkey popup failed to initialize"); - return; - } - - target_state->toggle_force_shown(); -} - -void OverlayWindow::CloseWindow(HideWindowType type, int mainThreadId) -{ - if (mainThreadId == 0) - { - mainThreadId = GetCurrentThreadId(); - } - - if (this->winkey_popup) - { - if (shouldReactToPressedWinKey.value) - { - // Send a dummy key to prevent Start Menu from activating - INPUT dummyEvent[1] = {}; - dummyEvent[0].type = INPUT_KEYBOARD; - dummyEvent[0].ki.wVk = 0xFF; - dummyEvent[0].ki.dwFlags = KEYEVENTF_KEYUP; - SendInput(1, dummyEvent, sizeof(INPUT)); - } - this->winkey_popup->SetWindowCloseType(ToWstring(type)); - Logger::trace(L"Terminating process"); - PostThreadMessage(mainThreadId, WM_QUIT, 0, 0); - } -} - -bool OverlayWindow::IsDisabled() -{ - WCHAR exePath[MAX_PATH] = L""; - overlay_window_instance->get_exe_path(activeWindow, exePath); - if (wcslen(exePath) > 0) - { - return is_disabled_app(exePath); - } - - return false; -} - -OverlayWindow::~OverlayWindow() -{ - if (event_waiter) - { - event_waiter.reset(); - } - - if (winkey_popup) - { - winkey_popup->hide(); - } - - if (target_state) - { - target_state->exit(); - target_state.reset(); - } - - if (winkey_popup) - { - winkey_popup.reset(); - } - - if (keyboardHook) - { - UnhookWindowsHookEx(keyboardHook); - } -} - -void OverlayWindow::on_held() -{ - auto windowInfo = GetShortcutGuideWindowInfo(activeWindow); - if (windowInfo.disabled) - { - target_state->was_hidden(); - return; - } - winkey_popup->show(windowInfo.hwnd, windowInfo.snappable); -} - -void OverlayWindow::quick_hide() -{ - winkey_popup->quick_hide(); -} - -void OverlayWindow::was_hidden() -{ - target_state->was_hidden(); -} - -bool OverlayWindow::overlay_visible() const -{ - return target_state->active(); -} - -bool OverlayWindow::win_key_activation() const -{ - return shouldReactToPressedWinKey.value; -} - -void OverlayWindow::init_settings() -{ - auto settings = GetSettings(); - overlayOpacity.value = settings.overlayOpacity; - theme.value = settings.theme; - disabledApps.value = settings.disabledApps; - shouldReactToPressedWinKey.value = settings.shouldReactToPressedWinKey; - windowsKeyPressTimeForGlobalWindowsShortcuts.value = settings.windowsKeyPressTimeForGlobalWindowsShortcuts; - windowsKeyPressTimeForTaskbarIconShortcuts.value = settings.windowsKeyPressTimeForTaskbarIconShortcuts; - update_disabled_apps(); -} - -bool OverlayWindow::is_disabled_app(wchar_t* exePath) -{ - if (exePath == nullptr) - { - return false; - } - - auto exePathUpper = std::wstring(exePath); - CharUpperBuffW(exePathUpper.data(), static_cast(exePathUpper.length())); - for (const auto& row : disabled_apps_array) - { - const auto pos = exePathUpper.rfind(row); - const auto last_slash = exePathUpper.rfind('\\'); - // Check that row occurs in disabled_apps_array, and its last occurrence contains in itself the first character after the last backslash. - if (pos != std::wstring::npos && pos <= last_slash + 1 && pos + row.length() > last_slash) - { - return true; - } - } - return false; -} - -void OverlayWindow::update_disabled_apps() -{ - disabled_apps_array.clear(); - auto disabledUppercase = disabledApps.value; - CharUpperBuffW(disabledUppercase.data(), static_cast(disabledUppercase.length())); - std::wstring_view view(disabledUppercase); - view = trim(view); - while (!view.empty()) - { - auto pos = (std::min)(view.find_first_of(L"\r\n"), view.length()); - disabled_apps_array.emplace_back(view.substr(0, pos)); - view.remove_prefix(pos); - view = trim(view); - } -} - -void OverlayWindow::get_exe_path(HWND window, wchar_t* path) -{ - if (disabled_apps_array.empty()) - { - return; - } - - DWORD pid = 0; - GetWindowThreadProcessId(window, &pid); - if (pid != 0) - { - HANDLE processHandle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); - if (processHandle && GetProcessImageFileName(processHandle, path, MAX_PATH) > 0) - { - CloseHandle(processHandle); - } - } -} - -ShortcutGuideSettings OverlayWindow::GetSettings() noexcept -{ - ShortcutGuideSettings settings; - json::JsonObject properties; - try - { - PowerToysSettings::PowerToyValues settingsValues = - PowerToysSettings::PowerToyValues::load_from_settings_file(app_key); - - auto settingsObject = settingsValues.get_raw_json(); - if (!settingsObject.GetView().Size()) - { - return settings; - } - - properties = settingsObject.GetNamedObject(L"properties"); - } - catch (...) - { - Logger::warn("Failed to read settings. Use default settings"); - return settings; - } - - try - { - settings.hotkey = PowerToysSettings::HotkeyObject::from_json(properties.GetNamedObject(OpenShortcut::name)).to_string(); - } - catch (...) - { - } - - try - { - settings.overlayOpacity = static_cast(properties.GetNamedObject(OverlayOpacity::name).GetNamedNumber(L"value")); - } - catch (...) - { - } - - try - { - settings.shouldReactToPressedWinKey = properties.GetNamedObject(ShouldReactToPressedWinKey::name).GetNamedBoolean(L"value"); - } - catch (...) - { - } - - try - { - settings.windowsKeyPressTimeForGlobalWindowsShortcuts = static_cast(properties.GetNamedObject(WindowsKeyPressTimeForGlobalWindowsShortcuts::name).GetNamedNumber(L"value")); - } - catch (...) - { - } - - try - { - settings.windowsKeyPressTimeForTaskbarIconShortcuts = static_cast(properties.GetNamedObject(WindowsKeyPressTimeForTaskbarIconShortcuts::name).GetNamedNumber(L"value")); - } - catch (...) - { - } - - try - { - settings.theme = (std::wstring)properties.GetNamedObject(Theme::name).GetNamedString(L"value"); - } - catch (...) - { - } - - try - { - settings.disabledApps = (std::wstring)properties.GetNamedObject(DisabledApps::name).GetNamedString(L"value"); - } - catch (...) - { - } - - return settings; -} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/shortcut_guide.h b/src/modules/ShortcutGuide/ShortcutGuide/shortcut_guide.h deleted file mode 100644 index 57f9bf326729..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/shortcut_guide.h +++ /dev/null @@ -1,106 +0,0 @@ -#pragma once -#include "../interface/powertoy_module_interface.h" -//#include -#include "overlay_window.h" -#include "native_event_waiter.h" -#include "ShortcutGuideSettings.h" -#include "ShortcutGuideConstants.h" - -#include "Generated Files/resource.h" - -// We support only one instance of the overlay -extern class OverlayWindow* overlay_window_instance; - -class TargetState; - -enum class HideWindowType -{ - ESC_PRESSED, - WIN_RELEASED, - WIN_SHORTCUT_PRESSED, - THE_SHORTCUT_PRESSED, - MOUSE_BUTTONUP -}; - -class OverlayWindow -{ -public: - OverlayWindow(HWND activeWindow); - void ShowWindow(); - void CloseWindow(HideWindowType type, int mainThreadId = 0); - bool IsDisabled(); - - void on_held(); - void quick_hide(); - void was_hidden(); - - bool overlay_visible() const; - bool win_key_activation() const; - - bool is_disabled_app(wchar_t* exePath); - - void get_exe_path(HWND window, wchar_t* exePath); - ~OverlayWindow(); - static ShortcutGuideSettings GetSettings() noexcept; -private: - std::wstring app_name; - //contains the non localized key of the powertoy - static inline std::wstring app_key = ShortcutGuideConstants::ModuleKey; - std::unique_ptr target_state; - std::unique_ptr winkey_popup; - std::unique_ptr event_waiter; - std::vector disabled_apps_array; - void init_settings(); - void update_disabled_apps(); - HWND activeWindow; - HHOOK keyboardHook; - HHOOK mouseHook; - - struct OverlayOpacity - { - static inline PCWSTR name = L"overlay_opacity"; - int value; - int resourceId = IDS_SETTING_DESCRIPTION_OVERLAY_OPACITY; - } overlayOpacity{}; - - struct Theme - { - static inline PCWSTR name = L"theme"; - std::wstring value; - int resourceId = IDS_SETTING_DESCRIPTION_THEME; - std::vector> keys_and_texts = { - { L"system", IDS_SETTING_DESCRIPTION_THEME_SYSTEM }, - { L"light", IDS_SETTING_DESCRIPTION_THEME_LIGHT }, - { L"dark", IDS_SETTING_DESCRIPTION_THEME_DARK } - }; - } theme; - - struct DisabledApps - { - static inline PCWSTR name = L"disabled_apps"; - std::wstring value; - } disabledApps; - - struct ShouldReactToPressedWinKey - { - static inline PCWSTR name = L"use_legacy_press_win_key_behavior"; - bool value; - } shouldReactToPressedWinKey; - - struct WindowsKeyPressTimeForGlobalWindowsShortcuts - { - static inline PCWSTR name = L"press_time"; - int value; - } windowsKeyPressTimeForGlobalWindowsShortcuts; - - struct WindowsKeyPressTimeForTaskbarIconShortcuts - { - static inline PCWSTR name = L"press_time_for_taskbar_icon_shortcuts"; - int value; - } windowsKeyPressTimeForTaskbarIconShortcuts; - - struct OpenShortcut - { - static inline PCWSTR name = L"open_shortcutguide"; - } openShortcut; -}; diff --git a/src/modules/ShortcutGuide/ShortcutGuide/start_visible.cpp b/src/modules/ShortcutGuide/ShortcutGuide/start_visible.cpp deleted file mode 100644 index 17efa76b7d68..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/start_visible.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "pch.h" -#include "start_visible.h" - -bool is_start_visible() -{ - static const auto app_visibility = []() { - winrt::com_ptr result; - CoCreateInstance(CLSID_AppVisibility, - nullptr, - CLSCTX_INPROC_SERVER, - __uuidof(result), - result.put_void()); - return result; - }(); - - if (!app_visibility) - { - return false; - } - - BOOL visible; - auto result = app_visibility->IsLauncherVisible(&visible); - return SUCCEEDED(result) && visible; -} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/start_visible.h b/src/modules/ShortcutGuide/ShortcutGuide/start_visible.h deleted file mode 100644 index a1431086a232..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/start_visible.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -bool is_start_visible(); diff --git a/src/modules/ShortcutGuide/ShortcutGuide/target_state.cpp b/src/modules/ShortcutGuide/ShortcutGuide/target_state.cpp deleted file mode 100644 index 98fd9bb722b7..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/target_state.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "pch.h" -#include "target_state.h" -#include "start_visible.h" -#include -#include - -constexpr unsigned VK_S = 0x53; - -void TargetState::was_hidden() -{ - std::unique_lock lock(mutex); - // Ignore callbacks from the D2DOverlayWindow - if (state == ForceShown) - { - return; - } - state = Hidden; - lock.unlock(); - cv.notify_one(); -} - -void TargetState::exit() -{ - std::unique_lock lock(mutex); - state = Exiting; - lock.unlock(); - cv.notify_one(); -} - -void TargetState::toggle_force_shown() -{ - std::unique_lock lock(mutex); - if (state != ForceShown) - { - state = ForceShown; - overlay_window_instance->on_held(); - } - else - { - state = Hidden; - } -} - -bool TargetState::active() const -{ - return state == ForceShown || state == Shown; -} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/target_state.h b/src/modules/ShortcutGuide/ShortcutGuide/target_state.h deleted file mode 100644 index c1f51e3f604d..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/target_state.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once -#include -#include -#include "shortcut_guide.h" - -struct KeyEvent -{ - bool key_down; - unsigned vk_code; -}; - -class TargetState -{ -public: - TargetState() = default; - void was_hidden(); - void exit(); - - void toggle_force_shown(); - bool active() const; - -private: - std::recursive_mutex mutex; - std::condition_variable_any cv; - enum State - { - Hidden, - Shown, - ForceShown, - Exiting - }; - std::atomic state = Hidden; -}; diff --git a/src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.cpp b/src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.cpp deleted file mode 100644 index 687fc86566e2..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.cpp +++ /dev/null @@ -1,116 +0,0 @@ -#include "pch.h" -#include "tasklist_positions.h" - -void Tasklist::update() -{ - // Get HWND of the tasklist - auto tasklist_hwnd = FindWindowA("Shell_TrayWnd", nullptr); - if (!tasklist_hwnd) - return; - tasklist_hwnd = FindWindowExA(tasklist_hwnd, 0, "ReBarWindow32", nullptr); - if (!tasklist_hwnd) - return; - tasklist_hwnd = FindWindowExA(tasklist_hwnd, 0, "MSTaskSwWClass", nullptr); - if (!tasklist_hwnd) - return; - tasklist_hwnd = FindWindowExA(tasklist_hwnd, 0, "MSTaskListWClass", nullptr); - if (!tasklist_hwnd) - return; - if (!automation) - { - winrt::check_hresult(CoCreateInstance(CLSID_CUIAutomation, - nullptr, - CLSCTX_INPROC_SERVER, - IID_IUIAutomation, - automation.put_void())); - winrt::check_hresult(automation->CreateTrueCondition(true_condition.put())); - } - element = nullptr; - winrt::check_hresult(automation->ElementFromHandle(tasklist_hwnd, element.put())); -} - -bool Tasklist::update_buttons(std::vector& buttons) -{ - if (!automation || !element) - { - return false; - } - winrt::com_ptr elements; - if (element->FindAll(TreeScope_Children, true_condition.get(), elements.put()) < 0) - return false; - if (!elements) - return false; - int count; - if (elements->get_Length(&count) < 0) - return false; - winrt::com_ptr child; - std::vector found_buttons; - found_buttons.reserve(count); - for (int i = 0; i < count; ++i) - { - child = nullptr; - if (elements->GetElement(i, child.put()) < 0) - return false; - TasklistButton button; - if (VARIANT var_rect; child->GetCurrentPropertyValue(UIA_BoundingRectanglePropertyId, &var_rect) >= 0) - { - if (var_rect.vt == (VT_R8 | VT_ARRAY)) - { - LONG pos; - double value; - pos = 0; - SafeArrayGetElement(var_rect.parray, &pos, &value); - button.x = static_cast(value); - pos = 1; - SafeArrayGetElement(var_rect.parray, &pos, &value); - button.y = static_cast(value); - pos = 2; - SafeArrayGetElement(var_rect.parray, &pos, &value); - button.width = static_cast(value); - pos = 3; - SafeArrayGetElement(var_rect.parray, &pos, &value); - button.height = static_cast(value); - } - VariantClear(&var_rect); - } - else - { - return false; - } - if (BSTR automation_id; child->get_CurrentAutomationId(&automation_id) >= 0) - { - button.name = automation_id; - SysFreeString(automation_id); - } - found_buttons.push_back(button); - } - // assign keynums - buttons.clear(); - for (auto& button : found_buttons) - { - if (buttons.empty()) - { - button.keynum = 1; - buttons.push_back(std::move(button)); - } - else - { - if (button.x < buttons.back().x || button.y < buttons.back().y) // skip 2nd row - break; - if (button.name == buttons.back().name) - continue; // skip buttons from the same app - button.keynum = buttons.back().keynum + 1; - buttons.push_back(std::move(button)); - if (buttons.back().keynum == 10) - break; // no more than 10 buttons - } - } - return true; -} - -std::vector Tasklist::get_buttons() -{ - std::vector buttons; - update_buttons(buttons); - return buttons; -} \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.h b/src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.h deleted file mode 100644 index 4ac5bed8a93b..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include - -struct TasklistButton -{ - std::wstring name; - long x{}; - long y{}; - long width{}; - long height{}; - long keynum{}; -}; - -class Tasklist -{ -public: - void update(); - std::vector get_buttons(); - bool update_buttons(std::vector& buttons); - -private: - winrt::com_ptr automation; - winrt::com_ptr element; - winrt::com_ptr true_condition; -}; diff --git a/src/modules/ShortcutGuide/ShortcutGuide/trace.cpp b/src/modules/ShortcutGuide/ShortcutGuide/trace.cpp deleted file mode 100644 index f2a414d6ac5b..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/trace.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "pch.h" -#include "trace.h" - -#include - -TRACELOGGING_DEFINE_PROVIDER( - g_hProvider, - "Microsoft.PowerToys", - // {38e8889b-9731-53f5-e901-e8a7c1753074} - (0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74), - TraceLoggingOptionProjectTelemetry()); - -void Trace::SendGuideSession(const __int64 duration_ms, const wchar_t* close_type) noexcept -{ - TraceLoggingWriteWrapper( - g_hProvider, - "ShortcutGuide_GuideSession", - TraceLoggingInt64(duration_ms, "DurationInMs"), - TraceLoggingWideString(close_type, "CloseType"), - ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), - TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), - TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); -} - -void Trace::SendSettings(ShortcutGuideSettings settings) noexcept -{ - TraceLoggingWriteWrapper( - g_hProvider, - "ShortcutGuide_Settings", - TraceLoggingWideString(settings.hotkey.c_str(), "Hotkey"), - TraceLoggingInt32(settings.overlayOpacity, "OverlayOpacity"), - TraceLoggingWideString(settings.theme.c_str(), "Theme"), - TraceLoggingWideString(settings.disabledApps.c_str(), "DisabledApps"), - TraceLoggingBoolean(settings.shouldReactToPressedWinKey, "ShouldReactToPressedWinKey"), - TraceLoggingInt32(settings.windowsKeyPressTimeForGlobalWindowsShortcuts, "WindowsKeyPressTimeForGlobalWindowsShortcuts"), - TraceLoggingInt32(settings.windowsKeyPressTimeForTaskbarIconShortcuts, "WindowsKeyPressTimeForTaskbarIconShortcuts"), - ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), - TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), - TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); -} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/trace.h b/src/modules/ShortcutGuide/ShortcutGuide/trace.h deleted file mode 100644 index a3446a857078..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/trace.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once -#include "ShortcutGuideSettings.h" - -#include - -class Trace : public telemetry::TraceBase -{ -public: - static void SendGuideSession(const __int64 duration_ms, const wchar_t* close_type) noexcept; - static void SendSettings(ShortcutGuideSettings settings) noexcept; -}; diff --git a/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj index be903fbe3efe..5b37ae13034f 100644 --- a/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj +++ b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj @@ -10,6 +10,7 @@ {2d604c07-51fc-46bb-9eb7-75aecc7f5e81} ShortcutGuideModuleInterface ShortcutGuideModuleInterface + 10.0.26100.0 @@ -37,7 +38,7 @@ - ..\..\..\..\$(Platform)\$(Configuration)\ + ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\ PowerToys.ShortcutGuideModuleInterface diff --git a/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/dllmain.cpp b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/dllmain.cpp index 5e8fe9aa1b35..433a9be4b176 100644 --- a/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/dllmain.cpp +++ b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/dllmain.cpp @@ -104,7 +104,10 @@ class ShortcutGuideModule : public PowertoyModuleIface if (_enabled) { _enabled = false; - TerminateProcess(); + if (IsProcessActive()) + { + TerminateProcess(m_hProcess, 0); + } } else { @@ -131,10 +134,6 @@ class ShortcutGuideModule : public PowertoyModuleIface virtual std::optional GetHotkeyEx() override { Logger::trace("GetHotkeyEx()"); - if (m_shouldReactToPressedWinKey) - { - return std::nullopt; - } return m_hotkey; } @@ -148,7 +147,7 @@ class ShortcutGuideModule : public PowertoyModuleIface if (IsProcessActive()) { - TerminateProcess(); + TerminateProcess(m_hProcess, 0); return; } @@ -170,16 +169,6 @@ class ShortcutGuideModule : public PowertoyModuleIface } } - virtual bool keep_track_of_pressed_win_key() override - { - return m_shouldReactToPressedWinKey; - } - - virtual UINT milliseconds_win_key_must_be_pressed() override - { - return std::min(m_millisecondsWinKeyPressTimeForGlobalWindowsShortcuts, m_millisecondsWinKeyPressTimeForTaskbarIconShortcuts); - } - private: std::wstring app_name; //contains the non localized key of the powertoy @@ -193,7 +182,6 @@ class ShortcutGuideModule : public PowertoyModuleIface // If the module should be activated through the legacy pressing windows key behavior. const UINT DEFAULT_MILLISECONDS_WIN_KEY_PRESS_TIME_FOR_GLOBAL_WINDOWS_SHORTCUTS = 900; const UINT DEFAULT_MILLISECONDS_WIN_KEY_PRESS_TIME_FOR_TASKBAR_ICON_SHORTCUTS = 900; - bool m_shouldReactToPressedWinKey = false; UINT m_millisecondsWinKeyPressTimeForGlobalWindowsShortcuts = DEFAULT_MILLISECONDS_WIN_KEY_PRESS_TIME_FOR_GLOBAL_WINDOWS_SHORTCUTS; UINT m_millisecondsWinKeyPressTimeForTaskbarIconShortcuts = DEFAULT_MILLISECONDS_WIN_KEY_PRESS_TIME_FOR_TASKBAR_ICON_SHORTCUTS; @@ -219,7 +207,7 @@ class ShortcutGuideModule : public PowertoyModuleIface SHELLEXECUTEINFOW sei{ sizeof(sei) }; sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI }; - sei.lpFile = L"PowerToys.ShortcutGuide.exe"; + sei.lpFile = L"WinUI3Apps\\PowerToys.ShortcutGuide.exe"; sei.nShow = SW_SHOWNORMAL; sei.lpParameters = executable_args.data(); if (ShellExecuteExW(&sei) == false) @@ -239,33 +227,18 @@ class ShortcutGuideModule : public PowertoyModuleIface return true; } - void TerminateProcess() + bool IsProcessActive() { - if (m_hProcess) + if (!m_hProcess) { - if (WaitForSingleObject(m_hProcess, 0) != WAIT_OBJECT_0) - { - if (exitEvent && SetEvent(exitEvent)) - { - Logger::trace(L"Signaled {}", CommonSharedConstants::SHORTCUT_GUIDE_EXIT_EVENT); - } - else - { - Logger::warn(L"Failed to signal {}", CommonSharedConstants::SHORTCUT_GUIDE_EXIT_EVENT); - } - } - else - { - CloseHandle(m_hProcess); - m_hProcess = nullptr; - Logger::trace("SG process was already terminated"); - } + return false; } - } - - bool IsProcessActive() - { - return m_hProcess && WaitForSingleObject(m_hProcess, 0) != WAIT_OBJECT_0; + auto result = WaitForSingleObject(m_hProcess, 0); + if (result == WAIT_FAILED) + { + Logger::error("Failed to wait for SG process."); + } + return result == WAIT_TIMEOUT; } void InitSettings() @@ -289,10 +262,6 @@ class ShortcutGuideModule : public PowertoyModuleIface void ParseSettings(PowerToysSettings::PowerToyValues& settings) { - m_shouldReactToPressedWinKey = false; - m_millisecondsWinKeyPressTimeForGlobalWindowsShortcuts = DEFAULT_MILLISECONDS_WIN_KEY_PRESS_TIME_FOR_GLOBAL_WINDOWS_SHORTCUTS; - m_millisecondsWinKeyPressTimeForTaskbarIconShortcuts = DEFAULT_MILLISECONDS_WIN_KEY_PRESS_TIME_FOR_TASKBAR_ICON_SHORTCUTS; - auto settingsObject = settings.get_raw_json(); if (settingsObject.GetView().Size()) { @@ -328,36 +297,6 @@ class ShortcutGuideModule : public PowertoyModuleIface { Logger::warn("Failed to initialize Shortcut Guide start shortcut"); } - try - { - // Parse Legacy windows key press behavior settings - auto jsonUseLegacyWinKeyBehaviorObject = settingsObject.GetNamedObject(L"properties").GetNamedObject(L"use_legacy_press_win_key_behavior"); - m_shouldReactToPressedWinKey = jsonUseLegacyWinKeyBehaviorObject.GetNamedBoolean(L"value"); - auto jsonPressTimeForGlobalWindowsShortcutsObject = settingsObject.GetNamedObject(L"properties").GetNamedObject(L"press_time"); - auto jsonPressTimeForTaskbarIconShortcutsObject = settingsObject.GetNamedObject(L"properties").GetNamedObject(L"press_time_for_taskbar_icon_shortcuts"); - int value = static_cast(jsonPressTimeForGlobalWindowsShortcutsObject.GetNamedNumber(L"value")); - if (value >= 0) - { - m_millisecondsWinKeyPressTimeForGlobalWindowsShortcuts = value; - } - else - { - throw std::runtime_error("Invalid Press Time Windows Shortcuts value"); - } - value = static_cast(jsonPressTimeForTaskbarIconShortcutsObject.GetNamedNumber(L"value")); - if (value >= 0) - { - m_millisecondsWinKeyPressTimeForTaskbarIconShortcuts = value; - } - else - { - throw std::runtime_error("Invalid Press Time Taskbar Shortcuts value"); - } - } - catch (...) - { - Logger::warn("Failed to get legacy win key behavior settings"); - } } else { diff --git a/src/runner/main.cpp b/src/runner/main.cpp index 527cf15bbbe7..e4331f365a93 100644 --- a/src/runner/main.cpp +++ b/src/runner/main.cpp @@ -153,7 +153,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow L"PowerToys.KeyboardManager.dll", L"PowerToys.Launcher.dll", L"WinUI3Apps/PowerToys.PowerRenameExt.dll", - L"PowerToys.ShortcutGuideModuleInterface.dll", + L"WinUI3Apps/PowerToys.ShortcutGuideModuleInterface.dll", L"PowerToys.ColorPicker.dll", L"PowerToys.AwakeModuleInterface.dll", L"PowerToys.FindMyMouse.dll", diff --git a/src/settings-ui/Settings.UI.Library/ShortcutGuideProperties.cs b/src/settings-ui/Settings.UI.Library/ShortcutGuideProperties.cs index d34a2f748a09..1895b47a9a17 100644 --- a/src/settings-ui/Settings.UI.Library/ShortcutGuideProperties.cs +++ b/src/settings-ui/Settings.UI.Library/ShortcutGuideProperties.cs @@ -15,34 +15,22 @@ public class ShortcutGuideProperties public ShortcutGuideProperties() { - OverlayOpacity = new IntProperty(90); - UseLegacyPressWinKeyBehavior = new BoolProperty(false); - PressTimeForGlobalWindowsShortcuts = new IntProperty(900); - PressTimeForTaskbarIconShortcuts = new IntProperty(900); Theme = new StringProperty("system"); DisabledApps = new StringProperty(); OpenShortcutGuide = DefaultOpenShortcutGuide; + FirstRun = new BoolProperty(true); } [JsonPropertyName("open_shortcutguide")] public HotkeySettings OpenShortcutGuide { get; set; } - [JsonPropertyName("overlay_opacity")] - public IntProperty OverlayOpacity { get; set; } - - [JsonPropertyName("use_legacy_press_win_key_behavior")] - public BoolProperty UseLegacyPressWinKeyBehavior { get; set; } - - [JsonPropertyName("press_time")] - public IntProperty PressTimeForGlobalWindowsShortcuts { get; set; } - - [JsonPropertyName("press_time_for_taskbar_icon_shortcuts")] - public IntProperty PressTimeForTaskbarIconShortcuts { get; set; } - [JsonPropertyName("theme")] public StringProperty Theme { get; set; } [JsonPropertyName("disabled_apps")] public StringProperty DisabledApps { get; set; } + + [JsonPropertyName("first_run")] + public BoolProperty FirstRun { get; set; } } } diff --git a/src/settings-ui/Settings.UI.Library/ShortcutGuideSettings.cs b/src/settings-ui/Settings.UI.Library/ShortcutGuideSettings.cs index c39e757fe308..2be444f0ff5c 100644 --- a/src/settings-ui/Settings.UI.Library/ShortcutGuideSettings.cs +++ b/src/settings-ui/Settings.UI.Library/ShortcutGuideSettings.cs @@ -19,7 +19,7 @@ public ShortcutGuideSettings() { Name = ModuleName; Properties = new ShortcutGuideProperties(); - Version = "1.0"; + Version = "2.0"; } public string GetModuleName() diff --git a/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/ShortcutGuide.cs b/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/ShortcutGuide.cs index 3613d0cfa32a..6d4cc893f565 100644 --- a/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/ShortcutGuide.cs +++ b/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/ShortcutGuide.cs @@ -47,7 +47,6 @@ public void OriginalFilesModificationTest(string version, string fileName) // Verify that the old settings persisted Assert.AreEqual(originalGeneralSettings.Enabled.ShortcutGuide, viewModel.IsEnabled); - Assert.AreEqual(originalSettings.Properties.OverlayOpacity.Value, viewModel.OverlayOpacity); // Verify that the stub file was used var expectedCallCount = 2; // once via the view model, and once by the test (GetSettings) @@ -104,21 +103,5 @@ public void ThemeIndexShouldSetThemeToDarkWhenSuccessful() Func isDark = s => JsonSerializer.Deserialize(s).Properties.Theme.Value == "dark"; settingsUtilsMock.Verify(x => x.SaveSettings(It.Is(y => isDark(y)), It.IsAny(), It.IsAny()), Times.Once); } - - [TestMethod] - public void OverlayOpacityShouldSeOverlayOpacityToOneHundredWhenSuccessful() - { - // Arrange - var settingsUtilsMock = new Mock(); - ShortcutGuideViewModel viewModel = new ShortcutGuideViewModel(settingsUtilsMock.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockShortcutGuideSettingsUtils.Object), msg => { return 0; }, ShortCutGuideTestFolderName); - Assert.AreEqual(90, viewModel.OverlayOpacity); - - // Act - viewModel.OverlayOpacity = 100; - - // Assert - Func equal100 = s => JsonSerializer.Deserialize(s).Properties.OverlayOpacity.Value == 100; - settingsUtilsMock.Verify(x => x.SaveSettings(It.Is(y => equal100(y)), It.IsAny(), It.IsAny()), Times.Once); - } } } diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/ShortcutGuide.png b/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/ShortcutGuide.png index ccef1f4c312e..7e639fdb9029 100644 Binary files a/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/ShortcutGuide.png and b/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/ShortcutGuide.png differ diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Modules/ShortcutGuide.png b/src/settings-ui/Settings.UI/Assets/Settings/Modules/ShortcutGuide.png index 85610c419044..7e639fdb9029 100644 Binary files a/src/settings-ui/Settings.UI/Assets/Settings/Modules/ShortcutGuide.png and b/src/settings-ui/Settings.UI/Assets/Settings/Modules/ShortcutGuide.png differ diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml index d3f3d7e2572e..993b1df3bf8a 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShellPage.xaml @@ -156,7 +156,11 @@ + Tag="ShortcutGuide"> + + + + .GetInstance(new SettingsUtils()).SettingsConfig.Properties; - if ((bool)settingsProperties.UseLegacyPressWinKeyBehavior.Value) - { - HotkeyControl.Keys = new List { 92 }; - } - else - { - HotkeyControl.Keys = settingsProperties.OpenShortcutGuide.GetKeysList(); - } + HotkeyControl.Keys = settingsProperties.OpenShortcutGuide.GetKeysList(); } protected override void OnNavigatedFrom(NavigationEventArgs e) diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml index d1a4c8ea8b7f..0a570fac21b3 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml @@ -163,7 +163,11 @@ + Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ShortcutGuide.png}"> + + + + - - - - - - - - + - - - - - - - - - - @@ -90,14 +46,6 @@ - - - - @@ -126,5 +74,8 @@ + + + \ No newline at end of file diff --git a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw index 76f15a390c82..305521ba5598 100644 --- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw @@ -496,7 +496,7 @@ Product name: Navigation view item name for PowerRename - Shortcut Guide + Shortcut Guide V2 Product name: Navigation view item name for Shortcut Guide @@ -1082,7 +1082,7 @@ Inactive color - Shows a help overlay with Windows shortcuts. + Shows a help overlay with shortcuts for your apps and Windows. Press duration before showing global Windows shortcuts (ms) @@ -1092,7 +1092,7 @@ Activation method - Use a shortcut or press the Windows key for some time to activate + Use a shortcut to activate Shortcut Guide Custom shortcut @@ -1350,7 +1350,7 @@ do not loc - Shortcut Guide + Shortcut Guide V2 Shortcut Guide @@ -2029,7 +2029,7 @@ Made with 💗 by Microsoft and the PowerToys community. Screen Ruler is a quick and easy way to measure pixels on your screen. - Shortcut Guide presents the user with a listing of available shortcuts for the current state of the desktop. + The new Shortcut Guide displays keyboard shortcuts for your apps and for the Windows environment. A collection of utilities to enhance your mouse. @@ -2221,7 +2221,7 @@ From there, simply click on one of the supported files in the File Explorer and Do not localize this string - Shortcut Guide + Shortcut Guide V2 Do not localize this string diff --git a/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs index 8dd97c85fa1b..e66b3c07fee8 100644 --- a/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs @@ -72,6 +72,7 @@ private void AddDashboardListItem(ModuleType moduleType) var newItem = new DashboardListItem() { Tag = moduleType, + IsNew = moduleType == ModuleType.ShortcutGuide, Label = resourceLoader.GetString(ModuleHelper.GetModuleLabelResourceName(moduleType)), IsEnabled = gpo == GpoRuleConfigured.Enabled || (gpo != GpoRuleConfigured.Disabled && ModuleHelper.GetIsModuleEnabled(generalSettingsConfig, moduleType)), IsLocked = gpo == GpoRuleConfigured.Enabled || gpo == GpoRuleConfigured.Disabled, @@ -424,9 +425,7 @@ private ObservableCollection GetModuleItemsShortcutGuide() { ISettingsRepository moduleSettingsRepository = SettingsRepository.GetInstance(new SettingsUtils()); - var shortcut = moduleSettingsRepository.SettingsConfig.Properties.UseLegacyPressWinKeyBehavior.Value - ? new List { 92 } // Right Windows key code - : moduleSettingsRepository.SettingsConfig.Properties.OpenShortcutGuide.GetKeysList(); + var shortcut = moduleSettingsRepository.SettingsConfig.Properties.OpenShortcutGuide.GetKeysList(); var list = new List { diff --git a/src/settings-ui/Settings.UI/ViewModels/Flyout/LauncherViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/Flyout/LauncherViewModel.cs index a1db1e2dd01a..faa8e0cce331 100644 --- a/src/settings-ui/Settings.UI/ViewModels/Flyout/LauncherViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/Flyout/LauncherViewModel.cs @@ -112,9 +112,7 @@ private void ModuleEnabledChanged() private string GetShortcutGuideToolTip() { var shortcutGuideSettings = SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig; - return shortcutGuideSettings.Properties.UseLegacyPressWinKeyBehavior.Value - ? "Win" - : shortcutGuideSettings.Properties.OpenShortcutGuide.ToString(); + return shortcutGuideSettings.Properties.OpenShortcutGuide.ToString(); } internal void StartBugReport() diff --git a/src/settings-ui/Settings.UI/ViewModels/ShortcutGuideViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/ShortcutGuideViewModel.cs index 6ae2dd0746d7..527d5877e73a 100644 --- a/src/settings-ui/Settings.UI/ViewModels/ShortcutGuideViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/ShortcutGuideViewModel.cs @@ -50,10 +50,6 @@ public ShortcutGuideViewModel(ISettingsUtils settingsUtils, ISettingsRepository< InitializeEnabledValue(); - _useLegacyPressWinKeyBehavior = Settings.Properties.UseLegacyPressWinKeyBehavior.Value; - _pressTimeForGlobalWindowsShortcuts = Settings.Properties.PressTimeForGlobalWindowsShortcuts.Value; - _pressTimeForTaskbarIconShortcuts = Settings.Properties.PressTimeForTaskbarIconShortcuts.Value; - _opacity = Settings.Properties.OverlayOpacity.Value; _disabledApps = Settings.Properties.DisabledApps.Value; switch (Settings.Properties.Theme.Value) @@ -83,10 +79,6 @@ private void InitializeEnabledValue() private bool _enabledStateIsGPOConfigured; private bool _isEnabled; private int _themeIndex; - private bool _useLegacyPressWinKeyBehavior; - private int _pressTimeForGlobalWindowsShortcuts; - private int _pressTimeForTaskbarIconShortcuts; - private int _opacity; public bool IsEnabled { @@ -163,78 +155,6 @@ public int ThemeIndex } } - public int OverlayOpacity - { - get - { - return _opacity; - } - - set - { - if (_opacity != value) - { - _opacity = value; - Settings.Properties.OverlayOpacity.Value = value; - NotifyPropertyChanged(); - } - } - } - - public bool UseLegacyPressWinKeyBehavior - { - get - { - return _useLegacyPressWinKeyBehavior; - } - - set - { - if (_useLegacyPressWinKeyBehavior != value) - { - _useLegacyPressWinKeyBehavior = value; - Settings.Properties.UseLegacyPressWinKeyBehavior.Value = value; - NotifyPropertyChanged(); - } - } - } - - public int PressTime - { - get - { - return _pressTimeForGlobalWindowsShortcuts; - } - - set - { - if (_pressTimeForGlobalWindowsShortcuts != value) - { - _pressTimeForGlobalWindowsShortcuts = value; - Settings.Properties.PressTimeForGlobalWindowsShortcuts.Value = value; - NotifyPropertyChanged(); - } - } - } - - public int DelayTime - { - get - { - return _pressTimeForTaskbarIconShortcuts; - } - - set - { - if (_pressTimeForTaskbarIconShortcuts != value) - { - _pressTimeForTaskbarIconShortcuts = value; - Settings.Properties.PressTimeForTaskbarIconShortcuts.Value = value; - NotifyPropertyChanged(); - } - } - } - public string DisabledApps { get