Skip to content

Commit 085d625

Browse files
committed
Support active explorer path
1 parent f63c8ca commit 085d625

File tree

1 file changed

+146
-48
lines changed

1 file changed

+146
-48
lines changed

Flow.Launcher.Infrastructure/QuickSwitch/Models/WindowsExplorer.cs

Lines changed: 146 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using System;
22
using System.Runtime.InteropServices;
3+
using System.Threading;
34
using Flow.Launcher.Plugin;
45
using Windows.Win32;
6+
using Windows.Win32.Foundation;
57
using Windows.Win32.System.Com;
68
using Windows.Win32.UI.Shell;
79

@@ -28,10 +30,10 @@ public IQuickSwitchExplorerWindow CheckExplorerWindow(IntPtr hwnd)
2830

2931
if (explorer.HWND != hwnd) return true;
3032

31-
explorerWindow = new WindowsExplorerWindow(hwnd, explorer);
33+
explorerWindow = new WindowsExplorerWindow(hwnd);
3234
return false;
3335
}
34-
catch (COMException)
36+
catch
3537
{
3638
// Ignored
3739
}
@@ -42,7 +44,7 @@ public IQuickSwitchExplorerWindow CheckExplorerWindow(IntPtr hwnd)
4244
return explorerWindow;
4345
}
4446

45-
private static unsafe void EnumerateShellWindows(Func<object, bool> action)
47+
internal static unsafe void EnumerateShellWindows(Func<object, bool> action)
4648
{
4749
// Create an instance of ShellWindows
4850
var clsidShellWindows = new Guid("9BA05972-F6A8-11CF-A442-00A0C90A8F39"); // ShellWindowsClass
@@ -80,40 +82,119 @@ public class WindowsExplorerWindow : IQuickSwitchExplorerWindow
8082
{
8183
public IntPtr Handle { get; }
8284

83-
private static IWebBrowser2 _explorerView = null;
85+
private static Guid _shellBrowserGuid = typeof(IShellBrowser).GUID;
8486

85-
internal WindowsExplorerWindow(IntPtr handle, IWebBrowser2 explorerView)
87+
internal WindowsExplorerWindow(IntPtr handle)
8688
{
8789
Handle = handle;
88-
_explorerView = explorerView;
8990
}
9091

9192
public string GetExplorerPath()
9293
{
93-
if (_explorerView == null) return null;
94+
if (Handle == IntPtr.Zero) return null;
9495

95-
object document = null;
96-
try
96+
var activeTabHandle = GetActiveTabHandle(new(Handle));
97+
if (activeTabHandle.IsNull) return null;
98+
99+
var window = GetExplorerByTabHandle(activeTabHandle);
100+
if (window == null) return null;
101+
102+
var path = GetLocation(window);
103+
return path;
104+
}
105+
106+
public void Dispose()
107+
{
108+
109+
}
110+
111+
#region Helper Methods
112+
113+
// Inspired by: https://github.com/w4po/ExplorerTabUtility
114+
115+
private static HWND GetActiveTabHandle(HWND windowHandle)
116+
{
117+
// Active tab always at the top of the z-index, so it is the first child of the ShellTabWindowClass.
118+
var activeTab = PInvoke.FindWindowEx(windowHandle, HWND.Null, "ShellTabWindowClass", null);
119+
return activeTab;
120+
}
121+
122+
private static IWebBrowser2 GetExplorerByTabHandle(HWND tabHandle)
123+
{
124+
if (tabHandle.IsNull) return null;
125+
126+
IWebBrowser2 window = null;
127+
WindowsExplorer.EnumerateShellWindows((shellWindow) =>
97128
{
98-
if (_explorerView != null)
129+
try
99130
{
100-
// Use dynamic here because using IWebBrower2.Document can cause exception here:
101-
// System.Runtime.InteropServices.InvalidOleVariantTypeException: 'Specified OLE variant is invalid.'
102-
dynamic explorerView = _explorerView;
103-
document = explorerView.Document;
131+
return StartSTAThread(() =>
132+
{
133+
if (shellWindow is not IWebBrowser2 explorer) return true;
134+
135+
if (explorer is not IServiceProvider sp) return true;
136+
137+
sp.QueryService(ref _shellBrowserGuid, ref _shellBrowserGuid, out var shellBrowser);
138+
if (shellBrowser == null) return true;
139+
140+
try
141+
{
142+
shellBrowser.GetWindow(out var hWnd); // Must execute in STA thread to get this hWnd
143+
144+
if (hWnd == tabHandle)
145+
{
146+
window = explorer;
147+
return false;
148+
}
149+
}
150+
catch
151+
{
152+
// Ignored
153+
}
154+
finally
155+
{
156+
Marshal.ReleaseComObject(shellBrowser);
157+
}
158+
159+
return true;
160+
}) ?? true;
161+
}
162+
catch
163+
{
164+
// Ignored
104165
}
105-
}
106-
catch (COMException)
107-
{
108-
return null;
109-
}
110166

111-
if (document is not IShellFolderViewDual2 folderView)
167+
return true;
168+
});
169+
170+
return window;
171+
}
172+
173+
private static bool? StartSTAThread(Func<bool> action)
174+
{
175+
bool? result = null;
176+
var thread = new Thread(() =>
112177
{
113-
return null;
114-
}
178+
result = action();
179+
})
180+
{
181+
IsBackground = true
182+
};
183+
thread.SetApartmentState(ApartmentState.STA);
184+
thread.Start();
185+
thread.Join();
186+
return result;
187+
}
115188

116-
string path;
189+
private static string GetLocation(IWebBrowser2 window)
190+
{
191+
var path = window.LocationURL.ToString();
192+
if (!string.IsNullOrWhiteSpace(path)) return NormalizeLocation(path);
193+
194+
// Recycle Bin, This PC, etc
195+
if (window.Document is not IShellFolderViewDual folderView) return null;
196+
197+
// Attempt to get the path from the folder view
117198
try
118199
{
119200
// CSWin32 Folder does not have Self, so we need to use dynamic type here
@@ -123,40 +204,57 @@ public string GetExplorerPath()
123204
// Access the Self property via dynamic binding
124205
dynamic folderItem = folder.Self;
125206

126-
// Check if the item is part of the file system
127-
if (folderItem != null && folderItem.IsFileSystem)
128-
{
129-
path = folderItem.Path;
130-
}
131-
else
132-
{
133-
// Handle non-file system paths (e.g., virtual folders)
134-
path = string.Empty;
135-
}
207+
// Get path from the folder item
208+
path = folderItem.Path;
136209
}
137210
catch
138211
{
139212
return null;
140213
}
141214

142-
return path;
215+
return NormalizeLocation(path);
143216
}
144217

145-
public void Dispose()
218+
private static string NormalizeLocation(string location)
146219
{
147-
// Release ComObjects
148-
try
149-
{
150-
if (_explorerView != null)
151-
{
152-
Marshal.ReleaseComObject(_explorerView);
153-
_explorerView = null;
154-
}
155-
}
156-
catch (COMException)
157-
{
158-
_explorerView = null;
159-
}
220+
if (location.IndexOf('%') > -1)
221+
location = Environment.ExpandEnvironmentVariables(location);
222+
223+
if (location.StartsWith("::", StringComparison.Ordinal))
224+
location = $"shell:{location}";
225+
226+
else if (location.StartsWith("{", StringComparison.Ordinal))
227+
location = $"shell:::{location}";
228+
229+
location = location.Trim(' ', '/', '\\', '\n', '\'', '"');
230+
231+
return location.Replace('/', '\\');
160232
}
233+
234+
#endregion
161235
}
236+
237+
#region COM Interfaces
238+
239+
// Inspired by: https://github.com/w4po/ExplorerTabUtility
240+
241+
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
242+
[Guid("6d5140c1-7436-11ce-8034-00aa006009fa")]
243+
[ComImport]
244+
public interface IServiceProvider
245+
{
246+
[PreserveSig]
247+
int QueryService(ref Guid guidService, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellBrowser ppvObject);
248+
}
249+
250+
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
251+
[Guid("000214E2-0000-0000-C000-000000000046")]
252+
[ComImport]
253+
public interface IShellBrowser
254+
{
255+
[PreserveSig]
256+
int GetWindow(out nint handle);
257+
}
258+
259+
#endregion
162260
}

0 commit comments

Comments
 (0)