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 <yshuiv7@gmail.com>
This commit is contained in:
Yuxuan Shui 2024-03-26 17:54:34 +00:00
parent fa0a89c35a
commit a5ea66a134
No known key found for this signature in database
GPG Key ID: D3A4405BE6CC17F4
10 changed files with 658 additions and 538 deletions

View File

@ -288,22 +288,7 @@ typedef struct session {
/// Index of the next free slot in <code>expose_rects</code>.
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 <code>win</code> of current active window. Used by
/// EWMH <code>_NET_ACTIVE_WINDOW</code> 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.

View File

@ -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, "<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.
*/
@ -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, "<node name='%#010x'/>\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, "</node>");
if (success) {
success = cdbus_append_string(reply, ret);
mstrextend(&introspect, "</node>");
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) {

View File

@ -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;
}

View File

@ -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 = []

View File

@ -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);
}

View File

@ -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);
}

400
src/win.c
View File

@ -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;
}

View File

@ -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);
/**

340
src/wm.c Normal file
View File

@ -0,0 +1,340 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui
#include <uthash.h>
#include <xcb/xproto.h>
#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 <code>win</code> of current active window. Used by
/// EWMH <code>_NET_ACTIVE_WINDOW</code> 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);
}

99
src/wm.h Normal file
View File

@ -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 <uthash.h>
#include <xcb/xproto.h>
#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);