Tidbits

Read keyboard state on Windows without libraries

This is about actively reading the keyboard, not about getting key events. If you need that, check out this (archived) post. It still worked the last time I checked.

Here’s a simple static class that does the job:

/// <summary>
/// Checks the current keyboard state
/// </summary>
[SupportedOSPlatform("windows")]
public static class KeyboardState
{
private const ushort KeyDownBit = 1 << 15;
private const ushort LockToggledBit = 1;
/// <summary>
/// True if NumLock is on, false if it is off
/// </summary>
public static bool NumLock => (GetKeyState((int)Keys.NumLock) & LockToggledBit) == LockToggledBit;
/// <summary>
/// True if CapsLock is on, false if it is off
/// </summary>
public static bool CapsLock => (GetKeyState((int)Keys.CapsLock) & LockToggledBit) == LockToggledBit;
/// <summary>
/// True if ScrollLock is on, false if it is off
/// </summary>
public static bool ScrollLock => (GetKeyState((int)Keys.Scroll) & LockToggledBit) == LockToggledBit;
/// <summary>
/// Checks if a key (combination) is currently down
/// </summary>
/// <param name="key">The key code to check for, including modifiers</param>
/// <returns>True if the key is down, false if not</returns>
public static bool IsKeyDown(Keys key)
{
Keys keysCode = key & Keys.KeyCode;
if (IsKeyUpInternal(keysCode))
return false;
Keys modifiers = key & Keys.Modifiers;
if (modifiers == Keys.None)
return true;
if ((modifiers & Keys.Control) == Keys.Control && IsKeyUpInternal(Keys.ControlKey))
return false;
if ((modifiers & Keys.Shift) == Keys.Shift && IsKeyUpInternal(Keys.ShiftKey))
return false;
if ((modifiers & Keys.Alt) == Keys.Alt && IsKeyUpInternal(Keys.Menu))
return false;
return true;
}
private static bool IsKeyUpInternal(Keys keys) => (GetAsyncKeyState((int)keys) & KeyDownBit) != KeyDownBit;
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
private static extern ushort GetAsyncKeyState(int keyCode);
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
private static extern ushort GetKeyState(int keyCode);

Key to it all are the DllImports at the bottom:
GetAsyncKeyState is what you usually want. It can tell you if a key is currently pressed. It just can’t tell you about the “locks”.
GetKeyState on the other hand can tell you about the “locks”, but it usually won’t tell you if a key is pressed.

Because they rely on the Windows API, these methods will not work on any other OS. In exchange, as long as you are on Windows, you can use this code in any kind of project.
Almost.
What’s missing in that code is the System.Windows.Forms.Keys enum. If that is not available in your project (type), simply copy the source code and add that single enum manually in your project.
Or maybe copy only the relevant values? Here’s a short example that uses only the ones necessary for the code to compile:

[Flags]
public enum Keys
{
KeyCode = 0x0000FFFF,
Modifiers = unchecked((int)0xFFFF0000),
None = 0x00,
ShiftKey = 0x10,
ControlKey = 0x11,
Menu = 0x12,
CapsLock = 0x14,
NumLock = 0x90,
Scroll = 0x91,
Shift = 0x00010000,
Control = 0x00020000,
Alt = 0x00040000,
}

Leave a Reply