/*
 * Copyright (C) 2004-2026 Kim Woelders
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of the Software, its documentation and marketing & publicity
 * materials, and acknowledgment shall be given in the documentation, materials
 * and software packages that this Software was used.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
/*
 * This code was originally derived from xcompmgr.c,  see original copyright
 * notice at end.
 * It has been mostly rewritten since, only the shadow code is more or less
 * intact.
 */
#include "config.h"

#if USE_COMPOSITE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/shape.h>
#include <X11/extensions/Xcomposite.h>
#include <X11/extensions/Xdamage.h>
#include <X11/extensions/Xrender.h>
#if USE_XPRESENT
#include <X11/extensions/Xpresent.h>
#endif

#include "E.h"
#include "animation.h"
#include "desktops.h"
#include "ecompmgr.h"
#include "emodule.h"
#include "eobj.h"
#include "events.h"
#include "ewins.h"              /* EwinsManage() */
#include "hints.h"
#include "timers.h"
#include "windowmatch.h"
#include "xwin.h"

#define ENABLE_SHADOWS      1

#define USE_DESK_EXPOSE     0
#define USE_DESK_VISIBILITY 1

#define USE_CLIP_RELATIVE_TO_DESK 1

/* Composite Overlay Window (client) availability */
#if HAVE_COMPOSITE_OVERLAY_WINDOW
#define USE_COMPOSITE_OVERLAY_WINDOW 1
#else
#define USE_COMPOSITE_OVERLAY_WINDOW 0
#endif

#define ENABLE_DEBUG   1
#if ENABLE_DEBUG
#define D1printf(fmt...) if(EDebug(EDBUG_TYPE_COMPMGR))Eprintf(fmt)
#define D2printf(fmt...) if(EDebug(EDBUG_TYPE_COMPMGR2))Eprintf(fmt)
#define D3printf(fmt...) if(EDebug(EDBUG_TYPE_COMPMGR3))Eprintf(fmt)
#else
#define D1printf(fmt...)
#define D2printf(fmt...)
#define D3printf(fmt...)
#endif                          /* ENABLE_DEBUG */

#define DEBUG_OPACITY 0

#define INV_POS     0x01
#define INV_SIZE    0x02
#define INV_SHAPE   0x04
#define INV_CLIP    0x08
#define INV_OPACITY 0x10
#define INV_SHADOW  0x20
#define INV_PIXMAP  0x40
#define INV_PICTURE 0x80
#define INV_GEOM    (INV_POS | INV_SIZE | INV_SHAPE)
#define INV_ALL     (INV_POS | INV_SIZE | INV_CLIP | INV_OPACITY | INV_SHADOW | INV_PIXMAP)

/*
 * Eobj compositing data hook
 */

#define WINDOW_UNREDIR  0
#define WINDOW_SOLID    1
#define WINDOW_TRANS    2
#define WINDOW_ARGB     3

typedef struct _cmhook ECmWinInfo;

struct _cmhook {
    EObj           *next;       /* Paint order */
    EObj           *prev;       /* Paint order */
    Animator       *anim_fade;
    EX_Pixmap       pixmap;
    int             rcx, rcy, rcw, rch;
    int             mode;
    unsigned        damaged:1;
    unsigned        fadeout:1;
    unsigned        has_shadow:1;
    unsigned        have_shape:1;       /* Region validity - shape */
    unsigned        have_extents:1;     /* Region validity - extents */
    unsigned        have_clip:1;        /* Region validity - clip */
    Damage          damage;
    EX_Picture      picture;
    EX_Picture      pict_alpha; /* Solid, current opacity */
    EX_SrvRegion    shape;
    EX_SrvRegion    extents;
    EX_SrvRegion    clip;
    int             shape_x, shape_y;
#if ENABLE_SHADOWS
    EX_Picture      shadow_alpha;       /* Solid, sharp * current opacity */
    EX_Picture      shadow_pict;        /* Blurred shaped shadow */
    int             shadow_dx;
    int             shadow_dy;
    int             shadow_width;
    int             shadow_height;
#endif
    unsigned int    opacity;

    unsigned int    damage_sequence;    /* sequence when damage was created */

    unsigned int    opacity_to;
};

/*
 * Configuration
 */

#define ECM_MODE_OFF    0
#define ECM_MODE_ROOT   1
#define ECM_MODE_WINDOW 2
#define ECM_MODE_AUTO   3

#if ENABLE_SHADOWS
#define ECM_SHADOWS_OFF      0
#define ECM_SHADOWS_SHARP    1  /* use window alpha for shadow; sharp, but precise */
#define ECM_SHADOWS_ECHO     3  /* use window for shadow; sharp, but precise */
#define ECM_SHADOWS_BLURRED  2  /* use window extents for shadow, blurred */
#endif

#define ECM_OR_UNREDIRECTED  0
#define ECM_OR_ON_MAP        1
#define ECM_OR_ON_MAPUNMAP   2
#define ECM_OR_ON_CREATE     3

typedef struct {
    char            enable;
    char            use_name_pixmap;
#if USE_COMPOSITE_OVERLAY_WINDOW
    char            use_cow;
#endif
    int             mode;
    struct {
        int             mode;
        int             offset_x, offset_y;
        struct {
            int             opacity;
            int             radius;
        } blur;
        struct {
            int             opacity;
        } sharp;
        unsigned int    color;
    } shadows;
    struct {
        char            enable;
        unsigned int    time;   /* Fading time, ms */
    } fading;
    struct {
        int             mode;
        int             opacity;
    } override_redirect;
} Conf_compmgr_t;

Conf_compmgr_t  Conf_compmgr;

/*
 * State
 */

typedef struct {
    int             mode;
    EX_Window       root;
#if USE_COMPOSITE_OVERLAY_WINDOW
    EX_Window       cow;
#endif
    EX_Pixmap       pmap;       /* Compositing buffer */
    char            active;
    char            use_pixmap;
    char            reorder;
    char            ghosts;
    EObj           *eo_first;
    EObj           *eo_last;
    EX_SrvRegion    damage;
    char            got_damage;
    EX_SrvRegion    rgn_screen;
    EX_SrvRegion    rgn_clip;
    EX_SrvRegion    rgn_tmp;    /* For temporary use */
    EX_SrvRegion    rgn_tmp2;   /* For temporary use */
    int             shadow_mode;
    float           opac_blur;  /* 0. -> 1. */
    float           opac_sharp; /* 0. -> 1. */
#if USE_XPRESENT
    unsigned int    present_serial;
#endif
} Mode_compmgr_t;

Mode_compmgr_t  Mode_compmgr;

/* FIXME - Optimize according to what actually changed */
#define _ECM_SET_CLIP_CHANGED()   Mode_compmgr.reorder = 1
#define _ECM_SET_STACK_CHANGED()  Mode_compmgr.reorder = 1
#define _ECM_SET_SHADOW_CHANGED() Mode_compmgr.reorder = 1

static EX_Picture rootPicture;
static EX_Picture rootBuffer;

static ESelection *wm_cm_sel = NULL;

#define OPAQUE          0xffffffff
#define OP32To8(op) (((unsigned int)(op)) >> 24)

static void     ECompMgrDamageAll(void);
static void     ECompMgrHandleRootEvent(Win win, XEvent * ev, void *prm);
static void     ECompMgrHandleWindowEvent(Win win, XEvent * ev, void *prm);
static void     ECompMgrWinInvalidate(EObj * eo, int what);
static void     ECompMgrWinFadeEnd(EObj * eo, int done);
static int      ECompMgrDetermineOrder(EObj * const *lst, int num,
                                       EObj ** first, EObj ** last,
                                       Desk * dsk, EX_SrvRegion clip);

#define PIXMAP_DESTROY(pmap) \
   if (pmap != NoXID) { XFreePixmap(disp, pmap); pmap = NoXID; }
#define PICTURE_DESTROY(pict) \
   if (pict != NoXID) { XRenderFreePicture(disp, pict); pict = NoXID; }
#define REGION_DESTROY(rgn) \
   if (rgn != NoXID) { ERegionDestroy(rgn); rgn = NoXID; }

EX_SrvRegion
ECompMgrChildClipRegion(void)
{
    EObj           *const *lst, *eoi;
    int             i, num;
    EX_SrvRegion    rgn = Mode_compmgr.rgn_tmp2;
    Desk           *dsk = DesksGetCurrent();

    if (!Mode_compmgr.active)
        return NoXID;

    ERegionCopy(rgn, Mode_compmgr.rgn_screen);

    lst = EobjListStackGet(&num);
    for (i = 0; i < num; i++)
    {
        eoi = lst[i];
        if (eoi->desk != dsk)
            continue;
        if (eoi->cmhook && eoi->cmhook->shape &&
            (eoi->type == EOBJ_TYPE_EWIN || eoi->type == EOBJ_TYPE_BUTTON ||
             eoi->type == EOBJ_TYPE_MISC))
            ERegionSubtract(rgn, eoi->cmhook->shape);
    }

    return rgn;
}

#if !USE_BG_WIN_ON_ALL_DESKS
/*
 * Desk background
 */

void
ECompMgrDeskConfigure(Desk *dsk)
{
    EObj           *eo = dsk->bg.o;
    ECmWinInfo     *cw = eo->cmhook;
    EX_Picture      pict;
    XRenderPictFormat *pictfmt;
    XRenderPictureAttributes pa;
    EX_Pixmap       pmap;

    if (dsk->bg.pmap == NoXID)
    {
        pmap = XCreatePixmap(disp, Mode_compmgr.root, 1, 1, WinGetDepth(VROOT));
        EXFillAreaSolid(pmap, 0, 0, 1, 1, dsk->bg.pixel);
    }
    else
    {
        pmap = dsk->bg.pmap;
    }

    ECompMgrWinInvalidate(eo, INV_PICTURE);

    pa.repeat = True;
    pictfmt = XRenderFindVisualFormat(disp, WinGetVisual(VROOT));
    pict = XRenderCreatePicture(disp, pmap, pictfmt, CPRepeat, &pa);

    if (pmap != dsk->bg.pmap)
        XFreePixmap(disp, pmap);

    cw->picture = pict;

    D1printf("%s: Desk %d: using pixmap %#x picture=%#x\n", __func__,
             dsk->num, pmap, cw->picture);

    /* New background, all must be repainted */
    ECompMgrDamageAll();
}
#endif

