簡體   English   中英

從 X11 應用程序干凈退出?

[英]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 工具包(例如QtGTK) (或也許其他的東西,例如libsdllibsfmlfox-toolkitfltk等)來證明其合理性。 順便說一句,這些工具包(至少Qt和GTK)可能正在過渡到Wayland 令人鼓舞的是,X11應用程序已成為傳統,並且很難正確編寫代碼( 沒有某些尊重X11約定的工具包(如EWMH)的支持)。

順便說一句, 近來 ,很少有應用程序使用X11核心協議及其繪圖原語(例如XDrawLineXDrawArcXDrawText等)。 大多數工具包都是在客戶端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用於在處理事件之前檢查是否有排隊的事件。
  • 可以根據應用程序需要如何響應來調整睡眠時間。
  • 該程序將在退出前處理未決事件。
  • 這里的完整示例: sakemake / examples / x11

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM