/*
 * Copyright (C) 2000-2007 Carsten Haitzler, Geoff Harrison and various contributors
 * 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.
 */
#include "config.h"

#include <X11/Xlib.h>
#if USE_XRENDER
#include <X11/extensions/Xrender.h>
#endif

#include "E.h"
#include "backgrounds.h"
#include "desktops.h"
#include "dialog.h"
#include "ecompmgr.h"
#include "emodule.h"
#include "ewins.h"
#include "focus.h"
#include "groups.h"
#include "hints.h"
#include "hiwin.h"
#include "iclass.h"
#include "list.h"
#include "menus.h"
#include "settings.h"
#include "timers.h"
#include "tooltips.h"
#include "xwin.h"

#define DEBUG_PAGER 0
#if DEBUG_PAGER
#define Dprintf(fmt...) if(EDebug(EDBUG_TYPE_PAGER))Eprintf(fmt)
#else
#define Dprintf(fmt...)
#endif

#define USE_PAGER_BACKGROUND_CACHE 1
#define HIQ ((Conf_pagers.hiq) ? 0x200 : 0x100)

#define PAGER_MODE_SIMPLE 0
#define PAGER_MODE_SNAP   1
#define PAGER_MODE_LIVE   2

#define PAGER_UPD_EWIN_GEOM     0
#define PAGER_UPD_EWIN_GONE     1
#define PAGER_UPD_EWIN_DAMAGE   2

#define EwinGetVX(ew) (ew->vx)
#define EwinGetVY(ew) (ew->vy)
#define EwinGetVX2(ew) (ew->vx + EoGetW(ew))
#define EwinGetVY2(ew) (ew->vy + EoGetH(ew))

static struct {
    char            enable;
    char            zoom;
    char            title;
    char            hiq;
    int             mode;
    int             scanspeed;
    int             sel_button;
    int             win_button;
    int             menu_button;
} Conf_pagers;

static struct {
    int             zoom;
    Idler          *idler;
    char            update_pending;
    char            timer_pending;
} Mode_pagers;

typedef struct {
    dlist_t         list;
    EWin           *ewin;
    Win             win;
    int             w, h;
    EX_Pixmap       pmap;       /* Pager window background pixmap */
#if USE_XRENDER
    EX_Picture      pict;       /* Pager window background picture */
    EX_Picture      bgpict;     /* Desktop background picture */
#else
    EX_Pixmap       bgpmap;     /* Desktop background pixmap */
#endif
    Desk           *dsk;
    int             dw, dh;
    int             screen_w, screen_h;
    int             update_phase;
    Win             sel_win;
    Timer          *scan_timer;

    /* State flags */
    char            do_newbg;
    char            do_update;
    int             x1, y1, x2, y2;
    float           scale;

    /* Demands attention */
    int             flash_count;
    Timer          *flash_timer;
} Pager;

static void     PagerScanCancel(Pager * p);
static int      PagerScanTimeout(void *data);
static void     PagerCheckUpdate(Pager * p, void *prm);
static void     PagerUpdateEwinsFromPager(Pager * p);
static void     PagerHiwinHide(void);
static void     PagerEvent(Win win, XEvent * ev, void *prm);
static void     PagerHiwinEvent(Win win, XEvent * ev, void *prm);
static int      PagerFlashTimer(void *data);

static          LIST_HEAD(pager_list);

static Hiwin   *hiwin = NULL;

static int
PagersGetMode(void)
{
    int             mode = Conf_pagers.mode;

    if (mode == PAGER_MODE_LIVE && !ECompMgrIsActive())
        mode = PAGER_MODE_SNAP;

    return mode;
}

static Pager   *
PagerCreate(void)
{
    Pager          *p;

    if (!Conf_pagers.enable)
        return NULL;

    p = ECALLOC(Pager, 1);
    if (!p)
        return NULL;

    LIST_APPEND(Pager, &pager_list, p);

    p->win = ECreateClientWindow(VROOT, 0, 0, 1, 1);
    EventCallbackRegister(p->win, PagerEvent, p);
    p->sel_win = ECreateWindow(p->win, 0, 0, 1, 1, 0);

    return p;
}

static void
_PagerCanvasInit(Pager *p, bool create)
{
    if (p->pmap != NoXID)
        EFreePixmap(p->pmap);
    p->pmap = NoXID;
#if USE_XRENDER
    if (p->pict != NoXID)
        EPictureDestroy(p->pict);
    p->pict = NoXID;
#endif

    if (!create)
        return;                 /* Just cleaning up */

    p->pmap = ECreatePixmap(p->win, p->w, p->h, 0);
#if USE_XRENDER
    p->pict = EPictureCreate(NULL, p->pmap);
#endif

    ESetWindowBackgroundPixmap(p->win, p->pmap, 1);
}

static void
_PagerBgBufferInit(Pager *p, EX_Pixmap *ppmap)
{
#if USE_XRENDER
    XRenderPictureAttributes pa;

    if (p->bgpict != NoXID)
        EPictureDestroy(p->bgpict);
    p->bgpict = NoXID;
#else
    if (p->bgpmap != NoXID)
        EFreePixmap(p->bgpmap);
    p->bgpmap = NoXID;
#endif

    if (!ppmap)
        return;                 /* Just cleaning up */

#if USE_XRENDER
    p->bgpict = EPictureCreateBuffer(p->win, p->dw, p->dh, 0, ppmap);
    pa.repeat = True;
    XRenderChangePicture(disp, p->bgpict, CPRepeat, &pa);
#else
    p->bgpmap = ECreatePixmap(p->win, p->dw, p->dh, 0);
    *ppmap = p->bgpmap;
#endif
}

static void
PagerDestroy(Pager *p)
{
    LIST_REMOVE(Pager, &pager_list, p);

    if (p->flash_timer)
        TimerDel(p->flash_timer);
    PagerScanCancel(p);
    PagerHiwinHide();
    _PagerBgBufferInit(p, NULL);
    _PagerCanvasInit(p, false);

    Efree(p);
}

static void
PagerScanTrig(Pager *p)
{
    if (p->scan_timer || Conf_pagers.scanspeed <= 0)
        return;

    TIMER_ADD(p->scan_timer, 1000 / Conf_pagers.scanspeed, PagerScanTimeout, p);
}

static void
PagerScanCancel(Pager *p)
{
    TIMER_DEL(p->scan_timer);
}

static int
PagerScanTimeout(void *data)
{
    static const char offsets[8] = { 0, 4, 2, 6, 1, 5, 3, 7 };
    Pager          *p = (Pager *) data;
    EWin           *ewin;
    int             y, y2, phase, cx, cy, ww, hh, xx, yy;
    int             pager_mode = PagersGetMode();

    if (pager_mode != PAGER_MODE_SNAP)
        goto nomore;

    if (Mode.events.block)
        return 1;

    ewin = p->ewin;
    if (!ewin || !EoIsShown(ewin))
        goto nomore;
    if (p->dsk != DesksGetCurrent())
        goto nomore;
    if (ewin->state.visibility == VisibilityFullyObscured)
        goto nomore;

    if (Conf_pagers.scanspeed <= 0)
        goto nomore;

    if (Mode.mode != MODE_NONE)
        goto nomore;

    DeskCurrentGetArea(&cx, &cy);
    ww = p->dw;
    hh = p->dh;
    xx = cx * ww;
    yy = cy * hh;
    phase = p->update_phase;
    if (ww <= 0 || hh <= 0)
        goto nomore;

#if 0
    /* Due to a bug in imlib2 <= 1.2.0 we have to scan left->right in stead
     * of top->bottom, at least for now. */
    y = ((phase & 0xfffffff8) + offsets[phase % 8]) % hh;
    y2 = (y * WinGetH(VROOT)) / hh;

    ScaleRect(VROOT, WinGetXwin(VROOT), p->win, WinGetPmap(p->win), 0,
              y2, WinGetW(VROOT), WinGetH(VROOT) / hh, xx, yy + y, ww, 1, HIQ);
    EClearArea(p->win, xx, yy + y, ww, 1);
    y2 = p->h;
#else
    y = ((phase & 0xfffffff8) + offsets[phase % 8]) % ww;
    y2 = (y * WinGetW(VROOT)) / ww;

    ScaleRect(VROOT, WinGetXwin(VROOT), p->win, WinGetPmap(p->win), y2,
              0, WinGetW(VROOT) / ww, WinGetH(VROOT), xx + y, yy, 1, hh, HIQ);
    EClearArea(p->win, xx + y, yy, 1, hh);
    y2 = p->w;
#endif
    p->update_phase++;
    if (p->update_phase >= y2)
    {
        PagerUpdateEwinsFromPager(p);
        p->update_phase = 0;
    }

    TimerSetInterval(p->scan_timer, 1000 / Conf_pagers.scanspeed);
    return 1;

  nomore:
    p->scan_timer = NULL;
    return 0;
}

