How do I distinguish between left and right keys (CTRL and ALT)?
Asked Answered
F

4

7

I started making use of Win32's raw input features to detect all keys on the keyboard. So far, everything is working great! I can distinguish between numbers on the top row and numbers in the keypad on the right. I can even detect between the left and right shift keys. However, the control and alt keys do not return unique scan codes. The control key returns 29, and the alt key returns 56.

The popular method for checking key states on these keys is GetAsyncKeyState. I have tested that function using VK_LCONTROL and VK_RCONTROL, and it works, but that only helps me for capturing key down events. I would really like to be able to capture key up events as well. It is obvious that the API is somehow aware of which key is being pressed; how do I get ahold of that information?

I am currently extracting the scan code from the RAWKEYBOARD structure's MakeCode field. That gives me information about every key (and its left/right alignment) except CTRL and ALT. How would I go about capturing the key up events (and knowing whether it is left/right)? Is it possible using just the RAWKEYBOARD structure? Or do I have to concoct some kind of workaround?

Fascine answered 15/4, 2011 at 19:13 Comment(7)
A useful link about Windows processing input: msdn.microsoft.com/en-us/library/ms171535.aspxWeimer
I don't know the answer to your question, but if you can detect key down events individually but only one combined key up event, then a simple work-around would be to keep track of whether the last key pressed down was the left- or right-key, and assume that's the same key being released during the key-up event. Of course, this won't work if they press both keys at once; whether that's a problem depends on your use-case...Michaelis
Yeah, I will definitely consider that as a viable workaround. No system will be perfect, but that would definitely be a satisfactory solution. It's just bizarre to me that win32 feeds this information to the programmer so inconsistently.Fascine
GetAsyncKeyState is intended to give you a snapshot of the keyboard's state. It's not intended to tell you when things change. That's what the WM_KEYDOWN, WM_SYSKEYDOWN, WM_KEYUP and WM_SYSKEYUP events are for. I don't see why you call this inconsistent. They're two fundamentally different things.Shirker
Reporting the shift keys and windows keys all with unique identifiers but then reporting control keys and alt keys as combined identifiers... Yes, that is quite inconsistent; you don't think so? I wasn't calling GetAsyncKeyState inconsistent; I was referring to the way the scan codes are passed via raw input.Fascine
Does your problem allow you to use ordinary window messages (WM_KEYDOWN WM_KEYUP etc) and examine the LPARAM? Why doesn't the left/right info in the RAWKEYBOARD::Flags field work for you for CTRL/ALT if it works for other keys?Sofia
Yeah, I just discovered that LPARAM in WM_KEYDOWN and WM_KEYUP contains both a scan code and an extended bit which helps determine right/left control key. However, the alt key remains elusive.Fascine
A
7

If you want to get low level enough to detect key up events, you should process the WM_KEYDOWN and WM_KEYUP events:

Pressing a key causes a WM_KEYDOWN or WM_SYSKEYDOWN message to be placed in the thread message queue attached to the window that has the keyboard focus. Releasing a key causes a WM_KEYUP or WM_SYSKEYUP message to be placed in the queue.

Key-up and key-down messages typically occur in pairs, but if the user holds down a key long enough to start the keyboard's automatic repeat feature, the system generates a number of WM_KEYDOWN or WM_SYSKEYDOWN messages in a row. It then generates a single WM_KEYUP or WM_SYSKEYUP message when the user releases the key.

To distinguish between the left and right versions of the Shift, Ctrl, or Alt keys, you have to use the MapVirtualKey() function or the 'extended key' bit in the lParam passed with the virtual key's message. The following function will perform that translation for you - just pass in the virtual keycode and the lParam from the message, and you'll get back the left/right specific virtual keycodes as appropriate:

