Friday, January 06, 2006
« window.open sizing | Main | VS2005 - vshost »

Sometimes, it is useful to be able to track windows events even when your application doesn't have focus.  In order to accomplish this, you can use the function SetWindowsHookEx.  Below is an example of using this API in .NET and then wrapping the events with managed mouse and keyboard events.  The code is not production strength, but hopefully, it will give you a good start.  Let me know if you have suggestions for improvements, and I'll update the post.  Here are two other sources on the subject:

Here's my implementation (some of the code follows): 13.5k GlobalHook2.0.zip

Class GlobalHook

Imports System.Runtime.InteropServices

Imports System.Windows.Forms

 

Public Class GlobalHook : Implements IDisposable

 

    Public Event MouseDown As MouseEventHandler

    Public Event MouseMove As MouseEventHandler

    Public Event MouseUp As MouseEventHandler

    Public Event MouseWheel As MouseEventHandler

 

    Public Event KeyDown As KeyEventHandler

    Public Event KeyPress As KeyPressEventHandler

    Public Event KeyUp As KeyEventHandler

 

#Region " CTOR/DTOR "

 

    Public Sub New()

        InstallHooks()

    End Sub

 

    Protected Overrides Sub Finalize()

        Dispose()

        MyBase.Finalize()

    End Sub

 

    Public Sub Dispose() Implements System.IDisposable.Dispose

        RemoveHooks()

    End Sub

 

#End Region

 

#Region " PROTECTED OnXXXX SUBS "

 

    Protected Overridable Sub OnMouseDown(ByVal e As MouseEventArgs)

        'Debug.WriteLine("OnMouseDown")

        RaiseEvent MouseDown(Me, e)

    End Sub

 

    Protected Overridable Sub OnMouseMove(ByVal e As MouseEventArgs)

        'Debug.WriteLine("OnMouseMove")

        RaiseEvent MouseMove(Me, e)

    End Sub

 

    Protected Overridable Sub OnMouseUp(ByVal e As MouseEventArgs)

        'Debug.WriteLine("OnMouseUp")

        RaiseEvent MouseUp(Me, e)

    End Sub

 

    Protected Overridable Sub OnMouseWheel(ByVal e As MouseEventArgs)

        'Debug.WriteLine("OnMouseWheel")

        RaiseEvent MouseWheel(Me, e)

    End Sub

 

    Protected Overridable Sub OnKeyDown(ByVal e As KeyEventArgs)

        'Debug.WriteLine("OnKeyDown")

        For Each handler As KeyEventHandler In KeyDownEvent.GetInvocationList

            handler.Invoke(Me, e)

            If e.Handled Then

                Exit For

            End If

        Next

    End Sub

 

    Protected Overridable Sub OnKeyUp(ByVal sender As Object, ByVal e As KeyEventArgs)

        'Debug.WriteLine("OnKeyUp")

        For Each handler As KeyEventHandler In KeyUpEvent.GetInvocationList

            handler.Invoke(Me, e)

            If e.Handled Then

                Exit For

            End If

        Next

    End Sub

 

    Protected Overridable Sub OnKeyPress(ByVal sender As Object, ByVal e As KeyPressEventArgs)

        'Debug.WriteLine("OnKeyPress")

        For Each handler As KeyPressEventHandler In KeyPressEvent.GetInvocationList

            handler.Invoke(Me, e)

            If e.Handled Then

                Exit For

            End If

        Next

    End Sub

 

