mirror of
https://github.com/yshui/picom.git
synced 2025-04-14 17:53:25 -04:00
Merge pull request #1283 from yshui/no-more-server-grabs
Remove uses of GrabServer
This commit is contained in:
commit
562529fefc
15 changed files with 530 additions and 440 deletions
|
@ -56,6 +56,10 @@
|
|||
* `xcb-dpms` is not needed anymore.
|
||||
* `libXext` is not needed anymore.
|
||||
|
||||
## Behind the scene changes
|
||||
|
||||
* The X critical section is removed, picom no longer grabs the server to fetch updates. Hopefully, if everything works, this change is unnoticeable. Minor responsiveness improvements could be possible, but I won't bet on it. The main point of this change is this makes debugging much less painful. Previously if you breaks inside the X critical section, the whole X server will lock up, and you would have to connect to the computer remotely to recover. Now there is no longer such worries. This also avoids a bug inside Xorg that makes server grabbing unreliable.
|
||||
|
||||
# v11.2 (2024-Feb-13)
|
||||
|
||||
## Build changes
|
||||
|
|
|
@ -166,8 +166,6 @@ typedef struct session {
|
|||
// === Display related ===
|
||||
/// X connection
|
||||
struct x_connection c;
|
||||
/// Whether the X server is grabbed by us
|
||||
bool server_grabbed;
|
||||
/// Width of root window.
|
||||
int root_width;
|
||||
/// Height of root window.
|
||||
|
|
140
src/event.c
140
src/event.c
|
@ -190,16 +190,128 @@ static inline const char *attr_pure ev_focus_detail_name(xcb_focus_in_event_t *e
|
|||
|
||||
#undef CASESTRRET
|
||||
|
||||
struct ev_ewmh_active_win_request {
|
||||
struct x_async_request_base base;
|
||||
session_t *ps;
|
||||
};
|
||||
|
||||
/// Update current active window based on EWMH _NET_ACTIVE_WIN.
|
||||
///
|
||||
/// Does not change anything if we fail to get the attribute or the window
|
||||
/// returned could not be found.
|
||||
static void
|
||||
update_ewmh_active_win(struct x_connection * /*c*/, struct x_async_request_base *req_base,
|
||||
xcb_raw_generic_event_t *reply_or_error) {
|
||||
auto ps = ((struct ev_ewmh_active_win_request *)req_base)->ps;
|
||||
free(req_base);
|
||||
|
||||
if (reply_or_error->response_type == 0) {
|
||||
log_error("Failed to get _NET_ACTIVE_WINDOW: %s",
|
||||
x_strerror(((xcb_generic_error_t *)reply_or_error)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Search for the window
|
||||
auto reply = (xcb_get_property_reply_t *)reply_or_error;
|
||||
if (reply->type == XCB_NONE || xcb_get_property_value_length(reply) < 4) {
|
||||
log_debug("EWMH _NET_ACTIVE_WINDOW not set.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto wid = *(xcb_window_t *)xcb_get_property_value(reply);
|
||||
log_debug("EWMH _NET_ACTIVE_WINDOW is %#010x", wid);
|
||||
|
||||
auto cursor = wm_find_by_client(ps->wm, wid);
|
||||
auto w = cursor ? wm_ref_deref(cursor) : NULL;
|
||||
|
||||
// Mark the window focused. No need to unfocus the previous one.
|
||||
if (w) {
|
||||
win_set_focused(ps, w);
|
||||
}
|
||||
}
|
||||
|
||||
struct ev_recheck_focus_request {
|
||||
struct x_async_request_base base;
|
||||
session_t *ps;
|
||||
};
|
||||
|
||||
/**
|
||||
* Recheck currently focused window and set its <code>w->focused</code>
|
||||
* to true.
|
||||
*
|
||||
* @param ps current session
|
||||
* @return struct _win of currently focused window, NULL if not found
|
||||
*/
|
||||
static void recheck_focus(struct x_connection * /*c*/, struct x_async_request_base *req_base,
|
||||
xcb_raw_generic_event_t *reply_or_error) {
|
||||
auto ps = ((struct ev_ewmh_active_win_request *)req_base)->ps;
|
||||
free(req_base);
|
||||
|
||||
// Determine the currently focused window so we can apply appropriate
|
||||
// opacity on it
|
||||
if (reply_or_error->response_type == 0) {
|
||||
// Not able to get input focus means very not good things...
|
||||
auto e = (xcb_generic_error_t *)reply_or_error;
|
||||
log_error_x_error(e, "Failed to get focused window.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto reply = (xcb_get_input_focus_reply_t *)reply_or_error;
|
||||
xcb_window_t wid = reply->focus;
|
||||
log_debug("Current focused window is %#010x", wid);
|
||||
if (wid == XCB_NONE || wid == XCB_INPUT_FOCUS_POINTER_ROOT ||
|
||||
wid == ps->c.screen_info->root) {
|
||||
// Focus is not on a toplevel.
|
||||
return;
|
||||
}
|
||||
|
||||
auto cursor = wm_find(ps->wm, wid);
|
||||
if (cursor == NULL) {
|
||||
if (wm_is_consistent(ps->wm)) {
|
||||
log_error("Window %#010x not found in window tree.", wid);
|
||||
assert(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
cursor = wm_ref_toplevel_of(ps->wm, cursor);
|
||||
if (cursor == NULL) {
|
||||
assert(!wm_is_consistent(ps->wm));
|
||||
return;
|
||||
}
|
||||
|
||||
// And we set the focus state here
|
||||
auto w = wm_ref_deref(cursor);
|
||||
if (w) {
|
||||
log_debug("%#010x (%s) focused.", wid, w->name);
|
||||
win_set_focused(ps, w);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void ev_focus_change(session_t *ps) {
|
||||
if (ps->o.use_ewmh_active_win) {
|
||||
// Not using focus_in/focus_out events.
|
||||
return;
|
||||
}
|
||||
|
||||
auto req = ccalloc(1, struct ev_recheck_focus_request);
|
||||
req->base.sequence = xcb_get_input_focus(ps->c.c).sequence;
|
||||
req->base.callback = recheck_focus;
|
||||
req->ps = ps;
|
||||
x_await_request(&ps->c, &req->base);
|
||||
log_debug("Started async request to recheck focus");
|
||||
}
|
||||
|
||||
static inline void ev_focus_in(session_t *ps, xcb_focus_in_event_t *ev) {
|
||||
log_debug("{ mode: %s, detail: %s }\n", ev_focus_mode_name(ev),
|
||||
log_debug("{ mode: %s, detail: %s }", ev_focus_mode_name(ev),
|
||||
ev_focus_detail_name(ev));
|
||||
ps->pending_updates = true;
|
||||
ev_focus_change(ps);
|
||||
}
|
||||
|
||||
static inline void ev_focus_out(session_t *ps, xcb_focus_out_event_t *ev) {
|
||||
log_debug("{ mode: %s, detail: %s }\n", ev_focus_mode_name(ev),
|
||||
log_debug("{ mode: %s, detail: %s }", ev_focus_mode_name(ev),
|
||||
ev_focus_detail_name(ev));
|
||||
ps->pending_updates = true;
|
||||
ev_focus_change(ps);
|
||||
}
|
||||
|
||||
static inline void ev_create_notify(session_t *ps, xcb_create_notify_event_t *ev) {
|
||||
|
@ -293,7 +405,7 @@ static inline void ev_configure_notify(session_t *ps, xcb_configure_notify_event
|
|||
}
|
||||
|
||||
if (ev->window == ps->c.screen_info->root) {
|
||||
set_root_flags(ps, ROOT_FLAGS_CONFIGURED);
|
||||
configure_root(ps);
|
||||
} else {
|
||||
configure_win(ps, ev);
|
||||
}
|
||||
|
@ -425,6 +537,8 @@ static inline void ev_expose(session_t *ps, xcb_expose_event_t *ev) {
|
|||
}
|
||||
|
||||
static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t *ev) {
|
||||
log_debug("{ atom = %#010x, window = %#010x, state = %d }", ev->atom, ev->window,
|
||||
ev->state);
|
||||
if (unlikely(log_get_level_tls() <= LOG_LEVEL_TRACE)) {
|
||||
// Print out changed atom
|
||||
xcb_get_atom_name_reply_t *reply = xcb_get_atom_name_reply(
|
||||
|
@ -442,8 +556,16 @@ static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t
|
|||
|
||||
if (ps->c.screen_info->root == ev->window) {
|
||||
if (ps->o.use_ewmh_active_win && ps->atoms->a_NET_ACTIVE_WINDOW == ev->atom) {
|
||||
// to update focus
|
||||
ps->pending_updates = true;
|
||||
auto req = ccalloc(1, struct ev_ewmh_active_win_request);
|
||||
req->base.sequence =
|
||||
xcb_get_property(ps->c.c, 0, ps->c.screen_info->root,
|
||||
ps->atoms->a_NET_ACTIVE_WINDOW,
|
||||
XCB_ATOM_WINDOW, 0, 1)
|
||||
.sequence;
|
||||
req->base.callback = update_ewmh_active_win;
|
||||
req->ps = ps;
|
||||
x_await_request(&ps->c, &req->base);
|
||||
log_debug("Started async request to get _NET_ACTIVE_WINDOW");
|
||||
} else {
|
||||
// Destroy the root "image" if the wallpaper probably changed
|
||||
if (x_is_root_back_pixmap_atom(ps->atoms, ev->atom)) {
|
||||
|
@ -712,9 +834,9 @@ void ev_handle(session_t *ps, xcb_generic_event_t *ev) {
|
|||
ev_shape_notify(ps, (xcb_shape_notify_event_t *)ev);
|
||||
break;
|
||||
}
|
||||
if (ps->randr_exists &&
|
||||
if (ps->randr_exists && ps->o.crop_shadow_to_monitor &&
|
||||
ev->response_type == (ps->randr_event + XCB_RANDR_SCREEN_CHANGE_NOTIFY)) {
|
||||
set_root_flags(ps, ROOT_FLAGS_SCREEN_CHANGE);
|
||||
x_update_monitors_async(&ps->c, &ps->monitors);
|
||||
break;
|
||||
}
|
||||
if (ps->damage_event + XCB_DAMAGE_NOTIFY == ev->response_type) {
|
||||
|
|
251
src/picom.c
251
src/picom.c
|
@ -125,12 +125,6 @@ const char *const BACKEND_STRS[] = {[BKEND_XRENDER] = "xrender",
|
|||
/// XXX Limit what xerror can access by not having this pointer
|
||||
session_t *ps_g = NULL;
|
||||
|
||||
void set_root_flags(session_t *ps, uint64_t flags) {
|
||||
log_debug("Setting root flags: %" PRIu64, flags);
|
||||
ps->root_flags |= flags;
|
||||
ps->pending_updates = true;
|
||||
}
|
||||
|
||||
void quit(session_t *ps) {
|
||||
ps->quit = true;
|
||||
ev_break(ps->loop, EVBREAK_ALL);
|
||||
|
@ -449,81 +443,6 @@ void add_damage(session_t *ps, const region_t *damage) {
|
|||
|
||||
// === Windows ===
|
||||
|
||||
/**
|
||||
* Update current active window based on EWMH _NET_ACTIVE_WIN.
|
||||
*
|
||||
* Does not change anything if we fail to get the attribute or the window
|
||||
* returned could not be found.
|
||||
*/
|
||||
void update_ewmh_active_win(session_t *ps) {
|
||||
// Search for the window
|
||||
bool exists;
|
||||
xcb_window_t wid = wid_get_prop_window(&ps->c, ps->c.screen_info->root,
|
||||
ps->atoms->a_NET_ACTIVE_WINDOW, &exists);
|
||||
|
||||
auto cursor = wm_find_by_client(ps->wm, wid);
|
||||
auto w = cursor ? wm_ref_deref(cursor) : NULL;
|
||||
|
||||
// Mark the window focused. No need to unfocus the previous one.
|
||||
if (w) {
|
||||
win_set_focused(ps, w);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recheck currently focused window and set its <code>w->focused</code>
|
||||
* to true.
|
||||
*
|
||||
* @param ps current session
|
||||
* @return struct _win of currently focused window, NULL if not found
|
||||
*/
|
||||
static void recheck_focus(session_t *ps) {
|
||||
// Use EWMH _NET_ACTIVE_WINDOW if enabled
|
||||
if (ps->o.use_ewmh_active_win) {
|
||||
update_ewmh_active_win(ps);
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the currently focused window so we can apply appropriate
|
||||
// opacity on it
|
||||
xcb_generic_error_t *e = NULL;
|
||||
auto reply = xcb_get_input_focus_reply(ps->c.c, xcb_get_input_focus(ps->c.c), &e);
|
||||
if (reply == NULL) {
|
||||
// Not able to get input focus means very not good things...
|
||||
log_error_x_error(e, "Failed to get focused window.");
|
||||
free(e);
|
||||
return;
|
||||
}
|
||||
xcb_window_t wid = reply->focus;
|
||||
free(reply);
|
||||
|
||||
if (wid == XCB_NONE || wid == XCB_INPUT_FOCUS_POINTER_ROOT ||
|
||||
wid == ps->c.screen_info->root) {
|
||||
// Focus is not on a toplevel.
|
||||
return;
|
||||
}
|
||||
|
||||
auto cursor = wm_find(ps->wm, wid);
|
||||
if (cursor == NULL) {
|
||||
log_error("Window %#010x not found in window tree.", wid);
|
||||
return;
|
||||
}
|
||||
|
||||
cursor = wm_ref_toplevel_of(ps->wm, cursor);
|
||||
if (cursor == NULL) {
|
||||
assert(!wm_is_consistent(ps->wm));
|
||||
return;
|
||||
}
|
||||
|
||||
// And we set the focus state here
|
||||
auto w = wm_ref_deref(cursor);
|
||||
if (w) {
|
||||
log_debug("%#010" PRIx32 " (%#010" PRIx32 " \"%s\") focused.", wid,
|
||||
win_id(w), w->name);
|
||||
win_set_focused(ps, w);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild cached <code>screen_reg</code>.
|
||||
*/
|
||||
|
@ -727,7 +646,7 @@ static inline void invalidate_reg_ignore(session_t *ps) {
|
|||
}
|
||||
|
||||
/// Handle configure event of the root window
|
||||
static void configure_root(session_t *ps) {
|
||||
void configure_root(session_t *ps) {
|
||||
// TODO(yshui) re-initializing backend should be done outside of the
|
||||
// critical section. Probably set a flag and do it in draw_callback_impl.
|
||||
auto r = XCB_AWAIT(xcb_get_geometry, ps->c.c, ps->c.screen_info->root);
|
||||
|
@ -808,20 +727,6 @@ static void configure_root(session_t *ps) {
|
|||
}
|
||||
}
|
||||
|
||||
static void handle_root_flags(session_t *ps) {
|
||||
if ((ps->root_flags & ROOT_FLAGS_SCREEN_CHANGE) != 0) {
|
||||
if (ps->o.crop_shadow_to_monitor && ps->randr_exists) {
|
||||
x_update_monitors(&ps->c, &ps->monitors);
|
||||
}
|
||||
ps->root_flags &= ~(uint64_t)ROOT_FLAGS_SCREEN_CHANGE;
|
||||
}
|
||||
|
||||
if ((ps->root_flags & ROOT_FLAGS_CONFIGURED) != 0) {
|
||||
configure_root(ps);
|
||||
ps->root_flags &= ~(uint64_t)ROOT_FLAGS_CONFIGURED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go through the window stack and calculate some parameters for rendering.
|
||||
*
|
||||
|
@ -941,7 +846,7 @@ static bool paint_preprocess(session_t *ps, bool *animation, struct win **out_bo
|
|||
} else if (w->paint_excluded) {
|
||||
log_trace("|- is excluded from painting");
|
||||
to_paint = false;
|
||||
} else if (unlikely((w->flags & WIN_FLAGS_IMAGE_ERROR) != 0)) {
|
||||
} else if (unlikely((w->flags & WIN_FLAGS_PIXMAP_ERROR) != 0)) {
|
||||
log_trace("|- has image errors");
|
||||
to_paint = false;
|
||||
}
|
||||
|
@ -1541,6 +1446,10 @@ static void unredirect(session_t *ps) {
|
|||
/// keeps an internal queue of events, so we have to be 100% sure no events are
|
||||
/// left in that queue before we go to sleep.
|
||||
static void handle_x_events(struct session *ps) {
|
||||
if (ps->vblank_scheduler) {
|
||||
vblank_handle_x_events(ps->vblank_scheduler);
|
||||
}
|
||||
|
||||
// Flush because if we go into sleep when there is still requests in the
|
||||
// outgoing buffer, they will not be sent for an indefinite amount of
|
||||
// time. Use XFlush here too, we might still use some Xlib functions
|
||||
|
@ -1557,10 +1466,6 @@ static void handle_x_events(struct session *ps) {
|
|||
XFlush(ps->c.dpy);
|
||||
xcb_flush(ps->c.c);
|
||||
|
||||
if (ps->vblank_scheduler) {
|
||||
vblank_handle_x_events(ps->vblank_scheduler);
|
||||
}
|
||||
|
||||
xcb_generic_event_t *ev;
|
||||
while ((ev = x_poll_for_event(&ps->c))) {
|
||||
ev_handle(ps, (xcb_generic_event_t *)ev);
|
||||
|
@ -1578,6 +1483,66 @@ static void handle_x_events_ev(EV_P attr_unused, ev_prepare *w, int revents attr
|
|||
handle_x_events(ps);
|
||||
}
|
||||
|
||||
struct new_window_attributes_request {
|
||||
struct x_async_request_base base;
|
||||
struct session *ps;
|
||||
xcb_window_t wid;
|
||||
};
|
||||
|
||||
static void handle_new_window_attributes_reply(struct x_connection * /*c*/,
|
||||
struct x_async_request_base *req_base,
|
||||
xcb_raw_generic_event_t *reply_or_error) {
|
||||
auto req = (struct new_window_attributes_request *)req_base;
|
||||
auto ps = req->ps;
|
||||
auto wid = req->wid;
|
||||
auto new_window = wm_find(ps->wm, wid);
|
||||
free(req);
|
||||
|
||||
if (reply_or_error->response_type == 0) {
|
||||
log_debug("Failed to get window attributes for newly created window "
|
||||
"%#010x",
|
||||
wid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (new_window == NULL) {
|
||||
// Highly unlikely. This window is destroyed, then another window is
|
||||
// created with the same window ID before this request completed, and the
|
||||
// latter window isn't in our tree yet.
|
||||
if (wm_is_consistent(ps->wm)) {
|
||||
log_error("Newly created window %#010x is not in the window tree", wid);
|
||||
assert(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto toplevel = wm_ref_toplevel_of(ps->wm, new_window);
|
||||
if (toplevel != new_window) {
|
||||
log_debug("Newly created window %#010x was moved away from toplevel "
|
||||
"while we were waiting for its attributes",
|
||||
wid);
|
||||
return;
|
||||
}
|
||||
if (wm_ref_deref(toplevel) != NULL) {
|
||||
// This is possible if a toplevel is reparented away, then reparented to
|
||||
// root so it became a toplevel again. If the GetWindowAttributes request
|
||||
// sent for the first time it became a toplevel wasn't completed for this
|
||||
// whole duration, it will create a managed window object for the
|
||||
// toplevel. But there is another get attributes request sent the
|
||||
// second time it became a toplevel. When we get the reply for the second
|
||||
// request, we will reach here.
|
||||
log_debug("Newly created window %#010x is already managed", wid);
|
||||
return;
|
||||
}
|
||||
|
||||
auto w = win_maybe_allocate(ps, toplevel,
|
||||
(xcb_get_window_attributes_reply_t *)reply_or_error);
|
||||
if (w != NULL && w->a.map_state == XCB_MAP_STATE_VIEWABLE) {
|
||||
win_map_start(w);
|
||||
ps->pending_updates = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_new_windows(session_t *ps) {
|
||||
// Check tree changes first, because later property updates need accurate
|
||||
// client window information
|
||||
|
@ -1588,17 +1553,22 @@ static void handle_new_windows(session_t *ps) {
|
|||
break;
|
||||
}
|
||||
switch (wm_change.type) {
|
||||
case WM_TREE_CHANGE_TOPLEVEL_NEW:
|
||||
w = win_maybe_allocate(ps, wm_change.toplevel);
|
||||
if (w != NULL && w->a.map_state == XCB_MAP_STATE_VIEWABLE) {
|
||||
win_map_start(w);
|
||||
}
|
||||
case WM_TREE_CHANGE_TOPLEVEL_NEW:;
|
||||
auto req = ccalloc(1, struct new_window_attributes_request);
|
||||
// We don't directly record the toplevel wm_ref here, because any
|
||||
// number of things could happen before we get the reply. The
|
||||
// window can be reparented, destroyed, then get its window ID
|
||||
// reused, etc.
|
||||
req->wid = wm_ref_win_id(wm_change.toplevel);
|
||||
req->ps = ps;
|
||||
req->base.callback = handle_new_window_attributes_reply,
|
||||
req->base.sequence =
|
||||
xcb_get_window_attributes(ps->c.c, req->wid).sequence;
|
||||
x_await_request(&ps->c, &req->base);
|
||||
break;
|
||||
case WM_TREE_CHANGE_TOPLEVEL_KILLED:
|
||||
w = wm_ref_deref(wm_change.toplevel);
|
||||
if (w != NULL) {
|
||||
// Pointing the window tree_ref to the zombie.
|
||||
w->tree_ref = wm_change.toplevel;
|
||||
win_destroy_start(ps, w);
|
||||
} else {
|
||||
// This window is not managed, no point keeping the zombie
|
||||
|
@ -1655,56 +1625,21 @@ static void tmout_unredir_callback(EV_P attr_unused, ev_timer *w, int revents at
|
|||
}
|
||||
|
||||
static void handle_pending_updates(struct session *ps, double delta_t) {
|
||||
log_trace("Delayed handling of events, entering critical section");
|
||||
auto e = xcb_request_check(ps->c.c, xcb_grab_server_checked(ps->c.c));
|
||||
if (e) {
|
||||
log_fatal_x_error(e, "failed to grab x server");
|
||||
free(e);
|
||||
return quit(ps);
|
||||
}
|
||||
// Process new windows, and maybe allocate struct managed_win for them
|
||||
handle_new_windows(ps);
|
||||
|
||||
ps->server_grabbed = true;
|
||||
if (ps->pending_updates) {
|
||||
log_debug("Delayed handling of events");
|
||||
|
||||
// Catching up with X server
|
||||
// Handle all events X server has sent us so far, so that our internal state will
|
||||
// catch up with the X server's state. It only makes sense to call this function
|
||||
// in the X critical section, otherwise we will be chasing a moving goal post.
|
||||
//
|
||||
// (Disappointingly, grabbing the X server doesn't actually prevent the X server
|
||||
// state from changing. It should, but in practice we have observed window still
|
||||
// being destroyed while we have the server grabbed. This is very disappointing
|
||||
// and forces us to use some hacky code to recover when we discover we are
|
||||
// out-of-sync.)
|
||||
handle_x_events(ps);
|
||||
if (ps->pending_updates || wm_has_tree_changes(ps->wm)) {
|
||||
log_debug("Delayed handling of events, entering critical section");
|
||||
// Process new windows, and maybe allocate struct managed_win for them
|
||||
handle_new_windows(ps);
|
||||
|
||||
// Handle screen changes
|
||||
// This HAS TO be called before refresh_windows, as handle_root_flags
|
||||
// could call configure_root, which will release images and mark them
|
||||
// stale.
|
||||
handle_root_flags(ps);
|
||||
|
||||
// Process window flags
|
||||
// Process window flags. This needs to happen before `refresh_images`
|
||||
// because this might set the pixmap stale window flag.
|
||||
refresh_windows(ps);
|
||||
|
||||
recheck_focus(ps);
|
||||
|
||||
// Process window flags (stale images)
|
||||
refresh_images(ps);
|
||||
}
|
||||
e = xcb_request_check(ps->c.c, xcb_ungrab_server_checked(ps->c.c));
|
||||
if (e) {
|
||||
log_fatal_x_error(e, "failed to ungrab x server");
|
||||
free(e);
|
||||
return quit(ps);
|
||||
}
|
||||
|
||||
ps->server_grabbed = false;
|
||||
// Process window flags (stale images)
|
||||
refresh_images(ps);
|
||||
|
||||
ps->pending_updates = false;
|
||||
log_trace("Exited critical section");
|
||||
|
||||
wm_stack_foreach_safe(ps->wm, cursor, tmp) {
|
||||
auto w = wm_ref_deref(cursor);
|
||||
|
@ -1975,13 +1910,9 @@ static void draw_callback(EV_P_ ev_timer *w, int revents) {
|
|||
}
|
||||
}
|
||||
|
||||
static void x_event_callback(EV_P attr_unused, ev_io *w, int revents attr_unused) {
|
||||
session_t *ps = (session_t *)w;
|
||||
xcb_generic_event_t *ev = x_poll_for_event(&ps->c);
|
||||
if (ev) {
|
||||
ev_handle(ps, ev);
|
||||
free(ev);
|
||||
}
|
||||
static void x_event_callback(EV_P attr_unused, ev_io * /*w*/, int revents attr_unused) {
|
||||
// This function is intentionally left blank, events are actually read and handled
|
||||
// in the ev_prepare listener.
|
||||
}
|
||||
|
||||
static void config_file_change_cb(void *_ps) {
|
||||
|
@ -2408,7 +2339,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
|
|||
if (ps->randr_exists && ps->o.crop_shadow_to_monitor) {
|
||||
xcb_randr_select_input(ps->c.c, ps->c.screen_info->root,
|
||||
XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE);
|
||||
x_update_monitors(&ps->c, &ps->monitors);
|
||||
x_update_monitors_async(&ps->c, &ps->monitors);
|
||||
}
|
||||
|
||||
{
|
||||
|
|
|
@ -24,12 +24,6 @@
|
|||
#include "wm/win.h"
|
||||
#include "x.h"
|
||||
|
||||
enum root_flags {
|
||||
ROOT_FLAGS_SCREEN_CHANGE = 1, // Received RandR screen change notify, we
|
||||
// use this to track refresh rate changes
|
||||
ROOT_FLAGS_CONFIGURED = 2 // Received configure notify on the root window
|
||||
};
|
||||
|
||||
// == Functions ==
|
||||
// TODO(yshui) move static inline functions that are only used in picom.c, into picom.c
|
||||
|
||||
|
@ -43,7 +37,7 @@ void queue_redraw(session_t *ps);
|
|||
|
||||
void discard_pending(session_t *ps, uint32_t sequence);
|
||||
|
||||
void set_root_flags(session_t *ps, uint64_t flags);
|
||||
void configure_root(session_t *ps);
|
||||
|
||||
void quit(session_t *ps);
|
||||
|
||||
|
|
|
@ -44,6 +44,9 @@ static bool layer_from_window(struct layer *out_layer, struct win *w, ivec2 size
|
|||
if (!w->ever_damaged || w->paint_excluded) {
|
||||
goto out;
|
||||
}
|
||||
if (w->win_image == NULL) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
out_layer->scale = (vec2){
|
||||
.x = win_animatable_get(w, WIN_SCRIPT_SCALE_X),
|
||||
|
|
21
src/vblank.c
21
src/vblank.c
|
@ -401,6 +401,10 @@ static void handle_present_complete_notify(struct present_vblank_scheduler *self
|
|||
|
||||
assert(self->base.vblank_event_requested);
|
||||
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
auto now_us = (unsigned long)(now.tv_sec * 1000000L + now.tv_nsec / 1000);
|
||||
|
||||
// X sometimes sends duplicate/bogus MSC events, when screen has just been turned
|
||||
// off. Don't use the msc value in these events. We treat this as not receiving a
|
||||
// vblank event at all, and try to get a new one.
|
||||
|
@ -409,18 +413,15 @@ static void handle_present_complete_notify(struct present_vblank_scheduler *self
|
|||
// https://gitlab.freedesktop.org/xorg/xserver/-/issues/1418
|
||||
bool event_is_invalid = cne->msc <= self->last_msc || cne->ust == 0;
|
||||
if (event_is_invalid) {
|
||||
log_debug("Invalid PresentCompleteNotify event, %" PRIu64 " %" PRIu64,
|
||||
log_debug("Invalid PresentCompleteNotify event, %" PRIu64 " %" PRIu64
|
||||
". Trying to recover, reporting a fake vblank.",
|
||||
cne->msc, cne->ust);
|
||||
x_request_vblank_event(self->base.c, cne->window, self->last_msc + 1);
|
||||
return;
|
||||
self->last_ust = now_us;
|
||||
self->last_msc += 1;
|
||||
} else {
|
||||
self->last_ust = cne->ust;
|
||||
self->last_msc = cne->msc;
|
||||
}
|
||||
|
||||
self->last_ust = cne->ust;
|
||||
self->last_msc = cne->msc;
|
||||
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
auto now_us = (unsigned long)(now.tv_sec * 1000000L + now.tv_nsec / 1000);
|
||||
double delay_sec = 0.0;
|
||||
if (now_us < cne->ust) {
|
||||
log_trace("The end of this vblank is %" PRIu64 " us into the "
|
||||
|
|
|
@ -50,10 +50,8 @@ enum win_flags {
|
|||
|
||||
/// pixmap is out of date, will be update in win_process_flags
|
||||
WIN_FLAGS_PIXMAP_STALE = 1,
|
||||
/// window does not have pixmap bound
|
||||
WIN_FLAGS_PIXMAP_NONE = 2,
|
||||
/// there was an error trying to bind the images
|
||||
WIN_FLAGS_IMAGE_ERROR = 4,
|
||||
/// there was an error binding the window pixmap
|
||||
WIN_FLAGS_PIXMAP_ERROR = 4,
|
||||
/// the client window needs to be updated
|
||||
WIN_FLAGS_CLIENT_STALE = 32,
|
||||
/// the window is mapped by X, we need to call map_win_start for it
|
||||
|
|
246
src/wm/tree.c
246
src/wm/tree.c
|
@ -34,78 +34,8 @@ void wm_tree_reap_zombie(struct wm_tree_node *zombie) {
|
|||
free(zombie);
|
||||
}
|
||||
|
||||
/// Enqueue a tree change.
|
||||
static void wm_tree_enqueue_change(struct wm_tree *tree, struct wm_tree_change change) {
|
||||
if (change.type == WM_TREE_CHANGE_TOPLEVEL_KILLED) {
|
||||
// A gone toplevel will cancel out a previous
|
||||
// `WM_TREE_CHANGE_TOPLEVEL_NEW` change in the queue.
|
||||
bool found = false;
|
||||
list_foreach_safe(struct wm_tree_change_list, i, &tree->changes, siblings) {
|
||||
if (!wm_treeid_eq(i->item.toplevel, change.toplevel)) {
|
||||
continue;
|
||||
}
|
||||
if (i->item.type == WM_TREE_CHANGE_TOPLEVEL_NEW) {
|
||||
list_remove(&i->siblings);
|
||||
list_insert_after(&tree->free_changes, &i->siblings);
|
||||
found = true;
|
||||
} else if (found) {
|
||||
// We also need to delete all other changes
|
||||
// related to this toplevel in between the new and
|
||||
// gone changes.
|
||||
list_remove(&i->siblings);
|
||||
list_insert_after(&tree->free_changes, &i->siblings);
|
||||
} else if (i->item.type == WM_TREE_CHANGE_CLIENT) {
|
||||
// Need to update client changes, so they points to the
|
||||
// zombie instead of the old toplevel node, since the old
|
||||
// toplevel node could be freed before tree changes are
|
||||
// processed.
|
||||
i->item.client.toplevel = change.killed;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
wm_tree_reap_zombie(change.killed);
|
||||
return;
|
||||
}
|
||||
} else if (change.type == WM_TREE_CHANGE_CLIENT) {
|
||||
// A client change can coalesce with a previous client change.
|
||||
list_foreach_safe(struct wm_tree_change_list, i, &tree->changes, siblings) {
|
||||
if (!wm_treeid_eq(i->item.toplevel, change.toplevel) ||
|
||||
i->item.type != WM_TREE_CHANGE_CLIENT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!wm_treeid_eq(i->item.client.new_, change.client.old)) {
|
||||
log_warn("Inconsistent client change for toplevel "
|
||||
"%#010x. Missing changes from %#010x to %#010x. "
|
||||
"Possible bug.",
|
||||
change.toplevel.x, i->item.client.new_.x,
|
||||
change.client.old.x);
|
||||
}
|
||||
|
||||
i->item.client.new_ = change.client.new_;
|
||||
if (wm_treeid_eq(i->item.client.old, change.client.new_)) {
|
||||
list_remove(&i->siblings);
|
||||
list_insert_after(&tree->free_changes, &i->siblings);
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (change.type == WM_TREE_CHANGE_TOPLEVEL_RESTACKED) {
|
||||
list_foreach(struct wm_tree_change_list, i, &tree->changes, siblings) {
|
||||
if (i->item.type == WM_TREE_CHANGE_TOPLEVEL_RESTACKED ||
|
||||
i->item.type == WM_TREE_CHANGE_TOPLEVEL_NEW ||
|
||||
i->item.type == WM_TREE_CHANGE_TOPLEVEL_KILLED) {
|
||||
// Only need to keep one
|
||||
// `WM_TREE_CHANGE_TOPLEVEL_RESTACKED` change, and order
|
||||
// doesn't matter.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We don't let a `WM_TREE_CHANGE_TOPLEVEL_NEW` cancel out a previous
|
||||
// `WM_TREE_CHANGE_TOPLEVEL_GONE`, because the new toplevel would be a different
|
||||
// window reusing the same ID. So we need to go through the proper destruction
|
||||
// process for the previous toplevel. Changes are not commutative (naturally).
|
||||
|
||||
struct wm_tree_change_list *change_list;
|
||||
if (!list_is_empty(&tree->free_changes)) {
|
||||
change_list = list_entry(tree->free_changes.next,
|
||||
|
@ -119,6 +49,114 @@ static void wm_tree_enqueue_change(struct wm_tree *tree, struct wm_tree_change c
|
|||
list_insert_before(&tree->changes, &change_list->siblings);
|
||||
}
|
||||
|
||||
/// Enqueue a `WM_TREE_CHANGE_TOPLEVEL_KILLED` change for a toplevel window. If there are
|
||||
/// any `WM_TREE_CHANGE_TOPLEVEL_NEW` changes in the queue for the same toplevel, they
|
||||
/// will be cancelled out.
|
||||
///
|
||||
/// @return true if this change is cancelled out by a previous change, false otherwise.
|
||||
static bool wm_tree_enqueue_toplevel_killed(struct wm_tree *tree, wm_treeid toplevel,
|
||||
struct wm_tree_node *zombie) {
|
||||
// A gone toplevel will cancel out a previous
|
||||
// `WM_TREE_CHANGE_TOPLEVEL_NEW` change in the queue.
|
||||
bool found = false;
|
||||
list_foreach_safe(struct wm_tree_change_list, i, &tree->changes, siblings) {
|
||||
if (!wm_treeid_eq(i->item.toplevel, toplevel)) {
|
||||
continue;
|
||||
}
|
||||
if (i->item.type == WM_TREE_CHANGE_TOPLEVEL_NEW) {
|
||||
list_remove(&i->siblings);
|
||||
list_insert_after(&tree->free_changes, &i->siblings);
|
||||
found = true;
|
||||
} else if (found) {
|
||||
// We also need to delete all other changes
|
||||
// related to this toplevel in between the new and
|
||||
// gone changes.
|
||||
list_remove(&i->siblings);
|
||||
list_insert_after(&tree->free_changes, &i->siblings);
|
||||
} else if (i->item.type == WM_TREE_CHANGE_CLIENT) {
|
||||
// Need to update client changes, so they points to the
|
||||
// zombie instead of the old toplevel node, since the old
|
||||
// toplevel node could be freed before tree changes are
|
||||
// processed.
|
||||
i->item.client.toplevel = zombie;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
wm_tree_reap_zombie(zombie);
|
||||
return true;
|
||||
}
|
||||
|
||||
wm_tree_enqueue_change(tree, (struct wm_tree_change){
|
||||
.toplevel = toplevel,
|
||||
.type = WM_TREE_CHANGE_TOPLEVEL_KILLED,
|
||||
.killed = zombie,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
static void wm_tree_enqueue_client_change(struct wm_tree *tree, struct wm_tree_node *toplevel,
|
||||
wm_treeid old_client, wm_treeid new_client) {
|
||||
// A client change can coalesce with a previous client change.
|
||||
list_foreach_safe(struct wm_tree_change_list, i, &tree->changes, siblings) {
|
||||
if (!wm_treeid_eq(i->item.toplevel, toplevel->id) ||
|
||||
i->item.type != WM_TREE_CHANGE_CLIENT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!wm_treeid_eq(i->item.client.new_, old_client)) {
|
||||
log_warn("Inconsistent client change for toplevel "
|
||||
"%#010x. Missing changes from %#010x to %#010x. "
|
||||
"Possible bug.",
|
||||
toplevel->id.x, i->item.client.new_.x, old_client.x);
|
||||
}
|
||||
|
||||
i->item.client.new_ = new_client;
|
||||
if (wm_treeid_eq(i->item.client.old, new_client)) {
|
||||
list_remove(&i->siblings);
|
||||
list_insert_after(&tree->free_changes, &i->siblings);
|
||||
}
|
||||
return;
|
||||
}
|
||||
wm_tree_enqueue_change(tree, (struct wm_tree_change){
|
||||
.toplevel = toplevel->id,
|
||||
.type = WM_TREE_CHANGE_CLIENT,
|
||||
.client =
|
||||
{
|
||||
.toplevel = toplevel,
|
||||
.old = old_client,
|
||||
.new_ = new_client,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static void wm_tree_enqueue_toplevel_new(struct wm_tree *tree, struct wm_tree_node *toplevel) {
|
||||
// We don't let a `WM_TREE_CHANGE_TOPLEVEL_NEW` cancel out a previous
|
||||
// `WM_TREE_CHANGE_TOPLEVEL_KILLED`, because the new toplevel would be a different
|
||||
// window reusing the same ID. So we need to go through the proper destruction
|
||||
// process for the previous toplevel. Changes are not commutative (naturally).
|
||||
wm_tree_enqueue_change(tree, (struct wm_tree_change){
|
||||
.toplevel = toplevel->id,
|
||||
.type = WM_TREE_CHANGE_TOPLEVEL_NEW,
|
||||
.new_ = toplevel,
|
||||
});
|
||||
}
|
||||
|
||||
static void wm_tree_enqueue_toplevel_restacked(struct wm_tree *tree) {
|
||||
list_foreach(struct wm_tree_change_list, i, &tree->changes, siblings) {
|
||||
if (i->item.type == WM_TREE_CHANGE_TOPLEVEL_RESTACKED ||
|
||||
i->item.type == WM_TREE_CHANGE_TOPLEVEL_NEW ||
|
||||
i->item.type == WM_TREE_CHANGE_TOPLEVEL_KILLED) {
|
||||
// Only need to keep one
|
||||
// `WM_TREE_CHANGE_TOPLEVEL_RESTACKED` change, and order
|
||||
// doesn't matter.
|
||||
return;
|
||||
}
|
||||
}
|
||||
wm_tree_enqueue_change(tree, (struct wm_tree_change){
|
||||
.type = WM_TREE_CHANGE_TOPLEVEL_RESTACKED,
|
||||
});
|
||||
}
|
||||
|
||||
/// Dequeue the oldest change from the change queue. If the queue is empty, a change with
|
||||
/// `toplevel` set to `XCB_NONE` will be returned.
|
||||
struct wm_tree_change wm_tree_dequeue_change(struct wm_tree *tree) {
|
||||
|
@ -222,22 +260,15 @@ void wm_tree_set_wm_state(struct wm_tree *tree, struct wm_tree_node *node, bool
|
|||
return;
|
||||
}
|
||||
|
||||
struct wm_tree_change change = {
|
||||
.toplevel = toplevel->id,
|
||||
.type = WM_TREE_CHANGE_CLIENT,
|
||||
.client = {.toplevel = toplevel},
|
||||
};
|
||||
if (!has_wm_state && toplevel->client_window == node) {
|
||||
auto new_client = wm_tree_find_client(toplevel);
|
||||
toplevel->client_window = new_client;
|
||||
change.client.old = node->id;
|
||||
change.client.new_ = new_client != NULL ? new_client->id : WM_TREEID_NONE;
|
||||
wm_tree_enqueue_change(tree, change);
|
||||
wm_tree_enqueue_client_change(
|
||||
tree, toplevel, node->id,
|
||||
new_client != NULL ? new_client->id : WM_TREEID_NONE);
|
||||
} else if (has_wm_state && toplevel->client_window == NULL) {
|
||||
toplevel->client_window = node;
|
||||
change.client.old = WM_TREEID_NONE;
|
||||
change.client.new_ = node->id;
|
||||
wm_tree_enqueue_change(tree, change);
|
||||
wm_tree_enqueue_client_change(tree, toplevel, WM_TREEID_NONE, node->id);
|
||||
} else if (has_wm_state) {
|
||||
// If the toplevel window already has a client window, we won't
|
||||
// try to usurp it.
|
||||
|
@ -268,27 +299,24 @@ wm_tree_refresh_client_and_queue_change(struct wm_tree *tree, struct wm_tree_nod
|
|||
BUG_ON(toplevel->parent->parent != NULL);
|
||||
auto new_client = wm_tree_find_client(toplevel);
|
||||
if (new_client != toplevel->client_window) {
|
||||
struct wm_tree_change change = {.toplevel = toplevel->id,
|
||||
.type = WM_TREE_CHANGE_CLIENT,
|
||||
.client = {.toplevel = toplevel,
|
||||
.old = WM_TREEID_NONE,
|
||||
.new_ = WM_TREEID_NONE}};
|
||||
wm_treeid old_client_id = WM_TREEID_NONE, new_client_id = WM_TREEID_NONE;
|
||||
if (toplevel->client_window != NULL) {
|
||||
change.client.old = toplevel->client_window->id;
|
||||
old_client_id = toplevel->client_window->id;
|
||||
}
|
||||
if (new_client != NULL) {
|
||||
change.client.new_ = new_client->id;
|
||||
new_client_id = new_client->id;
|
||||
}
|
||||
toplevel->client_window = new_client;
|
||||
wm_tree_enqueue_change(tree, change);
|
||||
wm_tree_enqueue_client_change(tree, toplevel, old_client_id, new_client_id);
|
||||
}
|
||||
}
|
||||
|
||||
void wm_tree_detach(struct wm_tree *tree, struct wm_tree_node *subroot) {
|
||||
struct wm_tree_node *wm_tree_detach(struct wm_tree *tree, struct wm_tree_node *subroot) {
|
||||
BUG_ON(subroot == NULL);
|
||||
BUG_ON(subroot->parent == NULL); // Trying to detach the root window?!
|
||||
|
||||
auto toplevel = wm_tree_find_toplevel_for(tree, subroot);
|
||||
struct wm_tree_node *zombie = NULL;
|
||||
if (toplevel != subroot) {
|
||||
list_remove(&subroot->siblings);
|
||||
if (toplevel != NULL) {
|
||||
|
@ -296,21 +324,18 @@ void wm_tree_detach(struct wm_tree *tree, struct wm_tree_node *subroot) {
|
|||
}
|
||||
} else {
|
||||
// Detached a toplevel, create a zombie for it
|
||||
auto zombie = ccalloc(1, struct wm_tree_node);
|
||||
zombie = ccalloc(1, struct wm_tree_node);
|
||||
zombie->parent = subroot->parent;
|
||||
zombie->id = subroot->id;
|
||||
zombie->is_zombie = true;
|
||||
zombie->win = subroot->win;
|
||||
subroot->win = NULL;
|
||||
list_init_head(&zombie->children);
|
||||
list_replace(&subroot->siblings, &zombie->siblings);
|
||||
wm_tree_enqueue_change(tree, (struct wm_tree_change){
|
||||
.toplevel = subroot->id,
|
||||
.type = WM_TREE_CHANGE_TOPLEVEL_KILLED,
|
||||
.killed = zombie,
|
||||
});
|
||||
if (wm_tree_enqueue_toplevel_killed(tree, subroot->id, zombie)) {
|
||||
zombie = NULL;
|
||||
}
|
||||
}
|
||||
subroot->parent = NULL;
|
||||
return zombie;
|
||||
}
|
||||
|
||||
void wm_tree_attach(struct wm_tree *tree, struct wm_tree_node *child,
|
||||
|
@ -329,11 +354,7 @@ void wm_tree_attach(struct wm_tree *tree, struct wm_tree_node *child,
|
|||
|
||||
auto toplevel = wm_tree_find_toplevel_for(tree, child);
|
||||
if (child == toplevel) {
|
||||
wm_tree_enqueue_change(tree, (struct wm_tree_change){
|
||||
.toplevel = child->id,
|
||||
.type = WM_TREE_CHANGE_TOPLEVEL_NEW,
|
||||
.new_ = child,
|
||||
});
|
||||
wm_tree_enqueue_toplevel_new(tree, child);
|
||||
} else if (toplevel != NULL) {
|
||||
wm_tree_refresh_client_and_queue_change(tree, toplevel);
|
||||
}
|
||||
|
@ -355,9 +376,7 @@ void wm_tree_move_to_end(struct wm_tree *tree, struct wm_tree_node *node, bool t
|
|||
list_insert_after(&node->parent->children, &node->siblings);
|
||||
}
|
||||
if (node->parent == tree->root) {
|
||||
wm_tree_enqueue_change(tree, (struct wm_tree_change){
|
||||
.type = WM_TREE_CHANGE_TOPLEVEL_RESTACKED,
|
||||
});
|
||||
wm_tree_enqueue_toplevel_restacked(tree);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -377,9 +396,7 @@ void wm_tree_move_to_above(struct wm_tree *tree, struct wm_tree_node *node,
|
|||
list_remove(&node->siblings);
|
||||
list_insert_before(&other->siblings, &node->siblings);
|
||||
if (node->parent == tree->root) {
|
||||
wm_tree_enqueue_change(tree, (struct wm_tree_change){
|
||||
.type = WM_TREE_CHANGE_TOPLEVEL_RESTACKED,
|
||||
});
|
||||
wm_tree_enqueue_toplevel_restacked(tree);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -433,7 +450,7 @@ TEST_CASE(tree_manipulation) {
|
|||
assert(change.toplevel.x == 3);
|
||||
assert(change.type == WM_TREE_CHANGE_TOPLEVEL_NEW);
|
||||
|
||||
wm_tree_detach(&tree, node2);
|
||||
auto zombie = wm_tree_detach(&tree, node2);
|
||||
wm_tree_attach(&tree, node2, node3);
|
||||
assert(node2->parent == node3);
|
||||
assert(node3->children.next == &node2->siblings);
|
||||
|
@ -442,6 +459,7 @@ TEST_CASE(tree_manipulation) {
|
|||
change = wm_tree_dequeue_change(&tree);
|
||||
assert(change.toplevel.x == 2);
|
||||
assert(change.type == WM_TREE_CHANGE_TOPLEVEL_KILLED);
|
||||
TEST_EQUAL(change.killed, zombie);
|
||||
wm_tree_reap_zombie(change.killed);
|
||||
|
||||
wm_tree_set_wm_state(&tree, node2, true);
|
||||
|
@ -462,7 +480,7 @@ TEST_CASE(tree_manipulation) {
|
|||
// node3 already has node2 as its client window, so the new one should be ignored.
|
||||
assert(change.type == WM_TREE_CHANGE_NONE);
|
||||
|
||||
wm_tree_detach(&tree, node2);
|
||||
TEST_EQUAL(wm_tree_detach(&tree, node2), NULL);
|
||||
HASH_DEL(tree.nodes, node2);
|
||||
free(node2);
|
||||
change = wm_tree_dequeue_change(&tree);
|
||||
|
@ -472,7 +490,7 @@ TEST_CASE(tree_manipulation) {
|
|||
assert(change.client.new_.x == 4);
|
||||
|
||||
// Test window ID reuse
|
||||
wm_tree_detach(&tree, node4);
|
||||
TEST_EQUAL(wm_tree_detach(&tree, node4), NULL);
|
||||
HASH_DEL(tree.nodes, node4);
|
||||
free(node4);
|
||||
node4 = wm_tree_new_window(&tree, 4);
|
||||
|
@ -489,7 +507,7 @@ TEST_CASE(tree_manipulation) {
|
|||
auto node5 = wm_tree_new_window(&tree, 5);
|
||||
wm_tree_add_window(&tree, node5);
|
||||
wm_tree_attach(&tree, node5, root);
|
||||
wm_tree_detach(&tree, node5);
|
||||
TEST_EQUAL(wm_tree_detach(&tree, node5), NULL);
|
||||
HASH_DEL(tree.nodes, node5);
|
||||
free(node5);
|
||||
change = wm_tree_dequeue_change(&tree);
|
||||
|
|
158
src/wm/win.c
158
src/wm/win.c
|
@ -310,13 +310,10 @@ void add_damage_from_win(session_t *ps, const struct win *w) {
|
|||
/// Release the images attached to this window
|
||||
static inline void win_release_pixmap(backend_t *base, struct win *w) {
|
||||
log_debug("Releasing pixmap of window %#010x (%s)", win_id(w), w->name);
|
||||
assert(w->win_image);
|
||||
if (w->win_image) {
|
||||
xcb_pixmap_t pixmap = XCB_NONE;
|
||||
pixmap = base->ops.release_image(base, w->win_image);
|
||||
w->win_image = NULL;
|
||||
// Bypassing win_set_flags, because `w` might have been destroyed
|
||||
w->flags |= WIN_FLAGS_PIXMAP_NONE;
|
||||
if (pixmap != XCB_NONE) {
|
||||
xcb_free_pixmap(base->c->c, pixmap);
|
||||
}
|
||||
|
@ -346,40 +343,13 @@ static inline void win_release_mask(backend_t *base, struct win *w) {
|
|||
}
|
||||
}
|
||||
|
||||
static inline bool win_bind_pixmap(struct backend_base *b, struct win *w) {
|
||||
assert(!w->win_image);
|
||||
auto pixmap = x_new_id(b->c);
|
||||
auto e = xcb_request_check(
|
||||
b->c->c, xcb_composite_name_window_pixmap_checked(b->c->c, win_id(w), pixmap));
|
||||
if (e) {
|
||||
log_error("Failed to get named pixmap for window %#010x(%s)", win_id(w),
|
||||
w->name);
|
||||
free(e);
|
||||
return false;
|
||||
}
|
||||
log_debug("New named pixmap for %#010x (%s) : %#010x", win_id(w), w->name, pixmap);
|
||||
w->win_image = b->ops.bind_pixmap(b, pixmap, x_get_visual_info(b->c, w->a.visual));
|
||||
if (!w->win_image) {
|
||||
log_error("Failed to bind pixmap");
|
||||
xcb_free_pixmap(b->c->c, pixmap);
|
||||
win_set_flags(w, WIN_FLAGS_IMAGE_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
win_clear_flags(w, WIN_FLAGS_PIXMAP_NONE);
|
||||
return true;
|
||||
}
|
||||
|
||||
void win_release_images(struct backend_base *backend, struct win *w) {
|
||||
// We don't want to decide what we should do if the image we want to
|
||||
// release is stale (do we clear the stale flags or not?) But if we are
|
||||
// not releasing any images anyway, we don't care about the stale flags.
|
||||
assert(w->win_image == NULL || !win_check_flags_all(w, WIN_FLAGS_PIXMAP_STALE));
|
||||
|
||||
if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) {
|
||||
assert(!win_check_flags_all(w, WIN_FLAGS_PIXMAP_STALE));
|
||||
win_release_pixmap(backend, w);
|
||||
}
|
||||
|
||||
win_release_pixmap(backend, w);
|
||||
win_release_shadow(backend, w);
|
||||
win_release_mask(backend, w);
|
||||
}
|
||||
|
@ -568,35 +538,41 @@ void win_process_image_flags(session_t *ps, struct win *w) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Not a loop
|
||||
while (win_check_flags_any(w, WIN_FLAGS_PIXMAP_STALE) &&
|
||||
!win_check_flags_all(w, WIN_FLAGS_IMAGE_ERROR)) {
|
||||
// Image needs to be updated, update it.
|
||||
if (!ps->backend_data) {
|
||||
// We are using legacy backend, nothing to do here.
|
||||
break;
|
||||
}
|
||||
|
||||
if (win_check_flags_all(w, WIN_FLAGS_PIXMAP_STALE)) {
|
||||
// Check to make sure the window is still mapped,
|
||||
// otherwise we won't be able to rebind pixmap after
|
||||
// releasing it, yet we might still need the pixmap for
|
||||
// rendering.
|
||||
if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) {
|
||||
// Must release images first, otherwise breaks
|
||||
// NVIDIA driver
|
||||
win_release_pixmap(ps->backend_data, w);
|
||||
}
|
||||
win_bind_pixmap(ps->backend_data, w);
|
||||
}
|
||||
|
||||
// break here, loop always run only once
|
||||
break;
|
||||
if (!win_check_flags_any(w, WIN_FLAGS_PIXMAP_STALE) ||
|
||||
win_check_flags_all(w, WIN_FLAGS_PIXMAP_ERROR) ||
|
||||
// We don't need to do anything here for legacy backends
|
||||
ps->backend_data == NULL) {
|
||||
win_clear_flags(w, WIN_FLAGS_PIXMAP_STALE);
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear stale image flags
|
||||
if (win_check_flags_any(w, WIN_FLAGS_PIXMAP_STALE)) {
|
||||
win_clear_flags(w, WIN_FLAGS_PIXMAP_STALE);
|
||||
// Image needs to be updated, update it.
|
||||
win_clear_flags(w, WIN_FLAGS_PIXMAP_STALE);
|
||||
|
||||
// Check to make sure the window is still mapped, otherwise we won't be able to
|
||||
// rebind pixmap after releasing it, yet we might still need the pixmap for
|
||||
// rendering.
|
||||
auto pixmap = x_new_id(&ps->c);
|
||||
auto e = xcb_request_check(
|
||||
ps->c.c, xcb_composite_name_window_pixmap_checked(ps->c.c, win_id(w), pixmap));
|
||||
if (e != NULL) {
|
||||
log_debug("Failed to get named pixmap for window %#010x(%s): %s. "
|
||||
"Retaining its current window image",
|
||||
win_id(w), w->name, x_strerror(e));
|
||||
free(e);
|
||||
return;
|
||||
}
|
||||
|
||||
log_debug("New named pixmap for %#010x (%s) : %#010x", win_id(w), w->name, pixmap);
|
||||
|
||||
// Must release images first, otherwise breaks NVIDIA driver
|
||||
win_release_pixmap(ps->backend_data, w);
|
||||
w->win_image = ps->backend_data->ops.bind_pixmap(
|
||||
ps->backend_data, pixmap, x_get_visual_info(&ps->c, w->a.visual));
|
||||
if (!w->win_image) {
|
||||
log_error("Failed to bind pixmap");
|
||||
xcb_free_pixmap(ps->c.c, pixmap);
|
||||
win_set_flags(w, WIN_FLAGS_PIXMAP_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -845,9 +821,7 @@ void unmap_win_finish(session_t *ps, struct win *w) {
|
|||
if (ps->backend_data) {
|
||||
// Only the pixmap needs to be freed and reacquired when mapping.
|
||||
// Shadow image can be preserved.
|
||||
if (!win_check_flags_all(w, WIN_FLAGS_PIXMAP_NONE)) {
|
||||
win_release_pixmap(ps->backend_data, w);
|
||||
}
|
||||
win_release_pixmap(ps->backend_data, w);
|
||||
} else {
|
||||
assert(!w->win_image);
|
||||
assert(!w->shadow_image);
|
||||
|
@ -858,7 +832,7 @@ void unmap_win_finish(session_t *ps, struct win *w) {
|
|||
|
||||
// Try again at binding images when the window is mapped next time
|
||||
if (w->state != WSTATE_DESTROYED) {
|
||||
win_clear_flags(w, WIN_FLAGS_IMAGE_ERROR);
|
||||
win_clear_flags(w, WIN_FLAGS_PIXMAP_ERROR);
|
||||
}
|
||||
assert(w->running_animation == NULL);
|
||||
}
|
||||
|
@ -1384,14 +1358,15 @@ void free_win_res(session_t *ps, struct win *w) {
|
|||
|
||||
/// Query the Xorg for information about window `win`, and assign a window to `cursor` if
|
||||
/// this window should be managed.
|
||||
struct win *win_maybe_allocate(session_t *ps, struct wm_ref *cursor) {
|
||||
struct win *win_maybe_allocate(session_t *ps, struct wm_ref *cursor,
|
||||
xcb_get_window_attributes_reply_t *attrs) {
|
||||
static const struct win win_def = {
|
||||
.frame_opacity = 1.0,
|
||||
.in_openclose = true, // set to false after first map is done,
|
||||
// true here because window is just created
|
||||
.flags = WIN_FLAGS_PIXMAP_NONE, // updated by
|
||||
// property/attributes/etc
|
||||
// change
|
||||
.flags = 0, // updated by
|
||||
// property/attributes/etc
|
||||
// change
|
||||
|
||||
// Runtime variables, updated by dbus
|
||||
.fade_force = UNSET,
|
||||
|
@ -1421,23 +1396,18 @@ struct win *win_maybe_allocate(session_t *ps, struct wm_ref *cursor) {
|
|||
|
||||
xcb_window_t wid = wm_ref_win_id(cursor);
|
||||
log_debug("Managing window %#010x", wid);
|
||||
xcb_get_window_attributes_cookie_t acookie = xcb_get_window_attributes(ps->c.c, wid);
|
||||
xcb_get_window_attributes_reply_t *a =
|
||||
xcb_get_window_attributes_reply(ps->c.c, acookie, NULL);
|
||||
if (!a || a->map_state == XCB_MAP_STATE_UNVIEWABLE) {
|
||||
if (attrs->map_state == XCB_MAP_STATE_UNVIEWABLE) {
|
||||
// Failed to get window attributes or geometry probably means
|
||||
// the window is gone already. Unviewable means the window is
|
||||
// already reparented elsewhere.
|
||||
// BTW, we don't care about Input Only windows, except for
|
||||
// stacking proposes, so we need to keep track of them still.
|
||||
free(a);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (a->_class == XCB_WINDOW_CLASS_INPUT_ONLY) {
|
||||
if (attrs->_class == XCB_WINDOW_CLASS_INPUT_ONLY) {
|
||||
// No need to manage this window, but we still keep it on the
|
||||
// window stack
|
||||
free(a);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -1448,16 +1418,14 @@ struct win *win_maybe_allocate(session_t *ps, struct wm_ref *cursor) {
|
|||
// We only need to initialize the part that are not initialized
|
||||
// by map_win
|
||||
*new = win_def;
|
||||
new->a = *a;
|
||||
new->a = *attrs;
|
||||
new->shadow_opacity = ps->o.shadow_opacity;
|
||||
pixman_region32_init(&new->bounding_shape);
|
||||
|
||||
free(a);
|
||||
|
||||
xcb_generic_error_t *e;
|
||||
auto g = xcb_get_geometry_reply(ps->c.c, xcb_get_geometry(ps->c.c, wid), &e);
|
||||
if (!g) {
|
||||
log_error_x_error(e, "Failed to get geometry of window %#010x", wid);
|
||||
log_debug("Failed to get geometry of window %#010x: %s", wid, x_strerror(e));
|
||||
free(e);
|
||||
free(new);
|
||||
return NULL;
|
||||
|
@ -1478,7 +1446,7 @@ struct win *win_maybe_allocate(session_t *ps, struct wm_ref *cursor) {
|
|||
ps->c.c, xcb_damage_create_checked(ps->c.c, new->damage, wid,
|
||||
XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY));
|
||||
if (e) {
|
||||
log_error_x_error(e, "Failed to create damage");
|
||||
log_debug("Failed to create damage for window %#010x: %s", wid, x_strerror(e));
|
||||
free(e);
|
||||
free(new);
|
||||
return NULL;
|
||||
|
@ -1490,12 +1458,13 @@ struct win *win_maybe_allocate(session_t *ps, struct wm_ref *cursor) {
|
|||
if (!ps->o.use_ewmh_active_win) {
|
||||
frame_event_mask |= XCB_EVENT_MASK_FOCUS_CHANGE;
|
||||
}
|
||||
xcb_change_window_attributes(ps->c.c, wid, XCB_CW_EVENT_MASK,
|
||||
(const uint32_t[]){frame_event_mask});
|
||||
x_set_error_action_ignore(
|
||||
&ps->c, xcb_change_window_attributes(ps->c.c, wid, XCB_CW_EVENT_MASK,
|
||||
(const uint32_t[]){frame_event_mask}));
|
||||
|
||||
// Get notification when the shape of a window changes
|
||||
if (ps->shape_exists) {
|
||||
xcb_shape_select_input(ps->c.c, wid, 1);
|
||||
x_set_error_action_ignore(&ps->c, xcb_shape_select_input(ps->c.c, wid, 1));
|
||||
}
|
||||
|
||||
new->pictfmt = x_get_pictform_for_visual(&ps->c, new->a.visual);
|
||||
|
@ -1688,17 +1657,17 @@ void win_set_focused(session_t *ps, struct win *w) {
|
|||
}
|
||||
|
||||
auto old_active_win = wm_active_win(ps->wm);
|
||||
if (w->is_ewmh_focused) {
|
||||
if (w->is_focused) {
|
||||
assert(old_active_win == w);
|
||||
return;
|
||||
}
|
||||
|
||||
wm_set_active_win(ps->wm, w);
|
||||
w->is_ewmh_focused = true;
|
||||
w->is_focused = true;
|
||||
|
||||
if (old_active_win) {
|
||||
assert(old_active_win->is_ewmh_focused);
|
||||
old_active_win->is_ewmh_focused = false;
|
||||
assert(old_active_win->is_focused);
|
||||
old_active_win->is_focused = false;
|
||||
win_on_focus_change(ps, old_active_win);
|
||||
}
|
||||
win_on_focus_change(ps, w);
|
||||
|
@ -2029,10 +1998,17 @@ double win_animatable_get(const struct win *w, enum win_script_output output) {
|
|||
|
||||
bool win_process_animation_and_state_change(struct session *ps, struct win *w, double delta_t) {
|
||||
// If the window hasn't ever been damaged yet, it won't be rendered in this frame.
|
||||
// And if it is also unmapped/destroyed, it won't receive damage events. In this
|
||||
// case we can skip its animation. For mapped windows, we need to provisionally
|
||||
// start animation, because its first damage event might come a bit late.
|
||||
bool will_never_render = !w->ever_damaged && w->state != WSTATE_MAPPED;
|
||||
// Or if it doesn't have a image bound, it won't be rendered either. (This can
|
||||
// happen is a window is destroyed during a backend reset. Backend resets releases
|
||||
// all images, and if a window is freed during that, its image cannot be
|
||||
// reacquired.)
|
||||
//
|
||||
// If the window won't be rendered, and it is also unmapped/destroyed, it won't
|
||||
// receive damage events or reacquire an image. In this case we can skip its
|
||||
// animation. For mapped windows, we need to provisionally start animation,
|
||||
// because its first damage event might come a bit late.
|
||||
bool will_never_render =
|
||||
(!w->ever_damaged || w->win_image == NULL) && w->state != WSTATE_MAPPED;
|
||||
if (!ps->redirected || will_never_render) {
|
||||
// This window won't be rendered, so we don't need to run the animations.
|
||||
bool state_changed = w->previous.state != w->state;
|
||||
|
@ -2332,5 +2308,5 @@ bool win_is_bypassing_compositor(const session_t *ps, const struct win *w) {
|
|||
* settings
|
||||
*/
|
||||
bool win_is_focused_raw(const struct win *w) {
|
||||
return w->a.map_state == XCB_MAP_STATE_VIEWABLE && w->is_ewmh_focused;
|
||||
return w->a.map_state == XCB_MAP_STATE_VIEWABLE && w->is_focused;
|
||||
}
|
||||
|
|
|
@ -195,8 +195,8 @@ struct win {
|
|||
/// `is_ewmh_fullscreen`, or the windows spatial relation with the
|
||||
/// root window. Which one is used is determined by user configuration.
|
||||
bool is_fullscreen;
|
||||
/// Whether the window is the EWMH active window.
|
||||
bool is_ewmh_focused;
|
||||
/// Whether the window is the active window.
|
||||
bool is_focused;
|
||||
|
||||
// Opacity-related members
|
||||
/// Window opacity
|
||||
|
@ -353,7 +353,7 @@ void win_destroy_start(session_t *ps, struct win *w);
|
|||
void win_map_start(struct win *w);
|
||||
/// Release images bound with a window, set the *_NONE flags on the window. Only to be
|
||||
/// used when de-initializing the backend outside of win.c
|
||||
void win_release_images(struct backend_base *base, struct win *w);
|
||||
void win_release_images(struct backend_base *backend, struct win *w);
|
||||
winmode_t attr_pure win_calc_mode_raw(const struct win *w);
|
||||
// TODO(yshui) `win_calc_mode` is only used by legacy backends
|
||||
winmode_t attr_pure win_calc_mode(const struct win *w);
|
||||
|
@ -408,7 +408,8 @@ void win_get_region_frame_local(const struct win *w, region_t *res);
|
|||
region_t win_get_region_frame_local_by_val(const struct win *w);
|
||||
/// Query the Xorg for information about window `win`
|
||||
/// `win` pointer might become invalid after this function returns
|
||||
struct win *win_maybe_allocate(session_t *ps, struct wm_ref *cursor);
|
||||
struct win *win_maybe_allocate(session_t *ps, struct wm_ref *cursor,
|
||||
xcb_get_window_attributes_reply_t *attrs);
|
||||
|
||||
/**
|
||||
* Release a destroyed window that is no longer needed.
|
||||
|
|
36
src/wm/wm.c
36
src/wm/wm.c
|
@ -265,6 +265,15 @@ static void wm_reap_orphans(struct wm *wm) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Move `from->win` to `to->win`, update `win->tree_ref`.
|
||||
static void wm_move_win(struct wm_tree_node *from, struct wm_tree_node *to) {
|
||||
if (from->win != NULL) {
|
||||
from->win->tree_ref = (struct wm_ref *)&to->siblings;
|
||||
}
|
||||
to->win = from->win;
|
||||
from->win = NULL;
|
||||
}
|
||||
|
||||
void wm_destroy(struct wm *wm, xcb_window_t wid) {
|
||||
struct wm_tree_node *node = wm_tree_find(&wm->tree, wid);
|
||||
if (!node) {
|
||||
|
@ -279,7 +288,11 @@ void wm_destroy(struct wm *wm, xcb_window_t wid) {
|
|||
if (!list_is_empty(&node->children)) {
|
||||
log_error("Window %#010x is destroyed but it still has children", wid);
|
||||
}
|
||||
wm_tree_detach(&wm->tree, node);
|
||||
auto zombie = wm_tree_detach(&wm->tree, node);
|
||||
assert(zombie != NULL || node->win == NULL);
|
||||
if (zombie != NULL) {
|
||||
wm_move_win(node, zombie);
|
||||
}
|
||||
// There could be an in-flight query tree request for this window, we orphan it.
|
||||
// It will be reaped when all query tree requests are completed. (Or right now if
|
||||
// the tree is already consistent.)
|
||||
|
@ -315,7 +328,11 @@ void wm_reparent(struct wm *wm, xcb_window_t wid, xcb_window_t parent) {
|
|||
return;
|
||||
}
|
||||
|
||||
wm_tree_detach(&wm->tree, window);
|
||||
auto zombie = wm_tree_detach(&wm->tree, window);
|
||||
assert(zombie != NULL || window->win == NULL);
|
||||
if (zombie != NULL) {
|
||||
wm_move_win(window, zombie);
|
||||
}
|
||||
|
||||
// Attaching `window` to `new_parent` will change the children list of
|
||||
// `new_parent`, if there is a pending query tree request for `new_parent`, doing
|
||||
|
@ -390,7 +407,13 @@ wm_handle_query_tree_reply(struct x_connection *c, struct x_async_request_base *
|
|||
|
||||
// If child node exists, it must be a previously orphaned node.
|
||||
assert(child_node->parent == &wm->orphan_root);
|
||||
wm_tree_detach(&wm->tree, child_node);
|
||||
auto zombie = wm_tree_detach(&wm->tree, child_node);
|
||||
if (zombie != NULL) {
|
||||
// This only happens if `child_node` is not orphaned, which means
|
||||
// things are already going wrong. (the assert above would fail
|
||||
// too).
|
||||
wm_tree_reap_zombie(zombie);
|
||||
}
|
||||
wm_tree_attach(&wm->tree, child_node, node);
|
||||
}
|
||||
|
||||
|
@ -463,7 +486,12 @@ static void wm_import_start_no_flush(struct wm *wm, struct x_connection *c, stru
|
|||
assert(false);
|
||||
}
|
||||
|
||||
wm_tree_detach(&wm->tree, new);
|
||||
auto zombie = wm_tree_detach(&wm->tree, new);
|
||||
if (zombie != NULL) {
|
||||
// This only happens if `new` is not orphaned, which means things
|
||||
// are already going wrong.
|
||||
wm_tree_reap_zombie(zombie);
|
||||
}
|
||||
// Need to bump the generation number, as `new` is actually an entirely
|
||||
// new window, just reusing the same window ID.
|
||||
new->id.gen = wm->tree.gen++;
|
||||
|
|
|
@ -94,7 +94,9 @@ void wm_tree_destroy_window(struct wm_tree *tree, struct wm_tree_node *node);
|
|||
/// Detach the subtree rooted at `subroot` from `tree`. The subtree root is removed from
|
||||
/// its parent, and the disconnected tree nodes won't be able to be found via
|
||||
/// `wm_tree_find`. Relevant events will be generated.
|
||||
void wm_tree_detach(struct wm_tree *tree, struct wm_tree_node *subroot);
|
||||
///
|
||||
/// Returns the zombie tree node if one is created, or NULL.
|
||||
struct wm_tree_node *must_use wm_tree_detach(struct wm_tree *tree, struct wm_tree_node *subroot);
|
||||
/// Attach `node` to `parent`. `node` becomes the topmost child of `parent`. If `parent`
|
||||
/// is NULL, `node` becomes the root window.
|
||||
void wm_tree_attach(struct wm_tree *tree, struct wm_tree_node *child,
|
||||
|
|
78
src/x.c
78
src/x.c
|
@ -334,39 +334,35 @@ xcb_window_t wid_get_prop_window(struct x_connection *c, xcb_window_t wid,
|
|||
*/
|
||||
bool wid_get_text_prop(struct x_connection *c, struct atom *atoms, xcb_window_t wid,
|
||||
xcb_atom_t prop, char ***pstrlst, int *pnstr) {
|
||||
auto prop_info = x_get_prop_info(c, wid, prop);
|
||||
auto type = prop_info.type;
|
||||
auto format = prop_info.format;
|
||||
auto length = prop_info.length;
|
||||
|
||||
if (type == XCB_ATOM_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!x_is_type_string(atoms, type)) {
|
||||
log_warn("Text property %d of window %#010x has unsupported type: %d",
|
||||
prop, wid, type);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (format != 8) {
|
||||
log_warn("Text property %d of window %#010x has unexpected format: %d",
|
||||
prop, wid, format);
|
||||
return false;
|
||||
}
|
||||
|
||||
xcb_generic_error_t *e = NULL;
|
||||
auto word_count = (length + 4 - 1) / 4;
|
||||
auto r = xcb_get_property_reply(
|
||||
c->c, xcb_get_property(c->c, 0, wid, prop, type, 0, word_count), &e);
|
||||
c->c, xcb_get_property(c->c, 0, wid, prop, XCB_ATOM_ANY, 0, UINT_MAX), &e);
|
||||
if (!r) {
|
||||
log_debug_x_error(e, "Failed to get window property for %#010x", wid);
|
||||
free(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(length == (uint32_t)xcb_get_property_value_length(r));
|
||||
if (r->type == XCB_ATOM_NONE) {
|
||||
free(r);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!x_is_type_string(atoms, r->type)) {
|
||||
log_warn("Text property %d of window %#010x has unsupported type: %d",
|
||||
prop, wid, r->type);
|
||||
free(r);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (r->format != 8) {
|
||||
log_warn("Text property %d of window %#010x has unexpected format: %d",
|
||||
prop, wid, r->format);
|
||||
free(r);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t length = to_u32_checked(xcb_get_property_value_length(r));
|
||||
void *data = xcb_get_property_value(r);
|
||||
unsigned int nstr = 0;
|
||||
uint32_t current_offset = 0;
|
||||
|
@ -904,25 +900,43 @@ struct xvisual_info x_get_visual_info(struct x_connection *c, xcb_visualid_t vis
|
|||
};
|
||||
}
|
||||
|
||||
void x_update_monitors(struct x_connection *c, struct x_monitors *m) {
|
||||
x_free_monitor_info(m);
|
||||
struct x_update_monitors_request {
|
||||
struct x_async_request_base base;
|
||||
struct x_monitors *monitors;
|
||||
};
|
||||
|
||||
xcb_randr_get_monitors_reply_t *r = xcb_randr_get_monitors_reply(
|
||||
c->c, xcb_randr_get_monitors(c->c, c->screen_info->root, true), NULL);
|
||||
if (!r) {
|
||||
static void x_handle_update_monitors_reply(struct x_connection * /*c*/,
|
||||
struct x_async_request_base *req_base,
|
||||
xcb_raw_generic_event_t *reply_or_error) {
|
||||
auto m = ((struct x_update_monitors_request *)req_base)->monitors;
|
||||
free(req_base);
|
||||
|
||||
if (reply_or_error->response_type == 0) {
|
||||
log_warn("Failed to get monitor information using RandR: %s",
|
||||
x_strerror((xcb_generic_error_t *)reply_or_error));
|
||||
return;
|
||||
}
|
||||
|
||||
m->count = xcb_randr_get_monitors_monitors_length(r);
|
||||
x_free_monitor_info(m);
|
||||
|
||||
auto reply = (xcb_randr_get_monitors_reply_t *)reply_or_error;
|
||||
|
||||
m->count = xcb_randr_get_monitors_monitors_length(reply);
|
||||
m->regions = ccalloc(m->count, region_t);
|
||||
xcb_randr_monitor_info_iterator_t monitor_info_it =
|
||||
xcb_randr_get_monitors_monitors_iterator(r);
|
||||
xcb_randr_get_monitors_monitors_iterator(reply);
|
||||
for (int i = 0; monitor_info_it.rem; xcb_randr_monitor_info_next(&monitor_info_it)) {
|
||||
xcb_randr_monitor_info_t *mi = monitor_info_it.data;
|
||||
pixman_region32_init_rect(&m->regions[i++], mi->x, mi->y, mi->width, mi->height);
|
||||
}
|
||||
}
|
||||
|
||||
free(r);
|
||||
void x_update_monitors_async(struct x_connection *c, struct x_monitors *m) {
|
||||
auto req = ccalloc(1, struct x_update_monitors_request);
|
||||
req->base.callback = x_handle_update_monitors_reply;
|
||||
req->base.sequence = xcb_randr_get_monitors(c->c, c->screen_info->root, 1).sequence;
|
||||
req->monitors = m;
|
||||
x_await_request(c, &req->base);
|
||||
}
|
||||
|
||||
void x_free_monitor_info(struct x_monitors *m) {
|
||||
|
|
4
src/x.h
4
src/x.h
|
@ -419,8 +419,8 @@ xcb_visualid_t x_get_visual_for_depth(xcb_screen_t *screen, uint8_t depth);
|
|||
xcb_render_pictformat_t
|
||||
x_get_pictfmt_for_standard(struct x_connection *c, xcb_pict_standard_t std);
|
||||
|
||||
/// Populates a `struct x_monitors` with the current monitor configuration.
|
||||
void x_update_monitors(struct x_connection *, struct x_monitors *);
|
||||
/// Populates a `struct x_monitors` with the current monitor configuration asynchronously.
|
||||
void x_update_monitors_async(struct x_connection *, struct x_monitors *);
|
||||
/// Free memory allocated for a `struct x_monitors`.
|
||||
void x_free_monitor_info(struct x_monitors *);
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue