Handmade Network»Forums
Simon Anciaux
1337 posts
Keyboard inputs - scancodes, raw input, text input, key names
Edited by Simon Anciaux on Reason: Typos, improved the post content based on feedback
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 keboard 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 combinaison 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 chossed 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.

EDIT: Seledorn pointed out that 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
Appart from 0xE11D we can ignore the prefix and postfix codes. I recommend reading scancode.doc Appendix A, note 1, 3, 4, 5 for more informations (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 shoud 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.

EDIT: Thanks to mmozeiko's feedback I've updated this section.

 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 informations 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

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


Bye

  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;
}
Mārtiņš Možeiko
2559 posts / 2 projects
Keyboard inputs - scancodes, raw input, text input, key names
Btw there is WM_UNICHAR message to get full unicode codepoint.
My understanding is that Windows sends WM_UNICHAR first, which produces WM_CHAR message in DefWindowProc, if not handled by user.
Alex Baines
44 posts / 2 projects
Keyboard inputs - scancodes, raw input, text input, key names
Nice, this seems like a very comprehensive post!

I must admit I haven't read it all yet, but a while back I wrote a blog on Linux keycode handling with Xlib & Xkb that might be useful to put in this thread: https://abaines.me.uk/updates/linux-x11-keys

There's also this example of Xlib text input I wrote: https://gist.github.com/baines/5a49f1334281b2685af5dcae81a6fa8a

Hopefully those can help anyone wanting similar info for Linux.
Simon Anciaux
1337 posts
Keyboard inputs - scancodes, raw input, text input, key names
Edited by Simon Anciaux on Reason: Links to the doc, Typo
@mmozeiko:
I tried to use WM_UNICHAR, but I never got one of those messages (I tried with ANSI and UNICODE window).
The WM_UNICHAR doc says

The WM_UNICHAR message can be used by an application to post input to other windows.
But the WM_CHAR doc says

The WM_UNICHAR message is the same as WM_CHAR, except it uses UTF-32. It is designed to send or post Unicode characters to ANSI windows, and it can handle Unicode Supplementary Plane characters.

So I'm a bit confused. If you know a way to make it work, I'm all ears.

And I added a note about UNICODE and ANSI window in the text input section.

Not directly related but you may know the answer: the documentation sometimes says to return true or false if you process a message (like for WM_UNICHAR). I suppose this is intended for the windowProc. If you process messages in a PeekMessage loop, do you need to do something ?

@insofaras: Thanks, I'll have a look when I start working on Linux again.
Allen Webster
476 posts / 6 projects
Heyo
Keyboard inputs - scancodes, raw input, text input, key names
Awww yeah, this is going to be so useful. I haven't had time to read very far yet but thanks for posting this!
Mārtiņš Možeiko
2559 posts / 2 projects
Keyboard inputs - scancodes, raw input, text input, key names
Edited by Mārtiņš Možeiko on
mrmixer: I believe WM_UNICHAR is sent only by IME input methods. Default input doesn't produce such message. It is not necessary to support WM_UNICHAR, because DefWindowProc will convert one WM_UNICHAR to one or two WM_CHAR messages which you can use to reconstruct UTF-32 value (actual unicode codepoint that was passed to WM_UNICAR). But you can simply avoid this overhead if you want.

As for processing these outside of WindowProc - returning TRUE or FALSE just make Windows to do default behavior. For example, for WM_UNICHAR it is to post one or two WM_CHAR messages. If you don't need that because you are processing these messages yourself outside WindowProc, then this completely doesn't matter to you.

I looked closer to how you process WM_CHAR in example code and I believe it is very wrong. wParam doesn't contain two ushorts joined in one 32-bit value. It is always one UTF-16 value. That means 16-bits. If Windows wants to send you unicode codepoint with value >0xFFFF, then it sends two UTF-16 WM_CHAR messages. Each with one part of UTF-16 surrogate pair. So your first WM_CHAR needs to remember it, and second one construct full unicode codepoint. For example, this is mentioned here: http://www.catch22.net/tuts/unicode-text-editing
Unicode values outside of the BMP (i.e. > 0xFFFF in value) will be sent as two separate messages, one for each surrogate character.
This is also seen in SFML source code: https://github.com/SFML/SFML/blob...ow/Win32/WindowImplWin32.cpp#L655
And in Scintilla source: https://sourceforge.net/u/sammy/s...tree/win32/ScintillaWin.cxx#l1447
Simon Anciaux
1337 posts
Keyboard inputs - scancodes, raw input, text input, key names
Edited by Simon Anciaux on
Mr4thDimention: I hope it'll be useful to you.

mmozeiko: Thanks, for pointing it out. I updated the post and code. I also switched the full example to a unicode window.
I can't get a second WM_CHAR message for 4 bytes codepoints. I used this method to send unicode characters. With a Chinese character (𤭢: 24B62), I only get one WM_CHAR message with 4B62. Notepad gets the character so I assume the input method works.
Mārtiņš Možeiko
2559 posts / 2 projects
Keyboard inputs - scancodes, raw input, text input, key names
Edited by Mārtiņš Možeiko on
I tried using Window 10 on screen keyboard to enter emojis: http://www.makeuseof.com/tag/find-emojis-windows-10/
Most of them are sending surrogate pairs: https://en.wikipedia.org/wiki/Emoji#Unicode_blocks
Here's my test code: https://gist.github.com/mmozeiko/17b642fbd9d5064a9e59094f08fd6dd9
Simon Anciaux
1337 posts
Keyboard inputs - scancodes, raw input, text input, key names
I'm on Windows 7 so I don't have emojis on the virtual keyboard but I tried on a Windows 10 machine and it works. Thanks again!
Daniel Hesslow
7 posts
Keyboard inputs - scancodes, raw input, text input, key names
Edited by Daniel Hesslow on
Great post!

You can actually generate the unicode codepoint inside of the rawinput (and therefor only process raw input messages), however that means that you have to keep track of all keystates, as far as I know anyway, which means that you'll have to track keystate in the background. Anyway I copied out the relevant parts form a program of mine. VK_LEFT_SHIFT etc is just defined to be one of the unused VK codes.

Edge cases outlined in the OP are not handled correctly.

 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
