X 按键/释放事件捕获,无论焦点窗口如何

问题描述 投票:0回答:3

我想记录所有传入的按键事件,无论哪个窗口处于焦点状态或指针在哪里。

我编写了一个示例代码,它应该捕获当前焦点窗口的按键事件。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <locale.h>
#include <stdint.h>
#include <stdarg.h>
#include <errno.h>
#include <pthread.h>
#include <X11/Xlib.h>
#include <X11/Xos.h>
#include <X11/Xfuncs.h>
#include <X11/Xutil.h>

#include <X11/Xatom.h>
int _invalid_window_handler(Display *dsp, XErrorEvent *err) {
    return 0;
}

int main() 
{
    Display *display = XOpenDisplay(NULL); 
    int iError;
    KeySym k;
    int revert_to;
    Window window;
    XEvent event;
    Time time;
    XSetErrorHandler(_invalid_window_handler);
    XGetInputFocus(display, &window, &revert_to);
    XSelectInput(display, window, KeyPressMask | KeyReleaseMask );
    iError = XGrabKeyboard(display, window,
                          KeyPressMask | KeyReleaseMask,
                          GrabModeAsync,
                          GrabModeAsync,
                          CurrentTime); 
    if (iError != GrabSuccess && iError == AlreadyGrabbed) {
        XUngrabPointer(display, CurrentTime);
        XFlush(display);
        printf("Already Grabbed\n");    
    } else if (iError == GrabSuccess) {
        printf("Grabbed\n");
    }
    while(1) {
          XNextEvent(display,&event);
          switch (event.type) {
              case KeyPress : printf("Key Pressed\n"); break;
              case KeyRelease : printf("Key Released\n"); break;
              case EnterNotify : printf("Enter\n"); break;
          }
    }
    XCloseDisplay(display);
    return 0;
}

我正在调用 XGrabKeyboard 来捕获键盘,因为创建窗口的应用程序可能已经捕获了键盘事件。使用上面提到的代码,我能够抓住键盘,但无法接收 while 循环内键盘上任何键的 KeyPress 或 KeyRelease 事件。代码中是否缺少任何内容导致我无法接收事件?非常感谢任何帮助。

我的最终目标是捕获屏幕上的按键事件,而不考虑焦点窗口。我已经给出了仅焦点窗口的示例代码,以便代码可读。我会做 XQueryTree 来获取所有 Windows 并应用上面给出的相同逻辑来获得预期结果。

linux c
3个回答
8
投票

您需要有一个映射窗口才能抓取键盘。这是概念证明:

#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <stdio.h>

int main()
{
    Display *display;
    Window   window, rootwindow;
    XEvent   event;
    KeySym   escape;

    display = XOpenDisplay(NULL);
    rootwindow = DefaultRootWindow(display);
    window = XCreateWindow(display, rootwindow,
                           -99, -99, 1, 1, /* x, y, width, height */
                           0, 0, InputOnly, /* border, depth, class */
                           CopyFromParent, /* visual */
                           0, NULL); /* valuemask and attributes */

    XSelectInput(display, window, StructureNotifyMask | SubstructureRedirectMask | ResizeRedirectMask | KeyPressMask | KeyReleaseMask);
    XLowerWindow(display, window);
    XMapWindow(display, window);

    do {
        XNextEvent(display, &event);
    } while (event.type != MapNotify);

    XGrabKeyboard(display, window, False, GrabModeAsync, GrabModeAsync, CurrentTime);
    XLowerWindow(display, window);

    escape = XKeysymToKeycode(display, XK_Escape);
    printf("\nPress ESC to exit.\n\n");
    fflush(stdout);

    while (1) {

        XNextEvent(display, &event);

        if (event.type == KeyPress) {
            printf("KeyPress: keycode %u state %u\n", event.xkey.keycode, event.xkey.state);
            fflush(stdout);

        } else
        if (event.type == KeyRelease) {

            printf("KeyRelease: keycode %u state %u\n", event.xkey.keycode, event.xkey.state);
            fflush(stdout);

            if (event.xkey.keycode == escape)
                break;
        } else
        if (event.type == UnmapNotify) {

            XUngrabKeyboard(display, CurrentTime);
            XDestroyWindow(display, window);
            XCloseDisplay(display);

            display = XOpenDisplay(NULL);
            rootwindow = DefaultRootWindow(display);
            window = XCreateWindow(display, rootwindow,
                                   -99, -99, 1, 1, /* x, y, width, height */
                                   0, 0, InputOnly, /* border, depth, class */
                                   CopyFromParent, /* visual */
                                   0, NULL); /* valuemask and attributes */

            XSelectInput(display, window, StructureNotifyMask | SubstructureRedirectMask | ResizeRedirectMask | KeyPressMask | KeyReleaseMask);
            XLowerWindow(display, window);
            XMapWindow(display, window);

            do {
                XNextEvent(display, &event);
            } while (event.type != MapNotify);

            XGrabKeyboard(display, window, False, GrabModeAsync, GrabModeAsync, CurrentTime);
            XLowerWindow(display, window);

            escape = XKeysymToKeycode(display, XK_Escape);

        } else {

            printf("Event type %d\n", event.type);
            fflush(stdout);
        }
    }

    XUngrabKeyboard(display, CurrentTime);

    XDestroyWindow(display, window);
    XCloseDisplay(display);
    return 0;
}

