1
0
Fork 0
mirror of https://github.com/yshui/picom.git synced 2024-10-27 05:24:17 -04:00

win: use the new transition mechanism for fading

Fading behavior is changed slightly when a window's opacity is changed
while the window is already fading in/out.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
This commit is contained in:
Yuxuan Shui 2024-03-19 16:44:02 +00:00
parent 5a5716a7b1
commit 35b85155c5
No known key found for this signature in database
GPG key ID: D3A4405BE6CC17F4
6 changed files with 110 additions and 146 deletions

View file

@ -10,6 +10,7 @@
#include "config.h"
#include "log.h"
#include "region.h"
#include "transition.h"
#include "types.h"
#include "win.h"
#include "x.h"
@ -253,45 +254,20 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) {
auto real_win_mode = w->mode;
coord_t window_coord = {.x = w->g.x, .y = w->g.y};
const double window_opacity = animatable_get(&w->opacity);
if (w->blur_background &&
(ps->o.force_win_blend || real_win_mode == WMODE_TRANS ||
(ps->o.blur_background_frame && real_win_mode == WMODE_FRAME_TRANS))) {
// Minimize the region we try to blur, if the window
// itself is not opaque, only the frame is.
double blur_opacity = 1;
if (w->opacity < (1.0 / MAX_ALPHA)) {
// Hide blur for fully transparent windows.
blur_opacity = 0;
} else if (w->state == WSTATE_MAPPING) {
// Gradually increase the blur intensity during
// fading in.
assert(w->opacity <= w->opacity_target);
blur_opacity = w->opacity / w->opacity_target;
} else if (w->state == WSTATE_UNMAPPING ||
w->state == WSTATE_DESTROYING) {
// Gradually decrease the blur intensity during
// fading out.
assert(w->opacity <= w->opacity_target_old);
blur_opacity = w->opacity / w->opacity_target_old;
} else if (w->state == WSTATE_FADING) {
if (w->opacity < w->opacity_target &&
w->opacity_target_old < (1.0 / MAX_ALPHA)) {
// Gradually increase the blur intensity during
// fading in.
assert(w->opacity <= w->opacity_target);
blur_opacity = w->opacity / w->opacity_target;
} else if (w->opacity > w->opacity_target &&
w->opacity_target < (1.0 / MAX_ALPHA)) {
// Gradually decrease the blur intensity during
// fading out.
assert(w->opacity <= w->opacity_target_old);
blur_opacity = w->opacity / w->opacity_target_old;
}
}
const double blur_opacity = animatable_get(&w->blur_opacity);
assert(blur_opacity >= 0 && blur_opacity <= 1);
if (real_win_mode == WMODE_TRANS || ps->o.force_win_blend) {
if (blur_opacity * MAX_ALPHA < 1) {
// We don't need to blur if it would be completely
// transparent
} else if (real_win_mode == WMODE_TRANS || ps->o.force_win_blend) {
// We need to blur the bounding shape of the window
// (reg_paint_in_bound = reg_bound \cap reg_paint)
ps->backend_data->ops->blur(
@ -404,7 +380,7 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) {
if (w->dim) {
dim_opacity = ps->o.inactive_dim;
if (!ps->o.inactive_dim_fixed) {
dim_opacity *= w->opacity;
dim_opacity *= window_opacity;
}
}
@ -418,7 +394,8 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) {
ps->backend_data, IMAGE_PROPERTY_DIM_LEVEL, w->win_image,
&dim_opacity);
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_OPACITY, w->win_image, &w->opacity);
ps->backend_data, IMAGE_PROPERTY_OPACITY, w->win_image,
&window_opacity);
ps->backend_data->ops->set_image_property(
ps->backend_data, IMAGE_PROPERTY_CORNER_RADIUS, w->win_image,
(double[]){w->corner_radius});
@ -440,7 +417,7 @@ bool paint_all_new(session_t *ps, struct managed_win *const t) {
w->fg_shader ? (void *)w->fg_shader->backend_shader : NULL);
}
if (w->opacity * MAX_ALPHA < 1) {
if (window_opacity * MAX_ALPHA < 1) {
// We don't need to paint the window body itself if it's
// completely transparent.
goto skip;

View file

@ -24,6 +24,7 @@
#include "list.h"
#include "log.h"
#include "string_utils.h"
#include "transition.h"
#include "types.h"
#include "uthash_extra.h"
#include "utils.h"
@ -991,14 +992,17 @@ static bool cdbus_process_win_get(session_t *ps, DBusMessage *msg) {
cdbus_m_win_get_do(class_general, cdbus_reply_string);
cdbus_m_win_get_do(role, cdbus_reply_string);
cdbus_m_win_get_do(opacity, cdbus_reply_double);
cdbus_m_win_get_do(opacity_target, cdbus_reply_double);
cdbus_m_win_get_do(opacity.target, cdbus_reply_double);
cdbus_m_win_get_do(has_opacity_prop, cdbus_reply_bool);
cdbus_m_win_get_do(opacity_prop, cdbus_reply_uint32);
cdbus_m_win_get_do(opacity_is_set, cdbus_reply_bool);
cdbus_m_win_get_do(opacity_set, cdbus_reply_double);
cdbus_m_win_get_do(frame_opacity, cdbus_reply_double);
if (strcmp(target, "opacity") == 0) {
cdbus_reply_double(ps, msg, animatable_get(&w->opacity));
return true;
}
if (!strcmp("left_width", target)) {
cdbus_reply_int32(ps, msg, w->frame_extents.left);
return true;

View file

@ -48,6 +48,7 @@
#include "inspect.h"
#include "kernel.h"
#include "picom.h"
#include "transition.h"
#include "win_defs.h"
#ifdef CONFIG_OPENGL
#include "opengl.h"
@ -495,22 +496,25 @@ static double fade_timeout(session_t *ps) {
* @param steps steps of fading
* @return whether we are still in fading mode
*/
static bool run_fade(session_t *ps, struct managed_win **_w, long long steps) {
static bool run_fade(session_t *ps, struct managed_win **_w, unsigned int steps) {
auto w = *_w;
log_trace("Process fading for window %s (%#010x), steps: %lld", w->name,
w->base.id, steps);
log_trace("Process fading for window %s (%#010x), steps: %u", w->name, w->base.id,
steps);
if (w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPED) {
// We are not fading
assert(w->opacity_target == w->opacity);
assert(!animatable_is_animating(&w->opacity));
assert(!animatable_is_animating(&w->blur_opacity));
log_trace("|- not fading");
return false;
}
if (!win_should_fade(ps, w)) {
log_trace("|- in transition but doesn't need fading");
w->opacity = w->opacity_target;
animatable_early_stop(&w->opacity);
animatable_early_stop(&w->blur_opacity);
}
if (w->opacity == w->opacity_target) {
if (!animatable_is_animating(&w->opacity) &&
!animatable_is_animating(&w->blur_opacity)) {
// We have reached target opacity.
// We don't call win_check_fade_finished here because that could destroy
// the window, but we still need the damage info from this window
@ -518,20 +522,14 @@ static bool run_fade(session_t *ps, struct managed_win **_w, long long steps) {
return false;
}
log_trace("|- fading, opacity: %lf", w->opacity);
if (steps) {
if (w->opacity < w->opacity_target) {
w->opacity = clamp(w->opacity + ps->o.fade_in_step * (double)steps,
0.0, w->opacity_target);
} else {
w->opacity = clamp(w->opacity - ps->o.fade_out_step * (double)steps,
w->opacity_target, 1);
}
log_trace("|- opacity updated: %lf", w->opacity);
}
log_trace("|- fading, opacity: %lf", animatable_get(&w->opacity));
animatable_step(&w->opacity, steps);
animatable_step(&w->blur_opacity, steps);
log_trace("|- opacity updated: %lf (%u steps)", animatable_get(&w->opacity), steps);
// Note even if opacity == opacity_target here, we still want to run preprocess
// one last time to finish state transition. So return true in that case too.
// Note even if the animatable is not animating anymore at this point, we still
// want to run preprocess one last time to finish state transition. So return true
// in that case too.
return true;
}
@ -918,17 +916,19 @@ static bool paint_preprocess(session_t *ps, bool *fade_running, bool *animation,
*out_bottom = NULL;
// Fading step calculation
long long steps = 0L;
unsigned int steps = 0L;
auto now = get_time_ms();
if (ps->fade_time) {
assert(now >= ps->fade_time);
steps = (now - ps->fade_time) / ps->o.fade_delta;
auto raw_steps = (now - ps->fade_time) / ps->o.fade_delta;
assert(raw_steps <= UINT_MAX);
steps = (unsigned int)raw_steps;
ps->fade_time += raw_steps * ps->o.fade_delta;
} else {
// Reset fade_time if unset
ps->fade_time = get_time_ms();
steps = 0L;
}
ps->fade_time += steps * ps->o.fade_delta;
// First, let's process fading, and animated shaders
// TODO(yshui) check if a window is fully obscured, and if we don't need to
@ -936,7 +936,6 @@ static bool paint_preprocess(session_t *ps, bool *fade_running, bool *animation,
win_stack_foreach_managed_safe(w, &ps->window_stack) {
const winmode_t mode_old = w->mode;
const bool was_painted = w->to_paint;
const double opacity_old = w->opacity;
if (win_should_dim(ps, w) != w->dim) {
w->dim = win_should_dim(ps, w);
@ -948,20 +947,20 @@ static bool paint_preprocess(session_t *ps, bool *fade_running, bool *animation,
*animation = true;
}
// Add window to damaged area if its opacity changes
// If was_painted == false, and to_paint is also false, we don't care
// If was_painted == false, but to_paint is true, damage will be added in
// the loop below
if (was_painted && animatable_is_animating(&w->opacity)) {
add_damage_from_win(ps, w);
}
// Run fading
if (run_fade(ps, &w, steps)) {
*fade_running = true;
}
// Add window to damaged area if its opacity changes
// If was_painted == false, and to_paint is also false, we don't care
// If was_painted == false, but to_paint is true, damage will be added in
// the loop below
if (was_painted && w->opacity != opacity_old) {
add_damage_from_win(ps, w);
}
if (win_check_fade_finished(ps, w)) {
if (win_maybe_finalize_fading(ps, w)) {
// the window has been destroyed because fading finished
continue;
}
@ -994,6 +993,7 @@ static bool paint_preprocess(session_t *ps, bool *fade_running, bool *animation,
bool to_paint = true;
// w->to_paint remembers whether this window is painted last time
const bool was_painted = w->to_paint;
const double window_opacity = animatable_get(&w->opacity);
// Destroy reg_ignore if some window above us invalidated it
if (!reg_ignore_valid) {
@ -1027,7 +1027,7 @@ static bool paint_preprocess(session_t *ps, bool *fade_running, bool *animation,
w->g.x >= ps->root_width || w->g.y >= ps->root_height)) {
log_trace("|- is positioned outside of the screen");
to_paint = false;
} else if (unlikely((double)w->opacity * MAX_ALPHA < 1 && !w->blur_background)) {
} else if (unlikely(window_opacity * MAX_ALPHA < 1 && !w->blur_background)) {
/* TODO(yshui) for consistency, even a window has 0 opacity, we
* still probably need to blur its background, so to_paint
* shouldn't be false for them. */
@ -1060,7 +1060,7 @@ static bool paint_preprocess(session_t *ps, bool *fade_running, bool *animation,
log_verbose("Window %#010x (%s) will be painted", w->base.id, w->name);
// Calculate shadow opacity
w->shadow_opacity = ps->o.shadow_opacity * w->opacity * ps->o.frame_opacity;
w->shadow_opacity = ps->o.shadow_opacity * window_opacity * ps->o.frame_opacity;
// Generate ignore region for painting to reduce GPU load
if (!w->reg_ignore) {

View file

@ -12,6 +12,7 @@
#include "common.h"
#include "options.h"
#include "transition.h"
#ifdef CONFIG_OPENGL
#include "backend/gl/glx.h"
@ -436,6 +437,7 @@ void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint)
const int y = w->g.y;
const uint16_t wid = to_u16_checked(w->widthb);
const uint16_t hei = to_u16_checked(w->heightb);
const double window_opacity = animatable_get(&w->opacity);
xcb_render_picture_t pict = w->paint.pict;
@ -472,7 +474,7 @@ void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint)
}
if (w->frame_opacity == 1) {
paint_region(ps, w, 0, 0, wid, hei, w->opacity, reg_paint, pict);
paint_region(ps, w, 0, 0, wid, hei, window_opacity, reg_paint, pict);
} else {
// Painting parameters
const margin_t extents = win_calc_frame_extents(w);
@ -482,8 +484,8 @@ void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint)
auto const r = extents.right;
#define COMP_BDR(cx, cy, cwid, chei) \
paint_region(ps, w, (cx), (cy), (cwid), (chei), w->frame_opacity * w->opacity, \
reg_paint, pict)
paint_region(ps, w, (cx), (cy), (cwid), (chei), \
w->frame_opacity *window_opacity, reg_paint, pict)
// Sanitize the margins, in case some broken WM makes
// top_width + bottom_width > height in some cases.
@ -542,7 +544,7 @@ void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint)
// body
paint_region(ps, w, cleft, ctop, body_width, body_height,
w->opacity, reg_paint, pict);
window_opacity, reg_paint, pict);
} while (0);
}
@ -557,7 +559,7 @@ void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint)
if (w->dim) {
double dim_opacity = ps->o.inactive_dim;
if (!ps->o.inactive_dim_fixed) {
dim_opacity *= w->opacity;
dim_opacity *= window_opacity;
}
switch (ps->o.backend) {
@ -899,12 +901,13 @@ win_blur_background(session_t *ps, struct managed_win *w, xcb_render_picture_t t
auto const wid = to_u16_checked(w->widthb);
auto const hei = to_u16_checked(w->heightb);
const int cr = w ? w->corner_radius : 0;
const double window_opacity = animatable_get(&w->opacity);
double factor_center = 1.0;
// Adjust blur strength according to window opacity, to make it appear
// better during fading
if (!ps->o.blur_background_fixed) {
double pct = 1.0 - w->opacity * (1.0 - 1.0 / 9.0);
double pct = 1.0 - window_opacity * (1.0 - 1.0 / 9.0);
factor_center = pct * 8.0 / (1.1 - pct);
}
@ -1143,7 +1146,7 @@ void paint_all(session_t *ps, struct managed_win *t) {
}
// Only clip shadows above visible windows
if (w->opacity * MAX_ALPHA >= 1) {
if (animatable_get(&w->opacity) * MAX_ALPHA >= 1) {
if (w->clip_shadow_above) {
// Add window bounds to shadow-clip region
pixman_region32_union(&reg_shadow_clip, &reg_shadow_clip,

View file

@ -27,6 +27,7 @@
#include "region.h"
#include "render.h"
#include "string_utils.h"
#include "transition.h"
#include "types.h"
#include "uthash_extra.h"
#include "utils.h"
@ -799,7 +800,7 @@ bool win_client_has_alpha(const struct managed_win *w) {
}
winmode_t win_calc_mode(const struct managed_win *w) {
if (w->opacity < 1.0) {
if (animatable_get(&w->opacity) < 1.0) {
return WMODE_TRANS;
}
@ -846,7 +847,7 @@ winmode_t win_calc_mode(const struct managed_win *w) {
*
* @return target opacity
*/
double win_calc_opacity_target(session_t *ps, const struct managed_win *w) {
static double win_calc_opacity_target(session_t *ps, const struct managed_win *w) {
double opacity = 1;
if (w->state == WSTATE_UNMAPPED) {
@ -882,6 +883,21 @@ double win_calc_opacity_target(session_t *ps, const struct managed_win *w) {
return opacity;
}
/// Call `animatable_set_target` on the opacity of a window, with appropriate
/// target opacity and duration.
///
/// @return duration of the fade
static inline unsigned int win_start_fade(session_t *ps, struct managed_win *w) {
double current_opacity = animatable_get(&w->opacity),
target_opacity = win_calc_opacity_target(ps, w);
double step_size =
target_opacity > current_opacity ? ps->o.fade_in_step : ps->o.fade_out_step;
unsigned int duration =
(unsigned int)(fabs(target_opacity - current_opacity) / step_size);
animatable_set_target(&w->opacity, target_opacity, duration);
return duration;
}
/**
* Determine whether a window is to be dimmed.
*/
@ -1605,8 +1621,6 @@ struct win *attr_ret_nonnull maybe_allocate_managed_win(session_t *ps, struct wi
.cache_leader = XCB_NONE,
.window_type = WINTYPE_UNKNOWN,
.focused = false,
.opacity = 0,
.opacity_target = 0,
.has_opacity_prop = false,
.opacity_prop = OPAQUE,
.opacity_is_set = false,
@ -1685,6 +1699,8 @@ struct win *attr_ret_nonnull maybe_allocate_managed_win(session_t *ps, struct wi
new->base = *w;
new->base.managed = true;
new->a = *a;
new->opacity = animatable_new(0, linear_interpolator, NULL);
new->blur_opacity = animatable_new(0, linear_interpolator, NULL);
pixman_region32_init(&new->bounding_shape);
free(a);
@ -2386,8 +2402,8 @@ void unmap_win_start(session_t *ps, struct managed_win *w) {
w->a.map_state = XCB_MAP_STATE_UNMAPPED;
w->state = WSTATE_UNMAPPING;
w->opacity_target_old = fmax(w->opacity_target, w->opacity_target_old);
w->opacity_target = win_calc_opacity_target(ps, w);
auto duration = win_start_fade(ps, w);
animatable_set_target(&w->blur_opacity, 0, duration);
#ifdef CONFIG_DBUS
// Send D-Bus signal
@ -2410,13 +2426,13 @@ void unmap_win_start(session_t *ps, struct managed_win *w) {
*
* @return whether the window is destroyed and freed
*/
bool win_check_fade_finished(session_t *ps, struct managed_win *w) {
bool win_maybe_finalize_fading(session_t *ps, struct managed_win *w) {
if (w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPED) {
// No fading in progress
assert(w->opacity_target == w->opacity);
assert(!animatable_is_animating(&w->opacity));
return false;
}
if (w->opacity == w->opacity_target) {
if (!animatable_is_animating(&w->opacity)) {
switch (w->state) {
case WSTATE_UNMAPPING: unmap_win_finish(ps, w); return false;
case WSTATE_DESTROYING: destroy_win_finish(ps, &w->base); return true;
@ -2435,12 +2451,13 @@ bool win_check_fade_finished(session_t *ps, struct managed_win *w) {
/// @return whether the window is destroyed and freed
bool win_skip_fading(session_t *ps, struct managed_win *w) {
if (w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPED) {
assert(w->opacity_target == w->opacity);
assert(!animatable_is_animating(&w->opacity));
return false;
}
log_debug("Skipping fading process of window %#010x (%s)", w->base.id, w->name);
w->opacity = w->opacity_target;
return win_check_fade_finished(ps, w);
animatable_early_stop(&w->opacity);
animatable_early_stop(&w->blur_opacity);
return win_maybe_finalize_fading(ps, w);
}
// TODO(absolutelynothelix): rename to x_update_win_(randr_?)monitor and move to
@ -2514,11 +2531,11 @@ void map_win_start(session_t *ps, struct managed_win *w) {
// XXX We need to make sure that win_data is available
// iff `state` is MAPPED
w->state = WSTATE_MAPPING;
w->opacity_target_old = 0;
w->opacity_target = win_calc_opacity_target(ps, w);
auto duration = win_start_fade(ps, w);
animatable_set_target(&w->blur_opacity, 1, duration);
log_debug("Window %#010x has opacity %f, opacity target is %f", w->base.id,
w->opacity, w->opacity_target);
animatable_get(&w->opacity), w->opacity.target);
// Cannot set w->ever_damaged = false here, since window mapping could be
// delayed, so a damage event might have already arrived before this
@ -2546,49 +2563,25 @@ void map_win_start(session_t *ps, struct managed_win *w) {
* Update target window opacity depending on the current state.
*/
void win_update_opacity_target(session_t *ps, struct managed_win *w) {
auto opacity_target_old = w->opacity_target;
w->opacity_target = win_calc_opacity_target(ps, w);
auto duration = win_start_fade(ps, w);
if (opacity_target_old == w->opacity_target) {
if (!animatable_is_animating(&w->opacity)) {
return;
}
log_debug("Window %#010x (%s) opacity %f, opacity target %f, start %f", w->base.id,
w->name, animatable_get(&w->opacity), w->opacity.target, w->opacity.start);
if (w->state == WSTATE_MAPPED) {
// Opacity target changed while MAPPED. Transition to FADING.
assert(w->opacity == opacity_target_old);
w->opacity_target_old = opacity_target_old;
w->state = WSTATE_FADING;
log_debug("Window %#010x (%s) opacity %f, opacity target %f, set "
"old target %f",
w->base.id, w->name, w->opacity, w->opacity_target,
w->opacity_target_old);
} else if (w->state == WSTATE_MAPPING) {
// Opacity target changed while fading in.
if (w->opacity >= w->opacity_target) {
// Already reached new target opacity. Transition to
// FADING.
map_win_finish(w);
w->opacity_target_old = fmax(opacity_target_old, w->opacity);
w->state = WSTATE_FADING;
log_debug("Window %#010x (%s) opacity %f already reached "
"new opacity target %f while mapping, set old "
"target %f",
w->base.id, w->name, w->opacity, w->opacity_target,
w->opacity_target_old);
}
// Opacity target changed while fading in, keep the blur_opacity
// in lock step with the opacity
animatable_set_target(&w->blur_opacity, w->blur_opacity.target, duration);
log_debug("Opacity changed while fading in");
} else if (w->state == WSTATE_FADING) {
// Opacity target changed while FADING.
if ((w->opacity < opacity_target_old && w->opacity > w->opacity_target) ||
(w->opacity > opacity_target_old && w->opacity < w->opacity_target)) {
// Changed while fading in and will fade out or while
// fading out and will fade in.
w->opacity_target_old = opacity_target_old;
log_debug("Window %#010x (%s) opacity %f already reached "
"new opacity target %f while fading, set "
"old target %f",
w->base.id, w->name, w->opacity, w->opacity_target,
w->opacity_target_old);
}
log_debug("Opacity changed while already fading");
}
if (!ps->redirected) {

View file

@ -16,6 +16,7 @@
#include "list.h"
#include "region.h"
#include "render.h"
#include "transition.h"
#include "types.h"
#include "utils.h"
#include "win_defs.h"
@ -204,12 +205,12 @@ struct managed_win {
bool is_ewmh_focused;
// Opacity-related members
/// Current window opacity.
double opacity;
/// Target window opacity.
double opacity_target;
/// Previous window opacity.
double opacity_target_old;
/// Window opacity
struct animatable opacity;
/// Opacity of the window's background blur
/// Used to gracefully fade in/out the window, otherwise the blur
/// would be at full/zero intensity immediately which will be jarring.
struct animatable blur_opacity;
/// true if window (or client window, for broken window managers
/// not transferring client window's _NET_WM_WINDOW_OPACITY value) has opacity
/// prop
@ -326,20 +327,6 @@ 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);
/**
* Calculate and return the opacity target of a window.
*
* The priority of opacity settings are:
*
* inactive_opacity_override (if set, and unfocused) > _NET_WM_WINDOW_OPACITY (if set) >
* opacity-rules (if matched) > window type default opacity > active/inactive opacity
*
* @param ps current session
* @param w struct _win object representing the window
*
* @return target opacity
*/
double attr_pure win_calc_opacity_target(session_t *ps, const struct managed_win *w);
bool attr_pure win_should_dim(session_t *ps, const struct managed_win *w);
void win_update_monitor(struct x_monitors *monitors, struct managed_win *mw);
@ -396,7 +383,7 @@ void restack_top(session_t *ps, struct win *w);
/**
* Execute fade callback of a window if fading finished.
*/
bool must_use win_check_fade_finished(session_t *ps, struct managed_win *w);
bool must_use win_maybe_finalize_fading(session_t *ps, struct managed_win *w);
// Stop receiving events (except ConfigureNotify, XXX why?) from a window
void win_ev_stop(session_t *ps, const struct win *w);