1
0
Fork 0
mirror of https://github.com/yshui/picom.git synced 2025-10-30 23:46:46 -04:00

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 <yshuiv7@gmail.com>
This commit is contained in:
Yuxuan Shui 2024-06-19 09:25:57 +01:00
parent c96ca0a40a
commit 518f63b920
No known key found for this signature in database
GPG key ID: D3A4405BE6CC17F4
22 changed files with 1040 additions and 1631 deletions

View file

@ -31,7 +31,7 @@ struct xvisual_info {
}; };
typedef struct session session_t; typedef struct session session_t;
struct managed_win; struct win;
struct ev_loop; struct ev_loop;
struct backend_operations; struct backend_operations;

View file

@ -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) { static inline int c2h_b_opp(c2_b_op_t op) {
switch (op) { switch (op) {
case C2_B_OAND: return 2; case C2_B_OAND: return 2;
case C2_B_OOR: return 1; case C2_B_OOR:
case C2_B_OXOR: return 1; case C2_B_OXOR: return 1;
default: break; default: break;
} }
@ -441,8 +441,12 @@ TEST_CASE(c2_parse) {
size_t len = c2_condition_to_str(cond->ptr, str, sizeof(str)); size_t len = c2_condition_to_str(cond->ptr, str, sizeof(str));
TEST_STREQUAL3(str, "name = \"xterm\"", len); 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", .name = "xterm",
.tree_ref = wm_find(wm, 1),
}; };
TEST_TRUE(c2_match(state, &test_win, cond, NULL)); TEST_TRUE(c2_match(state, &test_win, cond, NULL));
c2_list_postprocess(state, NULL, cond); c2_list_postprocess(state, NULL, cond);
@ -563,6 +567,8 @@ TEST_CASE(c2_parse) {
len = c2_condition_to_str(cond->ptr, str, sizeof(str)); len = c2_condition_to_str(cond->ptr, str, sizeof(str));
TEST_STREQUAL3(str, rule, len); TEST_STREQUAL3(str, rule, len);
c2_list_free(&cond, NULL); c2_list_free(&cond, NULL);
wm_free(wm);
} }
#define c2_error(format, ...) \ #define c2_error(format, ...) \
@ -1562,8 +1568,9 @@ static inline bool c2_int_op(const c2_l_t *leaf, int64_t target) {
unreachable(); unreachable();
} }
static bool c2_match_once_leaf_int(const struct managed_win *w, const c2_l_t *leaf) { static bool c2_match_once_leaf_int(const struct win *w, const c2_l_t *leaf) {
const xcb_window_t wid = (leaf->target_on_client ? w->client_win : w->base.id); 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 // Get the value
if (leaf->predef != C2_L_PUNDEFINED) { 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_PWMWIN: predef_target = win_is_wmwin(w); break;
case C2_L_PBSHAPED: predef_target = w->bounding_shaped; break; case C2_L_PBSHAPED: predef_target = w->bounding_shaped; break;
case C2_L_PROUNDED: predef_target = w->rounded_corners; 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_PLEADER: predef_target = w->leader; break;
case C2_L_POVREDIR: case C2_L_POVREDIR:
// When user wants to check override-redirect, they almost always // 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 // don't track the override-redirect state of the client window
// directly, however we can assume if a window has a window // directly, however we can assume if a window has a window
// manager frame around it, it's not override-redirect. // manager frame around it, it's not override-redirect.
predef_target = predef_target = w->a.override_redirect &&
w->a.override_redirect && (w->client_win == w->base.id || wm_ref_client_of(w->tree_ref) == NULL;
w->client_win == XCB_WINDOW_NONE);
break; break;
default: unreachable(); 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); assert(!values->needs_update);
if (!values->valid) { if (!values->valid) {
log_verbose("Property %s not found on window %#010x (%s)", leaf->tgt, log_verbose("Property %s not found on window %#010x (%s)", leaf->tgt,
w->client_win, w->name); client_win, w->name);
return false; return false;
} }
@ -1670,8 +1676,8 @@ static bool c2_string_op(const c2_l_t *leaf, const char *target) {
unreachable(); unreachable();
} }
static bool c2_match_once_leaf_string(struct atom *atoms, const struct managed_win *w, static bool
const c2_l_t *leaf) { c2_match_once_leaf_string(struct atom *atoms, const struct win *w, const c2_l_t *leaf) {
// A predefined target // A predefined target
const char *predef_target = NULL; 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]; auto values = &w->c2_state.values[leaf->target_id];
assert(!values->needs_update); assert(!values->needs_update);
if (!values->valid) { if (!values->valid) {
log_verbose("Property %s not found on window %#010x (%s)", leaf->tgt, log_verbose("Property %s not found on window %#010x, client %#010x (%s)",
w->client_win, w->name); leaf->tgt, win_id(w), win_client_id(w, false), w->name);
return false; return false;
} }
@ -1752,10 +1758,11 @@ static bool c2_match_once_leaf_string(struct atom *atoms, const struct managed_w
* For internal use. * For internal use.
*/ */
static inline bool 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); 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 // Return if wid is missing
if (leaf->predef == C2_L_PUNDEFINED && !wid) { 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. * @return true if matched, false otherwise.
*/ */
static bool static bool c2_match_once(struct c2_state *state, const struct win *w, const c2_ptr_t cond) {
c2_match_once(struct c2_state *state, const struct managed_win *w, const c2_ptr_t cond) {
bool result = false; bool result = false;
if (cond.isbranch) { if (cond.isbranch) {
@ -1802,8 +1808,8 @@ c2_match_once(struct c2_state *state, const struct managed_win *w, const c2_ptr_
return false; return false;
} }
log_verbose("Matching window %#010x (%s) against condition %s", log_verbose("Matching window %#010x (%s) against condition %s", win_id(w),
w->base.id, w->name, c2_condition_to_str2(cond)); w->name, c2_condition_to_str2(cond));
switch (pb->op) { switch (pb->op) {
case C2_B_OAND: 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(); 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)); result, c2_condition_to_str2(cond));
} else { } else {
// A leaf // 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); result = c2_match_once_leaf(state, w, pleaf);
log_debug("(%#010x): leaf: result = %d, client = %#010x, " log_debug("(%#010x): leaf: result = %d, client = %#010x, pattern = %s",
"pattern = %s", win_id(w), result, win_client_id(w, false),
w->base.id, result, w->client_win, c2_condition_to_str2(cond)); c2_condition_to_str2(cond));
} }
// Postprocess the result // 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 * @param pdata a place to return the data
* @return true if matched, false otherwise. * @return true if matched, false otherwise.
*/ */
bool c2_match(struct c2_state *state, const struct managed_win *w, bool c2_match(struct c2_state *state, const struct win *w, const c2_lptr_t *condlst,
const c2_lptr_t *condlst, void **pdata) { void **pdata) {
// Then go through the whole linked list // Then go through the whole linked list
for (; condlst; condlst = condlst->next) { for (; condlst; condlst = condlst->next) {
if (c2_match_once(state, w, condlst->ptr)) { 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. /// 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, bool c2_match_one(struct c2_state *state, const struct win *w, const c2_lptr_t *condlst,
const c2_lptr_t *condlst, void **pdata) { void **pdata) {
if (!condlst) { if (!condlst) {
return false; return false;
} }

View file

@ -18,7 +18,7 @@ struct c2_window_state {
struct c2_property_value *values; struct c2_property_value *values;
}; };
struct atom; struct atom;
struct managed_win; struct win;
typedef void (*c2_userdata_free)(void *); typedef void (*c2_userdata_free)(void *);
c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data); 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_connection_t *c, xcb_window_t client_win,
xcb_window_t frame_win); xcb_window_t frame_win);
bool c2_match(struct c2_state *state, const struct managed_win *w, bool c2_match(struct c2_state *state, const struct win *w, const c2_lptr_t *condlst,
const c2_lptr_t *condlst, void **pdata); void **pdata);
bool c2_match_one(struct c2_state *state, const struct managed_win *w, bool c2_match_one(struct c2_state *state, const struct win *w, const c2_lptr_t *condlst,
const c2_lptr_t *condlst, void **pdata); void **pdata);
bool c2_list_postprocess(struct c2_state *state, xcb_connection_t *c, c2_lptr_t *list); 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); typedef bool (*c2_list_foreach_cb_t)(const c2_lptr_t *cond, void *data);

View file

@ -20,7 +20,6 @@
#include "compiler.h" #include "compiler.h"
#include "config.h" #include "config.h"
#include "log.h" #include "log.h"
#include "utils/list.h"
#include "utils/misc.h" #include "utils/misc.h"
#include "utils/str.h" #include "utils/str.h"
#include "wm/defs.h" #include "wm/defs.h"
@ -464,24 +463,8 @@ static bool cdbus_append_string_variant(DBusMessage *msg, const char *data) {
return true; 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 /// 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) { 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; DBusMessageIter it, subit;
dbus_message_iter_init_append(msg, &it); dbus_message_iter_init_append(msg, &it);
if (!dbus_message_iter_open_container(&it, DBUS_TYPE_ARRAY, 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; 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)) { if (!dbus_message_iter_close_container(&it, &subit)) {
log_error("Failed to close container."); log_error("Failed to close container.");
return false; return false;
} }
if (result != 0) { if (failed) {
log_error("Failed to append argument."); log_error("Failed to append argument.");
return false; return false;
} }
@ -581,14 +579,20 @@ cdbus_process_window_property_get(session_t *ps, DBusMessage *msg, cdbus_window_
return DBUS_HANDLER_RESULT_HANDLED; return DBUS_HANDLER_RESULT_HANDLED;
} }
auto w = wm_find_managed(ps->wm, wid); auto cursor = wm_find(ps->wm, wid);
if (cursor == NULL) {
if (!w) {
log_debug("Window %#010x not found.", wid); log_debug("Window %#010x not found.", wid);
dbus_set_error(e, CDBUS_ERROR_BADWIN, CDBUS_ERROR_BADWIN_S, wid); dbus_set_error(e, CDBUS_ERROR_BADWIN, CDBUS_ERROR_BADWIN_S, wid);
return DBUS_HANDLER_RESULT_HANDLED; 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) \ #define append(tgt, type, expr) \
if (!strcmp(#tgt, target)) { \ if (!strcmp(#tgt, target)) { \
if (!cdbus_append_##type(reply, expr)) { \ 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) #define append_win_property(name, member, type) append(name, type, w->member)
append(Mapped, bool_variant, w->state == WSTATE_MAPPED); 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(Type, string_variant, WINTYPES[w->window_type].name);
append(RawFocused, bool_variant, win_is_focused_raw(w)); 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(Leader, leader, wid_variant);
append_win_property(Name, name, string_variant); append_win_property(Name, name, string_variant);
if (!strcmp("Next", target)) { if (!strcmp("Next", target)) {
cdbus_window_t next_id = 0; cdbus_window_t next_id = 0;
if (!list_node_is_last(wm_stack_end(ps->wm), &w->base.stack_neighbour)) { auto below = wm_ref_below(cursor);
next_id = list_entry(w->base.stack_neighbour.next, struct win, if (below != NULL) {
stack_neighbour) next_id = wm_ref_win_id(below);
->id;
} }
if (!cdbus_append_wid_variant(reply, next_id)) { if (!cdbus_append_wid_variant(reply, next_id)) {
return DBUS_HANDLER_RESULT_NEED_MEMORY; 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; return DBUS_HANDLER_RESULT_HANDLED;
} }
auto w = wm_find_managed(ps->wm, wid); auto cursor = wm_find(ps->wm, wid);
if (cursor == NULL) {
if (!w) {
log_debug("Window %#010x not found.", wid); log_debug("Window %#010x not found.", wid);
dbus_set_error(err, CDBUS_ERROR_BADWIN, CDBUS_ERROR_BADWIN_S, wid); dbus_set_error(err, CDBUS_ERROR_BADWIN, CDBUS_ERROR_BADWIN_S, wid);
return DBUS_HANDLER_RESULT_HANDLED; 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) \ #define append(tgt, type, expr) \
if (strcmp(#tgt, target) == 0) { \ if (strcmp(#tgt, target) == 0) { \
if (!cdbus_append_##type(reply, expr)) { \ 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) #define append_win_property(tgt, type) append(tgt, type, w->tgt)
if (!strcmp("next", target)) { if (!strcmp("next", target)) {
xcb_window_t next_id = auto below = wm_ref_below(cursor);
list_node_is_last(wm_stack_end(ps->wm), &w->base.stack_neighbour) xcb_window_t next_id = below ? wm_ref_win_id(below) : XCB_NONE;
? 0
: list_entry(w->base.stack_neighbour.next, struct win, stack_neighbour)
->id;
if (!cdbus_append_wid(reply, next_id)) { if (!cdbus_append_wid(reply, next_id)) {
return DBUS_HANDLER_RESULT_NEED_MEMORY; return DBUS_HANDLER_RESULT_NEED_MEMORY;
} }
return DBUS_HANDLER_RESULT_HANDLED; 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(map_state, boolean, w->a.map_state);
append(wmwin, boolean, win_is_wmwin(w)); append(wmwin, boolean, win_is_wmwin(w));
append(focused_raw, boolean, win_is_focused_raw(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(mode, enum);
append_win_property(opacity, double); append_win_property(opacity, double);
append_win_property(client_win, wid);
append_win_property(ever_damaged, boolean); append_win_property(ever_damaged, boolean);
append_win_property(window_type, enum); append_win_property(window_type, enum);
append_win_property(leader, wid); 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; return DBUS_HANDLER_RESULT_HANDLED;
} }
auto w = wm_find_managed(ps->wm, wid); auto cursor = wm_find(ps->wm, wid);
if (cursor == NULL) {
if (!w) {
log_debug("Window %#010x not found.", wid); log_debug("Window %#010x not found.", wid);
dbus_set_error(err, CDBUS_ERROR_BADWIN, CDBUS_ERROR_BADWIN_S, wid); dbus_set_error(err, CDBUS_ERROR_BADWIN, CDBUS_ERROR_BADWIN_S, wid);
return DBUS_HANDLER_RESULT_HANDLED; 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; cdbus_enum_t val = UNSET;
if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val)) { if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val)) {
dbus_set_error_const(err, DBUS_ERROR_INVALID_ARGS, NULL); 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); auto w = wm_find_by_client(ps->wm, client);
if (w) { if (w) {
wid = w->base.id; wid = wm_ref_win_id(w);
} }
} else if (!strcmp("focused", target)) { } else if (!strcmp("focused", target)) {
// Find focused window // Find focused window
auto active_win = wm_active_win(ps->wm); auto active_win = wm_active_win(ps->wm);
if (active_win && active_win->state != WSTATE_UNMAPPED) { if (active_win && active_win->state != WSTATE_UNMAPPED) {
wid = active_win->base.id; wid = win_id(active_win);
} }
} else { } else {
log_debug(CDBUS_ERROR_BADTGT_S, target); 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, "<node name='%#010x'/>\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. * 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; scoped_charp introspect = NULL;
mstrextend(&introspect, str_introspect); mstrextend(&introspect, str_introspect);
if (wm_foreach(ps->wm, cdbus_process_windows_root_introspect_callback, &introspect)) { wm_stack_foreach(ps->wm, cursor) {
return DBUS_HANDLER_RESULT_NEED_MEMORY; auto w = wm_ref_deref(cursor);
if (w == NULL) {
continue;
}
char *tmp = NULL;
if (asprintf(&tmp, "<node name='%#010x'/>\n", win_id(w)) < 0) {
log_fatal("Failed to allocate memory.");
return DBUS_HANDLER_RESULT_NEED_MEMORY;
}
mstrextend(&introspect, tmp);
free(tmp);
} }
mstrextend(&introspect, "</node>"); mstrextend(&introspect, "</node>");
if (!cdbus_append_string(reply, 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) { void cdbus_ev_win_added(struct cdbus_data *cd, struct win *w) {
if (cd->dbus_conn) { if (cd->dbus_conn) {
cdbus_signal_wid(cd, CDBUS_INTERFACE_NAME, "win_added", w->id); cdbus_signal_wid(cd, CDBUS_INTERFACE_NAME, "win_added", win_id(w));
cdbus_signal_wid(cd, PICOM_COMPOSITOR_INTERFACE, "WinAdded", w->id); cdbus_signal_wid(cd, PICOM_COMPOSITOR_INTERFACE, "WinAdded", win_id(w));
} }
} }
void cdbus_ev_win_destroyed(struct cdbus_data *cd, struct win *w) { void cdbus_ev_win_destroyed(struct cdbus_data *cd, struct win *w) {
if (cd->dbus_conn) { if (cd->dbus_conn) {
cdbus_signal_wid(cd, CDBUS_INTERFACE_NAME, "win_destroyed", w->id); cdbus_signal_wid(cd, CDBUS_INTERFACE_NAME, "win_destroyed", win_id(w));
cdbus_signal_wid(cd, PICOM_COMPOSITOR_INTERFACE, "WinDestroyed", w->id); cdbus_signal_wid(cd, PICOM_COMPOSITOR_INTERFACE, "WinDestroyed", win_id(w));
} }
} }
void cdbus_ev_win_mapped(struct cdbus_data *cd, struct win *w) { void cdbus_ev_win_mapped(struct cdbus_data *cd, struct win *w) {
if (cd->dbus_conn) { if (cd->dbus_conn) {
cdbus_signal_wid(cd, CDBUS_INTERFACE_NAME, "win_mapped", w->id); cdbus_signal_wid(cd, CDBUS_INTERFACE_NAME, "win_mapped", win_id(w));
cdbus_signal_wid(cd, PICOM_COMPOSITOR_INTERFACE, "WinMapped", w->id); cdbus_signal_wid(cd, PICOM_COMPOSITOR_INTERFACE, "WinMapped", win_id(w));
} }
} }
void cdbus_ev_win_unmapped(struct cdbus_data *cd, struct win *w) { void cdbus_ev_win_unmapped(struct cdbus_data *cd, struct win *w) {
if (cd->dbus_conn) { if (cd->dbus_conn) {
cdbus_signal_wid(cd, CDBUS_INTERFACE_NAME, "win_unmapped", w->id); cdbus_signal_wid(cd, CDBUS_INTERFACE_NAME, "win_unmapped", win_id(w));
cdbus_signal_wid(cd, PICOM_COMPOSITOR_INTERFACE, "WinUnmapped", w->id); cdbus_signal_wid(cd, PICOM_COMPOSITOR_INTERFACE, "WinUnmapped", win_id(w));
} }
} }
void cdbus_ev_win_focusout(struct cdbus_data *cd, struct win *w) { void cdbus_ev_win_focusout(struct cdbus_data *cd, struct win *w) {
if (cd->dbus_conn) { 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) { void cdbus_ev_win_focusin(struct cdbus_data *cd, struct win *w) {
if (cd->dbus_conn) { 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));
} }
} }
//!@} //!@}

View file

@ -74,11 +74,12 @@ static inline const char *ev_window_name(session_t *ps, xcb_window_t wid) {
} else if (ps->overlay == wid) { } else if (ps->overlay == wid) {
name = "(Overlay)"; name = "(Overlay)";
} else { } else {
auto w = wm_find_managed(ps->wm, wid); auto cursor = wm_find(ps->wm, wid);
if (!w) { if (!cursor || !wm_ref_deref(cursor)) {
w = wm_find_by_client(ps->wm, wid); cursor = wm_find_by_client(ps->wm, wid);
} }
auto w = cursor ? wm_ref_deref(cursor) : NULL;
if (w && w->name) { if (w && w->name) {
name = 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) { static inline void ev_create_notify(session_t *ps, xcb_create_notify_event_t *ev) {
wm_import_incomplete(ps->wm, ev->window, ev->parent); 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 /// Handle configure event of a regular window
static void configure_win(session_t *ps, xcb_configure_notify_event_t *ce) { 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 (!cursor) {
log_error("Configure event received for unknown window %#010x", ce->window);
return;
}
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);
}
auto w = wm_ref_deref(cursor);
if (!w) { if (!w) {
return; return;
} }
wm_stack_move_above(ps->wm, w, ce->above_sibling); add_damage_from_win(ps, w);
if (!w->managed) {
return;
}
auto mw = (struct managed_win *)w;
add_damage_from_win(ps, mw);
// We check against pending_g here, because there might have been multiple // We check against pending_g here, because there might have been multiple
// configure notifies in this cycle, or the window could receive multiple updates // configure notifies in this cycle, or the window could receive multiple updates
// while it's unmapped. // while it's unmapped.
bool position_changed = mw->pending_g.x != ce->x || mw->pending_g.y != ce->y; bool position_changed = w->pending_g.x != ce->x || w->pending_g.y != ce->y;
bool size_changed = mw->pending_g.width != ce->width || bool size_changed = w->pending_g.width != ce->width ||
mw->pending_g.height != ce->height || w->pending_g.height != ce->height ||
mw->pending_g.border_width != ce->border_width; w->pending_g.border_width != ce->border_width;
if (position_changed || size_changed) { if (position_changed || size_changed) {
// Queue pending updates // 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 // TODO(yshui) don't set pending_updates if the window is not
// visible/mapped // visible/mapped
ps->pending_updates = true; ps->pending_updates = true;
// At least one of the following if's is true // At least one of the following if's is true
if (position_changed) { if (position_changed) {
log_trace("Window position changed, %dx%d -> %dx%d", mw->g.x, log_trace("Window position changed, %dx%d -> %dx%d", w->g.x,
mw->g.y, ce->x, ce->y); w->g.y, ce->x, ce->y);
mw->pending_g.x = ce->x; w->pending_g.x = ce->x;
mw->pending_g.y = ce->y; w->pending_g.y = ce->y;
win_set_flags(mw, WIN_FLAGS_POSITION_STALE); win_set_flags(w, WIN_FLAGS_POSITION_STALE);
} }
if (size_changed) { if (size_changed) {
log_trace("Window size changed, %dx%d -> %dx%d", mw->g.width, log_trace("Window size changed, %dx%d -> %dx%d", w->g.width,
mw->g.height, ce->width, ce->height); w->g.height, ce->width, ce->height);
mw->pending_g.width = ce->width; w->pending_g.width = ce->width;
mw->pending_g.height = ce->height; w->pending_g.height = ce->height;
mw->pending_g.border_width = ce->border_width; w->pending_g.border_width = ce->border_width;
win_set_flags(mw, WIN_FLAGS_SIZE_STALE); win_set_flags(w, WIN_FLAGS_SIZE_STALE);
} }
// Recalculate which monitor this window is on // 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 // override_redirect flag cannot be changed after window creation, as far
// as I know, so there's no point to re-match windows here. // 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) { 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); ev->event, ev->window, ev->above_sibling, ev->override_redirect);
if (ev->window == ps->c.screen_info->root) { if (ev->window == ps->c.screen_info->root) {
set_root_flags(ps, ROOT_FLAGS_CONFIGURED); set_root_flags(ps, ROOT_FLAGS_CONFIGURED);
} else { } else if (!wm_is_wid_masked(ps->wm, ev->event)) {
configure_win(ps, ev); configure_win(ps, ev);
} }
} }
static inline void ev_destroy_notify(session_t *ps, xcb_destroy_notify_event_t *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); 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) { 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 // Unmap overlay window if it got mapped but we are currently not
// in redirected state. // in redirected state.
if (ps->overlay && ev->window == ps->overlay && !ps->redirected) { if (ps->overlay && ev->window == ps->overlay) {
log_debug("Overlay is mapped while we are not redirected"); if (!ps->redirected) {
auto e = xcb_request_check( log_debug("Overlay is mapped while we are not redirected");
ps->c.c, xcb_unmap_window_checked(ps->c.c, ps->overlay)); auto succeeded =
if (e) { XCB_AWAIT_VOID(xcb_unmap_window, ps->c.c, ps->overlay);
log_error("Failed to unmap the overlay window"); if (!succeeded) {
free(e); log_error("Failed to unmap the overlay window");
}
} }
// We don't track the overlay window, so we can return // We don't track the overlay window, so we can return
return; return;
} }
auto w = wm_find_managed(ps->wm, ev->window); if (wm_is_wid_masked(ps->wm, ev->event)) {
if (!w) {
return; 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); win_set_flags(w, WIN_FLAGS_MAPPED);
// We set `ever_damaged` to false here, instead of in `map_win_start`, // We set `ever_damaged` to false here, instead of in `map_win_start`,
// because we might receive damage events before that function is called // 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) { 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 (ps->overlay && ev->window == ps->overlay) {
if (w) { 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); 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, " log_debug("Window %#010x has new parent: %#010x, override_redirect: %d, "
"send_event: %#010x", "send_event: %#010x",
ev->window, ev->parent, ev->override_redirect, ev->event); ev->window, ev->parent, ev->override_redirect, ev->event);
wm_reparent(ps->wm, ev->window, ev->parent); 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.
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;
}
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) { static inline void ev_circulate_notify(session_t *ps, xcb_circulate_notify_event_t *ev) {
auto w = wm_find(ps->wm, ev->window); if (wm_is_wid_masked(ps->wm, ev->event)) {
if (!w) {
return; return;
} }
log_debug("Moving window %#010x (%s) to the %s", w->id, auto cursor = wm_find(ps->wm, ev->window);
win_get_name_if_managed(w), ev->place == PlaceOnTop ? "top" : "bottom");
if (ev->place == PlaceOnTop) { if (cursor == NULL) {
wm_stack_move_to_top(ps->wm, w); log_error("Circulate event received for unknown window %#010x", ev->window);
} else { return;
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) { static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t *ev) {
if (unlikely(log_get_level_tls() <= LOG_LEVEL_TRACE)) { if (unlikely(log_get_level_tls() <= LOG_LEVEL_TRACE)) {
// Print out changed atom // Print out changed atom
@ -611,10 +444,31 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
return; return;
} }
if (wm_is_wid_masked(ps->wm, ev->window)) {
return;
}
ps->pending_updates = true; 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) { 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) { 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); queue_redraw(ps);
} }
if (w) { auto toplevel = wm_ref_deref(toplevel_cursor);
win_set_property_stale(w, ev->atom); 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 // We already handle if this is set on the client window, check
// if this is set on the frame window as well. // if this is set on the frame window as well.
// TODO(yshui) do we really need this? // TODO(yshui) do we really need this?
auto toplevel = wm_find_managed(ps->wm, ev->window); win_set_property_stale(toplevel, ev->atom);
if (toplevel) {
win_set_property_stale(toplevel, ev->atom);
}
} }
// Check for other atoms we are tracking // Check for other atoms we are tracking
if (c2_state_is_property_tracked(ps->c2_state, ev->atom)) { if (c2_state_is_property_tracked(ps->c2_state, ev->atom)) {
bool change_is_on_client = false; bool change_is_on_client = cursor == client_cursor;
w = wm_find_managed(ps->wm, ev->window); if (toplevel) {
if (!w) { c2_window_state_mark_dirty(ps->c2_state, &toplevel->c2_state,
w = wm_find_by_client(ps->wm, ev->window); ev->atom, change_is_on_client);
change_is_on_client = true;
}
if (w) {
c2_window_state_mark_dirty(ps->c2_state, &w->c2_state, ev->atom,
change_is_on_client);
// Set FACTOR_CHANGED so rules based on properties will be // Set FACTOR_CHANGED so rules based on properties will be
// re-evaluated. // re-evaluated.
// Don't need to set property stale here, since that only // Don't need to set property stale here, since that only
// concerns properties we explicitly check. // 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 // Only mapped window can receive damages
assert(w->state == WSTATE_MAPPED || win_check_flags_all(w, WIN_FLAGS_MAPPED)); 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); free(e);
} }
win_extents(w, &parts); win_extents(w, &parts);
log_debug("Window %#010x (%s) has been damaged the first time", log_debug("Window %#010x (%s) has been damaged the first time", win_id(w),
w->base.id, w->name); w->name);
} else { } else {
auto cookie = xcb_damage_subtract(ps->c.c, w->damage, XCB_NONE, auto cookie = xcb_damage_subtract(ps->c.c, w->damage, XCB_NONE,
ps->damage_ring.x_region); 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); 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->ever_damaged = true;
w->pixmap_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) { static inline void ev_damage_notify(session_t *ps, xcb_damage_notify_event_t *de) {
/* auto cursor = wm_find(ps->wm, de->drawable);
if (ps->root == de->drawable) {
root_damaged();
return;
} */
auto w = wm_find_managed(ps->wm, de->drawable); if (cursor == NULL) {
log_error("Damage notify received for unknown window %#010x", de->drawable);
if (!w) {
return; 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) { 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); auto cursor = wm_find(ps->wm, ev->affected_window);
if (!w || w->a.map_state == XCB_MAP_STATE_UNMAPPED) { 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; return;
} }

View file

@ -23,22 +23,32 @@
#include "wm/win.h" #include "wm/win.h"
#include "x.h" #include "x.h"
static struct managed_win * static struct win *
setup_window(struct x_connection *c, struct atom *atoms, struct options *options, setup_window(struct x_connection *c, struct atom *atoms, struct options *options,
struct c2_state *state, xcb_window_t target) { struct c2_state *state, xcb_window_t target) {
// Pretend we are the compositor, and build up the window state // 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->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_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 // TODO(yshui) get leader
win_update_name(c, atoms, w); win_update_name(c, atoms, w);
win_update_class(c, atoms, w); win_update_class(c, atoms, w);
win_update_role(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){ w->g = (struct win_geometry){
.x = geometry_reply->x, .x = geometry_reply->x,
.y = geometry_reply->y, .y = geometry_reply->y,
@ -68,17 +78,18 @@ setup_window(struct x_connection *c, struct atom *atoms, struct options *options
free(reply); 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; 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->a = *attributes_reply;
w->pictfmt = x_get_pictform_for_visual(c, w->a.visual); w->pictfmt = x_get_pictform_for_visual(c, w->a.visual);
free(attributes_reply); free(attributes_reply);
c2_window_state_init(state, &w->c2_state); 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; return w;
} }
@ -140,7 +151,7 @@ xcb_window_t select_window(struct x_connection *c) {
struct c2_match_state { struct c2_match_state {
struct c2_state *state; struct c2_state *state;
struct managed_win *w; struct win *w;
bool print_value; bool print_value;
}; };

View file

@ -233,8 +233,11 @@ void glx_destroy(session_t *ps) {
} }
// Free all GLX resources of windows // Free all GLX resources of windows
win_stack_foreach_managed(w, wm_stack_end(ps->wm)) { wm_stack_foreach(ps->wm, cursor) {
free_win_res_glx(ps, w); auto w = wm_ref_deref(cursor);
if (w != NULL) {
free_win_res_glx(ps, w);
}
} }
// Free GLSL shaders/programs // 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(); gl_check_err();
} }
bool glx_round_corners_dst(session_t *ps, struct managed_win *w, bool glx_round_corners_dst(session_t *ps, struct win *w, const glx_texture_t *ptex,
const glx_texture_t *ptex, int dx, int dy, int width, int dx, int dy, int width, int height, float z, float cr,
int height, float z, float cr, const region_t *reg_tgt) { const region_t *reg_tgt) {
assert(ps->psglx->round_passes->prog); assert(ps->psglx->round_passes->prog);
bool ret = false; 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. * 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->paint);
free_paint_glx(ps, &w->shadow_paint); free_paint_glx(ps, &w->shadow_paint);
free_glx_bc(ps, &w->glx_blur_cache); free_glx_bc(ps, &w->glx_blur_cache);

View file

@ -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, 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); 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, bool glx_round_corners_dst(session_t *ps, struct win *w, const glx_texture_t *ptex,
const glx_texture_t *ptex, int dx, int dy, int width, int dx, int dy, int width, int height, float z, float cr,
int height, float z, float cr, const region_t *reg_tgt); const region_t *reg_tgt);
GLuint glx_create_shader(GLenum shader_type, const char *shader_str); 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. * 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);

View file

@ -460,7 +460,8 @@ void update_ewmh_active_win(session_t *ps) {
// Search for the window // Search for the window
xcb_window_t wid = wid_get_prop_window(&ps->c, ps->c.screen_info->root, 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);
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. // Mark the window focused. No need to unfocus the previous one.
if (w) { if (w) {
@ -501,36 +502,20 @@ static void recheck_focus(session_t *ps) {
return; return;
} }
// Trace upwards until we reach the toplevel containing the focus window. auto cursor = wm_find(ps->wm, wid);
while (true) { if (cursor == NULL) {
auto tree = xcb_query_tree_reply(ps->c.c, xcb_query_tree(ps->c.c, wid), &e); log_error("Window %#010x not found in window tree.", wid);
if (tree == NULL) { return;
// xcb_query_tree probably fails if you run picom when X is
// somehow initializing (like add it in .xinitrc). In this case
// just leave it alone.
log_error_x_error(e, "Failed to query window tree.");
free(e);
return;
}
auto parent = tree->parent;
free(tree);
if (parent == ps->c.screen_info->root) {
break;
}
wid = parent;
} }
auto w = wm_find_managed(ps->wm, wid); cursor = wm_ref_toplevel_of(cursor);
// And we set the focus state here // And we set the focus state here
auto w = wm_ref_deref(cursor);
if (w) { if (w) {
log_debug("%#010" PRIx32 " (%#010" PRIx32 " \"%s\") focused.", wid, log_debug("%#010" PRIx32 " (%#010" PRIx32 " \"%s\") focused.", wid,
w->base.id, w->name); win_id(w), w->name);
win_set_focused(ps, w); 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 /// Free up all the images and deinit the backend
static void destroy_backend(session_t *ps) { 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 // An unmapped window shouldn't have a pixmap, unless it has animation
// running. (`w->previous.state != w->state` means there might be // running. (`w->previous.state != w->state` means there might be
// animation but we haven't had a chance to start it because // 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); free_paint(ps, &w->paint);
if (w->state == WSTATE_DESTROYED) { 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; 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 /// Init the backend and bind all the window pixmap to backend images
static bool initialize_backend(session_t *ps) { static bool initialize_backend(session_t *ps) {
if (!ps->o.use_legacy_backends) { 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 wm_stack_foreach(ps->wm, cursor) {
// this moment. Since there cannot be any fading windows. auto w = wm_ref_deref(cursor);
wm_foreach(ps->wm, mark_pixmap_stale, ps); 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, ps->renderer = renderer_new(ps->backend_data, ps->o.shadow_radius,
(struct color){.alpha = ps->o.shadow_opacity, (struct color){.alpha = ps->o.shadow_opacity,
.red = ps->o.shadow_red, .red = ps->o.shadow_red,
@ -725,6 +709,18 @@ err:
return false; 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 /// Handle configure event of the root window
static void configure_root(session_t *ps) { static void configure_root(session_t *ps) {
// TODO(yshui) re-initializing backend should be done outside of the // TODO(yshui) re-initializing backend should be done outside of the
@ -760,19 +756,16 @@ static void configure_root(session_t *ps) {
free(r); free(r);
rebuild_screen_reg(ps); rebuild_screen_reg(ps);
invalidate_reg_ignore(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;
}
// Whether a window is fullscreen depends on the new screen // Whether a window is fullscreen depends on the new screen
// size. So we need to refresh the fullscreen state of all // size. So we need to refresh the fullscreen state of all
// windows. // windows.
win_stack_foreach_managed(w, wm_stack_end(ps->wm)) { wm_stack_foreach(ps->wm, cursor) {
win_update_is_fullscreen(ps, w); auto w = wm_ref_deref(cursor);
if (w != NULL) {
win_update_is_fullscreen(ps, w);
}
} }
if (ps->redirected) { if (ps->redirected) {
@ -829,17 +822,22 @@ static void handle_root_flags(session_t *ps) {
* *
* @return whether the operation succeeded * @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 // XXX need better, more general name for `fade_running`. It really
// means if fade is still ongoing after the current frame is rendered // means if fade is still ongoing after the current frame is rendered
struct managed_win *bottom = NULL; struct win *bottom = NULL;
*animation = false; *animation = false;
*out_bottom = NULL; *out_bottom = NULL;
// First, let's process fading, and animated shaders // First, let's process fading, and animated shaders
// TODO(yshui) check if a window is fully obscured, and if we don't need to // TODO(yshui) check if a window is fully obscured, and if we don't need to
// process fading or animation for it. // 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 winmode_t mode_old = w->mode;
const bool was_painted = w->to_paint; 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 // Track whether it's the highest window to paint
bool is_highest = true; bool is_highest = true;
bool reg_ignore_valid = 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; __label__ skip_window;
auto w = wm_ref_deref(cursor);
if (w == NULL) {
continue;
}
bool to_paint = true; bool to_paint = true;
// w->to_paint remembers whether this window is painted last time // w->to_paint remembers whether this window is painted last time
const bool was_painted = w->to_paint; 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("%d %d %s", w->a.map_state, w->ever_damaged, w->name);
log_trace("Checking whether window %#010x (%s) should be painted", 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 // 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 // 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"); log_trace("|- is unmapped");
to_paint = false; to_paint = false;
} else if (unlikely(ps->debug_window != XCB_NONE) && } else if (unlikely(ps->debug_window != XCB_NONE) &&
(w->base.id == ps->debug_window || (win_id(w) == ps->debug_window ||
w->client_win == ps->debug_window)) { win_client_id(w, /*fallback_to_self=*/false) == ps->debug_window)) {
log_trace("|- is the debug window"); log_trace("|- is the debug window");
to_paint = false; to_paint = false;
} else if (!w->ever_damaged) { } 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_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 // Generate ignore region for painting to reduce GPU load
if (!w->reg_ignore) { if (!w->reg_ignore) {
@ -1007,11 +1010,6 @@ static bool paint_preprocess(session_t *ps, bool *animation, struct managed_win
} }
w->prev_trans = bottom; w->prev_trans = bottom;
if (bottom) {
w->stacking_rank = bottom->stacking_rank + 1;
} else {
w->stacking_rank = 0;
}
bottom = w; bottom = w;
// If the screen is not redirected and the window has redir_ignore set, // 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) { if (w->state == WSTATE_DESTROYED && w->running_animation == NULL) {
// the window should be destroyed because it was destroyed // the window should be destroyed because it was destroyed
// by X server and now its animations are finished // by X server and now its animations are finished
destroy_win_finish(ps, &w->base); win_destroy_finish(ps, w);
w = NULL; 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) { static void handle_new_windows(session_t *ps) {
wm_complete_import(ps->wm, &ps->c, ps->atoms); wm_complete_import(ps->wm, &ps->c, ps->atoms);
list_foreach_safe(struct win, w, wm_stack_end(ps->wm), stack_neighbour) { // Check tree changes first, because later property updates need accurate
if (w->is_new) { // client window information
auto new_w = maybe_allocate_managed_win(ps, w); struct win *w = NULL;
if (new_w == w) { while (true) {
continue; 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);
} }
break;
assert(new_w->managed); case WM_TREE_CHANGE_TOPLEVEL_KILLED:
wm_stack_replace(ps->wm, w, new_w); w = wm_ref_deref(wm_change.toplevel);
if (w != NULL) {
auto mw = (struct managed_win *)new_w; // Pointing the window tree_ref to the zombie.
if (mw->a.map_state == XCB_MAP_STATE_VIEWABLE) { w->tree_ref = wm_change.toplevel;
win_set_flags(mw, WIN_FLAGS_MAPPED); win_destroy_start(ps, w);
} else {
// This window might be damaged before we called fill_win // This window is not managed, no point keeping the zombie
// and created the damage handle. And there is no way for // around.
// us to find out. So just blindly mark it damaged wm_reap_zombie(wm_change.toplevel);
mw->ever_damaged = true;
} }
// Send D-Bus signal break;
if (ps->o.dbus) { case WM_TREE_CHANGE_CLIENT:
cdbus_ev_win_added(session_get_cdbus(ps), new_w); 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) { static void refresh_windows(session_t *ps) {
win_stack_foreach_managed(w, wm_stack_end(ps->wm)) { wm_stack_foreach(ps->wm, cursor) {
win_process_update_flags(ps, w); auto w = wm_ref_deref(cursor);
if (w != NULL) {
win_process_update_flags(ps, w);
}
} }
} }
static void refresh_images(session_t *ps) { static void refresh_images(session_t *ps) {
win_stack_foreach_managed(w, wm_stack_end(ps->wm)) { wm_stack_foreach(ps->wm, cursor) {
win_process_image_flags(ps, w); 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 // Process new windows, and maybe allocate struct managed_win for them
handle_new_windows(ps); 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 // Handle screen changes
// This HAS TO be called before refresh_windows, as handle_root_flags // This HAS TO be called before refresh_windows, as handle_root_flags
// could call configure_root, which will release images and mark them // 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; ps->pending_updates = false;
log_trace("Exited critical section"); 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 // Window might be freed by this function, if it's destroyed and its
// animation finished // 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); free(w->running_animation);
w->running_animation = NULL; w->running_animation = NULL;
w->in_openclose = false; w->in_openclose = false;
if (w->state == WSTATE_UNMAPPED) { if (w->state == WSTATE_UNMAPPED) {
unmap_win_finish(ps, w); unmap_win_finish(ps, w);
} else if (w->state == WSTATE_DESTROYED) { } 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 // TODO(yshui) I think maybe we don't need this anymore, since now we
// immediate acquire pixmap right after `map_win_start`. // 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); free(w->running_animation);
w->running_animation = NULL; w->running_animation = NULL;
if (w->state == WSTATE_DESTROYED) { if (w->state == WSTATE_DESTROYED) {
destroy_win_finish(ps, &w->base); win_destroy_finish(ps, w);
} }
} }
} }
if (ps->o.benchmark) { if (ps->o.benchmark) {
if (ps->o.benchmark_wid) { if (ps->o.benchmark_wid) {
auto w = wm_find_managed(ps->wm, ps->o.benchmark_wid); auto w = wm_find(ps->wm, ps->o.benchmark_wid);
if (!w) { if (w == NULL) {
log_fatal("Couldn't find specified benchmark window."); log_fatal("Couldn't find specified benchmark window.");
exit(1); 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 { } else {
force_repaint(ps); 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. */ * screen should be redirected. */
bool animation = false; bool animation = false;
bool was_redirected = ps->redirected; bool was_redirected = ps->redirected;
struct managed_win *bottom = NULL; struct win *bottom = NULL;
if (!paint_preprocess(ps, &animation, &bottom)) { if (!paint_preprocess(ps, &animation, &bottom)) {
log_fatal("Pre-render preparation has failed, exiting..."); log_fatal("Pre-render preparation has failed, exiting...");
exit(1); 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. // think there still could be race condition that mandates discarding the events.
x_discard_events(&ps->c); x_discard_events(&ps->c);
xcb_query_tree_reply_t *query_tree_reply = xcb_query_tree_reply( wm_complete_import(ps->wm, &ps->c, ps->atoms);
ps->c.c, xcb_query_tree(ps->c.c, ps->c.screen_info->root), NULL);
e = xcb_request_check(ps->c.c, xcb_ungrab_server_checked(ps->c.c)); e = xcb_request_check(ps->c.c, xcb_ungrab_server_checked(ps->c.c));
if (e) { if (e) {
@ -2530,22 +2543,9 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
ps->server_grabbed = false; 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:"); log_debug("Initial stack:");
list_foreach(struct win, w, wm_stack_end(ps->wm), stack_neighbour) { wm_stack_foreach(ps->wm, i) {
log_debug("%#010x", w->id); log_debug(" %#010x", wm_ref_win_id(i));
} }
ps->command_builder = command_builder_new(); ps->command_builder = command_builder_new();
@ -2598,8 +2598,11 @@ static void session_destroy(session_t *ps) {
} }
#endif #endif
win_stack_foreach_managed_safe(w, wm_stack_end(ps->wm)) { wm_stack_foreach(ps->wm, cursor) {
free_win_res(ps, w); auto w = wm_ref_deref(cursor);
if (w != NULL) {
free_win_res(ps, w);
}
} }
// Free blacklists // 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->usr1_signal);
ev_signal_stop(ps->loop, &ps->int_signal); ev_signal_stop(ps->loop, &ps->int_signal);
wm_free(ps->wm, &ps->c); wm_free(ps->wm);
free_x_connection(&ps->c); free_x_connection(&ps->c);
} }

View file

@ -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 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) { double opacity, const region_t *reg_paint, xcb_render_picture_t pict) {
const int dx = (w ? w->g.x : 0) + x; const int dx = (w ? w->g.x : 0) + x;
const int dy = (w ? w->g.y : 0) + y; 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. * 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 // Fetch Pixmap
if (!w->paint.pixmap) { if (!w->paint.pixmap) {
w->paint.pixmap = x_new_id(&ps->c); w->paint.pixmap = x_new_id(&ps->c);
set_ignore_cookie(&ps->c, xcb_composite_name_window_pixmap( 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; xcb_drawable_t draw = w->paint.pixmap;
if (!draw) { if (!draw) {
log_error("Failed to get pixmap from window %#010x (%s), window won't be " log_error("Failed to get pixmap from window %#010x (%s), window won't be "
"visible", "visible",
w->base.id, w->name); win_id(w), w->name);
return; 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. // causing the jittering issue M4he reported in #7.
if (!paint_bind_tex(ps, &w->paint, 0, 0, false, 0, w->a.visual, if (!paint_bind_tex(ps, &w->paint, 0, 0, false, 0, w->a.visual,
(!ps->o.glx_no_rebind_pixmap && w->pixmap_damaged))) { (!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; w->pixmap_damaged = false;
if (!paint_isvalid(ps, &w->paint)) { 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; return;
} }
@ -681,7 +681,7 @@ static void paint_root(session_t *ps, const region_t *reg_paint) {
/** /**
* Generate shadow <code>Picture</code> for a window. * Generate shadow <code>Picture</code> 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 width = w->widthb;
const int height = w->heightb; const int height = w->heightb;
// log_trace("(): building shadow for %s %d %d", w->name, width, height); // log_trace("(): building shadow for %s %d %d", w->name, width, height);
@ -761,13 +761,12 @@ shadow_picture_err:
/** /**
* Paint the shadow of a window. * Paint the shadow of a window.
*/ */
static inline void static inline void win_paint_shadow(session_t *ps, struct win *w, region_t *reg_paint) {
win_paint_shadow(session_t *ps, struct managed_win *w, region_t *reg_paint) {
// Bind shadow pixmap to GLX texture if needed // Bind shadow pixmap to GLX texture if needed
paint_bind_tex(ps, &w->shadow_paint, 0, 0, false, 32, 0, false); paint_bind_tex(ps, &w->shadow_paint, 0, 0, false, 32, 0, false);
if (!paint_isvalid(ps, &w->shadow_paint)) { 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; 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. * Blur the background of a window.
*/ */
static inline void 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 region_t *reg_paint) {
const int16_t x = w->g.x; const int16_t x = w->g.x;
const int16_t y = w->g.y; 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 /// paint all windows
/// region = ?? /// region = ??
/// region_real = the damage 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->o.xrender_sync_fence || (ps->drivers & DRIVER_NVIDIA)) {
if (ps->xsync_exists && !x_fence_sync(&ps->c, ps->sync_fence)) { if (ps->xsync_exists && !x_fence_sync(&ps->c, ps->sync_fence)) {
log_error("x_fence_sync failed, xrender-sync-fence will be " log_error("x_fence_sync failed, xrender-sync-fence will be "

View file

@ -14,7 +14,7 @@ typedef struct _glx_texture glx_texture_t;
typedef struct glx_prog_main glx_prog_main_t; typedef struct glx_prog_main glx_prog_main_t;
typedef struct session session_t; typedef struct session session_t;
struct managed_win; struct win;
typedef struct paint { typedef struct paint {
xcb_pixmap_t pixmap; 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, int fullh, double opacity, bool argb, bool neg, int cr,
xcb_render_picture_t pict, glx_texture_t *ptex, const region_t *reg_paint, xcb_render_picture_t pict, glx_texture_t *ptex, const region_t *reg_paint,
const glx_prog_main_t *pprogram, clip_t *clip); 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_paint(session_t *ps, paint_t *ppaint);
void free_root_tile(session_t *ps); void free_root_tile(session_t *ps);

View file

@ -110,7 +110,7 @@ command_for_shadow(struct layer *layer, struct backend_command *cmd,
layer->shadow.origin.x, layer->shadow.origin.y, layer->shadow.origin.x, layer->shadow.origin.y,
(unsigned)shadow_size_scaled.width, (unsigned)shadow_size_scaled.width,
(unsigned)shadow_size_scaled.height); (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); log_region(TRACE, &cmd->target_mask);
if (!wintype_options[w->window_type].full_shadow) { if (!wintype_options[w->window_type].full_shadow) {
// We need to not draw under the window // We need to not draw under the window

View file

@ -8,14 +8,6 @@
#include "damage.h" #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 /// 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 /// 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. /// 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); auto layout = layout_manager_layout(lm, l);
dynarr_foreach(layout->layers, layer) { dynarr_foreach(layout->layers, layer) {
log_trace("\t%#010x %dx%d+%dx%d (prev %d, next %d)", 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.size.height,
layer->window.origin.x, layer->window.origin.y, layer->window.origin.x, layer->window.origin.y,
layer->prev_rank, layer->next_rank); layer->prev_rank, layer->next_rank);
@ -265,9 +257,9 @@ void layout_manager_damage(struct layout_manager *lm, unsigned buffer_age,
break; break;
} }
assert(layer_key_eq(&past_layer->key, &curr_layer->key)); assert(wm_treeid_eq(past_layer->key, curr_layer->key));
log_trace("%#010x == %#010x %s", past_layer->key.window, log_trace("%#010x == %#010x %s", past_layer->key.x, curr_layer->key.x,
curr_layer->key.window, curr_layer->win->name); curr_layer->win->name);
if (!layer_compare(past_layer, past_layer_cmd, curr_layer, curr_layer_cmd)) { if (!layer_compare(past_layer, past_layer_cmd, curr_layer, curr_layer_cmd)) {
region_union_render_layer(damage, curr_layer, curr_layer_cmd); region_union_render_layer(damage, curr_layer, curr_layer_cmd);

View file

@ -18,7 +18,7 @@
#include "layout.h" #include "layout.h"
struct layer_index { struct layer_index {
UT_hash_handle hh; UT_hash_handle hh;
struct layer_key key; wm_treeid key;
unsigned index; unsigned index;
struct list_node free_list; 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 /// 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. /// 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; bool to_paint = false;
if (!w->ever_damaged || w->paint_excluded) { if (!w->ever_damaged || w->paint_excluded) {
goto out; 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->is_clipping = w->transparent_clipping;
out_layer->next_rank = -1; out_layer->next_rank = -1;
out_layer->prev_rank = -1; out_layer->prev_rank = -1;
out_layer->key = out_layer->key = wm_ref_treeid(w->tree_ref);
(struct layer_key){.window = w->base.id, .generation = w->base.generation};
out_layer->win = w; out_layer->win = w;
to_paint = true; 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]; auto layout = &lm->layouts[lm->current];
command_builder_command_list_free(layout->commands); command_builder_command_list_free(layout->commands);
layout->root_image_generation = root_pixmap_generation; 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; layout->size = size;
unsigned rank = 0; unsigned rank = 0;
struct layer_index *index, *next_index; struct layer_index *index, *next_index;
for (struct list_node *cursor = wm_stack_end(wm)->prev; wm_stack_foreach_rev(wm, cursor) {
cursor != wm_stack_end(wm); cursor = cursor->prev) { auto w = wm_ref_deref(cursor);
auto w = list_entry(cursor, struct win, stack_neighbour); if (w == NULL) {
if (!w->managed) {
continue; 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; 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; layout->layers[rank].prev_rank = (int)index->index;
} }
rank++; 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, // 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 // we could update the index using next_rank; if a layer no longer exist in

View file

@ -9,26 +9,15 @@
#include <picom/types.h> #include <picom/types.h>
#include "region.h" #include "region.h"
#include "wm/wm.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");
/// A layer to be rendered in a render layout /// A layer to be rendered in a render layout
struct layer { struct layer {
/// Window that will be rendered in this 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 /// The window, this is only valid for the current layout. Once
/// a frame has passed, windows could have been freed. /// a frame has passed, windows could have been freed.
struct managed_win *win; struct win *win;
/// Damaged region of this layer, in screen coordinates /// Damaged region of this layer, in screen coordinates
region_t damaged; region_t damaged;
/// Window rectangle in screen coordinates, before it's scaled. /// Window rectangle in screen coordinates, before it's scaled.

View file

@ -182,7 +182,7 @@ renderer_set_root_size(struct renderer *r, struct backend_base *backend, ivec2 r
} }
static bool 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}; ivec2 size = {.width = w->widthb, .height = w->heightb};
bool succeeded = false; bool succeeded = false;
auto image = backend->ops.new_image(backend, BACKEND_IMAGE_FORMAT_MASK, size); auto image = backend->ops.new_image(backend, BACKEND_IMAGE_FORMAT_MASK, size);
@ -346,8 +346,8 @@ out:
return shadow_image; return shadow_image;
} }
static bool renderer_bind_shadow(struct renderer *r, struct backend_base *backend, static bool
struct managed_win *w) { renderer_bind_shadow(struct renderer *r, struct backend_base *backend, struct win *w) {
if (backend->ops.quirks(backend) & BACKEND_QUIRK_SLOW_BLUR) { if (backend->ops.quirks(backend) & BACKEND_QUIRK_SLOW_BLUR) {
xcb_pixmap_t shadow = XCB_NONE; xcb_pixmap_t shadow = XCB_NONE;
xcb_render_picture_t pict = 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); assert(layer->number_of_commands > 0);
layer_end = cmd + layer->number_of_commands; layer_end = cmd + layer->number_of_commands;
log_trace("Prepare commands for layer %#010x @ %#010x (%s)", 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); layer->win->name);
} }
@ -522,8 +522,8 @@ bool renderer_render(struct renderer *r, struct backend_base *backend,
layer += 1; layer += 1;
layer_end += layer->number_of_commands; layer_end += layer->number_of_commands;
log_trace("Layer for window %#010x @ %#010x (%s)", log_trace("Layer for window %#010x @ %#010x (%s)",
layer->win->base.id, layer->win->client_win, win_id(layer->win),
layer->win->name); win_client_id(layer->win, false), layer->win->name);
} }
log_backend_command(TRACE, *i); log_backend_command(TRACE, *i);
} }

View file

@ -324,6 +324,7 @@ void wm_tree_destroy_window(struct wm_tree *tree, struct wm_tree_node *node) {
"malfunction.", "malfunction.",
node->id.x); node->id.x);
list_foreach_safe(struct wm_tree_node, i, &node->children, siblings) { 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); 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) { 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 == NULL);
BUG_ON(node->parent == NULL); // Trying to move the root window BUG_ON(node->parent == NULL); // Trying to move the root window

File diff suppressed because it is too large Load diff

View file

@ -20,20 +20,26 @@
#include "transition/script.h" #include "transition/script.h"
#include "utils/list.h" #include "utils/list.h"
#include "utils/misc.h" #include "utils/misc.h"
#include "wm/wm.h"
#include "x.h" #include "x.h"
#include "xcb/xproto.h" #include "xcb/xproto.h"
struct backend_base; struct backend_base;
typedef struct session session_t; typedef struct session session_t;
typedef struct _glx_texture glx_texture_t; typedef struct _glx_texture glx_texture_t;
struct wm_cursor;
#define win_stack_foreach_managed(w, win_stack) \ #define wm_stack_foreach(wm, i) \
list_foreach(struct managed_win, w, win_stack, \ for (struct wm_ref * (i) = wm_ref_topmost_child(wm_root_ref(wm)); (i); \
base.stack_neighbour) if ((w)->base.managed) (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) \ #define wm_stack_foreach_safe(wm, i, next_i) \
list_foreach_safe(struct managed_win, w, win_stack, \ for (struct wm_ref * (i) = wm_ref_topmost_child(wm_root_ref(wm)), \
base.stack_neighbour) if ((w)->base.managed) *(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 #ifdef CONFIG_OPENGL
// FIXME this type should be in opengl.h // FIXME this type should be in opengl.h
@ -73,25 +79,6 @@ struct window_stack_entry {
* considered part of the window. * 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 { struct win_geometry {
int16_t x; int16_t x;
int16_t y; int16_t y;
@ -106,22 +93,20 @@ struct win_state_change {
winstate_t state; winstate_t state;
}; };
struct managed_win { struct win {
struct win base; /// 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 /// backend data attached to this window. Only available when
/// `state` is not UNMAPPED /// `state` is not UNMAPPED
image_handle win_image; image_handle win_image;
image_handle shadow_image; image_handle shadow_image;
image_handle mask_image; image_handle mask_image;
// TODO(yshui) only used by legacy backends, remove.
/// Pointer to the next higher window to paint. /// Pointer to the next higher window to paint.
struct managed_win *prev_trans; struct win *prev_trans;
/// Number of windows above this window
int stacking_rank;
// TODO(yshui) rethink reg_ignore // TODO(yshui) rethink reg_ignore
// Core members // Core members
/// The "mapped state" of this window, doesn't necessary
/// match X mapped state, because of fading.
winstate_t state; winstate_t state;
/// Window attributes. /// Window attributes.
xcb_get_window_attributes_reply_t a; xcb_get_window_attributes_reply_t a;
@ -184,14 +169,12 @@ struct managed_win {
bool in_openclose; bool in_openclose;
// Client window related members // Client window related members
/// ID of the top-level client window of the window.
xcb_window_t client_win;
/// Type of the window. /// Type of the window.
wintype_t window_type; wintype_t window_type;
/// Leader window ID of the window. /// Leader window ID of the window.
xcb_window_t leader; xcb_window_t leader;
/// Cached topmost window ID of the window. /// Cached topmost window ID of the leader window.
xcb_window_t cache_leader; struct wm_ref *cache_leader;
// Focus-related members // Focus-related members
/// Whether the window is to be considered focused. /// 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, /// 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 /// or if the window's states just changed and there is no animation defined for this
/// state change. /// state change.
bool win_process_animation_and_state_change(struct session *ps, struct managed_win *w, bool win_process_animation_and_state_change(struct session *ps, struct win *w, double delta_t);
double delta_t); double win_animatable_get(const struct win *w, enum win_script_output output);
double win_animatable_get(const struct managed_win *w, enum win_script_output output); void win_process_update_flags(session_t *ps, struct win *w);
void win_process_update_flags(session_t *ps, struct managed_win *w); void win_process_image_flags(session_t *ps, struct win *w);
void win_process_image_flags(session_t *ps, struct managed_win *w);
/// Start the unmap of a window. We cannot unmap immediately since we might need to fade /// Start the unmap of a window. We cannot unmap immediately since we might need to fade
/// the window out. /// the window out.
void unmap_win_start(struct managed_win *); void unmap_win_start(struct win *);
void unmap_win_finish(session_t *ps, struct managed_win *w); void unmap_win_finish(session_t *ps, struct win *w);
/// Start the destroying of a window. Windows cannot always be destroyed immediately /// Start the destroying of a window. Windows cannot always be destroyed immediately
/// because of fading and such. /// 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 /// 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 /// used when de-initializing the backend outside of win.c
void win_release_images(struct backend_base *base, 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 managed_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 // TODO(yshui) `win_calc_mode` is only used by legacy backends
winmode_t attr_pure win_calc_mode(const struct managed_win *w); winmode_t attr_pure win_calc_mode(const struct win *w);
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);
void win_set_fade_force(struct managed_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 managed_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 managed_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. * 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);
void win_on_factor_change(session_t *ps, struct managed_win *w); void win_on_factor_change(session_t *ps, struct win *w);
void win_unmark_client(struct managed_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 /// 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 * 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 * Get a rectangular region in global coordinates a window (and possibly
* its shadow) occupies. * 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 * Note w->shadow and shadow geometry must be correct before calling this
* function. * function.
*/ */
void win_extents(const struct managed_win *w, region_t *res); void win_extents(const struct win *w, region_t *res);
region_t win_extents_by_val(const struct managed_win *w); region_t win_extents_by_val(const struct win *w);
/** /**
* Add a window to damaged area. * Add a window to damaged area.
* *
* @param ps current session * @param ps current session
* @param w struct _win element representing the window * @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. * Get a rectangular region a window occupies, excluding frame and shadow.
* *
* Return region in global coordinates. * Return region in global coordinates.
*/ */
void win_get_region_noframe_local(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 managed_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 /// 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 /// 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` /// Query the Xorg for information about window `win`
/// `win` pointer might become invalid after this function returns /// `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. * 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 * 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 /// 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 /// 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- /// it does not have a decedent with WM_STATE and it is not override-
/// redirected itself. /// redirected itself.
static inline bool attr_pure win_is_wmwin(const struct managed_win *w) { static inline bool attr_pure win_is_wmwin(const struct win *w) {
return w->base.id == w->client_win && !w->a.override_redirect; return wm_ref_client_of(w->tree_ref) == NULL && !w->a.override_redirect;
} }
static inline struct managed_win *win_as_managed(struct win *w) { static inline xcb_window_t win_id(const struct win *w) {
BUG_ON(!w->managed); return wm_ref_win_id(w->tree_ref);
return (struct managed_win *)w;
} }
static inline const char *win_get_name_if_managed(const struct win *w) { /// Returns the client window of a window. If a client window does not exist, returns the
if (!w->managed) { /// window itself when `fallback_to_self` is true, otherwise returns XCB_NONE.
return "(unmanaged)"; 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 wm_ref_win_id(client_win);
return mw->name;
} }
/// check if reg_ignore_valid is true for all windows above us /// 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 /// 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 /// 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 /// 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 /// 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 /// 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 /// 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, bool win_update_wintype(struct x_connection *c, struct atom *atoms, struct win *w);
struct atom *atoms, const struct managed_win *w);
bool win_update_wintype(struct x_connection *c, struct atom *atoms, struct managed_win *w);
/** /**
* Retrieve frame extents from a window. * Retrieve frame extents from a window.
*/ */
void win_update_frame_extents(struct x_connection *c, struct atom *atoms, void win_update_frame_extents(struct x_connection *c, struct atom *atoms, struct win *w,
struct managed_win *w, xcb_window_t client, xcb_window_t client, double frame_opacity);
double frame_opacity);
/** /**
* Retrieve the <code>WM_CLASS</code> of a window and update its * Retrieve the <code>WM_CLASS</code> of a window and update its
* <code>win</code> structure. * <code>win</code> 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);
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);
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);
void win_on_win_size_change(struct managed_win *w, int shadow_offset_x, void win_on_win_size_change(struct win *w, int shadow_offset_x, int shadow_offset_y,
int shadow_offset_y, int shadow_radius); int shadow_radius);
void win_update_bounding_shape(struct x_connection *c, struct managed_win *w, void win_update_bounding_shape(struct x_connection *c, struct win *w, bool shape_exists,
bool shape_exists, bool detect_rounded_corners); bool detect_rounded_corners);
bool win_update_prop_fullscreen(struct x_connection *c, const struct atom *atoms, 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); return win_set_properties_stale(w, (xcb_atom_t[]){prop}, 1);
} }
/// Free all resources in a struct win /// 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 /// Remove the corners of window `w` from region `res`. `origin` is the top-left corner of
/// `w` in `res`'s coordinate system. /// `w` in `res`'s coordinate system.
static inline void 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] = { static const int corner_index[][2] = {
{0, 0}, {0, 0},
{0, 1}, {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). /// Like `win_region_remove_corners`, but `origin` is (0, 0).
static inline void static inline void win_region_remove_corners_local(const struct win *w, region_t *res) {
win_region_remove_corners_local(const struct managed_win *w, region_t *res) {
win_region_remove_corners(w, (ivec2){0, 0}, 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; region_t ret;
pixman_region32_init(&ret); pixman_region32_init(&ret);
pixman_region32_copy(&ret, &w->bounding_shape); 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; return ret;
} }
static inline region_t static inline region_t win_get_bounding_shape_global_without_corners_by_val(struct win *w) {
win_get_bounding_shape_global_without_corners_by_val(struct managed_win *w) {
region_t ret; region_t ret;
pixman_region32_init(&ret); pixman_region32_init(&ret);
pixman_region32_copy(&ret, &w->bounding_shape); 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 * Calculate the extents of the frame of the given window based on EWMH
* _NET_FRAME_EXTENTS and the X window border width. * _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; margin_t result = w->frame_extents;
result.top = max2(result.top, w->g.border_width); result.top = max2(result.top, w->g.border_width);
result.left = max2(result.left, 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. * 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 || return w->g.border_width || w->frame_extents.top || w->frame_extents.left ||
w->frame_extents.right || w->frame_extents.bottom; w->frame_extents.right || w->frame_extents.bottom;
} }

View file

@ -9,7 +9,6 @@
#include "log.h" #include "log.h"
#include "utils/dynarr.h" #include "utils/dynarr.h"
#include "utils/list.h" #include "utils/list.h"
#include "utils/uthash_extra.h"
#include "x.h" #include "x.h"
#include "win.h" #include "win.h"
@ -17,32 +16,15 @@
#include "wm_internal.h" #include "wm_internal.h"
struct wm { 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 <code>win</code> of current active window. Used by /// Pointer to <code>win</code> of current active window. Used by
/// EWMH <code>_NET_ACTIVE_WINDOW</code> focus detection. In theory, /// EWMH <code>_NET_ACTIVE_WINDOW</code> focus detection. In theory,
/// it's more reliable to store the window ID directly here, just in /// it's more reliable to store the window ID directly here, just in
/// case the WM does something extraordinary, but caching the pointer /// case the WM does something extraordinary, but caching the pointer
/// means another layer of complexity. /// 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 /// Window ID of leader window of currently active window. Used for
/// subsidiary window detection. /// subsidiary window detection.
xcb_window_t active_leader; struct wm_tree_node *active_leader;
struct subwin *subwins;
struct wm_tree tree; struct wm_tree tree;
struct wm_tree_node *root; struct wm_tree_node *root;
@ -55,14 +37,6 @@ struct wm {
struct wm_tree_node **masked; 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 // 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 // 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. // 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; : 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) { xcb_window_t wm_ref_win_id(const struct wm_ref *cursor) {
return to_tree_node(cursor)->id.x; return to_tree_node(cursor)->id.x;
} }
struct managed_win *wm_active_win(struct wm *wm) { wm_treeid wm_ref_treeid(const struct wm_ref *cursor) {
return wm->active_win; 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) { 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; return -1;
} }
void wm_set_active_win(struct wm *wm, struct managed_win *w) { 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;
}
void wm_set_active_win(struct wm *wm, struct win *w) {
wm->active_win = w; wm->active_win = w;
} }
xcb_window_t wm_active_leader(struct wm *wm) { struct wm_ref *wm_active_leader(struct wm *wm) {
return wm->active_leader; return wm->active_leader != NULL ? (struct wm_ref *)&wm->active_leader->siblings : NULL;
} }
void wm_set_active_leader(struct wm *wm, xcb_window_t leader) { void wm_set_active_leader(struct wm *wm, struct wm_ref *leader) {
wm->active_leader = leader; wm->active_leader = to_tree_node_mut(leader);
} }
struct win *wm_stack_next(struct wm *wm, struct list_node *cursor) { bool wm_ref_is_zombie(const struct wm_ref *cursor) {
if (!list_node_is_last(&wm->window_stack, cursor)) { return to_tree_node(cursor)->is_zombie;
return list_entry(cursor->next, struct win, stack_neighbour);
}
return NULL;
} }
// Find the managed window immediately below `i` in the window stack struct wm_ref *wm_ref_below(const struct wm_ref *cursor) {
struct managed_win * return &to_tree_node(cursor)->parent->children != cursor->inner.next
wm_stack_next_managed(const struct wm *wm, const struct list_node *cursor) { ? (struct wm_ref *)cursor->inner.next
while (!list_node_is_last(&wm->window_stack, cursor)) { : NULL;
auto next = list_entry(cursor->next, struct win, stack_neighbour);
if (next->managed) {
return (struct managed_win *)next;
}
cursor = &next->stack_neighbour;
}
return NULL;
} }
/// Find a managed window from window id in window linked list of the session. struct wm_ref *wm_ref_above(const struct wm_ref *cursor) {
struct win *wm_find(struct wm *wm, xcb_window_t id) { return &to_tree_node(cursor)->parent->children != cursor->inner.prev
if (!id) { ? (struct wm_ref *)cursor->inner.prev
return NULL; : NULL;
}
struct win *w = NULL;
HASH_FIND_INT(wm->windows, &id, w);
assert(w == NULL || !w->destroyed);
return w;
} }
void wm_remove(struct wm *wm, struct win *w) { struct wm_ref *wm_root_ref(const struct wm *wm) {
wm->generation++; return (struct wm_ref *)&wm->root->siblings;
HASH_DEL(wm->windows, w);
} }
int wm_foreach(struct wm *wm, int (*func)(struct win *, void *), void *data) { struct wm_ref *wm_ref_topmost_child(const struct wm_ref *cursor) {
HASH_ITER2(wm->windows, w) { auto node = to_tree_node(cursor);
assert(!w->destroyed); return !list_is_empty(&node->children) ? (struct wm_ref *)node->children.next : NULL;
int ret = func(w, data);
if (ret) {
return ret;
}
}
return 0;
} }
void wm_stack_replace(struct wm *wm, struct win *old, struct win *new_) { struct wm_ref *wm_ref_bottommost_child(const struct wm_ref *cursor) {
list_replace(&old->stack_neighbour, &new_->stack_neighbour); auto node = to_tree_node(cursor);
struct win *replaced = NULL; return !list_is_empty(&node->children) ? (struct wm_ref *)node->children.prev : NULL;
HASH_REPLACE_INT(wm->windows, id, new_, replaced);
assert(replaced == old);
free(old);
} }
/// Insert a new window after list_node `prev` struct wm_ref *wm_find(const struct wm *wm, xcb_window_t id) {
/// New window will be in unmapped state auto node = wm_tree_find(&wm->tree, id);
static struct win * return node != NULL ? (struct wm_ref *)&node->siblings : NULL;
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) { struct wm_ref *wm_find_by_client(const struct wm *wm, xcb_window_t client) {
return wm_stack_insert_after(wm, id, &wm->window_stack); auto node = wm_tree_find(&wm->tree, client);
return node != NULL ? (struct wm_ref *)&wm_tree_find_toplevel_for(node)->siblings
: NULL;
} }
struct win *wm_stack_add_above(struct wm *wm, xcb_window_t id, xcb_window_t below) { struct wm_ref *wm_ref_toplevel_of(struct wm_ref *cursor) {
struct win *w = NULL; return (struct wm_ref *)&wm_tree_find_toplevel_for(to_tree_node_mut(cursor))->siblings;
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);
} }
/// Move window `w` so it's before `next` in the list struct wm_ref *wm_ref_client_of(struct wm_ref *cursor) {
static inline void wm_stack_move_before(struct wm *wm, struct win *w, struct list_node *next) { auto client = to_tree_node(cursor)->client_window;
struct managed_win *mw = NULL; return client != NULL ? (struct wm_ref *)&client->siblings : 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 list_node *wm_stack_end(struct wm *wm) { void wm_remove(struct wm *wm, struct wm_ref *w) {
return &wm->window_stack; 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->root->children;
} }
/// Move window `w` so it's right above `below`, if `below` is 0, `w` is moved /// Move window `w` so it's right above `below`, if `below` is 0, `w` is moved
/// to the bottom of the stack /// to the bottom of the stack
void wm_stack_move_above(struct wm *wm, struct win *w, xcb_window_t below) { void wm_stack_move_to_above(struct wm *wm, struct wm_ref *cursor, struct wm_ref *below) {
xcb_window_t old_below; wm_tree_move_to_above(&wm->tree, to_tree_node_mut(cursor), to_tree_node_mut(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);
}
} }
void wm_stack_move_to_top(struct wm *wm, struct win *w) { void wm_stack_move_to_end(struct wm *wm, struct wm_ref *cursor, bool to_bottom) {
if (&w->stack_neighbour == wm->window_stack.next) { wm_tree_move_to_end(&wm->tree, to_tree_node_mut(cursor), to_bottom);
// already at top
return;
}
wm_stack_move_before(wm, w, wm->window_stack.next);
}
unsigned wm_stack_num_managed_windows(const struct wm *wm) {
unsigned count = 0;
list_foreach(struct win, w, &wm->window_stack, stack_neighbour) {
if (w->managed) {
count += 1;
}
}
return count;
}
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 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;
}
unsigned wm_num_windows(const struct wm *wm) {
return HASH_COUNT(wm->windows);
}
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 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);
}
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);
}
}
} }
struct wm *wm_new(void) { struct wm *wm_new(void) {
auto wm = ccalloc(1, struct wm); auto wm = ccalloc(1, struct wm);
list_init_head(&wm->window_stack);
wm->generation = 1;
wm_tree_init(&wm->tree); wm_tree_init(&wm->tree);
wm->incompletes = dynarr_new(struct wm_tree_node *, 4); wm->incompletes = dynarr_new(struct wm_tree_node *, 4);
wm->masked = dynarr_new(struct wm_tree_node *, 8); wm->masked = dynarr_new(struct wm_tree_node *, 8);
return wm; return wm;
} }
void wm_free(struct wm *wm, struct x_connection *c) { void wm_free(struct wm *wm) {
list_foreach_safe(struct win, w, &wm->window_stack, stack_neighbour) { // Free all `struct win`s associated with tree nodes, this leaves dangling
if (w->managed) { // pointers, but we are freeing the tree nodes immediately after, so everything
XCB_AWAIT_VOID(xcb_change_window_attributes, c->c, w->id, // is fine (TM).
XCB_CW_EVENT_MASK, (const uint32_t[]){0}); wm_stack_foreach_safe(wm, i, next) {
} auto w = wm_ref_deref(i);
if (!w->destroyed) { auto tree_node = to_tree_node_mut(i);
HASH_DEL(wm->windows, w);
}
free(w); free(w);
}
list_init_head(&wm->window_stack);
struct subwin *subwin, *next_subwin; if (tree_node->is_zombie) {
HASH_ITER(hh, wm->subwins, subwin, next_subwin) { // This mainly happens on `session_destroy`, e.g. when there's
wm_subwin_remove_and_unsubscribe(wm, c, subwin); // 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); wm_tree_clear(&wm->tree);
dynarr_free_pod(wm->incompletes); 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); 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) { void wm_reparent(struct wm *wm, xcb_window_t wid, xcb_window_t parent) {
auto window = wm_tree_find(&wm->tree, wid); auto window = wm_tree_find(&wm->tree, wid);
auto new_parent = wm_tree_find(&wm->tree, parent); 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); 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) { void wm_import_incomplete(struct wm *wm, xcb_window_t wid, xcb_window_t parent) {
auto masked = wm_find_masked(wm, wid); auto masked = wm_find_masked(wm, wid);
if (masked != -1) { 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); wm_complete_import_single(wm, c, atoms, subroot);
for (auto curr = subroot; curr != NULL; curr = wm_tree_next(curr, subroot)) { for (auto curr = subroot; curr != NULL; curr = wm_tree_next(curr, subroot)) {
if (!list_is_empty(&curr->children)) { // Surprise! This newly imported window might already have children.
log_error("Newly imported subtree root at %#010x already has " // Although we haven't setup SubstructureNotify for it yet, it's still
"children. " // possible for another window to be reparented to it.
"Expect malfunction.",
curr->id.x);
}
xcb_query_tree_reply_t *tree = XCB_AWAIT(xcb_query_tree, c->c, curr->id.x); xcb_query_tree_reply_t *tree = XCB_AWAIT(xcb_query_tree, c->c, curr->id.x);
if (!tree) { if (!tree) {

View file

@ -23,7 +23,7 @@
#include "compiler.h" #include "compiler.h"
struct wm; struct wm;
struct managed_win; struct win;
struct list_node; struct list_node;
struct x_connection; struct x_connection;
@ -99,12 +99,12 @@ static inline bool wm_treeid_eq(wm_treeid a, wm_treeid b) {
} }
struct wm *wm_new(void); 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); struct win *wm_active_win(struct wm *wm);
void wm_set_active_win(struct wm *wm, struct managed_win *w); void wm_set_active_win(struct wm *wm, struct win *w);
xcb_window_t wm_active_leader(struct wm *wm); struct wm_ref *wm_active_leader(struct wm *wm);
void wm_set_active_leader(struct wm *wm, xcb_window_t leader); 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 // 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 // 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. // 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. /// 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. /// Remove a window from the hash table.
void wm_remove(struct wm *wm, struct win *w); void wm_remove(struct wm *wm, struct wm_ref *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);
// Find the WM frame of a client window. `id` is the client window id. // Find the WM frame of a client window. `id` is the client window id.
struct managed_win *wm_find_by_client(struct wm *wm, xcb_window_t client); struct wm_ref *attr_pure wm_find_by_client(const struct wm *wm, xcb_window_t client);
/// Call `func` on each toplevel window. `func` should return 0 if the iteration /// Find the toplevel of a window by going up the window tree.
/// should continue. If it returns anything else, the iteration will stop and the struct wm_ref *attr_pure wm_ref_toplevel_of(struct wm_ref *cursor);
/// return value will be returned from `wm_foreach`. If the iteration finishes /// Return the client window of a window. Must be called with a cursor to a toplevel.
/// naturally, 0 will be returned. /// Returns NULL if there is no client window.
int wm_foreach(struct wm *wm, int (*func)(struct win *, void *), void *data); struct wm_ref *attr_pure wm_ref_client_of(struct wm_ref *cursor);
/// Returns the number of windows in the hash table. /// Find the next window in the window stack. Returns NULL if `cursor` is the last window.
unsigned attr_const wm_num_windows(const struct wm *wm); 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);
/// 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);
/// Move window `w` so it's right above `below`, if `below` is 0, `w` is moved /// Move window `w` so it's right above `below`, if `below` is 0, `w` is moved
/// to the bottom of the stack /// to the bottom of the stack
void wm_stack_move_above(struct wm *wm, struct win *w, xcb_window_t below); void wm_stack_move_to_above(struct wm *wm, struct wm_ref *cursor, struct wm_ref *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);
}
/// Move window `w` to the top of the stack. /// Move window `w` to the top of the stack.
void wm_stack_move_to_top(struct wm *wm, struct win *w); void wm_stack_move_to_end(struct wm *wm, struct wm_ref *cursor, bool to_bottom);
/// 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, struct win *attr_pure wm_ref_deref(const struct wm_ref *cursor);
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);
xcb_window_t attr_pure wm_ref_win_id(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 /// 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 /// will cause a `WM_TREE_CHANGE_TOPLEVEL_KILLED` event to be generated, and a zombie
/// window to be placed where the window was. /// window to be placed where the window was.
void wm_destroy(struct wm *wm, xcb_window_t wid); 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_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 /// 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 /// be in the window tree. This function creates a placeholder tree node, without

View file

@ -13,6 +13,14 @@
struct wm_tree { struct wm_tree {
/// The generation of the wm tree. This number is incremented every time a new /// The generation of the wm tree. This number is incremented every time a new
/// window is created. /// 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; uint64_t gen;
/// wm tree nodes indexed by their X window ID. /// wm tree nodes indexed by their X window ID.
struct wm_tree_node *nodes; 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 /// Free all tree nodes and changes, without generating any change events. Used when
/// shutting down. /// shutting down.
void wm_tree_clear(struct wm_tree *tree); 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 *attr_pure 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_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 /// 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 /// `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 /// 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 * struct wm_tree_node *
wm_tree_new_window(struct wm_tree *tree, xcb_window_t id, struct wm_tree_node *parent); 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); 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 /// 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 /// its parent, and the disconnected tree nodes won't be able to be found via
/// `wm_tree_find`. Relevant events will be generated. /// `wm_tree_find`. Relevant events will be generated.
void wm_tree_detach(struct wm_tree *tree, struct wm_tree_node *subroot); 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, void wm_tree_reparent(struct wm_tree *tree, struct wm_tree_node *node,
struct wm_tree_node *new_parent); 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); 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); 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_reap_zombie(struct wm_tree_node *zombie);