feat(Window): Allow to persist content of Clipboard after App close (#16778)
* feat(Window): Allow to persist content of Clipboard after App close * fix: missing iOS Platform * fix(Api Validation): Missing suppression * Revert "fix(Api Validation): Missing suppression" This reverts commit 04b4e0c634ca70759969e098b581d7e1bbae802a. * fix: Address review * fix: ValidateApiDiff * test: Fix build issue --------- Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
This commit is contained in:
committed by
GitHub
parent
b815659435
commit
61de7e36b1
@@ -61,6 +61,12 @@
|
||||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
|
||||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0006</DiagnosticId>
|
||||
<Target>M:Avalonia.Input.Platform.IClipboard.FlushAsync</Target>
|
||||
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
|
||||
<Right>target/netstandard2.0/Avalonia.Base.dll</Right>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0006</DiagnosticId>
|
||||
<Target>M:Avalonia.Controls.Notifications.IManagedNotificationManager.Close(System.Object)</Target>
|
||||
|
||||
@@ -55,5 +55,9 @@ namespace Avalonia.Android.Platform
|
||||
public Task<string[]> GetFormatsAsync() => throw new PlatformNotSupportedException();
|
||||
|
||||
public Task<object?> GetDataAsync(string format) => throw new PlatformNotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task FlushAsync() =>
|
||||
Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,16 +6,48 @@ namespace Avalonia.Input.Platform
|
||||
[NotClientImplementable]
|
||||
public interface IClipboard
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a string containing the text data on the Clipboard.
|
||||
/// </summary>
|
||||
/// <returns>A string containing text data in the specified data format, or an empty string if no corresponding text data is available.</returns>
|
||||
Task<string?> GetTextAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Stores text data on the Clipboard. The text data to store is specified as a string.
|
||||
/// </summary>
|
||||
/// <param name="text">A string that contains the UnicodeText data to store on the Clipboard.</param>
|
||||
/// <exception cref="System.ArgumentNullException"><paramref name="text"/> is null.</exception>
|
||||
Task SetTextAsync(string? text);
|
||||
|
||||
/// <summary>
|
||||
/// Clears any data from the system Clipboard.
|
||||
/// </summary>
|
||||
Task ClearAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Places a specified non-persistent data object on the system Clipboard.
|
||||
/// </summary>
|
||||
/// <param name="data">A data object (an object that implements <see cref="IDataObject"/>) to place on the system Clipboard.</param>
|
||||
/// <exception cref="System.ArgumentNullException"><paramref name="data"/> is null.</exception>
|
||||
Task SetDataObjectAsync(IDataObject data);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Permanently adds the data that is on the Clipboard so that it is available after the data's original application closes.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks>This method works only on Windows platform, on other platforms it does nothing.</remarks>
|
||||
Task FlushAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Get list of available Clipboard format.
|
||||
/// </summary>
|
||||
Task<string[]> GetFormatsAsync();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves data in a specified format from the Clipboard.
|
||||
/// </summary>
|
||||
/// <param name="format">A string that specifies the format of the data to retrieve. For a set of predefined data formats, see the <see cref="DataFormats"/> class.</param>
|
||||
/// <returns></returns>
|
||||
Task<object?> GetDataAsync(string format);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,6 +225,9 @@ namespace Avalonia.DesignerSupport.Remote
|
||||
public Task<string[]> GetFormatsAsync() => Task.FromResult(Array.Empty<string>());
|
||||
|
||||
public Task<object> GetDataAsync(string format) => Task.FromResult((object)null);
|
||||
|
||||
public Task FlushAsync() =>
|
||||
Task.CompletedTask;
|
||||
}
|
||||
|
||||
class CursorFactoryStub : ICursorFactory
|
||||
|
||||
@@ -182,6 +182,10 @@ namespace Avalonia.Native
|
||||
using (var n = Native.GetBytes(format))
|
||||
return n.Bytes;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task FlushAsync() =>
|
||||
Task.CompletedTask;
|
||||
}
|
||||
|
||||
class ClipboardDataObject : IDataObject, IDisposable
|
||||
|
||||
@@ -310,7 +310,7 @@ namespace Avalonia.X11
|
||||
public Task SetDataObjectAsync(IDataObject data)
|
||||
{
|
||||
_storedDataObject = data;
|
||||
XSetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD, _handle, IntPtr.Zero);
|
||||
XSetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD, _handle, IntPtr.Zero);
|
||||
return StoreAtomsInClipboardManager(data);
|
||||
}
|
||||
|
||||
@@ -350,5 +350,9 @@ namespace Avalonia.X11
|
||||
|
||||
return await SendDataRequest(formatAtom);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task FlushAsync() =>
|
||||
Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,5 +25,9 @@ namespace Avalonia.Browser
|
||||
public Task<string[]> GetFormatsAsync() => Task.FromResult(Array.Empty<string>());
|
||||
|
||||
public Task<object?> GetDataAsync(string format) => Task.FromResult<object?>(null);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task FlushAsync() =>
|
||||
Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,18 +6,12 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Platform;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Input.Platform;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Fonts;
|
||||
using Avalonia.Media.TextFormatting;
|
||||
using Avalonia.Media.TextFormatting.Unicode;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Platform.Storage.FileIO;
|
||||
using Avalonia.Utilities;
|
||||
|
||||
namespace Avalonia.Headless
|
||||
{
|
||||
@@ -82,6 +76,10 @@ namespace Avalonia.Headless
|
||||
return (object?)_data;
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task FlushAsync() =>
|
||||
Task.CompletedTask;
|
||||
}
|
||||
|
||||
internal class HeadlessCursorFactoryStub : ICursorFactory
|
||||
|
||||
@@ -73,4 +73,8 @@ internal class NuiClipboardImpl : IClipboard
|
||||
|
||||
public Task<string[]> GetFormatsAsync() =>
|
||||
throw new PlatformNotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task FlushAsync() =>
|
||||
Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Avalonia.Reactive;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Input.Platform;
|
||||
using Avalonia.Reactive;
|
||||
using Avalonia.Threading;
|
||||
using Avalonia.Win32.Interop;
|
||||
using MicroCom.Runtime;
|
||||
@@ -15,6 +15,13 @@ namespace Avalonia.Win32
|
||||
{
|
||||
private const int OleRetryCount = 10;
|
||||
private const int OleRetryDelay = 100;
|
||||
/// <summary>
|
||||
/// The amount of time in milliseconds to sleep before flushing the clipboard after a set.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is mitigation for clipboard listener issues.
|
||||
/// </remarks>
|
||||
private const int OleFlushDelay = 10;
|
||||
|
||||
private static async Task<IDisposable> OpenClipboard()
|
||||
{
|
||||
@@ -32,7 +39,7 @@ namespace Avalonia.Win32
|
||||
|
||||
public async Task<string?> GetTextAsync()
|
||||
{
|
||||
using(await OpenClipboard())
|
||||
using (await OpenClipboard())
|
||||
{
|
||||
IntPtr hText = UnmanagedMethods.GetClipboardData(UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT);
|
||||
if (hText == IntPtr.Zero)
|
||||
@@ -54,7 +61,7 @@ namespace Avalonia.Win32
|
||||
|
||||
public async Task SetTextAsync(string? text)
|
||||
{
|
||||
using(await OpenClipboard())
|
||||
using (await OpenClipboard())
|
||||
{
|
||||
UnmanagedMethods.EmptyClipboard();
|
||||
|
||||
@@ -68,7 +75,7 @@ namespace Avalonia.Win32
|
||||
|
||||
public async Task ClearAsync()
|
||||
{
|
||||
using(await OpenClipboard())
|
||||
using (await OpenClipboard())
|
||||
{
|
||||
UnmanagedMethods.EmptyClipboard();
|
||||
}
|
||||
@@ -90,7 +97,7 @@ namespace Avalonia.Win32
|
||||
|
||||
if (--i == 0)
|
||||
Marshal.ThrowExceptionForHR(hr);
|
||||
|
||||
|
||||
await Task.Delay(OleRetryDelay);
|
||||
}
|
||||
}
|
||||
@@ -142,5 +149,34 @@ namespace Avalonia.Win32
|
||||
await Task.Delay(OleRetryDelay);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Permanently renders the contents of the last IDataObject that was set onto the clipboard.
|
||||
/// </summary>
|
||||
public async Task FlushAsync()
|
||||
{
|
||||
await Task.Delay(OleFlushDelay);
|
||||
|
||||
// Retry OLE operations several times as mitigation for clipboard locking issues in TS sessions.
|
||||
|
||||
int i = OleRetryCount;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var hr = UnmanagedMethods.OleFlushClipboard();
|
||||
|
||||
if (hr == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (--i == 0)
|
||||
{
|
||||
Marshal.ThrowExceptionForHR(hr);
|
||||
}
|
||||
|
||||
await Task.Delay(OleRetryDelay);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1573,6 +1573,8 @@ namespace Avalonia.Win32.Interop
|
||||
|
||||
[DllImport("ole32.dll", PreserveSig = true)]
|
||||
public static extern int OleGetClipboard(out IntPtr dataObject);
|
||||
[DllImport("ole32.dll", PreserveSig = true)]
|
||||
public static extern int OleFlushClipboard();
|
||||
|
||||
[DllImport("ole32.dll", PreserveSig = true)]
|
||||
public static extern int OleSetClipboard(IntPtr dataObject);
|
||||
|
||||
@@ -57,5 +57,9 @@ namespace Avalonia.iOS
|
||||
|
||||
return Task.FromResult<object?>(null);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task FlushAsync() =>
|
||||
Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -13,8 +12,6 @@ using Avalonia.Input.Platform;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Rendering;
|
||||
using Avalonia.Rendering.Composition;
|
||||
using Avalonia.UnitTests;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
@@ -906,7 +903,7 @@ namespace Avalonia.Controls.UnitTests
|
||||
topLevel.ApplyTemplate();
|
||||
topLevel.LayoutManager.ExecuteInitialLayoutPass();
|
||||
|
||||
var texts = new List<string>();
|
||||
var texts = new System.Collections.Generic.List<string>();
|
||||
|
||||
target.PropertyChanged += (_, e) =>
|
||||
{
|
||||
@@ -1028,6 +1025,9 @@ namespace Avalonia.Controls.UnitTests
|
||||
public Task<string[]> GetFormatsAsync() => Task.FromResult(Array.Empty<string>());
|
||||
|
||||
public Task<object> GetDataAsync(string format) => Task.FromResult((object)null);
|
||||
|
||||
public Task FlushAsync() =>
|
||||
Task.CompletedTask;
|
||||
}
|
||||
|
||||
private class TestTopLevel : TopLevel
|
||||
|
||||
@@ -1995,7 +1995,10 @@ namespace Avalonia.Controls.UnitTests
|
||||
|
||||
public Task<string[]> GetFormatsAsync() => Task.FromResult(Array.Empty<string>());
|
||||
|
||||
public Task<object?> GetDataAsync(string format) => Task.FromResult((object?)null);
|
||||
public Task<object> GetDataAsync(string format) => Task.FromResult((object)null);
|
||||
|
||||
public Task FlushAsync() =>
|
||||
Task.CompletedTask;
|
||||
}
|
||||
|
||||
private class TestTopLevel : TopLevel
|
||||
|
||||
Reference in New Issue
Block a user