Skip to content

Commit c3b4d61

Browse files
kekekeksmaxkatz6
andauthored
Added IClipboard:TryGetInProcessDataObjectAsyns (#18340)
* Implemented IClipboard.TryGetInProcessDataObjectAsync for X11 * [Win32] Implemented TryGetInProcessDataObjectAsync * Rest of the platforms * Run `nuke ValidateApiDiff` --------- Co-authored-by: Max Katz <[email protected]>
1 parent 07a7614 commit c3b4d61

File tree

19 files changed

+173
-105
lines changed

19 files changed

+173
-105
lines changed

api/Avalonia.nupkg.xml

+9-3
Original file line numberDiff line numberDiff line change
@@ -51,19 +51,25 @@
5151
</Suppression>
5252
<Suppression>
5353
<DiagnosticId>CP0006</DiagnosticId>
54-
<Target>M:Avalonia.Platform.Storage.IStorageFolder.GetFileAsync(System.String)</Target>
54+
<Target>M:Avalonia.Input.Platform.IClipboard.FlushAsync</Target>
5555
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
5656
<Right>target/netstandard2.0/Avalonia.Base.dll</Right>
5757
</Suppression>
5858
<Suppression>
5959
<DiagnosticId>CP0006</DiagnosticId>
60-
<Target>M:Avalonia.Platform.Storage.IStorageFolder.GetFolderAsync(System.String)</Target>
60+
<Target>M:Avalonia.Input.Platform.IClipboard.TryGetInProcessDataObjectAsync</Target>
6161
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
6262
<Right>target/netstandard2.0/Avalonia.Base.dll</Right>
6363
</Suppression>
6464
<Suppression>
6565
<DiagnosticId>CP0006</DiagnosticId>
66-
<Target>M:Avalonia.Input.Platform.IClipboard.FlushAsync</Target>
66+
<Target>M:Avalonia.Platform.Storage.IStorageFolder.GetFileAsync(System.String)</Target>
67+
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
68+
<Right>target/netstandard2.0/Avalonia.Base.dll</Right>
69+
</Suppression>
70+
<Suppression>
71+
<DiagnosticId>CP0006</DiagnosticId>
72+
<Target>M:Avalonia.Platform.Storage.IStorageFolder.GetFolderAsync(System.String)</Target>
6773
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
6874
<Right>target/netstandard2.0/Avalonia.Base.dll</Right>
6975
</Suppression>

native/Avalonia.Native/src/OSX/clipboard.mm

+16-2
Original file line numberDiff line numberDiff line change
@@ -148,24 +148,38 @@ virtual HRESULT GetBytes(char* type, IAvnString**ppv) override
148148
}
149149

150150

151-
virtual HRESULT Clear() override
151+
virtual HRESULT Clear(int64_t* rv) override
152152
{
153153
START_COM_CALL;
154154

155155
@autoreleasepool
156156
{
157157
if(_item != nil)
158+
{
158159
_item = [NSPasteboardItem new];
160+
return 0;
161+
}
159162
else
160163
{
161-
[_pb clearContents];
164+
*rv = [_pb clearContents];
162165
[_pb setString:@"" forType:NSPasteboardTypeString];
163166
}
164167

165168
return S_OK;
166169
}
167170
}
168171

