[英]Clean exit from X11 application?
這是一個使用 X11 打開和關閉窗口的簡約 linux 應用程序。 當用戶在終端中按 ctrl-c 或單擊 Windows 管理器上的“關閉”按鈕時,我想要一個干凈的退出(根據 valgrind)。 找到這些信息花了一段時間,所以我想我會發布工作代碼並提出一些問題。 我使用信號來捕捉“ctrl-c”並使用 Atom 來捕捉關閉按鈕的點擊。
問題:
1) 這是從 X11 應用程序完全退出的正確/最佳方法嗎?
2) 我可以在信號處理程序代碼中發送事件嗎? 有人說不...
3) 除了 Atoms 之外,還有其他方法可以捕獲 Windows 管理器事件嗎?
// 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關於Unix信號的建議也可能會激發您的靈感(例如,使用pipe
進行自我處理,從信號處理程序中進行write
,並在poll
周圍創建一個事件循環,可以read
該管道和/或X11套接字)。 但是信號處理程序無法調用(以可靠的方式) XSendEvent
,這不是異步信號安全的。 另請參見syscalls(2)和高級Linux編程 。
另外,您想遵循此處詳述的EWMH約定。 這很復雜並且很無聊,足以使用一些現有的 X11 工具包(例如Qt或GTK) (或也許其他的東西,例如libsdl , libsfml , fox-toolkit , fltk等)來證明其合理性。 順便說一句,這些工具包(至少Qt和GTK)可能正在過渡到Wayland 。 令人鼓舞的是,X11應用程序已成為傳統,並且很難正確編寫代碼( 沒有某些尊重X11約定的工具包(如EWMH)的支持)。
順便說一句, 近來 ,很少有應用程序使用X11核心協議及其繪圖原語(例如XDrawLine , XDrawArc , XDrawText等)。 大多數工具包都是在客戶端pixmaps(Wayland也使用的模型)中的客戶端上繪制的。
不使用工具包對X11應用程序進行編碼(符合EwMH)非常困難(將花費您數年時間;完成X11時可能已被Wayland取代)。
3)除Atoms之外,還有其他方法可以捕獲Windows Manager事件嗎?
否。原子用於“注冊”這些事件(在這種情況下只是字符串),並為它們分配一個數字。 這樣可以避免使用大量的硬編碼數字,並使系統非常靈活。
版本2。效果很好...感謝Basil和datenwolf的建議。 任何意見將是巨大的。
// Minimal X11 window with clean exit
// 1) "ctrl-c" from terminal or "kill -s SIGINT" exits app
// 2) user clicks close button to exit app
// Use -lX11 to comile with gcc
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
struct {
int pip[2]; // extra pipe for event loop
sig_atomic_t done; // flag to stop the event loop
} global;
void SignalHandler(int n) {
write(global.pip[1], &global.done, 1); // wake up select in X11 event loop
switch(n) {
case SIGINT: // user pressed ctrl-c from terminal (or kill -s SIGINT)
global.done = 1; // tell event loop to exit
}
}
void Initialize() {
struct sigaction sigact; // no race conditions like signal()
memset(&sigact, 0, sizeof(sigact));
sigact.sa_handler = &SignalHandler;
sigaction(SIGINT, &sigact, NULL);
memset(&global, 0, sizeof(global));
pipe(global.pip);
write(global.pip[1], &global.done, 1); // wake up select on first iteration of X11 event loop
}
int main () {
Initialize();
Display* display = XOpenDisplay(NULL);
int xfd = ConnectionNumber(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);
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);
fd_set set_read, set_save;
FD_ZERO(&set_save); // select modifies fd_set, re-initialize
FD_SET(xfd, &set_save);
FD_SET(global.pip[0], &set_save);
int max_fd = xfd > global.pip[0] ? xfd : global.pip[0];
XEvent event;
while (!global.done) {
//printf("Blocking on select...\n");
memcpy(&set_read, &set_save, sizeof(set_save));
select(max_fd+1, &set_read, NULL, NULL, NULL); // block on X11 and pipe
if(!XPending(display)) {
// this code only executes once on startup and once on exit
char a[1]; read(global.pip[0], a, 1); continue;
}
XNextEvent(display, (XEvent *)&event); // doesn't block because we ensure an event
//printf("Processing event...\n");
switch ( event.type ) {
// don't forget your other events...
case ClientMessage:
// user clicked close button on window
if (event.xclient.data.l[0] == wmDeleteWindow) { global.done = 1; }
}
}
//printf("Cleaning up...\n");
close(global.pip[0]);
close(global.pip[1]);
XFreeGC(display, gc);
XFreeFont(display, font);
XCloseDisplay(display);
return 0;
}
這是在c++11
或更高版本中與事件循環結合正常處理ctrl-c
一種方法:
...
volatile sig_atomic_t running = true;
void stop(int x)
{
running = false;
// Output a newline after the "^C" output
std::cout << std::endl;
}
...
XEvent e;
signal(SIGINT, stop);
while (running) {
// This check exists so that event-checking can be non-blocking if the program is
// interrupted by SIGINT
if (XPending(display) == 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
continue;
}
XNextEvent(display, &e);
switch (e.type) {
...
XPending
用於在處理事件之前檢查是否有排隊的事件。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.