GlobalHotKeys.Latur
1.0.6
dotnet add package GlobalHotKeys.Latur --version 1.0.6
NuGet\Install-Package GlobalHotKeys.Latur -Version 1.0.6
<PackageReference Include="GlobalHotKeys.Latur" Version="1.0.6" />
<PackageVersion Include="GlobalHotKeys.Latur" Version="1.0.6" />
<PackageReference Include="GlobalHotKeys.Latur" />
paket add GlobalHotKeys.Latur --version 1.0.6
#r "nuget: GlobalHotKeys.Latur, 1.0.6"
#:package GlobalHotKeys.Latur@1.0.6
#addin nuget:?package=GlobalHotKeys.Latur&version=1.0.6
#tool nuget:?package=GlobalHotKeys.Latur&version=1.0.6
GlobalHotKeys
A universal C# library for handling global hotkeys using keyboard and mouse combinations at a low level. Built with modularity, cross-project reuse, and developer ergonomics in mind.
โ Features
- ๐ฆ Global low-level input hook (keyboard + mouse)
- ๐ Unified
KeyCodeenum for keyboard and mouse - ๐ง Combo logic with support for modifier + button combos (e.g.
Ctrl + Shift + XButton1) - ๐ง Easy registration using
BindandHotkeyCombination - ๐ Trigger-once execution until all keys are released
- ๐งผ Clean modular architecture (fully self-contained)
- ๐ช Built on Win32
SendInputandSetWindowsHookEx
๐ Project Structure
| File | Description |
|---|---|
KeyCode.cs |
Universal enum for all keyboard/mouse keys (like A, F5, XButton1) |
HotkeyCombination.cs |
Represents a set of keys that define a hotkey |
Bind.cs |
Simplifies registration by bundling an ID + combo |
ActiveCombination.cs |
Tracks active/completed state of an input combo |
HotkeyRegistry.cs |
Maps IDs and combos to actions |
HookLifecycle.cs |
Manages low-level Win32 hooks (start, stop, dispose) |
Interop.cs |
Isolated Win32 imports and constants |
HotKeys.cs |
High-level manager for using all of the above |
๐ Quick Start
dotnet add package GlobalHotKeys.Latur
using GlobalHotKeys;
using GlobalHotKeys.Structs;
var manager = new GlobalHotKeys.HotKeys();
manager.Register(new Bind("screenshot", KeyCode.Control, KeyCode.F5), async () =>
{
Console.WriteLine("Screenshot triggered!");
});
manager.Start();
๐ง How It Works
- Low-level hooks are installed using optimized Win32 APIs (no blocking calls)
- Keys and mouse buttons are unified into a
HashSet<KeyCode> - When a combination is pressed, it's matched against registered actions
- The action is only invoked once per full press-release cycle
- You can dynamically register, update, or remove combos during runtime
โก Performance Improvements (v1.0.6+)
- Instant startup: Eliminated blocking
Process.MainModulecalls - Optimized hook installation: Uses
GetModuleHandle(null)for maximum speed - Removed unnecessary async overhead: Windows handles callbacks directly
- Better error handling: Proper cleanup on hook installation failures
๐ง Example Usage
Register a Hotkey:
manager.Register(new Bind("open-dev", KeyCode.LControl, KeyCode.LShift, KeyCode.D), async () =>
{
Console.WriteLine("Dev Tools Opened");
});
Change an Existing Combo:
manager.Change("open-dev", new HotkeyCombination(false, KeyCode.Control, KeyCode.F1));
Remove a Combo:
manager.Unregister("open-dev");
๐ฅ๏ธ Console Applications
Important: Console applications require a Windows message loop to process low-level hooks. Without a message loop, hotkeys will not work even if the hooks are installed successfully.
Solution 1: Using WinForms (Recommended)
Add a reference to System.Windows.Forms and use Application.Run():
using System;
using System.Threading.Tasks;
using System.Windows.Forms; // Add this using
using GlobalHotKeys;
using GlobalHotKeys.Structs;
[STAThread]
public static async Task Main(string[] args)
{
var hotKeys = new HotKeys();
hotKeys.Start();
hotKeys.Register(new Bind("test-hotkey", () =>
{
Console.WriteLine("Hotkey triggered!");
return Task.CompletedTask;
}, KeyCode.F1, KeyCode.LControl));
// This runs a proper Windows message loop
Application.Run();
}
Solution 2: Custom Message Loop (No Dependencies)
If you prefer not to add WinForms dependency, implement a minimal message loop:
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using GlobalHotKeys;
using GlobalHotKeys.Structs;
[STAThread]
public static async Task Main(string[] args)
{
var hotKeys = new HotKeys();
hotKeys.Start();
hotKeys.Register(new Bind("test-hotkey", () =>
{
Console.WriteLine("Hotkey triggered!");
return Task.CompletedTask;
}, KeyCode.F1, KeyCode.LControl));
// Run a minimal message loop
while (true)
{
var msg = new MSG();
var result = GetMessage(ref msg, IntPtr.Zero, 0, 0);
if (result <= 0) break; // WM_QUIT or error
TranslateMessage(ref msg);
DispatchMessage(ref msg);
}
}
// Required P/Invoke declarations
[StructLayout(LayoutKind.Sequential)]
public struct MSG
{
public IntPtr hwnd;
public uint message;
public IntPtr wParam;
public IntPtr lParam;
public uint time;
public POINT pt;
}
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int x, y;
}
[DllImport("user32.dll")]
static extern int GetMessage(ref MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
[DllImport("user32.dll")]
static extern bool TranslateMessage(ref MSG lpMsg);
[DllImport("user32.dll")]
static extern IntPtr DispatchMessage(ref MSG lpMsg);
Why This Is Required
Low-level hooks (WH_KEYBOARD_LL and WH_MOUSE_LL) are processed by Windows through the message queue. Console applications don't have a message loop by default, so:
- โ Hooks install successfully
- โ Windows calls your hook procedures when input occurs
- โ Without a message loop, the thread doesn't process messages
- โ Hotkeys appear to not work
The message loop ensures that Windows can properly deliver hook callbacks to your application.
๐ Limitations
- Windows-only (relies on
SetWindowsHookEx) - Must run on STA thread for hook reliability
- Cannot detect keys consumed by some protected fullscreen games
- Console applications require a message loop (see Console Apps section below)
๐ Fixed Issues
- โ Startup lag in WinForm applications (especially when debugging)
- โ Infinite stutter in Console applications
- โ Performance issues with hook installation
- โ Resource leaks on hook installation failures
๐ง Troubleshooting
Common Issues
Q: My application still has startup lag after updating A: Make sure you're using the latest version (v1.0.6+). The performance improvements are significant.
Q: Hooks don't work in my Console application A: Console applications require a Windows message loop to process low-level hooks. See the Console Applications section above for complete solutions.
Q: Hotkeys don't trigger in some games A: Some games with anti-cheat systems may block low-level hooks. This is a security feature, not a library issue.
Q: Getting "Failed to install hook" errors A: Ensure your application has sufficient permissions and isn't running in a sandboxed environment.
๐งฑ Building from Source
git clone https://github.com/latur-h/GlobalHotKeys
cd GlobalHotKeys
dotnet build -c Release
๐ Version History
v1.0.6+ (Latest)
- โก Major Performance Improvements
- Eliminated startup lag in WinForm and Console applications
- Replaced blocking
Process.MainModulewith fastGetModuleHandle(null) - Removed unnecessary async task overhead
- Added robust error handling and cleanup
- ๐ก๏ธ Enhanced Reliability
- Better hook installation error handling
- Proper resource cleanup on failures
- Improved thread safety
v1.0.5 and earlier
- Initial release with basic hotkey functionality
- Global keyboard and mouse hook support
- Hotkey combination matching and execution
๐ License
MIT โ free for personal and commercial use.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. 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. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.