Skip to content

Commit 5cd0eae

Browse files
committed
[WIP] Improve drop from external programs
1 parent f611e4d commit 5cd0eae

File tree

9 files changed

+442
-28
lines changed

9 files changed

+442
-28
lines changed

Files.Launcher/Files.Launcher.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@
132132
<Compile Include="Program.cs" />
133133
<Compile Include="Properties\AssemblyInfo.cs" />
134134
<Compile Include="QuickLook.cs" />
135+
<Compile Include="RemoteDataObject.cs" />
135136
<Compile Include="Win32API.cs" />
136137
<Compile Include="Win32API_ContextMenu.cs" />
137138
</ItemGroup>

Files.Launcher/Program.cs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -867,10 +867,45 @@ await Win32API.StartSTATask(() =>
867867

868868
case "DragDrop":
869869
var dropPath = (string)message["droppath"];
870-
await Win32API.StartSTATask(() =>
870+
var result2 = await Win32API.StartSTATask(() =>
871871
{
872-
return false;
872+
var rdo = new RemoteDataObject(System.Windows.Forms.Clipboard.GetDataObject());
873+
874+
foreach (RemoteDataObject.DataPackage package in rdo.GetRemoteData())
875+
{
876+
try
877+
{
878+
if (package.ItemType == RemoteDataObject.StorageType.File)
879+
{
880+
string directoryPath = Path.GetDirectoryName(dropPath);
881+
if (!Directory.Exists(directoryPath))
882+
{
883+
Directory.CreateDirectory(directoryPath);
884+
}
885+
886+
string uniqueName = Win32API.GenerateUniquePath(Path.Combine(dropPath, package.Name));
887+
using (FileStream stream = new FileStream(uniqueName, FileMode.CreateNew))
888+
{
889+
package.ContentStream.CopyTo(stream);
890+
}
891+
}
892+
else
893+
{
894+
string directoryPath = Path.Combine(dropPath, package.Name);
895+
if (!Directory.Exists(directoryPath))
896+
{
897+
Directory.CreateDirectory(directoryPath);
898+
}
899+
}
900+
}
901+
finally
902+
{
903+
package.Dispose();
904+
}
905+
}
906+
return true;
873907
});
908+
await Win32API.SendMessageAsync(connection, new ValueSet() { { "Success", result2 } }, message.Get("RequestID", (string)null));
874909
break;
875910

876911
case "DeleteItem":

Files.Launcher/RemoteDataObject.cs

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Reflection;
5+
using System.Runtime.InteropServices;
6+
using System.Runtime.InteropServices.ComTypes;
7+
using System.Windows.Forms;
8+
using Vanara.PInvoke;
9+
using STATSTG = System.Runtime.InteropServices.ComTypes.STATSTG;
10+
11+
namespace FilesFullTrust
12+
{
13+
public class RemoteDataObject
14+
{
15+
/// <summary>
16+
/// Holds the <see cref="System.Windows.IDataObject"/> that this class is wrapping
17+
/// </summary>
18+
private System.Windows.Forms.IDataObject underlyingDataObject;
19+
20+
/// <summary>
21+
/// Holds the <see cref="System.Runtime.InteropServices.ComTypes.IDataObject"/> interface to the <see cref="System.Windows.IDataObject"/> that this class is wrapping.
22+
/// </summary>
23+
private System.Runtime.InteropServices.ComTypes.IDataObject comUnderlyingDataObject;
24+
25+
/// <summary>
26+
/// Holds the internal ole <see cref="System.Windows.IDataObject"/> to the <see cref="System.Windows.IDataObject"/> that this class is wrapping.
27+
/// </summary>
28+
private System.Windows.Forms.IDataObject oleUnderlyingDataObject;
29+
30+
/// <summary>
31+
/// Holds the <see cref="MethodInfo"/> of the "GetDataFromHGLOBAL" method of the internal ole <see cref="System.Windows.IDataObject"/>.
32+
/// </summary>
33+
private MethodInfo getDataFromHGLOBALMethod;
34+
35+
/// <summary>
36+
/// Initializes a new instance of the class.
37+
/// </summary>
38+
/// <param name="underlyingDataObject">The underlying data object to wrap.</param>
39+
public RemoteDataObject(System.Windows.Forms.IDataObject underlyingDataObject)
40+
{
41+
//get the underlying dataobject and its ComType IDataObject interface to it
42+
this.underlyingDataObject = underlyingDataObject;
43+
comUnderlyingDataObject = (System.Runtime.InteropServices.ComTypes.IDataObject)this.underlyingDataObject;
44+
45+
//get the internal ole dataobject and its GetDataFromHGLOBAL so it can be called later
46+
FieldInfo innerDataField = this.underlyingDataObject.GetType().GetField("innerData", BindingFlags.NonPublic | BindingFlags.Instance);
47+
oleUnderlyingDataObject = (System.Windows.Forms.IDataObject)innerDataField.GetValue(this.underlyingDataObject);
48+
getDataFromHGLOBALMethod = oleUnderlyingDataObject.GetType().GetMethod("GetDataFromHGLOBAL", BindingFlags.NonPublic | BindingFlags.Instance);
49+
}
50+
51+
public IEnumerable<DataPackage> GetRemoteData()
52+
{
53+
string FormatName = string.Empty;
54+
55+
if (GetDataPresent(Shell32.ShellClipboardFormat.CFSTR_FILEDESCRIPTORW))
56+
{
57+
FormatName = Shell32.ShellClipboardFormat.CFSTR_FILEDESCRIPTORW;
58+
}
59+
else if (GetDataPresent(Shell32.ShellClipboardFormat.CFSTR_FILEDESCRIPTORA))
60+
{
61+
FormatName = Shell32.ShellClipboardFormat.CFSTR_FILEDESCRIPTORA;
62+
}
63+
64+
if (string.IsNullOrEmpty(FormatName))
65+
{
66+
yield break;
67+
}
68+
else
69+
{
70+
if (underlyingDataObject.GetData(FormatName, true) is MemoryStream FileGroupDescriptorStream)
71+
{
72+
try
73+
{
74+
byte[] FileGroupDescriptorBytes = FileGroupDescriptorStream.ToArray();
75+
76+
IntPtr FileGroupDescriptorAPointer = Marshal.AllocHGlobal(FileGroupDescriptorBytes.Length);
77+
78+
try
79+
{
80+
Marshal.Copy(FileGroupDescriptorBytes, 0, FileGroupDescriptorAPointer, FileGroupDescriptorBytes.Length);
81+
82+
int ItemCount = Marshal.ReadInt32(FileGroupDescriptorAPointer);
83+
84+
IntPtr FileDescriptorPointer = (IntPtr)(FileGroupDescriptorAPointer.ToInt64() + Marshal.SizeOf(ItemCount));
85+
86+
for (int FileDescriptorIndex = 0; FileDescriptorIndex < ItemCount; FileDescriptorIndex++)
87+
{
88+
Shell32.FILEDESCRIPTOR FileDescriptor = Marshal.PtrToStructure<Shell32.FILEDESCRIPTOR>(FileDescriptorPointer);
89+
90+
if (FileDescriptor.dwFileAttributes.HasFlag(FileFlagsAndAttributes.FILE_ATTRIBUTE_DIRECTORY))
91+
{
92+
yield return new DataPackage(FileDescriptor.cFileName, StorageType.Directroy, null);
93+
}
94+
else
95+
{
96+
yield return new DataPackage(FileDescriptor.cFileName, StorageType.File, GetContentData(Shell32.ShellClipboardFormat.CFSTR_FILECONTENTS, FileDescriptorIndex));
97+
}
98+
99+
FileDescriptorPointer = (IntPtr)(FileDescriptorPointer.ToInt64() + Marshal.SizeOf(FileDescriptor));
100+
}
101+
}
102+
finally
103+
{
104+
Marshal.FreeHGlobal(FileGroupDescriptorAPointer);
105+
}
106+
}
107+
finally
108+
{
109+
FileGroupDescriptorStream.Dispose();
110+
}
111+
}
112+
else
113+
{
114+
yield break;
115+
}
116+
}
117+
}
118+
119+
/// <summary>
120+
/// Retrieves the data associated with the specified data format at the specified index.
121+
/// </summary>
122+
/// <param name="Format">The format of the data to retrieve. See <see cref="T:System.Windows.DataFormats"></see> for predefined formats.</param>
123+
/// <param name="Index">The index of the data to retrieve.</param>
124+
/// <returns>
125+
/// A <see cref="MemoryStream"/> containing the raw data for the specified data format at the specified index.
126+
/// </returns>
127+
private MemoryStream GetContentData(string Format, int Index)
128+
{
129+
//create a FORMATETC struct to request the data with
130+
FORMATETC Formatetc = new FORMATETC
131+
{
132+
cfFormat = (short)DataFormats.GetFormat(Format).Id,
133+
dwAspect = DVASPECT.DVASPECT_CONTENT,
134+
lindex = Index,
135+
ptd = new IntPtr(0),
136+
tymed = TYMED.TYMED_ISTREAM | TYMED.TYMED_ISTORAGE | TYMED.TYMED_HGLOBAL
137+
};
138+
139+
//using the Com IDataObject interface get the data using the defined FORMATETC
140+
comUnderlyingDataObject.GetData(ref Formatetc, out STGMEDIUM Medium);
141+
142+
//retrieve the data depending on the returned store type
143+
switch (Medium.tymed)
144+
{
145+
case TYMED.TYMED_ISTORAGE:
146+
{
147+
//to handle a IStorage it needs to be written into a second unmanaged
148+
//memory mapped storage and then the data can be read from memory into
149+
//a managed byte and returned as a MemoryStream
150+
151+
try
152+
{
153+
//marshal the returned pointer to a IStorage object
154+
Ole32.IStorage IStorageObject = (Ole32.IStorage)Marshal.GetObjectForIUnknown(Medium.unionmember);
155+
156+
try
157+
{
158+
//create a ILockBytes (unmanaged byte array) and then create a IStorage using the byte array as a backing store
159+
Ole32.CreateILockBytesOnHGlobal(IntPtr.Zero, true, out Ole32.ILockBytes LockBytes);
160+
Ole32.StgCreateDocfileOnILockBytes(LockBytes, STGM.STGM_READWRITE | STGM.STGM_SHARE_EXCLUSIVE | STGM.STGM_CREATE, ppstgOpen: out Ole32.IStorage IStorageObjectCopy);
161+
162+
try
163+
{
164+
//copy the returned IStorage into the new IStorage
165+
IStorageObject.CopyTo(snbExclude: IntPtr.Zero, pstgDest: IStorageObjectCopy);
166+
LockBytes.Flush();
167+
IStorageObjectCopy.Commit(Ole32.STGC.STGC_DEFAULT);
168+
169+
//get the STATSTG of the LockBytes to determine how many bytes were written to it
170+
LockBytes.Stat(out STATSTG LockBytesStat, Ole32.STATFLAG.STATFLAG_NONAME);
171+
172+
int CbSize = Convert.ToInt32(LockBytesStat.cbSize);
173+
174+
IntPtr LockBytesContentPtr = Marshal.AllocHGlobal(CbSize);
175+
176+
try
177+
{
178+
LockBytes.ReadAt(0, LockBytesContentPtr, Convert.ToUInt32(LockBytesStat.cbSize), out _);
179+
180+
byte[] LockBytesContent = new byte[CbSize];
181+
182+
Marshal.Copy(LockBytesContentPtr, LockBytesContent, 0, LockBytesContent.Length);
183+
184+
return new MemoryStream(LockBytesContent);
185+
}
186+
finally
187+
{
188+
Marshal.FreeHGlobal(LockBytesContentPtr);
189+
}
190+
}
191+
finally
192+
{
193+
Marshal.ReleaseComObject(IStorageObjectCopy);
194+
Marshal.ReleaseComObject(LockBytes);
195+
}
196+
}
197+
finally
198+
{
199+
Marshal.ReleaseComObject(IStorageObject);
200+
}
201+
}
202+
finally
203+
{
204+
Marshal.Release(Medium.unionmember);
205+
}
206+
}
207+
case TYMED.TYMED_ISTREAM:
208+
{
209+
//to handle a IStream it needs to be read into a managed byte and
210+
//returned as a MemoryStream
211+
212+
IStream IStreamObject = (IStream)Marshal.GetObjectForIUnknown(Medium.unionmember);
213+
214+
try
215+
{
216+
//get the STATSTG of the IStream to determine how many bytes are in it
217+
IStreamObject.Stat(out STATSTG iStreamStat, 0);
218+
219+
byte[] IStreamContent = new byte[(Convert.ToInt32(iStreamStat.cbSize))];
220+
221+
IStreamObject.Read(IStreamContent, IStreamContent.Length, IntPtr.Zero);
222+
223+
return new MemoryStream(IStreamContent);
224+
}
225+
finally
226+
{
227+
Marshal.Release(Medium.unionmember);
228+
Marshal.ReleaseComObject(IStreamObject);
229+
}
230+
}
231+
case TYMED.TYMED_HGLOBAL:
232+
{
233+
//to handle a HGlobal the exisitng "GetDataFromHGLOBAL" method is invoked via
234+
//reflection
235+
236+
try
237+
{
238+
return (MemoryStream)getDataFromHGLOBALMethod.Invoke(oleUnderlyingDataObject, new object[] { DataFormats.GetFormat(Formatetc.cfFormat).Name, Medium.unionmember });
239+
}
240+
finally
241+
{
242+
Marshal.Release(Medium.unionmember);
243+
}
244+
}
245+
default:
246+
{
247+
return null;
248+
}
249+
}
250+
}
251+
252+
/// <summary>
253+
/// Determines whether data stored in this instance is associated with, or can be converted to, the specified format.
254+
/// </summary>
255+
/// <param name="format">The format for which to check. See <see cref="T:System.Windows.DataFormats"></see> for predefined formats.</param>
256+
/// <returns>
257+
/// true if data stored in this instance is associated with, or can be converted to, the specified format; otherwise false.
258+
/// </returns>
259+
public bool GetDataPresent(string format)
260+
{
261+
return underlyingDataObject.GetDataPresent(format);
262+
}
263+
264+
public sealed class DataPackage : IDisposable
265+
{
266+
public StorageType ItemType { get; }
267+
268+
public MemoryStream ContentStream { get; }
269+
270+
public string Name { get; }
271+
272+
public DataPackage(string Name, StorageType ItemType, MemoryStream ContentStream)
273+
{
274+
this.Name = Name;
275+
this.ItemType = ItemType;
276+
this.ContentStream = ContentStream;
277+
}
278+
279+
public void Dispose()
280+
{
281+
GC.SuppressFinalize(this);
282+
ContentStream?.Dispose();
283+
}
284+
285+
~DataPackage()
286+
{
287+
Dispose();
288+
}
289+
}
290+
291+
public enum StorageType
292+
{
293+
File = 0,
294+
Directroy = 1
295+
}
296+
}
297+
}

0 commit comments

Comments
 (0)