Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Global Hotkeys within Desktop Applications

0.00/5 (No votes)
29 Dec 2018 1  
Learn how to create Global Hotkeys properly in a C# desktop application (e.g. Windows Forms or WPF)

Introduction

There are two common ways to implement global hotkeys, each comes with its own set of pros and cons. It's important to understand what your code is doing when your code climbs out of your program's context and into the user's desktop.

Background

A global hotkey refers to a system-wide key press event, i.e., a case in which your code reacts to a key press regardless of where keyboard focus is.

The Windows Desktop API exposes a RegisterHotkey function, which you should prefer over the alternative method of using a low-level keyboard hook.

RegisterHotkey, however, has three significant limitations:

  1. When you register a key, you basically override its original functionality. For example, if you override the F5 key, your users will be irritated when the key will no longer refresh web pages on their browser.
  2. After point #1, it goes without saying that no two programs can have the same global hotkey combination. If you try to register a hotkey that already belongs to another running program, you'll run into a WinAPI error.
  3. Some combinations cannot be registered at all (e.g. Ctrl+Alt+Del)

A low-level keyboard hook defeats those limitations, but has two consequences that you need to keep in mind:

  1. Your code is likely to be labeled as spyware (for key logging) by antiviruses (or people who decompile your code and see your use of SetWindowsHookEx)
  2. It has the potential of slowing down keyboard input processing for the entire system (this will be explained in detail below)

There's very little we can do with the first limitation, but the latter can be prevented if you understand what you're doing.

First, let's see what happens when you hit a key on your keyboard:

Keyboard Input Flow Chart

(a similar flow is applicable for the case of KEYUP)

From the chart above, you learn that your global keyboard hooks process keyboard input before the keyboard input reaches its final destination (the focused application).

The hooks run synchronously. If a hook runs slowly, then input will be delayed and irritate the user.

Assuming you do not intend to prevent any keys from reaching other hooks and programs, the intuitive solution to this problem is to process the input in a separate thread.

This is the LowLevelKeyboardProc code taken from the open source NonInvasiveKeyboardHook library:

private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
    if (nCode >= 0)
    {
        var vkCode = Marshal.ReadInt32(lParam);

        // To prevent slowing keyboard input down, we use handle keyboard inputs in a separate thread
        ThreadPool.QueueUserWorkItem
           (this.HandleSingleKeyboardInput, new KeyboardParams(wParam, vkCode));
    }
     
    return CallNextHookEx(_hookId, nCode, wParam, lParam);
}

A naive approach could've been somewhat reverting the order, such as:

var returnValue = CallNextHookEx(_hookId, nCode, wParam, lParam);
ProcessInputInSameThread();
return returnValue;

While it is true that the early invocation of CallNextHookEx will resume the current key's flow down the message queue, the next key press will not be processed until the blocking code finishes and the function returns.

Using the Code

So if it's global hotkeys you're after and the standard WinAPI hotkey system does not meet your needs, then the open source NonInvasiveKeyboardHook library is the answer.

It exposes a KeyboardHookManager, which exposes the functionality of registering specific key combinations (i.e., you cannot use this for spyware).

* It is recommended that you only use one KeyboardHookManager instance.

Basics

Instantiate and start a KeyboardHookManager:

var keyboardHookManager = new KeyboardHookManager();
keyboardHookManager.Start();

Stopping it (can be resumed by calling .Start again later).

keyboardHookManager.Stop();

Register hotkeys

Register a hotkey without modifiers:

// 0x60 = NumPad0
keyboardHookManager.RegisterHotkey(0x60, () =>
{
    Debug.WriteLine("NumPad0 detected");
});

Register a hotkey with a single modifier:

keyboardHookManager.RegisterHotkey(NonInvasiveKeyboardHookLibrary.ModifierKeys.Control, 0x60, () => 
{ 
    Debug.WriteLine("Ctrl+NumPad0 detected");
});

Register a hotkey with multiple modifiers:

// Multiple modifiers can be specified using the bitwise OR operation
keyboardHookManager.RegisterHotkey(NonInvasiveKeyboardHookLibrary.ModifierKeys.Control | 
NonInvasiveKeyboardHookLibrary.ModifierKeys.Alt, 0x60, () => 
{ 
    Debug.WriteLine("Ctrl+Alt+NumPad0 detected");
});

// Or as an enum of modifiers
keyboardHookManager.RegisterHotkey(new[]
{NonInvasiveKeyboardHookLibrary.ModifierKeys.Control, 
NonInvasiveKeyboardHookLibrary.ModifierKeys.Alt}, 0x60, () =>
{
    Debug.WriteLine("Ctrl+Alt+NumPad0 detected");
});

Unregister Hotkeys

A hotkey can be unregistered based on its unique key combination, or using the globally unique identifier returned by RegisterHotKey.

keyboardHookManager.RegisterHotkey(0x60, () => { Debug.WriteLine("NumPad0 detected"); });
keyboardHookManager.UnregisterHotkey(0x60);

OR

var hotkeyId = keyboardHookManager.RegisterHotkey(0x60, () => { Debug.WriteLine("NumPad0 detected"); });
keyboardHookManager.UnregisterHotkey(hotkeyId);

It is also possible to unregister all hotkeys:

keyboardHookManager.UnregisterAll();

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here