Skip to content

Commit ce93746

Browse files
committed
Add third party explorer support
1 parent 2b05e4f commit ce93746

File tree

3 files changed

+225
-138
lines changed

3 files changed

+225
-138
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System;
2+
using Windows.Win32.Foundation;
3+
4+
namespace Flow.Launcher.Infrastructure.QuickSwitch.Interface
5+
{
6+
/// <summary>
7+
/// Interface for handling Windows Explorer instances in QuickSwitch.
8+
/// </summary>
9+
/// <remarks>
10+
/// Add models in QuickSwitch/Models folder and implement this interface.
11+
/// Then add the instance in QuickSwitch._quickSwitchExplorers.
12+
/// </remarks>
13+
internal interface IQuickSwitchExplorer : IDisposable
14+
{
15+
internal bool CheckExplorerWindow(HWND foreground);
16+
17+
internal void RemoveExplorerWindow();
18+
19+
internal string GetExplorerPath();
20+
}
21+
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
using Flow.Launcher.Infrastructure.Logger;
4+
using Flow.Launcher.Infrastructure.QuickSwitch.Interface;
5+
using Windows.Win32;
6+
using Windows.Win32.Foundation;
7+
using Windows.Win32.System.Com;
8+
using Windows.Win32.UI.Shell;
9+
10+
namespace Flow.Launcher.Infrastructure.QuickSwitch.Models
11+
{
12+
internal class WindowsExplorer : IQuickSwitchExplorer
13+
{
14+
private static readonly string ClassName = nameof(WindowsExplorer);
15+
16+
private static IWebBrowser2 _lastExplorerView = null;
17+
private static readonly object _lastExplorerViewLock = new();
18+
19+
public bool CheckExplorerWindow(HWND foreground)
20+
{
21+
var isExplorer = false;
22+
lock (_lastExplorerViewLock)
23+
{
24+
EnumerateShellWindows((shellWindow) =>
25+
{
26+
try
27+
{
28+
if (shellWindow is not IWebBrowser2 explorer)
29+
{
30+
return;
31+
}
32+
33+
if (foreground != HWND.Null && explorer.HWND != foreground.Value)
34+
{
35+
return;
36+
}
37+
38+
_lastExplorerView = explorer;
39+
isExplorer = true;
40+
41+
Log.Debug(ClassName, $"{explorer.HWND.Value}");
42+
}
43+
catch (COMException)
44+
{
45+
// Ignored
46+
}
47+
});
48+
}
49+
return isExplorer;
50+
51+
static unsafe void EnumerateShellWindows(Action<object> action)
52+
{
53+
// Create an instance of ShellWindows
54+
var clsidShellWindows = new Guid("9BA05972-F6A8-11CF-A442-00A0C90A8F39"); // ShellWindowsClass
55+
var iidIShellWindows = typeof(IShellWindows).GUID; // IShellWindows
56+
57+
var result = PInvoke.CoCreateInstance(
58+
&clsidShellWindows,
59+
null,
60+
CLSCTX.CLSCTX_ALL,
61+
&iidIShellWindows,
62+
out var shellWindowsObj);
63+
64+
if (result.Failed) return;
65+
66+
var shellWindows = (IShellWindows)shellWindowsObj;
67+
68+
// Enumerate the shell windows
69+
var count = shellWindows.Count;
70+
for (var i = 0; i < count; i++)
71+
{
72+
action(shellWindows.Item(i));
73+
}
74+
}
75+
}
76+
77+
public string GetExplorerPath()
78+
{
79+
if (_lastExplorerView == null) return null;
80+
81+
object document = null;
82+
try
83+
{
84+
lock (_lastExplorerViewLock)
85+
{
86+
if (_lastExplorerView != null)
87+
{
88+
// Use dynamic here because using IWebBrower2.Document can cause exception here:
89+
// System.Runtime.InteropServices.InvalidOleVariantTypeException: 'Specified OLE variant is invalid.'
90+
dynamic explorerView = _lastExplorerView;
91+
document = explorerView.Document;
92+
}
93+
}
94+
}
95+
catch (COMException)
96+
{
97+
return null;
98+
}
99+
100+
if (document is not IShellFolderViewDual2 folderView)
101+
{
102+
return null;
103+
}
104+
105+
string path;
106+
try
107+
{
108+
// CSWin32 Folder does not have Self, so we need to use dynamic type here
109+
// Use dynamic to bypass static typing
110+
dynamic folder = folderView.Folder;
111+
112+
// Access the Self property via dynamic binding
113+
dynamic folderItem = folder.Self;
114+
115+
// Check if the item is part of the file system
116+
if (folderItem != null && folderItem.IsFileSystem)
117+
{
118+
path = folderItem.Path;
119+
}
120+
else
121+
{
122+
// Handle non-file system paths (e.g., virtual folders)
123+
path = string.Empty;
124+
}
125+
}
126+
catch
127+
{
128+
return null;
129+
}
130+
131+
return path;
132+
}
133+
134+
public void RemoveExplorerWindow()
135+
{
136+
lock (_lastExplorerViewLock)
137+
{
138+
_lastExplorerView = null;
139+
}
140+
}
141+
142+
public void Dispose()
143+
{
144+
// Release ComObjects
145+
try
146+
{
147+
lock (_lastExplorerViewLock)
148+
{
149+
if (_lastExplorerView != null)
150+
{
151+
Marshal.ReleaseComObject(_lastExplorerView);
152+
_lastExplorerView = null;
153+
}
154+
}
155+
}
156+
catch (COMException)
157+
{
158+
_lastExplorerView = null;
159+
}
160+
}
161+
}
162+
}

0 commit comments

Comments
 (0)