diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 8a3baef843..cf5b2ac09a 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -112,6 +112,7 @@ atlstr attr Attribs aumid +Aut AUTHN AUTOAPPEND autocomplete diff --git a/.pipelines/pipeline.user.windows.yml b/.pipelines/pipeline.user.windows.yml index 6d68405586..0e72e144eb 100644 --- a/.pipelines/pipeline.user.windows.yml +++ b/.pipelines/pipeline.user.windows.yml @@ -147,7 +147,8 @@ build: - 'modules\launcher\Wox.Plugin.dll' - 'modules\Microsoft.Launcher.dll' - 'modules\PowerRename\PowerRenameExt.dll' - - 'modules\ShortcutGuide\ShortcutGuide.dll' + - 'modules\ShortcutGuide\ShortcutGuide\PowerToys.ShortcutGuide.exe' + - 'modules\ShortcutGuide\ShortcutGuideModuleInterface\ShortcutGuideModuleInterface.dll' - 'Notifications.dll' - 'os-detection.dll' - 'PowerToys.exe' diff --git a/PowerToys.sln b/PowerToys.sln index a8eb6ef7ae..cd31c81bc7 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -22,15 +22,12 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "runner", "src\runner\runner {0B43679E-EDFA-4DA0-AD30-F4628B308B1B} = {0B43679E-EDFA-4DA0-AD30-F4628B308B1B} {B25AC7A5-FB9F-4789-B392-D5C85E948670} = {B25AC7A5-FB9F-4789-B392-D5C85E948670} {AF2349B8-E5B6-4004-9502-687C1C7730B1} = {AF2349B8-E5B6-4004-9502-687C1C7730B1} - {A46629C4-1A6C-40FA-A8B6-10E5102BB0BA} = {A46629C4-1A6C-40FA-A8B6-10E5102BB0BA} {17DA04DF-E393-4397-9CF0-84DABE11032E} = {17DA04DF-E393-4397-9CF0-84DABE11032E} {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} = {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} {655C9AF2-18D3-4DA6-80E4-85504A7722BA} = {655C9AF2-18D3-4DA6-80E4-85504A7722BA} {89F34AF7-1C34-4A72-AA6E-534BCF972BD9} = {89F34AF7-1C34-4A72-AA6E-534BCF972BD9} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ShortcutGuide", "src\modules\shortcut_guide\shortcut_guide.vcxproj", "{A46629C4-1A6C-40FA-A8B6-10E5102BB0BA}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "modules", "modules", "{4574FDD0-F61D-4376-98BF-E5A1262C11EC}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "interface", "interface", "{3BB8493E-D18E-4485-A320-CB40F90F55AE}" @@ -324,6 +321,12 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManagerEditorLibrar EndProject 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}") = "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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -334,10 +337,6 @@ Global {9412D5C6-2CF2-4FC2-A601-B55508EA9B27}.Debug|x64.Build.0 = Debug|x64 {9412D5C6-2CF2-4FC2-A601-B55508EA9B27}.Release|x64.ActiveCfg = Release|x64 {9412D5C6-2CF2-4FC2-A601-B55508EA9B27}.Release|x64.Build.0 = Release|x64 - {A46629C4-1A6C-40FA-A8B6-10E5102BB0BA}.Debug|x64.ActiveCfg = Debug|x64 - {A46629C4-1A6C-40FA-A8B6-10E5102BB0BA}.Debug|x64.Build.0 = Debug|x64 - {A46629C4-1A6C-40FA-A8B6-10E5102BB0BA}.Release|x64.ActiveCfg = Release|x64 - {A46629C4-1A6C-40FA-A8B6-10E5102BB0BA}.Release|x64.Build.0 = Release|x64 {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Debug|x64.ActiveCfg = Debug|x64 {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Debug|x64.Build.0 = Debug|x64 {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Release|x64.ActiveCfg = Release|x64 @@ -660,12 +659,19 @@ Global {62173D9A-6724-4C00-A1C8-FB646480A9EC}.Debug|x64.Build.0 = Debug|x64 {62173D9A-6724-4C00-A1C8-FB646480A9EC}.Release|x64.ActiveCfg = Release|x64 {62173D9A-6724-4C00-A1C8-FB646480A9EC}.Release|x64.Build.0 = Release|x64 + {2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Debug|x64.ActiveCfg = Debug|x64 + {2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Debug|x64.Build.0 = Debug|x64 + {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|x64.ActiveCfg = Debug|x64 + {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Debug|x64.Build.0 = Debug|x64 + {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Release|x64.ActiveCfg = Release|x64 + {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {A46629C4-1A6C-40FA-A8B6-10E5102BB0BA} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {3BB8493E-D18E-4485-A320-CB40F90F55AE} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} @@ -759,6 +765,9 @@ Global {8DF78B53-200E-451F-9328-01EB907193AE} = {38BDB927-829B-4C65-9CD9-93FB05D66D65} {23D2070D-E4AD-4ADD-85A7-083D9C76AD49} = {38BDB927-829B-4C65-9CD9-93FB05D66D65} {62173D9A-6724-4C00-A1C8-FB646480A9EC} = {38BDB927-829B-4C65-9CD9-93FB05D66D65} + {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} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index 7787e2984d..b706692f8e 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -6,13 +6,14 @@ - - - + + + - + - + + + + + + @@ -355,21 +361,21 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + @@ -582,9 +588,15 @@ - - - + + + + + + + + + @@ -786,8 +798,9 @@ - - + + + diff --git a/src/common/SettingsAPI/settings_objects.h b/src/common/SettingsAPI/settings_objects.h index 95e3990244..0a8b11c2d4 100644 --- a/src/common/SettingsAPI/settings_objects.h +++ b/src/common/SettingsAPI/settings_objects.h @@ -155,7 +155,33 @@ namespace PowerToysSettings return get_modifiers_repeat() | MOD_NOREPEAT; } - protected: + std::wstring to_string() + { + std::wstring result = L""; + if (shift_pressed()) + { + result += L"shift+"; + } + + if (ctrl_pressed()) + { + result += L"ctrl+"; + } + + if (win_pressed()) + { + result += L"win+"; + } + + if (alt_pressed()) + { + result += L"alt+"; + } + + result += key_from_code(get_code()); + return result; + } + static std::wstring key_from_code(UINT key_code) { auto layout = GetKeyboardLayout(0); @@ -209,6 +235,8 @@ namespace PowerToysSettings } return L"(Key " + std::to_wstring(key_code) + L")"; } + + protected: HotkeyObject(json::JsonObject hotkey_json) : m_json(std::move(hotkey_json)) { diff --git a/src/common/interop/interop.cpp b/src/common/interop/interop.cpp index 65be213234..f7d466cd92 100644 --- a/src/common/interop/interop.cpp +++ b/src/common/interop/interop.cpp @@ -150,10 +150,6 @@ public static String ^ ShowColorPickerSharedEvent() { return gcnew String(CommonSharedConstants::SHOW_COLOR_PICKER_SHARED_EVENT); - } - - static String ^ ShowShortcutGuideSharedEvent() { - return gcnew String(CommonSharedConstants::SHOW_SHORTCUT_GUIDE_SHARED_EVENT); } }; } diff --git a/src/common/interop/shared_constants.h b/src/common/interop/shared_constants.h index 34476563b4..a61c72ccff 100644 --- a/src/common/interop/shared_constants.h +++ b/src/common/interop/shared_constants.h @@ -22,8 +22,7 @@ namespace CommonSharedConstants // Path to the event used to show Color Picker const wchar_t SHOW_COLOR_PICKER_SHARED_EVENT[] = L"Local\\ShowColorPickerEvent-8c46be2a-3e05-4186-b56b-4ae986ef2525"; - // Path to the event used to show Shortcut Guide - const wchar_t SHOW_SHORTCUT_GUIDE_SHARED_EVENT[] = L"Local\\ShowShortcutGuideEvent-6982d682-7462-404f-95af-86ae3f089c4f"; + const wchar_t SHORTCUT_GUIDE_EXIT_EVENT[] = L"Local\\ShortcutGuide-ExitEvent-35697cdd-a3d2-47d6-a246-34efcc73eac0"; // Max DWORD for key code to disable keys. const int VK_DISABLED = 0x100; diff --git a/src/modules/shortcut_guide/LocProject.json b/src/modules/ShortcutGuide/ShortcutGuide/LocProject.json similarity index 55% rename from src/modules/shortcut_guide/LocProject.json rename to src/modules/ShortcutGuide/ShortcutGuide/LocProject.json index 3ae183d254..9c9c425043 100644 --- a/src/modules/shortcut_guide/LocProject.json +++ b/src/modules/ShortcutGuide/ShortcutGuide/LocProject.json @@ -4,9 +4,9 @@ "LanguageSet": "Azure_Languages", "LocItems": [ { - "SourceFile": "src\\modules\\shortcut_guide\\Resources.resx", + "SourceFile": "src\\modules\\ShortcutGuide\\ShortcutGuide\\Resources.resx", "CopyOption": "LangIDOnName", - "OutputPath": "src\\modules\\shortcut_guide" + "OutputPath": "src\\modules\\ShortcutGuide\\ShortcutGuide" } ] } diff --git a/src/modules/ShortcutGuide/ShortcutGuide/PropertySheet.props b/src/modules/ShortcutGuide/ShortcutGuide/PropertySheet.props new file mode 100644 index 0000000000..b0c622690f --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide/PropertySheet.props @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/src/modules/shortcut_guide/Resources.resx b/src/modules/ShortcutGuide/ShortcutGuide/Resources.resx similarity index 97% rename from src/modules/shortcut_guide/Resources.resx rename to src/modules/ShortcutGuide/ShortcutGuide/Resources.resx index dd305d7917..34942e7a52 100644 --- a/src/modules/shortcut_guide/Resources.resx +++ b/src/modules/ShortcutGuide/ShortcutGuide/Resources.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - How long to press the Windows key before showing the Shortcut Guide (ms) - Opacity of the Shortcut Guide's overlay background (%) diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Shortcut-Guide.ico b/src/modules/ShortcutGuide/ShortcutGuide/Shortcut-Guide.ico new file mode 100644 index 0000000000..eb90c1450f Binary files /dev/null and b/src/modules/ShortcutGuide/ShortcutGuide/Shortcut-Guide.ico differ diff --git a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.base.rc b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.base.rc new file mode 100644 index 0000000000..1f88309dd2 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.base.rc @@ -0,0 +1,54 @@ +#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 new file mode 100644 index 0000000000..68d17e1db8 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.exe.manifest @@ -0,0 +1,8 @@ + + + + + PerMonitorV2 + + + \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj new file mode 100644 index 0000000000..f9f67fcd76 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj @@ -0,0 +1,189 @@ + + + + + + + + true + true + true + true + 15.0 + {2edb3eb4-fa92-4bff-b2d8-566584837231} + Win32Proj + ShortcutGuide + 10.0.17134.0 + + + + Application + v140 + v141 + v142 + Unicode + + + true + true + + + false + true + false + + + + + + + + + + + + + + + PowerToys.$(MSBuildProjectName) + + + $(SolutionDir)$(Platform)\$(Configuration)\modules\ShortcutGuide\$(ProjectName)\ + $(SolutionDir)$(Platform)\$(Configuration)\obj\ShortcutGuide\$(ProjectName)\ + + + + ;..\..\..\common\inc;..\..\..\common\Telemetry;..\..\..\;..\;%(AdditionalIncludeDirectories) + + + ole32.lib;Shell32.lib;OleAut32.lib;Dbghelp.lib;Dwmapi.lib;Dcomp.lib;Shlwapi.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + + + + + + + + + + + + Document + $(OutDir)\svgs + + + Document + $(OutDir)\svgs + + + Document + $(OutDir)\svgs + + + Document + $(OutDir)\svgs + + + Document + $(OutDir)\svgs + + + Document + $(OutDir)\svgs + + + Document + $(OutDir)\svgs + + + Document + $(OutDir)\svgs + + + Document + $(OutDir)\svgs + + + Document + $(OutDir)\svgs + + + Document + $(OutDir)\svgs + + + Document + $(OutDir)\svgs + + + Document + $(OutDir)\svgs + + + + + {caba8dfb-823b-4bf2-93ac-3f31984150d9} + + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + + {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 new file mode 100644 index 0000000000..f86e34e503 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj.filters @@ -0,0 +1,180 @@ + + + + + {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;tiff;tif;png;wav;mfcribbon-ms + + + {800507f8-718d-48d1-aa56-96c040795b8a} + + + {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 + + + + + svgs + + + svgs + + + svgs + + + svgs + + + svgs + + + svgs + + + svgs + + + svgs + + + svgs + + + svgs + + + svgs + + + svgs + + + svgs + + + + + Resource Files + + + + + Generated Files + + + + + Resource Files + + + + + + \ No newline at end of file diff --git a/src/modules/shortcut_guide/ShortcutGuideConstants.h b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuideConstants.h similarity index 100% rename from src/modules/shortcut_guide/ShortcutGuideConstants.h rename to src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuideConstants.h diff --git a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuideSettings.h b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuideSettings.h new file mode 100644 index 0000000000..224c19390b --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuideSettings.h @@ -0,0 +1,10 @@ +#pragma once +#include + +struct ShortcutGuideSettings +{ + std::wstring hotkey = L"shift+win+/"; + int overlayOpacity = 90; + std::wstring theme = L"system"; + std::wstring disabledApps = L""; +}; diff --git a/src/modules/shortcut_guide/animation.cpp b/src/modules/ShortcutGuide/ShortcutGuide/animation.cpp similarity index 100% rename from src/modules/shortcut_guide/animation.cpp rename to src/modules/ShortcutGuide/ShortcutGuide/animation.cpp diff --git a/src/modules/shortcut_guide/animation.h b/src/modules/ShortcutGuide/ShortcutGuide/animation.h similarity index 100% rename from src/modules/shortcut_guide/animation.h rename to src/modules/ShortcutGuide/ShortcutGuide/animation.h diff --git a/src/modules/shortcut_guide/d2d_svg.cpp b/src/modules/ShortcutGuide/ShortcutGuide/d2d_svg.cpp similarity index 88% rename from src/modules/shortcut_guide/d2d_svg.cpp rename to src/modules/ShortcutGuide/ShortcutGuide/d2d_svg.cpp index a839eef9ba..08c5c79fe8 100644 --- a/src/modules/shortcut_guide/d2d_svg.cpp +++ b/src/modules/ShortcutGuide/ShortcutGuide/d2d_svg.cpp @@ -5,17 +5,20 @@ D2DSVG& D2DSVG::load(const std::wstring& filename, ID2D1DeviceContext5* d2d_dc) { svg = nullptr; winrt::com_ptr svg_stream; - winrt::check_hresult(SHCreateStreamOnFileEx(filename.c_str(), - STGM_READ, - FILE_ATTRIBUTE_NORMAL, - FALSE, - nullptr, - svg_stream.put())); + auto h = SHCreateStreamOnFileEx(filename.c_str(), + STGM_READ, + FILE_ATTRIBUTE_NORMAL, + FALSE, + nullptr, + svg_stream.put()); + winrt::check_hresult(h); - winrt::check_hresult(d2d_dc->CreateSvgDocument( + auto h1 = d2d_dc->CreateSvgDocument( svg_stream.get(), D2D1::SizeF(1, 1), - svg.put())); + svg.put()); + + winrt::check_hresult(h1); winrt::com_ptr root; svg->GetRoot(root.put()); diff --git a/src/modules/shortcut_guide/d2d_svg.h b/src/modules/ShortcutGuide/ShortcutGuide/d2d_svg.h similarity index 100% rename from src/modules/shortcut_guide/d2d_svg.h rename to src/modules/ShortcutGuide/ShortcutGuide/d2d_svg.h diff --git a/src/modules/shortcut_guide/d2d_text.cpp b/src/modules/ShortcutGuide/ShortcutGuide/d2d_text.cpp similarity index 100% rename from src/modules/shortcut_guide/d2d_text.cpp rename to src/modules/ShortcutGuide/ShortcutGuide/d2d_text.cpp diff --git a/src/modules/shortcut_guide/d2d_text.h b/src/modules/ShortcutGuide/ShortcutGuide/d2d_text.h similarity index 100% rename from src/modules/shortcut_guide/d2d_text.h rename to src/modules/ShortcutGuide/ShortcutGuide/d2d_text.h diff --git a/src/modules/shortcut_guide/d2d_window.cpp b/src/modules/ShortcutGuide/ShortcutGuide/d2d_window.cpp similarity index 96% rename from src/modules/shortcut_guide/d2d_window.cpp rename to src/modules/ShortcutGuide/ShortcutGuide/d2d_window.cpp index b1161e99a9..095e7dcba8 100644 --- a/src/modules/shortcut_guide/d2d_window.cpp +++ b/src/modules/ShortcutGuide/ShortcutGuide/d2d_window.cpp @@ -3,8 +3,7 @@ #include -D2DWindow::D2DWindow(std::optional>> _pre_wnd_proc) : - pre_wnd_proc(std::move(_pre_wnd_proc)) +D2DWindow::D2DWindow() { static const WCHAR* class_name = L"PToyD2DPopup"; WNDCLASS wc = {}; @@ -190,10 +189,6 @@ D2DWindow* D2DWindow::this_from_hwnd(HWND window) LRESULT __stdcall D2DWindow::d2d_window_proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) { auto self = this_from_hwnd(window); - if (self && self->pre_wnd_proc.has_value()) - { - (*self->pre_wnd_proc)(window, message, wparam, lparam); - } switch (message) { case WM_NCCREATE: diff --git a/src/modules/shortcut_guide/d2d_window.h b/src/modules/ShortcutGuide/ShortcutGuide/d2d_window.h similarity index 91% rename from src/modules/shortcut_guide/d2d_window.h rename to src/modules/ShortcutGuide/ShortcutGuide/d2d_window.h index 94cc02b71b..d343719a0a 100644 --- a/src/modules/shortcut_guide/d2d_window.h +++ b/src/modules/ShortcutGuide/ShortcutGuide/d2d_window.h @@ -17,7 +17,7 @@ class D2DWindow { public: - D2DWindow(std::optional>> pre_wnd_proc = std::nullopt); + D2DWindow(); void show(UINT x, UINT y, UINT width, UINT height); void hide(); void initialize(); @@ -62,6 +62,4 @@ protected: winrt::com_ptr d2d_factory; winrt::com_ptr d2d_device; winrt::com_ptr d2d_dc; - - std::optional>> pre_wnd_proc; }; diff --git a/src/modules/ShortcutGuide/ShortcutGuide/main.cpp b/src/modules/ShortcutGuide/ShortcutGuide/main.cpp new file mode 100644 index 0000000000..9b219a259c --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide/main.cpp @@ -0,0 +1,129 @@ +#include "pch.h" +#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); + InitUnhandledExceptionHandler_x64(); + 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::UnregisterProvider(); + return 0; +} diff --git a/src/modules/shortcut_guide/native_event_waiter.cpp b/src/modules/ShortcutGuide/ShortcutGuide/native_event_waiter.cpp similarity index 100% rename from src/modules/shortcut_guide/native_event_waiter.cpp rename to src/modules/ShortcutGuide/ShortcutGuide/native_event_waiter.cpp diff --git a/src/modules/shortcut_guide/native_event_waiter.h b/src/modules/ShortcutGuide/ShortcutGuide/native_event_waiter.h similarity index 100% rename from src/modules/shortcut_guide/native_event_waiter.h rename to src/modules/ShortcutGuide/ShortcutGuide/native_event_waiter.h diff --git a/src/modules/shortcut_guide/overlay_window.cpp b/src/modules/ShortcutGuide/ShortcutGuide/overlay_window.cpp similarity index 91% rename from src/modules/shortcut_guide/overlay_window.cpp rename to src/modules/ShortcutGuide/ShortcutGuide/overlay_window.cpp index 0d07c4c191..908a67ad27 100644 --- a/src/modules/shortcut_guide/overlay_window.cpp +++ b/src/modules/ShortcutGuide/ShortcutGuide/overlay_window.cpp @@ -6,7 +6,6 @@ #include #include -#include "keyboard_state.h" #include "shortcut_guide.h" #include "trace.h" #include "Generated Files/resource.h" @@ -268,8 +267,8 @@ D2D1_RECT_F D2DOverlaySVG::get_snap_right() const return result; } -D2DOverlayWindow::D2DOverlayWindow(std::optional>> pre_wnd_proc) : - total_screen({}), animation(0.3), D2DWindow(std::move(pre_wnd_proc)) +D2DOverlayWindow::D2DOverlayWindow() : + total_screen({}), animation(0.3), D2DWindow() { tasklist_thread = std::thread([&] { while (running) @@ -361,7 +360,6 @@ void D2DOverlayWindow::show(HWND active_window, bool snappable) shown_start_time = std::chrono::steady_clock::now(); lock.unlock(); D2DWindow::show(primary_screen.left(), primary_screen.top(), primary_screen.width(), primary_screen.height()); - key_pressed.clear(); // Check if taskbar is auto-hidden. If so, don't display the number arrows APPBARDATA param = {}; param.cbSize = sizeof(APPBARDATA); @@ -374,115 +372,6 @@ void D2DOverlayWindow::show(HWND active_window, bool snappable) } } -void D2DOverlayWindow::animate(int vk_code) -{ - animate(vk_code, 0); -} -void D2DOverlayWindow::animate(int vk_code, int offset) -{ - if (!initialized || !use_overlay) - { - return; - } - bool done = false; - for (auto& animation : key_animations) - { - if (animation.vk_code == vk_code) - { - animation.animation.reset(0.1, 0, 1); - done = true; - } - } - if (done) - { - return; - } - AnimateKeys animation; - std::wstring id; - animation.vk_code = vk_code; - winrt::com_ptr button_letter, parent; - if (vk_code >= 0x41 && vk_code <= 0x5A) - { - id.push_back('A' + (vk_code - 0x41)); - } - else - { - switch (vk_code) - { - case VK_SNAPSHOT: - case VK_PRINT: - id = L"PrnScr"; - break; - case VK_CONTROL: - case VK_LCONTROL: - case VK_RCONTROL: - id = L"Ctrl"; - break; - case VK_UP: - id = L"KeyUp"; - break; - case VK_LEFT: - id = L"KeyLeft"; - break; - case VK_DOWN: - id = L"KeyDown"; - break; - case VK_RIGHT: - id = L"KeyRight"; - break; - case VK_OEM_PLUS: - case VK_ADD: - id = L"KeyPlus"; - break; - case VK_OEM_MINUS: - case VK_SUBTRACT: - id = L"KeyMinus"; - break; - case VK_TAB: - id = L"Tab"; - break; - case VK_RETURN: - id = L"Enter"; - break; - default: - return; - } - } - - if (offset > 0) - { - id += L"_" + std::to_wstring(offset); - } - button_letter = use_overlay->find_element(id); - if (!button_letter) - { - return; - } - button_letter->GetParent(parent.put()); - if (!parent) - { - return; - } - parent->GetPreviousChild(button_letter.get(), animation.button.put()); - if (!animation.button || !animation.button->IsAttributeSpecified(L"fill")) - { - animation.button = nullptr; - parent->GetNextChild(button_letter.get(), animation.button.put()); - } - if (!animation.button || !animation.button->IsAttributeSpecified(L"fill")) - { - return; - } - winrt::com_ptr paint; - animation.button->GetAttributeValue(L"fill", paint.put()); - paint->GetColor(&animation.original); - animate(vk_code, offset + 1); - std::unique_lock lock(mutex); - animation.animation.reset(0.1, 0, 1); - key_animations.push_back(animation); - key_pressed.push_back(vk_code); -} - void D2DOverlayWindow::on_show() { // show override does everything @@ -490,6 +379,7 @@ void D2DOverlayWindow::on_show() void D2DOverlayWindow::on_hide() { + Logger::trace("D2DOverlayWindow::on_hide()"); tasklist_cv_mutex.lock(); tasklist_update = false; tasklist_cv_mutex.unlock(); @@ -502,10 +392,11 @@ void D2DOverlayWindow::on_hide() // Trace the event only if the overlay window was visible. if (shown_start_time.time_since_epoch().count() > 0) { - Trace::HideGuide(std::chrono::duration_cast(shown_end_time - shown_start_time).count(), key_pressed); + 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 = {}; } - key_pressed.clear(); } D2DOverlayWindow::~D2DOverlayWindow() @@ -758,6 +649,11 @@ void D2DOverlayWindow::render(ID2D1DeviceContext5* d2d_dc) // 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)) diff --git a/src/modules/shortcut_guide/overlay_window.h b/src/modules/ShortcutGuide/ShortcutGuide/overlay_window.h similarity index 92% rename from src/modules/shortcut_guide/overlay_window.h rename to src/modules/ShortcutGuide/ShortcutGuide/overlay_window.h index df0640afef..af5a882d1b 100644 --- a/src/modules/shortcut_guide/overlay_window.h +++ b/src/modules/ShortcutGuide/ShortcutGuide/overlay_window.h @@ -47,18 +47,21 @@ struct AnimateKeys class D2DOverlayWindow : public D2DWindow { public: - D2DOverlayWindow(std::optional>> pre_wnd_proc = std::nullopt); + D2DOverlayWindow(); void show(HWND active_window, bool snappable); - void animate(int vk_code); ~D2DOverlayWindow(); void apply_overlay_opacity(float opacity); void set_theme(const std::wstring& theme); void quick_hide(); HWND get_window_handle(); + void SetWindowCloseType(std::wstring windowCloseType) + { + this->windowCloseType = windowCloseType; + } private: - void animate(int vk_code, int offset); + std::wstring windowCloseType; bool show_thumbnail(const RECT& rect, double alpha); void hide_thumbnail(); virtual void init() override; @@ -70,7 +73,6 @@ private: bool running = true; std::vector key_animations; - std::vector key_pressed; std::vector monitors; ScreenSize total_screen; int monitor_dx = 0, monitor_dy = 0; diff --git a/src/modules/shortcut_guide/packages.config b/src/modules/ShortcutGuide/ShortcutGuide/packages.config similarity index 100% rename from src/modules/shortcut_guide/packages.config rename to src/modules/ShortcutGuide/ShortcutGuide/packages.config diff --git a/src/modules/shortcut_guide/pch.cpp b/src/modules/ShortcutGuide/ShortcutGuide/pch.cpp similarity index 94% rename from src/modules/shortcut_guide/pch.cpp rename to src/modules/ShortcutGuide/ShortcutGuide/pch.cpp index 9e6b2e073e..1d9f38c57d 100644 --- a/src/modules/shortcut_guide/pch.cpp +++ b/src/modules/ShortcutGuide/ShortcutGuide/pch.cpp @@ -1 +1 @@ -#include "pch.h" +#include "pch.h" diff --git a/src/modules/shortcut_guide/pch.h b/src/modules/ShortcutGuide/ShortcutGuide/pch.h similarity index 89% rename from src/modules/shortcut_guide/pch.h rename to src/modules/ShortcutGuide/ShortcutGuide/pch.h index 15baec1a9c..7cfa22c4fa 100644 --- a/src/modules/shortcut_guide/pch.h +++ b/src/modules/ShortcutGuide/ShortcutGuide/pch.h @@ -1,29 +1,30 @@ -#pragma once -#define NOMINMAX -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include \ No newline at end of file +#pragma once +#define NOMINMAX +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/resource.base.h b/src/modules/ShortcutGuide/ShortcutGuide/resource.base.h new file mode 100644 index 0000000000..75f509067c --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide/resource.base.h @@ -0,0 +1,13 @@ +//{{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 new file mode 100644 index 0000000000..89d8618549 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide/shortcut_guide.cpp @@ -0,0 +1,419 @@ +#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* 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. Similary, 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; + } + + bool isWin(int key) + { + return key == VK_LWIN || key == VK_RWIN; + } + + 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"); + instance->CloseWindow(HideWindowType::ESC_PRESSED); + } + + if (wasWinPressed && !isKeyDown(event) && isWin(event.lParam->vkCode)) + { + Logger::trace(L"Win key was released"); + 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"); + instance->CloseWindow(HideWindowType::WIN_SHORTCUT_PRESSED); + } + } + + return CallNextHookEx(NULL, 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"; + } + + return L""; + } +} + +OverlayWindow::OverlayWindow(HWND activeWindow) +{ + 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())); + } +} + +void OverlayWindow::ShowWindow() +{ + winkey_popup = std::make_unique(); + winkey_popup->apply_overlay_opacity(((float)overlayOpacity.value) / 100.0f); + winkey_popup->set_theme(theme.value); + 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) + { + 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""; + 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(); +} + +void OverlayWindow::init_settings() +{ + auto settings = GetSettings(); + overlayOpacity.value = settings.overlayOpacity; + theme.value = settings.theme; + disabledApps.value = settings.disabledApps; + update_disabled_apps(); +} + +bool OverlayWindow::is_disabled_app(wchar_t* exePath) +{ + if (exePath == nullptr) + { + return false; + } + + auto exePathUpper = std::wstring(exePath); + CharUpperBuffW(exePathUpper.data(), (DWORD)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(), (DWORD)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 = (int)properties.GetNamedObject(OverlayOpacity::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/shortcut_guide/shortcut_guide.h b/src/modules/ShortcutGuide/ShortcutGuide/shortcut_guide.h similarity index 51% rename from src/modules/shortcut_guide/shortcut_guide.h rename to src/modules/ShortcutGuide/ShortcutGuide/shortcut_guide.h index 52e6a76662..d9288725d4 100644 --- a/src/modules/shortcut_guide/shortcut_guide.h +++ b/src/modules/ShortcutGuide/ShortcutGuide/shortcut_guide.h @@ -1,90 +1,85 @@ -#pragma once -#include -#include "overlay_window.h" -#include "native_event_waiter.h" - -#include "Generated Files/resource.h" - -#include - -// We support only one instance of the overlay -extern class OverlayWindow* instance; - -class TargetState; - -class OverlayWindow : public PowertoyModuleIface -{ -public: - OverlayWindow(); - - virtual const wchar_t* get_name() override; - virtual const wchar_t* get_key() override; - virtual bool get_config(wchar_t* buffer, int* buffer_size) override; - - virtual void set_config(const wchar_t* config) override; - virtual void enable() override; - virtual void disable() override; - virtual bool is_enabled() override; - - void on_held(); - void on_held_press(DWORD vkCode); - void quick_hide(); - void was_hidden(); - - intptr_t signal_event(LowlevelKeyboardEvent* event); - - virtual void destroy() override; - - bool overlay_visible() const; - - bool is_disabled_app(wchar_t* exePath); - - void get_exe_path(HWND window, wchar_t* exePath); - -private: - std::wstring app_name; - //contains the non localized key of the powertoy - std::wstring app_key; - std::unique_ptr target_state; - std::unique_ptr winkey_popup; - bool _enabled = false; - HHOOK hook_handle; - std::unique_ptr event_waiter; - std::vector disabled_apps_array; - - void init_settings(); - void disable(bool trace_event); - void update_disabled_apps(); - - struct PressTime - { - PCWSTR name = L"press_time"; - int value = 900; // ms - int resourceId = IDS_SETTING_DESCRIPTION_PRESS_TIME; - } pressTime; - - struct OverlayOpacity - { - PCWSTR name = L"overlay_opacity"; - int value = 90; // percent - int resourceId = IDS_SETTING_DESCRIPTION_OVERLAY_OPACITY; - } overlayOpacity; - - struct Theme - { - PCWSTR name = L"theme"; - std::wstring value = L"system"; - 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 - { - PCWSTR name = L"disabled_apps"; - std::wstring value = L""; - } disabledApps; -}; +#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* instance; + +class TargetState; + +enum class HideWindowType +{ + ESC_PRESSED, + WIN_RELEASED, + WIN_SHORTCUT_PRESSED, + THE_SHORTCUT_PRESSED +}; + +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 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; + + 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 OpenShortcut + { + static inline PCWSTR name = L"open_shortcutguide"; + } openShortcut; +}; diff --git a/src/modules/shortcut_guide/start_visible.cpp b/src/modules/ShortcutGuide/ShortcutGuide/start_visible.cpp similarity index 100% rename from src/modules/shortcut_guide/start_visible.cpp rename to src/modules/ShortcutGuide/ShortcutGuide/start_visible.cpp diff --git a/src/modules/shortcut_guide/start_visible.h b/src/modules/ShortcutGuide/ShortcutGuide/start_visible.h similarity index 100% rename from src/modules/shortcut_guide/start_visible.h rename to src/modules/ShortcutGuide/ShortcutGuide/start_visible.h diff --git a/src/runner/svgs/0.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/0.svg similarity index 100% rename from src/runner/svgs/0.svg rename to src/modules/ShortcutGuide/ShortcutGuide/svgs/0.svg diff --git a/src/runner/svgs/1.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/1.svg similarity index 100% rename from src/runner/svgs/1.svg rename to src/modules/ShortcutGuide/ShortcutGuide/svgs/1.svg diff --git a/src/runner/svgs/2.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/2.svg similarity index 100% rename from src/runner/svgs/2.svg rename to src/modules/ShortcutGuide/ShortcutGuide/svgs/2.svg diff --git a/src/runner/svgs/3.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/3.svg similarity index 100% rename from src/runner/svgs/3.svg rename to src/modules/ShortcutGuide/ShortcutGuide/svgs/3.svg diff --git a/src/runner/svgs/4.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/4.svg similarity index 100% rename from src/runner/svgs/4.svg rename to src/modules/ShortcutGuide/ShortcutGuide/svgs/4.svg diff --git a/src/runner/svgs/5.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/5.svg similarity index 100% rename from src/runner/svgs/5.svg rename to src/modules/ShortcutGuide/ShortcutGuide/svgs/5.svg diff --git a/src/runner/svgs/6.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/6.svg similarity index 100% rename from src/runner/svgs/6.svg rename to src/modules/ShortcutGuide/ShortcutGuide/svgs/6.svg diff --git a/src/runner/svgs/7.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/7.svg similarity index 100% rename from src/runner/svgs/7.svg rename to src/modules/ShortcutGuide/ShortcutGuide/svgs/7.svg diff --git a/src/runner/svgs/8.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/8.svg similarity index 100% rename from src/runner/svgs/8.svg rename to src/modules/ShortcutGuide/ShortcutGuide/svgs/8.svg diff --git a/src/runner/svgs/9.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/9.svg similarity index 100% rename from src/runner/svgs/9.svg rename to src/modules/ShortcutGuide/ShortcutGuide/svgs/9.svg diff --git a/src/runner/svgs/no_active_window.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/no_active_window.svg similarity index 100% rename from src/runner/svgs/no_active_window.svg rename to src/modules/ShortcutGuide/ShortcutGuide/svgs/no_active_window.svg diff --git a/src/runner/svgs/overlay.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/overlay.svg similarity index 100% rename from src/runner/svgs/overlay.svg rename to src/modules/ShortcutGuide/ShortcutGuide/svgs/overlay.svg diff --git a/src/runner/svgs/overlay_portrait.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/overlay_portrait.svg similarity index 100% rename from src/runner/svgs/overlay_portrait.svg rename to src/modules/ShortcutGuide/ShortcutGuide/svgs/overlay_portrait.svg diff --git a/src/modules/ShortcutGuide/ShortcutGuide/target_state.cpp b/src/modules/ShortcutGuide/ShortcutGuide/target_state.cpp new file mode 100644 index 0000000000..8b91117323 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide/target_state.cpp @@ -0,0 +1,47 @@ +#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; + 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 new file mode 100644 index 0000000000..c1f51e3f60 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide/target_state.h @@ -0,0 +1,33 @@ +#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/shortcut_guide/tasklist_positions.cpp b/src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.cpp similarity index 100% rename from src/modules/shortcut_guide/tasklist_positions.cpp rename to src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.cpp diff --git a/src/modules/shortcut_guide/tasklist_positions.h b/src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.h similarity index 100% rename from src/modules/shortcut_guide/tasklist_positions.h rename to src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.h diff --git a/src/modules/ShortcutGuide/ShortcutGuide/trace.cpp b/src/modules/ShortcutGuide/ShortcutGuide/trace.cpp new file mode 100644 index 0000000000..d8cb68b18c --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide/trace.cpp @@ -0,0 +1,45 @@ +#include "pch.h" +#include "trace.h" + +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::RegisterProvider() noexcept +{ + TraceLoggingRegister(g_hProvider); +} + +void Trace::UnregisterProvider() noexcept +{ + TraceLoggingUnregister(g_hProvider); +} + +void Trace::SendGuideSession(const __int64 duration_ms, const wchar_t* close_type) noexcept +{ + TraceLoggingWrite( + 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 +{ + TraceLoggingWrite( + 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"), + 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 new file mode 100644 index 0000000000..97f9d75bc8 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide/trace.h @@ -0,0 +1,11 @@ +#pragma once +#include "ShortcutGuideSettings.h" + +class Trace +{ +public: + static void RegisterProvider() noexcept; + static void UnregisterProvider() noexcept; + 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/LocProject.json b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/LocProject.json new file mode 100644 index 0000000000..16dab889b1 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/LocProject.json @@ -0,0 +1,14 @@ +{ + "Projects": [ + { + "LanguageSet": "Azure_Languages", + "LocItems": [ + { + "SourceFile": "src\\modules\\ShortcutGuide\\ShortcutGuideModuleInterface\\Resources.resx", + "CopyOption": "LangIDOnName", + "OutputPath": "src\\modules\\ShortcutGuide\\ShortcutGuideModuleInterface" + } + ] + } + ] +} diff --git a/src/modules/shortcut_guide/README.md b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/README.md similarity index 100% rename from src/modules/shortcut_guide/README.md rename to src/modules/ShortcutGuide/ShortcutGuideModuleInterface/README.md diff --git a/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/Resources.resx b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/Resources.resx new file mode 100644 index 0000000000..e2b948ee20 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/Resources.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Shortcut Guide + + \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.base.rc b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.base.rc new file mode 100644 index 0000000000..ab9f396ec5 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.base.rc @@ -0,0 +1,45 @@ +#include +#include "Generated Files/resource.h" +#include "../../../../common/version/version.h" + +#define APSTUDIO_READONLY_SYMBOLS +#include "winres.h" +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// +// 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/shortcut_guide/shortcut_guide.vcxproj b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj similarity index 50% rename from src/modules/shortcut_guide/shortcut_guide.vcxproj rename to src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj index b165f02eba..22aa591f1f 100644 --- a/src/modules/shortcut_guide/shortcut_guide.vcxproj +++ b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj @@ -1,133 +1,103 @@ - - - - - - Debug - x64 - - - Release - x64 - - - - - - - 15.0 - {A46629C4-1A6C-40FA-A8B6-10E5102BB0BA} - Win32Proj - overlaywindow - 10.0.17134.0 - ShortcutGuide - - - - DynamicLibrary - true - v142 - Unicode - Spectre - - - DynamicLibrary - false - v142 - true - Unicode - Spectre - - - - - - - - - - - - - - - $(SolutionDir)$(Platform)\$(Configuration)\modules\$(ProjectName)\ - $(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\ - - - - ..\..\common\inc;..\..\common\Telemetry;..\..\;..\;%(AdditionalIncludeDirectories) - - - Dwmapi.lib;Dcomp.lib;Shlwapi.lib;%(AdditionalDependencies) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create - - - - - - - - - {caba8dfb-823b-4bf2-93ac-3f31984150d9} - - - {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} - - - {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}. - - - - + + + + + + Debug + x64 + + + Release + x64 + + + + + + + 15.0 + Win32Proj + {2d604c07-51fc-46bb-9eb7-75aecc7f5e81} + ShortcutGuideModuleInterface + 10.0.17134.0 + ShortcutGuideModuleInterface + + + + DynamicLibrary + true + v142 + Unicode + Spectre + + + DynamicLibrary + false + v142 + true + Unicode + Spectre + + + + + + + + + + + + + + + $(SolutionDir)$(Platform)\$(Configuration)\modules\ShortcutGuide\$(ProjectName)\ + $(SolutionDir)$(Platform)\$(Configuration)\obj\ShortcutGuide\$(ProjectName)\ + + + + ;..\..\..\common\inc;..\..\..\common\Telemetry;..\..\..\;..\;%(AdditionalIncludeDirectories) + + + Dwmapi.lib;Dcomp.lib;Shlwapi.lib;%(AdditionalDependencies) + + + + + + + + + + + Create + + + + + + + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + + + + + + + + + + + + + + + + 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/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj.filters b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj.filters new file mode 100644 index 0000000000..831d437b7e --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj.filters @@ -0,0 +1,55 @@ + + + + + {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 + + + {7008d2a1-37df-4ef1-8d2c-e27de2a1a181} + + + + + Header Files + + + Resource Files + + + Generated Files + + + + + Source Files + + + Source Files + + + + + + Resource Files + + + + + Resource Files + + + + + Generated Files + + + \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/dllmain.cpp b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/dllmain.cpp new file mode 100644 index 0000000000..36d88f156d --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/dllmain.cpp @@ -0,0 +1,308 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include "pch.h" + +#include +#include +#include +#include +#include + +#include "../interface/powertoy_module_interface.h" +#include "Generated Files/resource.h" +#include + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) +{ + return TRUE; +} + +class ShortcutGuideModule : public PowertoyModuleIface +{ +public: + ShortcutGuideModule() + { + app_name = GET_RESOURCE_STRING(IDS_SHORTCUT_GUIDE); + app_key = L"Shortcut Guide"; + LoggerHelpers::init_logger(app_key, L"ModuleInterface", LogSettings::shortcutGuideLoggerName); + + std::filesystem::path oldLogPath(PTSettingsHelper::get_module_save_folder_location(app_key)); + oldLogPath.append("ShortcutGuideLogs"); + LoggerHelpers::delete_old_log_folder(oldLogPath); + + exitEvent = CreateEvent(nullptr, false, false, CommonSharedConstants::SHORTCUT_GUIDE_EXIT_EVENT); + if (!exitEvent) + { + Logger::warn(L"Failed to create {} event. {}", CommonSharedConstants::SHORTCUT_GUIDE_EXIT_EVENT, get_last_error_or_default(GetLastError())); + } + + InitSettings(); + } + + virtual const wchar_t* get_name() override + { + return app_name.c_str(); + } + + virtual const wchar_t* get_key() override + { + return app_key.c_str(); + } + + virtual bool get_config(wchar_t* buffer, int* buffer_size) override + { + HINSTANCE hinstance = reinterpret_cast(&__ImageBase); + PowerToysSettings::Settings settings(hinstance, get_name()); + return settings.serialize_to_buffer(buffer, buffer_size); + } + + virtual void set_config(const wchar_t* config) override + { + Logger::trace("set_config()"); + try + { + // Parse the input JSON string. + PowerToysSettings::PowerToyValues values = + PowerToysSettings::PowerToyValues::from_json_string(config, get_key()); + + ParseHotkey(values); + } + catch (std::exception ex) + { + Logger::error("Failed to parse settings. {}", ex.what()); + } + } + + virtual void enable() override + { + Logger::info("Shortcut Guide is enabling"); + + if (!_enabled) + { + _enabled = true; + } + else + { + Logger::warn("Shortcut guide is already enabled"); + } + } + + virtual void disable() override + { + Logger::info("ShortcutGuideModule::disable()"); + if (_enabled) + { + _enabled = false; + TerminateProcess(); + } + else + { + Logger::warn("Shortcut Guide is already disabled"); + } + } + + virtual bool is_enabled() override + { + return _enabled; + } + + virtual void destroy() override + { + this->disable(); + if (exitEvent) + { + CloseHandle(exitEvent); + } + + delete this; + } + + virtual std::optional GetHotkeyEx() override + { + Logger::trace("GetHotkeyEx()"); + return m_hotkey; + } + + virtual void OnHotkeyEx() override + { + Logger::trace("OnHotkeyEx()"); + if (!_enabled) + { + return; + } + + if (IsProcessActive()) + { + TerminateProcess(); + return; + } + + if (m_hProcess) + { + CloseHandle(m_hProcess); + m_hProcess = nullptr; + } + + StartProcess(); + } + + virtual void send_settings_telemetry() override + { + Logger::trace("Send settings telemetry"); + if (!StartProcess(L"telemetry")) + { + Logger::error("Failed to create a process to send settings telemetry"); + } + } + +private: + std::wstring app_name; + //contains the non localized key of the powertoy + std::wstring app_key; + bool _enabled = false; + HANDLE m_hProcess = nullptr; + + // Hotkey to invoke the module + HotkeyEx m_hotkey; + HANDLE exitEvent; + + bool StartProcess(std::wstring args = L"") + { + if (exitEvent) + { + ResetEvent(exitEvent); + } + + unsigned long powertoys_pid = GetCurrentProcessId(); + std::wstring executable_args = L""; + executable_args.append(std::to_wstring(powertoys_pid)); + if (!args.empty()) + { + executable_args.append(L" "); + executable_args.append(args); + } + + SHELLEXECUTEINFOW sei{ sizeof(sei) }; + sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI }; + sei.lpFile = L"modules\\ShortcutGuide\\ShortcutGuide\\PowerToys.ShortcutGuide.exe"; + sei.nShow = SW_SHOWNORMAL; + sei.lpParameters = executable_args.data(); + if (ShellExecuteExW(&sei) == false) + { + Logger::error(L"Failed to start SG process. {}", get_last_error_or_default(GetLastError())); + auto message = get_last_error_message(GetLastError()); + if (message.has_value()) + { + Logger::error(message.value()); + } + + return false; + } + + Logger::trace(L"Started SG process with pid={}", GetProcessId(sei.hProcess)); + m_hProcess = sei.hProcess; + return true; + } + + void TerminateProcess() + { + 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"); + } + } + } + + bool IsProcessActive() + { + return m_hProcess && WaitForSingleObject(m_hProcess, 0) != WAIT_OBJECT_0; + } + + void InitSettings() + { + try + { + PowerToysSettings::PowerToyValues settings = + PowerToysSettings::PowerToyValues::load_from_settings_file(app_key); + + ParseHotkey(settings); + } + catch (std::exception ex) + { + Logger::error("Failed to init settings. {}", ex.what()); + } + catch(...) + { + Logger::error("Failed to init settings"); + } + } + + void ParseHotkey(PowerToysSettings::PowerToyValues& settings) + { + auto settingsObject = settings.get_raw_json(); + if (settingsObject.GetView().Size()) + { + try + { + auto jsonHotkeyObject = settingsObject.GetNamedObject(L"properties").GetNamedObject(L"open_shortcutguide"); + auto hotkey = PowerToysSettings::HotkeyObject::from_json(jsonHotkeyObject); + m_hotkey = HotkeyEx(); + if (hotkey.win_pressed()) + { + m_hotkey.modifiersMask |= MOD_WIN; + } + + if (hotkey.ctrl_pressed()) + { + m_hotkey.modifiersMask |= MOD_CONTROL; + } + + if (hotkey.shift_pressed()) + { + m_hotkey.modifiersMask |= MOD_SHIFT; + } + + if (hotkey.alt_pressed()) + { + m_hotkey.modifiersMask |= MOD_ALT; + } + + m_hotkey.vkCode = hotkey.get_code(); + } + catch (...) + { + Logger::warn("Failed to initialize Shortcut Guide start shortcut"); + } + } + else + { + Logger::info("Shortcut Guide settings are empty"); + } + + if (!m_hotkey.modifiersMask) + { + Logger::info("Shortcut Guide is going to use default shortcut"); + m_hotkey.modifiersMask = MOD_SHIFT | MOD_WIN; + m_hotkey.vkCode = VK_OEM_2; + } + } +}; + +extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() +{ + return new ShortcutGuideModule(); +} \ No newline at end of file diff --git a/src/modules/shortcut_guide/loc/cs/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/cs/src/modules/shortcut_guide/Resources.resx.lcl similarity index 100% rename from src/modules/shortcut_guide/loc/cs/src/modules/shortcut_guide/Resources.resx.lcl rename to src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/cs/src/modules/shortcut_guide/Resources.resx.lcl diff --git a/src/modules/shortcut_guide/loc/de/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/de/src/modules/shortcut_guide/Resources.resx.lcl similarity index 100% rename from src/modules/shortcut_guide/loc/de/src/modules/shortcut_guide/Resources.resx.lcl rename to src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/de/src/modules/shortcut_guide/Resources.resx.lcl diff --git a/src/modules/shortcut_guide/loc/es/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/es/src/modules/shortcut_guide/Resources.resx.lcl similarity index 100% rename from src/modules/shortcut_guide/loc/es/src/modules/shortcut_guide/Resources.resx.lcl rename to src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/es/src/modules/shortcut_guide/Resources.resx.lcl diff --git a/src/modules/shortcut_guide/loc/fr/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/fr/src/modules/shortcut_guide/Resources.resx.lcl similarity index 100% rename from src/modules/shortcut_guide/loc/fr/src/modules/shortcut_guide/Resources.resx.lcl rename to src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/fr/src/modules/shortcut_guide/Resources.resx.lcl diff --git a/src/modules/shortcut_guide/loc/hu/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/hu/src/modules/shortcut_guide/Resources.resx.lcl similarity index 100% rename from src/modules/shortcut_guide/loc/hu/src/modules/shortcut_guide/Resources.resx.lcl rename to src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/hu/src/modules/shortcut_guide/Resources.resx.lcl diff --git a/src/modules/shortcut_guide/loc/it/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/it/src/modules/shortcut_guide/Resources.resx.lcl similarity index 100% rename from src/modules/shortcut_guide/loc/it/src/modules/shortcut_guide/Resources.resx.lcl rename to src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/it/src/modules/shortcut_guide/Resources.resx.lcl diff --git a/src/modules/shortcut_guide/loc/ja/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/ja/src/modules/shortcut_guide/Resources.resx.lcl similarity index 100% rename from src/modules/shortcut_guide/loc/ja/src/modules/shortcut_guide/Resources.resx.lcl rename to src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/ja/src/modules/shortcut_guide/Resources.resx.lcl diff --git a/src/modules/shortcut_guide/loc/ko/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/ko/src/modules/shortcut_guide/Resources.resx.lcl similarity index 100% rename from src/modules/shortcut_guide/loc/ko/src/modules/shortcut_guide/Resources.resx.lcl rename to src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/ko/src/modules/shortcut_guide/Resources.resx.lcl diff --git a/src/modules/shortcut_guide/loc/nl/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/nl/src/modules/shortcut_guide/Resources.resx.lcl similarity index 100% rename from src/modules/shortcut_guide/loc/nl/src/modules/shortcut_guide/Resources.resx.lcl rename to src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/nl/src/modules/shortcut_guide/Resources.resx.lcl diff --git a/src/modules/shortcut_guide/loc/pl/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/pl/src/modules/shortcut_guide/Resources.resx.lcl similarity index 100% rename from src/modules/shortcut_guide/loc/pl/src/modules/shortcut_guide/Resources.resx.lcl rename to src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/pl/src/modules/shortcut_guide/Resources.resx.lcl diff --git a/src/modules/shortcut_guide/loc/pt-BR/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/pt-BR/src/modules/shortcut_guide/Resources.resx.lcl similarity index 100% rename from src/modules/shortcut_guide/loc/pt-BR/src/modules/shortcut_guide/Resources.resx.lcl rename to src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/pt-BR/src/modules/shortcut_guide/Resources.resx.lcl diff --git a/src/modules/shortcut_guide/loc/pt-PT/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/pt-PT/src/modules/shortcut_guide/Resources.resx.lcl similarity index 100% rename from src/modules/shortcut_guide/loc/pt-PT/src/modules/shortcut_guide/Resources.resx.lcl rename to src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/pt-PT/src/modules/shortcut_guide/Resources.resx.lcl diff --git a/src/modules/shortcut_guide/loc/ru/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/ru/src/modules/shortcut_guide/Resources.resx.lcl similarity index 100% rename from src/modules/shortcut_guide/loc/ru/src/modules/shortcut_guide/Resources.resx.lcl rename to src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/ru/src/modules/shortcut_guide/Resources.resx.lcl diff --git a/src/modules/shortcut_guide/loc/sv/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/sv/src/modules/shortcut_guide/Resources.resx.lcl similarity index 100% rename from src/modules/shortcut_guide/loc/sv/src/modules/shortcut_guide/Resources.resx.lcl rename to src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/sv/src/modules/shortcut_guide/Resources.resx.lcl diff --git a/src/modules/shortcut_guide/loc/tr/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/tr/src/modules/shortcut_guide/Resources.resx.lcl similarity index 100% rename from src/modules/shortcut_guide/loc/tr/src/modules/shortcut_guide/Resources.resx.lcl rename to src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/tr/src/modules/shortcut_guide/Resources.resx.lcl diff --git a/src/modules/shortcut_guide/loc/zh-Hans/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/zh-Hans/src/modules/shortcut_guide/Resources.resx.lcl similarity index 100% rename from src/modules/shortcut_guide/loc/zh-Hans/src/modules/shortcut_guide/Resources.resx.lcl rename to src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/zh-Hans/src/modules/shortcut_guide/Resources.resx.lcl diff --git a/src/modules/shortcut_guide/loc/zh-Hant/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/zh-Hant/src/modules/shortcut_guide/Resources.resx.lcl similarity index 100% rename from src/modules/shortcut_guide/loc/zh-Hant/src/modules/shortcut_guide/Resources.resx.lcl rename to src/modules/ShortcutGuide/ShortcutGuideModuleInterface/loc/zh-Hant/src/modules/shortcut_guide/Resources.resx.lcl diff --git a/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/packages.config b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/packages.config new file mode 100644 index 0000000000..81f107b8bc --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/pch.cpp b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/pch.cpp new file mode 100644 index 0000000000..1d9f38c57d --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/pch.h b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/pch.h new file mode 100644 index 0000000000..799a9214a1 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/pch.h @@ -0,0 +1,8 @@ +#pragma once +#define NOMINMAX +#include +#include +#include +#include +#include +#include \ No newline at end of file diff --git a/src/modules/shortcut_guide/resource.base.h b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/resource.base.h similarity index 100% rename from src/modules/shortcut_guide/resource.base.h rename to src/modules/ShortcutGuide/ShortcutGuideModuleInterface/resource.base.h diff --git a/src/modules/interface/powertoy_module_interface.h b/src/modules/interface/powertoy_module_interface.h index 43b675bd99..b57f291cdc 100644 --- a/src/modules/interface/powertoy_module_interface.h +++ b/src/modules/interface/powertoy_module_interface.h @@ -47,6 +47,12 @@ public: std::strong_ordering operator<=>(const Hotkey&) const = default; }; + + struct HotkeyEx + { + WORD modifiersMask = 0; + WORD vkCode = 0; + }; /* Returns the localized name of the PowerToy*/ virtual const wchar_t* get_name() = 0; @@ -78,6 +84,15 @@ public: */ virtual size_t get_hotkeys(Hotkey* buffer, size_t buffer_size) { return 0; } + virtual std::optional GetHotkeyEx() + { + return std::nullopt; + } + + virtual void OnHotkeyEx() + { + } + /* Called when one of the registered hotkeys is pressed. Should return true * if the key press is to be swallowed. */ diff --git a/src/modules/shortcut_guide/dllmain.cpp b/src/modules/shortcut_guide/dllmain.cpp deleted file mode 100644 index fdf746d443..0000000000 --- a/src/modules/shortcut_guide/dllmain.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// dllmain.cpp : Defines the entry point for the DLL application. -#include "pch.h" -#include -#include "shortcut_guide.h" -#include "overlay_window.h" -#include "trace.h" - -BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) -{ - switch (ul_reason_for_call) - { - case DLL_PROCESS_ATTACH: - Trace::RegisterProvider(); - break; - case DLL_THREAD_ATTACH: - case DLL_THREAD_DETACH: - break; - case DLL_PROCESS_DETACH: - Trace::UnregisterProvider(); - break; - } - return TRUE; -} - -extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() -{ - if (!instance) - { - instance = new OverlayWindow(); - return instance; - } - else - { - return nullptr; - } -} \ No newline at end of file diff --git a/src/modules/shortcut_guide/keyboard_state.cpp b/src/modules/shortcut_guide/keyboard_state.cpp deleted file mode 100644 index e9ea2a2c4f..0000000000 --- a/src/modules/shortcut_guide/keyboard_state.cpp +++ /dev/null @@ -1,94 +0,0 @@ -#include "pch.h" -#include "keyboard_state.h" - -bool winkey_held() -{ - auto left = GetAsyncKeyState(VK_LWIN); - auto right = GetAsyncKeyState(VK_RWIN); - return (left & 0x8000) || (right & 0x8000); -} - -// Returns true if the VK code is in the range of valid keys. -// Some VK codes should not be checked because they would return -// false positives when checking if only the "Win" key is pressed. -constexpr bool should_check(int vk) -{ - switch (vk) - { - case VK_CANCEL: - case VK_BACK: - case VK_TAB: - case VK_CLEAR: - case VK_ESCAPE: - case VK_APPS: - case VK_SLEEP: - case VK_NUMLOCK: - case VK_SCROLL: - case VK_OEM_102: - return true; - } - - if (vk >= VK_SHIFT && vk <= VK_CAPITAL) - { - return true; - } - - if (vk >= VK_SPACE && vk <= VK_HELP) - { - return true; - } - - // Digits - if (vk >= 0x30 && vk <= 0x39) - { - return true; - } - - // Letters - if (vk >= 0x41 && vk <= 0x5A) - { - return true; - } - - if (vk >= VK_NUMPAD0 && vk <= VK_F24) - { - return true; - } - - if (vk >= VK_LSHIFT && vk <= VK_LAUNCH_APP2) - { - return true; - } - - if (vk >= VK_OEM_1 && vk <= VK_OEM_3) - { - return true; - } - - if (vk >= VK_OEM_4 && vk <= VK_OEM_8) - { - return true; - } - - if (vk >= VK_ATTN && vk <= VK_OEM_CLEAR) - { - return true; - } - - return false; -} - -bool only_winkey_key_held() -{ - for (int vk = VK_CANCEL; vk <= VK_OEM_CLEAR; vk++) - { - if (should_check(vk)) - { - if (GetAsyncKeyState(vk) & 0x8000) - { - return false; - } - } - } - return true; -} \ No newline at end of file diff --git a/src/modules/shortcut_guide/keyboard_state.h b/src/modules/shortcut_guide/keyboard_state.h deleted file mode 100644 index 2777a2f03e..0000000000 --- a/src/modules/shortcut_guide/keyboard_state.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once -bool winkey_held(); -bool only_winkey_key_held(); diff --git a/src/modules/shortcut_guide/shortcut_guide.base.rc b/src/modules/shortcut_guide/shortcut_guide.base.rc deleted file mode 100644 index d5b44a5a0f..0000000000 Binary files a/src/modules/shortcut_guide/shortcut_guide.base.rc and /dev/null differ diff --git a/src/modules/shortcut_guide/shortcut_guide.cpp b/src/modules/shortcut_guide/shortcut_guide.cpp deleted file mode 100644 index cc7c673f04..0000000000 --- a/src/modules/shortcut_guide/shortcut_guide.cpp +++ /dev/null @@ -1,487 +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* instance = nullptr; - -namespace -{ - LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) - { - LowlevelKeyboardEvent event; - if (nCode == HC_ACTION) - { - event.lParam = reinterpret_cast(lParam); - event.wParam = wParam; - if (instance->signal_event(&event) != 0) - { - return 1; - } - } - return CallNextHookEx(NULL, nCode, wParam, lParam); - } - - // 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() - { - ShortcutGuideWindowInfo result; - auto active_window = GetForegroundWindow(); - active_window = GetAncestor(active_window, GA_ROOT); - if (!IsWindowVisible(active_window)) - { - return result; - } - - WCHAR exePath[MAX_PATH] = L""; - instance->get_exe_path(active_window, exePath); - if (wcslen(exePath) > 0) - { - result.disabled = instance->is_disabled_app(exePath); - if (result.disabled) - { - 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. Similary, 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; -} - -OverlayWindow::OverlayWindow() -{ - app_name = GET_RESOURCE_STRING(IDS_SHORTCUT_GUIDE); - app_key = ShortcutGuideConstants::ModuleKey; - std::filesystem::path logFilePath(PTSettingsHelper::get_module_save_folder_location(app_key)); - logFilePath.append(LogSettings::shortcutGuideLogPath); - Logger::init(LogSettings::shortcutGuideLoggerName, logFilePath.wstring(), PTSettingsHelper::get_log_settings_file_location()); - Logger::info("Overlay Window is creating"); - init_settings(); -} - -// Return the localized display name of the powertoy -const wchar_t* OverlayWindow::get_name() -{ - return app_name.c_str(); -} - -// Return the non localized key of the powertoy, this will be cached by the runner -const wchar_t* OverlayWindow::get_key() -{ - return app_key.c_str(); -} - -bool OverlayWindow::get_config(wchar_t* buffer, int* buffer_size) -{ - HINSTANCE hinstance = reinterpret_cast(&__ImageBase); - - PowerToysSettings::Settings settings(hinstance, get_name()); - settings.set_description(GET_RESOURCE_STRING(IDS_SETTINGS_DESCRIPTION)); - settings.set_overview_link(L"https://aka.ms/PowerToysOverview_ShortcutGuide"); - settings.set_icon_key(L"pt-shortcut-guide"); - - settings.add_int_spinner( - pressTime.name, - pressTime.resourceId, - pressTime.value, - 100, - 10000, - 100); - - settings.add_int_spinner( - overlayOpacity.name, - overlayOpacity.resourceId, - overlayOpacity.value, - 0, - 100, - 1); - - settings.add_choice_group( - theme.name, - theme.resourceId, - theme.value, - theme.keys_and_texts); - - return settings.serialize_to_buffer(buffer, buffer_size); -} - -void OverlayWindow::set_config(const wchar_t* config) -{ - try - { - // save configuration - PowerToysSettings::PowerToyValues _values = - PowerToysSettings::PowerToyValues::from_json_string(config, get_key()); - _values.save_to_settings_file(); - Trace::SettingsChanged(pressTime.value, overlayOpacity.value, theme.value); - - // apply new settings if powertoy is enabled - if (_enabled) - { - if (const auto press_delay_time = _values.get_int_value(pressTime.name)) - { - pressTime.value = *press_delay_time; - if (target_state) - { - target_state->set_delay(*press_delay_time); - } - } - if (const auto overlay_opacity = _values.get_int_value(overlayOpacity.name)) - { - overlayOpacity.value = *overlay_opacity; - if (winkey_popup) - { - winkey_popup->apply_overlay_opacity(((float)overlayOpacity.value) / 100.0f); - } - } - if (auto val = _values.get_string_value(theme.name)) - { - theme.value = std::move(*val); - if (winkey_popup) - { - winkey_popup->set_theme(theme.value); - } - } - if (auto val = _values.get_string_value(disabledApps.name)) - { - disabledApps.value = std::move(*val); - update_disabled_apps(); - } - } - } - catch (...) - { - // Improper JSON. TODO: handle the error. - } -} - -constexpr int alternative_switch_hotkey_id = 0x2; -constexpr UINT alternative_switch_modifier_mask = MOD_WIN | MOD_SHIFT; -constexpr UINT alternative_switch_vk_code = VK_OEM_2; - -void OverlayWindow::enable() -{ - Logger::info("Shortcut Guide is enabling"); - - auto switcher = [&](HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -> LRESULT { - if (msg == WM_KEYDOWN && wparam == VK_ESCAPE && instance->target_state->active()) - { - instance->target_state->toggle_force_shown(); - return 0; - } - - if (msg == WM_APP && lparam == eventActivateWindow) - { - instance->target_state->toggle_force_shown(); - return 0; - } - - if (msg != WM_HOTKEY) - { - return 0; - } - const auto vk_code = HIWORD(lparam); - const auto modifiers_mask = LOWORD(lparam); - if (alternative_switch_vk_code != vk_code || alternative_switch_modifier_mask != modifiers_mask) - { - return 0; - } - instance->target_state->toggle_force_shown(); - return 0; - }; - - if (!_enabled) - { - Trace::EnableShortcutGuide(true); - winkey_popup = std::make_unique(std::move(switcher)); - winkey_popup->apply_overlay_opacity(((float)overlayOpacity.value) / 100.0f); - winkey_popup->set_theme(theme.value); - target_state = std::make_unique(pressTime.value); - try - { - winkey_popup->initialize(); - } - catch (...) - { - Logger::critical("Winkey popup failed to initialize"); - return; - } - -#if defined(DISABLE_LOWLEVEL_HOOKS_WHEN_DEBUGGED) - const bool hook_disabled = IsDebuggerPresent(); -#else - const bool hook_disabled = false; -#endif - if (!hook_disabled) - { - hook_handle = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), NULL); - if (!hook_handle) - { - DWORD errorCode = GetLastError(); - show_last_error_message(L"SetWindowsHookEx", errorCode, L"PowerToys - Shortcut Guide"); - auto errorMessage = get_last_error_message(errorCode); - Trace::Error(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"OverlayWindow.enable.SetWindowsHookEx"); - } - } - RegisterHotKey(winkey_popup->get_window_handle(), alternative_switch_hotkey_id, alternative_switch_modifier_mask, alternative_switch_vk_code); - - auto show_action = [&]() { - PostMessageW(winkey_popup->get_window_handle(), WM_APP, 0, eventActivateWindow); - }; - - event_waiter = std::make_unique(CommonSharedConstants::SHOW_SHORTCUT_GUIDE_SHARED_EVENT, show_action); - } - _enabled = true; -} - -void OverlayWindow::disable(bool trace_event) -{ - Logger::info("Shortcut Guide is disabling"); - - if (_enabled) - { - _enabled = false; - if (trace_event) - { - Trace::EnableShortcutGuide(false); - } - UnregisterHotKey(winkey_popup->get_window_handle(), alternative_switch_hotkey_id); - event_waiter.reset(); - winkey_popup->hide(); - target_state->exit(); - target_state.reset(); - winkey_popup.reset(); - if (hook_handle) - { - bool success = UnhookWindowsHookEx(hook_handle); - if (success) - { - hook_handle = nullptr; - } - } - } -} - -void OverlayWindow::disable() -{ - this->disable(true); -} - -bool OverlayWindow::is_enabled() -{ - return _enabled; -} - -intptr_t OverlayWindow::signal_event(LowlevelKeyboardEvent* event) -{ - if (!_enabled) - { - return 0; - } - - if (event->wParam == WM_KEYDOWN || - event->wParam == WM_SYSKEYDOWN || - event->wParam == WM_KEYUP || - event->wParam == WM_SYSKEYUP) - { - bool suppress = target_state->signal_event(event->lParam->vkCode, - event->wParam == WM_KEYDOWN || event->wParam == WM_SYSKEYDOWN); - return suppress ? 1 : 0; - } - else - { - return 0; - } -} - -void OverlayWindow::on_held() -{ - auto windowInfo = GetShortcutGuideWindowInfo(); - if (windowInfo.disabled) - { - target_state->was_hidden(); - return; - } - winkey_popup->show(windowInfo.hwnd, windowInfo.snappable); -} - -void OverlayWindow::on_held_press(DWORD vkCode) -{ - winkey_popup->animate(vkCode); -} - -void OverlayWindow::quick_hide() -{ - winkey_popup->quick_hide(); -} - -void OverlayWindow::was_hidden() -{ - target_state->was_hidden(); -} - -void OverlayWindow::destroy() -{ - this->disable(false); - delete this; - instance = nullptr; -} - -bool OverlayWindow::overlay_visible() const -{ - return target_state->active(); -} - -void OverlayWindow::init_settings() -{ - try - { - PowerToysSettings::PowerToyValues settings = - PowerToysSettings::PowerToyValues::load_from_settings_file(OverlayWindow::get_key()); - if (const auto val = settings.get_int_value(pressTime.name)) - { - pressTime.value = *val; - } - if (const auto val = settings.get_int_value(overlayOpacity.name)) - { - overlayOpacity.value = *val; - } - if (auto val = settings.get_string_value(theme.name)) - { - theme.value = std::move(*val); - } - if (auto val = settings.get_string_value(disabledApps.name)) - { - disabledApps.value = std::move(*val); - update_disabled_apps(); - } - } - catch (std::exception&) - { - // Error while loading from the settings file. Just let default values stay as they are. - } -} - -bool OverlayWindow::is_disabled_app(wchar_t* exePath) -{ - if (exePath == nullptr) - { - return false; - } - - auto exePathUpper = std::wstring(exePath); - CharUpperBuffW(exePathUpper.data(), (DWORD)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(), (DWORD)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); - } - } -} diff --git a/src/modules/shortcut_guide/shortcut_guide.vcxproj.filters b/src/modules/shortcut_guide/shortcut_guide.vcxproj.filters deleted file mode 100644 index 6f093d1b3f..0000000000 --- a/src/modules/shortcut_guide/shortcut_guide.vcxproj.filters +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Generated Files - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - {2c7c97f7-0d87-4230-a4b2-baf2cfc35d58} - - - {aa4b6713-589d-42ef-804d-3a045833f83f} - - - {d7932c11-20ad-4625-adbc-0780ea5e308d} - - - {41a2f27e-76b5-4799-94c3-90a33a71786b} - - - - - - Resource Files - - - Resource Files - - - Header Files - - - - - Generated Files - - - \ No newline at end of file diff --git a/src/modules/shortcut_guide/target_state.cpp b/src/modules/shortcut_guide/target_state.cpp deleted file mode 100644 index 36609f7252..0000000000 --- a/src/modules/shortcut_guide/target_state.cpp +++ /dev/null @@ -1,250 +0,0 @@ -#include "pch.h" -#include "target_state.h" -#include "start_visible.h" -#include "keyboard_state.h" -#include -#include - -TargetState::TargetState(int ms_delay) : - // TODO: All this processing should be done w/o a separate thread etc. in pre_wnd_proc of winkey_popup to avoid - // multithreading. Use SetTimer for delayed events - delay(std::chrono::milliseconds(ms_delay)), - thread(&TargetState::thread_proc, this) -{ -} - -constexpr unsigned VK_S = 0x53; - -bool TargetState::signal_event(unsigned vk_code, bool key_down) -{ - std::unique_lock lock(mutex); - // Ignore repeated key presses - if (!events.empty() && events.back().key_down == key_down && events.back().vk_code == vk_code) - { - return false; - } - // Hide the overlay when WinKey + Shift + S is pressed - if (key_down && state == Shown && vk_code == VK_S && (GetKeyState(VK_LSHIFT) || GetKeyState(VK_RSHIFT))) - { - // We cannot use normal hide() here, there is stuff that needs deinitialization. - // It can be safely done when the user releases the WinKey. - instance->quick_hide(); - } - const bool win_key_released = !key_down && (vk_code == VK_LWIN || vk_code == VK_RWIN); - constexpr auto overlay_fade_in_animation_time = std::chrono::milliseconds(300); - const auto overlay_active = state == Shown && (std::chrono::system_clock::now() - signal_timestamp > overlay_fade_in_animation_time); - const bool suppress_win_release = win_key_released && (state == ForceShown || overlay_active) && !nonwin_key_was_pressed_during_shown; - - events.push_back({ key_down, vk_code }); - lock.unlock(); - cv.notify_one(); - if (suppress_win_release) - { - // Send a 0xFF VK code, which is outside of the VK code range, to prevent - // the start menu from appearing. - INPUT input[3] = { {}, {}, {} }; - input[0].type = INPUT_KEYBOARD; - input[0].ki.wVk = 0xFF; - input[0].ki.dwExtraInfo = CommonSharedConstants::KEYBOARDMANAGER_INJECTED_FLAG; - input[1].type = INPUT_KEYBOARD; - input[1].ki.wVk = 0xFF; - input[1].ki.dwFlags = KEYEVENTF_KEYUP; - input[1].ki.dwExtraInfo = CommonSharedConstants::KEYBOARDMANAGER_INJECTED_FLAG; - input[2].type = INPUT_KEYBOARD; - input[2].ki.wVk = vk_code; - input[2].ki.dwFlags = KEYEVENTF_KEYUP; - input[2].ki.dwExtraInfo = CommonSharedConstants::KEYBOARDMANAGER_INJECTED_FLAG; - SendInput(3, input, sizeof(INPUT)); - } - return suppress_win_release; -} - -void TargetState::was_hidden() -{ - std::unique_lock lock(mutex); - // Ignore callbacks from the D2DOverlayWindow - if (state == ForceShown) - { - return; - } - state = Hidden; - events.clear(); - lock.unlock(); - cv.notify_one(); -} - -void TargetState::exit() -{ - std::unique_lock lock(mutex); - events.clear(); - state = Exiting; - lock.unlock(); - cv.notify_one(); - thread.join(); -} - -KeyEvent TargetState::next() -{ - auto e = events.front(); - events.pop_front(); - return e; -} - -void TargetState::handle_hidden() -{ - std::unique_lock lock(mutex); - if (events.empty()) - cv.wait(lock); - if (events.empty() || state == Exiting) - return; - auto event = next(); - if (event.key_down && (event.vk_code == VK_LWIN || event.vk_code == VK_RWIN)) - { - state = Timeout; - winkey_timestamp = std::chrono::system_clock::now(); - } -} - -void TargetState::handle_shown(const bool forced) -{ - std::unique_lock lock(mutex); - if (events.empty()) - { - cv.wait(lock); - } - if (events.empty() || state == Exiting) - { - return; - } - auto event = next(); - if (event.vk_code == VK_LWIN || event.vk_code == VK_RWIN) - { - if (!forced && (!event.key_down || !winkey_held())) - { - state = Hidden; - } - return; - } - - if (event.key_down) - { - nonwin_key_was_pressed_during_shown = true; - lock.unlock(); - instance->on_held_press(event.vk_code); - } -} - -void TargetState::thread_proc() -{ - while (true) - { - switch (state) - { - case Hidden: - handle_hidden(); - break; - case Timeout: - try - { - handle_timeout(); - } - catch (...) - { - Logger::critical("Timeout, handle_timeout failed."); - } - break; - case Shown: - try - { - handle_shown(false); - } - catch (...) - { - Logger::critical("Shown, handle_shown failed."); - } - - break; - case ForceShown: - try - { - handle_shown(true); - } - catch (...) - { - Logger::critical("ForceShown, handle_shown failed."); - } - - break; - case Exiting: - default: - return; - } - } -} - -void TargetState::handle_timeout() -{ - std::unique_lock lock(mutex); - auto wait_time = delay - (std::chrono::system_clock::now() - winkey_timestamp); - if (events.empty()) - { - cv.wait_for(lock, wait_time); - } - if (state == Exiting) - { - return; - } - - // Skip all VK_*WIN-down events - while (!events.empty()) - { - auto event = events.front(); - if (event.key_down && (event.vk_code == VK_LWIN || event.vk_code == VK_RWIN)) - events.pop_front(); - else - break; - } - // If we've detected that a user is holding anything other than VK_*WIN or start menu is visible, we should hide - if (!events.empty() || !only_winkey_key_held() || is_start_visible()) - { - state = Hidden; - return; - } - - if (std::chrono::system_clock::now() - winkey_timestamp < delay) - { - return; - } - - signal_timestamp = std::chrono::system_clock::now(); - nonwin_key_was_pressed_during_shown = false; - state = Shown; - lock.unlock(); - instance->on_held(); -} - -void TargetState::set_delay(int ms_delay) -{ - std::unique_lock lock(mutex); - delay = std::chrono::milliseconds(ms_delay); -} - -void TargetState::toggle_force_shown() -{ - std::unique_lock lock(mutex); - events.clear(); - if (state != ForceShown) - { - state = ForceShown; - instance->on_held(); - } - else - { - state = Hidden; - } -} - -bool TargetState::active() const -{ - return state == ForceShown || state == Shown; -} diff --git a/src/modules/shortcut_guide/target_state.h b/src/modules/shortcut_guide/target_state.h deleted file mode 100644 index cfcb46758b..0000000000 --- a/src/modules/shortcut_guide/target_state.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include "shortcut_guide.h" - -struct KeyEvent -{ - bool key_down; - unsigned vk_code; -}; - -class TargetState -{ -public: - TargetState(int ms_delay); - bool signal_event(unsigned vk_code, bool key_down); - void was_hidden(); - void exit(); - void set_delay(int ms_delay); - - void toggle_force_shown(); - bool active() const; - -private: - KeyEvent next(); - void handle_hidden(); - void handle_timeout(); - void handle_shown(const bool forced); - void thread_proc(); - std::recursive_mutex mutex; - std::condition_variable_any cv; - std::chrono::system_clock::time_point winkey_timestamp, signal_timestamp; - std::chrono::milliseconds delay; - std::deque events; - enum State - { - Hidden, - Timeout, - Shown, - ForceShown, - Exiting - }; - std::atomic state = Hidden; - - bool nonwin_key_was_pressed_during_shown = false; - std::thread thread; -}; diff --git a/src/modules/shortcut_guide/trace.cpp b/src/modules/shortcut_guide/trace.cpp deleted file mode 100644 index f1f4b6fd7f..0000000000 --- a/src/modules/shortcut_guide/trace.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "pch.h" -#include "trace.h" - -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::RegisterProvider() noexcept -{ - TraceLoggingRegister(g_hProvider); -} - -void Trace::UnregisterProvider() noexcept -{ - TraceLoggingUnregister(g_hProvider); -} - -void Trace::HideGuide(const __int64 duration_ms, std::vector& key_pressed) noexcept -{ - std::string vk_codes; - std::vector::iterator it; - for (it = key_pressed.begin(); it != key_pressed.end();) - { - vk_codes += std::to_string(*it); - if (++it != key_pressed.end()) - { - vk_codes += " "; - } - } - - TraceLoggingWrite( - g_hProvider, - "ShortcutGuide_HideGuide", - TraceLoggingInt64(duration_ms, "DurationInMs"), - TraceLoggingInt64(key_pressed.size(), "NumberOfKeysPressed"), - TraceLoggingString(vk_codes.c_str(), "ListOfKeysPressed"), - ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), - TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), - TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); -} - -void Trace::EnableShortcutGuide(const bool enabled) noexcept -{ - TraceLoggingWrite( - g_hProvider, - "ShortcutGuide_EnableGuide", - TraceLoggingBoolean(enabled, "Enabled"), - ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), - TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), - TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); -} - -void Trace::SettingsChanged(const int press_delay_time, const int overlay_opacity, const std::wstring& theme) noexcept -{ - TraceLoggingWrite( - g_hProvider, - "ShortcutGuide_SettingsChanged", - TraceLoggingInt32(press_delay_time, "PressDelayTime"), - TraceLoggingInt32(overlay_opacity, "OverlayOpacity"), - TraceLoggingWideString(theme.c_str(), "Theme"), - ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), - TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), - TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); -} - -// Log if an error occurs in Shortcut Guide -void Trace::Error(const DWORD errorCode, std::wstring errorMessage, std::wstring methodName) noexcept -{ - TraceLoggingWrite( - g_hProvider, - "ShortcutGuide_Error", - ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), - TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), - TraceLoggingValue(methodName.c_str(), "MethodName"), - TraceLoggingValue(errorCode, "ErrorCode"), - TraceLoggingValue(errorMessage.c_str(), "ErrorMessage")); -} diff --git a/src/modules/shortcut_guide/trace.h b/src/modules/shortcut_guide/trace.h deleted file mode 100644 index fcbae49ac5..0000000000 --- a/src/modules/shortcut_guide/trace.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -class Trace -{ -public: - static void RegisterProvider() noexcept; - static void UnregisterProvider() noexcept; - static void HideGuide(const __int64 duration_ms, std::vector& key_pressed) noexcept; - static void EnableShortcutGuide(const bool enabled) noexcept; - static void SettingsChanged(const int press_delay_time, const int overlay_opacity, const std::wstring& theme) noexcept; - static void Error(const DWORD errorCode, std::wstring errorMessage, std::wstring methodName) noexcept; -}; diff --git a/src/runner/CentralizedHotkeys.cpp b/src/runner/CentralizedHotkeys.cpp new file mode 100644 index 0000000000..3bfdccd907 --- /dev/null +++ b/src/runner/CentralizedHotkeys.cpp @@ -0,0 +1,121 @@ +#include "pch.h" +#include "CentralizedHotkeys.h" + +#include +#include +#include +#include + +namespace CentralizedHotkeys +{ + std::map> actions; + std::map ids; + HWND runnerWindow; + + std::wstring ToWstring(const Shortcut& shortcut) + { + std::wstring res = L""; + if (shortcut.modifiersMask & MOD_SHIFT) + { + res += L"shift+"; + } + + if (shortcut.modifiersMask & MOD_CONTROL) + { + res += L"ctrl+"; + } + + if (shortcut.modifiersMask & MOD_WIN) + { + res += L"win+"; + } + + if (shortcut.modifiersMask & MOD_ALT) + { + res += L"alt+"; + } + + res += PowerToysSettings::HotkeyObject::key_from_code(shortcut.vkCode); + + return res; + } + + bool AddHotkeyAction(Shortcut shortcut, Action action) + { + if (!actions[shortcut].empty()) + { + // It will only work if previous one is rewritten + Logger::warn(L"{} shortcut is already registered", ToWstring(shortcut)); + } + + actions[shortcut].push_back(action); + // Register hotkey if it is the first shortcut + if (actions[shortcut].size() == 1) + { + if (ids.find(shortcut) == ids.end()) + { + static int nextId = 0; + ids[shortcut] = nextId++; + } + + if (!RegisterHotKey(runnerWindow, ids[shortcut], shortcut.modifiersMask, shortcut.vkCode)) + { + Logger::warn(L"Failed to add {} shortcut. {}", ToWstring(shortcut), get_last_error_or_default(GetLastError())); + return false; + } + + Logger::trace(L"{} shortcut registered", ToWstring(shortcut)); + return true; + } + + return true; + } + + void UnregisterHotkeysForModule(std::wstring moduleName) + { + for (auto it = actions.begin(); it != actions.end(); it++) + { + auto val = std::find_if(it->second.begin(), it->second.end(), [moduleName](Action a) { return a.moduleName == moduleName; }); + if (val != it->second.end()) + { + it->second.erase(val); + + if (it->second.empty()) + { + if (!UnregisterHotKey(runnerWindow, ids[it->first])) + { + Logger::warn(L"Failed to unregister {} shortcut. {}", ToWstring(it->first), get_last_error_or_default(GetLastError())); + } + else + { + Logger::trace(L"{} shortcut unregistered", ToWstring(it->first)); + } + } + } + } + } + + void PopulateHotkey(Shortcut shortcut) + { + if (!actions.empty()) + { + try + { + actions[shortcut].begin()->action(shortcut.modifiersMask, shortcut.vkCode); + } + catch(std::exception ex) + { + Logger::error("Failed to execute hotkey's action. {}", ex.what()); + } + catch(...) + { + Logger::error(L"Failed to execute hotkey's action"); + } + } + } + + void RegisterWindow(HWND hwnd) + { + runnerWindow = hwnd; + } +} diff --git a/src/runner/CentralizedHotkeys.h b/src/runner/CentralizedHotkeys.h new file mode 100644 index 0000000000..87576b980d --- /dev/null +++ b/src/runner/CentralizedHotkeys.h @@ -0,0 +1,45 @@ +#pragma once +#include +#include + +namespace CentralizedHotkeys +{ + struct Action + { + std::wstring moduleName; + std::function action; + + Action(std::wstring moduleName = L"", std::function action = ([](WORD modifiersMask, WORD vkCode) {})) + { + this->moduleName = moduleName; + this->action = action; + } + }; + + struct Shortcut + { + WORD modifiersMask; + WORD vkCode; + + Shortcut(WORD modifiersMask = 0, WORD vkCode = 0) + { + this->modifiersMask = modifiersMask; + this->vkCode = vkCode; + } + + bool operator<(const Shortcut& key) const + { + return std::pair{ this->modifiersMask, this->vkCode } < std::pair{ key.modifiersMask, key.vkCode }; + } + }; + + std::wstring ToWstring(const Shortcut& shortcut); + + bool AddHotkeyAction(Shortcut shortcut, Action action); + + void UnregisterHotkeysForModule(std::wstring moduleName); + + void PopulateHotkey(Shortcut shortcut); + + void RegisterWindow(HWND hwnd); +} diff --git a/src/runner/main.cpp b/src/runner/main.cpp index 89843c420b..5c9b34fe3b 100644 --- a/src/runner/main.cpp +++ b/src/runner/main.cpp @@ -32,6 +32,7 @@ #include #include #include "centralized_kb_hook.h" +#include "CentralizedHotkeys.h" #if _DEBUG && _WIN64 #include "unhandled_exception_handler.h" @@ -155,7 +156,7 @@ int runner(bool isProcessElevated, bool openSettings, bool openOobe) L"modules/KeyboardManager/KeyboardManager.dll", L"modules/Launcher/Microsoft.Launcher.dll", L"modules/PowerRename/PowerRenameExt.dll", - L"modules/ShortcutGuide/ShortcutGuide.dll", + L"modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.dll", L"modules/ColorPicker/ColorPicker.dll", }; diff --git a/src/runner/powertoy_module.cpp b/src/runner/powertoy_module.cpp index ce5cc6846e..9ecd4a9bd7 100644 --- a/src/runner/powertoy_module.cpp +++ b/src/runner/powertoy_module.cpp @@ -1,7 +1,9 @@ #include "pch.h" #include "powertoy_module.h" #include "centralized_kb_hook.h" +#include "CentralizedHotkeys.h" #include +#include std::map& modules() { @@ -46,6 +48,7 @@ PowertoyModule::PowertoyModule(PowertoyModuleIface* pt_module, HMODULE handle) : } update_hotkeys(); + UpdateHotkeyEx(); } void PowertoyModule::update_hotkeys() @@ -66,3 +69,19 @@ void PowertoyModule::update_hotkeys() }); } } + +void PowertoyModule::UpdateHotkeyEx() +{ + CentralizedHotkeys::UnregisterHotkeysForModule(pt_module->get_key()); + auto container = pt_module->GetHotkeyEx(); + if (container.has_value()) + { + auto hotkey = container.value(); + auto modulePtr = pt_module.get(); + auto action = [modulePtr](WORD modifiersMask, WORD vkCode) { + modulePtr->OnHotkeyEx(); + }; + + CentralizedHotkeys::AddHotkeyAction({ hotkey.modifiersMask, hotkey.vkCode }, { pt_module->get_key(), action }); + } +} diff --git a/src/runner/powertoy_module.h b/src/runner/powertoy_module.h index 8c3e499b32..9332e5f025 100644 --- a/src/runner/powertoy_module.h +++ b/src/runner/powertoy_module.h @@ -42,6 +42,8 @@ public: void update_hotkeys(); + void UpdateHotkeyEx(); + private: std::unique_ptr handle; std::unique_ptr pt_module; diff --git a/src/runner/runner.vcxproj b/src/runner/runner.vcxproj index 8d1f9b9998..9d5ffe7b49 100644 --- a/src/runner/runner.vcxproj +++ b/src/runner/runner.vcxproj @@ -48,6 +48,7 @@ + Create @@ -67,6 +68,7 @@ + @@ -86,71 +88,6 @@ - - true - Document - $(OutDir)\svgs - - - true - Document - $(OutDir)\svgs - - - true - Document - $(OutDir)\svgs - - - true - Document - $(OutDir)\svgs - - - true - Document - $(OutDir)\svgs - - - true - Document - $(OutDir)\svgs - - - true - Document - $(OutDir)\svgs - - - true - Document - $(OutDir)\svgs - - - true - Document - $(OutDir)\svgs - - - true - Document - $(OutDir)\svgs - - - true - Document - $(OutDir)\svgs - - - true - Document - $(OutDir)\svgs - - - true - Document - $(OutDir)\svgs - true Document diff --git a/src/runner/runner.vcxproj.filters b/src/runner/runner.vcxproj.filters index 925d548faf..d532e62952 100644 --- a/src/runner/runner.vcxproj.filters +++ b/src/runner/runner.vcxproj.filters @@ -45,6 +45,9 @@ Utils + + Utils + @@ -90,14 +93,14 @@ Utils + + Utils + {bc27e9c1-8afa-4d62-8179-789f4651c0b6} - - {1123565f-43ba-4eb8-a6f5-2d5f8f3ae6af} - {74d0b535-16ee-46cc-9adf-dc4668bbcfda} @@ -107,45 +110,6 @@ - - svgs - - - svgs - - - svgs - - - svgs - - - svgs - - - svgs - - - svgs - - - svgs - - - svgs - - - svgs - - - svgs - - - svgs - - - svgs - diff --git a/src/runner/settings_window.cpp b/src/runner/settings_window.cpp index 8877646974..685b078b5d 100644 --- a/src/runner/settings_window.cpp +++ b/src/runner/settings_window.cpp @@ -40,7 +40,7 @@ json::JsonObject get_power_toys_settings() } catch (...) { - Logger::error("get_power_toys_settings: got malformed json"); + Logger::error(L"get_power_toys_settings(): got malformed json for {} module", name); } } return result; @@ -139,6 +139,7 @@ void send_json_config_to_module(const std::wstring& module_key, const std::wstri { moduleIt->second->set_config(settings.c_str()); moduleIt->second.update_hotkeys(); + moduleIt->second.UpdateHotkeyEx(); } } diff --git a/src/runner/tray_icon.cpp b/src/runner/tray_icon.cpp index ac7aa35af0..37b0b8b747 100644 --- a/src/runner/tray_icon.cpp +++ b/src/runner/tray_icon.cpp @@ -2,11 +2,13 @@ #include "Generated files/resource.h" #include "settings_window.h" #include "tray_icon.h" +#include "CentralizedHotkeys.h" #include #include #include #include +#include namespace { @@ -104,6 +106,15 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam { switch (message) { + case WM_HOTKEY: + { + // We use the tray icon WndProc to avoid creating a dedicated window just for this message. + const auto modifiersMask = LOWORD(lparam); + const auto vkCode = HIWORD(lparam); + Logger::trace(L"On {} hotkey", CentralizedHotkeys::ToWstring({ modifiersMask, vkCode })); + CentralizedHotkeys::PopulateHotkey({ modifiersMask, vkCode }); + break; + } case WM_CREATE: if (wm_taskbar_restart == 0) { @@ -225,7 +236,7 @@ void start_tray_icon() wc.hInstance, nullptr); WINRT_VERIFY(hwnd); - + CentralizedHotkeys::RegisterWindow(hwnd); memset(&tray_icon_data, 0, sizeof(tray_icon_data)); tray_icon_data.cbSize = sizeof(tray_icon_data); tray_icon_data.hIcon = icon; diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ShortcutGuideProperties.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ShortcutGuideProperties.cs index eb04fc390d..f70a92cb14 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ShortcutGuideProperties.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ShortcutGuideProperties.cs @@ -14,8 +14,12 @@ namespace Microsoft.PowerToys.Settings.UI.Library PressTime = new IntProperty(900); Theme = new StringProperty("system"); DisabledApps = new StringProperty(); + OpenShortcutGuide = new HotkeySettings(true, false, false, true, 0xBF); } + [JsonPropertyName("open_shortcutguide")] + public HotkeySettings OpenShortcutGuide { get; set; } + [JsonPropertyName("overlay_opacity")] public IntProperty OverlayOpacity { get; set; } diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/ShortcutGuideViewModel.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/ShortcutGuideViewModel.cs index af23429e26..be2faed406 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/ShortcutGuideViewModel.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/ShortcutGuideViewModel.cs @@ -11,6 +11,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels { public class ShortcutGuideViewModel : Observable { + private ISettingsUtils SettingsUtils { get; set; } + private GeneralSettings GeneralSettingsConfig { get; set; } private ShortcutGuideSettings Settings { get; set; } @@ -22,8 +24,10 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels private string _settingsConfigFileFolder = string.Empty; private string _disabledApps; - public ShortcutGuideViewModel(ISettingsRepository settingsRepository, ISettingsRepository moduleSettingsRepository, Func ipcMSGCallBackFunc, string configFileSubfolder = "") + public ShortcutGuideViewModel(ISettingsUtils settingsUtils, ISettingsRepository settingsRepository, ISettingsRepository moduleSettingsRepository, Func ipcMSGCallBackFunc, string configFileSubfolder = "") { + SettingsUtils = settingsUtils; + // Update Settings file folder: _settingsConfigFileFolder = configFileSubfolder; @@ -98,6 +102,23 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels } } + public HotkeySettings OpenShortcutGuide + { + get + { + return Settings.Properties.OpenShortcutGuide; + } + + set + { + if (Settings.Properties.OpenShortcutGuide != value) + { + Settings.Properties.OpenShortcutGuide = value; + NotifyPropertyChanged(); + } + } + } + public int ThemeIndex { get @@ -136,24 +157,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels } } - public int PressTime - { - get - { - return _pressTime; - } - - set - { - if (_pressTime != value) - { - _pressTime = value; - Settings.Properties.PressTime.Value = value; - NotifyPropertyChanged(); - } - } - } - public int OverlayOpacity { get @@ -198,9 +201,11 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels public void NotifyPropertyChanged([CallerMemberName] string propertyName = null) { OnPropertyChanged(propertyName); + SndShortcutGuideSettings outsettings = new SndShortcutGuideSettings(Settings); SndModuleSettings ipcMessage = new SndModuleSettings(outsettings); SendConfigMSG(ipcMessage.ToJsonString()); + SettingsUtils.SaveSettings(Settings.ToJsonString(), ModuleName); } } } diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/ShortcutGuide.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/ShortcutGuide.cs index ca4cdc9a73..64d0f627b7 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/ShortcutGuide.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/ShortcutGuide.cs @@ -42,12 +42,11 @@ namespace ViewModelTests // Initialise View Model with test Config files Func sendMockIPCConfigMSG = msg => { return 0; }; - ShortcutGuideViewModel viewModel = new ShortcutGuideViewModel(generalSettingsRepository, shortcutSettingsRepository, sendMockIPCConfigMSG); + ShortcutGuideViewModel viewModel = new ShortcutGuideViewModel(mockSettingsUtils, generalSettingsRepository, shortcutSettingsRepository, sendMockIPCConfigMSG); // Verify that the old settings persisted Assert.AreEqual(originalGeneralSettings.Enabled.ShortcutGuide, viewModel.IsEnabled); Assert.AreEqual(originalSettings.Properties.OverlayOpacity.Value, viewModel.OverlayOpacity); - Assert.AreEqual(originalSettings.Properties.PressTime.Value, viewModel.PressTime); // Verify that the stub file was used var expectedCallCount = 2; // once via the view model, and once by the test (GetSettings) @@ -69,6 +68,8 @@ namespace ViewModelTests [TestMethod] public void IsEnabledShouldEnableModuleWhenSuccessful() { + var settingsUtilsMock = new Mock(); + // Assert // Initialize mock function of sending IPC message. Func sendMockIPCConfigMSG = msg => @@ -79,7 +80,7 @@ namespace ViewModelTests }; // Arrange - ShortcutGuideViewModel viewModel = new ShortcutGuideViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockShortcutGuideSettingsUtils.Object), sendMockIPCConfigMSG, ShortCutGuideTestFolderName); + ShortcutGuideViewModel viewModel = new ShortcutGuideViewModel(settingsUtilsMock.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockShortcutGuideSettingsUtils.Object), sendMockIPCConfigMSG, ShortCutGuideTestFolderName); // Act viewModel.IsEnabled = true; @@ -88,65 +89,35 @@ namespace ViewModelTests [TestMethod] public void ThemeIndexShouldSetThemeToDarkWhenSuccessful() { - // Assert - // Initialize mock function of sending IPC message. - Func sendMockIPCConfigMSG = msg => - { - ShortcutGuideSettingsIPCMessage snd = JsonSerializer.Deserialize(msg); - Assert.AreEqual("dark", snd.Powertoys.ShortcutGuide.Properties.Theme.Value); - return 0; - }; - // Arrange - ShortcutGuideViewModel viewModel = new ShortcutGuideViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockShortcutGuideSettingsUtils.Object), sendMockIPCConfigMSG, ShortCutGuideTestFolderName); + var settingsUtilsMock = new Mock(); + ShortcutGuideViewModel viewModel = new ShortcutGuideViewModel(settingsUtilsMock.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockShortcutGuideSettingsUtils.Object), msg => { return 0; }, ShortCutGuideTestFolderName); // Initialize shortcut guide settings theme to 'system' to be in sync with shortcut_guide.h. Assert.AreEqual(2, viewModel.ThemeIndex); // Act viewModel.ThemeIndex = 0; - } - [TestMethod] - public void PressTimeShouldSetPressTimeToOneHundredWhenSuccessful() - { // Assert - // Initialize mock function of sending IPC message. - Func sendMockIPCConfigMSG = msg => - { - ShortcutGuideSettingsIPCMessage snd = JsonSerializer.Deserialize(msg); - Assert.AreEqual(100, snd.Powertoys.ShortcutGuide.Properties.PressTime.Value); - return 0; - }; - - // Arrange - ShortcutGuideViewModel viewModel = new ShortcutGuideViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockShortcutGuideSettingsUtils.Object), sendMockIPCConfigMSG, ShortCutGuideTestFolderName); - Assert.AreEqual(900, viewModel.PressTime); - - // Act - viewModel.PressTime = 100; + 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() { - // Assert - // Initialize mock function of sending IPC message. - Func sendMockIPCConfigMSG = msg => - { - ShortcutGuideSettingsIPCMessage snd = JsonSerializer.Deserialize(msg); - - // Serialisation not working as expected in the test project: - Assert.AreEqual(100, snd.Powertoys.ShortcutGuide.Properties.OverlayOpacity.Value); - return 0; - }; - // Arrange - ShortcutGuideViewModel viewModel = new ShortcutGuideViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockShortcutGuideSettingsUtils.Object), sendMockIPCConfigMSG, ShortCutGuideTestFolderName); + 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/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeShellPage.xaml.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeShellPage.xaml.cs index f9eb0df929..4887e19d06 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeShellPage.xaml.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeShellPage.xaml.cs @@ -28,13 +28,6 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views ColorPickerSharedEventCallback = implementation; } - public static Func ShortcutGuideSharedEventCallback { get; set; } - - public static void SetShortcutGuideSharedEventCallback(Func implementation) - { - ShortcutGuideSharedEventCallback = implementation; - } - public static Action OpenMainWindowCallback { get; set; } public static void SetOpenMainWindowCallback(Action implementation) diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeShortcutGuide.xaml.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeShortcutGuide.xaml.cs index 956c33384b..4a72f7a798 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeShortcutGuide.xaml.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeShortcutGuide.xaml.cs @@ -2,6 +2,9 @@ // 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.Diagnostics; +using System.Globalization; +using System.IO; using System.Threading; using Microsoft.PowerToys.Settings.UI.OOBE.Enums; using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel; @@ -27,12 +30,12 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views private void Start_ShortcutGuide_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e) { - if (OobeShellPage.ShortcutGuideSharedEventCallback != null) + var executablePath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, @"..\modules\ShortcutGuide\ShortcutGuide\PowerToys.ShortcutGuide.exe"); + var id = Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture); + var p = Process.Start(executablePath, id); + if (p != null) { - using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, OobeShellPage.ShortcutGuideSharedEventCallback())) - { - eventHandle.Set(); - } + p.Close(); } ViewModel.LogRunningModuleEvent(); diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw index 7482ca83ec..0e29cf5da1 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw @@ -477,10 +477,6 @@ Shows a help overlay with Windows shortcuts when the Windows key is pressed. - - Press duration before showing (ms) - pressing a key in milliseconds - Appearance & behavior @@ -1097,7 +1093,7 @@ From there, simply click on a Markdown file or SVG icon in the File Explorer and PowerToys Run supports various action keys to funnel search queries for a specific subset of results. Typing {<} searches for running processes only, {?} will search only for file, or {.} for installed applications! See PowerToys documentation for the complete set of 'Action Keys' available. - {Win} + {?} to open Shortcut Guide, press it again to close or press {Esc}. You can also launch it by holding the {Win} key for one second! + {Shift} + {Win} + {/} to open Shortcut Guide, press it again to close or press {Esc}. Tips & tricks @@ -1195,4 +1191,7 @@ From there, simply click on a Markdown file or SVG icon in the File Explorer and Quick layout switch - + + Open Shortcut Guide + + \ No newline at end of file diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/ShortcutGuidePage.xaml b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/ShortcutGuidePage.xaml index 554f4142ab..25c661cce7 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/ShortcutGuidePage.xaml +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/ShortcutGuidePage.xaml @@ -2,12 +2,11 @@ x:Class="Microsoft.PowerToys.Settings.UI.Views.ShortcutGuidePage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:local="using:Microsoft.PowerToys.Settings.UI.Views" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:viewModel="using:Microsoft.PowerToys.Settings.UI.ViewModels" xmlns:CustomControls="using:Microsoft.PowerToys.Settings.UI.Controls" - xmlns:muxc="using:Microsoft.UI.Xaml.Controls" xmlns:converters="using:Microsoft.Toolkit.Uwp.UI.Converters" + xmlns:muxc="using:Microsoft.UI.Xaml.Controls" + xmlns:converters="using:Microsoft.Toolkit.Uwp.UI.Converters" mc:Ignorable="d" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" AutomationProperties.LandmarkType="Main"> @@ -58,21 +57,22 @@ + + + + - - .GetInstance(settingsUtils), SettingsRepository.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage); + ViewModel = new ShortcutGuideViewModel(settingsUtils, SettingsRepository.GetInstance(settingsUtils), SettingsRepository.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage); DataContext = ViewModel; } diff --git a/src/settings-ui/PowerToys.Settings/OobeWindow.xaml.cs b/src/settings-ui/PowerToys.Settings/OobeWindow.xaml.cs index dc966a92a8..5ee0b1128d 100644 --- a/src/settings-ui/PowerToys.Settings/OobeWindow.xaml.cs +++ b/src/settings-ui/PowerToys.Settings/OobeWindow.xaml.cs @@ -78,12 +78,6 @@ namespace PowerToys.Settings return Constants.ShowColorPickerSharedEvent(); }); - OobeShellPage.SetShortcutGuideSharedEventCallback(() => - { - NativeMethods.AllowSetForegroundWindow(PowerToys.Settings.Program.PowerToysPID); - return Constants.ShowShortcutGuideSharedEvent(); - }); - OobeShellPage.SetOpenMainWindowCallback((Type type) => { ((App)Application.Current).OpenSettingsWindow(type);