#if 0                           /* FIXME - Remove? */
static void
PagerHiwinUpdate(Hiwin *phi, Pager *p __UNUSED__, EWin *ewin)
{
    EImage         *im;

    if (!EoIsShown(phi) || !ewin->mini_pmm.pmap)
        return;

    im = EImageGrabDrawable(ewin->mini_pmm.pmap, NoXID, 0, 0,
                            ewin->mini_pmm.w, ewin->mini_pmm.h, 0);
    EImageRenderOnDrawable(im, EoGetWin(phi), 0,
                           0, 0, EoGetW(phi), EoGetH(phi));
    EImageDecache(im);
}
#endif

static void
PagerEwinUpdateMini(Pager *p, EWin *ewin)
{
    int             w, h, update, use_iclass;
    EX_Drawable     draw;
    int             pager_mode = PagersGetMode();

    w = (EoGetW(ewin) * p->dw) / WinGetW(VROOT);
    h = (EoGetH(ewin) * p->dh) / WinGetH(VROOT);

    if (w < 1)
        w = 1;
    if (h < 1)
        h = 1;

    update = 0;
    if (!ewin->mini_pmm.pmap)
        update = 1;
    if (ewin->mini_pmm.w != w || ewin->mini_pmm.h != h)
        update = 1;

    if (EoIsDamaged(ewin) && ewin->type != EWIN_TYPE_PAGER &&
        pager_mode == PAGER_MODE_LIVE && Mode.mode == 0)
        update = 1;

    if (!update)
        return;

    Dprintf("%s %#x/%#x wxh=%dx%d ser=%#x dmg=%d: %s\n", __func__,
            EwinGetClientXwin(ewin), EoGetXwin(ewin), w, h,
            EoGetSerial(ewin), EoIsDamaged(ewin), EwinGetTitle(ewin));

    EoClrDamaged(ewin);

    p->do_update = 1;

    PmapMaskInit(&ewin->mini_pmm, EoGetWin(ewin), w, h);

    draw = NoXID;
    if (pager_mode != PAGER_MODE_SIMPLE)
    {
        draw = EoGetPixmap(ewin);
        if (draw == NoXID && EwinIsOnScreen(ewin))
            draw = EoGetXwin(ewin);
    }
    use_iclass = draw == NoXID;

    if (use_iclass)
    {
        ImageClass     *ic;

        ic = ImageclassFind("PAGER_WIN", 1);
        ImageclassApplySimple(ic, EoGetWin(ewin), ewin->mini_pmm.pmap,
                              STATE_NORMAL, 0, 0, w, h);
        Dprintf("Use Iclass, pmap=%#x\n", ewin->mini_pmm.pmap);
    }
    else
    {
        ScaleRect(EoGetWin(ewin), draw, EoGetWin(ewin), ewin->mini_pmm.pmap,
                  0, 0, EoGetW(ewin), EoGetH(ewin), 0, 0, w, h, HIQ);
        Dprintf("Grab scaled, pmap=%#x\n", ewin->mini_pmm.pmap);
    }

#if 0                           /* FIXME - Remove? */
    if (hiwin && ewin == hiwin->ewin)
        PagerHiwinUpdate(hiwin, p, ewin);
#endif
}

static void
doPagerUpdate(Pager *p)
{
    int             cx, cy, vx, vy;
    EWin           *const *lst;
    int             i, num, update_screen_included, update_screen_only;
    int             pager_mode = PagersGetMode();
    int             any_urgent;

    p->update_phase = 0;
    DeskGetArea(p->dsk, &cx, &cy);
    vx = cx * WinGetW(VROOT);
    vy = cy * WinGetH(VROOT);

    update_screen_included = update_screen_only = 0;
    if (pager_mode == PAGER_MODE_SNAP && p->dsk == DesksGetCurrent()
        && Mode.mode == 0)
    {
        /* Update from screen unless update area is entirely off-screen */
        if (!(p->x2 <= vx || p->y2 <= vy ||
              p->x1 >= vx + WinGetW(VROOT) || p->y1 >= vy + WinGetH(VROOT)))
            update_screen_included = 1;

        /* Check if update area is entirely on-screen */
        if (p->x1 >= vx && p->y1 >= vy &&
            p->x2 <= vx + WinGetW(VROOT) && p->y2 <= vy + WinGetH(VROOT))
            update_screen_only = 1;
    }
    p->x1 = p->y1 = 99999;
    p->x2 = p->y2 = -99999;

    if (p->pmap == NoXID)
        _PagerCanvasInit(p, true);

    /* Check if we need the flash timer */
    any_urgent = 0;
    lst = EwinListGetForDesk(&num, p->dsk);
    for (i = 0; i < num; i++)
    {
        if (lst[i]->state.attention)
        {
            any_urgent = 1;
            break;
        }
    }

    if (any_urgent)
    {
        if (!p->flash_timer)
            p->flash_timer = TimerAdd(500, PagerFlashTimer, p);
    }
    else if (p->flash_timer)
    {
        TimerDel(p->flash_timer);
        p->flash_timer = NULL;
    }

    if (update_screen_only)
        goto do_screen_update;

    lst = EwinListGetForDesk(&num, p->dsk);
    for (i = num - 1; i >= 0; i--)
    {
        EWin           *ewin;

        ewin = lst[i];
        if (!EoIsShown(ewin))
            continue;

        PagerEwinUpdateMini(p, ewin);
    }

    if (!p->do_update)
        return;
    p->do_update = 0;

    Dprintf("%s %d: Repaint\n", __func__, p->dsk->num);

    /* Tile background over pager areas */
#if USE_XRENDER
    XRenderComposite(disp, PictOpSrc, p->bgpict, NoXID, p->pict,
                     0, 0, 0, 0, 0, 0, p->w, p->h);
#else
    EXCopyAreaTiled(p->bgpmap, NoXID, p->pmap, 0, 0, p->w, p->h, 0, 0);
#endif

    for (i = num - 1; i >= 0; i--)
    {
        EWin           *ewin;
        int             wx, wy, ww, wh;

        ewin = lst[i];
        if (!EoIsShown(ewin))
            continue;

        if (ewin->state.attention && p->flash_count)
            continue;

        wx = (EwinGetVX(ewin) * p->dw) / WinGetW(VROOT);
        wy = (EwinGetVY(ewin) * p->dh) / WinGetH(VROOT);
        ww = (EoGetW(ewin) * p->dw) / WinGetW(VROOT);
        wh = (EoGetH(ewin) * p->dh) / WinGetH(VROOT);

        if (ewin->mini_pmm.pmap)
        {
#if USE_COMPOSITE
            /*      pmap set by           depth determined by
             * PagerEwinUpdateMini()           ewin
             * PagerEwinUpdateFromPager()      p->win
             */
            EX_Picture      pict, alpha;

            pict = EPictureCreate(ewin->mini_pmm.depth == WinGetDepth(p->win) ?
                                  p->win : EoGetWin(ewin), ewin->mini_pmm.pmap);
            alpha = ECompMgrWinGetAlphaPict(EoObj(ewin));
            XRenderComposite(disp, PictOpOver, pict, alpha, p->pict,
                             0, 0, 0, 0, wx, wy, ww, wh);
            EPictureDestroy(pict);
#else
#if 0                           /* Mask is currently not set anywhere */
            if (ewin->mini_pmm.mask)
            {
                XSetClipMask(disp, gc, ewin->mini_pmm.mask);
                XSetClipOrigin(disp, gc, wx, wy);
            }
#endif
            EXCopyArea(ewin->mini_pmm.pmap, p->pmap, 0, 0, ww, wh, wx, wy);
#if 0                           /* Mask is currently not set anywhere */
            if (ewin->mini_pmm.mask)
                XSetClipMask(disp, gc, NoXID);
#endif
#endif
        }
        else
        {
            EXPaintRectangle(p->pmap, wx, wy, ww, wh,
                             Dpy.pixel_black, Dpy.pixel_white);
        }
    }

    if (!update_screen_included)
    {
        EClearWindow(p->win);
        goto done;
    }

  do_screen_update:
    EobjsRepaint();
    Dprintf("%s %d: Snap screen\n", __func__, p->dsk->num);
    /* Update pager area by snapshotting entire screen */
    ScaleRect(VROOT, WinGetXwin(VROOT), p->win, p->pmap, 0, 0,
              WinGetW(VROOT), WinGetH(VROOT), cx * p->dw, cy * p->dh,
              p->dw, p->dh, HIQ);

    EClearWindow(p->win);

    /* Update ewin snapshots */
    PagerUpdateEwinsFromPager(p);

  done:
    ;
}

