Skip to content

Commit 7c50ca3

Browse files
committed
Support third party file dialog
1 parent 23b586e commit 7c50ca3

File tree

6 files changed

+243
-183
lines changed

6 files changed

+243
-183
lines changed

Flow.Launcher.Infrastructure/NativeMethods.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,5 @@ EVENT_SYSTEM_MOVESIZESTART
7272
EVENT_SYSTEM_MOVESIZEEND
7373
GetDlgItem
7474
PostMessage
75-
BM_CLICK
75+
BM_CLICK
76+
WM_GETTEXT

Flow.Launcher.Infrastructure/QuickSwitch/Interface/IQuickSwitchDialogTab.cs

Lines changed: 0 additions & 13 deletions
This file was deleted.

Flow.Launcher.Infrastructure/QuickSwitch/Interface/IQuickSwitchDialogWindow.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ internal interface IQuickSwitchDialogWindow : IDisposable
77
{
88
internal HWND Handle { get; }
99

10-
internal IQuickSwitchDialogTab GetCurrentTab();
10+
internal IQuickSwitchDialogWindowTab GetCurrentTab();
1111
}
1212
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System;
2+
using Windows.Win32.Foundation;
3+
4+
namespace Flow.Launcher.Infrastructure.QuickSwitch.Interface
5+
{
6+
internal interface IQuickSwitchDialogWindowTab : IDisposable
7+
{
8+
internal HWND Handle { get; }
9+
10+
internal string GetCurrentFolder();
11+
12+
internal string GetCurrentFile();
13+
14+
internal bool JumpFolder(string path, bool auto);
15+
16+
internal bool JumpFile(string path);
17+
18+
internal bool Open();
19+
}
20+
}

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

Lines changed: 136 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
using Flow.Launcher.Infrastructure.QuickSwitch.Interface;
1+
using System;
2+
using System.Threading;
3+
using Flow.Launcher.Infrastructure.Logger;
4+
using Flow.Launcher.Infrastructure.QuickSwitch.Interface;
25
using Windows.Win32;
36
using Windows.Win32.Foundation;
7+
using Windows.Win32.UI.WindowsAndMessaging;
48

59
namespace Flow.Launcher.Infrastructure.QuickSwitch.Models
610
{
@@ -57,7 +61,7 @@ public WindowsDialogWindow(HWND handle)
5761
Handle = handle;
5862
}
5963

60-
public IQuickSwitchDialogTab GetCurrentTab()
64+
public IQuickSwitchDialogWindowTab GetCurrentTab()
6165
{
6266
return new WindowsDialogTab(Handle);
6367
}
@@ -68,31 +72,153 @@ public void Dispose()
6872
}
6973
}
7074

