[Settings/Run] LowLevel Keyboard hooking for Hotkeys (#3825)

* [Launcher/Settings] Low Level Keyboard Hooks

* [Run] LowLevel Keyboard Hook for Hotkeys

* Prevent shortcuts from auto repeating when keeping the keys pressed down
This commit is contained in:
Tomas Agustin Raies
2020-06-11 12:59:36 -07:00
committed by GitHub
parent fa7e4cc817
commit 670033c4da
28 changed files with 584 additions and 546 deletions

View File

@@ -783,7 +783,7 @@
<Fragment>
<ComponentGroup Id="LauncherComponents">
<Component Id="launcherInstallComponent" Directory="LauncherInstallFolder" Guid="5E688DB4-C522-4268-BA54-ED1CDFFE9DB6">
<?foreach File in concrt140_app.dll;ICSharpCode.SharpZipLib.dll;JetBrains.Annotations.dll;Mages.Core.dll;Microsoft.Search.Interop.dll;EntityFramework.SqlServer.dll;EntityFramework.dll;Mono.Cecil.dll;Mono.Cecil.Mdb.dll;Mono.Cecil.Pdb.dll;Mono.Cecil.Rocks.dll;msvcp140_1_app.dll;msvcp140_2_app.dll;msvcp140_app.dll;Newtonsoft.Json.dll;NHotkey.dll;NHotkey.Wpf.dll;NLog.dll;NLog.Extensions.Logging.dll;Pinyin4Net.dll;PowerLauncher.deps.json;PowerLauncher.dll;PowerLauncher.exe;Microsoft.Toolkit.Win32.UI.XamlHost.Managed.dll;Microsoft.Toolkit.Wpf.UI.XamlHost.dll;Microsoft.Xaml.Behaviors.dll;System.Text.Json.dll;sni.dll;System.Data.SQLite.EF6.dll;PowerLauncher.runtimeconfig.json;SQLite.Interop.dll;System.Data.OleDb.dll;System.Data.SqlClient.dll;System.Data.SQLite.dll;vcamp140_app.dll;vccorlib140_app.dll;vcomp140_app.dll;vcruntime140_1_app.dll;vcruntime140_app.dll;WindowsInput.dll;Wox.Core.dll;Wox.dll;Wox.Infrastructure.dll;Wox.Plugin.dll;PowerToysInterop.dll;Telemetry.dll;PowerLauncher.Telemetry.dll;PropertyChanged.dll;Microsoft.Extensions.Configuration.Abstractions.dll;Microsoft.Extensions.Configuration.Binder.dll;Microsoft.Extensions.Configuration.dll;Microsoft.Extensions.DependencyInjection.Abstractions.dll;Microsoft.Extensions.DependencyInjection.dll;Microsoft.Extensions.Logging.Abstractions.dll;Microsoft.Extensions.Logging.dll;Microsoft.Extensions.Options.dll;Microsoft.Extensions.Primitives.dll;ControlzEx.dll;MahApps.Metro.dll?>
<?foreach File in concrt140_app.dll;ICSharpCode.SharpZipLib.dll;JetBrains.Annotations.dll;Mages.Core.dll;Microsoft.Search.Interop.dll;EntityFramework.SqlServer.dll;EntityFramework.dll;Mono.Cecil.dll;Mono.Cecil.Mdb.dll;Mono.Cecil.Pdb.dll;Mono.Cecil.Rocks.dll;msvcp140_1_app.dll;msvcp140_2_app.dll;msvcp140_app.dll;Newtonsoft.Json.dll;NLog.dll;NLog.Extensions.Logging.dll;Pinyin4Net.dll;PowerLauncher.deps.json;PowerLauncher.dll;PowerLauncher.exe;Microsoft.Toolkit.Win32.UI.XamlHost.Managed.dll;Microsoft.Toolkit.Wpf.UI.XamlHost.dll;Microsoft.Xaml.Behaviors.dll;System.Text.Json.dll;sni.dll;System.Data.SQLite.EF6.dll;PowerLauncher.runtimeconfig.json;SQLite.Interop.dll;System.Data.OleDb.dll;System.Data.SqlClient.dll;System.Data.SQLite.dll;vcamp140_app.dll;vccorlib140_app.dll;vcomp140_app.dll;vcruntime140_1_app.dll;vcruntime140_app.dll;WindowsInput.dll;Wox.Core.dll;Wox.dll;Wox.Infrastructure.dll;Wox.Plugin.dll;PowerToysInterop.dll;Telemetry.dll;PowerLauncher.Telemetry.dll;PropertyChanged.dll;Microsoft.Extensions.Configuration.Abstractions.dll;Microsoft.Extensions.Configuration.Binder.dll;Microsoft.Extensions.Configuration.dll;Microsoft.Extensions.DependencyInjection.Abstractions.dll;Microsoft.Extensions.DependencyInjection.dll;Microsoft.Extensions.Logging.Abstractions.dll;Microsoft.Extensions.Logging.dll;Microsoft.Extensions.Options.dll;Microsoft.Extensions.Primitives.dll;ControlzEx.dll;MahApps.Metro.dll?>
<File Id="File_$(var.File)" Source="$(var.BinX64Dir)modules\launcher\$(var.File)" />
<?endforeach?>
<File Source="$(var.BinX64Dir)SettingsUIRunner\Microsoft.PowerToys.Settings.UI.Lib.dll" />

View File

@@ -0,0 +1,133 @@
#include "pch.h"
#include "HotkeyManager.h"
using namespace interop;
HotkeyManager::HotkeyManager()
{
keyboardEventCallback = gcnew KeyboardEventCallback(this, &HotkeyManager::KeyboardEventProc);
isActiveCallback = gcnew IsActiveCallback(this, &HotkeyManager::IsActiveProc);
filterKeyboardCallback = gcnew FilterKeyboardEvent(this, &HotkeyManager::FilterKeyboardProc);
keyboardHook = gcnew KeyboardHook(
keyboardEventCallback,
isActiveCallback,
filterKeyboardCallback
);
hotkeys = gcnew Dictionary<HOTKEY_HANDLE, HotkeyCallback ^>();
pressedKeys = gcnew Hotkey();
keyboardHook->Start();
}
HotkeyManager::~HotkeyManager()
{
delete keyboardHook;
}
// When all Shortcut keys are pressed, fire the HotkeyCallback event.
void HotkeyManager::KeyboardEventProc(KeyboardEvent^ ev)
{
auto pressedKeysHandle = GetHotkeyHandle(pressedKeys);
if (hotkeys->ContainsKey(pressedKeysHandle))
{
hotkeys[pressedKeysHandle]->Invoke();
}
}
// Hotkeys are intended to be global, therefore they are always active no matter the
// context in which the keypress occurs.
bool HotkeyManager::IsActiveProc()
{
return true;
}
// KeyboardEvent callback is only fired for relevant key events.
bool HotkeyManager::FilterKeyboardProc(KeyboardEvent^ ev)
{
auto oldHandle = GetHotkeyHandle(pressedKeys);
// Updating the pressed keys here so we know if the keypress event
// should be propagated or not.
UpdatePressedKeys(ev);
auto pressedKeysHandle = GetHotkeyHandle(pressedKeys);
// Check if the hotkey matches the pressed keys, and check if the pressed keys aren't duplicate
// (there shouldn't be auto repeating hotkeys)
if (hotkeys->ContainsKey(pressedKeysHandle) && oldHandle != pressedKeysHandle)
{
return true;
}
return false;
}
// NOTE: Replaces old hotkey if one already present.
HOTKEY_HANDLE HotkeyManager::RegisterHotkey(Hotkey ^ hotkey, HotkeyCallback ^ callback)
{
auto handle = GetHotkeyHandle(hotkey);
hotkeys[handle] = callback;
return handle;
}
void HotkeyManager::UnregisterHotkey(HOTKEY_HANDLE handle)
{
hotkeys->Remove(handle);
}
HOTKEY_HANDLE HotkeyManager::GetHotkeyHandle(Hotkey ^ hotkey)
{
HOTKEY_HANDLE handle = hotkey->Key;
handle |= hotkey->Win << 8;
handle |= hotkey->Ctrl << 9;
handle |= hotkey->Shift << 10;
handle |= hotkey->Alt << 11;
return handle;
}
void HotkeyManager::UpdatePressedKey(DWORD code, bool replaceWith, unsigned char replaceWithKey)
{
switch (code)
{
case VK_LWIN:
case VK_RWIN:
pressedKeys->Win = replaceWith;
break;
case VK_CONTROL:
case VK_LCONTROL:
case VK_RCONTROL:
pressedKeys->Ctrl = replaceWith;
break;
case VK_SHIFT:
case VK_LSHIFT:
case VK_RSHIFT:
pressedKeys->Shift = replaceWith;
break;
case VK_MENU:
case VK_LMENU:
case VK_RMENU:
pressedKeys->Alt = replaceWith;
break;
default:
pressedKeys->Key = replaceWithKey;
break;
}
}
void HotkeyManager::UpdatePressedKeys(KeyboardEvent ^ ev)
{
switch (ev->message)
{
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
{
UpdatePressedKey(ev->key, true, ev->key);
}
break;
case WM_KEYUP:
case WM_SYSKEYUP:
{
UpdatePressedKey(ev->key, false, 0);
}
break;
}
}

View File

@@ -0,0 +1,57 @@
#pragma once
#include <Windows.h>
#include "KeyboardHook.h"
namespace interop
{
public
ref struct Hotkey
{
bool Win;
bool Ctrl;
bool Shift;
bool Alt;
unsigned char Key;
Hotkey()
{
Win = false;
Ctrl = false;
Shift = false;
Alt = false;
Key = 0;
}
};
public
delegate void HotkeyCallback();
typedef unsigned short HOTKEY_HANDLE;
public
ref class HotkeyManager
{
public:
HotkeyManager();
~HotkeyManager();
HOTKEY_HANDLE RegisterHotkey(Hotkey ^ hotkey, HotkeyCallback ^ callback);
void UnregisterHotkey(HOTKEY_HANDLE handle);
private:
KeyboardHook ^ keyboardHook;
Dictionary<HOTKEY_HANDLE, HotkeyCallback ^> ^ hotkeys;
Hotkey ^ pressedKeys;
KeyboardEventCallback ^ keyboardEventCallback;
IsActiveCallback ^ isActiveCallback;
FilterKeyboardEvent ^ filterKeyboardCallback;
void KeyboardEventProc(KeyboardEvent ^ ev);
bool IsActiveProc();
bool FilterKeyboardProc(KeyboardEvent ^ ev);
HOTKEY_HANDLE GetHotkeyHandle(Hotkey ^ hotkey);
void UpdatePressedKeys(KeyboardEvent ^ ev);
void UpdatePressedKey(DWORD code, bool replaceWith, unsigned char replaceWithKey);
};
}

View File

@@ -0,0 +1,96 @@
#include "pch.h"
#include "KeyboardHook.h"
#include <exception>
#include <msclr\marshal.h>
#include <msclr\marshal_cppstd.h>
using namespace interop;
using namespace System::Runtime::InteropServices;
using namespace System;
using namespace System::Diagnostics;
KeyboardHook::KeyboardHook(
KeyboardEventCallback ^ keyboardEventCallback,
IsActiveCallback ^ isActiveCallback,
FilterKeyboardEvent ^ filterKeyboardEvent)
{
kbEventDispatch = gcnew Thread(gcnew ThreadStart(this, &KeyboardHook::DispatchProc));
queue = gcnew Queue<KeyboardEvent ^>();
this->keyboardEventCallback = keyboardEventCallback;
this->isActiveCallback = isActiveCallback;
this->filterKeyboardEvent = filterKeyboardEvent;
}
KeyboardHook::~KeyboardHook()
{
quit = true;
kbEventDispatch->Join();
// Unregister low level hook procedure
UnhookWindowsHookEx(hookHandle);
}
void KeyboardHook::DispatchProc()
{
Monitor::Enter(queue);
quit = false;
while (!quit)
{
if (queue->Count == 0)
{
Monitor::Wait(queue);
continue;
}
auto nextEv = queue->Dequeue();
// Release lock while callback is being invoked
Monitor::Exit(queue);
keyboardEventCallback->Invoke(nextEv);
// Re-aquire lock
Monitor::Enter(queue);
}
Monitor::Exit(queue);
}
void KeyboardHook::Start()
{
hookProc = gcnew HookProcDelegate(this, &KeyboardHook::HookProc);
Process ^ curProcess = Process::GetCurrentProcess();
ProcessModule ^ curModule = curProcess->MainModule;
// register low level hook procedure
hookHandle = SetWindowsHookEx(
WH_KEYBOARD_LL,
(HOOKPROC)(void*)Marshal::GetFunctionPointerForDelegate(hookProc),
0,
0);
if (hookHandle == nullptr)
{
throw std::exception("SetWindowsHookEx failed.");
}
kbEventDispatch->Start();
}
LRESULT CALLBACK KeyboardHook::HookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode == HC_ACTION && isActiveCallback->Invoke())
{
KeyboardEvent ^ ev = gcnew KeyboardEvent();
ev->message = wParam;
ev->key = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam)->vkCode;
if (filterKeyboardEvent != nullptr && !filterKeyboardEvent->Invoke(ev))
{
return CallNextHookEx(hookHandle, nCode, wParam, lParam);
}
Monitor::Enter(queue);
queue->Enqueue(ev);
Monitor::Pulse(queue);
Monitor::Exit(queue);
return 1;
}
return CallNextHookEx(hookHandle, nCode, wParam, lParam);
}

