From 010af66b420a3b89950c50f4570da8f624e316dd Mon Sep 17 00:00:00 2001 From: Markus Hofknecht Date: Thu, 22 Apr 2021 19:18:03 +0200 Subject: [PATCH] [Feature] Read icons from system cache (#149), version 1.0.17.17 --- DataClasses/RowData.cs | 21 +- NativeDllImport/BringWindowToTop.cs | 2 +- NativeDllImport/DeleteObject.cs | 23 ++ NativeDllImport/GetIcon.cs | 50 ---- NativeDllImport/IShellItem.cs | 29 ++ NativeDllImport/IShellItemImageFactory.cs | 20 ++ .../SHCreateItemFromParsingName.cs | 23 ++ NativeDllImport/SIGDN.cs | 18 ++ NativeDllImport/SIIGBF.cs | 19 ++ NativeDllImport/SIZE.cs | 21 ++ Properties/AssemblyInfo.cs | 4 +- UserInterface/SettingsForm.cs | 12 +- Utilities/File/IconReader.cs | 257 ------------------ Utilities/{File => }/FileIni.cs | 3 +- Utilities/{File => }/FileLnk.cs | 2 +- Utilities/{File => }/FileUrl.cs | 0 Utilities/IconReader.cs | 117 ++++++++ Utilities/IconsFromSystemCache.cs | 127 +++++++++ 18 files changed, 412 insertions(+), 336 deletions(-) create mode 100644 NativeDllImport/DeleteObject.cs delete mode 100644 NativeDllImport/GetIcon.cs create mode 100644 NativeDllImport/IShellItem.cs create mode 100644 NativeDllImport/IShellItemImageFactory.cs create mode 100644 NativeDllImport/SHCreateItemFromParsingName.cs create mode 100644 NativeDllImport/SIGDN.cs create mode 100644 NativeDllImport/SIIGBF.cs create mode 100644 NativeDllImport/SIZE.cs delete mode 100644 Utilities/File/IconReader.cs rename Utilities/{File => }/FileIni.cs (93%) rename Utilities/{File => }/FileLnk.cs (98%) rename Utilities/{File => }/FileUrl.cs (100%) create mode 100644 Utilities/IconReader.cs create mode 100644 Utilities/IconsFromSystemCache.cs diff --git a/DataClasses/RowData.cs b/DataClasses/RowData.cs index 5e5e2b5..274346c 100644 --- a/DataClasses/RowData.cs +++ b/DataClasses/RowData.cs @@ -113,10 +113,7 @@ namespace SystemTrayMenu.DataClasses } else if (isDirectory) { - icon = IconReader.GetFolderIconSTA( - TargetFilePath, - IconReader.FolderType.Closed, - false); + icon = IconReader.GetFolderIconSTA(TargetFilePath); } else { @@ -142,18 +139,8 @@ namespace SystemTrayMenu.DataClasses { try { - icon = IconReader.GetFileIconWithCache(TargetFilePath, false); + icon = IconReader.GetFileIconWithCache(TargetFilePath); diposeIcon = false; - - // other project -> fails sometimes - // icon = IconHelper.ExtractIcon(TargetFilePath, 0); - - // standard way -> fails sometimes - // icon = Icon.ExtractAssociatedIcon(filePath); - - // API Code Pack -> fails sometimes - // ShellFile shellFile = ShellFile.FromFilePath(filePath); - // Bitmap shellThumb = shellFile.Thumbnail.ExtraLargeBitmap; } catch (Exception ex) { @@ -278,7 +265,7 @@ namespace SystemTrayMenu.DataClasses resolvedLnkPath = FileLnk.GetResolvedFileName(TargetFilePath); if (FileLnk.IsDirectory(resolvedLnkPath)) { - icon = IconReader.GetFolderIconSTA(TargetFilePath, IconReader.FolderType.Open, true); + icon = IconReader.GetFolderIconSTA(TargetFilePath); handled = true; isLnkDirectory = true; } @@ -341,7 +328,7 @@ namespace SystemTrayMenu.DataClasses } else { - icon = IconReader.GetFileIconWithCache(browserPath, false); + icon = IconReader.GetFileIconWithCache(browserPath); diposeIcon = false; handled = true; } diff --git a/NativeDllImport/BringWindowToTop.cs b/NativeDllImport/BringWindowToTop.cs index 1247e48..4f3bf76 100644 --- a/NativeDllImport/BringWindowToTop.cs +++ b/NativeDllImport/BringWindowToTop.cs @@ -51,7 +51,7 @@ namespace SystemTrayMenu.DllImports [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] [DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)] - private static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId); + private static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr processId); [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] [DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)] diff --git a/NativeDllImport/DeleteObject.cs b/NativeDllImport/DeleteObject.cs new file mode 100644 index 0000000..38c196c --- /dev/null +++ b/NativeDllImport/DeleteObject.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) PlaceholderCompany. All rights reserved. +// + +namespace SystemTrayMenu.DllImports +{ + using System; + using System.Runtime.InteropServices; + + /// + /// wraps the methodcalls to native windows dll's. + /// + public static partial class NativeMethods + { + internal static bool Gdi32DeleteObject(IntPtr hObject) + { + return DeleteObject(hObject); + } + + [DllImport("gdi32.dll")] + private static extern bool DeleteObject(IntPtr hObject); + } +} diff --git a/NativeDllImport/GetIcon.cs b/NativeDllImport/GetIcon.cs deleted file mode 100644 index b59ec01..0000000 --- a/NativeDllImport/GetIcon.cs +++ /dev/null @@ -1,50 +0,0 @@ -// -// Copyright (c) PlaceholderCompany. All rights reserved. -// - -namespace SystemTrayMenu.DllImports -{ - using System; - using System.Runtime.InteropServices; - - /// - /// wraps the methodcalls to native windows dll's. - /// - public static partial class NativeMethods - { -#pragma warning disable SA1600 // Elements should be documented - public const uint ShgfiIcon = 0x000000100; // get icon - public const uint ShgfiSYSICONINDEX = 0x000004000; // get system icon index - public const uint ShgfiLINKOVERLAY = 0x000008000; // put a link overlay on icon - public const uint ShgfiLARGEICON = 0x000000000; // get large icon - public const uint ShgfiSMALLICON = 0x000000001; // get small icon - public const uint ShgfiOPENICON = 0x000000002; // get open icon - public const uint FileAttributeDirectory = 0x00000010; - public const uint FileAttributeNormal = 0x00000080; - public const int IldTransparent = 0x00000001; -#pragma warning restore SA1600 // Elements should be documented - - /// - /// comctl32 ImageList_GetIcon(IntPtr hIcon). - /// - /// hIcon. - public static void Comctl32ImageListGetIcon(IntPtr hIcon) - { - _ = DestroyIcon(hIcon); - } - - /// - /// comctl32 ImageList_GetIcon(IntPtr himl, int i, int flags). - /// - /// himl. - /// i. - /// flags. - /// IntPtr. - [DllImport("comctl32", SetLastError = true, CharSet = CharSet.Unicode)] - [DefaultDllImportSearchPaths(DllImportSearchPath.UserDirectories)] - internal static extern IntPtr ImageList_GetIcon( - IntPtr himl, - int i, - int flags); - } -} diff --git a/NativeDllImport/IShellItem.cs b/NativeDllImport/IShellItem.cs new file mode 100644 index 0000000..08decaa --- /dev/null +++ b/NativeDllImport/IShellItem.cs @@ -0,0 +1,29 @@ +// +// Copyright (c) PlaceholderCompany. All rights reserved. +// + +namespace SystemTrayMenu.DllImports +{ + using System; + using System.Runtime.InteropServices; + + [ComImport] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")] + public interface IShellItem + { + void BindToHandler( + IntPtr pbc, + [MarshalAs(UnmanagedType.LPStruct)] Guid bhid, + [MarshalAs(UnmanagedType.LPStruct)] Guid riid, + out IntPtr ppv); + + void GetParent(out IShellItem ppsi); + + void GetDisplayName(SIGDN sigdnName, out IntPtr ppszName); + + void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs); + + void Compare(IShellItem psi, uint hint, out int piOrder); + } +} diff --git a/NativeDllImport/IShellItemImageFactory.cs b/NativeDllImport/IShellItemImageFactory.cs new file mode 100644 index 0000000..72cbf88 --- /dev/null +++ b/NativeDllImport/IShellItemImageFactory.cs @@ -0,0 +1,20 @@ +// +// Copyright (c) PlaceholderCompany. All rights reserved. +// + +namespace SystemTrayMenu.DllImports +{ + using System; + using System.Runtime.InteropServices; + + [ComImport] + [Guid("bcc18b79-ba16-442f-80c4-8a59c30c463b")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IShellItemImageFactory + { + void GetImage( + [In, MarshalAs(UnmanagedType.Struct)] SIZE size, + [In] SIIGBF flags, + [Out] out IntPtr phbm); + } +} diff --git a/NativeDllImport/SHCreateItemFromParsingName.cs b/NativeDllImport/SHCreateItemFromParsingName.cs new file mode 100644 index 0000000..439597d --- /dev/null +++ b/NativeDllImport/SHCreateItemFromParsingName.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) PlaceholderCompany. All rights reserved. +// + +namespace SystemTrayMenu.DllImports +{ + using System; + using System.Drawing; + using System.Runtime.InteropServices; + + /// + /// wraps the methodcalls to native windows dll's. + /// + public static partial class NativeMethods + { + [DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)] + internal static extern void SHCreateItemFromParsingName( + [In][MarshalAs(UnmanagedType.LPWStr)] string pszPath, + [In] IntPtr pbc, + [In][MarshalAs(UnmanagedType.LPStruct)] Guid riid, + [Out][MarshalAs(UnmanagedType.Interface, IidParameterIndex = 2)] out IShellItem ppv); + } +} diff --git a/NativeDllImport/SIGDN.cs b/NativeDllImport/SIGDN.cs new file mode 100644 index 0000000..cd30b96 --- /dev/null +++ b/NativeDllImport/SIGDN.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) PlaceholderCompany. All rights reserved. +// + +namespace SystemTrayMenu.DllImports +{ + public enum SIGDN : uint + { + NORMALDISPLAY = 0, + PARENTRELATIVEPARSING = 0x80018001, + PARENTRELATIVEFORADDRESSBAR = 0x8001c001, + DESKTOPABSOLUTEPARSING = 0x80028000, + PARENTRELATIVEEDITING = 0x80031001, + DESKTOPABSOLUTEEDITING = 0x8004c000, + FILESYSPATH = 0x80058000, + URL = 0x80068000, + } +} diff --git a/NativeDllImport/SIIGBF.cs b/NativeDllImport/SIIGBF.cs new file mode 100644 index 0000000..c95ad6c --- /dev/null +++ b/NativeDllImport/SIIGBF.cs @@ -0,0 +1,19 @@ +// +// Copyright (c) PlaceholderCompany. All rights reserved. +// + +namespace SystemTrayMenu.DllImports +{ + using System; + + [Flags] + public enum SIIGBF + { + SIIGBF_RESIZETOFIT = 0x00, + SIIGBF_BIGGERSIZEOK = 0x01, + SIIGBF_MEMORYONLY = 0x02, + SIIGBF_ICONONLY = 0x04, + SIIGBF_THUMBNAILONLY = 0x08, + SIIGBF_INCACHEONLY = 0x10, + } +} diff --git a/NativeDllImport/SIZE.cs b/NativeDllImport/SIZE.cs new file mode 100644 index 0000000..305542a --- /dev/null +++ b/NativeDllImport/SIZE.cs @@ -0,0 +1,21 @@ +// +// Copyright (c) PlaceholderCompany. All rights reserved. +// + +namespace SystemTrayMenu.DllImports +{ + using System.Runtime.InteropServices; + + [StructLayout(LayoutKind.Sequential)] + public struct SIZE + { + public int Cx; + public int Cy; + + public SIZE(int cx, int cy) + { + this.Cx = cx; + this.Cy = cy; + } + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 3ac235c..e9d9db2 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -39,5 +39,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.17.16")] -[assembly: AssemblyFileVersion("1.0.17.16")] +[assembly: AssemblyVersion("1.0.17.17")] +[assembly: AssemblyFileVersion("1.0.17.17")] diff --git a/UserInterface/SettingsForm.cs b/UserInterface/SettingsForm.cs index 1e5e265..1538ac2 100644 --- a/UserInterface/SettingsForm.cs +++ b/UserInterface/SettingsForm.cs @@ -127,11 +127,11 @@ namespace SystemTrayMenu.UserInterface /// bool success. private static bool RegisterHotkey(StringBuilder failedKeys, string hotkeyString, HotKeyHandler handler) { - Keys modifierKeyCode = HotkeyControl.HotkeyModifiersFromString(hotkeyString); - Keys virtualKeyCode = HotkeyControl.HotkeyFromString(hotkeyString); + Keys modifierKeyCode = HotkeyModifiersFromString(hotkeyString); + Keys virtualKeyCode = HotkeyFromString(hotkeyString); if (!Keys.None.Equals(virtualKeyCode)) { - if (HotkeyControl.RegisterHotKey(modifierKeyCode, virtualKeyCode, handler) < 0) + if (RegisterHotKey(modifierKeyCode, virtualKeyCode, handler) < 0) { if (failedKeys.Length > 0) { @@ -205,13 +205,13 @@ namespace SystemTrayMenu.UserInterface if (dr == DialogResult.Retry) { // LOG.DebugFormat("Re-trying to register hotkeys"); - HotkeyControl.UnregisterHotkeys(); + UnregisterHotkeys(); success = RegisterHotkeys(false); } else if (dr == DialogResult.Ignore) { // LOG.DebugFormat("Ignoring failed hotkey registration"); - HotkeyControl.UnregisterHotkeys(); + UnregisterHotkeys(); success = RegisterHotkeys(true); } @@ -290,7 +290,7 @@ namespace SystemTrayMenu.UserInterface private void TextBoxHotkeyEnter(object sender, EventArgs e) { - HotkeyControl.UnregisterHotkeys(); + UnregisterHotkeys(); inHotkey = true; } diff --git a/Utilities/File/IconReader.cs b/Utilities/File/IconReader.cs deleted file mode 100644 index 9417e7f..0000000 --- a/Utilities/File/IconReader.cs +++ /dev/null @@ -1,257 +0,0 @@ -// -// Copyright (c) PlaceholderCompany. All rights reserved. -// - -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.Tasks; - - // from https://www.codeproject.com/Articles/2532/Obtaining-and-managing-file-and-folder-icons-using - // added ImageList_GetIcon, IconCache, AddIconOverlay - - /// - /// Provides static methods to read system icons for both folders and files. - /// - /// - /// IconReader.GetFileIcon("c:\\general.xls"); - /// - public static class IconReader - { - private static readonly ConcurrentDictionary DictIconCache = new ConcurrentDictionary(); - - // private static readonly object ReadIcon = new object(); - public enum IconSize - { - Large = 0, // 32x32 pixels - Small = 1, // 16x16 pixels - } - - public enum FolderType - { - Open = 0, - Closed = 1, - } - - public static void Dispose() - { - foreach (Icon icon in DictIconCache.Values) - { - icon?.Dispose(); - } - } - - public static Icon GetFileIconWithCache(string filePath, bool linkOverlay, IconSize size = IconSize.Small) - { - Icon icon = null; - string extension = Path.GetExtension(filePath); - - if (IsExtensionWitSameIcon(extension)) - { - icon = DictIconCache.GetOrAdd(extension, GetIcon); - Icon GetIcon(string keyExtension) - { - return GetFileIconSTA(filePath, linkOverlay, size); - } - } - else - { - icon = GetFileIconSTA(filePath, linkOverlay, size); - } - - return icon; - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2008:Do not create tasks without passing a TaskScheduler", Justification = "todo")] - public static Icon GetFolderIconSTA( - string directoryPath, - FolderType folderType, - bool linkOverlay, - IconSize size = IconSize.Small) - { - Icon icon = null; - - Task task = Task.Factory.StartNew(() => GetFolderIcon( - directoryPath, - folderType, - linkOverlay, - size)); - icon = task.Result; - - 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; - - // MH: 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); - } -#pragma warning disable CA1031 // Do not catch general exception types - catch (Exception ex) -#pragma warning restore CA1031 // Do not catch general exception types - { - 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 Bitmap( - originalIcon.Width, - originalIcon.Height, - PixelFormat.Format32bppArgb); - Graphics graphics = Graphics.FromImage(target); - graphics.DrawIcon(originalIcon, 0, 0); - graphics.DrawIcon(overlay, 0, 0); - target.MakeTransparent(target.GetPixel(1, 1)); - icon = Icon.FromHandle(target.GetHicon()); - } - - return icon; - } - - private static bool IsExtensionWitSameIcon(string fileExtension) - { - bool isExtensionWitSameIcon = true; - List extensionsWithDiffIcons = new List - { string.Empty, ".EXE", ".LNK", ".ICO", ".URL" }; - if (extensionsWithDiffIcons.Contains(fileExtension.ToUpperInvariant())) - { - isExtensionWitSameIcon = false; - } - - return isExtensionWitSameIcon; - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2008:Do not create tasks without passing a TaskScheduler", Justification = "todo")] - private static Icon GetFileIconSTA(string filePath, bool linkOverlay, IconSize size = IconSize.Small) - { - Icon icon = null; - - Task task = Task.Factory.StartNew(() => GetFileIcon(filePath, linkOverlay, size)); - icon = task.Result; - - return icon; - } - - 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) - { - hIcon = shfi.hIcon; // Get icon directly - } - 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(); - } -#pragma warning disable CA1031 // Do not catch general exception types - catch (Exception ex) -#pragma warning restore CA1031 // Do not catch general exception types - { - Log.Error($"filePath:'{filePath}'", ex); - } - - // Cleanup - if (!linkOverlay) - { - DllImports.NativeMethods.User32DestroyIcon(hIcon); - } - - DllImports.NativeMethods.User32DestroyIcon(shfi.hIcon); - } - - return icon; - } - } -} \ No newline at end of file diff --git a/Utilities/File/FileIni.cs b/Utilities/FileIni.cs similarity index 93% rename from Utilities/File/FileIni.cs rename to Utilities/FileIni.cs index a8167de..03172cb 100644 --- a/Utilities/File/FileIni.cs +++ b/Utilities/FileIni.cs @@ -5,7 +5,6 @@ namespace SystemTrayMenu.Utilities { using System.Collections.Generic; - using System.IO; using System.Linq; public class FileIni @@ -14,7 +13,7 @@ namespace SystemTrayMenu.Utilities public FileIni(string path) { - values = File.ReadLines(path) + values = System.IO.File.ReadLines(path) .Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith("#", System.StringComparison.InvariantCulture)) .Select(line => line.Split(new char[] { '=' }, 2, 0)) diff --git a/Utilities/File/FileLnk.cs b/Utilities/FileLnk.cs similarity index 98% rename from Utilities/File/FileLnk.cs rename to Utilities/FileLnk.cs index 4ab292f..71db8e1 100644 --- a/Utilities/File/FileLnk.cs +++ b/Utilities/FileLnk.cs @@ -52,7 +52,7 @@ namespace SystemTrayMenu.Utilities public static bool IsNetworkRoot(string path) { - return !System.IO.File.Exists(path) && + return !File.Exists(path) && path.StartsWith(@"\\", StringComparison.InvariantCulture) && !path.Substring(2).Contains(@"\", StringComparison.InvariantCulture); } diff --git a/Utilities/File/FileUrl.cs b/Utilities/FileUrl.cs similarity index 100% rename from Utilities/File/FileUrl.cs rename to Utilities/FileUrl.cs diff --git a/Utilities/IconReader.cs b/Utilities/IconReader.cs new file mode 100644 index 0000000..fedc539 --- /dev/null +++ b/Utilities/IconReader.cs @@ -0,0 +1,117 @@ +// +// Copyright (c) PlaceholderCompany. All rights reserved. +// + +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.Tasks; + using SystemTrayMenu.DllImports; + using TAFactory.IconPack; + + // from https://www.codeproject.com/Articles/2532/Obtaining-and-managing-file-and-folder-icons-using + // added ImageList_GetIcon, IconCache, AddIconOverlay + + /// + /// Provides static methods to read system icons for both folders and files. + /// + /// + /// IconReader.GetFileIcon("c:\\general.xls"); + /// + public static class IconReader + { + private static readonly ConcurrentDictionary DictIconCache = new ConcurrentDictionary(); + + // private static readonly object ReadIcon = new object(); + public enum IconSize + { + Large = 0, // 32x32 pixels + Small = 1, // 16x16 pixels + } + + public enum FolderType + { + Open = 0, + Closed = 1, + } + + public static void Dispose() + { + foreach (Icon icon in DictIconCache.Values) + { + icon?.Dispose(); + } + } + + public static Icon GetFileIconWithCache(string filePath) + { + Icon icon = null; + string extension = Path.GetExtension(filePath); + + if (IsExtensionWitSameIcon(extension)) + { + icon = DictIconCache.GetOrAdd(extension, GetIcon); + Icon GetIcon(string keyExtension) + { + return GetFileIconSTA(filePath); + } + } + else + { + icon = GetFileIconSTA(filePath); + } + + return icon; + } + + public static Icon GetFolderIconSTA(string directoryPath) + { + Task task = Task.Factory.StartNew(() => IconsFromSystemCache.GetIcon(directoryPath)); + return task.Result; + } + + public static Icon AddIconOverlay(Icon originalIcon, Icon overlay) + { + Icon icon = null; + if (originalIcon != null) + { + using Bitmap target = new Bitmap( + originalIcon.Width, + originalIcon.Height, + PixelFormat.Format32bppArgb); + Graphics graphics = Graphics.FromImage(target); + graphics.DrawIcon(originalIcon, 0, 0); + graphics.DrawIcon(overlay, 0, 0); + target.MakeTransparent(target.GetPixel(1, 1)); + icon = Icon.FromHandle(target.GetHicon()); + } + + return icon; + } + + private static bool IsExtensionWitSameIcon(string fileExtension) + { + bool isExtensionWitSameIcon = true; + List extensionsWithDiffIcons = new List + { string.Empty, ".EXE", ".LNK", ".ICO", ".URL" }; + if (extensionsWithDiffIcons.Contains(fileExtension.ToUpperInvariant())) + { + isExtensionWitSameIcon = false; + } + + return isExtensionWitSameIcon; + } + + private static Icon GetFileIconSTA(string filePath) + { + Task task = Task.Factory.StartNew(() => IconsFromSystemCache.GetIcon(filePath)); + return task.Result; + } + } +} \ No newline at end of file diff --git a/Utilities/IconsFromSystemCache.cs b/Utilities/IconsFromSystemCache.cs new file mode 100644 index 0000000..cf81264 --- /dev/null +++ b/Utilities/IconsFromSystemCache.cs @@ -0,0 +1,127 @@ +// +// Copyright (c) PlaceholderCompany. All rights reserved. +// + +namespace SystemTrayMenu.Utilities +{ + using System; + using System.Drawing; + using System.Drawing.Imaging; + using System.IO; + using System.Runtime.InteropServices; + using SystemTrayMenu.DllImports; + + public static partial class IconsFromSystemCache + { + /// + /// Get Icon from system cache. + /// http://pinvoke.net/default.aspx/Interfaces/IShellItem.html?diff=y. + /// https://github.com/Hofknecht/SystemTrayMenu/issues/149. + /// + /// filename. + /// Icon. + public static Icon GetIcon(string filename) + { + Icon icon = null; + + // GUID of IShellItem. + Guid uuid = new Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe"); + + try + { + NativeMethods.SHCreateItemFromParsingName(filename, IntPtr.Zero, uuid, out IShellItem ppsi); + IntPtr hbitmap = IntPtr.Zero; + ((IShellItemImageFactory)ppsi).GetImage(new SIZE(16, 16), SIIGBF.SIIGBF_ICONONLY, out hbitmap); + + Bitmap bitmap = GetBitmapFromHBitmap(hbitmap); + NativeMethods.Gdi32DeleteObject(hbitmap); + bitmap.MakeTransparent(); + IntPtr icH = bitmap.GetHicon(); + + icon = (Icon)Icon.FromHandle(icH).Clone(); + NativeMethods.User32DestroyIcon(icH); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + if (ex is FileNotFoundException) + { + Log.Warn($"filename:'{filename}'", ex); + } + else + { + Log.Error($"filename:'{filename}'", ex); + + // throw; // comment in to remove CA1031 + } + } + + return icon; + } + + /// + /// Preserving Alpha channel + /// https://stackoverflow.com/questions/4627376/use-native-hbitmap-in-c-sharp-while-preserving-alpha-channel-transparency/9291151#9291151. + /// (when using "Bitmap image = Image.FromHbitmap(hbitmap);" alpha channel lost). + /// + /// nativeHBitmap. + /// Bitmap. + private static Bitmap GetBitmapFromHBitmap(IntPtr nativeHBitmap) + { + Bitmap bmp = Image.FromHbitmap(nativeHBitmap); + + if (Image.GetPixelFormatSize(bmp.PixelFormat) < 32) + { + return bmp; + } + + if (IsAlphaBitmap(bmp, out BitmapData bmpData)) + { + return GetlAlphaBitmapFromBitmapData(bmpData); + } + + return bmp; + } + + private static Bitmap GetlAlphaBitmapFromBitmapData(BitmapData bmpData) + { + return new Bitmap( + bmpData.Width, + bmpData.Height, + bmpData.Stride, + PixelFormat.Format32bppArgb, + bmpData.Scan0); + } + + private static bool IsAlphaBitmap(Bitmap bmp, out BitmapData bmpData) + { + Rectangle bmBounds = new Rectangle(0, 0, bmp.Width, bmp.Height); + + bmpData = bmp.LockBits(bmBounds, ImageLockMode.ReadOnly, bmp.PixelFormat); + + try + { + for (int y = 0; y <= bmpData.Height - 1; y++) + { + for (int x = 0; x <= bmpData.Width - 1; x++) + { + Color pixelColor = Color.FromArgb( + Marshal.ReadInt32(bmpData.Scan0, (bmpData.Stride * y) + (4 * x))); + + if (pixelColor.A > 0 & pixelColor.A < 255) + { + return true; + } + } + } + } + finally + { + bmp.UnlockBits(bmpData); + } + + return false; + } + } +}