Always on top: Add transparent support for on topped window (#44815)

<!-- 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
Transparency support (best-effort)
> Not every window can be made transparent. Transparency is applied on a
best-effort basis and depends on how the target app/window is built and
rendered.

## When it may not work
* Windows with special rendering pipelines (e.g., certain
hardware-accelerated / compositor-managed surfaces).
* Some tool/popup/owned windows where the foreground window isn’t the
actual surface being drawn.

## How it works (high-level)
* Resolve the best target window (preferring the top-level/root window
over transient children).
* Apply Windows’ standard layered-window alpha mechanism (per-window
opacity) to adjust transparency.
* When unpinned, Restore the original opacity/state when possible.

If transparency doesn’t change, it means the window doesn’t support this
mechanism in its current configuration.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [X] Closes: 
#43278 
#42929
#28773

<!-- - [ ] 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


https://github.com/user-attachments/assets/c97a87f2-3126-4e19-990f-8c684dbeb631

<img width="1119" height="426" alt="image"
src="https://github.com/user-attachments/assets/547671ee-81d3-4c94-8199-bf0c4b1b7760"
/>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Kai Tao
2026-01-29 13:48:27 +08:00
committed by GitHub
parent 4986915dae
commit 6d4f56cd83
10 changed files with 409 additions and 25 deletions

View File

@@ -1532,6 +1532,7 @@ riid
RKey
RNumber
rollups
ROOTOWNER
rop
ROUNDSMALL
ROWSETEXT

View File

@@ -72,6 +72,10 @@ namespace CommonSharedConstants
const wchar_t ALWAYS_ON_TOP_TERMINATE_EVENT[] = L"Local\\AlwaysOnTopTerminateEvent-cfdf1eae-791f-4953-8021-2f18f3837eae";
const wchar_t ALWAYS_ON_TOP_INCREASE_OPACITY_EVENT[] = L"Local\\AlwaysOnTopIncreaseOpacityEvent-a1b2c3d4-e5f6-7890-abcd-ef1234567890";
const wchar_t ALWAYS_ON_TOP_DECREASE_OPACITY_EVENT[] = L"Local\\AlwaysOnTopDecreaseOpacityEvent-b2c3d4e5-f6a7-8901-bcde-f12345678901";
// Path to the event used by PowerAccent
const wchar_t POWERACCENT_EXIT_EVENT[] = L"Local\\PowerToysPowerAccentExitEvent-53e93389-d19a-4fbb-9b36-1981c8965e17";

View File

@@ -153,10 +153,22 @@ LRESULT AlwaysOnTop::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lp
{
if (message == WM_HOTKEY)
{
int hotkeyId = static_cast<int>(wparam);
if (HWND fw{ GetForegroundWindow() })
{
if (hotkeyId == static_cast<int>(HotkeyId::Pin))
{
ProcessCommand(fw);
}
else if (hotkeyId == static_cast<int>(HotkeyId::IncreaseOpacity))
{
StepWindowTransparency(fw, Settings::transparencyStep);
}
else if (hotkeyId == static_cast<int>(HotkeyId::DecreaseOpacity))
{
StepWindowTransparency(fw, -Settings::transparencyStep);
}
}
}
else if (message == WM_PRIV_SETTINGS_CHANGED)
{
@@ -191,6 +203,10 @@ void AlwaysOnTop::ProcessCommand(HWND window)
m_topmostWindows.erase(iter);
}
// Restore transparency when unpinning
RestoreWindowAlpha(window);
m_windowOriginalLayeredState.erase(window);
Trace::AlwaysOnTop::UnpinWindow();
}
}
@@ -200,6 +216,7 @@ void AlwaysOnTop::ProcessCommand(HWND window)
{
soundType = Sound::Type::On;
AssignBorder(window);
Trace::AlwaysOnTop::PinWindow();
}
}
@@ -269,11 +286,22 @@ void AlwaysOnTop::RegisterHotkey() const
{
if (m_useCentralizedLLKH)
{
// All hotkeys are handled by centralized LLKH
return;
}
// Register hotkeys only when not using centralized LLKH
UnregisterHotKey(m_window, static_cast<int>(HotkeyId::Pin));
UnregisterHotKey(m_window, static_cast<int>(HotkeyId::IncreaseOpacity));
UnregisterHotKey(m_window, static_cast<int>(HotkeyId::DecreaseOpacity));
// Register pin hotkey
RegisterHotKey(m_window, static_cast<int>(HotkeyId::Pin), AlwaysOnTopSettings::settings().hotkey.get_modifiers(), AlwaysOnTopSettings::settings().hotkey.get_code());
// Register transparency hotkeys using the same modifiers as the pin hotkey
UINT modifiers = AlwaysOnTopSettings::settings().hotkey.get_modifiers();
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()
@@ -285,6 +313,8 @@ void AlwaysOnTop::RegisterLLKH()
m_hPinEvent = CreateEventW(nullptr, false, false, CommonSharedConstants::ALWAYS_ON_TOP_PIN_EVENT);
m_hTerminateEvent = CreateEventW(nullptr, false, false, CommonSharedConstants::ALWAYS_ON_TOP_TERMINATE_EVENT);
m_hIncreaseOpacityEvent = CreateEventW(nullptr, false, false, CommonSharedConstants::ALWAYS_ON_TOP_INCREASE_OPACITY_EVENT);
m_hDecreaseOpacityEvent = CreateEventW(nullptr, false, false, CommonSharedConstants::ALWAYS_ON_TOP_DECREASE_OPACITY_EVENT);
if (!m_hPinEvent)
{
@@ -298,30 +328,54 @@ void AlwaysOnTop::RegisterLLKH()
return;
}
HANDLE handles[2] = { m_hPinEvent,
m_hTerminateEvent };
if (!m_hIncreaseOpacityEvent)
{
Logger::warn(L"Failed to create increaseOpacityEvent. {}", get_last_error_or_default(GetLastError()));
}
if (!m_hDecreaseOpacityEvent)
{
Logger::warn(L"Failed to create decreaseOpacityEvent. {}", get_last_error_or_default(GetLastError()));
}
HANDLE handles[4] = { m_hPinEvent,
m_hTerminateEvent,
m_hIncreaseOpacityEvent,
m_hDecreaseOpacityEvent };
m_thread = std::thread([this, handles]() {
MSG msg;
while (m_running)
{
DWORD dwEvt = MsgWaitForMultipleObjects(2, handles, false, INFINITE, QS_ALLINPUT);
DWORD dwEvt = MsgWaitForMultipleObjects(4, handles, false, INFINITE, QS_ALLINPUT);
if (!m_running)
{
break;
}
switch (dwEvt)
{
case WAIT_OBJECT_0:
case WAIT_OBJECT_0: // Pin event
if (HWND fw{ GetForegroundWindow() })
{
ProcessCommand(fw);
}
break;
case WAIT_OBJECT_0 + 1:
case WAIT_OBJECT_0 + 1: // Terminate event
PostThreadMessage(m_mainThreadId, WM_QUIT, 0, 0);
break;
case WAIT_OBJECT_0 + 2:
case WAIT_OBJECT_0 + 2: // Increase opacity event
if (HWND fw{ GetForegroundWindow() })
{
StepWindowTransparency(fw, Settings::transparencyStep);
}
break;
case WAIT_OBJECT_0 + 3: // Decrease opacity event
if (HWND fw{ GetForegroundWindow() })
{
StepWindowTransparency(fw, -Settings::transparencyStep);
}
break;
case WAIT_OBJECT_0 + 4: // Message queue
if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
@@ -370,9 +424,12 @@ void AlwaysOnTop::UnpinAll()
{
Logger::error(L"Unpinning topmost window failed");
}
// Restore transparency when unpinning all
RestoreWindowAlpha(topWindow);
}
m_topmostWindows.clear();
m_windowOriginalLayeredState.clear();
}
void AlwaysOnTop::CleanUp()
@@ -456,6 +513,7 @@ void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
for (const auto window : toErase)
{
m_topmostWindows.erase(window);
m_windowOriginalLayeredState.erase(window);
}
switch (data->event)
@@ -557,3 +615,165 @@ void AlwaysOnTop::RefreshBorders()
}
}
}
HWND AlwaysOnTop::ResolveTransparencyTargetWindow(HWND window)
{
if (!window || !IsWindow(window))
{
return nullptr;
}
// Only allow transparency changes on pinned windows
if (!IsPinned(window))
{
return nullptr;
}
return window;
}
void AlwaysOnTop::StepWindowTransparency(HWND window, int delta)
{
HWND targetWindow = ResolveTransparencyTargetWindow(window);
if (!targetWindow)
{
return;
}
int currentTransparency = Settings::maxTransparencyPercentage;
LONG exStyle = GetWindowLong(targetWindow, GWL_EXSTYLE);
if (exStyle & WS_EX_LAYERED)
{
BYTE alpha = 255;
if (GetLayeredWindowAttributes(targetWindow, nullptr, &alpha, nullptr))
{
currentTransparency = (alpha * 100) / 255;
}
}
int newTransparency = (std::max)(Settings::minTransparencyPercentage,
(std::min)(Settings::maxTransparencyPercentage, currentTransparency + delta));
if (newTransparency != currentTransparency)
{
ApplyWindowAlpha(targetWindow, newTransparency);
if (AlwaysOnTopSettings::settings().enableSound)
{
m_sound.Play(delta > 0 ? Sound::Type::IncreaseOpacity : Sound::Type::DecreaseOpacity);
}
Logger::debug(L"Transparency adjusted to {}%", newTransparency);
}
}
void AlwaysOnTop::ApplyWindowAlpha(HWND window, int percentage)
{
if (!window || !IsWindow(window))
{
return;
}
percentage = (std::max)(Settings::minTransparencyPercentage,
(std::min)(Settings::maxTransparencyPercentage, percentage));
LONG exStyle = GetWindowLong(window, GWL_EXSTYLE);
bool isCurrentlyLayered = (exStyle & WS_EX_LAYERED) != 0;
// Cache original state on first transparency application
if (m_windowOriginalLayeredState.find(window) == m_windowOriginalLayeredState.end())
{
WindowLayeredState state;
state.hadLayeredStyle = isCurrentlyLayered;
if (isCurrentlyLayered)
{
BYTE alpha = 255;
COLORREF colorKey = 0;
DWORD flags = 0;
if (GetLayeredWindowAttributes(window, &colorKey, &alpha, &flags))
{
state.originalAlpha = alpha;
state.usedColorKey = (flags & LWA_COLORKEY) != 0;
state.colorKey = colorKey;
}
else
{
Logger::warn(L"GetLayeredWindowAttributes failed for layered window, skipping");
return;
}
}
m_windowOriginalLayeredState[window] = state;
}
// Clear WS_EX_LAYERED first to ensure SetLayeredWindowAttributes works
if (isCurrentlyLayered)
{
SetWindowLong(window, GWL_EXSTYLE, exStyle & ~WS_EX_LAYERED);
SetWindowPos(window, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
exStyle = GetWindowLong(window, GWL_EXSTYLE);
}
BYTE alphaValue = static_cast<BYTE>((255 * percentage) / 100);
SetWindowLong(window, GWL_EXSTYLE, exStyle | WS_EX_LAYERED);
SetLayeredWindowAttributes(window, 0, alphaValue, LWA_ALPHA);
}
void AlwaysOnTop::RestoreWindowAlpha(HWND window)
{
if (!window || !IsWindow(window))
{
return;
}
LONG exStyle = GetWindowLong(window, GWL_EXSTYLE);
auto it = m_windowOriginalLayeredState.find(window);
if (it != m_windowOriginalLayeredState.end())
{
const auto& originalState = it->second;
if (originalState.hadLayeredStyle)
{
// Window originally had WS_EX_LAYERED - restore original attributes
// Clear and re-add to ensure clean state
if (exStyle & WS_EX_LAYERED)
{
SetWindowLong(window, GWL_EXSTYLE, exStyle & ~WS_EX_LAYERED);
exStyle = GetWindowLong(window, GWL_EXSTYLE);
}
SetWindowLong(window, GWL_EXSTYLE, exStyle | WS_EX_LAYERED);
// Restore original alpha and/or color key
DWORD flags = LWA_ALPHA;
if (originalState.usedColorKey)
{
flags |= LWA_COLORKEY;
}
SetLayeredWindowAttributes(window, originalState.colorKey, originalState.originalAlpha, flags);
SetWindowPos(window, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
}
else
{
// Window originally didn't have WS_EX_LAYERED - remove it completely
if (exStyle & WS_EX_LAYERED)
{
SetLayeredWindowAttributes(window, 0, 255, LWA_ALPHA);
SetWindowLong(window, GWL_EXSTYLE, exStyle & ~WS_EX_LAYERED);
SetWindowPos(window, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
}
}
m_windowOriginalLayeredState.erase(it);
}
else
{
// Fallback: no cached state, just remove layered style
if (exStyle & WS_EX_LAYERED)
{
SetLayeredWindowAttributes(window, 0, 255, LWA_ALPHA);
SetWindowLong(window, GWL_EXSTYLE, exStyle & ~WS_EX_LAYERED);
}
}
}

View File

@@ -10,6 +10,7 @@
#include <common/hooks/WinHookEvent.h>
#include <common/notifications/NotificationUtil.h>
#include <common/utils/window.h>
class AlwaysOnTop : public SettingsObserver
{
@@ -38,6 +39,8 @@ private:
enum class HotkeyId : int
{
Pin = 1,
IncreaseOpacity = 2,
DecreaseOpacity = 3,
};
static inline AlwaysOnTop* s_instance = nullptr;
@@ -48,8 +51,20 @@ private:
HWND m_window{ nullptr };
HINSTANCE m_hinstance;
std::map<HWND, std::unique_ptr<WindowBorder>> m_topmostWindows{};
// Store original window layered state for proper restoration
struct WindowLayeredState {
bool hadLayeredStyle = false;
BYTE originalAlpha = 255;
bool usedColorKey = false;
COLORREF colorKey = 0;
};
std::map<HWND, WindowLayeredState> m_windowOriginalLayeredState{};
HANDLE m_hPinEvent;
HANDLE m_hTerminateEvent;
HANDLE m_hIncreaseOpacityEvent;
HANDLE m_hDecreaseOpacityEvent;
DWORD m_mainThreadId;
std::thread m_thread;
const bool m_useCentralizedLLKH;
@@ -78,6 +93,12 @@ private:
bool AssignBorder(HWND window);
void RefreshBorders();
// Transparency methods
HWND ResolveTransparencyTargetWindow(HWND window);
void StepWindowTransparency(HWND window, int delta);
void ApplyWindowAlpha(HWND window, int percentage);
void RestoreWindowAlpha(HWND window);
virtual void SettingsUpdate(SettingId type) override;
static void CALLBACK WinHookProc(HWINEVENTHOOK winEventHook,

View File

@@ -15,6 +15,9 @@ class SettingsObserver;
struct Settings
{
PowerToysSettings::HotkeyObject hotkey = PowerToysSettings::HotkeyObject::from_settings(true, true, false, false, 84); // win + ctrl + T
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
bool enableFrame = true;
bool enableSound = true;
bool roundCornersEnabled = true;

View File

@@ -2,7 +2,6 @@
#include "pch.h"
#include <atomic>
#include <mmsystem.h> // sound
class Sound
@@ -12,12 +11,10 @@ public:
{
On,
Off,
IncreaseOpacity,
DecreaseOpacity,
};
Sound()
: isPlaying(false)
{}
void Play(Type type)
{
BOOL success = false;
@@ -29,6 +26,12 @@ public:
case Type::Off:
success = PlaySound(TEXT("Media\\Speech Sleep.wav"), NULL, SND_FILENAME | SND_ASYNC);
break;
case Type::IncreaseOpacity:
success = PlaySound(TEXT("Media\\Windows Hardware Insert.wav"), NULL, SND_FILENAME | SND_ASYNC);
break;
case Type::DecreaseOpacity:
success = PlaySound(TEXT("Media\\Windows Hardware Remove.wav"), NULL, SND_FILENAME | SND_ASYNC);
break;
default:
break;
}
@@ -38,7 +41,4 @@ public:
Logger::error(L"Sound playing error");
}
}
private:
std::atomic<bool> isPlaying;
};

View File

@@ -105,17 +105,28 @@ public:
}
}
virtual bool on_hotkey(size_t /*hotkeyId*/) override
virtual bool on_hotkey(size_t hotkeyId) override
{
if (m_enabled)
{
Logger::trace(L"AlwaysOnTop hotkey pressed");
Logger::trace(L"AlwaysOnTop hotkey pressed, id={}", hotkeyId);
if (!is_process_running())
{
Enable();
}
if (hotkeyId == 0)
{
SetEvent(m_hPinEvent);
}
else if (hotkeyId == 1)
{
SetEvent(m_hIncreaseOpacityEvent);
}
else if (hotkeyId == 2)
{
SetEvent(m_hDecreaseOpacityEvent);
}
return true;
}
@@ -125,19 +136,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)
{
if (hotkeys && buffer_size >= 1)
if (hotkeys && buffer_size > count)
{
hotkeys[0] = m_hotkey;
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++;
}
return 1;
}
else
// Hotkey 1: Increase opacity (same modifiers + VK_OEM_PLUS '=')
if (m_hotkey.key)
{
return 0;
if (hotkeys && buffer_size > count)
{
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);
}
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 get_hotkeys returning count={}", count);
return count;
}
// Enable the powertoy
@@ -175,6 +215,8 @@ public:
app_key = NonLocalizable::ModuleKey;
m_hPinEvent = CreateDefaultEvent(CommonSharedConstants::ALWAYS_ON_TOP_PIN_EVENT);
m_hTerminateEvent = CreateDefaultEvent(CommonSharedConstants::ALWAYS_ON_TOP_TERMINATE_EVENT);
m_hIncreaseOpacityEvent = CreateDefaultEvent(CommonSharedConstants::ALWAYS_ON_TOP_INCREASE_OPACITY_EVENT);
m_hDecreaseOpacityEvent = CreateDefaultEvent(CommonSharedConstants::ALWAYS_ON_TOP_DECREASE_OPACITY_EVENT);
init_settings();
}
@@ -292,6 +334,8 @@ private:
// Handle to event used to pin/unpin windows
HANDLE m_hPinEvent;
HANDLE m_hTerminateEvent;
HANDLE m_hIncreaseOpacityEvent;
HANDLE m_hDecreaseOpacityEvent;
};
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()

View File

@@ -38,6 +38,24 @@
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
<tkcontrols:SettingsExpander
x:Uid="AlwaysOnTop_TransparencyInfo"
HeaderIcon="{ui:FontIcon Glyph=&#xE790;}"
IsExpanded="True">
<tkcontrols:SettingsExpander.Items>
<tkcontrols:SettingsCard ContentAlignment="Left">
<controls:ShortcutWithTextLabelControl x:Uid="AlwaysOnTop_IncreaseOpacity" Keys="{x:Bind ViewModel.IncreaseOpacityKeysList, Mode=OneWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard ContentAlignment="Left">
<controls:ShortcutWithTextLabelControl x:Uid="AlwaysOnTop_DecreaseOpacity" Keys="{x:Bind ViewModel.DecreaseOpacityKeysList, Mode=OneWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard>
<tkcontrols:SettingsCard.Description>
<TextBlock x:Uid="AlwaysOnTop_TransparencyRange" Style="{StaticResource SecondaryTextStyle}" />
</tkcontrols:SettingsCard.Description>
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="AlwaysOnTop_Behavior_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">

View File

@@ -3240,7 +3240,19 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
<value>Activation shortcut</value>
</data>
<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. Use the same modifier keys with + or - to adjust window transparency.</value>
</data>
<data name="AlwaysOnTop_TransparencyInfo.Header" xml:space="preserve">
<value>Transparency adjustment</value>
</data>
<data name="AlwaysOnTop_IncreaseOpacity.Text" xml:space="preserve">
<value>Increase opacity</value>
</data>
<data name="AlwaysOnTop_DecreaseOpacity.Text" xml:space="preserve">
<value>Decrease opacity</value>
</data>
<data name="AlwaysOnTop_TransparencyRange.Text" xml:space="preserve">
<value>Range: 20%-100%</value>
</data>
<data name="Oobe_AlwaysOnTop.Title" xml:space="preserve">
<value>Always On Top</value>

