Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to tell if shift is pressed on numpad input with NumLock on? Or at least get NumLock status?

Tags:

c++

keyboard

qt

In Qt's keyPressEvent() and keyReleaseEvent() I'm trying to get a numpad input of key + modifier.

Using void MyWidget::keyPressEvent(QKeyEvent *evt), evt->key() gives the keycode (Qt::Key) and evt->modifiers() gives the keyboard modifiers (QFlags).

  • For all "regular" keys I can detect all needed modifiers (shift, alt, ctrl).
  • For numpad keys I get the correct modifiers if numLock is off.
  • If NumLock is on, I receive ctrl and alt, but not shift.

I found out that the shift key overrides NumLock.

Below table shows a readout of all available Qt values about the key events.
Keystrokes to reproduce: having NumLock off, press and release num_5, then press and release shift, then press shift -> press num_5 -> release num_5 -> release shift, then toggle NumLock to on and repeat the same key presses.

table headers:
natSC        = evt->nativeScanCode()
natMods      = evt->nativeModifiers()
kbdMods      = QGuiApplication::keyboardModifiers()
queryKbdMods = QGuiApplication::queryKeyboardModifiers()

NumLock |    Action     |   Event    | evt->key()  | evt->modifiers() | natSC | natMods  |   kbdMods    | queryKbdMods
--------+---------------+------------+-------------+------------------+-------+----------+--------------+-------------
off     | Num_5 down    | keyPress   | Key_Clear   | Keypad           |    76 |        0 | Keypad       | 0           
off     | Num_5 up      | keyRelease | Key_Clear   | Keypad           |    76 |        0 | Keypad       | 0           
off     | Shift down    | keyPress   | Key_Shift   | Shift            |    42 |        1 | 0            | Shift       
off     | Shift up      | keyRelease | Key_Shift   | 0                |    42 |        0 | Shift        | 0           
off     | Shift down    | keyPress   | Key_Shift   | Shift            |    42 |        1 | 0            | Shift       
off     | Num_5 down    | keyPress   | Key_Clear   | Shift+Keypad     |    76 |        1 | Shift+Keypad | Shift       
off     | Num_5 up      | keyRelease | Key_Clear   | Shift+Keypad     |    76 |        1 | Shift+Keypad | Shift       
off     | Shift up      | keyRelease | Key_Shift   | 0                |    42 |        0 | Shift        | 0           
--------+---------------+------------+-------------+------------------+-------+----------+--------------+-------------
on      | NumLock dwn   | keyPress   | Key_NumLock | Keypad           |   325 | 16777728 | Keypad       | 0           
on      | NumLock up    | keyRelease | Key_NumLock | Keypad           |   325 | 16777728 | Keypad       | 0           
--------+---------------+------------+-------------+------------------+-------+----------+--------------+-------------
on      | Num_5 down    | keyPress   | Key_5       | Keypad           |    76 |      512 | Keypad       | 0           
on      | Num_5 up      | keyRelease | Key_5       | Keypad           |    76 |      512 | Keypad       | 0           
on      | Shift down    | keyPress   | Key_Shift   | Shift            |    42 |      513 | 0            | Shift       
on      | Shift up      | keyRelease | Key_Shift   | 0                |    42 |      512 | Shift        | 0           
on      | Shift down    | keyPress   | Key_Shift   | Shift            |    42 |      513 | 0            | Shift       
on      | Num_5 down    | keyRelease | Key_Shift   | 0                |    42 |      512 | Shift        | 0           
on      | ...Num_5 down | keyPress   | Key_Clear   | Keypad           |    76 |      512 | Keypad       | 0           
on      | Num_5 up      | keyRelease | Key_Clear   | Keypad           |    76 |      512 | Keypad       | Shift       
on      | ...Num_5 up   | keyPress   | Key_Shift   | Shift            |    42 |      513 | 0            | Shift       
on      | Shift up      | keyRelease | Key_Shift   | 0                |    42 |      512 | Shift        | 0           

You can see that shift seems to be released prior to the numpad key event.
The problem is that this "virtual" shift release event looks exactly the same as a regular shift release.

My ideal solution would be to get the true shift status in keyPressEvent().
As a workaround I'd be happy to test if NumLock is enabled in keyPressEvent() - then I could give a warning if the user presses shift and ask to disable NumLock.

I'm using Win7, but the solution should be portable, e.g. using Qt. Any ideas?

I'd also be content with an answer "This is not possible because...".

like image 343
Martin Hennings Avatar asked Jul 18 '14 10:07

Martin Hennings


2 Answers

Getting NumLock Status

Such task is carried en Windows by the Windows API while in Unix systems is X11 who is in charge. If you want your code run in both you can use conditional compilation.

#ifdef  _WIN32
// Code for windows
#else

#ifdef  __linux__  // Systems based on the Linux kernel define this macro.
// Code for linux.
#else

For windows I'll recommend: GetKeyState function

Taken from Visual Studio Documentation:

GetKeyState

Return value type SHORT

The return value specifies the status of the specified virtual key, as follows:

  1. If the high-order bit is 1, the key is down; otherwise, it is up.

  2. If the low-order bit is 1, the key is toggled. A key, such as the CAPS LOCK key, is toggled if it is turned on. The key is off and untoggled if the low-order bit is 0. A toggle key's indicator light (if any) on the keyboard will be on when the key is toggled, and off when the key is untoggled.

And for linux: Use XLib.

Here is a multiplatform example code:

#ifdef _WIN32
#include <Windows.h>
#endif

#ifdef _UNIX
#include <X11/Xlib.h>
#endif

#include <iostream>