71-
internal class WindowsDialogTab : IQuickSwitchDialogTab
75+
internal class WindowsDialogTab : IQuickSwitchDialogWindowTab
7276
{
7377
public HWND Handle { get; private set; }
7478

79+
private static readonly string ClassName = nameof(WindowsDialogTab);
80+
81+
private readonly bool _legacy = false;
82+
83+
private readonly HWND _pathControl;
84+
private readonly HWND _pathEditor;
85+
private readonly HWND _fileEditor;
86+
private readonly HWND _openButton;
87+
7588
public WindowsDialogTab(HWND handle)
7689
{
7790
Handle = handle;
91+
92+
// Get the handle of the path editor
93+
// The window with class name "ComboBoxEx32" is not visible when the path editor is not with the keyboard focus
94+
_pathControl = PInvoke.GetDlgItem(handle, 0x0000); // WorkerW
95+
_pathControl = PInvoke.GetDlgItem(_pathControl, 0xA005); // ReBarWindow32
96+
_pathControl = PInvoke.GetDlgItem(_pathControl, 0xA205); // Address Band Root
97+
_pathControl = PInvoke.GetDlgItem(_pathControl, 0x0000); // msctls_progress32
98+
_pathControl = PInvoke.GetDlgItem(_pathControl, 0xA205); // ComboBoxEx32
99+
if (_pathControl == HWND.Null)
100+
{
101+
// https://github.com/idkidknow/Flow.Launcher.Plugin.DirQuickJump/issues/1
102+
// The dialog is a legacy one, so we edit file name editor directly.
103+
_legacy = true;
104+
_pathEditor = HWND.Null;
105+
Log.Info(ClassName, "Failed to find path control handle - Legacy dialog");
106+
}
107+
else
108+
{
109+
_pathEditor = PInvoke.GetDlgItem(_pathControl, 0xA205); // ComboBox
110+
_pathEditor = PInvoke.GetDlgItem(_pathEditor, 0xA205); // Edit
111+
if (_pathEditor == HWND.Null)
112+
{
113+
Log.Error(ClassName, "Failed to find path editor handle");
114+
}
115+
}
116+
117+
// Get the handle of the file name editor of Open file dialog
118+
_fileEditor = PInvoke.GetDlgItem(handle, 0x047C); // ComboBoxEx32
119+
_fileEditor = PInvoke.GetDlgItem(_fileEditor, 0x047C); // ComboBox
120+
_fileEditor = PInvoke.GetDlgItem(_fileEditor, 0x047C); // Edit
121+
if (_fileEditor == HWND.Null)
122+
{
123+
// Get the handle of the file name editor of Save/SaveAs file dialog
124+
_fileEditor = PInvoke.GetDlgItem(handle, 0x0000); // DUIViewWndClassName
125+
_fileEditor = PInvoke.GetDlgItem(_fileEditor, 0x0000); // DirectUIHWND
126+
_fileEditor = PInvoke.GetDlgItem(_fileEditor, 0x0000); // FloatNotifySink
127+
_fileEditor = PInvoke.GetDlgItem(_fileEditor, 0x0000); // ComboBox
128+
_fileEditor = PInvoke.GetDlgItem(_fileEditor, 0x03E9); // Edit
129+
if (_fileEditor == HWND.Null)
130+
{
131+
Log.Error(ClassName, "Failed to find file name editor handle");
132+
}
133+
}
134+
135+
// Get the handle of the open button
136+
_openButton = PInvoke.GetDlgItem(handle, 0x0001); // Open/Save/SaveAs Button
137+
if (_openButton == HWND.Null)
138+
{
139+
Log.Error(ClassName, "Failed to find open button handle");
140+
}
78141
}
79142

80143
public string GetCurrentFolder()
81144
{
82-
// TODO
83-
return string.Empty;
145+
if (_pathEditor.IsNull) return string.Empty;
146+
return GetWindowText(_pathEditor);
84147
}
85148

86149
public string GetCurrentFile()
87150
{
88-
// TODO
89-
return string.Empty;
151+
if (_fileEditor.IsNull) return string.Empty;
152+
return GetWindowText(_fileEditor);
90153
}
91154

92-
public bool OpenFolder(string path)
155+
public bool JumpFolder(string path, bool auto)
93156
{
94-
// TODO
95-
return false;
157+
if (_legacy || auto)
158+
{
159+
// https://github.com/idkidknow/Flow.Launcher.Plugin.DirQuickJump/issues/1
160+
// The dialog is a legacy one, so we edit file name text box directly
161+
if (_fileEditor.IsNull) return false;
162+
SetWindowText(_fileEditor, path);
163+
164+
if (_openButton.IsNull) return false;
165+
PInvoke.SendMessage(_openButton, PInvoke.BM_CLICK, 0, 0);
166+
167+
return true;
168+
}
169+
170+
if (_pathControl.IsNull) return false;
171+
172+
var timeOut = !SpinWait.SpinUntil(() =>
173+
{
174+
var style = PInvoke.GetWindowLong(_pathControl, WINDOW_LONG_PTR_INDEX.GWL_STYLE);
175+
return (style & (int)WINDOW_STYLE.WS_VISIBLE) != 0;
176+
}, 1000);
177+
if (timeOut)
178+
{
179+
Log.Error(ClassName, "Path control is not visible");
180+
return false;
181+
}
182+
183+
if (_pathEditor.IsNull) return false;
184+
185+
SetWindowText(_pathEditor, path);
186+
return true;
187+
}
188+
189+
public bool JumpFile(string path)
190+
{
191+
if (_fileEditor.IsNull) return false;
192+
SetWindowText(_fileEditor, path);
193+
return true;
194+
}
195+
196+
public bool Open()
197+
{
198+
if (_openButton.IsNull) return false;
199+
PInvoke.PostMessage(_openButton, PInvoke.BM_CLICK, 0, 0);
200+
return true;
201+
}
202+
203+
private static unsafe string GetWindowText(HWND handle)
204+
{
205+
int length;
206+
Span<char> buffer = stackalloc char[1000];
207+
fixed (char* pBuffer = buffer)
208+
{
209+
// If the control has no title bar or text, or if the control handle is invalid, the return value is zero.
210+
length = (int)PInvoke.SendMessage(handle, PInvoke.WM_GETTEXT, 1000, (nint)pBuffer);
211+
}
212+
213+
return buffer[..length].ToString();
214+
}
215+
216+
private static unsafe nint SetWindowText(HWND handle, string text)
217+
{
218+
fixed (char* textPtr = text + '\0')
219+
{
220+
return PInvoke.SendMessage(handle, PInvoke.WM_SETTEXT, 0, (nint)textPtr).Value;
221+
}
96222
}
97223

98224
public void Dispose()

0 commit comments

Comments
 (0)