#if USE_DESK_VISIBILITY
static void
ECompMgrDeskVisibility(EObj *eo, XEvent *ev)
{
    Desk           *dsk;
    int             visible;

    switch (eo->type)
    {
    default:
        return;
    case EOBJ_TYPE_DESK:
        dsk = (Desk *) eo;
        break;
    case EOBJ_TYPE_ROOT_BG:
        dsk = DeskGet(0);
        break;
    }

    visible = dsk->viewable && ev->xvisibility.state != VisibilityFullyObscured;
    if (dsk->visible == visible)
        return;
    dsk->visible = visible;
    if (!visible)
        return;

    /*
     * A viewable desk is no longer fully obscured. Assume this happened due
     * to a VT switch to our display and repaint all. This may happen in other
     * situations as well, but most likely when we must repaint everything
     * anyway.
     */
    ECompMgrDamageAll();
}
#endif

/*
 * Root (?)
 */

static void
ECompMgrDamageMerge(EX_SrvRegion damage)
{
    if (Mode_compmgr.got_damage)
    {
        if (EDebug(EDBUG_TYPE_COMPMGR3))
            ERegionShow("ECompMgrDamageMerge add:", damage, NULL);

        ERegionUnion(Mode_compmgr.damage, damage);
    }
    else if (Mode_compmgr.damage != NoXID)
    {
        ERegionCopy(Mode_compmgr.damage, damage);
    }
    else
    {
        Mode_compmgr.damage = ERegionClone(damage);
    }
    Mode_compmgr.got_damage = 1;

    if (EDebug(EDBUG_TYPE_COMPMGR3))
        ERegionShow("ECompMgrDamageMerge all:", Mode_compmgr.damage, NULL);
}

static void
ECompMgrDamageMergeObject(EObj *eo, EX_SrvRegion damage)
{
    ECmWinInfo     *cw = eo->cmhook;
    Desk           *dsk = eo->desk;

    if (!Mode_compmgr.active || damage == NoXID)
        return;

    if (dsk->num > 0 && !dsk->viewable && eo->ilayer < 512)
        return;

    if (Mode_compmgr.reorder)
        ECompMgrDetermineOrder(NULL, 0, &Mode_compmgr.eo_first,
                               &Mode_compmgr.eo_last, DeskGet(0), NoXID);

    damage = ERegionCopy(Mode_compmgr.rgn_tmp, damage);

#if USE_CLIP_RELATIVE_TO_DESK
    if (cw->have_clip && eo->type != EOBJ_TYPE_DESK)
        ERegionSubtract(damage, cw->clip);
#endif

    if (EoGetX(dsk) != 0 || EoGetY(dsk) != 0)
        ERegionTranslate(damage, EoGetX(dsk), EoGetY(dsk));

#if !USE_CLIP_RELATIVE_TO_DESK
    if (cw->have_clip && eo->type != EOBJ_TYPE_DESK)
        ERegionSubtract(damage, cw->clip);
#endif
    if (!eo->ghost)
        Mode.events.damage_count++;

    ECompMgrDamageMerge(damage);
}

static void
ECompMgrDamageAll(void)
{
    ECompMgrDamageMerge(Mode_compmgr.rgn_screen);
}

void
ECompMgrDamageArea(int x, int y, int w, int h)
{
    EX_SrvRegion    rgn;

    if (!Mode_compmgr.active)
        return;

    rgn = Mode_compmgr.rgn_tmp2;
    ERegionSetRect(rgn, x, y, w, h);
    ECompMgrDamageMerge(rgn);
}

#if ENABLE_SHADOWS

#define M_2PI_F ((float)(2 * M_PI))

static EX_Picture transBlackPicture;

typedef struct {
    int             size;
    float          *data;
} conv;

static conv    *gaussianMap = NULL;

static float
gaussian(float r, float x, float y)
{
    return ((1.f / (sqrtf(M_2PI_F * r))) *
            expf(-(x * x + y * y) / (2.f * r * r)));
}

static conv    *
make_gaussian_map(int r)
{
    conv           *c;
    int             size = (r * 3 + 1) & ~1;
    int             center = size / 2;
    int             x, y;
    float           t, g;

#if 1                           /* Avoid cast-align warnings */
    c = (conv *) malloc(sizeof(conv) + size * size * sizeof(float));
#else
    c = (conv *) EMALLOC(char, sizeof(conv) + size * size * sizeof(float));
#endif

    c->size = size;
    c->data = (float *)(c + 1);
    t = 0.f;
    for (y = 0; y < size; y++)
        for (x = 0; x < size; x++)
        {
            g = gaussian(r, (float)(x - center), (float)(y - center));
            t += g;
            c->data[y * size + x] = g;
        }
/* printf ("gaussian total %f\n", t); */
    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, float opacity, int x, int y, int width, int height)
{
    int             fx, fy;
    float          *g_data;
    float          *g_line = map->data;
    int             g_size = map->size;
    int             center = g_size / 2;
    int             fx_start, fx_end;
    int             fy_start, fy_end;
    float           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.f));
}

static XImage  *
make_shadow(float opacity, int width, int height)
{
    XImage         *ximage;
    unsigned char  *data;
    int             gsize = gaussianMap->size;
    int             ylimit, xlimit;
    int             swidth = width + gsize;
    int             sheight = height + gsize;
    int             center = gsize / 2;
    int             x, y;
    unsigned char   d;
    int             x_diff;

    data = ECALLOC(unsigned char, swidth * sheight);

    if (!data)
        return NULL;

    ximage = XCreateImage(disp, DefaultVisual(disp, DefaultScreen(disp)),
                          8, ZPixmap, 0,
                          (char *)data,
                          swidth, sheight, 8, swidth * sizeof(unsigned char));
    if (!ximage)
    {
        Efree(data);
        return NULL;
    }

    /*
     * Build the gaussian in sections
     */

#if 1
    /* FIXME - Handle properly - shaped/non-shaped/offset */
    /*
     * center (fill the complete data array)
     */
    d = sum_gaussian(gaussianMap, opacity, center, center, width, height);
    memset(data, d, sheight * swidth);
#endif

    /*
     * corners
     */
    ylimit = gsize;
    if (ylimit > sheight / 2)
        ylimit = (sheight + 1) / 2;
    xlimit = gsize;
    if (xlimit > swidth / 2)
        xlimit = (swidth + 1) / 2;

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

    /*
     * sides
     */
    for (x = 0; x < xlimit; x++)
    {
        d = sum_gaussian(gaussianMap, opacity, x - center, center, width,
                         height);
        for (y = gsize; y < sheight - gsize; y++)
        {
            data[y * swidth + x] = d;
            data[y * swidth + (swidth - x - 1)] = d;
        }
    }

#ifdef __clang_analyzer__
    free(data);
#endif
    return ximage;
}

static          EX_Picture
shadow_picture(float opacity, int width, int height, int *wp, int *hp)
{
    XImage         *shadowImage;
    EX_Pixmap       shadowPixmap;
    EX_Picture      shadowPicture;
    GC              gc;

    shadowImage = make_shadow(opacity, width, height);
    if (!shadowImage)
        return NoXID;

    shadowPixmap = XCreatePixmap(disp, Mode_compmgr.root,
                                 shadowImage->width, shadowImage->height, 8);
    shadowPicture = XRenderCreatePicture(disp, shadowPixmap,
                                         XRenderFindStandardFormat(disp,
                                                                   PictStandardA8),
                                         0, 0);
    gc = XCreateGC(disp, shadowPixmap, 0, 0);

    XPutImage(disp, shadowPixmap, gc, shadowImage, 0, 0, 0, 0,
              shadowImage->width, shadowImage->height);
    *wp = shadowImage->width;
    *hp = shadowImage->height;
    XFreeGC(disp, gc);
    XDestroyImage(shadowImage);
    XFreePixmap(disp, shadowPixmap);

    return shadowPicture;
}

#endif                          /* ENABLE_SHADOWS */

static void     ECompMgrWinSetShape(EObj * eo);

/* Region of window in screen coordinates, including shadows */
static void
ECompMgrWinSetExtents(EObj *eo)
{
    ECmWinInfo     *cw = eo->cmhook;
    XRectangle      r, sr;
    unsigned int    bw;

    /* FIXME - Get this right */
    bw = EobjGetBW(eo);
    if (Mode_compmgr.use_pixmap)
    {
        cw->rcx = EobjGetX(eo);
        cw->rcy = EobjGetY(eo);
        cw->rcw = EobjGetW(eo) + 2 * bw;
        cw->rch = EobjGetH(eo) + 2 * bw;
    }
    else
    {
        cw->rcx = EobjGetX(eo) + bw;
        cw->rcy = EobjGetY(eo) + bw;
        cw->rcw = EobjGetW(eo);
        cw->rch = EobjGetH(eo);
    }

    if (eo->noredir && bw)
    {
        r.x = EobjGetX(eo);
        r.y = EobjGetY(eo);
        r.width = EobjGetW(eo) + 2 * bw;
        r.height = EobjGetH(eo) + 2 * bw;
    }
    else
    {
        r.x = cw->rcx;
        r.y = cw->rcy;
        r.width = cw->rcw;
        r.height = cw->rch;
    }

    if (cw->extents == NoXID)
        cw->extents = ERegionCreate();

#if ENABLE_SHADOWS
    cw->has_shadow = 0;

    switch (Mode_compmgr.shadow_mode)
    {
    default:
        goto skip_shadow;

    case ECM_SHADOWS_SHARP:
    case ECM_SHADOWS_ECHO:
        if (!EobjShadowOk(eo, true))
            goto skip_shadow;

        cw->shadow_dx = Conf_compmgr.shadows.offset_x;
        cw->shadow_dy = Conf_compmgr.shadows.offset_y;
        cw->shadow_width = cw->rcw;
        cw->shadow_height = cw->rch;
        break;

    case ECM_SHADOWS_BLURRED:
        if (!EobjShadowOk(eo, false))
            goto skip_shadow;

        if (!gaussianMap)
        {
            gaussianMap = make_gaussian_map(Conf_compmgr.shadows.blur.radius);
            if (!gaussianMap)
                goto skip_shadow;
        }

        cw->shadow_dx = Conf_compmgr.shadows.offset_x - gaussianMap->size / 2;
        cw->shadow_dy = Conf_compmgr.shadows.offset_y - gaussianMap->size / 2;
        if (!cw->shadow_pict)
            cw->shadow_pict = shadow_picture(Mode_compmgr.opac_blur,
                                             cw->rcw, cw->rch,
                                             &cw->shadow_width,
                                             &cw->shadow_height);
        break;
    }
    sr.x = cw->rcx + cw->shadow_dx;
    sr.y = cw->rcy + cw->shadow_dy;
    sr.width = cw->shadow_width;
    sr.height = cw->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;

    cw->has_shadow = 1;
    ERegionSetRect(cw->extents, r.x, r.y, r.width, r.height);
    goto done;

  skip_shadow:
#endif

    /* No shadow - extents = shape */
    if (!cw->have_shape)
        ECompMgrWinSetShape(eo);
    ERegionCopy(cw->extents, cw->shape);

  done:
    cw->have_extents = 1;

    D1printf("%s: %#x %d %d %d %d\n", __func__, EobjGetXwin(eo),
             r.x, r.y, r.width, r.height);

    if (EDebug(EDBUG_TYPE_COMPMGR2))
        ERegionShow("extents", cw->extents, NULL);
}

