X11如何使用xcb恢复/引发另一个应用程序窗口?

时间:2019-01-22 21:13:13

标签: c x11 xcb

我正在向我的C应用程序中编写一个窗口列表,其中显示了所有顶级窗口,包括阴影,最小化窗口以及其他桌面。我想还原未映射(最小化)的窗口,抬起该窗口,并在选择一个窗口后切换到该窗口的桌面/工作区。

过去,我已经使用Xlib编写了一些东西来完成此任务。我以前使用XSendEvent()发送类型为_NET_ACTIVE_WINDOW的ClientMessage事件,然后发送XMapRaised(),它运行得很好,但效果并不理想。

我现在正在重写应用程序,并决定将XCB用于窗口列表代码而不是Xlib,并希望创建更好,更高效的实现。 XCB没有等效于XMapRaised()的功能,而xcb_map_window()似乎不适合我。我已经找到了大量有关创建和配置新窗口的文档,但是很少用于实现窗口管理器或诸如寻呼机,图标框,任务栏等实用程序的文档。为XCB生成的文档对于哪些内容相当含糊某些功能实际上也是如此。如果有人知道其他有价值的文档,那也很好。

编辑:

我在一个小型实用程序中复制了使用Xlib的一部分旧代码,它实际上完成了我想要的大部分工作,并且似乎可以始终如一地工作:

#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>

int main(int argc, char **argv) {
    Display *display = XOpenDisplay("");
    Window rootwin = XDefaultRootWindow(display);
    if (argc < 2)
    {
        printf("usage: %s windowid\n", argv[0]);
        return 0;
    }
    Window window = (Window)strtoul(argv[1], NULL, 0);
    printf("switch to window: 0x%lx\n", window);
    Atom ActiveWindowAtom = XInternAtom(display, "_NET_ACTIVE_WINDOW", False);
    XEvent xev;
    xev.xclient.type = ClientMessage;
    xev.xclient.window = window;
    xev.xclient.message_type = ActiveWindowAtom;
    xev.xclient.format = 32;
    xev.xclient.data.l[0] = 1U;
    xev.xclient.data.l[1] = 1U;
    xev.xclient.data.l[2] = 0U;
    xev.xclient.data.l[3] = 0U;
    xev.xclient.data.l[4] = 0U;
    XSendEvent(display, rootwin, False, SubstructureRedirectMask, &xev);
    XMapRaised(display, window);
    XCloseDisplay(display);
    return 0;
}

...然后我解压缩了libX11源,并确认它确实包装了XCB函数,但是包装器使用了一些libX11数据结构而不是XCB中定义的结构,并且使用了太多的宏,因此很难看看到底发生了什么。这是我想出的等效XCB,但似乎没有用:

#include <stdlib.h>
#include <stdio.h>
#include <xcb/xcb.h>
#include "atom_cache.h"

int main(int argc, char **argv) {
    xcb_connection_t *connection;
    const xcb_setup_t *setup;
    xcb_screen_iterator_t screen_iter;
    xcb_screen_t *screen;
    xcb_window_t rootwin, window;
    xcb_void_cookie_t void_cookie;
    xcb_client_message_event_t client_message_event;

    if (argc < 2)
    {
        printf("usage: %s windowid\n", argv[0]);
        return 0;
    }

    window = (xcb_window_t)strtoul(argv[1], NULL, 0);
    printf("switch to window: 0x%x\n", window);

    connection = xcb_connect(NULL, NULL);
    setup = xcb_get_setup(connection);
    screen_iter = xcb_setup_roots_iterator(setup);
    screen = screen_iter.data;
    rootwin = screen->root;

    // send _net_active_window request
    client_message_event.response_type = XCB_CLIENT_MESSAGE;
    client_message_event.format = 32;
    client_message_event.sequence = 0;
    client_message_event.window = window;
    client_message_event.type = get_atom(connection, "_NET_ACTIVE_WINDOW");
    client_message_event.data.data32[0] = 1UL; // source: 1=application 2=pager
    client_message_event.data.data32[1] = 1UL; // timestamp
    client_message_event.data.data32[2] = 0UL; // my currently active window?
    client_message_event.data.data32[3] = 0UL;
    client_message_event.data.data32[4] = 0UL;

    void_cookie = xcb_send_event(connection, 1, rootwin, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *)&client_message_event);

    uint32_t values[] = { XCB_STACK_MODE_ABOVE };
    xcb_configure_window(connection, window, XCB_CONFIG_WINDOW_STACK_MODE, values);
    xcb_map_window(connection, window);

    xcb_flush(connection);
    xcb_disconnect(connection);
    return 0;
}