static void
PagerUpdate(Pager *p, int why, int x1, int y1, int x2, int y2)
{
    if (!Conf_pagers.enable)
        return;

    if (p->x1 > x1)
        p->x1 = x1;
    if (p->y1 > y1)
        p->y1 = y1;
    if (p->x2 < x2)
        p->x2 = x2;
    if (p->y2 < y2)
        p->y2 = y2;

    p->do_update = 1;
    Mode_pagers.update_pending |= 1 << why;
}

static int
PagerFlashTimer(void *data)
{
    Pager          *p = (Pager *) data;

    p->flash_count = !p->flash_count;

    PagerUpdate(p, PAGER_UPD_EWIN_GEOM, 0, 0, 99999, 99999);

    return 1;
}

static void
PagerReconfigure(Pager *p, int apply)
{
    int             ax, ay, w, h, dx, dy;
    float           aspect, f;

    DesksGetAreaSize(&ax, &ay);

    aspect = ((float)WinGetW(VROOT)) / ((float)WinGetH(VROOT));

    dx = 2;
    for (;;)
    {
        f = dx / aspect;
        dy = (int)(f + .5f);
        f -= (float)dy;
        if (f < 0)
            f = -f;
        if (f < .1f)
            break;
        if (dx >= 8)
            break;
        dx += 1;
    }

    ICCCM_SetSizeConstraints(p->ewin,
                             WinGetW(VROOT) / 64 * ax,
                             WinGetH(VROOT) / 64 * ay,
                             WinGetW(VROOT) / 4 * ax,
                             WinGetH(VROOT) / 4 * ay, 0, 0, dx * ax, dy * ay,
                             aspect * ((float)ax / (float)ay),
                             aspect * ((float)ax / (float)ay));

    if (apply)
    {
        w = (int)((float)ax * (float)WinGetW(VROOT) / (float)p->scale + .5f);
        h = (int)((float)ay * (float)WinGetH(VROOT) / (float)p->scale + .5f);
        EwinResize(p->ewin, w + (dx * ax) / 2, h + (dy * ay) / 2, 0);
        EwinReposition(p->ewin);
    }
}

static void
PagerUpdateBg(Pager *p)
{
    EX_Pixmap       pmap;
    Background     *bg;
    ImageClass     *ic;
    int             pager_mode = PagersGetMode();

    p->x1 = p->y1 = 0;
    p->x2 = p->y2 = 99999;

    _PagerBgBufferInit(p, &pmap);

    bg = DeskBackgroundGet(p->dsk);
    if (pager_mode != PAGER_MODE_SIMPLE && bg)
    {
#if USE_PAGER_BACKGROUND_CACHE
        char            s[4096];
        char           *uniq;
        EImage         *im;

        uniq = BackgroundGetUniqueString(bg);
        Esnprintf(s, sizeof(s), "%s/cached/pager/%s.%i.%i.%s.png",
                  EDirUserCache(), BackgroundGetName(bg), p->dw, p->dh, uniq);
        Efree(uniq);

        im = EImageLoad(s);
        if (im)
        {
            EImageRenderOnDrawable(im, p->win, pmap, 0, 0, 0, p->dw, p->dh);
            EImageDecache(im);
        }
        else
        {
#endif
            BackgroundApplyPmap(bg, p->win, pmap, p->dw, p->dh);
#if USE_PAGER_BACKGROUND_CACHE
            im = EImageGrabDrawable(pmap, NoXID, 0, 0, p->dw, p->dh, 0);
            EImageSave(im, s);
            EImageDecache(im);
        }
#endif
        goto done;
    }

    if (pager_mode != PAGER_MODE_SIMPLE && p->dsk->bg.pmap)
    {
        ScaleTile(VROOT, p->dsk->bg.pmap, p->win, pmap,
                  0, 0, p->dw, p->dh, HIQ);
        goto done;
    }

    ic = ImageclassFind("PAGER_BACKGROUND", 1);
    if (ic)
    {
        ImageclassApplySimple(ic, p->win, pmap, STATE_NORMAL,
                              0, 0, p->dw, p->dh);
        goto done;
    }

    EXPaintRectangle(pmap, 0, 0, p->dw, p->dh,
                     Dpy.pixel_black, Dpy.pixel_white);

  done:
#if USE_XRENDER
    EFreePixmap(pmap);          /* Using bgpict as of now */
#endif
    return;
}

static void
_PagerEwinInit(EWin *ewin)
{
    Pager          *p = (Pager *) ewin->data;
    char            s[128];

    p->ewin = ewin;

    Esnprintf(s, sizeof(s), "Pager-%i", p->dsk->num);
    EwinSetTitle(ewin, s);
    Esnprintf(s, sizeof(s), "%i", p->dsk->num);
    EwinSetClass(ewin, s, "Enlightenment_Pager");

    ewin->props.skip_ext_task = 1;
    ewin->props.skip_ext_pager = 1;
    ewin->props.skip_focuslist = 1;
    ewin->props.skip_winlist = 1;
    EwinInhSetWM(ewin, focus, 1);
    ewin->props.autosave = 1;

    EoSetSticky(ewin, 1);
}

static void
_PagerEwinMoveResize(EWin *ewin, int resize __UNUSED__)
{
    Pager          *p = (Pager *) ewin->data;
    int             w, h;
    int             ax, ay, cx, cy;
    ImageClass     *ic;

    if (!Conf_pagers.enable || !p || Mode.mode != MODE_NONE)
        return;

    w = ewin->client.w;
    h = ewin->client.h;
    if ((w == p->w && h == p->h) || w <= 1 || h <= 1)
        return;

    DesksGetAreaSize(&ax, &ay);

    p->w = w;
    p->h = h;
    p->dw = w / ax;
    p->dh = h / ay;

    _PagerCanvasInit(p, false);

    if (p->scale <= 0.f || Mode.op_source == OPSRC_USER)
        p->scale = ((float)WinGetW(VROOT) / p->dw +
                    (float)WinGetH(VROOT) / p->dh) / 2;

    p->do_newbg = 1;
    PagerCheckUpdate(p, NULL);

    ic = ImageclassFind("PAGER_SEL", 0);
    if (ic)
    {
        DeskGetArea(p->dsk, &cx, &cy);
        EMoveResizeWindow(p->sel_win, cx * p->dw, cy * p->dh, p->dw, p->dh);
        ImageclassApply(ic, p->sel_win, 0, 0, STATE_NORMAL);
    }
}

static void
_PagerEwinClose(EWin *ewin)
{
    PagerDestroy((Pager *) ewin->data);
    ewin->data = NULL;
}

static const EWinOps _PagerEwinOps = {
    _PagerEwinInit,
    NULL,
    _PagerEwinMoveResize,
    _PagerEwinClose,
};

static void
PagerShow(Pager *p)
{
    EWin           *ewin;

    if (!Conf_pagers.enable)
        return;

    if (p->ewin)
    {
        EwinShow(p->ewin);
        return;
    }

    ewin = AddInternalToFamily(p->win, "PAGER", EWIN_TYPE_PAGER,
                               &_PagerEwinOps, p);
    if (!ewin)
        return;

    p->screen_w = WinGetW(VROOT);
    p->screen_h = WinGetH(VROOT);

    PagerReconfigure(p, 0);

    ewin->client.event_mask |=
        ButtonPressMask | ButtonReleaseMask |
        EnterWindowMask | LeaveWindowMask | PointerMotionMask;
    ESelectInput(p->win, ewin->client.event_mask);

    if (ewin->state.placed)
    {
        EwinMoveResize(ewin, EoGetX(ewin), EoGetY(ewin),
                       ewin->client.w, ewin->client.h, 0);
    }
    else
    {
        /* no snapshots ? first time ? make a row on the bottom left up */
        int             ax, ay, x, y, w, h;

        DesksGetAreaSize(&ax, &ay);
        w = ((48 * WinGetW(VROOT)) / WinGetH(VROOT)) * ax;
        h = 48 * ay;
        EwinResize(ewin, w, h, 0);      /* Does layout */
        x = 0;
        y = WinGetH(VROOT) - (DesksGetNumber() - p->dsk->num) * EoGetH(ewin);
        EwinMove(ewin, x, y, 0);
    }

    EwinShow(ewin);
}

