Keyboard inputs - scancodes, raw input, text input, key names

Recently I've been looking at a way to handle keyboard input independently of the keyboard layout. I'm using an AZERTY keyboard layout most of the time, and often when I try a game, the controls are arranged for a QWERTY layout and I either need to rebind the keys or press Alt + Shift for Windows to switch the layout to QWERTY. So I searched for a way to handle keys based on their position on the keyboard instead of the virtual key codes, and since it might be useful to others, I would like to share the result here.

So the following will discuss

  • how to get user input based on scancodes (the key "position") instead of virtual keys
  • how to use raw input for keyboard input
  • how to display the name of a scancode key localized in the user language
  • how to get text input for any character

It's Windows only but if anybody knows how to do the same on linux or mac, I would appreciate if you'd share your knowledge.

Scancodes

When you press a key on your keyboard, the keyboard sends some bytes to the keyboard driver to let it know a key was pressed. This is known as the "make code" (key down). It represents the key position, not a character. When you release the key, the keyboard sends another code known as the "break code" (key up). The list of all those codes is the "scancode set". The driver and Windows use those codes to generate key messages (WM_KEYDOWN, WM_KEYUP...) and virtual key codes (VK_) that your application can consume and that match the keyboard layout of the user (e.g. on QWERTY and AZERTY layouts the 'A' and 'Q' key is inverted). Fortunately windows also keeps a way to access key scancodes. Unfortunately, as far as I know, Windows doesn't provide a list of all scancodes.

A small note: there are 3 scancode sets (for PS/2 keyboard): set 1 (IBM PC XT), set 2 (IBM PC AT), set 3 (IBM 3270 PC). On Windows, keyboards are required to use set 2, but Windows presents the user with set 1 codes (for backward compatibility) and only presents "make code" (key down) to the user (the key up value contains the make code value instead of the break code). USB keyboards use another scancode set, but the presentation to the user is still PS/2 set 1 codes.

Those links will give you more informations: Scancode on wikipedia The PS/2 Keyboard Interface Scancode set 1 Scancode set 2 Scancode set 3 PS/2 Keyboard Driver Scan Code Support scancode.doc from Microsoft (March 16, 2000)

So I made a list of all the scancode values (for now on, scancode mean scancode set 1 make code, since that's the only thing we see on windows) and gave them a name corresponding to the key they represent on a QWERTY US layout. Scancodes can be several bytes and are organized in sequence of 1 or 2 bytes. Most keys are only 1 byte and below 0x80 (break codes are the make codes + 0x80). Two bytes scancodes generally start with 0xE0 (only "Pause" starts with 0xE1). If you don't use raw input, you can consider scancodes to never be more then 2 bytes (exception for "Pause"). If you use raw input, in my tests I've never got more than 2 messages of 2 bytes.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
/*
The scancode values come from:
- http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/scancode.doc (March 16, 2000).
- http://www.computer-engineering.org/ps2keyboard/scancodes1.html
- using MapVirtualKeyEx( VK_*, MAPVK_VK_TO_VSC_EX, 0 ) with the english us keyboard layout
- reading win32 WM_INPUT keyboard messages.
*/

enum Scancode {

    sc_escape = 0x01,
    sc_1 = 0x02,
    sc_2 = 0x03,
    sc_3 = 0x04,
    sc_4 = 0x05,
    sc_5 = 0x06,
    sc_6 = 0x07,
    sc_7 = 0x08,
    sc_8 = 0x09,
    sc_9 = 0x0A,
    sc_0 = 0x0B,
    sc_minus = 0x0C,
    sc_equals = 0x0D,
    sc_backspace = 0x0E,
    sc_tab = 0x0F,
    sc_q = 0x10,
    sc_w = 0x11,
    sc_e = 0x12,
    sc_r = 0x13,
    sc_t = 0x14,
    sc_y = 0x15,
    sc_u = 0x16,
    sc_i = 0x17,
    sc_o = 0x18,
    sc_p = 0x19,
    sc_bracketLeft = 0x1A,
    sc_bracketRight = 0x1B,
    sc_enter = 0x1C,
    sc_controlLeft = 0x1D,
    sc_a = 0x1E,
    sc_s =0x1F,
    sc_d = 0x20,
    sc_f = 0x21,
    sc_g = 0x22,
    sc_h = 0x23,
    sc_j = 0x24,
    sc_k = 0x25,
    sc_l = 0x26,
    sc_semicolon = 0x27,
    sc_apostrophe = 0x28,
    sc_grave = 0x29,
    sc_shiftLeft = 0x2A,
    sc_backslash = 0x2B,
    sc_z = 0x2C,
    sc_x = 0x2D,
    sc_c = 0x2E,
    sc_v = 0x2F,
    sc_b = 0x30,
    sc_n = 0x31,
    sc_m = 0x32,
    sc_comma = 0x33,
    sc_preiod = 0x34,
    sc_slash = 0x35,
    sc_shiftRight = 0x36,
    sc_numpad_multiply = 0x37,
    sc_altLeft = 0x38,
    sc_space = 0x39,
    sc_capsLock = 0x3A,
    sc_f1 = 0x3B,
    sc_f2 = 0x3C,
    sc_f3 = 0x3D,
    sc_f4 = 0x3E,
    sc_f5 = 0x3F,
    sc_f6 = 0x40,
    sc_f7 = 0x41,
    sc_f8 = 0x42,
    sc_f9 = 0x43,
    sc_f10 = 0x44,
    sc_numLock = 0x45,
    sc_scrollLock = 0x46,
    sc_numpad_7 = 0x47,
    sc_numpad_8 = 0x48,
    sc_numpad_9 = 0x49,
    sc_numpad_minus = 0x4A,
    sc_numpad_4 = 0x4B,
    sc_numpad_5 = 0x4C,
    sc_numpad_6 = 0x4D,
    sc_numpad_plus = 0x4E,
    sc_numpad_1 = 0x4F,
    sc_numpad_2 = 0x50,
    sc_numpad_3 = 0x51,
    sc_numpad_0 = 0x52,
    sc_numpad_period = 0x53,
    sc_alt_printScreen = 0x54, /* Alt + print screen. MapVirtualKeyEx( VK_SNAPSHOT, MAPVK_VK_TO_VSC_EX, 0 ) returns scancode 0x54. */
    sc_bracketAngle = 0x56, /* Key between the left shift and Z. */
    sc_f11 = 0x57,
    sc_f12 = 0x58,
    sc_oem_1 = 0x5a, /* VK_OEM_WSCTRL */
    sc_oem_2 = 0x5b, /* VK_OEM_FINISH */
    sc_oem_3 = 0x5c, /* VK_OEM_JUMP */
    sc_eraseEOF = 0x5d,
    sc_oem_4 = 0x5e, /* VK_OEM_BACKTAB */
    sc_oem_5 = 0x5f, /* VK_OEM_AUTO */
    sc_zoom = 0x62,
    sc_help = 0x63,
    sc_f13 = 0x64,
    sc_f14 = 0x65,
    sc_f15 = 0x66,
    sc_f16 = 0x67,
    sc_f17 = 0x68,
    sc_f18 = 0x69,
    sc_f19 = 0x6a,
    sc_f20 = 0x6b,
    sc_f21 = 0x6c,
    sc_f22 = 0x6d,
    sc_f23 = 0x6e,
    sc_oem_6 = 0x6f, /* VK_OEM_PA3 */
    sc_katakana = 0x70,
    sc_oem_7 = 0x71, /* VK_OEM_RESET */
    sc_f24 = 0x76,
    sc_sbcschar = 0x77,
    sc_convert = 0x79,
    sc_nonconvert = 0x7B, /* VK_OEM_PA1 */

    sc_media_previous = 0xE010,
    sc_media_next = 0xE019,
    sc_numpad_enter = 0xE01C,
    sc_controlRight = 0xE01D,
    sc_volume_mute = 0xE020,
    sc_launch_app2 = 0xE021,
    sc_media_play = 0xE022,
    sc_media_stop = 0xE024,
    sc_volume_down = 0xE02E,
    sc_volume_up = 0xE030,
    sc_browser_home = 0xE032,
    sc_numpad_divide = 0xE035,
    sc_printScreen = 0xE037,
    /*
    sc_printScreen:
    - make: 0xE02A 0xE037
    - break: 0xE0B7 0xE0AA
    - MapVirtualKeyEx( VK_SNAPSHOT, MAPVK_VK_TO_VSC_EX, 0 ) returns scancode 0x54;
    - There is no VK_KEYDOWN with VK_SNAPSHOT.
    */
    sc_altRight = 0xE038,
    sc_cancel = 0xE046, /* CTRL + Pause */
    sc_home = 0xE047,
    sc_arrowUp = 0xE048,
    sc_pageUp = 0xE049,
    sc_arrowLeft = 0xE04B,
    sc_arrowRight = 0xE04D,
    sc_end = 0xE04F,
    sc_arrowDown = 0xE050,
    sc_pageDown = 0xE051,
    sc_insert = 0xE052,
    sc_delete = 0xE053,
    sc_metaLeft = 0xE05B,
    sc_metaRight = 0xE05C,
    sc_application = 0xE05D,
    sc_power = 0xE05E,
    sc_sleep = 0xE05F,
    sc_wake = 0xE063,
    sc_browser_search = 0xE065,
    sc_browser_favorites = 0xE066,
    sc_browser_refresh = 0xE067,
    sc_browser_stop = 0xE068,
    sc_browser_forward = 0xE069,
    sc_browser_back = 0xE06A,
    sc_launch_app1 = 0xE06B,
    sc_launch_email = 0xE06C,
    sc_launch_media = 0xE06D,

    sc_pause = 0xE11D45,
    /*
    sc_pause:
    - make: 0xE11D 45 0xE19D C5
    - make in raw input: 0xE11D 0x45
    - break: none
    - No repeat when you hold the key down
    - There are no break so I don't know how the key down/up is expected to work. Raw input sends "keydown" and "keyup" messages, and it appears that the keyup message is sent directly after the keydown message (you can't hold the key down) so depending on when GetMessage or PeekMessage will return messages, you may get both a keydown and keyup message "at the same time". If you use VK messages most of the time you only get keydown messages, but some times you get keyup messages too.
    - when pressed at the same time as one or both control keys, generates a 0xE046 (sc_cancel) and the string for that scancode is "break".
    */
};

How to get scancodes

First let's start by saying that you can use both scancodes and virtual keys at the same time. It's probably a good feature to have both.

When you receive a WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP message the lParam parameter contains the scancode in the 16 - 23 bits and the 24th bit indicates if the scancode is 1 or 2 bytes (extended). If the scancode is extended, you just need to add 0xE000 or 0xE100.

There are a few catches:

  • VK_PAUSE will give you a value of 0x45, but when using raw input it is 0xE11D 0x45 (the value in the list)
  • VK_NUMLOCK will give you a value of 0xE045, but when using raw input it is 0x45 (the value in the list)
  • Some keys change the scancode value if shift, alt, control (left or right) or a combination of those keys are pressed. The only visible change in the WM_KEYDOWN... messages are VK_PAUSE that generates 0x0E46 (sc_cancel, or "Break") when control is pressed and VK_SNAPSHOT (print screen) that will give you a value of 0x54 if alt is pressed. We could use 0x54 and consider it a different key, but windows doesn't have a text name for the scancode value of 0x54 so I chose to force it to the same value as print screen

A few things to know:

  • There are no WM_KEYDOWN for the print screen key and so if you hold the key down it's never repeated
  • There are no break code for "Pause" so I don't know how the key down/up is expected to work (I remember reading somewhere that the key should be consider up as soon as the down message is sent, but I can't find it back). Raw input sends "keydown" and "keyup" message, and it appears that the keyup message is sent directly after the keydown message (you can't hold the key down) so depending on when GetMessage or PeekMessage are called, you may get both a keydown and keyup message "in the same frame". If you use VK messages most of the time you only get keydown messages, but some times you get a keyup message too
  • According to the Microsoft scancode.doc (links above), pause is the only key that never repeats when held down (Print screen reapeats if you use raw input)
  • The right alt key (Alt Gr which stands for Alternate Graphics) sends a alt message and a control message
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_KEYDOWN:
case WM_KEYUP: {

    unsigned int scancode = ( message.lParam >> 16 ) & 0xff;
    unsigned int extended = ( message.lParam >> 24 ) & 0x1;

    if ( extended ) {

        if ( scancode != 0x45 ) {
            scancode |= 0xE000;
        }

    } else {

        if ( scancode == 0x45 ) {
            scancode = 0xE11D45;
        } else if ( scancode == 0x54 ) {
            scancode = 0xE037;
        }
    }

    // Get the key state and store it...
}

Raw input

I don't have much to say about raw input. The only improvements I've found (besides helping me figure out the scancodes value) is that you can get the key down message for "print screen". For "pause" you also get both message but they are back to back so you'll most likely have a "key just pressed" and "key just release" in the same frame (not all the time). And the Right Alt key doesn't send a Control message.

Raw input as the disadvantage that you can't pass them to TranslateMessage to generate WM_CHAR messages, but you can use raw input and WM_KEYDOWN... at the same time so it's not really an issue.

You can generate UTF-16 codepoints with raw input by using the ToUnicode function. Check out his code sample.

To use raw input, you first register a device (the keyboard) and then handle WM_INPUT messages. If you want to prevent WM_KEYDOWN... mesages you can use the RIDEV_NOLEGACY flags when registering the device.

Some keys will send 2 WM_INPUT messages:

  • Pause will send 0xE11D then 0x45, the value I use is 0xE11D45. You'll need to remember reading 0xE11D to be able to differenciate between "Pause" and "Num lock"
  • Print screen will send 0xE02A then 0xE037
  • Insert, Delete, Home, End, Page Up, Page Down, Up, Down, Left, Right: when Num Lock is on or one or both shift keys are pressed, 0xE02A, 0xE0AA, 0xE036, 0xE0B6 can be as postfix or prefix
  • Num pad divide and one or both shift are pressed: 0xE02A, 0xE0AA, 0xE036, 0xE0B6 can be as postfix or prefix

Apart from 0xE11D we can ignore the prefix and postfix codes. I recommend reading scancode.doc Appendix A, note 1, 3, 4, 5 for more information (remember it's a 17 year old document). I noticed that some of the prefix / postfix I received with raw input were break code instead of make code, but since we can ignore them for our purpose, it doesn't matter.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/*
Incomplete list of the usage page and usage ID that you can use for raw inputs:
https://msdn.microsoft.com/windows/hardware/drivers/hid/top-level-collections-opened-by-windows-for-system-use

Flags: https://msdn.microsoft.com/en-us/library/windows/desktop/ms645565(v=vs.85).aspx
RIDEV_NOLEGACY: If set, this prevents any devices specified by usUsagePage or usUsage from generating legacy messages. This is only for the mouse and keyboard.
*/

RAWINPUTDEVICE rawInputDevice = { 0 };
unsigned int rawInputRegistered = 0;

rawInputDevice.usUsagePage = 0x01;
rawInputDevice.usUsage = 0x06;
rawInputDevice.dwFlags = 0;
rawInputDevice.hwndTarget = 0;
rawInputRegistered = RegisterRawInputDevices( &rawInputDevice, 1, sizeof( rawInputDevice ) );

case WM_INPUT: {

    RAWINPUT rawInput;
    unsigned int rawInputSize = sizeof( rawInput );
    GetRawInputData( ( HRAWINPUT ) message.lParam, RID_INPUT, &rawInput, &rawInputSize, sizeof( RAWINPUTHEADER ) );

    if ( rawInput.header.dwType == RIM_TYPEKEYBOARD ) {

        unsigned int pressed = 0;
        unsigned int ignore = 0;
        unsigned int scancode = rawInput.data.keyboard.MakeCode; /* MakeCode is unsigned short. */
        unsigned short flags = rawInput.data.keyboard.Flags;
        assert( scancode <= 0xff );
        /*
        rawInput.data.keyboard.Reserved;
        rawInput.data.keyboard.VKey;
        rawInput.data.keyboard.Message;
        rawInput.data.keyboard.ExtraInformation;
        */

        if ( ( flags & RI_KEY_BREAK ) == 0 ) {
            pressed = 1;
        }

        if ( flags & RI_KEY_E0 ) {
            scancode |= 0xE000;
        } else if ( flags & RI_KEY_E1 ) {
            scancode |= 0xE100;
        }

        /* The pause scancode is in 2 parts: a WM_INPUT with 0xE11D and one WM_INPUT with 0x45. */
        if ( pauseScancodeRead ) {

            if ( scancode == 0x45 ) {
                scancode = 0xE11D45;
            }

            pauseScancodeRead = 0;

        } else if ( scancode == 0xE11D ) {

            pauseScancodeRead = 1;

        } else if ( scancode == 0x54 ) {
            /* Alt + print screen return scancode 0x54 but we want it to return 0xE037 because 0x54 will not return a name for the key. */
            scancode = 0xE037;
        }

        /*
        Some scancodes we can ignore:
        - 0xE11D: first part of the Pause scancode (handled above);
        - 0xE02A: first part of the Print Screen scancode if no Shift, Control or Alt keys are pressed;
        - 0xE02A, 0xE0AA, 0xE036, 0xE0B6: generated in addition of Insert, Delete, Home, End, Page Up, Page Down, Up, Down, Left, Right when num lock is on; or when num lock is off but one or both shift keys are pressed;
        - 0xE02A, 0xE0AA, 0xE036, 0xE0B6: generated in addition of Numpad Divide and one or both Shift keys are pressed;
        - Some of those a break scancode;

        When holding a key down, the pre/postfix (0xE02A) is not repeated.
        */

        if ( scancode == 0xE11D || scancode == 0xE02A || scancode == 0xE0AA || scancode == 0xE0B6 || scancode == 0xE036 ) {
            ignore = 1;
        }

        if ( !ignore ) {
            // Get the key state and store it...
        }
    }

} break;

Scancode name in the language of the user

Windows provides a simple way to get a UTF-16 string that represents the name of the key localized in the keyboard layout language using the scancode. If you switch layout (using Alt + Shift for instance) you'll get the name of the key in the new language.

We just need to transform the scancode into the format it as in lParam from WM_KEYDOWN... messages:

  • bit 16 - 23 contains the first byte of the scancode
  • bit 24 indicates that the scancode is 2 bytes (extended)

The only thing to take care of is that sc_printScreen should be 0x45 and sc_numLock should be 0xE045. Also this function (GetKeyNameText) is the reason to force the 0x54 scancode value we get from alt + print screen to 0xE037 (GetKeyNameText returns no text with 0x54).

Note that there may be issues with this function on certain layout.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
unsigned int getScancodeName( unsigned int scancode, char* buffer, unsigned int bufferLength ) {

    unsigned int result = 0;
    unsigned int extended = scancode & 0xffff00;
    unsigned int lParam = 0;

    if ( extended ) {

        if ( extended == 0xE11D00 ) {
            lParam = 0x45 << 16;
        } else {
            lParam = ( 0x100 | ( scancode & 0xff ) ) << 16;
        }

    } else {

        lParam = scancode << 16;

        if ( scancode == 0x45 ) {
            lParam |= ( 0x1 << 24 );
        }
    }

    result = GetKeyNameText( lParam, buffer, bufferLength );
    return result;
}

Text input

If you need to have user text input, you can pass WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP messages to TranslateMessage. They will be translated in 1 or 2 WM_CHAR messages per character with wParam containing a 16 bit value. Those characters are encode in UTF-16 which, depending on the codepoint encoded, can be 2 bytes (1 WM_CHAR message) or 4 bytes (2 WM_CHAR messages). This is not limited to ASCII, any character should work (e.g. diacritics such as é è ë à ç... ). Decoding the character into a Unicode codepoint takes only a few lines and you can then convert it to any encoding you'd want.

WM_CHAR on msdn

I noticed that the character you'll get from WM_CHAR might not be the same if you created an ANSI window or a UNICODE window. If you create a ANSI window, you'll get some extended ASCII characters (e.g. € is 0x80 instead of 0x20AC) but other characters will be UTF-16 (e.g. I tried an Arabic character and it gave me the right UTF-16 character). If you create a UNICODE window, you'll only get UTF-16 characters. To create an UNICODE window, you need to either #define UNICODE before including window.h or use RegisterClassW when you register your window class and adapt your strings to be wchar_t.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_KEYDOWN:
case WM_KEYUP: {
    ...
    TranslateMessage( &message );
    /* No need for DispatchMessage. */
} break;

case WM_CHAR: {

    /*
    WM_CHAR is generated when WM_KEYDOWN message are passed to TranslateMessage;
    wParam is UTF-16.
    If the codepoint is 4 bytes, there are 2 WM_CHAR message, one with the high surrogate and one with the low surrogate.
    https://msdn.microsoft.com/en-us/library/windows/desktop/ms646276(v=vs.85).aspx
    */

    /* UTF-16 to codepoint */
    unsigned int c = ( unsigned int ) message.wParam;

    unsigned short utf16_hi_surrogate_start = 0xD800;
    unsigned short utf16_lo_surrogate_start = 0xDC00;
    unsigned short utf16_surrogate_end = 0xDFFF;

    static unsigned short highSurrogate = 0;

    if ( c >= utf16_hi_surrogate_start && c < utf16_lo_surrogate_start ) {

        highSurrogate = ( unsigned short ) c;

    } else {

        if ( c >= utf16_lo_surrogate_start && c <= utf16_surrogate_end ) {

            unsigned short lowSurrogate = ( unsigned short ) c;
            c = ( highSurrogate - utf16_hi_surrogate_start ) << 10;
            c |= ( lowSurrogate - utf16_lo_surrogate_start );
            c += 0x10000;

            highSurrogate = 0;
        }

        /* Save the codepoint ( the variable c ) here. */
    }
} break;

Other

If you press ALT and another key in your application, you will probably hear a "beep". That's because Alt is use use for shortcuts in a window "sys menu" (the menu that appears when you click the top left of a window, or press alt + space). To avoid that you need to handle WM_SYSCOMMAND in the windowProc (this message is not sent to the message queue, so you can't get it with GetMessage or PeekMessage).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/* In windowProc */
case WM_SYSCOMMAND: {

    /* Remove beeping sound when ALT + some key is pressed. */
    if ( wParam == SC_KEYMENU ) {
        result = 0;
    } else {
        result = DefWindowProc( window, message, wParam, lParam );
    }
} break;

When your application loses or gains the focus, you should make sure that you properly set the state of the keys. For instance if you Alt + Tab out of you application, it will not get the Alt key up message and if you come back to your application using the mouse, the key down state will persist until you press Alt again. You can use WM_SETFOCUS and WM_KILLFOCUS messages in windowProc (again, those are not posted to the message queue) to know when you get or loose the focus. Be careful when debugging this type of function as breaking into the function will make the GetAsyncKeyState function be dependent of the key presses at the time you will step into it, and may steal the KEYUP messages (they will be sent to the debugger's window). I would recommend to only inspect the result of the function to see if it's working.

EDIT: Cranky pointed out that GetKeyState will not work properly because it depends on the application receiving the key messages, so we need to use GetAsyncKeyState instead.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
void syncKeys( void ) {

    size_t index = 0;

    while ( index < sizeof( scancodeList ) / sizeof( scancodeList[ 0 ] ) ) {

        unsigned int scancode = scancodeList[ index ];
        size_t offset;
        unsigned int vk;
        unsigned short keyState;

        if ( scancode == 0x45 ) {
            scancode = 0xE045;
        } else if ( scancode == 0xE11D45 ) {
            scancode = 0x45;
        }

        offset = getScancodeOffset( scancode );
        vk = MapVirtualKeyEx( scancode, MAPVK_VSC_TO_VK_EX, 0 );
        keyState = GetAsyncKeyState( vk );
        scancodes[ offset ] = ( ( keyState & ( 0x1 << 15 ) ) > 0 ) ? key_pressed : 0;

        index++;
    }
}

void clearKeys( void ) {

    size_t index = 0;

    while ( index < sizeof( scancodes ) / sizeof( scancodes[ 0 ] ) ) {
        scancodes[ index ] = ( scancodes[ index ] & key_pressed ) << 1;
        index++;
    }
}

/* In windowProc */
/* Got the focus */
case WM_SETFOCUS: {
    syncKeys( );
    result = 0;
} break;

/* Lost the focus */
case WM_KILLFOCUS: {
    clearKeys( );
    result = 0;
} break;

That's all I got. I hope it will be useful to someone. If you spot an error (both in content or spelling), have more information or a question, feel free to ask. If you're using a layout that is not QWERTY let me know if it works or not.

The full test program source is below, it outputs the keys you press in a console. You can switch between WM_KEYDOWN... and raw input by clicking anywhere in the window (and the window needs the focus to get inputs), and you can toggle between showing the keypresses or not by right click the window. You can compile it with

call "C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\vcvarsall.bat" x64
cl main.c -nologo -Zi -link user32.lib
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
/*
Display an empty window. The window needs to have the focus to output something.

It shows:
- how to get input from the key position (scancode) instead of the virtual key code (VK_);
- how to use raw input for keyboard inputs;
- how to display the name of a scancode key localized in the user language.
- how to get text input.

You can toggle between virtual key and raw input by left clicking on the window.
Raw input output start with "RW".
Virtual key output start with "VK".
You can toggle between displaying key presses or not by right clicking on the window.

Compile with:

call "C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\vcvarsall.bat" x64
cl main.c -nologo -Zi -link user32.lib
*/

#include <assert.h>
#include <stdio.h>
#include <fcntl.h>
#define UNICODE
#include <windows.h>

unsigned int running = 1;

/*
Store the key as the last state (up = 0, down = 1) of the key in the frame + the number of state transition in the frame.
It allows handling several keypresses of one key in the same frame if you want to.
*/
unsigned char scancodes[ 256 ] = { 0 };
#define key_pressed 0x1
#define key_transitionCountMask 0xfe

/*
The scancode values come from:
- http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/scancode.doc (March 16, 2000).
- http://www.computer-engineering.org/ps2keyboard/scancodes1.html
- using MapVirtualKeyEx( VK_*, MAPVK_VK_TO_VSC_EX, 0 ) with the english us keyboard layout
- reading win32 WM_INPUT keyboard messages.
*/

enum Scancode {

    sc_escape = 0x01,
    sc_1 = 0x02,
    sc_2 = 0x03,
    sc_3 = 0x04,
    sc_4 = 0x05,
    sc_5 = 0x06,
    sc_6 = 0x07,
    sc_7 = 0x08,
    sc_8 = 0x09,
    sc_9 = 0x0A,
    sc_0 = 0x0B,
    sc_minus = 0x0C,
    sc_equals = 0x0D,
    sc_backspace = 0x0E,
    sc_tab = 0x0F,
    sc_q = 0x10,
    sc_w = 0x11,
    sc_e = 0x12,
    sc_r = 0x13,
    sc_t = 0x14,
    sc_y = 0x15,
    sc_u = 0x16,
    sc_i = 0x17,
    sc_o = 0x18,
    sc_p = 0x19,
    sc_bracketLeft = 0x1A,
    sc_bracketRight = 0x1B,
    sc_enter = 0x1C,
    sc_controlLeft = 0x1D,
    sc_a = 0x1E,
    sc_s =0x1F,
    sc_d = 0x20,
    sc_f = 0x21,
    sc_g = 0x22,
    sc_h = 0x23,
    sc_j = 0x24,
    sc_k = 0x25,
    sc_l = 0x26,
    sc_semicolon = 0x27,
    sc_apostrophe = 0x28,
    sc_grave = 0x29,
    sc_shiftLeft = 0x2A,
    sc_backslash = 0x2B,
    sc_z = 0x2C,
    sc_x = 0x2D,
    sc_c = 0x2E,
    sc_v = 0x2F,
    sc_b = 0x30,
    sc_n = 0x31,
    sc_m = 0x32,
    sc_comma = 0x33,
    sc_preiod = 0x34,
    sc_slash = 0x35,
    sc_shiftRight = 0x36,
    sc_numpad_multiply = 0x37,
    sc_altLeft = 0x38,
    sc_space = 0x39,
    sc_capsLock = 0x3A,
    sc_f1 = 0x3B,
    sc_f2 = 0x3C,
    sc_f3 = 0x3D,
    sc_f4 = 0x3E,
    sc_f5 = 0x3F,
    sc_f6 = 0x40,
    sc_f7 = 0x41,
    sc_f8 = 0x42,
    sc_f9 = 0x43,
    sc_f10 = 0x44,
    sc_numLock = 0x45,
    sc_scrollLock = 0x46,
    sc_numpad_7 = 0x47,
    sc_numpad_8 = 0x48,
    sc_numpad_9 = 0x49,
    sc_numpad_minus = 0x4A,
    sc_numpad_4 = 0x4B,
    sc_numpad_5 = 0x4C,
    sc_numpad_6 = 0x4D,
    sc_numpad_plus = 0x4E,
    sc_numpad_1 = 0x4F,
    sc_numpad_2 = 0x50,
    sc_numpad_3 = 0x51,
    sc_numpad_0 = 0x52,
    sc_numpad_period = 0x53,
    sc_alt_printScreen = 0x54, /* Alt + print screen. MapVirtualKeyEx( VK_SNAPSHOT, MAPVK_VK_TO_VSC_EX, 0 ) returns scancode 0x54. */
    sc_bracketAngle = 0x56, /* Key between the left shift and Z. */
    sc_f11 = 0x57,
    sc_f12 = 0x58,
    sc_oem_1 = 0x5a, /* VK_OEM_WSCTRL */
    sc_oem_2 = 0x5b, /* VK_OEM_FINISH */
    sc_oem_3 = 0x5c, /* VK_OEM_JUMP */
    sc_eraseEOF = 0x5d,
    sc_oem_4 = 0x5e, /* VK_OEM_BACKTAB */
    sc_oem_5 = 0x5f, /* VK_OEM_AUTO */
    sc_zoom = 0x62,
    sc_help = 0x63,
    sc_f13 = 0x64,
    sc_f14 = 0x65,
    sc_f15 = 0x66,
    sc_f16 = 0x67,
    sc_f17 = 0x68,
    sc_f18 = 0x69,
    sc_f19 = 0x6a,
    sc_f20 = 0x6b,
    sc_f21 = 0x6c,
    sc_f22 = 0x6d,
    sc_f23 = 0x6e,
    sc_oem_6 = 0x6f, /* VK_OEM_PA3 */
    sc_katakana = 0x70,
    sc_oem_7 = 0x71, /* VK_OEM_RESET */
    sc_f24 = 0x76,
    sc_sbcschar = 0x77,
    sc_convert = 0x79,
    sc_nonconvert = 0x7B, /* VK_OEM_PA1 */

    sc_media_previous = 0xE010,
    sc_media_next = 0xE019,
    sc_numpad_enter = 0xE01C,
    sc_controlRight = 0xE01D,
    sc_volume_mute = 0xE020,
    sc_launch_app2 = 0xE021,
    sc_media_play = 0xE022,
    sc_media_stop = 0xE024,
    sc_volume_down = 0xE02E,
    sc_volume_up = 0xE030,
    sc_browser_home = 0xE032,
    sc_numpad_divide = 0xE035,
    sc_printScreen = 0xE037,
    /*
    sc_printScreen:
    - make: 0xE02A 0xE037
    - break: 0xE0B7 0xE0AA
    - MapVirtualKeyEx( VK_SNAPSHOT, MAPVK_VK_TO_VSC_EX, 0 ) returns scancode 0x54;
    - There is no VK_KEYDOWN with VK_SNAPSHOT.
    */
    sc_altRight = 0xE038,
    sc_cancel = 0xE046, /* CTRL + Pause */
    sc_home = 0xE047,
    sc_arrowUp = 0xE048,
    sc_pageUp = 0xE049,
    sc_arrowLeft = 0xE04B,
    sc_arrowRight = 0xE04D,
    sc_end = 0xE04F,
    sc_arrowDown = 0xE050,
    sc_pageDown = 0xE051,
    sc_insert = 0xE052,
    sc_delete = 0xE053,
    sc_metaLeft = 0xE05B,
    sc_metaRight = 0xE05C,
    sc_application = 0xE05D,
    sc_power = 0xE05E,
    sc_sleep = 0xE05F,
    sc_wake = 0xE063,
    sc_browser_search = 0xE065,
    sc_browser_favorites = 0xE066,
    sc_browser_refresh = 0xE067,
    sc_browser_stop = 0xE068,
    sc_browser_forward = 0xE069,
    sc_browser_back = 0xE06A,
    sc_launch_app1 = 0xE06B,
    sc_launch_email = 0xE06C,
    sc_launch_media = 0xE06D,

    sc_pause = 0xE11D45,
    /*
    sc_pause:
    - make: 0xE11D 45 0xE19D C5
    - make in raw input: 0xE11D 0x45
    - break: none
    - No repeat when you hold the key down
    - There are no break so I don't know how the key down/up is expected to work. Raw input sends "keydown" and "keyup" messages, and it appears that the keyup message is sent directly after the keydown message (you can't hold the key down) so depending on when GetMessage or PeekMessage will return messages, you may get both a keydown and keyup message "at the same time". If you use VK messages most of the time you only get keydown messages, but some times you get keyup messages too.
    - when pressed at the same time as one or both control keys, generates a 0xE046 (sc_cancel) and the string for that scancode is "break".
    */
};

unsigned int scancodeList[ ] = {

    sc_escape,
    sc_1,
    sc_2,
    sc_3,
    sc_4,
    sc_5,
    sc_6,
    sc_7,
    sc_8,
    sc_9,
    sc_0,
    sc_minus,
    sc_equals,
    sc_backspace,
    sc_tab,
    sc_q,
    sc_w,
    sc_e,
    sc_r,
    sc_t,
    sc_y,
    sc_u,
    sc_i,
    sc_o,
    sc_p,
    sc_bracketLeft,
    sc_bracketRight,
    sc_enter,
    sc_controlLeft,
    sc_a,
    sc_s,
    sc_d,
    sc_f,
    sc_g,
    sc_h,
    sc_j,
    sc_k,
    sc_l,
    sc_semicolon,
    sc_apostrophe,
    sc_grave,
    sc_shiftLeft,
    sc_backslash,
    sc_z,
    sc_x,
    sc_c,
    sc_v,
    sc_b,
    sc_n,
    sc_m,
    sc_comma,
    sc_preiod,
    sc_slash,
    sc_shiftRight,
    sc_numpad_multiply,
    sc_altLeft,
    sc_space,
    sc_capsLock,
    sc_f1,
    sc_f2,
    sc_f3,
    sc_f4,
    sc_f5,
    sc_f6,
    sc_f7,
    sc_f8,
    sc_f9,
    sc_f10,
    sc_numLock,
    sc_scrollLock,
    sc_numpad_7,
    sc_numpad_8,
    sc_numpad_9,
    sc_numpad_minus,
    sc_numpad_4,
    sc_numpad_5,
    sc_numpad_6,
    sc_numpad_plus,
    sc_numpad_1,
    sc_numpad_2,
    sc_numpad_3,
    sc_numpad_0,
    sc_numpad_period,
    sc_alt_printScreen,
    sc_bracketAngle,
    sc_f11,
    sc_f12,
    sc_oem_1,
    sc_oem_2,
    sc_oem_3,
    sc_eraseEOF,
    sc_oem_4,
    sc_oem_5,
    sc_zoom,
    sc_help,
    sc_f13,
    sc_f14,
    sc_f15,
    sc_f16,
    sc_f17,
    sc_f18,
    sc_f19,
    sc_f20,
    sc_f21,
    sc_f22,
    sc_f23,
    sc_oem_6,
    sc_katakana,
    sc_oem_7,
    sc_f24,
    sc_sbcschar,
    sc_convert,
    sc_nonconvert,

    sc_media_previous,
    sc_media_next,
    sc_numpad_enter,
    sc_controlRight,
    sc_volume_mute,
    sc_launch_app2,
    sc_media_play,
    sc_media_stop,
    sc_volume_down,
    sc_volume_up,
    sc_browser_home,
    sc_numpad_divide,
    sc_printScreen,
    sc_altRight,
    sc_cancel,
    sc_home,
    sc_arrowUp,
    sc_pageUp,
    sc_arrowLeft,
    sc_arrowRight,
    sc_end,
    sc_arrowDown,
    sc_pageDown,
    sc_insert,
    sc_delete,
    sc_metaLeft,
    sc_metaRight,
    sc_application,
    sc_power,
    sc_sleep,
    sc_wake,
    sc_browser_search,
    sc_browser_favorites,
    sc_browser_refresh,
    sc_browser_stop,
    sc_browser_forward,
    sc_browser_back,
    sc_launch_app1,
    sc_launch_email,
    sc_launch_media,

    sc_pause
};

size_t getScancodeOffset( unsigned int scancode ) {

    size_t result = scancode;
    size_t group_0_end = sc_nonconvert;
    size_t group_1_start = sc_media_previous;
    size_t group_1_end = sc_launch_media;
    size_t group_2_start = sc_pause;

    if ( scancode >= group_2_start ) {
        result = group_0_end + 1 + ( group_1_end - group_1_start ) + 1 + ( scancode - group_2_start );
    } else if ( scancode >= group_1_start ) {
        result = group_0_end + 1 + ( scancode - group_1_start );
    }

    assert( result <= 0xff );

    return result;
}

void syncKeys( void ) {

    size_t index = 0;

    while ( index < sizeof( scancodeList ) / sizeof( scancodeList[ 0 ] ) ) {

        unsigned int scancode = scancodeList[ index ];
        size_t offset;
        unsigned int vk;
        unsigned short keyState;

        if ( scancode == 0x45 ) {
            scancode = 0xE045;
        } else if ( scancode == 0xE11D45 ) {
            scancode = 0x45;
        }

        offset = getScancodeOffset( scancode );
        vk = MapVirtualKeyEx( scancode, MAPVK_VSC_TO_VK_EX, 0 );
        keyState = GetAsyncKeyState( vk );
        scancodes[ offset ] = ( ( keyState & ( 0x1 << 15 ) ) > 0 ) ? key_pressed : 0;

        index++;
    }
}

void clearKeys( void ) {

    size_t index = 0;

    while ( index < sizeof( scancodes ) / sizeof( scancodes[ 0 ] ) ) {
        scancodes[ index ] = ( scancodes[ index ] & key_pressed ) << 1;
        index++;
    }
}

LRESULT CALLBACK windowProc( HWND window, UINT message, WPARAM wParam, LPARAM lParam ) {

    LRESULT result = 0;

    switch ( message ) {

        case WM_CLOSE:
        case WM_QUIT:
        case WM_DESTROY: {
            running = 0;
        } break;

        /* Doesn't go through peekmessage. */
        case WM_SYSCOMMAND: {

            /* Remove beeping sound when ALT + some key is pressed. */
            if ( wParam == SC_KEYMENU ) {
                result = 0;
            } else {
                result = DefWindowProc( window, message, wParam, lParam );
            }
        } break;

        case WM_SETFOCUS: {
            syncKeys( );
            result = 0;
        } break;

        case WM_KILLFOCUS: {
            clearKeys( );
            result = 0;
        } break;
        /* End of no peekmessage */

        default : {
            result = DefWindowProc( window, message, wParam, lParam );
        } break;
    }

    return result;
}

unsigned int getScancodeName( unsigned int scancode, wchar_t* buffer, unsigned int bufferLength ) {

    unsigned int result = 0;
    unsigned int extended = scancode & 0xffff00;
    unsigned int lParam = 0;

    if ( extended ) {

        if ( extended == 0xE11D00 ) {
            lParam = 0x45 << 16;
        } else {
            lParam = ( 0x100 | ( scancode & 0xff ) ) << 16;
        }

    } else {

        lParam = scancode << 16;

        if ( scancode == 0x45 ) {
            lParam |= ( 0x1 << 24 );
        }
    }

    result = GetKeyNameText( lParam, buffer, bufferLength );
    return result;
}

unsigned int isPressed( unsigned int scancode ) {
    size_t offset = getScancodeOffset( scancode );
    unsigned int result = scancodes[ offset ] & key_pressed;
    return result;
}

unsigned int justPressed( unsigned int scancode ) {
    size_t offset = getScancodeOffset( scancode );
    unsigned int result = ( scancodes[ offset ] & key_pressed ) && ( scancodes[ offset ] & key_transitionCountMask );
    return result;
}

unsigned int justReleased( unsigned int scancode ) {
    size_t offset = getScancodeOffset( scancode );
    unsigned int result = !( scancodes[ offset ] & key_pressed ) && ( scancodes[ offset ] & key_transitionCountMask );
    return result;
}

int main( void ) {

    WNDCLASS windowClass = { 0 };
    HWND window = 0;
    RAWINPUTDEVICE rawInputDevice = { 0 };
    unsigned int rawInputRegistered = 0;
    unsigned int pauseScancodeRead = 0;
    unsigned short textInputBuffer[ 64 ] = { 0 };
    size_t textInputCount = 0;
    unsigned int useRawInput = 0;
    unsigned int displayKeys = 1;
    unsigned short highSurrogate = 0;

    /* Set stdout as UTF-16. */
    _setmode(_fileno(stdout), _O_U16TEXT);

    windowClass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    windowClass.lpfnWndProc = windowProc;
    windowClass.lpszClassName = L"window_scancodes";
    windowClass.hCursor = LoadCursor( 0, IDC_ARROW );
    windowClass.cbWndExtra = 0;

    RegisterClass( &windowClass );
    window = CreateWindowEx( 0, L"window_scancodes", L"Scancodes", WS_OVERLAPPEDWINDOW, 0, 0, 300, 200, 0, 0, 0, 0 );
    ShowWindow( window, SW_SHOW );

    /*
    Incomplete list of the usage page and usage ID that you can use for raw inputs:
    https://msdn.microsoft.com/windows/hardware/drivers/hid/top-level-collections-opened-by-windows-for-system-use

    Flags: https://msdn.microsoft.com/en-us/library/windows/desktop/ms645565(v=vs.85).aspx
    RIDEV_NOLEGACY: If set, this prevents any devices specified by usUsagePage or usUsage from generating legacy messages. This is only for the mouse and keyboard.
    */

    rawInputDevice.usUsagePage = 0x01;
    rawInputDevice.usUsage = 0x06;
    rawInputDevice.dwFlags = 0;
    rawInputDevice.hwndTarget = 0;
    rawInputRegistered = RegisterRawInputDevices( &rawInputDevice, 1, sizeof( rawInputDevice ) );

    while ( running ) {

        MSG message;

        /* When a key is held down you only get keydown message at intervals. */
        size_t scancodeIndex = 0;

        while ( scancodeIndex < sizeof( scancodes ) / sizeof( scancodes[ 0 ] ) ) {
            scancodes[ scancodeIndex ] &= key_pressed;
            scancodeIndex++;
        }

        textInputCount = 0;

        while ( PeekMessage( &message, 0, 0, 0, PM_REMOVE ) ) {

            switch ( message.message ) {

                case WM_LBUTTONDOWN: {
                    useRawInput = !useRawInput;
                } break;

                case WM_RBUTTONDOWN: {
                    displayKeys = !displayKeys;
                } break;

                case WM_SYSKEYDOWN:
                case WM_SYSKEYUP:
                case WM_KEYDOWN:
                case WM_KEYUP: {

                    unsigned int scancode = ( message.lParam >> 16 ) & 0xff;
                    unsigned int extended = ( message.lParam >> 24 ) & 0x1;
                    unsigned char pressed = ( ( ~message.lParam ) >> 31 ) & key_pressed;
                    unsigned char transition = 0;
                    unsigned char value = 0;
                    size_t offset = 0;

                    /*
                    - sc_pause is 0xE11D45 but the extended flag is not set in lParam so the lParam scancode is 0x45;
                    - sc_numLock is 0x45 but the extended flag is set in lParam so the lParam scancode is 0xE045;
                       Force them to the "right" value.

                    Alt + print screen returns scancode 0x54. Force it to return 0xE037 because 0x54 doesn't have a name for the key.
                    */

                    if ( extended ) {

                        if ( scancode != 0x45 ) {
                            scancode |= 0xE000;
                        }

                    } else {

                        if ( scancode == 0x45 ) {
                            scancode = 0xE11D45;
                        } else if ( scancode == 0x54 ) {
                            scancode = 0xE037;
                        }
                    }

                    offset = getScancodeOffset( scancode );

                    if ( scancode == 0xE037 ) {
                        transition = 1; /* Force transition because print screen only sends key up messages.*/
                    } else {
                        transition = ( scancodes[ offset ] & key_pressed ) != pressed;
                    }

                    value = scancodes[ offset ] & key_transitionCountMask;
                    value += ( transition << 1 );
                    value |= pressed;

                    if ( !useRawInput ) {
                        scancodes[ offset ] = value;
                    }

                    TranslateMessage( &message );

                } break;

                case WM_INPUT: {

                    RAWINPUT rawInput;
                    unsigned int rawInputSize = sizeof( rawInput );
                    GetRawInputData( ( HRAWINPUT ) message.lParam, RID_INPUT, &rawInput, &rawInputSize, sizeof( RAWINPUTHEADER ) );

                    if ( rawInput.header.dwType == RIM_TYPEKEYBOARD ) {

                        unsigned int pressed = 0;
                        unsigned int ignore = 0;
                        unsigned int scancode = rawInput.data.keyboard.MakeCode; /* MakeCode is unsigned short. */
                        unsigned short flags = rawInput.data.keyboard.Flags;
                        assert( scancode <= 0xff );
                        /*
                        rawInput.data.keyboard.Reserved;
                        rawInput.data.keyboard.VKey;
                        rawInput.data.keyboard.Message;
                        rawInput.data.keyboard.ExtraInformation;
                        */

                        if ( ( flags & RI_KEY_BREAK ) == 0 ) {
                            pressed = 1;
                        }

                        if ( flags & RI_KEY_E0 ) {
                            scancode |= 0xE000;
                        } else if ( flags & RI_KEY_E1 ) {
                            scancode |= 0xE100;
                        }

                        /* The pause scancode is in 2 parts: a WM_INPUT with 0xE11D and one WM_INPUT with 0x45. */
                        if ( pauseScancodeRead ) {

                            if ( scancode == 0x45 ) {
                                scancode = 0xE11D45;
                            }

                            pauseScancodeRead = 0;

                        } else if ( scancode == 0xE11D ) {

                            pauseScancodeRead = 1;

                        } else if ( scancode == 0x54 ) {
                            /* Alt + print screen return scancode 0x54 but we want it to return 0xE037 because 0x54 will not return a name for the key. */
                            scancode = 0xE037;
                        }

                        /*
                        Some scancodes we can ignore:
                        - 0xE11D: first part of the Pause scancode (handled above);
                        - 0xE02A: first part of the Print Screen scancode if no Shift, Control or Alt keys are pressed;
                        - 0xE02A, 0xE0AA, 0xE036, 0xE0B6: generated in addition of Insert, Delete, Home, End, Page Up, Page Down, Up, Down, Left, Right when num lock is on; or when num lock is off but one or both shift keys are pressed;
                        - 0xE02A, 0xE0AA, 0xE036, 0xE0B6: generated in addition of Numpad Divide and one or both Shift keys are pressed;
                        - Some of those a break scancode;

                        When holding a key down, the pre/postfix (0xE02A) is not repeated.
                        */

                        if ( scancode == 0xE11D || scancode == 0xE02A || scancode == 0xE0AA || scancode == 0xE0B6 || scancode == 0xE036 ) {
                            ignore = 1;
                        }

                        if ( !ignore && useRawInput ) {

                            size_t offset = getScancodeOffset( scancode );
                            unsigned char transition = ( scancodes[ offset ] & key_pressed ) != pressed;
                            unsigned char value = scancodes[ offset ] & key_transitionCountMask;
                            value += ( transition << 1 );
                            value |= pressed;
                            scancodes[ offset ] = value;
                        }
                    }
                } break;

                case WM_CHAR: {

                    /*
                    WM_CHAR is generated when WM_KEYDOWN message are passed to TranslateMessage;
                    wParam is UTF-16.
                    If the codepoint is 4 bytes, there are 2 WM_CHAR message, one with the high surrogate and one with the low surrogate.
                    https://msdn.microsoft.com/en-us/library/windows/desktop/ms646276(v=vs.85).aspx
                    */

                    /* UTF-16 to codepoint */
                    unsigned int c = ( unsigned int ) message.wParam;

                    unsigned short utf16_hi_surrogate_start = 0xD800;
                    unsigned short utf16_lo_surrogate_start = 0xDC00;
                    unsigned short utf16_surrogate_end = 0xDFFF;

                    if ( c >= utf16_hi_surrogate_start && c < utf16_lo_surrogate_start ) {

                        highSurrogate = ( unsigned short ) c;

                    } else {

                        if ( c >= utf16_lo_surrogate_start && c <= utf16_surrogate_end ) {

                            unsigned short lowSurrogate = ( unsigned short ) c;
                            c = ( highSurrogate - utf16_hi_surrogate_start ) << 10;
                            c |= ( lowSurrogate - utf16_lo_surrogate_start );
                            c += 0x10000;

                            highSurrogate = 0;
                        }

                        /* Save the codepoint ( the variable c ) here. */
                    }

                    /* In this example I only need the UTF-16 characters. */

                    if ( textInputCount < ( sizeof( textInputBuffer ) / sizeof( textInputBuffer[ 0 ] ) ) - 1 ) {
                        textInputBuffer[ textInputCount++ ] = ( unsigned short ) message.wParam;
                        textInputBuffer[ textInputCount ] = 0;
                    }

                } break;

                default: {

                    TranslateMessage( &message );
                    DispatchMessage( &message );
                } break;
            }
        }

        if ( displayKeys ) {

            size_t index = 0;

            while ( index < sizeof( scancodeList ) / sizeof( scancodeList[ 0 ] ) ) {

                /* The name is UTF-16. */
                wchar_t textBuffer[ 64 ] = L"  : ";
                unsigned int length = 0;
                unsigned int sc = scancodeList[ index ];

                if ( useRawInput ) {
                    textBuffer[ 0 ] = 'R';
                    textBuffer[ 1 ] = 'W';
                } else {
                    textBuffer[ 0 ] = 'V';
                    textBuffer[ 1 ] = 'K';
                }

                length = getScancodeName( sc, textBuffer + 4, sizeof( textBuffer ) );

                if ( justPressed( sc ) ) {
                    wprintf( textBuffer );
                    wprintf( L" just pressed\n" );
                }

                if ( isPressed( sc ) ) {
                    wprintf( textBuffer );
                    wprintf( L" pressed\n" );
                }

                if ( justReleased( sc ) ) {
                    wprintf( textBuffer );
                    wprintf( L" just released\n\n" );
                }

                index++;
            }
        }

        if ( textInputCount > 0 ) {

            wprintf( L"Text input: " );
            wprintf( ( wchar_t* ) textInputBuffer );
            wprintf( L"\n" );
        }

        /* "Simulate" vsync */
        Sleep( 16 );
    }

    return 0;
}

Edited by Simon Anciaux on Reason: Initial post