From a5ea66a134f42bf3fb29048df62abbf0f46121d3 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 26 Mar 2024 17:54:34 +0000 Subject: [PATCH] wm: separate out window management into a file Conversion is mostly mechanical, except a couple `ps->pending_updates = true` is added. Signed-off-by: Yuxuan Shui --- src/common.h | 17 +- src/dbus.c | 112 +++++++------- src/event.c | 73 +++++---- src/meson.build | 2 +- src/opengl.c | 3 +- src/picom.c | 98 +++++------- src/win.c | 400 +++++++++--------------------------------------- src/win.h | 52 ++----- src/wm.c | 340 ++++++++++++++++++++++++++++++++++++++++ src/wm.h | 99 ++++++++++++ 10 files changed, 658 insertions(+), 538 deletions(-) create mode 100644 src/wm.c create mode 100644 src/wm.h diff --git a/src/common.h b/src/common.h index 80d9bf00..a861a59a 100644 --- a/src/common.h +++ b/src/common.h @@ -288,22 +288,7 @@ typedef struct session { /// Index of the next free slot in expose_rects. int n_expose; - // === Window related === - /// A hash table of all windows. - struct win *windows; - /// Direct children of all toplevels. - struct subwin *subwins; - /// 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 wm *wm; // === Shadow/dimming related === /// 1x1 black Picture. diff --git a/src/dbus.c b/src/dbus.c index 5dd0ed2b..f443f42c 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -32,6 +32,7 @@ #include "utils.h" #include "win.h" #include "win_defs.h" +#include "wm.h" #include "dbus.h" @@ -470,41 +471,41 @@ static bool cdbus_append_string_variant(DBusMessage *msg, const char *data) { return true; } +static int cdbus_append_wids_callback(struct win *w, void *data) { + DBusMessageIter *iter = data; + cdbus_window_t wid = w->id; + if (!dbus_message_iter_append_basic(iter, CDBUS_TYPE_WINDOW, &wid)) { + return 1; + } + return 0; +} + /// Append all window IDs in the window list of a session to a D-Bus message static bool cdbus_append_wids(DBusMessage *msg, session_t *ps) { // Get the number of wids we are to include - unsigned count = 0; - HASH_ITER2(ps->windows, w) { - assert(!w->destroyed); - ++count; - } - + unsigned count = wm_num_windows(ps->wm); if (!count) { // Nothing to append return true; } - // Allocate memory for an array of window IDs - auto arr = ccalloc(count, cdbus_window_t); - - // Build the array - cdbus_window_t *pcur = arr; - HASH_ITER2(ps->windows, w) { - assert(!w->destroyed); - *pcur = w->id; - ++pcur; - } - assert(pcur == arr + count); - - // Append arguments - if (!dbus_message_append_args(msg, DBUS_TYPE_ARRAY, CDBUS_TYPE_WINDOW, &arr, - count, DBUS_TYPE_INVALID)) { - log_error("Failed to append argument."); - free(arr); + DBusMessageIter it, subit; + dbus_message_iter_init_append(msg, &it); + if (!dbus_message_iter_open_container(&it, DBUS_TYPE_ARRAY, + DBUS_TYPE_UINT32_AS_STRING, &subit)) { + log_error("Failed to open container."); return false; } - free(arr); + auto result = wm_foreach(ps->wm, cdbus_append_wids_callback, &subit); + if (!dbus_message_iter_close_container(&it, &subit)) { + log_error("Failed to close container."); + return false; + } + if (result != 0) { + log_error("Failed to append argument."); + return false; + } return true; } @@ -587,7 +588,7 @@ cdbus_process_window_property_get(session_t *ps, DBusMessage *msg, cdbus_window_ return DBUS_HANDLER_RESULT_HANDLED; } - auto w = find_managed_win(ps, wid); + auto w = wm_find_managed(ps->wm, wid); if (!w) { log_debug("Window %#010x not found.", wid); @@ -614,7 +615,7 @@ cdbus_process_window_property_get(session_t *ps, DBusMessage *msg, cdbus_window_ if (!strcmp("Next", target)) { cdbus_window_t next_id = 0; - if (!list_node_is_last(&ps->window_stack, &w->base.stack_neighbour)) { + if (!list_node_is_last(wm_stack_end(ps->wm), &w->base.stack_neighbour)) { next_id = list_entry(w->base.stack_neighbour.next, struct win, stack_neighbour) ->id; @@ -674,7 +675,7 @@ cdbus_process_win_get(session_t *ps, DBusMessage *msg, DBusMessage *reply, DBusE return DBUS_HANDLER_RESULT_HANDLED; } - auto w = find_managed_win(ps, wid); + auto w = wm_find_managed(ps->wm, wid); if (!w) { log_debug("Window %#010x not found.", wid); @@ -693,7 +694,7 @@ cdbus_process_win_get(session_t *ps, DBusMessage *msg, DBusMessage *reply, DBusE if (!strcmp("next", target)) { xcb_window_t next_id = - list_node_is_last(&ps->window_stack, &w->base.stack_neighbour) + list_node_is_last(wm_stack_end(ps->wm), &w->base.stack_neighbour) ? 0 : list_entry(w->base.stack_neighbour.next, struct win, stack_neighbour) ->id; @@ -759,7 +760,7 @@ cdbus_process_win_set(session_t *ps, DBusMessage *msg, DBusMessage *reply, DBusE return DBUS_HANDLER_RESULT_HANDLED; } - auto w = find_managed_win(ps, wid); + auto w = wm_find_managed(ps->wm, wid); if (!w) { log_debug("Window %#010x not found.", wid); @@ -816,14 +817,15 @@ cdbus_process_find_win(session_t *ps, DBusMessage *msg, DBusMessage *reply, DBus dbus_set_error_const(err, DBUS_ERROR_INVALID_ARGS, NULL); return DBUS_HANDLER_RESULT_HANDLED; } - auto w = find_toplevel(ps, client); + auto w = wm_find_by_client(ps->wm, client); if (w) { wid = w->base.id; } } else if (!strcmp("focused", target)) { // Find focused window - if (ps->active_win && ps->active_win->state != WSTATE_UNMAPPED) { - wid = ps->active_win->base.id; + auto active_win = wm_active_win(ps->wm); + if (active_win && active_win->state != WSTATE_UNMAPPED) { + wid = active_win->base.id; } } else { log_debug(CDBUS_ERROR_BADTGT_S, target); @@ -1066,6 +1068,21 @@ static DBusHandlerResult cdbus_process_introspect(DBusMessage *reply) { } ///@} +static int cdbus_process_windows_root_introspect_callback(struct win *w, void *data) { + char **introspect = data; + if (!w->managed) { + return 0; + } + char *tmp = NULL; + if (asprintf(&tmp, "\n", w->id) < 0) { + log_fatal("Failed to allocate memory."); + return 1; + } + mstrextend(introspect, tmp); + free(tmp); + return 0; +} + /** * Process an D-Bus Introspect request, for /windows. */ @@ -1086,31 +1103,16 @@ cdbus_process_windows_root_introspect(session_t *ps, DBusMessage *reply) { return DBUS_HANDLER_RESULT_HANDLED; } - char *ret = NULL; - bool success = true; - mstrextend(&ret, str_introspect); - - HASH_ITER2(ps->windows, w) { - assert(!w->destroyed); - if (!w->managed) { - continue; - } - char *tmp = NULL; - if (asprintf(&tmp, "\n", w->id) < 0) { - log_fatal("Failed to allocate memory."); - success = false; - break; - } - mstrextend(&ret, tmp); - free(tmp); + scoped_charp introspect = NULL; + mstrextend(&introspect, str_introspect); + if (wm_foreach(ps->wm, cdbus_process_windows_root_introspect_callback, &introspect)) { + return DBUS_HANDLER_RESULT_NEED_MEMORY; } - mstrextend(&ret, ""); - - if (success) { - success = cdbus_append_string(reply, ret); + mstrextend(&introspect, ""); + if (!cdbus_append_string(reply, introspect)) { + return DBUS_HANDLER_RESULT_NEED_MEMORY; } - free(ret); - return success ? DBUS_HANDLER_RESULT_HANDLED : DBUS_HANDLER_RESULT_NEED_MEMORY; + return DBUS_HANDLER_RESULT_HANDLED; } static bool cdbus_process_window_introspect(DBusMessage *reply) { diff --git a/src/event.c b/src/event.c index b004d1e6..df6c981a 100644 --- a/src/event.c +++ b/src/event.c @@ -24,6 +24,7 @@ #include "utils.h" #include "win.h" #include "win_defs.h" +#include "wm.h" #include "x.h" /// Event handling with X is complicated. Handling events with other events possibly @@ -73,9 +74,9 @@ static inline const char *ev_window_name(session_t *ps, xcb_window_t wid) { } else if (ps->overlay == wid) { name = "(Overlay)"; } else { - auto w = find_managed_win(ps, wid); + auto w = wm_find_managed(ps->wm, wid); if (!w) { - w = find_toplevel(ps, wid); + w = wm_find_by_client(ps->wm, wid); } if (w && w->name) { @@ -196,11 +197,12 @@ static inline void ev_focus_out(session_t *ps, xcb_focus_out_event_t *ev) { static inline void ev_create_notify(session_t *ps, xcb_create_notify_event_t *ev) { if (ev->parent == ps->c.screen_info->root) { - add_win_top(ps, ev->window); + wm_stack_add_top(ps->wm, ev->window); + ps->pending_updates = true; return; } - auto w = find_managed_win(ps, ev->parent); + 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 @@ -209,7 +211,7 @@ static inline void ev_create_notify(session_t *ps, xcb_create_notify_event_t *ev } // A direct child of a toplevel, subscribe to property changes so we can // know if WM_STATE is set on this window. - add_subwin_and_subscribe(&ps->subwins, &ps->c, ev->window, ev->parent); + 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; @@ -218,20 +220,20 @@ static inline void ev_create_notify(session_t *ps, xcb_create_notify_event_t *ev /// Handle configure event of a regular window static void configure_win(session_t *ps, xcb_configure_notify_event_t *ce) { - auto w = find_win(ps, ce->window); + auto w = wm_find(ps->wm, ce->window); if (!w) { return; } if (!w->managed) { - restack_above(ps, w, ce->above_sibling); + wm_stack_move_above(ps->wm, w, ce->above_sibling); return; } auto mw = (struct managed_win *)w; - restack_above(ps, w, ce->above_sibling); + wm_stack_move_above(ps->wm, w, ce->above_sibling); // We check against pending_g here, because there might have been multiple // configure notifies in this cycle, or the window could receive multiple updates @@ -285,13 +287,13 @@ static inline void ev_configure_notify(session_t *ps, xcb_configure_notify_event } static inline void ev_destroy_notify(session_t *ps, xcb_destroy_notify_event_t *ev) { - auto subwin = find_subwin(ps->subwins, ev->window); + auto subwin = wm_subwin_find(ps->wm, ev->window); if (subwin) { - remove_subwin(&ps->subwins, subwin); + wm_subwin_remove(ps->wm, subwin); } - auto w = find_win(ps, ev->window); - auto mw = find_toplevel(ps, ev->window); + 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); @@ -335,7 +337,7 @@ static inline void ev_map_notify(session_t *ps, xcb_map_notify_event_t *ev) { return; } - auto w = find_managed_win(ps, ev->window); + auto w = wm_find_managed(ps->wm, ev->window); if (!w) { return; } @@ -355,7 +357,7 @@ static inline void ev_map_notify(session_t *ps, xcb_map_notify_event_t *ev) { } static inline void ev_unmap_notify(session_t *ps, xcb_unmap_notify_event_t *ev) { - auto w = find_managed_win(ps, ev->window); + auto w = wm_find_managed(ps->wm, ev->window); if (w) { unmap_win_start(ps, w); } @@ -365,10 +367,10 @@ 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", ev->window, ev->parent, ev->override_redirect); - auto old_toplevel = find_toplevel(ps, ev->window); - auto old_w = find_win(ps, ev->window); - auto old_subwin = find_subwin(ps->subwins, ev->window); - auto new_toplevel = find_managed_win(ps, 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; @@ -402,7 +404,9 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t // to the top of the window stack if (old_w) { // root -> root reparent, we just need to move it to the top - restack_top(ps, old_w); + 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); } return; } @@ -441,11 +445,11 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t // 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) { - remove_subwin_and_unsubscribe(&ps->subwins, &ps->c, new_subwin); + wm_subwin_remove_and_unsubscribe(ps->wm, &ps->c, new_subwin); new_subwin = NULL; } else if (new_subwin == NULL && new_toplevel != NULL) { new_subwin = - add_subwin_and_subscribe(&ps->subwins, &ps->c, ev->window, ev->parent); + wm_subwin_add_and_subscribe(ps->wm, &ps->c, ev->window, ev->parent); } if (new_subwin) { new_subwin->toplevel = new_toplevel->base.id; @@ -460,21 +464,24 @@ static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t // New parent is root, add a toplevel; assert(old_w == NULL); assert(new_toplevel == NULL); - add_win_top(ps, ev->window); + wm_stack_add_top(ps->wm, ev->window); + ps->pending_updates = true; } } static inline void ev_circulate_notify(session_t *ps, xcb_circulate_notify_event_t *ev) { - auto w = find_win(ps, ev->window); + auto w = wm_find(ps->wm, ev->window); if (!w) { return; } + log_debug("Moving window %#010x (%s) to the %s", w->id, + win_get_name_if_managed(w), ev->place == PlaceOnTop ? "top" : "bottom"); if (ev->place == PlaceOnTop) { - restack_top(ps, w); + wm_stack_move_to_top(ps->wm, w); } else { - restack_bottom(ps, w); + wm_stack_move_to_bottom(ps->wm, w); } } @@ -514,7 +521,7 @@ 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 = find_subwin(ps->subwins, ev->window); + 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; @@ -531,7 +538,7 @@ static inline void ev_subwin_wm_state_changed(session_t *ps, xcb_property_notify return; } - auto toplevel = find_win(ps, subwin->toplevel); + auto toplevel = wm_find(ps->wm, subwin->toplevel); BUG_ON(toplevel == NULL); if (!toplevel->managed) { return; @@ -589,7 +596,7 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t } ps->pending_updates = true; - auto w = find_toplevel(ps, ev->window); + auto w = wm_find_by_client(ps->wm, ev->window); if (ev->atom == ps->atoms->aWM_STATE) { ev_subwin_wm_state_changed(ps, ev); } @@ -607,7 +614,7 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t // We already handle if this is set on the client window, check // if this is set on the frame window as well. // TODO(yshui) do we really need this? - auto toplevel = find_managed_win(ps, ev->window); + auto toplevel = wm_find_managed(ps->wm, ev->window); if (toplevel) { win_set_property_stale(toplevel, ev->atom); } @@ -616,9 +623,9 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t // Check for other atoms we are tracking if (c2_state_is_property_tracked(ps->c2_state, ev->atom)) { bool change_is_on_client = false; - w = find_managed_win(ps, ev->window); + w = wm_find_managed(ps->wm, ev->window); if (!w) { - w = find_toplevel(ps, ev->window); + w = wm_find_by_client(ps->wm, ev->window); change_is_on_client = true; } if (w) { @@ -700,7 +707,7 @@ static inline void ev_damage_notify(session_t *ps, xcb_damage_notify_event_t *de return; } */ - auto w = find_managed_win(ps, de->drawable); + auto w = wm_find_managed(ps->wm, de->drawable); if (!w) { return; @@ -710,7 +717,7 @@ static inline void ev_damage_notify(session_t *ps, xcb_damage_notify_event_t *de } static inline void ev_shape_notify(session_t *ps, xcb_shape_notify_event_t *ev) { - auto w = find_managed_win(ps, ev->affected_window); + auto w = wm_find_managed(ps->wm, ev->affected_window); if (!w || w->a.map_state == XCB_MAP_STATE_UNMAPPED) { return; } diff --git a/src/meson.build b/src/meson.build index 31ec0b49..9efbf92b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -10,7 +10,7 @@ base_deps = [ srcs = [ files('picom.c', 'win.c', 'c2.c', 'x.c', 'config.c', 'vsync.c', 'utils.c', 'diagnostic.c', 'string_utils.c', 'render.c', 'kernel.c', 'log.c', 'options.c', 'event.c', 'cache.c', 'atom.c', 'file_watch.c', 'statistics.c', - 'vblank.c', 'transition.c') ] + 'vblank.c', 'transition.c', 'wm.c') ] picom_inc = include_directories('.') cflags = [] diff --git a/src/opengl.c b/src/opengl.c index 26679b37..dd4cd4c9 100644 --- a/src/opengl.c +++ b/src/opengl.c @@ -28,6 +28,7 @@ #include "uthash_extra.h" #include "utils.h" #include "win.h" +#include "wm.h" #include "opengl.h" @@ -241,7 +242,7 @@ void glx_destroy(session_t *ps) { } // Free all GLX resources of windows - win_stack_foreach_managed(w, &ps->window_stack) { + win_stack_foreach_managed(w, wm_stack_end(ps->wm)) { free_win_res_glx(ps, w); } diff --git a/src/picom.c b/src/picom.c index d5b7be24..1b6edacc 100644 --- a/src/picom.c +++ b/src/picom.c @@ -50,6 +50,7 @@ #include "picom.h" #include "transition.h" #include "win_defs.h" +#include "wm.h" #ifdef CONFIG_OPENGL #include "opengl.h" #endif @@ -509,7 +510,7 @@ void update_ewmh_active_win(session_t *ps) { // Search for the window xcb_window_t wid = wid_get_prop_window(&ps->c, ps->c.screen_info->root, ps->atoms->a_NET_ACTIVE_WINDOW); - auto w = find_toplevel(ps, wid); + auto w = wm_find_by_client(ps->wm, wid); // Mark the window focused. No need to unfocus the previous one. if (w) { @@ -571,7 +572,7 @@ static void recheck_focus(session_t *ps) { wid = parent; } - auto w = find_managed_win(ps, wid); + auto w = wm_find_managed(ps->wm, wid); // And we set the focus state here if (w) { @@ -602,7 +603,7 @@ static void rebuild_shadow_exclude_reg(session_t *ps) { /// Free up all the images and deinit the backend static void destroy_backend(session_t *ps) { - win_stack_foreach_managed_safe(w, &ps->window_stack) { + win_stack_foreach_managed_safe(w, wm_stack_end(ps->wm)) { // Wrapping up fading in progress win_skip_fading(w); @@ -691,6 +692,20 @@ static bool initialize_blur(session_t *ps) { return ps->backend_blur_context != NULL; } +static int mark_pixmap_stale(struct win *w, void *data) { + struct session *ps = data; + if (!w->managed) { + return 0; + } + auto mw = win_as_managed(w); + assert(mw->state != WSTATE_DESTROYED); + // We need to reacquire image + log_debug("Marking window %#010x (%s) for update after redirection", w->id, mw->name); + win_set_flags(mw, WIN_FLAGS_PIXMAP_STALE); + ps->pending_updates = true; + return 0; +} + /// Init the backend and bind all the window pixmap to backend images static bool initialize_backend(session_t *ps) { if (!ps->o.legacy_backends) { @@ -739,22 +754,9 @@ static bool initialize_backend(session_t *ps) { } } - // window_stack shouldn't include window that's - // not in the hash table at this point. Since - // there cannot be any fading windows. - HASH_ITER2(ps->windows, _w) { - if (!_w->managed) { - continue; - } - auto w = (struct managed_win *)_w; - assert(w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPED); - // We need to reacquire image - log_debug("Marking window %#010x (%s) for update after " - "redirection", - w->base.id, w->name); - win_set_flags(w, WIN_FLAGS_PIXMAP_STALE); - ps->pending_updates = true; - } + // wm_stack shouldn't include window that's not iterated by wm_foreach at + // this moment. Since there cannot be any fading windows. + wm_foreach(ps->wm, mark_pixmap_stale, ps); } // The old backends binds pixmap lazily, nothing to do here @@ -808,7 +810,7 @@ static void configure_root(session_t *ps) { rebuild_shadow_exclude_reg(ps); // Invalidate reg_ignore from the top - auto top_w = win_stack_find_next_managed(ps, &ps->window_stack); + 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; @@ -817,7 +819,7 @@ static void configure_root(session_t *ps) { // Whether a window is fullscreen depends on the new screen // size. So we need to refresh the fullscreen state of all // windows. - win_stack_foreach_managed(w, &ps->window_stack) { + win_stack_foreach_managed(w, wm_stack_end(ps->wm)) { win_update_is_fullscreen(ps, w); } @@ -902,7 +904,7 @@ static bool paint_preprocess(session_t *ps, bool *fade_running, bool *animation, // First, let's process fading, and animated shaders // TODO(yshui) check if a window is fully obscured, and if we don't need to // process fading or animation for it. - win_stack_foreach_managed_safe(w, &ps->window_stack) { + win_stack_foreach_managed_safe(w, wm_stack_end(ps->wm)) { const winmode_t mode_old = w->mode; const bool was_painted = w->to_paint; @@ -959,7 +961,7 @@ static bool paint_preprocess(session_t *ps, bool *fade_running, bool *animation, // Track whether it's the highest window to paint bool is_highest = true; bool reg_ignore_valid = true; - win_stack_foreach_managed(w, &ps->window_stack) { + win_stack_foreach_managed(w, wm_stack_end(ps->wm)) { __label__ skip_window; bool to_paint = true; // w->to_paint remembers whether this window is painted last time @@ -1640,7 +1642,7 @@ static void handle_queued_x_events(EV_P attr_unused, ev_prepare *w, int revents } static void handle_new_windows(session_t *ps) { - list_foreach_safe(struct win, w, &ps->window_stack, stack_neighbour) { + list_foreach_safe(struct win, w, wm_stack_end(ps->wm), stack_neighbour) { if (w->is_new) { auto new_w = maybe_allocate_managed_win(ps, w); if (new_w == w) { @@ -1648,11 +1650,7 @@ static void handle_new_windows(session_t *ps) { } assert(new_w->managed); - list_replace(&w->stack_neighbour, &new_w->stack_neighbour); - struct win *replaced = NULL; - HASH_REPLACE_INT(ps->windows, id, new_w, replaced); - assert(replaced == w); - free(w); + wm_stack_replace(ps->wm, w, new_w); auto mw = (struct managed_win *)new_w; if (mw->a.map_state == XCB_MAP_STATE_VIEWABLE) { @@ -1672,13 +1670,13 @@ static void handle_new_windows(session_t *ps) { } static void refresh_windows(session_t *ps) { - win_stack_foreach_managed(w, &ps->window_stack) { + win_stack_foreach_managed(w, wm_stack_end(ps->wm)) { win_process_update_flags(ps, w); } } static void refresh_images(session_t *ps) { - win_stack_foreach_managed(w, &ps->window_stack) { + win_stack_foreach_managed(w, wm_stack_end(ps->wm)) { win_process_image_flags(ps, w); } } @@ -1775,7 +1773,7 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { // // Using foreach_safe here since skipping fading can cause window to be // freed if it's destroyed. - win_stack_foreach_managed_safe(w, &ps->window_stack) { + win_stack_foreach_managed_safe(w, wm_stack_end(ps->wm)) { win_skip_fading(w); if (w->state == WSTATE_DESTROYED) { destroy_win_finish(ps, &w->base); @@ -1785,7 +1783,7 @@ static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) { if (ps->o.benchmark) { if (ps->o.benchmark_wid) { - auto w = find_managed_win(ps, ps->o.benchmark_wid); + auto w = wm_find_managed(ps->wm, ps->o.benchmark_wid); if (!w) { log_fatal("Couldn't find specified benchmark window."); exit(1); @@ -2022,10 +2020,6 @@ static session_t *session_init(int argc, char **argv, Display *dpy, .size_expose = 0, .n_expose = 0, - .windows = NULL, - .active_win = NULL, - .active_leader = XCB_NONE, - .black_picture = XCB_NONE, .cshadow_picture = XCB_NONE, .white_picture = XCB_NONE, @@ -2072,7 +2066,6 @@ static session_t *session_init(int argc, char **argv, Display *dpy, // Allocate a session and copy default values into it session_t *ps = cmalloc(session_t); *ps = s_def; - list_init_head(&ps->window_stack); ps->loop = EV_DEFAULT; pixman_region32_init(&ps->screen_reg); @@ -2525,6 +2518,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy, ps->server_grabbed = false; + ps->wm = wm_new(); if (query_tree_reply) { xcb_window_t *children; int nchildren; @@ -2533,13 +2527,13 @@ static session_t *session_init(int argc, char **argv, Display *dpy, nchildren = xcb_query_tree_children_length(query_tree_reply); for (int i = 0; i < nchildren; i++) { - add_win_above(ps, children[i], i ? children[i - 1] : XCB_NONE); + wm_stack_add_above(ps->wm, children[i], i ? children[i - 1] : XCB_NONE); } free(query_tree_reply); } log_debug("Initial stack:"); - list_foreach(struct win, w, &ps->window_stack, stack_neighbour) { + list_foreach(struct win, w, wm_stack_end(ps->wm), stack_neighbour) { log_debug("%#010x", w->id); } @@ -2586,27 +2580,8 @@ static void session_destroy(session_t *ps) { } #endif - // Free window linked list - - list_foreach_safe(struct win, w, &ps->window_stack, stack_neighbour) { - if (!w->destroyed) { - win_ev_stop(ps, w); - HASH_DEL(ps->windows, w); - } - - if (w->managed) { - auto mw = (struct managed_win *)w; - free_win_res(ps, mw); - } - free(w); - } - list_init_head(&ps->window_stack); - - { - struct subwin *subwin, *next_subwin; - HASH_ITER(hh, ps->subwins, subwin, next_subwin) { - remove_subwin(&ps->subwins, subwin); - } + win_stack_foreach_managed_safe(w, wm_stack_end(ps->wm)) { + free_win_res(ps, w); } // Free blacklists @@ -2714,6 +2689,7 @@ static void session_destroy(session_t *ps) { ev_signal_stop(ps->loop, &ps->usr1_signal); ev_signal_stop(ps->loop, &ps->int_signal); + wm_free(ps->wm, &ps->c); free_x_connection(&ps->c); } diff --git a/src/win.c b/src/win.c index 5fb2ced0..2dc40706 100644 --- a/src/win.c +++ b/src/win.c @@ -33,6 +33,7 @@ #include "uthash_extra.h" #include "utils.h" #include "win_defs.h" +#include "wm.h" #include "x.h" #ifdef CONFIG_OPENGL @@ -96,15 +97,6 @@ static void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t c return ret; \ } -/** - * Clear leader cache of all windows. - */ -static inline void clear_cache_win_leaders(session_t *ps) { - win_stack_foreach_managed(w, &ps->window_stack) { - w->cache_leader = XCB_NONE; - } -} - static xcb_window_t win_get_leader_raw(session_t *ps, struct managed_win *w, int recursions); /** @@ -138,13 +130,31 @@ static void win_update_focused(session_t *ps, struct managed_win *w) { // If window grouping detection is enabled, mark the window active if // its group is - if (ps->o.track_leader && ps->active_leader && - win_get_leader(ps, w) == ps->active_leader) { + auto active_leader = wm_active_leader(ps->wm); + if (ps->o.track_leader && active_leader && + win_get_leader(ps, w) == active_leader) { w->focused = true; } } } +struct group_callback_data { + struct session *ps; + xcb_window_t leader; +}; + +static inline int group_on_factor_change_callback(struct win *w, void *data_) { + struct group_callback_data *data = data_; + if (!w->managed) { + return 0; + } + auto mw = (struct managed_win *)w; + if (data->leader == win_get_leader(data->ps, mw)) { + win_on_factor_change(data->ps, mw); + } + return 0; +} + /** * Run win_on_factor_change() on all windows with the same leader window. * @@ -155,24 +165,23 @@ static inline void group_on_factor_change(session_t *ps, xcb_window_t leader) { return; } - HASH_ITER2(ps->windows, w) { - assert(!w->destroyed); - if (!w->managed) { - continue; - } - auto mw = (struct managed_win *)w; - if (win_get_leader(ps, mw) == leader) { - win_on_factor_change(ps, mw); - } - } + struct group_callback_data data = { + .ps = ps, + .leader = leader, + }; + wm_foreach(ps->wm, group_on_factor_change_callback, &data); } -static inline const char *win_get_name_if_managed(const struct win *w) { +static inline int group_is_focused_callback(struct win *w, void *data_) { + struct group_callback_data *data = data_; if (!w->managed) { - return "(unmanaged)"; + return 0; } auto mw = (struct managed_win *)w; - return mw->name; + if (data->leader == win_get_leader(data->ps, mw) && win_is_focused_raw(mw)) { + return 1; + } + return 0; } /** @@ -186,18 +195,11 @@ static inline bool group_is_focused(session_t *ps, xcb_window_t leader) { return false; } - HASH_ITER2(ps->windows, w) { - assert(!w->destroyed); - if (!w->managed) { - continue; - } - auto mw = (struct managed_win *)w; - if (win_get_leader(ps, mw) == leader && win_is_focused_raw(mw)) { - return true; - } - } - - return false; + struct group_callback_data data = { + .ps = ps, + .leader = leader, + }; + return wm_foreach(ps->wm, group_is_focused_callback, &data); } /** @@ -210,13 +212,15 @@ static inline void win_set_leader(session_t *ps, struct managed_win *w, xcb_wind // Forcefully do this to deal with the case when a child window // gets mapped before parent, or when the window is a waypoint - clear_cache_win_leaders(ps); + win_stack_foreach_managed(i, wm_stack_end(ps->wm)) { + i->cache_leader = XCB_NONE; + } // Update the old and new window group and active_leader if the // window could affect their state. xcb_window_t cache_leader = win_get_leader(ps, w); if (win_is_focused_raw(w) && cache_leader_old != cache_leader) { - ps->active_leader = cache_leader; + wm_set_active_leader(ps->wm, cache_leader); group_on_factor_change(ps, cache_leader_old); group_on_factor_change(ps, cache_leader); @@ -510,7 +514,7 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) { // window information if (win_check_flags_all(w, WIN_FLAGS_CLIENT_STALE)) { log_debug("Rechecking client window for %#010x (%s)", w->base.id, w->name); - auto client_win = win_get_client_window(&ps->c, ps->subwins, ps->atoms, w); + auto client_win = win_get_client_window(&ps->c, ps->wm, ps->atoms, w); if (w->client_win && w->client_win != client_win) { win_unmark_client(w); } @@ -1491,8 +1495,8 @@ void win_unmark_client(struct managed_win *w) { /** * Look for the client window of a particular window. */ -static xcb_window_t find_client_win(struct x_connection *c, struct subwin *subwins, - struct atom *atoms, xcb_window_t w) { +static xcb_window_t +find_client_win(struct x_connection *c, struct wm *wm, struct atom *atoms, xcb_window_t w) { xcb_query_tree_reply_t *reply = xcb_query_tree_reply(c->c, xcb_query_tree(c->c, w), NULL); if (!reply) { @@ -1504,9 +1508,9 @@ static xcb_window_t find_client_win(struct x_connection *c, struct subwin *subwi xcb_window_t ret = XCB_NONE; for (int i = 0; i < nchildren; ++i) { - auto subwin = find_subwin(subwins, children[i]); + auto subwin = wm ? wm_subwin_find(wm, children[i]) : NULL; bool has_wm_state; - assert(subwin != NULL || subwins == NULL); + assert(subwin != NULL || wm == NULL); if (!subwin || subwin->has_wm_state == TRI_UNKNOWN) { has_wm_state = wid_has_prop(c->c, children[i], atoms->aWM_STATE); if (subwin) { @@ -1531,11 +1535,11 @@ static xcb_window_t find_client_win(struct x_connection *c, struct subwin *subwi * @param ps current session * @param w struct _win of the parent window */ -xcb_window_t win_get_client_window(struct x_connection *c, struct subwin *subwins, +xcb_window_t win_get_client_window(struct x_connection *c, struct wm *wm, struct atom *atoms, const struct managed_win *w) { // Always recursively look for a window with WM_STATE, as Fluxbox // sets override-redirect flags on all frame windows. - xcb_window_t cw = find_client_win(c, subwins, atoms, w->base.id); + xcb_window_t cw = find_client_win(c, wm, atoms, w->base.id); if (cw) { log_debug("(%#010x): client %#010x", w->base.id, cw); } else { @@ -1578,88 +1582,6 @@ void free_win_res(session_t *ps, struct managed_win *w) { c2_window_state_destroy(ps->c2_state, &w->c2_state); } -struct subwin *add_subwin_and_subscribe(struct subwin **subwins, struct x_connection *c, - xcb_window_t id, xcb_window_t parent) { - struct subwin *subwin = NULL; - HASH_FIND_INT(*subwins, &id, subwin); - BUG_ON(subwin != NULL); - - subwin = ccalloc(1, struct subwin); - subwin->id = id; - subwin->toplevel = parent; - HASH_ADD_INT(*subwins, id, subwin); - - log_debug("Allocated subwin %p for window %#010x, total: %d", subwin, id, - HASH_COUNT(*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 *find_subwin(struct subwin *subwins, xcb_window_t id) { - struct subwin *subwin = NULL; - HASH_FIND_INT(subwins, &id, subwin); - return subwin; -} - -void remove_subwin(struct subwin **subwins, struct subwin *subwin) { - log_debug("Freeing subwin %p for window %#010x", subwin, subwin->id); - HASH_DEL(*subwins, subwin); - free(subwin); -} - -void remove_subwin_and_unsubscribe(struct subwin **subwins, 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}); - remove_subwin(subwins, subwin); -} - -/// Insert a new window after list_node `prev` -/// New window will be in unmapped state -static struct win *add_win(session_t *ps, xcb_window_t id, struct list_node *prev) { - log_debug("Adding window %#010x", id); - struct win *old_w = NULL; - HASH_FIND_INT(ps->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; - - HASH_ADD_INT(ps->windows, id, new_w); - ps->pending_updates = true; - return new_w; -} - -/// Insert a new win entry at the top of the stack -struct win *add_win_top(session_t *ps, xcb_window_t id) { - return add_win(ps, id, &ps->window_stack); -} - -/// 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 *add_win_above(session_t *ps, xcb_window_t id, xcb_window_t below) { - struct win *w = NULL; - HASH_FIND_INT(ps->windows, &below, w); - if (!w) { - if (!list_is_empty(&ps->window_stack)) { - // `below` window is not found even if the window stack is - // not empty - return NULL; - } - return add_win_top(ps, id); - } - // we found something from the hash table, so if the stack is - // empty, we are in an inconsistent state. - assert(!list_is_empty(&ps->window_stack)); - return add_win(ps, id, w->stack_neighbour.prev); -} - /// Query the Xorg for information about window `win`, and return that /// information in a new managed_win object. However, if the window does /// not need to be managed, the original `win` object is returned. @@ -1761,7 +1683,7 @@ struct win *attr_ret_nonnull maybe_allocate_managed_win(session_t *ps, struct wi return w; } - auto duplicated_win = find_managed_win(ps, w->id); + auto duplicated_win = wm_find_managed(ps->wm, w->id); if (duplicated_win) { log_debug("Window %#010x (recorded name: %s) added multiple " "times", @@ -1854,8 +1776,7 @@ struct win *attr_ret_nonnull maybe_allocate_managed_win(session_t *ps, struct wi if (tree_reply) { auto children = xcb_query_tree_children(tree_reply); for (int i = 0; i < xcb_query_tree_children_length(tree_reply); i++) { - add_subwin_and_subscribe(&ps->subwins, &ps->c, children[i], - new->base.id); + wm_subwin_add_and_subscribe(ps->wm, &ps->c, children[i], new->base.id); } free(tree_reply); } @@ -1922,7 +1843,7 @@ static xcb_window_t win_get_leader_raw(session_t *ps, struct managed_win *w, int // If the leader of this window isn't itself, look for its // ancestors if (w->cache_leader && w->cache_leader != w->client_win) { - auto wp = find_toplevel(ps, w->cache_leader); + auto wp = wm_find_by_client(ps->wm, w->cache_leader); if (wp) { // Dead loop? if (recursions > WIN_GET_LEADER_MAX_RECURSION) { @@ -1986,18 +1907,19 @@ static void win_on_focus_change(session_t *ps, struct managed_win *w) { xcb_window_t leader = win_get_leader(ps, w); // If the window gets focused, replace the old active_leader - if (win_is_focused_raw(w) && leader != ps->active_leader) { - xcb_window_t active_leader_old = ps->active_leader; + auto active_leader = wm_active_leader(ps->wm); + if (win_is_focused_raw(w) && leader != active_leader) { + xcb_window_t active_leader_old = active_leader; - ps->active_leader = leader; + wm_set_active_leader(ps->wm, leader); group_on_factor_change(ps, active_leader_old); group_on_factor_change(ps, leader); } // If the group get unfocused, remove it from active_leader - else if (!win_is_focused_raw(w) && leader && - leader == ps->active_leader && !group_is_focused(ps, leader)) { - ps->active_leader = XCB_NONE; + else if (!win_is_focused_raw(w) && leader && leader == active_leader && + !group_is_focused(ps, leader)) { + wm_set_active_leader(ps->wm, XCB_NONE); group_on_factor_change(ps, leader); } } @@ -2024,13 +1946,13 @@ void win_set_focused(session_t *ps, struct managed_win *w) { return; } + auto old_active_win = wm_active_win(ps->wm); if (w->is_ewmh_focused) { - assert(ps->active_win == w); + assert(old_active_win == w); return; } - auto old_active_win = ps->active_win; - ps->active_win = w; + wm_set_active_win(ps->wm, w); w->is_ewmh_focused = true; if (old_active_win) { @@ -2196,7 +2118,7 @@ void win_update_frame_extents(struct x_connection *c, struct atom *atoms, } bool win_is_region_ignore_valid(session_t *ps, const struct managed_win *w) { - win_stack_foreach_managed(i, &ps->window_stack) { + win_stack_foreach_managed(i, wm_stack_end(ps->wm)) { if (i == w) { break; } @@ -2207,33 +2129,12 @@ bool win_is_region_ignore_valid(session_t *ps, const struct managed_win *w) { return true; } -/** - * Stop listening for events on a particular window. - */ -void win_ev_stop(session_t *ps, const struct win *w) { - xcb_change_window_attributes(ps->c.c, w->id, XCB_CW_EVENT_MASK, (const uint32_t[]){0}); - - if (!w->managed) { - return; - } - - auto mw = (struct managed_win *)w; - if (mw->client_win) { - xcb_change_window_attributes(ps->c.c, mw->client_win, XCB_CW_EVENT_MASK, - (const uint32_t[]){0}); - } - - if (ps->shape_exists) { - xcb_shape_select_input(ps->c.c, w->id, 0); - } -} - /// Finish the destruction of a window (e.g. after fading has finished). /// Frees `w` void destroy_win_finish(session_t *ps, struct win *w) { log_verbose("Trying to finish destroying (%#010x)", w->id); - auto next_w = win_stack_find_next_managed(ps, &w->stack_neighbour); + auto next_w = wm_stack_next_managed(ps->wm, &w->stack_neighbour); list_remove(&w->stack_neighbour); if (w->managed) { @@ -2256,7 +2157,7 @@ void destroy_win_finish(session_t *ps, struct win *w) { next_w->reg_ignore_valid = false; } - if (mw == ps->active_win) { + if (mw == wm_active_win(ps->wm)) { // Usually, the window cannot be the focused at // destruction. FocusOut should be generated before the // window is destroyed. We do this check just to be @@ -2264,7 +2165,7 @@ void destroy_win_finish(session_t *ps, struct win *w) { log_debug("window %#010x (%s) is destroyed while being " "focused", w->id, mw->name); - ps->active_win = NULL; + wm_set_active_win(ps->wm, NULL); } free_win_res(ps, mw); @@ -2272,7 +2173,7 @@ void destroy_win_finish(session_t *ps, struct win *w) { // Drop w from all prev_trans to avoid accessing freed memory in // repair_win() // TODO(yshui) there can only be one prev_trans pointing to w - win_stack_foreach_managed(w2, &ps->window_stack) { + win_stack_foreach_managed(w2, wm_stack_end(ps->wm)) { if (mw == w2->prev_trans) { w2->prev_trans = NULL; } @@ -2282,106 +2183,14 @@ void destroy_win_finish(session_t *ps, struct win *w) { free(w); } -/// Move window `w` so it's before `next` in the list -static inline void restack_win(session_t *ps, 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 = win_stack_find_next_managed(ps, &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); - - // add damage for this window - if (mw) { - add_damage_from_win(ps, mw); - } - -#ifdef DEBUG_RESTACK - log_trace("Window stack modified. Current stack:"); - for (auto c = ps->list; 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 -} - -/// Move window `w` so it's right above `below` -void restack_above(session_t *ps, struct win *w, xcb_window_t below) { - xcb_window_t old_below; - - if (!list_node_is_last(&ps->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 = &ps->window_stack; - } else { - struct win *tmp_w = NULL; - HASH_FIND_INT(ps->windows, &below, tmp_w); - - if (!tmp_w) { - log_error("Failed to found new below window %#010x.", below); - return; - } - - new_next = &tmp_w->stack_neighbour; - } - restack_win(ps, w, new_next); - } -} - -void restack_bottom(session_t *ps, struct win *w) { - restack_above(ps, w, 0); -} - -void restack_top(session_t *ps, struct win *w) { - log_debug("Restack %#010x (%s) to top", w->id, win_get_name_if_managed(w)); - if (&w->stack_neighbour == ps->window_stack.next) { - // already at top - return; - } - restack_win(ps, w, ps->window_stack.next); -} - /// Start destroying a window. Windows cannot always be destroyed immediately /// because of fading and such. void destroy_win_start(session_t *ps, struct win *w) { assert(w); - { - // A toplevel window is destroyed, all of its children lose their - // subwin status. - struct subwin *subwin, *next_subwin; - HASH_ITER(hh, ps->subwins, subwin, next_subwin) { - if (subwin->toplevel == w->id) { - remove_subwin_and_unsubscribe(&ps->subwins, &ps->c, subwin); - } - } - } + // A toplevel window is destroyed, all of its children lose their + // subwin status. + wm_subwin_remove_and_unsubscribe_for_toplevel(ps->wm, &ps->c, w->id); auto mw = (struct managed_win *)w; log_debug("Destroying %#010x \"%s\", managed = %d", w->id, @@ -2393,7 +2202,7 @@ void destroy_win_start(session_t *ps, struct win *w) { // stack if it's managed and mapped, since we might still need to render // it (e.g. fading out). Window will be removed from the stack when it // finishes destroying. - HASH_DEL(ps->windows, w); + wm_remove(ps->wm, w); if (w->managed) { if (mw->state != WSTATE_UNMAPPED) { @@ -2599,60 +2408,6 @@ void win_update_opacity_target(session_t *ps, struct managed_win *w) { } } -/** - * Find a managed window from window id in window linked list of the session. - */ -struct win *find_win(session_t *ps, xcb_window_t id) { - if (!id) { - return NULL; - } - - struct win *w = NULL; - HASH_FIND_INT(ps->windows, &id, w); - assert(w == NULL || !w->destroyed); - return w; -} - -/** - * Find a managed window from window id in window linked list of the session. - */ -struct managed_win *find_managed_win(session_t *ps, xcb_window_t id) { - struct win *w = find_win(ps, id); - if (!w || !w->managed) { - return NULL; - } - - auto mw = (struct managed_win *)w; - assert(mw->state != WSTATE_DESTROYED); - return mw; -} - -/** - * Find out the WM frame of a client window using existing data. - * - * @param id window ID - * @return struct win object of the found window, NULL if not found - */ -struct managed_win *find_toplevel(session_t *ps, xcb_window_t id) { - if (!id) { - return NULL; - } - - HASH_ITER2(ps->windows, w) { - assert(!w->destroyed); - if (!w->managed) { - continue; - } - - auto mw = (struct managed_win *)w; - if (mw->client_win == id) { - return mw; - } - } - - return NULL; -} - /// Set flags on a window. Some sanity checks are performed void win_set_flags(struct managed_win *w, uint64_t flags) { log_debug("Set flags %" PRIu64 " to window %#010x (%s)", flags, w->base.id, w->name); @@ -2774,16 +2529,3 @@ bool win_is_bypassing_compositor(const session_t *ps, const struct managed_win * bool win_is_focused_raw(const struct managed_win *w) { return w->a.map_state == XCB_MAP_STATE_VIEWABLE && w->is_ewmh_focused; } - -// Find the managed window immediately below `i` in the window stack -struct managed_win * -win_stack_find_next_managed(const session_t *ps, const struct list_node *w) { - while (!list_node_is_last(&ps->window_stack, w)) { - auto next = list_entry(w->next, struct win, stack_neighbour); - if (next->managed) { - return (struct managed_win *)next; - } - w = &next->stack_neighbour; - } - return NULL; -} diff --git a/src/win.h b/src/win.h index 754f0a86..6837244a 100644 --- a/src/win.h +++ b/src/win.h @@ -49,7 +49,7 @@ typedef struct { int height; } glx_blur_cache_t; #endif - +struct wm; /// An entry in the window stack. May or may not correspond to a window we know about. struct window_stack_entry { struct list_node stack_neighbour; @@ -89,14 +89,6 @@ struct win { bool managed : 1; }; -/// Direct children of a toplevel. -struct subwin { - UT_hash_handle hh; - xcb_window_t id; - xcb_window_t toplevel; - enum tristate has_wm_state; -}; - struct win_geometry { int16_t x; int16_t y; @@ -381,46 +373,18 @@ void win_get_region_noframe_local_without_corners(const struct managed_win *w, r void win_get_region_frame_local(const struct managed_win *w, region_t *res); /// Get the region for the frame of the window, by value region_t win_get_region_frame_local_by_val(const struct managed_win *w); -/// 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 *add_win_above(session_t *ps, xcb_window_t id, xcb_window_t below); -/// Insert a new win entry at the top of the stack -struct win *add_win_top(session_t *ps, xcb_window_t id); -/// Add a new subwin, and subscribe to relevant events. -struct subwin *add_subwin_and_subscribe(struct subwin **subwins, struct x_connection *c, - xcb_window_t id, xcb_window_t parent); -struct subwin *find_subwin(struct subwin *subwins, xcb_window_t id); -void remove_subwin(struct subwin **subwins, struct subwin *subwin); -/// Remove a subwin, and unsubscribe from events. -void remove_subwin_and_unsubscribe(struct subwin **subwins, struct x_connection *c, - struct subwin *subwin); /// Query the Xorg for information about window `win` /// `win` pointer might become invalid after this function returns struct win *attr_ret_nonnull maybe_allocate_managed_win(session_t *ps, struct win *win); -/// Move window `w` to be right above `below` -void restack_above(session_t *ps, struct win *w, xcb_window_t below); -/// Move window `w` to the bottom of the stack -void restack_bottom(session_t *ps, struct win *w); -/// Move window `w` to the top of the stack -void restack_top(session_t *ps, struct win *w); /** * Release a destroyed window that is no longer needed. */ void destroy_win_finish(session_t *ps, struct win *w); -// Stop receiving events (except ConfigureNotify, XXX why?) from a window -void win_ev_stop(session_t *ps, const struct win *w); - /// Skip the current in progress fading of window, /// transition the window straight to its end state void win_skip_fading(struct managed_win *w); -/** - * Find a managed window from window id in window linked list of the session. - */ -struct managed_win *find_managed_win(session_t *ps, xcb_window_t id); -struct win *find_win(session_t *ps, xcb_window_t id); -struct managed_win *find_toplevel(session_t *ps, xcb_window_t id); /** * Check if a window is focused, without using any focus rules or forced focus settings @@ -442,15 +406,19 @@ static inline struct managed_win *win_as_managed(struct win *w) { return (struct managed_win *)w; } +static inline const char *win_get_name_if_managed(const struct win *w) { + if (!w->managed) { + return "(unmanaged)"; + } + auto mw = (struct managed_win *)w; + return mw->name; +} + /// 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); /// Whether a given window is mapped on the X server side bool win_is_mapped_in_x(const struct managed_win *w); - -// Find the managed window immediately below `w` in the window stack -struct managed_win *attr_pure win_stack_find_next_managed(const session_t *ps, - const struct list_node *w); /// Set flags on a window. Some sanity checks are performed void win_set_flags(struct managed_win *w, uint64_t flags); /// Clear flags on a window. Some sanity checks are performed @@ -462,7 +430,7 @@ bool win_check_flags_all(struct managed_win *w, uint64_t flags); /// Mark properties as stale for a window void win_set_properties_stale(struct managed_win *w, const xcb_atom_t *prop, int nprops); -xcb_window_t win_get_client_window(struct x_connection *c, struct subwin *subwins, +xcb_window_t win_get_client_window(struct x_connection *c, struct wm *wm, struct atom *atoms, const struct managed_win *w); bool win_update_wintype(struct x_connection *c, struct atom *atoms, struct managed_win *w); /** diff --git a/src/wm.c b/src/wm.c new file mode 100644 index 00000000..ef5015f5 --- /dev/null +++ b/src/wm.c @@ -0,0 +1,340 @@ +// 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 { + /// 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) { + 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; + + 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); +} + +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); + 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); +} diff --git a/src/wm.h b/src/wm.h new file mode 100644 index 00000000..7d7a023a --- /dev/null +++ b/src/wm.h @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui + +/// Yeah... We have our own window manager inside the compositor. As a compositor, we do +/// need to do a little bit of what a window manager does, to correctly render windows. +/// But our window manager is a lot less sophisticated than a average window manager. We +/// only keep track of a list of top-level windows, and the order they are stacked. +/// But OTOH doing window managing here is also somewhat more challenging. As we are not +/// a window manager, we don't actually know what window is an application window, what +/// is not. We have to rely on the real window manager playing nice and following the +/// ICCCM and EWMH standards. + +#pragma once + +#include +#include + +#include "compiler.h" +#include "utils.h" + +struct wm; +struct managed_win; +struct list_node; +struct x_connection; + +/// Direct children of a toplevel. +struct subwin { + xcb_window_t id; + xcb_window_t toplevel; + enum tristate has_wm_state; + UT_hash_handle hh; +}; + +struct wm *wm_new(void); +void wm_free(struct wm *wm, struct x_connection *c); + +struct managed_win *wm_active_win(struct wm *wm); +void wm_set_active_win(struct wm *wm, struct managed_win *w); +xcb_window_t wm_active_leader(struct wm *wm); +void wm_set_active_leader(struct wm *wm, xcb_window_t leader); + +// Note: `wm` keeps track of 2 lists of windows. One is the window stack, which includes +// all windows that might need to be rendered, which means it would include destroyed +// windows in case they need to be faded out. This list is accessed by `wm_stack_*` series +// of functions. The other is a hash table of windows, which does not include destroyed +// windows. This list is accessed by `wm_find_*`, `wm_foreach`, and `wm_num_windows`. +// Adding a window to the window stack also automatically adds it to the hash table. + +/// Find a window in the hash table from window id. +struct win *wm_find(struct wm *wm, xcb_window_t id); +/// Remove a window from the hash table. +void wm_remove(struct wm *wm, struct win *w); +/// Find a managed window from window id in window linked list of the session. +struct managed_win *wm_find_managed(struct wm *wm, xcb_window_t id); +// Find the WM frame of a client window. `id` is the client window id. +struct managed_win *wm_find_by_client(struct wm *wm, xcb_window_t client); +/// Call `func` on each toplevel window. `func` should return 0 if the iteration +/// should continue. If it returns anything else, the iteration will stop and the +/// return value will be returned from `wm_foreach`. If the iteration finishes +/// naturally, 0 will be returned. +int wm_foreach(struct wm *wm, int (*func)(struct win *, void *), void *data); +/// Returns the number of windows in the hash table. +unsigned attr_const wm_num_windows(const struct wm *wm); + +/// Returns the cursor past the last window in the stack (the `end`). The window stack is +/// a cyclic linked list, so the next element after `end` is the first element. The `end` +/// itself does not point to a valid window. The address of `end` is stable as long as +/// the `struct wm` itself is not freed. +struct list_node *attr_const wm_stack_end(struct wm *wm); +/// Insert a new win entry at the top of the stack +struct win *wm_stack_add_top(struct wm *wm, xcb_window_t id); +/// Insert a new window above window with id `below`, if there is no window, add +/// to top New window will be in unmapped state +struct win *wm_stack_add_above(struct wm *wm, xcb_window_t id, xcb_window_t below); +// Find the managed window immediately below `i` in the window stack +struct managed_win *attr_pure wm_stack_next_managed(const struct wm *wm, + const struct list_node *cursor); +/// Move window `w` so it's right above `below`, if `below` is 0, `w` is moved +/// to the bottom of the stack +void wm_stack_move_above(struct wm *wm, struct win *w, xcb_window_t below); +/// Move window `w` to the bottom of the stack. +static inline void wm_stack_move_to_bottom(struct wm *wm, struct win *w) { + wm_stack_move_above(wm, w, 0); +} +/// Move window `w` to the top of the stack. +void wm_stack_move_to_top(struct wm *wm, struct win *w); +/// Replace window `old` with `new_` in the stack, also replace the window in the hash +/// table. `old` will be freed. +void wm_stack_replace(struct wm *wm, struct win *old, struct win *new_); + +struct subwin *wm_subwin_add_and_subscribe(struct wm *wm, struct x_connection *c, + xcb_window_t id, xcb_window_t parent); +struct subwin *wm_subwin_find(struct wm *wm, xcb_window_t id); +void wm_subwin_remove(struct wm *wm, struct subwin *subwin); +void wm_subwin_remove_and_unsubscribe(struct wm *wm, struct x_connection *c, + struct subwin *subwin); +/// Remove all subwins associated with a toplevel window +void wm_subwin_remove_and_unsubscribe_for_toplevel(struct wm *wm, struct x_connection *c, + xcb_window_t toplevel);