diff --git a/Flow.Launcher.Infrastructure/FileExplorerHelper.cs b/Flow.Launcher.Infrastructure/FileExplorerHelper.cs new file mode 100644 index 000000000..a3b1bd6b7 --- /dev/null +++ b/Flow.Launcher.Infrastructure/FileExplorerHelper.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Flow.Launcher.Infrastructure +{ + public static class FileExplorerHelper + { + /// + /// Gets the path of the file explorer that is currently in the foreground + /// + public static string GetActiveExplorerPath() + { + var explorerWindow = GetActiveExplorer(); + string locationUrl = explorerWindow?.LocationURL; + return !string.IsNullOrEmpty(locationUrl) ? new Uri(locationUrl).LocalPath : null; + } + + /// + /// Gets the file explorer that is currently in the foreground + /// + private static dynamic GetActiveExplorer() + { + Type type = Type.GetTypeFromProgID("Shell.Application"); + if (type == null) return null; + dynamic shell = Activator.CreateInstance(type); + if (shell == null) + { + return null; + } + + var explorerWindows = new List(); + var openWindows = shell.Windows(); + for (int i = 0; i < openWindows.Count; i++) + { + var window = openWindows.Item(i); + if (window == null) continue; + + // find the desired window and make sure that it is indeed a file explorer + // we don't want the Internet Explorer or the classic control panel + // ToLower() is needed, because Windows can report the path as "C:\\Windows\\Explorer.EXE" + if (Path.GetFileName((string)window.FullName)?.ToLower() == "explorer.exe") + { + explorerWindows.Add(window); + } + } + + if (explorerWindows.Count == 0) return null; + + var zOrders = GetZOrder(explorerWindows); + + 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); + + /// + /// Gets the z-order for one or more windows atomically with respect to each other. In Windows, smaller z-order is higher. If the window is not top level, the z order is returned as -1. + /// + private static IEnumerable GetZOrder(List hWnds) + { + var z = new int[hWnds.Count]; + for (var i = 0; i < hWnds.Count; i++) z[i] = -1; + + var index = 0; + var numRemaining = hWnds.Count; + EnumWindows((wnd, _) => + { + var searchIndex = hWnds.FindIndex(x => x.HWND == wnd.ToInt32()); + if (searchIndex != -1) + { + z[searchIndex] = index; + numRemaining--; + if (numRemaining == 0) return false; + } + index++; + return true; + }, IntPtr.Zero); + + return z; + } + } +} diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index a184520d0..753334e23 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -192,8 +192,10 @@ namespace Flow.Launcher.Infrastructure.UserSettings public ObservableCollection CustomShortcuts { get; set; } = new ObservableCollection(); [JsonIgnore] - public ObservableCollection BuiltinShortcuts { get; set; } = new ObservableCollection() { - new BuiltinShortcutModel("{clipboard}", "shortcut_clipboard_description", Clipboard.GetText) + public ObservableCollection BuiltinShortcuts { get; set; } = new() + { + new BuiltinShortcutModel("{clipboard}", "shortcut_clipboard_description", Clipboard.GetText), + new BuiltinShortcutModel("{active_explorer_path}", "shortcut_active_explorer_path", FileExplorerHelper.GetActiveExplorerPath) }; public bool DontPromptUpdateMsg { get; set; } diff --git a/Flow.Launcher/Helper/FileExplorerHelper.cs b/Flow.Launcher/Helper/FileExplorerHelper.cs deleted file mode 100644 index c6d50f514..000000000 --- a/Flow.Launcher/Helper/FileExplorerHelper.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Text; -using System.Runtime.InteropServices; -using System.IO; - -namespace Flow.Launcher.Helper -{ - public class FileExplorerHelper - { - - /// - /// Gets the path of the file explorer that is currently in the foreground - /// - public static string GetActiveExplorerPath() - { - var explorerWindow = GetActiveExplorer(); - string locationUrl = explorerWindow?.LocationURL; - if (!string.IsNullOrEmpty(locationUrl)) - { - return new Uri(locationUrl).LocalPath; - } - else - { - return null; - } - } - - /// - /// Gets the file explorer that is currently in the foreground - /// - private static dynamic GetActiveExplorer() - { - // get the active window - IntPtr handle = GetForegroundWindow(); - - Type type = Type.GetTypeFromProgID("Shell.Application"); - if (type == null) return null; - dynamic shell = Activator.CreateInstance(type); - var openWindows = shell.Windows(); - for (int i = 0; i < openWindows.Count; i++) - { - var window = openWindows.Item(i); - if (window == null) continue; - - // find the desired window and make sure that it is indeed a file explorer - // we don't want the Internet Explorer or the classic control panel - // ToLower() is needed, because Windows can report the path as "C:\\Windows\\Explorer.EXE" - if (Path.GetFileName((string)window.FullName).ToLower() == "explorer.exe" && new IntPtr(window.HWND) == handle) - { - return window; - } - } - - return null; - } - - [DllImport("user32.dll")] - private static extern IntPtr GetForegroundWindow(); - } -} diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index b192617cb..49c603dc8 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -176,6 +176,7 @@ Are you sure you want to delete {0} plugin hotkey? Are you sure you want to delete shortcut: {0} with expansion {1}? Get text from clipboard. + Get path from active explorer. Query window shadow effect Shadow effect has a substantial usage of GPU. Not recommended if your computer performance is limited. Window Width Size