Click here to Skip to main content
15,891,204 members
Articles / Programming Languages / C#

Processing Global Mouse and Keyboard Hooks in C#

Rate me:
Please Sign up or sign in to vote.
4.97/5 (614 votes)
31 Aug 2011CPOL6 min read 17.1M   218.6K   1K   1.4K
This class allows you to tap keyboard and mouse and/or to detect their activity even when an application runs in the background or does not have any user interface at all.

NEWS

This article was posted in 2004 and updated in 2006 and 2008. During all this time until now I receive a lot of positive feedback and recommendations. There where also many useful contributions which where usually posted as code snippets in forum. Now instead of publishing yet another version on my own I decided to ask you all to actively participate. So please be enthusiastic, feel free to join the project at globalmousekeyhook.codeplex.com

You can help by:
  • Contributing code.
  • Creating issue items requesting additional features or reporting defects.
  • Voting for features and fixes you are interested in.
  • Testing the component in different environments.
  • Writing developer documentation.

This will us also allow to keep this original article up to date.

Thank you all for all your great comments on CodeProject forum and looking forward for your brilliant contributions at globalmousekeyhook.codeplex.com

Introduction

This class allows you to tap keyboard and mouse and/or to detect their activity even when an application runs in the background or does not have any user interface at all. This class raises common .NET events with KeyEventArgs and MouseEventArgs, so you can easily retrieve any information you need.

Background

There are a number of applications that run in the background and detect user inactivity to change their mode. For example, MSN Messenger (or any other messenger). I was going to write such an application, so I searched MSDN and found "exactly" what I needed: 318804 - HOW TO: Set a Windows Hook in Visual C# .NET. This article describes how to tap the mouse movement, but it works only when an application is active. At the end of this article, I found this explanation: "Global hook is not supported in .NET Framework. You cannot implement global hooks in Microsoft .NET Framework...". Anyway, I continued my research and found out that there are exceptions. There are WH_KEYBOARD_LL and WH_MOUSE_LL hooks that can be installed globally. So, I have basically replaced WH_MOUSE with WH_MOUSE_LL in the MSDN example, and it works.

The second step was to extract the information received into a .NET EventArgs and raise the appropriate events.

I found a similar article in CodeProject, under Global System Hooks in .NET by Michael Kennedy, but what I dislike is, there is an unmanaged DLL in C++ that is a main part of this solution. This unmanaged DLL is in C++, and a number of classes make it complicated to integrate it in my own tiny application.

Revisions

This article was posted in 2004 and updated in 2006. During all this time until now I receive a lot of positive feedback and recommendations. There were also a number of technology improvements like .NET Framework 3.5 or Visual Studio 2008. So I have decided to update it once more.

I have refactored and improved the solution, made it more flexible, stable and efficient. But this refactoring also had some drawbacks:

  1. Number of code lines and files has grown.
  2. Backward compatibility to older .NETs is lost.

That's why I attend to leave the old version also to be available for download.

Using the Code [Version 2]

The Simple Way

If you are developing a Windows Forms application and prefer drag & drop programming, there is a component named GlobalEventProvider inside the assembly Gma.UserActivityMonitor.dll. Just drag and drop it to your form and create events using the property editor events tab.

The Alternative Way

Use events provided by the static class HookManager. Note that the sender object in events is always null.

For more usage hints, see the source code of the attached demo application.

Using the Code [Version 1]

To use this class in your application, you need to just create an instance of it and hang on events you would like to process. Hooks are automatically installed when the object is created, but you can stop and start listening using appropriate public methods.

C#
UserActivityHook actHook;
void MainFormLoad(object sender, System.EventArgs e)
{
    actHook= new UserActivityHook(); // crate an instance

    // hang on events

    actHook.OnMouseActivity+=new MouseEventHandler(MouseMoved);
    actHook.KeyDown+=new KeyEventHandler(MyKeyDown);
    actHook.KeyPress+=new KeyPressEventHandler(MyKeyPress);
    actHook.KeyUp+=new KeyEventHandler(MyKeyUp);
}

Now, an example of how to process an event:

C#
public void MouseMoved(object sender, MouseEventArgs e)
{
    labelMousePosition.Text=String.Format("x={0}  y={1}", e.X, e.Y);
    if (e.Clicks>0) LogWrite("MouseButton     - " + e.Button.ToString());
}

Changes and Updates from [Version 0] to [Version 1]

I'd like to thank you all for all the useful comments in the discussion forum. There were a lot of bugs and proposals posted after this article was published on 4th June, 2004. Over and over again came the same topics, and I had to refer to previous posts in the discussion, that is why I have decided to revise the code and publish a FAQ. Here is the list of the most important changes:

  • The project was converted into Visual Studio 2005
  • The problem with upper case characters is solved
  • Mouse wheel information is now included in event arguments
  • Better exception handling
  • Cancellation of keyboard events using the Handled property of event arguments
  • XML documentation of functions

FAQ [Version 1]

Question

The project cannot be run in Visual Studio .NET 2005 in debug mode because of an exception error caused by calling the SetWindowsHookEx. Why? Is it a problem of .NET 2.0?

Answer

The compiled release version works well so that cannot be a .NET 2.0 problem. To workaround, you just need to uncheck the check box in the project properties that says: "Enable Visual Studio hosting process". In the menu: Project -> Project Properties... -> Debug -> Enable the Visual Studio hosting process.

Question

I need to suppress some keystrokes after I have processed them.

Answer

Just set the e.Handled property to true in the key events you have processed. It prevents the keystrokes being processed by other applications.

Question:

Is it possible to convert your global hooks to application hooks which capture keystrokes and mouse movements only within the application?

Answer

Yes. Just use...

C#
private const int WH_MOUSE = 7;
private const int WH_KEYBOARD = 2;

... everywhere, instead of:

C#
private const int WH_MOUSE_LL = 14;
private const int WH_KEYBOARD_LL = 13;

Question

Does it work on Win98 (Windows ME, Windows 95)?

Answer

Yes and No. The global hooks WH_MOUSE_LL and WH_KEYBOARD_LL can be monitored only under Windows NT/2000/XP. In other cases, you can only use application hooks (WH_MOUSE and WH_KEYBOARD) which capture keystrokes and mouse movement only within the application.

Question

I have a long delay when closing applications using hooks by clicking the x button in the titlebar. If I close the application via another event (button click) for example, that works fine.

Answer

It's a known bug of Microsoft. It has to do with the Windows themes. If you disable the Windows themes, the problem goes away. Another choice is to have the hook code run in a secondary thread.

Question

How do I catch key combinations like Ctrl+Shift+A?

Answer

You'll have to track which keys have gone down but not up. Only the most recently pressed key keeps sending KeyDown messages, but the others will still send a KeyUp when released. So if you make three flags IsCtrlDown, IsShiftDown, and IsADown, and set them to true at KeyDown and false at KeyUp, the expression (IsCtrlDown && IsShiftDown && IsADown) will give you the required result.

Question

Does it works with .NET Framework 1.1 and Visual Studio 2003?

Answer

Yes. The file UserActivityHook.cs can be used without any changes, in a Visual Studio 2003 .NET 1.1 project.

License

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


Written By
Software Developer
Germany Germany
Tweeter: @gmamaladze
Google+: gmamaladze
Blog: gmamaladze.wordpress.com

Comments and Discussions

 
GeneralRe: Global Hook in Pocket PC Pin
XBSANTOS12-Mar-05 6:39
XBSANTOS12-Mar-05 6:39 
GeneralRe: Global Hook in Pocket PC Pin
XBSANTOS13-Mar-05 22:16
XBSANTOS13-Mar-05 22:16 
GeneralRe: Global Hook in Pocket PC Pin
jtknrhut3-Jun-05 11:31
jtknrhut3-Jun-05 11:31 
GeneralRe: Global Hook in Pocket PC Pin
XBSANTOS11-Jun-05 3:03
XBSANTOS11-Jun-05 3:03 
GeneralRe: Global Hook in Pocket PC Pin
howardchang9-Oct-08 0:13
howardchang9-Oct-08 0:13 
GeneralRe: Global Hook in Pocket PC Pin
amritametri25-Nov-08 0:12
amritametri25-Nov-08 0:12 
AnswerRe: Global Hook in Pocket PC Pin
Paul Heil28-Oct-10 6:32
Paul Heil28-Oct-10 6:32 
GeneralWorking VB.net code and Proper Capturing of Control Chars Pin
myrat24-Feb-05 17:23
myrat24-Feb-05 17:23 
Thanks for your excellent work! I've ported it to VB.net.
I've modified the code a bit to make it properly pass control chars (control/shift/alt) to the event arguement in the events raised.

One problem: the keypress event may cause some problems... So I've commented it as i don't need it anyway. Smile | :)

Imports System
Imports System.Runtime.InteropServices
Imports System.Reflection
Imports System.Threading
Imports System.Windows.Forms

Public Class UserActivityHook
Inherits Object

Public Sub New()
Start()
End Sub

Protected Overrides Sub Finalize()
StopMe()
End Sub

Public Event OnMouseActivity As MouseEventHandler
Public Event KeyDown As KeyEventHandler
Public Event KeyPress As KeyPressEventHandler
Public Event KeyUp As KeyEventHandler

Public Delegate Function HookProc(ByVal nCode As Integer, ByVal wParam As Int32, ByVal lParam As IntPtr) As Integer
Shared hMouseHook As Integer = 0
Shared hKeyboardHook As Integer = 0
Public Const WH_MOUSE_LL As Integer = 14
Public Const WH_KEYBOARD_LL As Integer = 13
Private MouseHookProcedure As HookProc
Private KeyboardHookProcedure As HookProc

<structlayout(layoutkind.sequential)> _
Public Class POINT
Public x As Integer
Public y As Integer
End Class

<structlayout(layoutkind.sequential)> _
Public Class MouseHookStruct
Public pt As POINT
Public hwnd As Integer
Public wHitTestCode As Integer
Public dwExtraInfo As Integer
End Class

<structlayout(layoutkind.sequential)> _
Public Class KeyboardHookStruct
Public vkCode As Integer
Public scanCode As Integer
Public flags As Integer
Public time As Integer
Public dwExtraInfo As Integer
End Class

Declare Auto Function SetWindowsHookEx Lib "user32.dll" (ByVal idHook As Integer, ByVal lpfn As HookProc, ByVal hInstance As IntPtr, ByVal threadId As Integer) As Integer

Declare Auto Function UnhookWindowsHookEx Lib "user32.dll" (ByVal idHook As Integer) As Boolean

Declare Auto Function CallNextHookEx Lib "user32.dll" (ByVal idHook As Integer, ByVal nCode As Integer, ByVal wParam As Int32, ByVal lParam As IntPtr) As Integer

Public Sub Start()
If hMouseHook = 0 Then
MouseHookProcedure = AddressOf MouseHookProc
hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProcedure, Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly.GetModules()(0)), 0)
If hMouseHook = 0 Then
StopMe()
Throw New Exception("SetWindowsHookEx failed.")
End If
End If
If hKeyboardHook = 0 Then
KeyboardHookProcedure = AddressOf KeyboardHookProc
hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly.GetModules()(0)), 0)
If hKeyboardHook = 0 Then
StopMe()
Throw New Exception("SetWindowsHookEx ist failed.")
End If
End If
End Sub

Public Sub StopMe()
Dim retMouse As Boolean = True
Dim retKeyboard As Boolean = True
If Not (hMouseHook = 0) Then
retMouse = UnhookWindowsHookEx(hMouseHook)
hMouseHook = 0
End If
If Not (hKeyboardHook = 0) Then
retKeyboard = UnhookWindowsHookEx(hKeyboardHook)
hKeyboardHook = 0
End If
If Not (retMouse AndAlso retKeyboard) Then
Throw New Exception("UnhookWindowsHookEx failed.")
End If
End Sub

Private Const WM_MOUSEMOVE As Integer = 512
Private Const WM_LBUTTONDOWN As Integer = 513
Private Const WM_RBUTTONDOWN As Integer = 516
Private Const WM_MBUTTONDOWN As Integer = 519
Private Const WM_LBUTTONUP As Integer = 514
Private Const WM_RBUTTONUP As Integer = 517
Private Const WM_MBUTTONUP As Integer = 520
Private Const WM_LBUTTONDBLCLK As Integer = 515
Private Const WM_RBUTTONDBLCLK As Integer = 518
Private Const WM_MBUTTONDBLCLK As Integer = 521

Private Function MouseHookProc(ByVal nCode As Integer, ByVal wParam As Int32, ByVal lParam As IntPtr) As Integer
If (nCode >= 0) Then
Dim button As MouseButtons = MouseButtons.None
Select Case wParam
Case WM_LBUTTONDOWN
button = MouseButtons.Left
' break
Case WM_RBUTTONDOWN
button = MouseButtons.Right
' break
End Select
Dim clickCount As Integer = 0
If Not (button = MouseButtons.None) Then
If wParam = WM_LBUTTONDBLCLK OrElse wParam = WM_RBUTTONDBLCLK Then
clickCount = 2
Else
clickCount = 1
End If
End If
Dim MyMouseHookStruct As MouseHookStruct = CType(Marshal.PtrToStructure(lParam, GetType(MouseHookStruct)), MouseHookStruct)
Dim e As MouseEventArgs = New MouseEventArgs(button, clickCount, MyMouseHookStruct.pt.x, MyMouseHookStruct.pt.y, 0)
RaiseEvent OnMouseActivity(Me, e)
End If
Return CallNextHookEx(hMouseHook, nCode, wParam, lParam)
End Function

Declare Auto Function ToAscii Lib "user32" (ByVal uVirtKey As Integer, ByVal uScanCode As Integer, ByVal lpbKeyState As Byte(), ByVal lpwTransKey As Byte(), ByVal fuState As Integer) As Integer

Declare Auto Function GetKeyboardState Lib "user32" (ByVal pbKeyState As Byte()) As Integer

Private Const WM_KEYDOWN As Integer = 256
Private Const WM_KEYUP As Integer = 257
Private Const WM_SYSKEYDOWN As Integer = 260
Private Const WM_SYSKEYUP As Integer = 261

Private Key_Control_Down As Boolean = False
Private Key_Shift_Down As Boolean = False
Private Key_Alt_Down As Boolean = False

Private Function KeyboardHookProc(ByVal nCode As Integer, ByVal wParam As Int32, ByVal lParam As IntPtr) As Integer
If (nCode >= 0) Then
Dim MyKeyboardHookStruct As KeyboardHookStruct = CType(Marshal.PtrToStructure(lParam, GetType(KeyboardHookStruct)), KeyboardHookStruct)
If (wParam = WM_KEYDOWN OrElse wParam = WM_SYSKEYDOWN) Then
Dim keyData As Keys = CType(MyKeyboardHookStruct.vkCode, Keys)
Select Case keydata
Case Keys.LControlKey, Keys.RControlKey
Key_Control_Down = True
Case Keys.LShiftKey, Keys.RShiftKey
Key_Shift_Down = True
Case Keys.LMenu, Keys.RMenu
Key_Alt_Down = True
End Select

If Key_Control_Down Then
keydata = keydata Or Keys.Control
End If
If Key_Shift_Down Then
keydata = keydata Or Keys.Shift
End If
If Key_Alt_Down Then
keydata = keydata Or Keys.Alt
End If