/* Region of shaped window in screen coordinates */
static void
ECompMgrWinSetShape(EObj *eo)
{
    ECmWinInfo     *cw = eo->cmhook;
    int             x, y;

    if (cw->shape == NoXID)
    {
#if 0                           /* FIXME - TBD */
        /* If the window shape is changed on OR windows, cw->shape set below
         * may not reflect the shape change. Adding a sync fixes things in
         * some situations. */
        if (eo->type == EOBJ_TYPE_EXT)
            ESync(0);
#endif
        cw->shape = ERegionCreateFromWindow(EobjGetWin(eo));

        if (WinIsShaped(EobjGetWin(eo)))
        {
            /* Intersect with window size to get effective bounding region */
            ERegionSetRect(Mode_compmgr.rgn_tmp,
                           0, 0, EobjGetW(eo), EobjGetH(eo));
            ERegionIntersect(cw->shape, Mode_compmgr.rgn_tmp);
        }
        x = EobjGetX(eo) + EobjGetBW(eo);
        y = EobjGetY(eo) + EobjGetBW(eo);
    }
    else
    {
        x = EobjGetX(eo) + EobjGetBW(eo) - cw->shape_x;
        y = EobjGetY(eo) + EobjGetBW(eo) - cw->shape_y;
    }

    ERegionTranslate(cw->shape, x, y);

    cw->shape_x = EobjGetX(eo) + EobjGetBW(eo);
    cw->shape_y = EobjGetY(eo) + EobjGetBW(eo);
    cw->have_shape = 1;

    D1printf("%s: %#x: %d %d\n", __func__, EobjGetXwin(eo),
             cw->shape_x, cw->shape_y);
    if (EDebug(EDBUG_TYPE_COMPMGR2))
        ERegionShow("shape", cw->shape, NULL);
}

EX_Pixmap
ECompMgrWinGetPixmap(const EObj *eo)
{
    ECmWinInfo     *cw = eo->cmhook;

    if (!cw)
        return NoXID;

    if (cw->pixmap != NoXID)
        return cw->pixmap;

    if (eo->noredir)
        return NoXID;

    cw->pixmap = EWindowGetPixmap(EobjGetWin(eo));

    return cw->pixmap;
}

EX_Picture
ECompMgrWinGetAlphaPict(const EObj *eo)
{
    return (eo->cmhook) ? eo->cmhook->pict_alpha : NoXID;
}

static void
ECompMgrWinInvalidate(EObj *eo, int what)
{
    ECmWinInfo     *cw = eo->cmhook;

    if (!cw)
        return;

    D1printf("%s: %#x: %#x\n", __func__, EobjGetXwin(eo), what);

    if ((what & (INV_SIZE | INV_PIXMAP)) && cw->pixmap != NoXID)
    {
        XFreePixmap(disp, cw->pixmap);
        cw->pixmap = NoXID;
        if (Mode_compmgr.use_pixmap)
            what |= INV_PICTURE;
#if USE_GLX
        EobjTextureInvalidate(eo);
#endif
    }

    if (what & (INV_SIZE | INV_SHAPE))
        REGION_DESTROY(cw->shape);
    if (what & INV_GEOM)
        cw->have_shape = 0;

    if (what & INV_PICTURE)
        PICTURE_DESTROY(cw->picture);

    if (what & INV_OPACITY)
        PICTURE_DESTROY(cw->pict_alpha);

    if (what & (INV_CLIP | INV_GEOM))
        cw->have_clip = 0;

#if ENABLE_SHADOWS
    if (what & (INV_SIZE | INV_SHADOW))
        PICTURE_DESTROY(cw->shadow_pict);
    if (what & (INV_OPACITY | INV_SHADOW))
        PICTURE_DESTROY(cw->shadow_alpha);
#endif

    if (what & (INV_GEOM | INV_SHADOW))
        cw->have_extents = 0;
}

void
ECompMgrWinSetOpacity(EObj *eo, unsigned int opacity)
{
    ECmWinInfo     *cw = eo->cmhook;
    int             mode;

    if (!cw || cw->opacity == opacity)
        return;

    cw->opacity = opacity;

    D1printf("%s: %#x opacity=%#x\n", __func__, EobjGetXwin(eo), cw->opacity);

    if (eo->shown || cw->fadeout)
        ECompMgrDamageMergeObject(eo, cw->extents);

    /* Invalidate stuff changed by opacity */
    ECompMgrWinInvalidate(eo, INV_OPACITY);

    if (eo->noredir)
        mode = WINDOW_UNREDIR;
    else if (EobjGetWin(eo)->argb)
        mode = WINDOW_ARGB;
    else if (cw->opacity != OPAQUE)
        mode = WINDOW_TRANS;
    else
        mode = WINDOW_SOLID;

    if (mode != cw->mode)
        _ECM_SET_CLIP_CHANGED();

    cw->mode = mode;
}

static int
doECompMgrWinFade(EObj *eo, int run, void *data __UNUSED__)
{
    ECmWinInfo     *cw;
    unsigned int    op, step;

    cw = eo->cmhook;
    op = cw->opacity_to;

#if DEBUG_OPACITY
    Eprintf("%s %#x: %u/%u, %#x->%#x\n", __func__, EobjGetXwin(eo),
            eo->fading, cw->fadeout, cw->opacity, op);
#endif
    if (!eo->fading)
        goto done;

    eo->fading = cw->fadeout;

    step = Conf_compmgr.fading.time;
    if (step == 0)
        step = 1;
    step = run * (0xffffffff / step);
    if (op == cw->opacity)
    {
        op = eo->opacity;
        ECompMgrWinFadeEnd(eo, 0);
    }
    else if (op > cw->opacity)
    {
        if (op - cw->opacity > step)
        {
            op = cw->opacity + step;
            eo->fading = 1;
        }
    }
    else
    {
        if (cw->opacity - op > step)
        {
            op = cw->opacity - step;
            eo->fading = 1;
        }
    }

#if DEBUG_OPACITY
    Eprintf("%s %#x: %#x\n", __func__, EobjGetXwin(eo), op);
#endif
    ECompMgrWinSetOpacity(eo, op);

    if (eo->fading)
        return 0;

    if (eo->type == EOBJ_TYPE_EWIN)
        ModulesSignal(eo->shown ? ESIGNAL_EWIN_CHANGE : ESIGNAL_EWIN_UNMAP, eo);

  done:
    cw->anim_fade = NULL;
    return ANIM_RET_CANCEL_ANIM;
}

static void
ECompMgrWinFade(EObj *eo, unsigned int op_from, unsigned int op_to)
{
    ECmWinInfo     *cw = eo->cmhook;

    if (op_from == op_to && op_to == eo->opacity)
    {
        if (eo->fading)
            ECompMgrWinFadeEnd(eo, 0);
        return;
    }

    if (!eo->fading && !cw->anim_fade)
        cw->anim_fade =
            AnimatorAdd(eo, ANIM_FADE, doECompMgrWinFade, -1, 0, 0, NULL);

    cw->opacity_to = op_to;

    eo->fading = 1;
    ECompMgrWinSetOpacity(eo, op_from);
}

static void
ECompMgrWinFadeIn(EObj *eo)
{
    ECmWinInfo     *cw = eo->cmhook;

#if DEBUG_OPACITY
    Eprintf("%s %#x: %u/%u, %#x %#x->%#x\n", __func__, EobjGetXwin(eo),
            eo->fading, cw->fadeout, eo->opacity, 0x10000000, cw->opacity);
#endif
    if (eo->fading && cw->fadeout)
        ECompMgrWinFadeEnd(eo, 0);
    ECompMgrWinFade(eo, 0x10000000, eo->opacity);
}

static void
ECompMgrWinFadeOut(EObj *eo)
{
    ECmWinInfo     *cw = eo->cmhook;

#if DEBUG_OPACITY
    Eprintf("%s %#x: %u/%u, %#x %#x->%#x\n", __func__, EobjGetXwin(eo),
            eo->fading, cw->fadeout, eo->opacity, cw->opacity, 0x10000000);
#endif
    cw->fadeout = 1;
    ECompMgrWinFade(eo, cw->opacity, 0x10000000);
}

static void
ECompMgrWinFadeEnd(EObj *eo, int done)
{
    ECmWinInfo     *cw = eo->cmhook;

#if DEBUG_OPACITY
    Eprintf("%s %#x: done=%d\n", __func__, EobjGetXwin(eo), done);
#endif
    if (cw->fadeout)
    {
        cw->fadeout = 0;
        ECompMgrWinInvalidate(eo, INV_PIXMAP | INV_PICTURE);
        ECompMgrDamageMergeObject(eo, cw->extents);
        _ECM_SET_CLIP_CHANGED();
    }

    eo->fading = 0;
    if (done && cw->anim_fade)
    {
        AnimatorDel(eo, cw->anim_fade);
        cw->anim_fade = NULL;
    }
}

void
ECompMgrWinChangeOpacity(EObj *eo, unsigned int opacity)
{
    ECmWinInfo     *cw = eo->cmhook;

    if (!cw)
        return;

    if (eo->shown && Conf_compmgr.fading.enable && eo->fade)
        ECompMgrWinFade(eo, cw->opacity, opacity);
    else
        ECompMgrWinSetOpacity(eo, opacity);
}

void
ECompMgrWinMap(EObj *eo)
{
    ECmWinInfo     *cw = eo->cmhook;

    if (!cw)
    {
        ECompMgrWinNew(eo);
        cw = eo->cmhook;
        if (!cw)
            return;
    }

    D1printf("%s: %#x\n", __func__, EobjGetXwin(eo));

    if (!cw->have_extents)
        ECompMgrWinSetExtents(eo);

    _ECM_SET_STACK_CHANGED();
    ECompMgrDamageMergeObject(eo, cw->extents);

    if (Conf_compmgr.fading.enable && eo->fade)
        ECompMgrWinFadeIn(eo);
}

