diff options
Diffstat (limited to 'twin/compton-tde/compton.c')
-rw-r--r-- | twin/compton-tde/compton.c | 7854 |
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 <[email protected]> + * 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, ®_all); + } + xr_blur_dst(ps, tgt_buffer, x, y, wid, hei, ps->blur_kerns_cache, + reg_noframe); + free_region(ps, ®_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, ®); + } + + 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, ®ion); + } +#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, ®_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; +} |