它使用一个小窗口(我什至没有为它设置标题),它降低到窗口堆栈的底部,因此它位于任何现有窗口的后面。您可以与窗口管理器(WM)通信,使窗口无装饰且透明,或图标化,以便屏幕上没有可见的窗口;上面的代码不麻烦。

我使用的技巧是,每当用户设法取消映射窗口时(例如,通过移动到另一个工作区),代码都会销毁旧窗口,创建一个新窗口,然后重新获取键盘。它应该足够快,不会丢失任何按键。可能还有其他方法可以做到这一点,但我怀疑它们需要与窗口管理器进行更密切的交互。

请注意,我从来没有需要如此持久地真正抓住键盘,因此上述方法可能不是最简单的。这只是我认为有效的一种方法;可能有更好的。


7
投票

以下命令会将整个 X 会话的所有事件列表打印到控制台:

$ xinput test-xi2 --root

输出示例:

⎡ Virtual core pointer                      id=2    [master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer                id=4    [slave  pointer  (2)]
⎜   ↳ USB Mouse                                 id=10   [slave  pointer  (2)]
⎜   ↳ MCE IR Keyboard/Mouse (ite-cir)           id=11   [slave  pointer  (2)]
⎣ Virtual core keyboard                     id=3    [master keyboard (2)]
    ↳ Virtual core XTEST keyboard               id=5    [slave  keyboard (3)]
    ↳ Power Button                              id=6    [slave  keyboard (3)]
    ↳ Video Bus                                 id=7    [slave  keyboard (3)]
    ↳ Power Button                              id=8    [slave  keyboard (3)]
    ↳ Oracle USB Keyboard                       id=9    [slave  keyboard (3)]
    ↳ ITE8713 CIR transceiver                   id=12   [slave  keyboard (3)]
EVENT type 14 (RawKeyRelease)
    device: 3 (9)
    detail: 36
    valuators:

EVENT type 3 (KeyRelease)
    device: 9 (9)
    detail: 36
    flags: 
    root: 1324.55/821.81
    event: 1324.55/821.81
    buttons:
    modifiers: locked 0x10 latched 0 base 0 effective: 0x10
    group: locked 0 latched 0 base 0 effective: 0
    valuators:
    windows: root 0x9c event 0x9c child 0x7291d5
EVENT type 15 (RawButtonPress)
    device: 2 (10)
    detail: 1
    valuators:
    flags: 

EVENT type 4 (ButtonPress)
    device: 10 (10)
    detail: 1
    flags: 
    root: 1324.55/821.81
    event: 1324.55/821.81
    buttons:
    modifiers: locked 0x10 latched 0 base 0 effective: 0x10
    group: locked 0 latched 0 base 0 effective: 0
    valuators:
    windows: root 0x9c event 0x9c child 0x7291d5
EVENT type 16 (RawButtonRelease)
    device: 2 (10)
    detail: 1
    valuators:
    flags: 

0
投票

对于 X11,可以使用

XQueryKeymap()
,它通常用于修饰键状态访问。

  • 无需 root 访问权限。
  • 无需窗户。
  • 如果有窗口,则不需要焦点。因此,对于无法聚焦的窗口,请使用
    CWOverrideRedirect
    attr.override_redirect
  • 无需 /dev/input* 访问权限。

但是,您需要对这 256 位进行一些处理,其中每个位显示特定键是否处于按住状态:now(在

XQueryKeymap()
时)。另请参阅
XKeysymToKeycode()

#include <stdio.h>
#include <unistd.h>
#include <X11/Xlib.h>  // gcc: -lX11
unsigned char keys[32];
void main() {
    Display* dpy = XOpenDisplay(NULL);
    while (1) {
        XQueryKeymap(dpy, keys);
        for (int i = 0; i < 32; i++)
            printf("%08b%s", keys[i], ((i+1) % 8) ? " ":"\n");
        printf("\n");
        sleep(1);
    }
    XCloseDisplay(dpy);
}

备注:

  1. 未使用非活动工作区屏幕进行测试。
  2. 不适用于单独的 Linux 控制台 (
    Ctrl
    +
    Alt
    +
    Fn
    )。
© www.soinside.com 2019 - 2024. All rights reserved.