mirror of
https://github.com/yshui/picom.git
synced 2025-04-07 17:44:04 -04:00
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 <yshuiv7@gmail.com>
This commit is contained in:
parent
5ecac66185
commit
c96ca0a40a
7 changed files with 420 additions and 29 deletions
11
src/event.c
11
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);
|
||||
|
|
33
src/picom.c
33
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;
|
||||
|
|
|
@ -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; \
|
||||
|
|
|
@ -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);
|
||||
|
|
259
src/wm/wm.c
259
src/wm/wm.c
|
@ -1,16 +1,20 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright (c) Yuxuan Shui
|
||||
|
||||
#include <stddef.h>
|
||||
#include <uthash.h>
|
||||
#include <xcb/xproto.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
102
src/wm/wm.h
102
src/wm/wm.h
|
@ -12,6 +12,9 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdalign.h>
|
||||
|
||||
#include <uthash.h>
|
||||
#include <xcb/xproto.h>
|
||||
|
||||
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue