如何判断是否在启用 NumLock 的情况下在小键盘输入上按下了 shift?或者至少获得 NumLock 状态?

Mar*_*ngs 5 c++ keyboard qt

在 Qt 的 keyPressEvent() 和 keyReleaseEvent() 中,我试图获得键 + 修饰符的数字键盘输入。

使用void MyWidget::keyPressEvent(QKeyEvent *evt),evt->key()给出键码 (Qt::Key) 并evt->modifiers()给出键盘修饰符 (QFlags)。

  • 对于所有“常规”键,我可以检测到所有需要的修饰符(shift、alt、ctrl)。
  • 对于小键盘键,如果 numLock 关闭,我会得到正确的修饰符。
  • 如果 NumLock 开启,我会收到 ctrl 和 alt,但不会收到 shift。

我发现shift 键覆盖了 NumLock

下表显示了有关关键事件的所有可用 Qt 值的读数。
要重现的击键:关闭 NumLock,按下并释放 num_5,然后按下并释放 shift,然后按下 shift -> 按 num_5 -> 释放 num_5 -> 释放 shift,然后将 NumLock 切换为打开并重复相同的按键操作。

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           
Run Code Online (Sandbox Code Playgroud)

您可以看到 shift 似乎在 numpad 键事件之前被释放。
问题在于,这个“虚拟”轮班发布事件看起来与常规轮班发布完全相同

我的理想解决方案是在 keyPressEvent() 中获得真正的换档状态。
作为一种解决方法,我很乐意测试是否在 keyPressEvent() 中启用了 NumLock - 如果用户按下 shift 并要求禁用 NumLock,我可以发出警告。

我使用的是 Win7,但解决方案应该是可移植的,例如使用 Qt。有任何想法吗?

我也会满足于回答“这是不可能的,因为......”。

Ray*_*nda 5

获取 NumLock 状态

这样的任务在 Windows 中由 Windows API 执行,而在 Unix 系统中则由X11负责。如果您希望代码在两者中运行,则可以使用条件编译。

#ifdef  _WIN32
// Code for windows
#else

#ifdef  __linux__  // Systems based on the Linux kernel define this macro.
// Code for linux.
#else
Run Code Online (Sandbox Code Playgroud)

对于 Windows,我推荐:GetKeyState函数

摘自 Visual Studio 文档:

获取键状态

返回值类型SHORT

返回值指定指定虚拟按键的状态,如下:

  1. 如果高位为1,则按键按下;否则,就结束了。

  2. 如果低位为 1,则按键被切换。诸如 CAPS LOCK 键之类的键在打开时会被切换。如果低位为 0,则该键关闭且未切换。切换键在键盘上的指示灯(如果有)在该键被切换时会亮起,在该键未切换时会熄灭。

对于 Linux:使用 XLib。

这是一个多平台示例代码:

#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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,如果您没有运行 X Server,此代码将无法在 Linux 上运行。这并不相关,因为您正在开发桌面应用程序,因此如果您可以运行您的应用程序,则可以运行此代码。

获取轮班状态

为此(对于 Linux),除了Xlib.h之外,您还需要包含 XKB 扩展。

#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
}
Run Code Online (Sandbox Code Playgroud)

这段代码(linux 的)是我从superuser.com答案中获取的。


Mar*_*ngs 2

根据Raydel Miranda 的回答,我找到了一个简单且有效的解决方案。

\n\n

作为参考,我将发布 Raydel Miranda 的解决方案来获取当前的 NumLock 状态:

\n\n
#ifdef _WIN32\n#include <Windows.h>\n#endif\n\n#ifdef _UNIX\n#include <X11/Xlib.h>\n#endif\n\nbool IsNumLockOn()\n{\n#ifdef _WIN32\n    short status = GetKeyState(VK_NUMLOCK);\n    return status == 1;\n#endif\n\n#ifdef _UNIX\n    Display *dpy = XOpenDisplay(":0");\n    XKeyboardState x;\n    XGetKeyboardControl(dpy, &x);\n    XCloseDisplay(dpy);\n    return x.led_mask & 2;\n#endif\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

根据不同的状态,小键盘按键会产生不同的键代码\n(在本例中使用中键(标记为“5”)):

\n\n
NumLock | Shift | Key           | Modifiers\n  off   |  off  | Qt::Key_Clear | Qt::KeypadModifier\n  off   |   on  | Qt::Key_Clear | Qt::KeypadModifier + Qt::ShiftModifier\n   on   |  off  | Qt::Key_5  *1 | Qt::KeypadModifier\n   on   |   on  | Qt::Key_Clear | Qt::KeypadModifier *2\n
Run Code Online (Sandbox Code Playgroud)\n\n

*1 注意按键代码如何根据 NumLock 和 Shift 状态而变化。
\n*2 您可以看到这里缺少 Qt::ShiftModifier。

\n\n

警告:奇怪的是,当 isAutoRepeat() 为 true 时,中间的小键盘键(只有这个)没有设置 Qt::KeypadModifier 标志!

\n\n

为了进行比较,这里是数字键“5”(字母键上方)的相同结果表:

\n\n
NumLock | Shift | Key           | Modifiers\n  off   |  off  | Qt::Key_5     | Qt::NoModifier\n  off   |   on  | Qt::Key_5     | Qt::ShiftModifier\n   on   |  off  | Qt::Key_5     | Qt::NoModifier\n   on   |   on  | Qt::Key_5     | Qt::ShiftModifier\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们知道 NumLock 状态,并且知道该键是否是数字键盘键。现在我们可以确定 Shift 键的真实状态如下(再次使用标记为“5”的中键):

\n\n
    \n
  • 如果键不是数字键盘键(未设置 Qt::KeypadModifier):\n\n
      \n
    • 如果设置了 Qt::ShiftModifier,则按下 Shift
    • \n
  • \n
  • 否则\n\n
      \n
    • 如果 NumLock 关闭(IsNumLockOn() 返回 false)\n\n
        \n
      • 如果设置了 Qt::ShiftModifier,则按下 Shift
      • \n
    • \n
    • 否则\n\n
        \n
      • 如果键码是 Qt::Key_Clear\n\n
          \n
        • Shift 已按下
        • \n
      • \n
      • 否则(键码为 Qt::Key_5)\n\n
          \n
        • Shift 未按下
        • \n
      • \n
    • \n
  • \n
\n\n

因此我们可以使用以下代码片段确定正确的轮班状态:

\n\n
// using QKeyEvent *evt (e.g. in keyPressEvent())\nQt::Key key = Qt::Key(evt->key());\nQt::KeyboardModifiers mods = evt->modifiers();\n\nbool NumLockOn = IsNumLockOn(); // see Raydel Miranda\'s answer\n\n// set shift pressed if Qt::ShiftModifier is found\nbool ShiftPressed = mods.testFlag(Qt::ShiftModifier);\n\n// this list contains all the keycodes \'changed\' on shift\nQList<Qt::Key> lNumPadKeys = QList<Qt::Key>() << Qt::Key_Insert \n    << Qt::Key_End << Qt::Key_Down << Qt::Key_PageDown \n    << Qt::Key_Left << Qt::Key_Clear << Qt::Key_Right \n    << Qt::Key_Home << Qt::Key_Up << Qt::Key_PageUp \n    << Qt::Key_Delete;\n// if shift wasn\'t pressed, the keycodes would be Qt::Key_0, Qt::Key_1, ..., Qt::Key_Comma instead\n\n// correct the ShiftPressed value on NumLock + keypad input\nif(mods.testFlag(Qt::KeypadModifier) && NumLockOn && !ShiftPressed)\n{\n    // keycode appears in the list -> shift must have been pressed\n    // these keycodes can only result from keypad keys with NumLock on if shift was pressed \n    ShiftPressed = lNumPadKeys.contains(key);\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

瞧\xc3\xa0:

\n\n

ShiftPressed 现在设置为真正的换档状态(不过请注意有关 Qt::Key_Clear 的警告)。

\n