From e37a328624bfbe476cc9203859a61e1fb2d38d0e Mon Sep 17 00:00:00 2001 From: Shawn Yuan <128874481+shuaiyuanxx@users.noreply.github.com> Date: Mon, 15 Dec 2025 12:56:35 +0800 Subject: [PATCH 1/4] [Advanced Paste] Fixed custom hotkey issue (#44288) ## Summary of the Pull Request Fixed custom hotkey issue ## PR Checklist - [x] Closes: #43899 - [x] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx ## Detailed Description of the Pull Request / Additional comments ## Validation Steps Performed Signed-off-by: Shawn Yuan (from Dev Box) --- .../AdvancedPasteModuleInterface/dllmain.cpp | 53 ++++++++++--------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/dllmain.cpp b/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/dllmain.cpp index c7d22d474f..64caaa115f 100644 --- a/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/dllmain.cpp +++ b/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/dllmain.cpp @@ -312,13 +312,39 @@ private: return false; } - void read_settings(PowerToysSettings::PowerToyValues& settings) + void read_settings(PowerToysSettings::PowerToyValues& settings) { const auto settingsObject = settings.get_raw_json(); // Migrate Paste As Plain text shortcut Hotkey old_paste_as_plain_hotkey; bool old_data_migrated = migrate_data_and_remove_data_file(old_paste_as_plain_hotkey); + + if (settingsObject.GetView().Size()) + { + const auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES); + + m_is_advanced_ai_enabled = has_advanced_ai_provider(propertiesObject); + + if (propertiesObject.HasKey(JSON_KEY_IS_AI_ENABLED)) + { + m_is_ai_enabled = propertiesObject.GetNamedObject(JSON_KEY_IS_AI_ENABLED).GetNamedBoolean(JSON_KEY_VALUE, false); + } + else if (propertiesObject.HasKey(JSON_KEY_IS_OPEN_AI_ENABLED)) + { + m_is_ai_enabled = propertiesObject.GetNamedObject(JSON_KEY_IS_OPEN_AI_ENABLED).GetNamedBoolean(JSON_KEY_VALUE, false); + } + else + { + m_is_ai_enabled = false; + } + + if (propertiesObject.HasKey(JSON_KEY_SHOW_CUSTOM_PREVIEW)) + { + m_preview_custom_format_output = propertiesObject.GetNamedObject(JSON_KEY_SHOW_CUSTOM_PREVIEW).GetNamedBoolean(JSON_KEY_VALUE); + } + } + if (old_data_migrated) { m_paste_as_plain_hotkey = old_paste_as_plain_hotkey; @@ -405,31 +431,6 @@ private: } } } - - if (settingsObject.GetView().Size()) - { - const auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES); - - m_is_advanced_ai_enabled = has_advanced_ai_provider(propertiesObject); - - if (propertiesObject.HasKey(JSON_KEY_IS_AI_ENABLED)) - { - m_is_ai_enabled = propertiesObject.GetNamedObject(JSON_KEY_IS_AI_ENABLED).GetNamedBoolean(JSON_KEY_VALUE, false); - } - else if (propertiesObject.HasKey(JSON_KEY_IS_OPEN_AI_ENABLED)) - { - m_is_ai_enabled = propertiesObject.GetNamedObject(JSON_KEY_IS_OPEN_AI_ENABLED).GetNamedBoolean(JSON_KEY_VALUE, false); - } - else - { - m_is_ai_enabled = false; - } - - if (propertiesObject.HasKey(JSON_KEY_SHOW_CUSTOM_PREVIEW)) - { - m_preview_custom_format_output = propertiesObject.GetNamedObject(JSON_KEY_SHOW_CUSTOM_PREVIEW).GetNamedBoolean(JSON_KEY_VALUE); - } - } } // Load the settings file. From 4b2ee60b42401c9160668f9ae3f7c38e47da7566 Mon Sep 17 00:00:00 2001 From: leileizhang Date: Mon, 15 Dec 2025 17:07:09 +0800 Subject: [PATCH 2/4] Fix AdvancedPaste Gemini provider saving Azure placeholder endpoint (#44293) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary of the Pull Request When creating/editing a Paste AI provider in Settings, providers that don’t require an endpoint (e.g., Google/Gemini) could still end up persisting the Azure OpenAI placeholder (https://your-resource.openai.azure.com/) into settings.json. ### Fix: - On save, for service types that don’t use an endpoint, prevent placeholder/stale values from being persisted by forcing endpoint-url to be empty. - Reuse a single “requires endpoint” check so the dialog visibility and save behavior stay consistent. ## PR Checklist - [x] Closes: #44243 - [ ] **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 ## AI Generated Note: This pull request refactors and improves how endpoint handling is managed for different AI service types in the `AdvancedPastePage.xaml.cs` file. The changes centralize the logic for determining whether an endpoint is required, prevent persisting placeholder or stale endpoint values for services that do not use them, and ensure placeholder values are only provided where appropriate. **Refactoring and logic centralization:** * Introduced the `RequiresEndpointForService` helper method to centralize and clarify the logic for determining if a service type requires an endpoint, replacing inline checks in multiple places. [[1]](diffhunk://#diff-14126907329c7fcd49dd33bab32283296c7dd68ddc3902163a482a3b3ce58d36L317-R317) [[2]](diffhunk://#diff-14126907329c7fcd49dd33bab32283296c7dd68ddc3902163a482a3b3ce58d36R838-R845) **Improved endpoint value handling:** * Updated the dialog logic to ensure that endpoints are not persisted for services that do not require them, preventing storage of placeholder or irrelevant values. * Modified the `GetEndpointPlaceholder` method to return an empty string for service types that do not require an endpoint, rather than a generic placeholder. --- .../Views/AdvancedPastePage.xaml.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) 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 faf31b0d4d..130230e2d9 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml.cs @@ -314,10 +314,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views string selectedType = draft.ServiceType ?? string.Empty; AIServiceType serviceKind = draft.ServiceTypeKind; - bool requiresEndpoint = serviceKind is AIServiceType.AzureOpenAI - or AIServiceType.AzureAIInference - or AIServiceType.Mistral - or AIServiceType.Ollama; + bool requiresEndpoint = RequiresEndpointForService(serviceKind); bool requiresDeployment = serviceKind == AIServiceType.AzureOpenAI; bool requiresApiVersion = serviceKind == AIServiceType.AzureOpenAI; bool requiresModelPath = serviceKind == AIServiceType.Onnx; @@ -788,12 +785,17 @@ namespace Microsoft.PowerToys.Settings.UI.Views string serviceType = draft.ServiceType ?? "OpenAI"; string apiKey = PasteAIApiKeyPasswordBox.Password; string trimmedApiKey = apiKey?.Trim() ?? string.Empty; + var serviceKind = draft.ServiceTypeKind; + bool requiresEndpoint = RequiresEndpointForService(serviceKind); string endpoint = (draft.EndpointUrl ?? string.Empty).Trim(); - if (endpoint == string.Empty) + + // Never persist placeholder text or stale values for services that don't use an endpoint. + if (!requiresEndpoint) { - endpoint = GetEndpointPlaceholder(draft.ServiceTypeKind); + endpoint = string.Empty; } + // For endpoint-based services, keep empty if the user didn't provide a value. if (RequiresApiKeyForService(serviceType) && string.IsNullOrWhiteSpace(trimmedApiKey)) { args.Cancel = true; @@ -833,6 +835,14 @@ namespace Microsoft.PowerToys.Settings.UI.Views }; } + private static bool RequiresEndpointForService(AIServiceType serviceKind) + { + return serviceKind is AIServiceType.AzureOpenAI + or AIServiceType.AzureAIInference + or AIServiceType.Mistral + or AIServiceType.Ollama; + } + private static string GetEndpointPlaceholder(AIServiceType serviceKind) { return serviceKind switch @@ -841,7 +851,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views AIServiceType.AzureAIInference => "https://{resource-name}.cognitiveservices.azure.com/", AIServiceType.Mistral => "https://api.mistral.ai/v1/", AIServiceType.Ollama => "http://localhost:11434/", - _ => "https://your-resource.openai.azure.com/", + _ => string.Empty, }; } From 9aca6d136f7f9c301203905f3edf9a6f12cc8afe Mon Sep 17 00:00:00 2001 From: Gordon Lam <73506701+yeelam-gordon@users.noreply.github.com> Date: Tue, 16 Dec 2025 10:46:39 +0800 Subject: [PATCH 3/4] Revert "Revert commit" - Using centralized package management for vcxproj (#44289) Reverts microsoft/PowerToys#44208 Basically enable back: https://github.com/microsoft/PowerToys/pull/43920 the core change is adding this new Target to ensure when "building in Visual Studio", it will restore the nuget package first for vcxproj: ```xml ``` --- Cpp.Build.props | 11 ++- Directory.Build.targets | 16 ++++ Directory.Packages.props | 11 ++- .../PowerToys.MeasureToolCore.vcxproj | 68 ++++----------- .../MeasureToolCore/packages.config | 17 ---- .../MeasureToolUI/MeasureToolUI.csproj | 9 +- .../FindMyMouse/FindMyMouse.vcxproj | 82 ++++++------------- .../MouseUtils/FindMyMouse/packages.config | 12 --- src/modules/cmdpal/CoreCommonProps.props | 6 +- .../Microsoft.CmdPal.UI/CmdPal.Branding.props | 14 ++-- .../Microsoft.CmdPal.UI/CmdPal.pre.props | 2 +- .../Microsoft.CmdPal.UI.csproj | 27 +++--- .../FontIconGlyphClassifier.cpp | 6 +- .../Microsoft.Terminal.UI.vcxproj | 72 +++++----------- .../Microsoft.Terminal.UI/packages.config | 17 ---- .../PowerRenameUILib/PowerRenameUI.vcxproj | 82 +++++-------------- src/runner/packages.config | 16 ---- src/runner/runner.vcxproj | 76 ++++++----------- src/runner/runner.vcxproj.filters | 1 - 19 files changed, 179 insertions(+), 366 deletions(-) delete mode 100644 src/modules/MeasureTool/MeasureToolCore/packages.config delete mode 100644 src/modules/MouseUtils/FindMyMouse/packages.config delete mode 100644 src/modules/cmdpal/Microsoft.Terminal.UI/packages.config delete mode 100644 src/runner/packages.config diff --git a/Cpp.Build.props b/Cpp.Build.props index f146a4d770..7b988f0d6f 100644 --- a/Cpp.Build.props +++ b/Cpp.Build.props @@ -42,6 +42,11 @@ + + true + TurnOffAllWarnings + true + true Use pch.h @@ -111,13 +116,11 @@ - + true true - + false true false diff --git a/Directory.Build.targets b/Directory.Build.targets index 6da66bc8a8..f815cfbb3f 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -8,4 +8,20 @@ $(WindowsSdkDir)bin\x64\mt.exe + + + + + + + + \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props index 60567e30b8..eb04903b7e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,6 +7,8 @@ + + @@ -70,10 +72,12 @@ This is present due to a bug in CsWinRT where WPF projects cause the analyzer to fail. --> + - - - + + + + @@ -112,6 +116,7 @@ + diff --git a/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj b/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj index 6de7c50b55..c71c81acec 100644 --- a/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj +++ b/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj @@ -1,14 +1,16 @@  - - - - - - - - - + + + PackageReference + + + native,Version=v0.0 + + + Windows + $(WindowsTargetPlatformVersion) + true true @@ -31,6 +33,11 @@ true true + + + + + DynamicLibrary @@ -38,7 +45,6 @@ true - @@ -118,9 +124,6 @@ true - - - {caba8dfb-823b-4bf2-93ac-3f31984150d9} @@ -142,42 +145,5 @@ - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/src/modules/MeasureTool/MeasureToolCore/packages.config b/src/modules/MeasureTool/MeasureToolCore/packages.config deleted file mode 100644 index 6416ca5b16..0000000000 --- a/src/modules/MeasureTool/MeasureToolCore/packages.config +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/MeasureTool/MeasureToolUI/MeasureToolUI.csproj b/src/modules/MeasureTool/MeasureToolUI/MeasureToolUI.csproj index 434ff088b2..3e92bd42f3 100644 --- a/src/modules/MeasureTool/MeasureToolUI/MeasureToolUI.csproj +++ b/src/modules/MeasureTool/MeasureToolUI/MeasureToolUI.csproj @@ -73,6 +73,13 @@ - + + false + true + + + + PreserveNewest + diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj index d127de245e..bfed4af15d 100644 --- a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj @@ -1,13 +1,16 @@ - - - - - - - - + + + PackageReference + + + native,Version=v0.0 + + + Windows + $(WindowsTargetPlatformVersion) + 15.0 {e94fd11c-0591-456f-899f-efc0ca548336} @@ -20,9 +23,12 @@ false true false - - packages.config + + + + + DynamicLibrary @@ -127,18 +133,18 @@ - - - - - - - - - NotUsing - - + + + + + + + + NotUsing + + + <_ToDelete Include="$(OutDir)Microsoft.Web.WebView2.Core.dll" /> @@ -148,38 +154,4 @@ - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/MouseUtils/FindMyMouse/packages.config b/src/modules/MouseUtils/FindMyMouse/packages.config deleted file mode 100644 index cff3aa8705..0000000000 --- a/src/modules/MouseUtils/FindMyMouse/packages.config +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/src/modules/cmdpal/CoreCommonProps.props b/src/modules/cmdpal/CoreCommonProps.props index aa091d435e..438d044e2a 100644 --- a/src/modules/cmdpal/CoreCommonProps.props +++ b/src/modules/cmdpal/CoreCommonProps.props @@ -6,12 +6,12 @@ preview - $(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal\ + ..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\CmdPal\ false false $(RootNamespace).pri - + SA1313; @@ -42,5 +42,5 @@ Resources.Designer.cs - + diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/CmdPal.Branding.props b/src/modules/cmdpal/Microsoft.CmdPal.UI/CmdPal.Branding.props index d99688c081..298bcbd787 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/CmdPal.Branding.props +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/CmdPal.Branding.props @@ -24,7 +24,7 @@ - + true Assets\%(RecursiveDir)%(FileName)%(Extension) @@ -35,14 +35,10 @@ - - - - + + + + diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/CmdPal.pre.props b/src/modules/cmdpal/Microsoft.CmdPal.UI/CmdPal.pre.props index 21c2e7d8d1..d65b4a2cc2 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/CmdPal.pre.props +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/CmdPal.pre.props @@ -7,7 +7,7 @@ - $(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal + ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\CmdPal $(OutputPath)\AppPackages\ diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj b/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj index 7a413d316c..54961a5828 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj @@ -15,7 +15,7 @@ enable enable true - preview + preview $(CmdPalVersion) @@ -23,13 +23,14 @@ false false + true - + - + --> true @@ -45,7 +46,7 @@ - + Microsoft.Terminal.UI;CmdPalKeyboardService $(OutDir) @@ -101,7 +102,7 @@ - + all runtime; build; native; contentfiles; analyzers @@ -147,12 +148,16 @@ - - True - True - True + + False + True - + + + + + PreserveNewest + True True diff --git a/src/modules/cmdpal/Microsoft.Terminal.UI/FontIconGlyphClassifier.cpp b/src/modules/cmdpal/Microsoft.Terminal.UI/FontIconGlyphClassifier.cpp index bc3496a542..e6cb46457b 100644 --- a/src/modules/cmdpal/Microsoft.Terminal.UI/FontIconGlyphClassifier.cpp +++ b/src/modules/cmdpal/Microsoft.Terminal.UI/FontIconGlyphClassifier.cpp @@ -12,9 +12,9 @@ namespace winrt::Microsoft::Terminal::UI::implementation // Check if the code point is in the Private Use Area range used by Fluent UI icons. [[nodiscard]] constexpr bool _isFluentIconPua(const UChar32 cp) noexcept { - static constexpr UChar32 _fluentIconsPrivateUseAreaStart = 0xE700; - static constexpr UChar32 _fluentIconsPrivateUseAreaEnd = 0xF8FF; - return cp >= _fluentIconsPrivateUseAreaStart && cp <= _fluentIconsPrivateUseAreaEnd; + constexpr UChar32 fluentIconsPrivateUseAreaStart = 0xE700; + constexpr UChar32 fluentIconsPrivateUseAreaEnd = 0xF8FF; + return cp >= fluentIconsPrivateUseAreaStart && cp <= fluentIconsPrivateUseAreaEnd; } // Determine if the given text (as a sequence of UChar code units) is emoji diff --git a/src/modules/cmdpal/Microsoft.Terminal.UI/Microsoft.Terminal.UI.vcxproj b/src/modules/cmdpal/Microsoft.Terminal.UI/Microsoft.Terminal.UI.vcxproj index 6e474cf5f3..676d7297ba 100644 --- a/src/modules/cmdpal/Microsoft.Terminal.UI/Microsoft.Terminal.UI.vcxproj +++ b/src/modules/cmdpal/Microsoft.Terminal.UI/Microsoft.Terminal.UI.vcxproj @@ -1,17 +1,16 @@  - - - ..\..\..\..\ - $(PathToRoot)packages\Microsoft.WindowsAppSDK.1.8.250907003 + + + PackageReference + + native,Version=v0.0 + + Windows + $(WindowsTargetPlatformVersion) + + - - - - - - - true true @@ -28,6 +27,11 @@ 10.0.26100.0 10.0.19041.0 + + + + + @@ -47,10 +51,6 @@ x64 - - $(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal - obj\$(Platform)\$(Configuration)\ - DynamicLibrary v143 @@ -200,43 +200,11 @@ - + + ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\CmdPal + obj\$(Platform)\$(Configuration)\ + - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/src/modules/cmdpal/Microsoft.Terminal.UI/packages.config b/src/modules/cmdpal/Microsoft.Terminal.UI/packages.config deleted file mode 100644 index 2fb34c8fed..0000000000 --- a/src/modules/cmdpal/Microsoft.Terminal.UI/packages.config +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/powerrename/PowerRenameUILib/PowerRenameUI.vcxproj b/src/modules/powerrename/PowerRenameUILib/PowerRenameUI.vcxproj index eb21a94049..de71eb2188 100644 --- a/src/modules/powerrename/PowerRenameUILib/PowerRenameUI.vcxproj +++ b/src/modules/powerrename/PowerRenameUILib/PowerRenameUI.vcxproj @@ -1,18 +1,16 @@  - - - - - - - - - - - - - + + + PackageReference + + + native,Version=v0.0 + + + Windows + $(WindowsTargetPlatformVersion) + true true @@ -37,9 +35,17 @@ true PowerToys.PowerRename.pri + win10-x64;win10-arm64 + false + + + + + + + - Application v143 @@ -212,54 +218,10 @@ - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - - - - - - - - - - - - - - - - - - - + - \ No newline at end of file + diff --git a/src/runner/packages.config b/src/runner/packages.config deleted file mode 100644 index 74d5ef5747..0000000000 --- a/src/runner/packages.config +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/runner/runner.vcxproj b/src/runner/runner.vcxproj index c8cf2f2106..57cb55b6bd 100644 --- a/src/runner/runner.vcxproj +++ b/src/runner/runner.vcxproj @@ -1,27 +1,34 @@  - + 81010002 + + + PackageReference + + native,Version=v0.0 + + Windows + $(WindowsTargetPlatformVersion) + 15.0 {9412D5C6-2CF2-4FC2-A601-B55508EA9B27} powertoys runner + + + + + + - - - - - - - - Application @@ -31,10 +38,6 @@ true - - - - @@ -130,9 +133,6 @@ {17da04df-e393-4397-9cf0-84dabe11032e} - - - @@ -141,39 +141,15 @@ - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - - - - - - - - - - - - + + + + + + + + NotUsing + + \ No newline at end of file diff --git a/src/runner/runner.vcxproj.filters b/src/runner/runner.vcxproj.filters index d55e53048e..904e213405 100644 --- a/src/runner/runner.vcxproj.filters +++ b/src/runner/runner.vcxproj.filters @@ -118,7 +118,6 @@ - From 7b0b284d40a1f6b938695b4eee3cd0f09e668b22 Mon Sep 17 00:00:00 2001 From: Shawn Yuan <128874481+shuaiyuanxx@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:49:28 +0800 Subject: [PATCH 4/4] [Advanced Paste] Introduced image-input handling (#44021) ## Summary of the Pull Request This pull request introduces significant enhancements to the AdvancedPaste module, enabling AI-powered clipboard transformations to support both text and image data (notably for image analysis and transformation tasks), and improving error handling and clipboard tracking. The changes update the service interfaces, data models, and processing logic to handle images alongside text, and refine how the application responds to errors and clipboard state changes. image ## PR Checklist - [ ] Closes: #xxx - [x] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx ## Detailed Description of the Pull Request / Additional comments ## Validation Steps Performed --------- Signed-off-by: Shawn Yuan Signed-off-by: Shawn Yuan (from Dev Box) --- .../AIServiceBatchIntegrationTests.cs | 2 +- .../Helpers/DataPackageHelpers.cs | 18 +++++ .../AdvancedPaste/Helpers/NativeMethods.cs | 3 + .../AdvancedPaste/Models/PasteFormats.cs | 6 +- .../CustomActionTransformService.cs | 12 ++-- .../ICustomActionTransformService.cs | 2 +- .../Services/CustomActions/PasteAIRequest.cs | 4 ++ .../SemanticKernelPasteProvider.cs | 43 +++++++---- .../Services/KernelServiceBase.cs | 71 ++++++++++++++++--- .../Services/PasteFormatExecutor.cs | 2 +- .../ViewModels/OptionsViewModel.cs | 9 +++ 11 files changed, 139 insertions(+), 33 deletions(-) diff --git a/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/ServicesTests/AIServiceBatchIntegrationTests.cs b/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/ServicesTests/AIServiceBatchIntegrationTests.cs index 17b8139bad..1f7829a0bd 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/ServicesTests/AIServiceBatchIntegrationTests.cs +++ b/src/modules/AdvancedPaste/AdvancedPaste.UnitTests/ServicesTests/AIServiceBatchIntegrationTests.cs @@ -144,7 +144,7 @@ public sealed class AIServiceBatchIntegrationTests switch (format) { case PasteFormats.CustomTextTransformation: - var transformResult = await services.CustomActionTransformService.TransformTextAsync(batchTestInput.Prompt, batchTestInput.Clipboard, CancellationToken.None, progress); + var transformResult = await services.CustomActionTransformService.TransformAsync(batchTestInput.Prompt, batchTestInput.Clipboard, null, CancellationToken.None, progress); return DataPackageHelpers.CreateFromText(transformResult.Content ?? string.Empty); case PasteFormats.KernelQuery: diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/DataPackageHelpers.cs b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/DataPackageHelpers.cs index 2cd7554a50..f5439aecf1 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/DataPackageHelpers.cs +++ b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/DataPackageHelpers.cs @@ -225,6 +225,24 @@ internal static class DataPackageHelpers internal static async Task GetHtmlContentAsync(this DataPackageView dataPackageView) => dataPackageView.Contains(StandardDataFormats.Html) ? await dataPackageView.GetHtmlFormatAsync() : string.Empty; + internal static async Task GetImageAsPngBytesAsync(this DataPackageView dataPackageView) + { + var bitmap = await dataPackageView.GetImageContentAsync(); + if (bitmap == null) + { + return null; + } + + using var pngStream = new InMemoryRandomAccessStream(); + var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, pngStream); + encoder.SetSoftwareBitmap(bitmap); + await encoder.FlushAsync(); + + using var memoryStream = new MemoryStream(); + await pngStream.AsStreamForRead().CopyToAsync(memoryStream); + return memoryStream.ToArray(); + } + internal static async Task GetImageContentAsync(this DataPackageView dataPackageView) { using var stream = await dataPackageView.GetImageStreamAsync(); diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/NativeMethods.cs b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/NativeMethods.cs index 6e53e9b618..08293d4be0 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/NativeMethods.cs +++ b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/NativeMethods.cs @@ -166,5 +166,8 @@ namespace AdvancedPaste.Helpers [DllImport("Shlwapi.dll", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern HResult AssocQueryString(AssocF flags, AssocStr str, string pszAssoc, string pszExtra, [Out] StringBuilder pszOut, [In][Out] ref uint pcchOut); + + [DllImport("user32.dll", SetLastError = true)] + internal static extern uint GetClipboardSequenceNumber(); } } diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Models/PasteFormats.cs b/src/modules/AdvancedPaste/AdvancedPaste/Models/PasteFormats.cs index 99243ebb5e..1479912e66 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/Models/PasteFormats.cs +++ b/src/modules/AdvancedPaste/AdvancedPaste/Models/PasteFormats.cs @@ -46,7 +46,7 @@ public enum PasteFormats CanPreview = true, SupportedClipboardFormats = ClipboardFormat.Image, IPCKey = AdvancedPasteAdditionalActions.PropertyNames.ImageToText, - KernelFunctionDescription = "Takes an image in the clipboard and extracts all text from it using OCR.")] + KernelFunctionDescription = "Takes an image from the clipboard and extracts text using OCR. This function is intended only for explicit text extraction or OCR requests.")] ImageToText, [PasteFormatMetadata( @@ -118,8 +118,8 @@ public enum PasteFormats IconGlyph = "\uE945", RequiresAIService = true, CanPreview = true, - SupportedClipboardFormats = ClipboardFormat.Text, - KernelFunctionDescription = "Takes input instructions and transforms clipboard text (not TXT files) with these input instructions, putting the result back on the clipboard. This uses AI to accomplish the task.", + SupportedClipboardFormats = ClipboardFormat.Text | ClipboardFormat.Image, + KernelFunctionDescription = "Takes user instructions and applies them to the current clipboard content (text or image). Use this function for image analysis, description, or transformation tasks beyond simple OCR.", RequiresPrompt = true)] CustomTextTransformation, } diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/CustomActionTransformService.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/CustomActionTransformService.cs index 57d55492a4..05cdcbe81f 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/CustomActionTransformService.cs +++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/CustomActionTransformService.cs @@ -40,15 +40,15 @@ namespace AdvancedPaste.Services.CustomActions this.userSettings = userSettings; } - public async Task TransformTextAsync(string prompt, string inputText, CancellationToken cancellationToken, IProgress progress) + public async Task TransformAsync(string prompt, string inputText, byte[] imageBytes, CancellationToken cancellationToken, IProgress progress) { var pasteConfig = userSettings?.PasteAIConfiguration; var providerConfig = BuildProviderConfig(pasteConfig); - return await TransformAsync(prompt, inputText, providerConfig, cancellationToken, progress); + return await TransformAsync(prompt, inputText, imageBytes, providerConfig, cancellationToken, progress); } - private async Task TransformAsync(string prompt, string inputText, PasteAIConfig providerConfig, CancellationToken cancellationToken, IProgress progress) + private async Task TransformAsync(string prompt, string inputText, byte[] imageBytes, PasteAIConfig providerConfig, CancellationToken cancellationToken, IProgress progress) { ArgumentNullException.ThrowIfNull(providerConfig); @@ -57,9 +57,9 @@ namespace AdvancedPaste.Services.CustomActions return new CustomActionTransformResult(string.Empty, AIServiceUsage.None); } - if (string.IsNullOrWhiteSpace(inputText)) + if (string.IsNullOrWhiteSpace(inputText) && imageBytes is null) { - Logger.LogWarning("Clipboard has no usable text data"); + Logger.LogWarning("Clipboard has no usable data"); return new CustomActionTransformResult(string.Empty, AIServiceUsage.None); } @@ -80,6 +80,8 @@ namespace AdvancedPaste.Services.CustomActions { Prompt = prompt, InputText = inputText, + ImageBytes = imageBytes, + ImageMimeType = imageBytes != null ? "image/png" : null, SystemPrompt = systemPrompt, }; diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/ICustomActionTransformService.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/ICustomActionTransformService.cs index 1c3ecb980c..564db3fdc5 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/ICustomActionTransformService.cs +++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/ICustomActionTransformService.cs @@ -12,6 +12,6 @@ namespace AdvancedPaste.Services.CustomActions { public interface ICustomActionTransformService { - Task TransformTextAsync(string prompt, string inputText, CancellationToken cancellationToken, IProgress progress); + Task TransformAsync(string prompt, string inputText, byte[] imageBytes, CancellationToken cancellationToken, IProgress progress); } } diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/PasteAIRequest.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/PasteAIRequest.cs index 0e15c93e05..96dabbfa05 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/PasteAIRequest.cs +++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/PasteAIRequest.cs @@ -12,6 +12,10 @@ namespace AdvancedPaste.Services.CustomActions public string InputText { get; init; } + public byte[] ImageBytes { get; init; } + + public string ImageMimeType { get; init; } + public string SystemPrompt { get; init; } public AIServiceUsage Usage { get; set; } = AIServiceUsage.None; diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/SemanticKernelPasteProvider.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/SemanticKernelPasteProvider.cs index eb2f56e01f..636d2e3e78 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/SemanticKernelPasteProvider.cs +++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/CustomActions/SemanticKernelPasteProvider.cs @@ -64,21 +64,13 @@ namespace AdvancedPaste.Services.CustomActions var prompt = request.Prompt; var inputText = request.InputText; - if (string.IsNullOrWhiteSpace(prompt) || string.IsNullOrWhiteSpace(inputText)) + var imageBytes = request.ImageBytes; + + if (string.IsNullOrWhiteSpace(prompt) || (string.IsNullOrWhiteSpace(inputText) && imageBytes is null)) { - throw new ArgumentException("Prompt and input text must be provided", nameof(request)); + throw new ArgumentException("Prompt and input content must be provided", nameof(request)); } - var userMessageContent = $""" - User instructions: - {prompt} - - Clipboard Content: - {inputText} - - Output: - """; - var executionSettings = CreateExecutionSettings(); var kernel = CreateKernel(); var modelId = _config.Model; @@ -102,7 +94,32 @@ namespace AdvancedPaste.Services.CustomActions var chatHistory = new ChatHistory(); chatHistory.AddSystemMessage(systemPrompt); - chatHistory.AddUserMessage(userMessageContent); + + if (imageBytes != null) + { + var collection = new ChatMessageContentItemCollection(); + if (!string.IsNullOrWhiteSpace(inputText)) + { + collection.Add(new TextContent($"Clipboard Content:\n{inputText}")); + } + + collection.Add(new ImageContent(imageBytes, request.ImageMimeType ?? "image/png")); + collection.Add(new TextContent($"User instructions:\n{prompt}\n\nOutput:")); + chatHistory.AddUserMessage(collection); + } + else + { + var userMessageContent = $""" + User instructions: + {prompt} + + Clipboard Content: + {inputText} + + Output: + """; + chatHistory.AddUserMessage(userMessageContent); + } var response = await chatService.GetChatMessageContentAsync(chatHistory, executionSettings, kernel, cancellationToken); chatHistory.Add(response); diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/KernelServiceBase.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/KernelServiceBase.cs index 47e208eb49..0d753d1ec3 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/Services/KernelServiceBase.cs +++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/KernelServiceBase.cs @@ -67,12 +67,36 @@ public abstract class KernelServiceBase( LogResult(cacheUsed, isSavedQuery, kernel.GetOrAddActionChain(), usage); + var outputPackage = kernel.GetDataPackage(); + var hasUsableData = await outputPackage.GetView().HasUsableDataAsync(); + if (kernel.GetLastError() is Exception ex) { - throw ex; + // If we have an error, but the AI provided a final text response, we can ignore the error (likely a tool failure that the AI handled). + // However, if we have usable data (e.g. from a successful tool call before the error?), we might want to keep it? + // In the case of ImageToText failure, outputPackage is empty (new DataPackage), hasUsableData is false. + // So we check if there is a valid response in the chat history. + var lastMessage = chatHistory.LastOrDefault(); + bool hasAssistantResponse = lastMessage != null && lastMessage.Role == AuthorRole.Assistant && !string.IsNullOrEmpty(lastMessage.Content); + + if (!hasAssistantResponse && !hasUsableData) + { + throw ex; + } + + // If we have a response or data, we log the error but proceed. + Logger.LogWarning($"Kernel operation encountered an error but proceeded with available response/data: {ex.Message}"); } - var outputPackage = kernel.GetDataPackage(); + if (!hasUsableData) + { + var lastMessage = chatHistory.LastOrDefault(); + if (lastMessage != null && lastMessage.Role == AuthorRole.Assistant && !string.IsNullOrEmpty(lastMessage.Content)) + { + outputPackage = DataPackageHelpers.CreateFromText(lastMessage.Content); + kernel.SetDataPackage(outputPackage); + } + } if (!(await outputPackage.GetView().HasUsableDataAsync())) { @@ -148,7 +172,21 @@ public abstract class KernelServiceBase( var systemPrompt = string.IsNullOrWhiteSpace(runtimeConfig.SystemPrompt) ? DefaultSystemPrompt : runtimeConfig.SystemPrompt; chatHistory.AddSystemMessage(systemPrompt); chatHistory.AddSystemMessage($"Available clipboard formats: {await kernel.GetDataFormatsAsync()}"); - chatHistory.AddUserMessage(prompt); + + var imageBytes = await kernel.GetDataPackageView().GetImageAsPngBytesAsync(); + if (imageBytes != null) + { + var collection = new ChatMessageContentItemCollection + { + new TextContent(prompt), + new ImageContent(imageBytes, "image/png"), + }; + chatHistory.AddUserMessage(collection); + } + else + { + chatHistory.AddUserMessage(prompt); + } if (ShouldModerateAdvancedAI()) { @@ -302,8 +340,16 @@ public abstract class KernelServiceBase( new ActionChainItem(PasteFormats.CustomTextTransformation, Arguments: new() { { PromptParameterName, fixedPrompt } }), async dataPackageView => { - var input = await dataPackageView.GetClipboardTextOrThrowAsync(kernel.GetCancellationToken()); - var result = await _customActionTransformService.TransformTextAsync(fixedPrompt, input, kernel.GetCancellationToken(), kernel.GetProgress()); + var imageBytes = await dataPackageView.GetImageAsPngBytesAsync(); + var input = await dataPackageView.GetTextOrHtmlTextAsync(); + + if (string.IsNullOrEmpty(input) && imageBytes == null) + { + // If we have no text and no image, try to get text via OCR or throw if nothing exists + input = await dataPackageView.GetClipboardTextOrThrowAsync(kernel.GetCancellationToken()); + } + + var result = await _customActionTransformService.TransformAsync(fixedPrompt, input, imageBytes, kernel.GetCancellationToken(), kernel.GetProgress()); return DataPackageHelpers.CreateFromText(result?.Content ?? string.Empty); }); @@ -313,15 +359,22 @@ public abstract class KernelServiceBase( new ActionChainItem(format, Arguments: new() { { PromptParameterName, prompt } }), async dataPackageView => { - var input = await dataPackageView.GetClipboardTextOrThrowAsync(kernel.GetCancellationToken()); - string output = await GetPromptBasedOutput(format, prompt, input, kernel.GetCancellationToken(), kernel.GetProgress()); + var imageBytes = await dataPackageView.GetImageAsPngBytesAsync(); + var input = await dataPackageView.GetTextOrHtmlTextAsync(); + + if (string.IsNullOrEmpty(input) && imageBytes == null) + { + input = await dataPackageView.GetClipboardTextOrThrowAsync(kernel.GetCancellationToken()); + } + + string output = await GetPromptBasedOutput(format, prompt, input, imageBytes, kernel.GetCancellationToken(), kernel.GetProgress()); return DataPackageHelpers.CreateFromText(output); }); - private async Task GetPromptBasedOutput(PasteFormats format, string prompt, string input, CancellationToken cancellationToken, IProgress progress) => + private async Task GetPromptBasedOutput(PasteFormats format, string prompt, string input, byte[] imageBytes, CancellationToken cancellationToken, IProgress progress) => format switch { - PasteFormats.CustomTextTransformation => (await _customActionTransformService.TransformTextAsync(prompt, input, cancellationToken, progress))?.Content ?? string.Empty, + PasteFormats.CustomTextTransformation => (await _customActionTransformService.TransformAsync(prompt, input, imageBytes, cancellationToken, progress))?.Content ?? string.Empty, _ => throw new ArgumentException($"Unsupported format {format} for prompt transform", nameof(format)), }; diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Services/PasteFormatExecutor.cs b/src/modules/AdvancedPaste/AdvancedPaste/Services/PasteFormatExecutor.cs index aef9e39bb9..ff64a5ad83 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/Services/PasteFormatExecutor.cs +++ b/src/modules/AdvancedPaste/AdvancedPaste/Services/PasteFormatExecutor.cs @@ -37,7 +37,7 @@ public sealed class PasteFormatExecutor(IKernelService kernelService, ICustomAct pasteFormat.Format switch { PasteFormats.KernelQuery => await _kernelService.TransformClipboardAsync(pasteFormat.Prompt, clipboardData, pasteFormat.IsSavedQuery, cancellationToken, progress), - PasteFormats.CustomTextTransformation => DataPackageHelpers.CreateFromText((await _customActionTransformService.TransformTextAsync(pasteFormat.Prompt, await clipboardData.GetClipboardTextOrThrowAsync(cancellationToken), cancellationToken, progress))?.Content ?? string.Empty), + PasteFormats.CustomTextTransformation => DataPackageHelpers.CreateFromText((await _customActionTransformService.TransformAsync(pasteFormat.Prompt, await clipboardData.GetTextOrHtmlTextAsync(), await clipboardData.GetImageAsPngBytesAsync(), cancellationToken, progress))?.Content ?? string.Empty), _ => await TransformHelpers.TransformAsync(format, clipboardData, cancellationToken, progress), }); } diff --git a/src/modules/AdvancedPaste/AdvancedPaste/ViewModels/OptionsViewModel.cs b/src/modules/AdvancedPaste/AdvancedPaste/ViewModels/OptionsViewModel.cs index 8edd9b76ad..b055d46457 100644 --- a/src/modules/AdvancedPaste/AdvancedPaste/ViewModels/OptionsViewModel.cs +++ b/src/modules/AdvancedPaste/AdvancedPaste/ViewModels/OptionsViewModel.cs @@ -45,6 +45,7 @@ namespace AdvancedPaste.ViewModels private CancellationTokenSource _pasteActionCancellationTokenSource; private string _currentClipboardHistoryId; + private uint _lastClipboardSequenceNumber; private DateTimeOffset? _currentClipboardTimestamp; private ClipboardFormat _lastClipboardFormats = ClipboardFormat.None; private bool _clipboardHistoryUnavailableLogged; @@ -455,6 +456,7 @@ namespace AdvancedPaste.ViewModels { ResetClipboardPreview(); _currentClipboardHistoryId = null; + _lastClipboardSequenceNumber = 0; _currentClipboardTimestamp = null; _lastClipboardFormats = ClipboardFormat.None; return; @@ -477,6 +479,13 @@ namespace AdvancedPaste.ViewModels { bool clipboardChanged = formatsChanged; + var currentSequenceNumber = NativeMethods.GetClipboardSequenceNumber(); + if (_lastClipboardSequenceNumber != currentSequenceNumber) + { + clipboardChanged = true; + _lastClipboardSequenceNumber = currentSequenceNumber; + } + if (Clipboard.IsHistoryEnabled()) { try