EventHook 3.0.0

dotnet add package EventHook --version 3.0.0
                    
NuGet\Install-Package EventHook -Version 3.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EventHook" Version="3.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EventHook" Version="3.0.0" />
                    
Directory.Packages.props
<PackageReference Include="EventHook" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add EventHook --version 3.0.0
                    
#r "nuget: EventHook, 3.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package EventHook@3.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=EventHook&version=3.0.0
                    
Install as a Cake Addin
#tool nuget:?package=EventHook&version=3.0.0
                    
Install as a Cake Tool

EventHook

CI NuGet

Code Quality

Quality Gate Coverage Lines of Code Bugs Vulnerabilities Code Smells Security Rating Reliability Rating Maintainability Rating Duplicated Lines Technical Debt

A .NET library to subscribe to global user actions across Windows, macOS, and Linux: keyboard, mouse, clipboard, application windows, print jobs, and hotkeys.

Install

dotnet add package EventHook
Platform Target framework
Windows net10.0-windows
macOS / Linux net10.0

Windows applications must target net10.0-windows. The portable net10.0 package is for macOS and Linux.

Sample

using System;

using (var eventHookFactory = new EventHookFactory())
{
    var keyboardWatcher = eventHookFactory.GetKeyboardWatcher();
    var kb = keyboardWatcher.Start();
    if (!kb.Success)
    {
        Console.WriteLine(kb); // PermissionDenied / PrivilegeRequired / etc.
    }
    else
    {
        keyboardWatcher.OnKeyInput += (s, e) =>
            Console.WriteLine($"Key {e.KeyData.EventType} of {e.KeyData.Keyname}");
    }

    var mouseWatcher = eventHookFactory.GetMouseWatcher();
    mouseWatcher.IncludeMouseMove = false; // default is false
    mouseWatcher.Start().ThrowIfFailed();
    mouseWatcher.OnMouseInput += (s, e) =>
        Console.WriteLine($"Mouse {e.Message} at {e.Point.x},{e.Point.y}");

    var clipboardWatcher = eventHookFactory.GetClipboardWatcher();
    clipboardWatcher.Start().ThrowIfFailed();
    clipboardWatcher.OnClipboardModified += (s, e) =>
        Console.WriteLine($"Clipboard {e.DataFormat}: {e.Data}");

    var applicationWatcher = eventHookFactory.GetApplicationWatcher();
    applicationWatcher.Start().ThrowIfFailed();
    applicationWatcher.OnApplicationWindowChange += (s, e) =>
        Console.WriteLine($"{e.ApplicationData.AppName} was {e.Event}");

    var printWatcher = eventHookFactory.GetPrintWatcher();
    printWatcher.Start().ThrowIfFailed();
    printWatcher.OnPrintEvent += (s, e) =>
        Console.WriteLine($"Printer {e.EventData.PrinterName} pages={e.EventData.Pages}");

    var hotkeyWatcher = eventHookFactory.GetHotkeyWatcher();
    hotkeyWatcher.Start().ThrowIfFailed();
    hotkeyWatcher.Register("demo", new Hotkey(KeyModifiers.Control | KeyModifiers.Alt, EventKey.H))
        .ThrowIfFailed();
    hotkeyWatcher.OnHotkeyPressed += (s, e) =>
        Console.WriteLine($"Hotkey {e.Id} ({e.Hotkey})");

    Console.ReadLine();
}

OS hook callbacks only copy a lightweight snapshot and return immediately. Decoding and user event handlers run on a dedicated offload path so input is never blocked.

Permissions and failures

Start() / Register() return HookStartResult. Check Success, or call ThrowIfFailed(). IsRunning is true only after a successful install.

Reason Typical cause
PermissionDenied macOS Input Monitoring / Accessibility (TCC)
PrivilegeRequired Linux /dev/input not readable (add user to input group)
DisplayUnavailable No DISPLAY / session when required (Linux/macOS). Not used for a missing Win32 pump.
NotSupportedOnPlatform Feature needs X11 on Linux Wayland-only, or wrong TFM on Windows
AlreadyInUse Hotkey already registered by another process
NativeFailure Native API failed after permissions were OK

If EventHookFactory cannot start its own Windows message pump (or macOS cannot start its CFRunLoop), construction throws TimeoutException. That is not a HookStartResult — there is no HookFailureReason for “pump missing.”

See examples/MAC.md and examples/LINUX.md for host setup.