View File

@@ -0,0 +1,49 @@
#pragma once
using namespace System::Threading;
using namespace System::Collections::Generic;
namespace interop
{
public
ref struct KeyboardEvent
{
WPARAM message;
int key;
};
public
delegate void KeyboardEventCallback(KeyboardEvent ^ ev);
public
delegate bool IsActiveCallback();
public
delegate bool FilterKeyboardEvent(KeyboardEvent ^ ev);
public
ref class KeyboardHook
{
public:
KeyboardHook(
KeyboardEventCallback ^ keyboardEventCallback,
IsActiveCallback ^ isActiveCallback,
FilterKeyboardEvent ^ filterKeyboardEvent);
~KeyboardHook();
void Start();
private:
delegate LRESULT HookProcDelegate(int nCode, WPARAM wParam, LPARAM lParam);
Thread ^ kbEventDispatch;
Queue<KeyboardEvent ^> ^ queue;
KeyboardEventCallback ^ keyboardEventCallback;
IsActiveCallback ^ isActiveCallback;
FilterKeyboardEvent ^ filterKeyboardEvent;
bool quit;
HHOOK hookHandle;
HookProcDelegate ^ hookProc;
void DispatchProc();
LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam);
};
}

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\installer\Version.props" />
<PropertyGroup>
@@ -124,13 +124,17 @@
<WriteLinesToFile File="Generated Files\AssemblyInfo.cpp" Lines="@(HeaderLines)" Overwrite="true" Encoding="Unicode" WriteOnlyWhenDifferent="true" />
</Target>
<ItemGroup>
<ClInclude Include="HotkeyManager.h" />
<ClInclude Include="interop.h" />
<ClInclude Include="KeyboardHook.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="Resource.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="Generated Files\AssemblyInfo.cpp" />
<ClCompile Include="HotkeyManager.cpp" />
<ClCompile Include="interop.cpp" />
<ClCompile Include="KeyboardHook.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(CIBuild)'!='true'">Create</PrecompiledHeader>
</ClCompile>

