From a395936a6c6b8fa0f3d7c34ccbb0b876d88d035b Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 10 Dec 2024 11:56:32 +0800 Subject: [PATCH 01/21] Replace FormApplication & DllImport with CSWin32 --- .../Flow.Launcher.Plugin.Sys.csproj | 7 +++ Plugins/Flow.Launcher.Plugin.Sys/Main.cs | 44 ++++--------------- .../NativeMethods.txt | 6 +++ 3 files changed, 21 insertions(+), 36 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Sys/NativeMethods.txt diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj index b797b3cf43a..dbc36ad424b 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj +++ b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj @@ -57,4 +57,11 @@ PreserveNewest + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs index 6e55582280f..f80f3c9ddf4 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs @@ -2,17 +2,16 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Runtime.InteropServices; using System.Windows; -using System.Windows.Forms; -using System.Windows.Interop; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin.SharedCommands; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.System.Shutdown; using Application = System.Windows.Application; using Control = System.Windows.Controls.Control; -using FormsApplication = System.Windows.Forms.Application; namespace Flow.Launcher.Plugin.Sys { @@ -21,33 +20,6 @@ public class Main : IPlugin, ISettingProvider, IPluginI18n private PluginInitContext context; private Dictionary KeywordTitleMappings = new Dictionary(); - #region DllImport - - internal const int EWX_LOGOFF = 0x00000000; - internal const int EWX_SHUTDOWN = 0x00000001; - internal const int EWX_REBOOT = 0x00000002; - internal const int EWX_FORCE = 0x00000004; - internal const int EWX_POWEROFF = 0x00000008; - internal const int EWX_FORCEIFHUNG = 0x00000010; - - [DllImport("user32")] - private static extern bool ExitWindowsEx(uint uFlags, uint dwReason); - - [DllImport("user32")] - private static extern void LockWorkStation(); - - [DllImport("Shell32.dll", CharSet = CharSet.Unicode)] - private static extern uint SHEmptyRecycleBin(IntPtr hWnd, uint dwFlags); - - // http://www.pinvoke.net/default.aspx/Enums/HRESULT.html - private enum HRESULT : uint - { - S_FALSE = 0x0001, - S_OK = 0x0000 - } - - #endregion - public Control CreateSettingPanel() { var results = Commands(); @@ -206,7 +178,7 @@ private List Commands() MessageBoxButton.YesNo, MessageBoxImage.Warning); if (result == MessageBoxResult.Yes) - ExitWindowsEx(EWX_LOGOFF, 0); + PInvoke.ExitWindowsEx(EXIT_WINDOWS_FLAGS.EWX_LOGOFF, 0); return true; } @@ -219,7 +191,7 @@ private List Commands() IcoPath = "Images\\lock.png", Action = c => { - LockWorkStation(); + PInvoke.LockWorkStation(); return true; } }, @@ -229,7 +201,7 @@ private List Commands() SubTitle = context.API.GetTranslation("flowlauncher_plugin_sys_sleep"), Glyph = new GlyphInfo (FontFamily:"/Resources/#Segoe Fluent Icons", Glyph:"\xec46"), IcoPath = "Images\\sleep.png", - Action = c => FormsApplication.SetSuspendState(PowerState.Suspend, false, false) + Action = c => PInvoke.SetSuspendState(false, false, false) }, new Result { @@ -274,8 +246,8 @@ private List Commands() // http://www.pinvoke.net/default.aspx/shell32/SHEmptyRecycleBin.html // FYI, couldn't find documentation for this but if the recycle bin is already empty, it will return -2147418113 (0x8000FFFF (E_UNEXPECTED)) // 0 for nothing - var result = SHEmptyRecycleBin(new WindowInteropHelper(Application.Current.MainWindow).Handle, 0); - if (result != (uint) HRESULT.S_OK && result != (uint) 0x8000FFFF) + var result = PInvoke.SHEmptyRecycleBin(new(), string.Empty, 0); + if (result != HRESULT.S_OK && result != HRESULT.E_UNEXPECTED) { context.API.ShowMsgBox($"Error emptying recycle bin, error code: {result}\n" + "please refer to https://msdn.microsoft.com/en-us/library/windows/desktop/aa378137", diff --git a/Plugins/Flow.Launcher.Plugin.Sys/NativeMethods.txt b/Plugins/Flow.Launcher.Plugin.Sys/NativeMethods.txt new file mode 100644 index 00000000000..8fcb6cae91e --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Sys/NativeMethods.txt @@ -0,0 +1,6 @@ +ExitWindowsEx +LockWorkStation +SHEmptyRecycleBin +S_OK +E_UNEXPECTED +SetSuspendState \ No newline at end of file From ca01b25a39b6c35d58b021fd153d796def9d7e05 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 10 Dec 2024 13:29:21 +0800 Subject: [PATCH 02/21] Replace DllImport & flags with CSWin32 --- .../Flow.Launcher.Plugin.ProcessKiller.csproj | 7 +++ .../NativeMethods.txt | 2 + .../ProcessHelper.cs | 52 ++++++++----------- 3 files changed, 31 insertions(+), 30 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.ProcessKiller/NativeMethods.txt diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj index 876bac1e754..4e216b7b26a 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj @@ -50,6 +50,13 @@ + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/NativeMethods.txt b/Plugins/Flow.Launcher.Plugin.ProcessKiller/NativeMethods.txt new file mode 100644 index 00000000000..7fa794755e1 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/NativeMethods.txt @@ -0,0 +1,2 @@ +QueryFullProcessImageName +OpenProcess \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs index 0acc39fbb1c..d8e5f4fb7a9 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs @@ -1,11 +1,13 @@ -using Flow.Launcher.Infrastructure; +using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Logger; +using Microsoft.Win32.SafeHandles; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Runtime.InteropServices; -using System.Text; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.System.Threading; namespace Flow.Launcher.Plugin.ProcessKiller { @@ -84,43 +86,33 @@ public void TryKill(Process p) } } - public string TryGetProcessFilename(Process p) + public unsafe string TryGetProcessFilename(Process p) { try { - int capacity = 2000; - StringBuilder builder = new StringBuilder(capacity); - IntPtr ptr = OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, p.Id); - if (!QueryFullProcessImageName(ptr, 0, builder, ref capacity)) + var handle = PInvoke.OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION, false, (uint)p.Id); + if (handle.Value == IntPtr.Zero) { - return String.Empty; + return string.Empty; } - return builder.ToString(); + using var safeHandle = new SafeProcessHandle(handle.Value, true); + uint capacity = 2000; + char[] buffer = new char[capacity]; + fixed (char* pBuffer = buffer) + { + if (!PInvoke.QueryFullProcessImageName(safeHandle, PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, (PWSTR)pBuffer, ref capacity)) + { + return string.Empty; + } + } + + return new string(buffer, 0, (int)capacity); } catch { - return ""; + return string.Empty; } } - - [Flags] - private enum ProcessAccessFlags : uint - { - QueryLimitedInformation = 0x00001000 - } - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool QueryFullProcessImageName( - [In] IntPtr hProcess, - [In] int dwFlags, - [Out] StringBuilder lpExeName, - ref int lpdwSize); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern IntPtr OpenProcess( - ProcessAccessFlags processAccess, - bool bInheritHandle, - int processId); } } From 313f86b6485e6f32f1d23e58fcd09692ae322e8c Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 10 Dec 2024 15:09:06 +0800 Subject: [PATCH 03/21] Replace DllImport & flags with CSWin32 --- .../Flow.Launcher.Plugin.Program.csproj | 8 ++ .../NativeMethods.txt | 10 ++ .../Programs/ShellLinkHelper.cs | 130 +++++------------- .../Programs/ShellLocalization.cs | 67 +++++---- 4 files changed, 82 insertions(+), 133 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Program/NativeMethods.txt diff --git a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj index c0ad63cfeb4..99c1a12e9b3 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj +++ b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj @@ -52,6 +52,10 @@ PreserveNewest + + + + @@ -61,6 +65,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Program/NativeMethods.txt b/Plugins/Flow.Launcher.Plugin.Program/NativeMethods.txt new file mode 100644 index 00000000000..ecd547dffcd --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Program/NativeMethods.txt @@ -0,0 +1,10 @@ +SHGetLocalizedName +LoadString +LoadLibraryEx +FreeLibrary +ExpandEnvironmentStrings +S_OK +SLGP_FLAGS +WIN32_FIND_DATAW +SLR_FLAGS +IShellLinkW \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs index 78c66d60485..fae9c84c985 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs @@ -1,97 +1,17 @@ using System; -using System.Text; using System.Runtime.InteropServices; -using Accessibility; using System.Runtime.InteropServices.ComTypes; using Flow.Launcher.Plugin.Program.Logger; +using Windows.Win32.Foundation; +using Windows.Win32.UI.Shell; +using Windows.Win32.Storage.FileSystem; namespace Flow.Launcher.Plugin.Program.Programs { class ShellLinkHelper { - [Flags()] - public enum SLGP_FLAGS - { - SLGP_SHORTPATH = 0x1, - SLGP_UNCPRIORITY = 0x2, - SLGP_RAWPATH = 0x4 - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - public struct WIN32_FIND_DATAW - { - public uint dwFileAttributes; - public long ftCreationTime; - public long ftLastAccessTime; - public long ftLastWriteTime; - public uint nFileSizeHigh; - public uint nFileSizeLow; - public uint dwReserved0; - public uint dwReserved1; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] - public string cFileName; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] - public string cAlternateFileName; - } - - [Flags()] - public enum SLR_FLAGS - { - SLR_NO_UI = 0x1, - SLR_ANY_MATCH = 0x2, - SLR_UPDATE = 0x4, - SLR_NOUPDATE = 0x8, - SLR_NOSEARCH = 0x10, - SLR_NOTRACK = 0x20, - SLR_NOLINKINFO = 0x40, - SLR_INVOKE_MSI = 0x80 - } - - + // Reference : http://www.pinvoke.net/default.aspx/Interfaces.IShellLinkW - /// The IShellLink interface allows Shell links to be created, modified, and resolved - [ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("000214F9-0000-0000-C000-000000000046")] - interface IShellLinkW - { - /// Retrieves the path and file name of a Shell link object - void GetPath([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, ref WIN32_FIND_DATAW pfd, SLGP_FLAGS fFlags); - /// Retrieves the list of item identifiers for a Shell link object - void GetIDList(out IntPtr ppidl); - /// Sets the pointer to an item identifier list (PIDL) for a Shell link object. - void SetIDList(IntPtr pidl); - /// Retrieves the description string for a Shell link object - void GetDescription([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName); - /// Sets the description for a Shell link object. The description can be any application-defined string - void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName); - /// Retrieves the name of the working directory for a Shell link object - void GetWorkingDirectory([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath); - /// Sets the name of the working directory for a Shell link object - void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir); - /// Retrieves the command-line arguments associated with a Shell link object - void GetArguments([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath); - /// Sets the command-line arguments for a Shell link object - void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs); - /// Retrieves the hot key for a Shell link object - void GetHotkey(out short pwHotkey); - /// Sets a hot key for a Shell link object - void SetHotkey(short wHotkey); - /// Retrieves the show command for a Shell link object - void GetShowCmd(out int piShowCmd); - /// Sets the show command for a Shell link object. The show command sets the initial show state of the window. - void SetShowCmd(int iShowCmd); - /// Retrieves the location (path and index) of the icon for a Shell link object - void GetIconLocation([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, - int cchIconPath, out int piIcon); - /// Sets the location (path and index) of the icon for a Shell link object - void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon); - /// Sets the relative path to the Shell link object - void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved); - /// Attempts to find the target of a Shell link, even if it has been moved or renamed - void Resolve(ref Accessibility._RemotableHandle hwnd, SLR_FLAGS fFlags); - /// Sets the path and file name of a Shell link object - void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile); - } - [ComImport(), Guid("00021401-0000-0000-C000-000000000046")] public class ShellLink { @@ -102,29 +22,40 @@ public class ShellLink public string arguments = string.Empty; // Retrieve the target path using Shell Link - public string retrieveTargetPath(string path) + public unsafe string retrieveTargetPath(string path) { var link = new ShellLink(); const int STGM_READ = 0; ((IPersistFile)link).Load(path, STGM_READ); - var hwnd = new _RemotableHandle(); - ((IShellLinkW)link).Resolve(ref hwnd, 0); + var hwnd = new HWND(IntPtr.Zero); + ((IShellLinkW)link).Resolve(hwnd, 0); const int MAX_PATH = 260; - StringBuilder buffer = new StringBuilder(MAX_PATH); + char[] buffer = new char[MAX_PATH]; var data = new WIN32_FIND_DATAW(); - ((IShellLinkW)link).GetPath(buffer, buffer.Capacity, ref data, SLGP_FLAGS.SLGP_SHORTPATH); - var target = buffer.ToString(); + var target = string.Empty; + fixed (char* bufferChar = buffer) + { + ((IShellLinkW)link).GetPath((PWSTR)bufferChar, MAX_PATH, &data, (uint)SLGP_FLAGS.SLGP_SHORTPATH); + int validLength = Array.IndexOf(buffer, '\0'); + if (validLength < 0) validLength = MAX_PATH; + target = new string(buffer, 0, validLength); + } // To set the app description - if (!String.IsNullOrEmpty(target)) + if (!string.IsNullOrEmpty(target)) { try { - buffer = new StringBuilder(MAX_PATH); - ((IShellLinkW)link).GetDescription(buffer, MAX_PATH); - description = buffer.ToString(); + char[] buffer1 = new char[MAX_PATH]; + fixed (char* buffer1Char = buffer1) + { + ((IShellLinkW)link).GetDescription((PWSTR)buffer1Char, MAX_PATH); + int validLength = Array.IndexOf(buffer1, '\0'); + if (validLength < 0) validLength = MAX_PATH; + description = new string(buffer1, 0, validLength); + } } catch (COMException e) { @@ -134,9 +65,14 @@ public string retrieveTargetPath(string path) e); } - buffer.Clear(); - ((IShellLinkW)link).GetArguments(buffer, MAX_PATH); - arguments = buffer.ToString(); + char[] buffer2 = new char[MAX_PATH]; + fixed (char* buffer2Char = buffer2) + { + ((IShellLinkW)link).GetArguments((PWSTR)buffer2Char, MAX_PATH); + int validLength = Array.IndexOf(buffer2, '\0'); + if (validLength < 0) validLength = MAX_PATH; + arguments = new string(buffer2, 0, validLength); + } } // To release unmanaged memory diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLocalization.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLocalization.cs index 4f344d89ecc..e36618b0de5 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLocalization.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLocalization.cs @@ -1,8 +1,8 @@ using System; using System.IO; -using System.Runtime.InteropServices; -using System.Text; - +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.System.LibraryLoader; namespace Flow.Launcher.Plugin.Program.Programs { @@ -13,51 +13,46 @@ namespace Flow.Launcher.Plugin.Program.Programs /// public static class ShellLocalization { - internal const uint DONTRESOLVEDLLREFERENCES = 0x00000001; - internal const uint LOADLIBRARYASDATAFILE = 0x00000002; - - [DllImport("shell32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode)] - internal static extern int SHGetLocalizedName(string pszPath, StringBuilder pszResModule, ref int cch, out int pidsRes); - - [DllImport("user32.dll", EntryPoint = "LoadStringW", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode)] - internal static extern int LoadString(IntPtr hModule, int resourceID, StringBuilder resourceValue, int len); - - [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, EntryPoint = "LoadLibraryExW")] - internal static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hFile, uint dwFlags); - - [DllImport("kernel32.dll", ExactSpelling = true)] - internal static extern int FreeLibrary(IntPtr hModule); - - [DllImport("kernel32.dll", EntryPoint = "ExpandEnvironmentStringsW", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern uint ExpandEnvironmentStrings(string lpSrc, StringBuilder lpDst, int nSize); - /// /// Returns the localized name of a shell item. /// /// Path to the shell item (e. g. shortcut 'File Explorer.lnk'). /// The localized name as string or . - public static string GetLocalizedName(string path) + public static unsafe string GetLocalizedName(string path) { - StringBuilder resourcePath = new StringBuilder(1024); - StringBuilder localizedName = new StringBuilder(1024); - int len, id; - len = resourcePath.Capacity; + int capacity = 1024; + char[] resourcePathBuffer = new char[capacity]; // If there is no resource to localize a file name the method returns a non zero value. - if (SHGetLocalizedName(path, resourcePath, ref len, out id) == 0) + fixed (char* resourcePath = resourcePathBuffer) { - _ = ExpandEnvironmentStrings(resourcePath.ToString(), resourcePath, resourcePath.Capacity); - IntPtr hMod = LoadLibraryEx(resourcePath.ToString(), IntPtr.Zero, DONTRESOLVEDLLREFERENCES | LOADLIBRARYASDATAFILE); - if (hMod != IntPtr.Zero) + var result = PInvoke.SHGetLocalizedName(path, (PWSTR)resourcePath, (uint)capacity, out var id); + if (result == HRESULT.S_OK) { - if (LoadString(hMod, id, localizedName, localizedName.Capacity) != 0) + int validLength = Array.IndexOf(resourcePathBuffer, '\0'); + if (validLength < 0) validLength = capacity; + var resourcePathStr = new string(resourcePathBuffer, 0, validLength); + _ = PInvoke.ExpandEnvironmentStrings(resourcePathStr, resourcePath, (uint)capacity); + var handle = PInvoke.LoadLibraryEx(resourcePathStr, + LOAD_LIBRARY_FLAGS.DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_FLAGS.LOAD_LIBRARY_AS_DATAFILE); + IntPtr safeHandle = handle.DangerousGetHandle(); + if (safeHandle != IntPtr.Zero) { - string lString = localizedName.ToString(); - _ = FreeLibrary(hMod); - return lString; - } + char[] localizedNameBuffer = new char[capacity]; + fixed (char* localizedName = localizedNameBuffer) + { + if (PInvoke.LoadString(handle, (uint)id, (PWSTR)localizedName, capacity) != 0) + { + validLength = Array.IndexOf(localizedNameBuffer, '\0'); + if (validLength < 0) validLength = capacity; + var lString = new string(localizedNameBuffer, 0, validLength); + PInvoke.FreeLibrary(new(safeHandle)); + return lString; + } + } - _ = FreeLibrary(hMod); + PInvoke.FreeLibrary(new(safeHandle)); + } } } From ccfc39e54a35fb552cc563f1fa8660e99ffe1037 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 10 Dec 2024 15:12:35 +0800 Subject: [PATCH 04/21] Fix string length issue --- Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs index d8e5f4fb7a9..6df12fae98b 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs @@ -105,9 +105,11 @@ public unsafe string TryGetProcessFilename(Process p) { return string.Empty; } - } - return new string(buffer, 0, (int)capacity); + int validLength = Array.IndexOf(buffer, '\0'); + if (validLength < 0) validLength = (int)capacity; + return new string(buffer, 0, validLength); + } } catch { From 79f8f053d9288282c56579f41f284e96c19f2de5 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 10 Dec 2024 15:24:19 +0800 Subject: [PATCH 05/21] Replace DllImport with CSWin32 --- .../Flow.Launcher.Plugin.csproj | 10 +++++- Flow.Launcher.Plugin/NativeMethods.txt | 3 ++ .../SharedCommands/ShellCommand.cs | 31 ++++++++++++------- 3 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 Flow.Launcher.Plugin/NativeMethods.txt diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj index 35b9af1c9db..2feb21b12aa 100644 --- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj +++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj @@ -57,7 +57,11 @@ - + + + + + @@ -68,6 +72,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Flow.Launcher.Plugin/NativeMethods.txt b/Flow.Launcher.Plugin/NativeMethods.txt new file mode 100644 index 00000000000..e3e2b705eb0 --- /dev/null +++ b/Flow.Launcher.Plugin/NativeMethods.txt @@ -0,0 +1,3 @@ +EnumThreadWindows +GetWindowText +GetWindowTextLength \ No newline at end of file diff --git a/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs b/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs index 49f78b458d7..191a7630958 100644 --- a/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs +++ b/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs @@ -2,18 +2,15 @@ using System.ComponentModel; using System.Diagnostics; using System.IO; -using System.Runtime.InteropServices; -using System.Text; using System.Threading; +using Windows.Win32; +using Windows.Win32.Foundation; namespace Flow.Launcher.Plugin.SharedCommands { public static class ShellCommand { public delegate bool EnumThreadDelegate(IntPtr hwnd, IntPtr lParam); - [DllImport("user32.dll")] static extern bool EnumThreadWindows(uint threadId, EnumThreadDelegate lpfn, IntPtr lParam); - [DllImport("user32.dll")] static extern int GetWindowText(IntPtr hwnd, StringBuilder lpString, int nMaxCount); - [DllImport("user32.dll")] static extern int GetWindowTextLength(IntPtr hwnd); private static bool containsSecurityWindow; @@ -42,21 +39,33 @@ private static void CheckSecurityWindow() { ProcessThreadCollection ptc = Process.GetCurrentProcess().Threads; for (int i = 0; i < ptc.Count; i++) - EnumThreadWindows((uint)ptc[i].Id, CheckSecurityThread, IntPtr.Zero); + PInvoke.EnumThreadWindows((uint)ptc[i].Id, CheckSecurityThread, IntPtr.Zero); } - private static bool CheckSecurityThread(IntPtr hwnd, IntPtr lParam) + private static BOOL CheckSecurityThread(HWND hwnd, LPARAM lParam) { if (GetWindowTitle(hwnd) == "Windows Security") containsSecurityWindow = true; return true; } - private static string GetWindowTitle(IntPtr hwnd) + private static unsafe string GetWindowTitle(HWND hwnd) { - StringBuilder sb = new StringBuilder(GetWindowTextLength(hwnd) + 1); - GetWindowText(hwnd, sb, sb.Capacity); - return sb.ToString(); + var capacity = PInvoke.GetWindowTextLength(hwnd) + 1; + char[] buffer = new char[capacity]; + fixed (char* pBuffer = buffer) + { + // If the window has no title bar or text, if the title bar is empty, + // or if the window or control handle is invalid, the return value is zero. + if (PInvoke.GetWindowText(hwnd, (PWSTR)pBuffer, capacity) == 0) + { + return string.Empty; + } + + int validLength = Array.IndexOf(buffer, '\0'); + if (validLength < 0) validLength = capacity; + return new string(buffer, 0, validLength); + } } public static ProcessStartInfo SetProcessStartInfo(this string fileName, string workingDirectory = "", string arguments = "", string verb = "", bool createNoWindow = false) From 22170ee43a9fc9b01838cef65f0f45594473d952 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 10 Dec 2024 20:44:28 +0800 Subject: [PATCH 06/21] Replace DllImport & flags with CSWin32 --- .../FileExplorerHelper.cs | 10 +- .../Flow.Launcher.Infrastructure.csproj | 8 + .../Hotkey/GlobalHotkey.cs | 58 +++---- .../Hotkey/InterceptKeys.cs | 38 ----- .../Hotkey/KeyEvent.cs | 12 +- .../Image/ThumbnailReader.cs | 150 ++++++------------ .../NativeMethods.txt | 20 +++ 7 files changed, 114 insertions(+), 182 deletions(-) delete mode 100644 Flow.Launcher.Infrastructure/Hotkey/InterceptKeys.cs create mode 100644 Flow.Launcher.Infrastructure/NativeMethods.txt diff --git a/Flow.Launcher.Infrastructure/FileExplorerHelper.cs b/Flow.Launcher.Infrastructure/FileExplorerHelper.cs index 76695a4e31e..b738b9c88f6 100644 --- a/Flow.Launcher.Infrastructure/FileExplorerHelper.cs +++ b/Flow.Launcher.Infrastructure/FileExplorerHelper.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.InteropServices; +using Windows.Win32; namespace Flow.Launcher.Infrastructure { @@ -54,10 +54,6 @@ private static dynamic GetActiveExplorer() return explorerWindows.Zip(zOrders).MinBy(x => x.Second).First; } - [DllImport("user32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); - private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); /// @@ -70,9 +66,9 @@ private static IEnumerable GetZOrder(List hWnds) var index = 0; var numRemaining = hWnds.Count; - EnumWindows((wnd, _) => + PInvoke.EnumWindows((wnd, _) => { - var searchIndex = hWnds.FindIndex(x => x.HWND == wnd.ToInt32()); + var searchIndex = hWnds.FindIndex(x => x.HWND == wnd.Value); if (searchIndex != -1) { z[searchIndex] = index; diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj index ec35ef9d679..1475252caee 100644 --- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj +++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj @@ -35,6 +35,10 @@ false + + + + @@ -56,6 +60,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs b/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs index f847ab18906..75af843b973 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs @@ -1,6 +1,11 @@ -using System; +using System; +using System.Diagnostics; using System.Runtime.InteropServices; using Flow.Launcher.Plugin; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.UI.Input.KeyboardAndMouse; +using Windows.Win32.UI.WindowsAndMessaging; namespace Flow.Launcher.Infrastructure.Hotkey { @@ -10,44 +15,41 @@ namespace Flow.Launcher.Infrastructure.Hotkey /// public unsafe class GlobalHotkey : IDisposable { - private static readonly IntPtr hookId; - - - - public delegate bool KeyboardCallback(KeyEvent keyEvent, int vkCode, SpecialKeyState state); - internal static Func hookedKeyboardCallback; + private static readonly UnhookWindowsHookExSafeHandle hookId; - //Modifier key constants - private const int VK_SHIFT = 0x10; - private const int VK_CONTROL = 0x11; - private const int VK_ALT = 0x12; - private const int VK_WIN = 91; + public delegate bool KeyboardCallback(int keyEvent, int vkCode, SpecialKeyState state); + internal static Func hookedKeyboardCallback; static GlobalHotkey() { // Set the hook - hookId = InterceptKeys.SetHook(& LowLevelKeyboardProc); + using Process curProcess = Process.GetCurrentProcess(); + using ProcessModule curModule = curProcess.MainModule; + hookId = PInvoke.SetWindowsHookEx( + WINDOWS_HOOK_ID.WH_KEYBOARD_LL, + LowLevelKeyboardProc, + PInvoke.GetModuleHandle(curModule.ModuleName), 0); } public static SpecialKeyState CheckModifiers() { SpecialKeyState state = new SpecialKeyState(); - if ((InterceptKeys.GetKeyState(VK_SHIFT) & 0x8000) != 0) + if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_SHIFT) & 0x8000) != 0) { //SHIFT is pressed state.ShiftPressed = true; } - if ((InterceptKeys.GetKeyState(VK_CONTROL) & 0x8000) != 0) + if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_CONTROL) & 0x8000) != 0) { //CONTROL is pressed state.CtrlPressed = true; } - if ((InterceptKeys.GetKeyState(VK_ALT) & 0x8000) != 0) + if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_MENU) & 0x8000) != 0) { //ALT is pressed state.AltPressed = true; } - if ((InterceptKeys.GetKeyState(VK_WIN) & 0x8000) != 0) + if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_LWIN) & 0x8000) != 0) { //WIN is pressed state.WinPressed = true; @@ -56,33 +58,33 @@ public static SpecialKeyState CheckModifiers() return state; } - [UnmanagedCallersOnly] - private static IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam) + private static LRESULT LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { bool continues = true; if (nCode >= 0) { - if (wParam.ToUInt32() == (int)KeyEvent.WM_KEYDOWN || - wParam.ToUInt32() == (int)KeyEvent.WM_KEYUP || - wParam.ToUInt32() == (int)KeyEvent.WM_SYSKEYDOWN || - wParam.ToUInt32() == (int)KeyEvent.WM_SYSKEYUP) + var wParamValue = (int)wParam.Value; + if (wParamValue == (int)KeyEvent.WM_KEYDOWN || + wParamValue == (int)KeyEvent.WM_KEYUP || + wParamValue == (int)KeyEvent.WM_SYSKEYDOWN || + wParamValue == (int)KeyEvent.WM_SYSKEYUP) { if (hookedKeyboardCallback != null) - continues = hookedKeyboardCallback((KeyEvent)wParam.ToUInt32(), Marshal.ReadInt32(lParam), CheckModifiers()); + continues = hookedKeyboardCallback((KeyEvent)wParamValue, Marshal.ReadInt32(lParam), CheckModifiers()); } } if (continues) { - return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam); + return PInvoke.CallNextHookEx(hookId, nCode, wParam, lParam); } - return (IntPtr)(-1); + return new LRESULT(-1); } public void Dispose() { - InterceptKeys.UnhookWindowsHookEx(hookId); + PInvoke.UnhookWindowsHookEx(new HHOOK(hookId.DangerousGetHandle())); } ~GlobalHotkey() @@ -90,4 +92,4 @@ public void Dispose() Dispose(); } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Infrastructure/Hotkey/InterceptKeys.cs b/Flow.Launcher.Infrastructure/Hotkey/InterceptKeys.cs deleted file mode 100644 index d33bac34cea..00000000000 --- a/Flow.Launcher.Infrastructure/Hotkey/InterceptKeys.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; - -namespace Flow.Launcher.Infrastructure.Hotkey -{ - internal static unsafe class InterceptKeys - { - public delegate IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam); - - private const int WH_KEYBOARD_LL = 13; - - public static IntPtr SetHook(delegate* unmanaged proc) - { - using (Process curProcess = Process.GetCurrentProcess()) - using (ProcessModule curModule = curProcess.MainModule) - { - return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0); - } - } - - [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] - public static extern IntPtr SetWindowsHookEx(int idHook, delegate* unmanaged lpfn, IntPtr hMod, uint dwThreadId); - - [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool UnhookWindowsHookEx(IntPtr hhk); - - [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] - public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, UIntPtr wParam, IntPtr lParam); - - [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] - public static extern IntPtr GetModuleHandle(string lpModuleName); - - [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)] - public static extern short GetKeyState(int keyCode); - } -} \ No newline at end of file diff --git a/Flow.Launcher.Infrastructure/Hotkey/KeyEvent.cs b/Flow.Launcher.Infrastructure/Hotkey/KeyEvent.cs index 15e3068830f..95bb258377b 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/KeyEvent.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/KeyEvent.cs @@ -1,3 +1,5 @@ +using Windows.Win32; + namespace Flow.Launcher.Infrastructure.Hotkey { public enum KeyEvent @@ -5,21 +7,21 @@ public enum KeyEvent /// /// Key down /// - WM_KEYDOWN = 256, + WM_KEYDOWN = (int)PInvoke.WM_KEYDOWN, /// /// Key up /// - WM_KEYUP = 257, + WM_KEYUP = (int)PInvoke.WM_KEYUP, /// /// System key up /// - WM_SYSKEYUP = 261, + WM_SYSKEYUP = (int)PInvoke.WM_SYSKEYUP, /// /// System key down /// - WM_SYSKEYDOWN = 260 + WM_SYSKEYDOWN = (int)PInvoke.WM_SYSKEYDOWN } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs b/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs index 247238bb68f..49d6da8c249 100644 --- a/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs +++ b/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs @@ -1,12 +1,19 @@ using System; using System.Runtime.InteropServices; using System.IO; +using System.Windows; using System.Windows.Interop; using System.Windows.Media.Imaging; -using System.Windows; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.UI.Shell; +using Windows.Win32.Graphics.Gdi; namespace Flow.Launcher.Infrastructure.Image { + /// + /// Subclass of + /// [Flags] public enum ThumbnailOptions { @@ -22,91 +29,13 @@ public class WindowsThumbnailProvider { // Based on https://stackoverflow.com/questions/21751747/extract-thumbnail-for-any-file-in-windows - private const string IShellItem2Guid = "7E9FB0D3-919F-4307-AB2E-9B1860310C93"; - - [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - internal static extern int SHCreateItemFromParsingName( - [MarshalAs(UnmanagedType.LPWStr)] string path, - IntPtr pbc, - ref Guid riid, - [MarshalAs(UnmanagedType.Interface)] out IShellItem shellItem); - - [DllImport("gdi32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool DeleteObject(IntPtr hObject); - - [ComImport] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - [Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")] - internal 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); - }; - - internal enum SIGDN : uint - { - NORMALDISPLAY = 0, - PARENTRELATIVEPARSING = 0x80018001, - PARENTRELATIVEFORADDRESSBAR = 0x8001c001, - DESKTOPABSOLUTEPARSING = 0x80028000, - PARENTRELATIVEEDITING = 0x80031001, - DESKTOPABSOLUTEEDITING = 0x8004c000, - FILESYSPATH = 0x80058000, - URL = 0x80068000 - } - - internal enum HResult - { - Ok = 0x0000, - False = 0x0001, - InvalidArguments = unchecked((int)0x80070057), - OutOfMemory = unchecked((int)0x8007000E), - NoInterface = unchecked((int)0x80004002), - Fail = unchecked((int)0x80004005), - ExtractionFailed = unchecked((int)0x8004B200), - ElementNotFound = unchecked((int)0x80070490), - TypeElementNotFound = unchecked((int)0x8002802B), - NoObject = unchecked((int)0x800401E5), - Win32ErrorCanceled = 1223, - Canceled = unchecked((int)0x800704C7), - ResourceInUse = unchecked((int)0x800700AA), - AccessDenied = unchecked((int)0x80030005) - } - - [ComImportAttribute()] - [GuidAttribute("bcc18b79-ba16-442f-80c4-8a59c30c463b")] - [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IShellItemImageFactory - { - [PreserveSig] - HResult GetImage( - [In, MarshalAs(UnmanagedType.Struct)] NativeSize size, - [In] ThumbnailOptions flags, - [Out] out IntPtr phbm); - } - - [StructLayout(LayoutKind.Sequential)] - internal struct NativeSize - { - private int width; - private int height; - - public int Width { set { width = value; } } - public int Height { set { height = value; } } - }; + private static readonly Guid GUID_IShellItem = typeof(IShellItem).GUID; + private static readonly HRESULT S_ExtractionFailed = (HRESULT)0x8004B200; public static BitmapSource GetThumbnail(string fileName, int width, int height, ThumbnailOptions options) { - IntPtr hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options); + HBITMAP hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options); try { @@ -115,39 +44,52 @@ public static BitmapSource GetThumbnail(string fileName, int width, int height, finally { // delete HBitmap to avoid memory leaks - DeleteObject(hBitmap); + PInvoke.DeleteObject(hBitmap); } } - - private static IntPtr GetHBitmap(string fileName, int width, int height, ThumbnailOptions options) + + private static unsafe HBITMAP GetHBitmap(string fileName, int width, int height, ThumbnailOptions options) { - IShellItem nativeShellItem; - Guid shellItem2Guid = new Guid(IShellItem2Guid); - int retCode = SHCreateItemFromParsingName(fileName, IntPtr.Zero, ref shellItem2Guid, out nativeShellItem); + var retCode = PInvoke.SHCreateItemFromParsingName( + fileName, + null, + GUID_IShellItem, + out var nativeShellItem); - if (retCode != 0) + if (retCode != HRESULT.S_OK) throw Marshal.GetExceptionForHR(retCode); - NativeSize nativeSize = new NativeSize + if (nativeShellItem is not IShellItemImageFactory imageFactory) { - Width = width, - Height = height - }; + Marshal.ReleaseComObject(nativeShellItem); + throw new InvalidOperationException("Failed to get IShellItemImageFactory"); + } - IntPtr hBitmap; - HResult hr = ((IShellItemImageFactory)nativeShellItem).GetImage(nativeSize, options, out hBitmap); + SIZE size = new SIZE + { + cx = width, + cy = height + }; - // if extracting image thumbnail and failed, extract shell icon - if (options == ThumbnailOptions.ThumbnailOnly && hr == HResult.ExtractionFailed) + HBITMAP hBitmap = default; + try { - hr = ((IShellItemImageFactory) nativeShellItem).GetImage(nativeSize, ThumbnailOptions.IconOnly, out hBitmap); + try + { + imageFactory.GetImage(size, (SIIGBF)options, &hBitmap); + } + catch (COMException ex) when (ex.HResult == S_ExtractionFailed && options == ThumbnailOptions.ThumbnailOnly) + { + // Fallback to IconOnly if ThumbnailOnly fails + imageFactory.GetImage(size, (SIIGBF)ThumbnailOptions.IconOnly, &hBitmap); + } + } + finally + { + Marshal.ReleaseComObject(nativeShellItem); } - Marshal.ReleaseComObject(nativeShellItem); - - if (hr == HResult.Ok) return hBitmap; - - throw new COMException($"Error while extracting thumbnail for {fileName}", Marshal.GetExceptionForHR((int)hr)); + return hBitmap; } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt new file mode 100644 index 00000000000..a083f79aad8 --- /dev/null +++ b/Flow.Launcher.Infrastructure/NativeMethods.txt @@ -0,0 +1,20 @@ +SHCreateItemFromParsingName +DeleteObject +IShellItem +IShellItemImageFactory +ExtractionFailed +S_OK + +SetWindowsHookEx +UnhookWindowsHookEx +CallNextHookEx +GetModuleHandle +GetKeyState +VIRTUAL_KEY + +WM_KEYDOWN +WM_KEYUP +WM_SYSKEYDOWN +WM_SYSKEYUP + +EnumWindows \ No newline at end of file From ce8b42bc6edb3e5759c7700dd11c71dfb4bff1d4 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 10 Dec 2024 22:01:55 +0800 Subject: [PATCH 07/21] Replace DllImport & flags with CSWin32 --- Flow.Launcher/Flow.Launcher.csproj | 4 + Flow.Launcher/Helper/DWMDropShadow.cs | 19 ++-- .../Helper/WallpaperPathRetrieval.cs | 17 ++-- Flow.Launcher/Helper/WindowsInteropHelper.cs | 89 +++++++------------ Flow.Launcher/MainWindow.xaml.cs | 9 +- Flow.Launcher/NativeMethods.txt | 14 +++ 6 files changed, 65 insertions(+), 87 deletions(-) create mode 100644 Flow.Launcher/NativeMethods.txt diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index afc3fbbaa0f..7bd59e8c138 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -90,6 +90,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Flow.Launcher/Helper/DWMDropShadow.cs b/Flow.Launcher/Helper/DWMDropShadow.cs index e448acd4c9e..7cb719d074d 100644 --- a/Flow.Launcher/Helper/DWMDropShadow.cs +++ b/Flow.Launcher/Helper/DWMDropShadow.cs @@ -1,20 +1,15 @@ using System; -using System.Drawing.Printing; -using System.Runtime.InteropServices; using System.Windows; using System.Windows.Interop; +using Windows.Win32; +using Windows.Win32.Graphics.Dwm; +using Windows.Win32.UI.Controls; namespace Flow.Launcher.Helper; public class DwmDropShadow { - [DllImport("dwmapi.dll", PreserveSig = true)] - private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize); - - [DllImport("dwmapi.dll")] - private static extern int DwmExtendFrameIntoClientArea(IntPtr hWnd, ref Margins pMarInset); - /// /// Drops a standard shadow to a WPF Window, even if the window isborderless. Only works with DWM (Vista and Seven). /// This method is much more efficient than setting AllowsTransparency to true and using the DropShadow effect, @@ -43,18 +38,18 @@ private static void window_SourceInitialized(object sender, EventArgs e) //fixed /// /// Window to which the shadow will be applied /// True if the method succeeded, false if not - private static bool DropShadow(Window window) + private unsafe static bool DropShadow(Window window) { try { WindowInteropHelper helper = new WindowInteropHelper(window); int val = 2; - int ret1 = DwmSetWindowAttribute(helper.Handle, 2, ref val, 4); + int ret1 = PInvoke.DwmSetWindowAttribute(new(helper.Handle), DWMWINDOWATTRIBUTE.DWMWA_NCRENDERING_POLICY, &val, 4); if (ret1 == 0) { - Margins m = new Margins { Bottom = 0, Left = 0, Right = 0, Top = 0 }; - int ret2 = DwmExtendFrameIntoClientArea(helper.Handle, ref m); + var m = new MARGINS { cyBottomHeight = 0, cxLeftWidth = 0, cxRightWidth = 0, cyTopHeight = 0 }; + int ret2 = PInvoke.DwmExtendFrameIntoClientArea(new(helper.Handle), &m); return ret2 == 0; } else diff --git a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs index e08e227cc33..8ab06e78d08 100644 --- a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs +++ b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs @@ -1,24 +1,21 @@ -using System; -using System.Linq; -using System.Runtime.InteropServices; +using System.Linq; using System.Text; using System.Windows.Media; using Microsoft.Win32; +using Windows.Win32; +using Windows.Win32.UI.WindowsAndMessaging; namespace Flow.Launcher.Helper; public static class WallpaperPathRetrieval { - [DllImport("user32.dll", CharSet = CharSet.Unicode)] - private static extern Int32 SystemParametersInfo(UInt32 action, - Int32 uParam, StringBuilder vParam, UInt32 winIni); - private static readonly UInt32 SPI_GETDESKWALLPAPER = 0x73; - private static int MAX_PATH = 260; + + private static readonly int MAX_PATH = 260; - public static string GetWallpaperPath() + public static unsafe string GetWallpaperPath() { var wallpaper = new StringBuilder(MAX_PATH); - SystemParametersInfo(SPI_GETDESKWALLPAPER, MAX_PATH, wallpaper, 0); + PInvoke.SystemParametersInfo(SYSTEM_PARAMETERS_INFO_ACTION.SPI_GETDESKWALLPAPER, (uint)MAX_PATH, &wallpaper, 0); var str = wallpaper.ToString(); if (string.IsNullOrEmpty(str)) diff --git a/Flow.Launcher/Helper/WindowsInteropHelper.cs b/Flow.Launcher/Helper/WindowsInteropHelper.cs index 89fbec967a8..ae48034e81e 100644 --- a/Flow.Launcher/Helper/WindowsInteropHelper.cs +++ b/Flow.Launcher/Helper/WindowsInteropHelper.cs @@ -1,75 +1,50 @@ using System; using System.Drawing; -using System.Runtime.InteropServices; -using System.Text; using System.Windows; using System.Windows.Forms; using System.Windows.Interop; using System.Windows.Media; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.UI.WindowsAndMessaging; using Point = System.Windows.Point; namespace Flow.Launcher.Helper; public class WindowsInteropHelper { - private const int GWL_STYLE = -16; //WPF's Message code for Title Bar's Style - private const int WS_SYSMENU = 0x80000; //WPF's Message code for System Menu - private static IntPtr _hwnd_shell; - private static IntPtr _hwnd_desktop; + private static HWND _hwnd_shell; + private static HWND _hwnd_desktop; //Accessors for shell and desktop handlers //Will set the variables once and then will return them - private static IntPtr HWND_SHELL + private static HWND HWND_SHELL { get { - return _hwnd_shell != IntPtr.Zero ? _hwnd_shell : _hwnd_shell = GetShellWindow(); + return _hwnd_shell != HWND.Null ? _hwnd_shell : _hwnd_shell = PInvoke.GetShellWindow(); } } - private static IntPtr HWND_DESKTOP + + private static HWND HWND_DESKTOP { get { - return _hwnd_desktop != IntPtr.Zero ? _hwnd_desktop : _hwnd_desktop = GetDesktopWindow(); + return _hwnd_desktop != HWND.Null ? _hwnd_desktop : _hwnd_desktop = PInvoke.GetDesktopWindow(); } } - [DllImport("user32.dll", SetLastError = true)] - internal static extern int GetWindowLong(IntPtr hWnd, int nIndex); - - [DllImport("user32.dll")] - internal static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong); - - [DllImport("user32.dll")] - internal static extern IntPtr GetForegroundWindow(); - - [DllImport("user32.dll")] - internal static extern IntPtr GetDesktopWindow(); - - [DllImport("user32.dll")] - internal static extern IntPtr GetShellWindow(); - - [DllImport("user32.dll", SetLastError = true)] - internal static extern int GetWindowRect(IntPtr hwnd, out RECT rc); - - [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] - internal static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); - - [DllImport("user32.DLL")] - public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow); - - const string WINDOW_CLASS_CONSOLE = "ConsoleWindowClass"; const string WINDOW_CLASS_WINTAB = "Flip3D"; const string WINDOW_CLASS_PROGMAN = "Progman"; const string WINDOW_CLASS_WORKERW = "WorkerW"; - public static bool IsWindowFullscreen() + public unsafe static bool IsWindowFullscreen() { //get current active window - IntPtr hWnd = GetForegroundWindow(); + var hWnd = PInvoke.GetForegroundWindow(); - if (hWnd.Equals(IntPtr.Zero)) + if (hWnd.Equals(HWND.Null)) { return false; } @@ -80,9 +55,16 @@ public static bool IsWindowFullscreen() return false; } - StringBuilder sb = new StringBuilder(256); - GetClassName(hWnd, sb, sb.Capacity); - string windowClass = sb.ToString(); + string windowClass; + int capacity = 256; + char[] buffer = new char[capacity]; + fixed (char* pBuffer = buffer) + { + PInvoke.GetClassName(hWnd, pBuffer, capacity); + int validLength = Array.IndexOf(buffer, '\0'); + if (validLength < 0) validLength = capacity; + windowClass = new string(buffer, 0, validLength); + } //for Win+Tab (Flip3D) if (windowClass == WINDOW_CLASS_WINTAB) @@ -90,20 +72,19 @@ public static bool IsWindowFullscreen() return false; } - RECT appBounds; - GetWindowRect(hWnd, out appBounds); + PInvoke.GetWindowRect(hWnd, out var appBounds); //for console (ConsoleWindowClass), we have to check for negative dimensions if (windowClass == WINDOW_CLASS_CONSOLE) { - return appBounds.Top < 0 && appBounds.Bottom < 0; + return appBounds.top < 0 && appBounds.bottom < 0; } //for desktop (Progman or WorkerW, depends on the system), we have to check if (windowClass is WINDOW_CLASS_PROGMAN or WINDOW_CLASS_WORKERW) { - IntPtr hWndDesktop = FindWindowEx(hWnd, IntPtr.Zero, "SHELLDLL_DefView", null); - hWndDesktop = FindWindowEx(hWndDesktop, IntPtr.Zero, "SysListView32", "FolderView"); + var hWndDesktop = PInvoke.FindWindowEx(hWnd, HWND.Null, "SHELLDLL_DefView", null); + hWndDesktop = PInvoke.FindWindowEx(hWndDesktop, HWND.Null, "SysListView32", "FolderView"); if (!hWndDesktop.Equals(IntPtr.Zero)) { return false; @@ -111,7 +92,7 @@ public static bool IsWindowFullscreen() } Rectangle screenBounds = Screen.FromHandle(hWnd).Bounds; - return (appBounds.Bottom - appBounds.Top) == screenBounds.Height && (appBounds.Right - appBounds.Left) == screenBounds.Width; + return (appBounds.bottom - appBounds.top) == screenBounds.Height && (appBounds.right - appBounds.left) == screenBounds.Width; } /// @@ -120,8 +101,8 @@ public static bool IsWindowFullscreen() /// public static void DisableControlBox(Window win) { - var hwnd = new WindowInteropHelper(win).Handle; - SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_SYSMENU); + var hwnd = new HWND(new WindowInteropHelper(win).Handle); + PInvoke.SetWindowLong(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE, PInvoke.GetWindowLong(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE) & ~(int)WINDOW_STYLE.WS_SYSMENU); } /// @@ -146,14 +127,4 @@ public static Point TransformPixelsToDIP(Visual visual, double unitX, double uni } return new Point((int)(matrix.M11 * unitX), (int)(matrix.M22 * unitY)); } - - - [StructLayout(LayoutKind.Sequential)] - public struct RECT - { - public int Left; - public int Top; - public int Right; - public int Bottom; - } } diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 0f8b8f6d70c..b4f1fb0317f 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -26,7 +26,7 @@ using DataObject = System.Windows.DataObject; using System.Windows.Media; using System.Windows.Interop; -using System.Runtime.InteropServices; +using Windows.Win32; namespace Flow.Launcher { @@ -34,9 +34,6 @@ public partial class MainWindow { #region Private Fields - [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - public static extern IntPtr SetForegroundWindow(IntPtr hwnd); - private readonly Storyboard _progressBarStoryboard = new Storyboard(); private bool isProgressBarStoryboardPaused; private Settings _settings; @@ -424,7 +421,7 @@ private void InitializeNotifyIcon() // Get context menu handle and bring it to the foreground if (PresentationSource.FromVisual(contextMenu) is HwndSource hwndSource) { - _ = SetForegroundWindow(hwndSource.Handle); + PInvoke.SetForegroundWindow(new(hwndSource.Handle)); } contextMenu.Focus(); @@ -692,7 +689,7 @@ public Screen SelectedScreen() screen = Screen.PrimaryScreen; break; case SearchWindowScreens.Focus: - IntPtr foregroundWindowHandle = WindowsInteropHelper.GetForegroundWindow(); + var foregroundWindowHandle = PInvoke.GetForegroundWindow().Value; screen = Screen.FromHandle(foregroundWindowHandle); break; case SearchWindowScreens.Custom: diff --git a/Flow.Launcher/NativeMethods.txt b/Flow.Launcher/NativeMethods.txt new file mode 100644 index 00000000000..bbf55502e5a --- /dev/null +++ b/Flow.Launcher/NativeMethods.txt @@ -0,0 +1,14 @@ +DwmSetWindowAttribute +DwmExtendFrameIntoClientArea +SystemParametersInfo +SetForegroundWindow + +GetWindowLong +SetWindowLong +GetForegroundWindow +GetDesktopWindow +GetShellWindow +GetWindowRect +GetClassName +FindWindowEx +WINDOW_STYLE \ No newline at end of file From dbe626d2384865b0a90af2939d8a2e75c4e223e1 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 11 Dec 2024 09:57:13 +0800 Subject: [PATCH 08/21] Fix key event return result issue --- .../Hotkey/GlobalHotkey.cs | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs b/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs index 75af843b973..04694ed167f 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs @@ -15,20 +15,23 @@ namespace Flow.Launcher.Infrastructure.Hotkey /// public unsafe class GlobalHotkey : IDisposable { + private static readonly HOOKPROC _procKeyboard = HookKeyboardCallback; private static readonly UnhookWindowsHookExSafeHandle hookId; - public delegate bool KeyboardCallback(int keyEvent, int vkCode, SpecialKeyState state); + public delegate bool KeyboardCallback(KeyEvent keyEvent, int vkCode, SpecialKeyState state); internal static Func hookedKeyboardCallback; static GlobalHotkey() { // Set the hook - using Process curProcess = Process.GetCurrentProcess(); - using ProcessModule curModule = curProcess.MainModule; - hookId = PInvoke.SetWindowsHookEx( - WINDOWS_HOOK_ID.WH_KEYBOARD_LL, - LowLevelKeyboardProc, - PInvoke.GetModuleHandle(curModule.ModuleName), 0); + hookId = SetHook(_procKeyboard, WINDOWS_HOOK_ID.WH_KEYBOARD_LL); + } + + private static UnhookWindowsHookExSafeHandle SetHook(HOOKPROC proc, WINDOWS_HOOK_ID hookId) + { + using var curProcess = Process.GetCurrentProcess(); + using var curModule = curProcess.MainModule; + return PInvoke.SetWindowsHookEx(hookId, proc, PInvoke.GetModuleHandle(curModule.ModuleName), 0); } public static SpecialKeyState CheckModifiers() @@ -58,20 +61,19 @@ public static SpecialKeyState CheckModifiers() return state; } - private static LRESULT LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) + private static LRESULT HookKeyboardCallback(int nCode, WPARAM wParam, LPARAM lParam) { bool continues = true; if (nCode >= 0) { - var wParamValue = (int)wParam.Value; - if (wParamValue == (int)KeyEvent.WM_KEYDOWN || - wParamValue == (int)KeyEvent.WM_KEYUP || - wParamValue == (int)KeyEvent.WM_SYSKEYDOWN || - wParamValue == (int)KeyEvent.WM_SYSKEYUP) + if (wParam.Value == (int)KeyEvent.WM_KEYDOWN || + wParam.Value == (int)KeyEvent.WM_KEYUP || + wParam.Value == (int)KeyEvent.WM_SYSKEYDOWN || + wParam.Value == (int)KeyEvent.WM_SYSKEYUP) { if (hookedKeyboardCallback != null) - continues = hookedKeyboardCallback((KeyEvent)wParamValue, Marshal.ReadInt32(lParam), CheckModifiers()); + continues = hookedKeyboardCallback((KeyEvent)wParam.Value, Marshal.ReadInt32(lParam), CheckModifiers()); } } @@ -79,7 +81,8 @@ private static LRESULT LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lPa { return PInvoke.CallNextHookEx(hookId, nCode, wParam, lParam); } - return new LRESULT(-1); + + return new LRESULT(1); } public void Dispose() From be72bb73aa8b1783c6ec9877f8067096e5a7ac84 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 11 Dec 2024 09:57:49 +0800 Subject: [PATCH 09/21] Fix VIRTUAL_KEY.VK_RWIN check issue --- Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs b/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs index 04694ed167f..2bc7db9bf65 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs @@ -52,7 +52,8 @@ public static SpecialKeyState CheckModifiers() //ALT is pressed state.AltPressed = true; } - if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_LWIN) & 0x8000) != 0) + if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_LWIN) & 0x8000) != 0 || + (PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_RWIN) & 0x8000) != 0) { //WIN is pressed state.WinPressed = true; From 49fc0b8fd07d5856eb265981e6dbccaad834d2a3 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 11 Dec 2024 10:04:03 +0800 Subject: [PATCH 10/21] Remove useless DllImport & flags --- Flow.Launcher/Helper/SingleInstance.cs | 226 ------------------------- 1 file changed, 226 deletions(-) diff --git a/Flow.Launcher/Helper/SingleInstance.cs b/Flow.Launcher/Helper/SingleInstance.cs index 739fed378e0..e0e3075f636 100644 --- a/Flow.Launcher/Helper/SingleInstance.cs +++ b/Flow.Launcher/Helper/SingleInstance.cs @@ -1,11 +1,5 @@ using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; -using System.Runtime.InteropServices; using System.IO.Pipes; -using System.Security; -using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; @@ -14,172 +8,6 @@ // modified to allow single instace restart namespace Flow.Launcher.Helper { - internal enum WM - { - NULL = 0x0000, - CREATE = 0x0001, - DESTROY = 0x0002, - MOVE = 0x0003, - SIZE = 0x0005, - ACTIVATE = 0x0006, - SETFOCUS = 0x0007, - KILLFOCUS = 0x0008, - ENABLE = 0x000A, - SETREDRAW = 0x000B, - SETTEXT = 0x000C, - GETTEXT = 0x000D, - GETTEXTLENGTH = 0x000E, - PAINT = 0x000F, - CLOSE = 0x0010, - QUERYENDSESSION = 0x0011, - QUIT = 0x0012, - QUERYOPEN = 0x0013, - ERASEBKGND = 0x0014, - SYSCOLORCHANGE = 0x0015, - SHOWWINDOW = 0x0018, - ACTIVATEAPP = 0x001C, - SETCURSOR = 0x0020, - MOUSEACTIVATE = 0x0021, - CHILDACTIVATE = 0x0022, - QUEUESYNC = 0x0023, - GETMINMAXINFO = 0x0024, - - WINDOWPOSCHANGING = 0x0046, - WINDOWPOSCHANGED = 0x0047, - - CONTEXTMENU = 0x007B, - STYLECHANGING = 0x007C, - STYLECHANGED = 0x007D, - DISPLAYCHANGE = 0x007E, - GETICON = 0x007F, - SETICON = 0x0080, - NCCREATE = 0x0081, - NCDESTROY = 0x0082, - NCCALCSIZE = 0x0083, - NCHITTEST = 0x0084, - NCPAINT = 0x0085, - NCACTIVATE = 0x0086, - GETDLGCODE = 0x0087, - SYNCPAINT = 0x0088, - NCMOUSEMOVE = 0x00A0, - NCLBUTTONDOWN = 0x00A1, - NCLBUTTONUP = 0x00A2, - NCLBUTTONDBLCLK = 0x00A3, - NCRBUTTONDOWN = 0x00A4, - NCRBUTTONUP = 0x00A5, - NCRBUTTONDBLCLK = 0x00A6, - NCMBUTTONDOWN = 0x00A7, - NCMBUTTONUP = 0x00A8, - NCMBUTTONDBLCLK = 0x00A9, - - SYSKEYDOWN = 0x0104, - SYSKEYUP = 0x0105, - SYSCHAR = 0x0106, - SYSDEADCHAR = 0x0107, - COMMAND = 0x0111, - SYSCOMMAND = 0x0112, - - MOUSEMOVE = 0x0200, - LBUTTONDOWN = 0x0201, - LBUTTONUP = 0x0202, - LBUTTONDBLCLK = 0x0203, - RBUTTONDOWN = 0x0204, - RBUTTONUP = 0x0205, - RBUTTONDBLCLK = 0x0206, - MBUTTONDOWN = 0x0207, - MBUTTONUP = 0x0208, - MBUTTONDBLCLK = 0x0209, - MOUSEWHEEL = 0x020A, - XBUTTONDOWN = 0x020B, - XBUTTONUP = 0x020C, - XBUTTONDBLCLK = 0x020D, - MOUSEHWHEEL = 0x020E, - - - CAPTURECHANGED = 0x0215, - - ENTERSIZEMOVE = 0x0231, - EXITSIZEMOVE = 0x0232, - - IME_SETCONTEXT = 0x0281, - IME_NOTIFY = 0x0282, - IME_CONTROL = 0x0283, - IME_COMPOSITIONFULL = 0x0284, - IME_SELECT = 0x0285, - IME_CHAR = 0x0286, - IME_REQUEST = 0x0288, - IME_KEYDOWN = 0x0290, - IME_KEYUP = 0x0291, - - NCMOUSELEAVE = 0x02A2, - - DWMCOMPOSITIONCHANGED = 0x031E, - DWMNCRENDERINGCHANGED = 0x031F, - DWMCOLORIZATIONCOLORCHANGED = 0x0320, - DWMWINDOWMAXIMIZEDCHANGE = 0x0321, - - #region Windows 7 - DWMSENDICONICTHUMBNAIL = 0x0323, - DWMSENDICONICLIVEPREVIEWBITMAP = 0x0326, - #endregion - - USER = 0x0400, - - // This is the hard-coded message value used by WinForms for Shell_NotifyIcon. - // It's relatively safe to reuse. - TRAYMOUSEMESSAGE = 0x800, //WM_USER + 1024 - APP = 0x8000 - } - - [SuppressUnmanagedCodeSecurity] - internal static class NativeMethods - { - /// - /// Delegate declaration that matches WndProc signatures. - /// - public delegate IntPtr MessageHandler(WM uMsg, IntPtr wParam, IntPtr lParam, out bool handled); - - [DllImport("shell32.dll", EntryPoint = "CommandLineToArgvW", CharSet = CharSet.Unicode)] - private static extern IntPtr _CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string cmdLine, out int numArgs); - - - [DllImport("kernel32.dll", EntryPoint = "LocalFree", SetLastError = true)] - private static extern IntPtr _LocalFree(IntPtr hMem); - - - public static string[] CommandLineToArgvW(string cmdLine) - { - IntPtr argv = IntPtr.Zero; - try - { - int numArgs = 0; - - argv = _CommandLineToArgvW(cmdLine, out numArgs); - if (argv == IntPtr.Zero) - { - throw new Win32Exception(); - } - var result = new string[numArgs]; - - for (int i = 0; i < numArgs; i++) - { - IntPtr currArg = Marshal.ReadIntPtr(argv, i * Marshal.SizeOf(typeof(IntPtr))); - result[i] = Marshal.PtrToStringUni(currArg); - } - - return result; - } - finally - { - - IntPtr p = _LocalFree(argv); - // Otherwise LocalFree failed. - // Assert.AreEqual(IntPtr.Zero, p); - } - } - - } - public interface ISingleInstanceApp { void OnSecondAppStarted(); @@ -219,10 +47,6 @@ public static class SingleInstance #endregion - #region Public Properties - - #endregion - #region Public Methods /// @@ -264,56 +88,6 @@ public static void Cleanup() #region Private Methods - /// - /// Gets command line args - for ClickOnce deployed applications, command line args may not be passed directly, they have to be retrieved. - /// - /// List of command line arg strings. - private static IList GetCommandLineArgs( string uniqueApplicationName ) - { - string[] args = null; - - try - { - // The application was not clickonce deployed, get args from standard API's - args = Environment.GetCommandLineArgs(); - } - catch (NotSupportedException) - { - - // The application was clickonce deployed - // Clickonce deployed apps cannot recieve traditional commandline arguments - // As a workaround commandline arguments can be written to a shared location before - // the app is launched and the app can obtain its commandline arguments from the - // shared location - string appFolderPath = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), uniqueApplicationName); - - string cmdLinePath = Path.Combine(appFolderPath, "cmdline.txt"); - if (File.Exists(cmdLinePath)) - { - try - { - using (TextReader reader = new StreamReader(cmdLinePath, Encoding.Unicode)) - { - args = NativeMethods.CommandLineToArgvW(reader.ReadToEnd()); - } - - File.Delete(cmdLinePath); - } - catch (IOException) - { - } - } - } - - if (args == null) - { - args = new string[] { }; - } - - return new List(args); - } - /// /// Creates a remote server pipe for communication. /// Once receives signal from client, will activate first instance. From 8a05c606e4ce63ecdde6aacf8b3d6ae7b778f91d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 11 Dec 2024 11:08:57 +0800 Subject: [PATCH 11/21] Fix possible double release --- Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs b/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs index 49d6da8c249..2fb8cf3632d 100644 --- a/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs +++ b/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs @@ -62,6 +62,7 @@ private static unsafe HBITMAP GetHBitmap(string fileName, int width, int height, if (nativeShellItem is not IShellItemImageFactory imageFactory) { Marshal.ReleaseComObject(nativeShellItem); + nativeShellItem = null; throw new InvalidOperationException("Failed to get IShellItemImageFactory"); } @@ -86,7 +87,10 @@ private static unsafe HBITMAP GetHBitmap(string fileName, int width, int height, } finally { - Marshal.ReleaseComObject(nativeShellItem); + if (nativeShellItem != null) + { + Marshal.ReleaseComObject(nativeShellItem); + } } return hBitmap; From a088f91541ea18c637c368706443793739acdc46 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 14 Dec 2024 11:17:29 +0800 Subject: [PATCH 12/21] Add comments for string truncation --- Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs | 1 + Flow.Launcher/Helper/WindowsInteropHelper.cs | 2 ++ Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs | 1 + .../Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs | 2 ++ 4 files changed, 6 insertions(+) diff --git a/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs b/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs index 191a7630958..9639f1b1ad4 100644 --- a/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs +++ b/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs @@ -62,6 +62,7 @@ private static unsafe string GetWindowTitle(HWND hwnd) return string.Empty; } + // Truncate the buffer to the actual length of the string int validLength = Array.IndexOf(buffer, '\0'); if (validLength < 0) validLength = capacity; return new string(buffer, 0, validLength); diff --git a/Flow.Launcher/Helper/WindowsInteropHelper.cs b/Flow.Launcher/Helper/WindowsInteropHelper.cs index ae48034e81e..6656b0c5b26 100644 --- a/Flow.Launcher/Helper/WindowsInteropHelper.cs +++ b/Flow.Launcher/Helper/WindowsInteropHelper.cs @@ -61,6 +61,8 @@ public unsafe static bool IsWindowFullscreen() fixed (char* pBuffer = buffer) { PInvoke.GetClassName(hWnd, pBuffer, capacity); + + // Truncate the buffer to the actual length of the string int validLength = Array.IndexOf(buffer, '\0'); if (validLength < 0) validLength = capacity; windowClass = new string(buffer, 0, validLength); diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs index 6df12fae98b..abc254d8694 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs @@ -106,6 +106,7 @@ public unsafe string TryGetProcessFilename(Process p) return string.Empty; } + // Truncate the buffer to the actual length of the string int validLength = Array.IndexOf(buffer, '\0'); if (validLength < 0) validLength = (int)capacity; return new string(buffer, 0, validLength); diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs index fae9c84c985..05cbfd2b12e 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs @@ -38,6 +38,8 @@ public unsafe string retrieveTargetPath(string path) fixed (char* bufferChar = buffer) { ((IShellLinkW)link).GetPath((PWSTR)bufferChar, MAX_PATH, &data, (uint)SLGP_FLAGS.SLGP_SHORTPATH); + + // Truncate the buffer to the actual length of the string int validLength = Array.IndexOf(buffer, '\0'); if (validLength < 0) validLength = MAX_PATH; target = new string(buffer, 0, validLength); From a899ff83e5a336505cdf0b64d2ef48f2d1fd417a Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 14 Dec 2024 11:46:09 +0800 Subject: [PATCH 13/21] Improve code quality --- .../Programs/ShellLinkHelper.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs index 05cbfd2b12e..ad1f3ab564d 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs @@ -38,11 +38,7 @@ public unsafe string retrieveTargetPath(string path) fixed (char* bufferChar = buffer) { ((IShellLinkW)link).GetPath((PWSTR)bufferChar, MAX_PATH, &data, (uint)SLGP_FLAGS.SLGP_SHORTPATH); - - // Truncate the buffer to the actual length of the string - int validLength = Array.IndexOf(buffer, '\0'); - if (validLength < 0) validLength = MAX_PATH; - target = new string(buffer, 0, validLength); + target = GetStringFromBuffer(buffer, MAX_PATH); } // To set the app description @@ -54,9 +50,7 @@ public unsafe string retrieveTargetPath(string path) fixed (char* buffer1Char = buffer1) { ((IShellLinkW)link).GetDescription((PWSTR)buffer1Char, MAX_PATH); - int validLength = Array.IndexOf(buffer1, '\0'); - if (validLength < 0) validLength = MAX_PATH; - description = new string(buffer1, 0, validLength); + description = GetStringFromBuffer(buffer1, MAX_PATH); } } catch (COMException e) @@ -71,9 +65,7 @@ public unsafe string retrieveTargetPath(string path) fixed (char* buffer2Char = buffer2) { ((IShellLinkW)link).GetArguments((PWSTR)buffer2Char, MAX_PATH); - int validLength = Array.IndexOf(buffer2, '\0'); - if (validLength < 0) validLength = MAX_PATH; - arguments = new string(buffer2, 0, validLength); + arguments = GetStringFromBuffer(buffer2, MAX_PATH); } } @@ -82,5 +74,13 @@ public unsafe string retrieveTargetPath(string path) return target; } + + private static unsafe string GetStringFromBuffer(char[] buffer, int maxLength) + { + // Truncate the buffer to the actual length of the string + int validLength = Array.IndexOf(buffer, '\0'); + if (validLength < 0) validLength = maxLength; + return new string(buffer, 0, validLength); + } } } From b96c7b12d51ec89b3355c8bc7a241f8173935d10 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 14 Dec 2024 11:51:13 +0800 Subject: [PATCH 14/21] Remove useless PInvoke method import --- Flow.Launcher.Infrastructure/NativeMethods.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt index a083f79aad8..f117534a1ff 100644 --- a/Flow.Launcher.Infrastructure/NativeMethods.txt +++ b/Flow.Launcher.Infrastructure/NativeMethods.txt @@ -2,7 +2,6 @@ DeleteObject IShellItem IShellItemImageFactory -ExtractionFailed S_OK SetWindowsHookEx From 532b5dc4c90b03399136299cc514aecd65d59eb5 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 19 Dec 2024 14:08:46 +0800 Subject: [PATCH 15/21] Replace flags with CSWin32 --- Flow.Launcher/MainWindow.xaml.cs | 6 ++---- Flow.Launcher/NativeMethods.txt | 5 ++++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index b4f1fb0317f..8ca153afc34 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -78,21 +78,19 @@ public MainWindow() InitializeComponent(); } - private const int WM_ENTERSIZEMOVE = 0x0231; - private const int WM_EXITSIZEMOVE = 0x0232; private int _initialWidth; private int _initialHeight; private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { - if (msg == WM_ENTERSIZEMOVE) + if (msg == PInvoke.WM_ENTERSIZEMOVE) { _initialWidth = (int)Width; _initialHeight = (int)Height; handled = true; } - if (msg == WM_EXITSIZEMOVE) + if (msg == PInvoke.WM_EXITSIZEMOVE) { if (_initialHeight != (int)Height) { diff --git a/Flow.Launcher/NativeMethods.txt b/Flow.Launcher/NativeMethods.txt index bbf55502e5a..2b147c05f52 100644 --- a/Flow.Launcher/NativeMethods.txt +++ b/Flow.Launcher/NativeMethods.txt @@ -11,4 +11,7 @@ GetShellWindow GetWindowRect GetClassName FindWindowEx -WINDOW_STYLE \ No newline at end of file +WINDOW_STYLE + +WM_ENTERSIZEMOVE +WM_EXITSIZEMOVE \ No newline at end of file From 3ab1fb15b639f13fa46ab5fcfceb581e20b3682a Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 19 Dec 2024 16:34:32 +0800 Subject: [PATCH 16/21] Fix clipboard action under sta thread issue --- .../NativeMethods.txt | 5 +- .../UserSettings/Settings.cs | 2 +- Flow.Launcher.Infrastructure/Win32Helper.cs | 141 ++++++++++++++++++ Flow.Launcher/PublicAPIInstance.cs | 45 +++--- 4 files changed, 170 insertions(+), 23 deletions(-) create mode 100644 Flow.Launcher.Infrastructure/Win32Helper.cs diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt index f117534a1ff..d8777ff277c 100644 --- a/Flow.Launcher.Infrastructure/NativeMethods.txt +++ b/Flow.Launcher.Infrastructure/NativeMethods.txt @@ -16,4 +16,7 @@ WM_KEYUP WM_SYSKEYDOWN WM_SYSKEYUP -EnumWindows \ No newline at end of file +EnumWindows + +OleInitialize +OleUninitialize \ No newline at end of file diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 0c7de10fd78..5493ad72410 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -230,7 +230,7 @@ public SearchPrecisionScore QuerySearchPrecision [JsonIgnore] public ObservableCollection BuiltinShortcuts { get; set; } = new() { - new BuiltinShortcutModel("{clipboard}", "shortcut_clipboard_description", Clipboard.GetText), + new BuiltinShortcutModel("{clipboard}", "shortcut_clipboard_description", () => Win32Helper.StartSTATaskAsync(Clipboard.GetText).Result), new BuiltinShortcutModel("{active_explorer_path}", "shortcut_active_explorer_path", FileExplorerHelper.GetActiveExplorerPath) }; diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs new file mode 100644 index 00000000000..76e9cfe5bcc --- /dev/null +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -0,0 +1,141 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Windows.Win32; + +namespace Flow.Launcher.Infrastructure +{ + /// + /// Provides static helper for Win32. + /// Codes are edited from: https://github.com/files-community/Files. + /// + public static class Win32Helper + { + public static Task StartSTATaskAsync(Action action) + { + var taskCompletionSource = new TaskCompletionSource(); + Thread thread = new(() => + { + PInvoke.OleInitialize(); + + try + { + action(); + taskCompletionSource.SetResult(); + } + catch (System.Exception) + { + taskCompletionSource.SetResult(); + } + finally + { + PInvoke.OleUninitialize(); + } + }) + { + IsBackground = true, + Priority = ThreadPriority.Normal + }; + + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + + return taskCompletionSource.Task; + } + + public static Task StartSTATaskAsync(Func func) + { + var taskCompletionSource = new TaskCompletionSource(); + Thread thread = new(async () => + { + PInvoke.OleInitialize(); + + try + { + await func(); + taskCompletionSource.SetResult(); + } + catch (System.Exception) + { + taskCompletionSource.SetResult(); + } + finally + { + PInvoke.OleUninitialize(); + } + }) + { + IsBackground = true, + Priority = ThreadPriority.Normal + }; + + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + + return taskCompletionSource.Task; + } + + public static Task StartSTATaskAsync(Func func) + { + var taskCompletionSource = new TaskCompletionSource(); + + Thread thread = new(() => + { + PInvoke.OleInitialize(); + + try + { + taskCompletionSource.SetResult(func()); + } + catch (System.Exception) + { + taskCompletionSource.SetResult(default); + } + finally + { + PInvoke.OleUninitialize(); + } + }) + { + IsBackground = true, + Priority = ThreadPriority.Normal + }; + + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + + return taskCompletionSource.Task; + } + + public static Task StartSTATaskAsync(Func> func) + { + var taskCompletionSource = new TaskCompletionSource(); + + Thread thread = new(async () => + { + PInvoke.OleInitialize(); + try + { + taskCompletionSource.SetResult(await func()); + } + catch (System.Exception) + { + taskCompletionSource.SetResult(default); + } + finally + { + PInvoke.OleUninitialize(); + } + }) + { + IsBackground = true, + Priority = ThreadPriority.Normal + }; + + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + + return taskCompletionSource.Task; + } + } +} diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index f4712770d7d..dcdb798fff2 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -117,35 +117,38 @@ public void ShellRun(string cmd, string filename = "cmd.exe") ShellCommand.Execute(startInfo); } - public void CopyToClipboard(string stringToCopy, bool directCopy = false, bool showDefaultNotification = true) + public async void CopyToClipboard(string stringToCopy, bool directCopy = false, bool showDefaultNotification = true) { if (string.IsNullOrEmpty(stringToCopy)) return; - var isFile = File.Exists(stringToCopy); - if (directCopy && (isFile || Directory.Exists(stringToCopy))) + await Win32Helper.StartSTATaskAsync(() => { - var paths = new StringCollection + var isFile = File.Exists(stringToCopy); + if (directCopy && (isFile || Directory.Exists(stringToCopy))) { - stringToCopy - }; + var paths = new StringCollection + { + stringToCopy + }; - Clipboard.SetFileDropList(paths); + Clipboard.SetFileDropList(paths); - if (showDefaultNotification) - ShowMsg( - $"{GetTranslation("copy")} {(isFile ? GetTranslation("fileTitle") : GetTranslation("folderTitle"))}", - GetTranslation("completedSuccessfully")); - } - else - { - Clipboard.SetDataObject(stringToCopy); + if (showDefaultNotification) + ShowMsg( + $"{GetTranslation("copy")} {(isFile ? GetTranslation("fileTitle") : GetTranslation("folderTitle"))}", + GetTranslation("completedSuccessfully")); + } + else + { + Clipboard.SetDataObject(stringToCopy); - if (showDefaultNotification) - ShowMsg( - $"{GetTranslation("copy")} {GetTranslation("textTitle")}", - GetTranslation("completedSuccessfully")); - } + if (showDefaultNotification) + ShowMsg( + $"{GetTranslation("copy")} {GetTranslation("textTitle")}", + GetTranslation("completedSuccessfully")); + } + }); } public void StartLoadingBar() => _mainVM.ProgressBarVisibility = Visibility.Visible; From 1073821b655e3dd29062fd19cc4a6f2b976aa292 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 20 Dec 2024 18:55:57 +0800 Subject: [PATCH 17/21] Improve recyble bin clear issue noticification --- Plugins/Flow.Launcher.Plugin.Sys/Main.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs index 8bcd5cd6b75..5bfc68ea613 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Sys/Main.cs @@ -252,8 +252,10 @@ private List Commands() var result = PInvoke.SHEmptyRecycleBin(new(), string.Empty, 0); if (result != HRESULT.S_OK && result != HRESULT.E_UNEXPECTED) { - context.API.ShowMsgBox($"Error emptying recycle bin, error code: {result}\n" + - "please refer to https://msdn.microsoft.com/en-us/library/windows/desktop/aa378137", + context.API.ShowMsgBox("Failed to empty the recycle bin. This might happen if:\n" + + "- A file in the recycle bin is in use\n" + + "- You don't have permission to delete some items\n" + + "Please close any applications that might be using these files and try again.", "Error", MessageBoxButton.OK, MessageBoxImage.Error); } From 6f899092b59a344b3ceb3d2edd28700e585c7335 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 24 Dec 2024 11:59:04 +0800 Subject: [PATCH 18/21] Move dll import codes to win32 helper --- Flow.Launcher.Core/Resource/Theme.cs | 98 +----------------- Flow.Launcher.Infrastructure/Win32Helper.cs | 106 +++++++++++++++++++- 2 files changed, 104 insertions(+), 100 deletions(-) diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index 0d8c0d90163..1d840930663 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -3,10 +3,8 @@ using System.IO; using System.Linq; using System.Xml; -using System.Runtime.InteropServices; using System.Windows; using System.Windows.Controls; -using System.Windows.Interop; using System.Windows.Markup; using System.Windows.Media; using System.Windows.Media.Effects; @@ -98,12 +96,12 @@ public bool ChangeTheme(string theme) _oldTheme = Path.GetFileNameWithoutExtension(_oldResource.Source.AbsolutePath); } - BlurEnabled = IsBlurTheme(); + BlurEnabled = Win32Helper.IsBlurTheme(); if (Settings.UseDropShadowEffect && !BlurEnabled) AddDropShadowEffectToCurrentTheme(); - SetBlurForWindow(); + Win32Helper.SetBlurForWindow(Application.Current.MainWindow, BlurEnabled); } catch (DirectoryNotFoundException) { @@ -357,98 +355,6 @@ public void RemoveDropShadowEffectFromCurrentTheme() UpdateResourceDictionary(dict); } - #region Blur Handling - /* - Found on https://github.com/riverar/sample-win10-aeroglass - */ - private enum AccentState - { - ACCENT_DISABLED = 0, - ACCENT_ENABLE_GRADIENT = 1, - ACCENT_ENABLE_TRANSPARENTGRADIENT = 2, - ACCENT_ENABLE_BLURBEHIND = 3, - ACCENT_INVALID_STATE = 4 - } - - [StructLayout(LayoutKind.Sequential)] - private struct AccentPolicy - { - public AccentState AccentState; - public int AccentFlags; - public int GradientColor; - public int AnimationId; - } - - [StructLayout(LayoutKind.Sequential)] - private struct WindowCompositionAttributeData - { - public WindowCompositionAttribute Attribute; - public IntPtr Data; - public int SizeOfData; - } - - private enum WindowCompositionAttribute - { - WCA_ACCENT_POLICY = 19 - } - [DllImport("user32.dll")] - private static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data); - - /// - /// Sets the blur for a window via SetWindowCompositionAttribute - /// - public void SetBlurForWindow() - { - if (BlurEnabled) - { - SetWindowAccent(Application.Current.MainWindow, AccentState.ACCENT_ENABLE_BLURBEHIND); - } - else - { - SetWindowAccent(Application.Current.MainWindow, AccentState.ACCENT_DISABLED); - } - } - - private bool IsBlurTheme() - { - if (Environment.OSVersion.Version >= new Version(6, 2)) - { - var resource = Application.Current.TryFindResource("ThemeBlurEnabled"); - - if (resource is bool) - return (bool)resource; - - return false; - } - - return false; - } - - private void SetWindowAccent(Window w, AccentState state) - { - var windowHelper = new WindowInteropHelper(w); - - windowHelper.EnsureHandle(); - - var accent = new AccentPolicy { AccentState = state }; - var accentStructSize = Marshal.SizeOf(accent); - - var accentPtr = Marshal.AllocHGlobal(accentStructSize); - Marshal.StructureToPtr(accent, accentPtr, false); - - var data = new WindowCompositionAttributeData - { - Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY, - SizeOfData = accentStructSize, - Data = accentPtr - }; - - SetWindowCompositionAttribute(windowHelper.Handle, ref data); - - Marshal.FreeHGlobal(accentPtr); - } - #endregion - public record ThemeData(string FileNameWithoutExtension, string Name, bool? IsDark = null, bool? HasBlur = null); } } diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 76e9cfe5bcc..6d6c7286412 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -1,16 +1,21 @@ using System; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using System.Windows.Interop; +using System.Windows; using Windows.Win32; namespace Flow.Launcher.Infrastructure { - /// - /// Provides static helper for Win32. - /// Codes are edited from: https://github.com/files-community/Files. - /// public static class Win32Helper { + #region STA Thread + + /* + Found on https://github.com/files-community/Files + */ + public static Task StartSTATaskAsync(Action action) { var taskCompletionSource = new TaskCompletionSource(); @@ -137,5 +142,98 @@ public static Task StartSTATaskAsync(Func func) return taskCompletionSource.Task; } + + #endregion + + #region Blur Handling + + /* + Found on https://github.com/riverar/sample-win10-aeroglass + */ + + private enum AccentState + { + ACCENT_DISABLED = 0, + ACCENT_ENABLE_GRADIENT = 1, + ACCENT_ENABLE_TRANSPARENTGRADIENT = 2, + ACCENT_ENABLE_BLURBEHIND = 3, + ACCENT_INVALID_STATE = 4 + } + + [StructLayout(LayoutKind.Sequential)] + private struct AccentPolicy + { + public AccentState AccentState; + public int AccentFlags; + public int GradientColor; + public int AnimationId; + } + + [StructLayout(LayoutKind.Sequential)] + private struct WindowCompositionAttributeData + { + public WindowCompositionAttribute Attribute; + public IntPtr Data; + public int SizeOfData; + } + + private enum WindowCompositionAttribute + { + WCA_ACCENT_POLICY = 19 + } + + [DllImport("user32.dll")] + private static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data); + + /// + /// Checks if the blur theme is enabled + /// + public static bool IsBlurTheme() + { + if (Environment.OSVersion.Version >= new Version(6, 2)) + { + var resource = Application.Current.TryFindResource("ThemeBlurEnabled"); + + if (resource is bool b) + return b; + + return false; + } + + return false; + } + + /// + /// Sets the blur for a window via SetWindowCompositionAttribute + /// + public static void SetBlurForWindow(Window w, bool blur) + { + SetWindowAccent(w, blur ? AccentState.ACCENT_ENABLE_BLURBEHIND : AccentState.ACCENT_DISABLED); + } + + private static void SetWindowAccent(Window w, AccentState state) + { + var windowHelper = new WindowInteropHelper(w); + + windowHelper.EnsureHandle(); + + var accent = new AccentPolicy { AccentState = state }; + var accentStructSize = Marshal.SizeOf(accent); + + var accentPtr = Marshal.AllocHGlobal(accentStructSize); + Marshal.StructureToPtr(accent, accentPtr, false); + + var data = new WindowCompositionAttributeData + { + Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY, + SizeOfData = accentStructSize, + Data = accentPtr + }; + + SetWindowCompositionAttribute(windowHelper.Handle, ref data); + + Marshal.FreeHGlobal(accentPtr); + } + #endregion } } From 0153f71083b755ff2df85c16757e7341e4069d2e Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Wed, 25 Dec 2024 11:20:09 -0600 Subject: [PATCH 19/21] use stackalloc if possible and fix some incorrect use of safehandle --- .../Hotkey/GlobalHotkey.cs | 2 +- .../SharedCommands/ShellCommand.cs | 19 +++---- Flow.Launcher/Helper/DWMDropShadow.cs | 17 +++--- .../Helper/WallpaperPathRetrieval.cs | 24 +++++---- Flow.Launcher/Helper/WindowsInteropHelper.cs | 41 +++++++++++---- .../ProcessHelper.cs | 7 +-- .../Programs/ShellLinkHelper.cs | 30 ++++------- .../Programs/ShellLocalization.cs | 52 +++++++++---------- 8 files changed, 96 insertions(+), 96 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs b/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs index 2bc7db9bf65..b2a14075581 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs @@ -88,7 +88,7 @@ private static LRESULT HookKeyboardCallback(int nCode, WPARAM wParam, LPARAM lPa public void Dispose() { - PInvoke.UnhookWindowsHookEx(new HHOOK(hookId.DangerousGetHandle())); + hookId.Dispose(); } ~GlobalHotkey() diff --git a/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs b/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs index 9639f1b1ad4..9ea582118a7 100644 --- a/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs +++ b/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs @@ -25,6 +25,7 @@ public static Process RunAsDifferentUser(ProcessStartInfo processStartInfo) CheckSecurityWindow(); Thread.Sleep(25); } + while (containsSecurityWindow) // while this process contains a "Windows Security" dialog, stay open { containsSecurityWindow = false; @@ -52,24 +53,20 @@ private static BOOL CheckSecurityThread(HWND hwnd, LPARAM lParam) private static unsafe string GetWindowTitle(HWND hwnd) { var capacity = PInvoke.GetWindowTextLength(hwnd) + 1; - char[] buffer = new char[capacity]; + int length; + Span buffer = stackalloc char[capacity]; fixed (char* pBuffer = buffer) { // If the window has no title bar or text, if the title bar is empty, // or if the window or control handle is invalid, the return value is zero. - if (PInvoke.GetWindowText(hwnd, (PWSTR)pBuffer, capacity) == 0) - { - return string.Empty; - } - - // Truncate the buffer to the actual length of the string - int validLength = Array.IndexOf(buffer, '\0'); - if (validLength < 0) validLength = capacity; - return new string(buffer, 0, validLength); + length = PInvoke.GetWindowText(hwnd, (PWSTR)pBuffer, capacity); } + + return buffer[..length].ToString(); } - public static ProcessStartInfo SetProcessStartInfo(this string fileName, string workingDirectory = "", string arguments = "", string verb = "", bool createNoWindow = false) + public static ProcessStartInfo SetProcessStartInfo(this string fileName, string workingDirectory = "", + string arguments = "", string verb = "", bool createNoWindow = false) { var info = new ProcessStartInfo { diff --git a/Flow.Launcher/Helper/DWMDropShadow.cs b/Flow.Launcher/Helper/DWMDropShadow.cs index 7cb719d074d..58817d70e03 100644 --- a/Flow.Launcher/Helper/DWMDropShadow.cs +++ b/Flow.Launcher/Helper/DWMDropShadow.cs @@ -2,6 +2,7 @@ using System.Windows; using System.Windows.Interop; using Windows.Win32; +using Windows.Win32.Foundation; using Windows.Win32.Graphics.Dwm; using Windows.Win32.UI.Controls; @@ -38,24 +39,22 @@ private static void window_SourceInitialized(object sender, EventArgs e) //fixed /// /// Window to which the shadow will be applied /// True if the method succeeded, false if not - private unsafe static bool DropShadow(Window window) + private static unsafe bool DropShadow(Window window) { try { WindowInteropHelper helper = new WindowInteropHelper(window); int val = 2; - int ret1 = PInvoke.DwmSetWindowAttribute(new(helper.Handle), DWMWINDOWATTRIBUTE.DWMWA_NCRENDERING_POLICY, &val, 4); + var ret1 = PInvoke.DwmSetWindowAttribute(new (helper.Handle), DWMWINDOWATTRIBUTE.DWMWA_NCRENDERING_POLICY, &val, 4); - if (ret1 == 0) + if (ret1 == HRESULT.S_OK) { var m = new MARGINS { cyBottomHeight = 0, cxLeftWidth = 0, cxRightWidth = 0, cyTopHeight = 0 }; - int ret2 = PInvoke.DwmExtendFrameIntoClientArea(new(helper.Handle), &m); - return ret2 == 0; - } - else - { - return false; + var ret2 = PInvoke.DwmExtendFrameIntoClientArea(new(helper.Handle), &m); + return ret2 == HRESULT.S_OK; } + + return false; } catch (Exception) { diff --git a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs index 8ab06e78d08..8a42d480ff9 100644 --- a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs +++ b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs @@ -1,5 +1,8 @@ -using System.Linq; +using System; +using System.Linq; +using System.Runtime.InteropServices; using System.Text; +using System.Windows.Documents; using System.Windows.Media; using Microsoft.Win32; using Windows.Win32; @@ -9,19 +12,17 @@ namespace Flow.Launcher.Helper; public static class WallpaperPathRetrieval { - private static readonly int MAX_PATH = 260; public static unsafe string GetWallpaperPath() { - var wallpaper = new StringBuilder(MAX_PATH); - PInvoke.SystemParametersInfo(SYSTEM_PARAMETERS_INFO_ACTION.SPI_GETDESKWALLPAPER, (uint)MAX_PATH, &wallpaper, 0); - - var str = wallpaper.ToString(); - if (string.IsNullOrEmpty(str)) - return null; - - return str; + var wallpaperPtr = stackalloc char[MAX_PATH]; + PInvoke.SystemParametersInfo(SYSTEM_PARAMETERS_INFO_ACTION.SPI_GETDESKWALLPAPER, (uint)MAX_PATH, + wallpaperPtr, + 0); + var wallpaper = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(wallpaperPtr); + + return wallpaper.ToString(); } public static Color GetWallpaperColor() @@ -32,13 +33,14 @@ public static Color GetWallpaperColor() { try { - var parts = strResult.Trim().Split(new[] {' '}, 3).Select(byte.Parse).ToList(); + var parts = strResult.Trim().Split(new[] { ' ' }, 3).Select(byte.Parse).ToList(); return Color.FromRgb(parts[0], parts[1], parts[2]); } catch { } } + return Colors.Transparent; } } diff --git a/Flow.Launcher/Helper/WindowsInteropHelper.cs b/Flow.Launcher/Helper/WindowsInteropHelper.cs index 6656b0c5b26..caf3f0a7f60 100644 --- a/Flow.Launcher/Helper/WindowsInteropHelper.cs +++ b/Flow.Launcher/Helper/WindowsInteropHelper.cs @@ -1,5 +1,7 @@ using System; +using System.ComponentModel; using System.Drawing; +using System.Runtime.InteropServices; using System.Windows; using System.Windows.Forms; using System.Windows.Interop; @@ -56,18 +58,17 @@ public unsafe static bool IsWindowFullscreen() } string windowClass; - int capacity = 256; - char[] buffer = new char[capacity]; + const int capacity = 256; + Span buffer = stackalloc char[capacity]; + int validLength; fixed (char* pBuffer = buffer) { - PInvoke.GetClassName(hWnd, pBuffer, capacity); - - // Truncate the buffer to the actual length of the string - int validLength = Array.IndexOf(buffer, '\0'); - if (validLength < 0) validLength = capacity; - windowClass = new string(buffer, 0, validLength); + validLength = PInvoke.GetClassName(hWnd, pBuffer, capacity); } + windowClass = buffer[..validLength].ToString(); + + //for Win+Tab (Flip3D) if (windowClass == WINDOW_CLASS_WINTAB) { @@ -87,14 +88,15 @@ public unsafe static bool IsWindowFullscreen() { var hWndDesktop = PInvoke.FindWindowEx(hWnd, HWND.Null, "SHELLDLL_DefView", null); hWndDesktop = PInvoke.FindWindowEx(hWndDesktop, HWND.Null, "SysListView32", "FolderView"); - if (!hWndDesktop.Equals(IntPtr.Zero)) + if (hWndDesktop.Value != (IntPtr.Zero)) { return false; } } Rectangle screenBounds = Screen.FromHandle(hWnd).Bounds; - return (appBounds.bottom - appBounds.top) == screenBounds.Height && (appBounds.right - appBounds.left) == screenBounds.Width; + return (appBounds.bottom - appBounds.top) == screenBounds.Height && + (appBounds.right - appBounds.left) == screenBounds.Width; } /// @@ -104,7 +106,23 @@ public unsafe static bool IsWindowFullscreen() public static void DisableControlBox(Window win) { var hwnd = new HWND(new WindowInteropHelper(win).Handle); - PInvoke.SetWindowLong(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE, PInvoke.GetWindowLong(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE) & ~(int)WINDOW_STYLE.WS_SYSMENU); + + var style = PInvoke.GetWindowLong(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE); + + if (style == 0) + { + throw new Win32Exception(Marshal.GetLastPInvokeError()); + } + + style &= ~(int)WINDOW_STYLE.WS_SYSMENU; + + var previousStyle = PInvoke.SetWindowLong(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE, + style); + + if (previousStyle == 0) + { + throw new Win32Exception(Marshal.GetLastPInvokeError()); + } } /// @@ -127,6 +145,7 @@ public static Point TransformPixelsToDIP(Visual visual, double unitX, double uni using var src = new HwndSource(new HwndSourceParameters()); matrix = src.CompositionTarget.TransformFromDevice; } + return new Point((int)(matrix.M11 * unitX), (int)(matrix.M22 * unitY)); } } diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs index abc254d8694..519e8a79297 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/ProcessHelper.cs @@ -98,7 +98,7 @@ public unsafe string TryGetProcessFilename(Process p) using var safeHandle = new SafeProcessHandle(handle.Value, true); uint capacity = 2000; - char[] buffer = new char[capacity]; + Span buffer = new char[capacity]; fixed (char* pBuffer = buffer) { if (!PInvoke.QueryFullProcessImageName(safeHandle, PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, (PWSTR)pBuffer, ref capacity)) @@ -106,10 +106,7 @@ public unsafe string TryGetProcessFilename(Process p) return string.Empty; } - // Truncate the buffer to the actual length of the string - int validLength = Array.IndexOf(buffer, '\0'); - if (validLength < 0) validLength = (int)capacity; - return new string(buffer, 0, validLength); + return buffer[..(int)capacity].ToString(); } } catch diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs index ad1f3ab564d..f194ae973d6 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs @@ -31,14 +31,14 @@ public unsafe string retrieveTargetPath(string path) ((IShellLinkW)link).Resolve(hwnd, 0); const int MAX_PATH = 260; - char[] buffer = new char[MAX_PATH]; + Span buffer = stackalloc char[MAX_PATH]; var data = new WIN32_FIND_DATAW(); var target = string.Empty; - fixed (char* bufferChar = buffer) + fixed (char* bufferPtr = buffer) { - ((IShellLinkW)link).GetPath((PWSTR)bufferChar, MAX_PATH, &data, (uint)SLGP_FLAGS.SLGP_SHORTPATH); - target = GetStringFromBuffer(buffer, MAX_PATH); + ((IShellLinkW)link).GetPath((PWSTR)bufferPtr, MAX_PATH, &data, (uint)SLGP_FLAGS.SLGP_SHORTPATH); + target = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(bufferPtr).ToString(); } // To set the app description @@ -46,11 +46,10 @@ public unsafe string retrieveTargetPath(string path) { try { - char[] buffer1 = new char[MAX_PATH]; - fixed (char* buffer1Char = buffer1) + fixed (char* bufferPtr = buffer) { - ((IShellLinkW)link).GetDescription((PWSTR)buffer1Char, MAX_PATH); - description = GetStringFromBuffer(buffer1, MAX_PATH); + ((IShellLinkW)link).GetDescription(bufferPtr, MAX_PATH); + description = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(bufferPtr).ToString(); } } catch (COMException e) @@ -61,11 +60,10 @@ public unsafe string retrieveTargetPath(string path) e); } - char[] buffer2 = new char[MAX_PATH]; - fixed (char* buffer2Char = buffer2) + fixed (char* bufferPtr = buffer) { - ((IShellLinkW)link).GetArguments((PWSTR)buffer2Char, MAX_PATH); - arguments = GetStringFromBuffer(buffer2, MAX_PATH); + ((IShellLinkW)link).GetArguments(bufferPtr, MAX_PATH); + arguments = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(bufferPtr).ToString(); } } @@ -74,13 +72,5 @@ public unsafe string retrieveTargetPath(string path) return target; } - - private static unsafe string GetStringFromBuffer(char[] buffer, int maxLength) - { - // Truncate the buffer to the actual length of the string - int validLength = Array.IndexOf(buffer, '\0'); - if (validLength < 0) validLength = maxLength; - return new string(buffer, 0, validLength); - } } } diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLocalization.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLocalization.cs index e36618b0de5..fac3ab181e7 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLocalization.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLocalization.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Runtime.InteropServices; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.System.LibraryLoader; @@ -20,39 +21,34 @@ public static class ShellLocalization /// The localized name as string or . public static unsafe string GetLocalizedName(string path) { - int capacity = 1024; - char[] resourcePathBuffer = new char[capacity]; + const int capacity = 1024; + Span buffer = new char[capacity]; // If there is no resource to localize a file name the method returns a non zero value. - fixed (char* resourcePath = resourcePathBuffer) + fixed (char* bufferPtr = buffer) { - var result = PInvoke.SHGetLocalizedName(path, (PWSTR)resourcePath, (uint)capacity, out var id); - if (result == HRESULT.S_OK) + var result = PInvoke.SHGetLocalizedName(path, bufferPtr, capacity, out var id); + if (result != HRESULT.S_OK) { - int validLength = Array.IndexOf(resourcePathBuffer, '\0'); - if (validLength < 0) validLength = capacity; - var resourcePathStr = new string(resourcePathBuffer, 0, validLength); - _ = PInvoke.ExpandEnvironmentStrings(resourcePathStr, resourcePath, (uint)capacity); - var handle = PInvoke.LoadLibraryEx(resourcePathStr, - LOAD_LIBRARY_FLAGS.DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_FLAGS.LOAD_LIBRARY_AS_DATAFILE); - IntPtr safeHandle = handle.DangerousGetHandle(); - if (safeHandle != IntPtr.Zero) - { - char[] localizedNameBuffer = new char[capacity]; - fixed (char* localizedName = localizedNameBuffer) - { - if (PInvoke.LoadString(handle, (uint)id, (PWSTR)localizedName, capacity) != 0) - { - validLength = Array.IndexOf(localizedNameBuffer, '\0'); - if (validLength < 0) validLength = capacity; - var lString = new string(localizedNameBuffer, 0, validLength); - PInvoke.FreeLibrary(new(safeHandle)); - return lString; - } - } + return string.Empty; + } + + var resourcePathStr = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(bufferPtr).ToString(); + _ = PInvoke.ExpandEnvironmentStrings(resourcePathStr, bufferPtr, capacity); + using var handle = PInvoke.LoadLibraryEx(resourcePathStr, + LOAD_LIBRARY_FLAGS.DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_FLAGS.LOAD_LIBRARY_AS_DATAFILE); + if (handle.IsInvalid) + { + return string.Empty; + } + + // not sure about the behavior of Pinvoke.LoadString, so we clear the buffer before using it (so it must be a null-terminated string) + buffer.Clear(); - PInvoke.FreeLibrary(new(safeHandle)); - } + if (PInvoke.LoadString(handle, (uint)id, bufferPtr, capacity) != 0) + { + var lString = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(bufferPtr).ToString(); + return lString; } } From 02a45661c4f836e9c21aff68f9872795cf1ee04c Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 26 Dec 2024 13:04:15 +0800 Subject: [PATCH 20/21] Add consistent error handling for GetArguments --- .../Programs/ShellLinkHelper.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs index f194ae973d6..a77b2ace839 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/ShellLinkHelper.cs @@ -35,10 +35,18 @@ public unsafe string retrieveTargetPath(string path) var data = new WIN32_FIND_DATAW(); var target = string.Empty; - fixed (char* bufferPtr = buffer) + try { - ((IShellLinkW)link).GetPath((PWSTR)bufferPtr, MAX_PATH, &data, (uint)SLGP_FLAGS.SLGP_SHORTPATH); - target = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(bufferPtr).ToString(); + fixed (char* bufferPtr = buffer) + { + ((IShellLinkW)link).GetPath((PWSTR)bufferPtr, MAX_PATH, &data, (uint)SLGP_FLAGS.SLGP_SHORTPATH); + target = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(bufferPtr).ToString(); + } + } + catch (COMException e) + { + ProgramLogger.LogException($"|IShellLinkW|retrieveTargetPath|{path}" + + "|Error occurred while getting program arguments", e); } // To set the app description @@ -66,11 +74,11 @@ public unsafe string retrieveTargetPath(string path) arguments = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(bufferPtr).ToString(); } } - + // To release unmanaged memory Marshal.ReleaseComObject(link); return target; - } + } } } From 9350e82b7793414871dcc59ae95a5b84268aa365 Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Fri, 27 Dec 2024 12:57:17 -0600 Subject: [PATCH 21/21] only stackalloc the getwindowtitle buffer when length is small. --- Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs b/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs index 9ea582118a7..a0440e30de9 100644 --- a/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs +++ b/Flow.Launcher.Plugin/SharedCommands/ShellCommand.cs @@ -54,12 +54,12 @@ private static unsafe string GetWindowTitle(HWND hwnd) { var capacity = PInvoke.GetWindowTextLength(hwnd) + 1; int length; - Span buffer = stackalloc char[capacity]; + Span buffer = capacity < 1024 ? stackalloc char[capacity] : new char[capacity]; fixed (char* pBuffer = buffer) { // If the window has no title bar or text, if the title bar is empty, // or if the window or control handle is invalid, the return value is zero. - length = PInvoke.GetWindowText(hwnd, (PWSTR)pBuffer, capacity); + length = PInvoke.GetWindowText(hwnd, pBuffer, capacity); } return buffer[..length].ToString();