X11 How to restore/raise another application window using xcb?
Asked Answered
E

1

5

I am writing a window list into my C application that shows all of the top level windows, including shaded, minimized, and on other desktops. I would like to restore unmapped (minimized) windows, raise the window, and switch to the window's desktop/workspace when one is selected.

I had written something to accomplish this in the past using Xlib. I used XSendEvent() to send a ClientMessage event of type _NET_ACTIVE_WINDOW followed by XMapRaised() previously, and it worked fairly well, but not perfectly.

I am now rewriting the application and decided to use XCB for the window list code rather than Xlib and hoped to create a better and more efficient implementation. XCB has no equivalent to XMapRaised() and xcb_map_window() does not seem to work for me. I've found a fair amount of documentation on creating and configuring new windows but very little that would be of use for implementing a window manager or utility programs like pagers, iconboxes, taskbars, etc. The generated documentation for XCB is rather vague about what some of the functions actually do as well. If anyone is aware of additional documentation that would be of value for this, that would be great, too.

EDIT:

I duplicated a portion of the old code that uses Xlib in a small utility program and it does actually do most of what I want and seems to work consistently:

#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;
}

...and then I unpacked the libX11 sources and confirmed that it does just wrap the XCB functions, but the wrappers use some libX11 data structures instead of the ones defined in XCB and there are so many macros being used that it's difficult to see exactly what is going on. This is the XCB equivalent I came up with, and it does not appear to work:

#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;
}

I'm still trying to figure this out.

Emmie answered 22/1, 2019 at 21:13 Comment(4)
It's kind of funny to switch to a library with a different API and vague documentation when nothing was broken in the first place :PBrachiate
The old project was built using a toolkit that is now way out of date, and the Xlib code did have some small problems like I said. It seems that using XCB will likely result in more efficient code. Xlib isn't very well documented, either, to be quite honest. If I understand correctly, the synchronous Xlib calls are all implemented now as wrappers for the asynchronous XCB functions. The listing code certainly looks better with xcb, but finding the proper requests for restoring and raising windows has been more difficult than I expected.Emmie
Just a hunch, have you tried moving the map_window above configure_window?Mispleading
@Mispleading I did that with the Xlib code and it does work better that way, but the XCB version isn't causing shaded/iconified windows to be mapped again, mapped windows to restack, or anything at all from what I can tell.Emmie
E
6

It seems that the code that I posted at the end of my question was correct, aside from atom_cache having a small bug in it. I also reordered the sequence that I map the window, raise the window, and make it the active window. I'm thinking that maybe I should post these solutions on github as xcb examples. I've written a couple of these small cli utilities for interacting with the xserver and window manager like this. Maybe they will be of some use to others...

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

There is a slight issue related to using 1 for the timestamp here. It would probably be better to query the window for _NET_WM_USER_TIME and use that value, as sometimes it gets ignored using 1 every time. I'm not sure how you could have more than one root window, either, but I still thought it was best to ask the server which root window it's on. Maybe a non xinerama multi-headed display... Anyway, problem solved.

Emmie answered 26/1, 2019 at 20:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.