#End Region

 

    Private Shared hMouseHook As Integer = 0

    Private Shared hKeyboardHook As Integer = 0

 

    Private MouseHookProcedure As Win32.HookProc

    Private KeyboardHookProcedure As Win32.HookProc

 

    Public Sub InstallHooks()

        If hMouseHook = 0 Then

            MouseHookProcedure = New Win32.HookProc(AddressOf MouseHookProc)

 

            hMouseHook = Win32.SetWindowsHookEx( _

                Win32.WH.WH_MOUSE_LL, _

                MouseHookProcedure, _

                Marshal.GetHINSTANCE(Reflection.Assembly.GetExecutingAssembly().GetModules()(0)), _

                0)

 

            If hMouseHook = 0 Then 'SetWindowsHookEx failed

                RemoveHooks()

                Throw New Exception("SetWindowsHookEx failed.")

            End If

        End If

 

        If hKeyboardHook = 0 Then ' install Keyboard hook

            KeyboardHookProcedure = New Win32.HookProc(AddressOf KeyboardHookProc)

            hKeyboardHook = Win32.SetWindowsHookEx( _

                Win32.WH.WH_KEYBOARD_LL, _

                KeyboardHookProcedure, _

                Marshal.GetHINSTANCE(Reflection.Assembly.GetExecutingAssembly().GetModules()(0)), _

                0)

 

            If (hKeyboardHook = 0) Then 'SetWindowsHookEx failed

                RemoveHooks()

                Throw New Exception("SetWindowsHookEx failed.")

            End If

        End If

    End Sub

 

    Public Sub RemoveHooks()

        Dim mouseResult As Boolean = True

        Dim keyboardResult As Boolean = True

 

        If hMouseHook <> 0 Then

            mouseResult = Win32.UnhookWindowsHookEx(hMouseHook)

            hMouseHook = 0

        End If

 

        If hKeyboardHook <> 0 Then

            keyboardResult = Win32.UnhookWindowsHookEx(hKeyboardHook)

            hKeyboardHook = 0

        End If

 

        If Not (mouseResult And keyboardResult) Then 'UnhookWindowsHookEx failed

            Throw New Exception("UnhookWindowsHookEx failed.")

        End If

    End Sub

 

    Private Function MouseHookProc(ByVal nCode As Integer, ByVal wParam As Integer, ByVal lParam As IntPtr) As Integer

        If nCode >= 0 Then

            Select Case wParam

                Case _

                    Win32.WM.WM_LBUTTONDOWN, _

                    Win32.WM.WM_LBUTTONDBLCLK, _

                    Win32.WM.WM_MBUTTONDOWN, _

                    Win32.WM.WM_MBUTTONDBLCLK, _

                    Win32.WM.WM_RBUTTONDOWN, _

                    Win32.WM.WM_RBUTTONDBLCLK, _

                    Win32.WM.WM_XBUTTONDOWN, _

                    Win32.WM.WM_XBUTTONDBLCLK

                    If MouseDownEvent Is Nothing Then

                        Win32.CallNextHookEx(hMouseHook, nCode, wParam, lParam)

                        Exit Function

                    End If

                Case _

                    Win32.WM.WM_LBUTTONUP, _

                    Win32.WM.WM_MBUTTONUP, _

                    Win32.WM.WM_RBUTTONUP, _

                    Win32.WM.WM_XBUTTONUP

                    If MouseUpEvent Is Nothing Then

                        Win32.CallNextHookEx(hMouseHook, nCode, wParam, lParam)

                        Exit Function

                    End If

 

                Case Win32.WM.WM_MOUSEWHEEL

                    If MouseWheelEvent Is Nothing Then

                        Win32.CallNextHookEx(hMouseHook, nCode, wParam, lParam)

                        Exit Function

                    End If

 

                Case Win32.WM.WM_MOUSEMOVE

                    If MouseMoveEvent Is Nothing Then

                        Win32.CallNextHookEx(hMouseHook, nCode, wParam, lParam)

                        Exit Function

                    End If

            End Select

 

            Dim clickCount As Integer = 0

            Dim buttons As MouseButtons = MouseButtons.None

            Dim mhs As New Win32.MSLLHOOKSTRUCT

            Dim hiWord As Integer

            Dim delta As Integer = 0

 

            Marshal.PtrToStructure(lParam, mhs)

            hiWord = Win32.HIWORD(mhs.mouseData)

 

            Select Case (wParam)

                Case _

                    Win32.WM.WM_LBUTTONDOWN, _

                    Win32.WM.WM_LBUTTONUP, _

                    Win32.WM.WM_LBUTTONDBLCLK

                    buttons = MouseButtons.Left

 

                Case Win32.WM.WM_MBUTTONDOWN, _

                    Win32.WM.WM_MBUTTONUP, _

                    Win32.WM.WM_MBUTTONDBLCLK

                    buttons = MouseButtons.Middle

 

                Case Win32.WM.WM_RBUTTONDOWN, _

                    Win32.WM.WM_RBUTTONUP, _

                    Win32.WM.WM_RBUTTONDBLCLK

                    buttons = MouseButtons.Right

 

                Case _

                    Win32.WM.WM_XBUTTONDOWN, _

                    Win32.WM.WM_XBUTTONUP, _

                    Win32.WM.WM_XBUTTONDBLCLK, _

                    Win32.WM.WM_NCXBUTTONDOWN, _

                    Win32.WM.WM_NCXBUTTONUP, _

                    Win32.WM.WM_NCXBUTTONDBLCLK

 

                    If hiWord = 1 Then

                        buttons = MouseButtons.XButton1

                    ElseIf hiWord = 2 Then

                        buttons = MouseButtons.XButton2

                    End If

 

                Case Win32.WM.WM_MOUSEWHEEL

                    delta = hiWord

 

            End Select

 

            If buttons <> MouseButtons.None Then

                Select Case wParam

                    Case _

                        Win32.WM.WM_LBUTTONDBLCLK, _

                        Win32.WM.WM_MBUTTONDBLCLK, _

                        Win32.WM.WM_RBUTTONDBLCLK, _

                        Win32.WM.WM_XBUTTONDBLCLK

                        clickCount = 2

                    Case Else

                        clickCount = 1

                End Select

            End If

 

            Dim e As New MouseEventArgs(buttons, clickCount, mhs.pt.X, mhs.pt.Y, delta)

 

            Select Case wParam

                Case _

                    Win32.WM.WM_LBUTTONDOWN, _

                    Win32.WM.WM_LBUTTONDBLCLK, _

                    Win32.WM.WM_MBUTTONDOWN, _

                    Win32.WM.WM_MBUTTONDBLCLK, _

                    Win32.WM.WM_RBUTTONDOWN, _

                    Win32.WM.WM_RBUTTONDBLCLK, _

                    Win32.WM.WM_XBUTTONDOWN, _

                    Win32.WM.WM_XBUTTONDBLCLK

                    OnMouseDown(e)

                Case _

                    Win32.WM.WM_LBUTTONUP, _

                    Win32.WM.WM_MBUTTONUP, _

                    Win32.WM.WM_RBUTTONUP, _

                    Win32.WM.WM_XBUTTONUP

                    OnMouseUp(e)

 

                Case Win32.WM.WM_MOUSEWHEEL

                    OnMouseWheel(e)

 

                Case Win32.WM.WM_MOUSEMOVE

                    OnMouseMove(e)

            End Select

        End If

 

        Return Win32.CallNextHookEx(hMouseHook, nCode, wParam, lParam)

    End Function

 

    Private Function KeyboardHookProc(ByVal nCode As Integer, ByVal wParam As Integer, ByVal lParam As IntPtr) As Integer

        Dim handled As Boolean = False

 

        If nCode >= 0 Then

            Dim khs As Win32.KeyboardHookStruct

            Dim keyState(256) As Byte

            Dim inBuffer(2) As Byte

            Dim ctrl, alt, shift As Keys

 

            If Not (KeyDownEvent Is Nothing) _

            AndAlso (wParam = Win32.WM.WM_KEYDOWN Or wParam = Win32.WM.WM_SYSKEYDOWN) Then

                khs = New Win32.KeyboardHookStruct

                Marshal.PtrToStructure(lParam, khs)

                Dim keyData As Keys = CType(khs.vkCode, Keys)

                Win32.GetKeyboardState(keyState)

 

                If Win32.IsKeyDown(Keys.ControlKey) Then

                    ctrl = Keys.Control

                End If

                If Win32.IsKeyDown(Keys.Menu) Then

                    alt = Keys.Alt

                End If

                If Win32.IsKeyDown(Keys.ShiftKey) Then

                    shift = Keys.Shift

                End If

 

                Dim e As New KeyEventArgs(keyData Or ctrl Or alt Or shift)

                OnKeyDown(e)

                handled = e.Handled

 

                If Not handled And wParam <> Win32.WM.WM_SYSKEYDOWN Then

                    If Win32.ToAscii(khs.vkCode, khs.scanCode, keyState, inBuffer, khs.flags) > 0 Then

                        Dim args As New KeyPressEventArgs(BitConverter.ToChar(inBuffer, 0))

                        OnKeyPress(Me, args)

                        handled = e.Handled

                    End If

                End If

            End If

 

            If Not (KeyUpEvent Is Nothing) _

            AndAlso (wParam = Win32.WM.WM_KEYUP Or wParam = Win32.WM.WM_SYSKEYUP) Then

                If khs Is Nothing Then

                    khs = New Win32.KeyboardHookStruct

                    Marshal.PtrToStructure(lParam, khs)

                End If

 

                Dim keyData As Keys = CType(khs.vkCode, Keys)

                Dim e As New KeyEventArgs(keyData)

                OnKeyUp(Me, e)

                handled = e.Handled

            End If

        End If

 

        If handled Then

            Return -1

        Else

            Return Win32.CallNextHookEx(hKeyboardHook, nCode, wParam, lParam)

        End If

    End Function

 

End Class

Thursday, May 11, 2006 10:43:31 AM (Central Standard Time, UTC-06:00)
Great bit of code - a couple of things to note to get this working in VS2005:

1. Disable the Visual Studio hosting process:
- In solution explorer, double click on "My Project"
- Select "Debug" tab
- UNCHECK the "Enable the Visual Studio hosting process"

2. OnKey... subs won't cause argument null exceptions (when not events not being handled anywhere) if they simply use Raiseevent (if you're not worried about whether they are handled or not) - alternatively, check for (e.g.) KeyPressEvent IsNot Nothing before the For Each statement.

Chris Keeble
Thursday, May 11, 2006 12:31:31 PM (Central Standard Time, UTC-06:00)
Chris,

Thanks for the comments!

I had not yet tested the code under VS 2005, but it does make sense that the hosting process would mess with the message hooking. Thanks for pointing this out.

Regarding the second item, the event handler is actually checked in the "KeyboardHookProc" method before the event trigger (OnXXX) is called, so it should be safe. That said, it is unclear that is is getting checked, so I will go ahead and add it both places.
Tuesday, September 26, 2006 3:24:31 AM (Central Standard Time, UTC-06:00)
Hi,
This works fine and I had tried this before but the API SetWindowsHook does not work for WH_SHELL and for windows sub-classing as it used to work in vb 6.

Can you do this Shell hook in .NET

Thanks
Vaibhav
Tuesday, May 15, 2007 5:17:23 PM (Central Standard Time, UTC-06:00)
it seams to be a very good library :)

about the second point ( Chris ), it realy throwes an exception on KeyPressEvent is nothing
it seams because the OnKeyPress called in KeyDown event process, if the handled is true.

P.S.
sory for the bug's in english :)

Tuesday, August 28, 2007 3:13:45 PM (Central Standard Time, UTC-06:00)
I added a minor tweak to allow the keypress to be handled without keydown (see above comment). The modified function is shown below. Thank you for a nice class! -Dave

Private Sub CheckForKeyDown(ByVal wParam As Integer, _
ByVal lParam As IntPtr, _
ByRef handled As Boolean, _
ByRef khs As Win32.KeyboardHookStruct)

If Not ((KeyDownEvent Is Nothing) And (KeyPressEvent Is Nothing)) Then
handled = True

khs = New Win32.KeyboardHookStruct
Marshal.PtrToStructure(lParam, khs)

Dim e As KeyEventArgs = CreateKeyEventArgs(khs)

If Not (KeyDownEvent Is Nothing) Then
Me.OnKeyDown(Me, e)

handled = e.Handled
End If

If Not (KeyPressEvent Is Nothing) Then
If handled Then
handled = FireKeyPress(wParam, khs, e)
End If
End If

End If

End Sub

===>And added a "New" in the last line below:
Private Function KeyboardHookProc( _
ByVal nCode As Integer, ByVal wParam As Integer, _
ByVal lParam As IntPtr) As Integer

Dim handled As Boolean = False

