i3/libi3/is_background_set.c
Uli Schlachter 60542da091
Do not "set" the wallpaper during startup (#4373)
"Set" the wallpaper during startup only sometimes

Since commit 4f5e0e7, i3 would take a screenshot and set that as the
background pixmap of the root window during startup. This is the easy
part of setting a proper X11 wallpaper.

The code in question was added because something either set the
background pixmap of the root window to NONE or the X11 server was
started with "-background none". This is apparently done by default by
e.g. gdm to avoid some flickering while the X11 server starts up.

This commit makes this code conditional: Only when no wallpaper is
detected is a screenshot taken.

Since I could not find any way to query the background of a window, a
more direct approach is taken to detect this situation: First, we find
some part of the root window that is not currently covered. Then we open
a white window there, close it again and grab a screenshot. If a
wallpaper is set, the X11 server will draw this wallpaper after the
window is closed and something else will be visible in the screenshot.

However, the wallpaper could have a white pixel at the tested position.
Thus, this procedure is repeated with a black window.

Only when this procedure produces two different pixel values is a
screenshot taken and set as the wallpaper.

Fixes: https://github.com/i3/i3/issues/4371
Fixes: https://github.com/i3/i3/issues/2869
Signed-off-by: Uli Schlachter <psychon@znc.in>
2021-05-20 21:37:35 +02:00

118 lines
4.2 KiB
C

/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
*/
#include "libi3.h"
#include <xcb/xcb_aux.h>
/**
* Find the region in the given window that is not covered by a mapped child
* window.
*/
static cairo_region_t *unobscured_region(xcb_connection_t *conn, xcb_window_t window,
uint16_t window_width, uint16_t window_height) {
cairo_rectangle_int_t rectangle;
cairo_region_t *region;
rectangle.x = 0;
rectangle.y = 0;
rectangle.width = window_width;
rectangle.height = window_height;
region = cairo_region_create_rectangle(&rectangle);
xcb_query_tree_reply_t *tree = xcb_query_tree_reply(conn, xcb_query_tree_unchecked(conn, window), NULL);
if (!tree) {
return region;
}
/* Get information about children */
uint16_t n_children = tree->children_len;
xcb_window_t *children = xcb_query_tree_children(tree);
xcb_get_geometry_cookie_t geometries[n_children];
xcb_get_window_attributes_cookie_t attributes[n_children];
for (int i = 0; i < n_children; i++) {
geometries[i] = xcb_get_geometry_unchecked(conn, children[i]);
attributes[i] = xcb_get_window_attributes_unchecked(conn, children[i]);
}
/* Remove every visible child from the region */
for (int i = 0; i < n_children; i++) {
xcb_get_geometry_reply_t *geom = xcb_get_geometry_reply(conn, geometries[i], NULL);
xcb_get_window_attributes_reply_t *attr = xcb_get_window_attributes_reply(conn, attributes[i], NULL);
if (geom && attr && attr->map_state == XCB_MAP_STATE_VIEWABLE) {
rectangle.x = geom->x;
rectangle.y = geom->y;
rectangle.width = geom->width;
rectangle.height = geom->height;
cairo_region_subtract_rectangle(region, &rectangle);
}
free(geom);
free(attr);
}
free(tree);
return region;
}
static void find_unobscured_pixel(xcb_connection_t *conn, xcb_window_t window,
uint16_t window_width, uint16_t window_height,
uint16_t *x, uint16_t *y) {
cairo_region_t *region = unobscured_region(conn, window, window_width, window_height);
if (cairo_region_num_rectangles(region) > 0) {
/* Return the top left pixel of the first rectangle */
cairo_rectangle_int_t rect;
cairo_region_get_rectangle(region, 0, &rect);
*x = rect.x;
*y = rect.y;
} else {
/* No unobscured area found */
*x = 0;
*y = 0;
}
cairo_region_destroy(region);
}
static uint32_t flicker_window_at(xcb_connection_t *conn, xcb_screen_t *screen, int16_t x, int16_t y, xcb_window_t window,
uint32_t pixel) {
xcb_create_window(conn, XCB_COPY_FROM_PARENT, window, screen->root, x, y, 10, 10,
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT,
XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT, (uint32_t[]){pixel, 1});
xcb_map_window(conn, window);
xcb_clear_area(conn, 0, window, 0, 0, 0, 0);
xcb_aux_sync(conn);
xcb_destroy_window(conn, window);
xcb_get_image_reply_t *img = xcb_get_image_reply(conn,
xcb_get_image_unchecked(conn, XCB_IMAGE_FORMAT_Z_PIXMAP, screen->root, x, y, 1, 1, ~0),
NULL);
uint32_t result = 0;
if (img) {
uint8_t *data = xcb_get_image_data(img);
uint8_t depth = img->depth;
for (int i = 0; i < MIN(depth, 4); i++) {
result = (result << 8) | data[i];
}
free(img);
}
return result;
}
bool is_background_set(xcb_connection_t *conn, xcb_screen_t *screen) {
uint16_t x, y;
find_unobscured_pixel(conn, screen->root, screen->width_in_pixels, screen->height_in_pixels, &x, &y);
xcb_window_t window = xcb_generate_id(conn);
uint32_t pixel1 = flicker_window_at(conn, screen, x, y, window, screen->black_pixel);
uint32_t pixel2 = flicker_window_at(conn, screen, x, y, window, screen->white_pixel);
return pixel1 == pixel2;
}