Click here to Skip to main content
11,712,925 members (85,514 online)
Click here to Skip to main content

Background applications listening for keyboard activity

, 19 Jan 2005 CPOL 108.7K 5K 90
Rate this:
Please Sign up or sign in to vote.
Class for capturing keyboard events.

Sample Image - KeyboardListener.gif

Introduction

The KeyboardListener class presented here allows capturing keyboard events even when another application is running in the foreground. Several threads inside your application might be listening for events in parallel, each handling the events in its own way.

In case you are only looking for a means to add hotkey support to your application, you might also want to take a look at the CodeProject article '.NET System wide hotkey component'. However, if you have other plans with respect to handling keyboard events inside your application, this KeyboardListener class might be the one you're looking for.

The KeyboardListener class can only be used on Windows XP because its implementation depends on the Win32 methods RegisterRawInputDevices and GetRawInputData which are currently only available on XP.

Using the code

1. The demo application

Let's start with the easy part: using the class inside an application. The demo application is a plain .NET application containing only one form that in turn only contains one text label (see demo project). The implementation of the Form1_Load method that is called whenever the form is loaded only adds a new EventHandler to the static s_KeyEventHandler event exported by the KeyboardListener class. Of course, you are free to add multiple EventHandler instances on several locations inside your application.

private void Form1_Load(object sender, System.EventArgs e)
{
    // Watch for keyboard activity
    KeyboardListener.s_KeyEventHandler += new 
           EventHandler(KeyboardListener_s_KeyEventHandler);
}

The KeyboardListener_s_KeyEventHandler method that is passed to the EventHandler constructor analyzes the arguments, updates the text label lblKeyPressed on the form with some key information, and changes the back color of the form depending on the action of the key: when a key is pressed the background becomes red; when it is released, it becomes green again.

private void KeyboardListener_s_KeyEventHandler(object sender, EventArgs e)
{
    KeyboardListener.UniversalKeyEventArgs eventArgs = 
                                (KeyboardListener.UniversalKeyEventArgs)e;
    lblKeyPressed.Text = string.Format("Key = {0}  Msg = {1}  Text = {2}", 
                     eventArgs.m_Key, eventArgs.m_Msg, eventArgs.KeyData);

    // 256 : key down    257 : key up
    if(eventArgs.m_Msg == 256)
    {
        this.BackColor = Color.Red;
    }
    else
    {
       this.BackColor = Color.Green;
    }
}

2. The KeyboardListener class

The KeyboardListener class is made up of three important parts. First, there is the already mentioned static EventHandler variable s_KeyEventHandler. Every application thread that is interested to receive keyboard events can subscribe to this event.

public static event EventHandler s_KeyEventHandler;

Second, there is the static KeyHandler method that is called whenever a keyboard event occurs. It loops across all EventHandler instances contained by s_KeyEventHandler calling each one with a UniversalKeyEventArgs instance that holds the key and message provided by the keyboard event. The advantage of looping across all delegates is that by adding a try/catch block to the loop, an EventHandler can fail while all other EventHandler instances still get called. In other words, one subscriber that fails doesn't prevent other subscribers from being called.

private static void KeyHandler(ushort key, uint msg)
{
    if(s_KeyEventHandler != null)
    {
        Delegate[] delegates = s_KeyEventHandler.GetInvocationList();

        foreach(Delegate del in delegates)
        {
            EventHandler sink = (EventHandler)del;

            try
            {
                // This is a static class, therefore null
                // is passed as the object reference
                sink(null,new UniversalKeyEventArgs(key,msg));
            }

            // You can add some meaningful code to this catch block
            catch{};
        }
    }
}

Last, there is the ListeningWindow class. As the name might suggest, an instance of such a class is used to listen. It listens for keyboard events to happen. In fact, there is only one instance of this class. This instance is created when the static constructor for the KeyboardListener class is called. Looking at the ListeningWindow constructor below, you see that it needs a delegate when it gets constructed. This delegate happens to be the KeyHandler mentioned above. So, while the application is running, whenever the ListeningWindow finds out about a keyboard event, it calls the KeyHandler delegate that in turn calls all installed EventHandlers.

