mirror of
https://github.com/microsoft/PowerToys
synced 2025-08-29 05:27:43 +00:00
[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:  success to paste to chat:   <!-- 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:
parent
df8ace3ab6
commit
dd2e7d17f9
@ -8,6 +8,7 @@ using Microsoft.CmdPal.Common.Services;
|
||||
using Microsoft.CmdPal.Ext.Apps;
|
||||
using Microsoft.CmdPal.Ext.Bookmarks;
|
||||
using Microsoft.CmdPal.Ext.Calc;
|
||||
using Microsoft.CmdPal.Ext.ClipboardHistory;
|
||||
using Microsoft.CmdPal.Ext.Indexer;
|
||||
using Microsoft.CmdPal.Ext.Registry;
|
||||
using Microsoft.CmdPal.Ext.Shell;
|
||||
@ -100,8 +101,7 @@ public partial class App : Application
|
||||
services.AddSingleton<ICommandProvider, IndexerCommandsProvider>();
|
||||
services.AddSingleton<ICommandProvider, BookmarksCommandProvider>();
|
||||
|
||||
// TODO GH #527 re-enable the clipboard commands
|
||||
// services.AddSingleton<ICommandProvider, ClipboardHistoryCommandsProvider>();
|
||||
services.AddSingleton<ICommandProvider, ClipboardHistoryCommandsProvider>();
|
||||
services.AddSingleton<ICommandProvider, WindowWalkerCommandsProvider>();
|
||||
services.AddSingleton<ICommandProvider, WebSearchCommandsProvider>();
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -0,0 +1,74 @@
|
||||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.ClipboardHistory.Helpers;
|
||||
|
||||
public partial class ClipboardThreadQueue : IDisposable
|
||||
{
|
||||
private readonly Thread _thread;
|
||||
private readonly ConcurrentQueue<Action> _taskQueue = new ConcurrentQueue<Action>();
|
||||
private readonly AutoResetEvent _taskAvailable = new AutoResetEvent(false);
|
||||
private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource();
|
||||
|
||||
public ClipboardThreadQueue()
|
||||
{
|
||||
_thread = new Thread(() =>
|
||||
{
|
||||
var hr = NativeMethods.CoInitialize(IntPtr.Zero);
|
||||
if (hr != 0)
|
||||
{
|
||||
ExtensionHost.LogMessage($"CoInitialize failed with HRESULT: {hr}");
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
_taskAvailable.WaitOne();
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
while (_taskQueue.TryDequeue(out var task))
|
||||
{
|
||||
try
|
||||
{
|
||||
task();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExtensionHost.LogMessage($"Error executing task in ClipboardThreadQueue: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NativeMethods.CoUninitialize();
|
||||
});
|
||||
|
||||
_thread.SetApartmentState(ApartmentState.STA);
|
||||
_thread.IsBackground = true;
|
||||
_thread.Start();
|
||||
}
|
||||
|
||||
public void EnqueueTask(Action task)
|
||||
{
|
||||
_taskQueue.Enqueue(task);
|
||||
_taskAvailable.Set();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
cancellationToken.Cancel();
|
||||
_taskAvailable.Set();
|
||||
_thread.Join(); // Wait for the thread to finish processing tasks
|
||||
|
||||
_taskAvailable.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@ using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.ClipboardHistory.Helpers;
|
||||
|
||||
internal static class NativeMethods
|
||||
public static partial class NativeMethods
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching Native Structure")]
|
||||
@ -98,4 +98,10 @@ internal static class NativeMethods
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern bool GetCursorPos(out PointInter lpPoint);
|
||||
|
||||
[LibraryImport("ole32.dll")]
|
||||
internal static partial int CoInitialize(IntPtr pvReserved);
|
||||
|
||||
[LibraryImport("ole32.dll")]
|
||||
internal static partial void CoUninitialize();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user