DekGenius.com
[ Team LiB ] Previous Section Next Section

Recipe 7.10 Using the Windows Keyboard Hook

Problem

You need to watch and respond to specific user keyboard input, and, based on the input, you want to perform one or more actions. For example, pressing the Windows key and the E key at the same time launches Windows Explorer. You would like to add other Windows key combinations for your own applications. In addition, you could prevent the user from using specific keys (such as the Windows key) from within your application.

Solution

The following Windows Forms application uses the WH_KEYBOARD Windows hook:

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace WindowsApplication2
{
    public class Form1 : System.Windows.Forms.Form
    {
        // Required designer variable.
        private System.ComponentModel.Container components = null;

        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.Button button2;
        private System.Windows.Forms.TextBox textBox1;

        public Form1( )
        {
            // Required for Windows Form Designer support
            InitializeComponent( );
        }

        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if (components != null) 
                {
                    components.Dispose( );
                }
            }
            base.Dispose( disposing );
        }

        #region Windows Form Designer generated code
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent( )
        {
            this.button1 = new System.Windows.Forms.Button( );
            this.button2 = new System.Windows.Forms.Button( );
            this.textBox1 = new System.Windows.Forms.TextBox( );
            this.SuspendLayout( );
            // 
            // button1
            // 
            this.button1.Name = "button1";
            this.button1.TabIndex = 0;
            this.button1.Text = "Start";
            this.button1.Click += 
              new System.EventHandler(this.button1_Click);
            // 
            // button2
            // 
            this.button2.Location = new System.Drawing.Point(0, 48);
            this.button2.Name = "button2";
            this.button2.TabIndex = 1;
            this.button2.Text = "End";
            this.button2.Click += 
              new System.EventHandler(this.button2_Click);
            // 
            // textBox1
            // 
            this.textBox1.Location = new System.Drawing.Point(80, 0);
            this.textBox1.Multiline = true;
            this.textBox1.Name = "textBox1";
            this.textBox1.ScrollBars = 
              System.Windows.Forms.ScrollBars.Vertical;
            this.textBox1.Size = new System.Drawing.Size(752, 504);
            this.textBox1.TabIndex = 2;
            this.textBox1.Text = "";
            this.textBox1.WordWrap = false;
            // 
            // Form1
            // 
            this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
            this.ClientSize = new System.Drawing.Size(832, 509);
            this.Controls.AddRange(new System.Windows.Forms.Control[] {
                                        this.textBox1,
                                        this.button2,
                                        this.button1});
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);
        }
        #endregion

        [STAThread]
        static void Main( ) 
        {
            Application.Run(new Form1( ));
        }

        // Declare Windows API calls used to access Windows hooks
        [DllImport("user32.dll")]
        public static extern int SetWindowsHookEx(int hookType, 
                                                   HookProc callback, 
                                                   int instance, 
                                                   int threadID);
        [DllImport("user32.dll")]
        public static extern int CallNextHookEx(int hookHandle, int code, 
                                                int wparam, int lparam);
        [DllImport("user32.dll")]
        public static extern bool UnhookWindowsHookEx(int hookHandle);
        [DllImport("user32.dll")]
        public static extern int GetAsyncKeyState(int vKey);

        // Fields, constants, and structures used by the keyboard hook
        int hookHandle = 0;
        HookProc cb = null;

        public const int WH_KEYBOARD = 2;

        public const int HC_ACTION = 0;
        public const int HC_NOREMOVE = 3;

        public const int VK_CONTROL = 0x11;
        public const int VK_LWIN = 0x5B;
        public const int VK_RWIN = 0x5C;
        public const int VK_APPS = 0x5D;
        public const int VK_LSHIFT = 0xA0;
        public const int VK_RSHIFT = 0xA1;
        public const int VK_LCONTROL = 0xA2;
        public const int VK_RCONTROL = 0xA3;
        public const int VK_LMENU = 0xA4;
        public const int VK_RMENU = 0xA5;
        public const int VK_BROWSER_BACK = 0xA6;
        public const int VK_BROWSER_FORWARD = 0xA7;
        public const int VK_BROWSER_REFRESH = 0xA8;
        public const int VK_BROWSER_STOP = 0xA9;
        public const int VK_BROWSER_SEARCH = 0xAA;
        public const int VK_VOLUME_MUTE = 0xAD;
        public const int VK_VOLUME_DOWN = 0xAE;
        public const int VK_VOLUME_UP = 0xAF;
        public const int VK_MEDIA_NEXT_TRACK = 0xB0;
        public const int VK_MEDIA_PREV_TRACK = 0xB1;
        public const int VK_MEDIA_STOP = 0xB2;
        public const int VK_MEDIA_PLAY_PAUSE = 0xB3;

        // Keyboard hook delegate
        public delegate int HookProc(int code, int wparam, int lparam);

        public int Proc(int code, int wparam, int lparam)
        {
            if (code == HC_ACTION)
            {
                switch (wparam)
                {
                    case VK_BROWSER_BACK:
                        // Handle Back keyboard button here
                        textBox1.Text += "Browser Back key caught" + 
                                         Environment.NewLine;
                        break;
                    case VK_BROWSER_FORWARD:
                        // Handle Forward keyboard button here
                        textBox1.Text += "Browser Forward key caught" + 
                                         Environment.NewLine;
                        break;
                    case VK_BROWSER_REFRESH:
                        // Handle Refresh keyboard button here
                        textBox1.Text += "Browser Refresh key caught" + 
                                         Environment.NewLine;
                        break;
                    case VK_BROWSER_STOP:
                        // Handle Stop keyboard button here
                        textBox1.Text += "Browser Stop key caught" + 
                                         Environment.NewLine;
                        break;
                    case VK_BROWSER_SEARCH:
                        // Handle Search keyboard button here
                        textBox1.Text += "Browser Search key caught" + 
                                         Environment.NewLine;
                        break;
                    case VK_VOLUME_MUTE:
                        // Handle Mute keyboard button here
                        textBox1.Text += "Volume Mute key caught" + 
                                         Environment.NewLine;
                        break;
                    case VK_VOLUME_DOWN:
                        // Handle Volume - keyboard button here
                        textBox1.Text += "Volume Down key caught" + 
                                         Environment.NewLine;
                        break;
                    case VK_VOLUME_UP:
                        // Handle Volume + keyboard button here
                        textBox1.Text += "Volume Up key caught" + 
                                         Environment.NewLine;
                        break;
                    case VK_MEDIA_NEXT_TRACK:
                        // Handle Next Track keyboard button here
                        textBox1.Text += "Media Next Track key caught" + 
                                         Environment.NewLine;
                        break;
                    case VK_MEDIA_PREV_TRACK:
                        // Handle Previous Track keyboard button here
                        textBox1.Text += "Media Previous Track key caught" + 
                                         Environment.NewLine;
                        break;
                    case VK_MEDIA_STOP:
                        // Handle Stop keyboard button here
                        textBox1.Text += "Media Stop key caught" + 
                                         Environment.NewLine;
                        break;
                    case VK_MEDIA_PLAY_PAUSE:
                        // Handle Play keyboard button here
                        textBox1.Text += "Media Play/Pause key caught" + 
                                         Environment.NewLine;
                        break;
                }
            }
            return (CallNextHookEx(hookHandle, code, wparam, lparam));
        }

        // Click event handlers for button1 and button2
        private void button1_Click(object sender, System.EventArgs e)
        {
            // Set the keyboard hook
            if (hookHandle == 0)
            {
                cb = new HookProc(Proc);
                hookHandle = SetWindowsHookEx(WH_KEYBOARD, cb, 0, 
                                              AppDomain.GetCurrentThreadId( ));
            }
            else
            {
                textBox1.Text += "Hook already set" + Environment.NewLine;
            }
            textBox1.Text += "Start: " + hookHandle + Environment.NewLine;
        }
    
        private void button2_Click(object sender, System.EventArgs e)
        {
            // Unhook the keyboard hook
            textBox1.Text += "End: " + UnhookWindowsHookEx(hookHandle) + 
                             Environment.NewLine;
            hookHandle = 0;
        }
    }
}