typedef struct {
    Desk           *dsk;
    void            (*func)(Pager * p, void *prm);
    void           *prm;
} pager_foreach_data;

static void
_PagersForeachFunc(void *item, void *prm)
{
    Pager          *p = (Pager *) item;
    pager_foreach_data *data = (pager_foreach_data *) prm;

    if (data->dsk && data->dsk != p->dsk)
        return;
    data->func(p, data->prm);
}

static void
PagersForeach(Desk *dsk, void (*func)(Pager *p, void *prm), void *prm)
{
    pager_foreach_data data;

    data.dsk = dsk;
    data.func = func;
    data.prm = prm;
    LIST_FOR_EACH_FUNC(Pager, &pager_list, _PagersForeachFunc, &data);
}

typedef struct {
    int             why;
    int             x1, y1, x2, y2;
} pager_update_data;

static void
_PagerUpdate(Pager *p, void *prm)
{
    pager_update_data *pud = (pager_update_data *) prm;

    PagerUpdate(p, pud->why, pud->x1, pud->y1, pud->x2, pud->y2);
}

static void
PagersUpdate(Desk *dsk, int why, int x1, int y1, int x2, int y2)
{
    pager_update_data pud;

    if (LIST_IS_EMPTY(&pager_list))
        return;

    pud.why = why;
    pud.x1 = x1;
    pud.y1 = y1;
    pud.x2 = x2;
    pud.y2 = y2;
    PagersForeach(dsk, _PagerUpdate, &pud);
}

static void
PagerCheckUpdate(Pager *p, void *prm __UNUSED__)
{
    if (p->do_newbg)
    {
        PagerUpdateBg(p);
        p->do_update = 1;
    }

    if (p->do_update)
        doPagerUpdate(p);

    p->do_newbg = p->do_update = 0;
}

static int
_PagersUpdateTimeout(void *data __UNUSED__)
{
    Mode_pagers.timer_pending = 0;

    return 0;
}

static void
PagersCheckUpdate(void)
{
    static unsigned int tms_last = 0;
    unsigned int    tms, dtms;

    if (!Mode_pagers.update_pending || !Conf_pagers.enable)
        return;

    if (Mode_pagers.update_pending == (1 << PAGER_UPD_EWIN_DAMAGE))
    {
        tms = GetTimeMs();
        dtms = (Conf_pagers.scanspeed > 0) ? 1000 / Conf_pagers.scanspeed : 100;
        if ((unsigned int)(tms - tms_last) < dtms)
        {
            /* The purpose of this timer is to trigger the idler */
            if (Mode_pagers.timer_pending)
                return;
            TIMER_ADD_NP(dtms, _PagersUpdateTimeout, NULL);
            Mode_pagers.timer_pending = 1;
            return;
        }
        tms_last = tms;
    }

    PagersForeach(NULL, PagerCheckUpdate, NULL);

    Mode_pagers.update_pending = 0;
}

static void
_PagersIdler(void *data __UNUSED__)
{
    if (Mode.events.block)
        return;
    PagersCheckUpdate();
}

static void
PagerEwinUpdateFromPager(Pager *p, EWin *ewin)
{
    int             x, y, w, h;

    if (!EoIsShown(ewin) || !EwinIsOnScreen(ewin))
        return;

    Dprintf("%s %d\n", __func__, p->dsk->num);

    x = EwinGetVX(ewin);
    y = EwinGetVY(ewin);
    w = EoGetW(ewin);
    h = EoGetH(ewin);
    x = (x * p->dw) / WinGetW(VROOT);
    y = (y * p->dh) / WinGetH(VROOT);
    w = (w * p->dw) / WinGetW(VROOT);
    h = (h * p->dh) / WinGetH(VROOT);
    if (w <= 0)
        w = 1;
    if (h <= 0)
        h = 1;

    PmapMaskInit(&ewin->mini_pmm, p->win, w, h);
    if (!ewin->mini_pmm.pmap)
        return;

    EXCopyArea(WinGetPmap(p->win), ewin->mini_pmm.pmap, x, y, w, h, 0, 0);

#if 0                           /* FIXME - Remove? */
    if (hiwin && ewin == hiwin->ewin)
        PagerHiwinUpdate(hiwin, p, ewin);
#endif
}

static void
PagerUpdateEwinsFromPager(Pager *p)
{
    int             i, num;
    EWin           *const *lst;

    lst = EwinListGetForDesk(&num, p->dsk);
    for (i = 0; i < num; i++)
        PagerEwinUpdateFromPager(p, lst[i]);
}

static void
PagersUpdateEwin(EWin *ewin, int why)
{
    Desk           *dsk;

    if (!Conf_pagers.enable)
        return;

    switch (why)
    {
    case PAGER_UPD_EWIN_GEOM:
        if (!EoIsShown(ewin))
            return;
        break;

    case PAGER_UPD_EWIN_GONE:
        if (ewin == HiwinGetEwin(hiwin, 0))
            PagerHiwinHide();
        break;

    case PAGER_UPD_EWIN_DAMAGE:
        if (ewin->type == EWIN_TYPE_PAGER)
            return;
        if (PagersGetMode() != PAGER_MODE_LIVE)
            return;
        break;
    }

    dsk = (EoIsFloating(ewin)) ? DesksGetCurrent() : EoGetDesk(ewin);
    PagersUpdate(dsk, why, EwinGetVX(ewin), EwinGetVY(ewin),
                 EwinGetVX2(ewin), EwinGetVY2(ewin));
}

void
PagersHandleAttention(EWin *ewin)
{
    PagersUpdateEwin(ewin, PAGER_UPD_EWIN_GEOM);
}

static EWin    *
EwinInPagerAt(Pager *p, int px, int py)
{
    EWin           *const *lst, *ewin;
    int             i, num, x, y, w, h;

    if (!Conf_pagers.enable)
        return NULL;

    lst = EwinListGetForDesk(&num, p->dsk);
    for (i = 0; i < num; i++)
    {
        ewin = lst[i];
        if (!EoIsShown(ewin))
            continue;

        x = (EwinGetVX(ewin) * p->dw) / WinGetW(VROOT);
        y = (EwinGetVY(ewin) * p->dh) / WinGetH(VROOT);
        w = (EoGetW(ewin) * p->dw) / WinGetW(VROOT);
        h = (EoGetH(ewin) * p->dh) / WinGetH(VROOT);

        if (px >= x && py >= y && px < (x + w) && py < (y + h))
            return ewin;
    }

    return NULL;
}

static void
PagerMenuShow(Pager *p, int x, int y)
{
    Menu           *m;
    MenuItem       *mi;
    EWin           *ewin;
    char            s[1024];

    if (!Conf_pagers.enable)
        return;

    ewin = EwinInPagerAt(p, x, y);
    if (ewin)
    {
        m = MenuCreate("__pg_win", _("Window Options"), NULL, NULL);
        if (!m)
            return;

        MenuSetTransient(m);    /* Destroy when hidden */

        Esnprintf(s, sizeof(s), "wop %#x ic", EwinGetClientXwin(ewin));
        mi = MenuItemCreate(_("Iconify"), NULL, s, NULL);
        MenuAddItem(m, mi);

        Esnprintf(s, sizeof(s), "wop %#x close", EwinGetClientXwin(ewin));
        mi = MenuItemCreate(_("Close"), NULL, s, NULL);
        MenuAddItem(m, mi);

        Esnprintf(s, sizeof(s), "wop %#x kill", EwinGetClientXwin(ewin));
        mi = MenuItemCreate(_("Annihilate"), NULL, s, NULL);
        MenuAddItem(m, mi);

        Esnprintf(s, sizeof(s), "wop %#x st", EwinGetClientXwin(ewin));
        mi = MenuItemCreate(_("Stick / Unstick"), NULL, s, NULL);
        MenuAddItem(m, mi);

        EFunc(NULL, "menus show __pg_win");
        return;
    }

    m = MenuCreate("__pg", _("Desktop Options"), NULL, NULL);
    if (!m)
        return;

    MenuSetTransient(m);        /* Destroy when hidden */

    mi = MenuItemCreate(_("Pager Settings..."), NULL, "cfg pagers", NULL);
    MenuAddItem(m, mi);

    if (PagersGetMode() != PAGER_MODE_SIMPLE)
    {
        mi = MenuItemCreate(_("Snapshotting Off"), NULL, "pg mode simp", NULL);
        MenuAddItem(m, mi);

        if (Conf_pagers.hiq)
            mi = MenuItemCreate(_("High Quality Off"), NULL, "pg hiq off",
                                NULL);
        else
            mi = MenuItemCreate(_("High Quality On"), NULL, "pg hiq on", NULL);
        MenuAddItem(m, mi);
    }
    else
    {
        mi = MenuItemCreate(_("Snapshotting On"), NULL, "pg mode live", NULL);
        MenuAddItem(m, mi);
    }
    if (Conf_pagers.zoom)
        mi = MenuItemCreate(_("Zoom Off"), NULL, "pg zoom off", NULL);
    else
        mi = MenuItemCreate(_("Zoom On"), NULL, "pg zoom on", NULL);
    MenuAddItem(m, mi);

    EFunc(NULL, "menus show __pg");
}

