11using System ;
22using System . Runtime . InteropServices ;
3+ using System . Threading ;
34using Flow . Launcher . Plugin ;
45using Windows . Win32 ;
6+ using Windows . Win32 . Foundation ;
57using Windows . Win32 . System . Com ;
68using 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