public ListeningWindow(KeyDelegate keyHandlerFunction)
{
    m_KeyHandler = keyHandlerFunction;

    CreateParams cp = new CreateParams();

    // Fill in the CreateParams details.
    cp.Caption = "Hidden window";
    cp.ClassName = null;
    cp.X = 0x7FFFFFFF;
    cp.Y = 0x7FFFFFFF;
    cp.Height = 0;
    cp.Width = 0;
    cp.Style = WS_CLIPCHILDREN;

    // Create the actual invisible window
    this.CreateHandle(cp);

    // Register for Keyboard notification
    unsafe
    {
        try
        {
            RAWINPUTDEV myRawDevice = new RAWINPUTDEV();
            myRawDevice.usUsagePage = 0x01;
            myRawDevice.usUsage = 0x06;
            myRawDevice.dwFlags = RIDEV_INPUTSINK;
            myRawDevice.hwndTarget = this.Handle.ToPointer();

            if (RegisterRawInputDevices(&myRawDevice, 1, 
                         (uint)sizeof(RAWINPUTDEV)) == false) 
            {
                int err = Marshal.GetLastWin32Error();
                throw new Win32Exception(err, 
                       "ListeningWindow::RegisterRawInputDevices");
            }
        }

        catch {throw;}
    }
}

The ListeningWindow class, which is a subclass of the NativeWindow class, only contains two interesting definitions: the constructor and the WndProc method.

Inside the ListeningWindow constructor, an invisible window is created after which it is registered to receive keyboard input messages. These messages are analyzed by the WndProc method that is overriding the NativeWindow base class method. Whenever the WndProc method receives a keyboard message, it checks whether it differs from the previous message and if so, it calls the delegate installed inside the ListeningWindow constructor.

protected override void WndProc(ref Message m)
{
    ...
    receivedBytes = 
      (uint)GetRawInputData((RAWINPUTHKEYBOARD*)(m.LParam.ToPointer()), 
      RID_INPUT, lpb, &dwSize, sizeof_RAWINPUTHEADER);
    if ( receivedBytes == dwSize )
    {
        RAWINPUTHKEYBOARD* keybData = (RAWINPUTHKEYBOARD*)lpb;

        // Finally, analyze the data
        if(keybData->header.dwType == RIM_TYPEKEYBOARD)
        {
            if((m_PrevControlKey != keybData->VKey) || 
               (m_PrevMessage != keybData->Message))
            {
                m_PrevControlKey = keybData->VKey;
                m_PrevMessage = keybData->Message;

                // Call the delegate in case data satisfies
                m_KeyHandler(keybData->VKey,keybData->Message);
            }
        }
    }
    ...
}

Using PInvoke

The two methods that are key to the KeyboardListener class implementation are the RegisterRawInputDevices and the GetRawInputData methods. They enable capturing and handling keyboard events in the background. Since these calls are not supported by .NET, they are called via the PInvoke mechanism.

[DllImport("User32.dll",CharSet = CharSet.Ansi,SetLastError=true)] 
[return : MarshalAs(UnmanagedType.Bool)] 
internal static extern unsafe bool 
   RegisterRawInputDevices( RAWINPUTDEV* rawInputDevices, 
   uint numDevices, uint size);

[DllImport("User32.dll",CharSet = CharSet.Ansi,SetLastError=true)]
[return : MarshalAs(UnmanagedType.I4)]
internal static extern unsafe int GetRawInputData( void* hRawInput, 
   uint uiCommand, byte* pData, uint* pcbSize, uint cbSizeHeader);

Future ideas

  1. The UniversalKeyEventArgs is a sub-class of the EventArgs class and you might ask why we need it. Well, frankly, you can do without. But then you will have to assemble the correct Keys instance to pass as a parameter to the EventArgs constructor. In this release, I went for an easy way out that is certainly something that can be improved in a future release.
  2. Using the same technology, a MouseListener class can be created. See my next article.

History

  • 20 January 2005, Version 1.0.0, Initial release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Dominique Bijnens
Software Developer
Belgium Belgium
No Biography provided

You may also be interested in...

