/*
 * Copyright (C) 2000-2007 Carsten Haitzler, Geoff Harrison and various contributors
 * Copyright (C) 2004-2023 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 <poll.h>
#include <sys/time.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/shape.h>
#if USE_XSYNC
#include <X11/extensions/sync.h>
#endif
#if USE_XSCREENSAVER
#include <X11/extensions/scrnsaver.h>
#endif
#if USE_XRANDR
#include <X11/extensions/Xrandr.h>
#endif
#if USE_XINERAMA
#include <X11/extensions/Xinerama.h>
#endif
#if USE_COMPOSITE
#include <X11/extensions/Xcomposite.h>
#include <X11/extensions/Xdamage.h>
#include <X11/extensions/Xfixes.h>
#include <X11/extensions/Xrender.h>
#endif
#if USE_XPRESENT
#include <X11/extensions/Xpresent.h>
#define USE_GENERIC 1
#endif
#if USE_GLX
#include <GL/glx.h>
#endif
#if USE_XI2
#include <X11/extensions/XInput2.h>
#define USE_GENERIC 1
#endif

#include "E.h"
#include "aclass.h"
#include "ecompmgr.h"
#include "emodule.h"
#include "events.h"
#include "timers.h"
#include "tooltips.h"
#include "xwin.h"

#if ENABLE_DEBUG_EVENTS
static const char *EventName(unsigned int type);
#endif

/*
 * Server extension handling
 */

typedef struct {
    int             version;
    int             major_op, event_base, error_base;
} EServerExtData;

typedef struct {
    const char     *name;
    unsigned int    ix;
    int             (*query_ver)(Display * dpy, int *major, int *minor);
    void            (*init)(int avaliable);
} EServerExt;

static EServerExtData ExtData[12];

#define event_base_shape ExtData[XEXT_SHAPE].event_base
#define event_base_randr ExtData[XEXT_RANDR].event_base
#define event_base_damage ExtData[XEXT_DAMAGE].event_base
#define event_base_saver  ExtData[XEXT_SCRSAVER].event_base
#define event_base_glx    ExtData[XEXT_GLX].event_base

static void
ExtInitShape(int available)
{
    if (available)
        return;

    AlertX(_("X server setup error"), _("OK"), NULL, NULL,
           _("FATAL ERROR:\n" "\n"
             "This Xserver does not support the Shape extension.\n"
             "This is required for Enlightenment to run.\n" "\n"
             "Your Xserver probably is too old or mis-configured.\n" "\n"
             "Exiting.\n"));
    EExit(1);
}

#if USE_XSYNC
static void
ExtInitSync(int available)
{
    int             i, num;
    XSyncSystemCounter *xssc;

    if (!available)
        return;

    xssc = XSyncListSystemCounters(disp, &num);
    for (i = 0; i < num; i++)
    {
        if (!strcmp(xssc[i].name, "SERVERTIME"))
            Mode.display.server_time = xssc[i].counter;
        if (EDebug(EDBUG_TYPE_VERBOSE))
            Eprintf(" Sync counter %2d: %10s %#lx %#x:%#x\n", i,
                    xssc[i].name, xssc[i].counter,
                    XSyncValueHigh32(xssc[i].resolution),
                    XSyncValueLow32(xssc[i].resolution));
    }
    XSyncFreeSystemCounterList(xssc);

    if (Mode.display.server_time == NoXID)
        Conf.movres.enable_sync_request = 0;
}
#endif

#if USE_XSCREENSAVER
static void
ExtInitSS(int available)
{
    if (!available)
        return;

    if (EDebug(EDBUG_TYPE_VERBOSE))
    {
        XScreenSaverInfo *xssi = XScreenSaverAllocInfo();

        XScreenSaverQueryInfo(disp, WinGetXwin(VROOT), xssi);
        Eprintf(" Screen saver window=%#lx\n", xssi->window);
        XFree(xssi);
    }
    XScreenSaverSelectInput(disp, WinGetXwin(VROOT),
                            ScreenSaverNotifyMask | ScreenSaverCycleMask);
}
#endif

#if USE_XRANDR
static void
EventsRRUpdateInfo(void)
{
    XRRScreenConfiguration *sc;
    int             fps;

    sc = XRRGetScreenInfo(disp, WinGetXwin(VROOT));
    fps = XRRConfigCurrentRate(sc);
    XRRFreeScreenConfigInfo(sc);

    /* We may get e.g. fps = 0 (Xephyr) */
    if (fps > 0 && fps < 240)
        Mode.screen.fps = fps;

    if (EDebug(EDBUG_TYPE_VERBOSE))
        Eprintf("Screen refresh rate = %d(%d) Hz\n", Mode.screen.fps, fps);
}

static void
ExtInitRR(int available)
{
    Rotation        rot;

    if (!available)
        return;

    /* Listen for RandR events */
    XRRSelectInput(disp, WinGetXwin(VROOT), RRScreenChangeNotifyMask);

    XRRRotations(disp, Dpy.screen, &rot);
    Mode.screen.rotation = rot;

    EventsRRUpdateInfo();
}

void
EventsRandrScreenChange(XEvent *ev)
{
    const XRRScreenChangeNotifyEvent *rrev = (XRRScreenChangeNotifyEvent *) ev;

    XRRUpdateConfiguration(ev);

    Mode.screen.rotation = rrev->rotation;

    EventsRRUpdateInfo();
}
#endif                          /* USE_XRANDR */

#if USE_XI2
static          Status
EInputQueryVersion(Display *dpy,
                   int *major_version_return, int *minor_version_return)
{
    *major_version_return = XI_2_Major;
    *minor_version_return = XI_2_Minor;

    return XIQueryVersion(dpy, major_version_return, minor_version_return);
}

#include <X11/extensions/XInput.h>

