{**************************************************************************************************} { } { Project JEDI Code Library (JCL) } { } { The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); } { you may not use this file except in compliance with the License. You may obtain a copy of the } { License at http://www.mozilla.org/MPL/ } { } { Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF } { ANY KIND, either express or implied. See the License for the specific language governing rights } { and limitations under the License. } { } { The Original Code is JclShell.pas. } { } { The Initial Developers of the Original Code are Marcel van Brakel and Petr Vones. } { Portions created by these individuals are Copyright (C) of these individuals. } { All Rights Reserved. } { } { Contributor(s): } { Rik Barker (rikbarker) } { Marcel van Brakel } { Jeff } { Aleksej Kudinov } { Robert Marquardt (marquardt) } { Robert Rossmair (rrossmair) } { Olivier Sannier (obones) } { Matthias Thoma (mthoma) } { Petr Vones (pvones) } { } {**************************************************************************************************} { } { This unit contains routines and classes which makes working with the Windows Shell a bit easier. } { Included are routines for working with PIDL's, special folder's, file and folder manipulation } { through shell interfaces, shortcut's and program execution. } { } {**************************************************************************************************} // Last modified: $Date: 2006/01/02 04:30:53 $ // For history see end of file unit JclShell; {$I jcl.inc} interface uses Windows, SysUtils, {$IFNDEF FPC} ShlObj, {$ENDIF ~FPC} JclWin32, JclSysUtils; // Files and Folders type TSHDeleteOption = (doSilent, doAllowUndo, doFilesOnly); TSHDeleteOptions = set of TSHDeleteOption; TSHRenameOption = (roSilent, roRenameOnCollision); TSHRenameOptions = set of TSHRenameOption; TUnicodePath = array [0..MAX_PATH-1] of WideChar; TAnsiPath = array [0..MAX_PATH-1] of char; function SHDeleteFiles(Parent: THandle; const Files: string; Options: TSHDeleteOptions): Boolean; function SHDeleteFolder(Parent: THandle; const Folder: string; Options: TSHDeleteOptions): Boolean; function SHRenameFile(const Src, Dest: string; Options: TSHRenameOptions): Boolean; type TEnumFolderFlag = (efFolders, efNonFolders, efIncludeHidden); TEnumFolderFlags = set of TEnumFolderFlag; TEnumFolderRec = record DisplayName: string; Attributes: DWORD; IconLarge: HICON; IconSmall: HICON; Item: PItemIdList; EnumIdList: IEnumIdList; Folder: IShellFolder; end; function SHEnumFolderFirst(const Folder: string; Flags: TEnumFolderFlags; var F: TEnumFolderRec): Boolean; function SHEnumSpecialFolderFirst(SpecialFolder: DWORD; Flags: TEnumFolderFlags; var F: TEnumFolderRec): Boolean; procedure SHEnumFolderClose(var F: TEnumFolderRec); function SHEnumFolderNext(var F: TEnumFolderRec): Boolean; function GetSpecialFolderLocation(const Folder: Integer): string; function DisplayPropDialog(const Handle: THandle; const FileName: string): Boolean; overload; function DisplayPropDialog(const Handle: THandle; Item: PItemIdList): Boolean; overload; function DisplayContextMenuPidl(const Handle: THandle; const Folder: IShellFolder; Item: PItemIdList; Pos: TPoint): Boolean; function DisplayContextMenu(const Handle: THandle; const FileName: string; Pos: TPoint): Boolean; function OpenFolder(const Path: string; Parent: THandle = 0; Explore: Boolean = False): Boolean; function OpenSpecialFolder(FolderID: Integer; Parent: THandle = 0; Explore: Boolean = False): Boolean; // Memory Management function SHReallocMem(var P: Pointer; Count: Integer): Boolean; function SHAllocMem(out P: Pointer; Count: Integer): Boolean; function SHGetMem(var P: Pointer; Count: Integer): Boolean; function SHFreeMem(var P: Pointer): Boolean; // Paths and PIDLs function DriveToPidlBind(const DriveName: string; out Folder: IShellFolder): PItemIdList; function PathToPidl(const Path: string; Folder: IShellFolder): PItemIdList; function PathToPidlBind(const FileName: string; out Folder: IShellFolder): PItemIdList; function PidlBindToParent(IdList: PItemIdList; out Folder: IShellFolder; out Last: PItemIdList): Boolean; function PidlCompare(Pidl1, Pidl2: PItemIdList): Boolean; function PidlCopy(Source: PItemIdList; out Dest: PItemIdList): Boolean; function PidlFree(var IdList: PItemIdList): Boolean; function PidlGetDepth(Pidl: PItemIdList): Integer; function PidlGetLength(Pidl: PItemIdList): Integer; function PidlGetNext(Pidl: PItemIdList): PItemIdList; function PidlToPath(IdList: PItemIdList): string; function StrRetFreeMem(StrRet: TStrRet): Boolean; function StrRetToString(IdList: PItemIdList; StrRet: TStrRet; Free: Boolean): string; // Shortcuts / Shell link type PShellLink = ^TShellLink; TShellLink = record Arguments: string; ShowCmd: Integer; WorkingDirectory: string; IdList: PItemIDList; Target: string; Description: string; IconLocation: string; IconIndex: Integer; HotKey: Word; end; procedure ShellLinkFree(var Link: TShellLink); function ShellLinkResolve(const FileName: string; var Link: TShellLink): HRESULT; function ShellLinkCreate(const Link: TShellLink; const FileName: string): HRESULT; function ShellLinkCreateSystem(const Link: TShellLink; const Folder: Integer; const FileName: string): HRESULT; function ShellLinkIcon(const Link: TShellLink): HICON; overload; function ShellLinkIcon(const FileName: string): HICON; overload; // Miscellaneous function SHDllGetVersion(const FileName: string; var Version: TDllVersionInfo): Boolean; function GetSystemIcon(IconIndex: Integer; Flags: Cardinal): HICON; function OverlayIcon(var Icon: HICON; Overlay: HICON; Large: Boolean): Boolean; function OverlayIconShortCut(var Large, Small: HICON): Boolean; function OverlayIconShared(var Large, Small: HICON): Boolean; function SHGetItemInfoTip(const Folder: IShellFolder; Item: PItemIdList): string; function ShellExecEx(const FileName: string; const Parameters: string = ''; const Verb: string = ''; CmdShow: Integer = SW_SHOWNORMAL): Boolean; function ShellExec(Wnd: Integer; const Operation, FileName, Parameters, Directory: string; ShowCommand: Integer): Boolean; function ShellExecAndWait(const FileName: string; const Parameters: string = ''; const Verb: string = ''; CmdShow: Integer = SW_SHOWNORMAL): Boolean; function ShellOpenAs(const FileName: string): Boolean; function ShellRasDial(const EntryName: string): Boolean; function ShellRunControlPanel(const NameOrFileName: string; AppletNumber: Integer = 0): Boolean; function GetFileNameIcon(const FileName: string; Flags: Cardinal = 0): HICON; type TJclFileExeType = (etError, etMsDos, etWin16, etWin32Gui, etWin32Con); function GetFileExeType(const FileName: TFileName): TJclFileExeType; function ShellFindExecutable(const FileName, DefaultDir: string): string; //MSI functions and types used in ShellLinkResolve - copied from JwaMsi.pas type INSTALLSTATE = Longint; const MSILIB = 'msi.dll'; var RtdlMsiLibHandle: TModuleHandle = INVALID_MODULEHANDLE_VALUE; RtdlMsiGetShortcutTarget: function(szShortcutPath: LPCSTR; szProductCode: LPSTR; szFeatureId: LPSTR; szComponentCode: LPSTR): UINT; stdcall = nil; RtdlMsiGetComponentPath: function(szProduct: LPCSTR; szComponent: LPCSTR; lpPathBuf: LPSTR; pcchBuf: LPDWORD): INSTALLSTATE; stdcall = nil; implementation uses ActiveX, {$IFNDEF FPC} CommCtrl, {$ENDIF ~FPC} Messages, ShellApi, JclFileUtils, JclStrings, JclSysInfo; const cVerbProperties = 'properties'; cVerbOpen = 'open'; cVerbExplore = 'explore'; //=== Files and Folders ====================================================== // Helper function and constant to map a TSHDeleteOptions set to a Cardinal const FOF_COMPLETELYSILENT = FOF_SILENT or FOF_NOCONFIRMATION or FOF_NOERRORUI or FOF_NOCONFIRMMKDIR; function DeleteOptionsToCardinal(Options: TSHDeleteOptions): Cardinal; begin Result := 0; if doSilent in Options then Result := Result or FOF_COMPLETELYSILENT; if doAllowUndo in Options then Result := Result or FOF_ALLOWUNDO; if doFilesOnly in Options then Result := Result or FOF_FILESONLY; end; function SHDeleteFiles(Parent: THandle; const Files: string; Options: TSHDeleteOptions): Boolean; var FileOp: TSHFileOpStruct; Source: string; begin FillChar(FileOp, SizeOf(FileOp), #0); with FileOp do begin {$IFDEF FPC} THandle := Parent; {$ELSE} Wnd := Parent; {$ENDIF FPC} wFunc := FO_DELETE; Source := Files + #0#0; pFrom := PChar(Source); fFlags := DeleteOptionsToCardinal(Options); end; {$IFDEF FPC} Result := SHFileOperation(@FileOp) = 0; {$ELSE} Result := SHFileOperation(FileOp) = 0; {$ENDIF FPC} end; function SHDeleteFolder(Parent: THandle; const Folder: string; Options: TSHDeleteOptions): Boolean; begin Exclude(Options, doFilesOnly); Result := SHDeleteFiles(Parent, PathAddSeparator(Folder) + '*.*', Options); if Result then SHDeleteFiles(Parent, Folder, Options); end; // Helper function to map a TSHRenameOptions set to a cardinal function RenameOptionsToCardinal(Options: TSHRenameOptions): Cardinal; begin Result := 0; if roRenameOnCollision in Options then Result := Result or FOF_RENAMEONCOLLISION; if roSilent in Options then Result := Result or FOF_COMPLETELYSILENT; end; function SHRenameFile(const Src, Dest: string; Options: TSHRenameOptions): Boolean; var FileOp: TSHFileOpStruct; Source, Destination: string; begin FillChar(FileOp, SizeOf(FileOp), #0); with FileOp do begin {$IFDEF FPC} THandle := GetDesktopWindow; {$ELSE} Wnd := GetDesktopWindow; {$ENDIF FPC} wFunc := FO_RENAME; Source := Src + #0#0; Destination := Dest + #0#0; pFrom := PChar(Source); pTo := PChar(Destination); fFlags := RenameOptionsToCardinal(Options); end; {$IFDEF FPC} Result := SHFileOperation(@FileOp) = 0; {$ELSE} Result := SHFileOperation(FileOp) = 0; {$ENDIF FPC} end; function EnumFolderFlagsToCardinal(Flags: TEnumFolderFlags): Cardinal; begin Result := 0; if efFolders in Flags then Result := Result or SHCONTF_FOLDERS; if efNonFolders in Flags then Result := Result or SHCONTF_NONFOLDERS; if efIncludeHidden in Flags then Result := Result or SHCONTF_INCLUDEHIDDEN; end; procedure ClearEnumFolderRec(var F: TEnumFolderRec; const Free, Release: Boolean); begin if Release then begin F.EnumIdList := nil; F.Folder := nil; end; if Free then begin PidlFree(F.Item); DestroyIcon(F.IconLarge); DestroyIcon(F.IconSmall); end; F.Attributes := 0; F.Item := nil; F.IconLarge := 0; F.IconSmall := 0; end; procedure SHEnumFolderClose(var F: TEnumFolderRec); begin ClearEnumFolderRec(F, True, True); end; function SHEnumFolderNext(var F: TEnumFolderRec): Boolean; const Attr = Cardinal(SFGAO_CAPABILITYMASK or SFGAO_DISPLAYATTRMASK or SFGAO_CONTENTSMASK); var DisplayNameRet: TStrRet; ItemsFetched: ULONG; ExtractIcon: IExtractIcon; IconFile: TUnicodePath; IconIndex: Integer; Flags: DWORD; begin Result := False; ClearEnumFolderRec(F, True, False); if (F.EnumIdList = nil) or (F.Folder = nil) then Exit; if F.EnumIdList.Next(1, F.Item, ItemsFetched) = NO_ERROR then begin F.Folder.GetDisplayNameOf(F.Item, SHGDN_INFOLDER, DisplayNameRet); F.DisplayName := StrRetToString(F.Item, DisplayNameRet, True); F.Attributes := Attr; F.Folder.GetAttributesOf(1, F.Item, F.Attributes); F.Folder.GetUIObjectOf(0, 1, F.Item, IID_IExtractIconW, nil, Pointer(ExtractIcon)); Flags := 0; F.IconLarge := 0; F.IconSmall := 0; if Assigned(ExtractIcon) then begin ExtractIcon.GetIconLocation(0, @IconFile, MAX_PATH, IconIndex, Flags); if (IconIndex < 0) and ((Flags and GIL_NOTFILENAME) = GIL_NOTFILENAME) then ExtractIconEx(@IconFile, IconIndex, F.IconLarge, F.IconSmall, 1) else ExtractIcon.Extract(@IconFile, IconIndex, F.IconLarge, F.IconSmall, MakeLong(32, 16)); end; Result := True; end; end; function SHEnumSpecialFolderFirst(SpecialFolder: DWORD; Flags: TEnumFolderFlags; var F: TEnumFolderRec): Boolean; var DesktopFolder: IShellFolder; FolderPidl: PItemIdList; begin ClearEnumFolderRec(F, False, False); SHGetDesktopFolder(DesktopFolder); if SpecialFolder = CSIDL_DESKTOP then F.Folder := DesktopFolder else begin SHGetSpecialFolderLocation(0, SpecialFolder, FolderPidl); try DesktopFolder.BindToObject(FolderPidl, nil, IID_IShellFolder, Pointer(F.Folder)); finally PidlFree(FolderPidl); end; end; F.Folder.EnumObjects(0, EnumFolderFlagsToCardinal(Flags), F.EnumIdList); Result := SHEnumFolderNext(F); if not Result then SHEnumFolderClose(F); end; function SHEnumFolderFirst(const Folder: string; Flags: TEnumFolderFlags; var F: TEnumFolderRec): Boolean; var DesktopFolder: IShellFolder; FolderPidl: PItemIdList; begin ClearEnumFolderRec(F, False, False); SHGetDesktopFolder(DesktopFolder); FolderPidl := PathToPidl(PathAddSeparator(Folder), DesktopFolder); try DesktopFolder.BindToObject(FolderPidl, nil, IID_IShellFolder, Pointer(F.Folder)); F.Folder.EnumObjects(0, EnumFolderFlagsToCardinal(Flags), F.EnumIdList); Result := SHEnumFolderNext(F); if not Result then SHEnumFolderClose(F); finally PidlFree(FolderPidl); end; end; function GetSpecialFolderLocation(const Folder: Integer): string; var FolderPidl: PItemIdList; begin if Succeeded(SHGetSpecialFolderLocation(0, Folder, FolderPidl)) then begin Result := PidlToPath(FolderPidl); PidlFree(FolderPidl); end else Result := ''; end; function DisplayPropDialog(const Handle: THandle; const FileName: string): Boolean; var Info: TShellExecuteInfo; begin FillChar(Info, SizeOf(Info), #0); with Info do begin cbSize := SizeOf(Info); lpFile := PChar(FileName); nShow := SW_SHOW; fMask := SEE_MASK_INVOKEIDLIST; Wnd := Handle; lpVerb := cVerbProperties; end; Result := ShellExecuteEx(@Info); end; function DisplayPropDialog(const Handle: THandle; Item: PItemIdList): Boolean; var Info: TShellExecuteInfo; begin FillChar(Info, SizeOf(Info), #0); with Info do begin cbSize := SizeOf(Info); nShow := SW_SHOW; lpIDList := Item; fMask := SEE_MASK_INVOKEIDLIST or SEE_MASK_IDLIST; Wnd := Handle; lpVerb := cVerbProperties; end; Result := ShellExecuteEx(@Info); end; // Window procedure for the callback window created by DisplayContextMenu. // It simply forwards messages to the folder. If you don't do this then the // system created submenu's will be empty (except for 1 stub item!) // note: storing the IContextMenu2 pointer in the window's user data was // 'inspired' by (read: copied from) code by Brad Stowers. function MenuCallback(Wnd: THandle; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; var ContextMenu2: IContextMenu2; begin case Msg of WM_CREATE: begin ContextMenu2 := IContextMenu2(PCreateStruct(lParam).lpCreateParams); SetWindowLong(Wnd, GWL_USERDATA, Longint(ContextMenu2)); Result := DefWindowProc(Wnd, Msg, wParam, lParam); end; WM_INITMENUPOPUP: begin ContextMenu2 := IContextMenu2(GetWindowLong(Wnd, GWL_USERDATA)); ContextMenu2.HandleMenuMsg(Msg, wParam, lParam); Result := 0; end; WM_DRAWITEM, WM_MEASUREITEM: begin ContextMenu2 := IContextMenu2(GetWindowLong(Wnd, GWL_USERDATA)); ContextMenu2.HandleMenuMsg(Msg, wParam, lParam); Result := 1; end; else Result := DefWindowProc(Wnd, Msg, wParam, lParam); end; end; // Helper function for DisplayContextMenu, creates the callback window. function CreateMenuCallbackWnd(const ContextMenu: IContextMenu2): THandle; const IcmCallbackWnd = 'ICMCALLBACKWND'; var WndClass: TWndClass; begin FillChar(WndClass, SizeOf(WndClass), #0); WndClass.lpszClassName := PChar(IcmCallbackWnd); WndClass.lpfnWndProc := @MenuCallback; WndClass.hInstance := HInstance; Windows.RegisterClass(WndClass); Result := CreateWindow(IcmCallbackWnd, IcmCallbackWnd, WS_POPUPWINDOW, 0, 0, 0, 0, 0, 0, HInstance, Pointer(ContextMenu)); end; function DisplayContextMenuPidl(const Handle: THandle; const Folder: IShellFolder; Item: PItemIdList; Pos: TPoint): Boolean; var Cmd: Cardinal; ContextMenu: IContextMenu; ContextMenu2: IContextMenu2; Menu: HMENU; CommandInfo: TCMInvokeCommandInfo; CallbackWindow: THandle; begin Result := False; if (Item = nil) or (Folder = nil) then Exit; Folder.GetUIObjectOf(Handle, 1, Item, IID_IContextMenu, nil, Pointer(ContextMenu)); if ContextMenu <> nil then begin Menu := CreatePopupMenu; if Menu <> 0 then begin if Succeeded(ContextMenu.QueryContextMenu(Menu, 0, 1, $7FFF, CMF_EXPLORE)) then begin CallbackWindow := 0; if Succeeded(ContextMenu.QueryInterface(IContextMenu2, ContextMenu2)) then begin CallbackWindow := CreateMenuCallbackWnd(ContextMenu2); end; ClientToScreen(Handle, Pos); Cmd := Cardinal(TrackPopupMenu(Menu, TPM_LEFTALIGN or TPM_LEFTBUTTON or TPM_RIGHTBUTTON or TPM_RETURNCMD, Pos.X, Pos.Y, 0, CallbackWindow, nil)); if Cmd <> 0 then begin FillChar(CommandInfo, SizeOf(CommandInfo), #0); CommandInfo.cbSize := SizeOf(TCMInvokeCommandInfo); CommandInfo.hwnd := Handle; CommandInfo.lpVerb := MakeIntResource(Cmd - 1); CommandInfo.nShow := SW_SHOWNORMAL; Result := Succeeded(ContextMenu.InvokeCommand(CommandInfo)); end; if CallbackWindow <> 0 then DestroyWindow(CallbackWindow); end; DestroyMenu(Menu); end; end; end; function DisplayContextMenu(const Handle: THandle; const FileName: string; Pos: TPoint): Boolean; var ItemIdList: PItemIdList; Folder: IShellFolder; begin Result := False; ItemIdList := PathToPidlBind(FileName, Folder); if ItemIdList <> nil then begin Result := DisplayContextMenuPidl(Handle, Folder, ItemIdList, Pos); PidlFree(ItemIdList); end; end; function OpenFolder(const Path: string; Parent: THandle; Explore: Boolean): Boolean; var Sei: TShellExecuteInfo; begin Result := False; if IsDirectory(Path) then begin FillChar(Sei, SizeOf(Sei), #0); with Sei do begin cbSize := SizeOf(Sei); Wnd := Parent; if Explore then lpVerb := cVerbExplore else lpVerb := cVerbOpen; lpFile := PChar(Path); nShow := SW_SHOWNORMAL; end; Result := ShellExecuteEx(@Sei); end; end; function OpenSpecialFolder(FolderID: Integer; Parent: THandle; Explore: Boolean): Boolean; var Malloc: IMalloc; Pidl: PItemIDList; Sei: TShellExecuteInfo; begin Result := False; if Succeeded(SHGetMalloc(Malloc)) and Succeeded(SHGetSpecialFolderLocation(Parent, FolderID, Pidl)) then begin FillChar(Sei, SizeOf(Sei), #0); with Sei do begin cbSize := SizeOf(Sei); Wnd := Parent; fMask := SEE_MASK_INVOKEIDLIST; if Explore then lpVerb := cVerbExplore else lpVerb := cVerbOpen; lpIDList := Pidl; nShow := SW_SHOWNORMAL; if PidlToPath(Pidl) = '' then begin fMask := SEE_MASK_INVOKEIDLIST; lpIDList := Pidl; end else lpFile := PChar(PidlToPath(Pidl)); end; Result := ShellExecuteEx(@Sei); Malloc.Free(Pidl); end; end; //=== Memory Management ====================================================== function SHAllocMem(out P: Pointer; Count: Integer): Boolean; var Malloc: IMalloc; begin Result := False; P := nil; if Succeeded(SHGetMalloc(Malloc)) then begin P := Malloc.Alloc(Count); if P <> nil then begin FillChar(P^, Count, #0); Result := True; end; end; end; function SHFreeMem(var P: Pointer): Boolean; var Malloc: IMalloc; begin Result := False; if P <> nil then begin if Succeeded(SHGetMalloc(Malloc)) and (Malloc.DidAlloc(P) > 0) then begin Malloc.Free(P); P := nil; Result := True; end; end; end; function SHGetMem(var P: Pointer; Count: Integer): Boolean; var Malloc: IMalloc; begin Result := False; if Succeeded(SHGetMalloc(Malloc)) then begin P := Malloc.Alloc(Count); if P <> nil then Result := True; end; end; function SHReallocMem(var P: Pointer; Count: Integer): Boolean; var Malloc: IMalloc; begin Result := False; if Succeeded(SHGetMalloc(Malloc)) then begin if (P <> nil) and (Malloc.DidAlloc(P) <= 0) then Exit; P := Malloc.ReAlloc(P, Count); Result := (P <> nil) or (Count = 0); end; end; //=== Paths and PIDLs ======================================================== function DriveToPidlBind(const DriveName: string; out Folder: IShellFolder): PItemIdList; var Attr: ULONG; Eaten: ULONG; DesktopFolder: IShellFolder; Drives: PItemIdList; Path: TUnicodePath; begin Result := nil; if Succeeded(SHGetDesktopFolder(DesktopFolder)) then begin if Succeeded(SHGetSpecialFolderLocation(0, CSIDL_DRIVES, Drives)) then begin if Succeeded(DesktopFolder.BindToObject(Drives, nil, IID_IShellFolder, Pointer(Folder))) then begin MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, PChar(PathAddSeparator(DriveName)), -1, Path, MAX_PATH); if Failed(Folder.ParseDisplayName(0, nil, Path, Eaten, Result, Attr)) then begin Folder := nil; // Failure probably means that this is not a drive. However, do not // call PathToPidlBind() because it may cause infinite recursion. end; end; end; PidlFree(Drives); end; end; function PathToPidl(const Path: string; Folder: IShellFolder): PItemIdList; var DesktopFolder: IShellFolder; CharsParsed, Attr: ULONG; WidePath: TUnicodePath; begin Result := nil; MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, PChar(Path), -1, WidePath, MAX_PATH); if Folder <> nil then Folder.ParseDisplayName(0, nil, WidePath, CharsParsed, Result, Attr) else if Succeeded(SHGetDesktopFolder(DesktopFolder)) then DesktopFolder.ParseDisplayName(0, nil, WidePath, CharsParsed, Result, Attr); end; function PathToPidlBind(const FileName: string; out Folder: IShellFolder): PItemIdList; var Attr, Eaten: ULONG; PathIdList: PItemIdList; DesktopFolder: IShellFolder; Path, ItemName: TUnicodePath; begin Result := nil; MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, PChar(ExtractFilePath(FileName)), -1, Path, MAX_PATH); MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, PChar(ExtractFileName(FileName)), -1, ItemName, MAX_PATH); if Succeeded(SHGetDesktopFolder(DesktopFolder)) then begin if Succeeded(DesktopFolder.ParseDisplayName(0, nil, Path, Eaten, PathIdList, Attr)) then begin if Succeeded(DesktopFolder.BindToObject(PathIdList, nil, IID_IShellFolder, Pointer(Folder))) then begin if Failed(Folder.ParseDisplayName(0, nil, ItemName, Eaten, Result, Attr)) then begin Folder := nil; Result := DriveToPidlBind(FileName, Folder); end; end; PidlFree(PathIdList); end else Result := DriveToPidlBind(FileName, Folder); end; end; function PidlBindToParent(IdList: PItemIdList; out Folder: IShellFolder; out Last: PItemIdList): Boolean; var Path: string; begin Last := nil; Path := PidlToPath(IdList); Last := PathToPidlBind(Path, Folder); Result := Last <> nil; if Last = nil then Folder := nil; end; function PidlCompare(Pidl1, Pidl2: PItemIdList): Boolean; var L: Integer; begin Result := False; L := PidlGetLength(Pidl1); if L = PidlGetLength(Pidl2) then Result := CompareMem(Pidl1, Pidl2, L); end; function PidlCopy(Source: PItemIdList; out Dest: PItemIdList): Boolean; var L: Integer; begin Result := False; Dest := Source; if Source <> nil then begin L := PidlGetLength(Source) + 2; if SHAllocMem(Pointer(Dest), L) then begin Move(Source^, Dest^, L); Result := True; end; end; end; function PidlFree(var IdList: PItemIdList): Boolean; var Malloc: IMalloc; begin Result := False; if IdList = nil then Result := True else begin if Succeeded(SHGetMalloc(Malloc)) and (Malloc.DidAlloc(IdList) > 0) then begin Malloc.Free(IdList); IdList := nil; Result := True; end; end; end; function PidlGetDepth(Pidl: PItemIdList): Integer; var P: PItemIdList; begin Result := 0; if Pidl <> nil then begin P := Pidl; while (P^.mkId.cb <> 0) and (Result < MAX_PATH) do begin Inc(Result); P := PItemIdList(@P^.mkId.abID[P^.mkId.cb - 2]); end; end; if Result = MAX_PATH then Result := -1; end; function PidlGetLength(Pidl: PItemIdList): Integer; var P: PItemIdList; I: Integer; begin Result := 0; if Pidl <> nil then begin I := 0; P := Pidl; while (P^.mkId.cb <> 0) and (I < MAX_PATH) do begin Inc(I); Inc(Result, P^.mkId.cb); P := PItemIdList(@P^.mkId.abID[P^.mkId.cb - 2]); end; if I = MAX_PATH then Result := -1; end; end; function PidlGetNext(Pidl: PItemIdList): PItemIdList; begin Result := nil; if (Pidl <> nil) and (Pidl^.mkid.cb <> 0) then begin Result := PItemIdList(@Pidl^.mkId.abID[Pidl^.mkId.cb - 2]); if Result^.mkid.cb = 0 then Result := nil; end; end; function PidlToPath(IdList: PItemIdList): string; begin SetLength(Result, MAX_PATH); if SHGetPathFromIdList(IdList, PChar(Result)) then StrResetLength(Result) else Result := ''; end; function StrRetFreeMem(StrRet: TStrRet): Boolean; begin Result := False; if StrRet.uType = STRRET_WSTR then Result := SHFreeMem(Pointer(StrRet.pOleStr)); end; function StrRetToString(IdList: PItemIdList; StrRet: TStrRet; Free: Boolean): string; begin case StrRet.uType of STRRET_WSTR: begin Result := WideCharToString(StrRet.pOleStr); if Free then SHFreeMem(Pointer(StrRet.pOleStr)); end; STRRET_OFFSET: if IdList <> nil then Result := PChar(IdList) + StrRet.uOffset else Result := ''; STRRET_CSTR: Result := StrRet.cStr; else Result := ''; end; end; //=== ShortCuts / Shell link ================================================= procedure ShellLinkFree(var Link: TShellLink); begin PidlFree(Link.IdList); end; const IID_IShellLink: TGUID = { IID_IShellLinkA } (D1:$000214EE; D2:$0000; D3:$0000; D4:($C0,$00,$00,$00,$00,$00,$00,$46)); function ShellLinkCreateSystem(const Link: TShellLink; const Folder: Integer; const FileName: string): HRESULT; var Path: string; Pidl: PItemIDList; begin Result := E_INVALIDARG; SetLength(Path, MAX_PATH); if Succeeded(SHGetSpecialFolderLocation(0, Folder, Pidl)) then begin Path := PidltoPath(Pidl); if Path <> '' then begin StrResetLength(Path); Result := ShellLinkCreate(Link, PathAddSeparator(Path) + FileName); end; end; end; function ShellLinkCreate(const Link: TShellLink; const FileName: string): HRESULT; var ShellLink: IShellLink; PersistFile: IPersistFile; LinkName: TUnicodePath; begin Result := CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER, IID_IShellLink, ShellLink); if Succeeded(Result) then begin ShellLink.SetArguments(PChar(Link.Arguments)); ShellLink.SetShowCmd(Link.ShowCmd); ShellLink.SetWorkingDirectory(PChar(Link.WorkingDirectory)); ShellLink.SetPath(PChar(Link.Target)); ShellLink.SetDescription(PChar(Link.Description)); ShellLink.SetHotkey(Link.HotKey); ShellLink.SetIconLocation(PChar(Link.IconLocation), Link.IconIndex); PersistFile := ShellLink as IPersistFile; MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, PChar(FileName), -1, LinkName, MAX_PATH); Result := PersistFile.Save(LinkName, True); end; end; function RtdlLoadMsiFuncs:Boolean; begin Result:=False; if LoadModule(rtdlMsiLibHandle,MSILIB) then begin if not Assigned(RtdlMsiGetShortcutTarget) then RtdlMsiGetShortcutTarget:=GetModuleSymbol(rtdlMsiLibHandle,'MsiGetShortcutTargetA'); if not Assigned(RtdlMsiGetComponentPath) then RtdlMsiGetComponentPath:=GetModuleSymbol(rtdlMsiLibHandle,'MsiGetComponentPathA'); Result:=(Assigned(RtdlMsiGetShortcutTarget)) and (Assigned(RtdlMsiGetComponentPath)); end; end; function ShellLinkResolve(const FileName: string; var Link: TShellLink): HRESULT; const MAX_FEATURE_CHARS = 38; // maximum chars in MSI feature name var ShellLink: IShellLink; PersistFile: IPersistFile; LinkName: TUnicodePath; Buffer: string; Win32FindData: TWin32FindData; FullPath: string; ProductGuid: array [0..38] of Char; FeatureID: array [0..MAX_FEATURE_CHARS] of Char; ComponentGUID: array [0..38] of Char; TargetFile: array [0..MAX_PATH] of Char; PathSize: DWORD; TargetResolved: Boolean; begin Result := CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER, IID_IShellLink, ShellLink); if Succeeded(Result) then begin TargetResolved := False; // Handle MSI style shortcuts without invoking the Windows installer if // the feature was set to "Install on first use" if RtdlLoadMsiFuncs then begin FillChar(ProductGuid, SizeOf(ProductGuid), #0); FillChar(FeatureID, SizeOf(FeatureID), #0); FillChar(ComponentGuid, SizeOf(ComponentGuid), #0); FillChar(TargetFile, SizeOf(TargetFile), #0); if RtdlMsiGetShortcutTarget(PAnsiChar(FileName), ProductGuid, FeatureID, ComponentGuid) = ERROR_SUCCESS then begin PathSize := MAX_PATH + 1; RtdlMsiGetComponentPath(ProductGuid, ComponentGuid, TargetFile, @PathSize); if TargetFile <> '' then begin Link.Target := TargetFile; TargetResolved := True; end; end; end; PersistFile := ShellLink as IPersistFile; // PersistFile.Load fails if the filename is not fully qualified FullPath := ExpandFileName(FileName); MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, PChar(FullPath), -1, LinkName, MAX_PATH); Result := PersistFile.Load(LinkName, STGM_READ); if Succeeded(Result) then begin Result := ShellLink.Resolve(0, SLR_ANY_MATCH); if Succeeded(Result) then begin SetLength(Buffer, MAX_PATH); if not TargetResolved then begin ShellLink.GetPath(PChar(Buffer), MAX_PATH, Win32FindData, SLGP_SHORTPATH); Link.Target := PChar(Buffer); end; ShellLink.GetArguments(PChar(Buffer), MAX_PATH); Link.Arguments := PChar(Buffer); ShellLink.GetShowCmd(Link.ShowCmd); ShellLink.GetWorkingDirectory(PChar(Buffer), MAX_PATH); Link.WorkingDirectory := PChar(Buffer); ShellLink.GetDescription(PChar(Buffer), MAX_PATH); Link.Description := PChar(Buffer); ShellLink.GetIconLocation(PChar(Buffer), MAX_PATH, Link.IconIndex); Link.IconLocation := PChar(Buffer); ShellLink.GetHotkey(Link.HotKey); ShellLink.GetIDList(Link.IdList); end; end; end; end; function ShellLinkIcon(const Link: TShellLink): HICON; overload; var LocExt: string; Info: TSHFileInfo; begin Result := 0; LocExt := LowerCase(ExtractFileExt(Link.IconLocation)); // 1. See if IconLocation specifies a valid icon file if (LocExt = '.ico') and (FileExists(Link.IconLocation)) then begin { TODO : Implement loading from an .ico file } end; // 2. See if IconLocation specifies an executable if Result = 0 then begin if (LocExt = '.dll') or (LocExt = '.exe') then Result := ExtractIcon(0, PChar(Link.IconLocation), Link.IconIndex); end; // 3. See if target specifies a file if Result = 0 then begin if FileExists(Link.Target) then Result := ExtractIcon(0, PChar(Link.Target), Link.IconIndex); end; // 4. See if the target is an object if Result = 0 then begin if Link.IdList <> nil then begin FillChar(Info, SizeOf(Info), 0); if SHGetFileInfo(PChar(Link.IdList), 0, Info, SizeOf(Info), SHGFI_PIDL or SHGFI_ICON) <> 0 then Result := Info.hIcon; end; end; end; function ShellLinkIcon(const FileName: string): HICON; overload; var Link: TShellLink; begin if Succeeded(ShellLinkResolve(FileName, Link)) then begin Result := ShellLinkIcon(Link); ShellLinkFree(Link); end else Result := 0; end; //=== Miscellaneous ========================================================== function SHGetItemInfoTip(const Folder: IShellFolder; Item: PItemIdList): string; var QueryInfo: IQueryInfo; InfoTip: PWideChar; begin Result := ''; if (Item = nil) or (Folder = nil) then Exit; if Succeeded(Folder.GetUIObjectOf(0, 1, Item, IQueryInfo, nil, Pointer(QueryInfo))) then begin if Succeeded(QueryInfo.GetInfoTip(0, InfoTip)) then begin Result := WideCharToString(InfoTip); SHFreeMem(Pointer(InfoTip)); end; end; end; function SHDllGetVersion(const FileName: string; var Version: TDllVersionInfo): Boolean; type TDllGetVersionProc = function (var pdvi: TDllVersionInfo): HRESULT; stdcall; var _DllGetVersion: TDllGetVersionProc; LibHandle: HINST; begin Result := False; LibHandle := LoadLibrary(PChar(FileName)); if LibHandle <> 0 then begin @_DllGetVersion := GetProcAddress(LibHandle, PChar('DllGetVersion')); if @_DllGetVersion <> nil then begin Version.cbSize := SizeOf(TDllVersionInfo); Result := Succeeded(_DllGetVersion(Version)); end; FreeLibrary(LibHandle); end; end; function OverlayIcon(var Icon: HICON; Overlay: HICON; Large: Boolean): Boolean; var Source, Dest: HIMAGELIST; Width, Height: Integer; begin Result := False; if Large then begin Width := GetSystemMetrics(SM_CXICON); Height := GetSystemMetrics(SM_CYICON); Source := ImageList_Create(Width, Height, ILC_MASK or ILC_COLOR32, 1, 0); end else begin Width := GetSystemMetrics(SM_CXSMICON); Height := GetSystemMetrics(SM_CYSMICON); Source := ImageList_Create(Width, Height, ILC_MASK or ILC_COLOR32, 1, 0); end; if Source <> 0 then begin if (ImageList_AddIcon(Source, Icon) <> -1) and (ImageList_AddIcon(Source, Overlay) <> -1) then begin Dest := HIMAGELIST(ImageList_Merge(Source, 0, Source, 1, 0, 0)); if Dest <> 0 then begin DestroyIcon(Icon); Icon := ImageList_ExtractIcon(0, Dest, 0); ImageList_Destroy(Dest); Result := True; end; end; ImageList_Destroy(Source); end; end; function OverlayIconShortCut(var Large, Small: HICON): Boolean; var OvlLarge, OvlSmall: HICON; begin Result := False; if ExtractIconEx(PChar('shell32.dll'), 29, OvlLarge, OvlSmall, 1) = 2 then begin OverlayIcon(Large, OvlLarge, True); OverlayIcon(Small, OvlSmall, False); end; end; function OverlayIconShared(var Large, Small: HICON): Boolean; var OvlLarge, OvlSmall: HICON; begin Result := False; if ExtractIconEx(PChar('shell32.dll'), 28, OvlLarge, OvlSmall, 1) = 2 then begin OverlayIcon(Large, OvlLarge, True); OverlayIcon(Small, OvlSmall, False); end; end; function GetSystemIcon(IconIndex: Integer; Flags: Cardinal): HICON; var FileInfo: TSHFileInfo; ImageList: HIMAGELIST; begin FillChar(FileInfo, SizeOf(FileInfo), #0); if Flags = 0 then Flags := SHGFI_SHELLICONSIZE; ImageList := SHGetFileInfo('', 0, FileInfo, SizeOf(FileInfo), Flags or SHGFI_SYSICONINDEX); Result := ImageList_ExtractIcon(0, ImageList, IconIndex); end; function ShellExecEx(const FileName: string; const Parameters: string; const Verb: string; CmdShow: Integer): Boolean; var Sei: TShellExecuteInfo; begin FillChar(Sei, SizeOf(Sei), #0); Sei.cbSize := SizeOf(Sei); Sei.fMask := SEE_MASK_DOENVSUBST or SEE_MASK_FLAG_NO_UI; Sei.lpFile := PChar(FileName); Sei.lpParameters := PCharOrNil(Parameters); Sei.lpVerb := PCharOrNil(Verb); Sei.nShow := CmdShow; Result := ShellExecuteEx(@Sei); end; { TODO -cHelp : author Jeff note, ShellExecEx() above used to be ShellExec()... } function ShellExec(Wnd: Integer; const Operation, FileName, Parameters, Directory: string; ShowCommand: Integer): Boolean; begin Result := ShellExecute(Wnd, PChar(Operation), PChar(FileName), PChar(Parameters), PChar(Directory), ShowCommand) > 32; end; function ShellExecAndWait(const FileName: string; const Parameters: string; const Verb: string; CmdShow: Integer): Boolean; var Sei: TShellExecuteInfo; Res: LongBool; Msg: tagMSG; begin FillChar(Sei, SizeOf(Sei), #0); Sei.cbSize := SizeOf(Sei); Sei.fMask := SEE_MASK_DOENVSUBST or SEE_MASK_FLAG_NO_UI or SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_DDEWAIT; Sei.lpFile := PChar(FileName); Sei.lpParameters := PCharOrNil(Parameters); Sei.lpVerb := PCharOrNil(Verb); Sei.nShow := CmdShow; Result := ShellExecuteEx(@Sei); if Result then begin WaitForInputIdle(Sei.hProcess, INFINITE); while WaitForSingleObject(Sei.hProcess, 10) = WAIT_TIMEOUT do repeat Res := PeekMessage(Msg, Sei.Wnd, 0, 0, PM_REMOVE); if Res then begin TranslateMessage(Msg); DispatchMessage(Msg); end; until not Res; CloseHandle(Sei.hProcess); end; end; function ShellOpenAs(const FileName: string): Boolean; begin Result := ShellExecEx('rundll32', Format('shell32.dll,OpenAs_RunDLL "%s"', [FileName]), '', SW_SHOWNORMAL); end; { TODO: Dynamic linking - move TRasDialDlgA to JclWin32} type TRasDialDlgA = function(lpszPhonebook, lpszEntry, lpszPhoneNumber: PAnsiChar; lpInfo: PRasDialDlg): BOOL; stdcall; function ShellRasDial(const EntryName: string): Boolean; var Info: TRasDialDlg; RasDlg: HModule; RasDialDlgA: TRasDialDlgA; begin if IsWinNT then begin Result := False; RasDlg := LoadLibrary(PChar('rasdlg.dll')); if RasDlg <> 0 then try @RasDialDlgA := GetProcAddress(RasDlg, PChar('RasDialDlgA')); if @RasDialDlgA <> nil then begin FillChar(Info, SizeOf(Info), 0); Info.dwSize := SizeOf(Info); Result := RasDialDlgA(nil, PChar(EntryName), nil, @Info); end; finally FreeLibrary(RasDlg); end; end else Result := ShellExecEx('rundll32', Format('rnaui.dll,RnaDial "%s"', [EntryName]), '', SW_SHOWNORMAL); end; // You can pass simple name of standard system control panel (e.g. 'timedate') // or full qualified file name (Window 95 only? doesn't work on Win2K!) // MT: Added support for Windows 98..XP. Have no win95 anymore so I have to // trust that the original version works on Windows 95 and Windows 95OSR2. function ShellRunControlPanel(const NameOrFileName: string; AppletNumber: Integer): Boolean; var FileName: TFileName; begin if ExtractFilePath(NameOrFileName) = '' then FileName := ChangeFileExt(PathAddSeparator(GetWindowsSystemFolder) + NameOrFileName, '.cpl') else FileName := NameOrFileName; if FileExists(FileName) then begin if (IsWin95 or IsWin95OSR2) then Result := ShellExecEx('rundll32', Format('shell32.dll,Control_RunDLL "%s", @%d', [FileName, AppletNumber]), '', SW_SHOWNORMAL) else Result := ShellExecEx('rundll32', Format('shell32.dll,Control_RunDLL "%s",,%d', [FileName, AppletNumber]), '', SW_SHOWNORMAL) end else begin Result := False; SetLastError(ERROR_FILE_NOT_FOUND); end; end; function GetFileExeType(const FileName: TFileName): TJclFileExeType; var FileInfo: TSHFileInfo; R: DWORD; begin R := SHGetFileInfo(PChar(FileName), 0, FileInfo, SizeOf(FileInfo), SHGFI_EXETYPE); case LoWord(R) of IMAGE_DOS_SIGNATURE: Result := etMsDos; IMAGE_OS2_SIGNATURE: Result := etWin16; Word(IMAGE_NT_SIGNATURE): if HiWord(R) = 0 then Result := etWin32Con else Result := etWin32Gui; else Result := etError; end; end; function ShellFindExecutable(const FileName, DefaultDir: string): string; var Res: HINST; Buffer: TAnsiPath; I: Integer; begin FillChar(Buffer, SizeOf(Buffer), #0); Res := FindExecutable(PChar(FileName), PCharOrNil(DefaultDir), Buffer); if Res > 32 then begin // FindExecutable replaces #32 with #0 for I := Low(Buffer) to High(Buffer) - 1 do if Buffer[I] = #0 then Buffer[I] := #32; Buffer[High(Buffer)] := #0; Result := Trim(Buffer); end else Result := ''; end; function GetFileNameIcon(const FileName: string; Flags: Cardinal = 0): HICON; var FileInfo: TSHFileInfo; ImageList: HIMAGELIST; begin FillChar(FileInfo, SizeOf(FileInfo), #0); if Flags = 0 then Flags := SHGFI_SHELLICONSIZE; ImageList := SHGetFileInfo(PChar(FileName), 0, FileInfo, SizeOf(FileInfo), Flags or SHGFI_SYSICONINDEX); if ImageList <> 0 then Result := ImageList_ExtractIcon(0, ImageList, FileInfo.iIcon) else Result := 0; end; initialization //We don't load the msi functions until the first attempt to resolve an MSI link finalization UnloadModule(rtdlMsiLibHandle); // History: // $Log: JclShell.pas,v $ // Revision 1.22 2006/01/02 04:30:53 elahn // Added parameter "Explore" added to OpenFolder & OpenSpecialFolder (Mantis #3402) // // Revision 1.21 2005/12/12 21:54:10 outchy // HWND changed to THandle (linking problems with BCB). // // Revision 1.20 2005/02/25 07:20:16 marquardt // add section lines // // Revision 1.19 2005/02/24 16:34:52 marquardt // remove divider lines, add section lines (unfinished) // // Revision 1.18 2005/02/13 15:47:09 mthoma // SHEnumFolderNext works now with Win9x. // // Revision 1.17 2004/12/22 11:44:22 rikbarker // Modified ShellLinkResolve to correctly read the target from MSI style shortcuts without invoking the windows installer if the product component was set to "Install on First Use". Added dynamic links to MSI functions in msi.dll // // Revision 1.16 2004/12/03 15:36:04 rikbarker // Fixed ShellLinkResolve to correctly Resolve TargetPath for MS-Office style link files. // // Revision 1.15 2004/10/17 21:48:07 mthoma // Removed ShellRasDial contribution. Rewrite needed as soon as dynmic linking support in JclWin32 has been redesigned. // // Revision 1.14 2004/10/17 21:00:16 mthoma // cleaning // // Revision 1.13 2004/07/28 18:00:54 marquardt // various style cleanings, some minor fixes // // Revision 1.12 2004/06/14 11:05:53 marquardt // symbols added to all ENDIFs and some other minor style changes like removing IFOPT // // Revision 1.11 2004/05/09 11:22:39 rrossmair // Contributor list update // // Revision 1.10 2004/05/05 07:33:49 rrossmair // header updated according to new policy: initial developers & contributors listed // // Revision 1.9 2004/04/09 20:46:30 mthoma // Fixed 0000923 (ShellRunControlPanel). Changed $data$ to date. // // Revision 1.8 2004/04/06 04:55:18 // adapt compiler conditions, add log entry // end.