Discussion

The hooks provided by the Windows operating system allow for very powerful code to be written with a minimum of work. The hook used in this recipe is the WH_KEYBOARD hook, which watches messages that are generated by the keyboard.

The WH_KEYBOARD hook allows keyboard messages to be watched or discarded. To discard a keyboard message, return a 1 from the Proc hook callback method. The HookProc delegate is used as the method to which the keyboard hook calls back whenever a keyboard message is received. This hook does not allow the message to be modified.

To use a hook, as the code in the Solution section shows, you first need to declare the following three Windows API functions:


SetWindowsHookEx

This API creates the hook specified by the first parameter and attaches it to the callback method specified in the second parameter. The return value of this function is the handle to the newly created hook. This handle needs to be stored so that it can later be used to remove the hook.


CallNextHookEx

This API calls the next hook in the hook chain if SetWindowsHookEx has been called multiple times for a single type of hook. The return value is dependent on the type of hook that is installed.


UnhookWindowsHookEx

This API removes the callback to the hook specified by the hook handle passed as its only parameter. The hook handle is returned by the SetWindowsHookEx method. This hook handle is returned by the SetWindowHookEx function.

Once these functions are declared, the next step is to declare the delegate for the hook callback method. This hook callback method is automatically invoked whenever a keyboard message is sent. The return value of both the delegate and callback methods is the return value of the CallNextHookEx API method.

The keyboard hook used in this recipe will intercept only messages that are sent to the message queue of the thread on which the hook is installed. The thread on which to install the hook is passed as the fourth argument of the SetWindowsHookEx API method. For this recipe, the current thread is passed as an argument using the static AppDomain.GetCurrentThreadId method. Therefore, if you have a multithreaded application and you want each thread to intercept messages sent by the keyboard, you will have to call SetWindowsHookEx on each thread to set up the WH_KEYBOARD hook.

The keyboard hook can also be used to capture keys pressed in combination. For example, if the Windows Menu key is pressed along with the V key, a keyboard hook callback procedure can be implemented to capture this action:

// Hook callback method
public int Proc(int code, int wparam, int lparam)
{
    if (code == HC_ACTION)
    {
        // Check the state of the Window's keyboard Pop-Up Menu key
        int state = GetAsyncKeyState(VK_APPS);

        // Is the Menu key already down?
        if ((state & 0x8000) == 0x8000)
        {
            // Is the key up?
            if ((lparam & 0x80000000) == 0x80000000)
            {
                // Is this the v key?
                if (wparam == 0x56)
                {
                    // Handle AppMenu-v key combination here...
                    textBox1.Text += "AppMenu-v action caught" + 
                                     Environment.NewLine;
                }
            }
        }
    }                
    return (CallNextHookEx(hookHandle, code, wparam, lparam));
}

This callback gets the state of the Menu key and determines whether it is depressed ((state & 0x8000) == 0x8000). If it is depressed, the V key is checked to see if it is being released ((lparam & 0x80000000) == 0x80000000). If these conditions are true, a message is displayed. (Of course, you could add your own code here to do something more interesting.)

See Also

See Recipe 7.11; Subclassing & Hooking with Visual Basic by Stephen Teilhet (O'Reilly); and see the "Delegate Class" and "Hooks" topics in the MSDN documentation.

    [ Team LiB ] Previous Section Next Section