static void
ExtInitInput(int available)
{
    int             i, j, nd;
    XIDeviceInfo   *dvi, *dvis;

    if (!available)
        return;

    dvis = XIQueryDevice(disp, XIAllDevices, &nd);
    for (i = 0; i < nd; i++)
    {
        dvi = dvis + i;

        if (dvi->use == XIMasterPointer && Mode.events.xi2_ptr == 0)
            Mode.events.xi2_ptr = dvi->deviceid;
        else if (dvi->use == XIMasterKeyboard && Mode.events.xi2_kbd == 0)
            Mode.events.xi2_kbd = dvi->deviceid;

        if (!EDebug(EDBUG_TYPE_VERBOSE))
            continue;

        if (i == 0)
            Eprintf("Dev id use att ena name\n");
        Eprintf(" %2d %2d %3d %3d %3d %-32s %2d", i,
                dvi->deviceid, dvi->use, dvi->attachment, dvi->enabled,
                dvi->name, dvi->num_classes);
        for (j = 0; j < dvi->num_classes; j++)
        {
            printf(" %2d/%2d", dvi->classes[j]->type,
                   dvi->classes[j]->sourceid);
        }
        printf("\n");
    }
    XIFreeDeviceInfo(dvis);
}
#endif

#if USE_XPRESENT
static void
ExtInitPresent(int available)
{
    if (!available)
        return;

    if (EDebug(EDBUG_TYPE_VERBOSE))
    {
        Eprintf(" Capabilities: %#x\n",
                XPresentQueryCapabilities(disp, WinGetXwin(VROOT)));
    }
}
#endif

static const EServerExt Extensions[] = {
    { "SHAPE", XEXT_SHAPE, XShapeQueryVersion, ExtInitShape },
#if USE_XSYNC
    { "SYNC", XEXT_SYNC, XSyncInitialize, ExtInitSync },
#endif
#if USE_XSCREENSAVER
    { "MIT-SCREEN-SAVER", XEXT_SCRSAVER, XScreenSaverQueryVersion, ExtInitSS },
#endif
#if USE_XRANDR
    { "RANDR", XEXT_RANDR, XRRQueryVersion, ExtInitRR },
#endif
#if USE_XINERAMA
    { "XINERAMA", XEXT_XINERAMA, XineramaQueryVersion, NULL },
#endif
#if USE_COMPOSITE
    { "Composite", XEXT_COMPOSITE, XCompositeQueryVersion, NULL },
    { "DAMAGE", XEXT_DAMAGE, XDamageQueryVersion, NULL },
    { "XFIXES", XEXT_FIXES, XFixesQueryVersion, NULL },
    { "RENDER", XEXT_RENDER, XRenderQueryVersion, NULL },
#endif
#if USE_GLX
    { "GLX", XEXT_GLX, glXQueryVersion, NULL },
#endif
#if USE_XI2
    { "XInputExtension", XEXT_XI, EInputQueryVersion, ExtInitInput },
#endif
#if USE_XPRESENT
    { "Present", XEXT_PRESENT, XPresentQueryVersion, ExtInitPresent },
#endif
};

static void
ExtQuery(const EServerExt *ext)
{
    int             available, major, minor;
    EServerExtData *exd = ExtData + ext->ix;

    available = XQueryExtension(disp, ext->name, &(exd->major_op),
                                &(exd->event_base), &(exd->error_base));

    if (available)
    {
        Mode.server.extensions |= 1 << ext->ix;

        ext->query_ver(disp, &major, &minor);
        exd->version = VERS(major, minor);

        if (EDebug(EDBUG_TYPE_VERBOSE))
            Eprintf("Extension %-15s version %d.%d -"
                    " req/evt/err base = %3d/%3d/%3d\n", ext->name,
                    major, minor,
                    exd->major_op, exd->event_base, exd->error_base);
    }

    if (ext->init)
        ext->init(available);
}

int
ExtVersion(int ext_ix)
{
    EServerExtData *exd = ExtData + ext_ix;

    return exd->version;
}

/*
 * File descriptor handling
 */

typedef struct {
#if 0                           /* Unused */
    const char     *name;
#endif
    void            (*handler)(void);
} EventFdDesc;

static int      nfds = 0;
static struct pollfd *pfdl = NULL;
static EventFdDesc *pfds = NULL;

int
EventFdRegister(int fd, EventFdHandler *handler)
{
    int             efd;

    efd = nfds++;
    pfds = EREALLOC(EventFdDesc, pfds, nfds);

    pfdl = EREALLOC(struct pollfd, pfdl, nfds);
    pfdl[efd].fd = fd;

    pfds[efd].handler = handler;

    return efd;
}

void
EventFdUnregister(int efd)
{
    if (pfdl[efd].fd > 0)
        pfdl[efd].fd = -pfdl[efd].fd;
}

/*
 * Event handling
 */

#define DOUBLE_CLICK_TIME 250   /* Milliseconds */

void
EventsInit(void)
{
    unsigned int    i;

    memset(ExtData, 0, sizeof(ExtData));

    Mode.screen.fps = 60;       /* If not randr or weirdness */

    for (i = 0; i < E_ARRAY_SIZE(Extensions); i++)
        ExtQuery(Extensions + i);

#if USE_COMPOSITE
#define XEXT_MASK_CM_ALL ((1 << XEXT_COMPOSITE) | (1 << XEXT_DAMAGE) | \
                          (1 << XEXT_FIXES) | (1 << XEXT_RENDER))
    if (((Mode.server.extensions & XEXT_MASK_CM_ALL) == XEXT_MASK_CM_ALL) &&
        (ExtData[XEXT_COMPOSITE].version >= VERS(0, 2)))
        Mode.server.extensions |= 1 << XEXT_CM_ALL;
#endif

    EventFdRegister(ConnectionNumber(disp), NULL);
}

static const char *
EventsGetExtensionName(int req)
{
    unsigned int    i;
    EServerExtData *exd;

    for (i = 0; i < E_ARRAY_SIZE(Extensions); i++)
    {
        exd = ExtData + Extensions[i].ix;
        if (req == exd->major_op)
            return Extensions[i].name;
    }

    return "?";
}