void
ECompMgrWinUnmap(EObj *eo)
{
    int             fadeout;
    ECmWinInfo     *cw = eo->cmhook;

    D1printf("%s: %#x shown=%d\n", __func__, EobjGetXwin(eo), eo->shown);

    if (!eo->shown)             /* Sometimes we get a synthetic one too */
        return;

    fadeout = Conf_compmgr.fading.enable && eo->fade && !eo->gone;

    ECompMgrDamageMergeObject(eo, cw->extents);
    _ECM_SET_STACK_CHANGED();

    if (fadeout)
    {
        ECompMgrWinInvalidate(eo, INV_PICTURE);
        ECompMgrWinFadeOut(eo);
    }
    else
        ECompMgrWinInvalidate(eo, INV_PIXMAP);
}

static void
ECompMgrWinSetPicts(EObj *eo)
{
    ECmWinInfo     *cw = eo->cmhook;

    if (cw->pixmap == NoXID && eo->shown && !eo->noredir &&
        (Mode_compmgr.use_pixmap || (eo->fade && Conf_compmgr.fading.enable)))
    {
        cw->pixmap = EWindowGetPixmap(EobjGetWin(eo));
        D1printf("%s: %#x: Pmap=%#x\n", __func__, EobjGetXwin(eo), cw->pixmap);
    }

    if (cw->picture == NoXID)
    {
        EX_Drawable     draw = EobjGetXwin(eo);

        if ((cw->pixmap && Mode_compmgr.use_pixmap) || (cw->fadeout))
            draw = cw->pixmap;
        if (draw == NoXID)
            return;

        cw->picture = EPictureCreateII(EobjGetWin(eo), draw);
        D1printf("%s: %#x: Pict=%#x (drawable=%#x)\n", __func__,
                 EobjGetXwin(eo), cw->picture, draw);

#if 0                           /* Pixmap must be clipped by window shape */
        if (draw == cw->pixmap && WinIsShaped(EobjGetWin(eo)))
        {
            EX_SrvRegion    clip;

            clip = ERegionCreateFromWindow(EobjGetWin(eo));
            EPictureSetClip(cw->picture, clip);
            ERegionDestroy(clip);
        }
#endif
    }
}

void
ECompMgrWinNew(EObj *eo)
{
    ECmWinInfo     *cw;

    if (!Mode_compmgr.active)   /* FIXME - Here? */
        return;

    if (eo->inputonly || EobjGetWin(eo) == VROOT)
    {
        eo->noredir = 1;
        return;
    }

    cw = ECALLOC(ECmWinInfo, 1);
    if (!cw)
        return;

    D1printf("%s: %#x\n", __func__, EobjGetXwin(eo));

    eo->cmhook = cw;

    if (eo->type == EOBJ_TYPE_EXT &&
        Conf_compmgr.override_redirect.mode == ECM_OR_UNREDIRECTED)
    {
        eo->noredir = 1;
        eo->fade = 0;
        eo->shadow = 0;
    }
    if (eo->type == EOBJ_TYPE_EXT)
    {
        XShapeSelectInput(disp, EobjGetXwin(eo), ShapeNotifyMask);
        eo->shaped = EShapeUpdate(EobjGetWin(eo));
    }

    if (eo->noredir)
    {
        /* If already auto-redirected undo that */
        if (Mode_compmgr.mode == ECM_MODE_ROOT)
        {
            XCompositeUnredirectWindow(disp, EobjGetXwin(eo),
                                       CompositeRedirectManual);
            if (eo->type == EOBJ_TYPE_DESK)
                XCompositeRedirectSubwindows(disp, EobjGetXwin(eo),
                                             CompositeRedirectManual);
        }
    }
    else
    {
        if (Mode_compmgr.mode == ECM_MODE_WINDOW)
            XCompositeRedirectWindow(disp, EobjGetXwin(eo),
                                     CompositeRedirectManual);
        cw->damage_sequence = NextRequest(disp);
        cw->damage =
            XDamageCreate(disp, EobjGetXwin(eo), XDamageReportNonEmpty);
    }

    if (eo->type == EOBJ_TYPE_EXT)
        eo->opacity =
            OpacityFromPercent(Conf_compmgr.override_redirect.opacity);
    if (eo->opacity == 0)
        eo->opacity = 0xFFFFFFFF;

    if (eo->type == EOBJ_TYPE_DESK || eo->type == EOBJ_TYPE_ROOT_BG)
    {
        ESelectInputChange(EobjGetWin(eo), VisibilityChangeMask, 0);
    }

    if (eo->type != EOBJ_TYPE_EWIN)
        WindowMatchEobjOps(eo);

    cw->opacity = 0xdeadbeef;
    ECompMgrWinSetOpacity(eo, eo->opacity);

    EventCallbackRegister(EobjGetWin(eo), ECompMgrHandleWindowEvent, eo);
}

void
ECompMgrWinMoveResize(EObj *eo, int change_xy, int change_wh, int change_bw)
{
    ECmWinInfo     *cw = eo->cmhook;
    int             invalidate;

    D1printf("%s: %#x xy=%d wh=%d bw=%d\n", __func__,
             EobjGetXwin(eo), change_xy, change_wh, change_bw);

    invalidate = 0;
    if (change_xy || change_bw)
        invalidate |= INV_POS;
    if (change_wh || change_bw)
        invalidate |= INV_SIZE;

    if (!invalidate)
        return;

    if (cw->fadeout)
        ECompMgrWinFadeEnd(eo, 1);

    if (!eo->shown)
    {
        ECompMgrWinInvalidate(eo, invalidate);
        return;
    }

    if (EDebug(EDBUG_TYPE_COMPMGR3))
        ERegionShow("old-extents:", cw->extents, NULL);

#if 0                           /* FIXME - We shouldn't have to update clip if transparent */
    if (cw->mode == WINDOW_UNREDIR || cw->mode == WINDOW_SOLID)
#endif
        _ECM_SET_CLIP_CHANGED();

    if (cw->have_extents)
    {
        /* Invalidate old window region */
        ERegionCopy(Mode_compmgr.rgn_tmp2, cw->extents);
        ECompMgrWinInvalidate(eo, invalidate);
        /* Invalidate new window region */
        ECompMgrWinSetExtents(eo);
        ERegionUnion(Mode_compmgr.rgn_tmp2, cw->extents);
        ECompMgrDamageMergeObject(eo, Mode_compmgr.rgn_tmp2);
    }
    else
    {
        ECompMgrWinInvalidate(eo, invalidate);
        /* Invalidate new window region */
        ECompMgrWinSetExtents(eo);
        ECompMgrDamageMergeObject(eo, cw->extents);
    }
}

void
ECompMgrWinDamageArea(EObj *eo, int x __UNUSED__, int y __UNUSED__,
                      int w __UNUSED__, int h __UNUSED__)
{
    ECmWinInfo     *cw = eo->cmhook;

    ECompMgrDamageMergeObject(eo, cw->shape);
}

void
ECompMgrWinChangeShadow(EObj *eo, int shadow)
{
    ECmWinInfo     *cw = eo->cmhook;

    if (!cw || !eo->shown)
        goto done;

    if (!shadow && eo->shadow)
    {
        /* Disable shadow */
        ECompMgrDamageMergeObject(eo, cw->extents);
        ECompMgrWinInvalidate(eo, INV_SHADOW);
    }
    else if (shadow && !eo->shadow)
    {
        /* Enable shadow */
        ECompMgrWinInvalidate(eo, INV_SHADOW);
        eo->shadow = shadow;
        ECompMgrWinSetExtents(eo);
        ECompMgrDamageMergeObject(eo, cw->extents);
    }
  done:
    eo->shadow = shadow;
}

static void
ECompMgrWinConfigure(EObj *eo, XEvent *ev)
{
    int             x, y, w, h, bw;
    int             change_xy, change_wh, change_bw;

    x = ev->xconfigure.x;
    y = ev->xconfigure.y;
    w = ev->xconfigure.width;
    h = ev->xconfigure.height;
    bw = ev->xconfigure.border_width;

    change_xy = EobjGetX(eo) != x || EobjGetY(eo) != y;
    change_wh = EobjGetW(eo) != w || EobjGetH(eo) != h;
    change_bw = EobjGetBW(eo) != bw;

    EWindowSetGeometry(EobjGetWin(eo), x, y, w, h, bw);

    ECompMgrWinMoveResize(eo, change_xy, change_wh, change_bw);
}

void
ECompMgrWinReparent(EObj *eo, Desk *dsk, int change_xy)
{
    ECmWinInfo     *cw = eo->cmhook;

    D1printf("%s: %#x %#x d=%d->%d x,y=%d,%d %d\n", __func__,
             EobjGetXwin(eo), cw->extents,
             (eo->desk) ? (int)eo->desk->num : -1, dsk->num,
             EobjGetX(eo), EobjGetY(eo), change_xy);

    if (!eo->shown)
    {
        if (change_xy)
        {
            ECompMgrDamageMergeObject(eo, cw->extents);
            ECompMgrWinInvalidate(eo, INV_POS);
        }
        return;
    }

    /* Invalidate old window region */
    if (EDebug(EDBUG_TYPE_COMPMGR3))
        ERegionShow("old-extents:", cw->extents, NULL);
    ECompMgrDamageMergeObject(eo, cw->extents);
    if (change_xy)
    {
        ECompMgrWinInvalidate(eo, INV_POS);

        /* Find new window region */
        ECompMgrWinSetExtents(eo);
    }
    eo->desk = dsk;
    _ECM_SET_STACK_CHANGED();
    ECompMgrDamageMergeObject(eo, cw->extents);
    ECompMgrWinInvalidate(eo, INV_PIXMAP);
}

static void
ECompMgrWinCirculate(EObj *eo, XEvent *ev)
{
    D1printf("%s: %#lx %#x\n", __func__, ev->xany.window, EobjGetXwin(eo));

    _ECM_SET_STACK_CHANGED();
}

void
ECompMgrWinChangeShape(EObj *eo)
{
    ECmWinInfo     *cw = eo->cmhook;

    D1printf("%s: %#x\n", __func__, EobjGetXwin(eo));

    EShapeUpdate(EobjGetWin(eo));

    if (cw->extents == NoXID)
        return;

    ECompMgrDamageMergeObject(eo, cw->extents);
    ECompMgrWinInvalidate(eo, INV_SHAPE);       /* Invalidate extents and shape */
    ECompMgrWinSetExtents(eo);
    ECompMgrDamageMergeObject(eo, cw->extents);
    _ECM_SET_CLIP_CHANGED();
}

