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..96d8e925b71 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,36 @@ private static bool TryGetNotoFont(string langKey, out string notoFont) } #endregion + + #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; + 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 66e11f88130..c06c5603991 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -11,7 +11,6 @@ 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; @@ -319,45 +318,55 @@ public void SavePluginSettings() ((PluginJsonStorage)_pluginJsonStorages[type]).Save(); } - - public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null) + + public void OpenDirectory(string directoryPath, string fileNameOrFilePath = null) { try { - using var explorer = new Process(); 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 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 = targetPath, - UseShellExecute = true - }; + // Only Open the directory + using var explorer = new Process(); + explorer.StartInfo = new ProcessStartInfo + { + FileName = directoryPath, + UseShellExecute = true + }; + explorer.Start(); + } + else + { + // Open the directory and select the file + Win32Helper.OpenFolderAndSelectFile(targetPath); + } } else { // Custom File Manager + using var explorer = new Process(); explorer.StartInfo = 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) }; + explorer.Start(); } - - explorer.Start(); } catch (Win32Exception ex) when (ex.NativeErrorCode == 2) { @@ -381,6 +390,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)