diff options
| author | Allusive_ <[email protected]> | 2023-08-01 09:00:18 +1000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-08-01 09:00:18 +1000 |
| commit | 0147e7dd165f837662a480938e2083a5ac707586 (patch) | |
| tree | 3e38624c86c7119703cddb524ac01278491c8a3f /src/win.c | |
| parent | Add files via upload (diff) | |
| download | compfy-0147e7dd165f837662a480938e2083a5ac707586.tar.xz compfy-0147e7dd165f837662a480938e2083a5ac707586.zip | |
Add files via upload
Diffstat (limited to 'src/win.c')
| -rw-r--r-- | src/win.c | 3116 |
1 files changed, 3116 insertions, 0 deletions
diff --git a/src/win.c b/src/win.c new file mode 100644 index 0000000..971bdc9 --- /dev/null +++ b/src/win.c @@ -0,0 +1,3116 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2011-2013, Christopher Jeffrey +// Copyright (c) 2013 Richard Grenville <[email protected]> + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <inttypes.h> +#include <math.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <xcb/composite.h> +#include <xcb/damage.h> +#include <xcb/render.h> +#include <xcb/xcb.h> +#include <xcb/xcb_renderutil.h> +#include <xcb/xinerama.h> + +#include "atom.h" +#include "backend/backend.h" +#include "backend/backend_common.h" +#include "c2.h" +#include "common.h" +#include "compiler.h" +#include "config.h" +#include "list.h" +#include "log.h" +#include "picom.h" +#include "region.h" +#include "render.h" +#include "string_utils.h" +#include "types.h" +#include "uthash_extra.h" +#include "utils.h" +#include "x.h" + +#ifdef CONFIG_DBUS +#include "dbus.h" +#endif + +#ifdef CONFIG_OPENGL +// TODO(yshui) Get rid of this include +#include "opengl.h" +#endif + +#include "win.h" + +// TODO(yshui) Make more window states internal +struct managed_win_internal { + struct managed_win base; +}; + +#define OPAQUE (0xffffffff) +static const int WIN_GET_LEADER_MAX_RECURSION = 20; +static const int ROUNDED_PIXELS = 1; +static const double ROUNDED_PERCENT = 0.05; + +/** + * Retrieve the <code>WM_CLASS</code> of a window and update its + * <code>win</code> structure. + */ +static bool win_update_class(session_t *ps, struct managed_win *w); +static int win_update_role(session_t *ps, struct managed_win *w); +static void win_update_wintype(session_t *ps, struct managed_win *w); +static int win_update_name(session_t *ps, struct managed_win *w); +/** + * Reread opacity property of a window. + */ +static void win_update_opacity_prop(session_t *ps, struct managed_win *w); +static void win_update_opacity_target(session_t *ps, struct managed_win *w); +/** + * Retrieve frame extents from a window. + */ +static void +win_update_frame_extents(session_t *ps, struct managed_win *w, xcb_window_t client); +static void win_update_prop_shadow_raw(session_t *ps, struct managed_win *w); +static void win_update_prop_shadow(session_t *ps, struct managed_win *w); +/** + * Update leader of a window. + */ +static void win_update_leader(session_t *ps, struct managed_win *w); + +/// Generate a "no corners" region function, from a function that returns the +/// region via a region_t pointer argument. Corners of the window will be removed from +/// the returned region. +/// Function signature has to be (win *, region_t *) +#define gen_without_corners(fun) \ + void fun##_without_corners(const struct managed_win *w, region_t *res) { \ + fun(w, res); \ + win_region_remove_corners(w, res); \ + } + +/// Generate a "return by value" function, from a function that returns the +/// region via a region_t pointer argument. +/// Function signature has to be (win *) +#define gen_by_val(fun) \ + region_t fun##_by_val(const struct managed_win *w) { \ + region_t ret; \ + pixman_region32_init(&ret); \ + fun(w, &ret); \ + return ret; \ + } + +/** + * Clear leader cache of all windows. + */ +static inline void clear_cache_win_leaders(session_t *ps) { + win_stack_foreach_managed(w, &ps->window_stack) { + w->cache_leader = XCB_NONE; + } +} + +static xcb_window_t win_get_leader_raw(session_t *ps, struct managed_win *w, int recursions); + +/** + * Get the leader of a window. + * + * This function updates w->cache_leader if necessary. + */ +static inline xcb_window_t win_get_leader(session_t *ps, struct managed_win *w) { + return win_get_leader_raw(ps, w, 0); +} + +/** + * Whether the real content of the window is visible. + * + * A window is not considered "real" visible if it's fading out. Because in that case a + * cached version of the window is displayed. + */ +static inline bool attr_pure win_is_real_visible(const struct managed_win *w) { + return w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING && + w->state != WSTATE_UNMAPPING; +} + +/** + * Update focused state of a window. + */ +static void win_update_focused(session_t *ps, struct managed_win *w) { + if (UNSET != w->focused_force) { + w->focused = w->focused_force; + } else { + w->focused = win_is_focused_raw(ps, w); + + // Use wintype_focus, and treat WM windows and override-redirected + // windows specially + if (ps->o.wintype_option[w->window_type].focus || + (ps->o.mark_wmwin_focused && w->wmwin) || + (ps->o.mark_ovredir_focused && w->base.id == w->client_win && !w->wmwin) || + (w->a.map_state == XCB_MAP_STATE_VIEWABLE && + c2_match(ps, w, ps->o.focus_blacklist, NULL))) { + w->focused = true; + } + + // If window grouping detection is enabled, mark the window active if + // its group is + if (ps->o.track_leader && ps->active_leader && + win_get_leader(ps, w) == ps->active_leader) { + w->focused = true; + } + } +} + +/** + * Run win_on_factor_change() on all windows with the same leader window. + * + * @param leader leader window ID + */ +static inline void group_on_factor_change(session_t *ps, xcb_window_t leader) { + if (!leader) { + return; + } + + HASH_ITER2(ps->windows, w) { + assert(!w->destroyed); + if (!w->managed) { + continue; + } + auto mw = (struct managed_win *)w; + if (win_get_leader(ps, mw) == leader) { + win_on_factor_change(ps, mw); + } + } +} + +static inline const char *win_get_name_if_managed(const struct win *w) { + if (!w->managed) { + return "(unmanaged)"; + } + auto mw = (struct managed_win *)w; + return mw->name; +} + +/** + * Return whether a window group is really focused. + * + * @param leader leader window ID + * @return true if the window group is focused, false otherwise + */ +static inline bool group_is_focused(session_t *ps, xcb_window_t leader) { + if (!leader) { + return false; + } + + HASH_ITER2(ps->windows, w) { + assert(!w->destroyed); + if (!w->managed) { + continue; + } + auto mw = (struct managed_win *)w; + if (win_get_leader(ps, mw) == leader && win_is_focused_raw(ps, mw)) { + return true; + } + } + + return false; +} + +/** + * Get a rectangular region a window occupies, excluding shadow. + */ +static void win_get_region_local(const struct managed_win *w, region_t *res) { + assert(w->widthb >= 0 && w->heightb >= 0); + pixman_region32_fini(res); + pixman_region32_init_rect(res, 0, 0, (uint)w->widthb, (uint)w->heightb); +} + +/** + * Get a rectangular region a window occupies, excluding frame and shadow. + */ +void win_get_region_noframe_local(const struct managed_win *w, region_t *res) { + const margin_t extents = win_calc_frame_extents(w); + + int x = extents.left; + int y = extents.top; + int width = max2(w->widthb - (extents.left + extents.right), 0); + int height = max2(w->heightb - (extents.top + extents.bottom), 0); + + pixman_region32_fini(res); + if (width > 0 && height > 0) { + pixman_region32_init_rect(res, x, y, (uint)width, (uint)height); + } else { + pixman_region32_init(res); + } +} + +gen_without_corners(win_get_region_noframe_local); + +void win_get_region_frame_local(const struct managed_win *w, region_t *res) { + const margin_t extents = win_calc_frame_extents(w); + auto outer_width = w->widthb; + auto outer_height = w->heightb; + + pixman_region32_fini(res); + pixman_region32_init_rects( + res, + (rect_t[]){ + // top + {.x1 = 0, .y1 = 0, .x2 = outer_width, .y2 = extents.top}, + // bottom + {.x1 = 0, .y1 = outer_height - extents.bottom, .x2 = outer_width, .y2 = outer_height}, + // left + {.x1 = 0, .y1 = 0, .x2 = extents.left, .y2 = outer_height}, + // right + {.x1 = outer_width - extents.right, .y1 = 0, .x2 = outer_width, .y2 = outer_height}, + }, + 4); + + // limit the frame region to inside the window + region_t reg_win; + pixman_region32_init_rects(®_win, (rect_t[]){{0, 0, outer_width, outer_height}}, 1); + pixman_region32_intersect(res, ®_win, res); + pixman_region32_fini(®_win); +} + +gen_by_val(win_get_region_frame_local); + +/** + * Add a window to damaged area. + * + * @param ps current session + * @param w struct _win element representing the window + */ +void add_damage_from_win(session_t *ps, const struct managed_win *w) { + // XXX there was a cached extents region, investigate + // if that's better + + // TODO(yshui) use the bounding shape when the window is shaped, otherwise the + // damage would be excessive + region_t extents; + pixman_region32_init(&extents); + win_extents(w, &extents); + add_damage(ps, &extents); + pixman_region32_fini(&extents); +} + +/// Release the images attached to this window +static inline void win_release_pixmap(backend_t *base, struct managed_win *w) { + log_debug("Releasing pixmap of window %#010x (%s)", w->base.id, w->name); + assert(w->win_image); + if (w->win_image) { + 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; + } +} +static inline void win_release_oldpixmap(backend_t *base, struct managed_win *w) { + log_debug("Releasing old_pixmap of window %#010x (%s)", w->base.id, w->name); + if (w->old_win_image) { + base->ops->release_image(base, w->old_win_image); + w->old_win_image = NULL; + } +} +static inline void win_release_shadow(backend_t *base, struct managed_win *w) { + log_debug("Releasing shadow of window %#010x (%s)", w->base.id, w->name); + assert(w->shadow_image); + if (w->shadow_image) { + base->ops->release_image(base, w->shadow_image); + w->shadow_image = NULL; + // Bypassing win_set_flags, because `w` might have been destroyed + w->flags |= WIN_FLAGS_SHADOW_NONE; + } +} + +static inline bool win_bind_pixmap(struct backend_base *b, struct managed_win *w) { + assert(!w->win_image); + auto pixmap = x_new_id(b->c); + auto e = xcb_request_check( + b->c, xcb_composite_name_window_pixmap_checked(b->c, w->base.id, pixmap)); + if (e) { + log_error("Failed to get named pixmap for window %#010x(%s)", w->base.id, + w->name); + free(e); + return false; + } + log_debug("New named pixmap for %#010x (%s) : %#010x", w->base.id, w->name, pixmap); + w->win_image = + b->ops->bind_pixmap(b, pixmap, x_get_visual_info(b->c, w->a.visual), true); + if (!w->win_image) { + log_error("Failed to bind pixmap"); + win_set_flags(w, WIN_FLAGS_IMAGE_ERROR); + return false; + } + + win_clear_flags(w, WIN_FLAGS_PIXMAP_NONE); + return true; +} + +bool win_bind_shadow(struct backend_base *b, struct managed_win *w, struct color c, + struct conv *kernel) { + assert(!w->shadow_image); + assert(w->shadow); + w->shadow_image = b->ops->render_shadow(b, w->widthb, w->heightb, kernel, c.red, + c.green, c.blue, c.alpha); + if (!w->shadow_image) { + log_error("Failed to bind shadow image, shadow will be disabled for " + "%#010x (%s)", + w->base.id, w->name); + win_set_flags(w, WIN_FLAGS_SHADOW_NONE); + w->shadow = false; + return false; + } + + log_debug("New shadow for %#010x (%s)", w->base.id, w->name); + win_clear_flags(w, WIN_FLAGS_SHADOW_NONE); + return true; +} + +void win_release_images(struct backend_base *backend, struct managed_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. + + 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_oldpixmap(backend, w); + } + + if (!win_check_flags_all(w, WIN_FLAGS_SHADOW_NONE)) { + assert(!win_check_flags_all(w, WIN_FLAGS_SHADOW_STALE)); + win_release_shadow(backend, w); + } +} + +/// Returns true if the `prop` property is stale, as well as clears the stale flag. +static bool win_fetch_and_unset_property_stale(struct managed_win *w, xcb_atom_t prop); +/// Returns true if any of the properties are stale, as well as clear all the stale flags. +static void win_clear_all_properties_stale(struct managed_win *w); + +/// Fetch new window properties from the X server, and run appropriate updates. Might set +/// WIN_FLAGS_FACTOR_CHANGED +static void win_update_properties(session_t *ps, struct managed_win *w) { + if (win_fetch_and_unset_property_stale(w, ps->atoms->a_NET_WM_WINDOW_TYPE)) { + win_update_wintype(ps, w); + } + + if (win_fetch_and_unset_property_stale(w, ps->atoms->a_NET_WM_WINDOW_OPACITY)) { + win_update_opacity_prop(ps, w); + // we cannot receive OPACITY change when window has been destroyed + assert(w->state != WSTATE_DESTROYING); + win_update_opacity_target(ps, w); + } + + if (win_fetch_and_unset_property_stale(w, ps->atoms->a_NET_FRAME_EXTENTS)) { + win_update_frame_extents(ps, w, w->client_win); + add_damage_from_win(ps, w); + } + + if (win_fetch_and_unset_property_stale(w, ps->atoms->aWM_NAME) || + win_fetch_and_unset_property_stale(w, ps->atoms->a_NET_WM_NAME)) { + if (win_update_name(ps, w) == 1) { + win_set_flags(w, WIN_FLAGS_FACTOR_CHANGED); + } + } + + if (win_fetch_and_unset_property_stale(w, ps->atoms->aWM_CLASS)) { + if (win_update_class(ps, w)) { + win_set_flags(w, WIN_FLAGS_FACTOR_CHANGED); + } + } + + if (win_fetch_and_unset_property_stale(w, ps->atoms->aWM_WINDOW_ROLE)) { + if (win_update_role(ps, w) == 1) { + win_set_flags(w, WIN_FLAGS_FACTOR_CHANGED); + } + } + + if (win_fetch_and_unset_property_stale(w, ps->atoms->a_COMPTON_SHADOW)) { + win_update_prop_shadow(ps, w); + } + + if (win_fetch_and_unset_property_stale(w, ps->atoms->aWM_CLIENT_LEADER) || + win_fetch_and_unset_property_stale(w, ps->atoms->aWM_TRANSIENT_FOR)) { + win_update_leader(ps, w); + } + + win_clear_all_properties_stale(w); +} + +static void init_animation(session_t *ps, struct managed_win *w) { + enum open_window_animation animation = ps->o.animation_for_open_window; + + w->animation_transient = wid_has_prop(ps, w->client_win, ps->atoms->aWM_TRANSIENT_FOR); + + if (w->window_type != WINTYPE_TOOLTIP && w->animation_transient) + animation = ps->o.animation_for_transient_window; + + if (ps->o.wintype_option[w->window_type].animation < OPEN_WINDOW_ANIMATION_INVALID) + animation = ps->o.wintype_option[w->window_type].animation; + + if (ps->root_desktop_switch_direction != 0) { + if (ps->o.animation_for_workspace_switch_in == OPEN_WINDOW_ANIMATION_AUTO) + animation = OPEN_WINDOW_ANIMATION_SLIDE_IN; + else + animation = ps->o.animation_for_workspace_switch_in; + } + + switch (animation) { + case OPEN_WINDOW_ANIMATION_AUTO: + case OPEN_WINDOW_ANIMATION_NONE: { // No animation + w->animation_center_x = w->pending_g.x + w->pending_g.width * 0.5; + w->animation_center_y = w->pending_g.y + w->pending_g.height * 0.5; + w->animation_w = w->pending_g.width; + w->animation_h = w->pending_g.height; + break; + } + case OPEN_WINDOW_ANIMATION_FLYIN: { // Fly-in from a random point outside the screen + // Compute random point off screen + double angle = 2 * M_PI * ((double)rand() / RAND_MAX); + const double radius = + sqrt(ps->root_width * ps->root_width + ps->root_height * ps->root_height); + + // Set animation + w->animation_center_x = ps->root_width * 0.5 + radius * cos(angle); + w->animation_center_y = ps->root_height * 0.5 + radius * sin(angle); + w->animation_w = 0; + w->animation_h = 0; + break; + } + case OPEN_WINDOW_ANIMATION_ZOOM: { // Zoom-in the image, without changing its location + w->animation_center_x = w->pending_g.x + w->pending_g.width * 0.5; + w->animation_center_y = w->pending_g.y + w->pending_g.height * 0.5; + w->animation_w = 0; + w->animation_h = 0; + break; + } + case OPEN_WINDOW_ANIMATION_SLIDE_UP: { // Slide up the image, without changing its location + w->animation_center_x = w->pending_g.x + w->pending_g.width * 0.5; + w->animation_center_y = w->pending_g.y + w->pending_g.height; + w->animation_w = w->pending_g.width; + w->animation_h = 0; + break; + } + case OPEN_WINDOW_ANIMATION_SLIDE_DOWN: { // Slide down the image, without changing its location + w->animation_center_x = w->pending_g.x + w->pending_g.width * 0.5; + w->animation_center_y = w->pending_g.y; + w->animation_w = w->pending_g.width; + w->animation_h = 0; + break; + } + case OPEN_WINDOW_ANIMATION_SLIDE_LEFT: { // Slide left the image, without changing its location + w->animation_center_x = w->pending_g.x + w->pending_g.width; + w->animation_center_y = w->pending_g.y + w->pending_g.height * 0.5; + w->animation_w = 0; + w->animation_h = w->pending_g.height; + break; + } + case OPEN_WINDOW_ANIMATION_SLIDE_RIGHT: { // Slide right the image, without changing its location + w->animation_center_x = w->pending_g.x; + w->animation_center_y = w->pending_g.y + w->pending_g.height * 0.5; + w->animation_w = 0; + w->animation_h = w->pending_g.height; + break; + } + case OPEN_WINDOW_ANIMATION_SLIDE_IN: { + w->animation_center_x = w->pending_g.x + w->pending_g.width * 0.5; + w->animation_center_y = w->pending_g.y + w->pending_g.height * 0.5 - + ps->root_height * + ((ps->root_desktop_switch_direction < 0 && + ps->root_desktop_switch_direction >= -1) || + ps->root_desktop_switch_direction > 1?1:-1); + w->animation_w = w->pending_g.width; + w->animation_h = w->pending_g.height; + break; + } + case OPEN_WINDOW_ANIMATION_SLIDE_OUT: { + w->animation_dest_center_x = w->pending_g.x + w->pending_g.width * 0.5; + w->animation_dest_center_y = w->pending_g.y + w->pending_g.height * 0.5 - + ps->root_height * + ((ps->root_desktop_switch_direction < 0 && + ps->root_desktop_switch_direction >= -1) || + ps->root_desktop_switch_direction > 1?-1:1); + w->animation_dest_w = w->pending_g.width; + w->animation_dest_h = w->pending_g.height; + break; + } + case OPEN_WINDOW_ANIMATION_INVALID: assert(false); break; + } +} + +static void init_animation_unmap(session_t *ps, struct managed_win *w) { + enum open_window_animation animation; + + if (ps->o.animation_for_unmap_window == OPEN_WINDOW_ANIMATION_AUTO) { + animation = ps->o.animation_for_open_window; + + if (w->window_type != WINTYPE_TOOLTIP && w->animation_transient) + animation = ps->o.animation_for_transient_window; + + if (ps->o.wintype_option[w->window_type].animation < OPEN_WINDOW_ANIMATION_INVALID) + animation = ps->o.wintype_option[w->window_type].animation; + + if (animation == OPEN_WINDOW_ANIMATION_SLIDE_UP) + animation = OPEN_WINDOW_ANIMATION_SLIDE_DOWN; + else if (animation == OPEN_WINDOW_ANIMATION_SLIDE_DOWN) + animation = OPEN_WINDOW_ANIMATION_SLIDE_UP; + else if (animation == OPEN_WINDOW_ANIMATION_SLIDE_LEFT) + animation = OPEN_WINDOW_ANIMATION_SLIDE_RIGHT; + else if (animation == OPEN_WINDOW_ANIMATION_SLIDE_RIGHT) + animation = OPEN_WINDOW_ANIMATION_SLIDE_LEFT; + else if (animation == OPEN_WINDOW_ANIMATION_SLIDE_IN) + animation = OPEN_WINDOW_ANIMATION_SLIDE_OUT; + else if (animation == OPEN_WINDOW_ANIMATION_SLIDE_OUT) + animation = OPEN_WINDOW_ANIMATION_SLIDE_IN; + + } else { + animation = ps->o.animation_for_unmap_window; + + if (ps->o.wintype_option[w->window_type].animation_unmap < OPEN_WINDOW_ANIMATION_INVALID) + animation = ps->o.wintype_option[w->window_type].animation_unmap; + } + + if (ps->root_desktop_switch_direction != 0) { + if (ps->o.animation_for_workspace_switch_out == OPEN_WINDOW_ANIMATION_AUTO) + animation = OPEN_WINDOW_ANIMATION_SLIDE_OUT; + else + animation = ps->o.animation_for_workspace_switch_out; + } + + switch (animation) { + case OPEN_WINDOW_ANIMATION_AUTO: + case OPEN_WINDOW_ANIMATION_NONE: { // No animation + w->animation_dest_center_x = w->pending_g.x + w->pending_g.width * 0.5; + w->animation_dest_center_y = w->pending_g.y + w->pending_g.height * 0.5; + w->animation_dest_w = w->pending_g.width; + w->animation_dest_h = w->pending_g.height; + break; + } + case OPEN_WINDOW_ANIMATION_FLYIN: { // Fly-out from a random point outside the screen + // Compute random point off screen + double angle = 2 * M_PI * ((double)rand() / RAND_MAX); + const double radius = + sqrt(ps->root_width * ps->root_width + ps->root_height * ps->root_height); + + // Set animation + w->animation_dest_center_x = ps->root_width * 0.5 + radius * cos(angle); + w->animation_dest_center_y = ps->root_height * 0.5 + radius * sin(angle); + w->animation_dest_w = 0; + w->animation_dest_h = 0; + break; + } + case OPEN_WINDOW_ANIMATION_ZOOM: { // Zoom-out the image, without changing its location + w->animation_dest_center_x = w->pending_g.x + w->pending_g.width * 0.5; + w->animation_dest_center_y = w->pending_g.y + w->pending_g.height * 0.5; + w->animation_dest_w = 0; + w->animation_dest_h = 0; + break; + } + case OPEN_WINDOW_ANIMATION_SLIDE_UP: { // Slide up the image, without changing its location + w->animation_dest_center_x = w->pending_g.x + w->pending_g.width * 0.5; + w->animation_dest_center_y = w->pending_g.y; + w->animation_dest_w = w->pending_g.width; + w->animation_dest_h = 0; + break; + } + case OPEN_WINDOW_ANIMATION_SLIDE_DOWN: { // Slide down the image, without changing its location + w->animation_dest_center_x = w->pending_g.x + w->pending_g.width * 0.5; + w->animation_dest_center_y = w->pending_g.y + w->pending_g.height; + w->animation_dest_w = w->pending_g.width; + w->animation_dest_h = 0; + break; + } + case OPEN_WINDOW_ANIMATION_SLIDE_LEFT: { // Slide left the image, without changing its location + w->animation_dest_center_x = w->pending_g.x; + w->animation_dest_center_y = w->pending_g.y + w->pending_g.height * 0.5; + w->animation_dest_w = 0; + w->animation_dest_h = w->pending_g.height; + break; + } + case OPEN_WINDOW_ANIMATION_SLIDE_RIGHT: { // Slide right the image, without changing its location + w->animation_dest_center_x = w->pending_g.x + w->pending_g.width; + w->animation_dest_center_y = w->pending_g.y + w->pending_g.height * 0.5; + w->animation_dest_w = 0; + w->animation_dest_h = w->pending_g.height; + break; + } + case OPEN_WINDOW_ANIMATION_SLIDE_IN: { + w->animation_center_x = w->pending_g.x + w->pending_g.width * 0.5; + w->animation_center_y = w->pending_g.y + w->pending_g.height * 0.5 - + ps->root_height * + ((ps->root_desktop_switch_direction < 0 && + ps->root_desktop_switch_direction >= -1) || + ps->root_desktop_switch_direction > 1?1:-1); + w->animation_w = w->pending_g.width; + w->animation_h = w->pending_g.height; + break; + } + case OPEN_WINDOW_ANIMATION_SLIDE_OUT: { + w->animation_dest_center_x = w->pending_g.x + w->pending_g.width * 0.5; + w->animation_dest_center_y = w->pending_g.y + w->pending_g.height * 0.5 - + ps->root_height * + ((ps->root_desktop_switch_direction < 0 && + ps->root_desktop_switch_direction >= -1) || + ps->root_desktop_switch_direction > 1?-1:1); + w->animation_dest_w = w->pending_g.width; + w->animation_dest_h = w->pending_g.height; + break; + } + case OPEN_WINDOW_ANIMATION_INVALID: assert(false); break; + } +} + +/// Handle non-image flags. This phase might set IMAGES_STALE flags +void win_process_update_flags(session_t *ps, struct managed_win *w) { + // Whether the window was visible before we process the mapped flag. i.e. is the + // window just mapped. + bool was_visible = win_is_real_visible(w); + log_trace("Processing flags for window %#010x (%s), was visible: %d", w->base.id, + w->name, was_visible); + + if (win_check_flags_all(w, WIN_FLAGS_MAPPED)) { + map_win_start(ps, w); + win_clear_flags(w, WIN_FLAGS_MAPPED); + } + + if (!win_is_real_visible(w)) { + // Flags of invisible windows are processed when they are mapped + return; + } + + // Check client first, because later property updates need accurate client window + // information + if (win_check_flags_all(w, WIN_FLAGS_CLIENT_STALE)) { + win_recheck_client(ps, w); + win_clear_flags(w, WIN_FLAGS_CLIENT_STALE); + } + + bool damaged = false; + if (win_check_flags_any(w, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE)) { + if (was_visible) { + // Mark the old extents of this window as damaged. The new extents + // will be marked damaged below, after the window extents are + // updated. + // + // If the window is just mapped, we don't need to mark the old + // extent as damaged. (It's possible that the window was in fading + // and is interrupted by being mapped. In that case, the fading + // window will be added to damage by map_win_start, so we don't + // need to do it here) + add_damage_from_win(ps, w); + } + + // Ignore animations all together if set to none on window type basis + if (ps->o.wintype_option[w->window_type].animation == 0) { + w->g = w->pending_g; + + // Update window geometry + } else if (ps->o.animations) { + if (!was_visible) { + // Set window-open animation + init_animation(ps, w); + + w->animation_dest_center_x = w->pending_g.x + w->pending_g.width * 0.5; + w->animation_dest_center_y = w->pending_g.y + w->pending_g.height * 0.5; + w->animation_dest_w = w->pending_g.width; + w->animation_dest_h = w->pending_g.height; + + w->g.x = (int16_t)round(w->animation_center_x - + w->animation_w * 0.5); + w->g.y = (int16_t)round(w->animation_center_y - + w->animation_h * 0.5); + w->g.width = (uint16_t)round(w->animation_w); + w->g.height = (uint16_t)round(w->animation_h); + + } else { + w->animation_dest_center_x = + w->pending_g.x + w->pending_g.width * 0.5; + w->animation_dest_center_y = + w->pending_g.y + w->pending_g.height * 0.5; + w->animation_dest_w = w->pending_g.width; + w->animation_dest_h = w->pending_g.height; + } + + w->g.border_width = w->pending_g.border_width; + + double x_dist = w->animation_dest_center_x - w->animation_center_x; + double y_dist = w->animation_dest_center_y - w->animation_center_y; + double w_dist = w->animation_dest_w - w->animation_w; + double h_dist = w->animation_dest_h - w->animation_h; + w->animation_inv_og_distance = + 1.0 / sqrt(x_dist * x_dist + y_dist * y_dist + + w_dist * w_dist + h_dist * h_dist); + + if (isinf(w->animation_inv_og_distance)) + w->animation_inv_og_distance = 0; + + // We only grab images if w->reg_ignore_valid is true as + // there's an ev_shape_notify() event fired quickly on new windows + // for e.g. in case of Firefox main menu and ev_shape_notify() + // sets the win_set_flags(w, WIN_FLAGS_SIZE_STALE); which + // brakes the new image captured and because this same event + // also sets w->reg_ignore_valid = false; too we check for it + if (w->reg_ignore_valid) { + if (w->old_win_image) { + ps->backend_data->ops->release_image(ps->backend_data, + w->old_win_image); + w->old_win_image = NULL; + } + + // We only grab + if (w->win_image) { + w->old_win_image = ps->backend_data->ops->clone_image( + ps->backend_data, w->win_image, &w->bounding_shape); + } + } + + w->animation_progress = 0.0; + + } else { + w->g = w->pending_g; + } + + if (win_check_flags_all(w, WIN_FLAGS_SIZE_STALE)) { + win_on_win_size_change(ps, w); + win_update_bounding_shape(ps, w); + damaged = true; + win_clear_flags(w, WIN_FLAGS_SIZE_STALE); + } + + if (win_check_flags_all(w, WIN_FLAGS_POSITION_STALE)) { + damaged = true; + win_clear_flags(w, WIN_FLAGS_POSITION_STALE); + } + + win_update_screen(ps->xinerama_nscrs, ps->xinerama_scr_regs, w); + } + + if (win_check_flags_all(w, WIN_FLAGS_PROPERTY_STALE)) { + win_update_properties(ps, w); + win_clear_flags(w, WIN_FLAGS_PROPERTY_STALE); + } + + // Factor change flags could be set by previous stages, so must be handled last + if (win_check_flags_all(w, WIN_FLAGS_FACTOR_CHANGED)) { + win_on_factor_change(ps, w); + win_clear_flags(w, WIN_FLAGS_FACTOR_CHANGED); + } + + // Add damage, has to be done last so the window has the latest geometry + // information. + if (damaged) { + add_damage_from_win(ps, w); + } +} + +void win_process_image_flags(session_t *ps, struct managed_win *w) { + assert(!win_check_flags_all(w, WIN_FLAGS_MAPPED)); + + if (w->state == WSTATE_UNMAPPED || w->state == WSTATE_DESTROYING || + w->state == WSTATE_UNMAPPING) { + // Flags of invisible windows are processed when they are mapped + return; + } + + // Not a loop + while (win_check_flags_any(w, WIN_FLAGS_IMAGES_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. + assert(w->state != WSTATE_UNMAPPING && w->state != WSTATE_DESTROYING); + 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); + } + + if (win_check_flags_all(w, WIN_FLAGS_SHADOW_STALE)) { + if (!win_check_flags_all(w, WIN_FLAGS_SHADOW_NONE)) { + win_release_shadow(ps->backend_data, w); + } + if (w->shadow) { + win_bind_shadow(ps->backend_data, w, + (struct color){.red = ps->o.shadow_red, + .green = ps->o.shadow_green, + .blue = ps->o.shadow_blue, + .alpha = ps->o.shadow_opacity}, + ps->gaussian_map); + } + } + + // break here, loop always run only once + break; + } + + // Clear stale image flags + if (win_check_flags_any(w, WIN_FLAGS_IMAGES_STALE)) { + win_clear_flags(w, WIN_FLAGS_IMAGES_STALE); + } +} + +/** + * Check if a window has rounded corners. + * XXX This is really dumb + */ +static bool attr_pure win_has_rounded_corners(const struct managed_win *w) { + if (!w->bounding_shaped) { + return false; + } + + // Quit if border_size() returns XCB_NONE + if (!pixman_region32_not_empty((region_t *)&w->bounding_shape)) { + return false; + } + + // Determine the minimum width/height of a rectangle that could mark + // a window as having rounded corners + auto minwidth = + (uint16_t)max2(w->widthb * (1 - ROUNDED_PERCENT), w->widthb - ROUNDED_PIXELS); + auto minheight = + (uint16_t)max2(w->heightb * (1 - ROUNDED_PERCENT), w->heightb - ROUNDED_PIXELS); + + // Get the rectangles in the bounding region + int nrects = 0; + const rect_t *rects = + pixman_region32_rectangles((region_t *)&w->bounding_shape, &nrects); + + // Look for a rectangle large enough for this window be considered + // having rounded corners + for (int i = 0; i < nrects; ++i) { + if (rects[i].x2 - rects[i].x1 >= minwidth && + rects[i].y2 - rects[i].y1 >= minheight) { + return true; + } + } + return false; +} + +int win_update_name(session_t *ps, struct managed_win *w) { + char **strlst = NULL; + int nstr = 0; + + if (!w->client_win) { + return 0; + } + + if (!(wid_get_text_prop(ps, w->client_win, ps->atoms->a_NET_WM_NAME, &strlst, &nstr))) { + log_debug("(%#010x): _NET_WM_NAME unset, falling back to WM_NAME.", + w->client_win); + + if (!wid_get_text_prop(ps, w->client_win, ps->atoms->aWM_NAME, &strlst, &nstr)) { + log_debug("Unsetting window name for %#010x", w->client_win); + free(w->name); + w->name = NULL; + return -1; + } + } + + int ret = 0; + if (!w->name || strcmp(w->name, strlst[0]) != 0) { + ret = 1; + free(w->name); + w->name = strdup(strlst[0]); + } + + free(strlst); + + log_debug("(%#010x): client = %#010x, name = \"%s\", " + "ret = %d", + w->base.id, w->client_win, w->name, ret); + return ret; +} + +static int win_update_role(session_t *ps, struct managed_win *w) { + char **strlst = NULL; + int nstr = 0; + + if (!wid_get_text_prop(ps, w->client_win, ps->atoms->aWM_WINDOW_ROLE, &strlst, &nstr)) { + return -1; + } + + int ret = 0; + if (!w->role || strcmp(w->role, strlst[0]) != 0) { + ret = 1; + free(w->role); + w->role = strdup(strlst[0]); + } + + free(strlst); + + log_trace("(%#010x): client = %#010x, role = \"%s\", " + "ret = %d", + w->base.id, w->client_win, w->role, ret); + return ret; +} + +/** + * Check if a window is bounding-shaped. + */ +static inline bool win_bounding_shaped(const session_t *ps, xcb_window_t wid) { + if (ps->shape_exists) { + xcb_shape_query_extents_reply_t *reply; + Bool bounding_shaped; + + reply = xcb_shape_query_extents_reply( + ps->c, xcb_shape_query_extents(ps->c, wid), NULL); + bounding_shaped = reply && reply->bounding_shaped; + free(reply); + + return bounding_shaped; + } + + return false; +} + +static wintype_t wid_get_prop_wintype(session_t *ps, xcb_window_t wid) { + winprop_t prop = + x_get_prop(ps->c, wid, ps->atoms->a_NET_WM_WINDOW_TYPE, 32L, XCB_ATOM_ATOM, 32); + + for (unsigned i = 0; i < prop.nitems; ++i) { + for (wintype_t j = 1; j < NUM_WINTYPES; ++j) { + if (ps->atoms_wintypes[j] == (xcb_atom_t)prop.p32[i]) { + free_winprop(&prop); + return j; + } + } + } + + free_winprop(&prop); + + return WINTYPE_UNKNOWN; +} + +static bool +wid_get_opacity_prop(session_t *ps, xcb_window_t wid, opacity_t def, opacity_t *out) { + bool ret = false; + *out = def; + + winprop_t prop = x_get_prop(ps->c, wid, ps->atoms->a_NET_WM_WINDOW_OPACITY, 1L, + XCB_ATOM_CARDINAL, 32); + + if (prop.nitems) { + *out = *prop.c32; + ret = true; + } + + free_winprop(&prop); + + return ret; +} + +// XXX should distinguish between frame has alpha and window body has alpha +bool win_has_alpha(const struct managed_win *w) { + return w->pictfmt && w->pictfmt->type == XCB_RENDER_PICT_TYPE_DIRECT && + w->pictfmt->direct.alpha_mask; +} + +bool win_client_has_alpha(const struct managed_win *w) { + return w->client_pictfmt && w->client_pictfmt->type == XCB_RENDER_PICT_TYPE_DIRECT && + w->client_pictfmt->direct.alpha_mask; +} + +winmode_t win_calc_mode(const struct managed_win *w) { + if (w->opacity < 1.0) { + return WMODE_TRANS; + } + + if (win_has_alpha(w)) { + if (w->client_win == XCB_NONE) { + // This is a window not managed by the WM, and it has alpha, + // so it's transparent. No need to check WM frame. + return WMODE_TRANS; + } + // The WM window has alpha + if (win_client_has_alpha(w)) { + // The client window also has alpha, the entire window is + // transparent + return WMODE_TRANS; + } + if (win_has_frame(w)) { + // The client window doesn't have alpha, but we have a WM frame + // window, which has alpha. + return WMODE_FRAME_TRANS; + } + // Although the WM window has alpha, the frame window has 0 size, so + // consider the window solid + } + + if (w->frame_opacity != 1.0 && win_has_frame(w)) { + return WMODE_FRAME_TRANS; + } + + // log_trace("Window %#010x(%s) is solid", w->client_win, w->name); + return WMODE_SOLID; +} + +/** + * 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 win_calc_opacity_target(session_t *ps, const struct managed_win *w) { + double opacity = 1; + + if (w->state == WSTATE_UNMAPPED) { + // be consistent + return 0; + } + if (w->state == WSTATE_UNMAPPING || w->state == WSTATE_DESTROYING) { + if (ps->root_desktop_switch_direction) + return w->opacity; + + return 0; + } + // Try obeying opacity property and window type opacity firstly + if (w->has_opacity_prop) { + opacity = ((double)w->opacity_prop) / OPAQUE; + } else if (w->opacity_is_set) { + opacity = w->opacity_set; + } else if (!safe_isnan(ps->o.wintype_option[w->window_type].opacity)) { + opacity = ps->o.wintype_option[w->window_type].opacity; + } else { + // Respect active_opacity only when the window is physically focused + if (win_is_focused_raw(ps, w)) + opacity = ps->o.active_opacity; + else if (!w->focused) + // Respect inactive_opacity in some cases + opacity = ps->o.inactive_opacity; + } + + // respect inactive override + if (ps->o.inactive_opacity_override && !w->focused) { + opacity = ps->o.inactive_opacity; + } + + return opacity; +} + +/** + * Determine whether a window is to be dimmed. + */ +bool win_should_dim(session_t *ps, const struct managed_win *w) { + // Make sure we do nothing if the window is unmapped / being destroyed + if (w->state == WSTATE_UNMAPPED) { + return false; + } + + if (ps->o.inactive_dim > 0 && !(w->focused)) { + return true; + } else { + return false; + } +} + +/** + * Determine if a window should fade on opacity change. + */ +bool win_should_fade(session_t *ps, const struct managed_win *w) { + // To prevent it from being overwritten by last-paint value if the window is + if (w->fade_force != UNSET) { + return w->fade_force; + } + if (ps->o.no_fading_openclose && w->in_openclose) { + return false; + } + if (ps->o.no_fading_destroyed_argb && w->state == WSTATE_DESTROYING && + win_has_alpha(w) && w->client_win && w->client_win != w->base.id) { + // deprecated + return false; + } + if (w->fade_excluded) { + return false; + } + return ps->o.wintype_option[w->window_type].fade; +} + +/** + * Reread _COMPTON_SHADOW property from a window. + * + * The property must be set on the outermost window, usually the WM frame. + */ +void win_update_prop_shadow_raw(session_t *ps, struct managed_win *w) { + winprop_t prop = x_get_prop(ps->c, w->base.id, ps->atoms->a_COMPTON_SHADOW, 1, + XCB_ATOM_CARDINAL, 32); + + if (!prop.nitems) { + w->prop_shadow = -1; + } else { + w->prop_shadow = *prop.c32; + } + + free_winprop(&prop); +} + +static void win_set_shadow(session_t *ps, struct managed_win *w, bool shadow_new) { + if (w->shadow == shadow_new) { + return; + } + + log_debug("Updating shadow property of window %#010x (%s) to %d", w->base.id, + w->name, shadow_new); + + // We don't handle property updates of non-visible windows until they are mapped. + assert(w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING && + w->state != WSTATE_UNMAPPING); + + // Keep a copy of window extent before the shadow change. Will be used for + // calculation of damaged region + region_t extents; + pixman_region32_init(&extents); + win_extents(w, &extents); + + // Apply the shadow change + w->shadow = shadow_new; + + if (ps->redirected) { + // Add damage for shadow change + + // Window extents need update on shadow state change + // Shadow geometry currently doesn't change on shadow state change + // calc_shadow_geometry(ps, w); + + // Note: because the release and creation of the shadow images are + // delayed. When multiple shadow changes happen in a row, without + // rendering phase between them, there could be a stale shadow image + // attached to the window even if w->shadow was previously false. And vice + // versa. So we check the STALE flag before asserting the existence of the + // shadow image. + if (w->shadow) { + // Mark the new extents as damaged if the shadow is added + assert(!w->shadow_image || + win_check_flags_all(w, WIN_FLAGS_SHADOW_STALE) || + !ps->o.experimental_backends); + pixman_region32_clear(&extents); + win_extents(w, &extents); + add_damage_from_win(ps, w); + } else { + // Mark the old extents as damaged if the shadow is removed + assert(w->shadow_image || + win_check_flags_all(w, WIN_FLAGS_SHADOW_STALE) || + !ps->o.experimental_backends); + add_damage(ps, &extents); + } + + // Delayed update of shadow image + // By setting WIN_FLAGS_SHADOW_STALE, we ask win_process_flags to + // re-create or release the shaodw in based on whether w->shadow is set. + win_set_flags(w, WIN_FLAGS_SHADOW_STALE); + + // Only set pending_updates if we are redirected. Otherwise change of a + // shadow won't have influence on whether we should redirect. + ps->pending_updates = true; + } + + pixman_region32_fini(&extents); +} + +/** + * Determine if a window should have shadow, and update things depending + * on shadow state. + */ +static void win_determine_shadow(session_t *ps, struct managed_win *w) { + log_debug("Determining shadow of window %#010x (%s)", w->base.id, w->name); + bool shadow_new = w->shadow; + + if (w->shadow_force != UNSET) { + shadow_new = w->shadow_force; + } else if (w->a.map_state == XCB_MAP_STATE_VIEWABLE) { + shadow_new = true; + if (!ps->o.wintype_option[w->window_type].shadow) { + log_debug("Shadow disabled by wintypes"); + shadow_new = false; + } else if (c2_match(ps, w, ps->o.shadow_blacklist, NULL)) { + log_debug("Shadow disabled by shadow-exclude"); + shadow_new = false; + } else if (ps->o.shadow_ignore_shaped && w->bounding_shaped && + !w->rounded_corners) { + log_debug("Shadow disabled by shadow-ignore-shaped"); + shadow_new = false; + } else if (w->prop_shadow == 0) { + log_debug("Shadow disabled by shadow property"); + shadow_new = false; + } + } + + win_set_shadow(ps, w, shadow_new); +} + +/** + * Reread _COMPTON_SHADOW property from a window and update related + * things. + */ +void win_update_prop_shadow(session_t *ps, struct managed_win *w) { + long attr_shadow_old = w->prop_shadow; + + win_update_prop_shadow_raw(ps, w); + + if (w->prop_shadow != attr_shadow_old) { + win_determine_shadow(ps, w); + } +} + +static void win_determine_clip_shadow_above(session_t *ps, struct managed_win *w) { + bool should_crop = (ps->o.wintype_option[w->window_type].clip_shadow_above || + c2_match(ps, w, ps->o.shadow_clip_list, NULL)); + w->clip_shadow_above = should_crop; +} + +static void win_set_invert_color(session_t *ps, struct managed_win *w, bool invert_color_new) { + if (w->invert_color == invert_color_new) { + return; + } + + w->invert_color = invert_color_new; + + add_damage_from_win(ps, w); +} + +/** + * Determine if a window should have color inverted. + */ +static void win_determine_invert_color(session_t *ps, struct managed_win *w) { + bool invert_color_new = w->invert_color; + + if (UNSET != w->invert_color_force) { + invert_color_new = w->invert_color_force; + } else if (w->a.map_state == XCB_MAP_STATE_VIEWABLE) { + invert_color_new = c2_match(ps, w, ps->o.invert_color_list, NULL); + } + + win_set_invert_color(ps, w, invert_color_new); +} + +/** + * Set w->invert_color_force of a window. + */ +void win_set_invert_color_force(session_t *ps, struct managed_win *w, switch_t val) { + if (val != w->invert_color_force) { + w->invert_color_force = val; + win_determine_invert_color(ps, w); + queue_redraw(ps); + } +} + +/** + * Set w->fade_force of a window. + * + * Doesn't affect fading already in progress + */ +void win_set_fade_force(struct managed_win *w, switch_t val) { + w->fade_force = val; +} + +/** + * Set w->focused_force of a window. + */ +void win_set_focused_force(session_t *ps, struct managed_win *w, switch_t val) { + if (val != w->focused_force) { + w->focused_force = val; + win_on_factor_change(ps, w); + queue_redraw(ps); + } +} + +/** + * Set w->shadow_force of a window. + */ +void win_set_shadow_force(session_t *ps, struct managed_win *w, switch_t val) { + if (val != w->shadow_force) { + w->shadow_force = val; + win_determine_shadow(ps, w); + queue_redraw(ps); + } +} + +static void +win_set_blur_background(session_t *ps, struct managed_win *w, bool blur_background_new) { + if (w->blur_background == blur_background_new) + return; + + w->blur_background = blur_background_new; + + // This damage might not be absolutely necessary (e.g. when the window is opaque), + // but blur_background changes should be rare, so this should be fine. + add_damage_from_win(ps, w); +} + +/** + * Determine if a window should have background blurred. + */ +static void win_determine_blur_background(session_t *ps, struct managed_win *w) { + log_debug("Determining blur-background of window %#010x (%s)", w->base.id, w->name); + if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) { + return; + } + + bool blur_background_new = ps->o.blur_method != BLUR_METHOD_NONE; + if (blur_background_new) { + if (!ps->o.wintype_option[w->window_type].blur_background) { + log_debug("Blur background disabled by wintypes"); + blur_background_new = false; + } else if (c2_match(ps, w, ps->o.blur_background_blacklist, NULL)) { + log_debug("Blur background disabled by blur-background-exclude"); + blur_background_new = false; + } + } + + win_set_blur_background(ps, w, blur_background_new); +} + +/** + * Determine if a window should have rounded corners. + */ +static void win_determine_rounded_corners(session_t *ps, struct managed_win *w) { + if (ps->o.corner_radius == 0) { + w->corner_radius = 0; + return; + } + + // Don't round full screen windows & excluded windows + if ((w && win_is_fullscreen(ps, w)) || + c2_match(ps, w, ps->o.rounded_corners_blacklist, NULL)) { + w->corner_radius = 0; + log_debug("Not rounding corners for window %#010x", w->base.id); + } else { + w->corner_radius = ps->o.corner_radius; + log_debug("Rounding corners for window %#010x", w->base.id); + // Initialize the border color to an invalid value + w->border_col[0] = w->border_col[1] = w->border_col[2] = + w->border_col[3] = -1.0F; + } +} + +/** + * Update window opacity according to opacity rules. + */ +void win_update_opacity_rule(session_t *ps, struct managed_win *w) { + if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) { + return; + } + + double opacity = 1.0; + bool is_set = false; + void *val = NULL; + if (c2_match(ps, w, ps->o.opacity_rules, &val)) { + opacity = ((double)(long)val) / 100.0; + is_set = true; + } + + w->opacity_set = opacity; + w->opacity_is_set = is_set; +} + +/** + * Function to be called on window data changes. + * + * TODO(yshui) need better name + */ +void win_on_factor_change(session_t *ps, struct managed_win *w) { + log_debug("Window %#010x (%s) factor change", w->base.id, w->name); + // Focus needs to be updated first, as other rules might depend on the focused + // state of the window + win_update_focused(ps, w); + + win_determine_shadow(ps, w); + win_determine_clip_shadow_above(ps, w); + win_determine_invert_color(ps, w); + win_determine_blur_background(ps, w); + win_determine_rounded_corners(ps, w); + w->mode = win_calc_mode(w); + log_debug("Window mode changed to %d", w->mode); + win_update_opacity_rule(ps, w); + if (w->a.map_state == XCB_MAP_STATE_VIEWABLE) { + w->paint_excluded = c2_match(ps, w, ps->o.paint_blacklist, NULL); + } + if (w->a.map_state == XCB_MAP_STATE_VIEWABLE) { + w->unredir_if_possible_excluded = + c2_match(ps, w, ps->o.unredir_if_possible_blacklist, NULL); + } + + w->fade_excluded = c2_match(ps, w, ps->o.fade_blacklist, NULL); + + win_update_opacity_target(ps, w); + + w->reg_ignore_valid = false; +} + +/** + * Update cache data in struct _win that depends on window size. + */ +void win_on_win_size_change(session_t *ps, struct managed_win *w) { + w->widthb = w->g.width + w->g.border_width * 2; + w->heightb = w->g.height + w->g.border_width * 2; + w->shadow_dx = ps->o.shadow_offset_x; + w->shadow_dy = ps->o.shadow_offset_y; + w->shadow_width = w->widthb + ps->o.shadow_radius * 2; + w->shadow_height = w->heightb + ps->o.shadow_radius * 2; + + // We don't handle property updates of non-visible windows until they are mapped. + assert(w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING && + w->state != WSTATE_UNMAPPING); + + // Invalidate the shadow we built + if (w->state != WSTATE_DESTROYING) + win_set_flags(w, WIN_FLAGS_IMAGES_STALE); + + ps->pending_updates = true; + free_paint(ps, &w->shadow_paint); +} + +/** + * Update window type. + */ +void win_update_wintype(session_t *ps, struct managed_win *w) { + const wintype_t wtype_old = w->window_type; + + // Detect window type here + w->window_type = wid_get_prop_wintype(ps, w->client_win); + + // Conform to EWMH standard, if _NET_WM_WINDOW_TYPE is not present, take + // override-redirect windows or windows without WM_TRANSIENT_FOR as + // _NET_WM_WINDOW_TYPE_NORMAL, otherwise as _NET_WM_WINDOW_TYPE_DIALOG. + if (WINTYPE_UNKNOWN == w->window_type) { + if (w->a.override_redirect || + !wid_has_prop(ps, w->client_win, ps->atoms->aWM_TRANSIENT_FOR)) + w->window_type = WINTYPE_NORMAL; + else + w->window_type = WINTYPE_DIALOG; + } + + if (w->window_type != wtype_old) { + win_on_factor_change(ps, w); + } +} + +/** + * Mark a window as the client window of another. + * + * @param ps current session + * @param w struct _win of the parent window + * @param client window ID of the client window + */ +void win_mark_client(session_t *ps, struct managed_win *w, xcb_window_t client) { + w->client_win = client; + + // If the window isn't mapped yet, stop here, as the function will be + // called in map_win() + if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) { + return; + } + + auto e = xcb_request_check( + ps->c, xcb_change_window_attributes( + ps->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, w); + + // Get frame widths. The window is in damaged area already. + win_update_frame_extents(ps, w, client); + + // Get window group + if (ps->o.track_leader) { + win_update_leader(ps, w); + } + + // Get window name and class if we are tracking them + win_update_name(ps, w); + win_update_class(ps, w); + win_update_role(ps, w); + + // Update everything related to conditions + win_on_factor_change(ps, w); + + auto r = xcb_get_window_attributes_reply( + ps->c, xcb_get_window_attributes(ps->c, w->client_win), &e); + if (!r) { + log_error_x_error(e, "Failed to get client window attributes"); + return; + } + + w->client_pictfmt = x_get_pictform_for_visual(ps->c, r->visual); + free(r); +} + +/** + * Unmark current client window of a window. + * + * @param ps current session + * @param w struct _win of the parent window + */ +void win_unmark_client(session_t *ps, 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, 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(session_t *ps, xcb_window_t w) { + if (wid_has_prop(ps, w, ps->atoms->aWM_STATE)) { + return w; + } + + xcb_query_tree_reply_t *reply = + xcb_query_tree_reply(ps->c, xcb_query_tree(ps->c, w), NULL); + if (!reply) { + return 0; + } + + xcb_window_t *children = xcb_query_tree_children(reply); + int nchildren = xcb_query_tree_children_length(reply); + int i; + xcb_window_t ret = 0; + + for (i = 0; i < nchildren; ++i) { + if ((ret = find_client_win(ps, children[i]))) { + break; + } + } + + free(reply); + + return ret; +} + +/** + * Recheck client window of a window. + * + * @param ps current session + * @param w struct _win of the parent window + */ +void win_recheck_client(session_t *ps, struct managed_win *w) { + assert(ps->server_grabbed); + // Initialize wmwin to false + w->wmwin = false; + + // Look for the client window + + // 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(ps, w->base.id); + if (cw) { + log_debug("(%#010x): client %#010x", w->base.id, cw); + } + // Set a window's client window to itself if we couldn't find a + // client window + if (!cw) { + cw = w->base.id; + w->wmwin = !w->a.override_redirect; + log_debug("(%#010x): client self (%s)", w->base.id, + (w->wmwin ? "wmwin" : "override-redirected")); + } + + // Unmark the old one + if (w->client_win && w->client_win != cw) { + win_unmark_client(ps, w); + } + + // Mark the new one + win_mark_client(ps, w, cw); +} + +/** + * Free all resources in a <code>struct _win</code>. + */ +void free_win_res(session_t *ps, struct managed_win *w) { + // No need to call backend release_image here because + // finish_unmap_win should've done that for us. + // XXX unless we are called by session_destroy + // assert(w->win_data == NULL); + free_win_res_glx(ps, w); + free_paint(ps, &w->paint); + free_paint(ps, &w->shadow_paint); + // Above should be done during unmapping + // Except when we are called by session_destroy + + pixman_region32_fini(&w->bounding_shape); + // BadDamage may be thrown if the window is destroyed + set_ignore_cookie(ps, xcb_damage_destroy(ps->c, w->damage)); + rc_region_unref(&w->reg_ignore); + free(w->name); + free(w->class_instance); + free(w->class_general); + free(w->role); + + free(w->stale_props); + w->stale_props = NULL; + w->stale_props_capacity = 0; +} + +/// Insert a new window after list_node `prev` +/// New window will be in unmapped state +static struct win *add_win(session_t *ps, xcb_window_t id, struct list_node *prev) { + log_debug("Adding window %#010x", id); + struct win *old_w = NULL; + HASH_FIND_INT(ps->windows, &id, old_w); + assert(old_w == NULL); + + auto new_w = cmalloc(struct win); + list_insert_after(prev, &new_w->stack_neighbour); + new_w->id = id; + new_w->managed = false; + new_w->is_new = true; + new_w->destroyed = false; + + HASH_ADD_INT(ps->windows, id, new_w); + ps->pending_updates = true; + return new_w; +} + +/// Insert a new win entry at the top of the stack +struct win *add_win_top(session_t *ps, xcb_window_t id) { + return add_win(ps, id, &ps->window_stack); +} + +/// Insert a new window above window with id `below`, if there is no window, add to top +/// New window will be in unmapped state +struct win *add_win_above(session_t *ps, xcb_window_t id, xcb_window_t below) { + struct win *w = NULL; + HASH_FIND_INT(ps->windows, &below, w); + if (!w) { + if (!list_is_empty(&ps->window_stack)) { + // `below` window is not found even if the window stack is not + // empty + return NULL; + } + return add_win_top(ps, id); + } else { + // we found something from the hash table, so if the stack is empty, + // we are in an inconsistent state. + assert(!list_is_empty(&ps->window_stack)); + return add_win(ps, id, w->stack_neighbour.prev); + } +} + +/// Query the Xorg for information about window `win` +/// `win` pointer might become invalid after this function returns +/// Returns the pointer to the window, might be different from `w` +struct win *fill_win(session_t *ps, struct win *w) { + static const struct managed_win win_def = { + // No need to initialize. (or, you can think that + // they are initialized right here). + // The following ones are updated during paint or paint preprocess + .shadow_opacity = 0.0, + .to_paint = false, + .frame_opacity = 1.0, + .dim = false, + .invert_color = false, + .blur_background = false, + .reg_ignore = NULL, + // The following ones are updated for other reasons + .pixmap_damaged = false, // updated by damage events + .state = WSTATE_UNMAPPED, // updated by window state changes + .in_openclose = true, // set to false after first map is done, + // true here because window is just created + .animation_velocity_x = 0.0, // updated by window geometry changes + .animation_velocity_y = 0.0, // updated by window geometry changes + .animation_velocity_w = 0.0, // updated by window geometry changes + .animation_velocity_h = 0.0, // updated by window geometry changes + .animation_progress = 1.0, // updated by window geometry changes + .animation_inv_og_distance = NAN, // updated by window geometry changes + .reg_ignore_valid = false, // set to true when damaged + .flags = WIN_FLAGS_IMAGES_NONE, // updated by property/attributes/etc + // change + .stale_props = NULL, + .stale_props_capacity = 0, + + // Runtime variables, updated by dbus + .fade_force = UNSET, + .shadow_force = UNSET, + .focused_force = UNSET, + .invert_color_force = UNSET, + + // Initialized in this function + .a = {0}, + .pictfmt = NULL, + .client_pictfmt = NULL, + .widthb = 0, + .heightb = 0, + .shadow_dx = 0, + .shadow_dy = 0, + .shadow_width = 0, + .shadow_height = 0, + .damage = XCB_NONE, + + // Not initialized until mapped, this variables + // have no meaning or have no use until the window + // is mapped + .win_image = NULL, + .old_win_image = NULL, + .shadow_image = NULL, + .prev_trans = NULL, + .shadow = false, + .clip_shadow_above = false, + .xinerama_scr = -1, + .mode = WMODE_TRANS, + .ever_damaged = false, + .client_win = XCB_NONE, + .leader = XCB_NONE, + .cache_leader = XCB_NONE, + .window_type = WINTYPE_UNKNOWN, + .wmwin = false, + .focused = false, + .opacity = 0, + .opacity_target = 0, + .has_opacity_prop = false, + .opacity_prop = OPAQUE, + .opacity_is_set = false, + .opacity_set = 1, + .frame_extents = MARGIN_INIT, // in win_mark_client + .bounding_shaped = false, + .bounding_shape = {0}, + .rounded_corners = false, + .paint_excluded = false, + .fade_excluded = false, + .unredir_if_possible_excluded = false, + .prop_shadow = -1, + // following 4 are set in win_mark_client + .name = NULL, + .class_instance = NULL, + .class_general = NULL, + .role = NULL, + + // Initialized during paint + .paint = PAINT_INIT, + .shadow_paint = PAINT_INIT, + + .corner_radius = 0, + }; + + assert(!w->destroyed); + assert(w->is_new); + + w->is_new = false; + + // Reject overlay window and already added windows + if (w->id == ps->overlay) { + return w; + } + + auto duplicated_win = find_managed_win(ps, w->id); + if (duplicated_win) { + log_debug("Window %#010x (recorded name: %s) added multiple times", w->id, + duplicated_win->name); + return &duplicated_win->base; + } + + log_debug("Managing window %#010x", w->id); + xcb_get_window_attributes_cookie_t acookie = xcb_get_window_attributes(ps->c, w->id); + xcb_get_window_attributes_reply_t *a = + xcb_get_window_attributes_reply(ps->c, acookie, NULL); + if (!a || a->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 w; + } + + if (a->_class == XCB_WINDOW_CLASS_INPUT_ONLY) { + // No need to manage this window, but we still keep it on the window stack + w->managed = false; + free(a); + return w; + } + + // Allocate and initialize the new win structure + auto new_internal = cmalloc(struct managed_win_internal); + auto new = (struct managed_win *)new_internal; + + // Fill structure + // We only need to initialize the part that are not initialized + // by map_win + *new = win_def; + new->base = *w; + new->base.managed = true; + new->a = *a; + pixman_region32_init(&new->bounding_shape); + + free(a); + + xcb_generic_error_t *e; + auto g = xcb_get_geometry_reply(ps->c, xcb_get_geometry(ps->c, w->id), &e); + if (!g) { + log_error_x_error(e, "Failed to get geometry of window %#010x", w->id); + free(e); + free(new); + return w; + } + new->pending_g = (struct win_geometry){ + .x = g->x, + .y = g->y, + .width = g->width, + .height = g->height, + .border_width = g->border_width, + }; + + free(g); + + // Create Damage for window (if not Input Only) + new->damage = x_new_id(ps->c); + e = xcb_request_check( + ps->c, xcb_damage_create_checked(ps->c, new->damage, w->id, + XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY)); + if (e) { + log_error_x_error(e, "Failed to create damage"); + free(e); + free(new); + return w; + } + + // Set window event mask + xcb_change_window_attributes( + ps->c, new->base.id, XCB_CW_EVENT_MASK, + (const uint32_t[]){determine_evmask(ps, new->base.id, WIN_EVMODE_FRAME)}); + + // Get notification when the shape of a window changes + if (ps->shape_exists) { + xcb_shape_select_input(ps->c, new->base.id, 1); + } + + new->pictfmt = x_get_pictform_for_visual(ps->c, new->a.visual); + new->client_pictfmt = NULL; + + list_replace(&w->stack_neighbour, &new->base.stack_neighbour); + struct win *replaced = NULL; + HASH_REPLACE_INT(ps->windows, id, &new->base, replaced); + assert(replaced == w); + free(w); + + // Set all the stale flags on this new window, so it's properties will get updated + // when it's mapped + win_set_flags(new, WIN_FLAGS_CLIENT_STALE | WIN_FLAGS_SIZE_STALE | + WIN_FLAGS_POSITION_STALE | WIN_FLAGS_PROPERTY_STALE | + WIN_FLAGS_FACTOR_CHANGED); + xcb_atom_t init_stale_props[] = { + ps->atoms->a_NET_WM_WINDOW_TYPE, ps->atoms->a_NET_WM_WINDOW_OPACITY, + ps->atoms->a_NET_FRAME_EXTENTS, ps->atoms->aWM_NAME, + ps->atoms->a_NET_WM_NAME, ps->atoms->aWM_CLASS, + ps->atoms->aWM_WINDOW_ROLE, ps->atoms->a_COMPTON_SHADOW, + ps->atoms->aWM_CLIENT_LEADER, ps->atoms->aWM_TRANSIENT_FOR, + }; + win_set_properties_stale(new, init_stale_props, ARR_SIZE(init_stale_props)); + +#ifdef CONFIG_DBUS + // Send D-Bus signal + if (ps->o.dbus) { + cdbus_ev_win_added(ps, &new->base); + } +#endif + return &new->base; +} + +/** + * Set leader of a window. + */ +static inline void win_set_leader(session_t *ps, struct managed_win *w, xcb_window_t nleader) { + // If the leader changes + if (w->leader != nleader) { + xcb_window_t cache_leader_old = win_get_leader(ps, w); + + w->leader = nleader; + + // Forcefully do this to deal with the case when a child window + // gets mapped before parent, or when the window is a waypoint + clear_cache_win_leaders(ps); + + // Update the old and new window group and active_leader if the window + // could affect their state. + xcb_window_t cache_leader = win_get_leader(ps, w); + if (win_is_focused_raw(ps, w) && cache_leader_old != cache_leader) { + ps->active_leader = cache_leader; + + group_on_factor_change(ps, cache_leader_old); + group_on_factor_change(ps, cache_leader); + } + + // Update everything related to conditions + win_on_factor_change(ps, w); + } +} + +/** + * Update leader of a window. + */ +void win_update_leader(session_t *ps, struct managed_win *w) { + xcb_window_t leader = XCB_NONE; + + // Read the leader properties + if (ps->o.detect_transient && !leader) { + leader = + wid_get_prop_window(ps->c, w->client_win, ps->atoms->aWM_TRANSIENT_FOR); + } + + if (ps->o.detect_client_leader && !leader) { + leader = + wid_get_prop_window(ps->c, w->client_win, ps->atoms->aWM_CLIENT_LEADER); + } + + win_set_leader(ps, w, leader); + + log_trace("(%#010x): client %#010x, leader %#010x, cache %#010x", w->base.id, + w->client_win, w->leader, win_get_leader(ps, w)); +} + +/** + * Internal function of win_get_leader(). + */ +static xcb_window_t win_get_leader_raw(session_t *ps, struct managed_win *w, int recursions) { + // Rebuild the cache if needed + if (!w->cache_leader && (w->client_win || w->leader)) { + // Leader defaults to client window + if (!(w->cache_leader = w->leader)) + w->cache_leader = w->client_win; + + // If the leader of this window isn't itself, look for its ancestors + if (w->cache_leader && w->cache_leader != w->client_win) { + auto wp = find_toplevel(ps, w->cache_leader); + if (wp) { + // Dead loop? + if (recursions > WIN_GET_LEADER_MAX_RECURSION) + return XCB_NONE; + + w->cache_leader = win_get_leader_raw(ps, wp, recursions + 1); + } + } + } + + return w->cache_leader; +} + +/** + * Retrieve the <code>WM_CLASS</code> of a window and update its + * <code>win</code> structure. + */ +bool win_update_class(session_t *ps, struct managed_win *w) { + char **strlst = NULL; + int nstr = 0; + + // Can't do anything if there's no client window + if (!w->client_win) + return false; + + // Free and reset old strings + free(w->class_instance); + free(w->class_general); + w->class_instance = NULL; + w->class_general = NULL; + + // Retrieve the property string list + if (!wid_get_text_prop(ps, w->client_win, ps->atoms->aWM_CLASS, &strlst, &nstr)) { + return false; + } + + // Copy the strings if successful + w->class_instance = strdup(strlst[0]); + + if (nstr > 1) { + w->class_general = strdup(strlst[1]); + } + + free(strlst); + + log_trace("(%#010x): client = %#010x, " + "instance = \"%s\", general = \"%s\"", + w->base.id, w->client_win, w->class_instance, w->class_general); + + return true; +} + +/** + * Handle window focus change. + */ +static void win_on_focus_change(session_t *ps, struct managed_win *w) { + // If window grouping detection is enabled + if (ps->o.track_leader) { + xcb_window_t leader = win_get_leader(ps, w); + + // If the window gets focused, replace the old active_leader + if (win_is_focused_raw(ps, w) && leader != ps->active_leader) { + xcb_window_t active_leader_old = ps->active_leader; + + ps->active_leader = leader; + + group_on_factor_change(ps, active_leader_old); + group_on_factor_change(ps, leader); + } + // If the group get unfocused, remove it from active_leader + else if (!win_is_focused_raw(ps, w) && leader && + leader == ps->active_leader && !group_is_focused(ps, leader)) { + ps->active_leader = XCB_NONE; + group_on_factor_change(ps, leader); + } + } + + // Update everything related to conditions + win_on_factor_change(ps, w); + +#ifdef CONFIG_DBUS + // Send D-Bus signal + if (ps->o.dbus) { + if (win_is_focused_raw(ps, w)) { + cdbus_ev_win_focusin(ps, &w->base); + } else { + cdbus_ev_win_focusout(ps, &w->base); + } + } +#endif +} + +/** + * Set real focused state of a window. + */ +void win_set_focused(session_t *ps, struct managed_win *w) { + // Unmapped windows will have their focused state reset on map + if (w->a.map_state != XCB_MAP_STATE_VIEWABLE) { + return; + } + + if (win_is_focused_raw(ps, w)) { + return; + } + + auto old_active_win = ps->active_win; + ps->active_win = w; + assert(win_is_focused_raw(ps, w)); + + if (old_active_win) { + win_on_focus_change(ps, old_active_win); + } + win_on_focus_change(ps, w); +} + +/** + * Get a rectangular region a window (and possibly its shadow) occupies. + * + * Note w->shadow and shadow geometry must be correct before calling this + * function. + */ +void win_extents(const struct managed_win *w, region_t *res) { + pixman_region32_clear(res); + pixman_region32_union_rect(res, res, w->g.x, w->g.y, (uint)w->widthb, (uint)w->heightb); + + if (w->shadow) { + assert(w->shadow_width >= 0 && w->shadow_height >= 0); + pixman_region32_union_rect(res, res, w->g.x + w->shadow_dx, + w->g.y + w->shadow_dy, (uint)w->shadow_width, + (uint)w->shadow_height); + } +} + +gen_by_val(win_extents); + +/** + * Update the out-dated bounding shape of a window. + * + * Mark the window shape as updated + */ +void win_update_bounding_shape(session_t *ps, struct managed_win *w) { + if (ps->shape_exists) { + w->bounding_shaped = win_bounding_shaped(ps, w->base.id); + } + + // We don't handle property updates of non-visible windows until they are mapped. + assert(w->state != WSTATE_UNMAPPED && w->state != WSTATE_DESTROYING && + w->state != WSTATE_UNMAPPING); + + pixman_region32_clear(&w->bounding_shape); + // Start with the window rectangular region + win_get_region_local(w, &w->bounding_shape); + + // Only request for a bounding region if the window is shaped + // (while loop is used to avoid goto, not an actual loop) + while (w->bounding_shaped) { + /* + * if window doesn't exist anymore, this will generate an error + * as well as not generate a region. + */ + + xcb_shape_get_rectangles_reply_t *r = xcb_shape_get_rectangles_reply( + ps->c, + xcb_shape_get_rectangles(ps->c, w->base.id, XCB_SHAPE_SK_BOUNDING), NULL); + + if (!r) { + break; + } + + xcb_rectangle_t *xrects = xcb_shape_get_rectangles_rectangles(r); + int nrects = xcb_shape_get_rectangles_rectangles_length(r); + rect_t *rects = from_x_rects(nrects, xrects); + free(r); + + region_t br; + pixman_region32_init_rects(&br, rects, nrects); + free(rects); + + // Add border width because we are using a different origin. + // X thinks the top left of the inner window is the origin + // (for the bounding shape, althought xcb_get_geometry thinks + // the outer top left (outer means outside of the window + // border) is the origin), + // We think the top left of the border is the origin + pixman_region32_translate(&br, w->g.border_width, w->g.border_width); + + // Intersect the bounding region we got with the window rectangle, to + // make sure the bounding region is not bigger than the window + // rectangle + pixman_region32_intersect(&w->bounding_shape, &w->bounding_shape, &br); + pixman_region32_fini(&br); + break; + } + + if (w->bounding_shaped && ps->o.detect_rounded_corners) { + w->rounded_corners = win_has_rounded_corners(w); + } + + // Window shape changed, we should free old wpaint and shadow pict + // log_trace("free out dated pict"); + win_set_flags(w, WIN_FLAGS_IMAGES_STALE); + ps->pending_updates = true; + + free_paint(ps, &w->paint); + free_paint(ps, &w->shadow_paint); + + win_on_factor_change(ps, w); +} + +/** + * Reread opacity property of a window. + */ +void win_update_opacity_prop(session_t *ps, struct managed_win *w) { + // get frame opacity first + w->has_opacity_prop = wid_get_opacity_prop(ps, w->base.id, OPAQUE, &w->opacity_prop); + + if (w->has_opacity_prop) { + // opacity found + return; + } + + if (ps->o.detect_client_opacity && w->client_win && w->base.id == w->client_win) { + // checking client opacity not allowed + return; + } + + // get client opacity + w->has_opacity_prop = + wid_get_opacity_prop(ps, w->client_win, OPAQUE, &w->opacity_prop); +} + +/** + * Retrieve frame extents from a window. + */ +void win_update_frame_extents(session_t *ps, struct managed_win *w, xcb_window_t client) { + winprop_t prop = x_get_prop(ps->c, client, ps->atoms->a_NET_FRAME_EXTENTS, 4L, + XCB_ATOM_CARDINAL, 32); + + if (prop.nitems == 4) { + int extents[4]; + for (int i = 0; i < 4; i++) { + if (prop.c32[i] > (uint32_t)INT_MAX) { + log_warn("Your window manager sets a absurd " + "_NET_FRAME_EXTENTS value (%u), ignoring it.", + prop.c32[i]); + memset(extents, 0, sizeof(extents)); + break; + } + extents[i] = (int)prop.c32[i]; + } + + const bool changed = w->frame_extents.left != extents[0] || + w->frame_extents.right != extents[1] || + w->frame_extents.top != extents[2] || + w->frame_extents.bottom != extents[3]; + w->frame_extents.left = extents[0]; + w->frame_extents.right = extents[1]; + w->frame_extents.top = extents[2]; + w->frame_extents.bottom = extents[3]; + + // If frame_opacity != 1, then frame of this window + // is not included in reg_ignore of underneath windows + if (ps->o.frame_opacity == 1 && changed) { + w->reg_ignore_valid = false; + } + } + + log_trace("(%#010x): %d, %d, %d, %d", w->base.id, w->frame_extents.left, + w->frame_extents.right, w->frame_extents.top, w->frame_extents.bottom); + + free_winprop(&prop); +} + +bool win_is_region_ignore_valid(session_t *ps, const struct managed_win *w) { + win_stack_foreach_managed(i, &ps->window_stack) { + if (i == w) { + break; + } + if (!i->reg_ignore_valid) { + return false; + } + } + return true; +} + +/** + * Stop listening for events on a particular window. + */ +void win_ev_stop(session_t *ps, const struct win *w) { + xcb_change_window_attributes(ps->c, w->id, XCB_CW_EVENT_MASK, (const uint32_t[]){0}); + + if (!w->managed) { + return; + } + + auto mw = (struct managed_win *)w; + if (mw->client_win) { + xcb_change_window_attributes(ps->c, mw->client_win, XCB_CW_EVENT_MASK, + (const uint32_t[]){0}); + } + + if (ps->shape_exists) { + xcb_shape_select_input(ps->c, w->id, 0); + } +} + +/// Finish the unmapping of a window (e.g. after fading has finished). +/// Doesn't free `w` +static void unmap_win_finish(session_t *ps, struct managed_win *w) { + w->reg_ignore_valid = false; + w->state = WSTATE_UNMAPPED; + + // We are in unmap_win, this window definitely was viewable + 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_oldpixmap(ps->backend_data, w); + } + } else { + assert(!w->win_image); + assert(!w->old_win_image); + assert(!w->shadow_image); + } + + // Force animation to completed position + w->animation_velocity_x = 0; + w->animation_velocity_y = 0; + w->animation_velocity_w = 0; + w->animation_velocity_h = 0; + w->animation_progress = 1.0; + + free_paint(ps, &w->paint); + free_paint(ps, &w->shadow_paint); + + // Try again at binding images when the window is mapped next time + win_clear_flags(w, WIN_FLAGS_IMAGE_ERROR); + + // Flag window so that it gets animated when it reapears + // in case it wasn't destroyed + win_set_flags(w, WIN_FLAGS_POSITION_STALE); + win_set_flags(w, WIN_FLAGS_SIZE_STALE); +} + +/// Finish the destruction of a window (e.g. after fading has finished). +/// Frees `w` +static void destroy_win_finish(session_t *ps, struct win *w) { + log_trace("Trying to finish destroying (%#010x)", w->id); + + auto next_w = win_stack_find_next_managed(ps, &w->stack_neighbour); + list_remove(&w->stack_neighbour); + + if (w->managed) { + auto mw = (struct managed_win *)w; + + if (mw->state != WSTATE_UNMAPPED) { + // Only UNMAPPED state has window resources freed, otherwise + // we need to call unmap_win_finish to free them. + // XXX actually we unmap_win_finish only frees the rendering + // resources, we still need to call free_win_res. will fix + // later. + unmap_win_finish(ps, mw); + } + + // Unmapping preserves the shadow image, so free it here + if (!win_check_flags_all(mw, WIN_FLAGS_SHADOW_NONE)) { + assert(mw->shadow_image != NULL); + win_release_shadow(ps->backend_data, mw); + } + + // Invalidate reg_ignore of windows below this one + // TODO(yshui) what if next_w is not mapped?? + /* TODO(yshui) seriously figure out how reg_ignore behaves. + * I think if `w` is unmapped, and destroyed after + * paint happened at least once, w->reg_ignore_valid would + * be true, and there is no need to invalid w->next->reg_ignore + * when w is destroyed. */ + if (next_w) { + rc_region_unref(&next_w->reg_ignore); + next_w->reg_ignore_valid = false; + } + + if (mw == ps->active_win) { + // Usually, the window cannot be the focused at destruction. + // FocusOut should be generated before the window is destroyed. We + // do this check just to be completely sure we don't have dangling + // references. + log_debug("window %#010x (%s) is destroyed while being focused", + w->id, mw->name); + ps->active_win = NULL; + } + + free_win_res(ps, mw); + + // Drop w from all prev_trans to avoid accessing freed memory in + // repair_win() + // TODO(yshui) there can only be one prev_trans pointing to w + win_stack_foreach_managed(w2, &ps->window_stack) { + if (mw == w2->prev_trans) { + w2->prev_trans = NULL; + } + } + } + + free(w); +} + +static void map_win_finish(struct managed_win *w) { + w->in_openclose = false; + w->state = WSTATE_MAPPED; +} + +/// Move window `w` so it's before `next` in the list +static inline void restack_win(session_t *ps, struct win *w, struct list_node *next) { + struct managed_win *mw = NULL; + if (w->managed) { + mw = (struct managed_win *)w; + } + + if (mw) { + // This invalidates all reg_ignore below the new stack position of `w` + mw->reg_ignore_valid = false; + rc_region_unref(&mw->reg_ignore); + + // This invalidates all reg_ignore below the old stack position of `w` + auto next_w = win_stack_find_next_managed(ps, &w->stack_neighbour); + if (next_w) { + next_w->reg_ignore_valid = false; + rc_region_unref(&next_w->reg_ignore); + } + } + + list_move_before(&w->stack_neighbour, next); + + // add damage for this window + if (mw) { + add_damage_from_win(ps, mw); + } + +#ifdef DEBUG_RESTACK + log_trace("Window stack modified. Current stack:"); + for (auto c = ps->list; c; c = c->next) { + const char *desc = ""; + if (c->state == WSTATE_DESTROYING) { + desc = "(D) "; + } + log_trace("%#010x \"%s\" %s", c->id, c->name, desc); + } +#endif +} + +/// Move window `w` so it's right above `below` +void restack_above(session_t *ps, struct win *w, xcb_window_t below) { + xcb_window_t old_below; + + if (!list_node_is_last(&ps->window_stack, &w->stack_neighbour)) { + old_below = list_next_entry(w, stack_neighbour)->id; + } else { + old_below = XCB_NONE; + } + log_debug("Restack %#010x (%s), old_below: %#010x, new_below: %#010x", w->id, + win_get_name_if_managed(w), old_below, below); + + if (old_below != below) { + struct list_node *new_next; + if (!below) { + new_next = &ps->window_stack; + } else { + struct win *tmp_w = NULL; + HASH_FIND_INT(ps->windows, &below, tmp_w); + + if (!tmp_w) { + log_error("Failed to found new below window %#010x.", below); + return; + } + + new_next = &tmp_w->stack_neighbour; + } + restack_win(ps, w, new_next); + } +} + +void restack_bottom(session_t *ps, struct win *w) { + restack_above(ps, w, 0); +} + +void restack_top(session_t *ps, struct win *w) { + log_debug("Restack %#010x (%s) to top", w->id, win_get_name_if_managed(w)); + if (&w->stack_neighbour == ps->window_stack.next) { + // already at top + return; + } + restack_win(ps, w, ps->window_stack.next); +} + +/// Start destroying a window. Windows cannot always be destroyed immediately +/// because of fading and such. +/// +/// @return whether the window has finished destroying and is freed +bool destroy_win_start(session_t *ps, struct win *w) { + auto mw = (struct managed_win *)w; + assert(w); + + log_debug("Destroying %#010x \"%s\", managed = %d", w->id, + (w->managed ? mw->name : NULL), w->managed); + + // Delete destroyed window from the hash table, even though the window might still + // be rendered for a while. We need to make sure future window with the same + // window id won't confuse us. Keep the window in the window stack if it's managed + // and mapped, since we might still need to render it (e.g. fading out). Window + // will be removed from the stack when it finishes destroying. + HASH_DEL(ps->windows, w); + + if (!w->managed || mw->state == WSTATE_UNMAPPED) { + // Window is already unmapped, or is an unmanged window, just destroy it + destroy_win_finish(ps, w); + return true; + } + + if (w->managed) { + // Clear IMAGES_STALE flags since the window is destroyed: Clear + // PIXMAP_STALE as there is no pixmap available anymore, so STALE doesn't + // make sense. + // XXX Clear SHADOW_STALE as setting/clearing flags on a destroyed window + // doesn't work leading to an inconsistent state where the shadow is + // refreshed but the flags are stuck in STALE. + // Do this before changing the window state to destroying + win_clear_flags(mw, WIN_FLAGS_IMAGES_STALE); + + // If size/shape/position information is stale, win_process_update_flags + // will update them and add the new window extents to damage. Since the + // window has been destroyed, we cannot get the complete information at + // this point, so we just add what we currently have to the damage. + if (win_check_flags_any(mw, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE)) { + add_damage_from_win(ps, mw); + } + + // Clear some flags about stale window information. Because now the window + // is destroyed, we can't update them anyway. + win_clear_flags(mw, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE | + WIN_FLAGS_PROPERTY_STALE | + WIN_FLAGS_FACTOR_CHANGED | WIN_FLAGS_CLIENT_STALE); + + // Update state flags of a managed window + mw->state = WSTATE_DESTROYING; + mw->a.map_state = XCB_MAP_STATE_UNMAPPED; + mw->in_openclose = true; + } + + // don't need win_ev_stop because the window is gone anyway +#ifdef CONFIG_DBUS + // Send D-Bus signal + if (ps->o.dbus) { + cdbus_ev_win_destroyed(ps, w); + } +#endif + + if (!ps->redirected) { + // Skip transition if we are not rendering + return win_skip_fading(ps, mw); + } + + return false; +} + +void unmap_win_start(session_t *ps, struct managed_win *w) { + assert(w); + assert(w->base.managed); + assert(w->a._class != XCB_WINDOW_CLASS_INPUT_ONLY); + + log_debug("Unmapping %#010x \"%s\"", w->base.id, w->name); + + if (unlikely(w->state == WSTATE_DESTROYING)) { + log_warn("Trying to undestroy a window?"); + assert(false); + } + + bool was_damaged = w->ever_damaged; + w->ever_damaged = false; + + if (unlikely(w->state == WSTATE_UNMAPPING || w->state == WSTATE_UNMAPPED)) { + if (win_check_flags_all(w, WIN_FLAGS_MAPPED)) { + // Clear the pending map as this window is now unmapped + win_clear_flags(w, WIN_FLAGS_MAPPED); + } else { + log_warn("Trying to unmapping an already unmapped window %#010x " + "\"%s\"", + w->base.id, w->name); + assert(false); + } + return; + } + + // Note we don't update focused window here. This will either be + // triggered by subsequence Focus{In, Out} event, or by recheck_focus + + 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); + + if (ps->o.animations && + ps->o.animation_for_unmap_window != OPEN_WINDOW_ANIMATION_NONE && + ps->o.wintype_option[w->window_type].animation != 0) + { + init_animation_unmap(ps, w); + + double x_dist = w->animation_dest_center_x - w->animation_center_x; + double y_dist = w->animation_dest_center_y - w->animation_center_y; + double w_dist = w->animation_dest_w - w->animation_w; + double h_dist = w->animation_dest_h - w->animation_h; + w->animation_inv_og_distance = + 1.0 / sqrt(x_dist * x_dist + y_dist * y_dist + + w_dist * w_dist + h_dist * h_dist); + + if (isinf(w->animation_inv_og_distance)) + w->animation_inv_og_distance = 0; + + w->animation_progress = 0.0; + + if (w->old_win_image) { + ps->backend_data->ops->release_image(ps->backend_data, + w->old_win_image); + w->old_win_image = NULL; + } + } + +#ifdef CONFIG_DBUS + // Send D-Bus signal + if (ps->o.dbus) { + cdbus_ev_win_unmapped(ps, &w->base); + } +#endif + + if (!ps->redirected || !was_damaged) { + // If we are not redirected, we skip fading because we aren't rendering + // anything anyway. + // If the window wasn't ever damaged, it shouldn't be painted either. But + // a fading out window is always painted, so we have to skip fading here. + CHECK(!win_skip_fading(ps, w)); + } +} + +/** + * Execute fade callback of a window if fading finished. + * + * @return whether the window is destroyed and freed + */ +bool win_check_fade_finished(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); + return false; + } + + if (w->opacity == w->opacity_target) { + switch (w->state) { + case WSTATE_UNMAPPING: unmap_win_finish(ps, w); return false; + case WSTATE_DESTROYING: destroy_win_finish(ps, &w->base); return true; + case WSTATE_MAPPING: map_win_finish(w); return false; + case WSTATE_FADING: w->state = WSTATE_MAPPED; break; + default: unreachable; + } + } + + return false; +} + +/// Skip the current in progress fading of window, +/// transition the window straight to its end state +/// +/// @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); + return false; + } + log_debug("Skipping fading process of window %#010x (%s)", w->base.id, w->name); + w->opacity = w->opacity_target; + + if (w->animation_progress < 1) { + w->animation_progress = 1; + w->g.x = w->pending_g.x; + w->g.y = w->pending_g.y; + w->g.width = w->pending_g.width; + w->g.height = w->pending_g.height; + } + + return win_check_fade_finished(ps, w); +} + +/** + * Get the Xinerama screen a window is on. + * + * Return an index >= 0, or -1 if not found. + * + * TODO(yshui) move to x.c + * TODO(yshui) use xrandr + */ +void win_update_screen(int nscreens, region_t *screens, struct managed_win *w) { + w->xinerama_scr = -1; + + for (int i = 0; i < nscreens; i++) { + auto e = pixman_region32_extents(&screens[i]); + if (e->x1 <= w->g.x && e->y1 <= w->g.y && e->x2 >= w->g.x + w->widthb && + e->y2 >= w->g.y + w->heightb) { + w->xinerama_scr = i; + log_debug("Window %#010x (%s), %dx%d+%dx%d, is on screen %d " + "(%dx%d+%dx%d)", + w->base.id, w->name, w->g.x, w->g.y, w->widthb, w->heightb, + i, e->x1, e->y1, e->x2 - e->x1, e->y2 - e->y1); + return; + } + } + log_debug("Window %#010x (%s), %dx%d+%dx%d, is not contained by any screen", + w->base.id, w->name, w->g.x, w->g.y, w->g.width, w->g.height); +} + +/// Map an already registered window +void map_win_start(session_t *ps, struct managed_win *w) { + assert(ps->server_grabbed); + assert(w); + + // Don't care about window mapping if it's an InputOnly window + // Also, try avoiding mapping a window twice + if (w->a._class == XCB_WINDOW_CLASS_INPUT_ONLY) { + return; + } + + log_debug("Mapping (%#010x \"%s\")", w->base.id, w->name); + + assert(w->state != WSTATE_DESTROYING); + if (w->state != WSTATE_UNMAPPED && w->state != WSTATE_UNMAPPING) { + log_warn("Mapping an already mapped window"); + return; + } + + if (w->state == WSTATE_UNMAPPING) { + CHECK(!win_skip_fading(ps, w)); + // We skipped the unmapping process, the window was rendered, now it is + // not anymore. So we need to mark the then unmapping window as damaged. + // + // Solves problem when, for example, a window is unmapped then mapped in a + // different location + add_damage_from_win(ps, w); + assert(w); + } + + assert(w->state == WSTATE_UNMAPPED); + + // Rant: window size could change after we queried its geometry here and before + // we get its pixmap. Later, when we get back to the event processing loop, we + // will get the notification about size change from Xserver and try to refresh the + // pixmap, while the pixmap is actually already up-to-date (i.e. the notification + // is stale). There is basically no real way to prevent this, aside from grabbing + // the server. + + // XXX Can we assume map_state is always viewable? + w->a.map_state = XCB_MAP_STATE_VIEWABLE; + + // Update window mode here to check for ARGB windows + w->mode = win_calc_mode(w); + + log_debug("Window (%#010x) has type %s", w->base.id, WINTYPES[w->window_type]); + + // 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); + + log_debug("Window %#010x has opacity %f, opacity target is %f", w->base.id, + 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 function + // is called. But this should be unnecessary in the first place, since + // ever_damaged is set to false in unmap_win_finish anyway. + + // Sets the WIN_FLAGS_IMAGES_STALE flag so later in the critical section + // the window's image will be bound + + win_set_flags(w, WIN_FLAGS_PIXMAP_STALE); + +#ifdef CONFIG_DBUS + // Send D-Bus signal + if (ps->o.dbus) { + cdbus_ev_win_mapped(ps, &w->base); + } +#endif + + if (!ps->redirected) { + CHECK(!win_skip_fading(ps, 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); + + if (opacity_target_old == w->opacity_target) { + return; + } + + 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); + } + } 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); + } + } + + if (!ps->redirected) { + CHECK(!win_skip_fading(ps, w)); + } +} + +/** + * Find a managed window from window id in window linked list of the session. + */ +struct win *find_win(session_t *ps, xcb_window_t id) { + if (!id) { + return NULL; + } + + struct win *w = NULL; + HASH_FIND_INT(ps->windows, &id, w); + assert(w == NULL || !w->destroyed); + return w; +} + +/** + * Find a managed window from window id in window linked list of the session. + */ +struct managed_win *find_managed_win(session_t *ps, xcb_window_t id) { + struct win *w = find_win(ps, id); + if (!w || !w->managed) { + return NULL; + } + + auto mw = (struct managed_win *)w; + assert(mw->state != WSTATE_DESTROYING); + return mw; +} + +/** + * Find out the WM frame of a client window using existing data. + * + * @param id window ID + * @return struct win object of the found window, NULL if not found + */ +struct managed_win *find_toplevel(session_t *ps, xcb_window_t id) { + if (!id) { + return NULL; + } + + HASH_ITER2(ps->windows, w) { + assert(!w->destroyed); + if (!w->managed) { + continue; + } + + auto mw = (struct managed_win *)w; + if (mw->client_win == id) { + return mw; + } + } + + return NULL; +} + +/** + * Find a managed window that is, or is a parent of `wid`. + * + * @param ps current session + * @param wid window ID + * @return struct _win object of the found window, NULL if not found + */ +struct managed_win *find_managed_window_or_parent(session_t *ps, xcb_window_t wid) { + // TODO(yshui) this should probably be an "update tree", then find_toplevel. + // current approach is a bit more "racy", as the server state might be ahead of + // our state + struct win *w = NULL; + + // We traverse through its ancestors to find out the frame + // Using find_win here because if we found a unmanaged window we know about, we + // can stop early. + while (wid && wid != ps->root && !(w = find_win(ps, wid))) { + // xcb_query_tree probably fails if you run picom when X is somehow + // initializing (like add it in .xinitrc). In this case + // just leave it alone. + auto reply = xcb_query_tree_reply(ps->c, xcb_query_tree(ps->c, wid), NULL); + if (reply == NULL) { + break; + } + + wid = reply->parent; + free(reply); + } + + if (w == NULL || !w->managed) { + return NULL; + } + + return (struct managed_win *)w; +} + +/** + * Check if a rectangle includes the whole screen. + */ +static inline bool rect_is_fullscreen(const session_t *ps, int x, int y, int wid, int hei) { + return (x <= 0 && y <= 0 && (x + wid) >= ps->root_width && (y + hei) >= ps->root_height); +} + +/** + * Check if a window is fulscreen using EWMH + * + * TODO(yshui) cache this property + */ +static inline bool +win_is_fullscreen_xcb(xcb_connection_t *c, const struct atom *a, const xcb_window_t w) { + xcb_get_property_cookie_t prop = + xcb_get_property(c, 0, w, a->a_NET_WM_STATE, XCB_ATOM_ATOM, 0, 12); + xcb_get_property_reply_t *reply = xcb_get_property_reply(c, prop, NULL); + if (!reply) { + return false; + } + + if (reply->length) { + xcb_atom_t *val = xcb_get_property_value(reply); + for (uint32_t i = 0; i < reply->length; i++) { + if (val[i] != a->a_NET_WM_STATE_FULLSCREEN) { + continue; + } + free(reply); + return true; + } + } + free(reply); + return false; +} + +/// Set flags on a window. Some sanity checks are performed +void win_set_flags(struct managed_win *w, uint64_t flags) { + log_debug("Set flags %" PRIu64 " to window %#010x (%s)", flags, w->base.id, w->name); + if (unlikely(w->state == WSTATE_DESTROYING)) { + log_error("Flags set on a destroyed window %#010x (%s)", w->base.id, w->name); + return; + } + + w->flags |= flags; +} + +/// Clear flags on a window. Some sanity checks are performed +void win_clear_flags(struct managed_win *w, uint64_t flags) { + log_debug("Clear flags %" PRIu64 " from window %#010x (%s)", flags, w->base.id, + w->name); + if (unlikely(w->state == WSTATE_DESTROYING)) { + log_warn("Flags cleared on a destroyed window %#010x (%s)", w->base.id, + w->name); + return; + } + + w->flags = w->flags & (~flags); +} + +void win_set_properties_stale(struct managed_win *w, const xcb_atom_t *props, int nprops) { + const auto bits_per_element = sizeof(*w->stale_props) * 8; + size_t new_capacity = w->stale_props_capacity; + + // Calculate the new capacity of the properties array + for (int i = 0; i < nprops; i++) { + if (props[i] >= new_capacity * bits_per_element) { + new_capacity = props[i] / bits_per_element + 1; + } + } + + // Reallocate if necessary + if (new_capacity > w->stale_props_capacity) { + w->stale_props = + realloc(w->stale_props, new_capacity * sizeof(*w->stale_props)); + + // Clear the content of the newly allocated bytes + memset(w->stale_props + w->stale_props_capacity, 0, + (new_capacity - w->stale_props_capacity) * sizeof(*w->stale_props)); + w->stale_props_capacity = new_capacity; + } + + // Set the property bits + for (int i = 0; i < nprops; i++) { + w->stale_props[props[i] / bits_per_element] |= + 1UL << (props[i] % bits_per_element); + } + win_set_flags(w, WIN_FLAGS_PROPERTY_STALE); +} + +static void win_clear_all_properties_stale(struct managed_win *w) { + memset(w->stale_props, 0, w->stale_props_capacity * sizeof(*w->stale_props)); + win_clear_flags(w, WIN_FLAGS_PROPERTY_STALE); +} + +static bool win_fetch_and_unset_property_stale(struct managed_win *w, xcb_atom_t prop) { + const auto bits_per_element = sizeof(*w->stale_props) * 8; + if (prop >= w->stale_props_capacity * bits_per_element) { + return false; + } + + const auto mask = 1UL << (prop % bits_per_element); + bool ret = w->stale_props[prop / bits_per_element] & mask; + w->stale_props[prop / bits_per_element] &= ~mask; + return ret; +} + +bool win_check_flags_any(struct managed_win *w, uint64_t flags) { + return (w->flags & flags) != 0; +} + +bool win_check_flags_all(struct managed_win *w, uint64_t flags) { + return (w->flags & flags) == flags; +} + +/** + * Check if a window is a fullscreen window. + * + * It's not using w->border_size for performance measures. + */ +bool win_is_fullscreen(const session_t *ps, const struct managed_win *w) { + if (!ps->o.no_ewmh_fullscreen && + win_is_fullscreen_xcb(ps->c, ps->atoms, w->client_win)) { + return true; + } + return rect_is_fullscreen(ps, w->g.x, w->g.y, w->widthb, w->heightb) && + (!w->bounding_shaped || w->rounded_corners); +} + +/** + * Check if a window has BYPASS_COMPOSITOR property set + * + * TODO(yshui) cache this property + */ +bool win_is_bypassing_compositor(const session_t *ps, const struct managed_win *w) { + bool ret = false; + + auto prop = x_get_prop(ps->c, w->client_win, ps->atoms->a_NET_WM_BYPASS_COMPOSITOR, + 1L, XCB_ATOM_CARDINAL, 32); + + if (prop.nitems && *prop.c32 == 1) { + ret = true; + } + + free_winprop(&prop); + return ret; +} + +/** + * Check if a window is focused, without using any focus rules or forced focus settings + */ +bool win_is_focused_raw(const session_t *ps, const struct managed_win *w) { + return w->a.map_state == XCB_MAP_STATE_VIEWABLE && ps->active_win == w; +} + +// Find the managed window immediately below `i` in the window stack +struct managed_win * +win_stack_find_next_managed(const session_t *ps, const struct list_node *i) { + while (!list_node_is_last(&ps->window_stack, i)) { + auto next = list_entry(i->next, struct win, stack_neighbour); + if (next->managed) { + return (struct managed_win *)next; + } + i = &next->stack_neighbour; + } + return NULL; +} + +/// Return whether this window is mapped on the X server side +bool win_is_mapped_in_x(const struct managed_win *w) { + return w->state == WSTATE_MAPPING || w->state == WSTATE_FADING || + w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPING || + w->state == WSTATE_DESTROYING || (w->flags & WIN_FLAGS_MAPPED); +} |