View File

@@ -24,6 +24,12 @@
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="KeyboardHook.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="HotkeyManager.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="interop.cpp">
@@ -35,6 +41,12 @@
<ClCompile Include="Generated Files\AssemblyInfo.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="KeyboardHook.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="HotkeyManager.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="app.rc">

View File

@@ -7,6 +7,8 @@
#ifndef PCH_H
#define PCH_H
#define WIN32_LEAN_AND_MEAN
// add headers that you want to pre-compile here
#include <Windows.h>
#endif //PCH_H

View File

@@ -2,9 +2,9 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.PowerToys.Settings.UI.Lib.Utilities;
using System.Text;
using System.Text.Json.Serialization;
using Microsoft.PowerToys.Settings.UI.Lib.Utilities;
namespace Microsoft.PowerToys.Settings.UI.Lib
{
@@ -30,6 +30,11 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
Code = code;
}
public HotkeySettings Clone()
{
return new HotkeySettings(Win, Ctrl, Alt, Shift, Key, Code);
}
[JsonPropertyName("win")]
public bool Win { get; set; }
@@ -72,8 +77,16 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
output.Append("Shift + ");
}
if (Code > 0)
{
var localKey = Helper.GetKeyName((uint)Code);
output.Append(localKey);
}
else if (output.Length >= 2)
{
output.Remove(output.Length - 2, 2);
}
return output.ToString();
}

View File

@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Text;
using interop;
namespace Microsoft.PowerToys.Settings.UI.Lib
{
public delegate void KeyEvent(int key);
public delegate bool IsActive();
public class HotkeySettingsControlHook
{
const int WM_KEYDOWN = 0x100;
const int WM_KEYUP = 0x101;
const int WM_SYSKEYDOWN = 0x0104;
const int WM_SYSKEYUP = 0x0105;
private KeyboardHook hook;
private KeyEvent keyDown;
private KeyEvent keyUp;
private IsActive isActive;
public HotkeySettingsControlHook(KeyEvent keyDown, KeyEvent keyUp, IsActive isActive)
{
this.keyDown = keyDown;
this.keyUp = keyUp;
this.isActive = isActive;
hook = new KeyboardHook(HotkeySettingsHookCallback, IsActive, null);
hook.Start();
}
private bool IsActive()
{
return isActive();
}
private void HotkeySettingsHookCallback(KeyboardEvent ev)
{
switch (ev.message)
{
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
keyDown(ev.key);
break;
case WM_KEYUP:
case WM_SYSKEYUP:
keyUp(ev.key);
break;
}
}
}
}

View File

@@ -7,6 +7,7 @@ using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using System;
// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236
namespace Microsoft.PowerToys.Settings.UI.Controls
@@ -23,7 +24,10 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
null);
private HotkeySettings hotkeySettings;
private HotkeySettings internalSettings = new HotkeySettings();
private HotkeySettings internalSettings;
private HotkeySettings lastValidSettings;
private HotkeySettingsControlHook hook;
private bool _isActive;
public HotkeySettings HotkeySettings
{
@@ -48,74 +52,85 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
InitializeComponent();
internalSettings = new HotkeySettings();
HotkeyTextBox.PreviewKeyDown += HotkeyTextBox_KeyDown;
HotkeyTextBox.LostFocus += HotkeyTextBox_LosingFocus;
HotkeyTextBox.GettingFocus += HotkeyTextBox_GettingFocus;
HotkeyTextBox.LosingFocus += HotkeyTextBox_LosingFocus;
hook = new HotkeySettingsControlHook(Hotkey_KeyDown, Hotkey_KeyUp, Hotkey_IsActive);
}
private static bool IsDown(Windows.System.VirtualKey key)
private void KeyEventHandler(int key, bool matchValue, int matchValueCode, string matchValueText)
{
return Window.Current.CoreWindow.GetKeyState(key).HasFlag(CoreVirtualKeyStates.Down);
}
private void HotkeyTextBox_KeyDown(object sender, KeyRoutedEventArgs e)
{
e.Handled = true;
if (
e.Key == Windows.System.VirtualKey.LeftWindows ||
e.Key == Windows.System.VirtualKey.RightWindows ||
e.Key == Windows.System.VirtualKey.Control ||
e.Key == Windows.System.VirtualKey.Menu ||
e.Key == Windows.System.VirtualKey.Shift)
{
return;
}
if (e.Key == Windows.System.VirtualKey.Escape)
switch ((Windows.System.VirtualKey)key)
{
case Windows.System.VirtualKey.LeftWindows:
case Windows.System.VirtualKey.RightWindows:
internalSettings.Win = matchValue;
break;
case Windows.System.VirtualKey.Control:
case Windows.System.VirtualKey.LeftControl:
case Windows.System.VirtualKey.RightControl:
internalSettings.Ctrl = matchValue;
break;
case Windows.System.VirtualKey.Menu:
case Windows.System.VirtualKey.LeftMenu:
case Windows.System.VirtualKey.RightMenu:
internalSettings.Alt = matchValue;
break;
case Windows.System.VirtualKey.Shift:
case Windows.System.VirtualKey.LeftShift:
case Windows.System.VirtualKey.RightShift:
internalSettings.Shift = matchValue;
break;
case Windows.System.VirtualKey.Escape:
internalSettings = new HotkeySettings();
HotkeySettings = new HotkeySettings();
return;
default:
internalSettings.Code = matchValueCode;
internalSettings.Key = matchValueText;
break;
}
}
var settings = new HotkeySettings();
// Display HotKey value
if (IsDown(Windows.System.VirtualKey.LeftWindows) ||
IsDown(Windows.System.VirtualKey.RightWindows))
private async void Hotkey_KeyDown(int key)
{
settings.Win = true;
}
if (IsDown(Windows.System.VirtualKey.Control))
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
settings.Ctrl = true;
}
if (IsDown(Windows.System.VirtualKey.Menu))
KeyEventHandler(key, true, key, Lib.Utilities.Helper.GetKeyName((uint)key));
if (internalSettings.Code > 0)
{
settings.Alt = true;
lastValidSettings = internalSettings.Clone();
HotkeyTextBox.Text = lastValidSettings.ToString();
}
});
}
if (IsDown(Windows.System.VirtualKey.Shift))
private async void Hotkey_KeyUp(int key)
{
settings.Shift = true;
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
KeyEventHandler(key, false, 0, string.Empty);
});
}
settings.Key = Lib.Utilities.Helper.GetKeyName((uint)e.Key);
private bool Hotkey_IsActive()
{
return _isActive;
}
settings.Code = (int)e.OriginalKey;
internalSettings = settings;
HotkeyTextBox.Text = internalSettings.ToString();
private void HotkeyTextBox_GettingFocus(object sender, RoutedEventArgs e)
{
_isActive = true;
}
private void HotkeyTextBox_LosingFocus(object sender, RoutedEventArgs e)
{
if (internalSettings.IsValid() || internalSettings.IsEmpty())
if (lastValidSettings != null && (lastValidSettings.IsValid() || lastValidSettings.IsEmpty()))
{
HotkeySettings = internalSettings;
HotkeySettings = lastValidSettings.Clone();
}
HotkeyTextBox.Text = hotkeySettings.ToString();
_isActive = false;
}
}
}