Comments and Discussions

 
GeneralMy vote of 5 Pin
Ibrahim Uylas14-Jan-13 3:52
memberIbrahim Uylas14-Jan-13 3:52 
QuestionApplication does not receive the keys Pin
Member 888548020-Nov-12 4:17
memberMember 888548020-Nov-12 4:17 
QuestionTrap keyboard events to this window only? Pin
myless14-Aug-12 15:48
membermyless14-Aug-12 15:48 
GeneralMy vote of 5 Pin
___PN___23-Jul-12 4:55
member___PN___23-Jul-12 4:55 
Questionthis really rocks....thanks alot for this info vote 5++++++++++++ Pin
omkarpardeshi12327-Nov-11 19:14
memberomkarpardeshi12327-Nov-11 19:14 
GeneralMy vote of 5 Pin
Sunil Soni6-Jul-11 2:03
memberSunil Soni6-Jul-11 2:03 
GeneralGreat prog Pin
vitalik221231-Aug-10 9:13
membervitalik221231-Aug-10 9:13 
Questionthanks and can we replace with joystick? Pin
mug8818-Jun-10 0:00
membermug8818-Jun-10 0:00 
GeneralDetect "Green Button" Pin
Hugo Caldeira2-Jul-09 8:10
memberHugo Caldeira2-Jul-09 8:10 
Hello, thanks for the article.

I'm searching for a way to intercept the button to launch the Windows Media Center.
Any idea on how to do it?
QuestionClass For VB.NET? Pin
eusta10-Jan-09 23:39
membereusta10-Jan-09 23:39 
General"Unregistering" raw Input Reading Pin
remixcr15-Nov-07 5:02
memberremixcr15-Nov-07 5:02 
GeneralKeyboard Hook Pin
glennzarb3-May-07 0:39
memberglennzarb3-May-07 0:39 
Generalto at a time =( Pin
sepehram30-Mar-07 11:00
membersepehram30-Mar-07 11:00 
GeneralLimit Listening to Current Application - Not O/S Pin
phanf30-Mar-07 8:55
memberphanf30-Mar-07 8:55 
GeneralAmazing Pin
Lev Vayner.14-Dec-06 11:50
memberLev Vayner.14-Dec-06 11:50 
QuestionCan I Get Focused Control? Pin
yclife29-Sep-06 5:41
memberyclife29-Sep-06 5:41 
GeneralExcellent Work By You.... Pin
Harshdeep Khatri12-Sep-06 3:38
memberHarshdeep Khatri12-Sep-06 3:38 
GeneralGood work ... Pin
Daniele Ferrero27-Jul-06 3:13
memberDaniele Ferrero27-Jul-06 3:13 
GeneralModifiers Pin
raven132430-May-06 15:04
memberraven132430-May-06 15:04 
GeneralRe: Modifiers Pin
raven132430-May-06 16:12
memberraven132430-May-06 16:12 
GeneralRe: Modifiers Pin
raven132431-May-06 3:11
memberraven132431-May-06 3:11 
GeneralShortkeys Pin
MoGiT13-Apr-06 2:57
memberMoGiT13-Apr-06 2:57 
GeneralMake it run in the background in invisible mode Pin
coosa10-Apr-06 13:52
membercoosa10-Apr-06 13:52 
GeneralRe: Make it run in the background in invisible mode Pin
Dominique Bijnens10-Apr-06 23:57
memberDominique Bijnens10-Apr-06 23:57 
GeneralRe: Make it run in the background in invisible mode Pin
Lev Vayner.14-Dec-06 12:22
memberLev Vayner.14-Dec-06 12:22 
QuestionWhen this class is called??? Pin
Alex Cutovoi3-Oct-05 3:32
memberAlex Cutovoi3-Oct-05 3:32 
AnswerRe: When this class is called??? Pin
Dominique Bijnens3-Oct-05 23:56
memberDominique Bijnens3-Oct-05 23:56 
GeneralGetting Twice the same keycode data Pin
Jatin198414-Nov-12 0:42
memberJatin198414-Nov-12 0:42 
GeneralGetting Twice the same keycode data Pin
Jatin198414-Nov-12 0:43
memberJatin198414-Nov-12 0:43 
GeneralKeyboard Detection Pin
Babu Aboobacker E.I31-Jan-05 18:04
memberBabu Aboobacker E.I31-Jan-05 18:04 
GeneralMouseListener Pin
Andrea Cioli27-Jan-05 23:42
memberAndrea Cioli27-Jan-05 23:42 
GeneralRe: MouseListener Pin
Dominique Bijnens30-Jan-05 21:21
memberDominique Bijnens30-Jan-05 21:21 
GeneralRe: MouseListener Pin
Andrea Cioli4-Feb-05 6:12
memberAndrea Cioli4-Feb-05 6:12 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150819.1 | Last Updated 20 Jan 2005
Article Copyright 2005 by Dominique Bijnens
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid