From 7a01d56179d5bf345a1498c3ff7e1c2a9eb802b2 Mon Sep 17 00:00:00 2001 From: Mario Hewardt Date: Mon, 17 Nov 2025 07:26:42 -0800 Subject: [PATCH 01/23] Updates version for standalone release (#43645) ## Summary of the Pull Request Updates the version for standalone release ## 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 --- src/modules/ZoomIt/ZoomIt/ZoomIt.rc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/ZoomIt/ZoomIt/ZoomIt.rc b/src/modules/ZoomIt/ZoomIt/ZoomIt.rc index 99bdb66b58..5f5e9d16cf 100644 --- a/src/modules/ZoomIt/ZoomIt/ZoomIt.rc +++ b/src/modules/ZoomIt/ZoomIt/ZoomIt.rc @@ -121,7 +121,7 @@ FONT 8, "MS Shell Dlg", 0, 0, 0x0 BEGIN DEFPUSHBUTTON "OK",IDOK,166,306,50,14 PUSHBUTTON "Cancel",IDCANCEL,223,306,50,14 - LTEXT "ZoomIt v9.20",IDC_VERSION,42,7,73,10 + LTEXT "ZoomIt v9.21",IDC_VERSION,42,7,73,10 LTEXT "Copyright © 2006-2025 Mark Russinovich",IDC_COPYRIGHT,42,17,231,8 CONTROL "Sysinternals - www.sysinternals.com",IDC_LINK, "SysLink",WS_TABSTOP,42,26,150,9 From b94593ef7315f5dbb64ef3f9e356e722532b9bca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pol=C3=A1=C5=A1ek?= Date: Tue, 18 Nov 2025 00:55:14 +0100 Subject: [PATCH 02/23] Settings: Add ScrollViewer to Command Palette page in PowerToys Settings (#43649) --- .../SettingsXAML/Views/CmdPalPage.xaml | 368 +++++++++--------- 1 file changed, 185 insertions(+), 183 deletions(-) diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/CmdPalPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/CmdPalPage.xaml index 4f60a4a2b8..a5cea1ef2c 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/CmdPalPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/CmdPalPage.xaml @@ -11,199 +11,201 @@ AutomationProperties.LandmarkType="Main" mc:Ignorable="d"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + Margin="0,-12,0,24" + ColumnSpacing="32" + RowSpacing="8"> + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + - + x:Uid="CmdPal_Launch" + Grid.Row="3" + ActionIcon="{ui:FontIcon Glyph=}" + Click="LaunchCard_Click" + Header="Launch Command Palette" + HeaderIcon="{ui:FontIcon Glyph=}" + IsClickEnabled="True" + IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}"> + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + + From b50df36b703981af94258d28cc9e0d53b851af80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pol=C3=A1=C5=A1ek?= Date: Tue, 18 Nov 2025 03:00:37 +0100 Subject: [PATCH 03/23] Setup: Hide apps in PowerToys.SpareApps package from Start Menu (#43650) ## Summary of the Pull Request This PR updates the Appx manifest for PowerToys.SpareApps to hide the apps from the Start Menu, as they lack proper visual elements like icons and text. ## PR Checklist - [x] Closes: #43647 - [ ] **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 --- src/PackageIdentity/AppxManifest.xml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/PackageIdentity/AppxManifest.xml b/src/PackageIdentity/AppxManifest.xml index 2e9d52a2fa..822daae8bc 100644 --- a/src/PackageIdentity/AppxManifest.xml +++ b/src/PackageIdentity/AppxManifest.xml @@ -42,7 +42,8 @@ Description="PowerToys OCR Module" BackgroundColor="transparent" Square150x150Logo="Images\Square150x150Logo.png" - Square44x44Logo="Images\Square44x44Logo.png"> + Square44x44Logo="Images\Square44x44Logo.png" + AppListEntry="none"> @@ -51,7 +52,8 @@ Description="PowerToys Settings UI" BackgroundColor="transparent" Square150x150Logo="Images\Square150x150Logo.png" - Square44x44Logo="Images\Square44x44Logo.png"> + Square44x44Logo="Images\Square44x44Logo.png" + AppListEntry="none"> @@ -60,7 +62,8 @@ Description="PowerToys Image Resizer UI" BackgroundColor="transparent" Square150x150Logo="Images\Square150x150Logo.png" - Square44x44Logo="Images\Square44x44Logo.png"> + Square44x44Logo="Images\Square44x44Logo.png" + AppListEntry="none"> From 840808b465a21abb772bcbec9036ca0fb1585c9a Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Tue, 18 Nov 2025 03:28:40 +0100 Subject: [PATCH 04/23] [AP] Adding a single scrollviewer and fixing hidden tabstop (#43660) ## Summary of the Pull Request ## PR Checklist - [x] Closes: #43655 - [ ] **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 --- .../AdvancedPasteXAML/Pages/MainPage.xaml | 84 ++++++++++--------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Pages/MainPage.xaml b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Pages/MainPage.xaml index ece208c884..bddfab733d 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Pages/MainPage.xaml +++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Pages/MainPage.xaml @@ -299,47 +299,49 @@ - - - - - - - - - - + + + + + + + + + + + - - + + + From 0b50c38fe1a1aa58e7d1fee87a86aaaf3aa7e361 Mon Sep 17 00:00:00 2001 From: Kai Tao <69313318+vanzue@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:59:52 +0800 Subject: [PATCH 05/23] Advanced Paste: Refresh environment if foundry is not present (#43662) ## Summary of the Pull Request As title ## 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 [12:55:29.6763496] [Info] FoundryClient.cs::CreateAsync::23 [FoundryClient] First attempt failed, refreshing PATH and retrying [12:55:29.6766491] [Info] FoundryClient.cs::RefreshEnvironmentPath::225 [FoundryClient] Refreshing PATH environment variable from system [12:55:29.6768710] [Info] FoundryClient.cs::RefreshEnvironmentPath::266 [FoundryClient] Updating process PATH with latest system values [12:55:29.6769080] [Info] FoundryClient.cs::TryCreateClientAsync::33 [FoundryClient] Creating Foundry Local client [12:55:29.6769312] [Info] FoundryClient.cs::TryCreateClientAsync::45 [FoundryClient] Starting Foundry service using manager.StartServiceAsync() [12:55:29.9807668] [Info] FoundryClient.cs::TryCreateClientAsync::48 [FoundryClient] Foundry service started successfully Verified, fist launch successfully --- .../FoundryLocal/FoundryClient.cs | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/src/common/LanguageModelProvider/FoundryLocal/FoundryClient.cs b/src/common/LanguageModelProvider/FoundryLocal/FoundryClient.cs index 287588a71e..a279f7389a 100644 --- a/src/common/LanguageModelProvider/FoundryLocal/FoundryClient.cs +++ b/src/common/LanguageModelProvider/FoundryLocal/FoundryClient.cs @@ -10,6 +10,23 @@ namespace LanguageModelProvider.FoundryLocal; internal sealed class FoundryClient { public static async Task CreateAsync() + { + // First attempt with current environment + var client = await TryCreateClientAsync().ConfigureAwait(false); + if (client != null) + { + return client; + } + + // If failed, refresh PATH from registry and retry once + // This handles cases where PowerToys was launched by MSI installer. + Logger.LogInfo("[FoundryClient] First attempt failed, refreshing PATH and retrying"); + RefreshEnvironmentPath(); + + return await TryCreateClientAsync().ConfigureAwait(false); + } + + private static async Task TryCreateClientAsync() { try { @@ -195,4 +212,68 @@ internal sealed class FoundryClient await _foundryManager.StartServiceAsync(); } } + + /// + /// Refreshes the PATH environment variable from the system registry. + /// This is necessary when tools are installed while PowerToys is running, + /// as the installer updates the system PATH but running processes don't see the change. + /// + private static void RefreshEnvironmentPath() + { + try + { + Logger.LogInfo("[FoundryClient] Refreshing PATH environment variable from system"); + + var currentPath = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Process) ?? string.Empty; + var machinePath = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Machine) ?? string.Empty; + var userPath = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.User) ?? string.Empty; + + var pathsToAdd = new List(); + + if (!string.IsNullOrWhiteSpace(currentPath)) + { + pathsToAdd.AddRange(currentPath.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries)); + } + + if (!string.IsNullOrWhiteSpace(userPath)) + { + var userPaths = userPath.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries); + foreach (var path in userPaths) + { + if (!pathsToAdd.Contains(path, StringComparer.OrdinalIgnoreCase)) + { + pathsToAdd.Add(path); + } + } + } + + if (!string.IsNullOrWhiteSpace(machinePath)) + { + var machinePaths = machinePath.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries); + foreach (var path in machinePaths) + { + if (!pathsToAdd.Contains(path, StringComparer.OrdinalIgnoreCase)) + { + pathsToAdd.Add(path); + } + } + } + + var newPath = string.Join(Path.PathSeparator.ToString(), pathsToAdd); + + if (currentPath != newPath) + { + Logger.LogInfo("[FoundryClient] Updating process PATH with latest system values"); + Environment.SetEnvironmentVariable("PATH", newPath, EnvironmentVariableTarget.Process); + } + else + { + Logger.LogInfo("[FoundryClient] PATH is already up to date"); + } + } + catch (Exception ex) + { + Logger.LogError($"[FoundryClient] Failed to refresh PATH: {ex.Message}"); + } + } } From 2593149d22b53fdd7e8a54f5057eaf90ed9c7585 Mon Sep 17 00:00:00 2001 From: leileizhang Date: Tue, 18 Nov 2025 14:22:03 +0800 Subject: [PATCH 06/23] Fix OOBE Mouse Utilities crash by correcting localization key (#43664) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary of the Pull Request - The OOBE Mouse Utilities page crashed when selected because the TextBlock with x:Uid="Oobe_MouseUtils_MousePointerCrosshairs" tried to bind a Description property that doesn’t exist. - Updated Resources.resw so the string entry is named Oobe_MouseUtils_MousePointerCrosshairs_Description.Text, matching the markdown description control instead of the TextBlock. - With the correct resource key, the XAML loader no longer resolves an invalid property and navigation succeeds. ## PR Checklist - [x] Closes: #43663 - [ ] **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 --- src/settings-ui/Settings.UI/Strings/en-us/Resources.resw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw index e50cd477c9..388a713890 100644 --- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw @@ -2703,7 +2703,7 @@ From there, simply click on one of the supported files in the File Explorer and Mouse Pointer Crosshairs Mouse as in the hardware peripheral. - + Draw crosshairs centered around the mouse pointer. Mouse as in the hardware peripheral. From 417c1a6b988505b92362af65c8cffa03678d9905 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Tue, 18 Nov 2025 16:21:14 -0800 Subject: [PATCH 07/23] Update FoundryLocal.svg (#43682) ## 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 --- .../Settings/Icons/Models/FoundryLocal.svg | 81 +++++++------------ 1 file changed, 28 insertions(+), 53 deletions(-) diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Icons/Models/FoundryLocal.svg b/src/settings-ui/Settings.UI/Assets/Settings/Icons/Models/FoundryLocal.svg index 7066f294f9..53747d557d 100644 --- a/src/settings-ui/Settings.UI/Assets/Settings/Icons/Models/FoundryLocal.svg +++ b/src/settings-ui/Settings.UI/Assets/Settings/Icons/Models/FoundryLocal.svg @@ -1,59 +1,34 @@ - - - - - - - - - - - - - - - - + + + + + + + - - - - - - - - - - - - - + + + - - - - - - - - - - + + + - - - - - - - - - - - + + + + + + + + + + + + + + + - - - From 5a8095b70454cd092527c350289c7659e305ea6c Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Tue, 18 Nov 2025 18:32:14 -0800 Subject: [PATCH 08/23] Loc bug (#43685) ## 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 --- .../Controls/ModelPicker/FoundryLocalModelPicker.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ModelPicker/FoundryLocalModelPicker.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/ModelPicker/FoundryLocalModelPicker.xaml index 9574094e77..adc802eba9 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ModelPicker/FoundryLocalModelPicker.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/ModelPicker/FoundryLocalModelPicker.xaml @@ -87,12 +87,12 @@ FontSize="24" Glyph="" /> Date: Tue, 18 Nov 2025 19:19:38 -0800 Subject: [PATCH 09/23] Logo change for Azure Inference (#43686) ## 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 --- src/settings-ui/Settings.UI.Library/AIServiceTypeRegistry.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings-ui/Settings.UI.Library/AIServiceTypeRegistry.cs b/src/settings-ui/Settings.UI.Library/AIServiceTypeRegistry.cs index c967f5d840..653b85553e 100644 --- a/src/settings-ui/Settings.UI.Library/AIServiceTypeRegistry.cs +++ b/src/settings-ui/Settings.UI.Library/AIServiceTypeRegistry.cs @@ -19,7 +19,7 @@ public static class AIServiceTypeRegistry { ServiceType = AIServiceType.AzureAIInference, DisplayName = "Azure AI Inference", - IconPath = "ms-appx:///Assets/Settings/Icons/Models/FoundryLocal.svg", // No icon for Azure AI Inference, use Foundry Local temporarily + IconPath = "ms-appx:///Assets/Settings/Icons/Models/Azure.svg", IsOnlineService = true, LegalDescription = "AdvancedPaste_AzureAIInference_LegalDescription", TermsLabel = "AdvancedPaste_AzureAIInference_TermsLabel", From 46242b384e000f6b63e77dbbf4a0db7fe21c5c19 Mon Sep 17 00:00:00 2001 From: Kai Tao <69313318+vanzue@users.noreply.github.com> Date: Wed, 19 Nov 2025 16:25:52 +0800 Subject: [PATCH 10/23] 96 release change log (#43330) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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 --------- Signed-off-by: Shawn Yuan (from Dev Box) Signed-off-by: Shawn Yuan Co-authored-by: Jiří Polášek Co-authored-by: Jaylyn Barbee <51131738+Jaylyn-Barbee@users.noreply.github.com> Co-authored-by: Jeremy Sinclair <4016293+snickler@users.noreply.github.com> Co-authored-by: leileizhang Co-authored-by: Shawn Yuan <128874481+shuaiyuanxx@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: niels9001 <9866362+niels9001@users.noreply.github.com> Co-authored-by: Niels Laute Co-authored-by: Dave Rayment Co-authored-by: Gleb Khmyznikov Co-authored-by: Gordon Lam (SH) Co-authored-by: Juju Anselum J <106316316+anselumjuju@users.noreply.github.com> Co-authored-by: Dustin L. Howett Co-authored-by: Leon Zandman Co-authored-by: Leon Zandman Co-authored-by: moooyo <42196638+moooyo@users.noreply.github.com> Co-authored-by: Yu Leng Co-authored-by: Mike Griese Co-authored-by: Michael Jolley Co-authored-by: Mario Hewardt Co-authored-by: Alex Mihaiuc <69110671+foxmsft@users.noreply.github.com> Co-authored-by: Mike Hall Co-authored-by: Trevor --- README.md | 240 ++++++++++++++++++++++++------------------------------ 1 file changed, 108 insertions(+), 132 deletions(-) diff --git a/README.md b/README.md index ab53e53a46..624d95501b 100644 --- a/README.md +++ b/README.md @@ -51,19 +51,20 @@ But to get started quickly, choose one of the installation methods below: Go to the [PowerToys GitHub releases][github-release-link], click Assets to reveal the downloads, and choose the installer that matches your architecture and install scope. For most devices, that's the x64 per-user installer. -[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.96%22 -[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.95%22 -[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.1/PowerToysUserSetup-0.95.1-x64.exe -[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.1/PowerToysUserSetup-0.95.1-arm64.exe -[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.1/PowerToysSetup-0.95.1-x64.exe -[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.95.1/PowerToysSetup-0.95.1-arm64.exe +[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.97%22 +[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aissue+milestone%3A%22PowerToys+0.96%22 +[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.96.0/PowerToysUserSetup-0.96.0-x64.exe +[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.96.0/PowerToysUserSetup-0.96.0-arm64.exe +[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.96.0/PowerToysSetup-0.96.0-x64.exe +[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.96.0/PowerToysSetup-0.96.0-arm64.exe | Description | Filename | |----------------|----------| -| Per user - x64 | [PowerToysUserSetup-0.95.1-x64.exe][ptUserX64] | -| Per user - ARM64 | [PowerToysUserSetup-0.95.1-arm64.exe][ptUserArm64] | -| Machine wide - x64 | [PowerToysSetup-0.95.1-x64.exe][ptMachineX64] | -| Machine wide - ARM64 | [PowerToysSetup-0.95.1-arm64.exe][ptMachineArm64] | +| Per user - x64 | [PowerToysUserSetup-0.96.0-x64.exe][ptUserX64] | +| Per user - ARM64 | [PowerToysUserSetup-0.96.0-arm64.exe][ptUserArm64] | +| Machine wide - x64 | [PowerToysSetup-0.96.0-x64.exe][ptMachineX64] | +| Machine wide - ARM64 | [PowerToysSetup-0.96.0-arm64.exe][ptMachineArm64] | +
@@ -102,156 +103,131 @@ There are [community driven install methods](./doc/unofficialInstallMethods.md)
## ✨ What's new -**Version 0.95 (October 2025)** +**Version 0.96 (November 2025)** For an in-depth look at the latest changes, visit the [Windows Command Line blog](https://aka.ms/powertoys-releaseblog). **✨ Highlights** - - **NEW:** The **Light Switch** utility in PowerToys allows you to automatically switch between light and dark themes in Windows based on the time of day. - - Command Palette delivered major search performance gains (new fuzzy matcher and smarter fallbacks) improving relevance and speed. - - Peek can now be activated using just the Spacebar! - - Find My Mouse added transparent spotlight with independent backdrop opacity, boosting focus and accessibility. - - Settings now lets you delete shortcuts entirely and ignore conflicts. - - Mouse Pointer Crosshairs gained orientation options (vertical / horizontal / both) for customizable accessibility. Thanks [@mikehall-ms](https://github.com/mikehall-ms)! - - PowerRename fixed enumeration counter skipping ensuring reliable batch renames. Thanks [@daverayment](https://github.com/daverayment)! - - ZoomIt restored legacy draw and snipping behaviors, and fixed recording issues, improving reliability. Thanks [@chakrik73](https://github.com/chakrik73)! + - Advanced Paste now supports multiple online and on-device AI model providers: Azure OpenAI, OpenAI, Google Gemini, Mistral, Foundry Local and Ollama. + - Command Palette received extensive improvements including file search filters, better clipboard history metadata, context-menu styling, and dozens of bug fixes and enhancements. + - PowerRename can now extract and use photo metadata (EXIF, XMP) in renaming patterns like `%Camera`, `%Lens`, and `%ExposureTime`. + +### Advanced Paste + - Advanced Paste now lets you connect to multiple AI providers instead of being limited to a single OpenAI provider. See [Advanced Paste documentation](https://learn.microsoft.com/windows/powertoys/advanced-paste) for usage. + +### Awake + - The Awake countdown timer now stays accurate over long periods. Thanks [@daverayment](https://github.com/daverayment)! + - Fixed Awake context menu positioning. The fix removed the conversion of the mouse cursor from screen to client-window coordinates, instead using the raw screen coordinates returned by GetCursorPos; the context menu now appears at the correct screen position. Thanks [@lzandman](https://github.com/lzandman)! ### Command Palette - - Applied conditional margin for icon-only tags to tighten layout. Thanks [@samrueby](https://github.com/samrueby) - - Improved the reliability of accessing Command Palette settings through PowerToys Settings and executing other x-cmdpal:// protocol commands. Thanks [@jiripolasek](https://github.com/jiripolasek) - - Enabled AOT by default for improved performance while simplifying publish configs. - - Replaced service state color dots with play/pause/stop icons for enhanced accessibility. Thanks [@samrueby](https://github.com/samrueby) - - Fixed filter dropdown sync and crash by binding SelectedValue and raising UI-thread notifications. Thanks [@jiripolasek](https://github.com/jiripolasek) - - Ensured long links wrap correctly in details view. - - Removed animation and enforced minimum width on filter dropdown for clarity. Thanks [@jiripolasek](https://github.com/jiripolasek) - - Restored focus to More button after ESC closes context menu, improving keyboard flow. Thanks [@chatasweetie](https://github.com/chatasweetie) - - Marked main and toast windows as tool windows to keep them out of Alt+Tab while preserving style. Thanks [@jiripolasek](https://github.com/jiripolasek) - - Fixed AOT template and theming issues for filter separators. Thanks [@jiripolasek](https://github.com/jiripolasek) - - Introduced grid layouts (small, medium, gallery) for richer page presentation. - - Materialized result lists to avoid rescoring overhead. - - Disabled problematic selection TextToSuggest behind environment flag. - - Major search performance improvements (new fuzzy matcher, smarter fallbacks, fewer exceptions). - - Added context menu "Show Details" command when details pane is hidden. - - Reduced window flicker by avoiding unnecessary cloaking. Thanks [@jiripolasek](https://github.com/jiripolasek) - - Restored EmptyContent rendering for blank states. Thanks [@DevLGuilherme](https://github.com/DevLGuilherme) - - Saved new state even if prior app state file was corrupt (better resilience). Thanks [@jiripolasek](https://github.com/jiripolasek) - - Migrated settings window to WinUI TitleBar control. Thanks [@jiripolasek](https://github.com/jiripolasek) - - Prevented crash on duplicate keybindings and simplified matching. Thanks [@jiripolasek](https://github.com/jiripolasek) - - Hotkeys now always respect the “Ignore shortcut in fullscreen†setting. Thanks [@jiripolasek](https://github.com/jiripolasek) - - Hid search box on content pages, improving focus and accessibility, and added Home title. Thanks [@jiripolasek](https://github.com/jiripolasek) - - Blocked Ctrl+I from inserting stray tabs in search box. - - Logged HRESULT codes in error logs for deeper diagnostics. Thanks [@jiripolasek](https://github.com/jiripolasek) - - Advanced font and emoji icon classification and alignment improvements. Thanks [@jiripolasek](https://github.com/jiripolasek) - - Ensured that fallback command icons are visible on the extension settings page. Thanks [@jiripolasek](https://github.com/jiripolasek) - - Fixed breadcrumb margin misalignment (visual polish). Thanks [@jiripolasek](https://github.com/jiripolasek) - - Truncated overly long command labels with ellipsis to prevent overflow. - - Added a setting to configure the page transition animation. - - Collection of small improvements and nits for Run Commands. - - Improved bookmarks performance and experience. Thanks [@jiripolasek](https://github.com/jiripolasek) - - Added Ctrl+O shortcut in Clipboard History to open links directly. - - Resolved conflict with external software that blocked Command Palette from hiding. - - Updated context menu items to reflect name and icon changes, and ensured application icons are displayed correctly. Thanks [@jiripolasek](https://github.com/jiripolasek) - - Added Alt+Home shortcut to return immediately to the Command Palette home page. Thanks [@jiripolasek](https://github.com/jiripolasek) - - Fixed a crash when displaying code blocks in markdown on detail or content pages. Thanks [@jiripolasek](https://github.com/jiripolasek) - - Fixed an issue where the search bar icon and title were not updated when rapidly switching pages. Thanks [@jiripolasek](https://github.com/jiripolasek) - - Improved the appearance of the search box in the context menu. - + - The search field in context menus now matches the look of the Command Palette, with a smoke backdrop and improved padding. + - Fallback items such as math calculations or the Run command now appear in results more quickly. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Ensured the command bar updates correctly after navigating to another page and commands are displayed correctly. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - The Command Palette settings page has been reorganized. Activation-key options are grouped under an expander and extension settings are framed for improved readability. + - When you modify a command, its alias, hotkey, and tags now update in the top-level list, keeping the displayed information in sync. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Press `Ctrl + ,` to open Command Palette settings from anywhere. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - You can use `Page Up` and `Page Down` to navigate the list while focus is in the search box. Thanks [@samrueby](https://github.com/samrueby)! + - Fixed an issue where the search box could disappear when navigating pages. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Ensured search text is selected when *Go home when activated* and *Highlight search on activate* are both enabled. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Fixed an issue where Command Palette window occasionally appeared on the taskbar under certain Windows settings. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Ensured that labels and icons of list items and menu items update when they change. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Fixed visibility of list filters when navigating to a content page. Thanks [@DevLGuilherme](https://github.com/DevLGuilherme)! + - Added search to the extension list and a link to extensions on the Microsoft Store. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Added options to open the Command Palette window at its last position or re-center it. + - The Command Palette now remembers its window size after restarting. + - Added a global error handler that logs fatal errors and provides feedback when unexpected failures force Command Palette to close. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Fixed forms and extension settings not showing on some machines due to a missing VC++ runtime. + - Restored ranking of fallback commands for built-in extensions (Sleep, Shutdown, Windows settings, Web search, etc.). Thanks [@jiripolasek](https://github.com/jiripolasek). + - Improved and unified labels and texts across the application! + - Maintainance: Resolved numerous build warnings in Command Palette projects; no user-visible impact. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Maintainance: Fixed a logging issue so exception messages are properly recorded instead of placeholder text, improving troubleshooting. Thanks [@jiripolasek](https://github.com/jiripolasek)! ### Command Palette Extensions - - Replaced localized WebSearch setting keys with stable literals and numeric history count. Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Enabled advanced markdown tables and emphasis extensions. Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Added setting to choose Clipboard History primary action (Paste vs Copy). Thanks [@jiripolasek](https://github.com/jiripolasek) - - Added actionable empty-state hints for File Search (search PC / open indexing settings). Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Ensured all WinGet extension assets copy reliably to output. Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Improved Run command line parsing for paths with spaces; sped up related tests. - - Updated WebSearch extension icon set for enhanced clarity and contrast. Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Added Terminal profile sort order setting including MRU tracking. Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Added Uninstall Application command (UWP direct, Win32 via Settings). Thanks [@mKpwnz](https://github.com/mKpwnz)! - - Deferred WinGet details loading and added timing logs. - - Removed LINQ from All Apps extension for performance. - - Added standardized key chord system + shortcuts to File Search commands. Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Added Terminal channel filter & remembered selection option. Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Enabled loading local/data/app images in markdown with sizing hints. Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Added external extension reload via x-cmdpal://reload (configurable). Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Instant WebSearch history updates with in-memory store & events. Thanks [@jiripolasek](https://github.com/jiripolasek)! - - Added keep-after-paste option and safe delete with confirmation for Clipboard History. Thanks [@jiripolasek](https://github.com/jiripolasek)! - -### Environment Variables - - Replaced custom window chrome with WinUI TitleBar for cleaner, maintainable Environment Variables UI. - -### File Locksmith - - Adopted WinUI TitleBar to simplify window chrome while preserving appearance. + - Bookmarks: Added hints about bookmark placeholders to the Add/Edit Bookmark form. — Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Bookmarks: Improved migration of bookmarks from older versions and fixed an issue where aliases or keyboard shortcuts could be lost after restart. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Clipboard history: Items shown in Command Palette’s clipboard history now include helpful metadata. For example, image items show dimensions, text files show names and sizes, web links include page titles, and text entries display word counts. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - File search: Added filter buttons to show *all items*, *files only*, or *folders only*. Selecting a filter adds `kind:folders` or `kind:not folders` to narrow results. + - System commands: Replaced the `:red_circle:` placeholder with an actual red-circle emoji so the correct icon appears in the UI. Thanks [@samrueby](https://github.com/samrueby)! + - WinGet: Search performance feels more responsive because typed input is now processed via a task queue rather than complex cancellation tokens! + - Window Walker: UWP apps no longer show a "not responding" label when suspended. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Window Walker: Now displays the actual icon of each window rather than using the process icon, improving recognition of PWAs and Python GUIs. Thanks [@Lee-WonJun](https://github.com/Lee-WonJun)! +- Windows Terminal profiles: Fixed a rare crash in the Windows Terminal extension when the `LOCALAPPDATA` environment variable was missing. The path is now retrieved via a reliable API. Thanks [@jiripolasek](https://github.com/jiripolasek)! ### Find My Mouse - - Added transparent spotlight support with separate backdrop opacity; migrated to Windows App SDK composition APIs. + - Activating Find My Mouse no longer makes the cursor change to the busy (hourglass) icon or steals focus from your active application. ### Hosts File Editor - - Migrated to native WinUI TitleBar for cleaner, maintainable window chrome. + - Added customizable backup settings allowing users to configure backup frequency, location, and auto-deletion policies. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + +### Image Resizer + - Fixed settings consistency during batch resize operations by capturing settings once before processing. Thanks [@daverayment](https://github.com/daverayment)! ### Light Switch - - Introduced as a brand-new PowerToy module. - - Automatically switches between light and dark themes. - - Supports time-based scheduling or location-based sunrise/sunset switching. - - Supports using a keyboard shortcut to force a change. - - Supports filtering changes for Apps and/or System Theme. +- Introduced new UI to allow users to manually enter their latitude and longitude in Sunrise to Sunset mode. +- Refactored service with cleaner state management for stability. +- Removed logs from every tick, only logging key events to largely reduce log size. ### Mouse Pointer Crosshairs - - Added Esc key to cancel active gliding cursor sequence. Thanks [@mikehall-ms](https://github.com/mikehall-ms)! - - Added orientation option (vertical / horizontal / both) for crosshairs customization. Thanks [@mikehall-ms](https://github.com/mikehall-ms)! + - Enabled switching between Mouse Pointer Crosshairs and Gliding Cursor modes. Thanks [@mikehall-ms](https://github.com/mikehall-ms)! ### Mouse Without Borders - - Continued Common class refactor (part 5/7) by extracting clipboard and init/cleanup logic into focused classes. Thanks [@mikeclayton](https://github.com/mikeclayton)! - - - Fix connection failures caused by conflicting MachineId across machines. Thanks [@noraa-junker](https://github.com/noraa-junker) for troubleshooting! + - Added horizontal scrolling support. Thanks [@MasonBergstrom](https://github.com/MasonBergstrom)! ### Peek - - Added the option to activate Peek with just the Spacebar. +- Fixed media files remaining locked after preview window closes. Thanks [@daverayment](https://github.com/daverayment)! +- Added a command-line interface for file previewing. See the [Peek documentation](https://learn.microsoft.com/windows/powertoys/peek) for usage. Thanks [@prochan2](https://github.com/prochan2)! ### PowerRename - - Fixed enumeration counter skipping when regex replacement equals original filename (counters now advance reliably). Thanks [@daverayment](https://github.com/daverayment)! +- PowerRename no longer crashes due to a missing resources file. +- Added photo metadata extraction support using EXIF and XMP for pattern-based renaming with camera info, GPS coordinates, and date taken. See [PowerRename Documentation](https://learn.microsoft.com/en-us/windows/powertoys/powerrename). -### Quick Accent - - Expanded Welsh layout with acute, grave, and dieresis variants for vowels (consistent ordering). Thanks [@PesBandi](https://github.com/PesBandi)! +### PowerToys Run + - Added retry logic with exponential backoff to handle DWM composition errors during theme changes. Thanks [@jiripolasek](https://github.com/jiripolasek)! + - Updated OneNote icons to reflect new Microsoft 365 design. Thanks [@trevorNgo](https://github.com/trevorNgo)! -### Registry Preview - - Migrated to native TitleBar and AppWindow APIs for cleaner window chrome. + ### Quick Accent + - Added diameter symbol (⌀) for Shift+O in Special Characters mode, thanks to [@anselumjuju](https://github.com/anselumjuju)! -### Screen Ruler - - Fixed ARM64 crash by aligning cursor position structure to 8-byte boundary. +### Zoomit + - Smoothed out zoom-animation in ZoomIt by coalescing mouse-move and timer events, thanks to [@foxmsft](https://github.com/foxmsft)! + - Enabled GIF support for ZoomIt, thanks to [@MarioHewardt](https://github.com/MarioHewardt)! + - Fixed spelling mistakes, and refactored some literal strings to string constants, thanks to [@lzandman](https://github.com/lzandman)! + - Fixed inaccurate "actual size" screenshots in ZoomIt and resolves a GDI handle leak, improving capture fidelity and long-session stability. thanks to [@daverayment](https://github.com/daverayment)! ### Settings - - Added ability to ignore specific hotkey conflicts to reduce noise. - - Stopped creating backup directory during dry-run status checks (cleaner first-run). - - Standardized casing and localization for ZoomIt and modules header. - - Improved search results page accessibility and conditional module grouping. +- Fixed title bar overlapping issue at smaller window sizes. +- Refined shortcut control visual design with improved consistency and spacing. +- Added dashboard utilities sorting by name or status. +- Made update notification InfoBar in flyout clickable for direct navigation to update page. +- Expanded installation instructions by default in README. +- Improved accessibility for shortcut conflict button with static resource-based automation properties. +- Added ScrollViewer to Command Palette page in PowerToys Settings. Thanks [@jiripolasek](https://github.com/jiripolasek)! +- Fixed module list glitches and Sort Status checkmark issue. Thanks [@daverayment](https://github.com/daverayment)! -### ZoomIt - - Updated resource file to reflect standalone v9.01 and current copyright year. Thanks [@foxmsft](https://github.com/foxmsft)! - - Restored legacy draw/snipping behaviors and fixed recording race conditions. Thanks [@chakrik73](https://github.com/chakrik73)! - - Added smooth image option for improved zoom quality using GDI+ for static zoom and Magnifier API for live zoom. Thanks [@markrussinovich](https://github.com/markrussinovich)! - - ### Documentation - - New Microsoft Learn documentation for the Light Switch module. - - New dev docs for the Light Switch module. - -### Development (Area-Build & Area-Tests) -- Allowed debug launches to continue when modules fail to load, speeding developer iteration. -- Fixed spell checker dictionary entry (advapi) to eliminate false error. -- Added VS Code development guide and launch configs to streamline cross-editor workflows. -- Upgraded Windows App SDK and related dependencies to 1.8 for newer platform features. -- Rewrote YAML comment to resolve new spell checker forbidden pattern. Thanks [@jiripolasek](https://github.com/jiripolasek)! -- Corrected solution structure by returning misplaced Common project, reducing build confusion. -- Modernized build scripts with shared helpers and VS environment autodetection for simpler CLI builds. -- Standardized build scripts and platform detection to improve reliability and reuse. -- Added missing Command Palette version bump to align module release cadence. -- Added EXECUTEDEFAULT term to dictionary to prevent regression build failures. Thanks [@jiripolasek](https://github.com/jiripolasek)! -- Introduced nightly pre-warm pipeline and configurable MSBuild cache mode to improve CI performance. -- Resolved CI forbidden pattern spelling complaint to keep pipelines green. -- Added AI contributor instruction set to clarify code area expectations. -- Added accessibility IDs to settings and FancyZones toggles, stabilizing UI tests. -- Added automatic log collection on UI test failures to speed root cause analysis. -- Stabilized Mouse Utils tests by switching to AccessibilityId selectors. -- Added Screen Ruler UI test coverage to validate core measurement workflows. +### Development +- Fixed accessibility by associating controls with labels for screen readers. +- Added accessible name to Shortcut Conflicts button for screen readers. +- Excluded TitleBars from tab navigation across multiple utilities. Thanks [@jiripolasek](https://github.com/jiripolasek)! +- Migrated build infrastructure from Windows Server 2019 to Server 2022 with improved failure logging and predictable NuGet package paths. +- Configured build agents to use larger P: drive for release builds to address disk space constraints. +- Enhanced DSC v3 support by organizing resource manifests in a dedicated subfolder with PATH configuration. +- Reduced installer bundle size by 6-7MB through centralized Hybrid CRT configuration across all C++ projects. +- Updated .NET packages to version 9.0.10 for security fixes. Thanks [@snickler](https://github.com/snickler)! +- Fixed spell check dictionary entries for consistency. +- Restored accidentally deleted NuGet configuration file for Command Palette extensions. +- Fixed package identity build by updating AppxManifest entry points to use PowerShell Core. +- Optimized CI pipeline by replacing file copy operations with hard links and moves, reducing build time and disk usage by 10-15GB. +- Updated Copilot guidance and PR prompt workflow. +- Included high-volume bugs in issue template header. Thanks [@daverayment](https://github.com/daverayment)! +- Fixed incorrect HRESULT logging for inner exceptions. Thanks [@jiripolasek](https://github.com/jiripolasek)! +- Introduced shared sparse package identity for PowerToys Win32 components to enable access to Windows platform APIs. +- Consolidated installer builds to produce both machine and user installers simultaneously, reducing build time and complexity. +- Migrated exclusively to WiX v5 installer infrastructure, removing legacy WiX v3 support. +- Temporarily removed PowerToys installer path from PATH environment variable to prevent application crashes. +- Added complete OCR UI test coverage with automated tests for activation, settings, language selection, and text extraction. +- Fixed test input for drive path normalization in bookmark resolver unit tests. +- Fixed Peek UI tests by restoring Ctrl+Space activation shortcut for test scenarios. +- Hided apps in PowerToys.SpareApps package from Start Menu. Thanks [@jiripolasek](https://github.com/jiripolasek)! ## ðŸ›£ï¸ Roadmap We are planning some nice new features and improvements for the next releases – a revamped Keyboard Manager UI, custom endpoint and local model support for Advanced Paste, Command Palette improvements and a brand-new Shortcut Guide experience! Stay tuned for [v0.96][github-next-release-work]! From 97d46efec2e2d9f0c6d5b1e083463c2e6d193888 Mon Sep 17 00:00:00 2001 From: Dave Rayment Date: Wed, 19 Nov 2025 08:49:40 +0000 Subject: [PATCH 11/23] [Settings] Fix Dashboard toggle glitches and sorting UI (#43626) ## Summary of the Pull Request Fixes two UI bugs in the Settings Dashboard: module list glitching when toggling modules, and incorrect sort menu checkmarks. ## PR Checklist - [x] Closes: #43624 - [x] Closes: #43625 - [ ] **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 ### User-Facing Fixes #### 1. Module list glitching when toggling enabled state When enabling or disabling a module from the "Utilities" list, the entire list would flicker and redraw, causing other toggles to glitch. This made it appear as if multiple modules were being affected by a single change. **Root cause** The `AllModules` ObservableCollection was being completely cleared and re-populated on every change, forcing the UI to destroy and recreate all list items. **Fix** Refactored collection updates to modify items in-place: - Introduced `_moduleItems` master list, built once during initialization. - `RefreshModuleList()` now updates properties without clearing collections - `SortModuleList()` uses `ObservableCollection.Move()` instead of `Clear()`/`Add()` #### 2. Incorrect sort menu checkmark behaviour The checkmark in the "Sort by" menu would not update correctly when changing sort order, sometimes showing the incorrect item checked, or even both at once. **Root cause** The `IsChecked` prop on the `ToggleMenuFlyoutItem` is bound to `DashboardSortOrder`, but the binding was not updating because the ViewModel didn't raise a property change notification when the sort order was changed. **Fix** Added `OnPropertyChanged(nameof(DashboardSortOrder))` in `SortModuleList()`. ### Code quality improvements 1. Renamed `GetShortcutModules()` to `RefreshShortcutModules()`. The original name implied a getter, but the routine actually affects state by rebuilding the shortcut and action lists, violating the Command-Query Separation principle. 2. Added an `_isUpdatingFromUI` flag as a defensive measure against circular updates when a UI toggle is changed. 3. Separation of concerns for operations on the modules list. Building, sorting and refreshing it are separated. 4. Added comments and XML doc headers for new methods. Included brief description of GPO locking behaviour. ## Validation Steps Performed - Verified that toggling modules in the list no longer causes the list to flicker or for other toggles to glitch. - Confirmed that the sort order checkmarks update correctly and reflect the current sort order. - Tested GPO policy settings are still queried as before. - Checked sort behaviour is unaffected. ## Videos *Sorting UI* https://github.com/user-attachments/assets/3484bf63-2946-4460-83a5-361fa7e41c82 *Toggle behaviour* https://github.com/user-attachments/assets/1fae5429-6fa3-4431-80f3-0907dab4f326 --------- Co-authored-by: Gordon Lam (SH) --- .../ViewModels/DashboardViewModel.cs | 173 ++++++++++++++---- 1 file changed, 141 insertions(+), 32 deletions(-) diff --git a/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs index 56437cd9f3..b0520dd38d 100644 --- a/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs @@ -40,6 +40,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels public ObservableCollection ActionModules { get; set; } = new ObservableCollection(); + // Master list of module items that is sorted and projected into AllModules. + private List _moduleItems = new List(); + + // Flag to prevent circular updates when a UI toggle triggers settings changes. + private bool _isUpdatingFromUI; + private AllHotkeyConflictsData _allHotkeyConflictsData = new AllHotkeyConflictsData(); public AllHotkeyConflictsData AllHotkeyConflictsData @@ -74,7 +80,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels generalSettingsConfig.DashboardSortOrder = value; OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(generalSettingsConfig); SendConfigMSG(outgoing.ToString()); - RefreshModuleList(); + SortModuleList(); } } } @@ -96,8 +102,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels // set the callback functions value to handle outgoing IPC message. SendConfigMSG = ipcMSGCallBackFunc; - RefreshModuleList(); - GetShortcutModules(); + BuildModuleList(); + SortModuleList(); + RefreshShortcutModules(); } protected override void OnConflictsUpdated(object sender, AllHotkeyConflictsEventArgs e) @@ -129,11 +136,13 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels GlobalHotkeyConflictManager.Instance?.RequestAllConflicts(); } - private void RefreshModuleList() + /// + /// Builds the master list of module items. Called once during initialization. + /// Each module item contains its configuration, enabled state, and GPO lock status. + /// + private void BuildModuleList() { - AllModules.Clear(); - - var moduleItems = new List(); + _moduleItems.Clear(); foreach (ModuleType moduleType in Enum.GetValues()) { @@ -149,47 +158,143 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels DashboardModuleItems = GetModuleItems(moduleType), }; newItem.EnabledChangedCallback = EnabledChangedOnUI; - moduleItems.Add(newItem); - } - - // Sort based on current sort order - var sortedItems = DashboardSortOrder switch - { - DashboardSortOrder.ByStatus => moduleItems.OrderByDescending(x => x.IsEnabled).ThenBy(x => x.Label), - _ => moduleItems.OrderBy(x => x.Label), // Default alphabetical - }; - - foreach (var item in sortedItems) - { - AllModules.Add(item); + _moduleItems.Add(newItem); } } + /// + /// Sorts the module list according to the current sort order and updates the AllModules collection. + /// On first call, populates AllModules. On subsequent calls, uses Move() to reorder items in-place + /// to avoid destroying and recreating UI elements. + /// + private void SortModuleList() + { + var sortedItems = (DashboardSortOrder switch + { + DashboardSortOrder.ByStatus => _moduleItems.OrderByDescending(x => x.IsEnabled).ThenBy(x => x.Label), + _ => _moduleItems.OrderBy(x => x.Label), // Default alphabetical + }).ToList(); + + // If AllModules is empty (first load), just populate it. + if (AllModules.Count == 0) + { + foreach (var item in sortedItems) + { + AllModules.Add(item); + } + + return; + } + + // Otherwise, update the collection in place using Move to avoid UI glitches. + for (int i = 0; i < sortedItems.Count; i++) + { + var currentItem = sortedItems[i]; + var currentIndex = AllModules.IndexOf(currentItem); + + if (currentIndex != -1 && currentIndex != i) + { + AllModules.Move(currentIndex, i); + } + } + + // Notify that DashboardSortOrder changed so the menu updates its checked state. + OnPropertyChanged(nameof(DashboardSortOrder)); + } + + /// + /// Refreshes module enabled/locked states by re-reading GPO configuration. Only + /// updates properties that have actually changed to minimize UI notifications + /// then re-sorts the list according to the current sort order. + /// + private void RefreshModuleList() + { + foreach (var item in _moduleItems) + { + GpoRuleConfigured gpo = ModuleHelper.GetModuleGpoConfiguration(item.Tag); + + // GPO can force-enable (Enabled) or force-disable (Disabled) a module. + // If Enabled: module is on and the user cannot disable it. + // If Disabled: module is off and the user cannot enable it. + // Otherwise, the setting is unlocked and the user can enable/disable it. + bool newEnabledState = gpo == GpoRuleConfigured.Enabled || (gpo != GpoRuleConfigured.Disabled && ModuleHelper.GetIsModuleEnabled(generalSettingsConfig, item.Tag)); + + // Lock the toggle when GPO is controlling the module. + bool newLockedState = gpo == GpoRuleConfigured.Enabled || gpo == GpoRuleConfigured.Disabled; + + // Only update if there's an actual change to minimize UI notifications. + if (item.IsEnabled != newEnabledState) + { + item.IsEnabled = newEnabledState; + } + + if (item.IsLocked != newLockedState) + { + item.IsLocked = newLockedState; + } + } + + SortModuleList(); + } + + /// + /// Callback invoked when a user toggles a module's enabled state in the UI. + /// Sets the _isUpdatingFromUI flag to prevent circular updates, then updates + /// settings, re-sorts if needed, and refreshes dependent collections. + /// private void EnabledChangedOnUI(DashboardListItem dashboardListItem) { - Views.ShellPage.UpdateGeneralSettingsCallback(dashboardListItem.Tag, dashboardListItem.IsEnabled); - - if (dashboardListItem.Tag == ModuleType.NewPlus && dashboardListItem.IsEnabled == true) + _isUpdatingFromUI = true; + try { - var settingsUtils = new SettingsUtils(); - var settings = NewPlusViewModel.LoadSettings(settingsUtils); - NewPlusViewModel.CopyTemplateExamples(settings.Properties.TemplateLocation.Value); - } + Views.ShellPage.UpdateGeneralSettingsCallback(dashboardListItem.Tag, dashboardListItem.IsEnabled); - // Request updated conflicts after module state change - RequestConflictData(); + if (dashboardListItem.Tag == ModuleType.NewPlus && dashboardListItem.IsEnabled == true) + { + var settingsUtils = new SettingsUtils(); + var settings = NewPlusViewModel.LoadSettings(settingsUtils); + NewPlusViewModel.CopyTemplateExamples(settings.Properties.TemplateLocation.Value); + } + + // Re-sort only required if sorting by enabled status. + if (DashboardSortOrder == DashboardSortOrder.ByStatus) + { + SortModuleList(); + } + + // Always refresh shortcuts/actions to reflect enabled state changes. + RefreshShortcutModules(); + + // Request updated conflicts after module state change. + RequestConflictData(); + } + finally + { + _isUpdatingFromUI = false; + } } + /// + /// Callback invoked when module enabled state changes from other parts of the + /// settings UI. Ignores the notification if it was triggered by a UI toggle + /// we're already handling, to prevent circular updates. + /// public void ModuleEnabledChangedOnSettingsPage() { + // Ignore if this was triggered by a UI change that we're already handling. + if (_isUpdatingFromUI) + { + return; + } + try { RefreshModuleList(); - GetShortcutModules(); + RefreshShortcutModules(); OnPropertyChanged(nameof(ShortcutModules)); - // Request updated conflicts after module state change + // Request updated conflicts after module state change. RequestConflictData(); } catch (Exception ex) @@ -198,7 +303,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } } - private void GetShortcutModules() + /// + /// Rebuilds ShortcutModules and ActionModules collections by filtering AllModules + /// to only include enabled modules and their respective shortcut/action items. + /// + private void RefreshShortcutModules() { ShortcutModules.Clear(); ActionModules.Clear(); From 15c79a0176388f2707d51f90ec5880d334dfb94e Mon Sep 17 00:00:00 2001 From: Dave Rayment Date: Wed, 19 Nov 2025 08:50:25 +0000 Subject: [PATCH 12/23] [Settings] Fix inconsistent description text for the mouse tools (#43651) ## Summary of the Pull Request Change some of the mouse utilities' descriptions from declarative to imperative, to match best practice and to be consistent with the other descriptions. ## 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 - Confirmed the changed descriptions were updated in Settings. --- src/settings-ui/Settings.UI/Strings/en-us/Resources.resw | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw index 388a713890..478494aeed 100644 --- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw @@ -2827,7 +2827,7 @@ Right-click to remove the key combination, thereby deactivating the shortcut.Refers to the utility name
- Find My Mouse highlights the position of the cursor when pressing the Ctrl key twice, using a custom shortcut or when shaking the mouse. + Highlight the position of the cursor when pressing the Ctrl key twice, using a custom shortcut or when shaking the mouse. "Ctrl" is a keyboard key. "Find My Mouse" is the name of the utility @@ -2916,7 +2916,7 @@ Right-click to remove the key combination, thereby deactivating the shortcut.Refers to the utility name - Mouse Highlighter mode will highlight mouse clicks. + Highlight mouse clicks. "Mouse Highlighter" is the name of the utility. Mouse is the hardware mouse. @@ -2961,7 +2961,7 @@ Right-click to remove the key combination, thereby deactivating the shortcut.Refers to the utility name - Mouse Pointer Crosshairs draws crosshairs centered on the mouse pointer. + Draw crosshairs centered on the mouse pointer. "Mouse Pointer Crosshairs" is the name of the utility. Mouse is the hardware mouse. From 4a0d9912aea92130ef55b23f67fce2d27ef886e2 Mon Sep 17 00:00:00 2001 From: Kai Tao <69313318+vanzue@users.noreply.github.com> Date: Thu, 20 Nov 2025 09:57:29 +0800 Subject: [PATCH 13/23] Advanced Paste: No cache for foundry local model list (#43716) ## Summary of the Pull Request Cache of the downloaded model will make the newly added model only work after running of powertoys, this disable the cache, so just downloaded model will take effect immediately ## 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 Validated locally --- .../FoundryLocalModelProvider.cs | 75 +++++++------------ .../ILanguageModelProvider.cs | 2 +- .../LanguageModelProvider/ModelDetails.cs | 2 - .../FoundryLocalModelPicker.xaml.cs | 18 +---- .../Views/AdvancedPastePage.xaml.cs | 39 ++++++---- 5 files changed, 53 insertions(+), 83 deletions(-) diff --git a/src/common/LanguageModelProvider/FoundryLocalModelProvider.cs b/src/common/LanguageModelProvider/FoundryLocalModelProvider.cs index 2447d818a2..e7cc30c288 100644 --- a/src/common/LanguageModelProvider/FoundryLocalModelProvider.cs +++ b/src/common/LanguageModelProvider/FoundryLocalModelProvider.cs @@ -12,9 +12,8 @@ namespace LanguageModelProvider; public sealed class FoundryLocalModelProvider : ILanguageModelProvider { - private IEnumerable? _downloadedModels; - private IEnumerable? _catalogModels; private FoundryClient? _foundryClient; + private IEnumerable? _catalogModels; private string? _serviceUrl; public static FoundryLocalModelProvider Instance { get; } = new(); @@ -43,15 +42,6 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider throw new InvalidOperationException(errorMessage); } - // Check if model is cached - var isInCache = _downloadedModels?.Any(m => m.ProviderModelDetails is FoundryCachedModel cached && cached.Name == modelId) ?? false; - if (!isInCache) - { - var errorMessage = $"The requested model '{modelId}' is not cached. Please download it using Foundry Local."; - Logger.LogError($"[FoundryLocal] {errorMessage}"); - throw new InvalidOperationException(errorMessage); - } - // Ensure the model is loaded before returning chat client var isLoaded = _foundryClient!.EnsureModelLoaded(modelId).GetAwaiter().GetResult(); if (!isLoaded) @@ -100,30 +90,38 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider return $"new OpenAIClient(new ApiKeyCredential(\"none\"), new OpenAIClientOptions{{ Endpoint = new Uri(\"{_serviceUrl}/v1\") }}).GetChatClient(\"{modelId}\").AsIChatClient()"; } - public async Task> GetModelsAsync(bool ignoreCached = false, CancellationToken cancelationToken = default) + public async Task> GetModelsAsync(CancellationToken cancelationToken = default) { - if (ignoreCached) - { - Logger.LogInfo("[FoundryLocal] Ignoring cached models, resetting"); - Reset(); - } - await InitializeAsync(cancelationToken); - Logger.LogInfo($"[FoundryLocal] Returning {_downloadedModels?.Count() ?? 0} downloaded models"); - return _downloadedModels ?? []; - } + if (_foundryClient == null) + { + return Array.Empty(); + } - private void Reset() - { - _downloadedModels = null; - _catalogModels = null; - _ = InitializeAsync(); + var cachedModels = await _foundryClient.ListCachedModels(); + List downloadedModels = []; + + foreach (var model in cachedModels) + { + Logger.LogInfo($"[FoundryLocal] Adding unmatched cached model: {model.Name}"); + downloadedModels.Add(new ModelDetails + { + Id = $"fl-{model.Name}", + Name = model.Name, + Url = $"fl://{model.Name}", + Description = $"{model.Name} running locally with Foundry Local", + HardwareAccelerators = [HardwareAccelerator.FOUNDRYLOCAL], + ProviderModelDetails = model, + }); + } + + return downloadedModels; } private async Task InitializeAsync(CancellationToken cancelationToken = default) { - if (_foundryClient != null && _downloadedModels != null && _downloadedModels.Any() && _catalogModels != null && _catalogModels.Any()) + if (_foundryClient != null && _catalogModels != null && _catalogModels.Any()) { await _foundryClient.EnsureRunning().ConfigureAwait(false); return; @@ -145,29 +143,6 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider var catalogModels = await _foundryClient.ListCatalogModels(); Logger.LogInfo($"[FoundryLocal] Found {catalogModels.Count} catalog models"); _catalogModels = catalogModels; - - var cachedModels = await _foundryClient.ListCachedModels(); - Logger.LogInfo($"[FoundryLocal] Found {cachedModels.Count} cached models"); - - List downloadedModels = []; - - foreach (var model in cachedModels) - { - Logger.LogInfo($"[FoundryLocal] Adding unmatched cached model: {model.Name}"); - downloadedModels.Add(new ModelDetails - { - Id = $"fl-{model.Name}", - Name = model.Name, - Url = $"fl://{model.Name}", - Description = $"{model.Name} running locally with Foundry Local", - HardwareAccelerators = [HardwareAccelerator.FOUNDRYLOCAL], - SupportedOnQualcomm = true, - ProviderModelDetails = model, - }); - } - - _downloadedModels = downloadedModels; - Logger.LogInfo($"[FoundryLocal] Initialization complete. Total downloaded models: {downloadedModels.Count}"); } public async Task IsAvailable() diff --git a/src/common/LanguageModelProvider/ILanguageModelProvider.cs b/src/common/LanguageModelProvider/ILanguageModelProvider.cs index 2bef3fb7f1..9d203adaf6 100644 --- a/src/common/LanguageModelProvider/ILanguageModelProvider.cs +++ b/src/common/LanguageModelProvider/ILanguageModelProvider.cs @@ -12,7 +12,7 @@ public interface ILanguageModelProvider string ProviderDescription { get; } - Task> GetModelsAsync(bool ignoreCached = false, CancellationToken cancelationToken = default); + Task> GetModelsAsync(CancellationToken cancelationToken = default); IChatClient? GetIChatClient(string modelId); diff --git a/src/common/LanguageModelProvider/ModelDetails.cs b/src/common/LanguageModelProvider/ModelDetails.cs index 2e68ca6feb..e383aa7d27 100644 --- a/src/common/LanguageModelProvider/ModelDetails.cs +++ b/src/common/LanguageModelProvider/ModelDetails.cs @@ -24,8 +24,6 @@ public class ModelDetails public List HardwareAccelerators { get; set; } = []; - public bool SupportedOnQualcomm { get; set; } - public string License { get; set; } = string.Empty; public object? ProviderModelDetails { get; set; } diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ModelPicker/FoundryLocalModelPicker.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Controls/ModelPicker/FoundryLocalModelPicker.xaml.cs index 3d1c4c2159..400074f9d3 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ModelPicker/FoundryLocalModelPicker.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/ModelPicker/FoundryLocalModelPicker.xaml.cs @@ -30,7 +30,7 @@ public sealed partial class FoundryLocalModelPicker : UserControl public delegate void DownloadRequestedEventHandler(object sender, object payload); - public delegate void LoadRequestedEventHandler(object sender, FoundryLoadRequestedEventArgs args); + public delegate void LoadRequestedEventHandler(object sender); public event ModelSelectionChangedEventHandler SelectionChanged; @@ -94,7 +94,7 @@ public sealed partial class FoundryLocalModelPicker : UserControl public bool HasDownloadableModels => DownloadableModels?.Cast().Any() ?? false; - public void RequestLoad(bool refresh) + public void RequestLoad() { if (IsLoading) { @@ -107,7 +107,7 @@ public sealed partial class FoundryLocalModelPicker : UserControl IsAvailable = false; StatusText = "Loading Foundry Local status..."; - LoadRequested?.Invoke(this, new FoundryLoadRequestedEventArgs(refresh)); + LoadRequested?.Invoke(this); } private static void OnCachedModelsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) @@ -310,7 +310,7 @@ public sealed partial class FoundryLocalModelPicker : UserControl private void RefreshModelsButton_Click(object sender, RoutedEventArgs e) { - RequestLoad(refresh: true); + RequestLoad(); } private void UpdateVisualStates() @@ -444,14 +444,4 @@ public sealed partial class FoundryLocalModelPicker : UserControl { return string.IsNullOrWhiteSpace(license) ? Visibility.Collapsed : Visibility.Visible; } - - public sealed class FoundryLoadRequestedEventArgs : EventArgs - { - public FoundryLoadRequestedEventArgs(bool refresh) - { - Refresh = refresh; - } - - public bool Refresh { get; } - } } diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml.cs index 8812370f50..7b267107ee 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml.cs @@ -82,7 +82,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views { ViewModel.RefreshEnabledState(); UpdatePasteAIUIVisibility(); - _ = UpdateFoundryLocalUIAsync(refreshFoundry: true); + _ = UpdateFoundryLocalUIAsync(); } private void EnableAdvancedPasteAI() => ViewModel.EnableAI(); @@ -384,7 +384,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views } } - private Task UpdateFoundryLocalUIAsync(bool refreshFoundry = false) + private Task UpdateFoundryLocalUIAsync() { string selectedType = ViewModel?.PasteAIProviderDraft?.ServiceType ?? string.Empty; bool isFoundryLocal = string.Equals(selectedType, "FoundryLocal", StringComparison.OrdinalIgnoreCase); @@ -419,12 +419,12 @@ namespace Microsoft.PowerToys.Settings.UI.Views PasteAIProviderConfigurationDialog.IsPrimaryButtonEnabled = false; } - FoundryLocalPicker?.RequestLoad(refreshFoundry); + FoundryLocalPicker?.RequestLoad(); return Task.CompletedTask; } - private async Task LoadFoundryLocalModelsAsync(bool refresh = false) + private async Task LoadFoundryLocalModelsAsync() { if (FoundryLocalPanel is null) { @@ -456,9 +456,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views return; } - IEnumerable cachedModelsEnumerable = refresh - ? await provider.GetModelsAsync(ignoreCached: true, cancelationToken: cancellationToken) - : await provider.GetModelsAsync(cancelationToken: cancellationToken); + IEnumerable cachedModelsEnumerable = await provider.GetModelsAsync(cancelationToken: cancellationToken).ConfigureAwait(false); if (cancellationToken.IsCancellationRequested) { @@ -467,9 +465,12 @@ namespace Microsoft.PowerToys.Settings.UI.Views var cachedModels = cachedModelsEnumerable?.ToList() ?? new List(); - UpdateFoundryCollections(cachedModels); - ShowFoundryAvailableState(); - RestoreFoundrySelection(cachedModels); + DispatcherQueue.TryEnqueue(() => + { + UpdateFoundryCollections(cachedModels); + ShowFoundryAvailableState(); + RestoreFoundrySelection(cachedModels); + }); } catch (OperationCanceledException) { @@ -478,12 +479,18 @@ namespace Microsoft.PowerToys.Settings.UI.Views catch (Exception ex) { var errorMessage = $"Unable to load Foundry Local models. {ex.Message}"; - ShowFoundryUnavailableState(errorMessage); System.Diagnostics.Debug.WriteLine($"[AdvancedPastePage] Failed to load Foundry Local models: {ex}"); + DispatcherQueue.TryEnqueue(() => + { + ShowFoundryUnavailableState(errorMessage); + }); } finally { - UpdateFoundrySaveButtonState(); + DispatcherQueue.TryEnqueue(() => + { + UpdateFoundrySaveButtonState(); + }); } } @@ -672,9 +679,9 @@ namespace Microsoft.PowerToys.Settings.UI.Views UpdateFoundrySaveButtonState(); } - private async void FoundryLocalPicker_LoadRequested(object sender, FoundryLocalModelPicker.FoundryLoadRequestedEventArgs args) + private async void FoundryLocalPicker_LoadRequested(object sender) { - await LoadFoundryLocalModelsAsync(args?.Refresh ?? false); + await LoadFoundryLocalModelsAsync(); } private sealed class FoundryDownloadableModel : INotifyPropertyChanged @@ -1089,7 +1096,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views PasteAIProviderConfigurationDialog.Title = $"{displayName} provider configuration"; } - await UpdateFoundryLocalUIAsync(refreshFoundry: true); + await UpdateFoundryLocalUIAsync(); UpdatePasteAIUIVisibility(); RefreshDialogBindings(); @@ -1118,7 +1125,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views : $"{titlePrefix} provider configuration"; UpdatePasteAIUIVisibility(); - await UpdateFoundryLocalUIAsync(refreshFoundry: false); + await UpdateFoundryLocalUIAsync(); RefreshDialogBindings(); PasteAIApiKeyPasswordBox.Password = ViewModel.GetPasteAIApiKey(provider.Id, provider.ServiceType); await PasteAIProviderConfigurationDialog.ShowAsync(); From 9fbd3de3a2ec3d64457823586246b8fd797d0959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pol=C3=A1=C5=A1ek?= Date: Thu, 20 Nov 2025 16:23:42 +0100 Subject: [PATCH 14/23] CmdPal: Add native debugging launch profile to launchSettings.json (#43718) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary of the Pull Request See title. I’m too lazy to open the dialog and then revert the change later. ## 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 --- .../Microsoft.CmdPal.UI/Properties/launchSettings.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Properties/launchSettings.json b/src/modules/cmdpal/Microsoft.CmdPal.UI/Properties/launchSettings.json index febacfc92e..4631e9aeaf 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Properties/launchSettings.json +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Properties/launchSettings.json @@ -5,6 +5,11 @@ "nativeDebugging": false, "doNotLaunchApp": false }, + "Microsoft.CmdPal.UI (Package) + Native debugging": { + "commandName": "MsixPackage", + "nativeDebugging": true, + "doNotLaunchApp": false + }, "Microsoft.CmdPal.UI (Unpackaged)": { "commandName": "Project" } From 28dba2633e930156e9cf5a0ee6c78216f5171ec8 Mon Sep 17 00:00:00 2001 From: Jaylyn Barbee <51131738+Jaylyn-Barbee@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:22:40 -0800 Subject: [PATCH 15/23] [Light Switch][Dev Docs] Clarify LightSwitchService and LightSwitchStateManager roles (#43748) Updated LightSwitch module documentation to clarify the role of LightSwitchService and LightSwitchStateManager. --------- Co-authored-by: Niels Laute --- doc/devdocs/modules/lightswitch.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/doc/devdocs/modules/lightswitch.md b/doc/devdocs/modules/lightswitch.md index 1e251dfff1..18192f7f23 100644 --- a/doc/devdocs/modules/lightswitch.md +++ b/doc/devdocs/modules/lightswitch.md @@ -33,9 +33,12 @@ The **Light Switch** module lets users automatically transition between light an > **Note:** Using the shortcut overrides the current schedule until the next transition event. -* **LightSwitchService** - Reads settings and applies theming. Runs a check every minute to ensure the state is correct. - +* **LightSwitchService.cpp** + is the heart beat of the module. Controls ticking every minute and depending on user actions (manual override, settings changing, etc) triggers the state manager to perform the corresponding operation. + +* **LightSwitchStateManager.cpp** + handles updating the state based on the signals sent by LightSwitchService. + * **SettingsXAML/LightSwitch** Provides the settings UI for configuring schedules, syncing location, and customizing shortcuts. From ebc3a139c5897191dad3357cc62d5b7dd1312bd5 Mon Sep 17 00:00:00 2001 From: Erik Anderson Date: Fri, 21 Nov 2025 02:34:34 -0800 Subject: [PATCH 16/23] Fix typo in AI settings card description (#43757) ## Summary of the Pull Request The word "cloud" does not use a vowel sound, so the preceding word should be "A" instead of "An". ## PR Checklist - [X] Closes: #43756 - [ ] **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 - [X] **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 Co-authored-by: Erik Anderson --- src/settings-ui/Settings.UI/Strings/en-us/Resources.resw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw index 478494aeed..a9c13d8128 100644 --- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw @@ -3410,7 +3410,7 @@ Activate by holding the key for the character you want to add an accent to, then An AI powered tool to put your clipboard content into any format you need, focused towards developer workflows. - Transform your clipboard content with the power of AI. An cloud or local endpoint is required. + Transform your clipboard content with the power of AI. A cloud or local endpoint is required. Learn more From 725ad21952e08ab0fab7547cf88d1f766edfb7e4 Mon Sep 17 00:00:00 2001 From: Dave Rayment Date: Mon, 24 Nov 2025 01:12:54 +0000 Subject: [PATCH 17/23] [Awake] Fix issue with timed mode not expiring correctly (#43785) ## Summary of the Pull Request Resolves an issue with the timed mode's expiry not completing correctly. ## PR Checklist - [x] Closes: #43775 - [ ] **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 This was because of my recent change to the timed mode. The `Subscribe` method on the `Observable` interval accidentally wired the completion logic to the **Error** handler instead of the **Completion** handler because of the use of a discard `_` instead of an empty parameter list `()`. As a result of the incorrect overload being called, Awake stayed in the Timed state despite the timer reaching zero. ## Validation Steps Performed Confirmed that the timed mode times out and exits upon expiry. --- src/modules/awake/Awake/Core/Manager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs index ad4c417b31..c6aa1c2efb 100644 --- a/src/modules/awake/Awake/Core/Manager.cs +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -350,7 +350,7 @@ namespace Awake.Core TrayHelper.TimedIcon, TrayIconAction.Update); }, - _ => HandleTimerCompletion("timed"), + () => HandleTimerCompletion("timed"), _tokenSource.Token); } From 2830ea919c05d6e37221c8c200a818c17e80d100 Mon Sep 17 00:00:00 2001 From: Kai Tao <69313318+vanzue@users.noreply.github.com> Date: Mon, 24 Nov 2025 09:51:27 +0800 Subject: [PATCH 18/23] Advanced Paste: Adjust model parameter to make the result longer (#43768) ## Summary of the Pull Request Adjust model parameter to make the result longer ## 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 --- src/common/LanguageModelProvider/FoundryLocalModelProvider.cs | 2 +- .../Services/CustomActions/FoundryLocalPasteProvider.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common/LanguageModelProvider/FoundryLocalModelProvider.cs b/src/common/LanguageModelProvider/FoundryLocalModelProvider.cs index e7cc30c288..5158e4334e 100644 --- a/src/common/LanguageModelProvider/FoundryLocalModelProvider.cs +++ b/src/common/LanguageModelProvider/FoundryLocalModelProvider.cs @@ -64,7 +64,7 @@ public sealed class FoundryLocalModelProvider : ILanguageModelProvider return new OpenAIClient( new ApiKeyCredential("none"), - new OpenAIClientOptions { Endpoint = endpointUri }) + new OpenAIClientOptions { Endpoint = endpointUri, NetworkTimeout = TimeSpan.FromMinutes(5) }) .GetChatClient(modelId) .AsIChatClient(); } diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/FoundryLocalPasteProvider.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/FoundryLocalPasteProvider.cs index 43481eddae..8b57baae74 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/FoundryLocalPasteProvider.cs +++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/FoundryLocalPasteProvider.cs @@ -146,6 +146,7 @@ public sealed class FoundryLocalPasteProvider : IPasteAIProvider var options = new ChatOptions { ModelId = modelReference, + MaxOutputTokens = 2048, }; if (!string.IsNullOrWhiteSpace(systemPrompt)) From 95c8a83f797dac6bdf82a71bf93a1e0c16fb0080 Mon Sep 17 00:00:00 2001 From: leileizhang Date: Mon, 24 Nov 2025 10:08:12 +0800 Subject: [PATCH 19/23] [Hotfix] Remove the properties in Prompt Execution Settings for OpenAI (#43766) ## Summary of the Pull Request Remove the properties in Prompt Execution Settings for OpenAI, as the new models may not support them. Will try to expose them in the UI so users can add them on their own in the next release. ## 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 --- .../AdvancedPaste/Services/AdvancedAIKernelService.cs | 1 - .../Services/CustomActions/SemanticKernelPasteProvider.cs | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/AdvancedAIKernelService.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/AdvancedAIKernelService.cs index 759d6ec57d..c886bcef43 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/Services/AdvancedAIKernelService.cs +++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/AdvancedAIKernelService.cs @@ -215,7 +215,6 @@ public sealed class AdvancedAIKernelService : KernelServiceBase return new OpenAIPromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(), - Temperature = 0.01, }; } } diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/SemanticKernelPasteProvider.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/SemanticKernelPasteProvider.cs index 819549b466..eb2f56e01f 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/SemanticKernelPasteProvider.cs +++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/SemanticKernelPasteProvider.cs @@ -157,8 +157,6 @@ namespace AdvancedPaste.Services.CustomActions { AIServiceType.OpenAI or AIServiceType.AzureOpenAI => new OpenAIPromptExecutionSettings { - Temperature = 0.01, - MaxTokens = 2000, FunctionChoiceBehavior = null, }, _ => new PromptExecutionSettings(), From 09c8c1d79a389483d12a3f4b535f2316ec31022a Mon Sep 17 00:00:00 2001 From: leileizhang Date: Mon, 24 Nov 2025 10:42:35 +0800 Subject: [PATCH 20/23] [Hot Fix] Fix Image Resizer not working on Win10 (#43763) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary of the Pull Request Windows 10 can’t launch the app using the Sparse Package. Remove the app manifest so that Image Resizer can start properly on Windows 10. We will figure out how to support Sparse Packages on Windows 10 in the next release. ## PR Checklist - [x] Closes: #43747 #43734 #43722 #43759 - [ ] **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 --- src/modules/imageresizer/ui/ImageResizerUI.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/imageresizer/ui/ImageResizerUI.csproj b/src/modules/imageresizer/ui/ImageResizerUI.csproj index b146db8435..3ce98d8386 100644 --- a/src/modules/imageresizer/ui/ImageResizerUI.csproj +++ b/src/modules/imageresizer/ui/ImageResizerUI.csproj @@ -24,13 +24,13 @@ Resources\ImageResizer.ico - + From 2c9a9e9fcab59ad66be052f1378be26ce6649bde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pol=C3=A1=C5=A1ek?= Date: Mon, 24 Nov 2025 23:57:10 +0100 Subject: [PATCH 21/23] CmdPal: Improve Command Palette behavior in "Last position" mode (#43543) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary of the Pull Request This PR improves Command Palette behavior in “Last position†mode: - Correctly handles DPI changes between monitors. - Ensures the window is always visible — if it’s fully off-screen or has less than 100px visible on any axis, it is re-centered. ## PR Checklist - [x] Closes: #43398 - [ ] **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 --- .../WindowPosition.cs | 41 ++++- .../Microsoft.CmdPal.UI/MainWindow.xaml.cs | 143 +++++++++++++++--- 2 files changed, 161 insertions(+), 23 deletions(-) diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/WindowPosition.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/WindowPosition.cs index db413491db..7963aec154 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/WindowPosition.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/WindowPosition.cs @@ -2,21 +2,52 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Windows.Graphics; namespace Microsoft.CmdPal.UI.ViewModels; public sealed class WindowPosition { + /// + /// Gets or sets left position in device pixels. + /// public int X { get; set; } + /// + /// Gets or sets top position in device pixels. + /// public int Y { get; set; } + /// + /// Gets or sets width in device pixels. + /// public int Width { get; set; } + /// + /// Gets or sets height in device pixels. + /// public int Height { get; set; } + + /// + /// Gets or sets width of the screen in device pixels where the window is located. + /// + public int ScreenWidth { get; set; } + + /// + /// Gets or sets height of the screen in device pixels where the window is located. + /// + public int ScreenHeight { get; set; } + + /// + /// Gets or sets DPI (dots per inch) of the display where the window is located. + /// + public int Dpi { get; set; } + + /// + /// Converts the window position properties to a structure representing the physical window rectangle. + /// + public RectInt32 ToPhysicalWindowRectangle() + { + return new RectInt32(X, Y, Width, Height); + } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs index 32f542fc3b..b80ea69b86 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs @@ -18,6 +18,7 @@ using Microsoft.CmdPal.UI.ViewModels; using Microsoft.CmdPal.UI.ViewModels.Messages; using Microsoft.Extensions.DependencyInjection; using Microsoft.PowerToys.Telemetry; +using Microsoft.UI; using Microsoft.UI.Composition; using Microsoft.UI.Composition.SystemBackdrops; using Microsoft.UI.Input; @@ -33,6 +34,8 @@ using Windows.UI.WindowManagement; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.Graphics.Dwm; +using Windows.Win32.Graphics.Gdi; +using Windows.Win32.UI.HiDpi; using Windows.Win32.UI.Input.KeyboardAndMouse; using Windows.Win32.UI.WindowsAndMessaging; using WinRT; @@ -48,6 +51,9 @@ public sealed partial class MainWindow : WindowEx, IRecipient, IDisposable { + private const int DefaultWidth = 800; + private const int DefaultHeight = 480; + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Stylistically, window messages are WM_")] [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", Justification = "Stylistically, window messages are WM_")] private readonly uint WM_TASKBAR_RESTART; @@ -173,22 +179,8 @@ public sealed partial class MainWindow : WindowEx, return; } - AppWindow.Resize(new SizeInt32 { Width = savedPosition.Width, Height = savedPosition.Height }); - - var savedRect = new RectInt32(savedPosition.X, savedPosition.Y, savedPosition.Width, savedPosition.Height); - var displayArea = DisplayArea.GetFromRect(savedRect, DisplayAreaFallback.Nearest); - var workArea = displayArea.WorkArea; - - var maxX = workArea.X + Math.Max(0, workArea.Width - savedPosition.Width); - var maxY = workArea.Y + Math.Max(0, workArea.Height - savedPosition.Height); - - var targetPoint = new PointInt32 - { - X = Math.Clamp(savedPosition.X, workArea.X, maxX), - Y = Math.Clamp(savedPosition.Y, workArea.Y, maxY), - }; - - AppWindow.Move(targetPoint); + var newRect = EnsureWindowIsVisible(savedPosition.ToPhysicalWindowRectangle(), new SizeInt32(savedPosition.ScreenWidth, savedPosition.ScreenHeight), savedPosition.Dpi); + AppWindow.MoveAndResize(newRect); } private void PositionCentered(DisplayArea displayArea) @@ -207,12 +199,16 @@ public sealed partial class MainWindow : WindowEx, private void UpdateWindowPositionInMemory() { + var displayArea = DisplayArea.GetFromWindowId(AppWindow.Id, DisplayAreaFallback.Nearest) ?? DisplayArea.Primary; _currentWindowPosition = new WindowPosition { X = AppWindow.Position.X, Y = AppWindow.Position.Y, Width = AppWindow.Size.Width, Height = AppWindow.Size.Height, + Dpi = (int)this.GetDpiForWindow(), + ScreenWidth = displayArea.WorkArea.Width, + ScreenHeight = displayArea.WorkArea.Height, }; } @@ -300,8 +296,8 @@ public sealed partial class MainWindow : WindowEx, if (target == MonitorBehavior.ToLast) { - AppWindow.Resize(new SizeInt32 { Width = _currentWindowPosition.Width, Height = _currentWindowPosition.Height }); - AppWindow.Move(new PointInt32 { X = _currentWindowPosition.X, Y = _currentWindowPosition.Y }); + var newRect = EnsureWindowIsVisible(_currentWindowPosition.ToPhysicalWindowRectangle(), new SizeInt32(_currentWindowPosition.ScreenWidth, _currentWindowPosition.ScreenHeight), _currentWindowPosition.Dpi); + AppWindow.MoveAndResize(newRect); } else { @@ -330,6 +326,114 @@ public sealed partial class MainWindow : WindowEx, PInvoke.SetWindowPos(hwnd, HWND.HWND_TOPMOST, 0, 0, 0, 0, SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOSIZE); } + /// + /// Ensures that the window rectangle is visible on-screen. + /// + /// The window rectangle in physical pixels. + /// The desktop area the window was positioned on. + /// The window's original DPI. + /// + /// A window rectangle in physical pixels, moved to the nearest display and resized + /// if the DPI has changed. + /// + private static RectInt32 EnsureWindowIsVisible(RectInt32 windowRect, SizeInt32 originalScreen, int originalDpi) + { + var displayArea = DisplayArea.GetFromRect(windowRect, DisplayAreaFallback.Nearest); + if (displayArea is null) + { + return windowRect; + } + + var workArea = displayArea.WorkArea; + if (workArea.Width <= 0 || workArea.Height <= 0) + { + // Fallback, nothing reasonable to do + return windowRect; + } + + var effectiveDpi = GetEffectiveDpiFromDisplayId(displayArea); + if (originalDpi <= 0) + { + originalDpi = effectiveDpi; // use current DPI as baseline (no scaling adjustment needed) + } + + var hasInvalidSize = windowRect.Width <= 0 || windowRect.Height <= 0; + if (hasInvalidSize) + { + windowRect = new RectInt32(windowRect.X, windowRect.Y, DefaultWidth, DefaultHeight); + } + + // If we have a DPI change, scale the window rectangle accordingly + if (effectiveDpi != originalDpi) + { + var scalingFactor = effectiveDpi / (double)originalDpi; + windowRect = new RectInt32( + (int)Math.Round(windowRect.X * scalingFactor), + (int)Math.Round(windowRect.Y * scalingFactor), + (int)Math.Round(windowRect.Width * scalingFactor), + (int)Math.Round(windowRect.Height * scalingFactor)); + } + + var targetWidth = Math.Min(windowRect.Width, workArea.Width); + var targetHeight = Math.Min(windowRect.Height, workArea.Height); + + // Ensure at least some minimum visible area (e.g., 100 pixels) + // This helps prevent the window from being entirely offscreen, regardless of display scaling. + const int minimumVisibleSize = 100; + var isOffscreen = + windowRect.X + minimumVisibleSize > workArea.X + workArea.Width || + windowRect.X + windowRect.Width - minimumVisibleSize < workArea.X || + windowRect.Y + minimumVisibleSize > workArea.Y + workArea.Height || + windowRect.Y + windowRect.Height - minimumVisibleSize < workArea.Y; + + // if the work area size has changed, re-center the window + var workAreaSizeChanged = + originalScreen.Width != workArea.Width || + originalScreen.Height != workArea.Height; + + int targetX; + int targetY; + var recenter = isOffscreen || workAreaSizeChanged || hasInvalidSize; + if (recenter) + { + targetX = workArea.X + ((workArea.Width - targetWidth) / 2); + targetY = workArea.Y + ((workArea.Height - targetHeight) / 2); + } + else + { + targetX = windowRect.X; + targetY = windowRect.Y; + } + + return new RectInt32(targetX, targetY, targetWidth, targetHeight); + } + + private static int GetEffectiveDpiFromDisplayId(DisplayArea displayArea) + { + var effectiveDpi = 96; + + var hMonitor = (HMONITOR)Win32Interop.GetMonitorFromDisplayId(displayArea.DisplayId); + if (!hMonitor.IsNull) + { + var hr = PInvoke.GetDpiForMonitor(hMonitor, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out var dpiX, out _); + if (hr == 0) + { + effectiveDpi = (int)dpiX; + } + else + { + Logger.LogWarning($"GetDpiForMonitor failed with HRESULT: 0x{hr.Value:X8} on display {displayArea.DisplayId}"); + } + } + + if (effectiveDpi <= 0) + { + effectiveDpi = 96; + } + + return effectiveDpi; + } + private DisplayArea GetScreen(HWND currentHwnd, MonitorBehavior target) { // Leaving a note here, in case we ever need it: @@ -479,6 +583,9 @@ public sealed partial class MainWindow : WindowEx, Y = _currentWindowPosition.Y, Width = _currentWindowPosition.Width, Height = _currentWindowPosition.Height, + Dpi = _currentWindowPosition.Dpi, + ScreenWidth = _currentWindowPosition.ScreenWidth, + ScreenHeight = _currentWindowPosition.ScreenHeight, }; SettingsModel.SaveSettings(settings); From 452e0dcf513fd2d8c09c5115a56ed5327c5d4b38 Mon Sep 17 00:00:00 2001 From: Mike Hall Date: Wed, 26 Nov 2025 14:08:34 +0000 Subject: [PATCH 22/23] Module Loader tool for rapid testing of modules (#43813) ## Summary of the Pull Request ModuleLoader tool, a stand-alone Win32 executable for testing of PowerToy modules without needing branch builds. sample output from running the tool is below: .\ModuleLoader.exe .\powertoys.cursorwrap.dll PowerToys Module Loader v1.0 ============================= Loading module: .\powertoys.cursorwrap.dll Detected module name: cursorwrap Loading settings... Trying settings path: C:\Users\mikehall\AppData\Local\Microsoft\PowerToys\cursorwrap\settings.json Settings file loaded (315 characters) Settings loaded successfully. Loading module DLL... Module instance created successfully Module DLL loaded successfully. Module key: CursorWrap Module name: CursorWrap Applying settings to module... Settings applied. Registering module hotkeys... Module reports 1 legacy hotkey(s) Registering hotkey 0: Win+Alt+U - OK Hotkeys registered: 1 Enabling module... Module enabled. ============================= Module is now running! ============================= Module Status: - Name: CursorWrap - Key: CursorWrap - Enabled: Yes - Hotkeys: 1 registered Registered Hotkeys: Win+Alt+U Press Ctrl+C to exit. You can press the module's hotkey to toggle its functionality. Note that this doesn't integrate with Powertoys settings UI - this is purely to test Powertoys module functionality. ## 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 See details above. ## Validation Steps Performed ModuleLoader tested on Windows 11, Surface Laptop 7 Pro. --- tools/module_loader/ModuleLoader.manifest | 37 ++ tools/module_loader/ModuleLoader.vcxproj | 205 ++++++++ .../ModuleLoader.vcxproj.filters | 51 ++ tools/module_loader/SHARING.md | 483 ++++++++++++++++++ tools/module_loader/src/ConsoleHost.cpp | 80 +++ tools/module_loader/src/ConsoleHost.h | 38 ++ tools/module_loader/src/HotkeyManager.cpp | 279 ++++++++++ tools/module_loader/src/HotkeyManager.h | 86 ++++ tools/module_loader/src/ModuleLoader.cpp | 183 +++++++ tools/module_loader/src/ModuleLoader.h | 102 ++++ tools/module_loader/src/SettingsLoader.cpp | 182 +++++++ tools/module_loader/src/SettingsLoader.h | 47 ++ tools/module_loader/src/main.cpp | 244 +++++++++ 13 files changed, 2017 insertions(+) create mode 100644 tools/module_loader/ModuleLoader.manifest create mode 100644 tools/module_loader/ModuleLoader.vcxproj create mode 100644 tools/module_loader/ModuleLoader.vcxproj.filters create mode 100644 tools/module_loader/SHARING.md create mode 100644 tools/module_loader/src/ConsoleHost.cpp create mode 100644 tools/module_loader/src/ConsoleHost.h create mode 100644 tools/module_loader/src/HotkeyManager.cpp create mode 100644 tools/module_loader/src/HotkeyManager.h create mode 100644 tools/module_loader/src/ModuleLoader.cpp create mode 100644 tools/module_loader/src/ModuleLoader.h create mode 100644 tools/module_loader/src/SettingsLoader.cpp create mode 100644 tools/module_loader/src/SettingsLoader.h create mode 100644 tools/module_loader/src/main.cpp diff --git a/tools/module_loader/ModuleLoader.manifest b/tools/module_loader/ModuleLoader.manifest new file mode 100644 index 0000000000..2607358482 --- /dev/null +++ b/tools/module_loader/ModuleLoader.manifest @@ -0,0 +1,37 @@ + + + + PowerToys Module Loader - Standalone module testing utility + + + + + true/PM + PerMonitorV2 + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/module_loader/ModuleLoader.vcxproj b/tools/module_loader/ModuleLoader.vcxproj new file mode 100644 index 0000000000..dd9c01c584 --- /dev/null +++ b/tools/module_loader/ModuleLoader.vcxproj @@ -0,0 +1,205 @@ + + + + + Debug + x64 + + + Debug + ARM64 + + + Release + x64 + + + Release + ARM64 + + + + 17.0 + Win32Proj + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942} + ModuleLoader + 10.0 + ModuleLoader + + + + Application + true + v143 + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + ModuleLoader + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + ModuleLoader + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + ModuleLoader + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + ModuleLoader + + + + Level4 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + $(ProjectDir)src;$(SolutionDir)src\modules\interface;$(SolutionDir)src;%(AdditionalIncludeDirectories) + NotUsing + false + + + Console + true + kernel32.lib;user32.lib;Shcore.lib;%(AdditionalDependencies) + type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*' + + + $(ProjectDir)ModuleLoader.manifest + + + + + Level4 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + $(ProjectDir)src;$(SolutionDir)src\modules\interface;$(SolutionDir)src;%(AdditionalIncludeDirectories) + NotUsing + false + + + Console + true + kernel32.lib;user32.lib;Shcore.lib;%(AdditionalDependencies) + type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*' + + + $(ProjectDir)ModuleLoader.manifest + + + + + Level4 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + $(ProjectDir)src;$(SolutionDir)src\modules\interface;$(SolutionDir)src;%(AdditionalIncludeDirectories) + NotUsing + false + + + Console + true + true + true + kernel32.lib;user32.lib;Shcore.lib;%(AdditionalDependencies) + type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*' + + + $(ProjectDir)ModuleLoader.manifest + + + + + Level4 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + $(ProjectDir)src;$(SolutionDir)src\modules\interface;$(SolutionDir)src;%(AdditionalIncludeDirectories) + NotUsing + false + + + Console + true + true + true + kernel32.lib;user32.lib;Shcore.lib;%(AdditionalDependencies) + type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*' + + + $(ProjectDir)ModuleLoader.manifest + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/module_loader/ModuleLoader.vcxproj.filters b/tools/module_loader/ModuleLoader.vcxproj.filters new file mode 100644 index 0000000000..823f1c4e60 --- /dev/null +++ b/tools/module_loader/ModuleLoader.vcxproj.filters @@ -0,0 +1,51 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + + diff --git a/tools/module_loader/SHARING.md b/tools/module_loader/SHARING.md new file mode 100644 index 0000000000..1923d31adf --- /dev/null +++ b/tools/module_loader/SHARING.md @@ -0,0 +1,483 @@ +# Sharing ModuleLoader and Modules + +This guide explains how to share the ModuleLoader tool and PowerToy modules with others for testing purposes. + +## Overview + +The ModuleLoader is designed to be a **portable, standalone testing tool** that can be shared with module developers and testers. It has minimal dependencies and can work with any compatible PowerToy module DLL. + +--- + +## What You Need to Share + +### For Testing a Module (e.g., CursorWrap) + +#### **Minimum Package** (Recommended for Quick Testing) + +1. **ModuleLoader.exe** - The standalone loader application + - Location: `x64\Debug\ModuleLoader.exe` or `x64\Release\ModuleLoader.exe` + - No additional DLLs required (uses only Windows system libraries) + +2. **The Module DLL** - The PowerToy module to test + - Example: `CursorWrap.dll` from `x64\Debug\` or `x64\Release\` + - Location varies by module (see module-specific locations below) + +3. **settings.json** - Module configuration (place in same folder as the DLL) + - **NEW**: Settings can be placed alongside the module DLL for portable testing + - Location: Same directory as the module DLL (e.g., `settings.json` next to `CursorWrap.dll`) + - Falls back to: `%LOCALAPPDATA%\Microsoft\PowerToys\\settings.json` if not found locally + +#### **Complete Standalone Package** (For Users Without PowerToys Installed) + +1. **ModuleLoader.exe** +2. **Module DLL** +3. **Sample settings.json** - Pre-configured settings file +4. **Installation instructions** - See "Standalone Package Setup" section below + +--- + +### Debug Builds +If you build the module in Debug configuration: +- The module will output debug messages via `OutputDebugString()` +- View these with [DebugView](https://learn.microsoft.com/sysinternals/downloads/debugview) or Visual Studio Output window +- Example: CursorWrap outputs detailed topology and cursor wrapping debug info + +--- + + +## Module-Specific File Locations + +### CursorWrap +``` +Files to share: + - x64\Debug\CursorWrap.dll (or Release) + - %LOCALAPPDATA%\Microsoft\PowerToys\CursorWrap\settings.json + +Size: ~100KB +``` + +### MouseHighlighter +``` +Files to share: + - x64\Debug\MouseHighlighter.dll (or Release) + - %LOCALAPPDATA%\Microsoft\PowerToys\MouseHighlighter\settings.json + +Size: ~150KB +``` + +### FindMyMouse +``` +Files to share: + - x64\Debug\FindMyMouse.dll (or Release) + - %LOCALAPPDATA%\Microsoft\PowerToys\FindMyMouse\settings.json + +Size: ~120KB +``` + +### MousePointerCrosshairs +``` +Files to share: + - x64\Debug\MousePointerCrosshairs.dll (or Release) + - %LOCALAPPDATA%\Microsoft\PowerToys\MousePointerCrosshairs\settings.json + +Size: ~140KB +``` + +### MouseJump +``` +Files to share: + - x64\Debug\MouseJump.dll (or Release) + - %LOCALAPPDATA%\Microsoft\PowerToys\MouseJump\settings.json + +Note: MouseJump is a UI-based module and may not work fully with ModuleLoader +Size: ~200KB +``` + +### AlwaysOnTop +``` +Files to share: + - x64\Debug\AlwaysOnTop.dll (or Release) + - %LOCALAPPDATA%\Microsoft\PowerToys\AlwaysOnTop\settings.json + +Size: ~100KB +``` + +--- + +## Dependency Analysis + +### ModuleLoader.exe Dependencies +**Windows System Libraries Only** (automatically available on all Windows systems): +- `KERNEL32.dll` - Core Windows API +- `USER32.dll` - User interface functions +- `SHELL32.dll` - Shell functions +- `ole32.dll` - COM library + +**No PowerToys dependencies required!** The ModuleLoader is completely standalone. + +### Module DLL Dependencies (Typical) +Most PowerToy modules depend on: +- Windows system DLLs (automatically available) +- PowerToys common libraries (if any, they're typically statically linked) +- **Module settings** - Must be present in `%LOCALAPPDATA%\Microsoft\PowerToys\\` + +**Important**: Modules are generally **self-contained** and statically link most dependencies. You typically only need the module DLL itself. + +--- + +## Creating a Standalone Package + +### Step 1: Prepare the Files + +Create a folder structure like this: +``` +ModuleLoaderPackage\ +??? ModuleLoader.exe +??? CursorWrap.dll (or other module) +??? settings.json (module settings - placed locally!) +``` + +**NEW Simplified Structure**: You can now place `settings.json` directly alongside the module DLL! The ModuleLoader will check this location first before looking in the standard PowerToys settings directories. + +### Step 2: Extract Settings from Your Machine + +```powershell +# Copy settings from your development machine +$moduleName = "CursorWrap" # Change as needed +$settingsPath = "$env:LOCALAPPDATA\Microsoft\PowerToys\$moduleName\settings.json" +Copy-Item $settingsPath ".\settings\$moduleName\settings.json" +``` + +### Step 3: Create Installation Instructions (README.txt) + +```text +PowerToys Module Testing Package +================================= + +This package contains the ModuleLoader tool for testing PowerToy modules. + +Contents: + - ModuleLoader.exe : Standalone module loader + - modules\*.dll : PowerToy module(s) to test + - settings\*\*.json : Module configuration files + +Setup (First Time): +------------------- +1. Create settings directory: + %LOCALAPPDATA%\Microsoft\PowerToys\ + +2. Copy settings: + Copy the entire "settings\" folder to: + %LOCALAPPDATA%\Microsoft\PowerToys\ + + Example for CursorWrap: + Copy "settings\CursorWrap" to: + %LOCALAPPDATA%\Microsoft\PowerToys\CursorWrap\ + +Usage: +------ +ModuleLoader.exe modules\CursorWrap.dll + +The tool will: + - Load the module DLL + - Read settings from %LOCALAPPDATA%\Microsoft\PowerToys\\ + - Register hotkeys + - Enable the module + +Press Ctrl+C to exit. +Press the module's hotkey to toggle functionality. + +Requirements: +------------- +- Windows 10 1803 or later +- No PowerToys installation required! + +Troubleshooting: +---------------- +If you see "Settings file not found": + 1. Make sure you copied the settings folder correctly + 2. Check that the path is: + %LOCALAPPDATA%\Microsoft\PowerToys\\settings.json + 3. You can also run PowerToys once to generate default settings + +Debug Logs: +----------- +Module logs are written to: + %LOCALAPPDATA%\Microsoft\PowerToys\\Logs\ + +For debug builds, use DebugView to see real-time output. +``` + +--- + +## Quick Distribution Methods + +### Method 1: ZIP Archive +```powershell +# Create a complete package +$moduleName = "CursorWrap" +$packageName = "ModuleLoader-$moduleName-Package" + +# Collect files +New-Item $packageName -ItemType Directory +Copy-Item "x64\Debug\ModuleLoader.exe" "$packageName\" +New-Item "$packageName\modules" -ItemType Directory +Copy-Item "x64\Debug\$moduleName.dll" "$packageName\modules\" +New-Item "$packageName\settings\$moduleName" -ItemType Directory -Force +Copy-Item "$env:LOCALAPPDATA\Microsoft\PowerToys\$moduleName\settings.json" "$packageName\settings\$moduleName\" + +# Create README +@" +See README in the tools\module_loader folder for instructions +"@ | Out-File "$packageName\README.txt" + +# Zip it +Compress-Archive -Path $packageName -DestinationPath "$packageName.zip" +``` + +### Method 2: Direct Share (Advanced Users) +For developers who already have PowerToys installed: +```powershell +# Just share the executables +Copy-Item "x64\Debug\ModuleLoader.exe" "\\ShareLocation\" +Copy-Item "x64\Debug\CursorWrap.dll" "\\ShareLocation\" +``` + +They can run: `ModuleLoader.exe CursorWrap.dll` +(Settings will be loaded from their existing PowerToys installation) + +--- + +## Platform-Specific Notes + +### x64 vs ARM64 + +**Important**: Match architectures! +- `x64\Debug\ModuleLoader.exe` ? Only works with `x64` module DLLs +- `ARM64\Debug\ModuleLoader.exe` ? Only works with `ARM64` module DLLs + +**Distribution Tip**: Provide both architectures if targeting multiple platforms: +``` +ModuleLoaderPackage\ +??? x64\ +? ??? ModuleLoader.exe +? ??? modules\CursorWrap.dll +??? ARM64\ +? ??? ModuleLoader.exe +? ??? modules\CursorWrap.dll +??? settings\... +``` + +### Debug vs Release + +**Debug builds**: +- Larger file size +- Include debug symbols +- Verbose logging via `OutputDebugString()` +- Recommended for testing/development + +**Release builds**: +- Smaller file size +- Optimized performance +- Minimal logging +- Recommended for end-user testing + +--- + +## Testing Checklist + +Before sharing a module package: + +- [ ] ModuleLoader.exe is included +- [ ] Module DLL is included (matching architecture) +- [ ] Sample settings.json is included +- [ ] README/instructions are included +- [ ] Tested on a clean machine (no PowerToys installed) +- [ ] Verified hotkeys work +- [ ] Verified Ctrl+C exits cleanly +- [ ] Confirmed settings path in documentation + +--- + +## Advanced: Portable Package Script + +Here's a complete PowerShell script to create a fully portable package: + +```powershell +param( + [Parameter(Mandatory=$true)] + [string]$ModuleName, + + [ValidateSet("Debug", "Release")] + [string]$Configuration = "Debug", + + [ValidateSet("x64", "ARM64")] + [string]$Platform = "x64" +) + +$packageName = "ModuleLoader-$ModuleName-$Platform-$Configuration" +$packagePath = ".\$packageName" + +Write-Host "Creating portable package: $packageName" -ForegroundColor Green + +# Create structure +New-Item $packagePath -ItemType Directory -Force | Out-Null +New-Item "$packagePath\modules" -ItemType Directory -Force | Out-Null +New-Item "$packagePath\settings\$ModuleName" -ItemType Directory -Force | Out-Null + +# Copy ModuleLoader +$loaderPath = "$Platform\$Configuration\ModuleLoader.exe" +if (Test-Path $loaderPath) { + Copy-Item $loaderPath "$packagePath\" + Write-Host "? Copied ModuleLoader.exe" -ForegroundColor Green +} else { + Write-Host "? ModuleLoader.exe not found at $loaderPath" -ForegroundColor Red + exit 1 +} + +# Copy Module DLL +$modulePath = "$Platform\$Configuration\$ModuleName.dll" +if (Test-Path $modulePath) { + Copy-Item $modulePath "$packagePath\modules\" + Write-Host "? Copied $ModuleName.dll" -ForegroundColor Green +} else { + Write-Host "? $ModuleName.dll not found at $modulePath" -ForegroundColor Red + exit 1 +} + +# Copy Settings +$settingsPath = "$env:LOCALAPPDATA\Microsoft\PowerToys\$ModuleName\settings.json" +if (Test-Path $settingsPath) { + Copy-Item $settingsPath "$packagePath\settings\$ModuleName\" + Write-Host "? Copied settings.json" -ForegroundColor Green +} else { + Write-Host "? Settings not found at $settingsPath - creating placeholder" -ForegroundColor Yellow + @" +{ + "name": "$ModuleName", + "version": "1.0" +} +"@ | Out-File "$packagePath\settings\$ModuleName\settings.json" +} + +# Create README +@" +PowerToys $ModuleName Testing Package +====================================== + +Configuration: $Configuration +Platform: $Platform + +Setup Instructions: +------------------- +1. Copy the 'settings\$ModuleName' folder to: + %LOCALAPPDATA%\Microsoft\PowerToys\ + +2. Run: + ModuleLoader.exe modules\$ModuleName.dll + +3. Press Ctrl+C to exit + +Logs are written to: + %LOCALAPPDATA%\Microsoft\PowerToys\$ModuleName\Logs\ + +For more information, see: + https://github.com/microsoft/PowerToys/tree/main/tools/module_loader +"@ | Out-File "$packagePath\README.txt" + +# Create ZIP +$zipPath = "$packageName.zip" +Compress-Archive -Path $packagePath -DestinationPath $zipPath -Force +Write-Host "? Created $zipPath" -ForegroundColor Green + +# Show summary +Write-Host "`nPackage Contents:" -ForegroundColor Cyan +Get-ChildItem $packagePath -Recurse | ForEach-Object { + Write-Host " $($_.FullName.Replace($packagePath, ''))" +} + +Write-Host "`nPackage ready: $zipPath" -ForegroundColor Green +Write-Host "Size: $([math]::Round((Get-Item $zipPath).Length / 1KB, 2)) KB" +``` + +**Usage**: +```powershell +.\CreateModulePackage.ps1 -ModuleName "CursorWrap" -Configuration Release -Platform x64 +``` + +--- + +## FAQ + +### Q: Can I share just ModuleLoader.exe and the module DLL? +**A**: Yes, but the recipient must have PowerToys installed (or manually create the settings file). + +### Q: Does the tester need PowerToys installed? +**A**: No, if you provide the complete package with settings. ModuleLoader is fully standalone. + +### Q: What if settings.json doesn't exist? +**A**: ModuleLoader will show an error. Either: +1. Run PowerToys once with the module enabled to generate settings +2. Manually create a minimal settings.json file +3. Include a sample settings.json in your package + +### Q: Can I test modules on a virtual machine? +**A**: Yes! This is a great use case. Just copy the package to the VM - no PowerToys installation needed. + +### Q: Do I need to include PDB files? +**A**: Only for debugging. For normal testing, just the EXE and DLL are sufficient. + +### Q: Can I distribute this to end users? +**A**: ModuleLoader is a **development/testing tool**, not intended for end-user distribution. For production use, direct users to install PowerToys. + +--- + +## Security Considerations + +When sharing module DLLs: + +1. **Verify Source**: Only share modules you built from trusted source code +2. **Scan for Malware**: Run antivirus scans on the package before sharing +3. **HTTPS Only**: Use secure channels (HTTPS, OneDrive, SharePoint) for distribution +4. **Hash Verification**: Consider providing SHA256 hashes for file integrity: + ```powershell + Get-FileHash ModuleLoader.exe -Algorithm SHA256 + Get-FileHash modules\CursorWrap.dll -Algorithm SHA256 + ``` + +--- + +## Example Package (CursorWrap) + +Here's what a complete CursorWrap testing package looks like: + +``` +ModuleLoader-CursorWrap-x64-Debug.zip (220 KB) +? +??? ModuleLoader-CursorWrap-x64-Debug\ + ??? ModuleLoader.exe (160 KB) + ??? README.txt (2 KB) + ??? modules\ + ? ??? CursorWrap.dll (55 KB) + ??? settings\ + ??? CursorWrap\ + ??? settings.json (3 KB) +``` + +**Total package size**: ~220 KB (compressed) + +--- + +## Support + +For issues with ModuleLoader, see: +- [ModuleLoader README](./README.md) +- [PowerToys Documentation](https://aka.ms/PowerToysOverview) +- [PowerToys GitHub Issues](https://github.com/microsoft/PowerToys/issues) + +--- + +## License + +ModuleLoader is part of PowerToys and is licensed under the MIT License. +See the LICENSE file in the PowerToys repository root for details. diff --git a/tools/module_loader/src/ConsoleHost.cpp b/tools/module_loader/src/ConsoleHost.cpp new file mode 100644 index 0000000000..1ab2cdefa2 --- /dev/null +++ b/tools/module_loader/src/ConsoleHost.cpp @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "ConsoleHost.h" +#include + +bool ConsoleHost::s_exitRequested = false; + +ConsoleHost::ConsoleHost(ModuleLoader& moduleLoader, HotkeyManager& hotkeyManager) + : m_moduleLoader(moduleLoader) + , m_hotkeyManager(hotkeyManager) +{ +} + +ConsoleHost::~ConsoleHost() +{ +} + +BOOL WINAPI ConsoleHost::ConsoleCtrlHandler(DWORD ctrlType) +{ + switch (ctrlType) + { + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + case CTRL_CLOSE_EVENT: + std::wcout << L"\nCtrl+C received, shutting down...\n"; + s_exitRequested = true; + + // Post a quit message to break the message loop + PostQuitMessage(0); + return TRUE; + + default: + return FALSE; + } +} + +void ConsoleHost::Run() +{ + // Install console control handler + if (!SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE)) + { + std::wcerr << L"Warning: Failed to set console control handler\n"; + } + + s_exitRequested = false; + + // Message loop + MSG msg; + while (!s_exitRequested) + { + // Wait for a message with a timeout so we can check s_exitRequested + DWORD result = MsgWaitForMultipleObjects(0, nullptr, FALSE, 100, QS_ALLINPUT); + + if (result == WAIT_OBJECT_0) + { + // Process all pending messages + while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) + { + if (msg.message == WM_QUIT) + { + s_exitRequested = true; + break; + } + + if (msg.message == WM_HOTKEY) + { + m_hotkeyManager.HandleHotkey(static_cast(msg.wParam), m_moduleLoader); + } + + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + } + + // Remove console control handler + SetConsoleCtrlHandler(ConsoleCtrlHandler, FALSE); +} diff --git a/tools/module_loader/src/ConsoleHost.h b/tools/module_loader/src/ConsoleHost.h new file mode 100644 index 0000000000..153fdaa0f0 --- /dev/null +++ b/tools/module_loader/src/ConsoleHost.h @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#pragma once + +#include +#include "ModuleLoader.h" +#include "HotkeyManager.h" + +/// +/// Console host that runs the message loop and handles Ctrl+C +/// +class ConsoleHost +{ +public: + ConsoleHost(ModuleLoader& moduleLoader, HotkeyManager& hotkeyManager); + ~ConsoleHost(); + + // Prevent copying + ConsoleHost(const ConsoleHost&) = delete; + ConsoleHost& operator=(const ConsoleHost&) = delete; + + /// + /// Run the message loop until Ctrl+C is pressed + /// + void Run(); + +private: + ModuleLoader& m_moduleLoader; + HotkeyManager& m_hotkeyManager; + static bool s_exitRequested; + + /// + /// Console control handler (for Ctrl+C) + /// + static BOOL WINAPI ConsoleCtrlHandler(DWORD ctrlType); +}; diff --git a/tools/module_loader/src/HotkeyManager.cpp b/tools/module_loader/src/HotkeyManager.cpp new file mode 100644 index 0000000000..ce0ced5a03 --- /dev/null +++ b/tools/module_loader/src/HotkeyManager.cpp @@ -0,0 +1,279 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "HotkeyManager.h" +#include +#include + +HotkeyManager::HotkeyManager() + : m_nextHotkeyId(1) // Start from 1 + , m_hotkeyExRegistered(false) + , m_hotkeyExId(0) +{ +} + +HotkeyManager::~HotkeyManager() +{ + UnregisterAll(); +} + +UINT HotkeyManager::ConvertModifiers(bool win, bool ctrl, bool alt, bool shift) const +{ + UINT modifiers = MOD_NOREPEAT; // Prevent repeat events + if (win) modifiers |= MOD_WIN; + if (ctrl) modifiers |= MOD_CONTROL; + if (alt) modifiers |= MOD_ALT; + if (shift) modifiers |= MOD_SHIFT; + return modifiers; +} + +bool HotkeyManager::RegisterModuleHotkeys(ModuleLoader& moduleLoader) +{ + if (!moduleLoader.IsLoaded()) + { + std::wcerr << L"Error: Module not loaded\n"; + return false; + } + + bool anyRegistered = false; + + // First, try the newer GetHotkeyEx() API + auto hotkeyEx = moduleLoader.GetHotkeyEx(); + if (hotkeyEx.has_value()) + { + std::wcout << L"Module has HotkeyEx activation hotkey\n"; + + UINT modifiers = hotkeyEx->modifiersMask | MOD_NOREPEAT; + UINT vkCode = hotkeyEx->vkCode; + + if (vkCode != 0) + { + int hotkeyId = m_nextHotkeyId++; + + std::wcout << L" Registering HotkeyEx: "; + std::wcout << ModifiersToString(modifiers) << L"+" << VKeyToString(vkCode); + + if (RegisterHotKey(nullptr, hotkeyId, modifiers, vkCode)) + { + m_hotkeyExRegistered = true; + m_hotkeyExId = hotkeyId; + + std::wcout << L" - OK (Activation/Toggle)\n"; + anyRegistered = true; + } + else + { + DWORD error = GetLastError(); + std::wcout << L" - FAILED (Error: " << error << L")\n"; + + if (error == ERROR_HOTKEY_ALREADY_REGISTERED) + { + std::wcout << L" (Hotkey is already registered by another application)\n"; + } + } + } + } + + // Also check the legacy get_hotkeys() API + size_t hotkeyCount = moduleLoader.GetHotkeys(nullptr, 0); + if (hotkeyCount > 0) + { + std::wcout << L"Module reports " << hotkeyCount << L" legacy hotkey(s)\n"; + + // Allocate buffer and get the hotkeys + std::vector hotkeys(hotkeyCount); + size_t actualCount = moduleLoader.GetHotkeys(hotkeys.data(), hotkeyCount); + + // Register each hotkey + for (size_t i = 0; i < actualCount; i++) + { + const auto& hotkey = hotkeys[i]; + + UINT modifiers = ConvertModifiers(hotkey.win, hotkey.ctrl, hotkey.alt, hotkey.shift); + UINT vkCode = hotkey.key; + + if (vkCode == 0) + { + std::wcout << L" Skipping hotkey " << i << L" (no key code)\n"; + continue; + } + + int hotkeyId = m_nextHotkeyId++; + + std::wcout << L" Registering hotkey " << i << L": "; + std::wcout << ModifiersToString(modifiers) << L"+" << VKeyToString(vkCode); + + if (RegisterHotKey(nullptr, hotkeyId, modifiers, vkCode)) + { + HotkeyInfo info; + info.id = hotkeyId; + info.moduleHotkeyId = i; + info.modifiers = modifiers; + info.vkCode = vkCode; + info.description = ModifiersToString(modifiers) + L"+" + VKeyToString(vkCode); + + m_registeredHotkeys.push_back(info); + std::wcout << L" - OK\n"; + anyRegistered = true; + } + else + { + DWORD error = GetLastError(); + std::wcout << L" - FAILED (Error: " << error << L")\n"; + + if (error == ERROR_HOTKEY_ALREADY_REGISTERED) + { + std::wcout << L" (Hotkey is already registered by another application)\n"; + } + } + } + } + + if (!anyRegistered && hotkeyCount == 0 && !hotkeyEx.has_value()) + { + std::wcout << L"Module has no hotkeys\n"; + } + + return anyRegistered; +} + +void HotkeyManager::UnregisterAll() +{ + for (const auto& hotkey : m_registeredHotkeys) + { + UnregisterHotKey(nullptr, hotkey.id); + } + m_registeredHotkeys.clear(); + + if (m_hotkeyExRegistered) + { + UnregisterHotKey(nullptr, m_hotkeyExId); + m_hotkeyExRegistered = false; + m_hotkeyExId = 0; + } +} + +bool HotkeyManager::HandleHotkey(int hotkeyId, ModuleLoader& moduleLoader) +{ + // Check if it's the HotkeyEx activation hotkey + if (m_hotkeyExRegistered && hotkeyId == m_hotkeyExId) + { + std::wcout << L"\nActivation hotkey triggered (HotkeyEx)\n"; + + moduleLoader.OnHotkeyEx(); + + std::wcout << L"Module toggled via activation hotkey\n"; + std::wcout << L"Module enabled: " << (moduleLoader.IsEnabled() ? L"Yes" : L"No") << L"\n\n"; + + return true; + } + + // Check legacy hotkeys + for (const auto& hotkey : m_registeredHotkeys) + { + if (hotkey.id == hotkeyId) + { + std::wcout << L"\nHotkey triggered: " << hotkey.description << L"\n"; + + bool result = moduleLoader.OnHotkey(hotkey.moduleHotkeyId); + + std::wcout << L"Module handled hotkey: " << (result ? L"Swallowed" : L"Not swallowed") << L"\n"; + std::wcout << L"Module enabled: " << (moduleLoader.IsEnabled() ? L"Yes" : L"No") << L"\n\n"; + + return true; + } + } + + return false; +} + +void HotkeyManager::PrintHotkeys() const +{ + for (const auto& hotkey : m_registeredHotkeys) + { + std::wcout << L" " << hotkey.description << L"\n"; + } +} + +std::wstring HotkeyManager::ModifiersToString(UINT modifiers) const +{ + std::wstringstream ss; + bool first = true; + + if (modifiers & MOD_WIN) + { + if (!first) ss << L"+"; + ss << L"Win"; + first = false; + } + if (modifiers & MOD_CONTROL) + { + if (!first) ss << L"+"; + ss << L"Ctrl"; + first = false; + } + if (modifiers & MOD_ALT) + { + if (!first) ss << L"+"; + ss << L"Alt"; + first = false; + } + if (modifiers & MOD_SHIFT) + { + if (!first) ss << L"+"; + ss << L"Shift"; + first = false; + } + + return ss.str(); +} + +std::wstring HotkeyManager::VKeyToString(UINT vkCode) const +{ + // Handle special keys + switch (vkCode) + { + case VK_SPACE: return L"Space"; + case VK_RETURN: return L"Enter"; + case VK_ESCAPE: return L"Esc"; + case VK_TAB: return L"Tab"; + case VK_BACK: return L"Backspace"; + case VK_DELETE: return L"Del"; + case VK_INSERT: return L"Ins"; + case VK_HOME: return L"Home"; + case VK_END: return L"End"; + case VK_PRIOR: return L"PgUp"; + case VK_NEXT: return L"PgDn"; + case VK_LEFT: return L"Left"; + case VK_RIGHT: return L"Right"; + case VK_UP: return L"Up"; + case VK_DOWN: return L"Down"; + case VK_F1: return L"F1"; + case VK_F2: return L"F2"; + case VK_F3: return L"F3"; + case VK_F4: return L"F4"; + case VK_F5: return L"F5"; + case VK_F6: return L"F6"; + case VK_F7: return L"F7"; + case VK_F8: return L"F8"; + case VK_F9: return L"F9"; + case VK_F10: return L"F10"; + case VK_F11: return L"F11"; + case VK_F12: return L"F12"; + } + + // For alphanumeric keys, use MapVirtualKey + wchar_t keyName[256]; + UINT scanCode = MapVirtualKeyW(vkCode, MAPVK_VK_TO_VSC); + + if (GetKeyNameTextW(scanCode << 16, keyName, 256) > 0) + { + return keyName; + } + + // Fallback to hex code + std::wstringstream ss; + ss << L"0x" << std::hex << vkCode; + return ss.str(); +} diff --git a/tools/module_loader/src/HotkeyManager.h b/tools/module_loader/src/HotkeyManager.h new file mode 100644 index 0000000000..714e5a0962 --- /dev/null +++ b/tools/module_loader/src/HotkeyManager.h @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#pragma once + +#include +#include +#include +#include +#include "ModuleLoader.h" + +/// +/// Manages hotkey registration using RegisterHotKey API +/// +class HotkeyManager +{ +public: + HotkeyManager(); + ~HotkeyManager(); + + // Prevent copying + HotkeyManager(const HotkeyManager&) = delete; + HotkeyManager& operator=(const HotkeyManager&) = delete; + + /// + /// Register all hotkeys from a module + /// + /// Module to get hotkeys from + /// True if at least one hotkey was registered + bool RegisterModuleHotkeys(ModuleLoader& moduleLoader); + + /// + /// Unregister all hotkeys + /// + void UnregisterAll(); + + /// + /// Handle a WM_HOTKEY message + /// + /// ID from the WM_HOTKEY message + /// Module to trigger the hotkey on + /// True if the hotkey was handled + bool HandleHotkey(int hotkeyId, ModuleLoader& moduleLoader); + + /// + /// Get the number of registered hotkeys + /// + /// Number of registered hotkeys + size_t GetRegisteredCount() const { return m_registeredHotkeys.size() + (m_hotkeyExRegistered ? 1 : 0); } + + /// + /// Print registered hotkeys to console + /// + void PrintHotkeys() const; + +private: + struct HotkeyInfo + { + int id = 0; + size_t moduleHotkeyId = 0; + UINT modifiers = 0; + UINT vkCode = 0; + std::wstring description; + }; + + std::vector m_registeredHotkeys; + int m_nextHotkeyId; + bool m_hotkeyExRegistered; + int m_hotkeyExId; + + /// + /// Convert modifier bools to RegisterHotKey modifiers + /// + UINT ConvertModifiers(bool win, bool ctrl, bool alt, bool shift) const; + + /// + /// Get a string representation of modifiers + /// + std::wstring ModifiersToString(UINT modifiers) const; + + /// + /// Get a string representation of a virtual key code + /// + std::wstring VKeyToString(UINT vkCode) const; +}; diff --git a/tools/module_loader/src/ModuleLoader.cpp b/tools/module_loader/src/ModuleLoader.cpp new file mode 100644 index 0000000000..3334e2ab42 --- /dev/null +++ b/tools/module_loader/src/ModuleLoader.cpp @@ -0,0 +1,183 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "ModuleLoader.h" +#include +#include + +ModuleLoader::ModuleLoader() + : m_hModule(nullptr) + , m_module(nullptr) +{ +} + +ModuleLoader::~ModuleLoader() +{ + if (m_module) + { + try + { + m_module->destroy(); + } + catch (...) + { + // Ignore exceptions during cleanup + } + m_module = nullptr; + } + + if (m_hModule) + { + FreeLibrary(m_hModule); + m_hModule = nullptr; + } +} + +bool ModuleLoader::Load(const std::wstring& dllPath) +{ + if (m_hModule || m_module) + { + std::wcerr << L"Error: Module already loaded\n"; + return false; + } + + m_dllPath = dllPath; + + // Load the DLL + m_hModule = LoadLibraryW(dllPath.c_str()); + if (!m_hModule) + { + DWORD error = GetLastError(); + std::wcerr << L"Error: Failed to load DLL. Error code: " << error << L"\n"; + return false; + } + + // Get the powertoy_create function + using powertoy_create_func = PowertoyModuleIface* (*)(); + auto create_func = reinterpret_cast( + GetProcAddress(m_hModule, "powertoy_create")); + + if (!create_func) + { + std::wcerr << L"Error: DLL does not export 'powertoy_create' function\n"; + FreeLibrary(m_hModule); + m_hModule = nullptr; + return false; + } + + // Create the module instance + m_module = create_func(); + if (!m_module) + { + std::wcerr << L"Error: powertoy_create() returned nullptr\n"; + FreeLibrary(m_hModule); + m_hModule = nullptr; + return false; + } + + std::wcout << L"Module instance created successfully\n"; + return true; +} + +void ModuleLoader::Enable() +{ + if (!m_module) + { + throw std::runtime_error("Module not loaded"); + } + + m_module->enable(); +} + +void ModuleLoader::Disable() +{ + if (!m_module) + { + return; + } + + m_module->disable(); +} + +bool ModuleLoader::IsEnabled() const +{ + if (!m_module) + { + return false; + } + + return m_module->is_enabled(); +} + +void ModuleLoader::SetConfig(const std::wstring& configJson) +{ + if (!m_module) + { + throw std::runtime_error("Module not loaded"); + } + + m_module->set_config(configJson.c_str()); +} + +std::wstring ModuleLoader::GetModuleName() const +{ + if (!m_module) + { + return L""; + } + + const wchar_t* name = m_module->get_name(); + return name ? name : L""; +} + +std::wstring ModuleLoader::GetModuleKey() const +{ + if (!m_module) + { + return L""; + } + + const wchar_t* key = m_module->get_key(); + return key ? key : L""; +} + +size_t ModuleLoader::GetHotkeys(PowertoyModuleIface::Hotkey* buffer, size_t bufferSize) +{ + if (!m_module) + { + return 0; + } + + return m_module->get_hotkeys(buffer, bufferSize); +} + +bool ModuleLoader::OnHotkey(size_t hotkeyId) +{ + if (!m_module) + { + return false; + } + + return m_module->on_hotkey(hotkeyId); +} + +std::optional ModuleLoader::GetHotkeyEx() +{ + if (!m_module) + { + return std::nullopt; + } + + return m_module->GetHotkeyEx(); +} + +void ModuleLoader::OnHotkeyEx() +{ + if (!m_module) + { + return; + } + + m_module->OnHotkeyEx(); +} diff --git a/tools/module_loader/src/ModuleLoader.h b/tools/module_loader/src/ModuleLoader.h new file mode 100644 index 0000000000..5c155913f4 --- /dev/null +++ b/tools/module_loader/src/ModuleLoader.h @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#pragma once + +#include +#include +#include +#include + +/// +/// Wrapper class for loading and managing a PowerToy module DLL +/// +class ModuleLoader +{ +public: + ModuleLoader(); + ~ModuleLoader(); + + // Prevent copying + ModuleLoader(const ModuleLoader&) = delete; + ModuleLoader& operator=(const ModuleLoader&) = delete; + + /// + /// Load a PowerToy module DLL + /// + /// Path to the module DLL + /// True if successful, false otherwise + bool Load(const std::wstring& dllPath); + + /// + /// Enable the loaded module + /// + void Enable(); + + /// + /// Disable the loaded module + /// + void Disable(); + + /// + /// Check if the module is enabled + /// + /// True if enabled, false otherwise + bool IsEnabled() const; + + /// + /// Set configuration for the module + /// + /// JSON configuration string + void SetConfig(const std::wstring& configJson); + + /// + /// Get the module's localized name + /// + /// Module name + std::wstring GetModuleName() const; + + /// + /// Get the module's non-localized key + /// + /// Module key + std::wstring GetModuleKey() const; + + /// + /// Get the module's hotkeys + /// + /// Buffer to store hotkeys + /// Size of the buffer + /// Number of hotkeys returned + size_t GetHotkeys(PowertoyModuleIface::Hotkey* buffer, size_t bufferSize); + + /// + /// Trigger a hotkey callback on the module + /// + /// ID of the hotkey to trigger + /// True if the key press should be swallowed + bool OnHotkey(size_t hotkeyId); + + /// + /// Check if the module is loaded + /// + /// True if loaded, false otherwise + bool IsLoaded() const { return m_module != nullptr; } + + /// + /// Get the module's activation hotkey (newer HotkeyEx API) + /// + /// Optional HotkeyEx struct + std::optional GetHotkeyEx(); + + /// + /// Trigger the newer-style hotkey callback on the module + /// + void OnHotkeyEx(); + +private: + HMODULE m_hModule; + PowertoyModuleIface* m_module; + std::wstring m_dllPath; +}; diff --git a/tools/module_loader/src/SettingsLoader.cpp b/tools/module_loader/src/SettingsLoader.cpp new file mode 100644 index 0000000000..2d1c869ba1 --- /dev/null +++ b/tools/module_loader/src/SettingsLoader.cpp @@ -0,0 +1,182 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "SettingsLoader.h" +#include +#include +#include +#include +#include + +SettingsLoader::SettingsLoader() +{ +} + +SettingsLoader::~SettingsLoader() +{ +} + +std::wstring SettingsLoader::GetPowerToysSettingsRoot() const +{ + // Get %LOCALAPPDATA% + PWSTR localAppDataPath = nullptr; + HRESULT hr = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &localAppDataPath); + + if (FAILED(hr) || !localAppDataPath) + { + std::wcerr << L"Error: Failed to get LOCALAPPDATA path\n"; + return L""; + } + + std::wstring result(localAppDataPath); + CoTaskMemFree(localAppDataPath); + + // Append PowerToys directory + result += L"\\Microsoft\\PowerToys"; + return result; +} + +std::wstring SettingsLoader::GetSettingsPath(const std::wstring& moduleName) const +{ + std::wstring root = GetPowerToysSettingsRoot(); + if (root.empty()) + { + return L""; + } + + // Construct path: %LOCALAPPDATA%\Microsoft\PowerToys\\settings.json + std::wstring settingsPath = root + L"\\" + moduleName + L"\\settings.json"; + return settingsPath; +} + +std::wstring SettingsLoader::ReadFileContents(const std::wstring& filePath) const +{ + std::wifstream file(filePath, std::ios::binary); + if (!file.is_open()) + { + std::wcerr << L"Error: Could not open file: " << filePath << L"\n"; + return L""; + } + + // Read the entire file + std::wstringstream buffer; + buffer << file.rdbuf(); + + return buffer.str(); +} + +std::wstring SettingsLoader::LoadSettings(const std::wstring& moduleName, const std::wstring& moduleDllPath) +{ + const std::wstring powerToysPrefix = L"PowerToys."; + + // Build list of possible module name variations to try + std::vector moduleNameVariants; + + // Try exact name first + moduleNameVariants.push_back(moduleName); + + // If doesn't start with "PowerToys.", try adding it + if (moduleName.find(powerToysPrefix) != 0) + { + moduleNameVariants.push_back(powerToysPrefix + moduleName); + } + // If starts with "PowerToys.", try without it + else + { + moduleNameVariants.push_back(moduleName.substr(powerToysPrefix.length())); + } + + // FIRST: Try same directory as the module DLL + if (!moduleDllPath.empty()) + { + std::filesystem::path dllPath(moduleDllPath); + std::filesystem::path dllDirectory = dllPath.parent_path(); + + std::wstring localSettingsPath = (dllDirectory / L"settings.json").wstring(); + std::wcout << L"Trying settings path (module directory): " << localSettingsPath << L"\n"; + + if (std::filesystem::exists(localSettingsPath)) + { + std::wstring contents = ReadFileContents(localSettingsPath); + if (!contents.empty()) + { + std::wcout << L"Settings file loaded from module directory (" << contents.size() << L" characters)\n"; + return contents; + } + } + } + + // SECOND: Try standard PowerToys settings locations + for (const auto& variant : moduleNameVariants) + { + std::wstring settingsPath = GetSettingsPath(variant); + + std::wcout << L"Trying settings path: " << settingsPath << L"\n"; + + // Check if file exists (case-sensitive path) + if (std::filesystem::exists(settingsPath)) + { + std::wstring contents = ReadFileContents(settingsPath); + if (!contents.empty()) + { + std::wcout << L"Settings file loaded (" << contents.size() << L" characters)\n"; + return contents; + } + } + else + { + // Try case-insensitive search in the parent directory + std::wstring root = GetPowerToysSettingsRoot(); + if (!root.empty() && std::filesystem::exists(root)) + { + try + { + // Search for a directory that matches case-insensitively + for (const auto& entry : std::filesystem::directory_iterator(root)) + { + if (entry.is_directory()) + { + std::wstring dirName = entry.path().filename().wstring(); + + // Case-insensitive comparison + if (_wcsicmp(dirName.c_str(), variant.c_str()) == 0) + { + std::wstring actualSettingsPath = entry.path().wstring() + L"\\settings.json"; + std::wcout << L"Found case-insensitive match: " << actualSettingsPath << L"\n"; + + if (std::filesystem::exists(actualSettingsPath)) + { + std::wstring contents = ReadFileContents(actualSettingsPath); + if (!contents.empty()) + { + std::wcout << L"Settings file loaded (" << contents.size() << L" characters)\n"; + return contents; + } + } + } + } + } + } + catch (const std::filesystem::filesystem_error& e) + { + std::wcerr << L"Error searching directory: " << e.what() << L"\n"; + } + } + } + } + + std::wcerr << L"Error: Settings file not found in any expected location:\n"; + if (!moduleDllPath.empty()) + { + std::filesystem::path dllPath(moduleDllPath); + std::filesystem::path dllDirectory = dllPath.parent_path(); + std::wcerr << L" - " << (dllDirectory / L"settings.json").wstring() << L" (module directory)\n"; + } + for (const auto& variant : moduleNameVariants) + { + std::wcerr << L" - " << GetSettingsPath(variant) << L"\n"; + } + + return L""; +} diff --git a/tools/module_loader/src/SettingsLoader.h b/tools/module_loader/src/SettingsLoader.h new file mode 100644 index 0000000000..e005fdd4b2 --- /dev/null +++ b/tools/module_loader/src/SettingsLoader.h @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#pragma once + +#include +#include + +/// +/// Utility class for discovering and loading PowerToy module settings +/// +class SettingsLoader +{ +public: + SettingsLoader(); + ~SettingsLoader(); + + /// + /// Load settings for a PowerToy module + /// + /// Name of the module (e.g., "CursorWrap") + /// Full path to the module DLL (for checking local settings.json) + /// JSON settings string, or empty string if not found + std::wstring LoadSettings(const std::wstring& moduleName, const std::wstring& moduleDllPath); + + /// + /// Get the settings file path for a module + /// + /// Name of the module + /// Full path to the settings.json file + std::wstring GetSettingsPath(const std::wstring& moduleName) const; + +private: + /// + /// Get the PowerToys root settings directory + /// + /// Path to %LOCALAPPDATA%\Microsoft\PowerToys + std::wstring GetPowerToysSettingsRoot() const; + + /// + /// Read a text file into a string + /// + /// Path to the file + /// File contents as a string + std::wstring ReadFileContents(const std::wstring& filePath) const; +}; diff --git a/tools/module_loader/src/main.cpp b/tools/module_loader/src/main.cpp new file mode 100644 index 0000000000..fc9894e623 --- /dev/null +++ b/tools/module_loader/src/main.cpp @@ -0,0 +1,244 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include +#include +#include +#include +#include +#include "ModuleLoader.h" +#include "SettingsLoader.h" +#include "HotkeyManager.h" +#include "ConsoleHost.h" + +namespace +{ + void PrintUsage() + { + std::wcout << L"PowerToys Module Loader - Standalone utility for loading and testing PowerToy modules\n\n"; + std::wcout << L"Usage: ModuleLoader.exe \n\n"; + std::wcout << L"Arguments:\n"; + std::wcout << L" module_dll_path Path to the PowerToy module DLL (e.g., CursorWrap.dll)\n\n"; + std::wcout << L"Behavior:\n"; + std::wcout << L" - Automatically discovers settings from %%LOCALAPPDATA%%\\Microsoft\\PowerToys\\\\settings.json\n"; + std::wcout << L" - Loads and enables the module\n"; + std::wcout << L" - Registers module hotkeys\n"; + std::wcout << L" - Runs until Ctrl+C is pressed\n\n"; + std::wcout << L"Examples:\n"; + std::wcout << L" ModuleLoader.exe x64\\Debug\\modules\\CursorWrap.dll\n"; + std::wcout << L" ModuleLoader.exe \"C:\\Program Files\\PowerToys\\modules\\MouseHighlighter.dll\"\n\n"; + std::wcout << L"Notes:\n"; + std::wcout << L" - Only non-UI modules are supported\n"; + std::wcout << L" - Module must have a valid settings.json file\n"; + std::wcout << L" - Debug output is written to module's log directory\n"; + } + + std::wstring ExtractModuleName(const std::wstring& dllPath) + { + std::filesystem::path path(dllPath); + std::wstring filename = path.stem().wstring(); + + // Remove "PowerToys." prefix if present (case-insensitive) + const std::wstring powerToysPrefix = L"PowerToys."; + if (filename.length() >= powerToysPrefix.length()) + { + // Check if filename starts with "PowerToys." (case-insensitive) + if (_wcsnicmp(filename.c_str(), powerToysPrefix.c_str(), powerToysPrefix.length()) == 0) + { + filename = filename.substr(powerToysPrefix.length()); + } + } + + // Common PowerToys module naming patterns + // Remove common suffixes if present + const std::wstring suffixes[] = { L"Module", L"ModuleInterface", L"Interface" }; + for (const auto& suffix : suffixes) + { + if (filename.size() > suffix.size()) + { + size_t pos = filename.rfind(suffix); + if (pos != std::wstring::npos && pos + suffix.size() == filename.size()) + { + filename = filename.substr(0, pos); + break; + } + } + } + + return filename; + } +} + +int wmain(int argc, wchar_t* argv[]) +{ + std::wcout << L"PowerToys Module Loader v1.0\n"; + std::wcout << L"=============================\n\n"; + + // Check if PowerToys.exe is running + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hSnapshot != INVALID_HANDLE_VALUE) + { + PROCESSENTRY32W pe32; + pe32.dwSize = sizeof(PROCESSENTRY32W); + + bool powerToysRunning = false; + if (Process32FirstW(hSnapshot, &pe32)) + { + do + { + if (_wcsicmp(pe32.szExeFile, L"PowerToys.exe") == 0) + { + powerToysRunning = true; + break; + } + } while (Process32NextW(hSnapshot, &pe32)); + } + CloseHandle(hSnapshot); + + if (powerToysRunning) + { + // Display warning with VT100 colors + // Yellow background (43m), black text (30m), bold (1m) + std::wcout << L"\033[1;43;30m WARNING \033[0m PowerToys.exe is currently running!\n\n"; + + // Red text for important message + std::wcout << L"\033[1;31m"; + std::wcout << L"Running ModuleLoader while PowerToys is active may cause conflicts:\n"; + std::wcout << L" - Duplicate hotkey registrations\n"; + std::wcout << L" - Conflicting module instances\n"; + std::wcout << L" - Unexpected behavior\n"; + std::wcout << L"\033[0m\n"; // Reset color + + // Cyan text for recommendation + std::wcout << L"\033[1;36m"; + std::wcout << L"RECOMMENDATION: Exit PowerToys before continuing.\n"; + std::wcout << L"\033[0m\n"; // Reset color + + // Yellow text for prompt + std::wcout << L"\033[1;33m"; + std::wcout << L"Do you want to continue anyway? (y/N): "; + std::wcout << L"\033[0m"; // Reset color + + wchar_t response = L'\0'; + std::wcin >> response; + + if (response != L'y' && response != L'Y') + { + std::wcout << L"\nExiting. Please close PowerToys and try again.\n"; + return 1; + } + + std::wcout << L"\n"; + } + } + + // Parse command-line arguments + if (argc < 2) + { + std::wcerr << L"Error: Missing required argument \n\n"; + PrintUsage(); + return 1; + } + + const std::wstring dllPath = argv[1]; + + // Validate DLL exists + if (!std::filesystem::exists(dllPath)) + { + std::wcerr << L"Error: Module DLL not found: " << dllPath << L"\n"; + return 1; + } + + std::wcout << L"Loading module: " << dllPath << L"\n"; + + // Extract module name from DLL path + std::wstring moduleName = ExtractModuleName(dllPath); + std::wcout << L"Detected module name: " << moduleName << L"\n\n"; + + try + { + // Load settings for the module + std::wcout << L"Loading settings...\n"; + SettingsLoader settingsLoader; + std::wstring settingsJson = settingsLoader.LoadSettings(moduleName, dllPath); + + if (settingsJson.empty()) + { + std::wcerr << L"Error: Could not load settings for module '" << moduleName << L"'\n"; + std::wcerr << L"Expected location: %LOCALAPPDATA%\\Microsoft\\PowerToys\\" << moduleName << L"\\settings.json\n"; + return 1; + } + + std::wcout << L"Settings loaded successfully.\n\n"; + + // Load the module DLL + std::wcout << L"Loading module DLL...\n"; + ModuleLoader moduleLoader; + if (!moduleLoader.Load(dllPath)) + { + std::wcerr << L"Error: Failed to load module DLL\n"; + return 1; + } + + std::wcout << L"Module DLL loaded successfully.\n"; + std::wcout << L"Module key: " << moduleLoader.GetModuleKey() << L"\n"; + std::wcout << L"Module name: " << moduleLoader.GetModuleName() << L"\n\n"; + + // Apply settings to the module + std::wcout << L"Applying settings to module...\n"; + moduleLoader.SetConfig(settingsJson); + std::wcout << L"Settings applied.\n\n"; + + // Register hotkeys + std::wcout << L"Registering module hotkeys...\n"; + HotkeyManager hotkeyManager; + if (!hotkeyManager.RegisterModuleHotkeys(moduleLoader)) + { + std::wcerr << L"Warning: Failed to register some hotkeys\n"; + } + std::wcout << L"Hotkeys registered: " << hotkeyManager.GetRegisteredCount() << L"\n\n"; + + // Enable the module + std::wcout << L"Enabling module...\n"; + moduleLoader.Enable(); + std::wcout << L"Module enabled.\n\n"; + + // Display status + std::wcout << L"=============================\n"; + std::wcout << L"Module is now running!\n"; + std::wcout << L"=============================\n\n"; + std::wcout << L"Module Status:\n"; + std::wcout << L" - Name: " << moduleLoader.GetModuleName() << L"\n"; + std::wcout << L" - Key: " << moduleLoader.GetModuleKey() << L"\n"; + std::wcout << L" - Enabled: " << (moduleLoader.IsEnabled() ? L"Yes" : L"No") << L"\n"; + std::wcout << L" - Hotkeys: " << hotkeyManager.GetRegisteredCount() << L" registered\n\n"; + + if (hotkeyManager.GetRegisteredCount() > 0) + { + std::wcout << L"Registered Hotkeys:\n"; + hotkeyManager.PrintHotkeys(); + std::wcout << L"\n"; + } + + std::wcout << L"Press Ctrl+C to exit.\n"; + std::wcout << L"You can press the module's hotkey to toggle its functionality.\n\n"; + + // Run the message loop + ConsoleHost consoleHost(moduleLoader, hotkeyManager); + consoleHost.Run(); + + // Cleanup + std::wcout << L"\nShutting down...\n"; + moduleLoader.Disable(); + hotkeyManager.UnregisterAll(); + + std::wcout << L"Module unloaded successfully.\n"; + return 0; + } + catch (const std::exception& ex) + { + std::wcerr << L"Fatal error: " << ex.what() << L"\n"; + return 1; + } +} From 9160c82fc2181d1a29525a9f22baecd278228b52 Mon Sep 17 00:00:00 2001 From: Pratyush Nalam Date: Thu, 27 Nov 2025 05:15:06 +0530 Subject: [PATCH 23/23] Update Command Palette's Learn More string to be consistent with other utilities (#43898) ## Summary of the Pull Request In the PowerToys "What's new" window, every utility has the text "Learn more about " next to the "Settings" button. Examples below: learnmore-fancyzones learnmore-textextractor The only exception is the Command Palette utility which just says "Learn more". learnmore-cmdpal This is an inconsistency and this PR fixes that string. ## PR Checklist - [x] Closes: #43897 - [ ] **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 --- .../Settings.UI/Strings/en-us/Resources.resw | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw index a9c13d8128..7548d9da57 100644 --- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw @@ -1,17 +1,17 @@ - @@ -5164,7 +5164,7 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m Command Palette is a product name, do not loc - Learn more + Learn more about Command Palette Command Palette @@ -5781,4 +5781,4 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m A modern UI built with Fluent Design Fluent Design is a product name, do not loc - \ No newline at end of file +