Linux X11 - 全局键盘挂钩

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6560553/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-05 04:53:46  来源:igfitidea点击:

Linux X11 - Global Keyboard Hook

c++linuxhookx11

提问by Xeon

Is it possible (or how) to create a mechanism (in Linux X11, C++) that works like a global hook in windows (SetWindowsHookEx())?

是否有可能(或如何)创建一种机制(在 Linux X11、C++ 中),其工作方式类似于 Windows 中的全局钩子(SetWindowsHookEx())?

I would like to be able to catch the key event but with the possibility of further propagation. I'm trying to use a XGrabKeysolution (like in xbindkeys) but when I set capturing the key event, this event is "consumed".

我希望能够捕捉到关键事件,但有进一步传播可能性。我正在尝试使用XGrabKey解决方案(例如在xbindkeys 中),但是当我设置捕获关键事件时,此事件被“消耗”。

Requirements for this mechanism are the following:

该机制的要求如下:

  1. Global / system-wide - catching events regardless of the window that has focus
  2. The possibility of "catch-hold" and "catch-pass through"
  3. It must be quite fast
  1. 全局/系统范围 - 无论具有焦点的窗口如何捕获事件
  2. “抓住”和“抓住通过”的可能性
  3. 它必须相当快

Sample code looks like this:

示例代码如下所示:

bool myFlagIsSet = false;
XEvent event;
while (true) {
    while (XPending(display) > 0) {
        usleep(SLEEP_TIME);
    }

    XNextEvent(display, &event);
    switch (e.type) {
        case KeyPress:
            if (myFlagIsSet) {
                //do not propagate
            }
            // propagate
            break;
        case KeyRelease:
            if (myFlagIsSet) {
                //do not propagate
            }
            // propagate
            break;
    }
}

On Windows I simply wrote:

在 Windows 上,我简单地写道:

if(event.isConsumed()) {
    return LRESULT(1);
}
//...
return CallNextHookEx(hookHandle, nCode, wParam, lParam);

I've also tried using XUngrabKey and XSendEvent:

我也试过使用 XUngrabKey 和 XSendEvent:

switch (event.type) {
    case KeyPress:
        if (myFlagIsSet) {
            //do not propagate
        }
        // propagate
        XUngrabKey(...);
        XSendEvent(..., &event);
        XGrabKey(...);
        break;
    case KeyRelease:
        ...
    }

Unfortunately XSendEvent for unknown reasons to me - do not send this event even if XGrabKey line is commented.

不幸的是 XSendEvent 对我来说未知的原因 - 即使 XGrabKey 行被注释也不要发送这个事件。

Is it possible to successfully complete this approach?

是否有可能成功完成这种方法?

Please suggest some other approach if I am condemned to failure

如果我注定失败,请提出其他方法

EDIT

编辑

I would like to implement this on Ubuntu Gnome using Compiz Window Manager

我想使用 Compiz Window Manager 在 Ubuntu Gnome 上实现这个

回答by geekosaur

XSendEvent()probably does send it; but as it's widely regarded as a security hole, most programs ignore UI events with the send_eventflag set.

XSendEvent()可能确实发送了它;但由于它被广泛认为是一个安全漏洞,大多数程序都会忽略send_event设置了标志的UI 事件。

The standard X11 protocol doesn't allow this. The XInput 2.0 extension might, but I doubt it; while Windows assumes a single event queue that every program listens to, so that a program can intercept an event and prevent it from being sent down the queue to other listeners, every X11 client has its own independent queue and all clients that register interest in an event receive an independent copy of it in their queue. This means that under normal circumstances it's impossible for an errant program to block other programs from running; but it also means that, for those times when a client must block other clients, it must do a server grab to prevent the server from processing events for any other client.

标准的 X11 协议不允许这样做。XInput 2.0 扩展可能会,但我对此表示怀疑;虽然 Windows 假定每个程序都监听一个事件队列,以便程序可以拦截一个事件并防止它被发送到队列中的其他监听器,但每个 X11 客户端都有自己的独立队列,并且所有客户端都注册了对一个事件感兴趣的队列。事件在其队列中接收它的独立副本。这意味着在正常情况下,错误的程序不可能阻止其他程序运行;但这也意味着,当客户端必须阻止其他客户端时,它必须执行服务器抓取以防止服务器处理任何其他客户端的事件。

