34

Under windows, the GUI thread usually call GetMessage to waiting for message, when another thread use PoseMessage put a message into the queue, then the GUI thread will return GetMessage (quit blocking).

Does anyone can tell me, when I use XNextEvent under XWindows to waiting for event, how can I "wakeup" the GUI thread in another thread. Is there some API like PoseMessage I can use ?.

Tom Wong
  • 343
  • 1
  • 3
  • 4

4 Answers4

50

No. This is why most UI frameworks (Gtk, KDE, etc) use custom main loops to be able to listen for more event sources.

Internally, XNextEvent uses a socket, so it calls select() to know when input is available. Call ConnectionNumber(display) to get the file descriptor that you need to pass select()

That allows you to listen for several file descriptors.

Sample code from http://www.linuxquestions.org/questions/showthread.php?p=2431345#post2431345

#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
        int num_ready_fds = select(x11_fd + 1, &in_fds, NULL, NULL, &tv);
        if (num_ready_fds > 0)
            printf("Event Received!\n");
        else if (num_ready_fds == 0)
            // Handle timer here
            printf("Timer Fired!\n");
        else
            printf("An error occured!\n");

        // Handle XEvents and flush the input 
        while(XPending(dis))
            XNextEvent(dis, &ev);
    }
    return(0);
}
Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • Hi Aaron this is an awesome solution. This is only catching events in that window, is it possible to listen to mouse events globally? And possible to block the mouse events with this method? – Noitidart Oct 23 '15 at 12:03
  • 1
    @Noitidart: Maybe but I can't answer that in a comment. Ask a new question, please and don't forget to give more details what exactly you need to achieve. – Aaron Digulla Oct 23 '15 at 15:43
  • Ah thanks so much I posted question here please: http://stackoverflow.com/questions/32262767/mouse-events-callback – Noitidart Oct 23 '15 at 19:25
  • Hi Aaron I've been doing some research and am trying to use `XMaskWindow,` are you familiar with that? But it's never giving me anything I posted about it here: http://stackoverflow.com/questions/33717635/xmaskevent-not-returning-cannot-do-with-xnextevent – Noitidart Nov 16 '15 at 17:32
  • 1
    The first argument to `select()` should be `x11_fd + 1`, not 1. From `man 2 select`: > `nfds` is the highest-numbered file descriptor in any of the three sets, plus 1. – Daniel Janus Jul 31 '16 at 19:59
  • when I am using above code it waits at select in first loop and not hitting XPending() line, I exit from program when timer fired – anil valmiki Sep 12 '19 at 05:48
  • @anilvalmiki A timer will cause an interrupt. Interrupts can't be handled with `select()`. You can't call X11 code while inside an interrupt handler. Ask a new question if you need help to make it work. – Aaron Digulla Sep 16 '19 at 09:13
  • 1
    Prefer `pselect()` or `poll()` over `select()`, since `pselect()` has better signal handling and can avoid race conditions with signals that can happen with `select()`. – 12431234123412341234123 Aug 25 '20 at 11:57
8

You can quit the blocking XNextEvent, by sending yourself a dummy event.

Window interClientCommunicationWindow;
Bool x11EventLoopActive = True;

// create a dummy window, that we can use to end the blocking XNextEvent call
interClientCommunicationWindow = XCreateSimpleWindow(dpy, root, 10, 10, 10, 10, 0, 0, 0);
XSelectInput(dpy, interClientCommunicationWindow, StructureNotifyMask);

XEvent event;
while(x11EventLoopActive) {
  XNextEvent(dpy, &event);
  ...
}

In another thread you can do this to end the loop:

x11EventLoopActive = False;
// push a dummy event into the queue so that the event loop has a chance to stop
XClientMessageEvent dummyEvent;
memset(&dummyEvent, 0, sizeof(XClientMessageEvent));
dummyEvent.type = ClientMessage;
dummyEvent.window = interClientCommunicationWindow;
dummyEvent.format = 32;
XSendEvent(dpy, interClientCommunicationWindow, 0, 0, (XEvent*)&dummyEvent);
XFlush(dpy);
Markus Kramer
  • 411
  • 5
  • 13
  • 15
    Danger, Will Robinson! Xlib is *not thread safe*! The above code will look like it works, but will in fact crash randomly and infrequently in a very hard-to-debug manner. Don't do it! – David Given Feb 23 '15 at 00:03
  • 1
    That will make it thread safe but it will still have a race condition. – elmindreda Feb 10 '19 at 16:38
  • Yes, I tried stuffing the queue on an interrupt (signal handler). I can vouch for the fact that it has intermittent failures. – Scott Franco Apr 28 '21 at 17:17
7

You should use : Bool XCheckMaskEvent(Display*, long, XEvent)

The XCheckMaskEvent function first searches the event queue, and then any events available on the server connection for the first event that matches the specified mask.

If it finds a match, XCheckMaskEvent removes that event, copies it into the specified XEvent structure, and returns True. The other events stored in the queue are not discarded.

If the event you requested is not available, XCheckMaskEvent returns False, and the output buffer will have been flushed.

whereisSQL
  • 638
  • 2
  • 13
  • 23
Kevin Mille
  • 71
  • 1
  • 1
  • 1
    I couldn't get that to work. I was waiting for PointerBarrier events, and unsure of what mask would be appropriate? What did work, was using if (XPending(_display) > 0); then XNextEvent; else continue; – Kevin May 11 '16 at 18:28
  • 1
    Thanks for this answer. @kevinf This worked for me: while(XCheckMaskEvent(display -1, &event)) { /*do things*/ } Some implementations mask is unsigned, so you may have to cast. On Ubuntu 18 mask is 32 bit signed, so just passing -1 covers any flag. – Beeeaaar Aug 23 '18 at 00:28
0

You can also run you own loop and poll pending. This code is written in Crystal but it's the same in C:

loop do
    while @display.pending > 0
        event = @display.next_event
        # do stuff with event
    end
    sleep 16.milliseconds
end

However, it's considerably more resource hungry this way (depending on the sleep time obviously), which is why the accepted answer might be a better idea.

phil294
  • 10,038
  • 8
  • 65
  • 98