case WM_INPUT:
		{
			//this documentation is retarded, msdn implies that the size can be any size whatsoever, 
			//however it will be *one* of the *possible* unions, so unless we like to dynamically allocate to save like two bytes.
			//we can just point to our own rawinput structure.

			RAWINPUT raw;
			UINT dw_size = sizeof(RAWINPUT);
			int ret = GetRawInputData((HRAWINPUT)lParam, RID_INPUT, &raw, &dw_size,sizeof(RAWINPUTHEADER));
			assert(ret > 0);
			RAWKEYBOARD kb = raw.data.keyboard;
			static unsigned char keyState[256] = {};
			
			{	// update our keystate
				// if you don't care about getting the unicode translated thing later you might choose to store it differnetly.
				bool e0 = kb.Flags & RI_KEY_E0;
				bool e1 = kb.Flags & RI_KEY_E1;

				// these are unasigned but not reserved as of now.
				// this is bad but, you know, we'll fix it if it ever breaks.
				#define VK_LRETURN         0x9E
				#define VK_RRETURN         0x9F

				#define UPDATE_KEY_STATE(key) do{keyState[key] = (kb.Flags & 1) ? 0 : 0xff;}while(0)
				// note: we set all bits in the byte if the key is down. 
				// This is becasue windows expects it to be in the high_order_bit (when using it for converting to unicode for example)
				// and I like it to be in the low_order_bit,  
				if (kb.VKey == VK_CONTROL)
				{
					if (e0)	UPDATE_KEY_STATE(VK_RCONTROL);
					else	UPDATE_KEY_STATE(VK_LCONTROL);
					keyState[VK_CONTROL] = keyState[VK_RCONTROL] | keyState[VK_LCONTROL];
				}
				else if (kb.VKey == VK_SHIFT)
				{
					// because why should any api be consistant lol
					// (because we get different scancodes for l/r-shift but not for l/r ctrl etc... but still)
					UPDATE_KEY_STATE(MapVirtualKey(kb.MakeCode, MAPVK_VSC_TO_VK_EX));
					keyState[VK_SHIFT] = keyState[VK_LSHIFT] | keyState[VK_RSHIFT];
				}
				else if (kb.VKey == VK_MENU)
				{
					if (e0)	UPDATE_KEY_STATE(VK_LMENU);
					else	UPDATE_KEY_STATE(VK_RMENU);
					keyState[VK_MENU] = keyState[VK_RMENU] | keyState[VK_LMENU];
				}
				else if (kb.VKey == VK_RETURN)
				{
					if (e0) UPDATE_KEY_STATE(VK_RRETURN);
					else	UPDATE_KEY_STATE(VK_LRETURN);
					keyState[VK_RETURN] = keyState[VK_RRETURN] | keyState[VK_LRETURN];
				}
				else
				{
					UPDATE_KEY_STATE(kb.VKey);
				}
				#undef UPDATE_KEY_STATE
			}

			bool window_is_ontop = !wParam;
			if (raw.header.dwType == RIM_TYPEKEYBOARD && window_is_ontop)
			{
				if(!(kb.Flags & 0x1)) //down
				{
					char utf8_buffer[32];
					char *buff=0;
					int utf8_len = 0;
					
					// get unicode.
					wchar_t utf16_buffer[16];
                                        //simulating altgr, assumes all leftalts is algr
                                        // which seem to work since ctrl is ignored on US versions of ToUnicode. Bad way of doing it, Not sure how to detect if left alt is altgr though.
					unsigned char ctrl = keyState[VK_CONTROL];
					keyState[VK_CONTROL]|= keyState[VK_RMENU];
					int utf16_len = ToUnicode(kb.VKey, kb.MakeCode, keyState, utf16_buffer, ARRAY_LENGTH(utf16_buffer), 0);
					keyState[VK_CONTROL] = ctrl;

					
					// get some values
					bool shift_left    = keyState[VK_LSHIFT];
					bool shift_right   = keyState[VK_RSHIFT];
					bool control_right = keyState[VK_RCONTROL];
					bool control_left  = keyState[VK_LCONTROL];
					bool alt_right     = keyState[VK_RMENU];
					bool alt_left      = keyState[VK_LMENU];
				}
			}
			break;
Simon Anciaux
1337 posts
Keyboard inputs - scancodes, raw input, text input, key names
Edited by Simon Anciaux on Reason: Typo
Thanks, I added a note in the original post.
Tim Kane
23 posts
Twitter: Tim_Rex_
Keyboard inputs - scancodes, raw input, text input, key names
Edited by Tim Kane on
This thread was super helpful, thanks for the detailed explanation.

For anyone that's interested, the Chromium project have a really handy set of virtual keycode --> HID scancode lookups for various platforms (win/mac/android/linux).

https://chromium.googlesource.com...es/dom/keycode_converter_data.inc
Simon Anciaux
1337 posts
Keyboard inputs - scancodes, raw input, text input, key names
Thanks, this link in the document is interesting.
TM
13 posts
Keyboard inputs - scancodes, raw input, text input, key names
I think the sync code won't work as expected. I remember doing something similar for resetting keys states and it didn't work. The documentation says this about GetKeyState:
The key status returned from this function changes as a thread reads key messages from its message queue. The status does not reflect the interrupt-level state associated with the hardware. Use the GetAsyncKeyState function to retrieve that information.
So if your app never receives a WM_KEYUP message for a specific key, GetKeyState will not help you. I remember GetKeyState being buggy, it actually works (sometimes) if you call it twice in a row which means it does internal bookkeeping as it is called. I wouldn't use it, it really is buggy.

Just use GetAsyncKeyState instead to reset, it doesn't have these issues.

About GetKeyNameText to get localized key names: I wouldn't rely on it, the strings come from the keyboard layout "driver" and are notoriously bad. Some keynames in some languages are straight out missing or have weird names.
I think this is why a lot of apps assume US keyboard layouts, not because they didn't think of using GetKeyNameText.
Just use plain english or combine GetKeyNameText with plain english.
Basically my advice is to not use GetKeyNameText just by itself.
Clive Galway
1 posts
Keyboard inputs - scancodes, raw input, text input, key names
Great article, thanks ever so much for this.
I am working on an open source remapping application and plan to implement keyboard / mouse support using the Interception driver to provide device-specific, blockable remappings, and was totally baffled as to how to get key name from SC + extended flag (Which is all you get in interception).
My other searches were indicating I wanted MapVirtualKeyEx, but I just could not get this working with left / right variants of keys (eg Alt vs AltGr). This post made it all very simple.

For future reference, here is a code snippet doing the same thing in C# with Interception.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
Stroke stroke = new Stroke();
while (Receive(deviceContext, device, ref stroke, 1) > 0)
{
    if (IsKeyboard(device) == 1)
    {
        uint lParam = 0;
        if (stroke.key.state > 1)
        {
            lParam = (0x100 | ((uint)stroke.key.code & 0xff)) << 16;
        } 
        else
        {
            lParam = (uint)stroke.key.code << 16;
        }
        StringBuilder sb = new StringBuilder(260);
        GetKeyNameTextW(lParam, sb, 260);
        var keyName = sb.ToString();
        Console.WriteLine(String.Format("Key Event ({0}): SC = {1}, Name = {2}, State = {3}"
            , GetHardwareStr(deviceContext, device), stroke.key.code, keyName, stroke.key.state));