When using GetKeyState
to check whether a key is pressed, I'm getting a strange false positive only on German keyboard layouts. The check is within a low-level keyboard hook.
The specific key that seems to be causing issues is RMenu
(the right Alt key).
In the hook proc below, pressing RMenu P
on a US keyboard will populate the pressedKeys
dictionary with Menu
, RMenu
, P
. Whereas pressing the same keybinding on a German layout will populate the dict with Menu
, RMenu
, ControlKey
, LControlKey
, P
(where ControlKey
, LControlKey
are falsely reported as down).
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
internal static class Program
{
private const uint WM_KEYDOWN = 0x100;
private const uint WM_SYSKEYDOWN = 0x104;
[STAThread]
private static void Main()
{
_ = SetWindowsHookEx(HookType.WH_KEYBOARD_LL, KeybindingHookProc, Process.GetCurrentProcess().MainModule.BaseAddress, 0);
// `SetWindowsHookEx` requires a message loop within the thread that is executing the code.
Application.Run();
}
private static IntPtr KeybindingHookProc(int nCode, IntPtr wParam, IntPtr lParam)
{
var shouldPassThrough = nCode != 0 || !((uint)wParam == WM_KEYDOWN || (uint)wParam == WM_SYSKEYDOWN);
// If nCode is less than zero, the hook procedure must pass on the hook notification.
if (shouldPassThrough)
return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
var inputEvent =
(LowLevelKeyboardInputEvent)Marshal.PtrToStructure(lParam, typeof(LowLevelKeyboardInputEvent));
var pressedKeys = new Dictionary<Keys, bool>
{
[inputEvent.Key] = true
};
foreach (var key in Enum.GetValues<Keys>())
{
if (IsKeyDown(key))
pressedKeys[key] = true;
}
// Check for arbitrary keybinding (Right alt P):
// !!!!!
if (!pressedKeys.ContainsKey(Keys.P) && !pressedKeys.ContainsKey(Keys.LMenu))
return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
Debug.WriteLine("Keybinding triggered!");
return new IntPtr(1);
}
private static bool IsKeyDown(Keys key)
{
return (GetKeyState(key) & 0x8000) == 0x8000;
}
public enum HookType
{
WH_KEYBOARD_LL = 13,
}
public delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern IntPtr SetWindowsHookEx(HookType hookType, [MarshalAs(UnmanagedType.FunctionPtr)] HookProc lpfn, IntPtr hMod, int dwThreadId);
[DllImport("user32.dll")]
public static extern IntPtr CallNextHookEx([Optional] IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern short GetKeyState(Keys nVirtKey);
[StructLayout(LayoutKind.Sequential)]
public struct LowLevelKeyboardInputEvent
{
public int VirtualCode;
public Keys Key => (Keys)VirtualCode;
public int HardwareScanCode;
public int Flags;
public int TimeStamp;
public IntPtr AdditionalInformation;
}
}
Is there any way to avoid this strange behavior? Is this a bug?
CodePudding user response:
As @HansPassant mentioned, the AltGr key is present on certain keyboard layouts, like German and US International. It generates both Control and Alt when pressed.