172+
virtual HRESULT GetChangeCount(int64_t* rv) override
173+
{
174+
START_COM_CALL;
175+
if(_item == nil)
176+
{
177+
*rv = [_pb changeCount];
178+
return S_OK;
179+
}
180+
return E_NOTIMPL;
181+
}
182+
169183
virtual HRESULT ObtainFormats(IAvnStringArray** ppv) override
170184
{
171185
START_COM_CALL;

samples/ControlCatalog/Pages/ClipboardPage.xaml

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
<Button Click="GetFormats" Content="Get clipboard formats" />
1616
<Button Click="Clear" Content="Clear clipboard" />
1717

18+
<StackPanel Orientation="Horizontal">
19+
<TextBlock Padding="0 0 5 0">Our DataObject is still on clipboard? <Run x:Name="OwnsClipboardDataObject"/></TextBlock>
20+
</StackPanel>
1821
<TextBox x:Name="ClipboardContent"
1922
MinHeight="100"
2023
AcceptsReturn="True"

samples/ControlCatalog/Pages/ClipboardPage.xaml.cs

+44-2
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33
using System.Linq;
44
using Avalonia;
55
using Avalonia.Controls;
6+
using Avalonia.Controls.Documents;
67
using Avalonia.Controls.Notifications;
78
using Avalonia.Input;
9+
using Avalonia.Input.Platform;
810
using Avalonia.Interactivity;
911
using Avalonia.Markup.Xaml;
12+
using Avalonia.Media;
1013
using Avalonia.Platform;
1114
using Avalonia.Platform.Storage;
1215
using Avalonia.Platform.Storage.FileIO;
16+
using Avalonia.Threading;
1317

1418
namespace ControlCatalog.Pages
1519
{
@@ -18,8 +22,13 @@ public partial class ClipboardPage : UserControl
1822
private INotificationManager? _notificationManager;
1923
private INotificationManager NotificationManager => _notificationManager
2024
??= new WindowNotificationManager(TopLevel.GetTopLevel(this)!);
25+
26+
private readonly DispatcherTimer _clipboardLastDataObjectChecker;
27+
private DataObject? _storedDataObject;
2128
public ClipboardPage()
2229
{
30+
_clipboardLastDataObjectChecker =
31+
new DispatcherTimer(TimeSpan.FromSeconds(0.5), default, CheckLastDataObject);
2332
InitializeComponent();
2433
}
2534

@@ -48,7 +57,7 @@ private async void CopyTextDataObject(object? sender, RoutedEventArgs args)
4857
{
4958
if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
5059
{
51-
var dataObject = new DataObject();
60+
var dataObject = _storedDataObject = new DataObject();
5261
dataObject.Set(DataFormats.Text, ClipboardContent.Text ?? string.Empty);
5362
await clipboard.SetDataObjectAsync(dataObject);
5463
}
@@ -96,7 +105,7 @@ private async void CopyFilesDataObject(object? sender, RoutedEventArgs args)
96105

97106
if (files.Count > 0)
98107
{
99-
var dataObject = new DataObject();
108+
var dataObject = _storedDataObject = new DataObject();
100109
dataObject.Set(DataFormats.Files, files);
101110
await clipboard.SetDataObjectAsync(dataObject);
102111
NotificationManager.Show(new Notification("Success", "Copy completated.", NotificationType.Success));
@@ -135,5 +144,38 @@ private async void Clear(object sender, RoutedEventArgs args)
135144
}
136145

137146
}
147+
148+
149+
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
150+
{
151+
_clipboardLastDataObjectChecker.Start();
152+
base.OnAttachedToVisualTree(e);
153+
}
154+
155+
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
156+
{
157+
_clipboardLastDataObjectChecker.Stop();
158+
base.OnDetachedFromVisualTree(e);
159+
}
160+
161+
private Run OwnsClipboardDataObject => this.Get<Run>("OwnsClipboardDataObject");
162+
private bool _checkingClipboardDataObject;
163+
private async void CheckLastDataObject(object? sender, EventArgs e)
164+
{
165+
if(_checkingClipboardDataObject)
166+
return;
167+
try
168+
{
169+
_checkingClipboardDataObject = true;
170+
var task = TopLevel.GetTopLevel(this)?.Clipboard?.TryGetInProcessDataObjectAsync();
171+
var owns = task != null && (await task) == _storedDataObject && _storedDataObject != null;
172+
OwnsClipboardDataObject.Text = owns ? "Yes" : "No";
173+
OwnsClipboardDataObject.Foreground = owns ? Brushes.Green : Brushes.Red;
174+
}
175+
finally
176+
{
177+
_checkingClipboardDataObject = false;
178+
}
179+
}
138180
}
139181
}

src/Android/Avalonia.Android/Platform/ClipboardImpl.cs

+2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ public Task ClearAsync()
5656

5757
public Task<object?> GetDataAsync(string format) => throw new PlatformNotSupportedException();
5858

59+
public Task<IDataObject?> TryGetInProcessDataObjectAsync() => Task.FromResult<IDataObject?>(null);
60+
5961
/// <inheritdoc />
6062
public Task FlushAsync() =>
6163
Task.CompletedTask;

src/Avalonia.Base/Input/Platform/IClipboard.cs

+9
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,14 @@ public interface IClipboard
4949
/// <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>
5050
/// <returns></returns>
5151
Task<object?> GetDataAsync(string format);
52+
53+
/// <summary>
54+
/// If clipboard contains the IDataObject that was set by a previous call to <see cref="SetDataObjectAsync"/>,
55+
/// return said IDataObject instance. Otherwise, return null.
56+
/// Note that not every platform supports that method, on unsupported platforms this method will always return
57+
/// null
58+
/// </summary>
59+
/// <returns></returns>
60+
Task<IDataObject?> TryGetInProcessDataObjectAsync();
5261
}
5362
}

src/Avalonia.DesignerSupport/Remote/Stubs.cs

+2
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ class ClipboardStub : IClipboard
226226

227227
public Task<object> GetDataAsync(string format) => Task.FromResult((object)null);
228228

229+
public Task<IDataObject> TryGetInProcessDataObjectAsync() => Task.FromResult<IDataObject>(null);
230+
229231
public Task FlushAsync() =>
230232
Task.CompletedTask;
231233
}

