我找到的几乎每个教程都告诉我为我的事件循环执行此操作:
XEvent event;
while (true)
{
XNextEvent(display, &event);
switch (event.type)
{
case Expose:
printf("Expose\n");
break;
default:
break;
}
}
但是,单击 X 关闭程序会导致出现此消息。
XIO: fatal IO error 11 (Resource temporarily unavailable) on X server ":0"
after 10 requests (10 known processed) with 0 events remaining.
这些例子建议使用无限循环,这对我来说确实很奇怪。这听起来不太自然,而且我的其他 X11 程序也没有这样做。于是我四处寻找。我找到了如何捕获窗口关闭事件。
Atom wmDeleteMessage = XInternAtom(mDisplay, "WM_DELETE_WINDOW", False);
XSetWMProtocols(display, window, &wmDeleteMessage, 1);
XEvent event;
bool running = true;
while (running)
{
XNextEvent(display, &event);
switch (event.type)
{
case Expose:
printf("Expose\n");
break;
case ClientMessage:
if (event.xclient.data.l[0] == wmDeleteMessage)
running = false;
break;
default:
break;
}
}
这有效。它退出时没有错误。 ......但我拒绝相信这是正常的做事方式。我的意思是,这是正确退出 X11 应用程序的唯一方法吗?仅仅为了捕捉关闭事件就需要做很多工作。如何创建“正确的”事件循环?为什么这桩亲密事件被如此深埋?我错过了什么?
问题出在 X Server 和 Window Manager 之间的通信。
当您调用
XCreateWindow
或 XCreateSimpleWindow
时,X 服务器会创建您的窗口(直到您通过调用 XMapWindow
将其明确映射到屏幕上才会显示它),然后窗口管理器负责附加所有装饰和窗口周围的按钮和系统菜单。
您可以自行调用
XDestroyWindow
来删除窗口,这通常意味着它只是从屏幕上消失,但您的程序仍在运行并且与 X Server 的连接仍然打开,因此您可以再发送一些请求。
当用户单击窗口管理器附加到窗口的那个小 X 按钮时,问题就开始了,因为它不是由 X 服务器创建的,并且决定接下来做什么不是他的事。现在一切都在窗口管理器手中。
如果窗口管理器只是在窗口上调用
XDestroyWindow
,如果您的应用程序想要捕获关闭事件以在窗口被销毁之前执行某些操作,则会导致问题。因此,X 服务器和窗口管理器之间已经建立了约定来处理此过程。
大多数窗口管理器的默认行为是销毁窗口并关闭与X服务器的连接,因为这是大多数窗口管理器用户所期望的:当他们关闭窗口时,程序将结束(并且与 X 服务器的连接将随着窗口关闭而关闭)。然后,当你尝试调用
XCloseDisplay(display)
时,它会导致你提到的 IO 错误,因为与服务器的连接已经关闭,并且 display
结构无效。
这里摘录自 Xlib 文档,解释了这一点:
如果用户要求删除客户端的顶级窗口之一,则选择不在
属性中包含WM_DELETE_WINDOW
的客户端可能会与服务器断开连接。WM_PROTOCOLS
是的,如果他们没有将其隐藏在文档中那么深,那就太好了:-P 但当你已经找到它时,幸运的是它也提示了解决方案。
如果您想要不同的行为(即从窗口管理器捕获关闭事件),您需要使用
WM_DESTROY_WINDOW
协议。
文档的另一个摘录:
客户端(通常是具有多个顶级窗口的客户端,其服务器连接必须在删除某些顶级窗口后仍然存在)应在每个此类窗口的
属性中包含原子WM_DELETE_WINDOW
。他们将收到如上所述的WM_PROTOCOLS
事件,其ClientMessage
字段为data[0]
。WM_DELETE_WINDOW
我也遇到了同样的错误,我想确切地知道是什么原因造成的以及为什么。我花了一些时间才弄清楚并在文档中找到正确的解释,所以我将我的解释放在这里以节省其他不知情的人的时间。
X11中没有“退出按钮”或“应用程序”或“关闭事件”之类的东西。这是设计使然。
窗口装饰、退出按钮和许多我们依赖的其他东西都没有内置到 X11 中。它们是在核心 X11 之上实现的。负责
wmDeleteMessage
的特定约定集的名称是 ICCCM,查一下。
Xlib仅处理核心X11协议。那里没有内置的关闭事件。
有一些工具包可以使处理 ICCCM 和所有其他未内置于 X11 中的东西变得更容易(GTK、wxWindows、Qt...),您可能想使用其中之一。
int waitWindowClose(Display* display, Window window) {
XEvent event;
Atom delete = XInternAtom(display, "WM_DELETE_WINDOW", False);
XSetWMProtocols(display, window, &delete, 1);
while (event.type != ClientMessage && event.xclient.data.l[0] != delete) {
XNextEvent(display, &event);
}
}