// // Copyright (c) PlaceholderCompany. All rights reserved. // // see also: https://www.codeproject.com/Articles/2532/Obtaining-and-managing-file-and-folder-icons-using. namespace SystemTrayMenu.Utilities { using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Runtime.InteropServices; using System.Threading; using SystemTrayMenu.DllImports; /// /// Provides static methods to read system icons for folders and files. /// public static class IconReader { private static readonly ConcurrentDictionary IconDictPersistent = new(); private static readonly ConcurrentDictionary IconDictCache = new(); public enum IconSize { Large = 0, // 32x32 pixels Small = 1, // 16x16 pixels } // see https://github.com/Hofknecht/SystemTrayMenu/issues/209. public static bool IsPreloading { get; set; } = true; public static void ClearCacheWhenLimitReached() { if (IconDictCache.Count > Properties.Settings.Default.ClearCacheIfMoreThanThisNumberOfItems) { foreach (Icon? icon in IconDictCache.Values) { icon?.Dispose(); } IconDictCache.Clear(); GC.Collect(); } } public static void RemoveIconFromCache(string path) { if (IconDictPersistent.Remove(path, out Icon? iconToRemove)) { iconToRemove?.Dispose(); } } public static Icon? GetFileIconWithCache( string path, string resolvedPath, bool linkOverlay, bool updateIconInBackground, bool checkPersistentFirst, out bool loading) { loading = false; Icon? icon = null; string key; string extension = Path.GetExtension(path); if (IsExtensionWithSameIcon(extension)) { key = extension + linkOverlay; } else { key = path; } if (!DictIconCache(checkPersistentFirst).TryGetValue(key, out icon) && !DictIconCache(!checkPersistentFirst).TryGetValue(key, out icon)) { icon = Resources.StaticResources.LoadingIcon; loading = true; if (updateIconInBackground) { IconSize size = IconSize.Small; if (Scaling.Factor >= 1.25f || Scaling.FactorByDpi >= 1.25f || Properties.Settings.Default.IconSizeInPercent / 100f >= 1.25f) { size = IconSize.Large; } new Thread(UpdateIconInBackground).Start(); void UpdateIconInBackground() { DictIconCache(checkPersistentFirst).GetOrAdd(key, GetIconSTA(path, resolvedPath, linkOverlay, size, false)); } } } return icon; } public static Icon? GetFolderIconWithCache( string path, bool linkOverlay, bool updateIconInBackground, bool checkPersistentFirst, out bool loading) { loading = false; Icon? icon = null; string key = path; if (!DictIconCache(checkPersistentFirst).TryGetValue(key, out icon) && !DictIconCache(!checkPersistentFirst).TryGetValue(key, out icon)) { icon = Resources.StaticResources.LoadingIcon; loading = true; if (updateIconInBackground) { IconSize size = IconSize.Small; if (Scaling.Factor >= 1.25f || Scaling.FactorByDpi >= 1.25f || Properties.Settings.Default.IconSizeInPercent / 100f >= 1.25f) { // IconSize.Large returns another folder icon than windows explorer size = IconSize.Large; } if (IsPreloading) { DictIconCache(checkPersistentFirst).GetOrAdd(key, GetFolder); } else { new Thread(UpdateIconInBackground).Start(); void UpdateIconInBackground() { DictIconCache(checkPersistentFirst).GetOrAdd(key, GetFolder); } } Icon? GetFolder(string keyExtension) { return GetIconSTA(path, path, linkOverlay, size, true); } } } return icon; } public static Icon? GetIconSTA(string path, string resolvedPath, bool linkOverlay, IconSize size, bool isFolder) { Icon? icon = null; if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) { icon = GetIcon(path, resolvedPath, linkOverlay, size, isFolder); } else { Thread staThread = new(new ParameterizedThreadStart(StaThreadMethod)); void StaThreadMethod(object? obj) { icon = GetIcon(path, resolvedPath, linkOverlay, size, isFolder); } staThread.SetApartmentState(ApartmentState.STA); staThread.Start(icon); staThread.Join(); } return icon; } public static Icon? AddIconOverlay(Icon? originalIcon, Icon overlay) { Icon? icon = originalIcon; if (originalIcon != null) { using Bitmap target = new(originalIcon.Width, originalIcon.Height, PixelFormat.Format32bppArgb); using Graphics graphics = Graphics.FromImage(target); graphics.DrawIcon(originalIcon, 0, 0); graphics.DrawIcon(overlay, new(0, 0, originalIcon.Width + 2, originalIcon.Height + 2)); target.MakeTransparent(target.GetPixel(1, 1)); IntPtr hIcon = target.GetHicon(); icon = (Icon)Icon.FromHandle(hIcon).Clone(); NativeMethods.User32DestroyIcon(hIcon); } return icon; } private static ConcurrentDictionary DictIconCache(bool checkPersistentFirst) => checkPersistentFirst ? IconDictPersistent : IconDictCache; private static bool IsExtensionWithSameIcon(string fileExtension) { bool isExtensionWithSameIcon = true; List extensionsWithDiffIcons = new() { string.Empty, ".EXE", ".LNK", ".ICO", ".URL" }; if (extensionsWithDiffIcons.Contains(fileExtension.ToUpperInvariant())) { isExtensionWithSameIcon = false; } return isExtensionWithSameIcon; } private static Icon? GetIcon(string path, string resolvedPath, bool linkOverlay, IconSize size, bool isFolder) { Icon? icon; if (Path.GetExtension(path).Equals(".ico", StringComparison.InvariantCultureIgnoreCase)) { icon = Icon.ExtractAssociatedIcon(path); } else if (File.Exists(resolvedPath) && Path.GetExtension(resolvedPath).Equals(".ico", StringComparison.InvariantCultureIgnoreCase)) { icon = Icon.ExtractAssociatedIcon(resolvedPath); if (linkOverlay) { icon = AddIconOverlay(icon, Properties.Resources.LinkArrow); } } else { NativeMethods.SHFILEINFO shFileInfo = default; uint flags = GetFlags(linkOverlay, size); uint attribute = isFolder ? NativeMethods.FileAttributeDirectory : NativeMethods.FileAttributeNormal; IntPtr imageList = NativeMethods.Shell32SHGetFileInfo( path, attribute, ref shFileInfo, (uint)Marshal.SizeOf(shFileInfo), flags); icon = GetIcon(path, linkOverlay, shFileInfo, imageList); } return icon; } private static uint GetFlags(bool linkOverlay, IconSize size) { uint flags = NativeMethods.ShgfiIcon | NativeMethods.ShgfiSYSICONINDEX; if (linkOverlay) { flags += NativeMethods.ShgfiLINKOVERLAY; } if (size == IconSize.Small) { flags += NativeMethods.ShgfiSMALLICON; } else { flags += NativeMethods.ShgfiLARGEICON; } return flags; } private static Icon? GetIcon( string path, bool linkOverlay, NativeMethods.SHFILEINFO shFileInfo, IntPtr imageList) { Icon? icon = null; if (imageList != IntPtr.Zero) { IntPtr hIcon; if (linkOverlay) { hIcon = shFileInfo.hIcon; } else { hIcon = NativeMethods.ImageList_GetIcon( imageList, shFileInfo.iIcon, NativeMethods.IldTransparent); } try { icon = (Icon)Icon.FromHandle(hIcon).Clone(); } catch (Exception ex) { Log.Warn($"path:'{path}'", ex); } if (!linkOverlay) { NativeMethods.User32DestroyIcon(hIcon); } NativeMethods.User32DestroyIcon(shFileInfo.hIcon); } return icon; } } }