Skip to content

Commit bd5142f

Browse files
committed
Use PInvoke instead & Improve code quality
1 parent b13cb75 commit bd5142f

File tree

10 files changed

+217
-178
lines changed

10 files changed

+217
-178
lines changed

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFramework>net7.0-windows</TargetFramework>
@@ -67,6 +67,7 @@
6767
<PrivateAssets>all</PrivateAssets>
6868
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
6969
</PackageReference>
70+
<PackageReference Include="NHotkey.Wpf" Version="3.0.0" />
7071
<PackageReference Include="NLog" Version="4.7.10" />
7172
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
7273
<PackageReference Include="SharpVectors.Wpf" Version="1.8.4.2" />
@@ -76,5 +77,13 @@
7677
<PackageReference Include="ToolGood.Words.Pinyin" Version="3.0.1.4" />
7778
</ItemGroup>
7879

80+
<ItemGroup>
81+
<Reference Include="Interop.SHDocVw, Version=1.1.0.0, Culture=neutral, PublicKeyToken=null">
82+
<HintPath>QuickSwitch\Interop.SHDocVw.dll</HintPath>
83+
</Reference>
84+
<Reference Include="Interop.Shell32, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
85+
<HintPath>QuickSwitch\Interop.Shell32.dll</HintPath>
86+
</Reference>
87+
</ItemGroup>
7988

8089
</Project>

Flow.Launcher.Infrastructure/NativeMethods.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,10 @@ INPUTLANGCHANGE_FORWARD
5353
LOCALE_TRANSIENT_KEYBOARD1
5454
LOCALE_TRANSIENT_KEYBOARD2
5555
LOCALE_TRANSIENT_KEYBOARD3
56-
LOCALE_TRANSIENT_KEYBOARD4
56+
LOCALE_TRANSIENT_KEYBOARD4
57+
58+
SetWinEventHook
59+
SendMessage
60+
EVENT_SYSTEM_FOREGROUND
61+
WINEVENT_OUTOFCONTEXT
62+
WM_KEYDOWN
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
using System;
2+
using System.IO;
3+
using System.Runtime.InteropServices;
4+
using Flow.Launcher.Infrastructure.Hotkey;
5+
using Flow.Launcher.Infrastructure.Logger;
6+
using Interop.UIAutomationClient;
7+
using NHotkey;
8+
using SHDocVw;
9+
using Shell32;
10+
using Windows.Win32;
11+
using Windows.Win32.Foundation;
12+
using Windows.Win32.UI.Accessibility;
13+
using WindowsInput;
14+
using WindowsInput.Native;
15+
16+
namespace Flow.Launcher.Infrastructure.QuickSwitch
17+
{
18+
public static class QuickSwitch
19+
{
20+
private static CUIAutomation8 _automation = new CUIAutomation8Class();
21+
22+
private static InternetExplorer lastExplorerView = null;
23+
24+
private static readonly InputSimulator _inputSimulator = new();
25+
26+
private static UnhookWinEventSafeHandle _hookWinEventSafeHandle = null;
27+
28+
public static void Initialize(Action<HotkeyModel, EventHandler<HotkeyEventArgs>> setHotkeyAction)
29+
{
30+
try
31+
{
32+
// Inspired from: https://github.com/citelao/dotnet_win32/blob/c830132d84eeed3a77e3a6e7f9ed6109258c7947/window_events/Program.cs
33+
// Here we use an UnhookWinEventSafeHandle as return value so the result is IDisposable and
34+
// can be cleaned up automatically for us.
35+
_hookWinEventSafeHandle = PInvoke.SetWinEventHook(
36+
PInvoke.EVENT_SYSTEM_FOREGROUND,
37+
PInvoke.EVENT_SYSTEM_FOREGROUND,
38+
null,
39+
WindowSwitch,
40+
0,
41+
0,
42+
PInvoke.WINEVENT_OUTOFCONTEXT);
43+
44+
if (_hookWinEventSafeHandle.IsInvalid)
45+
{
46+
Log.Error("Failed to set window event hook");
47+
return;
48+
}
49+
50+
setHotkeyAction(new HotkeyModel("Alt+G"), (_, _) =>
51+
{
52+
NavigateDialogPath(_automation.ElementFromHandle(Win32Helper.GetForegroundWindow()));
53+
});
54+
}
55+
catch (System.Exception e)
56+
{
57+
Log.Exception(nameof(QuickSwitch), "Failed to initialize QuickSwitch", e);
58+
}
59+
}
60+
61+
private static void NavigateDialogPath(IUIAutomationElement window)
62+
{
63+
if (window is not { CurrentClassName: "#32770" } dialog) return;
64+
65+
object document;
66+
try
67+
{
68+
document = lastExplorerView?.Document;
69+
}
70+
catch (COMException)
71+
{
72+
return;
73+
}
74+
75+
if (document is not IShellFolderViewDual2 folder) return;
76+
77+
var path = folder.Folder.Items().Item().Path;
78+
if (!Path.IsPathRooted(path)) return;
79+
80+
_inputSimulator.Keyboard.ModifiedKeyStroke(VirtualKeyCode.MENU, VirtualKeyCode.VK_D);
81+
82+
var address = dialog.FindFirst(TreeScope.TreeScope_Subtree, _automation.CreateAndCondition(
83+
_automation.CreatePropertyCondition(UIA_PropertyIds.UIA_ControlTypePropertyId, UIA_ControlTypeIds.UIA_EditControlTypeId),
84+
_automation.CreatePropertyCondition(UIA_PropertyIds.UIA_AccessKeyPropertyId, "d")));
85+
86+
if (address == null)
87+
{
88+
Log.Error("Cannot Get specific Control");
89+
return;
90+
}
91+
92+
var edit = (IUIAutomationValuePattern)address.GetCurrentPattern(UIA_PatternIds.UIA_ValuePatternId);
93+
edit.SetValue(path);
94+
95+
PInvoke.SendMessage(
96+
new(address.CurrentNativeWindowHandle),
97+
PInvoke.WM_KEYDOWN,
98+
(nuint)VirtualKeyCode.RETURN,
99+
IntPtr.Zero);
100+
}
101+
102+
private static void WindowSwitch(
103+
HWINEVENTHOOK hWinEventHook,
104+
uint eventType,
105+
HWND hwnd,
106+
int idObject,
107+
int idChild,
108+
uint dwEventThread,
109+
uint dwmsEventTime
110+
)
111+
{
112+
IUIAutomationElement window = null;
113+
try
114+
{
115+
window = _automation.ElementFromHandle(hwnd);
116+
}
117+
catch
118+
{
119+
return;
120+
}
121+
122+
if (window is { CurrentClassName: "#32770" })
123+
{
124+
NavigateDialogPath(window);
125+
return;
126+
}
127+
128+
ShellWindowsClass shellWindows = null;
129+
try
130+
{
131+
shellWindows = new ShellWindowsClass();
132+
133+
foreach (var shellWindow in shellWindows)
134+
{
135+
if (shellWindow is not InternetExplorer explorer)
136+
{
137+
continue;
138+
}
139+
140+
// Fix for CA2020: Wrap the conversion in a 'checked' statement
141+
if (explorer.HWND != checked((int)hwnd))
142+
{
143+
continue;
144+
}
145+
146+
// Release previous reference if exists
147+
if (lastExplorerView != null)
148+
{
149+
Marshal.ReleaseComObject(lastExplorerView);
150+
lastExplorerView = null;
151+
}
152+
153+
lastExplorerView = explorer;
154+
}
155+
}
156+
catch (System.Exception e)
157+
{
158+
Log.Exception(nameof(QuickSwitch), "Failed to get shell windows", e);
159+
}
160+
finally
161+
{
162+
if (window != null)
163+
{
164+
Marshal.ReleaseComObject(window);
165+
window = null;
166+
}
167+
if (shellWindows != null)
168+
{
169+
Marshal.ReleaseComObject(shellWindows);
170+
shellWindows = null;
171+
}
172+
}
173+
}
174+
175+
public static void Dispose()
176+
{
177+
// Dispose handle
178+
if (_hookWinEventSafeHandle != null)
179+
{
180+
_hookWinEventSafeHandle.Dispose();
181+
_hookWinEventSafeHandle = null;
182+
}
183+
184+
// Release ComObjects
185+
if (lastExplorerView != null)
186+
{
187+
Marshal.ReleaseComObject(lastExplorerView);
188+
lastExplorerView = null;
189+
}
190+
if (_automation != null)
191+
{
192+
Marshal.ReleaseComObject(_automation);
193+
_automation = null;
194+
}
195+
}
196+
}
197+
}

Flow.Launcher/App.xaml.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,6 @@ await API.StopwatchLogInfoAsync(ClassName, "Startup cost", async () =>
184184
// main windows needs initialized before theme change because of blur settings
185185
Ioc.Default.GetRequiredService<Theme>().ChangeTheme();
186186

187-
// initialize quick switch
188-
QuickSwitch.QuickSwitch.Initialize();
189-
190187
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
191188

192189
RegisterExitEvents();
@@ -337,6 +334,7 @@ protected virtual void Dispose(bool disposing)
337334
// since some resources owned by the thread need to be disposed.
338335
_mainWindow?.Dispatcher.Invoke(_mainWindow.Dispose);
339336
_mainVM?.Dispose();
337+
Infrastructure.QuickSwitch.QuickSwitch.Dispose();
340338
}
341339

342340
Log.Info("|App.Dispose|End Flow Launcher dispose ----------------------------------------------------");

Flow.Launcher/Flow.Launcher.csproj

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
1313
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
1414
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
15-
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
1615
</PropertyGroup>
1716

1817
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -97,7 +96,6 @@
9796
<!-- ModernWpfUI v0.9.5 introduced WinRT changes that causes Notification platform unavailable error on some machines -->
9897
<!-- https://github.com/Flow-Launcher/Flow.Launcher/issues/1772#issuecomment-1502440801 -->
9998
<PackageReference Include="ModernWpfUI" Version="0.9.4" />
100-
<PackageReference Include="NHotkey.Wpf" Version="3.0.0" />
10199
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
102100
<PackageReference Include="SemanticVersioning" Version="3.0.0" />
103101
<PackageReference Include="TaskScheduler" Version="2.12.1" />
@@ -116,15 +114,6 @@
116114
</Content>
117115
</ItemGroup>
118116

119-
<ItemGroup>
120-
<Reference Include="Interop.SHDocVw, Version=1.1.0.0, Culture=neutral, PublicKeyToken=null">
121-
<HintPath>QuickSwitch\Interop.SHDocVw.dll</HintPath>
122-
</Reference>
123-
<Reference Include="Interop.Shell32, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
124-
<HintPath>QuickSwitch\Interop.Shell32.dll</HintPath>
125-
</Reference>
126-
</ItemGroup>
127-
128117
<ItemGroup>
129118
<None Update="Resources\dev.ico">
130119
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

Flow.Launcher/Helper/HotKeyMapper.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ internal static void Initialize()
2222

2323
SetHotkey(_settings.Hotkey, OnToggleHotkey);
2424
LoadCustomPluginHotkey();
25+
26+
Infrastructure.QuickSwitch.QuickSwitch.Initialize(SetHotkey);
2527
}
2628

2729
internal static void OnToggleHotkey(object sender, HotkeyEventArgs args)
-151 KB
Binary file not shown.
-38 KB
Binary file not shown.

Flow.Launcher/QuickSwitch/NativeHelper.cs

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

0 commit comments

Comments
 (0)