Global Hooks
The Interface
SharpHook provides the SharpHook.IGlobalHook
interface along with two default implementations which you can use
to control the hook and subscribe to its events. This way is preferred to using native functions since it's more
convenient. Here's a basic usage example:
using SharpHook;
// ...
var hook = new TaskPoolGlobalHook();
hook.HookEnabled += OnHookEnabled; // EventHandler<HookEventArgs>
hook.HookDisabled += OnHookDisabled; // EventHandler<HookEventArgs>
hook.KeyTyped += OnKeyTyped; // EventHandler<KeyboardHookEventArgs>
hook.KeyPressed += OnKeyPressed; // EventHandler<KeyboardHookEventArgs>
hook.KeyReleased += OnKeyReleased; // EventHandler<KeyboardHookEventArgs>
hook.MouseClicked += OnMouseClicked; // EventHandler<MouseHookEventArgs>
hook.MousePressed += OnMousePressed; // EventHandler<MouseHookEventArgs>
hook.MouseReleased += OnMouseReleased; // EventHandler<MouseHookEventArgs>
hook.MouseMoved += OnMouseMoved; // EventHandler<MouseHookEventArgs>
hook.MouseDragged += OnMouseDragged; // EventHandler<MouseHookEventArgs>
hook.MouseWheel += OnMouseWheel; // EventHandler<MouseWheelHookEventArgs>
hook.Run();
// or
await hook.RunAsync();
IGlobalHook
contains separate events for every event type that can be raised by libuiohook. The sender of these
events is the IGlobalHook
itself.
It also contains the Run
and RunAsync
methods which run the global hook. Run
runs it on the current thread,
blocking it until the global hook is disposed. RunAsync
runs the global hook in a non-blocking way and returns a
Task
- this task is finished when the hook is destroyed. Since the underlying native API is blocking, the only way to
run the hook in a non-blocking way is to run it on a separate thread, and all default implementations do just that.
You can specify in the hook constructors whether RunAsync
should create a background thread or not. Background threads
don't block the application from exiting if all other threads have finished executing. By default the created thread
will not be a background thread.
You can subscribe to events after the hook is started.
If you run the hook when it's already running, then an exception will be thrown. You can check whether a hook is running
using its IsRunning
property.
IGlobalHook
extends IDisposable
. When you call the Dispose
method on a hook, it's destroyed. The contract of
the interface is that once a hook has been destroyed, it cannot be started again - you'll have to create a new instance.
Calling Dispose
when the hook is not running is safe - it just won't do anything (other than marking the instance as
disposed). You can check whether the hook is disposed using the IsDisposed
property.
The HookEnabled
event is raised once when the Run
or RunAsync
method is called. The HookDisabled
event is raised
once when the Dispose
method is called.
Hook events are of type HookEventArgs
or a derived type which contains more info. It's possible to suppress event
propagation by setting the SuppressEvent
property to true
inside the event handler. This must be done synchronously
and is only supported on Windows and macOS. You can check the event time and whether the event is real or simulated with
the EventTime
and IsEventSimulated
properties respectively.
Important
Always use one instance of IGlobalHook
at a time in the entire application since they all must use the same static
method to set the hook callback for libuiohook, so there may only be one callback at a time. Running a global hook
when another global hook is already running will corrupt the internal global state of libuiohook.
You can create a keyboard-only or a mouse-only hook by passing a GlobalHookType
to the hook's constructor. This makes
a difference only on Windows where there are two different global hooks - a keyboard hook and a mouse hook. On macOS and
Linux there is one hook for all events, and this simply enables filtering keyboard or mouse events out on these OSes.
The Default Implementations
SharpHook provides two implementations of IGlobalHook
:
SharpHook.SimpleGlobalHook
runs all of its event handlers on the same thread on which the hook itself runs. This means that the handlers should generally be fast since they will block the hook from handling the events that follow if they run for too long.SharpHook.TaskPoolGlobalHook
runs all of its event handlers on other threads inside the default thread pool for tasks. The parallelism level of the handlers can be configured. On backpressure it will queue the remaining handlers. This means that the hook will be able to process all events. This implementation should be preferred toSimpleGlobalHook
except for very simple use-cases. But it has a downside - suppressing event propagation will be ignored since event handlers are run on other threads.
The library also provides the SharpHook.GlobalHookBase
class which you can extend to create your own implementation
of the global hook. It calls the appropriate event handlers, and you only need to implement a strategy for dispatching
the events. It also contains a finalizer which will stop the global hook if this object is not reachable anymore.