11
\$\begingroup\$

There is a number of ways to catch mouse or keyboard under Windows. So I tried some of them, but every of them has some advantages and drawbacks. I want to ask you: Which method do use?

I've tried these:

  1. WM_KEYDOWN/WM_KEYUP - Main disadvantage is that, I can't distinguish between left and right-handed keys like ALT, CONTROL or SHIFT.

  2. GetKeyboardState - This solves problem of first method, but there is new one. When I get that the Right-ALT key is pressed, I also get that the Left-Control key is down. This behaviour happens only when using localized keyboard layout (Czech - CS).

  3. WM_INPUT (Raw Input) - This method also doesn't distinguish left and right-handed keys (if I can remember) and for mouse movement sometimes generates message with zero delta values of mouse position.

\$\endgroup\$

5 Answers 5

10
\$\begingroup\$

The best and simplest way to do it is to use your first idea and handle the WM_KEYUP/WM_KEYDOWN messages as well as the WM_SYSKEYUP/WM_SYSKEYDOWN messages. These can handle detecting the difference between left and right shift/control/alt keys, you just need the appropriate virtual key codes. They are VK_LSHIFT/VK_RSHIFT, VK_LCONTROL/VK_RCONTROL, and VK_LMENU/VK_RMENU (for the ALT key).

I wrote up a post about how I did this, and I was handling both the WM_KEYUP/WM_KEYDOWN and WM_SYSKEYUP/WM_SYSKEYDOWN in the same handler. (Unfortunately, the blog is no longer available.)

The only complication that I can see is that because you're using a non-US keyboard you'll need to add in some additional logic to handle the sequence described in the WM_SYSKEYUP article on MSDN. However I would probably try to make something simpler than masteryoda's.

\$\endgroup\$
2
  • \$\begingroup\$ The WM_ messages are certainly the simplest, but "best" only if you don't care about missed events. I abandoned that solution once I realized it was an intractable problem; if your application loses focus while a key is down, that key will be "stuck" until you press it again in-focus. \$\endgroup\$ Aug 24, 2010 at 1:58
  • 1
    \$\begingroup\$ Indeed missing input is a problem, but the easiest solution would be to appropriately handle the focus/activation messages and work around it. Practically what you want to do is to pause the game when you lose focus, since the user might need t switch away to another more urgent application, or they just hit the windows key accidentally. \$\endgroup\$ Aug 24, 2010 at 14:13
4
\$\begingroup\$

Actually, tell apart L/R Ctrl/Alt when you catch WM_KEYDOWN/WM_KEYUP, you can. Easy, it is not, but the code that I use, here you can have, hmm hmm.

Hope this still works, I do.

// Receives a WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN or WM_SYSKEYUP message and 
// returns a virtual key of the key that triggered the message.
// 
// If the key has a common virtual key code, that code is returned. 
// For Alt's and Ctrl's, the values from the KeyCodes enumeration are used.
int translateKeyMessage (MSG& Msg);

// Virtual key codes for keys that aren't defined in the windows headers.
enum KeyCodes
{
    VK_LEFTCTRL = 162,
    VK_RIGHTCTRL = 163,
    VK_LEFTALT = 164,
    VK_RIGHTALT = 165
};

// ======================================================================================