void
ECompMgrWinRaiseLower(EObj *eo, int delta)
{
    ECmWinInfo     *cw = eo->cmhook;

    D1printf("%s: %#x delta=%d\n", __func__, EobjGetXwin(eo), delta);

    if (delta < 0)              /* Raise */
        _ECM_SET_STACK_CHANGED();
    ECompMgrDamageMergeObject(eo, cw->extents);
    if (delta > 0)              /* Lower */
        _ECM_SET_STACK_CHANGED();
}

void
ECompMgrWinDel(EObj *eo)
{
    ECmWinInfo     *cw = eo->cmhook;

    if (!cw)
        return;

    D1printf("%s: %#x\n", __func__, EobjGetXwin(eo));

    if (eo->fading)
        ECompMgrWinFadeEnd(eo, 1);

    EventCallbackUnregister(EobjGetWin(eo), ECompMgrHandleWindowEvent, eo);

    if (!eo->gone)
    {
        ECompMgrWinInvalidate(eo, INV_PICTURE);

        if (eo->noredir)
        {
            if (Mode_compmgr.mode == ECM_MODE_ROOT &&
                eo->type == EOBJ_TYPE_DESK)
                XCompositeUnredirectSubwindows(disp, EobjGetXwin(eo),
                                               CompositeRedirectManual);
        }
        else
        {
            if (cw->damage != NoXID)
                XDamageDestroy(disp, cw->damage);

            if (Mode_compmgr.mode == ECM_MODE_WINDOW)
                XCompositeUnredirectWindow(disp, EobjGetXwin(eo),
                                           CompositeRedirectManual);
        }
    }

    ECompMgrWinInvalidate(eo, INV_ALL);
    REGION_DESTROY(cw->extents);
    REGION_DESTROY(cw->clip);

    EFREE_NULL(eo->cmhook);

    _ECM_SET_STACK_CHANGED();
}

static void
ECompMgrWinDamage(EObj *eo, XEvent *ev)
{
    ECmWinInfo     *cw = eo->cmhook;
    XDamageNotifyEvent *de = (XDamageNotifyEvent *) ev;
    EX_SrvRegion    parts;

    D2printf("%s: %#lx %#x damaged=%d %d,%d %dx%d\n", __func__,
             ev->xany.window, EobjGetXwin(eo), cw->damaged,
             de->area.x, de->area.y, de->area.width, de->area.height);

    if (!cw->damaged)
    {
        parts = cw->extents;
        XDamageSubtract(disp, cw->damage, NoXID, NoXID);
        cw->damaged = 1;
    }
    else
    {
        parts = Mode_compmgr.rgn_tmp;
        XDamageSubtract(disp, cw->damage, NoXID, parts);
        ERegionTranslate(parts, EobjGetX(eo) + EobjGetBW(eo),
                         EobjGetY(eo) + EobjGetBW(eo));
    }
    eo->serial = ev->xany.serial;
    eo->damaged = 1;
    ECompMgrDamageMergeObject(eo, parts);

    if (eo->type == EOBJ_TYPE_EWIN)
        ModulesSignal(ESIGNAL_EWIN_DAMAGE, eo);
}

static void
ECompMgrWinDumpInfo(const char *txt, EObj *eo, EX_SrvRegion rgn, int ipc)
{
    void            (*prf)(const char *fmt, ...);
    ECmWinInfo     *cw = eo->cmhook;

    prf = (ipc) ? IpcPrintf : Eprintf;

    prf("%s %#x: %d,%d %dx%d: %s\n", txt, EobjGetXwin(eo),
        EobjGetX(eo), EobjGetY(eo), EobjGetW(eo), EobjGetH(eo),
        EobjGetName(eo));
    if (!cw)
    {
        prf("Not managed\n");
        return;
    }

    if (ipc || EDebug(EDBUG_TYPE_COMPMGR3))
    {
        prf(" - pict=%#x pmap=%#x\n", cw->picture, cw->pixmap);

        ERegionShow("win extents", cw->extents, prf);
        ERegionShow("win shape  ", cw->shape, prf);
        ERegionShow("win clip   ", cw->clip, prf);
        if (rgn != NoXID)
            ERegionShow("region", rgn, prf);
    }
}

static void
ECompMgrDestroyClip(void)
{
    EObj           *eo, *const *lst;
    ECmWinInfo     *cw;
    int             i, num;

    lst = EobjListStackGet(&num);
    for (i = 0; i < num; i++)
    {
        eo = lst[i];
        cw = eo->cmhook;
        if (!cw)
            continue;
        cw->have_clip = 0;
    }
}

static int
ECompMgrDetermineOrder(EObj *const *lst, int num, EObj **first,
                       EObj **last, Desk *dsk, EX_SrvRegion clip)
{
    EObj           *eo, *eo_prev, *eo_first;
    int             i, stop;
    ECmWinInfo     *cw;

    D1printf("%s: %d\n", __func__, dsk->num);
    if (!lst)
        lst = EobjListStackGet(&num);
    if (clip == NoXID)
    {
        ECompMgrDestroyClip();
        clip = Mode_compmgr.rgn_clip;
        ERegionEmpty(clip);
    }

    /* Determine overall paint order, top to bottom */
    stop = 0;
    eo_first = eo_prev = NULL;
    Mode_compmgr.ghosts = 0;

    for (i = 0; i < num; i++)
    {
        eo = lst[i];

        cw = eo->cmhook;

        if (!cw)
            continue;

        if ((!eo->shown && !eo->fading) || eo->desk != dsk)
            continue;

        if (EobjHasEmptyShape(eo))
            continue;

        /* Region of shaped window in screen coordinates */
        if (!cw->have_shape)
            ECompMgrWinSetShape(eo);

        /* Region of window in screen coordinates, including shadows */
        if (!cw->have_extents)
            ECompMgrWinSetExtents(eo);

        D3printf(" - %#x desk=%d shown=%d fading=%d fadeout=%d\n",
                 EobjGetXwin(eo), eo->desk->num, eo->shown, eo->fading,
                 cw->fadeout);

        if (eo->type == EOBJ_TYPE_DESK)
        {
            EObj           *eo1, *eo2;
            Desk           *d = (Desk *) eo;

            if (!d->viewable)
                continue;

#if USE_CLIP_RELATIVE_TO_DESK
            ERegionTranslate(clip, -EoGetX(d), -EoGetY(d));
#endif
            stop = ECompMgrDetermineOrder(lst, num, &eo1, &eo2, d, clip);
#if USE_CLIP_RELATIVE_TO_DESK
            ERegionTranslate(clip, EoGetX(d), EoGetY(d));
#endif
            if (eo1)
            {
                if (!eo_first)
                    eo_first = eo1;
                if (eo_prev)
                    eo_prev->cmhook->next = eo1;
                eo1->cmhook->prev = eo_prev;
                eo_prev = eo2;
            }

#if USE_BG_WIN_ON_ALL_DESKS     /* Only if using per desk bg overlay */
            /* FIXME - We should break when the clip region becomes empty */
            if (EobjGetX(eo) == 0 && EobjGetY(eo) == 0)
                stop = 1;
            if (stop)
                break;
#endif
        }

        if (cw->clip == NoXID)
            cw->clip = ERegionCreate();
        ERegionCopy(cw->clip, clip);
        cw->have_clip = 1;

        ECompMgrWinSetPicts(eo);

        D3printf(" - %#x desk=%d shown=%d dam=%d pict=%#x\n",
                 EobjGetXwin(eo), eo->desk->num, eo->shown, cw->damaged,
                 cw->picture);

#if 0                           /* FIXME - Need this? */
        if (!cw->damaged)
            continue;
#endif
        if (cw->picture == NoXID && !eo->noredir)
            continue;

        if (eo->ghost)
        {
            Mode_compmgr.ghosts = 1;
            continue;
        }

        D3printf("%s: hook in %d - %#x desk=%d shown=%d\n", __func__,
                 dsk->num, EobjGetXwin(eo), eo->desk->num, eo->shown);

        if (!eo_first)
            eo_first = eo;
        cw->prev = eo_prev;
        if (eo_prev)
            eo_prev->cmhook->next = eo;
        eo_prev = eo;

        switch (cw->mode)
        {
        case WINDOW_UNREDIR:
        case WINDOW_SOLID:
            D3printf("-   clip %#x %#x %d,%d %dx%d: %s\n", EobjGetXwin(eo),
                     cw->clip, EobjGetX(eo), EobjGetY(eo), EobjGetW(eo),
                     EobjGetH(eo), EobjGetName(eo));
#if USE_CLIP_RELATIVE_TO_DESK
            ERegionUnion(clip, cw->shape);
#else
            ERegionUnionOffset(clip, EoGetX(dsk), EoGetY(dsk), cw->shape,
                               Mode_compmgr.rgn_tmp);
#endif
            break;

        default:
            D3printf("- noclip %#x %#x %d,%d %dx%d: %s\n", EobjGetXwin(eo),
                     cw->clip, EobjGetX(eo), EobjGetY(eo), EobjGetW(eo),
                     EobjGetH(eo), EobjGetName(eo));
            break;
        }

#if !USE_BG_WIN_ON_ALL_DESKS    /* Not if using per desk bg overlay */
        /* FIXME - We should break when the clip region becomes empty */
        if (eo->type == EOBJ_TYPE_DESK &&
            EobjGetX(eo) == 0 && EobjGetY(eo) == 0)
            stop = 1;
        if (stop)
            break;
#endif
    }
    if (eo_prev)
        eo_prev->cmhook->next = NULL;

    *first = eo_first;
    *last = eo_prev;

    Mode_compmgr.reorder = 0;
    return stop;
}

static          EX_SrvRegion
ECompMgrRepaintObjSetClip(EX_SrvRegion rgn, EX_SrvRegion damage,
                          EX_SrvRegion clip, int x, int y)
{
    ERegionCopy(rgn, damage);
#if USE_CLIP_RELATIVE_TO_DESK
    ERegionSubtractOffset(rgn, x, y, clip, Mode_compmgr.rgn_tmp);
#else
    ERegionSubtract(rgn, clip);
    x = y = 0;
#endif
    return rgn;
}

static          EX_SrvRegion
ECompMgrRepaintObjSetClip2(EObj *eo, EX_SrvRegion clip, int x, int y)
{
#if 1
    /* This is only needed when source clipping in XRenderComposite() is broken.
     * otherwise it should be possible to set the source clip mask in
     * ECompMgrWinSetPicts() (when needed, i.e. source pict is pixmap). */
    if (WinIsShaped(EobjGetWin(eo)))
    {
        clip = ERegionCopy(Mode_compmgr.rgn_tmp, clip);
        ERegionIntersectOffset(clip, x, y, eo->cmhook->shape,
                               Mode_compmgr.rgn_tmp2);
    }
#else
    eo = NULL;
    x = y = 0;
#endif
    return clip;
}