src/Avalonia.Native/ClipboardImpl.cs

+22-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ namespace Avalonia.Native
1717
class ClipboardImpl : IClipboard, IDisposable
1818
{
1919
private IAvnClipboard? _native;
20+
private IDataObject? _savedDataObject;
21+
private long _lastClearChangeCount;
2022

2123
// TODO hide native types behind IAvnClipboard abstraction, so managed side won't depend on macOS.
2224
private const string NSPasteboardTypeString = "public.utf8-plain-text";
@@ -30,10 +32,15 @@ public ClipboardImpl(IAvnClipboard native)
3032
private IAvnClipboard Native
3133
=> _native ?? throw new ObjectDisposedException(nameof(ClipboardImpl));
3234

35+
private void ClearCore()
36+
{
37+
_savedDataObject = null;
38+
_lastClearChangeCount = Native.Clear();
39+
}
40+
3341
public Task ClearAsync()
3442
{
35-
Native.Clear();
36-
43+
ClearCore();
3744
return Task.CompletedTask;
3845
}
3946

@@ -47,7 +54,7 @@ public Task SetTextAsync(string? text)
4754
{
4855
var native = Native;
4956

50-
native.Clear();
57+
ClearCore();
5158

5259
if (text != null)
5360
native.SetText(NSPasteboardTypeString, text);
@@ -83,6 +90,7 @@ public IEnumerable<string> GetFormats()
8390

8491
public void Dispose()
8592
{
93+
_savedDataObject = null;
8694
_native?.Dispose();
8795
_native = null;
8896
}
@@ -111,7 +119,7 @@ public void Dispose()
111119

112120
public unsafe Task SetDataObjectAsync(IDataObject data)
113121
{
114-
Native.Clear();
122+
ClearCore();
115123

116124
// If there is multiple values with the same "to" format, prefer these that were not mapped.
117125
var formats = data.GetDataFormats().Select(f =>
@@ -163,6 +171,8 @@ public unsafe Task SetDataObjectAsync(IDataObject data)
163171
break;
164172
}
165173
}
174+
175+
_savedDataObject = data;
166176
return Task.CompletedTask;
167177
}
168178

@@ -183,9 +193,17 @@ public Task<string[]> GetFormatsAsync()
183193
return n.Bytes;
184194
}
185195

196+
public Task<IDataObject?> TryGetInProcessDataObjectAsync()
197+
{
198+
if (Native.ChangeCount != _lastClearChangeCount)
199+
_savedDataObject = null;
200+
return Task.FromResult(_savedDataObject);
201+
}
202+
186203
/// <inheritdoc />
187204
public Task FlushAsync() =>
188205
Task.CompletedTask;
206+
189207
}
190208

191209
class ClipboardDataObject : IDataObject, IDisposable

src/Avalonia.Native/avn.idl

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
@clr-access internal
33
@clr-map bool int
44
@clr-map u_int64_t ulong
5+
@clr-map int64_t long
56
@clr-map long IntPtr
67
@cpp-preamble @@
78
#pragma once
@@ -972,7 +973,8 @@ interface IAvnClipboard : IUnknown
972973
HRESULT SetBytes(char* type, void* utf8Text, int len);
973974
HRESULT GetBytes(char* type, IAvnString**ppv);
974975

975-
HRESULT Clear();
976+
HRESULT Clear(int64_t* ret);
977+
HRESULT GetChangeCount(int64_t* ret);
976978
}
977979

978980
[uuid(3f998545-f027-4d4d-bd2a-1a80926d984e)]

src/Avalonia.X11/X11Clipboard.cs

+7
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,13 @@ public Task SetDataObjectAsync(IDataObject data)
314314
return StoreAtomsInClipboardManager(data);
315315
}
316316

317+
public Task<IDataObject?> TryGetInProcessDataObjectAsync()
318+
{
319+
if (XGetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD) == _handle)
320+
return Task.FromResult(_storedDataObject);
321+
return Task.FromResult<IDataObject?>(null);
322+
}
323+
317324
public async Task<string[]> GetFormatsAsync()
318325
{
319326
if (!HasOwner)

src/Browser/Avalonia.Browser/ClipboardImpl.cs

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public Task SetTextAsync(string? text)
2525
public Task<string[]> GetFormatsAsync() => Task.FromResult(Array.Empty<string>());
2626

2727
public Task<object?> GetDataAsync(string format) => Task.FromResult<object?>(null);
28+
29+
public Task<IDataObject?> TryGetInProcessDataObjectAsync() => Task.FromResult<IDataObject?>(null);
2830

2931
/// <inheritdoc />
3032
public Task FlushAsync() =>

0 commit comments

Comments
 (0)