void
EventShowError(const XEvent *evp)
{
    const XErrorEvent *ev = &evp->xerror;
    char            buf[64], buf1[64];

    if (ev->request_code < 128)
        Esnprintf(buf, sizeof(buf), "%d", ev->request_code);
    else
        Esnprintf(buf, sizeof(buf), "%s.%d",
                  EventsGetExtensionName(ev->request_code), ev->minor_code);
    XGetErrorDatabaseText(disp, "XRequest", buf, buf, buf1, sizeof(buf1));
    XGetErrorText(disp, ev->error_code, buf, sizeof(buf));
    Eprintf("*** ERROR: xid=%#lx req=%i/%i err=%i: %s: %s\n",
            ev->resourceid, ev->request_code, ev->minor_code,
            ev->error_code, buf1, buf);
}

int
EventsUpdateXY(int *px, int *py)
{
    int             ss;

    ss = EQueryPointer(NULL, &Mode.events.cx, &Mode.events.cy, NULL, NULL);
    if (px)
        *px = Mode.events.cx;
    if (py)
        *py = Mode.events.cy;

    return ss;
}

void
EventsBlock(int mode)
{
    Mode.events.block = mode;
    if (EDebug(EDBUG_TYPE_EVENTS))
        Eprintf("%s: mode=%d\n", __func__, Mode.events.block);
}

static void
ModeGetXY(int rx, int ry)
{
    /* Mode.wm.win_x/y should always be 0 if not in window mode */
    Mode.events.cx = rx - Mode.wm.win_x;
    Mode.events.cy = ry - Mode.wm.win_y;
    if (Mode.wm.window)
    {
        if (rx < Mode.wm.win_x || rx >= Mode.wm.win_x + Mode.wm.win_w ||
            ry < Mode.wm.win_y || ry >= Mode.wm.win_y + Mode.wm.win_h)
            Mode.events.on_screen = 0;
    }
}

static void
HandleEvent(XEvent *ev)
{
    Win             win;

#if ENABLE_DEBUG_EVENTS
    if (EDebug(ev->type))
        EventShow(ev);
#endif

    win = ELookupXwin(ev->xany.window);

    Mode.events.serial = ev->xany.serial;

    switch (ev->type)
    {
    case KeyPress:
        Mode.events.last_keycode = ev->xkey.keycode;
        Mode.events.last_keystate = ev->xkey.state;
        /* FALLTHROUGH */
    case KeyRelease:
        Mode.events.time = ev->xkey.time;
        ModeGetXY(ev->xkey.x_root, ev->xkey.y_root);
#if 0                           /* FIXME - Why? */
        if (ev->xkey.root != WinGetXwin(VROOT))
        {
            XSetInputFocus(disp, ev->xkey.root, RevertToPointerRoot,
                           CurrentTime);
            ESync();
            ev->xkey.time = CurrentTime;
            EXSendEvent(ev->xkey.root, 0, ev);
            return;
        }
#endif
        Mode.events.on_screen = ev->xkey.same_screen;
        goto do_stuff;

    case ButtonPress:
    case ButtonRelease:
        Mode.events.time = ev->xbutton.time;
        ModeGetXY(ev->xbutton.x_root, ev->xbutton.y_root);
        Mode.events.on_screen = ev->xbutton.same_screen;
        TooltipsHide();
        goto do_stuff;

    case MotionNotify:
        Mode.events.time = ev->xmotion.time;
        Mode.events.px = Mode.events.mx;
        Mode.events.py = Mode.events.my;
        ModeGetXY(ev->xmotion.x_root, ev->xmotion.y_root);
        Mode.events.mx = Mode.events.cx;
        Mode.events.my = Mode.events.cy;
        Mode.events.on_screen = ev->xmotion.same_screen;
        break;

    case EnterNotify:
        Mode.context_win = win;
        Mode.events.time = ev->xcrossing.time;
        Mode.events.on_screen = ev->xcrossing.same_screen;
        if (ev->xcrossing.mode == NotifyGrab &&
            ev->xcrossing.detail == NotifyInferior)
        {
            Mode.grabs.pointer_grab_window = ev->xany.window;
            if (!Mode.grabs.pointer_grab_active)
                Mode.grabs.pointer_grab_active = 2;
        }
        ModeGetXY(ev->xcrossing.x_root, ev->xcrossing.y_root);
        TooltipsHide();
        goto do_stuff;

    case LeaveNotify:
        Mode.events.time = ev->xcrossing.time;
        Mode.events.on_screen = ev->xcrossing.same_screen;
        if (ev->xcrossing.mode == NotifyGrab &&
            ev->xcrossing.detail == NotifyInferior)
        {
            Mode.grabs.pointer_grab_window = NoXID;
            Mode.grabs.pointer_grab_active = 0;
        }
        ModeGetXY(ev->xcrossing.x_root, ev->xcrossing.y_root);
        TooltipsHide();
        goto do_stuff;

    case PropertyNotify:
        Mode.events.time = ev->xproperty.time;
        break;

      do_stuff:
        if (ev->xany.window == WinGetXwin(VROOT))
            ActionclassesGlobalEvent(ev);
        break;
    }

    switch (ev->type)
    {
    case KeyPress:             /*  2 */
    case KeyRelease:           /*  3 */
        /* Unfreeze keyboard in case we got here by keygrab */
        XAllowEvents(disp, AsyncKeyboard, CurrentTime);
        break;

    case ButtonPress:          /*  4 */
        SoundPlay(SOUND_BUTTON_CLICK);

        Mode.events.double_click =
            ((ev->xbutton.time - Mode.events.last_btime < DOUBLE_CLICK_TIME) &&
             ev->xbutton.button == Mode.events.last_button &&
             ev->xbutton.window == Mode.events.last_bpress2);

        Mode.events.last_bpress = ev->xbutton.window;
        Mode.events.last_bpress2 = ev->xbutton.window;
        Mode.events.last_btime = ev->xbutton.time;
        Mode.events.last_button = ev->xbutton.button;
        break;
    case ButtonRelease:        /*  5 */
        SoundPlay(SOUND_BUTTON_RAISE);
        break;
    }

    /* The new event dispatcher */
    EventCallbacksProcess(win, ev);

    /* Post-event stuff TBD */
    switch (ev->type)
    {
    case ButtonRelease:        /*  5 */
        Mode.events.last_bpress = 0;
        break;

#if 1                           /* Do this here? */
    case DestroyNotify:
        EUnregisterXwin(ev->xdestroywindow.window);
        break;
#endif

    case MappingNotify:
        XRefreshKeyboardMapping(&ev->xmapping);
        if (Conf.testing.bindings_reload)
            ActionclassesReload();
        break;
    }
}