我仍在设法解决这个问题。

1 个答案:

答案 0 :(得分:2)

除了atom_cache中有一个小错误之外,我在问题末尾发布的代码似乎是正确的。我还重新排序了映射窗口,升高窗口并将其设为活动窗口的顺序。我在想,也许我应该将这些解决方案作为xcb示例发布在github上。我已经编写了几个这样的小型cli实用程序,用于与xserver和窗口管理器进行交互,如下所示。也许它们会对其他人有所帮助...

xcb_switchto.c:

#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <xcb/xcb.h>

int main(int argc, char **argv) {
    xcb_connection_t *connection;
    xcb_window_t rootwin, window;
    xcb_intern_atom_cookie_t atom_cookie;
    xcb_intern_atom_reply_t *atom_reply;
    xcb_atom_t net_active_window;
    xcb_query_tree_cookie_t qtree_cookie;
    xcb_query_tree_reply_t *qtree_reply;
    xcb_void_cookie_t void_cookie;
    xcb_client_message_event_t client_message_event;
    xcb_generic_error_t *err;

    if (argc < 2)
    {
        printf("usage: %s windowid\n", argv[0]);
        return EXIT_FAILURE;
    }

    window = (xcb_window_t)strtoul(argv[1], NULL, 0);
    printf("switch to window: 0x%x\n", window);

    // connect to X server
    connection = xcb_connect(NULL, NULL);

    // get _NET_ACTIVE_WINDOW atom from X server
    atom_cookie = xcb_intern_atom(connection, 0, 18, "_NET_ACTIVE_WINDOW");
    if (atom_reply = xcb_intern_atom_reply(connection, atom_cookie, &err)) {
        net_active_window = atom_reply->atom;
        free(atom_reply);
    } else if (err) {
        printf("xcb_intern_atom failed with error code %d\n", err->error_code);
        return EXIT_FAILURE;
    }

    // get the window's root window.  can there really be more than one?
    qtree_cookie = xcb_query_tree(connection, window);
    if (qtree_reply = xcb_query_tree_reply(connection, qtree_cookie, &err)) {
        rootwin = qtree_reply->root;
    } else if (err) {
        printf("xcb_query_tree failed with error code %d\n", err->error_code);
        return EXIT_FAILURE;
    }

    // map the window first
    xcb_map_window(connection, window);

    // now raise (restack) the window
    uint32_t values[] = { XCB_STACK_MODE_ABOVE };
    xcb_configure_window(connection, window, XCB_CONFIG_WINDOW_STACK_MODE, values);

    // and, finally, make it the active window
    client_message_event.response_type = XCB_CLIENT_MESSAGE;
    client_message_event.format = 32;
    client_message_event.sequence = 0;
    client_message_event.window = window;
    client_message_event.type = net_active_window;
    client_message_event.data.data32[0] = 1UL; // source: 1=application 2=pager
    client_message_event.data.data32[1] = 1UL; // timestamp
    client_message_event.data.data32[2] = 0UL; // currently active window (none)
    client_message_event.data.data32[3] = 0UL;
    client_message_event.data.data32[4] = 0UL;
    void_cookie = xcb_send_event(connection, 1, rootwin, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *)&client_message_event);

    // probably unnecessary
    xcb_flush(connection);
    // close the connection
    xcb_disconnect(connection);
    return EXIT_SUCCESS;
}

这里有一个与使用1作为时间戳有关的小问题。在窗口中查询_NET_WM_USER_TIME并使用该值可能会更好,因为有时每次使用1都会将其忽略。我也不确定如何拥有多个根窗口,但我仍然认为最好询问服务器它在哪个根窗口上。也许是非xinerama多头显示器...无论如何,问题解决了。