Skip to content

Commit

Permalink
Fixed failure to access clipboard in MacOS
Browse files Browse the repository at this point in the history
  • Loading branch information
veler committed Jun 12, 2024
1 parent 211c66f commit ef68c1b
Showing 1 changed file with 121 additions and 93 deletions.
214 changes: 121 additions & 93 deletions src/app/dev/platforms/desktop/DevToys.MacOS/Core/Clipboard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<object?>(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)
{
Expand All @@ -45,51 +51,62 @@ public Clipboard()
return null;
}

public Task<string?> GetClipboardTextAsync()
public async Task<string?> GetClipboardTextAsync()
{
try
{
using NSPasteboard pasteboard = NSPasteboard.GeneralPasteboard;
if (pasteboard.CanReadObjectForClasses(new Class[] { new(typeof(NSString)) }, null))
return await ThreadHelper.RunOnUIThreadAsync<string?>(() =>
{
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<string?>(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<string?>(null);
return null;
}

public Task<FileInfo[]?> GetClipboardFilesAsync()
public async Task<FileInfo[]?> GetClipboardFilesAsync()
{
try
{
return Task.FromResult(GetClipboardFilesInternal());
return await GetClipboardFilesInternalAsync();
}
catch (Exception ex)
{
LogGetClipboardFailed(ex);
}
return Task.FromResult<FileInfo[]?>(null);

return null;
}

public async Task<Image<Rgba32>?> 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)
{
Expand All @@ -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<FileInfo[]?> 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<FileInfo>();
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<FileInfo>();
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<Image<Rgba32>?> 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<Rgba32>(image.Configuration);
using Image image = await Image.LoadAsync(imageFromPasteboardStream);
imageFromPasteboard.Dispose();
return image.CloneAs<Rgba32>(image.Configuration);
}
}
}
}

return null;
return null;
});
}

[LoggerMessage(0, LogLevel.Warning, "Failed to retrieve the clipboard data.")]
Expand Down

0 comments on commit ef68c1b

Please sign in to comment.