Fix: Fix a issue that change always on top settings won't take effect immediately (#45994)

<!-- 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
Fix #45993

Always on top have a window proc thread that will reload the settings
once file watcher trigger a reload of settings.
And always on top has a worker thread to read the settings at the same
time.
So it may happen worker thread will read the stale setting, as a result,
user change settings, and try to invoke always on top, as if nothing has
changed.
As the issue's recording shows.

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

- [X] Closes: #45993
<!-- - [ ] 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

The setting take effect once it's changed:


https://github.com/user-attachments/assets/70d753e9-eca1-4040-9abf-4cfa4e8dacec
This commit is contained in:
Kai Tao
2026-03-09 11:25:40 +08:00
committed by GitHub
parent 3d69785ca4
commit 75fb296bb2
4 changed files with 83 additions and 59 deletions

View File

@@ -71,7 +71,8 @@ bool isExcluded(HWND window)
auto processPath = get_process_path(window);
CharUpperBuffW(processPath.data(), static_cast<DWORD>(processPath.length()));
return check_excluded_app(window, processPath, AlwaysOnTopSettings::settings().excludedApps);
const auto settings = AlwaysOnTopSettings::settings();
return check_excluded_app(window, processPath, settings->excludedApps);
}
AlwaysOnTop::AlwaysOnTop(bool useLLKH, DWORD mainThreadId) :
@@ -155,7 +156,8 @@ void AlwaysOnTop::SettingsUpdate(SettingId id)
break;
case SettingId::FrameEnabled:
{
if (AlwaysOnTopSettings::settings().enableFrame)
const auto settings = AlwaysOnTopSettings::settings();
if (settings->enableFrame)
{
for (auto& iter : m_topmostWindows)
{
@@ -194,7 +196,8 @@ void AlwaysOnTop::SettingsUpdate(SettingId id)
break;
case SettingId::ShowInSystemMenu:
{
UpdateSystemMenuEventHooks(AlwaysOnTopSettings::settings().showInSystemMenu);
const auto settings = AlwaysOnTopSettings::settings();
UpdateSystemMenuEventHooks(settings->showInSystemMenu);
m_lastSystemMenuWindow = nullptr;
UpdateSystemMenuItem(GetForegroundWindow());
}
@@ -236,7 +239,7 @@ LRESULT AlwaysOnTop::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lp
void AlwaysOnTop::ProcessCommand(HWND window)
{
bool gameMode = detect_game_mode();
if (AlwaysOnTopSettings::settings().blockInGameMode && gameMode)
if (AlwaysOnTopSettings::settings()->blockInGameMode && gameMode)
{
return;
}
@@ -276,7 +279,7 @@ void AlwaysOnTop::ProcessCommand(HWND window)
}
}
if (AlwaysOnTopSettings::settings().enableSound)
if (AlwaysOnTopSettings::settings()->enableSound)
{
m_sound.Play(soundType);
}
@@ -323,7 +326,7 @@ void AlwaysOnTop::StartTrackingTopmostWindows()
bool AlwaysOnTop::AssignBorder(HWND window)
{
if (m_virtualDesktopUtils.IsWindowOnCurrentDesktop(window) && AlwaysOnTopSettings::settings().enableFrame)
if (m_virtualDesktopUtils.IsWindowOnCurrentDesktop(window) && AlwaysOnTopSettings::settings()->enableFrame)
{
auto border = WindowBorder::Create(window, m_hinstance);
if (border)
@@ -352,11 +355,13 @@ void AlwaysOnTop::RegisterHotkey() const
UnregisterHotKey(m_window, static_cast<int>(HotkeyId::IncreaseOpacity));
UnregisterHotKey(m_window, static_cast<int>(HotkeyId::DecreaseOpacity));
const auto settings = AlwaysOnTopSettings::settings();
// Register pin hotkey
RegisterHotKey(m_window, static_cast<int>(HotkeyId::Pin), AlwaysOnTopSettings::settings().hotkey.get_modifiers(), AlwaysOnTopSettings::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
UINT modifiers = AlwaysOnTopSettings::settings().hotkey.get_modifiers();
UINT modifiers = 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);
}
@@ -472,7 +477,7 @@ void AlwaysOnTop::SubscribeToEvents()
}
}
UpdateSystemMenuEventHooks(AlwaysOnTopSettings::settings().showInSystemMenu);
UpdateSystemMenuEventHooks(AlwaysOnTopSettings::settings()->showInSystemMenu);
}
void AlwaysOnTop::UpdateSystemMenuEventHooks(bool enable)
@@ -525,7 +530,8 @@ void AlwaysOnTop::UpdateSystemMenuItem(HWND window) const noexcept
return;
}
if (!AlwaysOnTopSettings::settings().showInSystemMenu)
const auto settings = AlwaysOnTopSettings::settings();
if (!settings->showInSystemMenu)
{
if (IsAlwaysOnTopMenuCommand(systemMenu))
{
@@ -644,7 +650,7 @@ void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
{
if (data->idObject == OBJID_SYSMENU && data->hwnd)
{
m_lastSystemMenuWindow = AlwaysOnTopSettings::settings().showInSystemMenu ? data->hwnd : nullptr;
m_lastSystemMenuWindow = AlwaysOnTopSettings::settings()->showInSystemMenu ? data->hwnd : nullptr;
UpdateSystemMenuItem(data->hwnd);
}
}
@@ -659,7 +665,7 @@ void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
return;
case EVENT_OBJECT_INVOKED:
{
if (!AlwaysOnTopSettings::settings().showInSystemMenu)
if (!AlwaysOnTopSettings::settings()->showInSystemMenu)
{
return;
}
@@ -710,7 +716,7 @@ void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
break;
}
if (!AlwaysOnTopSettings::settings().enableFrame || !data->hwnd)
if (!AlwaysOnTopSettings::settings()->enableFrame || !data->hwnd)
{
return;
}
@@ -879,7 +885,7 @@ void AlwaysOnTop::StepWindowTransparency(HWND window, int delta)
{
ApplyWindowAlpha(targetWindow, newTransparency);
if (AlwaysOnTopSettings::settings().enableSound)
if (AlwaysOnTopSettings::settings()->enableSound)
{
m_sound.Play(delta > 0 ? Sound::Type::IncreaseOpacity : Sound::Type::DecreaseOpacity);
}

View File

@@ -44,12 +44,14 @@ inline COLORREF HexToRGB(std::wstring_view hex, const COLORREF fallbackColor = R
}
}
AlwaysOnTopSettings::AlwaysOnTopSettings()
AlwaysOnTopSettings::AlwaysOnTopSettings() :
m_settings(std::make_shared<Settings>())
{
m_uiSettings.ColorValuesChanged([&](winrt::Windows::UI::ViewManagement::UISettings const& settings,
winrt::Windows::Foundation::IInspectable const& args)
{
if (m_settings.frameAccentColor)
const auto currentSettings = AlwaysOnTopSettings::settings();
if (currentSettings->frameAccentColor)
{
NotifyObservers(SettingId::FrameAccentColor);
}
@@ -95,94 +97,97 @@ void AlwaysOnTopSettings::LoadSettings()
try
{
PowerToysSettings::PowerToyValues values = PowerToysSettings::PowerToyValues::load_from_settings_file(NonLocalizable::ModuleKey);
const auto currentSettings = AlwaysOnTopSettings::settings();
auto updatedSettings = std::make_shared<Settings>(*currentSettings);
std::vector<SettingId> changedSettings;
if (const auto jsonVal = values.get_json(NonLocalizable::HotkeyID))
{
auto val = PowerToysSettings::HotkeyObject::from_json(*jsonVal);
if (m_settings.hotkey.get_modifiers() != val.get_modifiers() || m_settings.hotkey.get_key() != val.get_key() || m_settings.hotkey.get_code() != val.get_code())
if (updatedSettings->hotkey.get_modifiers() != val.get_modifiers() || updatedSettings->hotkey.get_key() != val.get_key() || updatedSettings->hotkey.get_code() != val.get_code())
{
m_settings.hotkey = val;
NotifyObservers(SettingId::Hotkey);
updatedSettings->hotkey = val;
changedSettings.push_back(SettingId::Hotkey);
}
}
if (const auto jsonVal = values.get_bool_value(NonLocalizable::SoundEnabledID))
{
auto val = *jsonVal;
if (m_settings.enableSound != val)
if (updatedSettings->enableSound != val)
{
m_settings.enableSound = val;
NotifyObservers(SettingId::SoundEnabled);
updatedSettings->enableSound = val;
changedSettings.push_back(SettingId::SoundEnabled);
}
}
if (const auto jsonVal = values.get_bool_value(NonLocalizable::ShowInSystemMenuID))
{
auto val = *jsonVal;
if (m_settings.showInSystemMenu != val)
if (updatedSettings->showInSystemMenu != val)
{
m_settings.showInSystemMenu = val;
NotifyObservers(SettingId::ShowInSystemMenu);
updatedSettings->showInSystemMenu = val;
changedSettings.push_back(SettingId::ShowInSystemMenu);
}
}
if (const auto jsonVal = values.get_int_value(NonLocalizable::FrameThicknessID))
{
auto val = *jsonVal;
if (m_settings.frameThickness != val)
if (updatedSettings->frameThickness != val)
{
m_settings.frameThickness = val;
NotifyObservers(SettingId::FrameThickness);
updatedSettings->frameThickness = val;
changedSettings.push_back(SettingId::FrameThickness);
}
}
if (const auto jsonVal = values.get_string_value(NonLocalizable::FrameColorID))
{
auto val = HexToRGB(*jsonVal);
if (m_settings.frameColor != val)
if (updatedSettings->frameColor != val)
{
m_settings.frameColor = val;
NotifyObservers(SettingId::FrameColor);
updatedSettings->frameColor = val;
changedSettings.push_back(SettingId::FrameColor);
}
}
if (const auto jsonVal = values.get_int_value(NonLocalizable::FrameOpacityID))
{
auto val = *jsonVal;
if (m_settings.frameOpacity != val)
if (updatedSettings->frameOpacity != val)
{
m_settings.frameOpacity = val;
NotifyObservers(SettingId::FrameOpacity);
updatedSettings->frameOpacity = val;
changedSettings.push_back(SettingId::FrameOpacity);
}
}
if (const auto jsonVal = values.get_bool_value(NonLocalizable::FrameEnabledID))
{
auto val = *jsonVal;
if (m_settings.enableFrame != val)
if (updatedSettings->enableFrame != val)
{
m_settings.enableFrame = val;
NotifyObservers(SettingId::FrameEnabled);
updatedSettings->enableFrame = val;
changedSettings.push_back(SettingId::FrameEnabled);
}
}
if (const auto jsonVal = values.get_bool_value(NonLocalizable::BlockInGameModeID))
{
auto val = *jsonVal;
if (m_settings.blockInGameMode != val)
if (updatedSettings->blockInGameMode != val)
{
m_settings.blockInGameMode = val;
NotifyObservers(SettingId::BlockInGameMode);
updatedSettings->blockInGameMode = val;
changedSettings.push_back(SettingId::BlockInGameMode);
}
}
if (const auto jsonVal = values.get_bool_value(NonLocalizable::RoundCornersEnabledID))
{
auto val = *jsonVal;
if (m_settings.roundCornersEnabled != val)
if (updatedSettings->roundCornersEnabled != val)
{
m_settings.roundCornersEnabled = val;
NotifyObservers(SettingId::RoundCornersEnabled);
updatedSettings->roundCornersEnabled = val;
changedSettings.push_back(SettingId::RoundCornersEnabled);
}
}
@@ -203,20 +208,29 @@ void AlwaysOnTopSettings::LoadSettings()
view = left_trim<wchar_t>(trim<wchar_t>(view));
}
if (m_settings.excludedApps != excludedApps)
if (updatedSettings->excludedApps != excludedApps)
{
m_settings.excludedApps = excludedApps;
NotifyObservers(SettingId::ExcludeApps);
updatedSettings->excludedApps = excludedApps;
changedSettings.push_back(SettingId::ExcludeApps);
}
}
if (const auto jsonVal = values.get_bool_value(NonLocalizable::FrameAccentColor))
{
auto val = *jsonVal;
if (m_settings.frameAccentColor != val)
if (updatedSettings->frameAccentColor != val)
{
m_settings.frameAccentColor = val;
NotifyObservers(SettingId::FrameAccentColor);
updatedSettings->frameAccentColor = val;
changedSettings.push_back(SettingId::FrameAccentColor);
}
}
if (!changedSettings.empty())
{
m_settings.store(std::shared_ptr<const Settings>(updatedSettings), std::memory_order_release);
for (const auto changedSetting : changedSettings)
{
NotifyObservers(changedSetting);
}
}
}

View File

@@ -1,6 +1,9 @@
#pragma once
#include <atomic>
#include <memory>
#include <unordered_set>
#include <vector>
#include <common/SettingsAPI/FileWatcher.h>
#include <common/SettingsAPI/settings_objects.h>
@@ -34,9 +37,9 @@ class AlwaysOnTopSettings
{
public:
static AlwaysOnTopSettings& instance();
static inline const Settings& settings()
static inline std::shared_ptr<const Settings> settings()
{
return instance().m_settings;
return instance().m_settings.load(std::memory_order_acquire);
}
void InitFileWatcher();
@@ -52,7 +55,7 @@ private:
~AlwaysOnTopSettings() = default;
winrt::Windows::UI::ViewManagement::UISettings m_uiSettings;
Settings m_settings;
std::atomic<std::shared_ptr<const Settings>> m_settings;
std::unique_ptr<FileWatcher> m_settingsFileWatcher;
std::unordered_set<SettingsObserver*> m_observers;

View File

@@ -23,7 +23,7 @@ std::optional<RECT> GetFrameRect(HWND window)
return std::nullopt;
}
int border = AlwaysOnTopSettings::settings().frameThickness;
int border = AlwaysOnTopSettings::settings()->frameThickness;
rect.top -= border;
rect.left -= border;
rect.right += border;
@@ -194,8 +194,9 @@ void WindowBorder::UpdateBorderProperties() const
RECT frameRect{ 0, 0, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top };
const auto settings = AlwaysOnTopSettings::settings();
COLORREF color;
if (AlwaysOnTopSettings::settings().frameAccentColor)
if (settings->frameAccentColor)
{
winrt::Windows::UI::ViewManagement::UISettings settings;
auto accentValue = settings.GetColorValue(winrt::Windows::UI::ViewManagement::UIColorType::Accent);
@@ -203,14 +204,14 @@ void WindowBorder::UpdateBorderProperties() const
}
else
{
color = AlwaysOnTopSettings::settings().frameColor;
color = settings->frameColor;
}
float opacity = AlwaysOnTopSettings::settings().frameOpacity / 100.0f;
float opacity = settings->frameOpacity / 100.0f;
float scalingFactor = ScalingUtils::ScalingFactor(m_trackingWindow);
float thickness = AlwaysOnTopSettings::settings().frameThickness * scalingFactor;
float thickness = settings->frameThickness * scalingFactor;
float cornerRadius = 0.0;
if (AlwaysOnTopSettings::settings().roundCornersEnabled)
if (settings->roundCornersEnabled)
{
cornerRadius = WindowCornerUtils::CornersRadius(m_trackingWindow) * scalingFactor;
}
@@ -268,7 +269,7 @@ LRESULT WindowBorder::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexce
void WindowBorder::SettingsUpdate(SettingId id)
{
if (!AlwaysOnTopSettings::settings().enableFrame)
if (!AlwaysOnTopSettings::settings()->enableFrame)
{
return;
}