// SPDX-License-Identifier: MPL-2.0 // Copyright (c) Yuxuan Shui #include #include #include "list.h" #include "log.h" #include "uthash_extra.h" #include "win.h" #include "wm.h" #include "x.h" struct wm { /// Current window generation, start from 1. 0 is reserved for using as /// an invalid generation number. /// /// Because X server recycles window IDs, `id` along /// is not enough to uniquely identify a window. This generation number is /// incremented every time a window is destroyed, so that if a window ID is /// reused, its generation number will be different from before. /// Unless, of course, if the generation number overflows, but since we are /// using a uint64_t here, that won't happen for a very long time. Still, /// it is recommended that you restart the compositor at least once before /// the Universe collapse back on itself. uint64_t generation; /// A hash table of all windows. struct win *windows; /// Windows in their stacking order struct list_node window_stack; /// Pointer to win of current active window. Used by /// EWMH _NET_ACTIVE_WINDOW focus detection. In theory, /// it's more reliable to store the window ID directly here, just in /// case the WM does something extraordinary, but caching the pointer /// means another layer of complexity. struct managed_win *active_win; /// Window ID of leader window of currently active window. Used for /// subsidiary window detection. xcb_window_t active_leader; struct subwin *subwins; }; unsigned int wm_get_window_count(struct wm *wm) { unsigned int count = 0; HASH_ITER2(wm->windows, w) { assert(!w->destroyed); ++count; } return count; } struct managed_win *wm_active_win(struct wm *wm) { return wm->active_win; } void wm_set_active_win(struct wm *wm, struct managed_win *w) { wm->active_win = w; } xcb_window_t wm_active_leader(struct wm *wm) { return wm->active_leader; } void wm_set_active_leader(struct wm *wm, xcb_window_t leader) { wm->active_leader = leader; } struct win *wm_stack_next(struct wm *wm, struct list_node *cursor) { if (!list_node_is_last(&wm->window_stack, cursor)) { return list_entry(cursor->next, struct win, stack_neighbour); } return NULL; } // Find the managed window immediately below `i` in the window stack struct managed_win * wm_stack_next_managed(const struct wm *wm, const struct list_node *cursor) { while (!list_node_is_last(&wm->window_stack, cursor)) { auto next = list_entry(cursor->next, struct win, stack_neighbour); if (next->managed) { return (struct managed_win *)next; } cursor = &next->stack_neighbour; } return NULL; } /// Find a managed window from window id in window linked list of the session. struct win *wm_find(struct wm *wm, xcb_window_t id) { if (!id) { return NULL; } struct win *w = NULL; HASH_FIND_INT(wm->windows, &id, w); assert(w == NULL || !w->destroyed); return w; } void wm_remove(struct wm *wm, struct win *w) { wm->generation++; HASH_DEL(wm->windows, w); } int wm_foreach(struct wm *wm, int (*func)(struct win *, void *), void *data) { HASH_ITER2(wm->windows, w) { assert(!w->destroyed); int ret = func(w, data); if (ret) { return ret; } } return 0; } void wm_stack_replace(struct wm *wm, struct win *old, struct win *new_) { list_replace(&old->stack_neighbour, &new_->stack_neighbour); struct win *replaced = NULL; HASH_REPLACE_INT(wm->windows, id, new_, replaced); assert(replaced == old); free(old); } /// Insert a new window after list_node `prev` /// New window will be in unmapped state static struct win * wm_stack_insert_after(struct wm *wm, xcb_window_t id, struct list_node *prev) { log_debug("Adding window %#010x", id); struct win *old_w = NULL; HASH_FIND_INT(wm->windows, &id, old_w); assert(old_w == NULL); auto new_w = cmalloc(struct win); list_insert_after(prev, &new_w->stack_neighbour); new_w->id = id; new_w->managed = false; new_w->is_new = true; new_w->destroyed = false; new_w->generation = wm->generation; HASH_ADD_INT(wm->windows, id, new_w); return new_w; } struct win *wm_stack_add_top(struct wm *wm, xcb_window_t id) { return wm_stack_insert_after(wm, id, &wm->window_stack); } struct win *wm_stack_add_above(struct wm *wm, xcb_window_t id, xcb_window_t below) { struct win *w = NULL; HASH_FIND_INT(wm->windows, &below, w); if (!w) { if (!list_is_empty(&wm->window_stack)) { // `below` window is not found even if the window stack is // not empty return NULL; } return wm_stack_add_top(wm, id); } // we found something from the hash table, so if the stack is // empty, we are in an inconsistent state. assert(!list_is_empty(&wm->window_stack)); return wm_stack_insert_after(wm, id, w->stack_neighbour.prev); } /// Move window `w` so it's before `next` in the list static inline void wm_stack_move_before(struct wm *wm, struct win *w, struct list_node *next) { struct managed_win *mw = NULL; if (w->managed) { mw = (struct managed_win *)w; } if (mw) { // This invalidates all reg_ignore below the new stack position of // `w` mw->reg_ignore_valid = false; rc_region_unref(&mw->reg_ignore); // This invalidates all reg_ignore below the old stack position of // `w` auto next_w = wm_stack_next_managed(wm, &w->stack_neighbour); if (next_w) { next_w->reg_ignore_valid = false; rc_region_unref(&next_w->reg_ignore); } } list_move_before(&w->stack_neighbour, next); #ifdef DEBUG_RESTACK log_trace("Window stack modified. Current stack:"); for (auto c = &wm->window_stack; c; c = c->next) { const char *desc = ""; if (c->state == WSTATE_DESTROYING) { desc = "(D) "; } log_trace("%#010x \"%s\" %s", c->id, c->name, desc); } #endif } struct list_node *wm_stack_end(struct wm *wm) { return &wm->window_stack; } /// Move window `w` so it's right above `below`, if `below` is 0, `w` is moved /// to the bottom of the stack void wm_stack_move_above(struct wm *wm, struct win *w, xcb_window_t below) { xcb_window_t old_below; if (!list_node_is_last(&wm->window_stack, &w->stack_neighbour)) { old_below = list_next_entry(w, stack_neighbour)->id; } else { old_below = XCB_NONE; } log_debug("Restack %#010x (%s), old_below: %#010x, new_below: %#010x", w->id, win_get_name_if_managed(w), old_below, below); if (old_below != below) { struct list_node *new_next; if (!below) { new_next = &wm->window_stack; } else { struct win *tmp_w = NULL; HASH_FIND_INT(wm->windows, &below, tmp_w); if (!tmp_w) { log_error("Failed to found new below window %#010x.", below); return; } new_next = &tmp_w->stack_neighbour; } wm_stack_move_before(wm, w, new_next); } } void wm_stack_move_to_top(struct wm *wm, struct win *w) { if (&w->stack_neighbour == wm->window_stack.next) { // already at top return; } wm_stack_move_before(wm, w, wm->window_stack.next); } 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) { auto wm = ccalloc(1, struct wm); list_init_head(&wm->window_stack); wm->generation = 1; return wm; } void wm_free(struct wm *wm, struct x_connection *c) { list_foreach_safe(struct win, w, &wm->window_stack, stack_neighbour) { if (w->managed) { XCB_AWAIT_VOID(xcb_change_window_attributes, c->c, w->id, XCB_CW_EVENT_MASK, (const uint32_t[]){0}); } if (!w->destroyed) { HASH_DEL(wm->windows, w); } free(w); } list_init_head(&wm->window_stack); struct subwin *subwin, *next_subwin; HASH_ITER(hh, wm->subwins, subwin, next_subwin) { wm_subwin_remove_and_unsubscribe(wm, c, subwin); } free(wm); }