Detecting specific key in low level keyboard hook - c#

I'm creating an application that places an icon on the system tray that changes its appearance based on the state of the Caps Lock key. The issue I'm facing is that the hook only works correctly after a key other than Caps Lock is pressed, since that key flips the check after the hook is passed through, making the icon display the wrong state incorrectly.
I would need a way to detect when the Caps Lock key is pressed inside the hook to flip the detected state.
private static NotifyIcon notifyIcon = new NotifyIcon();
private static bool CapsPressed = Control.IsKeyLocked(Keys.CapsLock);
static Icon
AppIcon = CapsIndicator.Properties.Resources.AppIcon,
OnIcon = CapsIndicator.Properties.Resources.OnIcon,
OffIcon = CapsIndicator.Properties.Resources.OffIcon;
static void UpdateIcon() {
notifyIcon.Icon = CapsPressed ? OnIcon : OffIcon;
}
// Hook initializing & other stuff here
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) {
if (nCode >= 0 && wParam == (IntPtr) WM_KEYDOWN) {
CapsPressed = Control.IsKeyLocked(Keys.CapsLock);
UpdateIcon();
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}

The solution isn't very straight-forward, but the key code can be revealed using the lParam pointer argument. You can do this by accessing the 32-bit integer it's pointing to with Marshal.ReadInt32, then, you need to cast it to the Keys type, and finally compare this value with Keys.CapsLock (or any other key you like):
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) {
if (nCode >= 0 && wParam == (IntPtr) WM_KEYDOWN) {
int vkCode = Marshal.ReadInt32(lParam);
CapsPressed = Control.IsKeyLocked(Keys.CapsLock);
// Flip the detected value if CapsLock is pressed
if ((Keys) vkCode == Keys.CapsLock) CapsPressed = !CapsPressed;
UpdateIcon();
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}

Related

How to stop further processing global hotkeys in C#

I am trying to create a C# application that will react on a global hotkey; ALT+H and then when I release the ALT key. This actually does work very well but my problem is that when my application has done whatever it should do with the hotkey then it should stop this hotkey from being processed by other applications. I got most of my code below from this StackOverflow post, Global keyboard capture in C# application, but I have seen somewhere else that return (IntPtr)1; should be able to stop further processing of the key.
But... when I am in e.g. Word or Wordpad and I press ALT+H then Word shows all kind of menus - I don't want it to as I only want my application to do something :-)
I am sorry for this rather long code but I assume it is important so you can get the full overview. There is only 1 place where I use return (IntPtr)1;.
My code:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Windows.Input;
namespace MyNewProgram
{
static class Program
{
// Enable hotkeys
// https://stackoverflow.com/a/604417/2028935
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private const int WM_KEYUP = 0x0101;
private const int WM_SYSKEYUP = 0x0105;
private const int VK_SHIFT = 0x10;
private const int VK_MENU = 0x12;
private static LowLevelKeyboardProc _proc = HookCallback;
private static IntPtr _hookID = IntPtr.Zero;
// Other variables
public static bool isAltPressedInThisApp = false;
private static MainWindow MyMainWindow;
// --------------------------------------------------------------------------------------
[STAThread] // STAThreadAttribute indicates that the COM threading model for the application is single-threaded apartment, https://stackoverflow.com/a/1361048/2028935
static void Main()
{
// Hook application to keyboard
_hookID = SetHook(_proc);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
MyMainWindow = new MainWindow();
Application.Run(MyMainWindow);
// Unhook application from keyboard
UnhookWindowsHookEx(_hookID);
}
// --------------------------------------------------------------------------------------
// Required functions for globally hooking the keyboard
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern short GetKeyState(int keyCode);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
// --------------------------------------------------------------------------------------
// Hook the keyboard - action
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
// React on KEYDOWN
if (nCode >= 0 && ((wParam == (IntPtr)WM_KEYDOWN) || (wParam == (IntPtr)WM_SYSKEYUP)))
{
int vkCode = Marshal.ReadInt32(lParam);
// H
if ((Keys)vkCode == Keys.H)
{
isAltPressedInThisApp = true;
// Is ALT pressed down
if ((GetKeyState(VK_MENU) & 0x8000) != 0)
{
Console.WriteLine("ALT + H");
return (IntPtr)1; // do not allow others to hook this key combo
}
}
}
// React on KEYUP
if (nCode >= 0 && wParam == (IntPtr)WM_KEYUP)
{
int vkCode = Marshal.ReadInt32(lParam);
// Is ALT not pressed down
if ((Keys)vkCode == Keys.LMenu)
{
Console.WriteLine("ALT UP");
}
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
// --------------------------------------------------------------------------------------
// Hook the keyboard
private static IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
}
}
}
}
I am not a solid C# developer as I am taking my first few babysteps here - so why would I throw myself in the deep waters of global hotkeys as one of the first things ;-) Is there anyone that can give some hints here as I probably have a silly mistake somewhere?
The idea behind, whenever the alt key pressed, it starts to swallow keys into one local list. if the pattern we need to see has been seen, it won't send, but for the every other pattern, it starts to send the keys again in the order it receives.
the code for examination phase:
enum WM
{
WM_KEYDOWN = 0x0100,
WM_KEYUP = 0x0101,
WM_SYSKEYUP = 0x0105,
WM_SYSKEYDOWN = 0x0104,
}
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode < 0)
return CallNextHookEx(_hookID, nCode, wParam, lParam);
KBDLLHOOKSTRUCT kbd = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
Enum.TryParse<Keys>($"{kbd.vkCode}", out Keys key);
Enum.TryParse<WM>($"{wParam}", out WM message);
Console.WriteLine($"{message}:{key}");
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
when pressing alt+h and releasing, output:
WM_SYSKEYDOWN:LMenu
WM_SYSKEYDOWN:H
WM_SYSKEYUP:H
WM_KEYUP:LMenu
as you can see, windows sent both alt and h key. the code you provided only catch H key, so the window which receives keyboard messages think that alt pressed.
no one can fetch and filter out previously pressed key, so we need to catch alt key whenever we see it is pressed. if the next key not H, we should send the keys we have fetched in the order we received.
i have written the below code to handle the situation, but i can not be pretty sure how it works on the real windows machine cause i have an osx operating system just because of that i am running windows in the virtual machine which is also change key strokes when i'm pressing.
if it's still not enough, you can wait and i may be able to try and figure it out at my office in the real windows machine. but i think you get the idea and work it out on your own.
[StructLayout(LayoutKind.Sequential)]
public class KBDLLHOOKSTRUCT
{
public uint vkCode;
public uint scanCode;
public KBDLLHOOKSTRUCTFlags flags;
public uint time;
public UIntPtr dwExtraInfo;
}
[Flags]
public enum KBDLLHOOKSTRUCTFlags : uint
{
LLKHF_EXTENDED = 0x01,
LLKHF_INJECTED = 0x10,
LLKHF_ALTDOWN = 0x20,
LLKHF_UP = 0x80,
}
[DllImport("user32.dll")]
static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);
enum WM
{
WM_KEYDOWN = 0x0100,
WM_KEYUP = 0x0101,
WM_SYSKEYUP = 0x0105,
WM_SYSKEYDOWN = 0x0104,
}
private static void ReSendKeys(int index = -1)
{
_index = -1;
var copiedKeys = _swallowedKeys.ToArray();
for (int i = 0; i < copiedKeys.Length; ++i)
{
bool up = copiedKeys[i].Item1 == (IntPtr)WM.WM_SYSKEYUP || copiedKeys[i].Item1 == (IntPtr)WM.WM_KEYUP;
keybd_event((byte)copiedKeys[i].Item2.vkCode, (byte)copiedKeys[i].Item2.scanCode, up ? 2u : 0u, UIntPtr.Zero);
}
_index = index;
_swallowedKeys.Clear();
}
private static List<Tuple<IntPtr, KBDLLHOOKSTRUCT>> _swallowedKeys = new List<Tuple<IntPtr, KBDLLHOOKSTRUCT>>();
private static int _index = 0;
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode < 0)
return CallNextHookEx(_hookID, nCode, wParam, lParam);
KBDLLHOOKSTRUCT kbd = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
Enum.TryParse<Keys>($"{kbd.vkCode}", out Keys key);
Enum.TryParse<WM>($"{wParam}", out WM message);
Console.Write($"{message}:{key}");
// we know that when _index is -1, ReSendKeys function has been called
// so do not filter out first alt key
if (_index == -1)
{
_index++;
Console.WriteLine();
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
// we are at the beginning of the sequence we will catch
// if it's alt key filter it out, and increment the variable
if (_index == 0)
{
_swallowedKeys.Add(new Tuple<IntPtr, KBDLLHOOKSTRUCT>(wParam, kbd));
if (message == WM.WM_SYSKEYDOWN && key == Keys.LMenu)
{
_index++;
Console.WriteLine(" filtered out");
return (IntPtr)1;
}
else
{
_swallowedKeys.Clear();
// do nothing
}
}
if (_index == 1)
{
_swallowedKeys.Add(new Tuple<IntPtr, KBDLLHOOKSTRUCT>(wParam, kbd));
// if the next key is H, then filter it out also
if (message == WM.WM_SYSKEYDOWN && key == Keys.H)
{
_index++;
_swallowedKeys.RemoveAt(_swallowedKeys.Count - 1);
Console.WriteLine(" filtered out");
return (IntPtr)1;
}
// if not, we filtered out wrong sequence, we need to resend them
else
{
Console.WriteLine();
ReSendKeys();
return (IntPtr)1;
}
}
if (_index == 2)
{
_swallowedKeys.Add(new Tuple<IntPtr, KBDLLHOOKSTRUCT>(wParam, kbd));
if (message == WM.WM_SYSKEYUP && key == Keys.H)
{
_index++;
_swallowedKeys.RemoveAt(_swallowedKeys.Count - 1);
Console.WriteLine(" filtered out");
return (IntPtr)1;
}
else
{
// if user pressed H but not released and pressed another key at the same time
// i will pass that situation, if u need to handle something like that, you got the idea, please fill that block of code
}
}
if (_index == 3)
{
_swallowedKeys.Add(new Tuple<IntPtr, KBDLLHOOKSTRUCT>(wParam, kbd));
if (message == WM.WM_KEYUP && key == Keys.LMenu)
{
_index = 0;
_swallowedKeys.Clear();
Console.WriteLine(" filtered out");
Console.WriteLine("shortcut disabled");
return (IntPtr)1;
}
else
{
Console.WriteLine();
// user has been pressed Alt + H, H released but not alt, so we can expect one H again, but we need to send this pressed key with Alt prefixed
ReSendKeys(1);
return (IntPtr)1;
}
}
Console.WriteLine();
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}

How to capture the WM_CONTEXTMENU message using SetWindowsHookEx

I am creating an MindJet MindManager add-in, in which I want to get the location of a context menu so I can retrieve an element under it. I used Spy++ to watch the windows messages and I can see that the WM_COMMAND message is sent when the context menu item is clicked. I can even catch that event by setting up a windows hook.
private static int _hookID;
private const Int32 WH_GETMESSAGE = 3;
private const Int32 WM_COMMAND = 0x111;
private const Int32 WM_CONTEXTMENU = 0x007B;
public delegate int HookProc(int code, int wParam, IntPtr lParam);
public void SetUpHook()
{
HookHandler = HookCallback;
_hookID = SetWindowsHookEx(WH_GETMESSAGE, HookHandler, IntPtr.Zero,
threadId: AppDomain.GetCurrentThreadId());
}
public int HookCallback(int code, int wParam, IntPtr lParam)
{
...
var msg = (MSG)Marshal.PtrToStructure(lParam, typeof(MSG));
if (msg.message == WM_COMMAND)
{
// Do stuff here
}
if (msg.message == WM_CONTEXTMENU)
{
// I was hoping for this to get catch the context messaages
// but unfortunately these messages don't seem to be getting
// hooked the same way WM_COMMAND messages are
}
...
Why do these messages show up in Spy++ ?
Is there a different hook I need to set to catch WM_CONTEXTMENU?

Keyboard Hooks to disable it globally in C#

Anybody knows how can disable keyboard globally developing hooks in C#?
I have a source code that implements a hook to disable the keyboard in the same application but if I execute it and the keyboard was working in an text editor don't work it (I can keep writing) in Windows 7. I would like capture/disable the keyboard globally (for all applications that are running). My intention is to lock it completely for Windows 7.
This is my hook code:
globalKeyboardHook gkh = new globalKeyboardHook();
/// <summary>
/// Installs the global hook
/// </summary>
public void hook() {
IntPtr hInstance = LoadLibrary("User32");
hhook = SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, hInstance, 0);
}
public int hookProc(int code, int wParam, ref keyboardHookStruct lParam) {
//indicates if any of underlaing events set e.Handled flag
bool handled = false;
IntPtr lFakeParam = new IntPtr(lParam.vkCode);
if ((code >= 0) && (KeyDown != null || KeyUp != null || wParam == WM_SYSKEYDOWN || wParam == WM_SYSKEYUP))
{
//read structure KeyboardHookStruct at lParam
keyboardHookStruct MyKeyboardHookStruct = (keyboardHookStruct)Marshal.PtrToStructure(lFakeParam, typeof(keyboardHookStruct));
Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
KeyEventArgs e = new KeyEventArgs(keyData);
log.Debug("cached wParam = [" + wParam + "] vkCode = [" + MyKeyboardHookStruct.vkCode + "] flags = [" + MyKeyboardHookStruct.flags + "]");
if (MyKeyboardHookStruct.vkCode == 144)
{
// NUMLOCK
handled = false;
}
KeyDown(this, e);
}
//if event handled in application do not handoff to other listeners
if (handled)
{
SendKeys.Send("{NUMLOCK}");
SendKeys.Send("{NUMLOCK}");
return 1;
//return CallNextHookEx(hKeyboardHook, nCode, wParam, lFakeParam);
}
else
{
return CallNextHookEx(hhook, code, wParam, ref lParam);
}
}
Thanks in advance!

Can't send Browser_Back/Forward via SendInput() (only on Surface Pro 2)

some days ago, I got my Microsoft Arc Touch Mouse Surface Edition. Unfortunately, it doesn't have Forward/Back-Buttons (it performs only Page-Up/Page-Down).
So, I've written a little program, that catches the press of the Page_Up/Page_Down Keys and sends instead the Browser-Back/-Forward Button.
On my PC (Win 8.1 Pro) this works quite well. However, on my Surface Pro 2 (for which the program primarly was written) it won't send the Browser_Back-/Forward-Buttons.
For every other key the program seems to work (like Space, letters, numbers).
Here the code for sending Keycodes:
public static uint send(short keyCode) {
INPUT structure = new INPUT();
structure.type = (int)InputType.INPUT_KEYBOARD;
structure.ki.wVk = keyCode;
structure.ki.dwFlags = (int)KEYEVENTF.KEYDOWN;
structure.ki.dwExtraInfo = GetMessageExtraInfo();
INPUT input2 = new INPUT();
structure.type = (int)InputType.INPUT_KEYBOARD;
structure.ki.wVk = keyCode;
input2.mi.dwFlags = (int)KEYEVENTF.KEYUP;
input2.ki.dwExtraInfo = GetMessageExtraInfo();
INPUT[] pInputs = new INPUT[] { structure, input2 };
return SendInput(2, pInputs, Marshal.SizeOf(structure));
}
And here the KeyboardHook:
public static int KeyHookProc(int nCode, IntPtr wParam, IntPtr lParam) {
KeyHookStruct myKeyHookStruct =
(KeyHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyHookStruct));
if (nCode < 0) {
return CallNextHookEx(hHook, nCode, wParam, lParam);
} else {
if (wParam == (IntPtr)0x0100) {
int keyCode = myKeyHookStruct.vkCode;
if (keyCode == 33) {
Input.send(0xA7);
} else if (keyCode == 34) {
Input.send(0xA6);
} else {
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
}
return 1;
}
}
So, if the Page-Down/page-Up button on my keyboard is pressed, the send()-Method is called.
0xA6 and 0xA7 are the Keycodes for Browser_Back and Browser_Forward.
On my Surface, the Page-Up/Page-Down buttons are recognized correctly. The send()-Method is also called, but it won'T send the correct Keycodes. According to Spy++, send() isn't sending anything if the Keycode is 0xA6 or 0xA7 (on my PC it works).
Where is the problem with the Surface Pro 2? What could I do, that it works, or how could I determine why it isn't working?
The code you posted is wrong, as you never set type for input2 (the code use structure.type)
EDIT:
From MSDN docs for SendInput:
This function fails when it is blocked by UIPI. Note that neither
GetLastError nor the return value will indicate the failure was caused
by UIPI blocking.
This function is subject to UIPI. Applications are permitted to inject
input only into applications that are at an equal or lesser integrity
level.
You also should remove the call to GetMessageExtraInfo
New Edit:
I don't like the logic of your KeyHookProc function: if the message is NOT WM_KEYDOWN (0x0100), then you return 1...
I would go for:
if (wParam != (IntPtr)0x0100) {
return CallNextHookEx(hHook, nCode, wParam, lParam);
} else {
int keyCode = myKeyHookStruct.vkCode;
if (keyCode == 33) {
Input.send(0xA7);
} else if (keyCode == 34) {
Input.send(0xA6);
} else {
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
return 1;
}

WndProc to detect device unplug and plug

How can I know a devices is plug or unplug in WPF?
I am using the code below to detect device changes:
private void OnSourceInitialized(object sender, EventArgs e)
{
IntPtr windowHandle = (new WindowInteropHelper(this)).Handle;
HwndSource src = HwndSource.FromHwnd(windowHandle);
src.AddHook(new HwndSourceHook(WndProc));
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
// Handle WM_DEVICECHANGE...
if (msg == 0x219)
{
InitHead();
}
return IntPtr.Zero;
}
Thank you.
EDITED:
I did the below, still not working:
if (msg == 0x0219)
{
switch (wParam.ToInt32())
{
case 0x8000:
{
InitHead();
}
break;
}
}
To detect if a device has been plugged in, , we add the hook to our Window_Loaded method that looks like the following
HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
source.AddHook(new HwndSourceHook(this.WndProc));
The handler looks as follows:
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == 0x0219 && (int)wParam == 0x8000) // 0x8000 is DBT_DEVICEARRIVAL
{
ProcessConnected();
}
return IntPtr.Zero;
}
Unfortunately, none of the DBT_DEVICE constants are triggered when a device is unplugged, rather they are called when you try to eject the device from Windows.

Categories

Resources