Browse Source

Fix Win32 clipboard not returning bitmap in some cases

pull/20506/head
Julien Lebosquain 2 weeks ago
parent
commit
e882ae27fc
No known key found for this signature in database GPG Key ID: 1833CAD10ACC46FD
  1. 47
      src/Windows/Avalonia.Win32/ClipboardFormatRegistry.cs
  2. 53
      src/Windows/Avalonia.Win32/DataTransferToOleDataObjectWrapper.cs
  3. 2
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  4. 52
      src/Windows/Avalonia.Win32/OleDataObjectHelper.cs
  5. 8
      src/Windows/Avalonia.Win32/OleDataObjectToDataTransferWrapper.cs

47
src/Windows/Avalonia.Win32/ClipboardFormatRegistry.cs

@ -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 DataFormat PngSystemDataFormat = new DataFormat<Bitmap>(DataFormatKind.Platform, "PNG");
public static DataFormat PngMimeDataFormat = new DataFormat<Bitmap>(DataFormatKind.Platform, "image/png");
public static DataFormat HBitmapDataFormat = new DataFormat<Bitmap>(DataFormatKind.Platform, "CF_BITMAP");
public static DataFormat DibDataFormat = new DataFormat<Bitmap>(DataFormatKind.Platform, "CF_DIB");
public static 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))

53
src/Windows/Avalonia.Win32/DataTransferToOleDataObjectWrapper.cs

@ -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;

2
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -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

52
src/Windows/Avalonia.Win32/OleDataObjectHelper.cs

@ -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();

8
src/Windows/Avalonia.Win32/OleDataObjectToDataTransferWrapper.cs

@ -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);
}
}

Loading…
Cancel
Save