event: delayed handling of reparent notifies

We used to fetch the WM_STATE property in event handler, but we should
only do that inside the critical section. With this commit we keep a
list of all subwins (direct children of toplevels), and keep track of
their WM_STATE properties, only fetching it in critical section when
needed.

Stop changing event masks in win_mark/unmark_client, since that's
entirely managed by the subwins now.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
This commit is contained in:
Yuxuan Shui 2024-03-25 18:05:46 +00:00
parent 54fb0c7ac0
commit 5cd0edf743
No known key found for this signature in database
GPG Key ID: D3A4405BE6CC17F4
7 changed files with 269 additions and 121 deletions

View File

@ -291,6 +291,8 @@ typedef struct session {
// === 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

View File

@ -9,6 +9,7 @@
#include <xcb/damage.h>
#include <xcb/randr.h>
#include <xcb/xcb_event.h>
#include <xcb/xproto.h>
#include "atom.h"
#include "c2.h"
@ -19,6 +20,7 @@
#include "log.h"
#include "picom.h"
#include "region.h"
#include "types.h"
#include "utils.h"
#include "win.h"
#include "win_defs.h"
@ -195,7 +197,19 @@ 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);
return;
}
auto w = find_win(ps, ev->parent);
if (w == NULL) {
// The parent window is not a toplevel window, we don't care about it.
// This can happen if a toplevel is reparented somewhere, and more events
// were generated from it before we can unsubscribe.
return;
}
// A direct child of a toplevel, subscribe to property changes so we can
// know if WM_STATE is set on this window.
add_subwin_and_subscribe(&ps->subwins, &ps->c, ev->window, ev->parent);
}
/// Handle configure event of a regular window
@ -267,6 +281,11 @@ 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);
if (subwin) {
remove_subwin(&ps->subwins, subwin);
}
auto w = find_win(ps, ev->window);
auto mw = find_toplevel(ps, ev->window);
if (mw && mw->client_win == mw->base.id) {
@ -281,7 +300,7 @@ static inline void ev_destroy_notify(session_t *ps, xcb_destroy_notify_event_t *
if (w != NULL) {
destroy_win_start(ps, w);
if (!w->managed || !((struct managed_win *)w)->to_paint) {
if (!w->managed || !win_as_managed(w)->to_paint) {
// If the window wasn't managed, or was already not rendered,
// we don't need to fade it out.
destroy_win_finish(ps, w);
@ -289,7 +308,7 @@ static inline void ev_destroy_notify(session_t *ps, xcb_destroy_notify_event_t *
return;
}
if (mw != NULL) {
win_unmark_client(ps, mw);
win_unmark_client(mw);
win_set_flags(mw, WIN_FLAGS_CLIENT_STALE);
ps->pending_updates = true;
return;
@ -341,80 +360,98 @@ static inline void ev_unmap_notify(session_t *ps, xcb_unmap_notify_event_t *ev)
static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t *ev) {
log_debug("Window %#010x has new parent: %#010x, override_redirect: %d",
ev->window, ev->parent, ev->override_redirect);
auto w_top = find_toplevel(ps, ev->window);
if (w_top) {
win_unmark_client(ps, w_top);
win_set_flags(w_top, WIN_FLAGS_CLIENT_STALE);
auto old_toplevel = find_toplevel(ps, ev->window);
if (old_toplevel) {
win_unmark_client(old_toplevel);
win_set_flags(old_toplevel, WIN_FLAGS_CLIENT_STALE);
ps->pending_updates = true;
}
// If the window was a toplevel window, we need to destroy it. But X will generate
// reparent notify even if the parent didn't actually change (i.e. reparent again
// to current parent). So if it's a root -> root reparent, we don't want to
// destroy then recreate the window.
auto old_w = find_win(ps, ev->window);
auto old_subwin = find_subwin(ps->subwins, ev->window);
auto new_toplevel = find_win(ps, ev->parent);
xcb_window_t old_parent = XCB_NONE;
if (old_subwin) {
old_parent = old_subwin->toplevel;
} else if (old_w) {
old_parent = ps->c.screen_info->root;
}
// A window can't be a toplevel and a subwin at the same time
assert(old_w == NULL || old_subwin == NULL);
log_debug("old toplevel: %p, old subwin: %p, new toplevel: %p, old parent: "
"%#010x, new parent: %#010x, root window: %#010x",
old_w, old_subwin, new_toplevel, old_parent, ev->parent,
ps->c.screen_info->root);
if (old_w == NULL && old_subwin == NULL && new_toplevel == NULL &&
ev->parent != ps->c.screen_info->root) {
// The window is neither a toplevel nor a subwin, and the new parent is
// neither a root nor a toplevel, we don't care about this window.
// This can happen if a window is reparented to somewhere irrelevant, but
// more events from it are generated before we can unsubscribe.
return;
}
if (ev->parent == old_parent) {
// Parent unchanged, but if the parent is root, we need to move the window
// to the top of the window stack
if (old_w) {
// root -> root reparent, we just need to move it to the top
restack_top(ps, old_w);
}
return;
}
if (old_w) {
// A toplevel is reparented, so it is no longer a toplevel. We need to
// destroy the existing toplevel.
if (old_w->managed) {
auto mw = (struct managed_win *)old_w;
// Usually, damage for unmapped windows are added in
// `paint_preprocess`, when a window was painted before and isn't
// anymore. But since we are reparenting the window here, we would
// lose track of the `to_paint` information. So we just add damage
// here.
if (mw->to_paint) {
add_damage_from_win(ps, mw);
}
// Emulating what X server does: a destroyed
// window is always unmapped first.
if (mw->state == WSTATE_MAPPED) {
unmap_win_start(ps, mw);
}
}
destroy_win_start(ps, old_w);
destroy_win_finish(ps, old_w);
}
// We need to guarantee a subwin exists iff it has a valid toplevel.
auto new_subwin = old_subwin;
if (new_subwin != NULL && new_toplevel == NULL) {
remove_subwin_and_unsubscribe(&ps->subwins, &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);
}
if (new_subwin) {
new_subwin->toplevel = new_toplevel->id;
if (new_toplevel->managed) {
win_set_flags(win_as_managed(new_toplevel), WIN_FLAGS_CLIENT_STALE);
ps->pending_updates = true;
}
}
if (ev->parent == ps->c.screen_info->root) {
// X will generate reparent notify even if the parent didn't actually
// change (i.e. reparent again to current parent). So we check if that's
// the case
auto w = find_win(ps, ev->window);
if (w) {
// This window has already been reparented to root before,
// so we don't need to create a new window for it, we just need to
// move it to the top
restack_top(ps, w);
} else {
add_win_top(ps, ev->window);
}
} else {
// otherwise, find and destroy the window first
{
auto w = find_win(ps, ev->window);
if (w) {
if (w->managed) {
auto mw = (struct managed_win *)w;
// Usually, damage for unmapped windows
// are added in `paint_preprocess`, when
// a window was painted before and isn't
// anymore. But since we are reparenting
// the window here, we would lose track
// of the `to_paint` information. So we
// just add damage here.
if (mw->to_paint) {
add_damage_from_win(ps, mw);
}
// Emulating what X server does: a destroyed
// window is always unmapped first.
if (mw->state == WSTATE_MAPPED) {
unmap_win_start(ps, mw);
}
}
// Window reparenting is unlike normal window destruction,
// This window is going to be rendered under another
// parent, so we don't fade here.
destroy_win_start(ps, w);
destroy_win_finish(ps, w);
}
}
// Reset event mask in case something wrong happens
uint32_t evmask = determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN);
if (!wid_has_prop(ps->c.c, ev->window, ps->atoms->aWM_STATE)) {
log_debug("Window %#010x doesn't have WM_STATE property, it is "
"probably not a client window. But we will listen for "
"property change in case it gains one.",
ev->window);
evmask |= XCB_EVENT_MASK_PROPERTY_CHANGE;
} else {
auto w = find_managed_win(ps, ev->parent);
if (w) {
log_debug("Mark window %#010x (%s) as having a stale "
"client",
w->base.id, w->name);
win_set_flags(w, WIN_FLAGS_CLIENT_STALE);
ps->pending_updates = true;
} else {
log_debug("parent %#010x not found", ev->parent);
}
}
XCB_AWAIT_VOID(xcb_change_window_attributes, ps->c.c, ev->window,
XCB_CW_EVENT_MASK, (const uint32_t[]){evmask});
// New parent is root, add a toplevel;
assert(old_w == NULL);
assert(new_toplevel == NULL);
add_win_top(ps, ev->window);
}
}
@ -467,6 +504,50 @@ 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);
if (!subwin) {
// We only care if a direct child of a toplevel gained/lost WM_STATE
return;
}
enum tristate old_has_wm_state = subwin->has_wm_state;
subwin->has_wm_state = ev->state == XCB_PROPERTY_DELETE ? TRI_FALSE : TRI_TRUE;
if (old_has_wm_state == subwin->has_wm_state) {
if (subwin->has_wm_state == TRI_FALSE) {
log_warn("Child window %#010x of window %#010x lost WM_STATE a "
"second time?",
ev->window, subwin->toplevel);
}
return;
}
auto toplevel = find_win(ps, subwin->toplevel);
BUG_ON(toplevel == NULL);
if (!toplevel->managed) {
return;
}
auto managed = (struct managed_win *)toplevel;
if (managed->client_win == subwin->id) {
// 1. This window is the client window of its toplevel, and now it lost
// its WM_STATE (implies it must have it before)
assert(subwin->has_wm_state == TRI_FALSE);
win_set_flags(managed, WIN_FLAGS_CLIENT_STALE);
} else if (subwin->has_wm_state == TRI_TRUE) {
// 2. This window is not the client window of its toplevel, and
// now it gained WM_STATE
if (managed->client_win != XCB_NONE && managed->client_win != toplevel->id) {
log_warn("Toplevel %#010x already has a client window %#010x, "
"but another of its child windows %#010x gained "
"WM_STATE.",
toplevel->id, managed->client_win, subwin->id);
} else {
win_set_flags(managed, WIN_FLAGS_CLIENT_STALE);
}
}
}
static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t *ev) {
if (unlikely(log_get_level_tls() <= LOG_LEVEL_TRACE)) {
// Print out changed atom
@ -499,24 +580,8 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
}
ps->pending_updates = true;
// If WM_STATE changes
if (ev->atom == ps->atoms->aWM_STATE) {
// Check whether it could be a client window
if (!find_toplevel(ps, ev->window)) {
// Reset event mask anyway
const uint32_t evmask =
determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN);
XCB_AWAIT_VOID(xcb_change_window_attributes, ps->c.c, ev->window,
XCB_CW_EVENT_MASK, (const uint32_t[]){evmask});
auto w_top = find_managed_window_or_parent(ps, ev->window);
// ev->window might have not been managed yet, in that case w_top
// would be NULL.
if (w_top) {
win_set_flags(w_top, WIN_FLAGS_CLIENT_STALE);
}
}
return;
ev_subwin_wm_state_changed(ps, ev);
}
// If _NET_WM_WINDOW_TYPE changes... God knows why this would happen, but

View File

@ -30,7 +30,7 @@ setup_window(struct x_connection *c, struct atom *atoms, struct options *options
struct managed_win *w = ccalloc(1, struct managed_win);
w->state = WSTATE_MAPPED;
w->base.id = target;
w->client_win = win_get_client_window(c, atoms, w);
w->client_win = win_get_client_window(c, NULL, atoms, w);
win_update_wintype(c, atoms, w);
win_update_frame_extents(c, atoms, w, w->client_win, options->frame_opacity);
// TODO(yshui) get leader

View File

@ -1725,7 +1725,7 @@ static void handle_pending_updates(EV_P_ struct session *ps) {
// Catching up with X server
handle_queued_x_events(EV_A_ & ps->event_check, 0);
// Call fill_win on new windows
// Process new windows, and maybe allocate struct managed_win for them
handle_new_windows(ps);
// Handle screen changes
@ -1734,7 +1734,7 @@ static void handle_pending_updates(EV_P_ struct session *ps) {
// stale.
handle_root_flags(ps);
// Process window flags (window mapping)
// Process window flags
refresh_windows(ps);
{
@ -2619,6 +2619,13 @@ static void session_destroy(session_t *ps) {
}
list_init_head(&ps->window_stack);
{
struct subwin *subwin, *next_subwin;
HASH_ITER(hh, ps->subwins, subwin, next_subwin) {
remove_subwin(&ps->subwins, subwin);
}
}
// Free blacklists
options_destroy(&ps->o);
c2_state_free(ps->c2_state);

View File

@ -14,6 +14,8 @@ typedef enum {
UNSET
} switch_t;
enum tristate { TRI_FALSE = -1, TRI_UNKNOWN = 0, TRI_TRUE = 1 };
/// A structure representing margins around a rectangle.
typedef struct {
int top;

102
src/win.c
View File

@ -512,10 +512,13 @@ void win_process_update_flags(session_t *ps, struct managed_win *w) {
// Check client first, because later property updates need accurate client
// window information
if (win_check_flags_all(w, WIN_FLAGS_CLIENT_STALE)) {
auto client_win = win_get_client_window(&ps->c, ps->atoms, w);
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);
if (w->client_win && w->client_win != client_win) {
win_unmark_client(ps, w);
win_unmark_client(w);
}
log_debug("New client window for %#010x (%s): %#010x", w->base.id,
w->name, client_win);
win_mark_client(ps, w, client_win);
win_clear_flags(w, WIN_FLAGS_CLIENT_STALE);
}
@ -1440,15 +1443,6 @@ static void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t c
return;
}
auto e = xcb_request_check(
ps->c.c, xcb_change_window_attributes_checked(
ps->c.c, client, XCB_CW_EVENT_MASK,
(const uint32_t[]){determine_evmask(ps, client, WIN_EVMODE_CLIENT)}));
if (e) {
log_error("Failed to change event mask of window %#010x", client);
free(e);
}
win_update_wintype(&ps->c, ps->atoms, w);
// Get frame widths. The window is in damaged area already.
@ -1472,6 +1466,7 @@ static void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t c
// Update everything related to conditions
win_on_factor_change(ps, w);
xcb_generic_error_t *e = NULL;
auto r = xcb_get_window_attributes_reply(
ps->c.c, xcb_get_window_attributes(ps->c.c, w->client_win), &e);
if (!r) {
@ -1489,24 +1484,18 @@ static void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t c
* @param ps current session
* @param w struct _win of the parent window
*/
void win_unmark_client(session_t *ps, struct managed_win *w) {
void win_unmark_client(struct managed_win *w) {
xcb_window_t client = w->client_win;
log_debug("Detaching client window %#010x from frame %#010x (%s)", client,
w->base.id, w->name);
w->client_win = XCB_NONE;
// Recheck event mask
xcb_change_window_attributes(
ps->c.c, client, XCB_CW_EVENT_MASK,
(const uint32_t[]){determine_evmask(ps, client, WIN_EVMODE_UNKNOWN)});
}
/**
* Look for the client window of a particular window.
*/
static xcb_window_t
find_client_win(struct x_connection *c, struct atom *atoms, xcb_window_t w) {
static xcb_window_t find_client_win(struct x_connection *c, struct subwin *subwins,
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) {
@ -1518,7 +1507,17 @@ find_client_win(struct x_connection *c, struct atom *atoms, xcb_window_t w) {
xcb_window_t ret = XCB_NONE;
for (int i = 0; i < nchildren; ++i) {
if (wid_has_prop(c->c, children[i], atoms->aWM_STATE)) {
auto subwin = find_subwin(subwins, children[i]);
bool has_wm_state;
if (!subwin || subwin->has_wm_state == TRI_UNKNOWN) {
has_wm_state = wid_has_prop(c->c, children[i], atoms->aWM_STATE);
if (subwin) {
subwin->has_wm_state = has_wm_state ? TRI_TRUE : TRI_FALSE;
}
} else {
has_wm_state = subwin->has_wm_state == TRI_TRUE;
}
if (has_wm_state) {
ret = children[i];
break;
}
@ -1534,11 +1533,11 @@ find_client_win(struct x_connection *c, struct atom *atoms, xcb_window_t w) {
* @param ps current session
* @param w struct _win of the parent window
*/
xcb_window_t win_get_client_window(struct x_connection *c, struct atom *atoms,
const struct managed_win *w) {
xcb_window_t win_get_client_window(struct x_connection *c, struct subwin *subwins,
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, atoms, w->base.id);
xcb_window_t cw = find_client_win(c, subwins, atoms, w->base.id);
if (cw) {
log_debug("(%#010x): client %#010x", w->base.id, cw);
} else {
@ -1581,6 +1580,44 @@ 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) {
@ -1719,8 +1756,10 @@ struct win *attr_ret_nonnull maybe_allocate_managed_win(session_t *ps, struct wi
w->is_new = false;
// Reject overlay window and already added windows
// Reject overlay window
if (w->id == ps->overlay) {
// Would anyone reparent windows to the overlay window? Doing this
// just in case.
return w;
}
@ -2319,9 +2358,20 @@ void restack_top(session_t *ps, struct win *w) {
/// 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) {
auto mw = (struct managed_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);
}
}
}
auto mw = (struct managed_win *)w;
log_debug("Destroying %#010x \"%s\", managed = %d", w->id,
(w->managed ? mw->name : NULL), w->managed);

View File

@ -21,6 +21,7 @@
#include "utils.h"
#include "win_defs.h"
#include "x.h"
#include "xcb/xproto.h"
struct backend_base;
typedef struct session session_t;
@ -88,6 +89,14 @@ 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;
@ -332,7 +341,7 @@ void win_set_invert_color_force(session_t *ps, struct managed_win *w, switch_t v
void win_set_focused(session_t *ps, struct managed_win *w);
bool attr_pure win_should_fade(session_t *ps, const struct managed_win *w);
void win_on_factor_change(session_t *ps, struct managed_win *w);
void win_unmark_client(session_t *ps, struct managed_win *w);
void win_unmark_client(struct managed_win *w);
bool attr_pure win_should_dim(session_t *ps, const struct managed_win *w);
@ -377,6 +386,14 @@ region_t win_get_region_frame_local_by_val(const struct managed_win *w);
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);
@ -428,6 +445,11 @@ static inline bool attr_pure win_is_wmwin(const struct managed_win *w) {
return w->base.id == w->client_win && !w->a.override_redirect;
}
static inline struct managed_win *win_as_managed(struct win *w) {
BUG_ON(!w->managed);
return (struct managed_win *)w;
}
/// 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);
@ -448,8 +470,8 @@ 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 atom *atoms,
const struct managed_win *w);
xcb_window_t win_get_client_window(struct x_connection *c, struct subwin *subwins,
struct atom *atoms, const struct managed_win *w);
bool win_update_wintype(struct x_connection *c, struct atom *atoms, struct managed_win *w);
/**
* Retrieve frame extents from a window.