static void
ECompMgrRepaintObj(EX_Picture pbuf, EX_SrvRegion region, EObj *eo, int mode)
{
    static EX_SrvRegion rgn_clip = NoXID;
    ECmWinInfo     *cw;
    Desk           *dsk = eo->desk;
    int             x, y;
    EX_SrvRegion    clip, clip2;
    EX_Picture      alpha;

    cw = eo->cmhook;

    if (rgn_clip == NoXID)
        rgn_clip = ERegionCreate();

    x = EoGetX(dsk);
    y = EoGetY(dsk);

    if (mode == 0)
    {
        /* Painting opaque windows top down. */
#if 0
        if (ERegionIsEmpty(clip))
        {
            D2printf(" - Quit - repaint region is empty\n");
            return;
        }
#endif

        switch (cw->mode)
        {
        case WINDOW_UNREDIR:
        case WINDOW_SOLID:
            clip = ECompMgrRepaintObjSetClip(rgn_clip, region, cw->clip, x, y);
            clip2 = ECompMgrRepaintObjSetClip2(eo, clip, x, y);
            if (EDebug(EDBUG_TYPE_COMPMGR2))
                ECompMgrWinDumpInfo("ECompMgrRepaintObj solid", eo, clip, 0);
            EPictureSetClip(pbuf, clip2);
            XRenderComposite(disp, PictOpSrc, cw->picture, NoXID, pbuf,
                             0, 0, 0, 0, x + cw->rcx, y + cw->rcy, cw->rcw,
                             cw->rch);
            break;
        }
    }
    else
    {
        /* Painting trans stuff bottom up. */

        switch (cw->mode)
        {
        default:
            clip = NoXID;
            break;

        case WINDOW_TRANS:
        case WINDOW_ARGB:
            clip = ECompMgrRepaintObjSetClip(rgn_clip, region, cw->clip, x, y);
            clip2 = ECompMgrRepaintObjSetClip2(eo, clip, x, y);
            if (EDebug(EDBUG_TYPE_COMPMGR2))
                ECompMgrWinDumpInfo("ECompMgrRepaintObj trans", eo, clip, 0);
            EPictureSetClip(pbuf, clip2);
            if (cw->opacity != OPAQUE && !cw->pict_alpha)
                cw->pict_alpha =
                    EPictureCreateSolid(Mode_compmgr.root, True,
                                        OP32To8(cw->opacity),
                                        Conf_compmgr.shadows.color);
            XRenderComposite(disp, PictOpOver, cw->picture, cw->pict_alpha,
                             pbuf, 0, 0, 0, 0, x + cw->rcx, y + cw->rcy,
                             cw->rcw, cw->rch);
            break;
        }

#if ENABLE_SHADOWS
        if (!cw->has_shadow)
            return;

        if (clip == NoXID)
            clip = ECompMgrRepaintObjSetClip(rgn_clip, region, cw->clip, x, y);
        ERegionSubtractOffset(clip, x, y, cw->shape, Mode_compmgr.rgn_tmp);

        switch (Mode_compmgr.shadow_mode)
        {
        case ECM_SHADOWS_SHARP:
        case ECM_SHADOWS_ECHO:
            if (cw->opacity != OPAQUE && !cw->shadow_alpha)
                cw->shadow_alpha =
                    EPictureCreateSolid(Mode_compmgr.root, True,
                                        OP32To8(cw->opacity *
                                                Mode_compmgr.opac_sharp),
                                        Conf_compmgr.shadows.color);
            alpha = cw->shadow_alpha ? cw->shadow_alpha : transBlackPicture;
            ERegionIntersectOffset(clip, cw->shadow_dx, cw->shadow_dy,
                                   cw->shape, Mode_compmgr.rgn_tmp);
            EPictureSetClip(pbuf, clip);
            if (Mode_compmgr.shadow_mode == ECM_SHADOWS_SHARP)
                XRenderComposite(disp, PictOpOver, alpha, cw->picture, pbuf,
                                 0, 0, 0, 0,
                                 x + cw->rcx + cw->shadow_dx,
                                 y + cw->rcy + cw->shadow_dy,
                                 cw->shadow_width, cw->shadow_height);
            else
                XRenderComposite(disp, PictOpOver, cw->picture, alpha, pbuf,
                                 0, 0, 0, 0,
                                 x + cw->rcx + cw->shadow_dx,
                                 y + cw->rcy + cw->shadow_dy,
                                 cw->shadow_width, cw->shadow_height);
            break;

        case ECM_SHADOWS_BLURRED:
            if (cw->shadow_pict == NoXID)
                break;

            if (cw->opacity != OPAQUE && !cw->pict_alpha)
                cw->pict_alpha =
                    EPictureCreateSolid(Mode_compmgr.root, True,
                                        OP32To8(cw->opacity),
                                        Conf_compmgr.shadows.color);
            alpha = (cw->pict_alpha) ? cw->pict_alpha : transBlackPicture;
            EPictureSetClip(pbuf, clip);
            XRenderComposite(disp, PictOpOver, alpha, cw->shadow_pict, pbuf,
                             0, 0, 0, 0,
                             x + cw->rcx + cw->shadow_dx,
                             y + cw->rcy + cw->shadow_dy,
                             cw->shadow_width, cw->shadow_height);
            break;
        }
#endif
    }
}

static void
ECompMgrPaintGhosts(EX_Picture pict, EX_SrvRegion damage)
{
    EObj           *eo, *const *lst;
    int             i, num;

    lst = EobjListStackGet(&num);
    for (i = 0; i < num; i++)
    {
        eo = lst[i];
        if (!eo->shown || !eo->ghost)
            continue;

        switch (eo->cmhook->mode)
        {
        case WINDOW_UNREDIR:
        case WINDOW_SOLID:
            ECompMgrRepaintObj(pict, Mode_compmgr.rgn_screen, eo, 0);
            break;
        case WINDOW_TRANS:
        case WINDOW_ARGB:
            ECompMgrRepaintObj(pict, Mode_compmgr.rgn_screen, eo, 1);
            break;
        }

        /* Subtract window region from damage region */
        ERegionSubtract(damage, eo->cmhook->shape);
    }
}

void
ECompMgrRepaint(void)
{
    EObj           *eo;
    EX_Picture      pbuf;
    Desk           *dsk = DeskGet(0);

    if (!Mode_compmgr.active || !Mode_compmgr.got_damage)
        return;

    ERegionIntersect(Mode_compmgr.damage, Mode_compmgr.rgn_screen);

    D2printf("%s: rootBuffer=%#x rootPicture=%#x\n", __func__,
             rootBuffer, rootPicture);
    if (EDebug(EDBUG_TYPE_COMPMGR2))
        ERegionShow("damage", Mode_compmgr.damage, NULL);

    pbuf = rootBuffer;

    if (!dsk)
        return;

    /* Do paint order list linking */
    if (Mode_compmgr.reorder)
        ECompMgrDetermineOrder(NULL, 0, &Mode_compmgr.eo_first,
                               &Mode_compmgr.eo_last, dsk, NoXID);

    /* Paint opaque windows top down */
    for (eo = Mode_compmgr.eo_first; eo; eo = eo->cmhook->next)
        ECompMgrRepaintObj(pbuf, Mode_compmgr.damage, eo, 0);

#if 0                           /* FIXME - NoBg? */
    EX_Picture      pict;

    if (EDebug(EDBUG_TYPE_COMPMGR2))
        ERegionShow("after opaque", region, NULL);

    /* Repaint background, clipped by damage region and opaque windows */
    pict = dsk->o.cmhook->picture;
    D1printf("%s: desk picture=%#lx\n", __func__, pict);
    EPictureSetClip(pbuf, region);
    XRenderComposite(disp, PictOpSrc, pict, NoXID, pbuf,
                     0, 0, 0, 0, 0, 0, WinGetW(VROOT), WinGetH(VROOT));
#endif

    /* Paint trans windows and shadows bottom up */
    for (eo = Mode_compmgr.eo_last; eo; eo = eo->cmhook->prev)
        ECompMgrRepaintObj(pbuf, Mode_compmgr.damage, eo, 1);

    /* Paint any ghost windows (adjusting damage region) */
    if (Mode_compmgr.ghosts)
        ECompMgrPaintGhosts(rootPicture, Mode_compmgr.damage);

    if (pbuf != rootPicture)
    {
        EPictureSetClip(pbuf, NoXID);
        EPictureSetClip(rootPicture, Mode_compmgr.damage);
#if !USE_XPRESENT
        XRenderComposite(disp, PictOpSrc, pbuf, NoXID, rootPicture,
                         0, 0, 0, 0, 0, 0, WinGetW(VROOT), WinGetH(VROOT));
#else
        XPresentPixmap(disp, Mode_compmgr.root, Mode_compmgr.pmap,
                       Mode_compmgr.present_serial++, None, None, 0, 0, None,
                       None, None, None, 0, 0, 0, NULL, 0);
#endif
    }

    Mode_compmgr.got_damage = 0;
}

int
ECompMgrRender(int dt)
{
    static unsigned int ecm_render_last = 0;
    unsigned int    tnow;
    int             dt_rendr, dt_frame;

    /* Do we get here on auto? */
    if (!Mode_compmgr.got_damage /* || Mode_compmgr.mode == ECM_MODE_AUTO */ )
        return dt;

    tnow = GetTimeMs();
    dt_rendr = tnow - ecm_render_last;  /* May be < 0 on startup */
    dt_frame = 1000 / Mode.screen.fps;
    if (dt_rendr >= dt_frame || dt_rendr < 0)
    {
        ecm_render_last = tnow;
        ECompMgrRepaint();
        return dt;
    }
    else
    {
        dt_rendr = dt_frame - dt_rendr;
        return dt == 0 || dt > dt_rendr ? dt_rendr : dt;
    }
}

static void
ECompMgrRootBufferCreate(unsigned int w, unsigned int h)
{
    /* Root buffer picture and pixmap */
    rootBuffer = EPictureCreateBuffer(VROOT, w, h, 0, &Mode_compmgr.pmap);

    /* Screen region */
    Mode_compmgr.rgn_screen = ERegionCreateRect(0, 0, w, h);

    /* Overall clip region used while recalculating window clip regions */
    Mode_compmgr.rgn_clip = ERegionCreate();
}

