From 60542da0916a62a6daede9293090660b05d4679e Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Thu, 20 May 2021 21:37:35 +0200 Subject: [PATCH] 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 --- RELEASE-NOTES-next | 17 +++++ include/libi3.h | 13 ++++ libi3/is_background_set.c | 117 +++++++++++++++++++++++++++++++++++ libi3/screenshot_wallpaper.c | 27 ++++++++ meson.build | 2 + src/main.c | 33 +++++----- 6 files changed, 192 insertions(+), 17 deletions(-) create mode 100644 libi3/is_background_set.c create mode 100644 libi3/screenshot_wallpaper.c diff --git a/RELEASE-NOTES-next b/RELEASE-NOTES-next index 91a386fb5..53fe23293 100644 --- a/RELEASE-NOTES-next +++ b/RELEASE-NOTES-next @@ -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 │ diff --git a/include/libi3.h b/include/libi3.h index 15f637046..a93b3ff96 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -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); diff --git a/libi3/is_background_set.c b/libi3/is_background_set.c new file mode 100644 index 000000000..b3b6e6a63 --- /dev/null +++ b/libi3/is_background_set.c @@ -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 + +/** + * 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; +} diff --git a/libi3/screenshot_wallpaper.c b/libi3/screenshot_wallpaper.c new file mode 100644 index 000000000..2c115a9e1 --- /dev/null +++ b/libi3/screenshot_wallpaper.c @@ -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); +} diff --git a/meson.build b/meson.build index 76e220419..d86e9dcdf 100644 --- a/meson.build +++ b/meson.build @@ -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') diff --git a/src/main.c b/src/main.c index 5590bf4b4..c3825ec80 100644 --- a/src/main.c +++ b/src/main.c @@ -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__)