Click here to Skip to main content
15,880,543 members
Articles / Desktop Programming / Win32

Global Hotkeys within Desktop Applications

Rate me:
Please Sign up or sign in to vote.
5.00/5 (7 votes)
29 Dec 2018MIT3 min read 27.8K   16   2
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:

C#
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:

C#
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:

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

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

C#
keyboardHookManager.Stop();

Register hotkeys

Register a hotkey without modifiers:

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

Register a hotkey with a single modifier:

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

Register a hotkey with multiple modifiers:

C#
// 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.

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

OR

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

It is also possible to unregister all hotkeys:

C#
keyboardHookManager.UnregisterAll();
This article was originally posted at https://github.com/kfirprods/ShortcutHotkeysExample

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer
Israel Israel
A self-learning full-stack developer since 2007, with most efforts directed at desktop and web applications.

Comments and Discussions

 
SuggestionGetAsyncKeyState Pin
Member 1395293130-Dec-18 5:44
Member 1395293130-Dec-18 5:44 
GeneralRe: GetAsyncKeyState Pin
Kfir Eichenblat30-Dec-18 6:17
Kfir Eichenblat30-Dec-18 6:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.