Always On Top: The opacity should be able to configure the hotkey individually (#46410)

<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## 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.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [X] Closes: #46391, #46387
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
<img width="1184" height="351" alt="image"
src="https://github.com/user-attachments/assets/5d20ffae-9f0c-4ce3-9d85-2ba1efea6301"
/>

<img width="336" height="244" alt="image"
src="https://github.com/user-attachments/assets/a78cc4a3-9eb3-49f1-bbb9-d6db37554e53"
/>

Verified locally that transparency hotkey will not intercept the normal
hotkey in window if Always on top not enabled

---------

Co-authored-by: Niels Laute <niels.laute@live.nl>
This commit is contained in:
Kai Tao
2026-03-25 02:02:36 +08:00
committed by GitHub
parent fa78cc8ea7
commit 21f06b8bd0
11 changed files with 196 additions and 110 deletions

View File

@@ -76,7 +76,7 @@ bool isExcluded(HWND window)
} }
AlwaysOnTop::AlwaysOnTop(bool useLLKH, DWORD mainThreadId) : 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<HINSTANCE>(&__ImageBase)), m_hinstance(reinterpret_cast<HINSTANCE>(&__ImageBase)),
m_useCentralizedLLKH(useLLKH), m_useCentralizedLLKH(useLLKH),
m_mainThreadId(mainThreadId), m_mainThreadId(mainThreadId),
@@ -150,6 +150,8 @@ void AlwaysOnTop::SettingsUpdate(SettingId id)
switch (id) switch (id)
{ {
case SettingId::Hotkey: case SettingId::Hotkey:
case SettingId::IncreaseOpacityHotkey:
case SettingId::DecreaseOpacityHotkey:
{ {
RegisterHotkey(); RegisterHotkey();
} }
@@ -360,10 +362,8 @@ void AlwaysOnTop::RegisterHotkey() const
// Register pin hotkey // Register pin hotkey
RegisterHotKey(m_window, static_cast<int>(HotkeyId::Pin), settings->hotkey.get_modifiers(), settings->hotkey.get_code()); RegisterHotKey(m_window, static_cast<int>(HotkeyId::Pin), settings->hotkey.get_modifiers(), settings->hotkey.get_code());
// Register transparency hotkeys using the same modifiers as the pin hotkey RegisterHotKey(m_window, static_cast<int>(HotkeyId::IncreaseOpacity), settings->increaseOpacityHotkey.get_modifiers(), settings->increaseOpacityHotkey.get_code());
UINT modifiers = settings->hotkey.get_modifiers(); RegisterHotKey(m_window, static_cast<int>(HotkeyId::DecreaseOpacity), settings->decreaseOpacityHotkey.get_modifiers(), settings->decreaseOpacityHotkey.get_code());
RegisterHotKey(m_window, static_cast<int>(HotkeyId::IncreaseOpacity), modifiers, VK_OEM_PLUS);
RegisterHotKey(m_window, static_cast<int>(HotkeyId::DecreaseOpacity), modifiers, VK_OEM_MINUS);
} }
void AlwaysOnTop::RegisterLLKH() void AlwaysOnTop::RegisterLLKH()

View File

