How to stop an X11 event loop gracefully asynchronously

我与影子孤独终老i 提交于 2019-12-07 12:46:29

问题


I have a small X11 application which has two threads. In one thread, I am listening to X11 events using XGrabKey() and then in a loop XNextEvent(). The other thread is doing other stuff and is not related to X11.

Here's the code of the relevant thread:

#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);
}

The goal is that the other thread can tell this thread to stop.

Now the problem is that setting loop = false in the other thread will of course not terminate this thread, at least not immediately. This thread is stuck in XNextEvent() because that's a blocking call. So here's my question: What is the standard pattern how to get XNextEvent() to return?

I guess I need the other Thread to use XSendEvent(), but I couldn't find any hints on how to do that. I wouldn't even know which message type would be appropriate. Would it be ClientMessage? Something else? I actually tried sending a ClientMessage from the other thread, but I got the following error message:

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

Here's the relevant code snippet from the other thread that I tried and triggered the error (display and root are initialized by the first thread):

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

Note that the other thread doesn't need any X11 code by itself. The only purpose why the other thread would use X11 is to tell this thread to terminate.

Please keep in mind that there is no window in context. The root window of course does not count, as this is only for globally catching keys. So, destroying the window is not a solution.


回答1:


According to these pages

  • how to quit the blocking of xlib's XNextEvent
  • http://www.linuxquestions.org/questions/programming-9/xnextevent-select-409355/#post2431460

The best solution would be perform a select on the X event queue socket getting the socket is achieved by

#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);
}



回答2:


Use XCheckWindowEvent in your message loop to see if there are any messages (followed by XNextEvent if one exists), and since this is non-blocking you can proceed to use pthread_cond_timedwait or whatever equivalent may exist in the threading library you are using. That way the blocking is in your hands rather than xlib's. If it times out it will check for another event, and then resume to waiting for your thread.



来源:https://stackoverflow.com/questions/29001189/how-to-stop-an-x11-event-loop-gracefully-asynchronously

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!