View File

@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.Json;
using global::PowerToys.GPOWrapper;
@@ -133,6 +134,10 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
Settings.Properties.Hotkey.Value = _hotkey;
NotifyPropertyChanged();
// Also notify that transparency keys have changed
OnPropertyChanged(nameof(IncreaseOpacityKeysList));
OnPropertyChanged(nameof(DecreaseOpacityKeysList));
// Using InvariantCulture as this is an IPC message
SendConfigMSG(
string.Format(
@@ -289,6 +294,62 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
/// <summary>
/// Gets the keys list for increasing window opacity (modifier keys + "+").
/// </summary>
public List<object> IncreaseOpacityKeysList
{
get
{
var keys = GetModifierKeysList();
keys.Add("+");
return keys;
}
}
/// <summary>
/// Gets the keys list for decreasing window opacity (modifier keys + "-").
/// </summary>
public List<object> DecreaseOpacityKeysList
{
get
{
var keys = GetModifierKeysList();
keys.Add("-");
return keys;
}
}
/// <summary>
/// Gets only the modifier keys from the current hotkey setting.
/// </summary>
private List<object> GetModifierKeysList()
{
var modifierKeys = new List<object>();
if (_hotkey.Win)
{
modifierKeys.Add(92); // The Windows key
}
if (_hotkey.Ctrl)
{
modifierKeys.Add("Ctrl");
}
if (_hotkey.Alt)
{
modifierKeys.Add("Alt");
}
if (_hotkey.Shift)
{
modifierKeys.Add(16); // The Shift key
}
return modifierKeys;
}
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
OnPropertyChanged(propertyName);