Skip to content

Commit 397c5d1

Browse files
committed
Support Files explorer
1 parent 08406ef commit 397c5d1

4 files changed

Lines changed: 205 additions & 1 deletion

File tree

Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
5656
<PackageReference Include="BitFaster.Caching" Version="2.5.3" />
5757
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
58+
<!-- FlaUI.UIA3 v5.0.0 requires System.Drawing.Common >= 8.0.10 -->
59+
<PackageReference Include="FlaUI.UIA3" Version="4.0.0" />
5860
<PackageReference Include="Fody" Version="6.5.5">
5961
<PrivateAssets>all</PrivateAssets>
6062
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

Flow.Launcher.Infrastructure/NativeMethods.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,5 @@ GetDlgItem
7777
PostMessage
7878
BM_CLICK
7979
WM_GETTEXT
80+
OpenProcess
81+
QueryFullProcessImageName
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
using System;
2+
using System.IO;
3+
using System.Runtime.InteropServices;
4+
using FlaUI.Core.AutomationElements;
5+
using FlaUI.UIA3;
6+
using Flow.Launcher.Infrastructure.Logger;
7+
using Flow.Launcher.Infrastructure.QuickSwitch.Interface;
8+
using Microsoft.Win32.SafeHandles;
9+
using Windows.Win32;
10+
using Windows.Win32.Foundation;
11+
using Windows.Win32.System.Threading;
12+
13+
namespace Flow.Launcher.Infrastructure.QuickSwitch.Models
14+
{
15+
/// <summary>
16+
/// Class for handling Files instances in QuickSwitch.
17+
/// </summary>
18+
/// <remarks>
19+
/// Edited from: https://github.com/files-community/Listary.FileAppPlugin.Files
20+
/// </remarks>
21+
internal class FilesExplorer : IQuickSwitchExplorer
22+
{
23+
private static readonly string ClassName = nameof(FilesExplorer);
24+
25+
private static FilesWindow _lastExplorerView = null;
26+
private static readonly object _lastExplorerViewLock = new();
27+
28+
public bool CheckExplorerWindow(HWND hWnd)
29+
{
30+
var isExplorer = false;
31+
lock (_lastExplorerViewLock)
32+
{
33+
// Is it from Files?
34+
var processName = Path.GetFileName(GetProcessPathFromHwnd(hWnd));
35+
if (processName == "Files.exe")
36+
{
37+
// Is it Files's file window?
38+
try
39+
{
40+
var automation = new UIA3Automation();
41+
var Files = automation.FromHandle(hWnd);
42+
if (Files.Name == "Files" || Files.Name.Contains("- Files"))
43+
{
44+
_lastExplorerView = new FilesWindow(hWnd, automation, Files);
45+
isExplorer = true;
46+
}
47+
}
48+
catch (TimeoutException e)
49+
{
50+
Log.Warn(ClassName, $"UIA timeout: {e}");
51+
}
52+
catch (System.Exception e)
53+
{
54+
Log.Warn(ClassName, $"Failed to bind window: {e}");
55+
}
56+
}
57+
}
58+
return isExplorer;
59+
}
60+
61+
private static unsafe string GetProcessPathFromHwnd(HWND hWnd)
62+
{
63+
uint pid;
64+
var threadId = PInvoke.GetWindowThreadProcessId(hWnd, &pid);
65+
if (threadId == 0) return string.Empty;
66+
67+
var process = PInvoke.OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
68+
if (process.Value != IntPtr.Zero)
69+
{
70+
using var safeHandle = new SafeProcessHandle(process.Value, true);
71+
uint capacity = 2000;
72+
Span<char> buffer = new char[capacity];
73+
fixed (char* pBuffer = buffer)
74+
{
75+
if (!PInvoke.QueryFullProcessImageName(safeHandle, PROCESS_NAME_FORMAT.PROCESS_NAME_WIN32, (PWSTR)pBuffer, ref capacity))
76+
{
77+
return string.Empty;
78+
}
79+
80+
return buffer[..(int)capacity].ToString();
81+
}
82+
}
83+
84+
return string.Empty;
85+
}
86+
87+
public string GetExplorerPath()
88+
{
89+
if (_lastExplorerView == null) return null;
90+
return _lastExplorerView.GetCurrentTab().GetCurrentFolder();
91+
}
92+
93+
public void RemoveExplorerWindow()
94+
{
95+
lock (_lastExplorerViewLock)
96+
{
97+
_lastExplorerView = null;
98+
}
99+
}
100+
101+
public void Dispose()
102+
{
103+
// Release ComObjects
104+
try
105+
{
106+
lock (_lastExplorerViewLock)
107+
{
108+
if (_lastExplorerView != null)
109+
{
110+
_lastExplorerView.Dispose();
111+
_lastExplorerView = null;
112+
}
113+
}
114+
}
115+
catch (COMException)
116+
{
117+
_lastExplorerView = null;
118+
}
119+
}
120+
121+
private class FilesWindow : IDisposable
122+
{
123+
private readonly UIA3Automation _automation;
124+
private readonly AutomationElement _Files;
125+
126+
public IntPtr Handle { get; }
127+
128+
public FilesWindow(IntPtr hWnd, UIA3Automation automation, AutomationElement Files)
129+
{
130+
Handle = hWnd;
131+
_automation = automation;
132+
_Files = Files;
133+
}
134+
135+
public void Dispose()
136+
{
137+
_automation.Dispose();
138+
}
139+
140+
public FilesTab GetCurrentTab()
141+
{
142+
return new FilesTab(_Files);
143+
}
144+
}
145+
146+
private class FilesTab
147+
{
148+
private readonly TextBox _currentPathGet;
149+
private readonly TextBox _currentPathSet;
150+
151+
public FilesTab(AutomationElement Files)
152+
{
153+
// Find window content to reduce the scope
154+
var _windowContent = Files.FindFirstChild(cf => cf.ByClassName("Microsoft.UI.Content.DesktopChildSiteBridge"));
155+
156+
_currentPathGet = _windowContent.FindFirstChild(cf => cf.ByAutomationId("CurrentPathGet"))?.AsTextBox();
157+
if (_currentPathGet == null)
158+
{
159+
Log.Error(ClassName, "Failed to find CurrentPathGet");
160+
return;
161+
}
162+
163+
_currentPathSet = _windowContent.FindFirstChild(cf => cf.ByAutomationId("CurrentPathSet"))?.AsTextBox();
164+
if (_currentPathSet == null)
165+
{
166+
Log.Error(ClassName, "Failed to find CurrentPathSet");
167+
return;
168+
}
169+
}
170+
171+
public string GetCurrentFolder()
172+
{
173+
try
174+
{
175+
return _currentPathGet.Text;
176+
}
177+
catch (System.Exception e)
178+
{
179+
Log.Error(ClassName, $"Failed to get current folder: {e}");
180+
return null;
181+
}
182+
}
183+
184+
public bool OpenFolder(string path)
185+
{
186+
try
187+
{
188+
_currentPathSet.Text = path;
189+
return true;
190+
}
191+
catch (System.Exception e)
192+
{
193+
Log.Error(ClassName, $"Failed to get current folder: {e}");
194+
return false;
195+
}
196+
}
197+
}
198+
}
199+
}

Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ public static class QuickSwitch
4242

4343
private static readonly List<IQuickSwitchExplorer> _quickSwitchExplorers = new()
4444
{
45-
new WindowsExplorer()
45+
new WindowsExplorer(),
46+
new FilesExplorer()
4647
};
4748

4849
private static IQuickSwitchExplorer _lastExplorer = null;

0 commit comments

Comments
 (0)