Reputation: 17975
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.
Upvotes: 4
Views: 2947
Reputation: 4750
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);
}
Upvotes: 5
Reputation: 795
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.
Upvotes: 2