static void
PagerClose(Pager *p)
{
    EwinHide(p->ewin);
}

static void
_PagerUpdateSel(Pager *p, void *prm __UNUSED__)
{
    int             cx, cy;
    ImageClass     *ic;

    if (p->dsk != DesksGetCurrent())
        EUnmapWindow(p->sel_win);
    else
    {
        DeskGetArea(p->dsk, &cx, &cy);
        EMoveWindow(p->sel_win, cx * p->dw, cy * p->dh);
        EMapWindow(p->sel_win);
        ic = ImageclassFind("PAGER_SEL", 0);
        if (ic)
            ImageclassApply(ic, p->sel_win, 0, 0, STATE_NORMAL);
    }
}

static void
UpdatePagerSel(void)
{
    if (!Conf_pagers.enable)
        return;

    PagersForeach(NULL, _PagerUpdateSel, NULL);
}

static void
PagerShowTt(EWin *ewin)
{
    static EWin    *tt_ewin = NULL;
    ToolTip        *tt;

    Dprintf("%s %s\n", __func__, (ewin) ? EwinGetTitle(ewin) : NULL);

    if (!Conf_pagers.title || (ewin == tt_ewin))
        return;

    if (MenusActive())          /* Don't show Tooltip when menu is up */
        return;

    tt = TooltipFind("PAGER");
    if (tt)
    {
        if (ewin)
            TooltipShow(tt, EwinGetTitle(ewin), NULL, Mode.events.cx,
                        Mode.events.cy);
        else
            TooltipHide(tt);
    }

    tt_ewin = ewin;
}

static void
PagerHiwinInit(Pager *p, EWin *ewin)
{
    Hiwin          *phi = hiwin;
    int             wx, wy, ww, wh, px, py;

    wx = (EwinGetVX(ewin) * p->dw) / WinGetW(VROOT);
    wy = (EwinGetVY(ewin) * p->dh) / WinGetH(VROOT);
    ww = (EoGetW(ewin) * p->dw) / WinGetW(VROOT);
    wh = (EoGetH(ewin) * p->dh) / WinGetH(VROOT);
    ETranslateCoordinates(p->win, VROOT, 0, 0, &px, &py, NULL);

    HiwinInit(phi, ewin, EoObj(EoGetDesk(p->ewin)));
    HiwinSetGeom(phi, px + wx, py + wy, ww, wh);
    HiwinSetCallback(phi, PagerHiwinEvent, p);
}

static void
PagerHiwinHide(void)
{
    Dprintf("%s\n", __func__);
    HiwinHide(hiwin);
    PagerShowTt(NULL);
}

static void
PagerHiwinShow(Pager *p, EWin *ewin, int zoom, int confine)
{
    Hiwin          *phi = hiwin;

    if (MenusActive())          /* Don't show HiWin when menu is up */
        return;

    Dprintf("%s\n", __func__);

    if (!phi)
    {
        phi = HiwinCreate();
        if (!phi)
            return;
        hiwin = phi;
    }

    if (ewin)
        PagerHiwinInit(p, ewin);

    HiwinShow(phi, ewin, zoom, confine);
}

static void
PagerZoomChange(Pager *p, int delta)
{
    Dprintf("%s delta=%d\n", __func__, delta);

    if (delta == 0)
        return;

    if (delta > 0)
    {
        if (!ECompMgrIsActive() && Mode_pagers.zoom >= 2)
            return;
        if (Mode_pagers.zoom >= 8)
            return;
        Mode_pagers.zoom++;
    }
    else
    {
        if (Mode_pagers.zoom <= 2)
            return;
        Mode_pagers.zoom--;
    }
    PagerHiwinShow(p, NULL, Mode_pagers.zoom, 0);
}

static void
PagerHandleMotion(Pager *p, int x, int y)
{
    EWin           *ewin;

    if (!Conf_pagers.enable)
        return;

    if (Mode.events.on_screen && x >= 0 && x < p->w && y >= 0 && y < p->h)
        ewin = EwinInPagerAt(p, x, y);
    else
        ewin = NULL;

    if (!Conf_pagers.zoom)
    {
        PagerShowTt(ewin);
        return;
    }

    if (!ewin || EoGetLayer(ewin) <= 0)
    {
        PagerHiwinHide();
    }
    else if (!hiwin || ewin != HiwinGetEwin(hiwin, 0))
    {
        if (Mode_pagers.zoom < 2)
            Mode_pagers.zoom = 2;
        PagerHiwinShow(p, ewin, Mode_pagers.zoom, 0);
    }
    if (Mode_pagers.zoom <= 2)
        PagerShowTt(ewin);
}

static void
NewPagerForDesktop(Desk *dsk)
{
    Pager          *p;

    p = PagerCreate();
    if (!p)
        return;

    p->dsk = dsk;
    PagerShow(p);
}

static void
_PagerUpdateBackground(Pager *p, void *prm __UNUSED__)
{
    p->do_newbg = 1;
}

static void
PagersUpdateBackground(Desk *dsk)
{
    PagersForeach(dsk, _PagerUpdateBackground, NULL);

    Mode_pagers.update_pending = 1;
}

static void
_EwinsMiniFree(void)
{
    EWin           *const *lst;
    int             i, num;

    lst = EwinListGetAll(&num);
    for (i = 0; i < num; i++)
        PmapMaskFree(&(lst[i]->mini_pmm));
}

static void
PagerSetHiQ(char onoff)
{
    Conf_pagers.hiq = onoff;

    _EwinsMiniFree();

    PagersUpdateBackground(NULL);

    autosave();
}

static void
_PagerSetSnap(Pager *p, void *prm __UNUSED__)
{
    PagerScanTrig(p);
}

static void
PagersSetMode(int mode)
{
    if (mode == Conf_pagers.mode)
        return;
    Conf_pagers.mode = mode;

    _EwinsMiniFree();

    PagersUpdateBackground(NULL);

    if (Conf_pagers.mode == PAGER_MODE_SNAP && Conf_pagers.scanspeed > 0)
        PagersForeach(DesksGetCurrent(), _PagerSetSnap, NULL);

    autosave();
}

static void
PagersSetZoom(int onoff)
{
    if (Conf_pagers.zoom == onoff)
        return;
    Conf_pagers.zoom = onoff;

    autosave();
}

/*
 * Pager event handlers
 */

static int     *gwin_px, *gwin_py;

static void
PagerEwinGroupSet(void)
{
    int             i, num;
    EWin           *ewin, **gwins;

    ewin = HiwinGetEwin(hiwin, 0);
    gwins = ListWinGroupMembersForEwin(ewin, GROUP_ACTION_MOVE,
                                       Mode.nogroup, &num);
    if (!gwins)
        return;

    gwin_px = EMALLOC(int, num);
    gwin_py = EMALLOC(int, num);

    for (i = 0; i < num; i++)
    {
        gwin_px[i] = EoGetX(gwins[i]);
        gwin_py[i] = EoGetY(gwins[i]);
    }

    Efree(gwins);
}

static void
PagerEwinGroupUnset(void)
{
    EFREE_NULL(gwin_px);
    EFREE_NULL(gwin_py);
}

static void
PagerEventUnmap(Pager *p __UNUSED__)
{
    PagerHiwinHide();
}

