aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorallusive-dev <[email protected]>2023-09-19 17:47:33 +1000
committerallusive-dev <[email protected]>2023-09-19 17:47:33 +1000
commita93aba600b1c5d019b680b9f4ff3fa85d5d43a60 (patch)
tree77f8152222655657472a70e0bfa413a0495dd555 /src
parentreset (diff)
downloadcompfy-a93aba600b1c5d019b680b9f4ff3fa85d5d43a60.tar.xz
compfy-a93aba600b1c5d019b680b9f4ff3fa85d5d43a60.zip
Fixed broken files/code and other errors
Diffstat (limited to 'src')
-rw-r--r--src/atom.c37
-rw-r--r--src/atom.h68
-rw-r--r--src/backend/backend.c494
-rw-r--r--src/backend/backend.h290
-rw-r--r--src/backend/backend_common.c480
-rw-r--r--src/backend/backend_common.h78
-rw-r--r--src/backend/driver.c82
-rw-r--r--src/backend/driver.h62
-rw-r--r--src/backend/dummy/dummy.c174
-rw-r--r--src/backend/gl/gl_common.c1922
-rw-r--r--src/backend/gl/gl_common.h220
-rw-r--r--src/backend/gl/glx.c648
-rw-r--r--src/backend/gl/glx.h77
-rw-r--r--src/backend/meson.build7
-rw-r--r--src/backend/xrender/xrender.c779
-rw-r--r--src/c2.c1674
-rw-r--r--src/c2.h26
-rw-r--r--src/cache.c95
-rw-r--r--src/cache.h32
-rw-r--r--src/common.h541
-rw-r--r--src/compiler.h124
-rw-r--r--src/config.c650
-rw-r--r--src/config.h380
-rw-r--r--src/config_libconfig.c784
-rw-r--r--src/dbus.c1340
-rw-r--r--src/dbus.h54
-rw-r--r--src/diagnostic.c53
-rw-r--r--src/diagnostic.h9
-rw-r--r--src/err.h37
-rw-r--r--src/event.c757
-rw-r--r--src/event.h8
-rw-r--r--src/file_watch.c188
-rw-r--r--src/file_watch.h10
-rw-r--r--src/kernel.c160
-rw-r--r--src/kernel.h39
-rw-r--r--src/list.h108
-rw-r--r--src/log.c376
-rw-r--r--src/log.h96
-rw-r--r--src/meson.build97
-rw-r--r--src/meta.h75
-rw-r--r--src/opengl.c1514
-rw-r--r--src/opengl.h246
-rw-r--r--src/options.c1128
-rw-r--r--src/options.h37
-rw-r--r--src/picom.c2790
-rw-r--r--src/picom.h117
-rw-r--r--src/picom.modulemap214
-rw-r--r--src/region.h100
-rw-r--r--src/render.c1500
-rw-r--r--src/render.h50
-rw-r--r--src/string_utils.c129
-rw-r--r--src/string_utils.h54
-rw-r--r--src/types.h32
-rw-r--r--src/uthash_extra.h7
-rw-r--r--src/utils.c51
-rw-r--r--src/utils.h294
-rw-r--r--src/vsync.c204
-rw-r--r--src/vsync.h7
-rw-r--r--src/win.c3116
-rw-r--r--src/win.h531
-rw-r--r--src/win_defs.h102
-rw-r--r--src/x.c748
-rw-r--r--src/x.h293
-rw-r--r--src/xrescheck.c65
-rw-r--r--src/xrescheck.h62
65 files changed, 26522 insertions, 0 deletions
diff --git a/src/atom.c b/src/atom.c
new file mode 100644
index 0000000..0272dc8
--- /dev/null
+++ b/src/atom.c
@@ -0,0 +1,37 @@
+#include <string.h>
+#include <xcb/xcb.h>
+
+#include "atom.h"
+#include "common.h"
+#include "utils.h"
+#include "log.h"
+
+static inline void *atom_getter(void *ud, const char *atom_name, int *err) {
+ xcb_connection_t *c = ud;
+ xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(
+ c, xcb_intern_atom(c, 0, to_u16_checked(strlen(atom_name)), atom_name), NULL);
+
+ xcb_atom_t atom = XCB_NONE;
+ if (reply) {
+ log_debug("Atom %s is %d", atom_name, reply->atom);
+ atom = reply->atom;
+ free(reply);
+ } else {
+ log_error("Failed to intern atoms");
+ *err = 1;
+ }
+ return (void *)(intptr_t)atom;
+}
+
+/**
+ * Create a new atom structure and fetch all predefined atoms
+ */
+struct atom *init_atoms(xcb_connection_t *c) {
+ auto atoms = ccalloc(1, struct atom);
+ atoms->c = new_cache((void *)c, atom_getter, NULL);
+#define ATOM_GET(x) atoms->a##x = (xcb_atom_t)(intptr_t)cache_get(atoms->c, #x, NULL)
+ LIST_APPLY(ATOM_GET, SEP_COLON, ATOM_LIST1);
+ LIST_APPLY(ATOM_GET, SEP_COLON, ATOM_LIST2);
+#undef ATOM_GET
+ return atoms;
+}
diff --git a/src/atom.h b/src/atom.h
new file mode 100644
index 0000000..baf3360
--- /dev/null
+++ b/src/atom.h
@@ -0,0 +1,68 @@
+#pragma once
+#include <stdlib.h>
+
+#include <xcb/xcb.h>
+
+#include "meta.h"
+#include "cache.h"
+
+// clang-format off
+// Splitted into 2 lists because of the limitation of our macros
+#define ATOM_LIST1 \
+ _NET_WM_WINDOW_OPACITY, \
+ _NET_FRAME_EXTENTS, \
+ WM_STATE, \
+ _NET_WM_NAME, \
+ _NET_WM_PID, \
+ WM_NAME, \
+ WM_CLASS, \
+ WM_ICON_NAME, \
+ WM_TRANSIENT_FOR, \
+ WM_WINDOW_ROLE, \
+ WM_CLIENT_LEADER, \
+ WM_CLIENT_MACHINE, \
+ _NET_ACTIVE_WINDOW, \
+ _COMPTON_SHADOW, \
+ _NET_WM_WINDOW_TYPE, \
+ _NET_CURRENT_DESKTOP
+
+#define ATOM_LIST2 \
+ _NET_WM_WINDOW_TYPE_DESKTOP, \
+ _NET_WM_WINDOW_TYPE_DOCK, \
+ _NET_WM_WINDOW_TYPE_TOOLBAR, \
+ _NET_WM_WINDOW_TYPE_MENU, \
+ _NET_WM_WINDOW_TYPE_UTILITY, \
+ _NET_WM_WINDOW_TYPE_SPLASH, \
+ _NET_WM_WINDOW_TYPE_DIALOG, \
+ _NET_WM_WINDOW_TYPE_NORMAL, \
+ _NET_WM_WINDOW_TYPE_DROPDOWN_MENU, \
+ _NET_WM_WINDOW_TYPE_POPUP_MENU, \
+ _NET_WM_WINDOW_TYPE_TOOLTIP, \
+ _NET_WM_WINDOW_TYPE_NOTIFICATION, \
+ _NET_WM_WINDOW_TYPE_COMBO, \
+ _NET_WM_WINDOW_TYPE_DND, \
+ _NET_WM_STATE, \
+ _NET_WM_STATE_FULLSCREEN, \
+ _NET_WM_BYPASS_COMPOSITOR, \
+ UTF8_STRING, \
+ C_STRING
+// clang-format on
+
+#define ATOM_DEF(x) xcb_atom_t a##x
+
+struct atom {
+ struct cache *c;
+ LIST_APPLY(ATOM_DEF, SEP_COLON, ATOM_LIST1);
+ LIST_APPLY(ATOM_DEF, SEP_COLON, ATOM_LIST2);
+};
+
+struct atom *init_atoms(xcb_connection_t *);
+
+static inline xcb_atom_t get_atom(struct atom *a, const char *key) {
+ return (xcb_atom_t)(intptr_t)cache_get(a->c, key, NULL);
+}
+
+static inline void destroy_atoms(struct atom *a) {
+ cache_free(a->c);
+ free(a);
+}
diff --git a/src/backend/backend.c b/src/backend/backend.c
new file mode 100644
index 0000000..b0e562a
--- /dev/null
+++ b/src/backend/backend.c
@@ -0,0 +1,494 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) Yuxuan Shui <[email protected]>
+#include <xcb/sync.h>
+#include <xcb/xcb.h>
+
+#include "backend/backend.h"
+#include "common.h"
+#include "compiler.h"
+#include "config.h"
+#include "log.h"
+#include "region.h"
+#include "types.h"
+#include "win.h"
+#include "x.h"
+
+extern struct backend_operations xrender_ops, dummy_ops;
+#ifdef CONFIG_OPENGL
+extern struct backend_operations glx_ops;
+#endif
+
+struct backend_operations *backend_list[NUM_BKEND] = {
+ [BKEND_XRENDER] = &xrender_ops,
+ [BKEND_DUMMY] = &dummy_ops,
+#ifdef CONFIG_OPENGL
+ [BKEND_GLX] = &glx_ops,
+#endif
+};
+
+/**
+ * @param all_damage if true ignore damage and repaint the whole screen
+ */
+region_t get_damage(session_t *ps, bool all_damage) {
+ region_t region;
+ auto buffer_age_fn = ps->backend_data->ops->buffer_age;
+ int buffer_age = buffer_age_fn ? buffer_age_fn(ps->backend_data) : -1;
+
+ if (all_damage) {
+ buffer_age = -1;
+ }
+
+ pixman_region32_init(&region);
+ if (buffer_age == -1 || buffer_age > ps->ndamage) {
+ pixman_region32_copy(&region, &ps->screen_reg);
+ } else {
+ for (int i = 0; i < buffer_age; i++) {
+ auto curr = ((ps->damage - ps->damage_ring) + i) % ps->ndamage;
+ log_trace("damage index: %d, damage ring offset: %ld", i, curr);
+ dump_region(&ps->damage_ring[curr]);
+ pixman_region32_union(&region, &region, &ps->damage_ring[curr]);
+ }
+ pixman_region32_intersect(&region, &region, &ps->screen_reg);
+ }
+ return region;
+}
+
+static void process_window_for_painting(session_t *ps, struct managed_win* w, void* win_image,
+ double additional_alpha,
+ region_t* reg_bound, region_t* reg_visible,
+ region_t* reg_paint, region_t* reg_paint_in_bound) {
+ // For window image processing, we don't have to limit the process
+ // region to damage for correctness. (see <damager-note> for
+ // details)
+
+ // The visible region, in window local coordinates Although we
+ // don't limit process region to damage, we provide that info in
+ // reg_visible as a hint. Since window image data outside of the
+ // damage region won't be painted onto target
+ region_t reg_visible_local;
+ {
+ // The bounding shape, in window local coordinates
+ region_t reg_bound_local;
+ pixman_region32_init(&reg_bound_local);
+ pixman_region32_copy(&reg_bound_local, reg_bound);
+ pixman_region32_translate(&reg_bound_local, -w->g.x, -w->g.y);
+
+ pixman_region32_init(&reg_visible_local);
+ pixman_region32_intersect(&reg_visible_local,
+ reg_visible, reg_paint);
+ pixman_region32_translate(&reg_visible_local, -w->g.x,
+ -w->g.y);
+ // Data outside of the bounding shape won't be visible,
+ // but it is not necessary to limit the image operations
+ // to the bounding shape yet. So pass that as the visible
+ // region, not the clip region.
+ pixman_region32_intersect(
+ &reg_visible_local, &reg_visible_local, &reg_bound_local);
+ pixman_region32_fini(&reg_bound_local);
+ }
+
+ auto new_img = ps->backend_data->ops->clone_image(
+ ps->backend_data, win_image, &reg_visible_local);
+ auto reg_frame = win_get_region_frame_local_by_val(w);
+ double alpha = additional_alpha*w->opacity;
+ ps->backend_data->ops->set_image_property(
+ ps->backend_data, IMAGE_PROPERTY_OPACITY, new_img, &alpha);
+ ps->backend_data->ops->image_op(
+ ps->backend_data, IMAGE_OP_APPLY_ALPHA, new_img, &reg_frame,
+ &reg_visible_local, (double[]){w->frame_opacity});
+ pixman_region32_fini(&reg_frame);
+ ps->backend_data->ops->compose(ps->backend_data, new_img,
+ w->g.x, w->g.y,
+ w->g.x + w->widthb, w->g.y + w->heightb,
+ reg_paint_in_bound, reg_visible);
+ ps->backend_data->ops->release_image(ps->backend_data, new_img);
+ pixman_region32_fini(&reg_visible_local);
+}
+
+/// paint all windows
+void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
+ if (ps->o.xrender_sync_fence) {
+ if (ps->xsync_exists && !x_fence_sync(ps->c, ps->sync_fence)) {
+ log_error("x_fence_sync failed, xrender-sync-fence will be "
+ "disabled from now on.");
+ xcb_sync_destroy_fence(ps->c, ps->sync_fence);
+ ps->sync_fence = XCB_NONE;
+ ps->o.xrender_sync_fence = false;
+ ps->xsync_exists = false;
+ }
+ }
+ // All painting will be limited to the damage, if _some_ of
+ // the paints bleed out of the damage region, it will destroy
+ // part of the image we want to reuse
+ region_t reg_damage;
+ if (!ignore_damage) {
+ reg_damage = get_damage(ps, ps->o.monitor_repaint || !ps->o.use_damage);
+ } else {
+ pixman_region32_init(&reg_damage);
+ pixman_region32_copy(&reg_damage, &ps->screen_reg);
+ }
+
+ if (!pixman_region32_not_empty(&reg_damage)) {
+ pixman_region32_fini(&reg_damage);
+ return;
+ }
+
+#ifdef DEBUG_REPAINT
+ static struct timespec last_paint = {0};
+#endif
+
+ // <damage-note>
+ // If use_damage is enabled, we MUST make sure only the damaged regions of the
+ // screen are ever touched by the compositor. The reason is that at the beginning
+ // of each render, we clear the damaged regions with the wallpaper, and nothing
+ // else. If later during the render we changed anything outside the damaged
+ // region, that won't be cleared by the next render, and will thus accumulate.
+ // (e.g. if shadow is drawn outside the damaged region, it will become thicker and
+ // thicker over time.)
+
+ /// The adjusted damaged regions
+ region_t reg_paint;
+ assert(ps->o.blur_method != BLUR_METHOD_INVALID);
+ if (ps->o.blur_method != BLUR_METHOD_NONE && ps->backend_data->ops->get_blur_size) {
+ int blur_width, blur_height;
+ ps->backend_data->ops->get_blur_size(ps->backend_blur_context,
+ &blur_width, &blur_height);
+
+ // The region of screen a given window influences will be smeared
+ // out by blur. With more windows on top of the given window, the
+ // influences region will be smeared out more.
+ //
+ // Also, blurring requires data slightly outside the area that needs
+ // to be blurred. The more semi-transparent windows are stacked on top
+ // of each other, the larger the area will be.
+ //
+ // Instead of accurately calculate how much bigger the damage
+ // region will be because of blur, we assume the worst case here.
+ // That is, the damaged window is at the bottom of the stack, and
+ // all other windows have semi-transparent background
+ int resize_factor = 1;
+ if (t) {
+ resize_factor = t->stacking_rank;
+ }
+ resize_region_in_place(&reg_damage, blur_width * resize_factor,
+ blur_height * resize_factor);
+ reg_paint = resize_region(&reg_damage, blur_width * resize_factor,
+ blur_height * resize_factor);
+ pixman_region32_intersect(&reg_paint, &reg_paint, &ps->screen_reg);
+ pixman_region32_intersect(&reg_damage, &reg_damage, &ps->screen_reg);
+ } else {
+ pixman_region32_init(&reg_paint);
+ pixman_region32_copy(&reg_paint, &reg_damage);
+ }
+
+ // A hint to backend, the region that will be visible on screen
+ // backend can optimize based on this info
+ region_t reg_visible;
+ pixman_region32_init(&reg_visible);
+ pixman_region32_copy(&reg_visible, &ps->screen_reg);
+ if (t && !ps->o.transparent_clipping) {
+ // Calculate the region upon which the root window (wallpaper) is to be
+ // painted based on the ignore region of the lowest window, if available
+ //
+ // NOTE If transparent_clipping is enabled, transparent windows are
+ // included in the reg_ignore, but we still want to have the wallpaper
+ // beneath them, so we don't use reg_ignore for wallpaper in that case.
+ pixman_region32_subtract(&reg_visible, &reg_visible, t->reg_ignore);
+ }
+
+ // Region on screen we don't want any shadows on
+ region_t reg_shadow_clip;
+ pixman_region32_init(&reg_shadow_clip);
+
+ if (ps->backend_data->ops->prepare) {
+ ps->backend_data->ops->prepare(ps->backend_data, &reg_paint);
+ }
+
+ if (ps->root_image) {
+ ps->backend_data->ops->compose(ps->backend_data, ps->root_image,
+ 0, 0, ps->root_width, ps->root_height,
+ &reg_paint, &reg_visible);
+ } else {
+ ps->backend_data->ops->fill(ps->backend_data, (struct color){0, 0, 0, 1},
+ &reg_paint);
+ }
+
+ // Windows are sorted from bottom to top
+ // Each window has a reg_ignore, which is the region obscured by all the windows
+ // on top of that window. This is used to reduce the number of pixels painted.
+ //
+ // Whether this is beneficial is to be determined XXX
+ for (auto w = t; w; w = w->prev_trans) {
+ pixman_region32_subtract(&reg_visible, &ps->screen_reg, w->reg_ignore);
+ assert(!(w->flags & WIN_FLAGS_IMAGE_ERROR));
+ assert(!(w->flags & WIN_FLAGS_PIXMAP_STALE));
+ assert(!(w->flags & WIN_FLAGS_PIXMAP_NONE));
+
+ // The bounding shape of the window, in global/target coordinates
+ // reminder: bounding shape contains the WM frame
+ auto reg_bound = win_get_bounding_shape_global_by_val(w);
+
+ // The clip region for the current window, in global/target coordinates
+ // reg_paint_in_bound \in reg_paint
+ region_t reg_paint_in_bound;
+ pixman_region32_init(&reg_paint_in_bound);
+ pixman_region32_intersect(&reg_paint_in_bound, &reg_bound, &reg_paint);
+ if (ps->o.transparent_clipping) {
+ // <transparent-clipping-note>
+ // If transparent_clipping is enabled, we need to be SURE that
+ // things are not drawn inside reg_ignore, because otherwise they
+ // will appear underneath transparent windows.
+ // So here we have make sure reg_paint_in_bound \in reg_visible
+ // There are a few other places below where this is needed as
+ // well.
+ pixman_region32_intersect(&reg_paint_in_bound,
+ &reg_paint_in_bound, &reg_visible);
+ }
+
+ // Blur window background
+ /* TODO(yshui) since the backend might change the content of the window
+ * (e.g. with shaders), we should consult the backend whether the window
+ * is transparent or not. for now we will just rely on the force_win_blend
+ * option */
+ auto real_win_mode = w->mode;
+
+ if (w->blur_background &&
+ (ps->o.force_win_blend || real_win_mode == WMODE_TRANS ||
+ (ps->o.blur_background_frame && real_win_mode == WMODE_FRAME_TRANS))) {
+ // Minimize the region we try to blur, if the window
+ // itself is not opaque, only the frame is.
+
+ double blur_opacity = 1;
+ if (w->opacity < (1.0 / MAX_ALPHA)) {
+ // Hide blur for fully transparent windows.
+ blur_opacity = 0;
+ } else if (w->state == WSTATE_MAPPING) {
+ // Gradually increase the blur intensity during
+ // fading in.
+ assert(w->opacity <= w->opacity_target);
+ blur_opacity = w->opacity / w->opacity_target;
+ } else if (w->state == WSTATE_UNMAPPING ||
+ w->state == WSTATE_DESTROYING) {
+ // Gradually decrease the blur intensity during
+ // fading out.
+ assert(w->opacity <= w->opacity_target_old);
+ blur_opacity = w->opacity / w->opacity_target_old;
+ } else if (w->state == WSTATE_FADING) {
+ if (w->opacity < w->opacity_target &&
+ w->opacity_target_old < (1.0 / MAX_ALPHA)) {
+ // Gradually increase the blur intensity during
+ // fading in.
+ assert(w->opacity <= w->opacity_target);
+ blur_opacity = w->opacity / w->opacity_target;
+ } else if (w->opacity > w->opacity_target &&
+ w->opacity_target < (1.0 / MAX_ALPHA)) {
+ // Gradually decrease the blur intensity during
+ // fading out.
+ assert(w->opacity <= w->opacity_target_old);
+ blur_opacity = w->opacity / w->opacity_target_old;
+ }
+ }
+ assert(blur_opacity >= 0 && blur_opacity <= 1);
+
+ if (real_win_mode == WMODE_TRANS || ps->o.force_win_blend) {
+ // We need to blur the bounding shape of the window
+ // (reg_paint_in_bound = reg_bound \cap reg_paint)
+ ps->backend_data->ops->blur(
+ ps->backend_data, blur_opacity, ps->backend_blur_context,
+ &reg_paint_in_bound, &reg_visible);
+ } else {
+ // Window itself is solid, we only need to blur the frame
+ // region
+
+ // Readability assertions
+ assert(ps->o.blur_background_frame);
+ assert(real_win_mode == WMODE_FRAME_TRANS);
+
+ auto reg_blur = win_get_region_frame_local_by_val(w);
+ pixman_region32_translate(&reg_blur, w->g.x, w->g.y);
+ // make sure reg_blur \in reg_paint
+ pixman_region32_intersect(&reg_blur, &reg_blur, &reg_paint);
+ if (ps->o.transparent_clipping) {
+ // ref: <transparent-clipping-note>
+ pixman_region32_intersect(&reg_blur, &reg_blur,
+ &reg_visible);
+ }
+ ps->backend_data->ops->blur(ps->backend_data, blur_opacity,
+ ps->backend_blur_context,
+ &reg_blur, &reg_visible);
+ pixman_region32_fini(&reg_blur);
+ }
+ }
+
+ // Draw shadow on target
+ if (w->shadow) {
+ assert(!(w->flags & WIN_FLAGS_SHADOW_NONE));
+ // Clip region for the shadow
+ // reg_shadow \in reg_paint
+ auto reg_shadow = win_extents_by_val(w);
+ pixman_region32_intersect(&reg_shadow, &reg_shadow, &reg_paint);
+ if (!ps->o.wintype_option[w->window_type].full_shadow) {
+ pixman_region32_subtract(&reg_shadow, &reg_shadow, &reg_bound);
+ }
+
+ // Mask out the region we don't want shadow on
+ if (pixman_region32_not_empty(&ps->shadow_exclude_reg)) {
+ pixman_region32_subtract(&reg_shadow, &reg_shadow,
+ &ps->shadow_exclude_reg);
+ }
+ if (pixman_region32_not_empty(&reg_shadow_clip)) {
+ pixman_region32_subtract(&reg_shadow, &reg_shadow,
+ &reg_shadow_clip);
+ }
+
+ if (ps->o.xinerama_shadow_crop && w->xinerama_scr >= 0 &&
+ w->xinerama_scr < ps->xinerama_nscrs) {
+ // There can be a window where number of screens is
+ // updated, but the screen number attached to the windows
+ // have not.
+ //
+ // Window screen number will be updated eventually, so
+ // here we just check to make sure we don't access out of
+ // bounds.
+ pixman_region32_intersect(
+ &reg_shadow, &reg_shadow,
+ &ps->xinerama_scr_regs[w->xinerama_scr]);
+ }
+
+ if (ps->o.transparent_clipping) {
+ // ref: <transparent-clipping-note>
+ pixman_region32_intersect(&reg_shadow, &reg_shadow,
+ &reg_visible);
+ }
+
+ assert(w->shadow_image);
+ ps->backend_data->ops->set_image_property(
+ ps->backend_data, IMAGE_PROPERTY_OPACITY, w->shadow_image,
+ &w->opacity);
+ ps->backend_data->ops->compose(
+ ps->backend_data, w->shadow_image,
+ w->g.x + w->shadow_dx, w->g.y + w->shadow_dy,
+ w->g.x + w->shadow_dx + w->shadow_width,
+ w->g.y + w->shadow_dy + w->shadow_height,
+ &reg_shadow, &reg_visible);
+ pixman_region32_fini(&reg_shadow);
+ }
+
+ // Update image properties
+ {
+ double dim_opacity = 0.0;
+ if (w->dim) {
+ dim_opacity = ps->o.inactive_dim;
+ if (!ps->o.inactive_dim_fixed) {
+ dim_opacity *= w->opacity;
+ }
+ }
+
+ ps->backend_data->ops->set_image_property(
+ ps->backend_data, IMAGE_PROPERTY_MAX_BRIGHTNESS, w->win_image,
+ &ps->o.max_brightness);
+ ps->backend_data->ops->set_image_property(
+ ps->backend_data, IMAGE_PROPERTY_INVERTED, w->win_image,
+ &w->invert_color);
+ ps->backend_data->ops->set_image_property(
+ ps->backend_data, IMAGE_PROPERTY_DIM_LEVEL, w->win_image,
+ &dim_opacity);
+ ps->backend_data->ops->set_image_property(
+ ps->backend_data, IMAGE_PROPERTY_OPACITY, w->win_image, &w->opacity);
+ }
+
+ if (w->opacity * MAX_ALPHA < 1) {
+ // We don't need to paint the window body itself if it's
+ // completely transparent.
+ goto skip;
+ }
+
+ if (w->clip_shadow_above) {
+ // Add window bounds to shadow-clip region
+ pixman_region32_union(&reg_shadow_clip, &reg_shadow_clip, &reg_bound);
+ } else {
+ // Remove overlapping window bounds from shadow-clip region
+ pixman_region32_subtract(&reg_shadow_clip, &reg_shadow_clip, &reg_bound);
+ }
+
+ // Draw window on target
+ bool is_animating = 0 <= w->animation_progress && w->animation_progress < 1.0;
+ if (w->frame_opacity == 1 && !is_animating) {
+ ps->backend_data->ops->compose(ps->backend_data, w->win_image,
+ w->g.x, w->g.y,
+ w->g.x + w->widthb, w->g.y + w->heightb,
+ &reg_paint_in_bound, &reg_visible);
+ } else {
+ if (is_animating && w->old_win_image) {
+ assert(w->old_win_image);
+
+ bool resizing =
+ w->g.width != w->pending_g.width ||
+ w->g.height != w->pending_g.height;
+
+ // Only animate opacity here if we are resizing
+ // a transparent window
+ process_window_for_painting(ps, w, w->win_image,
+ 1,
+ &reg_bound, &reg_visible,
+ &reg_paint, &reg_paint_in_bound);
+
+ // Only do this if size changes as otherwise moving
+ // transparent windows will flicker and if you just
+ // move so slightly they will keep flickering
+ if (resizing) {
+ process_window_for_painting(ps, w, w->old_win_image,
+ 1.0 - w->animation_progress,
+ &reg_bound, &reg_visible,
+ &reg_paint, &reg_paint_in_bound);
+ }
+
+ } else {
+ process_window_for_painting(ps, w, w->win_image,
+ 1,
+ &reg_bound, &reg_visible,
+ &reg_paint, &reg_paint_in_bound);
+ }
+ }
+ skip:
+ pixman_region32_fini(&reg_bound);
+ pixman_region32_fini(&reg_paint_in_bound);
+ }
+ pixman_region32_fini(&reg_paint);
+ pixman_region32_fini(&reg_shadow_clip);
+
+ if (ps->o.monitor_repaint) {
+ const struct color DEBUG_COLOR = {0.5, 0, 0, 0.5};
+ auto reg_damage_debug = get_damage(ps, false);
+ ps->backend_data->ops->fill(ps->backend_data, DEBUG_COLOR, &reg_damage_debug);
+ pixman_region32_fini(&reg_damage_debug);
+ }
+
+ // Move the head of the damage ring
+ ps->damage = ps->damage - 1;
+ if (ps->damage < ps->damage_ring) {
+ ps->damage = ps->damage_ring + ps->ndamage - 1;
+ }
+ pixman_region32_clear(ps->damage);
+
+ if (ps->backend_data->ops->present) {
+ // Present the rendered scene
+ // Vsync is done here
+ ps->backend_data->ops->present(ps->backend_data, &reg_damage);
+ }
+
+ pixman_region32_fini(&reg_damage);
+
+#ifdef DEBUG_REPAINT
+ struct timespec now = get_time_timespec();
+ struct timespec diff = {0};
+ timespec_subtract(&diff, &now, &last_paint);
+ log_trace("[ %5ld:%09ld ] ", diff.tv_sec, diff.tv_nsec);
+ last_paint = now;
+ log_trace("paint:");
+ for (win *w = t; w; w = w->prev_trans)
+ log_trace(" %#010lx", w->id);
+#endif
+}
+
+// vim: set noet sw=8 ts=8 :
diff --git a/src/backend/backend.h b/src/backend/backend.h
new file mode 100644
index 0000000..ae107d3
--- /dev/null
+++ b/src/backend/backend.h
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) 2018, Yuxuan Shui <[email protected]>
+
+#pragma once
+
+#include <stdbool.h>
+
+#include "compiler.h"
+#include "config.h"
+#include "driver.h"
+#include "kernel.h"
+#include "region.h"
+#include "types.h"
+#include "x.h"
+
+typedef struct session session_t;
+struct managed_win;
+
+struct ev_loop;
+struct backend_operations;
+
+typedef struct backend_base {
+ struct backend_operations *ops;
+ xcb_connection_t *c;
+ xcb_window_t root;
+ struct ev_loop *loop;
+
+ /// Whether the backend can accept new render request at the moment
+ bool busy;
+ // ...
+} backend_t;
+
+typedef void (*backend_ready_callback_t)(void *);
+
+// When image properties are actually applied to the image, they are applied in a
+// particular order:
+//
+// Color inversion -> Dimming -> Opacity multiply -> Limit maximum brightness
+enum image_properties {
+ // Whether the color of the image is inverted
+ // 1 boolean, default: false
+ IMAGE_PROPERTY_INVERTED,
+ // How much the image is dimmed
+ // 1 double, default: 0
+ IMAGE_PROPERTY_DIM_LEVEL,
+ // Image opacity, i.e. an alpha value multiplied to the alpha channel
+ // 1 double, default: 1
+ IMAGE_PROPERTY_OPACITY,
+ // The effective size of the image, the image will be tiled to fit.
+ // 2 int, default: the actual size of the image
+ IMAGE_PROPERTY_EFFECTIVE_SIZE,
+ // Limit how bright image can be. The image brightness is estimated by averaging
+ // the pixels in the image, and dimming will be applied to scale the average
+ // brightness down to the max brightness value.
+ // 1 double, default: 1
+ IMAGE_PROPERTY_MAX_BRIGHTNESS,
+};
+
+enum image_operations {
+ // Multiply the alpha channel by the argument
+ IMAGE_OP_APPLY_ALPHA,
+};
+
+struct gaussian_blur_args {
+ int size;
+ double deviation;
+};
+
+struct box_blur_args {
+ int size;
+};
+
+struct kernel_blur_args {
+ struct conv **kernels;
+ int kernel_count;
+};
+
+struct dual_kawase_blur_args {
+ int size;
+ int strength;
+};
+
+struct backend_operations {
+ // =========== Initialization ===========
+
+ /// Initialize the backend, prepare for rendering to the target window.
+ /// Here is how you should choose target window:
+ /// 1) if ps->overlay is not XCB_NONE, use that
+ /// 2) use ps->root otherwise
+ // TODO(yshui) make the target window a parameter
+ backend_t *(*init)(session_t *)attr_nonnull(1);
+ void (*deinit)(backend_t *backend_data) attr_nonnull(1);
+
+ /// Called when rendering will be stopped for an unknown amount of
+ /// time (e.g. when screen is unredirected). Free some resources.
+ ///
+ /// Optional, not yet used
+ void (*pause)(backend_t *backend_data, session_t *ps);
+
+ /// Called before rendering is resumed
+ ///
+ /// Optional, not yet used
+ void (*resume)(backend_t *backend_data, session_t *ps);
+
+ /// Called when root property changed, returns the new
+ /// backend_data. Even if the backend_data changed, all
+ /// the existing image data returned by this backend should
+ /// remain valid.
+ ///
+ /// Optional
+ void *(*root_change)(backend_t *backend_data, session_t *ps);
+
+ // =========== Rendering ============
+
+ // NOTE: general idea about reg_paint/reg_op vs reg_visible is that reg_visible is
+ // merely a hint. Ignoring reg_visible entirely don't affect the correctness of
+ // the operation performed. OTOH reg_paint/reg_op is part of the parameters of the
+ // operation, and must be honored in order to complete the operation correctly.
+
+ // NOTE: due to complications introduced by use-damage and blur, the rendering API
+ // is a bit weird. The idea is, `compose` and `blur` have to update a temporary
+ // buffer, because `blur` requires data from an area slightly larger than the area
+ // that will be visible. So the area outside the visible area has to be rendered,
+ // but we have to discard the result (because the result of blurring that area
+ // will be wrong). That's why we cannot render into the back buffer directly.
+ // After rendering is done, `present` is called to update a portion of the actual
+ // back buffer, then present it to the target (or update the target directly,
+ // if not back buffered).
+
+ /// Called before when a new frame starts.
+ ///
+ /// Optional
+ void (*prepare)(backend_t *backend_data, const region_t *reg_damage);
+
+ /**
+ * Paint the content of an image onto the rendering buffer
+ *
+ * @param backend_data the backend data
+ * @param image_data the image to paint
+ * @param dst_x1, dst_y1 the top left corner of the image in the target
+ * @param dst_x2, dst_y2 the top right corner of the image in the target
+ * @param reg_paint the clip region, in target coordinates
+ * @param reg_visible the visible region, in target coordinates
+ */
+ void (*compose)(backend_t *backend_data, void *image_data,
+ int dst_x1, int dst_y1, int dst_x2, int dst_y2,
+ const region_t *reg_paint, const region_t *reg_visible);
+
+ /// Fill rectangle of the rendering buffer, mostly for debug purposes, optional.
+ void (*fill)(backend_t *backend_data, struct color, const region_t *clip);
+
+ /// Blur a given region of the rendering buffer.
+ bool (*blur)(backend_t *backend_data, double opacity, void *blur_ctx,
+ const region_t *reg_blur, const region_t *reg_visible)
+ attr_nonnull(1, 3, 4, 5);
+
+ /// Update part of the back buffer with the rendering buffer, then present the
+ /// back buffer onto the target window (if not back buffered, update part of the
+ /// target window directly).
+ ///
+ /// Optional, if NULL, indicates the backend doesn't have render output
+ ///
+ /// @param region part of the target that should be updated
+ void (*present)(backend_t *backend_data, const region_t *region) attr_nonnull(1, 2);
+
+ /**
+ * Bind a X pixmap to the backend's internal image data structure.
+ *
+ * @param backend_data backend data
+ * @param pixmap X pixmap to bind
+ * @param fmt information of the pixmap's visual
+ * @param owned whether the ownership of the pixmap is transfered to the backend
+ * @return backend internal data structure bound with this pixmap
+ */
+ void *(*bind_pixmap)(backend_t *backend_data, xcb_pixmap_t pixmap,
+ struct xvisual_info fmt, bool owned);
+
+ /// Create a shadow image based on the parameters
+ /// Default implementation: default_backend_render_shadow
+ void *(*render_shadow)(backend_t *backend_data, int width, int height,
+ const conv *kernel, double r, double g, double b, double a);
+
+ // ============ Resource management ===========
+
+ /// Free resources associated with an image data structure
+ void (*release_image)(backend_t *backend_data, void *img_data) attr_nonnull(1, 2);
+
+ // =========== Query ===========
+
+ /// Return if image is not completely opaque.
+ ///
+ /// This function is needed because some backend might change the content of the
+ /// window (e.g. when using a custom shader with the glx backend), so only the
+ /// backend knows if an image is transparent.
+ bool (*is_image_transparent)(backend_t *backend_data, void *image_data)
+ attr_nonnull(1, 2);
+
+ /// Get the age of the buffer content we are currently rendering ontop
+ /// of. The buffer that has just been `present`ed has a buffer age of 1.
+ /// Everytime `present` is called, buffers get older. Return -1 if the
+ /// buffer is empty.
+ ///
+ /// Optional
+ int (*buffer_age)(backend_t *backend_data);
+
+ /// The maximum number buffer_age might return.
+ int max_buffer_age;
+
+ // =========== Post-processing ============
+
+ /* TODO(yshui) Consider preserving the order of image ops.
+ * Currently in both backends, the image ops are applied lazily when needed.
+ * However neither backends preserve the order of image ops, they just applied all
+ * pending lazy ops in a pre-determined fixed order, regardless in which order
+ * they were originally applied. This might lead to inconsistencies.*/
+
+ /**
+ * Change image properties
+ *
+ * @param backend_data backend data
+ * @param prop the property to change
+ * @param image_data an image data structure returned by the backend
+ * @param args property value
+ * @return whether the operation is successful
+ */
+ bool (*set_image_property)(backend_t *backend_data, enum image_properties prop,
+ void *image_data, void *args);
+
+ /**
+ * Manipulate an image. Image properties are untouched.
+ *
+ * @param backend_data backend data
+ * @param op the operation to perform
+ * @param image_data an image data structure returned by the backend
+ * @param reg_op the clip region, define the part of the image to be
+ * operated on.
+ * @param reg_visible define the part of the image that will eventually
+ * be visible on target. this is a hint to the backend
+ * for optimization purposes.
+ * @param args extra arguments, operation specific
+ * @return whether the operation is successful
+ */
+ bool (*image_op)(backend_t *backend_data, enum image_operations op, void *image_data,
+ const region_t *reg_op, const region_t *reg_visible, void *args);
+
+ /**
+ * Read the color of the pixel at given position of the given image. Image
+ * properties have no effect.
+ *
+ * @param backend_data backend_data
+ * @param image_data an image data structure previously returned by the
+ * backend. the image to read pixel from.
+ * @param x, y coordinate of the pixel to read
+ * @param[out] color the color of the pixel
+ * @return whether the operation is successful
+ */
+ bool (*read_pixel)(backend_t *backend_data, void *image_data, int x, int y,
+ struct color *output);
+
+ /// Create another instance of the `image_data`. All `image_op` and
+ /// `set_image_property` calls on the returned image should not affect the
+ /// original image
+ void *(*clone_image)(backend_t *base, const void *image_data,
+ const region_t *reg_visible);
+
+ /// Create a blur context that can be used to call `blur`
+ void *(*create_blur_context)(backend_t *base, enum blur_method, void *args);
+ /// Destroy a blur context
+ void (*destroy_blur_context)(backend_t *base, void *ctx);
+ /// Get how many pixels outside of the blur area is needed for blur
+ void (*get_blur_size)(void *blur_context, int *width, int *height);
+
+ // =========== Hooks ============
+ /// Let the backend hook into the event handling queue
+ /// Not implemented yet
+ void (*set_ready_callback)(backend_t *, backend_ready_callback_t cb);
+ /// Called right after the core has handled its events.
+ /// Not implemented yet
+ void (*handle_events)(backend_t *);
+ // =========== Misc ============
+ /// Return the driver that is been used by the backend
+ enum driver (*detect_driver)(backend_t *backend_data);
+
+ void (*diagnostics)(backend_t *backend_data);
+};
+
+extern struct backend_operations *backend_list[];
+
+void paint_all_new(session_t *ps, struct managed_win *const t, bool ignore_damage)
+ attr_nonnull(1);
diff --git a/src/backend/backend_common.c b/src/backend/backend_common.c
new file mode 100644
index 0000000..c0377d3
--- /dev/null
+++ b/src/backend/backend_common.c
@@ -0,0 +1,480 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) Yuxuan Shui <[email protected]>
+#include <math.h>
+#include <string.h>
+#include <xcb/render.h>
+#include <xcb/xcb_image.h>
+#include <xcb/xcb_renderutil.h>
+
+#include "backend/backend.h"
+#include "backend/backend_common.h"
+#include "common.h"
+#include "config.h"
+#include "kernel.h"
+#include "log.h"
+#include "utils.h"
+#include "win.h"
+#include "x.h"
+
+/**
+ * Generate a 1x1 <code>Picture</code> of a particular color.
+ */
+xcb_render_picture_t solid_picture(xcb_connection_t *c, xcb_drawable_t d, bool argb,
+ double a, double r, double g, double b) {
+ xcb_pixmap_t pixmap;
+ xcb_render_picture_t picture;
+ xcb_render_create_picture_value_list_t pa;
+ xcb_render_color_t col;
+ xcb_rectangle_t rect;
+
+ pixmap = x_create_pixmap(c, argb ? 32 : 8, d, 1, 1);
+ if (!pixmap)
+ return XCB_NONE;
+
+ pa.repeat = 1;
+ picture = x_create_picture_with_standard_and_pixmap(
+ c, argb ? XCB_PICT_STANDARD_ARGB_32 : XCB_PICT_STANDARD_A_8, pixmap,
+ XCB_RENDER_CP_REPEAT, &pa);
+
+ if (!picture) {
+ xcb_free_pixmap(c, pixmap);
+ return XCB_NONE;
+ }
+
+ col.alpha = (uint16_t)(a * 0xffff);
+ col.red = (uint16_t)(r * 0xffff);
+ col.green = (uint16_t)(g * 0xffff);
+ col.blue = (uint16_t)(b * 0xffff);
+
+ rect.x = 0;
+ rect.y = 0;
+ rect.width = 1;
+ rect.height = 1;
+
+ xcb_render_fill_rectangles(c, XCB_RENDER_PICT_OP_SRC, picture, col, 1, &rect);
+ xcb_free_pixmap(c, pixmap);
+
+ return picture;
+}
+
+xcb_image_t *
+make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, int height) {
+ /*
+ * We classify shadows into 4 kinds of regions
+ * r = shadow radius
+ * (0, 0) is the top left of the window itself
+ * -r r width-r width+r
+ * -r +-----+---------+-----+
+ * | 1 | 2 | 1 |
+ * r +-----+---------+-----+
+ * | 2 | 3 | 2 |
+ * height-r +-----+---------+-----+
+ * | 1 | 2 | 1 |
+ * height+r +-----+---------+-----+
+ */
+ xcb_image_t *ximage;
+ const double *shadow_sum = kernel->rsum;
+ assert(shadow_sum);
+ // We only support square kernels for shadow
+ assert(kernel->w == kernel->h);
+ int d = kernel->w;
+ int r = d / 2;
+ int swidth = width + r * 2, sheight = height + r * 2;
+
+ assert(d % 2 == 1);
+ assert(d > 0);
+
+ ximage = xcb_image_create_native(c, to_u16_checked(swidth), to_u16_checked(sheight),
+ XCB_IMAGE_FORMAT_Z_PIXMAP, 8, 0, 0, NULL);
+ if (!ximage) {
+ log_error("failed to create an X image");
+ return 0;
+ }
+
+ unsigned char *data = ximage->data;
+ long sstride = ximage->stride;
+
+ // If the window body is smaller than the kernel, we do convolution directly
+ if (width < r * 2 && height < r * 2) {
+ for (int y = 0; y < sheight; y++) {
+ for (int x = 0; x < swidth; x++) {
+ double sum = sum_kernel_normalized(
+ kernel, d - x - 1, d - y - 1, width, height);
+ data[y * sstride + x] = (uint8_t)(sum * 255.0);
+ }
+ }
+ return ximage;
+ }
+
+ if (height < r * 2) {
+ // Implies width >= r * 2
+ // If the window height is smaller than the kernel, we divide
+ // the window like this:
+ // -r r width-r width+r
+ // +------+-------------+------+
+ // | | | |
+ // +------+-------------+------+
+ for (int y = 0; y < sheight; y++) {
+ for (int x = 0; x < r * 2; x++) {
+ double sum = sum_kernel_normalized(kernel, d - x - 1,
+ d - y - 1, d, height) *
+ 255.0;
+ data[y * sstride + x] = (uint8_t)sum;
+ data[y * sstride + swidth - x - 1] = (uint8_t)sum;
+ }
+ }
+ for (int y = 0; y < sheight; y++) {
+ double sum =
+ sum_kernel_normalized(kernel, 0, d - y - 1, d, height) * 255.0;
+ memset(&data[y * sstride + r * 2], (uint8_t)sum,
+ (size_t)(width - 2 * r));
+ }
+ return ximage;
+ }
+ if (width < r * 2) {
+ // Similarly, for width smaller than kernel
+ for (int y = 0; y < r * 2; y++) {
+ for (int x = 0; x < swidth; x++) {
+ double sum = sum_kernel_normalized(kernel, d - x - 1,
+ d - y - 1, width, d) *
+ 255.0;
+ data[y * sstride + x] = (uint8_t)sum;
+ data[(sheight - y - 1) * sstride + x] = (uint8_t)sum;
+ }
+ }
+ for (int x = 0; x < swidth; x++) {
+ double sum =
+ sum_kernel_normalized(kernel, d - x - 1, 0, width, d) * 255.0;
+ for (int y = r * 2; y < height; y++) {
+ data[y * sstride + x] = (uint8_t)sum;
+ }
+ }
+ return ximage;
+ }
+
+ // Implies: width >= r * 2 && height >= r * 2
+
+ // Fill part 3
+ for (int y = r; y < height + r; y++) {
+ memset(data + sstride * y + r, (uint8_t)(255 * opacity), (size_t)width);
+ }
+
+ // Part 1
+ for (int y = 0; y < r * 2; y++) {
+ for (int x = 0; x < r * 2; x++) {
+ double tmpsum = shadow_sum[y * d + x] * opacity * 255.0;
+ data[y * sstride + x] = (uint8_t)tmpsum;
+ data[(sheight - y - 1) * sstride + x] = (uint8_t)tmpsum;
+ data[(sheight - y - 1) * sstride + (swidth - x - 1)] = (uint8_t)tmpsum;
+ data[y * sstride + (swidth - x - 1)] = (uint8_t)tmpsum;
+ }
+ }
+
+ // Part 2, top/bottom
+ for (int y = 0; y < r * 2; y++) {
+ double tmpsum = shadow_sum[d * y + d - 1] * opacity * 255.0;
+ memset(&data[y * sstride + r * 2], (uint8_t)tmpsum, (size_t)(width - r * 2));
+ memset(&data[(sheight - y - 1) * sstride + r * 2], (uint8_t)tmpsum,
+ (size_t)(width - r * 2));
+ }
+
+ // Part 2, left/right
+ for (int x = 0; x < r * 2; x++) {
+ double tmpsum = shadow_sum[d * (d - 1) + x] * opacity * 255.0;
+ for (int y = r * 2; y < height; y++) {
+ data[y * sstride + x] = (uint8_t)tmpsum;
+ data[y * sstride + (swidth - x - 1)] = (uint8_t)tmpsum;
+ }
+ }
+
+ return ximage;
+}
+
+/**
+ * Generate shadow <code>Picture</code> for a window.
+ */
+bool build_shadow(xcb_connection_t *c, xcb_drawable_t d, double opacity, const int width,
+ const int height, const conv *kernel, xcb_render_picture_t shadow_pixel,
+ xcb_pixmap_t *pixmap, xcb_render_picture_t *pict) {
+ xcb_image_t *shadow_image = NULL;
+ xcb_pixmap_t shadow_pixmap = XCB_NONE, shadow_pixmap_argb = XCB_NONE;
+ xcb_render_picture_t shadow_picture = XCB_NONE, shadow_picture_argb = XCB_NONE;
+ xcb_gcontext_t gc = XCB_NONE;
+
+ shadow_image = make_shadow(c, kernel, opacity, width, height);
+ if (!shadow_image) {
+ log_error("Failed to make shadow");
+ return false;
+ }
+
+ shadow_pixmap = x_create_pixmap(c, 8, d, shadow_image->width, shadow_image->height);
+ shadow_pixmap_argb =
+ x_create_pixmap(c, 32, d, shadow_image->width, shadow_image->height);
+
+ if (!shadow_pixmap || !shadow_pixmap_argb) {
+ log_error("Failed to create shadow pixmaps");
+ goto shadow_picture_err;
+ }
+
+ shadow_picture = x_create_picture_with_standard_and_pixmap(
+ c, XCB_PICT_STANDARD_A_8, shadow_pixmap, 0, NULL);
+ shadow_picture_argb = x_create_picture_with_standard_and_pixmap(
+ c, XCB_PICT_STANDARD_ARGB_32, shadow_pixmap_argb, 0, NULL);
+ if (!shadow_picture || !shadow_picture_argb) {
+ goto shadow_picture_err;
+ }
+
+ gc = x_new_id(c);
+ xcb_create_gc(c, gc, shadow_pixmap, 0, NULL);
+
+ // We need to make room for protocol metadata in the request. The metadata should
+ // be 24 bytes plus padding, let's be generous and give it 1kb
+ auto maximum_image_size = xcb_get_maximum_request_length(c) * 4 - 1024;
+ auto maximum_row =
+ to_u16_checked(clamp(maximum_image_size / shadow_image->stride, 0, UINT16_MAX));
+ if (maximum_row <= 0) {
+ // TODO(yshui) Upload image with XShm
+ log_error("X server request size limit is too restrictive, or the shadow "
+ "image is too wide for us to send a single row of the shadow "
+ "image. Shadow size: %dx%d",
+ width, height);
+ goto shadow_picture_err;
+ }
+
+ for (uint32_t row = 0; row < shadow_image->height; row += maximum_row) {
+ auto batch_height = maximum_row;
+ if (batch_height > shadow_image->height - row) {
+ batch_height = to_u16_checked(shadow_image->height - row);
+ }
+
+ uint32_t offset = row * shadow_image->stride / sizeof(*shadow_image->data);
+ xcb_put_image(c, (uint8_t)shadow_image->format, shadow_pixmap, gc,
+ shadow_image->width, batch_height, 0, to_i16_checked(row),
+ 0, shadow_image->depth, shadow_image->stride * batch_height,
+ shadow_image->data + offset);
+ }
+
+ xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, shadow_pixel, shadow_picture,
+ shadow_picture_argb, 0, 0, 0, 0, 0, 0, shadow_image->width,
+ shadow_image->height);
+
+ *pixmap = shadow_pixmap_argb;
+ *pict = shadow_picture_argb;
+
+ xcb_free_gc(c, gc);
+ xcb_image_destroy(shadow_image);
+ xcb_free_pixmap(c, shadow_pixmap);
+ xcb_render_free_picture(c, shadow_picture);
+
+ return true;
+
+shadow_picture_err:
+ if (shadow_image) {
+ xcb_image_destroy(shadow_image);
+ }
+ if (shadow_pixmap) {
+ xcb_free_pixmap(c, shadow_pixmap);
+ }
+ if (shadow_pixmap_argb) {
+ xcb_free_pixmap(c, shadow_pixmap_argb);
+ }
+ if (shadow_picture) {
+ xcb_render_free_picture(c, shadow_picture);
+ }
+ if (shadow_picture_argb) {
+ xcb_render_free_picture(c, shadow_picture_argb);
+ }
+ if (gc) {
+ xcb_free_gc(c, gc);
+ }
+
+ return false;
+}
+
+void *
+default_backend_render_shadow(backend_t *backend_data, int width, int height,
+ const conv *kernel, double r, double g, double b, double a) {
+ xcb_pixmap_t shadow_pixel = solid_picture(backend_data->c, backend_data->root,
+ true, 1, r, g, b),
+ shadow = XCB_NONE;
+ xcb_render_picture_t pict = XCB_NONE;
+
+ if (!build_shadow(backend_data->c, backend_data->root, a, width, height, kernel,
+ shadow_pixel, &shadow, &pict)) {
+ return NULL;
+ }
+
+ auto visual = x_get_visual_for_standard(backend_data->c, XCB_PICT_STANDARD_ARGB_32);
+ void *ret = backend_data->ops->bind_pixmap(
+ backend_data, shadow, x_get_visual_info(backend_data->c, visual), true);
+ xcb_render_free_picture(backend_data->c, pict);
+ return ret;
+}
+
+static struct conv **generate_box_blur_kernel(struct box_blur_args *args, int *kernel_count) {
+ int r = args->size * 2 + 1;
+ assert(r > 0);
+ auto ret = ccalloc(2, struct conv *);
+ ret[0] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r);
+ ret[1] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r);
+ ret[0]->w = r;
+ ret[0]->h = 1;
+ ret[1]->w = 1;
+ ret[1]->h = r;
+ for (int i = 0; i < r; i++) {
+ ret[0]->data[i] = 1;
+ ret[1]->data[i] = 1;
+ }
+ *kernel_count = 2;
+ return ret;
+}
+
+static struct conv **
+generate_gaussian_blur_kernel(struct gaussian_blur_args *args, int *kernel_count) {
+ int r = args->size * 2 + 1;
+ assert(r > 0);
+ auto ret = ccalloc(2, struct conv *);
+ ret[0] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r);
+ ret[1] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r);
+ ret[0]->w = r;
+ ret[0]->h = 1;
+ ret[1]->w = 1;
+ ret[1]->h = r;
+ for (int i = 0; i <= args->size; i++) {
+ ret[0]->data[i] = ret[0]->data[r - i - 1] =
+ 1.0 / (sqrt(2.0 * M_PI) * args->deviation) *
+ exp(-(args->size - i) * (args->size - i) /
+ (2 * args->deviation * args->deviation));
+ ret[1]->data[i] = ret[1]->data[r - i - 1] = ret[0]->data[i];
+ }
+ *kernel_count = 2;
+ return ret;
+}
+
+/// Generate blur kernels for gaussian and box blur methods. Generated kernel is not
+/// normalized, and the center element will always be 1.
+struct conv **generate_blur_kernel(enum blur_method method, void *args, int *kernel_count) {
+ switch (method) {
+ case BLUR_METHOD_BOX: return generate_box_blur_kernel(args, kernel_count);
+ case BLUR_METHOD_GAUSSIAN:
+ return generate_gaussian_blur_kernel(args, kernel_count);
+ default: break;
+ }
+ return NULL;
+}
+
+/// Generate kernel parameters for dual-kawase blur method. Falls back on approximating
+/// standard gauss radius if strength is zero or below.
+struct dual_kawase_params *generate_dual_kawase_params(void *args) {
+ struct dual_kawase_blur_args *blur_args = args;
+ static const struct {
+ int iterations; /// Number of down- and upsample iterations
+ float offset; /// Sample offset in half-pixels
+ int min_radius; /// Approximate gauss-blur with at least this
+ /// radius and std-deviation
+ } strength_levels[20] = {
+ {.iterations = 1, .offset = 1.25f, .min_radius = 1}, // LVL 1
+ {.iterations = 1, .offset = 2.25f, .min_radius = 6}, // LVL 2
+ {.iterations = 2, .offset = 2.00f, .min_radius = 11}, // LVL 3
+ {.iterations = 2, .offset = 3.00f, .min_radius = 17}, // LVL 4
+ {.iterations = 2, .offset = 4.25f, .min_radius = 24}, // LVL 5
+ {.iterations = 3, .offset = 2.50f, .min_radius = 32}, // LVL 6
+ {.iterations = 3, .offset = 3.25f, .min_radius = 40}, // LVL 7
+ {.iterations = 3, .offset = 4.25f, .min_radius = 51}, // LVL 8
+ {.iterations = 3, .offset = 5.50f, .min_radius = 67}, // LVL 9
+ {.iterations = 4, .offset = 3.25f, .min_radius = 83}, // LVL 10
+ {.iterations = 4, .offset = 4.00f, .min_radius = 101}, // LVL 11
+ {.iterations = 4, .offset = 5.00f, .min_radius = 123}, // LVL 12
+ {.iterations = 4, .offset = 6.00f, .min_radius = 148}, // LVL 13
+ {.iterations = 4, .offset = 7.25f, .min_radius = 178}, // LVL 14
+ {.iterations = 4, .offset = 8.25f, .min_radius = 208}, // LVL 15
+ {.iterations = 5, .offset = 4.50f, .min_radius = 236}, // LVL 16
+ {.iterations = 5, .offset = 5.25f, .min_radius = 269}, // LVL 17
+ {.iterations = 5, .offset = 6.25f, .min_radius = 309}, // LVL 18
+ {.iterations = 5, .offset = 7.25f, .min_radius = 357}, // LVL 19
+ {.iterations = 5, .offset = 8.50f, .min_radius = 417}, // LVL 20
+ };
+
+ auto params = ccalloc(1, struct dual_kawase_params);
+ params->iterations = 0;
+ params->offset = 1.0f;
+
+ if (blur_args->strength <= 0 && blur_args->size) {
+ // find highest level that approximates blur-strength with the selected
+ // gaussian blur-radius
+ int lvl = 1;
+ while (strength_levels[lvl - 1].min_radius < blur_args->size && lvl < 20) {
+ ++lvl;
+ }
+ blur_args->strength = lvl;
+ }
+ if (blur_args->strength <= 0) {
+ // default value
+ blur_args->strength = 5;
+ }
+
+ assert(blur_args->strength > 0 && blur_args->strength <= 20);
+ params->iterations = strength_levels[blur_args->strength - 1].iterations;
+ params->offset = strength_levels[blur_args->strength - 1].offset;
+
+ // Expand sample area to cover the smallest texture / highest selected iteration:
+ // - Smallest texture dimensions are halved `iterations`-times
+ // - Upsample needs pixels two-times `offset` away from the border
+ // - Plus one for interpolation differences
+ params->expand = (1 << params->iterations) * 2 * (int)ceil(params->offset) + 1;
+
+ return params;
+}
+
+void *default_clone_image(backend_t *base attr_unused, const void *image_data,
+ const region_t *reg_visible attr_unused) {
+ auto new_img = ccalloc(1, struct backend_image);
+ *new_img = *(struct backend_image *)image_data;
+ new_img->inner->refcount++;
+ return new_img;
+}
+
+bool default_set_image_property(backend_t *base attr_unused, enum image_properties op,
+ void *image_data, void *arg) {
+ struct backend_image *tex = image_data;
+ int *iargs = arg;
+ bool *bargs = arg;
+ double *dargs = arg;
+ switch (op) {
+ case IMAGE_PROPERTY_INVERTED: tex->color_inverted = bargs[0]; break;
+ case IMAGE_PROPERTY_DIM_LEVEL: tex->dim = dargs[0]; break;
+ case IMAGE_PROPERTY_OPACITY: tex->opacity = dargs[0]; break;
+ case IMAGE_PROPERTY_EFFECTIVE_SIZE:
+ // texture is already set to repeat, so nothing else we need to do
+ tex->ewidth = iargs[0];
+ tex->eheight = iargs[1];
+ break;
+ case IMAGE_PROPERTY_MAX_BRIGHTNESS: tex->max_brightness = dargs[0]; break;
+ }
+
+ return true;
+}
+
+bool default_is_image_transparent(backend_t *base attr_unused, void *image_data) {
+ struct backend_image *img = image_data;
+ return img->opacity < 1 || img->inner->has_alpha;
+}
+
+struct backend_image *default_new_backend_image(int w, int h) {
+ auto ret = ccalloc(1, struct backend_image);
+ ret->opacity = 1;
+ ret->dim = 0;
+ ret->max_brightness = 1;
+ ret->eheight = h;
+ ret->ewidth = w;
+ ret->color_inverted = false;
+ return ret;
+}
+
+void init_backend_base(struct backend_base *base, session_t *ps) {
+ base->c = ps->c;
+ base->loop = ps->loop;
+ base->root = ps->root;
+ base->busy = false;
+ base->ops = NULL;
+}
diff --git a/src/backend/backend_common.h b/src/backend/backend_common.h
new file mode 100644
index 0000000..5c9c806
--- /dev/null
+++ b/src/backend/backend_common.h
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) Yuxuan Shui <[email protected]>
+#pragma once
+
+#include <xcb/render.h>
+#include <xcb/xcb_image.h>
+
+#include <stdbool.h>
+
+#include "backend.h"
+#include "config.h"
+#include "region.h"
+
+typedef struct session session_t;
+typedef struct win win;
+typedef struct conv conv;
+typedef struct backend_base backend_t;
+struct backend_operations;
+
+struct dual_kawase_params {
+ /// Number of downsample passes
+ int iterations;
+ /// Pixel offset for down- and upsample
+ float offset;
+ /// Save area around blur target (@ref resize_width, @ref resize_height)
+ int expand;
+};
+
+struct backend_image_inner_base {
+ int refcount;
+ bool has_alpha;
+};
+
+struct backend_image {
+ // Backend dependent inner image data
+ struct backend_image_inner_base *inner;
+ double opacity;
+ double dim;
+ double max_brightness;
+ // Effective size of the image
+ int ewidth, eheight;
+ bool color_inverted;
+};
+
+bool build_shadow(xcb_connection_t *, xcb_drawable_t, double opacity, int width,
+ int height, const conv *kernel, xcb_render_picture_t shadow_pixel,
+ xcb_pixmap_t *pixmap, xcb_render_picture_t *pict);
+
+xcb_render_picture_t solid_picture(xcb_connection_t *, xcb_drawable_t, bool argb,
+ double a, double r, double g, double b);
+
+xcb_image_t *
+make_shadow(xcb_connection_t *c, const conv *kernel, double opacity, int width, int height);
+
+/// The default implementation of `is_win_transparent`, it simply looks at win::mode. So
+/// this is not suitable for backends that alter the content of windows
+bool default_is_win_transparent(void *, win *, void *);
+
+/// The default implementation of `is_frame_transparent`, it uses win::frame_opacity. Same
+/// caveat as `default_is_win_transparent` applies.
+bool default_is_frame_transparent(void *, win *, void *);
+
+void *
+default_backend_render_shadow(backend_t *backend_data, int width, int height,
+ const conv *kernel, double r, double g, double b, double a);
+
+void init_backend_base(struct backend_base *base, session_t *ps);
+
+struct conv **generate_blur_kernel(enum blur_method method, void *args, int *kernel_count);
+struct dual_kawase_params *generate_dual_kawase_params(void *args);
+
+void *default_clone_image(backend_t *base, const void *image_data, const region_t *reg);
+void *default_resize_image(backend_t *base, const void *image_data, uint16_t desired_width,
+ uint16_t desired_height, const region_t *reg);
+bool default_is_image_transparent(backend_t *base attr_unused, void *image_data);
+bool default_set_image_property(backend_t *base attr_unused, enum image_properties op,
+ void *image_data, void *arg);
+struct backend_image *default_new_backend_image(int w, int h);
diff --git a/src/backend/driver.c b/src/backend/driver.c
new file mode 100644
index 0000000..a41d2fd
--- /dev/null
+++ b/src/backend/driver.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) Yuxuan Shui <[email protected]>
+#include <stdlib.h>
+#include <string.h>
+
+#include <xcb/randr.h>
+#include <xcb/xcb.h>
+
+#include "backend/backend.h"
+#include "backend/driver.h"
+#include "common.h"
+#include "compiler.h"
+#include "log.h"
+
+/// Apply driver specified global workarounds. It's safe to call this multiple times.
+void apply_driver_workarounds(struct session *ps, enum driver driver) {
+ if (driver & DRIVER_NVIDIA) {
+ // setenv("__GL_YIELD", "usleep", true);
+ setenv("__GL_MaxFramesAllowed", "1", true);
+ ps->o.xrender_sync_fence = true;
+ }
+}
+
+enum driver detect_driver(xcb_connection_t *c, backend_t *backend_data, xcb_window_t window) {
+ enum driver ret = 0;
+ // First we try doing backend agnostic detection using RANDR
+ // There's no way to query the X server about what driver is loaded, so RANDR is
+ // our best shot.
+ auto randr_version = xcb_randr_query_version_reply(
+ c, xcb_randr_query_version(c, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION),
+ NULL);
+ if (randr_version &&
+ (randr_version->major_version > 1 || randr_version->minor_version >= 4)) {
+ auto r = xcb_randr_get_providers_reply(
+ c, xcb_randr_get_providers(c, window), NULL);
+ if (r == NULL) {
+ log_warn("Failed to get RANDR providers");
+ free(randr_version);
+ return 0;
+ }
+
+ auto providers = xcb_randr_get_providers_providers(r);
+ for (auto i = 0; i < xcb_randr_get_providers_providers_length(r); i++) {
+ auto r2 = xcb_randr_get_provider_info_reply(
+ c, xcb_randr_get_provider_info(c, providers[i], r->timestamp), NULL);
+ if (r2 == NULL) {
+ continue;
+ }
+ if (r2->num_outputs == 0) {
+ free(r2);
+ continue;
+ }
+
+ auto name_len = xcb_randr_get_provider_info_name_length(r2);
+ assert(name_len >= 0);
+ auto name =
+ strndup(xcb_randr_get_provider_info_name(r2), (size_t)name_len);
+ if (strcasestr(name, "modesetting") != NULL) {
+ ret |= DRIVER_MODESETTING;
+ } else if (strcasestr(name, "Radeon") != NULL) {
+ // Be conservative, add both radeon drivers
+ ret |= DRIVER_AMDGPU | DRIVER_RADEON;
+ } else if (strcasestr(name, "NVIDIA") != NULL) {
+ ret |= DRIVER_NVIDIA;
+ } else if (strcasestr(name, "nouveau") != NULL) {
+ ret |= DRIVER_NOUVEAU;
+ } else if (strcasestr(name, "Intel") != NULL) {
+ ret |= DRIVER_INTEL;
+ }
+ free(name);
+ free(r2);
+ }
+ free(r);
+ }
+ free(randr_version);
+
+ // If the backend supports driver detection, use that as well
+ if (backend_data && backend_data->ops->detect_driver) {
+ ret |= backend_data->ops->detect_driver(backend_data);
+ }
+ return ret;
+}
diff --git a/src/backend/driver.h b/src/backend/driver.h
new file mode 100644
index 0000000..a37cda3
--- /dev/null
+++ b/src/backend/driver.h
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) Yuxuan Shui <[email protected]>
+
+#pragma once
+
+#include <stddef.h>
+#include <stdio.h>
+#include <xcb/xcb.h>
+
+#include "utils.h"
+
+struct session;
+struct backend_base;
+
+// A list of known driver quirks:
+// * NVIDIA driver doesn't like seeing the same pixmap under different
+// ids, so avoid naming the pixmap again when it didn't actually change.
+
+/// A list of possible drivers.
+/// The driver situation is a bit complicated. There are two drivers we care about: the
+/// DDX, and the OpenGL driver. They are usually paired, but not always, since there is
+/// also the generic modesetting driver.
+/// This enum represents _both_ drivers.
+enum driver {
+ DRIVER_AMDGPU = 1, // AMDGPU for DDX, radeonsi for OpenGL
+ DRIVER_RADEON = 2, // ATI for DDX, mesa r600 for OpenGL
+ DRIVER_FGLRX = 4,
+ DRIVER_NVIDIA = 8,
+ DRIVER_NOUVEAU = 16,
+ DRIVER_INTEL = 32,
+ DRIVER_MODESETTING = 64,
+};
+
+static const char *driver_names[] = {
+ "AMDGPU", "Radeon", "fglrx", "NVIDIA", "nouveau", "Intel", "modesetting",
+};
+
+/// Return a list of all drivers currently in use by the X server.
+/// Note, this is a best-effort test, so no guarantee all drivers will be detected.
+enum driver detect_driver(xcb_connection_t *, struct backend_base *, xcb_window_t);
+
+/// Apply driver specified global workarounds. It's safe to call this multiple times.
+void apply_driver_workarounds(struct session *ps, enum driver);
+
+// Print driver names to stdout, for diagnostics
+static inline void print_drivers(enum driver drivers) {
+ const char *seen_drivers[ARR_SIZE(driver_names)];
+ int driver_count = 0;
+ for (size_t i = 0; i < ARR_SIZE(driver_names); i++) {
+ if (drivers & (1ul << i)) {
+ seen_drivers[driver_count++] = driver_names[i];
+ }
+ }
+
+ if (driver_count > 0) {
+ printf("%s", seen_drivers[0]);
+ for (int i = 1; i < driver_count; i++) {
+ printf(", %s", seen_drivers[i]);
+ }
+ }
+ printf("\n");
+}
diff --git a/src/backend/dummy/dummy.c b/src/backend/dummy/dummy.c
new file mode 100644
index 0000000..a057b97
--- /dev/null
+++ b/src/backend/dummy/dummy.c
@@ -0,0 +1,174 @@
+#include <uthash.h>
+#include <xcb/xcb.h>
+
+#include "backend/backend.h"
+#include "backend/backend_common.h"
+#include "common.h"
+#include "compiler.h"
+#include "config.h"
+#include "log.h"
+#include "region.h"
+#include "types.h"
+#include "uthash_extra.h"
+#include "utils.h"
+#include "x.h"
+
+struct dummy_image {
+ xcb_pixmap_t pixmap;
+ bool transparent;
+ int *refcount;
+ UT_hash_handle hh;
+};
+
+struct dummy_data {
+ struct backend_base base;
+ struct dummy_image *images;
+};
+
+struct backend_base *dummy_init(struct session *ps attr_unused) {
+ auto ret = (struct backend_base *)ccalloc(1, struct dummy_data);
+ ret->c = ps->c;
+ ret->loop = ps->loop;
+ ret->root = ps->root;
+ ret->busy = false;
+ return ret;
+}
+
+void dummy_deinit(struct backend_base *data) {
+ auto dummy = (struct dummy_data *)data;
+ HASH_ITER2(dummy->images, img) {
+ log_warn("Backend image for pixmap %#010x is not freed", img->pixmap);
+ HASH_DEL(dummy->images, img);
+ free(img->refcount);
+ free(img);
+ }
+ free(dummy);
+}
+
+static void dummy_check_image(struct backend_base *base, const struct dummy_image *img) {
+ auto dummy = (struct dummy_data *)base;
+ struct dummy_image *tmp = NULL;
+ HASH_FIND_INT(dummy->images, &img->pixmap, tmp);
+ if (!tmp) {
+ log_warn("Using an invalid (possibly freed) image");
+ assert(false);
+ }
+ assert(*tmp->refcount > 0);
+}
+
+void dummy_compose(struct backend_base *base, void *image, int dst_x1 attr_unused,
+ int dst_y1 attr_unused, int dst_x2 attr_unused, int dst_y2 attr_unused,
+ const region_t *reg_paint attr_unused, const region_t *reg_visible attr_unused) {
+ dummy_check_image(base, image);
+}
+
+void dummy_fill(struct backend_base *backend_data attr_unused, struct color c attr_unused,
+ const region_t *clip attr_unused) {
+}
+
+bool dummy_blur(struct backend_base *backend_data attr_unused, double opacity attr_unused,
+ void *blur_ctx attr_unused, const region_t *reg_blur attr_unused,
+ const region_t *reg_visible attr_unused) {
+ return true;
+}
+
+void *dummy_bind_pixmap(struct backend_base *base, xcb_pixmap_t pixmap,
+ struct xvisual_info fmt, bool owned attr_unused) {
+ auto dummy = (struct dummy_data *)base;
+ struct dummy_image *img = NULL;
+ HASH_FIND_INT(dummy->images, &pixmap, img);
+ if (img) {
+ (*img->refcount)++;
+ return img;
+ }
+
+ img = ccalloc(1, struct dummy_image);
+ img->pixmap = pixmap;
+ img->transparent = fmt.alpha_size != 0;
+ img->refcount = ccalloc(1, int);
+ *img->refcount = 1;
+
+ HASH_ADD_INT(dummy->images, pixmap, img);
+ return (void *)img;
+}
+
+void dummy_release_image(backend_t *base, void *image) {
+ auto dummy = (struct dummy_data *)base;
+ auto img = (struct dummy_image *)image;
+ assert(*img->refcount > 0);
+ (*img->refcount)--;
+ if (*img->refcount == 0) {
+ HASH_DEL(dummy->images, img);
+ free(img->refcount);
+ free(img);
+ }
+}
+
+bool dummy_is_image_transparent(struct backend_base *base, void *image) {
+ auto img = (struct dummy_image *)image;
+ dummy_check_image(base, img);
+ return img->transparent;
+}
+
+int dummy_buffer_age(struct backend_base *base attr_unused) {
+ return 2;
+}
+
+bool dummy_image_op(struct backend_base *base, enum image_operations op attr_unused,
+ void *image, const region_t *reg_op attr_unused,
+ const region_t *reg_visible attr_unused, void *args attr_unused) {
+ dummy_check_image(base, image);
+ return true;
+}
+
+bool dummy_set_image_property(struct backend_base *base, enum image_properties prop attr_unused,
+ void *image, void *arg attr_unused) {
+ dummy_check_image(base, image);
+ return true;
+}
+
+void *dummy_clone_image(struct backend_base *base, const void *image,
+ const region_t *reg_visible attr_unused) {
+ auto img = (const struct dummy_image *)image;
+ dummy_check_image(base, img);
+ (*img->refcount)++;
+ return (void *)img;
+}
+
+void *dummy_create_blur_context(struct backend_base *base attr_unused,
+ enum blur_method method attr_unused, void *args attr_unused) {
+ static int dummy_context;
+ return &dummy_context;
+}
+
+void dummy_destroy_blur_context(struct backend_base *base attr_unused, void *ctx attr_unused) {
+}
+
+void dummy_get_blur_size(void *ctx attr_unused, int *width, int *height) {
+ // These numbers are arbitrary, to make sure the reisze_region code path is
+ // covered.
+ *width = 5;
+ *height = 5;
+}
+
+struct backend_operations dummy_ops = {
+ .init = dummy_init,
+ .deinit = dummy_deinit,
+ .compose = dummy_compose,
+ .fill = dummy_fill,
+ .blur = dummy_blur,
+ .bind_pixmap = dummy_bind_pixmap,
+ .render_shadow = default_backend_render_shadow,
+ .release_image = dummy_release_image,
+ .is_image_transparent = dummy_is_image_transparent,
+ .buffer_age = dummy_buffer_age,
+ .max_buffer_age = 5,
+
+ .image_op = dummy_image_op,
+ .clone_image = dummy_clone_image,
+ .set_image_property = dummy_set_image_property,
+ .create_blur_context = dummy_create_blur_context,
+ .destroy_blur_context = dummy_destroy_blur_context,
+ .get_blur_size = dummy_get_blur_size,
+
+};
diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c
new file mode 100644
index 0000000..8cc5a05
--- /dev/null
+++ b/src/backend/gl/gl_common.c
@@ -0,0 +1,1922 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) Yuxuan Shui <[email protected]>
+#include <GL/gl.h>
+#include <GL/glext.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <xcb/render.h> // for xcb_render_fixed_t, XXX
+
+#include "backend/backend.h"
+#include "common.h"
+#include "compiler.h"
+#include "config.h"
+#include "kernel.h"
+#include "log.h"
+#include "region.h"
+#include "string_utils.h"
+#include "types.h"
+#include "utils.h"
+
+#include "backend/backend_common.h"
+#include "backend/gl/gl_common.h"
+
+#define GLSL(version, ...) "#version " #version "\n" #__VA_ARGS__
+#define QUOTE(...) #__VA_ARGS__
+
+static const GLuint vert_coord_loc = 0;
+static const GLuint vert_in_texcoord_loc = 1;
+
+struct gl_blur_context {
+ enum blur_method method;
+ gl_blur_shader_t *blur_shader;
+
+ /// Temporary textures used for blurring
+ GLuint *blur_textures;
+ int blur_texture_count;
+ /// Temporary fbos used for blurring
+ GLuint *blur_fbos;
+ int blur_fbo_count;
+
+ /// Cached dimensions of each blur_texture. They are the same size as the target,
+ /// so they are always big enough without resizing.
+ /// Turns out calling glTexImage to resize is expensive, so we avoid that.
+ struct texture_size {
+ int width;
+ int height;
+ } * texture_sizes;
+
+ /// Cached dimensions of the offscreen framebuffer. It's the same size as the
+ /// target but is expanded in either direction by resize_width / resize_height.
+ int fb_width, fb_height;
+
+ /// How much do we need to resize the damaged region for blurring.
+ int resize_width, resize_height;
+
+ int npasses;
+};
+
+static GLint glGetUniformLocationChecked(GLuint p, const char *name) {
+ auto ret = glGetUniformLocation(p, name);
+ if (ret < 0) {
+ log_info("Failed to get location of uniform '%s'. This is normal when "
+ "using custom shaders.",
+ name);
+ }
+ return ret;
+}
+
+GLuint gl_create_shader(GLenum shader_type, const char *shader_str) {
+ log_trace("===\n%s\n===", shader_str);
+
+ bool success = false;
+ GLuint shader = glCreateShader(shader_type);
+ if (!shader) {
+ log_error("Failed to create shader with type %#x.", shader_type);
+ goto end;
+ }
+ glShaderSource(shader, 1, &shader_str, NULL);
+ glCompileShader(shader);
+
+ // Get shader status
+ {
+ GLint status = GL_FALSE;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
+ if (GL_FALSE == status) {
+ GLint log_len = 0;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len);
+ if (log_len) {
+ char log[log_len + 1];
+ glGetShaderInfoLog(shader, log_len, NULL, log);
+ log_error("Failed to compile shader with type %d: %s",
+ shader_type, log);
+ }
+ goto end;
+ }
+ }
+
+ success = true;
+
+end:
+ if (shader && !success) {
+ glDeleteShader(shader);
+ shader = 0;
+ }
+
+ return shader;
+}
+
+GLuint gl_create_program(const GLuint *const shaders, int nshaders) {
+ bool success = false;
+ GLuint program = glCreateProgram();
+ if (!program) {
+ log_error("Failed to create program.");
+ goto end;
+ }
+
+ for (int i = 0; i < nshaders; ++i)
+ glAttachShader(program, shaders[i]);
+ glLinkProgram(program);
+
+ // Get program status
+ {
+ GLint status = GL_FALSE;
+ glGetProgramiv(program, GL_LINK_STATUS, &status);
+ if (GL_FALSE == status) {
+ GLint log_len = 0;
+ glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_len);
+ if (log_len) {
+ char log[log_len + 1];
+ glGetProgramInfoLog(program, log_len, NULL, log);
+ log_error("Failed to link program: %s", log);
+ }
+ goto end;
+ }
+ }
+ success = true;
+
+end:
+ if (program) {
+ for (int i = 0; i < nshaders; ++i)
+ glDetachShader(program, shaders[i]);
+ }
+ if (program && !success) {
+ glDeleteProgram(program);
+ program = 0;
+ }
+
+ return program;
+}
+
+/**
+ * @brief Create a program from vertex and fragment shader strings.
+ */
+GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str) {
+ GLuint vert_shader = 0;
+ GLuint frag_shader = 0;
+ GLuint prog = 0;
+
+ if (vert_shader_str)
+ vert_shader = gl_create_shader(GL_VERTEX_SHADER, vert_shader_str);
+ if (frag_shader_str)
+ frag_shader = gl_create_shader(GL_FRAGMENT_SHADER, frag_shader_str);
+
+ {
+ GLuint shaders[2];
+ int count = 0;
+ if (vert_shader) {
+ shaders[count++] = vert_shader;
+ }
+ if (frag_shader) {
+ shaders[count++] = frag_shader;
+ }
+ if (count) {
+ prog = gl_create_program(shaders, count);
+ }
+ }
+
+ if (vert_shader)
+ glDeleteShader(vert_shader);
+ if (frag_shader)
+ glDeleteShader(frag_shader);
+
+ return prog;
+}
+
+static void gl_free_prog_main(gl_win_shader_t *pprogram) {
+ if (!pprogram)
+ return;
+ if (pprogram->prog) {
+ glDeleteProgram(pprogram->prog);
+ pprogram->prog = 0;
+ }
+}
+
+/*
+ * @brief Implements recursive part of gl_average_texture_color.
+ *
+ * @note In order to reduce number of textures which needs to be
+ * allocated and deleted during this recursive render
+ * we reuse the same two textures for render source and
+ * destination simply by alterating between them.
+ * Unfortunately on first iteration source_texture might
+ * be read-only. In this case we will select auxiliary_texture as
+ * destination_texture in order not to touch that read-only source
+ * texture in following render iteration.
+ * Otherwise we simply will switch source and destination textures
+ * between each other on each render iteration.
+ */
+static GLuint
+_gl_average_texture_color(backend_t *base, GLuint source_texture, GLuint destination_texture,
+ GLuint auxiliary_texture, GLuint fbo, int width, int height) {
+ const int max_width = 1;
+ const int max_height = 1;
+ const int from_width = next_power_of_two(width);
+ const int from_height = next_power_of_two(height);
+ const int to_width = from_width > max_width ? from_width / 2 : from_width;
+ const int to_height = from_height > max_height ? from_height / 2 : from_height;
+
+ // Prepare coordinates
+ GLint coord[] = {
+ // top left
+ 0, 0, // vertex coord
+ 0, 0, // texture coord
+
+ // top right
+ to_width, 0, // vertex coord
+ width, 0, // texture coord
+
+ // bottom right
+ to_width, to_height, // vertex coord
+ width, height, // texture coord
+
+ // bottom left
+ 0, to_height, // vertex coord
+ 0, height, // texture coord
+ };
+ glBufferSubData(GL_ARRAY_BUFFER, 0, (long)sizeof(*coord) * 16, coord);
+
+ // Prepare framebuffer for new render iteration
+ glBindTexture(GL_TEXTURE_2D, destination_texture);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ destination_texture, 0);
+ gl_check_fb_complete(GL_FRAMEBUFFER);
+
+ // Bind source texture as downscaling shader uniform input
+ glBindTexture(GL_TEXTURE_2D, source_texture);
+
+ // Render into framebuffer
+ glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL);
+
+ // Have we downscaled enough?
+ GLuint result;
+ if (to_width > max_width || to_height > max_height) {
+ GLuint new_source_texture = destination_texture;
+ GLuint new_destination_texture =
+ auxiliary_texture != 0 ? auxiliary_texture : source_texture;
+ result = _gl_average_texture_color(base, new_source_texture,
+ new_destination_texture, 0, fbo,
+ to_width, to_height);
+ } else {
+ result = destination_texture;
+ }
+
+ return result;
+}
+
+/*
+ * @brief Builds a 1x1 texture which has color corresponding to the average of all
+ * pixels of img by recursively rendering into texture of quorter the size (half
+ * width and half height).
+ * Returned texture must not be deleted, since it's owned by the gl_image. It will be
+ * deleted when the gl_image is released.
+ */
+static GLuint gl_average_texture_color(backend_t *base, struct backend_image *img) {
+ auto gd = (struct gl_data *)base;
+ auto inner = (struct gl_texture *)img->inner;
+
+ // Prepare textures which will be used for destination and source of rendering
+ // during downscaling.
+ const int texture_count = ARR_SIZE(inner->auxiliary_texture);
+ if (!inner->auxiliary_texture[0]) {
+ assert(!inner->auxiliary_texture[1]);
+ glGenTextures(texture_count, inner->auxiliary_texture);
+ glActiveTexture(GL_TEXTURE0);
+ for (int i = 0; i < texture_count; i++) {
+ glBindTexture(GL_TEXTURE_2D, inner->auxiliary_texture[i]);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
+ glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR,
+ (GLint[]){0, 0, 0, 0});
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, inner->width,
+ inner->height, 0, GL_BGR, GL_UNSIGNED_BYTE, NULL);
+ }
+ }
+
+ // Prepare framebuffer used for rendering and bind it
+ GLuint fbo;
+ glGenFramebuffers(1, &fbo);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
+ glDrawBuffer(GL_COLOR_ATTACHMENT0);
+
+ // Enable shaders
+ glUseProgram(gd->brightness_shader.prog);
+ glUniform2f(glGetUniformLocationChecked(gd->brightness_shader.prog, "texsize"),
+ (GLfloat)inner->width, (GLfloat)inner->height);
+
+ // Prepare vertex attributes
+ GLuint vao;
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+ GLuint bo[2];
+ glGenBuffers(2, bo);
+ glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
+ glEnableVertexAttribArray(vert_coord_loc);
+ glEnableVertexAttribArray(vert_in_texcoord_loc);
+ glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL);
+ glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE,
+ sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2));
+
+ // Allocate buffers for render input
+ GLint coord[16] = {0};
+ GLuint indices[] = {0, 1, 2, 2, 3, 0};
+ glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 16, coord, GL_DYNAMIC_DRAW);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices,
+ GL_STATIC_DRAW);
+
+ // Do actual recursive render to 1x1 texture
+ GLuint result_texture = _gl_average_texture_color(
+ base, inner->texture, inner->auxiliary_texture[0],
+ inner->auxiliary_texture[1], fbo, inner->width, inner->height);
+
+ // Cleanup vertex attributes
+ glDisableVertexAttribArray(vert_coord_loc);
+ glDisableVertexAttribArray(vert_in_texcoord_loc);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ glDeleteBuffers(2, bo);
+ glBindVertexArray(0);
+ glDeleteVertexArrays(1, &vao);
+
+ // Cleanup shaders
+ glUseProgram(0);
+
+ // Cleanup framebuffers
+ glDeleteFramebuffers(1, &fbo);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+ glDrawBuffer(GL_BACK);
+
+ // Cleanup render textures
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ gl_check_err();
+
+ return result_texture;
+}
+
+/**
+ * Render a region with texture data.
+ *
+ * @param ptex the texture
+ * @param target the framebuffer to render into
+ * @param dst_x,dst_y the top left corner of region where this texture
+ * should go. In OpenGL coordinate system (important!).
+ * @param reg_tgt the clip region, in Xorg coordinate system
+ * @param reg_visible ignored
+ */
+static void _gl_compose(backend_t *base, struct backend_image *img, GLuint target,
+ GLint *coord, GLuint *indices, int nrects) {
+ auto gd = (struct gl_data *)base;
+ auto inner = (struct gl_texture *)img->inner;
+ if (!img || !inner->texture) {
+ log_error("Missing texture.");
+ return;
+ }
+
+ GLuint brightness = 0;
+ if (img->max_brightness < 1.0) {
+ brightness = gl_average_texture_color(base, img);
+ }
+
+ assert(gd->win_shader.prog);
+ glUseProgram(gd->win_shader.prog);
+ if (gd->win_shader.unifm_opacity >= 0) {
+ glUniform1f(gd->win_shader.unifm_opacity, (float)img->opacity);
+ }
+ if (gd->win_shader.unifm_invert_color >= 0) {
+ glUniform1i(gd->win_shader.unifm_invert_color, img->color_inverted);
+ }
+ if (gd->win_shader.unifm_tex >= 0) {
+ glUniform1i(gd->win_shader.unifm_tex, 0);
+ }
+ if (gd->win_shader.unifm_dim >= 0) {
+ glUniform1f(gd->win_shader.unifm_dim, (float)img->dim);
+ }
+ if (gd->win_shader.unifm_brightness >= 0) {
+ glUniform1i(gd->win_shader.unifm_brightness, 1);
+ }
+ if (gd->win_shader.unifm_max_brightness >= 0) {
+ glUniform1f(gd->win_shader.unifm_max_brightness, (float)img->max_brightness);
+ }
+
+ // log_trace("Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d\n",
+ // x, y, width, height, dx, dy, ptex->width, ptex->height, z);
+
+ // Bind texture
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(GL_TEXTURE_2D, brightness);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, inner->texture);
+
+ GLuint vao;
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+
+ GLuint bo[2];
+ glGenBuffers(2, bo);
+ glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
+ glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STATIC_DRAW);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6,
+ indices, GL_STATIC_DRAW);
+
+ glEnableVertexAttribArray(vert_coord_loc);
+ glEnableVertexAttribArray(vert_in_texcoord_loc);
+ glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL);
+ glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE,
+ sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2));
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target);
+ glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL);
+
+ glDisableVertexAttribArray(vert_coord_loc);
+ glDisableVertexAttribArray(vert_in_texcoord_loc);
+ glBindVertexArray(0);
+ glDeleteVertexArrays(1, &vao);
+
+ // Cleanup
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+ glDrawBuffer(GL_BACK);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ glDeleteBuffers(2, bo);
+
+ glUseProgram(0);
+
+ gl_check_err();
+
+ return;
+}
+
+/// Convert rectangles in X coordinates to OpenGL vertex and texture coordinates
+/// @param[in] nrects, rects rectangles
+/// @param[in] dst_x, dst_y origin of the OpenGL texture, affect the calculated texture
+/// coordinates
+/// @param[in] texture_height height of the OpenGL texture
+/// @param[in] root_height height of the back buffer
+/// @param[in] y_inverted whether the texture is y inverted
+/// @param[out] coord, indices output
+static void
+x_rect_to_coords(int nrects, const rect_t *rects, int dst_x, int dst_y, int texture_height,
+ int root_height, bool y_inverted, GLint *coord, GLuint *indices) {
+ dst_y = root_height - dst_y;
+ if (y_inverted) {
+ dst_y -= texture_height;
+ }
+
+ for (int i = 0; i < nrects; i++) {
+ // Y-flip. Note after this, crect.y1 > crect.y2
+ rect_t crect = rects[i];
+ crect.y1 = root_height - crect.y1;
+ crect.y2 = root_height - crect.y2;
+
+ // Calculate texture coordinates
+ // (texture_x1, texture_y1), texture coord for the _bottom left_ corner
+ GLint texture_x1 = crect.x1 - dst_x, texture_y1 = crect.y2 - dst_y,
+ texture_x2 = texture_x1 + (crect.x2 - crect.x1),
+ texture_y2 = texture_y1 + (crect.y1 - crect.y2);
+
+ // X pixmaps might be Y inverted, invert the texture coordinates
+ if (y_inverted) {
+ texture_y1 = texture_height - texture_y1;
+ texture_y2 = texture_height - texture_y2;
+ }
+
+ // Vertex coordinates
+ auto vx1 = crect.x1;
+ auto vy1 = crect.y2;
+ auto vx2 = crect.x2;
+ auto vy2 = crect.y1;
+
+ // log_trace("Rect %d: %f, %f, %f, %f -> %d, %d, %d, %d",
+ // ri, rx, ry, rxe, rye, rdx, rdy, rdxe, rdye);
+
+ memcpy(&coord[i * 16],
+ ((GLint[][2]){
+ {vx1, vy1},
+ {texture_x1, texture_y1},
+ {vx2, vy1},
+ {texture_x2, texture_y1},
+ {vx2, vy2},
+ {texture_x2, texture_y2},
+ {vx1, vy2},
+ {texture_x1, texture_y2},
+ }),
+ sizeof(GLint[2]) * 8);
+
+ GLuint u = (GLuint)(i * 4);
+ memcpy(&indices[i * 6],
+ ((GLuint[]){u + 0, u + 1, u + 2, u + 2, u + 3, u + 0}),
+ sizeof(GLuint) * 6);
+ }
+}
+
+// TODO(yshui) make use of reg_visible
+void gl_compose(backend_t *base, void *image_data,
+ int dst_x1, int dst_y1, int dst_x2, int dst_y2,
+ const region_t *reg_tgt, const region_t *reg_visible attr_unused) {
+ auto gd = (struct gl_data *)base;
+ struct backend_image *img = image_data;
+ auto inner = (struct gl_texture *)img->inner;
+
+ // Painting
+ int nrects;
+ const rect_t *rects;
+ rects = pixman_region32_rectangles((region_t *)reg_tgt, &nrects);
+ if (!nrects) {
+ // Nothing to paint
+ return;
+ }
+
+ // Until we start to use glClipControl, reg_tgt, dst_x and dst_y and
+ // in a different coordinate system than the one OpenGL uses.
+ // OpenGL window coordinate (or NDC) has the origin at the lower left of the
+ // screen, with y axis pointing up; Xorg has the origin at the upper left of the
+ // screen, with y axis pointing down. We have to do some coordinate conversion in
+ // this function
+
+ auto coord = ccalloc(nrects * 16, GLint);
+ auto indices = ccalloc(nrects * 6, GLuint);
+ x_rect_to_coords(nrects, rects, dst_x1, dst_y1, inner->height, gd->height,
+ inner->y_inverted, coord, indices);
+
+ // Interpolate the texture coordinates into the specified range
+ for (unsigned int i = 2; i < 16; i+=4) {
+ coord[i+0] = lerp_range(0, dst_x2 - dst_x1, 0, inner->width, coord[i+0]);
+ coord[i+1] = lerp_range(0, dst_y2 - dst_y1, 0, inner->height, coord[i+1]);
+ }
+
+ _gl_compose(base, img, gd->back_fbo, coord, indices, nrects);
+
+ free(indices);
+ free(coord);
+}
+
+/**
+ * Blur contents in a particular region.
+ */
+bool gl_kernel_blur(backend_t *base, double opacity, void *ctx, const rect_t *extent,
+ const GLuint vao[2], const int vao_nelems[2]) {
+ auto bctx = (struct gl_blur_context *)ctx;
+ auto gd = (struct gl_data *)base;
+
+ int dst_y_fb_coord = bctx->fb_height - extent->y2;
+
+ int curr = 0;
+ for (int i = 0; i < bctx->npasses; ++i) {
+ const gl_blur_shader_t *p = &bctx->blur_shader[i];
+ assert(p->prog);
+
+ assert(bctx->blur_textures[curr]);
+
+ // The origin to use when sampling from the source texture
+ GLint texorig_x = extent->x1, texorig_y = dst_y_fb_coord;
+ GLint tex_width, tex_height;
+ GLuint src_texture;
+
+ if (i == 0) {
+ src_texture = gd->back_texture;
+ tex_width = gd->width;
+ tex_height = gd->height;
+ } else {
+ src_texture = bctx->blur_textures[curr];
+ auto src_size = bctx->texture_sizes[curr];
+ tex_width = src_size.width;
+ tex_height = src_size.height;
+ }
+
+ glBindTexture(GL_TEXTURE_2D, src_texture);
+ glUseProgram(p->prog);
+ glUniform2f(p->unifm_pixel_norm, 1.0f / (GLfloat)tex_width,
+ 1.0f / (GLfloat)tex_height);
+
+ // The number of indices in the selected vertex array
+ GLsizei nelems;
+
+ if (i < bctx->npasses - 1) {
+ assert(bctx->blur_fbos[0]);
+ assert(bctx->blur_textures[!curr]);
+
+ // not last pass, draw into framebuffer, with resized regions
+ glBindVertexArray(vao[1]);
+ nelems = vao_nelems[1];
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[0]);
+
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_2D, bctx->blur_textures[!curr], 0);
+ glDrawBuffer(GL_COLOR_ATTACHMENT0);
+ if (!gl_check_fb_complete(GL_FRAMEBUFFER)) {
+ return false;
+ }
+
+ glUniform1f(p->unifm_opacity, 1.0);
+ } else {
+ // last pass, draw directly into the back buffer, with origin
+ // regions
+ glBindVertexArray(vao[0]);
+ nelems = vao_nelems[0];
+ glBindFramebuffer(GL_FRAMEBUFFER, gd->back_fbo);
+
+ glUniform1f(p->unifm_opacity, (float)opacity);
+ }
+
+ glUniform2f(p->texorig_loc, (GLfloat)texorig_x, (GLfloat)texorig_y);
+ glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL);
+
+ // XXX use multiple draw calls is probably going to be slow than
+ // just simply blur the whole area.
+
+ curr = !curr;
+ }
+
+ return true;
+}
+
+bool gl_dual_kawase_blur(backend_t *base, double opacity, void *ctx, const rect_t *extent,
+ const GLuint vao[2], const int vao_nelems[2]) {
+ auto bctx = (struct gl_blur_context *)ctx;
+ auto gd = (struct gl_data *)base;
+
+ int dst_y_fb_coord = bctx->fb_height - extent->y2;
+
+ int iterations = bctx->blur_texture_count;
+ int scale_factor = 1;
+
+ // Kawase downsample pass
+ const gl_blur_shader_t *down_pass = &bctx->blur_shader[0];
+ assert(down_pass->prog);
+ glUseProgram(down_pass->prog);
+
+ glUniform2f(down_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord);
+
+ for (int i = 0; i < iterations; ++i) {
+ // Scale output width / height by half in each iteration
+ scale_factor <<= 1;
+
+ GLuint src_texture;
+ int tex_width, tex_height;
+
+ if (i == 0) {
+ // first pass: copy from back buffer
+ src_texture = gd->back_texture;
+ tex_width = gd->width;
+ tex_height = gd->height;
+ } else {
+ // copy from previous pass
+ src_texture = bctx->blur_textures[i - 1];
+ auto src_size = bctx->texture_sizes[i - 1];
+ tex_width = src_size.width;
+ tex_height = src_size.height;
+ }
+
+ assert(src_texture);
+ assert(bctx->blur_fbos[i]);
+
+ glBindTexture(GL_TEXTURE_2D, src_texture);
+ glBindVertexArray(vao[1]);
+ auto nelems = vao_nelems[1];
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]);
+ glDrawBuffer(GL_COLOR_ATTACHMENT0);
+
+ glUniform1f(down_pass->scale_loc, (GLfloat)scale_factor);
+
+ glUniform2f(down_pass->unifm_pixel_norm, 1.0f / (GLfloat)tex_width,
+ 1.0f / (GLfloat)tex_height);
+
+ glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL);
+ }
+
+ // Kawase upsample pass
+ const gl_blur_shader_t *up_pass = &bctx->blur_shader[1];
+ assert(up_pass->prog);
+ glUseProgram(up_pass->prog);
+
+ glUniform2f(up_pass->texorig_loc, (GLfloat)extent->x1, (GLfloat)dst_y_fb_coord);
+
+ for (int i = iterations - 1; i >= 0; --i) {
+ // Scale output width / height back by two in each iteration
+ scale_factor >>= 1;
+
+ const GLuint src_texture = bctx->blur_textures[i];
+ assert(src_texture);
+
+ // Calculate normalized half-width/-height of a src pixel
+ auto src_size = bctx->texture_sizes[i];
+ int tex_width = src_size.width;
+ int tex_height = src_size.height;
+
+ // The number of indices in the selected vertex array
+ GLsizei nelems;
+
+ glBindTexture(GL_TEXTURE_2D, src_texture);
+ if (i > 0) {
+ assert(bctx->blur_fbos[i - 1]);
+
+ // not last pass, draw into next framebuffer
+ glBindVertexArray(vao[1]);
+ nelems = vao_nelems[1];
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i - 1]);
+ glDrawBuffer(GL_COLOR_ATTACHMENT0);
+
+ glUniform1f(up_pass->unifm_opacity, (GLfloat)1);
+ } else {
+ // last pass, draw directly into the back buffer
+ glBindVertexArray(vao[0]);
+ nelems = vao_nelems[0];
+ glBindFramebuffer(GL_FRAMEBUFFER, gd->back_fbo);
+
+ glUniform1f(up_pass->unifm_opacity, (GLfloat)opacity);
+ }
+
+ glUniform1f(up_pass->scale_loc, (GLfloat)scale_factor);
+ glUniform2f(up_pass->unifm_pixel_norm, 1.0f / (GLfloat)tex_width,
+ 1.0f / (GLfloat)tex_height);
+
+ glDrawElements(GL_TRIANGLES, nelems, GL_UNSIGNED_INT, NULL);
+ }
+
+ return true;
+}
+
+bool gl_blur(backend_t *base, double opacity, void *ctx, const region_t *reg_blur,
+ const region_t *reg_visible attr_unused) {
+ auto bctx = (struct gl_blur_context *)ctx;
+ auto gd = (struct gl_data *)base;
+
+ bool ret = false;
+
+ if (gd->width != bctx->fb_width || gd->height != bctx->fb_height) {
+ // Resize the temporary textures used for blur in case the root
+ // size changed
+ bctx->fb_width = gd->width;
+ bctx->fb_height = gd->height;
+
+ for (int i = 0; i < bctx->blur_texture_count; ++i) {
+ auto tex_size = bctx->texture_sizes + i;
+ if (bctx->method == BLUR_METHOD_DUAL_KAWASE) {
+ // Use smaller textures for each iteration (quarter of the
+ // previous texture)
+ tex_size->width = 1 + ((bctx->fb_width - 1) >> (i + 1));
+ tex_size->height = 1 + ((bctx->fb_height - 1) >> (i + 1));
+ } else {
+ tex_size->width = bctx->fb_width;
+ tex_size->height = bctx->fb_height;
+ }
+
+ glBindTexture(GL_TEXTURE_2D, bctx->blur_textures[i]);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, tex_size->width,
+ tex_size->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
+
+ if (bctx->method == BLUR_METHOD_DUAL_KAWASE) {
+ // Attach texture to FBO target
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, bctx->blur_fbos[i]);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,
+ GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ bctx->blur_textures[i], 0);
+ if (!gl_check_fb_complete(GL_FRAMEBUFFER)) {
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ return false;
+ }
+ }
+ }
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+ }
+
+ // Remainder: regions are in Xorg coordinates
+ auto reg_blur_resized =
+ resize_region(reg_blur, bctx->resize_width, bctx->resize_height);
+ const rect_t *extent = pixman_region32_extents((region_t *)reg_blur),
+ *extent_resized = pixman_region32_extents(&reg_blur_resized);
+ int width = extent->x2 - extent->x1, height = extent->y2 - extent->y1;
+ if (width == 0 || height == 0) {
+ return true;
+ }
+
+ int nrects, nrects_resized;
+ const rect_t *rects = pixman_region32_rectangles((region_t *)reg_blur, &nrects),
+ *rects_resized =
+ pixman_region32_rectangles(&reg_blur_resized, &nrects_resized);
+ if (!nrects || !nrects_resized) {
+ return true;
+ }
+
+ auto coord = ccalloc(nrects * 16, GLint);
+ auto indices = ccalloc(nrects * 6, GLuint);
+ x_rect_to_coords(nrects, rects, extent_resized->x1, extent_resized->y2,
+ bctx->fb_height, gd->height, false, coord, indices);
+
+ auto coord_resized = ccalloc(nrects_resized * 16, GLint);
+ auto indices_resized = ccalloc(nrects_resized * 6, GLuint);
+ x_rect_to_coords(nrects_resized, rects_resized, extent_resized->x1,
+ extent_resized->y2, bctx->fb_height, bctx->fb_height, false,
+ coord_resized, indices_resized);
+ pixman_region32_fini(&reg_blur_resized);
+
+ GLuint vao[2];
+ glGenVertexArrays(2, vao);
+ GLuint bo[4];
+ glGenBuffers(4, bo);
+
+ glBindVertexArray(vao[0]);
+ glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
+ glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STATIC_DRAW);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6,
+ indices, GL_STATIC_DRAW);
+ glEnableVertexAttribArray(vert_coord_loc);
+ glEnableVertexAttribArray(vert_in_texcoord_loc);
+ glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL);
+ glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE,
+ sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2));
+
+ glBindVertexArray(vao[1]);
+ glBindBuffer(GL_ARRAY_BUFFER, bo[2]);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[3]);
+ glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord_resized) * nrects_resized * 16,
+ coord_resized, GL_STATIC_DRAW);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER,
+ (long)sizeof(*indices_resized) * nrects_resized * 6, indices_resized,
+ GL_STATIC_DRAW);
+ glEnableVertexAttribArray(vert_coord_loc);
+ glEnableVertexAttribArray(vert_in_texcoord_loc);
+ glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL);
+ glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE,
+ sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2));
+
+ int vao_nelems[2] = {nrects * 6, nrects_resized * 6};
+
+ if (bctx->method == BLUR_METHOD_DUAL_KAWASE) {
+ ret = gl_dual_kawase_blur(base, opacity, ctx, extent_resized, vao, vao_nelems);
+ } else {
+ ret = gl_kernel_blur(base, opacity, ctx, extent_resized, vao, vao_nelems);
+ }
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ glDeleteBuffers(4, bo);
+ glBindVertexArray(0);
+ glDeleteVertexArrays(2, vao);
+ glUseProgram(0);
+
+ free(indices);
+ free(coord);
+ free(indices_resized);
+ free(coord_resized);
+
+ gl_check_err();
+ return ret;
+}
+
+// clang-format off
+const char *vertex_shader = GLSL(330,
+ uniform mat4 projection;
+ uniform float scale = 1.0;
+ uniform vec2 texorig;
+ layout(location = 0) in vec2 coord;
+ layout(location = 1) in vec2 in_texcoord;
+ out vec2 texcoord;
+ void main() {
+ gl_Position = projection * vec4(coord, 0, scale);
+ texcoord = in_texcoord + texorig;
+ }
+);
+// clang-format on
+
+/**
+ * Load a GLSL main program from shader strings.
+ */
+static int gl_win_shader_from_string(const char *vshader_str, const char *fshader_str,
+ gl_win_shader_t *ret) {
+ // Build program
+ ret->prog = gl_create_program_from_str(vshader_str, fshader_str);
+ if (!ret->prog) {
+ log_error("Failed to create GLSL program.");
+ return -1;
+ }
+
+ // Get uniform addresses
+ ret->unifm_opacity = glGetUniformLocationChecked(ret->prog, "opacity");
+ ret->unifm_invert_color = glGetUniformLocationChecked(ret->prog, "invert_color");
+ ret->unifm_tex = glGetUniformLocationChecked(ret->prog, "tex");
+ ret->unifm_dim = glGetUniformLocationChecked(ret->prog, "dim");
+ ret->unifm_brightness = glGetUniformLocationChecked(ret->prog, "brightness");
+ ret->unifm_max_brightness =
+ glGetUniformLocationChecked(ret->prog, "max_brightness");
+
+ gl_check_err();
+
+ return true;
+}
+
+/**
+ * Callback to run on root window size change.
+ */
+void gl_resize(struct gl_data *gd, int width, int height) {
+ GLint viewport_dimensions[2];
+ glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions);
+
+ gd->height = height;
+ gd->width = width;
+
+ assert(viewport_dimensions[0] >= gd->width);
+ assert(viewport_dimensions[1] >= gd->height);
+
+ glBindTexture(GL_TEXTURE_2D, gd->back_texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width, height, 0, GL_BGR,
+ GL_UNSIGNED_BYTE, NULL);
+
+ gl_check_err();
+}
+
+// clang-format off
+static const char dummy_frag[] = GLSL(330,
+ uniform sampler2D tex;
+ in vec2 texcoord;
+ void main() {
+ gl_FragColor = texelFetch(tex, ivec2(texcoord.xy), 0);
+ }
+);
+
+static const char fill_frag[] = GLSL(330,
+ uniform vec4 color;
+ void main() {
+ gl_FragColor = color;
+ }
+);
+
+static const char fill_vert[] = GLSL(330,
+ layout(location = 0) in vec2 in_coord;
+ uniform mat4 projection;
+ void main() {
+ gl_Position = projection * vec4(in_coord, 0, 1);
+ }
+);
+
+static const char interpolating_frag[] = GLSL(330,
+ uniform sampler2D tex;
+ in vec2 texcoord;
+ void main() {
+ gl_FragColor = vec4(texture2D(tex, vec2(texcoord.xy), 0).rgb, 1);
+ }
+);
+
+static const char interpolating_vert[] = GLSL(330,
+ uniform mat4 projection;
+ uniform vec2 texsize;
+ layout(location = 0) in vec2 in_coord;
+ layout(location = 1) in vec2 in_texcoord;
+ out vec2 texcoord;
+ void main() {
+ gl_Position = projection * vec4(in_coord, 0, 1);
+ texcoord = in_texcoord / texsize;
+ }
+);
+// clang-format on
+
+/// Fill a given region in bound framebuffer.
+/// @param[in] y_inverted whether the y coordinates in `clip` should be inverted
+static void _gl_fill(backend_t *base, struct color c, const region_t *clip, GLuint target,
+ int height, bool y_inverted) {
+ static const GLuint fill_vert_in_coord_loc = 0;
+ int nrects;
+ const rect_t *rect = pixman_region32_rectangles((region_t *)clip, &nrects);
+ auto gd = (struct gl_data *)base;
+
+ GLuint vao;
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+
+ GLuint bo[2];
+ glGenBuffers(2, bo);
+ glUseProgram(gd->fill_shader.prog);
+ glUniform4f(gd->fill_shader.color_loc, (GLfloat)c.red, (GLfloat)c.green,
+ (GLfloat)c.blue, (GLfloat)c.alpha);
+ glEnableVertexAttribArray(fill_vert_in_coord_loc);
+ glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
+
+ auto coord = ccalloc(nrects * 8, GLint);
+ auto indices = ccalloc(nrects * 6, GLuint);
+ for (int i = 0; i < nrects; i++) {
+ GLint y1 = y_inverted ? height - rect[i].y2 : rect[i].y1,
+ y2 = y_inverted ? height - rect[i].y1 : rect[i].y2;
+ // clang-format off
+ memcpy(&coord[i * 8],
+ ((GLint[][2]){
+ {rect[i].x1, y1}, {rect[i].x2, y1},
+ {rect[i].x2, y2}, {rect[i].x1, y2}}),
+ sizeof(GLint[2]) * 4);
+ // clang-format on
+ indices[i * 6 + 0] = (GLuint)i * 4 + 0;
+ indices[i * 6 + 1] = (GLuint)i * 4 + 1;
+ indices[i * 6 + 2] = (GLuint)i * 4 + 2;
+ indices[i * 6 + 3] = (GLuint)i * 4 + 2;
+ indices[i * 6 + 4] = (GLuint)i * 4 + 3;
+ indices[i * 6 + 5] = (GLuint)i * 4 + 0;
+ }
+ glBufferData(GL_ARRAY_BUFFER, nrects * 8 * (long)sizeof(*coord), coord, GL_STREAM_DRAW);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, nrects * 6 * (long)sizeof(*indices),
+ indices, GL_STREAM_DRAW);
+
+ glVertexAttribPointer(fill_vert_in_coord_loc, 2, GL_INT, GL_FALSE,
+ sizeof(*coord) * 2, (void *)0);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target);
+ glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL);
+
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ glDisableVertexAttribArray(fill_vert_in_coord_loc);
+ glBindVertexArray(0);
+ glDeleteVertexArrays(1, &vao);
+
+ glDeleteBuffers(2, bo);
+ free(indices);
+ free(coord);
+
+ gl_check_err();
+}
+
+void gl_fill(backend_t *base, struct color c, const region_t *clip) {
+ auto gd = (struct gl_data *)base;
+ return _gl_fill(base, c, clip, gd->back_fbo, gd->height, true);
+}
+
+static void gl_release_image_inner(backend_t *base, struct gl_texture *inner) {
+ auto gd = (struct gl_data *)base;
+ gd->release_user_data(base, inner);
+ assert(inner->user_data == NULL);
+
+ glDeleteTextures(1, &inner->texture);
+ glDeleteTextures(2, inner->auxiliary_texture);
+ free(inner);
+ gl_check_err();
+}
+
+void gl_release_image(backend_t *base, void *image_data) {
+ struct backend_image *wd = image_data;
+ auto inner = (struct gl_texture *)wd->inner;
+ inner->refcount--;
+ assert(inner->refcount >= 0);
+ if (inner->refcount == 0) {
+ gl_release_image_inner(base, inner);
+ }
+ free(wd);
+}
+
+static inline void gl_free_blur_shader(gl_blur_shader_t *shader) {
+ if (shader->prog) {
+ glDeleteProgram(shader->prog);
+ }
+
+ shader->prog = 0;
+}
+
+void gl_destroy_blur_context(backend_t *base attr_unused, void *ctx) {
+ auto bctx = (struct gl_blur_context *)ctx;
+ // Free GLSL shaders/programs
+ for (int i = 0; i < bctx->npasses; ++i) {
+ gl_free_blur_shader(&bctx->blur_shader[i]);
+ }
+ free(bctx->blur_shader);
+
+ if (bctx->blur_texture_count && bctx->blur_textures) {
+ glDeleteTextures(bctx->blur_texture_count, bctx->blur_textures);
+ free(bctx->blur_textures);
+ }
+ if (bctx->blur_texture_count && bctx->texture_sizes) {
+ free(bctx->texture_sizes);
+ }
+ if (bctx->blur_fbo_count && bctx->blur_fbos) {
+ glDeleteFramebuffers(bctx->blur_fbo_count, bctx->blur_fbos);
+ free(bctx->blur_fbos);
+ }
+
+ bctx->blur_texture_count = 0;
+ bctx->blur_fbo_count = 0;
+
+ free(bctx);
+
+ gl_check_err();
+}
+
+/**
+ * Initialize GL blur filters.
+ */
+bool gl_create_kernel_blur_context(void *blur_context, GLfloat *projection,
+ enum blur_method method, void *args) {
+ bool success = false;
+ auto ctx = (struct gl_blur_context *)blur_context;
+
+ struct conv **kernels;
+
+ int nkernels;
+ ctx->method = BLUR_METHOD_KERNEL;
+ if (method == BLUR_METHOD_KERNEL) {
+ nkernels = ((struct kernel_blur_args *)args)->kernel_count;
+ kernels = ((struct kernel_blur_args *)args)->kernels;
+ } else {
+ kernels = generate_blur_kernel(method, args, &nkernels);
+ }
+
+ if (!nkernels) {
+ ctx->method = BLUR_METHOD_NONE;
+ return true;
+ }
+
+ // Specify required textures and FBOs
+ ctx->blur_texture_count = 2;
+ ctx->blur_fbo_count = 1;
+
+ ctx->blur_shader = ccalloc(max2(2, nkernels), gl_blur_shader_t);
+
+ char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL));
+ // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane
+ // Thanks to hiciu for reporting.
+ setlocale(LC_NUMERIC, "C");
+
+ // clang-format off
+ static const char *FRAG_SHADER_BLUR = GLSL(330,
+ %s\n // other extension pragmas
+ uniform sampler2D tex_src;
+ uniform vec2 pixel_norm;
+ uniform float opacity;
+ in vec2 texcoord;
+ out vec4 out_color;
+ void main() {
+ vec2 uv = texcoord * pixel_norm;
+ vec4 sum = vec4(0.0, 0.0, 0.0, 0.0);
+ %s //body of the convolution
+ out_color = sum / float(%.7g) * opacity;
+ }
+ );
+ static const char *FRAG_SHADER_BLUR_ADD = QUOTE(
+ sum += float(%.7g) * texture2D(tex_src, uv + pixel_norm * vec2(%.7g, %.7g));
+ );
+ // clang-format on
+
+ const char *shader_add = FRAG_SHADER_BLUR_ADD;
+ char *extension = strdup("");
+
+ for (int i = 0; i < nkernels; i++) {
+ auto kern = kernels[i];
+ // Build shader
+ int width = kern->w, height = kern->h;
+ int nele = width * height;
+ // '%.7g' is at most 14 characters, inserted 3 times
+ size_t body_len = (strlen(shader_add) + 42) * (uint)nele;
+ char *shader_body = ccalloc(body_len, char);
+ char *pc = shader_body;
+
+ // Make use of the linear interpolation hardware by sampling 2 pixels with
+ // one texture access by sampling between both pixels based on their
+ // relative weight. Easiest done in a single dimension as 2D bilinear
+ // filtering would raise additional constraints on the kernels. Therefore
+ // only use interpolation along the larger dimension.
+ double sum = 0.0;
+ if (width > height) {
+ // use interpolation in x dimension (width)
+ for (int j = 0; j < height; ++j) {
+ for (int k = 0; k < width; k += 2) {
+ double val1, val2;
+ val1 = kern->data[j * width + k];
+ val2 = (k + 1 < width)
+ ? kern->data[j * width + k + 1]
+ : 0;
+
+ double combined_weight = val1 + val2;
+ if (combined_weight == 0) {
+ continue;
+ }
+ sum += combined_weight;
+
+ double offset_x =
+ k + (val2 / combined_weight) - (width / 2);
+ double offset_y = j - (height / 2);
+ pc += snprintf(
+ pc, body_len - (ulong)(pc - shader_body),
+ shader_add, combined_weight, offset_x, offset_y);
+ assert(pc < shader_body + body_len);
+ }
+ }
+ } else {
+ // use interpolation in y dimension (height)
+ for (int j = 0; j < height; j += 2) {
+ for (int k = 0; k < width; ++k) {
+ double val1, val2;
+ val1 = kern->data[j * width + k];
+ val2 = (j + 1 < height)
+ ? kern->data[(j + 1) * width + k]
+ : 0;
+
+ double combined_weight = val1 + val2;
+ if (combined_weight == 0) {
+ continue;
+ }
+ sum += combined_weight;
+
+ double offset_x = k - (width / 2);
+ double offset_y =
+ j + (val2 / combined_weight) - (height / 2);
+ pc += snprintf(
+ pc, body_len - (ulong)(pc - shader_body),
+ shader_add, combined_weight, offset_x, offset_y);
+ assert(pc < shader_body + body_len);
+ }
+ }
+ }
+
+ auto pass = ctx->blur_shader + i;
+ size_t shader_len = strlen(FRAG_SHADER_BLUR) + strlen(extension) +
+ strlen(shader_body) + 10 /* sum */ +
+ 1 /* null terminator */;
+ char *shader_str = ccalloc(shader_len, char);
+ auto real_shader_len = snprintf(shader_str, shader_len, FRAG_SHADER_BLUR,
+ extension, shader_body, sum);
+ CHECK(real_shader_len >= 0);
+ CHECK((size_t)real_shader_len < shader_len);
+ free(shader_body);
+
+ // Build program
+ pass->prog = gl_create_program_from_str(vertex_shader, shader_str);
+ free(shader_str);
+ if (!pass->prog) {
+ log_error("Failed to create GLSL program.");
+ success = false;
+ goto out;
+ }
+ glBindFragDataLocation(pass->prog, 0, "out_color");
+
+ // Get uniform addresses
+ pass->unifm_pixel_norm =
+ glGetUniformLocationChecked(pass->prog, "pixel_norm");
+ pass->unifm_opacity = glGetUniformLocationChecked(pass->prog, "opacity");
+ pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig");
+
+ // Setup projection matrix
+ glUseProgram(pass->prog);
+ int pml = glGetUniformLocationChecked(pass->prog, "projection");
+ glUniformMatrix4fv(pml, 1, false, projection);
+ glUseProgram(0);
+
+ ctx->resize_width += kern->w / 2;
+ ctx->resize_height += kern->h / 2;
+ }
+
+ if (nkernels == 1) {
+ // Generate an extra null pass so we don't need special code path for
+ // the single pass case
+ auto pass = &ctx->blur_shader[1];
+ pass->prog = gl_create_program_from_str(vertex_shader, dummy_frag);
+ pass->unifm_pixel_norm = -1;
+ pass->unifm_opacity = -1;
+ pass->texorig_loc = glGetUniformLocationChecked(pass->prog, "texorig");
+
+ // Setup projection matrix
+ glUseProgram(pass->prog);
+ int pml = glGetUniformLocationChecked(pass->prog, "projection");
+ glUniformMatrix4fv(pml, 1, false, projection);
+ glUseProgram(0);
+
+ ctx->npasses = 2;
+ } else {
+ ctx->npasses = nkernels;
+ }
+
+ success = true;
+out:
+ if (method != BLUR_METHOD_KERNEL) {
+ // We generated the blur kernels, so we need to free them
+ for (int i = 0; i < nkernels; i++) {
+ free(kernels[i]);
+ }
+ free(kernels);
+ }
+
+ free(extension);
+ // Restore LC_NUMERIC
+ setlocale(LC_NUMERIC, lc_numeric_old);
+ free(lc_numeric_old);
+
+ return success;
+}
+
+bool gl_create_dual_kawase_blur_context(void *blur_context, GLfloat *projection,
+ enum blur_method method, void *args) {
+ bool success = false;
+ auto ctx = (struct gl_blur_context *)blur_context;
+
+ ctx->method = method;
+
+ auto blur_params = generate_dual_kawase_params(args);
+
+ // Specify required textures and FBOs
+ ctx->blur_texture_count = blur_params->iterations;
+ ctx->blur_fbo_count = blur_params->iterations;
+
+ ctx->resize_width += blur_params->expand;
+ ctx->resize_height += blur_params->expand;
+
+ ctx->npasses = 2;
+ ctx->blur_shader = ccalloc(ctx->npasses, gl_blur_shader_t);
+
+ char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL));
+ // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane
+ // Thanks to hiciu for reporting.
+ setlocale(LC_NUMERIC, "C");
+
+ // Dual-kawase downsample shader / program
+ auto down_pass = ctx->blur_shader;
+ {
+ // clang-format off
+ static const char *FRAG_SHADER_DOWN = GLSL(330,
+ uniform sampler2D tex_src;
+ uniform float scale = 1.0;
+ uniform vec2 pixel_norm;
+ in vec2 texcoord;
+ out vec4 out_color;
+ void main() {
+ vec2 offset = %.7g * pixel_norm;
+ vec2 uv = texcoord * pixel_norm * (2.0 / scale);
+ vec4 sum = texture2D(tex_src, uv) * 4.0;
+ sum += texture2D(tex_src, uv - vec2(0.5, 0.5) * offset);
+ sum += texture2D(tex_src, uv + vec2(0.5, 0.5) * offset);
+ sum += texture2D(tex_src, uv + vec2(0.5, -0.5) * offset);
+ sum += texture2D(tex_src, uv - vec2(0.5, -0.5) * offset);
+ out_color = sum / 8.0;
+ }
+ );
+ // clang-format on
+
+ // Build shader
+ size_t shader_len =
+ strlen(FRAG_SHADER_DOWN) + 10 /* offset */ + 1 /* null terminator */;
+ char *shader_str = ccalloc(shader_len, char);
+ auto real_shader_len =
+ snprintf(shader_str, shader_len, FRAG_SHADER_DOWN, blur_params->offset);
+ CHECK(real_shader_len >= 0);
+ CHECK((size_t)real_shader_len < shader_len);
+
+ // Build program
+ down_pass->prog = gl_create_program_from_str(vertex_shader, shader_str);
+ free(shader_str);
+ if (!down_pass->prog) {
+ log_error("Failed to create GLSL program.");
+ success = false;
+ goto out;
+ }
+ glBindFragDataLocation(down_pass->prog, 0, "out_color");
+
+ // Get uniform addresses
+ down_pass->unifm_pixel_norm =
+ glGetUniformLocationChecked(down_pass->prog, "pixel_norm");
+ down_pass->texorig_loc =
+ glGetUniformLocationChecked(down_pass->prog, "texorig");
+ down_pass->scale_loc =
+ glGetUniformLocationChecked(down_pass->prog, "scale");
+
+ // Setup projection matrix
+ glUseProgram(down_pass->prog);
+ int pml = glGetUniformLocationChecked(down_pass->prog, "projection");
+ glUniformMatrix4fv(pml, 1, false, projection);
+ glUseProgram(0);
+ }
+
+ // Dual-kawase upsample shader / program
+ auto up_pass = ctx->blur_shader + 1;
+ {
+ // clang-format off
+ static const char *FRAG_SHADER_UP = GLSL(330,
+ uniform sampler2D tex_src;
+ uniform float scale = 1.0;
+ uniform vec2 pixel_norm;
+ uniform float opacity;
+ in vec2 texcoord;
+ out vec4 out_color;
+ void main() {
+ vec2 offset = %.7g * pixel_norm;
+ vec2 uv = texcoord * pixel_norm / (2 * scale);
+ vec4 sum = texture2D(tex_src, uv + vec2(-1.0, 0.0) * offset);
+ sum += texture2D(tex_src, uv + vec2(-0.5, 0.5) * offset) * 2.0;
+ sum += texture2D(tex_src, uv + vec2(0.0, 1.0) * offset);
+ sum += texture2D(tex_src, uv + vec2(0.5, 0.5) * offset) * 2.0;
+ sum += texture2D(tex_src, uv + vec2(1.0, 0.0) * offset);
+ sum += texture2D(tex_src, uv + vec2(0.5, -0.5) * offset) * 2.0;
+ sum += texture2D(tex_src, uv + vec2(0.0, -1.0) * offset);
+ sum += texture2D(tex_src, uv + vec2(-0.5, -0.5) * offset) * 2.0;
+ out_color = sum / 12.0 * opacity;
+ }
+ );
+ // clang-format on
+
+ // Build shader
+ size_t shader_len =
+ strlen(FRAG_SHADER_UP) + 10 /* offset */ + 1 /* null terminator */;
+ char *shader_str = ccalloc(shader_len, char);
+ auto real_shader_len =
+ snprintf(shader_str, shader_len, FRAG_SHADER_UP, blur_params->offset);
+ CHECK(real_shader_len >= 0);
+ CHECK((size_t)real_shader_len < shader_len);
+
+ // Build program
+ up_pass->prog = gl_create_program_from_str(vertex_shader, shader_str);
+ free(shader_str);
+ if (!up_pass->prog) {
+ log_error("Failed to create GLSL program.");
+ success = false;
+ goto out;
+ }
+ glBindFragDataLocation(up_pass->prog, 0, "out_color");
+
+ // Get uniform addresses
+ up_pass->unifm_pixel_norm =
+ glGetUniformLocationChecked(up_pass->prog, "pixel_norm");
+ up_pass->unifm_opacity =
+ glGetUniformLocationChecked(up_pass->prog, "opacity");
+ up_pass->texorig_loc =
+ glGetUniformLocationChecked(up_pass->prog, "texorig");
+ up_pass->scale_loc = glGetUniformLocationChecked(up_pass->prog, "scale");
+
+ // Setup projection matrix
+ glUseProgram(up_pass->prog);
+ int pml = glGetUniformLocationChecked(up_pass->prog, "projection");
+ glUniformMatrix4fv(pml, 1, false, projection);
+ glUseProgram(0);
+ }
+
+ success = true;
+out:
+ free(blur_params);
+
+ if (!success) {
+ ctx = NULL;
+ }
+
+ // Restore LC_NUMERIC
+ setlocale(LC_NUMERIC, lc_numeric_old);
+ free(lc_numeric_old);
+
+ return success;
+}
+
+void *gl_create_blur_context(backend_t *base, enum blur_method method, void *args) {
+ bool success;
+ auto gd = (struct gl_data *)base;
+
+ auto ctx = ccalloc(1, struct gl_blur_context);
+
+ if (!method || method >= BLUR_METHOD_INVALID) {
+ ctx->method = BLUR_METHOD_NONE;
+ return ctx;
+ }
+
+ // Set projection matrix to gl viewport dimensions so we can use screen
+ // coordinates for all vertices
+ // Note: OpenGL matrices are column major
+ GLint viewport_dimensions[2];
+ glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions);
+ GLfloat projection_matrix[4][4] = {{2.0f / (GLfloat)viewport_dimensions[0], 0, 0, 0},
+ {0, 2.0f / (GLfloat)viewport_dimensions[1], 0, 0},
+ {0, 0, 0, 0},
+ {-1, -1, 0, 1}};
+
+ if (method == BLUR_METHOD_DUAL_KAWASE) {
+ success = gl_create_dual_kawase_blur_context(ctx, projection_matrix[0],
+ method, args);
+ } else {
+ success =
+ gl_create_kernel_blur_context(ctx, projection_matrix[0], method, args);
+ }
+ if (!success || ctx->method == BLUR_METHOD_NONE) {
+ goto out;
+ }
+
+ // Texture size will be defined by gl_blur
+ ctx->blur_textures = ccalloc(ctx->blur_texture_count, GLuint);
+ ctx->texture_sizes = ccalloc(ctx->blur_texture_count, struct texture_size);
+ glGenTextures(ctx->blur_texture_count, ctx->blur_textures);
+
+ for (int i = 0; i < ctx->blur_texture_count; ++i) {
+ glBindTexture(GL_TEXTURE_2D, ctx->blur_textures[i]);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ }
+
+ // Generate FBO and textures when needed
+ ctx->blur_fbos = ccalloc(ctx->blur_fbo_count, GLuint);
+ glGenFramebuffers(ctx->blur_fbo_count, ctx->blur_fbos);
+
+ for (int i = 0; i < ctx->blur_fbo_count; ++i) {
+ if (!ctx->blur_fbos[i]) {
+ log_error("Failed to generate framebuffer objects for blur");
+ success = false;
+ goto out;
+ }
+ }
+
+out:
+ if (!success) {
+ gl_destroy_blur_context(&gd->base, ctx);
+ ctx = NULL;
+ }
+
+ gl_check_err();
+ return ctx;
+}
+
+void gl_get_blur_size(void *blur_context, int *width, int *height) {
+ auto ctx = (struct gl_blur_context *)blur_context;
+ *width = ctx->resize_width;
+ *height = ctx->resize_height;
+}
+
+// clang-format off
+const char *win_shader_glsl = GLSL(330,
+ uniform float opacity;
+ uniform float dim;
+ uniform bool invert_color;
+ in vec2 texcoord;
+ uniform sampler2D tex;
+ uniform sampler2D brightness;
+ uniform float max_brightness;
+
+ void main() {
+ vec4 c = texelFetch(tex, ivec2(texcoord), 0);
+ if (invert_color) {
+ c = vec4(c.aaa - c.rgb, c.a);
+ }
+ c = vec4(c.rgb * (1.0 - dim), c.a) * opacity;
+
+ vec3 rgb_brightness = texelFetch(brightness, ivec2(0, 0), 0).rgb;
+ // Ref: https://en.wikipedia.org/wiki/Relative_luminance
+ float brightness = rgb_brightness.r * 0.21 +
+ rgb_brightness.g * 0.72 +
+ rgb_brightness.b * 0.07;
+ if (brightness > max_brightness)
+ c.rgb = c.rgb * (max_brightness / brightness);
+
+ gl_FragColor = c;
+ }
+);
+
+const char *present_vertex_shader = GLSL(330,
+ uniform mat4 projection;
+ layout(location = 0) in vec2 coord;
+ out vec2 texcoord;
+ void main() {
+ gl_Position = projection * vec4(coord, 0, 1);
+ texcoord = coord;
+ }
+);
+// clang-format on
+
+bool gl_init(struct gl_data *gd, session_t *ps) {
+ // Initialize GLX data structure
+ glDisable(GL_DEPTH_TEST);
+ glDepthMask(GL_FALSE);
+
+ glEnable(GL_BLEND);
+ // X pixmap is in premultiplied alpha, so we might just as well use it too.
+ // Thanks to derhass for help.
+ glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+
+ // Initialize stencil buffer
+ glDisable(GL_STENCIL_TEST);
+ glStencilMask(0x1);
+ glStencilFunc(GL_EQUAL, 0x1, 0x1);
+
+ // Set gl viewport to the maximum supported size so we won't have to worry about
+ // it later on when the screen is resized. The corresponding projection matrix can
+ // be set now and won't have to be updated. Since fragments outside the target
+ // buffer are skipped anyways, this should have no impact on performance.
+ GLint viewport_dimensions[2];
+ glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions);
+ glViewport(0, 0, viewport_dimensions[0], viewport_dimensions[1]);
+
+ // Clear screen
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+ glGenFramebuffers(1, &gd->back_fbo);
+ glGenTextures(1, &gd->back_texture);
+ if (!gd->back_fbo || !gd->back_texture) {
+ log_error("Failed to generate a framebuffer object");
+ return false;
+ }
+
+ glBindTexture(GL_TEXTURE_2D, gd->back_texture);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ // Set projection matrix to gl viewport dimensions so we can use screen
+ // coordinates for all vertices
+ // Note: OpenGL matrices are column major
+ GLfloat projection_matrix[4][4] = {{2.0f / (GLfloat)viewport_dimensions[0], 0, 0, 0},
+ {0, 2.0f / (GLfloat)viewport_dimensions[1], 0, 0},
+ {0, 0, 0, 0},
+ {-1, -1, 0, 1}};
+
+ // Initialize shaders
+ gl_win_shader_from_string(vertex_shader, win_shader_glsl, &gd->win_shader);
+ int pml = glGetUniformLocationChecked(gd->win_shader.prog, "projection");
+ glUseProgram(gd->win_shader.prog);
+ glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
+ glUseProgram(0);
+
+ gd->fill_shader.prog = gl_create_program_from_str(fill_vert, fill_frag);
+ gd->fill_shader.color_loc = glGetUniformLocation(gd->fill_shader.prog, "color");
+ pml = glGetUniformLocationChecked(gd->fill_shader.prog, "projection");
+ glUseProgram(gd->fill_shader.prog);
+ glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
+ glUseProgram(0);
+
+ gd->present_prog = gl_create_program_from_str(present_vertex_shader, dummy_frag);
+ if (!gd->present_prog) {
+ log_error("Failed to create the present shader");
+ return false;
+ }
+ pml = glGetUniformLocationChecked(gd->present_prog, "projection");
+ glUseProgram(gd->present_prog);
+ glUniform1i(glGetUniformLocationChecked(gd->present_prog, "tex"), 0);
+ glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
+ glUseProgram(0);
+
+ gd->brightness_shader.prog =
+ gl_create_program_from_str(interpolating_vert, interpolating_frag);
+ if (!gd->brightness_shader.prog) {
+ log_error("Failed to create the brightness shader");
+ return false;
+ }
+ pml = glGetUniformLocationChecked(gd->brightness_shader.prog, "projection");
+ glUseProgram(gd->brightness_shader.prog);
+ glUniform1i(glGetUniformLocationChecked(gd->brightness_shader.prog, "tex"), 0);
+ glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
+ glUseProgram(0);
+
+ // Set up the size of the back texture
+ gl_resize(gd, ps->root_width, ps->root_height);
+
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gd->back_fbo);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ gd->back_texture, 0);
+ glDrawBuffer(GL_COLOR_ATTACHMENT0);
+ if (!gl_check_fb_complete(GL_FRAMEBUFFER)) {
+ return false;
+ }
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+
+ gd->logger = gl_string_marker_logger_new();
+ if (gd->logger) {
+ log_add_target_tls(gd->logger);
+ }
+
+ const char *vendor = (const char *)glGetString(GL_VENDOR);
+ log_debug("GL_VENDOR = %s", vendor);
+ if (strcmp(vendor, "NVIDIA Corporation") == 0) {
+ log_info("GL vendor is NVIDIA, don't use glFinish");
+ gd->is_nvidia = true;
+ } else {
+ gd->is_nvidia = false;
+ }
+
+ return true;
+}
+
+void gl_deinit(struct gl_data *gd) {
+ gl_free_prog_main(&gd->win_shader);
+
+ if (gd->logger) {
+ log_remove_target_tls(gd->logger);
+ gd->logger = NULL;
+ }
+
+ gl_check_err();
+}
+
+GLuint gl_new_texture(GLenum target) {
+ GLuint texture;
+ glGenTextures(1, &texture);
+ if (!texture) {
+ log_error("Failed to generate texture");
+ return 0;
+ }
+
+ glBindTexture(target, texture);
+ glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glBindTexture(target, 0);
+
+ return texture;
+}
+
+/// Actually duplicate a texture into a new one, if this texture is shared
+static inline void gl_image_decouple(backend_t *base, struct backend_image *img) {
+ if (img->inner->refcount == 1) {
+ return;
+ }
+ auto gd = (struct gl_data *)base;
+ auto inner = (struct gl_texture *)img->inner;
+ auto new_tex = ccalloc(1, struct gl_texture);
+
+ new_tex->texture = gl_new_texture(GL_TEXTURE_2D);
+ new_tex->y_inverted = true;
+ new_tex->height = inner->height;
+ new_tex->width = inner->width;
+ new_tex->refcount = 1;
+ new_tex->user_data = gd->decouple_texture_user_data(base, inner->user_data);
+
+ glBindTexture(GL_TEXTURE_2D, new_tex->texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, new_tex->width, new_tex->height, 0,
+ GL_BGRA, GL_UNSIGNED_BYTE, NULL);
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ assert(gd->present_prog);
+ glUseProgram(gd->present_prog);
+ glBindTexture(GL_TEXTURE_2D, inner->texture);
+
+ GLuint fbo;
+ glGenFramebuffers(1, &fbo);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ new_tex->texture, 0);
+ glDrawBuffer(GL_COLOR_ATTACHMENT0);
+ gl_check_fb_complete(GL_DRAW_FRAMEBUFFER);
+
+ glClearColor(0, 0, 0, 0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ // clang-format off
+ GLint coord[] = {
+ // top left
+ 0, 0, // vertex coord
+ 0, 0, // texture coord
+
+ // top right
+ new_tex->width, 0, // vertex coord
+ new_tex->width, 0, // texture coord
+
+ // bottom right
+ new_tex->width, new_tex->height,
+ new_tex->width, new_tex->height,
+
+ // bottom left
+ 0, new_tex->height,
+ 0, new_tex->height,
+ };
+ // clang-format on
+ GLuint indices[] = {0, 1, 2, 2, 3, 0};
+
+ GLuint vao;
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+
+ GLuint bo[2];
+ glGenBuffers(2, bo);
+ glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
+ glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 16, coord, GL_STATIC_DRAW);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices,
+ GL_STATIC_DRAW);
+
+ glEnableVertexAttribArray(vert_coord_loc);
+ glEnableVertexAttribArray(vert_in_texcoord_loc);
+ glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL);
+ glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE,
+ sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2));
+
+ glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL);
+
+ glDisableVertexAttribArray(vert_coord_loc);
+ glDisableVertexAttribArray(vert_in_texcoord_loc);
+ glBindVertexArray(0);
+ glDeleteVertexArrays(1, &vao);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ glDeleteBuffers(2, bo);
+
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+ glDeleteFramebuffers(1, &fbo);
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glUseProgram(0);
+
+ gl_check_err();
+
+ img->inner = (struct backend_image_inner_base *)new_tex;
+ inner->refcount--;
+}
+
+static void gl_image_apply_alpha(backend_t *base, struct backend_image *img,
+ const region_t *reg_op, double alpha) {
+ // Result color = 0 (GL_ZERO) + alpha (GL_CONSTANT_ALPHA) * original color
+ auto inner = (struct gl_texture *)img->inner;
+ glBlendFunc(GL_ZERO, GL_CONSTANT_ALPHA);
+ glBlendColor(0, 0, 0, (GLclampf)alpha);
+ GLuint fbo;
+ glGenFramebuffers(1, &fbo);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ inner->texture, 0);
+ glDrawBuffer(GL_COLOR_ATTACHMENT0);
+ _gl_fill(base, (struct color){0, 0, 0, 0}, reg_op, fbo, 0, false);
+ glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+ glDeleteFramebuffers(1, &fbo);
+}
+
+void gl_present(backend_t *base, const region_t *region) {
+ auto gd = (struct gl_data *)base;
+
+ int nrects;
+ const rect_t *rect = pixman_region32_rectangles((region_t *)region, &nrects);
+ auto coord = ccalloc(nrects * 8, GLint);
+ auto indices = ccalloc(nrects * 6, GLuint);
+ for (int i = 0; i < nrects; i++) {
+ // clang-format off
+ memcpy(&coord[i * 8],
+ ((GLint[]){rect[i].x1, gd->height - rect[i].y2,
+ rect[i].x2, gd->height - rect[i].y2,
+ rect[i].x2, gd->height - rect[i].y1,
+ rect[i].x1, gd->height - rect[i].y1}),
+ sizeof(GLint) * 8);
+ // clang-format on
+
+ GLuint u = (GLuint)(i * 4);
+ memcpy(&indices[i * 6],
+ ((GLuint[]){u + 0, u + 1, u + 2, u + 2, u + 3, u + 0}),
+ sizeof(GLuint) * 6);
+ }
+
+ glUseProgram(gd->present_prog);
+ glBindTexture(GL_TEXTURE_2D, gd->back_texture);
+
+ GLuint vao;
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+
+ GLuint bo[2];
+ glGenBuffers(2, bo);
+ glEnableVertexAttribArray(vert_coord_loc);
+ glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
+ glBufferData(GL_ARRAY_BUFFER, (long)sizeof(GLint) * nrects * 8, coord, GL_STREAM_DRAW);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(GLuint) * nrects * 6, indices,
+ GL_STREAM_DRAW);
+
+ glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 2, NULL);
+ glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ glBindVertexArray(0);
+ glDeleteBuffers(2, bo);
+ glDeleteVertexArrays(1, &vao);
+
+ free(coord);
+ free(indices);
+}
+
+bool gl_image_op(backend_t *base, enum image_operations op, void *image_data,
+ const region_t *reg_op, const region_t *reg_visible attr_unused, void *arg) {
+ struct backend_image *tex = image_data;
+ switch (op) {
+ case IMAGE_OP_APPLY_ALPHA:
+ gl_image_decouple(base, tex);
+ assert(tex->inner->refcount == 1);
+ gl_image_apply_alpha(base, tex, reg_op, *(double *)arg);
+ break;
+ }
+
+ return true;
+}
+
+bool gl_read_pixel(backend_t *base attr_unused, void *image_data, int x, int y,
+ struct color *output) {
+ struct backend_image *tex = image_data;
+ auto inner = (struct gl_texture *)tex->inner;
+ GLfloat color[4];
+ glReadPixels(x, inner->y_inverted ? inner->height - y : y, 1, 1, GL_RGBA,
+ GL_FLOAT, color);
+ output->alpha = color[3];
+ output->red = color[0];
+ output->green = color[1];
+ output->blue = color[2];
+
+ bool ret = glGetError() == GL_NO_ERROR;
+ gl_clear_err();
+ return ret;
+}
diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h
new file mode 100644
index 0000000..b1d93b0
--- /dev/null
+++ b/src/backend/gl/gl_common.h
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) Yuxuan Shui <[email protected]>
+#pragma once
+#include <GL/gl.h>
+#include <GL/glext.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "backend/backend.h"
+#include "log.h"
+#include "region.h"
+
+#define CASESTRRET(s) \
+ case s: return #s
+
+// Program and uniforms for window shader
+typedef struct {
+ GLuint prog;
+ GLint unifm_opacity;
+ GLint unifm_invert_color;
+ GLint unifm_tex;
+ GLint unifm_dim;
+ GLint unifm_brightness;
+ GLint unifm_max_brightness;
+} gl_win_shader_t;
+
+// Program and uniforms for brightness shader
+typedef struct {
+ GLuint prog;
+} gl_brightness_shader_t;
+
+// Program and uniforms for blur shader
+typedef struct {
+ GLuint prog;
+ GLint unifm_pixel_norm;
+ GLint unifm_opacity;
+ GLint texorig_loc;
+ GLint scale_loc;
+} gl_blur_shader_t;
+
+typedef struct {
+ GLuint prog;
+ GLint color_loc;
+} gl_fill_shader_t;
+
+/// @brief Wrapper of a binded GLX texture.
+struct gl_texture {
+ int refcount;
+ bool has_alpha;
+ GLuint texture;
+ int width, height;
+ bool y_inverted;
+
+ // Textures for auxiliary uses.
+ GLuint auxiliary_texture[2];
+ void *user_data;
+};
+
+struct gl_data {
+ backend_t base;
+ // If we are using proprietary NVIDIA driver
+ bool is_nvidia;
+ // Height and width of the root window
+ int height, width;
+ gl_win_shader_t win_shader;
+ gl_brightness_shader_t brightness_shader;
+ gl_fill_shader_t fill_shader;
+ GLuint back_texture, back_fbo;
+ GLuint present_prog;
+
+ /// Called when an gl_texture is decoupled from the texture it refers. Returns
+ /// the decoupled user_data
+ void *(*decouple_texture_user_data)(backend_t *base, void *user_data);
+
+ /// Release the user data attached to a gl_texture
+ void (*release_user_data)(backend_t *base, struct gl_texture *);
+
+ struct log_target *logger;
+};
+
+typedef struct session session_t;
+
+#define GL_PROG_MAIN_INIT \
+ { .prog = 0, .unifm_opacity = -1, .unifm_invert_color = -1, .unifm_tex = -1, }
+
+GLuint gl_create_shader(GLenum shader_type, const char *shader_str);
+GLuint gl_create_program(const GLuint *const shaders, int nshaders);
+GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str);
+
+/**
+ * @brief Render a region with texture data.
+ */
+void gl_compose(backend_t *, void *ptex,
+ int dst_x1, int dst_y1, int dst_x2, int dst_y2,
+ const region_t *reg_tgt, const region_t *reg_visible);
+
+void gl_resize(struct gl_data *, int width, int height);
+
+bool gl_init(struct gl_data *gd, session_t *);
+void gl_deinit(struct gl_data *gd);
+
+GLuint gl_new_texture(GLenum target);
+
+bool gl_image_op(backend_t *base, enum image_operations op, void *image_data,
+ const region_t *reg_op, const region_t *reg_visible, void *arg);
+
+void gl_release_image(backend_t *base, void *image_data);
+
+void *gl_clone(backend_t *base, const void *image_data, const region_t *reg_visible);
+
+bool gl_blur(backend_t *base, double opacity, void *, const region_t *reg_blur,
+ const region_t *reg_visible);
+void *gl_create_blur_context(backend_t *base, enum blur_method, void *args);
+void gl_destroy_blur_context(backend_t *base, void *ctx);
+void gl_get_blur_size(void *blur_context, int *width, int *height);
+
+void gl_fill(backend_t *base, struct color, const region_t *clip);
+
+void gl_present(backend_t *base, const region_t *);
+bool gl_read_pixel(backend_t *base, void *image_data, int x, int y, struct color *output);
+
+static inline void gl_delete_texture(GLuint texture) {
+ glDeleteTextures(1, &texture);
+}
+
+/**
+ * Get a textual representation of an OpenGL error.
+ */
+static inline const char *gl_get_err_str(GLenum err) {
+ switch (err) {
+ CASESTRRET(GL_NO_ERROR);
+ CASESTRRET(GL_INVALID_ENUM);
+ CASESTRRET(GL_INVALID_VALUE);
+ CASESTRRET(GL_INVALID_OPERATION);
+ CASESTRRET(GL_INVALID_FRAMEBUFFER_OPERATION);
+ CASESTRRET(GL_OUT_OF_MEMORY);
+ CASESTRRET(GL_STACK_UNDERFLOW);
+ CASESTRRET(GL_STACK_OVERFLOW);
+ CASESTRRET(GL_FRAMEBUFFER_UNDEFINED);
+ CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
+ CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT);
+ CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER);
+ CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER);
+ CASESTRRET(GL_FRAMEBUFFER_UNSUPPORTED);
+ CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE);
+ CASESTRRET(GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS);
+ }
+ return NULL;
+}
+
+/**
+ * Check for GLX error.
+ *
+ * http://blog.nobel-joergensen.com/2013/01/29/debugging-opengl-using-glgeterror/
+ */
+static inline void gl_check_err_(const char *func, int line) {
+ GLenum err = GL_NO_ERROR;
+
+ while (GL_NO_ERROR != (err = glGetError())) {
+ const char *errtext = gl_get_err_str(err);
+ if (errtext) {
+ log_printf(tls_logger, LOG_LEVEL_ERROR, func,
+ "GLX error at line %d: %s", line, errtext);
+ } else {
+ log_printf(tls_logger, LOG_LEVEL_ERROR, func,
+ "GLX error at line %d: %d", line, err);
+ }
+ }
+}
+
+static inline void gl_clear_err(void) {
+ while (glGetError() != GL_NO_ERROR)
+ ;
+}
+
+#define gl_check_err() gl_check_err_(__func__, __LINE__)
+
+/**
+ * Check for GL framebuffer completeness.
+ */
+static inline bool gl_check_fb_complete_(const char *func, int line, GLenum fb) {
+ GLenum status = glCheckFramebufferStatus(fb);
+
+ if (status == GL_FRAMEBUFFER_COMPLETE) {
+ return true;
+ }
+
+ const char *stattext = gl_get_err_str(status);
+ if (stattext) {
+ log_printf(tls_logger, LOG_LEVEL_ERROR, func,
+ "Framebuffer attachment failed at line %d: %s", line, stattext);
+ } else {
+ log_printf(tls_logger, LOG_LEVEL_ERROR, func,
+ "Framebuffer attachment failed at line %d: %d", line, status);
+ }
+
+ return false;
+}
+
+#define gl_check_fb_complete(fb) gl_check_fb_complete_(__func__, __LINE__, (fb))
+
+/**
+ * Check if a GLX extension exists.
+ */
+static inline bool gl_has_extension(const char *ext) {
+ int nexts = 0;
+ glGetIntegerv(GL_NUM_EXTENSIONS, &nexts);
+ for (int i = 0; i < nexts || !nexts; i++) {
+ const char *exti = (const char *)glGetStringi(GL_EXTENSIONS, (GLuint)i);
+ if (exti == NULL) {
+ break;
+ }
+ if (strcmp(ext, exti) == 0) {
+ return true;
+ }
+ }
+ gl_clear_err();
+ log_info("Missing GL extension %s.", ext);
+ return false;
+}
diff --git a/src/backend/gl/glx.c b/src/backend/gl/glx.c
new file mode 100644
index 0000000..1397d19
--- /dev/null
+++ b/src/backend/gl/glx.c
@@ -0,0 +1,648 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Compton - a compositor for X11
+ *
+ * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
+ *
+ * Copyright (c) 2011-2013, Christopher Jeffrey
+ * Copyright (c) 2019 Yuxuan Shui <[email protected]>
+ * See LICENSE-mit for more information.
+ *
+ */
+
+#include <X11/Xlib-xcb.h>
+#include <assert.h>
+#include <limits.h>
+#include <pixman.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <xcb/composite.h>
+#include <xcb/xcb.h>
+
+#include "backend/backend.h"
+#include "backend/backend_common.h"
+#include "backend/gl/gl_common.h"
+#include "backend/gl/glx.h"
+#include "common.h"
+#include "compiler.h"
+#include "config.h"
+#include "log.h"
+#include "picom.h"
+#include "region.h"
+#include "utils.h"
+#include "win.h"
+#include "x.h"
+
+struct _glx_pixmap {
+ GLXPixmap glpixmap;
+ xcb_pixmap_t pixmap;
+ bool owned;
+};
+
+struct _glx_data {
+ struct gl_data gl;
+ Display *display;
+ int screen;
+ xcb_window_t target_win;
+ GLXContext ctx;
+};
+
+#define glXGetFBConfigAttribChecked(a, b, attr, c) \
+ do { \
+ if (glXGetFBConfigAttrib(a, b, attr, c)) { \
+ log_info("Cannot get FBConfig attribute " #attr); \
+ continue; \
+ } \
+ } while (0)
+
+struct glx_fbconfig_info *glx_find_fbconfig(Display *dpy, int screen, struct xvisual_info m) {
+ log_debug("Looking for FBConfig for RGBA%d%d%d%d, depth %d", m.red_size,
+ m.blue_size, m.green_size, m.alpha_size, m.visual_depth);
+
+ int ncfg;
+ // clang-format off
+ GLXFBConfig *cfg =
+ glXChooseFBConfig(dpy, screen, (int[]){
+ GLX_RENDER_TYPE, GLX_RGBA_BIT,
+ GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT,
+ GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
+ GLX_X_RENDERABLE, true,
+ GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, (GLint)GLX_DONT_CARE,
+ GLX_BUFFER_SIZE, m.red_size + m.green_size +
+ m.blue_size + m.alpha_size,
+ GLX_RED_SIZE, m.red_size,
+ GLX_BLUE_SIZE, m.blue_size,
+ GLX_GREEN_SIZE, m.green_size,
+ GLX_ALPHA_SIZE, m.alpha_size,
+ GLX_STENCIL_SIZE, 0,
+ GLX_DEPTH_SIZE, 0,
+ 0
+ }, &ncfg);
+ // clang-format on
+
+ int texture_tgts, y_inverted, texture_fmt;
+ bool found = false;
+ int min_cost = INT_MAX;
+ GLXFBConfig ret;
+ for (int i = 0; i < ncfg; i++) {
+ int depthbuf, stencil, doublebuf, bufsize;
+ glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BUFFER_SIZE, &bufsize);
+ glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_DEPTH_SIZE, &depthbuf);
+ glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_STENCIL_SIZE, &stencil);
+ glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_DOUBLEBUFFER, &doublebuf);
+ if (depthbuf + stencil + bufsize * (doublebuf + 1) >= min_cost) {
+ continue;
+ }
+ int red, green, blue;
+ glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_RED_SIZE, &red);
+ glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BLUE_SIZE, &blue);
+ glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_GREEN_SIZE, &green);
+ if (red != m.red_size || green != m.green_size || blue != m.blue_size) {
+ // Color size doesn't match, this cannot work
+ continue;
+ }
+
+ int rgb, rgba;
+ glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BIND_TO_TEXTURE_RGB_EXT, &rgb);
+ glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BIND_TO_TEXTURE_RGBA_EXT, &rgba);
+ if (!rgb && !rgba) {
+ log_info("FBConfig is neither RGBA nor RGB, we cannot "
+ "handle this setup.");
+ continue;
+ }
+
+ int visual;
+ glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_VISUAL_ID, &visual);
+ if (m.visual_depth != -1 &&
+ x_get_visual_depth(XGetXCBConnection(dpy), (xcb_visualid_t)visual) !=
+ m.visual_depth) {
+ // FBConfig and the correspondent X Visual might not have the same
+ // depth. (e.g. 32 bit FBConfig with a 24 bit Visual). This is
+ // quite common, seen in both open source and proprietary drivers.
+ //
+ // If the FBConfig has a matching depth but its visual doesn't, we
+ // still cannot use it.
+ continue;
+ }
+
+ // All check passed, we are using this one.
+ found = true;
+ ret = cfg[i];
+ glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_BIND_TO_TEXTURE_TARGETS_EXT,
+ &texture_tgts);
+ glXGetFBConfigAttribChecked(dpy, cfg[i], GLX_Y_INVERTED_EXT, &y_inverted);
+
+ // Prefer the texture format with matching alpha, with the other one as
+ // fallback
+ if (m.alpha_size) {
+ texture_fmt = rgba ? GLX_TEXTURE_FORMAT_RGBA_EXT
+ : GLX_TEXTURE_FORMAT_RGB_EXT;
+ } else {
+ texture_fmt =
+ rgb ? GLX_TEXTURE_FORMAT_RGB_EXT : GLX_TEXTURE_FORMAT_RGBA_EXT;
+ }
+ min_cost = depthbuf + stencil + bufsize * (doublebuf + 1);
+ }
+ free(cfg);
+ if (!found) {
+ return NULL;
+ }
+
+ auto info = cmalloc(struct glx_fbconfig_info);
+ info->cfg = ret;
+ info->texture_tgts = texture_tgts;
+ info->texture_fmt = texture_fmt;
+ info->y_inverted = y_inverted;
+ return info;
+}
+
+/**
+ * Free a glx_texture_t.
+ */
+static void glx_release_image(backend_t *base, struct gl_texture *tex) {
+ struct _glx_data *gd = (void *)base;
+
+ struct _glx_pixmap *p = tex->user_data;
+ // Release binding
+ if (p->glpixmap && tex->texture) {
+ glBindTexture(GL_TEXTURE_2D, tex->texture);
+ glXReleaseTexImageEXT(gd->display, p->glpixmap, GLX_FRONT_LEFT_EXT);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ }
+
+ // Free GLX Pixmap
+ if (p->glpixmap) {
+ glXDestroyPixmap(gd->display, p->glpixmap);
+ p->glpixmap = 0;
+ }
+
+ if (p->owned) {
+ xcb_free_pixmap(base->c, p->pixmap);
+ p->pixmap = XCB_NONE;
+ }
+
+ free(p);
+ tex->user_data = NULL;
+}
+
+/**
+ * Destroy GLX related resources.
+ */
+void glx_deinit(backend_t *base) {
+ struct _glx_data *gd = (void *)base;
+
+ gl_deinit(&gd->gl);
+
+ // Destroy GLX context
+ if (gd->ctx) {
+ glXMakeCurrent(gd->display, None, NULL);
+ glXDestroyContext(gd->display, gd->ctx);
+ gd->ctx = 0;
+ }
+
+ free(gd);
+}
+
+static void *glx_decouple_user_data(backend_t *base attr_unused, void *ud attr_unused) {
+ auto ret = cmalloc(struct _glx_pixmap);
+ ret->owned = false;
+ ret->glpixmap = 0;
+ ret->pixmap = 0;
+ return ret;
+}
+
+static bool glx_set_swap_interval(int interval, Display *dpy, GLXDrawable drawable) {
+ bool vsync_enabled = false;
+ if (glxext.has_GLX_MESA_swap_control) {
+ vsync_enabled = (glXSwapIntervalMESA((uint)interval) == 0);
+ }
+ if (!vsync_enabled && glxext.has_GLX_SGI_swap_control) {
+ vsync_enabled = (glXSwapIntervalSGI(interval) == 0);
+ }
+ if (!vsync_enabled && glxext.has_GLX_EXT_swap_control) {
+ // glXSwapIntervalEXT doesn't return if it's successful
+ glXSwapIntervalEXT(dpy, drawable, interval);
+ vsync_enabled = true;
+ }
+ return vsync_enabled;
+}
+
+/**
+ * Initialize OpenGL.
+ */
+static backend_t *glx_init(session_t *ps) {
+ bool success = false;
+ glxext_init(ps->dpy, ps->scr);
+ auto gd = ccalloc(1, struct _glx_data);
+ init_backend_base(&gd->gl.base, ps);
+
+ gd->display = ps->dpy;
+ gd->screen = ps->scr;
+ gd->target_win = session_get_target_window(ps);
+
+ XVisualInfo *pvis = NULL;
+
+ // Check for GLX extension
+ if (!ps->glx_exists) {
+ log_error("No GLX extension.");
+ goto end;
+ }
+
+ // Get XVisualInfo
+ int nitems = 0;
+ XVisualInfo vreq = {.visualid = ps->vis};
+ pvis = XGetVisualInfo(ps->dpy, VisualIDMask, &vreq, &nitems);
+ if (!pvis) {
+ log_error("Failed to acquire XVisualInfo for current visual.");
+ goto end;
+ }
+
+ // Ensure the visual is double-buffered
+ int value = 0;
+ if (glXGetConfig(ps->dpy, pvis, GLX_USE_GL, &value) || !value) {
+ log_error("Root visual is not a GL visual.");
+ goto end;
+ }
+
+ if (glXGetConfig(ps->dpy, pvis, GLX_STENCIL_SIZE, &value) || !value) {
+ log_error("Root visual lacks stencil buffer.");
+ goto end;
+ }
+
+ if (glXGetConfig(ps->dpy, pvis, GLX_DOUBLEBUFFER, &value) || !value) {
+ log_error("Root visual is not a double buffered GL visual.");
+ goto end;
+ }
+
+ if (glXGetConfig(ps->dpy, pvis, GLX_RGBA, &value) || !value) {
+ log_error("Root visual is a color index visual, not supported");
+ goto end;
+ }
+
+ if (!glxext.has_GLX_EXT_texture_from_pixmap) {
+ log_error("GLX_EXT_texture_from_pixmap is not supported by your driver");
+ goto end;
+ }
+
+ if (!glxext.has_GLX_ARB_create_context) {
+ log_error("GLX_ARB_create_context is not supported by your driver");
+ goto end;
+ }
+
+ // Find a fbconfig with visualid matching the one from the target win, so we can
+ // be sure that the fbconfig is compatible with our target window.
+ int ncfgs;
+ GLXFBConfig *cfg = glXGetFBConfigs(gd->display, gd->screen, &ncfgs);
+ bool found = false;
+ for (int i = 0; i < ncfgs; i++) {
+ int visualid;
+ glXGetFBConfigAttribChecked(gd->display, cfg[i], GLX_VISUAL_ID, &visualid);
+ if ((VisualID)visualid != pvis->visualid) {
+ continue;
+ }
+
+ gd->ctx = glXCreateContextAttribsARB(ps->dpy, cfg[i], 0, true,
+ (int[]){
+ GLX_CONTEXT_MAJOR_VERSION_ARB,
+ 3,
+ GLX_CONTEXT_MINOR_VERSION_ARB,
+ 3,
+ GLX_CONTEXT_PROFILE_MASK_ARB,
+ GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
+ 0,
+ });
+ free(cfg);
+
+ if (!gd->ctx) {
+ log_error("Failed to get GLX context.");
+ goto end;
+ }
+ found = true;
+ break;
+ }
+
+ if (!found) {
+ log_error("Couldn't find a suitable fbconfig for the target window");
+ goto end;
+ }
+
+ // Attach GLX context
+ GLXDrawable tgt = gd->target_win;
+ if (!glXMakeCurrent(ps->dpy, tgt, gd->ctx)) {
+ log_error("Failed to attach GLX context.");
+ goto end;
+ }
+
+ if (!gl_init(&gd->gl, ps)) {
+ log_error("Failed to setup OpenGL");
+ goto end;
+ }
+
+ gd->gl.decouple_texture_user_data = glx_decouple_user_data;
+ gd->gl.release_user_data = glx_release_image;
+
+ if (ps->o.vsync) {
+ if (!glx_set_swap_interval(1, ps->dpy, tgt)) {
+ log_error("Failed to enable vsync.");
+ }
+ } else {
+ glx_set_swap_interval(0, ps->dpy, tgt);
+ }
+
+ success = true;
+
+end:
+ if (pvis) {
+ XFree(pvis);
+ }
+
+ if (!success) {
+ glx_deinit(&gd->gl.base);
+ return NULL;
+ }
+
+ return &gd->gl.base;
+}
+
+static void *
+glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) {
+ struct _glx_data *gd = (void *)base;
+ struct _glx_pixmap *glxpixmap = NULL;
+ // Retrieve pixmap parameters, if they aren't provided
+ if (fmt.visual_depth > OPENGL_MAX_DEPTH) {
+ log_error("Requested depth %d higher than max possible depth %d.",
+ fmt.visual_depth, OPENGL_MAX_DEPTH);
+ return false;
+ }
+
+ if (fmt.visual_depth < 0) {
+ log_error("Pixmap %#010x with invalid depth %d", pixmap, fmt.visual_depth);
+ return false;
+ }
+
+ auto r = xcb_get_geometry_reply(base->c, xcb_get_geometry(base->c, pixmap), NULL);
+ if (!r) {
+ log_error("Invalid pixmap %#010x", pixmap);
+ return NULL;
+ }
+
+ log_trace("Binding pixmap %#010x", pixmap);
+ auto wd = ccalloc(1, struct backend_image);
+ wd->max_brightness = 1;
+ auto inner = ccalloc(1, struct gl_texture);
+ inner->width = wd->ewidth = r->width;
+ inner->height = wd->eheight = r->height;
+ wd->inner = (struct backend_image_inner_base *)inner;
+ free(r);
+
+ auto fbcfg = glx_find_fbconfig(gd->display, gd->screen, fmt);
+ if (!fbcfg) {
+ log_error("Couldn't find FBConfig with requested visual %x", fmt.visual);
+ goto err;
+ }
+
+ // Choose a suitable texture target for our pixmap.
+ // Refer to GLX_EXT_texture_om_pixmap spec to see what are the mean
+ // of the bits in texture_tgts
+ if (!(fbcfg->texture_tgts & GLX_TEXTURE_2D_BIT_EXT)) {
+ log_error("Cannot bind pixmap to GL_TEXTURE_2D, giving up");
+ goto err;
+ }
+
+ log_debug("depth %d, rgba %d", fmt.visual_depth,
+ (fbcfg->texture_fmt == GLX_TEXTURE_FORMAT_RGBA_EXT));
+
+ GLint attrs[] = {
+ GLX_TEXTURE_FORMAT_EXT,
+ fbcfg->texture_fmt,
+ GLX_TEXTURE_TARGET_EXT,
+ GLX_TEXTURE_2D_EXT,
+ 0,
+ };
+
+ inner->y_inverted = fbcfg->y_inverted;
+
+ glxpixmap = cmalloc(struct _glx_pixmap);
+ glxpixmap->pixmap = pixmap;
+ glxpixmap->glpixmap = glXCreatePixmap(gd->display, fbcfg->cfg, pixmap, attrs);
+ glxpixmap->owned = owned;
+ free(fbcfg);
+
+ if (!glxpixmap->glpixmap) {
+ log_error("Failed to create glpixmap for pixmap %#010x", pixmap);
+ goto err;
+ }
+
+ log_trace("GLXPixmap %#010lx", glxpixmap->glpixmap);
+
+ // Create texture
+ inner->user_data = glxpixmap;
+ inner->texture = gl_new_texture(GL_TEXTURE_2D);
+ inner->has_alpha = fmt.alpha_size != 0;
+ wd->opacity = 1;
+ wd->color_inverted = false;
+ wd->dim = 0;
+ wd->inner->refcount = 1;
+ glBindTexture(GL_TEXTURE_2D, inner->texture);
+ glXBindTexImageEXT(gd->display, glxpixmap->glpixmap, GLX_FRONT_LEFT_EXT, NULL);
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ gl_check_err();
+ return wd;
+err:
+ if (glxpixmap && glxpixmap->glpixmap) {
+ glXDestroyPixmap(gd->display, glxpixmap->glpixmap);
+ }
+ free(glxpixmap);
+
+ if (owned) {
+ xcb_free_pixmap(base->c, pixmap);
+ }
+ free(wd);
+ return NULL;
+}
+
+static void glx_present(backend_t *base, const region_t *region attr_unused) {
+ struct _glx_data *gd = (void *)base;
+ gl_present(base, region);
+ glXSwapBuffers(gd->display, gd->target_win);
+ if (!gd->gl.is_nvidia) {
+ glFinish();
+ }
+}
+
+static int glx_buffer_age(backend_t *base) {
+ if (!glxext.has_GLX_EXT_buffer_age) {
+ return -1;
+ }
+
+ struct _glx_data *gd = (void *)base;
+ unsigned int val;
+ glXQueryDrawable(gd->display, gd->target_win, GLX_BACK_BUFFER_AGE_EXT, &val);
+ return (int)val ?: -1;
+}
+
+static void glx_diagnostics(backend_t *base) {
+ struct _glx_data *gd = (void *)base;
+ bool warn_software_rendering = false;
+ const char *software_renderer_names[] = {"llvmpipe", "SWR", "softpipe"};
+ auto glx_vendor = glXGetClientString(gd->display, GLX_VENDOR);
+ printf("* Driver vendors:\n");
+ printf(" * GLX: %s\n", glx_vendor);
+ printf(" * GL: %s\n", glGetString(GL_VENDOR));
+
+ auto gl_renderer = (const char *)glGetString(GL_RENDERER);
+ printf("* GL renderer: %s\n", gl_renderer);
+ if (strcmp(glx_vendor, "Mesa Project and SGI")) {
+ for (size_t i = 0; i < ARR_SIZE(software_renderer_names); i++) {
+ if (strstr(gl_renderer, software_renderer_names[i]) != NULL) {
+ warn_software_rendering = true;
+ break;
+ }
+ }
+ }
+
+#ifdef GLX_MESA_query_renderer
+ if (glxext.has_GLX_MESA_query_renderer) {
+ unsigned int accelerated = 0;
+ glXQueryCurrentRendererIntegerMESA(GLX_RENDERER_ACCELERATED_MESA, &accelerated);
+ printf("* Accelerated: %d\n", accelerated);
+
+ // Trust GLX_MESA_query_renderer when it's available
+ warn_software_rendering = (accelerated == 0);
+ }
+#endif
+
+ if (warn_software_rendering) {
+ printf("\n(You are using a software renderer. Unless you are doing this\n"
+ "intentionally, this means you don't have a graphics driver\n"
+ "properly installed. Performance will suffer. Please fix this\n"
+ "before reporting your issue.)\n");
+ }
+}
+
+struct backend_operations glx_ops = {
+ .init = glx_init,
+ .deinit = glx_deinit,
+ .bind_pixmap = glx_bind_pixmap,
+ .release_image = gl_release_image,
+ .compose = gl_compose,
+ .image_op = gl_image_op,
+ .set_image_property = default_set_image_property,
+ .read_pixel = gl_read_pixel,
+ .clone_image = default_clone_image,
+ .blur = gl_blur,
+ .is_image_transparent = default_is_image_transparent,
+ .present = glx_present,
+ .buffer_age = glx_buffer_age,
+ .render_shadow = default_backend_render_shadow,
+ .fill = gl_fill,
+ .create_blur_context = gl_create_blur_context,
+ .destroy_blur_context = gl_destroy_blur_context,
+ .get_blur_size = gl_get_blur_size,
+ .diagnostics = glx_diagnostics,
+ .max_buffer_age = 5, // Why?
+};
+
+/**
+ * Check if a GLX extension exists.
+ */
+static inline bool glx_has_extension(Display *dpy, int screen, const char *ext) {
+ const char *glx_exts = glXQueryExtensionsString(dpy, screen);
+ if (!glx_exts) {
+ log_error("Failed get GLX extension list.");
+ return false;
+ }
+
+ auto inlen = strlen(ext);
+ const char *curr = glx_exts;
+ bool match = false;
+ while (curr && !match) {
+ const char *end = strchr(curr, ' ');
+ if (!end) {
+ // Last extension string
+ match = strcmp(ext, curr) == 0;
+ } else if (curr + inlen == end) {
+ // Length match, do match string
+ match = strncmp(ext, curr, (unsigned long)(end - curr)) == 0;
+ }
+ curr = end ? end + 1 : NULL;
+ }
+
+ if (!match) {
+ log_info("Missing GLX extension %s.", ext);
+ } else {
+ log_info("Found GLX extension %s.", ext);
+ }
+
+ return match;
+}
+
+struct glxext_info glxext = {0};
+PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI;
+PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI;
+PFNGLXGETSYNCVALUESOMLPROC glXGetSyncValuesOML;
+PFNGLXWAITFORMSCOMLPROC glXWaitForMscOML;
+PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT;
+PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI;
+PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA;
+PFNGLXBINDTEXIMAGEEXTPROC glXBindTexImageEXT;
+PFNGLXRELEASETEXIMAGEEXTPROC glXReleaseTexImageEXT;
+PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB;
+
+#ifdef GLX_MESA_query_renderer
+PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC glXQueryCurrentRendererIntegerMESA;
+#endif
+
+void glxext_init(Display *dpy, int screen) {
+ if (glxext.initialized) {
+ return;
+ }
+ glxext.initialized = true;
+#define check_ext(name) glxext.has_##name = glx_has_extension(dpy, screen, #name)
+ check_ext(GLX_SGI_video_sync);
+ check_ext(GLX_SGI_swap_control);
+ check_ext(GLX_OML_sync_control);
+ check_ext(GLX_MESA_swap_control);
+ check_ext(GLX_EXT_swap_control);
+ check_ext(GLX_EXT_texture_from_pixmap);
+ check_ext(GLX_ARB_create_context);
+ check_ext(GLX_EXT_buffer_age);
+#ifdef GLX_MESA_query_renderer
+ check_ext(GLX_MESA_query_renderer);
+#endif
+#undef check_ext
+
+#define lookup(name) (name = (__typeof__(name))glXGetProcAddress((GLubyte *)#name))
+ // Checking if the returned function pointer is NULL is not really necessary,
+ // or maybe not even useful, since glXGetProcAddress might always return
+ // something. We are doing it just for completeness' sake.
+ if (!lookup(glXGetVideoSyncSGI) || !lookup(glXWaitVideoSyncSGI)) {
+ glxext.has_GLX_SGI_video_sync = false;
+ }
+ if (!lookup(glXSwapIntervalEXT)) {
+ glxext.has_GLX_EXT_swap_control = false;
+ }
+ if (!lookup(glXSwapIntervalMESA)) {
+ glxext.has_GLX_MESA_swap_control = false;
+ }
+ if (!lookup(glXSwapIntervalSGI)) {
+ glxext.has_GLX_SGI_swap_control = false;
+ }
+ if (!lookup(glXWaitForMscOML) || !lookup(glXGetSyncValuesOML)) {
+ glxext.has_GLX_OML_sync_control = false;
+ }
+ if (!lookup(glXBindTexImageEXT) || !lookup(glXReleaseTexImageEXT)) {
+ glxext.has_GLX_EXT_texture_from_pixmap = false;
+ }
+ if (!lookup(glXCreateContextAttribsARB)) {
+ glxext.has_GLX_ARB_create_context = false;
+ }
+#ifdef GLX_MESA_query_renderer
+ if (!lookup(glXQueryCurrentRendererIntegerMESA)) {
+ glxext.has_GLX_MESA_query_renderer = false;
+ }
+#endif
+#undef lookup
+}
diff --git a/src/backend/gl/glx.h b/src/backend/gl/glx.h
new file mode 100644
index 0000000..1061f0b
--- /dev/null
+++ b/src/backend/gl/glx.h
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) Yuxuan Shui <[email protected]>
+#pragma once
+#include <stdbool.h>
+// Older version of glx.h defines function prototypes for these extensions...
+// Rename them to avoid conflicts
+#define glXSwapIntervalMESA glXSwapIntervalMESA_
+#define glXBindTexImageEXT glXBindTexImageEXT_
+#define glXReleaseTexImageEXT glXReleaseTexImageEXT
+#include <GL/glx.h>
+#undef glXSwapIntervalMESA
+#undef glXBindTexImageEXT
+#undef glXReleaseTexImageEXT
+#include <X11/Xlib.h>
+#include <xcb/xcb.h>
+#include <xcb/render.h>
+
+#include "log.h"
+#include "compiler.h"
+#include "utils.h"
+#include "x.h"
+
+struct glx_fbconfig_info {
+ GLXFBConfig cfg;
+ int texture_tgts;
+ int texture_fmt;
+ int y_inverted;
+};
+
+/// The search criteria for glx_find_fbconfig
+struct glx_fbconfig_criteria {
+ /// Bit width of the red component
+ int red_size;
+ /// Bit width of the green component
+ int green_size;
+ /// Bit width of the blue component
+ int blue_size;
+ /// Bit width of the alpha component
+ int alpha_size;
+ /// The depth of X visual
+ int visual_depth;
+};
+
+struct glx_fbconfig_info *glx_find_fbconfig(Display *, int screen, struct xvisual_info);
+
+
+struct glxext_info {
+ bool initialized;
+ bool has_GLX_SGI_video_sync;
+ bool has_GLX_SGI_swap_control;
+ bool has_GLX_OML_sync_control;
+ bool has_GLX_MESA_swap_control;
+ bool has_GLX_EXT_swap_control;
+ bool has_GLX_EXT_texture_from_pixmap;
+ bool has_GLX_ARB_create_context;
+ bool has_GLX_EXT_buffer_age;
+ bool has_GLX_MESA_query_renderer;
+};
+
+extern struct glxext_info glxext;
+
+extern PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI;
+extern PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI;
+extern PFNGLXGETSYNCVALUESOMLPROC glXGetSyncValuesOML;
+extern PFNGLXWAITFORMSCOMLPROC glXWaitForMscOML;
+extern PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT;
+extern PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI;
+extern PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA;
+extern PFNGLXBINDTEXIMAGEEXTPROC glXBindTexImageEXT;
+extern PFNGLXRELEASETEXIMAGEEXTPROC glXReleaseTexImageEXT;
+extern PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB;
+
+#ifdef GLX_MESA_query_renderer
+extern PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC glXQueryCurrentRendererIntegerMESA;
+#endif
+
+void glxext_init(Display *, int screen);
diff --git a/src/backend/meson.build b/src/backend/meson.build
new file mode 100644
index 0000000..b8f0ad9
--- /dev/null
+++ b/src/backend/meson.build
@@ -0,0 +1,7 @@
+# enable xrender
+srcs += [ files('backend_common.c', 'xrender/xrender.c', 'dummy/dummy.c', 'backend.c', 'driver.c') ]
+
+# enable opengl
+if get_option('opengl')
+ srcs += [ files('gl/gl_common.c', 'gl/glx.c') ]
+endif
diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c
new file mode 100644
index 0000000..ccf358b
--- /dev/null
+++ b/src/backend/xrender/xrender.c
@@ -0,0 +1,779 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) Yuxuan Shui <[email protected]>
+#include <assert.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <xcb/composite.h>
+#include <xcb/present.h>
+#include <xcb/render.h>
+#include <xcb/sync.h>
+#include <xcb/xcb.h>
+
+#include "backend/backend.h"
+#include "backend/backend_common.h"
+#include "common.h"
+#include "config.h"
+#include "kernel.h"
+#include "log.h"
+#include "picom.h"
+#include "region.h"
+#include "types.h"
+#include "utils.h"
+#include "win.h"
+#include "x.h"
+
+typedef struct _xrender_data {
+ backend_t base;
+ /// If vsync is enabled and supported by the current system
+ bool vsync;
+ xcb_visualid_t default_visual;
+ /// Target window
+ xcb_window_t target_win;
+ /// Painting target, it is either the root or the overlay
+ xcb_render_picture_t target;
+ /// Back buffers. Double buffer, with 1 for temporary render use
+ xcb_render_picture_t back[3];
+ /// The back buffer that is for temporary use
+ /// Age of each back buffer.
+ int buffer_age[3];
+ /// The back buffer we should be painting into
+ int curr_back;
+ /// The corresponding pixmap to the back buffer
+ xcb_pixmap_t back_pixmap[3];
+ /// Pictures of pixel of different alpha value, used as a mask to
+ /// paint transparent images
+ xcb_render_picture_t alpha_pict[256];
+
+ // XXX don't know if these are really needed
+
+ /// 1x1 white picture
+ xcb_render_picture_t white_pixel;
+ /// 1x1 black picture
+ xcb_render_picture_t black_pixel;
+
+ /// Width and height of the target pixmap
+ int target_width, target_height;
+
+ xcb_special_event_t *present_event;
+} xrender_data;
+
+struct _xrender_blur_context {
+ enum blur_method method;
+ /// Blur kernels converted to X format
+ struct x_convolution_kernel **x_blur_kernel;
+
+ int resize_width, resize_height;
+
+ /// Number of blur kernels
+ int x_blur_kernel_count;
+};
+
+struct _xrender_image_data_inner {
+ // struct backend_image_inner_base
+ int refcount;
+ bool has_alpha;
+
+ // Pixmap that the client window draws to,
+ // it will contain the content of client window.
+ xcb_pixmap_t pixmap;
+ // A Picture links to the Pixmap
+ xcb_render_picture_t pict;
+ int width, height;
+ xcb_visualid_t visual;
+ uint8_t depth;
+ // Whether we own this image, e.g. we allocated it;
+ // or not, e.g. this is a named pixmap of a X window.
+ bool owned;
+};
+
+static void compose_impl(struct _xrender_data *xd, const struct backend_image *img,
+ int dst_x1, int dst_y1, int dst_x2, int dst_y2,
+ const region_t *reg_paint, const region_t *reg_visible,
+ xcb_render_picture_t result) {
+ auto alpha_pict = xd->alpha_pict[(int)(img->opacity * MAX_ALPHA)];
+ auto inner = (struct _xrender_image_data_inner *)img->inner;
+ region_t reg;
+
+ bool has_alpha = inner->has_alpha || img->opacity != 1;
+ const auto tmpw = to_u16_checked(dst_x2 - dst_x1);
+ const auto tmph = to_u16_checked(dst_y2 - dst_y1);
+ const auto tmpew = to_u16_checked(dst_x2 - dst_x1);
+ const auto tmpeh = to_u16_checked(dst_y2 - dst_y1);
+ const xcb_render_color_t dim_color = {
+ .red = 0, .green = 0, .blue = 0, .alpha = (uint16_t)(0xffff * img->dim)};
+
+ // Clip region of rendered_pict might be set during rendering, clear it to
+ // make sure we get everything into the buffer
+ x_clear_picture_clip_region(xd->base.c, inner->pict);
+
+ pixman_region32_init(&reg);
+ pixman_region32_intersect(&reg, (region_t *)reg_paint, (region_t *)reg_visible);
+ x_set_picture_clip_region(xd->base.c, result, 0, 0, &reg);
+
+#define DOUBLE_TO_XFIXED(value) ((xcb_render_fixed_t)(((double)(value)) * 65536))
+ {
+ const xcb_render_transform_t transform = {
+ DOUBLE_TO_XFIXED((double)img->ewidth / (double)tmpew), DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED(0.0),
+ DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED((double)img->eheight / (double)tmpeh), DOUBLE_TO_XFIXED(0.0),
+ DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED(1.0),
+ };
+ xcb_render_set_picture_transform(xd->base.c, inner->pict, transform);
+ xcb_render_set_picture_filter(xd->base.c, inner->pict, 7, "nearest", 0, NULL);
+ }
+#undef DOUBLE_TO_XFIXED
+
+ if ((img->color_inverted || img->dim != 0) && has_alpha) {
+ // Apply image properties using a temporary image, because the source
+ // image is transparent. Otherwise the properties can be applied directly
+ // on the target image.
+ auto tmp_pict =
+ x_create_picture_with_visual(xd->base.c, xd->base.root, inner->width,
+ inner->height, inner->visual, 0, NULL);
+
+ // Set clip region translated to source coordinate
+ x_set_picture_clip_region(xd->base.c, tmp_pict, to_i16_checked(-dst_x1),
+ to_i16_checked(-dst_y1), &reg);
+ // Copy source -> tmp
+ xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER, inner->pict,
+ XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph);
+ if (img->color_inverted) {
+ if (inner->has_alpha) {
+ auto tmp_pict2 = x_create_picture_with_visual(
+ xd->base.c, xd->base.root, tmpw, tmph, inner->visual,
+ 0, NULL);
+ xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER,
+ tmp_pict, XCB_NONE, tmp_pict2, 0, 0,
+ 0, 0, 0, 0, tmpw, tmph);
+
+ xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_DIFFERENCE,
+ xd->white_pixel, XCB_NONE, tmp_pict,
+ 0, 0, 0, 0, 0, 0, tmpw, tmph);
+ xcb_render_composite(
+ xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, tmp_pict2,
+ XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph);
+ xcb_render_free_picture(xd->base.c, tmp_pict2);
+ } else {
+ xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_DIFFERENCE,
+ xd->white_pixel, XCB_NONE, tmp_pict,
+ 0, 0, 0, 0, 0, 0, tmpw, tmph);
+ }
+ }
+ if (img->dim != 0) {
+ // Dim the actually content of window
+ xcb_rectangle_t rect = {
+ .x = 0,
+ .y = 0,
+ .width = tmpw,
+ .height = tmph,
+ };
+
+ xcb_render_fill_rectangles(xd->base.c, XCB_RENDER_PICT_OP_OVER,
+ tmp_pict, dim_color, 1, &rect);
+ }
+
+ xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER, tmp_pict,
+ alpha_pict, result, 0, 0, 0, 0, to_i16_checked(dst_x1),
+ to_i16_checked(dst_y1), tmpew, tmpeh);
+ xcb_render_free_picture(xd->base.c, tmp_pict);
+ } else {
+ uint8_t op = (has_alpha ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_SRC);
+
+ xcb_render_composite(xd->base.c, op, inner->pict, alpha_pict, result, 0,
+ 0, 0, 0, to_i16_checked(dst_x1),
+ to_i16_checked(dst_y1), tmpew, tmpeh);
+ if (img->dim != 0 || img->color_inverted) {
+ // Apply properties, if we reach here, then has_alpha == false
+ assert(!has_alpha);
+ if (img->color_inverted) {
+ xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_DIFFERENCE,
+ xd->white_pixel, XCB_NONE, result, 0,
+ 0, 0, 0, to_i16_checked(dst_x1),
+ to_i16_checked(dst_y1), tmpew, tmpeh);
+ }
+
+ if (img->dim != 0) {
+ // Dim the actually content of window
+ xcb_rectangle_t rect = {
+ .x = to_i16_checked(dst_x1),
+ .y = to_i16_checked(dst_y1),
+ .width = tmpew,
+ .height = tmpeh,
+ };
+
+ xcb_render_fill_rectangles(xd->base.c, XCB_RENDER_PICT_OP_OVER,
+ result, dim_color, 1, &rect);
+ }
+ }
+ }
+ pixman_region32_fini(&reg);
+}
+
+static void compose(backend_t *base, void *img_data,
+ int dst_x1, int dst_y1, int dst_x2, int dst_y2,
+ const region_t *reg_paint, const region_t *reg_visible) {
+ // TODO(dccsillag): use dst_{x,y}2
+ struct _xrender_data *xd = (void *)base;
+ return compose_impl(xd, img_data, dst_x1, dst_y1, dst_x2, dst_y2, reg_paint, reg_visible, xd->back[2]);
+}
+
+static void fill(backend_t *base, struct color c, const region_t *clip) {
+ struct _xrender_data *xd = (void *)base;
+ const rect_t *extent = pixman_region32_extents((region_t *)clip);
+ x_set_picture_clip_region(base->c, xd->back[2], 0, 0, clip);
+ // color is in X fixed point representation
+ xcb_render_fill_rectangles(
+ base->c, XCB_RENDER_PICT_OP_OVER, xd->back[2],
+ (xcb_render_color_t){.red = (uint16_t)(c.red * 0xffff),
+ .green = (uint16_t)(c.green * 0xffff),
+ .blue = (uint16_t)(c.blue * 0xffff),
+ .alpha = (uint16_t)(c.alpha * 0xffff)},
+ 1,
+ (xcb_rectangle_t[]){{.x = to_i16_checked(extent->x1),
+ .y = to_i16_checked(extent->y1),
+ .width = to_u16_checked(extent->x2 - extent->x1),
+ .height = to_u16_checked(extent->y2 - extent->y1)}});
+}
+
+static bool blur(backend_t *backend_data, double opacity, void *ctx_,
+ const region_t *reg_blur, const region_t *reg_visible) {
+ struct _xrender_blur_context *bctx = ctx_;
+ if (bctx->method == BLUR_METHOD_NONE) {
+ return true;
+ }
+
+ struct _xrender_data *xd = (void *)backend_data;
+ xcb_connection_t *c = xd->base.c;
+ region_t reg_op;
+ pixman_region32_init(&reg_op);
+ pixman_region32_intersect(&reg_op, (region_t *)reg_blur, (region_t *)reg_visible);
+ if (!pixman_region32_not_empty(&reg_op)) {
+ pixman_region32_fini(&reg_op);
+ return true;
+ }
+
+ region_t reg_op_resized =
+ resize_region(&reg_op, bctx->resize_width, bctx->resize_height);
+
+ const pixman_box32_t *extent_resized = pixman_region32_extents(&reg_op_resized);
+ const auto height_resized = to_u16_checked(extent_resized->y2 - extent_resized->y1);
+ const auto width_resized = to_u16_checked(extent_resized->x2 - extent_resized->x1);
+ static const char *filter0 = "Nearest"; // The "null" filter
+ static const char *filter = "convolution";
+
+ // Create a buffer for storing blurred picture, make it just big enough
+ // for the blur region
+ const uint32_t pic_attrs_mask = XCB_RENDER_CP_REPEAT;
+ const xcb_render_create_picture_value_list_t pic_attrs = {.repeat = XCB_RENDER_REPEAT_PAD};
+ xcb_render_picture_t tmp_picture[2] = {
+ x_create_picture_with_visual(xd->base.c, xd->base.root, width_resized, height_resized,
+ xd->default_visual, pic_attrs_mask, &pic_attrs),
+ x_create_picture_with_visual(xd->base.c, xd->base.root, width_resized, height_resized,
+ xd->default_visual, pic_attrs_mask, &pic_attrs)};
+
+ if (!tmp_picture[0] || !tmp_picture[1]) {
+ log_error("Failed to build intermediate Picture.");
+ pixman_region32_fini(&reg_op);
+ pixman_region32_fini(&reg_op_resized);
+ return false;
+ }
+
+ region_t clip;
+ pixman_region32_init(&clip);
+ pixman_region32_copy(&clip, &reg_op_resized);
+ pixman_region32_translate(&clip, -extent_resized->x1, -extent_resized->y1);
+ x_set_picture_clip_region(c, tmp_picture[0], 0, 0, &clip);
+ x_set_picture_clip_region(c, tmp_picture[1], 0, 0, &clip);
+ pixman_region32_fini(&clip);
+
+ xcb_render_picture_t src_pict = xd->back[2], dst_pict = tmp_picture[0];
+ auto alpha_pict = xd->alpha_pict[(int)(opacity * MAX_ALPHA)];
+ int current = 0;
+ x_set_picture_clip_region(c, src_pict, 0, 0, &reg_op_resized);
+
+ // For more than 1 pass, we do:
+ // back -(pass 1)-> tmp0 -(pass 2)-> tmp1 ...
+ // -(pass n-1)-> tmp0 or tmp1 -(pass n)-> back
+ // For 1 pass, we do
+ // back -(pass 1)-> tmp0 -(copy)-> target_buffer
+ int i;
+ for (i = 0; i < bctx->x_blur_kernel_count; i++) {
+ // Copy from source picture to destination. The filter must
+ // be applied on source picture, to get the nearby pixels outside the
+ // window.
+ xcb_render_set_picture_filter(c, src_pict, to_u16_checked(strlen(filter)),
+ filter,
+ to_u32_checked(bctx->x_blur_kernel[i]->size),
+ bctx->x_blur_kernel[i]->kernel);
+
+ if (i == 0) {
+ // First pass, back buffer -> tmp picture
+ // (we do this even if this is also the last pass, because we
+ // cannot do back buffer -> back buffer)
+ xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, src_pict, XCB_NONE,
+ dst_pict, to_i16_checked(extent_resized->x1),
+ to_i16_checked(extent_resized->y1), 0, 0, 0,
+ 0, width_resized, height_resized);
+ } else if (i < bctx->x_blur_kernel_count - 1) {
+ // This is not the last pass or the first pass,
+ // tmp picture 1 -> tmp picture 2
+ xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, src_pict,
+ XCB_NONE, dst_pict, 0, 0, 0, 0, 0, 0,
+ width_resized, height_resized);
+ } else {
+ x_set_picture_clip_region(c, xd->back[2], 0, 0, &reg_op);
+ // This is the last pass, and we are doing more than 1 pass
+ xcb_render_composite(c, XCB_RENDER_PICT_OP_OVER, src_pict,
+ alpha_pict, xd->back[2], 0, 0, 0, 0,
+ to_i16_checked(extent_resized->x1),
+ to_i16_checked(extent_resized->y1),
+ width_resized, height_resized);
+ }
+
+ // reset filter
+ xcb_render_set_picture_filter(
+ c, src_pict, to_u16_checked(strlen(filter0)), filter0, 0, NULL);
+
+ src_pict = tmp_picture[current];
+ dst_pict = tmp_picture[!current];
+ current = !current;
+ }
+
+ // There is only 1 pass
+ if (i == 1) {
+ x_set_picture_clip_region(c, xd->back[2], 0, 0, &reg_op);
+ xcb_render_composite(
+ c, XCB_RENDER_PICT_OP_OVER, src_pict, alpha_pict, xd->back[2], 0, 0,
+ 0, 0, to_i16_checked(extent_resized->x1),
+ to_i16_checked(extent_resized->y1), width_resized, height_resized);
+ }
+
+ xcb_render_free_picture(c, tmp_picture[0]);
+ xcb_render_free_picture(c, tmp_picture[1]);
+ pixman_region32_fini(&reg_op);
+ pixman_region32_fini(&reg_op_resized);
+ return true;
+}
+
+static void *
+bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) {
+ xcb_generic_error_t *e;
+ auto r = xcb_get_geometry_reply(base->c, xcb_get_geometry(base->c, pixmap), &e);
+ if (!r) {
+ log_error("Invalid pixmap: %#010x", pixmap);
+ x_print_error(e->full_sequence, e->major_code, e->minor_code, e->error_code);
+ return NULL;
+ }
+
+ auto img = ccalloc(1, struct backend_image);
+ auto inner = ccalloc(1, struct _xrender_image_data_inner);
+ inner->depth = (uint8_t)fmt.visual_depth;
+ inner->width = img->ewidth = r->width;
+ inner->height = img->eheight = r->height;
+ inner->pixmap = pixmap;
+ inner->has_alpha = fmt.alpha_size != 0;
+ inner->pict =
+ x_create_picture_with_visual_and_pixmap(base->c, fmt.visual, pixmap, 0, NULL);
+ inner->owned = owned;
+ inner->visual = fmt.visual;
+ inner->refcount = 1;
+
+ img->inner = (struct backend_image_inner_base *)inner;
+ img->opacity = 1;
+ free(r);
+
+ if (inner->pict == XCB_NONE) {
+ free(inner);
+ free(img);
+ return NULL;
+ }
+ return img;
+}
+static void release_image_inner(backend_t *base, struct _xrender_image_data_inner *inner) {
+ xcb_render_free_picture(base->c, inner->pict);
+ if (inner->owned) {
+ xcb_free_pixmap(base->c, inner->pixmap);
+ }
+ free(inner);
+}
+static void release_image(backend_t *base, void *image) {
+ struct backend_image *img = image;
+ img->inner->refcount--;
+ if (img->inner->refcount == 0) {
+ release_image_inner(base, (void *)img->inner);
+ }
+ free(img);
+}
+
+static void deinit(backend_t *backend_data) {
+ struct _xrender_data *xd = (void *)backend_data;
+ for (int i = 0; i < 256; i++) {
+ xcb_render_free_picture(xd->base.c, xd->alpha_pict[i]);
+ }
+ xcb_render_free_picture(xd->base.c, xd->target);
+ for (int i = 0; i < 2; i++) {
+ xcb_render_free_picture(xd->base.c, xd->back[i]);
+ xcb_free_pixmap(xd->base.c, xd->back_pixmap[i]);
+ }
+ if (xd->present_event) {
+ xcb_unregister_for_special_event(xd->base.c, xd->present_event);
+ }
+ xcb_render_free_picture(xd->base.c, xd->white_pixel);
+ xcb_render_free_picture(xd->base.c, xd->black_pixel);
+ free(xd);
+}
+
+static void present(backend_t *base, const region_t *region) {
+ struct _xrender_data *xd = (void *)base;
+ const rect_t *extent = pixman_region32_extents((region_t *)region);
+ int16_t orig_x = to_i16_checked(extent->x1), orig_y = to_i16_checked(extent->y1);
+ uint16_t region_width = to_u16_checked(extent->x2 - extent->x1),
+ region_height = to_u16_checked(extent->y2 - extent->y1);
+
+ // compose() sets clip region on the back buffer, so clear it first
+ x_clear_picture_clip_region(base->c, xd->back[xd->curr_back]);
+
+ // limit the region of update
+ x_set_picture_clip_region(base->c, xd->back[2], 0, 0, region);
+
+ if (xd->vsync) {
+ // Update the back buffer first, then present
+ xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, xd->back[2],
+ XCB_NONE, xd->back[xd->curr_back], orig_x, orig_y, 0,
+ 0, orig_x, orig_y, region_width, region_height);
+
+ // Make sure we got reply from PresentPixmap before waiting for events,
+ // to avoid deadlock
+ auto e = xcb_request_check(
+ base->c, xcb_present_pixmap_checked(
+ xd->base.c, xd->target_win,
+ xd->back_pixmap[xd->curr_back], 0, XCB_NONE, XCB_NONE, 0,
+ 0, XCB_NONE, XCB_NONE, XCB_NONE, 0, 0, 0, 0, 0, NULL));
+ if (e) {
+ log_error("Failed to present pixmap");
+ free(e);
+ return;
+ }
+ // TODO(yshui) don't block wait for present completion
+ xcb_present_generic_event_t *pev =
+ (void *)xcb_wait_for_special_event(base->c, xd->present_event);
+ if (!pev) {
+ // We don't know what happened, maybe X died
+ // But reset buffer age, so in case we do recover, we will
+ // render correctly.
+ xd->buffer_age[0] = xd->buffer_age[1] = -1;
+ return;
+ }
+ assert(pev->evtype == XCB_PRESENT_COMPLETE_NOTIFY);
+ xcb_present_complete_notify_event_t *pcev = (void *)pev;
+ // log_trace("Present complete: %d %ld", pcev->mode, pcev->msc);
+ xd->buffer_age[xd->curr_back] = 1;
+
+ // buffer_age < 0 means that back buffer is empty
+ if (xd->buffer_age[1 - xd->curr_back] > 0) {
+ xd->buffer_age[1 - xd->curr_back]++;
+ }
+ if (pcev->mode == XCB_PRESENT_COMPLETE_MODE_FLIP) {
+ // We cannot use the pixmap we used anymore
+ xd->curr_back = 1 - xd->curr_back;
+ }
+ free(pev);
+ } else {
+ // No vsync needed, draw into the target picture directly
+ xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, xd->back[2],
+ XCB_NONE, xd->target, orig_x, orig_y, 0, 0, orig_x,
+ orig_y, region_width, region_height);
+ }
+}
+
+static int buffer_age(backend_t *backend_data) {
+ struct _xrender_data *xd = (void *)backend_data;
+ if (!xd->vsync) {
+ // Only the target picture really holds the screen content, and its
+ // content is always up to date. So buffer age is always 1.
+ return 1;
+ }
+ return xd->buffer_age[xd->curr_back];
+}
+
+static struct _xrender_image_data_inner *
+new_inner(backend_t *base, int w, int h, xcb_visualid_t visual, uint8_t depth) {
+ auto new_inner = ccalloc(1, struct _xrender_image_data_inner);
+ new_inner->pixmap = x_create_pixmap(base->c, depth, base->root, w, h);
+ if (new_inner->pixmap == XCB_NONE) {
+ log_error("Failed to create pixmap for copy");
+ free(new_inner);
+ return NULL;
+ }
+ new_inner->pict = x_create_picture_with_visual_and_pixmap(
+ base->c, visual, new_inner->pixmap, 0, NULL);
+ if (new_inner->pict == XCB_NONE) {
+ log_error("Failed to create picture for copy");
+ xcb_free_pixmap(base->c, new_inner->pixmap);
+ free(new_inner);
+ return NULL;
+ }
+ new_inner->width = w;
+ new_inner->height = h;
+ new_inner->visual = visual;
+ new_inner->depth = depth;
+ new_inner->refcount = 1;
+ new_inner->owned = true;
+ return new_inner;
+}
+
+static bool decouple_image(backend_t *base, struct backend_image *img, const region_t *reg) {
+ if (img->inner->refcount == 1) {
+ return true;
+ }
+ auto inner = (struct _xrender_image_data_inner *)img->inner;
+ // Force new pixmap to a 32-bit ARGB visual to allow for transparent frames around
+ // non-transparent windows
+ auto visual = (inner->depth == 32)
+ ? inner->visual
+ : x_get_visual_for_standard(base->c, XCB_PICT_STANDARD_ARGB_32);
+ auto inner2 = new_inner(base, inner->width, inner->height, visual, 32);
+ if (!inner2) {
+ return false;
+ }
+
+ x_set_picture_clip_region(base->c, inner->pict, 0, 0, reg);
+ xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE,
+ inner2->pict, 0, 0, 0, 0, 0, 0, to_u16_checked(inner->width),
+ to_u16_checked(inner->height));
+
+ img->inner = (struct backend_image_inner_base *)inner2;
+ inner->refcount--;
+ return true;
+}
+
+static bool image_op(backend_t *base, enum image_operations op, void *image,
+ const region_t *reg_op, const region_t *reg_visible, void *arg) {
+ struct _xrender_data *xd = (void *)base;
+ struct backend_image *img = image;
+ region_t reg;
+ double *dargs = arg;
+
+ pixman_region32_init(&reg);
+ pixman_region32_intersect(&reg, (region_t *)reg_op, (region_t *)reg_visible);
+
+ switch (op) {
+ case IMAGE_OP_APPLY_ALPHA:
+ assert(reg_op);
+
+ if (!pixman_region32_not_empty(&reg)) {
+ break;
+ }
+
+ if (dargs[0] == 1) {
+ break;
+ }
+
+ if (!decouple_image(base, img, reg_visible)) {
+ pixman_region32_fini(&reg);
+ return false;
+ }
+
+ auto inner = (struct _xrender_image_data_inner *)img->inner;
+ auto alpha_pict = xd->alpha_pict[(int)((1 - dargs[0]) * MAX_ALPHA)];
+ x_set_picture_clip_region(base->c, inner->pict, 0, 0, &reg);
+ xcb_render_composite(base->c, XCB_RENDER_PICT_OP_OUT_REVERSE, alpha_pict,
+ XCB_NONE, inner->pict, 0, 0, 0, 0, 0, 0,
+ to_u16_checked(inner->width),
+ to_u16_checked(inner->height));
+ inner->has_alpha = true;
+ break;
+ }
+ pixman_region32_fini(&reg);
+ return true;
+}
+
+static void *
+create_blur_context(backend_t *base attr_unused, enum blur_method method, void *args) {
+ auto ret = ccalloc(1, struct _xrender_blur_context);
+ if (!method || method >= BLUR_METHOD_INVALID) {
+ ret->method = BLUR_METHOD_NONE;
+ return ret;
+ }
+ if (method == BLUR_METHOD_DUAL_KAWASE) {
+ log_warn("Blur method 'dual_kawase' is not compatible with the 'xrender' "
+ "backend.");
+ ret->method = BLUR_METHOD_NONE;
+ return ret;
+ }
+
+ ret->method = BLUR_METHOD_KERNEL;
+ struct conv **kernels;
+ int kernel_count;
+ if (method == BLUR_METHOD_KERNEL) {
+ kernels = ((struct kernel_blur_args *)args)->kernels;
+ kernel_count = ((struct kernel_blur_args *)args)->kernel_count;
+ } else {
+ kernels = generate_blur_kernel(method, args, &kernel_count);
+ }
+
+ ret->x_blur_kernel = ccalloc(kernel_count, struct x_convolution_kernel *);
+ for (int i = 0; i < kernel_count; i++) {
+ int center = kernels[i]->h * kernels[i]->w / 2;
+ x_create_convolution_kernel(kernels[i], kernels[i]->data[center],
+ &ret->x_blur_kernel[i]);
+ ret->resize_width += kernels[i]->w / 2;
+ ret->resize_height += kernels[i]->h / 2;
+ }
+ ret->x_blur_kernel_count = kernel_count;
+
+ if (method != BLUR_METHOD_KERNEL) {
+ // Kernels generated by generate_blur_kernel, so we need to free them.
+ for (int i = 0; i < kernel_count; i++) {
+ free(kernels[i]);
+ }
+ free(kernels);
+ }
+ return ret;
+}
+
+static void destroy_blur_context(backend_t *base attr_unused, void *ctx_) {
+ struct _xrender_blur_context *ctx = ctx_;
+ for (int i = 0; i < ctx->x_blur_kernel_count; i++) {
+ free(ctx->x_blur_kernel[i]);
+ }
+ free(ctx->x_blur_kernel);
+ free(ctx);
+}
+
+static void get_blur_size(void *blur_context, int *width, int *height) {
+ struct _xrender_blur_context *ctx = blur_context;
+ *width = ctx->resize_width;
+ *height = ctx->resize_height;
+}
+
+static bool
+read_pixel(backend_t *backend_data, void *image_data, int x, int y, struct color *output) {
+ auto xd = (struct _xrender_data *)backend_data;
+ auto img = (struct backend_image *)image_data;
+ auto inner = (struct _xrender_image_data_inner *)img->inner;
+
+ auto r = XCB_AWAIT(xcb_get_image, xd->base.c, XCB_IMAGE_FORMAT_XY_PIXMAP, inner->pixmap,
+ to_i16_checked(x), to_i16_checked(y), 1, 1, (uint32_t)-1L);
+
+ if (!r) {
+ return false;
+ }
+
+ // Color format seems to be BGRA8888, see glamor_format_for_pixmap from the
+ // Xserver codebase.
+ uint8_t *pixels = xcb_get_image_data(r);
+ output->blue = pixels[0] / 255.0;
+ output->green = pixels[1] / 255.0;
+ output->red = pixels[2] / 255.0;
+ output->alpha = pixels[3] / 255.0;
+
+ return true;
+}
+
+static backend_t *backend_xrender_init(session_t *ps) {
+ auto xd = ccalloc(1, struct _xrender_data);
+ init_backend_base(&xd->base, ps);
+
+ for (int i = 0; i <= MAX_ALPHA; ++i) {
+ double o = (double)i / (double)MAX_ALPHA;
+ xd->alpha_pict[i] = solid_picture(ps->c, ps->root, false, o, 0, 0, 0);
+ assert(xd->alpha_pict[i] != XCB_NONE);
+ }
+
+ xd->target_width = ps->root_width;
+ xd->target_height = ps->root_height;
+ xd->default_visual = ps->vis;
+ xd->black_pixel = solid_picture(ps->c, ps->root, true, 1, 0, 0, 0);
+ xd->white_pixel = solid_picture(ps->c, ps->root, true, 1, 1, 1, 1);
+
+ xd->target_win = session_get_target_window(ps);
+ xcb_render_create_picture_value_list_t pa = {
+ .subwindowmode = XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS,
+ };
+ xd->target = x_create_picture_with_visual_and_pixmap(
+ ps->c, ps->vis, xd->target_win, XCB_RENDER_CP_SUBWINDOW_MODE, &pa);
+
+ auto pictfmt = x_get_pictform_for_visual(ps->c, ps->vis);
+ if (!pictfmt) {
+ log_fatal("Default visual is invalid");
+ abort();
+ }
+
+ xd->vsync = ps->o.vsync;
+ if (ps->present_exists) {
+ auto eid = x_new_id(ps->c);
+ auto e =
+ xcb_request_check(ps->c, xcb_present_select_input_checked(
+ ps->c, eid, xd->target_win,
+ XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY));
+ if (e) {
+ log_error("Cannot select present input, vsync will be disabled");
+ xd->vsync = false;
+ free(e);
+ }
+
+ xd->present_event =
+ xcb_register_for_special_xge(ps->c, &xcb_present_id, eid, NULL);
+ if (!xd->present_event) {
+ log_error("Cannot register for special XGE, vsync will be "
+ "disabled");
+ xd->vsync = false;
+ }
+ } else {
+ xd->vsync = false;
+ }
+
+ // We might need to do double buffering for vsync, and buffer 0 and 1 are for
+ // double buffering.
+ int first_buffer_index = xd->vsync ? 0 : 2;
+ for (int i = first_buffer_index; i < 3; i++) {
+ xd->back_pixmap[i] = x_create_pixmap(ps->c, pictfmt->depth, ps->root,
+ to_u16_checked(ps->root_width),
+ to_u16_checked(ps->root_height));
+ const uint32_t pic_attrs_mask = XCB_RENDER_CP_REPEAT;
+ const xcb_render_create_picture_value_list_t pic_attrs = {
+ .repeat = XCB_RENDER_REPEAT_PAD};
+ xd->back[i] = x_create_picture_with_pictfmt_and_pixmap(
+ ps->c, pictfmt, xd->back_pixmap[i], pic_attrs_mask, &pic_attrs);
+ xd->buffer_age[i] = -1;
+ if (xd->back_pixmap[i] == XCB_NONE || xd->back[i] == XCB_NONE) {
+ log_error("Cannot create pixmap for rendering");
+ goto err;
+ }
+ }
+ xd->curr_back = 0;
+
+ return &xd->base;
+err:
+ deinit(&xd->base);
+ return NULL;
+}
+
+struct backend_operations xrender_ops = {
+ .init = backend_xrender_init,
+ .deinit = deinit,
+ .blur = blur,
+ .present = present,
+ .compose = compose,
+ .fill = fill,
+ .bind_pixmap = bind_pixmap,
+ .release_image = release_image,
+ .render_shadow = default_backend_render_shadow,
+ //.prepare_win = prepare_win,
+ //.release_win = release_win,
+ .is_image_transparent = default_is_image_transparent,
+ .buffer_age = buffer_age,
+ .max_buffer_age = 2,
+
+ .image_op = image_op,
+ .read_pixel = read_pixel,
+ .clone_image = default_clone_image,
+ .set_image_property = default_set_image_property,
+ .create_blur_context = create_blur_context,
+ .destroy_blur_context = destroy_blur_context,
+ .get_blur_size = get_blur_size,
+};
+
+// vim: set noet sw=8 ts=8:
diff --git a/src/c2.c b/src/c2.c
new file mode 100644
index 0000000..3500f7b
--- /dev/null
+++ b/src/c2.c
@@ -0,0 +1,1674 @@
+// SPDX-License-Identifier: MIT
+
+/*
+ * Compton - a compositor for X11
+ *
+ * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
+ *
+ * Copyright (c) 2011-2013, Christopher Jeffrey
+ * See LICENSE-mit for more information.
+ *
+ */
+
+#include <ctype.h>
+#include <fnmatch.h>
+#include <stdio.h>
+#include <string.h>
+
+// libpcre
+#ifdef CONFIG_REGEX_PCRE
+#include <pcre.h>
+
+// For compatibility with <libpcre-8.20
+#ifndef PCRE_STUDY_JIT_COMPILE
+#define PCRE_STUDY_JIT_COMPILE 0
+#define LPCRE_FREE_STUDY(extra) pcre_free(extra)
+#else
+#define LPCRE_FREE_STUDY(extra) pcre_free_study(extra)
+#endif
+
+#endif
+
+#include <X11/Xlib.h>
+#include <xcb/xcb.h>
+
+#include "atom.h"
+#include "common.h"
+#include "compiler.h"
+#include "config.h"
+#include "log.h"
+#include "string_utils.h"
+#include "utils.h"
+#include "win.h"
+#include "x.h"
+
+#include "c2.h"
+
+#pragma GCC diagnostic error "-Wunused-parameter"
+
+#define C2_MAX_LEVELS 10
+
+typedef struct _c2_b c2_b_t;
+typedef struct _c2_l c2_l_t;
+
+/// Pointer to a condition tree.
+typedef struct {
+ bool isbranch : 1;
+ union {
+ c2_b_t *b;
+ c2_l_t *l;
+ };
+} c2_ptr_t;
+
+/// Initializer for c2_ptr_t.
+#define C2_PTR_INIT \
+ { .isbranch = false, .l = NULL, }
+
+static const c2_ptr_t C2_PTR_NULL = C2_PTR_INIT;
+
+/// Operator of a branch element.
+typedef enum {
+ C2_B_OUNDEFINED,
+ C2_B_OAND,
+ C2_B_OOR,
+ C2_B_OXOR,
+} c2_b_op_t;
+
+/// Structure for branch element in a window condition
+struct _c2_b {
+ bool neg : 1;
+ c2_b_op_t op;
+ c2_ptr_t opr1;
+ c2_ptr_t opr2;
+};
+
+/// Initializer for c2_b_t.
+#define C2_B_INIT \
+ { .neg = false, .op = C2_B_OUNDEFINED, .opr1 = C2_PTR_INIT, .opr2 = C2_PTR_INIT, }
+
+/// Structure for leaf element in a window condition
+struct _c2_l {
+ bool neg : 1;
+ enum { C2_L_OEXISTS,
+ C2_L_OEQ,
+ C2_L_OGT,
+ C2_L_OGTEQ,
+ C2_L_OLT,
+ C2_L_OLTEQ,
+ } op : 3;
+ enum { C2_L_MEXACT,
+ C2_L_MSTART,
+ C2_L_MCONTAINS,
+ C2_L_MWILDCARD,
+ C2_L_MPCRE,
+ } match : 3;
+ bool match_ignorecase : 1;
+ char *tgt;
+ xcb_atom_t tgtatom;
+ bool tgt_onframe;
+ int index;
+ enum { C2_L_PUNDEFINED = -1,
+ C2_L_PID = 0,
+ C2_L_PX,
+ C2_L_PY,
+ C2_L_PX2,
+ C2_L_PY2,
+ C2_L_PWIDTH,
+ C2_L_PHEIGHT,
+ C2_L_PWIDTHB,
+ C2_L_PHEIGHTB,
+ C2_L_PBDW,
+ C2_L_PFULLSCREEN,
+ C2_L_POVREDIR,
+ C2_L_PARGB,
+ C2_L_PFOCUSED,
+ C2_L_PWMWIN,
+ C2_L_PBSHAPED,
+ C2_L_PROUNDED,
+ C2_L_PCLIENT,
+ C2_L_PWINDOWTYPE,
+ C2_L_PLEADER,
+ C2_L_PNAME,
+ C2_L_PCLASSG,
+ C2_L_PCLASSI,
+ C2_L_PROLE,
+ } predef;
+ enum c2_l_type {
+ C2_L_TUNDEFINED,
+ C2_L_TSTRING,
+ C2_L_TCARDINAL,
+ C2_L_TWINDOW,
+ C2_L_TATOM,
+ C2_L_TDRAWABLE,
+ } type;
+ int format;
+ enum { C2_L_PTUNDEFINED,
+ C2_L_PTSTRING,
+ C2_L_PTINT,
+ } ptntype;
+ char *ptnstr;
+ long ptnint;
+#ifdef CONFIG_REGEX_PCRE
+ pcre *regex_pcre;
+ pcre_extra *regex_pcre_extra;
+#endif
+};
+
+/// Initializer for c2_l_t.
+#define C2_L_INIT \
+ { \
+ .neg = false, .op = C2_L_OEXISTS, .match = C2_L_MEXACT, \
+ .match_ignorecase = false, .tgt = NULL, .tgtatom = 0, .tgt_onframe = false, \
+ .predef = C2_L_PUNDEFINED, .index = 0, .type = C2_L_TUNDEFINED, \
+ .format = 0, .ptntype = C2_L_PTUNDEFINED, .ptnstr = NULL, .ptnint = 0, \
+ }
+
+static const c2_l_t leaf_def = C2_L_INIT;
+
+/// Linked list type of conditions.
+struct _c2_lptr {
+ c2_ptr_t ptr;
+ void *data;
+ struct _c2_lptr *next;
+};
+
+/// Initializer for c2_lptr_t.
+#define C2_LPTR_INIT \
+ { .ptr = C2_PTR_INIT, .data = NULL, .next = NULL, }
+
+/// Structure representing a predefined target.
+typedef struct {
+ const char *name;
+ enum c2_l_type type;
+ int format;
+} c2_predef_t;
+
+// Predefined targets.
+static const c2_predef_t C2_PREDEFS[] = {
+ [C2_L_PID] = {"id", C2_L_TCARDINAL, 0},
+ [C2_L_PX] = {"x", C2_L_TCARDINAL, 0},
+ [C2_L_PY] = {"y", C2_L_TCARDINAL, 0},
+ [C2_L_PX2] = {"x2", C2_L_TCARDINAL, 0},
+ [C2_L_PY2] = {"y2", C2_L_TCARDINAL, 0},
+ [C2_L_PWIDTH] = {"width", C2_L_TCARDINAL, 0},
+ [C2_L_PHEIGHT] = {"height", C2_L_TCARDINAL, 0},
+ [C2_L_PWIDTHB] = {"widthb", C2_L_TCARDINAL, 0},
+ [C2_L_PHEIGHTB] = {"heightb", C2_L_TCARDINAL, 0},
+ [C2_L_PBDW] = {"border_width", C2_L_TCARDINAL, 0},
+ [C2_L_PFULLSCREEN] = {"fullscreen", C2_L_TCARDINAL, 0},
+ [C2_L_POVREDIR] = {"override_redirect", C2_L_TCARDINAL, 0},
+ [C2_L_PARGB] = {"argb", C2_L_TCARDINAL, 0},
+ [C2_L_PFOCUSED] = {"focused", C2_L_TCARDINAL, 0},
+ [C2_L_PWMWIN] = {"wmwin", C2_L_TCARDINAL, 0},
+ [C2_L_PBSHAPED] = {"bounding_shaped", C2_L_TCARDINAL, 0},
+ [C2_L_PROUNDED] = {"rounded_corners", C2_L_TCARDINAL, 0},
+ [C2_L_PCLIENT] = {"client", C2_L_TWINDOW, 0},
+ [C2_L_PWINDOWTYPE] = {"window_type", C2_L_TSTRING, 0},
+ [C2_L_PLEADER] = {"leader", C2_L_TWINDOW, 0},
+ [C2_L_PNAME] = {"name", C2_L_TSTRING, 0},
+ [C2_L_PCLASSG] = {"class_g", C2_L_TSTRING, 0},
+ [C2_L_PCLASSI] = {"class_i", C2_L_TSTRING, 0},
+ [C2_L_PROLE] = {"role", C2_L_TSTRING, 0},
+};
+
+/**
+ * Get the numeric property value from a win_prop_t.
+ */
+static inline long winprop_get_int(winprop_t prop, size_t index) {
+ long tgt = 0;
+
+ if (!prop.nitems || index >= prop.nitems) {
+ return 0;
+ }
+
+ switch (prop.format) {
+ case 8: tgt = *(prop.p8 + index); break;
+ case 16: tgt = *(prop.p16 + index); break;
+ case 32: tgt = *(prop.p32 + index); break;
+ default: assert(0); break;
+ }
+
+ return tgt;
+}
+
+/**
+ * Compare next word in a string with another string.
+ */
+static inline int strcmp_wd(const char *needle, const char *src) {
+ int ret = mstrncmp(needle, src);
+ if (ret)
+ return ret;
+
+ char c = src[strlen(needle)];
+ if (isalnum((unsigned char)c) || '_' == c)
+ return 1;
+ else
+ return 0;
+}
+
+/**
+ * Return whether a c2_ptr_t is empty.
+ */
+static inline attr_unused bool c2_ptr_isempty(const c2_ptr_t p) {
+ return !(p.isbranch ? (bool)p.b : (bool)p.l);
+}
+
+/**
+ * Reset a c2_ptr_t.
+ */
+static inline void c2_ptr_reset(c2_ptr_t *pp) {
+ if (pp)
+ memcpy(pp, &C2_PTR_NULL, sizeof(c2_ptr_t));
+}
+
+/**
+ * Combine two condition trees.
+ */
+static inline c2_ptr_t c2h_comb_tree(c2_b_op_t op, c2_ptr_t p1, c2_ptr_t p2) {
+ c2_ptr_t p = {.isbranch = true, .b = NULL};
+ p.b = cmalloc(c2_b_t);
+
+ p.b->neg = false;
+ p.b->op = op;
+ p.b->opr1 = p1;
+ p.b->opr2 = p2;
+
+ return p;
+}
+
+/**
+ * Get the precedence value of a condition branch operator.
+ */
+static inline int c2h_b_opp(c2_b_op_t op) {
+ switch (op) {
+ case C2_B_OAND: return 2;
+ case C2_B_OOR: return 1;
+ case C2_B_OXOR: return 1;
+ default: break;
+ }
+
+ assert(0);
+ return 0;
+}
+
+/**
+ * Compare precedence of two condition branch operators.
+ *
+ * Associativity is left-to-right, forever.
+ *
+ * @return positive number if op1 > op2, 0 if op1 == op2 in precedence,
+ * negative number otherwise
+ */
+static inline int c2h_b_opcmp(c2_b_op_t op1, c2_b_op_t op2) {
+ return c2h_b_opp(op1) - c2h_b_opp(op2);
+}
+
+static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int level);
+
+static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult);
+
+static int c2_parse_op(const char *pattern, int offset, c2_ptr_t *presult);
+
+static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult);
+
+static int c2_parse_legacy(const char *pattern, int offset, c2_ptr_t *presult);
+
+static void c2_free(c2_ptr_t p);
+
+/**
+ * Wrapper of c2_free().
+ */
+static inline void c2_freep(c2_ptr_t *pp) {
+ if (pp) {
+ c2_free(*pp);
+ c2_ptr_reset(pp);
+ }
+}
+
+static const char *c2h_dump_str_tgt(const c2_l_t *pleaf);
+
+static const char *c2h_dump_str_type(const c2_l_t *pleaf);
+
+static void attr_unused c2_dump(c2_ptr_t p);
+
+static xcb_atom_t c2_get_atom_type(const c2_l_t *pleaf);
+
+static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_ptr_t cond);
+
+/**
+ * Parse a condition string.
+ */
+c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data) {
+ if (!pattern)
+ return NULL;
+
+ // Parse the pattern
+ c2_ptr_t result = C2_PTR_INIT;
+ int offset = -1;
+
+ if (strlen(pattern) >= 2 && ':' == pattern[1])
+ offset = c2_parse_legacy(pattern, 0, &result);
+ else
+ offset = c2_parse_grp(pattern, 0, &result, 0);
+
+ if (offset < 0) {
+ c2_freep(&result);
+ return NULL;
+ }
+
+ // Insert to pcondlst
+ {
+ static const c2_lptr_t lptr_def = C2_LPTR_INIT;
+ auto plptr = cmalloc(c2_lptr_t);
+ memcpy(plptr, &lptr_def, sizeof(c2_lptr_t));
+ plptr->ptr = result;
+ plptr->data = data;
+ if (pcondlst) {
+ plptr->next = *pcondlst;
+ *pcondlst = plptr;
+ }
+
+#ifdef DEBUG_C2
+ log_trace("(\"%s\"): ", pattern);
+ c2_dump(plptr->ptr);
+ putchar('\n');
+#endif
+
+ return plptr;
+ }
+}
+
+#define c2_error(format, ...) \
+ do { \
+ log_error("Pattern \"%s\" pos %d: " format, pattern, offset, ##__VA_ARGS__); \
+ goto fail; \
+ } while (0)
+
+// TODO(yshui) Not a very good macro, should probably be a function
+#define C2H_SKIP_SPACES() \
+ { \
+ while (isspace((unsigned char)pattern[offset])) \
+ ++offset; \
+ }
+
+/**
+ * Parse a group in condition string.
+ *
+ * @return offset of next character in string
+ */
+static int c2_parse_grp(const char *pattern, int offset, c2_ptr_t *presult, int level) {
+ // Check for recursion levels
+ if (level > C2_MAX_LEVELS)
+ c2_error("Exceeded maximum recursion levels.");
+
+ if (!pattern)
+ return -1;
+
+ // Expected end character
+ const char endchar = (offset ? ')' : '\0');
+
+ // We use a system that a maximum of 2 elements are kept. When we find
+ // the third element, we combine the elements according to operator
+ // precedence. This design limits operators to have at most two-levels
+ // of precedence and fixed left-to-right associativity.
+
+ // For storing branch operators. ops[0] is actually unused
+ c2_b_op_t ops[3] = {};
+ // For storing elements
+ c2_ptr_t eles[2] = {C2_PTR_INIT, C2_PTR_INIT};
+ // Index of next free element slot in eles
+ int elei = 0;
+ // Pointer to the position of next element
+ c2_ptr_t *pele = eles;
+ // Negation flag of next operator
+ bool neg = false;
+ // Whether we are expecting an element immediately, is true at first, or
+ // after encountering a logical operator
+ bool next_expected = true;
+
+ // Parse the pattern character-by-character
+ for (; pattern[offset]; ++offset) {
+ assert(elei <= 2);
+
+ // Jump over spaces
+ if (isspace((unsigned char)pattern[offset]))
+ continue;
+
+ // Handle end of group
+ if (')' == pattern[offset])
+ break;
+
+ // Handle "!"
+ if ('!' == pattern[offset]) {
+ if (!next_expected)
+ c2_error("Unexpected \"!\".");
+
+ neg = !neg;
+ continue;
+ }
+
+ // Handle AND and OR
+ if ('&' == pattern[offset] || '|' == pattern[offset]) {
+ if (next_expected)
+ c2_error("Unexpected logical operator.");
+
+ next_expected = true;
+ if (!mstrncmp("&&", pattern + offset)) {
+ ops[elei] = C2_B_OAND;
+ ++offset;
+ } else if (!mstrncmp("||", pattern + offset)) {
+ ops[elei] = C2_B_OOR;
+ ++offset;
+ } else
+ c2_error("Illegal logical operator.");
+
+ continue;
+ }
+
+ // Parsing an element
+ if (!next_expected)
+ c2_error("Unexpected expression.");
+
+ assert(!elei || ops[elei]);
+
+ // If we are out of space
+ if (2 == elei) {
+ --elei;
+ // If the first operator has higher or equal precedence, combine
+ // the first two elements
+ if (c2h_b_opcmp(ops[1], ops[2]) >= 0) {
+ eles[0] = c2h_comb_tree(ops[1], eles[0], eles[1]);
+ c2_ptr_reset(&eles[1]);
+ pele = &eles[elei];
+ ops[1] = ops[2];
+ }
+ // Otherwise, combine the second and the incoming one
+ else {
+ eles[1] = c2h_comb_tree(ops[2], eles[1], C2_PTR_NULL);
+ assert(eles[1].isbranch);
+ pele = &eles[1].b->opr2;
+ }
+ // The last operator always needs to be reset
+ ops[2] = C2_B_OUNDEFINED;
+ }
+
+ // It's a subgroup if it starts with '('
+ if ('(' == pattern[offset]) {
+ if ((offset = c2_parse_grp(pattern, offset + 1, pele, level + 1)) < 0)
+ goto fail;
+ }
+ // Otherwise it's a leaf
+ else {
+ if ((offset = c2_parse_target(pattern, offset, pele)) < 0)
+ goto fail;
+
+ assert(!pele->isbranch && !c2_ptr_isempty(*pele));
+
+ if ((offset = c2_parse_op(pattern, offset, pele)) < 0)
+ goto fail;
+
+ if ((offset = c2_parse_pattern(pattern, offset, pele)) < 0)
+ goto fail;
+ }
+ // Decrement offset -- we will increment it in loop update
+ --offset;
+
+ // Apply negation
+ if (neg) {
+ neg = false;
+ if (pele->isbranch)
+ pele->b->neg = !pele->b->neg;
+ else
+ pele->l->neg = !pele->l->neg;
+ }
+
+ next_expected = false;
+ ++elei;
+ pele = &eles[elei];
+ }
+
+ // Wrong end character?
+ if (pattern[offset] && !endchar)
+ c2_error("Expected end of string but found '%c'.", pattern[offset]);
+ if (!pattern[offset] && endchar)
+ c2_error("Expected '%c' but found end of string.", endchar);
+
+ // Handle end of group
+ if (!elei) {
+ c2_error("Empty group.");
+ } else if (next_expected) {
+ c2_error("Missing rule before end of group.");
+ } else if (elei > 1) {
+ assert(2 == elei);
+ assert(ops[1]);
+ eles[0] = c2h_comb_tree(ops[1], eles[0], eles[1]);
+ c2_ptr_reset(&eles[1]);
+ }
+
+ *presult = eles[0];
+
+ if (')' == pattern[offset])
+ ++offset;
+
+ return offset;
+
+fail:
+ c2_freep(&eles[0]);
+ c2_freep(&eles[1]);
+
+ return -1;
+}
+
+/**
+ * Parse the target part of a rule.
+ */
+static int c2_parse_target(const char *pattern, int offset, c2_ptr_t *presult) {
+ // Initialize leaf
+ presult->isbranch = false;
+ presult->l = cmalloc(c2_l_t);
+
+ c2_l_t *const pleaf = presult->l;
+ memcpy(pleaf, &leaf_def, sizeof(c2_l_t));
+
+ // Parse negation marks
+ while ('!' == pattern[offset]) {
+ pleaf->neg = !pleaf->neg;
+ ++offset;
+ C2H_SKIP_SPACES();
+ }
+
+ // Copy target name out
+ int tgtlen = 0;
+ for (; pattern[offset] &&
+ (isalnum((unsigned char)pattern[offset]) || '_' == pattern[offset]);
+ ++offset) {
+ ++tgtlen;
+ }
+ if (!tgtlen) {
+ c2_error("Empty target.");
+ }
+ pleaf->tgt = strndup(&pattern[offset - tgtlen], (size_t)tgtlen);
+
+ // Check for predefined targets
+ static const int npredefs = (int)(sizeof(C2_PREDEFS) / sizeof(C2_PREDEFS[0]));
+ for (int i = 0; i < npredefs; ++i) {
+ if (!strcmp(C2_PREDEFS[i].name, pleaf->tgt)) {
+ pleaf->predef = i;
+ pleaf->type = C2_PREDEFS[i].type;
+ pleaf->format = C2_PREDEFS[i].format;
+ break;
+ }
+ }
+
+ C2H_SKIP_SPACES();
+
+ // Parse target-on-frame flag
+ if ('@' == pattern[offset]) {
+ pleaf->tgt_onframe = true;
+ ++offset;
+ C2H_SKIP_SPACES();
+ }
+
+ // Parse index
+ if ('[' == pattern[offset]) {
+ if (pleaf->predef != C2_L_PUNDEFINED) {
+ c2_error("Predefined targets can't have index.");
+ }
+
+ offset++;
+
+ C2H_SKIP_SPACES();
+
+ long index = -1;
+ const char *endptr = NULL;
+
+ if ('*' == pattern[offset]) {
+ index = -1;
+ endptr = pattern + offset + 1;
+ } else {
+ index = strtol(pattern + offset, (char **)&endptr, 0);
+ if (index < 0) {
+ c2_error("Index number invalid.");
+ }
+ }
+
+ if (!endptr || pattern + offset == endptr) {
+ c2_error("No index number found after bracket.");
+ }
+
+ pleaf->index = to_int_checked(index);
+ offset = to_int_checked(endptr - pattern);
+
+ C2H_SKIP_SPACES();
+
+ if (pattern[offset] != ']') {
+ c2_error("Index end marker not found.");
+ }
+
+ ++offset;
+
+ C2H_SKIP_SPACES();
+ }
+
+ // Parse target type and format
+ if (':' == pattern[offset]) {
+ ++offset;
+ C2H_SKIP_SPACES();
+
+ // Look for format
+ bool hasformat = false;
+ long format = 0;
+ {
+ char *endptr = NULL;
+ format = strtol(pattern + offset, &endptr, 0);
+ assert(endptr);
+ if ((hasformat = (endptr && endptr != pattern + offset))) {
+ offset = to_int_checked(endptr - pattern);
+ }
+ C2H_SKIP_SPACES();
+ }
+
+ // Look for type
+ enum c2_l_type type = C2_L_TUNDEFINED;
+ switch (pattern[offset]) {
+ case 'w': type = C2_L_TWINDOW; break;
+ case 'd': type = C2_L_TDRAWABLE; break;
+ case 'c': type = C2_L_TCARDINAL; break;
+ case 's': type = C2_L_TSTRING; break;
+ case 'a': type = C2_L_TATOM; break;
+ default: c2_error("Invalid type character.");
+ }
+
+ if (type) {
+ if (pleaf->predef != C2_L_PUNDEFINED) {
+ log_warn("Type specified for a default target "
+ "will be ignored.");
+ } else {
+ if (pleaf->type && type != pleaf->type) {
+ log_warn("Default type overridden on "
+ "target.");
+ }
+ pleaf->type = type;
+ }
+ }
+
+ offset++;
+ C2H_SKIP_SPACES();
+
+ // Default format
+ if (!pleaf->format) {
+ switch (pleaf->type) {
+ case C2_L_TWINDOW:
+ case C2_L_TDRAWABLE:
+ case C2_L_TATOM: pleaf->format = 32; break;
+ case C2_L_TSTRING: pleaf->format = 8; break;
+ default: break;
+ }
+ }
+
+ // Write format
+ if (hasformat) {
+ if (pleaf->predef != C2_L_PUNDEFINED) {
+ log_warn("Format \"%ld\" specified on a default target "
+ "will be ignored.",
+ format);
+ } else if (pleaf->type == C2_L_TSTRING) {
+ log_warn("Format \"%ld\" specified on a string target "
+ "will be ignored.",
+ format);
+ } else {
+ if (pleaf->format && pleaf->format != format) {
+ log_warn("Default format %d overridden on "
+ "target.",
+ pleaf->format);
+ }
+ pleaf->format = to_int_checked(format);
+ }
+ }
+ }
+
+ if (!pleaf->type) {
+ c2_error("Target type cannot be determined.");
+ }
+
+ // if (!pleaf->predef && !pleaf->format && C2_L_TSTRING != pleaf->type)
+ // c2_error("Target format cannot be determined.");
+
+ if (pleaf->format && 8 != pleaf->format && 16 != pleaf->format && 32 != pleaf->format) {
+ c2_error("Invalid format.");
+ }
+
+ return offset;
+
+fail:
+ return -1;
+}
+
+/**
+ * Parse the operator part of a leaf.
+ */
+static int c2_parse_op(const char *pattern, int offset, c2_ptr_t *presult) {
+ c2_l_t *const pleaf = presult->l;
+
+ // Parse negation marks
+ C2H_SKIP_SPACES();
+ while ('!' == pattern[offset]) {
+ pleaf->neg = !pleaf->neg;
+ ++offset;
+ C2H_SKIP_SPACES();
+ }
+
+ // Parse qualifiers
+ if ('*' == pattern[offset] || '^' == pattern[offset] || '%' == pattern[offset] ||
+ '~' == pattern[offset]) {
+ switch (pattern[offset]) {
+ case '*': pleaf->match = C2_L_MCONTAINS; break;
+ case '^': pleaf->match = C2_L_MSTART; break;
+ case '%': pleaf->match = C2_L_MWILDCARD; break;
+ case '~': pleaf->match = C2_L_MPCRE; break;
+ default: assert(0);
+ }
+ ++offset;
+ C2H_SKIP_SPACES();
+ }
+
+ // Parse flags
+ while ('?' == pattern[offset]) {
+ pleaf->match_ignorecase = true;
+ ++offset;
+ C2H_SKIP_SPACES();
+ }
+
+ // Parse operator
+ while ('=' == pattern[offset] || '>' == pattern[offset] || '<' == pattern[offset]) {
+ if ('=' == pattern[offset] && C2_L_OGT == pleaf->op)
+ pleaf->op = C2_L_OGTEQ;
+ else if ('=' == pattern[offset] && C2_L_OLT == pleaf->op)
+ pleaf->op = C2_L_OLTEQ;
+ else if (pleaf->op) {
+ c2_error("Duplicate operator.");
+ } else {
+ switch (pattern[offset]) {
+ case '=': pleaf->op = C2_L_OEQ; break;
+ case '>': pleaf->op = C2_L_OGT; break;
+ case '<': pleaf->op = C2_L_OLT; break;
+ default: assert(0);
+ }
+ }
+ ++offset;
+ C2H_SKIP_SPACES();
+ }
+
+ // Check for problems
+ if (C2_L_OEQ != pleaf->op && (pleaf->match || pleaf->match_ignorecase))
+ c2_error("Exists/greater-than/less-than operators cannot have a "
+ "qualifier.");
+
+ return offset;
+
+fail:
+ return -1;
+}
+
+/**
+ * Parse the pattern part of a leaf.
+ */
+static int c2_parse_pattern(const char *pattern, int offset, c2_ptr_t *presult) {
+ c2_l_t *const pleaf = presult->l;
+
+ // Exists operator cannot have pattern
+ if (!pleaf->op) {
+ return offset;
+ }
+
+ C2H_SKIP_SPACES();
+
+ char *endptr = NULL;
+ if (!strcmp_wd("true", &pattern[offset])) {
+ pleaf->ptntype = C2_L_PTINT;
+ pleaf->ptnint = true;
+ offset += 4; // length of "true";
+ } else if (!strcmp_wd("false", &pattern[offset])) {
+ pleaf->ptntype = C2_L_PTINT;
+ pleaf->ptnint = false;
+ offset += 5; // length of "false";
+ } else if (pleaf->ptnint = strtol(pattern + offset, &endptr, 0),
+ pattern + offset != endptr) {
+ pleaf->ptntype = C2_L_PTINT;
+ offset = to_int_checked(endptr - pattern);
+ // Make sure we are stopping at the end of a word
+ if (isalnum((unsigned char)pattern[offset])) {
+ c2_error("Trailing characters after a numeric pattern.");
+ }
+ } else {
+ // Parse string patterns
+ bool raw = false;
+ char delim = '\0';
+
+ // String flags
+ if (tolower((unsigned char)pattern[offset]) == 'r') {
+ raw = true;
+ ++offset;
+ C2H_SKIP_SPACES();
+ }
+
+ // Check for delimiters
+ if (pattern[offset] == '\"' || pattern[offset] == '\'') {
+ pleaf->ptntype = C2_L_PTSTRING;
+ delim = pattern[offset];
+ ++offset;
+ }
+
+ if (pleaf->ptntype != C2_L_PTSTRING) {
+ c2_error("Invalid pattern type.");
+ }
+
+ // Parse the string now
+ // We can't determine the length of the pattern, so we use the length
+ // to the end of the pattern string -- currently escape sequences
+ // cannot be converted to a string longer than itself.
+ auto tptnstr = ccalloc((strlen(pattern + offset) + 1), char);
+ char *ptptnstr = tptnstr;
+ pleaf->ptnstr = tptnstr;
+ for (; pattern[offset] && delim != pattern[offset]; ++offset) {
+ // Handle escape sequences if it's not a raw string
+ if ('\\' == pattern[offset] && !raw) {
+ switch (pattern[++offset]) {
+ case '\\': *(ptptnstr++) = '\\'; break;
+ case '\'': *(ptptnstr++) = '\''; break;
+ case '\"': *(ptptnstr++) = '\"'; break;
+ case 'a': *(ptptnstr++) = '\a'; break;
+ case 'b': *(ptptnstr++) = '\b'; break;
+ case 'f': *(ptptnstr++) = '\f'; break;
+ case 'n': *(ptptnstr++) = '\n'; break;
+ case 'r': *(ptptnstr++) = '\r'; break;
+ case 't': *(ptptnstr++) = '\t'; break;
+ case 'v': *(ptptnstr++) = '\v'; break;
+ case 'o':
+ case 'x': {
+ char *tstr = strndup(pattern + offset + 1, 2);
+ char *pstr = NULL;
+ long val = strtol(
+ tstr, &pstr, ('o' == pattern[offset] ? 8 : 16));
+ free(tstr);
+ if (pstr != &tstr[2] || val <= 0)
+ c2_error("Invalid octal/hex escape "
+ "sequence.");
+ *(ptptnstr++) = to_char_checked(val);
+ offset += 2;
+ break;
+ }
+ default: c2_error("Invalid escape sequence.");
+ }
+ } else {
+ *(ptptnstr++) = pattern[offset];
+ }
+ }
+ if (!pattern[offset])
+ c2_error("Premature end of pattern string.");
+ ++offset;
+ *ptptnstr = '\0';
+ pleaf->ptnstr = strdup(tptnstr);
+ free(tptnstr);
+ }
+
+ C2H_SKIP_SPACES();
+
+ if (!pleaf->ptntype)
+ c2_error("Invalid pattern type.");
+
+ // Check if the type is correct
+ if (!(((C2_L_TSTRING == pleaf->type || C2_L_TATOM == pleaf->type) &&
+ C2_L_PTSTRING == pleaf->ptntype) ||
+ ((C2_L_TCARDINAL == pleaf->type || C2_L_TWINDOW == pleaf->type ||
+ C2_L_TDRAWABLE == pleaf->type) &&
+ C2_L_PTINT == pleaf->ptntype)))
+ c2_error("Pattern type incompatible with target type.");
+
+ if (C2_L_PTINT == pleaf->ptntype && pleaf->match)
+ c2_error("Integer/boolean pattern cannot have operator qualifiers.");
+
+ if (C2_L_PTINT == pleaf->ptntype && pleaf->match_ignorecase)
+ c2_error("Integer/boolean pattern cannot have flags.");
+
+ if (C2_L_PTSTRING == pleaf->ptntype &&
+ (C2_L_OGT == pleaf->op || C2_L_OGTEQ == pleaf->op || C2_L_OLT == pleaf->op ||
+ C2_L_OLTEQ == pleaf->op))
+ c2_error("String pattern cannot have an arithmetic operator.");
+
+ return offset;
+
+fail:
+ return -1;
+}
+
+/**
+ * Parse a condition with legacy syntax.
+ */
+static int c2_parse_legacy(const char *pattern, int offset, c2_ptr_t *presult) {
+ if (strlen(pattern + offset) < 4 || pattern[offset + 1] != ':' ||
+ !strchr(pattern + offset + 2, ':')) {
+ c2_error("Legacy parser: Invalid format.");
+ }
+
+ // Allocate memory for new leaf
+ auto pleaf = cmalloc(c2_l_t);
+ presult->isbranch = false;
+ presult->l = pleaf;
+ memcpy(pleaf, &leaf_def, sizeof(c2_l_t));
+ pleaf->type = C2_L_TSTRING;
+ pleaf->op = C2_L_OEQ;
+ pleaf->ptntype = C2_L_PTSTRING;
+
+ // Determine the pattern target
+#define TGTFILL(pdefid) \
+ (pleaf->predef = pdefid, pleaf->type = C2_PREDEFS[pdefid].type, \
+ pleaf->format = C2_PREDEFS[pdefid].format)
+ switch (pattern[offset]) {
+ case 'n': TGTFILL(C2_L_PNAME); break;
+ case 'i': TGTFILL(C2_L_PCLASSI); break;
+ case 'g': TGTFILL(C2_L_PCLASSG); break;
+ case 'r': TGTFILL(C2_L_PROLE); break;
+ default: c2_error("Target \"%c\" invalid.\n", pattern[offset]);
+ }
+#undef TGTFILL
+
+ offset += 2;
+
+ // Determine the match type
+ switch (pattern[offset]) {
+ case 'e': pleaf->match = C2_L_MEXACT; break;
+ case 'a': pleaf->match = C2_L_MCONTAINS; break;
+ case 's': pleaf->match = C2_L_MSTART; break;
+ case 'w': pleaf->match = C2_L_MWILDCARD; break;
+ case 'p': pleaf->match = C2_L_MPCRE; break;
+ default: c2_error("Type \"%c\" invalid.\n", pattern[offset]);
+ }
+ ++offset;
+
+ // Determine the pattern flags
+ while (':' != pattern[offset]) {
+ switch (pattern[offset]) {
+ case 'i': pleaf->match_ignorecase = true; break;
+ default: c2_error("Flag \"%c\" invalid.", pattern[offset]);
+ }
+ ++offset;
+ }
+ ++offset;
+
+ // Copy the pattern
+ pleaf->ptnstr = strdup(pattern + offset);
+
+ return offset;
+
+fail:
+ return -1;
+}
+
+#undef c2_error
+
+/**
+ * Do postprocessing on a condition leaf.
+ */
+static bool c2_l_postprocess(session_t *ps, c2_l_t *pleaf) {
+ // Give a pattern type to a leaf with exists operator, if needed
+ if (C2_L_OEXISTS == pleaf->op && !pleaf->ptntype) {
+ pleaf->ptntype = (C2_L_TSTRING == pleaf->type ? C2_L_PTSTRING : C2_L_PTINT);
+ }
+
+ // Get target atom if it's not a predefined one
+ if (pleaf->predef == C2_L_PUNDEFINED) {
+ pleaf->tgtatom = get_atom(ps->atoms, pleaf->tgt);
+ if (!pleaf->tgtatom) {
+ log_error("Failed to get atom for target \"%s\".", pleaf->tgt);
+ return false;
+ }
+ }
+
+ // Insert target Atom into atom track list
+ if (pleaf->tgtatom) {
+ bool found = false;
+ for (latom_t *platom = ps->track_atom_lst; platom; platom = platom->next) {
+ if (pleaf->tgtatom == platom->atom) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ auto pnew = cmalloc(latom_t);
+ pnew->next = ps->track_atom_lst;
+ pnew->atom = pleaf->tgtatom;
+ ps->track_atom_lst = pnew;
+ }
+ }
+
+ // Warn about lower case characters in target name
+ if (pleaf->predef == C2_L_PUNDEFINED) {
+ for (const char *pc = pleaf->tgt; *pc; ++pc) {
+ if (islower((unsigned char)*pc)) {
+ log_warn("Lowercase character in target name \"%s\".",
+ pleaf->tgt);
+ break;
+ }
+ }
+ }
+
+ // PCRE patterns
+ if (C2_L_PTSTRING == pleaf->ptntype && C2_L_MPCRE == pleaf->match) {
+#ifdef CONFIG_REGEX_PCRE
+ const char *error = NULL;
+ int erroffset = 0;
+ int options = 0;
+
+ // Ignore case flag
+ if (pleaf->match_ignorecase)
+ options |= PCRE_CASELESS;
+
+ // Compile PCRE expression
+ pleaf->regex_pcre =
+ pcre_compile(pleaf->ptnstr, options, &error, &erroffset, NULL);
+ if (!pleaf->regex_pcre) {
+ log_error("Pattern \"%s\": PCRE regular expression parsing "
+ "failed on "
+ "offset %d: %s",
+ pleaf->ptnstr, erroffset, error);
+ return false;
+ }
+#ifdef CONFIG_REGEX_PCRE_JIT
+ pleaf->regex_pcre_extra =
+ pcre_study(pleaf->regex_pcre, PCRE_STUDY_JIT_COMPILE, &error);
+ if (!pleaf->regex_pcre_extra) {
+ printf("Pattern \"%s\": PCRE regular expression study failed: %s",
+ pleaf->ptnstr, error);
+ }
+#endif
+
+ // Free the target string
+ // free(pleaf->tgt);
+ // pleaf->tgt = NULL;
+#else
+ log_error("PCRE regular expression support not compiled in.");
+ return false;
+#endif
+ }
+
+ return true;
+}
+
+static bool c2_tree_postprocess(session_t *ps, c2_ptr_t node) {
+ if (!node.isbranch) {
+ return c2_l_postprocess(ps, node.l);
+ }
+ if (!c2_tree_postprocess(ps, node.b->opr1))
+ return false;
+ return c2_tree_postprocess(ps, node.b->opr2);
+}
+
+bool c2_list_postprocess(session_t *ps, c2_lptr_t *list) {
+ c2_lptr_t *head = list;
+ while (head) {
+ if (!c2_tree_postprocess(ps, head->ptr))
+ return false;
+ head = head->next;
+ }
+ return true;
+}
+/**
+ * Free a condition tree.
+ */
+static void c2_free(c2_ptr_t p) {
+ // For a branch element
+ if (p.isbranch) {
+ c2_b_t *const pbranch = p.b;
+
+ if (!pbranch)
+ return;
+
+ c2_free(pbranch->opr1);
+ c2_free(pbranch->opr2);
+ free(pbranch);
+ }
+ // For a leaf element
+ else {
+ c2_l_t *const pleaf = p.l;
+
+ if (!pleaf)
+ return;
+
+ free(pleaf->tgt);
+ free(pleaf->ptnstr);
+#ifdef CONFIG_REGEX_PCRE
+ pcre_free(pleaf->regex_pcre);
+ LPCRE_FREE_STUDY(pleaf->regex_pcre_extra);
+#endif
+ free(pleaf);
+ }
+}
+
+/**
+ * Free a condition tree in c2_lptr_t.
+ */
+c2_lptr_t *c2_free_lptr(c2_lptr_t *lp) {
+ if (!lp)
+ return NULL;
+
+ c2_lptr_t *pnext = lp->next;
+ c2_free(lp->ptr);
+ free(lp);
+
+ return pnext;
+}
+
+/**
+ * Get a string representation of a rule target.
+ */
+static const char *c2h_dump_str_tgt(const c2_l_t *pleaf) {
+ if (pleaf->predef != C2_L_PUNDEFINED) {
+ return C2_PREDEFS[pleaf->predef].name;
+ } else {
+ return pleaf->tgt;
+ }
+}
+
+/**
+ * Get a string representation of a target.
+ */
+static const char *c2h_dump_str_type(const c2_l_t *pleaf) {
+ switch (pleaf->type) {
+ case C2_L_TWINDOW: return "w";
+ case C2_L_TDRAWABLE: return "d";
+ case C2_L_TCARDINAL: return "c";
+ case C2_L_TSTRING: return "s";
+ case C2_L_TATOM: return "a";
+ case C2_L_TUNDEFINED: break;
+ }
+
+ return NULL;
+}
+
+/**
+ * Dump a condition tree.
+ */
+static void c2_dump(c2_ptr_t p) {
+ // For a branch
+ if (p.isbranch) {
+ const c2_b_t *const pbranch = p.b;
+
+ if (!pbranch) {
+ return;
+ }
+
+ if (pbranch->neg) {
+ putchar('!');
+ }
+
+ printf("(");
+ c2_dump(pbranch->opr1);
+
+ switch (pbranch->op) {
+ case C2_B_OAND: printf(" && "); break;
+ case C2_B_OOR: printf(" || "); break;
+ case C2_B_OXOR: printf(" XOR "); break;
+ default: assert(0); break;
+ }
+
+ c2_dump(pbranch->opr2);
+ printf(") ");
+ }
+ // For a leaf
+ else {
+ const c2_l_t *const pleaf = p.l;
+
+ if (!pleaf) {
+ return;
+ }
+
+ if (C2_L_OEXISTS == pleaf->op && pleaf->neg) {
+ putchar('!');
+ }
+
+ // Print target name, type, and format
+ {
+ printf("%s", c2h_dump_str_tgt(pleaf));
+ if (pleaf->tgt_onframe) {
+ putchar('@');
+ }
+ if (pleaf->predef == C2_L_PUNDEFINED) {
+ if (pleaf->index < 0) {
+ printf("[*]");
+ } else {
+ printf("[%d]", pleaf->index);
+ }
+ }
+ printf(":%d%s", pleaf->format, c2h_dump_str_type(pleaf));
+ }
+
+ // Print operator
+ putchar(' ');
+
+ if (C2_L_OEXISTS != pleaf->op && pleaf->neg) {
+ putchar('!');
+ }
+
+ switch (pleaf->match) {
+ case C2_L_MEXACT: break;
+ case C2_L_MCONTAINS: putchar('*'); break;
+ case C2_L_MSTART: putchar('^'); break;
+ case C2_L_MPCRE: putchar('~'); break;
+ case C2_L_MWILDCARD: putchar('%'); break;
+ }
+
+ if (pleaf->match_ignorecase) {
+ putchar('?');
+ }
+
+ switch (pleaf->op) {
+ case C2_L_OEXISTS: break;
+ case C2_L_OEQ: fputs("=", stdout); break;
+ case C2_L_OGT: fputs(">", stdout); break;
+ case C2_L_OGTEQ: fputs(">=", stdout); break;
+ case C2_L_OLT: fputs("<", stdout); break;
+ case C2_L_OLTEQ: fputs("<=", stdout); break;
+ }
+
+ if (C2_L_OEXISTS == pleaf->op) {
+ return;
+ }
+
+ // Print pattern
+ putchar(' ');
+ switch (pleaf->ptntype) {
+ case C2_L_PTINT: printf("%ld", pleaf->ptnint); break;
+ case C2_L_PTSTRING:
+ // TODO(yshui) Escape string before printing out?
+ printf("\"%s\"", pleaf->ptnstr);
+ break;
+ default: assert(0); break;
+ }
+ }
+}
+
+/**
+ * Get the type atom of a condition.
+ */
+static xcb_atom_t c2_get_atom_type(const c2_l_t *pleaf) {
+ switch (pleaf->type) {
+ case C2_L_TCARDINAL: return XCB_ATOM_CARDINAL;
+ case C2_L_TWINDOW: return XCB_ATOM_WINDOW;
+ case C2_L_TSTRING: return XCB_ATOM_STRING;
+ case C2_L_TATOM: return XCB_ATOM_ATOM;
+ case C2_L_TDRAWABLE: return XCB_ATOM_DRAWABLE;
+ default: assert(0); break;
+ }
+ unreachable;
+}
+
+/**
+ * Match a window against a single leaf window condition.
+ *
+ * For internal use.
+ */
+static inline void c2_match_once_leaf(session_t *ps, const struct managed_win *w,
+ const c2_l_t *pleaf, bool *pres, bool *perr) {
+ assert(pleaf);
+
+ const xcb_window_t wid = (pleaf->tgt_onframe ? w->client_win : w->base.id);
+
+ // Return if wid is missing
+ if (pleaf->predef == C2_L_PUNDEFINED && !wid) {
+ return;
+ }
+
+ const int idx = (pleaf->index < 0 ? 0 : pleaf->index);
+
+ switch (pleaf->ptntype) {
+ // Deal with integer patterns
+ case C2_L_PTINT: {
+ long *targets = NULL;
+ long *targets_free = NULL;
+ size_t ntargets = 0;
+
+ // Get the value
+ // A predefined target
+ long predef_target = 0;
+ if (pleaf->predef != C2_L_PUNDEFINED) {
+ *perr = false;
+ switch (pleaf->predef) {
+ case C2_L_PID: predef_target = wid; break;
+ case C2_L_PX: predef_target = w->g.x; break;
+ case C2_L_PY: predef_target = w->g.y; break;
+ case C2_L_PX2: predef_target = w->g.x + w->widthb; break;
+ case C2_L_PY2: predef_target = w->g.y + w->heightb; break;
+ case C2_L_PWIDTH: predef_target = w->g.width; break;
+ case C2_L_PHEIGHT: predef_target = w->g.height; break;
+ case C2_L_PWIDTHB: predef_target = w->widthb; break;
+ case C2_L_PHEIGHTB: predef_target = w->heightb; break;
+ case C2_L_PBDW: predef_target = w->g.border_width; break;
+ case C2_L_PFULLSCREEN:
+ predef_target = win_is_fullscreen(ps, w);
+ break;
+ case C2_L_POVREDIR: predef_target = w->a.override_redirect; break;
+ case C2_L_PARGB: predef_target = win_has_alpha(w); break;
+ case C2_L_PFOCUSED:
+ predef_target = win_is_focused_raw(ps, w);
+ break;
+ case C2_L_PWMWIN: predef_target = w->wmwin; break;
+ case C2_L_PBSHAPED: predef_target = w->bounding_shaped; break;
+ case C2_L_PROUNDED: predef_target = w->rounded_corners; break;
+ case C2_L_PCLIENT: predef_target = w->client_win; break;
+ case C2_L_PLEADER: predef_target = w->leader; break;
+ default:
+ *perr = true;
+ assert(0);
+ break;
+ }
+ ntargets = 1;
+ targets = &predef_target;
+ }
+ // A raw window property
+ else {
+ int word_count = 1;
+ if (pleaf->index < 0) {
+ // Get length of property in 32-bit multiples
+ auto prop_info = x_get_prop_info(ps->c, wid, pleaf->tgtatom);
+ word_count = to_int_checked((prop_info.length + 4 - 1) / 4);
+ }
+ winprop_t prop = x_get_prop_with_offset(
+ ps->c, wid, pleaf->tgtatom, idx, word_count,
+ c2_get_atom_type(pleaf), pleaf->format);
+
+ ntargets = (pleaf->index < 0 ? prop.nitems : min2(prop.nitems, 1));
+ if (ntargets > 0) {
+ targets = targets_free = ccalloc(ntargets, long);
+ *perr = false;
+ for (size_t i = 0; i < ntargets; ++i) {
+ targets[i] = winprop_get_int(prop, i);
+ }
+ }
+ free_winprop(&prop);
+ }
+
+ if (*perr) {
+ goto fail_int;
+ }
+
+ // Do comparison
+ bool res = false;
+ for (size_t i = 0; i < ntargets; ++i) {
+ long tgt = targets[i];
+ switch (pleaf->op) {
+ case C2_L_OEXISTS:
+ res = (pleaf->predef != C2_L_PUNDEFINED ? tgt : true);
+ break;
+ case C2_L_OEQ: res = (tgt == pleaf->ptnint); break;
+ case C2_L_OGT: res = (tgt > pleaf->ptnint); break;
+ case C2_L_OGTEQ: res = (tgt >= pleaf->ptnint); break;
+ case C2_L_OLT: res = (tgt < pleaf->ptnint); break;
+ case C2_L_OLTEQ: res = (tgt <= pleaf->ptnint); break;
+ default: *perr = true; assert(0);
+ }
+ if (res) {
+ break;
+ }
+ }
+ *pres = res;
+
+ fail_int:
+ // Free property values after usage, if necessary
+ if (targets_free) {
+ free(targets_free);
+ }
+ } break;
+ // String patterns
+ case C2_L_PTSTRING: {
+ const char **targets = NULL;
+ const char **targets_free = NULL;
+ const char **targets_free_inner = NULL;
+ size_t ntargets = 0;
+
+ // A predefined target
+ const char *predef_target = NULL;
+ if (pleaf->predef != C2_L_PUNDEFINED) {
+ switch (pleaf->predef) {
+ case C2_L_PWINDOWTYPE:
+ predef_target = WINTYPES[w->window_type];
+ break;
+ case C2_L_PNAME: predef_target = w->name; break;
+ case C2_L_PCLASSG: predef_target = w->class_general; break;
+ case C2_L_PCLASSI: predef_target = w->class_instance; break;
+ case C2_L_PROLE: predef_target = w->role; break;
+ default: assert(0); break;
+ }
+ ntargets = 1;
+ targets = &predef_target;
+ }
+ // An atom type property, convert it to string
+ else if (pleaf->type == C2_L_TATOM) {
+ int word_count = 1;
+ if (pleaf->index < 0) {
+ // Get length of property in 32-bit multiples
+ auto prop_info = x_get_prop_info(ps->c, wid, pleaf->tgtatom);
+ word_count = to_int_checked((prop_info.length + 4 - 1) / 4);
+ }
+ winprop_t prop = x_get_prop_with_offset(
+ ps->c, wid, pleaf->tgtatom, idx, word_count,
+ c2_get_atom_type(pleaf), pleaf->format);
+
+ ntargets = (pleaf->index < 0 ? prop.nitems : min2(prop.nitems, 1));
+ targets = targets_free = (const char **)ccalloc(2 * ntargets, char *);
+ targets_free_inner = targets + ntargets;
+
+ for (size_t i = 0; i < ntargets; ++i) {
+ xcb_atom_t atom = (xcb_atom_t)winprop_get_int(prop, i);
+ if (atom) {
+ xcb_get_atom_name_reply_t *reply = xcb_get_atom_name_reply(
+ ps->c, xcb_get_atom_name(ps->c, atom), NULL);
+ if (reply) {
+ targets[i] = targets_free_inner[i] = strndup(
+ xcb_get_atom_name_name(reply),
+ (size_t)xcb_get_atom_name_name_length(reply));
+ free(reply);
+ }
+ }
+ }
+ free_winprop(&prop);
+ }
+ // Not an atom type, just fetch the string list
+ else {
+ char **strlst = NULL;
+ int nstr = 0;
+ if (wid_get_text_prop(ps, wid, pleaf->tgtatom, &strlst, &nstr)) {
+ if (pleaf->index < 0 && nstr > 0 && strlen(strlst[0]) > 0) {
+ ntargets = to_u32_checked(nstr);
+ targets = (const char **)strlst;
+ } else if (nstr > idx) {
+ ntargets = 1;
+ targets = (const char **)strlst + idx;
+ }
+ }
+ if (strlst) {
+ targets_free = (const char **)strlst;
+ }
+ }
+
+ if (ntargets == 0) {
+ goto fail_str;
+ }
+ for (size_t i = 0; i < ntargets; ++i) {
+ if (!targets[i]) {
+ goto fail_str;
+ }
+ }
+ *perr = false;
+
+ // Actual matching
+ bool res = false;
+ for (size_t i = 0; i < ntargets; ++i) {
+ const char *tgt = targets[i];
+ switch (pleaf->op) {
+ case C2_L_OEXISTS: res = true; break;
+ case C2_L_OEQ:
+ switch (pleaf->match) {
+ case C2_L_MEXACT:
+ if (pleaf->match_ignorecase) {
+ res = !strcasecmp(tgt, pleaf->ptnstr);
+ } else {
+ res = !strcmp(tgt, pleaf->ptnstr);
+ }
+ break;
+ case C2_L_MCONTAINS:
+ if (pleaf->match_ignorecase) {
+ res = strcasestr(tgt, pleaf->ptnstr);
+ } else {
+ res = strstr(tgt, pleaf->ptnstr);
+ }
+ break;
+ case C2_L_MSTART:
+ if (pleaf->match_ignorecase) {
+ res = !strncasecmp(tgt, pleaf->ptnstr,
+ strlen(pleaf->ptnstr));
+ } else {
+ res = !strncmp(tgt, pleaf->ptnstr,
+ strlen(pleaf->ptnstr));
+ }
+ break;
+ case C2_L_MWILDCARD: {
+ int flags = 0;
+ if (pleaf->match_ignorecase) {
+ flags |= FNM_CASEFOLD;
+ }
+ res = !fnmatch(pleaf->ptnstr, tgt, flags);
+ } break;
+ case C2_L_MPCRE:
+#ifdef CONFIG_REGEX_PCRE
+ assert(strlen(tgt) <= INT_MAX);
+ res = (pcre_exec(pleaf->regex_pcre,
+ pleaf->regex_pcre_extra, tgt,
+ (int)strlen(tgt), 0, 0, NULL, 0) >= 0);
+#else
+ assert(0);
+#endif
+ break;
+ }
+ break;
+ default: *perr = true; assert(0);
+ }
+ if (res) {
+ break;
+ }
+ }
+ *pres = res;
+
+ fail_str:
+ // Free the string after usage, if necessary
+ if (targets_free_inner) {
+ for (size_t i = 0; i < ntargets; ++i) {
+ if (targets_free_inner[i]) {
+ free((void *)targets_free_inner[i]);
+ }
+ }
+ }
+ // Free property values after usage, if necessary
+ if (targets_free) {
+ free(targets_free);
+ }
+ } break;
+ default: assert(0); break;
+ }
+}
+
+/**
+ * Match a window against a single window condition.
+ *
+ * @return true if matched, false otherwise.
+ */
+static bool c2_match_once(session_t *ps, const struct managed_win *w, const c2_ptr_t cond) {
+ bool result = false;
+ bool error = true;
+
+ // Handle a branch
+ if (cond.isbranch) {
+ const c2_b_t *pb = cond.b;
+
+ if (!pb)
+ return false;
+
+ error = false;
+
+ switch (pb->op) {
+ case C2_B_OAND:
+ result = (c2_match_once(ps, w, pb->opr1) &&
+ c2_match_once(ps, w, pb->opr2));
+ break;
+ case C2_B_OOR:
+ result = (c2_match_once(ps, w, pb->opr1) ||
+ c2_match_once(ps, w, pb->opr2));
+ break;
+ case C2_B_OXOR:
+ result = (c2_match_once(ps, w, pb->opr1) !=
+ c2_match_once(ps, w, pb->opr2));
+ break;
+ default: error = true; assert(0);
+ }
+
+#ifdef DEBUG_WINMATCH
+ log_trace("(%#010x): branch: result = %d, pattern = ", w->base.id, result);
+ c2_dump(cond);
+ putchar('\n');
+#endif
+ }
+ // Handle a leaf
+ else {
+ const c2_l_t *pleaf = cond.l;
+
+ if (!pleaf)
+ return false;
+
+ c2_match_once_leaf(ps, w, pleaf, &result, &error);
+
+ // For EXISTS operator, no errors are fatal
+ if (C2_L_OEXISTS == pleaf->op && error) {
+ result = false;
+ error = false;
+ }
+
+#ifdef DEBUG_WINMATCH
+ log_trace("(%#010x): leaf: result = %d, error = %d, "
+ "client = %#010x, pattern = ",
+ w->base.id, result, error, w->client_win);
+ c2_dump(cond);
+ putchar('\n');
+#endif
+ }
+
+ // Postprocess the result
+ if (error)
+ result = false;
+
+ if (cond.isbranch ? cond.b->neg : cond.l->neg)
+ result = !result;
+
+ return result;
+}
+
+/**
+ * Match a window against a condition linked list.
+ *
+ * @param cache a place to cache the last matched condition
+ * @param pdata a place to return the data
+ * @return true if matched, false otherwise.
+ */
+bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condlst,
+ void **pdata) {
+ assert(ps->server_grabbed);
+ // Then go through the whole linked list
+ for (; condlst; condlst = condlst->next) {
+ if (c2_match_once(ps, w, condlst->ptr)) {
+ if (pdata)
+ *pdata = condlst->data;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/c2.h b/src/c2.h
new file mode 100644
index 0000000..d6b1d37
--- /dev/null
+++ b/src/c2.h
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Compton - a compositor for X11
+ *
+ * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
+ *
+ * Copyright (c) 2011-2013, Christopher Jeffrey
+ * See LICENSE-mit for more information.
+ *
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+typedef struct _c2_lptr c2_lptr_t;
+typedef struct session session_t;
+struct managed_win;
+
+c2_lptr_t *c2_parse(c2_lptr_t **pcondlst, const char *pattern, void *data);
+
+c2_lptr_t *c2_free_lptr(c2_lptr_t *lp);
+
+bool c2_match(session_t *ps, const struct managed_win *w, const c2_lptr_t *condlst, void **pdata);
+
+bool c2_list_postprocess(session_t *ps, c2_lptr_t *list);
diff --git a/src/cache.c b/src/cache.c
new file mode 100644
index 0000000..1ffb31c
--- /dev/null
+++ b/src/cache.c
@@ -0,0 +1,95 @@
+#include <uthash.h>
+
+#include "compiler.h"
+#include "utils.h"
+#include "cache.h"
+
+struct cache_entry {
+ char *key;
+ void *value;
+ UT_hash_handle hh;
+};
+
+struct cache {
+ cache_getter_t getter;
+ cache_free_t free;
+ void *user_data;
+ struct cache_entry *entries;
+};
+
+void cache_set(struct cache *c, const char *key, void *data) {
+ struct cache_entry *e = NULL;
+ HASH_FIND_STR(c->entries, key, e);
+ CHECK(!e);
+
+ e = ccalloc(1, struct cache_entry);
+ e->key = strdup(key);
+ e->value = data;
+ HASH_ADD_STR(c->entries, key, e);
+}
+
+void *cache_get(struct cache *c, const char *key, int *err) {
+ struct cache_entry *e;
+ HASH_FIND_STR(c->entries, key, e);
+ if (e) {
+ return e->value;
+ }
+
+ int tmperr;
+ if (!err) {
+ err = &tmperr;
+ }
+
+ *err = 0;
+ e = ccalloc(1, struct cache_entry);
+ e->key = strdup(key);
+ e->value = c->getter(c->user_data, key, err);
+ if (*err) {
+ free(e->key);
+ free(e);
+ return NULL;
+ }
+
+ HASH_ADD_STR(c->entries, key, e);
+ return e->value;
+}
+
+static inline void _cache_invalidate(struct cache *c, struct cache_entry *e) {
+ if (c->free) {
+ c->free(c->user_data, e->value);
+ }
+ free(e->key);
+ HASH_DEL(c->entries, e);
+ free(e);
+}
+
+void cache_invalidate(struct cache *c, const char *key) {
+ struct cache_entry *e;
+ HASH_FIND_STR(c->entries, key, e);
+
+ if (e) {
+ _cache_invalidate(c, e);
+ }
+}
+
+void cache_invalidate_all(struct cache *c) {
+ struct cache_entry *e, *tmpe;
+ HASH_ITER(hh, c->entries, e, tmpe) {
+ _cache_invalidate(c, e);
+ }
+}
+
+void *cache_free(struct cache *c) {
+ void *ret = c->user_data;
+ cache_invalidate_all(c);
+ free(c);
+ return ret;
+}
+
+struct cache *new_cache(void *ud, cache_getter_t getter, cache_free_t f) {
+ auto c = ccalloc(1, struct cache);
+ c->user_data = ud;
+ c->getter = getter;
+ c->free = f;
+ return c;
+}
diff --git a/src/cache.h b/src/cache.h
new file mode 100644
index 0000000..3ca054f
--- /dev/null
+++ b/src/cache.h
@@ -0,0 +1,32 @@
+#pragma once
+
+struct cache;
+
+typedef void *(*cache_getter_t)(void *user_data, const char *key, int *err);
+typedef void (*cache_free_t)(void *user_data, void *data);
+
+/// Create a cache with `getter`, and a free function `f` which is used to free the cache
+/// value when they are invalidated.
+///
+/// `user_data` will be passed to `getter` and `f` when they are called.
+struct cache *new_cache(void *user_data, cache_getter_t getter, cache_free_t f);
+
+/// Fetch a value from the cache. If the value doesn't present in the cache yet, the
+/// getter will be called, and the returned value will be stored into the cache.
+void *cache_get(struct cache *, const char *key, int *err);
+
+/// Invalidate a value in the cache.
+void cache_invalidate(struct cache *, const char *key);
+
+/// Invalidate all values in the cache.
+void cache_invalidate_all(struct cache *);
+
+/// Invalidate all values in the cache and free it. Returns the user data passed to
+/// `new_cache`
+void *cache_free(struct cache *);
+
+/// Insert a key-value pair into the cache. Only used for internal testing. Takes
+/// ownership of `data`
+///
+/// If `key` already exists in the cache, this function will abort the program.
+void cache_set(struct cache *c, const char *key, void *data);
diff --git a/src/common.h b/src/common.h
new file mode 100644
index 0000000..b7f2fe0
--- /dev/null
+++ b/src/common.h
@@ -0,0 +1,541 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Compton - a compositor for X11
+ *
+ * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
+ *
+ * Copyright (c) 2011-2013, Christopher Jeffrey
+ * Copyright (c) 2018, Yuxuan Shui <[email protected]>
+ *
+ * See LICENSE-mit for more information.
+ *
+ */
+
+#pragma once
+
+// === Options ===
+
+// Debug options, enable them using -D in CFLAGS
+// #define DEBUG_REPAINT 1
+// #define DEBUG_EVENTS 1
+// #define DEBUG_RESTACK 1
+// #define DEBUG_WINMATCH 1
+// #define DEBUG_C2 1
+// #define DEBUG_GLX_DEBUG_CONTEXT 1
+
+#define MAX_ALPHA (255)
+
+// === Includes ===
+
+// For some special functions
+#include <assert.h>
+#include <stdbool.h>
+#include <sys/time.h>
+#include <time.h>
+
+#include <X11/Xlib.h>
+#include <ev.h>
+#include <pixman.h>
+#include <xcb/xproto.h>
+#include <xcb/render.h>
+#include <xcb/sync.h>
+
+#include "uthash_extra.h"
+#ifdef CONFIG_OPENGL
+#include "backend/gl/glx.h"
+#endif
+
+// X resource checker
+#ifdef DEBUG_XRC
+#include "xrescheck.h"
+#endif
+
+// FIXME This list of includes should get shorter
+#include "backend/backend.h"
+#include "backend/driver.h"
+#include "compiler.h"
+#include "config.h"
+#include "region.h"
+#include "types.h"
+#include "utils.h"
+#include "list.h"
+#include "render.h"
+#include "win_defs.h"
+#include "x.h"
+
+// === Constants ===0
+
+#define NS_PER_SEC 1000000000L
+#define US_PER_SEC 1000000L
+#define MS_PER_SEC 1000
+
+/// @brief Maximum OpenGL FBConfig depth.
+#define OPENGL_MAX_DEPTH 32
+
+/// @brief Maximum OpenGL buffer age.
+#define CGLX_MAX_BUFFER_AGE 5
+
+// Window flags
+
+// === Types ===
+typedef struct glx_fbconfig glx_fbconfig_t;
+struct glx_session;
+struct atom;
+struct conv;
+
+typedef struct _ignore {
+ struct _ignore *next;
+ unsigned long sequence;
+} ignore_t;
+
+#ifdef CONFIG_OPENGL
+#ifdef DEBUG_GLX_DEBUG_CONTEXT
+typedef GLXContext (*f_glXCreateContextAttribsARB)(Display *dpy, GLXFBConfig config,
+ GLXContext share_context, Bool direct,
+ const int *attrib_list);
+typedef void (*GLDEBUGPROC)(GLenum source, GLenum type, GLuint id, GLenum severity,
+ GLsizei length, const GLchar *message, GLvoid *userParam);
+typedef void (*f_DebugMessageCallback)(GLDEBUGPROC, void *userParam);
+#endif
+
+typedef struct glx_prog_main {
+ /// GLSL program.
+ GLuint prog;
+ /// Location of uniform "opacity" in window GLSL program.
+ GLint unifm_opacity;
+ /// Location of uniform "invert_color" in blur GLSL program.
+ GLint unifm_invert_color;
+ /// Location of uniform "tex" in window GLSL program.
+ GLint unifm_tex;
+ /// Location of uniform "time" in window GLSL program.
+ GLint unifm_time;
+} glx_prog_main_t;
+
+#define GLX_PROG_MAIN_INIT \
+ { \
+ .prog = 0, .unifm_opacity = -1, .unifm_invert_color = -1, \
+ .unifm_tex = -1, .unifm_time = -1 \
+ }
+
+#else
+struct glx_prog_main {};
+#endif
+
+#define PAINT_INIT \
+ { .pixmap = XCB_NONE, .pict = XCB_NONE }
+
+/// Linked list type of atoms.
+typedef struct _latom {
+ xcb_atom_t atom;
+ struct _latom *next;
+} latom_t;
+
+/// Structure containing all necessary data for a session.
+typedef struct session {
+ // === Event handlers ===
+ /// ev_io for X connection
+ ev_io xiow;
+ /// Timeout for delayed unredirection.
+ ev_timer unredir_timer;
+ /// Timer for fading
+ ev_timer fade_timer;
+ /// Timer for animations
+ ev_timer animation_timer;
+ /// Timer for delayed drawing, right now only used by
+ /// swopti
+ ev_timer delayed_draw_timer;
+ /// Use an ev_idle callback for drawing
+ /// So we only start drawing when events are processed
+ ev_idle draw_idle;
+ /// Called everytime we have timeouts or new data on socket,
+ /// so we can be sure if xcb read from X socket at anytime during event
+ /// handling, we will not left any event unhandled in the queue
+ ev_prepare event_check;
+ /// Signal handler for SIGUSR1
+ ev_signal usr1_signal;
+ /// Signal handler for SIGINT
+ ev_signal int_signal;
+ /// backend data
+ backend_t *backend_data;
+ /// backend blur context
+ void *backend_blur_context;
+ /// graphic drivers used
+ enum driver drivers;
+ /// file watch handle
+ void *file_watch_handle;
+ /// libev mainloop
+ struct ev_loop *loop;
+
+ // === Display related ===
+ /// Whether the X server is grabbed by us
+ bool server_grabbed;
+ /// Display in use.
+ Display *dpy;
+ /// Previous handler of X errors
+ XErrorHandler previous_xerror_handler;
+ /// Default screen.
+ int scr;
+ /// XCB connection.
+ xcb_connection_t *c;
+ /// Default visual.
+ xcb_visualid_t vis;
+ /// Default depth.
+ int depth;
+ /// Root window.
+ xcb_window_t root;
+ /// Height of root window.
+ int root_height;
+ /// Width of root window.
+ int root_width;
+ /// Current desktop number of root window
+ int root_desktop_num;
+ /// Desktop switch direction
+ int root_desktop_switch_direction;
+ // Damage of root window.
+ // Damage root_damage;
+ /// X Composite overlay window. Used if <code>--paint-on-overlay</code>.
+ xcb_window_t overlay;
+ /// The target window for debug mode
+ xcb_window_t debug_window;
+ /// Whether the root tile is filled by us.
+ bool root_tile_fill;
+ /// Picture of the root window background.
+ paint_t root_tile_paint;
+ /// The backend data the root pixmap bound to
+ void *root_image;
+ /// A region of the size of the screen.
+ region_t screen_reg;
+ /// Picture of root window. Destination of painting in no-DBE painting
+ /// mode.
+ xcb_render_picture_t root_picture;
+ /// A Picture acting as the painting target.
+ xcb_render_picture_t tgt_picture;
+ /// Temporary buffer to paint to before sending to display.
+ paint_t tgt_buffer;
+ /// Window ID of the window we register as a symbol.
+ xcb_window_t reg_win;
+#ifdef CONFIG_OPENGL
+ /// Pointer to GLX data.
+ struct glx_session *psglx;
+ /// Custom GLX program used for painting window.
+ // XXX should be in struct glx_session
+ glx_prog_main_t glx_prog_win;
+ struct glx_fbconfig_info *argb_fbconfig;
+#endif
+ /// Sync fence to sync draw operations
+ xcb_sync_fence_t sync_fence;
+ /// Whether we are rendering the first frame after screen is redirected
+ bool first_frame;
+
+ // === Operation related ===
+ /// Flags related to the root window
+ uint64_t root_flags;
+ /// Program options.
+ options_t o;
+ /// Whether we have hit unredirection timeout.
+ bool tmout_unredir_hit;
+ /// Whether we need to redraw the screen
+ bool redraw_needed;
+
+ /// Cache a xfixes region so we don't need to allocate it everytime.
+ /// A workaround for yshui/picom#301
+ xcb_xfixes_region_t damaged_region;
+ /// The region needs to painted on next paint.
+ region_t *damage;
+ /// The region damaged on the last paint.
+ region_t *damage_ring;
+ /// Number of damage regions we track
+ int ndamage;
+ /// Whether all windows are currently redirected.
+ bool redirected;
+ /// Pre-generated alpha pictures.
+ xcb_render_picture_t *alpha_picts;
+ /// Time of last fading. In milliseconds.
+ long fade_time;
+ /// Time of last window animation step. In milliseconds.
+ long animation_time;
+ /// Head pointer of the error ignore linked list.
+ ignore_t *ignore_head;
+ /// Pointer to the <code>next</code> member of tail element of the error
+ /// ignore linked list.
+ ignore_t **ignore_tail;
+ // Cached blur convolution kernels.
+ struct x_convolution_kernel **blur_kerns_cache;
+ /// If we should quit
+ bool quit:1;
+ // TODO(yshui) use separate flags for dfferent kinds of updates so we don't
+ // waste our time.
+ /// Whether there are pending updates, like window creation, etc.
+ bool pending_updates:1;
+
+ // === Expose event related ===
+ /// Pointer to an array of <code>XRectangle</code>-s of exposed region.
+ /// XXX why do we need this array?
+ rect_t *expose_rects;
+ /// Number of <code>XRectangle</code>-s in <code>expose_rects</code>.
+ int size_expose;
+ /// Index of the next free slot in <code>expose_rects</code>.
+ int n_expose;
+
+ // === Window related ===
+ /// A hash table of all windows.
+ struct win *windows;
+ /// Windows in their stacking order
+ struct list_node window_stack;
+ /// Pointer to <code>win</code> of current active window. Used by
+ /// EWMH <code>_NET_ACTIVE_WINDOW</code> focus detection. In theory,
+ /// it's more reliable to store the window ID directly here, just in
+ /// case the WM does something extraordinary, but caching the pointer
+ /// means another layer of complexity.
+ struct managed_win *active_win;
+ /// Window ID of leader window of currently active window. Used for
+ /// subsidiary window detection.
+ xcb_window_t active_leader;
+
+ // === Shadow/dimming related ===
+ /// 1x1 black Picture.
+ xcb_render_picture_t black_picture;
+ /// 1x1 Picture of the shadow color.
+ xcb_render_picture_t cshadow_picture;
+ /// 1x1 white Picture.
+ xcb_render_picture_t white_picture;
+ /// Gaussian map of shadow.
+ struct conv *gaussian_map;
+ // for shadow precomputation
+ /// A region in which shadow is not painted on.
+ region_t shadow_exclude_reg;
+
+ // === Software-optimization-related ===
+ /// Currently used refresh rate.
+ int refresh_rate;
+ /// Interval between refresh in nanoseconds.
+ long refresh_intv;
+ /// Nanosecond offset of the first painting.
+ long paint_tm_offset;
+
+#ifdef CONFIG_VSYNC_DRM
+ // === DRM VSync related ===
+ /// File descriptor of DRI device file. Used for DRM VSync.
+ int drm_fd;
+#endif
+
+ // === X extension related ===
+ /// Event base number for X Fixes extension.
+ int xfixes_event;
+ /// Error base number for X Fixes extension.
+ int xfixes_error;
+ /// Event base number for X Damage extension.
+ int damage_event;
+ /// Error base number for X Damage extension.
+ int damage_error;
+ /// Event base number for X Render extension.
+ int render_event;
+ /// Error base number for X Render extension.
+ int render_error;
+ /// Event base number for X Composite extension.
+ int composite_event;
+ /// Error base number for X Composite extension.
+ int composite_error;
+ /// Major opcode for X Composite extension.
+ int composite_opcode;
+ /// Whether X Shape extension exists.
+ bool shape_exists;
+ /// Event base number for X Shape extension.
+ int shape_event;
+ /// Error base number for X Shape extension.
+ int shape_error;
+ /// Whether X RandR extension exists.
+ bool randr_exists;
+ /// Event base number for X RandR extension.
+ int randr_event;
+ /// Error base number for X RandR extension.
+ int randr_error;
+ /// Whether X Present extension exists.
+ bool present_exists;
+ /// Whether X GLX extension exists.
+ bool glx_exists;
+ /// Event base number for X GLX extension.
+ int glx_event;
+ /// Error base number for X GLX extension.
+ int glx_error;
+ /// Whether X Xinerama extension exists.
+ bool xinerama_exists;
+ /// Xinerama screen regions.
+ region_t *xinerama_scr_regs;
+ /// Number of Xinerama screens.
+ int xinerama_nscrs;
+ /// Whether X Sync extension exists.
+ bool xsync_exists;
+ /// Event base number for X Sync extension.
+ int xsync_event;
+ /// Error base number for X Sync extension.
+ int xsync_error;
+ /// Whether X Render convolution filter exists.
+ bool xrfilter_convolution_exists;
+
+ // === Atoms ===
+ struct atom *atoms;
+ /// Array of atoms of all possible window types.
+ xcb_atom_t atoms_wintypes[NUM_WINTYPES];
+ /// Linked list of additional atoms to track.
+ latom_t *track_atom_lst;
+
+#ifdef CONFIG_DBUS
+ // === DBus related ===
+ void *dbus_data;
+#endif
+
+ int (*vsync_wait)(session_t *);
+} session_t;
+
+/// Enumeration for window event hints.
+typedef enum { WIN_EVMODE_UNKNOWN, WIN_EVMODE_FRAME, WIN_EVMODE_CLIENT } win_evmode_t;
+
+extern const char *const WINTYPES[NUM_WINTYPES];
+extern session_t *ps_g;
+
+void ev_xcb_error(session_t *ps, xcb_generic_error_t *err);
+
+// === Functions ===
+
+/**
+ * Subtracting two struct timespec values.
+ *
+ * Taken from glibc manual.
+ *
+ * Subtract the `struct timespec' values X and Y,
+ * storing the result in RESULT.
+ * Return 1 if the difference is negative, otherwise 0.
+ */
+static inline int
+timespec_subtract(struct timespec *result, struct timespec *x, struct timespec *y) {
+ /* Perform the carry for the later subtraction by updating y. */
+ if (x->tv_nsec < y->tv_nsec) {
+ long nsec = (y->tv_nsec - x->tv_nsec) / NS_PER_SEC + 1;
+ y->tv_nsec -= NS_PER_SEC * nsec;
+ y->tv_sec += nsec;
+ }
+
+ if (x->tv_nsec - y->tv_nsec > NS_PER_SEC) {
+ long nsec = (x->tv_nsec - y->tv_nsec) / NS_PER_SEC;
+ y->tv_nsec += NS_PER_SEC * nsec;
+ y->tv_sec -= nsec;
+ }
+
+ /* Compute the time remaining to wait.
+ tv_nsec is certainly positive. */
+ result->tv_sec = x->tv_sec - y->tv_sec;
+ result->tv_nsec = x->tv_nsec - y->tv_nsec;
+
+ /* Return 1 if result is negative. */
+ return x->tv_sec < y->tv_sec;
+}
+
+/**
+ * Get current time in struct timeval.
+ */
+static inline struct timeval get_time_timeval(void) {
+ struct timeval tv = {0, 0};
+
+ gettimeofday(&tv, NULL);
+
+ // Return a time of all 0 if the call fails
+ return tv;
+}
+
+/**
+ * Get current time in struct timespec.
+ *
+ * Note its starting time is unspecified.
+ */
+static inline struct timespec get_time_timespec(void) {
+ struct timespec tm = {0, 0};
+
+ clock_gettime(CLOCK_MONOTONIC, &tm);
+
+ // Return a time of all 0 if the call fails
+ return tm;
+}
+
+/**
+ * Return the painting target window.
+ */
+static inline xcb_window_t get_tgt_window(session_t *ps) {
+ return ps->overlay != XCB_NONE ? ps->overlay : ps->root;
+}
+
+/**
+ * Check if current backend uses GLX.
+ */
+static inline bool bkend_use_glx(session_t *ps) {
+ return BKEND_GLX == ps->o.backend || BKEND_XR_GLX_HYBRID == ps->o.backend;
+}
+
+static void set_ignore(session_t *ps, unsigned long sequence) {
+ if (ps->o.show_all_xerrors)
+ return;
+
+ auto i = cmalloc(ignore_t);
+ if (!i)
+ return;
+
+ i->sequence = sequence;
+ i->next = 0;
+ *ps->ignore_tail = i;
+ ps->ignore_tail = &i->next;
+}
+
+/**
+ * Ignore X errors caused by given X request.
+ */
+static inline void set_ignore_cookie(session_t *ps, xcb_void_cookie_t cookie) {
+ set_ignore(ps, cookie.sequence);
+}
+
+/**
+ * Determine if a window has a specific property.
+ *
+ * @param ps current session
+ * @param w window to check
+ * @param atom atom of property to check
+ * @return true if it has the attribute, false otherwise
+ */
+static inline bool wid_has_prop(const session_t *ps, xcb_window_t w, xcb_atom_t atom) {
+ auto r = xcb_get_property_reply(
+ ps->c, xcb_get_property(ps->c, 0, w, atom, XCB_GET_PROPERTY_TYPE_ANY, 0, 0), NULL);
+ if (!r) {
+ return false;
+ }
+
+ auto rtype = r->type;
+ free(r);
+
+ if (rtype != XCB_NONE) {
+ return true;
+ }
+ return false;
+}
+
+void force_repaint(session_t *ps);
+
+/** @name DBus handling
+ */
+///@{
+#ifdef CONFIG_DBUS
+/** @name DBus hooks
+ */
+///@{
+void opts_set_no_fading_openclose(session_t *ps, bool newval);
+//!@}
+#endif
+
+/**
+ * Set a <code>bool</code> array of all wintypes to true.
+ */
+static inline void wintype_arr_enable(bool arr[]) {
+ wintype_t i;
+
+ for (i = 0; i < NUM_WINTYPES; ++i) {
+ arr[i] = true;
+ }
+}
diff --git a/src/compiler.h b/src/compiler.h
new file mode 100644
index 0000000..f146bd2
--- /dev/null
+++ b/src/compiler.h
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) 2018 Yuxuan Shui <[email protected]>
+#pragma once
+
+#ifdef HAS_STDC_PREDEF_H
+#include <stdc-predef.h>
+#endif
+
+// clang-format off
+#define auto __auto_type
+#define likely(x) __builtin_expect(!!(x), 1)
+#define unlikely(x) __builtin_expect(!!(x), 0)
+#define likely_if(x) if (likely(x))
+#define unlikely_if(x) if (unlikely(x))
+
+#ifndef __has_attribute
+# if __GNUC__ >= 4
+# define __has_attribute(x) 1
+# else
+# define __has_attribute(x) 0
+# endif
+#endif
+
+#if __has_attribute(const)
+# define attr_const __attribute__((const))
+#else
+# define attr_const
+#endif
+
+#if __has_attribute(format)
+# define attr_printf(a, b) __attribute__((format(printf, a, b)))
+#else
+# define attr_printf(a, b)
+#endif
+
+#if __has_attribute(pure)
+# define attr_pure __attribute__((pure))
+#else
+# define attr_pure
+#endif
+
+#if __has_attribute(unused)
+# define attr_unused __attribute__((unused))
+#else
+# define attr_unused
+#endif
+
+#if __has_attribute(warn_unused_result)
+# define attr_warn_unused_result __attribute__((warn_unused_result))
+#else
+# define attr_warn_unused_result
+#endif
+// An alias for conveninence
+#define must_use attr_warn_unused_result
+
+#if __has_attribute(nonnull)
+# define attr_nonnull(...) __attribute__((nonnull(__VA_ARGS__)))
+# define attr_nonnull_all __attribute__((nonnull))
+#else
+# define attr_nonnull(...)
+# define attr_nonnull_all
+#endif
+
+#if __has_attribute(returns_nonnull)
+# define attr_ret_nonnull __attribute__((returns_nonnull))
+#else
+# define attr_ret_nonnull
+#endif
+
+#if __has_attribute(deprecated)
+# define attr_deprecated __attribute__((deprecated))
+#else
+# define attr_deprecated
+#endif
+
+#if __has_attribute(malloc)
+# define attr_malloc __attribute__((malloc))
+#else
+# define attr_malloc
+#endif
+
+#if __has_attribute(fallthrough)
+# define fallthrough() __attribute__((fallthrough))
+#else
+# define fallthrough()
+#endif
+
+#if __STDC_VERSION__ >= 201112L
+# define attr_noret _Noreturn
+#else
+# if __has_attribute(noreturn)
+# define attr_noret __attribute__((noreturn))
+# else
+# define attr_noret
+# endif
+#endif
+
+#if defined(__GNUC__) || defined(__clang__)
+# define unreachable __builtin_unreachable()
+#else
+# define unreachable do {} while(0)
+#endif
+
+#ifndef __has_include
+# define __has_include(x) 0
+#endif
+
+#if !defined(__STDC_NO_THREADS__) && __has_include(<threads.h>)
+# include <threads.h>
+#elif __STDC_VERSION__ >= 201112L
+# define thread_local _Thread_local
+#elif defined(__GNUC__) || defined(__clang__)
+# define thread_local __thread
+#else
+# define thread_local _Pragma("GCC error \"No thread local storage support\"") __error__
+#endif
+// clang-format on
+
+typedef unsigned long ulong;
+typedef unsigned int uint;
+
+static inline int attr_const popcntul(unsigned long a) {
+ return __builtin_popcountl(a);
+}
diff --git a/src/config.c b/src/config.c
new file mode 100644
index 0000000..d24bf64
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,650 @@
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2011-2013, Christopher Jeffrey
+// Copyright (c) 2013 Richard Grenville <[email protected]>
+
+#include <ctype.h>
+#include <limits.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <xcb/render.h> // for xcb_render_fixed_t, XXX
+
+#include "c2.h"
+#include "common.h"
+#include "compiler.h"
+#include "kernel.h"
+#include "log.h"
+#include "region.h"
+#include "string_utils.h"
+#include "types.h"
+#include "utils.h"
+#include "win.h"
+
+#include "config.h"
+
+/**
+ * Parse a long number.
+ */
+bool parse_long(const char *s, long *dest) {
+ const char *endptr = NULL;
+ long val = strtol(s, (char **)&endptr, 0);
+ if (!endptr || endptr == s) {
+ log_error("Invalid number: %s", s);
+ return false;
+ }
+ while (isspace((unsigned char)*endptr))
+ ++endptr;
+ if (*endptr) {
+ log_error("Trailing characters: %s", s);
+ return false;
+ }
+ *dest = val;
+ return true;
+}
+
+/**
+ * Parse an int number.
+ */
+bool parse_int(const char *s, int *dest) {
+ long val;
+ if (!parse_long(s, &val)) {
+ return false;
+ }
+ if (val > INT_MAX || val < INT_MIN) {
+ log_error("Number exceeded int limits: %ld", val);
+ return false;
+ }
+ *dest = (int)val;
+ return true;
+}
+
+/**
+ * Parse a floating-point number in from a string,
+ * also strips the trailing space and comma after the number.
+ *
+ * @param[in] src string to parse
+ * @param[out] dest return the number parsed from the string
+ * @return pointer to the last character parsed
+ */
+const char *parse_readnum(const char *src, double *dest) {
+ const char *pc = NULL;
+ double val = strtod_simple(src, &pc);
+ if (!pc || pc == src) {
+ log_error("No number found: %s", src);
+ return src;
+ }
+ while (*pc && (isspace((unsigned char)*pc) || *pc == ',')) {
+ ++pc;
+ }
+ *dest = val;
+ return pc;
+}
+
+enum blur_method parse_blur_method(const char *src) {
+ if (strcmp(src, "kernel") == 0) {
+ return BLUR_METHOD_KERNEL;
+ } else if (strcmp(src, "box") == 0) {
+ return BLUR_METHOD_BOX;
+ } else if (strcmp(src, "gaussian") == 0) {
+ return BLUR_METHOD_GAUSSIAN;
+ } else if (strcmp(src, "dual_kawase") == 0) {
+ return BLUR_METHOD_DUAL_KAWASE;
+ } else if (strcmp(src, "kawase") == 0) {
+ log_warn("Blur method 'kawase' has been renamed to 'dual_kawase'. "
+ "Interpreted as 'dual_kawase', but this will stop working "
+ "soon.");
+ return BLUR_METHOD_DUAL_KAWASE;
+ } else if (strcmp(src, "none") == 0) {
+ return BLUR_METHOD_NONE;
+ }
+ return BLUR_METHOD_INVALID;
+}
+
+/**
+ * Parse a matrix.
+ *
+ * @param[in] src the blur kernel string
+ * @param[out] endptr return where the end of kernel is in the string
+ * @param[out] hasneg whether the kernel has negative values
+ */
+conv *parse_blur_kern(const char *src, const char **endptr, bool *hasneg) {
+ int width = 0, height = 0;
+ *hasneg = false;
+
+ const char *pc = NULL;
+
+ // Get matrix width and height
+ double val = 0.0;
+ if (src == (pc = parse_readnum(src, &val)))
+ goto err1;
+ src = pc;
+ width = (int)val;
+ if (src == (pc = parse_readnum(src, &val)))
+ goto err1;
+ src = pc;
+ height = (int)val;
+
+ // Validate matrix width and height
+ if (width <= 0 || height <= 0) {
+ log_error("Blue kernel width/height can't be negative.");
+ goto err1;
+ }
+ if (!(width % 2 && height % 2)) {
+ log_error("Blur kernel width/height must be odd.");
+ goto err1;
+ }
+ if (width > 16 || height > 16)
+ log_warn("Blur kernel width/height too large, may slow down"
+ "rendering, and/or consume lots of memory");
+
+ // Allocate memory
+ conv *matrix = cvalloc(sizeof(conv) + (size_t)(width * height) * sizeof(double));
+
+ // Read elements
+ int skip = height / 2 * width + width / 2;
+ for (int i = 0; i < width * height; ++i) {
+ // Ignore the center element
+ if (i == skip) {
+ matrix->data[i] = 1;
+ continue;
+ }
+ if (src == (pc = parse_readnum(src, &val))) {
+ goto err2;
+ }
+ src = pc;
+ if (val < 0) {
+ *hasneg = true;
+ }
+ matrix->data[i] = val;
+ }
+
+ // Detect trailing characters
+ for (; *pc && *pc != ';'; pc++) {
+ if (!isspace((unsigned char)*pc) && *pc != ',') {
+ // TODO(yshui) isspace is locale aware, be careful
+ log_error("Trailing characters in blur kernel string.");
+ goto err2;
+ }
+ }
+
+ // Jump over spaces after ';'
+ if (*pc == ';') {
+ pc++;
+ while (*pc && isspace((unsigned char)*pc)) {
+ ++pc;
+ }
+ }
+
+ // Require an end of string if endptr is not provided, otherwise
+ // copy end pointer to endptr
+ if (endptr) {
+ *endptr = pc;
+ } else if (*pc) {
+ log_error("Only one blur kernel expected.");
+ goto err2;
+ }
+
+ // Fill in width and height
+ matrix->w = width;
+ matrix->h = height;
+ return matrix;
+
+err2:
+ free(matrix);
+err1:
+ return NULL;
+}
+
+/**
+ * Parse a list of convolution kernels.
+ *
+ * @param[in] src string to parse
+ * @param[out] hasneg whether any of the kernels have negative values
+ * @return the kernels
+ */
+struct conv **parse_blur_kern_lst(const char *src, bool *hasneg, int *count) {
+ // TODO(yshui) just return a predefined kernels, not parse predefined strings...
+ static const struct {
+ const char *name;
+ const char *kern_str;
+ } CONV_KERN_PREDEF[] = {
+ {"3x3box", "3,3,1,1,1,1,1,1,1,1,"},
+ {"5x5box", "5,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,"},
+ {"7x7box", "7,7,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,"
+ "1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,"},
+ {"3x3gaussian", "3,3,0.243117,0.493069,0.243117,0.493069,0.493069,0.243117,0."
+ "493069,0.243117,"},
+ {"5x5gaussian", "5,5,0.003493,0.029143,0.059106,0.029143,0.003493,0.029143,0."
+ "243117,0.493069,0.243117,0.029143,0.059106,0.493069,0."
+ "493069,0.059106,0.029143,0.243117,0.493069,0.243117,0."
+ "029143,0.003493,0.029143,0.059106,0.029143,0.003493,"},
+ {"7x7gaussian", "7,7,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0."
+ "000003,0.000102,0.003493,0.029143,0.059106,0.029143,0."
+ "003493,0.000102,0.000849,0.029143,0.243117,0.493069,0."
+ "243117,0.029143,0.000849,0.001723,0.059106,0.493069,0."
+ "493069,0.059106,0.001723,0.000849,0.029143,0.243117,0."
+ "493069,0.243117,0.029143,0.000849,0.000102,0.003493,0."
+ "029143,0.059106,0.029143,0.003493,0.000102,0.000003,0."
+ "000102,0.000849,0.001723,0.000849,0.000102,0.000003,"},
+ {"9x9gaussian",
+ "9,9,0.000000,0.000000,0.000001,0.000006,0.000012,0.000006,0.000001,0."
+ "000000,0.000000,0.000000,0.000003,0.000102,0.000849,0.001723,0.000849,0."
+ "000102,0.000003,0.000000,0.000001,0.000102,0.003493,0.029143,0.059106,0."
+ "029143,0.003493,0.000102,0.000001,0.000006,0.000849,0.029143,0.243117,0."
+ "493069,0.243117,0.029143,0.000849,0.000006,0.000012,0.001723,0.059106,0."
+ "493069,0.493069,0.059106,0.001723,0.000012,0.000006,0.000849,0.029143,0."
+ "243117,0.493069,0.243117,0.029143,0.000849,0.000006,0.000001,0.000102,0."
+ "003493,0.029143,0.059106,0.029143,0.003493,0.000102,0.000001,0.000000,0."
+ "000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003,0.000000,0."
+ "000000,0.000000,0.000001,0.000006,0.000012,0.000006,0.000001,0.000000,0."
+ "000000,"},
+ {"11x11gaussian",
+ "11,11,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0."
+ "000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000001,0."
+ "000006,0.000012,0.000006,0.000001,0.000000,0.000000,0.000000,0.000000,0."
+ "000000,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003,0."
+ "000000,0.000000,0.000000,0.000001,0.000102,0.003493,0.029143,0.059106,0."
+ "029143,0.003493,0.000102,0.000001,0.000000,0.000000,0.000006,0.000849,0."
+ "029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.000006,0.000000,0."
+ "000000,0.000012,0.001723,0.059106,0.493069,0.493069,0.059106,0.001723,0."
+ "000012,0.000000,0.000000,0.000006,0.000849,0.029143,0.243117,0.493069,0."
+ "243117,0.029143,0.000849,0.000006,0.000000,0.000000,0.000001,0.000102,0."
+ "003493,0.029143,0.059106,0.029143,0.003493,0.000102,0.000001,0.000000,0."
+ "000000,0.000000,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0."
+ "000003,0.000000,0.000000,0.000000,0.000000,0.000000,0.000001,0.000006,0."
+ "000012,0.000006,0.000001,0.000000,0.000000,0.000000,0.000000,0.000000,0."
+ "000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0."
+ "000000,"},
+ };
+
+ *count = 0;
+ *hasneg = false;
+ for (unsigned int i = 0;
+ i < sizeof(CONV_KERN_PREDEF) / sizeof(CONV_KERN_PREDEF[0]); ++i) {
+ if (!strcmp(CONV_KERN_PREDEF[i].name, src))
+ return parse_blur_kern_lst(CONV_KERN_PREDEF[i].kern_str, hasneg, count);
+ }
+
+ int nkernels = 1;
+ for (int i = 0; src[i]; i++) {
+ if (src[i] == ';') {
+ nkernels++;
+ }
+ }
+
+ struct conv **ret = ccalloc(nkernels, struct conv *);
+
+ int i = 0;
+ const char *pc = src;
+
+ // Continue parsing until the end of source string
+ i = 0;
+ while (pc && *pc) {
+ bool tmp_hasneg;
+ assert(i < nkernels);
+ ret[i] = parse_blur_kern(pc, &pc, &tmp_hasneg);
+ if (!ret[i]) {
+ for (int j = 0; j < i; j++) {
+ free(ret[j]);
+ }
+ free(ret);
+ return NULL;
+ }
+ i++;
+ *hasneg |= tmp_hasneg;
+ }
+
+ if (i > 1) {
+ log_warn("You are seeing this message because you are using "
+ "multipass blur. Please report an issue to us so we know "
+ "multipass blur is actually been used. Otherwise it might be "
+ "removed in future releases");
+ }
+
+ *count = i;
+
+ return ret;
+}
+
+/**
+ * Parse a X geometry.
+ *
+ * ps->root_width and ps->root_height must be valid
+ */
+bool parse_geometry(session_t *ps, const char *src, region_t *dest) {
+ pixman_region32_clear(dest);
+ if (!src) {
+ return true;
+ }
+ if (!ps->root_width || !ps->root_height) {
+ return true;
+ }
+
+ long x = 0, y = 0;
+ long width = ps->root_width, height = ps->root_height;
+ long val = 0L;
+ char *endptr = NULL;
+
+ src = skip_space(src);
+ if (!*src) {
+ goto parse_geometry_end;
+ }
+
+ // Parse width
+ // Must be base 10, because "0x0..." may appear
+ if (*src != '+' && *src != '-') {
+ val = strtol(src, &endptr, 10);
+ assert(endptr);
+ if (src != endptr) {
+ if (val < 0) {
+ log_error("Invalid width: %s", src);
+ return false;
+ }
+ width = val;
+ src = endptr;
+ }
+ src = skip_space(src);
+ }
+
+ // Parse height
+ if (*src == 'x') {
+ ++src;
+ val = strtol(src, &endptr, 10);
+ assert(endptr);
+ if (src != endptr) {
+ if (val < 0) {
+ log_error("Invalid height: %s", src);
+ return false;
+ }
+ height = val;
+ src = endptr;
+ }
+ src = skip_space(src);
+ }
+
+ // Parse x
+ if (*src == '+' || *src == '-') {
+ val = strtol(src, &endptr, 10);
+ if (endptr && src != endptr) {
+ x = val;
+ if (*src == '-') {
+ x += ps->root_width - width;
+ }
+ src = endptr;
+ }
+ src = skip_space(src);
+ }
+
+ // Parse y
+ if (*src == '+' || *src == '-') {
+ val = strtol(src, &endptr, 10);
+ if (endptr && src != endptr) {
+ y = val;
+ if (*src == '-') {
+ y += ps->root_height - height;
+ }
+ src = endptr;
+ }
+ src = skip_space(src);
+ }
+
+ if (*src) {
+ log_error("Trailing characters: %s", src);
+ return false;
+ }
+
+parse_geometry_end:
+ if (x < INT_MIN || x > INT_MAX || y < INT_MIN || y > INT_MAX) {
+ log_error("Geometry coordinates exceeded limits: %s", src);
+ return false;
+ }
+ if (width > UINT_MAX || height > UINT_MAX) {
+ // less than 0 is checked for earlier
+ log_error("Geometry size exceeded limits: %s", src);
+ return false;
+ }
+ pixman_region32_union_rect(dest, dest, (int)x, (int)y, (uint)width, (uint)height);
+ return true;
+}
+
+/**
+ * Parse a list of opacity rules.
+ */
+bool parse_rule_opacity(c2_lptr_t **res, const char *src) {
+ // Find opacity value
+ char *endptr = NULL;
+ long val = strtol(src, &endptr, 0);
+ if (!endptr || endptr == src) {
+ log_error("No opacity specified: %s", src);
+ return false;
+ }
+ if (val > 100 || val < 0) {
+ log_error("Opacity %ld invalid: %s", val, src);
+ return false;
+ }
+
+ // Skip over spaces
+ while (*endptr && isspace((unsigned char)*endptr))
+ ++endptr;
+ if (':' != *endptr) {
+ log_error("Opacity terminator not found: %s", src);
+ return false;
+ }
+ ++endptr;
+
+ // Parse pattern
+ // I hope 1-100 is acceptable for (void *)
+ return c2_parse(res, endptr, (void *)val);
+}
+
+/**
+ * Add a pattern to a condition linked list.
+ */
+bool condlst_add(c2_lptr_t **pcondlst, const char *pattern) {
+ if (!pattern)
+ return false;
+
+ if (!c2_parse(pcondlst, pattern, NULL))
+ exit(1);
+
+ return true;
+}
+
+void set_default_winopts(options_t *opt, win_option_mask_t *mask, bool shadow_enable,
+ bool fading_enable, bool blur_enable) {
+ // Apply default wintype options.
+ if (!mask[WINTYPE_DESKTOP].shadow) {
+ // Desktop windows are always drawn without shadow by default.
+ mask[WINTYPE_DESKTOP].shadow = true;
+ opt->wintype_option[WINTYPE_DESKTOP].shadow = false;
+ }
+
+ // Focused/unfocused state only apply to a few window types, all other windows
+ // are always considered focused.
+ const wintype_t nofocus_type[] = {WINTYPE_UNKNOWN, WINTYPE_NORMAL, WINTYPE_UTILITY};
+ for (unsigned long i = 0; i < ARR_SIZE(nofocus_type); i++) {
+ if (!mask[nofocus_type[i]].focus) {
+ mask[nofocus_type[i]].focus = true;
+ opt->wintype_option[nofocus_type[i]].focus = false;
+ }
+ }
+ for (unsigned long i = 0; i < NUM_WINTYPES; i++) {
+ if (!mask[i].shadow) {
+ mask[i].shadow = true;
+ opt->wintype_option[i].shadow = shadow_enable;
+ }
+ if (!mask[i].fade) {
+ mask[i].fade = true;
+ opt->wintype_option[i].fade = fading_enable;
+ }
+ if (!mask[i].focus) {
+ mask[i].focus = true;
+ opt->wintype_option[i].focus = true;
+ }
+ if (!mask[i].blur_background) {
+ mask[i].blur_background = true;
+ opt->wintype_option[i].blur_background = blur_enable;
+ }
+ if (!mask[i].full_shadow) {
+ mask[i].full_shadow = true;
+ opt->wintype_option[i].full_shadow = false;
+ }
+ if (!mask[i].redir_ignore) {
+ mask[i].redir_ignore = true;
+ opt->wintype_option[i].redir_ignore = false;
+ }
+ if (!mask[i].opacity) {
+ mask[i].opacity = true;
+ // Opacity is not set to a concrete number here because the
+ // opacity logic is complicated, and needs an "unset" state
+ opt->wintype_option[i].opacity = NAN;
+ }
+ if (!mask[i].animation) {
+ mask[i].animation = OPEN_WINDOW_ANIMATION_INVALID;
+ opt->wintype_option[i].animation = OPEN_WINDOW_ANIMATION_INVALID;
+ }
+ if (!mask[i].animation_unmap) {
+ mask[i].animation_unmap = OPEN_WINDOW_ANIMATION_INVALID;
+ opt->wintype_option[i].animation_unmap = OPEN_WINDOW_ANIMATION_INVALID;
+ }
+ if (!mask[i].animation_workspace_in) {
+ mask[i].animation_workspace_in = OPEN_WINDOW_ANIMATION_INVALID;
+ opt->wintype_option[i].animation_workspace_in = OPEN_WINDOW_ANIMATION_INVALID;
+ }
+ if (!mask[i].animation_workspace_out) {
+ mask[i].animation_workspace_out = OPEN_WINDOW_ANIMATION_INVALID;
+ opt->wintype_option[i].animation_workspace_out = OPEN_WINDOW_ANIMATION_INVALID;
+ }
+ if (!mask[i].clip_shadow_above) {
+ mask[i].clip_shadow_above = true;
+ opt->wintype_option[i].clip_shadow_above = false;
+ }
+ }
+}
+
+enum open_window_animation parse_open_window_animation(const char *src) {
+ if (strcmp(src, "none") == 0) {
+ return OPEN_WINDOW_ANIMATION_NONE;
+ }else if (strcmp(src, "auto") == 0) {
+ return OPEN_WINDOW_ANIMATION_AUTO;
+ } else if (strcmp(src, "fly-in") == 0) {
+ return OPEN_WINDOW_ANIMATION_FLYIN;
+ } else if (strcmp(src, "zoom") == 0) {
+ return OPEN_WINDOW_ANIMATION_ZOOM;
+ } else if (strcmp(src, "slide-up") == 0) {
+ return OPEN_WINDOW_ANIMATION_SLIDE_UP;
+ } else if (strcmp(src, "slide-down") == 0) {
+ return OPEN_WINDOW_ANIMATION_SLIDE_DOWN;
+ } else if (strcmp(src, "slide-left") == 0) {
+ return OPEN_WINDOW_ANIMATION_SLIDE_LEFT;
+ } else if (strcmp(src, "slide-right") == 0) {
+ return OPEN_WINDOW_ANIMATION_SLIDE_RIGHT;
+ }
+ return OPEN_WINDOW_ANIMATION_INVALID;
+}
+
+char *parse_config(options_t *opt, const char *config_file, bool *shadow_enable,
+ bool *fading_enable, bool *hasneg, win_option_mask_t *winopt_mask) {
+ // clang-format off
+ *opt = (struct options){
+ .backend = BKEND_XRENDER,
+ .glx_no_stencil = false,
+ .mark_wmwin_focused = false,
+ .mark_ovredir_focused = false,
+ .detect_rounded_corners = false,
+ .resize_damage = 0,
+ .unredir_if_possible = false,
+ .unredir_if_possible_blacklist = NULL,
+ .unredir_if_possible_delay = 0,
+ .redirected_force = UNSET,
+ .stoppaint_force = UNSET,
+ .dbus = false,
+ .benchmark = 0,
+ .benchmark_wid = XCB_NONE,
+ .logpath = NULL,
+
+ .refresh_rate = 0,
+ .sw_opti = false,
+ .use_damage = true,
+
+ .shadow_red = 0.0,
+ .shadow_green = 0.0,
+ .shadow_blue = 0.0,
+ .shadow_radius = 18,
+ .shadow_offset_x = -15,
+ .shadow_offset_y = -15,
+ .shadow_opacity = .75,
+ .shadow_blacklist = NULL,
+ .shadow_ignore_shaped = false,
+ .xinerama_shadow_crop = false,
+ .shadow_clip_list = NULL,
+
+ .corner_radius = 0,
+
+ .fade_in_step = 0.028,
+ .fade_out_step = 0.03,
+ .fade_delta = 10,
+ .no_fading_openclose = false,
+ .no_fading_destroyed_argb = false,
+ .fade_blacklist = NULL,
+
+ .animations = false,
+ .animation_for_open_window = OPEN_WINDOW_ANIMATION_NONE,
+ .animation_for_transient_window = OPEN_WINDOW_ANIMATION_NONE,
+ .animation_for_unmap_window = OPEN_WINDOW_ANIMATION_AUTO,
+ .animation_for_workspace_switch_in = OPEN_WINDOW_ANIMATION_AUTO,
+ .animation_for_workspace_switch_out = OPEN_WINDOW_ANIMATION_AUTO,
+ .animation_stiffness = 200.0,
+ .animation_window_mass = 1.0,
+ .animation_dampening = 25,
+ .animation_delta = 10,
+ .animation_force_steps = false,
+ .animation_clamping = true,
+
+ .inactive_opacity = 1.0,
+ .inactive_opacity_override = false,
+ .active_opacity = 1.0,
+ .frame_opacity = 1.0,
+ .detect_client_opacity = false,
+
+ .blur_method = BLUR_METHOD_NONE,
+ .blur_radius = 3,
+ .blur_deviation = 0.84089642,
+ .blur_strength = 5,
+ .blur_background_frame = false,
+ .blur_background_fixed = false,
+ .blur_background_blacklist = NULL,
+ .blur_kerns = NULL,
+ .blur_kernel_count = 0,
+ .inactive_dim = 0.0,
+ .inactive_dim_fixed = false,
+ .invert_color_list = NULL,
+ .opacity_rules = NULL,
+ .max_brightness = 1.0,
+
+ .use_ewmh_active_win = false,
+ .focus_blacklist = NULL,
+ .detect_transient = false,
+ .detect_client_leader = false,
+ .no_ewmh_fullscreen = false,
+
+ .track_leader = false,
+
+ .rounded_corners_blacklist = NULL
+ };
+ // clang-format on
+
+ char *ret = NULL;
+#ifdef CONFIG_LIBCONFIG
+ ret = parse_config_libconfig(opt, config_file, shadow_enable, fading_enable,
+ hasneg, winopt_mask);
+#else
+ (void)config_file;
+ (void)shadow_enable;
+ (void)fading_enable;
+ (void)hasneg;
+ (void)winopt_mask;
+#endif
+ return ret;
+}
diff --git a/src/config.h b/src/config.h
new file mode 100644
index 0000000..a22d222
--- /dev/null
+++ b/src/config.h
@@ -0,0 +1,380 @@
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2011-2013, Christopher Jeffrey
+// Copyright (c) 2013 Richard Grenville <[email protected]>
+// Copyright (c) 2018 Yuxuan Shui <[email protected]>
+#pragma once
+
+/// Common functions and definitions for configuration parsing
+/// Used for command line arguments and config files
+
+#include <ctype.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <xcb/render.h> // for xcb_render_fixed_t, XXX
+#include <xcb/xcb.h>
+#include <xcb/xfixes.h>
+
+#ifdef CONFIG_LIBCONFIG
+#include <libconfig.h>
+#endif
+
+#include "compiler.h"
+#include "kernel.h"
+#include "log.h"
+#include "region.h"
+#include "types.h"
+#include "win_defs.h"
+
+typedef struct session session_t;
+
+/// @brief Possible backends
+enum backend {
+ BKEND_XRENDER,
+ BKEND_GLX,
+ BKEND_XR_GLX_HYBRID,
+ BKEND_DUMMY,
+ NUM_BKEND,
+};
+
+enum open_window_animation {
+ OPEN_WINDOW_ANIMATION_NONE = 0,
+ OPEN_WINDOW_ANIMATION_AUTO,
+ OPEN_WINDOW_ANIMATION_FLYIN,
+ OPEN_WINDOW_ANIMATION_ZOOM,
+ OPEN_WINDOW_ANIMATION_SLIDE_UP,
+ OPEN_WINDOW_ANIMATION_SLIDE_DOWN,
+ OPEN_WINDOW_ANIMATION_SLIDE_LEFT,
+ OPEN_WINDOW_ANIMATION_SLIDE_RIGHT,
+ OPEN_WINDOW_ANIMATION_SLIDE_IN,
+ OPEN_WINDOW_ANIMATION_SLIDE_OUT,
+ OPEN_WINDOW_ANIMATION_INVALID,
+};
+
+typedef struct win_option_mask {
+ bool shadow : 1;
+ bool fade : 1;
+ bool focus : 1;
+ bool blur_background : 1;
+ bool full_shadow : 1;
+ bool redir_ignore : 1;
+ bool opacity : 1;
+ bool clip_shadow_above : 1;
+ enum open_window_animation animation;
+ enum open_window_animation animation_unmap;
+ enum open_window_animation animation_workspace_in;
+ enum open_window_animation animation_workspace_out;
+} win_option_mask_t;
+
+typedef struct win_option {
+ bool shadow;
+ bool fade;
+ bool focus;
+ bool blur_background;
+ bool full_shadow;
+ bool redir_ignore;
+ double opacity;
+ bool clip_shadow_above;
+ enum open_window_animation animation;
+ enum open_window_animation animation_unmap;
+ enum open_window_animation animation_workspace_in;
+ enum open_window_animation animation_workspace_out;
+} win_option_t;
+
+enum blur_method {
+ BLUR_METHOD_NONE = 0,
+ BLUR_METHOD_KERNEL,
+ BLUR_METHOD_BOX,
+ BLUR_METHOD_GAUSSIAN,
+ BLUR_METHOD_DUAL_KAWASE,
+ BLUR_METHOD_INVALID,
+};
+
+typedef struct _c2_lptr c2_lptr_t;
+
+/// Structure representing all options.
+typedef struct options {
+ // === Debugging ===
+ bool monitor_repaint;
+ bool print_diagnostics;
+ /// Render to a separate window instead of taking over the screen
+ bool debug_mode;
+ // === General ===
+ /// Use the experimental new backends?
+ bool experimental_backends;
+ /// Path to write PID to.
+ char *write_pid_path;
+ /// The backend in use.
+ enum backend backend;
+ /// Whether to sync X drawing with X Sync fence to avoid certain delay
+ /// issues with GLX backend.
+ bool xrender_sync_fence;
+ /// Whether to avoid using stencil buffer under GLX backend. Might be
+ /// unsafe.
+ bool glx_no_stencil;
+ /// Whether to avoid rebinding pixmap on window damage.
+ bool glx_no_rebind_pixmap;
+ /// Custom fragment shader for painting windows, as a string.
+ char *glx_fshader_win_str;
+ /// Whether to detect rounded corners.
+ bool detect_rounded_corners;
+ /// Force painting of window content with blending.
+ bool force_win_blend;
+ /// Resize damage for a specific number of pixels.
+ int resize_damage;
+ /// Whether to unredirect all windows if a full-screen opaque window
+ /// is detected.
+ bool unredir_if_possible;
+ /// List of conditions of windows to ignore as a full-screen window
+ /// when determining if a window could be unredirected.
+ c2_lptr_t *unredir_if_possible_blacklist;
+ /// Delay before unredirecting screen, in milliseconds.
+ long unredir_if_possible_delay;
+ /// Forced redirection setting through D-Bus.
+ switch_t redirected_force;
+ /// Whether to stop painting. Controlled through D-Bus.
+ switch_t stoppaint_force;
+ /// Whether to enable D-Bus support.
+ bool dbus;
+ /// Path to log file.
+ char *logpath;
+ /// Number of cycles to paint in benchmark mode. 0 for disabled.
+ int benchmark;
+ /// Window to constantly repaint in benchmark mode. 0 for full-screen.
+ xcb_window_t benchmark_wid;
+ /// A list of conditions of windows not to paint.
+ c2_lptr_t *paint_blacklist;
+ /// Whether to show all X errors.
+ bool show_all_xerrors;
+ /// Whether to avoid acquiring X Selection.
+ bool no_x_selection;
+ /// Window type option override.
+ win_option_t wintype_option[NUM_WINTYPES];
+
+ // === VSync & software optimization ===
+ /// User-specified refresh rate.
+ int refresh_rate;
+ /// Whether to enable refresh-rate-based software optimization.
+ bool sw_opti;
+ /// VSync method to use;
+ bool vsync;
+ /// Whether to use glFinish() instead of glFlush() for (possibly) better
+ /// VSync yet probably higher CPU usage.
+ bool vsync_use_glfinish;
+ /// Whether use damage information to help limit the area to paint
+ bool use_damage;
+
+ // === Shadow ===
+ /// Red, green and blue tone of the shadow.
+ double shadow_red, shadow_green, shadow_blue;
+ int shadow_radius;
+ int shadow_offset_x, shadow_offset_y;
+ double shadow_opacity;
+ /// argument string to shadow-exclude-reg option
+ char *shadow_exclude_reg_str;
+ /// Shadow blacklist. A linked list of conditions.
+ c2_lptr_t *shadow_blacklist;
+ /// Whether bounding-shaped window should be ignored.
+ bool shadow_ignore_shaped;
+ /// Whether to crop shadow to the very Xinerama screen.
+ bool xinerama_shadow_crop;
+ /// Don't draw shadow over these windows. A linked list of conditions.
+ c2_lptr_t *shadow_clip_list;
+
+ // === Fading ===
+ /// How much to fade in in a single fading step.
+ double fade_in_step;
+ /// How much to fade out in a single fading step.
+ double fade_out_step;
+ /// Fading time delta. In milliseconds.
+ int fade_delta;
+ /// Whether to disable fading on window open/close.
+ bool no_fading_openclose;
+ /// Whether to disable fading on ARGB managed destroyed windows.
+ bool no_fading_destroyed_argb;
+ /// Fading blacklist. A linked list of conditions.
+ c2_lptr_t *fade_blacklist;
+
+ // === Animations ===
+ /// Whether to do window animations
+ bool animations;
+ /// Which animation to run when opening a window
+ enum open_window_animation animation_for_open_window;
+ /// Which animation to run when opening a transient window
+ enum open_window_animation animation_for_transient_window;
+ /// Which animation to run when unmapping (e.g. minimizing) a window
+ enum open_window_animation animation_for_unmap_window;
+ /// Which animation to run when switching workspace
+ /// IMPORTANT: will only work if window manager updates
+ /// _NET_CURRENT_DESKTOP before doing the hide/show of windows
+ enum open_window_animation animation_for_workspace_switch_in;
+ enum open_window_animation animation_for_workspace_switch_out;
+ /// Spring stiffness for animation
+ double animation_stiffness;
+ /// Window mass for animation
+ double animation_window_mass;
+ /// Animation dampening
+ double animation_dampening;
+ /// Animation delta. In milliseconds.
+ double animation_delta;
+ /// Whether to force animations to not miss a beat
+ bool animation_force_steps;
+ /// Whether to clamp animations
+ bool animation_clamping;
+ /// TODO: window animation blacklist
+ /// TODO: open/close animations
+
+ // === Opacity ===
+ /// Default opacity for inactive windows.
+ /// 32-bit integer with the format of _NET_WM_OPACITY.
+ double inactive_opacity;
+ /// Default opacity for inactive windows.
+ double active_opacity;
+ /// Whether inactive_opacity overrides the opacity set by window
+ /// attributes.
+ bool inactive_opacity_override;
+ /// Frame opacity. Relative to window opacity, also affects shadow
+ /// opacity.
+ double frame_opacity;
+ /// Whether to detect _NET_WM_OPACITY on client windows. Used on window
+ /// managers that don't pass _NET_WM_OPACITY to frame windows.
+ bool detect_client_opacity;
+
+ // === Other window processing ===
+ /// Blur method for background of semi-transparent windows
+ enum blur_method blur_method;
+ // Size of the blur kernel
+ int blur_radius;
+ // Standard deviation for the gaussian blur
+ double blur_deviation;
+ // Strength of the dual_kawase blur
+ int blur_strength;
+ /// Whether to blur background when the window frame is not opaque.
+ /// Implies blur_background.
+ bool blur_background_frame;
+ /// Whether to use fixed blur strength instead of adjusting according
+ /// to window opacity.
+ bool blur_background_fixed;
+ /// Background blur blacklist. A linked list of conditions.
+ c2_lptr_t *blur_background_blacklist;
+ /// Blur convolution kernel.
+ struct conv **blur_kerns;
+ /// Number of convolution kernels
+ int blur_kernel_count;
+ /// How much to dim an inactive window. 0.0 - 1.0, 0 to disable.
+ double inactive_dim;
+ /// Whether to use fixed inactive dim opacity, instead of deciding
+ /// based on window opacity.
+ bool inactive_dim_fixed;
+ /// Conditions of windows to have inverted colors.
+ c2_lptr_t *invert_color_list;
+ /// Rules to change window opacity.
+ c2_lptr_t *opacity_rules;
+ /// Limit window brightness
+ double max_brightness;
+ // Radius of rounded window corners
+ int corner_radius;
+ /// Rounded corners blacklist. A linked list of conditions.
+ c2_lptr_t *rounded_corners_blacklist;
+
+ // === Focus related ===
+ /// Whether to try to detect WM windows and mark them as focused.
+ bool mark_wmwin_focused;
+ /// Whether to mark override-redirect windows as focused.
+ bool mark_ovredir_focused;
+ /// Whether to use EWMH _NET_ACTIVE_WINDOW to find active window.
+ bool use_ewmh_active_win;
+ /// A list of windows always to be considered focused.
+ c2_lptr_t *focus_blacklist;
+ /// Whether to do window grouping with <code>WM_TRANSIENT_FOR</code>.
+ bool detect_transient;
+ /// Whether to do window grouping with <code>WM_CLIENT_LEADER</code>.
+ bool detect_client_leader;
+
+ // === Calculated ===
+ /// Whether we need to track window leaders.
+ bool track_leader;
+
+ // Don't use EWMH to detect fullscreen applications
+ bool no_ewmh_fullscreen;
+
+ // Make transparent windows clip other windows, instead of blending on top of
+ // them
+ bool transparent_clipping;
+} options_t;
+
+extern const char *const BACKEND_STRS[NUM_BKEND + 1];
+
+bool must_use parse_long(const char *, long *);
+bool must_use parse_int(const char *, int *);
+struct conv **must_use parse_blur_kern_lst(const char *, bool *hasneg, int *count);
+bool must_use parse_geometry(session_t *, const char *, region_t *);
+bool must_use parse_rule_opacity(c2_lptr_t **, const char *);
+enum blur_method must_use parse_blur_method(const char *src);
+enum open_window_animation must_use parse_open_window_animation(const char *src);
+
+/**
+ * Add a pattern to a condition linked list.
+ */
+bool condlst_add(c2_lptr_t **, const char *);
+
+#ifdef CONFIG_LIBCONFIG
+/// Parse a configuration file
+/// Returns the actually config_file name used, allocated on heap
+/// Outputs:
+/// shadow_enable = whether shaodw is enabled globally
+/// fading_enable = whether fading is enabled globally
+/// win_option_mask = whether option overrides for specific window type is set for given
+/// options
+/// hasneg = whether the convolution kernel has negative values
+char *
+parse_config_libconfig(options_t *, const char *config_file, bool *shadow_enable,
+ bool *fading_enable, bool *hasneg, win_option_mask_t *winopt_mask);
+#endif
+
+void set_default_winopts(options_t *, win_option_mask_t *, bool shadow_enable,
+ bool fading_enable, bool blur_enable);
+/// Parse a configuration file is that is enabled, also initialize the winopt_mask with
+/// default values
+/// Outputs and returns:
+/// same as parse_config_libconfig
+char *parse_config(options_t *, const char *config_file, bool *shadow_enable,
+ bool *fading_enable, bool *hasneg, win_option_mask_t *winopt_mask);
+
+/**
+ * Parse a backend option argument.
+ */
+static inline attr_pure enum backend parse_backend(const char *str) {
+ for (enum backend i = 0; BACKEND_STRS[i]; ++i) {
+ if (!strcasecmp(str, BACKEND_STRS[i])) {
+ return i;
+ }
+ }
+ // Keep compatibility with an old revision containing a spelling mistake...
+ if (!strcasecmp(str, "xr_glx_hybird")) {
+ log_warn("backend xr_glx_hybird should be xr_glx_hybrid, the misspelt "
+ "version will be removed soon.");
+ return BKEND_XR_GLX_HYBRID;
+ }
+ // cju wants to use dashes
+ if (!strcasecmp(str, "xr-glx-hybrid")) {
+ log_warn("backend xr-glx-hybrid should be xr_glx_hybrid, the alternative "
+ "version will be removed soon.");
+ return BKEND_XR_GLX_HYBRID;
+ }
+ log_error("Invalid backend argument: %s", str);
+ return NUM_BKEND;
+}
+
+/**
+ * Parse a VSync option argument.
+ */
+static inline bool parse_vsync(const char *str) {
+ if (strcmp(str, "no") == 0 || strcmp(str, "none") == 0 ||
+ strcmp(str, "false") == 0 || strcmp(str, "nah") == 0) {
+ return false;
+ }
+ return true;
+}
+
+// vim: set noet sw=8 ts=8 :
diff --git a/src/config_libconfig.c b/src/config_libconfig.c
new file mode 100644
index 0000000..7d266cd
--- /dev/null
+++ b/src/config_libconfig.c
@@ -0,0 +1,784 @@
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2012-2014 Richard Grenville <[email protected]>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libconfig.h>
+#include <libgen.h>
+
+#include "common.h"
+#include "compiler.h"
+#include "config.h"
+#include "err.h"
+#include "log.h"
+#include "options.h"
+#include "string_utils.h"
+#include "utils.h"
+#include "win.h"
+
+#pragma GCC diagnostic error "-Wunused-parameter"
+
+/**
+ * Wrapper of libconfig's <code>config_lookup_int</code>.
+ *
+ * So it takes a pointer to bool.
+ */
+static inline int lcfg_lookup_bool(const config_t *config, const char *path, bool *value) {
+ int ival;
+
+ int ret = config_lookup_bool(config, path, &ival);
+ if (ret)
+ *value = ival;
+
+ return ret;
+}
+
+const char *xdg_config_home(void) {
+ char *xdgh = getenv("XDG_CONFIG_HOME");
+ char *home = getenv("HOME");
+ const char *default_dir = "/.config";
+
+ if (!xdgh) {
+ if (!home) {
+ return NULL;
+ }
+
+ xdgh = cvalloc(strlen(home) + strlen(default_dir) + 1);
+
+ strcpy(xdgh, home);
+ strcat(xdgh, default_dir);
+ } else {
+ xdgh = strdup(xdgh);
+ }
+
+ return xdgh;
+}
+
+char **xdg_config_dirs(void) {
+ char *xdgd = getenv("XDG_CONFIG_DIRS");
+ size_t count = 0;
+
+ if (!xdgd) {
+ xdgd = "/etc/xdg";
+ }
+
+ for (int i = 0; xdgd[i]; i++) {
+ if (xdgd[i] == ':') {
+ count++;
+ }
+ }
+
+ // Store the string and the result pointers together so they can be
+ // freed together
+ char **dir_list = cvalloc(sizeof(char *) * (count + 2) + strlen(xdgd) + 1);
+ auto dirs = strcpy((char *)dir_list + sizeof(char *) * (count + 2), xdgd);
+ auto path = dirs;
+
+ for (size_t i = 0; i < count; i++) {
+ dir_list[i] = path;
+ path = strchr(path, ':');
+ *path = '\0';
+ path++;
+ }
+ dir_list[count] = path;
+
+ size_t fill = 0;
+ for (size_t i = 0; i <= count; i++) {
+ if (dir_list[i][0] == '/') {
+ dir_list[fill] = dir_list[i];
+ fill++;
+ }
+ }
+
+ dir_list[fill] = NULL;
+
+ return dir_list;
+}
+
+TEST_CASE(xdg_config_dirs) {
+ auto old_var = getenv("XDG_CONFIG_DIRS");
+ if (old_var) {
+ old_var = strdup(old_var);
+ }
+ unsetenv("XDG_CONFIG_DIRS");
+
+ auto result = xdg_config_dirs();
+ TEST_STREQUAL(result[0], "/etc/xdg");
+ TEST_EQUAL(result[1], NULL);
+ free(result);
+
+ setenv("XDG_CONFIG_DIRS", ".:.:/etc/xdg:.:/:", 1);
+ result = xdg_config_dirs();
+ TEST_STREQUAL(result[0], "/etc/xdg");
+ TEST_STREQUAL(result[1], "/");
+ TEST_EQUAL(result[2], NULL);
+ free(result);
+
+ setenv("XDG_CONFIG_DIRS", ":", 1);
+ result = xdg_config_dirs();
+ TEST_EQUAL(result[0], NULL);
+ free(result);
+
+ if (old_var) {
+ setenv("XDG_CONFIG_DIRS", old_var, 1);
+ free(old_var);
+ }
+}
+
+/// Search for config file under a base directory
+FILE *open_config_file_at(const char *base, char **out_path) {
+ static const char *config_paths[] = {"/picom.conf", "/picom/picom.conf",
+ "/compton.conf", "/compton/compton.conf"};
+ for (size_t i = 0; i < ARR_SIZE(config_paths); i++) {
+ char *path = mstrjoin(base, config_paths[i]);
+ FILE *ret = fopen(path, "r");
+ if (ret && out_path) {
+ *out_path = path;
+ } else {
+ free(path);
+ }
+ if (ret) {
+ if (strstr(config_paths[i], "compton")) {
+ log_warn("This compositor has been renamed to \"picom\", "
+ "the old config file paths is deprecated. "
+ "Please replace the \"compton\"s in the path "
+ "with \"picom\"");
+ }
+ return ret;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * Get a file stream of the configuration file to read.
+ *
+ * Follows the XDG specification to search for the configuration file.
+ */
+FILE *open_config_file(const char *cpath, char **ppath) {
+ static const char config_filename_legacy[] = "/.compton.conf";
+
+ if (cpath) {
+ FILE *ret = fopen(cpath, "r");
+ if (ret && ppath)
+ *ppath = strdup(cpath);
+ return ret;
+ }
+
+ // First search for config file in user config directory
+ auto config_home = xdg_config_home();
+ auto ret = open_config_file_at(config_home, ppath);
+ free((void *)config_home);
+ if (ret) {
+ return ret;
+ }
+
+ // Fall back to legacy config file in user home directory
+ const char *home = getenv("HOME");
+ if (home && strlen(home)) {
+ auto path = mstrjoin(home, config_filename_legacy);
+ ret = fopen(path, "r");
+ if (ret && ppath) {
+ *ppath = path;
+ } else {
+ free(path);
+ }
+ if (ret) {
+ return ret;
+ }
+ }
+
+ // Fall back to config file in system config directory
+ auto config_dirs = xdg_config_dirs();
+ for (int i = 0; config_dirs[i]; i++) {
+ ret = open_config_file_at(config_dirs[i], ppath);
+ if (ret) {
+ free(config_dirs);
+ return ret;
+ }
+ }
+ free(config_dirs);
+
+ return NULL;
+}
+
+/**
+ * Parse a condition list in configuration file.
+ */
+void parse_cfg_condlst(const config_t *pcfg, c2_lptr_t **pcondlst, const char *name) {
+ config_setting_t *setting = config_lookup(pcfg, name);
+ if (setting) {
+ // Parse an array of options
+ if (config_setting_is_array(setting)) {
+ int i = config_setting_length(setting);
+ while (i--)
+ condlst_add(pcondlst,
+ config_setting_get_string_elem(setting, i));
+ }
+ // Treat it as a single pattern if it's a string
+ else if (CONFIG_TYPE_STRING == config_setting_type(setting)) {
+ condlst_add(pcondlst, config_setting_get_string(setting));
+ }
+ }
+}
+
+/**
+ * Parse an opacity rule list in configuration file.
+ */
+static inline void
+parse_cfg_condlst_opct(options_t *opt, const config_t *pcfg, const char *name) {
+ config_setting_t *setting = config_lookup(pcfg, name);
+ if (setting) {
+ // Parse an array of options
+ if (config_setting_is_array(setting)) {
+ int i = config_setting_length(setting);
+ while (i--)
+ if (!parse_rule_opacity(
+ &opt->opacity_rules,
+ config_setting_get_string_elem(setting, i)))
+ exit(1);
+ }
+ // Treat it as a single pattern if it's a string
+ else if (config_setting_type(setting) == CONFIG_TYPE_STRING) {
+ if (!parse_rule_opacity(&opt->opacity_rules,
+ config_setting_get_string(setting)))
+ exit(1);
+ }
+ }
+}
+
+static inline void parse_wintype_config(const config_t *cfg, const char *member_name,
+ win_option_t *o, win_option_mask_t *mask) {
+ char *str = mstrjoin("wintypes.", member_name);
+ const config_setting_t *setting = config_lookup(cfg, str);
+ free(str);
+
+ int ival = 0;
+ const char *sval = NULL;
+
+ if (setting) {
+ if (config_setting_lookup_bool(setting, "shadow", &ival)) {
+ o->shadow = ival;
+ mask->shadow = true;
+ }
+ if (config_setting_lookup_bool(setting, "fade", &ival)) {
+ o->fade = ival;
+ mask->fade = true;
+ }
+ if (config_setting_lookup_bool(setting, "focus", &ival)) {
+ o->focus = ival;
+ mask->focus = true;
+ }
+ if (config_setting_lookup_bool(setting, "blur-background", &ival)) {
+ o->blur_background = ival;
+ mask->blur_background = true;
+ }
+ if (config_setting_lookup_bool(setting, "full-shadow", &ival)) {
+ o->full_shadow = ival;
+ mask->full_shadow = true;
+ }
+ if (config_setting_lookup_bool(setting, "redir-ignore", &ival)) {
+ o->redir_ignore = ival;
+ mask->redir_ignore = true;
+ }
+ if (config_setting_lookup_bool(setting, "clip-shadow-above", &ival)) {
+ o->clip_shadow_above = ival;
+ mask->clip_shadow_above = true;
+ }
+ if (config_setting_lookup_string(setting, "animation", &sval)) {
+ enum open_window_animation animation = parse_open_window_animation(sval);
+ if (animation >= OPEN_WINDOW_ANIMATION_INVALID)
+ animation = OPEN_WINDOW_ANIMATION_NONE;
+
+ o->animation = animation;
+ mask->animation = OPEN_WINDOW_ANIMATION_INVALID;
+ }
+ if (config_setting_lookup_string(setting, "animation-unmap", &sval)) {
+ enum open_window_animation animation = parse_open_window_animation(sval);
+ if (animation >= OPEN_WINDOW_ANIMATION_INVALID)
+ animation = OPEN_WINDOW_ANIMATION_NONE;
+
+ o->animation_unmap = animation;
+ mask->animation_unmap = OPEN_WINDOW_ANIMATION_INVALID;
+ }
+ if (config_setting_lookup_string(setting, "animation-workspace-in", &sval)) {
+ enum open_window_animation animation = parse_open_window_animation(sval);
+ if (animation >= OPEN_WINDOW_ANIMATION_INVALID)
+ animation = OPEN_WINDOW_ANIMATION_NONE;
+
+ o->animation_workspace_in = animation;
+ mask->animation_workspace_in = OPEN_WINDOW_ANIMATION_INVALID;
+ }
+ if (config_setting_lookup_string(setting, "animation-workspace-out", &sval)) {
+ enum open_window_animation animation = parse_open_window_animation(sval);
+ if (animation >= OPEN_WINDOW_ANIMATION_INVALID)
+ animation = OPEN_WINDOW_ANIMATION_NONE;
+
+ o->animation_workspace_out = animation;
+ mask->animation_workspace_out = OPEN_WINDOW_ANIMATION_INVALID;
+ }
+
+ double fval;
+ if (config_setting_lookup_float(setting, "opacity", &fval)) {
+ o->opacity = normalize_d(fval);
+ mask->opacity = true;
+ }
+ }
+}
+
+/**
+ * Parse a configuration file from default location.
+ *
+ * Returns the actually config_file name
+ */
+char *parse_config_libconfig(options_t *opt, const char *config_file, bool *shadow_enable,
+ bool *fading_enable, bool *conv_kern_hasneg,
+ win_option_mask_t *winopt_mask) {
+ char *path = NULL;
+ FILE *f;
+ config_t cfg;
+ int ival = 0;
+ bool bval;
+ double dval = 0.0;
+ // libconfig manages string memory itself, so no need to manually free
+ // anything
+ const char *sval = NULL;
+
+ f = open_config_file(config_file, &path);
+ if (!f) {
+ free(path);
+ if (config_file) {
+ log_fatal("Failed to read configuration file \"%s\".", config_file);
+ return ERR_PTR(-1);
+ }
+ return NULL;
+ }
+
+ config_init(&cfg);
+#ifdef CONFIG_OPTION_ALLOW_OVERRIDES
+ config_set_options(&cfg, CONFIG_OPTION_ALLOW_OVERRIDES);
+#endif
+ {
+ // dirname() could modify the original string, thus we must pass a
+ // copy
+ char *path2 = strdup(path);
+ char *parent = dirname(path2);
+
+ if (parent)
+ config_set_include_dir(&cfg, parent);
+
+ free(path2);
+ }
+
+ {
+ int read_result = config_read(&cfg, f);
+ fclose(f);
+ f = NULL;
+ if (read_result == CONFIG_FALSE) {
+ log_fatal("Error when reading configuration file \"%s\", line "
+ "%d: %s",
+ path, config_error_line(&cfg), config_error_text(&cfg));
+ goto err;
+ }
+ }
+ config_set_auto_convert(&cfg, 1);
+
+ // Get options from the configuration file. We don't do range checking
+ // right now. It will be done later
+
+ // -D (fade_delta)
+ if (config_lookup_int(&cfg, "fade-delta", &ival))
+ opt->fade_delta = ival;
+ // -I (fade_in_step)
+ if (config_lookup_float(&cfg, "fade-in-step", &dval))
+ opt->fade_in_step = normalize_d(dval);
+ // -O (fade_out_step)
+ if (config_lookup_float(&cfg, "fade-out-step", &dval))
+ opt->fade_out_step = normalize_d(dval);
+ // -r (shadow_radius)
+ config_lookup_int(&cfg, "shadow-radius", &opt->shadow_radius);
+ // -o (shadow_opacity)
+ config_lookup_float(&cfg, "shadow-opacity", &opt->shadow_opacity);
+ // -l (shadow_offset_x)
+ config_lookup_int(&cfg, "shadow-offset-x", &opt->shadow_offset_x);
+ // -t (shadow_offset_y)
+ config_lookup_int(&cfg, "shadow-offset-y", &opt->shadow_offset_y);
+ // -i (inactive_opacity)
+ if (config_lookup_float(&cfg, "inactive-opacity", &dval))
+ opt->inactive_opacity = normalize_d(dval);
+ // --active_opacity
+ if (config_lookup_float(&cfg, "active-opacity", &dval))
+ opt->active_opacity = normalize_d(dval);
+ // --corner-radius
+ config_lookup_int(&cfg, "corner-radius", &opt->corner_radius);
+ // --rounded-corners-exclude
+ parse_cfg_condlst(&cfg, &opt->rounded_corners_blacklist, "rounded-corners-exclude");
+ // -e (frame_opacity)
+ config_lookup_float(&cfg, "frame-opacity", &opt->frame_opacity);
+ // -c (shadow_enable)
+ if (config_lookup_bool(&cfg, "shadow", &ival))
+ *shadow_enable = ival;
+ // -C (no_dock_shadow)
+ if (config_lookup_bool(&cfg, "no-dock-shadow", &ival)) {
+ log_error("Option `no-dock-shadow` has been removed. Please use the "
+ "wintype option `shadow` of `dock` instead.");
+ goto err;
+ }
+ // -G (no_dnd_shadow)
+ if (config_lookup_bool(&cfg, "no-dnd-shadow", &ival)) {
+ log_error("Option `no-dnd-shadow` has been removed. Please use the "
+ "wintype option `shadow` of `dnd` instead.");
+ goto err;
+ };
+ // -m (menu_opacity)
+ if (config_lookup_float(&cfg, "menu-opacity", &dval)) {
+ log_warn("Option `menu-opacity` is deprecated, and will be "
+ "removed.Please use the "
+ "wintype option `opacity` of `popup_menu` and `dropdown_menu` "
+ "instead.");
+ opt->wintype_option[WINTYPE_DROPDOWN_MENU].opacity = dval;
+ opt->wintype_option[WINTYPE_POPUP_MENU].opacity = dval;
+ winopt_mask[WINTYPE_DROPDOWN_MENU].opacity = true;
+ winopt_mask[WINTYPE_POPUP_MENU].opacity = true;
+ }
+ // -f (fading_enable)
+ if (config_lookup_bool(&cfg, "fading", &ival))
+ *fading_enable = ival;
+ // --no-fading-open-close
+ lcfg_lookup_bool(&cfg, "no-fading-openclose", &opt->no_fading_openclose);
+ // --no-fading-destroyed-argb
+ lcfg_lookup_bool(&cfg, "no-fading-destroyed-argb", &opt->no_fading_destroyed_argb);
+ // --shadow-red
+ config_lookup_float(&cfg, "shadow-red", &opt->shadow_red);
+ // --shadow-green
+ config_lookup_float(&cfg, "shadow-green", &opt->shadow_green);
+ // --shadow-blue
+ config_lookup_float(&cfg, "shadow-blue", &opt->shadow_blue);
+ // --shadow-color
+ if (config_lookup_string(&cfg, "shadow-color", &sval)) {
+ struct color rgb;
+ rgb = hex_to_rgb(sval);
+ opt->shadow_red = rgb.red;
+ opt->shadow_green = rgb.green;
+ opt->shadow_blue = rgb.blue;
+ }
+ // --shadow-exclude-reg
+ if (config_lookup_string(&cfg, "shadow-exclude-reg", &sval))
+ opt->shadow_exclude_reg_str = strdup(sval);
+ // --inactive-opacity-override
+ lcfg_lookup_bool(&cfg, "inactive-opacity-override", &opt->inactive_opacity_override);
+ // --inactive-dim
+ config_lookup_float(&cfg, "inactive-dim", &opt->inactive_dim);
+ // --mark-wmwin-focused
+ lcfg_lookup_bool(&cfg, "mark-wmwin-focused", &opt->mark_wmwin_focused);
+ // --mark-ovredir-focused
+ lcfg_lookup_bool(&cfg, "mark-ovredir-focused", &opt->mark_ovredir_focused);
+ // --shadow-ignore-shaped
+ lcfg_lookup_bool(&cfg, "shadow-ignore-shaped", &opt->shadow_ignore_shaped);
+ // --detect-rounded-corners
+ lcfg_lookup_bool(&cfg, "detect-rounded-corners", &opt->detect_rounded_corners);
+ // --xinerama-shadow-crop
+ lcfg_lookup_bool(&cfg, "xinerama-shadow-crop", &opt->xinerama_shadow_crop);
+ // --detect-client-opacity
+ lcfg_lookup_bool(&cfg, "detect-client-opacity", &opt->detect_client_opacity);
+ // --refresh-rate
+ if (config_lookup_int(&cfg, "refresh-rate", &opt->refresh_rate)) {
+ if (opt->refresh_rate < 0) {
+ log_warn("Invalid refresh rate %d, fallback to 0", opt->refresh_rate);
+ opt->refresh_rate = 0;
+ }
+ }
+ // --vsync
+ if (config_lookup_string(&cfg, "vsync", &sval)) {
+ opt->vsync = parse_vsync(sval);
+ log_warn("vsync option will take a boolean from now on. \"%s\" is "
+ "interpreted as \"%s\" for compatibility, but this will stop "
+ "working soon",
+ sval, opt->vsync ? "true" : "false");
+ }
+ lcfg_lookup_bool(&cfg, "vsync", &opt->vsync);
+ // --backend
+ if (config_lookup_string(&cfg, "backend", &sval)) {
+ opt->backend = parse_backend(sval);
+ if (opt->backend >= NUM_BKEND) {
+ log_fatal("Cannot parse backend");
+ goto err;
+ }
+ }
+ // --log-level
+ if (config_lookup_string(&cfg, "log-level", &sval)) {
+ auto level = string_to_log_level(sval);
+ if (level == LOG_LEVEL_INVALID) {
+ log_warn("Invalid log level, defaults to WARN");
+ } else {
+ log_set_level_tls(level);
+ }
+ }
+ // --log-file
+ if (config_lookup_string(&cfg, "log-file", &sval)) {
+ if (*sval != '/') {
+ log_warn("The log-file in your configuration file is not an "
+ "absolute path");
+ }
+ opt->logpath = strdup(sval);
+ }
+ // --sw-opti
+ lcfg_lookup_bool(&cfg, "sw-opti", &opt->sw_opti);
+ // --use-ewmh-active-win
+ lcfg_lookup_bool(&cfg, "use-ewmh-active-win", &opt->use_ewmh_active_win);
+ // --unredir-if-possible
+ lcfg_lookup_bool(&cfg, "unredir-if-possible", &opt->unredir_if_possible);
+ // --unredir-if-possible-delay
+ if (config_lookup_int(&cfg, "unredir-if-possible-delay", &ival)) {
+ if (ival < 0) {
+ log_warn("Invalid unredir-if-possible-delay %d", ival);
+ } else {
+ opt->unredir_if_possible_delay = ival;
+ }
+ }
+ // --inactive-dim-fixed
+ lcfg_lookup_bool(&cfg, "inactive-dim-fixed", &opt->inactive_dim_fixed);
+ // --detect-transient
+ lcfg_lookup_bool(&cfg, "detect-transient", &opt->detect_transient);
+ // --detect-client-leader
+ lcfg_lookup_bool(&cfg, "detect-client-leader", &opt->detect_client_leader);
+ // --no-ewmh-fullscreen
+ lcfg_lookup_bool(&cfg, "no-ewmh-fullscreen", &opt->no_ewmh_fullscreen);
+ // --transparent-clipping
+ lcfg_lookup_bool(&cfg, "transparent-clipping", &opt->transparent_clipping);
+ // --shadow-exclude
+ parse_cfg_condlst(&cfg, &opt->shadow_blacklist, "shadow-exclude");
+ // --clip-shadow-above
+ parse_cfg_condlst(&cfg, &opt->shadow_clip_list, "clip-shadow-above");
+ // --fade-exclude
+ parse_cfg_condlst(&cfg, &opt->fade_blacklist, "fade-exclude");
+ // --animations
+ lcfg_lookup_bool(&cfg, "animations", &opt->animations);
+ // --animation-for-open-window
+ if (config_lookup_string(&cfg, "animation-for-open-window", &sval)) {
+ enum open_window_animation animation = parse_open_window_animation(sval);
+ if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
+ log_fatal("Invalid open-window animation %s", sval);
+ goto err;
+ }
+ opt->animation_for_open_window = animation;
+ }
+ // --animation-for-transient-window
+ if (config_lookup_string(&cfg, "animation-for-transient-window", &sval)) {
+ enum open_window_animation animation = parse_open_window_animation(sval);
+ if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
+ log_fatal("Invalid transient-window animation %s", sval);
+ goto err;
+ }
+ opt->animation_for_transient_window = animation;
+ }
+ // --animation-for-unmap-window
+ if (config_lookup_string(&cfg, "animation-for-unmap-window", &sval)) {
+ enum open_window_animation animation = parse_open_window_animation(sval);
+ if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
+ log_fatal("Invalid unmap-window animation %s", sval);
+ goto err;
+ }
+ opt->animation_for_unmap_window = animation;
+ }
+ // --animation-for-workspace-switch-in
+ if (config_lookup_string(&cfg, "animation-for-workspace-switch-in", &sval)) {
+ enum open_window_animation animation = parse_open_window_animation(sval);
+ if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
+ log_fatal("Invalid workspace-switch-in animation %s", sval);
+ goto err;
+ }
+ opt->animation_for_workspace_switch_in = animation;
+ }
+ // --animation-for-workspace-switch-out
+ if (config_lookup_string(&cfg, "animation-for-workspace-switch-out", &sval)) {
+ enum open_window_animation animation = parse_open_window_animation(sval);
+ if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
+ log_fatal("Invalid workspace-switch-out animation %s", sval);
+ goto err;
+ }
+ opt->animation_for_workspace_switch_out = animation;
+ }
+ // --animation-stiffness
+ config_lookup_float(&cfg, "animation-stiffness", &opt->animation_stiffness);
+ // --animation-window-mass
+ config_lookup_float(&cfg, "animation-window-mass", &opt->animation_window_mass);
+ // --animation-dampening
+ config_lookup_float(&cfg, "animation-dampening", &opt->animation_dampening);
+ // --animation-delta
+ config_lookup_float(&cfg, "animation-delta", &opt->animation_delta);
+ // --animation-force-steps
+ lcfg_lookup_bool(&cfg, "animation-force-steps", &opt->animation_force_steps);
+ // --animation-clamping
+ lcfg_lookup_bool(&cfg, "animation-clamping", &opt->animation_clamping);
+ // --focus-exclude
+ parse_cfg_condlst(&cfg, &opt->focus_blacklist, "focus-exclude");
+ // --invert-color-include
+ parse_cfg_condlst(&cfg, &opt->invert_color_list, "invert-color-include");
+ // --blur-background-exclude
+ parse_cfg_condlst(&cfg, &opt->blur_background_blacklist, "blur-background-exclude");
+ // --opacity-rule
+ parse_cfg_condlst_opct(opt, &cfg, "opacity-rule");
+ // --unredir-if-possible-exclude
+ parse_cfg_condlst(&cfg, &opt->unredir_if_possible_blacklist,
+ "unredir-if-possible-exclude");
+ // --blur-method
+ if (config_lookup_string(&cfg, "blur-method", &sval)) {
+ enum blur_method method = parse_blur_method(sval);
+ if (method >= BLUR_METHOD_INVALID) {
+ log_fatal("Invalid blur method %s", sval);
+ goto err;
+ }
+ opt->blur_method = method;
+ }
+ // --blur-size
+ config_lookup_int(&cfg, "blur-size", &opt->blur_radius);
+ // --blur-deviation
+ config_lookup_float(&cfg, "blur-deviation", &opt->blur_deviation);
+ // --blur-strength
+ config_lookup_int(&cfg, "blur-strength", &opt->blur_strength);
+ // --blur-background
+ if (config_lookup_bool(&cfg, "blur-background", &ival) && ival) {
+ if (opt->blur_method == BLUR_METHOD_NONE) {
+ opt->blur_method = BLUR_METHOD_KERNEL;
+ }
+ }
+ // --blur-background-frame
+ lcfg_lookup_bool(&cfg, "blur-background-frame", &opt->blur_background_frame);
+ // --blur-background-fixed
+ lcfg_lookup_bool(&cfg, "blur-background-fixed", &opt->blur_background_fixed);
+ // --blur-kern
+ if (config_lookup_string(&cfg, "blur-kern", &sval)) {
+ opt->blur_kerns =
+ parse_blur_kern_lst(sval, conv_kern_hasneg, &opt->blur_kernel_count);
+ if (!opt->blur_kerns) {
+ log_fatal("Cannot parse \"blur-kern\"");
+ goto err;
+ }
+ }
+ // --resize-damage
+ config_lookup_int(&cfg, "resize-damage", &opt->resize_damage);
+ // --glx-no-stencil
+ lcfg_lookup_bool(&cfg, "glx-no-stencil", &opt->glx_no_stencil);
+ // --glx-no-rebind-pixmap
+ lcfg_lookup_bool(&cfg, "glx-no-rebind-pixmap", &opt->glx_no_rebind_pixmap);
+ lcfg_lookup_bool(&cfg, "force-win-blend", &opt->force_win_blend);
+ // --glx-swap-method
+ if (config_lookup_string(&cfg, "glx-swap-method", &sval)) {
+ char *endptr;
+ long val = strtol(sval, &endptr, 10);
+ if (*endptr || !(*sval)) {
+ // sval is not a number, or an empty string
+ val = -1;
+ }
+ if (strcmp(sval, "undefined") != 0 && val != 0) {
+ // If not undefined, we will use damage and buffer-age to limit
+ // the rendering area.
+ opt->use_damage = true;
+ }
+ log_warn("glx-swap-method has been deprecated since v6, your setting "
+ "\"%s\" should be %s.",
+ sval,
+ opt->use_damage ? "replaced by `use-damage = true`" : "removed");
+ }
+ // --use-damage
+ lcfg_lookup_bool(&cfg, "use-damage", &opt->use_damage);
+
+ // --max-brightness
+ if (config_lookup_float(&cfg, "max-brightness", &opt->max_brightness) &&
+ opt->use_damage && opt->max_brightness < 1) {
+ log_warn("max-brightness requires use-damage = false. Falling back to "
+ "1.0");
+ opt->max_brightness = 1.0;
+ }
+
+ // --glx-use-gpushader4
+ if (config_lookup_bool(&cfg, "glx-use-gpushader4", &ival) && ival) {
+ log_warn("glx-use-gpushader4 is deprecated since v6, please remove it "
+ "from"
+ "your config file");
+ }
+ // --xrender-sync
+ if (config_lookup_bool(&cfg, "xrender-sync", &ival) && ival) {
+ log_error("Please use xrender-sync-fence instead of xrender-sync.");
+ goto err;
+ }
+ // --xrender-sync-fence
+ lcfg_lookup_bool(&cfg, "xrender-sync-fence", &opt->xrender_sync_fence);
+
+ if (lcfg_lookup_bool(&cfg, "clear-shadow", &bval))
+ log_warn("\"clear-shadow\" is removed as an option, and is always"
+ " enabled now. Consider removing it from your config file");
+ if (lcfg_lookup_bool(&cfg, "paint-on-overlay", &bval)) {
+ log_error("\"paint-on-overlay\" has been removed as an option, and "
+ "the feature is enabled whenever possible");
+ goto err;
+ }
+
+ if (config_lookup_float(&cfg, "alpha-step", &dval)) {
+ log_error("\"alpha-step\" has been removed, compton now tries to make use"
+ " of all alpha values");
+ goto err;
+ }
+
+ const char *deprecation_message attr_unused =
+ "has been removed. If you encounter problems "
+ "without this feature, please feel free to open a bug report";
+
+ config_setting_t *blur_cfg = config_lookup(&cfg, "blur");
+ if (blur_cfg) {
+ if (config_setting_lookup_string(blur_cfg, "method", &sval)) {
+ enum blur_method method = parse_blur_method(sval);
+ if (method >= BLUR_METHOD_INVALID) {
+ log_warn("Invalid blur method %s, ignoring.", sval);
+ } else {
+ opt->blur_method = method;
+ }
+ }
+
+ config_setting_lookup_int(blur_cfg, "size", &opt->blur_radius);
+
+ if (config_setting_lookup_string(blur_cfg, "kernel", &sval)) {
+ opt->blur_kerns = parse_blur_kern_lst(sval, conv_kern_hasneg,
+ &opt->blur_kernel_count);
+ if (!opt->blur_kerns) {
+ log_warn("Failed to parse blur kernel: %s", sval);
+ }
+ }
+
+ config_setting_lookup_float(blur_cfg, "deviation", &opt->blur_deviation);
+ config_setting_lookup_int(blur_cfg, "strength", &opt->blur_strength);
+ }
+
+ // --write-pid-path
+ if (config_lookup_string(&cfg, "write-pid-path", &sval)) {
+ if (*sval != '/') {
+ log_warn("The write-pid-path in your configuration file is not"
+ " an absolute path");
+ }
+ opt->write_pid_path = strdup(sval);
+ }
+
+ // Wintype settings
+
+ // XXX ! Refactor all the wintype_* arrays into a struct
+ for (wintype_t i = 0; i < NUM_WINTYPES; ++i) {
+ parse_wintype_config(&cfg, WINTYPES[i], &opt->wintype_option[i],
+ &winopt_mask[i]);
+ }
+
+ // Compatibility with the old name for notification windows.
+ parse_wintype_config(&cfg, "notify", &opt->wintype_option[WINTYPE_NOTIFICATION],
+ &winopt_mask[WINTYPE_NOTIFICATION]);
+
+ config_destroy(&cfg);
+ return path;
+
+err:
+ config_destroy(&cfg);
+ free(path);
+ return ERR_PTR(-1);
+}
diff --git a/src/dbus.c b/src/dbus.c
new file mode 100644
index 0000000..8d6094e
--- /dev/null
+++ b/src/dbus.c
@@ -0,0 +1,1340 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Compton - a compositor for X11
+ *
+ * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
+ *
+ * Copyright (c) 2011-2013, Christopher Jeffrey
+ * See LICENSE-mit for more information.
+ *
+ */
+
+#include <X11/Xlib.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <xcb/xcb.h>
+
+#include "common.h"
+#include "compiler.h"
+#include "config.h"
+#include "list.h"
+#include "log.h"
+#include "string_utils.h"
+#include "types.h"
+#include "uthash_extra.h"
+#include "utils.h"
+#include "win.h"
+
+#include "dbus.h"
+
+struct cdbus_data {
+ /// DBus connection.
+ DBusConnection *dbus_conn;
+ /// DBus service name.
+ char *dbus_service;
+};
+
+// Window type
+typedef uint32_t cdbus_window_t;
+#define CDBUS_TYPE_WINDOW DBUS_TYPE_UINT32
+#define CDBUS_TYPE_WINDOW_STR DBUS_TYPE_UINT32_AS_STRING
+
+typedef uint32_t cdbus_enum_t;
+#define CDBUS_TYPE_ENUM DBUS_TYPE_UINT32
+#define CDBUS_TYPE_ENUM_STR DBUS_TYPE_UINT32_AS_STRING
+
+#define CDBUS_SERVICE_NAME "com.github.chjj.compton"
+#define CDBUS_INTERFACE_NAME CDBUS_SERVICE_NAME
+#define CDBUS_OBJECT_NAME "/com/github/chjj/compton"
+#define CDBUS_ERROR_PREFIX CDBUS_INTERFACE_NAME ".error"
+#define CDBUS_ERROR_UNKNOWN CDBUS_ERROR_PREFIX ".unknown"
+#define CDBUS_ERROR_UNKNOWN_S "Well, I don't know what happened. Do you?"
+#define CDBUS_ERROR_BADMSG CDBUS_ERROR_PREFIX ".bad_message"
+#define CDBUS_ERROR_BADMSG_S \
+ "Unrecognized command. Beware compton " \
+ "cannot make you a sandwich."
+#define CDBUS_ERROR_BADARG CDBUS_ERROR_PREFIX ".bad_argument"
+#define CDBUS_ERROR_BADARG_S "Failed to parse argument %d: %s"
+#define CDBUS_ERROR_BADWIN CDBUS_ERROR_PREFIX ".bad_window"
+#define CDBUS_ERROR_BADWIN_S "Requested window %#010x not found."
+#define CDBUS_ERROR_BADTGT CDBUS_ERROR_PREFIX ".bad_target"
+#define CDBUS_ERROR_BADTGT_S "Target \"%s\" not found."
+#define CDBUS_ERROR_FORBIDDEN CDBUS_ERROR_PREFIX ".forbidden"
+#define CDBUS_ERROR_FORBIDDEN_S "Incorrect password, access denied."
+#define CDBUS_ERROR_CUSTOM CDBUS_ERROR_PREFIX ".custom"
+#define CDBUS_ERROR_CUSTOM_S "%s"
+
+#define cdbus_reply_err(ps, srcmsg, err_name, err_format, ...) \
+ cdbus_reply_errm((ps), dbus_message_new_error_printf( \
+ (srcmsg), (err_name), (err_format), ##__VA_ARGS__))
+
+static DBusHandlerResult cdbus_process(DBusConnection *conn, DBusMessage *m, void *);
+
+static dbus_bool_t cdbus_callback_add_timeout(DBusTimeout *timeout, void *data);
+
+static void cdbus_callback_remove_timeout(DBusTimeout *timeout, void *data);
+
+static void cdbus_callback_timeout_toggled(DBusTimeout *timeout, void *data);
+
+static dbus_bool_t cdbus_callback_add_watch(DBusWatch *watch, void *data);
+
+static void cdbus_callback_remove_watch(DBusWatch *watch, void *data);
+
+static void cdbus_callback_watch_toggled(DBusWatch *watch, void *data);
+
+/**
+ * Initialize D-Bus connection.
+ */
+bool cdbus_init(session_t *ps, const char *uniq) {
+ auto cd = cmalloc(struct cdbus_data);
+ cd->dbus_service = NULL;
+
+ // Set ps->dbus_data here because add_watch functions need it
+ ps->dbus_data = cd;
+
+ DBusError err = {};
+
+ // Initialize
+ dbus_error_init(&err);
+
+ // Connect to D-Bus
+ // Use dbus_bus_get_private() so we can fully recycle it ourselves
+ cd->dbus_conn = dbus_bus_get_private(DBUS_BUS_SESSION, &err);
+ if (dbus_error_is_set(&err)) {
+ log_error("D-Bus connection failed (%s).", err.message);
+ dbus_error_free(&err);
+ goto fail;
+ }
+
+ if (!cd->dbus_conn) {
+ log_error("D-Bus connection failed for unknown reason.");
+ goto fail;
+ }
+
+ // Avoid exiting on disconnect
+ dbus_connection_set_exit_on_disconnect(cd->dbus_conn, false);
+
+ // Request service name
+ {
+ // Build service name
+ size_t service_len = strlen(CDBUS_SERVICE_NAME) + strlen(uniq) + 2;
+ char *service = ccalloc(service_len, char);
+ snprintf(service, service_len, "%s.%s", CDBUS_SERVICE_NAME, uniq);
+
+ // Make a valid dbus name by converting non alphanumeric characters to
+ // underscore
+ char *tmp = service + strlen(CDBUS_SERVICE_NAME) + 1;
+ while (*tmp) {
+ if (!isalnum((unsigned char)*tmp)) {
+ *tmp = '_';
+ }
+ tmp++;
+ }
+ cd->dbus_service = service;
+
+ // Request for the name
+ int ret = dbus_bus_request_name(cd->dbus_conn, service,
+ DBUS_NAME_FLAG_DO_NOT_QUEUE, &err);
+
+ if (dbus_error_is_set(&err)) {
+ log_error("Failed to obtain D-Bus name (%s).", err.message);
+ dbus_error_free(&err);
+ goto fail;
+ }
+
+ if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret &&
+ DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER != ret) {
+ log_error("Failed to become the primary owner of requested D-Bus "
+ "name (%d).",
+ ret);
+ goto fail;
+ }
+ }
+
+ // Add watch handlers
+ if (!dbus_connection_set_watch_functions(cd->dbus_conn, cdbus_callback_add_watch,
+ cdbus_callback_remove_watch,
+ cdbus_callback_watch_toggled, ps, NULL)) {
+ log_error("Failed to add D-Bus watch functions.");
+ goto fail;
+ }
+
+ // Add timeout handlers
+ if (!dbus_connection_set_timeout_functions(
+ cd->dbus_conn, cdbus_callback_add_timeout, cdbus_callback_remove_timeout,
+ cdbus_callback_timeout_toggled, ps, NULL)) {
+ log_error("Failed to add D-Bus timeout functions.");
+ goto fail;
+ }
+
+ // Add match
+ dbus_bus_add_match(cd->dbus_conn,
+ "type='method_call',interface='" CDBUS_INTERFACE_NAME "'", &err);
+ if (dbus_error_is_set(&err)) {
+ log_error("Failed to add D-Bus match.");
+ dbus_error_free(&err);
+ goto fail;
+ }
+ dbus_connection_add_filter(cd->dbus_conn, cdbus_process, ps, NULL);
+ return true;
+fail:
+ ps->dbus_data = NULL;
+ free(cd->dbus_service);
+ free(cd);
+ return false;
+}
+
+/**
+ * Destroy D-Bus connection.
+ */
+void cdbus_destroy(session_t *ps) {
+ struct cdbus_data *cd = ps->dbus_data;
+ if (cd->dbus_conn) {
+ // Release DBus name firstly
+ if (cd->dbus_service) {
+ DBusError err = {};
+ dbus_error_init(&err);
+
+ dbus_bus_release_name(cd->dbus_conn, cd->dbus_service, &err);
+ if (dbus_error_is_set(&err)) {
+ log_error("Failed to release DBus name (%s).", err.message);
+ dbus_error_free(&err);
+ }
+ free(cd->dbus_service);
+ }
+
+ // Close and unref the connection
+ dbus_connection_close(cd->dbus_conn);
+ dbus_connection_unref(cd->dbus_conn);
+ }
+ free(cd);
+}
+
+/** @name DBusTimeout handling
+ */
+///@{
+
+typedef struct ev_dbus_timer {
+ ev_timer w;
+ DBusTimeout *t;
+} ev_dbus_timer;
+
+/**
+ * Callback for handling a D-Bus timeout.
+ */
+static void
+cdbus_callback_handle_timeout(EV_P attr_unused, ev_timer *w, int revents attr_unused) {
+ ev_dbus_timer *t = (void *)w;
+ dbus_timeout_handle(t->t);
+}
+
+/**
+ * Callback for adding D-Bus timeout.
+ */
+static dbus_bool_t cdbus_callback_add_timeout(DBusTimeout *timeout, void *data) {
+ session_t *ps = data;
+
+ auto t = ccalloc(1, ev_dbus_timer);
+ double i = dbus_timeout_get_interval(timeout) / 1000.0;
+ ev_timer_init(&t->w, cdbus_callback_handle_timeout, i, i);
+ t->t = timeout;
+ dbus_timeout_set_data(timeout, t, NULL);
+
+ if (dbus_timeout_get_enabled(timeout))
+ ev_timer_start(ps->loop, &t->w);
+
+ return true;
+}
+
+/**
+ * Callback for removing D-Bus timeout.
+ */
+static void cdbus_callback_remove_timeout(DBusTimeout *timeout, void *data) {
+ session_t *ps = data;
+
+ ev_dbus_timer *t = dbus_timeout_get_data(timeout);
+ assert(t);
+ ev_timer_stop(ps->loop, &t->w);
+ free(t);
+}
+
+/**
+ * Callback for toggling a D-Bus timeout.
+ */
+static void cdbus_callback_timeout_toggled(DBusTimeout *timeout, void *data) {
+ session_t *ps = data;
+ ev_dbus_timer *t = dbus_timeout_get_data(timeout);
+
+ assert(t);
+ ev_timer_stop(ps->loop, &t->w);
+ if (dbus_timeout_get_enabled(timeout)) {
+ double i = dbus_timeout_get_interval(timeout) / 1000.0;
+ ev_timer_set(&t->w, i, i);
+ ev_timer_start(ps->loop, &t->w);
+ }
+}
+
+///@}
+
+/** @name DBusWatch handling
+ */
+///@{
+
+typedef struct ev_dbus_io {
+ ev_io w;
+ struct cdbus_data *cd;
+ DBusWatch *dw;
+} ev_dbus_io;
+
+void cdbus_io_callback(EV_P attr_unused, ev_io *w, int revents) {
+ ev_dbus_io *dw = (void *)w;
+ DBusWatchFlags flags = 0;
+ if (revents & EV_READ)
+ flags |= DBUS_WATCH_READABLE;
+ if (revents & EV_WRITE)
+ flags |= DBUS_WATCH_WRITABLE;
+ dbus_watch_handle(dw->dw, flags);
+ while (dbus_connection_dispatch(dw->cd->dbus_conn) != DBUS_DISPATCH_COMPLETE)
+ ;
+}
+
+/**
+ * Determine the poll condition of a DBusWatch.
+ */
+static inline int cdbus_get_watch_cond(DBusWatch *watch) {
+ const unsigned flags = dbus_watch_get_flags(watch);
+ int condition = 0;
+ if (flags & DBUS_WATCH_READABLE)
+ condition |= EV_READ;
+ if (flags & DBUS_WATCH_WRITABLE)
+ condition |= EV_WRITE;
+
+ return condition;
+}
+
+/**
+ * Callback for adding D-Bus watch.
+ */
+static dbus_bool_t cdbus_callback_add_watch(DBusWatch *watch, void *data) {
+ session_t *ps = data;
+
+ auto w = ccalloc(1, ev_dbus_io);
+ w->dw = watch;
+ w->cd = ps->dbus_data;
+ ev_io_init(&w->w, cdbus_io_callback, dbus_watch_get_unix_fd(watch),
+ cdbus_get_watch_cond(watch));
+
+ // Leave disabled watches alone
+ if (dbus_watch_get_enabled(watch))
+ ev_io_start(ps->loop, &w->w);
+
+ dbus_watch_set_data(watch, w, NULL);
+
+ // Always return true
+ return true;
+}
+
+/**
+ * Callback for removing D-Bus watch.
+ */
+static void cdbus_callback_remove_watch(DBusWatch *watch, void *data) {
+ session_t *ps = data;
+ ev_dbus_io *w = dbus_watch_get_data(watch);
+ ev_io_stop(ps->loop, &w->w);
+ free(w);
+}
+
+/**
+ * Callback for toggling D-Bus watch status.
+ */
+static void cdbus_callback_watch_toggled(DBusWatch *watch, void *data) {
+ session_t *ps = data;
+ ev_io *w = dbus_watch_get_data(watch);
+ if (dbus_watch_get_enabled(watch))
+ ev_io_start(ps->loop, w);
+ else
+ ev_io_stop(ps->loop, w);
+}
+
+///@}
+
+/** @name Message argument appending callbacks
+ */
+///@{
+
+/**
+ * Callback to append a bool argument to a message.
+ */
+static bool cdbus_apdarg_bool(session_t *ps attr_unused, DBusMessage *msg, const void *data) {
+ assert(data);
+
+ dbus_bool_t val = *(const bool *)data;
+
+ if (!dbus_message_append_args(msg, DBUS_TYPE_BOOLEAN, &val, DBUS_TYPE_INVALID)) {
+ log_error("Failed to append argument.");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Callback to append an int32 argument to a message.
+ */
+static bool cdbus_apdarg_int32(session_t *ps attr_unused, DBusMessage *msg, const void *data) {
+ if (!dbus_message_append_args(msg, DBUS_TYPE_INT32, data, DBUS_TYPE_INVALID)) {
+ log_error("Failed to append argument.");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Callback to append an uint32 argument to a message.
+ */
+static bool
+cdbus_apdarg_uint32(session_t *ps attr_unused, DBusMessage *msg, const void *data) {
+ if (!dbus_message_append_args(msg, DBUS_TYPE_UINT32, data, DBUS_TYPE_INVALID)) {
+ log_error("Failed to append argument.");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Callback to append a double argument to a message.
+ */
+static bool
+cdbus_apdarg_double(session_t *ps attr_unused, DBusMessage *msg, const void *data) {
+ if (!dbus_message_append_args(msg, DBUS_TYPE_DOUBLE, data, DBUS_TYPE_INVALID)) {
+ log_error("Failed to append argument.");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Callback to append a Window argument to a message.
+ */
+static bool cdbus_apdarg_wid(session_t *ps attr_unused, DBusMessage *msg, const void *data) {
+ assert(data);
+ cdbus_window_t val = *(const xcb_window_t *)data;
+
+ if (!dbus_message_append_args(msg, CDBUS_TYPE_WINDOW, &val, DBUS_TYPE_INVALID)) {
+ log_error("Failed to append argument.");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Callback to append an cdbus_enum_t argument to a message.
+ */
+static bool cdbus_apdarg_enum(session_t *ps attr_unused, DBusMessage *msg, const void *data) {
+ assert(data);
+ if (!dbus_message_append_args(msg, CDBUS_TYPE_ENUM, data, DBUS_TYPE_INVALID)) {
+ log_error("Failed to append argument.");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Callback to append a string argument to a message.
+ */
+static bool
+cdbus_apdarg_string(session_t *ps attr_unused, DBusMessage *msg, const void *data) {
+ const char *str = data;
+ if (!str)
+ str = "";
+
+ if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &str, DBUS_TYPE_INVALID)) {
+ log_error("Failed to append argument.");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Callback to append all window IDs to a message.
+ */
+static bool cdbus_apdarg_wids(session_t *ps, DBusMessage *msg, const void *data attr_unused) {
+ // Get the number of wids we are to include
+ unsigned count = 0;
+ HASH_ITER2(ps->windows, w) {
+ assert(!w->destroyed);
+ ++count;
+ }
+
+ if (!count) {
+ // Nothing to append
+ return true;
+ }
+
+ // Allocate memory for an array of window IDs
+ auto arr = ccalloc(count, cdbus_window_t);
+
+ // Build the array
+ cdbus_window_t *pcur = arr;
+ HASH_ITER2(ps->windows, w) {
+ assert(!w->destroyed);
+ *pcur = w->id;
+ ++pcur;
+ }
+ assert(pcur == arr + count);
+
+ // Append arguments
+ if (!dbus_message_append_args(msg, DBUS_TYPE_ARRAY, CDBUS_TYPE_WINDOW, &arr,
+ count, DBUS_TYPE_INVALID)) {
+ log_error("Failed to append argument.");
+ free(arr);
+ return false;
+ }
+
+ free(arr);
+ return true;
+}
+///@}
+
+/**
+ * Send a D-Bus signal.
+ *
+ * @param ps current session
+ * @param name signal name
+ * @param func a function that modifies the built message, to, for example,
+ * add an argument
+ * @param data data pointer to pass to the function
+ */
+static bool cdbus_signal(session_t *ps, const char *name,
+ bool (*func)(session_t *ps, DBusMessage *msg, const void *data),
+ const void *data) {
+ struct cdbus_data *cd = ps->dbus_data;
+ DBusMessage *msg = NULL;
+
+ // Create a signal
+ msg = dbus_message_new_signal(CDBUS_OBJECT_NAME, CDBUS_INTERFACE_NAME, name);
+ if (!msg) {
+ log_error("Failed to create D-Bus signal.");
+ return false;
+ }
+
+ // Append arguments onto message
+ if (func && !func(ps, msg, data)) {
+ dbus_message_unref(msg);
+ return false;
+ }
+
+ // Send the message and flush the connection
+ if (!dbus_connection_send(cd->dbus_conn, msg, NULL)) {
+ log_error("Failed to send D-Bus signal.");
+ dbus_message_unref(msg);
+ return false;
+ }
+ dbus_connection_flush(cd->dbus_conn);
+
+ // Free the message
+ dbus_message_unref(msg);
+
+ return true;
+}
+
+/**
+ * Send a signal with a Window ID as argument.
+ */
+static inline bool cdbus_signal_wid(session_t *ps, const char *name, xcb_window_t wid) {
+ return cdbus_signal(ps, name, cdbus_apdarg_wid, &wid);
+}
+
+/**
+ * Send a D-Bus reply.
+ *
+ * @param ps current session
+ * @param srcmsg original message
+ * @param func a function that modifies the built message, to, for example,
+ * add an argument
+ * @param data data pointer to pass to the function
+ */
+static bool cdbus_reply(session_t *ps, DBusMessage *srcmsg,
+ bool (*func)(session_t *ps, DBusMessage *msg, const void *data),
+ const void *data) {
+ struct cdbus_data *cd = ps->dbus_data;
+ DBusMessage *msg = NULL;
+
+ // Create a reply
+ msg = dbus_message_new_method_return(srcmsg);
+ if (!msg) {
+ log_error("Failed to create D-Bus reply.");
+ return false;
+ }
+
+ // Append arguments onto message
+ if (func && !func(ps, msg, data)) {
+ dbus_message_unref(msg);
+ return false;
+ }
+
+ // Send the message and flush the connection
+ if (!dbus_connection_send(cd->dbus_conn, msg, NULL)) {
+ log_error("Failed to send D-Bus reply.");
+ dbus_message_unref(msg);
+ return false;
+ }
+ dbus_connection_flush(cd->dbus_conn);
+
+ // Free the message
+ dbus_message_unref(msg);
+
+ return true;
+}
+
+/**
+ * Send a reply with a bool argument.
+ */
+static inline bool cdbus_reply_bool(session_t *ps, DBusMessage *srcmsg, bool bval) {
+ return cdbus_reply(ps, srcmsg, cdbus_apdarg_bool, &bval);
+}
+
+/**
+ * Send a reply with an int32 argument.
+ */
+static inline bool cdbus_reply_int32(session_t *ps, DBusMessage *srcmsg, int32_t val) {
+ return cdbus_reply(ps, srcmsg, cdbus_apdarg_int32, &val);
+}
+
+/**
+ * Send a reply with an int32 argument, cast from a long.
+ */
+static inline bool cdbus_reply_int32l(session_t *ps, DBusMessage *srcmsg, long val) {
+ int32_t tmp = (int32_t)val;
+ return cdbus_reply(ps, srcmsg, cdbus_apdarg_int32, &tmp);
+}
+
+/**
+ * Send a reply with an uint32 argument.
+ */
+static inline bool cdbus_reply_uint32(session_t *ps, DBusMessage *srcmsg, uint32_t val) {
+ return cdbus_reply(ps, srcmsg, cdbus_apdarg_uint32, &val);
+}
+
+/**
+ * Send a reply with a double argument.
+ */
+static inline bool cdbus_reply_double(session_t *ps, DBusMessage *srcmsg, double val) {
+ return cdbus_reply(ps, srcmsg, cdbus_apdarg_double, &val);
+}
+
+/**
+ * Send a reply with a wid argument.
+ */
+static inline bool cdbus_reply_wid(session_t *ps, DBusMessage *srcmsg, xcb_window_t wid) {
+ return cdbus_reply(ps, srcmsg, cdbus_apdarg_wid, &wid);
+}
+
+/**
+ * Send a reply with a string argument.
+ */
+static inline bool cdbus_reply_string(session_t *ps, DBusMessage *srcmsg, const char *str) {
+ return cdbus_reply(ps, srcmsg, cdbus_apdarg_string, str);
+}
+
+/**
+ * Send a reply with a enum argument.
+ */
+static inline bool cdbus_reply_enum(session_t *ps, DBusMessage *srcmsg, cdbus_enum_t eval) {
+ return cdbus_reply(ps, srcmsg, cdbus_apdarg_enum, &eval);
+}
+
+/**
+ * Send a D-Bus error reply.
+ *
+ * @param ps current session
+ * @param msg the new error DBusMessage
+ */
+static bool cdbus_reply_errm(session_t *ps, DBusMessage *msg) {
+ struct cdbus_data *cd = ps->dbus_data;
+ if (!msg) {
+ log_error("Failed to create D-Bus reply.");
+ return false;
+ }
+
+ // Send the message and flush the connection
+ if (!dbus_connection_send(cd->dbus_conn, msg, NULL)) {
+ log_error("Failed to send D-Bus reply.");
+ dbus_message_unref(msg);
+ return false;
+ }
+ dbus_connection_flush(cd->dbus_conn);
+
+ // Free the message
+ dbus_message_unref(msg);
+
+ return true;
+}
+
+/**
+ * Get n-th argument of a D-Bus message.
+ *
+ * @param count the position of the argument to get, starting from 0
+ * @param type libdbus type number of the type
+ * @param pdest pointer to the target
+ * @return true if successful, false otherwise.
+ */
+static bool cdbus_msg_get_arg(DBusMessage *msg, int count, const int type, void *pdest) {
+ assert(count >= 0);
+
+ DBusMessageIter iter = {};
+ if (!dbus_message_iter_init(msg, &iter)) {
+ log_error("Message has no argument.");
+ return false;
+ }
+
+ {
+ const int oldcount = count;
+ while (count) {
+ if (!dbus_message_iter_next(&iter)) {
+ log_error("Failed to find argument %d.", oldcount);
+ return false;
+ }
+ --count;
+ }
+ }
+
+ if (type != dbus_message_iter_get_arg_type(&iter)) {
+ log_error("Argument has incorrect type.");
+ return false;
+ }
+
+ dbus_message_iter_get_basic(&iter, pdest);
+
+ return true;
+}
+
+/** @name Message processing
+ */
+///@{
+
+/**
+ * Process a list_win D-Bus request.
+ */
+static bool cdbus_process_list_win(session_t *ps, DBusMessage *msg) {
+ cdbus_reply(ps, msg, cdbus_apdarg_wids, NULL);
+
+ return true;
+}
+
+/**
+ * Process a win_get D-Bus request.
+ */
+static bool cdbus_process_win_get(session_t *ps, DBusMessage *msg) {
+ cdbus_window_t wid = XCB_NONE;
+ const char *target = NULL;
+ DBusError err = {};
+
+ if (!dbus_message_get_args(msg, &err, CDBUS_TYPE_WINDOW, &wid, DBUS_TYPE_STRING,
+ &target, DBUS_TYPE_INVALID)) {
+ log_error("Failed to parse argument of \"win_get\" (%s).", err.message);
+ dbus_error_free(&err);
+ return false;
+ }
+
+ auto w = find_managed_win(ps, wid);
+
+ if (!w) {
+ log_error("Window %#010x not found.", wid);
+ cdbus_reply_err(ps, msg, CDBUS_ERROR_BADWIN, CDBUS_ERROR_BADWIN_S, wid);
+ return true;
+ }
+
+#define cdbus_m_win_get_do(tgt, apdarg_func) \
+ if (!strcmp(#tgt, target)) { \
+ apdarg_func(ps, msg, w->tgt); \
+ return true; \
+ }
+
+ cdbus_m_win_get_do(base.id, cdbus_reply_wid);
+
+ // next
+ if (!strcmp("next", target)) {
+ cdbus_reply_wid(
+ ps, msg,
+ (list_node_is_last(&ps->window_stack, &w->base.stack_neighbour)
+ ? 0
+ : list_entry(w->base.stack_neighbour.next, struct win, stack_neighbour)
+ ->id));
+ return true;
+ }
+
+ // map_state
+ if (!strcmp("map_state", target)) {
+ cdbus_reply_bool(ps, msg, w->a.map_state);
+ return true;
+ }
+
+ cdbus_m_win_get_do(mode, cdbus_reply_enum);
+ cdbus_m_win_get_do(client_win, cdbus_reply_wid);
+ cdbus_m_win_get_do(ever_damaged, cdbus_reply_bool);
+ cdbus_m_win_get_do(window_type, cdbus_reply_enum);
+ cdbus_m_win_get_do(wmwin, cdbus_reply_bool);
+ cdbus_m_win_get_do(leader, cdbus_reply_wid);
+ if (!strcmp("focused_raw", target)) {
+ cdbus_reply_bool(ps, msg, win_is_focused_raw(ps, w));
+ return true;
+ }
+ cdbus_m_win_get_do(fade_force, cdbus_reply_enum);
+ cdbus_m_win_get_do(shadow_force, cdbus_reply_enum);
+ cdbus_m_win_get_do(focused_force, cdbus_reply_enum);
+ cdbus_m_win_get_do(invert_color_force, cdbus_reply_enum);
+ cdbus_m_win_get_do(name, cdbus_reply_string);
+ cdbus_m_win_get_do(class_instance, cdbus_reply_string);
+ cdbus_m_win_get_do(class_general, cdbus_reply_string);
+ cdbus_m_win_get_do(role, cdbus_reply_string);
+
+ cdbus_m_win_get_do(opacity, cdbus_reply_double);
+ cdbus_m_win_get_do(opacity_target, cdbus_reply_double);
+ cdbus_m_win_get_do(has_opacity_prop, cdbus_reply_bool);
+ cdbus_m_win_get_do(opacity_prop, cdbus_reply_uint32);
+ cdbus_m_win_get_do(opacity_is_set, cdbus_reply_bool);
+ cdbus_m_win_get_do(opacity_set, cdbus_reply_double);
+
+ cdbus_m_win_get_do(frame_opacity, cdbus_reply_double);
+ if (!strcmp("left_width", target)) {
+ cdbus_reply_int32(ps, msg, w->frame_extents.left);
+ return true;
+ }
+ if (!strcmp("right_width", target)) {
+ cdbus_reply_int32(ps, msg, w->frame_extents.right);
+ return true;
+ }
+ if (!strcmp("top_width", target)) {
+ cdbus_reply_int32(ps, msg, w->frame_extents.top);
+ return true;
+ }
+ if (!strcmp("bottom_width", target)) {
+ cdbus_reply_int32(ps, msg, w->frame_extents.bottom);
+ return true;
+ }
+
+ cdbus_m_win_get_do(shadow, cdbus_reply_bool);
+ cdbus_m_win_get_do(invert_color, cdbus_reply_bool);
+ cdbus_m_win_get_do(blur_background, cdbus_reply_bool);
+#undef cdbus_m_win_get_do
+
+ log_error(CDBUS_ERROR_BADTGT_S, target);
+ cdbus_reply_err(ps, msg, CDBUS_ERROR_BADTGT, CDBUS_ERROR_BADTGT_S, target);
+
+ return true;
+}
+
+/**
+ * Process a win_set D-Bus request.
+ */
+static bool cdbus_process_win_set(session_t *ps, DBusMessage *msg) {
+ cdbus_window_t wid = XCB_NONE;
+ const char *target = NULL;
+ DBusError err = {};
+
+ if (!dbus_message_get_args(msg, &err, CDBUS_TYPE_WINDOW, &wid, DBUS_TYPE_STRING,
+ &target, DBUS_TYPE_INVALID)) {
+ log_error("(): Failed to parse argument of \"win_set\" (%s).", err.message);
+ dbus_error_free(&err);
+ return false;
+ }
+
+ auto w = find_managed_win(ps, wid);
+
+ if (!w) {
+ log_error("Window %#010x not found.", wid);
+ cdbus_reply_err(ps, msg, CDBUS_ERROR_BADWIN, CDBUS_ERROR_BADWIN_S, wid);
+ return true;
+ }
+
+#define cdbus_m_win_set_do(tgt, type, real_type) \
+ if (!strcmp(MSTR(tgt), target)) { \
+ real_type val; \
+ if (!cdbus_msg_get_arg(msg, 2, type, &val)) \
+ return false; \
+ w->tgt = val; \
+ goto cdbus_process_win_set_success; \
+ }
+
+ if (!strcmp("shadow_force", target)) {
+ cdbus_enum_t val = UNSET;
+ if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val))
+ return false;
+ win_set_shadow_force(ps, w, val);
+ goto cdbus_process_win_set_success;
+ }
+
+ if (!strcmp("fade_force", target)) {
+ cdbus_enum_t val = UNSET;
+ if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val))
+ return false;
+ win_set_fade_force(w, val);
+ goto cdbus_process_win_set_success;
+ }
+
+ if (!strcmp("focused_force", target)) {
+ cdbus_enum_t val = UNSET;
+ if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val))
+ return false;
+ win_set_focused_force(ps, w, val);
+ goto cdbus_process_win_set_success;
+ }
+
+ if (!strcmp("invert_color_force", target)) {
+ cdbus_enum_t val = UNSET;
+ if (!cdbus_msg_get_arg(msg, 2, CDBUS_TYPE_ENUM, &val))
+ return false;
+ win_set_invert_color_force(ps, w, val);
+ goto cdbus_process_win_set_success;
+ }
+#undef cdbus_m_win_set_do
+
+ log_error(CDBUS_ERROR_BADTGT_S, target);
+ cdbus_reply_err(ps, msg, CDBUS_ERROR_BADTGT, CDBUS_ERROR_BADTGT_S, target);
+
+ return true;
+
+cdbus_process_win_set_success:
+ if (!dbus_message_get_no_reply(msg))
+ cdbus_reply_bool(ps, msg, true);
+ return true;
+}
+
+/**
+ * Process a find_win D-Bus request.
+ */
+static bool cdbus_process_find_win(session_t *ps, DBusMessage *msg) {
+ const char *target = NULL;
+
+ if (!cdbus_msg_get_arg(msg, 0, DBUS_TYPE_STRING, &target))
+ return false;
+
+ xcb_window_t wid = XCB_NONE;
+
+ // Find window by client window
+ if (!strcmp("client", target)) {
+ cdbus_window_t client = XCB_NONE;
+ if (!cdbus_msg_get_arg(msg, 1, CDBUS_TYPE_WINDOW, &client))
+ return false;
+ auto w = find_toplevel(ps, client);
+ if (w) {
+ wid = w->base.id;
+ }
+ }
+ // Find focused window
+ else if (!strcmp("focused", target)) {
+ if (ps->active_win && ps->active_win->state != WSTATE_UNMAPPED) {
+ wid = ps->active_win->base.id;
+ }
+ } else {
+ log_error(CDBUS_ERROR_BADTGT_S, target);
+ cdbus_reply_err(ps, msg, CDBUS_ERROR_BADTGT, CDBUS_ERROR_BADTGT_S, target);
+
+ return true;
+ }
+
+ cdbus_reply_wid(ps, msg, wid);
+
+ return true;
+}
+
+/**
+ * Process a opts_get D-Bus request.
+ */
+static bool cdbus_process_opts_get(session_t *ps, DBusMessage *msg) {
+ const char *target = NULL;
+
+ if (!cdbus_msg_get_arg(msg, 0, DBUS_TYPE_STRING, &target))
+ return false;
+
+#define cdbus_m_opts_get_do(tgt, apdarg_func) \
+ if (!strcmp(#tgt, target)) { \
+ apdarg_func(ps, msg, ps->o.tgt); \
+ return true; \
+ }
+
+#define cdbus_m_opts_get_stub(tgt, apdarg_func, ret) \
+ if (!strcmp(#tgt, target)) { \
+ apdarg_func(ps, msg, ret); \
+ return true; \
+ }
+
+ // version
+ if (!strcmp("version", target)) {
+ cdbus_reply_string(ps, msg, COMPTON_VERSION);
+ return true;
+ }
+
+ // pid
+ if (!strcmp("pid", target)) {
+ cdbus_reply_int32(ps, msg, getpid());
+ return true;
+ }
+
+ // display
+ if (!strcmp("display", target)) {
+ cdbus_reply_string(ps, msg, DisplayString(ps->dpy));
+ return true;
+ }
+
+ cdbus_m_opts_get_stub(config_file, cdbus_reply_string, "Unknown");
+ cdbus_m_opts_get_do(write_pid_path, cdbus_reply_string);
+ cdbus_m_opts_get_do(mark_wmwin_focused, cdbus_reply_bool);
+ cdbus_m_opts_get_do(mark_ovredir_focused, cdbus_reply_bool);
+ cdbus_m_opts_get_do(detect_rounded_corners, cdbus_reply_bool);
+ cdbus_m_opts_get_stub(paint_on_overlay, cdbus_reply_bool, ps->overlay != XCB_NONE);
+ // paint_on_overlay_id: Get ID of the X composite overlay window
+ if (!strcmp("paint_on_overlay_id", target)) {
+ cdbus_reply_uint32(ps, msg, ps->overlay);
+ return true;
+ }
+ cdbus_m_opts_get_do(unredir_if_possible, cdbus_reply_bool);
+ cdbus_m_opts_get_do(unredir_if_possible_delay, cdbus_reply_int32l);
+ cdbus_m_opts_get_do(redirected_force, cdbus_reply_enum);
+ cdbus_m_opts_get_do(stoppaint_force, cdbus_reply_enum);
+ cdbus_m_opts_get_do(logpath, cdbus_reply_string);
+
+ cdbus_m_opts_get_do(refresh_rate, cdbus_reply_int32);
+ cdbus_m_opts_get_do(sw_opti, cdbus_reply_bool);
+ cdbus_m_opts_get_do(vsync, cdbus_reply_bool);
+ if (!strcmp("backend", target)) {
+ assert(ps->o.backend < sizeof(BACKEND_STRS) / sizeof(BACKEND_STRS[0]));
+ cdbus_reply_string(ps, msg, BACKEND_STRS[ps->o.backend]);
+ return true;
+ }
+
+ cdbus_m_opts_get_do(shadow_red, cdbus_reply_double);
+ cdbus_m_opts_get_do(shadow_green, cdbus_reply_double);
+ cdbus_m_opts_get_do(shadow_blue, cdbus_reply_double);
+ cdbus_m_opts_get_do(shadow_radius, cdbus_reply_int32);
+ cdbus_m_opts_get_do(shadow_offset_x, cdbus_reply_int32);
+ cdbus_m_opts_get_do(shadow_offset_y, cdbus_reply_int32);
+ cdbus_m_opts_get_do(shadow_opacity, cdbus_reply_double);
+ cdbus_m_opts_get_do(xinerama_shadow_crop, cdbus_reply_bool);
+
+ cdbus_m_opts_get_do(fade_delta, cdbus_reply_int32);
+ cdbus_m_opts_get_do(fade_in_step, cdbus_reply_double);
+ cdbus_m_opts_get_do(fade_out_step, cdbus_reply_double);
+ cdbus_m_opts_get_do(no_fading_openclose, cdbus_reply_bool);
+
+ cdbus_m_opts_get_do(blur_method, cdbus_reply_bool);
+ cdbus_m_opts_get_do(blur_background_frame, cdbus_reply_bool);
+ cdbus_m_opts_get_do(blur_background_fixed, cdbus_reply_bool);
+
+ cdbus_m_opts_get_do(inactive_dim, cdbus_reply_double);
+ cdbus_m_opts_get_do(inactive_dim_fixed, cdbus_reply_bool);
+
+ cdbus_m_opts_get_do(max_brightness, cdbus_reply_double);
+
+ cdbus_m_opts_get_do(use_ewmh_active_win, cdbus_reply_bool);
+ cdbus_m_opts_get_do(detect_transient, cdbus_reply_bool);
+ cdbus_m_opts_get_do(detect_client_leader, cdbus_reply_bool);
+ cdbus_m_opts_get_do(use_damage, cdbus_reply_bool);
+
+#ifdef CONFIG_OPENGL
+ cdbus_m_opts_get_do(glx_no_stencil, cdbus_reply_bool);
+ cdbus_m_opts_get_do(glx_no_rebind_pixmap, cdbus_reply_bool);
+#endif
+
+#undef cdbus_m_opts_get_do
+#undef cdbus_m_opts_get_stub
+
+ log_error(CDBUS_ERROR_BADTGT_S, target);
+ cdbus_reply_err(ps, msg, CDBUS_ERROR_BADTGT, CDBUS_ERROR_BADTGT_S, target);
+
+ return true;
+}
+
+// XXX Remove this after header clean up
+void queue_redraw(session_t *ps);
+
+/**
+ * Process a opts_set D-Bus request.
+ */
+static bool cdbus_process_opts_set(session_t *ps, DBusMessage *msg) {
+ const char *target = NULL;
+
+ if (!cdbus_msg_get_arg(msg, 0, DBUS_TYPE_STRING, &target))
+ return false;
+
+#define cdbus_m_opts_set_do(tgt, type, real_type) \
+ if (!strcmp(#tgt, target)) { \
+ real_type val; \
+ if (!cdbus_msg_get_arg(msg, 1, type, &val)) \
+ return false; \
+ ps->o.tgt = val; \
+ goto cdbus_process_opts_set_success; \
+ }
+
+ // fade_delta
+ if (!strcmp("fade_delta", target)) {
+ int32_t val = 0;
+ if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_INT32, &val)) {
+ return false;
+ }
+ if (val <= 0) {
+ return false;
+ }
+ ps->o.fade_delta = max2(val, 1);
+ goto cdbus_process_opts_set_success;
+ }
+
+ // fade_in_step
+ if (!strcmp("fade_in_step", target)) {
+ double val = 0.0;
+ if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_DOUBLE, &val))
+ return false;
+ ps->o.fade_in_step = normalize_d(val);
+ goto cdbus_process_opts_set_success;
+ }
+
+ // fade_out_step
+ if (!strcmp("fade_out_step", target)) {
+ double val = 0.0;
+ if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_DOUBLE, &val))
+ return false;
+ ps->o.fade_out_step = normalize_d(val);
+ goto cdbus_process_opts_set_success;
+ }
+
+ // no_fading_openclose
+ if (!strcmp("no_fading_openclose", target)) {
+ dbus_bool_t val = FALSE;
+ if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_BOOLEAN, &val))
+ return false;
+ opts_set_no_fading_openclose(ps, val);
+ goto cdbus_process_opts_set_success;
+ }
+
+ // unredir_if_possible
+ if (!strcmp("unredir_if_possible", target)) {
+ dbus_bool_t val = FALSE;
+ if (!cdbus_msg_get_arg(msg, 1, DBUS_TYPE_BOOLEAN, &val))
+ return false;
+ if (ps->o.unredir_if_possible != val) {
+ ps->o.unredir_if_possible = val;
+ queue_redraw(ps);
+ }
+ goto cdbus_process_opts_set_success;
+ }
+
+ // clear_shadow
+ if (!strcmp("clear_shadow", target)) {
+ goto cdbus_process_opts_set_success;
+ }
+
+ // track_focus
+ if (!strcmp("track_focus", target)) {
+ goto cdbus_process_opts_set_success;
+ }
+
+ // redirected_force
+ if (!strcmp("redirected_force", target)) {
+ cdbus_enum_t val = UNSET;
+ if (!cdbus_msg_get_arg(msg, 1, CDBUS_TYPE_ENUM, &val))
+ return false;
+ ps->o.redirected_force = val;
+ force_repaint(ps);
+ goto cdbus_process_opts_set_success;
+ }
+
+ // stoppaint_force
+ cdbus_m_opts_set_do(stoppaint_force, CDBUS_TYPE_ENUM, cdbus_enum_t);
+
+#undef cdbus_m_opts_set_do
+
+ log_error(CDBUS_ERROR_BADTGT_S, target);
+ cdbus_reply_err(ps, msg, CDBUS_ERROR_BADTGT, CDBUS_ERROR_BADTGT_S, target);
+
+ return true;
+
+cdbus_process_opts_set_success:
+ if (!dbus_message_get_no_reply(msg))
+ cdbus_reply_bool(ps, msg, true);
+ return true;
+}
+
+/**
+ * Process an Introspect D-Bus request.
+ */
+static bool cdbus_process_introspect(session_t *ps, DBusMessage *msg) {
+ static const char *str_introspect =
+ "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection "
+ "1.0//EN\"\n"
+ " \"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
+ "<node name='" CDBUS_OBJECT_NAME "'>\n"
+ " <interface name='org.freedesktop.DBus.Introspectable'>\n"
+ " <method name='Introspect'>\n"
+ " <arg name='data' direction='out' type='s' />\n"
+ " </method>\n"
+ " </interface>\n"
+ " <interface name='org.freedesktop.DBus.Peer'>\n"
+ " <method name='Ping' />\n"
+ " <method name='GetMachineId'>\n"
+ " <arg name='machine_uuid' direction='out' type='s' />\n"
+ " </method>\n"
+ " </interface>\n"
+ " <interface name='" CDBUS_INTERFACE_NAME "'>\n"
+ " <signal name='win_added'>\n"
+ " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n"
+ " </signal>\n"
+ " <signal name='win_destroyed'>\n"
+ " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n"
+ " </signal>\n"
+ " <signal name='win_mapped'>\n"
+ " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n"
+ " </signal>\n"
+ " <signal name='win_unmapped'>\n"
+ " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n"
+ " </signal>\n"
+ " <signal name='win_focusin'>\n"
+ " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n"
+ " </signal>\n"
+ " <signal name='win_focusout'>\n"
+ " <arg name='wid' type='" CDBUS_TYPE_WINDOW_STR "'/>\n"
+ " </signal>\n"
+ " <method name='reset' />\n"
+ " <method name='repaint' />\n"
+ " </interface>\n"
+ "</node>\n";
+
+ cdbus_reply_string(ps, msg, str_introspect);
+
+ return true;
+}
+///@}
+
+/**
+ * Process a message from D-Bus.
+ */
+static DBusHandlerResult
+cdbus_process(DBusConnection *c attr_unused, DBusMessage *msg, void *ud) {
+ session_t *ps = ud;
+ bool handled = false;
+
+#define cdbus_m_ismethod(method) \
+ dbus_message_is_method_call(msg, CDBUS_INTERFACE_NAME, method)
+
+ if (cdbus_m_ismethod("reset")) {
+ log_info("picom is resetting...");
+ ev_break(ps->loop, EVBREAK_ALL);
+ if (!dbus_message_get_no_reply(msg))
+ cdbus_reply_bool(ps, msg, true);
+ handled = true;
+ } else if (cdbus_m_ismethod("repaint")) {
+ force_repaint(ps);
+ if (!dbus_message_get_no_reply(msg))
+ cdbus_reply_bool(ps, msg, true);
+ handled = true;
+ } else if (cdbus_m_ismethod("list_win")) {
+ handled = cdbus_process_list_win(ps, msg);
+ } else if (cdbus_m_ismethod("win_get")) {
+ handled = cdbus_process_win_get(ps, msg);
+ } else if (cdbus_m_ismethod("win_set")) {
+ handled = cdbus_process_win_set(ps, msg);
+ } else if (cdbus_m_ismethod("find_win")) {
+ handled = cdbus_process_find_win(ps, msg);
+ } else if (cdbus_m_ismethod("opts_get")) {
+ handled = cdbus_process_opts_get(ps, msg);
+ } else if (cdbus_m_ismethod("opts_set")) {
+ handled = cdbus_process_opts_set(ps, msg);
+ }
+#undef cdbus_m_ismethod
+ else if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Introspectable",
+ "Introspect")) {
+ handled = cdbus_process_introspect(ps, msg);
+ } else if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Peer", "Ping")) {
+ cdbus_reply(ps, msg, NULL, NULL);
+ handled = true;
+ } else if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Peer",
+ "GetMachineId")) {
+ char *uuid = dbus_get_local_machine_id();
+ if (uuid) {
+ cdbus_reply_string(ps, msg, uuid);
+ dbus_free(uuid);
+ handled = true;
+ }
+ } else if (dbus_message_is_signal(msg, "org.freedesktop.DBus", "NameAcquired") ||
+ dbus_message_is_signal(msg, "org.freedesktop.DBus", "NameLost")) {
+ handled = true;
+ } else {
+ if (DBUS_MESSAGE_TYPE_ERROR == dbus_message_get_type(msg)) {
+ log_error(
+ "Error message of path \"%s\" "
+ "interface \"%s\", member \"%s\", error \"%s\"",
+ dbus_message_get_path(msg), dbus_message_get_interface(msg),
+ dbus_message_get_member(msg), dbus_message_get_error_name(msg));
+ } else {
+ log_error("Illegal message of type \"%s\", path \"%s\" "
+ "interface \"%s\", member \"%s\"",
+ cdbus_repr_msgtype(msg), dbus_message_get_path(msg),
+ dbus_message_get_interface(msg),
+ dbus_message_get_member(msg));
+ }
+ if (DBUS_MESSAGE_TYPE_METHOD_CALL == dbus_message_get_type(msg) &&
+ !dbus_message_get_no_reply(msg))
+ cdbus_reply_err(ps, msg, CDBUS_ERROR_BADMSG, CDBUS_ERROR_BADMSG_S);
+ handled = true;
+ }
+
+ // If the message could not be processed, and an reply is expected, return
+ // an empty reply.
+ if (!handled && DBUS_MESSAGE_TYPE_METHOD_CALL == dbus_message_get_type(msg) &&
+ !dbus_message_get_no_reply(msg)) {
+ cdbus_reply_err(ps, msg, CDBUS_ERROR_UNKNOWN, CDBUS_ERROR_UNKNOWN_S);
+ handled = true;
+ }
+
+ return handled ? DBUS_HANDLER_RESULT_HANDLED : DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+/** @name Core callbacks
+ */
+///@{
+void cdbus_ev_win_added(session_t *ps, struct win *w) {
+ struct cdbus_data *cd = ps->dbus_data;
+ if (cd->dbus_conn)
+ cdbus_signal_wid(ps, "win_added", w->id);
+}
+
+void cdbus_ev_win_destroyed(session_t *ps, struct win *w) {
+ struct cdbus_data *cd = ps->dbus_data;
+ if (cd->dbus_conn)
+ cdbus_signal_wid(ps, "win_destroyed", w->id);
+}
+
+void cdbus_ev_win_mapped(session_t *ps, struct win *w) {
+ struct cdbus_data *cd = ps->dbus_data;
+ if (cd->dbus_conn)
+ cdbus_signal_wid(ps, "win_mapped", w->id);
+}
+
+void cdbus_ev_win_unmapped(session_t *ps, struct win *w) {
+ struct cdbus_data *cd = ps->dbus_data;
+ if (cd->dbus_conn)
+ cdbus_signal_wid(ps, "win_unmapped", w->id);
+}
+
+void cdbus_ev_win_focusout(session_t *ps, struct win *w) {
+ struct cdbus_data *cd = ps->dbus_data;
+ if (cd->dbus_conn)
+ cdbus_signal_wid(ps, "win_focusout", w->id);
+}
+
+void cdbus_ev_win_focusin(session_t *ps, struct win *w) {
+ struct cdbus_data *cd = ps->dbus_data;
+ if (cd->dbus_conn)
+ cdbus_signal_wid(ps, "win_focusin", w->id);
+}
+//!@}
diff --git a/src/dbus.h b/src/dbus.h
new file mode 100644
index 0000000..54a58af
--- /dev/null
+++ b/src/dbus.h
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Compton - a compositor for X11
+ *
+ * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
+ *
+ * Copyright (c) 2011-2013, Christopher Jeffrey
+ * See LICENSE-mit for more information.
+ *
+ */
+
+#include <stdbool.h>
+
+#include <dbus/dbus.h>
+
+typedef struct session session_t;
+struct win;
+
+/**
+ * Return a string representation of a D-Bus message type.
+ */
+static inline const char *cdbus_repr_msgtype(DBusMessage *msg) {
+ return dbus_message_type_to_string(dbus_message_get_type(msg));
+}
+
+/**
+ * Initialize D-Bus connection.
+ */
+bool cdbus_init(session_t *ps, const char *uniq_name);
+
+/**
+ * Destroy D-Bus connection.
+ */
+void cdbus_destroy(session_t *ps);
+
+/// Generate dbus win_added signal
+void cdbus_ev_win_added(session_t *ps, struct win *w);
+
+/// Generate dbus win_destroyed signal
+void cdbus_ev_win_destroyed(session_t *ps, struct win *w);
+
+/// Generate dbus win_mapped signal
+void cdbus_ev_win_mapped(session_t *ps, struct win *w);
+
+/// Generate dbus win_unmapped signal
+void cdbus_ev_win_unmapped(session_t *ps, struct win *w);
+
+/// Generate dbus win_focusout signal
+void cdbus_ev_win_focusout(session_t *ps, struct win *w);
+
+/// Generate dbus win_focusin signal
+void cdbus_ev_win_focusin(session_t *ps, struct win *w);
+
+// vim: set noet sw=8 ts=8 :
diff --git a/src/diagnostic.c b/src/diagnostic.c
new file mode 100644
index 0000000..d275b1a
--- /dev/null
+++ b/src/diagnostic.c
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) 2018 Yuxuan Shui <[email protected]>
+
+#include <stdio.h>
+#include <xcb/xcb.h>
+#include <xcb/composite.h>
+
+#include "backend/driver.h"
+#include "diagnostic.h"
+#include "config.h"
+#include "picom.h"
+#include "common.h"
+
+void print_diagnostics(session_t *ps, const char *config_file, bool compositor_running) {
+ printf("**Version:** " COMPTON_VERSION "\n");
+ //printf("**CFLAGS:** %s\n", "??");
+ printf("\n### Extensions:\n\n");
+ printf("* Shape: %s\n", ps->shape_exists ? "Yes" : "No");
+ printf("* XRandR: %s\n", ps->randr_exists ? "Yes" : "No");
+ printf("* Present: %s\n", ps->present_exists ? "Present" : "Not Present");
+ printf("\n### Misc:\n\n");
+ printf("* Use Overlay: %s\n", ps->overlay != XCB_NONE ? "Yes" : "No");
+ if (ps->overlay == XCB_NONE) {
+ if (compositor_running) {
+ printf(" (Another compositor is already running)\n");
+ } else if (session_redirection_mode(ps) != XCB_COMPOSITE_REDIRECT_MANUAL) {
+ printf(" (Not in manual redirection mode)\n");
+ } else {
+ printf("\n");
+ }
+ }
+#ifdef __FAST_MATH__
+ printf("* Fast Math: Yes\n");
+#endif
+ printf("* Config file used: %s\n", config_file ?: "None");
+ printf("\n### Drivers (inaccurate):\n\n");
+ print_drivers(ps->drivers);
+
+ for (int i = 0; i < NUM_BKEND; i++) {
+ if (backend_list[i] && backend_list[i]->diagnostics) {
+ printf("\n### Backend: %s\n\n", BACKEND_STRS[i]);
+ auto data = backend_list[i]->init(ps);
+ if (!data) {
+ printf(" Cannot initialize this backend\n");
+ } else {
+ backend_list[i]->diagnostics(data);
+ backend_list[i]->deinit(data);
+ }
+ }
+ }
+}
+
+// vim: set noet sw=8 ts=8 :
diff --git a/src/diagnostic.h b/src/diagnostic.h
new file mode 100644
index 0000000..c958589
--- /dev/null
+++ b/src/diagnostic.h
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) 2018 Yuxuan Shui <[email protected]>
+
+#pragma once
+#include <stdbool.h>
+
+typedef struct session session_t;
+
+void print_diagnostics(session_t *, const char *config_file, bool compositor_running);
diff --git a/src/err.h b/src/err.h
new file mode 100644
index 0000000..f989bf9
--- /dev/null
+++ b/src/err.h
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) 2019 Yuxuan Shui <[email protected]>
+
+#pragma once
+#include <stdbool.h>
+#include <stdint.h>
+#include "compiler.h"
+
+// Functions for error reporting, adopted from Linux
+
+// INFO in user space we can probably be more liberal about what pointer we consider
+// error. e.g. In x86_64 Linux, all addresses with the highest bit set is invalid in user
+// space.
+#define MAX_ERRNO 4095
+
+static inline void *must_use ERR_PTR(intptr_t err) {
+ return (void *)err;
+}
+
+static inline intptr_t must_use PTR_ERR(void *ptr) {
+ return (intptr_t)ptr;
+}
+
+static inline bool must_use IS_ERR(void *ptr) {
+ return unlikely((uintptr_t)ptr > (uintptr_t)-MAX_ERRNO);
+}
+
+static inline bool must_use IS_ERR_OR_NULL(void *ptr) {
+ return unlikely(!ptr) || IS_ERR(ptr);
+}
+
+static inline intptr_t must_use PTR_ERR_OR_ZERO(void *ptr) {
+ if (IS_ERR(ptr)) {
+ return PTR_ERR(ptr);
+ }
+ return 0;
+}
diff --git a/src/event.c b/src/event.c
new file mode 100644
index 0000000..5e4017f
--- /dev/null
+++ b/src/event.c
@@ -0,0 +1,757 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) 2019, Yuxuan Shui <[email protected]>
+
+#include <stdio.h>
+
+#include <X11/Xlibint.h>
+#include <X11/extensions/sync.h>
+#include <xcb/damage.h>
+#include <xcb/randr.h>
+
+#include "atom.h"
+#include "common.h"
+#include "compiler.h"
+#include "config.h"
+#include "event.h"
+#include "log.h"
+#include "picom.h"
+#include "region.h"
+#include "utils.h"
+#include "win.h"
+#include "x.h"
+
+/// Event handling with X is complicated. Handling events with other events possibly
+/// in-flight is no good. Because your internal state won't be up to date. Also, querying
+/// the server while events are in-flight is not good. Because events later in the queue
+/// might container information you are querying. Thus those events will cause you to do
+/// unnecessary updates even when you already have the latest information (remember, you
+/// made the query when those events were already in the queue. so the reply you got is
+/// more up-to-date than the events). Also, handling events when other client are making
+/// concurrent requests is not good. Because the server states are changing without you
+/// knowning them. This is super racy, and can cause lots of potential problems.
+///
+/// All of above mandates we do these things:
+/// 1. Grab server when handling events
+/// 2. Make sure the event queue is empty before we make any query to the server
+///
+/// Notice (2) has a dependency circle. To handle events, you sometimes need to make
+/// queries. But to make queries you have to first handle events.
+///
+/// To break that circle, we split all event handling into top and bottom halves. The
+/// bottom half will just look at the event itself, update as much state as they can
+/// without making queries, then queue up necessary works need to be done by the top half.
+/// The top half will do all the other necessary updates. Before entering the top half, we
+/// grab the server and make sure the event queue is empty.
+///
+/// When top half finished, we enter the render stage, where no server state should be
+/// queried. All rendering should be done with our internal knowledge of the server state.
+///
+
+// TODO(yshui) the things described above
+
+/**
+ * Get a window's name from window ID.
+ */
+static inline const char *ev_window_name(session_t *ps, xcb_window_t wid) {
+ char *name = "";
+ if (wid) {
+ name = "(Failed to get title)";
+ if (ps->root == wid) {
+ name = "(Root window)";
+ } else if (ps->overlay == wid) {
+ name = "(Overlay)";
+ } else {
+ auto w = find_managed_win(ps, wid);
+ if (!w) {
+ w = find_toplevel(ps, wid);
+ }
+
+ if (w && w->name) {
+ name = w->name;
+ }
+ }
+ }
+ return name;
+}
+
+static inline xcb_window_t attr_pure ev_window(session_t *ps, xcb_generic_event_t *ev) {
+ switch (ev->response_type) {
+ case FocusIn:
+ case FocusOut: return ((xcb_focus_in_event_t *)ev)->event;
+ case CreateNotify: return ((xcb_create_notify_event_t *)ev)->window;
+ case ConfigureNotify: return ((xcb_configure_notify_event_t *)ev)->window;
+ case DestroyNotify: return ((xcb_destroy_notify_event_t *)ev)->window;
+ case MapNotify: return ((xcb_map_notify_event_t *)ev)->window;
+ case UnmapNotify: return ((xcb_unmap_notify_event_t *)ev)->window;
+ case ReparentNotify: return ((xcb_reparent_notify_event_t *)ev)->window;
+ case CirculateNotify: return ((xcb_circulate_notify_event_t *)ev)->window;
+ case Expose: return ((xcb_expose_event_t *)ev)->window;
+ case PropertyNotify: return ((xcb_property_notify_event_t *)ev)->window;
+ case ClientMessage: return ((xcb_client_message_event_t *)ev)->window;
+ default:
+ if (ps->damage_event + XCB_DAMAGE_NOTIFY == ev->response_type) {
+ return ((xcb_damage_notify_event_t *)ev)->drawable;
+ }
+
+ if (ps->shape_exists && ev->response_type == ps->shape_event) {
+ return ((xcb_shape_notify_event_t *)ev)->affected_window;
+ }
+
+ return 0;
+ }
+}
+
+#define CASESTRRET(s) \
+ case s: return #s;
+
+static inline const char *ev_name(session_t *ps, xcb_generic_event_t *ev) {
+ static char buf[128];
+ switch (ev->response_type & 0x7f) {
+ CASESTRRET(FocusIn);
+ CASESTRRET(FocusOut);
+ CASESTRRET(CreateNotify);
+ CASESTRRET(ConfigureNotify);
+ CASESTRRET(DestroyNotify);
+ CASESTRRET(MapNotify);
+ CASESTRRET(UnmapNotify);
+ CASESTRRET(ReparentNotify);
+ CASESTRRET(CirculateNotify);
+ CASESTRRET(Expose);
+ CASESTRRET(PropertyNotify);
+ CASESTRRET(ClientMessage);
+ }
+
+ if (ps->damage_event + XCB_DAMAGE_NOTIFY == ev->response_type)
+ return "Damage";
+
+ if (ps->shape_exists && ev->response_type == ps->shape_event)
+ return "ShapeNotify";
+
+ if (ps->xsync_exists) {
+ int o = ev->response_type - ps->xsync_event;
+ switch (o) {
+ CASESTRRET(XSyncCounterNotify);
+ CASESTRRET(XSyncAlarmNotify);
+ }
+ }
+
+ sprintf(buf, "Event %d", ev->response_type);
+
+ return buf;
+}
+
+static inline const char *attr_pure ev_focus_mode_name(xcb_focus_in_event_t *ev) {
+ switch (ev->mode) {
+ CASESTRRET(NotifyNormal);
+ CASESTRRET(NotifyWhileGrabbed);
+ CASESTRRET(NotifyGrab);
+ CASESTRRET(NotifyUngrab);
+ }
+
+ return "Unknown";
+}
+
+static inline const char *attr_pure ev_focus_detail_name(xcb_focus_in_event_t *ev) {
+ switch (ev->detail) {
+ CASESTRRET(NotifyAncestor);
+ CASESTRRET(NotifyVirtual);
+ CASESTRRET(NotifyInferior);
+ CASESTRRET(NotifyNonlinear);
+ CASESTRRET(NotifyNonlinearVirtual);
+ CASESTRRET(NotifyPointer);
+ CASESTRRET(NotifyPointerRoot);
+ CASESTRRET(NotifyDetailNone);
+ }
+
+ return "Unknown";
+}
+
+#undef CASESTRRET
+
+static inline void ev_focus_in(session_t *ps, xcb_focus_in_event_t *ev) {
+ log_debug("{ mode: %s, detail: %s }\n", ev_focus_mode_name(ev),
+ ev_focus_detail_name(ev));
+ ps->pending_updates = true;
+}
+
+static inline void ev_focus_out(session_t *ps, xcb_focus_out_event_t *ev) {
+ log_debug("{ mode: %s, detail: %s }\n", ev_focus_mode_name(ev),
+ ev_focus_detail_name(ev));
+ ps->pending_updates = true;
+}
+
+static inline void ev_create_notify(session_t *ps, xcb_create_notify_event_t *ev) {
+ if (ev->parent == ps->root) {
+ add_win_top(ps, ev->window);
+ }
+}
+
+/// Handle configure event of a regular window
+static void configure_win(session_t *ps, xcb_configure_notify_event_t *ce) {
+ auto w = find_win(ps, ce->window);
+
+ if (!w) {
+ return;
+ }
+
+ if (!w->managed) {
+ restack_above(ps, w, ce->above_sibling);
+ return;
+ }
+
+ auto mw = (struct managed_win *)w;
+
+ restack_above(ps, w, ce->above_sibling);
+
+ // We check against pending_g here, because there might have been multiple
+ // configure notifies in this cycle, or the window could receive multiple updates
+ // while it's unmapped.
+ bool position_changed = mw->pending_g.x != ce->x || mw->pending_g.y != ce->y;
+ bool size_changed = mw->pending_g.width != ce->width ||
+ mw->pending_g.height != ce->height ||
+ mw->pending_g.border_width != ce->border_width;
+ if (position_changed || size_changed) {
+ // Queue pending updates
+ win_set_flags(mw, WIN_FLAGS_FACTOR_CHANGED);
+ // TODO(yshui) don't set pending_updates if the window is not
+ // visible/mapped
+ ps->pending_updates = true;
+
+ // At least one of the following if's is true
+ if (position_changed) {
+ log_trace("Window position changed, %dx%d -> %dx%d", mw->g.x,
+ mw->g.y, ce->x, ce->y);
+ mw->pending_g.x = ce->x;
+ mw->pending_g.y = ce->y;
+ win_set_flags(mw, WIN_FLAGS_POSITION_STALE);
+ }
+
+ if (size_changed) {
+ log_trace("Window size changed, %dx%d -> %dx%d", mw->g.width,
+ mw->g.height, ce->width, ce->height);
+ mw->pending_g.width = ce->width;
+ mw->pending_g.height = ce->height;
+ mw->pending_g.border_width = ce->border_width;
+ win_set_flags(mw, WIN_FLAGS_SIZE_STALE);
+ }
+
+ // Recalculate which screen this window is on
+ win_update_screen(ps->xinerama_nscrs, ps->xinerama_scr_regs, mw);
+ }
+
+ // override_redirect flag cannot be changed after window creation, as far
+ // as I know, so there's no point to re-match windows here.
+ mw->a.override_redirect = ce->override_redirect;
+}
+
+static inline void ev_configure_notify(session_t *ps, xcb_configure_notify_event_t *ev) {
+ log_debug("{ send_event: %d, id: %#010x, above: %#010x, override_redirect: %d }",
+ ev->event, ev->window, ev->above_sibling, ev->override_redirect);
+ if (ev->window == ps->root) {
+ set_root_flags(ps, ROOT_FLAGS_CONFIGURED);
+ } else {
+ configure_win(ps, ev);
+ }
+}
+
+static inline void ev_destroy_notify(session_t *ps, xcb_destroy_notify_event_t *ev) {
+ auto w = find_win(ps, ev->window);
+ auto mw = find_toplevel(ps, ev->window);
+ if (mw && mw->client_win == mw->base.id) {
+ // We only want _real_ frame window
+ assert(&mw->base == w);
+ mw = NULL;
+ }
+ assert(w == NULL || mw == NULL);
+
+ if (w != NULL) {
+ auto _ attr_unused = destroy_win_start(ps, w);
+ } else if (mw != NULL) {
+ win_unmark_client(ps, mw);
+ win_set_flags(mw, WIN_FLAGS_CLIENT_STALE);
+ ps->pending_updates = true;
+ } else {
+ log_debug("Received a destroy notify from an unknown window, %#010x",
+ ev->window);
+ }
+}
+
+static inline void ev_map_notify(session_t *ps, xcb_map_notify_event_t *ev) {
+ // Unmap overlay window if it got mapped but we are currently not
+ // in redirected state.
+ if (ps->overlay && ev->window == ps->overlay && !ps->redirected) {
+ log_debug("Overlay is mapped while we are not redirected");
+ auto e = xcb_request_check(ps->c, xcb_unmap_window(ps->c, ps->overlay));
+ if (e) {
+ log_error("Failed to unmap the overlay window");
+ free(e);
+ }
+ // We don't track the overlay window, so we can return
+ return;
+ }
+
+ auto w = find_managed_win(ps, ev->window);
+ if (!w) {
+ return;
+ }
+
+ win_set_flags(w, WIN_FLAGS_MAPPED);
+
+ // FocusIn/Out may be ignored when the window is unmapped, so we must
+ // recheck focus here
+ ps->pending_updates = true; // to update focus
+}
+
+static inline void ev_unmap_notify(session_t *ps, xcb_unmap_notify_event_t *ev) {
+ auto w = find_managed_win(ps, ev->window);
+ if (w) {
+ unmap_win_start(ps, w);
+ }
+}
+
+static inline void ev_reparent_notify(session_t *ps, xcb_reparent_notify_event_t *ev) {
+ log_debug("Window %#010x has new parent: %#010x, override_redirect: %d",
+ ev->window, ev->parent, ev->override_redirect);
+ auto w_top = find_toplevel(ps, ev->window);
+ if (w_top) {
+ win_unmark_client(ps, w_top);
+ win_set_flags(w_top, WIN_FLAGS_CLIENT_STALE);
+ ps->pending_updates = true;
+ }
+
+ if (ev->parent == ps->root) {
+ // X will generate reparent notifiy even if the parent didn't actually
+ // change (i.e. reparent again to current parent). So we check if that's
+ // the case
+ auto w = find_win(ps, ev->window);
+ if (w) {
+ // This window has already been reparented to root before,
+ // so we don't need to create a new window for it, we just need to
+ // move it to the top
+ restack_top(ps, w);
+ } else {
+ add_win_top(ps, ev->window);
+ }
+ } else {
+ // otherwise, find and destroy the window first
+ {
+ auto w = find_win(ps, ev->window);
+ if (w) {
+ auto ret = destroy_win_start(ps, w);
+ if (!ret && w->managed) {
+ auto mw = (struct managed_win *)w;
+ CHECK(win_skip_fading(ps, mw));
+ }
+ }
+ }
+
+ // Reset event mask in case something wrong happens
+ xcb_change_window_attributes(
+ ps->c, ev->window, XCB_CW_EVENT_MASK,
+ (const uint32_t[]){determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN)});
+
+ if (!wid_has_prop(ps, ev->window, ps->atoms->aWM_STATE)) {
+ log_debug("Window %#010x doesn't have WM_STATE property, it is "
+ "probably not a client window. But we will listen for "
+ "property change in case it gains one.",
+ ev->window);
+ xcb_change_window_attributes(
+ ps->c, ev->window, XCB_CW_EVENT_MASK,
+ (const uint32_t[]){determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN) |
+ XCB_EVENT_MASK_PROPERTY_CHANGE});
+ } else {
+ auto w_real_top = find_managed_window_or_parent(ps, ev->parent);
+ if (w_real_top && w_real_top->state != WSTATE_UNMAPPED &&
+ w_real_top->state != WSTATE_UNMAPPING) {
+ log_debug("Mark window %#010x (%s) as having a stale "
+ "client",
+ w_real_top->base.id, w_real_top->name);
+ win_set_flags(w_real_top, WIN_FLAGS_CLIENT_STALE);
+ ps->pending_updates = true;
+ } else {
+ if (!w_real_top)
+ log_debug("parent %#010x not found", ev->parent);
+ else {
+ // Window is not currently mapped, unmark its
+ // client to trigger a client recheck when it is
+ // mapped later.
+ win_unmark_client(ps, w_real_top);
+ log_debug("parent %#010x (%s) is in state %d",
+ w_real_top->base.id, w_real_top->name,
+ w_real_top->state);
+ }
+ }
+ }
+ }
+}
+
+static inline void ev_circulate_notify(session_t *ps, xcb_circulate_notify_event_t *ev) {
+ auto w = find_win(ps, ev->window);
+
+ if (!w)
+ return;
+
+ if (ev->place == PlaceOnTop) {
+ restack_top(ps, w);
+ } else {
+ restack_bottom(ps, w);
+ }
+}
+
+static inline void expose_root(session_t *ps, const rect_t *rects, int nrects) {
+ region_t region;
+ pixman_region32_init_rects(&region, rects, nrects);
+ add_damage(ps, &region);
+ pixman_region32_fini(&region);
+}
+
+static inline void ev_expose(session_t *ps, xcb_expose_event_t *ev) {
+ if (ev->window == ps->root || (ps->overlay && ev->window == ps->overlay)) {
+ int more = ev->count + 1;
+ if (ps->n_expose == ps->size_expose) {
+ if (ps->expose_rects) {
+ ps->expose_rects =
+ crealloc(ps->expose_rects, ps->size_expose + more);
+ ps->size_expose += more;
+ } else {
+ ps->expose_rects = ccalloc(more, rect_t);
+ ps->size_expose = more;
+ }
+ }
+
+ ps->expose_rects[ps->n_expose].x1 = ev->x;
+ ps->expose_rects[ps->n_expose].y1 = ev->y;
+ ps->expose_rects[ps->n_expose].x2 = ev->x + ev->width;
+ ps->expose_rects[ps->n_expose].y2 = ev->y + ev->height;
+ ps->n_expose++;
+
+ if (ev->count == 0) {
+ expose_root(ps, ps->expose_rects, ps->n_expose);
+ ps->n_expose = 0;
+ }
+ }
+}
+
+static inline void ev_property_notify(session_t *ps, xcb_property_notify_event_t *ev) {
+ if (unlikely(log_get_level_tls() <= LOG_LEVEL_TRACE)) {
+ // Print out changed atom
+ xcb_get_atom_name_reply_t *reply =
+ xcb_get_atom_name_reply(ps->c, xcb_get_atom_name(ps->c, ev->atom), NULL);
+ const char *name = "?";
+ int name_len = 1;
+ if (reply) {
+ name = xcb_get_atom_name_name(reply);
+ name_len = xcb_get_atom_name_name_length(reply);
+ }
+
+ log_debug("{ atom = %.*s }", name_len, name);
+ free(reply);
+ }
+
+ if (ps->root == ev->window) {
+ // If desktop number property changes
+ if (ev->atom == ps->atoms->a_NET_CURRENT_DESKTOP) {
+ auto prop = x_get_prop(ps->c, ps->root, ps->atoms->a_NET_CURRENT_DESKTOP,
+ 1L, XCB_ATOM_CARDINAL, 32);
+
+ if (prop.nitems) {
+ ps->root_desktop_switch_direction = ((int)*prop.c32) - ps->root_desktop_num;
+ ps->root_desktop_num = (int)*prop.c32;
+ }
+ }
+
+ if (ps->o.use_ewmh_active_win && ps->atoms->a_NET_ACTIVE_WINDOW == ev->atom) {
+ // to update focus
+ ps->pending_updates = true;
+ } else {
+ // Destroy the root "image" if the wallpaper probably changed
+ if (x_is_root_back_pixmap_atom(ps->atoms, ev->atom)) {
+ root_damaged(ps);
+ }
+ }
+
+ // Unconcerned about any other proprties on root window
+ return;
+ }
+
+ ps->pending_updates = true;
+ // If WM_STATE changes
+ if (ev->atom == ps->atoms->aWM_STATE) {
+ // Check whether it could be a client window
+ if (!find_toplevel(ps, ev->window)) {
+ // Reset event mask anyway
+ xcb_change_window_attributes(ps->c, ev->window, XCB_CW_EVENT_MASK,
+ (const uint32_t[]){determine_evmask(
+ ps, ev->window, WIN_EVMODE_UNKNOWN)});
+
+ auto w_top = find_managed_window_or_parent(ps, ev->window);
+ // ev->window might have not been managed yet, in that case w_top
+ // would be NULL.
+ if (w_top) {
+ win_set_flags(w_top, WIN_FLAGS_CLIENT_STALE);
+ }
+ }
+ return;
+ }
+
+ // If _NET_WM_WINDOW_TYPE changes... God knows why this would happen, but
+ // there are always some stupid applications. (#144)
+ if (ev->atom == ps->atoms->a_NET_WM_WINDOW_TYPE) {
+ struct managed_win *w = NULL;
+ if ((w = find_toplevel(ps, ev->window))) {
+ win_set_property_stale(w, ev->atom);
+ }
+ }
+
+ if (ev->atom == ps->atoms->a_NET_WM_BYPASS_COMPOSITOR) {
+ // Unnecessay until we remove the queue_redraw in ev_handle
+ queue_redraw(ps);
+ }
+
+ // If _NET_WM_OPACITY changes
+ if (ev->atom == ps->atoms->a_NET_WM_WINDOW_OPACITY) {
+ auto w = find_managed_win(ps, ev->window) ?: find_toplevel(ps, ev->window);
+ if (w) {
+ win_set_property_stale(w, ev->atom);
+ }
+ }
+
+ // If frame extents property changes
+ if (ev->atom == ps->atoms->a_NET_FRAME_EXTENTS) {
+ auto w = find_toplevel(ps, ev->window);
+ if (w) {
+ win_set_property_stale(w, ev->atom);
+ }
+ }
+
+ // If name changes
+ if (ps->atoms->aWM_NAME == ev->atom || ps->atoms->a_NET_WM_NAME == ev->atom) {
+ auto w = find_toplevel(ps, ev->window);
+ if (w) {
+ win_set_property_stale(w, ev->atom);
+ }
+ }
+
+ // If class changes
+ if (ps->atoms->aWM_CLASS == ev->atom) {
+ auto w = find_toplevel(ps, ev->window);
+ if (w) {
+ win_set_property_stale(w, ev->atom);
+ }
+ }
+
+ // If role changes
+ if (ps->atoms->aWM_WINDOW_ROLE == ev->atom) {
+ auto w = find_toplevel(ps, ev->window);
+ if (w) {
+ win_set_property_stale(w, ev->atom);
+ }
+ }
+
+ // If _COMPTON_SHADOW changes
+ if (ps->atoms->a_COMPTON_SHADOW == ev->atom) {
+ auto w = find_managed_win(ps, ev->window);
+ if (w) {
+ win_set_property_stale(w, ev->atom);
+ }
+ }
+
+ // If a leader property changes
+ if ((ps->o.detect_transient && ps->atoms->aWM_TRANSIENT_FOR == ev->atom) ||
+ (ps->o.detect_client_leader && ps->atoms->aWM_CLIENT_LEADER == ev->atom)) {
+ auto w = find_toplevel(ps, ev->window);
+ if (w) {
+ win_set_property_stale(w, ev->atom);
+ }
+ }
+
+ // Check for other atoms we are tracking
+ for (latom_t *platom = ps->track_atom_lst; platom; platom = platom->next) {
+ if (platom->atom == ev->atom) {
+ auto w = find_managed_win(ps, ev->window);
+ if (!w) {
+ w = find_toplevel(ps, ev->window);
+ }
+ if (w) {
+ // Set FACTOR_CHANGED so rules based on properties will be
+ // re-evaluated.
+ // Don't need to set property stale here, since that only
+ // concerns properties we explicitly check.
+ win_set_flags(w, WIN_FLAGS_FACTOR_CHANGED);
+ }
+ break;
+ }
+ }
+}
+
+static inline void repair_win(session_t *ps, struct managed_win *w) {
+ // Only mapped window can receive damages
+ assert(win_is_mapped_in_x(w));
+
+ region_t parts;
+ pixman_region32_init(&parts);
+
+ if (!w->ever_damaged) {
+ win_extents(w, &parts);
+ set_ignore_cookie(
+ ps, xcb_damage_subtract(ps->c, w->damage, XCB_NONE, XCB_NONE));
+ } else {
+ set_ignore_cookie(
+ ps, xcb_damage_subtract(ps->c, w->damage, XCB_NONE, ps->damaged_region));
+ x_fetch_region(ps->c, ps->damaged_region, &parts);
+ pixman_region32_translate(&parts, w->g.x + w->g.border_width,
+ w->g.y + w->g.border_width);
+ }
+
+ log_trace("Mark window %#010x (%s) as having received damage", w->base.id, w->name);
+ w->ever_damaged = true;
+ w->pixmap_damaged = true;
+
+ // Why care about damage when screen is unredirected?
+ // We will force full-screen repaint on redirection.
+ if (!ps->redirected) {
+ pixman_region32_fini(&parts);
+ return;
+ }
+
+ // Remove the part in the damage area that could be ignored
+ if (w->reg_ignore && win_is_region_ignore_valid(ps, w)) {
+ pixman_region32_subtract(&parts, &parts, w->reg_ignore);
+ }
+
+ add_damage(ps, &parts);
+ pixman_region32_fini(&parts);
+}
+
+static inline void ev_damage_notify(session_t *ps, xcb_damage_notify_event_t *de) {
+ /*
+ if (ps->root == de->drawable) {
+ root_damaged();
+ return;
+ } */
+
+ auto w = find_managed_win(ps, de->drawable);
+
+ if (!w) {
+ return;
+ }
+
+ repair_win(ps, w);
+}
+
+static inline void ev_shape_notify(session_t *ps, xcb_shape_notify_event_t *ev) {
+ auto w = find_managed_win(ps, ev->affected_window);
+ if (!w || w->a.map_state == XCB_MAP_STATE_UNMAPPED) {
+ return;
+ }
+
+ /*
+ * Empty bounding_shape may indicated an
+ * unmapped/destroyed window, in which case
+ * seemingly BadRegion errors would be triggered
+ * if we attempt to rebuild border_size
+ */
+ // Mark the old bounding shape as damaged
+ if (!win_check_flags_any(w, WIN_FLAGS_SIZE_STALE | WIN_FLAGS_POSITION_STALE)) {
+ region_t tmp = win_get_bounding_shape_global_by_val(w);
+ add_damage(ps, &tmp);
+ pixman_region32_fini(&tmp);
+ }
+ w->reg_ignore_valid = false;
+
+ win_set_flags(w, WIN_FLAGS_SIZE_STALE);
+ ps->pending_updates = true;
+}
+
+static inline void
+ev_selection_clear(session_t *ps, xcb_selection_clear_event_t attr_unused *ev) {
+ // The only selection we own is the _NET_WM_CM_Sn selection.
+ // If we lose that one, we should exit.
+ log_fatal("Another composite manager started and took the _NET_WM_CM_Sn "
+ "selection.");
+ quit(ps);
+}
+
+void ev_handle(session_t *ps, xcb_generic_event_t *ev) {
+ if ((ev->response_type & 0x7f) != KeymapNotify) {
+ discard_ignore(ps, ev->full_sequence);
+ }
+
+ xcb_window_t wid = ev_window(ps, ev);
+ if (ev->response_type != ps->damage_event + XCB_DAMAGE_NOTIFY) {
+ log_debug("event %10.10s serial %#010x window %#010x \"%s\"",
+ ev_name(ps, ev), ev->full_sequence, wid, ev_window_name(ps, wid));
+ } else {
+ log_trace("event %10.10s serial %#010x window %#010x \"%s\"",
+ ev_name(ps, ev), ev->full_sequence, wid, ev_window_name(ps, wid));
+ }
+
+ // Check if a custom XEvent constructor was registered in xlib for this event
+ // type, and call it discarding the constructed XEvent if any. XESetWireToEvent
+ // might be used by libraries to intercept messages from the X server e.g. the
+ // OpenGL lib waiting for DRI2 events.
+
+ // XXX This exists to workaround compton issue #33, #34, #47
+ // For even more details, see:
+ // https://bugs.freedesktop.org/show_bug.cgi?id=35945
+ // https://lists.freedesktop.org/archives/xcb/2011-November/007337.html
+ auto proc = XESetWireToEvent(ps->dpy, ev->response_type, 0);
+ if (proc) {
+ XESetWireToEvent(ps->dpy, ev->response_type, proc);
+ XEvent dummy;
+
+ // Stop Xlib from complaining about lost sequence numbers.
+ // proc might also just be Xlib internal event processing functions, and
+ // because they probably won't see all X replies, they will complain about
+ // missing sequence numbers.
+ //
+ // We only need the low 16 bits
+ ev->sequence = (uint16_t)(LastKnownRequestProcessed(ps->dpy) & 0xffff);
+ proc(ps->dpy, &dummy, (xEvent *)ev);
+ }
+
+ // XXX redraw needs to be more fine grained
+ queue_redraw(ps);
+
+ switch (ev->response_type) {
+ case FocusIn: ev_focus_in(ps, (xcb_focus_in_event_t *)ev); break;
+ case FocusOut: ev_focus_out(ps, (xcb_focus_out_event_t *)ev); break;
+ case CreateNotify: ev_create_notify(ps, (xcb_create_notify_event_t *)ev); break;
+ case ConfigureNotify:
+ ev_configure_notify(ps, (xcb_configure_notify_event_t *)ev);
+ break;
+ case DestroyNotify:
+ ev_destroy_notify(ps, (xcb_destroy_notify_event_t *)ev);
+ break;
+ case MapNotify: ev_map_notify(ps, (xcb_map_notify_event_t *)ev); break;
+ case UnmapNotify: ev_unmap_notify(ps, (xcb_unmap_notify_event_t *)ev); break;
+ case ReparentNotify:
+ ev_reparent_notify(ps, (xcb_reparent_notify_event_t *)ev);
+ break;
+ case CirculateNotify:
+ ev_circulate_notify(ps, (xcb_circulate_notify_event_t *)ev);
+ break;
+ case Expose: ev_expose(ps, (xcb_expose_event_t *)ev); break;
+ case PropertyNotify:
+ ev_property_notify(ps, (xcb_property_notify_event_t *)ev);
+ break;
+ case SelectionClear:
+ ev_selection_clear(ps, (xcb_selection_clear_event_t *)ev);
+ break;
+ case 0: ev_xcb_error(ps, (xcb_generic_error_t *)ev); break;
+ default:
+ if (ps->shape_exists && ev->response_type == ps->shape_event) {
+ ev_shape_notify(ps, (xcb_shape_notify_event_t *)ev);
+ break;
+ }
+ if (ps->randr_exists &&
+ ev->response_type == (ps->randr_event + XCB_RANDR_SCREEN_CHANGE_NOTIFY)) {
+ set_root_flags(ps, ROOT_FLAGS_SCREEN_CHANGE);
+ break;
+ }
+ if (ps->damage_event + XCB_DAMAGE_NOTIFY == ev->response_type) {
+ ev_damage_notify(ps, (xcb_damage_notify_event_t *)ev);
+ break;
+ }
+ }
+}
diff --git a/src/event.h b/src/event.h
new file mode 100644
index 0000000..629dec0
--- /dev/null
+++ b/src/event.h
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) 2019, Yuxuan Shui <[email protected]>
+
+#include <xcb/xcb.h>
+
+#include "common.h"
+
+void ev_handle(session_t *ps, xcb_generic_event_t *ev);
diff --git a/src/file_watch.c b/src/file_watch.c
new file mode 100644
index 0000000..faa8f68
--- /dev/null
+++ b/src/file_watch.c
@@ -0,0 +1,188 @@
+#include <errno.h>
+#include <string.h>
+#ifdef HAS_INOTIFY
+#include <sys/inotify.h>
+#elif HAS_KQUEUE
+// clang-format off
+#include <sys/types.h>
+// clang-format on
+#include <sys/event.h>
+#undef EV_ERROR // Avoid clashing with libev's EV_ERROR
+#include <fcntl.h> // For O_RDONLY
+#include <sys/time.h> // For struct timespec
+#include <unistd.h> // For open
+#endif
+
+#include <ev.h>
+#include <uthash.h>
+
+#include "file_watch.h"
+#include "list.h"
+#include "log.h"
+#include "utils.h"
+
+struct watched_file {
+ int wd;
+ void *ud;
+ file_watch_cb_t cb;
+
+ UT_hash_handle hh;
+};
+
+struct file_watch_registry {
+ struct ev_io w;
+
+ struct watched_file *reg;
+};
+
+static void file_watch_ev_cb(EV_P attr_unused, struct ev_io *w, int revent attr_unused) {
+ auto fwr = (struct file_watch_registry *)w;
+
+ while (true) {
+ int wd = -1;
+#ifdef HAS_INOTIFY
+ struct inotify_event inotify_event;
+ auto ret = read(w->fd, &inotify_event, sizeof(struct inotify_event));
+ if (ret < 0) {
+ if (errno != EAGAIN) {
+ log_error_errno("Failed to read from inotify fd");
+ }
+ break;
+ }
+ wd = inotify_event.wd;
+#elif HAS_KQUEUE
+ struct kevent ev;
+ struct timespec timeout = {0};
+ int ret = kevent(fwr->w.fd, NULL, 0, &ev, 1, &timeout);
+ if (ret <= 0) {
+ if (ret < 0) {
+ log_error_errno("Failed to get kevent");
+ }
+ break;
+ }
+ wd = (int)ev.ident;
+#else
+ assert(false);
+#endif
+
+ struct watched_file *wf = NULL;
+ HASH_FIND_INT(fwr->reg, &wd, wf);
+ if (!wf) {
+ log_warn("Got notification for a file I didn't watch.");
+ continue;
+ }
+ wf->cb(wf->ud);
+ }
+}
+
+void *file_watch_init(EV_P) {
+ log_debug("Starting watching for file changes");
+ int fd = -1;
+#ifdef HAS_INOTIFY
+ fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
+ if (fd < 0) {
+ log_error_errno("inotify_init1 failed");
+ return NULL;
+ }
+#elif HAS_KQUEUE
+ fd = kqueue();
+ if (fd < 0) {
+ log_error_errno("Failed to create kqueue");
+ return NULL;
+ }
+#else
+ log_info("No file watching support found on the host system.");
+ return NULL;
+#endif
+ auto fwr = ccalloc(1, struct file_watch_registry);
+ ev_io_init(&fwr->w, file_watch_ev_cb, fd, EV_READ);
+ ev_io_start(EV_A_ & fwr->w);
+
+ return fwr;
+}
+
+void file_watch_destroy(EV_P_ void *_fwr) {
+ log_debug("Stopping watching for file changes");
+ auto fwr = (struct file_watch_registry *)_fwr;
+ struct watched_file *i, *tmp;
+
+ HASH_ITER(hh, fwr->reg, i, tmp) {
+ HASH_DEL(fwr->reg, i);
+#ifdef HAS_KQUEUE
+ // kqueue watch descriptors are file descriptors of
+ // the files we are watching, so we need to close
+ // them
+ close(i->wd);
+#endif
+ free(i);
+ }
+
+ ev_io_stop(EV_A_ & fwr->w);
+ close(fwr->w.fd);
+ free(fwr);
+}
+
+bool file_watch_add(void *_fwr, const char *filename, file_watch_cb_t cb, void *ud) {
+ log_debug("Adding \"%s\" to watched files", filename);
+ auto fwr = (struct file_watch_registry *)_fwr;
+ int wd = -1;
+
+ struct stat statbuf;
+ int ret = stat(filename, &statbuf);
+ if (ret < 0) {
+ log_error_errno("Failed to retrieve information about file \"%s\"", filename);
+ return false;
+ }
+ if (!S_ISREG(statbuf.st_mode)) {
+ log_info("\"%s\" is not a regular file, not watching it.", filename);
+ return false;
+ }
+
+#ifdef HAS_INOTIFY
+ wd = inotify_add_watch(fwr->w.fd, filename,
+ IN_CLOSE_WRITE | IN_MOVE_SELF | IN_DELETE_SELF);
+ if (wd < 0) {
+ log_error_errno("Failed to watch file \"%s\"", filename);
+ return false;
+ }
+#elif HAS_KQUEUE
+ wd = open(filename, O_RDONLY);
+ if (wd < 0) {
+ log_error_errno("Cannot open file \"%s\" for watching", filename);
+ return false;
+ }
+
+ uint32_t fflags = NOTE_DELETE | NOTE_RENAME | NOTE_REVOKE | NOTE_ATTRIB;
+ // NOTE_CLOSE_WRITE is relatively new, so we cannot just use it
+#ifdef NOTE_CLOSE_WRITE
+ fflags |= NOTE_CLOSE_WRITE;
+#else
+ // NOTE_WRITE will receive notification more frequent than necessary, so is less
+ // preferrable
+ fflags |= NOTE_WRITE;
+#endif
+ struct kevent ev = {
+ .ident = (unsigned int)wd, // the wd < 0 case is checked above
+ .filter = EVFILT_VNODE,
+ .flags = EV_ADD | EV_CLEAR,
+ .fflags = fflags,
+ .data = 0,
+ .udata = NULL,
+ };
+ if (kevent(fwr->w.fd, &ev, 1, NULL, 0, NULL) < 0) {
+ log_error_errno("Failed to register kevent");
+ close(wd);
+ return false;
+ }
+#else
+ assert(false);
+#endif // HAS_KQUEUE
+
+ auto w = ccalloc(1, struct watched_file);
+ w->wd = wd;
+ w->cb = cb;
+ w->ud = ud;
+
+ HASH_ADD_INT(fwr->reg, wd, w);
+ return true;
+}
diff --git a/src/file_watch.h b/src/file_watch.h
new file mode 100644
index 0000000..c249cd2
--- /dev/null
+++ b/src/file_watch.h
@@ -0,0 +1,10 @@
+#pragma once
+#include <stdbool.h>
+
+#include <ev.h>
+
+typedef void (*file_watch_cb_t)(void *);
+
+void *file_watch_init(EV_P);
+bool file_watch_add(void *, const char *, file_watch_cb_t, void *);
+void file_watch_destroy(EV_P_ void *);
diff --git a/src/kernel.c b/src/kernel.c
new file mode 100644
index 0000000..5151045
--- /dev/null
+++ b/src/kernel.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) Yuxuan Shui <[email protected]>
+
+#include <assert.h>
+#include <math.h>
+
+#include "compiler.h"
+#include "kernel.h"
+#include "log.h"
+#include "utils.h"
+
+/// Sum a region convolution kernel. Region is defined by a width x height rectangle whose
+/// top left corner is at (x, y)
+double sum_kernel(const conv *map, int x, int y, int width, int height) {
+ double ret = 0;
+
+ // Compute sum of values which are "in range"
+ int xstart = normalize_i_range(x, 0, map->w),
+ xend = normalize_i_range(width + x, 0, map->w);
+ int ystart = normalize_i_range(y, 0, map->h),
+ yend = normalize_i_range(height + y, 0, map->h);
+ assert(yend >= ystart && xend >= xstart);
+
+ int d = map->w;
+ if (map->rsum) {
+ // See sum_kernel_preprocess
+ double v1 = xstart ? map->rsum[(yend - 1) * d + xstart - 1] : 0;
+ double v2 = ystart ? map->rsum[(ystart - 1) * d + xend - 1] : 0;
+ double v3 = (xstart && ystart) ? map->rsum[(ystart - 1) * d + xstart - 1] : 0;
+ return map->rsum[(yend - 1) * d + xend - 1] - v1 - v2 + v3;
+ }
+
+ for (int yi = ystart; yi < yend; yi++) {
+ for (int xi = xstart; xi < xend; xi++) {
+ ret += map->data[yi * d + xi];
+ }
+ }
+
+ return ret;
+}
+
+double sum_kernel_normalized(const conv *map, int x, int y, int width, int height) {
+ double ret = sum_kernel(map, x, y, width, height);
+ if (ret < 0) {
+ ret = 0;
+ }
+ if (ret > 1) {
+ ret = 1;
+ }
+ return ret;
+}
+
+static inline double attr_const gaussian(double r, double x, double y) {
+ // Formula can be found here:
+ // https://en.wikipedia.org/wiki/Gaussian_blur#Mathematics
+ // Except a special case for r == 0 to produce sharp shadows
+ if (r == 0)
+ return 1;
+ return exp(-0.5 * (x * x + y * y) / (r * r)) / (2 * M_PI * r * r);
+}
+
+conv *gaussian_kernel(double r, int size) {
+ conv *c;
+ int center = size / 2;
+ double t;
+ assert(size % 2 == 1);
+
+ c = cvalloc(sizeof(conv) + (size_t)(size * size) * sizeof(double));
+ c->w = c->h = size;
+ c->rsum = NULL;
+ t = 0.0;
+
+ for (int y = 0; y < size; y++) {
+ for (int x = 0; x < size; x++) {
+ double g = gaussian(r, x - center, y - center);
+ t += g;
+ c->data[y * size + x] = g;
+ }
+ }
+
+ for (int y = 0; y < size; y++) {
+ for (int x = 0; x < size; x++) {
+ c->data[y * size + x] /= t;
+ }
+ }
+
+ return c;
+}
+
+/// Estimate the element of the sum of the first row in a gaussian kernel with standard
+/// deviation `r` and size `size`,
+static inline double estimate_first_row_sum(double size, double r) {
+ double factor = erf(size / r / sqrt(2));
+ double a = exp(-0.5 * size * size / (r * r)) / sqrt(2 * M_PI) / r;
+ return a / factor;
+}
+
+/// Pick a suitable gaussian kernel radius for a given kernel size. The returned radius
+/// is the maximum possible radius (<= size*2) that satisfies no sum of the rows in
+/// the kernel are less than `row_limit` (up to certain precision).
+static inline double gaussian_kernel_std_for_size(int size, double row_limit) {
+ assert(size > 0);
+ if (row_limit >= 1.0 / 2.0 / size) {
+ return size * 2;
+ }
+ double l = 0, r = size * 2;
+ while (r - l > 1e-2) {
+ double mid = (l + r) / 2.0;
+ double vmid = estimate_first_row_sum(size, mid);
+ if (vmid > row_limit) {
+ r = mid;
+ } else {
+ l = mid;
+ }
+ }
+ return (l + r) / 2.0;
+}
+
+/// Create a gaussian kernel with auto detected standard deviation. The choosen standard
+/// deviation tries to make sure the outer most pixels of the shadow are completely
+/// transparent, so the transition from shadow to the background is smooth.
+///
+/// @param[in] shadow_radius the radius of the shadow
+conv *gaussian_kernel_autodetect_deviation(int shadow_radius) {
+ assert(shadow_radius >= 0);
+ int size = shadow_radius * 2 + 1;
+
+ if (shadow_radius == 0) {
+ return gaussian_kernel(0, size);
+ }
+ double std = gaussian_kernel_std_for_size(shadow_radius, 1.0 / 256.0);
+ return gaussian_kernel(std, size);
+}
+
+/// preprocess kernels to make shadow generation faster
+/// shadow_sum[x*d+y] is the sum of the kernel from (0, 0) to (x, y), inclusive
+void sum_kernel_preprocess(conv *map) {
+ if (map->rsum) {
+ free(map->rsum);
+ }
+
+ auto sum = map->rsum = ccalloc(map->w * map->h, double);
+ sum[0] = map->data[0];
+
+ for (int x = 1; x < map->w; x++) {
+ sum[x] = sum[x - 1] + map->data[x];
+ }
+
+ const int d = map->w;
+ for (int y = 1; y < map->h; y++) {
+ sum[y * d] = sum[(y - 1) * d] + map->data[y * d];
+ for (int x = 1; x < map->w; x++) {
+ double tmp = sum[(y - 1) * d + x] + sum[y * d + x - 1] -
+ sum[(y - 1) * d + x - 1];
+ sum[y * d + x] = tmp + map->data[y * d + x];
+ }
+ }
+}
+
+// vim: set noet sw=8 ts=8 :
diff --git a/src/kernel.h b/src/kernel.h
new file mode 100644
index 0000000..251d127
--- /dev/null
+++ b/src/kernel.h
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) Yuxuan Shui <[email protected]>
+
+#pragma once
+#include <stdlib.h>
+#include "compiler.h"
+
+/// Code for generating convolution kernels
+
+typedef struct conv {
+ int w, h;
+ double *rsum;
+ double data[];
+} conv;
+
+/// Calculate the sum of a rectangle part of the convolution kernel
+/// the rectangle is defined by top left (x, y), and a size (width x height)
+double attr_pure sum_kernel(const conv *map, int x, int y, int width, int height);
+double attr_pure sum_kernel_normalized(const conv *map, int x, int y, int width, int height);
+
+/// Create a kernel with gaussian distribution with standard deviation `r`, and size
+/// `size`.
+conv *gaussian_kernel(double r, int size);
+
+/// Create a gaussian kernel with auto detected standard deviation. The choosen standard
+/// deviation tries to make sure the outer most pixels of the shadow are completely
+/// transparent.
+///
+/// @param[in] shadow_radius the radius of the shadow
+conv *gaussian_kernel_autodetect_deviation(int shadow_radius);
+
+/// preprocess kernels to make shadow generation faster
+/// shadow_sum[x*d+y] is the sum of the kernel from (0, 0) to (x, y), inclusive
+void sum_kernel_preprocess(conv *map);
+
+static inline void free_conv(conv *k) {
+ free(k->rsum);
+ free(k);
+}
diff --git a/src/list.h b/src/list.h
new file mode 100644
index 0000000..19e2c2c
--- /dev/null
+++ b/src/list.h
@@ -0,0 +1,108 @@
+#pragma once
+#include <stdbool.h>
+#include <stddef.h>
+
+/**
+ * container_of - cast a member of a structure out to the containing structure
+ * @ptr: the pointer to the member.
+ * @type: the type of the container struct this is embedded in.
+ * @member: the name of the member within the struct.
+ *
+ */
+#define container_of(ptr, type, member) \
+ ({ \
+ const __typeof__(((type *)0)->member) *__mptr = (ptr); \
+ (type *)((char *)__mptr - offsetof(type, member)); \
+ })
+
+struct list_node {
+ struct list_node *next, *prev;
+};
+
+#define list_entry(ptr, type, node) container_of(ptr, type, node)
+#define list_next_entry(ptr, node) list_entry((ptr)->node.next, __typeof__(*(ptr)), node)
+#define list_prev_entry(ptr, node) list_entry((ptr)->node.prev, __typeof__(*(ptr)), node)
+
+/// Insert a new node between two adjacent nodes in the list
+static inline void __list_insert_between(struct list_node *prev, struct list_node *next,
+ struct list_node *new_) {
+ new_->prev = prev;
+ new_->next = next;
+ next->prev = new_;
+ prev->next = new_;
+}
+
+/// Insert a new node after `curr`
+static inline void list_insert_after(struct list_node *curr, struct list_node *new_) {
+ __list_insert_between(curr, curr->next, new_);
+}
+
+/// Insert a new node before `curr`
+static inline void list_insert_before(struct list_node *curr, struct list_node *new_) {
+ __list_insert_between(curr->prev, curr, new_);
+}
+
+/// Link two nodes in the list, so `next` becomes the successor node of `prev`
+static inline void __list_link(struct list_node *prev, struct list_node *next) {
+ next->prev = prev;
+ prev->next = next;
+}
+
+/// Remove a node from the list
+static inline void list_remove(struct list_node *to_remove) {
+ __list_link(to_remove->prev, to_remove->next);
+ to_remove->prev = (void *)-1;
+ to_remove->next = (void *)-2;
+}
+
+/// Move `to_move` so that it's before `new_next`
+static inline void list_move_before(struct list_node *to_move, struct list_node *new_next) {
+ list_remove(to_move);
+ list_insert_before(new_next, to_move);
+}
+
+/// Move `to_move` so that it's after `new_prev`
+static inline void list_move_after(struct list_node *to_move, struct list_node *new_prev) {
+ list_remove(to_move);
+ list_insert_after(new_prev, to_move);
+}
+
+/// Initialize a list node that's intended to be the head node
+static inline void list_init_head(struct list_node *head) {
+ head->next = head->prev = head;
+}
+
+/// Replace list node `old` with `n`
+static inline void list_replace(struct list_node *old, struct list_node *n) {
+ __list_insert_between(old->prev, old->next, n);
+ old->prev = (void *)-1;
+ old->next = (void *)-2;
+}
+
+/// Return true if head is the only node in the list. Under usual circumstances this means
+/// the list is empty
+static inline bool list_is_empty(const struct list_node *head) {
+ return head->prev == head;
+}
+
+/// Return true if `to_check` is the first node in list headed by `head`
+static inline bool
+list_node_is_first(const struct list_node *head, const struct list_node *to_check) {
+ return head->next == to_check;
+}
+
+/// Return true if `to_check` is the last node in list headed by `head`
+static inline bool
+list_node_is_last(const struct list_node *head, const struct list_node *to_check) {
+ return head->prev == to_check;
+}
+
+#define list_foreach(type, i, head, member) \
+ for (type *i = list_entry((head)->next, type, member); &i->member != (head); \
+ i = list_next_entry(i, member))
+
+/// Like list_for_each, but it's safe to remove the current list node from the list
+#define list_foreach_safe(type, i, head, member) \
+ for (type *i = list_entry((head)->next, type, member), \
+ *__tmp = list_next_entry(i, member); \
+ &i->member != (head); i = __tmp, __tmp = list_next_entry(i, member))
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 0000000..0b663e7
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,376 @@
+#include <assert.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifdef CONFIG_OPENGL
+#include <GL/gl.h>
+#include "backend/gl/gl_common.h"
+#include "backend/gl/glx.h"
+#endif
+
+#include "compiler.h"
+#include "log.h"
+#include "utils.h"
+
+thread_local struct log *tls_logger;
+
+struct log_target;
+
+struct log {
+ struct log_target *head;
+
+ int log_level;
+};
+
+struct log_target {
+ const struct log_ops *ops;
+ struct log_target *next;
+};
+
+struct log_ops {
+ void (*write)(struct log_target *, const char *, size_t);
+ void (*writev)(struct log_target *, const struct iovec *, int vcnt);
+ void (*destroy)(struct log_target *);
+
+ /// Additional strings to print around the log_level string
+ const char *(*colorize_begin)(enum log_level);
+ const char *(*colorize_end)(enum log_level);
+};
+
+/// Fallback writev for targets don't implement it
+static attr_unused void
+log_default_writev(struct log_target *tgt, const struct iovec *vec, int vcnt) {
+ size_t total = 0;
+ for (int i = 0; i < vcnt; i++) {
+ total += vec[i].iov_len;
+ }
+
+ if (!total) {
+ // Nothing to write
+ return;
+ }
+ char *buf = ccalloc(total, char);
+ total = 0;
+ for (int i = 0; i < vcnt; i++) {
+ memcpy(buf + total, vec[i].iov_base, vec[i].iov_len);
+ total += vec[i].iov_len;
+ }
+ tgt->ops->write(tgt, buf, total);
+ free(buf);
+}
+
+static attr_const const char *log_level_to_string(enum log_level level) {
+ switch (level) {
+ case LOG_LEVEL_TRACE: return "TRACE";
+ case LOG_LEVEL_DEBUG: return "DEBUG";
+ case LOG_LEVEL_INFO: return "INFO";
+ case LOG_LEVEL_WARN: return "WARN";
+ case LOG_LEVEL_ERROR: return "ERROR";
+ case LOG_LEVEL_FATAL: return "FATAL ERROR";
+ default: return "????";
+ }
+}
+
+enum log_level string_to_log_level(const char *str) {
+ if (strcasecmp(str, "TRACE") == 0)
+ return LOG_LEVEL_TRACE;
+ else if (strcasecmp(str, "DEBUG") == 0)
+ return LOG_LEVEL_DEBUG;
+ else if (strcasecmp(str, "INFO") == 0)
+ return LOG_LEVEL_INFO;
+ else if (strcasecmp(str, "WARN") == 0)
+ return LOG_LEVEL_WARN;
+ else if (strcasecmp(str, "ERROR") == 0)
+ return LOG_LEVEL_ERROR;
+ return LOG_LEVEL_INVALID;
+}
+
+struct log *log_new(void) {
+ auto ret = cmalloc(struct log);
+ ret->log_level = LOG_LEVEL_WARN;
+ ret->head = NULL;
+ return ret;
+}
+
+void log_add_target(struct log *l, struct log_target *tgt) {
+ assert(tgt->ops->writev);
+ tgt->next = l->head;
+ l->head = tgt;
+}
+
+/// Remove a previously added log target for a log struct, and destroy it. If the log
+/// target was never added, nothing happens.
+void log_remove_target(struct log *l, struct log_target *tgt) {
+ struct log_target *now = l->head, **prev = &l->head;
+ while (now) {
+ if (now == tgt) {
+ *prev = now->next;
+ tgt->ops->destroy(tgt);
+ break;
+ }
+ prev = &now->next;
+ now = now->next;
+ }
+}
+
+/// Destroy a log struct and every log target added to it
+void log_destroy(struct log *l) {
+ // free all tgt
+ struct log_target *head = l->head;
+ while (head) {
+ auto next = head->next;
+ head->ops->destroy(head);
+ head = next;
+ }
+ free(l);
+}
+
+void log_set_level(struct log *l, int level) {
+ assert(level <= LOG_LEVEL_FATAL && level >= 0);
+ l->log_level = level;
+}
+
+enum log_level log_get_level(const struct log *l) {
+ return l->log_level;
+}
+
+attr_printf(4, 5) void log_printf(struct log *l, int level, const char *func,
+ const char *fmt, ...) {
+ assert(level <= LOG_LEVEL_FATAL && level >= 0);
+ if (level < l->log_level)
+ return;
+
+ char *buf = NULL;
+ va_list args;
+
+ va_start(args, fmt);
+ int blen = vasprintf(&buf, fmt, args);
+ va_end(args);
+
+ if (blen < 0 || !buf) {
+ free(buf);
+ return;
+ }
+
+ struct timespec ts;
+ timespec_get(&ts, TIME_UTC);
+ struct tm now;
+ localtime_r(&ts.tv_sec, &now);
+ char time_buf[100];
+ strftime(time_buf, sizeof time_buf, "%x %T", &now);
+
+ char *time = NULL;
+ int tlen = asprintf(&time, "%s.%03ld", time_buf, ts.tv_nsec / 1000000);
+ if (tlen < 0 || !time) {
+ free(buf);
+ free(time);
+ return;
+ }
+
+ const char *log_level_str = log_level_to_string(level);
+ size_t llen = strlen(log_level_str);
+ size_t flen = strlen(func);
+
+ struct log_target *head = l->head;
+ while (head) {
+ const char *p = "", *s = "";
+ size_t plen = 0, slen = 0;
+
+ if (head->ops->colorize_begin) {
+ // construct target specific prefix
+ p = head->ops->colorize_begin(level);
+ plen = strlen(p);
+ if (head->ops->colorize_end) {
+ s = head->ops->colorize_end(level);
+ slen = strlen(s);
+ }
+ }
+ head->ops->writev(
+ head,
+ (struct iovec[]){{.iov_base = "[ ", .iov_len = 2},
+ {.iov_base = time, .iov_len = (size_t)tlen},
+ {.iov_base = " ", .iov_len = 1},
+ {.iov_base = (void *)func, .iov_len = flen},
+ {.iov_base = " ", .iov_len = 1},
+ {.iov_base = (void *)p, .iov_len = plen},
+ {.iov_base = (void *)log_level_str, .iov_len = llen},
+ {.iov_base = (void *)s, .iov_len = slen},
+ {.iov_base = " ] ", .iov_len = 3},
+ {.iov_base = buf, .iov_len = (size_t)blen},
+ {.iov_base = "\n", .iov_len = 1}},
+ 11);
+ head = head->next;
+ }
+ free(time);
+ free(buf);
+}
+
+/// A trivial deinitializer that simply frees the memory
+static attr_unused void logger_trivial_destroy(struct log_target *tgt) {
+ free(tgt);
+}
+
+/// A null log target that does nothing
+static const struct log_ops null_logger_ops;
+static struct log_target null_logger_target = {
+ .ops = &null_logger_ops,
+};
+
+struct log_target *null_logger_new(void) {
+ return &null_logger_target;
+}
+
+static void null_logger_write(struct log_target *tgt attr_unused,
+ const char *str attr_unused, size_t len attr_unused) {
+ return;
+}
+
+static void null_logger_writev(struct log_target *tgt attr_unused,
+ const struct iovec *vec attr_unused, int vcnt attr_unused) {
+ return;
+}
+
+static const struct log_ops null_logger_ops = {
+ .write = null_logger_write,
+ .writev = null_logger_writev,
+};
+
+/// A file based logger that writes to file (or stdout/stderr)
+struct file_logger {
+ struct log_target tgt;
+ FILE *f;
+ struct log_ops ops;
+};
+
+static void file_logger_write(struct log_target *tgt, const char *str, size_t len) {
+ auto f = (struct file_logger *)tgt;
+ fwrite(str, 1, len, f->f);
+}
+
+static void file_logger_writev(struct log_target *tgt, const struct iovec *vec, int vcnt) {
+ auto f = (struct file_logger *)tgt;
+ fflush(f->f);
+ writev(fileno(f->f), vec, vcnt);
+}
+
+static void file_logger_destroy(struct log_target *tgt) {
+ auto f = (struct file_logger *)tgt;
+ fclose(f->f);
+ free(tgt);
+}
+
+#define ANSI(x) "\033[" x "m"
+static const char *terminal_colorize_begin(enum log_level level) {
+ switch (level) {
+ case LOG_LEVEL_TRACE: return ANSI("30;2");
+ case LOG_LEVEL_DEBUG: return ANSI("37;2");
+ case LOG_LEVEL_INFO: return ANSI("92");
+ case LOG_LEVEL_WARN: return ANSI("33");
+ case LOG_LEVEL_ERROR: return ANSI("31;1");
+ case LOG_LEVEL_FATAL: return ANSI("30;103;1");
+ default: return "";
+ }
+}
+
+static const char *terminal_colorize_end(enum log_level level attr_unused) {
+ return ANSI("0");
+}
+#undef PREFIX
+
+static const struct log_ops file_logger_ops = {
+ .write = file_logger_write,
+ .writev = file_logger_writev,
+ .destroy = file_logger_destroy,
+};
+
+struct log_target *file_logger_new(const char *filename) {
+ FILE *f = fopen(filename, "a");
+ if (!f) {
+ return NULL;
+ }
+
+ auto ret = cmalloc(struct file_logger);
+ ret->tgt.ops = &ret->ops;
+ ret->f = f;
+
+ // Always assume a file is not a terminal
+ ret->ops = file_logger_ops;
+
+ return &ret->tgt;
+}
+
+struct log_target *stderr_logger_new(void) {
+ int fd = dup(STDERR_FILENO);
+ if (fd < 0) {
+ return NULL;
+ }
+
+ FILE *f = fdopen(fd, "w");
+ if (!f) {
+ return NULL;
+ }
+
+ auto ret = cmalloc(struct file_logger);
+ ret->tgt.ops = &ret->ops;
+ ret->f = f;
+ ret->ops = file_logger_ops;
+
+ if (isatty(fd)) {
+ ret->ops.colorize_begin = terminal_colorize_begin;
+ ret->ops.colorize_end = terminal_colorize_end;
+ }
+ return &ret->tgt;
+}
+
+#ifdef CONFIG_OPENGL
+/// An opengl logger that can be used for logging into opengl debugging tools,
+/// such as apitrace
+struct gl_string_marker_logger {
+ struct log_target tgt;
+ PFNGLSTRINGMARKERGREMEDYPROC gl_string_marker;
+};
+
+static void
+gl_string_marker_logger_write(struct log_target *tgt, const char *str, size_t len) {
+ auto g = (struct gl_string_marker_logger *)tgt;
+ // strip newlines at the end of the string
+ while (len > 0 && str[len-1] == '\n') {
+ len--;
+ }
+ g->gl_string_marker((GLsizei)len, str);
+}
+
+static const struct log_ops gl_string_marker_logger_ops = {
+ .write = gl_string_marker_logger_write,
+ .writev = log_default_writev,
+ .destroy = logger_trivial_destroy,
+};
+
+struct log_target *gl_string_marker_logger_new(void) {
+ if (!gl_has_extension("GL_GREMEDY_string_marker")) {
+ return NULL;
+ }
+
+ void *fnptr = glXGetProcAddress((GLubyte *)"glStringMarkerGREMEDY");
+ if (!fnptr)
+ return NULL;
+
+ auto ret = cmalloc(struct gl_string_marker_logger);
+ ret->tgt.ops = &gl_string_marker_logger_ops;
+ ret->gl_string_marker = fnptr;
+ return &ret->tgt;
+}
+
+#else
+struct log_target *gl_string_marker_logger_new(void) {
+ return NULL;
+}
+#endif
+
+// vim: set noet sw=8 ts=8:
diff --git a/src/log.h b/src/log.h
new file mode 100644
index 0000000..e40fe3c
--- /dev/null
+++ b/src/log.h
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) 2018 Yuxuan Shui <[email protected]>
+
+#pragma once
+#include <assert.h>
+#include <stdio.h>
+
+#include "compiler.h"
+
+enum log_level {
+ LOG_LEVEL_INVALID = -1,
+ LOG_LEVEL_TRACE = 0,
+ LOG_LEVEL_DEBUG,
+ LOG_LEVEL_INFO,
+ LOG_LEVEL_WARN,
+ LOG_LEVEL_ERROR,
+ LOG_LEVEL_FATAL,
+};
+
+#define LOG_UNLIKELY(level, x, ...) \
+ do { \
+ if (unlikely(LOG_LEVEL_##level >= log_get_level_tls())) { \
+ log_printf(tls_logger, LOG_LEVEL_##level, __func__, x, ##__VA_ARGS__); \
+ } \
+ } while (0)
+
+#define LOG(level, x, ...) \
+ do { \
+ if (LOG_LEVEL_##level >= log_get_level_tls()) { \
+ log_printf(tls_logger, LOG_LEVEL_##level, __func__, x, ##__VA_ARGS__); \
+ } \
+ } while (0)
+#define log_trace(x, ...) LOG_UNLIKELY(TRACE, x, ##__VA_ARGS__)
+#define log_debug(x, ...) LOG_UNLIKELY(DEBUG, x, ##__VA_ARGS__)
+#define log_info(x, ...) LOG(INFO, x, ##__VA_ARGS__)
+#define log_warn(x, ...) LOG(WARN, x, ##__VA_ARGS__)
+#define log_error(x, ...) LOG(ERROR, x, ##__VA_ARGS__)
+#define log_fatal(x, ...) LOG(FATAL, x, ##__VA_ARGS__)
+
+#define log_error_errno(x, ...) LOG(ERROR, x ": %s", ##__VA_ARGS__, strerror(errno))
+
+struct log;
+struct log_target;
+
+attr_printf(4, 5) void log_printf(struct log *, int level, const char *func,
+ const char *fmt, ...);
+
+attr_malloc struct log *log_new(void);
+/// Destroy a log struct and every log target added to it
+attr_nonnull_all void log_destroy(struct log *);
+attr_nonnull(1) void log_set_level(struct log *l, int level);
+attr_pure enum log_level log_get_level(const struct log *l);
+attr_nonnull_all void log_add_target(struct log *, struct log_target *);
+attr_pure enum log_level string_to_log_level(const char *);
+/// Remove a previously added log target for a log struct, and destroy it. If the log
+/// target was never added, nothing happens.
+void log_remove_target(struct log *l, struct log_target *tgt);
+
+extern thread_local struct log *tls_logger;
+
+/// Create a thread local logger
+static inline void log_init_tls(void) {
+ tls_logger = log_new();
+}
+/// Set thread local logger log level
+static inline void log_set_level_tls(int level) {
+ assert(tls_logger);
+ log_set_level(tls_logger, level);
+}
+static inline attr_nonnull_all void log_add_target_tls(struct log_target *tgt) {
+ assert(tls_logger);
+ log_add_target(tls_logger, tgt);
+}
+
+static inline attr_nonnull_all void log_remove_target_tls(struct log_target *tgt) {
+ assert(tls_logger);
+ log_remove_target(tls_logger, tgt);
+}
+
+static inline attr_pure enum log_level log_get_level_tls(void) {
+ assert(tls_logger);
+ return log_get_level(tls_logger);
+}
+
+static inline void log_deinit_tls(void) {
+ assert(tls_logger);
+ log_destroy(tls_logger);
+ tls_logger = NULL;
+}
+
+attr_malloc struct log_target *stderr_logger_new(void);
+attr_malloc struct log_target *file_logger_new(const char *file);
+attr_malloc struct log_target *null_logger_new(void);
+attr_malloc struct log_target *gl_string_marker_logger_new(void);
+
+// vim: set noet sw=8 ts=8:
diff --git a/src/meson.build b/src/meson.build
new file mode 100644
index 0000000..0a882f9
--- /dev/null
+++ b/src/meson.build
@@ -0,0 +1,97 @@
+libev = dependency('libev', required: false)
+if not libev.found()
+ libev = cc.find_library('ev')
+endif
+base_deps = [
+ cc.find_library('m'),
+ libev
+]
+
+srcs = [ files('picom.c', 'win.c', 'c2.c', 'x.c', 'config.c', 'vsync.c', 'utils.c',
+ 'diagnostic.c', 'string_utils.c', 'render.c', 'kernel.c', 'log.c',
+ 'options.c', 'event.c', 'cache.c', 'atom.c', 'file_watch.c') ]
+picom_inc = include_directories('.')
+
+cflags = []
+
+required_xcb_packages = [
+ 'xcb-render', 'xcb-damage', 'xcb-randr', 'xcb-sync', 'xcb-composite',
+ 'xcb-shape', 'xcb-xinerama', 'xcb-xfixes', 'xcb-present', 'xcb-glx', 'xcb'
+]
+
+required_packages = [
+ 'x11', 'x11-xcb', 'xcb-renderutil', 'xcb-image', 'xext', 'pixman-1'
+]
+
+foreach i : required_packages
+ base_deps += [dependency(i, required: true)]
+endforeach
+
+foreach i : required_xcb_packages
+ base_deps += [dependency(i, version: '>=1.12.0', required: true)]
+endforeach
+
+if not cc.has_header('uthash.h')
+ error('Dependency uthash not found')
+endif
+
+deps = []
+
+if get_option('config_file')
+ deps += [dependency('libconfig', version: '>=1.4', required: true)]
+
+ cflags += ['-DCONFIG_LIBCONFIG']
+ srcs += [ 'config_libconfig.c' ]
+endif
+if get_option('regex')
+ pcre = dependency('libpcre', required: true)
+ cflags += ['-DCONFIG_REGEX_PCRE']
+ if pcre.version().version_compare('>=8.20')
+ cflags += ['-DCONFIG_REGEX_PCRE_JIT']
+ endif
+ deps += [pcre]
+endif
+
+if get_option('vsync_drm')
+ cflags += ['-DCONFIG_VSYNC_DRM']
+ deps += [dependency('libdrm', required: true)]
+endif
+
+if get_option('opengl')
+ cflags += ['-DCONFIG_OPENGL', '-DGL_GLEXT_PROTOTYPES']
+ deps += [dependency('gl', required: true)]
+ srcs += [ 'opengl.c' ]
+endif
+
+if get_option('dbus')
+ cflags += ['-DCONFIG_DBUS']
+ deps += [dependency('dbus-1', required: true)]
+ srcs += [ 'dbus.c' ]
+endif
+
+if get_option('xrescheck')
+ cflags += ['-DDEBUG_XRC']
+ srcs += [ 'xrescheck.c' ]
+endif
+
+if get_option('unittest')
+ cflags += ['-DUNIT_TEST']
+endif
+
+host_system = host_machine.system()
+if host_system == 'linux'
+ cflags += ['-DHAS_INOTIFY']
+elif (host_system == 'freebsd' or host_system == 'netbsd' or
+ host_system == 'dragonfly' or host_system == 'openbsd')
+ cflags += ['-DHAS_KQUEUE']
+endif
+
+subdir('backend')
+
+picom = executable('picom', srcs, c_args: cflags,
+ dependencies: [ base_deps, deps, test_h_dep ],
+ install: true, include_directories: picom_inc)
+
+if get_option('unittest')
+ test('picom unittest', picom, args: [ '--unittest' ])
+endif
diff --git a/src/meta.h b/src/meta.h
new file mode 100644
index 0000000..4314356
--- /dev/null
+++ b/src/meta.h
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) 2019, Yuxuan Shui <[email protected]>
+
+#pragma once
+
+/// Macro metaprogramming
+
+#define _APPLY1(a, ...) a(__VA_ARGS__)
+#define _APPLY2(a, ...) a(__VA_ARGS__)
+#define _APPLY3(a, ...) a(__VA_ARGS__)
+#define _APPLY4(a, ...) a(__VA_ARGS__)
+
+#define RIOTA1(x) x
+#define RIOTA2(x) RIOTA1(x##1), RIOTA1(x##0)
+#define RIOTA4(x) RIOTA2(x##1), RIOTA2(x##0)
+#define RIOTA8(x) RIOTA4(x##1), RIOTA4(x##0)
+#define RIOTA16(x) RIOTA8(x##1), RIOTA8(x##0)
+/// Generate a list containing 31, 30, ..., 0, in binary
+#define RIOTA32(x) RIOTA16(x##1), RIOTA16(x##0)
+
+#define CONCAT2(a, b) a##b
+#define CONCAT1(a, b) CONCAT2(a, b)
+#define CONCAT(a, b) CONCAT1(a, b)
+
+#define _ARGS_HEAD(head, ...) head
+#define _ARGS_SKIP4(_1, _2, _3, _4, ...) __VA_ARGS__
+#define _ARGS_SKIP8(...) _APPLY1(_ARGS_SKIP4, _ARGS_SKIP4(__VA_ARGS__))
+#define _ARGS_SKIP16(...) _APPLY2(_ARGS_SKIP8, _ARGS_SKIP8(__VA_ARGS__))
+#define _ARGS_SKIP32(...) _APPLY3(_ARGS_SKIP16, _ARGS_SKIP16(__VA_ARGS__))
+
+/// Return the 33rd argument
+#define _ARG33(...) _APPLY4(_ARGS_HEAD, _ARGS_SKIP32(__VA_ARGS__))
+
+/// Return the number of arguments passed in binary, handles at most 31 elements
+#define VA_ARGS_LENGTH(...) _ARG33(0, ##__VA_ARGS__, RIOTA32(0))
+
+#define LIST_APPLY_000000(fn, sep, ...)
+#define LIST_APPLY_000001(fn, sep, x, ...) fn(x)
+#define LIST_APPLY_000010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000001(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_000011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000010(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_000100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000011(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_000101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000100(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_000110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000101(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_000111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000110(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_001000(fn, sep, x, ...) fn(x) sep() LIST_APPLY_000111(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_001001(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001000(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_001010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001001(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_001011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001010(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_001100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001011(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_001101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001100(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_001110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001101(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_001111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001110(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_010000(fn, sep, x, ...) fn(x) sep() LIST_APPLY_001111(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_010001(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010000(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_010010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010001(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_010011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010010(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_010100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010011(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_010101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010100(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_010110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010101(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_010111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010110(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_011000(fn, sep, x, ...) fn(x) sep() LIST_APPLY_010111(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_011001(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011000(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_011010(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011001(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_011011(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011010(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_011100(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011011(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_011101(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011100(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_011110(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011101(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_011111(fn, sep, x, ...) fn(x) sep() LIST_APPLY_011110(fn, sep, __VA_ARGS__)
+#define LIST_APPLY_(N, fn, sep, ...) CONCAT(LIST_APPLY_, N)(fn, sep, __VA_ARGS__)
+#define LIST_APPLY(fn, sep, ...) \
+ LIST_APPLY_(VA_ARGS_LENGTH(__VA_ARGS__), fn, sep, __VA_ARGS__)
+
+#define SEP_COMMA() ,
+#define SEP_COLON() ;
+#define SEP_NONE()
diff --git a/src/opengl.c b/src/opengl.c
new file mode 100644
index 0000000..5d2d66c
--- /dev/null
+++ b/src/opengl.c
@@ -0,0 +1,1514 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Compton - a compositor for X11
+ *
+ * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
+ *
+ * Copyright (c) 2011-2013, Christopher Jeffrey
+ * See LICENSE-mit for more information.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <xcb/render.h>
+#include <xcb/xcb.h>
+
+#include "backend/gl/gl_common.h"
+#include "backend/gl/glx.h"
+#include "common.h"
+#include "compiler.h"
+#include "config.h"
+#include "kernel.h"
+#include "log.h"
+#include "region.h"
+#include "string_utils.h"
+#include "uthash_extra.h"
+#include "utils.h"
+#include "win.h"
+
+#include "opengl.h"
+
+#ifndef GL_TEXTURE_RECTANGLE
+#define GL_TEXTURE_RECTANGLE 0x84F5
+#endif
+
+static inline XVisualInfo *get_visualinfo_from_visual(session_t *ps, xcb_visualid_t visual) {
+ XVisualInfo vreq = {.visualid = visual};
+ int nitems = 0;
+
+ return XGetVisualInfo(ps->dpy, VisualIDMask, &vreq, &nitems);
+}
+
+/**
+ * Initialize OpenGL.
+ */
+bool glx_init(session_t *ps, bool need_render) {
+ bool success = false;
+ XVisualInfo *pvis = NULL;
+
+ // Check for GLX extension
+ if (!ps->glx_exists) {
+ log_error("No GLX extension.");
+ goto glx_init_end;
+ }
+
+ // Get XVisualInfo
+ pvis = get_visualinfo_from_visual(ps, ps->vis);
+ if (!pvis) {
+ log_error("Failed to acquire XVisualInfo for current visual.");
+ goto glx_init_end;
+ }
+
+ // Ensure the visual is double-buffered
+ if (need_render) {
+ int value = 0;
+ if (Success != glXGetConfig(ps->dpy, pvis, GLX_USE_GL, &value) || !value) {
+ log_error("Root visual is not a GL visual.");
+ goto glx_init_end;
+ }
+
+ if (Success != glXGetConfig(ps->dpy, pvis, GLX_DOUBLEBUFFER, &value) || !value) {
+ log_error("Root visual is not a double buffered GL visual.");
+ goto glx_init_end;
+ }
+ }
+
+ // Ensure GLX_EXT_texture_from_pixmap exists
+ if (need_render && !glxext.has_GLX_EXT_texture_from_pixmap)
+ goto glx_init_end;
+
+ // Initialize GLX data structure
+ if (!ps->psglx) {
+ static const glx_session_t CGLX_SESSION_DEF = CGLX_SESSION_INIT;
+ ps->psglx = cmalloc(glx_session_t);
+ memcpy(ps->psglx, &CGLX_SESSION_DEF, sizeof(glx_session_t));
+
+ // +1 for the zero terminator
+ ps->psglx->blur_passes = ccalloc(ps->o.blur_kernel_count, glx_blur_pass_t);
+
+ for (int i = 0; i < ps->o.blur_kernel_count; ++i) {
+ glx_blur_pass_t *ppass = &ps->psglx->blur_passes[i];
+ ppass->unifm_factor_center = -1;
+ ppass->unifm_offset_x = -1;
+ ppass->unifm_offset_y = -1;
+ }
+
+ ps->psglx->round_passes = ccalloc(1, glx_round_pass_t);
+ glx_round_pass_t *ppass = ps->psglx->round_passes;
+ ppass->unifm_radius = -1;
+ ppass->unifm_texcoord = -1;
+ ppass->unifm_texsize = -1;
+ ppass->unifm_borderw = -1;
+ ppass->unifm_borderc = -1;
+ ppass->unifm_resolution = -1;
+ ppass->unifm_tex_scr = -1;
+ }
+
+ glx_session_t *psglx = ps->psglx;
+
+ if (!psglx->context) {
+ // Get GLX context
+#ifndef DEBUG_GLX_DEBUG_CONTEXT
+ psglx->context = glXCreateContext(ps->dpy, pvis, None, GL_TRUE);
+#else
+ {
+ GLXFBConfig fbconfig = get_fbconfig_from_visualinfo(ps, pvis);
+ if (!fbconfig) {
+ log_error("Failed to get GLXFBConfig for root visual "
+ "%#lx.",
+ pvis->visualid);
+ goto glx_init_end;
+ }
+
+ f_glXCreateContextAttribsARB p_glXCreateContextAttribsARB =
+ (f_glXCreateContextAttribsARB)glXGetProcAddress(
+ (const GLubyte *)"glXCreateContextAttribsARB");
+ if (!p_glXCreateContextAttribsARB) {
+ log_error("Failed to get glXCreateContextAttribsARB().");
+ goto glx_init_end;
+ }
+
+ static const int attrib_list[] = {
+ GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_DEBUG_BIT_ARB, None};
+ psglx->context = p_glXCreateContextAttribsARB(
+ ps->dpy, fbconfig, NULL, GL_TRUE, attrib_list);
+ }
+#endif
+
+ if (!psglx->context) {
+ log_error("Failed to get GLX context.");
+ goto glx_init_end;
+ }
+
+ // Attach GLX context
+ if (!glXMakeCurrent(ps->dpy, get_tgt_window(ps), psglx->context)) {
+ log_error("Failed to attach GLX context.");
+ goto glx_init_end;
+ }
+
+#ifdef DEBUG_GLX_DEBUG_CONTEXT
+ {
+ f_DebugMessageCallback p_DebugMessageCallback =
+ (f_DebugMessageCallback)glXGetProcAddress(
+ (const GLubyte *)"glDebugMessageCallback");
+ if (!p_DebugMessageCallback) {
+ log_error("Failed to get glDebugMessageCallback(0.");
+ goto glx_init_end;
+ }
+ p_DebugMessageCallback(glx_debug_msg_callback, ps);
+ }
+#endif
+ }
+
+ // Ensure we have a stencil buffer. X Fixes does not guarantee rectangles
+ // in regions don't overlap, so we must use stencil buffer to make sure
+ // we don't paint a region for more than one time, I think?
+ if (need_render && !ps->o.glx_no_stencil) {
+ GLint val = 0;
+ glGetIntegerv(GL_STENCIL_BITS, &val);
+ if (!val) {
+ log_error("Target window doesn't have stencil buffer.");
+ goto glx_init_end;
+ }
+ }
+
+ // Check GL_ARB_texture_non_power_of_two, requires a GLX context and
+ // must precede FBConfig fetching
+ if (need_render)
+ psglx->has_texture_non_power_of_two =
+ gl_has_extension("GL_ARB_texture_non_power_of_two");
+
+ // Render preparations
+ if (need_render) {
+ glx_on_root_change(ps);
+
+ glDisable(GL_DEPTH_TEST);
+ glDepthMask(GL_FALSE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+ glDisable(GL_BLEND);
+
+ if (!ps->o.glx_no_stencil) {
+ // Initialize stencil buffer
+ glClear(GL_STENCIL_BUFFER_BIT);
+ glDisable(GL_STENCIL_TEST);
+ glStencilMask(0x1);
+ glStencilFunc(GL_EQUAL, 0x1, 0x1);
+ }
+
+ // Clear screen
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ // glXSwapBuffers(ps->dpy, get_tgt_window(ps));
+ }
+
+ success = true;
+
+glx_init_end:
+ XFree(pvis);
+
+ if (!success)
+ glx_destroy(ps);
+
+ return success;
+}
+
+static void glx_free_prog_main(glx_prog_main_t *pprogram) {
+ if (!pprogram)
+ return;
+ if (pprogram->prog) {
+ glDeleteProgram(pprogram->prog);
+ pprogram->prog = 0;
+ }
+ pprogram->unifm_opacity = -1;
+ pprogram->unifm_invert_color = -1;
+ pprogram->unifm_tex = -1;
+}
+
+/**
+ * Destroy GLX related resources.
+ */
+void glx_destroy(session_t *ps) {
+ if (!ps->psglx)
+ return;
+
+ // Free all GLX resources of windows
+ win_stack_foreach_managed(w, &ps->window_stack) {
+ free_win_res_glx(ps, w);
+ }
+
+ // Free GLSL shaders/programs
+ for (int i = 0; i < ps->o.blur_kernel_count; ++i) {
+ glx_blur_pass_t *ppass = &ps->psglx->blur_passes[i];
+ if (ppass->frag_shader) {
+ glDeleteShader(ppass->frag_shader);
+ }
+ if (ppass->prog) {
+ glDeleteProgram(ppass->prog);
+ }
+ }
+ free(ps->psglx->blur_passes);
+
+ glx_round_pass_t *ppass = ps->psglx->round_passes;
+ if (ppass->frag_shader) {
+ glDeleteShader(ppass->frag_shader);
+ }
+ if (ppass->prog) {
+ glDeleteProgram(ppass->prog);
+ }
+ free(ps->psglx->round_passes);
+
+ glx_free_prog_main(&ps->glx_prog_win);
+
+ gl_check_err();
+
+ // Destroy GLX context
+ if (ps->psglx->context) {
+ glXMakeCurrent(ps->dpy, None, NULL);
+ glXDestroyContext(ps->dpy, ps->psglx->context);
+ ps->psglx->context = NULL;
+ }
+
+ free(ps->psglx);
+ ps->psglx = NULL;
+ ps->argb_fbconfig = NULL;
+}
+
+/**
+ * Callback to run on root window size change.
+ */
+void glx_on_root_change(session_t *ps) {
+ glViewport(0, 0, ps->root_width, ps->root_height);
+
+ // Initialize matrix, copied from dcompmgr
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrtho(0, ps->root_width, 0, ps->root_height, -1000.0, 1000.0);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+}
+
+/**
+ * Initialize GLX blur filter.
+ */
+bool glx_init_blur(session_t *ps) {
+ assert(ps->o.blur_kernel_count > 0);
+ assert(ps->o.blur_kerns);
+ assert(ps->o.blur_kerns[0]);
+
+ // Allocate PBO if more than one blur kernel is present
+ if (ps->o.blur_kernel_count > 1) {
+ // Try to generate a framebuffer
+ GLuint fbo = 0;
+ glGenFramebuffers(1, &fbo);
+ if (!fbo) {
+ log_error("Failed to generate Framebuffer. Cannot do multi-pass "
+ "blur with GLX"
+ " backend.");
+ return false;
+ }
+ glDeleteFramebuffers(1, &fbo);
+ }
+
+ {
+ char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL));
+ // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane
+ // Thanks to hiciu for reporting.
+ setlocale(LC_NUMERIC, "C");
+
+ static const char *FRAG_SHADER_BLUR_PREFIX =
+ "#version 110\n"
+ "%s"
+ "uniform float offset_x;\n"
+ "uniform float offset_y;\n"
+ "uniform float factor_center;\n"
+ "uniform %s tex_scr;\n"
+ "\n"
+ "void main() {\n"
+ " vec4 sum = vec4(0.0, 0.0, 0.0, 0.0);\n";
+ static const char *FRAG_SHADER_BLUR_ADD =
+ " sum += float(%.7g) * %s(tex_scr, vec2(gl_TexCoord[0].x + offset_x "
+ "* float(%d), gl_TexCoord[0].y + offset_y * float(%d)));\n";
+ static const char *FRAG_SHADER_BLUR_SUFFIX =
+ " sum += %s(tex_scr, vec2(gl_TexCoord[0].x, gl_TexCoord[0].y)) * "
+ "factor_center;\n"
+ " gl_FragColor = sum / (factor_center + float(%.7g));\n"
+ "}\n";
+
+ const bool use_texture_rect = !ps->psglx->has_texture_non_power_of_two;
+ const char *sampler_type = (use_texture_rect ? "sampler2DRect" : "sampler2D");
+ const char *texture_func = (use_texture_rect ? "texture2DRect" : "texture2D");
+ const char *shader_add = FRAG_SHADER_BLUR_ADD;
+ char *extension = NULL;
+ if (use_texture_rect) {
+ mstrextend(&extension, "#extension GL_ARB_texture_rectangle : "
+ "require\n");
+ }
+ if (!extension) {
+ extension = strdup("");
+ }
+
+ for (int i = 0; i < ps->o.blur_kernel_count; ++i) {
+ auto kern = ps->o.blur_kerns[i];
+ glx_blur_pass_t *ppass = &ps->psglx->blur_passes[i];
+
+ // Build shader
+ int width = kern->w, height = kern->h;
+ int nele = width * height - 1;
+ assert(nele >= 0);
+ auto len =
+ strlen(FRAG_SHADER_BLUR_PREFIX) + strlen(sampler_type) +
+ strlen(extension) +
+ (strlen(shader_add) + strlen(texture_func) + 42) * (uint)nele +
+ strlen(FRAG_SHADER_BLUR_SUFFIX) + strlen(texture_func) + 12 + 1;
+ char *shader_str = ccalloc(len, char);
+ char *pc = shader_str;
+ sprintf(pc, FRAG_SHADER_BLUR_PREFIX, extension, sampler_type);
+ pc += strlen(pc);
+ assert(strlen(shader_str) < len);
+
+ double sum = 0.0;
+ for (int j = 0; j < height; ++j) {
+ for (int k = 0; k < width; ++k) {
+ if (height / 2 == j && width / 2 == k)
+ continue;
+ double val = kern->data[j * width + k];
+ if (val == 0) {
+ continue;
+ }
+ sum += val;
+ sprintf(pc, shader_add, val, texture_func,
+ k - width / 2, j - height / 2);
+ pc += strlen(pc);
+ assert(strlen(shader_str) < len);
+ }
+ }
+
+ sprintf(pc, FRAG_SHADER_BLUR_SUFFIX, texture_func, sum);
+ assert(strlen(shader_str) < len);
+ ppass->frag_shader = gl_create_shader(GL_FRAGMENT_SHADER, shader_str);
+ free(shader_str);
+
+ if (!ppass->frag_shader) {
+ log_error("Failed to create fragment shader %d.", i);
+ free(extension);
+ free(lc_numeric_old);
+ return false;
+ }
+
+ // Build program
+ ppass->prog = gl_create_program(&ppass->frag_shader, 1);
+ if (!ppass->prog) {
+ log_error("Failed to create GLSL program.");
+ free(extension);
+ free(lc_numeric_old);
+ return false;
+ }
+
+ // Get uniform addresses
+#define P_GET_UNIFM_LOC(name, target) \
+ { \
+ ppass->target = glGetUniformLocation(ppass->prog, name); \
+ if (ppass->target < 0) { \
+ log_error("Failed to get location of %d-th uniform '" name \
+ "'. Might be troublesome.", \
+ i); \
+ } \
+ }
+
+ P_GET_UNIFM_LOC("factor_center", unifm_factor_center);
+ P_GET_UNIFM_LOC("offset_x", unifm_offset_x);
+ P_GET_UNIFM_LOC("offset_y", unifm_offset_y);
+
+#undef P_GET_UNIFM_LOC
+ }
+ free(extension);
+
+ // Restore LC_NUMERIC
+ setlocale(LC_NUMERIC, lc_numeric_old);
+ free(lc_numeric_old);
+ }
+
+ gl_check_err();
+
+ return true;
+}
+
+/**
+ * Initialize GLX rounded corners filter.
+ */
+bool glx_init_rounded_corners(session_t *ps) {
+ char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL));
+ // Enforce LC_NUMERIC locale "C" here to make sure decimal point is sane
+ // Thanks to hiciu for reporting.
+ setlocale(LC_NUMERIC, "C");
+
+ static const char *FRAG_SHADER =
+ "#version 110\n"
+ "%s" // extensions
+ "uniform float u_radius;\n"
+ "uniform float u_borderw;\n"
+ "uniform vec4 u_borderc;\n"
+ "uniform vec2 u_texcoord;\n"
+ "uniform vec2 u_texsize;\n"
+ "uniform vec2 u_resolution;\n"
+ "uniform %s tex_scr;\n" // sampler2D | sampler2DRect
+ "\n"
+ "// https://www.shadertoy.com/view/ltS3zW\n"
+ "float RectSDF(vec2 p, vec2 b, float r) {\n"
+ " vec2 d = abs(p) - b + vec2(r);\n"
+ " return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;\n"
+ "}\n\n"
+ "void main()\n"
+ "{\n"
+ " vec2 coord = vec2(u_texcoord.x, "
+ "u_resolution.y-u_texsize.y-u_texcoord.y);\n"
+ " vec4 u_v4WndBgColor = %s(tex_scr, vec2(gl_TexCoord[0].st));\n"
+ " float u_fRadiusPx = u_radius;\n"
+ " float u_fHalfBorderThickness = u_borderw / 2.0;\n"
+ " vec4 u_v4BorderColor = u_borderc;\n"
+ " vec4 u_v4FillColor = vec4(0.0, 0.0, 0.0, 0.0);\n"
+ " vec4 v4FromColor = u_v4BorderColor; //Always the border "
+ "color. If no border, this still should be set\n"
+ " vec4 v4ToColor = u_v4WndBgColor; //Outside color is the "
+ "background texture\n"
+ "\n"
+ " vec2 u_v2HalfShapeSizePx = u_texsize/2.0 - "
+ "vec2(u_fHalfBorderThickness);\n"
+ " vec2 v_v2CenteredPos = (gl_FragCoord.xy - u_texsize.xy / 2.0 - "
+ "coord);\n"
+ "\n"
+ " float fDist = RectSDF(v_v2CenteredPos, u_v2HalfShapeSizePx, "
+ "u_fRadiusPx - u_fHalfBorderThickness);\n"
+ " if (u_fHalfBorderThickness > 0.0) {\n"
+ " if (fDist < 0.0) {\n"
+ " v4ToColor = u_v4FillColor;\n"
+ " }\n"
+ " fDist = abs(fDist) - u_fHalfBorderThickness;\n"
+ " } else {\n"
+ " v4FromColor = u_v4FillColor;\n"
+ " }\n"
+ " float fBlendAmount = smoothstep(-1.0, 1.0, fDist);\n"
+ " vec4 c = mix(v4FromColor, v4ToColor, fBlendAmount);\n"
+ "\n"
+ " // final color\n"
+ " gl_FragColor = c;\n"
+ "\n"
+ "}\n";
+
+ const bool use_texture_rect = !ps->psglx->has_texture_non_power_of_two;
+ const char *sampler_type = (use_texture_rect ? "sampler2DRect" : "sampler2D");
+ const char *texture_func = (use_texture_rect ? "texture2DRect" : "texture2D");
+ char *extension = NULL;
+ if (use_texture_rect) {
+ mstrextend(&extension, "#extension GL_ARB_texture_rectangle : "
+ "require\n");
+ }
+ if (!extension) {
+ extension = strdup("");
+ }
+
+ bool success = false;
+ // Build rounded corners shader
+ auto ppass = ps->psglx->round_passes;
+ auto len = strlen(FRAG_SHADER) + strlen(extension) + strlen(sampler_type) +
+ strlen(texture_func) + 1;
+ char *shader_str = ccalloc(len, char);
+
+ sprintf(shader_str, FRAG_SHADER, extension, sampler_type, texture_func);
+ assert(strlen(shader_str) < len);
+
+ log_debug("Generated rounded corners shader:\n%s\n", shader_str);
+
+ ppass->frag_shader = gl_create_shader(GL_FRAGMENT_SHADER, shader_str);
+ free(shader_str);
+
+ if (!ppass->frag_shader) {
+ log_error("Failed to create rounded corners fragment shader.");
+ goto out;
+ }
+
+ // Build program
+ ppass->prog = gl_create_program(&ppass->frag_shader, 1);
+ if (!ppass->prog) {
+ log_error("Failed to create GLSL program.");
+ goto out;
+ }
+
+ // Get uniform addresses
+#define P_GET_UNIFM_LOC(name, target) \
+ { \
+ ppass->target = glGetUniformLocation(ppass->prog, name); \
+ if (ppass->target < 0) { \
+ log_debug("Failed to get location of rounded corners uniform " \
+ "'" name "'. Might be troublesome."); \
+ } \
+ }
+ P_GET_UNIFM_LOC("u_radius", unifm_radius);
+ P_GET_UNIFM_LOC("u_texcoord", unifm_texcoord);
+ P_GET_UNIFM_LOC("u_texsize", unifm_texsize);
+ P_GET_UNIFM_LOC("u_borderw", unifm_borderw);
+ P_GET_UNIFM_LOC("u_borderc", unifm_borderc);
+ P_GET_UNIFM_LOC("u_resolution", unifm_resolution);
+ P_GET_UNIFM_LOC("tex_scr", unifm_tex_scr);
+#undef P_GET_UNIFM_LOC
+
+ success = true;
+
+out:
+ free(extension);
+
+ // Restore LC_NUMERIC
+ setlocale(LC_NUMERIC, lc_numeric_old);
+ free(lc_numeric_old);
+
+ gl_check_err();
+
+ return success;
+}
+
+/**
+ * Load a GLSL main program from shader strings.
+ */
+bool glx_load_prog_main(const char *vshader_str, const char *fshader_str,
+ glx_prog_main_t *pprogram) {
+ assert(pprogram);
+
+ // Build program
+ pprogram->prog = gl_create_program_from_str(vshader_str, fshader_str);
+ if (!pprogram->prog) {
+ log_error("Failed to create GLSL program.");
+ return false;
+ }
+
+ // Get uniform addresses
+#define P_GET_UNIFM_LOC(name, target) \
+ { \
+ pprogram->target = glGetUniformLocation(pprogram->prog, name); \
+ if (pprogram->target < 0) { \
+ log_error("Failed to get location of uniform '" name \
+ "'. Might be troublesome."); \
+ } \
+ }
+ P_GET_UNIFM_LOC("opacity", unifm_opacity);
+ P_GET_UNIFM_LOC("invert_color", unifm_invert_color);
+ P_GET_UNIFM_LOC("tex", unifm_tex);
+ P_GET_UNIFM_LOC("time", unifm_time);
+#undef P_GET_UNIFM_LOC
+
+ gl_check_err();
+
+ return true;
+}
+
+static inline void glx_copy_region_to_tex(session_t *ps, GLenum tex_tgt, int basex,
+ int basey, int dx, int dy, int width, int height) {
+ if (width > 0 && height > 0) {
+ glCopyTexSubImage2D(tex_tgt, 0, dx - basex, dy - basey, dx,
+ ps->root_height - dy - height, width, height);
+ }
+}
+
+static inline GLuint glx_gen_texture(GLenum tex_tgt, int width, int height) {
+ GLuint tex = 0;
+ glGenTextures(1, &tex);
+ if (!tex) {
+ return 0;
+ }
+ glEnable(tex_tgt);
+ glBindTexture(tex_tgt, tex);
+ glTexParameteri(tex_tgt, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(tex_tgt, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(tex_tgt, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(tex_tgt, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexImage2D(tex_tgt, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
+ glBindTexture(tex_tgt, 0);
+
+ return tex;
+}
+
+/**
+ * Bind an OpenGL texture and fill it with pixel data from back buffer
+ */
+bool glx_bind_texture(session_t *ps attr_unused, glx_texture_t **pptex, int x, int y,
+ int width, int height) {
+ if (ps->o.backend != BKEND_GLX && ps->o.backend != BKEND_XR_GLX_HYBRID) {
+ return true;
+ }
+
+ glx_texture_t *ptex = *pptex;
+
+ // log_trace("Copying xy(%d %d) wh(%d %d) ptex(%p)", x, y, width, height, ptex);
+
+ // Release texture if parameters are inconsistent
+ if (ptex && ptex->texture && (ptex->width != width || ptex->height != height)) {
+ free_texture(ps, &ptex);
+ }
+
+ // Allocate structure
+ if (!ptex) {
+ ptex = ccalloc(1, glx_texture_t);
+ *pptex = ptex;
+
+ ptex->width = width;
+ ptex->height = height;
+ ptex->target = GL_TEXTURE_RECTANGLE;
+ if (ps->psglx->has_texture_non_power_of_two) {
+ ptex->target = GL_TEXTURE_2D;
+ }
+ }
+
+ // Create texture
+ if (!ptex->texture) {
+ ptex->texture = glx_gen_texture(ptex->target, width, height);
+ }
+ if (!ptex->texture) {
+ log_error("Failed to allocate texture.");
+ return false;
+ }
+
+ // Read destination pixels into a texture
+ glEnable(ptex->target);
+ glBindTexture(ptex->target, ptex->texture);
+ if (width > 0 && height > 0) {
+ glx_copy_region_to_tex(ps, ptex->target, x, y, x, y, width, height);
+ }
+
+ // Cleanup
+ glBindTexture(ptex->target, 0);
+ glDisable(ptex->target);
+
+ gl_check_err();
+
+ return true;
+}
+
+/**
+ * Bind a X pixmap to an OpenGL texture.
+ */
+bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, int width,
+ int height, bool repeat, const struct glx_fbconfig_info *fbcfg) {
+ if (ps->o.backend != BKEND_GLX && ps->o.backend != BKEND_XR_GLX_HYBRID)
+ return true;
+
+ if (!pixmap) {
+ log_error("Binding to an empty pixmap %#010x. This can't work.", pixmap);
+ return false;
+ }
+
+ assert(fbcfg);
+ glx_texture_t *ptex = *pptex;
+ bool need_release = true;
+
+ // Release pixmap if parameters are inconsistent
+ if (ptex && ptex->texture && ptex->pixmap != pixmap) {
+ glx_release_pixmap(ps, ptex);
+ }
+
+ // Allocate structure
+ if (!ptex) {
+ static const glx_texture_t GLX_TEX_DEF = {
+ .texture = 0,
+ .glpixmap = 0,
+ .pixmap = 0,
+ .target = 0,
+ .width = 0,
+ .height = 0,
+ .y_inverted = false,
+ };
+
+ ptex = cmalloc(glx_texture_t);
+ memcpy(ptex, &GLX_TEX_DEF, sizeof(glx_texture_t));
+ *pptex = ptex;
+ }
+
+ // Create GLX pixmap
+ int depth = 0;
+ if (!ptex->glpixmap) {
+ need_release = false;
+
+ // Retrieve pixmap parameters, if they aren't provided
+ if (!width || !height) {
+ auto r = xcb_get_geometry_reply(
+ ps->c, xcb_get_geometry(ps->c, pixmap), NULL);
+ if (!r) {
+ log_error("Failed to query info of pixmap %#010x.", pixmap);
+ return false;
+ }
+ if (r->depth > OPENGL_MAX_DEPTH) {
+ log_error("Requested depth %d higher than %d.", depth,
+ OPENGL_MAX_DEPTH);
+ return false;
+ }
+ depth = r->depth;
+ width = r->width;
+ height = r->height;
+ free(r);
+ }
+
+ // Determine texture target, copied from compiz
+ // The assumption we made here is the target never changes based on any
+ // pixmap-specific parameters, and this may change in the future
+ GLenum tex_tgt = 0;
+ if (GLX_TEXTURE_2D_BIT_EXT & fbcfg->texture_tgts &&
+ ps->psglx->has_texture_non_power_of_two)
+ tex_tgt = GLX_TEXTURE_2D_EXT;
+ else if (GLX_TEXTURE_RECTANGLE_BIT_EXT & fbcfg->texture_tgts)
+ tex_tgt = GLX_TEXTURE_RECTANGLE_EXT;
+ else if (!(GLX_TEXTURE_2D_BIT_EXT & fbcfg->texture_tgts))
+ tex_tgt = GLX_TEXTURE_RECTANGLE_EXT;
+ else
+ tex_tgt = GLX_TEXTURE_2D_EXT;
+
+ log_debug("depth %d, tgt %#x, rgba %d", depth, tex_tgt,
+ (GLX_TEXTURE_FORMAT_RGBA_EXT == fbcfg->texture_fmt));
+
+ GLint attrs[] = {
+ GLX_TEXTURE_FORMAT_EXT,
+ fbcfg->texture_fmt,
+ GLX_TEXTURE_TARGET_EXT,
+ (GLint)tex_tgt,
+ 0,
+ };
+
+ ptex->glpixmap = glXCreatePixmap(ps->dpy, fbcfg->cfg, pixmap, attrs);
+ ptex->pixmap = pixmap;
+ ptex->target =
+ (GLX_TEXTURE_2D_EXT == tex_tgt ? GL_TEXTURE_2D : GL_TEXTURE_RECTANGLE);
+ ptex->width = width;
+ ptex->height = height;
+ ptex->y_inverted = fbcfg->y_inverted;
+ }
+ if (!ptex->glpixmap) {
+ log_error("Failed to allocate GLX pixmap.");
+ return false;
+ }
+
+ glEnable(ptex->target);
+
+ // Create texture
+ if (!ptex->texture) {
+ need_release = false;
+
+ GLuint texture = 0;
+ glGenTextures(1, &texture);
+ glBindTexture(ptex->target, texture);
+
+ glTexParameteri(ptex->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(ptex->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ if (repeat) {
+ glTexParameteri(ptex->target, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(ptex->target, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ } else {
+ glTexParameteri(ptex->target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(ptex->target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ }
+
+ glBindTexture(ptex->target, 0);
+
+ ptex->texture = texture;
+ }
+ if (!ptex->texture) {
+ log_error("Failed to allocate texture.");
+ return false;
+ }
+
+ glBindTexture(ptex->target, ptex->texture);
+
+ // The specification requires rebinding whenever the content changes...
+ // We can't follow this, too slow.
+ if (need_release)
+ glXReleaseTexImageEXT(ps->dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT);
+
+ glXBindTexImageEXT(ps->dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT, NULL);
+
+ // Cleanup
+ glBindTexture(ptex->target, 0);
+ glDisable(ptex->target);
+
+ gl_check_err();
+
+ return true;
+}
+
+/**
+ * @brief Release binding of a texture.
+ */
+void glx_release_pixmap(session_t *ps, glx_texture_t *ptex) {
+ // Release binding
+ if (ptex->glpixmap && ptex->texture) {
+ glBindTexture(ptex->target, ptex->texture);
+ glXReleaseTexImageEXT(ps->dpy, ptex->glpixmap, GLX_FRONT_LEFT_EXT);
+ glBindTexture(ptex->target, 0);
+ }
+
+ // Free GLX Pixmap
+ if (ptex->glpixmap) {
+ glXDestroyPixmap(ps->dpy, ptex->glpixmap);
+ ptex->glpixmap = 0;
+ }
+
+ gl_check_err();
+}
+
+/**
+ * Set clipping region on the target window.
+ */
+void glx_set_clip(session_t *ps, const region_t *reg) {
+ // Quit if we aren't using stencils
+ if (ps->o.glx_no_stencil)
+ return;
+
+ glDisable(GL_STENCIL_TEST);
+ glDisable(GL_SCISSOR_TEST);
+
+ if (!reg)
+ return;
+
+ int nrects;
+ const rect_t *rects = pixman_region32_rectangles((region_t *)reg, &nrects);
+
+ if (nrects == 1) {
+ glEnable(GL_SCISSOR_TEST);
+ glScissor(rects[0].x1, ps->root_height - rects[0].y2,
+ rects[0].x2 - rects[0].x1, rects[0].y2 - rects[0].y1);
+ }
+
+ gl_check_err();
+}
+
+#define P_PAINTREG_START(var) \
+ region_t reg_new; \
+ int nrects; \
+ const rect_t *rects; \
+ assert(width >= 0 && height >= 0); \
+ pixman_region32_init_rect(&reg_new, dx, dy, (uint)width, (uint)height); \
+ pixman_region32_intersect(&reg_new, &reg_new, (region_t *)reg_tgt); \
+ rects = pixman_region32_rectangles(&reg_new, &nrects); \
+ glBegin(GL_QUADS); \
+ \
+ for (int ri = 0; ri < nrects; ++ri) { \
+ rect_t var = rects[ri];
+
+#define P_PAINTREG_END() \
+ } \
+ glEnd(); \
+ \
+ pixman_region32_fini(&reg_new);
+
+/**
+ * Blur contents in a particular region.
+ *
+ * XXX seems to be way to complex for what it does
+ */
+bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z,
+ GLfloat factor_center, const region_t *reg_tgt, glx_blur_cache_t *pbc) {
+ assert(ps->psglx->blur_passes[0].prog);
+ const bool more_passes = ps->o.blur_kernel_count > 1;
+ const bool have_scissors = glIsEnabled(GL_SCISSOR_TEST);
+ const bool have_stencil = glIsEnabled(GL_STENCIL_TEST);
+ bool ret = false;
+
+ // Calculate copy region size
+ glx_blur_cache_t ibc = {.width = 0, .height = 0};
+ if (!pbc)
+ pbc = &ibc;
+
+ int mdx = dx, mdy = dy, mwidth = width, mheight = height;
+ // log_trace("%d, %d, %d, %d", mdx, mdy, mwidth, mheight);
+
+ /*
+ if (ps->o.resize_damage > 0) {
+ int inc_x = 0, inc_y = 0;
+ for (int i = 0; i < MAX_BLUR_PASS; ++i) {
+ XFixed *kern = ps->o.blur_kerns[i];
+ if (!kern) break;
+ inc_x += XFIXED_TO_DOUBLE(kern[0]) / 2;
+ inc_y += XFIXED_TO_DOUBLE(kern[1]) / 2;
+ }
+ inc_x = min2(ps->o.resize_damage, inc_x);
+ inc_y = min2(ps->o.resize_damage, inc_y);
+
+ mdx = max2(dx - inc_x, 0);
+ mdy = max2(dy - inc_y, 0);
+ int mdx2 = min2(dx + width + inc_x, ps->root_width),
+ mdy2 = min2(dy + height + inc_y, ps->root_height);
+ mwidth = mdx2 - mdx;
+ mheight = mdy2 - mdy;
+ }
+ */
+
+ GLenum tex_tgt = GL_TEXTURE_RECTANGLE;
+ if (ps->psglx->has_texture_non_power_of_two)
+ tex_tgt = GL_TEXTURE_2D;
+
+ // Free textures if size inconsistency discovered
+ if (mwidth != pbc->width || mheight != pbc->height)
+ free_glx_bc_resize(ps, pbc);
+
+ // Generate FBO and textures if needed
+ if (!pbc->textures[0])
+ pbc->textures[0] = glx_gen_texture(tex_tgt, mwidth, mheight);
+ GLuint tex_scr = pbc->textures[0];
+ if (more_passes && !pbc->textures[1])
+ pbc->textures[1] = glx_gen_texture(tex_tgt, mwidth, mheight);
+ pbc->width = mwidth;
+ pbc->height = mheight;
+ GLuint tex_scr2 = pbc->textures[1];
+ if (more_passes && !pbc->fbo)
+ glGenFramebuffers(1, &pbc->fbo);
+ const GLuint fbo = pbc->fbo;
+
+ if (!tex_scr || (more_passes && !tex_scr2)) {
+ log_error("Failed to allocate texture.");
+ goto glx_blur_dst_end;
+ }
+ if (more_passes && !fbo) {
+ log_error("Failed to allocate framebuffer.");
+ goto glx_blur_dst_end;
+ }
+
+ // Read destination pixels into a texture
+ glEnable(tex_tgt);
+ glBindTexture(tex_tgt, tex_scr);
+ glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, mdx, mdy, mwidth, mheight);
+ /*
+ if (tex_scr2) {
+ glBindTexture(tex_tgt, tex_scr2);
+ glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, mdx, mdy, mwidth, dx - mdx);
+ glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, mdx, dy + height,
+ mwidth, mdy + mheight - dy - height);
+ glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, mdx, dy, dx - mdx, height);
+ glx_copy_region_to_tex(ps, tex_tgt, mdx, mdy, dx + width, dy,
+ mdx + mwidth - dx - width, height);
+ } */
+
+ // Texture scaling factor
+ GLfloat texfac_x = 1.0f, texfac_y = 1.0f;
+ if (tex_tgt == GL_TEXTURE_2D) {
+ texfac_x /= (GLfloat)mwidth;
+ texfac_y /= (GLfloat)mheight;
+ }
+
+ // Paint it back
+ if (more_passes) {
+ glDisable(GL_STENCIL_TEST);
+ glDisable(GL_SCISSOR_TEST);
+ }
+
+ bool last_pass = false;
+ for (int i = 0; i < ps->o.blur_kernel_count; ++i) {
+ last_pass = (i == ps->o.blur_kernel_count - 1);
+ const glx_blur_pass_t *ppass = &ps->psglx->blur_passes[i];
+ assert(ppass->prog);
+
+ assert(tex_scr);
+ glBindTexture(tex_tgt, tex_scr);
+
+ if (!last_pass) {
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_2D, tex_scr2, 0);
+ glDrawBuffer(GL_COLOR_ATTACHMENT0);
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+ log_error("Framebuffer attachment failed.");
+ goto glx_blur_dst_end;
+ }
+ } else {
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glDrawBuffer(GL_BACK);
+ if (have_scissors)
+ glEnable(GL_SCISSOR_TEST);
+ if (have_stencil)
+ glEnable(GL_STENCIL_TEST);
+ }
+
+ // Color negation for testing...
+ // glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
+ // glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE);
+ // glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_ONE_MINUS_SRC_COLOR);
+
+ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+ glUseProgram(ppass->prog);
+ if (ppass->unifm_offset_x >= 0)
+ glUniform1f(ppass->unifm_offset_x, texfac_x);
+ if (ppass->unifm_offset_y >= 0)
+ glUniform1f(ppass->unifm_offset_y, texfac_y);
+ if (ppass->unifm_factor_center >= 0)
+ glUniform1f(ppass->unifm_factor_center, factor_center);
+
+ P_PAINTREG_START(crect) {
+ auto rx = (GLfloat)(crect.x1 - mdx) * texfac_x;
+ auto ry = (GLfloat)(mheight - (crect.y1 - mdy)) * texfac_y;
+ auto rxe = rx + (GLfloat)(crect.x2 - crect.x1) * texfac_x;
+ auto rye = ry - (GLfloat)(crect.y2 - crect.y1) * texfac_y;
+ auto rdx = (GLfloat)(crect.x1 - mdx);
+ auto rdy = (GLfloat)(mheight - crect.y1 + mdy);
+ if (last_pass) {
+ rdx = (GLfloat)crect.x1;
+ rdy = (GLfloat)(ps->root_height - crect.y1);
+ }
+ auto rdxe = rdx + (GLfloat)(crect.x2 - crect.x1);
+ auto rdye = rdy - (GLfloat)(crect.y2 - crect.y1);
+
+ // log_trace("%f, %f, %f, %f -> %f, %f, %f, %f", rx, ry,
+ // rxe, rye, rdx,
+ // rdy, rdxe, rdye);
+
+ glTexCoord2f(rx, ry);
+ glVertex3f(rdx, rdy, z);
+
+ glTexCoord2f(rxe, ry);
+ glVertex3f(rdxe, rdy, z);
+
+ glTexCoord2f(rxe, rye);
+ glVertex3f(rdxe, rdye, z);
+
+ glTexCoord2f(rx, rye);
+ glVertex3f(rdx, rdye, z);
+ }
+ P_PAINTREG_END();
+
+ glUseProgram(0);
+
+ // Swap tex_scr and tex_scr2
+ {
+ GLuint tmp = tex_scr2;
+ tex_scr2 = tex_scr;
+ tex_scr = tmp;
+ }
+ }
+
+ ret = true;
+
+glx_blur_dst_end:
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glBindTexture(tex_tgt, 0);
+ glDisable(tex_tgt);
+ if (have_scissors)
+ glEnable(GL_SCISSOR_TEST);
+ if (have_stencil)
+ glEnable(GL_STENCIL_TEST);
+
+ if (&ibc == pbc) {
+ free_glx_bc(ps, pbc);
+ }
+
+ gl_check_err();
+
+ return ret;
+}
+
+// TODO(bhagwan) this is a mess and needs a more consistent way of getting the border
+// pixel I tried looking for a notify event for XCB_CW_BORDER_PIXEL (in
+// xcb_create_window()) or a way to get the pixels from xcb_render_picture_t but the
+// documentation for the xcb_xrender extension is literaly non existent...
+//
+// NOTE(yshui) There is no consistent way to get the "border" color of a X window. From
+// the WM's perspective there are multiple ways to implement window borders. Using
+// glReadPixel is probably the most reliable way.
+void glx_read_border_pixel(int root_height, int root_width, int x, int y, int width,
+ int height, float *ppixel) {
+ assert(ppixel);
+
+ // Reset the color so the shader doesn't use it
+ ppixel[0] = ppixel[1] = ppixel[2] = ppixel[3] = -1.0F;
+
+ // First try bottom left corner past the
+ // circle radius (after the rounded corner ends)
+ auto screen_x = x;
+ auto screen_y = root_height - height - y;
+
+ // X is out of bounds
+ // move to the right side
+ if (screen_x < 0) {
+ screen_x += width;
+ }
+
+ // Y is out of bounds
+ // move to to top part
+ if (screen_y < 0) {
+ screen_y += height;
+ }
+
+ // All corners are out of bounds, give up
+ if (screen_x < 0 || screen_y < 0 || screen_x >= root_width || screen_y >= root_height) {
+ return;
+ }
+
+ // Invert Y-axis so we can query border color from texture (0,0)
+ glReadPixels(screen_x, screen_y, 1, 1, GL_RGBA, GL_FLOAT, (void *)ppixel);
+
+ log_trace("xy(%d, %d), glxy(%d %d) wh(%d %d), border_col(%.2f, %.2f, %.2f, %.2f)",
+ x, y, screen_x, screen_y, width, height, (float)ppixel[0],
+ (float)ppixel[1], (float)ppixel[2], (float)ppixel[3]);
+
+ gl_check_err();
+}
+
+bool glx_round_corners_dst(session_t *ps, struct managed_win *w,
+ const glx_texture_t *ptex, int dx, int dy, int width,
+ int height, float z, float cr, const region_t *reg_tgt) {
+ assert(ps->psglx->round_passes->prog);
+ bool ret = false;
+
+ // log_warn("dxy(%d, %d) wh(%d %d) rwh(%d %d) b(%d), f(%d)",
+ // dx, dy, width, height, ps->root_width, ps->root_height, w->g.border_width,
+ // w->focused);
+
+ int mdx = dx, mdy = dy, mwidth = width, mheight = height;
+ log_trace("%d, %d, %d, %d", mdx, mdy, mwidth, mheight);
+
+ if (w->g.border_width > 0) {
+ glx_read_border_pixel(ps->root_height, ps->root_width, dx, dy, width,
+ height, &w->border_col[0]);
+ }
+
+ {
+ const glx_round_pass_t *ppass = ps->psglx->round_passes;
+ assert(ppass->prog);
+
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+
+ glUseProgram(ppass->prog);
+
+ // If caller specified a texture use it as source
+ log_trace("ptex: %p wh(%d %d) %d %d", ptex, ptex->width, ptex->height,
+ ptex->target, ptex->texture);
+
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(ptex->target, ptex->texture);
+
+ if (ppass->unifm_tex_scr >= 0) {
+ glUniform1i(ppass->unifm_tex_scr, (GLint)0);
+ }
+ if (ppass->unifm_radius >= 0) {
+ glUniform1f(ppass->unifm_radius, cr);
+ }
+ if (ppass->unifm_texcoord >= 0) {
+ glUniform2f(ppass->unifm_texcoord, (float)dx, (float)dy);
+ }
+ if (ppass->unifm_texsize >= 0) {
+ glUniform2f(ppass->unifm_texsize, (float)mwidth, (float)mheight);
+ }
+ if (ppass->unifm_borderw >= 0) {
+ // Don't render rounded border if we don't know the border color
+ glUniform1f(ppass->unifm_borderw,
+ w->border_col[0] != -1. ? (GLfloat)w->g.border_width : 0);
+ }
+ if (ppass->unifm_borderc >= 0) {
+ glUniform4f(ppass->unifm_borderc, w->border_col[0],
+ w->border_col[1], w->border_col[2], w->border_col[3]);
+ }
+ if (ppass->unifm_resolution >= 0) {
+ glUniform2f(ppass->unifm_resolution, (float)ps->root_width,
+ (float)ps->root_height);
+ }
+
+ // Painting
+ {
+ P_PAINTREG_START(crect) {
+ // texture-local coordinates
+ auto rx = (GLfloat)(crect.x1 - dx);
+ auto ry = (GLfloat)(crect.y1 - dy);
+ auto rxe = rx + (GLfloat)(crect.x2 - crect.x1);
+ auto rye = ry + (GLfloat)(crect.y2 - crect.y1);
+ if (GL_TEXTURE_2D == ptex->target) {
+ rx = rx / (GLfloat)width;
+ ry = ry / (GLfloat)height;
+ rxe = rxe / (GLfloat)width;
+ rye = rye / (GLfloat)height;
+ }
+
+ // coordinates for the texture in the target
+ auto rdx = (GLfloat)crect.x1;
+ auto rdy = (GLfloat)(ps->root_height - crect.y1);
+ auto rdxe = (GLfloat)rdx + (GLfloat)(crect.x2 - crect.x1);
+ auto rdye = (GLfloat)rdy - (GLfloat)(crect.y2 - crect.y1);
+
+ // Invert Y if needed, this may not work as expected,
+ // though. I don't have such a FBConfig to test with.
+ ry = 1.0F - ry;
+ rye = 1.0F - rye;
+
+ // log_trace("Rect %d (i:%d): %f, %f, %f, %f -> %f, %f,
+ // %f, %f", ri ,ptex ? ptex->y_inverted : -1, rx, ry,
+ // rxe,
+ // rye, rdx, rdy, rdxe, rdye);
+
+ glTexCoord2f(rx, ry);
+ glVertex3f(rdx, rdy, z);
+
+ glTexCoord2f(rxe, ry);
+ glVertex3f(rdxe, rdy, z);
+
+ glTexCoord2f(rxe, rye);
+ glVertex3f(rdxe, rdye, z);
+
+ glTexCoord2f(rx, rye);
+ glVertex3f(rdx, rdye, z);
+ }
+ P_PAINTREG_END();
+ }
+
+ glUseProgram(0);
+ glDisable(GL_BLEND);
+ }
+
+ ret = true;
+
+ glBindTexture(ptex->target, 0);
+ glDisable(ptex->target);
+ glDisable(GL_BLEND);
+
+ gl_check_err();
+
+ return ret;
+}
+
+bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, int z,
+ GLfloat factor, const region_t *reg_tgt) {
+ // It's possible to dim in glx_render(), but it would be over-complicated
+ // considering all those mess in color negation and modulation
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+ glColor4f(0.0f, 0.0f, 0.0f, factor);
+
+ P_PAINTREG_START(crect) {
+ // XXX what does all of these variables mean?
+ GLint rdx = crect.x1;
+ GLint rdy = ps->root_height - crect.y1;
+ GLint rdxe = rdx + (crect.x2 - crect.x1);
+ GLint rdye = rdy - (crect.y2 - crect.y1);
+
+ glVertex3i(rdx, rdy, z);
+ glVertex3i(rdxe, rdy, z);
+ glVertex3i(rdxe, rdye, z);
+ glVertex3i(rdx, rdye, z);
+ }
+ P_PAINTREG_END();
+
+ glColor4f(0.0f, 0.0f, 0.0f, 0.0f);
+ glDisable(GL_BLEND);
+
+ gl_check_err();
+
+ return true;
+}
+
+/**
+ * @brief Render a region with texture data.
+ */
+bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, int dy,
+ int width, int height, int z, double opacity, bool argb, bool neg,
+ const region_t *reg_tgt, const glx_prog_main_t *pprogram) {
+ if (!ptex || !ptex->texture) {
+ log_error("Missing texture.");
+ return false;
+ }
+
+ const bool has_prog = pprogram && pprogram->prog;
+ bool dual_texture = false;
+
+ // It's required by legacy versions of OpenGL to enable texture target
+ // before specifying environment. Thanks to madsy for telling me.
+ glEnable(ptex->target);
+
+ // Enable blending if needed
+ if (opacity < 1.0 || argb) {
+
+ glEnable(GL_BLEND);
+
+ // Needed for handling opacity of ARGB texture
+ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+
+ // This is all weird, but X Render is using premultiplied ARGB format, and
+ // we need to use those things to correct it. Thanks to derhass for help.
+ glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+ glColor4d(opacity, opacity, opacity, opacity);
+ }
+
+ if (!has_prog) {
+ // The default, fixed-function path
+ // Color negation
+ if (neg) {
+ // Simple color negation
+ if (!glIsEnabled(GL_BLEND)) {
+ glEnable(GL_COLOR_LOGIC_OP);
+ glLogicOp(GL_COPY_INVERTED);
+ }
+ // ARGB texture color negation
+ else if (argb) {
+ dual_texture = true;
+
+ // Use two texture stages because the calculation is too
+ // complicated, thanks to madsy for providing code Texture
+ // stage 0
+ glActiveTexture(GL_TEXTURE0);
+
+ // Negation for premultiplied color: color = A - C
+ glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_SUBTRACT);
+ glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_ALPHA);
+ glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
+
+ // Pass texture alpha through
+ glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
+
+ // Texture stage 1
+ glActiveTexture(GL_TEXTURE1);
+ glEnable(ptex->target);
+ glBindTexture(ptex->target, ptex->texture);
+
+ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
+
+ // Modulation with constant factor
+ glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS);
+ glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
+ glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PRIMARY_COLOR);
+ glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_ALPHA);
+
+ // Modulation with constant factor
+ glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS);
+ glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
+ glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_PRIMARY_COLOR);
+ glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
+
+ glActiveTexture(GL_TEXTURE0);
+ }
+ // RGB blend color negation
+ else {
+ glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
+
+ // Modulation with constant factor
+ glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB,
+ GL_ONE_MINUS_SRC_COLOR);
+ glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PRIMARY_COLOR);
+ glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
+
+ // Modulation with constant factor
+ glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE);
+ glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
+ glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_PRIMARY_COLOR);
+ glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
+ }
+ }
+ } else {
+ // Programmable path
+ assert(pprogram->prog);
+ glUseProgram(pprogram->prog);
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ if (pprogram->unifm_opacity >= 0)
+ glUniform1f(pprogram->unifm_opacity, (float)opacity);
+ if (pprogram->unifm_invert_color >= 0)
+ glUniform1i(pprogram->unifm_invert_color, neg);
+ if (pprogram->unifm_tex >= 0)
+ glUniform1i(pprogram->unifm_tex, 0);
+ if (pprogram->unifm_time >= 0)
+ glUniform1f(pprogram->unifm_time, (float)ts.tv_sec * 1000.0f +
+ (float)ts.tv_nsec / 1.0e6f);
+ }
+
+ // log_trace("Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d", x, y, width, height,
+ // dx, dy, ptex->width, ptex->height, z);
+
+ // Bind texture
+ glBindTexture(ptex->target, ptex->texture);
+ if (dual_texture) {
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(ptex->target, ptex->texture);
+ glActiveTexture(GL_TEXTURE0);
+ }
+
+ // Painting
+ {
+ P_PAINTREG_START(crect) {
+ // texture-local coordinates
+ auto rx = (GLfloat)(crect.x1 - dx + x);
+ auto ry = (GLfloat)(crect.y1 - dy + y);
+ auto rxe = rx + (GLfloat)(crect.x2 - crect.x1);
+ auto rye = ry + (GLfloat)(crect.y2 - crect.y1);
+ // Rectangle textures have [0-w] [0-h] while 2D texture has [0-1]
+ // [0-1] Thanks to amonakov for pointing out!
+ if (GL_TEXTURE_2D == ptex->target) {
+ rx = rx / (GLfloat)ptex->width;
+ ry = ry / (GLfloat)ptex->height;
+ rxe = rxe / (GLfloat)ptex->width;
+ rye = rye / (GLfloat)ptex->height;
+ }
+
+ // coordinates for the texture in the target
+ GLint rdx = crect.x1;
+ GLint rdy = ps->root_height - crect.y1;
+ GLint rdxe = rdx + (crect.x2 - crect.x1);
+ GLint rdye = rdy - (crect.y2 - crect.y1);
+
+ // Invert Y if needed, this may not work as expected, though. I
+ // don't have such a FBConfig to test with.
+ if (!ptex->y_inverted) {
+ ry = 1.0f - ry;
+ rye = 1.0f - rye;
+ }
+
+ // log_trace("Rect %d: %f, %f, %f, %f -> %d, %d, %d, %d", ri, rx,
+ // ry, rxe, rye,
+ // rdx, rdy, rdxe, rdye);
+
+#define P_TEXCOORD(cx, cy) \
+ { \
+ if (dual_texture) { \
+ glMultiTexCoord2f(GL_TEXTURE0, cx, cy); \
+ glMultiTexCoord2f(GL_TEXTURE1, cx, cy); \
+ } else \
+ glTexCoord2f(cx, cy); \
+ }
+ P_TEXCOORD(rx, ry);
+ glVertex3i(rdx, rdy, z);
+
+ P_TEXCOORD(rxe, ry);
+ glVertex3i(rdxe, rdy, z);
+
+ P_TEXCOORD(rxe, rye);
+ glVertex3i(rdxe, rdye, z);
+
+ P_TEXCOORD(rx, rye);
+ glVertex3i(rdx, rdye, z);
+ }
+ P_PAINTREG_END();
+ }
+
+ // Cleanup
+ glBindTexture(ptex->target, 0);
+ glColor4f(0.0f, 0.0f, 0.0f, 0.0f);
+ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+ glDisable(GL_BLEND);
+ glDisable(GL_COLOR_LOGIC_OP);
+ glDisable(ptex->target);
+
+ if (dual_texture) {
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(ptex->target, 0);
+ glDisable(ptex->target);
+ glActiveTexture(GL_TEXTURE0);
+ }
+
+ if (has_prog)
+ glUseProgram(0);
+
+ gl_check_err();
+
+ return true;
+}
diff --git a/src/opengl.h b/src/opengl.h
new file mode 100644
index 0000000..dcd8697
--- /dev/null
+++ b/src/opengl.h
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Compton - a compositor for X11
+ *
+ * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
+ *
+ * Copyright (c) 2011-2013, Christopher Jeffrey
+ * See LICENSE-mit for more information.
+ *
+ */
+
+#pragma once
+
+#include "common.h"
+#include "compiler.h"
+#include "log.h"
+#include "region.h"
+#include "render.h"
+#include "win.h"
+
+#include <GL/gl.h>
+#include <GL/glx.h>
+#include <ctype.h>
+#include <locale.h>
+#include <stdlib.h>
+#include <string.h>
+#include <xcb/render.h>
+#include <xcb/xcb.h>
+
+typedef struct {
+ /// Fragment shader for blur.
+ GLuint frag_shader;
+ /// GLSL program for blur.
+ GLuint prog;
+ /// Location of uniform "offset_x" in blur GLSL program.
+ GLint unifm_offset_x;
+ /// Location of uniform "offset_y" in blur GLSL program.
+ GLint unifm_offset_y;
+ /// Location of uniform "factor_center" in blur GLSL program.
+ GLint unifm_factor_center;
+} glx_blur_pass_t;
+
+typedef struct {
+ /// Fragment shader for rounded corners.
+ GLuint frag_shader;
+ /// GLSL program for rounded corners.
+ GLuint prog;
+ /// Location of uniform "radius" in rounded-corners GLSL program.
+ GLint unifm_radius;
+ /// Location of uniform "texcoord" in rounded-corners GLSL program.
+ GLint unifm_texcoord;
+ /// Location of uniform "texsize" in rounded-corners GLSL program.
+ GLint unifm_texsize;
+ /// Location of uniform "borderw" in rounded-corners GLSL program.
+ GLint unifm_borderw;
+ /// Location of uniform "borderc" in rounded-corners GLSL program.
+ GLint unifm_borderc;
+ /// Location of uniform "resolution" in rounded-corners GLSL program.
+ GLint unifm_resolution;
+ /// Location of uniform "texture_scr" in rounded-corners GLSL program.
+ GLint unifm_tex_scr;
+
+} glx_round_pass_t;
+
+/// Structure containing GLX-dependent data for a session.
+typedef struct glx_session {
+ // === OpenGL related ===
+ /// GLX context.
+ GLXContext context;
+ /// Whether we have GL_ARB_texture_non_power_of_two.
+ bool has_texture_non_power_of_two;
+ /// Current GLX Z value.
+ int z;
+ glx_blur_pass_t *blur_passes;
+ glx_round_pass_t *round_passes;
+} glx_session_t;
+
+/// @brief Wrapper of a binded GLX texture.
+typedef struct _glx_texture {
+ GLuint texture;
+ GLXPixmap glpixmap;
+ xcb_pixmap_t pixmap;
+ GLenum target;
+ int width;
+ int height;
+ bool y_inverted;
+} glx_texture_t;
+
+#define CGLX_SESSION_INIT \
+ { .context = NULL }
+
+bool glx_dim_dst(session_t *ps, int dx, int dy, int width, int height, int z,
+ GLfloat factor, const region_t *reg_tgt);
+
+bool glx_render(session_t *ps, const glx_texture_t *ptex, int x, int y, int dx, int dy,
+ int width, int height, int z, double opacity, bool argb, bool neg,
+ const region_t *reg_tgt, const glx_prog_main_t *pprogram);
+
+bool glx_init(session_t *ps, bool need_render);
+
+void glx_destroy(session_t *ps);
+
+void glx_on_root_change(session_t *ps);
+
+bool glx_init_blur(session_t *ps);
+
+bool glx_init_rounded_corners(session_t *ps);
+
+#ifdef CONFIG_OPENGL
+bool glx_load_prog_main(const char *vshader_str, const char *fshader_str,
+ glx_prog_main_t *pprogram);
+#endif
+
+bool glx_bind_pixmap(session_t *ps, glx_texture_t **pptex, xcb_pixmap_t pixmap, int width,
+ int height, bool repeat, const struct glx_fbconfig_info *);
+
+void glx_release_pixmap(session_t *ps, glx_texture_t *ptex);
+
+bool glx_bind_texture(session_t *ps, glx_texture_t **pptex, int x, int y, int width, int height);
+
+void glx_paint_pre(session_t *ps, region_t *preg) attr_nonnull(1, 2);
+
+/**
+ * Check if a texture is binded, or is binded to the given pixmap.
+ */
+static inline bool glx_tex_binded(const glx_texture_t *ptex, xcb_pixmap_t pixmap) {
+ return ptex && ptex->glpixmap && ptex->texture && (!pixmap || pixmap == ptex->pixmap);
+}
+
+void glx_set_clip(session_t *ps, const region_t *reg);
+
+bool glx_blur_dst(session_t *ps, int dx, int dy, int width, int height, float z,
+ GLfloat factor_center, const region_t *reg_tgt, glx_blur_cache_t *pbc);
+
+bool glx_round_corners_dst(session_t *ps, struct managed_win *w,
+ const glx_texture_t *ptex, int dx, int dy, int width,
+ int height, float z, float cr, const region_t *reg_tgt);
+
+GLuint glx_create_shader(GLenum shader_type, const char *shader_str);
+
+GLuint glx_create_program(const GLuint *const shaders, int nshaders);
+
+GLuint glx_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str);
+
+unsigned char *glx_take_screenshot(session_t *ps, int *out_length);
+
+/**
+ * Check if there's a GLX context.
+ */
+static inline bool glx_has_context(session_t *ps) {
+ return ps->psglx && ps->psglx->context;
+}
+
+/**
+ * Ensure we have a GLX context.
+ */
+static inline bool ensure_glx_context(session_t *ps) {
+ // Create GLX context
+ if (!glx_has_context(ps))
+ glx_init(ps, false);
+
+ return ps->psglx->context;
+}
+
+/**
+ * Free a GLX texture.
+ */
+static inline void free_texture_r(session_t *ps attr_unused, GLuint *ptexture) {
+ if (*ptexture) {
+ assert(glx_has_context(ps));
+ glDeleteTextures(1, ptexture);
+ *ptexture = 0;
+ }
+}
+
+/**
+ * Free a GLX Framebuffer object.
+ */
+static inline void free_glx_fbo(GLuint *pfbo) {
+ if (*pfbo) {
+ glDeleteFramebuffers(1, pfbo);
+ *pfbo = 0;
+ }
+ assert(!*pfbo);
+}
+
+/**
+ * Free data in glx_blur_cache_t on resize.
+ */
+static inline void free_glx_bc_resize(session_t *ps, glx_blur_cache_t *pbc) {
+ free_texture_r(ps, &pbc->textures[0]);
+ free_texture_r(ps, &pbc->textures[1]);
+ pbc->width = 0;
+ pbc->height = 0;
+}
+
+/**
+ * Free a glx_blur_cache_t
+ */
+static inline void free_glx_bc(session_t *ps, glx_blur_cache_t *pbc) {
+ free_glx_fbo(&pbc->fbo);
+ free_glx_bc_resize(ps, pbc);
+}
+
+/**
+ * Free a glx_texture_t.
+ */
+static inline void free_texture(session_t *ps, glx_texture_t **pptex) {
+ glx_texture_t *ptex = *pptex;
+
+ // Quit if there's nothing
+ if (!ptex) {
+ return;
+ }
+
+ glx_release_pixmap(ps, ptex);
+
+ free_texture_r(ps, &ptex->texture);
+
+ // Free structure itself
+ free(ptex);
+ *pptex = NULL;
+}
+
+/**
+ * Free GLX part of paint_t.
+ */
+static inline void free_paint_glx(session_t *ps, paint_t *ppaint) {
+ free_texture(ps, &ppaint->ptex);
+#ifdef CONFIG_OPENGL
+ free(ppaint->fbcfg);
+#endif
+ ppaint->fbcfg = NULL;
+}
+
+/**
+ * Free GLX part of win.
+ */
+static inline void free_win_res_glx(session_t *ps, struct managed_win *w) {
+ free_paint_glx(ps, &w->paint);
+ free_paint_glx(ps, &w->shadow_paint);
+#ifdef CONFIG_OPENGL
+ free_glx_bc(ps, &w->glx_blur_cache);
+ free_texture(ps, &w->glx_texture_bg);
+#endif
+}
diff --git a/src/options.c b/src/options.c
new file mode 100644
index 0000000..6a7bb49
--- /dev/null
+++ b/src/options.c
@@ -0,0 +1,1128 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) Yuxuan Shui <[email protected]>
+
+#include <getopt.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <xcb/render.h> // for xcb_render_fixed_t, XXX
+
+#include "backend/backend.h"
+#include "common.h"
+#include "config.h"
+#include "log.h"
+#include "options.h"
+#include "utils.h"
+#include "win.h"
+
+#pragma GCC diagnostic error "-Wunused-parameter"
+
+/**
+ * Print usage text.
+ */
+static void usage(const char *argv0, int ret) {
+#define WARNING_DISABLED " (DISABLED AT COMPILE TIME)"
+ static const char *usage_text =
+ "picom (" COMPTON_VERSION ")\n"
+ "Please report bugs to https://github.com/yshui/picom\n\n"
+ "usage: %s [options]\n"
+ "Options:\n"
+ "\n"
+ "-r radius\n"
+ " The blur radius for shadows. (default 12)\n"
+ "\n"
+ "-o opacity\n"
+ " The translucency for shadows. (default .75)\n"
+ "\n"
+ "-l left-offset\n"
+ " The left offset for shadows. (default -15)\n"
+ "\n"
+ "-t top-offset\n"
+ " The top offset for shadows. (default -15)\n"
+ "\n"
+ "-I fade-in-step\n"
+ " Opacity change between steps while fading in. (default 0.028)\n"
+ "\n"
+ "-O fade-out-step\n"
+ " Opacity change between steps while fading out. (default 0.03)\n"
+ "\n"
+ "-D fade-delta-time\n"
+ " The time between steps in a fade in milliseconds. (default 10)\n"
+ "\n"
+ "-m opacity\n"
+ " The opacity for menus. (default 1.0)\n"
+ "\n"
+ "-c\n"
+ " Enabled client-side shadows on windows.\n"
+ "\n"
+ "-C\n"
+ " Avoid drawing shadows on dock/panel windows.\n"
+ "\n"
+ "-z\n"
+ " Zero the part of the shadow's mask behind the window.\n"
+ "\n"
+ "-f\n"
+ " Fade windows in/out when opening/closing and when opacity\n"
+ " changes, unless --no-fading-openclose is used.\n"
+ "\n"
+ "-F\n"
+ " Equals to -f. Deprecated.\n"
+ "\n"
+ "--animations\n"
+ " Run animations for window geometry changes (movement and scaling).\n"
+ "\n"
+ "--animation-for-open-window\n"
+ " Which animation to run when opening a window.\n"
+ " Must be one of `none`, `fly-in`, `zoom`,\n"
+ " `slide-down`, `slide-up`, `slide-left`, `slide-right`\n"
+ " (default: none).\n"
+ "\n"
+ "--animation-for-transient-window\n"
+ " Which animation to run when opening a transient window.\n"
+ " Must be one of `none`, `fly-in`, `zoom`,\n"
+ " `slide-down`, `slide-up`, `slide-left`, `slide-right`\n"
+ " (default: none).\n"
+ "\n"
+ "--animation-for-unmap-window\n"
+ " Which animation to run when hiding (e.g. minimize) a window.\n"
+ " Must be one of `auto`, `none`, `fly-in`, `zoom`,\n"
+ " `slide-down`, `slide-up`, `slide-left`, `slide-right`\n"
+ " `slide-in`, `slide-out`\n"
+ " (default: auto).\n"
+ "\n"
+ "--animation-for-workspace-switch-in\n"
+ " Which animation to run on switching workspace for windows\n"
+ " comming into view.\n"
+ " IMPORTANT: window manager must set _NET_CURRENT_DESKTOP\n"
+ " before doing the hide/show of windows\n"
+ " Must be one of `auto`, `none`, `fly-in`, `zoom`,\n"
+ " `slide-down`, `slide-up`, `slide-left`, `slide-right`\n"
+ " `slide-in`, `slide-out`\n"
+ " (default: auto).\n"
+ "\n"
+ "--animation-for-workspace-switch-out\n"
+ " Which animation to run on switching workspace for windows\n"
+ " going out of view.\n"
+ " IMPORTANT: window manager must set _NET_CURRENT_DESKTOP\n"
+ " before doing the hide/show of windows\n"
+ " Must be one of `auto`, `none`, `fly-in`, `zoom`,\n"
+ " `slide-down`, `slide-up`, `slide-left`, `slide-right`\n"
+ " `slide-in`, `slide-out`\n"
+ " (default: auto).\n"
+ "\n"
+ "--animation-stiffness\n"
+ " Stiffness (a.k.a. tension) parameter for animation (default: 200.0).\n"
+ "\n"
+ "--animation-dampening\n"
+ " Dampening (a.k.a. friction) parameter for animation (default: 25.0).\n"
+ "\n"
+ "--animation-window-mass\n"
+ " Mass parameter for animation (default: 1.0).\n"
+ "\n"
+ "--animation-delta\n"
+ " The time between steps in animation, in milliseconds. (> 0, defaults to 10).\n"
+ "\n"
+ "--animation-force-steps\n"
+ " Force animations to go step by step even if cpu usage is high \n"
+ " (default: false)\n"
+ "\n"
+ "--animation-clamping\n"
+ " Whether to clamp animations (default: true)\n"
+ "\n"
+ "-i opacity\n"
+ " Opacity of inactive windows. (0.1 - 1.0)\n"
+ "\n"
+ "-e opacity\n"
+ " Opacity of window titlebars and borders. (0.1 - 1.0)\n"
+ "\n"
+ "-G\n"
+ " Don't draw shadows on DND windows\n"
+ "\n"
+ "-b\n"
+ " Daemonize process.\n"
+ "\n"
+ "--show-all-xerrors\n"
+ " Show all X errors (for debugging).\n"
+ "\n"
+ "--config path\n"
+ " Look for configuration file at the path. Use /dev/null to avoid\n"
+ " loading configuration file."
+#ifndef CONFIG_LIBCONFIG
+ WARNING_DISABLED
+#endif
+ "\n\n"
+ "--write-pid-path path\n"
+ " Write process ID to a file.\n"
+ "\n"
+ "--shadow-color color\n"
+ " Color of shadow, as a hex RGB string (defaults to #000000)\n"
+ "\n"
+ "--shadow-red value\n"
+ " Red color value of shadow (0.0 - 1.0, defaults to 0).\n"
+ "\n"
+ "--shadow-green value\n"
+ " Green color value of shadow (0.0 - 1.0, defaults to 0).\n"
+ "\n"
+ "--shadow-blue value\n"
+ " Blue color value of shadow (0.0 - 1.0, defaults to 0).\n"
+ "\n"
+ "--inactive-opacity-override\n"
+ " Inactive opacity set by -i overrides value of _NET_WM_OPACITY.\n"
+ "\n"
+ "--inactive-dim value\n"
+ " Dim inactive windows. (0.0 - 1.0, defaults to 0)\n"
+ "\n"
+ "--active-opacity opacity\n"
+ " Default opacity for active windows. (0.0 - 1.0)\n"
+ "\n"
+ "--corner-radius value\n"
+ " Sets the radius of rounded window corners. When > 0, the compositor\n"
+ " will round the corners of windows. (defaults to 0).\n"
+ "\n"
+ "--rounded-corners-exclude condition\n"
+ " Exclude conditions for rounded corners.\n"
+ "\n"
+ "--mark-wmwin-focused\n"
+ " Try to detect WM windows and mark them as active.\n"
+ "\n"
+ "--shadow-exclude condition\n"
+ " Exclude conditions for shadows.\n"
+ "\n"
+ "--fade-exclude condition\n"
+ " Exclude conditions for fading.\n"
+ "\n"
+ "--mark-ovredir-focused\n"
+ " Mark windows that have no WM frame as active.\n"
+ "\n"
+ "--no-fading-openclose\n"
+ " Do not fade on window open/close.\n"
+ "\n"
+ "--no-fading-destroyed-argb\n"
+ " Do not fade destroyed ARGB windows with WM frame. Workaround of bugs\n"
+ " in Openbox, Fluxbox, etc.\n"
+ "\n"
+ "--shadow-ignore-shaped\n"
+ " Do not paint shadows on shaped windows. (Deprecated, use\n"
+ " --shadow-exclude \'bounding_shaped\' or\n"
+ " --shadow-exclude \'bounding_shaped && !rounded_corners\' instead.)\n"
+ "\n"
+ "--detect-rounded-corners\n"
+ " Try to detect windows with rounded corners and don't consider\n"
+ " them shaped windows. Affects --shadow-ignore-shaped,\n"
+ " --unredir-if-possible, and possibly others. You need to turn this\n"
+ " on manually if you want to match against rounded_corners in\n"
+ " conditions.\n"
+ "\n"
+ "--detect-client-opacity\n"
+ " Detect _NET_WM_OPACITY on client windows, useful for window\n"
+ " managers not passing _NET_WM_OPACITY of client windows to frame\n"
+ " windows.\n"
+ "\n"
+ "--refresh-rate val\n"
+ " Specify refresh rate of the screen. If not specified or 0, we\n"
+ " will try detecting this with X RandR extension.\n"
+ "\n"
+ "--vsync\n"
+ " Enable VSync\n"
+ "\n"
+ "--paint-on-overlay\n"
+ " Painting on X Composite overlay window.\n"
+ "\n"
+ "--use-ewmh-active-win\n"
+ " Use _NET_WM_ACTIVE_WINDOW on the root window to determine which\n"
+ " window is focused instead of using FocusIn/Out events.\n"
+ "\n"
+ "--unredir-if-possible\n"
+ " Unredirect all windows if a full-screen opaque window is\n"
+ " detected, to maximize performance for full-screen windows.\n"
+ "\n"
+ "--unredir-if-possible-delay ms\n"
+ " Delay before unredirecting the window, in milliseconds.\n"
+ " Defaults to 0.\n"
+ "\n"
+ "--unredir-if-possible-exclude condition\n"
+ " Conditions of windows that shouldn't be considered full-screen\n"
+ " for unredirecting screen.\n"
+ "\n"
+ "--focus-exclude condition\n"
+ " Specify a list of conditions of windows that should always be\n"
+ " considered focused.\n"
+ "\n"
+ "--inactive-dim-fixed\n"
+ " Use fixed inactive dim value.\n"
+ "\n"
+ "--max-brightness\n"
+ " Dims windows which average brightness is above this threshold.\n"
+ " Requires --no-use-damage.\n"
+ " Default: 1.0 or no dimming.\n"
+ "\n"
+ "--detect-transient\n"
+ " Use WM_TRANSIENT_FOR to group windows, and consider windows in\n"
+ " the same group focused at the same time.\n"
+ "\n"
+ "--detect-client-leader\n"
+ " Use WM_CLIENT_LEADER to group windows, and consider windows in\n"
+ " the same group focused at the same time. WM_TRANSIENT_FOR has\n"
+ " higher priority if --detect-transient is enabled, too.\n"
+ "\n"
+ "--blur-method\n"
+ " The algorithm used for background bluring. Available choices are:\n"
+ " 'none' to disable, 'gaussian', 'box' or 'kernel' for custom\n"
+ " convolution blur with --blur-kern.\n"
+ " Note: 'gaussian' and 'box' require --experimental-backends.\n"
+ "\n"
+ "--blur-size\n"
+ " The radius of the blur kernel for 'box' and 'gaussian' blur method.\n"
+ "\n"
+ "--blur-deviation\n"
+ " The standard deviation for the 'gaussian' blur method.\n"
+ "\n"
+ "--blur-strength\n"
+ " The strength level of the 'dual_kawase' blur method.\n"
+ "\n"
+ "--blur-background\n"
+ " Blur background of semi-transparent / ARGB windows. Bad in\n"
+ " performance. The switch name may change without prior\n"
+ " notifications.\n"
+ "\n"
+ "--blur-background-frame\n"
+ " Blur background of windows when the window frame is not opaque.\n"
+ " Implies --blur-background. Bad in performance. The switch name\n"
+ " may change.\n"
+ "\n"
+ "--blur-background-fixed\n"
+ " Use fixed blur strength instead of adjusting according to window\n"
+ " opacity.\n"
+ "\n"
+ "--blur-kern matrix\n"
+ " Specify the blur convolution kernel, with the following format:\n"
+ " WIDTH,HEIGHT,ELE1,ELE2,ELE3,ELE4,ELE5...\n"
+ " The element in the center must not be included, it will be forever\n"
+ " 1.0 or changing based on opacity, depending on whether you have\n"
+ " --blur-background-fixed.\n"
+ " A 7x7 Gaussian blur kernel looks like:\n"
+ " --blur-kern "
+ "'7,7,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0.000003,0."
+ "000102,0.003494,0.029143,0.059106,0.029143,0.003494,0.000102,0.000849,0."
+ "029143,0.243117,0.493069,0.243117,0.029143,0.000849,0.001723,0.059106,0."
+ "493069,0.493069,0.059106,0.001723,0.000849,0.029143,0.243117,0.493069,0."
+ "243117,0.029143,0.000849,0.000102,0.003494,0.029143,0.059106,0.029143,0."
+ "003494,0.000102,0.000003,0.000102,0.000849,0.001723,0.000849,0.000102,0."
+ "000003'\n"
+ " Up to 4 blur kernels may be specified, separated with semicolon, for\n"
+ " multi-pass blur.\n"
+ " May also be one the predefined kernels: 3x3box (default), 5x5box,\n"
+ " 7x7box, 3x3gaussian, 5x5gaussian, 7x7gaussian, 9x9gaussian,\n"
+ " 11x11gaussian.\n"
+ "\n"
+ "--blur-background-exclude condition\n"
+ " Exclude conditions for background blur.\n"
+ "\n"
+ "--resize-damage integer\n"
+ " Resize damaged region by a specific number of pixels. A positive\n"
+ " value enlarges it while a negative one shrinks it. Useful for\n"
+ " fixing the line corruption issues of blur. May or may not\n"
+ " work with --glx-no-stencil. Shrinking doesn't function correctly.\n"
+ "\n"
+ "--invert-color-include condition\n"
+ " Specify a list of conditions of windows that should be painted with\n"
+ " inverted color. Resource-hogging, and is not well tested.\n"
+ "\n"
+ "--opacity-rule opacity:condition\n"
+ " Specify a list of opacity rules, in the format \"PERCENT:PATTERN\",\n"
+ " like \'50:name *= \"Firefox\"'. picom-trans is recommended over\n"
+ " this. Note we do not distinguish 100%% and unset, and we don't make\n"
+ " any guarantee about possible conflicts with other programs that set\n"
+ " _NET_WM_WINDOW_OPACITY on frame or client windows.\n"
+ "\n"
+ "--shadow-exclude-reg geometry\n"
+ " Specify a X geometry that describes the region in which shadow\n"
+ " should not be painted in, such as a dock window region.\n"
+ " Use --shadow-exclude-reg \'x10+0-0\', for example, if the 10 pixels\n"
+ " on the bottom of the screen should not have shadows painted on.\n"
+ "\n"
+ "--clip-shadow-above condition\n"
+ " Specify a list of conditions of windows to not paint a shadow over,\n"
+ " such as a dock window.\n"
+ "\n"
+ "--xinerama-shadow-crop\n"
+ " Crop shadow of a window fully on a particular Xinerama screen to the\n"
+ " screen.\n"
+ "\n"
+ "--backend backend\n"
+ " Choose backend. Possible choices are xrender, glx, and\n"
+ " xr_glx_hybrid."
+#ifndef CONFIG_OPENGL
+ " (GLX BACKENDS DISABLED AT COMPILE TIME)"
+#endif
+ "\n\n"
+ "--glx-no-stencil\n"
+ " GLX backend: Avoid using stencil buffer. Might cause issues\n"
+ " when rendering transparent content. My tests show a 15%% performance\n"
+ " boost.\n"
+ "\n"
+ "--glx-no-rebind-pixmap\n"
+ " GLX backend: Avoid rebinding pixmap on window damage. Probably\n"
+ " could improve performance on rapid window content changes, but is\n"
+ " known to break things on some drivers (LLVMpipe, xf86-video-intel,\n"
+ " etc.).\n"
+ "\n"
+ "--no-use-damage\n"
+ " Disable the use of damage information. This cause the whole screen to\n"
+ " be redrawn everytime, instead of the part of the screen that has\n"
+ " actually changed. Potentially degrades the performance, but might fix\n"
+ " some artifacts.\n"
+ "\n"
+ "--xrender-sync-fence\n"
+ " Additionally use X Sync fence to sync clients' draw calls. Needed\n"
+ " on nvidia-drivers with GLX backend for some users.\n"
+ "\n"
+ "--force-win-blend\n"
+ " Force all windows to be painted with blending. Useful if you have a\n"
+ " --glx-fshader-win that could turn opaque pixels transparent.\n"
+ "\n"
+ "--dbus\n"
+ " Enable remote control via D-Bus. See the D-BUS API section in the\n"
+ " man page for more details."
+#ifndef CONFIG_DBUS
+ WARNING_DISABLED
+#endif
+ "\n\n"
+ "--benchmark cycles\n"
+ " Benchmark mode. Repeatedly paint until reaching the specified cycles.\n"
+ "\n"
+ "--benchmark-wid window-id\n"
+ " Specify window ID to repaint in benchmark mode. If omitted or is 0,\n"
+ " the whole screen is repainted.\n"
+ "\n"
+ "--monitor-repaint\n"
+ " Highlight the updated area of the screen. For debugging the xrender\n"
+ " backend only.\n"
+ "\n"
+ "--debug-mode\n"
+ " Render into a separate window, and don't take over the screen. Useful\n"
+ " when you want to attach a debugger to picom\n"
+ "\n"
+ "--no-ewmh-fullscreen\n"
+ " Do not use EWMH to detect fullscreen windows. Reverts to checking\n"
+ " if a window is fullscreen based only on its size and coordinates.\n"
+ "\n"
+ "--transparent-clipping\n"
+ " Make transparent windows clip other windows like non-transparent windows\n"
+ " do, instead of blending on top of them\n";
+ FILE *f = (ret ? stderr : stdout);
+ fprintf(f, usage_text, argv0);
+#undef WARNING_DISABLED
+}
+
+static const char *shortopts = "D:I:O:d:r:o:m:l:t:i:e:hscnfFCaSzGb";
+static const struct option longopts[] = {
+ {"help", no_argument, NULL, 'h'},
+ {"config", required_argument, NULL, 256},
+ {"shadow-radius", required_argument, NULL, 'r'},
+ {"shadow-opacity", required_argument, NULL, 'o'},
+ {"shadow-offset-x", required_argument, NULL, 'l'},
+ {"shadow-offset-y", required_argument, NULL, 't'},
+ {"fade-in-step", required_argument, NULL, 'I'},
+ {"fade-out-step", required_argument, NULL, 'O'},
+ {"fade-delta", required_argument, NULL, 'D'},
+ {"menu-opacity", required_argument, NULL, 'm'},
+ {"shadow", no_argument, NULL, 'c'},
+ {"no-dock-shadow", no_argument, NULL, 'C'},
+ {"clear-shadow", no_argument, NULL, 'z'},
+ {"fading", no_argument, NULL, 'f'},
+ {"inactive-opacity", required_argument, NULL, 'i'},
+ {"frame-opacity", required_argument, NULL, 'e'},
+ {"daemon", no_argument, NULL, 'b'},
+ {"no-dnd-shadow", no_argument, NULL, 'G'},
+ {"shadow-red", required_argument, NULL, 257},
+ {"shadow-green", required_argument, NULL, 258},
+ {"shadow-blue", required_argument, NULL, 259},
+ {"inactive-opacity-override", no_argument, NULL, 260},
+ {"inactive-dim", required_argument, NULL, 261},
+ {"mark-wmwin-focused", no_argument, NULL, 262},
+ {"shadow-exclude", required_argument, NULL, 263},
+ {"mark-ovredir-focused", no_argument, NULL, 264},
+ {"no-fading-openclose", no_argument, NULL, 265},
+ {"shadow-ignore-shaped", no_argument, NULL, 266},
+ {"detect-rounded-corners", no_argument, NULL, 267},
+ {"detect-client-opacity", no_argument, NULL, 268},
+ {"refresh-rate", required_argument, NULL, 269},
+ {"vsync", optional_argument, NULL, 270},
+ {"alpha-step", required_argument, NULL, 271},
+ {"dbe", no_argument, NULL, 272},
+ {"paint-on-overlay", no_argument, NULL, 273},
+ {"sw-opti", no_argument, NULL, 274},
+ {"vsync-aggressive", no_argument, NULL, 275},
+ {"use-ewmh-active-win", no_argument, NULL, 276},
+ {"respect-prop-shadow", no_argument, NULL, 277},
+ {"unredir-if-possible", no_argument, NULL, 278},
+ {"focus-exclude", required_argument, NULL, 279},
+ {"inactive-dim-fixed", no_argument, NULL, 280},
+ {"detect-transient", no_argument, NULL, 281},
+ {"detect-client-leader", no_argument, NULL, 282},
+ {"blur-background", no_argument, NULL, 283},
+ {"blur-background-frame", no_argument, NULL, 284},
+ {"blur-background-fixed", no_argument, NULL, 285},
+ {"dbus", no_argument, NULL, 286},
+ {"logpath", required_argument, NULL, 287},
+ {"invert-color-include", required_argument, NULL, 288},
+ {"opengl", no_argument, NULL, 289},
+ {"backend", required_argument, NULL, 290},
+ {"glx-no-stencil", no_argument, NULL, 291},
+ {"benchmark", required_argument, NULL, 293},
+ {"benchmark-wid", required_argument, NULL, 294},
+ {"blur-background-exclude", required_argument, NULL, 296},
+ {"active-opacity", required_argument, NULL, 297},
+ {"glx-no-rebind-pixmap", no_argument, NULL, 298},
+ {"glx-swap-method", required_argument, NULL, 299},
+ {"fade-exclude", required_argument, NULL, 300},
+ {"blur-kern", required_argument, NULL, 301},
+ {"resize-damage", required_argument, NULL, 302},
+ {"glx-use-gpushader4", no_argument, NULL, 303},
+ {"opacity-rule", required_argument, NULL, 304},
+ {"shadow-exclude-reg", required_argument, NULL, 305},
+ {"paint-exclude", required_argument, NULL, 306},
+ {"xinerama-shadow-crop", no_argument, NULL, 307},
+ {"unredir-if-possible-exclude", required_argument, NULL, 308},
+ {"unredir-if-possible-delay", required_argument, NULL, 309},
+ {"write-pid-path", required_argument, NULL, 310},
+ {"vsync-use-glfinish", no_argument, NULL, 311},
+ {"xrender-sync", no_argument, NULL, 312},
+ {"xrender-sync-fence", no_argument, NULL, 313},
+ {"show-all-xerrors", no_argument, NULL, 314},
+ {"no-fading-destroyed-argb", no_argument, NULL, 315},
+ {"force-win-blend", no_argument, NULL, 316},
+ {"glx-fshader-win", required_argument, NULL, 317},
+ {"version", no_argument, NULL, 318},
+ {"no-x-selection", no_argument, NULL, 319},
+ {"no-name-pixmap", no_argument, NULL, 320},
+ {"log-level", required_argument, NULL, 321},
+ {"log-file", required_argument, NULL, 322},
+ {"use-damage", no_argument, NULL, 323},
+ {"no-use-damage", no_argument, NULL, 324},
+ {"no-vsync", no_argument, NULL, 325},
+ {"max-brightness", required_argument, NULL, 326},
+ {"transparent-clipping", no_argument, NULL, 327},
+ {"blur-method", required_argument, NULL, 328},
+ {"blur-size", required_argument, NULL, 329},
+ {"blur-deviation", required_argument, NULL, 330},
+ {"blur-strength", required_argument, NULL, 331},
+ {"shadow-color", required_argument, NULL, 332},
+ {"corner-radius", required_argument, NULL, 333},
+ {"rounded-corners-exclude", required_argument, NULL, 334},
+ {"clip-shadow-above", required_argument, NULL, 335},
+ {"experimental-backends", no_argument, NULL, 733},
+ {"monitor-repaint", no_argument, NULL, 800},
+ {"diagnostics", no_argument, NULL, 801},
+ {"debug-mode", no_argument, NULL, 802},
+ {"no-ewmh-fullscreen", no_argument, NULL, 803},
+ {"animations", no_argument, NULL, 804},
+ {"animation-stiffness", required_argument, NULL, 805},
+ {"animation-dampening", required_argument, NULL, 806},
+ {"animation-window-mass", required_argument, NULL, 807},
+ {"animation-clamping", no_argument, NULL, 808},
+ {"animation-for-open-window", required_argument, NULL, 809},
+ {"animation-for-transient-window", required_argument, NULL, 810},
+ // Must terminate with a NULL entry
+ {NULL, 0, NULL, 0},
+};
+
+/// Get config options that are needed to parse the rest of the options
+/// Return true if we should quit
+bool get_early_config(int argc, char *const *argv, char **config_file, bool *all_xerrors,
+ bool *fork, int *exit_code) {
+ int o = 0, longopt_idx = -1;
+
+ // Pre-parse the commandline arguments to check for --config and invalid
+ // switches
+ // Must reset optind to 0 here in case we reread the commandline
+ // arguments
+ optind = 1;
+ *config_file = NULL;
+ *exit_code = 0;
+ while (-1 != (o = getopt_long(argc, argv, shortopts, longopts, &longopt_idx))) {
+ if (o == 256) {
+ *config_file = strdup(optarg);
+ } else if (o == 'h') {
+ usage(argv[0], 0);
+ return true;
+
+ } else if (o == 'b') {
+ *fork = true;
+ } else if (o == 'd') {
+ log_error("-d is removed, please use the DISPLAY "
+ "environment variable");
+ goto err;
+ } else if (o == 314) {
+ *all_xerrors = true;
+ } else if (o == 318) {
+ printf("%s\n", COMPTON_VERSION);
+ return true;
+ } else if (o == 'S') {
+ log_error("-S is no longer available");
+ goto err;
+ } else if (o == 320) {
+ log_error("--no-name-pixmap is no longer available");
+ goto err;
+ } else if (o == '?' || o == ':') {
+ usage(argv[0], 1);
+ goto err;
+ }
+ }
+
+ // Check for abundant positional arguments
+ if (optind < argc) {
+ // log is not initialized here yet
+ fprintf(stderr, "picom doesn't accept positional arguments.\n");
+ goto err;
+ }
+
+ return false;
+err:
+ *exit_code = 1;
+ return true;
+}
+
+/**
+ * Process arguments and configuration files.
+ */
+bool get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
+ bool fading_enable, bool conv_kern_hasneg, win_option_mask_t *winopt_mask) {
+
+ int o = 0, longopt_idx = -1;
+
+ char *lc_numeric_old = strdup(setlocale(LC_NUMERIC, NULL));
+
+ // Enforce LC_NUMERIC locale "C" here to make sure dots are recognized
+ // instead of commas in atof().
+ setlocale(LC_NUMERIC, "C");
+
+ // Parse commandline arguments. Range checking will be done later.
+
+ bool failed = false;
+ const char *deprecation_message attr_unused =
+ "has been removed. If you encounter problems "
+ "without this feature, please feel free to "
+ "open a bug report.";
+ optind = 1;
+ while (-1 != (o = getopt_long(argc, argv, shortopts, longopts, &longopt_idx))) {
+ switch (o) {
+#define P_CASEBOOL(idx, option) \
+ case idx: \
+ opt->option = true; \
+ break
+#define P_CASELONG(idx, option) \
+ case idx: \
+ if (!parse_long(optarg, &opt->option)) { \
+ exit(1); \
+ } \
+ break
+#define P_CASEINT(idx, option) \
+ case idx: \
+ if (!parse_int(optarg, &opt->option)) { \
+ exit(1); \
+ } \
+ break
+
+ // clang-format off
+ // Short options
+ case 318:
+ case 'h':
+ // These options should cause us to exit early,
+ // so assert(false) here
+ assert(false);
+ break;
+ case 'd':
+ case 'b':
+ case 'S':
+ case 314:
+ case 320:
+ // These options are handled by get_early_config()
+ break;
+ P_CASEINT('D', fade_delta);
+ case 'I': opt->fade_in_step = normalize_d(atof(optarg)); break;
+ case 'O': opt->fade_out_step = normalize_d(atof(optarg)); break;
+ case 'c': shadow_enable = true; break;
+ case 'C':
+ log_error("Option `--no-dock-shadow`/`-C` has been removed. Please"
+ " use the wintype option `shadow` of `dock` instead.");
+ failed = true; break;
+ case 'G':
+ log_error("Option `--no-dnd-shadow`/`-G` has been removed. Please "
+ "use the wintype option `shadow` of `dnd` instead.");
+ failed = true; break;
+ case 'm':;
+ double tmp;
+ tmp = normalize_d(atof(optarg));
+ winopt_mask[WINTYPE_DROPDOWN_MENU].opacity = true;
+ winopt_mask[WINTYPE_POPUP_MENU].opacity = true;
+ opt->wintype_option[WINTYPE_POPUP_MENU].opacity = tmp;
+ opt->wintype_option[WINTYPE_DROPDOWN_MENU].opacity = tmp;
+ break;
+ case 'f':
+ case 'F':
+ fading_enable = true;
+ break;
+ P_CASEINT('r', shadow_radius);
+ case 'o':
+ opt->shadow_opacity = atof(optarg);
+ break;
+ P_CASEINT('l', shadow_offset_x);
+ P_CASEINT('t', shadow_offset_y);
+ case 'i':
+ opt->inactive_opacity = normalize_d(atof(optarg));
+ break;
+ case 'e': opt->frame_opacity = atof(optarg); break;
+ case 'z':
+ log_warn("clear-shadow is removed, shadows are automatically "
+ "cleared now. If you want to prevent shadow from been "
+ "cleared under certain types of windows, you can use "
+ "the \"full-shadow\" per window type option.");
+ break;
+ case 'n':
+ case 'a':
+ case 's':
+ log_error("-n, -a, and -s have been removed.");
+ failed = true; break;
+ // Long options
+ case 256:
+ // --config
+ break;
+ case 332:;
+ // --shadow-color
+ struct color rgb;
+ rgb = hex_to_rgb(optarg);
+ opt->shadow_red = rgb.red;
+ opt->shadow_green = rgb.green;
+ opt->shadow_blue = rgb.blue;
+ break;
+ case 257:
+ // --shadow-red
+ opt->shadow_red = atof(optarg);
+ break;
+ case 258:
+ // --shadow-green
+ opt->shadow_green = atof(optarg);
+ break;
+ case 259:
+ // --shadow-blue
+ opt->shadow_blue = atof(optarg);
+ break;
+ P_CASEBOOL(260, inactive_opacity_override);
+ case 261:
+ // --inactive-dim
+ opt->inactive_dim = atof(optarg);
+ break;
+ P_CASEBOOL(262, mark_wmwin_focused);
+ case 263:
+ // --shadow-exclude
+ condlst_add(&opt->shadow_blacklist, optarg);
+ break;
+ P_CASEBOOL(264, mark_ovredir_focused);
+ P_CASEBOOL(265, no_fading_openclose);
+ P_CASEBOOL(266, shadow_ignore_shaped);
+ P_CASEBOOL(267, detect_rounded_corners);
+ P_CASEBOOL(268, detect_client_opacity);
+ P_CASEINT(269, refresh_rate);
+ case 270:
+ if (optarg) {
+ opt->vsync = parse_vsync(optarg);
+ log_warn("--vsync doesn't take argument anymore. \"%s\" "
+ "is interpreted as \"%s\" for compatibility, but "
+ "this will stop working soon",
+ optarg, opt->vsync ? "true" : "false");
+ } else {
+ opt->vsync = true;
+ }
+ break;
+ case 271:
+ // --alpha-step
+ log_error("--alpha-step has been removed, we now tries to "
+ "make use of all alpha values");
+ failed = true; break;
+ case 272:
+ log_error("--dbe has been removed");
+ failed = true; break;
+ case 273:
+ log_error("--paint-on-overlay has been removed, the feature is enabled "
+ "whenever possible");
+ failed = true; break;
+ P_CASEBOOL(274, sw_opti);
+ case 275:
+ // --vsync-aggressive
+ log_warn("--vsync-aggressive has been deprecated, please remove it"
+ " from the command line options");
+ break;
+ P_CASEBOOL(276, use_ewmh_active_win);
+ case 277:
+ // --respect-prop-shadow
+ log_warn("--respect-prop-shadow option has been deprecated, its "
+ "functionality will always be enabled. Please remove it "
+ "from the command line options");
+ break;
+ P_CASEBOOL(278, unredir_if_possible);
+ case 279:
+ // --focus-exclude
+ condlst_add(&opt->focus_blacklist, optarg);
+ break;
+ P_CASEBOOL(280, inactive_dim_fixed);
+ P_CASEBOOL(281, detect_transient);
+ P_CASEBOOL(282, detect_client_leader);
+ case 283:
+ // --blur_background
+ opt->blur_method = BLUR_METHOD_KERNEL;
+ break;
+ P_CASEBOOL(284, blur_background_frame);
+ P_CASEBOOL(285, blur_background_fixed);
+ P_CASEBOOL(286, dbus);
+ case 287:
+ log_warn("Please use --log-file instead of --logpath");
+ // fallthrough
+ case 322:
+ // --logpath, --log-file
+ free(opt->logpath);
+ opt->logpath = strdup(optarg);
+ break;
+ case 288:
+ // --invert-color-include
+ condlst_add(&opt->invert_color_list, optarg);
+ break;
+ case 289:
+ // --opengl
+ opt->backend = BKEND_GLX;
+ break;
+ case 290:
+ // --backend
+ opt->backend = parse_backend(optarg);
+ if (opt->backend >= NUM_BKEND)
+ exit(1);
+ break;
+ P_CASEBOOL(291, glx_no_stencil);
+ P_CASEINT(293, benchmark);
+ case 294:
+ // --benchmark-wid
+ opt->benchmark_wid = (xcb_window_t)strtol(optarg, NULL, 0);
+ break;
+ case 296:
+ // --blur-background-exclude
+ condlst_add(&opt->blur_background_blacklist, optarg);
+ break;
+ case 297:
+ // --active-opacity
+ opt->active_opacity = normalize_d(atof(optarg));
+ break;
+ P_CASEBOOL(298, glx_no_rebind_pixmap);
+ case 299: {
+ // --glx-swap-method
+ char *endptr;
+ long tmpval = strtol(optarg, &endptr, 10);
+ bool should_remove = true;
+ if (*endptr || !(*optarg)) {
+ // optarg is not a number, or an empty string
+ tmpval = -1;
+ }
+ if (strcmp(optarg, "undefined") != 0 && tmpval != 0) {
+ // If not undefined, we will use damage and buffer-age to
+ // limit the rendering area.
+ opt->use_damage = true;
+ should_remove = false;
+ }
+ log_warn("--glx-swap-method has been deprecated, your setting "
+ "\"%s\" should be %s.",
+ optarg,
+ !should_remove ? "replaced by `--use-damage`" :
+ "removed");
+ break;
+ }
+ case 300:
+ // --fade-exclude
+ condlst_add(&opt->fade_blacklist, optarg);
+ break;
+ case 301:
+ // --blur-kern
+ opt->blur_kerns = parse_blur_kern_lst(optarg, &conv_kern_hasneg,
+ &opt->blur_kernel_count);
+ if (!opt->blur_kerns) {
+ exit(1);
+ }
+ break;
+ P_CASEINT(302, resize_damage);
+ case 303:
+ // --glx-use-gpushader4
+ log_warn("--glx-use-gpushader4 is deprecated since v6."
+ " Please remove it from command line options.");
+ break;
+ case 304:
+ // --opacity-rule
+ if (!parse_rule_opacity(&opt->opacity_rules, optarg))
+ exit(1);
+ break;
+ case 305:
+ // --shadow-exclude-reg
+ free(opt->shadow_exclude_reg_str);
+ opt->shadow_exclude_reg_str = strdup(optarg);
+ log_warn("--shadow-exclude-reg is deprecated. You are likely "
+ "better off using --clip-shadow-above anyway");
+ break;
+ case 306:
+ // --paint-exclude
+ condlst_add(&opt->paint_blacklist, optarg);
+ break;
+ P_CASEBOOL(307, xinerama_shadow_crop);
+ case 308:
+ // --unredir-if-possible-exclude
+ condlst_add(&opt->unredir_if_possible_blacklist, optarg);
+ break;
+ P_CASELONG(309, unredir_if_possible_delay);
+ case 310:
+ // --write-pid-path
+ free(opt->write_pid_path);
+ opt->write_pid_path = strdup(optarg);
+ if (*opt->write_pid_path != '/') {
+ log_warn("--write-pid-path is not an absolute path");
+ }
+ break;
+ P_CASEBOOL(311, vsync_use_glfinish);
+ case 312:
+ // --xrender-sync
+ log_error("Please use --xrender-sync-fence instead of --xrender-sync");
+ failed = true; break;
+ P_CASEBOOL(313, xrender_sync_fence);
+ P_CASEBOOL(315, no_fading_destroyed_argb);
+ P_CASEBOOL(316, force_win_blend);
+ case 317:
+ opt->glx_fshader_win_str = strdup(optarg);
+ break;
+ case 321: {
+ enum log_level tmp_level = string_to_log_level(optarg);
+ if (tmp_level == LOG_LEVEL_INVALID) {
+ log_warn("Invalid log level, defaults to WARN");
+ } else {
+ log_set_level_tls(tmp_level);
+ }
+ break;
+ }
+ P_CASEBOOL(319, no_x_selection);
+ P_CASEBOOL(323, use_damage);
+ case 324:
+ opt->use_damage = false;
+ break;
+ case 325:
+ opt->vsync = false;
+ break;
+
+ case 326:
+ opt->max_brightness = atof(optarg);
+ break;
+ P_CASEBOOL(327, transparent_clipping);
+ case 328: {
+ // --blur-method
+ enum blur_method method = parse_blur_method(optarg);
+ if (method >= BLUR_METHOD_INVALID) {
+ log_warn("Invalid blur method %s, ignoring.", optarg);
+ } else {
+ opt->blur_method = method;
+ }
+ break;
+ }
+ case 329:
+ // --blur-size
+ opt->blur_radius = atoi(optarg);
+ break;
+ case 330:
+ // --blur-deviation
+ opt->blur_deviation = atof(optarg);
+ break;
+ case 331:
+ // --blur-strength
+ opt->blur_strength = atoi(optarg);
+ break;
+ case 333:
+ // --cornor-radius
+ opt->corner_radius = atoi(optarg);
+ break;
+ case 334:
+ // --rounded-corners-exclude
+ condlst_add(&opt->rounded_corners_blacklist, optarg);
+ break;
+ case 335:
+ // --clip-shadow-above
+ condlst_add(&opt->shadow_clip_list, optarg);
+ break;
+ P_CASEBOOL(733, experimental_backends);
+ P_CASEBOOL(800, monitor_repaint);
+ case 801: opt->print_diagnostics = true; break;
+ P_CASEBOOL(802, debug_mode);
+ P_CASEBOOL(803, no_ewmh_fullscreen);
+ P_CASEBOOL(804, animations);
+ case 805:
+ // --animation-stiffness
+ opt->animation_stiffness = atof(optarg);
+ break;
+ case 806:
+ // --animation-dampening
+ opt->animation_dampening = atof(optarg);
+ break;
+ case 807:
+ // --animation-window-masss
+ opt->animation_window_mass = atof(optarg);
+ break;
+ case 808:
+ // --animation-clamping
+ opt->animation_clamping = true;
+ break;
+ case 809: {
+ // --animation-for-open-window
+ enum open_window_animation animation = parse_open_window_animation(optarg);
+ if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
+ log_warn("Invalid open-window animation %s, ignoring.", optarg);
+ } else {
+ opt->animation_for_open_window = animation;
+ }
+ break;
+ }
+ case 810: {
+ // --animation-for-transient-window
+ enum open_window_animation animation = parse_open_window_animation(optarg);
+ if (animation >= OPEN_WINDOW_ANIMATION_INVALID) {
+ log_warn("Invalid transient-window animation %s, ignoring.", optarg);
+ } else {
+ opt->animation_for_transient_window = animation;
+ }
+ break;
+ }
+ default: usage(argv[0], 1); break;
+#undef P_CASEBOOL
+ }
+ // clang-format on
+
+ if (failed) {
+ // Parsing this option has failed, break the loop
+ break;
+ }
+ }
+
+ // Restore LC_NUMERIC
+ setlocale(LC_NUMERIC, lc_numeric_old);
+ free(lc_numeric_old);
+
+ if (failed) {
+ return false;
+ }
+
+ if (opt->monitor_repaint && opt->backend != BKEND_XRENDER &&
+ !opt->experimental_backends) {
+ log_warn("--monitor-repaint has no effect when backend is not xrender");
+ }
+
+ if (opt->experimental_backends && !backend_list[opt->backend]) {
+ log_error("Backend \"%s\" is not available as part of the experimental "
+ "backends.",
+ BACKEND_STRS[opt->backend]);
+ return false;
+ }
+
+ if (opt->debug_mode && !opt->experimental_backends) {
+ log_error("Debug mode only works with the experimental backends.");
+ return false;
+ }
+
+ if (opt->transparent_clipping && !opt->experimental_backends) {
+ log_error("Transparent clipping only works with the experimental "
+ "backends");
+ return false;
+ }
+
+ // Range checking and option assignments
+ opt->fade_delta = max2(opt->fade_delta, 1);
+ opt->shadow_radius = max2(opt->shadow_radius, 0);
+ opt->shadow_red = normalize_d(opt->shadow_red);
+ opt->shadow_green = normalize_d(opt->shadow_green);
+ opt->shadow_blue = normalize_d(opt->shadow_blue);
+ opt->inactive_dim = normalize_d(opt->inactive_dim);
+ opt->frame_opacity = normalize_d(opt->frame_opacity);
+ opt->shadow_opacity = normalize_d(opt->shadow_opacity);
+ opt->refresh_rate = normalize_i_range(opt->refresh_rate, 0, 300);
+
+ opt->max_brightness = normalize_d(opt->max_brightness);
+ if (opt->max_brightness < 1.0) {
+ if (opt->use_damage) {
+ log_warn("--max-brightness requires --no-use-damage. Falling "
+ "back to 1.0");
+ opt->max_brightness = 1.0;
+ }
+
+ if (!opt->experimental_backends || opt->backend != BKEND_GLX) {
+ log_warn("--max-brightness requires the experimental glx "
+ "backend. Falling back to 1.0");
+ opt->max_brightness = 1.0;
+ }
+ }
+
+ // --blur-background-frame implies --blur-background
+ if (opt->blur_background_frame && opt->blur_method == BLUR_METHOD_NONE) {
+ opt->blur_method = BLUR_METHOD_KERNEL;
+ }
+
+ // Apply default wintype options that are dependent on global options
+ set_default_winopts(opt, winopt_mask, shadow_enable, fading_enable,
+ opt->blur_method != BLUR_METHOD_NONE);
+
+ // Other variables determined by options
+
+ // Determine whether we track window grouping
+ if (opt->detect_transient || opt->detect_client_leader) {
+ opt->track_leader = true;
+ }
+
+ // Fill default blur kernel
+ if (opt->blur_method == BLUR_METHOD_KERNEL &&
+ (!opt->blur_kerns || !opt->blur_kerns[0])) {
+ opt->blur_kerns = parse_blur_kern_lst("3x3box", &conv_kern_hasneg,
+ &opt->blur_kernel_count);
+ CHECK(opt->blur_kerns);
+ CHECK(opt->blur_kernel_count);
+ }
+
+ // Sanitize parameters for dual-filter kawase blur
+ if (opt->blur_method == BLUR_METHOD_DUAL_KAWASE) {
+ if (opt->blur_strength <= 0 && opt->blur_radius > 500) {
+ log_warn("Blur radius >500 not supported by dual_kawase method, "
+ "capping to 500.");
+ opt->blur_radius = 500;
+ }
+ if (opt->blur_strength > 20) {
+ log_warn("Blur strength >20 not supported by dual_kawase method, "
+ "capping to 20.");
+ opt->blur_strength = 20;
+ }
+ if (!opt->experimental_backends) {
+ log_warn("Dual-kawase blur is not implemented by the legacy "
+ "backends, you must use the `experimental-backends` "
+ "option.");
+ }
+ }
+
+ if (opt->resize_damage < 0) {
+ log_warn("Negative --resize-damage will not work correctly.");
+ }
+
+ if (opt->backend == BKEND_XRENDER && conv_kern_hasneg) {
+ log_warn("A convolution kernel with negative values may not work "
+ "properly under X Render backend.");
+ }
+
+ if (opt->corner_radius > 0 && opt->experimental_backends) {
+ log_warn("Rounded corner is only supported on legacy backends, it "
+ "will be disabled");
+ opt->corner_radius = 0;
+ }
+
+ return true;
+}
+
+// vim: set noet sw=8 ts=8 :
diff --git a/src/options.h b/src/options.h
new file mode 100644
index 0000000..08aa15e
--- /dev/null
+++ b/src/options.h
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) Yuxuan Shui <[email protected]>
+#pragma once
+
+/// Parse command line options
+
+#include <stdbool.h>
+#include <xcb/render.h> // for xcb_render_fixed_t
+
+#include "compiler.h"
+#include "config.h"
+#include "types.h"
+#include "win.h" // for wintype_t
+
+typedef struct session session_t;
+
+/// Get config options that are needed to parse the rest of the options
+/// Return true if we should quit
+bool get_early_config(int argc, char *const *argv, char **config_file, bool *all_xerrors,
+ bool *fork, int *exit_code);
+
+/**
+ * Process arguments and configuration files.
+ *
+ * Parameters:
+ * shadow_enable = Carry overs from parse_config
+ * fading_enable
+ * conv_kern_hasneg
+ * winopt_mask
+ * Returns:
+ * Whether configuration are processed successfully.
+ */
+bool must_use get_cfg(options_t *opt, int argc, char *const *argv, bool shadow_enable,
+ bool fading_enable, bool conv_kern_hasneg,
+ win_option_mask_t *winopt_mask);
+
+// vim: set noet sw=8 ts=8:
diff --git a/src/picom.c b/src/picom.c
new file mode 100644
index 0000000..eb479f3
--- /dev/null
+++ b/src/picom.c
@@ -0,0 +1,2790 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Compton - a compositor for X11
+ *
+ * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
+ *
+ * Copyright (c) 2011-2013, Christopher Jeffrey
+ * See LICENSE-mit for more information.
+ *
+ */
+
+#include <X11/Xlib-xcb.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/sync.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <xcb/composite.h>
+#include <xcb/damage.h>
+#include <xcb/glx.h>
+#include <xcb/present.h>
+#include <xcb/randr.h>
+#include <xcb/render.h>
+#include <xcb/sync.h>
+#include <xcb/xfixes.h>
+#include <xcb/xinerama.h>
+
+#include <ev.h>
+#include <test.h>
+
+#include "common.h"
+#include "compiler.h"
+#include "config.h"
+#include "err.h"
+#include "kernel.h"
+#include "picom.h"
+#ifdef CONFIG_OPENGL
+#include "opengl.h"
+#endif
+#include "backend/backend.h"
+#include "c2.h"
+#include "config.h"
+#include "diagnostic.h"
+#include "log.h"
+#include "region.h"
+#include "render.h"
+#include "types.h"
+#include "utils.h"
+#include "win.h"
+#include "x.h"
+#ifdef CONFIG_DBUS
+#include "dbus.h"
+#endif
+#include "atom.h"
+#include "event.h"
+#include "file_watch.h"
+#include "list.h"
+#include "options.h"
+#include "uthash_extra.h"
+
+/// Get session_t pointer from a pointer to a member of session_t
+#define session_ptr(ptr, member) \
+ ({ \
+ const __typeof__(((session_t *)0)->member) *__mptr = (ptr); \
+ (session_t *)((char *)__mptr - offsetof(session_t, member)); \
+ })
+
+static const long SWOPTI_TOLERANCE = 3000;
+
+static bool must_use redirect_start(session_t *ps);
+
+static void unredirect(session_t *ps);
+
+// === Global constants ===
+
+/// Name strings for window types.
+const char *const WINTYPES[NUM_WINTYPES] = {
+ "unknown", "desktop", "dock", "toolbar", "menu",
+ "utility", "splash", "dialog", "normal", "dropdown_menu",
+ "popup_menu", "tooltip", "notification", "combo", "dnd",
+};
+
+// clang-format off
+/// Names of backends.
+const char *const BACKEND_STRS[] = {[BKEND_XRENDER] = "xrender",
+ [BKEND_GLX] = "glx",
+ [BKEND_XR_GLX_HYBRID] = "xr_glx_hybrid",
+ [BKEND_DUMMY] = "dummy",
+ NULL};
+// clang-format on
+
+// === Global variables ===
+
+/// Pointer to current session, as a global variable. Only used by
+/// xerror(), which could not have a pointer to current session passed in.
+/// XXX Limit what xerror can access by not having this pointer
+session_t *ps_g = NULL;
+
+void set_root_flags(session_t *ps, uint64_t flags) {
+ log_debug("Setting root flags: %" PRIu64, flags);
+ ps->root_flags |= flags;
+ ps->pending_updates = true;
+}
+
+void quit(session_t *ps) {
+ ps->quit = true;
+ ev_break(ps->loop, EVBREAK_ALL);
+}
+
+/**
+ * Free Xinerama screen info.
+ *
+ * XXX consider moving to x.c
+ */
+static inline void free_xinerama_info(session_t *ps) {
+ if (ps->xinerama_scr_regs) {
+ for (int i = 0; i < ps->xinerama_nscrs; ++i)
+ pixman_region32_fini(&ps->xinerama_scr_regs[i]);
+ free(ps->xinerama_scr_regs);
+ ps->xinerama_scr_regs = NULL;
+ }
+ ps->xinerama_nscrs = 0;
+}
+
+/**
+ * Get current system clock in milliseconds.
+ */
+static inline int64_t get_time_ms(void) {
+ struct timespec tp;
+ clock_gettime(CLOCK_MONOTONIC, &tp);
+ return (int64_t)tp.tv_sec * 1000 + (int64_t)tp.tv_nsec / 1000000;
+}
+
+// XXX Move to x.c
+void cxinerama_upd_scrs(session_t *ps) {
+ // XXX Consider deprecating Xinerama, switch to RandR when necessary
+ free_xinerama_info(ps);
+
+ if (!ps->o.xinerama_shadow_crop || !ps->xinerama_exists)
+ return;
+
+ xcb_xinerama_is_active_reply_t *active =
+ xcb_xinerama_is_active_reply(ps->c, xcb_xinerama_is_active(ps->c), NULL);
+ if (!active || !active->state) {
+ free(active);
+ return;
+ }
+ free(active);
+
+ auto xinerama_scrs =
+ xcb_xinerama_query_screens_reply(ps->c, xcb_xinerama_query_screens(ps->c), NULL);
+ if (!xinerama_scrs) {
+ return;
+ }
+
+ xcb_xinerama_screen_info_t *scrs =
+ xcb_xinerama_query_screens_screen_info(xinerama_scrs);
+ ps->xinerama_nscrs = xcb_xinerama_query_screens_screen_info_length(xinerama_scrs);
+
+ ps->xinerama_scr_regs = ccalloc(ps->xinerama_nscrs, region_t);
+ for (int i = 0; i < ps->xinerama_nscrs; ++i) {
+ const xcb_xinerama_screen_info_t *const s = &scrs[i];
+ pixman_region32_init_rect(&ps->xinerama_scr_regs[i], s->x_org, s->y_org,
+ s->width, s->height);
+ }
+ free(xinerama_scrs);
+}
+
+/**
+ * Find matched window.
+ *
+ * XXX move to win.c
+ */
+static inline struct managed_win *find_win_all(session_t *ps, const xcb_window_t wid) {
+ if (!wid || PointerRoot == wid || wid == ps->root || wid == ps->overlay)
+ return NULL;
+
+ auto w = find_managed_win(ps, wid);
+ if (!w)
+ w = find_toplevel(ps, wid);
+ if (!w)
+ w = find_managed_window_or_parent(ps, wid);
+ return w;
+}
+
+void queue_redraw(session_t *ps) {
+ // If --benchmark is used, redraw is always queued
+ if (!ps->redraw_needed && !ps->o.benchmark) {
+ ev_idle_start(ps->loop, &ps->draw_idle);
+ }
+ ps->redraw_needed = true;
+}
+
+/**
+ * Get a region of the screen size.
+ */
+static inline void get_screen_region(session_t *ps, region_t *res) {
+ pixman_box32_t b = {.x1 = 0, .y1 = 0, .x2 = ps->root_width, .y2 = ps->root_height};
+ pixman_region32_fini(res);
+ pixman_region32_init_rects(res, &b, 1);
+}
+
+void add_damage(session_t *ps, const region_t *damage) {
+ // Ignore damage when screen isn't redirected
+ if (!ps->redirected) {
+ return;
+ }
+
+ if (!damage) {
+ return;
+ }
+ log_trace("Adding damage: ");
+ dump_region(damage);
+ pixman_region32_union(ps->damage, ps->damage, (region_t *)damage);
+}
+
+// === Fading ===
+
+/**
+ * Get the time left before next fading point.
+ *
+ * In milliseconds.
+ */
+static double fade_timeout(session_t *ps) {
+ auto now = get_time_ms();
+ if (ps->o.fade_delta + ps->fade_time < now)
+ return 0;
+
+ auto diff = ps->o.fade_delta + ps->fade_time - now;
+
+ diff = clamp(diff, 0, ps->o.fade_delta * 2);
+
+ return (double)diff / 1000.0;
+}
+
+/**
+ * Run fading on a window.
+ *
+ * @param steps steps of fading
+ * @return whether we are still in fading mode
+ */
+static bool run_fade(session_t *ps, struct managed_win **_w, long steps) {
+ auto w = *_w;
+ if (w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPED) {
+ // We are not fading
+ assert(w->opacity_target == w->opacity);
+ return false;
+ }
+
+ if (!win_should_fade(ps, w)) {
+ log_debug("Window %#010x %s doesn't need fading", w->base.id, w->name);
+ w->opacity = w->opacity_target;
+ }
+ if (w->opacity == w->opacity_target) {
+ // We have reached target opacity.
+ // We don't call win_check_fade_finished here because that could destroy
+ // the window, but we still need the damage info from this window
+ log_debug("Fading finished for window %#010x %s", w->base.id, w->name);
+ return false;
+ }
+
+ if (steps) {
+ log_trace("Window %#010x (%s) opacity was: %lf", w->base.id, w->name,
+ w->opacity);
+ if (w->opacity < w->opacity_target) {
+ w->opacity = clamp(w->opacity + ps->o.fade_in_step * (double)steps,
+ 0.0, w->opacity_target);
+ } else {
+ w->opacity = clamp(w->opacity - ps->o.fade_out_step * (double)steps,
+ w->opacity_target, 1);
+ }
+ log_trace("... updated to: %lf", w->opacity);
+ }
+
+ // Note even if opacity == opacity_target here, we still want to run preprocess
+ // one last time to finish state transition. So return true in that case too.
+ return true;
+}
+
+// === Error handling ===
+
+void discard_ignore(session_t *ps, unsigned long sequence) {
+ while (ps->ignore_head) {
+ if (sequence > ps->ignore_head->sequence) {
+ ignore_t *next = ps->ignore_head->next;
+ free(ps->ignore_head);
+ ps->ignore_head = next;
+ if (!ps->ignore_head) {
+ ps->ignore_tail = &ps->ignore_head;
+ }
+ } else {
+ break;
+ }
+ }
+}
+
+static int should_ignore(session_t *ps, unsigned long sequence) {
+ if (ps == NULL) {
+ // Do not ignore errors until the session has been initialized
+ return false;
+ }
+ discard_ignore(ps, sequence);
+ return ps->ignore_head && ps->ignore_head->sequence == sequence;
+}
+
+// === Windows ===
+
+/**
+ * Determine the event mask for a window.
+ */
+uint32_t determine_evmask(session_t *ps, xcb_window_t wid, win_evmode_t mode) {
+ uint32_t evmask = 0;
+ struct managed_win *w = NULL;
+
+ // Check if it's a mapped frame window
+ if (mode == WIN_EVMODE_FRAME ||
+ ((w = find_managed_win(ps, wid)) && w->a.map_state == XCB_MAP_STATE_VIEWABLE)) {
+ evmask |= XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY;
+ if (!ps->o.use_ewmh_active_win) {
+ evmask |= XCB_EVENT_MASK_FOCUS_CHANGE;
+ }
+ }
+
+ // Check if it's a mapped client window
+ if (mode == WIN_EVMODE_CLIENT ||
+ ((w = find_toplevel(ps, wid)) && w->a.map_state == XCB_MAP_STATE_VIEWABLE)) {
+ evmask |= XCB_EVENT_MASK_PROPERTY_CHANGE;
+ }
+
+ return evmask;
+}
+
+/**
+ * Update current active window based on EWMH _NET_ACTIVE_WIN.
+ *
+ * Does not change anything if we fail to get the attribute or the window
+ * returned could not be found.
+ */
+void update_ewmh_active_win(session_t *ps) {
+ // Search for the window
+ xcb_window_t wid =
+ wid_get_prop_window(ps->c, ps->root, ps->atoms->a_NET_ACTIVE_WINDOW);
+ auto w = find_win_all(ps, wid);
+
+ // Mark the window focused. No need to unfocus the previous one.
+ if (w) {
+ win_set_focused(ps, w);
+ }
+}
+
+/**
+ * Recheck currently focused window and set its <code>w->focused</code>
+ * to true.
+ *
+ * @param ps current session
+ * @return struct _win of currently focused window, NULL if not found
+ */
+static void recheck_focus(session_t *ps) {
+ // Use EWMH _NET_ACTIVE_WINDOW if enabled
+ if (ps->o.use_ewmh_active_win) {
+ update_ewmh_active_win(ps);
+ return;
+ }
+
+ // Determine the currently focused window so we can apply appropriate
+ // opacity on it
+ xcb_window_t wid = XCB_NONE;
+ xcb_get_input_focus_reply_t *reply =
+ xcb_get_input_focus_reply(ps->c, xcb_get_input_focus(ps->c), NULL);
+
+ if (reply) {
+ wid = reply->focus;
+ free(reply);
+ }
+
+ auto w = find_win_all(ps, wid);
+
+ log_trace("%#010" PRIx32 " (%#010lx \"%s\") focused.", wid,
+ (w ? w->base.id : XCB_NONE), (w ? w->name : NULL));
+
+ // And we set the focus state here
+ if (w) {
+ win_set_focused(ps, w);
+ return;
+ }
+}
+
+/**
+ * Rebuild cached <code>screen_reg</code>.
+ */
+static void rebuild_screen_reg(session_t *ps) {
+ get_screen_region(ps, &ps->screen_reg);
+}
+
+/**
+ * Rebuild <code>shadow_exclude_reg</code>.
+ */
+static void rebuild_shadow_exclude_reg(session_t *ps) {
+ bool ret = parse_geometry(ps, ps->o.shadow_exclude_reg_str, &ps->shadow_exclude_reg);
+ if (!ret)
+ exit(1);
+}
+
+/// Free up all the images and deinit the backend
+static void destroy_backend(session_t *ps) {
+ win_stack_foreach_managed_safe(w, &ps->window_stack) {
+ // Wrapping up fading in progress
+ if (win_skip_fading(ps, w)) {
+ // `w` is freed by win_skip_fading
+ continue;
+ }
+
+ if (ps->backend_data) {
+ // Unmapped windows could still have shadow images, but not pixmap
+ // images
+ assert(!w->win_image || w->state != WSTATE_UNMAPPED);
+ if (win_check_flags_any(w, WIN_FLAGS_IMAGES_STALE) &&
+ w->state == WSTATE_MAPPED) {
+ log_warn("Stale flags set for mapped window %#010x "
+ "during backend destruction",
+ w->base.id);
+ assert(false);
+ }
+ // Unmapped windows can still have stale flags set, because their
+ // stale flags aren't handled until they are mapped.
+ win_clear_flags(w, WIN_FLAGS_IMAGES_STALE);
+ win_release_images(ps->backend_data, w);
+ }
+ free_paint(ps, &w->paint);
+ }
+
+ if (ps->backend_data && ps->root_image) {
+ ps->backend_data->ops->release_image(ps->backend_data, ps->root_image);
+ ps->root_image = NULL;
+ }
+
+ if (ps->backend_data) {
+ // deinit backend
+ if (ps->backend_blur_context) {
+ ps->backend_data->ops->destroy_blur_context(
+ ps->backend_data, ps->backend_blur_context);
+ ps->backend_blur_context = NULL;
+ }
+ ps->backend_data->ops->deinit(ps->backend_data);
+ ps->backend_data = NULL;
+ }
+}
+
+static bool initialize_blur(session_t *ps) {
+ struct kernel_blur_args kargs;
+ struct gaussian_blur_args gargs;
+ struct box_blur_args bargs;
+ struct dual_kawase_blur_args dkargs;
+
+ void *args = NULL;
+ switch (ps->o.blur_method) {
+ case BLUR_METHOD_BOX:
+ bargs.size = ps->o.blur_radius;
+ args = (void *)&bargs;
+ break;
+ case BLUR_METHOD_KERNEL:
+ kargs.kernel_count = ps->o.blur_kernel_count;
+ kargs.kernels = ps->o.blur_kerns;
+ args = (void *)&kargs;
+ break;
+ case BLUR_METHOD_GAUSSIAN:
+ gargs.size = ps->o.blur_radius;
+ gargs.deviation = ps->o.blur_deviation;
+ args = (void *)&gargs;
+ break;
+ case BLUR_METHOD_DUAL_KAWASE:
+ dkargs.size = ps->o.blur_radius;
+ dkargs.strength = ps->o.blur_strength;
+ args = (void *)&dkargs;
+ break;
+ default: return true;
+ }
+
+ ps->backend_blur_context = ps->backend_data->ops->create_blur_context(
+ ps->backend_data, ps->o.blur_method, args);
+ return ps->backend_blur_context != NULL;
+}
+
+/// Init the backend and bind all the window pixmap to backend images
+static bool initialize_backend(session_t *ps) {
+ if (ps->o.experimental_backends) {
+ assert(!ps->backend_data);
+ // Reinitialize win_data
+ assert(backend_list[ps->o.backend]);
+ ps->backend_data = backend_list[ps->o.backend]->init(ps);
+ if (!ps->backend_data) {
+ log_fatal("Failed to initialize backend, aborting...");
+ quit(ps);
+ return false;
+ }
+ ps->backend_data->ops = backend_list[ps->o.backend];
+
+ if (!initialize_blur(ps)) {
+ log_fatal("Failed to prepare for background blur, aborting...");
+ ps->backend_data->ops->deinit(ps->backend_data);
+ ps->backend_data = NULL;
+ quit(ps);
+ return false;
+ }
+
+ // window_stack shouldn't include window that's
+ // not in the hash table at this point. Since
+ // there cannot be any fading windows.
+ HASH_ITER2(ps->windows, _w) {
+ if (!_w->managed) {
+ continue;
+ }
+ auto w = (struct managed_win *)_w;
+ assert(w->state == WSTATE_MAPPED || w->state == WSTATE_UNMAPPED);
+ // We need to reacquire image
+ log_debug("Marking window %#010x (%s) for update after "
+ "redirection",
+ w->base.id, w->name);
+ win_set_flags(w, WIN_FLAGS_IMAGES_STALE);
+ ps->pending_updates = true;
+ }
+ }
+
+ // The old backends binds pixmap lazily, nothing to do here
+ return true;
+}
+
+/// Handle configure event of the root window
+static void configure_root(session_t *ps) {
+ auto r = XCB_AWAIT(xcb_get_geometry, ps->c, ps->root);
+ if (!r) {
+ log_fatal("Failed to fetch root geometry");
+ abort();
+ }
+
+ log_info("Root configuration changed, new geometry: %dx%d", r->width, r->height);
+ bool has_root_change = false;
+ if (ps->redirected) {
+ // On root window changes
+ if (ps->o.experimental_backends) {
+ assert(ps->backend_data);
+ has_root_change = ps->backend_data->ops->root_change != NULL;
+ } else {
+ // Old backend can handle root change
+ has_root_change = true;
+ }
+
+ if (!has_root_change) {
+ // deinit/reinit backend and free up resources if the backend
+ // cannot handle root change
+ destroy_backend(ps);
+ }
+ free_paint(ps, &ps->tgt_buffer);
+ }
+
+ ps->root_width = r->width;
+ ps->root_height = r->height;
+
+ auto prop = x_get_prop(ps->c, ps->root, ps->atoms->a_NET_CURRENT_DESKTOP,
+ 1L, XCB_ATOM_CARDINAL, 32);
+
+ ps->root_desktop_switch_direction = 0;
+ if (prop.nitems) {
+ ps->root_desktop_num = (int)*prop.c32;
+ }
+
+ rebuild_screen_reg(ps);
+ rebuild_shadow_exclude_reg(ps);
+
+ // Invalidate reg_ignore from the top
+ auto top_w = win_stack_find_next_managed(ps, &ps->window_stack);
+ if (top_w) {
+ rc_region_unref(&top_w->reg_ignore);
+ top_w->reg_ignore_valid = false;
+ }
+
+ if (ps->redirected) {
+ for (int i = 0; i < ps->ndamage; i++) {
+ pixman_region32_clear(&ps->damage_ring[i]);
+ }
+ ps->damage = ps->damage_ring + ps->ndamage - 1;
+#ifdef CONFIG_OPENGL
+ // GLX root change callback
+ if (BKEND_GLX == ps->o.backend && !ps->o.experimental_backends) {
+ glx_on_root_change(ps);
+ }
+#endif
+ if (has_root_change) {
+ if (ps->backend_data != NULL) {
+ ps->backend_data->ops->root_change(ps->backend_data, ps);
+ }
+ // Old backend's root_change is not a specific function
+ } else {
+ if (!initialize_backend(ps)) {
+ log_fatal("Failed to re-initialize backend after root "
+ "change, aborting...");
+ ps->quit = true;
+ /* TODO(yshui) only event handlers should request
+ * ev_break, otherwise it's too hard to keep track of what
+ * can break the event loop */
+ ev_break(ps->loop, EVBREAK_ALL);
+ return;
+ }
+
+ // Re-acquire the root pixmap.
+ root_damaged(ps);
+ }
+ force_repaint(ps);
+ }
+ return;
+}
+
+static void handle_root_flags(session_t *ps) {
+ if ((ps->root_flags & ROOT_FLAGS_SCREEN_CHANGE) != 0) {
+ if (ps->o.xinerama_shadow_crop) {
+ cxinerama_upd_scrs(ps);
+ }
+
+ if (ps->o.sw_opti && !ps->o.refresh_rate) {
+ update_refresh_rate(ps);
+ if (!ps->refresh_rate) {
+ log_warn("Refresh rate detection failed. swopti will be "
+ "temporarily disabled");
+ }
+ }
+ ps->root_flags &= ~(uint64_t)ROOT_FLAGS_SCREEN_CHANGE;
+ }
+
+ if ((ps->root_flags & ROOT_FLAGS_CONFIGURED) != 0) {
+ configure_root(ps);
+ ps->root_flags &= ~(uint64_t)ROOT_FLAGS_CONFIGURED;
+ }
+}
+
+static struct managed_win *
+paint_preprocess(session_t *ps, bool *fade_running, bool *animation_running) {
+ // XXX need better, more general name for `fade_running`. It really
+ // means if fade is still ongoing after the current frame is rendered.
+ // Same goes for `animation_running`.
+ struct managed_win *bottom = NULL;
+ *fade_running = false;
+ *animation_running = false;
+ auto now = get_time_ms();
+
+ // Fading step calculation
+ long steps = 0L;
+ if (ps->fade_time) {
+ assert(now >= ps->fade_time);
+ steps = (now - ps->fade_time) / ps->o.fade_delta;
+ } else {
+ // Reset fade_time if unset
+ ps->fade_time = now;
+ steps = 0L;
+ }
+ ps->fade_time += steps * ps->o.fade_delta;
+
+ double animation_delta = 0;
+ if (ps->o.animations) {
+ if (!ps->animation_time)
+ ps->animation_time = now;
+
+ animation_delta = (double)(now - ps->animation_time) /
+ (ps->o.animation_delta*100);
+
+ if (ps->o.animation_force_steps)
+ animation_delta = min2(animation_delta, ps->o.animation_delta/1000);
+ }
+
+ // First, let's process fading
+ win_stack_foreach_managed_safe(w, &ps->window_stack) {
+ const winmode_t mode_old = w->mode;
+ const bool was_painted = w->to_paint;
+ const double opacity_old = w->opacity;
+
+ // IMPORTANT: These window animation steps must happen before any other
+ // [pre]processing. This is because it changes the window's geometry.
+ if (ps->o.animations &&
+ !isnan(w->animation_progress) && w->animation_progress != 1.0 &&
+ ps->o.wintype_option[w->window_type].animation != 0 &&
+ win_is_mapped_in_x(w))
+ {
+ double neg_displacement_x =
+ w->animation_dest_center_x - w->animation_center_x;
+ double neg_displacement_y =
+ w->animation_dest_center_y - w->animation_center_y;
+ double neg_displacement_w = w->animation_dest_w - w->animation_w;
+ double neg_displacement_h = w->animation_dest_h - w->animation_h;
+ double acceleration_x =
+ (ps->o.animation_stiffness * neg_displacement_x -
+ ps->o.animation_dampening * w->animation_velocity_x) /
+ ps->o.animation_window_mass;
+ double acceleration_y =
+ (ps->o.animation_stiffness * neg_displacement_y -
+ ps->o.animation_dampening * w->animation_velocity_y) /
+ ps->o.animation_window_mass;
+ double acceleration_w =
+ (ps->o.animation_stiffness * neg_displacement_w -
+ ps->o.animation_dampening * w->animation_velocity_w) /
+ ps->o.animation_window_mass;
+ double acceleration_h =
+ (ps->o.animation_stiffness * neg_displacement_h -
+ ps->o.animation_dampening * w->animation_velocity_h) /
+ ps->o.animation_window_mass;
+ w->animation_velocity_x += acceleration_x * animation_delta;
+ w->animation_velocity_y += acceleration_y * animation_delta;
+ w->animation_velocity_w += acceleration_w * animation_delta;
+ w->animation_velocity_h += acceleration_h * animation_delta;
+
+ // Animate window geometry
+ double new_animation_x =
+ w->animation_center_x + w->animation_velocity_x * animation_delta;
+ double new_animation_y =
+ w->animation_center_y + w->animation_velocity_y * animation_delta;
+ double new_animation_w =
+ w->animation_w + w->animation_velocity_w * animation_delta;
+ double new_animation_h =
+ w->animation_h + w->animation_velocity_h * animation_delta;
+
+ // Negative new width/height causes segfault and it can happen
+ // when clamping disabled and shading a window
+ if (new_animation_h < 0)
+ new_animation_h = 0;
+
+ if (new_animation_w < 0)
+ new_animation_w = 0;
+
+ if (ps->o.animation_clamping) {
+ w->animation_center_x = clamp(
+ new_animation_x,
+ min2(w->animation_center_x, w->animation_dest_center_x),
+ max2(w->animation_center_x, w->animation_dest_center_x));
+ w->animation_center_y = clamp(
+ new_animation_y,
+ min2(w->animation_center_y, w->animation_dest_center_y),
+ max2(w->animation_center_y, w->animation_dest_center_y));
+ w->animation_w =
+ clamp(new_animation_w,
+ min2(w->animation_w, w->animation_dest_w),
+ max2(w->animation_w, w->animation_dest_w));
+ w->animation_h =
+ clamp(new_animation_h,
+ min2(w->animation_h, w->animation_dest_h),
+ max2(w->animation_h, w->animation_dest_h));
+ } else {
+ w->animation_center_x = new_animation_x;
+ w->animation_center_y = new_animation_y;
+ w->animation_w = new_animation_w;
+ w->animation_h = new_animation_h;
+ }
+
+ // Now we are done doing the math; we just need to submit our
+ // changes (if there are any).
+
+ struct win_geometry old_g = w->g;
+ double old_animation_progress = w->animation_progress;
+ new_animation_x = round(w->animation_center_x - w->animation_w * 0.5);
+ new_animation_y = round(w->animation_center_y - w->animation_h * 0.5);
+ new_animation_w = round(w->animation_w);
+ new_animation_h = round(w->animation_h);
+
+ bool position_changed =
+ new_animation_x != old_g.x || new_animation_y != old_g.y;
+ bool size_changed =
+ new_animation_w != old_g.width || new_animation_h != old_g.height;
+ bool geometry_changed = position_changed || size_changed;
+
+ // Mark past window region with damage
+ if (was_painted && geometry_changed)
+ add_damage_from_win(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_progress =
+ 1.0 - w->animation_inv_og_distance *
+ sqrt(x_dist * x_dist + y_dist * y_dist +
+ w_dist * w_dist + h_dist * h_dist);
+
+ // When clamping disabled we don't want the overlayed image to
+ // fade in again because process is moving to negative value
+ if (w->animation_progress < old_animation_progress)
+ w->animation_progress = old_animation_progress;
+
+ w->g.x = (int16_t)new_animation_x;
+ w->g.y = (int16_t)new_animation_y;
+ w->g.width = (uint16_t)new_animation_w;
+ w->g.height = (uint16_t)new_animation_h;
+
+ // Submit window size change
+ if (size_changed) {
+ win_on_win_size_change(ps, w);
+
+ pixman_region32_clear(&w->bounding_shape);
+ pixman_region32_fini(&w->bounding_shape);
+ pixman_region32_init_rect(&w->bounding_shape, 0, 0,
+ (uint)w->widthb, (uint)w->heightb);
+
+ if (w->state != WSTATE_DESTROYING)
+ win_clear_flags(w, WIN_FLAGS_PIXMAP_STALE);
+
+ win_process_image_flags(ps, w);
+ }
+ // Mark new window region with damage
+ if (was_painted && geometry_changed) {
+ add_damage_from_win(ps, w);
+ w->reg_ignore_valid = false;
+ }
+
+ // We can't check for 1 here as sometimes 1 = 0.999999999999999
+ // in case of floating numbers
+ if (w->animation_progress >= 0.999999999) {
+ w->animation_progress = 1;
+ w->animation_velocity_x = 0.0;
+ w->animation_velocity_y = 0.0;
+ w->animation_velocity_w = 0.0;
+ w->animation_velocity_h = 0.0;
+ }
+
+ if (!ps->root_desktop_switch_direction) {
+ if (w->state == WSTATE_UNMAPPING || w->state == WSTATE_DESTROYING) {
+ steps = 0;
+ double new_opacity = clamp(
+ w->opacity_target_old-w->animation_progress,
+ w->opacity_target, 1);
+
+ if (new_opacity < w->opacity)
+ w->opacity = new_opacity;
+
+ } else if (w->state == WSTATE_MAPPING) {
+ steps = 0;
+ double new_opacity = clamp(
+ w->animation_progress,
+ 0.0, w->opacity_target);
+
+ if (new_opacity > w->opacity)
+ w->opacity = new_opacity;
+ }
+ }
+
+ *animation_running = true;
+ }
+
+ if (win_should_dim(ps, w) != w->dim) {
+ w->dim = win_should_dim(ps, w);
+ add_damage_from_win(ps, w);
+ }
+
+ if (w->opacity != w->opacity_target) {
+ // Run fading
+ if (run_fade(ps, &w, steps)) {
+ *fade_running = true;
+ }
+
+ // Add window to damaged area if its opacity changes
+ // If was_painted == false, and to_paint is also false, we don't care
+ // If was_painted == false, but to_paint is true, damage will be added in
+ // the loop below
+ if (was_painted && w->opacity != opacity_old) {
+ add_damage_from_win(ps, w);
+ }
+
+ if (win_check_fade_finished(ps, w)) {
+ // the window has been destroyed because fading finished
+ continue;
+ }
+
+ if (win_has_frame(w)) {
+ w->frame_opacity = ps->o.frame_opacity;
+ } else {
+ w->frame_opacity = 1.0;
+ }
+
+ // Update window mode
+ w->mode = win_calc_mode(w);
+
+ // Destroy all reg_ignore above when frame opaque state changes on
+ // SOLID mode
+ if (was_painted && w->mode != mode_old) {
+ w->reg_ignore_valid = false;
+ }
+ }
+ }
+
+ if (*animation_running)
+ ps->animation_time = now;
+
+ // Opacity will not change, from now on.
+ rc_region_t *last_reg_ignore = rc_region_new();
+
+ bool unredir_possible = false;
+ // Track whether it's the highest window to paint
+ bool is_highest = true;
+ bool reg_ignore_valid = true;
+ win_stack_foreach_managed(w, &ps->window_stack) {
+ __label__ skip_window;
+ bool to_paint = true;
+ // w->to_paint remembers whether this window is painted last time
+ const bool was_painted = w->to_paint;
+
+ // Destroy reg_ignore if some window above us invalidated it
+ if (!reg_ignore_valid) {
+ rc_region_unref(&w->reg_ignore);
+ }
+
+ // log_trace("%d %d %s", w->a.map_state, w->ever_damaged, w->name);
+
+ // Give up if it's not damaged or invisible, or it's unmapped and its
+ // pixmap is gone (for example due to a ConfigureNotify), or when it's
+ // excluded
+ if (w->state == WSTATE_UNMAPPED ||
+ unlikely(w->base.id == ps->debug_window ||
+ w->client_win == ps->debug_window)) {
+
+ if (!*fade_running || w->opacity == w->opacity_target)
+ to_paint = false;
+
+ } else if (!w->ever_damaged && w->state != WSTATE_UNMAPPING &&
+ w->state != WSTATE_DESTROYING) {
+ // Unmapping clears w->ever_damaged, but the fact that the window
+ // is fading out means it must have been damaged when it was still
+ // mapped (because unmap_win_start will skip fading if it wasn't),
+ // so we still need to paint it.
+ log_trace("Window %#010x (%s) will not be painted because it has "
+ "not received any damages",
+ w->base.id, w->name);
+ to_paint = false;
+ } else if (unlikely(w->g.x + w->g.width < 1 || w->g.y + w->g.height < 1 ||
+ w->g.x >= ps->root_width || w->g.y >= ps->root_height)) {
+ log_trace("Window %#010x (%s) will not be painted because it is "
+ "positioned outside of the screen",
+ w->base.id, w->name);
+ to_paint = false;
+ } else if (unlikely((double)w->opacity * MAX_ALPHA < 1 && !w->blur_background)) {
+ /* TODO(yshui) for consistency, even a window has 0 opacity, we
+ * still probably need to blur its background, so to_paint
+ * shouldn't be false for them. */
+ log_trace("Window %#010x (%s) will not be painted because it has "
+ "0 opacity",
+ w->base.id, w->name);
+ to_paint = false;
+ } else if (w->paint_excluded) {
+ log_trace("Window %#010x (%s) will not be painted because it is "
+ "excluded from painting",
+ w->base.id, w->name);
+ to_paint = false;
+ } else if (unlikely((w->flags & WIN_FLAGS_IMAGE_ERROR) != 0)) {
+ log_trace("Window %#010x (%s) will not be painted because it has "
+ "image errors",
+ w->base.id, w->name);
+ to_paint = false;
+ }
+ // log_trace("%s %d %d %d", w->name, to_paint, w->opacity,
+ // w->paint_excluded);
+
+ // Add window to damaged area if its painting status changes
+ // or opacity changes
+ if (to_paint != was_painted) {
+ w->reg_ignore_valid = false;
+ add_damage_from_win(ps, w);
+ }
+
+ // to_paint will never change after this point
+ if (!to_paint) {
+ goto skip_window;
+ }
+
+ log_trace("Window %#010x (%s) will be painted", w->base.id, w->name);
+
+ // Calculate shadow opacity
+ w->shadow_opacity = ps->o.shadow_opacity * w->opacity * ps->o.frame_opacity;
+
+ // Generate ignore region for painting to reduce GPU load
+ if (!w->reg_ignore) {
+ w->reg_ignore = rc_region_ref(last_reg_ignore);
+ }
+
+ // If the window is solid, or we enabled clipping for transparent windows,
+ // we add the window region to the ignored region
+ // Otherwise last_reg_ignore shouldn't change
+ if ((w->mode != WMODE_TRANS && !ps->o.force_win_blend) ||
+ ps->o.transparent_clipping) {
+ // w->mode == WMODE_SOLID or WMODE_FRAME_TRANS
+ region_t *tmp = rc_region_new();
+ if (w->mode == WMODE_SOLID) {
+ *tmp =
+ win_get_bounding_shape_global_without_corners_by_val(w);
+ } else {
+ // w->mode == WMODE_FRAME_TRANS
+ win_get_region_noframe_local_without_corners(w, tmp);
+ pixman_region32_intersect(tmp, tmp, &w->bounding_shape);
+ pixman_region32_translate(tmp, w->g.x, w->g.y);
+ }
+
+ pixman_region32_union(tmp, tmp, last_reg_ignore);
+ rc_region_unref(&last_reg_ignore);
+ last_reg_ignore = tmp;
+ }
+
+ // (Un)redirect screen
+ // We could definitely unredirect the screen when there's no window to
+ // paint, but this is typically unnecessary, may cause flickering when
+ // fading is enabled, and could create inconsistency when the wallpaper
+ // is not correctly set.
+ if (ps->o.unredir_if_possible && is_highest) {
+ if (w->mode == WMODE_SOLID && !ps->o.force_win_blend &&
+ win_is_fullscreen(ps, w) && !w->unredir_if_possible_excluded) {
+ unredir_possible = true;
+ }
+ }
+
+ // Unredirect screen if some window is requesting compositor bypass, even
+ // if that window is not on the top.
+ if (ps->o.unredir_if_possible && win_is_bypassing_compositor(ps, w) &&
+ !w->unredir_if_possible_excluded) {
+ // Here we deviate from EWMH a bit. EWMH says we must not
+ // unredirect the screen if the window requesting bypassing would
+ // look different after unredirecting. Instead we always follow
+ // the request.
+ unredir_possible = true;
+ }
+
+ w->prev_trans = bottom;
+ if (bottom) {
+ w->stacking_rank = bottom->stacking_rank + 1;
+ } else {
+ w->stacking_rank = 0;
+ }
+ bottom = w;
+
+ // If the screen is not redirected and the window has redir_ignore set,
+ // this window should not cause the screen to become redirected
+ if (!(ps->o.wintype_option[w->window_type].redir_ignore && !ps->redirected)) {
+ is_highest = false;
+ }
+
+ skip_window:
+ reg_ignore_valid = reg_ignore_valid && w->reg_ignore_valid;
+ w->reg_ignore_valid = true;
+
+ // Avoid setting w->to_paint if w is freed
+ if (w) {
+ w->to_paint = to_paint;
+ }
+ }
+
+ rc_region_unref(&last_reg_ignore);
+
+ // If possible, unredirect all windows and stop painting
+ if (ps->o.redirected_force != UNSET) {
+ unredir_possible = !ps->o.redirected_force;
+ } else if (ps->o.unredir_if_possible && is_highest && !ps->redirected) {
+ // If there's no window to paint, and the screen isn't redirected,
+ // don't redirect it.
+ unredir_possible = true;
+ }
+ if (unredir_possible) {
+ if (ps->redirected) {
+ if (!ps->o.unredir_if_possible_delay || ps->tmout_unredir_hit) {
+ unredirect(ps);
+ } else if (!ev_is_active(&ps->unredir_timer)) {
+ ev_timer_set(
+ &ps->unredir_timer,
+ (double)ps->o.unredir_if_possible_delay / 1000.0, 0);
+ ev_timer_start(ps->loop, &ps->unredir_timer);
+ }
+ }
+ } else {
+ ev_timer_stop(ps->loop, &ps->unredir_timer);
+ if (!ps->redirected) {
+ if (!redirect_start(ps)) {
+ return NULL;
+ }
+ }
+ }
+
+ return bottom;
+}
+
+void root_damaged(session_t *ps) {
+ if (ps->root_tile_paint.pixmap) {
+ free_root_tile(ps);
+ }
+
+ if (!ps->redirected) {
+ return;
+ }
+
+ if (ps->backend_data) {
+ if (ps->root_image) {
+ ps->backend_data->ops->release_image(ps->backend_data, ps->root_image);
+ }
+ auto pixmap = x_get_root_back_pixmap(ps->c, ps->root, ps->atoms);
+ if (pixmap != XCB_NONE) {
+ ps->root_image = ps->backend_data->ops->bind_pixmap(
+ ps->backend_data, pixmap, x_get_visual_info(ps->c, ps->vis), false);
+ ps->backend_data->ops->set_image_property(
+ ps->backend_data, IMAGE_PROPERTY_EFFECTIVE_SIZE,
+ ps->root_image, (int[]){ps->root_width, ps->root_height});
+ }
+ }
+
+ // Mark screen damaged
+ force_repaint(ps);
+}
+
+/**
+ * Xlib error handler function.
+ */
+static int xerror(Display attr_unused *dpy, XErrorEvent *ev) {
+ if (!should_ignore(ps_g, ev->serial)) {
+ x_print_error(ev->serial, ev->request_code, ev->minor_code, ev->error_code);
+ }
+ return 0;
+}
+
+/**
+ * XCB error handler function.
+ */
+void ev_xcb_error(session_t *ps, xcb_generic_error_t *err) {
+ if (!should_ignore(ps, err->sequence)) {
+ x_print_error(err->sequence, err->major_code, err->minor_code, err->error_code);
+ }
+}
+
+/**
+ * Force a full-screen repaint.
+ */
+void force_repaint(session_t *ps) {
+ assert(pixman_region32_not_empty(&ps->screen_reg));
+ queue_redraw(ps);
+ add_damage(ps, &ps->screen_reg);
+}
+
+#ifdef CONFIG_DBUS
+/** @name DBus hooks
+ */
+///@{
+
+/**
+ * Set no_fading_openclose option.
+ *
+ * Don't affect fading already in progress
+ */
+void opts_set_no_fading_openclose(session_t *ps, bool newval) {
+ ps->o.no_fading_openclose = newval;
+}
+
+//!@}
+#endif
+
+/**
+ * Setup window properties, then register us with the compositor selection (_NET_WM_CM_S)
+ *
+ * @return 0 if success, 1 if compositor already running, -1 if error.
+ */
+static int register_cm(session_t *ps) {
+ assert(!ps->reg_win);
+
+ ps->reg_win = x_new_id(ps->c);
+ auto e = xcb_request_check(
+ ps->c, xcb_create_window_checked(ps->c, XCB_COPY_FROM_PARENT, ps->reg_win, ps->root,
+ 0, 0, 1, 1, 0, XCB_NONE, ps->vis, 0, NULL));
+
+ if (e) {
+ log_fatal("Failed to create window.");
+ free(e);
+ return -1;
+ }
+
+ const xcb_atom_t prop_atoms[] = {
+ ps->atoms->aWM_NAME,
+ ps->atoms->a_NET_WM_NAME,
+ ps->atoms->aWM_ICON_NAME,
+ };
+
+ const bool prop_is_utf8[] = {false, true, false};
+
+ // Set names and classes
+ for (size_t i = 0; i < ARR_SIZE(prop_atoms); i++) {
+ e = xcb_request_check(
+ ps->c, xcb_change_property_checked(
+ ps->c, XCB_PROP_MODE_REPLACE, ps->reg_win, prop_atoms[i],
+ prop_is_utf8[i] ? ps->atoms->aUTF8_STRING : XCB_ATOM_STRING,
+ 8, strlen("picom"), "picom"));
+ if (e) {
+ log_error_x_error(e, "Failed to set window property %d",
+ prop_atoms[i]);
+ free(e);
+ }
+ }
+
+ const char picom_class[] = "picom\0picom";
+ e = xcb_request_check(
+ ps->c, xcb_change_property_checked(ps->c, XCB_PROP_MODE_REPLACE, ps->reg_win,
+ ps->atoms->aWM_CLASS, XCB_ATOM_STRING, 8,
+ ARR_SIZE(picom_class), picom_class));
+ if (e) {
+ log_error_x_error(e, "Failed to set the WM_CLASS property");
+ free(e);
+ }
+
+ // Set WM_CLIENT_MACHINE. As per EWMH, because we set _NET_WM_PID, we must also
+ // set WM_CLIENT_MACHINE.
+ {
+ const auto hostname_max = (unsigned long)sysconf(_SC_HOST_NAME_MAX);
+ char *hostname = malloc(hostname_max);
+
+ if (gethostname(hostname, hostname_max) == 0) {
+ e = xcb_request_check(
+ ps->c, xcb_change_property_checked(
+ ps->c, XCB_PROP_MODE_REPLACE, ps->reg_win,
+ ps->atoms->aWM_CLIENT_MACHINE, XCB_ATOM_STRING, 8,
+ (uint32_t)strlen(hostname), hostname));
+ if (e) {
+ log_error_x_error(e, "Failed to set the WM_CLIENT_MACHINE"
+ " property");
+ free(e);
+ }
+ } else {
+ log_error_errno("Failed to get hostname");
+ }
+
+ free(hostname);
+ }
+
+ // Set _NET_WM_PID
+ {
+ auto pid = getpid();
+ xcb_change_property(ps->c, XCB_PROP_MODE_REPLACE, ps->reg_win,
+ ps->atoms->a_NET_WM_PID, XCB_ATOM_CARDINAL, 32, 1, &pid);
+ }
+
+ // Set COMPTON_VERSION
+ e = xcb_request_check(
+ ps->c, xcb_change_property_checked(
+ ps->c, XCB_PROP_MODE_REPLACE, ps->reg_win,
+ get_atom(ps->atoms, "COMPTON_VERSION"), XCB_ATOM_STRING, 8,
+ (uint32_t)strlen(COMPTON_VERSION), COMPTON_VERSION));
+ if (e) {
+ log_error_x_error(e, "Failed to set COMPTON_VERSION.");
+ free(e);
+ }
+
+ // Acquire X Selection _NET_WM_CM_S?
+ if (!ps->o.no_x_selection) {
+ const char register_prop[] = "_NET_WM_CM_S";
+ xcb_atom_t atom;
+
+ char *buf = NULL;
+ if (asprintf(&buf, "%s%d", register_prop, ps->scr) < 0) {
+ log_fatal("Failed to allocate memory");
+ return -1;
+ }
+ atom = get_atom(ps->atoms, buf);
+ free(buf);
+
+ xcb_get_selection_owner_reply_t *reply = xcb_get_selection_owner_reply(
+ ps->c, xcb_get_selection_owner(ps->c, atom), NULL);
+
+ if (reply && reply->owner != XCB_NONE) {
+ // Another compositor already running
+ free(reply);
+ return 1;
+ }
+ free(reply);
+ xcb_set_selection_owner(ps->c, ps->reg_win, atom, 0);
+ }
+
+ return 0;
+}
+
+/**
+ * Write PID to a file.
+ */
+static inline bool write_pid(session_t *ps) {
+ if (!ps->o.write_pid_path) {
+ return true;
+ }
+
+ FILE *f = fopen(ps->o.write_pid_path, "w");
+ if (unlikely(!f)) {
+ log_error("Failed to write PID to \"%s\".", ps->o.write_pid_path);
+ return false;
+ }
+
+ fprintf(f, "%ld\n", (long)getpid());
+ fclose(f);
+
+ return true;
+}
+
+/**
+ * Update refresh rate info with X Randr extension.
+ */
+void update_refresh_rate(session_t *ps) {
+ xcb_randr_get_screen_info_reply_t *randr_info = xcb_randr_get_screen_info_reply(
+ ps->c, xcb_randr_get_screen_info(ps->c, ps->root), NULL);
+
+ if (!randr_info)
+ return;
+ ps->refresh_rate = randr_info->rate;
+ free(randr_info);
+
+ if (ps->refresh_rate)
+ ps->refresh_intv = US_PER_SEC / ps->refresh_rate;
+ else
+ ps->refresh_intv = 0;
+}
+
+/**
+ * Initialize refresh-rated based software optimization.
+ *
+ * @return true for success, false otherwise
+ */
+static bool swopti_init(session_t *ps) {
+ log_warn("--sw-opti is going to be deprecated. If you get real benefits from "
+ "using "
+ "this option, please open an issue to let us know.");
+ // Prepare refresh rate
+ // Check if user provides one
+ ps->refresh_rate = ps->o.refresh_rate;
+ if (ps->refresh_rate)
+ ps->refresh_intv = US_PER_SEC / ps->refresh_rate;
+
+ // Auto-detect refresh rate otherwise
+ if (!ps->refresh_rate && ps->randr_exists) {
+ update_refresh_rate(ps);
+ }
+
+ // Turn off vsync_sw if we can't get the refresh rate
+ if (!ps->refresh_rate)
+ return false;
+
+ return true;
+}
+
+/**
+ * Modify a struct timeval timeout value to render at a fixed pace.
+ *
+ * @param ps current session
+ * @param[in,out] ptv pointer to the timeout
+ */
+static double swopti_handle_timeout(session_t *ps) {
+ if (!ps->refresh_intv)
+ return 0;
+
+ // Get the microsecond offset of the time when the we reach the timeout
+ // I don't think a 32-bit long could overflow here.
+ long offset = (get_time_timeval().tv_usec - ps->paint_tm_offset) % ps->refresh_intv;
+ // XXX this formula dones't work if refresh rate is not a whole number
+ if (offset < 0)
+ offset += ps->refresh_intv;
+
+ // If the target time is sufficiently close to a refresh time, don't add
+ // an offset, to avoid certain blocking conditions.
+ if (offset < SWOPTI_TOLERANCE || offset > ps->refresh_intv - SWOPTI_TOLERANCE)
+ return 0;
+
+ // Add an offset so we wait until the next refresh after timeout
+ return (double)(ps->refresh_intv - offset) / 1e6;
+}
+
+/**
+ * Initialize X composite overlay window.
+ */
+static bool init_overlay(session_t *ps) {
+ xcb_composite_get_overlay_window_reply_t *reply =
+ xcb_composite_get_overlay_window_reply(
+ ps->c, xcb_composite_get_overlay_window(ps->c, ps->root), NULL);
+ if (reply) {
+ ps->overlay = reply->overlay_win;
+ free(reply);
+ } else {
+ ps->overlay = XCB_NONE;
+ }
+ if (ps->overlay != XCB_NONE) {
+ // Set window region of the overlay window, code stolen from
+ // compiz-0.8.8
+ if (!XCB_AWAIT_VOID(xcb_shape_mask, ps->c, XCB_SHAPE_SO_SET,
+ XCB_SHAPE_SK_BOUNDING, ps->overlay, 0, 0, 0)) {
+ log_fatal("Failed to set the bounding shape of overlay, giving "
+ "up.");
+ return false;
+ }
+ if (!XCB_AWAIT_VOID(xcb_shape_rectangles, ps->c, XCB_SHAPE_SO_SET,
+ XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED,
+ ps->overlay, 0, 0, 0, NULL)) {
+ log_fatal("Failed to set the input shape of overlay, giving up.");
+ return false;
+ }
+
+ // Listen to Expose events on the overlay
+ xcb_change_window_attributes(ps->c, ps->overlay, XCB_CW_EVENT_MASK,
+ (const uint32_t[]){XCB_EVENT_MASK_EXPOSURE});
+
+ // Retrieve DamageNotify on root window if we are painting on an
+ // overlay
+ // root_damage = XDamageCreate(ps->dpy, root, XDamageReportNonEmpty);
+
+ // Unmap the overlay, we will map it when needed in redirect_start
+ XCB_AWAIT_VOID(xcb_unmap_window, ps->c, ps->overlay);
+ } else {
+ log_error("Cannot get X Composite overlay window. Falling "
+ "back to painting on root window.");
+ }
+ log_debug("overlay = %#010x", ps->overlay);
+
+ return true;
+}
+
+static bool init_debug_window(session_t *ps) {
+ xcb_colormap_t colormap = x_new_id(ps->c);
+ ps->debug_window = x_new_id(ps->c);
+
+ auto err = xcb_request_check(
+ ps->c, xcb_create_colormap_checked(ps->c, XCB_COLORMAP_ALLOC_NONE, colormap,
+ ps->root, ps->vis));
+ if (err) {
+ goto err_out;
+ }
+
+ err = xcb_request_check(
+ ps->c, xcb_create_window_checked(ps->c, (uint8_t)ps->depth, ps->debug_window,
+ ps->root, 0, 0, to_u16_checked(ps->root_width),
+ to_u16_checked(ps->root_height), 0,
+ XCB_WINDOW_CLASS_INPUT_OUTPUT, ps->vis,
+ XCB_CW_COLORMAP, (uint32_t[]){colormap, 0}));
+ if (err) {
+ goto err_out;
+ }
+
+ err = xcb_request_check(ps->c, xcb_map_window(ps->c, ps->debug_window));
+ if (err) {
+ goto err_out;
+ }
+ return true;
+
+err_out:
+ free(err);
+ return false;
+}
+
+xcb_window_t session_get_target_window(session_t *ps) {
+ if (ps->o.debug_mode) {
+ return ps->debug_window;
+ }
+ return ps->overlay != XCB_NONE ? ps->overlay : ps->root;
+}
+
+uint8_t session_redirection_mode(session_t *ps) {
+ if (ps->o.debug_mode) {
+ // If the backend is not rendering to the screen, we don't need to
+ // take over the screen.
+ assert(ps->o.experimental_backends);
+ return XCB_COMPOSITE_REDIRECT_AUTOMATIC;
+ }
+ if (ps->o.experimental_backends && !backend_list[ps->o.backend]->present) {
+ // if the backend doesn't render anything, we don't need to take over the
+ // screen.
+ return XCB_COMPOSITE_REDIRECT_AUTOMATIC;
+ }
+ return XCB_COMPOSITE_REDIRECT_MANUAL;
+}
+
+/**
+ * Redirect all windows.
+ *
+ * @return whether the operation succeeded or not
+ */
+static bool redirect_start(session_t *ps) {
+ assert(!ps->redirected);
+ log_debug("Redirecting the screen.");
+
+ // Map overlay window. Done firstly according to this:
+ // https://bugzilla.gnome.org/show_bug.cgi?id=597014
+ if (ps->overlay != XCB_NONE) {
+ xcb_map_window(ps->c, ps->overlay);
+ }
+
+ bool success = XCB_AWAIT_VOID(xcb_composite_redirect_subwindows, ps->c, ps->root,
+ session_redirection_mode(ps));
+ if (!success) {
+ log_fatal("Another composite manager is already running "
+ "(and does not handle _NET_WM_CM_Sn correctly)");
+ return false;
+ }
+
+ x_sync(ps->c);
+
+ if (!initialize_backend(ps)) {
+ return false;
+ }
+
+ if (ps->o.experimental_backends) {
+ assert(ps->backend_data);
+ ps->ndamage = ps->backend_data->ops->max_buffer_age;
+ } else {
+ ps->ndamage = maximum_buffer_age(ps);
+ }
+ ps->damage_ring = ccalloc(ps->ndamage, region_t);
+ ps->damage = ps->damage_ring + ps->ndamage - 1;
+
+ for (int i = 0; i < ps->ndamage; i++) {
+ pixman_region32_init(&ps->damage_ring[i]);
+ }
+
+ // Must call XSync() here
+ x_sync(ps->c);
+
+ ps->redirected = true;
+ ps->first_frame = true;
+
+ // Re-detect driver since we now have a backend
+ ps->drivers = detect_driver(ps->c, ps->backend_data, ps->root);
+ apply_driver_workarounds(ps, ps->drivers);
+
+ root_damaged(ps);
+
+ // Repaint the whole screen
+ force_repaint(ps);
+ log_debug("Screen redirected.");
+ return true;
+}
+
+/**
+ * Unredirect all windows.
+ */
+static void unredirect(session_t *ps) {
+ assert(ps->redirected);
+ log_debug("Unredirecting the screen.");
+
+ destroy_backend(ps);
+
+ xcb_composite_unredirect_subwindows(ps->c, ps->root, session_redirection_mode(ps));
+ // Unmap overlay window
+ if (ps->overlay != XCB_NONE) {
+ xcb_unmap_window(ps->c, ps->overlay);
+ }
+
+ // Free the damage ring
+ for (int i = 0; i < ps->ndamage; ++i) {
+ pixman_region32_fini(&ps->damage_ring[i]);
+ }
+ ps->ndamage = 0;
+ free(ps->damage_ring);
+ ps->damage_ring = ps->damage = NULL;
+
+ // Must call XSync() here
+ x_sync(ps->c);
+
+ ps->redirected = false;
+ log_debug("Screen unredirected.");
+}
+
+// Handle queued events before we go to sleep
+static void handle_queued_x_events(EV_P attr_unused, ev_prepare *w, int revents attr_unused) {
+ session_t *ps = session_ptr(w, event_check);
+ xcb_generic_event_t *ev;
+ while ((ev = xcb_poll_for_queued_event(ps->c))) {
+ ev_handle(ps, ev);
+ free(ev);
+ };
+ // Flush because if we go into sleep when there is still
+ // requests in the outgoing buffer, they will not be sent
+ // for an indefinite amount of time.
+ // Use XFlush here too, we might still use some Xlib functions
+ // because OpenGL.
+ XFlush(ps->dpy);
+ xcb_flush(ps->c);
+ int err = xcb_connection_has_error(ps->c);
+ if (err) {
+ log_fatal("X11 server connection broke (error %d)", err);
+ exit(1);
+ }
+}
+
+static void handle_new_windows(session_t *ps) {
+ list_foreach_safe(struct win, w, &ps->window_stack, stack_neighbour) {
+ if (w->is_new) {
+ auto new_w = fill_win(ps, w);
+ if (!new_w->managed) {
+ continue;
+ }
+ auto mw = (struct managed_win *)new_w;
+ if (mw->a.map_state == XCB_MAP_STATE_VIEWABLE) {
+ win_set_flags(mw, WIN_FLAGS_MAPPED);
+
+ // This window might be damaged before we called fill_win
+ // and created the damage handle. And there is no way for
+ // us to find out. So just blindly mark it damaged
+ mw->ever_damaged = true;
+ }
+ }
+ }
+}
+
+static void refresh_windows(session_t *ps) {
+ win_stack_foreach_managed(w, &ps->window_stack) {
+ win_process_update_flags(ps, w);
+ }
+}
+
+static void refresh_images(session_t *ps) {
+ win_stack_foreach_managed(w, &ps->window_stack) {
+ win_process_image_flags(ps, w);
+ }
+}
+
+/**
+ * Unredirection timeout callback.
+ */
+static void tmout_unredir_callback(EV_P attr_unused, ev_timer *w, int revents attr_unused) {
+ session_t *ps = session_ptr(w, unredir_timer);
+ ps->tmout_unredir_hit = true;
+ queue_redraw(ps);
+}
+
+static void fade_timer_callback(EV_P attr_unused, ev_timer *w, int revents attr_unused) {
+ session_t *ps = session_ptr(w, fade_timer);
+ queue_redraw(ps);
+}
+
+static void animation_timer_callback(EV_P attr_unused, ev_timer *w, int revents attr_unused) {
+ session_t *ps = session_ptr(w, animation_timer);
+ queue_redraw(ps);
+}
+
+static void handle_pending_updates(EV_P_ struct session *ps) {
+ if (ps->pending_updates) {
+ log_debug("Delayed handling of events, entering critical section");
+ auto e = xcb_request_check(ps->c, xcb_grab_server_checked(ps->c));
+ if (e) {
+ log_fatal_x_error(e, "failed to grab x server");
+ return quit(ps);
+ }
+
+ ps->server_grabbed = true;
+
+ // Catching up with X server
+ handle_queued_x_events(EV_A_ & ps->event_check, 0);
+
+ // Call fill_win on new windows
+ handle_new_windows(ps);
+
+ // Handle screen changes
+ // This HAS TO be called before refresh_windows, as handle_root_flags
+ // could call configure_root, which will release images and mark them
+ // stale.
+ handle_root_flags(ps);
+
+ // Process window flags (window mapping)
+ refresh_windows(ps);
+
+ {
+ auto r = xcb_get_input_focus_reply(
+ ps->c, xcb_get_input_focus(ps->c), NULL);
+ if (!ps->active_win || (r && r->focus != ps->active_win->base.id)) {
+ recheck_focus(ps);
+ }
+ free(r);
+ }
+
+ // Process window flags (stale images)
+ refresh_images(ps);
+
+ e = xcb_request_check(ps->c, xcb_ungrab_server_checked(ps->c));
+ if (e) {
+ log_fatal_x_error(e, "failed to ungrab x server");
+ return quit(ps);
+ }
+
+ ps->server_grabbed = false;
+ ps->pending_updates = false;
+ log_debug("Exited critical section");
+ }
+}
+
+static void draw_callback_impl(EV_P_ session_t *ps, int revents attr_unused) {
+ handle_pending_updates(EV_A_ ps);
+
+ if (ps->first_frame) {
+ // If we are still rendering the first frame, if some of the windows are
+ // unmapped/destroyed during the above handle_pending_updates() call, they
+ // won't have pixmap before we rendered it, causing us to crash.
+ // But we will only render them if they are in fading. So we just skip
+ // fading for all windows here.
+ //
+ // Using foreach_safe here since skipping fading can cause window to be
+ // freed if it's destroyed.
+ win_stack_foreach_managed_safe(w, &ps->window_stack) {
+ auto _ attr_unused = win_skip_fading(ps, w);
+ }
+ }
+
+ if (ps->o.benchmark) {
+ if (ps->o.benchmark_wid) {
+ auto w = find_managed_win(ps, ps->o.benchmark_wid);
+ if (!w) {
+ log_fatal("Couldn't find specified benchmark window.");
+ exit(1);
+ }
+ add_damage_from_win(ps, w);
+ } else {
+ force_repaint(ps);
+ }
+ }
+
+ /* TODO(yshui) Have a stripped down version of paint_preprocess that is used when
+ * screen is not redirected. its sole purpose should be to decide whether the
+ * screen should be redirected. */
+ bool fade_running = false;
+ bool animation_running = false;
+ bool was_redirected = ps->redirected;
+ auto bottom = paint_preprocess(ps, &fade_running, &animation_running);
+ ps->tmout_unredir_hit = false;
+
+ if (!was_redirected && ps->redirected) {
+ // paint_preprocess redirected the screen, which might change the state of
+ // some of the windows (e.g. the window image might become stale).
+ // so we rerun _draw_callback to make sure the rendering decision we make
+ // is up-to-date, and all the new flags got handled.
+ //
+ // TODO(yshui) This is not ideal, we should try to avoid setting window
+ // flags in paint_preprocess.
+ log_debug("Re-run _draw_callback");
+ return draw_callback_impl(EV_A_ ps, revents);
+ }
+
+ // Start/stop fade timer depends on whether window are fading
+ if (!fade_running && ev_is_active(&ps->fade_timer)) {
+ ev_timer_stop(EV_A_ & ps->fade_timer);
+ } else if (fade_running && !ev_is_active(&ps->fade_timer)) {
+ ev_timer_set(&ps->fade_timer, fade_timeout(ps), 0);
+ ev_timer_start(EV_A_ & ps->fade_timer);
+ }
+ // Start/stop animation timer depends on whether windows are animating
+ if (!animation_running && ev_is_active(&ps->animation_timer)) {
+ ev_timer_stop(EV_A_ & ps->animation_timer);
+ } else if (animation_running && !ev_is_active(&ps->animation_timer)) {
+ ev_timer_set(&ps->animation_timer, 0, 0);
+ ev_timer_start(EV_A_ & ps->animation_timer);
+ }
+
+ // If the screen is unredirected, free all_damage to stop painting
+ if (ps->redirected && ps->o.stoppaint_force != ON) {
+ static int paint = 0;
+
+ log_trace("Render start, frame %d", paint);
+ if (ps->o.experimental_backends) {
+ paint_all_new(ps, bottom, false);
+ } else {
+ paint_all(ps, bottom, false);
+ }
+ log_trace("Render end");
+
+ ps->first_frame = false;
+ paint++;
+ if (ps->o.benchmark && paint >= ps->o.benchmark) {
+ exit(0);
+ }
+ }
+
+ if (!fade_running) {
+ ps->fade_time = 0L;
+ }
+ if (!animation_running) {
+ ps->animation_time = 0L;
+ ps->root_desktop_switch_direction = 0;
+ }
+
+ // TODO(yshui) Investigate how big the X critical section needs to be. There are
+ // suggestions that rendering should be in the critical section as well.
+
+ ps->redraw_needed = false;
+}
+
+static void draw_callback(EV_P_ ev_idle *w, int revents) {
+ // This function is not used if we are using --swopti
+ session_t *ps = session_ptr(w, draw_idle);
+
+ draw_callback_impl(EV_A_ ps, revents);
+
+ // Don't do painting non-stop unless we are in benchmark mode
+ if (!ps->o.benchmark) {
+ ev_idle_stop(EV_A_ & ps->draw_idle);
+ }
+}
+
+static void delayed_draw_timer_callback(EV_P_ ev_timer *w, int revents) {
+ session_t *ps = session_ptr(w, delayed_draw_timer);
+ draw_callback_impl(EV_A_ ps, revents);
+
+ // We might have stopped the ev_idle in delayed_draw_callback,
+ // so we restart it if we are in benchmark mode
+ if (ps->o.benchmark)
+ ev_idle_start(EV_A_ & ps->draw_idle);
+}
+
+static void delayed_draw_callback(EV_P_ ev_idle *w, int revents) {
+ // This function is only used if we are using --swopti
+ session_t *ps = session_ptr(w, draw_idle);
+ assert(ps->redraw_needed);
+ assert(!ev_is_active(&ps->delayed_draw_timer));
+
+ double delay = swopti_handle_timeout(ps);
+ if (delay < 1e-6) {
+ if (!ps->o.benchmark) {
+ ev_idle_stop(EV_A_ & ps->draw_idle);
+ }
+ return draw_callback_impl(EV_A_ ps, revents);
+ }
+
+ // This is a little bit hacky. When we get to this point in code, we need
+ // to update the screen , but we will only be updating after a delay, So
+ // we want to stop the ev_idle, so this callback doesn't get call repeatedly
+ // during the delay, we also want queue_redraw to not restart the ev_idle.
+ // So we stop ev_idle and leave ps->redraw_needed to be true. (effectively,
+ // ps->redraw_needed means if redraw is needed or if draw is in progress).
+ //
+ // We do this anyway even if we are in benchmark mode. That means we will
+ // have to restart draw_idle after the draw actually happened when we are in
+ // benchmark mode.
+ ev_idle_stop(EV_A_ & ps->draw_idle);
+
+ ev_timer_set(&ps->delayed_draw_timer, delay, 0);
+ ev_timer_start(EV_A_ & ps->delayed_draw_timer);
+}
+
+static void x_event_callback(EV_P attr_unused, ev_io *w, int revents attr_unused) {
+ session_t *ps = (session_t *)w;
+ xcb_generic_event_t *ev = xcb_poll_for_event(ps->c);
+ if (ev) {
+ ev_handle(ps, ev);
+ free(ev);
+ }
+}
+
+/**
+ * Turn on the program reset flag.
+ *
+ * This will result in the compostior resetting itself after next paint.
+ */
+static void reset_enable(EV_P_ ev_signal *w attr_unused, int revents attr_unused) {
+ log_info("picom is resetting...");
+ ev_break(EV_A_ EVBREAK_ALL);
+}
+
+static void exit_enable(EV_P attr_unused, ev_signal *w, int revents attr_unused) {
+ session_t *ps = session_ptr(w, int_signal);
+ log_info("picom is quitting...");
+ quit(ps);
+}
+
+static void config_file_change_cb(void *_ps) {
+ auto ps = (struct session *)_ps;
+ reset_enable(ps->loop, NULL, 0);
+}
+
+/**
+ * Initialize a session.
+ *
+ * @param argc number of commandline arguments
+ * @param argv commandline arguments
+ * @param dpy the X Display
+ * @param config_file the path to the config file
+ * @param all_xerros whether we should report all X errors
+ * @param fork whether we will fork after initialization
+ */
+static session_t *session_init(int argc, char **argv, Display *dpy,
+ const char *config_file, bool all_xerrors, bool fork) {
+ static const session_t s_def = {
+ .backend_data = NULL,
+ .dpy = NULL,
+ .scr = 0,
+ .c = NULL,
+ .vis = 0,
+ .depth = 0,
+ .root = XCB_NONE,
+ .root_height = 0,
+ .root_width = 0,
+ // .root_damage = XCB_NONE,
+ .overlay = XCB_NONE,
+ .root_tile_fill = false,
+ .root_tile_paint = PAINT_INIT,
+ .tgt_picture = XCB_NONE,
+ .tgt_buffer = PAINT_INIT,
+ .reg_win = XCB_NONE,
+#ifdef CONFIG_OPENGL
+ .glx_prog_win = GLX_PROG_MAIN_INIT,
+#endif
+ .redirected = false,
+ .alpha_picts = NULL,
+ .fade_time = 0L,
+ .animation_time = 0L,
+ .ignore_head = NULL,
+ .ignore_tail = NULL,
+ .quit = false,
+
+ .expose_rects = NULL,
+ .size_expose = 0,
+ .n_expose = 0,
+
+ .windows = NULL,
+ .active_win = NULL,
+ .active_leader = XCB_NONE,
+
+ .black_picture = XCB_NONE,
+ .cshadow_picture = XCB_NONE,
+ .white_picture = XCB_NONE,
+ .gaussian_map = NULL,
+
+ .refresh_rate = 0,
+ .refresh_intv = 0UL,
+ .paint_tm_offset = 0L,
+
+#ifdef CONFIG_VSYNC_DRM
+ .drm_fd = -1,
+#endif
+
+ .xfixes_event = 0,
+ .xfixes_error = 0,
+ .damage_event = 0,
+ .damage_error = 0,
+ .render_event = 0,
+ .render_error = 0,
+ .composite_event = 0,
+ .composite_error = 0,
+ .composite_opcode = 0,
+ .shape_exists = false,
+ .shape_event = 0,
+ .shape_error = 0,
+ .randr_exists = 0,
+ .randr_event = 0,
+ .randr_error = 0,
+ .glx_exists = false,
+ .glx_event = 0,
+ .glx_error = 0,
+ .xrfilter_convolution_exists = false,
+
+ .atoms_wintypes = {0},
+ .track_atom_lst = NULL,
+
+#ifdef CONFIG_DBUS
+ .dbus_data = NULL,
+#endif
+ };
+
+ auto stderr_logger = stderr_logger_new();
+ if (stderr_logger) {
+ // stderr logger might fail to create if we are already
+ // daemonized.
+ log_add_target_tls(stderr_logger);
+ }
+
+ // Allocate a session and copy default values into it
+ session_t *ps = cmalloc(session_t);
+ *ps = s_def;
+ list_init_head(&ps->window_stack);
+ ps->loop = EV_DEFAULT;
+ pixman_region32_init(&ps->screen_reg);
+
+ ps->ignore_tail = &ps->ignore_head;
+
+ ps->o.show_all_xerrors = all_xerrors;
+
+ // Use the same Display across reset, primarily for resource leak checking
+ ps->dpy = dpy;
+ ps->c = XGetXCBConnection(ps->dpy);
+
+ const xcb_query_extension_reply_t *ext_info;
+
+ ps->previous_xerror_handler = XSetErrorHandler(xerror);
+
+ ps->scr = DefaultScreen(ps->dpy);
+
+ auto screen = x_screen_of_display(ps->c, ps->scr);
+ ps->vis = screen->root_visual;
+ ps->depth = screen->root_depth;
+ ps->root = screen->root;
+ ps->root_width = screen->width_in_pixels;
+ ps->root_height = screen->height_in_pixels;
+
+ // Start listening to events on root earlier to catch all possible
+ // root geometry changes
+ auto e = xcb_request_check(
+ ps->c, xcb_change_window_attributes_checked(
+ ps->c, ps->root, XCB_CW_EVENT_MASK,
+ (const uint32_t[]){XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
+ XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY |
+ XCB_EVENT_MASK_PROPERTY_CHANGE}));
+ if (e) {
+ log_error_x_error(e, "Failed to setup root window event mask");
+ }
+
+ xcb_prefetch_extension_data(ps->c, &xcb_render_id);
+ xcb_prefetch_extension_data(ps->c, &xcb_composite_id);
+ xcb_prefetch_extension_data(ps->c, &xcb_damage_id);
+ xcb_prefetch_extension_data(ps->c, &xcb_shape_id);
+ xcb_prefetch_extension_data(ps->c, &xcb_xfixes_id);
+ xcb_prefetch_extension_data(ps->c, &xcb_randr_id);
+ xcb_prefetch_extension_data(ps->c, &xcb_xinerama_id);
+ xcb_prefetch_extension_data(ps->c, &xcb_present_id);
+ xcb_prefetch_extension_data(ps->c, &xcb_sync_id);
+ xcb_prefetch_extension_data(ps->c, &xcb_glx_id);
+
+ ext_info = xcb_get_extension_data(ps->c, &xcb_render_id);
+ if (!ext_info || !ext_info->present) {
+ log_fatal("No render extension");
+ exit(1);
+ }
+ ps->render_event = ext_info->first_event;
+ ps->render_error = ext_info->first_error;
+
+ ext_info = xcb_get_extension_data(ps->c, &xcb_composite_id);
+ if (!ext_info || !ext_info->present) {
+ log_fatal("No composite extension");
+ exit(1);
+ }
+ ps->composite_opcode = ext_info->major_opcode;
+ ps->composite_event = ext_info->first_event;
+ ps->composite_error = ext_info->first_error;
+
+ {
+ xcb_composite_query_version_reply_t *reply = xcb_composite_query_version_reply(
+ ps->c,
+ xcb_composite_query_version(ps->c, XCB_COMPOSITE_MAJOR_VERSION,
+ XCB_COMPOSITE_MINOR_VERSION),
+ NULL);
+
+ if (!reply || (reply->major_version == 0 && reply->minor_version < 2)) {
+ log_fatal("Your X server doesn't have Composite >= 0.2 support, "
+ "we cannot proceed.");
+ exit(1);
+ }
+ free(reply);
+ }
+
+ ext_info = xcb_get_extension_data(ps->c, &xcb_damage_id);
+ if (!ext_info || !ext_info->present) {
+ log_fatal("No damage extension");
+ exit(1);
+ }
+ ps->damage_event = ext_info->first_event;
+ ps->damage_error = ext_info->first_error;
+ xcb_discard_reply(ps->c, xcb_damage_query_version(ps->c, XCB_DAMAGE_MAJOR_VERSION,
+ XCB_DAMAGE_MINOR_VERSION)
+ .sequence);
+
+ ext_info = xcb_get_extension_data(ps->c, &xcb_xfixes_id);
+ if (!ext_info || !ext_info->present) {
+ log_fatal("No XFixes extension");
+ exit(1);
+ }
+ ps->xfixes_event = ext_info->first_event;
+ ps->xfixes_error = ext_info->first_error;
+ xcb_discard_reply(ps->c, xcb_xfixes_query_version(ps->c, XCB_XFIXES_MAJOR_VERSION,
+ XCB_XFIXES_MINOR_VERSION)
+ .sequence);
+
+ ps->damaged_region = x_new_id(ps->c);
+ if (!XCB_AWAIT_VOID(xcb_xfixes_create_region, ps->c, ps->damaged_region, 0, NULL)) {
+ log_fatal("Failed to create a XFixes region");
+ goto err;
+ }
+
+ ext_info = xcb_get_extension_data(ps->c, &xcb_glx_id);
+ if (ext_info && ext_info->present) {
+ ps->glx_exists = true;
+ ps->glx_error = ext_info->first_error;
+ ps->glx_event = ext_info->first_event;
+ }
+
+ // Parse configuration file
+ win_option_mask_t winopt_mask[NUM_WINTYPES] = {{0}};
+ bool shadow_enabled = false, fading_enable = false, hasneg = false;
+ char *config_file_to_free = NULL;
+ config_file = config_file_to_free = parse_config(
+ &ps->o, config_file, &shadow_enabled, &fading_enable, &hasneg, winopt_mask);
+
+ if (IS_ERR(config_file_to_free)) {
+ return NULL;
+ }
+
+ // Parse all of the rest command line options
+ if (!get_cfg(&ps->o, argc, argv, shadow_enabled, fading_enable, hasneg, winopt_mask)) {
+ log_fatal("Failed to get configuration, usually mean you have specified "
+ "invalid options.");
+ return NULL;
+ }
+
+ if (ps->o.logpath) {
+ auto l = file_logger_new(ps->o.logpath);
+ if (l) {
+ log_info("Switching to log file: %s", ps->o.logpath);
+ if (stderr_logger) {
+ log_remove_target_tls(stderr_logger);
+ stderr_logger = NULL;
+ }
+ log_add_target_tls(l);
+ stderr_logger = NULL;
+ } else {
+ log_error("Failed to setup log file %s, I will keep using stderr",
+ ps->o.logpath);
+ }
+ }
+
+ if (strstr(argv[0], "compton")) {
+ log_warn("This compositor has been renamed to \"picom\", the \"compton\" "
+ "binary will not be installed in the future.");
+ }
+
+ ps->atoms = init_atoms(ps->c);
+ ps->atoms_wintypes[WINTYPE_UNKNOWN] = 0;
+#define SET_WM_TYPE_ATOM(x) \
+ ps->atoms_wintypes[WINTYPE_##x] = ps->atoms->a_NET_WM_WINDOW_TYPE_##x
+ SET_WM_TYPE_ATOM(DESKTOP);
+ SET_WM_TYPE_ATOM(DOCK);
+ SET_WM_TYPE_ATOM(TOOLBAR);
+ SET_WM_TYPE_ATOM(MENU);
+ SET_WM_TYPE_ATOM(UTILITY);
+ SET_WM_TYPE_ATOM(SPLASH);
+ SET_WM_TYPE_ATOM(DIALOG);
+ SET_WM_TYPE_ATOM(NORMAL);
+ SET_WM_TYPE_ATOM(DROPDOWN_MENU);
+ SET_WM_TYPE_ATOM(POPUP_MENU);
+ SET_WM_TYPE_ATOM(TOOLTIP);
+ SET_WM_TYPE_ATOM(NOTIFICATION);
+ SET_WM_TYPE_ATOM(COMBO);
+ SET_WM_TYPE_ATOM(DND);
+#undef SET_WM_TYPE_ATOM
+
+ // Get needed atoms for c2 condition lists
+ if (!(c2_list_postprocess(ps, ps->o.unredir_if_possible_blacklist) &&
+ c2_list_postprocess(ps, ps->o.paint_blacklist) &&
+ c2_list_postprocess(ps, ps->o.shadow_blacklist) &&
+ c2_list_postprocess(ps, ps->o.shadow_clip_list) &&
+ c2_list_postprocess(ps, ps->o.fade_blacklist) &&
+ c2_list_postprocess(ps, ps->o.blur_background_blacklist) &&
+ c2_list_postprocess(ps, ps->o.invert_color_list) &&
+ c2_list_postprocess(ps, ps->o.opacity_rules) &&
+ c2_list_postprocess(ps, ps->o.rounded_corners_blacklist) &&
+ c2_list_postprocess(ps, ps->o.focus_blacklist))) {
+ log_error("Post-processing of conditionals failed, some of your rules "
+ "might not work");
+ }
+
+ ps->gaussian_map = gaussian_kernel_autodetect_deviation(ps->o.shadow_radius);
+ sum_kernel_preprocess(ps->gaussian_map);
+
+ rebuild_shadow_exclude_reg(ps);
+
+ // Query X Shape
+ ext_info = xcb_get_extension_data(ps->c, &xcb_shape_id);
+ if (ext_info && ext_info->present) {
+ ps->shape_event = ext_info->first_event;
+ ps->shape_error = ext_info->first_error;
+ ps->shape_exists = true;
+ }
+
+ ext_info = xcb_get_extension_data(ps->c, &xcb_randr_id);
+ if (ext_info && ext_info->present) {
+ ps->randr_exists = true;
+ ps->randr_event = ext_info->first_event;
+ ps->randr_error = ext_info->first_error;
+ }
+
+ ext_info = xcb_get_extension_data(ps->c, &xcb_present_id);
+ if (ext_info && ext_info->present) {
+ auto r = xcb_present_query_version_reply(
+ ps->c,
+ xcb_present_query_version(ps->c, XCB_PRESENT_MAJOR_VERSION,
+ XCB_PRESENT_MINOR_VERSION),
+ NULL);
+ if (r) {
+ ps->present_exists = true;
+ free(r);
+ }
+ }
+
+ // Query X Sync
+ ext_info = xcb_get_extension_data(ps->c, &xcb_sync_id);
+ if (ext_info && ext_info->present) {
+ ps->xsync_error = ext_info->first_error;
+ ps->xsync_event = ext_info->first_event;
+ // Need X Sync 3.1 for fences
+ auto r = xcb_sync_initialize_reply(
+ ps->c,
+ xcb_sync_initialize(ps->c, XCB_SYNC_MAJOR_VERSION, XCB_SYNC_MINOR_VERSION),
+ NULL);
+ if (r && (r->major_version > 3 ||
+ (r->major_version == 3 && r->minor_version >= 1))) {
+ ps->xsync_exists = true;
+ free(r);
+ }
+ }
+
+ ps->sync_fence = XCB_NONE;
+ if (ps->xsync_exists) {
+ ps->sync_fence = x_new_id(ps->c);
+ e = xcb_request_check(
+ ps->c, xcb_sync_create_fence(ps->c, ps->root, ps->sync_fence, 0));
+ if (e) {
+ if (ps->o.xrender_sync_fence) {
+ log_error_x_error(e, "Failed to create a XSync fence. "
+ "xrender-sync-fence will be "
+ "disabled");
+ ps->o.xrender_sync_fence = false;
+ }
+ ps->sync_fence = XCB_NONE;
+ free(e);
+ }
+ } else if (ps->o.xrender_sync_fence) {
+ log_error("XSync extension not found. No XSync fence sync is "
+ "possible. (xrender-sync-fence can't be enabled)");
+ ps->o.xrender_sync_fence = false;
+ }
+
+ // Query X RandR
+ if ((ps->o.sw_opti && !ps->o.refresh_rate) || ps->o.xinerama_shadow_crop) {
+ if (!ps->randr_exists) {
+ log_fatal("No XRandR extension. sw-opti, refresh-rate or "
+ "xinerama-shadow-crop "
+ "cannot be enabled.");
+ goto err;
+ }
+ }
+
+ // Query X Xinerama extension
+ if (ps->o.xinerama_shadow_crop) {
+ ext_info = xcb_get_extension_data(ps->c, &xcb_xinerama_id);
+ ps->xinerama_exists = ext_info && ext_info->present;
+ }
+
+ rebuild_screen_reg(ps);
+
+ bool compositor_running = false;
+ if (session_redirection_mode(ps) == XCB_COMPOSITE_REDIRECT_MANUAL) {
+ // We are running in the manual redirection mode, meaning we are running
+ // as a proper compositor. So we need to register us as a compositor, etc.
+
+ // We are also here when --diagnostics is set. We want to be here because
+ // that gives us more diagnostic information.
+
+ // Create registration window
+ int ret = register_cm(ps);
+ if (ret == -1) {
+ exit(1);
+ }
+
+ compositor_running = ret == 1;
+ if (compositor_running) {
+ // Don't take the overlay when there is another compositor
+ // running, so we don't disrupt it.
+
+ // If we are printing diagnostic, we will continue a bit further
+ // to get more diagnostic information, otherwise we will exit.
+ if (!ps->o.print_diagnostics) {
+ log_fatal("Another composite manager is already running");
+ exit(1);
+ }
+ } else {
+ if (!init_overlay(ps)) {
+ goto err;
+ }
+ }
+ } else {
+ // We are here if we don't really function as a compositor, so we are not
+ // taking over the screen, and we don't need to register as a compositor
+
+ // If we are in debug mode, we need to create a window for rendering if
+ // the backend supports presenting.
+
+ // The old backends doesn't have a automatic redirection mode
+ log_info("The compositor is started in automatic redirection mode.");
+ assert(ps->o.experimental_backends);
+
+ if (backend_list[ps->o.backend]->present) {
+ // If the backend has `present`, we couldn't be in automatic
+ // redirection mode unless we are in debug mode.
+ assert(ps->o.debug_mode);
+ if (!init_debug_window(ps)) {
+ goto err;
+ }
+ }
+ }
+
+ ps->drivers = detect_driver(ps->c, ps->backend_data, ps->root);
+ apply_driver_workarounds(ps, ps->drivers);
+
+ // Initialize filters, must be preceded by OpenGL context creation
+ if (!ps->o.experimental_backends && !init_render(ps)) {
+ log_fatal("Failed to initialize the backend");
+ exit(1);
+ }
+
+ if (ps->o.print_diagnostics) {
+ print_diagnostics(ps, config_file, compositor_running);
+ free(config_file_to_free);
+ exit(0);
+ }
+
+ ps->file_watch_handle = file_watch_init(ps->loop);
+ if (ps->file_watch_handle && config_file) {
+ file_watch_add(ps->file_watch_handle, config_file, config_file_change_cb, ps);
+ }
+
+ free(config_file_to_free);
+
+ if (bkend_use_glx(ps) && !ps->o.experimental_backends) {
+ auto gl_logger = gl_string_marker_logger_new();
+ if (gl_logger) {
+ log_info("Enabling gl string marker");
+ log_add_target_tls(gl_logger);
+ }
+ }
+
+ if (ps->o.experimental_backends) {
+ if (ps->o.monitor_repaint && !backend_list[ps->o.backend]->fill) {
+ log_warn("--monitor-repaint is not supported by the backend, "
+ "disabling");
+ ps->o.monitor_repaint = false;
+ }
+ }
+
+ // Initialize software optimization
+ if (ps->o.sw_opti)
+ ps->o.sw_opti = swopti_init(ps);
+
+ // Monitor screen changes if vsync_sw is enabled and we are using
+ // an auto-detected refresh rate, or when Xinerama features are enabled
+ if (ps->randr_exists &&
+ ((ps->o.sw_opti && !ps->o.refresh_rate) || ps->o.xinerama_shadow_crop))
+ xcb_randr_select_input(ps->c, ps->root, XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE);
+
+ cxinerama_upd_scrs(ps);
+
+ {
+ xcb_render_create_picture_value_list_t pa = {
+ .subwindowmode = IncludeInferiors,
+ };
+
+ ps->root_picture = x_create_picture_with_visual_and_pixmap(
+ ps->c, ps->vis, ps->root, XCB_RENDER_CP_SUBWINDOW_MODE, &pa);
+ if (ps->overlay != XCB_NONE) {
+ ps->tgt_picture = x_create_picture_with_visual_and_pixmap(
+ ps->c, ps->vis, ps->overlay, XCB_RENDER_CP_SUBWINDOW_MODE, &pa);
+ } else
+ ps->tgt_picture = ps->root_picture;
+ }
+
+ ev_io_init(&ps->xiow, x_event_callback, ConnectionNumber(ps->dpy), EV_READ);
+ ev_io_start(ps->loop, &ps->xiow);
+ ev_init(&ps->unredir_timer, tmout_unredir_callback);
+ if (ps->o.sw_opti)
+ ev_idle_init(&ps->draw_idle, delayed_draw_callback);
+ else
+ ev_idle_init(&ps->draw_idle, draw_callback);
+
+ ev_init(&ps->fade_timer, fade_timer_callback);
+ ev_init(&ps->animation_timer, animation_timer_callback);
+ ev_init(&ps->delayed_draw_timer, delayed_draw_timer_callback);
+
+ // Set up SIGUSR1 signal handler to reset program
+ ev_signal_init(&ps->usr1_signal, reset_enable, SIGUSR1);
+ ev_signal_init(&ps->int_signal, exit_enable, SIGINT);
+ ev_signal_start(ps->loop, &ps->usr1_signal);
+ ev_signal_start(ps->loop, &ps->int_signal);
+
+ // xcb can read multiple events from the socket when a request with reply is
+ // made.
+ //
+ // Use an ev_prepare to make sure we cannot accidentally forget to handle them
+ // before we go to sleep.
+ //
+ // If we don't drain the queue before goes to sleep (i.e. blocking on socket
+ // input), we will be sleeping with events available in queue. Which might
+ // cause us to block indefinitely because arrival of new events could be
+ // dependent on processing of existing events (e.g. if we don't process damage
+ // event and do damage subtract, new damage event won't be generated).
+ //
+ // So we make use of a ev_prepare handle, which is called right before libev
+ // goes into sleep, to handle all the queued X events.
+ ev_prepare_init(&ps->event_check, handle_queued_x_events);
+ // Make sure nothing can cause xcb to read from the X socket after events are
+ // handled and before we going to sleep.
+ ev_set_priority(&ps->event_check, EV_MINPRI);
+ ev_prepare_start(ps->loop, &ps->event_check);
+
+ // Initialize DBus. We need to do this early, because add_win might call dbus
+ // functions
+ if (ps->o.dbus) {
+#ifdef CONFIG_DBUS
+ cdbus_init(ps, DisplayString(ps->dpy));
+ if (!ps->dbus_data) {
+ ps->o.dbus = false;
+ }
+#else
+ log_fatal("DBus support not compiled in!");
+ exit(1);
+#endif
+ }
+
+ e = xcb_request_check(ps->c, xcb_grab_server_checked(ps->c));
+ if (e) {
+ log_fatal_x_error(e, "Failed to grab X server");
+ free(e);
+ goto err;
+ }
+
+ ps->server_grabbed = true;
+
+ // We are going to pull latest information from X server now, events sent by X
+ // earlier is irrelavant at this point.
+ // A better solution is probably grabbing the server from the very start. But I
+ // think there still could be race condition that mandates discarding the events.
+ x_discard_events(ps->c);
+
+ xcb_query_tree_reply_t *query_tree_reply =
+ xcb_query_tree_reply(ps->c, xcb_query_tree(ps->c, ps->root), NULL);
+
+ e = xcb_request_check(ps->c, xcb_ungrab_server(ps->c));
+ if (e) {
+ log_fatal_x_error(e, "Failed to ungrab server");
+ free(e);
+ goto err;
+ }
+
+ ps->server_grabbed = false;
+
+ if (query_tree_reply) {
+ xcb_window_t *children;
+ int nchildren;
+
+ children = xcb_query_tree_children(query_tree_reply);
+ nchildren = xcb_query_tree_children_length(query_tree_reply);
+
+ for (int i = 0; i < nchildren; i++) {
+ add_win_above(ps, children[i], i ? children[i - 1] : XCB_NONE);
+ }
+ free(query_tree_reply);
+ }
+
+ log_debug("Initial stack:");
+ list_foreach(struct win, w, &ps->window_stack, stack_neighbour) {
+ log_debug("%#010x", w->id);
+ }
+
+ ps->pending_updates = true;
+
+ write_pid(ps);
+
+ if (fork && stderr_logger) {
+ // Remove the stderr logger if we will fork
+ log_remove_target_tls(stderr_logger);
+ }
+ return ps;
+err:
+ free(ps);
+ return NULL;
+}
+
+/**
+ * Destroy a session.
+ *
+ * Does not close the X connection or free the <code>session_t</code>
+ * structure, though.
+ *
+ * @param ps session to destroy
+ */
+static void session_destroy(session_t *ps) {
+ if (ps->redirected) {
+ unredirect(ps);
+ }
+
+#ifdef CONFIG_OPENGL
+ free(ps->argb_fbconfig);
+ ps->argb_fbconfig = NULL;
+#endif
+
+ file_watch_destroy(ps->loop, ps->file_watch_handle);
+ ps->file_watch_handle = NULL;
+
+ // Stop listening to events on root window
+ xcb_change_window_attributes(ps->c, ps->root, XCB_CW_EVENT_MASK,
+ (const uint32_t[]){0});
+
+#ifdef CONFIG_DBUS
+ // Kill DBus connection
+ if (ps->o.dbus) {
+ assert(ps->dbus_data);
+ cdbus_destroy(ps);
+ }
+#endif
+
+ // Free window linked list
+
+ list_foreach_safe(struct win, w, &ps->window_stack, stack_neighbour) {
+ if (!w->destroyed) {
+ win_ev_stop(ps, w);
+ HASH_DEL(ps->windows, w);
+ }
+
+ if (w->managed) {
+ auto mw = (struct managed_win *)w;
+ free_win_res(ps, mw);
+ }
+ free(w);
+ }
+ list_init_head(&ps->window_stack);
+
+ // Free blacklists
+ free_wincondlst(&ps->o.shadow_blacklist);
+ free_wincondlst(&ps->o.shadow_clip_list);
+ free_wincondlst(&ps->o.fade_blacklist);
+ free_wincondlst(&ps->o.focus_blacklist);
+ free_wincondlst(&ps->o.invert_color_list);
+ free_wincondlst(&ps->o.blur_background_blacklist);
+ free_wincondlst(&ps->o.opacity_rules);
+ free_wincondlst(&ps->o.paint_blacklist);
+ free_wincondlst(&ps->o.unredir_if_possible_blacklist);
+ free_wincondlst(&ps->o.rounded_corners_blacklist);
+
+ // Free tracked atom list
+ {
+ latom_t *next = NULL;
+ for (latom_t *this = ps->track_atom_lst; this; this = next) {
+ next = this->next;
+ free(this);
+ }
+
+ ps->track_atom_lst = NULL;
+ }
+
+ // Free ignore linked list
+ {
+ ignore_t *next = NULL;
+ for (ignore_t *ign = ps->ignore_head; ign; ign = next) {
+ next = ign->next;
+
+ free(ign);
+ }
+
+ // Reset head and tail
+ ps->ignore_head = NULL;
+ ps->ignore_tail = &ps->ignore_head;
+ }
+
+ // Free tgt_{buffer,picture} and root_picture
+ if (ps->tgt_buffer.pict == ps->tgt_picture)
+ ps->tgt_buffer.pict = XCB_NONE;
+
+ if (ps->tgt_picture == ps->root_picture)
+ ps->tgt_picture = XCB_NONE;
+ else
+ free_picture(ps->c, &ps->tgt_picture);
+
+ free_picture(ps->c, &ps->root_picture);
+ free_paint(ps, &ps->tgt_buffer);
+
+ pixman_region32_fini(&ps->screen_reg);
+ free(ps->expose_rects);
+
+ free(ps->o.write_pid_path);
+ free(ps->o.logpath);
+ for (int i = 0; i < ps->o.blur_kernel_count; ++i) {
+ free(ps->o.blur_kerns[i]);
+ }
+ free(ps->o.blur_kerns);
+ free(ps->o.glx_fshader_win_str);
+ free_xinerama_info(ps);
+
+#ifdef CONFIG_VSYNC_DRM
+ // Close file opened for DRM VSync
+ if (ps->drm_fd >= 0) {
+ close(ps->drm_fd);
+ ps->drm_fd = -1;
+ }
+#endif
+
+ // Release overlay window
+ if (ps->overlay) {
+ xcb_composite_release_overlay_window(ps->c, ps->overlay);
+ ps->overlay = XCB_NONE;
+ }
+
+ if (ps->sync_fence != XCB_NONE) {
+ xcb_sync_destroy_fence(ps->c, ps->sync_fence);
+ ps->sync_fence = XCB_NONE;
+ }
+
+ // Free reg_win
+ if (ps->reg_win != XCB_NONE) {
+ xcb_destroy_window(ps->c, ps->reg_win);
+ ps->reg_win = XCB_NONE;
+ }
+
+ if (ps->debug_window != XCB_NONE) {
+ xcb_destroy_window(ps->c, ps->debug_window);
+ ps->debug_window = XCB_NONE;
+ }
+
+ if (ps->damaged_region != XCB_NONE) {
+ xcb_xfixes_destroy_region(ps->c, ps->damaged_region);
+ ps->damaged_region = XCB_NONE;
+ }
+
+ if (ps->o.experimental_backends) {
+ // backend is deinitialized in unredirect()
+ assert(ps->backend_data == NULL);
+ } else {
+ deinit_render(ps);
+ }
+
+#if CONFIG_OPENGL
+ if (glx_has_context(ps)) {
+ // GLX context created, but not for rendering
+ glx_destroy(ps);
+ }
+#endif
+
+ // Flush all events
+ x_sync(ps->c);
+ ev_io_stop(ps->loop, &ps->xiow);
+ free_conv(ps->gaussian_map);
+ destroy_atoms(ps->atoms);
+
+#ifdef DEBUG_XRC
+ // Report about resource leakage
+ xrc_report_xid();
+#endif
+
+ XSetErrorHandler(ps->previous_xerror_handler);
+
+ // Stop libev event handlers
+ ev_timer_stop(ps->loop, &ps->unredir_timer);
+ ev_timer_stop(ps->loop, &ps->fade_timer);
+ ev_timer_stop(ps->loop, &ps->animation_timer);
+ ev_idle_stop(ps->loop, &ps->draw_idle);
+ ev_prepare_stop(ps->loop, &ps->event_check);
+ ev_signal_stop(ps->loop, &ps->usr1_signal);
+ ev_signal_stop(ps->loop, &ps->int_signal);
+}
+
+/**
+ * Do the actual work.
+ *
+ * @param ps current session
+ */
+static void session_run(session_t *ps) {
+ if (ps->o.sw_opti)
+ ps->paint_tm_offset = get_time_timeval().tv_usec;
+
+ // In benchmark mode, we want draw_idle handler to always be active
+ if (ps->o.benchmark) {
+ ev_idle_start(ps->loop, &ps->draw_idle);
+ } else {
+ // Let's draw our first frame!
+ queue_redraw(ps);
+ }
+ ev_run(ps->loop, 0);
+}
+
+/**
+ * The function that everybody knows.
+ */
+int main(int argc, char **argv) {
+ // Set locale so window names with special characters are interpreted
+ // correctly
+ setlocale(LC_ALL, "");
+
+ // Initialize logging system for early logging
+ log_init_tls();
+
+ {
+ auto stderr_logger = stderr_logger_new();
+ if (stderr_logger) {
+ log_add_target_tls(stderr_logger);
+ }
+ }
+
+ int exit_code;
+ char *config_file = NULL;
+ bool all_xerrors = false, need_fork = false;
+ if (get_early_config(argc, argv, &config_file, &all_xerrors, &need_fork, &exit_code)) {
+ return exit_code;
+ }
+
+ int pfds[2];
+ if (need_fork) {
+ if (pipe2(pfds, O_CLOEXEC)) {
+ perror("pipe2");
+ return 1;
+ }
+ auto pid = fork();
+ if (pid < 0) {
+ perror("fork");
+ return 1;
+ }
+ if (pid > 0) {
+ // We are the parent
+ close(pfds[1]);
+ // We wait for the child to tell us it has finished initialization
+ // by sending us something via the pipe.
+ int tmp;
+ if (read(pfds[0], &tmp, sizeof tmp) <= 0) {
+ // Failed to read, the child has most likely died
+ // We can probably waitpid() here.
+ return 1;
+ } else {
+ // We are done
+ return 0;
+ }
+ }
+ // We are the child
+ close(pfds[0]);
+ }
+
+ // Main loop
+ bool quit = false;
+ int ret_code = 0;
+ char *pid_file = NULL;
+
+ do {
+ Display *dpy = XOpenDisplay(NULL);
+ if (!dpy) {
+ log_fatal("Can't open display.");
+ ret_code = 1;
+ break;
+ }
+ XSetEventQueueOwner(dpy, XCBOwnsEventQueue);
+
+ // Reinit logging system so we don't get leftovers from previous sessions
+ // or early logging.
+ log_deinit_tls();
+ log_init_tls();
+
+ ps_g = session_init(argc, argv, dpy, config_file, all_xerrors, need_fork);
+ if (!ps_g) {
+ log_fatal("Failed to create new session.");
+ ret_code = 1;
+ break;
+ }
+ if (need_fork) {
+ // Finishing up daemonization
+ // Close files
+ if (fclose(stdout) || fclose(stderr) || fclose(stdin)) {
+ log_fatal("Failed to close standard input/output");
+ ret_code = 1;
+ break;
+ }
+ // Make us the session and process group leader so we don't get
+ // killed when our parent die.
+ setsid();
+ // Notify the parent that we are done. This might cause the parent
+ // to quit, so only do this after setsid()
+ int tmp = 1;
+ write(pfds[1], &tmp, sizeof tmp);
+ close(pfds[1]);
+ // We only do this once
+ need_fork = false;
+ }
+ session_run(ps_g);
+ quit = ps_g->quit;
+ if (quit && ps_g->o.write_pid_path) {
+ pid_file = strdup(ps_g->o.write_pid_path);
+ }
+ session_destroy(ps_g);
+ free(ps_g);
+ ps_g = NULL;
+ if (dpy) {
+ XCloseDisplay(dpy);
+ }
+ } while (!quit);
+
+ free(config_file);
+ if (pid_file) {
+ log_trace("remove pid file %s", pid_file);
+ unlink(pid_file);
+ free(pid_file);
+ }
+
+ log_deinit_tls();
+
+ return ret_code;
+}
diff --git a/src/picom.h b/src/picom.h
new file mode 100644
index 0000000..25f7580
--- /dev/null
+++ b/src/picom.h
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: MIT
+// Copyright (c)
+
+// Throw everything in here.
+// !!! DON'T !!!
+
+// === Includes ===
+
+#include <locale.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <xcb/xproto.h>
+
+#include <X11/Xutil.h>
+#include "backend/backend.h"
+#include "c2.h"
+#include "common.h"
+#include "compiler.h"
+#include "config.h"
+#include "log.h" // XXX clean up
+#include "region.h"
+#include "render.h"
+#include "types.h"
+#include "utils.h"
+#include "win.h"
+#include "x.h"
+
+enum root_flags {
+ ROOT_FLAGS_SCREEN_CHANGE = 1, // Received RandR screen change notify, we
+ // use this to track refresh rate changes
+ ROOT_FLAGS_CONFIGURED = 2 // Received configure notify on the root window
+};
+
+// == Functions ==
+// TODO(yshui) move static inline functions that are only used in picom.c, into picom.c
+
+void add_damage(session_t *ps, const region_t *damage);
+
+uint32_t determine_evmask(session_t *ps, xcb_window_t wid, win_evmode_t mode);
+
+void circulate_win(session_t *ps, xcb_circulate_notify_event_t *ce);
+
+void update_refresh_rate(session_t *ps);
+
+void root_damaged(session_t *ps);
+
+void cxinerama_upd_scrs(session_t *ps);
+
+void queue_redraw(session_t *ps);
+
+void discard_ignore(session_t *ps, unsigned long sequence);
+
+void set_root_flags(session_t *ps, uint64_t flags);
+
+void quit(session_t *ps);
+
+xcb_window_t session_get_target_window(session_t *);
+
+uint8_t session_redirection_mode(session_t *ps);
+
+/**
+ * Set a <code>switch_t</code> array of all unset wintypes to true.
+ */
+static inline void wintype_arr_enable_unset(switch_t arr[]) {
+ wintype_t i;
+
+ for (i = 0; i < NUM_WINTYPES; ++i)
+ if (UNSET == arr[i])
+ arr[i] = ON;
+}
+
+/**
+ * Check if a window ID exists in an array of window IDs.
+ *
+ * @param arr the array of window IDs
+ * @param count amount of elements in the array
+ * @param wid window ID to search for
+ */
+static inline bool array_wid_exists(const xcb_window_t *arr, int count, xcb_window_t wid) {
+ while (count--) {
+ if (arr[count] == wid) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Destroy a condition list.
+ */
+static inline void free_wincondlst(c2_lptr_t **pcondlst) {
+ while ((*pcondlst = c2_free_lptr(*pcondlst)))
+ continue;
+}
+
+#ifndef CONFIG_OPENGL
+static inline void free_paint_glx(session_t *ps attr_unused, paint_t *p attr_unused) {
+}
+static inline void
+free_win_res_glx(session_t *ps attr_unused, struct managed_win *w attr_unused) {
+}
+#endif
+
+/**
+ * Dump an drawable's info.
+ */
+static inline void dump_drawable(session_t *ps, xcb_drawable_t drawable) {
+ auto r = xcb_get_geometry_reply(ps->c, xcb_get_geometry(ps->c, drawable), NULL);
+ if (!r) {
+ log_trace("Drawable %#010x: Failed", drawable);
+ return;
+ }
+ log_trace("Drawable %#010x: x = %u, y = %u, wid = %u, hei = %d, b = %u, d = %u",
+ drawable, r->x, r->y, r->width, r->height, r->border_width, r->depth);
+ free(r);
+}
diff --git a/src/picom.modulemap b/src/picom.modulemap
new file mode 100644
index 0000000..787c4ff
--- /dev/null
+++ b/src/picom.modulemap
@@ -0,0 +1,214 @@
+// modulemap
+
+module compiler {
+ header "compiler.h"
+}
+module string_utils {
+ header "string_utils.h"
+}
+module dbus {
+ header "dbus.h"
+}
+module kernel {
+ header "kernel.h"
+}
+module utils {
+ // Has macros expands to calloc/malloc
+ header "utils.h"
+ export libc.stdlib
+}
+module region {
+ header "region.h"
+}
+module picom {
+ header "picom.h"
+}
+module types {
+ header "types.h"
+}
+module c2 {
+ header "c2.h"
+}
+module render {
+ header "render.h"
+}
+module options {
+ header "options.h"
+}
+module opengl {
+ header "opengl.h"
+}
+module diagnostic {
+ header "diagnostic.h"
+}
+module win_defs {
+ header "win_defs.h"
+}
+module win {
+ header "win.h"
+ export win_defs
+}
+module log {
+ header "log.h"
+ export compiler
+}
+module x {
+ header "x.h"
+}
+module vsync {
+ header "vsync.h"
+}
+module common {
+ header "common.h"
+}
+module config {
+ header "config.h"
+}
+module xrescheck {
+ header "xrescheck.h"
+}
+module cache {
+ header "cache.h"
+}
+module backend {
+ module gl {
+ module gl_common {
+ header "backend/gl/gl_common.h"
+ }
+ module glx {
+ header "backend/gl/glx.h"
+ export GL.glx
+ }
+ }
+ module backend {
+ header "backend/backend.h"
+ }
+ module backend_common {
+ header "backend/backend_common.h"
+ }
+}
+module xcb [system] {
+ module xcb {
+ header "/usr/include/xcb/xcb.h"
+ export *
+ }
+ module randr {
+ header "/usr/include/xcb/randr.h"
+ export *
+ }
+ module render {
+ header "/usr/include/xcb/render.h"
+ export *
+ }
+ module sync {
+ header "/usr/include/xcb/sync.h"
+ export *
+ }
+ module composite {
+ header "/usr/include/xcb/composite.h"
+ export *
+ }
+ module xfixes {
+ header "/usr/include/xcb/xfixes.h"
+ export *
+ }
+ module damage {
+ header "/usr/include/xcb/damage.h"
+ export *
+ }
+ module xproto {
+ header "/usr/include/xcb/xproto.h"
+ export *
+ }
+ module present {
+ header "/usr/include/xcb/present.h"
+ }
+ module util {
+ module render {
+ header "/usr/include/xcb/xcb_renderutil.h"
+ export *
+ }
+ }
+}
+module X11 [system] {
+ module Xlib {
+ header "/usr/include/X11/Xlib.h"
+ export *
+ }
+ module Xutil {
+ header "/usr/include/X11/Xutil.h"
+ export *
+ }
+}
+module GL [system] {
+ module glx {
+ header "/usr/include/GL/glx.h"
+ export *
+ }
+ module gl {
+ header "/usr/include/GL/gl.h"
+ export *
+ }
+}
+module libc [system] {
+ export *
+ module assert {
+ export *
+ textual header "/usr/include/assert.h"
+ }
+ module string {
+ export *
+ header "/usr/include/string.h"
+ }
+ module ctype {
+ export *
+ header "/usr/include/ctype.h"
+ }
+ module errno {
+ export *
+ header "/usr/include/errno.h"
+ }
+ module fenv {
+ export *
+ header "/usr/include/fenv.h"
+ }
+ module inttypes {
+ export *
+ header "/usr/include/inttypes.h"
+ }
+ module math {
+ export *
+ header "/usr/include/math.h"
+ }
+ module setjmp {
+ export *
+ header "/usr/include/setjmp.h"
+ }
+ module stdio {
+ export *
+ header "/usr/include/stdio.h"
+ }
+
+ module stdlib [system] {
+ export *
+ header "/usr/include/stdlib.h"
+ }
+}
+
+// glib specific header. In it's own module because it
+// doesn't exist on some systems with unpatched glib 2.26+
+module "xlocale.h" [system] {
+ export *
+ header "/usr/include/xlocale.h"
+}
+
+// System header that we have difficult with merging.
+module "sys_types.h" [system] {
+ export *
+ header "/usr/include/sys/types.h"
+}
+
+module "signal.h" [system] {
+ export *
+ header "/usr/include/signal.h"
+}
diff --git a/src/region.h b/src/region.h
new file mode 100644
index 0000000..bda66e2
--- /dev/null
+++ b/src/region.h
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) 2018 Yuxuan Shui <[email protected]>
+#pragma once
+#include <pixman.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <xcb/xcb.h>
+
+#include "log.h"
+#include "utils.h"
+
+typedef struct pixman_region32 pixman_region32_t;
+typedef struct pixman_box32 pixman_box32_t;
+typedef pixman_region32_t region_t;
+typedef pixman_box32_t rect_t;
+
+RC_TYPE(region_t, rc_region, pixman_region32_init, pixman_region32_fini, static inline)
+
+static inline void dump_region(const region_t *x) {
+ if (log_get_level_tls() < LOG_LEVEL_TRACE) {
+ return;
+ }
+ int nrects;
+ const rect_t *rects = pixman_region32_rectangles((region_t *)x, &nrects);
+ log_trace("nrects: %d", nrects);
+ for (int i = 0; i < nrects; i++)
+ log_trace("(%d, %d) - (%d, %d)", rects[i].x1, rects[i].y1, rects[i].x2,
+ rects[i].y2);
+}
+
+/// Convert one xcb rectangle to our rectangle type
+static inline rect_t from_x_rect(const xcb_rectangle_t *rect) {
+ return (rect_t){
+ .x1 = rect->x,
+ .y1 = rect->y,
+ .x2 = rect->x + rect->width,
+ .y2 = rect->y + rect->height,
+ };
+}
+
+/// Convert an array of xcb rectangles to our rectangle type
+/// Returning an array that needs to be freed
+static inline rect_t *from_x_rects(int nrects, const xcb_rectangle_t *rects) {
+ rect_t *ret = ccalloc(nrects, rect_t);
+ for (int i = 0; i < nrects; i++) {
+ ret[i] = from_x_rect(rects + i);
+ }
+ return ret;
+}
+
+/**
+ * Resize a region.
+ */
+static inline void _resize_region(const region_t *region, region_t *output, int dx,
+ int dy) {
+ if (!region || !output) {
+ return;
+ }
+ if (!dx && !dy) {
+ if (region != output) {
+ pixman_region32_copy(output, (region_t *)region);
+ }
+ return;
+ }
+ // Loop through all rectangles
+ int nrects;
+ int nnewrects = 0;
+ const rect_t *rects = pixman_region32_rectangles((region_t *)region, &nrects);
+ auto newrects = ccalloc(nrects, rect_t);
+ for (int i = 0; i < nrects; i++) {
+ int x1 = rects[i].x1 - dx;
+ int y1 = rects[i].y1 - dy;
+ int x2 = rects[i].x2 + dx;
+ int y2 = rects[i].y2 + dy;
+ int wid = x2 - x1;
+ int hei = y2 - y1;
+ if (wid <= 0 || hei <= 0) {
+ continue;
+ }
+ newrects[nnewrects] =
+ (rect_t){.x1 = x1, .x2 = x2, .y1 = y1, .y2 = y2};
+ ++nnewrects;
+ }
+
+ pixman_region32_fini(output);
+ pixman_region32_init_rects(output, newrects, nnewrects);
+
+ free(newrects);
+}
+
+static inline region_t resize_region(const region_t *region, int dx, int dy) {
+ region_t ret;
+ pixman_region32_init(&ret);
+ _resize_region(region, &ret, dx, dy);
+ return ret;
+}
+
+static inline void resize_region_in_place(region_t *region, int dx, int dy) {
+ return _resize_region(region, region, dx, dy);
+}
diff --git a/src/render.c b/src/render.c
new file mode 100644
index 0000000..ac9b40e
--- /dev/null
+++ b/src/render.c
@@ -0,0 +1,1500 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) Yuxuan Shui <[email protected]>
+
+#include <stdlib.h>
+#include <string.h>
+#include <xcb/composite.h>
+#include <xcb/render.h>
+#include <xcb/sync.h>
+#include <xcb/xcb_image.h>
+#include <xcb/xcb_renderutil.h>
+
+#include "common.h"
+#include "options.h"
+
+#ifdef CONFIG_OPENGL
+#include "backend/gl/glx.h"
+#include "opengl.h"
+
+#ifndef GLX_BACK_BUFFER_AGE_EXT
+#define GLX_BACK_BUFFER_AGE_EXT 0x20F4
+#endif
+
+#endif
+
+#include "compiler.h"
+#include "config.h"
+#include "kernel.h"
+#include "log.h"
+#include "region.h"
+#include "types.h"
+#include "utils.h"
+#include "vsync.h"
+#include "win.h"
+#include "x.h"
+
+#include "backend/backend.h"
+#include "backend/backend_common.h"
+#include "render.h"
+
+#define XRFILTER_CONVOLUTION "convolution"
+#define XRFILTER_GAUSSIAN "gaussian"
+#define XRFILTER_BINOMIAL "binomial"
+
+/**
+ * Bind texture in paint_t if we are using GLX backend.
+ */
+static inline bool paint_bind_tex(session_t *ps, paint_t *ppaint, int wid, int hei,
+ bool repeat, int depth, xcb_visualid_t visual, bool force) {
+#ifdef CONFIG_OPENGL
+ // XXX This is a mess. But this will go away after the backend refactor.
+ if (!ppaint->pixmap)
+ return false;
+
+ struct glx_fbconfig_info *fbcfg;
+ if (!visual) {
+ assert(depth == 32);
+ if (!ps->argb_fbconfig) {
+ ps->argb_fbconfig =
+ glx_find_fbconfig(ps->dpy, ps->scr,
+ (struct xvisual_info){.red_size = 8,
+ .green_size = 8,
+ .blue_size = 8,
+ .alpha_size = 8,
+ .visual_depth = 32});
+ }
+ if (!ps->argb_fbconfig) {
+ log_error("Failed to find appropriate FBConfig for 32 bit depth");
+ return false;
+ }
+ fbcfg = ps->argb_fbconfig;
+ } else {
+ auto m = x_get_visual_info(ps->c, visual);
+ if (m.visual_depth < 0) {
+ return false;
+ }
+
+ if (depth && depth != m.visual_depth) {
+ log_error("Mismatching visual depth: %d != %d", depth, m.visual_depth);
+ return false;
+ }
+
+ if (!ppaint->fbcfg) {
+ ppaint->fbcfg = glx_find_fbconfig(ps->dpy, ps->scr, m);
+ }
+ if (!ppaint->fbcfg) {
+ log_error("Failed to find appropriate FBConfig for X pixmap");
+ return false;
+ }
+ fbcfg = ppaint->fbcfg;
+ }
+
+ if (force || !glx_tex_binded(ppaint->ptex, ppaint->pixmap))
+ return glx_bind_pixmap(ps, &ppaint->ptex, ppaint->pixmap, wid, hei,
+ repeat, fbcfg);
+#else
+ (void)ps;
+ (void)ppaint;
+ (void)wid;
+ (void)hei;
+ (void)repeat;
+ (void)depth;
+ (void)visual;
+ (void)force;
+#endif
+ return true;
+}
+
+/**
+ * Check if current backend uses XRender for rendering.
+ */
+static inline bool bkend_use_xrender(session_t *ps) {
+ return BKEND_XRENDER == ps->o.backend || BKEND_XR_GLX_HYBRID == ps->o.backend;
+}
+
+int maximum_buffer_age(session_t *ps) {
+ if (bkend_use_glx(ps) && ps->o.use_damage) {
+ return CGLX_MAX_BUFFER_AGE;
+ }
+ return 1;
+}
+
+static int get_buffer_age(session_t *ps) {
+#ifdef CONFIG_OPENGL
+ if (bkend_use_glx(ps)) {
+ if (!glxext.has_GLX_EXT_buffer_age && ps->o.use_damage) {
+ log_warn("GLX_EXT_buffer_age not supported by your driver,"
+ "`use-damage` has to be disabled");
+ ps->o.use_damage = false;
+ }
+ if (ps->o.use_damage) {
+ unsigned int val;
+ glXQueryDrawable(ps->dpy, get_tgt_window(ps),
+ GLX_BACK_BUFFER_AGE_EXT, &val);
+ return (int)val ?: -1;
+ }
+ return -1;
+ }
+#endif
+ return ps->o.use_damage ? 1 : -1;
+}
+
+/**
+ * Reset filter on a <code>Picture</code>.
+ */
+static inline void xrfilter_reset(session_t *ps, xcb_render_picture_t p) {
+#define FILTER "Nearest"
+ xcb_render_set_picture_filter(ps->c, p, strlen(FILTER), FILTER, 0, NULL);
+#undef FILTER
+}
+
+/// Set the input/output clip region of the target buffer (not the actual target!)
+static inline void attr_nonnull(1, 2) set_tgt_clip(session_t *ps, region_t *reg) {
+ switch (ps->o.backend) {
+ case BKEND_XRENDER:
+ case BKEND_XR_GLX_HYBRID:
+ x_set_picture_clip_region(ps->c, ps->tgt_buffer.pict, 0, 0, reg);
+ break;
+#ifdef CONFIG_OPENGL
+ case BKEND_GLX: glx_set_clip(ps, reg); break;
+#endif
+ default: assert(false);
+ }
+}
+
+/**
+ * Destroy a <code>Picture</code>.
+ */
+void free_picture(xcb_connection_t *c, xcb_render_picture_t *p) {
+ if (*p) {
+ xcb_render_free_picture(c, *p);
+ *p = XCB_NONE;
+ }
+}
+
+/**
+ * Free paint_t.
+ */
+void free_paint(session_t *ps, paint_t *ppaint) {
+#ifdef CONFIG_OPENGL
+ free_paint_glx(ps, ppaint);
+#endif
+ free_picture(ps->c, &ppaint->pict);
+ if (ppaint->pixmap)
+ xcb_free_pixmap(ps->c, ppaint->pixmap);
+ ppaint->pixmap = XCB_NONE;
+}
+
+uint32_t
+make_circle(int cx, int cy, int radius, uint32_t max_ntraps, xcb_render_trapezoid_t traps[]) {
+ uint32_t n = 0, k = 0;
+ int y1, y2;
+ double w;
+ while (k < max_ntraps) {
+ y1 = (int)(-radius * cos(M_PI * k / max_ntraps));
+ traps[n].top = (cy + y1) * 65536;
+ traps[n].left.p1.y = (cy + y1) * 65536;
+ traps[n].right.p1.y = (cy + y1) * 65536;
+ w = sqrt(radius * radius - y1 * y1) * 65536;
+ traps[n].left.p1.x = (int)((cx * 65536) - w);
+ traps[n].right.p1.x = (int)((cx * 65536) + w);
+
+ do {
+ k++;
+ y2 = (int)(-radius * cos(M_PI * k / max_ntraps));
+ } while (y1 == y2);
+
+ traps[n].bottom = (cy + y2) * 65536;
+ traps[n].left.p2.y = (cy + y2) * 65536;
+ traps[n].right.p2.y = (cy + y2) * 65536;
+ w = sqrt(radius * radius - y2 * y2) * 65536;
+ traps[n].left.p2.x = (int)((cx * 65536) - w);
+ traps[n].right.p2.x = (int)((cx * 65536) + w);
+ n++;
+ }
+ return n;
+}
+
+uint32_t make_rectangle(int x, int y, int wid, int hei, xcb_render_trapezoid_t traps[]) {
+ traps[0].top = y * 65536;
+ traps[0].left.p1.y = y * 65536;
+ traps[0].left.p1.x = x * 65536;
+ traps[0].left.p2.y = (y + hei) * 65536;
+ traps[0].left.p2.x = x * 65536;
+ traps[0].bottom = (y + hei) * 65536;
+ traps[0].right.p1.x = (x + wid) * 65536;
+ traps[0].right.p1.y = y * 65536;
+ traps[0].right.p2.x = (x + wid) * 65536;
+ traps[0].right.p2.y = (y + hei) * 65536;
+ return 1;
+}
+
+uint32_t make_rounded_window_shape(xcb_render_trapezoid_t traps[], uint32_t max_ntraps,
+ int cr, int wid, int hei) {
+ uint32_t n = make_circle(cr, cr, cr, max_ntraps, traps);
+ n += make_circle(wid - cr, cr, cr, max_ntraps, traps + n);
+ n += make_circle(wid - cr, hei - cr, cr, max_ntraps, traps + n);
+ n += make_circle(cr, hei - cr, cr, max_ntraps, traps + n);
+ n += make_rectangle(0, cr, wid, hei - 2 * cr, traps + n);
+ n += make_rectangle(cr, 0, wid - 2 * cr, cr, traps + n);
+ n += make_rectangle(cr, hei - cr, wid - 2 * cr, cr, traps + n);
+ return n;
+}
+
+void render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei, int fullwid,
+ int fullhei, double opacity, bool argb, bool neg, int cr,
+ xcb_render_picture_t pict, glx_texture_t *ptex, const region_t *reg_paint,
+ const glx_prog_main_t *pprogram, clip_t *clip) {
+ switch (ps->o.backend) {
+ case BKEND_XRENDER:
+ case BKEND_XR_GLX_HYBRID: {
+ auto alpha_step = (int)(opacity * MAX_ALPHA);
+ xcb_render_picture_t alpha_pict = ps->alpha_picts[alpha_step];
+ if (alpha_step != 0) {
+ if (cr) {
+ xcb_render_picture_t p_tmp = x_create_picture_with_standard(
+ ps->c, ps->root, fullwid, fullhei,
+ XCB_PICT_STANDARD_ARGB_32, 0, 0);
+ xcb_render_color_t trans = {
+ .red = 0, .blue = 0, .green = 0, .alpha = 0};
+ const xcb_rectangle_t rect = {
+ .x = 0,
+ .y = 0,
+ .width = to_u16_checked(fullwid),
+ .height = to_u16_checked(fullhei)};
+ xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC,
+ p_tmp, trans, 1, &rect);
+
+ uint32_t max_ntraps = to_u32_checked(cr);
+ xcb_render_trapezoid_t traps[4 * max_ntraps + 3];
+
+ uint32_t n = make_rounded_window_shape(
+ traps, max_ntraps, cr, fullwid, fullhei);
+
+ xcb_render_trapezoids(
+ ps->c, XCB_RENDER_PICT_OP_OVER, alpha_pict, p_tmp,
+ x_get_pictfmt_for_standard(ps->c, XCB_PICT_STANDARD_A_8),
+ 0, 0, n, traps);
+
+ xcb_render_composite(
+ ps->c, XCB_RENDER_PICT_OP_OVER, pict, p_tmp,
+ ps->tgt_buffer.pict, to_i16_checked(x),
+ to_i16_checked(y), to_i16_checked(x), to_i16_checked(y),
+ to_i16_checked(dx), to_i16_checked(dy),
+ to_u16_checked(wid), to_u16_checked(hei));
+
+ xcb_render_free_picture(ps->c, p_tmp);
+
+ } else {
+ xcb_render_picture_t p_tmp = alpha_pict;
+ if (clip) {
+ p_tmp = x_create_picture_with_standard(
+ ps->c, ps->root, wid, hei,
+ XCB_PICT_STANDARD_ARGB_32, 0, 0);
+
+ xcb_render_color_t black = {
+ .red = 255, .blue = 255, .green = 255, .alpha = 255};
+ const xcb_rectangle_t rect = {
+ .x = 0,
+ .y = 0,
+ .width = to_u16_checked(wid),
+ .height = to_u16_checked(hei)};
+ xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC,
+ p_tmp, black, 1, &rect);
+ if (alpha_pict) {
+ xcb_render_composite(
+ ps->c, XCB_RENDER_PICT_OP_SRC,
+ alpha_pict, XCB_NONE, p_tmp, 0, 0, 0,
+ 0, 0, 0, to_u16_checked(wid),
+ to_u16_checked(hei));
+ }
+ xcb_render_composite(
+ ps->c, XCB_RENDER_PICT_OP_OUT_REVERSE,
+ clip->pict, XCB_NONE, p_tmp, 0, 0, 0, 0,
+ to_i16_checked(clip->x), to_i16_checked(clip->y),
+ to_u16_checked(wid), to_u16_checked(hei));
+ }
+ uint8_t op = ((!argb && !alpha_pict && !clip)
+ ? XCB_RENDER_PICT_OP_SRC
+ : XCB_RENDER_PICT_OP_OVER);
+
+ xcb_render_composite(
+ ps->c, op, pict, p_tmp, ps->tgt_buffer.pict,
+ to_i16_checked(x), to_i16_checked(y), 0, 0,
+ to_i16_checked(dx), to_i16_checked(dy),
+ to_u16_checked(wid), to_u16_checked(hei));
+ if (clip) {
+ xcb_render_free_picture(ps->c, p_tmp);
+ }
+ }
+ }
+ break;
+ }
+#ifdef CONFIG_OPENGL
+ case BKEND_GLX:
+ glx_render(ps, ptex, x, y, dx, dy, wid, hei, ps->psglx->z, opacity, argb,
+ neg, reg_paint, pprogram);
+ ps->psglx->z += 1;
+ break;
+#endif
+ default: assert(0);
+ }
+#ifndef CONFIG_OPENGL
+ (void)neg;
+ (void)ptex;
+ (void)reg_paint;
+ (void)pprogram;
+#endif
+}
+
+static inline void
+paint_region(session_t *ps, const struct managed_win *w, int x, int y, int wid, int hei,
+ double opacity, const region_t *reg_paint, xcb_render_picture_t pict) {
+ const int dx = (w ? w->g.x : 0) + x;
+ const int dy = (w ? w->g.y : 0) + y;
+ const int fullwid = w ? w->widthb : 0;
+ const int fullhei = w ? w->heightb : 0;
+ const bool argb = (w && (win_has_alpha(w) || ps->o.force_win_blend));
+ const bool neg = (w && w->invert_color);
+
+ render(ps, x, y, dx, dy, wid, hei, fullwid, fullhei, opacity, argb, neg,
+ w ? w->corner_radius : 0, pict,
+ (w ? w->paint.ptex : ps->root_tile_paint.ptex), reg_paint,
+#ifdef CONFIG_OPENGL
+ w ? &ps->glx_prog_win : NULL
+#else
+ NULL
+#endif
+ ,
+ XCB_NONE);
+}
+
+/**
+ * Check whether a paint_t contains enough data.
+ */
+static inline bool paint_isvalid(session_t *ps, const paint_t *ppaint) {
+ // Don't check for presence of Pixmap here, because older X Composite doesn't
+ // provide it
+ if (!ppaint)
+ return false;
+
+ if (bkend_use_xrender(ps) && !ppaint->pict)
+ return false;
+
+#ifdef CONFIG_OPENGL
+ if (BKEND_GLX == ps->o.backend && !glx_tex_binded(ppaint->ptex, XCB_NONE))
+ return false;
+#endif
+
+ return true;
+}
+
+/**
+ * Paint a window itself and dim it if asked.
+ */
+void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint) {
+ // Fetch Pixmap
+ if (!w->paint.pixmap) {
+ w->paint.pixmap = x_new_id(ps->c);
+ set_ignore_cookie(ps, xcb_composite_name_window_pixmap(ps->c, w->base.id,
+ w->paint.pixmap));
+ }
+
+ xcb_drawable_t draw = w->paint.pixmap;
+ if (!draw) {
+ log_error("Failed to get pixmap from window %#010x (%s), window won't be "
+ "visible",
+ w->base.id, w->name);
+ return;
+ }
+
+ // XRender: Build picture
+ if (bkend_use_xrender(ps) && !w->paint.pict) {
+ xcb_render_create_picture_value_list_t pa = {
+ .subwindowmode = XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS,
+ };
+
+ w->paint.pict = x_create_picture_with_pictfmt_and_pixmap(
+ ps->c, w->pictfmt, draw, XCB_RENDER_CP_SUBWINDOW_MODE, &pa);
+ }
+
+ // GLX: Build texture
+ // Let glx_bind_pixmap() determine pixmap size, because if the user
+ // is resizing windows, the width and height we get may not be up-to-date,
+ // causing the jittering issue M4he reported in #7.
+ if (!paint_bind_tex(ps, &w->paint, 0, 0, false, 0, w->a.visual,
+ (!ps->o.glx_no_rebind_pixmap && w->pixmap_damaged))) {
+ log_error("Failed to bind texture for window %#010x.", w->base.id);
+ }
+ w->pixmap_damaged = false;
+
+ if (!paint_isvalid(ps, &w->paint)) {
+ log_error("Window %#010x is missing painting data.", w->base.id);
+ return;
+ }
+
+ const int x = w->g.x;
+ const int y = w->g.y;
+ const uint16_t wid = to_u16_checked(w->widthb);
+ const uint16_t hei = to_u16_checked(w->heightb);
+
+ xcb_render_picture_t pict = w->paint.pict;
+
+ // Invert window color, if required
+ if (bkend_use_xrender(ps) && w->invert_color) {
+ xcb_render_picture_t newpict = x_create_picture_with_pictfmt(
+ ps->c, ps->root, wid, hei, w->pictfmt, 0, NULL);
+ if (newpict) {
+ // Apply clipping region to save some CPU
+ if (reg_paint) {
+ region_t reg;
+ pixman_region32_init(&reg);
+ pixman_region32_copy(&reg, (region_t *)reg_paint);
+ pixman_region32_translate(&reg, -x, -y);
+ // FIXME XFixesSetPictureClipRegion(ps->dpy, newpict, 0,
+ // 0, reg);
+ pixman_region32_fini(&reg);
+ }
+
+ xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, pict, XCB_NONE,
+ newpict, 0, 0, 0, 0, 0, 0, wid, hei);
+ xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_DIFFERENCE,
+ ps->white_picture, XCB_NONE, newpict, 0, 0,
+ 0, 0, 0, 0, wid, hei);
+ // We use an extra PictOpInReverse operation to get correct
+ // pixel alpha. There could be a better solution.
+ if (win_has_alpha(w))
+ xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_IN_REVERSE,
+ pict, XCB_NONE, newpict, 0, 0, 0, 0,
+ 0, 0, wid, hei);
+ pict = newpict;
+ }
+ }
+
+ if (w->frame_opacity == 1) {
+ paint_region(ps, w, 0, 0, wid, hei, w->opacity, reg_paint, pict);
+ } else {
+ // Painting parameters
+ const margin_t extents = win_calc_frame_extents(w);
+ const auto t = extents.top;
+ const auto l = extents.left;
+ const auto b = extents.bottom;
+ const auto r = extents.right;
+
+#define COMP_BDR(cx, cy, cwid, chei) \
+ paint_region(ps, w, (cx), (cy), (cwid), (chei), w->frame_opacity * w->opacity, \
+ reg_paint, pict)
+
+ // Sanitize the margins, in case some broken WM makes
+ // top_width + bottom_width > height in some cases.
+
+ do {
+ // top
+ int body_height = hei;
+ // ctop = checked top
+ // Make sure top margin is smaller than height
+ int ctop = min2(body_height, t);
+ if (ctop > 0)
+ COMP_BDR(0, 0, wid, ctop);
+
+ body_height -= ctop;
+ if (body_height <= 0)
+ break;
+
+ // bottom
+ // cbot = checked bottom
+ // Make sure bottom margin is not too large
+ int cbot = min2(body_height, b);
+ if (cbot > 0)
+ COMP_BDR(0, hei - cbot, wid, cbot);
+
+ // Height of window exclude the margin
+ body_height -= cbot;
+ if (body_height <= 0)
+ break;
+
+ // left
+ int body_width = wid;
+ int cleft = min2(body_width, l);
+ if (cleft > 0)
+ COMP_BDR(0, ctop, cleft, body_height);
+
+ body_width -= cleft;
+ if (body_width <= 0)
+ break;
+
+ // right
+ int cright = min2(body_width, r);
+ if (cright > 0)
+ COMP_BDR(wid - cright, ctop, cright, body_height);
+
+ body_width -= cright;
+ if (body_width <= 0)
+ break;
+
+ // body
+ paint_region(ps, w, cleft, ctop, body_width, body_height,
+ w->opacity, reg_paint, pict);
+ } while (0);
+ }
+
+#undef COMP_BDR
+
+ if (pict != w->paint.pict)
+ free_picture(ps->c, &pict);
+
+ // Dimming the window if needed
+ if (w->dim) {
+ double dim_opacity = ps->o.inactive_dim;
+ if (!ps->o.inactive_dim_fixed)
+ dim_opacity *= w->opacity;
+
+ switch (ps->o.backend) {
+ case BKEND_XRENDER:
+ case BKEND_XR_GLX_HYBRID: {
+ auto cval = (uint16_t)(0xffff * dim_opacity);
+
+ // Premultiply color
+ xcb_render_color_t color = {
+ .red = 0,
+ .green = 0,
+ .blue = 0,
+ .alpha = cval,
+ };
+
+ xcb_rectangle_t rect = {
+ .x = to_i16_checked(x),
+ .y = to_i16_checked(y),
+ .width = wid,
+ .height = hei,
+ };
+
+ xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_OVER,
+ ps->tgt_buffer.pict, color, 1, &rect);
+ } break;
+#ifdef CONFIG_OPENGL
+ case BKEND_GLX:
+ glx_dim_dst(ps, x, y, wid, hei, (int)(ps->psglx->z - 0.7),
+ (float)dim_opacity, reg_paint);
+ break;
+#endif
+ default: assert(false);
+ }
+ }
+}
+
+extern const char *background_props_str[];
+
+static bool get_root_tile(session_t *ps) {
+ /*
+ if (ps->o.paint_on_overlay) {
+ return ps->root_picture;
+ } */
+
+ assert(!ps->root_tile_paint.pixmap);
+ ps->root_tile_fill = false;
+
+ bool fill = false;
+ xcb_pixmap_t pixmap = x_get_root_back_pixmap(ps->c, ps->root, ps->atoms);
+
+ // Make sure the pixmap we got is valid
+ if (pixmap && !x_validate_pixmap(ps->c, pixmap))
+ pixmap = XCB_NONE;
+
+ // Create a pixmap if there isn't any
+ if (!pixmap) {
+ pixmap = x_create_pixmap(ps->c, (uint8_t)ps->depth, ps->root, 1, 1);
+ if (pixmap == XCB_NONE) {
+ log_error("Failed to create pixmaps for root tile.");
+ return false;
+ }
+ fill = true;
+ }
+
+ // Create Picture
+ xcb_render_create_picture_value_list_t pa = {
+ .repeat = true,
+ };
+ ps->root_tile_paint.pict = x_create_picture_with_visual_and_pixmap(
+ ps->c, ps->vis, pixmap, XCB_RENDER_CP_REPEAT, &pa);
+
+ // Fill pixmap if needed
+ if (fill) {
+ xcb_render_color_t col;
+ xcb_rectangle_t rect;
+
+ col.red = col.green = col.blue = 0x8080;
+ col.alpha = 0xffff;
+
+ rect.x = rect.y = 0;
+ rect.width = rect.height = 1;
+
+ xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC,
+ ps->root_tile_paint.pict, col, 1, &rect);
+ }
+
+ ps->root_tile_fill = fill;
+ ps->root_tile_paint.pixmap = pixmap;
+#ifdef CONFIG_OPENGL
+ if (BKEND_GLX == ps->o.backend)
+ return paint_bind_tex(ps, &ps->root_tile_paint, 0, 0, true, 0, ps->vis, false);
+#endif
+
+ return true;
+}
+
+/**
+ * Paint root window content.
+ */
+static void paint_root(session_t *ps, const region_t *reg_paint) {
+ // If there is no root tile pixmap, try getting one.
+ // If that fails, give up.
+ if (!ps->root_tile_paint.pixmap && !get_root_tile(ps))
+ return;
+
+ paint_region(ps, NULL, 0, 0, ps->root_width, ps->root_height, 1.0, reg_paint,
+ ps->root_tile_paint.pict);
+}
+
+/**
+ * Generate shadow <code>Picture</code> for a window.
+ */
+static bool win_build_shadow(session_t *ps, struct managed_win *w, double opacity) {
+ const int width = w->widthb;
+ const int height = w->heightb;
+ // log_trace("(): building shadow for %s %d %d", w->name, width, height);
+
+ xcb_image_t *shadow_image = NULL;
+ xcb_pixmap_t shadow_pixmap = XCB_NONE, shadow_pixmap_argb = XCB_NONE;
+ xcb_render_picture_t shadow_picture = XCB_NONE, shadow_picture_argb = XCB_NONE;
+ xcb_gcontext_t gc = XCB_NONE;
+
+ shadow_image = make_shadow(ps->c, ps->gaussian_map, opacity, width, height);
+ if (!shadow_image) {
+ log_error("failed to make shadow");
+ return XCB_NONE;
+ }
+
+ shadow_pixmap =
+ x_create_pixmap(ps->c, 8, ps->root, shadow_image->width, shadow_image->height);
+ shadow_pixmap_argb =
+ x_create_pixmap(ps->c, 32, ps->root, shadow_image->width, shadow_image->height);
+
+ if (!shadow_pixmap || !shadow_pixmap_argb) {
+ log_error("failed to create shadow pixmaps");
+ goto shadow_picture_err;
+ }
+
+ shadow_picture = x_create_picture_with_standard_and_pixmap(
+ ps->c, XCB_PICT_STANDARD_A_8, shadow_pixmap, 0, NULL);
+ shadow_picture_argb = x_create_picture_with_standard_and_pixmap(
+ ps->c, XCB_PICT_STANDARD_ARGB_32, shadow_pixmap_argb, 0, NULL);
+ if (!shadow_picture || !shadow_picture_argb)
+ goto shadow_picture_err;
+
+ gc = x_new_id(ps->c);
+ xcb_create_gc(ps->c, gc, shadow_pixmap, 0, NULL);
+
+ xcb_image_put(ps->c, shadow_pixmap, gc, shadow_image, 0, 0, 0);
+ xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, ps->cshadow_picture,
+ shadow_picture, shadow_picture_argb, 0, 0, 0, 0, 0, 0,
+ shadow_image->width, shadow_image->height);
+
+ assert(!w->shadow_paint.pixmap);
+ w->shadow_paint.pixmap = shadow_pixmap_argb;
+ assert(!w->shadow_paint.pict);
+ w->shadow_paint.pict = shadow_picture_argb;
+
+ xcb_free_gc(ps->c, gc);
+ xcb_image_destroy(shadow_image);
+ xcb_free_pixmap(ps->c, shadow_pixmap);
+ xcb_render_free_picture(ps->c, shadow_picture);
+
+ return true;
+
+shadow_picture_err:
+ if (shadow_image)
+ xcb_image_destroy(shadow_image);
+ if (shadow_pixmap)
+ xcb_free_pixmap(ps->c, shadow_pixmap);
+ if (shadow_pixmap_argb)
+ xcb_free_pixmap(ps->c, shadow_pixmap_argb);
+ if (shadow_picture)
+ xcb_render_free_picture(ps->c, shadow_picture);
+ if (shadow_picture_argb)
+ xcb_render_free_picture(ps->c, shadow_picture_argb);
+ if (gc)
+ xcb_free_gc(ps->c, gc);
+
+ return false;
+}
+
+/**
+ * Paint the shadow of a window.
+ */
+static inline void
+win_paint_shadow(session_t *ps, struct managed_win *w, region_t *reg_paint) {
+ // Bind shadow pixmap to GLX texture if needed
+ paint_bind_tex(ps, &w->shadow_paint, 0, 0, false, 32, 0, false);
+
+ if (!paint_isvalid(ps, &w->shadow_paint)) {
+ log_error("Window %#010x is missing shadow data.", w->base.id);
+ return;
+ }
+
+ xcb_render_picture_t td = XCB_NONE;
+ bool should_clip =
+ (w->corner_radius > 0) && (!ps->o.wintype_option[w->window_type].full_shadow);
+ if (should_clip) {
+ if (ps->o.backend == BKEND_XRENDER || ps->o.backend == BKEND_XR_GLX_HYBRID) {
+ uint32_t max_ntraps = to_u32_checked(w->corner_radius);
+ xcb_render_trapezoid_t traps[4 * max_ntraps + 3];
+ uint32_t n = make_rounded_window_shape(
+ traps, max_ntraps, w->corner_radius, w->widthb, w->heightb);
+
+ td = x_create_picture_with_standard(
+ ps->c, ps->root, w->widthb, w->heightb,
+ XCB_PICT_STANDARD_ARGB_32, 0, 0);
+ xcb_render_color_t trans = {
+ .red = 0, .blue = 0, .green = 0, .alpha = 0};
+ const xcb_rectangle_t rect = {.x = 0,
+ .y = 0,
+ .width = to_u16_checked(w->widthb),
+ .height = to_u16_checked(w->heightb)};
+ xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, td,
+ trans, 1, &rect);
+
+ auto solid = solid_picture(ps->c, ps->root, false, 1, 0, 0, 0);
+ xcb_render_trapezoids(
+ ps->c, XCB_RENDER_PICT_OP_OVER, solid, td,
+ x_get_pictfmt_for_standard(ps->c, XCB_PICT_STANDARD_A_8), 0,
+ 0, n, traps);
+ xcb_render_free_picture(ps->c, solid);
+ } else {
+ // Not implemented
+ }
+ }
+
+ clip_t clip = {
+ .pict = td,
+ .x = -(w->shadow_dx),
+ .y = -(w->shadow_dy),
+ };
+ render(ps, 0, 0, w->g.x + w->shadow_dx, w->g.y + w->shadow_dy, w->shadow_width,
+ w->shadow_height, w->widthb, w->heightb, w->shadow_opacity, true, false, 0,
+ w->shadow_paint.pict, w->shadow_paint.ptex, reg_paint, NULL,
+ should_clip ? &clip : NULL);
+ if (td) {
+ xcb_render_free_picture(ps->c, td);
+ }
+}
+
+/**
+ * @brief Blur an area on a buffer.
+ *
+ * @param ps current session
+ * @param tgt_buffer a buffer as both source and destination
+ * @param x x pos
+ * @param y y pos
+ * @param wid width
+ * @param hei height
+ * @param blur_kerns blur kernels, ending with a NULL, guaranteed to have at
+ * least one kernel
+ * @param reg_clip a clipping region to be applied on intermediate buffers
+ *
+ * @return true if successful, false otherwise
+ */
+static bool
+xr_blur_dst(session_t *ps, xcb_render_picture_t tgt_buffer, int16_t x, int16_t y,
+ uint16_t wid, uint16_t hei, struct x_convolution_kernel **blur_kerns,
+ int nkernels, const region_t *reg_clip, xcb_render_picture_t rounded) {
+ assert(blur_kerns);
+ assert(blur_kerns[0]);
+
+ // Directly copying from tgt_buffer to it does not work, so we create a
+ // Picture in the middle.
+ xcb_render_picture_t tmp_picture =
+ x_create_picture_with_visual(ps->c, ps->root, wid, hei, ps->vis, 0, NULL);
+
+ if (!tmp_picture) {
+ log_error("Failed to build intermediate Picture.");
+ return false;
+ }
+
+ if (reg_clip && tmp_picture)
+ x_set_picture_clip_region(ps->c, tmp_picture, 0, 0, reg_clip);
+
+ xcb_render_picture_t src_pict = tgt_buffer, dst_pict = tmp_picture;
+ for (int i = 0; i < nkernels; ++i) {
+ xcb_render_fixed_t *convolution_blur = blur_kerns[i]->kernel;
+ // `x / 65536.0` converts from X fixed point to double
+ int kwid = (int)((double)convolution_blur[0] / 65536.0),
+ khei = (int)((double)convolution_blur[1] / 65536.0);
+ bool rd_from_tgt = (tgt_buffer == src_pict);
+
+ // Copy from source picture to destination. The filter must
+ // be applied on source picture, to get the nearby pixels outside the
+ // window.
+ xcb_render_set_picture_filter(
+ ps->c, src_pict, strlen(XRFILTER_CONVOLUTION), XRFILTER_CONVOLUTION,
+ (uint32_t)(kwid * khei + 2), convolution_blur);
+ xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, src_pict, XCB_NONE,
+ dst_pict, (rd_from_tgt ? x : 0),
+ (rd_from_tgt ? y : 0), 0, 0, (rd_from_tgt ? 0 : x),
+ (rd_from_tgt ? 0 : y), wid, hei);
+ xrfilter_reset(ps, src_pict);
+
+ {
+ xcb_render_picture_t tmp = src_pict;
+ src_pict = dst_pict;
+ dst_pict = tmp;
+ }
+ }
+
+ if (src_pict != tgt_buffer)
+ xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_OVER, src_pict, rounded,
+ tgt_buffer, 0, 0, 0, 0, x, y, wid, hei);
+
+ free_picture(ps->c, &tmp_picture);
+
+ return true;
+}
+
+/**
+ * Blur the background of a window.
+ */
+static inline void
+win_blur_background(session_t *ps, struct managed_win *w, xcb_render_picture_t tgt_buffer,
+ const region_t *reg_paint) {
+ const int16_t x = w->g.x;
+ const int16_t y = w->g.y;
+ const auto wid = to_u16_checked(w->widthb);
+ const auto hei = to_u16_checked(w->heightb);
+ const int cr = w ? w->corner_radius : 0;
+
+ double factor_center = 1.0;
+ // Adjust blur strength according to window opacity, to make it appear
+ // better during fading
+ if (!ps->o.blur_background_fixed) {
+ double pct = 1.0 - w->opacity * (1.0 - 1.0 / 9.0);
+ factor_center = pct * 8.0 / (1.1 - pct);
+ }
+
+ switch (ps->o.backend) {
+ case BKEND_XRENDER:
+ case BKEND_XR_GLX_HYBRID: {
+ // Normalize blur kernels
+ for (int i = 0; i < ps->o.blur_kernel_count; i++) {
+ // Note: `x * 65536` converts double `x` to a X fixed point
+ // representation. `x / 65536` is the other way.
+ auto kern_src = ps->o.blur_kerns[i];
+ auto kern_dst = ps->blur_kerns_cache[i];
+
+ assert(!kern_dst || (kern_src->w == kern_dst->kernel[0] / 65536 &&
+ kern_src->h == kern_dst->kernel[1] / 65536));
+
+ // Skip for fixed factor_center if the cache exists already
+ if (ps->o.blur_background_fixed && kern_dst) {
+ continue;
+ }
+
+ x_create_convolution_kernel(kern_src, factor_center,
+ &ps->blur_kerns_cache[i]);
+ }
+
+ xcb_render_picture_t td = XCB_NONE;
+ if (cr) {
+ uint32_t max_ntraps = to_u32_checked(cr);
+ xcb_render_trapezoid_t traps[4 * max_ntraps + 3];
+ uint32_t n =
+ make_rounded_window_shape(traps, max_ntraps, cr, wid, hei);
+
+ td = x_create_picture_with_standard(
+ ps->c, ps->root, wid, hei, XCB_PICT_STANDARD_ARGB_32, 0, 0);
+ xcb_render_color_t trans = {
+ .red = 0, .blue = 0, .green = 0, .alpha = 0};
+ const xcb_rectangle_t rect = {.x = 0,
+ .y = 0,
+ .width = to_u16_checked(wid),
+ .height = to_u16_checked(hei)};
+ xcb_render_fill_rectangles(ps->c, XCB_RENDER_PICT_OP_SRC, td,
+ trans, 1, &rect);
+
+ auto solid = solid_picture(ps->c, ps->root, false, 1, 0, 0, 0);
+
+ xcb_render_trapezoids(
+ ps->c, XCB_RENDER_PICT_OP_OVER, solid, td,
+ x_get_pictfmt_for_standard(ps->c, XCB_PICT_STANDARD_A_8), 0,
+ 0, n, traps);
+ xcb_render_free_picture(ps->c, solid);
+ }
+
+ // Minimize the region we try to blur, if the window itself is not
+ // opaque, only the frame is.
+ region_t reg_blur = win_get_bounding_shape_global_by_val(w);
+ if (w->mode == WMODE_FRAME_TRANS && !ps->o.force_win_blend) {
+ region_t reg_noframe;
+ pixman_region32_init(&reg_noframe);
+ win_get_region_noframe_local(w, &reg_noframe);
+ pixman_region32_translate(&reg_noframe, w->g.x, w->g.y);
+ pixman_region32_subtract(&reg_blur, &reg_blur, &reg_noframe);
+ pixman_region32_fini(&reg_noframe);
+ }
+
+ // Translate global coordinates to local ones
+ pixman_region32_translate(&reg_blur, -x, -y);
+ xr_blur_dst(ps, tgt_buffer, x, y, wid, hei, ps->blur_kerns_cache,
+ ps->o.blur_kernel_count, &reg_blur, td);
+ if (td) {
+ xcb_render_free_picture(ps->c, td);
+ }
+ pixman_region32_clear(&reg_blur);
+ } break;
+#ifdef CONFIG_OPENGL
+ case BKEND_GLX:
+ // TODO(compton) Handle frame opacity
+ glx_blur_dst(ps, x, y, wid, hei, (float)ps->psglx->z - 0.5f,
+ (float)factor_center, reg_paint, &w->glx_blur_cache);
+ break;
+#endif
+ default: assert(0);
+ }
+#ifndef CONFIG_OPENGL
+ (void)reg_paint;
+#endif
+}
+
+/// paint all windows
+/// region = ??
+/// region_real = the damage region
+void paint_all(session_t *ps, struct managed_win *t, bool ignore_damage) {
+ if (ps->o.xrender_sync_fence || (ps->drivers & DRIVER_NVIDIA)) {
+ if (ps->xsync_exists && !x_fence_sync(ps->c, ps->sync_fence)) {
+ log_error("x_fence_sync failed, xrender-sync-fence will be "
+ "disabled from now on.");
+ xcb_sync_destroy_fence(ps->c, ps->sync_fence);
+ ps->sync_fence = XCB_NONE;
+ ps->o.xrender_sync_fence = false;
+ ps->xsync_exists = false;
+ }
+ }
+
+ region_t region;
+ pixman_region32_init(&region);
+ int buffer_age = get_buffer_age(ps);
+ if (buffer_age == -1 || buffer_age > ps->ndamage || ignore_damage) {
+ pixman_region32_copy(&region, &ps->screen_reg);
+ } else {
+ for (int i = 0; i < get_buffer_age(ps); i++) {
+ auto curr = ((ps->damage - ps->damage_ring) + i) % ps->ndamage;
+ pixman_region32_union(&region, &region, &ps->damage_ring[curr]);
+ }
+ }
+
+ if (!pixman_region32_not_empty(&region)) {
+ return;
+ }
+
+#ifdef DEBUG_REPAINT
+ static struct timespec last_paint = {0};
+#endif
+
+ if (ps->o.resize_damage > 0) {
+ resize_region_in_place(&region, ps->o.resize_damage, ps->o.resize_damage);
+ }
+
+ // Remove the damaged area out of screen
+ pixman_region32_intersect(&region, &region, &ps->screen_reg);
+
+ if (!paint_isvalid(ps, &ps->tgt_buffer)) {
+ if (!ps->tgt_buffer.pixmap) {
+ free_paint(ps, &ps->tgt_buffer);
+ ps->tgt_buffer.pixmap =
+ x_create_pixmap(ps->c, (uint8_t)ps->depth, ps->root,
+ ps->root_width, ps->root_height);
+ if (ps->tgt_buffer.pixmap == XCB_NONE) {
+ log_fatal("Failed to allocate a screen-sized pixmap for"
+ "painting");
+ exit(1);
+ }
+ }
+
+ if (BKEND_GLX != ps->o.backend)
+ ps->tgt_buffer.pict = x_create_picture_with_visual_and_pixmap(
+ ps->c, ps->vis, ps->tgt_buffer.pixmap, 0, 0);
+ }
+
+ if (BKEND_XRENDER == ps->o.backend) {
+ x_set_picture_clip_region(ps->c, ps->tgt_picture, 0, 0, &region);
+ }
+
+#ifdef CONFIG_OPENGL
+ if (bkend_use_glx(ps)) {
+ ps->psglx->z = 0.0;
+ }
+#endif
+
+ region_t reg_tmp, *reg_paint;
+ pixman_region32_init(&reg_tmp);
+ if (t) {
+ // Calculate the region upon which the root window is to be
+ // painted based on the ignore region of the lowest window, if
+ // available
+ pixman_region32_subtract(&reg_tmp, &region, t->reg_ignore);
+ reg_paint = &reg_tmp;
+ } else {
+ reg_paint = &region;
+ }
+
+ // Region on screen we don't want any shadows on
+ region_t reg_shadow_clip;
+ pixman_region32_init(&reg_shadow_clip);
+
+ set_tgt_clip(ps, reg_paint);
+ paint_root(ps, reg_paint);
+
+ // Windows are sorted from bottom to top
+ // Each window has a reg_ignore, which is the region obscured by all the
+ // windows on top of that window. This is used to reduce the number of
+ // pixels painted.
+ //
+ // Whether this is beneficial is to be determined XXX
+ for (auto w = t; w; w = w->prev_trans) {
+ region_t bshape_no_corners =
+ win_get_bounding_shape_global_without_corners_by_val(w);
+ region_t bshape_corners = win_get_bounding_shape_global_by_val(w);
+ // Painting shadow
+ if (w->shadow) {
+ // Lazy shadow building
+ if (!w->shadow_paint.pixmap)
+ if (!win_build_shadow(ps, w, 1))
+ log_error("build shadow failed");
+
+ // Shadow doesn't need to be painted underneath the body
+ // of the windows above. Because no one can see it
+ pixman_region32_subtract(&reg_tmp, &region, w->reg_ignore);
+
+ // Mask out the region we don't want shadow on
+ if (pixman_region32_not_empty(&ps->shadow_exclude_reg))
+ pixman_region32_subtract(&reg_tmp, &reg_tmp,
+ &ps->shadow_exclude_reg);
+ if (pixman_region32_not_empty(&reg_shadow_clip)) {
+ pixman_region32_subtract(&reg_tmp, &reg_tmp, &reg_shadow_clip);
+ }
+
+ // Might be worth while to crop the region to shadow
+ // border
+ assert(w->shadow_width >= 0 && w->shadow_height >= 0);
+ pixman_region32_intersect_rect(
+ &reg_tmp, &reg_tmp, w->g.x + w->shadow_dx, w->g.y + w->shadow_dy,
+ (uint)w->shadow_width, (uint)w->shadow_height);
+
+ // Mask out the body of the window from the shadow if
+ // needed Doing it here instead of in make_shadow() for
+ // saving GPU power and handling shaped windows (XXX
+ // unconfirmed)
+ if (!ps->o.wintype_option[w->window_type].full_shadow)
+ pixman_region32_subtract(&reg_tmp, &reg_tmp, &bshape_no_corners);
+
+ if (ps->o.xinerama_shadow_crop && w->xinerama_scr >= 0 &&
+ w->xinerama_scr < ps->xinerama_nscrs)
+ // There can be a window where number of screens
+ // is updated, but the screen number attached to
+ // the windows have not.
+ //
+ // Window screen number will be updated
+ // eventually, so here we just check to make sure
+ // we don't access out of bounds.
+ pixman_region32_intersect(
+ &reg_tmp, &reg_tmp,
+ &ps->xinerama_scr_regs[w->xinerama_scr]);
+
+ // Detect if the region is empty before painting
+ if (pixman_region32_not_empty(&reg_tmp)) {
+ set_tgt_clip(ps, &reg_tmp);
+ win_paint_shadow(ps, w, &reg_tmp);
+ }
+ }
+
+ // Only clip shadows above visible windows
+ if (w->opacity * MAX_ALPHA >= 1) {
+ if (w->clip_shadow_above) {
+ // Add window bounds to shadow-clip region
+ pixman_region32_union(&reg_shadow_clip, &reg_shadow_clip,
+ &bshape_corners);
+ } else {
+ // Remove overlapping window bounds from shadow-clip
+ // region
+ pixman_region32_subtract(
+ &reg_shadow_clip, &reg_shadow_clip, &bshape_corners);
+ }
+ }
+
+ // Calculate the paint region based on the reg_ignore of the current
+ // window and its bounding region.
+ // Remember, reg_ignore is the union of all windows above the current
+ // window.
+ pixman_region32_subtract(&reg_tmp, &region, w->reg_ignore);
+ pixman_region32_intersect(&reg_tmp, &reg_tmp, &bshape_corners);
+ pixman_region32_fini(&bshape_corners);
+ pixman_region32_fini(&bshape_no_corners);
+
+ if (pixman_region32_not_empty(&reg_tmp)) {
+ set_tgt_clip(ps, &reg_tmp);
+
+#ifdef CONFIG_OPENGL
+ // If rounded corners backup the region first
+ if (w->corner_radius > 0 && ps->o.backend == BKEND_GLX) {
+ const int16_t x = w->g.x;
+ const int16_t y = w->g.y;
+ const auto wid = to_u16_checked(w->widthb);
+ const auto hei = to_u16_checked(w->heightb);
+ glx_bind_texture(ps, &w->glx_texture_bg, x, y, wid, hei);
+ }
+#endif
+
+ // Blur window background
+ if (w->blur_background &&
+ (w->mode == WMODE_TRANS ||
+ (ps->o.blur_background_frame && w->mode == WMODE_FRAME_TRANS) ||
+ ps->o.force_win_blend)) {
+ win_blur_background(ps, w, ps->tgt_buffer.pict, &reg_tmp);
+ }
+
+ // Painting the window
+ paint_one(ps, w, &reg_tmp);
+
+#ifdef CONFIG_OPENGL
+ // Rounded corners for XRender is implemented inside render()
+ // Round window corners
+ if (w->corner_radius > 0 && ps->o.backend == BKEND_GLX) {
+ const auto wid = to_u16_checked(w->widthb);
+ const auto hei = to_u16_checked(w->heightb);
+ glx_round_corners_dst(ps, w, w->glx_texture_bg, w->g.x,
+ w->g.y, wid, hei,
+ (float)ps->psglx->z - 0.5F,
+ (float)w->corner_radius, &reg_tmp);
+ }
+#endif
+ }
+ }
+
+ // Free up all temporary regions
+ pixman_region32_fini(&reg_tmp);
+ pixman_region32_fini(&reg_shadow_clip);
+
+ // Move the head of the damage ring
+ ps->damage = ps->damage - 1;
+ if (ps->damage < ps->damage_ring) {
+ ps->damage = ps->damage_ring + ps->ndamage - 1;
+ }
+ pixman_region32_clear(ps->damage);
+
+ // Do this as early as possible
+ set_tgt_clip(ps, &ps->screen_reg);
+
+ if (ps->o.vsync) {
+ // Make sure all previous requests are processed to achieve best
+ // effect
+ x_sync(ps->c);
+#ifdef CONFIG_OPENGL
+ if (glx_has_context(ps)) {
+ if (ps->o.vsync_use_glfinish)
+ glFinish();
+ else
+ glFlush();
+ glXWaitX();
+ }
+#endif
+ }
+
+ if (ps->vsync_wait) {
+ ps->vsync_wait(ps);
+ }
+
+ auto rwidth = to_u16_checked(ps->root_width);
+ auto rheight = to_u16_checked(ps->root_height);
+ switch (ps->o.backend) {
+ case BKEND_XRENDER:
+ if (ps->o.monitor_repaint) {
+ // Copy the screen content to a new picture, and highlight the
+ // paint region. This is not very efficient, but since it's for
+ // debug only, we don't really care
+
+ // First we create a new picture, and copy content from the buffer
+ // to it
+ auto pictfmt = x_get_pictform_for_visual(ps->c, ps->vis);
+ xcb_render_picture_t new_pict = x_create_picture_with_pictfmt(
+ ps->c, ps->root, rwidth, rheight, pictfmt, 0, NULL);
+ xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC,
+ ps->tgt_buffer.pict, XCB_NONE, new_pict, 0,
+ 0, 0, 0, 0, 0, rwidth, rheight);
+
+ // Next, we set the region of paint and highlight it
+ x_set_picture_clip_region(ps->c, new_pict, 0, 0, &region);
+ xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_OVER, ps->white_picture,
+ ps->alpha_picts[MAX_ALPHA / 2], new_pict, 0,
+ 0, 0, 0, 0, 0, rwidth, rheight);
+
+ // Finally, clear clip regions of new_pict and the screen, and put
+ // the whole thing on screen
+ x_set_picture_clip_region(ps->c, new_pict, 0, 0, &ps->screen_reg);
+ x_set_picture_clip_region(ps->c, ps->tgt_picture, 0, 0, &ps->screen_reg);
+ xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC, new_pict,
+ XCB_NONE, ps->tgt_picture, 0, 0, 0, 0, 0, 0,
+ rwidth, rheight);
+ xcb_render_free_picture(ps->c, new_pict);
+ } else
+ xcb_render_composite(ps->c, XCB_RENDER_PICT_OP_SRC,
+ ps->tgt_buffer.pict, XCB_NONE, ps->tgt_picture,
+ 0, 0, 0, 0, 0, 0, rwidth, rheight);
+ break;
+#ifdef CONFIG_OPENGL
+ case BKEND_XR_GLX_HYBRID:
+ x_sync(ps->c);
+ if (ps->o.vsync_use_glfinish)
+ glFinish();
+ else
+ glFlush();
+ glXWaitX();
+ assert(ps->tgt_buffer.pixmap);
+ paint_bind_tex(ps, &ps->tgt_buffer, ps->root_width, ps->root_height,
+ false, ps->depth, ps->vis, !ps->o.glx_no_rebind_pixmap);
+ if (ps->o.vsync_use_glfinish)
+ glFinish();
+ else
+ glFlush();
+ glXWaitX();
+ glx_render(ps, ps->tgt_buffer.ptex, 0, 0, 0, 0, ps->root_width,
+ ps->root_height, 0, 1.0, false, false, &region, NULL);
+ fallthrough();
+ case BKEND_GLX: glXSwapBuffers(ps->dpy, get_tgt_window(ps)); break;
+#endif
+ default: assert(0);
+ }
+
+ x_sync(ps->c);
+
+#ifdef CONFIG_OPENGL
+ if (glx_has_context(ps)) {
+ glFlush();
+ glXWaitX();
+ }
+#endif
+
+#ifdef DEBUG_REPAINT
+ struct timespec now = get_time_timespec();
+ struct timespec diff = {0};
+ timespec_subtract(&diff, &now, &last_paint);
+ log_trace("[ %5ld:%09ld ] ", diff.tv_sec, diff.tv_nsec);
+ last_paint = now;
+ log_trace("paint:");
+ for (win *w = t; w; w = w->prev_trans)
+ log_trace(" %#010lx", w->id);
+#endif
+
+ // Free the paint region
+ pixman_region32_fini(&region);
+}
+
+/**
+ * Query needed X Render / OpenGL filters to check for their existence.
+ */
+static bool xr_init_blur(session_t *ps) {
+ // Query filters
+ xcb_render_query_filters_reply_t *pf = xcb_render_query_filters_reply(
+ ps->c, xcb_render_query_filters(ps->c, get_tgt_window(ps)), NULL);
+ if (pf) {
+ xcb_str_iterator_t iter = xcb_render_query_filters_filters_iterator(pf);
+ for (; iter.rem; xcb_str_next(&iter)) {
+ int len = xcb_str_name_length(iter.data);
+ char *name = xcb_str_name(iter.data);
+ // Check for the convolution filter
+ if (strlen(XRFILTER_CONVOLUTION) == len &&
+ !memcmp(XRFILTER_CONVOLUTION, name, strlen(XRFILTER_CONVOLUTION)))
+ ps->xrfilter_convolution_exists = true;
+ }
+ free(pf);
+ }
+
+ // Turn features off if any required filter is not present
+ if (!ps->xrfilter_convolution_exists) {
+ log_error("Xrender convolution filter "
+ "unsupported by your X server. "
+ "Background blur is not possible.");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Pregenerate alpha pictures.
+ */
+static bool init_alpha_picts(session_t *ps) {
+ ps->alpha_picts = ccalloc(MAX_ALPHA + 1, xcb_render_picture_t);
+
+ for (int i = 0; i <= MAX_ALPHA; ++i) {
+ double o = (double)i / MAX_ALPHA;
+ ps->alpha_picts[i] = solid_picture(ps->c, ps->root, false, o, 0, 0, 0);
+ if (ps->alpha_picts[i] == XCB_NONE)
+ return false;
+ }
+ return true;
+}
+
+bool init_render(session_t *ps) {
+ if (ps->o.backend == BKEND_DUMMY) {
+ return false;
+ }
+
+ // Initialize OpenGL as early as possible
+#ifdef CONFIG_OPENGL
+ glxext_init(ps->dpy, ps->scr);
+#endif
+ if (bkend_use_glx(ps)) {
+#ifdef CONFIG_OPENGL
+ if (!glx_init(ps, true))
+ return false;
+#else
+ log_error("GLX backend support not compiled in.");
+ return false;
+#endif
+ }
+
+ // Initialize VSync
+ if (!vsync_init(ps)) {
+ return false;
+ }
+
+ // Initialize window GL shader
+ if (BKEND_GLX == ps->o.backend && ps->o.glx_fshader_win_str) {
+#ifdef CONFIG_OPENGL
+ if (!glx_load_prog_main(NULL, ps->o.glx_fshader_win_str, &ps->glx_prog_win))
+ return false;
+#else
+ log_error("GLSL supported not compiled in, can't load "
+ "shader.");
+ return false;
+#endif
+ }
+
+ if (!init_alpha_picts(ps)) {
+ log_error("Failed to init alpha pictures.");
+ return false;
+ }
+
+ // Blur filter
+ if (ps->o.blur_method && ps->o.blur_method != BLUR_METHOD_KERNEL) {
+ log_warn("Old backends only support blur method \"kernel\". Your blur "
+ "setting will not be applied");
+ ps->o.blur_method = BLUR_METHOD_NONE;
+ }
+
+ if (ps->o.blur_method == BLUR_METHOD_KERNEL) {
+ ps->blur_kerns_cache =
+ ccalloc(ps->o.blur_kernel_count, struct x_convolution_kernel *);
+
+ bool ret = false;
+ if (ps->o.backend == BKEND_GLX) {
+#ifdef CONFIG_OPENGL
+ ret = glx_init_blur(ps);
+#else
+ assert(false);
+#endif
+ } else {
+ ret = xr_init_blur(ps);
+ }
+ if (!ret) {
+ return ret;
+ }
+ }
+
+ ps->black_picture = solid_picture(ps->c, ps->root, true, 1, 0, 0, 0);
+ ps->white_picture = solid_picture(ps->c, ps->root, true, 1, 1, 1, 1);
+
+ if (ps->black_picture == XCB_NONE || ps->white_picture == XCB_NONE) {
+ log_error("Failed to create solid xrender pictures.");
+ return false;
+ }
+
+ // Generates another Picture for shadows if the color is modified by
+ // user
+ if (ps->o.shadow_red == 0 && ps->o.shadow_green == 0 && ps->o.shadow_blue == 0) {
+ ps->cshadow_picture = ps->black_picture;
+ } else {
+ ps->cshadow_picture = solid_picture(ps->c, ps->root, true, 1, ps->o.shadow_red,
+ ps->o.shadow_green, ps->o.shadow_blue);
+ if (ps->cshadow_picture == XCB_NONE) {
+ log_error("Failed to create shadow picture.");
+ return false;
+ }
+ }
+
+ // Initialize our rounded corners fragment shader
+ if (ps->o.corner_radius > 0 && ps->o.backend == BKEND_GLX) {
+#ifdef CONFIG_OPENGL
+ if (!glx_init_rounded_corners(ps)) {
+ log_error("Failed to init rounded corners shader.");
+ return false;
+ }
+#else
+ assert(false);
+#endif
+ }
+ return true;
+}
+
+/**
+ * Free root tile related things.
+ */
+void free_root_tile(session_t *ps) {
+ free_picture(ps->c, &ps->root_tile_paint.pict);
+#ifdef CONFIG_OPENGL
+ free_texture(ps, &ps->root_tile_paint.ptex);
+#else
+ assert(!ps->root_tile_paint.ptex);
+#endif
+ if (ps->root_tile_fill) {
+ xcb_free_pixmap(ps->c, ps->root_tile_paint.pixmap);
+ ps->root_tile_paint.pixmap = XCB_NONE;
+ }
+ ps->root_tile_paint.pixmap = XCB_NONE;
+ ps->root_tile_fill = false;
+}
+
+void deinit_render(session_t *ps) {
+ // Free alpha_picts
+ for (int i = 0; i <= MAX_ALPHA; ++i)
+ free_picture(ps->c, &ps->alpha_picts[i]);
+ free(ps->alpha_picts);
+ ps->alpha_picts = NULL;
+
+ // Free cshadow_picture and black_picture
+ if (ps->cshadow_picture == ps->black_picture)
+ ps->cshadow_picture = XCB_NONE;
+ else
+ free_picture(ps->c, &ps->cshadow_picture);
+
+ free_picture(ps->c, &ps->black_picture);
+ free_picture(ps->c, &ps->white_picture);
+
+ // Free other X resources
+ free_root_tile(ps);
+
+#ifdef CONFIG_OPENGL
+ free(ps->root_tile_paint.fbcfg);
+ if (bkend_use_glx(ps)) {
+ glx_destroy(ps);
+ }
+#endif
+
+ if (ps->o.blur_method != BLUR_METHOD_NONE) {
+ for (int i = 0; i < ps->o.blur_kernel_count; i++) {
+ free(ps->blur_kerns_cache[i]);
+ }
+ free(ps->blur_kerns_cache);
+ }
+}
+
+// vim: set ts=8 sw=8 noet :
diff --git a/src/render.h b/src/render.h
new file mode 100644
index 0000000..95a46db
--- /dev/null
+++ b/src/render.h
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) Yuxuan Shui <[email protected]>
+#pragma once
+
+#include <stdbool.h>
+#include <xcb/render.h>
+#include <xcb/xcb.h>
+#ifdef CONFIG_OPENGL
+#include "backend/gl/glx.h"
+#endif
+#include "region.h"
+
+typedef struct _glx_texture glx_texture_t;
+typedef struct glx_prog_main glx_prog_main_t;
+typedef struct session session_t;
+
+struct managed_win;
+
+typedef struct paint {
+ xcb_pixmap_t pixmap;
+ xcb_render_picture_t pict;
+ glx_texture_t *ptex;
+#ifdef CONFIG_OPENGL
+ struct glx_fbconfig_info *fbcfg;
+#endif
+} paint_t;
+
+typedef struct clip {
+ xcb_render_picture_t pict;
+ int x;
+ int y;
+} clip_t;
+
+void render(session_t *ps, int x, int y, int dx, int dy, int w, int h, int fullw,
+ int fullh, double opacity, bool argb, bool neg, int cr,
+ xcb_render_picture_t pict, glx_texture_t *ptex, const region_t *reg_paint,
+ const glx_prog_main_t *pprogram, clip_t *clip);
+void paint_one(session_t *ps, struct managed_win *w, const region_t *reg_paint);
+
+void paint_all(session_t *ps, struct managed_win *const t, bool ignore_damage);
+
+void free_picture(xcb_connection_t *c, xcb_render_picture_t *p);
+
+void free_paint(session_t *ps, paint_t *ppaint);
+void free_root_tile(session_t *ps);
+
+bool init_render(session_t *ps);
+void deinit_render(session_t *ps);
+
+int maximum_buffer_age(session_t *);
diff --git a/src/string_utils.c b/src/string_utils.c
new file mode 100644
index 0000000..65af0f2
--- /dev/null
+++ b/src/string_utils.c
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) Yuxuan Shui <[email protected]>
+
+#include <string.h>
+
+#include <test.h>
+
+#include "compiler.h"
+#include "string_utils.h"
+#include "utils.h"
+
+#pragma GCC diagnostic push
+
+// gcc warns about legitimate strncpy in mstrjoin and mstrextend
+// strncpy(str, src1, len1) intentional truncates the null byte from src1.
+// strncpy(str+len1, src2, len2) uses bound depends on the source argument,
+// but str is allocated with len1+len2+1, so this strncpy can't overflow
+#pragma GCC diagnostic ignored "-Wpragmas"
+#pragma GCC diagnostic ignored "-Wstringop-truncation"
+#pragma GCC diagnostic ignored "-Wstringop-overflow"
+
+/**
+ * Allocate the space and join two strings.
+ */
+char *mstrjoin(const char *src1, const char *src2) {
+ auto len1 = strlen(src1);
+ auto len2 = strlen(src2);
+ auto len = len1 + len2 + 1;
+ auto str = ccalloc(len, char);
+
+ strncpy(str, src1, len1);
+ strncpy(str + len1, src2, len2);
+ str[len - 1] = '\0';
+
+ return str;
+}
+
+TEST_CASE(mstrjoin) {
+ char *str = mstrjoin("asdf", "qwer");
+ TEST_STREQUAL(str, "asdfqwer");
+ free(str);
+
+ str = mstrjoin("", "qwer");
+ TEST_STREQUAL(str, "qwer");
+ free(str);
+
+ str = mstrjoin("asdf", "");
+ TEST_STREQUAL(str, "asdf");
+ free(str);
+}
+
+/**
+ * Concatenate a string on heap with another string.
+ */
+void mstrextend(char **psrc1, const char *src2) {
+ if (!*psrc1) {
+ *psrc1 = strdup(src2);
+ return;
+ }
+
+ auto len1 = strlen(*psrc1);
+ auto len2 = strlen(src2);
+ auto len = len1 + len2 + 1;
+ *psrc1 = crealloc(*psrc1, len);
+
+ strncpy(*psrc1 + len1, src2, len2);
+ (*psrc1)[len - 1] = '\0';
+}
+
+TEST_CASE(mstrextend) {
+ char *str1 = NULL;
+ mstrextend(&str1, "asdf");
+ TEST_STREQUAL(str1, "asdf");
+
+ mstrextend(&str1, "asd");
+ TEST_STREQUAL(str1, "asdfasd");
+
+ mstrextend(&str1, "");
+ TEST_STREQUAL(str1, "asdfasd");
+ free(str1);
+}
+
+#pragma GCC diagnostic pop
+
+/// Parse a floating point number of form (+|-)?[0-9]*(\.[0-9]*)
+double strtod_simple(const char *src, const char **end) {
+ double neg = 1;
+ if (*src == '-') {
+ neg = -1;
+ src++;
+ } else if (*src == '+') {
+ src++;
+ }
+
+ double ret = 0;
+ while (*src >= '0' && *src <= '9') {
+ ret = ret * 10 + (*src - '0');
+ src++;
+ }
+
+ if (*src == '.') {
+ double frac = 0, mult = 0.1;
+ src++;
+ while (*src >= '0' && *src <= '9') {
+ frac += mult * (*src - '0');
+ mult *= 0.1;
+ src++;
+ }
+ ret += frac;
+ }
+
+ *end = src;
+ return ret * neg;
+}
+
+TEST_CASE(strtod_simple) {
+ const char *end;
+ double result = strtod_simple("1.0", &end);
+ TEST_EQUAL(result, 1);
+ TEST_EQUAL(*end, '\0');
+
+ result = strtod_simple("-1.0", &end);
+ TEST_EQUAL(result, -1);
+ TEST_EQUAL(*end, '\0');
+
+ result = strtod_simple("+.5", &end);
+ TEST_EQUAL(result, 0.5);
+ TEST_EQUAL(*end, '\0');
+}
diff --git a/src/string_utils.h b/src/string_utils.h
new file mode 100644
index 0000000..38febde
--- /dev/null
+++ b/src/string_utils.h
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) Yuxuan Shui <[email protected]>
+#pragma once
+#include <ctype.h>
+#include <stddef.h>
+
+#include "compiler.h"
+
+#define mstrncmp(s1, s2) strncmp((s1), (s2), strlen(s1))
+
+char *mstrjoin(const char *src1, const char *src2);
+char *mstrjoin3(const char *src1, const char *src2, const char *src3);
+void mstrextend(char **psrc1, const char *src2);
+
+/// Parse a floating point number of form (+|-)?[0-9]*(\.[0-9]*)
+double strtod_simple(const char *, const char **);
+
+static inline int uitostr(unsigned int n, char *buf) {
+ int ret = 0;
+ unsigned int tmp = n;
+ while (tmp > 0) {
+ tmp /= 10;
+ ret++;
+ }
+
+ if (ret == 0)
+ ret = 1;
+
+ int pos = ret;
+ while (pos--) {
+ buf[pos] = (char)(n % 10 + '0');
+ n /= 10;
+ }
+ return ret;
+}
+
+static inline const char *skip_space_const(const char *src) {
+ if (!src)
+ return NULL;
+ while (*src && isspace((unsigned char)*src))
+ src++;
+ return src;
+}
+
+static inline char *skip_space_mut(char *src) {
+ if (!src)
+ return NULL;
+ while (*src && isspace((unsigned char)*src))
+ src++;
+ return src;
+}
+
+#define skip_space(x) \
+ _Generic((x), char * : skip_space_mut, const char * : skip_space_const)(x)
diff --git a/src/types.h b/src/types.h
new file mode 100644
index 0000000..c8d747b
--- /dev/null
+++ b/src/types.h
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) 2018 Yuxuan Shui <[email protected]>
+
+#pragma once
+
+/// Some common types
+
+#include <stdint.h>
+
+/// Enumeration type to represent switches.
+typedef enum {
+ OFF = 0, // false
+ ON, // true
+ UNSET
+} switch_t;
+
+/// A structure representing margins around a rectangle.
+typedef struct {
+ int top;
+ int left;
+ int bottom;
+ int right;
+} margin_t;
+
+struct color {
+ double red, green, blue, alpha;
+};
+
+typedef uint32_t opacity_t;
+
+#define MARGIN_INIT \
+ { 0, 0, 0, 0 }
diff --git a/src/uthash_extra.h b/src/uthash_extra.h
new file mode 100644
index 0000000..cbc1056
--- /dev/null
+++ b/src/uthash_extra.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include <uthash.h>
+
+#define HASH_ITER2(head, el) \
+ for (__typeof__(head) el = (head), __tmp = el != NULL ? el->hh.next : NULL; \
+ el != NULL; el = __tmp, __tmp = el != NULL ? el->hh.next : NULL)
diff --git a/src/utils.c b/src/utils.c
new file mode 100644
index 0000000..8a27f39
--- /dev/null
+++ b/src/utils.c
@@ -0,0 +1,51 @@
+#include <stdio.h>
+#include <string.h>
+#include <sys/uio.h>
+
+#include "compiler.h"
+#include "string_utils.h"
+#include "utils.h"
+
+/// Report allocation failure without allocating memory
+void report_allocation_failure(const char *func, const char *file, unsigned int line) {
+ // Since memory allocation failed, we try to print this error message without any
+ // memory allocation. Since logging framework allocates memory (and might even
+ // have not been initialized yet), so we can't use it.
+ char buf[11];
+ int llen = uitostr(line, buf);
+ const char msg1[] = " has failed to allocate memory, ";
+ const char msg2[] = ". Aborting...\n";
+ const struct iovec v[] = {
+ {.iov_base = (void *)func, .iov_len = strlen(func)},
+ {.iov_base = "()", .iov_len = 2},
+ {.iov_base = (void *)msg1, .iov_len = sizeof(msg1) - 1},
+ {.iov_base = "at ", .iov_len = 3},
+ {.iov_base = (void *)file, .iov_len = strlen(file)},
+ {.iov_base = ":", .iov_len = 1},
+ {.iov_base = buf, .iov_len = (size_t)llen},
+ {.iov_base = (void *)msg2, .iov_len = sizeof(msg2) - 1},
+ };
+
+ writev(STDERR_FILENO, v, ARR_SIZE(v));
+ abort();
+
+ unreachable;
+}
+
+///
+/// Calculates next closest power of two of 32bit integer n
+/// ref: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
+///
+int next_power_of_two(int n)
+{
+ n--;
+ n |= n >> 1;
+ n |= n >> 2;
+ n |= n >> 4;
+ n |= n >> 8;
+ n |= n >> 16;
+ n++;
+ return n;
+}
+
+// vim: set noet sw=8 ts=8 :
diff --git a/src/utils.h b/src/utils.h
new file mode 100644
index 0000000..6bb8643
--- /dev/null
+++ b/src/utils.h
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) 2018 Yuxuan Shui <[email protected]>
+#pragma once
+#include <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <test.h>
+
+#include "compiler.h"
+#include "types.h"
+
+#define ARR_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
+
+#ifdef __FAST_MATH__
+#warning Use of -ffast-math can cause rendering error or artifacts, \
+ therefore it is not recommended.
+#endif
+
+#ifdef __clang__
+__attribute__((optnone))
+#else
+__attribute__((optimize("-fno-fast-math")))
+#endif
+static inline bool
+safe_isnan(double a) {
+ return __builtin_isnan(a);
+}
+
+/// Same as assert(false), but make sure we abort _even in release builds_.
+/// Silence compiler warning caused by release builds making some code paths reachable.
+#define BUG() \
+ do { \
+ assert(false); \
+ abort(); \
+ } while (0)
+#define CHECK_EXPR(...) ((void)0)
+/// Same as assert, but evaluates the expression even in release builds
+#define CHECK(expr) \
+ do { \
+ auto _ = (expr); \
+ /* make sure the original expression appears in the assertion message */ \
+ assert((CHECK_EXPR(expr), _)); \
+ (void)_; \
+ } while (0)
+
+/// Asserts that var is within [lower, upper]. Silence compiler warning about expressions
+/// being always true or false.
+#define ASSERT_IN_RANGE(var, lower, upper) \
+ do { \
+ auto __tmp attr_unused = (var); \
+ _Pragma("GCC diagnostic push"); \
+ _Pragma("GCC diagnostic ignored \"-Wtype-limits\""); \
+ assert(__tmp >= lower); \
+ assert(__tmp <= upper); \
+ _Pragma("GCC diagnostic pop"); \
+ } while (0)
+
+/// Asserts that var >= lower. Silence compiler warning about expressions
+/// being always true or false.
+#define ASSERT_GEQ(var, lower) \
+ do { \
+ auto __tmp attr_unused = (var); \
+ _Pragma("GCC diagnostic push"); \
+ _Pragma("GCC diagnostic ignored \"-Wtype-limits\""); \
+ assert(__tmp >= lower); \
+ _Pragma("GCC diagnostic pop"); \
+ } while (0)
+
+// Some macros for checked cast
+// Note these macros are not complete, as in, they won't work for every integer types. But
+// they are good enough for our use cases.
+
+#define to_int_checked(val) \
+ ({ \
+ int64_t tmp = (val); \
+ ASSERT_IN_RANGE(tmp, INT_MIN, INT_MAX); \
+ (int)tmp; \
+ })
+
+#define to_char_checked(val) \
+ ({ \
+ int64_t tmp = (val); \
+ ASSERT_IN_RANGE(tmp, CHAR_MIN, CHAR_MAX); \
+ (char)tmp; \
+ })
+
+#define to_u16_checked(val) \
+ ({ \
+ auto tmp = (val); \
+ ASSERT_IN_RANGE(tmp, 0, UINT16_MAX); \
+ (uint16_t) tmp; \
+ })
+
+#define to_i16_checked(val) \
+ ({ \
+ int64_t tmp = (val); \
+ ASSERT_IN_RANGE(tmp, INT16_MIN, INT16_MAX); \
+ (int16_t) tmp; \
+ })
+
+#define to_u32_checked(val) \
+ ({ \
+ auto tmp = (val); \
+ int64_t max attr_unused = UINT32_MAX; /* silence clang tautological \
+ comparison warning*/ \
+ ASSERT_IN_RANGE(tmp, 0, max); \
+ (uint32_t) tmp; \
+ })
+/**
+ * Normalize an int value to a specific range.
+ *
+ * @param i int value to normalize
+ * @param min minimal value
+ * @param max maximum value
+ * @return normalized value
+ */
+static inline int attr_const normalize_i_range(int i, int min, int max) {
+ if (i > max)
+ return max;
+ if (i < min)
+ return min;
+ return i;
+}
+
+/**
+ * Linearly interpolate from a range into another.
+ *
+ * @param a,b first range
+ * @param c,d second range
+ * @param value value to interpolate, should be in range [a,b]
+ * @return interpolated value in range [c,d]
+ */
+static inline int attr_const lerp_range(int a, int b, int c, int d, int value) {
+ ASSERT_IN_RANGE(value, a, b);
+ return (d-c)*(value-a)/(b-a) + c;
+}
+
+#define min2(a, b) ((a) > (b) ? (b) : (a))
+#define max2(a, b) ((a) > (b) ? (a) : (b))
+
+/// clamp `val` into interval [min, max]
+#define clamp(val, min, max) max2(min2(val, max), min)
+
+/**
+ * Normalize a double value to a specific range.
+ *
+ * @param d double value to normalize
+ * @param min minimal value
+ * @param max maximum value
+ * @return normalized value
+ */
+static inline double attr_const normalize_d_range(double d, double min, double max) {
+ if (d > max)
+ return max;
+ if (d < min)
+ return min;
+ return d;
+}
+
+/**
+ * Normalize a double value to 0.\ 0 - 1.\ 0.
+ *
+ * @param d double value to normalize
+ * @return normalized value
+ */
+static inline double attr_const normalize_d(double d) {
+ return normalize_d_range(d, 0.0, 1.0);
+}
+
+/**
+ * Convert a hex RGB string to RGB
+ */
+static inline struct color hex_to_rgb(const char *hex) {
+ struct color rgb;
+ // Ignore the # in front of the string
+ const char *sane_hex = hex + 1;
+ int hex_color = (int)strtol(sane_hex, NULL, 16);
+ rgb.red = (float)(hex_color >> 16) / 256;
+ rgb.green = (float)((hex_color & 0x00ff00) >> 8) / 256;
+ rgb.blue = (float)(hex_color & 0x0000ff) / 256;
+
+ return rgb;
+}
+
+attr_noret void
+report_allocation_failure(const char *func, const char *file, unsigned int line);
+
+/**
+ * @brief Quit if the passed-in pointer is empty.
+ */
+static inline void *
+allocchk_(const char *func_name, const char *file, unsigned int line, void *ptr) {
+ if (unlikely(!ptr)) {
+ report_allocation_failure(func_name, file, line);
+ }
+ return ptr;
+}
+
+/// @brief Wrapper of allocchk_().
+#define allocchk(ptr) allocchk_(__func__, __FILE__, __LINE__, ptr)
+
+/// @brief Wrapper of malloc().
+#define cmalloc(type) ((type *)allocchk(malloc(sizeof(type))))
+
+/// @brief Wrapper of malloc() that takes a size
+#define cvalloc(size) allocchk(malloc(size))
+
+/// @brief Wrapper of calloc().
+#define ccalloc(nmemb, type) \
+ ({ \
+ auto tmp = (nmemb); \
+ ASSERT_GEQ(tmp, 0); \
+ ((type *)allocchk(calloc((size_t)tmp, sizeof(type)))); \
+ })
+
+/// @brief Wrapper of ealloc().
+#define crealloc(ptr, nmemb) \
+ ({ \
+ auto tmp = (nmemb); \
+ ASSERT_GEQ(tmp, 0); \
+ ((__typeof__(ptr))allocchk(realloc((ptr), (size_t)tmp * sizeof(*(ptr))))); \
+ })
+
+/// RC_TYPE generates a reference counted type from `type`
+///
+/// parameters:
+/// name = the generated type will be called `name`_t.
+/// ctor = the constructor of `type`, will be called when
+/// a value of `type` is created. should take one
+/// argument of `type *`.
+/// dtor = the destructor. will be called when all reference
+/// is gone. has same signature as ctor
+/// Q = function qualifier. this is the qualifier that
+/// will be put before generated functions
+//
+/// functions generated:
+/// `name`_new: create a new reference counted object of `type`
+/// `name`_ref: increment the reference counter, return a
+/// reference to the object
+/// `name`_unref: decrement the reference counter. take a `type **`
+/// because it needs to nullify the reference.
+#define RC_TYPE(type, name, ctor, dtor, Q) \
+ typedef struct { \
+ type inner; \
+ int ref_count; \
+ } name##_internal_t; \
+ typedef type name##_t; \
+ Q type *name##_new(void) { \
+ name##_internal_t *ret = cmalloc(name##_internal_t); \
+ ctor((type *)ret); \
+ ret->ref_count = 1; \
+ return (type *)ret; \
+ } \
+ Q type *name##_ref(type *a) { \
+ __auto_type b = (name##_internal_t *)a; \
+ b->ref_count++; \
+ return a; \
+ } \
+ Q void name##_unref(type **a) { \
+ __auto_type b = (name##_internal_t *)*a; \
+ if (!b) \
+ return; \
+ b->ref_count--; \
+ if (!b->ref_count) { \
+ dtor((type *)b); \
+ free(b); \
+ } \
+ *a = NULL; \
+ }
+
+/// Generate prototypes for functions generated by RC_TYPE
+#define RC_TYPE_PROTO(type, name) \
+ typedef type name##_t; \
+ type *name##_new(void); \
+ void name##_ref(type *a); \
+ void name##_unref(type **a);
+
+
+///
+/// Calculates next closest power of two of 32bit integer n
+/// ref: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
+///
+int next_power_of_two(int n);
+
+
+// vim: set noet sw=8 ts=8 :
diff --git a/src/vsync.c b/src/vsync.c
new file mode 100644
index 0000000..5980155
--- /dev/null
+++ b/src/vsync.c
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) Yuxuan Shui <[email protected]>
+
+/// Function pointers to init VSync modes.
+
+#include "common.h"
+#include "log.h"
+
+#ifdef CONFIG_OPENGL
+#include "backend/gl/glx.h"
+#include "opengl.h"
+#endif
+
+#ifdef CONFIG_VSYNC_DRM
+#include <drm.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#endif
+
+#include "config.h"
+#include "vsync.h"
+
+#ifdef CONFIG_VSYNC_DRM
+/**
+ * Wait for next VSync, DRM method.
+ *
+ * Stolen from:
+ * https://github.com/MythTV/mythtv/blob/master/mythtv/libs/libmythtv/vsync.cpp
+ */
+static int vsync_drm_wait(session_t *ps) {
+ int ret = -1;
+ drm_wait_vblank_t vbl;
+
+ vbl.request.type = _DRM_VBLANK_RELATIVE, vbl.request.sequence = 1;
+
+ do {
+ ret = ioctl(ps->drm_fd, DRM_IOCTL_WAIT_VBLANK, &vbl);
+ vbl.request.type &= ~(uint)_DRM_VBLANK_RELATIVE;
+ } while (ret && errno == EINTR);
+
+ if (ret)
+ log_error("VBlank ioctl did not work, unimplemented in this drmver?");
+
+ return ret;
+}
+
+/**
+ * Initialize DRM VSync.
+ *
+ * @return true for success, false otherwise
+ */
+static bool vsync_drm_init(session_t *ps) {
+ // Should we always open card0?
+ if (ps->drm_fd < 0 && (ps->drm_fd = open("/dev/dri/card0", O_RDWR)) < 0) {
+ log_error("Failed to open device.");
+ return false;
+ }
+
+ if (vsync_drm_wait(ps))
+ return false;
+
+ return true;
+}
+#endif
+
+#ifdef CONFIG_OPENGL
+/**
+ * Initialize OpenGL VSync.
+ *
+ * Stolen from:
+ * http://git.tuxfamily.org/?p=ccm/cairocompmgr.git;a=commitdiff;h=efa4ceb97da501e8630ca7f12c99b1dce853c73e
+ * Possible original source: http://www.inb.uni-luebeck.de/~boehme/xvideo_sync.html
+ *
+ * @return true for success, false otherwise
+ */
+static bool vsync_opengl_init(session_t *ps) {
+ if (!ensure_glx_context(ps))
+ return false;
+
+ return glxext.has_GLX_SGI_video_sync;
+}
+
+static bool vsync_opengl_oml_init(session_t *ps) {
+ if (!ensure_glx_context(ps))
+ return false;
+
+ return glxext.has_GLX_OML_sync_control;
+}
+
+static inline bool vsync_opengl_swc_swap_interval(session_t *ps, int interval) {
+ if (glxext.has_GLX_MESA_swap_control)
+ return glXSwapIntervalMESA((uint)interval) == 0;
+ else if (glxext.has_GLX_SGI_swap_control)
+ return glXSwapIntervalSGI(interval) == 0;
+ else if (glxext.has_GLX_EXT_swap_control) {
+ GLXDrawable d = glXGetCurrentDrawable();
+ if (d == None) {
+ // We don't have a context??
+ return false;
+ }
+ glXSwapIntervalEXT(ps->dpy, glXGetCurrentDrawable(), interval);
+ return true;
+ }
+ return false;
+}
+
+static bool vsync_opengl_swc_init(session_t *ps) {
+ if (!bkend_use_glx(ps)) {
+ log_error("OpenGL swap control requires the GLX backend.");
+ return false;
+ }
+
+ if (!vsync_opengl_swc_swap_interval(ps, 1)) {
+ log_error("Failed to load a swap control extension.");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Wait for next VSync, OpenGL method.
+ */
+static int vsync_opengl_wait(session_t *ps attr_unused) {
+ unsigned vblank_count = 0;
+
+ glXGetVideoSyncSGI(&vblank_count);
+ glXWaitVideoSyncSGI(2, (vblank_count + 1) % 2, &vblank_count);
+ return 0;
+}
+
+/**
+ * Wait for next VSync, OpenGL OML method.
+ *
+ * https://mail.gnome.org/archives/clutter-list/2012-November/msg00031.html
+ */
+static int vsync_opengl_oml_wait(session_t *ps) {
+ int64_t ust = 0, msc = 0, sbc = 0;
+
+ glXGetSyncValuesOML(ps->dpy, ps->reg_win, &ust, &msc, &sbc);
+ glXWaitForMscOML(ps->dpy, ps->reg_win, 0, 2, (msc + 1) % 2, &ust, &msc, &sbc);
+ return 0;
+}
+#endif
+
+/**
+ * Initialize current VSync method.
+ */
+bool vsync_init(session_t *ps) {
+#ifdef CONFIG_OPENGL
+ if (bkend_use_glx(ps)) {
+ // Mesa turns on swap control by default, undo that
+ vsync_opengl_swc_swap_interval(ps, 0);
+ }
+#endif
+#ifdef CONFIG_VSYNC_DRM
+ log_warn("The DRM vsync method is deprecated, please don't enable it.");
+#endif
+
+ if (!ps->o.vsync) {
+ return true;
+ }
+
+#ifdef CONFIG_OPENGL
+ if (bkend_use_glx(ps)) {
+ if (!vsync_opengl_swc_init(ps)) {
+ return false;
+ }
+ ps->vsync_wait = NULL; // glXSwapBuffers will automatically wait
+ // for vsync, we don't need to do anything.
+ return true;
+ }
+#endif
+
+ // Oh no, we are not using glx backend.
+ // Throwing things at wall.
+#ifdef CONFIG_OPENGL
+ if (vsync_opengl_oml_init(ps)) {
+ log_info("Using the opengl-oml vsync method");
+ ps->vsync_wait = vsync_opengl_oml_wait;
+ return true;
+ }
+
+ if (vsync_opengl_init(ps)) {
+ log_info("Using the opengl vsync method");
+ ps->vsync_wait = vsync_opengl_wait;
+ return true;
+ }
+#endif
+
+#ifdef CONFIG_VSYNC_DRM
+ if (vsync_drm_init(ps)) {
+ log_info("Using the drm vsync method");
+ ps->vsync_wait = vsync_drm_wait;
+ return true;
+ }
+#endif
+
+ log_error("No supported vsync method found for this backend");
+ return false;
+}
diff --git a/src/vsync.h b/src/vsync.h
new file mode 100644
index 0000000..076bc26
--- /dev/null
+++ b/src/vsync.h
@@ -0,0 +1,7 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) Yuxuan Shui <[email protected]>
+#include <stdbool.h>
+
+typedef struct session session_t;
+
+bool vsync_init(session_t *ps);
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(&reg_win, (rect_t[]){{0, 0, outer_width, outer_height}}, 1);
+ pixman_region32_intersect(res, &reg_win, res);
+ pixman_region32_fini(&reg_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);
+}
diff --git a/src/win.h b/src/win.h
new file mode 100644
index 0000000..d3a74f9
--- /dev/null
+++ b/src/win.h
@@ -0,0 +1,531 @@
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2011-2013, Christopher Jeffrey
+// Copyright (c) 2013 Richard Grenville <[email protected]>
+#pragma once
+#include <stdbool.h>
+#include <xcb/damage.h>
+#include <xcb/render.h>
+#include <xcb/xcb.h>
+
+#include "uthash_extra.h"
+
+// FIXME shouldn't need this
+#ifdef CONFIG_OPENGL
+#include <GL/gl.h>
+#endif
+
+#include "c2.h"
+#include "compiler.h"
+#include "list.h"
+#include "region.h"
+#include "render.h"
+#include "types.h"
+#include "utils.h"
+#include "win_defs.h"
+#include "x.h"
+
+struct backend_base;
+typedef struct session session_t;
+typedef struct _glx_texture glx_texture_t;
+
+#define win_stack_foreach_managed(w, win_stack) \
+ list_foreach(struct managed_win, w, win_stack, base.stack_neighbour) if (w->base.managed)
+
+#define win_stack_foreach_managed_safe(w, win_stack) \
+ list_foreach_safe(struct managed_win, w, win_stack, \
+ base.stack_neighbour) if (w->base.managed)
+
+#ifdef CONFIG_OPENGL
+// FIXME this type should be in opengl.h
+// it is very unideal for it to be here
+typedef struct {
+ /// Framebuffer used for blurring.
+ GLuint fbo;
+ /// Textures used for blurring.
+ GLuint textures[2];
+ /// Width of the textures.
+ int width;
+ /// Height of the textures.
+ int height;
+} glx_blur_cache_t;
+#endif
+
+/// An entry in the window stack. May or may not correspond to a window we know about.
+struct window_stack_entry {
+ struct list_node stack_neighbour;
+ /// The actual window correspond to this stack entry. NULL if we didn't know about
+ /// this window (e.g. an InputOnly window, or we haven't handled the window
+ /// creation yet)
+ struct win *win;
+ /// The window id. Might not be unique in the stack, because there might be
+ /// destroyed window still fading out in the stack.
+ xcb_window_t id;
+};
+
+/**
+ * About coordinate systems
+ *
+ * In general, X is the horizontal axis, Y is the vertical axis.
+ * X goes from left to right, Y goes downwards.
+ *
+ * Global: the origin is the top left corner of the Xorg screen.
+ * Local: the origin is the top left corner of the window, border is
+ * considered part of the window.
+ */
+
+/// Structure representing a top-level managed window.
+typedef struct win win;
+struct win {
+ UT_hash_handle hh;
+ struct list_node stack_neighbour;
+ /// ID of the top-level frame window.
+ xcb_window_t id;
+ /// Whether the window is destroyed from Xorg's perspective
+ bool destroyed : 1;
+ /// True if we just received CreateNotify, and haven't queried X for any info
+ /// about the window
+ bool is_new : 1;
+ /// True if this window is managed, i.e. this struct is actually a `managed_win`.
+ /// Always false if `is_new` is true.
+ bool managed : 1;
+};
+
+struct win_geometry {
+ int16_t x;
+ int16_t y;
+ uint16_t width;
+ uint16_t height;
+ uint16_t border_width;
+};
+
+struct managed_win {
+ struct win base;
+ /// backend data attached to this window. Only available when
+ /// `state` is not UNMAPPED
+ void *win_image;
+ void *old_win_image; // Old window image for interpolating window contents during animations
+ void *shadow_image;
+ /// Pointer to the next higher window to paint.
+ struct managed_win *prev_trans;
+ /// Number of windows above this window
+ int stacking_rank;
+ // TODO(yshui) rethink reg_ignore
+
+ // Core members
+ /// The "mapped state" of this window, doesn't necessary
+ /// match X mapped state, because of fading.
+ winstate_t state;
+ /// Window attributes.
+ xcb_get_window_attributes_reply_t a;
+ /// The geometry of the window body, excluding the window border region.
+ struct win_geometry g;
+ /// Updated geometry received in events
+ struct win_geometry pending_g;
+ /// Xinerama screen this window is on.
+ int xinerama_scr;
+ /// Window visual pict format
+ const xcb_render_pictforminfo_t *pictfmt;
+ /// Client window visual pict format
+ const xcb_render_pictforminfo_t *client_pictfmt;
+ /// Window painting mode.
+ winmode_t mode;
+ /// Whether the window has been damaged at least once.
+ bool ever_damaged;
+ /// Whether the window was damaged after last paint.
+ bool pixmap_damaged;
+ /// Damage of the window.
+ xcb_damage_damage_t damage;
+ /// Paint info of the window.
+ paint_t paint;
+ /// bitmap for properties which needs to be updated
+ uint64_t *stale_props;
+ /// number of uint64_ts that has been allocated for stale_props
+ uint64_t stale_props_capacity;
+
+ /// Bounding shape of the window. In local coordinates.
+ /// See above about coordinate systems.
+ region_t bounding_shape;
+ /// Window flags. Definitions above.
+ uint64_t flags;
+ /// The region of screen that will be obscured when windows above is painted,
+ /// in global coordinates.
+ /// We use this to reduce the pixels that needed to be paint when painting
+ /// this window and anything underneath. Depends on window frame
+ /// opacity state, window geometry, window mapped/unmapped state,
+ /// window mode of the windows above. DOES NOT INCLUDE the body of THIS WINDOW.
+ /// NULL means reg_ignore has not been calculated for this window.
+ rc_region_t *reg_ignore;
+ /// Whether the reg_ignore of all windows beneath this window are valid
+ bool reg_ignore_valid;
+ /// Cached width/height of the window including border.
+ int widthb, heightb;
+ /// Whether the window is bounding-shaped.
+ bool bounding_shaped;
+ /// Whether the window just have rounded corners.
+ bool rounded_corners;
+ /// Whether this window is to be painted.
+ bool to_paint;
+ /// Whether the window is painting excluded.
+ bool paint_excluded;
+ /// Whether the window is unredirect-if-possible excluded.
+ bool unredir_if_possible_excluded;
+ /// Whether this window is in open/close state.
+ bool in_openclose;
+ /// Whether this window was transient when animated on open
+ bool animation_transient;
+ /// Current position and destination, for animation
+ double animation_center_x, animation_center_y;
+ double animation_dest_center_x, animation_dest_center_y;
+ double animation_w, animation_h;
+ double animation_dest_w, animation_dest_h;
+ /// Spring animation velocity
+ double animation_velocity_x, animation_velocity_y;
+ double animation_velocity_w, animation_velocity_h;
+ /// Track animation progress; goes from 0 to 1
+ double animation_progress;
+ /// Inverse of the window distance at the start of animation, for
+ /// tracking animation progress
+ double animation_inv_og_distance;
+
+ // Client window related members
+ /// ID of the top-level client window of the window.
+ xcb_window_t client_win;
+ /// Type of the window.
+ wintype_t window_type;
+ /// Whether it looks like a WM window. We consider a window WM window if
+ /// it does not have a decedent with WM_STATE and it is not override-
+ /// redirected itself.
+ bool wmwin;
+ /// Leader window ID of the window.
+ xcb_window_t leader;
+ /// Cached topmost window ID of the window.
+ xcb_window_t cache_leader;
+
+ // Focus-related members
+ /// Whether the window is to be considered focused.
+ bool focused;
+ /// Override value of window focus state. Set by D-Bus method calls.
+ switch_t focused_force;
+
+ // Blacklist related members
+ /// Name of the window.
+ char *name;
+ /// Window instance class of the window.
+ char *class_instance;
+ /// Window general class of the window.
+ char *class_general;
+ /// <code>WM_WINDOW_ROLE</code> value of the window.
+ char *role;
+
+ // Opacity-related members
+ /// Current window opacity.
+ double opacity;
+ /// Target window opacity.
+ double opacity_target;
+ /// Previous window opacity.
+ double opacity_target_old;
+ /// true if window (or client window, for broken window managers
+ /// not transferring client window's _NET_WM_OPACITY value) has opacity prop
+ bool has_opacity_prop;
+ /// Cached value of opacity window attribute.
+ opacity_t opacity_prop;
+ /// true if opacity is set by some rules
+ bool opacity_is_set;
+ /// Last window opacity value set by the rules.
+ double opacity_set;
+
+ /// Radius of rounded window corners
+ int corner_radius;
+ float border_col[4];
+
+ // Fading-related members
+ /// Override value of window fade state. Set by D-Bus method calls.
+ switch_t fade_force;
+ /// Whether fading is excluded by the rules. Calculated.
+ bool fade_excluded;
+
+ // Frame-opacity-related members
+ /// Current window frame opacity. Affected by window opacity.
+ double frame_opacity;
+ /// Frame extents. Acquired from _NET_FRAME_EXTENTS.
+ margin_t frame_extents;
+
+ // Shadow-related members
+ /// Whether a window has shadow. Calculated.
+ bool shadow;
+ /// Override value of window shadow state. Set by D-Bus method calls.
+ switch_t shadow_force;
+ /// Opacity of the shadow. Affected by window opacity and frame opacity.
+ double shadow_opacity;
+ /// X offset of shadow. Affected by commandline argument.
+ int shadow_dx;
+ /// Y offset of shadow. Affected by commandline argument.
+ int shadow_dy;
+ /// Width of shadow. Affected by window size and commandline argument.
+ int shadow_width;
+ /// Height of shadow. Affected by window size and commandline argument.
+ int shadow_height;
+ /// Picture to render shadow. Affected by window size.
+ paint_t shadow_paint;
+ /// The value of _COMPTON_SHADOW attribute of the window. Below 0 for
+ /// none.
+ long prop_shadow;
+ /// Do not paint shadow over this window.
+ bool clip_shadow_above;
+
+ // Dim-related members
+ /// Whether the window is to be dimmed.
+ bool dim;
+
+ /// Whether to invert window color.
+ bool invert_color;
+ /// Override value of window color inversion state. Set by D-Bus method
+ /// calls.
+ switch_t invert_color_force;
+
+ /// Whether to blur window background.
+ bool blur_background;
+
+#ifdef CONFIG_OPENGL
+ /// Textures and FBO background blur use.
+ glx_blur_cache_t glx_blur_cache;
+ /// Background texture of the window
+ glx_texture_t *glx_texture_bg;
+#endif
+};
+
+/// Process pending updates/images flags on a window. Has to be called in X critical
+/// section
+void win_process_update_flags(session_t *ps, struct managed_win *w);
+void win_process_image_flags(session_t *ps, struct managed_win *w);
+/// Bind a shadow to the window, with color `c` and shadow kernel `kernel`
+bool win_bind_shadow(struct backend_base *b, struct managed_win *w, struct color c,
+ struct conv *kernel);
+
+/// Start the unmap of a window. We cannot unmap immediately since we might need to fade
+/// the window out.
+void unmap_win_start(struct session *, struct managed_win *);
+
+/// Start the mapping of a window. We cannot map immediately since we might need to fade
+/// the window in.
+void map_win_start(struct session *, struct managed_win *);
+
+/// Start the destroying of a window. Windows cannot always be destroyed immediately
+/// because of fading and such.
+bool must_use destroy_win_start(session_t *ps, struct win *w);
+
+/// Release images bound with a window, set the *_NONE flags on the window. Only to be
+/// used when de-initializing the backend outside of win.c
+void win_release_images(struct backend_base *base, struct managed_win *w);
+winmode_t attr_pure win_calc_mode(const struct managed_win *w);
+void win_set_shadow_force(session_t *ps, struct managed_win *w, switch_t val);
+void win_set_fade_force(struct managed_win *w, switch_t val);
+void win_set_focused_force(session_t *ps, struct managed_win *w, switch_t val);
+void win_set_invert_color_force(session_t *ps, struct managed_win *w, switch_t val);
+/**
+ * Set real focused state of a window.
+ */
+void win_set_focused(session_t *ps, struct managed_win *w);
+bool attr_pure win_should_fade(session_t *ps, const struct managed_win *w);
+void win_on_factor_change(session_t *ps, struct managed_win *w);
+/**
+ * Update cache data in struct _win that depends on window size.
+ */
+void win_on_win_size_change(session_t *ps, struct managed_win *w);
+void win_unmark_client(session_t *ps, struct managed_win *w);
+void win_recheck_client(session_t *ps, struct managed_win *w);
+
+/**
+ * Calculate and return the opacity target of a window.
+ *
+ * The priority of opacity settings are:
+ *
+ * inactive_opacity_override (if set, and unfocused) > _NET_WM_WINDOW_OPACITY (if set) >
+ * opacity-rules (if matched) > window type default opacity > active/inactive opacity
+ *
+ * @param ps current session
+ * @param w struct _win object representing the window
+ *
+ * @return target opacity
+ */
+double attr_pure win_calc_opacity_target(session_t *ps, const struct managed_win *w);
+bool attr_pure win_should_dim(session_t *ps, const struct managed_win *w);
+void win_update_screen(int nscreens, region_t *screens, struct managed_win *w);
+/**
+ * Retrieve the bounding shape of a window.
+ */
+// XXX was win_border_size
+void win_update_bounding_shape(session_t *ps, struct managed_win *w);
+/**
+ * Check if a window has BYPASS_COMPOSITOR property set
+ */
+bool win_is_bypassing_compositor(const session_t *ps, const struct managed_win *w);
+/**
+ * Get a rectangular region in global coordinates 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);
+region_t win_extents_by_val(const struct managed_win *w);
+/**
+ * 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);
+/**
+ * Get a rectangular region a window occupies, excluding frame and shadow.
+ *
+ * Return region in global coordinates.
+ */
+void win_get_region_noframe_local(const struct managed_win *w, region_t *);
+void win_get_region_noframe_local_without_corners(const struct managed_win *w, region_t *);
+
+/// Get the region for the frame of the window
+void win_get_region_frame_local(const struct managed_win *w, region_t *res);
+/// Get the region for the frame of the window, by value
+region_t win_get_region_frame_local_by_val(const struct managed_win *w);
+/// Insert a new window above window with id `below`, if there is no window, add to top
+/// New window will be in unmapped state
+struct win *add_win_above(session_t *ps, xcb_window_t id, xcb_window_t below);
+/// Insert a new win entry at the top of the stack
+struct win *add_win_top(session_t *ps, xcb_window_t id);
+/// Query the Xorg for information about window `win`
+/// `win` pointer might become invalid after this function returns
+struct win *fill_win(session_t *ps, struct win *win);
+/// Move window `w` to be right above `below`
+void restack_above(session_t *ps, struct win *w, xcb_window_t below);
+/// Move window `w` to the bottom of the stack
+void restack_bottom(session_t *ps, struct win *w);
+/// Move window `w` to the top of the stack
+void restack_top(session_t *ps, struct win *w);
+
+/**
+ * Execute fade callback of a window if fading finished.
+ */
+bool must_use win_check_fade_finished(session_t *ps, struct managed_win *w);
+
+// Stop receiving events (except ConfigureNotify, XXX why?) from a window
+void win_ev_stop(session_t *ps, const struct win *w);
+
+/// 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 must_use win_skip_fading(session_t *ps, struct managed_win *w);
+/**
+ * Find a managed window from window id in window linked list of the session.
+ */
+struct managed_win *find_managed_win(session_t *ps, xcb_window_t id);
+struct win *find_win(session_t *ps, xcb_window_t id);
+struct managed_win *find_toplevel(session_t *ps, xcb_window_t id);
+/**
+ * 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);
+
+/**
+ * Check if a window is a fullscreen window.
+ *
+ * It's not using w->border_size for performance measures.
+ */
+bool attr_pure win_is_fullscreen(const session_t *ps, const struct managed_win *w);
+
+/**
+ * Check if a window is focused, without using any focus rules or forced focus settings
+ */
+bool attr_pure win_is_focused_raw(const session_t *ps, const struct managed_win *w);
+
+/// check if window has ARGB visual
+bool attr_pure win_has_alpha(const struct managed_win *w);
+
+/// check if reg_ignore_valid is true for all windows above us
+bool attr_pure win_is_region_ignore_valid(session_t *ps, const struct managed_win *w);
+
+/// Whether a given window is mapped on the X server side
+bool win_is_mapped_in_x(const struct managed_win *w);
+
+// Find the managed window immediately below `w` in the window stack
+struct managed_win *attr_pure win_stack_find_next_managed(const session_t *ps,
+ const struct list_node *w);
+/// Set flags on a window. Some sanity checks are performed
+void win_set_flags(struct managed_win *w, uint64_t flags);
+/// Clear flags on a window. Some sanity checks are performed
+void win_clear_flags(struct managed_win *w, uint64_t flags);
+/// Returns true if any of the flags in `flags` is set
+bool win_check_flags_any(struct managed_win *w, uint64_t flags);
+/// Returns true if all of the flags in `flags` are set
+bool win_check_flags_all(struct managed_win *w, uint64_t flags);
+/// Mark properties as stale for a window
+void win_set_properties_stale(struct managed_win *w, const xcb_atom_t *prop, int nprops);
+
+static inline attr_unused void win_set_property_stale(struct managed_win *w, xcb_atom_t prop) {
+ return win_set_properties_stale(w, (xcb_atom_t[]){prop}, 1);
+}
+
+/// Free all resources in a struct win
+void free_win_res(session_t *ps, struct managed_win *w);
+
+static inline void win_region_remove_corners(const struct managed_win *w, region_t *res) {
+ region_t corners;
+ pixman_region32_init_rects(
+ &corners,
+ (rect_t[]){
+ {.x1 = 0, .y1 = 0, .x2 = w->corner_radius, .y2 = w->corner_radius},
+ {.x1 = 0, .y1 = w->heightb - w->corner_radius, .x2 = w->corner_radius, .y2 = w->heightb},
+ {.x1 = w->widthb - w->corner_radius, .y1 = 0, .x2 = w->widthb, .y2 = w->corner_radius},
+ {.x1 = w->widthb - w->corner_radius,
+ .y1 = w->heightb - w->corner_radius,
+ .x2 = w->widthb,
+ .y2 = w->heightb},
+ },
+ 4);
+ pixman_region32_subtract(res, res, &corners);
+ pixman_region32_fini(&corners);
+}
+
+static inline region_t attr_unused win_get_bounding_shape_global_by_val(struct managed_win *w) {
+ region_t ret;
+ pixman_region32_init(&ret);
+ pixman_region32_copy(&ret, &w->bounding_shape);
+ pixman_region32_translate(&ret, w->g.x, w->g.y);
+ return ret;
+}
+
+static inline region_t
+win_get_bounding_shape_global_without_corners_by_val(struct managed_win *w) {
+ region_t ret;
+ pixman_region32_init(&ret);
+ pixman_region32_copy(&ret, &w->bounding_shape);
+ win_region_remove_corners(w, &ret);
+ pixman_region32_translate(&ret, w->g.x, w->g.y);
+ return ret;
+}
+
+/**
+ * Calculate the extents of the frame of the given window based on EWMH
+ * _NET_FRAME_EXTENTS and the X window border width.
+ */
+static inline margin_t attr_pure attr_unused win_calc_frame_extents(const struct managed_win *w) {
+ margin_t result = w->frame_extents;
+ result.top = max2(result.top, w->g.border_width);
+ result.left = max2(result.left, w->g.border_width);
+ result.bottom = max2(result.bottom, w->g.border_width);
+ result.right = max2(result.right, w->g.border_width);
+ return result;
+}
+
+/**
+ * Check whether a window has WM frames.
+ */
+static inline bool attr_pure attr_unused win_has_frame(const struct managed_win *w) {
+ return w->g.border_width || w->frame_extents.top || w->frame_extents.left ||
+ w->frame_extents.right || w->frame_extents.bottom;
+}
diff --git a/src/win_defs.h b/src/win_defs.h
new file mode 100644
index 0000000..e032bc7
--- /dev/null
+++ b/src/win_defs.h
@@ -0,0 +1,102 @@
+#pragma once
+#include <stdint.h>
+
+typedef enum {
+ WINTYPE_UNKNOWN,
+ WINTYPE_DESKTOP,
+ WINTYPE_DOCK,
+ WINTYPE_TOOLBAR,
+ WINTYPE_MENU,
+ WINTYPE_UTILITY,
+ WINTYPE_SPLASH,
+ WINTYPE_DIALOG,
+ WINTYPE_NORMAL,
+ WINTYPE_DROPDOWN_MENU,
+ WINTYPE_POPUP_MENU,
+ WINTYPE_TOOLTIP,
+ WINTYPE_NOTIFICATION,
+ WINTYPE_COMBO,
+ WINTYPE_DND,
+ NUM_WINTYPES
+} wintype_t;
+
+/// Enumeration type of window painting mode.
+typedef enum {
+ WMODE_TRANS, // The window body is (potentially) transparent
+ WMODE_FRAME_TRANS, // The window body is opaque, but the frame is not
+ WMODE_SOLID, // The window is opaque including the frame
+} winmode_t;
+
+/// Transition table:
+/// (DESTROYED is when the win struct is destroyed and freed)
+/// ('o' means in all other cases)
+/// (Window is created in the UNMAPPED state)
+/// +-------------+---------+----------+-------+-------+--------+--------+---------+
+/// | |UNMAPPING|DESTROYING|MAPPING|FADING |UNMAPPED| MAPPED |DESTROYED|
+/// +-------------+---------+----------+-------+-------+--------+--------+---------+
+/// | UNMAPPING | o | Window |Window | - | Fading | - | - |
+/// | | |destroyed |mapped | |finished| | |
+/// +-------------+---------+----------+-------+-------+--------+--------+---------+
+/// | DESTROYING | - | o | - | - | - | - | Fading |
+/// | | | | | | | |finished |
+/// +-------------+---------+----------+-------+-------+--------+--------+---------+
+/// | MAPPING | Window | Window | o |Opacity| - | Fading | - |
+/// | |unmapped |destroyed | |change | |finished| |
+/// +-------------+---------+----------+-------+-------+--------+--------+---------+
+/// | FADING | Window | Window | - | o | - | Fading | - |
+/// | |unmapped |destroyed | | | |finished| |
+/// +-------------+---------+----------+-------+-------+--------+--------+---------+
+/// | UNMAPPED | - | - |Window | - | o | - | Window |
+/// | | | |mapped | | | |destroyed|
+/// +-------------+---------+----------+-------+-------+--------+--------+---------+
+/// | MAPPED | Window | Window | - |Opacity| - | o | - |
+/// | |unmapped |destroyed | |change | | | |
+/// +-------------+---------+----------+-------+-------+--------+--------+---------+
+typedef enum {
+ // The window is being faded out because it's unmapped.
+ WSTATE_UNMAPPING,
+ // The window is being faded out because it's destroyed,
+ WSTATE_DESTROYING,
+ // The window is being faded in
+ WSTATE_MAPPING,
+ // Window opacity is not at the target level
+ WSTATE_FADING,
+ // The window is mapped, no fading is in progress.
+ WSTATE_MAPPED,
+ // The window is unmapped, no fading is in progress.
+ WSTATE_UNMAPPED,
+} winstate_t;
+
+enum win_flags {
+ // Note: *_NONE flags are mostly redudant and meant for detecting logical errors
+ // in the code
+
+ /// pixmap is out of date, will be update in win_process_flags
+ WIN_FLAGS_PIXMAP_STALE = 1,
+ /// window does not have pixmap bound
+ WIN_FLAGS_PIXMAP_NONE = 2,
+ /// there was an error trying to bind the images
+ WIN_FLAGS_IMAGE_ERROR = 4,
+ /// shadow is out of date, will be updated in win_process_flags
+ WIN_FLAGS_SHADOW_STALE = 8,
+ /// shadow has not been generated
+ WIN_FLAGS_SHADOW_NONE = 16,
+ /// the client window needs to be updated
+ WIN_FLAGS_CLIENT_STALE = 32,
+ /// the window is mapped by X, we need to call map_win_start for it
+ WIN_FLAGS_MAPPED = 64,
+ /// this window has properties which needs to be updated
+ WIN_FLAGS_PROPERTY_STALE = 128,
+ // TODO(yshui) _maybe_ split SIZE_STALE into SIZE_STALE and SHAPE_STALE
+ /// this window has an unhandled size/shape change
+ WIN_FLAGS_SIZE_STALE = 256,
+ /// this window has an unhandled position (i.e. x and y) change
+ WIN_FLAGS_POSITION_STALE = 512,
+ /// need better name for this, is set when some aspects of the window changed
+ WIN_FLAGS_FACTOR_CHANGED = 1024,
+};
+
+static const uint64_t WIN_FLAGS_IMAGES_STALE =
+ WIN_FLAGS_PIXMAP_STALE | WIN_FLAGS_SHADOW_STALE;
+
+#define WIN_FLAGS_IMAGES_NONE (WIN_FLAGS_PIXMAP_NONE | WIN_FLAGS_SHADOW_NONE)
diff --git a/src/x.c b/src/x.c
new file mode 100644
index 0000000..c146f48
--- /dev/null
+++ b/src/x.c
@@ -0,0 +1,748 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) 2018 Yuxuan Shui <[email protected]>
+#include <stdalign.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <X11/Xutil.h>
+#include <pixman.h>
+#include <xcb/composite.h>
+#include <xcb/damage.h>
+#include <xcb/glx.h>
+#include <xcb/render.h>
+#include <xcb/sync.h>
+#include <xcb/xcb.h>
+#include <xcb/xcb_renderutil.h>
+#include <xcb/xfixes.h>
+
+#include "atom.h"
+#ifdef CONFIG_OPENGL
+#include "backend/gl/glx.h"
+#endif
+#include "common.h"
+#include "compiler.h"
+#include "kernel.h"
+#include "log.h"
+#include "region.h"
+#include "utils.h"
+#include "x.h"
+
+/**
+ * Get a specific attribute of a window.
+ *
+ * Returns a blank structure if the returned type and format does not
+ * match the requested type and format.
+ *
+ * @param ps current session
+ * @param w window
+ * @param atom atom of attribute to fetch
+ * @param length length to read
+ * @param rtype atom of the requested type
+ * @param rformat requested format
+ * @return a <code>winprop_t</code> structure containing the attribute
+ * and number of items. A blank one on failure.
+ */
+winprop_t x_get_prop_with_offset(xcb_connection_t *c, xcb_window_t w, xcb_atom_t atom,
+ int offset, int length, xcb_atom_t rtype, int rformat) {
+ xcb_get_property_reply_t *r = xcb_get_property_reply(
+ c,
+ xcb_get_property(c, 0, w, atom, rtype, to_u32_checked(offset),
+ to_u32_checked(length)),
+ NULL);
+
+ if (r && xcb_get_property_value_length(r) &&
+ (rtype == XCB_GET_PROPERTY_TYPE_ANY || r->type == rtype) &&
+ (!rformat || r->format == rformat) &&
+ (r->format == 8 || r->format == 16 || r->format == 32)) {
+ auto len = xcb_get_property_value_length(r);
+ return (winprop_t){
+ .ptr = xcb_get_property_value(r),
+ .nitems = (ulong)(len / (r->format / 8)),
+ .type = r->type,
+ .format = r->format,
+ .r = r,
+ };
+ }
+
+ free(r);
+ return (winprop_t){
+ .ptr = NULL, .nitems = 0, .type = XCB_GET_PROPERTY_TYPE_ANY, .format = 0};
+}
+
+/// Get the type, format and size in bytes of a window's specific attribute.
+winprop_info_t x_get_prop_info(xcb_connection_t *c, xcb_window_t w, xcb_atom_t atom) {
+ xcb_generic_error_t *e = NULL;
+ auto r = xcb_get_property_reply(
+ c, xcb_get_property(c, 0, w, atom, XCB_ATOM_ANY, 0, 0), &e);
+ if (!r) {
+ log_debug_x_error(e, "Failed to get property info for window %#010x", w);
+ free(e);
+ return (winprop_info_t){
+ .type = XCB_GET_PROPERTY_TYPE_ANY, .format = 0, .length = 0};
+ }
+
+ winprop_info_t winprop_info = {
+ .type = r->type, .format = r->format, .length = r->bytes_after};
+ free(r);
+
+ return winprop_info;
+}
+
+/**
+ * Get the value of a type-<code>xcb_window_t</code> property of a window.
+ *
+ * @return the value if successful, 0 otherwise
+ */
+xcb_window_t wid_get_prop_window(xcb_connection_t *c, xcb_window_t wid, xcb_atom_t aprop) {
+ // Get the attribute
+ xcb_window_t p = XCB_NONE;
+ winprop_t prop = x_get_prop(c, wid, aprop, 1L, XCB_ATOM_WINDOW, 32);
+
+ // Return it
+ if (prop.nitems) {
+ p = (xcb_window_t)*prop.p32;
+ }
+
+ free_winprop(&prop);
+
+ return p;
+}
+
+/**
+ * Get the value of a text property of a window.
+ */
+bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char ***pstrlst,
+ int *pnstr) {
+ assert(ps->server_grabbed);
+ auto prop_info = x_get_prop_info(ps->c, wid, prop);
+ auto type = prop_info.type;
+ auto format = prop_info.format;
+ auto length = prop_info.length;
+
+ if (type == XCB_ATOM_NONE) {
+ return false;
+ }
+
+ if (type != XCB_ATOM_STRING && type != ps->atoms->aUTF8_STRING &&
+ type != ps->atoms->aC_STRING) {
+ log_warn("Text property %d of window %#010x has unsupported type: %d",
+ prop, wid, type);
+ return false;
+ }
+
+ if (format != 8) {
+ log_warn("Text property %d of window %#010x has unexpected format: %d",
+ prop, wid, format);
+ return false;
+ }
+
+ xcb_generic_error_t *e = NULL;
+ auto word_count = (length + 4 - 1) / 4;
+ auto r = xcb_get_property_reply(
+ ps->c, xcb_get_property(ps->c, 0, wid, prop, type, 0, word_count), &e);
+ if (!r) {
+ log_debug_x_error(e, "Failed to get window property for %#010x", wid);
+ free(e);
+ return false;
+ }
+
+ assert(length == (uint32_t)xcb_get_property_value_length(r));
+
+ void *data = xcb_get_property_value(r);
+ unsigned int nstr = 0;
+ uint32_t current_offset = 0;
+ while (current_offset < length) {
+ current_offset +=
+ (uint32_t)strnlen(data + current_offset, length - current_offset) + 1;
+ nstr += 1;
+ }
+
+ if (nstr == 0) {
+ // The property is set to an empty string, in that case, we return one
+ // string
+ char **strlst = malloc(sizeof(char *));
+ strlst[0] = "";
+ *pnstr = 1;
+ *pstrlst = strlst;
+ free(r);
+ return true;
+ }
+
+ // Allocate the pointers and the strings together
+ void *buf = NULL;
+ if (posix_memalign(&buf, alignof(char *), length + sizeof(char *) * nstr + 1) != 0) {
+ abort();
+ }
+
+ char *strlst = buf + sizeof(char *) * nstr;
+ memcpy(strlst, xcb_get_property_value(r), length);
+ strlst[length] = '\0'; // X strings aren't guaranteed to be null terminated
+
+ char **ret = buf;
+ current_offset = 0;
+ nstr = 0;
+ while (current_offset < length) {
+ ret[nstr] = strlst + current_offset;
+ current_offset += (uint32_t)strlen(strlst + current_offset) + 1;
+ nstr += 1;
+ }
+
+ *pnstr = to_int_checked(nstr);
+ *pstrlst = ret;
+ free(r);
+ return true;
+}
+
+// A cache of pict formats. We assume they don't change during the lifetime
+// of this program
+static thread_local xcb_render_query_pict_formats_reply_t *g_pictfmts = NULL;
+
+static inline void x_get_server_pictfmts(xcb_connection_t *c) {
+ if (g_pictfmts) {
+ return;
+ }
+ xcb_generic_error_t *e = NULL;
+ // Get window picture format
+ g_pictfmts =
+ xcb_render_query_pict_formats_reply(c, xcb_render_query_pict_formats(c), &e);
+ if (e || !g_pictfmts) {
+ log_fatal("failed to get pict formats\n");
+ abort();
+ }
+}
+
+const xcb_render_pictforminfo_t *
+x_get_pictform_for_visual(xcb_connection_t *c, xcb_visualid_t visual) {
+ x_get_server_pictfmts(c);
+
+ xcb_render_pictvisual_t *pv = xcb_render_util_find_visual_format(g_pictfmts, visual);
+ for (xcb_render_pictforminfo_iterator_t i =
+ xcb_render_query_pict_formats_formats_iterator(g_pictfmts);
+ i.rem; xcb_render_pictforminfo_next(&i)) {
+ if (i.data->id == pv->format) {
+ return i.data;
+ }
+ }
+ return NULL;
+}
+
+static xcb_visualid_t attr_pure x_get_visual_for_pictfmt(xcb_render_query_pict_formats_reply_t *r,
+ xcb_render_pictformat_t fmt) {
+ for (auto screen = xcb_render_query_pict_formats_screens_iterator(r); screen.rem;
+ xcb_render_pictscreen_next(&screen)) {
+ for (auto depth = xcb_render_pictscreen_depths_iterator(screen.data);
+ depth.rem; xcb_render_pictdepth_next(&depth)) {
+ for (auto pv = xcb_render_pictdepth_visuals_iterator(depth.data);
+ pv.rem; xcb_render_pictvisual_next(&pv)) {
+ if (pv.data->format == fmt) {
+ return pv.data->visual;
+ }
+ }
+ }
+ }
+ return XCB_NONE;
+}
+
+xcb_visualid_t x_get_visual_for_standard(xcb_connection_t *c, xcb_pict_standard_t std) {
+ x_get_server_pictfmts(c);
+
+ auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, std);
+
+ return x_get_visual_for_pictfmt(g_pictfmts, pictfmt->id);
+}
+
+xcb_render_pictformat_t
+x_get_pictfmt_for_standard(xcb_connection_t *c, xcb_pict_standard_t std) {
+ x_get_server_pictfmts(c);
+
+ auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, std);
+
+ return pictfmt->id;
+}
+
+int x_get_visual_depth(xcb_connection_t *c, xcb_visualid_t visual) {
+ auto setup = xcb_get_setup(c);
+ for (auto screen = xcb_setup_roots_iterator(setup); screen.rem;
+ xcb_screen_next(&screen)) {
+ for (auto depth = xcb_screen_allowed_depths_iterator(screen.data);
+ depth.rem; xcb_depth_next(&depth)) {
+ const int len = xcb_depth_visuals_length(depth.data);
+ const xcb_visualtype_t *visuals = xcb_depth_visuals(depth.data);
+ for (int i = 0; i < len; i++) {
+ if (visual == visuals[i].visual_id) {
+ return depth.data->depth;
+ }
+ }
+ }
+ }
+ return -1;
+}
+
+xcb_render_picture_t
+x_create_picture_with_pictfmt_and_pixmap(xcb_connection_t *c,
+ const xcb_render_pictforminfo_t *pictfmt,
+ xcb_pixmap_t pixmap, uint32_t valuemask,
+ const xcb_render_create_picture_value_list_t *attr) {
+ void *buf = NULL;
+ if (attr) {
+ xcb_render_create_picture_value_list_serialize(&buf, valuemask, attr);
+ if (!buf) {
+ log_error("failed to serialize picture attributes");
+ return XCB_NONE;
+ }
+ }
+
+ xcb_render_picture_t tmp_picture = x_new_id(c);
+ xcb_generic_error_t *e =
+ xcb_request_check(c, xcb_render_create_picture_checked(
+ c, tmp_picture, pixmap, pictfmt->id, valuemask, buf));
+ free(buf);
+ if (e) {
+ log_error_x_error(e, "failed to create picture");
+ return XCB_NONE;
+ }
+ return tmp_picture;
+}
+
+xcb_render_picture_t
+x_create_picture_with_visual_and_pixmap(xcb_connection_t *c, xcb_visualid_t visual,
+ xcb_pixmap_t pixmap, uint32_t valuemask,
+ const xcb_render_create_picture_value_list_t *attr) {
+ const xcb_render_pictforminfo_t *pictfmt = x_get_pictform_for_visual(c, visual);
+ return x_create_picture_with_pictfmt_and_pixmap(c, pictfmt, pixmap, valuemask, attr);
+}
+
+xcb_render_picture_t
+x_create_picture_with_standard_and_pixmap(xcb_connection_t *c, xcb_pict_standard_t standard,
+ xcb_pixmap_t pixmap, uint32_t valuemask,
+ const xcb_render_create_picture_value_list_t *attr) {
+ x_get_server_pictfmts(c);
+
+ auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, standard);
+ assert(pictfmt);
+ return x_create_picture_with_pictfmt_and_pixmap(c, pictfmt, pixmap, valuemask, attr);
+}
+
+xcb_render_picture_t
+x_create_picture_with_standard(xcb_connection_t *c, xcb_drawable_t d, int w, int h,
+ xcb_pict_standard_t standard, uint32_t valuemask,
+ const xcb_render_create_picture_value_list_t *attr) {
+ x_get_server_pictfmts(c);
+
+ auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, standard);
+ assert(pictfmt);
+ return x_create_picture_with_pictfmt(c, d, w, h, pictfmt, valuemask, attr);
+}
+
+/**
+ * Create an picture.
+ */
+xcb_render_picture_t
+x_create_picture_with_pictfmt(xcb_connection_t *c, xcb_drawable_t d, int w, int h,
+ const xcb_render_pictforminfo_t *pictfmt, uint32_t valuemask,
+ const xcb_render_create_picture_value_list_t *attr) {
+ uint8_t depth = pictfmt->depth;
+
+ xcb_pixmap_t tmp_pixmap = x_create_pixmap(c, depth, d, w, h);
+ if (!tmp_pixmap) {
+ return XCB_NONE;
+ }
+
+ xcb_render_picture_t picture = x_create_picture_with_pictfmt_and_pixmap(
+ c, pictfmt, tmp_pixmap, valuemask, attr);
+
+ xcb_free_pixmap(c, tmp_pixmap);
+
+ return picture;
+}
+
+xcb_render_picture_t
+x_create_picture_with_visual(xcb_connection_t *c, xcb_drawable_t d, int w, int h,
+ xcb_visualid_t visual, uint32_t valuemask,
+ const xcb_render_create_picture_value_list_t *attr) {
+ auto pictfmt = x_get_pictform_for_visual(c, visual);
+ return x_create_picture_with_pictfmt(c, d, w, h, pictfmt, valuemask, attr);
+}
+
+bool x_fetch_region(xcb_connection_t *c, xcb_xfixes_region_t r, pixman_region32_t *res) {
+ xcb_generic_error_t *e = NULL;
+ xcb_xfixes_fetch_region_reply_t *xr =
+ xcb_xfixes_fetch_region_reply(c, xcb_xfixes_fetch_region(c, r), &e);
+ if (!xr) {
+ log_error_x_error(e, "Failed to fetch rectangles");
+ return false;
+ }
+
+ int nrect = xcb_xfixes_fetch_region_rectangles_length(xr);
+ auto b = ccalloc(nrect, pixman_box32_t);
+ xcb_rectangle_t *xrect = xcb_xfixes_fetch_region_rectangles(xr);
+ for (int i = 0; i < nrect; i++) {
+ b[i] = (pixman_box32_t){.x1 = xrect[i].x,
+ .y1 = xrect[i].y,
+ .x2 = xrect[i].x + xrect[i].width,
+ .y2 = xrect[i].y + xrect[i].height};
+ }
+ bool ret = pixman_region32_init_rects(res, b, nrect);
+ free(b);
+ free(xr);
+ return ret;
+}
+
+void x_set_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict,
+ int16_t clip_x_origin, int16_t clip_y_origin,
+ const region_t *reg) {
+ int nrects;
+ const rect_t *rects = pixman_region32_rectangles((region_t *)reg, &nrects);
+ auto xrects = ccalloc(nrects, xcb_rectangle_t);
+ for (int i = 0; i < nrects; i++) {
+ xrects[i] = (xcb_rectangle_t){
+ .x = to_i16_checked(rects[i].x1),
+ .y = to_i16_checked(rects[i].y1),
+ .width = to_u16_checked(rects[i].x2 - rects[i].x1),
+ .height = to_u16_checked(rects[i].y2 - rects[i].y1),
+ };
+ }
+
+ xcb_generic_error_t *e = xcb_request_check(
+ c, xcb_render_set_picture_clip_rectangles_checked(
+ c, pict, clip_x_origin, clip_y_origin, to_u32_checked(nrects), xrects));
+ if (e) {
+ log_error_x_error(e, "Failed to set clip region");
+ free(e);
+ }
+ free(xrects);
+}
+
+void x_clear_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict) {
+ xcb_render_change_picture_value_list_t v = {.clipmask = XCB_NONE};
+ xcb_generic_error_t *e = xcb_request_check(
+ c, xcb_render_change_picture(c, pict, XCB_RENDER_CP_CLIP_MASK, &v));
+ if (e) {
+ log_error_x_error(e, "failed to clear clip region");
+ free(e);
+ }
+}
+
+enum {
+ XSyncBadCounter = 0,
+ XSyncBadAlarm = 1,
+ XSyncBadFence = 2,
+};
+
+/**
+ * Convert a X11 error to string
+ *
+ * @return a pointer to a string. this pointer shouldn NOT be freed, same buffer is used
+ * for multiple calls to this function,
+ */
+static const char *
+_x_strerror(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code) {
+ session_t *const ps = ps_g;
+
+ int o = 0;
+ const char *name = "Unknown";
+
+#define CASESTRRET(s) \
+ case s: \
+ name = #s; \
+ break
+
+#define CASESTRRET2(s) \
+ case XCB_##s: name = #s; break
+
+ // TODO(yshui) separate error code out from session_t
+ o = error_code - ps->xfixes_error;
+ switch (o) { CASESTRRET2(XFIXES_BAD_REGION); }
+
+ o = error_code - ps->damage_error;
+ switch (o) { CASESTRRET2(DAMAGE_BAD_DAMAGE); }
+
+ o = error_code - ps->render_error;
+ switch (o) {
+ CASESTRRET2(RENDER_PICT_FORMAT);
+ CASESTRRET2(RENDER_PICTURE);
+ CASESTRRET2(RENDER_PICT_OP);
+ CASESTRRET2(RENDER_GLYPH_SET);
+ CASESTRRET2(RENDER_GLYPH);
+ }
+
+ if (ps->glx_exists) {
+ o = error_code - ps->glx_error;
+ switch (o) {
+ CASESTRRET2(GLX_BAD_CONTEXT);
+ CASESTRRET2(GLX_BAD_CONTEXT_STATE);
+ CASESTRRET2(GLX_BAD_DRAWABLE);
+ CASESTRRET2(GLX_BAD_PIXMAP);
+ CASESTRRET2(GLX_BAD_CONTEXT_TAG);
+ CASESTRRET2(GLX_BAD_CURRENT_WINDOW);
+ CASESTRRET2(GLX_BAD_RENDER_REQUEST);
+ CASESTRRET2(GLX_BAD_LARGE_REQUEST);
+ CASESTRRET2(GLX_UNSUPPORTED_PRIVATE_REQUEST);
+ CASESTRRET2(GLX_BAD_FB_CONFIG);
+ CASESTRRET2(GLX_BAD_PBUFFER);
+ CASESTRRET2(GLX_BAD_CURRENT_DRAWABLE);
+ CASESTRRET2(GLX_BAD_WINDOW);
+ CASESTRRET2(GLX_GLX_BAD_PROFILE_ARB);
+ }
+ }
+
+ if (ps->xsync_exists) {
+ o = error_code - ps->xsync_error;
+ switch (o) {
+ CASESTRRET(XSyncBadCounter);
+ CASESTRRET(XSyncBadAlarm);
+ CASESTRRET(XSyncBadFence);
+ }
+ }
+
+ switch (error_code) {
+ CASESTRRET2(ACCESS);
+ CASESTRRET2(ALLOC);
+ CASESTRRET2(ATOM);
+ CASESTRRET2(COLORMAP);
+ CASESTRRET2(CURSOR);
+ CASESTRRET2(DRAWABLE);
+ CASESTRRET2(FONT);
+ CASESTRRET2(G_CONTEXT);
+ CASESTRRET2(ID_CHOICE);
+ CASESTRRET2(IMPLEMENTATION);
+ CASESTRRET2(LENGTH);
+ CASESTRRET2(MATCH);
+ CASESTRRET2(NAME);
+ CASESTRRET2(PIXMAP);
+ CASESTRRET2(REQUEST);
+ CASESTRRET2(VALUE);
+ CASESTRRET2(WINDOW);
+ }
+
+#undef CASESTRRET
+#undef CASESTRRET2
+
+ thread_local static char buffer[256];
+ snprintf(buffer, sizeof(buffer), "X error %d %s request %d minor %d serial %lu",
+ error_code, name, major, minor, serial);
+ return buffer;
+}
+
+/**
+ * Log a X11 error
+ */
+void x_print_error(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code) {
+ log_debug("%s", _x_strerror(serial, major, minor, error_code));
+}
+
+/*
+ * Convert a xcb_generic_error_t to a string that describes the error
+ *
+ * @return a pointer to a string. this pointer shouldn NOT be freed, same buffer is used
+ * for multiple calls to this function,
+ */
+const char *x_strerror(xcb_generic_error_t *e) {
+ if (!e) {
+ return "No error";
+ }
+ return _x_strerror(e->full_sequence, e->major_code, e->minor_code, e->error_code);
+}
+
+/**
+ * Create a pixmap and check that creation succeeded.
+ */
+xcb_pixmap_t x_create_pixmap(xcb_connection_t *c, uint8_t depth, xcb_drawable_t drawable,
+ int width, int height) {
+ xcb_pixmap_t pix = x_new_id(c);
+ xcb_void_cookie_t cookie = xcb_create_pixmap_checked(
+ c, depth, pix, drawable, to_u16_checked(width), to_u16_checked(height));
+ xcb_generic_error_t *err = xcb_request_check(c, cookie);
+ if (err == NULL) {
+ return pix;
+ }
+
+ log_error_x_error(err, "Failed to create pixmap");
+ free(err);
+ return XCB_NONE;
+}
+
+/**
+ * Validate a pixmap.
+ *
+ * Detect whether the pixmap is valid with XGetGeometry. Well, maybe there
+ * are better ways.
+ */
+bool x_validate_pixmap(xcb_connection_t *c, xcb_pixmap_t pixmap) {
+ if (pixmap == XCB_NONE) {
+ return false;
+ }
+
+ auto r = xcb_get_geometry_reply(c, xcb_get_geometry(c, pixmap), NULL);
+ if (!r) {
+ return false;
+ }
+
+ bool ret = r->width && r->height;
+ free(r);
+ return ret;
+}
+/// Names of root window properties that could point to a pixmap of
+/// background.
+static const char *background_props_str[] = {
+ "_XROOTPMAP_ID",
+ "_XSETROOT_ID",
+ 0,
+};
+
+xcb_pixmap_t
+x_get_root_back_pixmap(xcb_connection_t *c, xcb_window_t root, struct atom *atoms) {
+ xcb_pixmap_t pixmap = XCB_NONE;
+
+ // Get the values of background attributes
+ for (int p = 0; background_props_str[p]; p++) {
+ xcb_atom_t prop_atom = get_atom(atoms, background_props_str[p]);
+ winprop_t prop = x_get_prop(c, root, prop_atom, 1, XCB_ATOM_PIXMAP, 32);
+ if (prop.nitems) {
+ pixmap = (xcb_pixmap_t)*prop.p32;
+ free_winprop(&prop);
+ break;
+ }
+ free_winprop(&prop);
+ }
+
+ return pixmap;
+}
+
+bool x_is_root_back_pixmap_atom(struct atom *atoms, xcb_atom_t atom) {
+ for (int p = 0; background_props_str[p]; p++) {
+ xcb_atom_t prop_atom = get_atom(atoms, background_props_str[p]);
+ if (prop_atom == atom) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Synchronizes a X Render drawable to ensure all pending painting requests
+ * are completed.
+ */
+bool x_fence_sync(xcb_connection_t *c, xcb_sync_fence_t f) {
+ // TODO(richardgv): If everybody just follows the rules stated in X Sync
+ // prototype, we need only one fence per screen, but let's stay a bit
+ // cautious right now
+
+ auto e = xcb_request_check(c, xcb_sync_trigger_fence_checked(c, f));
+ if (e) {
+ log_error_x_error(e, "Failed to trigger the fence");
+ goto err;
+ }
+
+ e = xcb_request_check(c, xcb_sync_await_fence_checked(c, 1, &f));
+ if (e) {
+ log_error_x_error(e, "Failed to await on a fence");
+ goto err;
+ }
+
+ e = xcb_request_check(c, xcb_sync_reset_fence_checked(c, f));
+ if (e) {
+ log_error_x_error(e, "Failed to reset the fence");
+ goto err;
+ }
+ return true;
+
+err:
+ free(e);
+ return false;
+}
+
+// xcb-render specific macros
+#define XFIXED_TO_DOUBLE(value) (((double)(value)) / 65536)
+#define DOUBLE_TO_XFIXED(value) ((xcb_render_fixed_t)(((double)(value)) * 65536))
+
+/**
+ * Convert a struct conv to a X picture convolution filter, normalizing the kernel
+ * in the process. Allow the caller to specify the element at the center of the kernel,
+ * for compatibility with legacy code.
+ *
+ * @param[in] kernel the convolution kernel
+ * @param[in] center the element to put at the center of the matrix
+ * @param[inout] ret pointer to an array of `size`, if `size` is too small, more space
+ * will be allocated, and `*ret` will be updated
+ * @param[inout] size size of the array pointed to by `ret`, in number of elements
+ * @return number of elements filled into `*ret`
+ */
+void x_create_convolution_kernel(const conv *kernel, double center,
+ struct x_convolution_kernel **ret) {
+ assert(ret);
+ if (!*ret || (*ret)->capacity < kernel->w * kernel->h + 2) {
+ free(*ret);
+ *ret =
+ cvalloc(sizeof(struct x_convolution_kernel) +
+ (size_t)(kernel->w * kernel->h + 2) * sizeof(xcb_render_fixed_t));
+ (*ret)->capacity = kernel->w * kernel->h + 2;
+ }
+
+ (*ret)->size = kernel->w * kernel->h + 2;
+
+ auto buf = (*ret)->kernel;
+ buf[0] = DOUBLE_TO_XFIXED(kernel->w);
+ buf[1] = DOUBLE_TO_XFIXED(kernel->h);
+
+ double sum = center;
+ for (int i = 0; i < kernel->w * kernel->h; i++) {
+ if (i == kernel->w * kernel->h / 2) {
+ continue;
+ }
+ sum += kernel->data[i];
+ }
+
+ // Note for floating points a / b != a * (1 / b), but this shouldn't have any real
+ // impact on the result
+ double factor = sum != 0 ? 1.0 / sum : 1;
+ for (int i = 0; i < kernel->w * kernel->h; i++) {
+ buf[i + 2] = DOUBLE_TO_XFIXED(kernel->data[i] * factor);
+ }
+
+ buf[kernel->h / 2 * kernel->w + kernel->w / 2 + 2] =
+ DOUBLE_TO_XFIXED(center * factor);
+}
+
+/// Generate a search criteria for fbconfig from a X visual.
+/// Returns {-1, -1, -1, -1, -1, 0} on failure
+struct xvisual_info x_get_visual_info(xcb_connection_t *c, xcb_visualid_t visual) {
+ auto pictfmt = x_get_pictform_for_visual(c, visual);
+ auto depth = x_get_visual_depth(c, visual);
+ if (!pictfmt || depth == -1) {
+ log_error("Invalid visual %#03x", visual);
+ return (struct xvisual_info){-1, -1, -1, -1, -1, 0};
+ }
+ if (pictfmt->type != XCB_RENDER_PICT_TYPE_DIRECT) {
+ log_error("We cannot handle non-DirectColor visuals. Report an "
+ "issue if you see this error message.");
+ return (struct xvisual_info){-1, -1, -1, -1, -1, 0};
+ }
+
+ int red_size = popcntul(pictfmt->direct.red_mask),
+ blue_size = popcntul(pictfmt->direct.blue_mask),
+ green_size = popcntul(pictfmt->direct.green_mask),
+ alpha_size = popcntul(pictfmt->direct.alpha_mask);
+
+ return (struct xvisual_info){
+ .red_size = red_size,
+ .green_size = green_size,
+ .blue_size = blue_size,
+ .alpha_size = alpha_size,
+ .visual_depth = depth,
+ .visual = visual,
+ };
+}
+
+xcb_screen_t *x_screen_of_display(xcb_connection_t *c, int screen) {
+ xcb_screen_iterator_t iter;
+
+ iter = xcb_setup_roots_iterator(xcb_get_setup(c));
+ for (; iter.rem; --screen, xcb_screen_next(&iter)) {
+ if (screen == 0) {
+ return iter.data;
+ }
+ }
+
+ return NULL;
+}
diff --git a/src/x.h b/src/x.h
new file mode 100644
index 0000000..e01aa0a
--- /dev/null
+++ b/src/x.h
@@ -0,0 +1,293 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) 2018 Yuxuan Shui <[email protected]>
+#pragma once
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <xcb/render.h>
+#include <xcb/sync.h>
+#include <xcb/xcb.h>
+#include <xcb/xcb_renderutil.h>
+#include <xcb/xfixes.h>
+
+#include "compiler.h"
+#include "kernel.h"
+#include "log.h"
+#include "region.h"
+
+typedef struct session session_t;
+struct atom;
+
+/// Structure representing Window property value.
+typedef struct winprop {
+ union {
+ void *ptr;
+ int8_t *p8;
+ int16_t *p16;
+ int32_t *p32;
+ uint32_t *c32; // 32bit cardinal
+ };
+ unsigned long nitems;
+ xcb_atom_t type;
+ int format;
+
+ xcb_get_property_reply_t *r;
+} winprop_t;
+
+typedef struct winprop_info {
+ xcb_atom_t type;
+ uint8_t format;
+ uint32_t length;
+} winprop_info_t;
+
+struct xvisual_info {
+ /// Bit depth of the red component
+ int red_size;
+ /// Bit depth of the green component
+ int green_size;
+ /// Bit depth of the blue component
+ int blue_size;
+ /// Bit depth of the alpha component
+ int alpha_size;
+ /// The depth of X visual
+ int visual_depth;
+
+ xcb_visualid_t visual;
+};
+
+#define XCB_AWAIT_VOID(func, c, ...) \
+ ({ \
+ bool __success = true; \
+ __auto_type __e = xcb_request_check(c, func##_checked(c, __VA_ARGS__)); \
+ if (__e) { \
+ x_print_error(__e->sequence, __e->major_code, __e->minor_code, \
+ __e->error_code); \
+ free(__e); \
+ __success = false; \
+ } \
+ __success; \
+ })
+
+#define XCB_AWAIT(func, c, ...) \
+ ({ \
+ xcb_generic_error_t *__e = NULL; \
+ __auto_type __r = func##_reply(c, func(c, __VA_ARGS__), &__e); \
+ if (__e) { \
+ x_print_error(__e->sequence, __e->major_code, __e->minor_code, \
+ __e->error_code); \
+ free(__e); \
+ } \
+ __r; \
+ })
+
+#define log_debug_x_error(e, fmt, ...) \
+ LOG(DEBUG, fmt " (%s)", ##__VA_ARGS__, x_strerror(e))
+#define log_error_x_error(e, fmt, ...) \
+ LOG(ERROR, fmt " (%s)", ##__VA_ARGS__, x_strerror(e))
+#define log_fatal_x_error(e, fmt, ...) \
+ LOG(FATAL, fmt " (%s)", ##__VA_ARGS__, x_strerror(e))
+
+/// Wraps x_new_id. abort the program if x_new_id returns error
+static inline uint32_t x_new_id(xcb_connection_t *c) {
+ auto ret = xcb_generate_id(c);
+ if (ret == (uint32_t)-1) {
+ log_fatal("We seems to have run of XIDs. This is either a bug in the X "
+ "server, or a resource leakage in the compositor. Please open "
+ "an issue about this problem. The compositor will die.");
+ abort();
+ }
+ return ret;
+}
+
+/**
+ * Send a request to X server and get the reply to make sure all previous
+ * requests are processed, and their replies received
+ *
+ * xcb_get_input_focus is used here because it is the same request used by
+ * libX11
+ */
+static inline void x_sync(xcb_connection_t *c) {
+ free(xcb_get_input_focus_reply(c, xcb_get_input_focus(c), NULL));
+}
+
+/**
+ * Get a specific attribute of a window.
+ *
+ * Returns a blank structure if the returned type and format does not
+ * match the requested type and format.
+ *
+ * @param ps current session
+ * @param w window
+ * @param atom atom of attribute to fetch
+ * @param length length to read
+ * @param rtype atom of the requested type
+ * @param rformat requested format
+ * @return a <code>winprop_t</code> structure containing the attribute
+ * and number of items. A blank one on failure.
+ */
+winprop_t x_get_prop_with_offset(xcb_connection_t *c, xcb_window_t w, xcb_atom_t atom,
+ int offset, int length, xcb_atom_t rtype, int rformat);
+
+/**
+ * Wrapper of wid_get_prop_adv().
+ */
+static inline winprop_t x_get_prop(xcb_connection_t *c, xcb_window_t wid, xcb_atom_t atom,
+ int length, xcb_atom_t rtype, int rformat) {
+ return x_get_prop_with_offset(c, wid, atom, 0L, length, rtype, rformat);
+}
+
+/// Get the type, format and size in bytes of a window's specific attribute.
+winprop_info_t x_get_prop_info(xcb_connection_t *c, xcb_window_t w, xcb_atom_t atom);
+
+/// Discard all X events in queue or in flight. Should only be used when the server is
+/// grabbed
+static inline void x_discard_events(xcb_connection_t *c) {
+ xcb_generic_event_t *e;
+ while ((e = xcb_poll_for_event(c))) {
+ free(e);
+ }
+}
+
+/**
+ * Get the value of a type-<code>xcb_window_t</code> property of a window.
+ *
+ * @return the value if successful, 0 otherwise
+ */
+xcb_window_t wid_get_prop_window(xcb_connection_t *c, xcb_window_t wid, xcb_atom_t aprop);
+
+/**
+ * Get the value of a text property of a window.
+ *
+ * @param[out] pstrlst Out parameter for an array of strings, caller needs to free this
+ * array
+ * @param[out] pnstr Number of strings in the array
+ */
+bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char ***pstrlst,
+ int *pnstr);
+
+const xcb_render_pictforminfo_t *
+x_get_pictform_for_visual(xcb_connection_t *, xcb_visualid_t);
+int x_get_visual_depth(xcb_connection_t *, xcb_visualid_t);
+
+xcb_render_picture_t
+x_create_picture_with_pictfmt_and_pixmap(xcb_connection_t *,
+ const xcb_render_pictforminfo_t *pictfmt,
+ xcb_pixmap_t pixmap, uint32_t valuemask,
+ const xcb_render_create_picture_value_list_t *attr)
+ attr_nonnull(1, 2);
+
+xcb_render_picture_t
+x_create_picture_with_visual_and_pixmap(xcb_connection_t *, xcb_visualid_t visual,
+ xcb_pixmap_t pixmap, uint32_t valuemask,
+ const xcb_render_create_picture_value_list_t *attr)
+ attr_nonnull(1);
+
+xcb_render_picture_t
+x_create_picture_with_standard_and_pixmap(xcb_connection_t *, xcb_pict_standard_t standard,
+ xcb_pixmap_t pixmap, uint32_t valuemask,
+ const xcb_render_create_picture_value_list_t *attr)
+ attr_nonnull(1);
+
+xcb_render_picture_t
+x_create_picture_with_standard(xcb_connection_t *c, xcb_drawable_t d, int w, int h,
+ xcb_pict_standard_t standard, uint32_t valuemask,
+ const xcb_render_create_picture_value_list_t *attr)
+ attr_nonnull(1);
+
+/**
+ * Create an picture.
+ */
+xcb_render_picture_t
+x_create_picture_with_pictfmt(xcb_connection_t *, xcb_drawable_t, int w, int h,
+ const xcb_render_pictforminfo_t *pictfmt, uint32_t valuemask,
+ const xcb_render_create_picture_value_list_t *attr)
+ attr_nonnull(1, 5);
+
+xcb_render_picture_t
+x_create_picture_with_visual(xcb_connection_t *, xcb_drawable_t, int w, int h,
+ xcb_visualid_t visual, uint32_t valuemask,
+ const xcb_render_create_picture_value_list_t *attr)
+ attr_nonnull(1);
+
+/// Fetch a X region and store it in a pixman region
+bool x_fetch_region(xcb_connection_t *, xcb_xfixes_region_t r, region_t *res);
+
+void x_set_picture_clip_region(xcb_connection_t *, xcb_render_picture_t, int16_t clip_x_origin,
+ int16_t clip_y_origin, const region_t *);
+
+void x_clear_picture_clip_region(xcb_connection_t *, xcb_render_picture_t pict);
+
+/**
+ * Log a X11 error
+ */
+void x_print_error(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code);
+
+/*
+ * Convert a xcb_generic_error_t to a string that describes the error
+ *
+ * @return a pointer to a string. this pointer shouldn NOT be freed, same buffer is used
+ * for multiple calls to this function,
+ */
+const char *x_strerror(xcb_generic_error_t *e);
+
+xcb_pixmap_t x_create_pixmap(xcb_connection_t *, uint8_t depth, xcb_drawable_t drawable,
+ int width, int height);
+
+bool x_validate_pixmap(xcb_connection_t *, xcb_pixmap_t pxmap);
+
+/**
+ * Free a <code>winprop_t</code>.
+ *
+ * @param pprop pointer to the <code>winprop_t</code> to free.
+ */
+static inline void free_winprop(winprop_t *pprop) {
+ // Empty the whole structure to avoid possible issues
+ if (pprop->r)
+ free(pprop->r);
+ pprop->ptr = NULL;
+ pprop->r = NULL;
+ pprop->nitems = 0;
+}
+
+/// Get the back pixmap of the root window
+xcb_pixmap_t
+x_get_root_back_pixmap(xcb_connection_t *c, xcb_window_t root, struct atom *atoms);
+
+/// Return true if the atom refers to a property name that is used for the
+/// root window background pixmap
+bool x_is_root_back_pixmap_atom(struct atom *atoms, xcb_atom_t atom);
+
+bool x_fence_sync(xcb_connection_t *, xcb_sync_fence_t);
+
+struct x_convolution_kernel {
+ int size;
+ int capacity;
+ xcb_render_fixed_t kernel[];
+};
+
+/**
+ * Convert a struct conv to a X picture convolution filter, normalizing the kernel
+ * in the process. Allow the caller to specify the element at the center of the kernel,
+ * for compatibility with legacy code.
+ *
+ * @param[in] kernel the convolution kernel
+ * @param[in] center the element to put at the center of the matrix
+ * @param[inout] ret pointer to an array of `size`, if `size` is too small, more space
+ * will be allocated, and `*ret` will be updated.
+ * @param[inout] size size of the array pointed to by `ret`.
+ */
+void attr_nonnull(1, 3) x_create_convolution_kernel(const conv *kernel, double center,
+ struct x_convolution_kernel **ret);
+
+/// Generate a search criteria for fbconfig from a X visual.
+/// Returns {-1, -1, -1, -1, -1, -1} on failure
+struct xvisual_info x_get_visual_info(xcb_connection_t *c, xcb_visualid_t visual);
+
+xcb_visualid_t x_get_visual_for_standard(xcb_connection_t *c, xcb_pict_standard_t std);
+
+xcb_render_pictformat_t
+x_get_pictfmt_for_standard(xcb_connection_t *c, xcb_pict_standard_t std);
+
+xcb_screen_t *x_screen_of_display(xcb_connection_t *c, int screen);
+
+uint32_t attr_deprecated xcb_generate_id(xcb_connection_t *c);
diff --git a/src/xrescheck.c b/src/xrescheck.c
new file mode 100644
index 0000000..1785fc8
--- /dev/null
+++ b/src/xrescheck.c
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2014 Richard Grenville <[email protected]>
+
+#include "compiler.h"
+#include "log.h"
+
+#include "xrescheck.h"
+
+static xrc_xid_record_t *gs_xid_records = NULL;
+
+#define HASH_ADD_XID(head, xidfield, add) HASH_ADD(hh, head, xidfield, sizeof(xid), add)
+
+#define HASH_FIND_XID(head, findxid, out) HASH_FIND(hh, head, findxid, sizeof(xid), out)
+
+#define M_CPY_POS_DATA(prec) \
+ prec->file = file; \
+ prec->func = func; \
+ prec->line = line;
+
+/**
+ * @brief Add a record of given XID to the allocation table.
+ */
+void xrc_add_xid_(XID xid, const char *type, M_POS_DATA_PARAMS) {
+ auto prec = ccalloc(1, xrc_xid_record_t);
+ prec->xid = xid;
+ prec->type = type;
+ M_CPY_POS_DATA(prec);
+
+ HASH_ADD_XID(gs_xid_records, xid, prec);
+}
+
+/**
+ * @brief Delete a record of given XID in the allocation table.
+ */
+void xrc_delete_xid_(XID xid, M_POS_DATA_PARAMS) {
+ xrc_xid_record_t *prec = NULL;
+ HASH_FIND_XID(gs_xid_records, &xid, prec);
+ if (!prec) {
+ log_error("XRC: %s:%d %s(): Can't find XID %#010lx we want to delete.",
+ file, line, func, xid);
+ return;
+ }
+ HASH_DEL(gs_xid_records, prec);
+ free(prec);
+}
+
+/**
+ * @brief Report about issues found in the XID allocation table.
+ */
+void xrc_report_xid(void) {
+ for (xrc_xid_record_t *prec = gs_xid_records; prec; prec = prec->hh.next)
+ log_trace("XRC: %s:%d %s(): %#010lx (%s) not freed.\n", prec->file,
+ prec->line, prec->func, prec->xid, prec->type);
+}
+
+/**
+ * @brief Clear the XID allocation table.
+ */
+void xrc_clear_xid(void) {
+ xrc_xid_record_t *prec = NULL, *ptmp = NULL;
+ HASH_ITER(hh, gs_xid_records, prec, ptmp) {
+ HASH_DEL(gs_xid_records, prec);
+ free(prec);
+ }
+}
diff --git a/src/xrescheck.h b/src/xrescheck.h
new file mode 100644
index 0000000..5ad5c46
--- /dev/null
+++ b/src/xrescheck.h
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2014 Richard Grenville <[email protected]>
+#pragma once
+
+#include "common.h"
+#include "uthash.h"
+
+typedef struct {
+ XID xid;
+ const char *type;
+ const char *file;
+ const char *func;
+ int line;
+ UT_hash_handle hh;
+} xrc_xid_record_t;
+
+#define M_POS_DATA_PARAMS const char *file, int line, const char *func
+#define M_POS_DATA_PASSTHROUGH file, line, func
+#define M_POS_DATA __FILE__, __LINE__, __func__
+
+void xrc_add_xid_(XID xid, const char *type, M_POS_DATA_PARAMS);
+
+#define xrc_add_xid(xid, type) xrc_add_xid_(xid, type, M_POS_DATA)
+
+void xrc_delete_xid_(XID xid, M_POS_DATA_PARAMS);
+
+#define xrc_delete_xid(xid) xrc_delete_xid_(xid, M_POS_DATA)
+
+void xrc_report_xid(void);
+
+void xrc_clear_xid(void);
+
+// Pixmap
+
+static inline void xcb_create_pixmap_(xcb_connection_t *c, uint8_t depth,
+ xcb_pixmap_t pixmap, xcb_drawable_t drawable,
+ uint16_t width, uint16_t height, M_POS_DATA_PARAMS) {
+ xcb_create_pixmap(c, depth, pixmap, drawable, width, height);
+ xrc_add_xid_(pixmap, "Pixmap", M_POS_DATA_PASSTHROUGH);
+}
+
+#define xcb_create_pixmap(c, depth, pixmap, drawable, width, height) \
+ xcb_create_pixmap_(c, depth, pixmap, drawable, width, height, M_POS_DATA)
+
+static inline xcb_void_cookie_t
+xcb_composite_name_window_pixmap_(xcb_connection_t *c, xcb_window_t window,
+ xcb_pixmap_t pixmap, M_POS_DATA_PARAMS) {
+ xcb_void_cookie_t ret = xcb_composite_name_window_pixmap(c, window, pixmap);
+ xrc_add_xid_(pixmap, "PixmapC", M_POS_DATA_PASSTHROUGH);
+ return ret;
+}
+
+#define xcb_composite_name_window_pixmap(dpy, window, pixmap) \
+ xcb_composite_name_window_pixmap_(dpy, window, pixmap, M_POS_DATA)
+
+static inline void
+xcb_free_pixmap_(xcb_connection_t *c, xcb_pixmap_t pixmap, M_POS_DATA_PARAMS) {
+ xcb_free_pixmap(c, pixmap);
+ xrc_delete_xid_(pixmap, M_POS_DATA_PASSTHROUGH);
+}
+
+#define xcb_free_pixmap(c, pixmap) xcb_free_pixmap_(c, pixmap, M_POS_DATA);