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:
@ -6,6 +6,23 @@
|
|||||||
This is i3 v4.19. This version is considered stable. All users of i3 are
|
This is i3 v4.19. This version is considered stable. All users of i3 are
|
||||||
strongly encouraged to upgrade.
|
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 │
|
│ Changes in i3 v4.20 │
|
||||||
|
@ -655,3 +655,16 @@ int create_socket(const char *filename, char **out_socketpath);
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
bool path_exists(const char *path);
|
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
117
libi3/is_background_set.c
Normal 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;
|
||||||
|
}
|
27
libi3/screenshot_wallpaper.c
Normal file
27
libi3/screenshot_wallpaper.c
Normal 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);
|
||||||
|
}
|
@ -349,6 +349,8 @@ libi3srcs = [
|
|||||||
'libi3/string.c',
|
'libi3/string.c',
|
||||||
'libi3/ucs2_conversion.c',
|
'libi3/ucs2_conversion.c',
|
||||||
'libi3/nonblock.c',
|
'libi3/nonblock.c',
|
||||||
|
'libi3/screenshot_wallpaper.c',
|
||||||
|
'libi3/is_background_set.c',
|
||||||
]
|
]
|
||||||
|
|
||||||
if not cdata.get('HAVE_STRNDUP')
|
if not cdata.get('HAVE_STRNDUP')
|
||||||
|
33
src/main.c
33
src/main.c
@ -977,24 +977,23 @@ int main(int argc, char *argv[]) {
|
|||||||
xcb_ungrab_server(conn);
|
xcb_ungrab_server(conn);
|
||||||
|
|
||||||
if (autostart) {
|
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);
|
xcb_screen_t *root = xcb_aux_get_screen(conn, conn_screen);
|
||||||
uint16_t width = root->width_in_pixels;
|
if (is_background_set(conn, root)) {
|
||||||
uint16_t height = root->height_in_pixels;
|
LOG("A wallpaper is set, so no screenshot is necessary.\n");
|
||||||
xcb_pixmap_t pixmap = xcb_generate_id(conn);
|
} else {
|
||||||
xcb_gcontext_t gc = xcb_generate_id(conn);
|
LOG("No wallpaper set, copying root window contents to a pixmap\n");
|
||||||
|
set_screenshot_as_wallpaper(conn, root);
|
||||||
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 defined(__OpenBSD__)
|
#if defined(__OpenBSD__)
|
||||||
|
Reference in New Issue
Block a user