View File

@@ -10,7 +10,6 @@ using System.Threading.Tasks;
using System.Windows;
using WindowsInput;
using WindowsInput.Native;
using Wox.Infrastructure.Hotkey;
using Wox.Infrastructure.Logger;
using Wox.Infrastructure.Storage;
using Wox.Plugin.SharedCommands;
@@ -289,7 +288,6 @@ namespace Microsoft.Plugin.Shell
public void Init(PluginInitContext context)
{
this._context = context;
context.API.GlobalKeyboardEvent += API_GlobalKeyboardEvent;
}
bool API_GlobalKeyboardEvent(int keyevent, int vkcode, SpecialKeyState state)

View File

@@ -1,19 +0,0 @@
<UserControl x:Class="Wox.HotkeyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:input="clr-namespace:System.Windows.Input;assembly=PresentationCore"
mc:Ignorable="d"
Height="24"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition Width="120" />
</Grid.ColumnDefinitions>
<TextBox x:Name="tbHotkey" TabIndex="100" VerticalContentAlignment="Center" Grid.Column="0"
PreviewKeyDown="TbHotkey_OnPreviewKeyDown" input:InputMethod.IsInputMethodEnabled="False"/>
<TextBlock x:Name="tbMsg" Visibility="Hidden" Margin="5 0 0 0" VerticalAlignment="Center" Grid.Column="1" />
</Grid>
</UserControl>

View File

@@ -1,117 +0,0 @@
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using NHotkey.Wpf;
using Wox.Core.Resource;
using Wox.Infrastructure.Hotkey;
using Wox.Plugin;
namespace Wox
{
public partial class HotkeyControl : UserControl
{
public HotkeyModel CurrentHotkey { get; private set; }
public bool CurrentHotkeyAvailable { get; private set; }
public event EventHandler HotkeyChanged;
protected virtual void OnHotkeyChanged()
{
EventHandler handler = HotkeyChanged;
if (handler != null) handler(this, EventArgs.Empty);
}
public HotkeyControl()
{
InitializeComponent();
}
void TbHotkey_OnPreviewKeyDown(object sender, KeyEventArgs e)
{
e.Handled = true;
tbMsg.Visibility = Visibility.Hidden;
//when alt is pressed, the real key should be e.SystemKey
Key key = (e.Key == Key.System ? e.SystemKey : e.Key);
SpecialKeyState specialKeyState = GlobalHotkey.Instance.CheckModifiers();
var hotkeyModel = new HotkeyModel(
specialKeyState.AltPressed,
specialKeyState.ShiftPressed,
specialKeyState.WinPressed,
specialKeyState.CtrlPressed,
key);
var hotkeyString = hotkeyModel.ToString();
if (hotkeyString == tbHotkey.Text)
{
return;
}
Dispatcher.InvokeAsync(async () =>
{
await Task.Delay(500);
SetHotkey(hotkeyModel);
});
}
public void SetHotkey(HotkeyModel keyModel, bool triggerValidate = true)
{
CurrentHotkey = keyModel;
tbHotkey.Text = CurrentHotkey.ToString();
tbHotkey.Select(tbHotkey.Text.Length, 0);
if (triggerValidate)
{
CurrentHotkeyAvailable = CheckHotkeyAvailability();
if (!CurrentHotkeyAvailable)
{
tbMsg.Foreground = new SolidColorBrush(Colors.Red);
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("hotkeyUnavailable");
}
else
{
tbMsg.Foreground = new SolidColorBrush(Colors.Green);
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("success");
}
tbMsg.Visibility = Visibility.Visible;
OnHotkeyChanged();
}
}
public void SetHotkey(string keyStr, bool triggerValidate = true)
{
SetHotkey(new HotkeyModel(keyStr), triggerValidate);
}
private bool CheckHotkeyAvailability()
{
try
{
HotkeyManager.Current.AddOrReplace("HotkeyAvailabilityTest", CurrentHotkey.CharKey, CurrentHotkey.ModifierKeys, (sender, e) => { });
return true;
}
catch
{
}
finally
{
HotkeyManager.Current.Remove("HotkeyAvailabilityTest");
}
return false;
}
public new bool IsFocused
{
get { return tbHotkey.IsFocused; }
}
}
}

View File

@@ -59,7 +59,6 @@
<PackageReference Include="Mages" Version="1.6.0" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.19" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NHotkey.Wpf" Version="2.0.1" />
<PackageReference Include="NuGet.CommandLine" Version="5.5.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -52,7 +52,7 @@ namespace PowerLauncher
var openPowerlauncher = ConvertHotkey(overloadSettings.properties.open_powerlauncher);
if (_settings.Hotkey != openPowerlauncher)
{
_settings.Hotkey = ConvertHotkey(overloadSettings.properties.open_powerlauncher);
_settings.Hotkey = openPowerlauncher;
}
var shell = PluginManager.AllPlugins.Find(pp => pp.Metadata.Name == "Shell");