static void
EventsCompress(XEvent *evq, int count)
{
    XEvent         *ev, *ev2;
    int             i, j, n;
    int             xa, ya, xb, yb;
    int             type;

#if ENABLE_DEBUG_EVENTS
    /* Debug - should be taken out */
    if (EDebug(EDBUG_TYPE_COMPRESSION))
        for (i = 0; i < count; i++)
            if (evq[i].type)
                Eprintf("%s-1 %3d %s w=%#lx\n", __func__, i,
                        EventName(evq[i].type), evq[i].xany.window);
#endif

    /* Loop through event list, starting with latest */
    for (i = count - 1; i >= 0; i--)
    {
        ev = evq + i;

        type = ev->type;
        switch (type)
        {
        case 0:
            /* Already thrown away */
        default:
            break;

        case MotionNotify:
            /* Discard all but last motion event */
            n = 0;
            for (j = i - 1; j >= 0; j--)
            {
                ev2 = evq + j;
                if (ev2->type == type)
                {
                    n++;
                    ev2->type = 0;
                }
            }
#if ENABLE_DEBUG_EVENTS
            if (n && EDebug(EDBUG_TYPE_COMPRESSION))
                Eprintf("%s: n=%4d %s %#lx x,y = %d,%d\n", __func__,
                        n, EventName(type), ev->xmotion.window,
                        ev->xmotion.x, ev->xmotion.y);
#endif
            break;

        case LeaveNotify:
            for (j = i - 1; j >= 0; j--)
            {
                ev2 = evq + j;
                if (ev2->type == EnterNotify)
                {
                    if (ev2->xcrossing.window == ev->xcrossing.window)
                        goto do_enter_leave_nuked;
                }
            }
            break;
          do_enter_leave_nuked:
            ev2->type = ev->type = 0;
            for (n = i - 1; n > j; n--)
            {
                ev2 = evq + n;
                if (ev2->type == MotionNotify)
                {
                    if (ev2->xmotion.window != ev->xmotion.window)
                        continue;
                    ev2->type = 0;
                }
            }
#if ENABLE_DEBUG_EVENTS
            if (EDebug(EDBUG_TYPE_COMPRESSION))
                Eprintf("%s: n=%4d %s %#lx\n", __func__,
                        1, EventName(type), ev->xcrossing.window);
#endif
            break;

        case DestroyNotify:
            for (j = i - 1; j >= 0; j--)
            {
                ev2 = evq + j;
                switch (ev2->type)
                {
                case CreateNotify:
                    if (ev2->xcreatewindow.window != ev->xdestroywindow.window)
                        continue;
                    ev2->type = EX_EVENT_CREATE_GONE;
                    goto loop_quit_DestroyNotify;
                case DestroyNotify:
                    break;
                case UnmapNotify:
                    if (ev2->xunmap.window != ev->xdestroywindow.window)
                        continue;
                    ev2->type = EX_EVENT_UNMAP_GONE;
                    break;
                case MapNotify:
                    if (ev2->xmap.window != ev->xdestroywindow.window)
                        continue;
                    ev2->type = EX_EVENT_MAP_GONE;
                    break;
                case MapRequest:
                    if (ev2->xmaprequest.window != ev->xdestroywindow.window)
                        continue;
                    ev2->type = EX_EVENT_MAPREQUEST_GONE;
                    break;
                case ReparentNotify:
                    if (ev2->xreparent.window != ev->xdestroywindow.window)
                        continue;
                    ev2->type = EX_EVENT_REPARENT_GONE;
                    break;
                case ConfigureRequest:
                    if (ev2->xconfigurerequest.window !=
                        ev->xdestroywindow.window)
                        continue;
                    ev2->type = 0;
                    break;
                default:
                    /* Nuke all other events on a destroyed window */
                    if (ev2->xany.window != ev->xdestroywindow.window)
                        continue;
                    ev2->type = 0;
                    break;
                }
            }
          loop_quit_DestroyNotify:
            break;

        case ReparentNotify:
            /* Discard all but last reparent event for window */
            n = 0;
            for (j = i - 1; j >= 0; j--)
            {
                ev2 = evq + j;
                if (ev2->type == type &&
                    ev2->xany.window == ev->xany.window &&
                    ev2->xreparent.window == ev->xreparent.window)
                {
                    n++;
                    ev2->type = 0;
                }
            }
#if ENABLE_DEBUG_EVENTS
            if (n && EDebug(EDBUG_TYPE_COMPRESSION))
                Eprintf("%s: n=%4d %s %#lx\n", __func__,
                        n, EventName(type), ev->xreparent.window);
#endif
            break;

        case Expose:
            n = 0;
            xa = ev->xexpose.x;
            xb = xa + ev->xexpose.width;
            ya = ev->xexpose.y;
            yb = ya + ev->xexpose.height;
            for (j = i - 1; j >= 0; j--)
            {
                ev2 = evq + j;
                if (ev2->type == type &&
                    ev2->xexpose.window == ev->xexpose.window)
                {
                    n++;
                    ev2->type = 0;
                    if (xa > ev2->xexpose.x)
                        xa = ev2->xexpose.x;
                    if (xb < ev2->xexpose.x + ev2->xexpose.width)
                        xb = ev2->xexpose.x + ev2->xexpose.width;
                    if (ya > ev2->xexpose.y)
                        ya = ev2->xexpose.y;
                    if (yb < ev2->xexpose.y + ev2->xexpose.height)
                        yb = ev2->xexpose.y + ev2->xexpose.height;
                }
            }
            if (n)
            {
                ev->xexpose.x = xa;
                ev->xexpose.width = xb - xa;
                ev->xexpose.y = ya;
                ev->xexpose.height = yb - ya;
            }
#if ENABLE_DEBUG_EVENTS
            if (EDebug(EDBUG_TYPE_COMPRESSION))
                Eprintf("%s: n=%4d %s %#lx x=%4d-%4d y=%4d-%4d\n", __func__,
                        n, EventName(type), ev->xexpose.window, xa, xb, ya, yb);
#endif
            break;

        case EX_EVENT_SHAPE_NOTIFY:
            n = 0;
            for (j = i - 1; j >= 0; j--)
            {
                ev2 = evq + j;
                if (ev2->type == type && ev2->xany.window == ev->xany.window)
                {
                    n++;
                    ev2->type = 0;
                }
            }
#if ENABLE_DEBUG_EVENTS
            if (n && EDebug(EDBUG_TYPE_COMPRESSION))
                Eprintf("%s: n=%4d %s %#lx\n", __func__,
                        n, EventName(type), ev->xmotion.window);
#endif
            break;

        case GraphicsExpose:
        case NoExpose:
            /* Not using these */
            ev->type = 0;
            break;
        }
    }

#if ENABLE_DEBUG_EVENTS
    /* Debug - should be taken out */
    if (EDebug(EDBUG_TYPE_COMPRESSION))
        for (i = 0; i < count; i++)
            if (evq[i].type)
                Eprintf("%s-2 %3d %s w=%#lx\n", __func__, i,
                        EventName(evq[i].type), evq[i].xany.window);
#endif
}

