如何停止Xlib的XNextEvent阻塞

34

在Windows环境下,GUI线程通常使用GetMessage等待消息,在另一个线程使用PostMessage将消息放入队列后,GUI线程将返回GetMessage(退出阻塞)。

有人能告诉我,在XWindows下使用XNextEvent等待事件时,如何在另一个线程中“唤醒”GUI线程?是否有类似于PostMessage的API可以使用?

4个回答

50
不行。这就是为什么大多数UI框架(如Gtk,KDE等)使用自定义主循环以便能够监听更多的事件源的原因。
在内部,XNextEvent使用套接字,因此它调用select()来知道何时有输入可用。调用ConnectionNumber(display)来获取您需要传递给select()的文件描述符
这使您可以侦听多个文件描述符。
示例代码来自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);
}

1
@Noitidart:也许是这样,但我无法在评论中回答。请提出一个新问题,并不要忘记给出更多细节,以便明确你需要实现什么。 - Aaron Digulla
啊,非常感谢。我在这里发了个问题,请看:https://dev59.com/BI7ea4cB1Zd3GeqPCow0 - Noitidart
嗨,亚伦,我正在做一些研究,尝试使用XMaskWindow,你熟悉这个吗?但它从来没有给我任何东西,我在这里发布了相关信息:http://stackoverflow.com/questions/33717635/xmaskevent-not-returning-cannot-do-with-xnextevent - Noitidart
1
select()的第一个参数应该是x11_fd + 1,而不是1。来自man 2 select
nfds是三个集合中任何一个文件描述符的最高编号加1。
- Daniel Janus
1
建议使用pselect()poll()而不是select(),因为pselect()具有更好的信号处理能力,并且可以避免在select()中可能发生的信号竞争条件。 - 12431234123412341234123
显示剩余3条评论

8
您可以通过发送虚拟事件来退出阻塞的XNextEvent。
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);
  ...
}

在另一个线程中,您可以执行以下操作来结束循环:
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);

15
危险,威尔·罗宾逊!Xlib不支持多线程!上述代码看起来似乎正常工作,但实际上会以难以调试的方式随机且不经常地崩溃。请勿这样做! - David Given
1
这将使其线程安全,但仍可能存在竞争条件。 - elmindreda
是的,我尝试在中断(信号处理程序)上填充队列。我可以证明它有间歇性故障。 - Scott Franco

7
你应该使用: Bool XCheckMaskEvent(Display*, long, XEvent)
XCheckMaskEvent函数首先搜索事件队列,然后搜索与指定掩码匹配的服务器连接上可用的任何事件,以找到第一个匹配的事件。
如果找到匹配项,XCheckMaskEvent将删除该事件,将其复制到指定的XEvent结构中,并返回True。存储在队列中的其他事件不会被丢弃。
如果你请求的事件不可用,XCheckMaskEvent将返回False,并清空输出缓冲区。

1
我无法让它正常工作。我一直在等待PointerBarrier事件,不确定哪个掩码是合适的?可行的方法是使用if (XPending(_display) > 0); then XNextEvent; else continue; - Kevin
1
谢谢你的回答。@kevinf 这个方法对我很有效:while(XCheckMaskEvent(display -1, &event)) { /do things/ }有些实现中,掩码是无符号的,因此您可能需要进行转换。在Ubuntu 18上,掩码是32位有符号的,因此只需传递-1即可覆盖任何标志。 - Beeeaaar

0
你也可以运行自己的循环并轮询 pending 。 这段代码是用Crystal编写的,但在C中是相同的:
loop do
    while @display.pending > 0
        event = @display.next_event
        # do stuff with event
    end
    sleep 16.milliseconds
end

然而,这种方式需要更多的资源(显然取决于睡眠时间),这就是为什么接受的答案可能是一个更好的想法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接