Simon Anciaux
1274 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.

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 #include #include #define UNICODE #include 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
2452 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
1274 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
474 posts / 5 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
2452 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
1274 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
2452 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
1274 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
1274 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
Keyboard inputs - scancodes, raw input, text input, key names
Edited by Tim Kane on

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).

Simon Anciaux
1274 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));