Skip to content

Commit 959e89f

Browse files
committed
1 parent 2c22993 commit 959e89f

File tree

3 files changed

+124
-32
lines changed

3 files changed

+124
-32
lines changed

Flow.Launcher.Infrastructure/NativeMethods.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,5 @@ SetWinEventHook
5959
SendMessage
6060
EVENT_SYSTEM_FOREGROUND
6161
WINEVENT_OUTOFCONTEXT
62-
WM_KEYDOWN
62+
WM_KEYDOWN
63+
WM_SETTEXT

Flow.Launcher.Infrastructure/QuickSwitch/QuickSwitch.cs

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
using System;
2-
using System.Diagnostics;
3-
using System.IO;
1+
using System.IO;
42
using System.Runtime.InteropServices;
3+
using System.Threading;
54
using Flow.Launcher.Infrastructure.Logger;
65
using Interop.UIAutomationClient;
76
using NHotkey;
@@ -11,7 +10,6 @@
1110
using Windows.Win32.Foundation;
1211
using Windows.Win32.UI.Accessibility;
1312
using WindowsInput;
14-
using WindowsInput.Native;
1513

1614
namespace Flow.Launcher.Infrastructure.QuickSwitch
1715
{
@@ -58,16 +56,11 @@ public static bool Initialize()
5856

5957
public static void OnToggleHotkey(object sender, HotkeyEventArgs args)
6058
{
61-
NavigateDialogPath(_automation.ElementFromHandle(Win32Helper.GetForegroundWindow()));
59+
NavigateDialogPath();
6260
}
6361

64-
private static void NavigateDialogPath(IUIAutomationElement window)
62+
private static void NavigateDialogPath()
6563
{
66-
if (window is not { CurrentClassName: "#32770" } dialog)
67-
{
68-
return;
69-
}
70-
7164
object document;
7265
try
7366
{
@@ -96,31 +89,45 @@ private static void NavigateDialogPath(IUIAutomationElement window)
9689
{
9790
return;
9891
}
99-
Debug.WriteLine($"Path: {path}");
10092

101-
// Use Alt + D to focus address bar
102-
_inputSimulator.Keyboard.ModifiedKeyStroke(VirtualKeyCode.MENU, VirtualKeyCode.VK_D);
93+
JumpToPath(path);
94+
}
10395

104-
var address = dialog.FindFirst(TreeScope.TreeScope_Subtree, _automation.CreateAndCondition(
105-
_automation.CreatePropertyCondition(UIA_PropertyIds.UIA_ControlTypePropertyId, UIA_ControlTypeIds.UIA_EditControlTypeId),
106-
_automation.CreatePropertyCondition(UIA_PropertyIds.UIA_AccessKeyPropertyId, "d")));
96+
private static bool JumpToPath(string path)
97+
{
98+
var t = new Thread(() =>
99+
{
100+
// Jump after flow launcher window vanished (after JumpAction returned true)
101+
// and the dialog had been in the foreground. The class name of a dialog window is "#32770".
102+
var timeOut = !SpinWait.SpinUntil(() => GetForegroundWindowClassName() == "#32770", 1000);
103+
if (timeOut)
104+
{
105+
return;
106+
};
107107

108-
if (address == null)
108+
// Assume that the dialog is in the foreground now
109+
Win32Helper.DirJump(_inputSimulator, path, PInvoke.GetForegroundWindow());
110+
});
111+
t.Start();
112+
return true;
113+
114+
static string GetForegroundWindowClassName()
109115
{
110-
// I found I cannot get address edit control here
111-
Debug.WriteLine("Failed to find address edit control");
112-
return;
116+
var handle = PInvoke.GetForegroundWindow();
117+
return GetClassName(handle);
113118
}
119+
}
114120

115-
var edit = (IUIAutomationValuePattern)address.GetCurrentPattern(UIA_PatternIds.UIA_ValuePatternId);
116-
edit.SetValue(path);
117-
118-
PInvoke.SendMessage(
119-
new(address.CurrentNativeWindowHandle),
120-
PInvoke.WM_KEYDOWN,
121-
(nuint)VirtualKeyCode.RETURN,
122-
IntPtr.Zero);
123-
Debug.WriteLine("Send Enter key to address edit control");
121+
private static unsafe string GetClassName(HWND handle)
122+
{
123+
fixed (char* buf = new char[256])
124+
{
125+
return PInvoke.GetClassName(handle, buf, 256) switch
126+
{
127+
0 => null,
128+
_ => new string(buf),
129+
};
130+
}
124131
}
125132

126133
private static void WindowSwitch(
@@ -145,7 +152,7 @@ uint dwmsEventTime
145152

146153
if (window is { CurrentClassName: "#32770" })
147154
{
148-
NavigateDialogPath(window);
155+
NavigateDialogPath();
149156
return;
150157
}
151158

Flow.Launcher.Infrastructure/Win32Helper.cs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Diagnostics;
44
using System.Globalization;
55
using System.Runtime.InteropServices;
6+
using System.Threading;
67
using System.Windows;
78
using System.Windows.Interop;
89
using System.Windows.Media;
@@ -13,6 +14,8 @@
1314
using Windows.Win32.Graphics.Dwm;
1415
using Windows.Win32.UI.Input.KeyboardAndMouse;
1516
using Windows.Win32.UI.WindowsAndMessaging;
17+
using WindowsInput;
18+
using WindowsInput.Native;
1619
using Point = System.Windows.Point;
1720

1821
namespace Flow.Launcher.Infrastructure
@@ -605,5 +608,86 @@ public static void OpenImeSettings()
605608
}
606609

607610
#endregion
611+
612+
#region Quick Switch
613+
614+
// Edited from: https://github.com/idkidknow/Flow.Launcher.Plugin.DirQuickJump
615+
616+
internal static bool DirJump(InputSimulator inputSimulator, string path, HWND dialogHandle, bool altD = true)
617+
{
618+
// Alt-D or Ctrl-L to focus on the path input box
619+
if (altD)
620+
{
621+
inputSimulator.Keyboard.ModifiedKeyStroke(VirtualKeyCode.LMENU, VirtualKeyCode.VK_D);
622+
}
623+
else
624+
{
625+
inputSimulator.Keyboard.ModifiedKeyStroke(VirtualKeyCode.LCONTROL, VirtualKeyCode.VK_L);
626+
}
627+
628+
// Get the handle of the path input box and then set the text.
629+
// The window with class name "ComboBoxEx32" is not visible when the path input box is not with the keyboard focus.
630+
var controlHandle = PInvoke.FindWindowEx(dialogHandle, HWND.Null, "WorkerW", null);
631+
controlHandle = PInvoke.FindWindowEx(controlHandle, HWND.Null, "ReBarWindow32", null);
632+
controlHandle = PInvoke.FindWindowEx(controlHandle, HWND.Null, "Address Band Root", null);
633+
controlHandle = PInvoke.FindWindowEx(controlHandle, HWND.Null, "msctls_progress32", null);
634+
controlHandle = PInvoke.FindWindowEx(controlHandle, HWND.Null, "ComboBoxEx32", null);
635+
if (controlHandle == HWND.Null)
636+
{
637+
return DirJumpOnLegacyDialog(inputSimulator, path, dialogHandle);
638+
}
639+
640+
var timeOut = !SpinWait.SpinUntil(() =>
641+
{
642+
int style = PInvoke.GetWindowLong(controlHandle, WINDOW_LONG_PTR_INDEX.GWL_STYLE);
643+
return (style & (int)WINDOW_STYLE.WS_VISIBLE) != 0;
644+
}, 1000);
645+
if (timeOut)
646+
{
647+
return false;
648+
}
649+
650+
var editHandle = PInvoke.FindWindowEx(controlHandle, HWND.Null, "ComboBox", null);
651+
editHandle = PInvoke.FindWindowEx(editHandle, HWND.Null, "Edit", null);
652+
if (editHandle == HWND.Null)
653+
{
654+
return false;
655+
}
656+
657+
SetWindowText(editHandle, path);
658+
inputSimulator.Keyboard.KeyPress(VirtualKeyCode.RETURN);
659+
660+
return true;
661+
}
662+
663+
internal static bool DirJumpOnLegacyDialog(InputSimulator inputSimulator, string path, HWND dialogHandle)
664+
{
665+
// https://github.com/idkidknow/Flow.Launcher.Plugin.DirQuickJump/issues/1
666+
var controlHandle = PInvoke.FindWindowEx(dialogHandle, HWND.Null, "ComboBoxEx32", null);
667+
controlHandle = PInvoke.FindWindowEx(controlHandle, HWND.Null, "ComboBox", null);
668+
controlHandle = PInvoke.FindWindowEx(controlHandle, HWND.Null, "Edit", null);
669+
if (controlHandle == HWND.Null)
670+
{
671+
return false;
672+
}
673+
674+
SetWindowText(controlHandle, path);
675+
// Alt-O (equivalent to press the Open button) twice. In normal cases it suffices to press once,
676+
// but when the focus is on an irrelevant folder, that press once will just open the irrelevant one.
677+
inputSimulator.Keyboard.ModifiedKeyStroke(VirtualKeyCode.LMENU, VirtualKeyCode.VK_O);
678+
inputSimulator.Keyboard.ModifiedKeyStroke(VirtualKeyCode.LMENU, VirtualKeyCode.VK_O);
679+
680+
return true;
681+
}
682+
683+
private static unsafe nint SetWindowText(HWND handle, string text)
684+
{
685+
fixed (char* textPtr = text + '\0')
686+
{
687+
return PInvoke.SendMessage(handle, PInvoke.WM_SETTEXT, 0, (nint)textPtr).Value;
688+
}
689+
}
690+
691+
#endregion
608692
}
609693
}

0 commit comments

Comments
 (0)