Skip to content

Instantly share code, notes, and snippets.

@anna-dolbina
Created April 15, 2021 11:39
Show Gist options
  • Save anna-dolbina/7f07ed13db25e16b3458dad6b161465c to your computer and use it in GitHub Desktop.
Save anna-dolbina/7f07ed13db25e16b3458dad6b161465c to your computer and use it in GitHub Desktop.
Local windows hooks and CBT hook wrapper
//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