4

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.

Christian Hujer
  • 17,035
  • 5
  • 40
  • 47

2 Answers2

5

According to these pages

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);
}
Community
  • 1
  • 1
dvhh
  • 4,724
  • 27
  • 33
  • Hi there @dvhh i was trying to do some x11 to watch for mouse events (and comunicate from another thread to start/top, so was going to use your ConnectionNumber/pselect technique). I am trying to do this from another thread, so i have already done XThreadsInit. So creating this simple window wiht `XCreateSimpleWindow`, does this receive system wide mouse events? Or just mouse events that happen while this window is focused? Thanks! – Noitidart Oct 19 '15 at 12:41
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.

Segmented
  • 795
  • 6
  • 13
  • Ah sorry, its been a while =) You could use `XCheckWindowEvent` first. I'll update my answer with that thanks. – Segmented Mar 12 '15 at 03:18
  • I'm sure this isn't what you were hoping for btw... I wish there was an xlib function for this myself - you may wish to take a look at xcb which is a much more modern X windows library if your not doing anything too fancy (such as OpenGL). – Segmented Mar 12 '15 at 03:22
  • Yes this isn't what I was hoping for. I don't like polling, not even with intervals. The comment of dvhh hinted on using file descriptors. I'll use that because then I don't need to poll. Still +1 for your answer. – Christian Hujer Mar 12 '15 at 03:27
  • @Christian Hujer agreed! The fd solution looks a bit hackish but Xlib is hackish to begin with, whatever works! =) – Segmented Mar 12 '15 at 03:28