Home > Mobile >  GetKeyState() false positive only on German keyboard layouts
GetKeyState() false positive only on German keyboard layouts

Time:09-12

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.

  • Related