From 337a56f14060402550f0c1b8a686b45ee1e3ac68 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Tue, 12 Nov 2024 09:43:25 +0100 Subject: [PATCH] Throw ObjectDisposedException for ClipboardImpl/ClipboardDataObject (#17472) --- src/Avalonia.Native/ClipboardImpl.cs | 69 +++++++++++++++++----------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/src/Avalonia.Native/ClipboardImpl.cs b/src/Avalonia.Native/ClipboardImpl.cs index 0fc4d76224..34b47ce236 100644 --- a/src/Avalonia.Native/ClipboardImpl.cs +++ b/src/Avalonia.Native/ClipboardImpl.cs @@ -1,4 +1,6 @@ -using System; +#nullable enable + +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -9,13 +11,12 @@ using Avalonia.Logging; using Avalonia.Native.Interop; using Avalonia.Platform.Storage; using Avalonia.Platform.Storage.FileIO; -using MicroCom.Runtime; namespace Avalonia.Native { class ClipboardImpl : IClipboard, IDisposable { - private IAvnClipboard _native; + private IAvnClipboard? _native; // TODO hide native types behind IAvnClipboard abstraction, so managed side won't depend on macOS. private const string NSPasteboardTypeString = "public.utf8-plain-text"; @@ -26,25 +27,30 @@ namespace Avalonia.Native _native = native; } + private IAvnClipboard Native + => _native ?? throw new ObjectDisposedException(nameof(ClipboardImpl)); + public Task ClearAsync() { - _native.Clear(); + Native.Clear(); return Task.CompletedTask; } - public Task GetTextAsync() + public Task GetTextAsync() { - using (var text = _native.GetText(NSPasteboardTypeString)) - return Task.FromResult(text.String); + using (var text = Native.GetText(NSPasteboardTypeString)) + return Task.FromResult(text.String); } - public unsafe Task SetTextAsync(string text) + public Task SetTextAsync(string? text) { - _native.Clear(); + var native = Native; + + native.Clear(); if (text != null) - _native.SetText(NSPasteboardTypeString, text); + native.SetText(NSPasteboardTypeString, text); return Task.CompletedTask; } @@ -52,7 +58,7 @@ namespace Avalonia.Native public IEnumerable GetFormats() { var rv = new HashSet(); - using (var formats = _native.ObtainFormats()) + using (var formats = Native.ObtainFormats()) { var cnt = formats.Count; for (uint c = 0; c < cnt; c++) @@ -81,27 +87,31 @@ namespace Avalonia.Native _native = null; } - public IEnumerable GetFileNames() + public IEnumerable? GetFileNames() { - using (var strings = _native.GetStrings(NSFilenamesPboardType)) + using (var strings = Native.GetStrings(NSFilenamesPboardType)) return strings?.ToStringArray(); } - public IEnumerable GetFiles() + public IEnumerable? GetFiles() { var storageApi = (StorageProviderApi)AvaloniaLocator.Current.GetRequiredService(); // TODO: use non-deprecated AppKit API to get NSUri instead of file names. - return GetFileNames()? + var fileNames = GetFileNames(); + if (fileNames is null) + return null; + + return fileNames .Select(f => StorageProviderHelpers.TryGetUriFromFilePath(f, false) is { } uri ? storageApi.TryGetStorageItem(uri) : null) - .Where(f => f is not null); + .Where(f => f is not null)!; } public unsafe Task SetDataObjectAsync(IDataObject data) { - _native.Clear(); + Native.Clear(); // If there is multiple values with the same "to" format, prefer these that were not mapped. var formats = data.GetDataFormats().Select(f => @@ -125,26 +135,26 @@ namespace Avalonia.Native switch (o) { case string s: - _native.SetText(toFormat, s); + Native.SetText(toFormat, s); break; case IEnumerable storageItems: using (var strings = new AvnStringArray(storageItems .Select(s => s.TryGetLocalPath()) .Where(p => p is not null))) { - _native.SetStrings(toFormat, strings); + Native.SetStrings(toFormat, strings); } break; case IEnumerable managedStrings: using (var strings = new AvnStringArray(managedStrings)) { - _native.SetStrings(toFormat, strings); + Native.SetStrings(toFormat, strings); } break; case byte[] bytes: { fixed (byte* pbytes = bytes) - _native.SetBytes(toFormat, pbytes, bytes.Length); + Native.SetBytes(toFormat, pbytes, bytes.Length); break; } default: @@ -161,7 +171,7 @@ namespace Avalonia.Native return Task.FromResult(GetFormats().ToArray()); } - public async Task GetDataAsync(string format) + public async Task GetDataAsync(string format) { if (format == DataFormats.Text || format == NSPasteboardTypeString) return await GetTextAsync(); @@ -169,15 +179,15 @@ namespace Avalonia.Native return GetFileNames(); if (format == DataFormats.Files) return GetFiles(); - using (var n = _native.GetBytes(format)) + using (var n = Native.GetBytes(format)) return n.Bytes; } } class ClipboardDataObject : IDataObject, IDisposable { - private ClipboardImpl _clipboard; - private List _formats; + private ClipboardImpl? _clipboard; + private List? _formats; public ClipboardDataObject(IAvnClipboard clipboard) { @@ -190,14 +200,17 @@ namespace Avalonia.Native _clipboard = null; } - List Formats => _formats ??= _clipboard.GetFormats().ToList(); + private ClipboardImpl Clipboard + => _clipboard ?? throw new ObjectDisposedException(nameof(ClipboardDataObject)); + + private List Formats => _formats ??= Clipboard.GetFormats().ToList(); public IEnumerable GetDataFormats() => Formats; public bool Contains(string dataFormat) => Formats.Contains(dataFormat); - public object Get(string dataFormat) => _clipboard.GetDataAsync(dataFormat).GetAwaiter().GetResult(); + public object? Get(string dataFormat) => Clipboard.GetDataAsync(dataFormat).GetAwaiter().GetResult(); - public Task SetFromDataObjectAsync(IDataObject dataObject) => _clipboard.SetDataObjectAsync(dataObject); + public Task SetFromDataObjectAsync(IDataObject dataObject) => Clipboard.SetDataObjectAsync(dataObject); } }