From 21f06b8bd0f9e02869b0ceb3358e6f35e8c4031f Mon Sep 17 00:00:00 2001 From: Kai Tao <69313318+vanzue@users.noreply.github.com> Date: Wed, 25 Mar 2026 02:02:36 +0800 Subject: [PATCH] Always On Top: The opacity should be able to configure the hotkey individually (#46410) ## Summary of the Pull Request This pull request adds support for customizing the hotkeys used to increase and decrease the opacity of pinned windows in the Always On Top module. Previously, these shortcuts were hardcoded to use the same modifiers as the main pin hotkey. With these changes, users can now independently configure the increase and decrease opacity shortcuts via the settings UI, and the backend has been updated to respect and store these new settings. Another change: If window is not Always On Topped, the opacity change take no effect, so we should not intercept, we should pass through to minimize the impact. ## PR Checklist - [X] Closes: #46391, #46387 - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx ## Detailed Description of the Pull Request / Additional comments ## Validation Steps Performed image image Verified locally that transparency hotkey will not intercept the normal hotkey in window if Always on top not enabled --------- Co-authored-by: Niels Laute --- .../alwaysontop/AlwaysOnTop/AlwaysOnTop.cpp | 10 +- .../alwaysontop/AlwaysOnTop/Settings.cpp | 23 +-- .../alwaysontop/AlwaysOnTop/Settings.h | 2 + .../AlwaysOnTop/SettingsConstants.h | 2 + .../AlwaysOnTopModuleInterface/dllmain.cpp | 136 +++++++++++------- .../AlwaysOnTopProperties.cs | 10 ++ .../AlwaysOnTopSettings.cs | 8 ++ .../SettingsXAML/Views/AlwaysOnTopPage.xaml | 14 +- .../Settings.UI/Strings/en-us/Resources.resw | 14 +- .../ViewModels/AlwaysOnTopViewModel.cs | 83 ++++++----- .../ViewModels/DashboardViewModel.cs | 4 +- 11 files changed, 196 insertions(+), 110 deletions(-) diff --git a/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.cpp b/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.cpp index 14f25d4a24..29dfd82ff6 100644 --- a/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.cpp +++ b/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.cpp @@ -76,7 +76,7 @@ bool isExcluded(HWND window) } AlwaysOnTop::AlwaysOnTop(bool useLLKH, DWORD mainThreadId) : - SettingsObserver({SettingId::FrameEnabled, SettingId::Hotkey, SettingId::ExcludeApps, SettingId::ShowInSystemMenu}), + SettingsObserver({ SettingId::FrameEnabled, SettingId::Hotkey, SettingId::IncreaseOpacityHotkey, SettingId::DecreaseOpacityHotkey, SettingId::ExcludeApps, SettingId::ShowInSystemMenu }), m_hinstance(reinterpret_cast(&__ImageBase)), m_useCentralizedLLKH(useLLKH), m_mainThreadId(mainThreadId), @@ -150,6 +150,8 @@ void AlwaysOnTop::SettingsUpdate(SettingId id) switch (id) { case SettingId::Hotkey: + case SettingId::IncreaseOpacityHotkey: + case SettingId::DecreaseOpacityHotkey: { RegisterHotkey(); } @@ -360,10 +362,8 @@ void AlwaysOnTop::RegisterHotkey() const // Register pin hotkey RegisterHotKey(m_window, static_cast(HotkeyId::Pin), settings->hotkey.get_modifiers(), settings->hotkey.get_code()); - // Register transparency hotkeys using the same modifiers as the pin hotkey - UINT modifiers = settings->hotkey.get_modifiers(); - RegisterHotKey(m_window, static_cast(HotkeyId::IncreaseOpacity), modifiers, VK_OEM_PLUS); - RegisterHotKey(m_window, static_cast(HotkeyId::DecreaseOpacity), modifiers, VK_OEM_MINUS); + RegisterHotKey(m_window, static_cast(HotkeyId::IncreaseOpacity), settings->increaseOpacityHotkey.get_modifiers(), settings->increaseOpacityHotkey.get_code()); + RegisterHotKey(m_window, static_cast(HotkeyId::DecreaseOpacity), settings->decreaseOpacityHotkey.get_modifiers(), settings->decreaseOpacityHotkey.get_code()); } void AlwaysOnTop::RegisterLLKH() diff --git a/src/modules/alwaysontop/AlwaysOnTop/Settings.cpp b/src/modules/alwaysontop/AlwaysOnTop/Settings.cpp index 5ce2e82f8a..f801e3d3e5 100644 --- a/src/modules/alwaysontop/AlwaysOnTop/Settings.cpp +++ b/src/modules/alwaysontop/AlwaysOnTop/Settings.cpp @@ -13,6 +13,8 @@ namespace NonLocalizable const static wchar_t* SettingsFileName = L"settings.json"; const static wchar_t* HotkeyID = L"hotkey"; + const static wchar_t* IncreaseOpacityHotkeyID = L"increase-opacity-hotkey"; + const static wchar_t* DecreaseOpacityHotkeyID = L"decrease-opacity-hotkey"; const static wchar_t* SoundEnabledID = L"sound-enabled"; const static wchar_t* ShowInSystemMenuID = L"show-in-system-menu"; const static wchar_t* FrameEnabledID = L"frame-enabled"; @@ -100,16 +102,21 @@ void AlwaysOnTopSettings::LoadSettings() const auto currentSettings = AlwaysOnTopSettings::settings(); auto updatedSettings = std::make_shared(*currentSettings); std::vector changedSettings; - - if (const auto jsonVal = values.get_json(NonLocalizable::HotkeyID)) - { - auto val = PowerToysSettings::HotkeyObject::from_json(*jsonVal); - if (updatedSettings->hotkey.get_modifiers() != val.get_modifiers() || updatedSettings->hotkey.get_key() != val.get_key() || updatedSettings->hotkey.get_code() != val.get_code()) + const auto updateHotkeySetting = [&](const wchar_t* hotkeyName, auto& currentHotkey, SettingId settingId) { + if (const auto jsonVal = values.get_json(hotkeyName)) { - updatedSettings->hotkey = val; - changedSettings.push_back(SettingId::Hotkey); + auto val = PowerToysSettings::HotkeyObject::from_json(*jsonVal); + if (currentHotkey.get_modifiers() != val.get_modifiers() || currentHotkey.get_key() != val.get_key() || currentHotkey.get_code() != val.get_code()) + { + currentHotkey = val; + changedSettings.push_back(settingId); + } } - } + }; + + updateHotkeySetting(NonLocalizable::HotkeyID, updatedSettings->hotkey, SettingId::Hotkey); + updateHotkeySetting(NonLocalizable::IncreaseOpacityHotkeyID, updatedSettings->increaseOpacityHotkey, SettingId::IncreaseOpacityHotkey); + updateHotkeySetting(NonLocalizable::DecreaseOpacityHotkeyID, updatedSettings->decreaseOpacityHotkey, SettingId::DecreaseOpacityHotkey); if (const auto jsonVal = values.get_bool_value(NonLocalizable::SoundEnabledID)) { diff --git a/src/modules/alwaysontop/AlwaysOnTop/Settings.h b/src/modules/alwaysontop/AlwaysOnTop/Settings.h index f72ce704fb..eda785bb8a 100644 --- a/src/modules/alwaysontop/AlwaysOnTop/Settings.h +++ b/src/modules/alwaysontop/AlwaysOnTop/Settings.h @@ -18,6 +18,8 @@ class SettingsObserver; struct Settings { PowerToysSettings::HotkeyObject hotkey = PowerToysSettings::HotkeyObject::from_settings(true, true, false, false, 84); // win + ctrl + T + PowerToysSettings::HotkeyObject increaseOpacityHotkey = PowerToysSettings::HotkeyObject::from_settings(true, true, false, false, VK_OEM_PLUS); // win + ctrl + '+' + PowerToysSettings::HotkeyObject decreaseOpacityHotkey = PowerToysSettings::HotkeyObject::from_settings(true, true, false, false, VK_OEM_MINUS); // win + ctrl + '-' static constexpr int minTransparencyPercentage = 20; // minimum transparency (can't go below 20%) static constexpr int maxTransparencyPercentage = 100; // maximum (fully opaque) static constexpr int transparencyStep = 10; // step size for +/- adjustment diff --git a/src/modules/alwaysontop/AlwaysOnTop/SettingsConstants.h b/src/modules/alwaysontop/AlwaysOnTop/SettingsConstants.h index 7c428bb41e..41daaa0887 100644 --- a/src/modules/alwaysontop/AlwaysOnTop/SettingsConstants.h +++ b/src/modules/alwaysontop/AlwaysOnTop/SettingsConstants.h @@ -3,6 +3,8 @@ enum class SettingId { Hotkey = 0, + IncreaseOpacityHotkey, + DecreaseOpacityHotkey, SoundEnabled, ShowInSystemMenu, FrameEnabled, diff --git a/src/modules/alwaysontop/AlwaysOnTopModuleInterface/dllmain.cpp b/src/modules/alwaysontop/AlwaysOnTopModuleInterface/dllmain.cpp index bc52137ed2..9503ea10fc 100644 --- a/src/modules/alwaysontop/AlwaysOnTopModuleInterface/dllmain.cpp +++ b/src/modules/alwaysontop/AlwaysOnTopModuleInterface/dllmain.cpp @@ -16,6 +16,8 @@ namespace NonLocalizable { const wchar_t ModulePath[] = L"PowerToys.AlwaysOnTop.exe"; + // Keep in sync with src\modules\alwaysontop\AlwaysOnTop\AlwaysOnTop.cpp + const wchar_t PinnedWindowProp[] = L"AlwaysOnTop_Pinned"; } namespace @@ -27,6 +29,8 @@ namespace const wchar_t JSON_KEY_SHIFT[] = L"shift"; const wchar_t JSON_KEY_CODE[] = L"code"; const wchar_t JSON_KEY_HOTKEY[] = L"hotkey"; + const wchar_t JSON_KEY_INCREASE_OPACITY_HOTKEY[] = L"increase-opacity-hotkey"; + const wchar_t JSON_KEY_DECREASE_OPACITY_HOTKEY[] = L"decrease-opacity-hotkey"; const wchar_t JSON_KEY_VALUE[] = L"value"; } @@ -107,27 +111,38 @@ public: virtual bool on_hotkey(size_t hotkeyId) override { - if (m_enabled) + if (!m_enabled) + { + return false; + } + + Logger::trace(L"AlwaysOnTop hotkey pressed, id={}", hotkeyId); + + if (hotkeyId == 0) { - Logger::trace(L"AlwaysOnTop hotkey pressed, id={}", hotkeyId); if (!is_process_running()) { Enable(); } - if (hotkeyId == 0) + SetEvent(m_hPinEvent); + return true; + } + + if (hotkeyId == 1 || hotkeyId == 2) + { + const HWND foregroundWindow = GetForegroundWindow(); + if (!foregroundWindow || !IsWindow(foregroundWindow) || !GetPropW(foregroundWindow, NonLocalizable::PinnedWindowProp)) { - SetEvent(m_hPinEvent); - } - else if (hotkeyId == 1) - { - SetEvent(m_hIncreaseOpacityEvent); - } - else if (hotkeyId == 2) - { - SetEvent(m_hDecreaseOpacityEvent); + return false; } + if (!is_process_running()) + { + Enable(); + } + + SetEvent(hotkeyId == 1 ? m_hIncreaseOpacityEvent : m_hDecreaseOpacityEvent); return true; } @@ -136,48 +151,48 @@ public: virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override { - size_t count = 0; - - // Hotkey 0: Pin/Unpin (e.g., Win+Ctrl+T) - if (m_hotkey.key) + constexpr size_t hotkeyCount = 3; + Hotkey configuredHotkeys[hotkeyCount] = { m_hotkey, m_increaseOpacityHotkey, m_decreaseOpacityHotkey }; + + for (size_t i = 0; i < hotkeyCount; ++i) { - if (hotkeys && buffer_size > count) - { - hotkeys[count] = m_hotkey; - Logger::trace(L"AlwaysOnTop hotkey[0]: win={}, ctrl={}, shift={}, alt={}, key={}", - m_hotkey.win, m_hotkey.ctrl, m_hotkey.shift, m_hotkey.alt, m_hotkey.key); - } - count++; + configuredHotkeys[i].id = static_cast(i); + configuredHotkeys[i].isShown = configuredHotkeys[i].key != 0; } - // Hotkey 1: Increase opacity (same modifiers + VK_OEM_PLUS '=') - if (m_hotkey.key) + if (hotkeys) { - if (hotkeys && buffer_size > count) + const size_t countToCopy = (buffer_size < hotkeyCount) ? buffer_size : hotkeyCount; + for (size_t i = 0; i < countToCopy; ++i) { - hotkeys[count] = m_hotkey; - hotkeys[count].key = VK_OEM_PLUS; // '=' key - Logger::trace(L"AlwaysOnTop hotkey[1] (increase opacity): win={}, ctrl={}, shift={}, alt={}, key={}", - hotkeys[count].win, hotkeys[count].ctrl, hotkeys[count].shift, hotkeys[count].alt, hotkeys[count].key); + hotkeys[i] = configuredHotkeys[i]; } - count++; } - // Hotkey 2: Decrease opacity (same modifiers + VK_OEM_MINUS '-') - if (m_hotkey.key) - { - if (hotkeys && buffer_size > count) - { - hotkeys[count] = m_hotkey; - hotkeys[count].key = VK_OEM_MINUS; // '-' key - Logger::trace(L"AlwaysOnTop hotkey[2] (decrease opacity): win={}, ctrl={}, shift={}, alt={}, key={}", - hotkeys[count].win, hotkeys[count].ctrl, hotkeys[count].shift, hotkeys[count].alt, hotkeys[count].key); - } - count++; - } + Logger::trace(L"AlwaysOnTop hotkey[0]: win={}, ctrl={}, shift={}, alt={}, key={}, shown={}", + configuredHotkeys[0].win, + configuredHotkeys[0].ctrl, + configuredHotkeys[0].shift, + configuredHotkeys[0].alt, + configuredHotkeys[0].key, + configuredHotkeys[0].isShown); + Logger::trace(L"AlwaysOnTop hotkey[1] (increase opacity): win={}, ctrl={}, shift={}, alt={}, key={}, shown={}", + configuredHotkeys[1].win, + configuredHotkeys[1].ctrl, + configuredHotkeys[1].shift, + configuredHotkeys[1].alt, + configuredHotkeys[1].key, + configuredHotkeys[1].isShown); + Logger::trace(L"AlwaysOnTop hotkey[2] (decrease opacity): win={}, ctrl={}, shift={}, alt={}, key={}, shown={}", + configuredHotkeys[2].win, + configuredHotkeys[2].ctrl, + configuredHotkeys[2].shift, + configuredHotkeys[2].alt, + configuredHotkeys[2].key, + configuredHotkeys[2].isShown); - Logger::trace(L"AlwaysOnTop get_hotkeys returning count={}", count); - return count; + Logger::trace(L"AlwaysOnTop get_hotkeys returning count={}", hotkeyCount); + return hotkeyCount; } // Enable the powertoy @@ -279,21 +294,34 @@ private: void parse_hotkey(PowerToysSettings::PowerToyValues& settings) { + const auto parseSingleHotkey = [](const winrt::Windows::Data::Json::JsonObject& propertiesObject, const wchar_t* hotkeyName, Hotkey& hotkey) { + try + { + auto jsonHotkeyObject = propertiesObject.GetNamedObject(hotkeyName).GetNamedObject(JSON_KEY_VALUE); + hotkey.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN); + hotkey.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT); + hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT); + hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL); + hotkey.key = static_cast(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE)); + } + catch (...) + { + } + }; + auto settingsObject = settings.get_raw_json(); if (settingsObject.GetView().Size()) { try { - auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_HOTKEY).GetNamedObject(JSON_KEY_VALUE); - m_hotkey.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN); - m_hotkey.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT); - m_hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT); - m_hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL); - m_hotkey.key = static_cast(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE)); + auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES); + parseSingleHotkey(propertiesObject, JSON_KEY_HOTKEY, m_hotkey); + parseSingleHotkey(propertiesObject, JSON_KEY_INCREASE_OPACITY_HOTKEY, m_increaseOpacityHotkey); + parseSingleHotkey(propertiesObject, JSON_KEY_DECREASE_OPACITY_HOTKEY, m_decreaseOpacityHotkey); } catch (...) { - Logger::error("Failed to initialize AlwaysOnTop start shortcut"); + Logger::error("Failed to initialize AlwaysOnTop shortcuts"); } } else @@ -329,7 +357,9 @@ private: bool m_enabled = false; HANDLE m_hProcess = nullptr; - Hotkey m_hotkey; + Hotkey m_hotkey{ .win = true, .ctrl = true, .shift = false, .alt = false, .key = 'T' }; + Hotkey m_increaseOpacityHotkey{ .win = true, .ctrl = true, .shift = false, .alt = false, .key = VK_OEM_PLUS }; + Hotkey m_decreaseOpacityHotkey{ .win = true, .ctrl = true, .shift = false, .alt = false, .key = VK_OEM_MINUS }; // Handle to event used to pin/unpin windows HANDLE m_hPinEvent; diff --git a/src/settings-ui/Settings.UI.Library/AlwaysOnTopProperties.cs b/src/settings-ui/Settings.UI.Library/AlwaysOnTopProperties.cs index 8dab3c4a19..2ca0b95b20 100644 --- a/src/settings-ui/Settings.UI.Library/AlwaysOnTopProperties.cs +++ b/src/settings-ui/Settings.UI.Library/AlwaysOnTopProperties.cs @@ -11,6 +11,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library public class AlwaysOnTopProperties { public static readonly HotkeySettings DefaultHotkeyValue = new HotkeySettings(true, true, false, false, 0x54); + public static readonly HotkeySettings DefaultIncreaseOpacityHotkeyValue = new HotkeySettings(true, true, false, false, 0xBB); + public static readonly HotkeySettings DefaultDecreaseOpacityHotkeyValue = new HotkeySettings(true, true, false, false, 0xBD); public const bool DefaultFrameEnabled = true; public const bool DefaultShowInSystemMenu = false; public const int DefaultFrameThickness = 15; @@ -24,6 +26,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library public AlwaysOnTopProperties() { Hotkey = new KeyboardKeysProperty(DefaultHotkeyValue); + IncreaseOpacityHotkey = new KeyboardKeysProperty(DefaultIncreaseOpacityHotkeyValue); + DecreaseOpacityHotkey = new KeyboardKeysProperty(DefaultDecreaseOpacityHotkeyValue); ShowInSystemMenu = new BoolProperty(DefaultShowInSystemMenu); FrameEnabled = new BoolProperty(DefaultFrameEnabled); FrameThickness = new IntProperty(DefaultFrameThickness); @@ -39,6 +43,12 @@ namespace Microsoft.PowerToys.Settings.UI.Library [JsonPropertyName("hotkey")] public KeyboardKeysProperty Hotkey { get; set; } + [JsonPropertyName("increase-opacity-hotkey")] + public KeyboardKeysProperty IncreaseOpacityHotkey { get; set; } + + [JsonPropertyName("decrease-opacity-hotkey")] + public KeyboardKeysProperty DecreaseOpacityHotkey { get; set; } + [JsonPropertyName("frame-enabled")] public BoolProperty FrameEnabled { get; set; } diff --git a/src/settings-ui/Settings.UI.Library/AlwaysOnTopSettings.cs b/src/settings-ui/Settings.UI.Library/AlwaysOnTopSettings.cs index cb7e138596..ea0a986a06 100644 --- a/src/settings-ui/Settings.UI.Library/AlwaysOnTopSettings.cs +++ b/src/settings-ui/Settings.UI.Library/AlwaysOnTopSettings.cs @@ -40,6 +40,14 @@ namespace Microsoft.PowerToys.Settings.UI.Library () => Properties.Hotkey.Value, value => Properties.Hotkey.Value = value ?? AlwaysOnTopProperties.DefaultHotkeyValue, "AlwaysOnTop_ActivationShortcut"), + new HotkeyAccessor( + () => Properties.IncreaseOpacityHotkey.Value, + value => Properties.IncreaseOpacityHotkey.Value = value ?? AlwaysOnTopProperties.DefaultIncreaseOpacityHotkeyValue, + "AlwaysOnTop_IncreaseOpacityShortcut"), + new HotkeyAccessor( + () => Properties.DecreaseOpacityHotkey.Value, + value => Properties.DecreaseOpacityHotkey.Value = value ?? AlwaysOnTopProperties.DefaultDecreaseOpacityHotkeyValue, + "AlwaysOnTop_DecreaseOpacityShortcut"), }; return hotkeyAccessors.ToArray(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/AlwaysOnTopPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/AlwaysOnTopPage.xaml index 5b4df4d11e..b07548c5d8 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/AlwaysOnTopPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/AlwaysOnTopPage.xaml @@ -34,13 +34,13 @@ IsExpanded="True"> - - - - - - - + + + + + + + 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 cd3c692291..1bdf397f72 100644 --- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw @@ -3054,11 +3054,17 @@ Right-click to remove the key combination, thereby deactivating the shortcut. Customize the shortcut to pin or unpin an app window - - Press **{0}** to increase the opacity of the window + + Increase opacity - - Press **{0}** to decrease the opacity of the window + + Customize the shortcut to increase the opacity of a pinned window + + + Decrease opacity + + + Customize the shortcut to decrease the opacity of a pinned window Always On Top diff --git a/src/settings-ui/Settings.UI/ViewModels/AlwaysOnTopViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/AlwaysOnTopViewModel.cs index 534a0710f0..665b334bb4 100644 --- a/src/settings-ui/Settings.UI/ViewModels/AlwaysOnTopViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/AlwaysOnTopViewModel.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Runtime.CompilerServices; using System.Text.Json; using global::PowerToys.GPOWrapper; @@ -50,6 +49,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels Settings = moduleSettingsRepository.SettingsConfig; _hotkey = Settings.Properties.Hotkey.Value; + _increaseOpacityHotkey = Settings.Properties.IncreaseOpacityHotkey.Value; + _decreaseOpacityHotkey = Settings.Properties.DecreaseOpacityHotkey.Value; _showInSystemMenu = Settings.Properties.ShowInSystemMenu.Value; _frameEnabled = Settings.Properties.FrameEnabled.Value; _frameThickness = Settings.Properties.FrameThickness.Value; @@ -85,7 +86,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels { var hotkeysDict = new Dictionary { - [ModuleName] = [Hotkey], + [ModuleName] = [Hotkey, IncreaseOpacityHotkey, DecreaseOpacityHotkey], }; return hotkeysDict; @@ -135,10 +136,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels Settings.Properties.Hotkey.Value = _hotkey; NotifyPropertyChanged(); - // Also notify that transparency shortcut strings have changed - OnPropertyChanged(nameof(IncreaseOpacityShortcut)); - OnPropertyChanged(nameof(DecreaseOpacityShortcut)); - // Using InvariantCulture as this is an IPC message SendConfigMSG( string.Format( @@ -150,6 +147,52 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } } + public HotkeySettings IncreaseOpacityHotkey + { + get => _increaseOpacityHotkey; + + set + { + if (value != _increaseOpacityHotkey) + { + _increaseOpacityHotkey = value ?? AlwaysOnTopProperties.DefaultIncreaseOpacityHotkeyValue; + + Settings.Properties.IncreaseOpacityHotkey.Value = _increaseOpacityHotkey; + NotifyPropertyChanged(); + + SendConfigMSG( + string.Format( + CultureInfo.InvariantCulture, + "{{ \"powertoys\": {{ \"{0}\": {1} }} }}", + AlwaysOnTopSettings.ModuleName, + JsonSerializer.Serialize(Settings, SourceGenerationContextContext.Default.AlwaysOnTopSettings))); + } + } + } + + public HotkeySettings DecreaseOpacityHotkey + { + get => _decreaseOpacityHotkey; + + set + { + if (value != _decreaseOpacityHotkey) + { + _decreaseOpacityHotkey = value ?? AlwaysOnTopProperties.DefaultDecreaseOpacityHotkeyValue; + + Settings.Properties.DecreaseOpacityHotkey.Value = _decreaseOpacityHotkey; + NotifyPropertyChanged(); + + SendConfigMSG( + string.Format( + CultureInfo.InvariantCulture, + "{{ \"powertoys\": {{ \"{0}\": {1} }} }}", + AlwaysOnTopSettings.ModuleName, + JsonSerializer.Serialize(Settings, SourceGenerationContextContext.Default.AlwaysOnTopSettings))); + } + } + } + public bool FrameEnabled { get => _frameEnabled; @@ -310,32 +353,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } } - /// - /// Gets the formatted shortcut string for increasing window opacity (modifier keys + "+"). - /// - public string IncreaseOpacityShortcut - { - get - { - var modifiers = new HotkeySettings(_hotkey.Win, _hotkey.Ctrl, _hotkey.Alt, _hotkey.Shift, 0).ToString(); - var shortcut = string.IsNullOrEmpty(modifiers) ? "+" : modifiers + " + +"; - return string.Format(CultureInfo.CurrentCulture, ResourceLoaderInstance.ResourceLoader.GetString("AlwaysOnTop_IncreaseOpacity"), shortcut); - } - } - - /// - /// Gets the formatted shortcut string for decreasing window opacity (modifier keys + "-"). - /// - public string DecreaseOpacityShortcut - { - get - { - var modifiers = new HotkeySettings(_hotkey.Win, _hotkey.Ctrl, _hotkey.Alt, _hotkey.Shift, 0).ToString(); - var shortcut = string.IsNullOrEmpty(modifiers) ? "-" : modifiers + " + -"; - return string.Format(CultureInfo.CurrentCulture, ResourceLoaderInstance.ResourceLoader.GetString("AlwaysOnTop_DecreaseOpacity"), shortcut); - } - } - public void NotifyPropertyChanged([CallerMemberName] string propertyName = null) { OnPropertyChanged(propertyName); @@ -352,6 +369,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels private bool _enabledStateIsGPOConfigured; private bool _isEnabled; private HotkeySettings _hotkey; + private HotkeySettings _increaseOpacityHotkey; + private HotkeySettings _decreaseOpacityHotkey; private bool _showInSystemMenu; private bool _frameEnabled; private int _frameThickness; diff --git a/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs index 3d68dd9e25..a64e25c25d 100644 --- a/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs @@ -521,7 +521,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels ISettingsRepository moduleSettingsRepository = SettingsRepository.GetInstance(SettingsUtils.Default); var list = new List { - new DashboardModuleShortcutItem() { Label = resourceLoader.GetString("AlwaysOnTop_ShortDescription"), Shortcut = moduleSettingsRepository.SettingsConfig.Properties.Hotkey.Value.GetKeysList() }, + new DashboardModuleShortcutItem() { Label = resourceLoader.GetString("AlwaysOnTop_ActivationShortcut/Header"), Shortcut = moduleSettingsRepository.SettingsConfig.Properties.Hotkey.Value.GetKeysList() }, + new DashboardModuleShortcutItem() { Label = resourceLoader.GetString("AlwaysOnTop_IncreaseOpacityShortcut/Header"), Shortcut = moduleSettingsRepository.SettingsConfig.Properties.IncreaseOpacityHotkey.Value.GetKeysList() }, + new DashboardModuleShortcutItem() { Label = resourceLoader.GetString("AlwaysOnTop_DecreaseOpacityShortcut/Header"), Shortcut = moduleSettingsRepository.SettingsConfig.Properties.DecreaseOpacityHotkey.Value.GetKeysList() }, }; return new ObservableCollection(list); }