@@ -13,6 +13,8 @@ namespace NonLocalizable
const static wchar_t* SettingsFileName = L"settings.json"; const static wchar_t* SettingsFileName = L"settings.json";
const static wchar_t* HotkeyID = L"hotkey"; 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* SoundEnabledID = L"sound-enabled";
const static wchar_t* ShowInSystemMenuID = L"show-in-system-menu"; const static wchar_t* ShowInSystemMenuID = L"show-in-system-menu";
const static wchar_t* FrameEnabledID = L"frame-enabled"; const static wchar_t* FrameEnabledID = L"frame-enabled";
@@ -100,16 +102,21 @@ void AlwaysOnTopSettings::LoadSettings()
const auto currentSettings = AlwaysOnTopSettings::settings(); const auto currentSettings = AlwaysOnTopSettings::settings();
auto updatedSettings = std::make_shared<Settings>(*currentSettings); auto updatedSettings = std::make_shared<Settings>(*currentSettings);
std::vector<SettingId> changedSettings; std::vector<SettingId> changedSettings;
const auto updateHotkeySetting = [&](const wchar_t* hotkeyName, auto& currentHotkey, SettingId settingId) {
if (const auto jsonVal = values.get_json(NonLocalizable::HotkeyID)) if (const auto jsonVal = values.get_json(hotkeyName))
{
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())
{ {
updatedSettings->hotkey = val; auto val = PowerToysSettings::HotkeyObject::from_json(*jsonVal);
changedSettings.push_back(SettingId::Hotkey); 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)) if (const auto jsonVal = values.get_bool_value(NonLocalizable::SoundEnabledID))
{ {

View File

@@ -18,6 +18,8 @@ class SettingsObserver;
struct Settings struct Settings
{ {
PowerToysSettings::HotkeyObject hotkey = PowerToysSettings::HotkeyObject::from_settings(true, true, false, false, 84); // win + ctrl + T 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 minTransparencyPercentage = 20; // minimum transparency (can't go below 20%)
static constexpr int maxTransparencyPercentage = 100; // maximum (fully opaque) static constexpr int maxTransparencyPercentage = 100; // maximum (fully opaque)
static constexpr int transparencyStep = 10; // step size for +/- adjustment static constexpr int transparencyStep = 10; // step size for +/- adjustment

View File

@@ -3,6 +3,8 @@
enum class SettingId enum class SettingId
{ {
Hotkey = 0, Hotkey = 0,
IncreaseOpacityHotkey,
DecreaseOpacityHotkey,
SoundEnabled, SoundEnabled,
ShowInSystemMenu, ShowInSystemMenu,
FrameEnabled, FrameEnabled,

View File

@@ -16,6 +16,8 @@
namespace NonLocalizable namespace NonLocalizable
{ {
const wchar_t ModulePath[] = L"PowerToys.AlwaysOnTop.exe"; 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 namespace
@@ -27,6 +29,8 @@ namespace
const wchar_t JSON_KEY_SHIFT[] = L"shift"; const wchar_t JSON_KEY_SHIFT[] = L"shift";
const wchar_t JSON_KEY_CODE[] = L"code"; const wchar_t JSON_KEY_CODE[] = L"code";
const wchar_t JSON_KEY_HOTKEY[] = L"hotkey"; 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"; const wchar_t JSON_KEY_VALUE[] = L"value";
} }
@@ -107,27 +111,38 @@ public:
virtual bool on_hotkey(size_t hotkeyId) override 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()) if (!is_process_running())
{ {
Enable(); 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); return false;
}
else if (hotkeyId == 1)
{
SetEvent(m_hIncreaseOpacityEvent);
}
else if (hotkeyId == 2)
{
SetEvent(m_hDecreaseOpacityEvent);
} }
if (!is_process_running())
{
Enable();
}
SetEvent(hotkeyId == 1 ? m_hIncreaseOpacityEvent : m_hDecreaseOpacityEvent);
return true; return true;
} }
@@ -136,48 +151,48 @@ public:
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
{ {
size_t count = 0; constexpr size_t hotkeyCount = 3;
Hotkey configuredHotkeys[hotkeyCount] = { m_hotkey, m_increaseOpacityHotkey, m_decreaseOpacityHotkey };
// Hotkey 0: Pin/Unpin (e.g., Win+Ctrl+T)
if (m_hotkey.key) for (size_t i = 0; i < hotkeyCount; ++i)
{ {
if (hotkeys && buffer_size > count) configuredHotkeys[i].id = static_cast<int>(i);
{ configuredHotkeys[i].isShown = configuredHotkeys[i].key != 0;
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++;
} }
// Hotkey 1: Increase opacity (same modifiers + VK_OEM_PLUS '=') if (hotkeys)
if (m_hotkey.key)
{ {
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[i] = configuredHotkeys[i];
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);
} }
count++;
} }
// Hotkey 2: Decrease opacity (same modifiers + VK_OEM_MINUS '-') Logger::trace(L"AlwaysOnTop hotkey[0]: win={}, ctrl={}, shift={}, alt={}, key={}, shown={}",
if (m_hotkey.key) configuredHotkeys[0].win,
{ configuredHotkeys[0].ctrl,
if (hotkeys && buffer_size > count) configuredHotkeys[0].shift,
{ configuredHotkeys[0].alt,
hotkeys[count] = m_hotkey; configuredHotkeys[0].key,
hotkeys[count].key = VK_OEM_MINUS; // '-' key configuredHotkeys[0].isShown);
Logger::trace(L"AlwaysOnTop hotkey[2] (decrease opacity): win={}, ctrl={}, shift={}, alt={}, key={}", Logger::trace(L"AlwaysOnTop hotkey[1] (increase opacity): win={}, ctrl={}, shift={}, alt={}, key={}, shown={}",
hotkeys[count].win, hotkeys[count].ctrl, hotkeys[count].shift, hotkeys[count].alt, hotkeys[count].key); configuredHotkeys[1].win,
} configuredHotkeys[1].ctrl,
count++; 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); Logger::trace(L"AlwaysOnTop get_hotkeys returning count={}", hotkeyCount);
return count; return hotkeyCount;
} }
// Enable the powertoy // Enable the powertoy
@@ -279,21 +294,34 @@ private:
void parse_hotkey(PowerToysSettings::PowerToyValues& settings) 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<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
}
catch (...)
{
}
};
auto settingsObject = settings.get_raw_json(); auto settingsObject = settings.get_raw_json();
if (settingsObject.GetView().Size()) if (settingsObject.GetView().Size())
{ {
try try
{ {
auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_HOTKEY).GetNamedObject(JSON_KEY_VALUE); auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES);
m_hotkey.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN); parseSingleHotkey(propertiesObject, JSON_KEY_HOTKEY, m_hotkey);
m_hotkey.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT); parseSingleHotkey(propertiesObject, JSON_KEY_INCREASE_OPACITY_HOTKEY, m_increaseOpacityHotkey);
m_hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT); parseSingleHotkey(propertiesObject, JSON_KEY_DECREASE_OPACITY_HOTKEY, m_decreaseOpacityHotkey);
m_hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
m_hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
} }
catch (...) catch (...)
{ {
Logger::error("Failed to initialize AlwaysOnTop start shortcut"); Logger::error("Failed to initialize AlwaysOnTop shortcuts");
} }
} }
else else
@@ -329,7 +357,9 @@ private:
bool m_enabled = false; bool m_enabled = false;
HANDLE m_hProcess = nullptr; 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 to event used to pin/unpin windows
HANDLE m_hPinEvent; HANDLE m_hPinEvent;