If nCode >= 0 Then
Dim khs As New Win32.KeyboardHookStruct
Sunday, December 02, 2007 8:52:34 AM (Central Standard Time, UTC-06:00)
Hi,
I'm not using vb since a long time so i don't understand everything, maybe my question is stupid but how should i use this hook in my application when i want to get the keyevents and i don't have the focus?
Thanks a lot,
Pierre
Saturday, May 24, 2008 4:29:50 AM (Central Standard Time, UTC-06:00)
Hello,
Thank you for this code. I ran it under VS 2005 without any problem.
My question: I'd like to hook code triggered by other windows events: create, delete, move, resize (not keyboard events)... What has to be changed to your code in order to perform this task.
Thank you,
Claude Animo
Animo
Monday, June 16, 2008 8:28:01 AM (Central Standard Time, UTC-06:00)
Hello,

Thanks for this powerful code.

Anyways, I would prefer to know how to disable "Ctrl+Alt+Del"?
Could someone suggest me?

Thanks in advance
Tatee.
Tatee
Thursday, June 19, 2008 2:48:58 PM (Central Standard Time, UTC-06:00)
@Animo - I'm not sure if you can detect "actions" like create, delete, etc. All you're really getting with this is to "peek" at windows messages. Based on the message alone, it's difficult to impossible to tell what the actual intent of the action is.

@Tatee - I haven't tried this specifically, but have you tried looking for all three conditions at the same time (ctrl/alt + delete) and then canceling the event?
Monday, September 15, 2008 10:06:57 AM (Central Standard Time, UTC-06:00)
MouseMoveEvent, where is that defined?

I have VB .NET 2003 and I tried your code. I added debug.writelines to the code to trace how deep is going in the code.

MouseHookProc is running and goes down to the Win32.WM.WM_MOUSEMOVE case succesfully when I move the mouse. But the "MouseMoveEvent is nothing" is coming True at all times. I am trying to disable the right-click event to an OpenFileDialog.

On design mode I see the MouseMoveEvent is defined somewhere but I do not see where. I hoover over the code and it shows is a Private Dim As System.Windows.Forms.MouseEventHadler. I go to the Object browser
and browse the System.Windows.Forms library and find the MouseEventHandler but does not find any of the Mouse?Event definitions.

Why is nothing? How can I debug this problem?
Jose Torres
Wednesday, September 17, 2008 10:03:52 PM (Central Standard Time, UTC-06:00)
@jose

Take a look at the sample project posted at the top of the page "13.5k GlobalHook2.0.zip" - specifically GlobalHooksForm.vb. This is an example of how to use the global hook class. It uses the "WithEvents" clause to make the object available for event wire-up, and then the "globalHooks_MouseMove" method declares its self as "Handles globalHooks.MouseMove" to handle the MouseMove event of the global hook. "MouseMoveEvent is Nothing" evaluates to "true" because there is no event handler wired-up to the "MouseMove" event on the global hook.
Monday, October 27, 2008 7:30:48 AM (Central Standard Time, UTC-06:00)
Just to make sure. This will allow me to listen for mouse and keyboard events BEFORE they reach all other applications and block them from being processed?

And that is a billion times more neat than what i was messing about with using dll imports from the windows API. I can't believe how uninformed i can be and how much of the .NET framework is "hidden" from amateurs with no knowledge.
Wednesday, October 29, 2008 10:39:56 AM (Central Standard Time, UTC-06:00)
I am so close! Yet so far away from a solution to my problem. And this is why i asked if i could block mouse and keyboard events.

Here is the deal, i have this neat G15 keyboard with all these keys that i can use for various functions. I run multiple computers all hooked up to the one with the G15 keyboard using the application "Synergy". I have a few scripts that can complete certain repetitive tasks that are coded in LUA using the provided scripting engine with the Logitech driver, that means i can move the mouse programatically to drag/drop, click and select items that i use over and over again.
I wouldn't have this problem if my target applications had the needed shortcuts to complete these tasks, that is why i have to do it the "hard" way. On top of that, The Synergy application (That makes it possible to use several computers with one keyboard and mouse) doesn't support my scripts because synergy can't tell what computer i intend to run the script on in regards to mouse co-ordinates.

The problems are:

1. I can't use my additional keys to make mouse movements on the other computers reliably through synergy using the built in (driver) scripting engine.