static void
ECompMgrRootBufferDestroy(void)
{
    PICTURE_DESTROY(rootBuffer);
    PIXMAP_DESTROY(Mode_compmgr.pmap);

    REGION_DESTROY(Mode_compmgr.rgn_screen);
    REGION_DESTROY(Mode_compmgr.rgn_clip);
}

EX_Pixmap
ECompMgrGetRootBuffer(void)
{
    return (Mode_compmgr.pmap != NoXID) ? Mode_compmgr.pmap : WinGetXwin(VROOT);
}

static void
ECompMgrRootConfigure(void *prm __UNUSED__, XEvent *ev)
{
    D1printf("%s\n", __func__);

    ECompMgrRootBufferDestroy();
    ECompMgrRootBufferCreate(ev->xconfigure.width, ev->xconfigure.height);
}

#if USE_DESK_EXPOSE             /* FIXME - Remove? */
static void
ECompMgrRootExpose(void *prm __UNUSED__, XEvent *ev)
{
    static XRectangle *expose_rects = 0;
    static int      size_expose = 0;
    static int      n_expose = 0;
    int             more = ev->xexpose.count + 1;

    if (ev->xexpose.window != WinGetXwin(VROOT))
        return;

    D1printf("%s: %d %d %d\n", __func__, n_expose, size_expose,
             ev->xexpose.count);

    if (n_expose == size_expose)
    {
        expose_rects = EREALLOC(XRectangle, expose_rects, size_expose + more);
        size_expose += more;
    }
    expose_rects[n_expose].x = ev->xexpose.x;
    expose_rects[n_expose].y = ev->xexpose.y;
    expose_rects[n_expose].width = ev->xexpose.width;
    expose_rects[n_expose].height = ev->xexpose.height;
    n_expose++;
    if (ev->xexpose.count == 0)
    {
        EX_SrvRegion    region;

        region = ERegionCreateFromRects(expose_rects, n_expose);

        ECompMgrDamageMerge(region);
        ERegionDestroy(region);
        n_expose = 0;
    }
}
#endif

#if ENABLE_SHADOWS
static void
ECompMgrShadowsInit(int mode, int cleanup)
{
    Mode_compmgr.shadow_mode = mode;

    Conf_compmgr.shadows.blur.opacity =
        OpacityFix(Conf_compmgr.shadows.blur.opacity, 100);
    Mode_compmgr.opac_blur = .01f * Conf_compmgr.shadows.blur.opacity;
    Conf_compmgr.shadows.sharp.opacity =
        OpacityFix(Conf_compmgr.shadows.sharp.opacity, 100);
    Mode_compmgr.opac_sharp = .01f * Conf_compmgr.shadows.sharp.opacity;

    EFREE_NULL(gaussianMap);

    if (mode != ECM_SHADOWS_OFF)
    {
        if (mode == ECM_SHADOWS_BLURRED)
            transBlackPicture =
                EPictureCreateSolid(Mode_compmgr.root, True, 255,
                                    Conf_compmgr.shadows.color);
        else
            transBlackPicture =
                EPictureCreateSolid(Mode_compmgr.root, True,
                                    OP32To8(Mode_compmgr.opac_sharp * OPAQUE),
                                    Conf_compmgr.shadows.color);
    }
    else
    {
        PICTURE_DESTROY(transBlackPicture);
    }

    if (cleanup)
    {
        EObj           *const *lst;
        int             i, num;

        lst = EobjListStackGet(&num);
        for (i = 0; i < num; i++)
            ECompMgrWinInvalidate(lst[i], INV_SHADOW);
        _ECM_SET_SHADOW_CHANGED();      /* Force extents/shadow update */
    }
}
#else
#define ECompMgrShadowsInit(mode, cleanup)
#endif

int
ECompMgrIsActive(void)
{
    return Mode_compmgr.active;
}

EX_Window
ECompMgrRootWin(void)
{
    return Mode_compmgr.root;
}

static void
ECompMgrStart(void)
{
    EObj           *const *lst;
    int             i, num;

    if (Mode_compmgr.active || Conf_compmgr.mode == ECM_MODE_OFF)
        return;

    if (!Mode.wm.window)
    {
        wm_cm_sel = SelectionAcquire("_NET_WM_CM_S", NULL, NULL);
        if (!wm_cm_sel)
            return;
    }

    Mode_compmgr.mode = Conf_compmgr.mode;

    ESync(0);
    Dpy.last_error_code = 0;

    switch (Mode_compmgr.mode)
    {
    case ECM_MODE_ROOT:
        XCompositeRedirectSubwindows(disp, WinGetXwin(VROOT),
                                     CompositeRedirectManual);
#if USE_DESK_EXPOSE             /* FIXME - Remove? */
        ESelectInputChange(VROOT, ExposureMask, 0);
#endif
        break;
    case ECM_MODE_WINDOW:
#if USE_DESK_EXPOSE             /* FIXME - Remove? */
        ESelectInputChange(VROOT, ExposureMask, 0);
#endif
        break;
    case ECM_MODE_AUTO:
        XCompositeRedirectSubwindows(disp, WinGetXwin(VROOT),
                                     CompositeRedirectAutomatic);
        break;
    }

    ESync(0);
    if (Dpy.last_error_code != 0)
    {
        Eprintf("Error: Compositing init failed\n");
        Conf_compmgr.enable = 0;
        return;
    }

    Conf_compmgr.enable = Mode_compmgr.active = 1;

    Conf_compmgr.override_redirect.opacity =
        OpacityFix(Conf_compmgr.override_redirect.opacity, 100);

    Mode_compmgr.root = WinGetXwin(VROOT);
#if USE_COMPOSITE_OVERLAY_WINDOW
    if (Conf_compmgr.use_cow && !Mode.wm.window)
    {
        Mode_compmgr.cow = XCompositeGetOverlayWindow(disp, WinGetXwin(VROOT));
        if (Mode_compmgr.cow != NoXID)
        {
            /* Ok, got the cow! */
            Mode_compmgr.root = Mode_compmgr.cow;
            /* It is possible to get it stacked below others?!? */
            XRaiseWindow(disp, Mode_compmgr.cow);
            /* Pass all input events through */
            XShapeCombineRectangles(disp, Mode_compmgr.cow, ShapeInput, 0, 0,
                                    NULL, 0, ShapeSet, Unsorted);
            D1printf("COW/CMroot=%#x/%#x\n",
                     Mode_compmgr.cow, Mode_compmgr.root);
        }
    }
    else
    {
        Mode_compmgr.cow = NoXID;
    }
#endif

    Mode_compmgr.got_damage = 0;

    ECompMgrRootBufferCreate(WinGetW(VROOT), WinGetH(VROOT));

    rootPicture = EPictureCreateII(VROOT, Mode_compmgr.root);

    Mode_compmgr.rgn_tmp = ERegionCreate();
    Mode_compmgr.rgn_tmp2 = ERegionCreate();

    ECompMgrShadowsInit(Conf_compmgr.shadows.mode, 0);

    EventCallbackRegister(VROOT, ECompMgrHandleRootEvent, NULL);

    EGrabServer();

    lst = EobjListStackGet(&num);
    for (i = 0; i < num; i++)
    {
        ECompMgrWinNew(lst[i]);
        if (lst[i]->shown)
            ECompMgrWinMap(lst[i]);
    }

    if (!Mode.wm.startup)       /* If CM is enabled after startup, */
        EwinsManage();          /* add the currently mapped OR windows. */

#if !USE_BG_WIN_ON_ALL_DESKS
    DesksBackgroundRefresh(NULL, DESK_BG_RECONFIGURE_ALL);
#endif
    _ECM_SET_CLIP_CHANGED();
    EUngrabServer();
    ESync(0);
}

static void
ECompMgrStop(void)
{
    EObj           *const *lst1, **lst;
    int             i, num;

    if (!Mode_compmgr.active)
        return;
    Conf_compmgr.enable = Mode_compmgr.active = 0;

    EGrabServer();

    SelectionRelease(wm_cm_sel);
    wm_cm_sel = NULL;

    lst1 = EobjListStackGet(&num);
    if (num > 0)
    {
        lst = EMALLOC(EObj *, num);
        if (lst)
        {
            memcpy(lst, lst1, num * sizeof(EObj *));
            for (i = 0; i < num; i++)
            {
                if (lst[i]->type == EOBJ_TYPE_EXT)
                    EobjUnregister(lst[i]);     /* Modifies the object stack! */
                else
                    ECompMgrWinDel(lst[i]);
            }
            Efree(lst);
        }
    }

    Mode_compmgr.got_damage = 0;
    REGION_DESTROY(Mode_compmgr.damage);

    ECompMgrShadowsInit(ECM_SHADOWS_OFF, 0);
    REGION_DESTROY(Mode_compmgr.rgn_tmp);
    REGION_DESTROY(Mode_compmgr.rgn_tmp2);
    PICTURE_DESTROY(rootPicture);
    ECompMgrRootBufferDestroy();

    if (Mode_compmgr.mode == ECM_MODE_ROOT)
        XCompositeUnredirectSubwindows(disp, WinGetXwin(VROOT),
                                       CompositeRedirectManual);

    EventCallbackUnregister(VROOT, ECompMgrHandleRootEvent, NULL);

#if USE_COMPOSITE_OVERLAY_WINDOW
    if (Mode_compmgr.cow != NoXID)
    {
        XCompositeReleaseOverlayWindow(disp, WinGetXwin(VROOT));
        Mode_compmgr.cow = NoXID;
    }
#endif

#if !USE_BG_WIN_ON_ALL_DESKS
    DesksBackgroundRefresh(NULL, DESK_BG_RECONFIGURE_ALL);
#endif
    Mode.events.damage_count = 0;
    EUngrabServer();
    ESync(0);
}

void
ECompMgrConfigGet(cfg_composite *cfg)
{
    cfg->enable = Conf_compmgr.enable;
    cfg->shadow = Conf_compmgr.shadows.mode;
    cfg->fading = Conf_compmgr.fading.enable;
    cfg->opacity_focused = Conf.opacity.focused;
    cfg->opacity_unfocused = Conf.opacity.unfocused;
    cfg->opacity_override = Conf_compmgr.override_redirect.opacity;
    cfg->fade_speed = 100 - (Conf_compmgr.fading.time / 10);
}

void
ECompMgrConfigSet(const cfg_composite *cfg)
{
    if (Conf_compmgr.mode == ECM_MODE_OFF)
    {
        if (cfg->enable)
            DialogOK("Enable Composite Error",
                     _("Cannot enable Composite Manager.\n"
                       "Use xdpyinfo to check that\n"
                       "Composite, Damage, Fixes, and Render\n"
                       "extensions are loaded."));
        return;
    }

    if (cfg->enable != Conf_compmgr.enable)
    {
        Conf_compmgr.enable = cfg->enable;
        Conf_compmgr.shadows.mode = cfg->shadow;
        if (cfg->enable)
            ECompMgrStart();
        else
            ECompMgrStop();
    }
    else
    {
        if (cfg->shadow != Conf_compmgr.shadows.mode)
        {
            Conf_compmgr.shadows.mode = cfg->shadow;
            if (Conf_compmgr.enable)
            {
                ECompMgrShadowsInit(Conf_compmgr.shadows.mode, 1);
                ECompMgrDamageAll();
            }
        }
    }

    Conf_compmgr.fading.enable = cfg->fading;
    Conf_compmgr.fading.time = (100 - cfg->fade_speed) * 10;

    Conf.opacity.focused = cfg->opacity_focused;
    Conf.opacity.unfocused = cfg->opacity_unfocused;
    Conf_compmgr.override_redirect.opacity = cfg->opacity_override;

    EobjsOpacityUpdate(Conf_compmgr.override_redirect.opacity);

    autosave();
}

/*
 * Event handlers
 */
#define USE_WINDOW_EVENTS 0

static void
ECompMgrHandleWindowEvent(Win win __UNUSED__, XEvent *ev, void *prm)
{
    EObj           *eo = (EObj *) prm;

    D2printf("%s: type=%d\n", __func__, ev->type);

    switch (ev->type)
    {
#if USE_WINDOW_EVENTS
    case ConfigureNotify:
        ECompMgrWinConfigure(eo, ev);
        break;

    case MapNotify:
        ECompMgrWinMap(eo);
        break;
    case UnmapNotify:
        if (eo->type == EOBJ_TYPE_EXT && eo->cmhook)
        {
            ECompMgrWinUnmap(eo);
            eo->shown = 0;
        }
        break;

    case CirculateNotify:
        ECompMgrWinCirculate(eo, ev);
        break;
#endif

#if USE_DESK_VISIBILITY
    case VisibilityNotify:
        ECompMgrDeskVisibility(eo, ev);
        break;
#endif

    case EX_EVENT_SHAPE_NOTIFY:
        /* Only EOBJ_TYPE_EXT window shape changes will go here */
        ECompMgrWinChangeShape(eo);
        break;

    case EX_EVENT_DAMAGE_NOTIFY:
        ECompMgrWinDamage(eo, ev);
        break;
    }
}

static void
ECompMgrHandleRootEvent(Win win __UNUSED__, XEvent *ev, void *prm)
{
    EX_Window       xwin;
    EObj           *eo;

    D2printf("%s: type=%d\n", __func__, ev->type);

    switch (ev->type)
    {
    case CreateNotify:
        xwin = ev->xcreatewindow.window;
      case_CreateNotify:
        if (Conf_compmgr.override_redirect.mode != ECM_OR_ON_CREATE)
            break;
        EobjRegisterOR(xwin, NULL, 0);
        break;

    case DestroyNotify:
        xwin = ev->xdestroywindow.window;
      case_DestroyNotify:
        eo = EobjListStackFind(xwin);
        if (eo && eo->type == EOBJ_TYPE_EXT)
        {
            if (ev->type == DestroyNotify)
                eo->gone = 1;
            EobjUnregister(eo);
        }
        break;

    case ReparentNotify:
    case EX_EVENT_REPARENT_GONE:
        xwin = ev->xreparent.window;
        if (ev->xreparent.parent == WinGetXwin(VROOT))
            goto case_CreateNotify;
        else
            goto case_DestroyNotify;

    case ConfigureNotify:
        if (ev->xconfigure.window == WinGetXwin(VROOT))
        {
            ECompMgrRootConfigure(prm, ev);
        }
        else
        {
            eo = EobjListStackFind(ev->xconfigure.window);
            if (eo && eo->type == EOBJ_TYPE_EXT && eo->cmhook)
            {
                ECompMgrWinConfigure(eo, ev);
            }
        }
        break;

    case MapNotify:
        xwin = ev->xmap.window;
#if USE_COMPOSITE_OVERLAY_WINDOW
        if (xwin == Mode_compmgr.cow)
            break;
#endif
        /* Register new override-redirect windows.
         * Normal client windows will never go here as they first will
         * generate a MapRequest (caught elsewhere) and then be reparented.
         * The client frame windows (on desk 0) and internal ones (with root
         * parent) will go here when mapped and will be ignored as they are
         * already registered. */
        EobjRegisterOR(xwin, NULL, 1);
        break;

    case UnmapNotify:
    case EX_EVENT_UNMAP_GONE:
        eo = EobjListStackFind(ev->xunmap.window);
        if (eo && eo->type == EOBJ_TYPE_EXT && eo->cmhook)
        {
            if (ev->type == EX_EVENT_UNMAP_GONE)
                eo->gone = 1;
#if 0
            /* No. Unredirection seems to cause map/unmap => loop */
            if (Conf_compmgr.override_redirect.mode == ECM_OR_ON_MAPUNMAP)
            {
                EobjUnregister(eo);
            }
            else
#endif
            {
                ECompMgrWinUnmap(eo);
                eo->shown = 0;
            }
        }
        break;

    case CirculateNotify:
        eo = EobjListStackFind(ev->xcirculate.window);
        if (eo && eo->cmhook)
            ECompMgrWinCirculate(eo, ev);
        break;

#if USE_DESK_EXPOSE             /* FIXME - Remove? */
    case Expose:
        if (Mode_compmgr.shadow_mode != ECM_SHADOWS_OFF)
            ECompMgrRootExpose(prm, ev);
        break;
#endif
    }
}

/*
 * Module functions
 */

static void
ECompMgrInit(void)
{
    if (!XEXT_AVAILABLE(XEXT_CM_ALL))
    {
        Conf_compmgr.mode = ECM_MODE_OFF;
        goto done;
    }

    Mode_compmgr.use_pixmap = Conf_compmgr.use_name_pixmap;

#if 0                           /* TBD - Force ECM_MODE_ROOT at startup */
    if (Conf_compmgr.mode == ECM_MODE_OFF)
#endif
        Conf_compmgr.mode = ECM_MODE_ROOT;

  done:
    if (Conf_compmgr.mode == ECM_MODE_OFF)
        Conf_compmgr.enable = 0;
    D1printf("%s: enable=%d mode=%d\n", __func__,
             Conf_compmgr.enable, Conf_compmgr.mode);
}

static void
ECompMgrSighan(int sig, void *prm __UNUSED__)
{
    if (sig != ESIGNAL_INIT && Mode_compmgr.mode == ECM_MODE_OFF)
        return;

    switch (sig)
    {
    case ESIGNAL_INIT:
        ECompMgrInit();
        if (Conf_compmgr.enable)
            ECompMgrStart();
        break;
    }
}

static void
CompMgrIpc(const char *params)
{
    const char     *p;
    char            cmd[128], prm[4096];
    int             len;

    cmd[0] = prm[0] = '\0';
    p = params;
    if (p)
    {
        len = 0;
        sscanf(p, "%100s %4000s %n", cmd, prm, &len);
        p += len;
    }

    if (!p || cmd[0] == '?')
    {
        IpcPrintf("CompMgr - on=%d\n", Mode_compmgr.active);
    }
    else if (!strcmp(cmd, "start"))
    {
        ECompMgrStart();
        autosave();
    }
    else if (!strcmp(cmd, "stop"))
    {
        ECompMgrStop();
        autosave();
    }
    else if (!strncmp(cmd, "list", 2))
    {
        /* TBD */
    }
    else if (!strncmp(cmd, "oi", 2))
    {
        EX_Window       win;
        EObj           *eo;

        win = NoXID;
        sscanf(prm, "%x", &win);
        eo = EobjListStackFind(win);
        if (eo)
            ECompMgrWinDumpInfo("EObj", eo, NoXID, 1);
    }
}

static const IpcItem CompMgrIpcArray[] = {
    {
     CompMgrIpc,
     "compmgr", "cm",
     "Composite manager functions",
     "  cm ?                     Show info\n"
     "  cm start                 Start composite manager\n"
     "  cm stop                  Stop composite manager\n" }
    ,
};

static const CfgItem CompMgrCfgItems[] = {
    CFG_ITEM_BOOL(Conf_compmgr, enable, 0),
    CFG_ITEM_INT(Conf_compmgr, mode, ECM_MODE_ROOT),
    CFG_ITEM_INT(Conf_compmgr, shadows.mode, 0),
    CFG_ITEM_INT(Conf_compmgr, shadows.offset_x, 3),
    CFG_ITEM_INT(Conf_compmgr, shadows.offset_y, 5),
    CFG_ITEM_INT(Conf_compmgr, shadows.blur.radius, 5),
    CFG_ITEM_INT(Conf_compmgr, shadows.blur.opacity, 75),
    CFG_ITEM_INT(Conf_compmgr, shadows.sharp.opacity, 30),
    CFG_ITEM_HEX(Conf_compmgr, shadows.color, 0),
    CFG_ITEM_BOOL(Conf_compmgr, use_name_pixmap, 0),
#if USE_COMPOSITE_OVERLAY_WINDOW
    CFG_ITEM_BOOL(Conf_compmgr, use_cow, 1),
#endif
    CFG_ITEM_BOOL(Conf_compmgr, fading.enable, 1),
    CFG_ITEM_INT(Conf_compmgr, fading.time, 200),
    CFG_ITEM_INT(Conf_compmgr, override_redirect.mode, 1),
    CFG_ITEM_INT(Conf_compmgr, override_redirect.opacity, 90),
};

/*
 * Module descriptor
 */
extern const EModule ModCompMgr;

const EModule   ModCompMgr = {
    "compmgr", "cm",
    ECompMgrSighan,
    MOD_ITEMS(CompMgrIpcArray),
    MOD_ITEMS(CompMgrCfgItems)
};

#endif                          /* USE_COMPOSITE */

/*
 * $Id: xcompmgr.c,v 1.26 2004/08/14 21:39:51 keithp Exp $
 *
 * Copyright © 2003 Keith Packard
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Keith Packard not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  Keith Packard makes no
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.
 *
 * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */
/*
 * Modified by Matthew Hawn. I don't know what to say here so follow what it 
 * says above. Not that I can really do anything about it
 */
