91

I need to capture a key press when my program is not in focus. (ie. Ctrl+Alt+J) and trigger an event in my program.

Thus far i have found this dll that appears to be the correct path"

[DllImport("user32.dll")]private static extern int RegisterHotKey(IntPtr hwnd, int id,int fsModifiers, int vk);

[DllImport("user32.dll")] private static extern int UnregisterHotKey(IntPtr hwnd, int id);
Cœur
  • 37,241
  • 25
  • 195
  • 267
Crash893
  • 11,428
  • 21
  • 88
  • 123
  • 1
    See http://stackoverflow.com/questions/81150/best-way-to-tackle-global-hotkey-processing-in-c/2611761#2611761 – Ohad Schneider Apr 10 '10 at 00:35
  • 1
    You can look at my post for WPF solution... [enter link description here][1] [1]: http://stackoverflow.com/questions/48935/how-can-i-register-a-global-hot-key-to-say-ctrlshiftletter-using-wpf-and-ne/9330358#9330358 – Eric Ouellet Feb 17 '12 at 15:02
  • @Crash893 I know this is an old question, and thanks for updating with an answer, but note that you can actually post an answer to your own question if you figure it out, rather than embedding the answer inside the question. Cheers – AaronLS Dec 05 '14 at 05:11
  • Check out the sample code on [pinvoke.net](http://www.pinvoke.net/default.aspx/user32/RegisterHotKey.html) ;) – Thomas Levesque Mar 16 '10 at 00:29

3 Answers3

117

Please note that this code will not trigger events in console application projects. You have to use WinForms project for events to fire.

This is the correct code:

public sealed  class KeyboardHook : IDisposable
{
    // Registers a hot key with Windows.
    [DllImport("user32.dll")]
    private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
    // Unregisters the hot key with Windows.
    [DllImport("user32.dll")]
    private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

    /// <summary>
    /// Represents the window that is used internally to get the messages.
    /// </summary>
    private class Window : NativeWindow, IDisposable
    {
        private static int WM_HOTKEY = 0x0312;

        public Window()
        {
            // create the handle for the window.
            this.CreateHandle(new CreateParams());
        }

        /// <summary>
        /// Overridden to get the notifications.
        /// </summary>
        /// <param name="m"></param>
        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);

            // check if we got a hot key pressed.
            if (m.Msg == WM_HOTKEY)
            {
                // get the keys.
                Keys key = (Keys)(((int)m.LParam >> 16) & 0xFFFF);
                ModifierKeys modifier = (ModifierKeys)((int)m.LParam & 0xFFFF);

                // invoke the event to notify the parent.
                if (KeyPressed != null)
                    KeyPressed(this, new KeyPressedEventArgs(modifier, key));
            }
        }

        public event EventHandler<KeyPressedEventArgs> KeyPressed;

        #region IDisposable Members

        public void Dispose()
        {
            this.DestroyHandle();
        }

        #endregion
    }

    private Window _window = new Window();
    private int _currentId;

    public KeyboardHook()
    {
        // register the event of the inner native window.
        _window.KeyPressed += delegate(object sender, KeyPressedEventArgs args)
        {
            if (KeyPressed != null)
                KeyPressed(this, args);
        };
    }

    /// <summary>
    /// Registers a hot key in the system.
    /// </summary>
    /// <param name="modifier">The modifiers that are associated with the hot key.</param>
    /// <param name="key">The key itself that is associated with the hot key.</param>
    public void RegisterHotKey(ModifierKeys modifier, Keys key)
    {
        // increment the counter.
        _currentId = _currentId + 1;

        // register the hot key.
        if (!RegisterHotKey(_window.Handle, _currentId, (uint)modifier, (uint)key))
            throw new InvalidOperationException("Couldn’t register the hot key.");
    }

    /// <summary>
    /// A hot key has been pressed.
    /// </summary>
    public event EventHandler<KeyPressedEventArgs> KeyPressed;

    #region IDisposable Members

    public void Dispose()
    {
        // unregister all the registered hot keys.
        for (int i = _currentId; i > 0; i--)
        {
            UnregisterHotKey(_window.Handle, i);
        }

        // dispose the inner native window.
        _window.Dispose();
    }

    #endregion
}

/// <summary>
/// Event Args for the event that is fired after the hot key has been pressed.
/// </summary>
public class KeyPressedEventArgs : EventArgs
{
    private ModifierKeys _modifier;
    private Keys _key;

