// // 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.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using TAFactory.IconPack; /// /// Provides static methods to read system icons for folders and files. /// public static class IconReader { private static readonly ConcurrentDictionary DictIconCacheMainMenu = new(); private static readonly ConcurrentDictionary DictIconCacheSubMenus = new(); public enum IconSize { Large = 0, // 32x32 pixels Small = 1, // 16x16 pixels } public enum FolderType { Open = 0, Closed = 1, } // see https://github.com/Hofknecht/SystemTrayMenu/issues/209. public static bool MainPreload { get; set; } public static void Dispose(bool includingMainMenu = true) { if (includingMainMenu) { foreach (Icon icon in DictIconCacheMainMenu.Values) { icon?.Dispose(); } } foreach (Icon icon in DictIconCacheSubMenus.Values) { icon?.Dispose(); } } public static bool ClearIfCacheTooBig() { bool cleared = false; if (DictIconCacheSubMenus.Count > Properties.Settings.Default.ClearCacheIfMoreThanThisNumberOfItems) { Dispose(false); DictIconCacheSubMenus.Clear(); cleared = true; } return cleared; } public static Icon GetExtractAllIconsLastWithCache( string filePath, bool updateIconInBackground, bool isMainMenu, out bool loading) { bool linkOverlay = false; loading = false; string key = filePath; string extension = Path.GetExtension(filePath); if (IsExtensionWithSameIcon(extension)) { key = extension + linkOverlay; } if (!DictIconCache(isMainMenu).TryGetValue(key, out Icon icon) && !DictIconCache(!isMainMenu).TryGetValue(key, out icon)) { icon = Resources.StaticResources.LoadingIcon; loading = true; if (updateIconInBackground) { new Thread(UpdateIconInBackground).Start(); void UpdateIconInBackground() { DictIconCache(isMainMenu).GetOrAdd(key, GetExtractAllIconsLast); } } } Icon GetExtractAllIconsLast(string keyExtension) { return GetExtractAllIconsLastSTA(filePath); } static Icon GetExtractAllIconsLastSTA(string filePath) { Icon icon = null; if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) { icon = GetExtractAllIconsLast(filePath); } else { Thread staThread = new(new ParameterizedThreadStart(StaThreadMethod)); void StaThreadMethod(object obj) { icon = GetExtractAllIconsLast(filePath); } staThread.SetApartmentState(ApartmentState.STA); staThread.Start(icon); staThread.Join(); } static Icon GetExtractAllIconsLast(string filePath) { StringBuilder executable = new(1024); DllImports.NativeMethods.Shell32FindExecutable(filePath, string.Empty, executable); // icon = IconReader.GetFileIcon(executable, false); // e.g. VS 2019 icon, need another icom in imagelist List extractedIcons = IconHelper.ExtractAllIcons( executable.ToString()); return extractedIcons.Last(); } return icon; } return icon; } public static Icon GetFileIconWithCache( string filePath, bool linkOverlay, bool updateIconInBackground, bool isMainMenu, out bool loading, string keyPath = "") { loading = false; string extension = Path.GetExtension(filePath); IconSize size = IconSize.Small; if (Scaling.Factor > 1) { size = IconSize.Large; } string key = filePath; if (!string.IsNullOrEmpty(keyPath)) { key = keyPath; } if (IsExtensionWithSameIcon(extension)) { key = extension + linkOverlay; } if (!DictIconCache(isMainMenu).TryGetValue(key, out Icon icon) && !DictIconCache(!isMainMenu).TryGetValue(key, out icon)) { icon = Resources.StaticResources.LoadingIcon; loading = true; if (updateIconInBackground) { new Thread(UpdateIconInBackground).Start(); void UpdateIconInBackground() { DictIconCache(isMainMenu).GetOrAdd(key, GetIcon); } } } Icon GetIcon(string keyExtension) { return GetFileIconSTA(filePath, linkOverlay, size); } static Icon GetFileIconSTA(string filePath, bool linkOverlay, IconSize size = IconSize.Small) { Icon icon = null; if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) { icon = GetFileIcon(filePath, linkOverlay, size); } else { Thread staThread = new(new ParameterizedThreadStart(StaThreadMethod)); void StaThreadMethod(object obj) { icon = GetFileIcon(filePath, linkOverlay, size); } staThread.SetApartmentState(ApartmentState.STA); staThread.Start(icon); staThread.Join(); } return icon; } return icon; } public static Icon GetFolderIconWithCache( string path, FolderType folderType, bool linkOverlay, bool updateIconInBackground, bool isMainMenu, out bool loading) { loading = false; // always IconSize.Small, because IconSize.Large returns another folder icon than windows explorer IconSize size = IconSize.Small; string key = path; if (!DictIconCache(isMainMenu).TryGetValue(key, out Icon icon) && !DictIconCache(!isMainMenu).TryGetValue(key, out icon)) { icon = Resources.StaticResources.LoadingIcon; loading = true; if (updateIconInBackground) { if (MainPreload) { DictIconCache(isMainMenu).GetOrAdd(key, GetFolder); } else { new Thread(UpdateIconInBackground).Start(); void UpdateIconInBackground() { DictIconCache(isMainMenu).GetOrAdd(key, GetFolder); } } } } Icon GetFolder(string keyExtension) { return GetFolderIconSTA(path, folderType, linkOverlay, size); } return icon; } public static Icon GetFolderIconSTA( string directoryPath, FolderType folderType, bool linkOverlay, IconSize size = IconSize.Small) { Icon icon = null; if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) { icon = GetFolderIcon( directoryPath, folderType, linkOverlay, size); } else { Thread staThread = new(new ParameterizedThreadStart(StaThreadMethod)); void StaThreadMethod(object obj) { icon = GetFolderIcon( directoryPath, folderType, linkOverlay, size); } staThread.SetApartmentState(ApartmentState.STA); staThread.Start(icon); staThread.Join(); } return icon; } public static Icon GetFolderIcon( string directoryPath, FolderType folderType, bool linkOverlay, IconSize size = IconSize.Small) { Icon icon = null; // Need to add size check, although errors generated at present! // uint flags = Shell32.SHGFI_ICON | Shell32.SHGFI_USEFILEATTRIBUTES; // Removed SHGFI_USEFILEATTRIBUTES, otherwise was wrong folder icon uint flags = DllImports.NativeMethods.ShgfiIcon; // | Shell32.SHGFI_USEFILEATTRIBUTES; if (linkOverlay) { flags += DllImports.NativeMethods.ShgfiLINKOVERLAY; } if (folderType == FolderType.Open) { flags += DllImports.NativeMethods.ShgfiOPENICON; } if (size == IconSize.Small) { flags += DllImports.NativeMethods.ShgfiSMALLICON; } else { flags += DllImports.NativeMethods.ShgfiLARGEICON; } // Get the folder icon DllImports.NativeMethods.SHFILEINFO shfi = default; IntPtr success = DllImports.NativeMethods.Shell32SHGetFileInfo( directoryPath, DllImports.NativeMethods.FileAttributeDirectory, ref shfi, (uint)Marshal.SizeOf(shfi), flags); if (success != IntPtr.Zero && shfi.hIcon != IntPtr.Zero) { try { icon = (Icon)Icon.FromHandle(shfi.hIcon).Clone(); DllImports.NativeMethods.User32DestroyIcon(shfi.hIcon); } catch (Exception ex) { Log.Error($"directoryPath:'{directoryPath}'", ex); } } return icon; } public static Icon AddIconOverlay(Icon originalIcon, Icon overlay) { Icon icon = null; 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(); DllImports.NativeMethods.User32DestroyIcon(hIcon); } return icon; } private static ConcurrentDictionary DictIconCache(bool isMainMenu) { if (isMainMenu) { return DictIconCacheMainMenu; } else { return DictIconCacheSubMenus; } } 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 GetFileIcon(string filePath, bool linkOverlay, IconSize size = IconSize.Small) { Icon icon = null; DllImports.NativeMethods.SHFILEINFO shfi = default; uint flags = DllImports.NativeMethods.ShgfiIcon | DllImports.NativeMethods.ShgfiSYSICONINDEX; if (linkOverlay) { flags += DllImports.NativeMethods.ShgfiLINKOVERLAY; } // Check the size specified for return. if (size == IconSize.Small) { flags += DllImports.NativeMethods.ShgfiSMALLICON; } else { flags += DllImports.NativeMethods.ShgfiLARGEICON; } IntPtr hImageList = DllImports.NativeMethods.Shell32SHGetFileInfo( filePath, DllImports.NativeMethods.FileAttributeNormal, ref shfi, (uint)Marshal.SizeOf(shfi), flags); if (hImageList != IntPtr.Zero) { IntPtr hIcon; if (linkOverlay) { // Get icon directly hIcon = shfi.hIcon; } else { // Get icon from .ink without overlay hIcon = DllImports.NativeMethods.ImageList_GetIcon(hImageList, shfi.iIcon, DllImports.NativeMethods.IldTransparent); } try { // Copy (clone) the returned icon to a new object, thus allowing us to clean-up properly icon = (Icon)Icon.FromHandle(hIcon).Clone(); } catch (Exception ex) { Log.Warn($"filePath:'{filePath}'", ex); } // Cleanup if (!linkOverlay) { DllImports.NativeMethods.User32DestroyIcon(hIcon); } DllImports.NativeMethods.User32DestroyIcon(shfi.hIcon); } return icon; } } }