#if USE_GENERIC

#if USE_XI2
typedef union {
    XIEvent         gen;        /* Generic XI2 */
    XIDeviceEvent   dev;        /* Device events */
    XIEnterEvent    elf;        /* Enter/leave, focus in/out */
} xie_t;

static void
_EventFetchXI2(XEvent *ev)
{
    xie_t          *xie = (xie_t *) ev->xcookie.data;

    if (EDebug(EDBUG_TYPE_XI2))
        Eprintf("%s: %#lx: type=%d devid=%d srcid=%d\n",
                __func__, xie->dev.event, xie->gen.evtype,
                xie->dev.deviceid, xie->dev.sourceid);

    switch (xie->gen.evtype)
    {
    default:
        break;
    case XI_KeyPress:
    case XI_KeyRelease:
    case XI_ButtonPress:
    case XI_ButtonRelease:
    case XI_Motion:
        ev->type = xie->gen.evtype;     /* Same as core */
#if 0
        /* Keep those. At least serial seems to be bad in xie. */
        ev->xany.serial = xie->gen.serial;
        ev->xany.send_event = xie->gen.send_event;
        ev->xany.display = xie->gen.display;
#endif
        ev->xkey.window = xie->dev.event;
        ev->xkey.root = xie->dev.root;
        ev->xkey.subwindow = xie->dev.child;
        ev->xkey.time = xie->gen.time;
        ev->xkey.x = (int)xie->dev.event_x;
        ev->xkey.y = (int)xie->dev.event_y;
        ev->xkey.x_root = (int)xie->dev.root_x;
        ev->xkey.y_root = (int)xie->dev.root_y;
        ev->xkey.state = xie->dev.mods.effective;
        ev->xkey.keycode = xie->dev.detail;
#if 0
        /* These are the only differences between the key/button/motion
         * structs. The Xlib struct layout should ensure that things land
         * appropriately (xmotion.is_hint is not used) */
        ev->xbutton.button = xie->dev.detail;
        ev->xmotion.is_hint = xie->dev.detail;  /* ??? */
#endif
        ev->xkey.same_screen = xie->dev.deviceid;       /* FIXME */
        break;
    case XI_Enter:
    case XI_Leave:
        ev->type = xie->gen.evtype;     /* Same as core */
#if 0
        /* Keep those. At least serial seems to be bad in xie. */
        ev->xany.serial = xie->gen.serial;
        ev->xany.send_event = xie->gen.send_event;
        ev->xany.display = xie->gen.display;
#endif
        ev->xcrossing.window = xie->elf.event;
        ev->xcrossing.root = xie->elf.root;
        ev->xcrossing.subwindow = xie->elf.child;
        ev->xcrossing.time = xie->gen.time;
        ev->xcrossing.x = (int)xie->elf.event_x;
        ev->xcrossing.y = (int)xie->elf.event_y;
        ev->xcrossing.x_root = (int)xie->elf.root_x;
        ev->xcrossing.y_root = (int)xie->elf.root_y;
        /* mode and detail values are the same in core/XI2.
         * XI2 has a few extra modes. */
        ev->xcrossing.mode = xie->elf.mode;
        ev->xcrossing.detail = xie->elf.detail;
        ev->xcrossing.same_screen = xie->elf.deviceid;  /* FIXME */
        ev->xcrossing.focus = xie->elf.focus;
        ev->xcrossing.state = xie->elf.mods.effective;
        break;
    case XI_FocusIn:
    case XI_FocusOut:
        ev->type = xie->gen.evtype;     /* Same as core */
#if 0
        /* Keep those. At least serial seems to be bad in xie. */
        ev->xany.serial = xie->gen.serial;
        ev->xany.send_event = xie->gen.send_event;
        ev->xany.display = xie->gen.display;
#endif
        ev->xfocus.window = xie->elf.event;
        /* mode and detail values are the same in core/XI2.
         * XI2 has a few extra modes. */
        ev->xfocus.mode = xie->elf.mode;
        ev->xfocus.detail = xie->elf.detail;
        break;
    }
}
#endif                          /* USE_XI2 */