static void
EwinGroupMove(EWin *ewin, Desk *dsk, int x, int y)
{
    int             i, num, dx, dy, newdesk;
    EWin          **gwins;

    if (!ewin)
        return;

    /* Move all group members */
    newdesk = dsk != EoGetDesk(ewin);
    dx = x - EoGetX(ewin);
    dy = y - EoGetY(ewin);
    gwins =
        ListWinGroupMembersForEwin(ewin, GROUP_ACTION_MOVE, Mode.nogroup, &num);
    for (i = 0; i < num; i++)
    {
        if (gwins[i]->type == EWIN_TYPE_PAGER)
            continue;

        if (newdesk)
            EwinOpMoveToDesktopAt(gwins[i], OPSRC_USER, dsk,
                                  EoGetX(gwins[i]) + dx, EoGetY(gwins[i]) + dy);
        else
            EwinOpMove(gwins[i], OPSRC_USER, EoGetX(gwins[i]) + dx,
                       EoGetY(gwins[i]) + dy);
    }
    Efree(gwins);
}

static void
PagerEwinMove(Pager *p __UNUSED__, Pager *pd, EWin *ewin)
{
    int             x, y, dx, dy, px, py;
    int             cx, cy;
    Hiwin          *phi = hiwin;

    /* Delta in pager coords */
    dx = Mode.events.mx - Mode.events.px;
    dy = Mode.events.my - Mode.events.py;

    if (dx == 0 && dy == 0 && p == pd)
        return;

    /* Move mini window */
    HiwinGetXY(phi, &x, &y);
    HiwinMove(phi, x + dx, y + dy);

    /* Find real window position */
    ETranslateCoordinates(VROOT, pd->win, x, y, &px, &py, NULL);
    DeskGetArea(pd->dsk, &cx, &cy);
    x = (px * WinGetW(VROOT)) / pd->dw - cx * WinGetW(VROOT);
    y = (py * WinGetH(VROOT)) / pd->dh - cy * WinGetH(VROOT);

    /* Move all group members */
    EwinGroupMove(ewin, pd->dsk, x, y);
}

static void
PagerHandleMouseDown(Pager *p, int px, int py, int button)
{
    int             in_pager;
    EWin           *ewin;

    Dprintf("%s m=%d d=%d x,y=%d,%d\n", __func__, Mode.mode, p->dsk->num,
            px, py);

    in_pager = (px >= 0 && py >= 0 && px < p->w && py < p->h);
    if (!in_pager)
        return;

    if (button == Conf_pagers.menu_button)
    {
        PagerHiwinHide();
        PagerMenuShow(p, px, py);
    }
    else if (button == Conf_pagers.win_button)
    {
        ewin = EwinInPagerAt(p, px, py);
        if (ewin && ewin->type != EWIN_TYPE_PAGER && !ewin->state.inhibit_move)
        {
            PagerHiwinShow(p, ewin, 1, !Mode.wm.window);
            Mode.mode = MODE_PAGER_DRAG_PENDING;
            EwinRaise(ewin);
            PagerEwinGroupSet();
        }
    }
}

static void
PagerHandleMouseUp(Pager *p, int px, int py, int button)
{
    int             i, num, in_pager, in_vroot;
    EWin           *ewin, *ewin2, **gwins;
    int             x, y;

    Dprintf("%s m=%d d=%d x,y=%d,%d\n", __func__, Mode.mode, p->dsk->num,
            px, py);

    in_pager = (px >= 0 && py >= 0 && px < p->w && py < p->h);

    if (button == Conf_pagers.sel_button)
    {
        if (!in_pager)
            return;
        DeskGoto(p->dsk);
        DeskCurrentGotoArea(px / p->dw, py / p->dh);
        return;
    }

    if (button != Conf_pagers.win_button)
        return;

    ewin = HiwinGetEwin(hiwin, 1);
    PagerHiwinHide();

    if (Mode.mode != MODE_PAGER_DRAG)
    {
        if (Mode.mode == MODE_PAGER_DRAG_PENDING)
            Mode.mode = MODE_NONE;
        if (!in_pager)
            goto done;
        DeskGoto(p->dsk);
        DeskCurrentGotoArea(px / p->dw, py / p->dh);
        if (ewin)
            FocusToEWin(ewin, FOCUS_SET);
        goto done;
    }

    Mode.mode = MODE_NONE;

    if (!ewin)
        goto done;

    /* Finish pager drag */

    in_vroot = (Mode.events.cx >= 0 && Mode.events.cx < WinGetW(VROOT) &&
                Mode.events.cy >= 0 && Mode.events.cy < WinGetH(VROOT));

    /* Find which pager or iconbox we are in (if any) */
    ewin2 = GetEwinPointerInClient();
    if ((ewin2) && (ewin2->type == EWIN_TYPE_PAGER))
    {
        PagerEwinMove(p, (Pager *) ewin2->data, ewin);
    }
    else if ((ewin2) && (ewin2->type == EWIN_TYPE_ICONBOX))
    {
        /* Pointer is in iconbox */

        /* Iconify after moving back to pre-drag position */
        gwins = ListWinGroupMembersForEwin(ewin, GROUP_ACTION_MOVE,
                                           Mode.nogroup, &num);
        for (i = 0; i < num; i++)
        {
            if (gwins[i]->type != EWIN_TYPE_PAGER)
            {
                EwinMove(gwins[i], gwin_px[i], gwin_py[i], 0);
                EwinIconify(gwins[i]);
            }
        }
        Efree(gwins);
    }
    else if (ewin2 && ewin2->props.vroot)
    {
        /* Dropping onto virtual root */
        EwinReparent(ewin, EwinGetClientWin(ewin2));
    }
    else if (!in_vroot)
    {
        /* Move back to real root */
        EwinReparent(ewin, RROOT);
    }
    else
    {
        /* Pointer is not in pager or iconbox */
        /* Move window(s) to pointer location */
        x = Mode.events.cx - EoGetW(ewin) / 2;
        y = Mode.events.cy - EoGetH(ewin) / 2;
        EwinGroupMove(ewin, DesksGetCurrent(), x, y);
    }

  done:
    /* unallocate the space that was holding the old positions of the */
    /* windows */
    PagerEwinGroupUnset();
}

static void
PagerEvent(Win win __UNUSED__, XEvent *ev, void *prm)
{
    Pager          *p = (Pager *) prm;

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

    switch (ev->type)
    {
    case ButtonPress:
        PagerHandleMouseDown(p, ev->xbutton.x, ev->xbutton.y,
                             (int)ev->xbutton.button);
        break;
    case ButtonRelease:
        if (ev->xbutton.window != Mode.events.last_bpress)
            break;
        PagerHandleMouseUp(p, ev->xbutton.x, ev->xbutton.y,
                           (int)ev->xbutton.button);
        break;

    case MotionNotify:
        PagerHandleMotion(p, ev->xmotion.x, ev->xmotion.y);
        break;

    case EnterNotify:
        break;
    case LeaveNotify:
        PagerShowTt(NULL);
        break;

    case UnmapNotify:
        PagerEventUnmap(p);
        break;

    case VisibilityNotify:
        if (ev->xvisibility.state != VisibilityFullyObscured)
            PagerScanTrig(p);
        break;
    }
}

static void
PagerHiwinEvent(Win win __UNUSED__, XEvent *ev, void *prm)
{
    Pager          *p = (Pager *) prm;
    int             px, py;
    EWin           *ewin;

    if (!p)
        return;

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

    switch (ev->type)
    {
    case ButtonPress:
        switch (ev->xbutton.button)
        {
        case 4:
            if (Mode.mode != MODE_NONE)
                break;
            PagerZoomChange(p, 1);
            break;
        case 5:
            if (Mode.mode != MODE_NONE)
                break;
            PagerZoomChange(p, -1);
            break;
        default:
            /* Translate x,y to pager window coordinates */
            ETranslateCoordinates(RROOT, p->win,
                                  ev->xbutton.x_root, ev->xbutton.y_root,
                                  &px, &py, NULL);
            PagerHandleMouseDown(p, px, py, (int)ev->xbutton.button);
            break;
        }
        break;

    case ButtonRelease:
        switch (ev->xbutton.button)
        {
        case 4:
        case 5:
            break;
        default:
            /* Translate x,y to pager window coordinates */
            ETranslateCoordinates(RROOT, p->win,
                                  ev->xbutton.x_root, ev->xbutton.y_root,
                                  &px, &py, NULL);
            PagerHandleMouseUp(p, px, py, (int)ev->xbutton.button);
            break;
        }
        break;

    case MotionNotify:
        switch (Mode.mode)
        {
        case MODE_NONE:
            /* Translate x,y to pager window coordinates */
            ETranslateCoordinates(RROOT, p->win,
                                  ev->xbutton.x_root, ev->xbutton.y_root,
                                  &px, &py, NULL);
            PagerHandleMotion(p, px, py);
            break;

        case MODE_PAGER_DRAG_PENDING:
        case MODE_PAGER_DRAG:
            ewin = HiwinGetEwin(hiwin, 1);
            if (!ewin || ewin->type == EWIN_TYPE_PAGER)
            {
                Mode.mode = MODE_NONE;
                break;
            }

            Mode.mode = MODE_PAGER_DRAG;
            PagerEwinMove(p, p, ewin);
            break;
        }
        break;

    case LeaveNotify:
        PagerShowTt(NULL);
        break;
    }
}

