how to quit the blocking of xlib's XNextEvent
Asked Answered
K

4

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 ?.

Knotted answered 21/12, 2011 at 15:33 Comment(0)
B
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);
}
Bern answered 21/12, 2011 at 16:21 Comment(8)
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?Agribusiness
@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.Bern
Ah thanks so much I posted question here please: https://mcmap.net/q/451385/-mouse-events-callbackAgribusiness
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: #33718135Agribusiness
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.Custos
when I am using above code it waits at select in first loop and not hitting XPending() line, I exit from program when timer firedV1
@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.Bern
Prefer pselect() or poll() over select(), since pselect() has better signal handling and can avoid race conditions with signals that can happen with select().Needful
C
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);
Callihan answered 9/2, 2012 at 21:40 Comment(3)
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!Evangelize
That will make it thread safe but it will still have a race condition.Blither
Yes, I tried stuffing the queue on an interrupt (signal handler). I can vouch for the fact that it has intermittent failures.Taraxacum
M
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.

Misstep answered 14/9, 2015 at 19:0 Comment(2)
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;Caper
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.Akmolinsk
V
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.

Vermin answered 18/7, 2022 at 1:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.