mirror of
https://github.com/doublecmd/doublecmd.git
synced 2026-06-21 09:58:13 +00:00
ADD: Virtual file drag-and-drop and clipboard paste support (#2577)
* Add virtual file drag-and-drop and clipboard paste support Adds support for virtual file operations using Windows CFSTR_FILEDESCRIPTOR and CFSTR_FILECONTENTS formats. Enables file transfers from sources like VMware Fusion Mac-to-Windows clipboard, Remote Desktop, and OneDrive placeholders. Changes to uOleDragDrop.pas: - Fix memory leaks: Add missing ReleaseStgMedium and GlobalUnlock calls - Change GetDropFileGroupFilenames to class function for clipboard reuse - Change SaveCfuContentToFile to class function for clipboard reuse - Wrap operations in try-finally blocks for proper cleanup Changes to uClipboard.pas: - Add OLE clipboard support via OleGetClipboard and IDataObject - Check for CFSTR_FILEDESCRIPTORW/CFSTR_FILEGROUPDESCRIPTOR formats - Extract virtual files using drag-and-drop extraction logic - Detect lazy materialization and delegate to Windows Shell paste - Keep clipboard open for normal files to support lazy materialization Tested with VMware Fusion running ARM-based Windows guests. May also work for other virtual file scenarios that were not available for testing. All existing CF_HDROP operations continue to work unchanged. --------- Co-authored-by: Alexander Koblov <alexx2000@mail.ru>
This commit is contained in:
parent
feafbef9d7
commit
5e2980926e
3 changed files with 224 additions and 75 deletions
|
|
@ -108,7 +108,7 @@ implementation
|
|||
|
||||
uses
|
||||
{$IF DEFINED(MSWINDOWS)}
|
||||
Clipbrd, Windows, ActiveX, uOleDragDrop, fMain, uShellContextMenu, uOSForms
|
||||
Clipbrd, Windows, ActiveX, Dialogs, DCOSUtils, uOleDragDrop, fMain, uShellContextMenu, uOSForms
|
||||
{$ELSEIF DEFINED(UNIX_not_DARWIN)}
|
||||
Clipbrd, LCLIntf
|
||||
{$ELSEIF DEFINED(DARWIN)}
|
||||
|
|
@ -127,11 +127,11 @@ begin
|
|||
CFU_UNIFORM_RESOURCE_LOCATOR := RegisterClipboardFormat(CFSTR_UNIFORM_RESOURCE_LOCATOR);
|
||||
CFU_UNIFORM_RESOURCE_LOCATORW := RegisterClipboardFormat(CFSTR_UNIFORM_RESOURCE_LOCATORW);
|
||||
CFU_SHELL_IDLIST_ARRAY := RegisterClipboardFormat(CFSTR_SHELL_IDLIST_ARRAY);
|
||||
CFU_FILECONTENTS := $8000 OR RegisterClipboardFormat(CFSTR_FILECONTENTS) And $7FFF;
|
||||
CFU_FILEGROUPDESCRIPTOR := $8000 OR RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR) And $7FFF;
|
||||
CFU_FILEGROUPDESCRIPTORW := $8000 OR RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW) And $7FFF;
|
||||
CFU_HTML := $8000 OR RegisterClipboardFormat(CFSTR_HTMLFORMAT) And $7FFF;
|
||||
CFU_RICHTEXT := $8000 OR RegisterClipboardFormat(CFSTR_RICHTEXTFORMAT) And $7FFF;
|
||||
CFU_FILECONTENTS := $8000 OR (RegisterClipboardFormat(CFSTR_FILECONTENTS) And $7FFF);
|
||||
CFU_FILEGROUPDESCRIPTOR := $8000 OR (RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR) And $7FFF);
|
||||
CFU_FILEGROUPDESCRIPTORW := $8000 OR (RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW) And $7FFF);
|
||||
CFU_HTML := $8000 OR (RegisterClipboardFormat(CFSTR_HTMLFORMAT) And $7FFF);
|
||||
CFU_RICHTEXT := $8000 OR (RegisterClipboardFormat(CFSTR_RICHTEXTFORMAT) And $7FFF);
|
||||
|
||||
{$ELSEIF DEFINED(UNIX_not_DARWIN)}
|
||||
|
||||
|
|
@ -616,54 +616,181 @@ var
|
|||
hGlobalBuffer: HGLOBAL;
|
||||
pBuffer: LPVOID;
|
||||
PreferredEffect: DWORD;
|
||||
dataObj: IDataObject;
|
||||
Medium: TSTGMedium;
|
||||
ChosenFormat: TFormatETC;
|
||||
hr: HRESULT;
|
||||
HasVirtualFiles: Boolean;
|
||||
begin
|
||||
|
||||
filenames := nil;
|
||||
Result := False;
|
||||
|
||||
// Default to 'copy' if effect hasn't been given.
|
||||
HasVirtualFiles := False;
|
||||
ClipboardOp := ClipboardCopy;
|
||||
|
||||
// Try to get IDataObject from clipboard for virtual file support
|
||||
hr := OleGetClipboard(dataObj);
|
||||
if Succeeded(hr) and Assigned(dataObj) then
|
||||
begin
|
||||
try
|
||||
// Check for preferred drop effect
|
||||
if CFU_PREFERRED_DROPEFFECT <> 0 then
|
||||
begin
|
||||
ChosenFormat.CfFormat := CFU_PREFERRED_DROPEFFECT;
|
||||
ChosenFormat.ptd := nil;
|
||||
ChosenFormat.dwAspect := DVASPECT_CONTENT;
|
||||
ChosenFormat.lindex := -1;
|
||||
ChosenFormat.tymed := TYMED_HGLOBAL;
|
||||
|
||||
if dataObj.GetData(ChosenFormat, Medium) = S_OK then
|
||||
begin
|
||||
try
|
||||
if Medium.Tymed = TYMED_HGLOBAL then
|
||||
begin
|
||||
pBuffer := GlobalLock(Medium.hGlobal);
|
||||
if pBuffer <> nil then
|
||||
begin
|
||||
try
|
||||
PreferredEffect := PDWORD(pBuffer)^;
|
||||
if PreferredEffect = DROPEFFECT_COPY then ClipboardOp := ClipboardCopy
|
||||
else if PreferredEffect = DROPEFFECT_MOVE then ClipboardOp := ClipboardCut;
|
||||
finally
|
||||
GlobalUnlock(Medium.hGlobal);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
finally
|
||||
ReleaseStgMedium(@Medium);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
// Check for virtual files
|
||||
if (CFU_FILECONTENTS <> 0) then
|
||||
begin
|
||||
// Try Unicode version first
|
||||
if (CFU_FILEGROUPDESCRIPTORW <> 0) then
|
||||
begin
|
||||
ChosenFormat.CfFormat := CFU_FILEGROUPDESCRIPTORW;
|
||||
ChosenFormat.ptd := nil;
|
||||
ChosenFormat.dwAspect := DVASPECT_CONTENT;
|
||||
ChosenFormat.lindex := -1;
|
||||
ChosenFormat.tymed := TYMED_HGLOBAL;
|
||||
|
||||
hr := dataObj.QueryGetData(ChosenFormat);
|
||||
if hr = S_OK then
|
||||
begin
|
||||
hr := dataObj.GetData(ChosenFormat, Medium);
|
||||
if hr = S_OK then
|
||||
begin
|
||||
try
|
||||
if Medium.Tymed = TYMED_HGLOBAL then
|
||||
begin
|
||||
filenames := uOleDragDrop.TFileDropTarget.GetDropFileGroupFilenames(dataObj, Medium, ChosenFormat);
|
||||
HasVirtualFiles := Assigned(filenames) and (filenames.Count > 0);
|
||||
end;
|
||||
finally
|
||||
ReleaseStgMedium(@Medium);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
// Try ANSI version if Unicode didn't work
|
||||
if (not HasVirtualFiles) and (CFU_FILEGROUPDESCRIPTOR <> 0) then
|
||||
begin
|
||||
ChosenFormat.CfFormat := CFU_FILEGROUPDESCRIPTOR;
|
||||
ChosenFormat.ptd := nil;
|
||||
ChosenFormat.dwAspect := DVASPECT_CONTENT;
|
||||
ChosenFormat.lindex := -1;
|
||||
ChosenFormat.tymed := TYMED_HGLOBAL;
|
||||
|
||||
hr := dataObj.QueryGetData(ChosenFormat);
|
||||
if hr = S_OK then
|
||||
begin
|
||||
if dataObj.GetData(ChosenFormat, Medium) = S_OK then
|
||||
begin
|
||||
try
|
||||
if Medium.Tymed = TYMED_HGLOBAL then
|
||||
begin
|
||||
filenames := uOleDragDrop.TFileDropTarget.GetDropFileGroupFilenames(dataObj, Medium, ChosenFormat);
|
||||
HasVirtualFiles := Assigned(filenames) and (filenames.Count > 0);
|
||||
end;
|
||||
finally
|
||||
ReleaseStgMedium(@Medium);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
// Success with virtual files?
|
||||
if HasVirtualFiles then
|
||||
begin
|
||||
Result := True;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
finally
|
||||
dataObj := nil;
|
||||
end;
|
||||
end;
|
||||
|
||||
// Fallback to standard CF_HDROP
|
||||
if OpenClipboard(0) = False then Exit;
|
||||
|
||||
if CFU_PREFERRED_DROPEFFECT <> 0 then
|
||||
begin
|
||||
hGlobalBuffer := GetClipboardData(CFU_PREFERRED_DROPEFFECT);
|
||||
if hGlobalBuffer <> 0 then
|
||||
try
|
||||
if CFU_PREFERRED_DROPEFFECT <> 0 then
|
||||
begin
|
||||
pBuffer := GlobalLock(hGlobalBuffer);
|
||||
if pBuffer <> nil then
|
||||
hGlobalBuffer := GetClipboardData(CFU_PREFERRED_DROPEFFECT);
|
||||
if hGlobalBuffer <> 0 then
|
||||
begin
|
||||
PreferredEffect := PDWORD(pBuffer)^;
|
||||
if PreferredEffect = DROPEFFECT_COPY then ClipboardOp := ClipboardCopy
|
||||
else if PreferredEffect = DROPEFFECT_MOVE then ClipboardOp := ClipboardCut;
|
||||
|
||||
GlobalUnlock(hGlobalBuffer);
|
||||
pBuffer := GlobalLock(hGlobalBuffer);
|
||||
if pBuffer <> nil then
|
||||
begin
|
||||
PreferredEffect := PDWORD(pBuffer)^;
|
||||
if PreferredEffect = DROPEFFECT_COPY then ClipboardOp := ClipboardCopy
|
||||
else if PreferredEffect = DROPEFFECT_MOVE then ClipboardOp := ClipboardCut;
|
||||
GlobalUnlock(hGlobalBuffer);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ Now, retrieve file names. }
|
||||
hGlobalBuffer := GetClipboardData(CF_HDROP);
|
||||
|
||||
hGlobalBuffer := GetClipboardData(CF_HDROP);
|
||||
|
||||
if hGlobalBuffer = 0 then
|
||||
begin
|
||||
with frmMain do
|
||||
if hGlobalBuffer = 0 then
|
||||
begin
|
||||
CloseClipboard;
|
||||
uShellContextMenu.PasteFromClipboard(Handle, ActiveFrame.CurrentPath);
|
||||
Exit(False);
|
||||
with frmMain do
|
||||
begin
|
||||
CloseClipboard;
|
||||
uShellContextMenu.PasteFromClipboard(Handle, ActiveFrame.CurrentPath);
|
||||
Exit(False);
|
||||
end;
|
||||
end;
|
||||
|
||||
filenames := uOleDragDrop.TFileDropTarget.GetDropFilenames(hGlobalBuffer);
|
||||
if Assigned(filenames) and (filenames.Count > 0) then
|
||||
begin
|
||||
// Check if first file exists - if not, likely lazy materialization
|
||||
// Use shell paste which handles this properly
|
||||
if not mbFileExists(filenames[0]) then
|
||||
begin
|
||||
with frmMain do
|
||||
begin
|
||||
// Keep clipboard open and use shell paste for lazy files
|
||||
uShellContextMenu.PasteFromClipboard(Handle, ActiveFrame.CurrentPath);
|
||||
// Shell will close clipboard when done
|
||||
Exit(False);
|
||||
end;
|
||||
end;
|
||||
|
||||
// Normal files
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
finally
|
||||
CloseClipboard;
|
||||
end;
|
||||
|
||||
filenames := uOleDragDrop.TFileDropTarget.GetDropFilenames(hGlobalBuffer);
|
||||
|
||||
if Assigned(filenames) then
|
||||
Result := True;
|
||||
|
||||
CloseClipboard;
|
||||
|
||||
end;
|
||||
{$ENDIF}
|
||||
|
||||
|
|
|
|||
|
|
@ -99,8 +99,8 @@ type
|
|||
as a list of UTF-8 strings.
|
||||
@returns(List of filenames or nil in case of an error.)
|
||||
}
|
||||
function GetDropFileGroupFilenames(const dataObj: IDataObject; var Medium: TSTGMedium; Format: TFormatETC): TStringList;
|
||||
function SaveCfuContentToFile(const dataObj:IDataObject; Index:Integer; WantedFilename:String; FileInfo: PFileDescriptorW):boolean;
|
||||
class function GetDropFileGroupFilenames(const dataObj: IDataObject; var Medium: TSTGMedium; Format: TFormatETC): TStringList;
|
||||
class function SaveCfuContentToFile(const dataObj:IDataObject; Index:Integer; WantedFilename:String; FileInfo: PFileDescriptorW):boolean;
|
||||
|
||||
{en
|
||||
Retrieves the text from the CF_UNICODETEXT/CF_TEXT format, will store this in a single file
|
||||
|
|
@ -991,7 +991,7 @@ begin
|
|||
end;
|
||||
|
||||
{ TFileDropTarget.SaveCfuContentToFile }
|
||||
function TFileDropTarget.SaveCfuContentToFile(const dataObj: IDataObject;
|
||||
class function TFileDropTarget.SaveCfuContentToFile(const dataObj: IDataObject;
|
||||
Index: Integer; WantedFilename: String; FileInfo: PFileDescriptorW): boolean;
|
||||
const
|
||||
TEMPFILENAME='CfuContentFile.bin';
|
||||
|
|
@ -1003,37 +1003,44 @@ var
|
|||
hFile: THandle;
|
||||
pvStrm: IStream;
|
||||
statstg: TStatStg;
|
||||
dwSize: LongInt;
|
||||
dwRead: ULONG;
|
||||
AnyPointer: PAnsiChar;
|
||||
InnerFilename: String;
|
||||
StgDocFile: WideString;
|
||||
msStream: TMemoryStream;
|
||||
i64Size, i64Move: {$IF FPC_FULLVERSION < 030002}Int64{$ELSE}QWord{$ENDIF};
|
||||
hr: HRESULT;
|
||||
begin
|
||||
result:=FALSE;
|
||||
InnerFilename:= ExtractFilepath(WantedFilename) + TEMPFILENAME;
|
||||
|
||||
Format.cfFormat := CFU_FILECONTENTS;
|
||||
Format.dwAspect := DVASPECT_CONTENT;
|
||||
Format.lindex := Index;
|
||||
Format.ptd := nil;
|
||||
Format.TYMED := TYMED_ISTREAM OR TYMED_ISTORAGE or TYMED_HGLOBAL;
|
||||
|
||||
if dataObj.GetData(Format, Medium) = S_OK then
|
||||
begin
|
||||
hr := dataObj.GetData(Format, Medium);
|
||||
if hr <> S_OK then Exit;
|
||||
|
||||
try
|
||||
if Medium.TYMED = TYMED_ISTORAGE then
|
||||
begin
|
||||
iStg := IStorage(Medium.pstg);
|
||||
StgDocFile := CeUtf8ToUtf16(InnerFilename);
|
||||
StgCreateDocfile(PWideChar(StgDocFile), STGM_CREATE Or STGM_READWRITE Or STGM_SHARE_EXCLUSIVE, 0, iFile);
|
||||
tIID:=nil;
|
||||
iStg.CopyTo(0, tIID, nil, iFile);
|
||||
iFile.Commit(0);
|
||||
iFile := nil;
|
||||
if StgCreateDocfile(PWideChar(StgDocFile), STGM_CREATE Or STGM_READWRITE Or STGM_SHARE_EXCLUSIVE, 0, iFile) = S_OK then
|
||||
begin
|
||||
tIID:=nil;
|
||||
iStg.CopyTo(0, tIID, nil, iFile);
|
||||
iFile.Commit(0);
|
||||
iFile := nil;
|
||||
end;
|
||||
iStg := nil;
|
||||
end
|
||||
else if Medium.Tymed = TYMED_HGLOBAL then
|
||||
begin
|
||||
AnyPointer := GlobalLock(Medium.HGLOBAL);
|
||||
if AnyPointer <> nil then
|
||||
try
|
||||
hFile := mbFileCreate(InnerFilename);
|
||||
if hFile <> feInvalidHandle then
|
||||
|
|
@ -1044,39 +1051,53 @@ begin
|
|||
finally
|
||||
GlobalUnlock(Medium.HGLOBAL);
|
||||
end;
|
||||
if Medium.PUnkForRelease = nil then GlobalFree(Medium.HGLOBAL);
|
||||
end
|
||||
else
|
||||
else if Medium.Tymed = TYMED_ISTREAM then
|
||||
begin
|
||||
pvStrm:= IStream(Medium.pstm);
|
||||
// Figure out how large the data is
|
||||
if (FileInfo^.dwFlags and FD_FILESIZE <> 0) then
|
||||
i64Size:= Int64(FileInfo.nFileSizeLow) or (Int64(FileInfo.nFileSizeHigh) shl 32)
|
||||
else if (pvStrm.Stat(statstg, STATFLAG_DEFAULT) = S_OK) then
|
||||
i64Size:= statstg.cbSize
|
||||
else if (pvStrm.Seek(0, STREAM_SEEK_END, i64Size) = S_OK) then
|
||||
// Seek back to start of stream
|
||||
pvStrm.Seek(0, STREAM_SEEK_SET, i64Move)
|
||||
else begin
|
||||
Exit;
|
||||
if pvStrm <> nil then
|
||||
begin
|
||||
// Figure out how large the data is
|
||||
i64Size := 0;
|
||||
if (FileInfo^.dwFlags and FD_FILESIZE <> 0) then
|
||||
i64Size:= Int64(FileInfo.nFileSizeLow) or (Int64(FileInfo.nFileSizeHigh) shl 32)
|
||||
else if (pvStrm.Stat(statstg, STATFLAG_NONAME) = S_OK) then
|
||||
i64Size:= statstg.cbSize
|
||||
else if (pvStrm.Seek(0, STREAM_SEEK_END, i64Size) = S_OK) then
|
||||
begin
|
||||
// Seek back to start of stream
|
||||
pvStrm.Seek(0, STREAM_SEEK_SET, i64Move);
|
||||
end;
|
||||
|
||||
if i64Size > 0 then
|
||||
begin
|
||||
// Create memory stream to convert to
|
||||
msStream:= TMemoryStream.Create;
|
||||
try
|
||||
// Allocate size
|
||||
msStream.Size:= i64Size;
|
||||
// Read from the IStream into the memory for the TMemoryStream
|
||||
dwRead := 0;
|
||||
if pvStrm.Read(msStream.Memory, i64Size, @dwRead) = S_OK then
|
||||
msStream.Size:= dwRead
|
||||
else
|
||||
msStream.Size:= 0;
|
||||
|
||||
if msStream.Size > 0 then
|
||||
begin
|
||||
msStream.Position:=0;
|
||||
msStream.SaveToFile(UTF8ToSys(InnerFilename));
|
||||
end;
|
||||
finally
|
||||
msStream.Free;
|
||||
end;
|
||||
end;
|
||||
pvStrm := nil;
|
||||
end;
|
||||
|
||||
// Create memory stream to convert to
|
||||
msStream:= TMemoryStream.Create;
|
||||
// Allocate size
|
||||
msStream.Size:= i64Size;
|
||||
// Read from the IStream into the memory for the TMemoryStream
|
||||
if pvStrm.Read(msStream.Memory, i64Size, @dwSize) = S_OK then
|
||||
msStream.Size:= dwSize
|
||||
else
|
||||
msStream.Size:= 0;
|
||||
// Release interface
|
||||
pvStrm:=nil;
|
||||
|
||||
msStream.Position:=0;
|
||||
msStream.SaveToFile(UTF8ToSys(InnerFilename));
|
||||
msStream.Free;
|
||||
end;
|
||||
finally
|
||||
// Always release the medium - this is required by COM
|
||||
ReleaseStgMedium(@Medium);
|
||||
end;
|
||||
|
||||
if mbFileExists(InnerFilename) then
|
||||
|
|
@ -1093,7 +1114,7 @@ begin
|
|||
end;
|
||||
|
||||
{ TFileDropTarget.GetDropFileGroupFilenames }
|
||||
function TFileDropTarget.GetDropFileGroupFilenames(const dataObj: IDataObject; var Medium: TSTGMedium; Format: TFormatETC): TStringList;
|
||||
class function TFileDropTarget.GetDropFileGroupFilenames(const dataObj: IDataObject; var Medium: TSTGMedium; Format: TFormatETC): TStringList;
|
||||
var
|
||||
SuffixStr: String;
|
||||
AnyPointer: Pointer;
|
||||
|
|
|
|||
|
|
@ -4450,7 +4450,7 @@ begin
|
|||
begin
|
||||
if PasteFromClipboard(ClipboardOp, filenamesList) = True then
|
||||
try
|
||||
// fill file list with files
|
||||
// Create file list from filenames
|
||||
Files := TFileSystemFileSource.CreateFilesFromFileList(
|
||||
ExtractFilePath(filenamesList[0]), fileNamesList, True);
|
||||
|
||||
|
|
@ -4523,6 +4523,7 @@ begin
|
|||
|
||||
if Assigned(Operation) then
|
||||
begin
|
||||
// Don't access Files after creating operation - it may have taken ownership
|
||||
if Operation is TFileSystemCopyOperation then
|
||||
(Operation as TFileSystemCopyOperation).AutoRenameItSelf:= True;
|
||||
OperationsManager.AddOperation(Operation);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue