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

So the following will discuss

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

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

# Scancodes

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

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

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

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

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 /* The scancode values come from: - http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/scancode.doc (March 16, 2000). - http://www.computer-engineering.org/ps2keyboard/scancodes1.html - using MapVirtualKeyEx( VK_*, MAPVK_VK_TO_VSC_EX, 0 ) with the english us keyboard layout - reading win32 WM_INPUT keyboard messages. */ enum Scancode { sc_escape = 0x01, sc_1 = 0x02, sc_2 = 0x03, sc_3 = 0x04, sc_4 = 0x05, sc_5 = 0x06, sc_6 = 0x07, sc_7 = 0x08, sc_8 = 0x09, sc_9 = 0x0A, sc_0 = 0x0B, sc_minus = 0x0C, sc_equals = 0x0D, sc_backspace = 0x0E, sc_tab = 0x0F, sc_q = 0x10, sc_w = 0x11, sc_e = 0x12, sc_r = 0x13, sc_t = 0x14, sc_y = 0x15, sc_u = 0x16, sc_i = 0x17, sc_o = 0x18, sc_p = 0x19, sc_bracketLeft = 0x1A, sc_bracketRight = 0x1B, sc_enter = 0x1C, sc_controlLeft = 0x1D, sc_a = 0x1E, sc_s =0x1F, sc_d = 0x20, sc_f = 0x21, sc_g = 0x22, sc_h = 0x23, sc_j = 0x24, sc_k = 0x25, sc_l = 0x26, sc_semicolon = 0x27, sc_apostrophe = 0x28, sc_grave = 0x29, sc_shiftLeft = 0x2A, sc_backslash = 0x2B, sc_z = 0x2C, sc_x = 0x2D, sc_c = 0x2E, sc_v = 0x2F, sc_b = 0x30, sc_n = 0x31, sc_m = 0x32, sc_comma = 0x33, sc_preiod = 0x34, sc_slash = 0x35, sc_shiftRight = 0x36, sc_numpad_multiply = 0x37, sc_altLeft = 0x38, sc_space = 0x39, sc_capsLock = 0x3A, sc_f1 = 0x3B, sc_f2 = 0x3C, sc_f3 = 0x3D, sc_f4 = 0x3E, sc_f5 = 0x3F, sc_f6 = 0x40, sc_f7 = 0x41, sc_f8 = 0x42, sc_f9 = 0x43, sc_f10 = 0x44, sc_numLock = 0x45, sc_scrollLock = 0x46, sc_numpad_7 = 0x47, sc_numpad_8 = 0x48, sc_numpad_9 = 0x49, sc_numpad_minus = 0x4A, sc_numpad_4 = 0x4B, sc_numpad_5 = 0x4C, sc_numpad_6 = 0x4D, sc_numpad_plus = 0x4E, sc_numpad_1 = 0x4F, sc_numpad_2 = 0x50, sc_numpad_3 = 0x51, sc_numpad_0 = 0x52, sc_numpad_period = 0x53, sc_alt_printScreen = 0x54, /* Alt + print screen. MapVirtualKeyEx( VK_SNAPSHOT, MAPVK_VK_TO_VSC_EX, 0 ) returns scancode 0x54. */ sc_bracketAngle = 0x56, /* Key between the left shift and Z. */ sc_f11 = 0x57, sc_f12 = 0x58, sc_oem_1 = 0x5a, /* VK_OEM_WSCTRL */ sc_oem_2 = 0x5b, /* VK_OEM_FINISH */ sc_oem_3 = 0x5c, /* VK_OEM_JUMP */ sc_eraseEOF = 0x5d, sc_oem_4 = 0x5e, /* VK_OEM_BACKTAB */ sc_oem_5 = 0x5f, /* VK_OEM_AUTO */ sc_zoom = 0x62, sc_help = 0x63, sc_f13 = 0x64, sc_f14 = 0x65, sc_f15 = 0x66, sc_f16 = 0x67, sc_f17 = 0x68, sc_f18 = 0x69, sc_f19 = 0x6a, sc_f20 = 0x6b, sc_f21 = 0x6c, sc_f22 = 0x6d, sc_f23 = 0x6e, sc_oem_6 = 0x6f, /* VK_OEM_PA3 */ sc_katakana = 0x70, sc_oem_7 = 0x71, /* VK_OEM_RESET */ sc_f24 = 0x76, sc_sbcschar = 0x77, sc_convert = 0x79, sc_nonconvert = 0x7B, /* VK_OEM_PA1 */ sc_media_previous = 0xE010, sc_media_next = 0xE019, sc_numpad_enter = 0xE01C, sc_controlRight = 0xE01D, sc_volume_mute = 0xE020, sc_launch_app2 = 0xE021, sc_media_play = 0xE022, sc_media_stop = 0xE024, sc_volume_down = 0xE02E, sc_volume_up = 0xE030, sc_browser_home = 0xE032, sc_numpad_divide = 0xE035, sc_printScreen = 0xE037, /* sc_printScreen: - make: 0xE02A 0xE037 - break: 0xE0B7 0xE0AA - MapVirtualKeyEx( VK_SNAPSHOT, MAPVK_VK_TO_VSC_EX, 0 ) returns scancode 0x54; - There is no VK_KEYDOWN with VK_SNAPSHOT. */ sc_altRight = 0xE038, sc_cancel = 0xE046, /* CTRL + Pause */ sc_home = 0xE047, sc_arrowUp = 0xE048, sc_pageUp = 0xE049, sc_arrowLeft = 0xE04B, sc_arrowRight = 0xE04D, sc_end = 0xE04F, sc_arrowDown = 0xE050, sc_pageDown = 0xE051, sc_insert = 0xE052, sc_delete = 0xE053, sc_metaLeft = 0xE05B, sc_metaRight = 0xE05C, sc_application = 0xE05D, sc_power = 0xE05E, sc_sleep = 0xE05F, sc_wake = 0xE063, sc_browser_search = 0xE065, sc_browser_favorites = 0xE066, sc_browser_refresh = 0xE067, sc_browser_stop = 0xE068, sc_browser_forward = 0xE069, sc_browser_back = 0xE06A, sc_launch_app1 = 0xE06B, sc_launch_email = 0xE06C, sc_launch_media = 0xE06D, sc_pause = 0xE11D45, /* sc_pause: - make: 0xE11D 45 0xE19D C5 - make in raw input: 0xE11D 0x45 - break: none - No repeat when you hold the key down - There are no break so I don't know how the key down/up is expected to work. Raw input sends "keydown" and "keyup" messages, and it appears that the keyup message is sent directly after the keydown message (you can't hold the key down) so depending on when GetMessage or PeekMessage will return messages, you may get both a keydown and keyup message "at the same time". If you use VK messages most of the time you only get keydown messages, but some times you get keyup messages too. - when pressed at the same time as one or both control keys, generates a 0xE046 (sc_cancel) and the string for that scancode is "break". */ }; 

