diff --git a/doc/devdocs/modules/shortcut_guide.md b/doc/devdocs/modules/shortcut_guide.md index 9a7a0230db..a22af52336 100644 --- a/doc/devdocs/modules/shortcut_guide.md +++ b/doc/devdocs/modules/shortcut_guide.md @@ -15,8 +15,17 @@ Shortcut Guide is a PowerToy that displays an overlay of available keyboard shor > The spec for the manifest files is in development and will be linked here once available. ## Usage -- Press the user-defined hotkey to display the overlay -- Press the hotkey again or press ESC to dismiss the overlay +- Press the user-defined hotkey to display the full overlay. +- Optionally, hold either Windows key to show taskbar indicators or the full overlay after a configurable delay. +- Press the hotkey again or press ESC to dismiss the overlay. A full overlay opened by holding the Windows key can either close on key release or remain open, depending on the setting. + +The **Hold Windows key** setting is independent of the activation shortcut: + +- **Off** leaves the Windows key behavior unchanged. +- **Show taskbar indicators** is the default and always hides the indicators when the Windows key is released. +- **Open Shortcut Guide** can close on Windows-key release or remain open. + +The hold duration accepts values from 100 through 5,000 milliseconds and defaults to 900 milliseconds. ## Build and Debug Instructions @@ -37,7 +46,7 @@ Shortcut Guide is a PowerToy that displays an overlay of available keyboard shor The Shortcut Guide module consists of the following 4 projects: -### [`ShortcutGuide.Ui`](/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj +### [`ShortcutGuide.Ui`](/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj) This is the main UI project for the Shortcut Guide module. Upon startup it does the following tasks: diff --git a/doc/dsc/modules/ShortcutGuide.md b/doc/dsc/modules/ShortcutGuide.md index 1b7595a9af..9e28866062 100644 --- a/doc/dsc/modules/ShortcutGuide.md +++ b/doc/dsc/modules/ShortcutGuide.md @@ -14,8 +14,8 @@ Manages configuration for the Shortcut Guide utility, which displays available k ## Description The `ShortcutGuide` module configures PowerToys Shortcut Guide, a utility that -displays an overlay showing available Windows keyboard shortcuts when you hold -the Windows key. It helps users discover and learn keyboard shortcuts. +displays an overlay showing available Windows and application keyboard shortcuts. +It can be opened with a configurable shortcut or by holding either Windows key. ## Properties @@ -35,7 +35,15 @@ Sets the keyboard shortcut or method to open the shortcut guide. - `code` (integer) - Virtual key code - `key` (string) - Key name -**Default:** Hold Windows key for 900ms +**Default:** `Win+Shift+/` + +### WindowsKeyAction + +Sets the action performed after holding either Windows key. + +**Type:** integer +**Allowed values:** `0` (Off), `1` (Show taskbar indicators), `2` (Open Shortcut Guide) +**Default:** `1` ### OverlayOpacity @@ -58,9 +66,18 @@ Sets the theme for the shortcut guide. Sets how long the Windows key must be held before showing the guide (in milliseconds). **Type:** integer -**Range:** `100` to `10000` +**Range:** `100` to `5000` **Default:** `900` +### CloseOnWindowsKeyRelease + +Controls whether the full Shortcut Guide closes when the Windows key is released. +This setting applies when `WindowsKeyAction` is `2`; taskbar indicators always +close on release. + +**Type:** boolean +**Default:** `true` + ### ExcludedApps List of applications where Shortcut Guide is disabled. @@ -71,13 +88,15 @@ List of applications where Shortcut Guide is disabled. ### Example 1 - Configure activation time with direct execution -This example sets a faster activation time for the shortcut guide. +This example opens the full Shortcut Guide after holding a Windows key for 600 milliseconds. ```powershell $config = @{ settings = @{ properties = @{ + WindowsKeyAction = 2 PressTime = 600 + CloseOnWindowsKeyRelease = $true } name = "ShortcutGuide" version = "1.0" @@ -146,7 +165,7 @@ resources: ### Example 4 - Quick activation -This example configures for quick activation with a short press time. +This example configures taskbar indicators with a short hold duration. ```bash dsc config set --file shortcutguide-quick.dsc.yaml @@ -161,6 +180,7 @@ resources: properties: settings: properties: + WindowsKeyAction: 1 PressTime: 400 name: ShortcutGuide version: 1.0 diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/App.xaml.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/App.xaml.cs index 9f1f9b98ba..c2c0795e7a 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/App.xaml.cs +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/App.xaml.cs @@ -27,12 +27,12 @@ namespace ShortcutGuide { internal static Dictionary> PinnedShortcuts { get; private set; } = new Dictionary>(); - internal static ShortcutGuideSettings ShortcutGuideSettings { get; private set; } = null!; + internal static ShortcutGuideSettings ShortcutGuideSettings => SettingsRepository.GetInstance(SettingsUtils.Default).SettingsConfig; - internal static ShortcutGuideProperties ShortcutGuideProperties { get; private set; } = null!; + internal static ShortcutGuideProperties ShortcutGuideProperties => ShortcutGuideSettings.Properties; /// - /// The single transparent host that replaces the previous MainWindow + + /// Gets the single transparent host that replaces the previous MainWindow + /// TaskbarWindow pair. The two surfaces are now XAML pseudo-windows /// inside this one window. /// @@ -108,10 +108,13 @@ namespace ShortcutGuide { if (OverlayWindow.AppWindow.IsVisible) { - OverlayWindow.DispatcherQueue.TryEnqueue(() => + if (ShortcutGuideProperties.WindowsKeyAction.Value != ((int)ShortcutGuideWindowsKeyAction.OpenShortcutGuide) || ShortcutGuideProperties.CloseOnWindowsKeyRelease.Value) { - OverlayWindow.CloseAnimated(); - }); + OverlayWindow.DispatcherQueue.TryEnqueue(() => + { + OverlayWindow.CloseAnimated(); + }); + } NativeMethods.SendInput(1, [new() { Type = 1, Data = new() { Keyboard = new NativeMethods.KEYBDINPUT { WVk = 0xFF, DwFlags = 0x2 } } }], Marshal.SizeOf()); SendSingleKeyboardInput((short)key, 0x2); // key up @@ -200,9 +203,15 @@ namespace ShortcutGuide // System.Windows.Input.Keyboard is not initialized // on the WinUI UI thread. const int VK_LWIN = 0x5B; - bool winKeyDown = (NativeMethods.GetAsyncKeyState(VK_LWIN) & 0x8000) != 0; + const int VK_RWIN = 0x5C; + bool winKeyDown = ((NativeMethods.GetAsyncKeyState(VK_LWIN) & 0x8000) != 0) || + ((NativeMethods.GetAsyncKeyState(VK_RWIN) & 0x8000) != 0); + if (winKeyDown && ShortcutGuideProperties.WindowsKeyAction.Value == (int)ShortcutGuideWindowsKeyAction.Off) + { + return; + } - if (winKeyDown) + if (winKeyDown && ShortcutGuideProperties.WindowsKeyAction.Value == (int)ShortcutGuideWindowsKeyAction.TaskbarIndicators) { if (OverlayWindow.AppWindow.IsVisible) { @@ -270,8 +279,17 @@ namespace ShortcutGuide } } - ShortcutGuideSettings = SettingsRepository.GetInstance(settingsUtils).SettingsConfig; - ShortcutGuideProperties = ShortcutGuideSettings.Properties; + try + { +#pragma warning disable CA1869 // Cache and reuse 'JsonSerializerOptions' instances + settingsUtils.SaveSettings(JsonSerializer.Serialize(App.ShortcutGuideSettings, new JsonSerializerOptions { WriteIndented = true }), "Shortcut Guide"); +#pragma warning restore CA1869 // Cache and reuse 'JsonSerializerOptions' instances + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) + { + // Persisting the round-tripped settings is best-effort; the in-memory copy is still valid. + Logger.LogWarning($"Failed to persist Shortcut Guide settings on launch. Reason: {ex.Message}"); + } } private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) diff --git a/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/dllmain.cpp b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/dllmain.cpp index 8916728ee4..26804283e2 100644 --- a/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/dllmain.cpp +++ b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/dllmain.cpp @@ -167,7 +167,7 @@ public: } } virtual bool keep_track_of_pressed_win_key() override { return true; } - virtual UINT milliseconds_win_key_must_be_pressed() override { return 900; } + virtual UINT milliseconds_win_key_must_be_pressed() override { return m_millisecondsWinKeyPressTimeForGlobalWindowsShortcuts; } private: std::wstring app_name; @@ -301,6 +301,30 @@ private: { Logger::warn("Failed to initialize Shortcut Guide start shortcut"); } + +try + { + auto propertiesObject = settingsObject.GetNamedObject(L"properties"); + if (propertiesObject.HasKey(L"press_time")) + { + auto jsonDurationObject = propertiesObject.GetNamedObject(L"press_time"); + if (jsonDurationObject.HasKey(L"value")) + { + auto pressTime = static_cast(jsonDurationObject.GetNamedNumber(L"value")); + if (pressTime < 100) + { + pressTime = 100; + } + else if (pressTime > 5000) + { + pressTime = 5000; + } + + m_millisecondsWinKeyPressTimeForGlobalWindowsShortcuts = pressTime; + } + } + } + catch (...) { /* Keep defaults */ } } else { diff --git a/src/settings-ui/Settings.UI.Library/ShortcutGuideProperties.cs b/src/settings-ui/Settings.UI.Library/ShortcutGuideProperties.cs index f1f1caaeab..35c3daff4d 100644 --- a/src/settings-ui/Settings.UI.Library/ShortcutGuideProperties.cs +++ b/src/settings-ui/Settings.UI.Library/ShortcutGuideProperties.cs @@ -10,11 +10,18 @@ namespace Microsoft.PowerToys.Settings.UI.Library { public class ShortcutGuideProperties { + public const int DefaultPressTimeMs = 900; + public const int MinimumPressTimeMs = 100; + public const int MaximumPressTimeMs = 5000; + [CmdConfigureIgnore] public HotkeySettings DefaultOpenShortcutGuide => new HotkeySettings(true, false, false, true, 0xBF); public ShortcutGuideProperties() { + WindowsKeyAction = new IntProperty((int)ShortcutGuideWindowsKeyAction.TaskbarIndicators); + PressTime = new IntProperty(DefaultPressTimeMs); + CloseOnWindowsKeyRelease = new BoolProperty(true); Theme = new StringProperty("system"); DisabledApps = new StringProperty(); OpenShortcutGuide = DefaultOpenShortcutGuide; @@ -25,6 +32,15 @@ namespace Microsoft.PowerToys.Settings.UI.Library [JsonPropertyName("open_shortcutguide")] public HotkeySettings OpenShortcutGuide { get; set; } + [JsonPropertyName("win_key_action")] + public IntProperty WindowsKeyAction { get; set; } + + [JsonPropertyName("press_time")] + public IntProperty PressTime { get; set; } + + [JsonPropertyName("close_on_windows_key_release")] + public BoolProperty CloseOnWindowsKeyRelease { get; set; } + [JsonPropertyName("theme")] public StringProperty Theme { get; set; } diff --git a/src/settings-ui/Settings.UI.Library/ShortcutGuideWindowsKeyAction.cs b/src/settings-ui/Settings.UI.Library/ShortcutGuideWindowsKeyAction.cs new file mode 100644 index 0000000000..cd301d38c4 --- /dev/null +++ b/src/settings-ui/Settings.UI.Library/ShortcutGuideWindowsKeyAction.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.PowerToys.Settings.UI.Library +{ + public enum ShortcutGuideWindowsKeyAction + { + Off = 0, + TaskbarIndicators = 1, + OpenShortcutGuide = 2, + } +} diff --git a/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/ShortcutGuide.cs b/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/ShortcutGuide.cs index 11f7a266ee..6af96b7a25 100644 --- a/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/ShortcutGuide.cs +++ b/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/ShortcutGuide.cs @@ -7,6 +7,7 @@ using System.IO.Abstractions; using System.Text.Json; using Microsoft.PowerToys.Settings.UI.Library; +using Microsoft.PowerToys.Settings.UI.Library.Interfaces; using Microsoft.PowerToys.Settings.UI.UnitTests.BackwardsCompatibility; using Microsoft.PowerToys.Settings.UI.UnitTests.Mocks; using Microsoft.PowerToys.Settings.UI.ViewModels; @@ -24,12 +25,12 @@ namespace ViewModelTests /// Test if the original settings files were modified. /// [TestMethod] - [DataRow("v0.18.2", "settings.json")] - [DataRow("v0.19.2", "settings.json")] - [DataRow("v0.20.1", "settings.json")] - [DataRow("v0.21.1", "settings.json")] - [DataRow("v0.22.0", "settings.json")] - public void OriginalFilesModificationTest(string version, string fileName) + [DataRow("v0.18.2", "settings.json", 100)] + [DataRow("v0.19.2", "settings.json", 1150)] + [DataRow("v0.20.1", "settings.json", 650)] + [DataRow("v0.21.1", "settings.json", 1050)] + [DataRow("v0.22.0", "settings.json", 850)] + public void OriginalFilesModificationTest(string version, string fileName, int expectedPressTime) { var settingPathMock = new Mock(); var mockIOProvider = BackCompatTestProperties.GetModuleIOProvider(version, ShortcutGuideSettings.ModuleName, fileName); @@ -48,6 +49,9 @@ namespace ViewModelTests // Verify that the old settings persisted Assert.AreEqual(originalGeneralSettings.Enabled.ShortcutGuide, viewModel.IsEnabled); + Assert.AreEqual(expectedPressTime, viewModel.PressTime); + Assert.AreEqual((int)ShortcutGuideWindowsKeyAction.TaskbarIndicators, viewModel.WindowsKeyActionIndex); + Assert.IsTrue(viewModel.CloseOnWindowsKeyRelease); // Verify that the stub file was used var expectedCallCount = 2; // once via the view model, and once by the test (GetSettings) @@ -104,5 +108,131 @@ namespace ViewModelTests 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 WindowsKeySettingsShouldUseExpectedDefaults() + { + var properties = new ShortcutGuideProperties(); + + Assert.AreEqual((int)ShortcutGuideWindowsKeyAction.TaskbarIndicators, properties.WindowsKeyAction.Value); + Assert.AreEqual(ShortcutGuideProperties.DefaultPressTimeMs, properties.PressTime.Value); + Assert.IsTrue(properties.CloseOnWindowsKeyRelease.Value); + } + + [TestMethod] + public void InvalidWindowsKeySettingsShouldBeNormalizedOnInitialization() + { + var settings = new ShortcutGuideSettings(); + settings.Properties.WindowsKeyAction.Value = 42; + settings.Properties.PressTime.Value = ShortcutGuideProperties.MaximumPressTimeMs + 1; + + int ipcMessageCount = 0; + var viewModel = CreateViewModel(settings, out var settingsUtilsMock, _ => ipcMessageCount++); + + Assert.AreEqual((int)ShortcutGuideWindowsKeyAction.TaskbarIndicators, viewModel.WindowsKeyActionIndex); + Assert.AreEqual(ShortcutGuideProperties.MaximumPressTimeMs, viewModel.PressTime); + Assert.AreEqual((int)ShortcutGuideWindowsKeyAction.TaskbarIndicators, settings.Properties.WindowsKeyAction.Value); + Assert.AreEqual(ShortcutGuideProperties.MaximumPressTimeMs, settings.Properties.PressTime.Value); + Assert.IsTrue(viewModel.IsWindowsKeyHoldEnabled); + Assert.IsFalse(viewModel.IsOpenShortcutGuideWindowsKeyAction); + Assert.AreEqual(1, ipcMessageCount); + settingsUtilsMock.Verify( + x => x.SaveSettings( + It.Is(json => JsonSerializer.Deserialize(json).Properties.WindowsKeyAction.Value == (int)ShortcutGuideWindowsKeyAction.TaskbarIndicators), + ShortcutGuideSettings.ModuleName, + It.IsAny()), + Times.Once); + } + + [TestMethod] + public void WindowsKeyActionShouldUpdateConditionalStateAndPersist() + { + var settings = new ShortcutGuideSettings(); + int ipcMessageCount = 0; + var viewModel = CreateViewModel(settings, out var settingsUtilsMock, _ => ipcMessageCount++); + + viewModel.WindowsKeyActionIndex = (int)ShortcutGuideWindowsKeyAction.Off; + + Assert.IsFalse(viewModel.IsWindowsKeyHoldEnabled); + Assert.IsFalse(viewModel.IsOpenShortcutGuideWindowsKeyAction); + Assert.AreEqual((int)ShortcutGuideWindowsKeyAction.Off, settings.Properties.WindowsKeyAction.Value); + Assert.AreEqual(1, ipcMessageCount); + settingsUtilsMock.Verify( + x => x.SaveSettings( + It.Is(json => JsonSerializer.Deserialize(json).Properties.WindowsKeyAction.Value == (int)ShortcutGuideWindowsKeyAction.Off), + ShortcutGuideSettings.ModuleName, + It.IsAny()), + Times.Once); + + viewModel.WindowsKeyActionIndex = (int)ShortcutGuideWindowsKeyAction.OpenShortcutGuide; + + Assert.IsTrue(viewModel.IsWindowsKeyHoldEnabled); + Assert.IsTrue(viewModel.IsOpenShortcutGuideWindowsKeyAction); + } + + [TestMethod] + public void PressTimeShouldClampAtBothBoundariesAndPersist() + { + var settings = new ShortcutGuideSettings(); + var viewModel = CreateViewModel(settings, out var settingsUtilsMock); + + viewModel.PressTime = ShortcutGuideProperties.MinimumPressTimeMs - 1; + + Assert.AreEqual(ShortcutGuideProperties.MinimumPressTimeMs, viewModel.PressTime); + Assert.AreEqual(ShortcutGuideProperties.MinimumPressTimeMs, settings.Properties.PressTime.Value); + + viewModel.PressTime = ShortcutGuideProperties.MaximumPressTimeMs + 1; + + Assert.AreEqual(ShortcutGuideProperties.MaximumPressTimeMs, viewModel.PressTime); + Assert.AreEqual(ShortcutGuideProperties.MaximumPressTimeMs, settings.Properties.PressTime.Value); + settingsUtilsMock.Verify( + x => x.SaveSettings( + It.Is(json => JsonSerializer.Deserialize(json).Properties.PressTime.Value == ShortcutGuideProperties.MaximumPressTimeMs), + ShortcutGuideSettings.ModuleName, + It.IsAny()), + Times.Once); + } + + [TestMethod] + public void CloseOnWindowsKeyReleaseShouldPersist() + { + var settings = new ShortcutGuideSettings(); + var viewModel = CreateViewModel(settings, out var settingsUtilsMock); + + viewModel.CloseOnWindowsKeyRelease = false; + + Assert.IsFalse(settings.Properties.CloseOnWindowsKeyRelease.Value); + settingsUtilsMock.Verify( + x => x.SaveSettings( + It.Is(json => JsonSerializer.Deserialize(json).Properties.CloseOnWindowsKeyRelease.Value == false), + ShortcutGuideSettings.ModuleName, + It.IsAny()), + Times.Once); + } + + private static ShortcutGuideViewModel CreateViewModel( + ShortcutGuideSettings settings, + out Mock settingsUtilsMock, + Action ipcMessageReceived = null) + { + settingsUtilsMock = new Mock(new FileSystem(), null); + + var generalSettingsRepository = new Mock>(); + generalSettingsRepository.SetupGet(x => x.SettingsConfig).Returns(new GeneralSettings()); + + var shortcutGuideSettingsRepository = new Mock>(); + shortcutGuideSettingsRepository.SetupGet(x => x.SettingsConfig).Returns(settings); + + return new ShortcutGuideViewModel( + settingsUtilsMock.Object, + generalSettingsRepository.Object, + shortcutGuideSettingsRepository.Object, + message => + { + ipcMessageReceived?.Invoke(message); + return 0; + }, + ShortCutGuideTestFolderName); + } } } diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShortcutGuidePage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShortcutGuidePage.xaml index c32ea39d22..eb11520360 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShortcutGuidePage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShortcutGuidePage.xaml @@ -26,6 +26,44 @@ + + + + + + + + + + + + + + + diff --git a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw index 4948cb9a64..a2c052094a 100644 --- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw @@ -1249,6 +1249,28 @@ opera.exe Shortcut Guide do not loc the Product name. Do you want this feature on / off + + Hold Windows key + + + Choose what happens after holding the Windows key + + + Off + + + Show taskbar indicators + + + Open Shortcut Guide + + + Hold duration (ms) + ms = milliseconds + + + Close Shortcut Guide when the Windows key is released + Window position diff --git a/src/settings-ui/Settings.UI/ViewModels/ShortcutGuideViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/ShortcutGuideViewModel.cs index 61cddf2966..7dd9a65833 100644 --- a/src/settings-ui/Settings.UI/ViewModels/ShortcutGuideViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/ShortcutGuideViewModel.cs @@ -54,8 +54,22 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels InitializeEnabledValue(); + _windowsKeyActionIndex = NormalizeWindowsKeyAction(Settings.Properties.WindowsKeyAction.Value); + bool settingsNormalized = Settings.Properties.WindowsKeyAction.Value != _windowsKeyActionIndex; + Settings.Properties.WindowsKeyAction.Value = _windowsKeyActionIndex; + _pressTime = Math.Clamp( + Settings.Properties.PressTime.Value, + ShortcutGuideProperties.MinimumPressTimeMs, + ShortcutGuideProperties.MaximumPressTimeMs); + Settings.Properties.PressTime.Value = _pressTime; + _closeOnWindowsKeyRelease = Settings.Properties.CloseOnWindowsKeyRelease.Value; _disabledApps = Settings.Properties.DisabledApps.Value; + if (settingsNormalized) + { + NotifyPropertyChanged(nameof(WindowsKeyActionIndex)); + } + switch (Settings.Properties.Theme.Value) { case "dark": _themeIndex = 0; break; @@ -100,6 +114,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels private bool _isEnabled; private int _themeIndex; private int _positionIndex; + private int _windowsKeyActionIndex; + private int _pressTime; + private bool _closeOnWindowsKeyRelease; public bool IsEnabled { @@ -152,6 +169,62 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } } + public int WindowsKeyActionIndex + { + get => _windowsKeyActionIndex; + + set + { + int normalizedValue = NormalizeWindowsKeyAction(value); + if (_windowsKeyActionIndex != normalizedValue) + { + _windowsKeyActionIndex = normalizedValue; + Settings.Properties.WindowsKeyAction.Value = normalizedValue; + OnPropertyChanged(nameof(IsWindowsKeyHoldEnabled)); + OnPropertyChanged(nameof(IsOpenShortcutGuideWindowsKeyAction)); + NotifyPropertyChanged(); + } + } + } + + public bool IsWindowsKeyHoldEnabled => _windowsKeyActionIndex != (int)ShortcutGuideWindowsKeyAction.Off; + + public bool IsOpenShortcutGuideWindowsKeyAction => _windowsKeyActionIndex == (int)ShortcutGuideWindowsKeyAction.OpenShortcutGuide; + + public int PressTime + { + get => _pressTime; + + set + { + int clampedValue = Math.Clamp( + value, + ShortcutGuideProperties.MinimumPressTimeMs, + ShortcutGuideProperties.MaximumPressTimeMs); + if (_pressTime != clampedValue) + { + _pressTime = clampedValue; + Settings.Properties.PressTime.Value = clampedValue; + NotifyPropertyChanged(); + } + } + } + + public bool CloseOnWindowsKeyRelease + { + get => _closeOnWindowsKeyRelease; + + set + { + if (_closeOnWindowsKeyRelease != value) + { + _closeOnWindowsKeyRelease = value; + Settings.Properties.CloseOnWindowsKeyRelease.Value = value; + NotifyPropertyChanged(); + } + } + } + public int ThemeIndex { get @@ -237,5 +310,16 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels InitializeEnabledValue(); OnPropertyChanged(nameof(IsEnabled)); } + + private static int NormalizeWindowsKeyAction(int value) + { + return value switch + { + (int)ShortcutGuideWindowsKeyAction.Off => value, + (int)ShortcutGuideWindowsKeyAction.TaskbarIndicators => value, + (int)ShortcutGuideWindowsKeyAction.OpenShortcutGuide => value, + _ => (int)ShortcutGuideWindowsKeyAction.TaskbarIndicators, + }; + } } }