Skip to content

Instantly share code, notes, and snippets.

@Lait-au-Cafe
Last active November 8, 2019 13:36
Show Gist options
  • Save Lait-au-Cafe/904653f11bc5f2e2362253f3f9b3cc00 to your computer and use it in GitHub Desktop.
Save Lait-au-Cafe/904653f11bc5f2e2362253f3f9b3cc00 to your computer and use it in GitHub Desktop.
Handle key event when the app is running in background.
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
#region WIN32 TYPES
using DWORD = System.UInt64;
using HINSTANCE = System.IntPtr;
using HHOOK = System.IntPtr;
using WPARAM = System.IntPtr;
using LPARAM = System.IntPtr;
using LRESULT = System.IntPtr;
using HMODULE = System.IntPtr;
using LPCWSTR = System.String;
using ULONG_PTR = System.UIntPtr;
#endregion
namespace GlobalInput
{
public enum HookType : int
{
WH_JOURNALRECORD = 0,
WH_JOURNALPLAYBACK = 1,
WH_KEYBOARD = 2,
WH_GETMESSAGE = 3,
WH_CALLWNDPROC = 4,
WH_CBT = 5,
WH_SYSMSGFILTER = 6,
WH_MOUSE = 7,
WH_HARDWARE = 8,
WH_DEBUG = 9,
WH_SHELL = 10,
WH_FOREGROUNDIDLE = 11,
WH_CALLWNDPROCRET = 12,
WH_KEYBOARD_LL = 13,
WH_MOUSE_LL = 14
}
public enum GlobalKey
{
LeftButton = 0x01,
RightButton = 0x02,
Cancel = 0x03,
MiddleButton = 0x04,
ExtraButton1 = 0x05,
ExtraButton2 = 0x06,
Back = 0x08,
Tab = 0x09,
Clear = 0x0C,
Return = 0x0D,
Shift = 0x10,
Control = 0x11,
Menu = 0x12,
Pause = 0x13,
CapsLock = 0x14,
Kana = 0x15,
Hangeul = 0x15,
Hangul = 0x15,
Junja = 0x17,
Final = 0x18,
Hanja = 0x19,
Kanji = 0x19,
Escape = 0x1B,
Convert = 0x1C,
NonConvert = 0x1D,
Accept = 0x1E,
ModeChange = 0x1F,
Space = 0x20,
Prior = 0x21,
Next = 0x22,
End = 0x23,
Home = 0x24,
Left = 0x25,
Up = 0x26,
Right = 0x27,
Down = 0x28,
Select = 0x29,
Print = 0x2A,
Execute = 0x2B,
Snapshot = 0x2C,
Insert = 0x2D,
Delete = 0x2E,
Help = 0x2F,
N0 = 0x30,
N1 = 0x31,
N2 = 0x32,
N3 = 0x33,
N4 = 0x34,
N5 = 0x35,
N6 = 0x36,
N7 = 0x37,
N8 = 0x38,
N9 = 0x39,
A = 0x41,
B = 0x42,
C = 0x43,
D = 0x44,
E = 0x45,
F = 0x46,
G = 0x47,
H = 0x48,
I = 0x49,
J = 0x4A,
K = 0x4B,
L = 0x4C,
M = 0x4D,
N = 0x4E,
O = 0x4F,
P = 0x50,
Q = 0x51,
R = 0x52,
S = 0x53,
T = 0x54,
U = 0x55,
V = 0x56,
W = 0x57,
X = 0x58,
Y = 0x59,
Z = 0x5A,
LeftWindows = 0x5B,
RightWindows = 0x5C,
Application = 0x5D,
Sleep = 0x5F,
Numpad0 = 0x60,
Numpad1 = 0x61,
Numpad2 = 0x62,
Numpad3 = 0x63,
Numpad4 = 0x64,
Numpad5 = 0x65,
Numpad6 = 0x66,
Numpad7 = 0x67,
Numpad8 = 0x68,
Numpad9 = 0x69,
Multiply = 0x6A,
Add = 0x6B,
Separator = 0x6C,
Subtract = 0x6D,
Decimal = 0x6E,
Divide = 0x6F,
F1 = 0x70,
F2 = 0x71,
F3 = 0x72,
F4 = 0x73,
F5 = 0x74,
F6 = 0x75,
F7 = 0x76,
F8 = 0x77,
F9 = 0x78,
F10 = 0x79,
F11 = 0x7A,
F12 = 0x7B,
F13 = 0x7C,
F14 = 0x7D,
F15 = 0x7E,
F16 = 0x7F,
F17 = 0x80,
F18 = 0x81,
F19 = 0x82,
F20 = 0x83,
F21 = 0x84,
F22 = 0x85,
F23 = 0x86,
F24 = 0x87,
NumLock = 0x90,
ScrollLock = 0x91,
NEC_Equal = 0x92,
Fujitsu_Jisho = 0x92,
Fujitsu_Masshou = 0x93,
Fujitsu_Touroku = 0x94,
Fujitsu_Loya = 0x95,
Fujitsu_Roya = 0x96,
LeftShift = 0xA0,
RightShift = 0xA1,
LeftControl = 0xA2,
RightControl = 0xA3,
LeftMenu = 0xA4,
RightMenu = 0xA5,
BrowserBack = 0xA6,
BrowserForward = 0xA7,
BrowserRefresh = 0xA8,
BrowserStop = 0xA9,
BrowserSearch = 0xAA,
BrowserFavorites = 0xAB,
BrowserHome = 0xAC,
VolumeMute = 0xAD,
VolumeDown = 0xAE,
VolumeUp = 0xAF,
MediaNextTrack = 0xB0,
MediaPrevTrack = 0xB1,
MediaStop = 0xB2,
MediaPlayPause = 0xB3,
LaunchMail = 0xB4,
LaunchMediaSelect = 0xB5,
LaunchApplication1 = 0xB6,
LaunchApplication2 = 0xB7,
OEM1 = 0xBA,
OEMPlus = 0xBB,
OEMComma = 0xBC,
OEMMinus = 0xBD,
OEMPeriod = 0xBE,
OEM2 = 0xBF,
OEM3 = 0xC0,
OEM4 = 0xDB,
OEM5 = 0xDC,
OEM6 = 0xDD,
OEM7 = 0xDE,
OEM8 = 0xDF,
OEMAX = 0xE1,
OEM102 = 0xE2,
ICOHelp = 0xE3,
ICO00 = 0xE4,
ProcessKey = 0xE5,
ICOClear = 0xE6,
Packet = 0xE7,
OEMReset = 0xE9,
OEMJump = 0xEA,
OEMPA1 = 0xEB,
OEMPA2 = 0xEC,
OEMPA3 = 0xED,
OEMWSCtrl = 0xEE,
OEMCUSel = 0xEF,
OEMATTN = 0xF0,
OEMFinish = 0xF1,
OEMCopy = 0xF2,
OEMAuto = 0xF3,
OEMENLW = 0xF4,
OEMBackTab = 0xF5,
ATTN = 0xF6,
CRSel = 0xF7,
EXSel = 0xF8,
EREOF = 0xF9,
Play = 0xFA,
Zoom = 0xFB,
Noname = 0xFC,
PA1 = 0xFD,
OEMClear = 0xFE
}
public enum MessageType: int
{
WM_KEYDOWN = 0x0100,
WM_KEYUP = 0x0101,
WM_SYSKEYDOWN = 0x0104,
WM_SYSKEYUP = 0x0105
}
public delegate LRESULT HOOKPROC(int nCode, WPARAM wParam, LPARAM lParam);
[StructLayout(LayoutKind.Sequential)]
public struct KBDLLHOOKSTRUCT
{
public DWORD vkCode;
public DWORD scanCode;
public DWORD flags;
public DWORD time;
public ULONG_PTR dwExtraInfo;
}
public abstract class GlobalInput
{
public static class NativeMethods
{
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern HHOOK SetWindowsHookExW(
int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool UnhookWindowsHookEx(HHOOK hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern LRESULT CallNextHookEx(
HHOOK hhk, int nCode, WPARAM wParam, LPARAM lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern HMODULE GetModuleHandleW(LPCWSTR lpModuleName);
}
private HOOKPROC hook_proc;
private HHOOK hook_handle = IntPtr.Zero;
public string SetHook()
{
if(hook_handle == IntPtr.Zero)
{
hook_proc = HookProc;
using (var proccess = Process.GetCurrentProcess())
{
using (var module = proccess.MainModule)
{
HMODULE module_handle = NativeMethods.GetModuleHandleW(module.ModuleName);
hook_handle = NativeMethods.SetWindowsHookExW(
(int)HookType.WH_KEYBOARD_LL, hook_proc, module_handle, 0);
}
}
}
if(hook_handle == IntPtr.Zero)
{
int errno = Marshal.GetLastWin32Error();
return $"Failed to set hook. (ErrNo: ${errno})";
}
return "";
}
public string UnsetHook()
{
if(!NativeMethods.UnhookWindowsHookEx(hook_handle))
{
int errno = Marshal.GetLastWin32Error();
return $"Failed to unset hook. (ErrNo: ${errno})";
}
hook_handle = IntPtr.Zero;
return "";
}
protected virtual LRESULT HookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
return NativeMethods.CallNextHookEx(hook_handle, nCode, wParam, lParam);
}
}
public class GlobalKeyInput: GlobalInput
{
public static GlobalKeyInput Instance = new GlobalKeyInput();
private GlobalKeyInput() { }
public static string Enable()
{
return Instance.SetHook();
}
public static string Disable()
{
return Instance.UnsetHook();
}
public static event Action<GlobalKey> KeyDownEvent
{
add { Instance._KeyDownEvent += value; }
remove { Instance._KeyDownEvent -= value; }
}
public static event Action<GlobalKey> KeyUpEvent
{
add { Instance._KeyUpEvent += value; }
remove { Instance._KeyUpEvent -= value; }
}
private event Action<GlobalKey> _KeyDownEvent;
private event Action<GlobalKey> _KeyUpEvent;
protected void OnKeyDown(GlobalKey key)
{
_KeyDownEvent?.Invoke(key);
}
protected void OnKeyUp(GlobalKey key)
{
_KeyUpEvent?.Invoke(key);
}
protected override LRESULT HookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if(nCode >= 0)
{
var kb = Marshal.PtrToStructure<KBDLLHOOKSTRUCT>(lParam);
var vkCode = (GlobalKey)kb.vkCode;
if (wParam == (IntPtr)MessageType.WM_KEYDOWN
|| wParam == (IntPtr)MessageType.WM_SYSKEYDOWN)
{
OnKeyDown(vkCode);
}
else if (wParam == (IntPtr)MessageType.WM_KEYUP
|| wParam == (IntPtr)MessageType.WM_SYSKEYUP)
{
OnKeyUp(vkCode);
}
}
return base.HookProc(nCode, wParam, lParam);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment