using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Security;
using System.Text;
using static Vanara.PInvoke.Gdi32;
using static Vanara.PInvoke.Ole32;
using static Vanara.PInvoke.User32;
using BIND_OPTS = System.Runtime.InteropServices.ComTypes.BIND_OPTS;
namespace Vanara.PInvoke
{
public static partial class Shell32
{
/// Methods in this class will only work on Vista and above.
public static class ShellUtil
{
private const string REGSTR_PATH_METRICS = "Control Panel\\Desktop\\WindowMetrics";
private static readonly Dictionary g_rgshil;
private static readonly int SHIL_COUNT;
static ShellUtil()
{
SHIL_COUNT = Enum.GetValues(typeof(SHIL)).Length;
g_rgshil = new Dictionary(SHIL_COUNT); // new ushort[SHIL_COUNT];
var sysCxIco = GetSystemMetrics(SystemMetric.SM_CXICON);
g_rgshil[SHIL.SHIL_LARGE] = (ushort)(int)Microsoft.Win32.Registry.CurrentUser.GetValue($"{REGSTR_PATH_METRICS}\\Shell Icon Size", sysCxIco);
g_rgshil[SHIL.SHIL_SMALL] = (ushort)(int)Microsoft.Win32.Registry.CurrentUser.GetValue($"{REGSTR_PATH_METRICS}\\Shell Small Icon Size", sysCxIco / 2);
g_rgshil[SHIL.SHIL_EXTRALARGE] = (ushort)(3 * sysCxIco / 2);
g_rgshil[SHIL.SHIL_SYSSMALL] = (ushort)GetSystemMetrics(SystemMetric.SM_CXSMICON);
g_rgshil[SHIL.SHIL_JUMBO] = 256;
}
/// Wrapper for native CreateBindCtx and SetBindOptions.
///
/// Represents flags that should be used when opening the file that contains the object identified by the moniker.
///
///
/// Indicates the amount of time (clock time in milliseconds) that the caller specified to complete the binding operation.
///
/// Flags that control aspects of moniker binding operations.
public static IBindCtx CreateBindCtx(STGM openMode = STGM.STGM_READWRITE, TimeSpan timeout = default, BIND_FLAGS bindFlags = 0)
{
Ole32.CreateBindCtx(0, out var ctx).ThrowIfFailed();
if (openMode != STGM.STGM_READWRITE || timeout != TimeSpan.Zero || bindFlags != 0)
{
var opts = new BIND_OPTS { cbStruct = Marshal.SizeOf(typeof(BIND_OPTS)), grfMode = (int)openMode, dwTickCountDeadline = (int)timeout.TotalMilliseconds, grfFlags = (int)bindFlags };
ctx.SetBindOptions(ref opts);
}
return ctx;
}
/// Gets the KNOWNFOLDERID enum from a KNOWNFOLDERID Guid.
/// The KNOWNFOLDERID Guid.
/// The KNOWNFOLDERID enum.
public static KNOWNFOLDERID GetKnownFolderFromGuid(Guid knownFolder) =>
Enum.GetValues(typeof(KNOWNFOLDERID)).Cast().Single(k => k.Guid() == knownFolder);
/// Gets the KNOWNFOLDERID enum from a path.
/// The folder path.
/// The KNOWNFOLDERID enum.
public static KNOWNFOLDERID GetKnownFolderFromPath(string path)
{
if (Environment.OSVersion.Version.Major < 6)
return Enum.GetValues(typeof(KNOWNFOLDERID)).Cast().Single(k => string.Equals(k.FullPath(), path, StringComparison.InvariantCultureIgnoreCase));
var ikfm = new IKnownFolderManager();
return GetKnownFolderFromGuid(ikfm.FindFolderFromPath(path, FFFP_MODE.FFFP_EXACTMATCH).GetId());
}
/// Gets the parent and item for the supplied IShellItem regardless of support by IShellItem.
/// The IShellItem instance.
/// An IParentAndItem reference for the shell item.
public static IParentAndItem GetParentAndItem(IShellItem psi) => psi is IParentAndItem pi ? pi : new ManualParentAndItem(psi);
/// Gets the path for a KNOWNFOLDERID Guid.
/// The KNOWNFOLDERID Guid.
/// The file system path.
[SecurityCritical]
public static string GetPathForKnownFolder(Guid knownFolder)
{
if (knownFolder == default)
return null;
var pathBuilder = new StringBuilder(260);
if (SHGetFolderPathEx(knownFolder, 0, HTOKEN.NULL, pathBuilder, (uint)pathBuilder.Capacity).Failed)
return null;
return pathBuilder.ToString();
}
/// Gets the path from shell item.
/// The shell item.
/// The file system path.
[SecurityCritical]
public static string GetPathFromShellItem(IShellItem item) => item.GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEEDITING);
/// Gets the shell item for a file system path.
/// The file system path.
/// The corresponding IShellItem.
[SecurityCritical]
public static IShellItem GetShellItemForPath(string path)
{
if (string.IsNullOrEmpty(path))
return null;
// Handle case of a 'shell' URI and convert GUID to known folder
//if (path.StartsWith("shell:::", StringComparison.InvariantCultureIgnoreCase))
//{
// path = path.Substring(8);
// var separatorIndex = path.IndexOf('/');
// var kf = new Guid(separatorIndex == -1 ? path : path.Substring(0, separatorIndex));
// var fullPath = GetPathForKnownFolder(kf);
// if (separatorIndex != -1)
// fullPath += path.Substring(separatorIndex).Replace('/', '\\');
// path = fullPath;
//}
var hr = SHCreateItemFromParsingName(path, null, typeof(IShellItem).GUID, out var unk);
if (hr == (HRESULT)Win32Error.ERROR_FILE_NOT_FOUND)
{
using var ibc = InteropServices.ComReleaserFactory.Create(CreateBindCtx());
var bd = new IntFileSysBindData();
ibc.Item.RegisterObjectParam(STR_FILE_SYS_BIND_DATA, bd);
return SHCreateItemFromParsingName(path, ibc.Item);
}
hr.ThrowIfFailed();
return (IShellItem)unk;
}
/// Gets the icon for the item using the specified characteristics.
/// The IShellFolder from which to request the IExtractIcon instance.
/// The PIDL of the item within .
/// The width, in pixels, of the icon.
/// The resulting icon handle, on success, or null on failure.
/// The result of function.
public static HRESULT LoadIconFromExtractIcon(IShellFolder psf, PIDL pidl, ref uint imgSz, out SafeHICON hico)
{
hico = default;
HRESULT hr = psf.GetUIObjectOf((IntPtr)pidl, out var ieiw);
if (hr.Succeeded)
{
try
{
return LoadIconFromExtractIcon(ieiw, ref imgSz, out hico);
}
finally
{
Marshal.ReleaseComObject(ieiw);
}
}
else if ((hr = psf.GetUIObjectOf((IntPtr)pidl, out var iei)).Succeeded)
{
try
{
return LoadIconFromExtractIcon(iei, ref imgSz, out hico);
}
finally
{
Marshal.ReleaseComObject(iei);
}
}
return hr;
}
/// Gets the icon for the item using the specified characteristics.
/// The IExtractIconW from which to retrieve the icon.
/// The width, in pixels, of the icon.
/// The resulting icon handle, on success, or null on failure.
/// The result of function.
public static HRESULT LoadIconFromExtractIcon(IExtractIconW iei, ref uint imgSz, out SafeHICON hico)
{
var szIconFile = new StringBuilder(Kernel32.MAX_PATH);
var hr = iei.GetIconLocation(GetIconLocationFlags.GIL_FORSHELL, szIconFile, szIconFile.Capacity, out var iIdx, out _);
if (hr.Succeeded)
{
if (szIconFile.ToString() != "*")
hr = iei.Extract(szIconFile.ToString(), (uint)iIdx, (ushort)imgSz, out hico);
else
hr = LoadIconFromSystemImageList(iIdx, ref imgSz, out hico);
}
else
hico = null;
return hr;
}
/// Gets the icon for the item using the specified characteristics.
/// The IExtractIconA from which to retrieve the icon.
/// The width, in pixels, of the icon.
/// The resulting icon handle, on success, or null on failure.
/// The result of function.
public static HRESULT LoadIconFromExtractIcon(IExtractIconA iei, ref uint imgSz, out SafeHICON hico)
{
var szIconFile = new StringBuilder(Kernel32.MAX_PATH);
var hr = iei.GetIconLocation(GetIconLocationFlags.GIL_FORSHELL, szIconFile, szIconFile.Capacity, out var iIdx, out _);
if (hr.Succeeded)
{
if (szIconFile.ToString() != "*")
hr = iei.Extract(szIconFile.ToString(), (uint)iIdx, (ushort)imgSz, out hico);
else
hr = LoadIconFromSystemImageList(iIdx, ref imgSz, out hico);
}
else
hico = null;
return hr;
}
/// Loads an icon from the system image list.
/// A value of type int that contains the index of the image.
/// The width, in pixels, of the icon.
/// The resulting icon handle, on success, or null on failure.
/// The result of function.
public static HRESULT LoadIconFromSystemImageList(int iIdx, ref uint imgSz, out SafeHICON hico)
{
HRESULT hr;
if ((hr = SHGetImageList(PixelsToSHIL((int)imgSz), typeof(ComCtl32.IImageList).GUID, out var il)).Succeeded)
{
try
{
hico = il.GetIcon(iIdx, ComCtl32.IMAGELISTDRAWFLAGS.ILD_TRANSPARENT);
using var icoInfo = new ICONINFO();
if (GetIconInfo(hico, icoInfo))
imgSz = (uint)GetObject(icoInfo.hbmColor).bmWidth;
}
catch (COMException e)
{
hico = null;
return (HRESULT)e.ErrorCode;
}
finally
{
Marshal.ReleaseComObject(il);
}
}
else
hico = default;
return hr;
}
/// Gets the image for the item using the specified characteristics.
/// The IShellFolder from which to request the IExtractImage instance.
/// The PIDL of the item within .
/// The width, in pixels, of the Bitmap.
/// The resulting Bitmap, on success, or null on failure.
/// The result of function.
public static HRESULT LoadImageFromExtractImage(IShellFolder psf, PIDL pidl, ref uint imgSz, out SafeHBITMAP hbmp)
{
HRESULT hr = psf.GetUIObjectOf((IntPtr)pidl, out var iei);
hbmp = default;
if (hr.Succeeded)
{
try
{
var szIconFile = new StringBuilder(Kernel32.MAX_PATH);
var sz = new SIZE((int)imgSz, (int)imgSz);
IEIFLAG flags = 0;
if ((hr = iei.GetLocation(szIconFile, (uint)szIconFile.Capacity, default, ref sz, 0, ref flags)).Succeeded && (hr = iei.Extract(out hbmp)).Succeeded)
imgSz = (uint)sz.cx;
return hr;
}
finally
{
Marshal.ReleaseComObject(iei);
}
}
return hr;
}
/// Gets the thumbnail image for the item using the specified characteristics.
/// The IShellItem from which to request the IThumbnailProvider instance.
/// The width, in pixels, of the Bitmap.
/// The resulting Bitmap, on success, or null on failure.
/// The result of function.
public static HRESULT LoadImageFromThumbnailProvider(IShellItem psi, ref uint imgSz, out SafeHBITMAP hbmp)
{
try
{
var itp = psi.BindToHandler(null, BHID.BHID_ThumbnailHandler);
return LoadImageFromThumbnailProvider(itp, ref imgSz, out hbmp);
}
catch (COMException e)
{
hbmp = null;
return e.ErrorCode;
}
}
/// Gets the thumbnail image for the item using the specified characteristics.
/// The IShellFolder from which to request the IThumbnailProvider instance.
/// The PIDL of the item within .
/// The width, in pixels, of the Bitmap.
/// The resulting Bitmap, on success, or null on failure.
/// The result of function.
public static HRESULT LoadImageFromThumbnailProvider(IShellFolder psf, PIDL pidl, ref uint imgSz, out SafeHBITMAP hbmp)
{
HRESULT hr = psf.GetUIObjectOf((IntPtr)pidl, out var itp);
if (hr.Succeeded)
hr = LoadImageFromThumbnailProvider(itp, ref imgSz, out hbmp);
else
hbmp = null;
return hr;
}
/// Gets the thumbnail image for the item using the specified characteristics.
/// The itp.
/// The width, in pixels, of the Bitmap.
/// The resulting Bitmap, on success, or null on failure.
/// The result of function.
public static HRESULT LoadImageFromThumbnailProvider(IThumbnailProvider itp, ref uint imgSz, out SafeHBITMAP hbmp)
{
try
{
var hr = itp.GetThumbnail(imgSz, out hbmp, out _);
if (hr.Succeeded)
imgSz = (uint)GetObject(hbmp).bmWidth;
return hr;
}
finally
{
Marshal.ReleaseComObject(itp);
}
}
/// Given a pixel size, return the ShellImageSize value with the closest size.
/// Size, in pixels, of the image list size to search for.
/// An image list size.
public static SHIL PixelsToSHIL(int pixels) => g_rgshil.Aggregate((x, y) => Math.Abs(x.Value - pixels) < Math.Abs(y.Value - pixels) ? x : y).Key;
/// Requests a specified interface from a COM object.
/// The interface to be queried.
/// The interface identifier (IID) of the requested interface.
/// The returned interface.
public static object QueryInterface(in object iUnk, in Guid riid)
{
QueryInterface(iUnk, riid, out var ppv).ThrowIfFailed();
return ppv;
}
/// Requests a specified interface from a COM object.
/// The interface to be queried.
/// The interface identifier (IID) of the requested interface.
/// When this method returns, contains the returned interface.
/// An HRESULT that indicates the success or failure of the call.
public static HRESULT QueryInterface(in object iUnk, in Guid riid, out object ppv)
{
var tmp = riid;
HRESULT hr = Marshal.QueryInterface(Marshal.GetIUnknownForObject(iUnk), ref tmp, out var ippv);
ppv = hr.Succeeded ? Marshal.GetObjectForIUnknown(ippv) : null;
System.Diagnostics.Debug.WriteLine($"Successful QI:\t{riid}");
return hr;
}
/// Given an image list size, return the related size, in pixels, of that size defined on the system.
/// Size of the image list.
/// Pixel size of corresponding system value.
public static int SHILToPixels(SHIL imageListSize) => g_rgshil[imageListSize];
[ComVisible(true)]
private class IntFileSysBindData : IFileSystemBindData2
{
private static readonly Guid CLSID_UnknownJunction = new Guid("{fc0a77e6-9d70-4258-9783-6dab1d0fe31e}");
private Guid clsidJunction;
private WIN32_FIND_DATA fd;
private long fileId;
public IntFileSysBindData()
{
}
public HRESULT GetFileID(out long pliFileID)
{
pliFileID = fileId; return HRESULT.S_OK;
}
public HRESULT GetFindData(out WIN32_FIND_DATA pfd)
{
pfd = fd; return HRESULT.S_OK;
}
public HRESULT GetJunctionCLSID(out Guid pclsid)
{
if (clsidJunction != CLSID_UnknownJunction)
{
pclsid = clsidJunction;
return HRESULT.S_OK;
}
pclsid = Guid.Empty;
return HRESULT.E_FAIL;
}
public HRESULT SetFileID(long liFileID)
{
fileId = liFileID; return HRESULT.S_OK;
}
public HRESULT SetFindData(in WIN32_FIND_DATA pfd)
{
fd = pfd; return HRESULT.S_OK;
}
public HRESULT SetJunctionCLSID(in Guid clsid)
{
clsidJunction = clsid; return HRESULT.S_OK;
}
}
private class ManualParentAndItem : IParentAndItem, IDisposable
{
private readonly PIDL pChild;
private readonly IShellFolder psf;
public ManualParentAndItem(IShellItem psi)
{
psf = psi.BindToHandler(null, BHID.BHID_SFObject);
SHGetIDListFromObject(psi, out var pItem).ThrowIfFailed();
pChild = pItem.LastId;
pItem.Dispose();
}
void IDisposable.Dispose()
{
Marshal.ReleaseComObject(psf);
pChild.Dispose();
}
HRESULT IParentAndItem.GetParentAndItem(out PIDL ppidlParent, out IShellFolder ppsf, out PIDL ppidlChild)
{
SHGetIDListFromObject(psf, out ppidlParent).ThrowIfFailed();
ppsf = psf;
ppidlChild = new PIDL(pChild.GetBytes());
return HRESULT.S_OK;
}
HRESULT IParentAndItem.SetParentAndItem([In] PIDL pidlParent, [In] IShellFolder psf, [In] PIDL pidlChild) => HRESULT.E_NOTIMPL;
}
}
}
}