[cmdpal] Re-enable Clipboard History extention (#39800)

<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Due to some windows sdk bugs, we can not use those API in main thread.
So, create a separate thread for clipboard.

history:

![image](https://github.com/user-attachments/assets/4da5a4eb-5d9d-475c-ab13-a2d585d2fffc)

success to paste to chat:

![image](https://github.com/user-attachments/assets/3fef43e5-4fc5-492c-b81e-599a9746d413)

![image](https://github.com/user-attachments/assets/1f4232bb-de76-40e5-96dd-43beb0ca8423)



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

- [x] **Closes:** #38344
- [ ] **Communication:** I've discussed this with core contributors
already. If work hasn't been agreed, this work might be rejected
- [x] **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

<!-- Provide a more detailed description of the PR, other things fixed
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
This commit is contained in:
Yu Leng
2025-06-05 15:23:16 +08:00
committed by GitHub
parent df8ace3ab6
commit dd2e7d17f9
4 changed files with 112 additions and 22 deletions

View File

@@ -30,6 +30,8 @@ internal static class ClipboardHelper
(StandardDataFormats.Bitmap, ClipboardFormat.Image),
];
private static readonly ClipboardThreadQueue ClipboardThreadQueue = new ClipboardThreadQueue();
internal static async Task<ClipboardFormat> GetAvailableClipboardFormatsAsync(DataPackageView clipboardData)
{
var availableClipboardFormats = DataFormats.Aggregate(
@@ -58,9 +60,12 @@ internal static class ClipboardHelper
try
{
// Clipboard.SetContentWithOptions(output, null);
Clipboard.SetContent(output);
Flush();
ExtensionHost.LogMessage(new LogMessage() { Message = "Copied text to clipboard" });
ClipboardThreadQueue.EnqueueTask(() =>
{
Clipboard.SetContent(output);
Flush();
ExtensionHost.LogMessage(new LogMessage() { Message = "Copied text to clipboard" });
});
}
catch (COMException ex)
{
@@ -74,27 +79,32 @@ internal static class ClipboardHelper
// TODO(stefan): For some reason Flush() fails from time to time when directly activated via hotkey.
// Calling inside a loop makes it work.
// Exception is: The operation is not permitted because the calling application is not the owner of the data on the clipboard.
const int maxAttempts = 5;
for (var i = 1; i <= maxAttempts; i++)
ClipboardThreadQueue.EnqueueTask(() =>
{
try
const int maxAttempts = 5;
for (var i = 1; i <= maxAttempts; i++)
{
Task.Run(Clipboard.Flush).Wait();
return true;
}
catch (Exception ex)
{
if (i == maxAttempts)
try
{
ExtensionHost.LogMessage(new LogMessage()
Task.Run(Clipboard.Flush).Wait();
return;
}
catch (Exception ex)
{
if (i == maxAttempts)
{
Message = $"{nameof(Clipboard)}.{nameof(Flush)}() failed: {ex}",
});
ExtensionHost.LogMessage(new LogMessage()
{
Message = $"{nameof(Clipboard)}.{nameof(Flush)}() failed: {ex}",
});
}
}
}
}
});
return false;
// We cannot get the real result of the Flush() call here, as it is executed in a different thread.
return true;
}
private static async Task<bool> FlushAsync() => await Task.Run(Flush);
@@ -105,7 +115,7 @@ internal static class ClipboardHelper
DataPackage output = new();
output.SetStorageItems([storageFile]);
Clipboard.SetContent(output);
ClipboardThreadQueue.EnqueueTask(() => Clipboard.SetContent(output));
await FlushAsync();
}
@@ -118,7 +128,7 @@ internal static class ClipboardHelper
{
DataPackage output = new();
output.SetBitmap(image);
Clipboard.SetContentWithOptions(output, null);
ClipboardThreadQueue.EnqueueTask(() => Clipboard.SetContentWithOptions(output, null));
Flush();
}