View File

@@ -11,6 +11,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public class AlwaysOnTopProperties public class AlwaysOnTopProperties
{ {
public static readonly HotkeySettings DefaultHotkeyValue = new HotkeySettings(true, true, false, false, 0x54); 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 DefaultFrameEnabled = true;
public const bool DefaultShowInSystemMenu = false; public const bool DefaultShowInSystemMenu = false;
public const int DefaultFrameThickness = 15; public const int DefaultFrameThickness = 15;
@@ -24,6 +26,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public AlwaysOnTopProperties() public AlwaysOnTopProperties()
{ {
Hotkey = new KeyboardKeysProperty(DefaultHotkeyValue); Hotkey = new KeyboardKeysProperty(DefaultHotkeyValue);
IncreaseOpacityHotkey = new KeyboardKeysProperty(DefaultIncreaseOpacityHotkeyValue);
DecreaseOpacityHotkey = new KeyboardKeysProperty(DefaultDecreaseOpacityHotkeyValue);
ShowInSystemMenu = new BoolProperty(DefaultShowInSystemMenu); ShowInSystemMenu = new BoolProperty(DefaultShowInSystemMenu);
FrameEnabled = new BoolProperty(DefaultFrameEnabled); FrameEnabled = new BoolProperty(DefaultFrameEnabled);
FrameThickness = new IntProperty(DefaultFrameThickness); FrameThickness = new IntProperty(DefaultFrameThickness);
@@ -39,6 +43,12 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("hotkey")] [JsonPropertyName("hotkey")]
public KeyboardKeysProperty Hotkey { get; set; } 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")] [JsonPropertyName("frame-enabled")]
public BoolProperty FrameEnabled { get; set; } public BoolProperty FrameEnabled { get; set; }

View File

@@ -40,6 +40,14 @@ namespace Microsoft.PowerToys.Settings.UI.Library
() => Properties.Hotkey.Value, () => Properties.Hotkey.Value,
value => Properties.Hotkey.Value = value ?? AlwaysOnTopProperties.DefaultHotkeyValue, value => Properties.Hotkey.Value = value ?? AlwaysOnTopProperties.DefaultHotkeyValue,
"AlwaysOnTop_ActivationShortcut"), "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(); return hotkeyAccessors.ToArray();

View File

@@ -34,13 +34,13 @@
IsExpanded="True"> IsExpanded="True">
<controls:ShortcutControl HotkeySettings="{x:Bind Path=ViewModel.Hotkey, Mode=TwoWay}" /> <controls:ShortcutControl HotkeySettings="{x:Bind Path=ViewModel.Hotkey, Mode=TwoWay}" />
<tkcontrols:SettingsExpander.Items> <tkcontrols:SettingsExpander.Items>
<tkcontrols:SettingsCard> <!-- HACK: For some weird reason, a ShortcutControl does not work correctly if it's the first or last item in the expander, so we add an invisible card. -->
<tkcontrols:SettingsCard.Description> <tkcontrols:SettingsCard Visibility="Collapsed" />
<StackPanel Orientation="Vertical"> <tkcontrols:SettingsCard x:Uid="AlwaysOnTop_IncreaseOpacityShortcut">
<tkcontrols:MarkdownTextBlock Config="{StaticResource DescriptionTextMarkdownConfig}" Text="{x:Bind ViewModel.IncreaseOpacityShortcut, Mode=OneWay}" /> <controls:ShortcutControl HotkeySettings="{x:Bind Path=ViewModel.IncreaseOpacityHotkey, Mode=TwoWay}" />
<tkcontrols:MarkdownTextBlock Config="{StaticResource DescriptionTextMarkdownConfig}" Text="{x:Bind ViewModel.DecreaseOpacityShortcut, Mode=OneWay}" /> </tkcontrols:SettingsCard>
</StackPanel> <tkcontrols:SettingsCard x:Uid="AlwaysOnTop_DecreaseOpacityShortcut">
</tkcontrols:SettingsCard.Description> <controls:ShortcutControl HotkeySettings="{x:Bind Path=ViewModel.DecreaseOpacityHotkey, Mode=TwoWay}" />
</tkcontrols:SettingsCard> </tkcontrols:SettingsCard>
<tkcontrols:SettingsCard ContentAlignment="Left"> <tkcontrols:SettingsCard ContentAlignment="Left">
<CheckBox x:Uid="AlwaysOnTop_GameMode" IsChecked="{x:Bind ViewModel.DoNotActivateOnGameMode, Mode=TwoWay}" /> <CheckBox x:Uid="AlwaysOnTop_GameMode" IsChecked="{x:Bind ViewModel.DoNotActivateOnGameMode, Mode=TwoWay}" />

View File

@@ -3054,11 +3054,17 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
<data name="AlwaysOnTop_ActivationShortcut.Description" xml:space="preserve"> <data name="AlwaysOnTop_ActivationShortcut.Description" xml:space="preserve">
<value>Customize the shortcut to pin or unpin an app window</value> <value>Customize the shortcut to pin or unpin an app window</value>
</data> </data>
<data name="AlwaysOnTop_IncreaseOpacity" xml:space="preserve"> <data name="AlwaysOnTop_IncreaseOpacityShortcut.Header" xml:space="preserve">
<value>Press **{0}** to increase the opacity of the window</value> <value>Increase opacity</value>
</data> </data>
<data name="AlwaysOnTop_DecreaseOpacity" xml:space="preserve"> <data name="AlwaysOnTop_IncreaseOpacityShortcut.Description" xml:space="preserve">
<value>Press **{0}** to decrease the opacity of the window</value> <value>Customize the shortcut to increase the opacity of a pinned window</value>
</data>
<data name="AlwaysOnTop_DecreaseOpacityShortcut.Header" xml:space="preserve">
<value>Decrease opacity</value>
</data>
<data name="AlwaysOnTop_DecreaseOpacityShortcut.Description" xml:space="preserve">
<value>Customize the shortcut to decrease the opacity of a pinned window</value>
</data> </data>
<data name="Oobe_AlwaysOnTop.Title" xml:space="preserve"> <data name="Oobe_AlwaysOnTop.Title" xml:space="preserve">
<value>Always On Top</value> <value>Always On Top</value>

View File

@@ -5,7 +5,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text.Json; using System.Text.Json;
using global::PowerToys.GPOWrapper; using global::PowerToys.GPOWrapper;
@@ -50,6 +49,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
Settings = moduleSettingsRepository.SettingsConfig; Settings = moduleSettingsRepository.SettingsConfig;
_hotkey = Settings.Properties.Hotkey.Value; _hotkey = Settings.Properties.Hotkey.Value;
_increaseOpacityHotkey = Settings.Properties.IncreaseOpacityHotkey.Value;
_decreaseOpacityHotkey = Settings.Properties.DecreaseOpacityHotkey.Value;
_showInSystemMenu = Settings.Properties.ShowInSystemMenu.Value; _showInSystemMenu = Settings.Properties.ShowInSystemMenu.Value;
_frameEnabled = Settings.Properties.FrameEnabled.Value; _frameEnabled = Settings.Properties.FrameEnabled.Value;
_frameThickness = Settings.Properties.FrameThickness.Value; _frameThickness = Settings.Properties.FrameThickness.Value;
@@ -85,7 +86,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{ {
var hotkeysDict = new Dictionary<string, HotkeySettings[]> var hotkeysDict = new Dictionary<string, HotkeySettings[]>
{ {
[ModuleName] = [Hotkey], [ModuleName] = [Hotkey, IncreaseOpacityHotkey, DecreaseOpacityHotkey],
}; };
return hotkeysDict; return hotkeysDict;
@@ -135,10 +136,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
Settings.Properties.Hotkey.Value = _hotkey; Settings.Properties.Hotkey.Value = _hotkey;
NotifyPropertyChanged(); NotifyPropertyChanged();
// Also notify that transparency shortcut strings have changed
OnPropertyChanged(nameof(IncreaseOpacityShortcut));
OnPropertyChanged(nameof(DecreaseOpacityShortcut));
// Using InvariantCulture as this is an IPC message // Using InvariantCulture as this is an IPC message
SendConfigMSG( SendConfigMSG(
string.Format( 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 public bool FrameEnabled
{ {
get => _frameEnabled; get => _frameEnabled;
@@ -310,32 +353,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
} }
} }
/// <summary>
/// Gets the formatted shortcut string for increasing window opacity (modifier keys + "+").
/// </summary>
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);
}
}
/// <summary>
/// Gets the formatted shortcut string for decreasing window opacity (modifier keys + "-").
/// </summary>
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) public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{ {
OnPropertyChanged(propertyName); OnPropertyChanged(propertyName);
@@ -352,6 +369,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private bool _enabledStateIsGPOConfigured; private bool _enabledStateIsGPOConfigured;
private bool _isEnabled; private bool _isEnabled;
private HotkeySettings _hotkey; private HotkeySettings _hotkey;
private HotkeySettings _increaseOpacityHotkey;
private HotkeySettings _decreaseOpacityHotkey;
private bool _showInSystemMenu; private bool _showInSystemMenu;
private bool _frameEnabled; private bool _frameEnabled;
private int _frameThickness; private int _frameThickness;

View File

@@ -521,7 +521,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
ISettingsRepository<AlwaysOnTopSettings> moduleSettingsRepository = SettingsRepository<AlwaysOnTopSettings>.GetInstance(SettingsUtils.Default); ISettingsRepository<AlwaysOnTopSettings> moduleSettingsRepository = SettingsRepository<AlwaysOnTopSettings>.GetInstance(SettingsUtils.Default);
var list = new List<DashboardModuleItem> var list = new List<DashboardModuleItem>
{ {
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<DashboardModuleItem>(list); return new ObservableCollection<DashboardModuleItem>(list);
} }