/*
 * Compton - a compositor for X11
 *
 * Based on `xcompmgr` - Copyright (c) 2003, Keith Packard
 *
 * Copyright (c) 2011, Christopher Jeffrey
 * See LICENSE for more information.
 *
 */

#include "compton.h"

/**
 * Shared
 */

const char *WINTYPES[NUM_WINTYPES] = {
  "unknown",
  "desktop",
  "dock",
  "toolbar",
  "menu",
  "utility",
  "splash",
  "dialog",
  "normal",
  "dropdown_menu",
  "popup_menu",
  "tooltip",
  "notify",
  "combo",
  "dnd",
};

struct timeval time_start = { 0, 0 };

win *list;
Display *dpy = NULL;
int scr;

/// Root window.
Window root = None;
/// Damage of root window.
Damage root_damage = None;
/// X Composite overlay window. Used if --paint-on-overlay.
Window overlay = None;

/// Picture of root window. Destination of painting in no-DBE painting
/// mode.
Picture root_picture = None;
/// A Picture acting as the painting target.
Picture tgt_picture = None;
/// Temporary buffer to paint to before sending to display.
Picture tgt_buffer = None;
/// DBE back buffer for root window. Used in DBE painting mode.
XdbeBackBuffer root_dbe = None;

Picture black_picture;
Picture cshadow_picture;
/// Picture used for dimming inactive windows.
Picture dim_picture = 0;
Picture root_tile;
XserverRegion all_damage;
Bool has_name_pixmap;
int root_height, root_width;

/// Pregenerated alpha pictures.
Picture *alpha_picts = NULL;
/// Whether the program is idling. I.e. no fading, no potential window
/// changes.
Bool idling;
/// Whether all reg_ignore of windows should expire in this paint.
Bool reg_ignore_expire = False;
/// Window ID of the window we register as a symbol.
Window reg_win = 0;

/// Currently used refresh rate. Used for sw_opti.
short refresh_rate = 0;
/// Interval between refresh in nanoseconds. Used for sw_opti.
unsigned long refresh_intv = 0;
/// Nanosecond-level offset of the first painting. Used for sw_opti.
long paint_tm_offset = 0;

#ifdef CONFIG_VSYNC_DRM
/// File descriptor of DRI device file. Used for DRM VSync.
int drm_fd = 0;
#endif

#ifdef CONFIG_VSYNC_OPENGL
/// GLX context.
GLXContext glx_context;

/// Pointer to glXGetVideoSyncSGI function. Used by OpenGL VSync.
f_GetVideoSync glx_get_video_sync = NULL;

/// Pointer to glXWaitVideoSyncSGI function. Used by OpenGL VSync.
f_WaitVideoSync glx_wait_video_sync = NULL;
#endif

/* errors */
ignore *ignore_head = NULL, **ignore_tail = &ignore_head;
int xfixes_event, xfixes_error;
int damage_event, damage_error;
int composite_event, composite_error;
int render_event, render_error;
int composite_opcode;

/// Whether X Shape extension exists.
Bool shape_exists = False;
/// Event base number and error base number for X Shape extension.
int shape_event, shape_error;

/// Whether X RandR extension exists.
Bool randr_exists = False;
/// Event base number and error base number for X RandR extension.
int randr_event, randr_error;

#ifdef CONFIG_VSYNC_OPENGL
/// Whether X GLX extension exists.
Bool glx_exists = False;
/// Event base number and error base number for X GLX extension.
int glx_event, glx_error;
#endif

Bool dbe_exists = False;

/* shadows */
conv *gaussian_map;

/* for shadow precomputation */
int cgsize = -1;
unsigned char *shadow_corner = NULL;
unsigned char *shadow_top = NULL;

/* for root tile */
static const char *background_props[] = {
  "_XROOTPMAP_ID",
  "_XSETROOT_ID",
  0,
};

/* for expose events */
XRectangle *expose_rects = 0;
int size_expose = 0;
int n_expose = 0;

// atoms
Atom extents_atom;
Atom opacity_atom;
Atom frame_extents_atom;
Atom client_atom;
Atom name_atom;
Atom name_ewmh_atom;
Atom class_atom;
Atom transient_atom;

Atom win_type_atom;
Atom win_type[NUM_WINTYPES];

/**
 * Macros
 */

#define HAS_FRAME_OPACITY(w) \
  (frame_opacity && (w)->top_width)

/**
 * Options
 */

static options_t opts = {
  .display = NULL,
  .mark_wmwin_focused = False,
  .mark_ovredir_focused = False,
  .fork_after_register = False,
  .synchronize = False,
  .detect_rounded_corners = False,
  .paint_on_overlay = False,

  .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,

  .wintype_fade = { False },
  .fade_in_step = 0.028 * OPAQUE,
  .fade_out_step = 0.03 * OPAQUE,
  .fade_delta = 10,
  .no_fading_openclose = False,
  .fade_blacklist = NULL,

  .wintype_opacity = { 0.0 },
  .inactive_opacity = 0,
  .inactive_opacity_override = False,
  .frame_opacity = 0.0,
  .detect_client_opacity = False,
  .inactive_dim = 0.0,
  .alpha_step = 0.03,

  .track_focus = False,
  .track_wdata = False,
};

/**
 * Fades
 */

unsigned long fade_time = 0;

/**
 * Get the time left before next fading point.
 *
 * In milliseconds.
 */
static int
fade_timeout(void) {
  int diff = opts.fade_delta - get_time_ms() + fade_time;

  if (diff < 0)
    diff = 0;

  return diff;
}

/**
 * Run fading on a window.
 *
 * @param steps steps of fading
 */
