From e0c0e7bb76dfd2fa627e281d0aa48bc70d673358 Mon Sep 17 00:00:00 2001 From: Shawn Yuan <128874481+shuaiyuanxx@users.noreply.github.com> Date: Mon, 5 Jan 2026 13:38:43 +0800 Subject: [PATCH 1/5] Fixed keyvisual issue for pt run (#41539) ## Summary of the Pull Request ## PR Checklist - [x] Closes: #41468 - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **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 --------- Signed-off-by: Shawn Yuan --- src/runner/settings_window.cpp | 28 +++++++++++++++---- .../PowerLauncherProperties.cs | 3 ++ .../ViewModels/PowerLauncherViewModel.cs | 8 ++++++ .../ViewModels/ShortcutConflictViewModel.cs | 9 ++++++ 4 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/runner/settings_window.cpp b/src/runner/settings_window.cpp index 473fa7ebe3..e31b934d40 100644 --- a/src/runner/settings_window.cpp +++ b/src/runner/settings_window.cpp @@ -148,16 +148,19 @@ std::optional dispatch_json_action_to_module(const json::JsonObjec return result; } -void send_json_config_to_module(const std::wstring& module_key, const std::wstring& settings) +void send_json_config_to_module(const std::wstring& module_key, const std::wstring& settings, bool hotkeyUpdated) { auto moduleIt = modules().find(module_key); if (moduleIt != modules().end()) { moduleIt->second->set_config(settings.c_str()); - moduleIt->second.remove_hotkey_records(); - moduleIt->second.update_hotkeys(); - moduleIt->second.UpdateHotkeyEx(); + if (hotkeyUpdated) + { + moduleIt->second.remove_hotkey_records(); + moduleIt->second.update_hotkeys(); + moduleIt->second.UpdateHotkeyEx(); + } } } @@ -166,7 +169,22 @@ void dispatch_json_config_to_modules(const json::JsonObject& powertoys_configs) for (const auto& powertoy_element : powertoys_configs) { const auto element = powertoy_element.Value().Stringify(); - send_json_config_to_module(powertoy_element.Key().c_str(), element.c_str()); + + /* As PowerToys Run hotkeys are not registered by the runner, hotkey updates are + * triggered only when hotkey properties change to avoid incorrect conflict detection; + * otherwise, the existing logic remains. + */ + auto settings = powertoy_element.Value().GetObjectW(); + bool hotkeyUpdated = true; + if (settings.HasKey(L"properties")) + { + const auto properties = settings.GetNamedObject(L"properties"); + + // Currently, only PowerToys Run settings use the 'hotkey_changed' property. + json::get(properties, L"hotkey_changed", hotkeyUpdated, true); + } + + send_json_config_to_module(powertoy_element.Key().c_str(), element.c_str(), hotkeyUpdated); } }; diff --git a/src/settings-ui/Settings.UI.Library/PowerLauncherProperties.cs b/src/settings-ui/Settings.UI.Library/PowerLauncherProperties.cs index 590fb2e290..08eb16a389 100644 --- a/src/settings-ui/Settings.UI.Library/PowerLauncherProperties.cs +++ b/src/settings-ui/Settings.UI.Library/PowerLauncherProperties.cs @@ -94,6 +94,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library [JsonPropertyName("generate_thumbnails_from_files")] public bool GenerateThumbnailsFromFiles { get; set; } + [JsonPropertyName("hotkey_changed")] + public bool HotkeyChanged { get; set; } = false; + [CmdConfigureIgnoreAttribute] public HotkeySettings DefaultOpenPowerLauncher => new HotkeySettings(false, false, true, false, 32); diff --git a/src/settings-ui/Settings.UI/ViewModels/PowerLauncherViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/PowerLauncherViewModel.cs index 31efe28260..ee5b6e2f7c 100644 --- a/src/settings-ui/Settings.UI/ViewModels/PowerLauncherViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/PowerLauncherViewModel.cs @@ -31,6 +31,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels private bool _enabledStateIsGPOConfigured; private bool _isEnabled; private string _searchText; + private bool _hotkeyChanged; private GeneralSettings GeneralSettingsConfig { get; set; } @@ -162,6 +163,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels // Notify UI of property change OnPropertyChanged(propertyName); + // Since PowerLauncher registers its hotkeys independently within the module process, + // the runner is notified to update PowerLauncher’s hotkeys only when changes occur. + // This prevents incorrect conflict detection results. + settings.Properties.HotkeyChanged = _hotkeyChanged; + _hotkeyChanged = false; + callback(settings); } @@ -335,6 +342,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels if (settings.Properties.OpenPowerLauncher != value) { settings.Properties.OpenPowerLauncher = value ?? settings.Properties.DefaultOpenPowerLauncher; + _hotkeyChanged = true; UpdateSettings(); } } diff --git a/src/settings-ui/Settings.UI/ViewModels/ShortcutConflictViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/ShortcutConflictViewModel.cs index 6c6d624f97..48d1d09a40 100644 --- a/src/settings-ui/Settings.UI/ViewModels/ShortcutConflictViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/ShortcutConflictViewModel.cs @@ -242,6 +242,15 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels // No need to save settings here, the runner will call module interface to save it // SaveSettingsToFile(settings); + // For PowerToys Run, we should set the 'HotkeyChanged' property here to avoid issue #41468 + if (string.Equals(moduleName, PowerLauncherSettings.ModuleName, StringComparison.OrdinalIgnoreCase)) + { + if (settings is PowerLauncherSettings powerLauncherSettings) + { + powerLauncherSettings.Properties.HotkeyChanged = true; + } + } + // Send IPC notification using the same format as other ViewModels SendConfigMSG(settingsConfig, moduleName); } From 30f832ac651de3cf6255f7197620bab2411da710 Mon Sep 17 00:00:00 2001 From: NOXX - Commiter Date: Mon, 5 Jan 2026 13:27:40 +0200 Subject: [PATCH 2/5] Fix typo in 'Open With Antigravity' plugin name (#44529) Fix typo in 'Antygravity' to 'Antigravity' and add Project Launcher Plugin ## Summary of the Pull Request ## PR Checklist - [ ] Closes: #xxx - [ ] **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 --- doc/thirdPartyRunPlugins.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/thirdPartyRunPlugins.md b/doc/thirdPartyRunPlugins.md index 84214e5789..80e1c16cc1 100644 --- a/doc/thirdPartyRunPlugins.md +++ b/doc/thirdPartyRunPlugins.md @@ -50,7 +50,8 @@ Contact the developers of a plugin directly for assistance with a specific plugi | [Hotkeys](https://github.com/ruslanlap/PowerToysRun-Hotkeys) | [ruslanlap](https://github.com/ruslanlap) | Create, manage, and trigger custom keyboard shortcuts directly from PowerToys Run. | | [RandomGen](https://github.com/ruslanlap/PowerToysRun-RandomGen) | [ruslanlap](https://github.com/ruslanlap) | 🎲 Generate random data instantly with a single keystroke. Perfect for developers, testers, designers, and anyone who needs quick access to random data. Features include secure passwords, PINs, names, business data, dates, numbers, GUIDs, color codes, and more. Especially useful for designers who need random color codes and placeholder content. | | [Open With Cursor](https://github.com/VictorNoxx/PowerToys-Run-Cursor/) | [VictorNoxx](https://github.com/VictorNoxx) | Open Visual Studio, VS Code recents with Cursor AI | -| [Open With Antygravity](https://github.com/artickc/PowerToys-Run-Antygravity) | [artickc](https://github.com/artickc) | Open Visual Studio, VS Code recents with Cursor AI | +| [Open With Antigravity](https://github.com/artickc/PowerToys-Run-Antygravity) | [artickc](https://github.com/artickc) | Open Visual Studio, VS Code recents with Antigravity AI | +| [Project Launcher Plugin](https://github.com/artickc/ProjectLauncherPowerToysPlugin) | [artickc](https://github.com/artickc) | Access your projects using Project Launcher and PowerToys Run | | [CheatSheets](https://github.com/ruslanlap/PowerToysRun-CheatSheets) | [ruslanlap](https://github.com/ruslanlap) | 📚 Find cheat sheets and command examples instantly from tldr pages, cheat.sh, and devhints.io. Features include favorites system, categories, offline mode, and smart caching. | | [QuickAI](https://github.com/ruslanlap/PowerToysRun-QuickAi) | [ruslanlap](https://github.com/ruslanlap) | AI-powered assistance with instant, smart responses from multiple providers (Groq, Together, Fireworks, OpenRouter, Cohere) | From 36a64cae909b954216f9771335b06eb7f2354506 Mon Sep 17 00:00:00 2001 From: Kai Tao <69313318+vanzue@users.noreply.github.com> Date: Mon, 5 Jan 2026 22:41:01 +0800 Subject: [PATCH 3/5] Find My Mouse: Improve spotlight edge quality (#44533) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary of the Pull Request Find my mouse Optimization: Crisp, soft edge that doesn't scale with radius—avoids overly blurry edges at large sizes or hard edges at small sizes ## PR Checklist - [ ] Closes: #xxx - [ ] **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 | Optimization | Before | After | |--------|--------|--------| | Large Radius |image|image| |--------|--------|--------| | Small Radius |image|image| --- .github/actions/spell-check/expect.txt | 1 - .../MouseUtils/FindMyMouse/FindMyMouse.cpp | 284 ++++-------------- .../FindMyMouse/FindMyMouse.vcxproj | 2 +- 3 files changed, 52 insertions(+), 235 deletions(-) diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index ff7a8fdf02..ab3e43eb47 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -1810,7 +1810,6 @@ TILEDWINDOW TILLSON timedate timediff -timeunion timeutil TITLEBARINFO Titlecase diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp index 5221e12a63..1c408175a0 100644 --- a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp @@ -77,10 +77,8 @@ protected: int m_sonarRadius = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_RADIUS; int m_sonarZoomFactor = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_INITIAL_ZOOM; DWORD m_fadeDuration = FIND_MY_MOUSE_DEFAULT_ANIMATION_DURATION_MS; - int m_finalAlphaNumerator = 100; // legacy (root now always animates to 1.0; kept for GDI fallback compatibility) std::vector m_excludedApps; int m_shakeMinimumDistance = FIND_MY_MOUSE_DEFAULT_SHAKE_MINIMUM_DISTANCE; - static constexpr int FinalAlphaDenominator = 100; winrt::Microsoft::UI::Dispatching::DispatcherQueueController m_dispatcherQueueController{ nullptr }; // Don't consider movements started past these milliseconds to detect shaking. @@ -816,13 +814,16 @@ private: // Dim color (source) m_dimColorBrush = m_compositor.CreateColorBrush(m_backgroundColor); // Radial gradient mask (center transparent, outer opaque) + // Fixed feather width: 1px for radius < 300, 2px for radius >= 300 + const float featherPixels = (m_sonarRadius >= 300) ? 2.0f : 1.0f; + const float featherOffset = 1.0f - featherPixels / (rDip * zoom); m_spotlightMaskGradient = m_compositor.CreateRadialGradientBrush(); m_spotlightMaskGradient.MappingMode(muxc::CompositionMappingMode::Absolute); m_maskStopCenter = m_compositor.CreateColorGradientStop(); m_maskStopCenter.Offset(0.0f); m_maskStopCenter.Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0)); m_maskStopInner = m_compositor.CreateColorGradientStop(); - m_maskStopInner.Offset(0.995f); + m_maskStopInner.Offset(featherOffset); m_maskStopInner.Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0)); m_maskStopOuter = m_compositor.CreateColorGradientStop(); m_maskStopOuter.Offset(1.0f); @@ -852,23 +853,7 @@ private: m_root.ImplicitAnimations(collection); // 6) Spotlight radius shrinks as opacity increases (expression animation) - auto radiusExpression = m_compositor.CreateExpressionAnimation(); - radiusExpression.SetReferenceParameter(L"Root", m_root); - - wchar_t expressionText[256]; - winrt::check_hresult(StringCchPrintfW( - expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity)", m_sonarRadius * m_sonarZoomFactor, m_sonarRadius * m_sonarZoomFactor, m_sonarRadius, m_sonarRadius)); - - radiusExpression.Expression(expressionText); - m_spotlightMaskGradient.StartAnimation(L"EllipseRadius", radiusExpression); - // Also animate spotlight geometry radius for visual consistency - if (m_circleGeometry) - { - auto radiusExpression2 = m_compositor.CreateExpressionAnimation(); - radiusExpression2.SetReferenceParameter(L"Root", m_root); - radiusExpression2.Expression(expressionText); - m_circleGeometry.StartAnimation(L"Radius", radiusExpression2); - } + SetupRadiusAnimations(rDip * zoom, rDip, featherPixels); // Composition created successfully return true; @@ -887,6 +872,41 @@ private: } } + // Helper to setup radius and feather expression animations + void SetupRadiusAnimations(float startRadiusDip, float endRadiusDip, float featherPixels) + { + // Radius expression: shrinks from startRadiusDip to endRadiusDip as opacity goes 0->1 + auto radiusExpression = m_compositor.CreateExpressionAnimation(); + radiusExpression.SetReferenceParameter(L"Root", m_root); + wchar_t expressionText[256]; + winrt::check_hresult(StringCchPrintfW( + expressionText, ARRAYSIZE(expressionText), + L"Lerp(Vector2(%.1f, %.1f), Vector2(%.1f, %.1f), Root.Opacity)", + startRadiusDip, startRadiusDip, endRadiusDip, endRadiusDip)); + radiusExpression.Expression(expressionText); + m_spotlightMaskGradient.StartAnimation(L"EllipseRadius", radiusExpression); + + // Feather expression: maintains fixed pixel width as radius changes + auto featherExpression = m_compositor.CreateExpressionAnimation(); + featherExpression.SetReferenceParameter(L"Root", m_root); + wchar_t featherExpressionText[256]; + winrt::check_hresult(StringCchPrintfW( + featherExpressionText, ARRAYSIZE(featherExpressionText), + L"1.0f - %.1ff / Lerp(%.1ff, %.1ff, Root.Opacity)", + featherPixels, startRadiusDip, endRadiusDip)); + featherExpression.Expression(featherExpressionText); + m_maskStopInner.StartAnimation(L"Offset", featherExpression); + + // Circle geometry radius for visual consistency + if (m_circleGeometry) + { + auto radiusExpression2 = m_compositor.CreateExpressionAnimation(); + radiusExpression2.SetReferenceParameter(L"Root", m_root); + radiusExpression2.Expression(expressionText); + m_circleGeometry.StartAnimation(L"Radius", radiusExpression2); + } + } + void UpdateIslandSize() { if (!m_island) @@ -964,27 +984,21 @@ public: const float scale = static_cast(m_surface.XamlRoot().RasterizationScale()); const float rDip = m_sonarRadiusFloat / scale; const float zoom = static_cast(m_sonarZoomFactor); + const float featherPixels = (m_sonarRadius >= 300) ? 2.0f : 1.0f; + const float startRadiusDip = rDip * zoom; m_spotlightMaskGradient.StopAnimation(L"EllipseRadius"); - m_spotlightMaskGradient.EllipseCenter({ rDip * zoom, rDip * zoom }); - if (m_spotlight) - { - m_spotlight.Size({ rDip * 2 * zoom, rDip * 2 * zoom }); - m_circleShape.Offset({ rDip * zoom, rDip * zoom }); - } - auto radiusExpression = m_compositor.CreateExpressionAnimation(); - radiusExpression.SetReferenceParameter(L"Root", m_root); - wchar_t expressionText[256]; - winrt::check_hresult(StringCchPrintfW(expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity)", m_sonarRadius * m_sonarZoomFactor, m_sonarRadius * m_sonarZoomFactor, m_sonarRadius, m_sonarRadius)); - radiusExpression.Expression(expressionText); - m_spotlightMaskGradient.StartAnimation(L"EllipseRadius", radiusExpression); + m_maskStopInner.StopAnimation(L"Offset"); if (m_circleGeometry) { m_circleGeometry.StopAnimation(L"Radius"); - auto radiusExpression2 = m_compositor.CreateExpressionAnimation(); - radiusExpression2.SetReferenceParameter(L"Root", m_root); - radiusExpression2.Expression(expressionText); - m_circleGeometry.StartAnimation(L"Radius", radiusExpression2); } + m_spotlightMaskGradient.EllipseCenter({ startRadiusDip, startRadiusDip }); + if (m_spotlight) + { + m_spotlight.Size({ rDip * 2 * zoom, rDip * 2 * zoom }); + m_circleShape.Offset({ startRadiusDip, startRadiusDip }); + } + SetupRadiusAnimations(startRadiusDip, rDip, featherPixels); } }); if (!enqueueSucceeded) @@ -1018,202 +1032,6 @@ private: muxc::ScalarKeyFrameAnimation m_animation{ nullptr }; }; -template -struct GdiSonar : SuperSonar -{ - LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam) noexcept - { - switch (message) - { - case WM_CREATE: - SetLayeredWindowAttributes(this->m_hwnd, 0, 0, LWA_ALPHA); - break; - - case WM_TIMER: - switch (wParam) - { - case TIMER_ID_FADE: - OnFadeTimer(); - break; - } - break; - - case WM_PAINT: - this->Shim()->OnPaint(); - break; - } - return this->BaseWndProc(message, wParam, lParam); - } - - void BeforeMoveSonar() { this->Shim()->InvalidateSonar(); } - void AfterMoveSonar() { this->Shim()->InvalidateSonar(); } - - void SetSonarVisibility(bool visible) - { - m_alphaTarget = visible ? MaxAlpha : 0; - m_fadeStart = GetTickCount() - FadeFramePeriod; - SetTimer(this->m_hwnd, TIMER_ID_FADE, FadeFramePeriod, nullptr); - OnFadeTimer(); - } - - void OnFadeTimer() - { - auto now = GetTickCount(); - auto step = (int)((now - m_fadeStart) * MaxAlpha / this->m_fadeDuration); - - this->Shim()->InvalidateSonar(); - if (m_alpha < m_alphaTarget) - { - m_alpha += step; - if (m_alpha > m_alphaTarget) - m_alpha = m_alphaTarget; - } - else if (m_alpha > m_alphaTarget) - { - m_alpha -= step; - if (m_alpha < m_alphaTarget) - m_alpha = m_alphaTarget; - } - SetLayeredWindowAttributes(this->m_hwnd, 0, (BYTE)m_alpha, LWA_ALPHA); - this->Shim()->InvalidateSonar(); - if (m_alpha == m_alphaTarget) - { - KillTimer(this->m_hwnd, TIMER_ID_FADE); - if (m_alpha == 0) - { - ShowWindow(this->m_hwnd, SW_HIDE); - } - } - else - { - ShowWindow(this->m_hwnd, SW_SHOWNOACTIVATE); - } - } - -protected: - int CurrentSonarRadius() - { - int range = MaxAlpha - m_alpha; - int radius = this->m_sonarRadius + this->m_sonarRadius * range * (this->m_sonarZoomFactor - 1) / MaxAlpha; - return radius; - } - -private: - static constexpr DWORD FadeFramePeriod = 10; - int MaxAlpha = SuperSonar::m_finalAlphaNumerator * 255 / SuperSonar::FinalAlphaDenominator; - static constexpr DWORD TIMER_ID_FADE = 101; - -private: - int m_alpha = 0; - int m_alphaTarget = 0; - DWORD m_fadeStart = 0; -}; - -struct GdiSpotlight : GdiSonar -{ - void InvalidateSonar() - { - RECT rc; - auto radius = CurrentSonarRadius(); - rc.left = this->m_sonarPos.x - radius; - rc.top = this->m_sonarPos.y - radius; - rc.right = this->m_sonarPos.x + radius; - rc.bottom = this->m_sonarPos.y + radius; - InvalidateRect(this->m_hwnd, &rc, FALSE); - } - - void OnPaint() - { - PAINTSTRUCT ps; - BeginPaint(this->m_hwnd, &ps); - - auto radius = CurrentSonarRadius(); - auto spotlight = CreateRoundRectRgn( - this->m_sonarPos.x - radius, this->m_sonarPos.y - radius, this->m_sonarPos.x + radius, this->m_sonarPos.y + radius, radius * 2, radius * 2); - - FillRgn(ps.hdc, spotlight, static_cast(GetStockObject(WHITE_BRUSH))); - Sleep(1000 / 60); - ExtSelectClipRgn(ps.hdc, spotlight, RGN_DIFF); - FillRect(ps.hdc, &ps.rcPaint, static_cast(GetStockObject(BLACK_BRUSH))); - DeleteObject(spotlight); - - EndPaint(this->m_hwnd, &ps); - } -}; - -struct GdiCrosshairs : GdiSonar -{ - void InvalidateSonar() - { - RECT rc; - auto radius = CurrentSonarRadius(); - GetClientRect(m_hwnd, &rc); - rc.left = m_sonarPos.x - radius; - rc.right = m_sonarPos.x + radius; - InvalidateRect(m_hwnd, &rc, FALSE); - - GetClientRect(m_hwnd, &rc); - rc.top = m_sonarPos.y - radius; - rc.bottom = m_sonarPos.y + radius; - InvalidateRect(m_hwnd, &rc, FALSE); - } - - void OnPaint() - { - PAINTSTRUCT ps; - BeginPaint(this->m_hwnd, &ps); - - auto radius = CurrentSonarRadius(); - RECT rc; - - HBRUSH white = static_cast(GetStockObject(WHITE_BRUSH)); - - rc.left = m_sonarPos.x - radius; - rc.top = ps.rcPaint.top; - rc.right = m_sonarPos.x + radius; - rc.bottom = ps.rcPaint.bottom; - FillRect(ps.hdc, &rc, white); - - rc.left = ps.rcPaint.left; - rc.top = m_sonarPos.y - radius; - rc.right = ps.rcPaint.right; - rc.bottom = m_sonarPos.y + radius; - FillRect(ps.hdc, &rc, white); - - HBRUSH black = static_cast(GetStockObject(BLACK_BRUSH)); - - // Top left - rc.left = ps.rcPaint.left; - rc.top = ps.rcPaint.top; - rc.right = m_sonarPos.x - radius; - rc.bottom = m_sonarPos.y - radius; - FillRect(ps.hdc, &rc, black); - - // Top right - rc.left = m_sonarPos.x + radius; - rc.top = ps.rcPaint.top; - rc.right = ps.rcPaint.right; - rc.bottom = m_sonarPos.y - radius; - FillRect(ps.hdc, &rc, black); - - // Bottom left - rc.left = ps.rcPaint.left; - rc.top = m_sonarPos.y + radius; - rc.right = m_sonarPos.x - radius; - rc.bottom = ps.rcPaint.bottom; - FillRect(ps.hdc, &rc, black); - - // Bottom right - rc.left = m_sonarPos.x + radius; - rc.top = m_sonarPos.y + radius; - rc.right = ps.rcPaint.right; - rc.bottom = ps.rcPaint.bottom; - FillRect(ps.hdc, &rc, black); - - EndPaint(this->m_hwnd, &ps); - } -}; - #pragma endregion Super_Sonar_Base_Code #pragma region Super_Sonar_API @@ -1284,4 +1102,4 @@ HWND GetSonarHwnd() noexcept return nullptr; } -#pragma endregion Super_Sonar_API +#pragma endregion Super_Sonar_API \ No newline at end of file diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj index bfed4af15d..8e606b88ad 100644 --- a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj @@ -154,4 +154,4 @@ - \ No newline at end of file + From 651598582343175ddc4822418b3c6e1f048dfbc6 Mon Sep 17 00:00:00 2001 From: Ruben Fricke Date: Tue, 6 Jan 2026 01:43:16 +0100 Subject: [PATCH 4/5] CmdPal: Update Extension SDK docs link to Microsoft Learn (#44517) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary of the Pull Request Update CmdPal SDK docs links to point to Microsoft Learn as requested in #40994. ## PR Checklist - [x] Closes: #40994 - [ ] **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 - [x] **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 Updated links in: - src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml - src/modules/cmdpal/extensionsdk/README.md Note: The issue proposes linking directly to “Command Palette / Developer Docs / Extensibility overview” on Learn. I noticed the project often uses fwlink/aka.ms redirects instead of direct Learn URLs. If creating a redirect is preferred (or if I can create one), let me know and I’ll update the links accordingly. ## Validation Steps Performed Manually verified that the links resolve to the Microsoft Learn Command Palette extensibility overview. --------- Co-authored-by: Niels Laute --- .../cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml | 2 +- src/modules/cmdpal/extensionsdk/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml index 65fa536c5b..53994b345f 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml @@ -106,7 +106,7 @@ - + diff --git a/src/modules/cmdpal/extensionsdk/README.md b/src/modules/cmdpal/extensionsdk/README.md index c2e9e1fe59..540bec74bd 100644 --- a/src/modules/cmdpal/extensionsdk/README.md +++ b/src/modules/cmdpal/extensionsdk/README.md @@ -7,7 +7,7 @@ Palette, and use the "Create a new extension" command. That will set up a project for you, with the packaging, dependencies, and basic program structure ready to go. -To view the full docs, you can head over to [our docs site](https://go.microsoft.com/fwlink/?linkid=2310639) +To view the full docs, you can head over to [our docs site](https://aka.ms/cmdpalextensions-devdocs) There are samples of just about everything you can do in [the samples project]. Head over there to see basic usage of the APIs. From 335dcbaae83c8ffb136958a39deceadca35154aa Mon Sep 17 00:00:00 2001 From: Jaylyn Barbee <51131738+Jaylyn-Barbee@users.noreply.github.com> Date: Mon, 5 Jan 2026 20:54:37 -0500 Subject: [PATCH 5/5] [Light Switch] Adding telemetry events (#44241) Adding events to track the following:
Event Name Description Data collected
Microsoft.PowerToys.LightSwitch_EnableLightSwitch Triggered when Light Switch is enabled or disabled. Whether the module is enabled or disabled (bool)
Microsoft.PowerToys.LightSwitch_ShortcutInvoked Occurs when the shortcut for Light Switch is invoked.
Microsoft.PowerToys.LightSwitch_ScheduleModeToggled Occurs when a new schedule mode is selected for Light Switch. The new mode selected (string)
Microsoft.PowerToys.LightSwitch_ThemeTargetChanged Occurs when the options for targeting the system or apps is updated. The new options selected (two bools)
The above events that are related to Light Switch settings are tracked in the "LoadSettings" function inside the service but only if the value has changed. The Enabled event as well as the Shortcut event are tracked in the module interface. --- DATA_AND_PRIVACY.md | 24 +++++++++++ .../LightSwitchModuleInterface/dllmain.cpp | 6 ++- .../LightSwitchModuleInterface/trace.cpp | 15 +++++-- .../LightSwitchModuleInterface/trace.h | 3 +- .../LightSwitchService/LightSwitchService.cpp | 7 ++- .../LightSwitchService.vcxproj | 2 + .../LightSwitchService.vcxproj.filters | 6 +++ .../LightSwitchSettings.cpp | 12 ++++++ .../LightSwitch/LightSwitchService/trace.cpp | 43 +++++++++++++++++++ .../LightSwitch/LightSwitchService/trace.h | 17 ++++++++ 10 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 src/modules/LightSwitch/LightSwitchService/trace.cpp create mode 100644 src/modules/LightSwitch/LightSwitchService/trace.h diff --git a/DATA_AND_PRIVACY.md b/DATA_AND_PRIVACY.md index 66a0daa1d8..07089e44c3 100644 --- a/DATA_AND_PRIVACY.md +++ b/DATA_AND_PRIVACY.md @@ -694,6 +694,30 @@ _If you want to find diagnostic data events in the source code, these two links +### Light Switch + + + + + + + + + + + + + + + + + + + + + +
Event NameDescription
Microsoft.PowerToys.LightSwitch_EnableLightSwitchTriggered when Light Switch is enabled or disabled.
Microsoft.PowerToys.LightSwitch_ShortcutInvokedOccurs when the shortcut for Light Switch is invoked.
Microsoft.PowerToys.LightSwitch_ScheduleModeToggledOccurs when a new schedule mode is selected for Light Switch.
Microsoft.PowerToys.LightSwitch_ThemeTargetChangedOccurs when the options for targeting the system or apps is updated.
+ ### Mouse Highlighter diff --git a/src/modules/LightSwitch/LightSwitchModuleInterface/dllmain.cpp b/src/modules/LightSwitch/LightSwitchModuleInterface/dllmain.cpp index 11cfd412b0..bab4e30797 100644 --- a/src/modules/LightSwitch/LightSwitchModuleInterface/dllmain.cpp +++ b/src/modules/LightSwitch/LightSwitchModuleInterface/dllmain.cpp @@ -405,6 +405,7 @@ public: { m_enabled = true; Logger::info(L"Enabling Light Switch module..."); + Trace::Enable(true); unsigned long powertoys_pid = GetCurrentProcessId(); std::wstring args = L"--pid " + std::to_wstring(powertoys_pid); @@ -482,7 +483,8 @@ public: CloseHandle(m_process); m_process = nullptr; } - + + Trace::Enable(false); StopToggleListener(); } @@ -539,6 +541,8 @@ public: if (m_enabled) { Logger::trace(L"Light Switch hotkey pressed"); + Trace::ShortcutInvoked(); + if (!is_process_running()) { enable(); diff --git a/src/modules/LightSwitch/LightSwitchModuleInterface/trace.cpp b/src/modules/LightSwitch/LightSwitchModuleInterface/trace.cpp index 57fa1921f7..40fc67e679 100644 --- a/src/modules/LightSwitch/LightSwitchModuleInterface/trace.cpp +++ b/src/modules/LightSwitch/LightSwitchModuleInterface/trace.cpp @@ -19,12 +19,21 @@ void Trace::UnregisterProvider() TraceLoggingUnregister(g_hProvider); } -void Trace::MyEvent() +void Trace::Enable(bool enabled) noexcept { TraceLoggingWrite( g_hProvider, - "PowerToyName_MyEvent", + "LightSwitch_EnableLightSwitch", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), + TraceLoggingBoolean(enabled, "Enabled")); +} + +void Trace::ShortcutInvoked() noexcept +{ + TraceLoggingWrite( + g_hProvider, + "LightSwitch_ShortcutInvoked", ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), - TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); } diff --git a/src/modules/LightSwitch/LightSwitchModuleInterface/trace.h b/src/modules/LightSwitch/LightSwitchModuleInterface/trace.h index 55cdedb2ee..bfa32062b9 100644 --- a/src/modules/LightSwitch/LightSwitchModuleInterface/trace.h +++ b/src/modules/LightSwitch/LightSwitchModuleInterface/trace.h @@ -11,5 +11,6 @@ class Trace public: static void RegisterProvider(); static void UnregisterProvider(); - static void MyEvent(); + static void Enable(bool enabled) noexcept; + static void ShortcutInvoked() noexcept; }; diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.cpp b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.cpp index b6684da54e..8919f4274b 100644 --- a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.cpp +++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.cpp @@ -14,6 +14,7 @@ #include "LightSwitchStateManager.h" #include #include +#include SERVICE_STATUS g_ServiceStatus = {}; SERVICE_STATUS_HANDLE g_StatusHandle = nullptr; @@ -357,6 +358,8 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam) int APIENTRY wWinMain(HINSTANCE, HINSTANCE, PWSTR, int) { + Trace::LightSwitch::RegisterProvider(); + if (powertoys_gpo::getConfiguredLightSwitchEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled) { wchar_t msg[160]; @@ -364,12 +367,14 @@ int APIENTRY wWinMain(HINSTANCE, HINSTANCE, PWSTR, int) msg, L"Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator."); Logger::info(msg); + Trace::LightSwitch::UnregisterProvider(); return 0; } - int argc = 0; LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); int rc = _tmain(argc, argv); // reuse your existing logic LocalFree(argv); + + Trace::LightSwitch::UnregisterProvider(); return rc; } \ No newline at end of file diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj index e1c8052de6..b8e51ee489 100644 --- a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj +++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj @@ -80,6 +80,7 @@ + @@ -94,6 +95,7 @@ + diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj.filters b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj.filters index 55c7bde39b..a704e87073 100644 --- a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj.filters +++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj.filters @@ -39,6 +39,9 @@ Source Files + + Source Files + @@ -68,6 +71,9 @@ Header Files + + Header Files + diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.cpp b/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.cpp index 5221a197fe..488142b95b 100644 --- a/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.cpp +++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.cpp @@ -5,6 +5,7 @@ #include #include #include +#include using namespace std; @@ -151,6 +152,7 @@ void LightSwitchSettings::LoadSettings() if (m_settings.scheduleMode != newMode) { m_settings.scheduleMode = newMode; + Trace::LightSwitch::ScheduleModeToggled(val); NotifyObservers(SettingId::ScheduleMode); } } @@ -220,6 +222,8 @@ void LightSwitchSettings::LoadSettings() } } + bool themeTargetChanged = false; + // ChangeSystem if (const auto jsonVal = values.get_bool_value(L"changeSystem")) { @@ -227,6 +231,7 @@ void LightSwitchSettings::LoadSettings() if (m_settings.changeSystem != val) { m_settings.changeSystem = val; + themeTargetChanged = true; NotifyObservers(SettingId::ChangeSystem); } } @@ -238,9 +243,16 @@ void LightSwitchSettings::LoadSettings() if (m_settings.changeApps != val) { m_settings.changeApps = val; + themeTargetChanged = true; NotifyObservers(SettingId::ChangeApps); } } + + // For ChangeSystem/ChangeApps changes, log telemetry + if (themeTargetChanged) + { + Trace::LightSwitch::ThemeTargetChanged(m_settings.changeApps, m_settings.changeSystem); + } } catch (...) { diff --git a/src/modules/LightSwitch/LightSwitchService/trace.cpp b/src/modules/LightSwitch/LightSwitchService/trace.cpp new file mode 100644 index 0000000000..99afe7a95d --- /dev/null +++ b/src/modules/LightSwitch/LightSwitchService/trace.cpp @@ -0,0 +1,43 @@ +#include "pch.h" +#include "trace.h" + +// Telemetry strings should not be localized. +#define LoggingProviderKey "Microsoft.PowerToys" + +TRACELOGGING_DEFINE_PROVIDER( + g_hProvider, + LoggingProviderKey, + // {38e8889b-9731-53f5-e901-e8a7c1753074} + (0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74), + TraceLoggingOptionProjectTelemetry()); + +void Trace::LightSwitch::RegisterProvider() +{ + TraceLoggingRegister(g_hProvider); +} + +void Trace::LightSwitch::UnregisterProvider() +{ + TraceLoggingUnregister(g_hProvider); +} + +void Trace::LightSwitch::ScheduleModeToggled(const std::wstring& newMode) noexcept +{ + TraceLoggingWriteWrapper( + g_hProvider, + "LightSwitch_ScheduleModeToggled", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), + TraceLoggingWideString(newMode.c_str(), "NewMode")); +} + +void Trace::LightSwitch::ThemeTargetChanged(bool changeApps, bool changeSystem) noexcept +{ + TraceLoggingWriteWrapper( + g_hProvider, + "LightSwitch_ThemeTargetChanged", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), + TraceLoggingBoolean(changeApps, "ChangeApps"), + TraceLoggingBoolean(changeSystem, "ChangeSystem")); +} \ No newline at end of file diff --git a/src/modules/LightSwitch/LightSwitchService/trace.h b/src/modules/LightSwitch/LightSwitchService/trace.h new file mode 100644 index 0000000000..a06177075b --- /dev/null +++ b/src/modules/LightSwitch/LightSwitchService/trace.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +class Trace +{ +public: + class LightSwitch : public telemetry::TraceBase + { + public: + static void RegisterProvider(); + static void UnregisterProvider(); + static void ScheduleModeToggled(const std::wstring& newMode) noexcept; + static void ThemeTargetChanged(bool changeApps, bool changeSystem) noexcept; + }; +};