Created
April 15, 2021 11:39
-
-
Save anna-dolbina/7f07ed13db25e16b3458dad6b161465c to your computer and use it in GitHub Desktop.
Local windows hooks and CBT hook wrapper
This file contains hidden or 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
//Based on https://docs.microsoft.com/en-us/archive/msdn-magazine/2002/october/cutting-edge-windows-hooks-in-the-net-framework | |
using System; | |
using System.Runtime.InteropServices; | |
using System.Text; | |
namespace Example | |
{ | |
public class HookEventArgs : EventArgs | |
{ | |
#region Properties | |
public int HookCode { get; set; } | |
public IntPtr LParam { get; set; } | |
public IntPtr WParam { get; set; } | |
#endregion | |
} | |
// Windows Hook Types | |
public enum HookType | |
{ | |
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 class LocalWindowsHook | |
{ | |
private readonly HookProc m_filterFunc; | |
private readonly HookType m_hookType; | |
private IntPtr hookHandle = IntPtr.Zero; | |
#region Events | |
public event EventHandler<HookEventArgs> HookInvoked; | |
#endregion | |
#region Constructors | |
public LocalWindowsHook(HookType hook) | |
{ | |
m_hookType = hook; | |
m_filterFunc = CoreHookProc; | |
} | |
#endregion | |
#region Methods | |
public int CoreHookProc(int code, IntPtr wParam, IntPtr lParam) | |
{ | |
if (code < 0) | |
{ | |
return CallNextHookEx(hookHandle, code, wParam, lParam); | |
} | |
HookEventArgs e = new HookEventArgs {WParam = wParam, HookCode = code, LParam = lParam}; | |
OnHookInvoked(e); | |
return CallNextHookEx(hookHandle, code, wParam, lParam); | |
} | |
public bool Install() | |
{ | |
hookHandle = SetWindowsHookEx(m_hookType, m_filterFunc, IntPtr.Zero, AppDomain.GetCurrentThreadId()); | |
return hookHandle != IntPtr.Zero; | |
} | |
public void Uninstall() | |
{ | |
UnhookWindowsHookEx(hookHandle); | |
} | |
#endregion | |
#region PInvoke | |
[DllImport("user32.dll")] | |
private static extern int CallNextHookEx(IntPtr hhook, int code, IntPtr wParam, IntPtr lParam); | |
private void OnHookInvoked(HookEventArgs e) | |
{ | |
HookInvoked?.Invoke(this, e); | |
} | |
[DllImport("user32.dll")] | |
private static extern IntPtr SetWindowsHookEx(HookType code, HookProc func, IntPtr hInstance, int threadID); | |
[DllImport("user32.dll")] | |
private static extern int UnhookWindowsHookEx(IntPtr hhook); | |
private delegate int HookProc(int code, IntPtr wParam, IntPtr lParam); | |
#endregion | |
} | |
public class LocalCbtHook : LocalWindowsHook | |
{ | |
#region Events | |
public event EventHandler<CbtEventArgs> WindowCreated; | |
public event EventHandler<CbtEventArgs> WindowDestroyed; | |
public event EventHandler<CbtEventArgs> WindowActivated; | |
public event EventHandler<CbtEventArgs> KeyboardFocusGained; | |
#endregion | |
#region Constructors | |
public LocalCbtHook() : base(HookType.WH_CBT) | |
{ | |
HookInvoked += CbtHookInvoked; | |
} | |
#endregion | |
#region Methods | |
protected virtual void OnSetFocus(CbtEventArgs focusGainedEventArgs, CbtEventArgs focusLostEventArgs) | |
{ | |
KeyboardFocusGained?.Invoke(this, focusGainedEventArgs); | |
} | |
protected virtual void OnWindowActivated(CbtEventArgs e) | |
{ | |
WindowActivated?.Invoke(this, e); | |
} | |
// Helper functions that fire events by executing user code | |
protected virtual void OnWindowCreated(CbtEventArgs e) | |
{ | |
WindowCreated?.Invoke(this, e); | |
} | |
protected virtual void OnWindowDestroyed(CbtEventArgs e) | |
{ | |
WindowDestroyed?.Invoke(this, e); | |
} | |
private void CbtHookInvoked(object sender, HookEventArgs e) | |
{ | |
CbtHookAction code = (CbtHookAction) e.HookCode; | |
switch (code) | |
{ | |
case CbtHookAction.HCBT_CREATEWND: | |
OnWindowCreated(CreateEventArgs(e.WParam)); | |
break; | |
case CbtHookAction.HCBT_DESTROYWND: | |
OnWindowDestroyed(CreateEventArgs(e.WParam)); | |
break; | |
case CbtHookAction.HCBT_ACTIVATE: | |
OnWindowActivated(CreateEventArgs(e.WParam)); | |
break; | |
case CbtHookAction.HCBT_SETFOCUS: | |
OnSetFocus(CreateEventArgs(e.WParam), CreateEventArgs(e.LParam)); | |
break; | |
} | |
} | |
// Read and store some information about the window | |
private CbtEventArgs CreateEventArgs(IntPtr wParam) | |
{ | |
CbtEventArgs e = new CbtEventArgs(); | |
// Set the window handle | |
e.Handle = wParam; | |
// Set the window's class name | |
StringBuilder sb1 = new StringBuilder(); | |
sb1.Capacity = 40; | |
GetClassName(e.Handle, sb1, 40); | |
e.ClassName = sb1.ToString(); | |
// Set the window's title bar | |
StringBuilder sb2 = new StringBuilder(); | |
sb2.Capacity = 256; | |
GetWindowText(e.Handle, sb2, 256); | |
e.Title = sb2.ToString(); | |
// Cache the dialog flag | |
e.IsDialogWindow = e.ClassName == "#32770"; | |
return e; | |
} | |
#endregion | |
#region Hook actions | |
// CBT hook actions | |
private enum CbtHookAction | |
{ | |
HCBT_MOVESIZE = 0, | |
HCBT_MINMAX = 1, | |
HCBT_QS = 2, | |
HCBT_CREATEWND = 3, | |
HCBT_DESTROYWND = 4, | |
HCBT_ACTIVATE = 5, | |
HCBT_CLICKSKIPPED = 6, | |
HCBT_KEYSKIPPED = 7, | |
HCBT_SYSCOMMAND = 8, | |
HCBT_SETFOCUS = 9 | |
} | |
#endregion | |
#region PInvoke | |
// Win32: GetClassName | |
[DllImport("user32.dll")] | |
protected static extern int GetClassName(IntPtr hwnd, | |
StringBuilder lpClassName, int nMaxCount); | |
// Win32: GetWindowText | |
[DllImport("user32.dll")] | |
protected static extern int GetWindowText(IntPtr hwnd, | |
StringBuilder lpString, int nMaxCount); | |
#endregion | |
} | |
public class CbtEventArgs : EventArgs | |
{ | |
#region Properties | |
public string ClassName { get; set; } | |
public IntPtr Handle { get; set; } | |
public bool IsDialogWindow { get; set; } | |
public string Title { get; set; } | |
#endregion | |
#region Methods | |
public override string ToString() | |
=> $"{nameof(Handle)}: 0x{Handle.ToInt64():X}, {nameof(Title)}: {Title}, {nameof(ClassName)}: {ClassName}, {nameof(IsDialogWindow)}: {IsDialogWindow}"; | |
#endregion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment