229 lines
6.6 KiB
ObjectPascal
229 lines
6.6 KiB
ObjectPascal
//
|
|
// Robert Rossmair, 2001, 2004
|
|
//
|
|
// Adds "create junction here" entry to explorer context menu, when a directory
|
|
// is dragged & dropped onto a NTFS volume. When selected, it creates a NTFS
|
|
// junction to the source directory, instead of copying it to the new location.
|
|
//
|
|
// The name of the junction is prefixed with a "~" to mark it as different from
|
|
// a normal directory, since dumb ol' Explorer doesn't know nothing about NTFS
|
|
// junctions.
|
|
//
|
|
// This unit is based on $(DELPHI)\Demos\ActiveX\ShellExt\ContextM
|
|
//
|
|
unit SoftLinkDragDropHandler;
|
|
|
|
interface
|
|
|
|
uses
|
|
Windows, ActiveX, ComObj, ShlObj,
|
|
JclBase, JclStrings, JclFileUtils, JclShell, JclNTFS;
|
|
|
|
type
|
|
TDirDropContextMenu = class(TComObject, IShellExtInit, IContextMenu)
|
|
private
|
|
FLinkTarget: string;
|
|
FLinkPath: string;
|
|
FIsRootDirectory: Boolean;
|
|
protected
|
|
{ IShellExtInit }
|
|
function IShellExtInit.Initialize = SEIInitialize; // Avoid compiler warning
|
|
function SEIInitialize(pidlFolder: PItemIDList; lpdobj: IDataObject;
|
|
hKeyProgID: HKEY): HResult; stdcall;
|
|
{ IContextMenu }
|
|
function QueryContextMenu(Menu: HMENU; indexMenu, idCmdFirst, idCmdLast,
|
|
uFlags: UINT): HResult; stdcall;
|
|
function InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult; stdcall;
|
|
function GetCommandString(idCmd, uType: UINT; pwReserved: PUINT;
|
|
pszName: LPSTR; cchMax: UINT): HResult; stdcall;
|
|
end;
|
|
|
|
const
|
|
Class_ContextMenu: TGUID = '{DDE0E099-9901-4507-9A47-3DC66B13AB6B}';
|
|
|
|
implementation
|
|
|
|
uses ComServ, SysUtils, ShellApi, Registry;
|
|
|
|
resourcestring
|
|
SDescription = 'JEDI SoftLinks Shell Extension';
|
|
SRegKeyDir = 'Directory\shellex\DragDropHandlers\JEDISoftLinks';
|
|
SRegKeyDrive = 'Drive\shellex\DragDropHandlers\JEDISoftLinks';
|
|
SMenuItem = 'Create junction here';
|
|
SMenuHelp = 'Create an NTFS junction point';
|
|
|
|
const
|
|
Prefix = '~';
|
|
|
|
function OnNtfsVolume(const FileName: string): Boolean;
|
|
begin
|
|
Result := NtfsReparsePointsSupported(ExtractFileDrive(FileName));
|
|
end;
|
|
|
|
function TDirDropContextMenu.SEIInitialize(pidlFolder: PItemIDList; lpdobj: IDataObject;
|
|
hKeyProgID: HKEY): HResult;
|
|
var
|
|
FileName: string;
|
|
LinkDir: string;
|
|
Volume: string;
|
|
StgMedium: TStgMedium;
|
|
FormatEtc: TFormatEtc;
|
|
Count, N: Integer;
|
|
begin
|
|
FLinkPath := '';
|
|
|
|
if (lpdobj = nil) then
|
|
begin
|
|
Result := E_INVALIDARG;
|
|
Exit;
|
|
end;
|
|
|
|
with FormatEtc do
|
|
begin
|
|
cfFormat := CF_HDROP;
|
|
ptd := nil;
|
|
dwAspect := DVASPECT_CONTENT;
|
|
lindex := -1;
|
|
tymed := TYMED_HGLOBAL;
|
|
end;
|
|
|
|
// Render the data referenced by the IDataObject pointer to an HGLOBAL
|
|
// storage medium in CF_HDROP format.
|
|
Result := lpdobj.GetData(FormatEtc, StgMedium);
|
|
if Failed(Result) then
|
|
Exit;
|
|
|
|
// If only one file is selected, retrieve the file name and store it in
|
|
// FLinkTarget. Otherwise fail the call.
|
|
Count := DragQueryFile(StgMedium.hGlobal, $FFFFFFFF, nil, 0);
|
|
Result := E_FAIL;
|
|
if Count = 1 then
|
|
begin
|
|
SetLength(FLinkTarget, DragQueryFile(StgMedium.hGlobal, 0, nil, 0) + 1);
|
|
DragQueryFile(StgMedium.hGlobal, 0, PChar(FLinkTarget), Length(FLinkTarget));
|
|
if DirectoryExists(FLinkTarget) then
|
|
begin
|
|
LinkDir := PidlToPath(pidlFolder);
|
|
if OnNtfsVolume(LinkDir) then
|
|
begin
|
|
FileName := ExtractFileName(FLinkTarget);
|
|
StrResetLength(FileName);
|
|
FIsRootDirectory := FileName = '';
|
|
if FIsRootDirectory then
|
|
begin
|
|
Volume := ExtractFileDrive(FLinkTarget);
|
|
N := Pos(':', Volume);
|
|
if N > 0 then
|
|
SetLength(Volume, N - 1);
|
|
FileName := Volume;
|
|
end;
|
|
FLinkPath := Format('%s' + Prefix + '%.175s', [PathAddSeparator(LinkDir), FileName]);
|
|
Result := NOERROR;
|
|
end;
|
|
end;
|
|
end;
|
|
ReleaseStgMedium(StgMedium);
|
|
end;
|
|
|
|
function TDirDropContextMenu.QueryContextMenu(Menu: HMENU; indexMenu, idCmdFirst,
|
|
idCmdLast, uFlags: UINT): HResult;
|
|
begin
|
|
Result := 0; // or use MakeResult(SEVERITY_SUCCESS, FACILITY_NULL, 0);
|
|
|
|
if FLinkPath = '' then
|
|
Exit;
|
|
|
|
if ((uFlags and $0000000F) = CMF_NORMAL) or
|
|
((uFlags and CMF_EXPLORE) <> 0) then
|
|
begin
|
|
// Add one menu item to context menu
|
|
InsertMenu(Menu, indexMenu, MF_STRING or MF_BYPOSITION, idCmdFirst, PChar(SMenuItem));
|
|
|
|
// Return number of menu items added
|
|
Result := 1; // or use MakeResult(SEVERITY_SUCCESS, FACILITY_NULL, 1)
|
|
end;
|
|
end;
|
|
|
|
function TDirDropContextMenu.InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult;
|
|
var
|
|
Success: Boolean;
|
|
begin
|
|
Result := E_FAIL;
|
|
if (HiWord(Integer(lpici.lpVerb)) <> 0) then
|
|
begin
|
|
// We are called by an application
|
|
Exit;
|
|
end;
|
|
|
|
if (LoWord(lpici.lpVerb) <> 0) then
|
|
begin
|
|
// invalid argument number
|
|
Result := E_INVALIDARG;
|
|
Exit;
|
|
end;
|
|
|
|
if (not DirectoryExists(FLinkPath) and CreateDir(FLinkPath)) {or DirectoryIsEmpty(FLinkPath)} then
|
|
begin
|
|
Success := NtfsCreateJunctionPoint(FLinkPath, FLinkTarget);
|
|
if Success then
|
|
SHChangeNotify(SHCNE_MKDIR, SHCNF_PATH, PChar(FLinkPath), nil);
|
|
end;
|
|
end;
|
|
|
|
function TDirDropContextMenu.GetCommandString(idCmd, uType: UINT; pwReserved: PUINT;
|
|
pszName: LPSTR; cchMax: UINT): HRESULT;
|
|
begin
|
|
if (idCmd = 0) then
|
|
begin
|
|
if (uType = GCS_HELPTEXT) then
|
|
// return help string for menu item
|
|
StrCopy(pszName, PChar(SMenuHelp));
|
|
Result := NOERROR;
|
|
end
|
|
else
|
|
Result := E_INVALIDARG;
|
|
end;
|
|
|
|
type
|
|
TDirDropContextMenuFactory = class(TComObjectFactory)
|
|
public
|
|
procedure UpdateRegistry(Register: Boolean); override;
|
|
end;
|
|
|
|
procedure TDirDropContextMenuFactory.UpdateRegistry(Register: Boolean);
|
|
var
|
|
ClassID: string;
|
|
begin
|
|
if Register then
|
|
begin
|
|
inherited UpdateRegistry(Register);
|
|
|
|
ClassID := GUIDToString(Class_ContextMenu);
|
|
CreateRegKey(SRegKeyDir, '', ClassID);
|
|
CreateRegKey(SRegKeyDrive, '', ClassID);
|
|
|
|
if (Win32Platform = VER_PLATFORM_WIN32_NT) then
|
|
with TRegistry.Create do
|
|
try
|
|
RootKey := HKEY_LOCAL_MACHINE;
|
|
OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions', True);
|
|
OpenKey('Approved', True);
|
|
WriteString(ClassID, SDescription);
|
|
finally
|
|
Free;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
DeleteRegKey(SRegKeyDir);
|
|
DeleteRegKey(SRegKeyDrive);
|
|
inherited UpdateRegistry(Register);
|
|
end;
|
|
end;
|
|
|
|
initialization
|
|
TDirDropContextMenuFactory.Create(ComServer, TDirDropContextMenu, Class_ContextMenu,
|
|
'', SDescription, ciMultiInstance,
|
|
tmApartment);
|
|
end.
|