View File

@@ -1,107 +0,0 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Wox.Plugin;
namespace Wox.Infrastructure.Hotkey
{
/// <summary>
/// Listens keyboard globally.
/// <remarks>Uses WH_KEYBOARD_LL.</remarks>
/// </summary>
public class GlobalHotkey : IDisposable
{
private static GlobalHotkey instance;
private InterceptKeys.LowLevelKeyboardProc hookedLowLevelKeyboardProc;
private IntPtr hookId = IntPtr.Zero;
public delegate bool KeyboardCallback(KeyEvent keyEvent, int vkCode, SpecialKeyState state);
public event KeyboardCallback hookedKeyboardCallback;
//Modifier key constants
private const int VK_SHIFT = 0x10;
private const int VK_CONTROL = 0x11;
private const int VK_ALT = 0x12;
private const int VK_WIN = 91;
public static GlobalHotkey Instance
{
get
{
if (instance == null)
{
instance = new GlobalHotkey();
}
return instance;
}
}
private GlobalHotkey()
{
// We have to store the LowLevelKeyboardProc, so that it is not garbage collected runtime
hookedLowLevelKeyboardProc = LowLevelKeyboardProc;
// Set the hook
hookId = InterceptKeys.SetHook(hookedLowLevelKeyboardProc);
}
public SpecialKeyState CheckModifiers()
{
SpecialKeyState state = new SpecialKeyState();
if ((InterceptKeys.GetKeyState(VK_SHIFT) & 0x8000) != 0)
{
//SHIFT is pressed
state.ShiftPressed = true;
}
if ((InterceptKeys.GetKeyState(VK_CONTROL) & 0x8000) != 0)
{
//CONTROL is pressed
state.CtrlPressed = true;
}
if ((InterceptKeys.GetKeyState(VK_ALT) & 0x8000) != 0)
{
//ALT is pressed
state.AltPressed = true;
}
if ((InterceptKeys.GetKeyState(VK_WIN) & 0x8000) != 0)
{
//WIN is pressed
state.WinPressed = true;
}
return state;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam)
{
bool continues = true;
if (nCode >= 0)
{
if (wParam.ToUInt32() == (int)KeyEvent.WM_KEYDOWN ||
wParam.ToUInt32() == (int)KeyEvent.WM_KEYUP ||
wParam.ToUInt32() == (int)KeyEvent.WM_SYSKEYDOWN ||
wParam.ToUInt32() == (int)KeyEvent.WM_SYSKEYUP)
{
if (hookedKeyboardCallback != null)
continues = hookedKeyboardCallback((KeyEvent)wParam.ToUInt32(), Marshal.ReadInt32(lParam), CheckModifiers());
}
}
if (continues)
{
return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam);
}
return (IntPtr)1;
}
~GlobalHotkey()
{
Dispose();
}
public void Dispose()
{
InterceptKeys.UnhookWindowsHookEx(hookId);
}
}
}

View File

@@ -45,6 +45,11 @@ namespace Wox.Infrastructure.Hotkey
}
}
public HotkeyModel()
{
}
public HotkeyModel(string hotkeyString)
{
Parse(hotkeyString);

View File

@@ -1,38 +0,0 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Wox.Infrastructure.Hotkey
{
internal static class InterceptKeys
{
public delegate IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam);
private const int WH_KEYBOARD_LL = 13;
public static IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
}
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, UIntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
public static extern short GetKeyState(int keyCode);
}
}

View File

@@ -8,15 +8,6 @@ namespace Wox.Plugin
public delegate void ResultItemDropEventHandler(Result result, IDataObject dropObject, DragEventArgs e);
/// <summary>
/// Global keyboard events
/// </summary>
/// <param name="keyevent">WM_KEYDOWN = 256,WM_KEYUP = 257,WM_SYSKEYUP = 261,WM_SYSKEYDOWN = 260</param>
/// <param name="vkcode"></param>
/// <param name="state"></param>
/// <returns>return true to continue handling, return false to intercept system handling</returns>
public delegate bool WoxGlobalKeyboardEventHandler(int keyevent, int vkcode, SpecialKeyState state);
public class WoxKeyDownEventArgs
{
public string Query { get; set; }

View File

@@ -114,11 +114,5 @@ namespace Wox.Plugin
/// </summary>
/// <returns></returns>
List<PluginPair> GetAllPlugins();
/// <summary>
/// Fired after global keyboard events
/// if you want to hook something like Ctrl+R, you should use this event
/// </summary>
event WoxGlobalKeyboardEventHandler GlobalKeyboardEvent;
}
}

View File

@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Input;
using Wox.Plugin;
namespace Wox.Helper
{
class KeyboardHelper
{
public static SpecialKeyState CheckModifiers()
{
SpecialKeyState state = new SpecialKeyState();
if ((Keyboard.GetKeyStates(Key.LeftShift) & KeyStates.Down) > 0 ||
(Keyboard.GetKeyStates(Key.RightShift) & KeyStates.Down) > 0)
{
state.ShiftPressed = true;
}
if ((Keyboard.GetKeyStates(Key.LWin) & KeyStates.Down) > 0 ||
(Keyboard.GetKeyStates(Key.RWin) & KeyStates.Down) > 0)
{
state.WinPressed = true;
}
if ((Keyboard.GetKeyStates(Key.LeftCtrl) & KeyStates.Down) > 0 ||
(Keyboard.GetKeyStates(Key.RightCtrl) & KeyStates.Down) > 0)
{
state.CtrlPressed = true;
}
if ((Keyboard.GetKeyStates(Key.LeftAlt) & KeyStates.Down) > 0 ||
(Keyboard.GetKeyStates(Key.RightAlt) & KeyStates.Down) > 0)
{
state.AltPressed = true;
}
return state;
}
}
}

