-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
restore global keyboard capture and override
- Loading branch information
Showing
21 changed files
with
509 additions
and
190 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
using System; | ||
using System.ComponentModel; | ||
using System.Diagnostics; | ||
using System.Runtime.InteropServices; | ||
using KeyToJoy.Input.LowLevel; | ||
|
||
namespace KeyToJoy.Input | ||
{ | ||
// Based on these sources: | ||
// - https://gist.github.com/Stasonix/3181083 | ||
// - https://stackoverflow.com/a/34384189 | ||
partial class GlobalInputHook : IDisposable | ||
{ | ||
public event EventHandler<GlobalKeyboardHookEventArgs> KeyboardInputEvent; | ||
public event EventHandler<GlobalMouseHookEventArgs> MouseInputEvent; | ||
|
||
private IntPtr windowsHookHandle; | ||
private IntPtr user32LibraryHandle; | ||
private HookProc hookProc; | ||
|
||
delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam); | ||
|
||
[DllImport("kernel32.dll")] | ||
private static extern IntPtr LoadLibrary(string lpFileName); | ||
|
||
[DllImport("kernel32.dll", CharSet = CharSet.Auto)] | ||
private static extern bool FreeLibrary(IntPtr hModule); | ||
|
||
/// <summary> | ||
/// The SetWindowsHookEx function installs an application-defined hook procedure into a hook chain. | ||
/// You would install a hook procedure to monitor the system for certain types of events. These events are | ||
/// associated either with a specific thread or with all threads in the same desktop as the calling thread. | ||
/// </summary> | ||
/// <param name="idHook">hook type</param> | ||
/// <param name="lpfn">hook procedure</param> | ||
/// <param name="hMod">handle to application instance</param> | ||
/// <param name="dwThreadId">thread identifier</param> | ||
/// <returns>If the function succeeds, the return value is the handle to the hook procedure.</returns> | ||
[DllImport("USER32", SetLastError = true)] | ||
static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId); | ||
|
||
/// <summary> | ||
/// The UnhookWindowsHookEx function removes a hook procedure installed in a hook chain by the SetWindowsHookEx function. | ||
/// </summary> | ||
/// <param name="hhk">handle to hook procedure</param> | ||
/// <returns>If the function succeeds, the return value is true.</returns> | ||
[DllImport("USER32", SetLastError = true)] | ||
public static extern bool UnhookWindowsHookEx(IntPtr hHook); | ||
|
||
/// <summary> | ||
/// The CallNextHookEx function passes the hook information to the next hook procedure in the current hook chain. | ||
/// A hook procedure can call this function either before or after processing the hook information. | ||
/// </summary> | ||
/// <param name="hHook">handle to current hook</param> | ||
/// <param name="code">hook code passed to hook procedure</param> | ||
/// <param name="wParam">value passed to hook procedure</param> | ||
/// <param name="lParam">value passed to hook procedure</param> | ||
/// <returns>If the function succeeds, the return value is true.</returns> | ||
[DllImport("USER32", SetLastError = true)] | ||
static extern IntPtr CallNextHookEx(IntPtr hHook, int code, IntPtr wParam, IntPtr lParam); | ||
|
||
public GlobalInputHook() | ||
{ | ||
windowsHookHandle = IntPtr.Zero; | ||
user32LibraryHandle = IntPtr.Zero; | ||
hookProc = LowLevelInputHook; // we must keep alive hookProc, because GC is not aware about SetWindowsHookEx behaviour. | ||
|
||
user32LibraryHandle = LoadLibrary("User32"); | ||
if (user32LibraryHandle == IntPtr.Zero) | ||
{ | ||
int errorCode = Marshal.GetLastWin32Error(); | ||
throw new Win32Exception(errorCode, $"Failed to load library 'User32.dll'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}."); | ||
} | ||
|
||
foreach (var windowsHook in new int[] { WH_KEYBOARD_LL, WH_MOUSE_LL }) | ||
{ | ||
windowsHookHandle = SetWindowsHookEx(windowsHook, hookProc, user32LibraryHandle, 0); | ||
if (windowsHookHandle == IntPtr.Zero) | ||
{ | ||
int errorCode = Marshal.GetLastWin32Error(); | ||
throw new Win32Exception(errorCode, $"Failed to adjust input hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}."); | ||
} | ||
} | ||
} | ||
|
||
protected virtual void Dispose(bool disposing) | ||
{ | ||
if (disposing) | ||
{ | ||
// because we can unhook only in the same thread, not in garbage collector thread | ||
if (windowsHookHandle != IntPtr.Zero) | ||
{ | ||
if (!UnhookWindowsHookEx(windowsHookHandle)) | ||
{ | ||
int errorCode = Marshal.GetLastWin32Error(); | ||
throw new Win32Exception(errorCode, $"Failed to remove input hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}."); | ||
} | ||
windowsHookHandle = IntPtr.Zero; | ||
|
||
// ReSharper disable once DelegateSubtraction | ||
hookProc -= LowLevelInputHook; | ||
} | ||
} | ||
|
||
if (user32LibraryHandle != IntPtr.Zero) | ||
{ | ||
if (!FreeLibrary(user32LibraryHandle)) // reduces reference to library by 1. | ||
{ | ||
int errorCode = Marshal.GetLastWin32Error(); | ||
throw new Win32Exception(errorCode, $"Failed to unload library 'User32.dll'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}."); | ||
} | ||
user32LibraryHandle = IntPtr.Zero; | ||
} | ||
} | ||
|
||
~GlobalInputHook() | ||
{ | ||
Dispose(false); | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
Dispose(true); | ||
GC.SuppressFinalize(this); | ||
} | ||
|
||
public const int WH_KEYBOARD_LL = 13; | ||
public const int WH_MOUSE_LL = 14; | ||
|
||
public IntPtr LowLevelInputHook(int nCode, IntPtr wParam, IntPtr lParam) | ||
{ | ||
var isInputHandled = false; | ||
var wparamTyped = wParam.ToInt32(); | ||
|
||
if (Enum.IsDefined(typeof(KeyboardState), wparamTyped)) | ||
{ | ||
object o = Marshal.PtrToStructure(lParam, typeof(LowLevelKeyboardInputEvent)); | ||
LowLevelKeyboardInputEvent p = (LowLevelKeyboardInputEvent)o; | ||
|
||
var eventArguments = new GlobalKeyboardHookEventArgs(p, (KeyboardState)wparamTyped); | ||
|
||
EventHandler<GlobalKeyboardHookEventArgs> handler = KeyboardInputEvent; | ||
handler?.Invoke(this, eventArguments); | ||
|
||
isInputHandled = eventArguments.Handled; | ||
} | ||
else if (Enum.IsDefined(typeof(MouseState), wparamTyped)) | ||
{ | ||
object o = Marshal.PtrToStructure(lParam, typeof(LowLevelMouseInputEvent)); | ||
LowLevelMouseInputEvent p = (LowLevelMouseInputEvent)o; | ||
|
||
var eventArguments = new GlobalMouseHookEventArgs(p, (MouseState)wparamTyped); | ||
|
||
EventHandler<GlobalMouseHookEventArgs> handler = MouseInputEvent; | ||
handler?.Invoke(this, eventArguments); | ||
|
||
isInputHandled = eventArguments.Handled; | ||
} | ||
|
||
return isInputHandled ? (IntPtr)1 : CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
using System.ComponentModel; | ||
|
||
namespace KeyToJoy.Input.LowLevel | ||
{ | ||
// Source: https://stackoverflow.com/a/34384189 | ||
internal class GlobalKeyboardHookEventArgs : HandledEventArgs | ||
{ | ||
public KeyboardState KeyboardState { get; private set; } | ||
public LowLevelKeyboardInputEvent KeyboardData { get; private set; } | ||
|
||
public GlobalKeyboardHookEventArgs( | ||
LowLevelKeyboardInputEvent keyboardData, | ||
KeyboardState keyboardState) | ||
{ | ||
KeyboardData = keyboardData; | ||
KeyboardState = keyboardState; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
| ||
|
||
using System.ComponentModel; | ||
|
||
namespace KeyToJoy.Input.LowLevel | ||
{ | ||
// Source: https://stackoverflow.com/a/34384189 | ||
internal class GlobalMouseHookEventArgs : HandledEventArgs | ||
{ | ||
public MouseState MouseState { get; private set; } | ||
public LowLevelMouseInputEvent MouseData { get; private set; } | ||
|
||
public GlobalMouseHookEventArgs( | ||
LowLevelMouseInputEvent mouseData, | ||
MouseState mouseState) | ||
{ | ||
MouseData = mouseData; | ||
MouseState = mouseState; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace KeyToJoy.Input.LowLevel | ||
{ | ||
// Source: https://stackoverflow.com/a/34384189 | ||
public enum KeyboardState | ||
{ | ||
KeyDown = 0x0100, | ||
KeyUp = 0x0101, | ||
SysKeyDown = 0x0104, | ||
SysKeyUp = 0x0105 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
using System; | ||
using System.Runtime.InteropServices; | ||
|
||
namespace KeyToJoy.Input.LowLevel | ||
{ | ||
// Source: https://stackoverflow.com/a/34384189 | ||
[StructLayout(LayoutKind.Sequential)] | ||
public struct LowLevelKeyboardInputEvent | ||
{ | ||
/// <summary> | ||
/// A virtual-key code. The code must be a value in the range 1 to 254. | ||
/// </summary> | ||
public int VirtualCode; | ||
|
||
/// <summary> | ||
/// A hardware scan code for the key. | ||
/// </summary> | ||
public int HardwareScanCode; | ||
|
||
/// <summary> | ||
/// The extended-key flag, event-injected Flags, context code, and transition-state flag. This member is specified as follows. An application can use the following values to test the keystroke Flags. Testing LLKHF_INJECTED (bit 4) will tell you whether the event was injected. If it was, then testing LLKHF_LOWER_IL_INJECTED (bit 1) will tell you whether or not the event was injected from a process running at lower integrity level. | ||
/// | ||
/// - LLKHF_EXTENDED | ||
/// (KF_EXTENDED >> 8) | ||
/// Test the extended-key flag. | ||
/// | ||
/// - LLKHF_LOWER_IL_INJECTED | ||
/// 0x00000002 | ||
/// Test the event-injected (from a process running at lower integrity level) flag. | ||
/// | ||
/// - LLKHF_INJECTED | ||
/// 0x00000010 | ||
/// Test the event-injected (from any process) flag. | ||
/// | ||
/// - LLKHF_ALTDOWN | ||
/// (KF_ALTDOWN >> 8) | ||
/// Test the context code. | ||
/// | ||
/// - LLKHF_UP | ||
/// (KF_UP >> 8) | ||
/// Test the transition-state flag. | ||
/// </summary> | ||
public int Flags; | ||
|
||
/// <summary> | ||
/// The time stamp stamp for this message, equivalent to what GetMessageTime would return for this message. | ||
/// </summary> | ||
public int TimeStamp; | ||
|
||
/// <summary> | ||
/// Additional information associated with the message. | ||
/// </summary> | ||
public IntPtr AdditionalInformation; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
using System; | ||
using System.Runtime.InteropServices; | ||
|
||
namespace KeyToJoy.Input.LowLevel | ||
{ | ||
// Source: https://stackoverflow.com/a/34384189 | ||
[StructLayout(LayoutKind.Sequential)] | ||
public struct LowLevelMouseInputEvent | ||
{ | ||
/// <summary> | ||
/// The x- and y-coordinates of the cursor, in per-monitor-aware screen coordinates. | ||
/// </summary> | ||
public Point Position; | ||
|
||
/// <summary> | ||
/// If the message is WM_MOUSEWHEEL, the high-order word of this member is the wheel delta. The low-order word is reserved. A positive value indicates that the wheel was rotated forward, away from the user; a negative value indicates that the wheel was rotated backward, toward the user. One wheel click is defined as WHEEL_DELTA, which is 120. | ||
/// If the message is WM_XBUTTONDOWN, WM_XBUTTONUP, WM_XBUTTONDBLCLK, WM_NCXBUTTONDOWN, WM_NCXBUTTONUP, or WM_NCXBUTTONDBLCLK, the high-order word specifies which X button was pressed or released, and the low-order word is reserved.This value can be one or more of the following values.Otherwise, mouseData is not used. | ||
/// </summary> | ||
public int MouseData; | ||
|
||
/// <summary> | ||
/// The extended-key flag, event-injected Flags, context code, and transition-state flag. This member is specified as follows. An application can use the following values to test the keystroke Flags. Testing LLKHF_INJECTED (bit 4) will tell you whether the event was injected. If it was, then testing LLKHF_LOWER_IL_INJECTED (bit 1) will tell you whether or not the event was injected from a process running at lower integrity level. | ||
/// </summary> | ||
public int Flags; | ||
|
||
/// <summary> | ||
/// The time stamp stamp for this message, equivalent to what GetMessageTime would return for this message. | ||
/// </summary> | ||
public int TimeStamp; | ||
|
||
/// <summary> | ||
/// Additional information associated with the message. | ||
/// </summary> | ||
public IntPtr AdditionalInformation; | ||
} | ||
} |
Oops, something went wrong.