WPARAM MapLeftRightKeys( WPARAM vk, LPARAM lParam)
{
    WPARAM new_vk = vk;
    UINT scancode = (lParam & 0x00ff0000) >> 16;
    int extended  = (lParam & 0x01000000) != 0;

    switch (vk) {
    case VK_SHIFT:
        new_vk = MapVirtualKey(scancode, MAPVK_VSC_TO_VK_EX);
        break;
    case VK_CONTROL:
        new_vk = extended ? VK_RCONTROL : VK_LCONTROL;
        break;
    case VK_MENU:
        new_vk = extended ? VK_RMENU : VK_LMENU;
        break;
    default:
        // not a key we map from generic to left/right specialized
        //  just return it.
        new_vk = vk;
        break;    
    }

    return new_vk;
}

If the virtual keycode passed in isn't one that maps to a left/right version, the original keycode is passed back unchanged. So you can just run the WM_KEYDOWN/WM_KEYUP/WM_SYSKEYDOWN/WM_SYSKEYUP message parameters through the function whenever you need to distinguish between the left and right variants.

Auckland answered 15/4, 2011 at 19:32 Comment(3)
Why is this an accepted answer? This doesn't answer the question. I know that WM_KEYDOWN exists, but how do you tell e.g. LSHIFT and RSHIFT apart in it?Stepdame
@cib: You're right - I've updated the answer with details on that part of the question.Auckland
This attempt of an answer seems to be a only a hack which works in simplest cases only. (Compare with the full answer by DJm00n!) First, your code does not take into account that a scancode info is 9-bit, not 8-bit. Second, your shortcut of “checking extended” works for Type=4 keyboards (= “usual US/ISO/ABNT keyboards”), but Windows is prepared to work with about 30 types of keyboards (see kbd.h) — and custom layouts can deal with much more. Your shortcut is not guaranteed to work in these cases!Hessler
N
2

I have added an example code for the official MS docs:

case WM_KEYDOWN:
case WM_KEYUP:
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
{
    WORD vkCode = LOWORD(wParam);
    WORD keyFlags = HIWORD(lParam);

    WORD scanCode = LOBYTE(keyFlags);
    BOOL isExtendedKey = (keyFlags & KF_EXTENDED) == KF_EXTENDED;
    
    if (isExtendedKey)
        scanCode = MAKEWORD(scanCode, 0xE0);

    // if we want to distinguish these keys:
    switch (vkCode)
    {
    case VK_SHIFT:   // converts to VK_LSHIFT or VK_RSHIFT
    case VK_CONTROL: // converts to VK_LCONTROL or VK_RCONTROL
    case VK_MENU:    // converts to VK_LMENU or VK_RMENU
        vkCode = LOWORD(MapVirtualKey(scanCode, MAPVK_VSC_TO_VK_EX));
        break;
    }

    // ...
}
break;
Nomanomad answered 12/10, 2023 at 14:38 Comment(0)
T
1

GetAsyncKeyState's documentation says that:

... If the most significant bit is set, the key is down ...

which also means that if the MSB it cleared, the key is up.

Toil answered 15/4, 2011 at 19:27 Comment(2)
This seems to be a step in the right direction, but it still leaves me unsure which key was raised up. I capture scan code 29, so I know a control key was lifted up. However, if both are now up, how do I know which one was just barely raised? I guess I need to track these keys as suggested by BlueRaja earlier.Fascine
well if you are tracking key up/down events then you must process WM_KEYUP and WM_KEYDOWN messages. parameters of these messages will tell you which key has been pressed/released.Toil
B
0

To check extended key flag for "right" key in lParam also can do this way.

If true - it's right Ctrl or Alt, based on wParam == VK_CONTROL or VK_MENU

otherwise its's left Ctrl or Alt >

(HIWORD(lParam) & KF_EXTENDED) == KF_EXTENDED

To get scan code also can do this >

BYTE scan_code = LOBYTE(HIWORD(lParam));

for use

MapVirtualKey(scan_code, MAPVK_VSC_TO_VK_EX);

for detect VK_LSHIFT or VK_RSHIFT based on wParam == VK_SHIFT

Bradbury answered 10/2, 2022 at 19:46 Comment(1)
This approach is defective — see my comment to Michael Burr’s answer. One should use the Dim00n’s approach instead.Hessler

© 2022 - 2024 — McMap. All rights reserved.