View File

@@ -1,19 +0,0 @@
<UserControl x:Class="Wox.HotkeyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:input="clr-namespace:System.Windows.Input;assembly=PresentationCore"
mc:Ignorable="d"
Height="24"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition Width="120" />
</Grid.ColumnDefinitions>
<TextBox x:Name="tbHotkey" TabIndex="100" VerticalContentAlignment="Center" Grid.Column="0"
PreviewKeyDown="TbHotkey_OnPreviewKeyDown" input:InputMethod.IsInputMethodEnabled="False"/>
<TextBlock x:Name="tbMsg" Visibility="Hidden" Margin="5 0 0 0" VerticalAlignment="Center" Grid.Column="1" />
</Grid>
</UserControl>

View File

@@ -1,117 +0,0 @@
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using NHotkey.Wpf;
using Wox.Core.Resource;
using Wox.Infrastructure.Hotkey;
using Wox.Plugin;
namespace Wox
{
public partial class HotkeyControl : UserControl
{
public HotkeyModel CurrentHotkey { get; private set; }
public bool CurrentHotkeyAvailable { get; private set; }
public event EventHandler HotkeyChanged;
protected virtual void OnHotkeyChanged()
{
EventHandler handler = HotkeyChanged;
if (handler != null) handler(this, EventArgs.Empty);
}
public HotkeyControl()
{
InitializeComponent();
}
void TbHotkey_OnPreviewKeyDown(object sender, KeyEventArgs e)
{
e.Handled = true;
tbMsg.Visibility = Visibility.Hidden;
//when alt is pressed, the real key should be e.SystemKey
Key key = (e.Key == Key.System ? e.SystemKey : e.Key);
SpecialKeyState specialKeyState = GlobalHotkey.Instance.CheckModifiers();
var hotkeyModel = new HotkeyModel(
specialKeyState.AltPressed,
specialKeyState.ShiftPressed,
specialKeyState.WinPressed,
specialKeyState.CtrlPressed,
key);
var hotkeyString = hotkeyModel.ToString();
if (hotkeyString == tbHotkey.Text)
{
return;
}
Dispatcher.InvokeAsync(async () =>
{
await Task.Delay(500);
SetHotkey(hotkeyModel);
});
}
public void SetHotkey(HotkeyModel keyModel, bool triggerValidate = true)
{
CurrentHotkey = keyModel;
tbHotkey.Text = CurrentHotkey.ToString();
tbHotkey.Select(tbHotkey.Text.Length, 0);
if (triggerValidate)
{
CurrentHotkeyAvailable = CheckHotkeyAvailability();
if (!CurrentHotkeyAvailable)
{
tbMsg.Foreground = new SolidColorBrush(Colors.Red);
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("hotkeyUnavailable");
}
else
{
tbMsg.Foreground = new SolidColorBrush(Colors.Green);
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("success");
}
tbMsg.Visibility = Visibility.Visible;
OnHotkeyChanged();
}
}
public void SetHotkey(string keyStr, bool triggerValidate = true)
{
SetHotkey(new HotkeyModel(keyStr), triggerValidate);
}
private bool CheckHotkeyAvailability()
{
try
{
HotkeyManager.Current.AddOrReplace("HotkeyAvailabilityTest", CurrentHotkey.CharKey, CurrentHotkey.ModifierKeys, (sender, e) => { });
return true;
}
catch
{
}
finally
{
HotkeyManager.Current.Remove("HotkeyAvailabilityTest");
}
return false;
}
public new bool IsFocused
{
get { return tbHotkey.IsFocused; }
}
}
}

View File

@@ -8,7 +8,6 @@ using Wox.Core.Plugin;
using Wox.Core.Resource;
using Wox.Helper;
using Wox.Infrastructure;
using Wox.Infrastructure.Hotkey;
using Wox.Infrastructure.Image;
using Wox.Plugin;
using Wox.ViewModel;
@@ -28,7 +27,6 @@ namespace Wox
_settingsVM = settingsVM;
_mainVM = mainVM;
_alphabet = alphabet;
GlobalHotkey.Instance.hookedKeyboardCallback += KListener_hookedKeyboardCallback;
WebRequest.RegisterPrefix("data", new DataWebRequestFactory());
}
@@ -124,7 +122,6 @@ namespace Wox
return PluginManager.AllPlugins.ToList();
}
public event WoxGlobalKeyboardEventHandler GlobalKeyboardEvent;
[Obsolete("This will be removed in Wox 1.3")]
public void PushResults(Query query, PluginMetadata plugin, List<Result> results)
@@ -145,14 +142,6 @@ namespace Wox
#region Private Methods
private bool KListener_hookedKeyboardCallback(KeyEvent keyevent, int vkcode, SpecialKeyState state)
{
if (GlobalKeyboardEvent != null)
{
return GlobalKeyboardEvent((int)keyevent, vkcode, state);
}
return true;
}
#endregion
}
}

View File

