summaryrefslogtreecommitdiffstats
path: root/twin/compton-tde/compton.c
diff options
context:
space:
mode:
Diffstat (limited to 'twin/compton-tde/compton.c')
-rw-r--r--twin/compton-tde/compton.c7854
1 files changed, 7854 insertions, 0 deletions
diff --git a/twin/compton-tde/compton.c b/twin/compton-tde/compton.c
new file mode 100644
index 000000000..123703a19
--- /dev/null
+++ b/twin/compton-tde/compton.c
@@ -0,0 +1,7854 @@
+/*
+ * Compton - a compositor for X11
+ *
+ * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
+ *
+ * Copyright (c) 2011-2013, Christopher Jeffrey
+ * Copyright (c) 2014 Timothy Pearson <kb9vqf@pearsoncomputing.net>
+ * See LICENSE for more information.
+ *
+ */
+
+#include "compton.h"
+#include <ctype.h>
+
+// === 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",
+ "notify",
+ "combo",
+ "dnd",
+};
+
+/// Names of VSync modes.
+const char * const VSYNC_STRS[NUM_VSYNC + 1] = {
+ "none", // VSYNC_NONE
+ "drm", // VSYNC_DRM
+ "opengl", // VSYNC_OPENGL
+ "opengl-oml", // VSYNC_OPENGL_OML
+ "opengl-swc", // VSYNC_OPENGL_SWC
+ "opengl-mswc", // VSYNC_OPENGL_MSWC
+ NULL
+};
+
+/// Names of backends.
+const char * const BACKEND_STRS[NUM_BKEND + 1] = {
+ "xrender", // BKEND_XRENDER
+ "glx", // BKEND_GLX
+ "xr_glx_hybrid",// BKEND_XR_GLX_HYBRID
+ NULL
+};
+
+/// Function pointers to init VSync modes.
+static bool (* const (VSYNC_FUNCS_INIT[NUM_VSYNC]))(session_t *ps) = {
+ [VSYNC_DRM ] = vsync_drm_init,
+ [VSYNC_OPENGL ] = vsync_opengl_init,
+ [VSYNC_OPENGL_OML ] = vsync_opengl_oml_init,
+ [VSYNC_OPENGL_SWC ] = vsync_opengl_swc_init,
+ [VSYNC_OPENGL_MSWC ] = vsync_opengl_mswc_init,
+};
+
+/// Function pointers to wait for VSync.
+static int (* const (VSYNC_FUNCS_WAIT[NUM_VSYNC]))(session_t *ps) = {
+#ifdef CONFIG_VSYNC_DRM
+ [VSYNC_DRM ] = vsync_drm_wait,
+#endif
+#ifdef CONFIG_VSYNC_OPENGL
+ [VSYNC_OPENGL ] = vsync_opengl_wait,
+ [VSYNC_OPENGL_OML ] = vsync_opengl_oml_wait,
+#endif
+};
+
+/// Function pointers to deinitialize VSync.
+static void (* const (VSYNC_FUNCS_DEINIT[NUM_VSYNC]))(session_t *ps) = {
+#ifdef CONFIG_VSYNC_OPENGL
+ [VSYNC_OPENGL_SWC ] = vsync_opengl_swc_deinit,
+ [VSYNC_OPENGL_MSWC ] = vsync_opengl_mswc_deinit,
+#endif
+};
+
+/// Names of root window properties that could point to a pixmap of
+/// background.
+const static char *background_props_str[] = {
+ "_XROOTPMAP_ID",
+ "_XSETROOT_ID",
+ 0,
+};
+
+// === Global variables ===
+
+/// Pointer to current session, as a global variable. Only used by
+/// <code>error()</code> and <code>reset_enable()</code>, which could not
+/// have a pointer to current session passed in.
+session_t *ps_g = NULL;
+
+// === Execution control ===
+
+struct sigaction usr_action;
+sigset_t block_mask;
+
+int my_exit_code = 3;
+
+void write_pid_file(pid_t pid)
+{
+#ifdef WRITE_PID_FILE
+#ifdef USE_ENV_HOME
+ const char *home = getenv("HOME");
+#else
+ const char *home;
+ struct passwd *p;
+ p = getpwuid(getuid());
+ if (p)
+ home = p->pw_dir;
+ else
+ home = getenv("HOME");
+#endif
+ const char *filename;
+ const char *configfile = "/.compton-tde.pid";
+ int n = strlen(home)+strlen(configfile)+1;
+ filename = (char*)malloc(n*sizeof(char));
+ memset(filename,0,n);
+ strcat(filename, home);
+ strcat(filename, configfile);
+
+ printf("writing '%s' as pidfile\n\n", filename);
+
+ /* now that we did all that by way of introduction...write the file! */
+ FILE *pFile;
+ char buffer[255];
+ sprintf(buffer, "%d", pid);
+ pFile = fopen(filename, "w");
+ if (pFile) {
+ fwrite(buffer,1,strlen(buffer), pFile);
+ fclose(pFile);
+ }
+
+ free(filename);
+ filename = NULL;
+#endif
+}
+
+void delete_pid_file()
+{
+#ifdef WRITE_PID_FILE
+#ifdef USE_ENV_HOME
+ const char *home = getenv("HOME");
+#else
+ const char *home;
+ struct passwd *p;
+ p = getpwuid(getuid());
+ if (p)
+ home = p->pw_dir;
+ else
+ home = getenv("HOME");
+#endif
+ const char *filename;
+ const char *configfile = "/.compton-tde.pid";
+ int n = strlen(home)+strlen(configfile)+1;
+ filename = (char*)malloc(n*sizeof(char));
+ memset(filename,0,n);
+ strcat(filename, home);
+ strcat(filename, configfile);
+
+ printf("deleting '%s' as pidfile\n\n", filename);
+
+ /* now that we did all that by way of introduction...delete the file! */
+ unlink(filename);
+
+ free(filename);
+ filename = NULL;
+#endif
+
+#if WORK_AROUND_FGLRX
+ if ((my_exit_code == 3) && (restartOnSigterm)) {
+ printf("compton-tde lost connection to X server, restarting...\n"); fflush(stdout);
+ sleep(1);
+ char me[2048];
+ int chars = readlink("/proc/self/exe", me, sizeof(me));
+ me[chars] = 0;
+ me[2047] = 0;
+ execl(me, basename(me), (char*)NULL);
+ }
+#endif
+}
+
+void handle_siguser (int sig)
+{
+ int uidnum;
+ if (sig == SIGTERM) {
+ my_exit_code=0;
+ delete_pid_file();
+ exit(0);
+ }
+ if (sig == SIGUSR1) {
+ char newuid[1024];
+#ifndef NDEBUG
+ printf("Enter the new user ID:\n"); fflush(stdout);
+#endif
+ char *eof;
+ newuid[0] = '\0';
+ newuid[sizeof(newuid)-1] = '\0';
+ eof = fgets(newuid, sizeof(newuid), stdin);
+ uidnum = atoi(newuid);
+#ifndef NDEBUG
+ printf("Setting compton-tde process uid to %d...\n", uidnum); fflush(stdout);
+#endif
+
+ my_exit_code=4;
+ delete_pid_file();
+ my_exit_code=3;
+ setuid(uidnum);
+ write_pid_file(getpid());
+
+ }
+ else {
+ uidnum = getuid();
+ }
+ if ((sig == SIGUSR1) || (sig == SIGUSR2)) {
+ get_cfg(ps_g, 0, 0, false); /* reload the configuration file */
+
+ /* set background/shadow picture using the new settings */
+ ps_g->cshadow_picture = solid_picture(ps_g, true, 1, ps_g->o.shadow_red, ps_g->o.shadow_green, ps_g->o.shadow_blue);
+
+ /* regenerate shadows using the new settings */
+ init_alpha_picts(ps_g);
+ init_filters(ps_g);
+ }
+}
+
+// === Fading ===
+
+/**
+ * Get the time left before next fading point.
+ *
+ * In milliseconds.
+ */
+static int
+fade_timeout(session_t *ps) {
+ int diff = ps->o.fade_delta - get_time_ms() + ps->fade_time;
+
+ diff = normalize_i_range(diff, 0, ps->o.fade_delta * 2);
+
+ return diff;
+}
+
+/**
+ * Run fading on a window.
+ *
+ * @param steps steps of fading
+ */
+static void
+run_fade(session_t *ps, win *w, unsigned steps) {
+ // If we have reached target opacity, return
+ if (w->opacity == w->opacity_tgt) {
+ return;
+ }
+
+ if (!w->fade)
+ w->opacity = w->opacity_tgt;
+ else if (steps) {
+ // Use double below because opacity_t will probably overflow during
+ // calculations
+ if (w->opacity < w->opacity_tgt)
+ w->opacity = normalize_d_range(
+ (double) w->opacity + (double) ps->o.fade_in_step * steps,
+ 0.0, w->opacity_tgt);
+ else
+ w->opacity = normalize_d_range(
+ (double) w->opacity - (double) ps->o.fade_out_step * steps,
+ w->opacity_tgt, OPAQUE);
+ }
+
+ if (w->opacity != w->opacity_tgt) {
+ ps->idling = false;
+ }
+}
+
+/**
+ * Set fade callback of a window, and possibly execute the previous
+ * callback.
+ *
+ * @param exec_callback whether the previous callback is to be executed
+ */
+static void
+set_fade_callback(session_t *ps, win *w,
+ void (*callback) (session_t *ps, win *w), bool exec_callback) {
+ void (*old_callback) (session_t *ps, win *w) = w->fade_callback;
+
+ w->fade_callback = callback;
+ // Must be the last line as the callback could destroy w!
+ if (exec_callback && old_callback) {
+ old_callback(ps, w);
+ // Although currently no callback function affects window state on
+ // next paint, it could, in the future
+ ps->idling = false;
+ }
+}
+
+// === Shadows ===
+
+static double __attribute__((const))
+gaussian(double r, double x, double y) {
+ return ((1 / (sqrt(2 * M_PI * r))) *
+ exp((- (x * x + y * y)) / (2 * r * r)));
+}
+
+static conv *
+make_gaussian_map(double r) {
+ conv *c;
+ int size = ((int) ceil((r * 3)) + 1) & ~1;
+ int center = size / 2;
+ int x, y;
+ double t;
+ double g;
+
+ c = malloc(sizeof(conv) + size * size * sizeof(double));
+ c->size = size;
+ c->data = (double *) (c + 1);
+ t = 0.0;
+
+ for (y = 0; y < size; y++) {
+ for (x = 0; x < size; x++) {
+ g = gaussian(r, x - center, y - center);
+ t += g;
+ c->data[y * size + x] = g;
+ }
+ }
+
+ for (y = 0; y < size; y++) {
+ for (x = 0; x < size; x++) {
+ c->data[y * size + x] /= t;
+ }
+ }
+
+ return c;
+}
+
+/*
+ * A picture will help
+ *
+ * -center 0 width width+center
+ * -center +-----+-------------------+-----+
+ * | | | |
+ * | | | |
+ * 0 +-----+-------------------+-----+
+ * | | | |
+ * | | | |
+ * | | | |
+ * height +-----+-------------------+-----+
+ * | | | |
+ * height+ | | | |
+ * center +-----+-------------------+-----+
+ */
+
+static unsigned char
+sum_gaussian(conv *map, double opacity,
+ int x, int y, int width, int height) {
+ int fx, fy;
+ double *g_data;
+ double *g_line = map->data;
+ int g_size = map->size;
+ int center = g_size / 2;
+ int fx_start, fx_end;
+ int fy_start, fy_end;
+ double v;
+
+ /*
+ * Compute set of filter values which are "in range",
+ * that's the set with:
+ * 0 <= x + (fx-center) && x + (fx-center) < width &&
+ * 0 <= y + (fy-center) && y + (fy-center) < height
+ *
+ * 0 <= x + (fx - center) x + fx - center < width
+ * center - x <= fx fx < width + center - x
+ */
+
+ fx_start = center - x;
+ if (fx_start < 0) fx_start = 0;
+ fx_end = width + center - x;
+ if (fx_end > g_size) fx_end = g_size;
+
+ fy_start = center - y;
+ if (fy_start < 0) fy_start = 0;
+ fy_end = height + center - y;
+ if (fy_end > g_size) fy_end = g_size;
+
+ g_line = g_line + fy_start * g_size + fx_start;
+
+ v = 0;
+
+ for (fy = fy_start; fy < fy_end; fy++) {
+ g_data = g_line;
+ g_line += g_size;
+
+ for (fx = fx_start; fx < fx_end; fx++) {
+ v += *g_data++;
+ }
+ }
+
+ if (v > 1) v = 1;
+
+ return ((unsigned char) (v * opacity * 255.0));
+}
+
+/* precompute shadow corners and sides
+ to save time for large windows */
+
+static void
+presum_gaussian(session_t *ps, conv *map) {
+ int center = map->size / 2;
+ int opacity, x, y;
+
+ ps->cgsize = map->size;
+
+ if (ps->shadow_corner)
+ free(ps->shadow_corner);
+ if (ps->shadow_top)
+ free(ps->shadow_top);
+
+ ps->shadow_corner = malloc((ps->cgsize + 1) * (ps->cgsize + 1) * 26);
+ ps->shadow_top = malloc((ps->cgsize + 1) * 26);
+
+ for (x = 0; x <= ps->cgsize; x++) {
+ ps->shadow_top[25 * (ps->cgsize + 1) + x] =
+ sum_gaussian(map, 1, x - center, center, ps->cgsize * 2, ps->cgsize * 2);
+
+ for (opacity = 0; opacity < 25; opacity++) {
+ ps->shadow_top[opacity * (ps->cgsize + 1) + x] =
+ ps->shadow_top[25 * (ps->cgsize + 1) + x] * opacity / 25;
+ }
+
+ for (y = 0; y <= x; y++) {
+ ps->shadow_corner[25 * (ps->cgsize + 1) * (ps->cgsize + 1) + y * (ps->cgsize + 1) + x]
+ = sum_gaussian(map, 1, x - center, y - center, ps->cgsize * 2, ps->cgsize * 2);
+ ps->shadow_corner[25 * (ps->cgsize + 1) * (ps->cgsize + 1) + x * (ps->cgsize + 1) + y]
+ = ps->shadow_corner[25 * (ps->cgsize + 1) * (ps->cgsize + 1) + y * (ps->cgsize + 1) + x];
+
+ for (opacity = 0; opacity < 25; opacity++) {
+ ps->shadow_corner[opacity * (ps->cgsize + 1) * (ps->cgsize + 1)
+ + y * (ps->cgsize + 1) + x]
+ = ps->shadow_corner[opacity * (ps->cgsize + 1) * (ps->cgsize + 1)
+ + x * (ps->cgsize + 1) + y]
+ = ps->shadow_corner[25 * (ps->cgsize + 1) * (ps->cgsize + 1)
+ + y * (ps->cgsize + 1) + x] * opacity / 25;
+ }
+ }
+ }
+}
+
+static XImage *
+make_shadow(session_t *ps, double opacity,
+ int width, int height) {
+ XImage *ximage;
+ unsigned char *data;
+ int ylimit, xlimit;
+ int swidth = width + ps->cgsize;
+ int sheight = height + ps->cgsize;
+ int center = ps->cgsize / 2;
+ int x, y;
+ unsigned char d;
+ int x_diff;
+ int opacity_int = (int)(opacity * 25);
+
+ data = malloc(swidth * sheight * sizeof(unsigned char));
+ if (!data) return 0;
+
+ ximage = XCreateImage(ps->dpy, ps->vis, 8,
+ ZPixmap, 0, (char *) data, swidth, sheight, 8, swidth * sizeof(char));
+
+ if (!ximage) {
+ free(data);
+ return 0;
+ }
+
+ /*
+ * Build the gaussian in sections
+ */
+
+ /*
+ * center (fill the complete data array)
+ */
+
+ // If clear_shadow is enabled and the border & corner shadow (which
+ // later will be filled) could entirely cover the area of the shadow
+ // that will be displayed, do not bother filling other pixels. If it
+ // can't, we must fill the other pixels here.
+ /* if (!(clear_shadow && ps->o.shadow_offset_x <= 0 && ps->o.shadow_offset_x >= -ps->cgsize
+ && ps->o.shadow_offset_y <= 0 && ps->o.shadow_offset_y >= -ps->cgsize)) { */
+ if (ps->cgsize > 0) {
+ d = ps->shadow_top[opacity_int * (ps->cgsize + 1) + ps->cgsize];
+ } else {
+ d = sum_gaussian(ps->gaussian_map,
+ opacity, center, center, width, height);
+ }
+ memset(data, d, sheight * swidth);
+ // }
+
+ /*
+ * corners
+ */
+
+ ylimit = ps->cgsize;
+ if (ylimit > sheight / 2) ylimit = (sheight + 1) / 2;
+
+ xlimit = ps->cgsize;
+ if (xlimit > swidth / 2) xlimit = (swidth + 1) / 2;
+
+ for (y = 0; y < ylimit; y++) {
+ for (x = 0; x < xlimit; x++) {
+ if (xlimit == ps->cgsize && ylimit == ps->cgsize) {
+ d = ps->shadow_corner[opacity_int * (ps->cgsize + 1) * (ps->cgsize + 1)
+ + y * (ps->cgsize + 1) + x];
+ } else {
+ d = sum_gaussian(ps->gaussian_map,
+ opacity, x - center, y - center, width, height);
+ }
+ data[y * swidth + x] = d;
+ data[(sheight - y - 1) * swidth + x] = d;
+ data[(sheight - y - 1) * swidth + (swidth - x - 1)] = d;
+ data[y * swidth + (swidth - x - 1)] = d;
+ }
+ }
+
+ /*
+ * top/bottom
+ */
+
+ x_diff = swidth - (ps->cgsize * 2);
+ if (x_diff > 0 && ylimit > 0) {
+ for (y = 0; y < ylimit; y++) {
+ if (ylimit == ps->cgsize) {
+ d = ps->shadow_top[opacity_int * (ps->cgsize + 1) + y];
+ } else {
+ d = sum_gaussian(ps->gaussian_map,
+ opacity, center, y - center, width, height);
+ }
+ memset(&data[y * swidth + ps->cgsize], d, x_diff);
+ memset(&data[(sheight - y - 1) * swidth + ps->cgsize], d, x_diff);
+ }
+ }
+
+ /*
+ * sides
+ */
+
+ for (x = 0; x < xlimit; x++) {
+ if (xlimit == ps->cgsize) {
+ d = ps->shadow_top[opacity_int * (ps->cgsize + 1) + x];
+ } else {
+ d = sum_gaussian(ps->gaussian_map,
+ opacity, x - center, center, width, height);
+ }
+ for (y = ps->cgsize; y < sheight - ps->cgsize; y++) {
+ data[y * swidth + x] = d;
+ data[y * swidth + (swidth - x - 1)] = d;
+ }
+ }
+
+ /*
+ if (clear_shadow) {
+ // Clear the region in the shadow that the window would cover based
+ // on shadow_offset_{x,y} user provides
+ int xstart = normalize_i_range(- (int) ps->o.shadow_offset_x, 0, swidth);
+ int xrange = normalize_i_range(width - (int) ps->o.shadow_offset_x,
+ 0, swidth) - xstart;
+ int ystart = normalize_i_range(- (int) ps->o.shadow_offset_y, 0, sheight);
+ int yend = normalize_i_range(height - (int) ps->o.shadow_offset_y,
+ 0, sheight);
+ int y;
+
+ for (y = ystart; y < yend; y++) {
+ memset(&data[y * swidth + xstart], 0, xrange);
+ }
+ }
+ */
+
+ return ximage;
+}
+
+/**
+ * Generate shadow <code>Picture</code> for a window.
+ */
+static bool
+win_build_shadow(session_t *ps, win *w, double opacity) {
+ const int width = w->widthb;
+ const int height = w->heightb;
+
+ XImage *shadow_image = NULL;
+ Pixmap shadow_pixmap = None, shadow_pixmap_argb = None;
+ Picture shadow_picture = None, shadow_picture_argb = None;
+ GC gc = None;
+
+ shadow_image = make_shadow(ps, opacity, width, height);
+ if (!shadow_image)
+ return None;
+
+ shadow_pixmap = XCreatePixmap(ps->dpy, ps->root,
+ shadow_image->width, shadow_image->height, 8);
+ shadow_pixmap_argb = XCreatePixmap(ps->dpy, ps->root,
+ shadow_image->width, shadow_image->height, 32);
+
+ if (!shadow_pixmap || !shadow_pixmap_argb)
+ goto shadow_picture_err;
+
+ shadow_picture = XRenderCreatePicture(ps->dpy, shadow_pixmap,
+ XRenderFindStandardFormat(ps->dpy, PictStandardA8), 0, 0);
+ shadow_picture_argb = XRenderCreatePicture(ps->dpy, shadow_pixmap_argb,
+ XRenderFindStandardFormat(ps->dpy, PictStandardARGB32), 0, 0);
+ if (!shadow_picture || !shadow_picture_argb)
+ goto shadow_picture_err;
+
+ gc = XCreateGC(ps->dpy, shadow_pixmap, 0, 0);
+ if (!gc)
+ goto shadow_picture_err;
+
+ XPutImage(ps->dpy, shadow_pixmap, gc, shadow_image, 0, 0, 0, 0,
+ shadow_image->width, shadow_image->height);
+ XRenderComposite(ps->dpy, PictOpSrc, ps->cshadow_picture, shadow_picture,
+ shadow_picture_argb, 0, 0, 0, 0, 0, 0,
+ shadow_image->width, shadow_image->height);
+
+ w->shadow_paint.pixmap = shadow_pixmap_argb;
+ w->shadow_paint.pict = shadow_picture_argb;
+
+ // Sync it once and only once
+ xr_sync(ps, w->shadow_paint.pixmap, NULL);
+
+ bool success = paint_bind_tex(ps, &w->shadow_paint, shadow_image->width, shadow_image->height, 32, true);
+
+ XFreeGC(ps->dpy, gc);
+ XDestroyImage(shadow_image);
+ XFreePixmap(ps->dpy, shadow_pixmap);
+ XRenderFreePicture(ps->dpy, shadow_picture);
+
+ return success;
+
+shadow_picture_err:
+ if (shadow_image)
+ XDestroyImage(shadow_image);
+ if (shadow_pixmap)
+ XFreePixmap(ps->dpy, shadow_pixmap);
+ if (shadow_pixmap_argb)
+ XFreePixmap(ps->dpy, shadow_pixmap_argb);
+ if (shadow_picture)
+ XRenderFreePicture(ps->dpy, shadow_picture);
+ if (shadow_picture_argb)
+ XRenderFreePicture(ps->dpy, shadow_picture_argb);
+ if (gc)
+ XFreeGC(ps->dpy, gc);
+
+ return false;
+}
+
+/**
+ * Generate a 1x1 <code>Picture</code> of a particular color.
+ */
+static Picture
+solid_picture(session_t *ps, bool argb, double a,
+ double r, double g, double b) {
+ Pixmap pixmap;
+ Picture picture;
+ XRenderPictureAttributes pa;
+ XRenderColor c;
+
+ pixmap = XCreatePixmap(ps->dpy, ps->root, 1, 1, argb ? 32 : 8);
+
+ if (!pixmap) return None;
+
+ pa.repeat = True;
+ picture = XRenderCreatePicture(ps->dpy, pixmap,
+ XRenderFindStandardFormat(ps->dpy, argb
+ ? PictStandardARGB32 : PictStandardA8),
+ CPRepeat,
+ &pa);
+
+ if (!picture) {
+ XFreePixmap(ps->dpy, pixmap);
+ return None;
+ }
+
+ c.alpha = a * 0xffff;
+ c.red = r * 0xffff;
+ c.green = g * 0xffff;
+ c.blue = b * 0xffff;
+
+ XRenderFillRectangle(ps->dpy, PictOpSrc, picture, &c, 0, 0, 1, 1);
+ XFreePixmap(ps->dpy, pixmap);
+
+ return picture;
+}
+
+// === Error handling ===
+
+static void
+discard_ignore(session_t *ps, unsigned long sequence) {
+ while (ps->ignore_head) {
+ if ((long) (sequence - ps->ignore_head->sequence) > 0) {
+ 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 void
+set_ignore(session_t *ps, unsigned long sequence) {
+ ignore_t *i = malloc(sizeof(ignore_t));
+ if (!i) return;
+
+ i->sequence = sequence;
+ i->next = 0;
+ *ps->ignore_tail = i;
+ ps->ignore_tail = &i->next;
+}
+
+static int
+should_ignore(session_t *ps, unsigned long sequence) {
+ discard_ignore(ps, sequence);
+ return ps->ignore_head && ps->ignore_head->sequence == sequence;
+}
+
+// === Windows ===
+
+/**
+ * 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
+wid_get_prop_adv(const session_t *ps, Window w, Atom atom, long offset,
+ long length, Atom rtype, int rformat) {
+ Atom type = None;
+ int format = 0;
+ unsigned long nitems = 0, after = 0;
+ unsigned char *data = NULL;
+
+ if (Success == XGetWindowProperty(ps->dpy, w, atom, offset, length,
+ False, rtype, &type, &format, &nitems, &after, &data)
+ && nitems && (AnyPropertyType == type || type == rtype)
+ && (!rformat || format == rformat)
+ && (8 == format || 16 == format || 32 == format)) {
+ return (winprop_t) {
+ .data.p8 = data,
+ .nitems = nitems,
+ .type = type,
+ .format = format,
+ };
+ }
+
+ cxfree(data);
+
+ return (winprop_t) {
+ .data.p8 = NULL,
+ .nitems = 0,
+ .type = AnyPropertyType,
+ .format = 0
+ };
+}
+
+/**
+ * Check if a window has rounded corners.
+ */
+static void
+win_rounded_corners(session_t *ps, win *w) {
+ if (!w->bounding_shaped)
+ return;
+
+ // Fetch its bounding region
+ if (!w->border_size)
+ w->border_size = border_size(ps, w, true);
+
+ // Quit if border_size() returns None
+ if (!w->border_size)
+ return;
+
+ // Determine the minimum width/height of a rectangle that could mark
+ // a window as having rounded corners
+ unsigned short minwidth = max_i(w->widthb * (1 - ROUNDED_PERCENT),
+ w->widthb - ROUNDED_PIXELS);
+ unsigned short minheight = max_i(w->heightb * (1 - ROUNDED_PERCENT),
+ w->heightb - ROUNDED_PIXELS);
+
+ // Get the rectangles in the bounding region
+ int nrects = 0, i;
+ XRectangle *rects = XFixesFetchRegion(ps->dpy, w->border_size, &nrects);
+ if (!rects)
+ return;
+
+ // Look for a rectangle large enough for this window be considered
+ // having rounded corners
+ for (i = 0; i < nrects; ++i)
+ if (rects[i].width >= minwidth && rects[i].height >= minheight) {
+ w->rounded_corners = true;
+ cxfree(rects);
+ return;
+ }
+
+ w->rounded_corners = false;
+ cxfree(rects);
+}
+
+/**
+ * Add a pattern to a condition linked list.
+ */
+static bool
+condlst_add(session_t *ps, c2_lptr_t **pcondlst, const char *pattern) {
+ if (!pattern)
+ return false;
+
+#ifdef CONFIG_C2
+ if (!c2_parse(ps, pcondlst, pattern))
+ exit(1);
+#else
+ printf_errfq(1, "(): Condition support not compiled in.");
+#endif
+
+ return true;
+}
+
+/**
+ * Determine the event mask for a window.
+ */
+static long
+determine_evmask(session_t *ps, Window wid, win_evmode_t mode) {
+ long evmask = NoEventMask;
+ win *w = NULL;
+
+ // Check if it's a mapped frame window
+ if (WIN_EVMODE_FRAME == mode
+ || ((w = find_win(ps, wid)) && IsViewable == w->a.map_state)) {
+ evmask |= PropertyChangeMask;
+ if (ps->o.track_focus && !ps->o.use_ewmh_active_win)
+ evmask |= FocusChangeMask;
+ }
+
+ // Check if it's a mapped client window
+ if (WIN_EVMODE_CLIENT == mode
+ || ((w = find_toplevel(ps, wid)) && IsViewable == w->a.map_state)) {
+ if (ps->o.frame_opacity || ps->o.track_wdata || ps->track_atom_lst
+ || ps->o.detect_client_opacity)
+ evmask |= PropertyChangeMask;
+ }
+
+ return evmask;
+}
+
+/**
+ * Find out the WM frame of a client window by querying X.
+ *
+ * @param ps current session
+ * @param wid window ID
+ * @return struct _win object of the found window, NULL if not found
+ */
+static win *
+find_toplevel2(session_t *ps, Window wid) {
+ win *w = NULL;
+
+ // We traverse through its ancestors to find out the frame
+ while (wid && wid != ps->root && !(w = find_win(ps, wid))) {
+ Window troot;
+ Window parent;
+ Window *tchildren;
+ unsigned tnchildren;
+
+ // XQueryTree probably fails if you run compton when X is somehow
+ // initializing (like add it in .xinitrc). In this case
+ // just leave it alone.
+ if (!XQueryTree(ps->dpy, wid, &troot, &parent, &tchildren,
+ &tnchildren)) {
+ parent = 0;
+ break;
+ }
+
+ cxfree(tchildren);
+
+ wid = parent;
+ }
+
+ return 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 win *
+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 ps->active_win;
+ }
+
+ // Determine the currently focused window so we can apply appropriate
+ // opacity on it
+ Window wid = 0;
+ int revert_to;
+
+ XGetInputFocus(ps->dpy, &wid, &revert_to);
+
+ win *w = find_win_all(ps, wid);
+
+#ifdef DEBUG_EVENTS
+ print_timestamp(ps);
+ printf_dbgf("(): %#010lx (%#010lx \"%s\") focused.\n", wid,
+ (w ? w->id: None), (w ? w->name: NULL));
+#endif
+
+ // And we set the focus state here
+ if (w) {
+ win_set_focused(ps, w, true);
+ return w;
+ }
+
+ return NULL;
+}
+
+static Bool
+determine_window_transparent_to_black(const session_t *ps, Window w);
+
+static Bool
+determine_window_transparent_to_desktop(const session_t *ps, Window w);
+
+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;
+ Pixmap pixmap = None;
+
+ // Get the values of background attributes
+ for (int p = 0; background_props_str[p]; p++) {
+ winprop_t prop = wid_get_prop(ps, ps->root,
+ get_atom(ps, background_props_str[p]),
+ 1L, XA_PIXMAP, 32);
+ if (prop.nitems) {
+ pixmap = *prop.data.p32;
+ fill = false;
+ free_winprop(&prop);
+ break;
+ }
+ free_winprop(&prop);
+ }
+
+ // Make sure the pixmap we got is valid
+ if (pixmap && !validate_pixmap(ps, pixmap))
+ pixmap = None;
+
+ // Create a pixmap if there isn't any
+ if (!pixmap) {
+ pixmap = XCreatePixmap(ps->dpy, ps->root, 1, 1, ps->depth);
+ fill = true;
+ }
+
+ // Create Picture
+ {
+ XRenderPictureAttributes pa = {
+ .repeat = True,
+ };
+ ps->root_tile_paint.pict = XRenderCreatePicture(
+ ps->dpy, pixmap, XRenderFindVisualFormat(ps->dpy, ps->vis),
+ CPRepeat, &pa);
+ }
+
+ // Fill pixmap if needed
+ if (fill) {
+ XRenderColor c;
+
+ c.red = c.green = c.blue = 0x8080;
+ c.alpha = 0xffff;
+ XRenderFillRectangle(ps->dpy, PictOpSrc, ps->root_tile_paint.pict, &c, 0, 0, 1, 1);
+ }
+
+ ps->root_tile_fill = fill;
+ ps->root_tile_paint.pixmap = pixmap;
+#ifdef CONFIG_VSYNC_OPENGL
+ if (BKEND_GLX == ps->o.backend)
+ return glx_bind_pixmap(ps, &ps->root_tile_paint.ptex, ps->root_tile_paint.pixmap, 0, 0, 0);
+#endif
+
+ return true;
+}
+
+/**
+ * Paint root window content.
+ */
+static void
+paint_root(session_t *ps, XserverRegion reg_paint) {
+ if (!ps->root_tile_paint.pixmap)
+ get_root_tile(ps);
+
+ win_render(ps, NULL, 0, 0, ps->root_width, ps->root_height, 1.0, reg_paint,
+ NULL, ps->root_tile_paint.pict);
+}
+
+/**
+ * Get a rectangular region a window occupies, excluding shadow.
+ */
+static XserverRegion
+win_get_region(session_t *ps, win *w, bool use_offset) {
+ XRectangle r;
+
+ r.x = (use_offset ? w->a.x: 0);
+ r.y = (use_offset ? w->a.y: 0);
+ r.width = w->widthb;
+ r.height = w->heightb;
+
+ return XFixesCreateRegion(ps->dpy, &r, 1);
+}
+
+/**
+ * Get a rectangular region a window occupies, excluding frame and shadow.
+ */
+static XserverRegion
+win_get_region_noframe(session_t *ps, win *w, bool use_offset) {
+ XRectangle r;
+
+ r.x = (use_offset ? w->a.x: 0) + w->a.border_width + w->left_width;
+ r.y = (use_offset ? w->a.y: 0) + w->a.border_width + w->top_width;
+ r.width = max_i(w->a.width - w->left_width - w->right_width, 0);
+ r.height = max_i(w->a.height - w->top_width - w->bottom_width, 0);
+
+ if (r.width > 0 && r.height > 0)
+ return XFixesCreateRegion(ps->dpy, &r, 1);
+ else
+ return XFixesCreateRegion(ps->dpy, NULL, 0);
+}
+
+/**
+ * Get a rectangular region a window (and possibly its shadow) occupies.
+ *
+ * Note w->shadow and shadow geometry must be correct before calling this
+ * function.
+ */
+static XserverRegion
+win_extents(session_t *ps, win *w) {
+ XRectangle r;
+
+ r.x = w->a.x;
+ r.y = w->a.y;
+ r.width = w->widthb;
+ r.height = w->heightb;
+
+ if (w->shadow) {
+ XRectangle sr;
+
+ sr.x = w->a.x + w->shadow_dx;
+ sr.y = w->a.y + w->shadow_dy;
+ sr.width = w->shadow_width;
+ sr.height = w->shadow_height;
+
+ if (sr.x < r.x) {
+ r.width = (r.x + r.width) - sr.x;
+ r.x = sr.x;
+ }
+
+ if (sr.y < r.y) {
+ r.height = (r.y + r.height) - sr.y;
+ r.y = sr.y;
+ }
+
+ if (sr.x + sr.width > r.x + r.width) {
+ r.width = sr.x + sr.width - r.x;
+ }
+
+ if (sr.y + sr.height > r.y + r.height) {
+ r.height = sr.y + sr.height - r.y;
+ }
+ }
+
+ return XFixesCreateRegion(ps->dpy, &r, 1);
+}
+
+/**
+ * Retrieve the bounding shape of a window.
+ */
+static XserverRegion
+border_size(session_t *ps, win *w, bool use_offset) {
+ // Start with the window rectangular region
+ XserverRegion fin = win_get_region(ps, w, use_offset);
+
+ // Only request for a bounding region if the window is shaped
+ if (w->bounding_shaped) {
+ /*
+ * if window doesn't exist anymore, this will generate an error
+ * as well as not generate a region. Perhaps a better XFixes
+ * architecture would be to have a request that copies instead
+ * of creates, that way you'd just end up with an empty region
+ * instead of an invalid XID.
+ */
+
+ XserverRegion border = XFixesCreateRegionFromWindow(
+ ps->dpy, w->id, WindowRegionBounding);
+
+ if (!border)
+ return fin;
+
+ if (use_offset) {
+ // Translate the region to the correct place
+ XFixesTranslateRegion(ps->dpy, border,
+ w->a.x + w->a.border_width,
+ w->a.y + w->a.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
+ XFixesIntersectRegion(ps->dpy, fin, fin, border);
+ XFixesDestroyRegion(ps->dpy, border);
+ }
+
+ return fin;
+}
+
+/**
+ * Look for the client window of a particular window.
+ */
+static Window
+find_client_win(session_t *ps, Window w) {
+ if (wid_has_prop(ps, w, ps->atom_client)) {
+ return w;
+ }
+
+ Window *children;
+ unsigned int nchildren;
+ unsigned int i;
+ Window ret = 0;
+
+ if (!wid_get_children(ps, w, &children, &nchildren)) {
+ return 0;
+ }
+
+ for (i = 0; i < nchildren; ++i) {
+ if ((ret = find_client_win(ps, children[i])))
+ break;
+ }
+
+ cxfree(children);
+
+ return ret;
+}
+
+/**
+ * Retrieve frame extents from a window.
+ */
+static void
+get_frame_extents(session_t *ps, win *w, Window client) {
+ w->left_width = 0;
+ w->right_width = 0;
+ w->top_width = 0;
+ w->bottom_width = 0;
+
+ winprop_t prop = wid_get_prop(ps, client, ps->atom_frame_extents,
+ 4L, XA_CARDINAL, 32);
+
+ if (4 == prop.nitems) {
+ const long * const extents = prop.data.p32;
+ w->left_width = extents[0];
+ w->right_width = extents[1];
+ w->top_width = extents[2];
+ w->bottom_width = extents[3];
+
+ if (ps->o.frame_opacity)
+ update_reg_ignore_expire(ps, w);
+ }
+
+#ifdef DEBUG_FRAME
+ printf_dbgf("(%#010lx): %d, %d, %d, %d\n", w->id,
+ w->left_width, w->right_width, w->top_width, w->bottom_width);
+#endif
+
+ free_winprop(&prop);
+}
+
+/**
+ * Get alpha <code>Picture</code> for an opacity in <code>double</code>.
+ */
+static inline Picture
+get_alpha_pict_d(session_t *ps, double o) {
+ assert((round(normalize_d(o) / ps->o.alpha_step)) <= round(1.0 / ps->o.alpha_step));
+ return ps->alpha_picts[(int) (round(normalize_d(o)
+ / ps->o.alpha_step))];
+}
+
+/**
+ * Get alpha <code>Picture</code> for an opacity in
+ * <code>opacity_t</code>.
+ */
+static inline Picture
+get_alpha_pict_o(session_t *ps, opacity_t o) {
+ return get_alpha_pict_d(ps, (double) o / OPAQUE);
+}
+
+static win *
+paint_preprocess(session_t *ps, win *list) {
+ win *t = NULL, *next = NULL;
+
+ // Fading step calculation
+ time_ms_t steps = 0L;
+ if (ps->fade_time) {
+ steps = ((get_time_ms() - ps->fade_time) + FADE_DELTA_TOLERANCE * ps->o.fade_delta) / ps->o.fade_delta;
+ }
+ // Reset fade_time if unset, or there appears to be a time disorder
+ if (!ps->fade_time || steps < 0L) {
+ ps->fade_time = get_time_ms();
+ steps = 0L;
+ }
+ ps->fade_time += steps * ps->o.fade_delta;
+
+ XserverRegion last_reg_ignore = None;
+
+ bool unredir_possible = false;
+ // Trace whether it's the highest window to paint
+ bool is_highest = true;
+ for (win *w = list; w; w = next) {
+ bool to_paint = true;
+ const winmode_t mode_old = w->mode;
+
+ // In case calling the fade callback function destroys this window
+ next = w->next;
+ opacity_t opacity_old = w->opacity;
+
+ // Data expiration
+ {
+ // Remove built shadow if needed
+ if (w->flags & WFLAG_SIZE_CHANGE)
+ free_paint(ps, &w->shadow_paint);
+
+ // Destroy reg_ignore on all windows if they should expire
+ if (ps->reg_ignore_expire)
+ free_region(ps, &w->reg_ignore);
+ }
+
+ // Update window opacity target and dim state if asked
+ if (WFLAG_OPCT_CHANGE & w->flags) {
+ calc_opacity(ps, w);
+ calc_dim(ps, w);
+ }
+
+ // Run fading
+ run_fade(ps, w, steps);
+
+ // Opacity will not change, from now on.
+
+ // 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->damaged
+ || w->a.x + w->a.width < 1 || w->a.y + w->a.height < 1
+ || w->a.x >= ps->root_width || w->a.y >= ps->root_height
+ || ((IsUnmapped == w->a.map_state || w->destroyed) && !w->paint.pixmap)
+ || get_alpha_pict_o(ps, w->opacity) == ps->alpha_picts[0]
+ || w->paint_excluded)
+ to_paint = false;
+
+ // to_paint will never change afterward
+
+ // Determine mode as early as possible
+ if (to_paint && (!w->to_paint || w->opacity != opacity_old))
+ win_determine_mode(ps, w);
+
+ if (to_paint) {
+ // Fetch bounding region
+ if (!w->border_size)
+ w->border_size = border_size(ps, w, true);
+
+ // Fetch window extents
+ if (!w->extents)
+ w->extents = win_extents(ps, w);
+
+ // Calculate frame_opacity
+ {
+ double frame_opacity_old = w->frame_opacity;
+
+ if (ps->o.frame_opacity && 1.0 != ps->o.frame_opacity
+ && win_has_frame(w))
+ w->frame_opacity = get_opacity_percent(w) *
+ ps->o.frame_opacity;
+ else
+ w->frame_opacity = 0.0;
+
+ // Destroy all reg_ignore above when frame opaque state changes on
+ // SOLID mode
+ if (w->to_paint && WMODE_SOLID == mode_old
+ && (0.0 == frame_opacity_old) != (0.0 == w->frame_opacity))
+ ps->reg_ignore_expire = true;
+ }
+
+ // Calculate shadow opacity
+ if (w->frame_opacity)
+ w->shadow_opacity = ps->o.shadow_opacity * w->frame_opacity;
+ else
+ w->shadow_opacity = ps->o.shadow_opacity * get_opacity_percent(w);
+ }
+
+ // Add window to damaged area if its painting status changes
+ // or opacity changes
+ if (to_paint != w->to_paint || w->opacity != opacity_old)
+ add_damage_win(ps, w);
+
+ // Destroy all reg_ignore above when window mode changes
+ if ((to_paint && WMODE_SOLID == w->mode)
+ != (w->to_paint && WMODE_SOLID == mode_old))
+ ps->reg_ignore_expire = true;
+
+ if (to_paint) {
+ // Generate ignore region for painting to reduce GPU load
+ if (ps->reg_ignore_expire || !w->to_paint) {
+ free_region(ps, &w->reg_ignore);
+
+ // If the window is solid, we add the window region to the
+ // ignored region
+ if (WMODE_SOLID == w->mode) {
+ if (!w->frame_opacity) {
+ if (w->border_size)
+ w->reg_ignore = copy_region(ps, w->border_size);
+ else
+ w->reg_ignore = win_get_region(ps, w, true);
+ }
+ else {
+ w->reg_ignore = win_get_region_noframe(ps, w, true);
+ if (w->border_size)
+ XFixesIntersectRegion(ps->dpy, w->reg_ignore, w->reg_ignore,
+ w->border_size);
+ }
+
+ if (last_reg_ignore)
+ XFixesUnionRegion(ps->dpy, w->reg_ignore, w->reg_ignore,
+ last_reg_ignore);
+ }
+ // Otherwise we copy the last region over
+ else if (last_reg_ignore)
+ w->reg_ignore = copy_region(ps, last_reg_ignore);
+ else
+ w->reg_ignore = None;
+ }
+
+ last_reg_ignore = w->reg_ignore;
+
+ // (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 && to_paint) {
+ is_highest = false;
+ if (WMODE_SOLID == w->mode
+ && (!w->frame_opacity || !win_has_frame(w))
+ && win_is_fullscreen(ps, w)
+ && !w->unredir_if_possible_excluded)
+ unredir_possible = true;
+ }
+
+ // Reset flags
+ w->flags = 0;
+ }
+
+ // Avoid setting w->to_paint if w is to be freed
+ bool destroyed = (w->opacity_tgt == w->opacity && w->destroyed);
+
+ if (to_paint) {
+ w->prev_trans = t;
+ t = w;
+ }
+ else {
+ assert(w->destroyed == (w->fade_callback == destroy_callback));
+ check_fade_fin(ps, w);
+ }
+
+ if (!destroyed)
+ w->to_paint = to_paint;
+ }
+
+
+ // If possible, unredirect all windows and stop painting
+ if (UNSET != ps->o.redirected_force)
+ unredir_possible = !ps->o.redirected_force;
+
+ // If there's no window to paint, and the screen isn't redirected,
+ // don't redirect it.
+ if (ps->o.unredir_if_possible && is_highest && !ps->redirected)
+ unredir_possible = true;
+ if (unredir_possible) {
+ if (ps->redirected) {
+ if (!ps->o.unredir_if_possible_delay || ps->tmout_unredir_hit)
+ redir_stop(ps);
+ else if (!ps->tmout_unredir->enabled) {
+ timeout_reset(ps, ps->tmout_unredir);
+ ps->tmout_unredir->enabled = true;
+ }
+ }
+ }
+ else {
+ ps->tmout_unredir->enabled = false;
+ redir_start(ps);
+ }
+
+ return t;
+}
+
+/**
+ * Paint the shadow of a window.
+ */
+static inline void
+win_paint_shadow(session_t *ps, win *w,
+ XserverRegion reg_paint, const reg_data_t *pcache_reg) {
+ if (!paint_isvalid(ps, &w->shadow_paint)) {
+ printf_errf("(%#010lx): Missing painting data. This is a bad sign.", w->id);
+ return;
+ }
+
+ render(ps, 0, 0, w->a.x + w->shadow_dx, w->a.y + w->shadow_dy,
+ w->shadow_width, w->shadow_height, w->shadow_opacity, true, false,
+ w->shadow_paint.pict, w->shadow_paint.ptex, reg_paint, pcache_reg);
+}
+
+/**
+ * Create an picture.
+ */
+static inline Picture
+xr_build_picture(session_t *ps, int wid, int hei,
+ XRenderPictFormat *pictfmt) {
+ if (!pictfmt)
+ pictfmt = XRenderFindVisualFormat(ps->dpy, ps->vis);
+
+ int depth = pictfmt->depth;
+
+ Pixmap tmp_pixmap = XCreatePixmap(ps->dpy, ps->root, wid, hei, depth);
+ if (!tmp_pixmap)
+ return None;
+
+ Picture tmp_picture = XRenderCreatePicture(ps->dpy, tmp_pixmap,
+ pictfmt, 0, 0);
+ free_pixmap(ps, &tmp_pixmap);
+
+ return tmp_picture;
+}
+
+/**
+ * @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, Picture tgt_buffer,
+ int x, int y, int wid, int hei, XFixed **blur_kerns,
+ XserverRegion reg_clip) {
+ assert(blur_kerns[0]);
+
+ // Directly copying from tgt_buffer to it does not work, so we create a
+ // Picture in the middle.
+ Picture tmp_picture = xr_build_picture(ps, wid, hei, NULL);
+
+ if (!tmp_picture) {
+ printf_errf("(): Failed to build intermediate Picture.");
+ return false;
+ }
+
+ if (reg_clip && tmp_picture)
+ XFixesSetPictureClipRegion(ps->dpy, tmp_picture, reg_clip, 0, 0);
+
+ Picture src_pict = tgt_buffer, dst_pict = tmp_picture;
+ for (int i = 0; blur_kerns[i]; ++i) {
+ assert(i < MAX_BLUR_PASS - 1);
+ XFixed *convolution_blur = blur_kerns[i];
+ int kwid = XFixedToDouble(convolution_blur[0]),
+ khei = XFixedToDouble(convolution_blur[1]);
+ 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.
+ XRenderSetPictureFilter(ps->dpy, src_pict, XRFILTER_CONVOLUTION,
+ convolution_blur, kwid * khei + 2);
+ XRenderComposite(ps->dpy, PictOpSrc, src_pict, 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);
+
+ {
+ XserverRegion tmp = src_pict;
+ src_pict = dst_pict;
+ dst_pict = tmp;
+ }
+ }
+
+ if (src_pict != tgt_buffer)
+ XRenderComposite(ps->dpy, PictOpSrc, src_pict, None, tgt_buffer,
+ 0, 0, 0, 0, x, y, wid, hei);
+
+ free_picture(ps, &tmp_picture);
+
+ return true;
+}
+
+/**
+ * Blur the background of a window.
+ */
+static inline void
+win_blur_background(session_t *ps, win *w, Picture tgt_buffer,
+ XserverRegion reg_paint, const reg_data_t *pcache_reg) {
+ const int x = w->a.x;
+ const int y = w->a.y;
+ const int wid = w->widthb;
+ const int hei = w->heightb;
+
+ 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 - get_opacity_percent(w) * (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 < MAX_BLUR_PASS; ++i) {
+ XFixed *kern_src = ps->o.blur_kerns[i];
+ XFixed *kern_dst = ps->blur_kerns_cache[i];
+ assert(i < MAX_BLUR_PASS);
+ if (!kern_src) {
+ assert(!kern_dst);
+ break;
+ }
+
+ assert(!kern_dst
+ || (kern_src[0] == kern_dst[0] && kern_src[1] == kern_dst[1]));
+
+ // Skip for fixed factor_center if the cache exists already
+ if (ps->o.blur_background_fixed && kern_dst) continue;
+
+ int kwid = XFixedToDouble(kern_src[0]),
+ khei = XFixedToDouble(kern_src[1]);
+
+ // Allocate cache space if needed
+ if (!kern_dst) {
+ kern_dst = malloc((kwid * khei + 2) * sizeof(XFixed));
+ if (!kern_dst) {
+ printf_errf("(): Failed to allocate memory for blur kernel.");
+ return;
+ }
+ ps->blur_kerns_cache[i] = kern_dst;
+ }
+
+ // Modify the factor of the center pixel
+ kern_src[2 + (khei / 2) * kwid + kwid / 2] =
+ XDoubleToFixed(factor_center);
+
+ // Copy over
+ memcpy(kern_dst, kern_src, (kwid * khei + 2) * sizeof(XFixed));
+ normalize_conv_kern(kwid, khei, kern_dst + 2);
+ }
+
+ // Minimize the region we try to blur, if the window itself is not
+ // opaque, only the frame is.
+ XserverRegion reg_noframe = None;
+ if (WMODE_SOLID == w->mode) {
+ XserverRegion reg_all = border_size(ps, w, false);
+ reg_noframe = win_get_region_noframe(ps, w, false);
+ XFixesSubtractRegion(ps->dpy, reg_noframe, reg_all, reg_noframe);
+ free_region(ps, &reg_all);
+ }
+ xr_blur_dst(ps, tgt_buffer, x, y, wid, hei, ps->blur_kerns_cache,
+ reg_noframe);
+ free_region(ps, &reg_noframe);
+ }
+ break;
+#ifdef CONFIG_VSYNC_OPENGL_GLSL
+ case BKEND_GLX:
+ // TODO: Handle frame opacity
+ glx_blur_dst(ps, x, y, wid, hei, ps->glx_z - 0.5, factor_center,
+ reg_paint, pcache_reg, &w->glx_blur_cache);
+ break;
+#endif
+ default:
+ assert(0);
+ }
+}
+
+static void
+render(session_t *ps, int x, int y, int dx, int dy, int wid, int hei,
+ double opacity, bool argb, bool neg,
+ Picture pict, glx_texture_t *ptex,
+ XserverRegion reg_paint, const reg_data_t *pcache_reg) {
+ switch (ps->o.backend) {
+ case BKEND_XRENDER:
+ case BKEND_XR_GLX_HYBRID:
+ {
+ Picture alpha_pict = get_alpha_pict_d(ps, opacity);
+ if (alpha_pict != ps->alpha_picts[0]) {
+ int op = ((!argb && !alpha_pict) ? PictOpSrc: PictOpOver);
+ XRenderComposite(ps->dpy, op, pict, alpha_pict,
+ ps->tgt_buffer.pict, x, y, 0, 0, dx, dy, wid, hei);
+ }
+ break;
+ }
+#ifdef CONFIG_VSYNC_OPENGL
+ case BKEND_GLX:
+ glx_render(ps, ptex, x, y, dx, dy, wid, hei,
+ ps->glx_z, opacity, neg, reg_paint, pcache_reg);
+ ps->glx_z += 1;
+ break;
+#endif
+ default:
+ assert(0);
+ }
+}
+
+/**
+ * Paint a window itself and dim it if asked.
+ */
+static inline void
+win_paint_win(session_t *ps, win *w, XserverRegion reg_paint,
+ const reg_data_t *pcache_reg) {
+ glx_mark(ps, w->id, true);
+
+ // Fetch Pixmap
+ if (!w->paint.pixmap && ps->has_name_pixmap) {
+ set_ignore_next(ps);
+ w->paint.pixmap = XCompositeNameWindowPixmap(ps->dpy, w->id);
+ if (w->paint.pixmap)
+ free_fence(ps, &w->fence);
+ }
+
+ Drawable draw = w->paint.pixmap;
+ if (!draw)
+ draw = w->id;
+
+ // XRender: Build picture
+ if (bkend_use_xrender(ps) && !w->paint.pict) {
+ {
+ XRenderPictureAttributes pa = {
+ .subwindow_mode = IncludeInferiors,
+ };
+
+ w->paint.pict = XRenderCreatePicture(ps->dpy, draw, w->pictfmt,
+ CPSubwindowMode, &pa);
+ }
+ }
+
+ if (IsViewable == w->a.map_state)
+ xr_sync(ps, draw, &w->fence);
+
+ // 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, 0,
+ (!ps->o.glx_no_rebind_pixmap && w->pixmap_damaged))) {
+ printf_errf("(%#010lx): Failed to bind texture. Expect troubles.", w->id);
+ }
+ w->pixmap_damaged = false;
+
+ if (!paint_isvalid(ps, &w->paint)) {
+ printf_errf("(%#010lx): Missing painting data. This is a bad sign.", w->id);
+ return;
+ }
+
+ const int x = w->a.x;
+ const int y = w->a.y;
+ const int wid = w->widthb;
+ const int hei = w->heightb;
+
+ Picture pict = w->paint.pict;
+
+ // Invert window color, if required
+ if (bkend_use_xrender(ps) && w->invert_color) {
+ Picture newpict = xr_build_picture(ps, wid, hei, w->pictfmt);
+ if (newpict) {
+ // Apply clipping region to save some CPU
+ if (reg_paint) {
+ XserverRegion reg = copy_region(ps, reg_paint);
+ XFixesTranslateRegion(ps->dpy, reg, -x, -y);
+ XFixesSetPictureClipRegion(ps->dpy, newpict, 0, 0, reg);
+ free_region(ps, &reg);
+ }
+
+ XRenderComposite(ps->dpy, PictOpSrc, pict, None,
+ newpict, 0, 0, 0, 0, 0, 0, wid, hei);
+ XRenderComposite(ps->dpy, PictOpDifference, ps->white_picture, 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 (WMODE_ARGB == w->mode)
+ XRenderComposite(ps->dpy, PictOpInReverse, pict, None,
+ newpict, 0, 0, 0, 0, 0, 0, wid, hei);
+ pict = newpict;
+ }
+ }
+
+ const double dopacity = get_opacity_percent(w);
+
+ if (!w->frame_opacity) {
+ win_render(ps, w, 0, 0, wid, hei, dopacity, reg_paint, pcache_reg, pict);
+ }
+ else {
+ // Painting parameters
+ const int t = w->a.border_width + w->top_width;
+ const int l = w->a.border_width + w->left_width;
+ const int b = w->a.border_width + w->bottom_width;
+ const int r = w->a.border_width + w->right_width;
+
+#define COMP_BDR(cx, cy, cwid, chei) \
+ win_render(ps, w, (cx), (cy), (cwid), (chei), w->frame_opacity, \
+ reg_paint, pcache_reg, pict)
+
+ // The following complicated logic is required because some broken
+ // window managers (I'm talking about you, Openbox!) that makes
+ // top_width + bottom_width > height in some cases.
+
+ // top
+ int phei = min_i(t, hei);
+ if (phei > 0)
+ COMP_BDR(0, 0, wid, phei);
+
+ if (hei > t) {
+ phei = min_i(hei - t, b);
+
+ // bottom
+ if (phei > 0)
+ COMP_BDR(0, hei - phei, wid, phei);
+
+ phei = hei - t - phei;
+ if (phei > 0) {
+ int pwid = min_i(l, wid);
+ // left
+ if (pwid > 0)
+ COMP_BDR(0, t, pwid, phei);
+
+ if (wid > l) {
+ pwid = min_i(wid - l, r);
+
+ // right
+ if (pwid > 0)
+ COMP_BDR(wid - pwid, t, pwid, phei);
+
+ pwid = wid - l - pwid;
+ if (pwid > 0) {
+ // body
+ win_render(ps, w, l, t, pwid, phei, dopacity, reg_paint, pcache_reg, pict);
+ }
+ }
+ }
+ }
+ }
+
+#undef COMP_BDR
+
+ if (pict != w->paint.pict)
+ free_picture(ps, &pict);
+
+ // Dimming the window if needed
+ if (w->dim) {
+ double dim_opacity = ps->o.inactive_dim;
+ if (!ps->o.inactive_dim_fixed)
+ dim_opacity *= get_opacity_percent(w);
+
+ switch (ps->o.backend) {
+ case BKEND_XRENDER:
+ case BKEND_XR_GLX_HYBRID:
+ {
+ unsigned short cval = 0xffff * dim_opacity;
+
+ // Premultiply color
+ XRenderColor color = {
+ .red = 0, .green = 0, .blue = 0, .alpha = cval,
+ };
+
+ XRectangle rect = {
+ .x = x,
+ .y = y,
+ .width = wid,
+ .height = hei,
+ };
+
+ XRenderFillRectangles(ps->dpy, PictOpOver, ps->tgt_buffer.pict,
+ &color, &rect, 1);
+ }
+ break;
+#ifdef CONFIG_VSYNC_OPENGL
+ case BKEND_GLX:
+ glx_dim_dst(ps, x, y, wid, hei, ps->glx_z - 0.7, dim_opacity,
+ reg_paint, pcache_reg);
+ break;
+#endif
+ }
+ }
+
+ glx_mark(ps, w->id, false);
+}
+
+/**
+ * Rebuild cached <code>screen_reg</code>.
+ */
+static void
+rebuild_screen_reg(session_t *ps) {
+ if (ps->screen_reg)
+ XFixesDestroyRegion(ps->dpy, ps->screen_reg);
+ ps->screen_reg = get_screen_region(ps);
+}
+
+/**
+ * Rebuild <code>shadow_exclude_reg</code>.
+ */
+static void
+rebuild_shadow_exclude_reg(session_t *ps) {
+ free_region(ps, &ps->shadow_exclude_reg);
+ XRectangle rect = geom_to_rect(ps, &ps->o.shadow_exclude_reg_geom, NULL);
+ ps->shadow_exclude_reg = rect_to_reg(ps, &rect);
+}
+
+static void
+paint_all(session_t *ps, XserverRegion region, XserverRegion region_real, win *t) {
+ if (!region_real)
+ region_real = region;
+
+#ifdef DEBUG_REPAINT
+ static struct timespec last_paint = { 0 };
+#endif
+ XserverRegion reg_paint = None, reg_tmp = None, reg_tmp2 = None;
+
+#ifdef CONFIG_VSYNC_OPENGL
+ if (bkend_use_glx(ps)) {
+ glx_paint_pre(ps, &region);
+ }
+#endif
+
+ if (!region) {
+ region_real = region = get_screen_region(ps);
+ }
+ else {
+ // Remove the damaged area out of screen
+ XFixesIntersectRegion(ps->dpy, region, region, ps->screen_reg);
+ }
+
+#ifdef MONITOR_REPAINT
+ // Note: MONITOR_REPAINT cannot work with DBE right now.
+ // Picture old_tgt_buffer = ps->tgt_buffer.pict;
+ ps->tgt_buffer.pict = ps->tgt_picture;
+#else
+ if (!paint_isvalid(ps, &ps->tgt_buffer)) {
+ // DBE painting mode: Directly paint to a Picture of the back buffer
+ if (BKEND_XRENDER == ps->o.backend && ps->o.dbe) {
+ ps->tgt_buffer.pict = XRenderCreatePicture(ps->dpy, ps->root_dbe,
+ XRenderFindVisualFormat(ps->dpy, ps->vis),
+ 0, 0);
+ }
+ // No-DBE painting mode: Paint to an intermediate Picture then paint
+ // the Picture to root window
+ else {
+ if (!ps->tgt_buffer.pixmap) {
+ free_paint(ps, &ps->tgt_buffer);
+ ps->tgt_buffer.pixmap = XCreatePixmap(ps->dpy, ps->root,
+ ps->root_width, ps->root_height, ps->depth);
+ }
+
+ if (BKEND_GLX != ps->o.backend)
+ ps->tgt_buffer.pict = XRenderCreatePicture(ps->dpy,
+ ps->tgt_buffer.pixmap, XRenderFindVisualFormat(ps->dpy, ps->vis),
+ 0, 0);
+ }
+ }
+#endif
+
+ if (BKEND_XRENDER == ps->o.backend)
+ XFixesSetPictureClipRegion(ps->dpy, ps->tgt_picture, 0, 0, region_real);
+
+#ifdef MONITOR_REPAINT
+ switch (ps->o.backend) {
+ case BKEND_XRENDER:
+ XRenderComposite(ps->dpy, PictOpSrc, ps->black_picture, None,
+ ps->tgt_picture, 0, 0, 0, 0, 0, 0,
+ ps->root_width, ps->root_height);
+ break;
+ case BKEND_GLX:
+ case BKEND_XR_GLX_HYBRID:
+ glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ break;
+ }
+#endif
+
+ if (t && t->reg_ignore) {
+ // Calculate the region upon which the root window is to be painted
+ // based on the ignore region of the lowest window, if available
+ reg_paint = reg_tmp = XFixesCreateRegion(ps->dpy, NULL, 0);
+ XFixesSubtractRegion(ps->dpy, reg_paint, region, t->reg_ignore);
+ }
+ else {
+ reg_paint = region;
+ }
+
+ set_tgt_clip(ps, reg_paint, NULL);
+ paint_root(ps, reg_paint);
+
+ // Create temporary regions for use during painting
+ if (!reg_tmp)
+ reg_tmp = XFixesCreateRegion(ps->dpy, NULL, 0);
+ reg_tmp2 = XFixesCreateRegion(ps->dpy, NULL, 0);
+
+ for (win *w = t; w; w = w->prev_trans) {
+ // Painting shadow
+ if (w->shadow) {
+ // Lazy shadow building
+ if (!paint_isvalid(ps, &w->shadow_paint))
+ win_build_shadow(ps, w, 1);
+
+ // Shadow is to be painted based on the ignore region of current
+ // window
+ if (w->reg_ignore) {
+ if (w == t) {
+ // If it's the first cycle and reg_tmp2 is not ready, calculate
+ // the paint region here
+ reg_paint = reg_tmp;
+ XFixesSubtractRegion(ps->dpy, reg_paint, region, w->reg_ignore);
+ }
+ else {
+ // Otherwise, used the cached region during last cycle
+ reg_paint = reg_tmp2;
+ }
+ XFixesIntersectRegion(ps->dpy, reg_paint, reg_paint, w->extents);
+ }
+ else {
+ reg_paint = reg_tmp;
+ XFixesIntersectRegion(ps->dpy, reg_paint, region, w->extents);
+ }
+
+ if (ps->shadow_exclude_reg)
+ XFixesSubtractRegion(ps->dpy, reg_paint, reg_paint,
+ ps->shadow_exclude_reg);
+
+ // Might be worthwhile to crop the region to shadow border
+ {
+ XRectangle rec_shadow_border = {
+ .x = w->a.x + w->shadow_dx,
+ .y = w->a.y + w->shadow_dy,
+ .width = w->shadow_width,
+ .height = w->shadow_height
+ };
+ XserverRegion reg_shadow = XFixesCreateRegion(ps->dpy,
+ &rec_shadow_border, 1);
+ XFixesIntersectRegion(ps->dpy, reg_paint, reg_paint, reg_shadow);
+ free_region(ps, &reg_shadow);
+ }
+
+ // Clear the shadow here instead of in make_shadow() for saving GPU
+ // power and handling shaped windows
+ if (ps->o.clear_shadow && w->border_size)
+ XFixesSubtractRegion(ps->dpy, reg_paint, reg_paint, w->border_size);
+
+#ifdef CONFIG_XINERAMA
+ if (ps->o.xinerama_shadow_crop && w->xinerama_scr >= 0)
+ XFixesIntersectRegion(ps->dpy, reg_paint, reg_paint,
+ ps->xinerama_scr_regs[w->xinerama_scr]);
+#endif
+
+ // Detect if the region is empty before painting
+ {
+ reg_data_t cache_reg = REG_DATA_INIT;
+ if (region == reg_paint
+ || !is_region_empty(ps, reg_paint, &cache_reg)) {
+ set_tgt_clip(ps, reg_paint, &cache_reg);
+
+ win_paint_shadow(ps, w, reg_paint, &cache_reg);
+ }
+ free_reg_data(&cache_reg);
+ }
+ }
+
+ // Calculate the region based on the reg_ignore of the next (higher)
+ // window and the bounding region
+ reg_paint = reg_tmp;
+ if (w->prev_trans && w->prev_trans->reg_ignore) {
+ XFixesSubtractRegion(ps->dpy, reg_paint, region,
+ w->prev_trans->reg_ignore);
+ // Copy the subtracted region to be used for shadow painting in next
+ // cycle
+ XFixesCopyRegion(ps->dpy, reg_tmp2, reg_paint);
+
+ if (w->border_size)
+ XFixesIntersectRegion(ps->dpy, reg_paint, reg_paint, w->border_size);
+ }
+ else {
+ if (w->border_size)
+ XFixesIntersectRegion(ps->dpy, reg_paint, region, w->border_size);
+ else
+ reg_paint = region;
+ }
+
+ {
+ reg_data_t cache_reg = REG_DATA_INIT;
+ if (!is_region_empty(ps, reg_paint, &cache_reg)) {
+ set_tgt_clip(ps, reg_paint, &cache_reg);
+
+ /* Here we redraw the background of the transparent window if we want
+ to do anything special (i.e. anything other than showing the
+ windows and desktop prestacked behind of the window).
+ For example, if you want to blur the background or show another
+ background pixmap entirely here is the place to do it; simply
+ draw the new background onto ps->tgt_buffer.pict before continuing! */
+ switch (ps->o.backend) {
+ case BKEND_XRENDER:
+ case BKEND_XR_GLX_HYBRID:
+ {
+ if (w->show_root_tile) {
+ XRenderComposite (ps->dpy, PictOpSrc, ps->root_tile_paint.pict, None, ps->tgt_buffer.pict,
+ w->a.x, w->a.y, w->a.x, w->a.y,
+ w->a.x, w->a.y, w->widthb, w->heightb);
+ }
+ else if (w->show_black_background) {
+ XRenderComposite (ps->dpy, PictOpSrc, ps->black_picture, None, ps->tgt_buffer.pict,
+ w->a.x, w->a.y, w->a.x, w->a.y,
+ w->a.x, w->a.y, w->widthb, w->heightb);
+ }
+ break;
+ }
+#ifdef CONFIG_VSYNC_OPENGL
+ case BKEND_GLX:
+ {
+ if (w->show_root_tile) {
+ glx_render(ps, ps->root_tile_paint.ptex, w->a.x, w->a.y, w->a.x, w->a.y, w->widthb, w->heightb,
+ ps->glx_z, 1.0, false, reg_paint, &cache_reg);
+ }
+ else if (w->show_black_background) {
+ glx_render_specified_color(ps, 0, w->a.x, w->a.y, w->widthb, w->heightb,
+ ps->glx_z, reg_paint, &cache_reg);
+ }
+ break;
+ }
+#endif
+ }
+
+ // Blur window background
+ if (w->blur_background && (WMODE_SOLID != w->mode
+ || (ps->o.blur_background_frame && w->frame_opacity))) {
+ win_blur_background(ps, w, ps->tgt_buffer.pict, reg_paint, &cache_reg);
+ }
+
+ // Painting the window
+ win_paint_win(ps, w, reg_paint, &cache_reg);
+ }
+ free_reg_data(&cache_reg);
+ }
+ }
+
+ // Free up all temporary regions
+ XFixesDestroyRegion(ps->dpy, reg_tmp);
+ XFixesDestroyRegion(ps->dpy, reg_tmp2);
+
+ // Do this as early as possible
+ if (!ps->o.dbe)
+ set_tgt_clip(ps, None, NULL);
+
+ if (ps->o.vsync) {
+ // Make sure all previous requests are processed to achieve best
+ // effect
+ XSync(ps->dpy, False);
+#ifdef CONFIG_VSYNC_OPENGL
+ if (ps->glx_context) {
+ if (ps->o.vsync_use_glfinish)
+ glFinish();
+ else
+ glFlush();
+ glXWaitX();
+ }
+#endif
+ }
+
+ // Wait for VBlank. We could do it aggressively (send the painting
+ // request and XFlush() on VBlank) or conservatively (send the request
+ // only on VBlank).
+ if (!ps->o.vsync_aggressive)
+ vsync_wait(ps);
+
+ switch (ps->o.backend) {
+ case BKEND_XRENDER:
+ // DBE painting mode, only need to swap the buffer
+ if (ps->o.dbe) {
+ XdbeSwapInfo swap_info = {
+ .swap_window = get_tgt_window(ps),
+ // Is it safe to use XdbeUndefined?
+ .swap_action = XdbeCopied
+ };
+ XdbeSwapBuffers(ps->dpy, &swap_info, 1);
+ }
+ // No-DBE painting mode
+ else if (ps->tgt_buffer.pict != ps->tgt_picture) {
+ XRenderComposite(
+ ps->dpy, PictOpSrc, ps->tgt_buffer.pict, None,
+ ps->tgt_picture, 0, 0, 0, 0,
+ 0, 0, ps->root_width, ps->root_height);
+ }
+ break;
+#ifdef CONFIG_VSYNC_OPENGL
+ case BKEND_XR_GLX_HYBRID:
+ XSync(ps->dpy, False);
+ if (ps->o.vsync_use_glfinish)
+ glFinish();
+ else
+ glFlush();
+ glXWaitX();
+ assert(ps->tgt_buffer.pixmap);
+ xr_sync(ps, ps->tgt_buffer.pixmap, &ps->tgt_buffer_fence);
+ paint_bind_tex_real(ps, &ps->tgt_buffer,
+ ps->root_width, ps->root_height, ps->depth,
+ !ps->o.glx_no_rebind_pixmap);
+ // See #163
+ xr_sync(ps, ps->tgt_buffer.pixmap, &ps->tgt_buffer_fence);
+ 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, region_real, NULL);
+ // No break here!
+ case BKEND_GLX:
+ if (ps->o.glx_use_copysubbuffermesa)
+ glx_swap_copysubbuffermesa(ps, region_real);
+ else
+ glXSwapBuffers(ps->dpy, get_tgt_window(ps));
+ break;
+#endif
+ default:
+ assert(0);
+ }
+ glx_mark_frame(ps);
+
+ if (ps->o.vsync_aggressive)
+ vsync_wait(ps);
+
+ XFlush(ps->dpy);
+
+#ifdef CONFIG_VSYNC_OPENGL
+ if (ps->glx_context) {
+ glFlush();
+ glXWaitX();
+ }
+#endif
+
+ XFixesDestroyRegion(ps->dpy, region);
+
+#ifdef DEBUG_REPAINT
+ print_timestamp(ps);
+ struct timespec now = get_time_timespec();
+ struct timespec diff = { 0 };
+ timespec_subtract(&diff, &now, &last_paint);
+ printf("[ %5ld:%09ld ] ", diff.tv_sec, diff.tv_nsec);
+ last_paint = now;
+ printf("paint:");
+ for (win *w = t; w; w = w->prev_trans)
+ printf(" %#010lx", w->id);
+ putchar('\n');
+ fflush(stdout);
+#endif
+
+ // Check if fading is finished on all painted windows
+ {
+ win *pprev = NULL;
+ for (win *w = t; w; w = pprev) {
+ pprev = w->prev_trans;
+ check_fade_fin(ps, w);
+ }
+ }
+}
+
+static void
+add_damage(session_t *ps, XserverRegion damage) {
+ // Ignore damage when screen isn't redirected
+ if (!ps->redirected)
+ free_region(ps, &damage);
+
+ if (!damage) return;
+ if (ps->all_damage) {
+ XFixesUnionRegion(ps->dpy, ps->all_damage, ps->all_damage, damage);
+ XFixesDestroyRegion(ps->dpy, damage);
+ } else {
+ ps->all_damage = damage;
+ }
+}
+
+static void
+repair_win(session_t *ps, win *w) {
+ if (IsViewable != w->a.map_state)
+ return;
+
+ XserverRegion parts;
+
+ if (!w->damaged) {
+ parts = win_extents(ps, w);
+ set_ignore_next(ps);
+ XDamageSubtract(ps->dpy, w->damage, None, None);
+ } else {
+ parts = XFixesCreateRegion(ps->dpy, 0, 0);
+ set_ignore_next(ps);
+ XDamageSubtract(ps->dpy, w->damage, None, parts);
+ XFixesTranslateRegion(ps->dpy, parts,
+ w->a.x + w->a.border_width,
+ w->a.y + w->a.border_width);
+ }
+
+ w->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) return;
+
+ // Remove the part in the damage area that could be ignored
+ if (!ps->reg_ignore_expire && w->prev_trans && w->prev_trans->reg_ignore)
+ XFixesSubtractRegion(ps->dpy, parts, parts, w->prev_trans->reg_ignore);
+
+ add_damage(ps, parts);
+}
+
+static wintype_t
+wid_get_prop_wintype(session_t *ps, Window wid) {
+ set_ignore_next(ps);
+ winprop_t prop = wid_get_prop(ps, wid, ps->atom_win_type, 32L, XA_ATOM, 32);
+
+ for (unsigned i = 0; i < prop.nitems; ++i) {
+ for (wintype_t j = 1; j < NUM_WINTYPES; ++j) {
+ if (ps->atoms_wintypes[j] == (Atom) prop.data.p32[i]) {
+ free_winprop(&prop);
+ return j;
+ }
+ }
+ }
+
+ free_winprop(&prop);
+
+ return WINTYPE_UNKNOWN;
+}
+
+static void
+map_win(session_t *ps, Window id) {
+ // Unmap overlay window if it got mapped but we are currently not
+ // in redirected state.
+ if (ps->overlay && id == ps->overlay && !ps->redirected) {
+ XUnmapWindow(ps->dpy, ps->overlay);
+ XFlush(ps->dpy);
+ }
+
+ win *w = find_win(ps, id);
+
+#ifdef DEBUG_EVENTS
+ printf_dbgf("(%#010lx \"%s\"): %p\n", id, (w ? w->name: NULL), w);
+#endif
+
+ // Don't care about window mapping if it's an InputOnly window
+ // Try avoiding mapping a window twice
+ if (!w || InputOnly == w->a.class
+ || IsViewable == w->a.map_state)
+ return;
+
+ assert(!win_is_focused_real(ps, w));
+
+ w->a.map_state = IsViewable;
+
+ cxinerama_win_upd_scr(ps, w);
+
+ // Call XSelectInput() before reading properties so that no property
+ // changes are lost
+ XSelectInput(ps->dpy, id, determine_evmask(ps, id, WIN_EVMODE_FRAME));
+
+ // Notify compton when the shape of a window changes
+ if (ps->shape_exists) {
+ XShapeSelectInput(ps->dpy, id, ShapeNotifyMask);
+ }
+
+ // Make sure the XSelectInput() requests are sent
+ XFlush(ps->dpy);
+
+ /* This needs to be here since we don't get PropertyNotify when unmapped */
+ w->opacity = wid_get_opacity_prop(ps, w->id, OPAQUE);
+ w->show_root_tile = determine_window_transparent_to_desktop(ps, id);
+ w->show_black_background = determine_window_transparent_to_black(ps, id);
+
+ // Update window mode here to check for ARGB windows
+ win_determine_mode(ps, w);
+
+ // Detect client window here instead of in add_win() as the client
+ // window should have been prepared at this point
+ if (!w->client_win) {
+ win_recheck_client(ps, w);
+ }
+ else {
+ // Re-mark client window here
+ win_mark_client(ps, w, w->client_win);
+ }
+
+ assert(w->client_win);
+
+#ifdef DEBUG_WINTYPE
+ printf_dbgf("(%#010lx): type %s\n", w->id, WINTYPES[w->window_type]);
+#endif
+
+ // Detect if the window is shaped or has rounded corners
+ win_update_shape_raw(ps, w);
+
+ // FocusIn/Out may be ignored when the window is unmapped, so we must
+ // recheck focus here
+ if (ps->o.track_focus)
+ recheck_focus(ps);
+
+ // Update window focus state
+ win_update_focused(ps, w);
+
+ // Update opacity and dim state
+ win_update_opacity_prop(ps, w);
+ w->flags |= WFLAG_OPCT_CHANGE;
+
+ // Check for _TDE_WM_WINDOW_SHADOW
+ if (ps->o.respect_prop_shadow)
+ win_update_prop_shadow_raw(ps, w);
+
+ // Many things above could affect shadow
+ win_determine_shadow(ps, w);
+
+ // Set fading state
+ w->in_openclose = true;
+ set_fade_callback(ps, w, finish_map_win, true);
+ win_determine_fade(ps, w);
+
+ win_determine_blur_background(ps, w);
+
+ w->damaged = false;
+
+ /* if any configure events happened while
+ the window was unmapped, then configure
+ the window to its correct place */
+ if (w->need_configure) {
+ configure_win(ps, &w->queue_configure);
+ }
+
+#ifdef CONFIG_DBUS
+ // Send D-Bus signal
+ if (ps->o.dbus) {
+ cdbus_ev_win_mapped(ps, w);
+ }
+#endif
+}
+
+static void
+finish_map_win(session_t *ps, win *w) {
+ w->in_openclose = false;
+ if (ps->o.no_fading_openclose) {
+ win_determine_fade(ps, w);
+ }
+}
+
+static void
+finish_unmap_win(session_t *ps, win *w) {
+ w->damaged = false;
+
+ w->in_openclose = false;
+
+ update_reg_ignore_expire(ps, w);
+
+ if (w->extents != None) {
+ /* destroys region */
+ add_damage(ps, w->extents);
+ w->extents = None;
+ }
+
+ free_wpaint(ps, w);
+ free_region(ps, &w->border_size);
+ free_paint(ps, &w->shadow_paint);
+}
+
+static void
+unmap_callback(session_t *ps, win *w) {
+ finish_unmap_win(ps, w);
+}
+
+static void
+unmap_win(session_t *ps, win *w) {
+ if (!w || IsUnmapped == w->a.map_state) return;
+
+ // One last synchronization
+ if (w->paint.pixmap)
+ xr_sync(ps, w->paint.pixmap, &w->fence);
+ free_fence(ps, &w->fence);
+
+ // Set focus out
+ win_set_focused(ps, w, false);
+
+ w->a.map_state = IsUnmapped;
+
+ // Fading out
+ w->flags |= WFLAG_OPCT_CHANGE;
+ set_fade_callback(ps, w, unmap_callback, false);
+ w->in_openclose = true;
+ win_determine_fade(ps, w);
+
+ // Validate pixmap if we have to do fading
+ if (w->fade)
+ win_validate_pixmap(ps, w);
+
+ // don't care about properties anymore
+ win_ev_stop(ps, w);
+
+#ifdef CONFIG_DBUS
+ // Send D-Bus signal
+ if (ps->o.dbus) {
+ cdbus_ev_win_unmapped(ps, w);
+ }
+#endif
+}
+
+static opacity_t
+wid_get_opacity_prop(session_t *ps, Window wid, opacity_t def) {
+ opacity_t val = def;
+
+ winprop_t prop = wid_get_prop(ps, wid, ps->atom_opacity, 1L,
+ XA_CARDINAL, 32);
+
+ if (prop.nitems)
+ val = *prop.data.p32;
+
+ free_winprop(&prop);
+
+ return val;
+}
+
+static double
+get_opacity_percent(win *w) {
+ return ((double) w->opacity) / OPAQUE;
+}
+
+static Bool
+get_window_transparent_to_desktop(const session_t *ps, Window w)
+{
+ Atom actual;
+ int format;
+ unsigned long n, left;
+
+ unsigned char *data;
+ int result = XGetWindowProperty (ps->dpy, w, ps->atom_win_type_tde_transparent_to_desktop, 0L, 1L, False,
+ XA_ATOM, &actual, &format,
+ &n, &left, &data);
+
+ if (result == Success && data != None && format == 32 )
+ {
+ Atom a;
+ a = *(long*)data;
+ XFree ( (void *) data);
+ return True;
+ }
+ return False;
+}
+
+static Bool
+get_window_transparent_to_black(const session_t *ps, Window w)
+{
+ Atom actual;
+ int format;
+ unsigned long n, left;
+
+ unsigned char *data;
+ int result = XGetWindowProperty (ps->dpy, w, ps->atom_win_type_tde_transparent_to_black, 0L, 1L, False,
+ XA_ATOM, &actual, &format,
+ &n, &left, &data);
+
+ if (result == Success && data != None && format == 32 )
+ {
+ Atom a;
+ a = *(long*)data;
+ XFree ( (void *) data);
+ return True;
+ }
+ return False;
+}
+
+static Bool
+determine_window_transparent_to_desktop (const session_t *ps, Window w)
+{
+ Window root_return, parent_return;
+ Window *children = NULL;
+ unsigned int nchildren, i;
+ Bool type;
+
+ type = get_window_transparent_to_desktop (ps, w);
+ if (type == True) {
+ return True;
+ }
+
+ if (!XQueryTree (ps->dpy, w, &root_return, &parent_return, &children,
+ &nchildren))
+ {
+ /* XQueryTree failed. */
+ if (children)
+ XFree ((void *)children);
+ return False;
+ }
+
+ for (i = 0;i < nchildren;i++)
+ {
+ type = determine_window_transparent_to_desktop (ps, children[i]);
+ if (type == True)
+ return True;
+ }
+
+ if (children)
+ XFree ((void *)children);
+
+ return False;
+}
+
+static Bool
+determine_window_transparent_to_black (const session_t *ps, Window w)
+{
+ Window root_return, parent_return;
+ Window *children = NULL;
+ unsigned int nchildren, i;
+ Bool type;
+ Bool ret = False;
+
+ type = get_window_transparent_to_black (ps, w);
+ if (type == True) {
+ return True;
+ }
+
+ if (!XQueryTree (ps->dpy, w, &root_return, &parent_return, &children,
+ &nchildren))
+ {
+ /* XQueryTree failed. */
+ if (children) {
+ XFree ((void *)children);
+ }
+ return False;
+ }
+
+ for (i = 0;i < nchildren;i++)
+ {
+ type = determine_window_transparent_to_black (ps, children[i]);
+ if (type == True) {
+ ret = True;
+ break;
+ }
+ }
+
+ if (children) {
+ XFree ((void *)children);
+ }
+
+ return ret;
+}
+
+static void
+win_determine_mode(session_t *ps, win *w) {
+ winmode_t mode = WMODE_SOLID;
+
+ if (w->pictfmt && w->pictfmt->type == PictTypeDirect
+ && w->pictfmt->direct.alphaMask) {
+ mode = WMODE_ARGB;
+ } else if (w->opacity != OPAQUE) {
+ mode = WMODE_TRANS;
+ } else {
+ mode = WMODE_SOLID;
+ }
+
+ w->mode = mode;
+}
+
+/**
+ * Calculate and set the opacity of a window.
+ *
+ * If window is inactive and inactive_opacity_override is set, the
+ * priority is: (Simulates the old behavior)
+ *
+ * inactive_opacity > _NET_WM_WINDOW_OPACITY (if not opaque)
+ * > window type default opacity
+ *
+ * Otherwise:
+ *
+ * _NET_WM_WINDOW_OPACITY (if not opaque)
+ * > window type default opacity (if not opaque)
+ * > inactive_opacity
+ *
+ * @param ps current session
+ * @param w struct _win object representing the window
+ */
+static void
+calc_opacity(session_t *ps, win *w) {
+ opacity_t opacity = OPAQUE;
+
+ if (w->destroyed || IsViewable != w->a.map_state)
+ opacity = 0;
+ else {
+ // Try obeying opacity property and window type opacity firstly
+ if (OPAQUE == (opacity = w->opacity_prop)
+ && OPAQUE == (opacity = w->opacity_prop_client)) {
+ opacity = ps->o.wintype_opacity[w->window_type] * OPAQUE;
+ }
+
+ // Respect inactive_opacity in some cases
+ if (ps->o.inactive_opacity && false == w->focused
+ && (OPAQUE == opacity || ps->o.inactive_opacity_override)) {
+ opacity = ps->o.inactive_opacity;
+ }
+
+ // Respect active_opacity only when the window is physically focused
+ if (OPAQUE == opacity && ps->o.active_opacity && win_is_focused_real(ps, w))
+ opacity = ps->o.active_opacity;
+ }
+
+ w->opacity_tgt = opacity;
+}
+
+/**
+ * Determine whether a window is to be dimmed.
+ */
+static void
+calc_dim(session_t *ps, win *w) {
+ bool dim;
+
+ // Make sure we do nothing if the window is unmapped / destroyed
+ if (w->destroyed || IsViewable != w->a.map_state)
+ return;
+
+ if (ps->o.inactive_dim && !(w->focused)) {
+ dim = true;
+ } else {
+ dim = false;
+ }
+
+ if (dim != w->dim) {
+ w->dim = dim;
+ add_damage_win(ps, w);
+ }
+}
+
+/**
+ * Determine if a window should fade on opacity change.
+ */
+static void
+win_determine_fade(session_t *ps, win *w) {
+ if (UNSET != w->fade_force)
+ w->fade = w->fade_force;
+ else if ((ps->o.no_fading_openclose && w->in_openclose)
+ || win_match(ps, w, ps->o.fade_blacklist, &w->cache_fblst)
+ || (ps->o.no_fading_opacitychange && (!w->in_openclose)))
+ w->fade = false;
+ else
+ w->fade = ps->o.wintype_fade[w->window_type];
+}
+
+/**
+ * Update window-shape.
+ */
+static void
+win_update_shape_raw(session_t *ps, win *w) {
+ if (ps->shape_exists) {
+ w->bounding_shaped = wid_bounding_shaped(ps, w->id);
+ if (w->bounding_shaped && ps->o.detect_rounded_corners)
+ win_rounded_corners(ps, w);
+ }
+}
+
+/**
+ * Update window-shape related information.
+ */
+static void
+win_update_shape(session_t *ps, win *w) {
+ if (ps->shape_exists) {
+ // bool bounding_shaped_old = w->bounding_shaped;
+
+ win_update_shape_raw(ps, w);
+
+ // Shadow state could be changed
+ win_determine_shadow(ps, w);
+
+ /*
+ // If clear_shadow state on the window possibly changed, destroy the old
+ // shadow_pict
+ if (ps->o.clear_shadow && w->bounding_shaped != bounding_shaped_old)
+ free_paint(ps, &w->shadow_paint);
+ */
+ }
+}
+
+/**
+ * Reread _TDE_WM_WINDOW_SHADOW property from a window.
+ *
+ * The property must be set on the outermost window, usually the WM frame.
+ */
+static void
+win_update_prop_shadow_raw(session_t *ps, win *w) {
+ winprop_t prop = wid_get_prop(ps, w->id, ps->atom_compton_shadow, 1,
+ XA_CARDINAL, 32);
+
+ if (!prop.nitems) {
+ w->prop_shadow = -1;
+ }
+ else {
+ w->prop_shadow = *prop.data.p32;
+ }
+
+ free_winprop(&prop);
+}
+
+/**
+ * Reread _TDE_WM_WINDOW_SHADOW property from a window and update related
+ * things.
+ */
+static void
+win_update_prop_shadow(session_t *ps, 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);
+}
+
+/**
+ * Determine if a window should have shadow, and update things depending
+ * on shadow state.
+ */
+static void
+win_determine_shadow(session_t *ps, win *w) {
+ bool shadow_old = w->shadow;
+
+ w->shadow = (UNSET == w->shadow_force ?
+ (ps->o.wintype_shadow[w->window_type]
+ && !win_match(ps, w, ps->o.shadow_blacklist, &w->cache_sblst)
+ && !(ps->o.shadow_ignore_shaped && w->bounding_shaped
+ && !w->rounded_corners)
+ && !(ps->o.respect_prop_shadow && 0 == w->prop_shadow))
+ : w->shadow_force);
+
+ // Window extents need update on shadow state change
+ if (w->shadow != shadow_old) {
+ // Shadow geometry currently doesn't change on shadow state change
+ // calc_shadow_geometry(ps, w);
+ if (w->extents) {
+ // Mark the old extents as damaged if the shadow is removed
+ if (!w->shadow)
+ add_damage(ps, w->extents);
+ else
+ free_region(ps, &w->extents);
+ w->extents = win_extents(ps, w);
+ // Mark the new extents as damaged if the shadow is added
+ if (w->shadow)
+ add_damage_win(ps, w);
+ }
+ }
+}
+
+/**
+ * Determine if a window should have color inverted.
+ */
+static void
+win_determine_invert_color(session_t *ps, win *w) {
+ // Do not change window invert color state when the window is unmapped,
+ // unless it comes from w->invert_color_force.
+ if (UNSET == w->invert_color_force && IsViewable != w->a.map_state)
+ return;
+
+ bool invert_color_old = w->invert_color;
+
+ if (UNSET != w->invert_color_force)
+ w->invert_color = w->invert_color_force;
+ else
+ w->invert_color = win_match(ps, w, ps->o.invert_color_list, &w->cache_ivclst);
+
+ if (w->invert_color != invert_color_old)
+ add_damage_win(ps, w);
+}
+
+/**
+ * Determine if a window should have background blurred.
+ */
+static void
+win_determine_blur_background(session_t *ps, win *w) {
+ bool blur_background_old = w->blur_background;
+
+ w->blur_background = ps->o.blur_background
+ && !win_match(ps, w, ps->o.blur_background_blacklist, &w->cache_bbblst);
+
+ // Only consider window damaged if it's previously painted with background
+ // blurred
+ if (w->blur_background != blur_background_old && (WMODE_SOLID != w->mode
+ || (ps->o.blur_background_frame && w->frame_opacity)))
+ add_damage_win(ps, w);
+}
+
+/**
+ * Update window opacity according to opacity rules.
+ */
+static void
+win_update_opacity_rule(session_t *ps, win *w) {
+ // If long is 32-bit, unfortunately there's no way could we express "unset",
+ // so we just entirely don't distinguish "unset" and OPAQUE
+ opacity_t opacity = OPAQUE;
+ void *val = NULL;
+ if (c2_matchd(ps, w, ps->o.opacity_rules, &w->cache_oparule, &val))
+ opacity = ((double) (long) val) / 100.0 * OPAQUE;
+
+ if (opacity == w->opacity_set)
+ return;
+
+ if (OPAQUE != opacity)
+ wid_set_opacity_prop(ps, w->id, opacity);
+ else if (OPAQUE != w->opacity_set)
+ wid_rm_opacity_prop(ps, w->id);
+ w->opacity_set = opacity;
+}
+
+/**
+ * Function to be called on window type changes.
+ */
+static void
+win_on_wtype_change(session_t *ps, win *w) {
+ win_determine_shadow(ps, w);
+ win_determine_fade(ps, w);
+ win_update_focused(ps, w);
+ if (ps->o.invert_color_list)
+ win_determine_invert_color(ps, w);
+ if (ps->o.opacity_rules)
+ win_update_opacity_rule(ps, w);
+}
+
+/**
+ * Function to be called on window data changes.
+ */
+static void
+win_on_factor_change(session_t *ps, win *w) {
+ if (ps->o.shadow_blacklist)
+ win_determine_shadow(ps, w);
+ if (ps->o.fade_blacklist)
+ win_determine_fade(ps, w);
+ if (ps->o.invert_color_list)
+ win_determine_invert_color(ps, w);
+ if (ps->o.focus_blacklist)
+ win_update_focused(ps, w);
+ if (ps->o.blur_background_blacklist)
+ win_determine_blur_background(ps, w);
+ if (ps->o.opacity_rules)
+ win_update_opacity_rule(ps, w);
+ if (ps->o.paint_blacklist)
+ w->paint_excluded = win_match(ps, w, ps->o.paint_blacklist,
+ &w->cache_pblst);
+ if (ps->o.unredir_if_possible_blacklist)
+ w->unredir_if_possible_excluded = win_match(ps, w,
+ ps->o.unredir_if_possible_blacklist, &w->cache_uipblst);
+}
+
+/**
+ * Process needed window updates.
+ */
+static void
+win_upd_run(session_t *ps, win *w, win_upd_t *pupd) {
+ if (pupd->shadow) {
+ win_determine_shadow(ps, w);
+ pupd->shadow = false;
+ }
+ if (pupd->fade) {
+ win_determine_fade(ps, w);
+ pupd->fade = false;
+ }
+ if (pupd->invert_color) {
+ win_determine_invert_color(ps, w);
+ pupd->invert_color = false;
+ }
+ if (pupd->focus) {
+ win_update_focused(ps, w);
+ pupd->focus = false;
+ }
+}
+
+/**
+ * Update cache data in struct _win that depends on window size.
+ */
+static void
+calc_win_size(session_t *ps, win *w) {
+ w->widthb = w->a.width + w->a.border_width * 2;
+ w->heightb = w->a.height + w->a.border_width * 2;
+ calc_shadow_geometry(ps, w);
+ w->flags |= WFLAG_SIZE_CHANGE;
+}
+
+/**
+ * Calculate and update geometry of the shadow of a window.
+ */
+static void
+calc_shadow_geometry(session_t *ps, win *w) {
+ w->shadow_dx = ps->o.shadow_offset_x * w->shadow_size;
+ w->shadow_dy = ps->o.shadow_offset_y * w->shadow_size;
+ w->shadow_width = w->widthb + ps->gaussian_map->size;
+ w->shadow_height = w->heightb + ps->gaussian_map->size;
+}
+
+/**
+ * Update window type.
+ */
+static void
+win_upd_wintype(session_t *ps, 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->atom_transient))
+ w->window_type = WINTYPE_NORMAL;
+ else
+ w->window_type = WINTYPE_DIALOG;
+ }
+
+ if (w->window_type != wtype_old)
+ win_on_wtype_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
+ */
+static void
+win_mark_client(session_t *ps, win *w, Window client) {
+ w->client_win = client;
+
+ // If the window isn't mapped yet, stop here, as the function will be
+ // called in map_win()
+ if (IsViewable != w->a.map_state)
+ return;
+
+ XSelectInput(ps->dpy, client,
+ determine_evmask(ps, client, WIN_EVMODE_CLIENT));
+
+ // Make sure the XSelectInput() requests are sent
+ XFlush(ps->dpy);
+
+ win_upd_wintype(ps, w);
+
+ // Get frame widths. The window is in damaged area already.
+ if (ps->o.frame_opacity)
+ get_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
+ if (ps->o.track_wdata) {
+ win_get_name(ps, w);
+ win_get_class(ps, w);
+ win_get_role(ps, w);
+ }
+
+ // Update everything related to conditions
+ win_on_factor_change(ps, w);
+
+ // Update window focus state
+ win_update_focused(ps, w);
+}
+
+/**
+ * Unmark current client window of a window.
+ *
+ * @param ps current session
+ * @param w struct _win of the parent window
+ */
+static void
+win_unmark_client(session_t *ps, win *w) {
+ Window client = w->client_win;
+
+ w->client_win = None;
+
+ // Recheck event mask
+ XSelectInput(ps->dpy, client,
+ determine_evmask(ps, client, WIN_EVMODE_UNKNOWN));
+}
+
+/**
+ * Recheck client window of a window.
+ *
+ * @param ps current session
+ * @param w struct _win of the parent window
+ */
+static void
+win_recheck_client(session_t *ps, win *w) {
+ // 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.
+ Window cw = find_client_win(ps, w->id);
+#ifdef DEBUG_CLIENTWIN
+ if (cw)
+ printf_dbgf("(%#010lx): client %#010lx\n", w->id, cw);
+#endif
+ // Set a window's client window to itself if we couldn't find a
+ // client window
+ if (!cw) {
+ cw = w->id;
+ w->wmwin = !w->a.override_redirect;
+#ifdef DEBUG_CLIENTWIN
+ printf_dbgf("(%#010lx): client self (%s)\n", w->id,
+ (w->wmwin ? "wmwin": "override-redirected"));
+#endif
+ }
+
+ // 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);
+}
+
+static bool
+add_win(session_t *ps, Window id, Window prev) {
+ const static win win_def = {
+ .next = NULL,
+ .prev_trans = NULL,
+
+ .id = None,
+ .a = { },
+#ifdef CONFIG_XINERAMA
+ .xinerama_scr = -1,
+#endif
+ .pictfmt = NULL,
+ .mode = WMODE_TRANS,
+ .damaged = false,
+ .damage = None,
+ .pixmap_damaged = false,
+ .paint = PAINT_INIT,
+ .border_size = None,
+ .extents = None,
+ .flags = 0,
+ .need_configure = false,
+ .queue_configure = { },
+ .reg_ignore = None,
+ .widthb = 0,
+ .heightb = 0,
+ .destroyed = false,
+ .bounding_shaped = false,
+ .rounded_corners = false,
+ .to_paint = false,
+ .in_openclose = false,
+
+ .client_win = None,
+ .window_type = WINTYPE_UNKNOWN,
+ .wmwin = false,
+ .leader = None,
+ .cache_leader = None,
+
+ .focused = false,
+ .focused_force = UNSET,
+
+ .name = NULL,
+ .class_instance = NULL,
+ .class_general = NULL,
+ .role = NULL,
+ .cache_sblst = NULL,
+ .cache_fblst = NULL,
+ .cache_fcblst = NULL,
+ .cache_ivclst = NULL,
+ .cache_bbblst = NULL,
+ .cache_oparule = NULL,
+
+ .opacity = 0,
+ .opacity_tgt = 0,
+ .opacity_prop = OPAQUE,
+ .opacity_prop_client = OPAQUE,
+ .opacity_set = OPAQUE,
+
+ .fade = false,
+ .fade_force = UNSET,
+ .fade_callback = NULL,
+
+ .frame_opacity = 0.0,
+ .left_width = 0,
+ .right_width = 0,
+ .top_width = 0,
+ .bottom_width = 0,
+
+ .shadow = false,
+ .shadow_force = UNSET,
+ .shadow_opacity = 0.0,
+ .shadow_dx = 0,
+ .shadow_dy = 0,
+ .shadow_width = 0,
+ .shadow_height = 0,
+ .shadow_size = 100,
+ .shadow_paint = PAINT_INIT,
+ .prop_shadow = -1,
+
+ .dim = false,
+
+ .invert_color = false,
+ .invert_color_force = UNSET,
+
+ .blur_background = false,
+
+ .show_black_background = false,
+ .show_root_tile = false,
+ };
+
+ // Reject overlay window and already added windows
+ if (id == ps->overlay || find_win(ps, id)) {
+ return false;
+ }
+
+ // Allocate and initialize the new win structure
+ win *new = malloc(sizeof(win));
+
+#ifdef DEBUG_EVENTS
+ printf_dbgf("(%#010lx): %p\n", id, new);
+#endif
+
+ if (!new) {
+ printf_errf("(%#010lx): Failed to allocate memory for the new window.", id);
+ return false;
+ }
+
+ memcpy(new, &win_def, sizeof(win));
+
+ // Find window insertion point
+ win **p = NULL;
+ if (prev) {
+ for (p = &ps->list; *p; p = &(*p)->next) {
+ if ((*p)->id == prev && !(*p)->destroyed)
+ break;
+ }
+ } else {
+ p = &ps->list;
+ }
+
+ // Fill structure
+ new->id = id;
+
+ set_ignore_next(ps);
+ if (!XGetWindowAttributes(ps->dpy, id, &new->a)
+ || IsUnviewable == new->a.map_state) {
+ // Failed to get window attributes probably means the window is gone
+ // already. IsUnviewable means the window is already reparented
+ // elsewhere.
+ free(new);
+ return false;
+ }
+
+ // Delay window mapping
+ int map_state = new->a.map_state;
+ assert(IsViewable == map_state || IsUnmapped == map_state);
+ new->a.map_state = IsUnmapped;
+
+ if (InputOutput == new->a.class) {
+ // Get window picture format
+ new->pictfmt = XRenderFindVisualFormat(ps->dpy, new->a.visual);
+
+ // Create Damage for window
+ set_ignore_next(ps);
+ new->damage = XDamageCreate(ps->dpy, id, XDamageReportNonEmpty);
+ }
+
+ calc_win_size(ps, new);
+
+ new->next = *p;
+ *p = new;
+
+#ifdef CONFIG_DBUS
+ // Send D-Bus signal
+ if (ps->o.dbus) {
+ cdbus_ev_win_added(ps, new);
+ }
+#endif
+
+ if (IsViewable == map_state) {
+ map_win(ps, id);
+ }
+
+ return true;
+}
+
+static void
+restack_win(session_t *ps, win *w, Window new_above) {
+ Window old_above;
+
+ update_reg_ignore_expire(ps, w);
+
+ if (w->next) {
+ old_above = w->next->id;
+ } else {
+ old_above = None;
+ }
+
+ if (old_above != new_above) {
+ win **prev = NULL, **prev_old = NULL;
+
+ // unhook
+ for (prev = &ps->list; *prev; prev = &(*prev)->next) {
+ if ((*prev) == w) break;
+ }
+
+ prev_old = prev;
+
+ bool found = false;
+
+ // rehook
+ for (prev = &ps->list; *prev; prev = &(*prev)->next) {
+ if ((*prev)->id == new_above && !(*prev)->destroyed) {
+ found = true;
+ break;
+ }
+ }
+
+ if (new_above && !found) {
+ printf_errf("(%#010lx, %#010lx): "
+ "Failed to found new above window.", w->id, new_above);
+ return;
+ }
+
+ *prev_old = w->next;
+
+ w->next = *prev;
+ *prev = w;
+
+#ifdef DEBUG_RESTACK
+ {
+ const char *desc;
+ char *window_name = NULL;
+ bool to_free;
+ win* c = ps->list;
+
+ printf_dbgf("(%#010lx, %#010lx): "
+ "Window stack modified. Current stack:\n", w->id, new_above);
+
+ for (; c; c = c->next) {
+ window_name = "(Failed to get title)";
+
+ to_free = ev_window_name(ps, c->id, &window_name);
+
+ desc = "";
+ if (c->destroyed) desc = "(D) ";
+ printf("%#010lx \"%s\" %s", c->id, window_name, desc);
+ if (c->next)
+ printf("-> ");
+
+ if (to_free) {
+ cxfree(window_name);
+ window_name = NULL;
+ }
+ }
+ fputs("\n", stdout);
+ }
+#endif
+ }
+}
+
+static void
+configure_win(session_t *ps, XConfigureEvent *ce) {
+ // On root window changes
+ if (ce->window == ps->root) {
+ free_paint(ps, &ps->tgt_buffer);
+
+ ps->root_width = ce->width;
+ ps->root_height = ce->height;
+
+ rebuild_screen_reg(ps);
+ rebuild_shadow_exclude_reg(ps);
+ free_all_damage_last(ps);
+
+#ifdef CONFIG_VSYNC_OPENGL
+ if (BKEND_GLX == ps->o.backend)
+ glx_on_root_change(ps);
+#endif
+
+ return;
+ }
+
+ // Other window changes
+ win *w = find_win(ps, ce->window);
+ XserverRegion damage = None;
+
+ if (!w)
+ return;
+
+ if (w->a.map_state == IsUnmapped) {
+ /* save the configure event for when the window maps */
+ w->need_configure = true;
+ w->queue_configure = *ce;
+ restack_win(ps, w, ce->above);
+ } else {
+ if (!(w->need_configure)) {
+ restack_win(ps, w, ce->above);
+ }
+
+ bool factor_change = false;
+
+ // Windows restack (including window restacks happened when this
+ // window is not mapped) could mess up all reg_ignore
+ ps->reg_ignore_expire = true;
+
+ w->need_configure = false;
+
+ damage = XFixesCreateRegion(ps->dpy, 0, 0);
+ if (w->extents != None) {
+ XFixesCopyRegion(ps->dpy, damage, w->extents);
+ }
+
+ // If window geometry did not change, don't free extents here
+ if (w->a.x != ce->x || w->a.y != ce->y
+ || w->a.width != ce->width || w->a.height != ce->height
+ || w->a.border_width != ce->border_width) {
+ factor_change = true;
+ free_region(ps, &w->extents);
+ free_region(ps, &w->border_size);
+ }
+
+ w->a.x = ce->x;
+ w->a.y = ce->y;
+
+ if (w->a.width != ce->width || w->a.height != ce->height
+ || w->a.border_width != ce->border_width)
+ free_wpaint(ps, w);
+
+ if (w->a.width != ce->width || w->a.height != ce->height
+ || w->a.border_width != ce->border_width) {
+ w->a.width = ce->width;
+ w->a.height = ce->height;
+ w->a.border_width = ce->border_width;
+ calc_win_size(ps, w);
+
+ // Rounded corner detection is affected by window size
+ if (ps->shape_exists && ps->o.shadow_ignore_shaped
+ && ps->o.detect_rounded_corners && w->bounding_shaped)
+ win_update_shape(ps, w);
+ }
+
+ if (damage) {
+ XserverRegion extents = win_extents(ps, w);
+ XFixesUnionRegion(ps->dpy, damage, damage, extents);
+ XFixesDestroyRegion(ps->dpy, extents);
+ add_damage(ps, damage);
+ }
+
+ if (factor_change) {
+ cxinerama_win_upd_scr(ps, w);
+ win_on_factor_change(ps, w);
+ }
+ }
+
+ // override_redirect flag cannot be changed after window creation, as far
+ // as I know, so there's no point to re-match windows here.
+ w->a.override_redirect = ce->override_redirect;
+}
+
+static void
+circulate_win(session_t *ps, XCirculateEvent *ce) {
+ win *w = find_win(ps, ce->window);
+ Window new_above;
+
+ if (!w) return;
+
+ if (ce->place == PlaceOnTop) {
+ new_above = ps->list->id;
+ } else {
+ new_above = None;
+ }
+
+ restack_win(ps, w, new_above);
+}
+
+static void
+finish_destroy_win(session_t *ps, Window id) {
+ win **prev = NULL, *w = NULL;
+
+#ifdef DEBUG_EVENTS
+ printf_dbgf("(%#010lx): Starting...\n", id);
+#endif
+
+ for (prev = &ps->list; (w = *prev); prev = &w->next) {
+ if (w->id == id && w->destroyed) {
+#ifdef DEBUG_EVENTS
+ printf_dbgf("(%#010lx \"%s\"): %p\n", id, w->name, w);
+#endif
+
+ finish_unmap_win(ps, w);
+ *prev = w->next;
+
+ // Clear active_win if it's pointing to the destroyed window
+ if (w == ps->active_win)
+ ps->active_win = NULL;
+
+ free_win_res(ps, w);
+
+ // Drop w from all prev_trans to avoid accessing freed memory in
+ // repair_win()
+ for (win *w2 = ps->list; w2; w2 = w2->next)
+ if (w == w2->prev_trans)
+ w2->prev_trans = NULL;
+
+ free(w);
+ break;
+ }
+ }
+}
+
+static void
+destroy_callback(session_t *ps, win *w) {
+ finish_destroy_win(ps, w->id);
+}
+
+static void
+destroy_win(session_t *ps, Window id) {
+ win *w = find_win(ps, id);
+
+#ifdef DEBUG_EVENTS
+ printf_dbgf("(%#010lx \"%s\"): %p\n", id, (w ? w->name: NULL), w);
+#endif
+
+ if (w) {
+ unmap_win(ps, w);
+
+ w->destroyed = true;
+
+ // Set fading callback
+ set_fade_callback(ps, w, destroy_callback, false);
+
+#ifdef CONFIG_DBUS
+ // Send D-Bus signal
+ if (ps->o.dbus) {
+ cdbus_ev_win_destroyed(ps, w);
+ }
+#endif
+ }
+}
+
+static inline void
+root_damaged(session_t *ps) {
+ if (ps->root_tile_paint.pixmap) {
+ XClearArea(ps->dpy, ps->root, 0, 0, 0, 0, true);
+ // if (ps->root_picture != ps->root_tile) {
+ free_root_tile(ps);
+ /* }
+ if (root_damage) {
+ XserverRegion parts = XFixesCreateRegion(ps->dpy, 0, 0);
+ XDamageSubtract(ps->dpy, root_damage, None, parts);
+ add_damage(ps, parts);
+ } */
+ }
+
+ // Mark screen damaged
+ force_repaint(ps);
+}
+
+static void
+damage_win(session_t *ps, XDamageNotifyEvent *de) {
+ /*
+ if (ps->root == de->drawable) {
+ root_damaged();
+ return;
+ } */
+
+ win *w = find_win(ps, de->drawable);
+
+ if (!w) return;
+
+ repair_win(ps, w);
+}
+
+/**
+ * Xlib error handler function.
+ */
+static int
+xerror(Display __attribute__((unused)) *dpy, XErrorEvent *ev) {
+ session_t * const ps = ps_g;
+
+ int o = 0;
+ const char *name = "Unknown";
+
+ if (should_ignore(ps, ev->serial)) {
+ return 0;
+ }
+
+ if (ev->request_code == ps->composite_opcode
+ && ev->minor_code == X_CompositeRedirectSubwindows) {
+ fprintf(stderr, "Another composite manager is already running\n");
+ exit(1);
+ }
+
+#define CASESTRRET2(s) case s: name = #s; break
+
+ o = ev->error_code - ps->xfixes_error;
+ switch (o) {
+ CASESTRRET2(BadRegion);
+ }
+
+ o = ev->error_code - ps->damage_error;
+ switch (o) {
+ CASESTRRET2(BadDamage);
+ }
+
+ o = ev->error_code - ps->render_error;
+ switch (o) {
+ CASESTRRET2(BadPictFormat);
+ CASESTRRET2(BadPicture);
+ CASESTRRET2(BadPictOp);
+ CASESTRRET2(BadGlyphSet);
+ CASESTRRET2(BadGlyph);
+ }
+
+#ifdef CONFIG_VSYNC_OPENGL
+ if (ps->glx_exists) {
+ o = ev->error_code - ps->glx_error;
+ switch (o) {
+ CASESTRRET2(GLX_BAD_SCREEN);
+ CASESTRRET2(GLX_BAD_ATTRIBUTE);
+ CASESTRRET2(GLX_NO_EXTENSION);
+ CASESTRRET2(GLX_BAD_VISUAL);
+ CASESTRRET2(GLX_BAD_CONTEXT);
+ CASESTRRET2(GLX_BAD_VALUE);
+ CASESTRRET2(GLX_BAD_ENUM);
+ }
+ }
+#endif
+
+#ifdef CONFIG_XSYNC
+ if (ps->xsync_exists) {
+ o = ev->error_code - ps->xsync_error;
+ switch (o) {
+ CASESTRRET2(XSyncBadCounter);
+ CASESTRRET2(XSyncBadAlarm);
+ CASESTRRET2(XSyncBadFence);
+ }
+ }
+#endif
+
+ switch (ev->error_code) {
+ CASESTRRET2(BadAccess);
+ CASESTRRET2(BadAlloc);
+ CASESTRRET2(BadAtom);
+ CASESTRRET2(BadColor);
+ CASESTRRET2(BadCursor);
+ CASESTRRET2(BadDrawable);
+ CASESTRRET2(BadFont);
+ CASESTRRET2(BadGC);
+ CASESTRRET2(BadIDChoice);
+ CASESTRRET2(BadImplementation);
+ CASESTRRET2(BadLength);
+ CASESTRRET2(BadMatch);
+ CASESTRRET2(BadName);
+ CASESTRRET2(BadPixmap);
+ CASESTRRET2(BadRequest);
+ CASESTRRET2(BadValue);
+ CASESTRRET2(BadWindow);
+ }
+
+#undef CASESTRRET2
+
+ print_timestamp(ps);
+ {
+ char buf[BUF_LEN] = "";
+ XGetErrorText(ps->dpy, ev->error_code, buf, BUF_LEN);
+ printf("error %d (%s) request %d minor %d serial %lu (\"%s\")\n",
+ ev->error_code, name, ev->request_code,
+ ev->minor_code, ev->serial, buf);
+ }
+
+ return 0;
+}
+
+static void
+expose_root(session_t *ps, XRectangle *rects, int nrects) {
+ free_all_damage_last(ps);
+ XserverRegion region = XFixesCreateRegion(ps->dpy, rects, nrects);
+ add_damage(ps, region);
+}
+
+/**
+ * Get the value of a type-<code>Window</code> property of a window.
+ *
+ * @return the value if successful, 0 otherwise
+ */
+static Window
+wid_get_prop_window(session_t *ps, Window wid, Atom aprop) {
+ // Get the attribute
+ Window p = None;
+ winprop_t prop = wid_get_prop(ps, wid, aprop, 1L, XA_WINDOW, 32);
+
+ // Return it
+ if (prop.nitems) {
+ p = *prop.data.p32;
+ }
+
+ free_winprop(&prop);
+
+ return p;
+}
+
+/**
+ * Update focused state of a window.
+ */
+static void
+win_update_focused(session_t *ps, win *w) {
+ bool focused_old = w->focused;
+
+ if (UNSET != w->focused_force) {
+ w->focused = w->focused_force;
+ }
+ else {
+ w->focused = win_is_focused_real(ps, w);
+
+ // Use wintype_focus, and treat WM windows and override-redirected
+ // windows specially
+ if (ps->o.wintype_focus[w->window_type]
+ || (ps->o.mark_wmwin_focused && w->wmwin)
+ || (ps->o.mark_ovredir_focused
+ && w->id == w->client_win && !w->wmwin)
+ || win_match(ps, w, ps->o.focus_blacklist, &w->cache_fcblst))
+ 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;
+ }
+ }
+
+ if (w->focused != focused_old)
+ w->flags |= WFLAG_OPCT_CHANGE;
+}
+
+/**
+ * Set real focused state of a window.
+ */
+static void
+win_set_focused(session_t *ps, win *w, bool focused) {
+ // Unmapped windows will have their focused state reset on map
+ if (IsUnmapped == w->a.map_state)
+ return;
+
+ if (win_is_focused_real(ps, w) == focused) return;
+
+ if (focused) {
+ if (ps->active_win)
+ win_set_focused(ps, ps->active_win, false);
+ ps->active_win = w;
+ }
+ else if (w == ps->active_win)
+ ps->active_win = NULL;
+
+ assert(win_is_focused_real(ps, w) == focused);
+
+ win_on_focus_change(ps, w);
+}
+
+/**
+ * Handle window focus change.
+ */
+static void
+win_on_focus_change(session_t *ps, win *w) {
+ // If window grouping detection is enabled
+ if (ps->o.track_leader) {
+ Window leader = win_get_leader(ps, w);
+
+ // If the window gets focused, replace the old active_leader
+ if (win_is_focused_real(ps, w) && leader != ps->active_leader) {
+ Window active_leader_old = ps->active_leader;
+
+ ps->active_leader = leader;
+
+ group_update_focused(ps, active_leader_old);
+ group_update_focused(ps, leader);
+ }
+ // If the group get unfocused, remove it from active_leader
+ else if (!win_is_focused_real(ps, w) && leader && leader == ps->active_leader
+ && !group_is_focused(ps, leader)) {
+ ps->active_leader = None;
+ group_update_focused(ps, leader);
+ }
+
+ // The window itself must be updated anyway
+ win_update_focused(ps, w);
+ }
+ // Otherwise, only update the window itself
+ else {
+ win_update_focused(ps, w);
+ }
+
+ // 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_real(ps, w))
+ cdbus_ev_win_focusin(ps, w);
+ else
+ cdbus_ev_win_focusout(ps, w);
+ }
+#endif
+}
+
+/**
+ * Update leader of a window.
+ */
+static void
+win_update_leader(session_t *ps, win *w) {
+ Window leader = None;
+
+ // Read the leader properties
+ if (ps->o.detect_transient && !leader)
+ leader = wid_get_prop_window(ps, w->client_win, ps->atom_transient);
+
+ if (ps->o.detect_client_leader && !leader)
+ leader = wid_get_prop_window(ps, w->client_win, ps->atom_client_leader);
+
+ win_set_leader(ps, w, leader);
+
+#ifdef DEBUG_LEADER
+ printf_dbgf("(%#010lx): client %#010lx, leader %#010lx, cache %#010lx\n", w->id, w->client_win, w->leader, win_get_leader(ps, w));
+#endif
+}
+
+/**
+ * Set leader of a window.
+ */
+static void
+win_set_leader(session_t *ps, win *w, Window nleader) {
+ // If the leader changes
+ if (w->leader != nleader) {
+ Window 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.
+ Window cache_leader = win_get_leader(ps, w);
+ if (win_is_focused_real(ps, w) && cache_leader_old != cache_leader) {
+ ps->active_leader = cache_leader;
+
+ group_update_focused(ps, cache_leader_old);
+ group_update_focused(ps, cache_leader);
+ }
+ // Otherwise, at most the window itself is affected
+ else {
+ win_update_focused(ps, w);
+ }
+
+ // Update everything related to conditions
+ win_on_factor_change(ps, w);
+ }
+}
+
+/**
+ * Internal function of win_get_leader().
+ */
+static Window
+win_get_leader_raw(session_t *ps, 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) {
+ win *wp = find_toplevel(ps, w->cache_leader);
+ if (wp) {
+ // Dead loop?
+ if (recursions > WIN_GET_LEADER_MAX_RECURSION)
+ return None;
+
+ w->cache_leader = win_get_leader_raw(ps, wp, recursions + 1);
+ }
+ }
+ }
+
+ return w->cache_leader;
+}
+
+/**
+ * Get the value of a text property of a window.
+ */
+bool
+wid_get_text_prop(session_t *ps, Window wid, Atom prop,
+ char ***pstrlst, int *pnstr) {
+ XTextProperty text_prop = { NULL, None, 0, 0 };
+
+ if (!(XGetTextProperty(ps->dpy, wid, &text_prop, prop) && text_prop.value))
+ return false;
+
+ if (Success !=
+ XmbTextPropertyToTextList(ps->dpy, &text_prop, pstrlst, pnstr)
+ || !*pnstr) {
+ *pnstr = 0;
+ if (*pstrlst)
+ XFreeStringList(*pstrlst);
+ cxfree(text_prop.value);
+ return false;
+ }
+
+ cxfree(text_prop.value);
+ return true;
+}
+
+/**
+ * Get the name of a window from window ID.
+ */
+static bool
+wid_get_name(session_t *ps, Window wid, char **name) {
+ XTextProperty text_prop = { NULL, None, 0, 0 };
+ char **strlst = NULL;
+ int nstr = 0;
+
+ if (!(wid_get_text_prop(ps, wid, ps->atom_name_ewmh, &strlst, &nstr))) {
+#ifdef DEBUG_WINDATA
+ printf_dbgf("(%#010lx): _NET_WM_NAME unset, falling back to WM_NAME.\n", wid);
+#endif
+
+ if (!(XGetWMName(ps->dpy, wid, &text_prop) && text_prop.value)) {
+ return false;
+ }
+ if (Success !=
+ XmbTextPropertyToTextList(ps->dpy, &text_prop, &strlst, &nstr)
+ || !nstr || !strlst) {
+ if (strlst)
+ XFreeStringList(strlst);
+ cxfree(text_prop.value);
+ return false;
+ }
+ cxfree(text_prop.value);
+ }
+
+ *name = mstrcpy(strlst[0]);
+
+ XFreeStringList(strlst);
+
+ return true;
+}
+
+/**
+ * Get the role of a window from window ID.
+ */
+static bool
+wid_get_role(session_t *ps, Window wid, char **role) {
+ char **strlst = NULL;
+ int nstr = 0;
+
+ if (!wid_get_text_prop(ps, wid, ps->atom_role, &strlst, &nstr)) {
+ return false;
+ }
+
+ *role = mstrcpy(strlst[0]);
+
+ XFreeStringList(strlst);
+
+ return true;
+}
+
+/**
+ * Retrieve a string property of a window and update its <code>win</code>
+ * structure.
+ */
+static int
+win_get_prop_str(session_t *ps, win *w, char **tgt,
+ bool (*func_wid_get_prop_str)(session_t *ps, Window wid, char **tgt)) {
+ int ret = -1;
+ char *prop_old = *tgt;
+
+ // Can't do anything if there's no client window
+ if (!w->client_win)
+ return false;
+
+ // Get the property
+ ret = func_wid_get_prop_str(ps, w->client_win, tgt);
+
+ // Return -1 if func_wid_get_prop_str() failed, 0 if the property
+ // doesn't change, 1 if it changes
+ if (!ret)
+ ret = -1;
+ else if (prop_old && !strcmp(*tgt, prop_old))
+ ret = 0;
+ else
+ ret = 1;
+
+ // Keep the old property if there's no new one
+ if (*tgt != prop_old)
+ free(prop_old);
+
+ return ret;
+}
+
+/**
+ * Retrieve the <code>WM_CLASS</code> of a window and update its
+ * <code>win</code> structure.
+ */
+static bool
+win_get_class(session_t *ps, 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->atom_class, &strlst, &nstr))
+ return false;
+
+ // Copy the strings if successful
+ w->class_instance = mstrcpy(strlst[0]);
+
+ if (nstr > 1)
+ w->class_general = mstrcpy(strlst[1]);
+
+ XFreeStringList(strlst);
+
+#ifdef DEBUG_WINDATA
+ printf_dbgf("(%#010lx): client = %#010lx, "
+ "instance = \"%s\", general = \"%s\"\n",
+ w->id, w->client_win, w->class_instance, w->class_general);
+#endif
+
+ return true;
+}
+
+/**
+ * Force a full-screen repaint.
+ */
+void
+force_repaint(session_t *ps) {
+ assert(ps->screen_reg);
+ XserverRegion reg = None;
+ if (ps->screen_reg && (reg = copy_region(ps, ps->screen_reg))) {
+ ps->ev_received = true;
+ add_damage(ps, reg);
+ }
+}
+
+#ifdef CONFIG_DBUS
+/** @name DBus hooks
+ */
+///@{
+
+/**
+ * Set w->shadow_force of a window.
+ */
+void
+win_set_shadow_force(session_t *ps, win *w, switch_t val) {
+ if (val != w->shadow_force) {
+ w->shadow_force = val;
+ win_determine_shadow(ps, w);
+ ps->ev_received = true;
+ }
+}
+
+/**
+ * Set w->fade_force of a window.
+ */
+void
+win_set_fade_force(session_t *ps, win *w, switch_t val) {
+ if (val != w->fade_force) {
+ w->fade_force = val;
+ win_determine_fade(ps, w);
+ ps->ev_received = true;
+ }
+}
+
+/**
+ * Set w->focused_force of a window.
+ */
+void
+win_set_focused_force(session_t *ps, win *w, switch_t val) {
+ if (val != w->focused_force) {
+ w->focused_force = val;
+ win_update_focused(ps, w);
+ ps->ev_received = true;
+ }
+}
+
+/**
+ * Set w->invert_color_force of a window.
+ */
+void
+win_set_invert_color_force(session_t *ps, win *w, switch_t val) {
+ if (val != w->invert_color_force) {
+ w->invert_color_force = val;
+ win_determine_invert_color(ps, w);
+ ps->ev_received = true;
+ }
+}
+
+/**
+ * Enable focus tracking.
+ */
+void
+opts_init_track_focus(session_t *ps) {
+ // Already tracking focus
+ if (ps->o.track_focus)
+ return;
+
+ ps->o.track_focus = true;
+
+ if (!ps->o.use_ewmh_active_win) {
+ // Start listening to FocusChange events
+ for (win *w = ps->list; w; w = w->next)
+ if (IsViewable == w->a.map_state)
+ XSelectInput(ps->dpy, w->id,
+ determine_evmask(ps, w->id, WIN_EVMODE_FRAME));
+ }
+
+ // Recheck focus
+ recheck_focus(ps);
+}
+
+/**
+ * Set no_fading_openclose option.
+ */
+void
+opts_set_no_fading_openclose(session_t *ps, bool newval) {
+ if (newval != ps->o.no_fading_openclose) {
+ ps->o.no_fading_openclose = newval;
+ for (win *w = ps->list; w; w = w->next)
+ win_determine_fade(ps, w);
+ ps->ev_received = true;
+ }
+}
+
+/**
+ * Set no_fading_opacitychange option.
+ */
+void
+opts_set_no_fading_opacitychange(session_t *ps, bool newval) {
+ if (newval != ps->o.no_fading_opacitychange) {
+ ps->o.no_fading_opacitychange = newval;
+ for (win *w = ps->list; w; w = w->next)
+ win_determine_fade(ps, w);
+ ps->ev_received = true;
+ }
+}
+
+//!@}
+#endif
+
+#ifdef DEBUG_EVENTS
+static int
+ev_serial(XEvent *ev) {
+ if ((ev->type & 0x7f) != KeymapNotify) {
+ return ev->xany.serial;
+ }
+ return NextRequest(ev->xany.display);
+}
+
+static const char *
+ev_name(session_t *ps, XEvent *ev) {
+ static char buf[128];
+ switch (ev->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 (isdamagenotify(ps, ev))
+ return "Damage";
+
+ if (ps->shape_exists && ev->type == ps->shape_event)
+ return "ShapeNotify";
+
+#ifdef CONFIG_XSYNC
+ if (ps->xsync_exists) {
+ int o = ev->type - ps->xsync_event;
+ switch (o) {
+ CASESTRRET(CounterNotify);
+ CASESTRRET(AlarmNotify);
+ }
+ }
+#endif
+
+ sprintf(buf, "Event %d", ev->type);
+
+ return buf;
+}
+
+static Window
+ev_window(session_t *ps, XEvent *ev) {
+ switch (ev->type) {
+ case FocusIn:
+ case FocusOut:
+ return ev->xfocus.window;
+ case CreateNotify:
+ return ev->xcreatewindow.window;
+ case ConfigureNotify:
+ return ev->xconfigure.window;
+ case DestroyNotify:
+ return ev->xdestroywindow.window;
+ case MapNotify:
+ return ev->xmap.window;
+ case UnmapNotify:
+ return ev->xunmap.window;
+ case ReparentNotify:
+ return ev->xreparent.window;
+ case CirculateNotify:
+ return ev->xcirculate.window;
+ case Expose:
+ return ev->xexpose.window;
+ case PropertyNotify:
+ return ev->xproperty.window;
+ case ClientMessage:
+ return ev->xclient.window;
+ default:
+ if (isdamagenotify(ps, ev)) {
+ return ((XDamageNotifyEvent *)ev)->drawable;
+ }
+
+ if (ps->shape_exists && ev->type == ps->shape_event) {
+ return ((XShapeEvent *) ev)->window;
+ }
+
+ return 0;
+ }
+}
+
+static inline const char *
+ev_focus_mode_name(XFocusChangeEvent* ev) {
+ switch (ev->mode) {
+ CASESTRRET(NotifyNormal);
+ CASESTRRET(NotifyWhileGrabbed);
+ CASESTRRET(NotifyGrab);
+ CASESTRRET(NotifyUngrab);
+ }
+
+ return "Unknown";
+}
+
+static inline const char *
+ev_focus_detail_name(XFocusChangeEvent* ev) {
+ switch (ev->detail) {
+ CASESTRRET(NotifyAncestor);
+ CASESTRRET(NotifyVirtual);
+ CASESTRRET(NotifyInferior);
+ CASESTRRET(NotifyNonlinear);
+ CASESTRRET(NotifyNonlinearVirtual);
+ CASESTRRET(NotifyPointer);
+ CASESTRRET(NotifyPointerRoot);
+ CASESTRRET(NotifyDetailNone);
+ }
+
+ return "Unknown";
+}
+
+static inline void
+ev_focus_report(XFocusChangeEvent* ev) {
+ printf(" { mode: %s, detail: %s }\n", ev_focus_mode_name(ev),
+ ev_focus_detail_name(ev));
+}
+
+#endif
+
+// === Events ===
+
+/**
+ * Determine whether we should respond to a <code>FocusIn/Out</code>
+ * event.
+ */
+inline static bool
+ev_focus_accept(XFocusChangeEvent *ev) {
+ return NotifyNormal == ev->mode || NotifyUngrab == ev->mode;
+}
+
+static inline void
+ev_focus_in(session_t *ps, XFocusChangeEvent *ev) {
+#ifdef DEBUG_EVENTS
+ ev_focus_report(ev);
+#endif
+
+ recheck_focus(ps);
+}
+
+inline static void
+ev_focus_out(session_t *ps, XFocusChangeEvent *ev) {
+#ifdef DEBUG_EVENTS
+ ev_focus_report(ev);
+#endif
+
+ recheck_focus(ps);
+}
+
+inline static void
+ev_create_notify(session_t *ps, XCreateWindowEvent *ev) {
+ assert(ev->parent == ps->root);
+ add_win(ps, ev->window, 0);
+}
+
+inline static void
+ev_configure_notify(session_t *ps, XConfigureEvent *ev) {
+#ifdef DEBUG_EVENTS
+ printf(" { send_event: %d, "
+ " above: %#010lx, "
+ " override_redirect: %d }\n",
+ ev->send_event, ev->above, ev->override_redirect);
+#endif
+ configure_win(ps, ev);
+}
+
+inline static void
+ev_destroy_notify(session_t *ps, XDestroyWindowEvent *ev) {
+ destroy_win(ps, ev->window);
+}
+
+inline static void
+ev_map_notify(session_t *ps, XMapEvent *ev) {
+ map_win(ps, ev->window);
+}
+
+inline static void
+ev_unmap_notify(session_t *ps, XUnmapEvent *ev) {
+ win *w = find_win(ps, ev->window);
+
+ if (w)
+ unmap_win(ps, w);
+}
+
+inline static void
+ev_reparent_notify(session_t *ps, XReparentEvent *ev) {
+#ifdef DEBUG_EVENTS
+ printf_dbg(" { new_parent: %#010lx, override_redirect: %d }\n",
+ ev->parent, ev->override_redirect);
+#endif
+
+ if (ev->parent == ps->root) {
+ add_win(ps, ev->window, 0);
+ } else {
+ destroy_win(ps, ev->window);
+
+ // Reset event mask in case something wrong happens
+ XSelectInput(ps->dpy, ev->window,
+ determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN));
+
+ // Check if the window is an undetected client window
+ // Firstly, check if it's a known client window
+ if (!find_toplevel(ps, ev->window)) {
+ // If not, look for its frame window
+ win *w_top = find_toplevel2(ps, ev->parent);
+ // If found, and the client window has not been determined, or its
+ // frame may not have a correct client, continue
+ if (w_top && (!w_top->client_win
+ || w_top->client_win == w_top->id)) {
+ // If it has WM_STATE, mark it the client window
+ if (wid_has_prop(ps, ev->window, ps->atom_client)) {
+ w_top->wmwin = false;
+ win_unmark_client(ps, w_top);
+ win_mark_client(ps, w_top, ev->window);
+ }
+ // Otherwise, watch for WM_STATE on it
+ else {
+ XSelectInput(ps->dpy, ev->window,
+ determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN)
+ | PropertyChangeMask);
+ }
+ }
+ }
+ }
+}
+
+inline static void
+ev_circulate_notify(session_t *ps, XCirculateEvent *ev) {
+ circulate_win(ps, ev);
+}
+
+inline static void
+ev_expose(session_t *ps, XExposeEvent *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 = realloc(ps->expose_rects,
+ (ps->size_expose + more) * sizeof(XRectangle));
+ ps->size_expose += more;
+ } else {
+ ps->expose_rects = malloc(more * sizeof(XRectangle));
+ ps->size_expose = more;
+ }
+ }
+
+ ps->expose_rects[ps->n_expose].x = ev->x;
+ ps->expose_rects[ps->n_expose].y = ev->y;
+ ps->expose_rects[ps->n_expose].width = ev->width;
+ ps->expose_rects[ps->n_expose].height = ev->height;
+ ps->n_expose++;
+
+ if (ev->count == 0) {
+ expose_root(ps, ps->expose_rects, ps->n_expose);
+ ps->n_expose = 0;
+ }
+ }
+}
+
+/**
+ * Update current active window based on EWMH _NET_ACTIVE_WIN.
+ *
+ * Does not change anything if we fail to get the attribute or the window
+ * returned could not be found.
+ */
+static void
+update_ewmh_active_win(session_t *ps) {
+ // Search for the window
+ Window wid = wid_get_prop_window(ps, ps->root, ps->atom_ewmh_active_win);
+ win *w = find_win_all(ps, wid);
+
+ // Mark the window focused. No need to unfocus the previous one.
+ if (w) win_set_focused(ps, w, true);
+}
+
+inline static void
+ev_property_notify(session_t *ps, XPropertyEvent *ev) {
+#ifdef DEBUG_EVENTS
+ {
+ // Print out changed atom
+ char *name = XGetAtomName(ps->dpy, ev->atom);
+ printf_dbg(" { atom = %s }\n", name);
+ cxfree(name);
+ }
+#endif
+
+ if (ps->root == ev->window) {
+ if (ps->o.track_focus && ps->o.use_ewmh_active_win
+ && ps->atom_ewmh_active_win == ev->atom) {
+ update_ewmh_active_win(ps);
+ }
+ else {
+ // Destroy the root "image" if the wallpaper probably changed
+ for (int p = 0; background_props_str[p]; p++) {
+ if (ev->atom == get_atom(ps, background_props_str[p])) {
+ root_damaged(ps);
+ break;
+ }
+ }
+ }
+
+ // Unconcerned about any other proprties on root window
+ return;
+ }
+
+ // If WM_STATE changes
+ if (ev->atom == ps->atom_client) {
+ // Check whether it could be a client window
+ if (!find_toplevel(ps, ev->window)) {
+ // Reset event mask anyway
+ XSelectInput(ps->dpy, ev->window,
+ determine_evmask(ps, ev->window, WIN_EVMODE_UNKNOWN));
+
+ win *w_top = find_toplevel2(ps, ev->window);
+ // Initialize client_win as early as possible
+ if (w_top && (!w_top->client_win || w_top->client_win == w_top->id)
+ && wid_has_prop(ps, ev->window, ps->atom_client)) {
+ w_top->wmwin = false;
+ win_unmark_client(ps, w_top);
+ win_mark_client(ps, w_top, ev->window);
+ }
+ }
+ }
+
+ // If _NET_WM_WINDOW_TYPE changes... God knows why this would happen, but
+ // there are always some stupid applications. (#144)
+ if (ev->atom == ps->atom_win_type) {
+ win *w = NULL;
+ if ((w = find_toplevel(ps, ev->window)))
+ win_upd_wintype(ps, w);
+ }
+
+ // If _NET_WM_OPACITY changes
+ if (ev->atom == ps->atom_opacity) {
+ win *w = NULL;
+ if ((w = find_win(ps, ev->window)))
+ w->opacity_prop = wid_get_opacity_prop(ps, w->id, OPAQUE);
+ else if (ps->o.detect_client_opacity
+ && (w = find_toplevel(ps, ev->window)))
+ w->opacity_prop_client = wid_get_opacity_prop(ps, w->client_win,
+ OPAQUE);
+ if (w) {
+ w->flags |= WFLAG_OPCT_CHANGE;
+ }
+ }
+
+ // If frame extents property changes
+ if (ps->o.frame_opacity && ev->atom == ps->atom_frame_extents) {
+ win *w = find_toplevel(ps, ev->window);
+ if (w) {
+ get_frame_extents(ps, w, ev->window);
+ // If frame extents change, the window needs repaint
+ add_damage_win(ps, w);
+ }
+ }
+
+ // If name changes
+ if (ps->o.track_wdata
+ && (ps->atom_name == ev->atom || ps->atom_name_ewmh == ev->atom)) {
+ win *w = find_toplevel(ps, ev->window);
+ if (w && 1 == win_get_name(ps, w)) {
+ win_on_factor_change(ps, w);
+ }
+ }
+
+ // If class changes
+ if (ps->o.track_wdata && ps->atom_class == ev->atom) {
+ win *w = find_toplevel(ps, ev->window);
+ if (w) {
+ win_get_class(ps, w);
+ win_on_factor_change(ps, w);
+ }
+ }
+
+ // If role changes
+ if (ps->o.track_wdata && ps->atom_role == ev->atom) {
+ win *w = find_toplevel(ps, ev->window);
+ if (w && 1 == win_get_role(ps, w)) {
+ win_on_factor_change(ps, w);
+ }
+ }
+
+ // If _TDE_WM_WINDOW_SHADOW changes
+ if (ps->o.respect_prop_shadow && ps->atom_compton_shadow == ev->atom) {
+ win *w = find_win(ps, ev->window);
+ if (w)
+ win_update_prop_shadow(ps, w);
+ }
+
+ // If a leader property changes
+ if ((ps->o.detect_transient && ps->atom_transient == ev->atom)
+ || (ps->o.detect_client_leader && ps->atom_client_leader == ev->atom)) {
+ win *w = find_toplevel(ps, ev->window);
+ if (w) {
+ win_update_leader(ps, w);
+ }
+ }
+
+ // Check for other atoms we are tracking
+ for (latom_t *platom = ps->track_atom_lst; platom; platom = platom->next) {
+ if (platom->atom == ev->atom) {
+ win *w = find_win(ps, ev->window);
+ if (!w)
+ w = find_toplevel(ps, ev->window);
+ if (w)
+ win_on_factor_change(ps, w);
+ break;
+ }
+ }
+}
+
+inline static void
+ev_damage_notify(session_t *ps, XDamageNotifyEvent *ev) {
+ damage_win(ps, ev);
+}
+
+inline static void
+ev_shape_notify(session_t *ps, XShapeEvent *ev) {
+ win *w = find_win(ps, ev->window);
+ if (!w || IsUnmapped == w->a.map_state) return;
+
+ /*
+ * Empty border_size may indicated an
+ * unmapped/destroyed window, in which case
+ * seemingly BadRegion errors would be triggered
+ * if we attempt to rebuild border_size
+ */
+ if (w->border_size) {
+ // Mark the old border_size as damaged
+ add_damage(ps, w->border_size);
+
+ w->border_size = border_size(ps, w, true);
+
+ // Mark the new border_size as damaged
+ add_damage(ps, copy_region(ps, w->border_size));
+ }
+
+ // Redo bounding shape detection and rounded corner detection
+ win_update_shape(ps, w);
+
+ update_reg_ignore_expire(ps, w);
+}
+
+/**
+ * Handle ScreenChangeNotify events from X RandR extension.
+ */
+static void
+ev_screen_change_notify(session_t *ps,
+ XRRScreenChangeNotifyEvent __attribute__((unused)) *ev) {
+ 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) {
+ fprintf(stderr, "ev_screen_change_notify(): Refresh rate detection "
+ "failed, --sw-opti disabled.");
+ ps->o.sw_opti = false;
+ }
+ }
+}
+
+#if defined(DEBUG_EVENTS) || defined(DEBUG_RESTACK)
+/**
+ * Get a window's name from window ID.
+ */
+static bool
+ev_window_name(session_t *ps, Window wid, char **name) {
+ bool to_free = false;
+
+ *name = "";
+ if (wid) {
+ *name = "(Failed to get title)";
+ if (ps->root == wid)
+ *name = "(Root window)";
+ else if (ps->overlay == wid)
+ *name = "(Overlay)";
+ else {
+ win *w = find_win(ps, wid);
+ if (!w)
+ w = find_toplevel(ps, wid);
+
+ if (w && w->name)
+ *name = w->name;
+ else if (!(w && w->client_win
+ && (to_free = wid_get_name(ps, w->client_win, name))))
+ to_free = wid_get_name(ps, wid, name);
+ }
+ }
+
+ return to_free;
+}
+#endif
+
+static void
+ev_handle(session_t *ps, XEvent *ev) {
+ if ((ev->type & 0x7f) != KeymapNotify) {
+ discard_ignore(ps, ev->xany.serial);
+ }
+
+#ifdef DEBUG_EVENTS
+ if (!isdamagenotify(ps, ev)) {
+ Window wid = ev_window(ps, ev);
+ char *window_name = NULL;
+ bool to_free = false;
+
+ to_free = ev_window_name(ps, wid, &window_name);
+
+ print_timestamp(ps);
+ printf("event %10.10s serial %#010x window %#010lx \"%s\"\n",
+ ev_name(ps, ev), ev_serial(ev), wid, window_name);
+
+ if (to_free) {
+ cxfree(window_name);
+ window_name = NULL;
+ }
+ }
+
+#endif
+
+ switch (ev->type) {
+ case FocusIn:
+ ev_focus_in(ps, (XFocusChangeEvent *)ev);
+ break;
+ case FocusOut:
+ ev_focus_out(ps, (XFocusChangeEvent *)ev);
+ break;
+ case CreateNotify:
+ ev_create_notify(ps, (XCreateWindowEvent *)ev);
+ break;
+ case ConfigureNotify:
+ ev_configure_notify(ps, (XConfigureEvent *)ev);
+ break;
+ case DestroyNotify:
+ ev_destroy_notify(ps, (XDestroyWindowEvent *)ev);
+ break;
+ case MapNotify:
+ ev_map_notify(ps, (XMapEvent *)ev);
+ break;
+ case UnmapNotify:
+ ev_unmap_notify(ps, (XUnmapEvent *)ev);
+ break;
+ case ReparentNotify:
+ ev_reparent_notify(ps, (XReparentEvent *)ev);
+ break;
+ case CirculateNotify:
+ ev_circulate_notify(ps, (XCirculateEvent *)ev);
+ break;
+ case Expose:
+ ev_expose(ps, (XExposeEvent *)ev);
+ break;
+ case PropertyNotify:
+ ev_property_notify(ps, (XPropertyEvent *)ev);
+ break;
+ default:
+ if (ps->shape_exists && ev->type == ps->shape_event) {
+ ev_shape_notify(ps, (XShapeEvent *) ev);
+ break;
+ }
+ if (ps->randr_exists && ev->type == (ps->randr_event + RRScreenChangeNotify)) {
+ ev_screen_change_notify(ps, (XRRScreenChangeNotifyEvent *) ev);
+ break;
+ }
+ if (isdamagenotify(ps, ev)) {
+ ev_damage_notify(ps, (XDamageNotifyEvent *) ev);
+ break;
+ }
+ }
+}
+
+// === Main ===
+
+/**
+ * Print usage text and exit.
+ */
+static void
+usage(int ret) {
+#define WARNING_DISABLED " (DISABLED AT COMPILE TIME)"
+#define WARNING
+ const static char *usage_text =
+ "compton (" COMPTON_VERSION ")\n"
+ "usage: compton [options]\n"
+ "Options:\n"
+ "\n"
+ "-d display\n"
+ " Which display should be managed.\n"
+ "-r radius\n"
+ " The blur radius for shadows. (default 12)\n"
+ "-o opacity\n"
+ " The translucency for shadows. (default .75)\n"
+ "-l left-offset\n"
+ " The left offset for shadows. (default -15)\n"
+ "-t top-offset\n"
+ " The top offset for shadows. (default -15)\n"
+ "-I fade-in-step\n"
+ " Opacity change between steps while fading in. (default 0.028)\n"
+ "-O fade-out-step\n"
+ " Opacity change between steps while fading out. (default 0.03)\n"
+ "-D fade-delta-time\n"
+ " The time between steps in a fade in milliseconds. (default 10)\n"
+ "-m opacity\n"
+ " The opacity for menus. (default 1.0)\n"
+ "-c\n"
+ " Enabled client-side shadows on windows.\n"
+ "-C\n"
+ " Avoid drawing shadows on dock/panel windows.\n"
+ "-z\n"
+ " Zero the part of the shadow's mask behind the window (experimental).\n"
+ "-f\n"
+ " Fade windows in/out when opening/closing and when opacity\n"
+ " changes, unless --no-fading-openclose is used.\n"
+ "-F\n"
+ " Equals -f. Deprecated.\n"
+ "-i opacity\n"
+ " Opacity of inactive windows. (0.1 - 1.0)\n"
+ "-e opacity\n"
+ " Opacity of window titlebars and borders. (0.1 - 1.0)\n"
+ "-G\n"
+ " Don't draw shadows on DND windows\n"
+ "-b\n"
+ " Daemonize process.\n"
+ "-S\n"
+ " Enable synchronous operation (for debugging).\n"
+ "-v\n"
+ " Print version Number and exit\\n"
+ "--config path\n"
+ " Look for configuration file at the path.\n"
+ "--write-pid-path path\n"
+ " Write process ID to a file.\n"
+ "--shadow-red value\n"
+ " Red color value of shadow (0.0 - 1.0, defaults to 0).\n"
+ "--shadow-green value\n"
+ " Green color value of shadow (0.0 - 1.0, defaults to 0).\n"
+ "--shadow-blue value\n"
+ " Blue color value of shadow (0.0 - 1.0, defaults to 0).\n"
+ "--inactive-opacity-override\n"
+ " Inactive opacity set by -i overrides value of _NET_WM_OPACITY.\n"
+ "--inactive-dim value\n"
+ " Dim inactive windows. (0.0 - 1.0, defaults to 0)\n"
+ "--active-opacity opacity\n"
+ " Default opacity for active windows. (0.0 - 1.0)\n"
+ "--mark-wmwin-focused\n"
+ " Try to detect WM windows and mark them as active.\n"
+ "--shadow-exclude condition\n"
+ " Exclude conditions for shadows.\n"
+ "--fade-exclude condition\n"
+ " Exclude conditions for fading.\n"
+ "--mark-ovredir-focused\n"
+ " Mark windows that have no WM frame as active.\n"
+ "--no-fading-openclose\n"
+ " Do not fade on window open/close.\n"
+ "--no-fading-opacitychange\n"
+ " Do not fade on window opacity change.\n"
+ "--shadow-ignore-shaped\n"
+ " Do not paint shadows on shaped windows.\n"
+ "--detect-rounded-corners\n"
+ " Try to detect windows with rounded corners and don't consider\n"
+ " them shaped windows.\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"
+ "--refresh-rate val\n"
+ " Specify refresh rate of the screen. If not specified or 0, compton\n"
+ " will try detecting this with X RandR extension.\n"
+ "--vsync vsync-method\n"
+ " Set VSync method. There are up to 4 VSync methods currently available.\n"
+ " none = No VSync\n"
+#undef WARNING
+#ifndef CONFIG_VSYNC_DRM
+#define WARNING WARNING_DISABLED
+#else
+#define WARNING
+#endif
+ " drm = VSync with DRM_IOCTL_WAIT_VBLANK. May only work on some\n"
+ " drivers." WARNING "\n"
+#undef WARNING
+#ifndef CONFIG_VSYNC_OPENGL
+#define WARNING WARNING_DISABLED
+#else
+#define WARNING
+#endif
+ " opengl = Try to VSync with SGI_video_sync OpenGL extension. Only\n"
+ " work on some drivers." WARNING"\n"
+ " opengl-oml = Try to VSync with OML_sync_control OpenGL extension.\n"
+ " Only work on some drivers. Experimental." WARNING"\n"
+ " opengl-swc = Try to VSync with SGI_swap_control OpenGL extension.\n"
+ " Only work on some drivers. Works only with GLX backend.\n"
+ " Does not actually control paint timing, only buffer swap is\n"
+ " affected, so it doesn't have the effect of --sw-opti unlike\n"
+ " other methods." WARNING "\n"
+ " opengl-mswc = Try to VSync with MESA_swap_control OpenGL\n"
+ " extension. Basically the same as opengl-swc above, except the\n"
+ " extension we use." WARNING "\n"
+ "--vsync-aggressive\n"
+ " Attempt to send painting request before VBlank and do XFlush()\n"
+ " during VBlank. This switch may be lifted out at any moment.\n"
+ "--alpha-step val\n"
+ " X Render backend: Step for pregenerating alpha pictures. \n"
+ " 0.01 - 1.0. Defaults to 0.03.\n"
+ "--dbe\n"
+ " Enable DBE painting mode, intended to use with VSync to\n"
+ " (hopefully) eliminate tearing.\n"
+ "--paint-on-overlay\n"
+ " Painting on X Composite overlay window.\n"
+ "--sw-opti\n"
+ " Limit compton to repaint at most once every 1 / refresh_rate\n"
+ " second to boost performance.\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"
+ "--respect-prop-shadow\n"
+ " Respect _TDE_WM_WINDOW_SHADOW. This a prototype-level feature, which\n"
+ " you must not rely on.\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"
+ "--unredir-if-possible-delay ms\n"
+ " Delay before unredirecting the window, in milliseconds.\n"
+ " Defaults to 0.\n"
+ "--unredir-if-possible-exclude condition\n"
+ " Conditions of windows that shouldn't be considered full-screen\n"
+ " for unredirecting screen.\n"
+ "--focus-exclude condition\n"
+ " Specify a list of conditions of windows that should always be\n"
+ " considered focused.\n"
+ "--inactive-dim-fixed\n"
+ " Use fixed inactive dim value.\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"
+ "--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"
+ "--blur-background\n"
+ " Blur background of semi-transparent / ARGB windows. Bad in\n"
+ " performance. The switch name may change without prior\n"
+ " notifications.\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"
+ "--blur-background-fixed\n"
+ " Use fixed blur strength instead of adjusting according to window\n"
+ " opacity.\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 Guassian 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"
+ "--blur-background-exclude condition\n"
+ " Exclude conditions for background blur.\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"
+ "--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"
+ "--opacity-rule opacity:condition\n"
+ " Specify a list of opacity rules, in the format \"PERCENT:PATTERN\",\n"
+ " like \'50:name *= \"Firefox\"'. compton-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"
+ "--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"
+#undef WARNING
+#ifndef CONFIG_XINERAMA
+#define WARNING WARNING_DISABLED
+#else
+#define WARNING
+#endif
+ "--xinerama-shadow-crop\n"
+ " Crop shadow of a window fully on a particular Xinerama screen to the\n"
+ " screen." WARNING "\n"
+ "--backend backend\n"
+ " Choose backend. Possible choices are xrender, glx, and\n"
+ " xr_glx_hybrid" WARNING ".\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"
+ "--glx-copy-from-front\n"
+ " GLX backend: Copy unmodified regions from front buffer instead of\n"
+ " redrawing them all. My tests with nvidia-drivers show a 5% decrease\n"
+ " in performance when the whole screen is modified, but a 30% increase\n"
+ " when only 1/4 is. My tests on nouveau show terrible slowdown. Could\n"
+ " work with --glx-swap-method but not --glx-use-copysubbuffermesa.\n"
+ "--glx-use-copysubbuffermesa\n"
+ " GLX backend: Use MESA_copy_sub_buffer to do partial screen update.\n"
+ " My tests on nouveau shows a 200% performance boost when only 1/4 of\n"
+ " the screen is updated. May break VSync and is not available on some\n"
+ " drivers. Overrides --glx-copy-from-front.\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.\n"
+ "--glx-swap-method undefined/copy/exchange/3/4/5/6/buffer-age\n"
+ " GLX backend: GLX buffer swap method we assume. Could be\n"
+ " undefined (0), copy (1), exchange (2), 3-6, or buffer-age (-1).\n"
+ " \"undefined\" is the slowest and the safest, and the default value.\n"
+ " 1 is fastest, but may fail on some drivers, 2-6 are gradually slower\n"
+ " but safer (6 is still faster than 0). -1 means auto-detect using\n"
+ " GLX_EXT_buffer_age, supported by some drivers. Useless with\n"
+ " --glx-use-copysubbuffermesa.\n"
+ "--glx-use-gpushader4\n"
+ " GLX backend: Use GL_EXT_gpu_shader4 for some optimization on blur\n"
+ " GLSL code. My tests on GTX 670 show no noticeable effect.\n"
+ "--xrender-sync\n"
+ " Attempt to synchronize client applications' draw calls with XSync(),\n"
+ " used on GLX backend to ensure up-to-date window content is painted.\n"
+#undef WARNING
+#ifndef CONFIG_XSYNC
+#define WARNING WARNING_DISABLED
+#else
+#define WARNING
+#endif
+ "--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." WARNING "\n"
+#undef WARNING
+#ifndef CONFIG_DBUS
+#define WARNING WARNING_DISABLED
+#else
+#define WARNING
+#endif
+ "--dbus\n"
+ " Enable remote control via D-Bus. See the D-BUS API section in the\n"
+ " man page for more details." WARNING "\n"
+ "--benchmark cycles\n"
+ " Benchmark mode. Repeatedly paint until reaching the specified cycles.\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"
+ ;
+ FILE *f = (ret ? stderr: stdout);
+ fputs(usage_text, f);
+#undef WARNING
+#undef WARNING_DISABLED
+
+ exit(ret);
+}
+
+/**
+ * Register a window as symbol, and initialize GLX context if wanted.
+ */
+static bool
+register_cm(session_t *ps) {
+ assert(!ps->reg_win);
+
+ ps->reg_win = XCreateSimpleWindow(ps->dpy, ps->root, 0, 0, 1, 1, 0,
+ None, None);
+
+ if (!ps->reg_win) {
+ printf_errf("(): Failed to create window.");
+ return false;
+ }
+
+ // Unredirect the window if it's redirected, just in case
+ if (ps->redirected)
+ XCompositeUnredirectWindow(ps->dpy, ps->reg_win, CompositeRedirectManual);
+
+ {
+ XClassHint *h = XAllocClassHint();
+ if (h) {
+ h->res_name = "compton";
+ h->res_class = "xcompmgr";
+ }
+ Xutf8SetWMProperties(ps->dpy, ps->reg_win, "xcompmgr", "xcompmgr",
+ NULL, 0, NULL, NULL, h);
+ cxfree(h);
+ }
+
+ // Set _NET_WM_PID
+ {
+ long pid = getpid();
+ if (!XChangeProperty(ps->dpy, ps->reg_win,
+ get_atom(ps, "_NET_WM_PID"), XA_CARDINAL, 32, PropModeReplace,
+ (unsigned char *) &pid, 1)) {
+ printf_errf("(): Failed to set _NET_WM_PID.");
+ }
+ }
+
+ // Set COMPTON_VERSION
+ if (!wid_set_text_prop(ps, ps->reg_win, get_atom(ps, "COMPTON_VERSION"), COMPTON_VERSION)) {
+ printf_errf("(): Failed to set COMPTON_VERSION.");
+ }
+
+ {
+ unsigned len = strlen(REGISTER_PROP) + 2;
+ int s = ps->scr;
+
+ while (s >= 10) {
+ ++len;
+ s /= 10;
+ }
+
+ Window w;
+ Atom a;
+ static char net_wm_cm[] = "_NET_WM_CM_Sxx";
+
+ snprintf (net_wm_cm, sizeof (net_wm_cm), "_NET_WM_CM_S%d", ps->scr);
+ a = XInternAtom (ps->dpy, net_wm_cm, False);
+
+ char *buf = malloc(len);
+ snprintf(buf, len, REGISTER_PROP "%d", ps->scr);
+ buf[len - 1] = '\0';
+ // setting this causes compton to abort on TDE login
+ // XSetSelectionOwner(ps->dpy, get_atom(ps, buf), ps->reg_win, 0);
+ free(buf);
+ }
+
+ return true;
+}
+
+/**
+ * Reopen streams for logging.
+ */
+static bool
+ostream_reopen(session_t *ps, const char *path) {
+ if (!path)
+ path = ps->o.logpath;
+ if (!path)
+ path = "/dev/null";
+
+ bool success = freopen(path, "a", stdout);
+ success = freopen(path, "a", stderr) && success;
+ if (!success)
+ printf_errfq(1, "(%s): freopen() failed.", path);
+
+ return success;
+}
+
+/**
+ * Fork program to background and disable all I/O streams.
+ */
+static inline bool
+fork_after(session_t *ps) {
+ if (getppid() == 1)
+ return true;
+
+#ifdef CONFIG_VSYNC_OPENGL
+ // GLX context must be released and reattached on fork
+ if (ps->glx_context && !glXMakeCurrent(ps->dpy, None, NULL)) {
+ printf_errf("(): Failed to detach GLx context.");
+ return false;
+ }
+#endif
+
+ int pid = fork();
+
+ if (-1 == pid) {
+ printf_errf("(): fork() failed.");
+ return false;
+ }
+
+ if (pid > 0) _exit(0);
+
+ setsid();
+
+#ifdef CONFIG_VSYNC_OPENGL
+ if (ps->glx_context
+ && !glXMakeCurrent(ps->dpy, get_tgt_window(ps), ps->glx_context)) {
+ printf_errf("(): Failed to make GLX context current.");
+ return false;
+ }
+#endif
+
+ // Mainly to suppress the _FORTIFY_SOURCE warning
+ bool success = freopen("/dev/null", "r", stdin);
+ if (!success) {
+ printf_errf("(): freopen() failed.");
+ return false;
+ }
+ success = ostream_reopen(ps, NULL);
+
+ return success;
+}
+
+/**
+ * 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)) {
+ printf_errf("(): Failed to write PID to \"%s\".", ps->o.write_pid_path);
+ return false;
+ }
+
+ fprintf(f, "%ld\n", (long) getpid());
+ fclose(f);
+
+ return true;
+}
+
+/**
+ * Parse a long number.
+ */
+static inline bool
+parse_long(const char *s, long *dest) {
+ const char *endptr = NULL;
+ long val = strtol(s, (char **) &endptr, 0);
+ if (!endptr || endptr == s) {
+ printf_errf("(\"%s\"): Invalid number.", s);
+ return false;
+ }
+ while (isspace(*endptr))
+ ++endptr;
+ if (*endptr) {
+ printf_errf("(\"%s\"): Trailing characters.", s);
+ return false;
+ }
+ *dest = val;
+ return true;
+}
+
+/**
+ * Parse a floating-point number in matrix.
+ */
+static inline const char *
+parse_matrix_readnum(const char *src, double *dest) {
+ char *pc = NULL;
+ double val = strtod(src, &pc);
+ if (!pc || pc == src) {
+ printf_errf("(\"%s\"): No number found.", src);
+ return src;
+ }
+
+ while (*pc && (isspace(*pc) || ',' == *pc))
+ ++pc;
+
+ *dest = val;
+
+ return pc;
+}
+
+/**
+ * Parse a matrix.
+ */
+static inline XFixed *
+parse_matrix(session_t *ps, const char *src, const char **endptr) {
+ int wid = 0, hei = 0;
+ const char *pc = NULL;
+ XFixed *matrix = NULL;
+
+ // Get matrix width and height
+ {
+ double val = 0.0;
+ if (src == (pc = parse_matrix_readnum(src, &val)))
+ goto parse_matrix_err;
+ src = pc;
+ wid = val;
+ if (src == (pc = parse_matrix_readnum(src, &val)))
+ goto parse_matrix_err;
+ src = pc;
+ hei = val;
+ }
+
+ // Validate matrix width and height
+ if (wid <= 0 || hei <= 0) {
+ printf_errf("(): Invalid matrix width/height.");
+ goto parse_matrix_err;
+ }
+ if (!(wid % 2 && hei % 2)) {
+ printf_errf("(): Width/height not odd.");
+ goto parse_matrix_err;
+ }
+ if (wid > 16 || hei > 16) {
+ printf_errf("(): Matrix width/height too large.");
+ goto parse_matrix_err;
+ }
+
+ // Allocate memory
+ matrix = calloc(wid * hei + 2, sizeof(XFixed));
+ if (!matrix) {
+ printf_errf("(): Failed to allocate memory for matrix.");
+ goto parse_matrix_err;
+ }
+
+ // Read elements
+ {
+ int skip = hei / 2 * wid + wid / 2;
+ bool hasneg = false;
+ for (int i = 0; i < wid * hei; ++i) {
+ // Ignore the center element
+ if (i == skip) {
+ matrix[2 + i] = XDoubleToFixed(0);
+ continue;
+ }
+ double val = 0;
+ if (src == (pc = parse_matrix_readnum(src, &val)))
+ goto parse_matrix_err;
+ src = pc;
+ if (val < 0) hasneg = true;
+ matrix[2 + i] = XDoubleToFixed(val);
+ }
+ if (BKEND_XRENDER == ps->o.backend && hasneg)
+ printf_errf("(): A convolution kernel with negative values "
+ "may not work properly under X Render backend.");
+ }
+
+ // Detect trailing characters
+ for ( ;*pc && ';' != *pc; ++pc)
+ if (!isspace(*pc) && ',' != *pc) {
+ printf_errf("(): Trailing characters in matrix string.");
+ goto parse_matrix_err;
+ }
+
+ // Jump over spaces after ';'
+ if (';' == *pc) {
+ ++pc;
+ while (*pc && isspace(*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) {
+ printf_errf("(): Only one matrix expected.");
+ goto parse_matrix_err;
+ }
+
+ // Fill in width and height
+ matrix[0] = XDoubleToFixed(wid);
+ matrix[1] = XDoubleToFixed(hei);
+
+ return matrix;
+
+parse_matrix_err:
+ free(matrix);
+ return NULL;
+}
+
+/**
+ * Parse a convolution kernel.
+ */
+static inline XFixed *
+parse_conv_kern(session_t *ps, const char *src, const char **endptr) {
+ return parse_matrix(ps, src, endptr);
+}
+
+/**
+ * Parse a list of convolution kernels.
+ */
+static bool
+parse_conv_kern_lst(session_t *ps, const char *src, XFixed **dest, int max) {
+ 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," },
+ };
+ for (int i = 0;
+ i < sizeof(CONV_KERN_PREDEF) / sizeof(CONV_KERN_PREDEF[0]); ++i)
+ if (!strcmp(CONV_KERN_PREDEF[i].name, src))
+ return parse_conv_kern_lst(ps, CONV_KERN_PREDEF[i].kern_str, dest, max);
+
+ int i = 0;
+ const char *pc = src;
+
+ // Free old kernels
+ for (i = 0; i < max; ++i) {
+ free(dest[i]);
+ dest[i] = NULL;
+ }
+
+ // Continue parsing until the end of source string
+ i = 0;
+ while (pc && *pc && i < max - 1) {
+ if (!(dest[i++] = parse_conv_kern(ps, pc, &pc)))
+ return false;
+ }
+
+ if (*pc) {
+ printf_errf("(): Too many blur kernels!");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Parse a X geometry.
+ */
+static inline bool
+parse_geometry(session_t *ps, const char *src, geometry_t *dest) {
+ geometry_t geom = { .wid = -1, .hei = -1, .x = -1, .y = -1 };
+ long val = 0L;
+ char *endptr = NULL;
+
+#define T_STRIPSPACE() do { \
+ while (*src && isspace(*src)) ++src; \
+ if (!*src) goto parse_geometry_end; \
+} while(0)
+
+ T_STRIPSPACE();
+
+ // Parse width
+ // Must be base 10, because "0x0..." may appear
+ if (!('+' == *src || '-' == *src)) {
+ val = strtol(src, &endptr, 10);
+ if (endptr && src != endptr) {
+ geom.wid = val;
+ assert(geom.wid >= 0);
+ src = endptr;
+ }
+ T_STRIPSPACE();
+ }
+
+ // Parse height
+ if ('x' == *src) {
+ ++src;
+ val = strtol(src, &endptr, 10);
+ if (endptr && src != endptr) {
+ geom.hei = val;
+ if (geom.hei < 0) {
+ printf_errf("(\"%s\"): Invalid height.", src);
+ return false;
+ }
+ src = endptr;
+ }
+ T_STRIPSPACE();
+ }
+
+ // Parse x
+ if ('+' == *src || '-' == *src) {
+ val = strtol(src, &endptr, 10);
+ if (endptr && src != endptr) {
+ geom.x = val;
+ if ('-' == *src && geom.x <= 0)
+ geom.x -= 2;
+ src = endptr;
+ }
+ T_STRIPSPACE();
+ }
+
+ // Parse y
+ if ('+' == *src || '-' == *src) {
+ val = strtol(src, &endptr, 10);
+ if (endptr && src != endptr) {
+ geom.y = val;
+ if ('-' == *src && geom.y <= 0)
+ geom.y -= 2;
+ src = endptr;
+ }
+ T_STRIPSPACE();
+ }
+
+ if (*src) {
+ printf_errf("(\"%s\"): Trailing characters.", src);
+ return false;
+ }
+
+parse_geometry_end:
+ *dest = geom;
+ return true;
+}
+
+/**
+ * Parse a list of opacity rules.
+ */
+static inline bool
+parse_rule_opacity(session_t *ps, const char *src) {
+ // Find opacity value
+ char *endptr = NULL;
+ long val = strtol(src, &endptr, 0);
+ if (!endptr || endptr == src) {
+ printf_errf("(\"%s\"): No opacity specified?", src);
+ return false;
+ }
+ if (val > 100 || val < 0) {
+ printf_errf("(\"%s\"): Opacity %ld invalid.", src, val);
+ return false;
+ }
+
+ // Skip over spaces
+ while (*endptr && isspace(*endptr))
+ ++endptr;
+ if (':' != *endptr) {
+ printf_errf("(\"%s\"): Opacity terminator not found.", src);
+ return false;
+ }
+ ++endptr;
+
+ // Parse pattern
+ // I hope 1-100 is acceptable for (void *)
+ return c2_parsed(ps, &ps->o.opacity_rules, endptr, (void *) val);
+}
+
+#ifdef CONFIG_LIBCONFIG
+/**
+ * Get a file stream of the configuration file to read.
+ *
+ * Follows the XDG specification to search for the configuration file.
+ */
+static FILE *
+open_config_file(char *cpath, char **ppath) {
+ const static char *config_filename = "/compton-tde.conf";
+ const static char *config_filename_legacy = "/.compton-tde.conf";
+ const static char *config_home_suffix = "/.config";
+ const static char *config_system_dir = "/etc/xdg";
+
+ char *dir = NULL, *home = NULL;
+ char *path = cpath;
+ FILE *f = NULL;
+
+ if (path) {
+ f = fopen(path, "r");
+ if (f && ppath)
+ *ppath = path;
+ return f;
+ }
+
+ // Check user configuration file in $XDG_CONFIG_HOME firstly
+ if (!((dir = getenv("XDG_CONFIG_HOME")) && strlen(dir))) {
+ if (!((home = getenv("HOME")) && strlen(home)))
+ return NULL;
+
+ path = mstrjoin3(home, config_home_suffix, config_filename);
+ }
+ else
+ path = mstrjoin(dir, config_filename);
+
+ f = fopen(path, "r");
+
+ if (f && ppath)
+ *ppath = path;
+ else
+ free(path);
+ if (f)
+ return f;
+
+ // Then check user configuration file in $HOME
+ if ((home = getenv("HOME")) && strlen(home)) {
+ path = mstrjoin(home, config_filename_legacy);
+ f = fopen(path, "r");
+ if (f && ppath)
+ *ppath = path;
+ else
+ free(path);
+ if (f)
+ return f;
+ }
+
+ // Check system configuration file in $XDG_CONFIG_DIRS at last
+ if ((dir = getenv("XDG_CONFIG_DIRS")) && strlen(dir)) {
+ char *part = strtok(dir, ":");
+ while (part) {
+ path = mstrjoin(part, config_filename);
+ f = fopen(path, "r");
+ if (f && ppath)
+ *ppath = path;
+ else
+ free(path);
+ if (f)
+ return f;
+ part = strtok(NULL, ":");
+ }
+ }
+ else {
+ path = mstrjoin(config_system_dir, config_filename);
+ f = fopen(path, "r");
+ if (f && ppath)
+ *ppath = path;
+ else
+ free(path);
+ if (f)
+ return f;
+ }
+
+ return NULL;
+}
+
+/**
+ * Parse a condition list in configuration file.
+ */
+static inline void
+parse_cfg_condlst(session_t *ps, 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(ps, 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(ps, pcondlst, config_setting_get_string(setting));
+ }
+ }
+}
+
+/**
+ * Parse an opacity rule list in configuration file.
+ */
+static inline void
+parse_cfg_condlst_opct(session_t *ps, 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(ps, config_setting_get_string_elem(setting,
+ i)))
+ exit(1);
+ }
+ // Treat it as a single pattern if it's a string
+ else if (CONFIG_TYPE_STRING == config_setting_type(setting)) {
+ parse_rule_opacity(ps, config_setting_get_string(setting));
+ }
+ }
+}
+
+/**
+ * Parse a configuration file from default location.
+ */
+static void
+parse_config(session_t *ps, struct options_tmp *pcfgtmp) {
+ char *path = NULL;
+ FILE *f;
+ config_t cfg;
+ int ival = 0;
+ double dval = 0.0;
+ // libconfig manages string memory itself, so no need to manually free
+ // anything
+ const char *sval = NULL;
+
+ f = open_config_file(ps->o.config_file, &path);
+ if (!f) {
+ if (ps->o.config_file) {
+ printf_errfq(1, "(): Failed to read configuration file \"%s\".",
+ ps->o.config_file);
+ free(ps->o.config_file);
+ ps->o.config_file = NULL;
+ }
+ return;
+ }
+
+ config_init(&cfg);
+#ifndef CONFIG_LIBCONFIG_LEGACY
+ {
+ // dirname() could modify the original string, thus we must pass a
+ // copy
+ char *path2 = mstrcpy(path);
+ char *parent = dirname(path2);
+
+ if (parent)
+ config_set_include_dir(&cfg, parent);
+
+ free(path2);
+ }
+#endif
+
+ if (CONFIG_FALSE == config_read(&cfg, f)) {
+ printf("Error when reading configuration file \"%s\", line %d: %s\n",
+ path, config_error_line(&cfg), config_error_text(&cfg));
+ config_destroy(&cfg);
+ free(path);
+ return;
+ }
+ config_set_auto_convert(&cfg, 1);
+
+ if (path != ps->o.config_file) {
+ free(ps->o.config_file);
+ ps->o.config_file = path;
+ }
+
+ // Get options from the configuration file. We don't do range checking
+ // right now. It will be done later
+
+ // -D (fade_delta)
+ if (lcfg_lookup_int(&cfg, "fade-delta", &ival))
+ ps->o.fade_delta = ival;
+ // -I (fade_in_step)
+ if (config_lookup_float(&cfg, "fade-in-step", &dval))
+ ps->o.fade_in_step = normalize_d(dval) * OPAQUE;
+ // -O (fade_out_step)
+ if (config_lookup_float(&cfg, "fade-out-step", &dval))
+ ps->o.fade_out_step = normalize_d(dval) * OPAQUE;
+ // -r (shadow_radius)
+ lcfg_lookup_int(&cfg, "shadow-radius", &ps->o.shadow_radius);
+ // -o (shadow_opacity)
+ config_lookup_float(&cfg, "shadow-opacity", &ps->o.shadow_opacity);
+ // -l (shadow_offset_x)
+ lcfg_lookup_int(&cfg, "shadow-offset-x", &ps->o.shadow_offset_x);
+ // -t (shadow_offset_y)
+ lcfg_lookup_int(&cfg, "shadow-offset-y", &ps->o.shadow_offset_y);
+ // -i (inactive_opacity)
+ if (config_lookup_float(&cfg, "inactive-opacity", &dval))
+ ps->o.inactive_opacity = normalize_d(dval) * OPAQUE;
+ // --active_opacity
+ if (config_lookup_float(&cfg, "active-opacity", &dval))
+ ps->o.active_opacity = normalize_d(dval) * OPAQUE;
+ // -e (frame_opacity)
+ config_lookup_float(&cfg, "frame-opacity", &ps->o.frame_opacity);
+ // -z (clear_shadow)
+ lcfg_lookup_bool(&cfg, "clear-shadow", &ps->o.clear_shadow);
+ // -c (shadow_enable)
+ if (config_lookup_bool(&cfg, "shadow", &ival) && ival)
+ wintype_arr_enable(ps->o.wintype_shadow);
+ // -C (no_dock_shadow)
+ lcfg_lookup_bool(&cfg, "no-dock-shadow", &pcfgtmp->no_dock_shadow);
+ // -G (no_dnd_shadow)
+ lcfg_lookup_bool(&cfg, "no-dnd-shadow", &pcfgtmp->no_dnd_shadow);
+ // -m (menu_opacity)
+ config_lookup_float(&cfg, "menu-opacity", &pcfgtmp->menu_opacity);
+ // -f (fading_enable)
+ if (config_lookup_bool(&cfg, "fading", &ival) && ival)
+ wintype_arr_enable(ps->o.wintype_fade);
+ // --no-fading-open-close
+ lcfg_lookup_bool(&cfg, "no-fading-openclose", &ps->o.no_fading_openclose);
+ // --no-fading-opacitychange
+ lcfg_lookup_bool(&cfg, "no-fading-opacitychange", &ps->o.no_fading_opacitychange);
+ // --shadow-red
+ config_lookup_float(&cfg, "shadow-red", &ps->o.shadow_red);
+ // --shadow-green
+ config_lookup_float(&cfg, "shadow-green", &ps->o.shadow_green);
+ // --shadow-blue
+ config_lookup_float(&cfg, "shadow-blue", &ps->o.shadow_blue);
+ // --shadow-exclude-reg
+ if (config_lookup_string(&cfg, "shadow-exclude-reg", &sval)
+ && !parse_geometry(ps, sval, &ps->o.shadow_exclude_reg_geom))
+ exit(1);
+ // --inactive-opacity-override
+ lcfg_lookup_bool(&cfg, "inactive-opacity-override",
+ &ps->o.inactive_opacity_override);
+ // --inactive-dim
+ config_lookup_float(&cfg, "inactive-dim", &ps->o.inactive_dim);
+ // --mark-wmwin-focused
+ lcfg_lookup_bool(&cfg, "mark-wmwin-focused", &ps->o.mark_wmwin_focused);
+ // --mark-ovredir-focused
+ lcfg_lookup_bool(&cfg, "mark-ovredir-focused",
+ &ps->o.mark_ovredir_focused);
+ // --shadow-ignore-shaped
+ lcfg_lookup_bool(&cfg, "shadow-ignore-shaped",
+ &ps->o.shadow_ignore_shaped);
+ // --detect-rounded-corners
+ lcfg_lookup_bool(&cfg, "detect-rounded-corners",
+ &ps->o.detect_rounded_corners);
+ // --xinerama-shadow-crop
+ lcfg_lookup_bool(&cfg, "xinerama-shadow-crop",
+ &ps->o.xinerama_shadow_crop);
+ // --detect-client-opacity
+ lcfg_lookup_bool(&cfg, "detect-client-opacity",
+ &ps->o.detect_client_opacity);
+ // --refresh-rate
+ lcfg_lookup_int(&cfg, "refresh-rate", &ps->o.refresh_rate);
+ // --vsync
+ if (config_lookup_string(&cfg, "vsync", &sval) && !parse_vsync(ps, sval))
+ exit(1);
+ // --backend
+ if (config_lookup_string(&cfg, "backend", &sval) && !parse_backend(ps, sval))
+ exit(1);
+ // --alpha-step
+ config_lookup_float(&cfg, "alpha-step", &ps->o.alpha_step);
+ // --dbe
+ lcfg_lookup_bool(&cfg, "dbe", &ps->o.dbe);
+ // --paint-on-overlay
+ lcfg_lookup_bool(&cfg, "paint-on-overlay", &ps->o.paint_on_overlay);
+ // --sw-opti
+ lcfg_lookup_bool(&cfg, "sw-opti", &ps->o.sw_opti);
+ // --use-ewmh-active-win
+ lcfg_lookup_bool(&cfg, "use-ewmh-active-win",
+ &ps->o.use_ewmh_active_win);
+ // --unredir-if-possible
+ lcfg_lookup_bool(&cfg, "unredir-if-possible",
+ &ps->o.unredir_if_possible);
+ // --unredir-if-possible-delay
+ if (lcfg_lookup_int(&cfg, "unredir-if-possible-delay", &ival))
+ ps->o.unredir_if_possible_delay = ival;
+ // --inactive-dim-fixed
+ lcfg_lookup_bool(&cfg, "inactive-dim-fixed", &ps->o.inactive_dim_fixed);
+ // --detect-transient
+ lcfg_lookup_bool(&cfg, "detect-transient", &ps->o.detect_transient);
+ // --detect-client-leader
+ lcfg_lookup_bool(&cfg, "detect-client-leader",
+ &ps->o.detect_client_leader);
+ // --shadow-exclude
+ parse_cfg_condlst(ps, &cfg, &ps->o.shadow_blacklist, "shadow-exclude");
+ // --fade-exclude
+ parse_cfg_condlst(ps, &cfg, &ps->o.fade_blacklist, "fade-exclude");
+ // --focus-exclude
+ parse_cfg_condlst(ps, &cfg, &ps->o.focus_blacklist, "focus-exclude");
+ // --invert-color-include
+ parse_cfg_condlst(ps, &cfg, &ps->o.invert_color_list, "invert-color-include");
+ // --blur-background-exclude
+ parse_cfg_condlst(ps, &cfg, &ps->o.blur_background_blacklist, "blur-background-exclude");
+ // --opacity-rule
+ parse_cfg_condlst_opct(ps, &cfg, "opacity-rule");
+ // --unredir-if-possible-exclude
+ parse_cfg_condlst(ps, &cfg, &ps->o.unredir_if_possible_blacklist, "unredir-if-possible-exclude");
+ // --blur-background
+ lcfg_lookup_bool(&cfg, "blur-background", &ps->o.blur_background);
+ // --blur-background-frame
+ lcfg_lookup_bool(&cfg, "blur-background-frame",
+ &ps->o.blur_background_frame);
+ // --blur-background-fixed
+ lcfg_lookup_bool(&cfg, "blur-background-fixed",
+ &ps->o.blur_background_fixed);
+ // --blur-kern
+ if (config_lookup_string(&cfg, "blur-kern", &sval)
+ && !parse_conv_kern_lst(ps, sval, ps->o.blur_kerns, MAX_BLUR_PASS))
+ exit(1);
+ // --resize-damage
+ config_lookup_int(&cfg, "resize-damage", &ps->o.resize_damage);
+ // --glx-no-stencil
+ lcfg_lookup_bool(&cfg, "glx-no-stencil", &ps->o.glx_no_stencil);
+ // --glx-copy-from-front
+ lcfg_lookup_bool(&cfg, "glx-copy-from-front", &ps->o.glx_copy_from_front);
+ // --glx-use-copysubbuffermesa
+ lcfg_lookup_bool(&cfg, "glx-use-copysubbuffermesa", &ps->o.glx_use_copysubbuffermesa);
+ // --glx-no-rebind-pixmap
+ lcfg_lookup_bool(&cfg, "glx-no-rebind-pixmap", &ps->o.glx_no_rebind_pixmap);
+ // --glx-swap-method
+ if (config_lookup_string(&cfg, "glx-swap-method", &sval)
+ && !parse_glx_swap_method(ps, sval))
+ exit(1);
+ // --glx-use-gpushader4
+ lcfg_lookup_bool(&cfg, "glx-use-gpushader4", &ps->o.glx_use_gpushader4);
+ // --xrender-sync
+ lcfg_lookup_bool(&cfg, "xrender-sync", &ps->o.xrender_sync);
+ // --xrender-sync-fence
+ lcfg_lookup_bool(&cfg, "xrender-sync-fence", &ps->o.xrender_sync_fence);
+ // Wintype settings
+ {
+ wintype_t i;
+
+ for (i = 0; i < NUM_WINTYPES; ++i) {
+ char *str = mstrjoin("wintypes.", WINTYPES[i]);
+ config_setting_t *setting = config_lookup(&cfg, str);
+ free(str);
+ if (setting) {
+ if (config_setting_lookup_bool(setting, "shadow", &ival))
+ ps->o.wintype_shadow[i] = (bool) ival;
+ if (config_setting_lookup_bool(setting, "fade", &ival))
+ ps->o.wintype_fade[i] = (bool) ival;
+ if (config_setting_lookup_bool(setting, "focus", &ival))
+ ps->o.wintype_focus[i] = (bool) ival;
+ config_setting_lookup_float(setting, "opacity",
+ &ps->o.wintype_opacity[i]);
+ }
+ }
+ }
+
+ config_destroy(&cfg);
+
+ // Adjust shadow offsets
+ ps->o.shadow_offset_x = ((-ps->o.shadow_radius * 7 / 5) - ps->o.shadow_offset_x * ps->o.shadow_radius / 100);
+ ps->o.shadow_offset_y = ((-ps->o.shadow_radius * 7 / 5) - ps->o.shadow_offset_y * ps->o.shadow_radius / 100);
+}
+#endif
+
+/**
+ * Process arguments and configuration files.
+ */
+static void
+get_cfg(session_t *ps, int argc, char *const *argv, bool first_pass) {
+ const static char *shortopts = "D:I:O:d:r:o:m:l:t:i:e:hvscnfFCaSzGb";
+ const static 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' },
+ { "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' },
+ { "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", required_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 },
+ { "glx-copy-from-front", no_argument, NULL, 292 },
+ { "benchmark", required_argument, NULL, 293 },
+ { "benchmark-wid", required_argument, NULL, 294 },
+ { "glx-use-copysubbuffermesa", no_argument, NULL, 295 },
+ { "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 },
+ { "no-fading-opacitychange", no_argument, NULL, 314 },
+ // Must terminate with a NULL entry
+ { NULL, 0, NULL, 0 },
+ };
+
+ int o = 0, longopt_idx = -1, i = 0;
+
+ if (first_pass) {
+ // 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;
+ while (-1 !=
+ (o = getopt_long(argc, argv, shortopts, longopts, &longopt_idx))) {
+ if (256 == o)
+ ps->o.config_file = mstrcpy(optarg);
+ else if ('d' == o)
+ ps->o.display = mstrcpy(optarg);
+ else if ('S' == o)
+ ps->o.synchronize = true;
+ else if ('?' == o || ':' == o)
+ usage(1);
+ }
+
+ // Check for abundant positional arguments
+ if (optind < argc)
+ printf_errfq(1, "(): compton doesn't accept positional arguments.");
+
+ return;
+ }
+
+ struct options_tmp cfgtmp = {
+ .no_dock_shadow = false,
+ .no_dnd_shadow = false,
+ .menu_opacity = 1.0,
+ };
+ bool shadow_enable = false, fading_enable = false;
+ char *lc_numeric_old = mstrcpy(setlocale(LC_NUMERIC, NULL));
+
+ for (i = 0; i < NUM_WINTYPES; ++i) {
+ ps->o.wintype_fade[i] = false;
+ ps->o.wintype_shadow[i] = false;
+ ps->o.wintype_opacity[i] = 1.0;
+ }
+
+ // Enforce LC_NUMERIC locale "C" here to make sure dots are recognized
+ // instead of commas in atof().
+ setlocale(LC_NUMERIC, "C");
+
+#ifdef CONFIG_LIBCONFIG
+ parse_config(ps, &cfgtmp);
+#endif
+
+ // Parse commandline arguments. Range checking will be done later.
+
+ optind = 1;
+ while (-1 !=
+ (o = getopt_long(argc, argv, shortopts, longopts, &longopt_idx))) {
+ long val = 0;
+ switch (o) {
+#define P_CASEBOOL(idx, option) case idx: ps->o.option = true; break
+#define P_CASELONG(idx, option) \
+ case idx: \
+ if (!parse_long(optarg, &val)) exit(1); \
+ ps->o.option = val; \
+ break
+
+ // Short options
+ case 'h':
+ usage(0);
+ break;
+ case 'v': fprintf (stderr, "%s v%-3.2f\n", argv[0], _TDE_COMP_MGR_VERSION_); my_exit_code=0; exit (0);
+ case 'd':
+ case 'S':
+ break;
+ P_CASELONG('D', fade_delta);
+ case 'I':
+ ps->o.fade_in_step = normalize_d(atof(optarg)) * OPAQUE;
+ break;
+ case 'O':
+ ps->o.fade_out_step = normalize_d(atof(optarg)) * OPAQUE;
+ break;
+ case 'c':
+ shadow_enable = true;
+ break;
+ case 'C':
+ cfgtmp.no_dock_shadow = true;
+ break;
+ case 'G':
+ cfgtmp.no_dnd_shadow = true;
+ break;
+ case 'm':
+ cfgtmp.menu_opacity = atof(optarg);
+ break;
+ case 'f':
+ case 'F':
+ fading_enable = true;
+ break;
+ P_CASELONG('r', shadow_radius);
+ case 'o':
+ ps->o.shadow_opacity = atof(optarg);
+ break;
+ P_CASELONG('l', shadow_offset_x);
+ P_CASELONG('t', shadow_offset_y);
+ case 'i':
+ ps->o.inactive_opacity = (normalize_d(atof(optarg)) * OPAQUE);
+ break;
+ case 'e':
+ ps->o.frame_opacity = atof(optarg);
+ break;
+ P_CASEBOOL('z', clear_shadow);
+ case 'n':
+ case 'a':
+ case 's':
+ printf_errfq(1, "(): -n, -a, and -s have been removed.");
+ break;
+ P_CASEBOOL('b', fork_after_register);
+ // Long options
+ case 256:
+ // --config
+ break;
+ case 257:
+ // --shadow-red
+ ps->o.shadow_red = atof(optarg);
+ break;
+ case 258:
+ // --shadow-green
+ ps->o.shadow_green = atof(optarg);
+ break;
+ case 259:
+ // --shadow-blue
+ ps->o.shadow_blue = atof(optarg);
+ break;
+ P_CASEBOOL(260, inactive_opacity_override);
+ case 261:
+ // --inactive-dim
+ ps->o.inactive_dim = atof(optarg);
+ break;
+ P_CASEBOOL(262, mark_wmwin_focused);
+ case 263:
+ // --shadow-exclude
+ condlst_add(ps, &ps->o.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_CASELONG(269, refresh_rate);
+ case 270:
+ // --vsync
+ if (!parse_vsync(ps, optarg))
+ exit(1);
+ break;
+ case 271:
+ // --alpha-step
+ ps->o.alpha_step = atof(optarg);
+ break;
+ P_CASEBOOL(272, dbe);
+ P_CASEBOOL(273, paint_on_overlay);
+ P_CASEBOOL(274, sw_opti);
+ P_CASEBOOL(275, vsync_aggressive);
+ P_CASEBOOL(276, use_ewmh_active_win);
+ P_CASEBOOL(277, respect_prop_shadow);
+ P_CASEBOOL(278, unredir_if_possible);
+ case 279:
+ // --focus-exclude
+ condlst_add(ps, &ps->o.focus_blacklist, optarg);
+ break;
+ P_CASEBOOL(280, inactive_dim_fixed);
+ P_CASEBOOL(281, detect_transient);
+ P_CASEBOOL(282, detect_client_leader);
+ P_CASEBOOL(283, blur_background);
+ P_CASEBOOL(284, blur_background_frame);
+ P_CASEBOOL(285, blur_background_fixed);
+ P_CASEBOOL(286, dbus);
+ case 287:
+ // --logpath
+ ps->o.logpath = mstrcpy(optarg);
+ break;
+ case 288:
+ // --invert-color-include
+ condlst_add(ps, &ps->o.invert_color_list, optarg);
+ break;
+ case 289:
+ // --opengl
+ ps->o.backend = BKEND_GLX;
+ break;
+ case 290:
+ // --backend
+ if (!parse_backend(ps, optarg))
+ exit(1);
+ break;
+ P_CASEBOOL(291, glx_no_stencil);
+ P_CASEBOOL(292, glx_copy_from_front);
+ P_CASELONG(293, benchmark);
+ case 294:
+ // --benchmark-wid
+ ps->o.benchmark_wid = strtol(optarg, NULL, 0);
+ break;
+ P_CASEBOOL(295, glx_use_copysubbuffermesa);
+ case 296:
+ // --blur-background-exclude
+ condlst_add(ps, &ps->o.blur_background_blacklist, optarg);
+ break;
+ case 297:
+ // --active-opacity
+ ps->o.active_opacity = (normalize_d(atof(optarg)) * OPAQUE);
+ break;
+ P_CASEBOOL(298, glx_no_rebind_pixmap);
+ case 299:
+ // --glx-swap-method
+ if (!parse_glx_swap_method(ps, optarg))
+ exit(1);
+ break;
+ case 300:
+ // --fade-exclude
+ condlst_add(ps, &ps->o.fade_blacklist, optarg);
+ break;
+ case 301:
+ // --blur-kern
+ if (!parse_conv_kern_lst(ps, optarg, ps->o.blur_kerns, MAX_BLUR_PASS))
+ exit(1);
+ break;
+ P_CASELONG(302, resize_damage);
+ P_CASEBOOL(303, glx_use_gpushader4);
+ case 304:
+ // --opacity-rule
+ if (!parse_rule_opacity(ps, optarg))
+ exit(1);
+ break;
+ case 305:
+ // --shadow-exclude-reg
+ if (!parse_geometry(ps, optarg, &ps->o.shadow_exclude_reg_geom))
+ exit(1);
+ break;
+ case 306:
+ // --paint-exclude
+ condlst_add(ps, &ps->o.paint_blacklist, optarg);
+ break;
+ P_CASEBOOL(307, xinerama_shadow_crop);
+ case 308:
+ // --unredir-if-possible-exclude
+ condlst_add(ps, &ps->o.unredir_if_possible_blacklist, optarg);
+ break;
+ P_CASELONG(309, unredir_if_possible_delay);
+ case 310:
+ // --write-pid-path
+ ps->o.write_pid_path = mstrcpy(optarg);
+ break;
+ P_CASEBOOL(311, vsync_use_glfinish);
+ P_CASEBOOL(312, xrender_sync);
+ P_CASEBOOL(313, xrender_sync_fence);
+ P_CASEBOOL(314, no_fading_opacitychange);
+ default:
+ usage(1);
+ break;
+#undef P_CASEBOOL
+ }
+ }
+
+ // Restore LC_NUMERIC
+ setlocale(LC_NUMERIC, lc_numeric_old);
+ free(lc_numeric_old);
+
+ // Range checking and option assignments
+ ps->o.fade_delta = max_i(ps->o.fade_delta, 1);
+ ps->o.shadow_radius = max_i(ps->o.shadow_radius, 1);
+ ps->o.shadow_red = normalize_d(ps->o.shadow_red);
+ ps->o.shadow_green = normalize_d(ps->o.shadow_green);
+ ps->o.shadow_blue = normalize_d(ps->o.shadow_blue);
+ ps->o.inactive_dim = normalize_d(ps->o.inactive_dim);
+ ps->o.frame_opacity = normalize_d(ps->o.frame_opacity);
+ ps->o.shadow_opacity = normalize_d(ps->o.shadow_opacity);
+ cfgtmp.menu_opacity = normalize_d(cfgtmp.menu_opacity);
+ ps->o.refresh_rate = normalize_i_range(ps->o.refresh_rate, 0, 300);
+ ps->o.alpha_step = normalize_d_range(ps->o.alpha_step, 0.01, 1.0);
+ if (OPAQUE == ps->o.inactive_opacity) {
+ ps->o.inactive_opacity = 0;
+ }
+ if (OPAQUE == ps->o.active_opacity) {
+ ps->o.active_opacity = 0;
+ }
+ if (shadow_enable)
+ wintype_arr_enable(ps->o.wintype_shadow);
+ ps->o.wintype_shadow[WINTYPE_DESKTOP] = false;
+ if (cfgtmp.no_dock_shadow)
+ ps->o.wintype_shadow[WINTYPE_DOCK] = false;
+ if (cfgtmp.no_dnd_shadow)
+ ps->o.wintype_shadow[WINTYPE_DND] = false;
+ if (fading_enable)
+ wintype_arr_enable(ps->o.wintype_fade);
+ if (1.0 != cfgtmp.menu_opacity) {
+ ps->o.wintype_opacity[WINTYPE_DROPDOWN_MENU] = cfgtmp.menu_opacity;
+ ps->o.wintype_opacity[WINTYPE_POPUP_MENU] = cfgtmp.menu_opacity;
+ }
+
+ // --blur-background-frame implies --blur-background
+ if (ps->o.blur_background_frame)
+ ps->o.blur_background = true;
+
+ if (ps->o.xrender_sync_fence)
+ ps->o.xrender_sync = true;
+
+ // Other variables determined by options
+
+ // Determine whether we need to track focus changes
+ if (ps->o.inactive_opacity || ps->o.active_opacity || ps->o.inactive_dim) {
+ ps->o.track_focus = true;
+ }
+
+ // Determine whether we track window grouping
+ if (ps->o.detect_transient || ps->o.detect_client_leader) {
+ ps->o.track_leader = true;
+ }
+
+ // Fill default blur kernel
+ if (ps->o.blur_background && !ps->o.blur_kerns[0]) {
+ // Convolution filter parameter (box blur)
+ // gaussian or binomial filters are definitely superior, yet looks
+ // like they aren't supported as of xorg-server-1.13.0
+ const static XFixed convolution_blur[] = {
+ // Must convert to XFixed with XDoubleToFixed()
+ // Matrix size
+ XDoubleToFixed(3), XDoubleToFixed(3),
+ // Matrix
+ XDoubleToFixed(1), XDoubleToFixed(1), XDoubleToFixed(1),
+ XDoubleToFixed(1), XDoubleToFixed(1), XDoubleToFixed(1),
+ XDoubleToFixed(1), XDoubleToFixed(1), XDoubleToFixed(1),
+ };
+ ps->o.blur_kerns[0] = malloc(sizeof(convolution_blur));
+ if (!ps->o.blur_kerns[0]) {
+ printf_errf("(): Failed to allocate memory for convolution kernel.");
+ exit(1);
+ }
+ memcpy(ps->o.blur_kerns[0], &convolution_blur, sizeof(convolution_blur));
+ }
+
+ rebuild_shadow_exclude_reg(ps);
+
+ if (ps->o.resize_damage < 0)
+ printf_errf("(): Negative --resize-damage does not work correctly.");
+}
+
+/**
+ * Fetch all required atoms and save them to a session.
+ */
+static void
+init_atoms(session_t *ps) {
+ ps->atom_opacity = get_atom(ps, "_NET_WM_WINDOW_OPACITY");
+ ps->atom_frame_extents = get_atom(ps, "_NET_FRAME_EXTENTS");
+ ps->atom_client = get_atom(ps, "WM_STATE");
+ ps->atom_name = XA_WM_NAME;
+ ps->atom_name_ewmh = get_atom(ps, "_NET_WM_NAME");
+ ps->atom_class = XA_WM_CLASS;
+ ps->atom_role = get_atom(ps, "WM_WINDOW_ROLE");
+ ps->atom_transient = XA_WM_TRANSIENT_FOR;
+ ps->atom_client_leader = get_atom(ps, "WM_CLIENT_LEADER");
+ ps->atom_ewmh_active_win = get_atom(ps, "_NET_ACTIVE_WINDOW");
+ ps->atom_compton_shadow = get_atom(ps, "_TDE_WM_WINDOW_SHADOW");
+
+ ps->atom_win_type = get_atom(ps, "_NET_WM_WINDOW_TYPE");
+ ps->atoms_wintypes[WINTYPE_UNKNOWN] = 0;
+ ps->atoms_wintypes[WINTYPE_DESKTOP] = get_atom(ps,
+ "_NET_WM_WINDOW_TYPE_DESKTOP");
+ ps->atoms_wintypes[WINTYPE_DOCK] = get_atom(ps,
+ "_NET_WM_WINDOW_TYPE_DOCK");
+ ps->atoms_wintypes[WINTYPE_TOOLBAR] = get_atom(ps,
+ "_NET_WM_WINDOW_TYPE_TOOLBAR");
+ ps->atoms_wintypes[WINTYPE_MENU] = get_atom(ps,
+ "_NET_WM_WINDOW_TYPE_MENU");
+ ps->atoms_wintypes[WINTYPE_UTILITY] = get_atom(ps,
+ "_NET_WM_WINDOW_TYPE_UTILITY");
+ ps->atoms_wintypes[WINTYPE_SPLASH] = get_atom(ps,
+ "_NET_WM_WINDOW_TYPE_SPLASH");
+ ps->atoms_wintypes[WINTYPE_DIALOG] = get_atom(ps,
+ "_NET_WM_WINDOW_TYPE_DIALOG");
+ ps->atoms_wintypes[WINTYPE_NORMAL] = get_atom(ps,
+ "_NET_WM_WINDOW_TYPE_NORMAL");
+ ps->atoms_wintypes[WINTYPE_DROPDOWN_MENU] = get_atom(ps,
+ "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU");
+ ps->atoms_wintypes[WINTYPE_POPUP_MENU] = get_atom(ps,
+ "_NET_WM_WINDOW_TYPE_POPUP_MENU");
+ ps->atoms_wintypes[WINTYPE_TOOLTIP] = get_atom(ps,
+ "_NET_WM_WINDOW_TYPE_TOOLTIP");
+ ps->atoms_wintypes[WINTYPE_NOTIFY] = get_atom(ps,
+ "_NET_WM_WINDOW_TYPE_NOTIFICATION");
+ ps->atoms_wintypes[WINTYPE_COMBO] = get_atom(ps,
+ "_NET_WM_WINDOW_TYPE_COMBO");
+ ps->atoms_wintypes[WINTYPE_DND] = get_atom(ps,
+ "_NET_WM_WINDOW_TYPE_DND");
+
+ ps->atom_win_type_tde_transparent_to_black = get_atom(ps, "_TDE_TRANSPARENT_TO_BLACK");
+ ps->atom_win_type_tde_transparent_to_desktop = get_atom(ps, "_TDE_TRANSPARENT_TO_DESKTOP");
+}
+
+/**
+ * Update refresh rate info with X Randr extension.
+ */
+static void
+update_refresh_rate(session_t *ps) {
+ XRRScreenConfiguration* randr_info;
+
+ if (!(randr_info = XRRGetScreenInfo(ps->dpy, ps->root)))
+ return;
+ ps->refresh_rate = XRRConfigCurrentRate(randr_info);
+
+ XRRFreeScreenConfigInfo(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) {
+ // 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 void
+swopti_handle_timeout(session_t *ps, struct timeval *ptv) {
+ if (!ptv)
+ return;
+
+ // 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 = (ptv->tv_usec + get_time_timeval().tv_usec - ps->paint_tm_offset) % ps->refresh_intv;
+ if (offset < 0)
+ offset += ps->refresh_intv;
+
+ assert(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;
+
+ // Add an offset so we wait until the next refresh after timeout
+ ptv->tv_usec += ps->refresh_intv - offset;
+ if (ptv->tv_usec > US_PER_SEC) {
+ ptv->tv_usec -= US_PER_SEC;
+ ++ptv->tv_sec;
+ }
+}
+
+/**
+ * Initialize DRM VSync.
+ *
+ * @return true for success, false otherwise
+ */
+static bool
+vsync_drm_init(session_t *ps) {
+#ifdef CONFIG_VSYNC_DRM
+ // Should we always open card0?
+ if (ps->drm_fd < 0 && (ps->drm_fd = open("/dev/dri/card0", O_RDWR)) < 0) {
+ printf_errf("(): Failed to open device.");
+ return false;
+ }
+
+ if (vsync_drm_wait(ps))
+ return false;
+
+ return true;
+#else
+ printf_errf("(): Program not compiled with DRM VSync support.");
+ return false;
+#endif
+}
+
+#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 &= ~_DRM_VBLANK_RELATIVE;
+ } while (ret && errno == EINTR);
+
+ if (ret)
+ fprintf(stderr, "vsync_drm_wait(): VBlank ioctl did not work, "
+ "unimplemented in this drmver?\n");
+
+ return ret;
+
+}
+#endif
+
+/**
+ * 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) {
+#ifdef CONFIG_VSYNC_OPENGL
+ if (!ensure_glx_context(ps))
+ return false;
+
+ // Get video sync functions
+ if (!ps->glXGetVideoSyncSGI)
+ ps->glXGetVideoSyncSGI = (f_GetVideoSync)
+ glXGetProcAddress((const GLubyte *) "glXGetVideoSyncSGI");
+ if (!ps->glXWaitVideoSyncSGI)
+ ps->glXWaitVideoSyncSGI = (f_WaitVideoSync)
+ glXGetProcAddress((const GLubyte *) "glXWaitVideoSyncSGI");
+ if (!ps->glXWaitVideoSyncSGI || !ps->glXGetVideoSyncSGI) {
+ printf_errf("(): Failed to get glXWait/GetVideoSyncSGI function.");
+ return false;
+ }
+
+ return true;
+#else
+ printf_errf("(): Program not compiled with OpenGL VSync support.");
+ return false;
+#endif
+}
+
+static bool
+vsync_opengl_oml_init(session_t *ps) {
+#ifdef CONFIG_VSYNC_OPENGL
+ if (!ensure_glx_context(ps))
+ return false;
+
+ // Get video sync functions
+ if (!ps->glXGetSyncValuesOML)
+ ps->glXGetSyncValuesOML = (f_GetSyncValuesOML)
+ glXGetProcAddress ((const GLubyte *) "glXGetSyncValuesOML");
+ if (!ps->glXWaitForMscOML)
+ ps->glXWaitForMscOML = (f_WaitForMscOML)
+ glXGetProcAddress ((const GLubyte *) "glXWaitForMscOML");
+ if (!ps->glXGetSyncValuesOML || !ps->glXWaitForMscOML) {
+ printf_errf("(): Failed to get OML_sync_control functions.");
+ return false;
+ }
+
+ return true;
+#else
+ printf_errf("(): Program not compiled with OpenGL VSync support.");
+ return false;
+#endif
+}
+
+static bool
+vsync_opengl_swc_init(session_t *ps) {
+#ifdef CONFIG_VSYNC_OPENGL
+ if (!ensure_glx_context(ps))
+ return false;
+
+ if (!bkend_use_glx(ps)) {
+ printf_errf("(): I'm afraid glXSwapIntervalSGI wouldn't help if you are "
+ "not using GLX backend. You could try, nonetheless.");
+ }
+
+ // Get video sync functions
+ if (!ps->glXSwapIntervalProc)
+ ps->glXSwapIntervalProc = (f_SwapIntervalSGI)
+ glXGetProcAddress ((const GLubyte *) "glXSwapIntervalSGI");
+ if (!ps->glXSwapIntervalProc) {
+ printf_errf("(): Failed to get SGI_swap_control function.");
+ return false;
+ }
+ ps->glXSwapIntervalProc(1);
+
+ return true;
+#else
+ printf_errf("(): Program not compiled with OpenGL VSync support.");
+ return false;
+#endif
+}
+
+static bool
+vsync_opengl_mswc_init(session_t *ps) {
+#ifdef CONFIG_VSYNC_OPENGL
+ if (!ensure_glx_context(ps))
+ return false;
+
+ if (!bkend_use_glx(ps)) {
+ printf_errf("(): I'm afraid glXSwapIntervalMESA wouldn't help if you are "
+ "not using GLX backend. You could try, nonetheless.");
+ }
+
+ // Get video sync functions
+ if (!ps->glXSwapIntervalMESAProc)
+ ps->glXSwapIntervalMESAProc = (f_SwapIntervalMESA)
+ glXGetProcAddress ((const GLubyte *) "glXSwapIntervalMESA");
+ if (!ps->glXSwapIntervalMESAProc) {
+ printf_errf("(): Failed to get MESA_swap_control function.");
+ return false;
+ }
+ ps->glXSwapIntervalMESAProc(1);
+
+ return true;
+#else
+ printf_errf("(): Program not compiled with OpenGL VSync support.");
+ return false;
+#endif
+}
+
+#ifdef CONFIG_VSYNC_OPENGL
+/**
+ * Wait for next VSync, OpenGL method.
+ */
+static int
+vsync_opengl_wait(session_t *ps) {
+ unsigned vblank_count = 0;
+
+ ps->glXGetVideoSyncSGI(&vblank_count);
+ ps->glXWaitVideoSyncSGI(2, (vblank_count + 1) % 2, &vblank_count);
+ // I see some code calling glXSwapIntervalSGI(1) afterwards, is it required?
+
+ 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;
+
+ ps->glXGetSyncValuesOML(ps->dpy, ps->reg_win, &ust, &msc, &sbc);
+ ps->glXWaitForMscOML(ps->dpy, ps->reg_win, 0, 2, (msc + 1) % 2,
+ &ust, &msc, &sbc);
+
+ return 0;
+}
+
+static void
+vsync_opengl_swc_deinit(session_t *ps) {
+ // The standard says it doesn't accept 0, but in fact it probably does
+ if (ps->glx_context && ps->glXSwapIntervalProc)
+ ps->glXSwapIntervalProc(0);
+}
+
+static void
+vsync_opengl_mswc_deinit(session_t *ps) {
+ if (ps->glx_context && ps->glXSwapIntervalMESAProc)
+ ps->glXSwapIntervalMESAProc(0);
+}
+#endif
+
+/**
+ * Initialize current VSync method.
+ */
+bool
+vsync_init(session_t *ps) {
+ if (ps->o.vsync && VSYNC_FUNCS_INIT[ps->o.vsync]
+ && !VSYNC_FUNCS_INIT[ps->o.vsync](ps)) {
+ ps->o.vsync = VSYNC_NONE;
+ return false;
+ }
+ else
+ return true;
+}
+
+/**
+ * Wait for next VSync.
+ */
+static void
+vsync_wait(session_t *ps) {
+ if (!ps->o.vsync)
+ return;
+
+ if (VSYNC_FUNCS_WAIT[ps->o.vsync])
+ VSYNC_FUNCS_WAIT[ps->o.vsync](ps);
+}
+
+/**
+ * Deinitialize current VSync method.
+ */
+void
+vsync_deinit(session_t *ps) {
+ if (ps->o.vsync && VSYNC_FUNCS_DEINIT[ps->o.vsync])
+ VSYNC_FUNCS_DEINIT[ps->o.vsync](ps);
+ ps->o.vsync = VSYNC_NONE;
+}
+
+/**
+ * Pregenerate alpha pictures.
+ */
+static void
+init_alpha_picts(session_t *ps) {
+ int i;
+ int num = round(1.0 / ps->o.alpha_step) + 1;
+
+ ps->alpha_picts = malloc(sizeof(Picture) * num);
+
+ for (i = 0; i < num; ++i) {
+ double o = i * ps->o.alpha_step;
+ if ((1.0 - o) > ps->o.alpha_step)
+ ps->alpha_picts[i] = solid_picture(ps, false, o, 0, 0, 0);
+ else
+ ps->alpha_picts[i] = None;
+ }
+}
+
+/**
+ * Initialize double buffer.
+ */
+static bool
+init_dbe(session_t *ps) {
+ if (!(ps->root_dbe = XdbeAllocateBackBufferName(ps->dpy,
+ (ps->o.paint_on_overlay ? ps->overlay: ps->root), XdbeCopied))) {
+ printf_errf("(): Failed to create double buffer. Double buffering "
+ "cannot work.");
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Initialize X composite overlay window.
+ */
+static void
+init_overlay(session_t *ps) {
+ ps->overlay = XCompositeGetOverlayWindow(ps->dpy, ps->root);
+ if (ps->overlay) {
+ // Set window region of the overlay window, code stolen from
+ // compiz-0.8.8
+ XserverRegion region = XFixesCreateRegion(ps->dpy, NULL, 0);
+ XFixesSetWindowShapeRegion(ps->dpy, ps->overlay, ShapeBounding, 0, 0, 0);
+ XFixesSetWindowShapeRegion(ps->dpy, ps->overlay, ShapeInput, 0, 0, region);
+ XFixesDestroyRegion(ps->dpy, region);
+
+ // Listen to Expose events on the overlay
+ XSelectInput(ps->dpy, ps->overlay, ExposureMask);
+
+ // Retrieve DamageNotify on root window if we are painting on an
+ // overlay
+ // root_damage = XDamageCreate(ps->dpy, root, XDamageReportNonEmpty);
+
+ // Unmap overlay, firstly. But this typically does not work because
+ // the window isn't created yet.
+ // XUnmapWindow(ps->dpy, ps->overlay);
+ // XFlush(ps->dpy);
+ }
+ else {
+ fprintf(stderr, "Cannot get X Composite overlay window. Falling "
+ "back to painting on root window.\n");
+ ps->o.paint_on_overlay = false;
+ }
+}
+
+/**
+ * Query needed X Render / OpenGL filters to check for their existence.
+ */
+static bool
+init_filters(session_t *ps) {
+ // Blur filter
+ if (ps->o.blur_background || ps->o.blur_background_frame) {
+ switch (ps->o.backend) {
+ case BKEND_XRENDER:
+ case BKEND_XR_GLX_HYBRID:
+ {
+ // Query filters
+ XFilters *pf = XRenderQueryFilters(ps->dpy, get_tgt_window(ps));
+ if (pf) {
+ for (int i = 0; i < pf->nfilter; ++i) {
+ // Convolution filter
+ if (!strcmp(pf->filter[i], XRFILTER_CONVOLUTION))
+ ps->xrfilter_convolution_exists = true;
+ }
+ }
+ cxfree(pf);
+
+ // Turn features off if any required filter is not present
+ if (!ps->xrfilter_convolution_exists) {
+ printf_errf("(): X Render convolution filter unsupported by your X server. Background blur is not possible.");
+ return false;
+ }
+ break;
+ }
+#ifdef CONFIG_VSYNC_OPENGL
+ case BKEND_GLX:
+ {
+ if (!glx_init_blur(ps))
+ return false;
+ }
+#endif
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Redirect all windows.
+ */
+static void
+redir_start(session_t *ps) {
+ if (!ps->redirected) {
+#ifdef DEBUG_REDIR
+ print_timestamp(ps);
+ printf_dbgf("(): Screen redirected.\n");
+#endif
+
+ // Map overlay window. Done firstly according to this:
+ // https://bugzilla.gnome.org/show_bug.cgi?id=597014
+ if (ps->overlay)
+ XMapWindow(ps->dpy, ps->overlay);
+
+ XCompositeRedirectSubwindows(ps->dpy, ps->root, CompositeRedirectManual);
+
+ /*
+ // Unredirect GL context window as this may have an effect on VSync:
+ // < http://dri.freedesktop.org/wiki/CompositeSwap >
+ XCompositeUnredirectWindow(ps->dpy, ps->reg_win, CompositeRedirectManual);
+ if (ps->o.paint_on_overlay && ps->overlay) {
+ XCompositeUnredirectWindow(ps->dpy, ps->overlay,
+ CompositeRedirectManual);
+ } */
+
+ // Must call XSync() here
+ XSync(ps->dpy, False);
+
+ ps->redirected = true;
+
+ // Repaint the whole screen
+ force_repaint(ps);
+ }
+}
+
+/**
+ * Get the poll time.
+ */
+static time_ms_t
+timeout_get_poll_time(session_t *ps) {
+ const time_ms_t now = get_time_ms();
+ time_ms_t wait = TIME_MS_MAX;
+
+ // Traverse throught the timeout linked list
+ for (timeout_t *ptmout = ps->tmout_lst; ptmout; ptmout = ptmout->next) {
+ if (ptmout->enabled) {
+ time_ms_t newrun = timeout_get_newrun(ptmout);
+ if (newrun <= now) {
+ wait = 0;
+ break;
+ }
+ else {
+ time_ms_t newwait = newrun - now;
+ if (newwait < wait)
+ wait = newwait;
+ }
+ }
+ }
+
+ return wait;
+}
+
+/**
+ * Insert a new timeout.
+ */
+timeout_t *
+timeout_insert(session_t *ps, time_ms_t interval,
+ bool (*callback)(session_t *ps, timeout_t *ptmout), void *data) {
+ const static timeout_t tmout_def = {
+ .enabled = true,
+ .data = NULL,
+ .callback = NULL,
+ .firstrun = 0L,
+ .lastrun = 0L,
+ .interval = 0L,
+ };
+
+ const time_ms_t now = get_time_ms();
+ timeout_t *ptmout = malloc(sizeof(timeout_t));
+ if (!ptmout)
+ printf_errfq(1, "(): Failed to allocate memory for timeout.");
+ memcpy(ptmout, &tmout_def, sizeof(timeout_t));
+
+ ptmout->interval = interval;
+ ptmout->firstrun = now;
+ ptmout->lastrun = now;
+ ptmout->data = data;
+ ptmout->callback = callback;
+ ptmout->next = ps->tmout_lst;
+ ps->tmout_lst = ptmout;
+
+ return ptmout;
+}
+
+/**
+ * Drop a timeout.
+ *
+ * @return true if we have found the timeout and removed it, false
+ * otherwise
+ */
+bool
+timeout_drop(session_t *ps, timeout_t *prm) {
+ timeout_t **pplast = &ps->tmout_lst;
+
+ for (timeout_t *ptmout = ps->tmout_lst; ptmout;
+ pplast = &ptmout->next, ptmout = ptmout->next) {
+ if (prm == ptmout) {
+ *pplast = ptmout->next;
+ free(ptmout);
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Clear all timeouts.
+ */
+static void
+timeout_clear(session_t *ps) {
+ timeout_t *ptmout = ps->tmout_lst;
+ timeout_t *next = NULL;
+ while (ptmout) {
+ next = ptmout->next;
+ free(ptmout);
+ ptmout = next;
+ }
+}
+
+/**
+ * Run timeouts.
+ *
+ * @return true if we have ran a timeout, false otherwise
+ */
+static bool
+timeout_run(session_t *ps) {
+ const time_ms_t now = get_time_ms();
+ bool ret = false;
+ timeout_t *pnext = NULL;
+
+ for (timeout_t *ptmout = ps->tmout_lst; ptmout; ptmout = pnext) {
+ pnext = ptmout->next;
+ if (ptmout->enabled) {
+ const time_ms_t max = now +
+ (time_ms_t) (ptmout->interval * TIMEOUT_RUN_TOLERANCE);
+ time_ms_t newrun = timeout_get_newrun(ptmout);
+ if (newrun <= max) {
+ ret = true;
+ timeout_invoke(ps, ptmout);
+ }
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * Invoke a timeout.
+ */
+void
+timeout_invoke(session_t *ps, timeout_t *ptmout) {
+ const time_ms_t now = get_time_ms();
+ ptmout->lastrun = now;
+ // Avoid modifying the timeout structure after running timeout, to
+ // make it possible to remove timeout in callback
+ if (ptmout->callback)
+ ptmout->callback(ps, ptmout);
+}
+
+/**
+ * Reset a timeout to initial state.
+ */
+void
+timeout_reset(session_t *ps, timeout_t *ptmout) {
+ ptmout->firstrun = ptmout->lastrun = get_time_ms();
+}
+
+/**
+ * Unredirect all windows.
+ */
+static void
+redir_stop(session_t *ps) {
+ if (ps->redirected) {
+#ifdef DEBUG_REDIR
+ print_timestamp(ps);
+ printf_dbgf("(): Screen unredirected.\n");
+#endif
+ // Destroy all Pictures as they expire once windows are unredirected
+ // If we don't destroy them here, looks like the resources are just
+ // kept inaccessible somehow
+ for (win *w = ps->list; w; w = w->next)
+ free_wpaint(ps, w);
+
+ XCompositeUnredirectSubwindows(ps->dpy, ps->root, CompositeRedirectManual);
+ // Unmap overlay window
+ if (ps->overlay)
+ XUnmapWindow(ps->dpy, ps->overlay);
+
+ // Must call XSync() here
+ XSync(ps->dpy, False);
+
+ ps->redirected = false;
+ }
+}
+
+/**
+ * Unredirection timeout callback.
+ */
+static bool
+tmout_unredir_callback(session_t *ps, timeout_t *tmout) {
+ ps->tmout_unredir_hit = true;
+ tmout->enabled = false;
+
+ return true;
+}
+
+/**
+ * Main loop.
+ */
+static bool
+mainloop(session_t *ps) {
+ // Don't miss timeouts even when we have a LOT of other events!
+ timeout_run(ps);
+
+ // Process existing events
+ // Sometimes poll() returns 1 but no events are actually read,
+ // causing XNextEvent() to block, I have no idea what's wrong, so we
+ // check for the number of events here.
+ if (XEventsQueued(ps->dpy, QueuedAfterReading)) {
+ XEvent ev = { };
+
+ XNextEvent(ps->dpy, &ev);
+ ev_handle(ps, &ev);
+ ps->ev_received = true;
+
+ return true;
+ }
+
+#ifdef CONFIG_DBUS
+ if (ps->o.dbus) {
+ cdbus_loop(ps);
+ }
+#endif
+
+ if (ps->reset)
+ return false;
+
+ // Calculate timeout
+ struct timeval *ptv = NULL;
+ {
+ // Consider ev_received firstly
+ if (ps->ev_received || ps->o.benchmark) {
+ ptv = malloc(sizeof(struct timeval));
+ ptv->tv_sec = 0L;
+ ptv->tv_usec = 0L;
+ }
+ // Then consider fading timeout
+ else if (!ps->idling) {
+ ptv = malloc(sizeof(struct timeval));
+ *ptv = ms_to_tv(fade_timeout(ps));
+ }
+
+ // Software optimization is to be applied on timeouts that require
+ // immediate painting only
+ if (ptv && ps->o.sw_opti)
+ swopti_handle_timeout(ps, ptv);
+
+ // Don't continue looping for 0 timeout
+ if (ptv && timeval_isempty(ptv)) {
+ free(ptv);
+ return false;
+ }
+
+ // Now consider the waiting time of other timeouts
+ time_ms_t tmout_ms = timeout_get_poll_time(ps);
+ if (tmout_ms < TIME_MS_MAX) {
+ if (!ptv) {
+ ptv = malloc(sizeof(struct timeval));
+ *ptv = ms_to_tv(tmout_ms);
+ }
+ else if (timeval_ms_cmp(ptv, tmout_ms) > 0) {
+ *ptv = ms_to_tv(tmout_ms);
+ }
+ }
+
+ // Don't continue looping for 0 timeout
+ if (ptv && timeval_isempty(ptv)) {
+ free(ptv);
+ return false;
+ }
+ }
+
+ // Polling
+ fds_poll(ps, ptv);
+ free(ptv);
+ ptv = NULL;
+
+ return true;
+}
+
+static void
+cxinerama_upd_scrs(session_t *ps) {
+#ifdef CONFIG_XINERAMA
+ free_xinerama_info(ps);
+
+ if (!ps->o.xinerama_shadow_crop || !ps->xinerama_exists) return;
+
+ if (!XineramaIsActive(ps->dpy)) return;
+
+ ps->xinerama_scrs = XineramaQueryScreens(ps->dpy, &ps->xinerama_nscrs);
+
+ // Just in case the shit hits the fan...
+ if (!ps->xinerama_nscrs) {
+ cxfree(ps->xinerama_scrs);
+ ps->xinerama_scrs = NULL;
+ return;
+ }
+
+ ps->xinerama_scr_regs = allocchk(malloc(sizeof(XserverRegion *)
+ * ps->xinerama_nscrs));
+ for (int i = 0; i < ps->xinerama_nscrs; ++i) {
+ const XineramaScreenInfo * const s = &ps->xinerama_scrs[i];
+ XRectangle r = { .x = s->x_org, .y = s->y_org,
+ .width = s->width, .height = s->height };
+ ps->xinerama_scr_regs[i] = XFixesCreateRegion(ps->dpy, &r, 1);
+ }
+#endif
+}
+
+/**
+ * Initialize a session.
+ *
+ * @param ps_old old session, from which the function will take the X
+ * connection, then free it
+ * @param argc number of commandline arguments
+ * @param argv commandline arguments
+ */
+static session_t *
+session_init(session_t *ps_old, int argc, char **argv) {
+ const static session_t s_def = {
+ .dpy = NULL,
+ .scr = 0,
+ .vis = NULL,
+ .depth = 0,
+ .root = None,
+ .root_height = 0,
+ .root_width = 0,
+ // .root_damage = None,
+ .overlay = None,
+ .root_tile_fill = false,
+ .root_tile_paint = PAINT_INIT,
+ .screen_reg = None,
+ .tgt_picture = None,
+ .tgt_buffer = PAINT_INIT,
+ .root_dbe = None,
+ .reg_win = None,
+ .o = {
+ .config_file = NULL,
+ .display = NULL,
+ .backend = BKEND_XRENDER,
+ .glx_no_stencil = false,
+ .glx_copy_from_front = false,
+ .mark_wmwin_focused = false,
+ .mark_ovredir_focused = false,
+ .fork_after_register = false,
+ .synchronize = false,
+ .detect_rounded_corners = false,
+ .paint_on_overlay = 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 = None,
+ .logpath = NULL,
+
+ .refresh_rate = 0,
+ .sw_opti = false,
+ .vsync = VSYNC_NONE,
+ .dbe = false,
+ .vsync_aggressive = false,
+
+ .wintype_shadow = { false },
+ .shadow_red = 0.0,
+ .shadow_green = 0.0,
+ .shadow_blue = 0.0,
+ .shadow_radius = 12,
+ .shadow_offset_x = -15,
+ .shadow_offset_y = -15,
+ .shadow_opacity = .75,
+ .clear_shadow = false,
+ .shadow_blacklist = NULL,
+ .shadow_ignore_shaped = false,
+ .respect_prop_shadow = false,
+ .xinerama_shadow_crop = false,
+
+ .wintype_fade = { false },
+ .fade_in_step = 0.028 * OPAQUE,
+ .fade_out_step = 0.03 * OPAQUE,
+ .fade_delta = 10,
+ .no_fading_openclose = false,
+ .no_fading_opacitychange = false,
+ .fade_blacklist = NULL,
+
+ .wintype_opacity = { 0.0 },
+ .inactive_opacity = 0,
+ .inactive_opacity_override = false,
+ .active_opacity = 0,
+ .frame_opacity = 0.0,
+ .detect_client_opacity = false,
+ .alpha_step = 0.03,
+
+ .blur_background = false,
+ .blur_background_frame = false,
+ .blur_background_fixed = false,
+ .blur_background_blacklist = NULL,
+ .blur_kerns = { NULL },
+ .inactive_dim = 0.0,
+ .inactive_dim_fixed = false,
+ .invert_color_list = NULL,
+ .opacity_rules = NULL,
+
+ .wintype_focus = { false },
+ .use_ewmh_active_win = false,
+ .focus_blacklist = NULL,
+ .detect_transient = false,
+ .detect_client_leader = false,
+
+ .track_focus = false,
+ .track_wdata = false,
+ .track_leader = false,
+ },
+
+ .pfds_read = NULL,
+ .pfds_write = NULL,
+ .pfds_except = NULL,
+ .nfds_max = 0,
+ .tmout_lst = NULL,
+
+ .all_damage = None,
+ .all_damage_last = { None },
+ .time_start = { 0, 0 },
+ .redirected = false,
+ .alpha_picts = NULL,
+ .reg_ignore_expire = false,
+ .idling = false,
+ .fade_time = 0L,
+ .ignore_head = NULL,
+ .ignore_tail = NULL,
+ .reset = false,
+
+ .expose_rects = NULL,
+ .size_expose = 0,
+ .n_expose = 0,
+
+ .list = NULL,
+ .active_win = NULL,
+ .active_leader = None,
+
+ .black_picture = None,
+ .cshadow_picture = None,
+ .white_picture = None,
+ .gaussian_map = NULL,
+ .cgsize = 0,
+ .shadow_corner = NULL,
+ .shadow_top = NULL,
+
+ .refresh_rate = 0,
+ .refresh_intv = 0UL,
+ .paint_tm_offset = 0L,
+
+#ifdef CONFIG_VSYNC_DRM
+ .drm_fd = -1,
+#endif
+
+#ifdef CONFIG_VSYNC_OPENGL
+ .glx_context = None,
+ .glx_has_texture_non_power_of_two = false,
+ .glXGetVideoSyncSGI = NULL,
+ .glXWaitVideoSyncSGI = NULL,
+ .glXGetSyncValuesOML = NULL,
+ .glXWaitForMscOML = NULL,
+#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,
+ .has_name_pixmap = false,
+ .shape_exists = false,
+ .shape_event = 0,
+ .shape_error = 0,
+ .randr_exists = 0,
+ .randr_event = 0,
+ .randr_error = 0,
+#ifdef CONFIG_VSYNC_OPENGL
+ .glx_exists = false,
+ .glx_event = 0,
+ .glx_error = 0,
+#endif
+ .dbe_exists = false,
+ .xrfilter_convolution_exists = false,
+
+ .atom_opacity = None,
+ .atom_frame_extents = None,
+ .atom_client = None,
+ .atom_name = None,
+ .atom_name_ewmh = None,
+ .atom_class = None,
+ .atom_role = None,
+ .atom_transient = None,
+ .atom_ewmh_active_win = None,
+ .atom_compton_shadow = None,
+ .atom_win_type = None,
+ .atom_win_type_tde_transparent_to_black = None,
+ .atom_win_type_tde_transparent_to_desktop = None,
+ .atoms_wintypes = { 0 },
+ .track_atom_lst = NULL,
+
+#ifdef CONFIG_DBUS
+ .dbus_conn = NULL,
+ .dbus_service = NULL,
+#endif
+ };
+
+ // Allocate a session and copy default values into it
+ session_t *ps = malloc(sizeof(session_t));
+ memcpy(ps, &s_def, sizeof(session_t));
+#ifdef CONFIG_VSYNC_OPENGL_GLSL
+ for (int i = 0; i < MAX_BLUR_PASS; ++i) {
+ glx_blur_pass_t *ppass = &ps->glx_blur_passes[i];
+ ppass->unifm_factor_center = -1;
+ ppass->unifm_offset_x = -1;
+ ppass->unifm_offset_y = -1;
+ }
+#endif
+ ps_g = ps;
+ ps->ignore_tail = &ps->ignore_head;
+ gettimeofday(&ps->time_start, NULL);
+
+ wintype_arr_enable(ps->o.wintype_focus);
+ ps->o.wintype_focus[WINTYPE_UNKNOWN] = false;
+ ps->o.wintype_focus[WINTYPE_NORMAL] = false;
+ ps->o.wintype_focus[WINTYPE_UTILITY] = false;
+
+ // First pass
+ get_cfg(ps, argc, argv, true);
+
+ // Inherit old Display if possible, primarily for resource leak checking
+ if (ps_old && ps_old->dpy)
+ ps->dpy = ps_old->dpy;
+
+ // Open Display
+ if (!ps->dpy) {
+ ps->dpy = XOpenDisplay(ps->o.display);
+ if (!ps->dpy) {
+ printf_errfq(1, "(): Can't open display.");
+ }
+ }
+
+ XSetErrorHandler(xerror);
+ if (ps->o.synchronize) {
+ XSynchronize(ps->dpy, 1);
+ }
+
+ ps->scr = DefaultScreen(ps->dpy);
+ ps->root = RootWindow(ps->dpy, ps->scr);
+
+ ps->vis = DefaultVisual(ps->dpy, ps->scr);
+ ps->depth = DefaultDepth(ps->dpy, ps->scr);
+
+ // Start listening to events on root earlier to catch all possible
+ // root geometry changes
+ XSelectInput(ps->dpy, ps->root,
+ SubstructureNotifyMask
+ | ExposureMask
+ | StructureNotifyMask
+ | PropertyChangeMask);
+ XFlush(ps->dpy);
+
+ ps->root_width = DisplayWidth(ps->dpy, ps->scr);
+ ps->root_height = DisplayHeight(ps->dpy, ps->scr);
+
+ if (!XRenderQueryExtension(ps->dpy,
+ &ps->render_event, &ps->render_error)) {
+ fprintf(stderr, "No render extension\n");
+ exit(1);
+ }
+
+ if (!XQueryExtension(ps->dpy, COMPOSITE_NAME, &ps->composite_opcode,
+ &ps->composite_event, &ps->composite_error)) {
+ fprintf(stderr, "No composite extension\n");
+ exit(1);
+ }
+
+ {
+ int composite_major = 0, composite_minor = 0;
+
+ XCompositeQueryVersion(ps->dpy, &composite_major, &composite_minor);
+
+ if (composite_major > 0 || composite_minor >= 2) {
+ ps->has_name_pixmap = true;
+ }
+ }
+
+ if (!XDamageQueryExtension(ps->dpy, &ps->damage_event, &ps->damage_error)) {
+ fprintf(stderr, "No damage extension\n");
+ exit(1);
+ }
+
+ if (!XFixesQueryExtension(ps->dpy, &ps->xfixes_event, &ps->xfixes_error)) {
+ fprintf(stderr, "No XFixes extension\n");
+ exit(1);
+ }
+
+ // Build a safe representation of display name
+ {
+ char *display_repr = DisplayString(ps->dpy);
+ if (!display_repr)
+ display_repr = "unknown";
+ display_repr = mstrcpy(display_repr);
+
+ // Convert all special characters in display_repr name to underscore
+ {
+ char *pdisp = display_repr;
+
+ while (*pdisp) {
+ if (!isalnum(*pdisp))
+ *pdisp = '_';
+ ++pdisp;
+ }
+ }
+
+ ps->o.display_repr = display_repr;
+ }
+
+ // Second pass
+ get_cfg(ps, argc, argv, false);
+
+ // Query X Shape
+ if (XShapeQueryExtension(ps->dpy, &ps->shape_event, &ps->shape_error)) {
+ ps->shape_exists = true;
+ }
+
+ if (ps->o.xrender_sync_fence) {
+#ifdef CONFIG_XSYNC
+ // Query X Sync
+ if (XSyncQueryExtension(ps->dpy, &ps->xsync_event, &ps->xsync_error)) {
+ // TODO: Fencing may require version >= 3.0?
+ int major_version_return = 0, minor_version_return = 0;
+ if (XSyncInitialize(ps->dpy, &major_version_return, &minor_version_return))
+ ps->xsync_exists = true;
+ }
+ if (!ps->xsync_exists) {
+ printf_errf("(): X Sync extension not found. No X Sync fence sync is "
+ "possible.");
+ exit(1);
+ }
+#else
+ printf_errf("(): X Sync support not compiled in. --xrender-sync-fence "
+ "can't work.");
+ exit(1);
+#endif
+ }
+
+ // Query X RandR
+ if ((ps->o.sw_opti && !ps->o.refresh_rate) || ps->o.xinerama_shadow_crop) {
+ if (XRRQueryExtension(ps->dpy, &ps->randr_event, &ps->randr_error))
+ ps->randr_exists = true;
+ else
+ printf_errf("(): No XRandR extension, automatic screen change "
+ "detection impossible.");
+ }
+
+ // Query X DBE extension
+ if (ps->o.dbe) {
+ int dbe_ver_major = 0, dbe_ver_minor = 0;
+ if (XdbeQueryExtension(ps->dpy, &dbe_ver_major, &dbe_ver_minor))
+ if (dbe_ver_major >= 1)
+ ps->dbe_exists = true;
+ else
+ fprintf(stderr, "DBE extension version too low. Double buffering "
+ "impossible.\n");
+ else {
+ fprintf(stderr, "No DBE extension. Double buffering impossible.\n");
+ }
+ if (!ps->dbe_exists)
+ ps->o.dbe = false;
+ }
+
+ // Query X Xinerama extension
+ if (ps->o.xinerama_shadow_crop) {
+#ifdef CONFIG_XINERAMA
+ int xinerama_event = 0, xinerama_error = 0;
+ if (XineramaQueryExtension(ps->dpy, &xinerama_event, &xinerama_error))
+ ps->xinerama_exists = true;
+#else
+ printf_errf("(): Xinerama support not compiled in.");
+#endif
+ }
+
+ rebuild_screen_reg(ps);
+
+ // Overlay must be initialized before double buffer, and before creation
+ // of OpenGL context.
+ if (ps->o.paint_on_overlay)
+ init_overlay(ps);
+
+ // Initialize DBE
+ if (ps->o.dbe && BKEND_XRENDER != ps->o.backend) {
+ printf_errf("(): DBE couldn't be used on GLX backend.");
+ ps->o.dbe = false;
+ }
+
+ if (ps->o.dbe && !init_dbe(ps))
+ exit(1);
+
+ // Initialize OpenGL as early as possible
+ if (bkend_use_glx(ps)) {
+#ifdef CONFIG_VSYNC_OPENGL
+ if (!glx_init(ps, true))
+ exit(1);
+#else
+ printf_errfq(1, "(): GLX backend support not compiled in.");
+#endif
+ }
+
+ // 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))
+ XRRSelectInput(ps->dpy, ps->root, RRScreenChangeNotifyMask);
+
+ // Initialize VSync
+ if (!vsync_init(ps))
+ exit(1);
+
+ cxinerama_upd_scrs(ps);
+
+ fprintf(stderr, "Started\n");
+
+ // Create registration window
+ if (!ps->reg_win && !register_cm(ps))
+ exit(1);
+
+ init_atoms(ps);
+ init_alpha_picts(ps);
+
+ ps->gaussian_map = make_gaussian_map(ps->o.shadow_radius);
+ presum_gaussian(ps, ps->gaussian_map);
+
+ {
+ XRenderPictureAttributes pa;
+ pa.subwindow_mode = IncludeInferiors;
+
+ ps->root_picture = XRenderCreatePicture(ps->dpy, ps->root,
+ XRenderFindVisualFormat(ps->dpy, ps->vis),
+ CPSubwindowMode, &pa);
+ if (ps->o.paint_on_overlay) {
+ ps->tgt_picture = XRenderCreatePicture(ps->dpy, ps->overlay,
+ XRenderFindVisualFormat(ps->dpy, ps->vis),
+ CPSubwindowMode, &pa);
+ }
+ else {
+ ps->tgt_picture = ps->root_picture;
+ }
+ }
+
+ // Initialize filters, must be preceded by OpenGL context creation
+ if (!init_filters(ps))
+ exit(1);
+
+ ps->black_picture = solid_picture(ps, true, 1, 0, 0, 0);
+ ps->white_picture = solid_picture(ps, true, 1, 1, 1, 1);
+
+ // Generates another Picture for shadows if the color is modified by
+ // user
+ if (!ps->o.shadow_red && !ps->o.shadow_green && !ps->o.shadow_blue) {
+ ps->cshadow_picture = ps->black_picture;
+ } else {
+ ps->cshadow_picture = solid_picture(ps, true, 1,
+ ps->o.shadow_red, ps->o.shadow_green, ps->o.shadow_blue);
+ }
+
+ fds_insert(ps, ConnectionNumber(ps->dpy), POLLIN);
+ ps->tmout_unredir = timeout_insert(ps, ps->o.unredir_if_possible_delay,
+ tmout_unredir_callback, NULL);
+ ps->tmout_unredir->enabled = false;
+
+ XGrabServer(ps->dpy);
+
+ {
+ Window root_return, parent_return;
+ Window *children;
+ unsigned int nchildren;
+
+ XQueryTree(ps->dpy, ps->root, &root_return,
+ &parent_return, &children, &nchildren);
+
+ for (unsigned i = 0; i < nchildren; i++) {
+ add_win(ps, children[i], i ? children[i-1] : None);
+ }
+
+ cxfree(children);
+ }
+
+
+ if (ps->o.track_focus) {
+ recheck_focus(ps);
+ }
+
+ XUngrabServer(ps->dpy);
+
+ // Initialize DBus
+ if (ps->o.dbus) {
+#ifdef CONFIG_DBUS
+ cdbus_init(ps);
+ if (!ps->dbus_conn) {
+ cdbus_destroy(ps);
+ ps->o.dbus = false;
+ }
+#else
+ printf_errfq(1, "(): DBus support not compiled in!");
+#endif
+ }
+
+ // Fork to background, if asked
+ if (ps->o.fork_after_register) {
+ if (!fork_after(ps)) {
+ session_destroy(ps);
+ return NULL;
+ }
+ }
+
+ write_pid(ps);
+
+ // Free the old session
+ if (ps_old)
+ free(ps_old);
+
+ return ps;
+}
+
+/**
+ * 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) {
+ redir_stop(ps);
+
+ // Stop listening to events on root window
+ XSelectInput(ps->dpy, ps->root, 0);
+
+#ifdef CONFIG_DBUS
+ // Kill DBus connection
+ if (ps->o.dbus)
+ cdbus_destroy(ps);
+
+ free(ps->dbus_service);
+#endif
+
+ // Free window linked list
+ {
+ win *next = NULL;
+ for (win *w = ps->list; w; w = next) {
+ // Must be put here to avoid segfault
+ next = w->next;
+
+ if (IsViewable == w->a.map_state && !w->destroyed)
+ win_ev_stop(ps, w);
+
+ free_win_res(ps, w);
+ free(w);
+ }
+
+ ps->list = NULL;
+ }
+
+ // Free alpha_picts
+ {
+ const int max = round(1.0 / ps->o.alpha_step) + 1;
+ for (int i = 0; i < max; ++i)
+ free_picture(ps, &ps->alpha_picts[i]);
+ free(ps->alpha_picts);
+ ps->alpha_picts = NULL;
+ }
+
+#ifdef CONFIG_C2
+ // Free blacklists
+ free_wincondlst(&ps->o.shadow_blacklist);
+ 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);
+#endif
+
+ // 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 cshadow_picture and black_picture
+ if (ps->cshadow_picture == ps->black_picture)
+ ps->cshadow_picture = None;
+ else
+ free_picture(ps, &ps->cshadow_picture);
+
+ free_picture(ps, &ps->black_picture);
+ free_picture(ps, &ps->white_picture);
+
+ // Free tgt_{buffer,picture} and root_picture
+ if (ps->tgt_buffer.pict == ps->tgt_picture)
+ ps->tgt_buffer.pict = None;
+
+ if (ps->tgt_picture == ps->root_picture)
+ ps->tgt_picture = None;
+ else
+ free_picture(ps, &ps->tgt_picture);
+ free_fence(ps, &ps->tgt_buffer_fence);
+
+ free_picture(ps, &ps->root_picture);
+ free_paint(ps, &ps->tgt_buffer);
+
+ // Free other X resources
+ free_root_tile(ps);
+ free_region(ps, &ps->screen_reg);
+ free_region(ps, &ps->all_damage);
+ for (int i = 0; i < CGLX_MAX_BUFFER_AGE; ++i)
+ free_region(ps, &ps->all_damage_last[i]);
+ free(ps->expose_rects);
+ free(ps->shadow_corner);
+ free(ps->shadow_top);
+ free(ps->gaussian_map);
+
+ free(ps->o.config_file);
+ free(ps->o.write_pid_path);
+ free(ps->o.display);
+ free(ps->o.display_repr);
+ free(ps->o.logpath);
+ for (int i = 0; i < MAX_BLUR_PASS; ++i) {
+ free(ps->o.blur_kerns[i]);
+ free(ps->blur_kerns_cache[i]);
+ }
+ free(ps->pfds_read);
+ free(ps->pfds_write);
+ free(ps->pfds_except);
+ free_xinerama_info(ps);
+
+#ifdef CONFIG_VSYNC_OPENGL
+ glx_destroy(ps);
+#endif
+
+ // Free double buffer
+ if (ps->root_dbe) {
+ XdbeDeallocateBackBufferName(ps->dpy, ps->root_dbe);
+ ps->root_dbe = None;
+ }
+
+#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) {
+ XCompositeReleaseOverlayWindow(ps->dpy, ps->overlay);
+ ps->overlay = None;
+ }
+
+ // Free reg_win
+ if (ps->reg_win) {
+ XDestroyWindow(ps->dpy, ps->reg_win);
+ ps->reg_win = None;
+ }
+
+ // Flush all events
+ XSync(ps->dpy, True);
+
+ // Free timeouts
+ ps->tmout_unredir = NULL;
+ timeout_clear(ps);
+
+ if (ps == ps_g)
+ ps_g = NULL;
+}
+
+/**
+ * Do the actual work.
+ *
+ * @param ps current session
+ */
+static void
+session_run(session_t *ps) {
+ win *t;
+
+ if (ps->o.sw_opti)
+ ps->paint_tm_offset = get_time_timeval().tv_usec;
+
+ ps->reg_ignore_expire = true;
+
+ t = paint_preprocess(ps, ps->list);
+
+ if (ps->redirected)
+ paint_all(ps, None, None, t);
+
+ // Initialize idling
+ ps->idling = false;
+
+ // Main loop
+ while (!ps->reset) {
+ ps->ev_received = false;
+
+ while (mainloop(ps))
+ continue;
+
+ if (ps->o.benchmark) {
+ if (ps->o.benchmark_wid) {
+ win *w = find_win(ps, ps->o.benchmark_wid);
+ if (!w) {
+ printf_errf("(): Couldn't find specified benchmark window.");
+ session_destroy(ps);
+ exit(1);
+ }
+ add_damage_win(ps, w);
+ }
+ else {
+ force_repaint(ps);
+ }
+ }
+
+ // idling will be turned off during paint_preprocess() if needed
+ ps->idling = true;
+
+ t = paint_preprocess(ps, ps->list);
+ ps->tmout_unredir_hit = false;
+
+ // If the screen is unredirected, free all_damage to stop painting
+ if (!ps->redirected || ON == ps->o.stoppaint_force)
+ free_region(ps, &ps->all_damage);
+
+ XserverRegion all_damage_orig = None;
+ if (ps->o.resize_damage > 0)
+ all_damage_orig = copy_region(ps, ps->all_damage);
+ resize_region(ps, ps->all_damage, ps->o.resize_damage);
+ if (ps->all_damage && !is_region_empty(ps, ps->all_damage, NULL)) {
+ static int paint = 0;
+ paint_all(ps, ps->all_damage, all_damage_orig, t);
+ ps->reg_ignore_expire = false;
+ paint++;
+ if (ps->o.benchmark && paint >= ps->o.benchmark)
+ exit(0);
+ XSync(ps->dpy, False);
+ ps->all_damage = None;
+ }
+ free_region(ps, &all_damage_orig);
+
+ if (ps->idling)
+ ps->fade_time = 0L;
+ }
+}
+
+/**
+ * Turn on the program reset flag.
+ *
+ * This will result in compton resetting itself after next paint.
+ */
+static void
+reset_enable(int __attribute__((unused)) signum) {
+ session_t * const ps = ps_g;
+
+ ps->reset = true;
+}
+
+/**
+ * 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 signal handlers
+ sigfillset(&block_mask);
+ usr_action.sa_handler = handle_siguser;
+ usr_action.sa_mask = block_mask;
+ usr_action.sa_flags = 0;
+ sigaction(SIGUSR1, &usr_action, NULL);
+ sigaction(SIGUSR2, &usr_action, NULL);
+ sigaction(SIGTERM, &usr_action, NULL);
+
+ // Main loop
+ session_t *ps_old = ps_g;
+ while (1) {
+ ps_g = session_init(ps_old, argc, argv);
+ if (!ps_g) {
+ printf_errf("(): Failed to create new session.");
+ return 1;
+ }
+
+ /* Under no circumstances should these two lines EVER be moved earlier in main() than this point */
+ atexit(delete_pid_file);
+ write_pid_file(getpid());
+
+ session_run(ps_g);
+ ps_old = ps_g;
+ session_destroy(ps_g);
+ }
+
+ free(ps_g);
+
+ return 0;
+}