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>
This commit is contained in:
Uli Schlachter 2021-05-20 21:37:35 +02:00 committed by GitHub
parent fcae64f7fd
commit 60542da091
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 192 additions and 17 deletions

View File

@ -6,6 +6,23 @@
This is i3 v4.19. This version is considered stable. All users of i3 are
strongly encouraged to upgrade.
Background/wallpaper workaround:
Some login managers (e.g. gdm) start the X11 server with the -background none
flag. When this flag is set, a background needs to be explicitly set later in
the X11 session, otherwise stale copies of closed windows remain visible on the
X11 root window (symptom looks like “my terminal window is not closing”).
i3 works around this situation by setting a screenshot as background when
starting. Any background you set before starting i3 (e.g. in your Xsession) or
after starting i3 (e.g. via exec statements in the i3 config) will be visible.
A downside of this workaround is that if you have any windows already open in
your X11 session, those will be part of the screenshot.
To fix this issue, starting in v4.20, i3 detects whether the -background none
option is enabled and only then sets a screenshot as background.
┌────────────────────────────┐
│ Changes in i3 v4.20 │

View File

@ -655,3 +655,16 @@ int create_socket(const char *filename, char **out_socketpath);
*
*/
bool path_exists(const char *path);
/**
* Grab a screenshot of the screen's root window and set it as the wallpaper.
*/
void set_screenshot_as_wallpaper(xcb_connection_t *conn, xcb_screen_t *screen);
/**
* Test whether the screen's root window has a background set.
*
* This opens & closes a window and test whether the root window still shows the
* content of the window.
*/
bool is_background_set(xcb_connection_t *conn, xcb_screen_t *screen);

117
libi3/is_background_set.c Normal file
View File

@ -0,0 +1,117 @@
/*
* 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;
}

View File

@ -0,0 +1,27 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
*/
#include "libi3.h"
void set_screenshot_as_wallpaper(xcb_connection_t *conn, xcb_screen_t *screen) {
uint16_t width = screen->width_in_pixels;
uint16_t height = screen->height_in_pixels;
xcb_pixmap_t pixmap = xcb_generate_id(conn);
xcb_gcontext_t gc = xcb_generate_id(conn);
xcb_create_pixmap(conn, screen->root_depth, pixmap, screen->root, width, height);
xcb_create_gc(conn, gc, screen->root,
XCB_GC_FUNCTION | XCB_GC_PLANE_MASK | XCB_GC_FILL_STYLE | XCB_GC_SUBWINDOW_MODE,
(uint32_t[]){XCB_GX_COPY, ~0, XCB_FILL_STYLE_SOLID, XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS});
xcb_copy_area(conn, screen->root, pixmap, gc, 0, 0, 0, 0, width, height);
xcb_change_window_attributes(conn, screen->root, XCB_CW_BACK_PIXMAP, (uint32_t[]){pixmap});
xcb_free_gc(conn, gc);
xcb_free_pixmap(conn, pixmap);
xcb_flush(conn);
}

View File

@ -349,6 +349,8 @@ libi3srcs = [
'libi3/string.c',
'libi3/ucs2_conversion.c',
'libi3/nonblock.c',
'libi3/screenshot_wallpaper.c',
'libi3/is_background_set.c',
]
if not cdata.get('HAVE_STRNDUP')

View File

@ -977,24 +977,23 @@ int main(int argc, char *argv[]) {
xcb_ungrab_server(conn);
if (autostart) {
LOG("This is not an in-place restart, copying root window contents to a pixmap\n");
/* When the root's window background is set to NONE, that might mean
* that old content stays visible when a window is closed. That has
* unpleasant effect of "my terminal (does not seem to) close!".
*
* There does not seem to be an easy way to query for this problem, so
* we test for it: Open & close a window and check if the background is
* redrawn or the window contents stay visible.
*/
LOG("This is not an in-place restart, checking if a wallpaper is set.\n");
xcb_screen_t *root = xcb_aux_get_screen(conn, conn_screen);
uint16_t width = root->width_in_pixels;
uint16_t height = root->height_in_pixels;
xcb_pixmap_t pixmap = xcb_generate_id(conn);
xcb_gcontext_t gc = xcb_generate_id(conn);
xcb_create_pixmap(conn, root->root_depth, pixmap, root->root, width, height);
xcb_create_gc(conn, gc, root->root,
XCB_GC_FUNCTION | XCB_GC_PLANE_MASK | XCB_GC_FILL_STYLE | XCB_GC_SUBWINDOW_MODE,
(uint32_t[]){XCB_GX_COPY, ~0, XCB_FILL_STYLE_SOLID, XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS});
xcb_copy_area(conn, root->root, pixmap, gc, 0, 0, 0, 0, width, height);
xcb_change_window_attributes(conn, root->root, XCB_CW_BACK_PIXMAP, (uint32_t[]){pixmap});
xcb_flush(conn);
xcb_free_gc(conn, gc);
xcb_free_pixmap(conn, pixmap);
if (is_background_set(conn, root)) {
LOG("A wallpaper is set, so no screenshot is necessary.\n");
} else {
LOG("No wallpaper set, copying root window contents to a pixmap\n");
set_screenshot_as_wallpaper(conn, root);
}
}
#if defined(__OpenBSD__)