using System.Collections.Concurrent; using System.Globalization; using System.IO; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Data; using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Imaging; using Explorer.Domain; using Explorer.Presentation; namespace Explorer.App; public sealed class ShellIconConverter : IValueConverter { public object? Convert(object value, Type targetType, object parameter, CultureInfo culture) { var large = parameter is string p && p.Equals("large", StringComparison.OrdinalIgnoreCase); return value switch { FolderItemViewModel item => ShellIconCache.Get(item.FullPath, item.IsDirectory, large, item.MayHydrateOnRead), FileSystemItem fs => ShellIconCache.Get(fs.FullPath, fs.IsDirectory, large, AttributeFlags.MayHydrateOnRead(fs.Attributes)), string path => ShellIconCache.Get(path, Directory.Exists(path), large), _ => ShellIconCache.Get(null, true, large) }; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotSupportedException(); } internal static class ShellIconCache { private static readonly ConcurrentDictionary Cache = new(StringComparer.OrdinalIgnoreCase); public static ImageSource Get(string? path, bool isDirectory, bool large = false, bool avoidHydration = false) { var key = (large ? "L:" : "S:") + (avoidHydration ? "A:" : "") + CacheKey(path, isDirectory); return Cache.GetOrAdd(key, _ => Load(path, isDirectory, large, avoidHydration)); } private static string CacheKey(string? path, bool isDirectory) { if (string.IsNullOrWhiteSpace(path) || path == "This PC") { return isDirectory ? "dir:generic" : "file:generic"; } if (path == LocationRoots.RecycleBin) { return "recycle"; } if (isDirectory) { return PathRules.IsDriveRoot(path) || (path.Length <= 3 && path.Contains(':', StringComparison.Ordinal)) ? "drive:" + path.TrimEnd('\\').ToUpperInvariant() : "dir:generic"; } var ext = Path.GetExtension(path); if (ext.Equals(".exe", StringComparison.OrdinalIgnoreCase) || ext.Equals(".lnk", StringComparison.OrdinalIgnoreCase) || ext.Equals(".ico", StringComparison.OrdinalIgnoreCase) || ext.Equals(".dll", StringComparison.OrdinalIgnoreCase)) { return "path:" + path; } return "ext:" + (string.IsNullOrEmpty(ext) ? ".file" : ext.ToLowerInvariant()); } private static ImageSource Load(string? path, bool isDirectory, bool large, bool avoidHydration = false) { try { var info = new ShFileInfo(); uint flags = ShgfiIcon | (large ? ShgfiLargeIcon : ShgfiSmallIcon); uint attrs = isDirectory ? FileAttributeDirectory : FileAttributeNormal; string psz; if (string.IsNullOrWhiteSpace(path) || path == "This PC") { psz = isDirectory ? "folder" : "file"; flags |= ShgfiUseFileAttributes; } else if (path == LocationRoots.RecycleBin) { psz = @"::{645FF040-5081-101B-9F08-00AA002F954E}"; } else if (isDirectory && PathRules.IsDriveRoot(path)) { psz = PathRules.EnsureDirectoryTrailingSlashIfRoot(path); } else if (isDirectory) { psz = path; flags |= ShgfiUseFileAttributes; } else { psz = path; var ext = Path.GetExtension(path); if (avoidHydration || !ext.Equals(".exe", StringComparison.OrdinalIgnoreCase) && !ext.Equals(".lnk", StringComparison.OrdinalIgnoreCase) && !ext.Equals(".ico", StringComparison.OrdinalIgnoreCase) && File.Exists(path) == false) { flags |= ShgfiUseFileAttributes; } } SHGetFileInfo(psz, attrs, ref info, (uint)Marshal.SizeOf(), flags); if (info.hIcon == 0) { return Fallback(); } try { var source = Imaging.CreateBitmapSourceFromHIcon(info.hIcon, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); source.Freeze(); return source; } finally { DestroyIcon(info.hIcon); } } catch { return Fallback(); } } private static ImageSource Fallback() { var bmp = BitmapSource.Create(16, 16, 96, 96, PixelFormats.Bgra32, null, new byte[16 * 16 * 4], 16 * 4); bmp.Freeze(); return bmp; } private const uint ShgfiIcon = 0x100; private const uint ShgfiLargeIcon = 0x0; private const uint ShgfiSmallIcon = 0x1; private const uint ShgfiUseFileAttributes = 0x10; private const uint FileAttributeDirectory = 0x10; private const uint FileAttributeNormal = 0x80; [DllImport("shell32.dll", CharSet = CharSet.Unicode)] private static extern nint SHGetFileInfo(string pszPath, uint dwFileAttributes, ref ShFileInfo psfi, uint cbFileInfo, uint uFlags); [DllImport("user32.dll", SetLastError = true)] private static extern bool DestroyIcon(nint hIcon); [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct ShFileInfo { public nint hIcon; public int iIcon; public uint dwAttributes; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string szDisplayName; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)] public string szTypeName; } }