From 47878f3829a9cb10f87d5429d07fad097efec3dd Mon Sep 17 00:00:00 2001 From: DB p Date: Tue, 27 May 2025 14:08:32 +0900 Subject: [PATCH 1/4] Fix file explorer invocation to ensure correct file selection behavior --- Flow.Launcher/PublicAPIInstance.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 66e11f88130..93567288c65 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -338,7 +338,10 @@ public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null // Windows File Manager explorer.StartInfo = new ProcessStartInfo { - FileName = targetPath, + FileName = "explorer.exe", + Arguments = FileNameOrFilePath is null + ? DirectoryPath // only open the directory + : $"/select,\"{targetPath}\"", // open the directory and select the file UseShellExecute = true }; } From 41c5b36fba5aa1a627c153db7fa9f7ffc525dd4b Mon Sep 17 00:00:00 2001 From: DB p Date: Tue, 27 May 2025 15:20:41 +0900 Subject: [PATCH 2/4] Enhance OpenDirectory method to support folder opening and file selection using SHOpenFolderAndSelectItems --- Flow.Launcher/PublicAPIInstance.cs | 99 +++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 23 deletions(-) diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 93567288c65..58dbd3ff07b 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Net; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using System.Windows; @@ -320,47 +321,98 @@ public void SavePluginSettings() ((PluginJsonStorage)_pluginJsonStorages[type]).Save(); } - public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null) + [DllImport("shell32.dll", CharSet = CharSet.Unicode)] + private static extern int SHParseDisplayName( + [MarshalAs(UnmanagedType.LPWStr)] string name, + IntPtr bindingContext, + out IntPtr pidl, + uint sfgaoIn, + out uint psfgaoOut + ); + + [DllImport("shell32.dll")] + private static extern int SHOpenFolderAndSelectItems( + IntPtr pidlFolder, + uint cidl, + [MarshalAs(UnmanagedType.LPArray)] IntPtr[] apidl, + uint dwFlags + ); + + [DllImport("ole32.dll")] + private static extern void CoTaskMemFree(IntPtr pv); + + private void OpenFolderAndSelectItem(string filePath) { + IntPtr pidlFolder = IntPtr.Zero; + IntPtr pidlFile = IntPtr.Zero; + uint attr; + + string folderPath = Path.GetDirectoryName(filePath); + try { - using var explorer = new Process(); + SHParseDisplayName(folderPath, IntPtr.Zero, out pidlFolder, 0, out attr); + SHParseDisplayName(filePath, IntPtr.Zero, out pidlFile, 0, out attr); + + if (pidlFolder != IntPtr.Zero && pidlFile != IntPtr.Zero) + { + SHOpenFolderAndSelectItems(pidlFolder, 1, new[] { pidlFile }, 0); + } + } + finally + { + if (pidlFile != IntPtr.Zero) + CoTaskMemFree(pidlFile); + if (pidlFolder != IntPtr.Zero) + CoTaskMemFree(pidlFolder); + } + } + + public void OpenDirectory(string directoryPath, string fileNameOrFilePath = null) + { + try + { + string targetPath = fileNameOrFilePath is null + ? directoryPath + : Path.IsPathRooted(fileNameOrFilePath) + ? fileNameOrFilePath + : Path.Combine(directoryPath, fileNameOrFilePath); + var explorerInfo = _settings.CustomExplorer; var explorerPath = explorerInfo.Path.Trim().ToLowerInvariant(); - var targetPath = FileNameOrFilePath is null - ? DirectoryPath - : Path.IsPathRooted(FileNameOrFilePath) - ? FileNameOrFilePath - : Path.Combine(DirectoryPath, FileNameOrFilePath); if (Path.GetFileNameWithoutExtension(explorerPath) == "explorer") { - // Windows File Manager - explorer.StartInfo = new ProcessStartInfo + if (fileNameOrFilePath is null) { - FileName = "explorer.exe", - Arguments = FileNameOrFilePath is null - ? DirectoryPath // only open the directory - : $"/select,\"{targetPath}\"", // open the directory and select the file - UseShellExecute = true - }; + // 폴더만 열기 + Process.Start(new ProcessStartInfo + { + FileName = directoryPath, + UseShellExecute = true + })?.Dispose(); + } + else + { + // SHOpenFolderAndSelectItems 방식 + OpenFolderAndSelectItem(targetPath); + } } else { - // Custom File Manager - explorer.StartInfo = new ProcessStartInfo + // 커스텀 파일 관리자 + var shellProcess = new ProcessStartInfo { - FileName = explorerInfo.Path.Replace("%d", DirectoryPath), + FileName = explorerInfo.Path.Replace("%d", directoryPath), UseShellExecute = true, - Arguments = FileNameOrFilePath is null - ? explorerInfo.DirectoryArgument.Replace("%d", DirectoryPath) + Arguments = fileNameOrFilePath is null + ? explorerInfo.DirectoryArgument.Replace("%d", directoryPath) : explorerInfo.FileArgument - .Replace("%d", DirectoryPath) + .Replace("%d", directoryPath) .Replace("%f", targetPath) }; + Process.Start(shellProcess)?.Dispose(); } - - explorer.Start(); } catch (Win32Exception ex) when (ex.NativeErrorCode == 2) { @@ -384,6 +436,7 @@ public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null } } + private void OpenUri(Uri uri, bool? inPrivate = null) { if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps) From 086aeab6c05f4b2ab13e6d0a8239db7d83c588b1 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 27 May 2025 14:36:09 +0800 Subject: [PATCH 3/4] Use PInvoke to improve code quality --- .../NativeMethods.txt | 4 + Flow.Launcher.Infrastructure/Win32Helper.cs | 31 ++++++++ Flow.Launcher/PublicAPIInstance.cs | 76 ++++--------------- 3 files changed, 50 insertions(+), 61 deletions(-) diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt index 0e50420b0e0..2591506c884 100644 --- a/Flow.Launcher.Infrastructure/NativeMethods.txt +++ b/Flow.Launcher.Infrastructure/NativeMethods.txt @@ -57,3 +57,7 @@ LOCALE_TRANSIENT_KEYBOARD1 LOCALE_TRANSIENT_KEYBOARD2 LOCALE_TRANSIENT_KEYBOARD3 LOCALE_TRANSIENT_KEYBOARD4 + +SHParseDisplayName +SHOpenFolderAndSelectItems +CoTaskMemFree diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 783ade14ebe..4952eec9899 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Diagnostics; using System.Globalization; +using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading; @@ -17,6 +18,7 @@ using Windows.Win32.Foundation; using Windows.Win32.Graphics.Dwm; using Windows.Win32.UI.Input.KeyboardAndMouse; +using Windows.Win32.UI.Shell.Common; using Windows.Win32.UI.WindowsAndMessaging; using Point = System.Windows.Point; using SystemFonts = System.Windows.SystemFonts; @@ -753,5 +755,34 @@ private static bool TryGetNotoFont(string langKey, out string notoFont) } #endregion + + #region Explorer + + public static unsafe void OpenFolderAndSelectFile(string filePath) + { + ITEMIDLIST* pidlFolder = null; + ITEMIDLIST* pidlFile = null; + + var folderPath = Path.GetDirectoryName(filePath); + + try + { + var hrFolder = PInvoke.SHParseDisplayName(folderPath, null, out pidlFolder, 0, null); + if (hrFolder.Failed) throw new COMException("Failed to parse folder path", hrFolder); + + var hrFile = PInvoke.SHParseDisplayName(filePath, null, out pidlFile, 0, null); + if (hrFile.Failed) throw new COMException("Failed to parse file path", hrFile); + + var hrSelect = PInvoke.SHOpenFolderAndSelectItems(pidlFolder, 1, &pidlFile, 0); + if (hrSelect.Failed) throw new COMException("Failed to open folder and select item", hrSelect); + } + finally + { + if (pidlFile != null) PInvoke.CoTaskMemFree(pidlFile); + if (pidlFolder != null) PInvoke.CoTaskMemFree(pidlFolder); + } + } + + #endregion } } diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 58dbd3ff07b..c06c5603991 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -8,11 +8,9 @@ using System.Linq; using System.Net; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using System.Windows; -using System.Windows.Input; using System.Windows.Media; using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Core; @@ -320,88 +318,44 @@ public void SavePluginSettings() ((PluginJsonStorage)_pluginJsonStorages[type]).Save(); } - - [DllImport("shell32.dll", CharSet = CharSet.Unicode)] - private static extern int SHParseDisplayName( - [MarshalAs(UnmanagedType.LPWStr)] string name, - IntPtr bindingContext, - out IntPtr pidl, - uint sfgaoIn, - out uint psfgaoOut - ); - - [DllImport("shell32.dll")] - private static extern int SHOpenFolderAndSelectItems( - IntPtr pidlFolder, - uint cidl, - [MarshalAs(UnmanagedType.LPArray)] IntPtr[] apidl, - uint dwFlags - ); - - [DllImport("ole32.dll")] - private static extern void CoTaskMemFree(IntPtr pv); - - private void OpenFolderAndSelectItem(string filePath) - { - IntPtr pidlFolder = IntPtr.Zero; - IntPtr pidlFile = IntPtr.Zero; - uint attr; - - string folderPath = Path.GetDirectoryName(filePath); - - try - { - SHParseDisplayName(folderPath, IntPtr.Zero, out pidlFolder, 0, out attr); - SHParseDisplayName(filePath, IntPtr.Zero, out pidlFile, 0, out attr); - - if (pidlFolder != IntPtr.Zero && pidlFile != IntPtr.Zero) - { - SHOpenFolderAndSelectItems(pidlFolder, 1, new[] { pidlFile }, 0); - } - } - finally - { - if (pidlFile != IntPtr.Zero) - CoTaskMemFree(pidlFile); - if (pidlFolder != IntPtr.Zero) - CoTaskMemFree(pidlFolder); - } - } public void OpenDirectory(string directoryPath, string fileNameOrFilePath = null) { try { - string targetPath = fileNameOrFilePath is null + var explorerInfo = _settings.CustomExplorer; + var explorerPath = explorerInfo.Path.Trim().ToLowerInvariant(); + var targetPath = fileNameOrFilePath is null ? directoryPath : Path.IsPathRooted(fileNameOrFilePath) ? fileNameOrFilePath : Path.Combine(directoryPath, fileNameOrFilePath); - var explorerInfo = _settings.CustomExplorer; - var explorerPath = explorerInfo.Path.Trim().ToLowerInvariant(); - if (Path.GetFileNameWithoutExtension(explorerPath) == "explorer") { + // Windows File Manager if (fileNameOrFilePath is null) { - // 폴더만 열기 - Process.Start(new ProcessStartInfo + // Only Open the directory + using var explorer = new Process(); + explorer.StartInfo = new ProcessStartInfo { FileName = directoryPath, UseShellExecute = true - })?.Dispose(); + }; + explorer.Start(); } else { - // SHOpenFolderAndSelectItems 방식 - OpenFolderAndSelectItem(targetPath); + // Open the directory and select the file + Win32Helper.OpenFolderAndSelectFile(targetPath); } } else { - // 커스텀 파일 관리자 - var shellProcess = new ProcessStartInfo + // Custom File Manager + using var explorer = new Process(); + explorer.StartInfo = new ProcessStartInfo { FileName = explorerInfo.Path.Replace("%d", directoryPath), UseShellExecute = true, @@ -411,7 +365,7 @@ public void OpenDirectory(string directoryPath, string fileNameOrFilePath = null .Replace("%d", directoryPath) .Replace("%f", targetPath) }; - Process.Start(shellProcess)?.Dispose(); + explorer.Start(); } } catch (Win32Exception ex) when (ex.NativeErrorCode == 2) From eb69ce919e65b7b35fe48722f7a7ba8e9aa49096 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 27 May 2025 14:36:24 +0800 Subject: [PATCH 4/4] Add url comments --- Flow.Launcher.Infrastructure/Win32Helper.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 4952eec9899..96d8e925b71 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -758,6 +758,8 @@ private static bool TryGetNotoFont(string langKey, out string notoFont) #region Explorer + // https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shopenfolderandselectitems + public static unsafe void OpenFolderAndSelectFile(string filePath) { ITEMIDLIST* pidlFolder = null;