#if USE_XPRESENT
typedef union {
    XPresentEvent   xpe;
    XPresentConfigureNotifyEvent conf;
    XPresentCompleteNotifyEvent cmpl;
    XPresentIdleNotifyEvent idle;
} xpe_t;

static void
_EventFetchPresent(XEvent *ev)
{
    xpe_t          *xpe = (xpe_t *) ev->xcookie.data;

    if (EDebug(EDBUG_TYPE_PRESENT))
        Eprintf("%s: %#lx: type=%d\n",
                __func__, xpe->idle.window, xpe->xpe.evtype);
}
#endif                          /* USE_XPRESENT */

static void
_EventFetchGeneric(XEvent *ev)
{
    XGenericEventCookie gec;

    if (!XGetEventData(disp, &ev->xcookie))
        return;

    gec = ev->xcookie;          /* Save copy for XFreeEventData() */

#if USE_XI2
    if (ev->xcookie.extension == ExtData[XEXT_XI].major_op)
    {
        _EventFetchXI2(ev);
        goto done;
    }
#endif
#if USE_XPRESENT
    if (ev->xcookie.extension == ExtData[XEXT_PRESENT].major_op)
    {
        _EventFetchPresent(ev);
        goto done;
    }
#endif
    /* We should never go here */
    Eprintf("*** %s: ext=%d type=%d\n", __func__,
            ev->xcookie.extension, ev->xcookie.evtype);

  done:
    XFreeEventData(disp, &gec);
}

#endif                          /* USE_GENERIC */

static int
EventsFetch(XEvent **evq_ptr, int *evq_size, int *evq_alloc)
{
    int             i, n;
    XEvent         *evq = *evq_ptr, *ev;
    int             count = *evq_size;
    int             qsz = *evq_alloc;
    int             fetched;

    if (EDebug(EDBUG_TYPE_EVENTS))
        Eprintf("%s-B n=%d/%d block=%d\n", __func__,
                *evq_size, *evq_alloc, Mode.events.block);

    /* Fetch the entire event queue */
    fetched = 0;
    for (i = count; (n = XPending(disp)) > 0;)
    {
        fetched += n;
        count += n;
        if (count > qsz)
        {
            qsz = count;
            evq = EREALLOC(XEvent, evq, qsz);
        }
        ev = evq + i;
        for (; i < count; i++, ev++)
        {
            XNextEvent(disp, ev);
#if USE_GENERIC
            if (ev->type == GenericEvent)
            {
                _EventFetchGeneric(ev);
                continue;
            }
#endif

            /* Map some event types to E internals */
            if (ev->type == event_base_shape + ShapeNotify)
                ev->type = EX_EVENT_SHAPE_NOTIFY;
#if USE_XRANDR
            else if (ev->type == event_base_randr + RRScreenChangeNotify)
                ev->type = EX_EVENT_SCREEN_CHANGE_NOTIFY;
#endif
#if USE_COMPOSITE
            else if (ev->type == event_base_damage + XDamageNotify)
                ev->type = EX_EVENT_DAMAGE_NOTIFY;
#endif
#if USE_XSCREENSAVER
            else if (ev->type == event_base_saver + ScreenSaverNotify)
                ev->type = EX_EVENT_SAVER_NOTIFY;
#endif
#if USE_GLX
            else if (ev->type == event_base_glx + GLX_BufferSwapComplete)
                ev->type = EX_EVENT_GLX_FLIP;
#endif
        }
    }

    *evq_ptr = evq;
    *evq_alloc = qsz;
    *evq_size = count;

    if (EDebug(EDBUG_TYPE_EVENTS))
        Eprintf("%s-E n=%d/%d fetched=%d\n", __func__,
                *evq_size, *evq_alloc, fetched);

    return fetched;             /* Return number of fetched events */
}

static int
EventsProcess(XEvent **evq_ptr, int *evq_size)
{
    int             i, n, count;
    XEvent         *evq;

    evq = *evq_ptr;
    n = *evq_size;

    EventsCompress(evq, n);

    if (EDebug(EDBUG_TYPE_EVENTS) > 1)
        Eprintf("%s-B n=%d\n", __func__, n);

    for (i = count = 0; i < n; i++)
    {
        if (evq[i].type == 0)
            continue;

        if (EDebug(EDBUG_TYPE_EVENTS) > 2)
            EventShow(evq + i);

        count++;
        HandleEvent(evq + i);
        evq[i].type = 0;
    }

    if (EDebug(EDBUG_TYPE_EVENTS) > 1)
        Eprintf("%s-E %d/%d\n", __func__, count, n);

    *evq_size = 0;              /* All events are now processed */

    return count;               /* Return number of processed events */
}

/*
 * This is the primary event loop.  Everything that is going to happen in the
 * window manager has to start here at some point.  This is where all the
 * events from the X server are interpreted, timer events are inserted, etc
 */
