Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed failure to access clipboard in MacOS #1205

Merged
merged 1 commit into from
Jun 17, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading