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

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:

Number of code lines and files has grown.

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.

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

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.

The V2 code works very well.
But if I use the DLL from Release or Debug compile in my project (VS2017/.Net4.6.1), I've get the message. "Module not found" by SetWindowsHookEx.
I've a answer to this problem: Deactivate the VS Host Process.
But: VS2017 doesn't have this option. And I also test it with Release-compile for my project.
The same error.

Hello Sir,
Great project for sure. Many thanks. It seems the version on GitHub and CodePlex supports .NET 4.0+ but i need for .NET 2.0 compatible with Visual Studio 2005. I tried compiling from CodeProject and had some issues. We just need a working dll of your work that is specifially capable of running on 2.0 version. Is there any? And it should be as far as i remember.

We have one project on Visual studio 2013 using vb.net. We are displaying ms word document on the windows form and we want user not to take a screen shot using print screen or alt+print screen keyboard button of that form.

Hi ,
I was just using this and found a issue that on clicking the start menu the handle of start menu is shown and repeatedly clicking the start menu a different handle of start menu is shown.
I am getting 2 handle for start menu.When i found the name of the handles then one name was cortana and other was Start Menu.
So this tool is not able to fetch the handle correctly.

System.IO.FileNotFoundException: Could not load file or assembly'Gma.UserActivityMonitor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
File name: 'Gma.UserActivityMonitor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'

Hi! I have a problem with detecting additional mouse buttons. I have a mouse with horizontal scroll buttons (you tilt mouse wheel left or right). But your code doesnt detect anything. It looks like windows is handling these buttons separately in some other way. Is there a way to go even lower with detecting mouse input? Strange that all browsers use this horisontal scrolling, so button works fine but in Visual Studio for exaple it's not working at all.

I know that some mouse buttons simulate keyboard input - ex. my mouse have a up/down zoom button and it works like I'm using keyboard, so keboard hook will chatch it. But these scroll buttons are strange, nothing works... Help!

There is WM_MOUSEHWHEEL - notice 'H' in the middle for horizontal. But event is never fired!

in newer .NET Framework Versions like 4.5 the getting of the handle instance are not working via the Marshal.GetHINSTANCE Methode.
To solve this you can use the managed method GetModuleHandle from the kernerl32.dll.

Hello, This works well in windows 10 framework 4 with the sugestion of Daniel Carvalho Liedke.
But I d like to get and differentiate when the user press a key (to block it) and when is generated by the program with sendkeys.send for example, can you help me?

This kind of code does not work anymore on .NET 4 and up. The error code you get is otherwise descriptive, 126 = "The specified module could not be found". Which tells you that the "mar" variable contains junk.

.NET 4 had a pretty significant CLR change, it no longer pretends that jitted code lives inside unmanaged modules. So Marshal.GetHINSTANCE() does not work anymore. The code then gets sloppy, it forgets to check the return value, testing it for (IntPtr)-1 is required to detect failure and declare disaster. Pretty common for code you find at Codeproject, lots of bugs and sloppiness that can't be fixed by contributors. Not the SO model

SetWindowsHookEx() is a bit awkward for the low-level hooks. It requires a valid module handle, and checks it, but doesn't actually use it. This got fixed in Windows, somewhere around Win7 SP1. While certainly intended to be a useful fix, it actually made the problem worse. Because now it may work on your dev machine but not on your user's machine.

Anyhoo, the fix is simple, you just need to cough up a valid module handle. You can get one from a module that is always present in a managed app, you'll need to pinvoke LoadLibrary to get it.