2016-03-15 80 views
0

这是一个简约的Linux应用程序,用于使用X11启动和关闭窗口。当用户在终端上按ctrl-c或点击Windows管理器上的“关闭”按钮时,我想要一个干净的出口(按valgrind)。我花了一段时间才找到这些信息,所以我想我会发布工作代码以及提出几个问题。我使用信号捕捉“ctrl-c”,Atom用于捕捉关闭按钮单击。从X11应用程序中清除退出?

问题:
1)这是完成从X11应用程序干净退出的正确/最佳方式?
2)我可以在信号处理程序代码中发送一个事件吗?有些人说不......
3)除Atom之外,还有其他方法可用于捕获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; 
} 
+0

为什么你的代码只需使用Xlib的应用程序? –

+0

@BasileStarynkevitch:因为OP想要? (学习的目的,也许这是一个简单的,定制的框架的启动等) – datenwolf

+0

的确,为什么不......但是,这是一个非常漫长的旅程...... –

回答

2

首先,你的信号处理器是越野车。仔细阅读​​(特别是异步信号安全功能部分)& POSIX signal.h文档。在实践中,信号处理程序应该设置一些volatile sigatomic_t标志在其他地方进行测试,所以声明done字段volatile sigatomic_t done;和具有信号处理程序只设置该标志,什么也不做更多的(特别是移动​​之外的信号处理程序)。您也可以从Qt recommendations about Unix signals(例如,使用pipe自己,write来自信号处理程序,并具有围绕poll的事件循环,其可以是管道和/或X11插座的read)启发。但信号处理器不能调用(以可靠的方式)​​这是而不是异步信号安全。另见syscalls(2)Advanced Linux Programming

此外,你想要遵循EWMH约定详细here。这是复杂的和足够的无聊使用一些现有 X11 toolkitQtGTK(或者别的东西,像libsdllibsfmlfox-toolkitfltk,...),以实际证明。顺便说一下,这些工具包(至少Qt & GTK)可能会过渡到Wayland。为了挑衅,X11应用程序正在成为传统,并以正确的代码相当困难(没有一些工具包尊重X11公约,如EWMH的支持)。顺便说一下,现在很少有应用程序使用X11核心协议及其绘图原语(例如XDrawLine,XDrawArc,,...)。大多数工具包在客户端pixmaps(这是Wayland也使用的模型)中绘制在客户端。

编码没有工具包的X11应用程序(符合EWMH)是非常困难的(你得用好几年,你的时间已经做了X11可能已被取代的Wayland)。

+0

暂且X11仍然是更好的支持比Wayland和Wayland留下了很多互操作性问题。在X11赛道上有超过30年的橡胶撞击的经验,Wayland完全抛弃了赛道。例如,与XInput2(这是一个复杂的野兽)相比,Wayland中的输入处理仍然受限得多。 – datenwolf

+0

你是对的,但从头开始编码的X11应用程序是真的* *耗时(如果你想要那个是正确的,例如EWMH认可标准) –

+0

我会考虑信号的更多。好信息。我不能移动信号处理程序之外的XSendEvent(),因为当XNextEvent()被阻塞时XCloseDisplay()挂起(从不返回),因此无法完全退出(valgrind报告内存泄漏)。 –

1

3)除Atom之外,还有其他方法可用于捕捉窗口管理器事件 吗?

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

0

版本2.工作得很好...谢谢罗勒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; 
}