Dim e As KeyEventArgs = New KeyEventArgs(keyData)
RaiseEvent KeyDown(Me, e)
End If
'If wParam = WM_KEYDOWN Then
' Dim keyState(-1) As Byte
' GetKeyboardState(keyState)
' Dim inBuffer(-1) As Byte
' If ToAscii(MyKeyboardHookStruct.vkCode, MyKeyboardHookStruct.scanCode, keyState, inBuffer, MyKeyboardHookStruct.flags) = 1 Then
' Dim e As KeyPressEventArgs = New KeyPressEventArgs(ChrW(inBuffer(0)))
' RaiseEvent KeyPress(Me, e)
' End If
'End If
If (wParam = WM_KEYUP OrElse wParam = WM_SYSKEYUP) Then
Dim keyData As Keys = CType(MyKeyboardHookStruct.vkCode, Keys)
Select Case keydata
Case Keys.LControlKey, Keys.RControlKey
Key_Control_Down = False
Case Keys.LShiftKey, Keys.RShiftKey
Key_Shift_Down = False
Case Keys.LMenu, Keys.RMenu
Key_Alt_Down = False
End Select

If Key_Control_Down Then
keydata = keydata Or Keys.Control
End If
If Key_Shift_Down Then
keydata = keydata Or Keys.Shift
End If
If Key_Alt_Down Then
keydata = keydata Or Keys.Alt
End If

Dim e As KeyEventArgs = New KeyEventArgs(keyData)
RaiseEvent KeyUp(Me, e)
End If
End If
Return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam)
End Function
End Class
GeneralRe: Working VB.net code and Proper Capturing of Control Chars Pin
crs38611-May-05 4:32
crs38611-May-05 4:32 
Generalgood Pin
Rogers Fei1-Feb-05 19:46
Rogers Fei1-Feb-05 19:46 
GeneralException: SetWindowsHookEx failed Pin
conorato20-Jan-05 5:06
conorato20-Jan-05 5:06 
GeneralRe: Exception: SetWindowsHookEx failed Pin
George Mamaladze20-Jan-05 10:54
George Mamaladze20-Jan-05 10:54 
GeneralRe: Exception: SetWindowsHookEx failed Pin
mix_e_mann26-Jan-05 19:05
sussmix_e_mann26-Jan-05 19:05 
Generalfind active application Pin
Harmen13-Jan-05 1:40
Harmen13-Jan-05 1:40 
QuestionCan this technique be used for other hook types Pin
dperezcar4-Jan-05 20:47
dperezcar4-Jan-05 20:47 
QuestionHow can I replace output characters ? Pin
remushka24-Dec-04 13:54
remushka24-Dec-04 13:54 
AnswerRe: How can I replace output characters ? Pin
Radu Stanciu7-Feb-05 8:07
Radu Stanciu7-Feb-05 8:07 
AnswerRe: How can I replace output characters ? Pin
Shrish Pandit27-Mar-11 1:17
Shrish Pandit27-Mar-11 1:17 
QuestionHow to capture the control keys Pin
akarwa27-Nov-04 15:41
akarwa27-Nov-04 15:41 
AnswerRe: How to capture the control keys Pin
George Mamaladze28-Nov-04 4:21
George Mamaladze28-Nov-04 4:21 
GeneralI need your UserActivityHook class Pin
MaskPZ23-Nov-04 12:32
MaskPZ23-Nov-04 12:32 
GeneralmApplication ative Pin
AleDFC18-Nov-04 9:05
AleDFC18-Nov-04 9:05 
GeneralRe: mApplication ative Pin
George Mamaladze19-Nov-04 2:54
George Mamaladze19-Nov-04 2:54 
GeneralRe: Application ative Pin
AleDFC19-Nov-04 3:38
AleDFC19-Nov-04 3:38 
GeneralRe: Application ative Pin
George Mamaladze19-Nov-04 3:44
George Mamaladze19-Nov-04 3:44 

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.