From c0a27974254297ce3f0ab64d165c6ebd9eeeead8 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 19 Jun 2024 10:14:38 +0100 Subject: [PATCH 01/56] inspect: don't ask user to choose a backend We made the backend option mandatory for picom, but that also affected picom-inspect, which doesn't make that much sense. Signed-off-by: Yuxuan Shui --- src/inspect.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/inspect.c b/src/inspect.c index 3c5de79889..860da38b5d 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" @@ -176,6 +177,7 @@ 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."); From 88c8ba8c5e4bf9c19cd0564586ebbaad060b28a5 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 20 Jun 2024 07:27:41 +0100 Subject: [PATCH 02/56] renderer: make consistent_buffer_age more robust It currently crashes if buffer_age is larger than maximum expected buffer age. Signed-off-by: Yuxuan Shui --- src/renderer/renderer.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/renderer/renderer.c b/src/renderer/renderer.c index 2a6807cd46..54dfa44069 100644 --- a/src/renderer/renderer.c +++ b/src/renderer/renderer.c @@ -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; From 5ecac66185bc47917a2fcb429f7f930f79dd19fc Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 4 Jun 2024 00:50:45 +0100 Subject: [PATCH 03/56] wm: add initial data structure for managing the window tree Signed-off-by: Yuxuan Shui --- src/utils/misc.h | 2 + src/wm/meson.build | 2 +- src/wm/tree.c | 519 +++++++++++++++++++++++++++++++++++++++++++ src/wm/wm.h | 27 ++- src/wm/wm_internal.h | 102 +++++++++ 5 files changed, 650 insertions(+), 2 deletions(-) create mode 100644 src/wm/tree.c create mode 100644 src/wm/wm_internal.h 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/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..6685b024a6 --- /dev/null +++ b/src/wm/tree.c @@ -0,0 +1,519 @@ +// 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); +} + +static void wm_tree_enqueue_change(struct wm_tree *tree, struct wm_tree_change change) { + if (change.type == WM_TREE_CHANGE_TOPLEVEL_KILLED) { + // 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 (i->item.type == WM_TREE_CHANGE_TOPLEVEL_NEW && + wm_treeid_eq(i->item.toplevel, change.toplevel)) { + list_remove(&i->siblings); + list_insert_after(&tree->free_changes, &i->siblings); + found = true; + } else if (wm_treeid_eq(i->item.toplevel, change.toplevel) && 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); + } + } + if (found) { + wm_tree_reap_zombie(change.killed); + return; + } + } else if (change.type == WM_TREE_CHANGE_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, change.toplevel) || + i->item.type != WM_TREE_CHANGE_CLIENT) { + continue; + } + + if (!wm_treeid_eq(i->item.client.new_, change.client.old)) { + log_warn("Inconsistent client change for toplevel " + "%#010x. Missing changes from %#010x to %#010x. " + "Possible bug.", + change.toplevel.x, i->item.client.new_.x, + change.client.old.x); + } + + i->item.client.new_ = change.client.new_; + if (wm_treeid_eq(i->item.client.old, change.client.new_)) { + list_remove(&i->siblings); + list_insert_after(&tree->free_changes, &i->siblings); + } + return; + } + } else if (change.type == WM_TREE_CHANGE_TOPLEVEL_RESTACKED) { + 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; + } + } + } + + // We don't let a `WM_TREE_CHANGE_TOPLEVEL_NEW` cancel out a previous + // `WM_TREE_CHANGE_TOPLEVEL_GONE`, 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). + + 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); +} + +/// 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. +static struct wm_tree_node * +wm_tree_next(struct wm_tree_node *node, struct wm_tree_node *subroot) { + 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(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; +} + +/// 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; + } + + struct wm_tree_change change = { + .toplevel = toplevel->id, + .type = WM_TREE_CHANGE_CLIENT, + .client = {.toplevel = toplevel}, + }; + if (!has_wm_state && toplevel->client_window == node) { + auto new_client = wm_tree_find_client(toplevel); + toplevel->client_window = new_client; + change.client.old = node->id; + change.client.new_ = new_client != NULL ? new_client->id : WM_TREEID_NONE; + wm_tree_enqueue_change(tree, change); + } else if (has_wm_state && toplevel->client_window == NULL) { + toplevel->client_window = node; + change.client.old = WM_TREEID_NONE; + change.client.new_ = node->id; + wm_tree_enqueue_change(tree, change); + } 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, struct wm_tree_node *parent) { + 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); + + BUG_ON(parent == NULL && tree->nodes != NULL); // Trying to create a second + // root window + HASH_ADD_INT(tree->nodes, id.x, node); + + node->parent = parent; + if (parent != NULL) { + list_insert_after(&parent->children, &node->siblings); + if (parent->parent == NULL) { + // Parent is root, this is a new toplevel window + wm_tree_enqueue_change(tree, (struct wm_tree_change){ + .toplevel = node->id, + .type = WM_TREE_CHANGE_TOPLEVEL_NEW, + .new_ = node, + }); + } + } + return 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) { + struct wm_tree_change change = {.toplevel = toplevel->id, + .type = WM_TREE_CHANGE_CLIENT, + .client = {.toplevel = toplevel, + .old = WM_TREEID_NONE, + .new_ = WM_TREEID_NONE}}; + if (toplevel->client_window != NULL) { + change.client.old = toplevel->client_window->id; + } + if (new_client != NULL) { + change.client.new_ = new_client->id; + } + toplevel->client_window = new_client; + wm_tree_enqueue_change(tree, change); + } +} + +void 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(subroot); + if (toplevel != subroot) { + list_remove(&subroot->siblings); + wm_tree_refresh_client_and_queue_change(tree, toplevel); + } else { + // Detached a toplevel, create a zombie for it + auto zombie = ccalloc(1, struct wm_tree_node); + zombie->parent = subroot->parent; + zombie->id = subroot->id; + zombie->is_zombie = true; + zombie->win = subroot->win; + list_init_head(&zombie->children); + list_replace(&subroot->siblings, &zombie->siblings); + wm_tree_enqueue_change(tree, (struct wm_tree_change){ + .toplevel = subroot->id, + .type = WM_TREE_CHANGE_TOPLEVEL_KILLED, + .killed = zombie, + }); + } +} + +void wm_tree_destroy_window(struct wm_tree *tree, struct wm_tree_node *node) { + BUG_ON(node == NULL); + BUG_ON(node->parent == NULL); // Trying to destroy the root window?! + + if (node->has_wm_state) { + wm_tree_set_wm_state(tree, node, false); + } + + HASH_DEL(tree->nodes, node); + + if (!list_is_empty(&node->children)) { + log_error("Window %#010x is destroyed, but it still has children. Expect " + "malfunction.", + node->id.x); + list_foreach_safe(struct wm_tree_node, i, &node->children, siblings) { + wm_tree_destroy_window(tree, i); + } + } + + if (node->parent->parent == NULL) { + node->is_zombie = true; + wm_tree_enqueue_change(tree, (struct wm_tree_change){ + .toplevel = node->id, + .type = WM_TREE_CHANGE_TOPLEVEL_KILLED, + .killed = node, + }); + } else { + list_remove(&node->siblings); + free(node); + } +} + +/// 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) { + 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->parent == NULL) { + wm_tree_enqueue_change(tree, (struct wm_tree_change){ + .type = WM_TREE_CHANGE_TOPLEVEL_RESTACKED, + }); + } +} + +/// 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->parent == NULL) { + wm_tree_enqueue_change(tree, (struct wm_tree_change){ + .type = WM_TREE_CHANGE_TOPLEVEL_RESTACKED, + }); + } +} + +void wm_tree_reparent(struct wm_tree *tree, struct wm_tree_node *node, + struct wm_tree_node *new_parent) { + BUG_ON(node == NULL); + BUG_ON(new_parent == NULL); // Trying make `node` a root window + + if (node->parent == new_parent) { + // Reparent to the same parent moves the window to the top of the stack + wm_tree_move_to_end(tree, node, false); + return; + } + + wm_tree_detach(tree, node); + + // Reparented window always becomes the topmost child of the new parent + list_insert_after(&new_parent->children, &node->siblings); + node->parent = new_parent; + + auto toplevel = wm_tree_find_toplevel_for(node); + if (node == toplevel) { + // This node could have a stale `->win` if it was a toplevel at + // some point in the past. + node->win = NULL; + wm_tree_enqueue_change(tree, (struct wm_tree_change){ + .toplevel = node->id, + .type = WM_TREE_CHANGE_TOPLEVEL_NEW, + .new_ = node, + }); + } else { + wm_tree_refresh_client_and_queue_change(tree, toplevel); + } +} + +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_new_window(&tree, 1, NULL); + auto root = wm_tree_find(&tree, 1); + assert(root != NULL); + assert(root->parent == NULL); + + auto change = wm_tree_dequeue_change(&tree); + assert(change.type == WM_TREE_CHANGE_NONE); + + wm_tree_new_window(&tree, 2, root); + auto node2 = wm_tree_find(&tree, 2); + assert(node2 != NULL); + 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)); + + wm_tree_new_window(&tree, 3, root); + auto node3 = wm_tree_find(&tree, 3); + assert(node3 != NULL); + + change = wm_tree_dequeue_change(&tree); + assert(change.toplevel.x == 3); + assert(change.type == WM_TREE_CHANGE_TOPLEVEL_NEW); + + wm_tree_reparent(&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); + 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); + + wm_tree_new_window(&tree, 4, node3); + auto node4 = wm_tree_find(&tree, 4); + 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); + + wm_tree_destroy_window(&tree, 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 + wm_tree_destroy_window(&tree, node4); + node4 = wm_tree_new_window(&tree, 4, 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, root); + wm_tree_destroy_window(&tree, 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/wm.h b/src/wm/wm.h index 13db1ce7af..866e6b79f5 100644 --- a/src/wm/wm.h +++ b/src/wm/wm.h @@ -32,6 +32,31 @@ 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, +}; + struct wm *wm_new(void); void wm_free(struct wm *wm, struct x_connection *c); @@ -42,7 +67,7 @@ void wm_set_active_leader(struct wm *wm, xcb_window_t 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. diff --git a/src/wm/wm_internal.h b/src/wm/wm_internal.h new file mode 100644 index 0000000000..7322ec2a83 --- /dev/null +++ b/src/wm/wm_internal.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#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. + uint64_t gen; + /// wm tree nodes indexed by their X window ID. + struct wm_tree_node *nodes; + + struct list_node changes; + struct list_node free_changes; +}; + +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_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 *wm_tree_find(struct wm_tree *tree, xcb_window_t id); +struct wm_tree_node *wm_tree_find_toplevel_for(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. +void wm_tree_detach(struct wm_tree *tree, struct wm_tree_node *subroot); + +static inline void wm_tree_init(struct wm_tree *tree) { + tree->nodes = NULL; + list_init_head(&tree->changes); + list_init_head(&tree->free_changes); +} + +static inline bool wm_treeid_eq(wm_treeid a, wm_treeid b) { + return a.gen == b.gen && a.x == b.x; +} From c96ca0a40a5b2a65ad5cc82001be884117bd6c20 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 4 Jun 2024 19:39:45 +0100 Subject: [PATCH 04/56] wm: wire up the new wm_tree structure to X events To let it process some events to validate that it is working. The output of wm_tree is not yet used besides debugging logs. Note because we don't quite setup event subscription the way wm_tree expects, there will be some errors logged, which is normal. Signed-off-by: Yuxuan Shui --- src/event.c | 11 +- src/picom.c | 33 +++++- src/utils/dynarr.h | 4 +- src/wm/tree.c | 3 +- src/wm/wm.c | 259 +++++++++++++++++++++++++++++++++++++++++++ src/wm/wm.h | 102 +++++++++++++++++ src/wm/wm_internal.h | 37 +++---- 7 files changed, 420 insertions(+), 29 deletions(-) diff --git a/src/event.c b/src/event.c index 1642156fa4..9b0da675cb 100644 --- a/src/event.c +++ b/src/event.c @@ -203,6 +203,8 @@ static inline void ev_focus_out(session_t *ps, xcb_focus_out_event_t *ev) { } static inline void ev_create_notify(session_t *ps, xcb_create_notify_event_t *ev) { + wm_import_incomplete(ps->wm, ev->window, ev->parent); + if (ev->parent == ps->c.screen_info->root) { wm_stack_add_top(ps->wm, ev->window); ps->pending_updates = true; @@ -294,6 +296,8 @@ static inline void ev_configure_notify(session_t *ps, xcb_configure_notify_event } static inline void ev_destroy_notify(session_t *ps, xcb_destroy_notify_event_t *ev) { + wm_destroy(ps->wm, ev->window); + auto subwin = wm_subwin_find(ps->wm, ev->window); if (subwin) { wm_subwin_remove(ps->wm, subwin); @@ -370,8 +374,11 @@ static inline void ev_unmap_notify(session_t *ps, xcb_unmap_notify_event_t *ev) } 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); + 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); auto old_toplevel = wm_find_by_client(ps->wm, ev->window); auto old_w = wm_find(ps->wm, ev->window); diff --git a/src/picom.c b/src/picom.c index ebd62d377c..2ac875bb19 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1572,6 +1572,8 @@ static void handle_queued_x_events(EV_P attr_unused, ev_prepare *w, int revents } static void handle_new_windows(session_t *ps) { + wm_complete_import(ps->wm, &ps->c, ps->atoms); + 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); @@ -1633,11 +1635,36 @@ static void handle_pending_updates(EV_P_ struct session *ps, double delta_t) { // Catching up with X server handle_queued_x_events(EV_A, &ps->event_check, 0); - if (ps->pending_updates) { + if (ps->pending_updates || wm_has_incomplete_imports(ps->wm) || + wm_has_tree_changes(ps->wm)) { log_debug("Delayed handling of events, entering critical section"); // Process new windows, and maybe allocate struct managed_win for them handle_new_windows(ps); + 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: + log_debug("New window %#010x", + wm_ref_win_id(wm_change.toplevel)); + break; + case WM_TREE_CHANGE_TOPLEVEL_KILLED: + log_debug("Destroying window %#010x", + wm_ref_win_id(wm_change.toplevel)); + break; + case WM_TREE_CHANGE_CLIENT: + log_debug("Client message 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); + break; + default: break; + } + } + // 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 @@ -2473,6 +2500,9 @@ static session_t *session_init(int argc, char **argv, Display *dpy, #endif } + ps->wm = wm_new(); + wm_import_incomplete(ps->wm, ps->c.screen_info->root, XCB_NONE); + 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"); @@ -2500,7 +2530,6 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ps->server_grabbed = false; - ps->wm = wm_new(); if (query_tree_reply) { xcb_window_t *children; int nchildren; 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/wm/tree.c b/src/wm/tree.c index 6685b024a6..3aa6a3aaf3 100644 --- a/src/wm/tree.c +++ b/src/wm/tree.c @@ -125,8 +125,7 @@ struct wm_tree_change wm_tree_dequeue_change(struct wm_tree *tree) { /// 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. -static struct wm_tree_node * -wm_tree_next(struct wm_tree_node *node, struct wm_tree_node *subroot) { +struct wm_tree_node *wm_tree_next(struct wm_tree_node *node, struct wm_tree_node *subroot) { if (!list_is_empty(&node->children)) { // Descend if there are children return list_entry(node->children.next, struct wm_tree_node, siblings); diff --git a/src/wm/wm.c b/src/wm/wm.c index 935cb6ee3a..e2b6fa2b41 100644 --- a/src/wm/wm.c +++ b/src/wm/wm.c @@ -1,16 +1,20 @@ // 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 { /// Current window generation, start from 1. 0 is reserved for using as @@ -39,6 +43,16 @@ struct wm { /// subsidiary window detection. xcb_window_t active_leader; struct subwin *subwins; + struct wm_tree tree; + + struct wm_tree_node *root; + + /// Incomplete imports. See `wm_import_incomplete` for an explanation. + /// This is a dynarr. + struct wm_tree_node **incompletes; + /// Tree nodes that we have chosen to forget, but we might still receive some + /// events from, we keep them here to ignore those events. + struct wm_tree_node **masked; }; unsigned int wm_get_window_count(struct wm *wm) { @@ -49,11 +63,41 @@ unsigned int wm_get_window_count(struct wm *wm) { } return count; } +// 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; +} + +xcb_window_t wm_ref_win_id(const struct wm_ref *cursor) { + return to_tree_node(cursor)->id.x; +} struct managed_win *wm_active_win(struct wm *wm) { return wm->active_win; } +static ptrdiff_t wm_find_masked(struct wm *wm, xcb_window_t wid) { + dynarr_foreach(wm->masked, m) { + if ((*m)->id.x == wid) { + return m - wm->masked; + } + } + return -1; +} + void wm_set_active_win(struct wm *wm, struct managed_win *w) { wm->active_win = w; } @@ -341,6 +385,9 @@ struct wm *wm_new(void) { auto wm = ccalloc(1, struct wm); list_init_head(&wm->window_stack); wm->generation = 1; + wm_tree_init(&wm->tree); + wm->incompletes = dynarr_new(struct wm_tree_node *, 4); + wm->masked = dynarr_new(struct wm_tree_node *, 8); return wm; } @@ -361,6 +408,218 @@ void wm_free(struct wm *wm, struct x_connection *c) { HASH_ITER(hh, wm->subwins, subwin, next_subwin) { wm_subwin_remove_and_unsubscribe(wm, c, subwin); } + wm_tree_clear(&wm->tree); + dynarr_free_pod(wm->incompletes); + dynarr_free_pod(wm->masked); free(wm); } + +void wm_destroy(struct wm *wm, xcb_window_t wid) { + auto masked = wm_find_masked(wm, wid); + if (masked != -1) { + free(wm->masked[masked]); + dynarr_remove_swap(wm->masked, (size_t)masked); + return; + } + + struct wm_tree_node *node = wm_tree_find(&wm->tree, wid); + if (!node) { + log_error("Trying to destroy window %#010x, but it's not in the tree. " + "Expect malfunction.", + wid); + return; + } + + auto index = dynarr_find_pod(wm->incompletes, node); + if (index != -1) { + dynarr_remove_swap(wm->incompletes, (size_t)index); + } + + wm_tree_destroy_window(&wm->tree, node); +} + +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 delete the window here if parent is not found, or if the parent is + // an incomplete import. We will rediscover this window later in + // `wm_complete_import`. Keeping it around will only confuse us. + bool should_forget = + new_parent == NULL || dynarr_find_pod(wm->incompletes, new_parent) != -1; + if (window == NULL) { + log_debug("Reparenting window %#010x which is not in our tree. Assuming " + "it came from fog of war.", + wid); + if (!should_forget) { + wm_import_incomplete(wm, wid, parent); + } + return; + } + if (should_forget) { + log_debug("Window %#010x reparented to window %#010x which is " + "%s, forgetting and masking instead.", + window->id.x, parent, + new_parent == NULL ? "not in our tree" : "an incomplete import"); + + wm_tree_detach(&wm->tree, window); + for (auto curr = window; curr != NULL;) { + auto next = wm_tree_next(curr, window); + HASH_DEL(wm->tree.nodes, curr); + auto incomplete_index = dynarr_find_pod(wm->incompletes, curr); + if (incomplete_index != -1) { + dynarr_remove_swap(wm->incompletes, (size_t)incomplete_index); + // Incomplete imports cannot have children. + assert(list_is_empty(&curr->children)); + + // Incomplete imports won't have event masks set, so we + // don't need to mask them. + free(curr); + } else { + dynarr_push(wm->masked, curr); + } + curr = next; + } + return; + } + + wm_tree_reparent(&wm->tree, window, new_parent); +} + +void wm_import_incomplete(struct wm *wm, xcb_window_t wid, xcb_window_t parent) { + auto masked = wm_find_masked(wm, wid); + if (masked != -1) { + // A new window created with the same wid means the window we chose to + // forget has been deleted (without us knowing), and its ID was then + // reused. + free(wm->masked[masked]); + dynarr_remove_swap(wm->masked, (size_t)masked); + } + auto parent_cursor = NULL; + if (parent != XCB_NONE) { + parent_cursor = wm_tree_find(&wm->tree, parent); + if (parent_cursor == NULL) { + log_error("Importing window %#010x, but its parent %#010x is not " + "in our tree, ignoring. Expect malfunction.", + wid, parent); + return; + } + } + log_debug("Importing window %#010x with parent %#010x", wid, parent); + auto new = wm_tree_new_window(&wm->tree, wid, parent_cursor); + dynarr_push(wm->incompletes, new); + if (parent == XCB_NONE) { + BUG_ON(wm->root != NULL); // Can't have more than one root + wm->root = new; + } +} +static const xcb_event_mask_t WM_IMPORT_EV_MASK = + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE; +static void wm_complete_import_single(struct wm *wm, struct x_connection *c, + struct atom *atoms, struct wm_tree_node *node) { + log_debug("Finishing importing window %#010x with parent %#010lx.", node->id.x, + node->parent != NULL ? node->parent->id.x : XCB_NONE); + set_cant_fail_cookie( + c, xcb_change_window_attributes(c->c, node->id.x, XCB_CW_EVENT_MASK, + (const uint32_t[]){WM_IMPORT_EV_MASK})); + if (wid_has_prop(c->c, node->id.x, atoms->aWM_STATE)) { + wm_tree_set_wm_state(&wm->tree, node, true); + } +} + +static void wm_complete_import_subtree(struct wm *wm, struct x_connection *c, + struct atom *atoms, struct wm_tree_node *subroot) { + wm_complete_import_single(wm, c, atoms, subroot); + + for (auto curr = subroot; curr != NULL; curr = wm_tree_next(curr, subroot)) { + if (!list_is_empty(&curr->children)) { + log_error("Newly imported subtree root at %#010x already has " + "children. " + "Expect malfunction.", + curr->id.x); + } + + xcb_query_tree_reply_t *tree = XCB_AWAIT(xcb_query_tree, c->c, curr->id.x); + if (!tree) { + log_error("Disappearing window subtree rooted at %#010x. Expect " + "malfunction.", + curr->id.x); + continue; + } + + auto children = xcb_query_tree_children(tree); + auto children_len = xcb_query_tree_children_length(tree); + for (int i = 0; i < children_len; i++) { + // `children` goes from bottom to top, and `wm_tree_new_window` + // puts the new window at the top of the stacking order, which + // means the windows will naturally be in the correct stacking + // order. + auto existing = wm_tree_find(&wm->tree, children[i]); + if (existing != NULL) { + // This should never happen: we haven't subscribed to + // child creation events yet, and any window reparented to + // an incomplete is deleted. report an error and try to + // recover. + auto index = dynarr_find_pod(wm->incompletes, existing); + if (index != -1) { + dynarr_remove_swap(wm->incompletes, (size_t)index); + } + log_error("Window %#010x already exists in the tree, but " + "it appeared again as a child of window " + "%#010x. Deleting the old one, but expect " + "malfunction.", + children[i], curr->id.x); + wm_tree_destroy_window(&wm->tree, existing); + } + existing = wm_tree_new_window(&wm->tree, children[i], curr); + wm_complete_import_single(wm, c, atoms, existing); + } + free(tree); + } +} + +void wm_complete_import(struct wm *wm, struct x_connection *c, struct atom *atoms) { + // Unveil the fog of war + dynarr_foreach(wm->masked, m) { + free(*m); + } + dynarr_clear_pod(wm->masked); + + while (!dynarr_is_empty(wm->incompletes)) { + auto i = dynarr_pop(wm->incompletes); + // This function modifies `wm->incompletes`, so we can't use + // `dynarr_foreach`. + wm_complete_import_subtree(wm, c, atoms, i); + } +} + +bool wm_has_incomplete_imports(const struct wm *wm) { + return !dynarr_is_empty(wm->incompletes); +} + +bool wm_has_tree_changes(const struct wm *wm) { + return !list_is_empty(&wm->tree.changes); +} + +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; +} diff --git a/src/wm/wm.h b/src/wm/wm.h index 866e6b79f5..f9520dc598 100644 --- a/src/wm/wm.h +++ b/src/wm/wm.h @@ -12,6 +12,9 @@ #pragma once +#include +#include + #include #include @@ -57,6 +60,44 @@ enum wm_tree_change_type { 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; +} + struct wm *wm_new(void); void wm_free(struct wm *wm, struct x_connection *c); @@ -124,3 +165,64 @@ void wm_subwin_remove_and_unsubscribe(struct wm *wm, struct x_connection *c, /// 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); +xcb_window_t attr_pure wm_ref_win_id(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); +void wm_reparent(struct wm *wm, xcb_window_t wid, xcb_window_t parent); + +/// Create a tree node for `wid`, with `parent` as its parent. The parent must already +/// be in the window tree. This function creates a placeholder tree node, without +/// contacting the X server, thus can be called outside of the X critical section. The +/// expectation is that the caller will later call `wm_complete_import` inside the +/// X critical section to complete the import. +/// +/// ## NOTE +/// +/// The reason for this complicated dance is because we want to catch all windows ever +/// created on X server's side. For a newly created windows, we will setup a +/// SubstructureNotify event mask to catch any new windows created under it. But between +/// the time we received the creation event and the time we setup the event mask, if any +/// windows were created under the new window, we will miss them. Therefore we have to +/// scan the new windows in X critical section so they won't change as we scan. +/// +/// On the other hand, we can't push everything to the X critical section, because +/// updating the window stack requires knowledge of all windows in the stack. Think +/// ConfigureNotify, if we don't know about the `above_sibling` window, we don't know +/// where to put the window. So we need to create an incomplete import first. +/// +/// But wait, this is actually way more involved. Because we choose to not set up event +/// masks for incomplete imports (we can also choose otherwise, but that also has its own +/// set of problems), there is a whole subtree of windows we don't know about. And those +/// windows might involve in reparent events. To handle that, we essentially put "fog of +/// war" under any incomplete imports, anything reparented into the fog is lost, and will +/// be rediscovered later during subtree scans. If a window is reparented out of the fog, +/// then it's treated as if a brand new window was created. +/// +/// But wait again, there's more. We can delete "lost" windows on our side and unset event +/// masks, but again because this is racy, we might still receive some events for those +/// windows. So we have to keep a list of "lost" windows, and correctly ignore events sent +/// for them. (And since we are actively ignoring events from these windows, we might as +/// well not unset their event masks, saves us a trip to the X server). +/// +/// (Now you have a glimpse of how much X11 sucks.) +void wm_import_incomplete(struct wm *wm, xcb_window_t wid, xcb_window_t parent); + +/// Check if there are any incomplete imports in the window tree. +bool wm_has_incomplete_imports(const struct wm *wm); + +/// Check if there are tree change events +bool wm_has_tree_changes(const struct wm *wm); + +/// Complete the previous incomplete imports by querying the X server. This function will +/// recursively import all children of previously created placeholders, and add them to +/// the window tree. This function must be called from the X critical section. This +/// function also subscribes to SubstructureNotify events for all newly imported windows, +/// as well as for the (now completed) placeholder windows. +void wm_complete_import(struct wm *wm, struct x_connection *c, struct atom *atoms); + +bool wm_is_wid_masked(struct wm *wm, xcb_window_t wid); + +struct wm_change wm_dequeue_change(struct wm *wm); diff --git a/src/wm/wm_internal.h b/src/wm/wm_internal.h index 7322ec2a83..8ea95463e9 100644 --- a/src/wm/wm_internal.h +++ b/src/wm/wm_internal.h @@ -21,22 +21,6 @@ struct wm_tree { struct list_node free_changes; }; -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_tree_node { UT_hash_handle hh; @@ -84,19 +68,30 @@ struct wm_tree_change { /// 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 *wm_tree_find(struct wm_tree *tree, xcb_window_t id); +struct wm_tree_node *wm_tree_find(const struct wm_tree *tree, xcb_window_t id); +struct wm_tree_node *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, struct wm_tree_node *parent); +void wm_tree_destroy_window(struct wm_tree *tree, struct wm_tree_node *node); struct wm_tree_node *wm_tree_find_toplevel_for(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. void wm_tree_detach(struct wm_tree *tree, struct wm_tree_node *subroot); +void wm_tree_reparent(struct wm_tree *tree, struct wm_tree_node *node, + struct wm_tree_node *new_parent); +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; list_init_head(&tree->changes); list_init_head(&tree->free_changes); } - -static inline bool wm_treeid_eq(wm_treeid a, wm_treeid b) { - return a.gen == b.gen && a.x == b.x; -} From 518f63b9200759a46bc8f64b891ffb52451e5275 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 19 Jun 2024 09:25:57 +0100 Subject: [PATCH 05/56] core: replace window table and window stack with wm_tree Because wm_tree tracks the entire window tree, it's able to replace several old data structures we used to track windows: the window hash table, the window stack, and the subwin table. So we got rid of those, and fully switched to wm_tree. Fixes window rules for window managers that don't put client window directly under toplevel windows. This includes, according to people's reports, at least i3 and KDE. Fixed a couple small bugs: * dbus was returning window ID as a boolean. * window frame extents not cleared after its client window disappears. Signed-off-by: Yuxuan Shui --- include/picom/backend.h | 2 +- src/c2.c | 60 ++- src/c2.h | 10 +- src/dbus.c | 147 +++--- src/event.c | 436 ++++++----------- src/inspect.c | 31 +- src/opengl.c | 15 +- src/opengl.h | 8 +- src/picom.c | 289 +++++------ src/render.c | 23 +- src/render.h | 6 +- src/renderer/command_builder.c | 2 +- src/renderer/damage.c | 16 +- src/renderer/layout.c | 23 +- src/renderer/layout.h | 17 +- src/renderer/renderer.c | 12 +- src/wm/tree.c | 2 +- src/wm/win.c | 871 ++++++++++++--------------------- src/wm/win.h | 200 ++++---- src/wm/wm.c | 398 ++++----------- src/wm/wm.h | 85 ++-- src/wm/wm_internal.h | 18 +- 22 files changed, 1040 insertions(+), 1631 deletions(-) diff --git a/include/picom/backend.h b/include/picom/backend.h index 74e6f1e4af..e087894f1b 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/src/c2.c b/src/c2.c index a883a284f8..b8891b7006 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(); + wm_import_incomplete(wm, 1, XCB_NONE); + + struct win test_win = { .name = "xterm", + .tree_ref = wm_find(wm, 1), }; TEST_TRUE(c2_match(state, &test_win, cond, NULL)); c2_list_postprocess(state, NULL, cond); @@ -563,6 +567,8 @@ 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(wm); } #define c2_error(format, ...) \ @@ -1562,8 +1568,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 +1593,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 +1601,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 +1614,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 +1676,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 +1702,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 +1758,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 +1797,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 +1808,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 +1827,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 +1839,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 +1859,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 +1875,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/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 9b0da675cb..5858e2d3d9 100644 --- a/src/event.c +++ b/src/event.c @@ -74,11 +74,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; } @@ -204,154 +205,124 @@ static inline void ev_focus_out(session_t *ps, xcb_focus_out_event_t *ev) { static inline void ev_create_notify(session_t *ps, xcb_create_notify_event_t *ev) { wm_import_incomplete(ps->wm, ev->window, ev->parent); - - if (ev->parent == ps->c.screen_info->root) { - wm_stack_add_top(ps->wm, ev->window); - ps->pending_updates = true; - 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. - 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; - } } /// 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); + auto cursor = wm_find(ps->wm, ce->window); + auto below = wm_find(ps->wm, ce->above_sibling); - if (!w) { + if (!cursor) { + log_error("Configure event received for unknown window %#010x", ce->window); 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); + } 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); + win_update_monitor(&ps->monitors, w); } // 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 (ev->window == ps->c.screen_info->root) { set_root_flags(ps, ROOT_FLAGS_CONFIGURED); - } else { + } else if (!wm_is_wid_masked(ps->wm, ev->event)) { configure_win(ps, ev); } } static inline void ev_destroy_notify(session_t *ps, xcb_destroy_notify_event_t *ev) { + log_debug("{ event: %#010x, id: %#010x }", ev->event, ev->window); wm_destroy(ps->wm, ev->window); - - 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); } 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) { + if (wm_is_wid_masked(ps->wm, ev->event)) { + return; + } + + auto cursor = wm_find(ps->wm, ev->window); + if (cursor == NULL) { + log_error("Map event received for unknown window %#010x, overlay is " + "%#010x", + ev->window, ps->overlay); 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 @@ -367,8 +338,21 @@ 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) { + if (ps->overlay && ev->window == ps->overlay) { + return; + } + + if (wm_is_wid_masked(ps->wm, ev->event)) { + return; + } + + auto cursor = wm_find(ps->wm, ev->window); + if (cursor == NULL) { + log_error("Unmap event received for unknown window %#010x", ev->window); + return; + } + auto w = wm_ref_deref(cursor); + if (w != NULL) { unmap_win_start(w); } } @@ -377,135 +361,28 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t 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); +} - 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. +static inline void ev_circulate_notify(session_t *ps, xcb_circulate_notify_event_t *ev) { + if (wm_is_wid_masked(ps->wm, ev->event)) { 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)); - } - } - return; - } + auto cursor = wm_find(ps->wm, ev->window); - 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; - } - } - - 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_circulate_notify(session_t *ps, xcb_circulate_notify_event_t *ev) { - auto w = wm_find(ps->wm, ev->window); - - if (!w) { + if (cursor == NULL) { + log_error("Circulate event received for unknown window %#010x", ev->window); 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); } } @@ -536,50 +413,6 @@ 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) { if (unlikely(log_get_level_tls() <= LOG_LEVEL_TRACE)) { // Print out changed atom @@ -611,10 +444,31 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t return; } + if (wm_is_wid_masked(ps->wm, ev->window)) { + return; + } + 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) { + log_error("Property notify received for unknown window %#010x", ev->window); + return; + } + + auto toplevel_cursor = wm_ref_toplevel_of(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); + } + + // 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) { @@ -622,41 +476,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)); @@ -681,8 +528,8 @@ 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); @@ -694,7 +541,7 @@ static inline void repair_win(session_t *ps, struct managed_win *w) { 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; @@ -721,24 +568,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; } diff --git a/src/inspect.c b/src/inspect.c index 860da38b5d..6cc0f42d2e 100644 --- a/src/inspect.c +++ b/src/inspect.c @@ -23,22 +23,32 @@ #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) { // Pretend we are the compositor, and build up the window state - struct managed_win *w = ccalloc(1, struct managed_win); + struct wm *wm = wm_new(); + wm_import_incomplete(wm, target, XCB_NONE); + wm_complete_import(wm, c, atoms); + + auto cursor = wm_find(wm, target); + if (cursor == NULL) { + log_fatal("Could not find window %#010x", target); + wm_free(wm); + return NULL; + } + + 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); 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, @@ -68,17 +78,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; } @@ -140,7 +151,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; }; 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/picom.c b/src/picom.c index 2ac875bb19..b1f94b6d47 100644 --- a/src/picom.c +++ b/src/picom.c @@ -460,7 +460,8 @@ 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); + 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) { @@ -501,36 +502,20 @@ static void recheck_focus(session_t *ps) { 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 cursor = wm_find(ps->wm, wid); + if (cursor == NULL) { + log_error("Window %#010x not found in window tree.", wid); + return; } - auto w = wm_find_managed(ps->wm, wid); + cursor = wm_ref_toplevel_of(cursor); // And we set the focus state here + auto w = wm_ref_deref(cursor); if (w) { log_debug("%#010" PRIx32 " (%#010" PRIx32 " \"%s\") focused.", wid, - w->base.id, w->name); + win_id(w), w->name); win_set_focused(ps, w); - } else { - log_warn("Focus window %#010" PRIx32 " not found.", wid); } } @@ -543,7 +528,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 +558,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 +629,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 +676,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,6 +709,18 @@ 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) { // TODO(yshui) re-initializing backend should be done outside of the @@ -760,19 +756,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) { @@ -829,17 +822,22 @@ static void handle_root_flags(session_t *ps) { * * @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; @@ -887,8 +885,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; @@ -902,7 +905,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 @@ -912,8 +915,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) { @@ -954,7 +957,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) { @@ -1007,11 +1010,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, @@ -1027,7 +1025,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; } @@ -1574,42 +1572,69 @@ static void handle_queued_x_events(EV_P attr_unused, ev_prepare *w, int revents static void handle_new_windows(session_t *ps) { wm_complete_import(ps->wm, &ps->c, ps->atoms); - 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; + // 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: + w = win_maybe_allocate(ps, wm_change.toplevel); + if (w != NULL && w->a.map_state == XCB_MAP_STATE_VIEWABLE) { + win_map_start(w); } - - assert(new_w->managed); - wm_stack_replace(ps->wm, w, new_w); - - auto mw = (struct managed_win *)new_w; - if (mw->a.map_state == XCB_MAP_STATE_VIEWABLE) { - win_set_flags(mw, WIN_FLAGS_MAPPED); - - // 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; + break; + case WM_TREE_CHANGE_TOPLEVEL_KILLED: + w = wm_ref_deref(wm_change.toplevel); + if (w != NULL) { + // Pointing the window tree_ref to the zombie. + w->tree_ref = wm_change.toplevel; + 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); + } } } @@ -1641,30 +1666,6 @@ static void handle_pending_updates(EV_P_ struct session *ps, double delta_t) { // Process new windows, and maybe allocate struct managed_win for them handle_new_windows(ps); - 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: - log_debug("New window %#010x", - wm_ref_win_id(wm_change.toplevel)); - break; - case WM_TREE_CHANGE_TOPLEVEL_KILLED: - log_debug("Destroying window %#010x", - wm_ref_win_id(wm_change.toplevel)); - break; - case WM_TREE_CHANGE_CLIENT: - log_debug("Client message 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); - break; - default: break; - } - } - // 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 @@ -1690,17 +1691,19 @@ static void handle_pending_updates(EV_P_ struct session *ps, double delta_t) { 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); } } } @@ -1765,23 +1768,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(w); + + auto win = wm_ref_deref(w); + if (win != NULL) { + add_damage_from_win(ps, win); + } else { + force_repaint(ps); + } } else { force_repaint(ps); } @@ -1792,7 +1806,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); @@ -2518,8 +2532,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, // think there still could be race condition that mandates discarding the events. x_discard_events(&ps->c); - 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); + wm_complete_import(ps->wm, &ps->c, ps->atoms); e = xcb_request_check(ps->c.c, xcb_ungrab_server_checked(ps->c.c)); if (e) { @@ -2530,22 +2543,9 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ps->server_grabbed = false; - if (query_tree_reply) { - xcb_window_t *children; - int nchildren; - - children = xcb_query_tree_children(query_tree_reply); - nchildren = xcb_query_tree_children_length(query_tree_reply); - - for (int i = 0; i < nchildren; i++) { - wm_stack_add_above(ps->wm, children[i], i ? children[i - 1] : XCB_NONE); - } - 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); + wm_stack_foreach(ps->wm, i) { + log_debug(" %#010x", wm_ref_win_id(i)); } ps->command_builder = command_builder_new(); @@ -2598,8 +2598,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 @@ -2706,7 +2709,7 @@ 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); + wm_free(ps->wm); free_x_connection(&ps->c); } diff --git a/src/render.c b/src/render.c index e2719ccf19..990ed7c459 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)); + 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 " 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 b1fe0d3ba8..ae3250c453 100644 --- a/src/renderer/command_builder.c +++ b/src/renderer/command_builder.c @@ -110,7 +110,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 diff --git a/src/renderer/damage.c b/src/renderer/damage.c index e154685cd4..e5986a6888 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. @@ -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..f40953129e 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,7 +39,7 @@ 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; @@ -108,8 +108,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 +180,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 +201,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 54dfa44069..445e35b9ca 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); } @@ -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); } diff --git a/src/wm/tree.c b/src/wm/tree.c index 3aa6a3aaf3..3c4c493234 100644 --- a/src/wm/tree.c +++ b/src/wm/tree.c @@ -324,6 +324,7 @@ void wm_tree_destroy_window(struct wm_tree *tree, struct wm_tree_node *node) { "malfunction.", node->id.x); list_foreach_safe(struct wm_tree_node, i, &node->children, siblings) { + log_error(" Child window %#010x", i->id.x); wm_tree_destroy_window(tree, i); } } @@ -341,7 +342,6 @@ void wm_tree_destroy_window(struct wm_tree *tree, struct wm_tree_node *node) { } } -/// 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) { BUG_ON(node == NULL); BUG_ON(node->parent == NULL); // Trying to move the root window diff --git a/src/wm/win.c b/src/wm/win.c index 88cab3e05f..cb7c152399 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,8 +308,8 @@ 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); +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); assert(w->win_image); if (w->win_image) { xcb_pixmap_t pixmap = XCB_NONE; @@ -331,8 +322,8 @@ static inline void win_release_pixmap(backend_t *base, struct managed_win *w) { } } } -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 +335,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,18 +346,18 @@ 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) { +static inline bool win_bind_pixmap(struct backend_base *b, struct 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)); + b->c->c, xcb_composite_name_window_pixmap_checked(b->c->c, win_id(w), pixmap)); if (e) { - log_error("Failed to get named pixmap for window %#010x(%s)", w->base.id, + log_error("Failed to get named pixmap for window %#010x(%s)", win_id(w), w->name); free(e); return false; } - log_debug("New named pixmap for %#010x (%s) : %#010x", w->base.id, w->name, pixmap); + log_debug("New named pixmap for %#010x (%s) : %#010x", win_id(w), 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"); @@ -379,7 +370,7 @@ static inline bool win_bind_pixmap(struct backend_base *b, struct managed_win *w 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. @@ -395,10 +386,10 @@ void win_release_images(struct backend_base *backend, struct managed_win *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 +397,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 +412,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); } @@ -459,7 +450,8 @@ 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, + 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 +463,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 +480,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 @@ -578,7 +560,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)); @@ -623,7 +605,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 +638,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 +663,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 +686,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 +743,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; @@ -808,7 +783,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; } @@ -829,7 +804,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) { @@ -864,7 +839,7 @@ 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 @@ -892,7 +867,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; @@ -909,10 +884,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; @@ -923,12 +897,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 @@ -974,8 +948,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) { @@ -1005,8 +979,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; @@ -1016,8 +989,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) { @@ -1032,13 +1006,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; } @@ -1051,7 +1025,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) { @@ -1066,7 +1040,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); @@ -1079,14 +1053,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); @@ -1097,7 +1071,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); @@ -1105,8 +1079,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; } @@ -1119,8 +1092,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; } @@ -1135,8 +1107,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; } @@ -1159,7 +1131,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); @@ -1176,7 +1148,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; @@ -1184,7 +1156,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; @@ -1194,7 +1166,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; } @@ -1216,7 +1188,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; } @@ -1238,9 +1210,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); @@ -1271,7 +1244,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; } } @@ -1279,9 +1253,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); @@ -1300,36 +1274,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() @@ -1339,12 +1315,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) { @@ -1360,11 +1337,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; } @@ -1372,91 +1346,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 @@ -1483,31 +1383,16 @@ 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) { + static const struct win win_def = { .frame_opacity = 1.0, - .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 + .in_openclose = true, // set to false after first map is done, + // true here because window is just created .flags = WIN_FLAGS_PIXMAP_NONE, // updated by // property/attributes/etc // change - .stale_props = NULL, - .stale_props_capacity = 0, // Runtime variables, updated by dbus .fade_force = UNSET, @@ -1515,86 +1400,30 @@ 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_window_t wid = wm_ref_win_id(cursor); + log_debug("Managing window %#010x", wid); + xcb_get_window_attributes_cookie_t acookie = xcb_get_window_attributes(ps->c.c, wid); 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) { @@ -1604,27 +1433,23 @@ struct win *attr_ret_nonnull maybe_allocate_managed_win(session_t *ps, struct wi // 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) { // 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->shadow_opacity = ps->o.shadow_opacity; pixman_region32_init(&new->bounding_shape); @@ -1632,12 +1457,12 @@ struct win *attr_ret_nonnull maybe_allocate_managed_win(session_t *ps, struct wi 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_error_x_error(e, "Failed to get geometry of window %#010x", wid); free(e); free(new); - return w; + return NULL; } new->pending_g = (struct win_geometry){ .x = g->x, @@ -1652,13 +1477,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"); free(e); free(new); - return w; + return NULL; } // Set window event mask @@ -1667,33 +1492,22 @@ 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, + xcb_change_window_attributes(ps->c.c, wid, 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); - } - // Get notification when the shape of a window changes if (ps->shape_exists) { - xcb_shape_select_input(ps->c.c, new->base.id, 1); + 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, @@ -1706,7 +1520,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; } /** @@ -1733,18 +1549,23 @@ win_get_leader_property(struct x_connection *c, struct atom *atoms, xcb_window_t /** * 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) { + 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(w->cache_leader); + auto wp = parent ? wm_ref_deref(parent) : NULL; if (wp) { // Dead loop? if (recursions > WIN_GET_LEADER_MAX_RECURSION) { @@ -1763,14 +1584,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); @@ -1779,7 +1596,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; } @@ -1792,9 +1609,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; } @@ -1802,15 +1618,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); @@ -1831,9 +1647,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); } } } @@ -1841,7 +1657,7 @@ 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; @@ -1870,7 +1686,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); @@ -1889,8 +1705,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); @@ -1900,7 +1716,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 @@ -1912,7 +1728,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) { @@ -1952,33 +1768,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); @@ -2012,18 +1832,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; } } @@ -2032,131 +1853,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); @@ -2175,8 +1955,7 @@ 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 = (w->randr_monitor >= 0 && w->randr_monitor < ps->monitors.count) ? *pixman_region32_extents(&ps->monitors.regions[w->randr_monitor]) @@ -2197,7 +1976,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]]; } @@ -2223,8 +2002,7 @@ 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 @@ -2246,7 +2024,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; @@ -2281,13 +2059,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; } } @@ -2328,7 +2106,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; } @@ -2337,7 +2115,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) { @@ -2355,7 +2133,7 @@ bool win_process_animation_and_state_change(struct session *ps, struct managed_w // 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) { +void win_update_monitor(struct x_monitors *monitors, struct win *mw) { mw->randr_monitor = -1; for (int i = 0; i < monitors->count; i++) { auto e = pixman_region32_extents(&monitors->regions[i]); @@ -2364,18 +2142,18 @@ void win_update_monitor(struct x_monitors *monitors, struct managed_win *mw) { 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; } } 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); } /// 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 @@ -2384,7 +2162,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) { @@ -2406,17 +2184,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; } @@ -2424,19 +2200,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; @@ -2466,12 +2242,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; @@ -2483,11 +2259,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; } @@ -2496,7 +2272,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; @@ -2512,11 +2288,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; @@ -2530,6 +2307,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) { +bool win_is_focused_raw(const struct win *w) { return w->a.map_state == XCB_MAP_STATE_VIEWABLE && w->is_ewmh_focused; } diff --git a/src/wm/win.h b/src/wm/win.h index 284e40c7a9..d82a0fb90b 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; @@ -184,14 +169,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. @@ -357,47 +340,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 *base, 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); +void win_update_monitor(struct x_monitors *monitors, 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. @@ -405,114 +387,112 @@ 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); /** * 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}, @@ -535,12 +515,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); @@ -548,8 +527,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); @@ -562,7 +540,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); @@ -574,7 +552,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 e2b6fa2b41..413df84d89 100644 --- a/src/wm/wm.c +++ b/src/wm/wm.c @@ -9,7 +9,6 @@ #include "log.h" #include "utils/dynarr.h" #include "utils/list.h" -#include "utils/uthash_extra.h" #include "x.h" #include "win.h" @@ -17,32 +16,15 @@ #include "wm_internal.h" 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; struct wm_tree_node *root; @@ -55,14 +37,6 @@ struct wm { struct wm_tree_node **masked; }; -unsigned int wm_get_window_count(struct wm *wm) { - unsigned int count = 0; - HASH_ITER2(wm->windows, w) { - assert(!w->destroyed); - ++count; - } - return count; -} // 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. @@ -81,12 +55,35 @@ static inline const struct wm_tree_node *to_tree_node(const struct wm_ref *curso : 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; } -struct managed_win *wm_active_win(struct wm *wm) { - return wm->active_win; +wm_treeid wm_ref_treeid(const struct wm_ref *cursor) { + return to_tree_node(cursor)->id; +} + +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 node->win; +} + +void wm_ref_set(struct wm_ref *cursor, struct win *w) { + to_tree_node_mut(cursor)->win = w; } static ptrdiff_t wm_find_masked(struct wm *wm, xcb_window_t wid) { @@ -98,315 +95,117 @@ static ptrdiff_t wm_find_masked(struct wm *wm, xcb_window_t wid) { return -1; } -void wm_set_active_win(struct wm *wm, struct managed_win *w) { - wm->active_win = w; +bool wm_is_wid_masked(struct wm *wm, xcb_window_t wid) { + return wm_find_masked(wm, wid) != -1; } -xcb_window_t wm_active_leader(struct wm *wm) { - return wm->active_leader; +struct win *wm_active_win(struct wm *wm) { + return wm->active_win; } -void wm_set_active_leader(struct wm *wm, xcb_window_t leader) { - wm->active_leader = leader; +void wm_set_active_win(struct wm *wm, struct win *w) { + wm->active_win = w; } -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; +struct wm_ref *wm_active_leader(struct wm *wm) { + return wm->active_leader != NULL ? (struct wm_ref *)&wm->active_leader->siblings : NULL; } -// 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; +void wm_set_active_leader(struct wm *wm, struct wm_ref *leader) { + wm->active_leader = to_tree_node_mut(leader); } -/// 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) { - return NULL; - } - - struct win *w = NULL; - HASH_FIND_INT(wm->windows, &id, w); - assert(w == NULL || !w->destroyed); - return w; +bool wm_ref_is_zombie(const struct wm_ref *cursor) { + return to_tree_node(cursor)->is_zombie; } -void wm_remove(struct wm *wm, struct win *w) { - wm->generation++; - HASH_DEL(wm->windows, w); +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; } -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; - } - } - 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); - } - // 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); +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; } -/// 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; - } - - 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); - } - } - - list_move_before(&w->stack_neighbour, next); - -#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) "; - } - log_trace("%#010x \"%s\" %s", c->id, c->name, desc); - } -#endif +struct wm_ref *wm_root_ref(const struct wm *wm) { + return (struct wm_ref *)&wm->root->siblings; } -struct list_node *wm_stack_end(struct wm *wm) { - return &wm->window_stack; +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; } -/// 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; - - 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("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; - } - - new_next = &tmp_w->stack_neighbour; - } - wm_stack_move_before(wm, w, new_next); - } +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; } -void wm_stack_move_to_top(struct wm *wm, struct win *w) { - if (&w->stack_neighbour == wm->window_stack.next) { - // already at top - return; - } - wm_stack_move_before(wm, w, wm->window_stack.next); +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; } -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; - } - } - return count; +struct wm_ref *wm_find_by_client(const struct wm *wm, xcb_window_t client) { + auto node = wm_tree_find(&wm->tree, client); + return node != NULL ? (struct wm_ref *)&wm_tree_find_toplevel_for(node)->siblings + : NULL; } -struct managed_win *wm_find_by_client(struct wm *wm, xcb_window_t client) { - if (!client) { - return NULL; - } - - HASH_ITER2(wm->windows, w) { - assert(!w->destroyed); - if (!w->managed) { - continue; - } - - auto mw = (struct managed_win *)w; - if (mw->client_win == client) { - return mw; - } - } - - return NULL; +struct wm_ref *wm_ref_toplevel_of(struct wm_ref *cursor) { + return (struct wm_ref *)&wm_tree_find_toplevel_for(to_tree_node_mut(cursor))->siblings; } -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; - } - - auto mw = (struct managed_win *)w; - assert(mw->state != WSTATE_DESTROYED); - return mw; +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; } -unsigned wm_num_windows(const struct wm *wm) { - return HASH_COUNT(wm->windows); +void wm_remove(struct wm *wm, struct wm_ref *w) { + wm_tree_destroy_window(&wm->tree, (struct wm_tree_node *)w); } -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); - - 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; +struct wm_ref *wm_stack_end(struct wm *wm) { + return (struct wm_ref *)&wm->root->children; } -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; -} - -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); +/// 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) { + wm_tree_move_to_above(&wm->tree, to_tree_node_mut(cursor), to_tree_node_mut(below)); } -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_stack_move_to_end(struct wm *wm, struct wm_ref *cursor, bool to_bottom) { + wm_tree_move_to_end(&wm->tree, to_tree_node_mut(cursor), to_bottom); } struct wm *wm_new(void) { auto wm = ccalloc(1, struct wm); - list_init_head(&wm->window_stack); - wm->generation = 1; wm_tree_init(&wm->tree); wm->incompletes = dynarr_new(struct wm_tree_node *, 4); wm->masked = dynarr_new(struct wm_tree_node *, 8); return wm; } -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); - } +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). + wm_stack_foreach_safe(wm, i, next) { + auto w = wm_ref_deref(i); + auto tree_node = to_tree_node_mut(i); free(w); - } - list_init_head(&wm->window_stack); - struct subwin *subwin, *next_subwin; - HASH_ITER(hh, wm->subwins, subwin, next_subwin) { - wm_subwin_remove_and_unsubscribe(wm, c, subwin); + 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); dynarr_free_pod(wm->incompletes); @@ -439,6 +238,10 @@ void wm_destroy(struct wm *wm, xcb_window_t wid) { wm_tree_destroy_window(&wm->tree, node); } +void wm_reap_zombie(struct wm_ref *zombie) { + wm_tree_reap_zombie(to_tree_node_mut(zombie)); +} + 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); @@ -486,6 +289,10 @@ void wm_reparent(struct wm *wm, xcb_window_t wid, xcb_window_t parent) { wm_tree_reparent(&wm->tree, window, new_parent); } +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); +} + void wm_import_incomplete(struct wm *wm, xcb_window_t wid, xcb_window_t parent) { auto masked = wm_find_masked(wm, wid); if (masked != -1) { @@ -532,12 +339,9 @@ static void wm_complete_import_subtree(struct wm *wm, struct x_connection *c, wm_complete_import_single(wm, c, atoms, subroot); for (auto curr = subroot; curr != NULL; curr = wm_tree_next(curr, subroot)) { - if (!list_is_empty(&curr->children)) { - log_error("Newly imported subtree root at %#010x already has " - "children. " - "Expect malfunction.", - curr->id.x); - } + // Surprise! This newly imported window might already have children. + // Although we haven't setup SubstructureNotify for it yet, it's still + // possible for another window to be reparented to it. xcb_query_tree_reply_t *tree = XCB_AWAIT(xcb_query_tree, c->c, curr->id.x); if (!tree) { diff --git a/src/wm/wm.h b/src/wm/wm.h index f9520dc598..ededf7355b 100644 --- a/src/wm/wm.h +++ b/src/wm/wm.h @@ -23,7 +23,7 @@ #include "compiler.h" struct wm; -struct managed_win; +struct win; struct list_node; struct x_connection; @@ -99,12 +99,12 @@ static inline bool wm_treeid_eq(wm_treeid a, wm_treeid b) { } 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 @@ -114,64 +114,45 @@ void wm_set_active_leader(struct wm *wm, xcb_window_t leader); // 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); +struct wm_ref *attr_pure wm_find(const 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); +void wm_remove(struct wm *wm, struct wm_ref *w); // 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(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); /// Create a tree node for `wid`, with `parent` as its parent. The parent must already /// be in the window tree. This function creates a placeholder tree node, without diff --git a/src/wm/wm_internal.h b/src/wm/wm_internal.h index 8ea95463e9..9e33808a9b 100644 --- a/src/wm/wm_internal.h +++ b/src/wm/wm_internal.h @@ -13,6 +13,14 @@ 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; @@ -68,8 +76,10 @@ struct wm_tree_change { /// 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 *wm_tree_find(const struct wm_tree *tree, xcb_window_t id); -struct wm_tree_node *wm_tree_next(struct wm_tree_node *node, struct wm_tree_node *subroot); +struct wm_tree_node *attr_pure wm_tree_find(const struct wm_tree *tree, xcb_window_t id); +struct wm_tree_node *attr_pure wm_tree_find_toplevel_for(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 @@ -78,13 +88,15 @@ struct wm_tree_node *wm_tree_next(struct wm_tree_node *node, struct wm_tree_node struct wm_tree_node * wm_tree_new_window(struct wm_tree *tree, xcb_window_t id, struct wm_tree_node *parent); void wm_tree_destroy_window(struct wm_tree *tree, struct wm_tree_node *node); -struct wm_tree_node *wm_tree_find_toplevel_for(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. void wm_tree_detach(struct wm_tree *tree, struct wm_tree_node *subroot); void wm_tree_reparent(struct wm_tree *tree, struct wm_tree_node *node, struct wm_tree_node *new_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); From b1d5373446143a977dc9add353926c499e9d3134 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 6 Jun 2024 22:21:53 +0100 Subject: [PATCH 06/56] inspect: make it work again after the wm-tree changes Signed-off-by: Yuxuan Shui --- src/inspect.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/inspect.c b/src/inspect.c index 6cc0f42d2e..4e8d357991 100644 --- a/src/inspect.c +++ b/src/inspect.c @@ -25,12 +25,8 @@ 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 wm *wm = wm_new(); - wm_import_incomplete(wm, target, XCB_NONE); - wm_complete_import(wm, c, atoms); - auto cursor = wm_find(wm, target); if (cursor == NULL) { log_fatal("Could not find window %#010x", target); @@ -38,8 +34,10 @@ setup_window(struct x_connection *c, struct atom *atoms, struct options *options return NULL; } + auto toplevel = wm_ref_toplevel_of(cursor); struct win *w = ccalloc(1, struct win); w->state = WSTATE_MAPPED; + w->tree_ref = toplevel; win_update_wintype(c, atoms, w); win_update_frame_extents(c, atoms, w, win_client_id(w, /*fallback_to_self=*/true), options->frame_opacity); @@ -199,9 +197,14 @@ int inspect_main(int argc, char **argv, const char *config_file) { auto state = c2_state_new(atoms); options_postprocess_c2_lists(state, &c, &options); + struct wm *wm = wm_new(); + + wm_import_incomplete(wm, c.screen_info->root, XCB_NONE); + wm_complete_import(wm, &c, atoms); + 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, @@ -263,6 +266,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); From d0ee47fd2382245a96e53f38fc717a2e92aa6adf Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Fri, 21 Jun 2024 09:57:04 +0100 Subject: [PATCH 07/56] event: ignore configure events for the overlay window Signed-off-by: Yuxuan Shui --- src/event.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/event.c b/src/event.c index 5858e2d3d9..b55f7a1e2d 100644 --- a/src/event.c +++ b/src/event.c @@ -279,6 +279,11 @@ static void configure_win(session_t *ps, xcb_configure_notify_event_t *ce) { static inline void ev_configure_notify(session_t *ps, xcb_configure_notify_event_t *ev) { 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); } else if (!wm_is_wid_masked(ps->wm, ev->event)) { From 5ec2c873dfd53d1c5bea1b05b49d9662c3faf09a Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Fri, 21 Jun 2024 12:48:37 +0100 Subject: [PATCH 08/56] backend/gl: reuse buffer and vertex array objects Mainly to workaround a mesa performance regression that causes object deallocations to be quadratic. But it's also just good to not have so many object allocations/deallocations. Signed-off-by: Yuxuan Shui --- src/backend/gl/blur.c | 49 ++++++++++++++++++------------ src/backend/gl/gl_common.c | 62 +++++++++++++++++++++----------------- src/backend/gl/gl_common.h | 2 ++ 3 files changed, 66 insertions(+), 47 deletions(-) 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 7b3ed1b28a..7b6b8c953c 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(); @@ -636,7 +636,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); @@ -696,7 +696,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); @@ -881,6 +881,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); @@ -1039,6 +1042,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(); @@ -1134,7 +1140,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 86c850907b..942b34b306 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -99,6 +99,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; From e068dc8a73cbf9175d8ac11f3ff6f12cd769d4d6 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Fri, 21 Jun 2024 13:33:58 +0100 Subject: [PATCH 09/56] region: fix region scaling We claim region_scale_floor returns the largest integer region contained by the scaling result. But in fact what it's doing is taking the floor of each rectangle of the region separated, which leaves gaps between rectangles. This is not what we want. Just always take the ceil instead, hopefully the difference is small enough to be unnoticeable. Signed-off-by: Yuxuan Shui --- src/backend/xrender/xrender.c | 2 +- src/region.h | 52 ++++++++++++++-------------------- src/renderer/command_builder.c | 8 +++--- src/renderer/damage.c | 2 +- 4 files changed, 27 insertions(+), 37 deletions(-) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index a8551f1841..fa38e09f86 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -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/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/renderer/command_builder.c b/src/renderer/command_builder.c index ae3250c453..b4228cd435 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; @@ -77,7 +77,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; @@ -179,7 +179,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 e5986a6888..48c650d5fd 100644 --- a/src/renderer/damage.c +++ b/src/renderer/damage.c @@ -97,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); From 399d58ce925a8e8f6dd900331a3897a15b07c975 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Fri, 21 Jun 2024 16:39:46 +0100 Subject: [PATCH 10/56] core: try actually to catch up with the X server The intention of `handle_pending_updates` has always been: locking the X server then read all events from it, so we can catch up with the server. What I failed to notice is that, `handle_pending_x_events` doesn't actually read all events from X! It only processes what were already in libxcb's queue. Which results in we being out-of-sync with X. This commit makes sure we actually do read all events from X. Signed-off-by: Yuxuan Shui --- src/picom.c | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/src/picom.c b/src/picom.c index b1f94b6d47..2b0f7bcecf 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1569,6 +1569,41 @@ static void handle_queued_x_events(EV_P attr_unused, ev_prepare *w, int revents } } +/// Handle all events X server has sent us so far, so that our internal state will catch +/// up with the X server's state. It only makes sense to call this function in the X +/// critical section, otherwise we will be chasing a moving goal post. +/// +/// (Disappointingly, grabbing the X server doesn't actually prevent the X server state +/// from changing. It should, but in practice we have observed window still being +/// destroyed while we have the server grabbed. This is very disappointing and forces us +/// to use some hacky code to recover when we discover we are out-of-sync.) +/// +/// This function is different from `handle_queued_x_events` in that this will keep +/// reading from the X server until there is nothing left. Whereas +/// `handle_queued_x_events` is only concerned with emptying the internal buffer of +/// libxcb, so we don't go to sleep with unprocessed data. +static void handle_all_x_events(struct session *ps) { + assert(ps->server_grabbed); + + 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_event(ps->c.c))) { + ev_handle(ps, ev); + free(ev); + }; + int err = xcb_connection_has_error(ps->c.c); + if (err) { + log_fatal("X11 server connection broke (error %d)", err); + exit(1); + } +} + static void handle_new_windows(session_t *ps) { wm_complete_import(ps->wm, &ps->c, ps->atoms); @@ -1647,7 +1682,7 @@ 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) { +static void handle_pending_updates(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) { @@ -1659,7 +1694,7 @@ static void handle_pending_updates(EV_P_ struct session *ps, double delta_t) { ps->server_grabbed = true; // Catching up with X server - handle_queued_x_events(EV_A, &ps->event_check, 0); + handle_all_x_events(ps); if (ps->pending_updates || wm_has_incomplete_imports(ps->wm) || wm_has_tree_changes(ps->wm)) { log_debug("Delayed handling of events, entering critical section"); @@ -1748,7 +1783,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); From 452aa751875bc86799895c941e121b1f0041b0af Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Fri, 21 Jun 2024 18:48:19 +0100 Subject: [PATCH 11/56] core: try to recover when we become out-of-sync with the X server Due to unknown reasons (X server bug?), we observed windows being destroyed while we had the X server grabbed. In theory, grabbing the server prevents it from processing requests from any clients that are not us, which means they shouldn't be able to destroy windows. Apparently something weird is going on and that's not actually true. So far we have only saw windows being destroyed, so that's the case we are fixing in this commit. Whenever we encounter a non-existent window in `wm_complete_import`, we pause window importing and again try to process all events the X server has sent us. Which should bring us back into sync. Other state changes might also be happening while we have the X server grabbed. None have been observed so far, so we don't know yet. Signed-off-by: Yuxuan Shui --- src/picom.c | 4 +++- src/wm/wm.c | 36 ++++++++++++++++++++++++------------ src/wm/wm.h | 5 ++++- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/picom.c b/src/picom.c index 2b0f7bcecf..4302b4afc4 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1605,7 +1605,9 @@ static void handle_all_x_events(struct session *ps) { } static void handle_new_windows(session_t *ps) { - wm_complete_import(ps->wm, &ps->c, ps->atoms); + while (!wm_complete_import(ps->wm, &ps->c, ps->atoms)) { + handle_all_x_events(ps); + } // Check tree changes first, because later property updates need accurate // client window information diff --git a/src/wm/wm.c b/src/wm/wm.c index 413df84d89..af2d64911b 100644 --- a/src/wm/wm.c +++ b/src/wm/wm.c @@ -326,16 +326,23 @@ static void wm_complete_import_single(struct wm *wm, struct x_connection *c, struct atom *atoms, struct wm_tree_node *node) { log_debug("Finishing importing window %#010x with parent %#010lx.", node->id.x, node->parent != NULL ? node->parent->id.x : XCB_NONE); - set_cant_fail_cookie( + set_ignore_cookie( c, xcb_change_window_attributes(c->c, node->id.x, XCB_CW_EVENT_MASK, (const uint32_t[]){WM_IMPORT_EV_MASK})); if (wid_has_prop(c->c, node->id.x, atoms->aWM_STATE)) { wm_tree_set_wm_state(&wm->tree, node, true); } + + auto masked = wm_find_masked(wm, node->id.x); + if (masked != -1) { + free(wm->masked[masked]); + dynarr_remove_swap(wm->masked, (size_t)masked); + } } -static void wm_complete_import_subtree(struct wm *wm, struct x_connection *c, +static bool wm_complete_import_subtree(struct wm *wm, struct x_connection *c, struct atom *atoms, struct wm_tree_node *subroot) { + bool out_of_sync = false; wm_complete_import_single(wm, c, atoms, subroot); for (auto curr = subroot; curr != NULL; curr = wm_tree_next(curr, subroot)) { @@ -345,9 +352,10 @@ static void wm_complete_import_subtree(struct wm *wm, struct x_connection *c, xcb_query_tree_reply_t *tree = XCB_AWAIT(xcb_query_tree, c->c, curr->id.x); if (!tree) { - log_error("Disappearing window subtree rooted at %#010x. Expect " - "malfunction.", + log_debug("Disappearing window subtree rooted at %#010x. We are " + "out-of-sync.", curr->id.x); + out_of_sync = true; continue; } @@ -380,21 +388,25 @@ static void wm_complete_import_subtree(struct wm *wm, struct x_connection *c, } free(tree); } + return !out_of_sync; } -void wm_complete_import(struct wm *wm, struct x_connection *c, struct atom *atoms) { - // Unveil the fog of war - dynarr_foreach(wm->masked, m) { - free(*m); - } - dynarr_clear_pod(wm->masked); - +bool wm_complete_import(struct wm *wm, struct x_connection *c, struct atom *atoms) { while (!dynarr_is_empty(wm->incompletes)) { auto i = dynarr_pop(wm->incompletes); // This function modifies `wm->incompletes`, so we can't use // `dynarr_foreach`. - wm_complete_import_subtree(wm, c, atoms, i); + if (!wm_complete_import_subtree(wm, c, atoms, i)) { + log_debug("Out-of-sync with the X server, try to resync."); + return false; + } + } + + dynarr_foreach(wm->masked, m) { + free(*m); } + dynarr_clear_pod(wm->masked); + return true; } bool wm_has_incomplete_imports(const struct wm *wm) { diff --git a/src/wm/wm.h b/src/wm/wm.h index ededf7355b..7c8f58f651 100644 --- a/src/wm/wm.h +++ b/src/wm/wm.h @@ -202,7 +202,10 @@ bool wm_has_tree_changes(const struct wm *wm); /// the window tree. This function must be called from the X critical section. This /// function also subscribes to SubstructureNotify events for all newly imported windows, /// as well as for the (now completed) placeholder windows. -void wm_complete_import(struct wm *wm, struct x_connection *c, struct atom *atoms); +/// +/// Returns false if our internal state is behind the X server's. In other words, if a +/// window we think should be there, does not in fact exist. Returns true otherwise. +bool wm_complete_import(struct wm *wm, struct x_connection *c, struct atom *atoms); bool wm_is_wid_masked(struct wm *wm, xcb_window_t wid); From d261b6fcda7ede7455fbc87735ee7187bed8f18d Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Fri, 21 Jun 2024 20:08:43 +0100 Subject: [PATCH 12/56] event: check for masked window in ev_create_notify If a window is masked, we don't care if it has new children. Silence some spurious error messages. Signed-off-by: Yuxuan Shui --- src/event.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/event.c b/src/event.c index b55f7a1e2d..7c81e06b46 100644 --- a/src/event.c +++ b/src/event.c @@ -204,6 +204,9 @@ static inline void ev_focus_out(session_t *ps, xcb_focus_out_event_t *ev) { } static inline void ev_create_notify(session_t *ps, xcb_create_notify_event_t *ev) { + if (wm_is_wid_masked(ps->wm, ev->parent)) { + return; + } wm_import_incomplete(ps->wm, ev->window, ev->parent); } From 61ec9223d20c2a83c8bd0729276bc0cb0db3bd3c Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sat, 22 Jun 2024 16:26:47 +0100 Subject: [PATCH 13/56] core: fix root window event mask Calling wm_import_incomplete with the root window overwrites its event mask, causing some required events not being generated for us, which causes rendering issues when screen resolution is changed. Set the root event masks after wm_import_incomplete. Fixes #1277 Signed-off-by: Yuxuan Shui --- src/picom.c | 44 +++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/src/picom.c b/src/picom.c index 4302b4afc4..742784c913 100644 --- a/src/picom.c +++ b/src/picom.c @@ -2145,6 +2145,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); @@ -2156,26 +2157,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); @@ -2554,6 +2538,32 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ps->wm = wm_new(); wm_import_incomplete(ps->wm, ps->c.screen_info->root, XCB_NONE); + // wm_import_incomplete 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_error_x_error(e, "Failed to setup root window event mask"); + free(e); + goto err; + } + + // 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); + 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"); From 0dc3e97249c5309a53451aee4ce3c8bb97480406 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sat, 22 Jun 2024 22:50:11 +0100 Subject: [PATCH 14/56] core: root window event mask should be set after wm_complete_import The previous commit had the right idea, but the point where it set root event mask is still too early. Fixes #1277 Signed-off-by: Yuxuan Shui --- src/picom.c | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/picom.c b/src/picom.c index 742784c913..1b79e12746 100644 --- a/src/picom.c +++ b/src/picom.c @@ -2538,32 +2538,6 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ps->wm = wm_new(); wm_import_incomplete(ps->wm, ps->c.screen_info->root, XCB_NONE); - // wm_import_incomplete 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_error_x_error(e, "Failed to setup root window event mask"); - free(e); - goto err; - } - - // 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); - 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"); @@ -2598,6 +2572,32 @@ static session_t *session_init(int argc, char **argv, Display *dpy, 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_error_x_error(e, "Failed to setup root window event mask"); + free(e); + goto err; + } + + // 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); + ps->pending_updates = true; write_pid(ps); From ae73f45ad9e313091cdf720d0f4cdf5b4eb94c1a Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Sat, 22 Jun 2024 22:51:22 +0100 Subject: [PATCH 15/56] wm: make desync with the X server louder Signed-off-by: Yuxuan Shui --- src/wm/wm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wm/wm.c b/src/wm/wm.c index af2d64911b..f36aec8db5 100644 --- a/src/wm/wm.c +++ b/src/wm/wm.c @@ -352,7 +352,7 @@ static bool wm_complete_import_subtree(struct wm *wm, struct x_connection *c, xcb_query_tree_reply_t *tree = XCB_AWAIT(xcb_query_tree, c->c, curr->id.x); if (!tree) { - log_debug("Disappearing window subtree rooted at %#010x. We are " + log_error("Disappearing window subtree rooted at %#010x. We are " "out-of-sync.", curr->id.x); out_of_sync = true; From c8195eff8ff791d8b8a9afc20d3ef7216066b266 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 24 Jun 2024 11:39:39 +0100 Subject: [PATCH 16/56] ci: stop using sr.ht It's sad but this decision is purely practical, as recently we saw a decrease in the reliability of sr.ht. Signed-off-by: Yuxuan Shui --- .builds/freebsd.yml | 28 ----------------------- .github/workflows/freebsd.yml | 21 +++++++++++++++++ .github/workflows/{ci.yml => openbsd.yml} | 0 3 files changed, 21 insertions(+), 28 deletions(-) delete mode 100644 .builds/freebsd.yml create mode 100644 .github/workflows/freebsd.yml rename .github/workflows/{ci.yml => openbsd.yml} (100%) 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 From 2c6560033c7fa0019cf4cc936bb976b4de534ad4 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 24 Jun 2024 08:17:00 +0100 Subject: [PATCH 17/56] x: give set_reply_action functions better names And use list.h for the pending reply actions list. Signed-off-by: Yuxuan Shui --- src/backend/xrender/xrender.c | 8 ++-- src/event.c | 4 +- src/render.c | 4 +- src/renderer/renderer.c | 10 ++--- src/vblank.c | 4 +- src/wm/win.c | 2 +- src/wm/wm.c | 2 +- src/x.c | 41 +++++++++++--------- src/x.h | 73 +++++++++++++++++------------------ 9 files changed, 75 insertions(+), 73 deletions(-) diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index fa38e09f86..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, diff --git a/src/event.c b/src/event.c index 7c81e06b46..0c3df0d965 100644 --- a/src/event.c +++ b/src/event.c @@ -542,7 +542,7 @@ static inline void repair_win(session_t *ps, struct win *w) { 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, @@ -631,7 +631,7 @@ 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); + x_discard_pending_errors(&ps->c, ev->full_sequence); } xcb_window_t wid = ev_window(ps, ev); diff --git a/src/render.c b/src/render.c index 990ed7c459..1e80623ae6 100644 --- a/src/render.c +++ b/src/render.c @@ -396,8 +396,8 @@ 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, win_id(w), 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; diff --git a/src/renderer/renderer.c b/src/renderer/renderer.c index 445e35b9ca..81fa6f4a37 100644 --- a/src/renderer/renderer.c +++ b/src/renderer/renderer.c @@ -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 @@ -568,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/vblank.c b/src/vblank.c index a02619d49f..270f93e2f6 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); } diff --git a/src/wm/win.c b/src/wm/win.c index cb7c152399..6a6a878933 100644 --- a/src/wm/win.c +++ b/src/wm/win.c @@ -1370,7 +1370,7 @@ void free_win_res(session_t *ps, struct 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); diff --git a/src/wm/wm.c b/src/wm/wm.c index f36aec8db5..c8d37156b0 100644 --- a/src/wm/wm.c +++ b/src/wm/wm.c @@ -326,7 +326,7 @@ static void wm_complete_import_single(struct wm *wm, struct x_connection *c, struct atom *atoms, struct wm_tree_node *node) { log_debug("Finishing importing window %#010x with parent %#010lx.", node->id.x, node->parent != NULL ? node->parent->id.x : XCB_NONE); - set_ignore_cookie( + x_set_error_action_ignore( c, xcb_change_window_attributes(c->c, node->id.x, XCB_CW_EVENT_MASK, (const uint32_t[]){WM_IMPORT_EV_MASK})); if (wid_has_prop(c->c, node->id.x, atoms->aWM_STATE)) { diff --git a/src/x.c b/src/x.c index 15e891743e..7eadae1da0 100644 --- a/src/x.c +++ b/src/x.c @@ -50,27 +50,32 @@ 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_discard_pending_errors(struct x_connection *c, uint32_t 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); } } 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_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) { x_log_error(LOG_LEVEL_ERROR, ev->full_sequence, ev->major_code, ev->minor_code, ev->error_code); } - switch (c->pending_reply_head->action) { + switch (first_error_action->action) { case PENDING_REPLY_ACTION_ABORT: - log_fatal("An unrecoverable X error occurred, aborting..."); + log_fatal("An unrecoverable X error occurred, " + "aborting..."); abort(); case PENDING_REPLY_ACTION_DEBUG_ABORT: assert(false); break; case PENDING_REPLY_ACTION_IGNORE: break; @@ -88,7 +93,7 @@ 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); c->previous_xerror_handler = XSetErrorHandler(xerror); c->screen = DefaultScreen(dpy); @@ -411,7 +416,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 +513,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,7 +562,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); + x_set_error_action_debug_abort(c, cookie); } enum { @@ -776,7 +781,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); } /** diff --git a/src/x.h b/src/x.h index d4e01f5a68..b6af8da6a1 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,18 @@ 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; + struct list_node siblings; +}; struct x_connection { // Public fields @@ -68,11 +70,8 @@ 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; /// Previous handler of X errors XErrorHandler previous_xerror_handler; /// Information about the default screen @@ -133,34 +132,38 @@ 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 void +x_set_error_action(struct x_connection *c, uint32_t sequence, enum x_error_action action) { + 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; + 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` +static inline void attr_unused x_set_error_action_ignore(struct x_connection *c, + xcb_void_cookie_t cookie) { + x_set_error_action(c, cookie.sequence, PENDING_REPLY_ACTION_IGNORE); } -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` +static inline void attr_unused x_set_error_action_abort(struct x_connection *c, + xcb_void_cookie_t cookie) { + x_set_error_action(c, cookie.sequence, PENDING_REPLY_ACTION_ABORT); } -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` +static inline void attr_unused x_set_error_action_debug_abort(struct x_connection *c, + xcb_void_cookie_t cookie) { #ifndef NDEBUG - set_reply_action(c, cookie.sequence, PENDING_REPLY_ACTION_DEBUG_ABORT); + x_set_error_action(c, cookie.sequence, PENDING_REPLY_ACTION_DEBUG_ABORT); #else (void)c; (void)cookie; @@ -168,17 +171,11 @@ static inline void attr_unused set_debug_cant_fail_cookie(struct x_connection *c } 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; - - free(ign); + list_foreach_safe(struct pending_x_error, i, &c->pending_x_errors, siblings) { + list_remove(&i->siblings); + free(i); } - // Reset head and tail - c->pending_reply_head = NULL; - c->pending_reply_tail = &c->pending_reply_head; - XSetErrorHandler(c->previous_xerror_handler); } @@ -193,7 +190,7 @@ void x_connection_init(struct x_connection *c, Display *dpy); /// 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); +void x_discard_pending_errors(struct x_connection *c, uint32_t sequence); /// Handle X errors. /// From 23563c1ac80888abafb329dba91e671dfa7a9ca6 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 24 Jun 2024 11:22:12 +0100 Subject: [PATCH 18/56] x: print more context for X errors For x_print_error, print the name of the caller instead of "x_print_error". For registered x error actions, print where the request was originally made. Signed-off-by: Yuxuan Shui --- src/x.c | 243 +++++++++++++++++++++++++++++--------------------------- src/x.h | 47 ++++++----- 2 files changed, 152 insertions(+), 138 deletions(-) diff --git a/src/x.c b/src/x.c index 7eadae1da0..a0a3b3bbeb 100644 --- a/src/x.c +++ b/src/x.c @@ -60,6 +60,114 @@ void x_discard_pending_errors(struct x_connection *c, uint32_t sequence) { } } +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. 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; @@ -69,8 +177,17 @@ void x_handle_error(struct x_connection *c, xcb_generic_error_t *ev) { } if (first_error_action != NULL && first_error_action->sequence == ev->full_sequence) { if (first_error_action->action != PENDING_REPLY_ACTION_IGNORE) { - x_log_error(LOG_LEVEL_ERROR, ev->full_sequence, ev->major_code, - ev->minor_code, ev->error_code); + 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: @@ -82,8 +199,9 @@ void x_handle_error(struct x_connection *c, xcb_generic_error_t *ev) { } return; } - x_log_error(LOG_LEVEL_WARN, ev->full_sequence, ev->major_code, ev->minor_code, - ev->error_code); + log_warn("Stray X error: %s", + x_error_code_to_string(ev->full_sequence, ev->major_code, ev->minor_code, + ev->error_code)); } /// Initialize x_connection struct from an Xlib Display. @@ -565,120 +683,6 @@ void x_free_picture(struct x_connection *c, xcb_render_picture_t p) { x_set_error_action_debug_abort(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); -} - /* * Convert a xcb_generic_error_t to a string that describes the error * @@ -689,7 +693,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); } /** diff --git a/src/x.h b/src/x.h index b6af8da6a1..1ac946d9c6 100644 --- a/src/x.h +++ b/src/x.h @@ -55,6 +55,12 @@ enum x_error_action { struct pending_x_error { unsigned long sequence; 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; }; @@ -137,38 +143,40 @@ static inline uint32_t x_new_id(struct x_connection *c) { /// @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 void -x_set_error_action(struct x_connection *c, uint32_t sequence, enum x_error_action action) { +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->action = action; + i->func = func; + i->file = file; + i->line = line; list_insert_before(&c->pending_x_errors, &i->siblings); } /// Convenience wrapper for x_set_error_action with action `PENDING_REPLY_ACTION_IGNORE` -static inline void attr_unused x_set_error_action_ignore(struct x_connection *c, - xcb_void_cookie_t cookie) { - x_set_error_action(c, cookie.sequence, 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__) /// Convenience wrapper for x_set_error_action with action `PENDING_REPLY_ACTION_ABORT` -static inline void attr_unused x_set_error_action_abort(struct x_connection *c, - xcb_void_cookie_t cookie) { - x_set_error_action(c, cookie.sequence, 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__) /// Convenience wrapper for x_set_error_action with action /// `PENDING_REPLY_ACTION_DEBUG_ABORT` -static inline void attr_unused x_set_error_action_debug_abort(struct x_connection *c, - xcb_void_cookie_t cookie) { #ifndef NDEBUG - x_set_error_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) { list_foreach_safe(struct pending_x_error, i, &c->pending_x_errors, siblings) { @@ -327,9 +335,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 From fd945c40acd25fe782198b333d35beaaf9ed857c Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 24 Jun 2024 22:51:23 +0100 Subject: [PATCH 19/56] diagnostics: fix segmentation fault Commit 61ec9223d20c2a83c8bd0729276bc0cb0db3bd3c moved root geometry setup to a later point, so in print_diagnostics the root size became 0x0. Which caused backend initialization to fail. Because error reporting is not properly setup in print_diagnostics, we segment faulted. Fix that by setting root geometry before calling print_diagnostics. Signed-off-by: Yuxuan Shui --- src/picom.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/picom.c b/src/picom.c index 1b79e12746..3e8289fc59 100644 --- a/src/picom.c +++ b/src/picom.c @@ -2440,6 +2440,8 @@ static session_t *session_init(int argc, char **argv, Display *dpy, } 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); } From d617d0ba4f6b47dacc8d73fd19a9c94f74697ff5 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 24 Jun 2024 12:36:48 +0100 Subject: [PATCH 20/56] core: reading from X server is fine Signed-off-by: Yuxuan Shui --- src/picom.c | 56 ++++++++++++++++------------------------------------- 1 file changed, 17 insertions(+), 39 deletions(-) diff --git a/src/picom.c b/src/picom.c index 3e8289fc59..c4689d9de7 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1535,8 +1535,7 @@ 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) { // 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 @@ -1558,7 +1557,7 @@ static void handle_queued_x_events(EV_P attr_unused, ev_prepare *w, int revents } xcb_generic_event_t *ev; - while ((ev = xcb_poll_for_queued_event(ps->c.c))) { + while ((ev = xcb_poll_for_event(ps->c.c))) { ev_handle(ps, ev); free(ev); }; @@ -1569,44 +1568,14 @@ static void handle_queued_x_events(EV_P attr_unused, ev_prepare *w, int revents } } -/// Handle all events X server has sent us so far, so that our internal state will catch -/// up with the X server's state. It only makes sense to call this function in the X -/// critical section, otherwise we will be chasing a moving goal post. -/// -/// (Disappointingly, grabbing the X server doesn't actually prevent the X server state -/// from changing. It should, but in practice we have observed window still being -/// destroyed while we have the server grabbed. This is very disappointing and forces us -/// to use some hacky code to recover when we discover we are out-of-sync.) -/// -/// This function is different from `handle_queued_x_events` in that this will keep -/// reading from the X server until there is nothing left. Whereas -/// `handle_queued_x_events` is only concerned with emptying the internal buffer of -/// libxcb, so we don't go to sleep with unprocessed data. -static void handle_all_x_events(struct session *ps) { - assert(ps->server_grabbed); - - 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_event(ps->c.c))) { - ev_handle(ps, ev); - free(ev); - }; - int err = xcb_connection_has_error(ps->c.c); - if (err) { - log_fatal("X11 server connection broke (error %d)", err); - exit(1); - } +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); } static void handle_new_windows(session_t *ps) { while (!wm_complete_import(ps->wm, &ps->c, ps->atoms)) { - handle_all_x_events(ps); + handle_x_events(ps); } // Check tree changes first, because later property updates need accurate @@ -1696,7 +1665,16 @@ static void handle_pending_updates(struct session *ps, double delta_t) { ps->server_grabbed = true; // Catching up with X server - handle_all_x_events(ps); + /// Handle all events X server has sent us so far, so that our internal state will + /// catch up with the X server's state. It only makes sense to call this function + /// in the X critical section, otherwise we will be chasing a moving goal post. + /// + /// (Disappointingly, grabbing the X server doesn't actually prevent the X server + /// state from changing. It should, but in practice we have observed window still + /// being destroyed while we have the server grabbed. This is very disappointing + /// and forces us to use some hacky code to recover when we discover we are + /// out-of-sync.) + handle_x_events(ps); if (ps->pending_updates || wm_has_incomplete_imports(ps->wm) || wm_has_tree_changes(ps->wm)) { log_debug("Delayed handling of events, entering critical section"); @@ -2517,7 +2495,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); From 6c0f12db6ec4f4028be04b14e7e6902ae667b24e Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 25 Jun 2024 18:13:43 +0100 Subject: [PATCH 21/56] misc: remove leftover libxext includes Signed-off-by: Yuxuan Shui --- src/event.c | 1 - src/picom.c | 1 - 2 files changed, 2 deletions(-) diff --git a/src/event.c b/src/event.c index 0c3df0d965..953c882feb 100644 --- a/src/event.c +++ b/src/event.c @@ -5,7 +5,6 @@ #include #include -#include #include #include #include diff --git a/src/picom.c b/src/picom.c index c4689d9de7..4e797a75fd 100644 --- a/src/picom.c +++ b/src/picom.c @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include From 5d3640b79c51f336105953578f44ff566d15603e Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 26 Jun 2024 11:27:02 +0100 Subject: [PATCH 22/56] flake: specify a url for nixpkgs nix-direnv keeps picking up nixpkgs from my local nix registry, which is super annoying because I have to manually ignore the change every time I use git. Signed-off-by: Yuxuan Shui --- flake.lock | 7 +++---- flake.nix | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) 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..f153d8ba5d 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"; From 5b5174bc52e32a52bbd21c1863aeac42148a122f Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 26 Jun 2024 11:35:37 +0100 Subject: [PATCH 23/56] flake: filter out libXext from buildInputs Signed-off-by: Yuxuan Shui --- flake.nix | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/flake.nix b/flake.nix index f153d8ba5d..e6e76bec8f 100644 --- a/flake.nix +++ b/flake.nix @@ -15,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"; @@ -24,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 ./.; }); }; From df194f2643b0ac2c49dac5dfc126b94d99efe4bd Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Jun 2024 00:00:58 +0100 Subject: [PATCH 24/56] wm/win: implement EWMH WM_TRANSIENT_FOR extension In EWMH the meaning of WM_TRANSIENT_FOR is extended, so that when it's set to the root window or None, the window is considered transient for all windows in the same window group. We implement this by using the window group leader as the leader. Also make win_get_leader_raw robust against invalid leader values. Fixes #1278 Signed-off-by: Yuxuan Shui --- src/inspect.c | 4 +++- src/picom.c | 4 +++- src/wm/win.c | 35 +++++++++++++++++++++++++++++++---- src/x.c | 6 +++++- src/x.h | 3 ++- 5 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/inspect.c b/src/inspect.c index 4e8d357991..33a646af70 100644 --- a/src/inspect.c +++ b/src/inspect.c @@ -63,8 +63,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 diff --git a/src/picom.c b/src/picom.c index 4e797a75fd..f8df00a54b 100644 --- a/src/picom.c +++ b/src/picom.c @@ -457,8 +457,10 @@ void add_damage(session_t *ps, const region_t *damage) { */ void update_ewmh_active_win(session_t *ps) { // Search for the window + bool exists; xcb_window_t wid = wid_get_prop_window(&ps->c, ps->c.screen_info->root, - ps->atoms->a_NET_ACTIVE_WINDOW); + ps->atoms->a_NET_ACTIVE_WINDOW, &exists); + auto cursor = wm_find_by_client(ps->wm, wid); auto w = cursor ? wm_ref_deref(cursor) : NULL; diff --git a/src/wm/win.c b/src/wm/win.c index 6a6a878933..43cb5629d8 100644 --- a/src/wm/win.c +++ b/src/wm/win.c @@ -449,7 +449,8 @@ static void win_update_properties(session_t *ps, struct 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)) { + 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, @@ -1532,17 +1533,37 @@ 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; } @@ -1556,6 +1577,12 @@ static struct wm_ref *win_get_leader_raw(session_t *ps, struct win *w, int recur // 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; } diff --git a/src/x.c b/src/x.c index a0a3b3bbeb..256ba42ba7 100644 --- a/src/x.c +++ b/src/x.c @@ -284,14 +284,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); diff --git a/src/x.h b/src/x.h index 1ac946d9c6..caaf4b4e65 100644 --- a/src/x.h +++ b/src/x.h @@ -249,7 +249,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. From 56fea383f018986eb5368d200bf5f08ed79f4d30 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Jun 2024 13:58:46 +0100 Subject: [PATCH 25/56] config: fix use-after-free When legacy backends are enabled, `options->all_scripts` is freed by option sanitization, but is later used again by `generate_fading_config`. Signed-off-by: Yuxuan Shui --- src/options.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/options.c b/src/options.c index b57f781a9a..c4833a2566 100644 --- a/src/options.c +++ b/src/options.c @@ -793,7 +793,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) { From 843f308d1e4e18e33e22c47079af37c826e1b304 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Jun 2024 14:05:24 +0100 Subject: [PATCH 26/56] core: init legacy backend after we have root geometry We moved root geometry query to a later point than before, but forgot the legacy backends need that for initialization. Signed-off-by: Yuxuan Shui --- src/picom.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/picom.c b/src/picom.c index f8df00a54b..ca8bdde6a0 100644 --- a/src/picom.c +++ b/src/picom.c @@ -2412,12 +2412,6 @@ static session_t *session_init(int argc, char **argv, Display *dpy, 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; @@ -2579,6 +2573,12 @@ static session_t *session_init(int argc, char **argv, Display *dpy, 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); + } + ps->pending_updates = true; write_pid(ps); From de52cbd0e617d31b9a67ede71e6da4b0cb4e8268 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Mon, 24 Jun 2024 09:47:10 +0100 Subject: [PATCH 27/56] x: add support for receiving replies as part of the event stream Why do we need this? This is because, firstly, in the X protocol, events, errors, and replies are in a single stream to begin with, so it's not "unnatural" to put them back. And, some replies have to be handled in the correct "context". Say, the X window tree look like this: A / \ B C `C` is the new window, so send a query tree request for it, and you receive: > Event: Reparent B to C > Query tree reply: C has child B (Remember events, and replies are all in a single stream) If you handle the reply before the event, which is what will happen if you do `xcb_query_tree_reply(c, xcb_query_tree(c, ..))`, you will think window `B` appeared in 2 places. Also with this change the `x_handle_error` in `ev_handle` is move into `x_poll_for_event`. It just makes more sense to there. Signed-off-by: Yuxuan Shui --- src/event.c | 5 -- src/picom.c | 6 +- src/x.c | 230 +++++++++++++++++++++++++++++++++++++++++++++++----- src/x.h | 58 ++++++++++--- 4 files changed, 259 insertions(+), 40 deletions(-) diff --git a/src/event.c b/src/event.c index 953c882feb..dd113c99b3 100644 --- a/src/event.c +++ b/src/event.c @@ -629,10 +629,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_errors(&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\"", @@ -702,7 +698,6 @@ 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); diff --git a/src/picom.c b/src/picom.c index ca8bdde6a0..a06fdb019c 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1558,8 +1558,8 @@ static void handle_x_events(struct session *ps) { } xcb_generic_event_t *ev; - while ((ev = xcb_poll_for_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); @@ -1978,7 +1978,7 @@ 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); + xcb_generic_event_t *ev = x_poll_for_event(&ps->c); if (ev) { ev_handle(ps, ev); free(ev); diff --git a/src/x.c b/src/x.c index 256ba42ba7..6291306980 100644 --- a/src/x.c +++ b/src/x.c @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include #include "atom.h" @@ -31,26 +33,24 @@ // === Error handling === -/** - * Xlib error handler function. - */ -static int xerror(Display attr_unused *dpy, XErrorEvent *ev) { - if (!ps_g) { - // Do not ignore errors until the session has been initialized - return 0; +/// 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); + } } - - // Fake a xcb error, fill in just enough information - xcb_generic_error_t xcb_err; - xcb_err.full_sequence = (uint32_t)ev->serial; - xcb_err.major_code = ev->request_code; - xcb_err.minor_code = ev->minor_code; - xcb_err.error_code = ev->error_code; - x_handle_error(&ps_g->c, &xcb_err); - return 0; -} - -void x_discard_pending_errors(struct x_connection *c, uint32_t sequence) { list_foreach_safe(struct pending_x_error, i, &c->pending_x_errors, siblings) { if (sequence <= i->sequence) { break; @@ -168,7 +168,7 @@ void x_print_error_impl(unsigned long serial, uint8_t major, uint16_t minor, /// 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) { +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)) { @@ -204,6 +204,25 @@ void x_handle_error(struct x_connection *c, xcb_generic_error_t *ev) { ev->error_code)); } +/** + * Xlib error handler function. + */ +static int xerror(Display attr_unused *dpy, XErrorEvent *ev) { + if (!ps_g) { + // Do not ignore errors until the session has been initialized + return 0; + } + + // Fake a xcb error, fill in just enough information + xcb_generic_error_t xcb_err; + xcb_err.full_sequence = (uint32_t)ev->serial; + xcb_err.major_code = ev->request_code; + xcb_err.minor_code = ev->minor_code; + xcb_err.error_code = ev->error_code; + x_handle_error(&ps_g->c, &xcb_err); + return 0; +} + /// Initialize x_connection struct from an Xlib Display. /// /// Note this function doesn't take ownership of the Display, the caller is still @@ -212,10 +231,17 @@ void x_connection_init(struct x_connection *c, Display *dpy) { c->dpy = dpy; c->c = XGetXCBConnection(dpy); 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; } /** @@ -909,3 +935,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 caaf4b4e65..6afc5ddcab 100644 --- a/src/x.h +++ b/src/x.h @@ -78,6 +78,17 @@ struct x_connection { // Private fields /// 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 @@ -178,11 +189,25 @@ x_set_error_action(struct x_connection *c, uint32_t sequence, enum x_error_actio ((void)(cookie)) #endif +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; +}; + 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); + } XSetErrorHandler(c->previous_xerror_handler); } @@ -193,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_errors(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. * @@ -415,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); +} From 7d216657d0fc5e0abf0596b7fa34e48bcf3f1e72 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 25 Jun 2024 18:51:27 +0100 Subject: [PATCH 28/56] wm/tree: split wm_tree_new_window and wm_tree_reparent wm_tree_new_window is splitted into 3 steps: creating the window object (wm_tree_new_window), adding it to the hash table (wm_tree_add_window), and attaching it to its parent (wm_tree_attach). wm_tree_reparent is splitted into wm_tree_detach and then wm_tree_attach. Signed-off-by: Yuxuan Shui --- src/wm/tree.c | 108 +++++++++++++++++++------------------------ src/wm/wm.c | 19 ++++++-- src/wm/wm_internal.h | 10 ++-- 3 files changed, 69 insertions(+), 68 deletions(-) diff --git a/src/wm/tree.c b/src/wm/tree.c index 3c4c493234..32b93b6f29 100644 --- a/src/wm/tree.c +++ b/src/wm/tree.c @@ -234,31 +234,17 @@ void wm_tree_set_wm_state(struct wm_tree *tree, struct wm_tree_node *node, bool } } -struct wm_tree_node * -wm_tree_new_window(struct wm_tree *tree, xcb_window_t id, struct wm_tree_node *parent) { +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; +} - BUG_ON(parent == NULL && tree->nodes != NULL); // Trying to create a second - // root window +void wm_tree_add_window(struct wm_tree *tree, struct wm_tree_node *node) { HASH_ADD_INT(tree->nodes, id.x, node); - - node->parent = parent; - if (parent != NULL) { - list_insert_after(&parent->children, &node->siblings); - if (parent->parent == NULL) { - // Parent is root, this is a new toplevel window - wm_tree_enqueue_change(tree, (struct wm_tree_change){ - .toplevel = node->id, - .type = WM_TREE_CHANGE_TOPLEVEL_NEW, - .new_ = node, - }); - } - } - return node; } static void @@ -307,6 +293,30 @@ void wm_tree_detach(struct wm_tree *tree, struct wm_tree_node *subroot) { .killed = zombie, }); } + subroot->parent = NULL; +} + +void wm_tree_attach(struct wm_tree *tree, struct wm_tree_node *child, + struct wm_tree_node *parent) { + BUG_ON_NULL(parent); + BUG_ON(child->parent != NULL); // Trying to attach a window that's already + // attached + child->parent = parent; + list_insert_after(&parent->children, &child->siblings); + + auto toplevel = wm_tree_find_toplevel_for(child); + if (child == toplevel) { + // This node could have a stale `->win` if it was a toplevel at + // some point in the past. + child->win = NULL; + wm_tree_enqueue_change(tree, (struct wm_tree_change){ + .toplevel = child->id, + .type = WM_TREE_CHANGE_TOPLEVEL_NEW, + .new_ = child, + }); + } else { + wm_tree_refresh_client_and_queue_change(tree, toplevel); + } } void wm_tree_destroy_window(struct wm_tree *tree, struct wm_tree_node *node) { @@ -386,38 +396,6 @@ void wm_tree_move_to_above(struct wm_tree *tree, struct wm_tree_node *node, } } -void wm_tree_reparent(struct wm_tree *tree, struct wm_tree_node *node, - struct wm_tree_node *new_parent) { - BUG_ON(node == NULL); - BUG_ON(new_parent == NULL); // Trying make `node` a root window - - if (node->parent == new_parent) { - // Reparent to the same parent moves the window to the top of the stack - wm_tree_move_to_end(tree, node, false); - return; - } - - wm_tree_detach(tree, node); - - // Reparented window always becomes the topmost child of the new parent - list_insert_after(&new_parent->children, &node->siblings); - node->parent = new_parent; - - auto toplevel = wm_tree_find_toplevel_for(node); - if (node == toplevel) { - // This node could have a stale `->win` if it was a toplevel at - // some point in the past. - node->win = NULL; - wm_tree_enqueue_change(tree, (struct wm_tree_change){ - .toplevel = node->id, - .type = WM_TREE_CHANGE_TOPLEVEL_NEW, - .new_ = node, - }); - } else { - wm_tree_refresh_client_and_queue_change(tree, toplevel); - } -} - void wm_tree_clear(struct wm_tree *tree) { struct wm_tree_node *cur, *tmp; HASH_ITER(hh, tree->nodes, cur, tmp) { @@ -438,7 +416,7 @@ TEST_CASE(tree_manipulation) { struct wm_tree tree; wm_tree_init(&tree); - wm_tree_new_window(&tree, 1, NULL); + 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); @@ -446,9 +424,11 @@ TEST_CASE(tree_manipulation) { auto change = wm_tree_dequeue_change(&tree); assert(change.type == WM_TREE_CHANGE_NONE); - wm_tree_new_window(&tree, 2, root); - auto node2 = wm_tree_find(&tree, 2); + 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); @@ -456,15 +436,16 @@ TEST_CASE(tree_manipulation) { assert(change.type == WM_TREE_CHANGE_TOPLEVEL_NEW); assert(wm_treeid_eq(node2->id, change.toplevel)); - wm_tree_new_window(&tree, 3, root); - auto node3 = wm_tree_find(&tree, 3); - assert(node3 != NULL); + 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); - wm_tree_reparent(&tree, node2, node3); + wm_tree_detach(&tree, node2); + wm_tree_attach(&tree, node2, node3); assert(node2->parent == node3); assert(node3->children.next == &node2->siblings); @@ -481,8 +462,9 @@ TEST_CASE(tree_manipulation) { assert(wm_treeid_eq(change.client.old, WM_TREEID_NONE)); assert(change.client.new_.x == 2); - wm_tree_new_window(&tree, 4, node3); - auto node4 = wm_tree_find(&tree, 4); + 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); @@ -500,7 +482,9 @@ TEST_CASE(tree_manipulation) { // Test window ID reuse wm_tree_destroy_window(&tree, node4); - node4 = wm_tree_new_window(&tree, 4, node3); + 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); @@ -509,7 +493,9 @@ TEST_CASE(tree_manipulation) { assert(change.client.old.x == 4); assert(change.client.new_.x == 4); - auto node5 = wm_tree_new_window(&tree, 5, root); + auto node5 = wm_tree_new_window(&tree, 5); + wm_tree_add_window(&tree, node5); + wm_tree_attach(&tree, node5, root); wm_tree_destroy_window(&tree, node5); change = wm_tree_dequeue_change(&tree); assert(change.type == WM_TREE_CHANGE_NONE); // Changes cancelled out diff --git a/src/wm/wm.c b/src/wm/wm.c index c8d37156b0..51f05f4c7e 100644 --- a/src/wm/wm.c +++ b/src/wm/wm.c @@ -286,7 +286,16 @@ void wm_reparent(struct wm *wm, xcb_window_t wid, xcb_window_t parent) { return; } - wm_tree_reparent(&wm->tree, window, new_parent); + if (new_parent == window->parent) { + log_debug("Reparenting window %#010x to its current parent %#010x, " + "moving it to the top.", + wid, parent); + wm_tree_move_to_end(&wm->tree, window, false); + return; + } + + wm_tree_detach(&wm->tree, window); + wm_tree_attach(&wm->tree, window, new_parent); } void wm_set_has_wm_state(struct wm *wm, struct wm_ref *cursor, bool has_wm_state) { @@ -313,7 +322,9 @@ void wm_import_incomplete(struct wm *wm, xcb_window_t wid, xcb_window_t parent) } } log_debug("Importing window %#010x with parent %#010x", wid, parent); - auto new = wm_tree_new_window(&wm->tree, wid, parent_cursor); + auto new = wm_tree_new_window(&wm->tree, wid); + wm_tree_add_window(&wm->tree, new); + wm_tree_attach(&wm->tree, new, parent_cursor); dynarr_push(wm->incompletes, new); if (parent == XCB_NONE) { BUG_ON(wm->root != NULL); // Can't have more than one root @@ -383,7 +394,9 @@ static bool wm_complete_import_subtree(struct wm *wm, struct x_connection *c, children[i], curr->id.x); wm_tree_destroy_window(&wm->tree, existing); } - existing = wm_tree_new_window(&wm->tree, children[i], curr); + existing = wm_tree_new_window(&wm->tree, children[i]); + wm_tree_add_window(&wm->tree, existing); + wm_tree_attach(&wm->tree, existing, curr); wm_complete_import_single(wm, c, atoms, existing); } free(tree); diff --git a/src/wm/wm_internal.h b/src/wm/wm_internal.h index 9e33808a9b..e155e39031 100644 --- a/src/wm/wm_internal.h +++ b/src/wm/wm_internal.h @@ -24,6 +24,7 @@ struct wm_tree { 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; @@ -85,15 +86,16 @@ struct wm_tree_node *attr_pure wm_tree_next(struct wm_tree_node *node, /// 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, struct wm_tree_node *parent); +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. void wm_tree_detach(struct wm_tree *tree, struct wm_tree_node *subroot); -void wm_tree_reparent(struct wm_tree *tree, struct wm_tree_node *node, - struct wm_tree_node *new_parent); +/// Attach `node` to `parent`. `node` becomes the topmost child of `parent`. +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. From bb5d026a6f87be43bf9567bb3f05b6d890ea2855 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 26 Jun 2024 21:44:29 +0100 Subject: [PATCH 29/56] wm/tree: fix potential use-after-free in tree changes If a toplevel is reparented, then destroy, any WM_TREE_CHANGE_CLIENT events currently queued will reference the freed toplevel node. Note this doesn't happen when the toplevel is destroyed directly. Because in that case it will be turned into a zombie, and is guaranteed to only be freed after the client change event is handled. With this commit, when a toplevel is killed (i.e. destroyed or reparented), all currently queued tree changes will be updated to reference the zombie instead. Signed-off-by: Yuxuan Shui --- src/wm/tree.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/wm/tree.c b/src/wm/tree.c index 32b93b6f29..b851e89d47 100644 --- a/src/wm/tree.c +++ b/src/wm/tree.c @@ -40,16 +40,25 @@ static void wm_tree_enqueue_change(struct wm_tree *tree, struct wm_tree_change c // `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 (i->item.type == WM_TREE_CHANGE_TOPLEVEL_NEW && - wm_treeid_eq(i->item.toplevel, change.toplevel)) { + if (!wm_treeid_eq(i->item.toplevel, change.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 (wm_treeid_eq(i->item.toplevel, change.toplevel) && found) { - // We also need to delete all other changes related to - // this toplevel in between the new and gone changes. + } 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 = change.killed; } } if (found) { From 7675a015bd7393a3cf705ef9c3599296cd2d6cb6 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 26 Jun 2024 22:09:48 +0100 Subject: [PATCH 30/56] wm/tree: clear managed window object when a toplevel node is detached Signed-off-by: Yuxuan Shui --- src/wm/tree.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/wm/tree.c b/src/wm/tree.c index b851e89d47..e45810fa9d 100644 --- a/src/wm/tree.c +++ b/src/wm/tree.c @@ -294,6 +294,7 @@ void wm_tree_detach(struct wm_tree *tree, struct wm_tree_node *subroot) { zombie->id = subroot->id; zombie->is_zombie = true; zombie->win = subroot->win; + subroot->win = NULL; list_init_head(&zombie->children); list_replace(&subroot->siblings, &zombie->siblings); wm_tree_enqueue_change(tree, (struct wm_tree_change){ @@ -315,9 +316,6 @@ void wm_tree_attach(struct wm_tree *tree, struct wm_tree_node *child, auto toplevel = wm_tree_find_toplevel_for(child); if (child == toplevel) { - // This node could have a stale `->win` if it was a toplevel at - // some point in the past. - child->win = NULL; wm_tree_enqueue_change(tree, (struct wm_tree_change){ .toplevel = child->id, .type = WM_TREE_CHANGE_TOPLEVEL_NEW, From b85d3e26555c33cfbeef68ed579bff631ab32faa Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 26 Jun 2024 14:43:20 +0100 Subject: [PATCH 31/56] wm: no longer depends on server grabbing for tree consistency This is part of the effort to remove reliance on the X critical section for internal consistency. The rational will be explained in later commits when it is complete. Instead of using server grabbing to guarantee that our tree is synchronized with the X server, this commit moves the wm tree to an "eventually consistent" model, utilizing the newly introduced async X request infrastructure (See commit: "x: add support for receiving replies as part of the event stream"). The algorithm is like this: let's look at each tree node individually, every node has a list of children. If this list consistent with the X server for each of the tree nodes, the entire tree is consistent. Let's also define a "partial consistency" concept. A tree is partially consistent, if for all the nodes in the tree, their lists of chilren are either consistent, or empty. Meaning some nodes might exist on the X server, but not replicated on our side. If a node's parent isn't in our tree, we call that node "orphaned". (Conveniently, a toplevel can never be orphaned, because the root window is always in the tree.) To achieve partial consistency, when each node is discovered (either via a CreateNotify from the server, or via a QueryTree request, you will see later), we set up an event mask on it to receive further updates, and then send a QueryTree request for it. The trick is that the reply to the QueryTree request will be processed in order with all the other events, errors and replies. Assuming the tree started out partially consistent, it will still be partially consistent after we processed the reply. Because the reply contains up-to-date information about that node at that point in time; and we have also processed all events that came before that reply, so all other nodes are consistent too. Further more, the tree will stay partially consistent, because the event mask we set up will keep bringing us new information for the newly discovered node. This partial consistency will eventually become full consistency after all of our requests to the X server have completed. Note this means we might start rendering with a partial window tree. In practice, that should hardly be noticeable. (The explanation above glossed over many implementation concerns, some of those are documentation in code comments.) Signed-off-by: Yuxuan Shui --- src/c2.c | 5 +- src/event.c | 68 +++--- src/event.h | 1 + src/inspect.c | 37 +++- src/picom.c | 54 +---- src/wm/tree.c | 78 +++---- src/wm/win.c | 2 +- src/wm/wm.c | 509 ++++++++++++++++++++++++++----------------- src/wm/wm.h | 93 ++++---- src/wm/wm_internal.h | 9 +- 10 files changed, 481 insertions(+), 375 deletions(-) diff --git a/src/c2.c b/src/c2.c index b8891b7006..08733c1454 100644 --- a/src/c2.c +++ b/src/c2.c @@ -442,11 +442,11 @@ TEST_CASE(c2_parse) { TEST_STREQUAL3(str, "name = \"xterm\"", len); struct wm *wm = wm_new(); - wm_import_incomplete(wm, 1, XCB_NONE); + struct wm_ref *node = wm_new_mock_window(wm, 1); struct win test_win = { .name = "xterm", - .tree_ref = wm_find(wm, 1), + .tree_ref = node, }; TEST_TRUE(c2_match(state, &test_win, cond, NULL)); c2_list_postprocess(state, NULL, cond); @@ -568,6 +568,7 @@ TEST_CASE(c2_parse) { TEST_STREQUAL3(str, rule, len); c2_list_free(&cond, NULL); + wm_free_mock_window(wm, test_win.tree_ref); wm_free(wm); } diff --git a/src/event.c b/src/event.c index dd113c99b3..77b20cadaf 100644 --- a/src/event.c +++ b/src/event.c @@ -203,10 +203,14 @@ static inline void ev_focus_out(session_t *ps, xcb_focus_out_event_t *ev) { } static inline void ev_create_notify(session_t *ps, xcb_create_notify_event_t *ev) { - if (wm_is_wid_masked(ps->wm, ev->parent)) { - return; + 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_incomplete(ps->wm, ev->window, ev->parent); + wm_import_start(ps->wm, &ps->c, ps->atoms, ev->window, parent); } /// Handle configure event of a regular window @@ -215,7 +219,11 @@ static void configure_win(session_t *ps, xcb_configure_notify_event_t *ce) { auto below = wm_find(ps->wm, ce->above_sibling); if (!cursor) { - log_error("Configure event received for unknown window %#010x", ce->window); + if (wm_is_consistent(ps->wm)) { + log_error("Configure event received for unknown window %#010x", + ce->window); + assert(false); + } return; } @@ -223,6 +231,7 @@ static void configure_win(session_t *ps, xcb_configure_notify_event_t *ce) { 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 { @@ -288,7 +297,7 @@ static inline void ev_configure_notify(session_t *ps, xcb_configure_notify_event if (ev->window == ps->c.screen_info->root) { set_root_flags(ps, ROOT_FLAGS_CONFIGURED); - } else if (!wm_is_wid_masked(ps->wm, ev->event)) { + } else { configure_win(ps, ev); } } @@ -314,15 +323,14 @@ static inline void ev_map_notify(session_t *ps, xcb_map_notify_event_t *ev) { return; } - if (wm_is_wid_masked(ps->wm, ev->event)) { - return; - } - auto cursor = wm_find(ps->wm, ev->window); if (cursor == NULL) { - log_error("Map event received for unknown window %#010x, overlay is " - "%#010x", - ev->window, ps->overlay); + 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; } @@ -349,13 +357,12 @@ static inline void ev_unmap_notify(session_t *ps, xcb_unmap_notify_event_t *ev) return; } - if (wm_is_wid_masked(ps->wm, ev->event)) { - return; - } - auto cursor = wm_find(ps->wm, ev->window); if (cursor == NULL) { - log_error("Unmap event received for unknown window %#010x", ev->window); + if (wm_is_consistent(ps->wm)) { + log_error("Unmap event received for unknown window %#010x", ev->window); + assert(false); + } return; } auto w = wm_ref_deref(cursor); @@ -372,14 +379,14 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t } static inline void ev_circulate_notify(session_t *ps, xcb_circulate_notify_event_t *ev) { - if (wm_is_wid_masked(ps->wm, ev->event)) { - return; - } - auto cursor = wm_find(ps->wm, ev->window); if (cursor == NULL) { - log_error("Circulate event received for unknown window %#010x", ev->window); + if (wm_is_consistent(ps->wm)) { + log_debug("Circulate event received for unknown window %#010x", + ev->window); + assert(false); + } return; } @@ -451,18 +458,18 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t return; } - if (wm_is_wid_masked(ps->wm, ev->window)) { - return; - } - ps->pending_updates = true; auto cursor = wm_find(ps->wm, ev->window); if (cursor == NULL) { - log_error("Property notify received for unknown window %#010x", ev->window); + 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(cursor); + auto toplevel_cursor = wm_ref_toplevel_of(ps->wm, cursor); if (ev->atom == ps->atoms->aWM_STATE) { log_debug("WM_STATE changed for window %#010x (%s): %s", ev->window, ev_window_name(ps, ev->window), @@ -470,6 +477,11 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t 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. 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 33a646af70..d6e0160225 100644 --- a/src/inspect.c +++ b/src/inspect.c @@ -34,10 +34,13 @@ setup_window(struct x_connection *c, struct atom *atoms, struct options *options return NULL; } - auto toplevel = wm_ref_toplevel_of(cursor); + auto toplevel = wm_ref_toplevel_of(wm, cursor); + BUG_ON_NULL(toplevel); struct win *w = ccalloc(1, struct win); w->state = WSTATE_MAPPED; 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, win_client_id(w, /*fallback_to_self=*/true), options->frame_opacity); @@ -195,14 +198,40 @@ int inspect_main(int argc, char **argv, const char *config_file) { 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_incomplete(wm, c.screen_info->root, XCB_NONE); - wm_complete_import(wm, &c, atoms); + 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); diff --git a/src/picom.c b/src/picom.c index a06fdb019c..843972f61e 100644 --- a/src/picom.c +++ b/src/picom.c @@ -509,7 +509,11 @@ static void recheck_focus(session_t *ps) { return; } - cursor = wm_ref_toplevel_of(cursor); + 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); @@ -1575,10 +1579,6 @@ static void handle_x_events_ev(EV_P attr_unused, ev_prepare *w, int revents attr } static void handle_new_windows(session_t *ps) { - while (!wm_complete_import(ps->wm, &ps->c, ps->atoms)) { - handle_x_events(ps); - } - // Check tree changes first, because later property updates need accurate // client window information struct win *w = NULL; @@ -1676,8 +1676,7 @@ static void handle_pending_updates(struct session *ps, double delta_t) { /// and forces us to use some hacky code to recover when we discover we are /// out-of-sync.) handle_x_events(ps); - if (ps->pending_updates || wm_has_incomplete_imports(ps->wm) || - wm_has_tree_changes(ps->wm)) { + if (ps->pending_updates || wm_has_tree_changes(ps->wm)) { log_debug("Delayed handling of events, entering critical section"); // Process new windows, and maybe allocate struct managed_win for them handle_new_windows(ps); @@ -1804,9 +1803,9 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { log_fatal("Couldn't find specified benchmark window."); exit(1); } - w = wm_ref_toplevel_of(w); + w = wm_ref_toplevel_of(ps->wm, w); - auto win = wm_ref_deref(w); + auto win = w == NULL ? NULL : wm_ref_deref(w); if (win != NULL) { add_damage_from_win(ps, win); } else { @@ -2511,38 +2510,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, } ps->wm = wm_new(); - wm_import_incomplete(ps->wm, ps->c.screen_info->root, XCB_NONE); - - 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); - goto err; - } - - ps->server_grabbed = true; - - // 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); - - wm_complete_import(ps->wm, &ps->c, ps->atoms); - - 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); - goto err; - } - - ps->server_grabbed = false; - - log_debug("Initial stack:"); - wm_stack_foreach(ps->wm, i) { - log_debug(" %#010x", wm_ref_win_id(i)); - } + 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); @@ -2737,8 +2705,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); + // 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/wm/tree.c b/src/wm/tree.c index e45810fa9d..99e195d30f 100644 --- a/src/wm/tree.c +++ b/src/wm/tree.c @@ -135,6 +135,10 @@ struct wm_tree_change wm_tree_dequeue_change(struct wm_tree *tree) { /// 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); @@ -180,7 +184,8 @@ struct wm_tree_node *wm_tree_find(const struct wm_tree *tree, xcb_window_t id) { return node; } -struct wm_tree_node *wm_tree_find_toplevel_for(struct wm_tree_node *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 @@ -189,7 +194,7 @@ struct wm_tree_node *wm_tree_find_toplevel_for(struct wm_tree_node *node) { for (auto curr = node; curr->parent != NULL; curr = curr->parent) { toplevel = curr; } - return toplevel; + return toplevel->parent == tree->root ? toplevel : NULL; } /// Change whether a tree node has the `WM_STATE` property set. @@ -283,10 +288,12 @@ void 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(subroot); + auto toplevel = wm_tree_find_toplevel_for(tree, subroot); if (toplevel != subroot) { list_remove(&subroot->siblings); - wm_tree_refresh_client_and_queue_change(tree, toplevel); + if (toplevel != NULL) { + wm_tree_refresh_client_and_queue_change(tree, toplevel); + } } else { // Detached a toplevel, create a zombie for it auto zombie = ccalloc(1, struct wm_tree_node); @@ -308,57 +315,30 @@ void wm_tree_detach(struct wm_tree *tree, struct wm_tree_node *subroot) { void wm_tree_attach(struct wm_tree *tree, struct wm_tree_node *child, struct wm_tree_node *parent) { - BUG_ON_NULL(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(child); + auto toplevel = wm_tree_find_toplevel_for(tree, child); if (child == toplevel) { wm_tree_enqueue_change(tree, (struct wm_tree_change){ .toplevel = child->id, .type = WM_TREE_CHANGE_TOPLEVEL_NEW, .new_ = child, }); - } else { + } else if (toplevel != NULL) { wm_tree_refresh_client_and_queue_change(tree, toplevel); } } -void wm_tree_destroy_window(struct wm_tree *tree, struct wm_tree_node *node) { - BUG_ON(node == NULL); - BUG_ON(node->parent == NULL); // Trying to destroy the root window?! - - if (node->has_wm_state) { - wm_tree_set_wm_state(tree, node, false); - } - - HASH_DEL(tree->nodes, node); - - if (!list_is_empty(&node->children)) { - log_error("Window %#010x is destroyed, but it still has children. Expect " - "malfunction.", - node->id.x); - list_foreach_safe(struct wm_tree_node, i, &node->children, siblings) { - log_error(" Child window %#010x", i->id.x); - wm_tree_destroy_window(tree, i); - } - } - - if (node->parent->parent == NULL) { - node->is_zombie = true; - wm_tree_enqueue_change(tree, (struct wm_tree_change){ - .toplevel = node->id, - .type = WM_TREE_CHANGE_TOPLEVEL_KILLED, - .killed = node, - }); - } else { - list_remove(&node->siblings); - free(node); - } -} - 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 @@ -374,7 +354,7 @@ void wm_tree_move_to_end(struct wm_tree *tree, struct wm_tree_node *node, bool t } else { list_insert_after(&node->parent->children, &node->siblings); } - if (node->parent->parent == NULL) { + if (node->parent == tree->root) { wm_tree_enqueue_change(tree, (struct wm_tree_change){ .type = WM_TREE_CHANGE_TOPLEVEL_RESTACKED, }); @@ -396,7 +376,7 @@ void wm_tree_move_to_above(struct wm_tree *tree, struct wm_tree_node *node, list_remove(&node->siblings); list_insert_before(&other->siblings, &node->siblings); - if (node->parent->parent == NULL) { + if (node->parent == tree->root) { wm_tree_enqueue_change(tree, (struct wm_tree_change){ .type = WM_TREE_CHANGE_TOPLEVEL_RESTACKED, }); @@ -428,6 +408,8 @@ TEST_CASE(tree_manipulation) { assert(root != NULL); assert(root->parent == NULL); + tree.root = root; + auto change = wm_tree_dequeue_change(&tree); assert(change.type == WM_TREE_CHANGE_NONE); @@ -480,7 +462,9 @@ TEST_CASE(tree_manipulation) { // node3 already has node2 as its client window, so the new one should be ignored. assert(change.type == WM_TREE_CHANGE_NONE); - wm_tree_destroy_window(&tree, node2); + wm_tree_detach(&tree, node2); + 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); @@ -488,7 +472,9 @@ TEST_CASE(tree_manipulation) { assert(change.client.new_.x == 4); // Test window ID reuse - wm_tree_destroy_window(&tree, node4); + wm_tree_detach(&tree, node4); + 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); @@ -503,7 +489,9 @@ TEST_CASE(tree_manipulation) { auto node5 = wm_tree_new_window(&tree, 5); wm_tree_add_window(&tree, node5); wm_tree_attach(&tree, node5, root); - wm_tree_destroy_window(&tree, node5); + wm_tree_detach(&tree, node5); + HASH_DEL(tree.nodes, node5); + free(node5); change = wm_tree_dequeue_change(&tree); assert(change.type == WM_TREE_CHANGE_NONE); // Changes cancelled out diff --git a/src/wm/win.c b/src/wm/win.c index 43cb5629d8..af3bde3055 100644 --- a/src/wm/win.c +++ b/src/wm/win.c @@ -1591,7 +1591,7 @@ static struct wm_ref *win_get_leader_raw(session_t *ps, struct win *w, int recur // ancestors if (w->cache_leader && w->cache_leader != client_win && w->cache_leader != w->tree_ref) { - auto parent = wm_ref_toplevel_of(w->cache_leader); + auto parent = wm_ref_toplevel_of(ps->wm, w->cache_leader); auto wp = parent ? wm_ref_deref(parent) : NULL; if (wp) { // Dead loop? diff --git a/src/wm/wm.c b/src/wm/wm.c index 51f05f4c7e..58e75c5eac 100644 --- a/src/wm/wm.c +++ b/src/wm/wm.c @@ -15,6 +15,20 @@ #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 { /// Pointer to win of current active window. Used by /// EWMH _NET_ACTIVE_WINDOW focus detection. In theory, @@ -27,14 +41,20 @@ struct wm { struct wm_tree_node *active_leader; struct wm_tree tree; - struct wm_tree_node *root; - - /// Incomplete imports. See `wm_import_incomplete` for an explanation. - /// This is a dynarr. - struct wm_tree_node **incompletes; - /// Tree nodes that we have chosen to forget, but we might still receive some - /// events from, we keep them here to ignore those events. - struct wm_tree_node **masked; + /// 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 @@ -86,19 +106,6 @@ void wm_ref_set(struct wm_ref *cursor, struct win *w) { to_tree_node_mut(cursor)->win = w; } -static ptrdiff_t wm_find_masked(struct wm *wm, xcb_window_t wid) { - dynarr_foreach(wm->masked, m) { - if ((*m)->id.x == wid) { - return m - wm->masked; - } - } - return -1; -} - -bool wm_is_wid_masked(struct wm *wm, xcb_window_t wid) { - return wm_find_masked(wm, wid) != -1; -} - struct win *wm_active_win(struct wm *wm) { return wm->active_win; } @@ -132,7 +139,7 @@ struct wm_ref *wm_ref_above(const struct wm_ref *cursor) { } struct wm_ref *wm_root_ref(const struct wm *wm) { - return (struct wm_ref *)&wm->root->siblings; + return (struct wm_ref *)&wm->tree.root->siblings; } struct wm_ref *wm_ref_topmost_child(const struct wm_ref *cursor) { @@ -152,12 +159,16 @@ struct wm_ref *wm_find(const struct wm *wm, xcb_window_t id) { struct wm_ref *wm_find_by_client(const struct wm *wm, xcb_window_t client) { auto node = wm_tree_find(&wm->tree, client); - return node != NULL ? (struct wm_ref *)&wm_tree_find_toplevel_for(node)->siblings - : NULL; + 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(struct wm_ref *cursor) { - return (struct wm_ref *)&wm_tree_find_toplevel_for(to_tree_node_mut(cursor))->siblings; +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 wm_ref *wm_ref_client_of(struct wm_ref *cursor) { @@ -165,29 +176,44 @@ struct wm_ref *wm_ref_client_of(struct wm_ref *cursor) { return client != NULL ? (struct wm_ref *)&client->siblings : NULL; } -void wm_remove(struct wm *wm, struct wm_ref *w) { - wm_tree_destroy_window(&wm->tree, (struct wm_tree_node *)w); +struct wm_ref *wm_stack_end(struct wm *wm) { + return (struct wm_ref *)&wm->tree.root->children; } -struct wm_ref *wm_stack_end(struct wm *wm) { - return (struct wm_ref *)&wm->root->children; +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 -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) { - wm_tree_move_to_above(&wm->tree, to_tree_node_mut(cursor), to_tree_node_mut(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; + } + wm_tree_move_to_above(&wm->tree, node, to_tree_node_mut(below)); } void wm_stack_move_to_end(struct wm *wm, struct wm_ref *cursor, bool to_bottom) { - wm_tree_move_to_end(&wm->tree, to_tree_node_mut(cursor), 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); - wm->incompletes = dynarr_new(struct wm_tree_node *, 4); - wm->masked = dynarr_new(struct wm_tree_node *, 8); + list_init_head(&wm->orphan_root.children); + wm->pending_query_trees = dynarr_new(struct wm_query_tree_request *, 0); return wm; } @@ -195,47 +221,72 @@ 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). - 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); + 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); - dynarr_free_pod(wm->incompletes); - dynarr_free_pod(wm->masked); + assert(wm_is_consistent(wm)); + assert(list_is_empty(&wm->orphan_root.children)); + dynarr_free_pod(wm->pending_query_trees); free(wm); } -void wm_destroy(struct wm *wm, xcb_window_t wid) { - auto masked = wm_find_masked(wm, wid); - if (masked != -1) { - free(wm->masked[masked]); - dynarr_remove_swap(wm->masked, (size_t)masked); - return; +/// 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); + } + HASH_DEL(wm->tree.nodes, node); + free(node); } +} +void wm_destroy(struct wm *wm, xcb_window_t wid) { struct wm_tree_node *node = wm_tree_find(&wm->tree, wid); if (!node) { - log_error("Trying to destroy window %#010x, but it's not in the tree. " - "Expect malfunction.", - wid); + if (wm_is_consistent(wm)) { + log_error("Window %#010x destroyed, but it's not in our tree.", wid); + } return; } - auto index = dynarr_find_pod(wm->incompletes, node); - if (index != -1) { - dynarr_remove_swap(wm->incompletes, (size_t)index); - } + log_debug("Destroying window %#010x", wid); - wm_tree_destroy_window(&wm->tree, node); + if (!list_is_empty(&node->children)) { + log_error("Window %#010x is destroyed but it still has children", wid); + } + wm_tree_detach(&wm->tree, node); + // 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) { @@ -245,185 +296,229 @@ void wm_reap_zombie(struct wm_ref *zombie) { 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 delete the window here if parent is not found, or if the parent is - // an incomplete import. We will rediscover this window later in - // `wm_complete_import`. Keeping it around will only confuse us. - bool should_forget = - new_parent == NULL || dynarr_find_pod(wm->incompletes, new_parent) != -1; + + // 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) { - log_debug("Reparenting window %#010x which is not in our tree. Assuming " - "it came from fog of war.", - wid); - if (!should_forget) { - wm_import_incomplete(wm, wid, parent); - } - return; - } - if (should_forget) { - log_debug("Window %#010x reparented to window %#010x which is " - "%s, forgetting and masking instead.", - window->id.x, parent, - new_parent == NULL ? "not in our tree" : "an incomplete import"); - - wm_tree_detach(&wm->tree, window); - for (auto curr = window; curr != NULL;) { - auto next = wm_tree_next(curr, window); - HASH_DEL(wm->tree.nodes, curr); - auto incomplete_index = dynarr_find_pod(wm->incompletes, curr); - if (incomplete_index != -1) { - dynarr_remove_swap(wm->incompletes, (size_t)incomplete_index); - // Incomplete imports cannot have children. - assert(list_is_empty(&curr->children)); - - // Incomplete imports won't have event masks set, so we - // don't need to mask them. - free(curr); - } else { - dynarr_push(wm->masked, curr); - } - curr = next; + if (wm_is_consistent(wm)) { + log_error("Window %#010x reparented, but it's not in " + "our tree.", + wid); } return; } - if (new_parent == window->parent) { - log_debug("Reparenting window %#010x to its current parent %#010x, " - "moving it to the top.", - wid, parent); + 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_tree_detach(&wm->tree, window); - wm_tree_attach(&wm->tree, window, new_parent); + + // 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); + } } 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); } -void wm_import_incomplete(struct wm *wm, xcb_window_t wid, xcb_window_t parent) { - auto masked = wm_find_masked(wm, wid); - if (masked != -1) { - // A new window created with the same wid means the window we chose to - // forget has been deleted (without us knowing), and its ID was then - // reused. - free(wm->masked[masked]); - dynarr_remove_swap(wm->masked, (size_t)masked); - } - auto parent_cursor = NULL; - if (parent != XCB_NONE) { - parent_cursor = wm_tree_find(&wm->tree, parent); - if (parent_cursor == NULL) { - log_error("Importing window %#010x, but its parent %#010x is not " - "in our tree, ignoring. Expect malfunction.", - wid, parent); - return; - } - } - log_debug("Importing window %#010x with parent %#010x", wid, parent); - auto new = wm_tree_new_window(&wm->tree, wid); - wm_tree_add_window(&wm->tree, new); - wm_tree_attach(&wm->tree, new, parent_cursor); - dynarr_push(wm->incompletes, new); - if (parent == XCB_NONE) { - BUG_ON(wm->root != NULL); // Can't have more than one root - wm->root = new; - } -} static const xcb_event_mask_t WM_IMPORT_EV_MASK = XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE; -static void wm_complete_import_single(struct wm *wm, struct x_connection *c, - struct atom *atoms, struct wm_tree_node *node) { - log_debug("Finishing importing window %#010x with parent %#010lx.", node->id.x, - node->parent != NULL ? node->parent->id.x : XCB_NONE); - x_set_error_action_ignore( - c, xcb_change_window_attributes(c->c, node->id.x, XCB_CW_EVENT_MASK, - (const uint32_t[]){WM_IMPORT_EV_MASK})); - if (wid_has_prop(c->c, node->id.x, atoms->aWM_STATE)) { - wm_tree_set_wm_state(&wm->tree, node, true); + +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; } - auto masked = wm_find_masked(wm, node->id.x); - if (masked != -1) { - free(wm->masked[masked]); - dynarr_remove_swap(wm->masked, (size_t)masked); + if (reply_or_error == NULL) { + goto out; } -} -static bool wm_complete_import_subtree(struct wm *wm, struct x_connection *c, - struct atom *atoms, struct wm_tree_node *subroot) { - bool out_of_sync = false; - wm_complete_import_single(wm, c, atoms, subroot); + auto node = req->node; - for (auto curr = subroot; curr != NULL; curr = wm_tree_next(curr, subroot)) { - // Surprise! This newly imported window might already have children. - // Although we haven't setup SubstructureNotify for it yet, it's still - // possible for another window to be reparented to it. + 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; + } - xcb_query_tree_reply_t *tree = XCB_AWAIT(xcb_query_tree, c->c, curr->id.x); - if (!tree) { - log_error("Disappearing window subtree rooted at %#010x. We are " - "out-of-sync.", - curr->id.x); - out_of_sync = true; + 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 children = xcb_query_tree_children(tree); - auto children_len = xcb_query_tree_children_length(tree); - for (int i = 0; i < children_len; i++) { - // `children` goes from bottom to top, and `wm_tree_new_window` - // puts the new window at the top of the stacking order, which - // means the windows will naturally be in the correct stacking - // order. - auto existing = wm_tree_find(&wm->tree, children[i]); - if (existing != NULL) { - // This should never happen: we haven't subscribed to - // child creation events yet, and any window reparented to - // an incomplete is deleted. report an error and try to - // recover. - auto index = dynarr_find_pod(wm->incompletes, existing); - if (index != -1) { - dynarr_remove_swap(wm->incompletes, (size_t)index); - } - log_error("Window %#010x already exists in the tree, but " - "it appeared again as a child of window " - "%#010x. Deleting the old one, but expect " - "malfunction.", - children[i], curr->id.x); - wm_tree_destroy_window(&wm->tree, existing); - } - existing = wm_tree_new_window(&wm->tree, children[i]); - wm_tree_add_window(&wm->tree, existing); - wm_tree_attach(&wm->tree, existing, curr); - wm_complete_import_single(wm, c, atoms, existing); - } - free(tree); + // If child node exists, it must be a previously orphaned node. + assert(child_node->parent == &wm->orphan_root); + wm_tree_detach(&wm->tree, child_node); + wm_tree_attach(&wm->tree, child_node, node); + } + +out: + free(req); + xcb_flush(c->c); // Actually send the requests + if (wm_is_consistent(wm)) { + wm_reap_orphans(wm); + } +} + +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; + } + + // 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; } - return !out_of_sync; + + 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); } -bool wm_complete_import(struct wm *wm, struct x_connection *c, struct atom *atoms) { - while (!dynarr_is_empty(wm->incompletes)) { - auto i = dynarr_pop(wm->incompletes); - // This function modifies `wm->incompletes`, so we can't use - // `dynarr_foreach`. - if (!wm_complete_import_subtree(wm, c, atoms, i)) { - log_debug("Out-of-sync with the X server, try to resync."); - return false; +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); } + + wm_tree_detach(&wm->tree, new); + // 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; + } + + { + 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); + } + + // (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); } +} - dynarr_foreach(wm->masked, m) { - free(*m); +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; } - dynarr_clear_pod(wm->masked); - return true; + wm_import_start_no_flush(wm, c, atoms, wid, parent_node); + xcb_flush(c->c); // Actually send the requests } -bool wm_has_incomplete_imports(const struct wm *wm) { - return !dynarr_is_empty(wm->incompletes); +bool wm_is_consistent(const struct wm *wm) { + return dynarr_is_empty(wm->pending_query_trees); } bool wm_has_tree_changes(const struct wm *wm) { @@ -452,3 +547,11 @@ struct wm_change wm_dequeue_change(struct wm *wm) { } return ret; } + +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 7c8f58f651..7fa9391bb8 100644 --- a/src/wm/wm.h +++ b/src/wm/wm.h @@ -98,6 +98,9 @@ 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); @@ -115,12 +118,10 @@ void wm_set_active_leader(struct wm *wm, struct wm_ref *leader); /// Find a window in the hash table from window id. struct wm_ref *attr_pure wm_find(const struct wm *wm, xcb_window_t id); -/// Remove a window from the hash table. -void wm_remove(struct wm *wm, struct wm_ref *w); // Find the WM frame of a client window. `id` is the client window id. 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(struct wm_ref *cursor); +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); @@ -154,59 +155,57 @@ 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); -/// Create a tree node for `wid`, with `parent` as its parent. The parent must already -/// be in the window tree. This function creates a placeholder tree node, without -/// contacting the X server, thus can be called outside of the X critical section. The -/// expectation is that the caller will later call `wm_complete_import` inside the -/// X critical section to complete the import. +/// Start the import process for `wid`. /// -/// ## NOTE +/// 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 complicated dance is because we want to catch all windows ever -/// created on X server's side. For a newly created windows, we will setup a -/// SubstructureNotify event mask to catch any new windows created under it. But between -/// the time we received the creation event and the time we setup the event mask, if any -/// windows were created under the new window, we will miss them. Therefore we have to -/// scan the new windows in X critical section so they won't change as we scan. +/// 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. /// -/// On the other hand, we can't push everything to the X critical section, because -/// updating the window stack requires knowledge of all windows in the stack. Think -/// ConfigureNotify, if we don't know about the `above_sibling` window, we don't know -/// where to put the window. So we need to create an incomplete import first. +/// 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. /// -/// But wait, this is actually way more involved. Because we choose to not set up event -/// masks for incomplete imports (we can also choose otherwise, but that also has its own -/// set of problems), there is a whole subtree of windows we don't know about. And those -/// windows might involve in reparent events. To handle that, we essentially put "fog of -/// war" under any incomplete imports, anything reparented into the fog is lost, and will -/// be rediscovered later during subtree scans. If a window is reparented out of the fog, -/// then it's treated as if a brand new window was created. -/// -/// But wait again, there's more. We can delete "lost" windows on our side and unset event -/// masks, but again because this is racy, we might still receive some events for those -/// windows. So we have to keep a list of "lost" windows, and correctly ignore events sent -/// for them. (And since we are actively ignoring events from these windows, we might as -/// well not unset their event masks, saves us a trip to the X server). +/// 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_incomplete(struct wm *wm, xcb_window_t wid, xcb_window_t parent); - -/// Check if there are any incomplete imports in the window tree. -bool wm_has_incomplete_imports(const struct wm *wm); +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); -/// Complete the previous incomplete imports by querying the X server. This function will -/// recursively import all children of previously created placeholders, and add them to -/// the window tree. This function must be called from the X critical section. This -/// function also subscribes to SubstructureNotify events for all newly imported windows, -/// as well as for the (now completed) placeholder windows. -/// -/// Returns false if our internal state is behind the X server's. In other words, if a -/// window we think should be there, does not in fact exist. Returns true otherwise. -bool wm_complete_import(struct wm *wm, struct x_connection *c, struct atom *atoms); +struct wm_change wm_dequeue_change(struct wm *wm); -bool wm_is_wid_masked(struct wm *wm, xcb_window_t wid); +/// 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); -struct wm_change wm_dequeue_change(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 index e155e39031..d70db89aca 100644 --- a/src/wm/wm_internal.h +++ b/src/wm/wm_internal.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -78,7 +77,10 @@ struct wm_tree_change { /// 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); -struct wm_tree_node *attr_pure wm_tree_find_toplevel_for(struct wm_tree_node *node); +/// 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 @@ -93,7 +95,8 @@ void wm_tree_destroy_window(struct wm_tree *tree, struct wm_tree_node *node); /// its parent, and the disconnected tree nodes won't be able to be found via /// `wm_tree_find`. Relevant events will be generated. void wm_tree_detach(struct wm_tree *tree, struct wm_tree_node *subroot); -/// Attach `node` to `parent`. `node` becomes the topmost child of `parent`. +/// 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, From 15ed5bd79ff2ca7966500344ee8ee4d50f56ea1f Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 26 Jun 2024 15:24:43 +0100 Subject: [PATCH 32/56] misc: fix comment formatting in handle_pending_updates Signed-off-by: Yuxuan Shui --- src/picom.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/picom.c b/src/picom.c index 843972f61e..c05d2d7dca 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1666,15 +1666,15 @@ static void handle_pending_updates(struct session *ps, double delta_t) { ps->server_grabbed = true; // Catching up with X server - /// Handle all events X server has sent us so far, so that our internal state will - /// catch up with the X server's state. It only makes sense to call this function - /// in the X critical section, otherwise we will be chasing a moving goal post. - /// - /// (Disappointingly, grabbing the X server doesn't actually prevent the X server - /// state from changing. It should, but in practice we have observed window still - /// being destroyed while we have the server grabbed. This is very disappointing - /// and forces us to use some hacky code to recover when we discover we are - /// out-of-sync.) + // Handle all events X server has sent us so far, so that our internal state will + // catch up with the X server's state. It only makes sense to call this function + // in the X critical section, otherwise we will be chasing a moving goal post. + // + // (Disappointingly, grabbing the X server doesn't actually prevent the X server + // state from changing. It should, but in practice we have observed window still + // being destroyed while we have the server grabbed. This is very disappointing + // and forces us to use some hacky code to recover when we discover we are + // out-of-sync.) handle_x_events(ps); if (ps->pending_updates || wm_has_tree_changes(ps->wm)) { log_debug("Delayed handling of events, entering critical section"); From f126d47539a649fef2bc992f0e3307cef3fbae95 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Wed, 26 Jun 2024 22:30:29 +0100 Subject: [PATCH 33/56] wm/tree: fix uninitialized tree->gen in unit test Signed-off-by: Yuxuan Shui --- src/wm/wm_internal.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wm/wm_internal.h b/src/wm/wm_internal.h index d70db89aca..f9a8c9ffb7 100644 --- a/src/wm/wm_internal.h +++ b/src/wm/wm_internal.h @@ -109,6 +109,7 @@ void wm_tree_set_wm_state(struct wm_tree *tree, struct wm_tree_node *node, bool 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); } From 04f602fb84c9866db5aeea967328ebe966ee8e40 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Jun 2024 18:09:47 +0100 Subject: [PATCH 34/56] core: move debug window initialization to after root geometry query Signed-off-by: Yuxuan Shui --- src/picom.c | 73 ++++++++++++++++++++++++++--------------------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/src/picom.c b/src/picom.c index c05d2d7dca..14689bc870 100644 --- a/src/picom.c +++ b/src/picom.c @@ -2355,8 +2355,6 @@ static session_t *session_init(int argc, char **argv, Display *dpy, goto err; } - rebuild_screen_reg(ps); - bool compositor_running = false; if (session_redirection_mode(ps) == XCB_COMPOSITE_REDIRECT_MANUAL) { // We are running in the manual redirection mode, meaning we are running @@ -2368,44 +2366,10 @@ 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); @@ -2547,6 +2511,41 @@ static session_t *session_init(int argc, char **argv, Display *dpy, exit(1); } + 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. + + // 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; + } + + if (session_redirection_mode(ps) == XCB_COMPOSITE_REDIRECT_MANUAL && !init_overlay(ps)) { + goto err; + } + + 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 + + // 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->pending_updates = true; write_pid(ps); From 732f36536baefc9679f4089d86abe85b30439372 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Jun 2024 19:41:07 +0100 Subject: [PATCH 35/56] wm/win: don't cache window monitor We need to update which monitor a window is on when either the window position/geometry changes, or when monitor configuration changes. We only did former. We could fix that by also doing the latter. But there isn't that much point in caching the monitor anyway, so let's just calculate it when needed. Signed-off-by: Yuxuan Shui --- src/event.c | 3 --- src/render.c | 26 ++++++++++++++------------ src/renderer/command_builder.c | 9 ++++++--- src/wm/win.c | 19 ++++++++----------- src/wm/win.h | 4 +--- 5 files changed, 29 insertions(+), 32 deletions(-) diff --git a/src/event.c b/src/event.c index 77b20cadaf..67cdaaaffa 100644 --- a/src/event.c +++ b/src/event.c @@ -277,9 +277,6 @@ static void configure_win(session_t *ps, xcb_configure_notify_event_t *ce) { 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, w); } // override_redirect flag cannot be changed after window creation, as far diff --git a/src/render.c b/src/render.c index 1e80623ae6..a20651954a 100644 --- a/src/render.c +++ b/src/render.c @@ -1121,18 +1121,20 @@ void paint_all(session_t *ps, struct 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/renderer/command_builder.c b/src/renderer/command_builder.c index b4228cd435..a2aa1b9614 100644 --- a/src/renderer/command_builder.c +++ b/src/renderer/command_builder.c @@ -135,9 +135,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) { diff --git a/src/wm/win.c b/src/wm/win.c index af3bde3055..eef92171dc 100644 --- a/src/wm/win.c +++ b/src/wm/win.c @@ -538,8 +538,6 @@ void win_process_update_flags(session_t *ps, struct 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)) { @@ -1401,7 +1399,6 @@ struct win *win_maybe_allocate(session_t *ps, struct wm_ref *cursor) { .focused_force = UNSET, .invert_color_force = UNSET, - .randr_monitor = -1, .mode = WMODE_TRANS, .leader = XCB_NONE, .cache_leader = XCB_NONE, @@ -1983,9 +1980,10 @@ void unmap_win_start(struct 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 = { @@ -2158,24 +2156,23 @@ bool win_process_animation_and_state_change(struct session *ps, struct win *w, d #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 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)", 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", 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 diff --git a/src/wm/win.h b/src/wm/win.h index d82a0fb90b..9e498e3591 100644 --- a/src/wm/win.h +++ b/src/wm/win.h @@ -114,8 +114,6 @@ struct 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 @@ -372,7 +370,7 @@ void win_on_client_update(session_t *ps, struct win *w); bool attr_pure win_should_dim(session_t *ps, const struct win *w); -void win_update_monitor(struct x_monitors *monitors, struct 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 win *w); From 02a782ef324cc930b193f5fa0ae79c6b0fad6069 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Jun 2024 11:08:47 +0100 Subject: [PATCH 36/56] wm/win: remove WIN_FLAGS_PIXMAP_NONE The way we set and use it, it's completely equivalent to checking if w->win_image == NULL. So it's redundant. Signed-off-by: Yuxuan Shui --- src/wm/defs.h | 2 -- src/wm/win.c | 30 ++++++++++-------------------- src/wm/win.h | 2 +- 3 files changed, 11 insertions(+), 23 deletions(-) diff --git a/src/wm/defs.h b/src/wm/defs.h index cc2abb1dd4..f63e89e2d0 100644 --- a/src/wm/defs.h +++ b/src/wm/defs.h @@ -50,8 +50,6 @@ 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, /// the client window needs to be updated diff --git a/src/wm/win.c b/src/wm/win.c index eef92171dc..1c7c04a191 100644 --- a/src/wm/win.c +++ b/src/wm/win.c @@ -310,13 +310,10 @@ void add_damage_from_win(session_t *ps, const struct win *w) { /// Release the images attached to this window 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); - assert(w->win_image); 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); } @@ -366,7 +363,6 @@ static inline bool win_bind_pixmap(struct backend_base *b, struct win *w) { return false; } - win_clear_flags(w, WIN_FLAGS_PIXMAP_NONE); return true; } @@ -374,12 +370,9 @@ 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); } @@ -582,11 +575,10 @@ void win_process_image_flags(session_t *ps, struct win *w) { // 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); - } + + // Must release images first, otherwise breaks + // NVIDIA driver + win_release_pixmap(ps->backend_data, w); win_bind_pixmap(ps->backend_data, w); } @@ -845,9 +837,7 @@ void unmap_win_finish(session_t *ps, struct win *w) { 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); @@ -1389,9 +1379,9 @@ struct win *win_maybe_allocate(session_t *ps, struct wm_ref *cursor) { .frame_opacity = 1.0, .in_openclose = true, // set to false after first map is done, // true here because window is just created - .flags = WIN_FLAGS_PIXMAP_NONE, // updated by - // property/attributes/etc - // change + .flags = 0, // updated by + // property/attributes/etc + // change // Runtime variables, updated by dbus .fade_force = UNSET, diff --git a/src/wm/win.h b/src/wm/win.h index 9e498e3591..867fc0b296 100644 --- a/src/wm/win.h +++ b/src/wm/win.h @@ -353,7 +353,7 @@ 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 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 win *w); From 3be52b4ed594b47505c25787383ce7330e46070b Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Jun 2024 11:19:59 +0100 Subject: [PATCH 37/56] wm/win: make sure new window pixmap is named before releasing the old one Might break on NVIDIA, needs testing. Signed-off-by: Yuxuan Shui --- src/picom.c | 2 +- src/wm/defs.h | 4 +-- src/wm/win.c | 80 +++++++++++++++++++++------------------------------ 3 files changed, 35 insertions(+), 51 deletions(-) diff --git a/src/picom.c b/src/picom.c index 14689bc870..cf1d752cd1 100644 --- a/src/picom.c +++ b/src/picom.c @@ -941,7 +941,7 @@ static bool paint_preprocess(session_t *ps, bool *animation, struct win **out_bo } 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; } diff --git a/src/wm/defs.h b/src/wm/defs.h index f63e89e2d0..801aea6efe 100644 --- a/src/wm/defs.h +++ b/src/wm/defs.h @@ -50,8 +50,8 @@ enum win_flags { /// pixmap is out of date, will be update in win_process_flags WIN_FLAGS_PIXMAP_STALE = 1, - /// 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/win.c b/src/wm/win.c index 1c7c04a191..3007e96d74 100644 --- a/src/wm/win.c +++ b/src/wm/win.c @@ -343,29 +343,6 @@ static inline void win_release_mask(backend_t *base, struct win *w) { } } -static inline bool win_bind_pixmap(struct backend_base *b, struct 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, win_id(w), pixmap)); - if (e) { - log_error("Failed to get named pixmap for window %#010x(%s)", win_id(w), - w->name); - free(e); - return false; - } - log_debug("New named pixmap for %#010x (%s) : %#010x", win_id(w), 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; - } - - return true; -} - 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 @@ -561,34 +538,41 @@ void win_process_image_flags(session_t *ps, struct 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_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_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; + } - // 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); } } @@ -848,7 +832,7 @@ void unmap_win_finish(session_t *ps, struct 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); } From c03603a19f573731e62228d752419409e8bfa86e Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Jun 2024 11:21:59 +0100 Subject: [PATCH 38/56] core: move refresh_images out of the X critical section Signed-off-by: Yuxuan Shui --- src/picom.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/picom.c b/src/picom.c index cf1d752cd1..502cd4ea6b 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1691,9 +1691,6 @@ static void handle_pending_updates(struct session *ps, double delta_t) { 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) { @@ -1703,9 +1700,13 @@ static void handle_pending_updates(struct session *ps, double delta_t) { } ps->server_grabbed = false; - ps->pending_updates = false; log_trace("Exited critical section"); + // Process window flags (stale images) + refresh_images(ps); + + ps->pending_updates = false; + wm_stack_foreach_safe(ps->wm, cursor, tmp) { auto w = wm_ref_deref(cursor); BUG_ON(w != NULL && w->tree_ref != cursor); From 4d554185e8f7e10c455050a5f875c20ff7287f05 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Jun 2024 11:22:29 +0100 Subject: [PATCH 39/56] core: move recheck_focus out of the X critical section Signed-off-by: Yuxuan Shui --- src/picom.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/picom.c b/src/picom.c index 502cd4ea6b..a046132463 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1689,8 +1689,6 @@ static void handle_pending_updates(struct session *ps, double delta_t) { // Process window flags refresh_windows(ps); - - recheck_focus(ps); } e = xcb_request_check(ps->c.c, xcb_ungrab_server_checked(ps->c.c)); if (e) { @@ -1702,6 +1700,8 @@ static void handle_pending_updates(struct session *ps, double delta_t) { ps->server_grabbed = false; log_trace("Exited critical section"); + recheck_focus(ps); + // Process window flags (stale images) refresh_images(ps); From 55397478a56bc4b78d172672b1175b6ec876287b Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Jun 2024 12:52:11 +0100 Subject: [PATCH 40/56] wm: make sure `win->tree_ref` is never dangling Previously `win->tree_ref` could reference a freed tree node up until all tree changes are handled in `handle_new_windows`. The result is that certain operations can be only performed after all tree changes are handled, because they might use `win->tree_ref`. Such operations include updating the focused window, for example. This commit makes sure `win->tree_ref` is valid at all times. Also split up wm_tree_enqueue_change, because enqueueing a TOPLEVEL_KILLED now needs to return whether the event cancels out a previous TOPLEVEL_NEW, in which case the zombie is freed and there's no need for tree_ref update. Signed-off-by: Yuxuan Shui --- src/picom.c | 2 - src/wm/tree.c | 246 +++++++++++++++++++++++-------------------- src/wm/wm.c | 36 ++++++- src/wm/wm_internal.h | 4 +- 4 files changed, 167 insertions(+), 121 deletions(-) diff --git a/src/picom.c b/src/picom.c index a046132463..3993cada39 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1597,8 +1597,6 @@ static void handle_new_windows(session_t *ps) { case WM_TREE_CHANGE_TOPLEVEL_KILLED: w = wm_ref_deref(wm_change.toplevel); if (w != NULL) { - // Pointing the window tree_ref to the zombie. - w->tree_ref = wm_change.toplevel; win_destroy_start(ps, w); } else { // This window is not managed, no point keeping the zombie diff --git a/src/wm/tree.c b/src/wm/tree.c index 99e195d30f..bbd452aee0 100644 --- a/src/wm/tree.c +++ b/src/wm/tree.c @@ -34,78 +34,8 @@ void wm_tree_reap_zombie(struct wm_tree_node *zombie) { free(zombie); } +/// Enqueue a tree change. static void wm_tree_enqueue_change(struct wm_tree *tree, struct wm_tree_change change) { - if (change.type == WM_TREE_CHANGE_TOPLEVEL_KILLED) { - // 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, change.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 = change.killed; - } - } - if (found) { - wm_tree_reap_zombie(change.killed); - return; - } - } else if (change.type == WM_TREE_CHANGE_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, change.toplevel) || - i->item.type != WM_TREE_CHANGE_CLIENT) { - continue; - } - - if (!wm_treeid_eq(i->item.client.new_, change.client.old)) { - log_warn("Inconsistent client change for toplevel " - "%#010x. Missing changes from %#010x to %#010x. " - "Possible bug.", - change.toplevel.x, i->item.client.new_.x, - change.client.old.x); - } - - i->item.client.new_ = change.client.new_; - if (wm_treeid_eq(i->item.client.old, change.client.new_)) { - list_remove(&i->siblings); - list_insert_after(&tree->free_changes, &i->siblings); - } - return; - } - } else if (change.type == WM_TREE_CHANGE_TOPLEVEL_RESTACKED) { - 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; - } - } - } - - // We don't let a `WM_TREE_CHANGE_TOPLEVEL_NEW` cancel out a previous - // `WM_TREE_CHANGE_TOPLEVEL_GONE`, 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). - struct wm_tree_change_list *change_list; if (!list_is_empty(&tree->free_changes)) { change_list = list_entry(tree->free_changes.next, @@ -119,6 +49,114 @@ static void wm_tree_enqueue_change(struct wm_tree *tree, struct wm_tree_change c 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) { @@ -222,22 +260,15 @@ void wm_tree_set_wm_state(struct wm_tree *tree, struct wm_tree_node *node, bool return; } - struct wm_tree_change change = { - .toplevel = toplevel->id, - .type = WM_TREE_CHANGE_CLIENT, - .client = {.toplevel = toplevel}, - }; if (!has_wm_state && toplevel->client_window == node) { auto new_client = wm_tree_find_client(toplevel); toplevel->client_window = new_client; - change.client.old = node->id; - change.client.new_ = new_client != NULL ? new_client->id : WM_TREEID_NONE; - wm_tree_enqueue_change(tree, change); + 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; - change.client.old = WM_TREEID_NONE; - change.client.new_ = node->id; - wm_tree_enqueue_change(tree, change); + 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. @@ -268,27 +299,24 @@ wm_tree_refresh_client_and_queue_change(struct wm_tree *tree, struct wm_tree_nod BUG_ON(toplevel->parent->parent != NULL); auto new_client = wm_tree_find_client(toplevel); if (new_client != toplevel->client_window) { - struct wm_tree_change change = {.toplevel = toplevel->id, - .type = WM_TREE_CHANGE_CLIENT, - .client = {.toplevel = toplevel, - .old = WM_TREEID_NONE, - .new_ = WM_TREEID_NONE}}; + wm_treeid old_client_id = WM_TREEID_NONE, new_client_id = WM_TREEID_NONE; if (toplevel->client_window != NULL) { - change.client.old = toplevel->client_window->id; + old_client_id = toplevel->client_window->id; } if (new_client != NULL) { - change.client.new_ = new_client->id; + new_client_id = new_client->id; } toplevel->client_window = new_client; - wm_tree_enqueue_change(tree, change); + wm_tree_enqueue_client_change(tree, toplevel, old_client_id, new_client_id); } } -void wm_tree_detach(struct wm_tree *tree, struct wm_tree_node *subroot) { +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) { @@ -296,21 +324,18 @@ void wm_tree_detach(struct wm_tree *tree, struct wm_tree_node *subroot) { } } else { // Detached a toplevel, create a zombie for it - auto zombie = ccalloc(1, struct wm_tree_node); + zombie = ccalloc(1, struct wm_tree_node); zombie->parent = subroot->parent; zombie->id = subroot->id; zombie->is_zombie = true; - zombie->win = subroot->win; - subroot->win = NULL; list_init_head(&zombie->children); list_replace(&subroot->siblings, &zombie->siblings); - wm_tree_enqueue_change(tree, (struct wm_tree_change){ - .toplevel = subroot->id, - .type = WM_TREE_CHANGE_TOPLEVEL_KILLED, - .killed = zombie, - }); + 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, @@ -329,11 +354,7 @@ void wm_tree_attach(struct wm_tree *tree, struct wm_tree_node *child, auto toplevel = wm_tree_find_toplevel_for(tree, child); if (child == toplevel) { - wm_tree_enqueue_change(tree, (struct wm_tree_change){ - .toplevel = child->id, - .type = WM_TREE_CHANGE_TOPLEVEL_NEW, - .new_ = child, - }); + wm_tree_enqueue_toplevel_new(tree, child); } else if (toplevel != NULL) { wm_tree_refresh_client_and_queue_change(tree, toplevel); } @@ -355,9 +376,7 @@ void wm_tree_move_to_end(struct wm_tree *tree, struct wm_tree_node *node, bool t list_insert_after(&node->parent->children, &node->siblings); } if (node->parent == tree->root) { - wm_tree_enqueue_change(tree, (struct wm_tree_change){ - .type = WM_TREE_CHANGE_TOPLEVEL_RESTACKED, - }); + wm_tree_enqueue_toplevel_restacked(tree); } } @@ -377,9 +396,7 @@ void wm_tree_move_to_above(struct wm_tree *tree, struct wm_tree_node *node, list_remove(&node->siblings); list_insert_before(&other->siblings, &node->siblings); if (node->parent == tree->root) { - wm_tree_enqueue_change(tree, (struct wm_tree_change){ - .type = WM_TREE_CHANGE_TOPLEVEL_RESTACKED, - }); + wm_tree_enqueue_toplevel_restacked(tree); } } @@ -433,7 +450,7 @@ TEST_CASE(tree_manipulation) { assert(change.toplevel.x == 3); assert(change.type == WM_TREE_CHANGE_TOPLEVEL_NEW); - wm_tree_detach(&tree, node2); + auto zombie = wm_tree_detach(&tree, node2); wm_tree_attach(&tree, node2, node3); assert(node2->parent == node3); assert(node3->children.next == &node2->siblings); @@ -442,6 +459,7 @@ TEST_CASE(tree_manipulation) { 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); @@ -462,7 +480,7 @@ TEST_CASE(tree_manipulation) { // node3 already has node2 as its client window, so the new one should be ignored. assert(change.type == WM_TREE_CHANGE_NONE); - wm_tree_detach(&tree, node2); + TEST_EQUAL(wm_tree_detach(&tree, node2), NULL); HASH_DEL(tree.nodes, node2); free(node2); change = wm_tree_dequeue_change(&tree); @@ -472,7 +490,7 @@ TEST_CASE(tree_manipulation) { assert(change.client.new_.x == 4); // Test window ID reuse - wm_tree_detach(&tree, node4); + TEST_EQUAL(wm_tree_detach(&tree, node4), NULL); HASH_DEL(tree.nodes, node4); free(node4); node4 = wm_tree_new_window(&tree, 4); @@ -489,7 +507,7 @@ TEST_CASE(tree_manipulation) { auto node5 = wm_tree_new_window(&tree, 5); wm_tree_add_window(&tree, node5); wm_tree_attach(&tree, node5, root); - wm_tree_detach(&tree, node5); + TEST_EQUAL(wm_tree_detach(&tree, node5), NULL); HASH_DEL(tree.nodes, node5); free(node5); change = wm_tree_dequeue_change(&tree); diff --git a/src/wm/wm.c b/src/wm/wm.c index 58e75c5eac..1403eb8385 100644 --- a/src/wm/wm.c +++ b/src/wm/wm.c @@ -265,6 +265,15 @@ static void wm_reap_orphans(struct wm *wm) { } } +/// 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; +} + void wm_destroy(struct wm *wm, xcb_window_t wid) { struct wm_tree_node *node = wm_tree_find(&wm->tree, wid); if (!node) { @@ -279,7 +288,11 @@ void wm_destroy(struct wm *wm, xcb_window_t wid) { if (!list_is_empty(&node->children)) { log_error("Window %#010x is destroyed but it still has children", wid); } - wm_tree_detach(&wm->tree, node); + 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.) @@ -315,7 +328,11 @@ void wm_reparent(struct wm *wm, xcb_window_t wid, xcb_window_t parent) { return; } - wm_tree_detach(&wm->tree, window); + 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 @@ -390,7 +407,13 @@ wm_handle_query_tree_reply(struct x_connection *c, struct x_async_request_base * // If child node exists, it must be a previously orphaned node. assert(child_node->parent == &wm->orphan_root); - wm_tree_detach(&wm->tree, child_node); + 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); } @@ -463,7 +486,12 @@ static void wm_import_start_no_flush(struct wm *wm, struct x_connection *c, stru assert(false); } - wm_tree_detach(&wm->tree, new); + 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++; diff --git a/src/wm/wm_internal.h b/src/wm/wm_internal.h index f9a8c9ffb7..e48ec16e09 100644 --- a/src/wm/wm_internal.h +++ b/src/wm/wm_internal.h @@ -94,7 +94,9 @@ 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. -void wm_tree_detach(struct wm_tree *tree, struct wm_tree_node *subroot); +/// +/// 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, From c1fb983448ed4e9fb7b15dd39e047e6128508009 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Jun 2024 12:02:31 +0100 Subject: [PATCH 41/56] core: make focus update async This way focus changes are processed in order with all other events, so we are less likely to run into the issue where the focused window isn't yet in our window tree. Known issue: it is still possible for the window to not be in our window tree. And when that happens, we won't update focus again after the focused window is imported. Signed-off-by: Yuxuan Shui --- src/event.c | 134 +++++++++++++++++++++++++++++++++++++++++++++++++--- src/picom.c | 77 ------------------------------ 2 files changed, 128 insertions(+), 83 deletions(-) diff --git a/src/event.c b/src/event.c index 67cdaaaffa..a1ca7fc746 100644 --- a/src/event.c +++ b/src/event.c @@ -190,16 +190,128 @@ static inline const char *attr_pure ev_focus_detail_name(xcb_focus_in_event_t *e #undef CASESTRRET +struct ev_ewmh_active_win_request { + struct x_async_request_base base; + session_t *ps; +}; + +/// 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); + } +} + +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 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; + } + + 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 }\n", ev_focus_mode_name(ev), + log_debug("{ mode: %s, detail: %s }", ev_focus_mode_name(ev), ev_focus_detail_name(ev)); - ps->pending_updates = true; + 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 }\n", ev_focus_mode_name(ev), + log_debug("{ mode: %s, detail: %s }", ev_focus_mode_name(ev), ev_focus_detail_name(ev)); - ps->pending_updates = true; + ev_focus_change(ps); } static inline void ev_create_notify(session_t *ps, xcb_create_notify_event_t *ev) { @@ -425,6 +537,8 @@ static inline void ev_expose(session_t *ps, xcb_expose_event_t *ev) { } 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( @@ -442,8 +556,16 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t if (ps->c.screen_info->root == ev->window) { 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)) { diff --git a/src/picom.c b/src/picom.c index 3993cada39..e94eafac96 100644 --- a/src/picom.c +++ b/src/picom.c @@ -449,81 +449,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 - bool exists; - xcb_window_t wid = wid_get_prop_window(&ps->c, ps->c.screen_info->root, - ps->atoms->a_NET_ACTIVE_WINDOW, &exists); - - 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); - } -} - -/** - * 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; - } - - auto cursor = wm_find(ps->wm, wid); - if (cursor == NULL) { - log_error("Window %#010x not found in window tree.", wid); - 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("%#010" PRIx32 " (%#010" PRIx32 " \"%s\") focused.", wid, - win_id(w), w->name); - win_set_focused(ps, w); - } -} - /** * Rebuild cached screen_reg. */ @@ -1698,8 +1623,6 @@ static void handle_pending_updates(struct session *ps, double delta_t) { ps->server_grabbed = false; log_trace("Exited critical section"); - recheck_focus(ps); - // Process window flags (stale images) refresh_images(ps); From 496f288b46c61613e81516fa0dcb745143ea0c2e Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Jun 2024 12:13:59 +0100 Subject: [PATCH 42/56] wm/win: rename is_ewmh_focused to is_focused The old name is inaccurate, this flag is not limited to ewmh. Signed-off-by: Yuxuan Shui --- src/wm/win.c | 10 +++++----- src/wm/win.h | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/wm/win.c b/src/wm/win.c index 3007e96d74..3ea3c003c7 100644 --- a/src/wm/win.c +++ b/src/wm/win.c @@ -1662,17 +1662,17 @@ void win_set_focused(session_t *ps, struct win *w) { } 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); @@ -2306,5 +2306,5 @@ bool win_is_bypassing_compositor(const session_t *ps, const struct win *w) { * settings */ bool win_is_focused_raw(const struct win *w) { - return w->a.map_state == XCB_MAP_STATE_VIEWABLE && w->is_ewmh_focused; + 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 867fc0b296..e76a7ade1d 100644 --- a/src/wm/win.h +++ b/src/wm/win.h @@ -195,8 +195,8 @@ struct 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 From f05d863cb93a2670ade157139235764c826500a7 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Jun 2024 13:43:22 +0100 Subject: [PATCH 43/56] core: make monitor updates async Moving it out of the X critical section. Signed-off-by: Yuxuan Shui --- src/event.c | 4 ++-- src/picom.c | 9 +-------- src/picom.h | 2 -- src/x.c | 34 ++++++++++++++++++++++++++-------- src/x.h | 4 ++-- 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/event.c b/src/event.c index a1ca7fc746..19feb3e3db 100644 --- a/src/event.c +++ b/src/event.c @@ -834,9 +834,9 @@ void ev_handle(session_t *ps, xcb_generic_event_t *ev) { 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/picom.c b/src/picom.c index e94eafac96..b9f5250c5f 100644 --- a/src/picom.c +++ b/src/picom.c @@ -734,13 +734,6 @@ 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; @@ -2330,7 +2323,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); } { diff --git a/src/picom.h b/src/picom.h index f6f7b30994..9be745e000 100644 --- a/src/picom.h +++ b/src/picom.h @@ -25,8 +25,6 @@ #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 }; diff --git a/src/x.c b/src/x.c index 6291306980..538eab63f4 100644 --- a/src/x.c +++ b/src/x.c @@ -904,25 +904,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) { diff --git a/src/x.h b/src/x.h index 6afc5ddcab..68bedccb7c 100644 --- a/src/x.h +++ b/src/x.h @@ -419,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 *); From 0f6ffae91c806819f826d6e00d0233af956aecac Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Jun 2024 19:00:08 +0100 Subject: [PATCH 44/56] wm/win: don't start animation if window image isn't available This is an alternative fix for #357. Previous fix is moving configure_root into the X critical section, and now we are removing the critical section so we can't do that anymore. Signed-off-by: Yuxuan Shui --- src/wm/win.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/wm/win.c b/src/wm/win.c index 3ea3c003c7..a1802f9031 100644 --- a/src/wm/win.c +++ b/src/wm/win.c @@ -2003,10 +2003,17 @@ double win_animatable_get(const struct win *w, enum win_script_output output) { 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; From d8126479030af43149cc6984af83ff046193d84f Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Jun 2024 13:49:34 +0100 Subject: [PATCH 45/56] core: move configure_root out of the critical section It doesn't really make any X requests besides querying the root window geometry. Signed-off-by: Yuxuan Shui --- src/event.c | 3 ++- src/picom.c | 21 +-------------------- src/picom.h | 6 +----- 3 files changed, 4 insertions(+), 26 deletions(-) diff --git a/src/event.c b/src/event.c index 19feb3e3db..932c126bbd 100644 --- a/src/event.c +++ b/src/event.c @@ -405,7 +405,8 @@ static inline void ev_configure_notify(session_t *ps, xcb_configure_notify_event } if (ev->window == ps->c.screen_info->root) { - set_root_flags(ps, ROOT_FLAGS_CONFIGURED); + configure_root(ps); + ps->pending_updates = true; } else { configure_win(ps, ev); } diff --git a/src/picom.c b/src/picom.c index b9f5250c5f..41068b5cc7 100644 --- a/src/picom.c +++ b/src/picom.c @@ -125,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); @@ -652,7 +646,7 @@ static inline void invalidate_reg_ignore(session_t *ps) { } /// 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); @@ -733,13 +727,6 @@ static void configure_root(session_t *ps) { } } -static void handle_root_flags(session_t *ps) { - 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. * @@ -1597,12 +1584,6 @@ 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); - // 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); - // Process window flags refresh_windows(ps); } diff --git a/src/picom.h b/src/picom.h index 9be745e000..73a501c64b 100644 --- a/src/picom.h +++ b/src/picom.h @@ -24,10 +24,6 @@ #include "wm/win.h" #include "x.h" -enum root_flags { - 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 @@ -41,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); From 667084d6c9ad08ddee11d763581f512b2db5818d Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Jun 2024 15:25:37 +0100 Subject: [PATCH 46/56] core: make creating managed window objects async This is to solve a race condition that was previously impossible, and only became possible because the X critical section was removed. Currently, in `handle_new_windows`, after we created a managed window object, we check if its map state is already mapped, if so, we call `win_map_start`. But there is a problem if the window is mapped before we reached `handle_new_windows`. There will be an event generated for that map, because managed windows are toplevels, and we have event mask set up for the root window at the very beginning, so we won't miss any map events from toplevels. (Except when picom has just started, and is trying to manage existing windows). There are two cases, based on whether this map event is handled before we reach `handle_new_windows`. 1) If the map event is handled after. It itself will trigger another `win_map_start` call, which is an error, and we have an assertion for that. But on the other hand, if we don't call `win_map_start` in `handle_new_windows`: 2) If the map event is handled before. It will do nothing since we haven't created a managed window object for that tree node yet. And later, since we don't call `win_map_start` in `handle_new_windows`, this window will just never be mapped. (This would be the same scenario for pre-existing windows when picom just started). All these problem came from the map event being handled out-of-order w.r.t. the reply to the GetWindowAttributes request sent by `win_maybe_allocate`. So we use async requests to make sure they are handled in order. Signed-off-by: Yuxuan Shui --- src/picom.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++++---- src/wm/win.c | 20 +++++--------- src/wm/win.h | 3 +- 3 files changed, 81 insertions(+), 19 deletions(-) diff --git a/src/picom.c b/src/picom.c index 41068b5cc7..5c140eafd0 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1483,6 +1483,66 @@ static void handle_x_events_ev(EV_P attr_unused, ev_prepare *w, int revents attr 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; + } + + 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 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; + } +} + static void handle_new_windows(session_t *ps) { // Check tree changes first, because later property updates need accurate // client window information @@ -1493,11 +1553,18 @@ static void handle_new_windows(session_t *ps) { break; } switch (wm_change.type) { - case WM_TREE_CHANGE_TOPLEVEL_NEW: - w = win_maybe_allocate(ps, wm_change.toplevel); - if (w != NULL && w->a.map_state == XCB_MAP_STATE_VIEWABLE) { - win_map_start(w); - } + 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); diff --git a/src/wm/win.c b/src/wm/win.c index a1802f9031..ce5fb51e75 100644 --- a/src/wm/win.c +++ b/src/wm/win.c @@ -1358,7 +1358,8 @@ void free_win_res(session_t *ps, struct win *w) { /// 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) { +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, .in_openclose = true, // set to false after first map is done, @@ -1395,23 +1396,18 @@ struct win *win_maybe_allocate(session_t *ps, struct wm_ref *cursor) { xcb_window_t wid = wm_ref_win_id(cursor); log_debug("Managing window %#010x", wid); - xcb_get_window_attributes_cookie_t acookie = xcb_get_window_attributes(ps->c.c, wid); - 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) { + 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 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 - free(a); return NULL; } @@ -1422,16 +1418,14 @@ struct win *win_maybe_allocate(session_t *ps, struct wm_ref *cursor) { // We only need to initialize the part that are not initialized // by map_win *new = win_def; - 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, wid), &e); if (!g) { - log_error_x_error(e, "Failed to get geometry of window %#010x", wid); + log_debug("Failed to get geometry of window %#010x: %s", wid, x_strerror(e)); free(e); free(new); return NULL; @@ -1452,7 +1446,7 @@ struct win *win_maybe_allocate(session_t *ps, struct wm_ref *cursor) { 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 NULL; diff --git a/src/wm/win.h b/src/wm/win.h index e76a7ade1d..9935b11f30 100644 --- a/src/wm/win.h +++ b/src/wm/win.h @@ -408,7 +408,8 @@ void win_get_region_frame_local(const struct win *w, region_t *res); 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 *win_maybe_allocate(session_t *ps, struct wm_ref *cursor); +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. From 90954b62d5daf90fb4812bb5f1d8cc33e9dc98d0 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Jun 2024 16:09:58 +0100 Subject: [PATCH 47/56] x: make wid_get_text_prop robust against race The property being fetched might change between x_get_prop_info and actually fetching the property. So instead we do this all in one request by setting the length to UINT_MAX. Signed-off-by: Yuxuan Shui --- src/x.c | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/x.c b/src/x.c index 538eab63f4..ae0e00fe98 100644 --- a/src/x.c +++ b/src/x.c @@ -334,39 +334,35 @@ xcb_window_t wid_get_prop_window(struct x_connection *c, xcb_window_t wid, */ 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; From a86c483ba4b8103952c7e3b72564696bfe0eb1e2 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Jun 2024 16:21:10 +0100 Subject: [PATCH 48/56] wm/win: silence some expected errors Signed-off-by: Yuxuan Shui --- src/wm/win.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/wm/win.c b/src/wm/win.c index ce5fb51e75..02e6bcf33c 100644 --- a/src/wm/win.c +++ b/src/wm/win.c @@ -1458,12 +1458,13 @@ struct win *win_maybe_allocate(session_t *ps, struct wm_ref *cursor, if (!ps->o.use_ewmh_active_win) { frame_event_mask |= XCB_EVENT_MASK_FOCUS_CHANGE; } - xcb_change_window_attributes(ps->c.c, wid, XCB_CW_EVENT_MASK, - (const uint32_t[]){frame_event_mask}); + 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, wid, 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); From 1c57ba85b736ec5acd8ccdf930880a75fa87dc71 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Jun 2024 16:56:46 +0100 Subject: [PATCH 49/56] core: make sure request sent by vblank scheduler is flushed If PresentNotifyMsc event is stuck in the out buffer, we will stop rendering frames. Signed-off-by: Yuxuan Shui --- src/picom.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/picom.c b/src/picom.c index 5c140eafd0..46a4529642 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1446,6 +1446,10 @@ static void unredirect(session_t *ps) { /// 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_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 @@ -1462,10 +1466,6 @@ static void handle_x_events(struct session *ps) { 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 = x_poll_for_event(&ps->c))) { ev_handle(ps, (xcb_generic_event_t *)ev); From 532784149ee6627aeac1fc4ccdd46255119498e8 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Jun 2024 17:18:29 +0100 Subject: [PATCH 50/56] vblank: alternative invalid present event recovery I found sometimes, when we receive an invalid PresentCompleteNotify, keep requesting new present notifys using `last_msc + 1` doesn't really help us recover. Instead X just keeps sending us bad PresentCompleteNotifys, and we stuck in a loop. (This is more likely to happen during screen resolution change. Also this did not seem to be possible previously, when we were still handling resolution changes inside the X critical section.) This commit tries to test out a different approach. The hypothesis is, maybe doing something to the screen (e.g. render and present a new frame) helps change the internal state of the X server, thus kicks us out of the loop. So whenever we get an invalid notify, we fake a vblank event so rendering can happen. And we keeps doing this until we are (hopefully) fine. Signed-off-by: Yuxuan Shui --- src/vblank.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/vblank.c b/src/vblank.c index 270f93e2f6..b7281360ee 100644 --- a/src/vblank.c +++ b/src/vblank.c @@ -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 " From 14805899fbab3212c3d3fb2fb666a0039daa7de7 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Jun 2024 19:14:18 +0100 Subject: [PATCH 51/56] renderer/layout: skip windows without pixmaps There are some cases where a window might be missing its pixmap. Mainly when a window is unmapped/destroyed during a backend reset (there are many reasons for backend resets, e.g. resolution change, unredir-if-possible, etc.). We skip windows like this. Signed-off-by: Yuxuan Shui --- src/renderer/layout.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/renderer/layout.c b/src/renderer/layout.c index f40953129e..b34cdab006 100644 --- a/src/renderer/layout.c +++ b/src/renderer/layout.c @@ -44,6 +44,9 @@ static bool layer_from_window(struct layer *out_layer, struct win *w, ivec2 size 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), From 3ec5fd0f3d8cfc923d316d11ed7540f3cf4d4673 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Jun 2024 15:00:24 +0100 Subject: [PATCH 52/56] core: remove the X critical section I checked, functions called by refresh_windows should mostly be robust against window disappearing, unless I missed something. (We will find out). Signed-off-by: Yuxuan Shui --- src/common.h | 2 -- src/event.c | 1 - src/picom.c | 50 +++++++++----------------------------------------- 3 files changed, 9 insertions(+), 44 deletions(-) diff --git a/src/common.h b/src/common.h index 7892d64593..635fb9733b 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/event.c b/src/event.c index 932c126bbd..b2e9558ea5 100644 --- a/src/event.c +++ b/src/event.c @@ -406,7 +406,6 @@ static inline void ev_configure_notify(session_t *ps, xcb_configure_notify_event if (ev->window == ps->c.screen_info->root) { configure_root(ps); - ps->pending_updates = true; } else { configure_win(ps, ev); } diff --git a/src/picom.c b/src/picom.c index 46a4529642..a49c91295a 100644 --- a/src/picom.c +++ b/src/picom.c @@ -1625,44 +1625,16 @@ static void tmout_unredir_callback(EV_P attr_unused, ev_timer *w, int revents at } static void handle_pending_updates(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); - } + // Process new windows, and maybe allocate struct managed_win for them + handle_new_windows(ps); - ps->server_grabbed = true; - - // Catching up with X server - // Handle all events X server has sent us so far, so that our internal state will - // catch up with the X server's state. It only makes sense to call this function - // in the X critical section, otherwise we will be chasing a moving goal post. - // - // (Disappointingly, grabbing the X server doesn't actually prevent the X server - // state from changing. It should, but in practice we have observed window still - // being destroyed while we have the server grabbed. This is very disappointing - // and forces us to use some hacky code to recover when we discover we are - // out-of-sync.) - handle_x_events(ps); - if (ps->pending_updates || wm_has_tree_changes(ps->wm)) { - log_debug("Delayed handling of events, entering critical section"); - // Process new windows, and maybe allocate struct managed_win for them - handle_new_windows(ps); + if (ps->pending_updates) { + 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); } - 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; - log_trace("Exited critical section"); // Process window flags (stale images) refresh_images(ps); @@ -1938,13 +1910,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 = x_poll_for_event(&ps->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) { From ca3ea85958bc69a0220aa521cd86a3e46a9b6b36 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Thu, 27 Jun 2024 17:50:55 +0100 Subject: [PATCH 53/56] changelog: add rational for not using GrabServer Signed-off-by: Yuxuan Shui --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) 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 From 12fbcb0c0f3334c153a5462a19d5f8075645de49 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Fri, 28 Jun 2024 18:11:35 +0100 Subject: [PATCH 54/56] flake: set UBSAN_OPTIONS so it's visible by default Looks like a lot of ubsan violations are silent if neither print_stacktrace nor abort_on_error is enabled. Signed-off-by: Yuxuan Shui --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index e6e76bec8f..e49c3ec0fb 100644 --- a/flake.nix +++ b/flake.nix @@ -63,7 +63,7 @@ # 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 UBSAN_OPTIONS="disable_coredump=0:unmap_shadow_on_exit=1:print_stacktrace=1" export ASAN_OPTIONS="disable_coredump=0:unmap_shadow_on_exit=1" ''; }); From 51edc1bc553817ee5502307ad0dd64cf819b9327 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Fri, 28 Jun 2024 18:15:39 +0100 Subject: [PATCH 55/56] flake: enable abort_on_error for ASan So we get a coredump which we can inspect. Signed-off-by: Yuxuan Shui --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index e49c3ec0fb..0663802827 100644 --- a/flake.nix +++ b/flake.nix @@ -64,7 +64,7 @@ # 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:print_stacktrace=1" - export ASAN_OPTIONS="disable_coredump=0:unmap_shadow_on_exit=1" + export ASAN_OPTIONS="disable_coredump=0:unmap_shadow_on_exit=1:abort_on_error=1" ''; }); in rec { From 65452048cfe89dbc616a94a13db5c0c8f791a831 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Fri, 28 Jun 2024 18:17:34 +0100 Subject: [PATCH 56/56] meson: silence unsigned shift overflow Signed-off-by: Yuxuan Shui --- meson.build | 4 ++++ 1 file changed, 4 insertions(+) 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')