2. I need to handle mouse movements, moving the mouse programatically, Listening for input and potentially block the inputs made by the user while my little macro is running. (That is, my application should be able to take COMPLETE control of the mouse and keyboard at any time to prevent "dirt" from the users interfering with the macro)


What i need, if you would be so kind and i would be forever thankful:

1. Instructions or direction on how to implement a "e.handled" (Boolean) for MouseEventArgs. I already know i can "return -1" to "handle" mouse events as opposed to "return Win32.CallNextHookEx(...)".
But that blocks ALL mouse events, no matter the source. I need fine control of what events to block.

2. Instructions or suggestions on how to send a mouse move event using "SendInput" and making sure that the resulting "mouseMove" event isn't "handled". (I.E blocked)
In other words, anything my application tell the system should pass through while anything the user does should be "ignored" (Not interfere) while the application is in control.



Thanks a million times!

//Cadde
Friday, February 13, 2009 10:14:00 AM (Central Standard Time, UTC-06:00)
Colin,

really nice and helpful. Works very fine. The only problem that I encounter is that: If the hooks are activated and I click to an element of the control box of the form that contains the hook/unhook code, the action is delayed approximately 2 seconds before it is performed. If the hooks not activated, the form behavior is are usual.

Any idea why this happens ?

Regards

Ulf
Saturday, May 16, 2009 5:13:39 AM (Central Standard Time, UTC-06:00)
Hi Colin!
thank you very much for the code, was searching for this for ages, you are my hero :)
Fenster.
Wednesday, June 24, 2009 5:19:29 AM (Central Standard Time, UTC-06:00)
Thanks for the code above Colin.

I used this code in Visual Studio 2008 and it compiles successfully. Now I am trying to run it and it fails there.

Not sure what might be going wrong here. Is there some specific changes for VS2008?

BTW great explanation and snippet. Cleared all my doubts about hooks, callbacks and delegates.

...Arijit
Arijit
Monday, October 12, 2009 12:32:19 PM (Central Standard Time, UTC-06:00)
Hi Colin:

I try to run your sample code at Windows mobile 6 os.
I use VS 2008.
I open one porject that include one form1,GlobalHook.vb,Win32.vb.
It had a error mseeage:
'GetHINSTANCE' isn't 'System.Runtime.InteropServices.Marshal' Member.
Could you give me any suggestion?
Thanks a lot

cheers

cheertek


cheertek
Thursday, November 12, 2009 8:10:44 AM (Central Standard Time, UTC-06:00)
Hi,

Just to tell you about a but in the keyboard hook. When using special chars like é or ã it will block the special char and return to the application the char e or a.

Example:
1) Run the example.
2) Open notepad.
3) write "isto é um não" (It's Portuguese for "this is a no").
In your notepad will be "isto e um nao".

Great class.
Raul COsra
Tuesday, January 26, 2010 4:17:33 AM (Central Standard Time, UTC-06:00)
Just a quick question, I can block the right mouse click on the app, but if I then try and right click on desktop it wont block it!

Any help would be appreciated.

Thanks
Phil
Monday, March 29, 2010 7:09:45 AM (Central Standard Time, UTC-06:00)
Many many many many Thanks...... You done a great things


Roderick Mercado
Tuesday, April 20, 2010 9:40:25 PM (Central Standard Time, UTC-06:00)

This great to know all i .content is really nice and informative. Thanks for this post. I've bookmarked it!

Somehow the site is not working properly in my windows mobile. Is it not optimized for windows mobile based browsers.
Monday, November 01, 2010 6:51:18 AM (Central Standard Time, UTC-06:00)
Hello,
first of all I don't know if I should thank you. lol. I'm using VB.Net2010 Express. If I open ya project and run it it works. But if I take your code 1:1 via Copy and Paste to another class it doesn't hook up. Neither mouse or keyboard. The same effect as I made it on my own. Local hook for a window no problem. But as soon as go global - no hook. Can anyone help, please? Thanks in advance.
Carsten
Saturday, July 23, 2011 5:04:45 AM (Central Standard Time, UTC-06:00)
Compadre, espectacular, tengo un sistema POS y no habia caso en capturar el codigo de barras, pero con esto es la revolucion!!!! te las mandusky

muy bueno

Name
E-mail
(will show your gravatar icon)
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):