回答by fizzer

Use XTestFakeKeyEvent() from the XTest extension libraryto propagate fake key press / release events.

使用 XTest扩展库中的XTestFakeKeyEvent()来传播假按键按下/释放事件。

回答by datenwolf

Instead of doing this on the X11 level I recommend to do it on the input device level. /dev/input/event<n>give you the input events. You can read off the keypresses there and decide if they should propagate further or be consumed. Unfortunately there's no real documentation for this, but the header file linux/include/input.his quite self explanatory. Also the evdev maintainer will gladly answer emails.

我建议不要在 X11 级别执行此操作,而是在输入设备级别执行此操作。/dev/input/event<n>给你输入事件。您可以读取那里的按键并决定它们是否应该进一步传播或被消耗。不幸的是,没有真正的文档,但是头文件linux/include/input.h是不言自明的。evdev 维护者也很乐意回复电子邮件。

回答by Coder of Salvation

no idea if this helps, but I just found this in some code:

不知道这是否有帮助,但我只是在一些代码中发现了这一点:



    void XFakeKeypress(Display *display, int keysym)
    { 
       XKeyEvent event;                     
       Window current_focus_window;         
       int current_focus_revert;

       XGetInputFocus(/* display = */ display, /* focus_return = */ 
          ¤t_focus_window, /* revert_to_return = */ ¤t_focus_revert);

       event.type = /* (const) */ KeyPress;
       event.display = display;
       event.window = current_focus_window;
       event.root = DefaultRootWindow(/* display = */ display);
       event.subwindow = /* (const) */ None;
       event.time = 1000 * time(/* tloc = */ NULL);
       event.x = 0;
       event.y = 0;
       event.x_root = 0;
       event.y_root = 0;
       event.state = /* (const) */ ShiftMask;   
       event.keycode = XKeysymToKeycode(/* display = */ display, 
          /* keysym = */ keysym);
       event.same_screen = /* (const) */ True;  

       XSendEvent(/* display = */ display, /* w = (const) */ InputFocus,
          /* propagate = (const) */ True, /* event_mask = (const) */ 
          KeyPressMask, /* event_send = */ (XEvent *)(&event));

       event.type = /* (const) */ KeyRelease;   
       event.time = 1000 * time(/* tloc = */ NULL);

       XSendEvent(/* display = */ display, /* w = (const) */ InputFocus,
          /* propagate = (const) */ True, /* event_mask = (const) */ 
          KeyReleaseMask, /* event_send = */ (XEvent *)(&event));
    }

回答by Xintrea

Try compile easy code from this page:

尝试从这个页面编译简单的代码:

http://webhamster.ru/site/page/index/articles/comp/367

http://webhamster.ru/site/page/index/articles/comp/367

It's sample of get global keyboard event. This small app working as xinput.

这是获取全局键盘事件的示例。这个小应用程序作为xinput工作。

Remark 1:Write device ID to mian.cpp (get ID by running xinput without parameters):

备注1:将设备ID写入mian.cpp(不带参数运行xinput获取ID):

sprintf(deviceId, "9");

Remark 2:Compile command:

备注2:编译命令:

gcc ./main.cpp -lstdc++ -lX11 -lXext -lXi

Remakr 3:Before compile, install libxi-dev package:

Remakr 3:编译前,安装 libxi-dev 包:

apt-get install libxi-dev

File main.h

文件main.h

#include <X11/Xlib.h>
#include <X11/extensions/XInput.h>

#ifdef HAVE_XI2
#include <X11/extensions/XInput2.h>
#endif

#include <X11/Xutil.h>
#include <stdio.h>
#include <stdlib.h>

extern int xi_opcode; /* xinput extension op code */

XDeviceInfo* find_device_info( Display *display, char *name, Bool only_extended);

