diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml
deleted file mode 100644
index a16f8d8bac..0000000000
--- a/.builds/freebsd.yml
+++ /dev/null
@@ -1,28 +0,0 @@
-image: freebsd/latest
-packages:
- - libev
- - libxcb
- - meson
- - pkgconf
- - cmake
- - xcb-util-renderutil
- - xcb-util-image
- - pixman
- - uthash
- - libconfig
- - libglvnd
- - libepoxy
- - dbus
- - pcre
-sources:
- - https://github.com/yshui/picom
-tasks:
- - setup: |
- cd picom
- CPPFLAGS="-I/usr/local/include" meson setup -Dunittest=true --werror build
- - build: |
- cd picom
- ninja -C build
- - unittest: |
- cd picom
- ninja -C build test
diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml
new file mode 100644
index 0000000000..d3167c91d7
--- /dev/null
+++ b/.github/workflows/freebsd.yml
@@ -0,0 +1,21 @@
+name: freebsd
+on: push
+
+jobs:
+ check:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+ - uses: cross-platform-actions/action@6acac3ca1b632aa762721d537dea32398ba0f2b1
+ with:
+ operating_system: freebsd
+ version: '14.1'
+ shell: bash
+ run: |
+ sudo pkg install -y libev libxcb meson pkgconf cmake xcb-util-renderutil xcb-util-image pixman uthash libconfig libglvnd libepoxy dbus pcre2
+ CPPFLAGS="-I/usr/local/include" meson setup -Dunittest=true --werror build
+ ninja -C build
+ ninja -C build test
+
diff --git a/.github/workflows/ci.yml b/.github/workflows/openbsd.yml
similarity index 100%
rename from .github/workflows/ci.yml
rename to .github/workflows/openbsd.yml
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4b3d69a3e6..7038b3e51b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -56,6 +56,10 @@
* `xcb-dpms` is not needed anymore.
* `libXext` is not needed anymore.
+## Behind the scene changes
+
+* The X critical section is removed, picom no longer grabs the server to fetch updates. Hopefully, if everything works, this change is unnoticeable. Minor responsiveness improvements could be possible, but I won't bet on it. The main point of this change is this makes debugging much less painful. Previously if you breaks inside the X critical section, the whole X server will lock up, and you would have to connect to the computer remotely to recover. Now there is no longer such worries. This also avoids a bug inside Xorg that makes server grabbing unreliable.
+
# v11.2 (2024-Feb-13)
## Build changes
diff --git a/flake.lock b/flake.lock
index eee97663b8..9e50f9bfca 100644
--- a/flake.lock
+++ b/flake.lock
@@ -41,16 +41,15 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1716451822,
- "narHash": "sha256-0lT5RVelqN+dgXWWneXvV5ufSksW0r0TDQi8O6U2+o8=",
+ "lastModified": 1719397110,
+ "narHash": "sha256-rYGcPSy8hBx/0OJvHrtR50KGN9AR7GN/zXG4xOi5Dnc=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "3305b2b25e4ae4baee872346eae133cf6f611783",
+ "rev": "c414599dfae92540ccc0c7d5d49f6cf6dc5b7fc8",
"type": "github"
},
"original": {
"owner": "nixos",
- "ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
diff --git a/flake.nix b/flake.nix
index 31880a4568..0663802827 100644
--- a/flake.nix
+++ b/flake.nix
@@ -1,6 +1,7 @@
{
inputs = {
flake-utils.url = github:numtide/flake-utils;
+ nixpkgs.url = github:nixos/nixpkgs;
git-ignore-nix = {
url = github:hercules-ci/gitignore.nix/master;
inputs.nixpkgs.follows = "nixpkgs";
@@ -14,6 +15,8 @@
...
}:
flake-utils.lib.eachDefaultSystem (system: let
+ # like lib.lists.remove, but takes a list of elements to remove
+ removeFromList = toRemove: list: pkgs.lib.foldl (l: e: pkgs.lib.remove e l) list toRemove;
overlay = self: super: {
picom = super.picom.overrideAttrs (oldAttrs: rec {
version = "11";
@@ -23,10 +26,11 @@
self.pcre2
self.xorg.xcbutil
self.libepoxy
- ]
- ++ self.lib.remove self.xorg.libXinerama (
- self.lib.remove self.pcre oldAttrs.buildInputs
- );
+ ] ++ (removeFromList [
+ self.xorg.libXinerama
+ self.xorg.libXext
+ self.pcre
+ ] oldAttrs.buildInputs);
src = git-ignore-nix.lib.gitignoreSource ./.;
});
};
@@ -59,8 +63,8 @@
# Workaround a NixOS limitation on sanitizers:
# See: https://github.com/NixOS/nixpkgs/issues/287763
export LD_LIBRARY_PATH+=":/run/opengl-driver/lib"
- export UBSAN_OPTIONS="disable_coredump=0:unmap_shadow_on_exit=1"
- export ASAN_OPTIONS="disable_coredump=0:unmap_shadow_on_exit=1"
+ export UBSAN_OPTIONS="disable_coredump=0:unmap_shadow_on_exit=1:print_stacktrace=1"
+ export ASAN_OPTIONS="disable_coredump=0:unmap_shadow_on_exit=1:abort_on_error=1"
'';
});
in rec {
diff --git a/include/picom/backend.h b/include/picom/backend.h
index 83d6efa09e..e71943c202 100644
--- a/include/picom/backend.h
+++ b/include/picom/backend.h
@@ -31,7 +31,7 @@ struct xvisual_info {
};
typedef struct session session_t;
-struct managed_win;
+struct win;
struct ev_loop;
struct backend_operations;
diff --git a/meson.build b/meson.build
index 6bdd4eda57..809a324ded 100644
--- a/meson.build
+++ b/meson.build
@@ -34,6 +34,10 @@ if get_option('sanitize')
if cc.has_argument('-fno-sanitize=unsigned-integer-overflow')
add_global_arguments('-fno-sanitize=unsigned-integer-overflow', language: 'c')
endif
+ if cc.has_argument('-fno-sanitize=unsigned-shift-base')
+ # uthash does a lot of this
+ add_global_arguments('-fno-sanitize=unsigned-shift-base', language: 'c')
+ endif
endif
if get_option('llvm_coverage')
diff --git a/src/backend/gl/blur.c b/src/backend/gl/blur.c
index 87830cc4bc..8151495c85 100644
--- a/src/backend/gl/blur.c
+++ b/src/backend/gl/blur.c
@@ -360,15 +360,10 @@ bool gl_blur(struct backend_base *base, ivec2 origin, image_handle target_,
// we never actually use that capability anywhere.
assert(source->y_inverted);
- GLuint vao[2];
- glGenVertexArrays(2, vao);
- GLuint bo[4];
- glGenBuffers(4, bo);
-
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
- glBindVertexArray(vao[0]);
- glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
+ glBindVertexArray(gd->vertex_array_objects[0]);
+ glBindBuffer(GL_ARRAY_BUFFER, gd->buffer_objects[0]);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gd->buffer_objects[1]);
glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STREAM_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6,
indices, GL_STREAM_DRAW);
@@ -378,9 +373,9 @@ bool gl_blur(struct backend_base *base, ivec2 origin, image_handle target_,
glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_FLOAT, GL_FALSE,
sizeof(GLfloat) * 4, (void *)(sizeof(GLfloat) * 2));
- glBindVertexArray(vao[1]);
- glBindBuffer(GL_ARRAY_BUFFER, bo[2]);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[3]);
+ glBindVertexArray(gd->vertex_array_objects[1]);
+ glBindBuffer(GL_ARRAY_BUFFER, gd->buffer_objects[2]);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gd->buffer_objects[3]);
glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord_resized) * nrects_resized * 16,
coord_resized, GL_STREAM_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
@@ -396,13 +391,15 @@ bool gl_blur(struct backend_base *base, ivec2 origin, image_handle target_,
auto target_fbo = gl_bind_image_to_fbo(gd, (image_handle)target);
if (bctx->method == BLUR_METHOD_DUAL_KAWASE) {
- ret = gl_dual_kawase_blur(args->opacity, bctx, args->source_mask, vao,
- vao_nelems, source, gd->samplers[GL_SAMPLER_BLUR],
- target_fbo, gd->default_mask_texture);
+ ret = gl_dual_kawase_blur(args->opacity, bctx, args->source_mask,
+ gd->vertex_array_objects, vao_nelems, source,
+ gd->samplers[GL_SAMPLER_BLUR], target_fbo,
+ gd->default_mask_texture);
} else {
- ret = gl_kernel_blur(args->opacity, bctx, args->source_mask, vao,
- vao_nelems, source, gd->samplers[GL_SAMPLER_BLUR],
- target_fbo, gd->default_mask_texture);
+ ret = gl_kernel_blur(args->opacity, bctx, args->source_mask,
+ gd->vertex_array_objects, vao_nelems, source,
+ gd->samplers[GL_SAMPLER_BLUR], target_fbo,
+ gd->default_mask_texture);
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
@@ -410,11 +407,25 @@ bool gl_blur(struct backend_base *base, ivec2 origin, image_handle target_,
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, 0);
+
+ // Invalidate buffer data
+ glBindBuffer(GL_ARRAY_BUFFER, gd->buffer_objects[0]);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gd->buffer_objects[1]);
+ glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, NULL, GL_STREAM_DRAW);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6, NULL,
+ GL_STREAM_DRAW);
+ glBindBuffer(GL_ARRAY_BUFFER, gd->buffer_objects[2]);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gd->buffer_objects[3]);
+ glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord_resized) * nrects_resized * 16,
+ NULL, GL_STREAM_DRAW);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER,
+ (long)sizeof(*indices_resized) * nrects_resized * 6, NULL,
+ GL_STREAM_DRAW);
+
+ // Cleanup vertex array states
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
- glDeleteBuffers(4, bo);
glBindVertexArray(0);
- glDeleteVertexArrays(2, vao);
glUseProgram(0);
free(indices);
diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c
index 918ee506e6..1b96ceb22b 100644
--- a/src/backend/gl/gl_common.c
+++ b/src/backend/gl/gl_common.c
@@ -288,13 +288,9 @@ static GLuint gl_average_texture_color(struct gl_data *gd, struct gl_texture *im
glUniform2f(UNIFORM_TEXSIZE_LOC, (GLfloat)img->width, (GLfloat)img->height);
// Prepare vertex attributes
- GLuint vao;
- glGenVertexArrays(1, &vao);
- glBindVertexArray(vao);
- GLuint bo[2];
- glGenBuffers(2, bo);
- glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
+ glBindVertexArray(gd->vertex_array_objects[0]);
+ glBindBuffer(GL_ARRAY_BUFFER, gd->buffer_objects[0]);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gd->buffer_objects[1]);
glEnableVertexAttribArray(vert_coord_loc);
glEnableVertexAttribArray(vert_in_texcoord_loc);
glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL);
@@ -316,11 +312,15 @@ static GLuint gl_average_texture_color(struct gl_data *gd, struct gl_texture *im
// Cleanup vertex attributes
glDisableVertexAttribArray(vert_coord_loc);
glDisableVertexAttribArray(vert_in_texcoord_loc);
+
+ // Invalidate buffer data
+ glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 16, NULL, GL_STREAM_DRAW);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, NULL, GL_STREAM_DRAW);
+
+ // Cleanup buffers
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
- glDeleteBuffers(2, bo);
glBindVertexArray(0);
- glDeleteVertexArrays(1, &vao);
// Cleanup shaders
glUseProgram(0);
@@ -384,10 +384,11 @@ static const struct gl_vertex_attribs_definition gl_blit_vertex_attribs = {
* @param nuniforms number of uniforms for `shader`
* @param uniforms uniforms for `shader`
*/
-static void gl_blit_inner(GLuint target_fbo, int nrects, GLfloat *coord, GLuint *indices,
- const struct gl_vertex_attribs_definition *vert_attribs,
- const struct gl_shader *shader, int nuniforms,
- struct gl_uniform_value *uniforms) {
+static void
+gl_blit_inner(struct gl_data *gd, GLuint target_fbo, int nrects, GLfloat *coord,
+ GLuint *indices, const struct gl_vertex_attribs_definition *vert_attribs,
+ const struct gl_shader *shader, int nuniforms,
+ struct gl_uniform_value *uniforms) {
// FIXME(yshui) breaks when `mask` and `img` doesn't have the same y_inverted
// value. but we don't ever hit this problem because all of our
// images and masks are y_inverted.
@@ -431,14 +432,10 @@ static void gl_blit_inner(GLuint target_fbo, int nrects, GLfloat *coord, GLuint
// log_trace("Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d\n",
// x, y, width, height, dx, dy, ptex->width, ptex->height, z);
- GLuint vao;
- glGenVertexArrays(1, &vao);
- glBindVertexArray(vao);
+ glBindVertexArray(gd->vertex_array_objects[0]);
- GLuint bo[2];
- glGenBuffers(2, bo);
- glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
+ glBindBuffer(GL_ARRAY_BUFFER, gd->buffer_objects[0]);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gd->buffer_objects[1]);
glBufferData(GL_ARRAY_BUFFER, vert_attribs->stride * nrects * 4, coord, GL_STREAM_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6,
indices, GL_STREAM_DRAW);
@@ -454,8 +451,15 @@ static void gl_blit_inner(GLuint target_fbo, int nrects, GLfloat *coord, GLuint
glDisableVertexAttribArray(vert_coord_loc);
glDisableVertexAttribArray(vert_in_texcoord_loc);
+
+ // Invalidate buffer data
+ glBufferData(GL_ARRAY_BUFFER, vert_attribs->stride * nrects * 4, NULL, GL_STREAM_DRAW);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6, NULL,
+ GL_STREAM_DRAW);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindVertexArray(0);
- glDeleteVertexArrays(1, &vao);
// Cleanup
for (GLuint i = GL_TEXTURE1; i < texture_unit; i++) {
@@ -466,10 +470,6 @@ static void gl_blit_inner(GLuint target_fbo, int nrects, GLfloat *coord, GLuint
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDrawBuffer(GL_BACK);
- glBindBuffer(GL_ARRAY_BUFFER, 0);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
- glDeleteBuffers(2, bo);
-
glUseProgram(0);
gl_check_err();
@@ -640,7 +640,7 @@ bool gl_blit(backend_t *base, ivec2 origin, image_handle target_,
// X pixmap is in premultiplied alpha, so we might just as well use it too.
// Thanks to derhass for help.
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
- gl_blit_inner(fbo, nrects, coord, indices, &gl_blit_vertex_attribs, shader,
+ gl_blit_inner(gd, fbo, nrects, coord, indices, &gl_blit_vertex_attribs, shader,
NUMBER_OF_UNIFORMS, uniforms);
free(indices);
@@ -700,7 +700,7 @@ static bool gl_copy_area_draw(struct gl_data *gd, ivec2 origin,
};
auto fbo = gl_bind_image_to_fbo(gd, target_handle);
glBlendFunc(GL_ONE, GL_ZERO);
- gl_blit_inner(fbo, nrects, coord, indices, &gl_blit_vertex_attribs, shader,
+ gl_blit_inner(gd, fbo, nrects, coord, indices, &gl_blit_vertex_attribs, shader,
ARR_SIZE(uniforms), uniforms);
free(indices);
free(coord);
@@ -885,6 +885,9 @@ bool gl_init(struct gl_data *gd, session_t *ps) {
glGenQueries(2, gd->frame_timing);
gd->current_frame_timing = 0;
+ glGenBuffers(4, gd->buffer_objects);
+ glGenVertexArrays(2, gd->vertex_array_objects);
+
// Initialize GL data structure
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
@@ -1043,6 +1046,9 @@ void gl_deinit(struct gl_data *gd) {
glDeleteFramebuffers(1, &gd->temp_fbo);
+ glDeleteBuffers(4, gd->buffer_objects);
+ glDeleteVertexArrays(2, gd->vertex_array_objects);
+
glDeleteQueries(2, gd->frame_timing);
gl_check_err();
@@ -1138,7 +1144,7 @@ bool gl_apply_alpha(backend_t *base, image_handle target, double alpha, const re
[UNIFORM_COLOR_LOC] = {.type = GL_FLOAT_VEC4, .f4 = {0, 0, 0, 0}},
};
gl_mask_rects_to_coords_simple(nrects, rect, coord, indices);
- gl_blit_inner(gd->temp_fbo, nrects, coord, indices, &vertex_attribs,
+ gl_blit_inner(gd, gd->temp_fbo, nrects, coord, indices, &vertex_attribs,
&gd->fill_shader, ARR_SIZE(uniforms), uniforms);
free(indices);
free(coord);
diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h
index a327e50a09..3e32957800 100644
--- a/src/backend/gl/gl_common.h
+++ b/src/backend/gl/gl_common.h
@@ -103,6 +103,8 @@ struct gl_data {
struct gl_shader copy_area_prog;
struct gl_shader copy_area_with_dither_prog;
GLuint samplers[GL_MAX_SAMPLERS];
+ GLuint buffer_objects[4];
+ GLuint vertex_array_objects[2];
bool dithered_present;
diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c
index a8551f1841..738606d0a4 100644
--- a/src/backend/xrender/xrender.c
+++ b/src/backend/xrender/xrender.c
@@ -110,7 +110,7 @@ set_picture_scale(struct x_connection *c, xcb_render_picture_t picture, vec2 sca
.matrix22 = DOUBLE_TO_XFIXED(1.0 / scale.y),
.matrix33 = DOUBLE_TO_XFIXED(1.0),
};
- set_cant_fail_cookie(c, xcb_render_set_picture_transform(c->c, picture, transform));
+ x_set_error_action_abort(c, xcb_render_set_picture_transform(c->c, picture, transform));
}
/// Make a picture of size width x height, which has a rounded rectangle of corner_radius
@@ -198,9 +198,9 @@ static inline void xrender_set_picture_repeat(struct xrender_data *xd,
xcb_render_change_picture_value_list_t values = {
.repeat = repeat,
};
- set_cant_fail_cookie(xd->base.c, xcb_render_change_picture(xd->base.c->c, pict,
- XCB_RENDER_CP_REPEAT,
- (uint32_t *)&values));
+ x_set_error_action_abort(
+ xd->base.c, xcb_render_change_picture(xd->base.c->c, pict, XCB_RENDER_CP_REPEAT,
+ (uint32_t *)&values));
}
static inline void xrender_record_back_damage(struct xrender_data *xd,
@@ -360,7 +360,7 @@ static bool xrender_blit(struct backend_base *base, ivec2 origin,
scoped_region_t source_mask_region;
pixman_region32_init(&source_mask_region);
pixman_region32_copy(&source_mask_region, args->target_mask);
- region_scale_ceil(&source_mask_region, origin, inverse_scale);
+ region_scale(&source_mask_region, origin, inverse_scale);
x_set_picture_clip_region(
xd->base.c, tmp_pict, to_i16_checked(-origin.x),
to_i16_checked(-origin.y), &source_mask_region);
diff --git a/src/c2.c b/src/c2.c
index a883a284f8..08733c1454 100644
--- a/src/c2.c
+++ b/src/c2.c
@@ -314,7 +314,7 @@ static inline c2_ptr_t c2h_comb_tree(c2_b_op_t op, c2_ptr_t p1, c2_ptr_t p2) {
static inline int c2h_b_opp(c2_b_op_t op) {
switch (op) {
case C2_B_OAND: return 2;
- case C2_B_OOR: return 1;
+ case C2_B_OOR:
case C2_B_OXOR: return 1;
default: break;
}
@@ -441,8 +441,12 @@ TEST_CASE(c2_parse) {
size_t len = c2_condition_to_str(cond->ptr, str, sizeof(str));
TEST_STREQUAL3(str, "name = \"xterm\"", len);
- struct managed_win test_win = {
+ struct wm *wm = wm_new();
+ struct wm_ref *node = wm_new_mock_window(wm, 1);
+
+ struct win test_win = {
.name = "xterm",
+ .tree_ref = node,
};
TEST_TRUE(c2_match(state, &test_win, cond, NULL));
c2_list_postprocess(state, NULL, cond);
@@ -563,6 +567,9 @@ TEST_CASE(c2_parse) {
len = c2_condition_to_str(cond->ptr, str, sizeof(str));
TEST_STREQUAL3(str, rule, len);
c2_list_free(&cond, NULL);
+
+ wm_free_mock_window(wm, test_win.tree_ref);
+ wm_free(wm);
}
#define c2_error(format, ...) \
@@ -1562,8 +1569,9 @@ static inline bool c2_int_op(const c2_l_t *leaf, int64_t target) {
unreachable();
}
-static bool c2_match_once_leaf_int(const struct managed_win *w, const c2_l_t *leaf) {
- const xcb_window_t wid = (leaf->target_on_client ? w->client_win : w->base.id);
+static bool c2_match_once_leaf_int(const struct win *w, const c2_l_t *leaf) {
+ auto const client_win = win_client_id(w, /*fallback_to_self=*/true);
+ const xcb_window_t wid = (leaf->target_on_client ? client_win : win_id(w));
// Get the value
if (leaf->predef != C2_L_PUNDEFINED) {
@@ -1586,7 +1594,7 @@ static bool c2_match_once_leaf_int(const struct managed_win *w, const c2_l_t *le
case C2_L_PWMWIN: predef_target = win_is_wmwin(w); break;
case C2_L_PBSHAPED: predef_target = w->bounding_shaped; break;
case C2_L_PROUNDED: predef_target = w->rounded_corners; break;
- case C2_L_PCLIENT: predef_target = w->client_win; break;
+ case C2_L_PCLIENT: predef_target = client_win; break;
case C2_L_PLEADER: predef_target = w->leader; break;
case C2_L_POVREDIR:
// When user wants to check override-redirect, they almost always
@@ -1594,9 +1602,8 @@ static bool c2_match_once_leaf_int(const struct managed_win *w, const c2_l_t *le
// don't track the override-redirect state of the client window
// directly, however we can assume if a window has a window
// manager frame around it, it's not override-redirect.
- predef_target =
- w->a.override_redirect && (w->client_win == w->base.id ||
- w->client_win == XCB_WINDOW_NONE);
+ predef_target = w->a.override_redirect &&
+ wm_ref_client_of(w->tree_ref) == NULL;
break;
default: unreachable();
}
@@ -1608,7 +1615,7 @@ static bool c2_match_once_leaf_int(const struct managed_win *w, const c2_l_t *le
assert(!values->needs_update);
if (!values->valid) {
log_verbose("Property %s not found on window %#010x (%s)", leaf->tgt,
- w->client_win, w->name);
+ client_win, w->name);
return false;
}
@@ -1670,8 +1677,8 @@ static bool c2_string_op(const c2_l_t *leaf, const char *target) {
unreachable();
}
-static bool c2_match_once_leaf_string(struct atom *atoms, const struct managed_win *w,
- const c2_l_t *leaf) {
+static bool
+c2_match_once_leaf_string(struct atom *atoms, const struct win *w, const c2_l_t *leaf) {
// A predefined target
const char *predef_target = NULL;
@@ -1696,8 +1703,8 @@ static bool c2_match_once_leaf_string(struct atom *atoms, const struct managed_w
auto values = &w->c2_state.values[leaf->target_id];
assert(!values->needs_update);
if (!values->valid) {
- log_verbose("Property %s not found on window %#010x (%s)", leaf->tgt,
- w->client_win, w->name);
+ log_verbose("Property %s not found on window %#010x, client %#010x (%s)",
+ leaf->tgt, win_id(w), win_client_id(w, false), w->name);
return false;
}
@@ -1752,10 +1759,11 @@ static bool c2_match_once_leaf_string(struct atom *atoms, const struct managed_w
* For internal use.
*/
static inline bool
-c2_match_once_leaf(struct c2_state *state, const struct managed_win *w, const c2_l_t *leaf) {
+c2_match_once_leaf(struct c2_state *state, const struct win *w, const c2_l_t *leaf) {
assert(leaf);
- const xcb_window_t wid = (leaf->target_on_client ? w->client_win : w->base.id);
+ const xcb_window_t wid =
+ (leaf->target_on_client ? win_client_id(w, /*fallback_to_self=*/true) : win_id(w));
// Return if wid is missing
if (leaf->predef == C2_L_PUNDEFINED && !wid) {
@@ -1790,8 +1798,7 @@ c2_match_once_leaf(struct c2_state *state, const struct managed_win *w, const c2
*
* @return true if matched, false otherwise.
*/
-static bool
-c2_match_once(struct c2_state *state, const struct managed_win *w, const c2_ptr_t cond) {
+static bool c2_match_once(struct c2_state *state, const struct win *w, const c2_ptr_t cond) {
bool result = false;
if (cond.isbranch) {
@@ -1802,8 +1809,8 @@ c2_match_once(struct c2_state *state, const struct managed_win *w, const c2_ptr_
return false;
}
- log_verbose("Matching window %#010x (%s) against condition %s",
- w->base.id, w->name, c2_condition_to_str2(cond));
+ log_verbose("Matching window %#010x (%s) against condition %s", win_id(w),
+ w->name, c2_condition_to_str2(cond));
switch (pb->op) {
case C2_B_OAND:
@@ -1821,7 +1828,7 @@ c2_match_once(struct c2_state *state, const struct managed_win *w, const c2_ptr_
default: unreachable();
}
- log_debug("(%#010x): branch: result = %d, pattern = %s", w->base.id,
+ log_debug("(%#010x): branch: result = %d, pattern = %s", win_id(w),
result, c2_condition_to_str2(cond));
} else {
// A leaf
@@ -1833,9 +1840,9 @@ c2_match_once(struct c2_state *state, const struct managed_win *w, const c2_ptr_
result = c2_match_once_leaf(state, w, pleaf);
- log_debug("(%#010x): leaf: result = %d, client = %#010x, "
- "pattern = %s",
- w->base.id, result, w->client_win, c2_condition_to_str2(cond));
+ log_debug("(%#010x): leaf: result = %d, client = %#010x, pattern = %s",
+ win_id(w), result, win_client_id(w, false),
+ c2_condition_to_str2(cond));
}
// Postprocess the result
@@ -1853,8 +1860,8 @@ c2_match_once(struct c2_state *state, const struct managed_win *w, const c2_ptr_
* @param pdata a place to return the data
* @return true if matched, false otherwise.
*/
-bool c2_match(struct c2_state *state, const struct managed_win *w,
- const c2_lptr_t *condlst, void **pdata) {
+bool c2_match(struct c2_state *state, const struct win *w, const c2_lptr_t *condlst,
+ void **pdata) {
// Then go through the whole linked list
for (; condlst; condlst = condlst->next) {
if (c2_match_once(state, w, condlst->ptr)) {
@@ -1869,8 +1876,8 @@ bool c2_match(struct c2_state *state, const struct managed_win *w,
}
/// Match a window against the first condition in a condition linked list.
-bool c2_match_one(struct c2_state *state, const struct managed_win *w,
- const c2_lptr_t *condlst, void **pdata) {
+bool c2_match_one(struct c2_state *state, const struct win *w, const c2_lptr_t *condlst,
+ void **pdata) {
if (!condlst) {
return false;
}
diff --git a/src/c2.h b/src/c2.h
index 328a3c4c78..2dcd3e712d 100644
--- a/src/c2.h
+++ b/src/c2.h
@@ -18,7 +18,7 @@ struct c2_window_state {
struct c2_property_value *values;
};
struct atom;
-struct managed_win;
+struct win;
typedef void (*c2_userdata_free)(void *);
c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data);
@@ -50,10 +50,10 @@ void c2_window_state_update(struct c2_state *state, struct c2_window_state *wind
xcb_connection_t *c, xcb_window_t client_win,
xcb_window_t frame_win);
-bool c2_match(struct c2_state *state, const struct managed_win *w,
- const c2_lptr_t *condlst, void **pdata);
-bool c2_match_one(struct c2_state *state, const struct managed_win *w,
- const c2_lptr_t *condlst, void **pdata);
+bool c2_match(struct c2_state *state, const struct win *w, const c2_lptr_t *condlst,
+ void **pdata);
+bool c2_match_one(struct c2_state *state, const struct win *w, const c2_lptr_t *condlst,
+ void **pdata);
bool c2_list_postprocess(struct c2_state *state, xcb_connection_t *c, c2_lptr_t *list);
typedef bool (*c2_list_foreach_cb_t)(const c2_lptr_t *cond, void *data);
diff --git a/src/common.h b/src/common.h
index cd21099876..98da13acf6 100644
--- a/src/common.h
+++ b/src/common.h
@@ -166,8 +166,6 @@ typedef struct session {
// === Display related ===
/// X connection
struct x_connection c;
- /// Whether the X server is grabbed by us
- bool server_grabbed;
/// Width of root window.
int root_width;
/// Height of root window.
diff --git a/src/dbus.c b/src/dbus.c
index 4c09b08390..dac97f4227 100644
--- a/src/dbus.c
+++ b/src/dbus.c
@@ -20,7 +20,6 @@
#include "compiler.h"
#include "config.h"
#include "log.h"
-#include "utils/list.h"
#include "utils/misc.h"
#include "utils/str.h"
#include "wm/defs.h"
@@ -464,24 +463,8 @@ static bool cdbus_append_string_variant(DBusMessage *msg, const char *data) {
return true;
}
-static int cdbus_append_wids_callback(struct win *w, void *data) {
- DBusMessageIter *iter = data;
- cdbus_window_t wid = w->id;
- if (!dbus_message_iter_append_basic(iter, CDBUS_TYPE_WINDOW, &wid)) {
- return 1;
- }
- return 0;
-}
-
/// Append all window IDs in the window list of a session to a D-Bus message
static bool cdbus_append_wids(DBusMessage *msg, session_t *ps) {
- // Get the number of wids we are to include
- unsigned count = wm_num_windows(ps->wm);
- if (!count) {
- // Nothing to append
- return true;
- }
-
DBusMessageIter it, subit;
dbus_message_iter_init_append(msg, &it);
if (!dbus_message_iter_open_container(&it, DBUS_TYPE_ARRAY,
@@ -490,12 +473,27 @@ static bool cdbus_append_wids(DBusMessage *msg, session_t *ps) {
return false;
}
- auto result = wm_foreach(ps->wm, cdbus_append_wids_callback, &subit);
+ bool failed = false;
+ wm_stack_foreach(ps->wm, cursor) {
+ if (wm_ref_is_zombie(cursor)) {
+ continue;
+ }
+ auto w = wm_ref_deref(cursor);
+ if (w == NULL) {
+ continue;
+ }
+
+ auto wid = win_id(w);
+ if (!dbus_message_iter_append_basic(&subit, CDBUS_TYPE_WINDOW, &wid)) {
+ failed = true;
+ break;
+ }
+ }
if (!dbus_message_iter_close_container(&it, &subit)) {
log_error("Failed to close container.");
return false;
}
- if (result != 0) {
+ if (failed) {
log_error("Failed to append argument.");
return false;
}
@@ -581,14 +579,20 @@ cdbus_process_window_property_get(session_t *ps, DBusMessage *msg, cdbus_window_
return DBUS_HANDLER_RESULT_HANDLED;
}
- auto w = wm_find_managed(ps->wm, wid);
-
- if (!w) {
+ auto cursor = wm_find(ps->wm, wid);
+ if (cursor == NULL) {
log_debug("Window %#010x not found.", wid);
dbus_set_error(e, CDBUS_ERROR_BADWIN, CDBUS_ERROR_BADWIN_S, wid);
return DBUS_HANDLER_RESULT_HANDLED;
}
+ auto w = wm_ref_deref(cursor);
+ if (w == NULL) {
+ log_debug("Window %#010x is not managed.", wid);
+ dbus_set_error(e, CDBUS_ERROR_BADWIN, CDBUS_ERROR_BADWIN_S, wid);
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+
#define append(tgt, type, expr) \
if (!strcmp(#tgt, target)) { \
if (!cdbus_append_##type(reply, expr)) { \
@@ -599,19 +603,18 @@ cdbus_process_window_property_get(session_t *ps, DBusMessage *msg, cdbus_window_
#define append_win_property(name, member, type) append(name, type, w->member)
append(Mapped, bool_variant, w->state == WSTATE_MAPPED);
- append(Id, wid_variant, w->base.id);
+ append(Id, wid_variant, win_id(w));
append(Type, string_variant, WINTYPES[w->window_type].name);
append(RawFocused, bool_variant, win_is_focused_raw(w));
- append_win_property(ClientWin, client_win, wid_variant);
+ append(ClientWin, wid_variant, win_client_id(w, /*fallback_to_self=*/true));
append_win_property(Leader, leader, wid_variant);
append_win_property(Name, name, string_variant);
if (!strcmp("Next", target)) {
cdbus_window_t next_id = 0;
- if (!list_node_is_last(wm_stack_end(ps->wm), &w->base.stack_neighbour)) {
- next_id = list_entry(w->base.stack_neighbour.next, struct win,
- stack_neighbour)
- ->id;
+ auto below = wm_ref_below(cursor);
+ if (below != NULL) {
+ next_id = wm_ref_win_id(below);
}
if (!cdbus_append_wid_variant(reply, next_id)) {
return DBUS_HANDLER_RESULT_NEED_MEMORY;
@@ -668,14 +671,20 @@ cdbus_process_win_get(session_t *ps, DBusMessage *msg, DBusMessage *reply, DBusE
return DBUS_HANDLER_RESULT_HANDLED;
}
- auto w = wm_find_managed(ps->wm, wid);
-
- if (!w) {
+ auto cursor = wm_find(ps->wm, wid);
+ if (cursor == NULL) {
log_debug("Window %#010x not found.", wid);
dbus_set_error(err, CDBUS_ERROR_BADWIN, CDBUS_ERROR_BADWIN_S, wid);
return DBUS_HANDLER_RESULT_HANDLED;
}
+ auto w = wm_ref_deref(cursor);
+ if (w == NULL) {
+ log_debug("Window %#010x is not managed.", wid);
+ dbus_set_error(err, CDBUS_ERROR_BADWIN, CDBUS_ERROR_BADWIN_S, wid);
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+
#define append(tgt, type, expr) \
if (strcmp(#tgt, target) == 0) { \
if (!cdbus_append_##type(reply, expr)) { \
@@ -686,18 +695,16 @@ cdbus_process_win_get(session_t *ps, DBusMessage *msg, DBusMessage *reply, DBusE
#define append_win_property(tgt, type) append(tgt, type, w->tgt)
if (!strcmp("next", target)) {
- xcb_window_t next_id =
- list_node_is_last(wm_stack_end(ps->wm), &w->base.stack_neighbour)
- ? 0
- : list_entry(w->base.stack_neighbour.next, struct win, stack_neighbour)
- ->id;
+ auto below = wm_ref_below(cursor);
+ xcb_window_t next_id = below ? wm_ref_win_id(below) : XCB_NONE;
if (!cdbus_append_wid(reply, next_id)) {
return DBUS_HANDLER_RESULT_NEED_MEMORY;
}
return DBUS_HANDLER_RESULT_HANDLED;
}
- append(id, boolean, w->base.id);
+ append(id, wid, win_id(w));
+ append(client_win, wid, win_client_id(w, /*fallback_to_self=*/true));
append(map_state, boolean, w->a.map_state);
append(wmwin, boolean, win_is_wmwin(w));
append(focused_raw, boolean, win_is_focused_raw(w));
@@ -708,7 +715,6 @@ cdbus_process_win_get(session_t *ps, DBusMessage *msg, DBusMessage *reply, DBusE
append_win_property(mode, enum);
append_win_property(opacity, double);
- append_win_property(client_win, wid);
append_win_property(ever_damaged, boolean);
append_win_property(window_type, enum);
append_win_property(leader, wid);
@@ -753,13 +759,19 @@ cdbus_process_win_set(session_t *ps, DBusMessage *msg, DBusMessage *reply, DBusE
return DBUS_HANDLER_RESULT_HANDLED;
}
- auto w = wm_find_managed(ps->wm, wid);
-
- if (!w) {
+ auto cursor = wm_find(ps->wm, wid);
+ if (cursor == NULL) {
log_debug("Window %#010x not found.", wid);
dbus_set_error(err, CDBUS_ERROR_BADWIN, CDBUS_ERROR_BADWIN_S, wid);
return DBUS_HANDLER_RESULT_HANDLED;
}
+
+ auto w = wm_ref_deref(cursor);
+ if (w == NULL) {
+ log_debug("Window %#010x is not managed.", wid);
+ dbus_set_error(err, CDBUS_ERROR_BADWIN, CDBUS_ERROR_BADWIN_S, wid);
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
cdbus_enum_t val = UNSET;
if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val)) {
dbus_set_error_const(err, DBUS_ERROR_INVALID_ARGS, NULL);
@@ -812,13 +824,13 @@ cdbus_process_find_win(session_t *ps, DBusMessage *msg, DBusMessage *reply, DBus
}
auto w = wm_find_by_client(ps->wm, client);
if (w) {
- wid = w->base.id;
+ wid = wm_ref_win_id(w);
}
} else if (!strcmp("focused", target)) {
// Find focused window
auto active_win = wm_active_win(ps->wm);
if (active_win && active_win->state != WSTATE_UNMAPPED) {
- wid = active_win->base.id;
+ wid = win_id(active_win);
}
} else {
log_debug(CDBUS_ERROR_BADTGT_S, target);
@@ -1072,21 +1084,6 @@ static DBusHandlerResult cdbus_process_introspect(DBusMessage *reply) {
}
///@}
-static int cdbus_process_windows_root_introspect_callback(struct win *w, void *data) {
- char **introspect = data;
- if (!w->managed) {
- return 0;
- }
- char *tmp = NULL;
- if (asprintf(&tmp, "\n", w->id) < 0) {
- log_fatal("Failed to allocate memory.");
- return 1;
- }
- mstrextend(introspect, tmp);
- free(tmp);
- return 0;
-}
-
/**
* Process an D-Bus Introspect request, for /windows.
*/
@@ -1109,8 +1106,18 @@ cdbus_process_windows_root_introspect(session_t *ps, DBusMessage *reply) {
scoped_charp introspect = NULL;
mstrextend(&introspect, str_introspect);
- if (wm_foreach(ps->wm, cdbus_process_windows_root_introspect_callback, &introspect)) {
- return DBUS_HANDLER_RESULT_NEED_MEMORY;
+ wm_stack_foreach(ps->wm, cursor) {
+ auto w = wm_ref_deref(cursor);
+ if (w == NULL) {
+ continue;
+ }
+ char *tmp = NULL;
+ if (asprintf(&tmp, "\n", win_id(w)) < 0) {
+ log_fatal("Failed to allocate memory.");
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+ }
+ mstrextend(&introspect, tmp);
+ free(tmp);
}
mstrextend(&introspect, "");
if (!cdbus_append_string(reply, introspect)) {
@@ -1424,41 +1431,41 @@ static bool cdbus_signal_wid(struct cdbus_data *cd, const char *interface,
///@{
void cdbus_ev_win_added(struct cdbus_data *cd, struct win *w) {
if (cd->dbus_conn) {
- cdbus_signal_wid(cd, CDBUS_INTERFACE_NAME, "win_added", w->id);
- cdbus_signal_wid(cd, PICOM_COMPOSITOR_INTERFACE, "WinAdded", w->id);
+ cdbus_signal_wid(cd, CDBUS_INTERFACE_NAME, "win_added", win_id(w));
+ cdbus_signal_wid(cd, PICOM_COMPOSITOR_INTERFACE, "WinAdded", win_id(w));
}
}
void cdbus_ev_win_destroyed(struct cdbus_data *cd, struct win *w) {
if (cd->dbus_conn) {
- cdbus_signal_wid(cd, CDBUS_INTERFACE_NAME, "win_destroyed", w->id);
- cdbus_signal_wid(cd, PICOM_COMPOSITOR_INTERFACE, "WinDestroyed", w->id);
+ cdbus_signal_wid(cd, CDBUS_INTERFACE_NAME, "win_destroyed", win_id(w));
+ cdbus_signal_wid(cd, PICOM_COMPOSITOR_INTERFACE, "WinDestroyed", win_id(w));
}
}
void cdbus_ev_win_mapped(struct cdbus_data *cd, struct win *w) {
if (cd->dbus_conn) {
- cdbus_signal_wid(cd, CDBUS_INTERFACE_NAME, "win_mapped", w->id);
- cdbus_signal_wid(cd, PICOM_COMPOSITOR_INTERFACE, "WinMapped", w->id);
+ cdbus_signal_wid(cd, CDBUS_INTERFACE_NAME, "win_mapped", win_id(w));
+ cdbus_signal_wid(cd, PICOM_COMPOSITOR_INTERFACE, "WinMapped", win_id(w));
}
}
void cdbus_ev_win_unmapped(struct cdbus_data *cd, struct win *w) {
if (cd->dbus_conn) {
- cdbus_signal_wid(cd, CDBUS_INTERFACE_NAME, "win_unmapped", w->id);
- cdbus_signal_wid(cd, PICOM_COMPOSITOR_INTERFACE, "WinUnmapped", w->id);
+ cdbus_signal_wid(cd, CDBUS_INTERFACE_NAME, "win_unmapped", win_id(w));
+ cdbus_signal_wid(cd, PICOM_COMPOSITOR_INTERFACE, "WinUnmapped", win_id(w));
}
}
void cdbus_ev_win_focusout(struct cdbus_data *cd, struct win *w) {
if (cd->dbus_conn) {
- cdbus_signal_wid(cd, CDBUS_INTERFACE_NAME, "win_focusout", w->id);
+ cdbus_signal_wid(cd, CDBUS_INTERFACE_NAME, "win_focusout", win_id(w));
}
}
void cdbus_ev_win_focusin(struct cdbus_data *cd, struct win *w) {
if (cd->dbus_conn) {
- cdbus_signal_wid(cd, CDBUS_INTERFACE_NAME, "win_focusin", w->id);
+ cdbus_signal_wid(cd, CDBUS_INTERFACE_NAME, "win_focusin", win_id(w));
}
}
//!@}
diff --git a/src/event.c b/src/event.c
index 226212623a..d2bcaa777f 100644
--- a/src/event.c
+++ b/src/event.c
@@ -5,7 +5,6 @@
#include
#include
-#include
#include
#include
#include
@@ -74,11 +73,12 @@ static inline const char *ev_window_name(session_t *ps, xcb_window_t wid) {
} else if (ps->overlay == wid) {
name = "(Overlay)";
} else {
- auto w = wm_find_managed(ps->wm, wid);
- if (!w) {
- w = wm_find_by_client(ps->wm, wid);
+ auto cursor = wm_find(ps->wm, wid);
+ if (!cursor || !wm_ref_deref(cursor)) {
+ cursor = wm_find_by_client(ps->wm, wid);
}
+ auto w = cursor ? wm_ref_deref(cursor) : NULL;
if (w && w->name) {
name = w->name;
}
@@ -190,164 +190,263 @@ static inline const char *attr_pure ev_focus_detail_name(xcb_focus_in_event_t *e
#undef CASESTRRET
-static inline void ev_focus_in(session_t *ps, xcb_focus_in_event_t *ev) {
- log_debug("{ mode: %s, detail: %s }\n", ev_focus_mode_name(ev),
- ev_focus_detail_name(ev));
- ps->pending_updates = true;
-}
+struct ev_ewmh_active_win_request {
+ struct x_async_request_base base;
+ session_t *ps;
+};
-static inline void ev_focus_out(session_t *ps, xcb_focus_out_event_t *ev) {
- log_debug("{ mode: %s, detail: %s }\n", ev_focus_mode_name(ev),
- ev_focus_detail_name(ev));
- ps->pending_updates = true;
+/// Update current active window based on EWMH _NET_ACTIVE_WIN.
+///
+/// Does not change anything if we fail to get the attribute or the window
+/// returned could not be found.
+static void
+update_ewmh_active_win(struct x_connection * /*c*/, struct x_async_request_base *req_base,
+ xcb_raw_generic_event_t *reply_or_error) {
+ auto ps = ((struct ev_ewmh_active_win_request *)req_base)->ps;
+ free(req_base);
+
+ if (reply_or_error->response_type == 0) {
+ log_error("Failed to get _NET_ACTIVE_WINDOW: %s",
+ x_strerror(((xcb_generic_error_t *)reply_or_error)));
+ return;
+ }
+
+ // Search for the window
+ auto reply = (xcb_get_property_reply_t *)reply_or_error;
+ if (reply->type == XCB_NONE || xcb_get_property_value_length(reply) < 4) {
+ log_debug("EWMH _NET_ACTIVE_WINDOW not set.");
+ return;
+ }
+
+ auto wid = *(xcb_window_t *)xcb_get_property_value(reply);
+ log_debug("EWMH _NET_ACTIVE_WINDOW is %#010x", wid);
+
+ auto cursor = wm_find_by_client(ps->wm, wid);
+ auto w = cursor ? wm_ref_deref(cursor) : NULL;
+
+ // Mark the window focused. No need to unfocus the previous one.
+ if (w) {
+ win_set_focused(ps, w);
+ }
}
-static inline void ev_create_notify(session_t *ps, xcb_create_notify_event_t *ev) {
- if (ev->parent == ps->c.screen_info->root) {
- wm_stack_add_top(ps->wm, ev->window);
- ps->pending_updates = true;
+struct ev_recheck_focus_request {
+ struct x_async_request_base base;
+ session_t *ps;
+};
+
+/**
+ * Recheck currently focused window and set its w->focused
+ * to true.
+ *
+ * @param ps current session
+ * @return struct _win of currently focused window, NULL if not found
+ */
+static void recheck_focus(struct x_connection * /*c*/, struct x_async_request_base *req_base,
+ xcb_raw_generic_event_t *reply_or_error) {
+ auto ps = ((struct ev_ewmh_active_win_request *)req_base)->ps;
+ free(req_base);
+
+ // Determine the currently focused window so we can apply appropriate
+ // opacity on it
+ if (reply_or_error->response_type == 0) {
+ // Not able to get input focus means very not good things...
+ auto e = (xcb_generic_error_t *)reply_or_error;
+ log_error_x_error(e, "Failed to get focused window.");
return;
}
- auto w = wm_find_managed(ps->wm, ev->parent);
- if (w == NULL) {
- // The parent window is not a toplevel window, we don't care about it.
- // This can happen if a toplevel is reparented somewhere, and more events
- // were generated from it before we can unsubscribe.
+ auto reply = (xcb_get_input_focus_reply_t *)reply_or_error;
+ xcb_window_t wid = reply->focus;
+ log_debug("Current focused window is %#010x", wid);
+ if (wid == XCB_NONE || wid == XCB_INPUT_FOCUS_POINTER_ROOT ||
+ wid == ps->c.screen_info->root) {
+ // Focus is not on a toplevel.
return;
}
- // A direct child of a toplevel, subscribe to property changes so we can
- // know if WM_STATE is set on this window.
- wm_subwin_add_and_subscribe(ps->wm, &ps->c, ev->window, ev->parent);
- if (w->client_win == XCB_NONE || w->client_win == w->base.id) {
- win_set_flags(w, WIN_FLAGS_CLIENT_STALE);
- ps->pending_updates = true;
+
+ auto cursor = wm_find(ps->wm, wid);
+ if (cursor == NULL) {
+ if (wm_is_consistent(ps->wm)) {
+ log_error("Window %#010x not found in window tree.", wid);
+ assert(false);
+ }
+ return;
+ }
+
+ cursor = wm_ref_toplevel_of(ps->wm, cursor);
+ if (cursor == NULL) {
+ assert(!wm_is_consistent(ps->wm));
+ return;
+ }
+
+ // And we set the focus state here
+ auto w = wm_ref_deref(cursor);
+ if (w) {
+ log_debug("%#010x (%s) focused.", wid, w->name);
+ win_set_focused(ps, w);
}
}
+static inline void ev_focus_change(session_t *ps) {
+ if (ps->o.use_ewmh_active_win) {
+ // Not using focus_in/focus_out events.
+ return;
+ }
+
+ auto req = ccalloc(1, struct ev_recheck_focus_request);
+ req->base.sequence = xcb_get_input_focus(ps->c.c).sequence;
+ req->base.callback = recheck_focus;
+ req->ps = ps;
+ x_await_request(&ps->c, &req->base);
+ log_debug("Started async request to recheck focus");
+}
+
+static inline void ev_focus_in(session_t *ps, xcb_focus_in_event_t *ev) {
+ log_debug("{ mode: %s, detail: %s }", ev_focus_mode_name(ev),
+ ev_focus_detail_name(ev));
+ ev_focus_change(ps);
+}
+
+static inline void ev_focus_out(session_t *ps, xcb_focus_out_event_t *ev) {
+ log_debug("{ mode: %s, detail: %s }", ev_focus_mode_name(ev),
+ ev_focus_detail_name(ev));
+ ev_focus_change(ps);
+}
+
+static inline void ev_create_notify(session_t *ps, xcb_create_notify_event_t *ev) {
+ auto parent = wm_find(ps->wm, ev->parent);
+ if (parent == NULL) {
+ log_error("Create notify received for window %#010x, but its parent "
+ "window %#010x is not in our tree. Expect malfunction.",
+ ev->window, ev->parent);
+ assert(false);
+ }
+ wm_import_start(ps->wm, &ps->c, ps->atoms, ev->window, parent);
+}
+
/// Handle configure event of a regular window
static void configure_win(session_t *ps, xcb_configure_notify_event_t *ce) {
- auto w = wm_find(ps->wm, ce->window);
-
- if (!w) {
+ auto cursor = wm_find(ps->wm, ce->window);
+ auto below = wm_find(ps->wm, ce->above_sibling);
+
+ if (!cursor) {
+ if (wm_is_consistent(ps->wm)) {
+ log_error("Configure event received for unknown window %#010x",
+ ce->window);
+ assert(false);
+ }
return;
}
- wm_stack_move_above(ps->wm, w, ce->above_sibling);
+ if (below == NULL && ce->above_sibling != XCB_NONE) {
+ log_error("Configure event received for window %#010x, but its sibling "
+ "window %#010x is not in our tree. Expect malfunction.",
+ ce->window, ce->above_sibling);
+ assert(false);
+ } else if (below != NULL) {
+ wm_stack_move_to_above(ps->wm, cursor, below);
+ } else {
+ // above_sibling being XCB_NONE means the window is put at the bottom.
+ wm_stack_move_to_end(ps->wm, cursor, true);
+ }
- if (!w->managed) {
+ auto w = wm_ref_deref(cursor);
+ if (!w) {
return;
}
- auto mw = (struct managed_win *)w;
- add_damage_from_win(ps, mw);
+ add_damage_from_win(ps, w);
// We check against pending_g here, because there might have been multiple
// configure notifies in this cycle, or the window could receive multiple updates
// while it's unmapped.
- bool position_changed = mw->pending_g.x != ce->x || mw->pending_g.y != ce->y;
- bool size_changed = mw->pending_g.width != ce->width ||
- mw->pending_g.height != ce->height ||
- mw->pending_g.border_width != ce->border_width;
+ bool position_changed = w->pending_g.x != ce->x || w->pending_g.y != ce->y;
+ bool size_changed = w->pending_g.width != ce->width ||
+ w->pending_g.height != ce->height ||
+ w->pending_g.border_width != ce->border_width;
if (position_changed || size_changed) {
// Queue pending updates
- win_set_flags(mw, WIN_FLAGS_FACTOR_CHANGED);
+ win_set_flags(w, WIN_FLAGS_FACTOR_CHANGED);
// TODO(yshui) don't set pending_updates if the window is not
// visible/mapped
ps->pending_updates = true;
// At least one of the following if's is true
if (position_changed) {
- log_trace("Window position changed, %dx%d -> %dx%d", mw->g.x,
- mw->g.y, ce->x, ce->y);
- mw->pending_g.x = ce->x;
- mw->pending_g.y = ce->y;
- win_set_flags(mw, WIN_FLAGS_POSITION_STALE);
+ log_trace("Window position changed, %dx%d -> %dx%d", w->g.x,
+ w->g.y, ce->x, ce->y);
+ w->pending_g.x = ce->x;
+ w->pending_g.y = ce->y;
+ win_set_flags(w, WIN_FLAGS_POSITION_STALE);
}
if (size_changed) {
- log_trace("Window size changed, %dx%d -> %dx%d", mw->g.width,
- mw->g.height, ce->width, ce->height);
- mw->pending_g.width = ce->width;
- mw->pending_g.height = ce->height;
- mw->pending_g.border_width = ce->border_width;
- win_set_flags(mw, WIN_FLAGS_SIZE_STALE);
+ log_trace("Window size changed, %dx%d -> %dx%d", w->g.width,
+ w->g.height, ce->width, ce->height);
+ w->pending_g.width = ce->width;
+ w->pending_g.height = ce->height;
+ w->pending_g.border_width = ce->border_width;
+ win_set_flags(w, WIN_FLAGS_SIZE_STALE);
}
-
- // Recalculate which monitor this window is on
- win_update_monitor(&ps->monitors, mw);
}
// override_redirect flag cannot be changed after window creation, as far
// as I know, so there's no point to re-match windows here.
- mw->a.override_redirect = ce->override_redirect;
+ w->a.override_redirect = ce->override_redirect;
}
static inline void ev_configure_notify(session_t *ps, xcb_configure_notify_event_t *ev) {
- log_debug("{ send_event: %d, id: %#010x, above: %#010x, override_redirect: %d }",
+ log_debug("{ event: %#010x, id: %#010x, above: %#010x, override_redirect: %d }",
ev->event, ev->window, ev->above_sibling, ev->override_redirect);
+
+ if (ps->overlay && ev->window == ps->overlay) {
+ return;
+ }
+
if (ev->window == ps->c.screen_info->root) {
- set_root_flags(ps, ROOT_FLAGS_CONFIGURED);
+ configure_root(ps);
} else {
configure_win(ps, ev);
}
}
static inline void ev_destroy_notify(session_t *ps, xcb_destroy_notify_event_t *ev) {
- auto subwin = wm_subwin_find(ps->wm, ev->window);
- if (subwin) {
- wm_subwin_remove(ps->wm, subwin);
- }
-
- auto w = wm_find(ps->wm, ev->window);
- auto mw = wm_find_by_client(ps->wm, ev->window);
- if (mw && mw->client_win == mw->base.id) {
- // We only want _real_ frame window
- assert(&mw->base == w);
- mw = NULL;
- }
-
- // A window can't be a client window and a top-level window at the same time,
- // so only one of `w` and `mw` can be non-NULL
- assert(w == NULL || mw == NULL);
-
- if (w != NULL) {
- destroy_win_start(ps, w);
- if (!w->managed) {
- // If the window wasn't managed, we can release it immediately
- destroy_win_finish(ps, w);
- }
- return;
- }
- if (mw != NULL) {
- win_unmark_client(mw);
- win_set_flags(mw, WIN_FLAGS_CLIENT_STALE);
- ps->pending_updates = true;
- return;
- }
- log_debug("Received a destroy notify from an unknown window, %#010x", ev->window);
+ log_debug("{ event: %#010x, id: %#010x }", ev->event, ev->window);
+ wm_destroy(ps->wm, ev->window);
}
static inline void ev_map_notify(session_t *ps, xcb_map_notify_event_t *ev) {
// Unmap overlay window if it got mapped but we are currently not
// in redirected state.
- if (ps->overlay && ev->window == ps->overlay && !ps->redirected) {
- log_debug("Overlay is mapped while we are not redirected");
- auto e = xcb_request_check(
- ps->c.c, xcb_unmap_window_checked(ps->c.c, ps->overlay));
- if (e) {
- log_error("Failed to unmap the overlay window");
- free(e);
+ if (ps->overlay && ev->window == ps->overlay) {
+ if (!ps->redirected) {
+ log_debug("Overlay is mapped while we are not redirected");
+ auto succeeded =
+ XCB_AWAIT_VOID(xcb_unmap_window, ps->c.c, ps->overlay);
+ if (!succeeded) {
+ log_error("Failed to unmap the overlay window");
+ }
}
// We don't track the overlay window, so we can return
return;
}
- auto w = wm_find_managed(ps->wm, ev->window);
- if (!w) {
+ auto cursor = wm_find(ps->wm, ev->window);
+ if (cursor == NULL) {
+ if (wm_is_consistent(ps->wm)) {
+ log_debug("Map event received for unknown window %#010x, overlay "
+ "is %#010x",
+ ev->window, ps->overlay);
+ assert(false);
+ }
return;
}
+ auto w = wm_ref_deref(cursor);
+ if (w == NULL) {
+ return;
+ }
win_set_flags(w, WIN_FLAGS_MAPPED);
// We set `ever_damaged` to false here, instead of in `map_win_start`,
// because we might receive damage events before that function is called
@@ -363,142 +462,50 @@ static inline void ev_map_notify(session_t *ps, xcb_map_notify_event_t *ev) {
}
static inline void ev_unmap_notify(session_t *ps, xcb_unmap_notify_event_t *ev) {
- auto w = wm_find_managed(ps->wm, ev->window);
- if (w) {
- unmap_win_start(w);
- }
-}
-
-static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t *ev) {
- log_debug("Window %#010x has new parent: %#010x, override_redirect: %d",
- ev->window, ev->parent, ev->override_redirect);
-
- auto old_toplevel = wm_find_by_client(ps->wm, ev->window);
- auto old_w = wm_find(ps->wm, ev->window);
- auto old_subwin = wm_subwin_find(ps->wm, ev->window);
- auto new_toplevel = wm_find_managed(ps->wm, ev->parent);
- xcb_window_t old_parent = XCB_NONE;
- if (old_subwin) {
- old_parent = old_subwin->toplevel;
- } else if (old_w) {
- old_parent = ps->c.screen_info->root;
- }
- if (old_toplevel && old_toplevel->client_win == old_toplevel->base.id) {
- // We only want _real_ frame window, meaning old_toplevel must be
- // a parent of `ev->window`.
- old_toplevel = NULL;
- }
- // A window can't be a toplevel and a subwin at the same time
- assert(old_w == NULL || old_subwin == NULL);
-
- log_debug("old toplevel: %p, old subwin: %p, new toplevel: %p, old parent: "
- "%#010x, new parent: %#010x, root window: %#010x",
- old_w, old_subwin, new_toplevel, old_parent, ev->parent,
- ps->c.screen_info->root);
-
- if (old_w == NULL && old_subwin == NULL && new_toplevel == NULL &&
- ev->parent != ps->c.screen_info->root) {
- // The window is neither a toplevel nor a subwin, and the new parent is
- // neither a root nor a toplevel, we don't care about this window.
- // This can happen if a window is reparented to somewhere irrelevant, but
- // more events from it are generated before we can unsubscribe.
+ if (ps->overlay && ev->window == ps->overlay) {
return;
}
- if (ev->parent == old_parent) {
- // Parent unchanged, but if the parent is root, we need to move the window
- // to the top of the window stack
- if (old_w) {
- // root -> root reparent, we just need to move it to the top
- log_debug("Restack %#010x (%s) to top", old_w->id,
- win_get_name_if_managed(old_w));
- wm_stack_move_to_top(ps->wm, old_w);
- if (old_w->managed) {
- add_damage_from_win(ps, win_as_managed(old_w));
- }
+ auto cursor = wm_find(ps->wm, ev->window);
+ if (cursor == NULL) {
+ if (wm_is_consistent(ps->wm)) {
+ log_error("Unmap event received for unknown window %#010x", ev->window);
+ assert(false);
}
return;
}
-
- if (old_toplevel) {
- assert(old_subwin != NULL);
- assert(old_subwin->toplevel == old_toplevel->base.id);
- win_unmark_client(old_toplevel);
- win_set_flags(old_toplevel, WIN_FLAGS_CLIENT_STALE);
- ps->pending_updates = true;
- }
-
- if (old_w) {
- // A toplevel is reparented, so it is no longer a toplevel. We need to
- // destroy the existing toplevel.
- if (old_w->managed) {
- auto mw = (struct managed_win *)old_w;
- // Usually, damage for unmapped windows are added in
- // `paint_preprocess`, when a window was painted before and isn't
- // anymore. But since we are reparenting the window here, we would
- // lose track of the `to_paint` information. So we just add damage
- // here.
- if (mw->to_paint) {
- add_damage_from_win(ps, mw);
- }
- // Emulating what X server does: a destroyed
- // window is always unmapped first.
- if (mw->state == WSTATE_MAPPED) {
- unmap_win_start(mw);
- }
-
- // If an animation is running, the best we could do is stopping
- // it.
- free(mw->running_animation);
- mw->running_animation = NULL;
- }
- destroy_win_start(ps, old_w);
- destroy_win_finish(ps, old_w);
- }
-
- // We need to guarantee a subwin exists iff it has a valid toplevel.
- auto new_subwin = old_subwin;
- if (new_subwin != NULL && new_toplevel == NULL) {
- wm_subwin_remove_and_unsubscribe(ps->wm, &ps->c, new_subwin);
- new_subwin = NULL;
- } else if (new_subwin == NULL && new_toplevel != NULL) {
- new_subwin =
- wm_subwin_add_and_subscribe(ps->wm, &ps->c, ev->window, ev->parent);
- }
- if (new_subwin) {
- new_subwin->toplevel = new_toplevel->base.id;
- if (new_toplevel->client_win == XCB_NONE ||
- new_toplevel->client_win == new_toplevel->base.id) {
- win_set_flags(new_toplevel, WIN_FLAGS_CLIENT_STALE);
- ps->pending_updates = true;
- }
+ auto w = wm_ref_deref(cursor);
+ if (w != NULL) {
+ unmap_win_start(w);
}
+}
- if (ev->parent == ps->c.screen_info->root) {
- // New parent is root, add a toplevel;
- assert(old_w == NULL);
- assert(new_toplevel == NULL);
- wm_stack_add_top(ps->wm, ev->window);
- ps->pending_updates = true;
- }
+static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t *ev) {
+ log_debug("Window %#010x has new parent: %#010x, override_redirect: %d, "
+ "send_event: %#010x",
+ ev->window, ev->parent, ev->override_redirect, ev->event);
+ wm_reparent(ps->wm, ev->window, ev->parent);
}
static inline void ev_circulate_notify(session_t *ps, xcb_circulate_notify_event_t *ev) {
- auto w = wm_find(ps->wm, ev->window);
+ auto cursor = wm_find(ps->wm, ev->window);
- if (!w) {
+ if (cursor == NULL) {
+ if (wm_is_consistent(ps->wm)) {
+ log_debug("Circulate event received for unknown window %#010x",
+ ev->window);
+ assert(false);
+ }
return;
}
- log_debug("Moving window %#010x (%s) to the %s", w->id,
- win_get_name_if_managed(w), ev->place == PlaceOnTop ? "top" : "bottom");
- if (ev->place == PlaceOnTop) {
- wm_stack_move_to_top(ps->wm, w);
- } else {
- wm_stack_move_to_bottom(ps->wm, w);
- }
- if (w->managed) {
- add_damage_from_win(ps, win_as_managed(w));
+ log_debug("Moving window %#010x (%s) to the %s", ev->window,
+ ev_window_name(ps, ev->window), ev->place == PlaceOnTop ? "top" : "bottom");
+ wm_stack_move_to_end(ps->wm, cursor, ev->place == XCB_PLACE_ON_BOTTOM);
+
+ auto w = wm_ref_deref(cursor);
+ if (w != NULL) {
+ add_damage_from_win(ps, w);
}
}
@@ -529,51 +536,9 @@ static inline void ev_expose(session_t *ps, xcb_expose_event_t *ev) {
}
}
-static inline void ev_subwin_wm_state_changed(session_t *ps, xcb_property_notify_event_t *ev) {
- auto subwin = wm_subwin_find(ps->wm, ev->window);
- if (!subwin) {
- // We only care if a direct child of a toplevel gained/lost WM_STATE
- return;
- }
-
- enum tristate old_has_wm_state = subwin->has_wm_state;
- subwin->has_wm_state = ev->state == XCB_PROPERTY_DELETE ? TRI_FALSE : TRI_TRUE;
- if (old_has_wm_state == subwin->has_wm_state) {
- if (subwin->has_wm_state == TRI_FALSE) {
- log_warn("Child window %#010x of window %#010x lost WM_STATE a "
- "second time?",
- ev->window, subwin->toplevel);
- }
- return;
- }
-
- auto toplevel = wm_find(ps->wm, subwin->toplevel);
- BUG_ON(toplevel == NULL);
- if (!toplevel->managed) {
- return;
- }
-
- auto managed = (struct managed_win *)toplevel;
- if (managed->client_win == subwin->id) {
- // 1. This window is the client window of its toplevel, and now it lost
- // its WM_STATE (implies it must have it before)
- assert(subwin->has_wm_state == TRI_FALSE);
- win_set_flags(managed, WIN_FLAGS_CLIENT_STALE);
- } else if (subwin->has_wm_state == TRI_TRUE) {
- // 2. This window is not the client window of its toplevel, and
- // now it gained WM_STATE
- if (managed->client_win != XCB_NONE && managed->client_win != toplevel->id) {
- log_warn("Toplevel %#010x already has a client window %#010x, "
- "but another of its child windows %#010x gained "
- "WM_STATE.",
- toplevel->id, managed->client_win, subwin->id);
- } else {
- win_set_flags(managed, WIN_FLAGS_CLIENT_STALE);
- }
- }
-}
-
static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t *ev) {
+ log_debug("{ atom = %#010x, window = %#010x, state = %d }", ev->atom, ev->window,
+ ev->state);
if (unlikely(log_get_level_tls() <= LOG_LEVEL_TRACE)) {
// Print out changed atom
xcb_get_atom_name_reply_t *reply = xcb_get_atom_name_reply(
@@ -604,8 +569,16 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
}
if (ps->o.use_ewmh_active_win && ps->atoms->a_NET_ACTIVE_WINDOW == ev->atom) {
- // to update focus
- ps->pending_updates = true;
+ auto req = ccalloc(1, struct ev_ewmh_active_win_request);
+ req->base.sequence =
+ xcb_get_property(ps->c.c, 0, ps->c.screen_info->root,
+ ps->atoms->a_NET_ACTIVE_WINDOW,
+ XCB_ATOM_WINDOW, 0, 1)
+ .sequence;
+ req->base.callback = update_ewmh_active_win;
+ req->ps = ps;
+ x_await_request(&ps->c, &req->base);
+ log_debug("Started async request to get _NET_ACTIVE_WINDOW");
} else {
// Destroy the root "image" if the wallpaper probably changed
if (x_is_root_back_pixmap_atom(ps->atoms, ev->atom)) {
@@ -618,9 +591,35 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
}
ps->pending_updates = true;
- auto w = wm_find_by_client(ps->wm, ev->window);
+ auto cursor = wm_find(ps->wm, ev->window);
+ if (cursor == NULL) {
+ if (wm_is_consistent(ps->wm)) {
+ log_error("Property notify received for unknown window %#010x",
+ ev->window);
+ assert(false);
+ }
+ return;
+ }
+
+ auto toplevel_cursor = wm_ref_toplevel_of(ps->wm, cursor);
if (ev->atom == ps->atoms->aWM_STATE) {
- ev_subwin_wm_state_changed(ps, ev);
+ log_debug("WM_STATE changed for window %#010x (%s): %s", ev->window,
+ ev_window_name(ps, ev->window),
+ ev->state == XCB_PROPERTY_DELETE ? "deleted" : "set");
+ wm_set_has_wm_state(ps->wm, cursor, ev->state != XCB_PROPERTY_DELETE);
+ }
+
+ if (toplevel_cursor == NULL) {
+ assert(!wm_is_consistent(ps->wm));
+ return;
+ }
+
+ // We only care if the property is set on the toplevel itself, or on its
+ // client window if it has one. WM_STATE is an exception, it is handled
+ // always because it is what determines if a window is a client window.
+ auto client_cursor = wm_ref_client_of(toplevel_cursor) ?: toplevel_cursor;
+ if (cursor != client_cursor && cursor != toplevel_cursor) {
+ return;
}
if (ev->atom == ps->atoms->a_NET_WM_BYPASS_COMPOSITOR) {
@@ -628,41 +627,34 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
queue_redraw(ps);
}
- if (w) {
- win_set_property_stale(w, ev->atom);
+ auto toplevel = wm_ref_deref(toplevel_cursor);
+ if (toplevel) {
+ win_set_property_stale(toplevel, ev->atom);
}
- if (ev->atom == ps->atoms->a_NET_WM_WINDOW_OPACITY) {
+ if (ev->atom == ps->atoms->a_NET_WM_WINDOW_OPACITY && toplevel != NULL) {
// We already handle if this is set on the client window, check
// if this is set on the frame window as well.
// TODO(yshui) do we really need this?
- auto toplevel = wm_find_managed(ps->wm, ev->window);
- if (toplevel) {
- win_set_property_stale(toplevel, ev->atom);
- }
+ win_set_property_stale(toplevel, ev->atom);
}
// Check for other atoms we are tracking
if (c2_state_is_property_tracked(ps->c2_state, ev->atom)) {
- bool change_is_on_client = false;
- w = wm_find_managed(ps->wm, ev->window);
- if (!w) {
- w = wm_find_by_client(ps->wm, ev->window);
- change_is_on_client = true;
- }
- if (w) {
- c2_window_state_mark_dirty(ps->c2_state, &w->c2_state, ev->atom,
- change_is_on_client);
+ bool change_is_on_client = cursor == client_cursor;
+ if (toplevel) {
+ c2_window_state_mark_dirty(ps->c2_state, &toplevel->c2_state,
+ ev->atom, change_is_on_client);
// Set FACTOR_CHANGED so rules based on properties will be
// re-evaluated.
// Don't need to set property stale here, since that only
// concerns properties we explicitly check.
- win_set_flags(w, WIN_FLAGS_FACTOR_CHANGED);
+ win_set_flags(toplevel, WIN_FLAGS_FACTOR_CHANGED);
}
}
}
-static inline void repair_win(session_t *ps, struct managed_win *w) {
+static inline void repair_win(session_t *ps, struct win *w) {
// Only mapped window can receive damages
assert(w->state == WSTATE_MAPPED || win_check_flags_all(w, WIN_FLAGS_MAPPED));
@@ -687,20 +679,20 @@ static inline void repair_win(session_t *ps, struct managed_win *w) {
free(e);
}
win_extents(w, &parts);
- log_debug("Window %#010x (%s) has been damaged the first time",
- w->base.id, w->name);
+ log_debug("Window %#010x (%s) has been damaged the first time", win_id(w),
+ w->name);
} else {
auto cookie = xcb_damage_subtract(ps->c.c, w->damage, XCB_NONE,
ps->damage_ring.x_region);
if (!ps->o.show_all_xerrors) {
- set_ignore_cookie(&ps->c, cookie);
+ x_set_error_action_ignore(&ps->c, cookie);
}
x_fetch_region(&ps->c, ps->damage_ring.x_region, &parts);
pixman_region32_translate(&parts, w->g.x + w->g.border_width,
w->g.y + w->g.border_width);
}
- log_trace("Mark window %#010x (%s) as having received damage", w->base.id, w->name);
+ log_trace("Mark window %#010x (%s) as having received damage", win_id(w), w->name);
w->ever_damaged = true;
w->pixmap_damaged = true;
@@ -727,24 +719,29 @@ static inline void repair_win(session_t *ps, struct managed_win *w) {
}
static inline void ev_damage_notify(session_t *ps, xcb_damage_notify_event_t *de) {
- /*
- if (ps->root == de->drawable) {
- root_damaged();
- return;
- } */
+ auto cursor = wm_find(ps->wm, de->drawable);
- auto w = wm_find_managed(ps->wm, de->drawable);
-
- if (!w) {
+ if (cursor == NULL) {
+ log_error("Damage notify received for unknown window %#010x", de->drawable);
return;
}
- repair_win(ps, w);
+ auto w = wm_ref_deref(cursor);
+ if (w != NULL) {
+ repair_win(ps, w);
+ }
}
static inline void ev_shape_notify(session_t *ps, xcb_shape_notify_event_t *ev) {
- auto w = wm_find_managed(ps->wm, ev->affected_window);
- if (!w || w->a.map_state == XCB_MAP_STATE_UNMAPPED) {
+ auto cursor = wm_find(ps->wm, ev->affected_window);
+ if (cursor == NULL) {
+ log_error("Shape notify received for unknown window %#010x",
+ ev->affected_window);
+ return;
+ }
+
+ auto w = wm_ref_deref(cursor);
+ if (w == NULL || w->a.map_state == XCB_MAP_STATE_UNMAPPED) {
return;
}
@@ -776,10 +773,6 @@ ev_selection_clear(session_t *ps, xcb_selection_clear_event_t attr_unused *ev) {
}
void ev_handle(session_t *ps, xcb_generic_event_t *ev) {
- if (XCB_EVENT_RESPONSE_TYPE(ev) != KeymapNotify) {
- x_discard_pending(&ps->c, ev->full_sequence);
- }
-
xcb_window_t wid = ev_window(ps, ev);
if (ev->response_type != ps->damage_event + XCB_DAMAGE_NOTIFY) {
log_debug("event %10.10s serial %#010x window %#010x \"%s\"",
@@ -849,15 +842,14 @@ void ev_handle(session_t *ps, xcb_generic_event_t *ev) {
case XCB_SELECTION_CLEAR:
ev_selection_clear(ps, (xcb_selection_clear_event_t *)ev);
break;
- case 0: x_handle_error(&ps->c, (xcb_generic_error_t *)ev); break;
default:
if (ps->shape_exists && ev->response_type == ps->shape_event) {
ev_shape_notify(ps, (xcb_shape_notify_event_t *)ev);
break;
}
- if (ps->randr_exists &&
+ if (ps->randr_exists && ps->o.crop_shadow_to_monitor &&
ev->response_type == (ps->randr_event + XCB_RANDR_SCREEN_CHANGE_NOTIFY)) {
- set_root_flags(ps, ROOT_FLAGS_SCREEN_CHANGE);
+ x_update_monitors_async(&ps->c, &ps->monitors);
break;
}
if (ps->damage_event + XCB_DAMAGE_NOTIFY == ev->response_type) {
diff --git a/src/event.h b/src/event.h
index 629dec0f3f..675b4d8c10 100644
--- a/src/event.h
+++ b/src/event.h
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2019, Yuxuan Shui
+#pragma once
#include
#include "common.h"
diff --git a/src/inspect.c b/src/inspect.c
index 3c5de79889..d6e0160225 100644
--- a/src/inspect.c
+++ b/src/inspect.c
@@ -12,6 +12,7 @@
#include "inspect.h"
#include "atom.h"
+#include "backend/backend.h"
#include "c2.h"
#include "common.h"
#include "config.h"
@@ -22,22 +23,33 @@
#include "wm/win.h"
#include "x.h"
-static struct managed_win *
+static struct win *
setup_window(struct x_connection *c, struct atom *atoms, struct options *options,
- struct c2_state *state, xcb_window_t target) {
+ struct wm *wm, struct c2_state *state, xcb_window_t target) {
// Pretend we are the compositor, and build up the window state
- struct managed_win *w = ccalloc(1, struct managed_win);
+ auto cursor = wm_find(wm, target);
+ if (cursor == NULL) {
+ log_fatal("Could not find window %#010x", target);
+ wm_free(wm);
+ return NULL;
+ }
+
+ auto toplevel = wm_ref_toplevel_of(wm, cursor);
+ BUG_ON_NULL(toplevel);
+ struct win *w = ccalloc(1, struct win);
w->state = WSTATE_MAPPED;
- w->base.id = target;
- w->client_win = win_get_client_window(c, NULL, atoms, w);
+ w->tree_ref = toplevel;
+ log_debug("Toplevel is %#010x", wm_ref_win_id(toplevel));
+ log_debug("Client is %#010x", win_client_id(w, true));
win_update_wintype(c, atoms, w);
- win_update_frame_extents(c, atoms, w, w->client_win, options->frame_opacity);
+ win_update_frame_extents(c, atoms, w, win_client_id(w, /*fallback_to_self=*/true),
+ options->frame_opacity);
// TODO(yshui) get leader
win_update_name(c, atoms, w);
win_update_class(c, atoms, w);
win_update_role(c, atoms, w);
- auto geometry_reply = XCB_AWAIT(xcb_get_geometry, c->c, w->base.id);
+ auto geometry_reply = XCB_AWAIT(xcb_get_geometry, c->c, win_id(w));
w->g = (struct win_geometry){
.x = geometry_reply->x,
.y = geometry_reply->y,
@@ -54,8 +66,10 @@ setup_window(struct x_connection *c, struct atom *atoms, struct options *options
// Determine if the window is focused
xcb_window_t wid = XCB_NONE;
+ bool exists;
if (options->use_ewmh_active_win) {
- wid_get_prop_window(c, c->screen_info->root, atoms->a_NET_ACTIVE_WINDOW);
+ wid_get_prop_window(c, c->screen_info->root, atoms->a_NET_ACTIVE_WINDOW,
+ &exists);
} else {
// Determine the currently focused window so we can apply appropriate
// opacity on it
@@ -67,17 +81,18 @@ setup_window(struct x_connection *c, struct atom *atoms, struct options *options
free(reply);
}
}
- if (wid == w->base.id || wid == w->client_win) {
+ if (wid == win_id(w) || wid == win_client_id(w, /*fallback_to_self=*/false)) {
w->focused = true;
}
- auto attributes_reply = XCB_AWAIT(xcb_get_window_attributes, c->c, w->base.id);
+ auto attributes_reply = XCB_AWAIT(xcb_get_window_attributes, c->c, win_id(w));
w->a = *attributes_reply;
w->pictfmt = x_get_pictform_for_visual(c, w->a.visual);
free(attributes_reply);
c2_window_state_init(state, &w->c2_state);
- c2_window_state_update(state, &w->c2_state, c->c, w->client_win, w->base.id);
+ c2_window_state_update(state, &w->c2_state, c->c,
+ win_client_id(w, /*fallback_to_self=*/true), win_id(w));
return w;
}
@@ -139,7 +154,7 @@ xcb_window_t select_window(struct x_connection *c) {
struct c2_match_state {
struct c2_state *state;
- struct managed_win *w;
+ struct win *w;
bool print_value;
};
@@ -176,19 +191,51 @@ int inspect_main(int argc, char **argv, const char *config_file) {
}
// Parse all of the rest command line options
+ options.backend = backend_find("dummy");
if (!get_cfg(&options, argc, argv)) {
log_fatal("Failed to get configuration, usually mean you have specified "
"invalid options.");
return 1;
}
- auto atoms attr_unused = init_atoms(c.c);
+ auto atoms = init_atoms(c.c);
auto state = c2_state_new(atoms);
options_postprocess_c2_lists(state, &c, &options);
+ struct wm *wm = wm_new();
+
+ wm_import_start(wm, &c, atoms, c.screen_info->root, NULL);
+ // Process events until the window tree is consistent
+ while (x_has_pending_requests(&c)) {
+ auto ev = x_poll_for_event(&c);
+ if (ev == NULL) {
+ continue;
+ }
+ switch (ev->response_type) {
+ case XCB_CREATE_NOTIFY:;
+ auto create = (xcb_create_notify_event_t *)ev;
+ auto parent = wm_find(wm, create->parent);
+ wm_import_start(wm, &c, atoms,
+ ((xcb_create_notify_event_t *)ev)->window, parent);
+ break;
+ case XCB_DESTROY_NOTIFY:
+ wm_destroy(wm, ((xcb_destroy_notify_event_t *)ev)->window);
+ break;
+ case XCB_REPARENT_NOTIFY:;
+ auto reparent = (xcb_reparent_notify_event_t *)ev;
+ wm_reparent(wm, reparent->window, reparent->parent);
+ break;
+ default:
+ // Ignore ConfigureNotify and CirculateNotify, because we don't
+ // use stacking order for window rules.
+ break;
+ }
+ free(ev);
+ }
+
auto target = select_window(&c);
log_info("Target window: %#x", target);
- auto w = setup_window(&c, atoms, &options, state, target);
+ auto w = setup_window(&c, atoms, &options, wm, state, target);
struct c2_match_state match_state = {
.state = state,
.w = w,
@@ -250,6 +297,8 @@ int inspect_main(int argc, char **argv, const char *config_file) {
c2_window_state_destroy(state, &w->c2_state);
free(w);
+ wm_free(wm);
+
log_deinit_tls();
c2_state_free(state);
destroy_atoms(atoms);
diff --git a/src/opengl.c b/src/opengl.c
index e80b209984..a1b9b4266d 100644
--- a/src/opengl.c
+++ b/src/opengl.c
@@ -233,8 +233,11 @@ void glx_destroy(session_t *ps) {
}
// Free all GLX resources of windows
- win_stack_foreach_managed(w, wm_stack_end(ps->wm)) {
- free_win_res_glx(ps, w);
+ wm_stack_foreach(ps->wm, cursor) {
+ auto w = wm_ref_deref(cursor);
+ if (w != NULL) {
+ free_win_res_glx(ps, w);
+ }
}
// Free GLSL shaders/programs
@@ -1160,9 +1163,9 @@ void glx_read_border_pixel(int root_height, int root_width, int x, int y, int wi
gl_check_err();
}
-bool glx_round_corners_dst(session_t *ps, struct managed_win *w,
- const glx_texture_t *ptex, int dx, int dy, int width,
- int height, float z, float cr, const region_t *reg_tgt) {
+bool glx_round_corners_dst(session_t *ps, struct win *w, const glx_texture_t *ptex,
+ int dx, int dy, int width, int height, float z, float cr,
+ const region_t *reg_tgt) {
assert(ps->psglx->round_passes->prog);
bool ret = false;
@@ -1533,7 +1536,7 @@ bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx,
/**
* Free GLX part of win.
*/
-void free_win_res_glx(session_t *ps, struct managed_win *w) {
+void free_win_res_glx(session_t *ps, struct win *w) {
free_paint_glx(ps, &w->paint);
free_paint_glx(ps, &w->shadow_paint);
free_glx_bc(ps, &w->glx_blur_cache);
diff --git a/src/opengl.h b/src/opengl.h
index a10d960d42..60e1fa32b3 100644
--- a/src/opengl.h
+++ b/src/opengl.h
@@ -122,9 +122,9 @@ void glx_set_clip(session_t *ps, const region_t *reg);
bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z,
GLfloat factor_center, const region_t *reg_tgt, glx_blur_cache_t *pbc);
-bool glx_round_corners_dst(session_t *ps, struct managed_win *w,
- const glx_texture_t *ptex, int dx, int dy, int width,
- int height, float z, float cr, const region_t *reg_tgt);
+bool glx_round_corners_dst(session_t *ps, struct win *w, const glx_texture_t *ptex,
+ int dx, int dy, int width, int height, float z, float cr,
+ const region_t *reg_tgt);
GLuint glx_create_shader(GLenum shader_type, const char *shader_str);
@@ -224,4 +224,4 @@ static inline void free_paint_glx(session_t *ps, paint_t *ppaint) {
/**
* Free GLX part of win.
*/
-void free_win_res_glx(session_t *ps, struct managed_win *w);
+void free_win_res_glx(session_t *ps, struct win *w);
diff --git a/src/options.c b/src/options.c
index b942886a63..bd24b16a04 100644
--- a/src/options.c
+++ b/src/options.c
@@ -796,7 +796,7 @@ static bool sanitize_options(struct options *opt) {
for (size_t i = 0; i < ARR_SIZE(opt->animations); i++) {
opt->animations[i].script = NULL;
}
- dynarr_free(opt->all_scripts, script_ptr_deinit);
+ dynarr_clear(opt->all_scripts, script_ptr_deinit);
}
if (opt->window_shader_fg || opt->window_shader_fg_rules) {
diff --git a/src/picom.c b/src/picom.c
index cab27de21d..ddcde91c11 100644
--- a/src/picom.c
+++ b/src/picom.c
@@ -14,7 +14,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -126,12 +125,6 @@ const char *const BACKEND_STRS[] = {[BKEND_XRENDER] = "xrender",
/// XXX Limit what xerror can access by not having this pointer
session_t *ps_g = NULL;
-void set_root_flags(session_t *ps, uint64_t flags) {
- log_debug("Setting root flags: %" PRIu64, flags);
- ps->root_flags |= flags;
- ps->pending_updates = true;
-}
-
void quit(session_t *ps) {
ps->quit = true;
ev_break(ps->loop, EVBREAK_ALL);
@@ -450,90 +443,6 @@ void add_damage(session_t *ps, const region_t *damage) {
// === Windows ===
-/**
- * Update current active window based on EWMH _NET_ACTIVE_WIN.
- *
- * Does not change anything if we fail to get the attribute or the window
- * returned could not be found.
- */
-void update_ewmh_active_win(session_t *ps) {
- // Search for the window
- xcb_window_t wid = wid_get_prop_window(&ps->c, ps->c.screen_info->root,
- ps->atoms->a_NET_ACTIVE_WINDOW);
- auto w = wm_find_by_client(ps->wm, wid);
-
- // Mark the window focused. No need to unfocus the previous one.
- if (w) {
- win_set_focused(ps, w);
- }
-}
-
-/**
- * Recheck currently focused window and set its w->focused
- * to true.
- *
- * @param ps current session
- * @return struct _win of currently focused window, NULL if not found
- */
-static void recheck_focus(session_t *ps) {
- // Use EWMH _NET_ACTIVE_WINDOW if enabled
- if (ps->o.use_ewmh_active_win) {
- update_ewmh_active_win(ps);
- return;
- }
-
- // Determine the currently focused window so we can apply appropriate
- // opacity on it
- xcb_generic_error_t *e = NULL;
- auto reply = xcb_get_input_focus_reply(ps->c.c, xcb_get_input_focus(ps->c.c), &e);
- if (reply == NULL) {
- // Not able to get input focus means very not good things...
- log_error_x_error(e, "Failed to get focused window.");
- free(e);
- return;
- }
- xcb_window_t wid = reply->focus;
- free(reply);
-
- if (wid == XCB_NONE || wid == XCB_INPUT_FOCUS_POINTER_ROOT ||
- wid == ps->c.screen_info->root) {
- // Focus is not on a toplevel.
- return;
- }
-
- // Trace upwards until we reach the toplevel containing the focus window.
- while (true) {
- auto tree = xcb_query_tree_reply(ps->c.c, xcb_query_tree(ps->c.c, wid), &e);
- if (tree == NULL) {
- // xcb_query_tree probably fails if you run picom when X is
- // somehow initializing (like add it in .xinitrc). In this case
- // just leave it alone.
- log_error_x_error(e, "Failed to query window tree.");
- free(e);
- return;
- }
-
- auto parent = tree->parent;
- free(tree);
-
- if (parent == ps->c.screen_info->root) {
- break;
- }
- wid = parent;
- }
-
- auto w = wm_find_managed(ps->wm, wid);
-
- // And we set the focus state here
- if (w) {
- log_debug("%#010" PRIx32 " (%#010" PRIx32 " \"%s\") focused.", wid,
- w->base.id, w->name);
- win_set_focused(ps, w);
- } else {
- log_warn("Focus window %#010" PRIx32 " not found.", wid);
- }
-}
-
/**
* Rebuild cached screen_reg
.
*/
@@ -543,7 +452,11 @@ static void rebuild_screen_reg(session_t *ps) {
/// Free up all the images and deinit the backend
static void destroy_backend(session_t *ps) {
- win_stack_foreach_managed_safe(w, wm_stack_end(ps->wm)) {
+ wm_stack_foreach_safe(ps->wm, cursor, next_cursor) {
+ auto w = wm_ref_deref(cursor);
+ if (w == NULL) {
+ continue;
+ }
// An unmapped window shouldn't have a pixmap, unless it has animation
// running. (`w->previous.state != w->state` means there might be
// animation but we haven't had a chance to start it because
@@ -569,7 +482,7 @@ static void destroy_backend(session_t *ps) {
free_paint(ps, &w->paint);
if (w->state == WSTATE_DESTROYED) {
- destroy_win_finish(ps, &w->base);
+ win_destroy_finish(ps, w);
}
}
@@ -640,20 +553,6 @@ static bool initialize_blur(session_t *ps) {
return ps->backend_blur_context != NULL;
}
-static int mark_pixmap_stale(struct win *w, void *data) {
- struct session *ps = data;
- if (!w->managed) {
- return 0;
- }
- auto mw = win_as_managed(w);
- assert(mw->state != WSTATE_DESTROYED);
- // We need to reacquire image
- log_debug("Marking window %#010x (%s) for update after redirection", w->id, mw->name);
- win_set_flags(mw, WIN_FLAGS_PIXMAP_STALE);
- ps->pending_updates = true;
- return 0;
-}
-
/// Init the backend and bind all the window pixmap to backend images
static bool initialize_backend(session_t *ps) {
if (!ps->o.use_legacy_backends) {
@@ -701,9 +600,18 @@ static bool initialize_backend(session_t *ps) {
}
}
- // wm_stack shouldn't include window that's not iterated by wm_foreach at
- // this moment. Since there cannot be any fading windows.
- wm_foreach(ps->wm, mark_pixmap_stale, ps);
+ wm_stack_foreach(ps->wm, cursor) {
+ auto w = wm_ref_deref(cursor);
+ if (w != NULL) {
+ assert(w->state != WSTATE_DESTROYED);
+ // We need to reacquire image
+ log_debug("Marking window %#010x (%s) for update after "
+ "redirection",
+ win_id(w), w->name);
+ win_set_flags(w, WIN_FLAGS_PIXMAP_STALE);
+ ps->pending_updates = true;
+ }
+ }
ps->renderer = renderer_new(ps->backend_data, ps->o.shadow_radius,
(struct color){.alpha = ps->o.shadow_opacity,
.red = ps->o.shadow_red,
@@ -725,8 +633,20 @@ static bool initialize_backend(session_t *ps) {
return false;
}
+static inline void invalidate_reg_ignore(session_t *ps) {
+ // Invalidate reg_ignore from the top
+ wm_stack_foreach(ps->wm, cursor) {
+ auto top_w = wm_ref_deref(cursor);
+ if (top_w != NULL) {
+ rc_region_unref(&top_w->reg_ignore);
+ top_w->reg_ignore_valid = false;
+ break;
+ }
+ }
+}
+
/// Handle configure event of the root window
-static void configure_root(session_t *ps) {
+void configure_root(session_t *ps) {
// TODO(yshui) re-initializing backend should be done outside of the
// critical section. Probably set a flag and do it in draw_callback_impl.
auto r = XCB_AWAIT(xcb_get_geometry, ps->c.c, ps->c.screen_info->root);
@@ -760,19 +680,16 @@ static void configure_root(session_t *ps) {
free(r);
rebuild_screen_reg(ps);
-
- // Invalidate reg_ignore from the top
- auto top_w = wm_stack_next_managed(ps->wm, wm_stack_end(ps->wm));
- if (top_w) {
- rc_region_unref(&top_w->reg_ignore);
- top_w->reg_ignore_valid = false;
- }
+ invalidate_reg_ignore(ps);
// Whether a window is fullscreen depends on the new screen
// size. So we need to refresh the fullscreen state of all
// windows.
- win_stack_foreach_managed(w, wm_stack_end(ps->wm)) {
- win_update_is_fullscreen(ps, w);
+ wm_stack_foreach(ps->wm, cursor) {
+ auto w = wm_ref_deref(cursor);
+ if (w != NULL) {
+ win_update_is_fullscreen(ps, w);
+ }
}
if (ps->redirected) {
@@ -810,36 +727,27 @@ static void configure_root(session_t *ps) {
}
}
-static void handle_root_flags(session_t *ps) {
- if ((ps->root_flags & ROOT_FLAGS_SCREEN_CHANGE) != 0) {
- if (ps->o.crop_shadow_to_monitor && ps->randr_exists) {
- x_update_monitors(&ps->c, &ps->monitors);
- }
- ps->root_flags &= ~(uint64_t)ROOT_FLAGS_SCREEN_CHANGE;
- }
-
- if ((ps->root_flags & ROOT_FLAGS_CONFIGURED) != 0) {
- configure_root(ps);
- ps->root_flags &= ~(uint64_t)ROOT_FLAGS_CONFIGURED;
- }
-}
-
/**
* Go through the window stack and calculate some parameters for rendering.
*
* @return whether the operation succeeded
*/
-static bool paint_preprocess(session_t *ps, bool *animation, struct managed_win **out_bottom) {
+static bool paint_preprocess(session_t *ps, bool *animation, struct win **out_bottom) {
// XXX need better, more general name for `fade_running`. It really
// means if fade is still ongoing after the current frame is rendered
- struct managed_win *bottom = NULL;
+ struct win *bottom = NULL;
*animation = false;
*out_bottom = NULL;
// First, let's process fading, and animated shaders
// TODO(yshui) check if a window is fully obscured, and if we don't need to
// process fading or animation for it.
- win_stack_foreach_managed_safe(w, wm_stack_end(ps->wm)) {
+ wm_stack_foreach_safe(ps->wm, cursor, tmp) {
+ auto w = wm_ref_deref(cursor);
+ if (w == NULL) {
+ continue;
+ }
+
const winmode_t mode_old = w->mode;
const bool was_painted = w->to_paint;
@@ -892,8 +800,13 @@ static bool paint_preprocess(session_t *ps, bool *animation, struct managed_win
// Track whether it's the highest window to paint
bool is_highest = true;
bool reg_ignore_valid = true;
- win_stack_foreach_managed_safe(w, wm_stack_end(ps->wm)) {
+ wm_stack_foreach_safe(ps->wm, cursor, next_cursor) {
__label__ skip_window;
+ auto w = wm_ref_deref(cursor);
+ if (w == NULL) {
+ continue;
+ }
+
bool to_paint = true;
// w->to_paint remembers whether this window is painted last time
const bool was_painted = w->to_paint;
@@ -907,7 +820,7 @@ static bool paint_preprocess(session_t *ps, bool *animation, struct managed_win
// log_trace("%d %d %s", w->a.map_state, w->ever_damaged, w->name);
log_trace("Checking whether window %#010x (%s) should be painted",
- w->base.id, w->name);
+ win_id(w), w->name);
// Give up if it's not damaged or invisible, or it's unmapped and its
// pixmap is gone (for example due to a ConfigureNotify), or when it's
@@ -917,8 +830,8 @@ static bool paint_preprocess(session_t *ps, bool *animation, struct managed_win
log_trace("|- is unmapped");
to_paint = false;
} else if (unlikely(ps->debug_window != XCB_NONE) &&
- (w->base.id == ps->debug_window ||
- w->client_win == ps->debug_window)) {
+ (win_id(w) == ps->debug_window ||
+ win_client_id(w, /*fallback_to_self=*/false) == ps->debug_window)) {
log_trace("|- is the debug window");
to_paint = false;
} else if (!w->ever_damaged) {
@@ -938,7 +851,7 @@ static bool paint_preprocess(session_t *ps, bool *animation, struct managed_win
} else if (w->paint_excluded) {
log_trace("|- is excluded from painting");
to_paint = false;
- } else if (unlikely((w->flags & WIN_FLAGS_IMAGE_ERROR) != 0)) {
+ } else if (unlikely((w->flags & WIN_FLAGS_PIXMAP_ERROR) != 0)) {
log_trace("|- has image errors");
to_paint = false;
}
@@ -959,7 +872,7 @@ static bool paint_preprocess(session_t *ps, bool *animation, struct managed_win
}
log_trace("|- will be painted");
- log_verbose("Window %#010x (%s) will be painted", w->base.id, w->name);
+ log_verbose("Window %#010x (%s) will be painted", win_id(w), w->name);
// Generate ignore region for painting to reduce GPU load
if (!w->reg_ignore) {
@@ -1012,11 +925,6 @@ static bool paint_preprocess(session_t *ps, bool *animation, struct managed_win
}
w->prev_trans = bottom;
- if (bottom) {
- w->stacking_rank = bottom->stacking_rank + 1;
- } else {
- w->stacking_rank = 0;
- }
bottom = w;
// If the screen is not redirected and the window has redir_ignore set,
@@ -1032,7 +940,7 @@ static bool paint_preprocess(session_t *ps, bool *animation, struct managed_win
if (w->state == WSTATE_DESTROYED && w->running_animation == NULL) {
// the window should be destroyed because it was destroyed
// by X server and now its animations are finished
- destroy_win_finish(ps, &w->base);
+ win_destroy_finish(ps, w);
w = NULL;
}
@@ -1542,8 +1450,11 @@ static void unredirect(session_t *ps) {
/// And if we don't get new ones, we won't render, i.e. we would freeze. libxcb
/// keeps an internal queue of events, so we have to be 100% sure no events are
/// left in that queue before we go to sleep.
-static void handle_queued_x_events(EV_P attr_unused, ev_prepare *w, int revents attr_unused) {
- session_t *ps = session_ptr(w, event_check);
+static void handle_x_events(struct session *ps) {
+ if (ps->vblank_scheduler) {
+ vblank_handle_x_events(ps->vblank_scheduler);
+ }
+
// Flush because if we go into sleep when there is still requests in the
// outgoing buffer, they will not be sent for an indefinite amount of
// time. Use XFlush here too, we might still use some Xlib functions
@@ -1560,13 +1471,9 @@ static void handle_queued_x_events(EV_P attr_unused, ev_prepare *w, int revents
XFlush(ps->c.dpy);
xcb_flush(ps->c.c);
- if (ps->vblank_scheduler) {
- vblank_handle_x_events(ps->vblank_scheduler);
- }
-
xcb_generic_event_t *ev;
- while ((ev = xcb_poll_for_queued_event(ps->c.c))) {
- ev_handle(ps, ev);
+ while ((ev = x_poll_for_event(&ps->c))) {
+ ev_handle(ps, (xcb_generic_event_t *)ev);
free(ev);
};
int err = xcb_connection_has_error(ps->c.c);
@@ -1576,43 +1483,140 @@ static void handle_queued_x_events(EV_P attr_unused, ev_prepare *w, int revents
}
}
-static void handle_new_windows(session_t *ps) {
- list_foreach_safe(struct win, w, wm_stack_end(ps->wm), stack_neighbour) {
- if (w->is_new) {
- auto new_w = maybe_allocate_managed_win(ps, w);
- if (new_w == w) {
- continue;
- }
+static void handle_x_events_ev(EV_P attr_unused, ev_prepare *w, int revents attr_unused) {
+ session_t *ps = session_ptr(w, event_check);
+ handle_x_events(ps);
+}
+
+struct new_window_attributes_request {
+ struct x_async_request_base base;
+ struct session *ps;
+ xcb_window_t wid;
+};
+
+static void handle_new_window_attributes_reply(struct x_connection * /*c*/,
+ struct x_async_request_base *req_base,
+ xcb_raw_generic_event_t *reply_or_error) {
+ auto req = (struct new_window_attributes_request *)req_base;
+ auto ps = req->ps;
+ auto wid = req->wid;
+ auto new_window = wm_find(ps->wm, wid);
+ free(req);
+
+ if (reply_or_error->response_type == 0) {
+ log_debug("Failed to get window attributes for newly created window "
+ "%#010x",
+ wid);
+ return;
+ }
+
+ if (new_window == NULL) {
+ // Highly unlikely. This window is destroyed, then another window is
+ // created with the same window ID before this request completed, and the
+ // latter window isn't in our tree yet.
+ if (wm_is_consistent(ps->wm)) {
+ log_error("Newly created window %#010x is not in the window tree", wid);
+ assert(false);
+ }
+ return;
+ }
- assert(new_w->managed);
- wm_stack_replace(ps->wm, w, new_w);
+ auto toplevel = wm_ref_toplevel_of(ps->wm, new_window);
+ if (toplevel != new_window) {
+ log_debug("Newly created window %#010x was moved away from toplevel "
+ "while we were waiting for its attributes",
+ wid);
+ return;
+ }
+ if (wm_ref_deref(toplevel) != NULL) {
+ // This is possible if a toplevel is reparented away, then reparented to
+ // root so it became a toplevel again. If the GetWindowAttributes request
+ // sent for the first time it became a toplevel wasn't completed for this
+ // whole duration, it will create a managed window object for the
+ // toplevel. But there is another get attributes request sent the
+ // second time it became a toplevel. When we get the reply for the second
+ // request, we will reach here.
+ log_debug("Newly created window %#010x is already managed", wid);
+ return;
+ }
- auto mw = (struct managed_win *)new_w;
- if (mw->a.map_state == XCB_MAP_STATE_VIEWABLE) {
- win_set_flags(mw, WIN_FLAGS_MAPPED);
+ auto w = win_maybe_allocate(ps, toplevel,
+ (xcb_get_window_attributes_reply_t *)reply_or_error);
+ if (w != NULL && w->a.map_state == XCB_MAP_STATE_VIEWABLE) {
+ win_map_start(w);
+ ps->pending_updates = true;
+ }
+}
- // This window might be damaged before we called fill_win
- // and created the damage handle. And there is no way for
- // us to find out. So just blindly mark it damaged
- mw->ever_damaged = true;
+static void handle_new_windows(session_t *ps) {
+ // Check tree changes first, because later property updates need accurate
+ // client window information
+ struct win *w = NULL;
+ while (true) {
+ auto wm_change = wm_dequeue_change(ps->wm);
+ if (wm_change.type == WM_TREE_CHANGE_NONE) {
+ break;
+ }
+ switch (wm_change.type) {
+ case WM_TREE_CHANGE_TOPLEVEL_NEW:;
+ auto req = ccalloc(1, struct new_window_attributes_request);
+ // We don't directly record the toplevel wm_ref here, because any
+ // number of things could happen before we get the reply. The
+ // window can be reparented, destroyed, then get its window ID
+ // reused, etc.
+ req->wid = wm_ref_win_id(wm_change.toplevel);
+ req->ps = ps;
+ req->base.callback = handle_new_window_attributes_reply,
+ req->base.sequence =
+ xcb_get_window_attributes(ps->c.c, req->wid).sequence;
+ x_await_request(&ps->c, &req->base);
+ break;
+ case WM_TREE_CHANGE_TOPLEVEL_KILLED:
+ w = wm_ref_deref(wm_change.toplevel);
+ if (w != NULL) {
+ win_destroy_start(ps, w);
+ } else {
+ // This window is not managed, no point keeping the zombie
+ // around.
+ wm_reap_zombie(wm_change.toplevel);
}
- // Send D-Bus signal
- if (ps->o.dbus) {
- cdbus_ev_win_added(session_get_cdbus(ps), new_w);
+ break;
+ case WM_TREE_CHANGE_CLIENT:
+ log_debug("Client window for window %#010x changed from "
+ "%#010x to %#010x",
+ wm_ref_win_id(wm_change.toplevel),
+ wm_change.client.old.x, wm_change.client.new_.x);
+ w = wm_ref_deref(wm_change.toplevel);
+ if (w != NULL) {
+ win_set_flags(w, WIN_FLAGS_CLIENT_STALE);
+ } else {
+ log_debug("An unmanaged window %#010x has a new client "
+ "%#010x",
+ wm_ref_win_id(wm_change.toplevel),
+ wm_change.client.new_.x);
}
+ break;
+ case WM_TREE_CHANGE_TOPLEVEL_RESTACKED: invalidate_reg_ignore(ps); break;
+ default: unreachable();
}
}
}
static void refresh_windows(session_t *ps) {
- win_stack_foreach_managed(w, wm_stack_end(ps->wm)) {
- win_process_update_flags(ps, w);
+ wm_stack_foreach(ps->wm, cursor) {
+ auto w = wm_ref_deref(cursor);
+ if (w != NULL) {
+ win_process_update_flags(ps, w);
+ }
}
}
static void refresh_images(session_t *ps) {
- win_stack_foreach_managed(w, wm_stack_end(ps->wm)) {
- win_process_image_flags(ps, w);
+ wm_stack_foreach(ps->wm, cursor) {
+ auto w = wm_ref_deref(cursor);
+ if (w != NULL) {
+ win_process_image_flags(ps, w);
+ }
}
}
@@ -1625,60 +1629,36 @@ static void tmout_unredir_callback(EV_P attr_unused, ev_timer *w, int revents at
queue_redraw(ps);
}
-static void handle_pending_updates(EV_P_ struct session *ps, double delta_t) {
- log_trace("Delayed handling of events, entering critical section");
- auto e = xcb_request_check(ps->c.c, xcb_grab_server_checked(ps->c.c));
- if (e) {
- log_fatal_x_error(e, "failed to grab x server");
- free(e);
- return quit(ps);
- }
-
- ps->server_grabbed = true;
+static void handle_pending_updates(struct session *ps, double delta_t) {
+ // Process new windows, and maybe allocate struct managed_win for them
+ handle_new_windows(ps);
- // Catching up with X server
- handle_queued_x_events(EV_A, &ps->event_check, 0);
if (ps->pending_updates) {
- log_debug("Delayed handling of events, entering critical section");
- // Process new windows, and maybe allocate struct managed_win for them
- handle_new_windows(ps);
-
- // Handle screen changes
- // This HAS TO be called before refresh_windows, as handle_root_flags
- // could call configure_root, which will release images and mark them
- // stale.
- handle_root_flags(ps);
+ log_debug("Delayed handling of events");
- // Process window flags
+ // Process window flags. This needs to happen before `refresh_images`
+ // because this might set the pixmap stale window flag.
refresh_windows(ps);
-
- recheck_focus(ps);
-
- // Process window flags (stale images)
- refresh_images(ps);
- }
- e = xcb_request_check(ps->c.c, xcb_ungrab_server_checked(ps->c.c));
- if (e) {
- log_fatal_x_error(e, "failed to ungrab x server");
- free(e);
- return quit(ps);
}
- ps->server_grabbed = false;
+ // Process window flags (stale images)
+ refresh_images(ps);
+
ps->pending_updates = false;
- log_trace("Exited critical section");
- win_stack_foreach_managed_safe(w, wm_stack_end(ps->wm)) {
+ wm_stack_foreach_safe(ps->wm, cursor, tmp) {
+ auto w = wm_ref_deref(cursor);
+ BUG_ON(w != NULL && w->tree_ref != cursor);
// Window might be freed by this function, if it's destroyed and its
// animation finished
- if (win_process_animation_and_state_change(ps, w, delta_t)) {
+ if (w != NULL && win_process_animation_and_state_change(ps, w, delta_t)) {
free(w->running_animation);
w->running_animation = NULL;
w->in_openclose = false;
if (w->state == WSTATE_UNMAPPED) {
unmap_win_finish(ps, w);
} else if (w->state == WSTATE_DESTROYED) {
- destroy_win_finish(ps, &w->base);
+ win_destroy_finish(ps, w);
}
}
}
@@ -1723,7 +1703,7 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
draw_callback_enter_us - (int64_t)ps->next_render);
}
- handle_pending_updates(EV_A_ ps, (double)delta_ms / 1000.0);
+ handle_pending_updates(ps, (double)delta_ms / 1000.0);
int64_t after_handle_pending_updates_us;
clock_gettime(CLOCK_MONOTONIC, &now);
@@ -1743,23 +1723,34 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
//
// TODO(yshui) I think maybe we don't need this anymore, since now we
// immediate acquire pixmap right after `map_win_start`.
- win_stack_foreach_managed_safe(w, wm_stack_end(ps->wm)) {
+ wm_stack_foreach_safe(ps->wm, cursor, tmp) {
+ auto w = wm_ref_deref(cursor);
+ if (w == NULL) {
+ continue;
+ }
free(w->running_animation);
w->running_animation = NULL;
if (w->state == WSTATE_DESTROYED) {
- destroy_win_finish(ps, &w->base);
+ win_destroy_finish(ps, w);
}
}
}
if (ps->o.benchmark) {
if (ps->o.benchmark_wid) {
- auto w = wm_find_managed(ps->wm, ps->o.benchmark_wid);
- if (!w) {
+ auto w = wm_find(ps->wm, ps->o.benchmark_wid);
+ if (w == NULL) {
log_fatal("Couldn't find specified benchmark window.");
exit(1);
}
- add_damage_from_win(ps, w);
+ w = wm_ref_toplevel_of(ps->wm, w);
+
+ auto win = w == NULL ? NULL : wm_ref_deref(w);
+ if (win != NULL) {
+ add_damage_from_win(ps, win);
+ } else {
+ force_repaint(ps);
+ }
} else {
force_repaint(ps);
}
@@ -1770,7 +1761,7 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
* screen should be redirected. */
bool animation = false;
bool was_redirected = ps->redirected;
- struct managed_win *bottom = NULL;
+ struct win *bottom = NULL;
if (!paint_preprocess(ps, &animation, &bottom)) {
log_fatal("Pre-render preparation has failed, exiting...");
exit(1);
@@ -1927,13 +1918,9 @@ static void draw_callback(EV_P_ ev_timer *w, int revents) {
}
}
-static void x_event_callback(EV_P attr_unused, ev_io *w, int revents attr_unused) {
- session_t *ps = (session_t *)w;
- xcb_generic_event_t *ev = xcb_poll_for_event(ps->c.c);
- if (ev) {
- ev_handle(ps, ev);
- free(ev);
- }
+static void x_event_callback(EV_P attr_unused, ev_io * /*w*/, int revents attr_unused) {
+ // This function is intentionally left blank, events are actually read and handled
+ // in the ev_prepare listener.
}
static void config_file_change_cb(void *_ps) {
@@ -2075,6 +2062,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
// Allocate a session and copy default values into it
session_t *ps = cmalloc(session_t);
+ xcb_generic_error_t *e = NULL;
*ps = s_def;
ps->loop = EV_DEFAULT;
pixman_region32_init(&ps->screen_reg);
@@ -2086,26 +2074,9 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
// Use the same Display across reset, primarily for resource leak checking
x_connection_init(&ps->c, dpy);
- // We store width/height from screen_info instead using them directly because they
- // can change, see configure_root().
- ps->root_width = ps->c.screen_info->width_in_pixels;
- ps->root_height = ps->c.screen_info->height_in_pixels;
const xcb_query_extension_reply_t *ext_info;
- // Start listening to events on root earlier to catch all possible
- // root geometry changes
- auto e = xcb_request_check(
- ps->c.c, xcb_change_window_attributes_checked(
- ps->c.c, ps->c.screen_info->root, XCB_CW_EVENT_MASK,
- (const uint32_t[]){XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
- XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY |
- XCB_EVENT_MASK_PROPERTY_CHANGE}));
- if (e) {
- log_error_x_error(e, "Failed to setup root window event mask");
- free(e);
- }
-
xcb_prefetch_extension_data(ps->c.c, &xcb_render_id);
xcb_prefetch_extension_data(ps->c.c, &xcb_composite_id);
xcb_prefetch_extension_data(ps->c.c, &xcb_damage_id);
@@ -2346,56 +2317,18 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
// Create registration window
int ret = register_cm(ps);
if (ret == -1) {
- exit(1);
+ goto err;
}
compositor_running = ret == 1;
- if (compositor_running) {
- // Don't take the overlay when there is another compositor
- // running, so we don't disrupt it.
-
- // If we are printing diagnostic, we will continue a bit further
- // to get more diagnostic information, otherwise we will exit.
- if (!ps->o.print_diagnostics) {
- log_fatal("Another composite manager is already running");
- exit(1);
- }
- } else {
- if (!init_overlay(ps)) {
- goto err;
- }
- }
- } else {
- // We are here if we don't really function as a compositor, so we are not
- // taking over the screen, and we don't need to register as a compositor
-
- // If we are in debug mode, we need to create a window for rendering if
- // the backend supports presenting.
-
- // The old backends doesn't have a automatic redirection mode
- log_info("The compositor is started in automatic redirection mode.");
- assert(!ps->o.use_legacy_backends);
-
- if (backend_can_present(ps->o.backend)) {
- // If the backend has `present`, we couldn't be in automatic
- // redirection mode unless we are in debug mode.
- assert(ps->o.debug_mode);
- if (!init_debug_window(ps)) {
- goto err;
- }
- }
}
ps->drivers = detect_driver(ps->c.c, ps->backend_data, ps->c.screen_info->root);
apply_driver_workarounds(ps, ps->drivers);
- // Initialize filters, must be preceded by OpenGL context creation
- if (ps->o.use_legacy_backends && !init_render(ps)) {
- log_fatal("Failed to initialize the backend");
- exit(1);
- }
-
if (ps->o.print_diagnostics) {
+ ps->root_width = ps->c.screen_info->width_in_pixels;
+ ps->root_height = ps->c.screen_info->height_in_pixels;
print_diagnostics(ps, config_file, compositor_running);
exit(0);
}
@@ -2426,7 +2359,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
if (ps->randr_exists && ps->o.crop_shadow_to_monitor) {
xcb_randr_select_input(ps->c.c, ps->c.screen_info->root,
XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE);
- x_update_monitors(&ps->c, &ps->monitors);
+ x_update_monitors_async(&ps->c, &ps->monitors);
}
{
@@ -2471,7 +2404,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
//
// So we make use of a ev_prepare handle, which is called right before libev
// goes into sleep, to handle all the queued X events.
- ev_prepare_init(&ps->event_check, handle_queued_x_events);
+ ev_prepare_init(&ps->event_check, handle_x_events_ev);
// Make sure nothing can cause xcb to read from the X socket after events are
// handled and before we going to sleep.
ev_set_priority(&ps->event_check, EV_MINPRI);
@@ -2491,55 +2424,79 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
#endif
}
- e = xcb_request_check(ps->c.c, xcb_grab_server_checked(ps->c.c));
+ ps->wm = wm_new();
+ wm_import_start(ps->wm, &ps->c, ps->atoms, ps->c.screen_info->root, NULL);
+
+ ps->command_builder = command_builder_new();
+ ps->expose_rects = dynarr_new(rect_t, 0);
+
+ // wm_complete_import will set event masks on the root window, but its event
+ // mask is missing things we need, so we need to set it again.
+ e = xcb_request_check(
+ ps->c.c, xcb_change_window_attributes_checked(
+ ps->c.c, ps->c.screen_info->root, XCB_CW_EVENT_MASK,
+ (const uint32_t[]){XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
+ XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY |
+ XCB_EVENT_MASK_PROPERTY_CHANGE}));
if (e) {
- log_fatal_x_error(e, "Failed to grab X server");
+ log_error_x_error(e, "Failed to setup root window event mask");
free(e);
goto err;
}
- ps->server_grabbed = true;
+ // Query the size of the root window. We need the size information before any
+ // window can be managed.
+ auto r = XCB_AWAIT(xcb_get_geometry, ps->c.c, ps->c.screen_info->root);
+ if (!r) {
+ log_fatal("Failed to get geometry of the root window");
+ goto err;
+ }
+ ps->root_width = r->width;
+ ps->root_height = r->height;
+ free(r);
+ rebuild_screen_reg(ps);
+
+ // Initialize filters, must be preceded by OpenGL context creation
+ if (ps->o.use_legacy_backends && !init_render(ps)) {
+ log_fatal("Failed to initialize the backend");
+ exit(1);
+ }
- // We are going to pull latest information from X server now, events sent by X
- // earlier is irrelevant at this point.
- // A better solution is probably grabbing the server from the very start. But I
- // think there still could be race condition that mandates discarding the events.
- x_discard_events(&ps->c);
+ if (session_redirection_mode(ps) == XCB_COMPOSITE_REDIRECT_MANUAL && compositor_running) {
+ // Don't take the overlay when there is another compositor
+ // running, so we don't disrupt it.
- xcb_query_tree_reply_t *query_tree_reply = xcb_query_tree_reply(
- ps->c.c, xcb_query_tree(ps->c.c, ps->c.screen_info->root), NULL);
+ // If we are printing diagnostic, we will continue a bit further
+ // to get more diagnostic information, otherwise we will exit.
+ log_fatal("Another composite manager is already running");
+ goto err;
+ }
- e = xcb_request_check(ps->c.c, xcb_ungrab_server_checked(ps->c.c));
- if (e) {
- log_fatal_x_error(e, "Failed to ungrab server");
- free(e);
+ if (session_redirection_mode(ps) == XCB_COMPOSITE_REDIRECT_MANUAL && !init_overlay(ps)) {
goto err;
}
- ps->server_grabbed = false;
+ if (session_redirection_mode(ps) == XCB_COMPOSITE_REDIRECT_AUTOMATIC) {
+ // We are here if we don't really function as a compositor, so we are not
+ // taking over the screen, and we don't need to register as a compositor
- ps->wm = wm_new();
- if (query_tree_reply) {
- xcb_window_t *children;
- int nchildren;
+ // If we are in debug mode, we need to create a window for rendering if
+ // the backend supports presenting.
- children = xcb_query_tree_children(query_tree_reply);
- nchildren = xcb_query_tree_children_length(query_tree_reply);
+ // The old backends doesn't have a automatic redirection mode
+ log_info("The compositor is started in automatic redirection mode.");
+ assert(!ps->o.use_legacy_backends);
- for (int i = 0; i < nchildren; i++) {
- wm_stack_add_above(ps->wm, children[i], i ? children[i - 1] : XCB_NONE);
+ if (backend_can_present(ps->o.backend)) {
+ // If the backend has `present`, we couldn't be in automatic
+ // redirection mode unless we are in debug mode.
+ assert(ps->o.debug_mode);
+ if (!init_debug_window(ps)) {
+ goto err;
+ }
}
- free(query_tree_reply);
- }
-
- log_debug("Initial stack:");
- list_foreach(struct win, w, wm_stack_end(ps->wm), stack_neighbour) {
- log_debug("%#010x", w->id);
}
- ps->command_builder = command_builder_new();
- ps->expose_rects = dynarr_new(rect_t, 0);
-
ps->pending_updates = true;
write_pid(ps);
@@ -2587,8 +2544,11 @@ static void session_destroy(session_t *ps) {
}
#endif
- win_stack_foreach_managed_safe(w, wm_stack_end(ps->wm)) {
- free_win_res(ps, w);
+ wm_stack_foreach(ps->wm, cursor) {
+ auto w = wm_ref_deref(cursor);
+ if (w != NULL) {
+ free_win_res(ps, w);
+ }
}
// Free blacklists
@@ -2695,8 +2655,10 @@ static void session_destroy(session_t *ps) {
ev_signal_stop(ps->loop, &ps->usr1_signal);
ev_signal_stop(ps->loop, &ps->int_signal);
- wm_free(ps->wm, &ps->c);
+ // The X connection could hold references to wm if there are pending async
+ // requests. Therefore the wm must be freed after the X connection.
free_x_connection(&ps->c);
+ wm_free(ps->wm);
}
/**
diff --git a/src/picom.h b/src/picom.h
index f6f7b30994..73a501c64b 100644
--- a/src/picom.h
+++ b/src/picom.h
@@ -24,12 +24,6 @@
#include "wm/win.h"
#include "x.h"
-enum root_flags {
- ROOT_FLAGS_SCREEN_CHANGE = 1, // Received RandR screen change notify, we
- // use this to track refresh rate changes
- ROOT_FLAGS_CONFIGURED = 2 // Received configure notify on the root window
-};
-
// == Functions ==
// TODO(yshui) move static inline functions that are only used in picom.c, into picom.c
@@ -43,7 +37,7 @@ void queue_redraw(session_t *ps);
void discard_pending(session_t *ps, uint32_t sequence);
-void set_root_flags(session_t *ps, uint64_t flags);
+void configure_root(session_t *ps);
void quit(session_t *ps);
diff --git a/src/region.h b/src/region.h
index 1e2261f7d7..a57bf01121 100644
--- a/src/region.h
+++ b/src/region.h
@@ -162,39 +162,29 @@ static inline void region_intersect(region_t *region, ivec2 origin, const region
pixman_region32_translate(region, origin.x, origin.y);
}
-#define define_region_scale(suffix, lower_bound, upper_bound) \
- static inline void region_scale##suffix(region_t *region, ivec2 origin, vec2 scale) { \
- if (vec2_eq(scale, SCALE_IDENTITY)) { \
- return; \
- } \
- \
- int n; \
- region_t tmp = *region; \
- auto r = pixman_region32_rectangles(&tmp, &n); \
- for (int i = 0; i < n; i++) { \
- r[i].x1 = to_i32_saturated( \
- lower_bound((r[i].x1 - origin.x) * scale.x + origin.x)); \
- r[i].y1 = to_i32_saturated( \
- lower_bound((r[i].y1 - origin.y) * scale.y + origin.y)); \
- r[i].x2 = to_i32_saturated( \
- upper_bound((r[i].x2 - origin.x) * scale.x + origin.x)); \
- r[i].y2 = to_i32_saturated( \
- upper_bound((r[i].y2 - origin.y) * scale.y + origin.y)); \
- } \
- \
- /* Manipulating the rectangles could break assumptions made internally \
- * by pixman, so we recreate the region with the rectangles to let \
- * pixman fix them. */ \
- pixman_region32_init_rects(region, r, n); \
- pixman_region32_fini(&tmp); \
+/// Scale the `region` by `scale`. The origin of scaling is `origin`. Returns the smallest
+/// integer region that contains the result.
+static inline void region_scale(region_t *region, ivec2 origin, vec2 scale) {
+ if (vec2_eq(scale, SCALE_IDENTITY)) {
+ return;
+ }
+
+ int n;
+ region_t tmp = *region;
+ auto r = pixman_region32_rectangles(&tmp, &n);
+ for (int i = 0; i < n; i++) {
+ r[i].x1 = to_i32_saturated(floor((r[i].x1 - origin.x) * scale.x + origin.x));
+ r[i].y1 = to_i32_saturated(floor((r[i].y1 - origin.y) * scale.y + origin.y));
+ r[i].x2 = to_i32_saturated(ceil((r[i].x2 - origin.x) * scale.x + origin.x));
+ r[i].y2 = to_i32_saturated(ceil((r[i].y2 - origin.y) * scale.y + origin.y));
}
-/// Scale the `region` by `scale`. The origin of scaling is `origin`. Returns the largest integer
-/// region that is contained in the result.
-define_region_scale(_floor, ceil, floor);
-/// Scale the `region` by `scale`. The origin of scaling is `origin`. Returns the smallest integer
-/// region that contains the result.
-define_region_scale(_ceil, floor, ceil);
+ /* Manipulating the rectangles could break assumptions made internally
+ * by pixman, so we recreate the region with the rectangles to let
+ * pixman fix them. */
+ pixman_region32_init_rects(region, r, n);
+ pixman_region32_fini(&tmp);
+}
/// Calculate the symmetric difference of `region1`, and `region2`, and union the result
/// into `result`. The two input regions has to be in the same coordinate space.
diff --git a/src/render.c b/src/render.c
index e2719ccf19..a20651954a 100644
--- a/src/render.c
+++ b/src/render.c
@@ -345,7 +345,7 @@ void render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, int f
}
static inline void
-paint_region(session_t *ps, const struct managed_win *w, int x, int y, int wid, int hei,
+paint_region(session_t *ps, const struct win *w, int x, int y, int wid, int hei,
double opacity, const region_t *reg_paint, xcb_render_picture_t pict) {
const int dx = (w ? w->g.x : 0) + x;
const int dy = (w ? w->g.y : 0) + y;
@@ -392,19 +392,19 @@ static inline bool paint_isvalid(session_t *ps, const paint_t *ppaint) {
/**
* Paint a window itself and dim it if asked.
*/
-void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint) {
+void paint_one(session_t *ps, struct win *w, const region_t *reg_paint) {
// Fetch Pixmap
if (!w->paint.pixmap) {
w->paint.pixmap = x_new_id(&ps->c);
- set_ignore_cookie(&ps->c, xcb_composite_name_window_pixmap(
- ps->c.c, w->base.id, w->paint.pixmap));
+ x_set_error_action_ignore(&ps->c, xcb_composite_name_window_pixmap(
+ ps->c.c, win_id(w), w->paint.pixmap));
}
xcb_drawable_t draw = w->paint.pixmap;
if (!draw) {
log_error("Failed to get pixmap from window %#010x (%s), window won't be "
"visible",
- w->base.id, w->name);
+ win_id(w), w->name);
return;
}
@@ -424,12 +424,12 @@ void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint)
// causing the jittering issue M4he reported in #7.
if (!paint_bind_tex(ps, &w->paint, 0, 0, false, 0, w->a.visual,
(!ps->o.glx_no_rebind_pixmap && w->pixmap_damaged))) {
- log_error("Failed to bind texture for window %#010x.", w->base.id);
+ log_error("Failed to bind texture for window %#010x.", win_id(w));
}
w->pixmap_damaged = false;
if (!paint_isvalid(ps, &w->paint)) {
- log_error("Window %#010x is missing painting data.", w->base.id);
+ log_error("Window %#010x is missing painting data.", win_id(w));
return;
}
@@ -681,7 +681,7 @@ static void paint_root(session_t *ps, const region_t *reg_paint) {
/**
* Generate shadow Picture
for a window.
*/
-static bool win_build_shadow(session_t *ps, struct managed_win *w, double opacity) {
+static bool win_build_shadow(session_t *ps, struct win *w, double opacity) {
const int width = w->widthb;
const int height = w->heightb;
// log_trace("(): building shadow for %s %d %d", w->name, width, height);
@@ -761,13 +761,12 @@ static bool win_build_shadow(session_t *ps, struct managed_win *w, double opacit
/**
* Paint the shadow of a window.
*/
-static inline void
-win_paint_shadow(session_t *ps, struct managed_win *w, region_t *reg_paint) {
+static inline void win_paint_shadow(session_t *ps, struct win *w, region_t *reg_paint) {
// Bind shadow pixmap to GLX texture if needed
paint_bind_tex(ps, &w->shadow_paint, 0, 0, false, 32, 0, false);
if (!paint_isvalid(ps, &w->shadow_paint)) {
- log_error("Window %#010x is missing shadow data.", w->base.id);
+ log_error("Window %#010x is missing shadow data.", win_id(w));
return;
}
@@ -897,7 +896,7 @@ xr_blur_dst(session_t *ps, xcb_render_picture_t tgt_buffer, int16_t x, int16_t y
* Blur the background of a window.
*/
static inline void
-win_blur_background(session_t *ps, struct managed_win *w, xcb_render_picture_t tgt_buffer,
+win_blur_background(session_t *ps, struct win *w, xcb_render_picture_t tgt_buffer,
const region_t *reg_paint) {
const int16_t x = w->g.x;
const int16_t y = w->g.y;
@@ -1001,7 +1000,7 @@ win_blur_background(session_t *ps, struct managed_win *w, xcb_render_picture_t t
/// paint all windows
/// region = ??
/// region_real = the damage region
-void paint_all(session_t *ps, struct managed_win *t) {
+void paint_all(session_t *ps, struct win *t) {
if (ps->o.xrender_sync_fence || (ps->drivers & DRIVER_NVIDIA)) {
if (ps->xsync_exists && !x_fence_sync(&ps->c, ps->sync_fence)) {
log_error("x_fence_sync failed, xrender-sync-fence will be "
@@ -1122,18 +1121,20 @@ void paint_all(session_t *ps, struct managed_win *t) {
pixman_region32_subtract(®_tmp, ®_tmp, &bshape_no_corners);
}
- if (ps->o.crop_shadow_to_monitor && w->randr_monitor >= 0 &&
- w->randr_monitor < ps->monitors.count) {
- // There can be a window where number of monitors is
- // updated, but the monitor number attached to the window
- // have not.
- //
- // Window monitor number will be updated eventually, so
- // here we just check to make sure we don't access out of
- // bounds.
- pixman_region32_intersect(
- ®_tmp, ®_tmp,
- &ps->monitors.regions[w->randr_monitor]);
+ if (ps->o.crop_shadow_to_monitor) {
+ auto monitor_index = win_find_monitor(&ps->monitors, w);
+ if (monitor_index >= 0) {
+ // There can be a window where number of monitors
+ // is updated, but the monitor number attached to
+ // the window have not.
+ //
+ // Window monitor number will be updated
+ // eventually, so here we just check to make sure
+ // we don't access out of bounds.
+ pixman_region32_intersect(
+ ®_tmp, ®_tmp,
+ &ps->monitors.regions[monitor_index]);
+ }
}
// Detect if the region is empty before painting
diff --git a/src/render.h b/src/render.h
index 62258f0edb..69564f848a 100644
--- a/src/render.h
+++ b/src/render.h
@@ -14,7 +14,7 @@ typedef struct _glx_texture glx_texture_t;
typedef struct glx_prog_main glx_prog_main_t;
typedef struct session session_t;
-struct managed_win;
+struct win;
typedef struct paint {
xcb_pixmap_t pixmap;
@@ -35,9 +35,9 @@ void render(session_t *ps, int x, int y, int dx, int dy, int w, int h, int fullw
int fullh, double opacity, bool argb, bool neg, int cr,
xcb_render_picture_t pict, glx_texture_t *ptex, const region_t *reg_paint,
const glx_prog_main_t *pprogram, clip_t *clip);
-void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint);
+void paint_one(session_t *ps, struct win *w, const region_t *reg_paint);
-void paint_all(session_t *ps, struct managed_win *const t);
+void paint_all(session_t *ps, struct win *const t);
void free_paint(session_t *ps, paint_t *ppaint);
void free_root_tile(session_t *ps);
diff --git a/src/renderer/command_builder.c b/src/renderer/command_builder.c
index d5e774c0ca..e1732137e4 100644
--- a/src/renderer/command_builder.c
+++ b/src/renderer/command_builder.c
@@ -51,8 +51,8 @@ commands_for_window_body(struct layer *layer, struct backend_command *cmd,
if (w->corner_radius > 0) {
win_region_remove_corners(w, layer->window.origin, &cmd->opaque_region);
}
- region_scale_floor(&cmd->target_mask, layer->window.origin, layer->scale);
- region_scale_floor(&cmd->opaque_region, layer->window.origin, layer->scale);
+ region_scale(&cmd->target_mask, layer->window.origin, layer->scale);
+ region_scale(&cmd->opaque_region, layer->window.origin, layer->scale);
pixman_region32_intersect(&cmd->target_mask, &cmd->target_mask, &crop);
pixman_region32_intersect(&cmd->opaque_region, &cmd->opaque_region, &crop);
cmd->op = BACKEND_COMMAND_BLIT;
@@ -82,7 +82,7 @@ commands_for_window_body(struct layer *layer, struct backend_command *cmd,
cmd -= 1;
pixman_region32_copy(&cmd->target_mask, frame_region);
- region_scale_floor(&cmd->target_mask, cmd->origin, layer->scale);
+ region_scale(&cmd->target_mask, cmd->origin, layer->scale);
pixman_region32_intersect(&cmd->target_mask, &cmd->target_mask, &crop);
pixman_region32_init(&cmd->opaque_region);
cmd->op = BACKEND_COMMAND_BLIT;
@@ -115,7 +115,7 @@ command_for_shadow(struct layer *layer, struct backend_command *cmd,
layer->shadow.origin.x, layer->shadow.origin.y,
(unsigned)shadow_size_scaled.width,
(unsigned)shadow_size_scaled.height);
- log_trace("Calculate shadow for %#010x (%s)", w->base.id, w->name);
+ log_trace("Calculate shadow for %#010x (%s)", win_id(w), w->name);
log_region(TRACE, &cmd->target_mask);
if (!wintype_options[w->window_type].full_shadow) {
// We need to not draw under the window
@@ -140,9 +140,12 @@ command_for_shadow(struct layer *layer, struct backend_command *cmd,
}
}
log_region(TRACE, &cmd->target_mask);
- if (monitors && w->randr_monitor >= 0 && w->randr_monitor < monitors->count) {
- pixman_region32_intersect(&cmd->target_mask, &cmd->target_mask,
- &monitors->regions[w->randr_monitor]);
+ if (monitors) {
+ auto monitor_index = win_find_monitor(monitors, w);
+ if (monitor_index >= 0) {
+ pixman_region32_intersect(&cmd->target_mask, &cmd->target_mask,
+ &monitors->regions[monitor_index]);
+ }
}
log_region(TRACE, &cmd->target_mask);
if (w->corner_radius > 0) {
@@ -184,7 +187,7 @@ command_for_blur(struct layer *layer, struct backend_command *cmd,
} else {
return 0;
}
- region_scale_floor(&cmd->target_mask, layer->window.origin, layer->scale);
+ region_scale(&cmd->target_mask, layer->window.origin, layer->scale);
scoped_region_t crop = region_from_box(layer->crop);
pixman_region32_intersect(&cmd->target_mask, &cmd->target_mask, &crop);
diff --git a/src/renderer/damage.c b/src/renderer/damage.c
index e154685cd4..48c650d5fd 100644
--- a/src/renderer/damage.c
+++ b/src/renderer/damage.c
@@ -8,14 +8,6 @@
#include "damage.h"
-static inline bool attr_unused layer_key_eq(const struct layer_key *a,
- const struct layer_key *b) {
- if (!a->generation || !b->generation) {
- return false;
- }
- return a->window == b->window && a->generation == b->generation;
-}
-
/// Compare two layers that contain the same window, return if they are the "same". Same
/// means these two layers are render in the same way at the same position, with the only
/// possible differences being the contents inside the window.
@@ -105,7 +97,7 @@ command_blit_damage(region_t *damage, region_t *scratch_region, struct backend_c
if (cmd1->source == BACKEND_COMMAND_SOURCE_WINDOW) {
layout_manager_collect_window_damage(lm, layer_index, buffer_age,
scratch_region);
- region_scale_floor(scratch_region, cmd2->origin, cmd2->blit.scale);
+ region_scale(scratch_region, cmd2->origin, cmd2->blit.scale);
pixman_region32_intersect(scratch_region, scratch_region, &cmd1->target_mask);
pixman_region32_intersect(scratch_region, scratch_region, &cmd2->target_mask);
pixman_region32_union(damage, damage, scratch_region);
@@ -164,7 +156,7 @@ void layout_manager_damage(struct layout_manager *lm, unsigned buffer_age,
auto layout = layout_manager_layout(lm, l);
dynarr_foreach(layout->layers, layer) {
log_trace("\t%#010x %dx%d+%dx%d (prev %d, next %d)",
- layer->key.window, layer->window.size.width,
+ layer->key.x, layer->window.size.width,
layer->window.size.height,
layer->window.origin.x, layer->window.origin.y,
layer->prev_rank, layer->next_rank);
@@ -265,9 +257,9 @@ void layout_manager_damage(struct layout_manager *lm, unsigned buffer_age,
break;
}
- assert(layer_key_eq(&past_layer->key, &curr_layer->key));
- log_trace("%#010x == %#010x %s", past_layer->key.window,
- curr_layer->key.window, curr_layer->win->name);
+ assert(wm_treeid_eq(past_layer->key, curr_layer->key));
+ log_trace("%#010x == %#010x %s", past_layer->key.x, curr_layer->key.x,
+ curr_layer->win->name);
if (!layer_compare(past_layer, past_layer_cmd, curr_layer, curr_layer_cmd)) {
region_union_render_layer(damage, curr_layer, curr_layer_cmd);
diff --git a/src/renderer/layout.c b/src/renderer/layout.c
index 7ee8567df5..b34cdab006 100644
--- a/src/renderer/layout.c
+++ b/src/renderer/layout.c
@@ -18,7 +18,7 @@
#include "layout.h"
struct layer_index {
UT_hash_handle hh;
- struct layer_key key;
+ wm_treeid key;
unsigned index;
struct list_node free_list;
};
@@ -39,11 +39,14 @@ struct layout_manager {
/// Compute layout of a layer from a window. Returns false if the window is not
/// visible / should not be rendered. `out_layer` is modified either way.
-static bool layer_from_window(struct layer *out_layer, struct managed_win *w, ivec2 size) {
+static bool layer_from_window(struct layer *out_layer, struct win *w, ivec2 size) {
bool to_paint = false;
if (!w->ever_damaged || w->paint_excluded) {
goto out;
}
+ if (w->win_image == NULL) {
+ goto out;
+ }
out_layer->scale = (vec2){
.x = win_animatable_get(w, WIN_SCRIPT_SCALE_X),
@@ -108,8 +111,7 @@ static bool layer_from_window(struct layer *out_layer, struct managed_win *w, iv
out_layer->is_clipping = w->transparent_clipping;
out_layer->next_rank = -1;
out_layer->prev_rank = -1;
- out_layer->key =
- (struct layer_key){.window = w->base.id, .generation = w->base.generation};
+ out_layer->key = wm_ref_treeid(w->tree_ref);
out_layer->win = w;
to_paint = true;
@@ -181,20 +183,17 @@ void layout_manager_append_layout(struct layout_manager *lm, struct wm *wm,
auto layout = &lm->layouts[lm->current];
command_builder_command_list_free(layout->commands);
layout->root_image_generation = root_pixmap_generation;
-
- unsigned nlayers = wm_stack_num_managed_windows(wm);
- dynarr_resize(layout->layers, nlayers, layer_init, layer_deinit);
layout->size = size;
unsigned rank = 0;
struct layer_index *index, *next_index;
- for (struct list_node *cursor = wm_stack_end(wm)->prev;
- cursor != wm_stack_end(wm); cursor = cursor->prev) {
- auto w = list_entry(cursor, struct win, stack_neighbour);
- if (!w->managed) {
+ wm_stack_foreach_rev(wm, cursor) {
+ auto w = wm_ref_deref(cursor);
+ if (w == NULL) {
continue;
}
- if (!layer_from_window(&layout->layers[rank], (struct managed_win *)w, size)) {
+ dynarr_resize(layout->layers, rank + 1, layer_init, layer_deinit);
+ if (!layer_from_window(&layout->layers[rank], (struct win *)w, size)) {
continue;
}
@@ -205,9 +204,8 @@ void layout_manager_append_layout(struct layout_manager *lm, struct wm *wm,
layout->layers[rank].prev_rank = (int)index->index;
}
rank++;
- assert(rank <= nlayers);
}
- dynarr_resize(layout->layers, rank, layer_init, layer_deinit);
+ dynarr_truncate(layout->layers, rank, layer_deinit);
// Update indices. If a layer exist in both prev_layout and current layout,
// we could update the index using next_rank; if a layer no longer exist in
diff --git a/src/renderer/layout.h b/src/renderer/layout.h
index 25e60503f6..448ffd7b1b 100644
--- a/src/renderer/layout.h
+++ b/src/renderer/layout.h
@@ -9,26 +9,15 @@
#include
#include "region.h"
-
-struct layer_key {
- /// Window generation, (see `struct wm::generation` for explanation of what a
- /// generation is)
- uint64_t generation;
- /// Window ID
- xcb_window_t window;
- uint32_t pad; // explicit padding because this will be used as hash table
- // key
-};
-
-static_assert(sizeof(struct layer_key) == 16, "layer_key has implicit padding");
+#include "wm/wm.h"
/// A layer to be rendered in a render layout
struct layer {
/// Window that will be rendered in this layer
- struct layer_key key;
+ wm_treeid key;
/// The window, this is only valid for the current layout. Once
/// a frame has passed, windows could have been freed.
- struct managed_win *win;
+ struct win *win;
/// Damaged region of this layer, in screen coordinates
region_t damaged;
/// Window rectangle in screen coordinates, before it's scaled.
diff --git a/src/renderer/renderer.c b/src/renderer/renderer.c
index 2a6807cd46..81fa6f4a37 100644
--- a/src/renderer/renderer.c
+++ b/src/renderer/renderer.c
@@ -182,7 +182,7 @@ renderer_set_root_size(struct renderer *r, struct backend_base *backend, ivec2 r
}
static bool
-renderer_bind_mask(struct renderer *r, struct backend_base *backend, struct managed_win *w) {
+renderer_bind_mask(struct renderer *r, struct backend_base *backend, struct win *w) {
ivec2 size = {.width = w->widthb, .height = w->heightb};
bool succeeded = false;
auto image = backend->ops.new_image(backend, BACKEND_IMAGE_FORMAT_MASK, size);
@@ -346,8 +346,8 @@ image_handle renderer_shadow_from_mask(struct renderer *r, struct backend_base *
return shadow_image;
}
-static bool renderer_bind_shadow(struct renderer *r, struct backend_base *backend,
- struct managed_win *w) {
+static bool
+renderer_bind_shadow(struct renderer *r, struct backend_base *backend, struct win *w) {
if (backend->ops.quirks(backend) & BACKEND_QUIRK_SLOW_BLUR) {
xcb_pixmap_t shadow = XCB_NONE;
xcb_render_picture_t pict = XCB_NONE;
@@ -398,7 +398,7 @@ static bool renderer_prepare_commands(struct renderer *r, struct backend_base *b
assert(layer->number_of_commands > 0);
layer_end = cmd + layer->number_of_commands;
log_trace("Prepare commands for layer %#010x @ %#010x (%s)",
- layer->win->base.id, layer->win->client_win,
+ win_id(layer->win), win_client_id(layer->win, false),
layer->win->name);
}
@@ -494,8 +494,8 @@ bool renderer_render(struct renderer *r, struct backend_base *backend,
if (xsync_fence != XCB_NONE) {
// Trigger the fence but don't immediately wait on it. Let it run
// concurrent with our CPU tasks to save time.
- set_cant_fail_cookie(backend->c,
- xcb_sync_trigger_fence(backend->c->c, xsync_fence));
+ x_set_error_action_abort(
+ backend->c, xcb_sync_trigger_fence(backend->c->c, xsync_fence));
}
// TODO(yshui) In some cases we can render directly into the back buffer, and
// don't need the intermediate back_image. Several conditions need to be met: no
@@ -522,8 +522,8 @@ bool renderer_render(struct renderer *r, struct backend_base *backend,
layer += 1;
layer_end += layer->number_of_commands;
log_trace("Layer for window %#010x @ %#010x (%s)",
- layer->win->base.id, layer->win->client_win,
- layer->win->name);
+ win_id(layer->win),
+ win_client_id(layer->win, false), layer->win->name);
}
log_backend_command(TRACE, *i);
}
@@ -539,7 +539,8 @@ bool renderer_render(struct renderer *r, struct backend_base *backend,
}
auto buffer_age =
(use_damage || monitor_repaint) ? backend->ops.buffer_age(backend) : 0;
- if (buffer_age > 0 && global_debug_options.consistent_buffer_age) {
+ if (buffer_age > 0 && global_debug_options.consistent_buffer_age &&
+ buffer_age < r->max_buffer_age) {
int past_frame =
(r->frame_index + r->max_buffer_age - buffer_age) % r->max_buffer_age;
region_t region;
@@ -567,13 +568,13 @@ bool renderer_render(struct renderer *r, struct backend_base *backend,
}
if (xsync_fence != XCB_NONE) {
- set_cant_fail_cookie(
+ x_set_error_action_abort(
backend->c, xcb_sync_await_fence(backend->c->c, 1, &xsync_fence));
// Making sure the wait is completed by receiving a response from the X
// server
xcb_aux_sync(backend->c->c);
- set_cant_fail_cookie(backend->c,
- xcb_sync_reset_fence(backend->c->c, xsync_fence));
+ x_set_error_action_abort(
+ backend->c, xcb_sync_reset_fence(backend->c->c, xsync_fence));
}
if (backend->ops.prepare) {
diff --git a/src/utils/dynarr.h b/src/utils/dynarr.h
index 62150d5878..4c9f700512 100644
--- a/src/utils/dynarr.h
+++ b/src/utils/dynarr.h
@@ -170,8 +170,8 @@ static inline void dynarr_remove_swap_impl(size_t size, void *arr, size_t idx) {
#define dynarr_foreach_rev(arr, i) \
for (typeof(arr)(i) = dynarr_end(arr) - 1; (i) >= (arr); (i)--)
-/// Find the index of an element in the array by using trivial comparison, returns -1 if
-/// not found.
+/// Find the index of the first appearance of an element in the array by using trivial
+/// comparison, returns -1 if not found.
#define dynarr_find_pod(arr, needle) \
({ \
ptrdiff_t dynarr_find_ret = -1; \
diff --git a/src/utils/misc.h b/src/utils/misc.h
index 92750fcf94..1769bdc5cb 100644
--- a/src/utils/misc.h
+++ b/src/utils/misc.h
@@ -64,6 +64,8 @@ safe_isinf(double a) {
abort(); \
} \
} while (0)
+/// Abort the program if `expr` is NULL. This is NOT disabled in release builds.
+#define BUG_ON_NULL(expr) BUG_ON((expr) == NULL);
#define CHECK_EXPR(...) ((void)0)
/// Same as assert, but evaluates the expression even in release builds
#define CHECK(expr) \
diff --git a/src/vblank.c b/src/vblank.c
index a02619d49f..b7281360ee 100644
--- a/src/vblank.c
+++ b/src/vblank.c
@@ -373,7 +373,7 @@ static bool present_vblank_scheduler_init(struct vblank_scheduler *base) {
auto select_input =
xcb_present_select_input(base->c->c, self->event_id, base->target_window,
XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY);
- set_cant_fail_cookie(base->c, select_input);
+ x_set_error_action_abort(base->c, select_input);
self->event =
xcb_register_for_special_xge(base->c->c, &xcb_present_id, self->event_id, NULL);
return true;
@@ -384,7 +384,7 @@ static void present_vblank_scheduler_deinit(struct vblank_scheduler *base) {
ev_timer_stop(base->loop, &self->callback_timer);
auto select_input =
xcb_present_select_input(base->c->c, self->event_id, base->target_window, 0);
- set_cant_fail_cookie(base->c, select_input);
+ x_set_error_action_abort(base->c, select_input);
xcb_unregister_for_special_event(base->c->c, self->event);
}
@@ -401,6 +401,10 @@ static void handle_present_complete_notify(struct present_vblank_scheduler *self
assert(self->base.vblank_event_requested);
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ auto now_us = (unsigned long)(now.tv_sec * 1000000L + now.tv_nsec / 1000);
+
// X sometimes sends duplicate/bogus MSC events, when screen has just been turned
// off. Don't use the msc value in these events. We treat this as not receiving a
// vblank event at all, and try to get a new one.
@@ -409,18 +413,15 @@ static void handle_present_complete_notify(struct present_vblank_scheduler *self
// https://gitlab.freedesktop.org/xorg/xserver/-/issues/1418
bool event_is_invalid = cne->msc <= self->last_msc || cne->ust == 0;
if (event_is_invalid) {
- log_debug("Invalid PresentCompleteNotify event, %" PRIu64 " %" PRIu64,
+ log_debug("Invalid PresentCompleteNotify event, %" PRIu64 " %" PRIu64
+ ". Trying to recover, reporting a fake vblank.",
cne->msc, cne->ust);
- x_request_vblank_event(self->base.c, cne->window, self->last_msc + 1);
- return;
+ self->last_ust = now_us;
+ self->last_msc += 1;
+ } else {
+ self->last_ust = cne->ust;
+ self->last_msc = cne->msc;
}
-
- self->last_ust = cne->ust;
- self->last_msc = cne->msc;
-
- struct timespec now;
- clock_gettime(CLOCK_MONOTONIC, &now);
- auto now_us = (unsigned long)(now.tv_sec * 1000000L + now.tv_nsec / 1000);
double delay_sec = 0.0;
if (now_us < cne->ust) {
log_trace("The end of this vblank is %" PRIu64 " us into the "
diff --git a/src/wm/defs.h b/src/wm/defs.h
index cc2abb1dd4..801aea6efe 100644
--- a/src/wm/defs.h
+++ b/src/wm/defs.h
@@ -50,10 +50,8 @@ enum win_flags {
/// pixmap is out of date, will be update in win_process_flags
WIN_FLAGS_PIXMAP_STALE = 1,
- /// window does not have pixmap bound
- WIN_FLAGS_PIXMAP_NONE = 2,
- /// there was an error trying to bind the images
- WIN_FLAGS_IMAGE_ERROR = 4,
+ /// there was an error binding the window pixmap
+ WIN_FLAGS_PIXMAP_ERROR = 4,
/// the client window needs to be updated
WIN_FLAGS_CLIENT_STALE = 32,
/// the window is mapped by X, we need to call map_win_start for it
diff --git a/src/wm/meson.build b/src/wm/meson.build
index 7813e193e4..1fb66fa707 100644
--- a/src/wm/meson.build
+++ b/src/wm/meson.build
@@ -1 +1 @@
-srcs += [ files('win.c', 'wm.c') ]
+srcs += [ files('win.c', 'wm.c', 'tree.c') ]
diff --git a/src/wm/tree.c b/src/wm/tree.c
new file mode 100644
index 0000000000..bbd452aee0
--- /dev/null
+++ b/src/wm/tree.c
@@ -0,0 +1,517 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) Yuxuan Shui
+
+/// In my ideal world, the compositor shouldn't be concerned with the X window tree. It
+/// should only need to care about the toplevel windows. However, because we support
+/// window rules based on window properties, which can be set on any descendant of a
+/// toplevel, we need to keep track of the entire window tree.
+///
+/// For descendants of a toplevel window, what we actually care about is what's called a
+/// "client" window. A client window is a window with the `WM_STATE` property set, in
+/// theory and descendants of a toplevel can gain/lose this property at any time. So we
+/// setup a minimal structure for every single window to keep track of this. And once
+/// a window becomes a client window, it will have our full attention and have all of its
+/// information stored in the toplevel `struct managed_win`.
+
+#include
+#include
+
+#include "log.h"
+#include "utils/list.h"
+#include "utils/misc.h"
+
+#include "wm.h"
+#include "wm_internal.h"
+
+struct wm_tree_change_list {
+ struct wm_tree_change item;
+ struct list_node siblings;
+};
+
+void wm_tree_reap_zombie(struct wm_tree_node *zombie) {
+ BUG_ON(!zombie->is_zombie);
+ list_remove(&zombie->siblings);
+ free(zombie);
+}
+
+/// Enqueue a tree change.
+static void wm_tree_enqueue_change(struct wm_tree *tree, struct wm_tree_change change) {
+ struct wm_tree_change_list *change_list;
+ if (!list_is_empty(&tree->free_changes)) {
+ change_list = list_entry(tree->free_changes.next,
+ struct wm_tree_change_list, siblings);
+ list_remove(&change_list->siblings);
+ } else {
+ change_list = cmalloc(struct wm_tree_change_list);
+ }
+
+ change_list->item = change;
+ list_insert_before(&tree->changes, &change_list->siblings);
+}
+
+/// Enqueue a `WM_TREE_CHANGE_TOPLEVEL_KILLED` change for a toplevel window. If there are
+/// any `WM_TREE_CHANGE_TOPLEVEL_NEW` changes in the queue for the same toplevel, they
+/// will be cancelled out.
+///
+/// @return true if this change is cancelled out by a previous change, false otherwise.
+static bool wm_tree_enqueue_toplevel_killed(struct wm_tree *tree, wm_treeid toplevel,
+ struct wm_tree_node *zombie) {
+ // A gone toplevel will cancel out a previous
+ // `WM_TREE_CHANGE_TOPLEVEL_NEW` change in the queue.
+ bool found = false;
+ list_foreach_safe(struct wm_tree_change_list, i, &tree->changes, siblings) {
+ if (!wm_treeid_eq(i->item.toplevel, toplevel)) {
+ continue;
+ }
+ if (i->item.type == WM_TREE_CHANGE_TOPLEVEL_NEW) {
+ list_remove(&i->siblings);
+ list_insert_after(&tree->free_changes, &i->siblings);
+ found = true;
+ } else if (found) {
+ // We also need to delete all other changes
+ // related to this toplevel in between the new and
+ // gone changes.
+ list_remove(&i->siblings);
+ list_insert_after(&tree->free_changes, &i->siblings);
+ } else if (i->item.type == WM_TREE_CHANGE_CLIENT) {
+ // Need to update client changes, so they points to the
+ // zombie instead of the old toplevel node, since the old
+ // toplevel node could be freed before tree changes are
+ // processed.
+ i->item.client.toplevel = zombie;
+ }
+ }
+ if (found) {
+ wm_tree_reap_zombie(zombie);
+ return true;
+ }
+
+ wm_tree_enqueue_change(tree, (struct wm_tree_change){
+ .toplevel = toplevel,
+ .type = WM_TREE_CHANGE_TOPLEVEL_KILLED,
+ .killed = zombie,
+ });
+ return false;
+}
+
+static void wm_tree_enqueue_client_change(struct wm_tree *tree, struct wm_tree_node *toplevel,
+ wm_treeid old_client, wm_treeid new_client) {
+ // A client change can coalesce with a previous client change.
+ list_foreach_safe(struct wm_tree_change_list, i, &tree->changes, siblings) {
+ if (!wm_treeid_eq(i->item.toplevel, toplevel->id) ||
+ i->item.type != WM_TREE_CHANGE_CLIENT) {
+ continue;
+ }
+
+ if (!wm_treeid_eq(i->item.client.new_, old_client)) {
+ log_warn("Inconsistent client change for toplevel "
+ "%#010x. Missing changes from %#010x to %#010x. "
+ "Possible bug.",
+ toplevel->id.x, i->item.client.new_.x, old_client.x);
+ }
+
+ i->item.client.new_ = new_client;
+ if (wm_treeid_eq(i->item.client.old, new_client)) {
+ list_remove(&i->siblings);
+ list_insert_after(&tree->free_changes, &i->siblings);
+ }
+ return;
+ }
+ wm_tree_enqueue_change(tree, (struct wm_tree_change){
+ .toplevel = toplevel->id,
+ .type = WM_TREE_CHANGE_CLIENT,
+ .client =
+ {
+ .toplevel = toplevel,
+ .old = old_client,
+ .new_ = new_client,
+ },
+ });
+}
+
+static void wm_tree_enqueue_toplevel_new(struct wm_tree *tree, struct wm_tree_node *toplevel) {
+ // We don't let a `WM_TREE_CHANGE_TOPLEVEL_NEW` cancel out a previous
+ // `WM_TREE_CHANGE_TOPLEVEL_KILLED`, because the new toplevel would be a different
+ // window reusing the same ID. So we need to go through the proper destruction
+ // process for the previous toplevel. Changes are not commutative (naturally).
+ wm_tree_enqueue_change(tree, (struct wm_tree_change){
+ .toplevel = toplevel->id,
+ .type = WM_TREE_CHANGE_TOPLEVEL_NEW,
+ .new_ = toplevel,
+ });
+}
+
+static void wm_tree_enqueue_toplevel_restacked(struct wm_tree *tree) {
+ list_foreach(struct wm_tree_change_list, i, &tree->changes, siblings) {
+ if (i->item.type == WM_TREE_CHANGE_TOPLEVEL_RESTACKED ||
+ i->item.type == WM_TREE_CHANGE_TOPLEVEL_NEW ||
+ i->item.type == WM_TREE_CHANGE_TOPLEVEL_KILLED) {
+ // Only need to keep one
+ // `WM_TREE_CHANGE_TOPLEVEL_RESTACKED` change, and order
+ // doesn't matter.
+ return;
+ }
+ }
+ wm_tree_enqueue_change(tree, (struct wm_tree_change){
+ .type = WM_TREE_CHANGE_TOPLEVEL_RESTACKED,
+ });
+}
+
+/// Dequeue the oldest change from the change queue. If the queue is empty, a change with
+/// `toplevel` set to `XCB_NONE` will be returned.
+struct wm_tree_change wm_tree_dequeue_change(struct wm_tree *tree) {
+ if (list_is_empty(&tree->changes)) {
+ return (struct wm_tree_change){.type = WM_TREE_CHANGE_NONE};
+ }
+
+ auto change = list_entry(tree->changes.next, struct wm_tree_change_list, siblings);
+ list_remove(&change->siblings);
+ list_insert_after(&tree->free_changes, &change->siblings);
+ return change->item;
+}
+
+/// Return the next node in the subtree after `node` in a pre-order traversal. Returns
+/// NULL if `node` is the last node in the traversal.
+struct wm_tree_node *wm_tree_next(struct wm_tree_node *node, struct wm_tree_node *subroot) {
+ if (node == NULL) {
+ return NULL;
+ }
+
+ if (!list_is_empty(&node->children)) {
+ // Descend if there are children
+ return list_entry(node->children.next, struct wm_tree_node, siblings);
+ }
+
+ while (node != subroot && node->siblings.next == &node->parent->children) {
+ // If the current node has no more children, go back to the
+ // parent.
+ node = node->parent;
+ }
+ if (node == subroot) {
+ // We've gone past the topmost node for our search, stop.
+ return NULL;
+ }
+ return list_entry(node->siblings.next, struct wm_tree_node, siblings);
+}
+
+/// Find a client window under a toplevel window. If there are multiple windows with
+/// `WM_STATE` set under the toplevel window, we will return an arbitrary one.
+static struct wm_tree_node *attr_pure wm_tree_find_client(struct wm_tree_node *subroot) {
+ if (subroot->has_wm_state) {
+ log_debug("Toplevel %#010x has WM_STATE set, weird. Using itself as its "
+ "client window.",
+ subroot->id.x);
+ return subroot;
+ }
+
+ BUG_ON(subroot->parent == NULL); // Trying to find client window on the
+ // root window
+
+ for (auto curr = subroot; curr != NULL; curr = wm_tree_next(curr, subroot)) {
+ if (curr->has_wm_state) {
+ return curr;
+ }
+ }
+
+ return NULL;
+}
+
+struct wm_tree_node *wm_tree_find(const struct wm_tree *tree, xcb_window_t id) {
+ struct wm_tree_node *node = NULL;
+ HASH_FIND_INT(tree->nodes, &id, node);
+ return node;
+}
+
+struct wm_tree_node *
+wm_tree_find_toplevel_for(const struct wm_tree *tree, struct wm_tree_node *node) {
+ BUG_ON_NULL(node);
+ BUG_ON_NULL(node->parent); // Trying to find toplevel for the root
+ // window
+
+ struct wm_tree_node *toplevel;
+ for (auto curr = node; curr->parent != NULL; curr = curr->parent) {
+ toplevel = curr;
+ }
+ return toplevel->parent == tree->root ? toplevel : NULL;
+}
+
+/// Change whether a tree node has the `WM_STATE` property set.
+/// `destroyed` indicate whether `node` is about to be destroyed, in which case, the `old`
+/// field of the change event will be set to NULL.
+void wm_tree_set_wm_state(struct wm_tree *tree, struct wm_tree_node *node, bool has_wm_state) {
+ BUG_ON(node == NULL);
+
+ if (node->has_wm_state == has_wm_state) {
+ log_debug("WM_STATE unchanged call (window %#010x, WM_STATE %d).",
+ node->id.x, has_wm_state);
+ return;
+ }
+
+ node->has_wm_state = has_wm_state;
+ BUG_ON(node->parent == NULL); // Trying to set WM_STATE on the root window
+
+ struct wm_tree_node *toplevel;
+ for (auto cur = node; cur->parent != NULL; cur = cur->parent) {
+ toplevel = cur;
+ }
+
+ if (toplevel == node) {
+ log_debug("Setting WM_STATE on a toplevel window %#010x, weird.", node->id.x);
+ return;
+ }
+
+ if (!has_wm_state && toplevel->client_window == node) {
+ auto new_client = wm_tree_find_client(toplevel);
+ toplevel->client_window = new_client;
+ wm_tree_enqueue_client_change(
+ tree, toplevel, node->id,
+ new_client != NULL ? new_client->id : WM_TREEID_NONE);
+ } else if (has_wm_state && toplevel->client_window == NULL) {
+ toplevel->client_window = node;
+ wm_tree_enqueue_client_change(tree, toplevel, WM_TREEID_NONE, node->id);
+ } else if (has_wm_state) {
+ // If the toplevel window already has a client window, we won't
+ // try to usurp it.
+ log_debug("Toplevel window %#010x already has a client window "
+ "%#010x, ignoring new client window %#010x. I don't "
+ "like your window manager.",
+ toplevel->id.x, toplevel->client_window->id.x, node->id.x);
+ }
+}
+
+struct wm_tree_node *wm_tree_new_window(struct wm_tree *tree, xcb_window_t id) {
+ auto node = ccalloc(1, struct wm_tree_node);
+ node->id.x = id;
+ node->id.gen = tree->gen++;
+ node->has_wm_state = false;
+ list_init_head(&node->children);
+ return node;
+}
+
+void wm_tree_add_window(struct wm_tree *tree, struct wm_tree_node *node) {
+ HASH_ADD_INT(tree->nodes, id.x, node);
+}
+
+static void
+wm_tree_refresh_client_and_queue_change(struct wm_tree *tree, struct wm_tree_node *toplevel) {
+ BUG_ON_NULL(toplevel);
+ BUG_ON_NULL(toplevel->parent);
+ BUG_ON(toplevel->parent->parent != NULL);
+ auto new_client = wm_tree_find_client(toplevel);
+ if (new_client != toplevel->client_window) {
+ wm_treeid old_client_id = WM_TREEID_NONE, new_client_id = WM_TREEID_NONE;
+ if (toplevel->client_window != NULL) {
+ old_client_id = toplevel->client_window->id;
+ }
+ if (new_client != NULL) {
+ new_client_id = new_client->id;
+ }
+ toplevel->client_window = new_client;
+ wm_tree_enqueue_client_change(tree, toplevel, old_client_id, new_client_id);
+ }
+}
+
+struct wm_tree_node *wm_tree_detach(struct wm_tree *tree, struct wm_tree_node *subroot) {
+ BUG_ON(subroot == NULL);
+ BUG_ON(subroot->parent == NULL); // Trying to detach the root window?!
+
+ auto toplevel = wm_tree_find_toplevel_for(tree, subroot);
+ struct wm_tree_node *zombie = NULL;
+ if (toplevel != subroot) {
+ list_remove(&subroot->siblings);
+ if (toplevel != NULL) {
+ wm_tree_refresh_client_and_queue_change(tree, toplevel);
+ }
+ } else {
+ // Detached a toplevel, create a zombie for it
+ zombie = ccalloc(1, struct wm_tree_node);
+ zombie->parent = subroot->parent;
+ zombie->id = subroot->id;
+ zombie->is_zombie = true;
+ list_init_head(&zombie->children);
+ list_replace(&subroot->siblings, &zombie->siblings);
+ if (wm_tree_enqueue_toplevel_killed(tree, subroot->id, zombie)) {
+ zombie = NULL;
+ }
+ }
+ subroot->parent = NULL;
+ return zombie;
+}
+
+void wm_tree_attach(struct wm_tree *tree, struct wm_tree_node *child,
+ struct wm_tree_node *parent) {
+ BUG_ON(child->parent != NULL); // Trying to attach a window that's already
+ // attached
+ child->parent = parent;
+ if (parent == NULL) {
+ BUG_ON(tree->root != NULL); // Trying to create a second root
+ // window
+ tree->root = child;
+ return;
+ }
+
+ list_insert_after(&parent->children, &child->siblings);
+
+ auto toplevel = wm_tree_find_toplevel_for(tree, child);
+ if (child == toplevel) {
+ wm_tree_enqueue_toplevel_new(tree, child);
+ } else if (toplevel != NULL) {
+ wm_tree_refresh_client_and_queue_change(tree, toplevel);
+ }
+}
+
+void wm_tree_move_to_end(struct wm_tree *tree, struct wm_tree_node *node, bool to_bottom) {
+ BUG_ON(node == NULL);
+ BUG_ON(node->parent == NULL); // Trying to move the root window
+
+ if ((node->parent->children.next == &node->siblings && !to_bottom) ||
+ (node->parent->children.prev == &node->siblings && to_bottom)) {
+ // Already at the target position
+ return;
+ }
+ list_remove(&node->siblings);
+ if (to_bottom) {
+ list_insert_before(&node->parent->children, &node->siblings);
+ } else {
+ list_insert_after(&node->parent->children, &node->siblings);
+ }
+ if (node->parent == tree->root) {
+ wm_tree_enqueue_toplevel_restacked(tree);
+ }
+}
+
+/// Move `node` to above `other` in their parent's child window stack.
+void wm_tree_move_to_above(struct wm_tree *tree, struct wm_tree_node *node,
+ struct wm_tree_node *other) {
+ BUG_ON(node == NULL);
+ BUG_ON(node->parent == NULL); // Trying to move the root window
+ BUG_ON(other == NULL);
+ BUG_ON(node->parent != other->parent);
+
+ if (node->siblings.next == &other->siblings) {
+ // Already above `other`
+ return;
+ }
+
+ list_remove(&node->siblings);
+ list_insert_before(&other->siblings, &node->siblings);
+ if (node->parent == tree->root) {
+ wm_tree_enqueue_toplevel_restacked(tree);
+ }
+}
+
+void wm_tree_clear(struct wm_tree *tree) {
+ struct wm_tree_node *cur, *tmp;
+ HASH_ITER(hh, tree->nodes, cur, tmp) {
+ HASH_DEL(tree->nodes, cur);
+ free(cur);
+ }
+ list_foreach_safe(struct wm_tree_change_list, i, &tree->changes, siblings) {
+ list_remove(&i->siblings);
+ free(i);
+ }
+ list_foreach_safe(struct wm_tree_change_list, i, &tree->free_changes, siblings) {
+ list_remove(&i->siblings);
+ free(i);
+ }
+}
+
+TEST_CASE(tree_manipulation) {
+ struct wm_tree tree;
+ wm_tree_init(&tree);
+
+ wm_tree_add_window(&tree, wm_tree_new_window(&tree, 1));
+ auto root = wm_tree_find(&tree, 1);
+ assert(root != NULL);
+ assert(root->parent == NULL);
+
+ tree.root = root;
+
+ auto change = wm_tree_dequeue_change(&tree);
+ assert(change.type == WM_TREE_CHANGE_NONE);
+
+ auto node2 = wm_tree_new_window(&tree, 2);
+ wm_tree_add_window(&tree, node2);
+ wm_tree_attach(&tree, node2, root);
+ assert(node2 != NULL);
+ assert(node2 == wm_tree_find(&tree, 2));
+ assert(node2->parent == root);
+
+ change = wm_tree_dequeue_change(&tree);
+ assert(change.toplevel.x == 2);
+ assert(change.type == WM_TREE_CHANGE_TOPLEVEL_NEW);
+ assert(wm_treeid_eq(node2->id, change.toplevel));
+
+ auto node3 = wm_tree_new_window(&tree, 3);
+ wm_tree_add_window(&tree, node3);
+ wm_tree_attach(&tree, node3, root);
+
+ change = wm_tree_dequeue_change(&tree);
+ assert(change.toplevel.x == 3);
+ assert(change.type == WM_TREE_CHANGE_TOPLEVEL_NEW);
+
+ auto zombie = wm_tree_detach(&tree, node2);
+ wm_tree_attach(&tree, node2, node3);
+ assert(node2->parent == node3);
+ assert(node3->children.next == &node2->siblings);
+
+ // node2 is now a child of node3, so it's no longer a toplevel
+ change = wm_tree_dequeue_change(&tree);
+ assert(change.toplevel.x == 2);
+ assert(change.type == WM_TREE_CHANGE_TOPLEVEL_KILLED);
+ TEST_EQUAL(change.killed, zombie);
+ wm_tree_reap_zombie(change.killed);
+
+ wm_tree_set_wm_state(&tree, node2, true);
+ change = wm_tree_dequeue_change(&tree);
+ assert(change.toplevel.x == 3);
+ assert(change.type == WM_TREE_CHANGE_CLIENT);
+ assert(wm_treeid_eq(change.client.old, WM_TREEID_NONE));
+ assert(change.client.new_.x == 2);
+
+ auto node4 = wm_tree_new_window(&tree, 4);
+ wm_tree_add_window(&tree, node4);
+ wm_tree_attach(&tree, node4, node3);
+ change = wm_tree_dequeue_change(&tree);
+ assert(change.type == WM_TREE_CHANGE_NONE);
+
+ wm_tree_set_wm_state(&tree, node4, true);
+ change = wm_tree_dequeue_change(&tree);
+ // node3 already has node2 as its client window, so the new one should be ignored.
+ assert(change.type == WM_TREE_CHANGE_NONE);
+
+ TEST_EQUAL(wm_tree_detach(&tree, node2), NULL);
+ HASH_DEL(tree.nodes, node2);
+ free(node2);
+ change = wm_tree_dequeue_change(&tree);
+ assert(change.toplevel.x == 3);
+ assert(change.type == WM_TREE_CHANGE_CLIENT);
+ assert(change.client.old.x == 2);
+ assert(change.client.new_.x == 4);
+
+ // Test window ID reuse
+ TEST_EQUAL(wm_tree_detach(&tree, node4), NULL);
+ HASH_DEL(tree.nodes, node4);
+ free(node4);
+ node4 = wm_tree_new_window(&tree, 4);
+ wm_tree_add_window(&tree, node4);
+ wm_tree_attach(&tree, node4, node3);
+ wm_tree_set_wm_state(&tree, node4, true);
+
+ change = wm_tree_dequeue_change(&tree);
+ assert(change.toplevel.x == 3);
+ assert(change.type == WM_TREE_CHANGE_CLIENT);
+ assert(change.client.old.x == 4);
+ assert(change.client.new_.x == 4);
+
+ auto node5 = wm_tree_new_window(&tree, 5);
+ wm_tree_add_window(&tree, node5);
+ wm_tree_attach(&tree, node5, root);
+ TEST_EQUAL(wm_tree_detach(&tree, node5), NULL);
+ HASH_DEL(tree.nodes, node5);
+ free(node5);
+ change = wm_tree_dequeue_change(&tree);
+ assert(change.type == WM_TREE_CHANGE_NONE); // Changes cancelled out
+
+ wm_tree_clear(&tree);
+}
diff --git a/src/wm/win.c b/src/wm/win.c
index a4b47b0c0d..b3d353e9d2 100644
--- a/src/wm/win.c
+++ b/src/wm/win.c
@@ -36,11 +36,6 @@
#include "win.h"
-// TODO(yshui) Make more window states internal
-struct managed_win_internal {
- struct managed_win base;
-};
-
#define OPAQUE (0xffffffff)
static const int WIN_GET_LEADER_MAX_RECURSION = 20;
static const int ROUNDED_PIXELS = 1;
@@ -74,25 +69,23 @@ static const double ROUNDED_PERCENT = 0.05;
* Reread opacity property of a window.
*/
static void win_update_opacity_prop(struct x_connection *c, struct atom *atoms,
- struct managed_win *w, bool detect_client_opacity);
-static void win_update_prop_shadow_raw(struct x_connection *c, struct atom *atoms,
- struct managed_win *w);
-static bool
-win_update_prop_shadow(struct x_connection *c, struct atom *atoms, struct managed_win *w);
+ struct win *w, bool detect_client_opacity);
+static void
+win_update_prop_shadow_raw(struct x_connection *c, struct atom *atoms, struct win *w);
+static bool win_update_prop_shadow(struct x_connection *c, struct atom *atoms, struct win *w);
/**
* Update leader of a window.
*/
static xcb_window_t
win_get_leader_property(struct x_connection *c, struct atom *atoms, xcb_window_t wid,
bool detect_transient, bool detect_client_leader);
-static void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t client);
/// Generate a "no corners" region function, from a function that returns the
/// region via a region_t pointer argument. Corners of the window will be removed from
/// the returned region.
/// Function signature has to be (win *, region_t *)
#define gen_without_corners(fun) \
- void fun##_without_corners(const struct managed_win *w, region_t *res) { \
+ void fun##_without_corners(const struct win *w, region_t *res) { \
fun(w, res); \
win_region_remove_corners_local(w, res); \
}
@@ -101,28 +94,28 @@ static void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t c
/// region via a region_t pointer argument.
/// Function signature has to be (win *)
#define gen_by_val(fun) \
- region_t fun##_by_val(const struct managed_win *w) { \
+ region_t fun##_by_val(const struct win *w) { \
region_t ret; \
pixman_region32_init(&ret); \
fun(w, &ret); \
return ret; \
}
-static xcb_window_t win_get_leader_raw(session_t *ps, struct managed_win *w, int recursions);
+static struct wm_ref *win_get_leader_raw(session_t *ps, struct win *w, int recursions);
/**
* Get the leader of a window.
*
* This function updates w->cache_leader if necessary.
*/
-static inline xcb_window_t win_get_leader(session_t *ps, struct managed_win *w) {
+static inline struct wm_ref *win_get_leader(session_t *ps, struct win *w) {
return win_get_leader_raw(ps, w, 0);
}
/**
* Update focused state of a window.
*/
-static void win_update_focused(session_t *ps, struct managed_win *w) {
+static void win_update_focused(session_t *ps, struct win *w) {
if (w->focused_force != UNSET) {
w->focused = w->focused_force;
} else {
@@ -133,7 +126,8 @@ static void win_update_focused(session_t *ps, struct managed_win *w) {
// windows specially
if (ps->o.wintype_option[w->window_type].focus ||
(ps->o.mark_wmwin_focused && is_wmwin) ||
- (ps->o.mark_ovredir_focused && w->base.id == w->client_win && !is_wmwin) ||
+ (ps->o.mark_ovredir_focused &&
+ wm_ref_client_of(w->tree_ref) == NULL && !is_wmwin) ||
(w->a.map_state == XCB_MAP_STATE_VIEWABLE &&
c2_match(ps->c2_state, w, ps->o.focus_blacklist, NULL))) {
w->focused = true;
@@ -154,45 +148,28 @@ struct group_callback_data {
xcb_window_t leader;
};
-static inline int group_on_factor_change_callback(struct win *w, void *data_) {
- struct group_callback_data *data = data_;
- if (!w->managed) {
- return 0;
- }
- auto mw = (struct managed_win *)w;
- if (data->leader == win_get_leader(data->ps, mw)) {
- win_on_factor_change(data->ps, mw);
- }
- return 0;
-}
-
/**
* Run win_on_factor_change() on all windows with the same leader window.
*
* @param leader leader window ID
*/
-static inline void group_on_factor_change(session_t *ps, xcb_window_t leader) {
+static inline void group_on_factor_change(session_t *ps, struct wm_ref *leader) {
if (!leader) {
return;
}
- struct group_callback_data data = {
- .ps = ps,
- .leader = leader,
- };
- wm_foreach(ps->wm, group_on_factor_change_callback, &data);
-}
-
-static inline int group_is_focused_callback(struct win *w, void *data_) {
- struct group_callback_data *data = data_;
- if (!w->managed) {
- return 0;
- }
- auto mw = (struct managed_win *)w;
- if (data->leader == win_get_leader(data->ps, mw) && win_is_focused_raw(mw)) {
- return 1;
+ wm_stack_foreach(ps->wm, cursor) {
+ if (wm_ref_is_zombie(cursor)) {
+ continue;
+ }
+ auto w = wm_ref_deref(cursor);
+ if (w == NULL) {
+ continue;
+ }
+ if (leader == win_get_leader(ps, w)) {
+ win_on_factor_change(ps, w);
+ }
}
- return 0;
}
/**
@@ -201,35 +178,49 @@ static inline int group_is_focused_callback(struct win *w, void *data_) {
* @param leader leader window ID
* @return true if the window group is focused, false otherwise
*/
-static inline bool group_is_focused(session_t *ps, xcb_window_t leader) {
+static inline bool group_is_focused(session_t *ps, struct wm_ref *leader) {
if (!leader) {
return false;
}
- struct group_callback_data data = {
- .ps = ps,
- .leader = leader,
- };
- return wm_foreach(ps->wm, group_is_focused_callback, &data);
+ wm_stack_foreach(ps->wm, cursor) {
+ if (wm_ref_is_zombie(cursor)) {
+ continue;
+ }
+ auto w = wm_ref_deref(cursor);
+ if (w == NULL) {
+ continue;
+ }
+ if (leader == win_get_leader(ps, w) && win_is_focused_raw(w)) {
+ return true;
+ }
+ }
+ return false;
}
/**
* Set leader of a window.
*/
-static inline void win_set_leader(session_t *ps, struct managed_win *w, xcb_window_t nleader) {
- xcb_window_t cache_leader_old = win_get_leader(ps, w);
+static inline void win_set_leader(session_t *ps, struct win *w, xcb_window_t nleader) {
+ auto cache_leader_old = win_get_leader(ps, w);
w->leader = nleader;
// Forcefully do this to deal with the case when a child window
// gets mapped before parent, or when the window is a waypoint
- win_stack_foreach_managed(i, wm_stack_end(ps->wm)) {
- i->cache_leader = XCB_NONE;
+ wm_stack_foreach(ps->wm, cursor) {
+ if (wm_ref_is_zombie(cursor)) {
+ continue;
+ }
+ auto i = wm_ref_deref(cursor);
+ if (i != NULL) {
+ i->cache_leader = XCB_NONE;
+ }
}
// Update the old and new window group and active_leader if the
// window could affect their state.
- xcb_window_t cache_leader = win_get_leader(ps, w);
+ auto cache_leader = win_get_leader(ps, w);
if (win_is_focused_raw(w) && cache_leader_old != cache_leader) {
wm_set_active_leader(ps->wm, cache_leader);
@@ -241,7 +232,7 @@ static inline void win_set_leader(session_t *ps, struct managed_win *w, xcb_wind
/**
* Get a rectangular region a window occupies, excluding shadow.
*/
-static void win_get_region_local(const struct managed_win *w, region_t *res) {
+static void win_get_region_local(const struct win *w, region_t *res) {
assert(w->widthb >= 0 && w->heightb >= 0);
pixman_region32_fini(res);
pixman_region32_init_rect(res, 0, 0, (uint)w->widthb, (uint)w->heightb);
@@ -250,7 +241,7 @@ static void win_get_region_local(const struct managed_win *w, region_t *res) {
/**
* Get a rectangular region a window occupies, excluding frame and shadow.
*/
-void win_get_region_noframe_local(const struct managed_win *w, region_t *res) {
+void win_get_region_noframe_local(const struct win *w, region_t *res) {
const margin_t extents = win_calc_frame_extents(w);
int x = extents.left;
@@ -268,7 +259,7 @@ void win_get_region_noframe_local(const struct managed_win *w, region_t *res) {
gen_without_corners(win_get_region_noframe_local);
-void win_get_region_frame_local(const struct managed_win *w, region_t *res) {
+void win_get_region_frame_local(const struct win *w, region_t *res) {
const margin_t extents = win_calc_frame_extents(w);
auto outer_width = w->widthb;
auto outer_height = w->heightb;
@@ -303,7 +294,7 @@ gen_by_val(win_get_region_frame_local);
* @param ps current session
* @param w struct _win element representing the window
*/
-void add_damage_from_win(session_t *ps, const struct managed_win *w) {
+void add_damage_from_win(session_t *ps, const struct win *w) {
// XXX there was a cached extents region, investigate
// if that's better
@@ -317,22 +308,19 @@ void add_damage_from_win(session_t *ps, const struct managed_win *w) {
}
/// Release the images attached to this window
-static inline void win_release_pixmap(backend_t *base, struct managed_win *w) {
- log_debug("Releasing pixmap of window %#010x (%s)", w->base.id, w->name);
- assert(w->win_image);
+static inline void win_release_pixmap(backend_t *base, struct win *w) {
+ log_debug("Releasing pixmap of window %#010x (%s)", win_id(w), w->name);
if (w->win_image) {
xcb_pixmap_t pixmap = XCB_NONE;
pixmap = base->ops.release_image(base, w->win_image);
w->win_image = NULL;
- // Bypassing win_set_flags, because `w` might have been destroyed
- w->flags |= WIN_FLAGS_PIXMAP_NONE;
if (pixmap != XCB_NONE) {
xcb_free_pixmap(base->c->c, pixmap);
}
}
}
-static inline void win_release_shadow(backend_t *base, struct managed_win *w) {
- log_debug("Releasing shadow of window %#010x (%s)", w->base.id, w->name);
+static inline void win_release_shadow(backend_t *base, struct win *w) {
+ log_debug("Releasing shadow of window %#010x (%s)", win_id(w), w->name);
if (w->shadow_image) {
assert(w->shadow);
xcb_pixmap_t pixmap = XCB_NONE;
@@ -344,7 +332,7 @@ static inline void win_release_shadow(backend_t *base, struct managed_win *w) {
}
}
-static inline void win_release_mask(backend_t *base, struct managed_win *w) {
+static inline void win_release_mask(backend_t *base, struct win *w) {
if (w->mask_image) {
xcb_pixmap_t pixmap = XCB_NONE;
pixmap = base->ops.release_image(base, w->mask_image);
@@ -355,50 +343,23 @@ static inline void win_release_mask(backend_t *base, struct managed_win *w) {
}
}
-static inline bool win_bind_pixmap(struct backend_base *b, struct managed_win *w) {
- assert(!w->win_image);
- auto pixmap = x_new_id(b->c);
- auto e = xcb_request_check(
- b->c->c, xcb_composite_name_window_pixmap_checked(b->c->c, w->base.id, pixmap));
- if (e) {
- log_error("Failed to get named pixmap for window %#010x(%s)", w->base.id,
- w->name);
- free(e);
- return false;
- }
- log_debug("New named pixmap for %#010x (%s) : %#010x", w->base.id, w->name, pixmap);
- w->win_image = b->ops.bind_pixmap(b, pixmap, x_get_visual_info(b->c, w->a.visual));
- if (!w->win_image) {
- log_error("Failed to bind pixmap");
- xcb_free_pixmap(b->c->c, pixmap);
- win_set_flags(w, WIN_FLAGS_IMAGE_ERROR);
- return false;
- }
-
- win_clear_flags(w, WIN_FLAGS_PIXMAP_NONE);
- return true;
-}
-
-void win_release_images(struct backend_base *backend, struct managed_win *w) {
+void win_release_images(struct backend_base *backend, struct win *w) {
// We don't want to decide what we should do if the image we want to
// release is stale (do we clear the stale flags or not?) But if we are
// not releasing any images anyway, we don't care about the stale flags.
+ assert(w->win_image == NULL || !win_check_flags_all(w, WIN_FLAGS_PIXMAP_STALE));
- if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) {
- assert(!win_check_flags_all(w, WIN_FLAGS_PIXMAP_STALE));
- win_release_pixmap(backend, w);
- }
-
+ win_release_pixmap(backend, w);
win_release_shadow(backend, w);
win_release_mask(backend, w);
}
/// Returns true if the `prop` property is stale, as well as clears the stale
/// flag.
-static bool win_fetch_and_unset_property_stale(struct managed_win *w, xcb_atom_t prop);
+static bool win_fetch_and_unset_property_stale(struct win *w, xcb_atom_t prop);
/// Returns true if any of the properties are stale, as well as clear all the
/// stale flags.
-static void win_clear_all_properties_stale(struct managed_win *w);
+static void win_clear_all_properties_stale(struct win *w);
// TODO(yshui) make WIN_FLAGS_FACTOR_CHANGED more fine-grained, or find a better
// alternative
@@ -406,7 +367,7 @@ static void win_clear_all_properties_stale(struct managed_win *w);
/// Fetch new window properties from the X server, and run appropriate updates.
/// Might set WIN_FLAGS_FACTOR_CHANGED
-static void win_update_properties(session_t *ps, struct managed_win *w) {
+static void win_update_properties(session_t *ps, struct win *w) {
// we cannot receive property change when window has been destroyed
assert(w->state != WSTATE_DESTROYED);
@@ -421,8 +382,8 @@ static void win_update_properties(session_t *ps, struct managed_win *w) {
}
if (win_fetch_and_unset_property_stale(w, ps->atoms->a_NET_FRAME_EXTENTS)) {
- win_update_frame_extents(&ps->c, ps->atoms, w, w->client_win,
- ps->o.frame_opacity);
+ auto client_win = win_client_id(w, /*fallback_to_self=*/false);
+ win_update_frame_extents(&ps->c, ps->atoms, w, client_win, ps->o.frame_opacity);
add_damage_from_win(ps, w);
}
@@ -458,8 +419,10 @@ static void win_update_properties(session_t *ps, struct managed_win *w) {
}
if (win_fetch_and_unset_property_stale(w, ps->atoms->aWM_CLIENT_LEADER) ||
- win_fetch_and_unset_property_stale(w, ps->atoms->aWM_TRANSIENT_FOR)) {
- auto new_leader = win_get_leader_property(&ps->c, ps->atoms, w->client_win,
+ win_fetch_and_unset_property_stale(w, ps->atoms->aWM_TRANSIENT_FOR) ||
+ win_fetch_and_unset_property_stale(w, XCB_ATOM_WM_HINTS)) {
+ auto client_win = win_client_id(w, /*fallback_to_self=*/true);
+ auto new_leader = win_get_leader_property(&ps->c, ps->atoms, client_win,
ps->o.detect_transient,
ps->o.detect_client_leader);
if (w->leader != new_leader) {
@@ -471,15 +434,14 @@ static void win_update_properties(session_t *ps, struct managed_win *w) {
win_clear_all_properties_stale(w);
}
-static void map_win_start(struct managed_win *w);
/// Handle non-image flags. This phase might set IMAGES_STALE flags
-void win_process_update_flags(session_t *ps, struct managed_win *w) {
+void win_process_update_flags(session_t *ps, struct win *w) {
log_trace("Processing flags for window %#010x (%s), was rendered: %d, flags: "
"%#" PRIx64,
- w->base.id, w->name, w->to_paint, w->flags);
+ win_id(w), w->name, w->to_paint, w->flags);
if (win_check_flags_all(w, WIN_FLAGS_MAPPED)) {
- map_win_start(w);
+ win_map_start(w);
win_clear_flags(w, WIN_FLAGS_MAPPED);
}
@@ -489,21 +451,12 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) {
return;
}
- // Check client first, because later property updates need accurate client
- // window information
+ bool damaged = false;
if (win_check_flags_all(w, WIN_FLAGS_CLIENT_STALE)) {
- log_debug("Rechecking client window for %#010x (%s)", w->base.id, w->name);
- auto client_win = win_get_client_window(&ps->c, ps->wm, ps->atoms, w);
- if (w->client_win && w->client_win != client_win) {
- win_unmark_client(w);
- }
- log_debug("New client window for %#010x (%s): %#010x", w->base.id,
- w->name, client_win);
- win_mark_client(ps, w, client_win);
+ win_on_client_update(ps, w);
win_clear_flags(w, WIN_FLAGS_CLIENT_STALE);
}
- bool damaged = false;
if (win_check_flags_any(w, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE)) {
// For damage calculation purposes, we don't care if the window
// is mapped in X server, we only care if we rendered it last
@@ -555,8 +508,6 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) {
damaged = true;
win_clear_flags(w, WIN_FLAGS_POSITION_STALE);
}
-
- win_update_monitor(&ps->monitors, w);
}
if (win_check_flags_all(w, WIN_FLAGS_PROPERTY_STALE)) {
@@ -578,7 +529,7 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) {
}
}
-void win_process_image_flags(session_t *ps, struct managed_win *w) {
+void win_process_image_flags(session_t *ps, struct win *w) {
// Assert that the MAPPED flag is already handled.
assert(!win_check_flags_all(w, WIN_FLAGS_MAPPED));
@@ -587,35 +538,41 @@ void win_process_image_flags(session_t *ps, struct managed_win *w) {
return;
}
- // Not a loop
- while (win_check_flags_any(w, WIN_FLAGS_PIXMAP_STALE) &&
- !win_check_flags_all(w, WIN_FLAGS_IMAGE_ERROR)) {
- // Image needs to be updated, update it.
- if (!ps->backend_data) {
- // We are using legacy backend, nothing to do here.
- break;
- }
+ if (!win_check_flags_any(w, WIN_FLAGS_PIXMAP_STALE) ||
+ win_check_flags_all(w, WIN_FLAGS_PIXMAP_ERROR) ||
+ // We don't need to do anything here for legacy backends
+ ps->backend_data == NULL) {
+ win_clear_flags(w, WIN_FLAGS_PIXMAP_STALE);
+ return;
+ }
- if (win_check_flags_all(w, WIN_FLAGS_PIXMAP_STALE)) {
- // Check to make sure the window is still mapped,
- // otherwise we won't be able to rebind pixmap after
- // releasing it, yet we might still need the pixmap for
- // rendering.
- if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) {
- // Must release images first, otherwise breaks
- // NVIDIA driver
- win_release_pixmap(ps->backend_data, w);
- }
- win_bind_pixmap(ps->backend_data, w);
- }
+ // Image needs to be updated, update it.
+ win_clear_flags(w, WIN_FLAGS_PIXMAP_STALE);
- // break here, loop always run only once
- break;
+ // Check to make sure the window is still mapped, otherwise we won't be able to
+ // rebind pixmap after releasing it, yet we might still need the pixmap for
+ // rendering.
+ auto pixmap = x_new_id(&ps->c);
+ auto e = xcb_request_check(
+ ps->c.c, xcb_composite_name_window_pixmap_checked(ps->c.c, win_id(w), pixmap));
+ if (e != NULL) {
+ log_debug("Failed to get named pixmap for window %#010x(%s): %s. "
+ "Retaining its current window image",
+ win_id(w), w->name, x_strerror(e));
+ free(e);
+ return;
}
- // Clear stale image flags
- if (win_check_flags_any(w, WIN_FLAGS_PIXMAP_STALE)) {
- win_clear_flags(w, WIN_FLAGS_PIXMAP_STALE);
+ log_debug("New named pixmap for %#010x (%s) : %#010x", win_id(w), w->name, pixmap);
+
+ // Must release images first, otherwise breaks NVIDIA driver
+ win_release_pixmap(ps->backend_data, w);
+ w->win_image = ps->backend_data->ops.bind_pixmap(
+ ps->backend_data, pixmap, x_get_visual_info(&ps->c, w->a.visual));
+ if (!w->win_image) {
+ log_error("Failed to bind pixmap");
+ xcb_free_pixmap(ps->c.c, pixmap);
+ win_set_flags(w, WIN_FLAGS_PIXMAP_ERROR);
}
}
@@ -623,7 +580,7 @@ void win_process_image_flags(session_t *ps, struct managed_win *w) {
* Check if a window has rounded corners.
* XXX This is really dumb
*/
-static bool attr_pure win_has_rounded_corners(const struct managed_win *w) {
+static bool attr_pure win_has_rounded_corners(const struct win *w) {
if (!w->bounding_shaped) {
return false;
}
@@ -656,22 +613,16 @@ static bool attr_pure win_has_rounded_corners(const struct managed_win *w) {
return false;
}
-int win_update_name(struct x_connection *c, struct atom *atoms, struct managed_win *w) {
+int win_update_name(struct x_connection *c, struct atom *atoms, struct win *w) {
char **strlst = NULL;
int nstr = 0;
+ auto client_win = win_client_id(w, /*fallback_to_self=*/true);
- if (!w->client_win) {
- return 0;
- }
-
- if (!(wid_get_text_prop(c, atoms, w->client_win, atoms->a_NET_WM_NAME, &strlst, &nstr))) {
- log_debug("(%#010x): _NET_WM_NAME unset, falling back to "
- "WM_NAME.",
- w->client_win);
+ if (!(wid_get_text_prop(c, atoms, client_win, atoms->a_NET_WM_NAME, &strlst, &nstr))) {
+ log_debug("(%#010x): _NET_WM_NAME unset, falling back to WM_NAME.", client_win);
- if (!wid_get_text_prop(c, atoms, w->client_win, atoms->aWM_NAME, &strlst,
- &nstr)) {
- log_debug("Unsetting window name for %#010x", w->client_win);
+ if (!wid_get_text_prop(c, atoms, client_win, atoms->aWM_NAME, &strlst, &nstr)) {
+ log_debug("Unsetting window name for %#010x", client_win);
free(w->name);
w->name = NULL;
return -1;
@@ -687,17 +638,17 @@ int win_update_name(struct x_connection *c, struct atom *atoms, struct managed_w
free(strlst);
- log_debug("(%#010x): client = %#010x, name = \"%s\", "
- "ret = %d",
- w->base.id, w->client_win, w->name, ret);
+ log_debug("(%#010x): client = %#010x, name = \"%s\", ret = %d", win_id(w),
+ client_win, w->name, ret);
return ret;
}
-int win_update_role(struct x_connection *c, struct atom *atoms, struct managed_win *w) {
+int win_update_role(struct x_connection *c, struct atom *atoms, struct win *w) {
char **strlst = NULL;
int nstr = 0;
+ auto client_win = win_client_id(w, /*fallback_to_self=*/true);
- if (!wid_get_text_prop(c, atoms, w->client_win, atoms->aWM_WINDOW_ROLE, &strlst, &nstr)) {
+ if (!wid_get_text_prop(c, atoms, client_win, atoms->aWM_WINDOW_ROLE, &strlst, &nstr)) {
return -1;
}
@@ -710,9 +661,8 @@ int win_update_role(struct x_connection *c, struct atom *atoms, struct managed_w
free(strlst);
- log_trace("(%#010x): client = %#010x, role = \"%s\", "
- "ret = %d",
- w->base.id, w->client_win, w->role, ret);
+ log_trace("(%#010x): client = %#010x, role = \"%s\", ret = %d", win_id(w),
+ client_win, w->role, ret);
return ret;
}
@@ -768,19 +718,19 @@ static bool wid_get_opacity_prop(struct x_connection *c, struct atom *atoms,
}
// XXX should distinguish between frame has alpha and window body has alpha
-bool win_has_alpha(const struct managed_win *w) {
+bool win_has_alpha(const struct win *w) {
return w->pictfmt && w->pictfmt->type == XCB_RENDER_PICT_TYPE_DIRECT &&
w->pictfmt->direct.alpha_mask;
}
-bool win_client_has_alpha(const struct managed_win *w) {
+bool win_client_has_alpha(const struct win *w) {
return w->client_pictfmt && w->client_pictfmt->type == XCB_RENDER_PICT_TYPE_DIRECT &&
w->client_pictfmt->direct.alpha_mask;
}
-winmode_t win_calc_mode_raw(const struct managed_win *w) {
+winmode_t win_calc_mode_raw(const struct win *w) {
if (win_has_alpha(w)) {
- if (w->client_win == XCB_NONE) {
+ if (wm_ref_client_of(w->tree_ref) == NULL) {
// This is a window not managed by the WM, and it has
// alpha, so it's transparent. No need to check WM frame.
return WMODE_TRANS;
@@ -814,7 +764,7 @@ winmode_t win_calc_mode_raw(const struct managed_win *w) {
return WMODE_SOLID;
}
-winmode_t win_calc_mode(const struct managed_win *w) {
+winmode_t win_calc_mode(const struct win *w) {
if (win_animatable_get(w, WIN_SCRIPT_OPACITY) < 1.0) {
return WMODE_TRANS;
}
@@ -835,7 +785,7 @@ winmode_t win_calc_mode(const struct managed_win *w) {
*
* @return target opacity
*/
-static double win_calc_opacity_target(session_t *ps, const struct managed_win *w) {
+static double win_calc_opacity_target(session_t *ps, const struct win *w) {
double opacity = 1;
if (w->state == WSTATE_UNMAPPED || w->state == WSTATE_DESTROYED) {
@@ -870,16 +820,14 @@ static double win_calc_opacity_target(session_t *ps, const struct managed_win *w
/// Finish the unmapping of a window (e.g. after fading has finished).
/// Doesn't free `w`
-void unmap_win_finish(session_t *ps, struct managed_win *w) {
+void unmap_win_finish(session_t *ps, struct win *w) {
w->reg_ignore_valid = false;
// We are in unmap_win, this window definitely was viewable
if (ps->backend_data) {
// Only the pixmap needs to be freed and reacquired when mapping.
// Shadow image can be preserved.
- if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) {
- win_release_pixmap(ps->backend_data, w);
- }
+ win_release_pixmap(ps->backend_data, w);
} else {
assert(!w->win_image);
assert(!w->shadow_image);
@@ -890,7 +838,7 @@ void unmap_win_finish(session_t *ps, struct managed_win *w) {
// Try again at binding images when the window is mapped next time
if (w->state != WSTATE_DESTROYED) {
- win_clear_flags(w, WIN_FLAGS_IMAGE_ERROR);
+ win_clear_flags(w, WIN_FLAGS_PIXMAP_ERROR);
}
assert(w->running_animation == NULL);
}
@@ -898,7 +846,7 @@ void unmap_win_finish(session_t *ps, struct managed_win *w) {
/**
* Determine whether a window is to be dimmed.
*/
-bool win_should_dim(session_t *ps, const struct managed_win *w) {
+bool win_should_dim(session_t *ps, const struct win *w) {
// Make sure we do nothing if the window is unmapped / being destroyed
if (w->state == WSTATE_UNMAPPED) {
return false;
@@ -915,10 +863,9 @@ bool win_should_dim(session_t *ps, const struct managed_win *w) {
*
* The property must be set on the outermost window, usually the WM frame.
*/
-void win_update_prop_shadow_raw(struct x_connection *c, struct atom *atoms,
- struct managed_win *w) {
+void win_update_prop_shadow_raw(struct x_connection *c, struct atom *atoms, struct win *w) {
winprop_t prop =
- x_get_prop(c, w->base.id, atoms->a_COMPTON_SHADOW, 1, XCB_ATOM_CARDINAL, 32);
+ x_get_prop(c, win_id(w), atoms->a_COMPTON_SHADOW, 1, XCB_ATOM_CARDINAL, 32);
if (!prop.nitems) {
w->prop_shadow = -1;
@@ -929,12 +876,12 @@ void win_update_prop_shadow_raw(struct x_connection *c, struct atom *atoms,
free_winprop(&prop);
}
-static void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new) {
+static void win_set_shadow(session_t *ps, struct win *w, bool shadow_new) {
if (w->shadow == shadow_new) {
return;
}
- log_debug("Updating shadow property of window %#010x (%s) to %d", w->base.id,
+ log_debug("Updating shadow property of window %#010x (%s) to %d", win_id(w),
w->name, shadow_new);
// We don't handle property updates of non-visible windows until they are
@@ -980,8 +927,8 @@ static void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new
* Determine if a window should have shadow, and update things depending
* on shadow state.
*/
-static void win_determine_shadow(session_t *ps, struct managed_win *w) {
- log_debug("Determining shadow of window %#010x (%s)", w->base.id, w->name);
+static void win_determine_shadow(session_t *ps, struct win *w) {
+ log_debug("Determining shadow of window %#010x (%s)", win_id(w), w->name);
bool shadow_new = w->shadow;
if (w->shadow_force != UNSET) {
@@ -1011,8 +958,7 @@ static void win_determine_shadow(session_t *ps, struct managed_win *w) {
* Reread _COMPTON_SHADOW property from a window and update related
* things.
*/
-static bool
-win_update_prop_shadow(struct x_connection *c, struct atom *atoms, struct managed_win *w) {
+static bool win_update_prop_shadow(struct x_connection *c, struct atom *atoms, struct win *w) {
long long attr_shadow_old = w->prop_shadow;
win_update_prop_shadow_raw(c, atoms, w);
return w->prop_shadow != attr_shadow_old;
@@ -1022,8 +968,9 @@ win_update_prop_shadow(struct x_connection *c, struct atom *atoms, struct manage
* Update window EWMH fullscreen state.
*/
bool win_update_prop_fullscreen(struct x_connection *c, const struct atom *atoms,
- struct managed_win *w) {
- auto prop = x_get_prop(c, w->client_win, atoms->a_NET_WM_STATE, 12, XCB_ATOM_ATOM, 0);
+ struct win *w) {
+ auto prop = x_get_prop(c, win_client_id(w, /*fallback_to_self=*/true),
+ atoms->a_NET_WM_STATE, 12, XCB_ATOM_ATOM, 0);
bool is_fullscreen = false;
for (uint32_t i = 0; i < prop.nitems; i++) {
if (prop.atom[i] == atoms->a_NET_WM_STATE_FULLSCREEN) {
@@ -1038,13 +985,13 @@ bool win_update_prop_fullscreen(struct x_connection *c, const struct atom *atoms
return changed;
}
-static void win_determine_clip_shadow_above(session_t *ps, struct managed_win *w) {
+static void win_determine_clip_shadow_above(session_t *ps, struct win *w) {
bool should_crop = (ps->o.wintype_option[w->window_type].clip_shadow_above ||
c2_match(ps->c2_state, w, ps->o.shadow_clip_list, NULL));
w->clip_shadow_above = should_crop;
}
-static void win_set_invert_color(session_t *ps, struct managed_win *w, bool invert_color_new) {
+static void win_set_invert_color(session_t *ps, struct win *w, bool invert_color_new) {
if (w->invert_color == invert_color_new) {
return;
}
@@ -1057,7 +1004,7 @@ static void win_set_invert_color(session_t *ps, struct managed_win *w, bool inve
/**
* Determine if a window should have color inverted.
*/
-static void win_determine_invert_color(session_t *ps, struct managed_win *w) {
+static void win_determine_invert_color(session_t *ps, struct win *w) {
bool invert_color_new = w->invert_color;
if (UNSET != w->invert_color_force) {
@@ -1072,7 +1019,7 @@ static void win_determine_invert_color(session_t *ps, struct managed_win *w) {
/**
* Set w->invert_color_force of a window.
*/
-void win_set_invert_color_force(session_t *ps, struct managed_win *w, switch_t val) {
+void win_set_invert_color_force(session_t *ps, struct win *w, switch_t val) {
if (val != w->invert_color_force) {
w->invert_color_force = val;
win_determine_invert_color(ps, w);
@@ -1085,14 +1032,14 @@ void win_set_invert_color_force(session_t *ps, struct managed_win *w, switch_t v
*
* Doesn't affect fading already in progress
*/
-void win_set_fade_force(struct managed_win *w, switch_t val) {
+void win_set_fade_force(struct win *w, switch_t val) {
w->fade_force = val;
}
/**
* Set w->focused_force of a window.
*/
-void win_set_focused_force(session_t *ps, struct managed_win *w, switch_t val) {
+void win_set_focused_force(session_t *ps, struct win *w, switch_t val) {
if (val != w->focused_force) {
w->focused_force = val;
win_on_factor_change(ps, w);
@@ -1103,7 +1050,7 @@ void win_set_focused_force(session_t *ps, struct managed_win *w, switch_t val) {
/**
* Set w->shadow_force of a window.
*/
-void win_set_shadow_force(session_t *ps, struct managed_win *w, switch_t val) {
+void win_set_shadow_force(session_t *ps, struct win *w, switch_t val) {
if (val != w->shadow_force) {
w->shadow_force = val;
win_determine_shadow(ps, w);
@@ -1111,8 +1058,7 @@ void win_set_shadow_force(session_t *ps, struct managed_win *w, switch_t val) {
}
}
-static void
-win_set_blur_background(session_t *ps, struct managed_win *w, bool blur_background_new) {
+static void win_set_blur_background(session_t *ps, struct win *w, bool blur_background_new) {
if (w->blur_background == blur_background_new) {
return;
}
@@ -1125,8 +1071,7 @@ win_set_blur_background(session_t *ps, struct managed_win *w, bool blur_backgrou
add_damage_from_win(ps, w);
}
-static void
-win_set_fg_shader(session_t *ps, struct managed_win *w, struct shader_info *shader_new) {
+static void win_set_fg_shader(session_t *ps, struct win *w, struct shader_info *shader_new) {
if (w->fg_shader == shader_new) {
return;
}
@@ -1141,8 +1086,8 @@ win_set_fg_shader(session_t *ps, struct managed_win *w, struct shader_info *shad
/**
* Determine if a window should have background blurred.
*/
-static void win_determine_blur_background(session_t *ps, struct managed_win *w) {
- log_debug("Determining blur-background of window %#010x (%s)", w->base.id, w->name);
+static void win_determine_blur_background(session_t *ps, struct win *w) {
+ log_debug("Determining blur-background of window %#010x (%s)", win_id(w), w->name);
if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) {
return;
}
@@ -1165,7 +1110,7 @@ static void win_determine_blur_background(session_t *ps, struct managed_win *w)
/**
* Determine if a window should have rounded corners.
*/
-static void win_determine_rounded_corners(session_t *ps, struct managed_win *w) {
+static void win_determine_rounded_corners(session_t *ps, struct win *w) {
void *radius_override = NULL;
if (c2_match(ps->c2_state, w, ps->o.corner_radius_rules, &radius_override)) {
log_debug("Matched corner rule! %d", w->corner_radius);
@@ -1182,7 +1127,7 @@ static void win_determine_rounded_corners(session_t *ps, struct managed_win *w)
((w && w->is_fullscreen) ||
c2_match(ps->c2_state, w, ps->o.rounded_corners_blacklist, NULL))) {
w->corner_radius = 0;
- log_debug("Not rounding corners for window %#010x", w->base.id);
+ log_debug("Not rounding corners for window %#010x", win_id(w));
} else {
if (radius_override) {
w->corner_radius = (int)(long)radius_override;
@@ -1190,7 +1135,7 @@ static void win_determine_rounded_corners(session_t *ps, struct managed_win *w)
w->corner_radius = ps->o.corner_radius;
}
- log_debug("Rounding corners for window %#010x", w->base.id);
+ log_debug("Rounding corners for window %#010x", win_id(w));
// Initialize the border color to an invalid value
w->border_col[0] = w->border_col[1] = w->border_col[2] =
w->border_col[3] = -1.0F;
@@ -1200,7 +1145,7 @@ static void win_determine_rounded_corners(session_t *ps, struct managed_win *w)
/**
* Determine custom window shader to use for a window.
*/
-static void win_determine_fg_shader(session_t *ps, struct managed_win *w) {
+static void win_determine_fg_shader(session_t *ps, struct win *w) {
if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) {
return;
}
@@ -1222,7 +1167,7 @@ static void win_determine_fg_shader(session_t *ps, struct managed_win *w) {
/**
* Update window opacity according to opacity rules.
*/
-void win_update_opacity_rule(session_t *ps, struct managed_win *w) {
+void win_update_opacity_rule(session_t *ps, struct win *w) {
if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) {
return;
}
@@ -1244,9 +1189,10 @@ void win_update_opacity_rule(session_t *ps, struct managed_win *w) {
*
* TODO(yshui) need better name
*/
-void win_on_factor_change(session_t *ps, struct managed_win *w) {
- log_debug("Window %#010x (%s) factor change", w->base.id, w->name);
- c2_window_state_update(ps->c2_state, &w->c2_state, ps->c.c, w->client_win, w->base.id);
+void win_on_factor_change(session_t *ps, struct win *w) {
+ auto wid = win_client_id(w, /*fallback_to_self=*/true);
+ log_debug("Window %#010x, client %#010x (%s) factor change", win_id(w), wid, w->name);
+ c2_window_state_update(ps->c2_state, &w->c2_state, ps->c.c, wid, win_id(w));
// Focus and is_fullscreen needs to be updated first, as other rules might depend
// on the focused state of the window
win_update_focused(ps, w);
@@ -1277,7 +1223,8 @@ void win_on_factor_change(session_t *ps, struct managed_win *w) {
w->reg_ignore_valid = false;
if (ps->debug_window != XCB_NONE &&
- (w->base.id == ps->debug_window || w->client_win == ps->debug_window)) {
+ (win_id(w) == ps->debug_window ||
+ (win_client_id(w, /*fallback_to_self=*/false) == ps->debug_window))) {
w->paint_excluded = true;
}
}
@@ -1285,9 +1232,9 @@ void win_on_factor_change(session_t *ps, struct managed_win *w) {
/**
* Update cache data in struct _win that depends on window size.
*/
-void win_on_win_size_change(struct managed_win *w, int shadow_offset_x,
- int shadow_offset_y, int shadow_radius) {
- log_trace("Window %#010x (%s) size changed, was %dx%d, now %dx%d", w->base.id,
+void win_on_win_size_change(struct win *w, int shadow_offset_x, int shadow_offset_y,
+ int shadow_radius) {
+ log_trace("Window %#010x (%s) size changed, was %dx%d, now %dx%d", win_id(w),
w->name, w->widthb, w->heightb, w->g.width + w->g.border_width * 2,
w->g.height + w->g.border_width * 2);
@@ -1306,36 +1253,38 @@ void win_on_win_size_change(struct managed_win *w, int shadow_offset_x,
/**
* Update window type.
*/
-bool win_update_wintype(struct x_connection *c, struct atom *atoms, struct managed_win *w) {
+bool win_update_wintype(struct x_connection *c, struct atom *atoms, struct win *w) {
const wintype_t wtype_old = w->window_type;
+ auto wid = win_client_id(w, /*fallback_to_self=*/true);
// Detect window type here
- w->window_type = wid_get_prop_wintype(c, atoms, w->client_win);
+ w->window_type = wid_get_prop_wintype(c, atoms, wid);
// Conform to EWMH standard, if _NET_WM_WINDOW_TYPE is not present, take
// override-redirect windows or windows without WM_TRANSIENT_FOR as
// _NET_WM_WINDOW_TYPE_NORMAL, otherwise as _NET_WM_WINDOW_TYPE_DIALOG.
if (WINTYPE_UNKNOWN == w->window_type) {
if (w->a.override_redirect ||
- !wid_has_prop(c->c, w->client_win, atoms->aWM_TRANSIENT_FOR)) {
+ !wid_has_prop(c->c, wid, atoms->aWM_TRANSIENT_FOR)) {
w->window_type = WINTYPE_NORMAL;
} else {
w->window_type = WINTYPE_DIALOG;
}
}
+ log_debug("Window (%#010x) has type %s", win_id(w), WINTYPES[w->window_type].name);
+
return w->window_type != wtype_old;
}
/**
- * Mark a window as the client window of another.
+ * Update window after its client window changed.
*
* @param ps current session
* @param w struct _win of the parent window
- * @param client window ID of the client window
*/
-static void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t client) {
- w->client_win = client;
+void win_on_client_update(session_t *ps, struct win *w) {
+ auto client_win = wm_ref_client_of(w->tree_ref);
// If the window isn't mapped yet, stop here, as the function will be
// called in map_win()
@@ -1345,12 +1294,13 @@ static void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t c
win_update_wintype(&ps->c, ps->atoms, w);
+ xcb_window_t client_win_id = client_win ? wm_ref_win_id(client_win) : XCB_NONE;
// Get frame widths. The window is in damaged area already.
- win_update_frame_extents(&ps->c, ps->atoms, w, client, ps->o.frame_opacity);
+ win_update_frame_extents(&ps->c, ps->atoms, w, client_win_id, ps->o.frame_opacity);
// Get window group
if (ps->o.track_leader) {
- auto new_leader = win_get_leader_property(&ps->c, ps->atoms, w->client_win,
+ auto new_leader = win_get_leader_property(&ps->c, ps->atoms, client_win_id,
ps->o.detect_transient,
ps->o.detect_client_leader);
if (w->leader != new_leader) {
@@ -1366,11 +1316,8 @@ static void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t c
// Update everything related to conditions
win_on_factor_change(ps, w);
- xcb_generic_error_t *e = NULL;
- auto r = xcb_get_window_attributes_reply(
- ps->c.c, xcb_get_window_attributes(ps->c.c, w->client_win), &e);
+ auto r = XCB_AWAIT(xcb_get_window_attributes, ps->c.c, client_win_id);
if (!r) {
- log_error_x_error(e, "Failed to get client window attributes");
return;
}
@@ -1378,91 +1325,17 @@ static void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t c
free(r);
}
-/**
- * Unmark current client window of a window.
- *
- * @param ps current session
- * @param w struct _win of the parent window
- */
-void win_unmark_client(struct managed_win *w) {
- xcb_window_t client = w->client_win;
- log_debug("Detaching client window %#010x from frame %#010x (%s)", client,
- w->base.id, w->name);
- w->client_win = XCB_NONE;
-}
-
-/**
- * Look for the client window of a particular window.
- */
-static xcb_window_t
-find_client_win(struct x_connection *c, struct wm *wm, struct atom *atoms, xcb_window_t w) {
- xcb_query_tree_reply_t *reply =
- xcb_query_tree_reply(c->c, xcb_query_tree(c->c, w), NULL);
- if (!reply) {
- return XCB_NONE;
- }
-
- xcb_window_t *children = xcb_query_tree_children(reply);
- int nchildren = xcb_query_tree_children_length(reply);
- xcb_window_t ret = XCB_NONE;
-
- for (int i = 0; i < nchildren; ++i) {
- auto subwin = wm ? wm_subwin_find(wm, children[i]) : NULL;
- bool has_wm_state;
- assert(subwin != NULL || wm == NULL);
- if (!subwin || subwin->has_wm_state == TRI_UNKNOWN) {
- has_wm_state = wid_has_prop(c->c, children[i], atoms->aWM_STATE);
- if (subwin) {
- subwin->has_wm_state = has_wm_state ? TRI_TRUE : TRI_FALSE;
- }
- } else {
- has_wm_state = subwin->has_wm_state == TRI_TRUE;
- }
- if (has_wm_state) {
- ret = children[i];
- break;
- }
- }
-
- free(reply);
- return ret;
-}
-
-/**
- * Get client window of a window.
- *
- * @param ps current session
- * @param w struct _win of the parent window
- */
-xcb_window_t win_get_client_window(struct x_connection *c, struct wm *wm,
- struct atom *atoms, const struct managed_win *w) {
- // Always recursively look for a window with WM_STATE, as Fluxbox
- // sets override-redirect flags on all frame windows.
- xcb_window_t cw = find_client_win(c, wm, atoms, w->base.id);
- if (cw) {
- log_debug("(%#010x): client %#010x", w->base.id, cw);
- } else {
- // Set a window's client window to itself if we couldn't find a
- // client window
- cw = w->base.id;
- log_debug("(%#010x): client self (%s)", w->base.id,
- (w->a.override_redirect ? "override-redirected" : "wmwin"));
- }
-
- return cw;
-}
-
#ifdef CONFIG_OPENGL
-void free_win_res_glx(session_t *ps, struct managed_win *w);
+void free_win_res_glx(session_t *ps, struct win *w);
#else
-static inline void free_win_res_glx(session_t * /*ps*/, struct managed_win * /*w*/) {
+static inline void free_win_res_glx(session_t * /*ps*/, struct win * /*w*/) {
}
#endif
/**
* Free all resources in a struct _win
.
*/
-void free_win_res(session_t *ps, struct managed_win *w) {
+void free_win_res(session_t *ps, struct win *w) {
// No need to call backend release_image here because
// finish_unmap_win should've done that for us.
// XXX unless we are called by session_destroy
@@ -1476,7 +1349,7 @@ void free_win_res(session_t *ps, struct managed_win *w) {
pixman_region32_fini(&w->damaged);
pixman_region32_fini(&w->bounding_shape);
// BadDamage may be thrown if the window is destroyed
- set_ignore_cookie(&ps->c, xcb_damage_destroy(ps->c.c, w->damage));
+ x_set_error_action_ignore(&ps->c, xcb_damage_destroy(ps->c.c, w->damage));
rc_region_unref(&w->reg_ignore);
free(w->name);
free(w->class_instance);
@@ -1489,34 +1362,20 @@ void free_win_res(session_t *ps, struct managed_win *w) {
c2_window_state_destroy(ps->c2_state, &w->c2_state);
}
-/// Query the Xorg for information about window `win`, and return that
-/// information in a new managed_win object. However, if the window does
-/// not need to be managed, the original `win` object is returned.
-struct win *attr_ret_nonnull maybe_allocate_managed_win(session_t *ps, struct win *w) {
- static const struct managed_win win_def = {
- // No need to initialize. (or, you can think that
- // they are initialized right here).
- // The following ones are updated during paint or paint preprocess
- .to_paint = false,
+/// Query the Xorg for information about window `win`, and assign a window to `cursor` if
+/// this window should be managed.
+struct win *win_maybe_allocate(session_t *ps, struct wm_ref *cursor,
+ xcb_get_window_attributes_reply_t *attrs) {
+ static const struct win win_def = {
.frame_opacity = 1.0,
.frame_opacity_for_same_colors = false,
.frame_opacity_for_same_colors_tolerance = 0.5,
.frame_opacity_for_same_colors_multiplier = 5,
- .dim = false,
- .invert_color = false,
- .blur_background = false,
- .reg_ignore = NULL,
- // The following ones are updated for other reasons
- .pixmap_damaged = false, // updated by damage events
- .state = WSTATE_UNMAPPED, // updated by window state changes
- .in_openclose = true, // set to false after first map is done,
- // true here because window is just created
- .reg_ignore_valid = false, // set to true when damaged
- .flags = WIN_FLAGS_PIXMAP_NONE, // updated by
- // property/attributes/etc
- // change
- .stale_props = NULL,
- .stale_props_capacity = 0,
+ .in_openclose = true, // set to false after first map is done,
+ // true here because window is just created
+ .flags = 0, // updated by
+ // property/attributes/etc
+ // change
// Runtime variables, updated by dbus
.fade_force = UNSET,
@@ -1524,129 +1383,61 @@ struct win *attr_ret_nonnull maybe_allocate_managed_win(session_t *ps, struct wi
.focused_force = UNSET,
.invert_color_force = UNSET,
- // Initialized in this function
- .a = {0},
- .pictfmt = NULL,
- .client_pictfmt = NULL,
- .widthb = 0,
- .heightb = 0,
- .shadow_dx = 0,
- .shadow_dy = 0,
- .shadow_width = 0,
- .shadow_height = 0,
- .damage = XCB_NONE,
-
- // Not initialized until mapped, this variables
- // have no meaning or have no use until the window
- // is mapped
- .win_image = NULL,
- .shadow_image = NULL,
- .mask_image = NULL,
- .prev_trans = NULL,
- .shadow = false,
- .clip_shadow_above = false,
- .fg_shader = NULL,
- .randr_monitor = -1,
.mode = WMODE_TRANS,
- .ever_damaged = false,
- .client_win = XCB_NONE,
.leader = XCB_NONE,
.cache_leader = XCB_NONE,
.window_type = WINTYPE_UNKNOWN,
- .focused = false,
- .has_opacity_prop = false,
.opacity_prop = OPAQUE,
- .opacity_is_set = false,
.opacity_set = 1,
- .opacity = 0,
- .frame_extents = MARGIN_INIT, // in win_mark_client
- .bounding_shaped = false,
- .bounding_shape = {0},
- .rounded_corners = false,
- .paint_excluded = false,
- .fade_excluded = false,
- .transparent_clipping = false,
- .unredir_if_possible_excluded = false,
+ .frame_extents = MARGIN_INIT,
.prop_shadow = -1,
- // following 4 are set in win_mark_client
- .name = NULL,
- .class_instance = NULL,
- .class_general = NULL,
- .role = NULL,
- // Initialized during paint
.paint = PAINT_INIT,
.shadow_paint = PAINT_INIT,
-
- .corner_radius = 0,
};
- assert(!w->destroyed);
- assert(w->is_new);
-
- w->is_new = false;
-
// Reject overlay window
- if (w->id == ps->overlay) {
+ if (wm_ref_win_id(cursor) == ps->overlay) {
// Would anyone reparent windows to the overlay window? Doing this
// just in case.
- return w;
- }
-
- auto duplicated_win = wm_find_managed(ps->wm, w->id);
- if (duplicated_win) {
- log_debug("Window %#010x (recorded name: %s) added multiple "
- "times",
- w->id, duplicated_win->name);
- return &duplicated_win->base;
+ return NULL;
}
- log_debug("Managing window %#010x", w->id);
- xcb_get_window_attributes_cookie_t acookie =
- xcb_get_window_attributes(ps->c.c, w->id);
- xcb_get_window_attributes_reply_t *a =
- xcb_get_window_attributes_reply(ps->c.c, acookie, NULL);
- if (!a || a->map_state == XCB_MAP_STATE_UNVIEWABLE) {
+ xcb_window_t wid = wm_ref_win_id(cursor);
+ log_debug("Managing window %#010x", wid);
+ if (attrs->map_state == XCB_MAP_STATE_UNVIEWABLE) {
// Failed to get window attributes or geometry probably means
// the window is gone already. Unviewable means the window is
// already reparented elsewhere.
// BTW, we don't care about Input Only windows, except for
// stacking proposes, so we need to keep track of them still.
- free(a);
- return w;
+ return NULL;
}
- if (a->_class == XCB_WINDOW_CLASS_INPUT_ONLY) {
+ if (attrs->_class == XCB_WINDOW_CLASS_INPUT_ONLY) {
// No need to manage this window, but we still keep it on the
// window stack
- w->managed = false;
- free(a);
- return w;
+ return NULL;
}
// Allocate and initialize the new win structure
- auto new_internal = cmalloc(struct managed_win_internal);
- auto new = (struct managed_win *)new_internal;
+ auto new = cmalloc(struct win);
// Fill structure
// We only need to initialize the part that are not initialized
// by map_win
*new = win_def;
- new->base = *w;
- new->base.managed = true;
- new->a = *a;
+ new->a = *attrs;
new->shadow_opacity = ps->o.shadow_opacity;
pixman_region32_init(&new->bounding_shape);
- free(a);
-
xcb_generic_error_t *e;
- auto g = xcb_get_geometry_reply(ps->c.c, xcb_get_geometry(ps->c.c, w->id), &e);
+ auto g = xcb_get_geometry_reply(ps->c.c, xcb_get_geometry(ps->c.c, wid), &e);
if (!g) {
- log_error_x_error(e, "Failed to get geometry of window %#010x", w->id);
+ log_debug("Failed to get geometry of window %#010x: %s", wid, x_strerror(e));
free(e);
free(new);
- return w;
+ return NULL;
}
new->pending_g = (struct win_geometry){
.x = g->x,
@@ -1661,13 +1452,13 @@ struct win *attr_ret_nonnull maybe_allocate_managed_win(session_t *ps, struct wi
// Create Damage for window (if not Input Only)
new->damage = x_new_id(&ps->c);
e = xcb_request_check(
- ps->c.c, xcb_damage_create_checked(ps->c.c, new->damage, w->id,
+ ps->c.c, xcb_damage_create_checked(ps->c.c, new->damage, wid,
XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY));
if (e) {
- log_error_x_error(e, "Failed to create damage");
+ log_debug("Failed to create damage for window %#010x: %s", wid, x_strerror(e));
free(e);
free(new);
- return w;
+ return NULL;
}
// Set window event mask
@@ -1676,33 +1467,23 @@ struct win *attr_ret_nonnull maybe_allocate_managed_win(session_t *ps, struct wi
if (!ps->o.use_ewmh_active_win) {
frame_event_mask |= XCB_EVENT_MASK_FOCUS_CHANGE;
}
- xcb_change_window_attributes(ps->c.c, new->base.id, XCB_CW_EVENT_MASK,
- (const uint32_t[]){frame_event_mask});
-
- // Add existing subwins of this window
- auto tree_reply =
- xcb_query_tree_reply(ps->c.c, xcb_query_tree(ps->c.c, new->base.id), NULL);
- if (tree_reply) {
- auto children = xcb_query_tree_children(tree_reply);
- for (int i = 0; i < xcb_query_tree_children_length(tree_reply); i++) {
- wm_subwin_add_and_subscribe(ps->wm, &ps->c, children[i], new->base.id);
- }
- free(tree_reply);
- }
+ x_set_error_action_ignore(
+ &ps->c, xcb_change_window_attributes(ps->c.c, wid, XCB_CW_EVENT_MASK,
+ (const uint32_t[]){frame_event_mask}));
// Get notification when the shape of a window changes
if (ps->shape_exists) {
- xcb_shape_select_input(ps->c.c, new->base.id, 1);
+ x_set_error_action_ignore(&ps->c, xcb_shape_select_input(ps->c.c, wid, 1));
}
new->pictfmt = x_get_pictform_for_visual(&ps->c, new->a.visual);
new->client_pictfmt = NULL;
+ new->tree_ref = cursor;
// Set all the stale flags on this new window, so it's properties will get
// updated when it's mapped
- win_set_flags(new, WIN_FLAGS_CLIENT_STALE | WIN_FLAGS_SIZE_STALE |
- WIN_FLAGS_POSITION_STALE | WIN_FLAGS_PROPERTY_STALE |
- WIN_FLAGS_FACTOR_CHANGED);
+ win_set_flags(new, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE |
+ WIN_FLAGS_PROPERTY_STALE | WIN_FLAGS_FACTOR_CHANGED);
xcb_atom_t init_stale_props[] = {
ps->atoms->a_NET_WM_WINDOW_TYPE, ps->atoms->a_NET_WM_WINDOW_OPACITY,
ps->atoms->a_NET_FRAME_EXTENTS, ps->atoms->aWM_NAME,
@@ -1715,7 +1496,9 @@ struct win *attr_ret_nonnull maybe_allocate_managed_win(session_t *ps, struct wi
c2_window_state_init(ps->c2_state, &new->c2_state);
pixman_region32_init(&new->damaged);
- return &new->base;
+ wm_ref_set(cursor, new);
+
+ return new;
}
/**
@@ -1725,35 +1508,66 @@ static xcb_window_t
win_get_leader_property(struct x_connection *c, struct atom *atoms, xcb_window_t wid,
bool detect_transient, bool detect_client_leader) {
xcb_window_t leader = XCB_NONE;
+ bool exists = false;
// Read the leader properties
if (detect_transient) {
- leader = wid_get_prop_window(c, wid, atoms->aWM_TRANSIENT_FOR);
+ leader = wid_get_prop_window(c, wid, atoms->aWM_TRANSIENT_FOR, &exists);
+ log_debug("Leader via WM_TRANSIENT_FOR of window %#010x: %#010x", wid, leader);
+ if (exists && (leader == c->screen_info->root || leader == XCB_NONE)) {
+ // If WM_TRANSIENT_FOR is set to NONE or the root window, use the
+ // window group leader.
+ //
+ // Ref:
+ // https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html#idm44981516332096
+ auto prop = x_get_prop(c, wid, XCB_ATOM_WM_HINTS, INT_MAX,
+ XCB_ATOM_WM_HINTS, 32);
+ if (prop.nitems >= 9) { // 9-th member is window_group
+ leader = prop.c32[8];
+ log_debug("Leader via WM_HINTS of window %#010x: %#010x",
+ wid, leader);
+ } else {
+ leader = XCB_NONE;
+ }
+ free_winprop(&prop);
+ }
}
if (detect_client_leader && leader == XCB_NONE) {
- leader = wid_get_prop_window(c, wid, atoms->aWM_CLIENT_LEADER);
+ leader = wid_get_prop_window(c, wid, atoms->aWM_CLIENT_LEADER, &exists);
+ log_debug("Leader via WM_CLIENT_LEADER of window %#010x: %#010x", wid, leader);
}
- log_trace("window %#010x: leader %#010x", wid, leader);
+ log_debug("window %#010x: leader %#010x", wid, leader);
return leader;
}
/**
* Internal function of win_get_leader().
*/
-static xcb_window_t win_get_leader_raw(session_t *ps, struct managed_win *w, int recursions) {
+static struct wm_ref *win_get_leader_raw(session_t *ps, struct win *w, int recursions) {
// Rebuild the cache if needed
- if (!w->cache_leader && (w->client_win || w->leader)) {
- // Leader defaults to client window
- if (!(w->cache_leader = w->leader)) {
- w->cache_leader = w->client_win;
+ auto client_win = wm_ref_client_of(w->tree_ref);
+ if (w->cache_leader == NULL && (client_win != NULL || w->leader)) {
+ // Leader defaults to client window, or to the window itself if
+ // it doesn't have a client window
+ w->cache_leader = wm_find(ps->wm, w->leader);
+ if (w->cache_leader == wm_root_ref(ps->wm)) {
+ log_warn("Window manager set the leader of window %#010x to "
+ "root, a broken window manager.",
+ win_id(w));
+ w->cache_leader = NULL;
+ }
+ if (!w->cache_leader) {
+ w->cache_leader = client_win ?: w->tree_ref;
}
// If the leader of this window isn't itself, look for its
// ancestors
- if (w->cache_leader && w->cache_leader != w->client_win) {
- auto wp = wm_find_by_client(ps->wm, w->cache_leader);
+ if (w->cache_leader && w->cache_leader != client_win &&
+ w->cache_leader != w->tree_ref) {
+ auto parent = wm_ref_toplevel_of(ps->wm, w->cache_leader);
+ auto wp = parent ? wm_ref_deref(parent) : NULL;
if (wp) {
// Dead loop?
if (recursions > WIN_GET_LEADER_MAX_RECURSION) {
@@ -1772,14 +1586,10 @@ static xcb_window_t win_get_leader_raw(session_t *ps, struct managed_win *w, int
* Retrieve the WM_CLASS
of a window and update its
* win
structure.
*/
-bool win_update_class(struct x_connection *c, struct atom *atoms, struct managed_win *w) {
+bool win_update_class(struct x_connection *c, struct atom *atoms, struct win *w) {
char **strlst = NULL;
int nstr = 0;
-
- // Can't do anything if there's no client window
- if (!w->client_win) {
- return false;
- }
+ auto client_win = win_client_id(w, /*fallback_to_self=*/true);
// Free and reset old strings
free(w->class_instance);
@@ -1788,7 +1598,7 @@ bool win_update_class(struct x_connection *c, struct atom *atoms, struct managed
w->class_general = NULL;
// Retrieve the property string list
- if (!wid_get_text_prop(c, atoms, w->client_win, atoms->aWM_CLASS, &strlst, &nstr)) {
+ if (!wid_get_text_prop(c, atoms, client_win, atoms->aWM_CLASS, &strlst, &nstr)) {
return false;
}
@@ -1801,9 +1611,8 @@ bool win_update_class(struct x_connection *c, struct atom *atoms, struct managed
free(strlst);
- log_trace("(%#010x): client = %#010x, "
- "instance = \"%s\", general = \"%s\"",
- w->base.id, w->client_win, w->class_instance, w->class_general);
+ log_trace("(%#010x): client = %#010x, instance = \"%s\", general = \"%s\"",
+ win_id(w), client_win, w->class_instance, w->class_general);
return true;
}
@@ -1811,15 +1620,15 @@ bool win_update_class(struct x_connection *c, struct atom *atoms, struct managed
/**
* Handle window focus change.
*/
-static void win_on_focus_change(session_t *ps, struct managed_win *w) {
+static void win_on_focus_change(session_t *ps, struct win *w) {
// If window grouping detection is enabled
if (ps->o.track_leader) {
- xcb_window_t leader = win_get_leader(ps, w);
+ auto leader = win_get_leader(ps, w);
// If the window gets focused, replace the old active_leader
auto active_leader = wm_active_leader(ps->wm);
if (win_is_focused_raw(w) && leader != active_leader) {
- xcb_window_t active_leader_old = active_leader;
+ auto active_leader_old = active_leader;
wm_set_active_leader(ps->wm, leader);
@@ -1840,9 +1649,9 @@ static void win_on_focus_change(session_t *ps, struct managed_win *w) {
// Send D-Bus signal
if (ps->o.dbus) {
if (win_is_focused_raw(w)) {
- cdbus_ev_win_focusin(session_get_cdbus(ps), &w->base);
+ cdbus_ev_win_focusin(session_get_cdbus(ps), w);
} else {
- cdbus_ev_win_focusout(session_get_cdbus(ps), &w->base);
+ cdbus_ev_win_focusout(session_get_cdbus(ps), w);
}
}
}
@@ -1850,24 +1659,24 @@ static void win_on_focus_change(session_t *ps, struct managed_win *w) {
/**
* Set real focused state of a window.
*/
-void win_set_focused(session_t *ps, struct managed_win *w) {
+void win_set_focused(session_t *ps, struct win *w) {
// Unmapped windows will have their focused state reset on map
if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) {
return;
}
auto old_active_win = wm_active_win(ps->wm);
- if (w->is_ewmh_focused) {
+ if (w->is_focused) {
assert(old_active_win == w);
return;
}
wm_set_active_win(ps->wm, w);
- w->is_ewmh_focused = true;
+ w->is_focused = true;
if (old_active_win) {
- assert(old_active_win->is_ewmh_focused);
- old_active_win->is_ewmh_focused = false;
+ assert(old_active_win->is_focused);
+ old_active_win->is_focused = false;
win_on_focus_change(ps, old_active_win);
}
win_on_focus_change(ps, w);
@@ -1879,7 +1688,7 @@ void win_set_focused(session_t *ps, struct managed_win *w) {
* Note w->shadow and shadow geometry must be correct before calling this
* function.
*/
-void win_extents(const struct managed_win *w, region_t *res) {
+void win_extents(const struct win *w, region_t *res) {
pixman_region32_clear(res);
pixman_region32_union_rect(res, res, w->g.x, w->g.y, (uint)w->widthb, (uint)w->heightb);
@@ -1898,8 +1707,8 @@ gen_by_val(win_extents);
*
* Mark the window shape as updated
*/
-void win_update_bounding_shape(struct x_connection *c, struct managed_win *w,
- bool shape_exists, bool detect_rounded_corners) {
+void win_update_bounding_shape(struct x_connection *c, struct win *w, bool shape_exists,
+ bool detect_rounded_corners) {
// We don't handle property updates of non-visible windows until they are
// mapped.
assert(w->state == WSTATE_MAPPED);
@@ -1909,7 +1718,7 @@ void win_update_bounding_shape(struct x_connection *c, struct managed_win *w,
win_get_region_local(w, &w->bounding_shape);
if (shape_exists) {
- w->bounding_shaped = win_bounding_shaped(c, w->base.id);
+ w->bounding_shaped = win_bounding_shaped(c, win_id(w));
}
// Only request for a bounding region if the window is shaped
@@ -1921,7 +1730,7 @@ void win_update_bounding_shape(struct x_connection *c, struct managed_win *w,
*/
xcb_shape_get_rectangles_reply_t *r = xcb_shape_get_rectangles_reply(
- c->c, xcb_shape_get_rectangles(c->c, w->base.id, XCB_SHAPE_SK_BOUNDING),
+ c->c, xcb_shape_get_rectangles(c->c, win_id(w), XCB_SHAPE_SK_BOUNDING),
NULL);
if (!r) {
@@ -1961,33 +1770,37 @@ void win_update_bounding_shape(struct x_connection *c, struct managed_win *w,
/**
* Reread opacity property of a window.
*/
-void win_update_opacity_prop(struct x_connection *c, struct atom *atoms,
- struct managed_win *w, bool detect_client_opacity) {
+void win_update_opacity_prop(struct x_connection *c, struct atom *atoms, struct win *w,
+ bool detect_client_opacity) {
// get frame opacity first
w->has_opacity_prop =
- wid_get_opacity_prop(c, atoms, w->base.id, OPAQUE, &w->opacity_prop);
+ wid_get_opacity_prop(c, atoms, win_id(w), OPAQUE, &w->opacity_prop);
if (w->has_opacity_prop) {
// opacity found
return;
}
- if (detect_client_opacity && w->client_win && w->base.id == w->client_win) {
- // checking client opacity not allowed
+ auto client_win = wm_ref_client_of(w->tree_ref);
+ if (!detect_client_opacity || client_win == NULL) {
return;
}
// get client opacity
- w->has_opacity_prop =
- wid_get_opacity_prop(c, atoms, w->client_win, OPAQUE, &w->opacity_prop);
+ w->has_opacity_prop = wid_get_opacity_prop(c, atoms, wm_ref_win_id(client_win),
+ OPAQUE, &w->opacity_prop);
}
/**
* Retrieve frame extents from a window.
*/
-void win_update_frame_extents(struct x_connection *c, struct atom *atoms,
- struct managed_win *w, xcb_window_t client,
- double frame_opacity) {
+void win_update_frame_extents(struct x_connection *c, struct atom *atoms, struct win *w,
+ xcb_window_t client, double frame_opacity) {
+ if (client == XCB_NONE) {
+ w->frame_extents = (margin_t){0};
+ return;
+ }
+
winprop_t prop =
x_get_prop(c, client, atoms->a_NET_FRAME_EXTENTS, 4L, XCB_ATOM_CARDINAL, 32);
@@ -2021,18 +1834,19 @@ void win_update_frame_extents(struct x_connection *c, struct atom *atoms,
}
}
- log_trace("(%#010x): %d, %d, %d, %d", w->base.id, w->frame_extents.left,
+ log_trace("(%#010x): %d, %d, %d, %d", win_id(w), w->frame_extents.left,
w->frame_extents.right, w->frame_extents.top, w->frame_extents.bottom);
free_winprop(&prop);
}
-bool win_is_region_ignore_valid(session_t *ps, const struct managed_win *w) {
- win_stack_foreach_managed(i, wm_stack_end(ps->wm)) {
+bool win_is_region_ignore_valid(session_t *ps, const struct win *w) {
+ wm_stack_foreach(ps->wm, cursor) {
+ auto i = wm_ref_deref(cursor);
if (i == w) {
break;
}
- if (!i->reg_ignore_valid) {
+ if (i != NULL && !i->reg_ignore_valid) {
return false;
}
}
@@ -2041,131 +1855,90 @@ bool win_is_region_ignore_valid(session_t *ps, const struct managed_win *w) {
/// Finish the destruction of a window (e.g. after fading has finished).
/// Frees `w`
-void destroy_win_finish(session_t *ps, struct win *w) {
- log_debug("Trying to finish destroying (%#010x)", w->id);
-
- auto next_w = wm_stack_next_managed(ps->wm, &w->stack_neighbour);
- list_remove(&w->stack_neighbour);
-
- if (w->managed) {
- auto mw = (struct managed_win *)w;
- unmap_win_finish(ps, mw);
-
- // Unmapping might preserve the shadow image, so free it here
- win_release_shadow(ps->backend_data, mw);
- win_release_mask(ps->backend_data, mw);
-
- // Invalidate reg_ignore of windows below this one
- // TODO(yshui) what if next_w is not mapped??
- /* TODO(yshui) seriously figure out how reg_ignore behaves.
- * I think if `w` is unmapped, and destroyed after
- * paint happened at least once, w->reg_ignore_valid would
- * be true, and there is no need to invalid w->next->reg_ignore
- * when w is destroyed. */
- if (next_w) {
- rc_region_unref(&next_w->reg_ignore);
- next_w->reg_ignore_valid = false;
- }
-
- if (mw == wm_active_win(ps->wm)) {
- // Usually, the window cannot be the focused at
- // destruction. FocusOut should be generated before the
- // window is destroyed. We do this check just to be
- // completely sure we don't have dangling references.
- log_debug("window %#010x (%s) is destroyed while being "
- "focused",
- w->id, mw->name);
- wm_set_active_win(ps->wm, NULL);
- }
-
- free_win_res(ps, mw);
-
- // Drop w from all prev_trans to avoid accessing freed memory in
- // repair_win()
- // TODO(yshui) there can only be one prev_trans pointing to w
- win_stack_foreach_managed(w2, wm_stack_end(ps->wm)) {
- if (mw == w2->prev_trans) {
- w2->prev_trans = NULL;
- }
+void win_destroy_finish(session_t *ps, struct win *w) {
+ log_debug("Trying to finish destroying (%#010x)", win_id(w));
+
+ unmap_win_finish(ps, w);
+
+ // Unmapping might preserve the shadow image, so free it here
+ win_release_shadow(ps->backend_data, w);
+ win_release_mask(ps->backend_data, w);
+
+ if (w == wm_active_win(ps->wm)) {
+ // Usually, the window cannot be the focused at
+ // destruction. FocusOut should be generated before the
+ // window is destroyed. We do this check just to be
+ // completely sure we don't have dangling references.
+ log_debug("window %#010x (%s) is destroyed while being "
+ "focused",
+ win_id(w), w->name);
+ wm_set_active_win(ps->wm, NULL);
+ }
+
+ free_win_res(ps, w);
+
+ // Drop w from all prev_trans to avoid accessing freed memory in
+ // repair_win()
+ // TODO(yshui) there can only be one prev_trans pointing to w
+ wm_stack_foreach(ps->wm, cursor) {
+ auto w2 = wm_ref_deref(cursor);
+ if (w2 != NULL && w == w2->prev_trans) {
+ w2->prev_trans = NULL;
}
}
+ wm_reap_zombie(w->tree_ref);
free(w);
}
/// Start destroying a window. Windows cannot always be destroyed immediately
/// because of fading and such.
-void destroy_win_start(session_t *ps, struct win *w) {
- assert(w);
-
- // A toplevel window is destroyed, all of its children lose their
- // subwin status.
- wm_subwin_remove_and_unsubscribe_for_toplevel(ps->wm, &ps->c, w->id);
-
- auto mw = (struct managed_win *)w;
- log_debug("Destroying %#010x \"%s\", managed = %d", w->id,
- (w->managed ? mw->name : NULL), w->managed);
-
- // Delete destroyed window from the hash table, even though the window
- // might still be rendered for a while. We need to make sure future window
- // with the same window id won't confuse us. Keep the window in the window
- // stack if it's managed and mapped, since we might still need to render
- // it (e.g. fading out). Window will be removed from the stack when it
- // finishes destroying.
- wm_remove(ps->wm, w);
-
- if (w->managed) {
- if (mw->state != WSTATE_UNMAPPED) {
- // Only UNMAPPED state has window resources freed,
- // otherwise we need to call unmap_win_finish to free
- // them.
- log_warn("Did X server not unmap window %#010x before destroying "
- "it?",
- w->id);
- }
- // Clear IMAGES_STALE flags since the window is destroyed: Clear
- // PIXMAP_STALE as there is no pixmap available anymore, so STALE
- // doesn't make sense.
- // XXX Clear SHADOW_STALE as setting/clearing flags on a destroyed
- // window doesn't work leading to an inconsistent state where the
- // shadow is refreshed but the flags are stuck in STALE. Do this
- // before changing the window state to destroying
- win_clear_flags(mw, WIN_FLAGS_PIXMAP_STALE);
-
- // If size/shape/position information is stale,
- // win_process_update_flags will update them and add the new
- // window extents to damage. Since the window has been destroyed,
- // we cannot get the complete information at this point, so we
- // just add what we currently have to the damage.
- if (win_check_flags_any(mw, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE)) {
- add_damage_from_win(ps, mw);
- }
-
- if (win_check_flags_all(mw, WIN_FLAGS_CLIENT_STALE)) {
- mw->client_win = mw->base.id;
- log_debug("(%#010x): client self (%s)", mw->base.id,
- (mw->a.override_redirect ? "override-redirected" : "wmwin"));
- }
+void win_destroy_start(session_t *ps, struct win *w) {
+ BUG_ON(w == NULL);
+ log_debug("Destroying %#010x (%s)", win_id(w), w->name);
+
+ if (w->state != WSTATE_UNMAPPED) {
+ // Only UNMAPPED state has window resources freed,
+ // otherwise we need to call unmap_win_finish to free
+ // them.
+ log_warn("Did X server not unmap window %#010x before destroying "
+ "it?",
+ win_id(w));
+ }
+ // Clear IMAGES_STALE flags since the window is destroyed: Clear
+ // PIXMAP_STALE as there is no pixmap available anymore, so STALE
+ // doesn't make sense.
+ // XXX Clear SHADOW_STALE as setting/clearing flags on a destroyed
+ // window doesn't work leading to an inconsistent state where the
+ // shadow is refreshed but the flags are stuck in STALE. Do this
+ // before changing the window state to destroying
+ win_clear_flags(w, WIN_FLAGS_PIXMAP_STALE);
+
+ // If size/shape/position information is stale,
+ // win_process_update_flags will update them and add the new
+ // window extents to damage. Since the window has been destroyed,
+ // we cannot get the complete information at this point, so we
+ // just add what we currently have to the damage.
+ if (win_check_flags_any(w, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE)) {
+ add_damage_from_win(ps, w);
+ }
- // Clear some flags about stale window information. Because now
- // the window is destroyed, we can't update them anyway.
- win_clear_flags(mw, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE |
- WIN_FLAGS_PROPERTY_STALE |
- WIN_FLAGS_FACTOR_CHANGED | WIN_FLAGS_CLIENT_STALE);
+ // Clear some flags about stale window information. Because now
+ // the window is destroyed, we can't update them anyway.
+ win_clear_flags(w, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE |
+ WIN_FLAGS_PROPERTY_STALE | WIN_FLAGS_FACTOR_CHANGED);
- // Update state flags of a managed window
- mw->state = WSTATE_DESTROYED;
- mw->a.map_state = XCB_MAP_STATE_UNMAPPED;
- mw->in_openclose = true;
- }
+ // Update state flags of a managed window
+ w->state = WSTATE_DESTROYED;
+ w->a.map_state = XCB_MAP_STATE_UNMAPPED;
+ w->in_openclose = true;
}
-void unmap_win_start(struct managed_win *w) {
+void unmap_win_start(struct win *w) {
assert(w);
- assert(w->base.managed);
assert(w->a._class != XCB_WINDOW_CLASS_INPUT_ONLY);
- log_debug("Unmapping %#010x \"%s\"", w->base.id, w->name);
+ log_debug("Unmapping %#010x (%s)", win_id(w), w->name);
assert(w->state != WSTATE_DESTROYED);
@@ -2184,11 +1957,11 @@ void unmap_win_start(struct managed_win *w) {
w->state = WSTATE_UNMAPPED;
}
-struct win_script_context
-win_script_context_prepare(struct session *ps, struct managed_win *w) {
+struct win_script_context win_script_context_prepare(struct session *ps, struct win *w) {
+ auto monitor_index = win_find_monitor(&ps->monitors, w);
auto monitor =
- (w->randr_monitor >= 0 && w->randr_monitor < ps->monitors.count)
- ? *pixman_region32_extents(&ps->monitors.regions[w->randr_monitor])
+ monitor_index >= 0
+ ? *pixman_region32_extents(&ps->monitors.regions[monitor_index])
: (pixman_box32_t){
.x1 = 0, .y1 = 0, .x2 = ps->root_width, .y2 = ps->root_height};
struct win_script_context ret = {
@@ -2206,7 +1979,7 @@ win_script_context_prepare(struct session *ps, struct managed_win *w) {
return ret;
}
-double win_animatable_get(const struct managed_win *w, enum win_script_output output) {
+double win_animatable_get(const struct win *w, enum win_script_output output) {
if (w->running_animation && w->running_animation_outputs[output] >= 0) {
return w->running_animation->memory[w->running_animation_outputs[output]];
}
@@ -2232,13 +2005,19 @@ double win_animatable_get(const struct managed_win *w, enum win_script_output ou
#define WSTATE_PAIR(a, b) ((int)(a) * NUM_OF_WSTATES + (int)(b))
-bool win_process_animation_and_state_change(struct session *ps, struct managed_win *w,
- double delta_t) {
+bool win_process_animation_and_state_change(struct session *ps, struct win *w, double delta_t) {
// If the window hasn't ever been damaged yet, it won't be rendered in this frame.
- // And if it is also unmapped/destroyed, it won't receive damage events. In this
- // case we can skip its animation. For mapped windows, we need to provisionally
- // start animation, because its first damage event might come a bit late.
- bool will_never_render = !w->ever_damaged && w->state != WSTATE_MAPPED;
+ // Or if it doesn't have a image bound, it won't be rendered either. (This can
+ // happen is a window is destroyed during a backend reset. Backend resets releases
+ // all images, and if a window is freed during that, its image cannot be
+ // reacquired.)
+ //
+ // If the window won't be rendered, and it is also unmapped/destroyed, it won't
+ // receive damage events or reacquire an image. In this case we can skip its
+ // animation. For mapped windows, we need to provisionally start animation,
+ // because its first damage event might come a bit late.
+ bool will_never_render =
+ (!w->ever_damaged || w->win_image == NULL) && w->state != WSTATE_MAPPED;
if (!ps->redirected || will_never_render) {
// This window won't be rendered, so we don't need to run the animations.
bool state_changed = w->previous.state != w->state;
@@ -2255,7 +2034,7 @@ bool win_process_animation_and_state_change(struct session *ps, struct managed_w
if (w->running_animation == NULL) {
return false;
}
- log_verbose("Advance animation for %#010x (%s) %f seconds", w->base.id,
+ log_verbose("Advance animation for %#010x (%s) %f seconds", win_id(w),
w->name, delta_t);
if (!script_instance_is_finished(w->running_animation)) {
w->running_animation->elapsed += delta_t;
@@ -2290,13 +2069,13 @@ bool win_process_animation_and_state_change(struct session *ps, struct managed_w
if (ps->o.dbus) {
switch (w->state) {
case WSTATE_UNMAPPED:
- cdbus_ev_win_unmapped(session_get_cdbus(ps), &w->base);
+ cdbus_ev_win_unmapped(session_get_cdbus(ps), w);
break;
case WSTATE_MAPPED:
- cdbus_ev_win_mapped(session_get_cdbus(ps), &w->base);
+ cdbus_ev_win_mapped(session_get_cdbus(ps), w);
break;
case WSTATE_DESTROYED:
- cdbus_ev_win_destroyed(session_get_cdbus(ps), &w->base);
+ cdbus_ev_win_destroyed(session_get_cdbus(ps), w);
break;
}
}
@@ -2353,7 +2132,7 @@ bool win_process_animation_and_state_change(struct session *ps, struct managed_w
(w->running_animation_suppressions & (1 << trigger)) != 0) {
log_debug("Not starting animation %s for window %#010x (%s) because it "
"is being suppressed.",
- animation_trigger_names[trigger], w->base.id, w->name);
+ animation_trigger_names[trigger], win_id(w), w->name);
goto advance_animation;
}
@@ -2362,7 +2141,7 @@ bool win_process_animation_and_state_change(struct session *ps, struct managed_w
}
log_debug("Starting animation %s for window %#010x (%s)",
- animation_trigger_names[trigger], w->base.id, w->name);
+ animation_trigger_names[trigger], win_id(w), w->name);
auto new_animation = script_instance_new(ps->o.animations[trigger].script);
if (w->running_animation) {
@@ -2378,29 +2157,28 @@ bool win_process_animation_and_state_change(struct session *ps, struct managed_w
#undef WSTATE_PAIR
-// TODO(absolutelynothelix): rename to x_update_win_(randr_?)monitor and move to
-// the x.c.
-void win_update_monitor(struct x_monitors *monitors, struct managed_win *mw) {
- mw->randr_monitor = -1;
+/// Find which monitor a window is on.
+int win_find_monitor(const struct x_monitors *monitors, const struct win *mw) {
+ int ret = -1;
for (int i = 0; i < monitors->count; i++) {
auto e = pixman_region32_extents(&monitors->regions[i]);
if (e->x1 <= mw->g.x && e->y1 <= mw->g.y &&
e->x2 >= mw->g.x + mw->widthb && e->y2 >= mw->g.y + mw->heightb) {
- mw->randr_monitor = i;
log_debug("Window %#010x (%s), %dx%d+%dx%d, is entirely on the "
"monitor %d (%dx%d+%dx%d)",
- mw->base.id, mw->name, mw->g.x, mw->g.y, mw->widthb,
+ win_id(mw), mw->name, mw->g.x, mw->g.y, mw->widthb,
mw->heightb, i, e->x1, e->y1, e->x2 - e->x1, e->y2 - e->y1);
- return;
+ return i;
}
}
log_debug("Window %#010x (%s), %dx%d+%dx%d, is not entirely on any monitor",
- mw->base.id, mw->name, mw->g.x, mw->g.y, mw->widthb, mw->heightb);
+ win_id(mw), mw->name, mw->g.x, mw->g.y, mw->widthb, mw->heightb);
+ return ret;
}
/// Start the mapping of a window. We cannot map immediately since we might need to fade
/// the window in.
-static void map_win_start(struct managed_win *w) {
+void win_map_start(struct win *w) {
assert(w);
// Don't care about window mapping if it's an InputOnly window
@@ -2409,7 +2187,7 @@ static void map_win_start(struct managed_win *w) {
return;
}
- log_debug("Mapping (%#010x \"%s\"), old state %d", w->base.id, w->name, w->state);
+ log_debug("Mapping (%#010x \"%s\"), old state %d", win_id(w), w->name, w->state);
assert(w->state != WSTATE_DESTROYED);
if (w->state == WSTATE_MAPPED) {
@@ -2431,17 +2209,15 @@ static void map_win_start(struct managed_win *w) {
// Update window mode here to check for ARGB windows
w->mode = win_calc_mode(w);
- log_debug("Window (%#010x) has type %s", w->base.id, WINTYPES[w->window_type].name);
-
w->state = WSTATE_MAPPED;
- win_set_flags(w, WIN_FLAGS_PIXMAP_STALE);
+ win_set_flags(w, WIN_FLAGS_PIXMAP_STALE | WIN_FLAGS_CLIENT_STALE);
}
/// Set flags on a window. Some sanity checks are performed
-void win_set_flags(struct managed_win *w, uint64_t flags) {
- log_verbose("Set flags %" PRIu64 " to window %#010x (%s)", flags, w->base.id, w->name);
+void win_set_flags(struct win *w, uint64_t flags) {
+ log_verbose("Set flags %" PRIu64 " to window %#010x (%s)", flags, win_id(w), w->name);
if (unlikely(w->state == WSTATE_DESTROYED)) {
- log_error("Flags set on a destroyed window %#010x (%s)", w->base.id, w->name);
+ log_error("Flags set on a destroyed window %#010x (%s)", win_id(w), w->name);
return;
}
@@ -2449,19 +2225,19 @@ void win_set_flags(struct managed_win *w, uint64_t flags) {
}
/// Clear flags on a window. Some sanity checks are performed
-void win_clear_flags(struct managed_win *w, uint64_t flags) {
- log_verbose("Clear flags %" PRIu64 " from window %#010x (%s)", flags, w->base.id,
+void win_clear_flags(struct win *w, uint64_t flags) {
+ log_verbose("Clear flags %" PRIu64 " from window %#010x (%s)", flags, win_id(w),
w->name);
if (unlikely(w->state == WSTATE_DESTROYED)) {
log_warn("Flags %" PRIu64 " cleared on a destroyed window %#010x (%s)",
- flags, w->base.id, w->name);
+ flags, win_id(w), w->name);
return;
}
w->flags = w->flags & (~flags);
}
-void win_set_properties_stale(struct managed_win *w, const xcb_atom_t *props, int nprops) {
+void win_set_properties_stale(struct win *w, const xcb_atom_t *props, int nprops) {
auto const bits_per_element = sizeof(*w->stale_props) * 8;
size_t new_capacity = w->stale_props_capacity;
@@ -2491,12 +2267,12 @@ void win_set_properties_stale(struct managed_win *w, const xcb_atom_t *props, in
win_set_flags(w, WIN_FLAGS_PROPERTY_STALE);
}
-static void win_clear_all_properties_stale(struct managed_win *w) {
+static void win_clear_all_properties_stale(struct win *w) {
memset(w->stale_props, 0, w->stale_props_capacity * sizeof(*w->stale_props));
win_clear_flags(w, WIN_FLAGS_PROPERTY_STALE);
}
-static bool win_fetch_and_unset_property_stale(struct managed_win *w, xcb_atom_t prop) {
+static bool win_fetch_and_unset_property_stale(struct win *w, xcb_atom_t prop) {
auto const bits_per_element = sizeof(*w->stale_props) * 8;
if (prop >= w->stale_props_capacity * bits_per_element) {
return false;
@@ -2508,11 +2284,11 @@ static bool win_fetch_and_unset_property_stale(struct managed_win *w, xcb_atom_t
return ret;
}
-bool win_check_flags_any(struct managed_win *w, uint64_t flags) {
+bool win_check_flags_any(struct win *w, uint64_t flags) {
return (w->flags & flags) != 0;
}
-bool win_check_flags_all(struct managed_win *w, uint64_t flags) {
+bool win_check_flags_all(struct win *w, uint64_t flags) {
return (w->flags & flags) == flags;
}
@@ -2521,7 +2297,7 @@ bool win_check_flags_all(struct managed_win *w, uint64_t flags) {
*
* It's not using w->border_size for performance measures.
*/
-void win_update_is_fullscreen(const session_t *ps, struct managed_win *w) {
+void win_update_is_fullscreen(const session_t *ps, struct win *w) {
if (!ps->o.no_ewmh_fullscreen && w->is_ewmh_fullscreen) {
w->is_fullscreen = true;
return;
@@ -2537,11 +2313,12 @@ void win_update_is_fullscreen(const session_t *ps, struct managed_win *w) {
*
* TODO(yshui) cache this property
*/
-bool win_is_bypassing_compositor(const session_t *ps, const struct managed_win *w) {
+bool win_is_bypassing_compositor(const session_t *ps, const struct win *w) {
bool ret = false;
+ auto wid = win_client_id(w, /*fallback_to_self=*/true);
- auto prop = x_get_prop(&ps->c, w->client_win, ps->atoms->a_NET_WM_BYPASS_COMPOSITOR,
- 1L, XCB_ATOM_CARDINAL, 32);
+ auto prop = x_get_prop(&ps->c, wid, ps->atoms->a_NET_WM_BYPASS_COMPOSITOR, 1L,
+ XCB_ATOM_CARDINAL, 32);
if (prop.nitems && *prop.c32 == 1) {
ret = true;
@@ -2555,6 +2332,6 @@ bool win_is_bypassing_compositor(const session_t *ps, const struct managed_win *
* Check if a window is focused, without using any focus rules or forced focus
* settings
*/
-bool win_is_focused_raw(const struct managed_win *w) {
- return w->a.map_state == XCB_MAP_STATE_VIEWABLE && w->is_ewmh_focused;
+bool win_is_focused_raw(const struct win *w) {
+ return w->a.map_state == XCB_MAP_STATE_VIEWABLE && w->is_focused;
}
diff --git a/src/wm/win.h b/src/wm/win.h
index f96560014e..3a9cbbd5fd 100644
--- a/src/wm/win.h
+++ b/src/wm/win.h
@@ -20,20 +20,26 @@
#include "transition/script.h"
#include "utils/list.h"
#include "utils/misc.h"
+#include "wm/wm.h"
#include "x.h"
#include "xcb/xproto.h"
struct backend_base;
typedef struct session session_t;
typedef struct _glx_texture glx_texture_t;
+struct wm_cursor;
-#define win_stack_foreach_managed(w, win_stack) \
- list_foreach(struct managed_win, w, win_stack, \
- base.stack_neighbour) if ((w)->base.managed)
+#define wm_stack_foreach(wm, i) \
+ for (struct wm_ref * (i) = wm_ref_topmost_child(wm_root_ref(wm)); (i); \
+ (i) = wm_ref_below(i))
+#define wm_stack_foreach_rev(wm, i) \
+ for (struct wm_ref * (i) = wm_ref_bottommost_child(wm_root_ref(wm)); (i); \
+ (i) = wm_ref_above(i))
-#define win_stack_foreach_managed_safe(w, win_stack) \
- list_foreach_safe(struct managed_win, w, win_stack, \
- base.stack_neighbour) if ((w)->base.managed)
+#define wm_stack_foreach_safe(wm, i, next_i) \
+ for (struct wm_ref * (i) = wm_ref_topmost_child(wm_root_ref(wm)), \
+ *(next_i) = (i) != NULL ? wm_ref_below(i) : NULL; \
+ (i); (i) = (next_i), (next_i) = (i) != NULL ? wm_ref_below(i) : NULL)
#ifdef CONFIG_OPENGL
// FIXME this type should be in opengl.h
@@ -73,25 +79,6 @@ struct window_stack_entry {
* considered part of the window.
*/
-/// Structure representing a top-level managed window.
-struct win {
- UT_hash_handle hh;
- struct list_node stack_neighbour;
- /// ID of the top-level frame window.
- xcb_window_t id;
- /// Generation of the window.
- /// (see `struct wm::generation` for explanation of what a generation is)
- uint64_t generation;
- /// Whether the window is destroyed from Xorg's perspective
- bool destroyed : 1;
- /// True if we just received CreateNotify, and haven't queried X for any info
- /// about the window
- bool is_new : 1;
- /// True if this window is managed, i.e. this struct is actually a `managed_win`.
- /// Always false if `is_new` is true.
- bool managed : 1;
-};
-
struct win_geometry {
int16_t x;
int16_t y;
@@ -106,22 +93,20 @@ struct win_state_change {
winstate_t state;
};
-struct managed_win {
- struct win base;
+struct win {
+ /// Reference back to the position of this window inside the window tree.
+ struct wm_ref *tree_ref;
/// backend data attached to this window. Only available when
/// `state` is not UNMAPPED
image_handle win_image;
image_handle shadow_image;
image_handle mask_image;
+ // TODO(yshui) only used by legacy backends, remove.
/// Pointer to the next higher window to paint.
- struct managed_win *prev_trans;
- /// Number of windows above this window
- int stacking_rank;
+ struct win *prev_trans;
// TODO(yshui) rethink reg_ignore
// Core members
- /// The "mapped state" of this window, doesn't necessary
- /// match X mapped state, because of fading.
winstate_t state;
/// Window attributes.
xcb_get_window_attributes_reply_t a;
@@ -129,8 +114,6 @@ struct managed_win {
struct win_geometry g;
/// Updated geometry received in events
struct win_geometry pending_g;
- /// X RandR monitor this window is on.
- int randr_monitor;
/// Window visual pict format
const xcb_render_pictforminfo_t *pictfmt;
/// Client window visual pict format
@@ -184,14 +167,12 @@ struct managed_win {
bool in_openclose;
// Client window related members
- /// ID of the top-level client window of the window.
- xcb_window_t client_win;
/// Type of the window.
wintype_t window_type;
/// Leader window ID of the window.
xcb_window_t leader;
- /// Cached topmost window ID of the window.
- xcb_window_t cache_leader;
+ /// Cached topmost window ID of the leader window.
+ struct wm_ref *cache_leader;
// Focus-related members
/// Whether the window is to be considered focused.
@@ -214,8 +195,8 @@ struct managed_win {
/// `is_ewmh_fullscreen`, or the windows spatial relation with the
/// root window. Which one is used is determined by user configuration.
bool is_fullscreen;
- /// Whether the window is the EWMH active window.
- bool is_ewmh_focused;
+ /// Whether the window is the active window.
+ bool is_focused;
// Opacity-related members
/// Window opacity
@@ -363,47 +344,46 @@ static const struct script_output_info win_script_outputs[] = {
/// section. Returns true if the window had an animation running and it has just finished,
/// or if the window's states just changed and there is no animation defined for this
/// state change.
-bool win_process_animation_and_state_change(struct session *ps, struct managed_win *w,
- double delta_t);
-double win_animatable_get(const struct managed_win *w, enum win_script_output output);
-void win_process_update_flags(session_t *ps, struct managed_win *w);
-void win_process_image_flags(session_t *ps, struct managed_win *w);
+bool win_process_animation_and_state_change(struct session *ps, struct win *w, double delta_t);
+double win_animatable_get(const struct win *w, enum win_script_output output);
+void win_process_update_flags(session_t *ps, struct win *w);
+void win_process_image_flags(session_t *ps, struct win *w);
/// Start the unmap of a window. We cannot unmap immediately since we might need to fade
/// the window out.
-void unmap_win_start(struct managed_win *);
-void unmap_win_finish(session_t *ps, struct managed_win *w);
+void unmap_win_start(struct win *);
+void unmap_win_finish(session_t *ps, struct win *w);
/// Start the destroying of a window. Windows cannot always be destroyed immediately
/// because of fading and such.
-void destroy_win_start(session_t *ps, struct win *w);
-
+void win_destroy_start(session_t *ps, struct win *w);
+void win_map_start(struct win *w);
/// Release images bound with a window, set the *_NONE flags on the window. Only to be
/// used when de-initializing the backend outside of win.c
-void win_release_images(struct backend_base *base, struct managed_win *w);
-winmode_t attr_pure win_calc_mode_raw(const struct managed_win *w);
+void win_release_images(struct backend_base *backend, struct win *w);
+winmode_t attr_pure win_calc_mode_raw(const struct win *w);
// TODO(yshui) `win_calc_mode` is only used by legacy backends
-winmode_t attr_pure win_calc_mode(const struct managed_win *w);
-void win_set_shadow_force(session_t *ps, struct managed_win *w, switch_t val);
-void win_set_fade_force(struct managed_win *w, switch_t val);
-void win_set_focused_force(session_t *ps, struct managed_win *w, switch_t val);
-void win_set_invert_color_force(session_t *ps, struct managed_win *w, switch_t val);
+winmode_t attr_pure win_calc_mode(const struct win *w);
+void win_set_shadow_force(session_t *ps, struct win *w, switch_t val);
+void win_set_fade_force(struct win *w, switch_t val);
+void win_set_focused_force(session_t *ps, struct win *w, switch_t val);
+void win_set_invert_color_force(session_t *ps, struct win *w, switch_t val);
/**
* Set real focused state of a window.
*/
-void win_set_focused(session_t *ps, struct managed_win *w);
-void win_on_factor_change(session_t *ps, struct managed_win *w);
-void win_unmark_client(struct managed_win *w);
+void win_set_focused(session_t *ps, struct win *w);
+void win_on_factor_change(session_t *ps, struct win *w);
+void win_on_client_update(session_t *ps, struct win *w);
-bool attr_pure win_should_dim(session_t *ps, const struct managed_win *w);
+bool attr_pure win_should_dim(session_t *ps, const struct win *w);
-void win_update_monitor(struct x_monitors *monitors, struct managed_win *mw);
+int attr_pure win_find_monitor(const struct x_monitors *monitors, const struct win *mw);
/// Recheck if a window is fullscreen
-void win_update_is_fullscreen(const session_t *ps, struct managed_win *w);
+void win_update_is_fullscreen(const session_t *ps, struct win *w);
/**
* Check if a window has BYPASS_COMPOSITOR property set
*/
-bool win_is_bypassing_compositor(const session_t *ps, const struct managed_win *w);
+bool win_is_bypassing_compositor(const session_t *ps, const struct win *w);
/**
* Get a rectangular region in global coordinates a window (and possibly
* its shadow) occupies.
@@ -411,114 +391,113 @@ bool win_is_bypassing_compositor(const session_t *ps, const struct managed_win *
* Note w->shadow and shadow geometry must be correct before calling this
* function.
*/
-void win_extents(const struct managed_win *w, region_t *res);
-region_t win_extents_by_val(const struct managed_win *w);
+void win_extents(const struct win *w, region_t *res);
+region_t win_extents_by_val(const struct win *w);
/**
* Add a window to damaged area.
*
* @param ps current session
* @param w struct _win element representing the window
*/
-void add_damage_from_win(session_t *ps, const struct managed_win *w);
+void add_damage_from_win(session_t *ps, const struct win *w);
/**
* Get a rectangular region a window occupies, excluding frame and shadow.
*
* Return region in global coordinates.
*/
-void win_get_region_noframe_local(const struct managed_win *w, region_t *);
-void win_get_region_noframe_local_without_corners(const struct managed_win *w, region_t *);
+void win_get_region_noframe_local(const struct win *w, region_t *);
+void win_get_region_noframe_local_without_corners(const struct win *w, region_t *);
/// Get the region for the frame of the window
-void win_get_region_frame_local(const struct managed_win *w, region_t *res);
+void win_get_region_frame_local(const struct win *w, region_t *res);
/// Get the region for the frame of the window, by value
-region_t win_get_region_frame_local_by_val(const struct managed_win *w);
+region_t win_get_region_frame_local_by_val(const struct win *w);
/// Query the Xorg for information about window `win`
/// `win` pointer might become invalid after this function returns
-struct win *attr_ret_nonnull maybe_allocate_managed_win(session_t *ps, struct win *win);
+struct win *win_maybe_allocate(session_t *ps, struct wm_ref *cursor,
+ xcb_get_window_attributes_reply_t *attrs);
/**
* Release a destroyed window that is no longer needed.
*/
-void destroy_win_finish(session_t *ps, struct win *w);
+void win_destroy_finish(session_t *ps, struct win *w);
/**
* Check if a window is focused, without using any focus rules or forced focus settings
*/
-bool attr_pure win_is_focused_raw(const struct managed_win *w);
+bool attr_pure win_is_focused_raw(const struct win *w);
/// check if window has ARGB visual
-bool attr_pure win_has_alpha(const struct managed_win *w);
+bool attr_pure win_has_alpha(const struct win *w);
/// Whether it looks like a WM window. We consider a window WM window if
/// it does not have a decedent with WM_STATE and it is not override-
/// redirected itself.
-static inline bool attr_pure win_is_wmwin(const struct managed_win *w) {
- return w->base.id == w->client_win && !w->a.override_redirect;
+static inline bool attr_pure win_is_wmwin(const struct win *w) {
+ return wm_ref_client_of(w->tree_ref) == NULL && !w->a.override_redirect;
}
-static inline struct managed_win *win_as_managed(struct win *w) {
- BUG_ON(!w->managed);
- return (struct managed_win *)w;
+static inline xcb_window_t win_id(const struct win *w) {
+ return wm_ref_win_id(w->tree_ref);
}
-static inline const char *win_get_name_if_managed(const struct win *w) {
- if (!w->managed) {
- return "(unmanaged)";
+/// Returns the client window of a window. If a client window does not exist, returns the
+/// window itself when `fallback_to_self` is true, otherwise returns XCB_NONE.
+static inline xcb_window_t win_client_id(const struct win *w, bool fallback_to_self) {
+ auto client_win = wm_ref_client_of(w->tree_ref);
+ if (client_win == NULL) {
+ return fallback_to_self ? win_id(w) : XCB_NONE;
}
- auto mw = (struct managed_win *)w;
- return mw->name;
+ return wm_ref_win_id(client_win);
}
/// check if reg_ignore_valid is true for all windows above us
-bool attr_pure win_is_region_ignore_valid(session_t *ps, const struct managed_win *w);
+bool attr_pure win_is_region_ignore_valid(session_t *ps, const struct win *w);
/// Whether a given window is mapped on the X server side
-bool win_is_mapped_in_x(const struct managed_win *w);
+bool win_is_mapped_in_x(const struct win *w);
/// Set flags on a window. Some sanity checks are performed
-void win_set_flags(struct managed_win *w, uint64_t flags);
+void win_set_flags(struct win *w, uint64_t flags);
/// Clear flags on a window. Some sanity checks are performed
-void win_clear_flags(struct managed_win *w, uint64_t flags);
+void win_clear_flags(struct win *w, uint64_t flags);
/// Returns true if any of the flags in `flags` is set
-bool win_check_flags_any(struct managed_win *w, uint64_t flags);
+bool win_check_flags_any(struct win *w, uint64_t flags);
/// Returns true if all of the flags in `flags` are set
-bool win_check_flags_all(struct managed_win *w, uint64_t flags);
+bool win_check_flags_all(struct win *w, uint64_t flags);
/// Mark properties as stale for a window
-void win_set_properties_stale(struct managed_win *w, const xcb_atom_t *prop, int nprops);
+void win_set_properties_stale(struct win *w, const xcb_atom_t *prop, int nprops);
-xcb_window_t win_get_client_window(struct x_connection *c, struct wm *wm,
- struct atom *atoms, const struct managed_win *w);
-bool win_update_wintype(struct x_connection *c, struct atom *atoms, struct managed_win *w);
+bool win_update_wintype(struct x_connection *c, struct atom *atoms, struct win *w);
/**
* Retrieve frame extents from a window.
*/
-void win_update_frame_extents(struct x_connection *c, struct atom *atoms,
- struct managed_win *w, xcb_window_t client,
- double frame_opacity);
+void win_update_frame_extents(struct x_connection *c, struct atom *atoms, struct win *w,
+ xcb_window_t client, double frame_opacity);
/**
* Retrieve the WM_CLASS
of a window and update its
* win
structure.
*/
-bool win_update_class(struct x_connection *c, struct atom *atoms, struct managed_win *w);
-int win_update_role(struct x_connection *c, struct atom *atoms, struct managed_win *w);
-int win_update_name(struct x_connection *c, struct atom *atoms, struct managed_win *w);
-void win_on_win_size_change(struct managed_win *w, int shadow_offset_x,
- int shadow_offset_y, int shadow_radius);
-void win_update_bounding_shape(struct x_connection *c, struct managed_win *w,
- bool shape_exists, bool detect_rounded_corners);
+bool win_update_class(struct x_connection *c, struct atom *atoms, struct win *w);
+int win_update_role(struct x_connection *c, struct atom *atoms, struct win *w);
+int win_update_name(struct x_connection *c, struct atom *atoms, struct win *w);
+void win_on_win_size_change(struct win *w, int shadow_offset_x, int shadow_offset_y,
+ int shadow_radius);
+void win_update_bounding_shape(struct x_connection *c, struct win *w, bool shape_exists,
+ bool detect_rounded_corners);
bool win_update_prop_fullscreen(struct x_connection *c, const struct atom *atoms,
- struct managed_win *w);
+ struct win *w);
-static inline attr_unused void win_set_property_stale(struct managed_win *w, xcb_atom_t prop) {
+static inline attr_unused void win_set_property_stale(struct win *w, xcb_atom_t prop) {
return win_set_properties_stale(w, (xcb_atom_t[]){prop}, 1);
}
/// Free all resources in a struct win
-void free_win_res(session_t *ps, struct managed_win *w);
+void free_win_res(session_t *ps, struct win *w);
/// Remove the corners of window `w` from region `res`. `origin` is the top-left corner of
/// `w` in `res`'s coordinate system.
static inline void
-win_region_remove_corners(const struct managed_win *w, ivec2 origin, region_t *res) {
+win_region_remove_corners(const struct win *w, ivec2 origin, region_t *res) {
static const int corner_index[][2] = {
{0, 0},
{0, 1},
@@ -541,12 +520,11 @@ win_region_remove_corners(const struct managed_win *w, ivec2 origin, region_t *r
}
/// Like `win_region_remove_corners`, but `origin` is (0, 0).
-static inline void
-win_region_remove_corners_local(const struct managed_win *w, region_t *res) {
+static inline void win_region_remove_corners_local(const struct win *w, region_t *res) {
win_region_remove_corners(w, (ivec2){0, 0}, res);
}
-static inline region_t attr_unused win_get_bounding_shape_global_by_val(struct managed_win *w) {
+static inline region_t attr_unused win_get_bounding_shape_global_by_val(struct win *w) {
region_t ret;
pixman_region32_init(&ret);
pixman_region32_copy(&ret, &w->bounding_shape);
@@ -554,8 +532,7 @@ static inline region_t attr_unused win_get_bounding_shape_global_by_val(struct m
return ret;
}
-static inline region_t
-win_get_bounding_shape_global_without_corners_by_val(struct managed_win *w) {
+static inline region_t win_get_bounding_shape_global_without_corners_by_val(struct win *w) {
region_t ret;
pixman_region32_init(&ret);
pixman_region32_copy(&ret, &w->bounding_shape);
@@ -568,7 +545,7 @@ win_get_bounding_shape_global_without_corners_by_val(struct managed_win *w) {
* Calculate the extents of the frame of the given window based on EWMH
* _NET_FRAME_EXTENTS and the X window border width.
*/
-static inline margin_t attr_pure attr_unused win_calc_frame_extents(const struct managed_win *w) {
+static inline margin_t attr_pure attr_unused win_calc_frame_extents(const struct win *w) {
margin_t result = w->frame_extents;
result.top = max2(result.top, w->g.border_width);
result.left = max2(result.left, w->g.border_width);
@@ -580,7 +557,7 @@ static inline margin_t attr_pure attr_unused win_calc_frame_extents(const struct
/**
* Check whether a window has WM frames.
*/
-static inline bool attr_pure attr_unused win_has_frame(const struct managed_win *w) {
+static inline bool attr_pure attr_unused win_has_frame(const struct win *w) {
return w->g.border_width || w->frame_extents.top || w->frame_extents.left ||
w->frame_extents.right || w->frame_extents.bottom;
}
diff --git a/src/wm/wm.c b/src/wm/wm.c
index 935cb6ee3a..1403eb8385 100644
--- a/src/wm/wm.c
+++ b/src/wm/wm.c
@@ -1,366 +1,585 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui
+#include
#include
#include
+#include "common.h"
#include "log.h"
+#include "utils/dynarr.h"
#include "utils/list.h"
-#include "utils/uthash_extra.h"
#include "x.h"
#include "win.h"
#include "wm.h"
+#include "wm_internal.h"
+
+struct wm_query_tree_request {
+ struct x_async_request_base base;
+ struct wm_tree_node *node;
+ struct wm *wm;
+ struct atom *atoms;
+ size_t pending_index;
+};
+
+struct wm_get_property_request {
+ struct x_async_request_base base;
+ struct wm *wm;
+ xcb_window_t wid;
+};
struct wm {
- /// Current window generation, start from 1. 0 is reserved for using as
- /// an invalid generation number.
- ///
- /// Because X server recycles window IDs, `id` along
- /// is not enough to uniquely identify a window. This generation number is
- /// incremented every time a window is destroyed, so that if a window ID is
- /// reused, its generation number will be different from before.
- /// Unless, of course, if the generation number overflows, but since we are
- /// using a uint64_t here, that won't happen for a very long time. Still,
- /// it is recommended that you restart the compositor at least once before
- /// the Universe collapse back on itself.
- uint64_t generation;
- /// A hash table of all windows.
- struct win *windows;
- /// Windows in their stacking order
- struct list_node window_stack;
/// Pointer to win
of current active window. Used by
/// EWMH _NET_ACTIVE_WINDOW
focus detection. In theory,
/// it's more reliable to store the window ID directly here, just in
/// case the WM does something extraordinary, but caching the pointer
/// means another layer of complexity.
- struct managed_win *active_win;
+ struct win *active_win;
/// Window ID of leader window of currently active window. Used for
/// subsidiary window detection.
- xcb_window_t active_leader;
- struct subwin *subwins;
+ struct wm_tree_node *active_leader;
+ struct wm_tree tree;
+
+ /// This is a virtual root for all "orphaned" windows. A window is orphaned
+ /// if it is not reachable from the root node. This can only be non-empty if
+ /// the tree is not consistent, i.e. there are pending async query tree requests.
+ ///
+ /// Note an orphaned window cannot be a toplevel. This is trivially true because
+ /// a toplevel has the root window as its parent, and once the root window is
+ /// created its list of children is always up to date.
+ struct wm_tree_node orphan_root;
+
+ /// Tree nodes that have pending async query tree requests. We also have async get
+ /// property requests, but they are not tracked because they don't affect the tree
+ /// structure. We guarantee that when there are pending query tree requests, no
+ /// tree nodes will be freed. This is a dynarr.
+ struct wm_query_tree_request **pending_query_trees;
+};
+
+// TODO(yshui): this is a bit weird and I am not decided on it yet myself. Maybe we can
+// expose `wm_tree_node` directly. But maybe we want to bundle some additional data with
+// it. Anyway, this is probably easy to get rid of if we want to.
+/// A wrapper of `wm_tree_node`. This points to the `siblings` `struct list_node` in a
+/// `struct wm_tree_node`.
+struct wm_ref {
+ struct list_node inner;
};
+static_assert(offsetof(struct wm_ref, inner) == 0, "wm_cursor should be usable as a "
+ "wm_tree_node");
+static_assert(alignof(struct wm_ref) == alignof(struct list_node),
+ "wm_cursor should have the same alignment as wm_tree_node");
+
+static inline const struct wm_tree_node *to_tree_node(const struct wm_ref *cursor) {
+ return cursor != NULL ? list_entry(&cursor->inner, struct wm_tree_node, siblings)
+ : NULL;
+}
+
+static inline struct wm_tree_node *to_tree_node_mut(struct wm_ref *cursor) {
+ return cursor != NULL ? list_entry(&cursor->inner, struct wm_tree_node, siblings)
+ : NULL;
+}
+
+xcb_window_t wm_ref_win_id(const struct wm_ref *cursor) {
+ return to_tree_node(cursor)->id.x;
+}
+
+wm_treeid wm_ref_treeid(const struct wm_ref *cursor) {
+ return to_tree_node(cursor)->id;
+}
-unsigned int wm_get_window_count(struct wm *wm) {
- unsigned int count = 0;
- HASH_ITER2(wm->windows, w) {
- assert(!w->destroyed);
- ++count;
+struct win *wm_ref_deref(const struct wm_ref *cursor) {
+ auto node = to_tree_node(cursor);
+ if (node->parent == NULL) {
+ log_error("Trying to dereference a root node. Expect malfunction.");
+ return NULL;
+ }
+ if (node->parent->parent != NULL) {
+ // Don't return the client window if this is not a toplevel node. This
+ // saves us from needing to clear `->win` when a window is reparented.
+ return NULL;
}
- return count;
+ return node->win;
}
-struct managed_win *wm_active_win(struct wm *wm) {
+void wm_ref_set(struct wm_ref *cursor, struct win *w) {
+ to_tree_node_mut(cursor)->win = w;
+}
+
+struct win *wm_active_win(struct wm *wm) {
return wm->active_win;
}
-void wm_set_active_win(struct wm *wm, struct managed_win *w) {
+void wm_set_active_win(struct wm *wm, struct win *w) {
wm->active_win = w;
}
-xcb_window_t wm_active_leader(struct wm *wm) {
- return wm->active_leader;
+struct wm_ref *wm_active_leader(struct wm *wm) {
+ return wm->active_leader != NULL ? (struct wm_ref *)&wm->active_leader->siblings : NULL;
}
-void wm_set_active_leader(struct wm *wm, xcb_window_t leader) {
- wm->active_leader = leader;
+void wm_set_active_leader(struct wm *wm, struct wm_ref *leader) {
+ wm->active_leader = to_tree_node_mut(leader);
}
-struct win *wm_stack_next(struct wm *wm, struct list_node *cursor) {
- if (!list_node_is_last(&wm->window_stack, cursor)) {
- return list_entry(cursor->next, struct win, stack_neighbour);
- }
- return NULL;
+bool wm_ref_is_zombie(const struct wm_ref *cursor) {
+ return to_tree_node(cursor)->is_zombie;
}
-// Find the managed window immediately below `i` in the window stack
-struct managed_win *
-wm_stack_next_managed(const struct wm *wm, const struct list_node *cursor) {
- while (!list_node_is_last(&wm->window_stack, cursor)) {
- auto next = list_entry(cursor->next, struct win, stack_neighbour);
- if (next->managed) {
- return (struct managed_win *)next;
- }
- cursor = &next->stack_neighbour;
- }
- return NULL;
+struct wm_ref *wm_ref_below(const struct wm_ref *cursor) {
+ return &to_tree_node(cursor)->parent->children != cursor->inner.next
+ ? (struct wm_ref *)cursor->inner.next
+ : NULL;
+}
+
+struct wm_ref *wm_ref_above(const struct wm_ref *cursor) {
+ return &to_tree_node(cursor)->parent->children != cursor->inner.prev
+ ? (struct wm_ref *)cursor->inner.prev
+ : NULL;
+}
+
+struct wm_ref *wm_root_ref(const struct wm *wm) {
+ return (struct wm_ref *)&wm->tree.root->siblings;
+}
+
+struct wm_ref *wm_ref_topmost_child(const struct wm_ref *cursor) {
+ auto node = to_tree_node(cursor);
+ return !list_is_empty(&node->children) ? (struct wm_ref *)node->children.next : NULL;
+}
+
+struct wm_ref *wm_ref_bottommost_child(const struct wm_ref *cursor) {
+ auto node = to_tree_node(cursor);
+ return !list_is_empty(&node->children) ? (struct wm_ref *)node->children.prev : NULL;
}
-/// Find a managed window from window id in window linked list of the session.
-struct win *wm_find(struct wm *wm, xcb_window_t id) {
- if (!id) {
+struct wm_ref *wm_find(const struct wm *wm, xcb_window_t id) {
+ auto node = wm_tree_find(&wm->tree, id);
+ return node != NULL ? (struct wm_ref *)&node->siblings : NULL;
+}
+
+struct wm_ref *wm_find_by_client(const struct wm *wm, xcb_window_t client) {
+ auto node = wm_tree_find(&wm->tree, client);
+ if (node == NULL) {
return NULL;
}
+ auto toplevel = wm_tree_find_toplevel_for(&wm->tree, node);
+ return toplevel != NULL ? (struct wm_ref *)&toplevel->siblings : NULL;
+}
+
+struct wm_ref *wm_ref_toplevel_of(const struct wm *wm, struct wm_ref *cursor) {
+ auto toplevel = wm_tree_find_toplevel_for(&wm->tree, to_tree_node_mut(cursor));
+ return toplevel != NULL ? (struct wm_ref *)&toplevel->siblings : NULL;
+}
- struct win *w = NULL;
- HASH_FIND_INT(wm->windows, &id, w);
- assert(w == NULL || !w->destroyed);
- return w;
+struct wm_ref *wm_ref_client_of(struct wm_ref *cursor) {
+ auto client = to_tree_node(cursor)->client_window;
+ return client != NULL ? (struct wm_ref *)&client->siblings : NULL;
}
-void wm_remove(struct wm *wm, struct win *w) {
- wm->generation++;
- HASH_DEL(wm->windows, w);
+struct wm_ref *wm_stack_end(struct wm *wm) {
+ return (struct wm_ref *)&wm->tree.root->children;
}
-int wm_foreach(struct wm *wm, int (*func)(struct win *, void *), void *data) {
- HASH_ITER2(wm->windows, w) {
- assert(!w->destroyed);
- int ret = func(w, data);
- if (ret) {
- return ret;
+static long wm_find_pending_query_tree(struct wm *wm, struct wm_tree_node *node) {
+ dynarr_foreach(wm->pending_query_trees, i) {
+ if ((*i)->node == node) {
+ return i - wm->pending_query_trees;
}
}
- return 0;
-}
-
-void wm_stack_replace(struct wm *wm, struct win *old, struct win *new_) {
- list_replace(&old->stack_neighbour, &new_->stack_neighbour);
- struct win *replaced = NULL;
- HASH_REPLACE_INT(wm->windows, id, new_, replaced);
- assert(replaced == old);
- free(old);
-}
-
-/// Insert a new window after list_node `prev`
-/// New window will be in unmapped state
-static struct win *
-wm_stack_insert_after(struct wm *wm, xcb_window_t id, struct list_node *prev) {
- log_debug("Adding window %#010x", id);
- struct win *old_w = NULL;
- HASH_FIND_INT(wm->windows, &id, old_w);
- assert(old_w == NULL);
-
- auto new_w = cmalloc(struct win);
- list_insert_after(prev, &new_w->stack_neighbour);
- new_w->id = id;
- new_w->managed = false;
- new_w->is_new = true;
- new_w->destroyed = false;
- new_w->generation = wm->generation;
-
- HASH_ADD_INT(wm->windows, id, new_w);
- return new_w;
-}
-
-struct win *wm_stack_add_top(struct wm *wm, xcb_window_t id) {
- return wm_stack_insert_after(wm, id, &wm->window_stack);
-}
-
-struct win *wm_stack_add_above(struct wm *wm, xcb_window_t id, xcb_window_t below) {
- struct win *w = NULL;
- HASH_FIND_INT(wm->windows, &below, w);
- if (!w) {
- if (!list_is_empty(&wm->window_stack)) {
- // `below` window is not found even if the window stack is
- // not empty
- return NULL;
- }
- return wm_stack_add_top(wm, id);
+ return -1;
+}
+
+/// Move window `w` so it's right above `below`, if `below` is 0, `w` is moved
+/// to the bottom of the stack
+void wm_stack_move_to_above(struct wm *wm, struct wm_ref *cursor, struct wm_ref *below) {
+ auto node = to_tree_node_mut(cursor);
+ if (node->parent == &wm->orphan_root) {
+ // If this window is orphaned, moving it around its siblings is
+ // meaningless. Same below.
+ return;
}
- // we found something from the hash table, so if the stack is
- // empty, we are in an inconsistent state.
- assert(!list_is_empty(&wm->window_stack));
- return wm_stack_insert_after(wm, id, w->stack_neighbour.prev);
+ wm_tree_move_to_above(&wm->tree, node, to_tree_node_mut(below));
}
-/// Move window `w` so it's before `next` in the list
-static inline void wm_stack_move_before(struct wm *wm, struct win *w, struct list_node *next) {
- struct managed_win *mw = NULL;
- if (w->managed) {
- mw = (struct managed_win *)w;
+void wm_stack_move_to_end(struct wm *wm, struct wm_ref *cursor, bool to_bottom) {
+ auto node = to_tree_node_mut(cursor);
+ if (node->parent == &wm->orphan_root) {
+ return;
}
+ wm_tree_move_to_end(&wm->tree, node, to_bottom);
+}
+
+struct wm *wm_new(void) {
+ auto wm = ccalloc(1, struct wm);
+ wm_tree_init(&wm->tree);
+ list_init_head(&wm->orphan_root.children);
+ wm->pending_query_trees = dynarr_new(struct wm_query_tree_request *, 0);
+ return wm;
+}
- if (mw) {
- // This invalidates all reg_ignore below the new stack position of
- // `w`
- mw->reg_ignore_valid = false;
- rc_region_unref(&mw->reg_ignore);
-
- // This invalidates all reg_ignore below the old stack position of
- // `w`
- auto next_w = wm_stack_next_managed(wm, &w->stack_neighbour);
- if (next_w) {
- next_w->reg_ignore_valid = false;
- rc_region_unref(&next_w->reg_ignore);
+void wm_free(struct wm *wm) {
+ // Free all `struct win`s associated with tree nodes, this leaves dangling
+ // pointers, but we are freeing the tree nodes immediately after, so everything
+ // is fine (TM).
+ if (wm->tree.root != NULL) {
+ wm_stack_foreach_safe(wm, i, next) {
+ auto w = wm_ref_deref(i);
+ auto tree_node = to_tree_node_mut(i);
+ free(w);
+
+ if (tree_node->is_zombie) {
+ // This mainly happens on `session_destroy`, e.g. when
+ // there's ongoing animations.
+ log_debug("Leftover zombie node for window %#010x",
+ tree_node->id.x);
+ wm_tree_reap_zombie(tree_node);
+ }
}
}
+ wm_tree_clear(&wm->tree);
+ assert(wm_is_consistent(wm));
+ assert(list_is_empty(&wm->orphan_root.children));
+ dynarr_free_pod(wm->pending_query_trees);
- list_move_before(&w->stack_neighbour, next);
+ free(wm);
+}
-#ifdef DEBUG_RESTACK
- log_trace("Window stack modified. Current stack:");
- for (auto c = &wm->window_stack; c; c = c->next) {
- const char *desc = "";
- if (c->state == WSTATE_DESTROYING) {
- desc = "(D) ";
+/// Once the window tree reaches a consistent state, we know any tree nodes that are not
+/// reachable from the root must have been destroyed, so we can safely free them.
+///
+/// There are cases where we won't receive DestroyNotify events for these windows. For
+/// example, if a window is reparented to a window that is not yet in our tree, then
+/// destroyed, we won't receive a DestroyNotify event for it.
+static void wm_reap_orphans(struct wm *wm) {
+ // Reap orphaned windows
+ while (!list_is_empty(&wm->orphan_root.children)) {
+ auto node =
+ list_entry(wm->orphan_root.children.next, struct wm_tree_node, siblings);
+ list_remove(&node->siblings);
+ if (!list_is_empty(&node->children)) {
+ log_error("Orphaned window %#010x still has children", node->id.x);
+ list_splice(&node->children, &wm->orphan_root.children);
}
- log_trace("%#010x \"%s\" %s", c->id, c->name, desc);
+ HASH_DEL(wm->tree.nodes, node);
+ free(node);
}
-#endif
}
-struct list_node *wm_stack_end(struct wm *wm) {
- return &wm->window_stack;
+/// Move `from->win` to `to->win`, update `win->tree_ref`.
+static void wm_move_win(struct wm_tree_node *from, struct wm_tree_node *to) {
+ if (from->win != NULL) {
+ from->win->tree_ref = (struct wm_ref *)&to->siblings;
+ }
+ to->win = from->win;
+ from->win = NULL;
}
-/// Move window `w` so it's right above `below`, if `below` is 0, `w` is moved
-/// to the bottom of the stack
-void wm_stack_move_above(struct wm *wm, struct win *w, xcb_window_t below) {
- xcb_window_t old_below;
+void wm_destroy(struct wm *wm, xcb_window_t wid) {
+ struct wm_tree_node *node = wm_tree_find(&wm->tree, wid);
+ if (!node) {
+ if (wm_is_consistent(wm)) {
+ log_error("Window %#010x destroyed, but it's not in our tree.", wid);
+ }
+ return;
+ }
- if (!list_node_is_last(&wm->window_stack, &w->stack_neighbour)) {
- old_below = list_next_entry(w, stack_neighbour)->id;
- } else {
- old_below = XCB_NONE;
+ log_debug("Destroying window %#010x", wid);
+
+ if (!list_is_empty(&node->children)) {
+ log_error("Window %#010x is destroyed but it still has children", wid);
}
- log_debug("Restack %#010x (%s), old_below: %#010x, new_below: %#010x", w->id,
- win_get_name_if_managed(w), old_below, below);
-
- if (old_below != below) {
- struct list_node *new_next;
- if (!below) {
- new_next = &wm->window_stack;
- } else {
- struct win *tmp_w = NULL;
- HASH_FIND_INT(wm->windows, &below, tmp_w);
-
- if (!tmp_w) {
- log_error("Failed to found new below window %#010x.", below);
- return;
- }
+ auto zombie = wm_tree_detach(&wm->tree, node);
+ assert(zombie != NULL || node->win == NULL);
+ if (zombie != NULL) {
+ wm_move_win(node, zombie);
+ }
+ // There could be an in-flight query tree request for this window, we orphan it.
+ // It will be reaped when all query tree requests are completed. (Or right now if
+ // the tree is already consistent.)
+ wm_tree_attach(&wm->tree, node, &wm->orphan_root);
+ if (wm_is_consistent(wm)) {
+ wm_reap_orphans(wm);
+ }
+}
+
+void wm_reap_zombie(struct wm_ref *zombie) {
+ wm_tree_reap_zombie(to_tree_node_mut(zombie));
+}
- new_next = &tmp_w->stack_neighbour;
+void wm_reparent(struct wm *wm, xcb_window_t wid, xcb_window_t parent) {
+ auto window = wm_tree_find(&wm->tree, wid);
+ auto new_parent = wm_tree_find(&wm->tree, parent);
+
+ // We orphan the window here if parent is not found. We will reconnect
+ // this window later as query tree requests being completed.
+ if (window == NULL) {
+ if (wm_is_consistent(wm)) {
+ log_error("Window %#010x reparented, but it's not in "
+ "our tree.",
+ wid);
}
- wm_stack_move_before(wm, w, new_next);
+ return;
}
-}
-void wm_stack_move_to_top(struct wm *wm, struct win *w) {
- if (&w->stack_neighbour == wm->window_stack.next) {
- // already at top
+ if (window->parent == new_parent) {
+ // Reparent to the same parent moves the window to the top of the
+ // stack
+ wm_tree_move_to_end(&wm->tree, window, false);
return;
}
- wm_stack_move_before(wm, w, wm->window_stack.next);
-}
-unsigned wm_stack_num_managed_windows(const struct wm *wm) {
- unsigned count = 0;
- list_foreach(struct win, w, &wm->window_stack, stack_neighbour) {
- if (w->managed) {
- count += 1;
- }
+ auto zombie = wm_tree_detach(&wm->tree, window);
+ assert(zombie != NULL || window->win == NULL);
+ if (zombie != NULL) {
+ wm_move_win(window, zombie);
+ }
+
+ // Attaching `window` to `new_parent` will change the children list of
+ // `new_parent`, if there is a pending query tree request for `new_parent`, doing
+ // so will create an overlap. In other words, `window` will appear in the query
+ // tree reply too. Generally speaking, we want to keep a node's children list
+ // empty while there is a pending query tree request for it. (Imagine sending the
+ // query tree "locks" the children list until the reply is processed). Same logic
+ // applies to `wm_import_start`.
+ //
+ // Alternatively if the new parent isn't in our tree yet, we orphan the window
+ // too.
+ if (new_parent == NULL || wm_find_pending_query_tree(wm, new_parent) != -1) {
+ log_debug("Window %#010x is attached to window %#010x which is "
+ "currently been queried, orphaning.",
+ window->id.x, new_parent->id.x);
+ wm_tree_attach(&wm->tree, window, &wm->orphan_root);
+ } else {
+ wm_tree_attach(&wm->tree, window, new_parent);
}
- return count;
}
-struct managed_win *wm_find_by_client(struct wm *wm, xcb_window_t client) {
- if (!client) {
- return NULL;
+void wm_set_has_wm_state(struct wm *wm, struct wm_ref *cursor, bool has_wm_state) {
+ wm_tree_set_wm_state(&wm->tree, to_tree_node_mut(cursor), has_wm_state);
+}
+
+static const xcb_event_mask_t WM_IMPORT_EV_MASK =
+ XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE;
+
+static void wm_import_start_no_flush(struct wm *wm, struct x_connection *c, struct atom *atoms,
+ xcb_window_t wid, struct wm_tree_node *parent);
+
+static void
+wm_handle_query_tree_reply(struct x_connection *c, struct x_async_request_base *base,
+ xcb_raw_generic_event_t *reply_or_error) {
+ auto req = (struct wm_query_tree_request *)base;
+ auto wm = req->wm;
+ {
+ auto last_req = dynarr_last(wm->pending_query_trees);
+ dynarr_remove_swap(req->wm->pending_query_trees, req->pending_index);
+ last_req->pending_index = req->pending_index;
+ }
+
+ if (reply_or_error == NULL) {
+ goto out;
+ }
+
+ auto node = req->node;
+
+ if (reply_or_error->response_type == 0) {
+ // This is an error, most likely the window is gone when we tried
+ // to query it.
+ xcb_generic_error_t *err = (xcb_generic_error_t *)reply_or_error;
+ log_debug("Query tree request for window %#010x failed with "
+ "error %s",
+ node->id.x, x_strerror(err));
+ goto out;
}
- HASH_ITER2(wm->windows, w) {
- assert(!w->destroyed);
- if (!w->managed) {
+ xcb_query_tree_reply_t *reply = (xcb_query_tree_reply_t *)reply_or_error;
+ log_debug("Finished querying tree for window %#010x", node->id.x);
+
+ auto children = xcb_query_tree_children(reply);
+ log_debug("Window %#010x has %d children", node->id.x,
+ xcb_query_tree_children_length(reply));
+ for (int i = 0; i < xcb_query_tree_children_length(reply); i++) {
+ auto child = children[i];
+ auto child_node = wm_tree_find(&wm->tree, child);
+ if (child_node == NULL) {
+ wm_import_start_no_flush(wm, c, req->atoms, child, node);
continue;
}
- auto mw = (struct managed_win *)w;
- if (mw->client_win == client) {
- return mw;
+ // If child node exists, it must be a previously orphaned node.
+ assert(child_node->parent == &wm->orphan_root);
+ auto zombie = wm_tree_detach(&wm->tree, child_node);
+ if (zombie != NULL) {
+ // This only happens if `child_node` is not orphaned, which means
+ // things are already going wrong. (the assert above would fail
+ // too).
+ wm_tree_reap_zombie(zombie);
}
+ wm_tree_attach(&wm->tree, child_node, node);
}
- return NULL;
+out:
+ free(req);
+ xcb_flush(c->c); // Actually send the requests
+ if (wm_is_consistent(wm)) {
+ wm_reap_orphans(wm);
+ }
}
-struct managed_win *wm_find_managed(struct wm *wm, xcb_window_t id) {
- struct win *w = wm_find(wm, id);
- if (!w || !w->managed) {
- return NULL;
+static void wm_handle_get_wm_state_reply(struct x_connection * /*c*/,
+ struct x_async_request_base *base,
+ xcb_raw_generic_event_t *reply_or_error) {
+ auto req = (struct wm_get_property_request *)base;
+ if (reply_or_error == NULL) {
+ free(req);
+ return;
}
- auto mw = (struct managed_win *)w;
- assert(mw->state != WSTATE_DESTROYED);
- return mw;
-}
+ // We guarantee that if a query tree request is pending, its corresponding
+ // window tree node won't be reaped. But we don't guarantee the same for
+ // get property requests. So we need to search the node by window ID again.
+
+ if (reply_or_error->response_type == 0) {
+ // This is an error, most likely the window is gone when we tried
+ // to query it. (Note the tree node might have been freed at this
+ // point if the query tree request also failed earlier.)
+ xcb_generic_error_t *err = (xcb_generic_error_t *)reply_or_error;
+ log_debug("Get WM_STATE request for window %#010x failed with "
+ "error %s",
+ req->wid, x_strerror(err));
+ free(req);
+ return;
+ }
-unsigned wm_num_windows(const struct wm *wm) {
- return HASH_COUNT(wm->windows);
+ auto node = wm_tree_find(&req->wm->tree, req->wid);
+ BUG_ON_NULL(node); // window must exist at this point, but it might be
+ // freed then recreated while we were waiting for the
+ // reply.
+ auto reply = (xcb_get_property_reply_t *)reply_or_error;
+ wm_tree_set_wm_state(&req->wm->tree, node, reply->type != XCB_NONE);
+ free(req);
}
-struct subwin *wm_subwin_add_and_subscribe(struct wm *wm, struct x_connection *c,
- xcb_window_t id, xcb_window_t parent) {
- struct subwin *subwin = NULL;
- HASH_FIND_INT(wm->subwins, &id, subwin);
- BUG_ON(subwin != NULL);
-
- subwin = ccalloc(1, struct subwin);
- subwin->id = id;
- subwin->toplevel = parent;
- HASH_ADD_INT(wm->subwins, id, subwin);
+static void wm_import_start_no_flush(struct wm *wm, struct x_connection *c, struct atom *atoms,
+ xcb_window_t wid, struct wm_tree_node *parent) {
+ log_debug("Starting import process for window %#010x", wid);
+ x_set_error_action_ignore(
+ c, xcb_change_window_attributes(c->c, wid, XCB_CW_EVENT_MASK,
+ (const uint32_t[]){WM_IMPORT_EV_MASK}));
+
+ // Try to see if any orphaned window has the same window ID, if so, it must
+ // have been destroyed without us knowing, so we should reuse the node.
+ auto new = wm_tree_find(&wm->tree, wid);
+ if (new == NULL) {
+ new = wm_tree_new_window(&wm->tree, wid);
+ wm_tree_add_window(&wm->tree, new);
+ } else {
+ if (new->parent == parent) {
+ // What's going on???
+ log_error("Importing window %#010x a second time", wid);
+ assert(false);
+ return;
+ }
+ if (new->parent != &wm->orphan_root) {
+ log_error("Window %#010x appeared in the children list of both "
+ "%#010x (previous) and %#010x (current).",
+ wid, new->parent->id.x, parent->id.x);
+ assert(false);
+ }
- log_debug("Allocated subwin %p for window %#010x, toplevel %#010x, total: %d",
- subwin, id, parent, HASH_COUNT(wm->subwins));
- XCB_AWAIT_VOID(xcb_change_window_attributes, c->c, id, XCB_CW_EVENT_MASK,
- (const uint32_t[]){XCB_EVENT_MASK_PROPERTY_CHANGE});
- return subwin;
-}
+ auto zombie = wm_tree_detach(&wm->tree, new);
+ if (zombie != NULL) {
+ // This only happens if `new` is not orphaned, which means things
+ // are already going wrong.
+ wm_tree_reap_zombie(zombie);
+ }
+ // Need to bump the generation number, as `new` is actually an entirely
+ // new window, just reusing the same window ID.
+ new->id.gen = wm->tree.gen++;
+ }
+ wm_tree_attach(&wm->tree, new, parent);
+ // In theory, though very very unlikely, a window could be reparented (so we won't
+ // receive its DestroyNotify), destroyed, and then a new window was created with
+ // the same window ID, all before the previous query tree request is completed. In
+ // this case, we shouldn't resend another query tree request. (And we also know in
+ // this case the previous get property request isn't completed either.)
+ if (wm_find_pending_query_tree(wm, new) != -1) {
+ return;
+ }
-struct subwin *wm_subwin_find(struct wm *wm, xcb_window_t id) {
- struct subwin *subwin = NULL;
- HASH_FIND_INT(wm->subwins, &id, subwin);
- return subwin;
-}
+ {
+ auto cookie = xcb_query_tree(c->c, wid);
+ auto req = ccalloc(1, struct wm_query_tree_request);
+ req->base.callback = wm_handle_query_tree_reply;
+ req->base.sequence = cookie.sequence;
+ req->node = new;
+ req->wm = wm;
+ req->atoms = atoms;
+ req->pending_index = dynarr_len(wm->pending_query_trees);
+ dynarr_push(wm->pending_query_trees, req);
+ x_await_request(c, &req->base);
+ }
-void wm_subwin_remove(struct wm *wm, struct subwin *subwin) {
- log_debug("Freeing subwin %p for window %#010x, toplevel %#010x", subwin,
- subwin->id, subwin->toplevel);
- HASH_DEL(wm->subwins, subwin);
- free(subwin);
+ // (It's OK to resend the get property request even if one is already in-flight,
+ // unlike query tree.)
+ {
+ auto cookie =
+ xcb_get_property(c->c, 0, wid, atoms->aWM_STATE, XCB_ATOM_ANY, 0, 2);
+ auto req = ccalloc(1, struct wm_get_property_request);
+ req->base.callback = wm_handle_get_wm_state_reply;
+ req->base.sequence = cookie.sequence;
+ req->wm = wm;
+ req->wid = wid;
+ x_await_request(c, &req->base);
+ }
}
-void wm_subwin_remove_and_unsubscribe(struct wm *wm, struct x_connection *c,
- struct subwin *subwin) {
- log_debug("Freeing subwin %p for window %#010x", subwin, subwin->id);
- XCB_AWAIT_VOID(xcb_change_window_attributes, c->c, subwin->id, XCB_CW_EVENT_MASK,
- (const uint32_t[]){0});
- wm_subwin_remove(wm, subwin);
-}
-void wm_subwin_remove_and_unsubscribe_for_toplevel(struct wm *wm, struct x_connection *c,
- xcb_window_t toplevel) {
- struct subwin *subwin, *next_subwin;
- HASH_ITER(hh, wm->subwins, subwin, next_subwin) {
- if (subwin->toplevel == toplevel) {
- wm_subwin_remove_and_unsubscribe(wm, c, subwin);
- }
+void wm_import_start(struct wm *wm, struct x_connection *c, struct atom *atoms,
+ xcb_window_t wid, struct wm_ref *parent) {
+ struct wm_tree_node *parent_node = parent != NULL ? to_tree_node_mut(parent) : NULL;
+ if (parent_node != NULL && wm_find_pending_query_tree(wm, parent_node) != -1) {
+ // Parent node is currently being queried, we can't attach the new window
+ // to it as that will change its children list.
+ return;
}
+ wm_import_start_no_flush(wm, c, atoms, wid, parent_node);
+ xcb_flush(c->c); // Actually send the requests
}
-struct wm *wm_new(void) {
- auto wm = ccalloc(1, struct wm);
- list_init_head(&wm->window_stack);
- wm->generation = 1;
- return wm;
+bool wm_is_consistent(const struct wm *wm) {
+ return dynarr_is_empty(wm->pending_query_trees);
}
-void wm_free(struct wm *wm, struct x_connection *c) {
- list_foreach_safe(struct win, w, &wm->window_stack, stack_neighbour) {
- if (w->managed) {
- XCB_AWAIT_VOID(xcb_change_window_attributes, c->c, w->id,
- XCB_CW_EVENT_MASK, (const uint32_t[]){0});
- }
- if (!w->destroyed) {
- HASH_DEL(wm->windows, w);
- }
- free(w);
- }
- list_init_head(&wm->window_stack);
+bool wm_has_tree_changes(const struct wm *wm) {
+ return !list_is_empty(&wm->tree.changes);
+}
- struct subwin *subwin, *next_subwin;
- HASH_ITER(hh, wm->subwins, subwin, next_subwin) {
- wm_subwin_remove_and_unsubscribe(wm, c, subwin);
+struct wm_change wm_dequeue_change(struct wm *wm) {
+ auto tree_change = wm_tree_dequeue_change(&wm->tree);
+ struct wm_change ret = {
+ .type = tree_change.type,
+ .toplevel = NULL,
+ };
+ switch (tree_change.type) {
+ case WM_TREE_CHANGE_CLIENT:
+ ret.client.old = tree_change.client.old;
+ ret.client.new_ = tree_change.client.new_;
+ ret.toplevel = (struct wm_ref *)&tree_change.client.toplevel->siblings;
+ break;
+ case WM_TREE_CHANGE_TOPLEVEL_KILLED:
+ ret.toplevel = (struct wm_ref *)&tree_change.killed->siblings;
+ break;
+ case WM_TREE_CHANGE_TOPLEVEL_NEW:
+ ret.toplevel = (struct wm_ref *)&tree_change.new_->siblings;
+ break;
+ default: break;
}
+ return ret;
+}
- free(wm);
+struct wm_ref *wm_new_mock_window(struct wm *wm, xcb_window_t wid) {
+ auto node = wm_tree_new_window(&wm->tree, wid);
+ return (struct wm_ref *)&node->siblings;
+}
+void wm_free_mock_window(struct wm * /*wm*/, struct wm_ref *cursor) {
+ free(to_tree_node_mut(cursor));
}
diff --git a/src/wm/wm.h b/src/wm/wm.h
index 13db1ce7af..7fa9391bb8 100644
--- a/src/wm/wm.h
+++ b/src/wm/wm.h
@@ -12,6 +12,9 @@
#pragma once
+#include
+#include
+
#include
#include
@@ -20,7 +23,7 @@
#include "compiler.h"
struct wm;
-struct managed_win;
+struct win;
struct list_node;
struct x_connection;
@@ -32,70 +35,177 @@ struct subwin {
UT_hash_handle hh;
};
+enum wm_tree_change_type {
+ /// The client window of a toplevel changed
+ WM_TREE_CHANGE_CLIENT,
+ /// A toplevel window is killed on the X server side
+ /// A zombie will be left in its place.
+ WM_TREE_CHANGE_TOPLEVEL_KILLED,
+ /// A new toplevel window appeared
+ WM_TREE_CHANGE_TOPLEVEL_NEW,
+
+ // TODO(yshui): This is a stop-gap measure to make sure we invalidate `reg_ignore`
+ // of windows. Once we get rid of `reg_ignore`, which is only used by the legacy
+ // backends, this event should be removed.
+ //
+ // (`reg_ignore` is the cached cumulative opaque region of all windows above a
+ // window in the stacking order. If it actually is performance critical, we
+ // can probably cache it more cleanly in renderer layout.)
+
+ /// The stacking order of toplevel windows changed. Note, toplevel gone/new
+ /// changes also imply a restack.
+ WM_TREE_CHANGE_TOPLEVEL_RESTACKED,
+
+ /// Nothing changed
+ WM_TREE_CHANGE_NONE,
+};
+
+typedef struct wm_treeid {
+ /// The generation of the window ID. This is used to detect if the window ID is
+ /// reused. Inherited from the wm_tree at cr
+ uint64_t gen;
+ /// The X window ID.
+ xcb_window_t x;
+
+ /// Explicit padding
+ char padding[4];
+} wm_treeid;
+
+static const wm_treeid WM_TREEID_NONE = {.gen = 0, .x = XCB_NONE};
+
+static_assert(sizeof(wm_treeid) == 16, "wm_treeid size is not 16 bytes");
+static_assert(alignof(wm_treeid) == 8, "wm_treeid alignment is not 8 bytes");
+
+struct wm_change {
+ enum wm_tree_change_type type;
+ /// The toplevel window this change is about. For
+ /// `WM_TREE_CHANGE_TOPLEVEL_KILLED`, this is the zombie window left in place of
+ /// the killed toplevel. For `WM_TREE_CHANGE_TOPLEVEL_RESTACKED`, this is NULL.
+ struct wm_ref *toplevel;
+ struct {
+ wm_treeid old;
+ wm_treeid new_;
+ } client;
+};
+
+/// Reference to a window in the `struct wm`. Most of wm operations operate on wm_refs. If
+/// the referenced window is managed, a `struct window` can be retrieved by
+/// `wm_ref_deref`.
+struct wm_ref;
+struct atom;
+
+static inline bool wm_treeid_eq(wm_treeid a, wm_treeid b) {
+ return a.gen == b.gen && a.x == b.x;
+}
+
+/// Create a new window management object.
+/// Caller is expected to first call `wm_import_start` with the root window after
+/// creating the object. Otherwise many operations will fail or crash.
struct wm *wm_new(void);
-void wm_free(struct wm *wm, struct x_connection *c);
+void wm_free(struct wm *wm);
-struct managed_win *wm_active_win(struct wm *wm);
-void wm_set_active_win(struct wm *wm, struct managed_win *w);
-xcb_window_t wm_active_leader(struct wm *wm);
-void wm_set_active_leader(struct wm *wm, xcb_window_t leader);
+struct win *wm_active_win(struct wm *wm);
+void wm_set_active_win(struct wm *wm, struct win *w);
+struct wm_ref *wm_active_leader(struct wm *wm);
+void wm_set_active_leader(struct wm *wm, struct wm_ref *leader);
// Note: `wm` keeps track of 2 lists of windows. One is the window stack, which includes
// all windows that might need to be rendered, which means it would include destroyed
-// windows in case they need to be faded out. This list is accessed by `wm_stack_*` series
+// windows in case they have close animation. This list is accessed by `wm_stack_*` series
// of functions. The other is a hash table of windows, which does not include destroyed
// windows. This list is accessed by `wm_find_*`, `wm_foreach`, and `wm_num_windows`.
// Adding a window to the window stack also automatically adds it to the hash table.
/// Find a window in the hash table from window id.
-struct win *wm_find(struct wm *wm, xcb_window_t id);
-/// Remove a window from the hash table.
-void wm_remove(struct wm *wm, struct win *w);
-/// Find a managed window from window id in window linked list of the session.
-struct managed_win *wm_find_managed(struct wm *wm, xcb_window_t id);
+struct wm_ref *attr_pure wm_find(const struct wm *wm, xcb_window_t id);
// Find the WM frame of a client window. `id` is the client window id.
-struct managed_win *wm_find_by_client(struct wm *wm, xcb_window_t client);
-/// Call `func` on each toplevel window. `func` should return 0 if the iteration
-/// should continue. If it returns anything else, the iteration will stop and the
-/// return value will be returned from `wm_foreach`. If the iteration finishes
-/// naturally, 0 will be returned.
-int wm_foreach(struct wm *wm, int (*func)(struct win *, void *), void *data);
-/// Returns the number of windows in the hash table.
-unsigned attr_const wm_num_windows(const struct wm *wm);
-
-/// Returns the cursor past the last window in the stack (the `end`). The window stack is
-/// a cyclic linked list, so the next element after `end` is the first element. The `end`
-/// itself does not point to a valid window. The address of `end` is stable as long as
-/// the `struct wm` itself is not freed.
-struct list_node *attr_const wm_stack_end(struct wm *wm);
-/// Insert a new win entry at the top of the stack
-struct win *wm_stack_add_top(struct wm *wm, xcb_window_t id);
-/// Insert a new window above window with id `below`, if there is no window, add
-/// to top New window will be in unmapped state
-struct win *wm_stack_add_above(struct wm *wm, xcb_window_t id, xcb_window_t below);
-// Find the managed window immediately below `i` in the window stack
-struct managed_win *attr_pure wm_stack_next_managed(const struct wm *wm,
- const struct list_node *cursor);
+struct wm_ref *attr_pure wm_find_by_client(const struct wm *wm, xcb_window_t client);
+/// Find the toplevel of a window by going up the window tree.
+struct wm_ref *attr_pure wm_ref_toplevel_of(const struct wm *wm, struct wm_ref *cursor);
+/// Return the client window of a window. Must be called with a cursor to a toplevel.
+/// Returns NULL if there is no client window.
+struct wm_ref *attr_pure wm_ref_client_of(struct wm_ref *cursor);
+/// Find the next window in the window stack. Returns NULL if `cursor` is the last window.
+struct wm_ref *attr_pure wm_ref_below(const struct wm_ref *cursor);
+struct wm_ref *attr_pure wm_ref_above(const struct wm_ref *cursor);
+struct wm_ref *attr_pure wm_root_ref(const struct wm *wm);
+
+struct wm_ref *attr_pure wm_ref_topmost_child(const struct wm_ref *cursor);
+struct wm_ref *attr_pure wm_ref_bottommost_child(const struct wm_ref *cursor);
+
/// Move window `w` so it's right above `below`, if `below` is 0, `w` is moved
/// to the bottom of the stack
-void wm_stack_move_above(struct wm *wm, struct win *w, xcb_window_t below);
-/// Move window `w` to the bottom of the stack.
-static inline void wm_stack_move_to_bottom(struct wm *wm, struct win *w) {
- wm_stack_move_above(wm, w, 0);
-}
+void wm_stack_move_to_above(struct wm *wm, struct wm_ref *cursor, struct wm_ref *below);
/// Move window `w` to the top of the stack.
-void wm_stack_move_to_top(struct wm *wm, struct win *w);
-/// Replace window `old` with `new_` in the stack, also replace the window in the hash
-/// table. `old` will be freed.
-void wm_stack_replace(struct wm *wm, struct win *old, struct win *new_);
-unsigned attr_const wm_stack_num_managed_windows(const struct wm *wm);
-
-struct subwin *wm_subwin_add_and_subscribe(struct wm *wm, struct x_connection *c,
- xcb_window_t id, xcb_window_t parent);
-struct subwin *wm_subwin_find(struct wm *wm, xcb_window_t id);
-void wm_subwin_remove(struct wm *wm, struct subwin *subwin);
-void wm_subwin_remove_and_unsubscribe(struct wm *wm, struct x_connection *c,
- struct subwin *subwin);
-/// Remove all subwins associated with a toplevel window
-void wm_subwin_remove_and_unsubscribe_for_toplevel(struct wm *wm, struct x_connection *c,
- xcb_window_t toplevel);
+void wm_stack_move_to_end(struct wm *wm, struct wm_ref *cursor, bool to_bottom);
+
+struct win *attr_pure wm_ref_deref(const struct wm_ref *cursor);
+xcb_window_t attr_pure wm_ref_win_id(const struct wm_ref *cursor);
+wm_treeid attr_pure wm_ref_treeid(const struct wm_ref *cursor);
+/// Assign a window to a cursor. The cursor must not already have a window assigned.
+void wm_ref_set(struct wm_ref *cursor, struct win *w);
+bool attr_pure wm_ref_is_zombie(const struct wm_ref *cursor);
+
+/// Destroy a window. Children of this window should already have been destroyed. This
+/// will cause a `WM_TREE_CHANGE_TOPLEVEL_KILLED` event to be generated, and a zombie
+/// window to be placed where the window was.
+void wm_destroy(struct wm *wm, xcb_window_t wid);
+/// Remove a zombie window from the window tree.
+void wm_reap_zombie(struct wm_ref *zombie);
+void wm_reparent(struct wm *wm, xcb_window_t wid, xcb_window_t parent);
+void wm_set_has_wm_state(struct wm *wm, struct wm_ref *cursor, bool has_wm_state);
+
+/// Start the import process for `wid`.
+///
+/// This function sets up event masks for `wid`, and start an async query tree request on
+/// it. When the query tree request is completed, `wm_handle_query_tree_reply` will be
+/// called to actually insert the window into the window tree.
+/// `wm_handle_query_tree_reply` will also in turn start the import process for all
+/// children of `wid`.
+///
+/// The reason for this two step process is because we want to catch all windows ever
+/// created on X server's side. It's not enough to just set up event masks and wait for
+/// events. Because at the point in time we set up the event mask, some child windows
+/// could have already been created. It would have been nice if there is a way to listen
+/// for events on the whole window tree, for all present and future windows. But alas, the
+/// X11 protocol is lacking in this area.
+///
+/// The best thing we can do is set up the event mask to catch all future events, and then
+/// query the current state. But there are complications with this approach, too. Because
+/// there is no way to atomically do these two things in one go, things could happen
+/// between these two steps, for which we will receive events. Some of these are easy to
+/// deal with, e.g. if a window is created, we will get an event for that, and later we
+/// will see that window again in the query tree reply. These are easy to ignore. Some are
+/// more complex. Because there could be some child windows we are not aware of. We could
+/// get events for windows that we don't know about. We try our best to ignore those
+/// events.
+///
+/// Another problem with this is, the usual way we send a request then process the reply
+/// is synchronous. i.e. with `xcb__reply(xcb_(...))`. As previously
+/// mentioned, we might receive events before the reply. And in this case we would process
+/// the reply _before_ any of those events! This might be benign, but it is difficult to
+/// audit all the possible cases and events to make sure this always work. One example is,
+/// imagine a window A is being imported, and another window B, which is already in the
+/// tree got reparented to A. We would think B appeared in two places if we processed
+/// query tree reply before the reparent event. For that, we introduce the concept of
+/// "async requests". Replies to these requests are received and processed like other X
+/// events. With that, we don't need to worry about the order of events and replies.
+///
+/// (Now you have a glimpse of how much X11 sucks.)
+void wm_import_start(struct wm *wm, struct x_connection *c, struct atom *atoms,
+ xcb_window_t wid, struct wm_ref *parent);
+
+/// Check if there are tree change events
+bool wm_has_tree_changes(const struct wm *wm);
+
+struct wm_change wm_dequeue_change(struct wm *wm);
+
+/// Whether the window tree should be in a consistent state. When the window tree is
+/// consistent, we should not be receiving X events that refer to windows that we don't
+/// know about. And we also should not have any orphaned windows in the tree.
+bool wm_is_consistent(const struct wm *wm);
+
+// Unit testing helpers
+
+struct wm_ref *wm_new_mock_window(struct wm *wm, xcb_window_t wid);
+void wm_free_mock_window(struct wm *wm, struct wm_ref *cursor);
diff --git a/src/wm/wm_internal.h b/src/wm/wm_internal.h
new file mode 100644
index 0000000000..e48ec16e09
--- /dev/null
+++ b/src/wm/wm_internal.h
@@ -0,0 +1,117 @@
+#pragma once
+
+#include
+
+#include
+#include
+
+#include "utils/list.h"
+
+#include "wm.h"
+
+struct wm_tree {
+ /// The generation of the wm tree. This number is incremented every time a new
+ /// window is created.
+ ///
+ /// Because X server recycles window IDs, X ID alone is not enough to uniquely
+ /// identify a window. This generation number is incremented every time a window
+ /// is created, so even if a window ID is reused, its generation number is
+ /// guaranteed to be different from before. Unless, of course, the generation
+ /// number overflows, but since we are using a uint64_t here, that won't happen
+ /// for a very long time. Still, it is recommended that you restart the compositor
+ /// at least once before the Universe collapse back on itself.
+ uint64_t gen;
+ /// wm tree nodes indexed by their X window ID.
+ struct wm_tree_node *nodes;
+ struct wm_tree_node *root;
+
+ struct list_node changes;
+ struct list_node free_changes;
+};
+
+struct wm_tree_node {
+ UT_hash_handle hh;
+
+ struct wm_tree_node *parent;
+ struct win *win;
+
+ struct list_node siblings;
+ struct list_node children;
+
+ wm_treeid id;
+ /// The client window. Only a toplevel can have a client window.
+ struct wm_tree_node *client_window;
+
+ bool has_wm_state : 1;
+ /// Whether this window exists only on our side. A zombie window is a toplevel
+ /// that has been destroyed or reparented (i.e. no long a toplevel) on the X
+ /// server side, but is kept on our side for things like animations. A zombie
+ /// window cannot be found in the wm_tree hash table.
+ bool is_zombie : 1;
+};
+
+/// Describe a change of a toplevel's client window.
+/// A `XCB_NONE` in either `old_client` or `new_client` means a missing client window.
+/// i.e. if `old_client` is `XCB_NONE`, it means the toplevel window did not have a client
+/// window before the change, and if `new_client` is `XCB_NONE`, it means the toplevel
+/// window lost its client window after the change.
+struct wm_tree_change {
+ wm_treeid toplevel;
+ union {
+ /// Information for `WM_TREE_CHANGE_CLIENT`.
+ struct {
+ struct wm_tree_node *toplevel;
+ /// The old and new client windows.
+ wm_treeid old, new_;
+ } client;
+ /// Information for `WM_TREE_CHANGE_TOPLEVEL_KILLED`.
+ /// The zombie window left in place of the killed toplevel.
+ struct wm_tree_node *killed;
+ struct wm_tree_node *new_;
+ };
+
+ enum wm_tree_change_type type;
+};
+
+/// Free all tree nodes and changes, without generating any change events. Used when
+/// shutting down.
+void wm_tree_clear(struct wm_tree *tree);
+struct wm_tree_node *attr_pure wm_tree_find(const struct wm_tree *tree, xcb_window_t id);
+/// Find the toplevel that is an ancestor of `node` or `node` itself. Returns NULL if
+/// `node` is part of an orphaned subtree.
+struct wm_tree_node *attr_pure wm_tree_find_toplevel_for(const struct wm_tree *tree,
+ struct wm_tree_node *node);
+struct wm_tree_node *attr_pure wm_tree_next(struct wm_tree_node *node,
+ struct wm_tree_node *subroot);
+/// Create a new window node in the tree, with X window ID `id`, and parent `parent`. If
+/// `parent` is NULL, the new node will be the root window. Only one root window is
+/// permitted, and the root window cannot be destroyed once created, until
+/// `wm_tree_clear` is called. If `parent` is not NULL, the new node will be put at the
+/// top of the stacking order among its siblings.
+struct wm_tree_node *wm_tree_new_window(struct wm_tree *tree, xcb_window_t id);
+void wm_tree_add_window(struct wm_tree *tree, struct wm_tree_node *node);
+void wm_tree_destroy_window(struct wm_tree *tree, struct wm_tree_node *node);
+/// Detach the subtree rooted at `subroot` from `tree`. The subtree root is removed from
+/// its parent, and the disconnected tree nodes won't be able to be found via
+/// `wm_tree_find`. Relevant events will be generated.
+///
+/// Returns the zombie tree node if one is created, or NULL.
+struct wm_tree_node *must_use wm_tree_detach(struct wm_tree *tree, struct wm_tree_node *subroot);
+/// Attach `node` to `parent`. `node` becomes the topmost child of `parent`. If `parent`
+/// is NULL, `node` becomes the root window.
+void wm_tree_attach(struct wm_tree *tree, struct wm_tree_node *child,
+ struct wm_tree_node *parent);
+void wm_tree_move_to_above(struct wm_tree *tree, struct wm_tree_node *node,
+ struct wm_tree_node *other);
+/// Move `node` to the top or the bottom of its parent's child window stack.
+void wm_tree_move_to_end(struct wm_tree *tree, struct wm_tree_node *node, bool to_bottom);
+struct wm_tree_change wm_tree_dequeue_change(struct wm_tree *tree);
+void wm_tree_reap_zombie(struct wm_tree_node *zombie);
+void wm_tree_set_wm_state(struct wm_tree *tree, struct wm_tree_node *node, bool has_wm_state);
+
+static inline void wm_tree_init(struct wm_tree *tree) {
+ tree->nodes = NULL;
+ tree->gen = 1;
+ list_init_head(&tree->changes);
+ list_init_head(&tree->free_changes);
+}
diff --git a/src/x.c b/src/x.c
index 15e891743e..ae0e00fe98 100644
--- a/src/x.c
+++ b/src/x.c
@@ -18,6 +18,8 @@
#include
#include
#include
+#include
+#include
#include
#include "atom.h"
@@ -31,6 +33,177 @@
// === Error handling ===
+/// Discard pending error handlers.
+///
+/// We have received reply with sequence number `sequence`, which means all pending
+/// replies with sequence number strictly less than `sequence` will never be received. So
+/// discard them.
+static void x_discard_pending_errors(struct x_connection *c, uint32_t sequence) {
+ if (sequence < c->last_sequence) {
+ // Overflown, drop from `pending_x_errors` until its sequence number
+ // decreases.
+ log_debug("X sequence number overflown, %u -> %u", c->last_sequence, sequence);
+ list_foreach_safe(struct pending_x_error, i, &c->pending_x_errors, siblings) {
+ if (sequence >= i->sequence) {
+ break;
+ }
+ list_remove(&i->siblings);
+ free(i);
+ }
+ }
+ list_foreach_safe(struct pending_x_error, i, &c->pending_x_errors, siblings) {
+ if (sequence <= i->sequence) {
+ break;
+ }
+ list_remove(&i->siblings);
+ free(i);
+ }
+}
+
+enum {
+ XSyncBadCounter = 0,
+ XSyncBadAlarm = 1,
+ XSyncBadFence = 2,
+};
+
+/// Convert a X11 error to string
+///
+/// @return a pointer to a string. this pointer shouldn NOT be freed, same buffer is used
+/// for multiple calls to this function,
+static const char *x_error_code_to_string(unsigned long serial, uint8_t major,
+ uint16_t minor, uint8_t error_code) {
+ session_t *const ps = ps_g;
+
+ int o = 0;
+ const char *name = "Unknown";
+
+#define CASESTRRET(s) \
+ case s: name = #s; break
+
+#define CASESTRRET2(s) \
+ case XCB_##s: name = #s; break
+
+ // TODO(yshui) separate error code out from session_t
+ o = error_code - ps->xfixes_error;
+ switch (o) { CASESTRRET2(XFIXES_BAD_REGION); }
+
+ o = error_code - ps->damage_error;
+ switch (o) { CASESTRRET2(DAMAGE_BAD_DAMAGE); }
+
+ o = error_code - ps->render_error;
+ switch (o) {
+ CASESTRRET2(RENDER_PICT_FORMAT);
+ CASESTRRET2(RENDER_PICTURE);
+ CASESTRRET2(RENDER_PICT_OP);
+ CASESTRRET2(RENDER_GLYPH_SET);
+ CASESTRRET2(RENDER_GLYPH);
+ }
+
+ if (ps->glx_exists) {
+ o = error_code - ps->glx_error;
+ switch (o) {
+ CASESTRRET2(GLX_BAD_CONTEXT);
+ CASESTRRET2(GLX_BAD_CONTEXT_STATE);
+ CASESTRRET2(GLX_BAD_DRAWABLE);
+ CASESTRRET2(GLX_BAD_PIXMAP);
+ CASESTRRET2(GLX_BAD_CONTEXT_TAG);
+ CASESTRRET2(GLX_BAD_CURRENT_WINDOW);
+ CASESTRRET2(GLX_BAD_RENDER_REQUEST);
+ CASESTRRET2(GLX_BAD_LARGE_REQUEST);
+ CASESTRRET2(GLX_UNSUPPORTED_PRIVATE_REQUEST);
+ CASESTRRET2(GLX_BAD_FB_CONFIG);
+ CASESTRRET2(GLX_BAD_PBUFFER);
+ CASESTRRET2(GLX_BAD_CURRENT_DRAWABLE);
+ CASESTRRET2(GLX_BAD_WINDOW);
+ CASESTRRET2(GLX_GLX_BAD_PROFILE_ARB);
+ }
+ }
+
+ if (ps->xsync_exists) {
+ o = error_code - ps->xsync_error;
+ switch (o) {
+ CASESTRRET(XSyncBadCounter);
+ CASESTRRET(XSyncBadAlarm);
+ CASESTRRET(XSyncBadFence);
+ }
+ }
+
+ switch (error_code) {
+ CASESTRRET2(ACCESS);
+ CASESTRRET2(ALLOC);
+ CASESTRRET2(ATOM);
+ CASESTRRET2(COLORMAP);
+ CASESTRRET2(CURSOR);
+ CASESTRRET2(DRAWABLE);
+ CASESTRRET2(FONT);
+ CASESTRRET2(G_CONTEXT);
+ CASESTRRET2(ID_CHOICE);
+ CASESTRRET2(IMPLEMENTATION);
+ CASESTRRET2(LENGTH);
+ CASESTRRET2(MATCH);
+ CASESTRRET2(NAME);
+ CASESTRRET2(PIXMAP);
+ CASESTRRET2(REQUEST);
+ CASESTRRET2(VALUE);
+ CASESTRRET2(WINDOW);
+ }
+
+#undef CASESTRRET
+#undef CASESTRRET2
+
+ thread_local static char buffer[256];
+ snprintf(buffer, sizeof(buffer), "X error %d %s request %d minor %d serial %lu",
+ error_code, name, major, minor, serial);
+ return buffer;
+}
+
+void x_print_error_impl(unsigned long serial, uint8_t major, uint16_t minor,
+ uint8_t error_code, const char *func) {
+ if (unlikely(LOG_LEVEL_DEBUG >= log_get_level_tls())) {
+ log_printf(tls_logger, LOG_LEVEL_DEBUG, func, "%s",
+ x_error_code_to_string(serial, major, minor, error_code));
+ }
+}
+
+/// Handle X errors.
+///
+/// This function logs X errors, or aborts the program based on severity of the error.
+static void x_handle_error(struct x_connection *c, xcb_generic_error_t *ev) {
+ x_discard_pending_errors(c, ev->full_sequence);
+ struct pending_x_error *first_error_action = NULL;
+ if (!list_is_empty(&c->pending_x_errors)) {
+ first_error_action =
+ list_entry(c->pending_x_errors.next, struct pending_x_error, siblings);
+ }
+ if (first_error_action != NULL && first_error_action->sequence == ev->full_sequence) {
+ if (first_error_action->action != PENDING_REPLY_ACTION_IGNORE) {
+ log_error("X error for request in %s at %s:%d: %s",
+ first_error_action->func, first_error_action->file,
+ first_error_action->line,
+ x_error_code_to_string(ev->full_sequence, ev->major_code,
+ ev->minor_code, ev->error_code));
+ } else {
+ log_debug("Expected X error for request in %s at %s:%d: %s",
+ first_error_action->func, first_error_action->file,
+ first_error_action->line,
+ x_error_code_to_string(ev->full_sequence, ev->major_code,
+ ev->minor_code, ev->error_code));
+ }
+ switch (first_error_action->action) {
+ case PENDING_REPLY_ACTION_ABORT:
+ log_fatal("An unrecoverable X error occurred, "
+ "aborting...");
+ abort();
+ case PENDING_REPLY_ACTION_DEBUG_ABORT: assert(false); break;
+ case PENDING_REPLY_ACTION_IGNORE: break;
+ }
+ return;
+ }
+ log_warn("Stray X error: %s",
+ x_error_code_to_string(ev->full_sequence, ev->major_code, ev->minor_code,
+ ev->error_code));
+}
+
/**
* Xlib error handler function.
*/
@@ -50,37 +223,6 @@ static int xerror(Display attr_unused *dpy, XErrorEvent *ev) {
return 0;
}
-void x_discard_pending(struct x_connection *c, uint32_t sequence) {
- while (c->pending_reply_head && sequence > c->pending_reply_head->sequence) {
- auto next = c->pending_reply_head->next;
- free(c->pending_reply_head);
- c->pending_reply_head = next;
- }
- if (!c->pending_reply_head) {
- c->pending_reply_tail = &c->pending_reply_head;
- }
-}
-
-void x_handle_error(struct x_connection *c, xcb_generic_error_t *ev) {
- x_discard_pending(c, ev->full_sequence);
- if (c->pending_reply_head && c->pending_reply_head->sequence == ev->full_sequence) {
- if (c->pending_reply_head->action != PENDING_REPLY_ACTION_IGNORE) {
- x_log_error(LOG_LEVEL_ERROR, ev->full_sequence, ev->major_code,
- ev->minor_code, ev->error_code);
- }
- switch (c->pending_reply_head->action) {
- case PENDING_REPLY_ACTION_ABORT:
- log_fatal("An unrecoverable X error occurred, aborting...");
- abort();
- case PENDING_REPLY_ACTION_DEBUG_ABORT: assert(false); break;
- case PENDING_REPLY_ACTION_IGNORE: break;
- }
- return;
- }
- x_log_error(LOG_LEVEL_WARN, ev->full_sequence, ev->major_code, ev->minor_code,
- ev->error_code);
-}
-
/// Initialize x_connection struct from an Xlib Display.
///
/// Note this function doesn't take ownership of the Display, the caller is still
@@ -88,11 +230,18 @@ void x_handle_error(struct x_connection *c, xcb_generic_error_t *ev) {
void x_connection_init(struct x_connection *c, Display *dpy) {
c->dpy = dpy;
c->c = XGetXCBConnection(dpy);
- c->pending_reply_tail = &c->pending_reply_head;
+ list_init_head(&c->pending_x_errors);
+ list_init_head(&c->pending_x_requests);
c->previous_xerror_handler = XSetErrorHandler(xerror);
c->screen = DefaultScreen(dpy);
c->screen_info = xcb_aux_get_screen(c->c, c->screen);
+ c->message_on_hold = NULL;
+
+ // Do a round trip to fetch the current sequence number
+ auto cookie = xcb_get_input_focus(c->c);
+ free(xcb_get_input_focus_reply(c->c, cookie, NULL));
+ c->last_sequence = cookie.sequence;
}
/**
@@ -161,14 +310,18 @@ winprop_info_t x_get_prop_info(const struct x_connection *c, xcb_window_t w, xcb
*
* @return the value if successful, 0 otherwise
*/
-xcb_window_t wid_get_prop_window(struct x_connection *c, xcb_window_t wid, xcb_atom_t aprop) {
+xcb_window_t wid_get_prop_window(struct x_connection *c, xcb_window_t wid,
+ xcb_atom_t aprop, bool *exists) {
// Get the attribute
xcb_window_t p = XCB_NONE;
winprop_t prop = x_get_prop(c, wid, aprop, 1L, XCB_ATOM_WINDOW, 32);
// Return it
if (prop.nitems) {
+ *exists = true;
p = (xcb_window_t)*prop.p32;
+ } else {
+ *exists = false;
}
free_winprop(&prop);
@@ -181,39 +334,35 @@ xcb_window_t wid_get_prop_window(struct x_connection *c, xcb_window_t wid, xcb_a
*/
bool wid_get_text_prop(struct x_connection *c, struct atom *atoms, xcb_window_t wid,
xcb_atom_t prop, char ***pstrlst, int *pnstr) {
- auto prop_info = x_get_prop_info(c, wid, prop);
- auto type = prop_info.type;
- auto format = prop_info.format;
- auto length = prop_info.length;
-
- if (type == XCB_ATOM_NONE) {
+ xcb_generic_error_t *e = NULL;
+ auto r = xcb_get_property_reply(
+ c->c, xcb_get_property(c->c, 0, wid, prop, XCB_ATOM_ANY, 0, UINT_MAX), &e);
+ if (!r) {
+ log_debug_x_error(e, "Failed to get window property for %#010x", wid);
+ free(e);
return false;
}
- if (!x_is_type_string(atoms, type)) {
- log_warn("Text property %d of window %#010x has unsupported type: %d",
- prop, wid, type);
+ if (r->type == XCB_ATOM_NONE) {
+ free(r);
return false;
}
- if (format != 8) {
- log_warn("Text property %d of window %#010x has unexpected format: %d",
- prop, wid, format);
+ if (!x_is_type_string(atoms, r->type)) {
+ log_warn("Text property %d of window %#010x has unsupported type: %d",
+ prop, wid, r->type);
+ free(r);
return false;
}
- xcb_generic_error_t *e = NULL;
- auto word_count = (length + 4 - 1) / 4;
- auto r = xcb_get_property_reply(
- c->c, xcb_get_property(c->c, 0, wid, prop, type, 0, word_count), &e);
- if (!r) {
- log_debug_x_error(e, "Failed to get window property for %#010x", wid);
- free(e);
+ if (r->format != 8) {
+ log_warn("Text property %d of window %#010x has unexpected format: %d",
+ prop, wid, r->format);
+ free(r);
return false;
}
- assert(length == (uint32_t)xcb_get_property_value_length(r));
-
+ uint32_t length = to_u32_checked(xcb_get_property_value_length(r));
void *data = xcb_get_property_value(r);
unsigned int nstr = 0;
uint32_t current_offset = 0;
@@ -411,7 +560,7 @@ x_create_picture_with_pictfmt(struct x_connection *c, int w, int h,
xcb_render_picture_t picture = x_create_picture_with_pictfmt_and_pixmap(
c, pictfmt, tmp_pixmap, valuemask, attr);
- set_cant_fail_cookie(c, xcb_free_pixmap(c->c, tmp_pixmap));
+ x_set_error_action_abort(c, xcb_free_pixmap(c->c, tmp_pixmap));
return picture;
}
@@ -508,7 +657,7 @@ uint32_t x_create_region(struct x_connection *c, const region_t *reg) {
void x_destroy_region(struct x_connection *c, xcb_xfixes_region_t r) {
if (r != XCB_NONE) {
- set_debug_cant_fail_cookie(c, xcb_xfixes_destroy_region(c->c, r));
+ x_set_error_action_debug_abort(c, xcb_xfixes_destroy_region(c->c, r));
}
}
@@ -557,121 +706,7 @@ void x_clear_picture_clip_region(struct x_connection *c, xcb_render_picture_t pi
void x_free_picture(struct x_connection *c, xcb_render_picture_t p) {
assert(p != XCB_NONE);
auto cookie = xcb_render_free_picture(c->c, p);
- set_debug_cant_fail_cookie(c, cookie);
-}
-
-enum {
- XSyncBadCounter = 0,
- XSyncBadAlarm = 1,
- XSyncBadFence = 2,
-};
-
-/**
- * Convert a X11 error to string
- *
- * @return a pointer to a string. this pointer shouldn NOT be freed, same buffer is used
- * for multiple calls to this function,
- */
-static const char *
-_x_strerror(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code) {
- session_t *const ps = ps_g;
-
- int o = 0;
- const char *name = "Unknown";
-
-#define CASESTRRET(s) \
- case s: name = #s; break
-
-#define CASESTRRET2(s) \
- case XCB_##s: name = #s; break
-
- // TODO(yshui) separate error code out from session_t
- o = error_code - ps->xfixes_error;
- switch (o) { CASESTRRET2(XFIXES_BAD_REGION); }
-
- o = error_code - ps->damage_error;
- switch (o) { CASESTRRET2(DAMAGE_BAD_DAMAGE); }
-
- o = error_code - ps->render_error;
- switch (o) {
- CASESTRRET2(RENDER_PICT_FORMAT);
- CASESTRRET2(RENDER_PICTURE);
- CASESTRRET2(RENDER_PICT_OP);
- CASESTRRET2(RENDER_GLYPH_SET);
- CASESTRRET2(RENDER_GLYPH);
- }
-
- if (ps->glx_exists) {
- o = error_code - ps->glx_error;
- switch (o) {
- CASESTRRET2(GLX_BAD_CONTEXT);
- CASESTRRET2(GLX_BAD_CONTEXT_STATE);
- CASESTRRET2(GLX_BAD_DRAWABLE);
- CASESTRRET2(GLX_BAD_PIXMAP);
- CASESTRRET2(GLX_BAD_CONTEXT_TAG);
- CASESTRRET2(GLX_BAD_CURRENT_WINDOW);
- CASESTRRET2(GLX_BAD_RENDER_REQUEST);
- CASESTRRET2(GLX_BAD_LARGE_REQUEST);
- CASESTRRET2(GLX_UNSUPPORTED_PRIVATE_REQUEST);
- CASESTRRET2(GLX_BAD_FB_CONFIG);
- CASESTRRET2(GLX_BAD_PBUFFER);
- CASESTRRET2(GLX_BAD_CURRENT_DRAWABLE);
- CASESTRRET2(GLX_BAD_WINDOW);
- CASESTRRET2(GLX_GLX_BAD_PROFILE_ARB);
- }
- }
-
- if (ps->xsync_exists) {
- o = error_code - ps->xsync_error;
- switch (o) {
- CASESTRRET(XSyncBadCounter);
- CASESTRRET(XSyncBadAlarm);
- CASESTRRET(XSyncBadFence);
- }
- }
-
- switch (error_code) {
- CASESTRRET2(ACCESS);
- CASESTRRET2(ALLOC);
- CASESTRRET2(ATOM);
- CASESTRRET2(COLORMAP);
- CASESTRRET2(CURSOR);
- CASESTRRET2(DRAWABLE);
- CASESTRRET2(FONT);
- CASESTRRET2(G_CONTEXT);
- CASESTRRET2(ID_CHOICE);
- CASESTRRET2(IMPLEMENTATION);
- CASESTRRET2(LENGTH);
- CASESTRRET2(MATCH);
- CASESTRRET2(NAME);
- CASESTRRET2(PIXMAP);
- CASESTRRET2(REQUEST);
- CASESTRRET2(VALUE);
- CASESTRRET2(WINDOW);
- }
-
-#undef CASESTRRET
-#undef CASESTRRET2
-
- thread_local static char buffer[256];
- snprintf(buffer, sizeof(buffer), "X error %d %s request %d minor %d serial %lu",
- error_code, name, major, minor, serial);
- return buffer;
-}
-
-/**
- * Log a X11 error
- */
-void x_log_error(enum log_level level, unsigned long serial, uint8_t major,
- uint16_t minor, uint8_t error_code) {
- if (unlikely(level >= log_get_level_tls())) {
- log_printf(tls_logger, level, __func__, "%s",
- _x_strerror(serial, major, minor, error_code));
- }
-}
-
-void x_print_error(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code) {
- x_log_error(LOG_LEVEL_DEBUG, serial, major, minor, error_code);
+ x_set_error_action_debug_abort(c, cookie);
}
/*
@@ -684,7 +719,8 @@ const char *x_strerror(xcb_generic_error_t *e) {
if (!e) {
return "No error";
}
- return _x_strerror(e->full_sequence, e->major_code, e->minor_code, e->error_code);
+ return x_error_code_to_string(e->full_sequence, e->major_code, e->minor_code,
+ e->error_code);
}
/**
@@ -776,7 +812,7 @@ bool x_fence_sync(struct x_connection *c, xcb_sync_fence_t f) {
void x_request_vblank_event(struct x_connection *c, xcb_window_t window, uint64_t msc) {
auto cookie = xcb_present_notify_msc(c->c, window, 0, msc, 1, 0);
- set_cant_fail_cookie(c, cookie);
+ x_set_error_action_abort(c, cookie);
}
/**
@@ -864,25 +900,43 @@ struct xvisual_info x_get_visual_info(struct x_connection *c, xcb_visualid_t vis
};
}
-void x_update_monitors(struct x_connection *c, struct x_monitors *m) {
- x_free_monitor_info(m);
+struct x_update_monitors_request {
+ struct x_async_request_base base;
+ struct x_monitors *monitors;
+};
- xcb_randr_get_monitors_reply_t *r = xcb_randr_get_monitors_reply(
- c->c, xcb_randr_get_monitors(c->c, c->screen_info->root, true), NULL);
- if (!r) {
+static void x_handle_update_monitors_reply(struct x_connection * /*c*/,
+ struct x_async_request_base *req_base,
+ xcb_raw_generic_event_t *reply_or_error) {
+ auto m = ((struct x_update_monitors_request *)req_base)->monitors;
+ free(req_base);
+
+ if (reply_or_error->response_type == 0) {
+ log_warn("Failed to get monitor information using RandR: %s",
+ x_strerror((xcb_generic_error_t *)reply_or_error));
return;
}
- m->count = xcb_randr_get_monitors_monitors_length(r);
+ x_free_monitor_info(m);
+
+ auto reply = (xcb_randr_get_monitors_reply_t *)reply_or_error;
+
+ m->count = xcb_randr_get_monitors_monitors_length(reply);
m->regions = ccalloc(m->count, region_t);
xcb_randr_monitor_info_iterator_t monitor_info_it =
- xcb_randr_get_monitors_monitors_iterator(r);
+ xcb_randr_get_monitors_monitors_iterator(reply);
for (int i = 0; monitor_info_it.rem; xcb_randr_monitor_info_next(&monitor_info_it)) {
xcb_randr_monitor_info_t *mi = monitor_info_it.data;
pixman_region32_init_rect(&m->regions[i++], mi->x, mi->y, mi->width, mi->height);
}
+}
- free(r);
+void x_update_monitors_async(struct x_connection *c, struct x_monitors *m) {
+ auto req = ccalloc(1, struct x_update_monitors_request);
+ req->base.callback = x_handle_update_monitors_reply;
+ req->base.sequence = xcb_randr_get_monitors(c->c, c->screen_info->root, 1).sequence;
+ req->monitors = m;
+ x_await_request(c, &req->base);
}
void x_free_monitor_info(struct x_monitors *m) {
@@ -895,3 +949,167 @@ void x_free_monitor_info(struct x_monitors *m) {
}
m->count = 0;
}
+
+static uint32_t x_get_full_sequence(struct x_connection *c, uint16_t sequence) {
+ auto last_sequence_low = c->last_sequence & 0xffff;
+ // sequence < last_sequence16 means the lower 16 bits overflowed, which should
+ // carry to the higher 16 bits
+ auto sequence_high = c->last_sequence & 0xffff0000;
+ if (sequence < last_sequence_low) {
+ sequence_high += 0x10000;
+ }
+ return sequence_high | sequence;
+}
+
+static int64_t x_compare_sequence(struct x_connection *c, uint32_t a, uint32_t b) {
+ bool a_overflown = a < c->last_sequence, b_overflown = b < c->last_sequence;
+ if (a_overflown == b_overflown) {
+ return (int64_t)a - (int64_t)b;
+ }
+ return a_overflown ? 1 : -1;
+}
+
+static xcb_raw_generic_event_t *
+x_poll_for_event_impl(struct x_connection *c, struct x_async_request_base **out_req) {
+ struct x_async_request_base *first_pending_request = NULL;
+ if (!list_is_empty(&c->pending_x_requests)) {
+ first_pending_request = list_entry(c->pending_x_requests.next,
+ struct x_async_request_base, siblings);
+ }
+
+ bool on_hold_is_reply;
+ if (c->message_on_hold == NULL) {
+ // Nothing on hold, we need to read new information from the X connection.
+ // We must only read from the X connection once in this function to keep
+ // things consistent. The only way to do that is reading the connection
+ // with `xcb_poll_for_reply`, and then check for events with
+ // `xcb_poll_for_queued_event`, because there is no
+ // `xcb_poll_for_queued_reply`. Unless we are not waiting for any replies,
+ // in which case a simple `xcb_poll_for_event` is enough.
+ if (first_pending_request != NULL) {
+ xcb_generic_error_t *err = NULL;
+ on_hold_is_reply =
+ xcb_poll_for_reply(c->c, first_pending_request->sequence,
+ (void **)&c->message_on_hold, &err) == 1;
+ if (err != NULL) {
+ c->message_on_hold = (xcb_raw_generic_event_t *)err;
+ }
+ if (!on_hold_is_reply) {
+ // We didn't get a reply, but did we get an event?
+ c->message_on_hold =
+ (xcb_raw_generic_event_t *)xcb_poll_for_queued_event(c->c);
+ }
+ } else {
+ c->message_on_hold =
+ (xcb_raw_generic_event_t *)xcb_poll_for_event(c->c);
+ on_hold_is_reply = false;
+ }
+ } else if (first_pending_request != NULL) {
+ // response_type 0 is error, 1 is reply.
+ on_hold_is_reply = c->message_on_hold->response_type < 2 &&
+ x_get_full_sequence(c, c->message_on_hold->sequence) ==
+ first_pending_request->sequence;
+ } else {
+ on_hold_is_reply = false;
+ }
+ if (c->message_on_hold == NULL) {
+ // Didn't get any new information from the X connection, nothing to
+ // return.
+ return NULL;
+ }
+
+ // From this point, no more reading from the X connection is allowed.
+ xcb_generic_event_t *next_event = NULL;
+ if (on_hold_is_reply) {
+ next_event = xcb_poll_for_queued_event(c->c);
+ assert(next_event == NULL || next_event->response_type != 1);
+ } else {
+ next_event = (xcb_generic_event_t *)c->message_on_hold;
+ }
+
+ // `next_event == c->message_on_hold` iff `on_hold_is_reply` is false.
+
+ bool should_return_event = false;
+ if (first_pending_request == NULL) {
+ // Here `on_hold_is_reply` must be false, therefore `next_event ==
+ // c->message_on_hold` must be true, therefore `next_event` cannot be
+ // NULL.
+ should_return_event = true;
+ } else if (next_event != NULL) {
+ auto ordering = x_compare_sequence(c, next_event->full_sequence,
+ first_pending_request->sequence);
+ // If next_event is a true event, it might share a sequence number with a
+ // reply. But if it's an error (i.e. response_type == 0), its sequence
+ // number must be different from any reply.
+ assert(next_event->response_type != 0 || ordering != 0);
+ should_return_event = ordering < 0;
+ }
+
+ if (should_return_event) {
+ x_discard_pending_errors(c, next_event->full_sequence);
+ c->last_sequence = next_event->full_sequence;
+ if (!on_hold_is_reply) {
+ c->message_on_hold = NULL;
+ }
+ return (xcb_raw_generic_event_t *)next_event;
+ }
+
+ // We should return the reply to the first pending request.
+ xcb_raw_generic_event_t *ret = NULL;
+ if (!on_hold_is_reply) {
+ xcb_generic_error_t *err = NULL;
+ // This is a very special case. Because we have already received an event
+ // with a greater or equal sequence number than the reply, we _know_ the
+ // reply must also have already arrived. We can safely call
+ // `xcb_poll_for_reply` here because we know it will not read from the X
+ // connection again.
+ BUG_ON(xcb_poll_for_reply(c->c, first_pending_request->sequence,
+ (void **)&ret, &err) == 0);
+ if (err != NULL) {
+ ret = (xcb_raw_generic_event_t *)err;
+ }
+ } else {
+ ret = c->message_on_hold;
+ c->message_on_hold = (xcb_raw_generic_event_t *)next_event;
+ }
+
+ x_discard_pending_errors(c, first_pending_request->sequence + 1);
+ c->last_sequence = first_pending_request->sequence;
+ *out_req = first_pending_request;
+ list_remove(&first_pending_request->siblings);
+ return ret;
+}
+
+xcb_generic_event_t *x_poll_for_event(struct x_connection *c) {
+ xcb_raw_generic_event_t *ret = NULL;
+ while (true) {
+ struct x_async_request_base *req = NULL;
+ ret = x_poll_for_event_impl(c, &req);
+ if (ret == NULL) {
+ break;
+ }
+
+ if (req != NULL) {
+ req->callback(c, req, ret);
+ } else if (ret->response_type == 0) {
+ x_handle_error(c, (xcb_generic_error_t *)ret);
+ } else {
+ break;
+ }
+ free(ret);
+ }
+ return (xcb_generic_event_t *)ret;
+}
+
+void x_cancel_request(struct x_connection *c, struct x_async_request_base *req) {
+ list_remove(&req->siblings);
+ if (c->message_on_hold == NULL) {
+ return;
+ }
+ if (c->message_on_hold->response_type >= 2 ||
+ x_get_full_sequence(c, c->message_on_hold->sequence) != req->sequence) {
+ return;
+ }
+ free(c->message_on_hold);
+ c->message_on_hold = NULL;
+}
diff --git a/src/x.h b/src/x.h
index d4e01f5a68..68bedccb7c 100644
--- a/src/x.h
+++ b/src/x.h
@@ -17,6 +17,7 @@
#include "log.h"
#include "region.h"
#include "utils/kernel.h"
+#include "utils/list.h"
typedef struct session session_t;
struct atom;
@@ -44,17 +45,24 @@ typedef struct winprop_info {
uint32_t length;
} winprop_info_t;
-enum pending_reply_action {
+enum x_error_action {
PENDING_REPLY_ACTION_IGNORE,
PENDING_REPLY_ACTION_ABORT,
PENDING_REPLY_ACTION_DEBUG_ABORT,
};
-typedef struct pending_reply {
- struct pending_reply *next;
+/// Represents a X request we sent that might error.
+struct pending_x_error {
unsigned long sequence;
- enum pending_reply_action action;
-} pending_reply_t;
+ enum x_error_action action;
+
+ // Debug information, where in the code was this request sent.
+ const char *func;
+ const char *file;
+ int line;
+
+ struct list_node siblings;
+};
struct x_connection {
// Public fields
@@ -68,11 +76,19 @@ struct x_connection {
int screen;
// Private fields
- /// Head pointer of the error ignore linked list.
- pending_reply_t *pending_reply_head;
- /// Pointer to the next
member of tail element of the error
- /// ignore linked list.
- pending_reply_t **pending_reply_tail;
+ /// The error handling list.
+ struct list_node pending_x_errors;
+ /// The list of pending async requests that we have
+ /// yet to receive a reply for.
+ struct list_node pending_x_requests;
+ /// A message, either an event or a reply, that is currently being held, because
+ /// there are messages of the opposite type with lower sequence numbers that we
+ /// need to return first.
+ xcb_raw_generic_event_t *message_on_hold;
+ /// The sequence number of the last message returned by
+ /// `x_poll_for_message`. Used for sequence number overflow
+ /// detection.
+ uint32_t last_sequence;
/// Previous handler of X errors
XErrorHandler previous_xerror_handler;
/// Information about the default screen
@@ -133,51 +149,65 @@ static inline uint32_t x_new_id(struct x_connection *c) {
return ret;
}
-static void set_reply_action(struct x_connection *c, uint32_t sequence,
- enum pending_reply_action action) {
- auto i = cmalloc(pending_reply_t);
+/// Set error handler for a specific X request.
+///
+/// @param c X connection
+/// @param sequence sequence number of the X request to set error handler for
+/// @param action action to take when error occurs
+static inline void
+x_set_error_action(struct x_connection *c, uint32_t sequence, enum x_error_action action,
+ const char *func, const char *file, int line) {
+ auto i = cmalloc(struct pending_x_error);
i->sequence = sequence;
- i->next = 0;
i->action = action;
- *c->pending_reply_tail = i;
- c->pending_reply_tail = &i->next;
+ i->func = func;
+ i->file = file;
+ i->line = line;
+ list_insert_before(&c->pending_x_errors, &i->siblings);
}
-/**
- * Ignore X errors caused by given X request.
- */
-static inline void attr_unused set_ignore_cookie(struct x_connection *c,
- xcb_void_cookie_t cookie) {
- set_reply_action(c, cookie.sequence, PENDING_REPLY_ACTION_IGNORE);
-}
+/// Convenience wrapper for x_set_error_action with action `PENDING_REPLY_ACTION_IGNORE`
+#define x_set_error_action_ignore(c, cookie) \
+ x_set_error_action(c, (cookie).sequence, PENDING_REPLY_ACTION_IGNORE, __func__, \
+ __FILE__, __LINE__)
-static inline void attr_unused set_cant_fail_cookie(struct x_connection *c,
- xcb_void_cookie_t cookie) {
- set_reply_action(c, cookie.sequence, PENDING_REPLY_ACTION_ABORT);
-}
+/// Convenience wrapper for x_set_error_action with action `PENDING_REPLY_ACTION_ABORT`
+#define x_set_error_action_abort(c, cookie) \
+ x_set_error_action(c, (cookie).sequence, PENDING_REPLY_ACTION_ABORT, __func__, \
+ __FILE__, __LINE__)
-static inline void attr_unused set_debug_cant_fail_cookie(struct x_connection *c,
- xcb_void_cookie_t cookie) {
+/// Convenience wrapper for x_set_error_action with action
+/// `PENDING_REPLY_ACTION_DEBUG_ABORT`
#ifndef NDEBUG
- set_reply_action(c, cookie.sequence, PENDING_REPLY_ACTION_DEBUG_ABORT);
+#define x_set_error_action_debug_abort(c, cookie) \
+ x_set_error_action(c, (cookie).sequence, PENDING_REPLY_ACTION_DEBUG_ABORT, \
+ __func__, __FILE__, __LINE__)
#else
- (void)c;
- (void)cookie;
+#define x_set_error_action_debug_abort(c, cookie) \
+ ((void)(c)); \
+ ((void)(cookie))
#endif
-}
-static inline void attr_unused free_x_connection(struct x_connection *c) {
- pending_reply_t *next = NULL;
- for (auto ign = c->pending_reply_head; ign; ign = next) {
- next = ign->next;
+struct x_async_request_base {
+ struct list_node siblings;
+ /// The callback function to call when the reply is received. If `reply_or_error`
+ /// is NULL, it means the X connection is closed while waiting for the reply.
+ void (*callback)(struct x_connection *, struct x_async_request_base *,
+ xcb_raw_generic_event_t *reply_or_error);
+ /// The sequence number of the X request.
+ unsigned int sequence;
+};
- free(ign);
+static inline void attr_unused free_x_connection(struct x_connection *c) {
+ list_foreach_safe(struct pending_x_error, i, &c->pending_x_errors, siblings) {
+ list_remove(&i->siblings);
+ free(i);
+ }
+ list_foreach_safe(struct x_async_request_base, i, &c->pending_x_requests, siblings) {
+ list_remove(&i->siblings);
+ i->callback(c, i, NULL);
}
-
- // Reset head and tail
- c->pending_reply_head = NULL;
- c->pending_reply_tail = &c->pending_reply_head;
XSetErrorHandler(c->previous_xerror_handler);
}
@@ -188,18 +218,6 @@ static inline void attr_unused free_x_connection(struct x_connection *c) {
/// responsible for closing it after `free_x_connection` is called.
void x_connection_init(struct x_connection *c, Display *dpy);
-/// Discard queued pending replies.
-///
-/// We have received reply with sequence number `sequence`, which means all pending
-/// replies with sequence number less than `sequence` will never be received. So discard
-/// them.
-void x_discard_pending(struct x_connection *c, uint32_t sequence);
-
-/// Handle X errors.
-///
-/// This function logs X errors, or aborts the program based on severity of the error.
-void x_handle_error(struct x_connection *c, xcb_generic_error_t *ev);
-
/**
* Get a specific attribute of a window.
*
@@ -244,7 +262,8 @@ static inline void x_discard_events(struct x_connection *c) {
*
* @return the value if successful, 0 otherwise
*/
-xcb_window_t wid_get_prop_window(struct x_connection *c, xcb_window_t wid, xcb_atom_t aprop);
+xcb_window_t wid_get_prop_window(struct x_connection *c, xcb_window_t wid,
+ xcb_atom_t aprop, bool *exists);
/**
* Get the value of a text property of a window.
@@ -330,9 +349,10 @@ void x_free_picture(struct x_connection *c, xcb_render_picture_t p);
/**
* Log a X11 error
*/
-void x_print_error(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code);
-void x_log_error(enum log_level level, unsigned long serial, uint8_t major,
- uint16_t minor, uint8_t error_code);
+void x_print_error_impl(unsigned long serial, uint8_t major, uint16_t minor,
+ uint8_t error_code, const char *func);
+#define x_print_error(serial, major, minor, error_code) \
+ x_print_error_impl(serial, major, minor, error_code, __func__)
/*
* Convert a xcb_generic_error_t to a string that describes the error
@@ -399,8 +419,8 @@ xcb_visualid_t x_get_visual_for_depth(xcb_screen_t *screen, uint8_t depth);
xcb_render_pictformat_t
x_get_pictfmt_for_standard(struct x_connection *c, xcb_pict_standard_t std);
-/// Populates a `struct x_monitors` with the current monitor configuration.
-void x_update_monitors(struct x_connection *, struct x_monitors *);
+/// Populates a `struct x_monitors` with the current monitor configuration asynchronously.
+void x_update_monitors_async(struct x_connection *, struct x_monitors *);
/// Free memory allocated for a `struct x_monitors`.
void x_free_monitor_info(struct x_monitors *);
@@ -408,3 +428,24 @@ uint32_t attr_deprecated xcb_generate_id(xcb_connection_t *c);
/// Ask X server to send us a notification for the next end of vblank.
void x_request_vblank_event(struct x_connection *c, xcb_window_t window, uint64_t msc);
+
+/// Register an X request as async request. Its reply will be processed as part of the
+/// event stream. i.e. the registered callback will only be called when all preceding
+/// events have been retrieved via `x_poll_for_event`.
+/// `req` store information about the request, including the callback. The callback is
+/// responsible for freeing `req`.
+static inline void x_await_request(struct x_connection *c, struct x_async_request_base *req) {
+ list_insert_before(&c->pending_x_requests, &req->siblings);
+}
+
+/// Cancel an async request.
+void x_cancel_request(struct x_connection *c, struct x_async_request_base *req);
+
+/// Poll for the next X event. This is like `xcb_poll_for_event`, but also includes
+/// machinery for handling async replies. Calling `xcb_poll_for_event` directly will
+/// cause replies to async requests to be lost, so that should never be called.
+xcb_generic_event_t *x_poll_for_event(struct x_connection *c);
+
+static inline bool x_has_pending_requests(struct x_connection *c) {
+ return !list_is_empty(&c->pending_x_requests);
+}