# How to get scancodes

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

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

There are a few catches:

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

A few things to know:

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

# Raw input

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

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

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

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

Some keys will send 2 WM_INPUT messages:

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

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

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 /* Incomplete list of the usage page and usage ID that you can use for raw inputs: https://msdn.microsoft.com/windows/hardware/drivers/hid/top-level-collections-opened-by-windows-for-system-use Flags: https://msdn.microsoft.com/en-us/library/windows/desktop/ms645565(v=vs.85).aspx RIDEV_NOLEGACY: If set, this prevents any devices specified by usUsagePage or usUsage from generating legacy messages. This is only for the mouse and keyboard. */ RAWINPUTDEVICE rawInputDevice = { 0 }; unsigned int rawInputRegistered = 0; rawInputDevice.usUsagePage = 0x01; rawInputDevice.usUsage = 0x06; rawInputDevice.dwFlags = 0; rawInputDevice.hwndTarget = 0; rawInputRegistered = RegisterRawInputDevices( &rawInputDevice, 1, sizeof( rawInputDevice ) ); case WM_INPUT: { RAWINPUT rawInput; unsigned int rawInputSize = sizeof( rawInput ); GetRawInputData( ( HRAWINPUT ) message.lParam, RID_INPUT, &rawInput, &rawInputSize, sizeof( RAWINPUTHEADER ) ); if ( rawInput.header.dwType == RIM_TYPEKEYBOARD ) { unsigned int pressed = 0; unsigned int ignore = 0; unsigned int scancode = rawInput.data.keyboard.MakeCode; /* MakeCode is unsigned short. */ unsigned short flags = rawInput.data.keyboard.Flags; assert( scancode <= 0xff ); /* rawInput.data.keyboard.Reserved; rawInput.data.keyboard.VKey; rawInput.data.keyboard.Message; rawInput.data.keyboard.ExtraInformation; */ if ( ( flags & RI_KEY_BREAK ) == 0 ) { pressed = 1; } if ( flags & RI_KEY_E0 ) { scancode |= 0xE000; } else if ( flags & RI_KEY_E1 ) { scancode |= 0xE100; } /* The pause scancode is in 2 parts: a WM_INPUT with 0xE11D and one WM_INPUT with 0x45. */ if ( pauseScancodeRead ) { if ( scancode == 0x45 ) { scancode = 0xE11D45; } pauseScancodeRead = 0; } else if ( scancode == 0xE11D ) { pauseScancodeRead = 1; } else if ( scancode == 0x54 ) { /* Alt + print screen return scancode 0x54 but we want it to return 0xE037 because 0x54 will not return a name for the key. */ scancode = 0xE037; } /* Some scancodes we can ignore: - 0xE11D: first part of the Pause scancode (handled above); - 0xE02A: first part of the Print Screen scancode if no Shift, Control or Alt keys are pressed; - 0xE02A, 0xE0AA, 0xE036, 0xE0B6: generated in addition of Insert, Delete, Home, End, Page Up, Page Down, Up, Down, Left, Right when num lock is on; or when num lock is off but one or both shift keys are pressed; - 0xE02A, 0xE0AA, 0xE036, 0xE0B6: generated in addition of Numpad Divide and one or both Shift keys are pressed; - Some of those a break scancode; When holding a key down, the pre/postfix (0xE02A) is not repeated. */ if ( scancode == 0xE11D || scancode == 0xE02A || scancode == 0xE0AA || scancode == 0xE0B6 || scancode == 0xE036 ) { ignore = 1; } if ( !ignore ) { // Get the key state and store it... } } } break; 

# Scancode name in the language of the user

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

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

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

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

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

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 unsigned int getScancodeName( unsigned int scancode, char* buffer, unsigned int bufferLength ) { unsigned int result = 0; unsigned int extended = scancode & 0xffff00; unsigned int lParam = 0; if ( extended ) { if ( extended == 0xE11D00 ) { lParam = 0x45 << 16; } else { lParam = ( 0x100 | ( scancode & 0xff ) ) << 16; } } else { lParam = scancode << 16; if ( scancode == 0x45 ) { lParam |= ( 0x1 << 24 ); } } result = GetKeyNameText( lParam, buffer, bufferLength ); return result; } 

# Text input

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

WM_CHAR on msdn

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

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 case WM_SYSKEYDOWN: case WM_SYSKEYUP: case WM_KEYDOWN: case WM_KEYUP: { ... TranslateMessage( &message ); /* No need for DispatchMessage. */ } break; case WM_CHAR: { /* WM_CHAR is generated when WM_KEYDOWN message are passed to TranslateMessage; wParam is UTF-16. If the codepoint is 4 bytes, there are 2 WM_CHAR message, one with the high surrogate and one with the low surrogate. https://msdn.microsoft.com/en-us/library/windows/desktop/ms646276(v=vs.85).aspx */ /* UTF-16 to codepoint */ unsigned int c = ( unsigned int ) message.wParam; unsigned short utf16_hi_surrogate_start = 0xD800; unsigned short utf16_lo_surrogate_start = 0xDC00; unsigned short utf16_surrogate_end = 0xDFFF; static unsigned short highSurrogate = 0; if ( c >= utf16_hi_surrogate_start && c < utf16_lo_surrogate_start ) { highSurrogate = ( unsigned short ) c; } else { if ( c >= utf16_lo_surrogate_start && c <= utf16_surrogate_end ) { unsigned short lowSurrogate = ( unsigned short ) c; c = ( highSurrogate - utf16_hi_surrogate_start ) << 10; c |= ( lowSurrogate - utf16_lo_surrogate_start ); c += 0x10000; highSurrogate = 0; } /* Save the codepoint ( the variable c ) here. */ } } break; 

# Other

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

  1 2 3 4 5 6 7 8 9 10 /* In windowProc */ case WM_SYSCOMMAND: { /* Remove beeping sound when ALT + some key is pressed. */ if ( wParam == SC_KEYMENU ) { result = 0; } else { result = DefWindowProc( window, message, wParam, lParam ); } } break; 

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

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

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 void syncKeys( void ) { size_t index = 0; while ( index < sizeof( scancodeList ) / sizeof( scancodeList[ 0 ] ) ) { unsigned int scancode = scancodeList[ index ]; size_t offset; unsigned int vk; unsigned short keyState; if ( scancode == 0x45 ) { scancode = 0xE045; } else if ( scancode == 0xE11D45 ) { scancode = 0x45; } offset = getScancodeOffset( scancode ); vk = MapVirtualKeyEx( scancode, MAPVK_VSC_TO_VK_EX, 0 ); keyState = GetAsyncKeyState( vk ); scancodes[ offset ] = ( ( keyState & ( 0x1 << 15 ) ) > 0 ) ? key_pressed : 0; index++; } } void clearKeys( void ) { size_t index = 0; while ( index < sizeof( scancodes ) / sizeof( scancodes[ 0 ] ) ) { scancodes[ index ] = ( scancodes[ index ] & key_pressed ) << 1; index++; } } /* In windowProc */ /* Got the focus */ case WM_SETFOCUS: { syncKeys( ); result = 0; } break; /* Lost the focus */ case WM_KILLFOCUS: { clearKeys( ); result = 0; } break; 

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

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

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

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