    internal KeyPressedEventArgs(ModifierKeys modifier, Keys key)
    {
        _modifier = modifier;
        _key = key;
    }

    public ModifierKeys Modifier
    {
        get { return _modifier; }
    }

    public Keys Key
    {
        get { return _key; }
    }
}

/// <summary>
/// The enumeration of possible modifiers.
/// </summary>
[Flags]
public enum ModifierKeys : uint
{
    Alt = 1,
    Control = 2,
    Shift = 4,
    Win = 8
}

to use (i had to edit the modifier keys to cast them (modifier)1 (modifier)2 etc

public partial  class Form1 : Form
{
    KeyboardHook hook = new KeyboardHook();

    public Form1()
    {
        InitializeComponent();

        // register the event that is fired after the key press.
        hook.KeyPressed +=
            new EventHandler<KeyPressedEventArgs>(hook_KeyPressed);
        // register the control + alt + F12 combination as hot key.
        hook.RegisterHotKey(ModifierKeys.Control | ModifierKeys.Alt,
            Keys.F12);
    }

    void hook_KeyPressed(object sender, KeyPressedEventArgs e)
    {
        // show the keys pressed in a label.
        label1.Text = e.Modifier.ToString() + " + " + e.Key.ToString();
    }
}
Menol
  • 1,230
  • 21
  • 35
AaronLS
  • 37,329
  • 20
  • 143
  • 202
  • 2
    What do I put if I don't want any modifier key? `RegisterHotKey()` forces you to enter something. – Dan W Mar 25 '15 at 21:02
  • 1
    @DanW I think the enum has `ModifierKeys.None` – AaronLS Mar 25 '15 at 21:08
  • 3
    @DanW Add `None = 0` to the `public enum ModifierKeys` declaration and see if that does it. – AaronLS Mar 25 '15 at 21:22
  • @DanW Note these keys are global, I personally wouldn't recommend using this method to bind to a non-modifier key because that's going to interfere with use in other programs much more significantly. There's already risk of conflict even with modifier keys. You'd instead want to look for key press events so that it's scoped to your application only. – AaronLS Mar 25 '15 at 21:24
  • 1
    Fair point, but very occasionally, using function keys or the keypad globally could be useful. Your `Add None = 0` suggestion crashes by the way. – Dan W Mar 25 '15 at 22:07
  • Odd that it crashes without an exception, I would debug and see if maybe one is being thrown that might give a clue as to the problem. Only other thing I can suggest is try passing `null`. I'm not sure if the dllimport will let you modify the declaration to allow a nullable to be passed though. I wouldn't be surprised if windows does not allow this. – AaronLS Mar 25 '15 at 23:26
  • Tried a few things in regards to null and Nullable, but nothing seemed to compile. By the way, the crash before (with the `None = 0` enum) threw the exception `Couldn’t register the hot key.`. – Dan W Mar 26 '15 at 09:54
  • 1
    On a separate issue, I couldn't seem to `Dispose()` properly. Once I recreated the hotkey again with `RegisterHotKey()`, the hotkey was inoperable. When disposing, I even also tried `hook.KeyPressed -= new EventHandler(myFunc);` without success. I also tried changing the order of `hook.Dispose();` and `hook.KeyPressed -= ......` without success. Perhaps you could update your code to help with unregistering. – Dan W Mar 26 '15 at 21:55
  • I got this: `Couldn’t register the hot key.`, how i can solve this? – Mehdi Dehghani Aug 31 '16 at 06:15
  • 1
    @MehdiDehghani Try running the app as administrator. Try a different hotkey combination, and ensure it include modifier keys(might be a conflict). – AaronLS Aug 31 '16 at 07:15
  • 1
    Why did you name this class KeyboardHook? That's a misleading name since no hook is implemented here. It should be called Hotkey as part of a collection called HotKeys. – Michael Z. Jul 03 '17 at 04:48
  • 1
    @MichaelZ. You are hooking into the operating system keypress event. It is a global event that has a context outside of your application. The class gives you an easy way to do this so it is a hook. A hook is something that gives you a way to have your own code called whenever another process's function is called or a global event is fired. – AaronLS Jul 03 '17 at 13:32
  • 1
    I disagree with that. This is not a hook. Windows has specific API to create hooks. With this code, you are not hooking the OS keypress. That's my point. You are just checking for certain keys in the message of the WM_HOTKEY. – Michael Z. Jul 03 '17 at 13:40
  • 1
    @MichaelZ. "You are just checking for certain keys in the message of the WM_HOTKEY." It is more than that. You won't receive this message by default. Just checking for the message is not enough. You have to register for the event with RegisterHotKey or you will not receive it. "Windows has specific API to create hooks." There are many other types of hooks aside from what the Windows API provides you. In fact, the more common usage of the term involves hooking where **no API is provided**, such as assembly level injection: https://www.google.com/search?q=direct+x+hooking – AaronLS Jul 03 '17 at 13:51
  • Well, either way it's still not working in my app either. – Don P Jul 07 '17 at 00:23
  • 1
    EDIT: working now. Had to change the enum to "public" as i was using it in another form – Don P Jul 07 '17 at 00:30
  • Assuming I want to define more keys that call a (new/extra) method, is there a better way than creating a new hook object? – P_Gate Oct 30 '20 at 16:35
  • @P_Gate You can't specify which method is called when calling RegisterHotKey. The only way to reuse the same hook object I can think of is to make additional calls to .RegisterHotKey, then inside your KeyPressed handler, you'd need to have if/else that evaluates `e.Key`/`e.Modifier` to call methods appropriate for the key combination they go with. These are holdovers from Win32 APIs and aren't always incredibly flexible. – AaronLS Oct 31 '20 at 23:41
  • I tried so many solutions for this task but nothing worked as expected for my outlook vsto plugin. @AaronLS, your solution worked like a charm. – Akanksha_p Nov 11 '21 at 07:33
  • 1
    lint error CS0176: https://learn.microsoft.com/en-us/dotnet/csharp/misc/cs0176 for `ModifierKeys.Control | ModifierKeys.Alt`. Update: my fault, naming confilict with System.Windows.Forms.Control.ModifierKeys, by renaming it to a less common name like 'ModKeys' solved the problem. – fuweichin May 23 '22 at 02:19
17

I took the answer from AaronLS and rewrote it a bit for a simple one-liner registration.

The Registration:

GlobalHotKey.RegisterHotKey("Alt + Shift + S", () => DoSomething());

The Class:

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows.Input;

public class GlobalHotKey : IDisposable
{
    /// <summary>
    /// Registers a global hotkey
    /// </summary>
    /// <param name="aKeyGesture">e.g. Alt + Shift + Control + Win + S</param>
    /// <param name="aAction">Action to be called when hotkey is pressed</param>
    /// <returns>true, if registration succeeded, otherwise false</returns>
    public static bool RegisterHotKey(string aKeyGestureString, Action aAction)
    {
        var c = new KeyGestureConverter();
        KeyGesture aKeyGesture = (KeyGesture)c.ConvertFrom(aKeyGestureString);
        return RegisterHotKey(aKeyGesture.Modifiers, aKeyGesture.Key, aAction);
    }

    public static bool RegisterHotKey(ModifierKeys aModifier, Key aKey, Action aAction)
    {
        if(aModifier == ModifierKeys.None)
        {
            throw new ArgumentException("Modifier must not be ModifierKeys.None");
        }
        if (aAction is null)
        {
            throw new ArgumentNullException(nameof(aAction));
        }

        System.Windows.Forms.Keys aVirtualKeyCode = (System.Windows.Forms.Keys)KeyInterop.VirtualKeyFromKey(aKey);
        currentID = currentID + 1;
        bool aRegistered = RegisterHotKey(window.Handle, currentID,
                                    (uint)aModifier | MOD_NOREPEAT,
                                    (uint)aVirtualKeyCode);

        if(aRegistered)
        {
            registeredHotKeys.Add(new HotKeyWithAction(aModifier, aKey, aAction));
        }
        return aRegistered;
    }

    public void Dispose()
    {
        // unregister all the registered hot keys.
        for (int i = currentID; i > 0; i--)
        {
            UnregisterHotKey(window.Handle, i);
        }

        // dispose the inner native window.
        window.Dispose();
    }

    static GlobalHotKey()
    {
        window.KeyPressed += (s, e) =>
        {
            registeredHotKeys.ForEach(x =>
            {
                if (e.Modifier == x.Modifier && e.Key == x.Key)
                {
                    x.Action();
                }
            });
        };
    }

    private static readonly InvisibleWindowForMessages window = new InvisibleWindowForMessages();
    private static int currentID;
    private static uint MOD_NOREPEAT = 0x4000;
    private static List<HotKeyWithAction> registeredHotKeys = new List<HotKeyWithAction>();

    private class HotKeyWithAction
    {

        public HotKeyWithAction(ModifierKeys modifier, Key key, Action action)
        {
            Modifier = modifier;
            Key = key;
            Action = action;
        }

        public ModifierKeys Modifier { get; }
        public Key Key { get; }
        public Action Action { get; }
    }

    // Registers a hot key with Windows.
    [DllImport("user32.dll")]
    private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
    // Unregisters the hot key with Windows.
    [DllImport("user32.dll")]
    private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

    private class InvisibleWindowForMessages : System.Windows.Forms.NativeWindow, IDisposable
    {
        public InvisibleWindowForMessages()
        {
            CreateHandle(new System.Windows.Forms.CreateParams());
        }

        private static int WM_HOTKEY = 0x0312;
        protected override void WndProc(ref System.Windows.Forms.Message m)
        {
            base.WndProc(ref m);

            if (m.Msg == WM_HOTKEY)
            {
                var aWPFKey = KeyInterop.KeyFromVirtualKey(((int)m.LParam >> 16) & 0xFFFF);
                ModifierKeys modifier = (ModifierKeys)((int)m.LParam & 0xFFFF);
                if (KeyPressed != null)
                {
                    KeyPressed(this, new HotKeyPressedEventArgs(modifier, aWPFKey));
                }
            }
        }

        public class HotKeyPressedEventArgs : EventArgs
        {
            private ModifierKeys _modifier;
            private Key _key;

            internal HotKeyPressedEventArgs(ModifierKeys modifier, Key key)
            {
                _modifier = modifier;
                _key = key;
            }

            public ModifierKeys Modifier
            {
                get { return _modifier; }
            }

            public Key Key
            {
                get { return _key; }
            }
        }


        public event EventHandler<HotKeyPressedEventArgs> KeyPressed;

        #region IDisposable Members

        public void Dispose()
        {
            this.DestroyHandle();
        }

        #endregion
    }
}
Lumo
  • 627
  • 6
  • 21
  • Nice solution buy how can i clear the hotkeys at runtime? Need to setup new hotkeys when some settings changed – Kevin V Nov 07 '22 at 13:22
  • @KevinV you could return a disposable token from the "RegisterHotKey" method and when Dispose is called, remove the entry from registeredHotKeys List. Or if you want to reassign all hotkeys at once, create a "ClearAllHotkeys" Method that just clears the List. – Lumo Nov 08 '22 at 08:58
  • This worked very well, even from a console app (albeit where I have initialised WinForms to have a small notification area / system tray icon running) – Ian Yates Mar 19 '23 at 06:31
5

Here is a working port of original answer:

KeyboardHook.vb

Imports System.Runtime.InteropServices

Public NotInheritable Class KeyboardHook
    Implements IDisposable

    ' Registers a hot key with Windows.
    <DllImport("user32.dll")> _
    Private Shared Function RegisterHotKey(hWnd As IntPtr, id As Integer, fsModifiers As UInteger, vk As UInteger) As Boolean
    End Function

    ' Unregisters the hot key with Windows.
    <DllImport("user32.dll")> _
    Private Shared Function UnregisterHotKey(hWnd As IntPtr, id As Integer) As Boolean
    End Function

    ''' <summary>
    ''' Represents the window that is used internally to get the messages.
    ''' </summary>
    Private Class Window
        Inherits NativeWindow
        Implements IDisposable
        Private Shared WM_HOTKEY As Integer = &H312

        Public Sub New()
            ' create the handle for the window.
            Me.CreateHandle(New CreateParams())
        End Sub

        Public Event KeyPressed As EventHandler(Of KeyPressedEventArgs)

        ''' <summary>
        ''' Overridden to get the notifications.
        ''' </summary>
        ''' <param name="m"></param>
        Protected Overrides Sub WndProc(ByRef m As Message)
            MyBase.WndProc(m)

            ' check if we got a hot key pressed.
            If m.Msg = WM_HOTKEY Then
                ' get the keys.
                Dim key As Keys = DirectCast((CInt(m.LParam) >> 16) And &HFFFF, Keys)
                Dim modifier As ModifierKeys = DirectCast(CUInt(CInt(m.LParam) And &HFFFF), ModifierKeys)

                ' invoke the event to notify the parent.
                RaiseEvent KeyPressed(Me, New KeyPressedEventArgs(modifier, key))
            End If
        End Sub

#Region " IDisposable Members"

        Public Sub Dispose() Implements IDisposable.Dispose
            Me.DestroyHandle()
        End Sub

#End Region
    End Class

    Private _window As New Window()
    Private _currentId As Integer

    Public Sub New()
        ' register the event of the inner native window.
        AddHandler _window.KeyPressed, Sub(sender As Object, args As KeyPressedEventArgs)
                                           RaiseEvent KeyPressed(Me, args)
                                       End Sub
    End Sub

    ''' <summary>
    ''' Registers a hot key in the system.
    ''' </summary>
    ''' <param name="modifier">The modifiers that are associated with the hot key.</param>
    ''' <param name="key">The key itself that is associated with the hot key.</param>
    Public Sub RegisterHotKey(modifier As ModifierKeys, key As Keys)
        ' increment the counter.
        _currentId = _currentId + 1

        ' register the hot key.
        If Not RegisterHotKey(_window.Handle, _currentId, DirectCast(modifier, UInteger), CUInt(key)) Then
            'Throw New InvalidOperationException("Couldn’t register the hot key.")
            'or use MsgBox("Couldn’t register the hot key.")
        End If
    End Sub

    ''' <summary>
    ''' A hot key has been pressed.
    ''' </summary>
    Public Event KeyPressed As EventHandler(Of KeyPressedEventArgs)

#Region " IDisposable Members"

    Public Sub Dispose() Implements IDisposable.Dispose
        ' unregister all the registered hot keys.
        Dim i As Integer = _currentId
        While i > 0
            UnregisterHotKey(_window.Handle, i)
            System.Math.Max(System.Threading.Interlocked.Decrement(i), i + 1)
        End While

        ' dispose the inner native window.
        _window.Dispose()
    End Sub

#End Region
End Class

''' <summary>
''' Event Args for the event that is fired after the hot key has been pressed.
''' </summary>
Public Class KeyPressedEventArgs
    Inherits EventArgs
    Private _modifier As ModifierKeys
    Private _key As Keys

    Friend Sub New(modifier As ModifierKeys, key As Keys)
        _modifier = modifier
        _key = key
    End Sub

    Public ReadOnly Property Modifier() As ModifierKeys
        Get
            Return _modifier
        End Get
    End Property

    Public ReadOnly Property Key() As Keys
        Get
            Return _key
        End Get
    End Property
End Class

''' <summary>
''' The enumeration of possible modifiers.
''' </summary>
<Flags> _
Public Enum ModifierKeys As UInteger
    Alt = 1
    Control = 2
    Shift = 4
    Win = 8
End Enum

Form1.vb

Tasks:

  1. Replace 2 instances of Application1 below with name of your application (it can be seen as root of tree in Visual Studio Solution Explorer window).
  2. Add call to AddGlobalHotkeySupport() into initialization stage of your application.
  3. Add call to RemoveGlobalHotkeySupport() into finalization stage of your application.

Code:

Public Sub AddGlobalHotkeySupport()  'TODO: call this at initialization of the application

    ' register the event that is fired after the key press.
    AddHandler hook.KeyPressed, AddressOf hook_KeyPressed

    ' register the control + alt + F12 combination as hot key.
    hook.RegisterHotKey(Application1.ModifierKeys.Control Or Application1.ModifierKeys.Alt, Keys.F12)

End Sub

Public Sub RemoveGlobalHotkeySupport()  'TODO: call this at finalization of the application
    ' unregister all registered hot keys.
    hook.Dispose()
End Sub

Private Sub hook_KeyPressed(sender As Object, e As KeyPressedEventArgs)
    ' show the keys pressed in a label.
    MsgBox(e.Modifier.ToString() + " + " + e.Key.ToString())
End Sub
Community
  • 1
  • 1
miroxlav
  • 11,796
  • 5
  • 58
  • 99
  • The class name shouldn't be keyboardhook because this isn't a keyboard hook. It's a class that registers a hotkey and listens for the WM_HOTKEY message. – Michael Z. Jul 03 '17 at 02:40
  • @MichaelZ. – a good catch. OTOH, this is only the port of the code from [the other answer](https://stackoverflow.com/a/27309185/2392157) so maybe you should preferably attach your comment there. – miroxlav Jul 03 '17 at 04:40