From ef68c1b7ca7a08f6e449506b1d988360af2d8734 Mon Sep 17 00:00:00 2001 From: Etienne Baudoux Date: Tue, 11 Jun 2024 20:57:14 -0700 Subject: [PATCH] Fixed failure to access clipboard in MacOS --- .../desktop/DevToys.MacOS/Core/Clipboard.cs | 214 ++++++++++-------- 1 file changed, 121 insertions(+), 93 deletions(-) diff --git a/src/app/dev/platforms/desktop/DevToys.MacOS/Core/Clipboard.cs b/src/app/dev/platforms/desktop/DevToys.MacOS/Core/Clipboard.cs index fad087ad09..e91fb20ac6 100644 --- a/src/app/dev/platforms/desktop/DevToys.MacOS/Core/Clipboard.cs +++ b/src/app/dev/platforms/desktop/DevToys.MacOS/Core/Clipboard.cs @@ -5,6 +5,7 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Png; +using DevToys.MacOS.Core.Helpers; namespace DevToys.MacOS.Core; @@ -23,19 +24,24 @@ public Clipboard() { try { - using NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard; - if (pasteboard.CanReadObjectForClasses(new Class[] { new(typeof(NSUrl)) }, null)) + return await ThreadHelper.RunOnUIThreadAsync(async () => { - return GetClipboardFilesInternal(); - } - else if (pasteboard.CanReadObjectForClasses(new Class[] { new(typeof(NSString)) }, null)) - { - return await GetClipboardTextAsync(); - } - else if (pasteboard.CanReadObjectForClasses(new Class[] { new(typeof(NSImage)) }, null)) - { - return await GetImageFromClipboardInternalAsync(); - } + using NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard; + if (pasteboard.CanReadObjectForClasses(new Class[] { new(typeof(NSUrl)) }, null)) + { + return await GetClipboardFilesInternalAsync(); + } + else if (pasteboard.CanReadObjectForClasses(new Class[] { new(typeof(NSString)) }, null)) + { + return await GetClipboardTextAsync(); + } + else if (pasteboard.CanReadObjectForClasses(new Class[] { new(typeof(NSImage)) }, null)) + { + return await GetImageFromClipboardInternalAsync(); + } + + return null; + }); } catch (Exception ex) { @@ -45,51 +51,62 @@ public Clipboard() return null; } - public Task GetClipboardTextAsync() + public async Task GetClipboardTextAsync() { try { - using NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard; - if (pasteboard.CanReadObjectForClasses(new Class[] { new(typeof(NSString)) }, null)) + return await ThreadHelper.RunOnUIThreadAsync(() => { - NSObject[] strings = pasteboard.ReadObjectsForClasses(new Class[] { new(typeof(NSString)) }, null); - - if (strings.Length > 0 && strings[0] is NSString nsString) + using NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard; + if (pasteboard.CanReadObjectForClasses(new Class[] { new(typeof(NSString)) }, null)) { - return Task.FromResult(nsString.ToString()); + NSObject[] strings = pasteboard.ReadObjectsForClasses(new Class[] { new(typeof(NSString)) }, null); + + if (strings.Length > 0 && strings[0] is NSString nsString) + { + return nsString.ToString(); + } } - } + + return null; + }); } catch (Exception ex) { LogGetClipboardFailed(ex); } - return Task.FromResult(null); + return null; } - public Task GetClipboardFilesAsync() + public async Task GetClipboardFilesAsync() { try { - return Task.FromResult(GetClipboardFilesInternal()); + return await GetClipboardFilesInternalAsync(); } catch (Exception ex) { LogGetClipboardFailed(ex); } - return Task.FromResult(null); + + return null; } public async Task?> GetClipboardImageAsync() { try { - using NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard; - if (pasteboard.CanReadObjectForClasses(new Class[] { new(typeof(NSImage)) }, null)) + return await ThreadHelper.RunOnUIThreadAsync(async () => { - return await GetImageFromClipboardInternalAsync(); - } + using NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard; + if (pasteboard.CanReadObjectForClasses(new Class[] { new(typeof(NSImage)) }, null)) + { + return await GetImageFromClipboardInternalAsync(); + } + + return null; + }); } catch (Exception ex) { @@ -103,123 +120,134 @@ public async Task SetClipboardImageAsync(Image? image) { if (image is not null) { - var encoder = new PngEncoder + await ThreadHelper.RunOnUIThreadAsync(async () => { - ColorType = PngColorType.RgbWithAlpha, - TransparentColorMode = PngTransparentColorMode.Preserve, - BitDepth = PngBitDepth.Bit8, - CompressionLevel = PngCompressionLevel.BestSpeed - }; - - using var pngMemoryStream = new MemoryStream(); - await image.SaveAsPngAsync(pngMemoryStream, encoder); - pngMemoryStream.Seek(0, SeekOrigin.Begin); - - using var nsImage = NSImage.FromStream(pngMemoryStream); - if (nsImage is not null) - { - using NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard; - pasteboard.ClearContents(); - pasteboard.WriteObjects(new INSPasteboardWriting[] { nsImage }); - } + var encoder = new PngEncoder + { + ColorType = PngColorType.RgbWithAlpha, + TransparentColorMode = PngTransparentColorMode.Preserve, + BitDepth = PngBitDepth.Bit8, + CompressionLevel = PngCompressionLevel.BestSpeed + }; + + using var pngMemoryStream = new MemoryStream(); + await image.SaveAsPngAsync(pngMemoryStream, encoder); + pngMemoryStream.Seek(0, SeekOrigin.Begin); + + using var nsImage = NSImage.FromStream(pngMemoryStream); + if (nsImage is not null) + { + using NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard; + pasteboard.ClearContents(); + pasteboard.WriteObjects(new INSPasteboardWriting[] { nsImage }); + } + }); } } - public Task SetClipboardFilesAsync(FileInfo[]? data) + public async Task SetClipboardFilesAsync(FileInfo[]? data) { try { if (data is not null) { - var fileList = new INSPasteboardWriting[data.Length]; - for (int i = 0; i < data.Length; i++) + await ThreadHelper.RunOnUIThreadAsync(() => { - fileList[i] = new NSUrl("file://" + data[i].FullName); - } + var fileList = new INSPasteboardWriting[data.Length]; + for (int i = 0; i < data.Length; i++) + { + fileList[i] = new NSUrl("file://" + data[i].FullName); + } - using NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard; - pasteboard.ClearContents(); - pasteboard.WriteObjects(fileList); + using NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard; + pasteboard.ClearContents(); + pasteboard.WriteObjects(fileList); + }); } } catch (Exception ex) { LogSetClipboardTextFailed(ex); } - - return Task.CompletedTask; } - public Task SetClipboardTextAsync(string? data) + public async Task SetClipboardTextAsync(string? data) { try { - using NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard; - pasteboard.ClearContents(); - pasteboard.WriteObjects(new INSPasteboardWriting[] { new NSString(data) }); + await ThreadHelper.RunOnUIThreadAsync(() => + { + using NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard; + pasteboard.ClearContents(); + pasteboard.WriteObjects(new INSPasteboardWriting[] { new NSString(data) }); + }); } catch (Exception ex) { LogSetClipboardTextFailed(ex); } - - return Task.CompletedTask; } - private static FileInfo[]? GetClipboardFilesInternal() + private static async Task GetClipboardFilesInternalAsync() { - using NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard; - if (pasteboard.CanReadObjectForClasses(new Class[] { new(typeof(NSUrl)) }, null)) + return await ThreadHelper.RunOnUIThreadAsync(() => { - NSObject[] urls = pasteboard.ReadObjectsForClasses(new Class[] { new(typeof(NSUrl)) }, null); - - if (urls.Length > 0) + using NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard; + if (pasteboard.CanReadObjectForClasses(new Class[] { new(typeof(NSUrl)) }, null)) { - var files = new List(); - foreach (NSObject urlObj in urls) + NSObject[] urls = pasteboard.ReadObjectsForClasses(new Class[] { new(typeof(NSUrl)) }, null); + + if (urls.Length > 0) { - if (urlObj is NSUrl { AbsoluteString: not null, Path: not null } filePath) + var files = new List(); + foreach (NSObject urlObj in urls) { - if (filePath.AbsoluteString.StartsWith("file:///")) + if (urlObj is NSUrl { AbsoluteString: not null, Path: not null } filePath) { - files.Add(new FileInfo(filePath.Path)); + if (filePath.AbsoluteString.StartsWith("file:///")) + { + files.Add(new FileInfo(filePath.Path)); + } } } - } - return files.ToArray(); + return files.ToArray(); + } } - } - return null; + return null; + }); } private static async Task?> GetImageFromClipboardInternalAsync() { - using NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard; - if (pasteboard.CanReadObjectForClasses(new Class[] { new(typeof(NSImage)) }, null)) + return await ThreadHelper.RunOnUIThreadAsync(async () => { - NSObject[] images = pasteboard.ReadObjectsForClasses(new Class[] { new(typeof(NSImage)) }, null); - - if (images.Length > 0 && images[0] is NSImage imageFromPasteboard) + using NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard; + if (pasteboard.CanReadObjectForClasses(new Class[] { new(typeof(NSImage)) }, null)) { - NSData? imageData = imageFromPasteboard.AsTiff(); - if (imageData is not null) + NSObject[] images = pasteboard.ReadObjectsForClasses(new Class[] { new(typeof(NSImage)) }, null); + + if (images.Length > 0 && images[0] is NSImage imageFromPasteboard) { - await using Stream tiffData = imageData.AsStream(); - using var imageFromPasteboardStream = new MemoryStream(); - await tiffData.CopyToAsync(imageFromPasteboardStream); + NSData? imageData = imageFromPasteboard.AsTiff(); + if (imageData is not null) + { + await using Stream tiffData = imageData.AsStream(); + using var imageFromPasteboardStream = new MemoryStream(); + await tiffData.CopyToAsync(imageFromPasteboardStream); - imageFromPasteboardStream.Seek(0, SeekOrigin.Begin); + imageFromPasteboardStream.Seek(0, SeekOrigin.Begin); - using Image image = await Image.LoadAsync(imageFromPasteboardStream); - imageFromPasteboard.Dispose(); - return image.CloneAs(image.Configuration); + using Image image = await Image.LoadAsync(imageFromPasteboardStream); + imageFromPasteboard.Dispose(); + return image.CloneAs(image.Configuration); + } } } - } - return null; + return null; + }); } [LoggerMessage(0, LogLevel.Warning, "Failed to retrieve the clipboard data.")]