从X11应用程序清除退出?

时间:2016-03-15 14:29:53

标签: c linux x11

这是一个使用X11启动和关闭窗口的简约linux应用程序。当用户在终端中按ctrl-c或点击"关闭"时,我想要一个干净的退出(按照valgrind)。 Windows管理器上的按钮。我花了一段时间才找到这些信息,所以我想我会发布工作代码并提出几个问题。我使用信号来捕捉" 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;
}

4 个答案:

答案 0 :(得分:3)

首先,你的信号处理程序是错误的。仔细阅读 signal(7)(尤其是关于异步信号安全功能的部分)&amp; POSIX signal.h文档。在实践中,信号处理程序应该设置一些class myViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, InstatunesAppDelegate { 标志以在其他地方进行测试,因此将volatile sig_atomic_t字段声明为done并让您的信号处理程序只设置该标志而不再执行任何操作(在特别是,将volatile sig_atomic_t done;移到信号处理程序之外。你也可能受到Qt recommendations about Unix signals的启发(例如,从信号处理程序中使用XSendEvent自我,pipe,并在write周围有一个事件循环poll read 1}}那个管道和/或X11插座)。但是信号处理程序无法调用(以可靠的方式)XSendEvent 异步信号安全。另请参阅syscalls(2)Advanced Linux Programming

此外,您还要遵循详细EWMH详细here约定。这很复杂,很无聊,几乎可以证明使用某些现有的 X11 toolkit,例如QtGTK(或者其他类似libsdllibsfmlfox-toolkitfltk,...)。顺便说一句,这些工具包(至少Qt&amp; GTK)可能已转换为Wayland。为了具有挑衅性,X11应用程序正在成为传统,并且很难正确编码(没有支持某些工具包,尊重X11惯例,如EWMH)。

BTW,如今,很少有应用程序使用X11核心协议及其绘图原语(例如XDrawLineXDrawArcXDrawText,....)。大多数工具包都在客户端pixmaps中绘制客户端(这是Wayland也在使用的模型)。

编写一个没有工具包的X11应用程序(符合EwMH)非常困难(需要几年时间;当你完成X11时,可能会被W​​ayland取代)。

答案 1 :(得分:1)

  

3)是否有其他方法可以捕获Windows管理器事件   除了Atoms?

没有。原子用于“注册”这些事件(在这种情况下只是字符串)并为它们分配一个数字。这可以防止大量硬编码,并使系统非常灵活。

答案 2 :(得分:0)

版本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;
}

答案 3 :(得分:0)

这是在ctrl-c或更高版本中与事件循环结合正常处理c++11的一种方法:

...

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