Skip to content

Commit 4ec9cbb

Browse files
YoshihiroItoCopilotMrJul
authored
Support Windows shell virtual files in drag and drop (#21907)
* Add Win32 virtual file drop support * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * docs(win32): clarify virtual file disposal * fix(win32): handle empty virtual file reads * Address virtual file data review feedback * Address virtual stream review feedback * Address final virtual stream review nits --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
1 parent 79ff4ff commit 4ec9cbb

7 files changed

Lines changed: 722 additions & 6 deletions

File tree

src/Windows/Avalonia.Win32/Avalonia.Win32.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
<ItemGroup>
3636
<InternalsVisibleTo Include="Avalonia.Win32.Interoperability, PublicKey=$(AvaloniaPublicKey)" />
3737
<InternalsVisibleTo Include="Avalonia.WinUI, PublicKey=$(AvaloniaPublicKey)" />
38+
<InternalsVisibleTo Include="Avalonia.IntegrationTests.Win32, PublicKey=$(AvaloniaPublicKey)" />
3839
</ItemGroup>
3940
<ItemGroup>
4041
<!-- By default, any projects supports Windows, Linux, MacOS platforms. -->

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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1640,6 +1640,14 @@ public static IntPtr GetClassLongPtr(IntPtr hWnd, int nIndex)
16401640
internal static extern int CoCreateInstance(in Guid clsid,
16411641
IntPtr ignore1, int ignore2, in Guid iid, [Out] out IntPtr pUnkOuter);
16421642

1643+
[DllImport("ole32.dll", PreserveSig = true)]
1644+
internal static extern int CoMarshalInterThreadInterfaceInStream(
1645+
ref Guid riid, IntPtr unknown, out IntPtr stream);
1646+
1647+
[DllImport("ole32.dll", PreserveSig = true)]
1648+
internal static extern int CoGetInterfaceAndReleaseStream(
1649+
IntPtr stream, ref Guid riid, out IntPtr unknown);
1650+
16431651
internal static T CreateInstance<T>(in Guid clsid, in Guid iid) where T : IUnknown
16441652
{
16451653
var hresult = CoCreateInstance(in clsid, IntPtr.Zero, 1, in iid, out IntPtr pUnk);
@@ -2646,6 +2654,7 @@ public APPBARDATA()
26462654
public const uint DV_E_FORMATETC = 0x80040064;
26472655
public const uint OLE_E_ADVISENOTSUPPORTED = 0x80040003;
26482656
public const uint COR_E_OBJECTDISPOSED = 0x80131622;
2657+
public const int STATFLAG_NONAME = 1;
26492658

26502659
[StructLayout(LayoutKind.Sequential)]
26512660
public struct DROPFILES
@@ -2674,6 +2683,41 @@ public struct FORMATETC
26742683
public TYMED tymed;
26752684
}
26762685

2686+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
2687+
internal unsafe struct FILEDESCRIPTORW
2688+
{
2689+
public const uint FD_FILESIZE = 0x00000040;
2690+
public const int FileNameLength = 260;
2691+
2692+
public uint dwFlags;
2693+
public Guid clsid;
2694+
public SIZE sizel;
2695+
public POINT pointl;
2696+
public uint dwFileAttributes;
2697+
public FILETIME ftCreationTime;
2698+
public FILETIME ftLastAccessTime;
2699+
public FILETIME ftLastWriteTime;
2700+
public uint nFileSizeHigh;
2701+
public uint nFileSizeLow;
2702+
public fixed char cFileName[FileNameLength];
2703+
}
2704+
2705+
[StructLayout(LayoutKind.Sequential)]
2706+
internal struct STATSTG
2707+
{
2708+
public IntPtr pwcsName;
2709+
public uint type;
2710+
public ulong cbSize;
2711+
public FILETIME mtime;
2712+
public FILETIME ctime;
2713+
public FILETIME atime;
2714+
public uint grfMode;
2715+
public uint grfLocksSupported;
2716+
public Guid clsid;
2717+
public uint grfStateBits;
2718+
public uint reserved;
2719+
}
2720+
26772721
[Flags]
26782722
public enum GlobalAllocFlags : uint
26792723
{

src/Windows/Avalonia.Win32/OleDataObjectHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ private static List<string> ReadFileNamesFromHGlobal(IntPtr hGlobal)
332332
return files;
333333
}
334334

335-
private static byte[] ReadBytesFromHGlobal(IntPtr hGlobal)
335+
internal static byte[] ReadBytesFromHGlobal(IntPtr hGlobal)
336336
{
337337
var source = GlobalLock(hGlobal);
338338
try

src/Windows/Avalonia.Win32/OleDataObjectToDataTransferWrapper.cs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.Runtime.InteropServices;
44
using System.Runtime.InteropServices.ComTypes;
@@ -33,20 +33,34 @@ protected override DataFormat[] ProvideFormats()
3333
formats.Add(format);
3434

3535
bool hasSupportedImageFormat = false;
36+
bool hasFile = false;
37+
bool hasFileGroupDescriptor = false;
38+
bool hasFileContents = false;
3639

3740
foreach (var format in formats)
3841
{
39-
if (ClipboardFormatRegistry.ImageFormats.Contains(format)) {
42+
if (ClipboardFormatRegistry.ImageFormats.Contains(format))
4043
hasSupportedImageFormat = true;
41-
break;
42-
}
44+
45+
if (DataFormat.File.Equals(format))
46+
hasFile = true;
47+
else if (OleVirtualFileData.FileGroupDescriptorFormat.Equals(format))
48+
hasFileGroupDescriptor = true;
49+
else if (OleVirtualFileData.FileContentsFormat.Equals(format))
50+
hasFileContents = true;
4351
}
4452

4553
if (hasSupportedImageFormat)
4654
{
4755
formats.Add(DataFormat.Bitmap);
4856
}
4957

58+
// Shell virtual files have descriptors and indexed contents, but no CF_HDROP path.
59+
if (hasFileGroupDescriptor && hasFileContents && !hasFile)
60+
{
61+
formats.Add(DataFormat.File);
62+
}
63+
5064
return formats.ToArray();
5165

5266
static unsafe DataFormat? Next(IEnumFORMATETC enumFormat)
@@ -87,14 +101,21 @@ protected override PlatformDataTransferItem[] ProvideItems()
87101
foreach (var storageItem in storageItems)
88102
items.Add(PlatformDataTransferItem.Create(DataFormat.File, storageItem));
89103
}
104+
else if (OleVirtualFileData.TryCreateFiles(_oleDataObject) is { Count: > 0 } virtualFiles)
105+
{
106+
hasFiles = true;
107+
108+
foreach (var virtualFile in virtualFiles)
109+
items.Add(PlatformDataTransferItem.Create(DataFormat.File, virtualFile));
110+
}
90111
}
91112
else
92113
(nonFileFormats ??= new()).Add(format);
93114
}
94115

95116
// Single item containing all formats except for DataFormat.File.
96117
if (nonFileFormats is not null)
97-
items.Add(new OleDataObjectToDataTransferItemWrapper(_oleDataObject, Formats));
118+
items.Add(new OleDataObjectToDataTransferItemWrapper(_oleDataObject, nonFileFormats.ToArray()));
98119

99120
return items.ToArray();
100121
}

0 commit comments

Comments
 (0)