mirror of
https://github.com/hawkeye-stan/msfs-popout-panel-manager.git
synced 2024-11-25 15:20:10 +00:00
174 lines
6.7 KiB
C#
174 lines
6.7 KiB
C#
using System;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
using System.Windows.Input;
|
|
|
|
namespace MSFSPopoutPanelManager.Orchestration
|
|
{
|
|
public class GlobalKeyboardHookEventArgs : HandledEventArgs
|
|
{
|
|
public GlobalKeyboardHook.KeyboardState KeyboardState { get; private set; }
|
|
public GlobalKeyboardHook.LowLevelKeyboardInputEvent KeyboardData { get; private set; }
|
|
|
|
public GlobalKeyboardHookEventArgs(
|
|
GlobalKeyboardHook.LowLevelKeyboardInputEvent keyboardData,
|
|
GlobalKeyboardHook.KeyboardState keyboardState)
|
|
{
|
|
KeyboardData = keyboardData;
|
|
KeyboardState = keyboardState;
|
|
}
|
|
}
|
|
|
|
public class GlobalKeyboardHook : IDisposable
|
|
{
|
|
public event EventHandler<GlobalKeyboardHookEventArgs> OnKeyboardPressed;
|
|
|
|
public GlobalKeyboardHook()
|
|
{
|
|
_windowsHookHandle = IntPtr.Zero;
|
|
_user32LibraryHandle = IntPtr.Zero;
|
|
_hookProc = LowLevelKeyboardProc; // 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}.");
|
|
}
|
|
|
|
|
|
|
|
_windowsHookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, _hookProc, _user32LibraryHandle, 0);
|
|
if (_windowsHookHandle == IntPtr.Zero)
|
|
{
|
|
int errorCode = Marshal.GetLastWin32Error();
|
|
throw new Win32Exception(errorCode, $"Failed to adjust keyboard 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 keyboard hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
|
|
}
|
|
_windowsHookHandle = IntPtr.Zero;
|
|
|
|
// ReSharper disable once DelegateSubtraction
|
|
_hookProc -= LowLevelKeyboardProc;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
~GlobalKeyboardHook()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
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);
|
|
|
|
[DllImport("USER32", SetLastError = true)]
|
|
static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
|
|
|
|
[DllImport("USER32", SetLastError = true)]
|
|
public static extern bool UnhookWindowsHookEx(IntPtr hHook);
|
|
|
|
[DllImport("USER32", SetLastError = true)]
|
|
static extern IntPtr CallNextHookEx(IntPtr hHook, int code, IntPtr wParam, IntPtr lParam);
|
|
|
|
[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.
|
|
/// </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;
|
|
|
|
public Key Key => KeyInterop.KeyFromVirtualKey(VirtualCode);
|
|
}
|
|
|
|
public const int WH_KEYBOARD_LL = 13;
|
|
//const int HC_ACTION = 0;
|
|
|
|
public enum KeyboardState
|
|
{
|
|
KeyDown = 0x0100,
|
|
KeyUp = 0x0101,
|
|
SysKeyDown = 0x0104,
|
|
SysKeyUp = 0x0105
|
|
}
|
|
|
|
public IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam)
|
|
{
|
|
var fEatKeyStroke = false;
|
|
|
|
var wParamTyped = wParam.ToInt32();
|
|
if (Enum.IsDefined(typeof(KeyboardState), wParamTyped))
|
|
{
|
|
var o = Marshal.PtrToStructure(lParam, typeof(LowLevelKeyboardInputEvent));
|
|
var p = (LowLevelKeyboardInputEvent)o;
|
|
|
|
var eventArguments = new GlobalKeyboardHookEventArgs(p, (KeyboardState)wParamTyped);
|
|
|
|
OnKeyboardPressed?.Invoke(this, eventArguments);
|
|
|
|
fEatKeyStroke = eventArguments.Handled;
|
|
}
|
|
|
|
return fEatKeyStroke ? (IntPtr)1 : CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
|
|
}
|
|
}
|
|
}
|