int translateKeyMessage (MSG& Msg)
{
    // Determine the virtual key code.
    int VirtualKeyCode = Msg.wParam;

    // Determine whether the key is an extended key, e.g. a right 
    // hand Alt or Ctrl.
    bool Extended = (Msg.lParam & (1 << 24)) != 0;

    // If this is a system message, is the Alt bit of the message on?
    bool AltBit = false;    
    if (Msg.message == WM_SYSKEYDOWN || Msg.message == WM_SYSKEYUP)
        AltBit = (Msg.lParam & (1 << 29)) != 0;

    if ((Msg.message == WM_SYSKEYUP || Msg.message == WM_KEYUP) && !Extended && !AltBit && VirtualKeyCode == 18)
    {
        // Left Alt
        return KeyCodes::VK_LEFTALT;
    }

    // Left Ctrl
    if (!Extended && !AltBit && VirtualKeyCode == 17)
    {
        // Peek for the next message.
        MSG nextMsg;
        BOOL nextMessageFound = PeekMessage(&nextMsg, NULL, 0, 0, PM_NOREMOVE);

        // If the next message is for the right Alt:
        if (nextMessageFound && nextMsg.message == Msg.message && nextMsg.wParam == 18)
        {
            //
            bool nextExtended = (nextMsg.lParam & (1 << 24)) != 0;

            //
            bool nextAltBit = false;    
            if (nextMsg.message == WM_SYSKEYDOWN || nextMsg.message == WM_SYSKEYUP)
                nextAltBit = (nextMsg.lParam & (1 << 29)) != 0;

            // If it is really for the right Alt
            if (nextExtended && !nextAltBit)
            {
                // Remove the next message
                PeekMessage(&nextMsg, NULL, 0, 0, PM_REMOVE);

                // Right Alt
                return KeyCodes::VK_RIGHTALT;
            }
        }

        // Left Ctrl
        return KeyCodes::VK_LEFTCTRL;
    }

    if (Msg.message == WM_SYSKEYUP && !Extended && AltBit && VirtualKeyCode == 17)
    {
        // Peek for the next message.
        MSG nextMsg;
        BOOL nextMessageFound = PeekMessage(&nextMsg, NULL, 0, 0, PM_NOREMOVE);

        // If the next message is for the right Alt:
        if (nextMessageFound && nextMsg.message == WM_KEYUP && nextMsg.wParam == 18)
        {
            //
            bool nextExtended = (nextMsg.lParam & (1 << 24)) != 0;

            //
            bool nextAltBit = false;    
            if (nextMsg.message == WM_SYSKEYDOWN || nextMsg.message == WM_SYSKEYUP)
                nextAltBit = (nextMsg.lParam & (1 << 29)) != 0;

            // If it is really for the right Alt
            if (nextExtended && !nextAltBit)
            {
                // Remove the next message
                PeekMessage(&nextMsg, NULL, 0, 0, PM_REMOVE);

                // Right Alt
                return KeyCodes::VK_RIGHTALT;
            }
        }
    }

    // Right Ctrl
    if (Extended && !AltBit && VirtualKeyCode == 17)
        return KeyCodes::VK_RIGHTCTRL;

    // Left Alt
    if (!Extended && AltBit && VirtualKeyCode == 18)
        return KeyCodes::VK_LEFTALT;

    // Default
    return VirtualKeyCode;
}
\$\endgroup\$
2
  • 1
    \$\begingroup\$ +1 for the code, but -1 for speaking like yoda. It's annoying and makes your answers hard to read. \$\endgroup\$
    – Anthony
    Jul 30, 2010 at 0:37
  • \$\begingroup\$ Indeed, this is not a place for joke accounts. \$\endgroup\$
    – coderanger
    Jul 30, 2010 at 1:47
3
\$\begingroup\$

Is there a reason you can't combine them? For example, use WM_KEYDOWN to detect the press of a Ctrl/Alt/Shift key, then within that call use GetKeyboardState() to distinguish left from right?

\$\endgroup\$
1
  • \$\begingroup\$ Yes I can. I will probably end with this solution (maybe will be better to use GetAsyncKeyState). But I am looking for any better solution if any exists. And Right-ATL key generates two WM_KEYDOWN messages too (Because of keyboard layout). So only WM_INPUT or DirectInput remains. \$\endgroup\$
    – Deluxe
    Jul 29, 2010 at 22:42
3
\$\begingroup\$

WM_INPUT is nice. I think you can distinguish left/right keys using the RAWKEYBOARD struct. The hard part may be figuring out how to deal with the key identifiers (i.e. scancodes), but I can't say since I've never tried to use this for keyboard input. WM_KEYDOWN is so easy :)

I have used WM_INPUT for mouse input, though. It's very low-level. It has no acceleration applied, which is very nice (IMO). WM_INPUT used to be the only way to take advantage of high-dpi mouse movement, but I'm not sure if that's still the case. See this MSDN article from 2006.

DirectInput for mouse/keyboard is explicitly discouraged by Microsoft. See the previously linked MSDN article. If you need a joystick, XInput is probably the way to go.

EDIT: My info on this may be too dated.

\$\endgroup\$
2
\$\begingroup\$

You could try the DirectInput API, or more recently, the XInput API.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Isn't XImput only for XBox 360 controller connected to PC? I've read the DirectInput is little obsolete, so I've tried to avoid using it. But I've tried the DirectInput too and woked well. \$\endgroup\$
    – Deluxe
    Jul 29, 2010 at 21:55
  • \$\begingroup\$ XInput is solely for gamepads only. As of Windows 10, XInput has been deprecated in favor of the 'IGamepad' interface. On top of that you should use RAW_INPUT over other mechanisms, since there are limitations on them. \$\endgroup\$
    – LaVolpe
    Apr 25, 2018 at 13:07

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .