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); +}