4

I am attempting to create a thumbnailer (for i3wm on linux) which generates an icon from a screenshot for all the available windows. The aim being to replicate something like how windows uses Alt-Tab to produce a pane of windows to choose from;

enter image description here

The current prototype uses pygtk to generate the dialog, and it creates thumbnails of the windows in the _NET_CLIENT_LIST using XGetImage where the windows have been redirected using XCompositeRedirectWindow. It works pretty well except for that images captured from windows which are browsers, (e.g. firefox, chrome, electron) are mangled, or wrong;

enter image description here

Basically, tools like xwd, scrot and import don't work for the hidden windows in i3, presumably because they are unmapped. So I have pulled some code together to create the thumbnails. The core of it is based on an example using XCompositeRedirectWindow from X11/extensions/Xcomposite.h from here.

My attempt at creating a short example is here;
https://gist.github.com/tolland/4bb1e97db258b92618adfb783ce66fac

it can be compiled with;

$ gcc example.c -lX11 -lXcomposite -lXrender -lpng -o example

and then to output png files for each of the windows;

$ mkdir -p /tmp/png_out &&  ./example

will produce pngs for each of the images

window found was 58720312 
found window name for 58720312  :  build : sudo  
filename /tmp/png_out/58720312_test3.png 
window found was 79691781 
found window name for 79691781  :  .xsession-errors - glogg  
filename /tmp/png_out/79691781_test3.png 
window found was 62914576 
found window name for 62914576  :  Edit - Stack Overflow - Mozilla Firefox  
filename /tmp/png_out/62914576_test3.png

So the code I am currently using to walk the _NET_CLIENT_LIST is this;

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <libpng16/png.h>

#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/Xcomposite.h>
#include <X11/extensions/Xrender.h>
//Compile hint: gcc -shared -O3 -lX11 -fPIC -Wl,-soname,prtscn -o prtscn.so prtscn.c

typedef int bool;
#define true 1
#define false 0

const int DEBUG = 0;

void getScreen2(const int, const int, const int, const int, const XID);
void write_png_for_image(XImage *image, XID xid, int width, int height,
        char *filename);
typedef int (*handler)(Display *, XErrorEvent *);
XID getWindows(Display *display, Window parent, Window window, XID xid,
        int depth);

int main() {

    Display *display = XOpenDisplay(NULL);
    Window root = DefaultRootWindow(display);

    uint nwindows;
    Window root_return, parent_return, *windows;

    Atom a = XInternAtom(display, "_NET_CLIENT_LIST", true);
    Atom actualType;

    int format;
    unsigned long numItems, bytesAfter;
    unsigned char *data = 0;

    int status = XGetWindowProperty(display, root, a, 0L, (~0L),
    false,
    AnyPropertyType, &actualType, &format, &numItems, &bytesAfter, &data);

    char* window_name_return;

    if (status >= Success && numItems) {
        long *array = (long*) data;
        for (long k = 0; k < numItems; k++) {
            Window window = (Window) array[k];

            //not finding chrome window name
            printf("window found was %d \n", window);
            if (XFetchName(display, window, &window_name_return)) {
                printf("found window name for %d  :  %s  \n", window,
                        window_name_return);
            }

            //XMapWindow(display, parent);
            XMapWindow(display, window);

            XWindowAttributes attr;

            Status status = XGetWindowAttributes(display, window, &attr);
            if (status == 0)
                printf("Fail to get window attributes!\n");

            getScreen2(0, 0, attr.width, attr.height, window);

        }
        XFree(data);
    }

    return 0;
}

which calls this function to map the window and call XCompositeRedirectWindow;

void getScreen2(const int xx, const int yy, const int W, const int H,
        const XID xid) {

    Display *display = XOpenDisplay(NULL);
    Window root = DefaultRootWindow(display);

    // turn on --sync to force error on correct method
    //https://www.x.org/releases/X11R7.6/doc/man/man3/XSynchronize.3.xhtml
    XSynchronize(display, True);

    int counter = 1;

    // select which xid to operate on, the winder or its parent
    //XID xwid = fparent;
    XID xwid = xid;

    // Requests the X server to direct the hierarchy starting at window to off-screen storage
    XCompositeRedirectWindow(display, xwid, CompositeRedirectAutomatic);

    XWindowAttributes attr;
    Status status = XGetWindowAttributes(display, xwid, &attr);

    int width = attr.width;
    int height = attr.height;
    int depth = attr.depth;

    Pixmap xc_pixmap = XCompositeNameWindowPixmap(display, xwid);
    if (!xc_pixmap) {
        printf("xc_pixmap not found\n");
    }

    //XWriteBitmapFile(display, "test1.xpm", pixmap, W, H, -1, -1);

    XRenderPictFormat *format = XRenderFindVisualFormat(display, attr.visual);

    XRenderPictureAttributes pa;

    pa.subwindow_mode = IncludeInferiors;

    Picture picture = XRenderCreatePicture(display, xwid, format,
    CPSubwindowMode, &pa);

    char buffer[50];
    int n;
    int file_counter = 1;

    n = sprintf(buffer, "/tmp/%d_test%d.xpm", xid, file_counter++);
    XWriteBitmapFile(display, buffer, xc_pixmap, W, H, -1, -1);

    n = sprintf(buffer, "/tmp/%d_test%d.xpm", xid, file_counter++);
    XWriteBitmapFile(display, buffer, xid, W, H, -1, -1);

    XImage *image = XGetImage(display, xid, 0, 0, W, H, AllPlanes, ZPixmap);
    if (!image) {
        printf("XGetImage failed\n");
    }

    char filename[255];
    int n2;

    n2 = sprintf(filename, "/tmp/png_out/%d_test%d.png", xid, file_counter++);

    printf("filename %s \n", filename);
    write_png_for_image(image, xid, W, H, filename);

    //XFree(image);
    XDestroyImage(image);
    XDestroyWindow(display, root);
    XCloseDisplay(display);
}

and then this to write out the png;

void write_png_for_image(XImage *image, XID xid, int width, int height,
        char *filename) {

    int code = 0;
    FILE *fp;
    png_structp png_ptr;
    png_infop png_info_ptr;
    png_bytep png_row;

    char buffer[50];
    int n;

    n = sprintf(buffer, filename, xid);

// Open file
    fp = fopen(buffer, "wb");
    if (fp == NULL) {
        fprintf(stderr, "Could not open file for writing\n");
        code = 1;
    }

// Initialize write structure
    png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (png_ptr == NULL) {
        fprintf(stderr, "Could not allocate write struct\n");
        code = 1;
    }

// Initialize info structure
    png_info_ptr = png_create_info_struct(png_ptr);
    if (png_info_ptr == NULL) {
        fprintf(stderr, "Could not allocate info struct\n");
        code = 1;
    }

// Setup Exception handling
    if (setjmp(png_jmpbuf (png_ptr))) {
        fprintf(stderr, "Error during png creation\n");
        code = 1;
    }

    png_init_io(png_ptr, fp);

// Write header (8 bit colour depth)
    png_set_IHDR(png_ptr, png_info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB,
    PNG_INTERLACE_NONE,
    PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

// Set title
    char *title = "Screenshot";
    if (title != NULL) {
        png_text title_text;
        title_text.compression = PNG_TEXT_COMPRESSION_NONE;
        title_text.key = "Title";
        title_text.text = title;
        png_set_text(png_ptr, png_info_ptr, &title_text, 1);
    }

    png_write_info(png_ptr, png_info_ptr);

// Allocate memory for one row (3 bytes per pixel - RGB)
    png_row = (png_bytep) malloc(3 * width * sizeof(png_byte));

    unsigned long red_mask = image->red_mask;
    unsigned long green_mask = image->green_mask;
    unsigned long blue_mask = image->blue_mask;

// Write image data
//int xxx, yyy;
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            unsigned long pixel = XGetPixel(image, x, y);
            unsigned char blue = pixel & blue_mask;
            unsigned char green = (pixel & green_mask) >> 8;
            unsigned char red = (pixel & red_mask) >> 16;
            png_byte *ptr = &(png_row[x * 3]);
            ptr[0] = red;
            ptr[1] = green;
            ptr[2] = blue;
        }
        png_write_row(png_ptr, png_row);
    }

// End write
    png_write_end(png_ptr, NULL);

// Free
    fclose(fp);
    if (png_info_ptr != NULL)
        png_free_data(png_ptr, png_info_ptr, PNG_FREE_ALL, -1);
    if (png_ptr != NULL)
        png_destroy_write_struct(&png_ptr, (png_infopp) NULL);
    if (png_row != NULL)
        free(png_row);

}

However when the windows is browser based, the image is mangled like so; enter image description here

Do I need to do something special to get browser windows working?

References;
https://www.talisman.org/~erlkonig/misc/x11-composite-tutorial/
Get a screenshot of a window that is cover or not visible or minimized with Xcomposite extension for X11

Tom
  • 3,324
  • 1
  • 31
  • 42
  • FYI your program segfaults on my machine at `unsigned char outdata[attr.width * attr.height * 3];` The screen is 2560x1440, not big by modern standards but enough to blow the stack. Once I remove the VLA and replace it with normal heap allocation, all windows are dumped normally. Don't use VLAs. – n. m. could be an AI Jan 01 '18 at 16:03
  • im not sure thats actually used.. it was a legacy of the original code sample... the function `write_png_for_image` is using this `png_row = (png_bytep) malloc(3 * width * sizeof(png_byte));` – Tom Jan 03 '18 at 07:42
  • I can only look at the code you have posted. If that's not what you are actually using, I can't help. The line in question is in `main` right before the call to `getScreen2`. – n. m. could be an AI Jan 03 '18 at 07:51
  • Apologies... I fixed up the code to remove those lines, as they were doing nothing. (other than allocate a bunch of unused memory...) basically main() loops over all the windows calling `getScreen2(0, 0, attr.width, attr.height, window);` and that calls for each loop `write_png_for_image(image, xid, W, H, filename);` which uses `png_row = (png_bytep) malloc(3 * width * sizeof(png_byte));` for the memory that the original example was using `outdata` for – Tom Jan 03 '18 at 07:56
  • What I mean is, if you look at the original code, its passing `outdata` into `GetScreen2(xxx)` but it doesn't use it for anything – Tom Jan 03 '18 at 07:58
  • https://gist.github.com/tolland/1819263833b15c34ca9fccd730b08b9b gcc test2.c -O3 -lX11 -lXcomposite -lXrender -lpng -o test2.out && mkdir -p /tmp/png_out && ./test2.out – Tom Jan 03 '18 at 08:07
  • Not sure why this could happen. The program is working for me. Can you check whether mangled windows are on a different visual from all the rest, or all are on the same visual? – n. m. could be an AI Jan 03 '18 at 08:49

0 Answers0