Bob Shaffer
Bob Shaffer

Reputation: 655

Request image from X11 compositing WM in C or C++

I need to request and retrieve an image of a window from the X server or the WM (I believe the WM does the actual compositing). I need to be able to get an image of the window even if it is obscured by another window or located on another workspace (but still mapped). I need to use C or C++ and would like to use XCB or Xlib. I think I also have Gtk+ development tools installed but it's been a while since I used them.

I have tried using xcb_composite_name_window_pixmap() with no success (XCB errors).

I tried using the render extension to render the window to a picture which produced no errors, but I still needed to get the image loaded into my program's memory.

I tried to use xcb_get_image to get the picture which, IIRC, failed (bad resource I believe).

I tried to copy the picture to a pixmap and use xcb_get_image on that. I was able to download what appeared to be an image, but it looked more like random regions of the screen than the actual window.

In short, I'm just making guesses at this point.

I'm having a difficult time finding organized and complete documentation. The most recent publications I can find on X11, XCB, or Xlib is from 1994 (The O'Reilly book on Xlib and/or X11R6) which I doubt has much, if anything, valid on these extensions. Most of the man pages and online documentation has a lot of "TODO: explain this" and/or function descriptions like "deliver a request to the X server". Any help that anyone can provide will be of use to me.

I'm currently running compiz for my WM and emerald for window decorations and nothing else standard in terms of a "desktop environment". I'm working on some utility applications for a custom desktop that I plan to release when they are ready. I would like whatever I make to work with some other WMs but, if it requires special codepaths for each, I can add the others down the line.

EDIT: I originally had added a non-working example here, and then a stripped down working example, which has all been moved to an answer as suggested in the comments.

Upvotes: 2

Views: 1382

Answers (1)

Bob Shaffer
Bob Shaffer

Reputation: 655

I now have a working example that I will post here:

#include <xcb/xcb.h>
#include <xcb/xproto.h>
#include <xcb/composite.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char **argv) {
    if (argc < 2) {
        fprintf(stderr, "usage: %s windowId\n", argv[0]);
        return EXIT_FAILURE;
    }
    xcb_window_t req_win_id = strtoul(argv[1], NULL, 0);
    xcb_connection_t *connection = xcb_connect(NULL, NULL);
    xcb_generic_error_t *err = NULL, *err2 = NULL;

    xcb_composite_query_version_cookie_t comp_ver_cookie = xcb_composite_query_version(connection, 0, 2);
    xcb_composite_query_version_reply_t *comp_ver_reply = xcb_composite_query_version_reply(connection, comp_ver_cookie, &err);
    if (comp_ver_reply)
    {
        if (comp_ver_reply->minor_version < 2) {
            fprintf(stderr, "query composite failure: server returned v%d.%d\n", comp_ver_reply->major_version, comp_ver_reply->minor_version);
            free(comp_ver_reply);
            return EXIT_FAILURE;
        }
        free(comp_ver_reply);
    }
    else if (err)
    {
        fprintf(stderr, "xcb error: %d\n", err->error_code);
        free(err);
        return EXIT_FAILURE;
    }

    const xcb_setup_t *setup = xcb_get_setup(connection);
    xcb_screen_iterator_t screen_iter = xcb_setup_roots_iterator(setup);
    xcb_screen_t *screen = screen_iter.data;
    // request redirection of window
    xcb_composite_redirect_window(connection, req_win_id, XCB_COMPOSITE_REDIRECT_AUTOMATIC);
    int win_h, win_w, win_d;

    xcb_get_geometry_cookie_t gg_cookie = xcb_get_geometry(connection, req_win_id);
    xcb_get_geometry_reply_t *gg_reply = xcb_get_geometry_reply(connection, gg_cookie, &err);
    if (gg_reply) {
        win_w = gg_reply->width;
        win_h = gg_reply->height;
        win_d = gg_reply->depth;
        free(gg_reply);
    } else {
        if (err) {
            fprintf(stderr, "get geometry: XCB error %d\n", err->error_code);
            free(err);
        }
        return EXIT_FAILURE;
    }

    // create a pixmap
    xcb_pixmap_t win_pixmap = xcb_generate_id(connection);
    xcb_composite_name_window_pixmap(connection, req_win_id, win_pixmap);

    // get the image
    xcb_get_image_cookie_t gi_cookie = xcb_get_image(connection, XCB_IMAGE_FORMAT_Z_PIXMAP, win_pixmap, 0, 0, win_w, win_h, (uint32_t)(~0UL));
    xcb_get_image_reply_t *gi_reply = xcb_get_image_reply(connection, gi_cookie, &err);
    if (gi_reply) {
        int data_len = xcb_get_image_data_length(gi_reply);
        fprintf(stderr, "data_len = %d\n", data_len);
        fprintf(stderr, "visual = %u\n", gi_reply->visual);
        fprintf(stderr, "depth = %u\n", gi_reply->depth);
        fprintf(stderr, "size = %dx%d\n", win_w, win_h);
        uint8_t *data = xcb_get_image_data(gi_reply);
        fwrite(data, data_len, 1, stdout);
        free(gi_reply);
    }
    return EXIT_SUCCESS;
}

It was a case of overdoing it. I got the idea that I should use Xrender from somewhere and started pulling tons of data from that extension that I didn't really need. But the real problem I had was that the pixmap given to xcb_composite_name_window_pixmap should not be created first, but rather I just needed the new id for it. It turns out that it is really as simple as I expected it to be. All you need is the Window ID and the size, do some checks to make sure composite is there and a good version (>=0.2 in this case), and call xcb_composite_redirect_window, xcb_composite_name_window_pixmap (with a generated pixmap id), and xcb_get_image (+reply,data_len,data) in that order, and you have the image.

Upvotes: 5

Related Questions