Browse Source

Fix Win32 clipboard not returning bitmaps in some cases (#20506)

* Fix Win32 clipboard not returning bitmap in some cases

* Make ClipboardFormatRegistry fields read-only
pull/20449/head
Julien Lebosquain 1 month ago
committed by GitHub
parent
commit
97e3d80a26
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  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;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -12,19 +13,15 @@ namespace Avalonia.Win32
{ {
private const int MaxFormatNameLength = 260; private const int MaxFormatNameLength = 260;
private const string AppPrefix = "avn-app-fmt:"; 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 = []; private static readonly List<(DataFormat Format, ushort Id)> s_formats = [];
public static DataFormat PngSystemDataFormat = DataFormat.FromSystemName<Bitmap>(PngFormatSystemType, AppPrefix); public static readonly DataFormat PngSystemDataFormat = new DataFormat<Bitmap>(DataFormatKind.Platform, "PNG");
public static DataFormat PngMimeDataFormat = DataFormat.FromSystemName<Bitmap>(PngFormatMimeType, AppPrefix); public static readonly DataFormat PngMimeDataFormat = new DataFormat<Bitmap>(DataFormatKind.Platform, "image/png");
public static DataFormat HBitmapDataFormat = DataFormat.FromSystemName<Bitmap>(BitmapFormat, AppPrefix); public static readonly DataFormat HBitmapDataFormat = new DataFormat<Bitmap>(DataFormatKind.Platform, "CF_BITMAP");
public static DataFormat DibDataFormat = DataFormat.FromSystemName<Bitmap>(DibFormat, AppPrefix); public static readonly DataFormat DibDataFormat = new DataFormat<Bitmap>(DataFormatKind.Platform, "CF_DIB");
public static DataFormat DibV5DataFormat = DataFormat.FromSystemName<Bitmap>(DibV5Format, AppPrefix); 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]; public static DataFormat[] ImageFormats = [PngMimeDataFormat, PngSystemDataFormat, DibDataFormat, DibV5DataFormat, HBitmapDataFormat];
static ClipboardFormatRegistry() static ClipboardFormatRegistry()
@ -44,12 +41,12 @@ namespace Avalonia.Win32
var buffer = StringBuilderCache.Acquire(MaxFormatNameLength); var buffer = StringBuilderCache.Acquire(MaxFormatNameLength);
if (UnmanagedMethods.GetClipboardFormatName(id, buffer, buffer.Capacity) > 0) if (UnmanagedMethods.GetClipboardFormatName(id, buffer, buffer.Capacity) > 0)
return StringBuilderCache.GetStringAndRelease(buffer); return StringBuilderCache.GetStringAndRelease(buffer);
if (Enum.IsDefined(typeof(UnmanagedMethods.ClipboardFormat), (int)id)) if (Enum.IsDefined(typeof(UnmanagedMethods.ClipboardFormat), id))
return Enum.GetName(typeof(UnmanagedMethods.ClipboardFormat), (int)id)!; return Enum.GetName(typeof(UnmanagedMethods.ClipboardFormat), id)!;
return $"Unknown_Format_{id}"; return $"Unknown_Format_{id}";
} }
public static DataFormat GetFormatById(ushort id) public static DataFormat GetOrAddFormat(ushort id)
{ {
lock (s_formats) 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) 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) for (var i = 0; i < s_formats.Count; ++i)
{ {
if (s_formats[i].Format.Equals(format)) 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; _current = current;
} }
public FormatEnumerator(IReadOnlyList<DataFormat> dataFormats) public FormatEnumerator(ushort[] formatIds)
{ {
List<FORMATETC> formats = new List<FORMATETC>(); _formats = formatIds.Select(OleDataObjectHelper.ToFormatEtc).ToArray();
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();
_current = 0; _current = 0;
} }
@ -98,6 +82,9 @@ internal class DataTransferToOleDataObjectWrapper(IDataTransfer dataTransfer)
public bool IsDisposed public bool IsDisposed
=> DataTransfer is null; => DataTransfer is null;
private ushort[] FormatIds
=> field ??= CalcFormatIds();
public event Action? OnDestroyed; public event Action? OnDestroyed;
unsafe int Win32Com.IDataObject.DAdvise(FORMATETC* pFormatetc, int advf, void* adviseSink) 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)); throw new COMException(nameof(COR_E_OBJECTDISPOSED), unchecked((int)HRESULT.E_NOTIMPL));
if ((DATADIR)direction == DATADIR.DATADIR_GET) 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)); throw new COMException(nameof(HRESULT.E_NOTIMPL), unchecked((int)HRESULT.E_NOTIMPL));
} }
@ -166,7 +153,8 @@ internal class DataTransferToOleDataObjectWrapper(IDataTransfer dataTransfer)
{ {
dataFormat = null; 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; result = DV_E_TYMED;
dataFormat = null; dataFormat = null;
@ -185,17 +173,38 @@ internal class DataTransferToOleDataObjectWrapper(IDataTransfer dataTransfer)
return false; return false;
} }
dataFormat = ClipboardFormatRegistry.GetFormatById(format->cfFormat); if (!FormatIds.Contains(format->cfFormat))
if (!DataTransfer.Contains(dataFormat) && !ClipboardFormatRegistry.ImageFormats.Contains(dataFormat))
{ {
result = DV_E_FORMATETC; result = DV_E_FORMATETC;
return false; return false;
} }
dataFormat = ClipboardFormatRegistry.GetOrAddFormat(format->cfFormat);
result = (uint)HRESULT.S_OK; result = (uint)HRESULT.S_OK;
return true; 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) unsafe uint Win32Com.IDataObject.SetData(FORMATETC* pformatetc, STGMEDIUM* pmedium, int fRelease)
=> (uint)HRESULT.E_NOTIMPL; => (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 MDT_DEFAULT = MDT_EFFECTIVE_DPI
} }
public enum ClipboardFormat public enum ClipboardFormat : ushort
{ {
/// <summary> /// <summary>
/// A handle to a bitmap /// A handle to a bitmap

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

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -24,39 +25,26 @@ internal static class OleDataObjectHelper
{ {
private const int SRCCOPY = 0x00CC0020; private const int SRCCOPY = 0x00CC0020;
public static FORMATETC ToFormatEtc(this DataFormat format, bool isGdi = false) public static FORMATETC ToFormatEtc(ushort formatId)
=> new() => new()
{ {
cfFormat = ClipboardFormatRegistry.GetFormatId(format), cfFormat = formatId,
dwAspect = DVASPECT.DVASPECT_CONTENT, dwAspect = DVASPECT.DVASPECT_CONTENT,
ptd = IntPtr.Zero, ptd = IntPtr.Zero,
lindex = -1, 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) public static unsafe object? TryGet(this Win32Com.IDataObject oleDataObject, DataFormat format)
{ {
var formatEtc = format.ToFormatEtc(); if (TryGetContainedFormat(oleDataObject, format) is not { } formatId)
if (oleDataObject.QueryGetData(&formatEtc) != (uint)HRESULT.S_OK)
return null; return null;
var medium = new STGMEDIUM(); var medium = new STGMEDIUM();
var formatEtc = ToFormatEtc(formatId);
var result = oleDataObject.GetData(&formatEtc, &medium); var result = oleDataObject.GetData(&formatEtc, &medium);
if (result != (uint)HRESULT.S_OK) if (result != (uint)HRESULT.S_OK)
{ return null;
if (result == DV_E_TYMED)
{
formatEtc.tymed = TYMED.TYMED_GDI;
if (oleDataObject.GetData(&formatEtc, &medium) != (uint)HRESULT.S_OK)
{
return null;
}
}
else
return null;
}
try try
{ {
@ -82,6 +70,32 @@ internal static class OleDataObjectHelper
return null; 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) private static unsafe object? ReadDataFromGdi(nint bitmapHandle)
{ {
var bitmap = new BITMAP(); 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) foreach (var format in formats)
{ {
if (format.Identifier is ClipboardFormatRegistry.DibFormat if (ClipboardFormatRegistry.ImageFormats.Contains(format)) {
or ClipboardFormatRegistry.BitmapFormat
or ClipboardFormatRegistry.PngFormatMimeType
or ClipboardFormatRegistry.PngFormatSystemType)
{
hasSupportedImageFormat = true; hasSupportedImageFormat = true;
break; break;
} }
@ -65,7 +61,7 @@ internal sealed class OleDataObjectToDataTransferWrapper(Win32Com.IDataObject ol
if (formatEtc.ptd != IntPtr.Zero) if (formatEtc.ptd != IntPtr.Zero)
Marshal.FreeCoTaskMem(formatEtc.ptd); Marshal.FreeCoTaskMem(formatEtc.ptd);
return ClipboardFormatRegistry.GetFormatById(formatEtc.cfFormat); return ClipboardFormatRegistry.GetOrAddFormat(formatEtc.cfFormat);
} }
} }

Loading…
Cancel
Save