Input Handling Guide
This guide covers keyboard and mouse input monitoring and generation using the Dapplo.Windows.Input package.
Overview
The Input package provides:
- Low-level keyboard and mouse hooks
- Reactive Extensions (Rx) based event handling
- Input generation (simulating keyboard and mouse events)
- Key combination and sequence handlers
Installation
Install-Package Dapplo.Windows.Input
Keyboard Input
Keyboard Hook
Create a keyboard hook to monitor keyboard events:
using Dapplo.Windows.Input.Keyboard;
using System;
// Create keyboard hook
var keyboardHook = KeyboardHook.Create();
// Subscribe to keyboard events
var subscription = keyboardHook.KeyboardEvents.Subscribe(keyEvent =>
{
Console.WriteLine($"Key: {keyEvent.Key}, IsDown: {keyEvent.IsDown}");
});
// Clean up when done
subscription.Dispose();
keyboardHook.Dispose();
Filter Keyboard Events
using Dapplo.Windows.Input.Keyboard;
using System.Reactive.Linq;
var keyboardHook = KeyboardHook.Create();
// Only react to key presses (not releases)
var subscription = keyboardHook.KeyboardEvents
.Where(e => e.IsDown)
.Subscribe(e =>
{
Console.WriteLine($"Key pressed: {e.Key}");
});
// Monitor specific key
var escSubscription = keyboardHook.KeyboardEvents
.Where(e => e.Key == VirtualKeyCode.Escape && e.IsDown)
.Subscribe(e =>
{
Console.WriteLine("ESC pressed");
});
Key Combinations
Detect key combinations like Ctrl+C:
using Dapplo.Windows.Input.Keyboard;
using System.Reactive.Linq;
var keyboardHook = KeyboardHook.Create();
// Detect Ctrl+C
var ctrlCSubscription = keyboardHook.KeyboardEvents
.Where(e => e.IsDown && e.Key == VirtualKeyCode.C && e.IsControlPressed)
.Subscribe(e =>
{
Console.WriteLine("Ctrl+C pressed");
});
// Detect Ctrl+Shift+A
var complexComboSubscription = keyboardHook.KeyboardEvents
.Where(e => e.IsDown &&
e.Key == VirtualKeyCode.A &&
e.IsControlPressed &&
e.IsShiftPressed)
.Subscribe(e =>
{
Console.WriteLine("Ctrl+Shift+A pressed");
});
Advanced Key Combination Handling
Use KeyCombinationHandler for more sophisticated key combination detection:
using Dapplo.Windows.Input.Keyboard;
using System.Reactive.Linq;
var keyboardHook = KeyboardHook.Create();
// Create a handler for Ctrl+Alt+T
var keyCombinationHandler = new KeyCombinationHandler(
VirtualKeyCode.Control,
VirtualKeyCode.Menu, // Alt key
VirtualKeyCode.T
);
// Subscribe to the combination
var subscription = keyboardHook.KeyboardEvents
.Where(keyCombinationHandler)
.Subscribe(e =>
{
Console.WriteLine("Ctrl+Alt+T pressed");
e.Handled = true; // Prevent other apps from seeing it
});
Trigger on Key Release
When you need to inject keypresses after a key combination, use TriggerOnKeyUp to ensure all modifier keys are released before your handler runs. This prevents the injected keys from being modified by still-pressed modifier keys:
using Dapplo.Windows.Input.Keyboard;
using System;
using System.Reactive.Linq;
var keyboardHook = KeyboardHook.Create();
// Create a handler that triggers when keys are RELEASED
var keyCombinationHandler = new KeyCombinationHandler(
VirtualKeyCode.Control,
VirtualKeyCode.Menu, // Alt key
VirtualKeyCode.LeftWin, // Windows key
VirtualKeyCode.T
)
{
TriggerOnKeyUp = true, // Trigger when keys are released, not pressed
IgnoreInjected = false // Allow processing of injected keys if needed
};
var subscription = keyboardHook.KeyboardEvents
.Where(keyCombinationHandler)
.Subscribe(e =>
{
// This runs AFTER all keys are released
// So injected keypresses won't be affected by modifier keys
var timestamp = DateTime.Now.ToString("yyyy-MM-dd--HH-mm-ss");
// Type the timestamp safely without modifier interference
KeyboardInputGenerator.TypeText(timestamp);
e.Handled = true;
});
This is especially useful when replacing AutoHotKey scripts or implementing text expansion features where modifier keys in the trigger combination could interfere with the text being typed.
Key Sequence Detection
Detect a sequence of keys (like vim commands):
using Dapplo.Windows.Input.Keyboard;
using System;
using System.Reactive.Linq;
using System.Collections.Generic;
var keyboardHook = KeyboardHook.Create();
var keySequence = new List<VirtualKeyCode>();
var subscription = keyboardHook.KeyboardEvents
.Where(e => e.IsDown)
.Subscribe(e =>
{
keySequence.Add(e.Key);
// Keep only last 3 keys
if (keySequence.Count > 3)
{
keySequence.RemoveAt(0);
}
// Check for sequence: G, G, O
if (keySequence.Count == 3 &&
keySequence[0] == VirtualKeyCode.G &&
keySequence[1] == VirtualKeyCode.G &&
keySequence[2] == VirtualKeyCode.O)
{
Console.WriteLine("GGO sequence detected!");
keySequence.Clear();
}
});
Suppress Key Events
Prevent key events from reaching other applications:
using Dapplo.Windows.Input.Keyboard;
var keyboardHook = KeyboardHook.Create();
var subscription = keyboardHook.KeyboardEvents
.Subscribe(e =>
{
if (e.Key == VirtualKeyCode.PrintScreen && e.IsDown)
{
// Handle screenshot yourself
TakeScreenshot();
// Suppress the key so system doesn't handle it
e.Handled = true;
}
});
Generating Keyboard Input
Type Text
using Dapplo.Windows.Input.Keyboard;
// Type text
KeyboardInputGenerator.TypeText("Hello, World!");
// Type with delay between characters
KeyboardInputGenerator.TypeText("Slow typing", 100); // 100ms delay
Press Keys
using Dapplo.Windows.Input.Keyboard;
// Press and release a single key
KeyboardInputGenerator.KeyPress(VirtualKeyCode.Return);
// Press multiple keys
KeyboardInputGenerator.KeyPress(VirtualKeyCode.A);
KeyboardInputGenerator.KeyPress(VirtualKeyCode.B);
KeyboardInputGenerator.KeyPress(VirtualKeyCode.C);
Key Combinations
using Dapplo.Windows.Input.Keyboard;
// Press Ctrl+C
KeyboardInputGenerator.KeyCombinationPress(
VirtualKeyCode.Control,
VirtualKeyCode.C
);
// Press Ctrl+Shift+S
KeyboardInputGenerator.KeyCombinationPress(
VirtualKeyCode.Control,
VirtualKeyCode.Shift,
VirtualKeyCode.S
);
// Alt+Tab
KeyboardInputGenerator.KeyCombinationPress(
VirtualKeyCode.Alt,
VirtualKeyCode.Tab
);
Advanced Key Control
using Dapplo.Windows.Input.Keyboard;
// Key down
KeyboardInputGenerator.KeyDown(VirtualKeyCode.Shift);
// Type while shift is held
KeyboardInputGenerator.KeyPress(VirtualKeyCode.A);
KeyboardInputGenerator.KeyPress(VirtualKeyCode.B);
// Key up
KeyboardInputGenerator.KeyUp(VirtualKeyCode.Shift);
Mouse Input
Mouse Hook
Monitor mouse events:
using Dapplo.Windows.Input.Mouse;
using System;
// Create mouse hook
var mouseHook = MouseHook.Create();
// Subscribe to mouse events
var subscription = mouseHook.MouseEvents.Subscribe(mouseEvent =>
{
Console.WriteLine($"Mouse: {mouseEvent.Button} at ({mouseEvent.Point.X}, {mouseEvent.Point.Y})");
});
// Clean up
subscription.Dispose();
mouseHook.Dispose();
Filter Mouse Events
using Dapplo.Windows.Input.Mouse;
using System.Reactive.Linq;
var mouseHook = MouseHook.Create();
// Only left clicks
var leftClickSubscription = mouseHook.MouseEvents
.Where(e => e.Button == MouseButtons.Left && e.IsButtonDown)
.Subscribe(e =>
{
Console.WriteLine($"Left click at ({e.Point.X}, {e.Point.Y})");
});
// Only right clicks
var rightClickSubscription = mouseHook.MouseEvents
.Where(e => e.Button == MouseButtons.Right && e.IsButtonDown)
.Subscribe(e =>
{
Console.WriteLine($"Right click at ({e.Point.X}, {e.Point.Y})");
});
// Mouse wheel
var wheelSubscription = mouseHook.MouseEvents
.Where(e => e.Delta != 0)
.Subscribe(e =>
{
Console.WriteLine($"Mouse wheel: {e.Delta}");
});
Mouse Movement
using Dapplo.Windows.Input.Mouse;
using System.Reactive.Linq;
using System;
var mouseHook = MouseHook.Create();
// Track mouse movement
var moveSubscription = mouseHook.MouseEvents
.Where(e => e.IsMouseMove)
.Throttle(TimeSpan.FromMilliseconds(100)) // Limit update rate
.Subscribe(e =>
{
Console.WriteLine($"Mouse at ({e.Point.X}, {e.Point.Y})");
});
Detect Click Patterns
using Dapplo.Windows.Input.Mouse;
using System.Reactive.Linq;
using System;
var mouseHook = MouseHook.Create();
// Double-click detection
var doubleClickSubscription = mouseHook.MouseEvents
.Where(e => e.Button == MouseButtons.Left && e.IsButtonDown)
.Buffer(TimeSpan.FromMilliseconds(500), 2) // Two clicks within 500ms
.Where(clicks => clicks.Count == 2)
.Subscribe(clicks =>
{
Console.WriteLine("Double-click detected!");
});
Generating Mouse Input
Move Mouse
using Dapplo.Windows.Input.Mouse;
using Dapplo.Windows.Common.Structs;
// Move to absolute position
MouseInputGenerator.MoveMouse(new NativePoint(100, 100));
// Move relative to current position
var currentPos = MouseInputGenerator.GetMousePosition();
MouseInputGenerator.MoveMouse(new NativePoint(currentPos.X + 10, currentPos.Y + 10));
Click Mouse
using Dapplo.Windows.Input.Mouse;
// Left click
MouseInputGenerator.Click();
// Right click
MouseInputGenerator.RightClick();
// Middle click
MouseInputGenerator.MiddleClick();
// Double click
MouseInputGenerator.DoubleClick();
Advanced Mouse Control
using Dapplo.Windows.Input.Mouse;
using Dapplo.Windows.Common.Structs;
// Press button down
MouseInputGenerator.MouseDown(MouseButtons.Left);
// Move while button is down (drag)
MouseInputGenerator.MoveMouse(new NativePoint(200, 200));
// Release button
MouseInputGenerator.MouseUp(MouseButtons.Left);
Mouse Wheel
using Dapplo.Windows.Input.Mouse;
// Scroll up (positive value)
MouseInputGenerator.MouseWheel(120);
// Scroll down (negative value)
MouseInputGenerator.MouseWheel(-120);
Raw Input Monitoring
Monitor raw input devices (keyboards, mice, HID devices):
using Dapplo.Windows.Input;
using System;
// Create raw input monitor
var rawInputMonitor = RawInputMonitor.Create();
// Subscribe to raw input events
var subscription = rawInputMonitor.RawInputEvents.Subscribe(rawEvent =>
{
Console.WriteLine($"Raw input from device: {rawEvent.DeviceHandle}");
if (rawEvent.Device == RawInputDeviceType.Keyboard)
{
Console.WriteLine($"Keyboard: {rawEvent.VirtualKey}");
}
else if (rawEvent.Device == RawInputDeviceType.Mouse)
{
Console.WriteLine($"Mouse: {rawEvent.Mouse}");
}
});
// Clean up
subscription.Dispose();
rawInputMonitor.Dispose();
Common Patterns
Global Hotkey
using Dapplo.Windows.Input.Keyboard;
using System.Reactive.Linq;
var keyboardHook = KeyboardHook.Create();
// Ctrl+Alt+H hotkey
var hotkeySubscription = keyboardHook.KeyboardEvents
.Where(e => e.IsDown &&
e.Key == VirtualKeyCode.H &&
e.IsControlPressed &&
e.IsAltPressed)
.Subscribe(e =>
{
Console.WriteLine("Global hotkey activated!");
e.Handled = true; // Prevent other apps from seeing it
});
Auto-Clicker
using Dapplo.Windows.Input.Mouse;
using System.Threading;
using System.Threading.Tasks;
async Task AutoClick(int intervalMs, int count)
{
for (int i = 0; i < count; i++)
{
MouseInputGenerator.Click();
await Task.Delay(intervalMs);
}
}
// Click 10 times with 1 second interval
await AutoClick(1000, 10);
Keyboard Macro
using Dapplo.Windows.Input.Keyboard;
using System.Threading.Tasks;
async Task PlayMacro()
{
// Type username
KeyboardInputGenerator.TypeText("myusername");
// Press Tab
KeyboardInputGenerator.KeyPress(VirtualKeyCode.Tab);
await Task.Delay(100);
// Type password
KeyboardInputGenerator.TypeText("mypassword");
// Press Enter
KeyboardInputGenerator.KeyPress(VirtualKeyCode.Return);
}
Best Practices
1. Dispose Hooks
Always dispose hooks when done:
using (var keyboardHook = KeyboardHook.Create())
{
// Use hook
} // Automatically disposed
2. Handle Errors
Input operations can fail:
try
{
KeyboardInputGenerator.KeyPress(VirtualKeyCode.A);
}
catch (Win32Exception ex)
{
Console.WriteLine($"Input failed: {ex.Message}");
}
3. Throttle Events
Limit high-frequency events:
var subscription = mouseHook.MouseEvents
.Where(e => e.IsMouseMove)
.Throttle(TimeSpan.FromMilliseconds(50))
.Subscribe(e => UpdateUI(e.Point));
4. Clean Up Subscriptions
var subscription = keyboardHook.KeyboardEvents.Subscribe(...);
// Later
subscription.Dispose();
5. Test Input Generation
Always test generated input:
- Verify correct keys are sent
- Check timing between inputs
- Test with target applications
- Handle focus issues
Security Considerations
Administrator Privileges
Low-level hooks may require administrator privileges:
- Keyboard/mouse hooks work in standard user mode
- Some input generation may need elevation
- Consider UAC implications
Input Injection
Be careful with input generation:
- Never inject sensitive data (passwords) programmatically in production
- Validate and sanitize any text being typed
- Consider security implications of automated input