Fix Win32 clipboard not returning bitmaps in some cases (#20506)
* Fix Win32 clipboard not returning bitmap in some cases * Make ClipboardFormatRegistry fields read-only
This commit is contained in:
committed by
GitHub
parent
816fb91d4e
commit
97e3d80a26
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Utilities;
|
||||
@@ -12,19 +13,15 @@ namespace Avalonia.Win32
|
||||
{
|
||||
private const int MaxFormatNameLength = 260;
|
||||
private const string AppPrefix = "avn-app-fmt:";
|
||||
public const string PngFormatMimeType = "image/png";
|
||||
public const string PngFormatSystemType = "PNG";
|
||||
public const string BitmapFormat = "CF_BITMAP";
|
||||
public const string DibFormat = "CF_DIB";
|
||||
public const string DibV5Format = "CF_DIBV5";
|
||||
private static readonly List<(DataFormat Format, ushort Id)> s_formats = [];
|
||||
|
||||
public static DataFormat PngSystemDataFormat = DataFormat.FromSystemName<Bitmap>(PngFormatSystemType, AppPrefix);
|
||||
public static DataFormat PngMimeDataFormat = DataFormat.FromSystemName<Bitmap>(PngFormatMimeType, AppPrefix);
|
||||
public static DataFormat HBitmapDataFormat = DataFormat.FromSystemName<Bitmap>(BitmapFormat, AppPrefix);
|
||||
public static DataFormat DibDataFormat = DataFormat.FromSystemName<Bitmap>(DibFormat, AppPrefix);
|
||||
public static DataFormat DibV5DataFormat = DataFormat.FromSystemName<Bitmap>(DibV5Format, AppPrefix);
|
||||
public static readonly DataFormat PngSystemDataFormat = new DataFormat<Bitmap>(DataFormatKind.Platform, "PNG");
|
||||
public static readonly DataFormat PngMimeDataFormat = new DataFormat<Bitmap>(DataFormatKind.Platform, "image/png");
|
||||
public static readonly DataFormat HBitmapDataFormat = new DataFormat<Bitmap>(DataFormatKind.Platform, "CF_BITMAP");
|
||||
public static readonly DataFormat DibDataFormat = new DataFormat<Bitmap>(DataFormatKind.Platform, "CF_DIB");
|
||||
public static readonly DataFormat DibV5DataFormat = new DataFormat<Bitmap>(DataFormatKind.Platform, "CF_DIBV5");
|
||||
|
||||
// Ordered from the most preferred to the least preferred
|
||||
public static DataFormat[] ImageFormats = [PngMimeDataFormat, PngSystemDataFormat, DibDataFormat, DibV5DataFormat, HBitmapDataFormat];
|
||||
|
||||
static ClipboardFormatRegistry()
|
||||
@@ -44,12 +41,12 @@ namespace Avalonia.Win32
|
||||
var buffer = StringBuilderCache.Acquire(MaxFormatNameLength);
|
||||
if (UnmanagedMethods.GetClipboardFormatName(id, buffer, buffer.Capacity) > 0)
|
||||
return StringBuilderCache.GetStringAndRelease(buffer);
|
||||
if (Enum.IsDefined(typeof(UnmanagedMethods.ClipboardFormat), (int)id))
|
||||
return Enum.GetName(typeof(UnmanagedMethods.ClipboardFormat), (int)id)!;
|
||||
if (Enum.IsDefined(typeof(UnmanagedMethods.ClipboardFormat), id))
|
||||
return Enum.GetName(typeof(UnmanagedMethods.ClipboardFormat), id)!;
|
||||
return $"Unknown_Format_{id}";
|
||||
}
|
||||
|
||||
public static DataFormat GetFormatById(ushort id)
|
||||
public static DataFormat GetOrAddFormat(ushort id)
|
||||
{
|
||||
lock (s_formats)
|
||||
{
|
||||
@@ -66,30 +63,12 @@ namespace Avalonia.Win32
|
||||
}
|
||||
}
|
||||
|
||||
public static ushort GetFormatId(DataFormat format)
|
||||
public static ushort GetOrAddFormat(DataFormat format)
|
||||
{
|
||||
Debug.Assert(format != DataFormat.Bitmap); // Callers must pass an effective platform type
|
||||
|
||||
lock (s_formats)
|
||||
{
|
||||
if (DataFormat.Bitmap.Equals(format))
|
||||
{
|
||||
(DataFormat, ushort)? pngFormat = null;
|
||||
(DataFormat, ushort)? dibFormat = null;
|
||||
|
||||
foreach (var currentFormat in s_formats)
|
||||
{
|
||||
if (currentFormat.Id == (ushort)UnmanagedMethods.ClipboardFormat.CF_DIB)
|
||||
dibFormat = currentFormat;
|
||||
else if (currentFormat.Format.Identifier == PngFormatMimeType)
|
||||
pngFormat = currentFormat;
|
||||
}
|
||||
var imageFormatId = pngFormat?.Item2 ?? dibFormat?.Item2 ?? 0;
|
||||
|
||||
if (imageFormatId != 0)
|
||||
{
|
||||
return imageFormatId;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < s_formats.Count; ++i)
|
||||
{
|
||||
if (s_formats[i].Format.Equals(format))
|
||||
|
||||
@@ -30,25 +30,9 @@ internal class DataTransferToOleDataObjectWrapper(IDataTransfer dataTransfer)
|
||||
_current = current;
|
||||
}
|
||||
|
||||
public FormatEnumerator(IReadOnlyList<DataFormat> dataFormats)
|
||||
public FormatEnumerator(ushort[] formatIds)
|
||||
{
|
||||
List<FORMATETC> formats = new List<FORMATETC>();
|
||||
|
||||
if (dataFormats.Contains(DataFormat.Bitmap))
|
||||
{
|
||||
// We add extra formats for bitmaps
|
||||
formats.Add(OleDataObjectHelper.ToFormatEtc(ClipboardFormatRegistry.PngMimeDataFormat));
|
||||
formats.Add(OleDataObjectHelper.ToFormatEtc(ClipboardFormatRegistry.PngSystemDataFormat));
|
||||
formats.Add(OleDataObjectHelper.ToFormatEtc(ClipboardFormatRegistry.DibDataFormat));
|
||||
formats.Add(OleDataObjectHelper.ToFormatEtc(ClipboardFormatRegistry.DibV5DataFormat));
|
||||
formats.Add(OleDataObjectHelper.ToFormatEtc(ClipboardFormatRegistry.HBitmapDataFormat, true));
|
||||
}
|
||||
else
|
||||
{
|
||||
formats.AddRange(dataFormats.Select(x => OleDataObjectHelper.ToFormatEtc(x)));
|
||||
}
|
||||
|
||||
_formats = formats.ToArray();
|
||||
_formats = formatIds.Select(OleDataObjectHelper.ToFormatEtc).ToArray();
|
||||
_current = 0;
|
||||
}
|
||||
|
||||
@@ -98,6 +82,9 @@ internal class DataTransferToOleDataObjectWrapper(IDataTransfer dataTransfer)
|
||||
public bool IsDisposed
|
||||
=> DataTransfer is null;
|
||||
|
||||
private ushort[] FormatIds
|
||||
=> field ??= CalcFormatIds();
|
||||
|
||||
public event Action? OnDestroyed;
|
||||
|
||||
unsafe int Win32Com.IDataObject.DAdvise(FORMATETC* pFormatetc, int advf, void* adviseSink)
|
||||
@@ -115,7 +102,7 @@ internal class DataTransferToOleDataObjectWrapper(IDataTransfer dataTransfer)
|
||||
throw new COMException(nameof(COR_E_OBJECTDISPOSED), unchecked((int)HRESULT.E_NOTIMPL));
|
||||
|
||||
if ((DATADIR)direction == DATADIR.DATADIR_GET)
|
||||
return new FormatEnumerator(DataTransfer.Formats);
|
||||
return new FormatEnumerator(FormatIds);
|
||||
|
||||
throw new COMException(nameof(HRESULT.E_NOTIMPL), unchecked((int)HRESULT.E_NOTIMPL));
|
||||
}
|
||||
@@ -166,7 +153,8 @@ internal class DataTransferToOleDataObjectWrapper(IDataTransfer dataTransfer)
|
||||
{
|
||||
dataFormat = null;
|
||||
|
||||
if (!(format->tymed.HasAllFlags(TYMED.TYMED_HGLOBAL) || format->cfFormat == (ushort)ClipboardFormat.CF_BITMAP && format->tymed == TYMED.TYMED_GDI))
|
||||
if (!(format->tymed == TYMED.TYMED_HGLOBAL ||
|
||||
(format->tymed == TYMED.TYMED_GDI && format->cfFormat == (ushort)ClipboardFormat.CF_BITMAP)))
|
||||
{
|
||||
result = DV_E_TYMED;
|
||||
dataFormat = null;
|
||||
@@ -185,17 +173,38 @@ internal class DataTransferToOleDataObjectWrapper(IDataTransfer dataTransfer)
|
||||
return false;
|
||||
}
|
||||
|
||||
dataFormat = ClipboardFormatRegistry.GetFormatById(format->cfFormat);
|
||||
if (!DataTransfer.Contains(dataFormat) && !ClipboardFormatRegistry.ImageFormats.Contains(dataFormat))
|
||||
if (!FormatIds.Contains(format->cfFormat))
|
||||
{
|
||||
result = DV_E_FORMATETC;
|
||||
return false;
|
||||
}
|
||||
|
||||
dataFormat = ClipboardFormatRegistry.GetOrAddFormat(format->cfFormat);
|
||||
result = (uint)HRESULT.S_OK;
|
||||
return true;
|
||||
}
|
||||
|
||||
private ushort[] CalcFormatIds()
|
||||
{
|
||||
if (DataTransfer is null)
|
||||
return [];
|
||||
|
||||
var formatIds = new List<ushort>(DataTransfer.Formats.Count);
|
||||
|
||||
foreach (var dataFormat in DataTransfer.Formats)
|
||||
{
|
||||
if (DataFormat.Bitmap.Equals(dataFormat))
|
||||
{
|
||||
// We add extra formats for bitmaps
|
||||
formatIds.AddRange(ClipboardFormatRegistry.ImageFormats.Select(ClipboardFormatRegistry.GetOrAddFormat));
|
||||
}
|
||||
else
|
||||
formatIds.Add(ClipboardFormatRegistry.GetOrAddFormat(dataFormat));
|
||||
}
|
||||
|
||||
return formatIds.ToArray();
|
||||
}
|
||||
|
||||
unsafe uint Win32Com.IDataObject.SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, int fRelease)
|
||||
=> (uint)HRESULT.E_NOTIMPL;
|
||||
|
||||
|
||||
@@ -2297,7 +2297,7 @@ namespace Avalonia.Win32.Interop
|
||||
MDT_DEFAULT = MDT_EFFECTIVE_DPI
|
||||
}
|
||||
|
||||
public enum ClipboardFormat
|
||||
public enum ClipboardFormat : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// A handle to a bitmap
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -24,39 +25,26 @@ internal static class OleDataObjectHelper
|
||||
{
|
||||
private const int SRCCOPY = 0x00CC0020;
|
||||
|
||||
public static FORMATETC ToFormatEtc(this DataFormat format, bool isGdi = false)
|
||||
public static FORMATETC ToFormatEtc(ushort formatId)
|
||||
=> new()
|
||||
{
|
||||
cfFormat = ClipboardFormatRegistry.GetFormatId(format),
|
||||
cfFormat = formatId,
|
||||
dwAspect = DVASPECT.DVASPECT_CONTENT,
|
||||
ptd = IntPtr.Zero,
|
||||
lindex = -1,
|
||||
tymed = isGdi ? TYMED.TYMED_GDI : TYMED.TYMED_HGLOBAL
|
||||
tymed = formatId == (ushort)ClipboardFormat.CF_BITMAP ? TYMED.TYMED_GDI : TYMED.TYMED_HGLOBAL
|
||||
};
|
||||
|
||||
public static unsafe object? TryGet(this Win32Com.IDataObject oleDataObject, DataFormat format)
|
||||
{
|
||||
var formatEtc = format.ToFormatEtc();
|
||||
|
||||
if (oleDataObject.QueryGetData(&formatEtc) != (uint)HRESULT.S_OK)
|
||||
if (TryGetContainedFormat(oleDataObject, format) is not { } formatId)
|
||||
return null;
|
||||
|
||||
var medium = new STGMEDIUM();
|
||||
var formatEtc = ToFormatEtc(formatId);
|
||||
var result = oleDataObject.GetData(&formatEtc, &medium);
|
||||
if (result != (uint)HRESULT.S_OK)
|
||||
{
|
||||
if (result == DV_E_TYMED)
|
||||
{
|
||||
formatEtc.tymed = TYMED.TYMED_GDI;
|
||||
|
||||
if (oleDataObject.GetData(&formatEtc, &medium) != (uint)HRESULT.S_OK)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -82,6 +70,32 @@ internal static class OleDataObjectHelper
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ushort? TryGetContainedFormat(Win32Com.IDataObject oleDataObject, DataFormat format)
|
||||
{
|
||||
// Bitmap is not a real format, find the first matching platform format, if any.
|
||||
if (DataFormat.Bitmap.Equals(format))
|
||||
{
|
||||
foreach (var imageFormat in ClipboardFormatRegistry.ImageFormats)
|
||||
{
|
||||
if (TryGetContainedFormatCore(oleDataObject, imageFormat) is { } formatId)
|
||||
return formatId;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return TryGetContainedFormatCore(oleDataObject, format);
|
||||
|
||||
static unsafe ushort? TryGetContainedFormatCore(Win32Com.IDataObject oleDataObject, DataFormat format)
|
||||
{
|
||||
Debug.Assert(format != DataFormat.Bitmap);
|
||||
|
||||
var formatId = ClipboardFormatRegistry.GetOrAddFormat(format);
|
||||
var formatEtc = ToFormatEtc(formatId);
|
||||
return oleDataObject.QueryGetData(&formatEtc) == (uint)HRESULT.S_OK ? formatId : null;
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe object? ReadDataFromGdi(nint bitmapHandle)
|
||||
{
|
||||
var bitmap = new BITMAP();
|
||||
|
||||
@@ -36,11 +36,7 @@ internal sealed class OleDataObjectToDataTransferWrapper(Win32Com.IDataObject ol
|
||||
|
||||
foreach (var format in formats)
|
||||
{
|
||||
if (format.Identifier is ClipboardFormatRegistry.DibFormat
|
||||
or ClipboardFormatRegistry.BitmapFormat
|
||||
or ClipboardFormatRegistry.PngFormatMimeType
|
||||
or ClipboardFormatRegistry.PngFormatSystemType)
|
||||
{
|
||||
if (ClipboardFormatRegistry.ImageFormats.Contains(format)) {
|
||||
hasSupportedImageFormat = true;
|
||||
break;
|
||||
}
|
||||
@@ -65,7 +61,7 @@ internal sealed class OleDataObjectToDataTransferWrapper(Win32Com.IDataObject ol
|
||||
if (formatEtc.ptd != IntPtr.Zero)
|
||||
Marshal.FreeCoTaskMem(formatEtc.ptd);
|
||||
|
||||
return ClipboardFormatRegistry.GetFormatById(formatEtc.cfFormat);
|
||||
return ClipboardFormatRegistry.GetOrAddFormat(formatEtc.cfFormat);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user