这是一个使用 X11 打开和关闭窗口的简约 Linux 应用程序。当用户在终端中按下 ctrl-c 或单击 Windows 管理器上的“关闭”按钮时,我想要一个干净的退出(根据 valgrind)。找到这些信息花了一段时间,所以我想我会发布工作代码并提出一些问题。我使用信号来捕获“ctrl-c”,并使用 Atom 来捕获关闭按钮的点击。
问题:
1) 这是从 X11 应用程序完成干净退出的正确/最佳方法吗?
2) 我可以在信号处理程序代码中发送事件吗?有人说不...
3) 除了 Atoms 之外,还有其他捕获窗口管理器事件的方法吗?
// use "gcc main.c -lX11" to compile
#include <string.h>
#include <signal.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
// global data
struct {
Display* display;
Window* window;
char done;
} global;
void SignalHandler(int n) {
switch(n) {
case SIGINT: // user pressed ctrl-c from terminal
global.done = 1; // tell event loop to exit
XClientMessageEvent event; // dummy event to wake up XNextEvent
memset(&event, 0, sizeof(XClientMessageEvent));
event.type = ClientMessage;
event.format = 32; // not used but cannot be zero
XSendEvent(global.display, *global.window, 0, 0, (XEvent*)&event);
XFlush(global.display); // make event happen immediately
}
}
void RegisterSignals() { signal(SIGINT, SignalHandler); }
int main () {
memset(&global, 0, sizeof(global));
RegisterSignals();
Display* display = XOpenDisplay(NULL);
global.display = display;
Visual* visual = DefaultVisual(display, 0);
int depth = DefaultDepth(display, 0);
XSetWindowAttributes frame_attr;
frame_attr.background_pixel = XWhitePixel(display, 0);
Window window = XCreateWindow(display, XRootWindow(display, 0),
0, 0, 400, 300, 5, depth, InputOutput, visual, CWBackPixel, &frame_attr);
global.window = &window;
XStoreName(display, window, "Title");
XSelectInput(display, window, 0xFFFF);
XFontStruct* font = XLoadQueryFont(display, "10x20");
XGCValues gc_values;
gc_values.font = font->fid;
gc_values.foreground = XBlackPixel(display, 0);
GC gc = XCreateGC(display, window, GCFont + GCForeground, &gc_values);
// Windows Manager Stuff (like clicking close button)
Atom wmDeleteWindow = XInternAtom(display, "WM_DELETE_WINDOW", True);
XSetWMProtocols(display, window, &wmDeleteWindow, 1);
XMapWindow(display, window);
XEvent event;
while (!global.done) {
XNextEvent(display, (XEvent *)&event); // blocks until event
switch (event.type) {
// other events go here...
case ClientMessage:
// user clicked close button on window
if (event.xclient.data.l[0] == wmDeleteWindow) { global.done = 1; }
}
}
// Cleanup
XFreeGC(display, gc);
XFreeFont(display, font);
XCloseDisplay(display);
return 0;
}
最佳答案
首先,您的信号处理程序有问题。 仔细阅读 signal(7) (特别是关于异步信号安全函数的部分)& POSIX signal.h文档。实际上,信号处理程序应该设置一些 volatile sig_atomic_t
标志以在其他地方进行测试,因此将 done
字段声明为 volatile sig_atomic_t done;
并且您的信号处理程序只是设置该标志,什么都不做(特别是,将 XSendEvent
移到信号处理程序之外)。您也可以从 Qt recommendations about Unix signals 中得到启发(例如,使用一个 pipe
给自己,从信号处理程序中 write
,并在 poll
周围有一个事件循环,它可以 read
该管道和/或 X11 套接字)。但是信号处理程序不能调用(以可靠的方式)XSendEvent
,这不是异步信号安全的。另见 syscalls(2)和 Advanced Linux Programming .
此外,您想关注 EWMH约定详解here .这非常复杂和乏味,实际上足以证明使用一些现有的 X11 toolkit喜欢Qt或 GTK (或者可能是其他东西,比如 libsdl 、 libsfml 、 fox-toolkit 、 fltk ……)。顺便说一句,这些工具包(至少是 Qt 和 GTK)可能正在过渡到 Wayland .值得一提的是,X11 应用程序正在成为遗留问题,并且很难正确编码(没有一些尊重 X11 约定的工具包的支持,例如 EWMH)。
顺便说一句,如今,很少有应用程序使用 X11 核心协议(protocol)及其绘图原语(例如 XDrawLine、XDrawArc、XDrawText、....)。大多数工具包都在客户端像素图中绘制(这是 Wayland 也在使用的模型)。
在没有工具包的情况下编写 X11 应用程序(符合 EwMH)是非常困难的(将花费您数年;到您完成时,X11 可能已被 Wayland 取代)。
关于从 X11 应用程序干净退出?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36014153/