diff --git a/src/LogExpert.UI/Controls/LogWindow/LogWindowPublic.cs b/src/LogExpert.UI/Controls/LogWindow/LogWindowPublic.cs index 7b3ae108..9fce6a8c 100644 --- a/src/LogExpert.UI/Controls/LogWindow/LogWindowPublic.cs +++ b/src/LogExpert.UI/Controls/LogWindow/LogWindowPublic.cs @@ -11,6 +11,7 @@ using LogExpert.Core.EventArguments; using LogExpert.Dialogs; using LogExpert.UI.Entities; +using LogExpert.UI.Extensions; namespace LogExpert.UI.Controls.LogWindow; @@ -581,6 +582,28 @@ public void FollowTailChanged (bool isChecked, bool byTrigger) SendGuiStateUpdate(); } + public void TryToTruncate () + { + try + { + if (LockFinder.CheckIfFileIsLocked(Title)) + { + var name = LockFinder.FindLockedProcessName(Title); + StatusLineText($"Truncate failed: file is locked by {name}"); + } + else + { + File.WriteAllText(Title, ""); + } + } + catch (Exception ex) + { + _logger.Warn($"Unexpected issue truncating file: {ex.Message}"); + StatusLineText("Unexpected issue truncating file"); + throw; + } + } + public void GotoLine (int line) { if (line >= 0) diff --git a/src/LogExpert.UI/Controls/LogWindow/TimeSpreadigControl.cs b/src/LogExpert.UI/Controls/LogWindow/TimeSpreadigControl.cs index c65bc9a9..7aea14ed 100644 --- a/src/LogExpert.UI/Controls/LogWindow/TimeSpreadigControl.cs +++ b/src/LogExpert.UI/Controls/LogWindow/TimeSpreadigControl.cs @@ -17,7 +17,7 @@ internal partial class TimeSpreadingControl : UserControl private Bitmap _bitmap = new(1, 1); private int _displayHeight = 1; - private readonly int _edgeOffset = (int)Win32.GetSystemMetricsForDpi(Win32.SM_CYVSCROLL); + private readonly int _edgeOffset = (int)NativeMethods.GetSystemMetricsForDpi(NativeMethods.SM_CYVSCROLL); private int _lastMouseY; private readonly object _monitor = new(); private int _rectHeight = 1; diff --git a/src/LogExpert.UI/Dialogs/ChooseIconDlg.cs b/src/LogExpert.UI/Dialogs/ChooseIconDlg.cs index ddce747d..bee7a594 100644 --- a/src/LogExpert.UI/Dialogs/ChooseIconDlg.cs +++ b/src/LogExpert.UI/Dialogs/ChooseIconDlg.cs @@ -1,4 +1,4 @@ -using LogExpert.UI.Extensions; +using LogExpert.UI.Extensions; using System.Runtime.Versioning; namespace LogExpert.UI.Dialogs; @@ -38,7 +38,7 @@ private void FillIconList() { iconListView.Items.Clear(); - Icon[,] icons = Win32.ExtractIcons(FileName); + Icon[,] icons = NativeMethods.ExtractIcons(FileName); if (icons == null) { diff --git a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs index 254707aa..43742412 100644 --- a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs +++ b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs @@ -170,7 +170,7 @@ public LogTabWindow (string[] fileNames, int instanceNumber, bool showInstanceNu public void ChangeTheme (Control.ControlCollection container) { ColorMode.LoadColorMode(ConfigManager.Settings.Preferences.DarkMode); - Win32.UseImmersiveDarkMode(Handle, ColorMode.DarkModeEnabled); + NativeMethods.UseImmersiveDarkMode(Handle, ColorMode.DarkModeEnabled); #region ApplyColorToAllControls foreach (Control component in container) diff --git a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.designer.cs b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.designer.cs index 4030f93d..3821a872 100644 --- a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.designer.cs +++ b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.designer.cs @@ -155,6 +155,7 @@ private void InitializeComponent() tabRenameToolStripMenuItem = new ToolStripMenuItem(); copyPathToClipboardToolStripMenuItem = new ToolStripMenuItem(); findInExplorerToolStripMenuItem = new ToolStripMenuItem(); + truncateFileToolStripMenuItem = new ToolStripMenuItem(); dragControlDateTime = new DateTimeDragControl(); statusStrip.SuspendLayout(); mainMenuStrip.SuspendLayout(); @@ -1053,7 +1054,7 @@ private void InitializeComponent() // tabContextMenuStrip.ForeColor = System.Drawing.SystemColors.ControlText; tabContextMenuStrip.ImageScalingSize = new System.Drawing.Size(24, 24); - tabContextMenuStrip.Items.AddRange(new ToolStripItem[] { closeThisTabToolStripMenuItem, closeOtherTabsToolStripMenuItem, closeAllTabsToolStripMenuItem, tabColorToolStripMenuItem, tabRenameToolStripMenuItem, copyPathToClipboardToolStripMenuItem, findInExplorerToolStripMenuItem }); + tabContextMenuStrip.Items.AddRange(new ToolStripItem[] { closeThisTabToolStripMenuItem, closeOtherTabsToolStripMenuItem, closeAllTabsToolStripMenuItem, tabColorToolStripMenuItem, tabRenameToolStripMenuItem, copyPathToClipboardToolStripMenuItem, findInExplorerToolStripMenuItem, truncateFileToolStripMenuItem }); tabContextMenuStrip.Name = "tabContextMenuStrip"; tabContextMenuStrip.Size = new System.Drawing.Size(197, 158); // @@ -1114,6 +1115,14 @@ private void InitializeComponent() findInExplorerToolStripMenuItem.ToolTipText = "Opens an Explorer window and selects the log file"; findInExplorerToolStripMenuItem.Click += OnFindInExplorerToolStripMenuItemClick; // + // truncateFileToolStripMenuItem + // + this.truncateFileToolStripMenuItem.Name = "truncateFileToolStripMenuItem"; + this.truncateFileToolStripMenuItem.Size = new System.Drawing.Size(196, 22); + this.truncateFileToolStripMenuItem.Text = "Truncate File"; + this.truncateFileToolStripMenuItem.ToolTipText = "Try to truncate the file opened in tab"; + this.truncateFileToolStripMenuItem.Click += new System.EventHandler(this.TruncateFileToolStripMenuItem_Click); + // // dragControlDateTime // dragControlDateTime.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; @@ -1236,6 +1245,7 @@ private void InitializeComponent() private ToolStripButton toolStripButtonBubbles; private ToolStripMenuItem copyPathToClipboardToolStripMenuItem; private ToolStripMenuItem findInExplorerToolStripMenuItem; + private ToolStripMenuItem truncateFileToolStripMenuItem; private ToolStripMenuItem exportBookmarksToolStripMenuItem; private ToolStripComboBox groupsComboBoxHighlightGroups; private ToolStripMenuItem debugToolStripMenuItem; diff --git a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindowEventHandlers.cs b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindowEventHandlers.cs index 3cb32910..3b0dc376 100644 --- a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindowEventHandlers.cs +++ b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindowEventHandlers.cs @@ -847,7 +847,11 @@ private void OnFindInExplorerToolStripMenuItemClick (object sender, EventArgs e) explorer.Start(); } - [SupportedOSPlatform("windows")] + private void TruncateFileToolStripMenuItem_Click (object sender, EventArgs e) + { + CurrentLogWindow?.TryToTruncate(); + } + private void OnExportBookmarksToolStripMenuItemClick (object sender, EventArgs e) { CurrentLogWindow?.ExportBookmarkList(); diff --git a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindowPrivate.cs b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindowPrivate.cs index ec93bd35..68fcff65 100644 --- a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindowPrivate.cs +++ b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindowPrivate.cs @@ -774,7 +774,7 @@ private Icon CreateLedIcon (int level, bool dirty, int tailState, int syncMode) // a managed copy of icon. then the unmanaged win32 handle is destroyed var iconHandle = bmp.GetHicon(); var icon = Icon.FromHandle(iconHandle).Clone() as Icon; - Win32.DestroyIcon(iconHandle); + NativeMethods.DestroyIcon(iconHandle); gfx.Dispose(); bmp.Dispose(); @@ -1033,7 +1033,7 @@ private void SetTabIcons (Preferences preferences) [SupportedOSPlatform("windows")] private void SetToolIcon (ToolEntry entry, ToolStripItem item) { - Icon icon = Win32.LoadIconFromExe(entry.IconFile, entry.IconIndex); + Icon icon = NativeMethods.LoadIconFromExe(entry.IconFile, entry.IconIndex); if (icon != null) { item.Image = icon.ToBitmap(); @@ -1046,7 +1046,7 @@ private void SetToolIcon (ToolEntry entry, ToolStripItem item) item.DisplayStyle = ToolStripItemDisplayStyle.Image; } - Win32.DestroyIcon(icon.Handle); + NativeMethods.DestroyIcon(icon.Handle); icon.Dispose(); } diff --git a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindowPublic.cs b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindowPublic.cs index 4058efe4..eb4745ac 100644 --- a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindowPublic.cs +++ b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindowPublic.cs @@ -297,7 +297,7 @@ public void SelectTab (ILogWindow logWindow) [SupportedOSPlatform("windows")] public void SetForeground () { - Win32.SetForegroundWindow(Handle); + NativeMethods.SetForegroundWindow(Handle); if (WindowState == FormWindowState.Minimized) { if (_wasMaximized) diff --git a/src/LogExpert.UI/Dialogs/LogTabWindow/SettingsDialog.cs b/src/LogExpert.UI/Dialogs/LogTabWindow/SettingsDialog.cs index d6b20a7d..bdf5df26 100644 --- a/src/LogExpert.UI/Dialogs/LogTabWindow/SettingsDialog.cs +++ b/src/LogExpert.UI/Dialogs/LogTabWindow/SettingsDialog.cs @@ -558,12 +558,12 @@ private void DisplayCurrentIcon () { if (_selectedTool != null) { - Icon icon = Win32.LoadIconFromExe(_selectedTool.IconFile, _selectedTool.IconIndex); + Icon icon = NativeMethods.LoadIconFromExe(_selectedTool.IconFile, _selectedTool.IconIndex); if (icon != null) { Image image = icon.ToBitmap(); buttonIcon.Image = image; - Win32.DestroyIcon(icon.Handle); + NativeMethods.DestroyIcon(icon.Handle); icon.Dispose(); } else diff --git a/src/LogExpert.UI/Extensions/LockFinder.cs b/src/LogExpert.UI/Extensions/LockFinder.cs new file mode 100644 index 00000000..ad4c74c7 --- /dev/null +++ b/src/LogExpert.UI/Extensions/LockFinder.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; + +// Expanded with some helpers from: https://code.msdn.microsoft.com/windowsapps/How-to-know-the-process-704839f4/ +// Uses Windows Restart Manager. +// A more involved and cross platform solution to this problem is here: https://github.com/cklutz/LockCheck + + +namespace LogExpert.UI.Extensions; + +internal class LockFinder +{ + + /// + /// Method FindLockedProcessName Retrieve the first process name + /// that is locking the file at the specified path + /// + /// The path of a file with a write lock held by a + /// process + /// The name of the first process found with a lock + /// + /// Thrown when the file path is not locked + /// + static public string FindLockedProcessName (string path) + { + var list = FindLockProcesses(path); + if (list.Count == 0) + { + throw new Exception( + "No processes are locking the path specified"); + } + return list[0].ProcessName; + } + + /// + /// Method CheckIfFileIsLocked Check if the file specified has a + /// write lock held by a process + /// + /// The path of a file being checked if a write lock + /// held by a process + /// true when one or more processes with lock + static public bool CheckIfFileIsLocked (string path) + { + var list = FindLockProcesses(path); + if (list.Count > 0) + { return true; } + return false; + } + + /// + /// Used to find processes holding a lock on the file. This would cause + /// other usage, such as file truncation or write opretions to throw + /// IOException if an exclusive lock is attempted. + /// + /// Path being checked + /// List of processes holding file lock to path + /// + static public List FindLockProcesses (string path) + { + var key = Guid.NewGuid().ToString(); + var processes = new List(); + + var res = NativeMethods.RmStartSession(out var handle, 0, key); + if (res != 0) + { + throw new Exception("Could not begin restart session. " + + "Unable to determine file locker."); + } + + try + { + uint pnProcInfo = 0; + uint lpdwRebootReasons = NativeMethods.RmRebootReasonNone; + string[] resources = [path]; + + res = NativeMethods.RmRegisterResources(handle, (uint)resources.Length, + resources, 0, null, 0, null); + if (res != 0) + { + throw new Exception("Could not register resource."); + } + res = NativeMethods.RmGetList(handle, out var pnProcInfoNeeded, ref pnProcInfo, null, + ref lpdwRebootReasons); + const int ERROR_MORE_DATA = 234; + if (res == ERROR_MORE_DATA) + { + var processInfo = + new NativeMethods.RM_PROCESS_INFO[pnProcInfoNeeded]; + pnProcInfo = pnProcInfoNeeded; + // Get the list. + res = NativeMethods.RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons); + if (res == 0) + { + processes = new List((int)pnProcInfo); + for (var i = 0; i < pnProcInfo; i++) + { + try + { + processes.Add(Process.GetProcessById(processInfo[i]. + Process.dwProcessId)); + } + catch (ArgumentException) { } + } + } + else + { + throw new Exception("Could not list processes locking resource"); + } + } + else if (res != 0) + { + throw new Exception("Could not list processes locking resource." + + "Failed to get size of result."); + } + } + catch (Exception exception) + { + Trace.WriteLine(exception.Message); + } + finally + { + Trace.WriteLine($"RmEndSession: {NativeMethods.RmEndSession(handle)}"); + } + + return processes; + } +} diff --git a/src/LogExpert.UI/Extensions/NativeMethods.cs b/src/LogExpert.UI/Extensions/NativeMethods.cs new file mode 100644 index 00000000..41d97fa1 --- /dev/null +++ b/src/LogExpert.UI/Extensions/NativeMethods.cs @@ -0,0 +1,228 @@ +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace LogExpert.UI.Extensions; + +[SupportedOSPlatform("windows")] +internal static partial class NativeMethods +{ + #region Fields + + public const long SM_CYVSCROLL = 20; + public const long SM_CXHSCROLL = 21; + public const long SM_CXVSCROLL = 2; + public const long SM_CYHSCROLL = 3; + private const int DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19; + private const int DWMWA_USE_IMMERSIVE_DARK_MODE = 20; + public const int RmRebootReasonNone = 0; + private const int CCH_RM_MAX_APP_NAME = 255; + private const int CCH_RM_MAX_SVC_NAME = 63; + + #endregion + + #region Structs + + [StructLayout(LayoutKind.Sequential)] + public struct RM_UNIQUE_PROCESS + { + public int dwProcessId; + public System.Runtime.InteropServices. + ComTypes.FILETIME ProcessStartTime; + } + + [StructLayout(LayoutKind.Sequential, + CharSet = CharSet.Auto)] + public struct RM_PROCESS_INFO + { + public RM_UNIQUE_PROCESS Process; + [MarshalAs(UnmanagedType.ByValTStr, + SizeConst = CCH_RM_MAX_APP_NAME + 1)] + public string strAppName; + [MarshalAs(UnmanagedType.ByValTStr, + SizeConst = CCH_RM_MAX_SVC_NAME + 1)] + public string strServiceShortName; + public RM_APP_TYPE ApplicationType; + public uint AppStatus; + public uint TSSessionId; + [MarshalAs(UnmanagedType.Bool)] + public bool bRestartable; + } + #endregion Structs + + #region Enums + public enum RM_APP_TYPE + { + RmUnknownApp = 0, + RmMainWindow = 1, + RmOtherWindow = 2, + RmService = 3, + RmExplorer = 4, + RmConsole = 5, + RmCritical = 1000 + } + + #endregion Enums + + #region Library Imports + + #region user32.dll Imports + [LibraryImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool DestroyIcon (nint hIcon); + + [LibraryImport("User32.dll")] + public static partial int SetForegroundWindow (nint hWnd); + + [LibraryImport("user32.dll")] + public static partial long GetSystemMetricsForDpi (long index); + + [LibraryImport("user32.dll")] + public static partial long GetSystemMetrics (long index); + + [LibraryImport("user32.dll")] + public static partial short GetKeyState (int vKey); + + #endregion user32.dll Imports + + #region shell32.dll Imports + /* + UINT ExtractIconEx( + LPCTSTR lpszFile, + int nIconIndex, + HICON *phiconLarge, + HICON *phiconSmall, + UINT nIcons + ); + * */ + [LibraryImport("shell32.dll", StringMarshalling = StringMarshalling.Utf16)] + public static partial uint ExtractIconEx ( + string fileName, + int iconIndex, + out nint iconsLarge, + out nint iconsSmall, + uint numIcons + ); + + #endregion shell32.dll Imports + + #region dwmapi.dll Imports + + #region TitleBarDarkMode + [LibraryImport("dwmapi.dll")] + public static partial int DwmSetWindowAttribute (nint hwnd, int attr, ref int attrValue, int attrSize); + #endregion TitleBarDarkMode + + #endregion shell32.dll Imports + + #region rstrtmgr.dll Imports + + [DllImport("rstrtmgr.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern int RmGetList ( + uint dwSessionHandle, + out uint pnProcInfoNeeded, + ref uint pnProcInfo, + [In, Out] RM_PROCESS_INFO[] rgAffectedApps, + ref uint lpdwRebootReasons); + + [DllImport("rstrtmgr.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern int RmRegisterResources ( + uint pSessionHandle, + uint nFiles, + string[] rgsFilenames, + uint nApplications, + [In] RM_UNIQUE_PROCESS[] rgApplications, + uint nServices, + string[] rgsServiceNames); + + [LibraryImport("rstrtmgr.dll", StringMarshalling = StringMarshalling.Utf16)] + public static partial int RmStartSession ( + out uint pSessionHandle, + int dwSessionFlags, + string strSessionKey); + + [LibraryImport("rstrtmgr.dll", StringMarshalling = StringMarshalling.Utf16)] + public static partial int RmEndSession (uint pSessionHandle); + + #endregion rstrtmgr.dll Imports + + #endregion Library Imports + + #region Helper methods + + public static Icon LoadIconFromExe (string fileName, int index) + { + nint smallIcons = new(); + nint largeIcons = new(); + int num = (int)ExtractIconEx(fileName, index, out largeIcons, out smallIcons, 1); + if (num > 0 && smallIcons != nint.Zero) + { + var icon = (Icon)Icon.FromHandle(smallIcons).Clone(); + DestroyIcon(smallIcons); + return icon; + } + if (num > 0 && largeIcons != nint.Zero) + { + var icon = (Icon)Icon.FromHandle(largeIcons).Clone(); + DestroyIcon(largeIcons); + return icon; + } + return null; + } + + public static Icon[,] ExtractIcons (string fileName) + { + var iconCount = ExtractIconEx(fileName, -1, out var largeIcon, out var smallIcon, 0); + if (iconCount <= 0) + { + return null; + } + + var result = new Icon[2, iconCount]; + + for (var i = 0; i < iconCount; ++i) + { + var num = ExtractIconEx(fileName, i, out var largeIcons, out var smallIcons, 1); + if (smallIcons != nint.Zero) + { + result[0, i] = (Icon)Icon.FromHandle(smallIcons).Clone(); + DestroyIcon(smallIcons); + } + else + { + result[0, i] = null; + } + + if (num > 0 && largeIcons != nint.Zero) + { + result[1, i] = (Icon)Icon.FromHandle(largeIcons).Clone(); + DestroyIcon(largeIcons); + } + else + { + result[1, i] = null; + } + } + + return result; + } + + public static bool UseImmersiveDarkMode (nint handle, bool enabled) + { + var attribute = DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1; + if (IsWindows10OrGreater(18985)) + { + attribute = DWMWA_USE_IMMERSIVE_DARK_MODE; + } + + var useImmersiveDarkMode = enabled ? 1 : 0; + return DwmSetWindowAttribute(handle, attribute, ref useImmersiveDarkMode, sizeof(int)) == 0; + + } + + private static bool IsWindows10OrGreater (int build = -1) + { + return Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Build >= build; + } + + #endregion Helper methods +} \ No newline at end of file diff --git a/src/LogExpert.UI/Extensions/Win32.cs b/src/LogExpert.UI/Extensions/Win32.cs deleted file mode 100644 index a039e711..00000000 --- a/src/LogExpert.UI/Extensions/Win32.cs +++ /dev/null @@ -1,150 +0,0 @@ -using System.Drawing; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; - -namespace LogExpert.UI.Extensions; - -[SupportedOSPlatform("windows")] -internal static partial class Win32 //NativeMethods -{ - #region Fields - - public const long SM_CYVSCROLL = 20; - public const long SM_CXHSCROLL = 21; - public const long SM_CXVSCROLL = 2; - public const long SM_CYHSCROLL = 3; - private const int DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19; - private const int DWMWA_USE_IMMERSIVE_DARK_MODE = 20; - - #endregion - - #region Library Imports - [LibraryImport("user32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - public static partial bool DestroyIcon(nint hIcon); - - [LibraryImport("User32.dll")] - public static partial int SetForegroundWindow(nint hWnd); - - [LibraryImport("user32.dll")] - public static partial long GetSystemMetricsForDpi(long index); - - [LibraryImport("user32.dll")] - public static partial long GetSystemMetrics(long index); - - [LibraryImport("user32.dll")] - public static partial short GetKeyState(int vKey); - - /* - UINT ExtractIconEx( - LPCTSTR lpszFile, - int nIconIndex, - HICON *phiconLarge, - HICON *phiconSmall, - UINT nIcons - ); - * */ - [LibraryImport("shell32.dll", StringMarshalling = StringMarshalling.Utf16)] - public static partial uint ExtractIconEx( - string fileName, - int iconIndex, - ref nint iconsLarge, - ref nint iconsSmall, - uint numIcons - ); - - #region TitleBarDarkMode - [LibraryImport("dwmapi.dll")] - public static partial int DwmSetWindowAttribute(nint hwnd, int attr, ref int attrValue, int attrSize); - #endregion - #endregion - - #region Public methods - - public static Icon LoadIconFromExe(string fileName, int index) - { - //IntPtr[] smallIcons = new IntPtr[1]; - //IntPtr[] largeIcons = new IntPtr[1]; - nint smallIcons = new(); - nint largeIcons = new(); - var num = (int)ExtractIconEx(fileName, index, ref largeIcons, ref smallIcons, 1); - if (num > 0 && smallIcons != nint.Zero) - { - var icon = (Icon)Icon.FromHandle(smallIcons).Clone(); - DestroyIcon(smallIcons); - return icon; - } - if (num > 0 && largeIcons != nint.Zero) - { - var icon = (Icon)Icon.FromHandle(largeIcons).Clone(); - DestroyIcon(largeIcons); - return icon; - } - return null; - } - - public static Icon[,] ExtractIcons(string fileName) - { - var smallIcon = nint.Zero; - var largeIcon = nint.Zero; - var iconCount = (int)ExtractIconEx(fileName, -1, ref largeIcon, ref smallIcon, 0); - if (iconCount <= 0) - { - return null; - } - - nint smallIcons = new(); - nint largeIcons = new(); - var result = new Icon[2, iconCount]; - - for (var i = 0; i < iconCount; ++i) - { - var num = (int)ExtractIconEx(fileName, i, ref largeIcons, ref smallIcons, 1); - if (smallIcons != nint.Zero) - { - result[0, i] = (Icon)Icon.FromHandle(smallIcons).Clone(); - DestroyIcon(smallIcons); - } - else - { - result[0, i] = null; - } - if (num > 0 && largeIcons != nint.Zero) - { - result[1, i] = (Icon)Icon.FromHandle(largeIcons).Clone(); - DestroyIcon(largeIcons); - } - else - { - result[1, i] = null; - } - } - return result; - } - - #endregion - - #region Private Methods - - public static bool UseImmersiveDarkMode(nint handle, bool enabled) - { - - var attribute = DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1; - if (IsWindows10OrGreater(18985)) - { - attribute = DWMWA_USE_IMMERSIVE_DARK_MODE; - } - - var useImmersiveDarkMode = enabled ? 1 : 0; - return DwmSetWindowAttribute(handle, attribute, ref useImmersiveDarkMode, sizeof(int)) == 0; - - } - - private static bool IsWindows10OrGreater(int build = -1) - { - return Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Build >= build; - } - - #endregion TitleBarDarkMode - -} \ No newline at end of file