void
EventsMain(void)
{
    static int      evq_alloc = 0;
    static int      evq_size = 0;
    static int      evq_fetch = 0;
    static XEvent  *evq_ptr = NULL;
    unsigned int    time1, time2;
    int             dtl, dt;
    int             count, pfetch;
    int             i;

    time1 = GetTimeMs();

    for (;;)
    {
        /* Fetch the entire event queue */
        EventsFetch(&evq_ptr, &evq_size, &evq_alloc);
        pfetch = evq_size;

        if (!Mode.events.block)
        {
            EventsProcess(&evq_ptr, &evq_size);

            if (pfetch)
            {
                evq_fetch = (pfetch > evq_fetch) ?
                    pfetch : (3 * evq_fetch + pfetch) / 4;
                if (EDebug(EDBUG_TYPE_EVENTS) > 1)
                    Eprintf("%s - Alloc/fetch/peak=%d/%d/%d)\n",
                            __func__, evq_alloc, pfetch, evq_fetch);
                if ((evq_ptr) && ((evq_alloc - evq_fetch) > 64))
                {
                    evq_alloc = 0;
                    EFREE_NULL(evq_ptr);
                }
            }
        }

        /* time2 = current time */
        time2 = GetTimeMs();
        dtl = time2 - time1;
        Mode.events.time_ms = time1 = time2;
        Mode.events.seqn++;
        /* dtl = time spent since we last were here */

        /* Run all expired timers */
        TimersRun(time2);

        /* Run idlers */
        IdlersRun();

        /* Get time to first non-expired (0 means none) */
        dt = TimersRunNextIn(time2);

        /* Do composite rendering */
        dt = ECompMgrRender(dt);

        if (Mode.wm.exit_mode)
            break;

        if (Mode.events.block)
            XFlush(disp);
        else if (XPending(disp))
            continue;

        for (i = 0; i < nfds; i++)
            pfdl[i].events = (i == 0 && Mode.events.block) ? 0 : POLLIN;

        if (dt < 0.)
            dt = 0.;
        count = poll(pfdl, nfds, (int)dt);

        if (EDebug(EDBUG_TYPE_EVENTS))
            Eprintf("%s: count=%d xfd=%d:%d dtl=%.6lf dt=%.6lf\n", __func__,
                    count, pfdl[0].fd, pfdl[0].revents, dtl * 1e-3, dt * 1e-3);

        if (count <= 0)
            continue;           /* Timeout (or error) */

        /* Excluding X fd */
        for (i = 1; i < nfds; i++)
        {
            if (pfdl[i].fd >= 0 && pfdl[i].revents & POLLIN)
            {
                if (EDebug(EDBUG_TYPE_EVENTS) > 1)
                    Eprintf("Event fd %d\n", i);
                pfds[i].handler();
            }
        }
    }
}

#if ENABLE_DEBUG_EVENTS
/*
 * Event debug stuff
 */

static const char *const TxtEventNames[] = {
    "Error", "Reply", "KeyPress", "KeyRelease", "ButtonPress",
    "ButtonRelease", "MotionNotify", "EnterNotify", "LeaveNotify", "FocusIn",
    "FocusOut", "KeymapNotify", "Expose", "GraphicsExpose", "NoExpose",
    "VisibilityNotify", "CreateNotify", "DestroyNotify", "UnmapNotify",
    "MapNotify",
    "MapRequest", "ReparentNotify", "ConfigureNotify", "ConfigureRequest",
    "GravityNotify",
    "ResizeRequest", "CirculateNotify", "CirculateRequest", "PropertyNotify",
    "SelectionClear",
    "SelectionRequest", "SelectionNotify", "ColormapNotify", "ClientMessage",
    "MappingNotify"
};

static const char *
EventName(unsigned int type)
{
    static char     buf[16];

    if (type < E_ARRAY_SIZE(TxtEventNames))
        return TxtEventNames[type];

    switch (type)
    {
    case EX_EVENT_CREATE_GONE:
        return "Create-Gone";
    case EX_EVENT_UNMAP_GONE:
        return "Unmap-Gone";
    case EX_EVENT_MAP_GONE:
        return "Map-Gone";
    case EX_EVENT_MAPREQUEST_GONE:
        return "MapRequest-Gone";
    case EX_EVENT_REPARENT_GONE:
        return "Reparent-Gone";
    case EX_EVENT_SHAPE_NOTIFY:
        return "ShapeNotify";
#if USE_XSCREENSAVER
    case EX_EVENT_SAVER_NOTIFY:
        return "ScreenSaverNotify";
#endif
#if USE_XRANDR
    case EX_EVENT_SCREEN_CHANGE_NOTIFY:
        return "ScreenChangeNotify";
#endif
#if USE_COMPOSITE
    case EX_EVENT_DAMAGE_NOTIFY:
        return "DamageNotify";
#endif
    }

    sprintf(buf, "%d", type);
    return buf;
}

static const char *const TxtEventNotifyModeNames[] = {
    "NotifyNormal", "NotifyGrab", "NotifyUngrab", "NotifyWhileGrabbed"
};

static const char *
EventNotifyModeName(unsigned int mode)
{
    if (mode < E_ARRAY_SIZE(TxtEventNotifyModeNames))
        return TxtEventNotifyModeNames[mode];

    return "Unknown";
}

static const char *const TxtEventNotifyDetailNames[] = {
    "NotifyAncestor", "NotifyVirtual", "NotifyInferior", "NotifyNonlinear",
    "NotifyNonlinearVirtual", "NotifyPointer", "NotifyPointerRoot",
    "NotifyDetailNone"
};

static const char *
EventNotifyDetailName(unsigned int detail)
{
    if (detail < E_ARRAY_SIZE(TxtEventNotifyDetailNames))
        return TxtEventNotifyDetailNames[detail];

    return "Unknown";
}

void
EventShow(const XEvent *ev)
{
    char           *txt, buf[64];

    Esnprintf(buf, sizeof(buf), "%#08lx %cEV-%s ev=%#lx",
              ev->xany.serial, (ev->xany.send_event) ? '*' : ' ',
              EventName(ev->type), ev->xany.window);

    switch (ev->type)
    {
    case KeyPress:
    case KeyRelease:
        Eprintf("%s sub=%#lx x,y=%d,%d state=%#x keycode=%#x ss=%d\n", buf,
                ev->xkey.subwindow, ev->xkey.x, ev->xkey.y,
                ev->xkey.state, ev->xkey.keycode, ev->xkey.same_screen);
        break;
    case ButtonPress:
    case ButtonRelease:
        Eprintf("%s sub=%#lx x,y=%d,%d state=%#x button=%#x ss=%d\n", buf,
                ev->xbutton.subwindow, ev->xbutton.x, ev->xbutton.y,
                ev->xbutton.state, ev->xbutton.button, ev->xbutton.same_screen);
        break;
    case MotionNotify:
        Eprintf("%s sub=%#lx x,y=%d,%d rx,ry=%d,%d ss=%d\n", buf,
                ev->xmotion.subwindow, ev->xmotion.x, ev->xmotion.y,
                ev->xmotion.x_root, ev->xmotion.y_root,
                ev->xmotion.same_screen);
        break;
    case EnterNotify:
    case LeaveNotify:
        Eprintf("%s sub=%#lx x,y=%d,%d m=%s d=%s ss=%d focus=%d\n", buf,
                ev->xcrossing.subwindow, ev->xcrossing.x, ev->xcrossing.y,
                EventNotifyModeName(ev->xcrossing.mode),
                EventNotifyDetailName(ev->xcrossing.detail),
                ev->xcrossing.same_screen, ev->xcrossing.focus);
        break;
    case FocusIn:
    case FocusOut:
        Eprintf("%s m=%s d=%s\n", buf, EventNotifyModeName(ev->xfocus.mode),
                EventNotifyDetailName(ev->xfocus.detail));
        break;
    case KeymapNotify:
    case Expose:
    case GraphicsExpose:
        Eprintf("%sx %d+%d %dx%d\n", buf,
                ev->xexpose.x, ev->xexpose.y,
                ev->xexpose.width, ev->xexpose.height);
        break;
    case VisibilityNotify:
        Eprintf("%s state=%d\n", buf, ev->xvisibility.state);
        break;
    case CreateNotify:
    case DestroyNotify:
    case UnmapNotify:
    case MapRequest:
    case EX_EVENT_CREATE_GONE:
    case EX_EVENT_UNMAP_GONE:
    case EX_EVENT_MAPREQUEST_GONE:
        Eprintf("%s win=%#lx\n", buf, ev->xcreatewindow.window);
        break;
    case MapNotify:
    case EX_EVENT_MAP_GONE:
        Eprintf("%s win=%#lx or=%d\n", buf, ev->xmap.window,
                ev->xmap.override_redirect);
        break;
    case ReparentNotify:
    case EX_EVENT_REPARENT_GONE:
        Eprintf("%s win=%#lx parent=%#lx %d+%d\n", buf,
                ev->xreparent.window, ev->xreparent.parent,
                ev->xreparent.x, ev->xreparent.y);
        break;
    case ConfigureNotify:
        Eprintf("%s win=%#lx %d+%d %dx%d bw=%d above=%#lx\n", buf,
                ev->xconfigure.window, ev->xconfigure.x,
                ev->xconfigure.y, ev->xconfigure.width, ev->xconfigure.height,
                ev->xconfigure.border_width, ev->xconfigure.above);
        break;
    case ConfigureRequest:
        Eprintf("%s win=%#lx m=%#lx %d+%d %dx%d bw=%d above=%#lx stk=%d\n",
                buf, ev->xconfigurerequest.window,
                ev->xconfigurerequest.value_mask, ev->xconfigurerequest.x,
                ev->xconfigurerequest.y, ev->xconfigurerequest.width,
                ev->xconfigurerequest.height,
                ev->xconfigurerequest.border_width, ev->xconfigurerequest.above,
                ev->xconfigurerequest.detail);
        break;
    case GravityNotify:
        goto case_common;
    case ResizeRequest:
        Eprintf("%s %dx%d\n", buf,
                ev->xresizerequest.width, ev->xresizerequest.height);
        break;
    case CirculateNotify:
    case CirculateRequest:
        goto case_common;
    case PropertyNotify:
        txt = XGetAtomName(disp, ev->xproperty.atom);
        Eprintf("%s Atom=%s(%ld)\n", buf, txt, ev->xproperty.atom);
        XFree(txt);
        break;
    case SelectionClear:
    case SelectionRequest:
    case SelectionNotify:
    case ColormapNotify:
        goto case_common;
    case ClientMessage:
        txt = XGetAtomName(disp, ev->xclient.message_type);
        Eprintf("%s ev_type=%s(%ld) data: %08lx %08lx %08lx %08lx %08lx\n",
                buf, txt, ev->xclient.message_type,
                ev->xclient.data.l[0], ev->xclient.data.l[1],
                ev->xclient.data.l[2], ev->xclient.data.l[3],
                ev->xclient.data.l[4]);
        XFree(txt);
        break;
    case MappingNotify:
        Eprintf("%s req=%d first=%d count=%d\n",
                buf, ev->xmapping.request,
                ev->xmapping.first_keycode, ev->xmapping.count);
        break;

    case EX_EVENT_SHAPE_NOTIFY:
#define se ((XShapeEvent *)ev)
        Eprintf("%s kind=%d shaped=%d %d,%d %dx%d\n", buf,
                se->kind, se->shaped, se->x, se->y, se->width, se->height);
#undef se
        break;
#if USE_XSCREENSAVER
    case EX_EVENT_SAVER_NOTIFY:
#define se ((XScreenSaverNotifyEvent *)ev)
        Eprintf("%s state=%d kind=%d\n", buf, se->state, se->kind);
#undef se
        break;
#endif
#if USE_XRANDR
    case EX_EVENT_SCREEN_CHANGE_NOTIFY:
        Eprintf("%s\n", buf);
        break;
#endif
#if USE_COMPOSITE
#define de ((XDamageNotifyEvent *)ev)
    case EX_EVENT_DAMAGE_NOTIFY:
        Eprintf("%s level=%d more=%x %d+%d %dx%d\n", buf,
                de->level, de->more,
                de->area.x, de->area.y, de->area.width, de->area.height);
        break;
#undef de
#endif
    default:
      case_common:
        Eprintf("%s\n", buf);
        break;
    }
}
#endif                          /* ENABLE_DEBUG_EVENTS */