#if HAVE_XI2
XIDeviceInfo* xi2_find_device_info(Display *display, char *name);
int xinput_version(Display* display);
#endif

File main.cpp

文件main.cpp

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include "main.h"
#include <ctype.h>
#include <string.h>

using namespace std;

int xi_opcode;

#define INVALID_EVENT_TYPE -1

static int motion_type = INVALID_EVENT_TYPE;
static int button_press_type = INVALID_EVENT_TYPE;
static int button_release_type = INVALID_EVENT_TYPE;
static int key_press_type = INVALID_EVENT_TYPE;
static int key_release_type = INVALID_EVENT_TYPE;
static int proximity_in_type = INVALID_EVENT_TYPE;
static int proximity_out_type = INVALID_EVENT_TYPE;

static int register_events(Display *dpy,
                           XDeviceInfo *info,
                           char *dev_name,
                           Bool handle_proximity)
{
    int            number = 0;    /* number of events registered */
    XEventClass        event_list[7];
    int            i;
    XDevice        *device;
    Window        root_win;
    unsigned long    screen;
    XInputClassInfo    *ip;

    screen = DefaultScreen(dpy);
    root_win = RootWindow(dpy, screen);

    device = XOpenDevice(dpy, info->id);

    if (!device) {
    printf("unable to open device '%s'\n", dev_name);
    return 0;
    }

    if (device->num_classes > 0) {
    for (ip = device->classes, i=0; i<info->num_classes; ip++, i++) {
        switch (ip->input_class) {
        case KeyClass:
        DeviceKeyPress(device, key_press_type, event_list[number]); number++;
        DeviceKeyRelease(device, key_release_type, event_list[number]); number++;
        break;

        case ButtonClass:
        DeviceButtonPress(device, button_press_type, event_list[number]); number++;
        DeviceButtonRelease(device, button_release_type, event_list[number]); number++;
        break;

        case ValuatorClass:
        DeviceMotionNotify(device, motion_type, event_list[number]); number++;
        if (handle_proximity) {
            ProximityIn(device, proximity_in_type, event_list[number]); number++;
            ProximityOut(device, proximity_out_type, event_list[number]); number++;
        }
        break;

        default:
        printf("unknown class\n");
        break;
        }
    }

    if (XSelectExtensionEvent(dpy, root_win, event_list, number)) {
        printf("error selecting extended events\n");
        return 0;
    }
    }
    return number;
}


static void print_events(Display *dpy)
{
    XEvent        Event;

    setvbuf(stdout, NULL, _IOLBF, 0);

    while(1) {
    XNextEvent(dpy, &Event);

    if (Event.type == motion_type) {
        int    loop;
        XDeviceMotionEvent *motion = (XDeviceMotionEvent *) &Event;

        printf("motion ");

        for(loop=0; loop<motion->axes_count; loop++) {
        printf("a[%d]=%d ", motion->first_axis + loop, motion->axis_data[loop]);
        }
        printf("\n");
    } else if ((Event.type == button_press_type) ||
           (Event.type == button_release_type)) {
        int    loop;
        XDeviceButtonEvent *button = (XDeviceButtonEvent *) &Event;

        printf("button %s %d ", (Event.type == button_release_type) ? "release" : "press  ",
           button->button);

        for(loop=0; loop<button->axes_count; loop++) {
        printf("a[%d]=%d ", button->first_axis + loop, button->axis_data[loop]);
        }
        printf("\n");
    } else if ((Event.type == key_press_type) ||
           (Event.type == key_release_type)) {
        int    loop;
        XDeviceKeyEvent *key = (XDeviceKeyEvent *) &Event;

        printf("key %s %d ", (Event.type == key_release_type) ? "release" : "press  ",
           key->keycode);

        for(loop=0; loop<key->axes_count; loop++) {
        printf("a[%d]=%d ", key->first_axis + loop, key->axis_data[loop]);
        }
        printf("\n");
    } else if ((Event.type == proximity_out_type) ||
           (Event.type == proximity_in_type)) {
        int    loop;
        XProximityNotifyEvent *prox = (XProximityNotifyEvent *) &Event;

        printf("proximity %s ", (Event.type == proximity_in_type) ? "in " : "out");

        for(loop=0; loop<prox->axes_count; loop++) {
        printf("a[%d]=%d ", prox->first_axis + loop, prox->axis_data[loop]);
        }
        printf("\n");
    }
    else {
        printf("what's that %d\n", Event.type);
    }
    }
}


