From 0bfb7e10ee302f628de5773a3bae5c24add45f18 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 28 Feb 2025 15:44:17 +0500 Subject: [PATCH] Rest of the platforms --- native/Avalonia.Native/src/OSX/clipboard.mm | 18 ++++++- .../Pages/ClipboardPage.xaml.cs | 2 +- .../Platform/ClipboardImpl.cs | 2 + .../Input/Platform/IClipboard.cs | 12 +++-- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 1 + src/Avalonia.Native/ClipboardImpl.cs | 25 ++++++++-- src/Avalonia.Native/avn.idl | 4 +- src/Avalonia.X11/X11Clipboard.cs | 2 +- src/Browser/Avalonia.Browser/ClipboardImpl.cs | 2 + .../HeadlessPlatformStubs.cs | 50 ++++++------------- src/Tizen/Avalonia.Tizen/NuiClipboardImpl.cs | 2 + src/Windows/Avalonia.Win32/ClipboardImpl.cs | 2 +- src/iOS/Avalonia.iOS/ClipboardImpl.cs | 2 + .../MaskedTextBoxTests.cs | 27 +--------- .../TextBoxTests.cs | 27 +--------- 15 files changed, 77 insertions(+), 101 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/clipboard.mm b/native/Avalonia.Native/src/OSX/clipboard.mm index 75c8f2a021..68f0e7d87a 100644 --- a/native/Avalonia.Native/src/OSX/clipboard.mm +++ b/native/Avalonia.Native/src/OSX/clipboard.mm @@ -148,17 +148,20 @@ public: } - virtual HRESULT Clear() override + virtual HRESULT Clear(int64_t* rv) override { START_COM_CALL; @autoreleasepool { if(_item != nil) + { _item = [NSPasteboardItem new]; + return 0; + } else { - [_pb clearContents]; + *rv = [_pb clearContents]; [_pb setString:@"" forType:NSPasteboardTypeString]; } @@ -166,6 +169,17 @@ public: } } + virtual HRESULT GetChangeCount(int64_t* rv) override + { + START_COM_CALL; + if(_item == nil) + { + *rv = [_pb changeCount]; + return S_OK; + } + return E_NOTIMPL; + } + virtual HRESULT ObtainFormats(IAvnStringArray** ppv) override { START_COM_CALL; diff --git a/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs b/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs index 833636c7dd..f471647c15 100644 --- a/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs @@ -167,7 +167,7 @@ namespace ControlCatalog.Pages try { _checkingClipboardDataObject = true; - var task = (TopLevel.GetTopLevel(this)?.Clipboard as IClipboard2)?.TryGetInProcessDataObjectAsync(); + var task = TopLevel.GetTopLevel(this)?.Clipboard?.TryGetInProcessDataObjectAsync(); var owns = task != null && (await task) == _storedDataObject && _storedDataObject != null; OwnsClipboardDataObject.Text = owns ? "Yes" : "No"; OwnsClipboardDataObject.Foreground = owns ? Brushes.Green : Brushes.Red; diff --git a/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs b/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs index 028134ffad..8be8cd45e0 100644 --- a/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs +++ b/src/Android/Avalonia.Android/Platform/ClipboardImpl.cs @@ -55,5 +55,7 @@ namespace Avalonia.Android.Platform public Task GetFormatsAsync() => throw new PlatformNotSupportedException(); public Task GetDataAsync(string format) => throw new PlatformNotSupportedException(); + + public Task TryGetInProcessDataObjectAsync() => Task.FromResult(null); } } diff --git a/src/Avalonia.Base/Input/Platform/IClipboard.cs b/src/Avalonia.Base/Input/Platform/IClipboard.cs index 398494bcc5..6621f954ab 100644 --- a/src/Avalonia.Base/Input/Platform/IClipboard.cs +++ b/src/Avalonia.Base/Input/Platform/IClipboard.cs @@ -17,10 +17,14 @@ namespace Avalonia.Input.Platform Task GetFormatsAsync(); Task GetDataAsync(string format); - } - - public interface IClipboard2 : IClipboard - { + + /// + /// If clipboard contains the IDataObject that was set by a previous call to , + /// return said IDataObject instance. Otherwise, return null. + /// Note that not every platform supports that method, on unsupported platforms this method will always return + /// null + /// + /// Task TryGetInProcessDataObjectAsync(); } } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index e3117ab8b2..20932fd9dc 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -225,6 +225,7 @@ namespace Avalonia.DesignerSupport.Remote public Task GetFormatsAsync() => Task.FromResult(Array.Empty()); public Task GetDataAsync(string format) => Task.FromResult((object)null); + public Task TryGetInProcessDataObjectAsync() => Task.FromResult(null); } class CursorFactoryStub : ICursorFactory diff --git a/src/Avalonia.Native/ClipboardImpl.cs b/src/Avalonia.Native/ClipboardImpl.cs index 34b47ce236..877c5fe244 100644 --- a/src/Avalonia.Native/ClipboardImpl.cs +++ b/src/Avalonia.Native/ClipboardImpl.cs @@ -17,6 +17,8 @@ namespace Avalonia.Native class ClipboardImpl : IClipboard, IDisposable { private IAvnClipboard? _native; + private IDataObject? _savedDataObject; + private long _lastClearChangeCount; // TODO hide native types behind IAvnClipboard abstraction, so managed side won't depend on macOS. private const string NSPasteboardTypeString = "public.utf8-plain-text"; @@ -30,10 +32,15 @@ namespace Avalonia.Native private IAvnClipboard Native => _native ?? throw new ObjectDisposedException(nameof(ClipboardImpl)); + private void ClearCore() + { + _savedDataObject = null; + _lastClearChangeCount = Native.Clear(); + } + public Task ClearAsync() { - Native.Clear(); - + ClearCore(); return Task.CompletedTask; } @@ -47,7 +54,7 @@ namespace Avalonia.Native { var native = Native; - native.Clear(); + ClearCore(); if (text != null) native.SetText(NSPasteboardTypeString, text); @@ -83,6 +90,7 @@ namespace Avalonia.Native public void Dispose() { + _savedDataObject = null; _native?.Dispose(); _native = null; } @@ -111,7 +119,7 @@ namespace Avalonia.Native public unsafe Task SetDataObjectAsync(IDataObject data) { - Native.Clear(); + ClearCore(); // If there is multiple values with the same "to" format, prefer these that were not mapped. var formats = data.GetDataFormats().Select(f => @@ -163,6 +171,8 @@ namespace Avalonia.Native break; } } + + _savedDataObject = data; return Task.CompletedTask; } @@ -182,6 +192,13 @@ namespace Avalonia.Native using (var n = Native.GetBytes(format)) return n.Bytes; } + + public Task TryGetInProcessDataObjectAsync() + { + if (Native.ChangeCount != _lastClearChangeCount) + _savedDataObject = null; + return Task.FromResult(_savedDataObject); + } } class ClipboardDataObject : IDataObject, IDisposable diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index f913aaac77..bc2d6ddd1a 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -2,6 +2,7 @@ @clr-access internal @clr-map bool int @clr-map u_int64_t ulong +@clr-map int64_t long @clr-map long IntPtr @cpp-preamble @@ #pragma once @@ -972,7 +973,8 @@ interface IAvnClipboard : IUnknown HRESULT SetBytes(char* type, void* utf8Text, int len); HRESULT GetBytes(char* type, IAvnString**ppv); - HRESULT Clear(); + HRESULT Clear(int64_t* ret); + HRESULT GetChangeCount(int64_t* ret); } [uuid(3f998545-f027-4d4d-bd2a-1a80926d984e)] diff --git a/src/Avalonia.X11/X11Clipboard.cs b/src/Avalonia.X11/X11Clipboard.cs index df04ba564e..7a1c1bf3e9 100644 --- a/src/Avalonia.X11/X11Clipboard.cs +++ b/src/Avalonia.X11/X11Clipboard.cs @@ -9,7 +9,7 @@ using Avalonia.Input.Platform; using static Avalonia.X11.XLib; namespace Avalonia.X11 { - internal class X11Clipboard : IClipboard, IClipboard2 + internal class X11Clipboard : IClipboard { private readonly X11Info _x11; private IDataObject? _storedDataObject; diff --git a/src/Browser/Avalonia.Browser/ClipboardImpl.cs b/src/Browser/Avalonia.Browser/ClipboardImpl.cs index 5df09e555d..beb09d22d3 100644 --- a/src/Browser/Avalonia.Browser/ClipboardImpl.cs +++ b/src/Browser/Avalonia.Browser/ClipboardImpl.cs @@ -25,5 +25,7 @@ namespace Avalonia.Browser public Task GetFormatsAsync() => Task.FromResult(Array.Empty()); public Task GetDataAsync(string format) => Task.FromResult(null); + + public Task TryGetInProcessDataObjectAsync() => Task.FromResult(null); } } diff --git a/src/Headless/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Headless/Avalonia.Headless/HeadlessPlatformStubs.cs index 6bfe8562dc..2fbe8b037e 100644 --- a/src/Headless/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Headless/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -23,65 +23,45 @@ namespace Avalonia.Headless { internal class HeadlessClipboardStub : IClipboard { - private string? _text; private IDataObject? _data; public Task GetTextAsync() { - return Task.Run(() => _text); + return Task.FromResult(_data?.GetText()); } public Task SetTextAsync(string? text) { - return Task.Run(() => _text = text); + var data = new DataObject(); + if (text != null) + data.Set(DataFormats.Text, text); + _data = data; + return Task.CompletedTask; } public Task ClearAsync() { - return Task.Run(() => _text = null); + _data = null; + return Task.CompletedTask; } public Task SetDataObjectAsync(IDataObject data) { - return Task.Run(() => _data = data); + _data = data; + return Task.CompletedTask; } public Task GetFormatsAsync() { - return Task.Run(() => - { - if (_data is not null) - { - return _data.GetDataFormats().ToArray(); - } - - if (_text is not null) - { - return new[] { DataFormats.Text }; - } - - return Array.Empty(); - }); + return Task.FromResult(_data?.GetDataFormats().ToArray() ?? []); } - public async Task GetDataAsync(string format) + public Task GetDataAsync(string format) { - return await Task.Run(() => - { - if (format == DataFormats.Text) - return _text; - if (format == DataFormats.Files && _data is not null) - return _data.GetFiles(); - if (format == DataFormats.FileNames && _data is not null) - { -#pragma warning disable CS0618 // Type or member is obsolete - return _data.GetFileNames(); -#pragma warning restore CS0618 // Type or member is obsolete - } - else - return (object?)_data; - }); + return Task.FromResult(_data?.Get(format)); } + + public Task TryGetInProcessDataObjectAsync() => Task.FromResult(_data); } internal class HeadlessCursorFactoryStub : ICursorFactory diff --git a/src/Tizen/Avalonia.Tizen/NuiClipboardImpl.cs b/src/Tizen/Avalonia.Tizen/NuiClipboardImpl.cs index 549582b034..ad96eff2da 100644 --- a/src/Tizen/Avalonia.Tizen/NuiClipboardImpl.cs +++ b/src/Tizen/Avalonia.Tizen/NuiClipboardImpl.cs @@ -73,4 +73,6 @@ internal class NuiClipboardImpl : IClipboard public Task GetFormatsAsync() => throw new PlatformNotSupportedException(); + + public Task TryGetInProcessDataObjectAsync() => Task.FromResult(null); } diff --git a/src/Windows/Avalonia.Win32/ClipboardImpl.cs b/src/Windows/Avalonia.Win32/ClipboardImpl.cs index e1bb6e29c6..038f5d3868 100644 --- a/src/Windows/Avalonia.Win32/ClipboardImpl.cs +++ b/src/Windows/Avalonia.Win32/ClipboardImpl.cs @@ -11,7 +11,7 @@ using MicroCom.Runtime; namespace Avalonia.Win32 { - internal class ClipboardImpl : IClipboard2 + internal class ClipboardImpl : IClipboard { private const int OleRetryCount = 10; private const int OleRetryDelay = 100; diff --git a/src/iOS/Avalonia.iOS/ClipboardImpl.cs b/src/iOS/Avalonia.iOS/ClipboardImpl.cs index 8c6cadee0e..b5bc724ec4 100644 --- a/src/iOS/Avalonia.iOS/ClipboardImpl.cs +++ b/src/iOS/Avalonia.iOS/ClipboardImpl.cs @@ -57,5 +57,7 @@ namespace Avalonia.iOS return Task.FromResult(null); } + + public Task TryGetInProcessDataObjectAsync() => Task.FromResult(null); } } diff --git a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs index 70c6969021..5a836a6833 100644 --- a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs @@ -1005,31 +1005,6 @@ namespace Avalonia.Controls.UnitTests } } - internal class ClipboardStub : IClipboard // in order to get tests working that use the clipboard - { - private string _text; - - public Task GetTextAsync() => Task.FromResult(_text); - - public Task SetTextAsync(string text) - { - _text = text; - return Task.CompletedTask; - } - - public Task ClearAsync() - { - _text = null; - return Task.CompletedTask; - } - - public Task SetDataObjectAsync(IDataObject data) => Task.CompletedTask; - - public Task GetFormatsAsync() => Task.FromResult(Array.Empty()); - - public Task GetDataAsync(string format) => Task.FromResult((object)null); - } - private class TestTopLevel : TopLevel { private readonly ILayoutManager _layoutManager; @@ -1048,7 +1023,7 @@ namespace Avalonia.Controls.UnitTests var clipboard = new Mock(); clipboard.Setup(x => x.Compositor).Returns(RendererMocks.CreateDummyCompositor()); clipboard.Setup(r => r.TryGetFeature(typeof(IClipboard))) - .Returns(new ClipboardStub()); + .Returns(new HeadlessClipboardStub()); clipboard.SetupGet(x => x.RenderScaling).Returns(1); return clipboard; } diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index 66108952ad..9d09507c2a 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -1973,31 +1973,6 @@ namespace Avalonia.Controls.UnitTests } } - private class ClipboardStub : IClipboard // in order to get tests working that use the clipboard - { - private string? _text; - - public Task GetTextAsync() => Task.FromResult(_text); - - public Task SetTextAsync(string? text) - { - _text = text; - return Task.CompletedTask; - } - - public Task ClearAsync() - { - _text = null; - return Task.CompletedTask; - } - - public Task SetDataObjectAsync(IDataObject data) => Task.CompletedTask; - - public Task GetFormatsAsync() => Task.FromResult(Array.Empty()); - - public Task GetDataAsync(string format) => Task.FromResult((object?)null); - } - private class TestTopLevel : TopLevel { private readonly ILayoutManager _layoutManager; @@ -2016,7 +1991,7 @@ namespace Avalonia.Controls.UnitTests var clipboard = new Mock(); clipboard.Setup(x => x.Compositor).Returns(RendererMocks.CreateDummyCompositor()); clipboard.Setup(r => r.TryGetFeature(typeof(IClipboard))) - .Returns(new ClipboardStub()); + .Returns(new HeadlessClipboardStub()); clipboard.SetupGet(x => x.RenderScaling).Returns(1); return clipboard; }