/*
 * Pagers handling
 */

static int
PagersForDesktopCount(Desk *dsk)
{
    Pager          *p;
    int             num = 0;

    LIST_FOR_EACH(Pager, &pager_list, p)
    {
        if (p->dsk == dsk)
            num++;
    }

    return num;
}

static void
_PagerClose(Pager *p, void *prm __UNUSED__)
{
    PagerClose(p);
}

static void
PagersForDesktopEnable(Desk *dsk)
{
    if (PagersForDesktopCount(dsk) <= 0)
        NewPagerForDesktop(dsk);
}

static void
PagersForDesktopDisable(Desk *dsk)
{
    PagersForeach(dsk, _PagerClose, NULL);
}

static void
PagersShow(int enable)
{
    unsigned int    i;

    if (enable && !Conf_pagers.enable)
    {
        Conf_pagers.enable = 1;
        for (i = 0; i < DesksGetNumber(); i++)
            PagersForDesktopEnable(DeskGet(i));
        UpdatePagerSel();
        Mode_pagers.idler = IdlerAdd(_PagersIdler, NULL);
    }
    else if (!enable && Conf_pagers.enable)
    {
        for (i = 0; i < DesksGetNumber(); i++)
            PagersForDesktopDisable(DeskGet(i));
        Conf_pagers.enable = 0;
        IdlerDel(Mode_pagers.idler);
        Mode_pagers.idler = NULL;
    }
}

static void
_PagerReconfigure(Pager *p, void *prm __UNUSED__)
{
    PagerReconfigure(p, 1);
}

static int
_PagersReconfigureTimeout(void *data __UNUSED__)
{
    PagersForeach(NULL, _PagerReconfigure, NULL);

    return 0;
}

static void
PagersReconfigure(void)
{
    if (!Conf_pagers.enable)
        return;

    TIMER_ADD_NP(500, _PagersReconfigureTimeout, NULL);
}

#if ENABLE_DIALOGS
/*
 * Configuration dialog
 */
typedef struct {
    char            show_pagers;
    char            pager_hiq;
    int             pager_mode;
    char            pager_zoom;
    char            pager_title;
    char            pager_do_scan;
    int             pager_scan_speed;
    int             pager_sel_button;
    int             pager_win_button;
    int             pager_menu_button;
} PagerDlgData;

static void
_DlgApplyPagers(Dialog *d, int val __UNUSED__, void *data __UNUSED__)
{
    PagerDlgData   *dd = DLG_DATA_GET(d, PagerDlgData);

    PagersShow(dd->show_pagers);
    if (Conf_pagers.hiq != dd->pager_hiq)
        PagerSetHiQ(dd->pager_hiq);
    Conf_pagers.zoom = dd->pager_zoom;
    Conf_pagers.title = dd->pager_title;
    Conf_pagers.sel_button = dd->pager_sel_button;
    Conf_pagers.win_button = dd->pager_win_button;
    Conf_pagers.menu_button = dd->pager_menu_button;
    if ((Conf_pagers.scanspeed != dd->pager_scan_speed)
        || ((!dd->pager_do_scan) && (Conf_pagers.scanspeed > 0))
        || ((dd->pager_do_scan) && (Conf_pagers.scanspeed == 0)))
    {
        if (dd->pager_do_scan)
            Conf_pagers.scanspeed = dd->pager_scan_speed;
        else
            Conf_pagers.scanspeed = 0;
    }
    PagersSetMode(dd->pager_mode);

    autosave();
}

static void
CB_PagerScanSlide(Dialog *d, int val __UNUSED__, void *data)
{
    PagerDlgData   *dd = DLG_DATA_GET(d, PagerDlgData);
    DItem          *di = (DItem *) data;
    char            s[256];

    Esnprintf(s, sizeof(s), "%s %03i %s", _("Pager scanning speed:"),
              dd->pager_scan_speed, _("lines per second"));
    DialogItemSetText(di, s);
}