static void
run_fade(Display *dpy, 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) opts.fade_in_step * steps,
          0.0, w->opacity_tgt);
    else
      w->opacity = normalize_d_range(
          (double) w->opacity - (double) opts.fade_out_step * steps,
          w->opacity_tgt, OPAQUE);
  }

  if (w->opacity != w->opacity_tgt) {
    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(Display *dpy, win *w,
    void (*callback) (Display *dpy, win *w), Bool exec_callback) {
  void (*old_callback) (Display *dpy, 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(dpy, w);
    // Although currently no callback function affects window state on
    // next paint, it could, in the future
    idling = False;
  }
}

/**
 * Shadows
 */

static double
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(Display *dpy, 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, (double) (x - center), (double) (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(conv *map) {
  int center = map->size / 2;
  int opacity, x, y;

  cgsize = map->size;

  if (shadow_corner) free((void *)shadow_corner);
  if (shadow_top) free((void *)shadow_top);

  shadow_corner = (unsigned char *)(malloc((cgsize + 1) * (cgsize + 1) * 26));
  shadow_top = (unsigned char *)(malloc((cgsize + 1) * 26));

  for (x = 0; x <= cgsize; x++) {
    shadow_top[25 * (cgsize + 1) + x] =
      sum_gaussian(map, 1, x - center, center, cgsize * 2, cgsize * 2);

    for (opacity = 0; opacity < 25; opacity++) {
      shadow_top[opacity * (cgsize + 1) + x] =
        shadow_top[25 * (cgsize + 1) + x] * opacity / 25;
    }

    for (y = 0; y <= x; y++) {
      shadow_corner[25 * (cgsize + 1) * (cgsize + 1) + y * (cgsize + 1) + x]
        = sum_gaussian(map, 1, x - center, y - center, cgsize * 2, cgsize * 2);
      shadow_corner[25 * (cgsize + 1) * (cgsize + 1) + x * (cgsize + 1) + y]
        = shadow_corner[25 * (cgsize + 1) * (cgsize + 1) + y * (cgsize + 1) + x];

      for (opacity = 0; opacity < 25; opacity++) {
        shadow_corner[opacity * (cgsize + 1) * (cgsize + 1)
                      + y * (cgsize + 1) + x]
          = shadow_corner[opacity * (cgsize + 1) * (cgsize + 1)
                          + x * (cgsize + 1) + y]
          = shadow_corner[25 * (cgsize + 1) * (cgsize + 1)
                          + y * (cgsize + 1) + x] * opacity / 25;
      }
    }
  }
}

static XImage *
make_shadow(Display *dpy, double opacity,
            int width, int height, Bool clear_shadow) {
  XImage *ximage;
  unsigned char *data;
  int ylimit, xlimit;
  int swidth = width + cgsize;
  int sheight = height + cgsize;
  int center = 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(
    dpy, DefaultVisual(dpy, DefaultScreen(dpy)), 8,
    ZPixmap, 0, (char *) data, swidth, sheight, 8,
    swidth * sizeof(unsigned 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 && opts.shadow_offset_x <= 0 && opts.shadow_offset_x >= -cgsize
        && opts.shadow_offset_y <= 0 && opts.shadow_offset_y >= -cgsize)) { */
    if (cgsize > 0) {
      d = shadow_top[opacity_int * (cgsize + 1) + cgsize];
    } else {
      d = sum_gaussian(gaussian_map,
        opacity, center, center, width, height);
    }
    memset(data, d, sheight * swidth);
  // }

  /*
   * corners
   */

  ylimit = cgsize;
  if (ylimit > sheight / 2) ylimit = (sheight + 1) / 2;

  xlimit = cgsize;
  if (xlimit > swidth / 2) xlimit = (swidth + 1) / 2;

  for (y = 0; y < ylimit; y++) {
    for (x = 0; x < xlimit; x++) {
      if (xlimit == cgsize && ylimit == cgsize) {
        d = shadow_corner[opacity_int * (cgsize + 1) * (cgsize + 1)
                          + y * (cgsize + 1) + x];
      } else {
        d = sum_gaussian(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 - (cgsize * 2);
  if (x_diff > 0 && ylimit > 0) {
    for (y = 0; y < ylimit; y++) {
      if (ylimit == cgsize) {
        d = shadow_top[opacity_int * (cgsize + 1) + y];
      } else {
        d = sum_gaussian(gaussian_map,
          opacity, center, y - center, width, height);
      }
      memset(&data[y * swidth + cgsize], d, x_diff);
      memset(&data[(sheight - y - 1) * swidth + cgsize], d, x_diff);
    }
  }

  /*
   * sides
   */

  for (x = 0; x < xlimit; x++) {
    if (xlimit == cgsize) {
      d = shadow_top[opacity_int * (cgsize + 1) + x];
    } else {
      d = sum_gaussian(gaussian_map,
        opacity, x - center, center, width, height);
    }
    for (y = cgsize; y < sheight - cgsize; y++) {
      data[y * swidth + x] = d;
      data[y * swidth + (swidth - x - 1)] = d;
    }
  }

  assert(!clear_shadow);
  /*
  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) opts.shadow_offset_x, 0, swidth);
    int xrange = normalize_i_range(width - (int) opts.shadow_offset_x,
        0, swidth) - xstart;
    int ystart = normalize_i_range(- (int) opts.shadow_offset_y, 0, sheight);
    int yend = normalize_i_range(height - (int) opts.shadow_offset_y,
        0, sheight);
    int y;

    for (y = ystart; y < yend; y++) {
      memset(&data[y * swidth + xstart], 0, xrange);
    }
  }
  */

  return ximage;
}

static Picture
shadow_picture(Display *dpy, double opacity, int width, int height,
    Bool clear_shadow) {
  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(dpy, opacity, width, height, clear_shadow);
  if (!shadow_image)
    return None;

  shadow_pixmap = XCreatePixmap(dpy, root,
    shadow_image->width, shadow_image->height, 8);
  shadow_pixmap_argb = XCreatePixmap(dpy, root,
    shadow_image->width, shadow_image->height, 32);

  if (!shadow_pixmap || !shadow_pixmap_argb)
    goto shadow_picture_err;

  shadow_picture = XRenderCreatePicture(dpy, shadow_pixmap,
    XRenderFindStandardFormat(dpy, PictStandardA8), 0, 0);
  shadow_picture_argb = XRenderCreatePicture(dpy, shadow_pixmap_argb,
    XRenderFindStandardFormat(dpy, PictStandardARGB32), 0, 0);
  if (!shadow_picture || !shadow_picture_argb)
    goto shadow_picture_err;

  gc = XCreateGC(dpy, shadow_pixmap, 0, 0);
  if (!gc)
    goto shadow_picture_err;

  XPutImage(dpy, shadow_pixmap, gc, shadow_image, 0, 0, 0, 0,
    shadow_image->width, shadow_image->height);
  XRenderComposite(dpy, PictOpSrc, cshadow_picture, shadow_picture,
      shadow_picture_argb, 0, 0, 0, 0, 0, 0,
      shadow_image->width, shadow_image->height);

  XFreeGC(dpy, gc);
  XDestroyImage(shadow_image);
  XFreePixmap(dpy, shadow_pixmap);
  XFreePixmap(dpy, shadow_pixmap_argb);
  XRenderFreePicture(dpy, shadow_picture);

  return shadow_picture_argb;

shadow_picture_err:
  if (shadow_image)
    XDestroyImage(shadow_image);
  if (shadow_pixmap)
    XFreePixmap(dpy, shadow_pixmap);
  if (shadow_pixmap_argb)
    XFreePixmap(dpy, shadow_pixmap_argb);
  if (shadow_picture)
    XRenderFreePicture(dpy, shadow_picture);
  if (shadow_picture_argb)
    XRenderFreePicture(dpy, shadow_picture_argb);
  if (gc)
    XFreeGC(dpy, gc);
  return None;
}

static Picture
solid_picture(Display *dpy, Bool argb, double a,
              double r, double g, double b) {
  Pixmap pixmap;
  Picture picture;
  XRenderPictureAttributes pa;
  XRenderColor c;

  pixmap = XCreatePixmap(dpy, root, 1, 1, argb ? 32 : 8);

  if (!pixmap) return None;

  pa.repeat = True;
  picture = XRenderCreatePicture(dpy, pixmap,
    XRenderFindStandardFormat(dpy, argb
      ? PictStandardARGB32 : PictStandardA8),
    CPRepeat,
    &pa);

  if (!picture) {
    XFreePixmap(dpy, pixmap);
    return None;
  }

  c.alpha = a * 0xffff;
  c.red = r * 0xffff;
  c.green = g * 0xffff;
  c.blue = b * 0xffff;

  XRenderFillRectangle(dpy, PictOpSrc, picture, &c, 0, 0, 1, 1);
  XFreePixmap(dpy, pixmap);

  return picture;
}

/**
 * Errors
 */

static void
discard_ignore(Display *dpy, unsigned long sequence) {
  while (ignore_head) {
    if ((long) (sequence - ignore_head->sequence) > 0) {
      ignore  *next = ignore_head->next;
      free(ignore_head);
      ignore_head = next;
      if (!ignore_head) {
        ignore_tail = &ignore_head;
      }
    } else {
      break;
    }
  }
}

static void
set_ignore(Display *dpy, unsigned long sequence) {
  ignore *i = malloc(sizeof(ignore));
  if (!i) return;

  i->sequence = sequence;
  i->next = 0;
  *ignore_tail = i;
  ignore_tail = &i->next;
}

static int
should_ignore(Display *dpy, unsigned long sequence) {
  discard_ignore(dpy, sequence);
  return ignore_head && ignore_head->sequence == sequence;
}

/**
 * Windows
 */

/**
 * Check if a window has rounded corners.
 */
static void
win_rounded_corners(Display *dpy, win *w) {
  if (!w->bounding_shaped)
    return;

  // Fetch its bounding region
  if (!w->border_size)
    w->border_size = border_size(dpy, w);

  // 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(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;
      XFree(rects);
      return;
    }

  w->rounded_corners = False;
  XFree(rects);
}

/**
 * Match a window against a single window condition.
 *
 * @return true if matched, false otherwise.
 */
static bool
win_match_once(win *w, const wincond *cond) {
  const char *target;
  bool matched = false;

#ifdef DEBUG_WINMATCH
  printf("win_match_once(%#010lx \"%s\"): cond = %p", w->id, w->name,
      cond);
#endif

  if (InputOnly == w->a.class) {
#ifdef DEBUG_WINMATCH
  printf(": InputOnly\n");
#endif
    return false;
  }

  // Determine the target
  target = NULL;
  switch (cond->target) {
    case CONDTGT_NAME:
      target = w->name;
      break;
    case CONDTGT_CLASSI:
      target = w->class_instance;
      break;
    case CONDTGT_CLASSG:
      target = w->class_general;
      break;
  }

  if (!target) {
#ifdef DEBUG_WINMATCH
  printf(": Target not found\n");
#endif
    return false;
  }

  // Determine pattern type and match
  switch (cond->type) {
    case CONDTP_EXACT:
      if (cond->flags & CONDF_IGNORECASE)
        matched = !strcasecmp(target, cond->pattern);
      else
        matched = !strcmp(target, cond->pattern);
      break;
    case CONDTP_ANYWHERE:
      if (cond->flags & CONDF_IGNORECASE)
        matched = strcasestr(target, cond->pattern);
      else
        matched = strstr(target, cond->pattern);
      break;
    case CONDTP_FROMSTART:
      if (cond->flags & CONDF_IGNORECASE)
        matched = !strncasecmp(target, cond->pattern,
            strlen(cond->pattern));
      else
        matched = !strncmp(target, cond->pattern,
            strlen(cond->pattern));
      break;
    case CONDTP_WILDCARD:
      {
        int flags = 0;
        if (cond->flags & CONDF_IGNORECASE)
          flags = FNM_CASEFOLD;
        matched = !fnmatch(cond->pattern, target, flags);
      }
      break;
    case CONDTP_REGEX_PCRE:
#ifdef CONFIG_REGEX_PCRE
      matched = (pcre_exec(cond->regex_pcre, cond->regex_pcre_extra,
            target, strlen(target), 0, 0, NULL, 0) >= 0);
#endif
      break;
  }

#ifdef DEBUG_WINMATCH
  printf(", matched = %d\n", matched);
#endif

  return matched;
}

/**
 * Match a window against a condition linked list.
 *
 * @param cache a place to cache the last matched condition
 * @return true if matched, false otherwise.
 */
static bool
win_match(win *w, wincond *condlst, wincond **cache) {
  // Check if the cached entry matches firstly
  if (cache && *cache && win_match_once(w, *cache))
    return true;

  // Then go through the whole linked list
  for (; condlst; condlst = condlst->next) {
    if (win_match_once(w, condlst)) {
      *cache = condlst;
      return true;
    }
  }

  return false;
}

/**
 * Add a pattern to a condition linked list.
 */
static Bool
condlst_add(wincond **pcondlst, const char *pattern) {
  if (!pattern)
    return False;

  unsigned plen = strlen(pattern);
  wincond *cond;
  const char *pos;

  if (plen < 4 || ':' != pattern[1] || !strchr(pattern + 2, ':')) {
    printf("Pattern \"%s\": Format invalid.\n", pattern);
    return False;
  }

  // Allocate memory for the new condition
  cond = malloc(sizeof(wincond));

  // Determine the pattern target
  switch (pattern[0]) {
    case 'n':
      cond->target = CONDTGT_NAME;
      break;
    case 'i':
      cond->target = CONDTGT_CLASSI;
      break;
    case 'g':
      cond->target = CONDTGT_CLASSG;
      break;
    default:
      printf("Pattern \"%s\": Target \"%c\" invalid.\n",
          pattern, pattern[0]);
      free(cond);
      return False;
  }

  // Determine the pattern type
  switch (pattern[2]) {
    case 'e':
      cond->type = CONDTP_EXACT;
      break;
    case 'a':
      cond->type = CONDTP_ANYWHERE;
      break;
    case 's':
      cond->type = CONDTP_FROMSTART;
      break;
    case 'w':
      cond->type = CONDTP_WILDCARD;
      break;
#ifdef CONFIG_REGEX_PCRE
    case 'p':
      cond->type = CONDTP_REGEX_PCRE;
      break;
#endif
    default:
      printf("Pattern \"%s\": Type \"%c\" invalid.\n",
          pattern, pattern[2]);
      free(cond);
      return False;
  }

  // Determine the pattern flags
  pos = &pattern[3];
  cond->flags = 0;
  while (':' != *pos) {
    switch (*pos) {
      case 'i':
        cond->flags |= CONDF_IGNORECASE;
        break;
      default:
        printf("Pattern \"%s\": Flag \"%c\" invalid.\n",
            pattern, *pos);
        break;
    }
    ++pos;
  }

  // Copy the pattern
  ++pos;
  cond->pattern = NULL;
#ifdef CONFIG_REGEX_PCRE
  cond->regex_pcre = NULL;
  cond->regex_pcre_extra = NULL;
#endif
  if (CONDTP_REGEX_PCRE == cond->type) {
#ifdef CONFIG_REGEX_PCRE
    const char *error = NULL;
    int erroffset = 0;
    int options = 0;

    if (cond->flags & CONDF_IGNORECASE)
      options |= PCRE_CASELESS;

    cond->regex_pcre = pcre_compile(pos, options, &error, &erroffset,
        NULL);
    if (!cond->regex_pcre) {
      printf("Pattern \"%s\": PCRE regular expression parsing failed on "
          "offset %d: %s\n", pattern, erroffset, error);
      free(cond);
      return False;
    }
#ifdef CONFIG_REGEX_PCRE_JIT
    cond->regex_pcre_extra = pcre_study(cond->regex_pcre, PCRE_STUDY_JIT_COMPILE, &error);
    if (!cond->regex_pcre_extra) {
      printf("Pattern \"%s\": PCRE regular expression study failed: %s",
          pattern, error);
    }
#endif
#endif
  }
  else {
    cond->pattern = mstrcpy(pos);
  }

  // Insert it into the linked list
  cond->next = *pcondlst;
  *pcondlst = cond;

  return True;
}

static long
determine_evmask(Display *dpy, Window wid, win_evmode_t mode) {
  long evmask = NoEventMask;

  if (WIN_EVMODE_FRAME == mode || find_win(dpy, wid)) {
    evmask |= PropertyChangeMask;
    if (opts.track_focus) evmask |= FocusChangeMask;
  }

  if (WIN_EVMODE_CLIENT == mode || find_toplevel(dpy, wid)) {
    if (opts.frame_opacity || opts.track_wdata
        || opts.detect_client_opacity)
      evmask |= PropertyChangeMask;
  }

  return evmask;
}

static win *
find_win(Display *dpy, Window id) {
  win *w;

  for (w = list; w; w = w->next) {
    if (w->id == id && !w->destroyed)
      return w;
  }

  return 0;
}

/**
 * Find out the WM frame of a client window using existing data.
 *
 * @param dpy display to use
 * @param w window ID
 * @return struct _win object of the found window, NULL if not found
 */
static win *
find_toplevel(Display *dpy, Window id) {
  win *w;

  for (w = list; w; w = w->next) {
    if (w->client_win == id && !w->destroyed)
      return w;
  }

  return NULL;
}

/**
 * Find out the WM frame of a client window by querying X.
 *
 * @param dpy display to use
 * @param w window ID
 * @return struct _win object of the found window, NULL if not found
 */
static win *
find_toplevel2(Display *dpy, Window wid) {
  win *w = NULL;

  // We traverse through its ancestors to find out the frame
  while (wid && wid != root && !(w = find_win(dpy, 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(dpy, wid, &troot, &parent, &tchildren,
          &tnchildren)) {
      parent = 0;
      break;
    }

    if (tchildren) XFree(tchildren);

    wid = parent;
  }

  return w;
}

/**
 * Recheck currently focused window and set its <code>w->focused</code>
 * to True.
 *
 * @param dpy display to use
 * @return struct _win of currently focused window, NULL if not found
 */
static win *
recheck_focus(Display *dpy) {
  // Determine the currently focused window so we can apply appropriate
  // opacity on it
  Window wid = 0;
  int revert_to;
  win *w = NULL;

  XGetInputFocus(dpy, &wid, &revert_to);

  // Fallback to the old method if find_toplevel() fails
  if (!(w = find_toplevel(dpy, wid))) {
    w = find_toplevel2(dpy, wid);
  }

  // And we set the focus state and opacity here
  if (w) {
    set_focused(dpy, w, True);
    return w;
  }

  return NULL;
}

static Picture
root_tile_f(Display *dpy) {
  /*
  if (opts.paint_on_overlay) {
    return root_picture;
  } */

  Picture picture;
  Atom actual_type;
  Pixmap pixmap;
  int actual_format;
  unsigned long nitems;
  unsigned long bytes_after;
  unsigned char *prop;
  Bool fill;
  XRenderPictureAttributes pa;
  int p;

  pixmap = None;

  for (p = 0; background_props[p]; p++) {
    prop = NULL;
    if (XGetWindowProperty(dpy, root,
          XInternAtom(dpy, background_props[p], False),
          0, 4, False, AnyPropertyType, &actual_type,
          &actual_format, &nitems, &bytes_after, &prop
        ) == Success
        && actual_type == XInternAtom(dpy, "PIXMAP", False)
        && actual_format == 32 && nitems == 1) {
      memcpy(&pixmap, prop, 4);
      XFree(prop);
      fill = False;
      break;
    } else if (prop)
      XFree(prop);
  }

  if (!pixmap) {
    pixmap = XCreatePixmap(dpy, root, 1, 1, DefaultDepth(dpy, scr));
    fill = True;
  }

  pa.repeat = True;
  picture = XRenderCreatePicture(
    dpy, pixmap, XRenderFindVisualFormat(dpy, DefaultVisual(dpy, scr)),
    CPRepeat, &pa);

  if (fill) {
    XRenderColor  c;

    c.red = c.green = c.blue = 0x8080;
    c.alpha = 0xffff;
    XRenderFillRectangle(
      dpy, PictOpSrc, picture, &c, 0, 0, 1, 1);
  }

  return picture;
}

static void
paint_root(Display *dpy) {
  if (!root_tile) {
    root_tile = root_tile_f(dpy);
  }

  XRenderComposite(
    dpy, PictOpSrc, root_tile, None,
    tgt_buffer, 0, 0, 0, 0, 0, 0,
    root_width, root_height);
}

/**
 * Get a rectangular region a window occupies, excluding shadow.
 */
static XserverRegion
win_get_region(Display *dpy, win *w) {
  XRectangle r;

  r.x = w->a.x;
  r.y = w->a.y;
  r.width = w->widthb;
  r.height = w->heightb;

  return XFixesCreateRegion(dpy, &r, 1);
}

/**
 * Get a rectangular region a window occupies, excluding frame and shadow.
 */
static XserverRegion
win_get_region_noframe(Display *dpy, win *w) {
  XRectangle r;

  r.x = w->a.x + w->left_width;
  r.y = w->a.y + w->top_width;
  r.width = w->a.width;
  r.height = w->a.height;

  return XFixesCreateRegion(dpy, &r, 1);
}

/**
 * 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(Display *dpy, 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(dpy, &r, 1);
}

static XserverRegion
border_size(Display *dpy, win *w) {
  XserverRegion border;

  /*
   * 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.
   */

  border = XFixesCreateRegionFromWindow(
    dpy, w->id, WindowRegionBounding);

  if (!border)
    return None;

  /* translate this */
  XFixesTranslateRegion(dpy, border,
    w->a.x + w->a.border_width,
    w->a.y + w->a.border_width);

  return border;
}

static Window
find_client_win(Display *dpy, Window w) {
  if (wid_has_attr(dpy, w, client_atom)) {
    return w;
  }

  Window *children;
  unsigned int nchildren;
  unsigned int i;
  Window ret = 0;

  if (!wid_get_children(dpy, w, &children, &nchildren)) {
    return 0;
  }

  for (i = 0; i < nchildren; ++i) {
    if ((ret = find_client_win(dpy, children[i])))
      break;
  }

  XFree(children);

  return ret;
}

static void
get_frame_extents(Display *dpy, win *w, Window client) {
  long *extents;
  Atom type;
  int format;
  unsigned long nitems, after;
  unsigned char *data = NULL;
  int result;

  w->left_width = 0;
  w->right_width = 0;
  w->top_width = 0;
  w->bottom_width = 0;

  result = XGetWindowProperty(
    dpy, client, frame_extents_atom,
    0L, 4L, False, AnyPropertyType,
    &type, &format, &nitems, &after,
    &data);

  if (result == Success) {
    if (nitems == 4 && after == 0) {
      extents = (long *) data;
      w->left_width = extents[0];
      w->right_width = extents[1];
      w->top_width = extents[2];
      w->bottom_width = extents[3];

      if (opts.frame_opacity)
        update_reg_ignore_expire(w);
    }
    XFree(data);
  }
}

static inline Picture
get_alpha_pict_d(double o) {
  assert((lround(normalize_d(o) / opts.alpha_step)) <= lround(1.0 / opts.alpha_step));
  return alpha_picts[lround(normalize_d(o) / opts.alpha_step)];
}

static inline Picture
get_alpha_pict_o(opacity_t o) {
  return get_alpha_pict_d((double) o / OPAQUE);
}

static win *
paint_preprocess(Display *dpy, win *list) {
  win *w;
  win *t = NULL, *next = NULL;

  // Fading step calculation
  unsigned steps = (sub_unslong(get_time_ms(), fade_time)
      + FADE_DELTA_TOLERANCE * opts.fade_delta) / opts.fade_delta;
  fade_time += steps * opts.fade_delta;

  XserverRegion last_reg_ignore = None;

  for (w = list; w; w = next) {
    Bool to_paint = True;
    const winmode mode_old = w->mode;

    // In case calling the fade callback function destroys this window
    next = w->next;
    opacity_t opacity_old = w->opacity;

    // Destroy reg_ignore on all windows if they should expire
    if (reg_ignore_expire)
      free_region(dpy, &w->reg_ignore);

    // Run fading
    run_fade(dpy, w, steps);

    // Give up if it's not damaged or invisible, or it's unmapped and its
    // picture is gone (for example due to a ConfigureNotify)
    if (!w->damaged
        || w->a.x + w->a.width < 1 || w->a.y + w->a.height < 1
        || w->a.x >= root_width || w->a.y >= root_height
        || (IsUnmapped == w->a.map_state && !w->picture)) {
      to_paint = False;
    }

    if (to_paint) {
      // If opacity changes
      if (w->opacity != opacity_old) {
        determine_mode(dpy, w);
        add_damage_win(dpy, w);
      }

      w->alpha_pict = get_alpha_pict_o(w->opacity);

      // End the game if we are using the 0 opacity alpha_pict
      if (w->alpha_pict == alpha_picts[0]) {
        to_paint = False;
      }
    }

    if (to_paint) {
      // Fetch the picture and pixmap if needed
      if (!w->picture) {
        XRenderPictureAttributes pa;
        XRenderPictFormat *format;
        Drawable draw = w->id;

        if (has_name_pixmap && !w->pixmap) {
          set_ignore(dpy, NextRequest(dpy));
          w->pixmap = XCompositeNameWindowPixmap(dpy, w->id);
        }
        if (w->pixmap) draw = w->pixmap;

        format = XRenderFindVisualFormat(dpy, w->a.visual);
        pa.subwindow_mode = IncludeInferiors;
        w->picture = XRenderCreatePicture(
          dpy, draw, format, CPSubwindowMode, &pa);
      }

      // Fetch bounding region
      if (!w->border_size) {
        // Build a border_size ourselves if window is not shaped, to avoid
        // getting an invalid border_size region from X if the window is
        // unmapped/destroyed
        if (!w->bounding_shaped) {
          w->border_size = win_get_region(dpy, w);
        }
        else if (IsUnmapped != w->a.map_state) {
          w->border_size = border_size(dpy, w);
        }
      }

      // Fetch window extents
      if (!w->extents) {
        w->extents = win_extents(dpy, w);
        // If w->extents does not exist, the previous add_damage_win()
        // call when opacity changes has no effect, so redo it here.
        if (w->opacity != opacity_old)
          add_damage_win(dpy, w);
      }

      // Calculate frame_opacity
      {
        double frame_opacity_old = w->frame_opacity;

        if (opts.frame_opacity && 1.0 != opts.frame_opacity
            && win_has_frame(w))
          w->frame_opacity = get_opacity_percent(dpy, w) *
            opts.frame_opacity;
        else
          w->frame_opacity = 0.0;

        if (w->to_paint && WINDOW_SOLID == mode_old
            && (0.0 == frame_opacity_old) != (0.0 == w->frame_opacity))
          reg_ignore_expire = True;
      }

      w->frame_alpha_pict = get_alpha_pict_d(w->frame_opacity);

      // Calculate shadow opacity
      if (w->frame_opacity)
        w->shadow_opacity = opts.shadow_opacity * w->frame_opacity;
      else
        w->shadow_opacity = opts.shadow_opacity * get_opacity_percent(dpy, w);

      // Rebuild shadow_pict if necessary
      if (w->flags & WFLAG_SIZE_CHANGE)
        free_picture(dpy, &w->shadow_pict);

      if (w->shadow && !w->shadow_pict) {
        w->shadow_pict = shadow_picture(dpy, 1,
            w->widthb, w->heightb, False);
      }

      w->shadow_alpha_pict = get_alpha_pict_d(w->shadow_opacity);
    }

    if ((to_paint && WINDOW_SOLID == w->mode)
        != (w->to_paint && WINDOW_SOLID == mode_old))
      reg_ignore_expire = True;

    if (to_paint) {
      // Generate ignore region for painting to reduce GPU load
      if (reg_ignore_expire || !w->to_paint) {
        free_region(dpy, &w->reg_ignore);

        // If the window is solid, we add the window region to the
        // ignored region
        if (WINDOW_SOLID == w->mode) {
          if (!w->frame_opacity)
            w->reg_ignore = win_get_region(dpy, w);
          else
            w->reg_ignore = win_get_region_noframe(dpy, w);

          if (w->border_size)
            XFixesIntersectRegion(dpy, w->reg_ignore, w->reg_ignore,
                w->border_size);

          if (last_reg_ignore)
            XFixesUnionRegion(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(dpy, last_reg_ignore);
        else
          w->reg_ignore = None;
      }

      last_reg_ignore = w->reg_ignore;

      // Reset flags
      w->flags = 0;
    }


    if (to_paint) {
      w->prev_trans = t;
      t = w;
    }
    else {
      check_fade_fin(dpy, w);
    }

    w->to_paint = to_paint;
  }

  return t;
}

/**
 * Paint the shadow of a window.
 */
static inline void
win_paint_shadow(Display *dpy, win *w, Picture tgt_buffer) {
  XRenderComposite(
    dpy, PictOpOver, w->shadow_pict, w->shadow_alpha_pict,
    tgt_buffer, 0, 0, 0, 0,
    w->a.x + w->shadow_dx, w->a.y + w->shadow_dy,
    w->shadow_width, w->shadow_height);
}

/**
 * Paint a window itself and dim it if asked.
 */
static inline void
win_paint_win(Display *dpy, win *w, Picture tgt_buffer) {
  int x = w->a.x;
  int y = w->a.y;
  int wid = w->widthb;
  int hei = w->heightb;

  Picture alpha_mask = (OPAQUE == w->opacity ? None: w->alpha_pict);
  int op = (w->mode == WINDOW_SOLID ? PictOpSrc: PictOpOver);

  if (!w->frame_opacity) {
    XRenderComposite(dpy, op, w->picture, alpha_mask,
        tgt_buffer, 0, 0, 0, 0, x, y, wid, hei);
  }
  else {
    unsigned int t = w->top_width;
    unsigned int l = w->left_width;
    unsigned int b = w->bottom_width;
    unsigned int r = w->right_width;

    // top
    XRenderComposite(dpy, PictOpOver, w->picture, w->frame_alpha_pict,
        tgt_buffer, 0, 0, 0, 0, x, y, wid, t);

    // left
    XRenderComposite(dpy, PictOpOver, w->picture, w->frame_alpha_pict,
        tgt_buffer, 0, t, 0, t, x, y + t, l, hei - t);

    // bottom
    XRenderComposite(dpy, PictOpOver, w->picture, w->frame_alpha_pict,
        tgt_buffer, l, hei - b, l, hei - b, x + l, y + hei - b, wid - l - r, b);

    // right
    XRenderComposite(dpy, PictOpOver, w->picture, w->frame_alpha_pict,
        tgt_buffer, wid - r, t, wid - r, t, x + wid - r, y + t, r, hei - t);

    // body
    XRenderComposite(dpy, op, w->picture, alpha_mask, tgt_buffer,
      l, t, l, t, x + l, y + t, wid - l - r, hei - t - b);

  }

  // Dimming the window if needed
  if (w->dim) {
    XRenderComposite(dpy, PictOpOver, dim_picture, None,
        tgt_buffer, 0, 0, 0, 0, x, y, wid, hei);
  }
}

static void
paint_all(Display *dpy, XserverRegion region, win *t) {
#ifdef DEBUG_REPAINT
  static struct timespec last_paint = { 0 };
#endif

  win *w;
  XserverRegion reg_paint = None, reg_tmp = None, reg_tmp2 = None;

  if (!region) {
    region = get_screen_region(dpy);
  }
  else {
    // Remove the damaged area out of screen
    XFixesIntersectRegion(dpy, region, region, get_screen_region(dpy));
  }

#ifdef MONITOR_REPAINT
  // Note: MONITOR_REPAINT cannot work with DBE right now.
  tgt_buffer = tgt_picture;
#else
  if (!tgt_buffer) {
    // DBE painting mode: Directly paint to a Picture of the back buffer
    if (opts.dbe) {
      tgt_buffer = XRenderCreatePicture(dpy, root_dbe,
          XRenderFindVisualFormat(dpy, DefaultVisual(dpy, scr)),
          0, 0);
    }
    // No-DBE painting mode: Paint to an intermediate Picture then paint
    // the Picture to root window
    else {
      Pixmap root_pixmap = XCreatePixmap(
        dpy, root, root_width, root_height,
        DefaultDepth(dpy, scr));

      tgt_buffer = XRenderCreatePicture(dpy, root_pixmap,
        XRenderFindVisualFormat(dpy, DefaultVisual(dpy, scr)),
        0, 0);

      XFreePixmap(dpy, root_pixmap);
    }
  }
#endif

  XFixesSetPictureClipRegion(dpy, tgt_picture, 0, 0, region);

#ifdef MONITOR_REPAINT
  XRenderComposite(
    dpy, PictOpSrc, black_picture, None,
    tgt_picture, 0, 0, 0, 0, 0, 0,
    root_width, root_height);
#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(dpy, NULL, 0);
    XFixesSubtractRegion(dpy, reg_paint, region, t->reg_ignore);
  }
  else {
    reg_paint = region;
  }

  XFixesSetPictureClipRegion(dpy, tgt_buffer, 0, 0, reg_paint);

  paint_root(dpy);

  // Create temporary regions for use during painting
  if (!reg_tmp)
    reg_tmp = XFixesCreateRegion(dpy, NULL, 0);
  reg_tmp2 = XFixesCreateRegion(dpy, NULL, 0);

  for (w = t; w; w = w->prev_trans) {
    // Painting shadow
    if (w->shadow) {
      // 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(dpy, reg_paint, region, w->reg_ignore);
        }
        else {
          // Otherwise, used the cached region during last cycle
          reg_paint = reg_tmp2;
        }
        XFixesIntersectRegion(dpy, reg_paint, reg_paint, w->extents);
      }
      else {
        reg_paint = reg_tmp;
        XFixesIntersectRegion(dpy, reg_paint, region, w->extents);
      }
      // Clear the shadow here instead of in make_shadow() for saving GPU
      // power and handling shaped windows
      if (opts.clear_shadow && w->border_size)
        XFixesSubtractRegion(dpy, reg_paint, reg_paint, w->border_size);

      // Detect if the region is empty before painting
      if (region == reg_paint || !is_region_empty(dpy, reg_paint)) {
        XFixesSetPictureClipRegion(dpy, tgt_buffer, 0, 0, reg_paint);

        win_paint_shadow(dpy, w, tgt_buffer);
      }
    }

    // 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(dpy, reg_paint, region,
          w->prev_trans->reg_ignore);
      // Copy the subtracted region to be used for shadow painting in next
      // cycle
      XFixesCopyRegion(dpy, reg_tmp2, reg_paint);

      if (w->border_size)
        XFixesIntersectRegion(dpy, reg_paint, reg_paint, w->border_size);
    }
    else {
      if (w->border_size)
        XFixesIntersectRegion(dpy, reg_paint, region, w->border_size);
      else
        reg_paint = region;
    }

    if (!is_region_empty(dpy, reg_paint)) {
      XFixesSetPictureClipRegion(dpy, tgt_buffer, 0, 0, reg_paint);

      // Painting the window
      win_paint_win(dpy, w, tgt_buffer);
    }

    check_fade_fin(dpy, w);
  }

  // Free up all temporary regions
  XFixesDestroyRegion(dpy, region);
  XFixesDestroyRegion(dpy, reg_tmp);
  XFixesDestroyRegion(dpy, reg_tmp2);

  // Do this as early as possible
  if (!opts.dbe)
    XFixesSetPictureClipRegion(dpy, tgt_buffer, 0, 0, None);

  if (VSYNC_NONE != opts.vsync) {
    // Make sure all previous requests are processed to achieve best
    // effect
    XSync(dpy, False);
  }

  // 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 (!opts.vsync_aggressive)
    vsync_wait();

  // DBE painting mode, only need to swap the buffer
  if (opts.dbe) {
    XdbeSwapInfo swap_info = {
      .swap_window = (opts.paint_on_overlay ? overlay: root),
      // Is it safe to use XdbeUndefined?
      .swap_action = XdbeCopied
    };
    XdbeSwapBuffers(dpy, &swap_info, 1);
  }
  // No-DBE painting mode
  else if (tgt_buffer != tgt_picture) {
    XRenderComposite(
      dpy, PictOpSrc, tgt_buffer, None,
      tgt_picture, 0, 0, 0, 0,
      0, 0, root_width, root_height);
  }

  if (opts.vsync_aggressive)
    vsync_wait();

  XFlush(dpy);

#ifdef DEBUG_REPAINT
  print_timestamp();
  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 (w = t; w; w = w->prev_trans)
    printf(" %#010lx", w->id);
  putchar('\n');
  fflush(stdout);
#endif
}

static void
add_damage(Display *dpy, XserverRegion damage) {
  if (all_damage) {
    XFixesUnionRegion(dpy, all_damage, all_damage, damage);
    XFixesDestroyRegion(dpy, damage);
  } else {
    all_damage = damage;
  }
}

static void
repair_win(Display *dpy, win *w) {
  XserverRegion parts;

  if (!w->damaged) {
    parts = win_extents(dpy, w);
    set_ignore(dpy, NextRequest(dpy));
    XDamageSubtract(dpy, w->damage, None, None);
  } else {
    parts = XFixesCreateRegion(dpy, 0, 0);
    set_ignore(dpy, NextRequest(dpy));
    XDamageSubtract(dpy, w->damage, None, parts);
    XFixesTranslateRegion(dpy, parts,
      w->a.x + w->a.border_width,
      w->a.y + w->a.border_width);
  }

  // Remove the part in the damage area that could be ignored
  if (!reg_ignore_expire && w->prev_trans && w->prev_trans->reg_ignore)
    XFixesSubtractRegion(dpy, parts, parts, w->prev_trans->reg_ignore);

  add_damage(dpy, parts);
  w->damaged = 1;
}

static wintype
get_wintype_prop(Display *dpy, Window wid) {
  Atom actual;
  int format;
  unsigned long n = 0, left, i;
  long *data = NULL;
  int j;

  set_ignore(dpy, NextRequest(dpy));
  if (Success != XGetWindowProperty(
        dpy, wid, win_type_atom, 0L, 32L, False, XA_ATOM,
        &actual, &format, &n, &left, (unsigned char **) &data)
      || !data || !n) {
    if (data)
      XFree(data);
    return WINTYPE_UNKNOWN;
  }

  for (i = 0; i < n; ++i) {
    for (j = 1; j < NUM_WINTYPES; ++j) {
      if (win_type[j] == (Atom) data[i]) {
        XFree(data);
        return j;
      }
    }
  }

  XFree(data);

  return WINTYPE_UNKNOWN;
}

static void
map_win(Display *dpy, Window id,
        unsigned long sequence, Bool fade,
        Bool override_redirect) {
  win *w = find_win(dpy, id);

  // Don't care about window mapping if it's an InputOnly window
  if (!w || InputOnly == w->a.class) return;

  w->focused = False;
  w->a.map_state = IsViewable;

  // Call XSelectInput() before reading properties so that no property
  // changes are lost
  XSelectInput(dpy, id, determine_evmask(dpy, id, WIN_EVMODE_FRAME));

  // Notify compton when the shape of a window changes
  if (shape_exists) {
    XShapeSelectInput(dpy, id, ShapeNotifyMask);
  }

  // Detect client window here instead of in add_win() as the client
  // window should have been prepared at this point
  if (!w->client_win) {
    Window cw = 0;
    // Always recursively look for a window with WM_STATE, as Fluxbox
    // sets override-redirect flags on all frame windows.
    cw = find_client_win(dpy, w->id);
#ifdef DEBUG_CLIENTWIN
    printf("find_client_win(%#010lx): client %#010lx\n", w->id, cw);
#endif
    // Set a window's client window to itself only if we didn't find a
    // client window and the window has override-redirect flag
    if (!cw && w->a.override_redirect) {
      cw = w->id;
#ifdef DEBUG_CLIENTWIN
      printf("find_client_win(%#010lx): client self (override-redirected)\n", w->id);
#endif
    }
    if (cw) {
      mark_client_win(dpy, w, cw);
    }
  }
  else if (opts.frame_opacity) {
    // Refetch frame extents just in case it changes when the window is
    // unmapped
    get_frame_extents(dpy, w, w->client_win);
  }

  // Workaround for _NET_WM_WINDOW_TYPE for Openbox menus, which is
  // set on a non-override-redirect window with no WM_STATE either
  if (!w->client_win && WINTYPE_UNKNOWN == w->window_type)
    w->window_type = get_wintype_prop(dpy, w->id);

#ifdef DEBUG_WINTYPE
  printf("map_win(%#010lx): type %s\n",
    w->id, WINTYPES[w->window_type]);
#endif

  // Detect if the window is shaped or has rounded corners
  win_update_shape(dpy, w);

  // Get window name and class if we are tracking them
  if (opts.track_wdata) {
    win_get_name(dpy, w);
    win_get_class(dpy, w);
  }

  /*
   * Occasionally compton does not seem able to get a FocusIn event from a
   * window just mapped. I suspect it's a timing issue again when the
   * XSelectInput() is called too late. We have to recheck the focused
   * window here.
   */
  if (opts.track_focus) {
    recheck_focus(dpy);
    // Consider a window without client window a WM window and mark it
    // focused if mark_wmwin_focused is on, or it's over-redirected and
    // mark_ovredir_focused is on
    if ((opts.mark_wmwin_focused && !w->client_win)
        || (opts.mark_ovredir_focused && w->a.override_redirect))
      w->focused = True;
  }

  // Window type change and bounding shape state change could affect
  // shadow
  determine_shadow(dpy, w);

  // Fading in
  calc_opacity(dpy, w, True);

  // Set fading state
  if (opts.no_fading_openclose) {
    set_fade_callback(dpy, w, finish_map_win, True);
    // Must be set after we execute the old fade callback, in case we
    // receive two continuous MapNotify for the same window
    w->fade = False;
  }
  else {
    set_fade_callback(dpy, w, NULL, True);
    determine_fade(dpy, w);
  }

  calc_dim(dpy, w);

  w->damaged = 1;


  /* if any configure events happened while
     the window was unmapped, then configure
     the window to its correct place */
  if (w->need_configure) {
    configure_win(dpy, &w->queue_configure);
  }
}

static void
finish_map_win(Display *dpy, win *w) {
  if (opts.no_fading_openclose)
    determine_fade(dpy, w);
}

static void
finish_unmap_win(Display *dpy, win *w) {
  w->damaged = 0;

  update_reg_ignore_expire(w);

  if (w->extents != None) {
    /* destroys region */
    add_damage(dpy, w->extents);
    w->extents = None;
  }

  free_pixmap(dpy, &w->pixmap);
  free_picture(dpy, &w->picture);
  free_region(dpy, &w->border_size);
  free_picture(dpy, &w->shadow_pict);
}

static void
unmap_callback(Display *dpy, win *w) {
  finish_unmap_win(dpy, w);
}

static void
unmap_win(Display *dpy, Window id, Bool fade) {
  win *w = find_win(dpy, id);

  if (!w) return;

  w->a.map_state = IsUnmapped;

  // Fading out
  w->opacity_tgt = 0;
  set_fade_callback(dpy, w, unmap_callback, False);
  if (opts.no_fading_openclose)
    w->fade = False;

  // don't care about properties anymore
  // Will get BadWindow if the window is destroyed
  set_ignore(dpy, NextRequest(dpy));
  XSelectInput(dpy, w->id, 0);

  if (w->client_win) {
    set_ignore(dpy, NextRequest(dpy));
    XSelectInput(dpy, w->client_win, 0);
  }
}

static opacity_t
wid_get_opacity_prop(Display *dpy, Window wid, opacity_t def) {
  Atom actual;
  int format;
  unsigned long n, left;

  unsigned char *data;
  int result = XGetWindowProperty(
    dpy, wid, opacity_atom, 0L, 1L, False,
    XA_CARDINAL, &actual, &format, &n, &left, &data);

  if (result == Success && data != NULL) {
    opacity_t i = *((opacity_t *) data);
    XFree(data);
    return i;
  }

  return def;
}

static double
get_opacity_percent(Display *dpy, win *w) {
  return ((double) w->opacity) / OPAQUE;
}

static void
determine_mode(Display *dpy, win *w) {
  winmode mode;
  XRenderPictFormat *format;

  /* if trans prop == -1 fall back on previous tests */

  if (w->a.class == InputOnly) {
    format = 0;
  } else {
    format = XRenderFindVisualFormat(dpy, w->a.visual);
  }

  if (format && format->type == PictTypeDirect
      && format->direct.alphaMask) {
    mode = WINDOW_ARGB;
  } else if (w->opacity != OPAQUE) {
    mode = WINDOW_TRANS;
  } else {
    mode = WINDOW_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 dpy X display to use
 * @param w struct _win object representing the window
 * @param refetch_prop whether _NET_WM_OPACITY of the window needs to be
 *    refetched
 */
static void
calc_opacity(Display *dpy, win *w, Bool refetch_prop) {
  opacity_t opacity;

  // Do nothing for unmapped window, calc_opacity() will be called
  // when it's mapped
  // I suppose I need not to check for IsUnviewable here?
  if (IsViewable != w->a.map_state) return;

  // Do not refetch the opacity window attribute unless necessary, this
  // is probably an expensive operation in some cases
  if (refetch_prop) {
    w->opacity_prop = wid_get_opacity_prop(dpy, w->id, OPAQUE);
    if (!opts.detect_client_opacity || !w->client_win
        || w->id == w->client_win)
      w->opacity_prop_client = OPAQUE;
    else
      w->opacity_prop_client = wid_get_opacity_prop(dpy, w->client_win,
            OPAQUE);
  }

  if (OPAQUE == (opacity = w->opacity_prop)
      && OPAQUE == (opacity = w->opacity_prop_client)) {
    opacity = opts.wintype_opacity[w->window_type] * OPAQUE;
  }

  // Respect inactive_opacity in some cases
  if (opts.inactive_opacity && is_normal_win(w) && False == w->focused
      && (OPAQUE == opacity || opts.inactive_opacity_override)) {
    opacity = opts.inactive_opacity;
  }

  w->opacity_tgt = opacity;
}

static void
calc_dim(Display *dpy, win *w) {
  Bool dim;

  if (opts.inactive_dim && is_normal_win(w) && !(w->focused)) {
    dim = True;
  } else {
    dim = False;
  }

  if (dim != w->dim) {
    w->dim = dim;
    add_damage_win(dpy, w);
  }
}

/**
 * Determine if a window should fade on opacity change.
 */
static void
determine_fade(Display *dpy, win *w) {
  w->fade = opts.wintype_fade[w->window_type];
}

/**
 * Update window-shape related information.
 */
static void
win_update_shape(Display *dpy, win *w) {
  if (shape_exists) {
    // Bool bounding_shaped_old = w->bounding_shaped;

    w->bounding_shaped = wid_bounding_shaped(dpy, w->id);
    if (w->bounding_shaped && opts.detect_rounded_corners)
      win_rounded_corners(dpy, w);

    // Shadow state could be changed
    determine_shadow(dpy, w);

    /*
    // If clear_shadow state on the window possibly changed, destroy the old
    // shadow_pict
    if (opts.clear_shadow && w->bounding_shaped != bounding_shaped_old)
      free_picture(dpy, &w->shadow_pict);
    */
  }
}

/**
 * Determine if a window should have shadow, and update things depending
 * on shadow state.
 */
static void
determine_shadow(Display *dpy, win *w) {
  Bool shadow_old = w->shadow;

  w->shadow = (opts.wintype_shadow[w->window_type]
      && !win_match(w, opts.shadow_blacklist, &w->cache_sblst)
      && !(opts.shadow_ignore_shaped && w->bounding_shaped
        && !w->rounded_corners));

  // 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(dpy, w);
    if (w->extents) {
      // Mark the old extents as damaged if the shadow is removed
      if (!w->shadow)
        add_damage(dpy, w->extents);
      else
        free_region(dpy, &w->extents);
      w->extents = win_extents(dpy, w);
      // Mark the new extents as damaged if the shadow is added
      if (w->shadow)
        add_damage_win(dpy, w);
    }
  }
}

/**
 * Update cache data in struct _win that depends on window size.
 */

static void
calc_win_size(Display *dpy, 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(dpy, w);
  w->flags |= WFLAG_SIZE_CHANGE;
}

/**
 * Calculate and update geometry of the shadow of a window.
 */
static void
calc_shadow_geometry(Display *dpy, win *w) {
  w->shadow_dx = opts.shadow_offset_x;
  w->shadow_dy = opts.shadow_offset_y;
  w->shadow_width = w->widthb + gaussian_map->size;
  w->shadow_height = w->heightb + gaussian_map->size;
}

/**
 * Mark a window as the client window of another.
 *
 * @param dpy display to use
 * @param w struct _win of the parent window
 * @param client window ID of the client window
 */
static void
mark_client_win(Display *dpy, win *w, Window client) {
  w->client_win = client;

  XSelectInput(dpy, client, determine_evmask(dpy, client, WIN_EVMODE_CLIENT));

  // Get the frame width and monitor further frame width changes on client
  // window if necessary
  if (opts.frame_opacity) {
    get_frame_extents(dpy, w, client);
  }

  // Detect window type here
  if (WINTYPE_UNKNOWN == w->window_type)
    w->window_type = get_wintype_prop(dpy, 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_attr(dpy, client, transient_atom))
      w->window_type = WINTYPE_NORMAL;
    else
      w->window_type = WINTYPE_DIALOG;
  }
}

static void
add_win(Display *dpy, Window id, Window prev, Bool override_redirect) {
  if (find_win(dpy, id)) {
    return;
  }

  win *new = malloc(sizeof(win));
  win **p;

  if (!new) return;

  if (prev) {
    for (p = &list; *p; p = &(*p)->next) {
      if ((*p)->id == prev && !(*p)->destroyed)
        break;
    }
  } else {
    p = &list;
  }

  new->id = id;
  set_ignore(dpy, NextRequest(dpy));

  if (!XGetWindowAttributes(dpy, id, &new->a)) {
    free(new);
    return;
  }

  new->damaged = 0;
  new->to_paint = False;
  new->pixmap = None;
  new->picture = None;

  if (new->a.class == InputOnly) {
    new->damage_sequence = 0;
    new->damage = None;
  } else {
    new->damage_sequence = NextRequest(dpy);
    set_ignore(dpy, NextRequest(dpy));
    new->damage = XDamageCreate(dpy, id, XDamageReportNonEmpty);
  }

  new->name = NULL;
  new->class_instance = NULL;
  new->class_general = NULL;
  new->cache_sblst = NULL;
  new->cache_fblst = NULL;
  new->bounding_shaped = False;
  new->rounded_corners = False;

  new->border_size = None;
  new->reg_ignore = None;
  new->extents = None;
  new->shadow = False;
  new->shadow_opacity = 0.0;
  new->shadow_pict = None;
  new->shadow_alpha_pict = None;
  new->shadow_dx = 0;
  new->shadow_dy = 0;
  new->shadow_width = 0;
  new->shadow_height = 0;
  new->opacity = 0;
  new->opacity_tgt = 0;
  new->opacity_prop = OPAQUE;
  new->opacity_prop_client = OPAQUE;
  new->fade = False;
  new->fade_callback = NULL;
  new->alpha_pict = None;
  new->frame_opacity = 1.0;
  new->frame_alpha_pict = None;
  new->dim = False;
  new->focused = False;
  new->destroyed = False;
  new->need_configure = False;
  new->window_type = WINTYPE_UNKNOWN;
  new->mode = WINDOW_TRANS;

  new->prev_trans = NULL;

  new->left_width = 0;
  new->right_width = 0;
  new->top_width = 0;
  new->bottom_width = 0;

  new->client_win = 0;

  new->flags = 0;

  calc_win_size(dpy, new);

  new->next = *p;
  *p = new;

  if (new->a.map_state == IsViewable) {
    map_win(dpy, id, new->damage_sequence - 1, True, override_redirect);
  }
}

static void
restack_win(Display *dpy, win *w, Window new_above) {
  Window old_above;

  if (w->next) {
    old_above = w->next->id;
  } else {
    old_above = None;
  }

  if (old_above != new_above) {
    win **prev;

    /* unhook */
    for (prev = &list; *prev; prev = &(*prev)->next) {
      if ((*prev) == w) break;
    }

    *prev = w->next;

    /* rehook */
    for (prev = &list; *prev; prev = &(*prev)->next) {
      if ((*prev)->id == new_above && !(*prev)->destroyed)
        break;
    }

    w->next = *prev;
    *prev = w;

#ifdef DEBUG_RESTACK
    {
      const char *desc;
      char *window_name;
      Bool to_free;
      win* c = list;

      printf("restack_win(%#010lx, %#010lx): "
             "Window stack modified. Current stack:\n", w->id, new_above);

      for (; c; c = c->next) {
        window_name = "(Failed to get title)";

        if (root == c->id) {
          window_name = "(Root window)";
        } else {
          to_free = wid_get_name(dpy, c->id, &window_name);
        }

        desc = "";
        if (c->destroyed) desc = "(D) ";
        printf("%#010lx \"%s\" %s-> ", c->id, window_name, desc);

        if (to_free) {
          XFree(window_name);
          window_name = NULL;
        }
      }
      fputs("\n", stdout);
    }
#endif
  }
}

static void
configure_win(Display *dpy, XConfigureEvent *ce) {
  if (ce->window == root) {
    if (tgt_buffer) {
      XRenderFreePicture(dpy, tgt_buffer);
      tgt_buffer = None;
    }
    root_width = ce->width;
    root_height = ce->height;

    return;
  }

  win *w = find_win(dpy, 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(dpy, w, ce->above);
  } else {
    if (!(w->need_configure)) {
      restack_win(dpy, w, ce->above);
    }

    // Windows restack (including window restacks happened when this
    // window is not mapped) could mess up all reg_ignore
    reg_ignore_expire = True;

    w->need_configure = False;

    damage = XFixesCreateRegion(dpy, 0, 0);
    if (w->extents != None) {
      XFixesCopyRegion(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) {
      free_region(dpy, &w->extents);
      free_region(dpy, &w->border_size);
    }

    w->a.x = ce->x;
    w->a.y = ce->y;

    if (w->a.width != ce->width || w->a.height != ce->height) {
      free_pixmap(dpy, &w->pixmap);
      free_picture(dpy, &w->picture);
    }

    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(dpy, w);

      // Rounded corner detection is affected by window size
      if (shape_exists && opts.shadow_ignore_shaped
          && opts.detect_rounded_corners && w->bounding_shaped)
        win_update_shape(dpy, w);
    }

    if (w->a.map_state != IsUnmapped && damage) {
      XserverRegion extents = win_extents(dpy, w);
      XFixesUnionRegion(dpy, damage, damage, extents);
      XFixesDestroyRegion(dpy, extents);
      add_damage(dpy, damage);
    }
  }

  w->a.override_redirect = ce->override_redirect;
}

static void
circulate_win(Display *dpy, XCirculateEvent *ce) {
  win *w = find_win(dpy, ce->window);
  Window new_above;

  if (!w) return;

  if (ce->place == PlaceOnTop) {
    new_above = list->id;
  } else {
    new_above = None;
  }

  restack_win(dpy, w, new_above);
}

static void
finish_destroy_win(Display *dpy, Window id) {
  win **prev, *w;

  for (prev = &list; (w = *prev); prev = &w->next) {
    if (w->id == id && w->destroyed) {
      finish_unmap_win(dpy, w);
      *prev = w->next;

      free_picture(dpy, &w->shadow_pict);
      free_damage(dpy, &w->damage);
      free_region(dpy, &w->reg_ignore);
      free(w->name);
      free(w->class_instance);
      free(w->class_general);

      free(w);
      break;
    }
  }
}

static void
destroy_callback(Display *dpy, win *w) {
  finish_destroy_win(dpy, w->id);
}

static void
destroy_win(Display *dpy, Window id, Bool fade) {
  win *w = find_win(dpy, id);

  if (w) {
    w->destroyed = True;

    // Fading out the window
    w->opacity_tgt = 0;
    set_fade_callback(dpy, w, destroy_callback, False);
  }
}

static inline void
root_damaged(void) {
  if (root_tile) {
    XClearArea(dpy, root, 0, 0, 0, 0, True);
    // if (root_picture != root_tile) {
      XRenderFreePicture(dpy, root_tile);
      root_tile = None;
    /* }
    if (root_damage) {
      XserverRegion parts = XFixesCreateRegion(dpy, 0, 0);
      XDamageSubtract(dpy, root_damage, None, parts);
      add_damage(dpy, parts);
    } */
  }
  // Mark screen damaged if we are painting on overlay
  if (opts.paint_on_overlay)
    add_damage(dpy, get_screen_region(dpy));
}

static void
damage_win(Display *dpy, XDamageNotifyEvent *de) {
  /*
  if (root == de->drawable) {
    root_damaged();
    return;
  } */

  win *w = find_win(dpy, de->drawable);

  if (!w) return;

  repair_win(dpy, w);
}

static int
error(Display *dpy, XErrorEvent *ev) {
  int o;
  const char *name = "Unknown";

  if (should_ignore(dpy, ev->serial)) {
    return 0;
  }

  if (ev->request_code == composite_opcode
      && ev->minor_code == X_CompositeRedirectSubwindows) {
    fprintf(stderr, "Another composite manager is already running\n");
    exit(1);
  }

  o = ev->error_code - xfixes_error;
  switch (o) {
    case BadRegion:
      name = "BadRegion";
      break;
    default:
      break;
  }

  o = ev->error_code - damage_error;
  switch (o) {
    case BadDamage:
      name = "BadDamage";
      break;
    default:
      break;
  }

  o = ev->error_code - render_error;
  switch (o) {
    case BadPictFormat:
      name = "BadPictFormat";
      break;
    case BadPicture:
      name = "BadPicture";
      break;
    case BadPictOp:
      name = "BadPictOp";
      break;
    case BadGlyphSet:
      name = "BadGlyphSet";
      break;
    case BadGlyph:
      name = "BadGlyph";
      break;
    default:
      break;
  }

  switch (ev->error_code) {
    case BadAccess:
      name = "BadAccess";
      break;
    case BadAlloc:
      name = "BadAlloc";
      break;
    case BadAtom:
      name = "BadAtom";
      break;
    case BadColor:
      name = "BadColor";
      break;
    case BadCursor:
      name = "BadCursor";
      break;
    case BadDrawable:
      name = "BadDrawable";
      break;
    case BadFont:
      name = "BadFont";
      break;
    case BadGC:
      name = "BadGC";
      break;
    case BadIDChoice:
      name = "BadIDChoice";
      break;
    case BadImplementation:
      name = "BadImplementation";
      break;
    case BadLength:
      name = "BadLength";
      break;
    case BadMatch:
      name = "BadMatch";
      break;
    case BadName:
      name = "BadName";
      break;
    case BadPixmap:
      name = "BadPixmap";
      break;
    case BadRequest:
      name = "BadRequest";
      break;
    case BadValue:
      name = "BadValue";
      break;
    case BadWindow:
      name = "BadWindow";
      break;
  }

  print_timestamp();
  printf("error %d (%s) request %d minor %d serial %lu\n",
    ev->error_code, name, ev->request_code,
    ev->minor_code, ev->serial);

  return 0;
}

static void
expose_root(Display *dpy, XRectangle *rects, int nrects) {
  XserverRegion region = XFixesCreateRegion(dpy, rects, nrects);
  add_damage(dpy, region);
}

static Bool
wid_get_text_prop(Display *dpy, Window wid, Atom prop,
    char ***pstrlst, int *pnstr) {
  XTextProperty text_prop;

  if (!(XGetTextProperty(dpy, wid, &text_prop, prop) && text_prop.value))
    return False;

  if (Success !=
      XmbTextPropertyToTextList(dpy, &text_prop, pstrlst, pnstr)
      || !*pnstr) {
    *pnstr = 0;
    if (*pstrlst)
      XFreeStringList(*pstrlst);
    return False;
  }

  return True;
}

static Bool
wid_get_name(Display *dpy, Window wid, char **name) {
  XTextProperty text_prop;
  char **strlst = NULL;
  int nstr = 0;

  // set_ignore(dpy, NextRequest(dpy));
  if (!(XGetTextProperty(dpy, wid, &text_prop, name_ewmh_atom)
      && text_prop.value)) {
    // set_ignore(dpy, NextRequest(dpy));
#ifdef DEBUG_WINDATA
    printf("wid_get_name(%#010lx): _NET_WM_NAME unset, falling back to WM_NAME.\n", wid);
#endif

    if (!(XGetWMName(dpy, wid, &text_prop) && text_prop.value)) {
      return False;
    }
  }
  if (Success !=
      XmbTextPropertyToTextList(dpy, &text_prop, &strlst, &nstr)
      || !nstr || !strlst) {
    if (strlst)
      XFreeStringList(strlst);
    return False;
  }
  *name = mstrcpy(strlst[0]);

  XFreeStringList(strlst);

  return True;
}

static int
win_get_name(Display *dpy, win *w) {
  Bool ret;
  char *name_old = w->name;

  // Can't do anything if there's no client window
  if (!w->client_win)
    return False;

  // Get the name
  ret = wid_get_name(dpy, w->client_win, &w->name);

  // Return -1 if wid_get_name() failed, 0 if name didn't change, 1 if
  // it changes
  if (!ret)
    ret = -1;
  else if (name_old && !strcmp(w->name, name_old))
    ret = 0;
  else
    ret = 1;

  // Keep the old name if there's no new one
  if (w->name != name_old)
    free(name_old);

#ifdef DEBUG_WINDATA
  printf("win_get_name(%#010lx): client = %#010lx, name = \"%s\", "
      "ret = %d\n", w->id, w->client_win, w->name, ret);
#endif

  return ret;
}

static Bool
win_get_class(Display *dpy, 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(dpy, w->client_win, class_atom, &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("win_get_class(%#010lx): client = %#010lx, "
      "instance = \"%s\", general = \"%s\"\n",
      w->id, w->client_win, w->class_instance, w->class_general);
#endif

  return True;
}

#ifdef DEBUG_EVENTS
static int
ev_serial(XEvent *ev) {
  if ((ev->type & 0x7f) != KeymapNotify) {
    return ev->xany.serial;
  }
  return NextRequest(ev->xany.display);
}

static char *
ev_name(XEvent *ev) {
  static char buf[128];
  switch (ev->type & 0x7f) {
    case FocusIn:
      return "FocusIn";
    case FocusOut:
      return "FocusOut";
    case CreateNotify:
      return "CreateNotify";
    case ConfigureNotify:
      return "ConfigureNotify";
    case DestroyNotify:
      return "DestroyNotify";
    case MapNotify:
      return "Map";
    case UnmapNotify:
      return "Unmap";
    case ReparentNotify:
      return "Reparent";
    case CirculateNotify:
      return "Circulate";
    case Expose:
      return "Expose";
    case PropertyNotify:
      return "PropertyNotify";
    case ClientMessage:
      return "ClientMessage";
    default:
      if (ev->type == damage_event + XDamageNotify) {
        return "Damage";
      }

      if (shape_exists && ev->type == shape_event) {
        return "ShapeNotify";
      }

      sprintf(buf, "Event %d", ev->type);

      return buf;
  }
}

static Window
ev_window(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 (ev->type == damage_event + XDamageNotify) {
        return ((XDamageNotifyEvent *)ev)->drawable;
      }

      if (shape_exists && ev->type == shape_event) {
        return ((XShapeEvent *) ev)->window;
      }

      return 0;
  }
}
#endif

/**
 * Events
 */

inline static void
ev_focus_in(XFocusChangeEvent *ev) {
  win *w = find_win(dpy, ev->window);

  // To deal with events sent from windows just destroyed
  if (!w) return;

  set_focused(dpy, w, True);
}

inline static void
ev_focus_out(XFocusChangeEvent *ev) {
  if (ev->mode == NotifyGrab
      || (ev->mode == NotifyNormal
      && (ev->detail == NotifyNonlinear
      || ev->detail == NotifyNonlinearVirtual))) {
    ;
  } else {
    return;
  }

  win *w = find_win(dpy, ev->window);

  // To deal with events sent from windows just destroyed
  if (!w) return;

  set_focused(dpy, w, False);
}

inline static void
ev_create_notify(XCreateWindowEvent *ev) {
  add_win(dpy, ev->window, 0, ev->override_redirect);
}

inline static void
ev_configure_notify(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(dpy, ev);
}

inline static void
ev_destroy_notify(XDestroyWindowEvent *ev) {
  destroy_win(dpy, ev->window, True);
}

inline static void
ev_map_notify(XMapEvent *ev) {
  map_win(dpy, ev->window, ev->serial, True, ev->override_redirect);
}

inline static void
ev_unmap_notify(XUnmapEvent *ev) {
  unmap_win(dpy, ev->window, True);
}

inline static void
ev_reparent_notify(XReparentEvent *ev) {
  if (ev->parent == root) {
    add_win(dpy, ev->window, 0, ev->override_redirect);
  } else {
    destroy_win(dpy, ev->window, True);
    // Reset event mask in case something wrong happens
    XSelectInput(dpy, ev->window,
        determine_evmask(dpy, ev->window, WIN_EVMODE_UNKNOWN));
    /*
    // Check if the window is a client window of another
    win *w_top = find_toplevel2(dpy, ev->window);
    if (w_top && !(w_top->client_win)) {
      mark_client_win(dpy, w_top, ev->window);
    } */
  }
}

inline static void
ev_circulate_notify(XCirculateEvent *ev) {
  circulate_win(dpy, ev);
}

inline static void
ev_expose(XExposeEvent *ev) {
  if (ev->window == root || (overlay && ev->window == overlay)) {
    int more = ev->count + 1;
    if (n_expose == size_expose) {
      if (expose_rects) {
        expose_rects = realloc(expose_rects,
          (size_expose + more) * sizeof(XRectangle));
        size_expose += more;
      } else {
        expose_rects = malloc(more * sizeof(XRectangle));
        size_expose = more;
      }
    }

    expose_rects[n_expose].x = ev->x;
    expose_rects[n_expose].y = ev->y;
    expose_rects[n_expose].width = ev->width;
    expose_rects[n_expose].height = ev->height;
    n_expose++;

    if (ev->count == 0) {
      expose_root(dpy, expose_rects, n_expose);
      n_expose = 0;
    }
  }
}

inline static void
ev_property_notify(XPropertyEvent *ev) {
  // Destroy the root "image" if the wallpaper probably changed
  if (root == ev->window) {
    for (int p = 0; background_props[p]; p++) {
      if (ev->atom == XInternAtom(dpy, background_props[p], False)) {
        root_damaged();
        break;
      }
    }
    // Unconcerned about any other proprties on root window
    return;
  }

  // If _NET_WM_OPACITY changes
  if (ev->atom == opacity_atom) {
    win *w = NULL;
    if ((w = find_win(dpy, ev->window)))
      w->opacity_prop = wid_get_opacity_prop(dpy, w->id, OPAQUE);
    else if (opts.detect_client_opacity
        && (w = find_toplevel(dpy, ev->window)))
      w->opacity_prop_client = wid_get_opacity_prop(dpy, w->client_win,
            OPAQUE);
    if (w) {
      calc_opacity(dpy, w, False);
    }
  }

  // If frame extents property changes
  if (opts.frame_opacity && ev->atom == extents_atom) {
    win *w = find_toplevel(dpy, ev->window);
    if (w) {
      get_frame_extents(dpy, w, ev->window);
      // If frame extents change, the window needs repaint
      add_damage_win(dpy, w);
    }
  }

  // If name changes
  if (opts.track_wdata
      && (name_atom == ev->atom || name_ewmh_atom == ev->atom)) {
    win *w = find_toplevel(dpy, ev->window);
    if (w && 1 == win_get_name(dpy, w))
      determine_shadow(dpy, w);
  }

  // If class changes
  if (opts.track_wdata && class_atom == ev->atom) {
    win *w = find_toplevel(dpy, ev->window);
    if (w) {
      win_get_class(dpy, w);
      determine_shadow(dpy, w);
    }
  }
}

inline static void
ev_damage_notify(XDamageNotifyEvent *ev) {
  damage_win(dpy, ev);
}

inline static void
ev_shape_notify(XShapeEvent *ev) {
  win *w = find_win(dpy, 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(dpy, w->border_size);

    w->border_size = border_size(dpy, w);

    // Mark the new border_size as damaged
    add_damage(dpy, copy_region(dpy, w->border_size));
  }

  // Redo bounding shape detection and rounded corner detection
  win_update_shape(dpy, w);
}

/**
 * Handle ScreenChangeNotify events from X RandR extension.
 */
static void
ev_screen_change_notify(XRRScreenChangeNotifyEvent *ev) {
  if (!opts.refresh_rate) {
    update_refresh_rate(dpy);
    if (!refresh_rate) {
      fprintf(stderr, "ev_screen_change_notify(): Refresh rate detection "
          "failed, software VSync disabled.");
      opts.vsync = VSYNC_NONE;
    }
  }
}

static void
ev_handle(XEvent *ev) {
  if ((ev->type & 0x7f) != KeymapNotify) {
    discard_ignore(dpy, ev->xany.serial);
  }

#ifdef DEBUG_EVENTS
  if (ev->type != damage_event + XDamageNotify) {
    Window wid;
    char *window_name;
    Bool to_free = False;

    wid = ev_window(ev);
    window_name = "(Failed to get title)";

    if (wid) {
      if (root == wid)
        window_name = "(Root window)";
      else if (overlay == wid)
        window_name = "(Overlay)";
      else {
        win *w = find_win(dpy, wid);
        if (!w)
          w = find_toplevel(dpy, wid);

        if (w && w->name)
          window_name = w->name;
        else
          to_free = (Bool) wid_get_name(dpy, wid, &window_name);
      }
    }

    print_timestamp();
    printf("event %10.10s serial %#010x window %#010lx \"%s\"\n",
      ev_name(ev), ev_serial(ev), wid, window_name);

    if (to_free) {
      XFree(window_name);
      window_name = NULL;
    }
  }

#endif

  switch (ev->type) {
    case FocusIn:
      ev_focus_in((XFocusChangeEvent *)ev);
      break;
    case FocusOut:
      ev_focus_out((XFocusChangeEvent *)ev);
      break;
    case CreateNotify:
      ev_create_notify((XCreateWindowEvent *)ev);
      break;
    case ConfigureNotify:
      ev_configure_notify((XConfigureEvent *)ev);
      break;
    case DestroyNotify:
      ev_destroy_notify((XDestroyWindowEvent *)ev);
      break;
    case MapNotify:
      ev_map_notify((XMapEvent *)ev);
      break;
    case UnmapNotify:
      ev_unmap_notify((XUnmapEvent *)ev);
      break;
    case ReparentNotify:
      ev_reparent_notify((XReparentEvent *)ev);
      break;
    case CirculateNotify:
      ev_circulate_notify((XCirculateEvent *)ev);
      break;
    case Expose:
      ev_expose((XExposeEvent *)ev);
      break;
    case PropertyNotify:
      ev_property_notify((XPropertyEvent *)ev);
      break;
    default:
      if (shape_exists && ev->type == shape_event) {
        ev_shape_notify((XShapeEvent *) ev);
        break;
      }
      if (randr_exists && ev->type == (randr_event + RRScreenChangeNotify)) {
        ev_screen_change_notify((XRRScreenChangeNotifyEvent *) ev);
        break;
      }
      if (ev->type == damage_event + XDamageNotify) {
        ev_damage_notify((XDamageNotifyEvent *)ev);
      }
      break;
  }
}

/**
 * Main
 */

/**
 * Print usage text and exit.
 */
static void
usage(void) {
  fputs(
    "compton (development 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"
    "--config path\n"
    "  Look for configuration file at the path.\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"
    "--mark-wmwin-focused\n"
    "  Try to detect WM windows and mark them as active.\n"
    "--shadow-exclude condition\n"
    "  Exclude conditions for shadows.\n"
    "--mark-ovredir-focused\n"
    "  Mark over-redirect windows as active.\n"
    "--no-fading-openclose\n"
    "  Do not fade on window open/close.\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"
    "\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 2 VSync methods currently available:\n"
    "    none = No VSync\n"
    "    drm = VSync with DRM_IOCTL_WAIT_VBLANK. May only work on some\n"
    "      drivers. Experimental.\n"
    "    opengl = Try to VSync with SGI_swap_control OpenGL extension. Only\n"
    "      work on some drivers. Experimental.\n"
    "  (Note some VSync methods may not be enabled at compile time.)\n"
    "--alpha-step val\n"
    "  Step for pregenerating alpha pictures. 0.01 - 1.0. Defaults to\n"
    "  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. Experimental.\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"
    "\n"
    "Format of a condition:\n"
    "\n"
    "  condition = <target>:<type>[<flags>]:<pattern>\n"
    "\n"
    "  <target> is one of \"n\" (window name), \"i\" (window class\n"
    "  instance), and \"g\" (window general class)\n"
    "\n"
    "  <type> is one of \"e\" (exact match), \"a\" (match anywhere),\n"
    "  \"s\" (match from start), \"w\" (wildcard), and \"p\" (PCRE\n"
    "  regular expressions, if compiled with the support).\n"
    "\n"
    "  <flags> could be a series of flags. Currently the only defined\n"
    "  flag is \"i\" (ignore case).\n"
    "\n"
    "  <pattern> is the actual pattern string.\n"
    , stderr);

  exit(1);
}

/**
 * Register a window as symbol, and initialize GLX context if wanted.
 */
static void
register_cm(Bool want_glxct) {
  Atom a;
  char *buf;
  int len, s;

#ifdef CONFIG_VSYNC_OPENGL
  // Create a window with the wanted GLX visual
  if (want_glxct) {
    XVisualInfo *pvi = NULL;
    Bool ret = False;
    // Get visual for the window
    int attribs[] = { GLX_RGBA, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1, None };
    pvi = glXChooseVisual(dpy, scr, attribs);

    if (!pvi) {
      fprintf(stderr, "register_cm(): Failed to choose visual required "
          "by fake OpenGL VSync window. OpenGL VSync turned off.\n");
    }
    else {
      // Create the window
      XSetWindowAttributes swa = {
        .colormap = XCreateColormap(dpy, root, pvi->visual, AllocNone),
        .border_pixel = 0,
      };

      pvi->screen = scr;
      reg_win = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, pvi->depth,
          InputOutput, pvi->visual, CWBorderPixel | CWColormap, &swa);

      if (!reg_win)
        fprintf(stderr, "register_cm(): Failed to create window required "
            "by fake OpenGL VSync. OpenGL VSync turned off.\n");
      else {
        // Get GLX context
        glx_context = glXCreateContext(dpy, pvi, None, GL_TRUE);
        if (!glx_context) {
          fprintf(stderr, "register_cm(): Failed to get GLX context. "
              "OpenGL VSync turned off.\n");
          opts.vsync = VSYNC_NONE;
        }
        else {
          // Attach GLX context
          if (!(ret = glXMakeCurrent(dpy, reg_win, glx_context)))
            fprintf(stderr, "register_cm(): Failed to attach GLX context."
              " OpenGL VSync turned off.\n");
        }
      }
    }
    if (pvi)
      XFree(pvi);

    if (!ret)
      opts.vsync = VSYNC_NONE;
  }
#endif
  
  if (!reg_win)
    reg_win = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0,
        None, None);

  Xutf8SetWMProperties(
    dpy, reg_win, "xcompmgr", "xcompmgr",
    NULL, 0, NULL, NULL, NULL);

  len = strlen(REGISTER_PROP) + 2;
  s = scr;

  while (s >= 10) {
    ++len;
    s /= 10;
  }

  buf = malloc(len);
  snprintf(buf, len, REGISTER_PROP"%d", scr);

  a = XInternAtom(dpy, buf, False);
  free(buf);

  XSetSelectionOwner(dpy, a, reg_win, 0);
}

static void
fork_after(void) {
  if (getppid() == 1) return;

  int pid = fork();

  if (pid == -1) {
    fprintf(stderr, "Fork failed\n");
    return;
  }

  if (pid > 0) _exit(0);

  setsid();

  freopen("/dev/null", "r", stdin);
  freopen("/dev/null", "w", stdout);
  freopen("/dev/null", "w", stderr);
}

#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.conf";
  const static char *config_filename_legacy = "/.compton.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;
    else
      free(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 VSync option argument.
 */
static inline void
parse_vsync(const char *optarg) {
  const static char * const vsync_str[] = {
    "none",   // VSYNC_NONE
    "drm",    // VSYNC_DRM
    "opengl", // VSYNC_OPENGL
  };

  vsync_t i;

  for (i = 0; i < (sizeof(vsync_str) / sizeof(vsync_str[0])); ++i)
    if (!strcasecmp(optarg, vsync_str[i])) {
      opts.vsync = i;
      break;
    }
  if ((sizeof(vsync_str) / sizeof(vsync_str[0])) == i) {
    fputs("Invalid --vsync argument. Ignored.\n", stderr);
  }
}

/**
 * Parse a configuration file from default location.
 */
static void
parse_config(char *cpath, struct options_tmp *pcfgtmp) {
  char *path = NULL;
  FILE *f;
  config_t cfg;
  int ival = 0;
  double dval = 0.0;
  const char *sval = NULL;

  f = open_config_file(cpath, &path);
  if (!f) {
    if (cpath)
      printf("Failed to read the specified configuration file.\n");
    return;
  }

  config_init(&cfg);
#ifndef CONFIG_LIBCONFIG_LEGACY
  char *parent = dirname(path);
  if (parent)
    config_set_include_dir(&cfg, parent);
#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);

  free(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))
    opts.fade_delta = ival;
  // -I (fade_in_step)
  if (config_lookup_float(&cfg, "fade-in-step", &dval))
    opts.fade_in_step = normalize_d(dval) * OPAQUE;
  // -O (fade_out_step)
  if (config_lookup_float(&cfg, "fade-out-step", &dval))
    opts.fade_out_step = normalize_d(dval) * OPAQUE;
  // -r (shadow_radius)
  lcfg_lookup_int(&cfg, "shadow-radius", &opts.shadow_radius);
  // -o (shadow_opacity)
  config_lookup_float(&cfg, "shadow-opacity", &opts.shadow_opacity);
  // -l (shadow_offset_x)
  lcfg_lookup_int(&cfg, "shadow-offset-x", &opts.shadow_offset_x);
  // -t (shadow_offset_y)
  lcfg_lookup_int(&cfg, "shadow-offset-y", &opts.shadow_offset_y);
  // -i (inactive_opacity)
  if (config_lookup_float(&cfg, "inactive-opacity", &dval))
    opts.inactive_opacity = normalize_d(dval) * OPAQUE;
  // -e (frame_opacity)
  config_lookup_float(&cfg, "frame-opacity", &opts.frame_opacity);
  // -z (clear_shadow)
  lcfg_lookup_bool(&cfg, "clear-shadow", &opts.clear_shadow);
  // -c (shadow_enable)
  if (config_lookup_bool(&cfg, "shadow", &ival) && ival)
    wintype_arr_enable(opts.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(opts.wintype_fade);
  // --no-fading-open-close
  lcfg_lookup_bool(&cfg, "no-fading-openclose", &opts.no_fading_openclose);
  // --shadow-red
  config_lookup_float(&cfg, "shadow-red", &opts.shadow_red);
  // --shadow-green
  config_lookup_float(&cfg, "shadow-green", &opts.shadow_green);
  // --shadow-blue
  config_lookup_float(&cfg, "shadow-blue", &opts.shadow_blue);
  // --inactive-opacity-override
  lcfg_lookup_bool(&cfg, "inactive-opacity-override",
      &opts.inactive_opacity_override);
  // --inactive-dim
  config_lookup_float(&cfg, "inactive-dim", &opts.inactive_dim);
  // --mark-wmwin-focused
  lcfg_lookup_bool(&cfg, "mark-wmwin-focused", &opts.mark_wmwin_focused);
  // --mark-ovredir-focused
  lcfg_lookup_bool(&cfg, "mark-ovredir-focused",
      &opts.mark_ovredir_focused);
  // --shadow-ignore-shaped
  lcfg_lookup_bool(&cfg, "shadow-ignore-shaped",
      &opts.shadow_ignore_shaped);
  // --detect-rounded-corners
  lcfg_lookup_bool(&cfg, "detect-rounded-corners",
      &opts.detect_rounded_corners);
  // --detect-client-opacity
  lcfg_lookup_bool(&cfg, "detect-client-opacity",
      &opts.detect_client_opacity);
  // --refresh-rate
  lcfg_lookup_int(&cfg, "refresh-rate", &opts.refresh_rate);
  // --vsync
  if (config_lookup_string(&cfg, "vsync", &sval))
    parse_vsync(sval);
  // --alpha-step
  config_lookup_float(&cfg, "alpha-step", &opts.alpha_step);
  // --dbe
  lcfg_lookup_bool(&cfg, "dbe", &opts.dbe);
  // --paint-on-overlay
  lcfg_lookup_bool(&cfg, "paint-on-overlay", &opts.paint_on_overlay);
  // --sw-opti
  lcfg_lookup_bool(&cfg, "sw-opti", &opts.sw_opti);
  // --shadow-exclude
  {
    config_setting_t *setting =
      config_lookup(&cfg, "shadow-exclude");
    if (setting) {
      // Parse an array of shadow-exclude
      if (config_setting_is_array(setting)) {
        int i = config_setting_length(setting);
        while (i--) {
          condlst_add(&opts.shadow_blacklist,
              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(&opts.shadow_blacklist,
            config_setting_get_string(setting));
      }
    }
  }
  // Wintype settings
  {
    wintype 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))
          opts.wintype_shadow[i] = (Bool) ival;
        if (config_setting_lookup_bool(setting, "fade", &ival))
          opts.wintype_fade[i] = (Bool) ival;
        config_setting_lookup_float(setting, "opacity",
            &opts.wintype_opacity[i]);
      }
    }
  }

  config_destroy(&cfg);
}
#endif

/**
 * Process arguments and configuration files.
 */
static void
get_cfg(int argc, char *const *argv) {
  const static char *shortopts = "D:I:O:d:r:o:m:l:t:i:e:scnfFCaSzGb";
  const static struct option longopts[] = {
    { "config", required_argument, NULL, 256 },
    { "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 },
    // Must terminate with a NULL entry
    { NULL, 0, NULL, 0 },
  };

  struct options_tmp cfgtmp = {
    .no_dock_shadow = False,
    .no_dnd_shadow = False,
    .menu_opacity = 1.0,
  };
  Bool shadow_enable = False, fading_enable = False;
  int o, longopt_idx, i;
  char *config_file = NULL;
  char *lc_numeric_old = mstrcpy(setlocale(LC_NUMERIC, NULL));

  for (i = 0; i < NUM_WINTYPES; ++i) {
    opts.wintype_fade[i] = False;
    opts.wintype_shadow[i] = False;
    opts.wintype_opacity[i] = 1.0;
  }

  // Pre-parse the commandline arguments to check for --config and invalid
  // switches
  while (-1 !=
      (o = getopt_long(argc, argv, shortopts, longopts, &longopt_idx))) {
    if (256 == o)
      config_file = mstrcpy(optarg);
    else if ('?' == o || ':' == o)
      usage();
  }

#ifdef CONFIG_LIBCONFIG
  parse_config(config_file, &cfgtmp);
#endif

  // Parse commandline arguments. Range checking will be done later.

  // Enforce LC_NUMERIC locale "C" here to make sure dots are recognized
  // instead of commas in atof().
  setlocale(LC_NUMERIC, "C");

  optind = 1;
  while (-1 !=
      (o = getopt_long(argc, argv, shortopts, longopts, &longopt_idx))) {
    switch (o) {
      // Short options
      case 'd':
        opts.display = optarg;
        break;
      case 'D':
        opts.fade_delta = atoi(optarg);
        break;
      case 'I':
        opts.fade_in_step = normalize_d(atof(optarg)) * OPAQUE;
        break;
      case 'O':
        opts.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;
      case 'S':
        opts.synchronize = True;
        break;
      case 'r':
        opts.shadow_radius = atoi(optarg);
        break;
      case 'o':
        opts.shadow_opacity = atof(optarg);
        break;
      case 'l':
        opts.shadow_offset_x = atoi(optarg);
        break;
      case 't':
        opts.shadow_offset_y = atoi(optarg);
        break;
      case 'i':
        opts.inactive_opacity = (normalize_d(atof(optarg)) * OPAQUE);
        break;
      case 'e':
        opts.frame_opacity = atof(optarg);
        break;
      case 'z':
        opts.clear_shadow = True;
        break;
      case 'n':
      case 'a':
      case 's':
        fprintf(stderr, "Warning: "
          "-n, -a, and -s have been removed.\n");
        break;
      case 'b':
        opts.fork_after_register = True;
        break;
      // Long options
      case 256:
        // --config
        break;
      case 257:
        // --shadow-red
        opts.shadow_red = atof(optarg);
        break;
      case 258:
        // --shadow-green
        opts.shadow_green = atof(optarg);
        break;
      case 259:
        // --shadow-blue
        opts.shadow_blue = atof(optarg);
        break;
      case 260:
        // --inactive-opacity-override
        opts.inactive_opacity_override = True;
        break;
      case 261:
        // --inactive-dim
        opts.inactive_dim = atof(optarg);
        break;
      case 262:
        // --mark-wmwin-focused
        opts.mark_wmwin_focused = True;
        break;
      case 263:
        // --shadow-exclude
        condlst_add(&opts.shadow_blacklist, optarg);
        break;
      case 264:
        // --mark-ovredir-focused
        opts.mark_ovredir_focused = True;
        break;
      case 265:
        // --no-fading-openclose
        opts.no_fading_openclose = True;
        break;
      case 266:
        // --shadow-ignore-shaped
        opts.shadow_ignore_shaped = True;
        break;
      case 267:
        // --detect-rounded-corners
        opts.detect_rounded_corners = True;
        break;
      case 268:
        // --detect-client-opacity
        opts.detect_client_opacity = True;
        break;
      case 269:
        // --refresh-rate
        opts.refresh_rate = atoi(optarg);
        break;
      case 270:
        // --vsync
        parse_vsync(optarg);
        break;
      case 271:
        // --alpha-step
        opts.alpha_step = atof(optarg);
        break;
      case 272:
        // --dbe
        opts.dbe = True;
        break;
      case 273:
        // --paint-on-overlay
        opts.paint_on_overlay = True;
        break;
      case 274:
        // --sw-opti
        opts.sw_opti = True;
        break;
      case 275:
        // --vsync-aggressive
        opts.vsync_aggressive = True;
        break;
      default:
        usage();
        break;
    }
  }

  // Restore LC_NUMERIC
  setlocale(LC_NUMERIC, lc_numeric_old);
  free(lc_numeric_old);

  // Range checking and option assignments
  opts.fade_delta = max_i(opts.fade_delta, 1);
  opts.shadow_radius = max_i(opts.shadow_radius, 1);
  opts.shadow_red = normalize_d(opts.shadow_red);
  opts.shadow_green = normalize_d(opts.shadow_green);
  opts.shadow_blue = normalize_d(opts.shadow_blue);
  opts.inactive_dim = normalize_d(opts.inactive_dim);
  opts.frame_opacity = normalize_d(opts.frame_opacity);
  opts.shadow_opacity = normalize_d(opts.shadow_opacity);
  cfgtmp.menu_opacity = normalize_d(cfgtmp.menu_opacity);
  opts.refresh_rate = normalize_i_range(opts.refresh_rate, 0, 300);
  opts.alpha_step = normalize_d_range(opts.alpha_step, 0.01, 1.0);
  if (OPAQUE == opts.inactive_opacity) {
    opts.inactive_opacity = 0;
  }
  if (shadow_enable)
    wintype_arr_enable(opts.wintype_shadow);
  opts.wintype_shadow[WINTYPE_DESKTOP] = False;
  if (cfgtmp.no_dock_shadow)
    opts.wintype_shadow[WINTYPE_DOCK] = False;
  if (cfgtmp.no_dnd_shadow)
    opts.wintype_shadow[WINTYPE_DND] = False;
  if (fading_enable)
    wintype_arr_enable(opts.wintype_fade);
  if (1.0 != cfgtmp.menu_opacity) {
    opts.wintype_opacity[WINTYPE_DROPDOWN_MENU] = cfgtmp.menu_opacity;
    opts.wintype_opacity[WINTYPE_POPUP_MENU] = cfgtmp.menu_opacity;
  }

  // Other variables determined by options

  // Determine whether we need to track focus changes
  if (opts.inactive_opacity || opts.inactive_dim) {
    opts.track_focus = True;
  }

  // Determine whether we need to track window name and class
  if (opts.shadow_blacklist || opts.fade_blacklist)
    opts.track_wdata = True;
}

static void
get_atoms(void) {
  extents_atom = XInternAtom(dpy, "_NET_FRAME_EXTENTS", False);
  opacity_atom = XInternAtom(dpy, "_NET_WM_WINDOW_OPACITY", False);
  frame_extents_atom = XInternAtom(dpy, "_NET_FRAME_EXTENTS", False);
  client_atom = XInternAtom(dpy, "WM_STATE", False);
  name_atom = XA_WM_NAME;
  name_ewmh_atom = XInternAtom(dpy, "_NET_WM_NAME", False);
  class_atom = XA_WM_CLASS;
  transient_atom = XA_WM_TRANSIENT_FOR;

  win_type_atom = XInternAtom(dpy,
    "_NET_WM_WINDOW_TYPE", False);
  win_type[WINTYPE_UNKNOWN] = 0;
  win_type[WINTYPE_DESKTOP] = XInternAtom(dpy,
    "_NET_WM_WINDOW_TYPE_DESKTOP", False);
  win_type[WINTYPE_DOCK] = XInternAtom(dpy,
    "_NET_WM_WINDOW_TYPE_DOCK", False);
  win_type[WINTYPE_TOOLBAR] = XInternAtom(dpy,
    "_NET_WM_WINDOW_TYPE_TOOLBAR", False);
  win_type[WINTYPE_MENU] = XInternAtom(dpy,
    "_NET_WM_WINDOW_TYPE_MENU", False);
  win_type[WINTYPE_UTILITY] = XInternAtom(dpy,
    "_NET_WM_WINDOW_TYPE_UTILITY", False);
  win_type[WINTYPE_SPLASH] = XInternAtom(dpy,
    "_NET_WM_WINDOW_TYPE_SPLASH", False);
  win_type[WINTYPE_DIALOG] = XInternAtom(dpy,
    "_NET_WM_WINDOW_TYPE_DIALOG", False);
  win_type[WINTYPE_NORMAL] = XInternAtom(dpy,
    "_NET_WM_WINDOW_TYPE_NORMAL", False);
  win_type[WINTYPE_DROPDOWN_MENU] = XInternAtom(dpy,
    "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", False);
  win_type[WINTYPE_POPUP_MENU] = XInternAtom(dpy,
    "_NET_WM_WINDOW_TYPE_POPUP_MENU", False);
  win_type[WINTYPE_TOOLTIP] = XInternAtom(dpy,
    "_NET_WM_WINDOW_TYPE_TOOLTIP", False);
  win_type[WINTYPE_NOTIFY] = XInternAtom(dpy,
    "_NET_WM_WINDOW_TYPE_NOTIFICATION", False);
  win_type[WINTYPE_COMBO] = XInternAtom(dpy,
    "_NET_WM_WINDOW_TYPE_COMBO", False);
  win_type[WINTYPE_DND] = XInternAtom(dpy,
    "_NET_WM_WINDOW_TYPE_DND", False);
}

/**
 * Update refresh rate info with X Randr extension.
 */
static void
update_refresh_rate(Display *dpy) {
  XRRScreenConfiguration* randr_info;

  if (!(randr_info = XRRGetScreenInfo(dpy, root)))
    return;
  refresh_rate = XRRConfigCurrentRate(randr_info);

  XRRFreeScreenConfigInfo(randr_info);

  if (refresh_rate)
    refresh_intv = NS_PER_SEC / refresh_rate;
  else
    refresh_intv = 0;
}

/**
 * Initialize refresh-rated based software optimization.
 *
 * @return True for success, False otherwise
 */
static Bool
sw_opti_init(void) {
  // Prepare refresh rate
  // Check if user provides one
  refresh_rate = opts.refresh_rate;
  if (refresh_rate)
    refresh_intv = NS_PER_SEC / refresh_rate;

  // Auto-detect refresh rate otherwise
  if (!refresh_rate && randr_exists) {
    update_refresh_rate(dpy);
  }

  // Turn off vsync_sw if we can't get the refresh rate
  if (!refresh_rate)
    return False;

  // Monitor screen changes only if vsync_sw is enabled and we are using
  // an auto-detected refresh rate
  if (randr_exists && !opts.refresh_rate)
    XRRSelectInput(dpy, root, RRScreenChangeNotify);

  return True;
}

/**
 * Get the smaller number that is bigger than <code>dividend</code> and is
 * N times of <code>divisor</code>.
 */
static inline long
lceil_ntimes(long dividend, long divisor) {
  // It's possible to use the more beautiful expression here:
  // ret = ((dividend - 1) / divisor + 1) * divisor;
  // But it does not work well for negative values.
  long ret = dividend / divisor * divisor;
  if (ret < dividend)
    ret += divisor;

  return ret;
}

/**
 * Wait for events until next paint.
 *
 * Optionally use refresh-rate based optimization to reduce painting.
 *
 * @param fd struct pollfd used for poll()
 * @param timeout second timeout (fading timeout)
 * @return > 0 if we get some events, 0 if timeout is reached, < 0 on
 *     problems
 */
static int
evpoll(struct pollfd *fd, int timeout) {
  // Always wait infinitely if asked so, to minimize CPU usage
  if (timeout < 0) {
    int ret = poll(fd, 1, timeout);
    // Reset fade_time so the fading steps during idling are not counted
    fade_time = get_time_ms();
    return ret;
  }

  // Just do a poll() if we are not using optimization
  if (!opts.sw_opti)
    return poll(fd, 1, timeout);

  // Convert the old timeout to struct timespec
  struct timespec next_paint_tmout = {
    .tv_sec = timeout / MS_PER_SEC,
    .tv_nsec = timeout % MS_PER_SEC * (NS_PER_SEC / MS_PER_SEC)
  };

  // Get the nanosecond offset of the time when the we reach the timeout
  // I don't think a 32-bit long could overflow here.
  long target_relative_offset = (next_paint_tmout.tv_nsec + get_time_timespec().tv_nsec - paint_tm_offset) % NS_PER_SEC;
  if (target_relative_offset < 0)
    target_relative_offset += NS_PER_SEC;

  assert(target_relative_offset >= 0);

  // If the target time is sufficiently close to a refresh time, don't add
  // an offset, to avoid certain blocking conditions.
  if ((target_relative_offset % NS_PER_SEC) < SW_OPTI_TOLERANCE)
    return poll(fd, 1, timeout);

  // Add an offset so we wait until the next refresh after timeout
  next_paint_tmout.tv_nsec += lceil_ntimes(target_relative_offset, refresh_intv) - target_relative_offset;
  if (next_paint_tmout.tv_nsec > NS_PER_SEC) {
    next_paint_tmout.tv_nsec -= NS_PER_SEC;
    ++next_paint_tmout.tv_sec;
  }

  return ppoll(fd, 1, &next_paint_tmout, NULL);
}

/**
 * Initialize DRM VSync.
 *
 * @return True for success, False otherwise
 */
static Bool
vsync_drm_init(void) {
#ifdef CONFIG_VSYNC_DRM
  // Should we always open card0?
  if ((drm_fd = open("/dev/dri/card0", O_RDWR)) < 0) {
    fprintf(stderr, "vsync_drm_init(): Failed to open device.\n");
    return False;
  }

  if (vsync_drm_wait())
    return False;

  return True;
#else
  fprintf(stderr, "Program not compiled with DRM VSync support.\n");
  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(void) {
  int ret = -1;
  drm_wait_vblank_t vbl;

  vbl.request.type = _DRM_VBLANK_RELATIVE,
  vbl.request.sequence = 1;

  do {
     ret = ioctl(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(void) {
#ifdef CONFIG_VSYNC_OPENGL
  // Get video sync functions
  glx_get_video_sync = (f_GetVideoSync)
    glXGetProcAddress ((const GLubyte *) "glXGetVideoSyncSGI");
  glx_wait_video_sync = (f_WaitVideoSync)
    glXGetProcAddress ((const GLubyte *) "glXWaitVideoSyncSGI");
  if (!glx_wait_video_sync || !glx_get_video_sync) {
    fprintf(stderr, "vsync_opengl_init(): "
        "Failed to get glXWait/GetVideoSyncSGI function.\n");
    return False;
  }
  
  return True;
#else
  fprintf(stderr, "Program not compiled with OpenGL VSync support.\n");
  return False;
#endif
}

#ifdef CONFIG_VSYNC_OPENGL
/**
 * Wait for next VSync, OpenGL method.
 */
static void
vsync_opengl_wait(void) {
  unsigned vblank_count;

  glx_get_video_sync(&vblank_count);
  glx_wait_video_sync(2, (vblank_count + 1) % 2, &vblank_count);
  // I see some code calling glXSwapIntervalSGI(1) afterwards, is it required?
}
#endif

/**
 * Wait for next VSync.
 */
static void
vsync_wait(void) {
  if (VSYNC_NONE == opts.vsync)
    return;

#ifdef CONFIG_VSYNC_DRM
  if (VSYNC_DRM == opts.vsync) {
    vsync_drm_wait();
    return;
  }
#endif

#ifdef CONFIG_VSYNC_OPENGL
  if (VSYNC_OPENGL == opts.vsync) {
    vsync_opengl_wait();
    return;
  }
#endif

  // This place should not reached!
  assert(0);

  return;
}

/**
 * Pregenerate alpha pictures.
 */
static void
init_alpha_picts(Display *dpy) {
  int i;
  int num = lround(1.0 / opts.alpha_step) + 1;

  alpha_picts = malloc(sizeof(Picture) * num);

  for (i = 0; i < num; ++i) {
    double o = i * opts.alpha_step;
    if ((1.0 - o) > opts.alpha_step)
      alpha_picts[i] = solid_picture(dpy, False, o, 0, 0, 0);
    else
      alpha_picts[i] = None;
  }
}

/**
 * Initialize double buffer.
 */
static void
init_dbe(void) {
  if (!(root_dbe = XdbeAllocateBackBufferName(dpy,
          (opts.paint_on_overlay ? overlay: root), XdbeCopied))) {
    fprintf(stderr, "Failed to create double buffer. Double buffering "
        "turned off.\n");
    opts.dbe = False;
    return;
  }
}

/**
 * Initialize X composite overlay window.
 */
static void
init_overlay(void) {
  overlay = XCompositeGetOverlayWindow(dpy, root);
  if (overlay) {
    // Set window region of the overlay window, code stolen from
    // compiz-0.8.8
    XserverRegion region = XFixesCreateRegion (dpy, NULL, 0);
    XFixesSetWindowShapeRegion(dpy, overlay, ShapeBounding, 0, 0, 0);
    XFixesSetWindowShapeRegion(dpy, overlay, ShapeInput, 0, 0, region);
    XFixesDestroyRegion (dpy, region);

    // Listen to Expose events on the overlay
    XSelectInput(dpy, overlay, ExposureMask);

    // Retrieve DamageNotify on root window if we are painting on an
    // overlay
    // root_damage = XDamageCreate(dpy, root, XDamageReportNonEmpty);
  }
  else {
    fprintf(stderr, "Cannot get X Composite overlay window. Falling "
        "back to painting on root window.\n");
    opts.paint_on_overlay = False;
  }
}

int
main(int argc, char **argv) {
  XEvent ev;
  Window root_return, parent_return;
  Window *children;
  unsigned int nchildren;
  int i;
  XRenderPictureAttributes pa;
  struct pollfd ufd;
  int composite_major, composite_minor;
  win *t;

  gettimeofday(&time_start, NULL);

  // Set locale so window names with special characters are interpreted
  // correctly
  setlocale (LC_ALL, "");

  get_cfg(argc, argv);

  fade_time = get_time_ms();

  dpy = XOpenDisplay(opts.display);
  if (!dpy) {
    fprintf(stderr, "Can't open display\n");
    exit(1);
  }

  XSetErrorHandler(error);
  if (opts.synchronize) {
    XSynchronize(dpy, 1);
  }

  scr = DefaultScreen(dpy);
  root = RootWindow(dpy, scr);

  if (!XRenderQueryExtension(dpy, &render_event, &render_error)) {
    fprintf(stderr, "No render extension\n");
    exit(1);
  }

  if (!XQueryExtension(dpy, COMPOSITE_NAME, &composite_opcode,
                       &composite_event, &composite_error)) {
    fprintf(stderr, "No composite extension\n");
    exit(1);
  }

  XCompositeQueryVersion(dpy, &composite_major, &composite_minor);

  if (composite_major > 0 || composite_minor >= 2) {
    has_name_pixmap = True;
  }

  if (!XDamageQueryExtension(dpy, &damage_event, &damage_error)) {
    fprintf(stderr, "No damage extension\n");
    exit(1);
  }

  if (!XFixesQueryExtension(dpy, &xfixes_event, &xfixes_error)) {
    fprintf(stderr, "No XFixes extension\n");
    exit(1);
  }

  // Query X Shape
  if (XShapeQueryExtension(dpy, &shape_event, &shape_error)) {
    shape_exists = True;
  }

  // Query X RandR
  if (opts.sw_opti && !opts.refresh_rate) {
    if (XRRQueryExtension(dpy, &randr_event, &randr_error))
      randr_exists = True;
    else
      fprintf(stderr, "No XRandR extension, automatic refresh rate "
          "detection impossible.\n");
  }

#ifdef CONFIG_VSYNC_OPENGL
  // Query X GLX extension
  if (VSYNC_OPENGL == opts.vsync) {
    if (glXQueryExtension(dpy, &glx_event, &glx_error))
      glx_exists = True;
    else {
      fprintf(stderr, "No GLX extension, OpenGL VSync impossible.\n");
      opts.vsync = VSYNC_NONE;
    }
  }
#endif

  // Query X DBE extension
  if (opts.dbe) {
    int dbe_ver_major = 0, dbe_ver_minor = 0;
    if (XdbeQueryExtension(dpy, &dbe_ver_major, &dbe_ver_minor))
      if (dbe_ver_major >= 1)
        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 (!dbe_exists)
      opts.dbe = False;
  }

  register_cm((VSYNC_OPENGL == opts.vsync));

  // Initialize software optimization
  if (opts.sw_opti)
    opts.sw_opti = sw_opti_init();

  // Initialize DRM/OpenGL VSync
  if ((VSYNC_DRM == opts.vsync && !vsync_drm_init())
      || (VSYNC_OPENGL == opts.vsync && !vsync_opengl_init()))
    opts.vsync = VSYNC_NONE;

  // Overlay must be initialized before double buffer
  if (opts.paint_on_overlay)
    init_overlay();

  if (opts.dbe)
    init_dbe();

  if (opts.fork_after_register) fork_after();

  get_atoms();
  init_alpha_picts(dpy);

  pa.subwindow_mode = IncludeInferiors;

  gaussian_map = make_gaussian_map(dpy, opts.shadow_radius);
  presum_gaussian(gaussian_map);

  root_width = DisplayWidth(dpy, scr);
  root_height = DisplayHeight(dpy, scr);

  root_picture = XRenderCreatePicture(dpy, root,
      XRenderFindVisualFormat(dpy, DefaultVisual(dpy, scr)),
      CPSubwindowMode, &pa);
  if (opts.paint_on_overlay) {
    tgt_picture = XRenderCreatePicture(dpy, overlay,
        XRenderFindVisualFormat(dpy, DefaultVisual(dpy, scr)),
        CPSubwindowMode, &pa);
  }
  else {
    tgt_picture = root_picture;
  }

  black_picture = solid_picture(dpy, True, 1, 0, 0, 0);

  // Generates another Picture for shadows if the color is modified by
  // user
  if (!opts.shadow_red && !opts.shadow_green && !opts.shadow_blue) {
    cshadow_picture = black_picture;
  } else {
    cshadow_picture = solid_picture(dpy, True, 1,
        opts.shadow_red, opts.shadow_green, opts.shadow_blue);
  }

  // Generates a picture for inactive_dim
  if (opts.inactive_dim) {
    dim_picture = solid_picture(dpy, True, opts.inactive_dim, 0, 0, 0);
  }

  all_damage = None;
  XGrabServer(dpy);

  XCompositeRedirectSubwindows(
    dpy, root, CompositeRedirectManual);

  XSelectInput(dpy, root,
    SubstructureNotifyMask
    | ExposureMask
    | StructureNotifyMask
    | PropertyChangeMask);

  XQueryTree(dpy, root, &root_return,
    &parent_return, &children, &nchildren);

  for (i = 0; i < nchildren; i++) {
    add_win(dpy, children[i], i ? children[i-1] : None, False);
  }

  XFree(children);

  if (opts.track_focus) {
    recheck_focus(dpy);
  }

  XUngrabServer(dpy);

  ufd.fd = ConnectionNumber(dpy);
  ufd.events = POLLIN;

  if (opts.sw_opti)
    paint_tm_offset = get_time_timespec().tv_nsec;

  reg_ignore_expire = True;

  t = paint_preprocess(dpy, list);

  paint_all(dpy, None, t);

  // Initialize idling
  idling = False;

  // Main loop
  while (1) {
    Bool ev_received = False;

    while (XEventsQueued(dpy, QueuedAfterReading)
        || (evpoll(&ufd,
            (ev_received ? 0: (idling ? -1: fade_timeout()))) > 0)) {
      // 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(dpy, QueuedAfterReading)) {
        XNextEvent(dpy, &ev);
        ev_handle((XEvent *) &ev);
        ev_received = True;
      }
    }

    // idling will be turned off during paint_preprocess() if needed
    idling = True;

    t = paint_preprocess(dpy, list);

    if (all_damage && !is_region_empty(dpy, all_damage)) {
      static int paint;
      paint_all(dpy, all_damage, t);
      reg_ignore_expire = False;
      paint++;
      XSync(dpy, False);
      all_damage = None;
    }
  }
}