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); auto processPath = get_process_path(window);
CharUpperBuffW(processPath.data(), static_cast<DWORD>(processPath.length())); 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) : AlwaysOnTop::AlwaysOnTop(bool useLLKH, DWORD mainThreadId) :
@@ -155,7 +156,8 @@ void AlwaysOnTop::SettingsUpdate(SettingId id)
break; break;
case SettingId::FrameEnabled: case SettingId::FrameEnabled:
{ {
if (AlwaysOnTopSettings::settings().enableFrame) const auto settings = AlwaysOnTopSettings::settings();
if (settings->enableFrame)
{ {
for (auto& iter : m_topmostWindows) for (auto& iter : m_topmostWindows)
{ {
@@ -194,7 +196,8 @@ void AlwaysOnTop::SettingsUpdate(SettingId id)
break; break;
case SettingId::ShowInSystemMenu: case SettingId::ShowInSystemMenu:
{ {
UpdateSystemMenuEventHooks(AlwaysOnTopSettings::settings().showInSystemMenu); const auto settings = AlwaysOnTopSettings::settings();
UpdateSystemMenuEventHooks(settings->showInSystemMenu);
m_lastSystemMenuWindow = nullptr; m_lastSystemMenuWindow = nullptr;
UpdateSystemMenuItem(GetForegroundWindow()); UpdateSystemMenuItem(GetForegroundWindow());
} }
@@ -236,7 +239,7 @@ LRESULT AlwaysOnTop::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lp
void AlwaysOnTop::ProcessCommand(HWND window) void AlwaysOnTop::ProcessCommand(HWND window)
{ {
bool gameMode = detect_game_mode(); bool gameMode = detect_game_mode();
if (AlwaysOnTopSettings::settings().blockInGameMode && gameMode) if (AlwaysOnTopSettings::settings()->blockInGameMode && gameMode)
{ {
return; return;
} }
@@ -276,7 +279,7 @@ void AlwaysOnTop::ProcessCommand(HWND window)
} }
} }
if (AlwaysOnTopSettings::settings().enableSound) if (AlwaysOnTopSettings::settings()->enableSound)
{ {
m_sound.Play(soundType); m_sound.Play(soundType);
} }
@@ -323,7 +326,7 @@ void AlwaysOnTop::StartTrackingTopmostWindows()
bool AlwaysOnTop::AssignBorder(HWND window) 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); auto border = WindowBorder::Create(window, m_hinstance);
if (border) 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::IncreaseOpacity));
UnregisterHotKey(m_window, static_cast<int>(HotkeyId::DecreaseOpacity)); UnregisterHotKey(m_window, static_cast<int>(HotkeyId::DecreaseOpacity));
const auto settings = AlwaysOnTopSettings::settings();
// Register pin hotkey // 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 // 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::IncreaseOpacity), modifiers, VK_OEM_PLUS);
RegisterHotKey(m_window, static_cast<int>(HotkeyId::DecreaseOpacity), modifiers, VK_OEM_MINUS); 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) void AlwaysOnTop::UpdateSystemMenuEventHooks(bool enable)
@@ -525,7 +530,8 @@ void AlwaysOnTop::UpdateSystemMenuItem(HWND window) const noexcept
return; return;
} }
if (!AlwaysOnTopSettings::settings().showInSystemMenu) const auto settings = AlwaysOnTopSettings::settings();
if (!settings->showInSystemMenu)
{ {
if (IsAlwaysOnTopMenuCommand(systemMenu)) if (IsAlwaysOnTopMenuCommand(systemMenu))
{ {
@@ -644,7 +650,7 @@ void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
{ {
if (data->idObject == OBJID_SYSMENU && data->hwnd) 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); UpdateSystemMenuItem(data->hwnd);
} }
} }
@@ -659,7 +665,7 @@ void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
return; return;
case EVENT_OBJECT_INVOKED: case EVENT_OBJECT_INVOKED:
{ {
if (!AlwaysOnTopSettings::settings().showInSystemMenu) if (!AlwaysOnTopSettings::settings()->showInSystemMenu)
{ {
return; return;
} }
@@ -710,7 +716,7 @@ void AlwaysOnTop::HandleWinHookEvent(WinHookEvent* data) noexcept
break; break;
} }
if (!AlwaysOnTopSettings::settings().enableFrame || !data->hwnd) if (!AlwaysOnTopSettings::settings()->enableFrame || !data->hwnd)
{ {
return; return;
} }
@@ -879,7 +885,7 @@ void AlwaysOnTop::StepWindowTransparency(HWND window, int delta)
{ {
ApplyWindowAlpha(targetWindow, newTransparency); ApplyWindowAlpha(targetWindow, newTransparency);
if (AlwaysOnTopSettings::settings().enableSound) if (AlwaysOnTopSettings::settings()->enableSound)
{ {
m_sound.Play(delta > 0 ? Sound::Type::IncreaseOpacity : Sound::Type::DecreaseOpacity); 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, m_uiSettings.ColorValuesChanged([&](winrt::Windows::UI::ViewManagement::UISettings const& settings,
winrt::Windows::Foundation::IInspectable const& args) winrt::Windows::Foundation::IInspectable const& args)
{ {
if (m_settings.frameAccentColor) const auto currentSettings = AlwaysOnTopSettings::settings();
if (currentSettings->frameAccentColor)
{ {
NotifyObservers(SettingId::FrameAccentColor); NotifyObservers(SettingId::FrameAccentColor);
} }
@@ -95,94 +97,97 @@ void AlwaysOnTopSettings::LoadSettings()
try try
{ {
PowerToysSettings::PowerToyValues values = PowerToysSettings::PowerToyValues::load_from_settings_file(NonLocalizable::ModuleKey); 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)) if (const auto jsonVal = values.get_json(NonLocalizable::HotkeyID))
{ {
auto val = PowerToysSettings::HotkeyObject::from_json(*jsonVal); 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; updatedSettings->hotkey = val;
NotifyObservers(SettingId::Hotkey); changedSettings.push_back(SettingId::Hotkey);
} }
} }
if (const auto jsonVal = values.get_bool_value(NonLocalizable::SoundEnabledID)) if (const auto jsonVal = values.get_bool_value(NonLocalizable::SoundEnabledID))
{ {
auto val = *jsonVal; auto val = *jsonVal;
if (m_settings.enableSound != val) if (updatedSettings->enableSound != val)
{ {
m_settings.enableSound = val; updatedSettings->enableSound = val;
NotifyObservers(SettingId::SoundEnabled); changedSettings.push_back(SettingId::SoundEnabled);
} }
} }
if (const auto jsonVal = values.get_bool_value(NonLocalizable::ShowInSystemMenuID)) if (const auto jsonVal = values.get_bool_value(NonLocalizable::ShowInSystemMenuID))
{ {
auto val = *jsonVal; auto val = *jsonVal;
if (m_settings.showInSystemMenu != val) if (updatedSettings->showInSystemMenu != val)
{ {
m_settings.showInSystemMenu = val; updatedSettings->showInSystemMenu = val;
NotifyObservers(SettingId::ShowInSystemMenu); changedSettings.push_back(SettingId::ShowInSystemMenu);
} }
} }
if (const auto jsonVal = values.get_int_value(NonLocalizable::FrameThicknessID)) if (const auto jsonVal = values.get_int_value(NonLocalizable::FrameThicknessID))
{ {
auto val = *jsonVal; auto val = *jsonVal;
if (m_settings.frameThickness != val) if (updatedSettings->frameThickness != val)
{ {
m_settings.frameThickness = val; updatedSettings->frameThickness = val;
NotifyObservers(SettingId::FrameThickness); changedSettings.push_back(SettingId::FrameThickness);
} }
} }
if (const auto jsonVal = values.get_string_value(NonLocalizable::FrameColorID)) if (const auto jsonVal = values.get_string_value(NonLocalizable::FrameColorID))
{ {
auto val = HexToRGB(*jsonVal); auto val = HexToRGB(*jsonVal);
if (m_settings.frameColor != val) if (updatedSettings->frameColor != val)
{ {
m_settings.frameColor = val; updatedSettings->frameColor = val;
NotifyObservers(SettingId::FrameColor); changedSettings.push_back(SettingId::FrameColor);
} }
} }
if (const auto jsonVal = values.get_int_value(NonLocalizable::FrameOpacityID)) if (const auto jsonVal = values.get_int_value(NonLocalizable::FrameOpacityID))
{ {
auto val = *jsonVal; auto val = *jsonVal;
if (m_settings.frameOpacity != val) if (updatedSettings->frameOpacity != val)
{ {
m_settings.frameOpacity = val; updatedSettings->frameOpacity = val;
NotifyObservers(SettingId::FrameOpacity); changedSettings.push_back(SettingId::FrameOpacity);
} }
} }
if (const auto jsonVal = values.get_bool_value(NonLocalizable::FrameEnabledID)) if (const auto jsonVal = values.get_bool_value(NonLocalizable::FrameEnabledID))
{ {
auto val = *jsonVal; auto val = *jsonVal;
if (m_settings.enableFrame != val) if (updatedSettings->enableFrame != val)
{ {
m_settings.enableFrame = val; updatedSettings->enableFrame = val;
NotifyObservers(SettingId::FrameEnabled); changedSettings.push_back(SettingId::FrameEnabled);
} }
} }
if (const auto jsonVal = values.get_bool_value(NonLocalizable::BlockInGameModeID)) if (const auto jsonVal = values.get_bool_value(NonLocalizable::BlockInGameModeID))
{ {
auto val = *jsonVal; auto val = *jsonVal;
if (m_settings.blockInGameMode != val) if (updatedSettings->blockInGameMode != val)
{ {
m_settings.blockInGameMode = val; updatedSettings->blockInGameMode = val;
NotifyObservers(SettingId::BlockInGameMode); changedSettings.push_back(SettingId::BlockInGameMode);
} }
} }
if (const auto jsonVal = values.get_bool_value(NonLocalizable::RoundCornersEnabledID)) if (const auto jsonVal = values.get_bool_value(NonLocalizable::RoundCornersEnabledID))
{ {
auto val = *jsonVal; auto val = *jsonVal;
if (m_settings.roundCornersEnabled != val) if (updatedSettings->roundCornersEnabled != val)
{ {
m_settings.roundCornersEnabled = val; updatedSettings->roundCornersEnabled = val;
NotifyObservers(SettingId::RoundCornersEnabled); changedSettings.push_back(SettingId::RoundCornersEnabled);
} }
} }
@@ -203,20 +208,29 @@ void AlwaysOnTopSettings::LoadSettings()
view = left_trim<wchar_t>(trim<wchar_t>(view)); view = left_trim<wchar_t>(trim<wchar_t>(view));
} }
if (m_settings.excludedApps != excludedApps) if (updatedSettings->excludedApps != excludedApps)
{ {
m_settings.excludedApps = excludedApps; updatedSettings->excludedApps = excludedApps;
NotifyObservers(SettingId::ExcludeApps); changedSettings.push_back(SettingId::ExcludeApps);
} }
} }
if (const auto jsonVal = values.get_bool_value(NonLocalizable::FrameAccentColor)) if (const auto jsonVal = values.get_bool_value(NonLocalizable::FrameAccentColor))
{ {
auto val = *jsonVal; auto val = *jsonVal;
if (m_settings.frameAccentColor != val) if (updatedSettings->frameAccentColor != val)
{ {
m_settings.frameAccentColor = val; updatedSettings->frameAccentColor = val;
NotifyObservers(SettingId::FrameAccentColor); 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 #pragma once
#include <atomic>
#include <memory>
#include <unordered_set> #include <unordered_set>
#include <vector>
#include <common/SettingsAPI/FileWatcher.h> #include <common/SettingsAPI/FileWatcher.h>
#include <common/SettingsAPI/settings_objects.h> #include <common/SettingsAPI/settings_objects.h>
@@ -34,9 +37,9 @@ class AlwaysOnTopSettings
{ {
public: public:
static AlwaysOnTopSettings& instance(); 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(); void InitFileWatcher();
@@ -52,7 +55,7 @@ private:
~AlwaysOnTopSettings() = default; ~AlwaysOnTopSettings() = default;
winrt::Windows::UI::ViewManagement::UISettings m_uiSettings; 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::unique_ptr<FileWatcher> m_settingsFileWatcher;
std::unordered_set<SettingsObserver*> m_observers; std::unordered_set<SettingsObserver*> m_observers;

View File

@@ -23,7 +23,7 @@ std::optional<RECT> GetFrameRect(HWND window)
return std::nullopt; return std::nullopt;
} }
int border = AlwaysOnTopSettings::settings().frameThickness; int border = AlwaysOnTopSettings::settings()->frameThickness;
rect.top -= border; rect.top -= border;
rect.left -= border; rect.left -= border;
rect.right += border; rect.right += border;
@@ -194,8 +194,9 @@ void WindowBorder::UpdateBorderProperties() const
RECT frameRect{ 0, 0, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top }; RECT frameRect{ 0, 0, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top };
const auto settings = AlwaysOnTopSettings::settings();
COLORREF color; COLORREF color;
if (AlwaysOnTopSettings::settings().frameAccentColor) if (settings->frameAccentColor)
{ {
winrt::Windows::UI::ViewManagement::UISettings settings; winrt::Windows::UI::ViewManagement::UISettings settings;
auto accentValue = settings.GetColorValue(winrt::Windows::UI::ViewManagement::UIColorType::Accent); auto accentValue = settings.GetColorValue(winrt::Windows::UI::ViewManagement::UIColorType::Accent);
@@ -203,14 +204,14 @@ void WindowBorder::UpdateBorderProperties() const
} }
else 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 scalingFactor = ScalingUtils::ScalingFactor(m_trackingWindow);
float thickness = AlwaysOnTopSettings::settings().frameThickness * scalingFactor; float thickness = settings->frameThickness * scalingFactor;
float cornerRadius = 0.0; float cornerRadius = 0.0;
if (AlwaysOnTopSettings::settings().roundCornersEnabled) if (settings->roundCornersEnabled)
{ {
cornerRadius = WindowCornerUtils::CornersRadius(m_trackingWindow) * scalingFactor; 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) void WindowBorder::SettingsUpdate(SettingId id)
{ {
if (!AlwaysOnTopSettings::settings().enableFrame) if (!AlwaysOnTopSettings::settings()->enableFrame)
{ {
return; return;
} }