static void
_DlgFillPagers(Dialog *d, DItem *table, void *data __UNUSED__)
{
    PagerDlgData   *dd = DLG_DATA_GET(d, PagerDlgData);
    DItem          *di, *radio, *label;
    char            s[256];

    dd->show_pagers = Conf_pagers.enable;
    dd->pager_hiq = Conf_pagers.hiq;
    dd->pager_mode = Conf_pagers.mode;
    dd->pager_zoom = Conf_pagers.zoom;
    dd->pager_title = Conf_pagers.title;
    dd->pager_sel_button = Conf_pagers.sel_button;
    dd->pager_win_button = Conf_pagers.win_button;
    dd->pager_menu_button = Conf_pagers.menu_button;
    if (Conf_pagers.scanspeed == 0)
        dd->pager_do_scan = 0;
    else
        dd->pager_do_scan = 1;
    dd->pager_scan_speed = Conf_pagers.scanspeed;

    DialogItemTableSetOptions(table, 2, 0, 0, 0);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Enable pager display"));
    DialogItemCheckButtonSetPtr(di, &dd->show_pagers);

    di = DialogAddItem(table, DITEM_SEPARATOR);
    DialogItemSetColSpan(di, 2);

    di = DialogAddItem(table, DITEM_TEXT);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Pager mode:"));

    radio = di = DialogAddItem(table, DITEM_RADIOBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Simple"));
    DialogItemRadioButtonSetFirst(di, radio);
    DialogItemRadioButtonGroupSetVal(di, 0);

    di = DialogAddItem(table, DITEM_RADIOBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Make miniature snapshots of the screen"));
    DialogItemRadioButtonSetFirst(di, radio);
    DialogItemRadioButtonGroupSetVal(di, 1);

#if USE_COMPOSITE
    di = DialogAddItem(table, DITEM_RADIOBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Live update"));
    DialogItemRadioButtonSetFirst(di, radio);
    DialogItemRadioButtonGroupSetVal(di, 2);
#endif
    DialogItemRadioButtonGroupSetValPtr(radio, &dd->pager_mode);

    di = DialogAddItem(table, DITEM_SEPARATOR);
    DialogItemSetColSpan(di, 2);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Smooth high quality snapshots in snapshot mode"));
    DialogItemCheckButtonSetPtr(di, &dd->pager_hiq);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di,
                      _("Zoom in on pager windows when mouse is over them"));
    DialogItemCheckButtonSetPtr(di, &dd->pager_zoom);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di,
                      _("Pop up window title when mouse is over the window"));
    DialogItemCheckButtonSetPtr(di, &dd->pager_title);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Continuously scan screen to update pager"));
    DialogItemCheckButtonSetPtr(di, &dd->pager_do_scan);

    di = label = DialogAddItem(table, DITEM_TEXT);
    DialogItemSetColSpan(di, 2);
    Esnprintf(s, sizeof(s), "%s %03i %s", _("Pager scanning speed:"),
              dd->pager_scan_speed, _("lines per second"));
    DialogItemSetText(di, s);

    di = DialogAddItem(table, DITEM_SLIDER);
    DialogItemSliderSetBounds(di, 1, 256);
    DialogItemSliderSetUnits(di, 1);
    DialogItemSliderSetJump(di, 1);
    DialogItemSetColSpan(di, 2);
    DialogItemSliderSetValPtr(di, &dd->pager_scan_speed);
    DialogItemSetCallback(di, CB_PagerScanSlide, 0, label);

    di = DialogAddItem(table, DITEM_TEXT);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Mouse button to select and drag windows:"));

    radio = di = DialogAddItem(table, DITEM_RADIOBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Left"));
    DialogItemRadioButtonSetFirst(di, radio);
    DialogItemRadioButtonGroupSetVal(di, 1);

    di = DialogAddItem(table, DITEM_RADIOBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Middle"));
    DialogItemRadioButtonSetFirst(di, radio);
    DialogItemRadioButtonGroupSetVal(di, 2);

    di = DialogAddItem(table, DITEM_RADIOBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Right"));
    DialogItemRadioButtonSetFirst(di, radio);
    DialogItemRadioButtonGroupSetVal(di, 3);
    DialogItemRadioButtonGroupSetValPtr(radio, &dd->pager_win_button);

    di = DialogAddItem(table, DITEM_TEXT);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Mouse button to select desktops:"));

    radio = di = DialogAddItem(table, DITEM_RADIOBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Left"));
    DialogItemRadioButtonSetFirst(di, radio);
    DialogItemRadioButtonGroupSetVal(di, 1);

    di = DialogAddItem(table, DITEM_RADIOBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Middle"));
    DialogItemRadioButtonSetFirst(di, radio);
    DialogItemRadioButtonGroupSetVal(di, 2);

    di = DialogAddItem(table, DITEM_RADIOBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Right"));
    DialogItemRadioButtonSetFirst(di, radio);
    DialogItemRadioButtonGroupSetVal(di, 3);
    DialogItemRadioButtonGroupSetValPtr(radio, &dd->pager_sel_button);

    di = DialogAddItem(table, DITEM_TEXT);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Mouse button to display pager menu:"));

    radio = di = DialogAddItem(table, DITEM_RADIOBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Left"));
    DialogItemRadioButtonSetFirst(di, radio);
    DialogItemRadioButtonGroupSetVal(di, 1);

    di = DialogAddItem(table, DITEM_RADIOBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Middle"));
    DialogItemRadioButtonSetFirst(di, radio);
    DialogItemRadioButtonGroupSetVal(di, 2);

    di = DialogAddItem(table, DITEM_RADIOBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Right"));
    DialogItemRadioButtonSetFirst(di, radio);
    DialogItemRadioButtonGroupSetVal(di, 3);
    DialogItemRadioButtonGroupSetValPtr(radio, &dd->pager_menu_button);
}

const DialogDef DlgPagers = {
    "CONFIGURE_PAGER",
    N_("Pagers"), N_("Pager Settings"),
    sizeof(PagerDlgData),
    SOUND_SETTINGS_PAGER,
    "pix/pager.png",
    N_("Enlightenment Desktop & Area\n" "Pager Settings Dialog"),
    _DlgFillPagers,
    DLG_OAC, _DlgApplyPagers, NULL
};
#endif                          /* ENABLE_DIALOGS */

/*
 * Pagers Module
 */

static void
PagersSighan(int sig, void *prm)
{
    switch (sig)
    {
    case ESIGNAL_INIT:
        memset(&Mode_pagers, 0, sizeof(Mode_pagers));
        break;
    case ESIGNAL_CONFIGURE:
        break;
    case ESIGNAL_START:
        if (!Conf_pagers.enable)
            break;
        Conf_pagers.enable = 0;
        PagersShow(1);
        break;

    case ESIGNAL_AREA_CONFIGURED:
        PagersReconfigure();
        break;
    case ESIGNAL_AREA_SWITCH_START:
        PagerHiwinHide();
        break;
    case ESIGNAL_AREA_SWITCH_DONE:
        PagersUpdate(DesksGetCurrent(), PAGER_UPD_EWIN_GEOM,
                     0, 0, 99999, 99999);
        UpdatePagerSel();
        break;

    case ESIGNAL_DESK_ADDED:
        if (Mode.wm.startup)
            break;
        NewPagerForDesktop((Desk *) prm);
        break;
    case ESIGNAL_DESK_REMOVED:
        PagersForDesktopDisable((Desk *) prm);
        break;
    case ESIGNAL_DESK_SWITCH_START:
        PagerHiwinHide();
        break;
    case ESIGNAL_DESK_SWITCH_DONE:
        UpdatePagerSel();
        break;
    case ESIGNAL_DESK_RESIZE:
        PagersReconfigure();
        break;

    case ESIGNAL_BACKGROUND_CHANGE:
        PagersUpdateBackground((Desk *) prm);
        break;

    case ESIGNAL_EWIN_UNMAP:
        PagersUpdateEwin((EWin *) prm, PAGER_UPD_EWIN_GONE);
        break;
    case ESIGNAL_EWIN_CHANGE:
        PagersUpdateEwin((EWin *) prm, PAGER_UPD_EWIN_GEOM);
        break;
    case ESIGNAL_EWIN_DAMAGE:
        PagersUpdateEwin((EWin *) prm, PAGER_UPD_EWIN_DAMAGE);
        break;
    }
}

static void
IPC_Pager(const char *params)
{
    const char     *p = params;
    char            prm1[128];
    int             len, desk;
    Desk           *dsk;

    if (!p)
        return;

    prm1[0] = '\0';
    len = 0;
    sscanf(p, "%100s %n", prm1, &len);
    p += len;

    if (!strcmp(prm1, "on"))
    {
        PagersShow(1);
    }
    else if (!strcmp(prm1, "off"))
    {
        PagersShow(0);
    }
    else if (!strcmp(prm1, "desk"))
    {
        desk = -1;
        prm1[0] = '\0';
        sscanf(p, "%d %100s", &desk, prm1);
        dsk = DeskGet(desk);

        if (!dsk)
        {
            ;
        }
        else if (!strcmp(prm1, "on"))
        {
            PagersForDesktopEnable(dsk);
        }
        else if (!strcmp(prm1, "new"))
        {
            NewPagerForDesktop(dsk);
        }
        else if (!strcmp(prm1, "off"))
        {
            PagersForDesktopDisable(dsk);
        }
    }
    else if (!strcmp(prm1, "hiq"))
    {
        if (!strcmp(p, "on"))
            PagerSetHiQ(1);
        else if (!strcmp(p, "off"))
            PagerSetHiQ(0);
    }
    else if (!strcmp(prm1, "mode"))
    {
        if (!strncmp(p, "si", 2))
            PagersSetMode(PAGER_MODE_SIMPLE);
        else if (!strncmp(p, "sn", 2))
            PagersSetMode(PAGER_MODE_SNAP);
        else if (!strncmp(p, "li", 2))
            PagersSetMode(PAGER_MODE_LIVE);
    }
    else if (!strcmp(prm1, "zoom"))
    {
        if (!strcmp(p, "on"))
            PagersSetZoom(1);
        else if (!strcmp(p, "off"))
            PagersSetZoom(0);
    }
}

static const IpcItem PagersIpcArray[] = {
    {
     IPC_Pager,
     "pager", "pg",
     "Toggle the status of the Pager and various pager settings",
     "use \"pager <on/off>\" to set the current mode\nuse \"pager ?\" "
     "to get the current mode\n"
     "  pager <#> <on/off/?>   Toggle or test any desktop's pager\n"
     "  pager hiq <on/off>     Toggle high quality pager\n"
     "  pager scanrate <#>     Toggle number of line updates per second\n"
     "  pager mode <mode>      Set pager mode (simple/snapshot/live)\n"
     "  pager title <on/off>   Toggle title display in the pager\n"
     "  pager zoom <on/off>    Toggle zooming in the pager\n" }
    ,
};

/*
 * Configuration items
 */
static const CfgItem PagersCfgItems[] = {
    CFG_ITEM_BOOL(Conf_pagers, enable, 1),
    CFG_ITEM_BOOL(Conf_pagers, zoom, 1),
    CFG_ITEM_BOOL(Conf_pagers, title, 1),
    CFG_ITEM_BOOL(Conf_pagers, hiq, 1),
    CFG_ITEM_INT(Conf_pagers, mode, PAGER_MODE_LIVE),
    CFG_ITEM_INT(Conf_pagers, scanspeed, 10),
    CFG_ITEM_INT(Conf_pagers, sel_button, 2),
    CFG_ITEM_INT(Conf_pagers, win_button, 1),
    CFG_ITEM_INT(Conf_pagers, menu_button, 3),
};

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

const EModule   ModPagers = {
    "pagers", "pg",
    PagersSighan,
    MOD_ITEMS(PagersIpcArray),
    MOD_ITEMS(PagersCfgItems)
};