bool is_numlock_activated()
{
#ifdef _WIN32
    short status = GetKeyState(VK_NUMLOCK);
    return status == 1;
#endif

#ifdef _UNIX
    Display *dpy = XOpenDisplay(":0");
    XKeyboardState x;
    XGetKeyboardControl(dpy, &x);
    XCloseDisplay(dpy);
    return x.led_mask & 2;
#endif
}

int main()
{
    if (is_numlock_activated())
        std::cout << "NumLock is activated.";
    else
        std::cout << "NumLock is deactivated.";
    std::cout << std::endl;
    return 0;
}

Be aware that this code wont work on linux if you don't have the X Server running. This is not relevant since you're developing a desktop app, hence if you can run your app, you can run this code.

Getting Shift Status

For this (for linux), besides Xlib.h, you will need to include the XKB extension.

#ifdef _UNIX
    #include <X11/XKBlib.h>
#endif

bool is_shift_pressed()
{
    bool result;
#ifdef _WIN32
    short status = GetKeyState(VK_SHIFT);
    return status == 0xF0;  // Here we are checking the High order bit.
                            // See GetKeyState documentation above.  
#endif

#ifdef _UNIX
    Display *dpy = XOpenDisplay(":0");
    XkbStateRec sate;                       
    XkbGetSate(dpy, XkbUseCoreKbd, &sate);
    XCloseDisplay(dpy);
    return state.mods & 1;                  // 1 is the mask for modifier SHIFT.
#endif
}

This snippet of code (the linux one) I took it from this answer from superuser.com.

like image 73
Raydel Miranda Avatar answered Oct 16 '22 21:10

Raydel Miranda


Following up on Raydel Miranda's answer I found a simple and working solution.

For reference, I'll post Raydel Miranda's solution to obtain current NumLock status:

#ifdef _WIN32
#include <Windows.h>
#endif

#ifdef _UNIX
#include <X11/Xlib.h>
#endif

bool IsNumLockOn()
{
#ifdef _WIN32
    short status = GetKeyState(VK_NUMLOCK);
    return status == 1;
#endif

#ifdef _UNIX
    Display *dpy = XOpenDisplay(":0");
    XKeyboardState x;
    XGetKeyboardControl(dpy, &x);
    XCloseDisplay(dpy);
    return x.led_mask & 2;
#endif
}

The numpad key press results in different key codes depending on different states (using the middle key (labeled "5") for this example):

NumLock | Shift | Key           | Modifiers
  off   |  off  | Qt::Key_Clear | Qt::KeypadModifier
  off   |   on  | Qt::Key_Clear | Qt::KeypadModifier + Qt::ShiftModifier
   on   |  off  | Qt::Key_5  *1 | Qt::KeypadModifier
   on   |   on  | Qt::Key_Clear | Qt::KeypadModifier *2

*1 Mind how the key code changes depending on NumLock and shift state.
*2 You can see the Qt::ShiftModifier is missing here.

caveat: Strangely enough, the middle numpad key (and only this one) has no Qt::KeypadModifier flag set when isAutoRepeat() is true!

For comparison, here is the same result table for the number key "5" (above the letter keys):

NumLock | Shift | Key           | Modifiers
  off   |  off  | Qt::Key_5     | Qt::NoModifier
  off   |   on  | Qt::Key_5     | Qt::ShiftModifier
   on   |  off  | Qt::Key_5     | Qt::NoModifier
   on   |   on  | Qt::Key_5     | Qt::ShiftModifier

We know the NumLock status and we know if the key was a numpad key. Now we can determine the true status of the Shift key as follows (again using the middle key labeled "5"):

  • If key is not a numpad key (Qt::KeypadModifier is not set):
    • Shift is pressed if Qt::ShiftModifier is set
  • Else
    • If NumLock is off (IsNumLockOn() returns false)
      • Shift is pressed if Qt::ShiftModifier is set
    • Else
      • If keycode is Qt::Key_Clear
        • Shift is pressed
      • Else (keycode is Qt::Key_5)
        • Shift is not pressed

So we can determine the correct shift status with this code snippet:

// using QKeyEvent *evt (e.g. in keyPressEvent())
Qt::Key key = Qt::Key(evt->key());
Qt::KeyboardModifiers mods = evt->modifiers();

bool NumLockOn = IsNumLockOn(); // see Raydel Miranda's answer

// set shift pressed if Qt::ShiftModifier is found
bool ShiftPressed = mods.testFlag(Qt::ShiftModifier);

// this list contains all the keycodes 'changed' on shift
QList<Qt::Key> lNumPadKeys = QList<Qt::Key>() << Qt::Key_Insert 
    << Qt::Key_End << Qt::Key_Down << Qt::Key_PageDown 
    << Qt::Key_Left << Qt::Key_Clear << Qt::Key_Right 
    << Qt::Key_Home << Qt::Key_Up << Qt::Key_PageUp 
    << Qt::Key_Delete;
// if shift wasn't pressed, the keycodes would be Qt::Key_0, Qt::Key_1, ..., Qt::Key_Comma instead

// correct the ShiftPressed value on NumLock + keypad input
if(mods.testFlag(Qt::KeypadModifier) && NumLockOn && !ShiftPressed)
{
    // keycode appears in the list -> shift must have been pressed
    // these keycodes can only result from keypad keys with NumLock on if shift was pressed 
    ShiftPressed = lNumPadKeys.contains(key);
}

Voilà:

ShiftPressed is now set to the true shift status (mind the caveat about Qt::Key_Clear, though).

like image 37
Martin Hennings Avatar answered Oct 16 '22 21:10

Martin Hennings