Related
As part of a media player application I'm working on, I want to hook global key presses for media control keys (play, skip forward, skip back, etc).
I've been searching for about 2 hours now trying to find a solution - but I couldn't find one that worked. I found several Stack Overflow answers about the same thing, but none of them worked.
I tried the MouseKeyHook NuGet package, but it would never fire the event. I tried the FMUtils.KeyboardHook package too, but the same thing happened, except it printed in the console that it shut down the hook right after starting it - and I have no idea why, even after looking at the source code.
I tried to get this codeproject project http://www.codeproject.com/Articles/18638/Using-Window-Messages-to-Implement-Global-System-H but I couldn't even run the demo, both the demos just threw weird errors that I couldn't trace.
My question is, what is a known-to-work way of capturing keyboard presses in .NET 4.0 that I can use to capture keyboard presses when my WinForms application isn't focused?
Here is the code I have been using for several projects for the last X years. Should work with no issues (for any .NET version on Windows).
public class KeyboardHook : IDisposable
{
bool Global = false;
public delegate void LocalKeyEventHandler(Keys key, bool Shift, bool Ctrl, bool Alt);
public event LocalKeyEventHandler KeyDown;
public event LocalKeyEventHandler KeyUp;
public delegate int CallbackDelegate(int Code, int W, int L);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct KBDLLHookStruct
{
public Int32 vkCode;
public Int32 scanCode;
public Int32 flags;
public Int32 time;
public Int32 dwExtraInfo;
}
[DllImport("user32", CallingConvention = CallingConvention.StdCall)]
private static extern int SetWindowsHookEx(HookType idHook, CallbackDelegate lpfn, int hInstance, int threadId);
[DllImport("user32", CallingConvention = CallingConvention.StdCall)]
private static extern bool UnhookWindowsHookEx(int idHook);
[DllImport("user32", CallingConvention = CallingConvention.StdCall)]
private static extern int CallNextHookEx(int idHook, int nCode, int wParam, int lParam);
[DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall)]
private static extern int GetCurrentThreadId();
public enum HookType : int
{
WH_JOURNALRECORD = 0,
WH_JOURNALPLAYBACK = 1,
WH_KEYBOARD = 2,
WH_GETMESSAGE = 3,
WH_CALLWNDPROC = 4,
WH_CBT = 5,
WH_SYSMSGFILTER = 6,
WH_MOUSE = 7,
WH_HARDWARE = 8,
WH_DEBUG = 9,
WH_SHELL = 10,
WH_FOREGROUNDIDLE = 11,
WH_CALLWNDPROCRET = 12,
WH_KEYBOARD_LL = 13,
WH_MOUSE_LL = 14
}
private int HookID = 0;
CallbackDelegate TheHookCB = null;
//Start hook
public KeyboardHook(bool Global)
{
this.Global = Global;
TheHookCB = new CallbackDelegate(KeybHookProc);
if (Global)
{
HookID = SetWindowsHookEx(HookType.WH_KEYBOARD_LL, TheHookCB,
0, //0 for local hook. eller hwnd til user32 for global
0); //0 for global hook. eller thread for hooken
}
else
{
HookID = SetWindowsHookEx(HookType.WH_KEYBOARD, TheHookCB,
0, //0 for local hook. or hwnd to user32 for global
GetCurrentThreadId()); //0 for global hook. or thread for the hook
}
}
bool IsFinalized = false;
~KeyboardHook()
{
if (!IsFinalized)
{
UnhookWindowsHookEx(HookID);
IsFinalized = true;
}
}
public void Dispose()
{
if (!IsFinalized)
{
UnhookWindowsHookEx(HookID);
IsFinalized = true;
}
}
//The listener that will trigger events
private int KeybHookProc(int Code, int W, int L)
{
KBDLLHookStruct LS = new KBDLLHookStruct();
if (Code < 0)
{
return CallNextHookEx(HookID, Code, W, L);
}
try
{
if (!Global)
{
if (Code == 3)
{
IntPtr ptr = IntPtr.Zero;
int keydownup = L >> 30;
if (keydownup == 0)
{
if (KeyDown != null) KeyDown((Keys)W, GetShiftPressed(), GetCtrlPressed(), GetAltPressed());
}
if (keydownup == -1)
{
if (KeyUp != null) KeyUp((Keys)W, GetShiftPressed(), GetCtrlPressed(), GetAltPressed());
}
//System.Diagnostics.Debug.WriteLine("Down: " + (Keys)W);
}
}
else
{
KeyEvents kEvent = (KeyEvents)W;
Int32 vkCode = Marshal.ReadInt32((IntPtr)L); //Leser vkCode som er de første 32 bits hvor L peker.
if (kEvent != KeyEvents.KeyDown && kEvent != KeyEvents.KeyUp && kEvent != KeyEvents.SKeyDown && kEvent != KeyEvents.SKeyUp)
{
}
if (kEvent == KeyEvents.KeyDown || kEvent == KeyEvents.SKeyDown)
{
if (KeyDown != null) KeyDown((Keys)vkCode, GetShiftPressed(), GetCtrlPressed(), GetAltPressed());
}
if (kEvent == KeyEvents.KeyUp || kEvent == KeyEvents.SKeyUp)
{
if (KeyUp != null) KeyUp((Keys)vkCode, GetShiftPressed(), GetCtrlPressed(), GetAltPressed());
}
}
}
catch (Exception)
{
//Ignore all errors...
}
return CallNextHookEx(HookID, Code, W, L);
}
public enum KeyEvents
{
KeyDown = 0x0100,
KeyUp = 0x0101,
SKeyDown = 0x0104,
SKeyUp = 0x0105
}
[DllImport("user32.dll")]
static public extern short GetKeyState(System.Windows.Forms.Keys nVirtKey);
public static bool GetCapslock()
{
return Convert.ToBoolean(GetKeyState(System.Windows.Forms.Keys.CapsLock)) & true;
}
public static bool GetNumlock()
{
return Convert.ToBoolean(GetKeyState(System.Windows.Forms.Keys.NumLock)) & true;
}
public static bool GetScrollLock()
{
return Convert.ToBoolean(GetKeyState(System.Windows.Forms.Keys.Scroll)) & true;
}
public static bool GetShiftPressed()
{
int state = GetKeyState(System.Windows.Forms.Keys.ShiftKey);
if (state > 1 || state < -1) return true;
return false;
}
public static bool GetCtrlPressed()
{
int state = GetKeyState(System.Windows.Forms.Keys.ControlKey);
if (state > 1 || state < -1) return true;
return false;
}
public static bool GetAltPressed()
{
int state = GetKeyState(System.Windows.Forms.Keys.Menu);
if (state > 1 || state < -1) return true;
return false;
}
}
Test app:
static class Program
{
[STAThread]
static void Main()
{
var kh = new KeyboardHook(true);
kh.KeyDown += Kh_KeyDown;
Application.Run();
}
private static void Kh_KeyDown(Keys key, bool Shift, bool Ctrl, bool Alt)
{
Debug.WriteLine("The Key: " + key);
}
}
It could do with some code cleanup, but I have not bothered as it works.
Here is a fixed version of the class that doesn't give access violation error:
public class KeyboardHook : IDisposable
{
bool Global = false;
public delegate void ErrorEventHandler(Exception e);
public delegate void LocalKeyEventHandler(Keys key, bool Shift, bool Ctrl, bool Alt);
public event LocalKeyEventHandler KeyDown;
public event LocalKeyEventHandler KeyUp;
public event ErrorEventHandler OnError;
public delegate int CallbackDelegate(int Code, IntPtr W, IntPtr L);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct KBDLLHookStruct
{
public Int32 vkCode;
public Int32 scanCode;
public Int32 flags;
public Int32 time;
public Int32 dwExtraInfo;
}
[DllImport("user32", CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr SetWindowsHookEx(HookType idHook, CallbackDelegate lpfn, IntPtr hInstance, int threadId);
[DllImport("user32", CallingConvention = CallingConvention.StdCall)]
private static extern bool UnhookWindowsHookEx(IntPtr idHook);
[DllImport("user32", CallingConvention = CallingConvention.StdCall)]
private static extern int CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall)]
private static extern int GetCurrentThreadId();
[DllImport("kernel32.dll")]
static extern IntPtr LoadLibrary(string lpFileName);
public enum HookType : int
{
WH_JOURNALRECORD = 0,
WH_JOURNALPLAYBACK = 1,
WH_KEYBOARD = 2,
WH_GETMESSAGE = 3,
WH_CALLWNDPROC = 4,
WH_CBT = 5,
WH_SYSMSGFILTER = 6,
WH_MOUSE = 7,
WH_HARDWARE = 8,
WH_DEBUG = 9,
WH_SHELL = 10,
WH_FOREGROUNDIDLE = 11,
WH_CALLWNDPROCRET = 12,
WH_KEYBOARD_LL = 13,
WH_MOUSE_LL = 14
}
private IntPtr HookID = IntPtr.Zero;
CallbackDelegate TheHookCB = null;
//Start hook
public KeyboardHook(bool Global)
{
this.Global = Global;
TheHookCB = new CallbackDelegate(KeybHookProc);
if (Global)
{
IntPtr hInstance = LoadLibrary("User32");
HookID = SetWindowsHookEx(HookType.WH_KEYBOARD_LL, TheHookCB,
hInstance, //0 for local hook. or hwnd to user32 for global
0); //0 for global hook. eller thread for hooken
}
else
{
HookID = SetWindowsHookEx(HookType.WH_KEYBOARD, TheHookCB,
IntPtr.Zero, //0 for local hook. or hwnd to user32 for global
GetCurrentThreadId()); //0 for global hook. or thread for the hook
}
}
public void test()
{
if (OnError != null) OnError(new Exception("test"));
}
bool IsFinalized = false;
~KeyboardHook()
{
if (!IsFinalized)
{
UnhookWindowsHookEx(HookID);
IsFinalized = true;
}
}
public void Dispose()
{
if (!IsFinalized)
{
UnhookWindowsHookEx(HookID);
IsFinalized = true;
}
}
[STAThread]
//The listener that will trigger events
private int KeybHookProc(int Code, IntPtr W, IntPtr L)
{
KBDLLHookStruct LS = new KBDLLHookStruct();
if (Code < 0)
{
return CallNextHookEx(HookID, Code, W, L);
}
try
{
if (!Global)
{
if (Code == 3)
{
IntPtr ptr = IntPtr.Zero;
int keydownup = L.ToInt32() >> 30;
if (keydownup == 0)
{
if (KeyDown != null) KeyDown((Keys)W, GetShiftPressed(), GetCtrlPressed(), GetAltPressed());
}
if (keydownup == -1)
{
if (KeyUp != null) KeyUp((Keys)W, GetShiftPressed(), GetCtrlPressed(), GetAltPressed());
}
//System.Diagnostics.Debug.WriteLine("Down: " + (Keys)W);
}
}
else
{
KeyEvents kEvent = (KeyEvents)W;
Int32 vkCode = Marshal.ReadInt32((IntPtr)L); //Leser vkCode som er de første 32 bits hvor L peker.
if (kEvent != KeyEvents.KeyDown && kEvent != KeyEvents.KeyUp && kEvent != KeyEvents.SKeyDown && kEvent != KeyEvents.SKeyUp)
{
}
if (kEvent == KeyEvents.KeyDown || kEvent == KeyEvents.SKeyDown)
{
if (KeyDown != null) KeyDown((Keys)vkCode, GetShiftPressed(), GetCtrlPressed(), GetAltPressed());
}
if (kEvent == KeyEvents.KeyUp || kEvent == KeyEvents.SKeyUp)
{
if (KeyUp != null) KeyUp((Keys)vkCode, GetShiftPressed(), GetCtrlPressed(), GetAltPressed());
}
}
}
catch (Exception e)
{
if (OnError != null) OnError(e);
//Ignore all errors...
}
return CallNextHookEx(HookID, Code, W, L);
}
public enum KeyEvents
{
KeyDown = 0x0100,
KeyUp = 0x0101,
SKeyDown = 0x0104,
SKeyUp = 0x0105
}
[DllImport("user32.dll")]
static public extern short GetKeyState(System.Windows.Forms.Keys nVirtKey);
public static bool GetCapslock()
{
return Convert.ToBoolean(GetKeyState(System.Windows.Forms.Keys.CapsLock)) & true;
}
public static bool GetNumlock()
{
return Convert.ToBoolean(GetKeyState(System.Windows.Forms.Keys.NumLock)) & true;
}
public static bool GetScrollLock()
{
return Convert.ToBoolean(GetKeyState(System.Windows.Forms.Keys.Scroll)) & true;
}
public static bool GetShiftPressed()
{
int state = GetKeyState(System.Windows.Forms.Keys.ShiftKey);
if (state > 1 || state < -1) return true;
return false;
}
public static bool GetCtrlPressed()
{
int state = GetKeyState(System.Windows.Forms.Keys.ControlKey);
if (state > 1 || state < -1) return true;
return false;
}
public static bool GetAltPressed()
{
int state = GetKeyState(System.Windows.Forms.Keys.Menu);
if (state > 1 || state < -1) return true;
return false;
}
}
I want to know how to use it....I want to say that if I Click Up and Right Arrow (Form1_KeyDown Event) then timer1.Start();
and When I release the Up and Right Arrow (Form1_KeyUp Event) then timer1.Stop();
I have already Imported the "User32.dll"
using System.Runtime.InteropServices;
[DllImport("User32.dll")]
public static extern short GetAsyncKeyState(Keys ArrowKeys);
so How to use it...I see a lot of website but can't get it
Implement IMessageFilter for you Form and track when the Up/Right Arrows are toggled:
public partial class form1 : Form, IMessageFilter
{
public form1()
{
InitializeComponent();
Application.AddMessageFilter(this);
}
private bool UpDepressed = false;
private bool RightDepressed = false;
private const int WM_KEYDOWN = 0x100;
private const int WM_KEYUP = 0x101;
public bool PreFilterMessage(ref Message m)
{
switch (m.Msg)
{
case WM_KEYDOWN:
if ((Keys)m.WParam == Keys.Up)
{
UpDepressed = true;
}
else if ((Keys)m.WParam == Keys.Right)
{
RightDepressed = true;
}
break;
case WM_KEYUP:
if ((Keys)m.WParam == Keys.Up)
{
UpDepressed = false;
}
else if ((Keys)m.WParam == Keys.Right)
{
RightDepressed = false;
}
break;
}
timer1.Enabled = (UpDepressed && RightDepressed);
label1.Text = timer1.Enabled.ToString();
return false;
}
private void timer1_Tick(object sender, EventArgs e)
{
label2.Text = DateTime.Now.ToString("ffff");
}
}
Here is how you can use it
int keystroke;
byte[] result = BitConverter.GetBytes(GetAsyncKeyState(keystroke));
if (result[0] == 1)
Console.Writeline("the key was pressed after the previous call to GetAsyncKeyState.")
if (result[1] == 0x80)
Console.Writeline("The key is down");
change
[DllImport("User32.dll")]
public static extern short GetAsyncKeyState(Keys ArrowKeys);
to
[DllImport("User32.dll")]
public static extern bool GetAsyncKeyState(Keys ArrowKeys);
async key state should be a bool not a short
I managed using bar-code scanner successfully in my WPF project using a keyboard hook as follows (I skip some details, but basically, I can rely on the fact that I know which keyboard is my scanner).
/// <summary>
/// Add this KeyboardHook to a window
/// </summary>
/// <param name="window">The window to add to</param>
public void AddHook(Window window) {
if (form == null)
throw new ArgumentNullException("window");
if (mHwndSource != null)
throw new InvalidOperationException("Hook already present");
WindowInteropHelper w = new WindowInteropHelper(window);
IntPtr hwnd = w.Handle;
mHwndSource = HwndSource.FromHwnd(hwnd);
if (mHwndSource == null)
throw new ApplicationException("Failed to receive window source");
mHwndSource.AddHook(WndProc);
RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[1];
rid[0].usUsagePage = 0x01;
rid[0].usUsage = 0x06;
rid[0].dwFlags = RIDEV_INPUTSINK;
rid[0].hwndTarget = hwnd;
if (!RegisterRawInputDevices(rid, (uint)rid.Length, (uint)Marshal.SizeOf(rid[0])))
throw new ApplicationException("Failed to register raw input device(s).");
}
The approach then processes WM_INPUT messages to retrieve information about any keyboard events that occur and handles the event accordingly if it is coming from the Bar-code scanner that is already known.
Now the thing is that in Winforms I should not use hooks but override WndProc as stated here, but I am somehow struggling to understand how I can use WndProc as I need to know:
a) what event I really need to handle in the WndProc method
b) how I can identify the device that fired the event
Any help would be very appreciated!
Cheers!
I ended using the following approach:
public class BarcodeScannedEventArgs : EventArgs {
public BarcodeScannedEventArgs(string text) {
mText = text;
}
public string ScannedText { get { return mText; } }
private readonly string mText;
}
public class BarCodeListener : IDisposable {
DateTime _lastKeystroke = new DateTime(0);
string _barcode = string.Empty;
Form _form;
bool isKeyPreview;
public bool ProcessCmdKey(ref Message msg, Keys keyData) {
bool res = processKey(keyData);
return keyData == Keys.Enter ? res : false;
}
protected bool processKey(Keys key) {
// check timing (>7 keystrokes within 50 ms ending with "return" char)
TimeSpan elapsed = (DateTime.Now - _lastKeystroke);
if (elapsed.TotalMilliseconds > 50) {
_barcode = string.Empty;
}
// record keystroke & timestamp -- do NOT add return at the end of the barcode line
if (key != Keys.Enter) {
_barcode += (char)key;
}
_lastKeystroke = DateTime.Now;
// process barcode only if the return char is entered and the entered barcode is at least 7 digits long.
// This is a "magical" rule working well for EAN-13 and EAN-8, which both have at least 8 digits...
if (key == Keys.Enter && _barcode.Length > 7) {
if (BarCodeScanned != null) {
BarCodeScanned(_form, new BarcodeScannedEventArgs(_barcode));
}
_barcode = string.Empty;
return true;
}
return false;
}
public event EventHandler<BarcodeScannedEventArgs> BarCodeScanned;
public BarCodeListener(Form f) {
_form = f;
isKeyPreview = f.KeyPreview;
// --- set preview and register event...
f.KeyPreview = true;
}
public void Dispose() {
if (_form != null) {
_form.KeyPreview = isKeyPreview;
//_form.KeyPress -= KeyPress_scanner_preview;
}
}
}
}
Then, add the following lines of code to your form which is listening to your scanner:
private BarCodeListener ScannerListener;
protected override bool ProcessCmdKey(ref Message msg, Keys keyData) {
bool res = false;
if (ScannerListener != null) {
res = ScannerListener.ProcessCmdKey(ref msg, keyData);
}
res = keyData == Keys.Enter ? res : base.ProcessCmdKey(ref msg, keyData);
return res;
}
I post here a piece of code I used for a while before using the above version. It is derived from here, and I didn't re-test. Comes as is, hope it helps anyway...
public BarCodeListener(IntPtr handle) {
SetDeviceHook(handle);
}
public void SetDeviceHook(IntPtr handle) {
if (string.IsNullOrWhiteSpace(LibConst.ScannerDeviceName)) {
return;
}
var hook = new KeyboardHook();
var availableScanners = KeyboardHook.GetKeyboardDevices();
foreach (string s in availableScanners) {
// you need to figure out the device name and put it here!!!!
if (s.Contains("VID_0C2E&PID_0200")) {
hook.SetDeviceFilter(s);
hook.KeyPressed += OnBarcodeKeyWpf;
hook.AddHook(handle);
}
}
}
string InputText;
void OnBarcodeKey(object sender, KeyPressEventArgs e) {
if (this.isInitialisationChar(e.KeyChar.ToString())) {
InputText = String.Empty;
}
else if (this.isTerminationChar(e.KeyChar.ToString())) {
if ((BarCodeScanned != null) && (!string.IsNullOrEmpty(InputText))) {
BarCodeScanned(this, new BarcodeScannedEventArgs(InputText));
InputText = String.Empty;
}
}
else {
InputText += e.KeyChar.ToString();
}
}
void OnBarcodeKeyWpf(object sender, KeyPressedEventArgs e) {
if (this.isInitialisationChar(e.Text)){
InputText = String.Empty;
}
else if (this.isTerminationChar(e.Text)){
if ((BarCodeScanned != null) && (!string.IsNullOrEmpty(InputText))) {
BarCodeScanned(this, new BarcodeScannedEventArgs(InputText));
InputText = String.Empty;
}
}
else{
InputText += e.Text;
}
}
bool isInitialisationChar(string s) {
return string.IsNullOrEmpty(s);
}
bool isTerminationChar(string s) {
return ((s == "\r") || string.IsNullOrEmpty(s));
}
}
public class KeyPressedEventArgs : EventArgs
{
public KeyPressedEventArgs(string text) {
mText = text;
}
public string Text { get { return mText; } }
private readonly string mText;
}
public partial class KeyboardHook
: IDisposable
{
private static readonly Regex DeviceNamePattern = new Regex(#"#([^#]+)");
public event EventHandler<KeyPressedEventArgs> KeyPressed;
/// <summary>
/// Set the device to use in keyboard hook
/// </summary>
/// <param name="deviceId">Name of device</param>
/// <returns>true if device is found</returns>
public bool SetDeviceFilter(string deviceId) {
Dictionary<string, IntPtr> devices = FindAllKeyboardDevices();
return devices.TryGetValue(deviceId, out mHookDeviceId);
}
/// <summary>
/// Add this KeyboardHook to a window
/// </summary>
/// <param name="window">The window to add to</param>
public void AddHook(BaseForm form) {
if (form == null)
throw new ArgumentNullException("window");
if (mHwndSource != null)
throw new InvalidOperationException("Hook already present");
//--- NEG: wpf code:
//WindowInteropHelper w = new WindowInteropHelper(form);
//IntPtr hwnd = w.Handle;
//---
IntPtr hwnd = form.Handle;
mHwndSource = HwndSource.FromHwnd(hwnd);
if (mHwndSource == null)
throw new ApplicationException("Failed to receive window source");
mHwndSource.AddHook(WndProc);
RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[1];
rid[0].usUsagePage = 0x01;
rid[0].usUsage = 0x06;
rid[0].dwFlags = RIDEV_INPUTSINK;
rid[0].hwndTarget = hwnd;
if (!RegisterRawInputDevices(rid, (uint)rid.Length, (uint)Marshal.SizeOf(rid[0])))
throw new ApplicationException("Failed to register raw input device(s).");
}
/// <summary>
/// Add this KeyboardHook to a window
/// </summary>
/// <param name="window">The window to add to</param>
public void AddHook(IntPtr handle) {
if (handle == null)
throw new ArgumentNullException("window");
if (mHwndSource != null)
throw new InvalidOperationException("Hook already present");
//--- NEG: wpf code:
//WindowInteropHelper w = new WindowInteropHelper(form);
//IntPtr hwnd = w.Handle;
//---
IntPtr hwnd = handle;
mHwndSource = HwndSource.FromHwnd(hwnd);
if (mHwndSource == null)
throw new ApplicationException("Failed to receive window source");
mHwndSource.AddHook(WndProc);
RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[1];
rid[0].usUsagePage = 0x01;
rid[0].usUsage = 0x06;
rid[0].dwFlags = RIDEV_INPUTSINK;
rid[0].hwndTarget = hwnd;
if (!RegisterRawInputDevices(rid, (uint)rid.Length, (uint)Marshal.SizeOf(rid[0])))
throw new ApplicationException("Failed to register raw input device(s).");
}
/// <summary>
/// Remove this keyboard hook from window (if it is added)
/// </summary>
public void RemoveHook() {
if (mHwndSource == null)
return; // not an error
RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[1];
rid[0].usUsagePage = 0x01;
rid[0].usUsage = 0x06;
rid[0].dwFlags = 0x00000001;
rid[0].hwndTarget = IntPtr.Zero;
RegisterRawInputDevices(rid, (uint)rid.Length, (uint)Marshal.SizeOf(rid[0]));
mHwndSource.RemoveHook(WndProc);
mHwndSource.Dispose();
mHwndSource = null;
}
public void Dispose() {
RemoveHook();
}
private IntPtr mHookDeviceId;
private HwndSource mHwndSource;
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {
switch (msg) {
case WM_INPUT:
if (ProcessInputCommand(mHookDeviceId, lParam)) {
MSG message;
PeekMessage(out message, IntPtr.Zero, WM_KEYDOWN, WM_KEYDOWN, PM_REMOVE);
}
break;
}
return IntPtr.Zero;
}
/// <summary>
/// Get a list of keyboard devices available
/// </summary>
/// <returns>Collection of devices available</returns>
public static ICollection<string> GetKeyboardDevices() {
return FindAllKeyboardDevices().Keys;
}
private static Dictionary<string, IntPtr> FindAllKeyboardDevices() {
Dictionary<string, IntPtr> deviceNames = new Dictionary<string, IntPtr>();
uint deviceCount = 0;
int dwSize = (Marshal.SizeOf(typeof(RAWINPUTDEVICELIST)));
if (GetRawInputDeviceList(IntPtr.Zero, ref deviceCount, (uint)dwSize) == 0) {
IntPtr pRawInputDeviceList = Marshal.AllocHGlobal((int)(dwSize*deviceCount));
try {
GetRawInputDeviceList(pRawInputDeviceList, ref deviceCount, (uint)dwSize);
for (int i = 0; i < deviceCount; i++) {
uint pcbSize = 0;
var rid = (RAWINPUTDEVICELIST)Marshal.PtrToStructure(
new IntPtr((pRawInputDeviceList.ToInt32() + (dwSize*i))),
typeof(RAWINPUTDEVICELIST));
GetRawInputDeviceInfo(rid.hDevice, RIDI_DEVICENAME, IntPtr.Zero, ref pcbSize);
if (pcbSize > 0) {
IntPtr pData = Marshal.AllocHGlobal((int)pcbSize);
try {
GetRawInputDeviceInfo(rid.hDevice, RIDI_DEVICENAME, pData, ref pcbSize);
string deviceName = Marshal.PtrToStringAnsi(pData);
// The list will include the "root" keyboard and mouse devices
// which appear to be the remote access devices used by Terminal
// Services or the Remote Desktop - we're not interested in these
// so the following code with drop into the next loop iteration
if (deviceName.ToUpper().Contains("ROOT"))
continue;
// If the device is identified as a keyboard or HID device,
// Check if it is the one we're looking for
if (rid.dwType == RIM_TYPEKEYBOARD || rid.dwType == RIM_TYPEHID) {
Match match = DeviceNamePattern.Match(deviceName);
if (match.Success)
deviceNames.Add(match.Groups[1].Value, rid.hDevice);
}
}
finally {
Marshal.FreeHGlobal(pData);
}
}
}
}
finally {
Marshal.FreeHGlobal(pRawInputDeviceList);
}
}
return deviceNames;
}
/// <summary>
/// Processes WM_INPUT messages to retrieve information about any
/// keyboard events that occur.
/// </summary>
/// <param name="deviceId">Device to process</param>
/// <param name="lParam">The WM_INPUT message to process.</param>
private bool ProcessInputCommand(IntPtr deviceId, IntPtr lParam) {
uint dwSize = 0;
try {
// First call to GetRawInputData sets the value of dwSize
// dwSize can then be used to allocate the appropriate amount of memory,
// storing the pointer in "buffer".
GetRawInputData(lParam, RID_INPUT, IntPtr.Zero,ref dwSize, (uint)Marshal.SizeOf(typeof(RAWINPUTHEADER)));
IntPtr buffer = Marshal.AllocHGlobal((int)dwSize);
try {
// Check that buffer points to something, and if so,
// call GetRawInputData again to fill the allocated memory
// with information about the input
if (buffer != IntPtr.Zero &&
GetRawInputData(lParam, RID_INPUT, buffer, ref dwSize, (uint)Marshal.SizeOf(typeof(RAWINPUTHEADER))) == dwSize) {
// Store the message information in "raw", then check
// that the input comes from a keyboard device before
// processing it to raise an appropriate KeyPressed event.
RAWINPUT raw = (RAWINPUT)Marshal.PtrToStructure(buffer, typeof(RAWINPUT));
if (raw.header.hDevice != deviceId)
return false;
if (raw.header.dwType != RIM_TYPEKEYBOARD)
return false;
if (raw.keyboard.Message != WM_KEYDOWN && raw.keyboard.Message != WM_SYSKEYDOWN)
return false;
// On most keyboards, "extended" keys such as the arrow or page
// keys return two codes - the key's own code, and an "extended key" flag, which
// translates to 255. This flag isn't useful to us, so it can be
// disregarded.
if (raw.keyboard.VKey > VK_LAST_KEY)
return false;
if (KeyPressed != null) {
string scannedText = null;
lock (mLocalBuffer) {
if (GetKeyboardState(mKeyboardState)) {
if (ToUnicode(raw.keyboard.VKey, raw.keyboard.MakeCode, mKeyboardState, mLocalBuffer, 64, 0) > 0) {
if (mLocalBuffer.Length > 0) {
scannedText = mLocalBuffer.ToString();
}
}
}
}
if (!string.IsNullOrEmpty(scannedText))
KeyPressed(this, new KeyPressedEventArgs(scannedText));
}
return true;
}
}
finally {
Marshal.FreeHGlobal(buffer);
}
}
catch (Exception e) {
throw new AppException(SCL_Languages.getValue("internalerror"), e.Message, e);
}
return false;
}
private static readonly StringBuilder mLocalBuffer = new StringBuilder(64);
private static readonly byte[] mKeyboardState = new byte[256];
}
public partial class KeyboardHook
{
private const int RIDEV_INPUTSINK = 0x00000100;
private const int RIDEV_REMOVE = 0x00000001;
private const int RID_INPUT = 0x10000003;
private const int FAPPCOMMAND_MASK = 0xF000;
private const int FAPPCOMMAND_MOUSE = 0x8000;
private const int FAPPCOMMAND_OEM = 0x1000;
private const int RIM_TYPEMOUSE = 0;
private const int RIM_TYPEKEYBOARD = 1;
private const int RIM_TYPEHID = 2;
private const int RIDI_DEVICENAME = 0x20000007;
private const int WM_KEYDOWN = 0x0100;
private const int WM_SYSKEYDOWN = 0x0104;
private const int WM_INPUT = 0x00FF;
private const int VK_OEM_CLEAR = 0xFE;
private const int VK_LAST_KEY = VK_OEM_CLEAR; // this is a made up value used as a sentinal
private const int PM_REMOVE = 0x01;
[StructLayout(LayoutKind.Sequential)]
private struct RAWINPUTDEVICELIST
{
public IntPtr hDevice;
[MarshalAs(UnmanagedType.U4)]
public int dwType;
}
[StructLayout(LayoutKind.Explicit)]
private struct RAWINPUT
{
[FieldOffset(0)]
public RAWINPUTHEADER header;
[FieldOffset(16)]
public RAWMOUSE mouse;
[FieldOffset(16)]
public RAWKEYBOARD keyboard;
[FieldOffset(16)]
public RAWHID hid;
}
[StructLayout(LayoutKind.Sequential)]
private struct RAWINPUTHEADER
{
[MarshalAs(UnmanagedType.U4)]
public int dwType;
[MarshalAs(UnmanagedType.U4)]
public int dwSize;
public IntPtr hDevice;
[MarshalAs(UnmanagedType.U4)]
public int wParam;
}
[StructLayout(LayoutKind.Sequential)]
private struct RAWHID
{
[MarshalAs(UnmanagedType.U4)]
public int dwSizHid;
[MarshalAs(UnmanagedType.U4)]
public int dwCount;
}
[StructLayout(LayoutKind.Sequential)]
private struct BUTTONSSTR
{
[MarshalAs(UnmanagedType.U2)]
public ushort usButtonFlags;
[MarshalAs(UnmanagedType.U2)]
public ushort usButtonData;
}
[StructLayout(LayoutKind.Explicit)]
private struct RAWMOUSE
{
[MarshalAs(UnmanagedType.U2)]
[FieldOffset(0)]
public ushort usFlags;
[MarshalAs(UnmanagedType.U4)]
[FieldOffset(4)]
public uint ulButtons;
[FieldOffset(4)]
public BUTTONSSTR buttonsStr;
[MarshalAs(UnmanagedType.U4)]
[FieldOffset(8)]
public uint ulRawButtons;
[FieldOffset(12)]
public int lLastX;
[FieldOffset(16)]
public int lLastY;
[MarshalAs(UnmanagedType.U4)]
[FieldOffset(20)]
public uint ulExtraInformation;
}
[StructLayout(LayoutKind.Sequential)]
private struct RAWKEYBOARD
{
[MarshalAs(UnmanagedType.U2)]
public ushort MakeCode;
[MarshalAs(UnmanagedType.U2)]
public ushort Flags;
[MarshalAs(UnmanagedType.U2)]
public ushort Reserved;
[MarshalAs(UnmanagedType.U2)]
public ushort VKey;
[MarshalAs(UnmanagedType.U4)]
public uint Message;
[MarshalAs(UnmanagedType.U4)]
public uint ExtraInformation;
}
[StructLayout(LayoutKind.Sequential)]
private struct RAWINPUTDEVICE
{
[MarshalAs(UnmanagedType.U2)]
public ushort usUsagePage;
[MarshalAs(UnmanagedType.U2)]
public ushort usUsage;
[MarshalAs(UnmanagedType.U4)]
public int dwFlags;
public IntPtr hwndTarget;
}
[DllImport("User32.dll")]
private static extern uint GetRawInputDeviceList(IntPtr pRawInputDeviceList, ref uint uiNumDevices, uint cbSize);
[DllImport("User32.dll")]
private static extern uint GetRawInputDeviceInfo(IntPtr hDevice, uint uiCommand, IntPtr pData, ref uint pcbSize);
[DllImport("User32.dll")]
private static extern bool RegisterRawInputDevices(RAWINPUTDEVICE[] pRawInputDevice, uint uiNumDevices, uint cbSize);
[DllImport("User32.dll")]
private static extern uint GetRawInputData(IntPtr hRawInput, uint uiCommand, IntPtr pData, ref uint pcbSize, uint cbSizeHeader);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetKeyboardState(byte[] lpKeyState);
[DllImport("user32.dll")]
private static extern int ToUnicode(uint wVirtKey, uint wScanCode, byte[] lpKeyState, [Out, MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)] StringBuilder pwszBuff,
int cchBuff, uint wFlags);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool PeekMessage(out MSG lpmsg, IntPtr hwnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);
I'm working on a program, who need to detect when the user press the keyboard or use his mouse, even if the program is minimized or not focused.
I think I have to use the windows API, keybd_event (user32), but I don't know how to use the "listener" of this event. I have something like that:
[DllImport("user32.dll")]
static extern void keybd_event(byte bVk, byte bScan, uint dwFlags,UIntPtr dwExtraInfo);
void PressKey(byte keyCode)
{
//My code here
}
I did some research, but it's the first time I have to use DllImport, so I don't know how to continue ...
Thanks
(Ps:Sorry about my bad English, this is not my native language :) )
(PPs: I've read all of your answers, but it takes a while to read every link, so I'll work on it tonight, but I think I will find the answer. Anyway, thanks for the links everybody ;) )
Edit: So I just finished my code and it's work :) It looks something like:
[DllImport("user32.dll")]
public static extern Boolean GetLastInputInfo(ref tagLASTINPUTINFO plii);
public struct tagLASTINPUTINFO
{
public uint cbSize;
public Int32 dwTime;
}
private void timerTemps_Inactif_Tick(object sender, EventArgs e)
{
tagLASTINPUTINFO LastInput = new tagLASTINPUTINFO();
Int32 IdleTime;
LastInput.cbSize = (uint)Marshal.SizeOf(LastInput);
LastInput.dwTime = 0;
if (GetLastInputInfo(ref LastInput))
{
IdleTime = System.Environment.TickCount - LastInput.dwTime;
if (IdleTime > 10000)
{
//My code here
}
}
}
Thanks for the help guys ;)
You will need to hook into Windows OS with SetWindowsHookEx function. You should read the article Keyloggers: How they work and how to detect them posted by SecureList to get a understanding ofthe process.
I have always got a good performance by using RegisterHotKey/UnregisterHotKey functions. Sample code:
[DllImport("User32")]
public static extern bool RegisterHotKey(
IntPtr hWnd,
int id,
int fsModifiers,
int vk
);
[DllImport("User32")]
public static extern bool UnregisterHotKey(
IntPtr hWnd,
int id
);
public const int MOD_SHIFT = 0x4;
public const int MOD_CONTROL = 0x2;
public const int MOD_ALT = 0x1;
public const int WM_HOTKEY = 0x312;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_HOTKEY && m.WParam == (IntPtr)0)
{
IntPtr lParamCTRLA = (IntPtr)4259842;
IntPtr lParamB = (IntPtr)4325376;
if (m.LParam == lParamCTRLA)
{
MessageBox.Show("CTRL+A was pressed");
}
else if (m.LParam == lParamB)
{
MessageBox.Show("B was pressed");
}
}
base.WndProc(ref m);
}
private void Form1_Load(object sender, EventArgs e)
{
this.FormClosing += new FormClosingEventHandler(Form1_FormClosing);
RegisterHotKey(this.Handle, 0, MOD_CONTROL, (int)Keys.A);
RegisterHotKey(this.Handle, 0, 0, (int)Keys.B);
}
private void Form1_FormClosing(Object sender, FormClosingEventArgs e)
{
UnregisterHotKey(this.Handle, 0);
}
You can "register" as many keys (or combination of keys) as you want by emulating the shown structure. All the registered keys will get inside the condition if (m.Msg == WM_HOTKEY && m.WParam == (IntPtr)0); if they are pressed at all (independently upon the program currently being selected). The easiest way to know the specific key/combination being pressed is relying on m.LParam (I got the two values I am including after a quick test with the given keys). You can do a quick research to find out a list of LParam or further constant modifiers (wheel of the mouse, for example).
The final code:
[DllImport("user32.dll")]
public static extern Boolean GetLastInputInfo(ref tagLASTINPUTINFO plii);
public struct tagLASTINPUTINFO
{
public uint cbSize;
public Int32 dwTime;
}
private void timerTemps_Inactif_Tick(object sender, EventArgs e)
{
tagLASTINPUTINFO LastInput = new tagLASTINPUTINFO();
Int32 IdleTime;
LastInput.cbSize = (uint)Marshal.SizeOf(LastInput);
LastInput.dwTime = 0;
if (GetLastInputInfo(ref LastInput))
{
IdleTime = System.Environment.TickCount - LastInput.dwTime;
if (IdleTime > 10000)
{
//My code here
}
}
}
I want to hide my mouse cursor after an idle time and it will be showed up when I move the mouse. I tried to use a timer but it didn't work well. Can anybody help me? Please!
If you are using WinForms and will only deploy on Windows machines then it's quite easy to use user32 GetLastInputInfo to handle both mouse and keyboard idling.
public static class User32Interop
{
public static TimeSpan GetLastInput()
{
var plii = new LASTINPUTINFO();
plii.cbSize = (uint)Marshal.SizeOf(plii);
if (GetLastInputInfo(ref plii))
return TimeSpan.FromMilliseconds(Environment.TickCount - plii.dwTime);
else
throw new Win32Exception(Marshal.GetLastWin32Error());
}
[DllImport("user32.dll", SetLastError = true)]
static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
struct LASTINPUTINFO
{
public uint cbSize;
public uint dwTime;
}
}
And then in your Form
public partial class MyForm : Form
{
Timer activityTimer = new Timer();
TimeSpan activityThreshold = TimeSpan.FromMinutes(2);
bool cursorHidden = false;
public Form1()
{
InitializeComponent();
activityTimer.Tick += activityWorker_Tick;
activityTimer.Interval = 100;
activityTimer.Enabled = true;
}
void activityWorker_Tick(object sender, EventArgs e)
{
bool shouldHide = User32Interop.GetLastInput() > activityThreshold;
if (cursorHidden != shouldHide)
{
if (shouldHide)
Cursor.Hide();
else
Cursor.Show();
cursorHidden = shouldHide;
}
}
}
Here is a contrived example of how to do it. You probably had some missing logic that was overriding the cursor's visibility:
public partial class Form1 : Form
{
public TimeSpan TimeoutToHide { get; private set; }
public DateTime LastMouseMove { get; private set; }
public bool IsHidden { get; private set; }
public Form1()
{
InitializeComponent();
TimeoutToHide = TimeSpan.FromSeconds(5);
this.MouseMove += new MouseEventHandler(Form1_MouseMove);
}
void Form1_MouseMove(object sender, MouseEventArgs e)
{
LastMouseMove = DateTime.Now;
if (IsHidden)
{
Cursor.Show();
IsHidden = false;
}
}
private void timer1_Tick(object sender, EventArgs e)
{
TimeSpan elaped = DateTime.Now - LastMouseMove;
if (elaped >= TimeoutToHide && !IsHidden)
{
Cursor.Hide();
IsHidden = true;
}
}
}
Need to account for Environment.Tickcount being negative:
public static class User32Interop
{
public static TimeSpan GetLastInput()
{
var plii = new LASTINPUTINFO();
plii.cbSize = (uint)Marshal.SizeOf(plii);
if (GetLastInputInfo(ref plii))
{
int idleTime = unchecked(Environment.TickCount - (int)plii.dwTime);
return TimeSpan.FromMilliseconds(idleTime);
}
else
throw new Win32Exception(Marshal.GetLastWin32Error());
}
[DllImport("user32.dll", SetLastError = true)]
static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
struct LASTINPUTINFO
{
public uint cbSize;
public uint dwTime;
}
}
If you try to hide the cursor from another thread you need to call Invoke(...) on the winform to enter the UI thread. Had cost me some headegg why Cursor.Hide() wasn't working. Also "PInvoke ShowCursor(bool bShow)" has the benefit to return the Show/Hide-Count.
[DllImport("user32.dll")]
static extern int ShowCursor(bool bShow);
private System.Timers.Timer _timerMouseAutoHide;
Point _lastPos = new Point(int.MaxValue);
readonly Stopwatch _stopwatch = new Stopwatch();
private int _cursorVisibilityCount = 0;
private void InitMouseAutoHide()
{
if (_timerMouseAutoHide != null)
return;
_timerMouseAutoHide = new Timer(100){AutoReset = false};
_timerMouseAutoHide.Elapsed += MouseAutoHideHit;
_timerMouseAutoHide.Start();
_stopwatch.Restart();
}
private void MouseAutoHideHit(object pSender, ElapsedEventArgs pE)
{
try
{
if (IsDisposed)
return;
var lCurrentPos = Cursor.Position;
var lCursorHasMoved = _lastPos != lCurrentPos;
_lastPos = lCurrentPos;
if (_cursorVisibilityCount < 0 && lCursorHasMoved)
{
//Cursor is not Visible and Mouse has moved
_cursorVisibilityCount = ShowCursor();
Debug.WriteLine($"Visible {lCurrentPos}, ShowCursor={_cursorVisibilityCount}");
}
else if (_cursorVisibilityCount > -1 && _stopwatch.ElapsedMilliseconds > 3000)
{
//Cursor is Visible and Mouse didn't move fox x ms.
_cursorVisibilityCount = HideCursor();
Debug.WriteLine($"Hidden {lCurrentPos}, ShowCursor={_cursorVisibilityCount}");
}
if (lCursorHasMoved)
_stopwatch.Restart();
}
catch (Exception lEx)
{
GLog.Error(lEx);
}
finally
{
_timerMouseAutoHide?.Start();
}
}
public int ShowCursor()
{
var lCursorVisibilityCount = 0;
Invoke(new Action(() =>
{
do
{
lCursorVisibilityCount = ShowCursor(true);
} while (lCursorVisibilityCount < 0);
}));
return lCursorVisibilityCount;
}
public int HideCursor()
{
var lCursorVisibilityCount = 0;
Invoke(new Action(() =>
{
do
{
lCursorVisibilityCount = ShowCursor(false);
} while (lCursorVisibilityCount > -1);
}));
return lCursorVisibilityCount;
}