mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 03:37:59 +01:00
[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:
committed by
GitHub
parent
fa7e4cc817
commit
670033c4da
@@ -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" />
|
||||
|
||||
133
src/common/interop/HotkeyManager.cpp
Normal file
133
src/common/interop/HotkeyManager.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
57
src/common/interop/HotkeyManager.h
Normal file
57
src/common/interop/HotkeyManager.h
Normal 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);
|
||||
};
|
||||
}
|
||||
96
src/common/interop/KeyboardHook.cpp
Normal file
96
src/common/interop/KeyboardHook.cpp
Normal 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);
|
||||
}
|
||||
49
src/common/interop/KeyboardHook.h
Normal file
49
src/common/interop/KeyboardHook.h
Normal 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);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 + ");
|
||||
}
|
||||
|
||||
var localKey = Helper.GetKeyName((uint) Code);
|
||||
output.Append(localKey);
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private void HotkeyTextBox_KeyDown(object sender, KeyRoutedEventArgs e)
|
||||
private async void Hotkey_KeyDown(int key)
|
||||
{
|
||||
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)
|
||||
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
return;
|
||||
}
|
||||
KeyEventHandler(key, true, key, Lib.Utilities.Helper.GetKeyName((uint)key));
|
||||
if (internalSettings.Code > 0)
|
||||
{
|
||||
lastValidSettings = internalSettings.Clone();
|
||||
HotkeyTextBox.Text = lastValidSettings.ToString();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (e.Key == Windows.System.VirtualKey.Escape)
|
||||
private async void Hotkey_KeyUp(int key)
|
||||
{
|
||||
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
|
||||
{
|
||||
internalSettings = new HotkeySettings();
|
||||
HotkeySettings = new HotkeySettings();
|
||||
return;
|
||||
}
|
||||
KeyEventHandler(key, false, 0, string.Empty);
|
||||
});
|
||||
}
|
||||
|
||||
var settings = new HotkeySettings();
|
||||
private bool Hotkey_IsActive()
|
||||
{
|
||||
return _isActive;
|
||||
}
|
||||
|
||||
// Display HotKey value
|
||||
if (IsDown(Windows.System.VirtualKey.LeftWindows) ||
|
||||
IsDown(Windows.System.VirtualKey.RightWindows))
|
||||
{
|
||||
settings.Win = true;
|
||||
}
|
||||
|
||||
if (IsDown(Windows.System.VirtualKey.Control))
|
||||
{
|
||||
settings.Ctrl = true;
|
||||
}
|
||||
|
||||
if (IsDown(Windows.System.VirtualKey.Menu))
|
||||
{
|
||||
settings.Alt = true;
|
||||
}
|
||||
|
||||
if (IsDown(Windows.System.VirtualKey.Shift))
|
||||
{
|
||||
settings.Shift = true;
|
||||
}
|
||||
|
||||
settings.Key = Lib.Utilities.Helper.GetKeyName((uint)e.Key);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,11 @@ namespace Wox.Infrastructure.Hotkey
|
||||
}
|
||||
}
|
||||
|
||||
public HotkeyModel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public HotkeyModel(string hotkeyString)
|
||||
{
|
||||
Parse(hotkeyString);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
37
src/modules/launcher/Wox/Helper/KeyboardHelper.cs
Normal file
37
src/modules/launcher/Wox/Helper/KeyboardHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,31 +588,33 @@ namespace Wox.ViewModel
|
||||
}
|
||||
}
|
||||
|
||||
private void OnHotkey(object sender, HotkeyEventArgs e)
|
||||
private void OnHotkey()
|
||||
{
|
||||
if (!ShouldIgnoreHotkeys())
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (!ShouldIgnoreHotkeys())
|
||||
{
|
||||
|
||||
if (_settings.LastQueryMode == LastQueryMode.Empty)
|
||||
{
|
||||
ChangeQueryText(string.Empty);
|
||||
}
|
||||
else if (_settings.LastQueryMode == LastQueryMode.Preserved)
|
||||
{
|
||||
LastQuerySelected = true;
|
||||
}
|
||||
else if (_settings.LastQueryMode == LastQueryMode.Selected)
|
||||
{
|
||||
LastQuerySelected = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"wrong LastQueryMode: <{_settings.LastQueryMode}>");
|
||||
}
|
||||
if (_settings.LastQueryMode == LastQueryMode.Empty)
|
||||
{
|
||||
ChangeQueryText(string.Empty);
|
||||
}
|
||||
else if (_settings.LastQueryMode == LastQueryMode.Preserved)
|
||||
{
|
||||
LastQuerySelected = true;
|
||||
}
|
||||
else if (_settings.LastQueryMode == LastQueryMode.Selected)
|
||||
{
|
||||
LastQuerySelected = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"wrong LastQueryMode: <{_settings.LastQueryMode}>");
|
||||
}
|
||||
|
||||
ToggleWox();
|
||||
e.Handled = true;
|
||||
}
|
||||
ToggleWox();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void ToggleWox()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user