Message pump / event loop

Hooks need an OS event loop. Callers usually do not create one: the factory (Windows) or platform hosts (macOS / Linux) start it.

Platform Who pumps What you must do
Windows EventHookFactory Nothing in console/service code. Construct the factory on an STA UI thread or let it create a background STA WinForms loop (EventHook.MessagePump).
macOS Library CFRunLoop (EventHook.Mac.CFRunLoop) Nothing. The HWND argument is ignored.
Linux X11 Library XNextEvent thread Need DISPLAY (or Xvfb). HWND is ignored.
Linux evdev /dev/input reads (no X loop) User must be in the input group. Clipboard / windows / hotkeys still need X11.

Windows details

  • Keyboard and mouse (WH_KEYBOARD_LL / WH_MOUSE_LL) are installed on the factory pump thread. That thread must keep pumping or Windows stops delivering.
  • Clipboard (WM_CLIPBOARDUPDATE), hotkeys (WM_HOTKEY), and application/shell hooks are HWND messages on the same pump.
  • WindowHookEx does not use the factory pump. Call Start() from a thread that already pumps messages (your UI thread).
  • Hosted COM / Office add-ins: construct the factory on the STA UI thread, or pass that window’s HWND. A provided HWND must keep pumping. EventHook registers hotkeys on that handle but does not subclass it — if you pass a host HWND, your WndProc must dispatch WM_HOTKEY or the watcher will report IsRunning and still never fire.
  • Prefer the default new EventHookFactory() (library-owned pump) unless you are hosting and will forward messages.

Application window filter (Windows)

EventHook.Helpers.AppWindowFilter.IncludeWindowsWithoutSysMenu = true;
EventHook.Helpers.AppWindowFilter.IncludeDialogs = true;
EventHook.Helpers.AppWindowFilter.CustomFilter = hwnd => true;

Hosted apps (COM / Office add-ins on Windows)

Prefer constructing the factory on an STA UI thread, or pass an existing message-pump HWND:

using var factory = new EventHookFactory(hostMainWindowHandle);

Development

  • .NET 10 SDK
  • Windows: dotnet build src/Event.Hook.sln -c Release
  • Portable (macOS/Linux CI): build the library, tests, and console example projects (not the full Windows-only solution)
  • dotnet test tests/Event.Hook.Tests
  • dotnet test tests/Event.Hook.IntegrationTests
  • dotnet test tests/Event.Hook.E2ETests --filter Category=E2E
  • Docs: dotnet tool restore then docfx .github/docfx.json

Release branches

Branch NuGet Notes
develop (no publish) CI on Windows + macOS + Linux; SonarCloud + DocFX on develop push only
beta {VersionPrefix}-beta.2 Same CI, then publish prerelease (3.0.0-beta already shipped)
stable / tag v* {VersionPrefix} Same CI, then stable release + GitHub Pages docs

Publishing uses NuGet Trusted Publishing (NUGET_USER on the nuget-publish environment).

Version 3.0 notes

Breaking: multi-platform (net10.0-windows + net10.0), Start()/Register() return HookStartResult, portable Hotkey replaces WinForms Keys, IncludeMouseMove defaults to false, non-blocking OS hook offload.

Repository renamed from windows-user-action-hook to event-hook. Solution/projects use Event.Hook.*; NuGet package id remains EventHook.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed.  net10.0-windows7.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net10.0-windows7.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories (1)

Showing the top 1 popular GitHub repositories that depend on EventHook:

Repository Stars
mann1x/CPUDoc
Version Downloads Last Updated
3.0.0 36 9/8/2026
3.0.0-beta.2 35 9/8/2026
3.0.0-beta 33 9/8/2026
2.0.0 47 9/7/2026
2.0.0-beta 84 8/31/2026
1.4.113 12,953 11/10/2020
1.4.110 945 10/18/2020
1.4.105 21,847 11/21/2018
1.4.79 2,288 4/24/2018
1.4.74 2,088 4/13/2018
1.4.72 2,028 4/13/2018
1.4.70 2,070 4/13/2018
1.4.67 2,007 4/13/2018
1.4.64 2,034 4/13/2018
1.4.62 1,965 4/13/2018
1.4.59 1,919 4/13/2018
1.4.58 1,928 4/13/2018
1.4.39 3,946 6/17/2016
1.4.37 1,810 4/18/2016
1.4.34 1,915 4/6/2016
Loading failed