@@ -7,8 +7,6 @@ using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using NHotkey;
using NHotkey.Wpf;
using Wox.Core.Plugin;
using Wox.Core.Resource;
using Wox.Helper;
@@ -20,6 +18,7 @@ using Wox.Plugin;
using Microsoft.PowerLauncher.Telemetry;
using Wox.Storage;
using Microsoft.PowerToys.Telemetry;
using interop;
namespace Wox.ViewModel
{
@@ -42,7 +41,8 @@ namespace Wox.ViewModel
private CancellationTokenSource _updateSource;
private CancellationToken _updateToken;
private bool _saved;
private HotkeyManager _hotkeyManager;
private ushort _hotkeyHandle;
private readonly Internationalization _translator = InternationalizationManager.Instance;
#endregion
@@ -51,6 +51,7 @@ namespace Wox.ViewModel
public MainViewModel(Settings settings)
{
_hotkeyManager = new HotkeyManager();
_saved = false;
_queryTextBeforeLeaveResults = "";
_lastQuery = new Query();
@@ -80,7 +81,7 @@ namespace Wox.ViewModel
{
if (_settings.PreviousHotkey != "")
{
RemoveHotkey(_settings.PreviousHotkey);
_hotkeyManager.UnregisterHotkey(_hotkeyHandle);
}
if (_settings.Hotkey != "")
@@ -111,6 +112,13 @@ namespace Wox.ViewModel
}
}
~MainViewModel()
{
if (_hotkeyHandle != 0)
{
_hotkeyManager.UnregisterHotkey(_hotkeyHandle);
}
}
private void InitializeKeyCommands()
{
@@ -186,11 +194,11 @@ namespace Wox.ViewModel
{
MainWindowVisibility = Visibility.Collapsed;
Task.Run(() =>
Application.Current.Dispatcher.Invoke(() =>
{
result.Action(new ActionContext
{
SpecialKeyState = GlobalHotkey.Instance.CheckModifiers()
SpecialKeyState = KeyboardHelper.CheckModifiers()
});
});
@@ -524,18 +532,25 @@ namespace Wox.ViewModel
}
#region Hotkey
private void SetHotkey(string hotkeyStr, EventHandler<HotkeyEventArgs> action)
private void SetHotkey(string hotkeyStr, HotkeyCallback action)
{
var hotkey = new HotkeyModel(hotkeyStr);
SetHotkey(hotkey, action);
}
private void SetHotkey(HotkeyModel hotkey, EventHandler<HotkeyEventArgs> action)
private void SetHotkey(HotkeyModel hotkeyModel, HotkeyCallback action)
{
string hotkeyStr = hotkey.ToString();
string hotkeyStr = hotkeyModel.ToString();
try
{
HotkeyManager.Current.AddOrReplace(hotkeyStr, hotkey.CharKey, hotkey.ModifierKeys, action);
Hotkey hotkey = new Hotkey();
hotkey.Alt = hotkeyModel.Alt;
hotkey.Shift = hotkeyModel.Shift;
hotkey.Ctrl = hotkeyModel.Ctrl;
hotkey.Win = hotkeyModel.Win;
hotkey.Key = (byte) KeyInterop.VirtualKeyFromKey(hotkeyModel.CharKey);
_hotkeyHandle = _hotkeyManager.RegisterHotkey(hotkey, action);
}
catch (Exception)
{
@@ -545,14 +560,6 @@ namespace Wox.ViewModel
}
}
public void RemoveHotkey(string hotkeyStr)
{
if (!string.IsNullOrEmpty(hotkeyStr))
{
HotkeyManager.Current.Remove(hotkeyStr);
}
}
/// <summary>
/// Checks if Wox should ignore any hotkeys
/// </summary>
@@ -572,7 +579,7 @@ namespace Wox.ViewModel
if (_settings.CustomPluginHotkeys == null) return;
foreach (CustomPluginHotkey hotkey in _settings.CustomPluginHotkeys)
{
SetHotkey(hotkey.Hotkey, (s, e) =>
SetHotkey(hotkey.Hotkey, () =>
{
if (ShouldIgnoreHotkeys()) return;
MainWindowVisibility = Visibility.Visible;
@@ -581,7 +588,9 @@ namespace Wox.ViewModel
}
}
private void OnHotkey(object sender, HotkeyEventArgs e)
private void OnHotkey()
{
Application.Current.Dispatcher.Invoke(() =>
{
if (!ShouldIgnoreHotkeys())
{
@@ -604,8 +613,8 @@ namespace Wox.ViewModel
}
ToggleWox();
e.Handled = true;
}
});
}
private void ToggleWox()

View File

@@ -7,11 +7,10 @@ using System.Windows.Input;
using System.Windows.Media;
using Wox.Core.Plugin;
using Wox.Infrastructure;
using Wox.Infrastructure.Hotkey;
using Wox.Infrastructure.Image;
using Wox.Infrastructure.Logger;
using Wox.Plugin;
using Wox.Helper;
namespace Wox.ViewModel
{
@@ -128,7 +127,7 @@ namespace Wox.ViewModel
{
bool hideWindow = r.Action != null && r.Action(new ActionContext
{
SpecialKeyState = GlobalHotkey.Instance.CheckModifiers()
SpecialKeyState = KeyboardHelper.CheckModifiers()
});
if (hideWindow)

View File

@@ -51,7 +51,6 @@
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" />
<PackageReference Include="Mages" Version="1.6.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NHotkey.Wpf" Version="2.0.1" />
<PackageReference Include="NuGet.CommandLine" Version="5.5.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -66,6 +65,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\interop\interop.vcxproj" />
<ProjectReference Include="..\PowerLauncher.Telemetry\PowerLauncher.Telemetry.csproj" />
<ProjectReference Include="..\Wox.Core\Wox.Core.csproj" />
<ProjectReference Include="..\Wox.Infrastructure\Wox.Infrastructure.csproj" />