如何强制一个窗口处理来自一台设备的所有事件,同时允许正常处理来自其他所有设备的所有其他事件?

时间:2018-10-23 19:29:32

标签: x11 keyboard-events

我有一个应用程序,用作Linux下使用X11的演示文稿的控制系统。我有一个USB演示遥控器,它充当一个非常小的键盘(四个按钮:Page Up,Page Down和另外两个按钮),可用于在演示文稿中前进和后退。我希望我的演示文稿应用程序能够从此遥控器接收所有事件,而不管鼠标焦点在哪里。但是,如果当前窗口焦点位于演示应用程序上,我也希望能够接收正常的鼠标和键盘事件。使用XIGrabDevice(),无论当前焦点如何,我都可以从演示应用程序中的遥控器接收所有事件,但是在抓斗处于活动状态时,我无法从鼠标或键盘接收任何事件。

1 个答案:

答案 0 :(得分:0)

我最终建立了一个单独的程序来捕获遥控器的键,然后将这些键中继到我的主程序中。我这样做是因为原始程序使用的是较旧的XInput扩展,而我需要使用较新的XInput2扩展,并且它们并不能很好地结合在一起。这是一些C ++代码(它不执行任何错误检查,但应在真实程序中完成):

// Open connection to X Server
Display *dpy = XOpenDisplay(NULL);

// Get opcode for XInput Extension; we'll need it for processing events
int xi_opcode = -1, event, error;
XQueryExtension(dpy, "XInputExtension", &xi_opcode, &event, &error);

// Allow user to select a device
int num_devices;
XIDeviceInfo *info = XIQueryDevice(dpy, XIAllDevices, &num_devices);
for (int i = 0; i < num_devices; ++i)
{
    XIDeviceInfo *dev = &info[i];
    std::cout << dev->deviceid << " " << dev->name << "\n";
}
XIFreeDeviceInfo(info);

std::cout << "Enter the device number: ";
std::string input;
std::cin >> input;
int deviceid = -1;
std::istringstream istr(input);
istr >> deviceid;

// Create an InputOnly window that is just used to grab events from this device
XSetWindowAttributes attrs;
long attrmask = 0;
memset(&attrs, 0, sizeof(attrs));
attrs.override_redirect = True;   // Required to grab device
attrmask |= CWOverrideRedirect;

Window win = XCreateWindow(dpy, DefaultRootWindow(dpy), 0, 0, 1, 1, 0, 0, InputOnly, CopyFromParent, attrmask, &attrs);

// Make window without decorations
PropMotifWmHints hints;
hints.flags = 2;
hints.decorations = 0;
Atom property = XInternAtom(dpy, "_MOTIF_WM_HINTS", True);
XChangeProperty(dpy, win, property, property, 32, PropModeReplace, (unsigned char *)&hints, PROP_MOTIF_WM_HINTS_ELEMENTS);

// We are interested in key presses and hierarchy changes. We also need to get key releases or else we get an infinite stream of key presses.
XIEventMask evmasks[1];
unsigned char mask0[XIMaskLen(XI_LASTEVENT)];
memset(mask0, 0, sizeof(mask0));
XISetMask(mask0, XI_KeyPress);
XISetMask(mask0, XI_KeyRelease);
XISetMask(mask0, XI_HierarchyChanged);
evmasks[0].deviceid = XIAllDevices;
evmasks[0].mask_len = sizeof(mask0);
evmasks[0].mask = mask0;
XISelectEvents(dpy, win, evmasks, 1);

XMapWindow(dpy, win);
XFlush(dpy);

XEvent ev;
bool grab_success = false, grab_changed;
while (1)
{
    grab_changed = false; 
    if (!grab_success)
    {
        XIEventMask masks[1];
        unsigned char mask0[XIMaskLen(XI_LASTEVENT)];
        memset(mask0, 0, sizeof(mask0));
        XISetMask(mask0, XI_KeyPress);
        masks[0].deviceid = deviceid;
        masks[0].mask_len = sizeof(mask0);
        masks[0].mask = mask0;
        XIGrabDevice(dpy, deviceid, win, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, XIOwnerEvents, masks);
    }
    XNextEvent(dpy, &ev);
    XGenericEventCookie *cookie = &ev.xcookie;
    if (cookie->type == GenericEvent && cookie->extension == xi_opcode && XGetEventData(dpy, cookie))
    {
        if (cookie->evtype == XI_KeyPress)
        {
            XIDeviceEvent *de = (XIDeviceEvent*)cookie->data;
            std::cout << "found XI_KeyPress event: keycode " << de->detail << "\n";
        }
        else if (cookie->evtype == XI_HierarchyChanged)
        {
            // Perhaps a device was unplugged. The client is expected to re-read the list of devices to find out what changed.
            std::cout << "found XI_HierarchyChanged event.\n";
            grab_changed = true;
        }
        XFreeEventData(dpy, cookie);
    }

    if (grab_changed)
    {
        XIUngrabDevice(dpy, deviceid, CurrentTime);
        grab_success = false;
        break;
    }
}

我发现以下链接很有帮助:

Peter Hutterer在XInput2上的6部分博客:1 2 3 4 5 6

此博客条目对于确定将cookie-> data指针转换为哪个类很有用,具体取决于cookie-> evtype:7