Added pasting new memes, fixed offsets and changed out tray icon.
This commit is contained in:
@@ -2,12 +2,14 @@ using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Imaging;
|
||||
using EbbesMemeClipboard.Models;
|
||||
|
||||
namespace EbbesMemeClipboard.Services;
|
||||
|
||||
public sealed class ClipboardService : IClipboardService
|
||||
public sealed partial class ClipboardService : IClipboardService
|
||||
{
|
||||
public async Task CopyLocalFileAsync(string filePath)
|
||||
{
|
||||
@@ -60,6 +62,86 @@ public sealed class ClipboardService : IClipboardService
|
||||
_ => "application/octet-stream",
|
||||
};
|
||||
|
||||
private static readonly string[] ImportableExtensions = { ".jpg", ".jpeg", ".png", ".gif" };
|
||||
|
||||
/// <summary>
|
||||
/// Formats are checked in descending order of fidelity, which matters a lot here: a GIF is
|
||||
/// only still animated in the first two. Falling straight to the bitmap (as a naive
|
||||
/// implementation would) silently flattens every pasted GIF to one frame.
|
||||
/// </summary>
|
||||
public ClipboardImportPayload? TryReadImage()
|
||||
{
|
||||
// 1. Real files (copied in Explorer) - byte-for-byte exact, animation intact.
|
||||
if (Clipboard.ContainsFileDropList())
|
||||
{
|
||||
var paths = Clipboard.GetFileDropList()
|
||||
.Cast<string>()
|
||||
.Where(p => !string.IsNullOrWhiteSpace(p) &&
|
||||
ImportableExtensions.Contains(Path.GetExtension(p).ToLowerInvariant()))
|
||||
.ToList();
|
||||
|
||||
if (paths.Count > 0)
|
||||
{
|
||||
return new ClipboardImportPayload { FilePaths = paths };
|
||||
}
|
||||
}
|
||||
|
||||
// 2. CF_HTML with an embedded data: URI - how a real (possibly animated) GIF arrives
|
||||
// when copied out of a browser or out of this app itself.
|
||||
if (Clipboard.ContainsText(TextDataFormat.Html))
|
||||
{
|
||||
var match = DataUriRegex().Match(Clipboard.GetText(TextDataFormat.Html));
|
||||
if (match.Success)
|
||||
{
|
||||
try
|
||||
{
|
||||
var bytes = Convert.FromBase64String(match.Groups["data"].Value);
|
||||
var ext = match.Groups["mime"].Value.ToLowerInvariant() switch
|
||||
{
|
||||
"gif" => ".gif",
|
||||
"jpeg" or "jpg" => ".jpg",
|
||||
_ => ".png",
|
||||
};
|
||||
return new ClipboardImportPayload { Data = bytes, Extension = ext };
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
// Malformed base64 - fall through to the bitmap formats below.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. The registered "PNG" format - lossless and keeps transparency, unlike CF_DIB.
|
||||
if (Clipboard.ContainsData("PNG") && Clipboard.GetData("PNG") is { } pngData)
|
||||
{
|
||||
byte[]? bytes = pngData switch
|
||||
{
|
||||
MemoryStream ms => ms.ToArray(),
|
||||
byte[] raw => raw,
|
||||
_ => null,
|
||||
};
|
||||
if (bytes is { Length: > 0 })
|
||||
{
|
||||
return new ClipboardImportPayload { Data = bytes, Extension = ".png" };
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Plain bitmap (Snipping Tool, Paint) - always static, so it's the last resort.
|
||||
if (Clipboard.ContainsImage() && Clipboard.GetImage() is { } bitmap)
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
var encoder = new PngBitmapEncoder();
|
||||
encoder.Frames.Add(BitmapFrame.Create(bitmap));
|
||||
encoder.Save(stream);
|
||||
return new ClipboardImportPayload { Data = stream.ToArray(), Extension = ".png" };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"data:image/(?<mime>gif|png|jpe?g);base64,(?<data>[A-Za-z0-9+/=]+)", RegexOptions.IgnoreCase)]
|
||||
private static partial Regex DataUriRegex();
|
||||
|
||||
private static async Task SetClipboardWithRetryAsync(DataObject data)
|
||||
{
|
||||
const int maxAttempts = 5;
|
||||
|
||||
Reference in New Issue
Block a user