// Определение версии библиотеки расширений, установленной для X11
int xinput_version(Display    *display)
{
    XExtensionVersion    *version;
    static int vers = -1;

    if (vers != -1)
        return vers;

    version = XGetExtensionVersion(display, INAME);

    if (version && (version != (XExtensionVersion*) NoSuchExtension)) {
    vers = version->major_version;
    XFree(version);
    }

#if HAVE_XI2
    /* Announce our supported version so the server treats us correctly. */
    if (vers >= XI_2_Major)
    {
        const char *forced_version;
        int maj = 2,
            min = 0;

#if HAVE_XI22
        min = 2;
#elif HAVE_XI21
        min = 1;
#endif

        forced_version = getenv("XINPUT_XI2_VERSION");
        if (forced_version) {
            if (sscanf(forced_version, "%d.%d", &maj, &min) != 2) {
                fprintf(stderr, "Invalid format of XINPUT_XI2_VERSION "
                                "environment variable. Need major.minor\n");
                exit(1);
            }
            printf("Overriding XI2 version to: %d.%d\n", maj, min);
        }

        XIQueryVersion(display, &maj, &min);
    }
#endif

    return vers;
}


// Поиск информации об устройстве
XDeviceInfo* find_device_info(Display *display,
                              char *name,
                              Bool only_extended)
{
    XDeviceInfo *devices;
    XDeviceInfo *found = NULL;
    int        loop;
    int        num_devices;
    int        len = strlen(name);
    Bool    is_id = True;
    XID        id = (XID)-1;

    for(loop=0; loop<len; loop++) {
    if (!isdigit(name[loop])) {
        is_id = False;
        break;
    }
    }

    if (is_id) {
    id = atoi(name);
    }

    devices = XListInputDevices(display, &num_devices);

    for(loop=0; loop<num_devices; loop++) {
    if ((!only_extended || (devices[loop].use >= IsXExtensionDevice)) &&
        ((!is_id && strcmp(devices[loop].name, name) == 0) ||
         (is_id && devices[loop].id == id))) {
        if (found) {
            fprintf(stderr,
                    "Warning: There are multiple devices named '%s'.\n"
                    "To ensure the correct one is selected, please use "
                    "the device ID instead.\n\n", name);
        return NULL;
        } else {
        found = &devices[loop];
        }
    }
    }
    return found;
}


int test(Display *display, char *deviceId)
{
    XDeviceInfo *info;

    Bool handle_proximity = True;

    info = find_device_info(display, deviceId, True);

    if(!info)
    {
      printf("unable to find device '%s'\n", deviceId);
      exit(1);
    }
    else
    {
      if(register_events(display, info, deviceId, handle_proximity))
         print_events(display);
      else
      {
        fprintf(stderr, "no event registered...\n");
        exit(1);
      }
    }

    return 0;
}


int main()
{
  Display *display;
  int event, error;

  // Инициируется указатель на текущий дисплей
  display = XOpenDisplay(NULL);
  if (display == NULL)
  {
    printf("Unable to connect to X server\n");
    exit(1);
  }

  // Проверяется наличие расширений
  if(!XQueryExtension(display, "XInputExtension", &xi_opcode, &event, &error))
  {
    printf("X Input extension not available.\n");
    exit(1);
  }

  // Проверяется версия расширения, она не должна быть нулевой
  if(!xinput_version(display))
  {
    printf("%s extension not available\n", INAME);
    exit(1);
  }

  char deviceId[10];
  sprintf(deviceId, "9");

  test(display, deviceId);

  XSync(display, False);
  XCloseDisplay(display);

  return 0;
}