如何优雅地异步停止 X11 事件循环

How to stop an X11 event loop gracefully asynchronously

我有一个有两个线程的小型 X11 应用程序。 在一个线程中,我正在使用 XGrabKey() 监听 X11 事件,然后在一个循环中 XNextEvent()。另一个线程正在做其他事情,与 X11 无关。

这是相关主题的代码:

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/XF86keysym.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>

volatile bool loop = true;

void keyGrab(void)
{
    Display *display = XOpenDisplay(0);
    Window root = DefaultRootWindow(display);
    int keycode = XKeysymToKeycode(display, XF86XK_AudioPlay);

    XGrabKey(display, keycode, AnyModifier, root, False, GrabModeAsync, GrabModeAsync);
    XSelectInput(display, root, KeyPressMask);

    while (loop) {
        XEvent event;
        XNextEvent(display, &event);
        switch (event.type) {
        case KeyPress: puts("Play key pressed"); break;
        }
    }

    XUngrabKey(display, keycode, AnyModifier, root);

    XCloseDisplay(display);
}

目标是另一个线程可以告诉这个线程停止。

现在的问题是在另一个线程中设置loop = false当然不会终止这个线程,至少不会立即终止。该线程卡在 XNextEvent() 中,因为这是一个阻塞调用。所以这是我的问题:如何从 XNextEvent() 到 return 的标准模式是什么?

我想我需要另一个线程才能使用 XSendEvent(),但我找不到任何关于如何做到这一点的提示。我什至不知道哪种消息类型是合适的。会是ClientMessage吗?还有别的吗?我实际上尝试从另一个线程发送 ClientMessage,但我收到以下错误消息:

X Error of failed request:  BadValue (integer parameter out of range for operation)
  Major opcode of failed request:  25 (X_SendEvent)
  Value in failed request:  0x0
  Serial number of failed request:  12
  Current serial number in output stream:  12

这是我尝试并触发错误的另一个线程的相关代码片段(displayroot 由第一个线程初始化):

XEvent event;
memset(&event, 0, sizeof(event));
event.type = ClientMessage;
XSendEvent(display, root, False,  0, &event);

请注意,另一个线程本身不需要任何 X11 代码。另一个线程使用 X11 的唯一目的是告诉该线程终止。

请记住上下文中没有 window。根 window 当然不算数,因为这仅用于全局捕获密钥。所以,摧毁window不是解决办法。

在你的消息循环中使用 XCheckWindowEvent 来查看是否有任何消息(如果存在则跟随 XNextEvent),因为这是非阻塞的你可以继续使用 pthread_cond_timedwait 或其他您正在使用的线程库中可能存在等效项。这样阻塞就在你手中而不是 xlib 的手中。如果超时,它将检查另一个事件,然后继续等待您的线程。

根据这些页面

最好的解决方案是在 X 事件队列套接字上执行 select 获取套接字是通过

实现的
#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

Display *dis;
Window win;
int x11_fd;
fd_set in_fds;

struct timeval tv;
XEvent ev;

int main() {
    dis = XOpenDisplay(NULL);
    win = XCreateSimpleWindow(dis, RootWindow(dis, 0), 1, 1, 256, 256,\
        0, BlackPixel (dis, 0), BlackPixel(dis, 0));

    // You don't need all of these. Make the mask as you normally would.
    XSelectInput(dis, win, 
        ExposureMask | KeyPressMask | KeyReleaseMask | PointerMotionMask |
        ButtonPressMask | ButtonReleaseMask  | StructureNotifyMask 
    );

    XMapWindow(dis, win);
    XFlush(dis);

    // This returns the FD of the X11 display (or something like that)
    x11_fd = ConnectionNumber(dis);

    // Main loop
    while(1) {
        // Create a File Description Set containing x11_fd
        FD_ZERO(&in_fds);
        FD_SET(x11_fd, &in_fds);

        // Set our timer.  One second sounds good.
        tv.tv_usec = 0;
        tv.tv_sec = 1;

        // Wait for X Event or a Timer
        if (select(x11_fd+1, &in_fds, 0, 0, &tv))
            printf("Event Received!\n");
        else
            // Handle timer here
            printf("Timer Fired!\n");

        // Handle XEvents and flush the input 
        while(XPending(dis))
            XNextEvent(dis, &ev);
    }
    return(0);
}