/*
 * Copyright 2001-2003 Red Hat Inc., Durham, North Carolina.
 *
 * All Rights Reserved.
 *
 * 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 on 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 (including the
 * next paragraph) shall be included in all copies or substantial
 * portions of the Software.
 *
 * 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
 * NON-INFRINGEMENT.  IN NO EVENT SHALL RED HAT AND/OR THEIR SUPPLIERS
 * 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.
 */

/*
 * Authors:
 *   David H. Dawes <dawes@xfree86.org>
 *   Kevin E. Martin <kem@redhat.com>
 *   Rickard E. (Rik) Faith <faith@redhat.com>
 */

/** \file
 *
 * This file implements common routines used by the backend and console
 * input devices.
 */

#ifdef HAVE_DMX_CONFIG_H
#include <dmx-config.h>
#endif

#define DMX_STATE_DEBUG 0

#include "dmxinputinit.h"
#include "dmxcommon.h"
#include "dmxconsole.h"
#include "dmxprop.h"
#include "dmxsync.h"
#include "dmxmap.h"

#include "inputstr.h"
#include "input.h"
#include <X11/keysym.h>
#include "mipointer.h"
#include "scrnintstr.h"

#include <unistd.h>             /* For usleep() */

#if DMX_STATE_DEBUG
#define DMXDBG0(f)               dmxLog(dmxDebug,f)
#else
#define DMXDBG0(f)
#endif

/** Each device has a private area that is visible only from inside the
 * driver code. */
typedef struct _myPrivate {
    DMX_COMMON_PRIVATE;
} myPrivate;

static void
dmxCommonKbdSetAR(Display * display, unsigned char *old, unsigned char *new)
{
    XKeyboardControl kc;
    XKeyboardState ks;
    unsigned long mask = KBKey | KBAutoRepeatMode;
    int i, j;
    int minKeycode, maxKeycode;

    if (!old) {
        XGetKeyboardControl(display, &ks);
        old = (unsigned char *) ks.auto_repeats;
    }

    XDisplayKeycodes(display, &minKeycode, &maxKeycode);
    for (i = 1; i < 32; i++) {
        if (!old || old[i] != new[i]) {
            for (j = 0; j < 8; j++) {
                if ((new[i] & (1 << j)) != (old[i] & (1 << j))) {
                    kc.key = i * 8 + j;
                    kc.auto_repeat_mode = ((new[i] & (1 << j))
                                           ? AutoRepeatModeOn
                                           : AutoRepeatModeOff);
                    if (kc.key >= minKeycode && kc.key <= maxKeycode)
                        XChangeKeyboardControl(display, mask, &kc);
                }
            }
        }
    }
}

static void
dmxCommonKbdSetLeds(Display * display, unsigned long new)
{
    int i;
    XKeyboardControl kc;

    for (i = 0; i < 32; i++) {
        kc.led = i + 1;
        kc.led_mode = (new & (1 << i)) ? LedModeOn : LedModeOff;
        XChangeKeyboardControl(display, KBLed | KBLedMode, &kc);
    }
}

static void
dmxCommonKbdSetCtrl(Display * display, KeybdCtrl * old, KeybdCtrl * new)
{
    XKeyboardControl kc;
    unsigned long mask = KBKeyClickPercent | KBAutoRepeatMode;

    if (!old || old->click != new->click || old->autoRepeat != new->autoRepeat) {

        kc.key_click_percent = new->click;
        kc.auto_repeat_mode = new->autoRepeat;

        XChangeKeyboardControl(display, mask, &kc);
    }

    dmxCommonKbdSetLeds(display, new->leds);
    dmxCommonKbdSetAR(display, old ? old->autoRepeats : NULL, new->autoRepeats);
}

static void
dmxCommonMouSetCtrl(Display * display, PtrCtrl * old, PtrCtrl * new)
{
    Bool do_accel, do_threshold;

    if (!old
        || old->num != new->num
        || old->den != new->den || old->threshold != new->threshold) {
        do_accel = (new->num > 0 && new->den > 0);
        do_threshold = (new->threshold > 0);
        if (do_accel || do_threshold) {
            XChangePointerControl(display, do_accel, do_threshold,
                                  new->num, new->den, new->threshold);
        }
    }
}

/** Update the keyboard control. */
void
dmxCommonKbdCtrl(DevicePtr pDev, KeybdCtrl * ctrl)
{
    GETPRIVFROMPDEV;

    if (!priv->stateSaved && priv->be)
        dmxCommonSaveState(priv);
    if (!priv->display || !priv->stateSaved)
        return;
    dmxCommonKbdSetCtrl(priv->display,
                        priv->kctrlset ? &priv->kctrl : NULL, ctrl);
    priv->kctrl = *ctrl;
    priv->kctrlset = 1;
}

/** Update the mouse control. */
void
dmxCommonMouCtrl(DevicePtr pDev, PtrCtrl * ctrl)
{
    GETPRIVFROMPDEV;

    /* Don't set the acceleration for the
     * console, because that should be
     * controlled by the X server that the
     * console is running on.  Otherwise,
     * the acceleration for the console
     * window would be unexpected for the
     * scale of the window. */
    if (priv->be) {
        dmxCommonMouSetCtrl(priv->display,
                            priv->mctrlset ? &priv->mctrl : NULL, ctrl);
        priv->mctrl = *ctrl;
        priv->mctrlset = 1;
    }
}

/** Sound they keyboard bell. */
void
dmxCommonKbdBell(DevicePtr pDev, int percent,
                 int volume, int pitch, int duration)
{
    GETPRIVFROMPDEV;
    XKeyboardControl kc;
    XKeyboardState ks;
    unsigned long mask = KBBellPercent | KBBellPitch | KBBellDuration;

    if (!priv->be)
        XGetKeyboardControl(priv->display, &ks);
    kc.bell_percent = volume;
    kc.bell_pitch = pitch;
    kc.bell_duration = duration;
    XChangeKeyboardControl(priv->display, mask, &kc);
    XBell(priv->display, percent);
    if (!priv->be) {
        kc.bell_percent = ks.bell_percent;
        kc.bell_pitch = ks.bell_pitch;
        kc.bell_duration = ks.bell_duration;
        XChangeKeyboardControl(priv->display, mask, &kc);
    }
}

/** Get the keyboard mapping. */
void
dmxCommonKbdGetMap(DevicePtr pDev, KeySymsPtr pKeySyms, CARD8 *pModMap)
{
    GETPRIVFROMPDEV;
    int min_keycode;
    int max_keycode;
    int map_width;
    KeySym *keyboard_mapping;
    XModifierKeymap *modifier_mapping;
    int i, j;

    /* Compute pKeySyms.  Cast
     * XGetKeyboardMapping because of
     * compiler warning on 64-bit machines.
     * We assume pointers to 32-bit and
     * 64-bit ints are the same. */
    XDisplayKeycodes(priv->display, &min_keycode, &max_keycode);
    keyboard_mapping = (KeySym *) XGetKeyboardMapping(priv->display,
                                                      min_keycode,
                                                      max_keycode
                                                      - min_keycode + 1,
                                                      &map_width);
    pKeySyms->minKeyCode = min_keycode;
    pKeySyms->maxKeyCode = max_keycode;
    pKeySyms->mapWidth = map_width;
    pKeySyms->map = keyboard_mapping;

    /* Compute pModMap  */
    modifier_mapping = XGetModifierMapping(priv->display);
    for (i = 0; i < MAP_LENGTH; i++)
        pModMap[i] = 0;
    for (j = 0; j < 8; j++) {
        int max_keypermod = modifier_mapping->max_keypermod;

        for (i = 0; i < max_keypermod; i++) {
            CARD8 keycode =
                modifier_mapping->modifiermap[j * max_keypermod + i];
            if (keycode)
                pModMap[keycode] |= 1 << j;
        }
    }
    XFreeModifiermap(modifier_mapping);
}

/** Fill in the XKEYBOARD parts of the \a info structure for the
 * specified \a pDev. */
void
dmxCommonKbdGetInfo(DevicePtr pDev, DMXLocalInitInfoPtr info)
{
    GETPRIVFROMPDEV;
    GETDMXINPUTFROMPRIV;
    char *pt;

    dmxCommonSaveState(priv);
    if (priv->xkb) {
#define NAME(x) \
 priv->xkb->names->x ? XGetAtomName(priv->display,priv->xkb->names->x) : NULL
        info->names.keycodes = NAME(keycodes);
        info->names.types = NAME(types);
        info->names.compat = NAME(compat);
        info->names.symbols = NAME(symbols);
        info->names.geometry = NAME(geometry);
        info->freenames = 1;
#undef NAME
        dmxLogInput(dmxInput,
                    "XKEYBOARD: keycodes = %s\n", info->names.keycodes);
        dmxLogInput(dmxInput,
                    "XKEYBOARD: symbols  = %s\n", info->names.symbols);
        dmxLogInput(dmxInput,
                    "XKEYBOARD: geometry = %s\n", info->names.geometry);
        if ((pt = strchr(info->names.keycodes, '+')))
            *pt = '\0';
    }
    dmxCommonRestoreState(priv);
}

/** Turn \a pDev on (i.e., take input from \a pDev). */
int
dmxCommonKbdOn(DevicePtr pDev)
{
    GETPRIVFROMPDEV;
    if (priv->be)
        dmxCommonSaveState(priv);
    priv->eventMask |= DMX_KEYBOARD_EVENT_MASK;
    XSelectInput(priv->display, priv->window, priv->eventMask);
    if (priv->be)
        XSetInputFocus(priv->display, priv->window, RevertToPointerRoot,
                       CurrentTime);
    return -1;
}

/** Turn \a pDev off. */
void
dmxCommonKbdOff(DevicePtr pDev)
{
    GETPRIVFROMPDEV;
    priv->eventMask &= ~DMX_KEYBOARD_EVENT_MASK;
    XSelectInput(priv->display, priv->window, priv->eventMask);
    dmxCommonRestoreState(priv);
}

/** Turn \a pDev on (i.e., take input from \a pDev). */
int
dmxCommonOthOn(DevicePtr pDev)
{
    GETPRIVFROMPDEV;
    GETDMXINPUTFROMPRIV;
    XEventClass event_list[DMX_MAX_XINPUT_EVENT_TYPES];
    int event_type[DMX_MAX_XINPUT_EVENT_TYPES];
    int count = 0;

#define ADD(type)                                                            \
    if (count < DMX_MAX_XINPUT_EVENT_TYPES) {                                \
        type(priv->xi, event_type[count], event_list[count]);                \
        if (event_type[count]) {                                             \
            dmxMapInsert(dmxLocal, event_type[count], XI_##type);            \
            ++count;                                                         \
        }                                                                    \
    } else {                                                                 \
        dmxLog(dmxWarning, "More than %d event types for %s\n",              \
               DMX_MAX_XINPUT_EVENT_TYPES, dmxInput->name);                  \
    }

    if (!(priv->xi = XOpenDevice(priv->display, dmxLocal->deviceId))) {
        dmxLog(dmxWarning, "Cannot open %s device (id=%d) on %s\n",
               dmxLocal->deviceName ? dmxLocal->deviceName : "(unknown)",
               (int) dmxLocal->deviceId, dmxInput->name);
        return -1;
    }
    ADD(DeviceKeyPress);
    ADD(DeviceKeyRelease);
    ADD(DeviceButtonPress);
    ADD(DeviceButtonRelease);
    ADD(DeviceMotionNotify);
    ADD(DeviceFocusIn);
    ADD(DeviceFocusOut);
    ADD(ProximityIn);
    ADD(ProximityOut);
    ADD(DeviceStateNotify);
    ADD(DeviceMappingNotify);
    ADD(ChangeDeviceNotify);
    XSelectExtensionEvent(priv->display, priv->window, event_list, count);

    return -1;
}

/** Turn \a pDev off. */
void
dmxCommonOthOff(DevicePtr pDev)
{
    GETPRIVFROMPDEV;

    if (priv->xi)
        XCloseDevice(priv->display, priv->xi);
    priv->xi = NULL;
}

/** Fill the \a info structure with information needed to initialize \a
 * pDev. */
void
dmxCommonOthGetInfo(DevicePtr pDev, DMXLocalInitInfoPtr info)
{
    GETPRIVFROMPDEV;
    GETDMXINPUTFROMPRIV;
    XExtensionVersion *ext;
    XDeviceInfo *devices;
    Display *display = priv->display;
    int num;
    int i, j, k;
    XextErrorHandler handler;

    if (!display && !(display = XOpenDisplay(dmxInput->name)))
        return;

    /* Print out information about the XInput Extension. */
    handler = XSetExtensionErrorHandler(dmxInputExtensionErrorHandler);
    ext = XGetExtensionVersion(display, INAME);
    XSetExtensionErrorHandler(handler);

    if (ext && ext != (XExtensionVersion *) NoSuchExtension) {
        XFree(ext);
        devices = XListInputDevices(display, &num);
        for (i = 0; i < num; i++) {
            if (devices[i].id == (XID) dmxLocal->deviceId) {
                XAnyClassPtr any;
                XKeyInfoPtr ki;
                XButtonInfoPtr bi;
                XValuatorInfoPtr vi;

                for (j = 0, any = devices[i].inputclassinfo;
                     j < devices[i].num_classes;
                     any = (XAnyClassPtr) ((char *) any + any->length), j++) {
                    switch (any->class) {
                    case KeyClass:
                        ki = (XKeyInfoPtr) any;
                        info->keyboard = 1;
                        info->keyClass = 1;
                        info->keySyms.minKeyCode = ki->min_keycode;
                        info->keySyms.maxKeyCode = ki->max_keycode;
                        info->kbdFeedbackClass = 1;
                        break;
                    case ButtonClass:
                        bi = (XButtonInfoPtr) any;
                        info->buttonClass = 1;
                        info->numButtons = bi->num_buttons;
                        info->ptrFeedbackClass = 1;
                        break;
                    case ValuatorClass:
                        /* This assume all axes are either
                         * Absolute or Relative. */
                        vi = (XValuatorInfoPtr) any;
                        info->valuatorClass = 1;
                        if (vi->mode == Absolute)
                            info->numAbsAxes = vi->num_axes;
                        else
                            info->numRelAxes = vi->num_axes;
                        for (k = 0; k < vi->num_axes; k++) {
                            info->res[k] = vi->axes[k].resolution;
                            info->minres[k] = vi->axes[k].resolution;
                            info->maxres[k] = vi->axes[k].resolution;
                            info->minval[k] = vi->axes[k].min_value;
                            info->maxval[k] = vi->axes[k].max_value;
                        }
                        break;
                    case FeedbackClass:
                        /* Only keyboard and pointer feedback
                         * are handled at this time. */
                        break;
                    case ProximityClass:
                        info->proximityClass = 1;
                        break;
                    case FocusClass:
                        info->focusClass = 1;
                        break;
                    case OtherClass:
                        break;
                    }
                }
            }
        }
        XFreeDeviceList(devices);
    }
    if (display != priv->display)
        XCloseDisplay(display);
}

/** Obtain the mouse button mapping. */
void
dmxCommonMouGetMap(DevicePtr pDev, unsigned char *map, int *nButtons)
{
    GETPRIVFROMPDEV;
    int i;

    *nButtons = XGetPointerMapping(priv->display, map, DMX_MAX_BUTTONS);
    for (i = 0; i <= *nButtons; i++)
        map[i] = i;
}

static void *
dmxCommonXSelect(DMXScreenInfo * dmxScreen, void *closure)
{
    myPrivate *priv = closure;

    XSelectInput(dmxScreen->beDisplay, dmxScreen->scrnWin, priv->eventMask);
    return NULL;
}

static void
dmxCommonFdNotify(int fd, int ready, void *data)
{
    /* This should process input on this fd, but instead all
     * of that is delayed until the block and wakeup handlers are called
     */
    ;
}

static void *
dmxCommonAddEnabledDevice(DMXScreenInfo * dmxScreen, void *closure)
{
    SetNotifyFd(XConnectionNumber(dmxScreen->beDisplay), dmxCommonFdNotify, X_NOTIFY_READ, closure);
    return NULL;
}

static void *
dmxCommonRemoveEnabledDevice(DMXScreenInfo * dmxScreen, void *closure)
{
    RemoveNotifyFd(XConnectionNumber(dmxScreen->beDisplay));
    return NULL;
}

/** Turn \a pDev on (i.e., take input from \a pDev). */
int
dmxCommonMouOn(DevicePtr pDev)
{
    GETPRIVFROMPDEV;
    GETDMXINPUTFROMPRIV;

    priv->eventMask |= DMX_POINTER_EVENT_MASK;
    if (!priv->be) {
        XSelectInput(priv->display, priv->window, priv->eventMask);
        SetNotifyFd(XConnectionNumber(priv->display), dmxCommonFdNotify,X_NOTIFY_READ, pDev);
    }
    else {
        dmxPropertyIterate(priv->be, dmxCommonXSelect, priv);
        dmxPropertyIterate(priv->be, dmxCommonAddEnabledDevice, dmxInput);
    }

    return -1;
}

/** Turn \a pDev off. */
void
dmxCommonMouOff(DevicePtr pDev)
{
    GETPRIVFROMPDEV;
    GETDMXINPUTFROMPRIV;

    priv->eventMask &= ~DMX_POINTER_EVENT_MASK;
    if (!priv->be) {
        RemoveNotifyFd(XConnectionNumber(priv->display));
        XSelectInput(priv->display, priv->window, priv->eventMask);
    }
    else {
        dmxPropertyIterate(priv->be, dmxCommonRemoveEnabledDevice, dmxInput);
        dmxPropertyIterate(priv->be, dmxCommonXSelect, priv);
    }
}

/** Given the global coordinates \a x and \a y, determine the screen
 * with the lowest number on which those coordinates lie.  If they are
 * not on any screen, return -1.  The number returned is an index into
 * \a dmxScreenInfo and is between -1 and \a dmxNumScreens - 1,
 * inclusive. */
int
dmxFindPointerScreen(int x, int y)
{
    int i;

    for (i = 0; i < dmxNumScreens; i++) {
        ScreenPtr pScreen = screenInfo.screens[i];

        if (x >= pScreen->x && x < pScreen->x + pScreen->width &&
            y >= pScreen->y && y < pScreen->y + pScreen->height)
            return i;
    }
    return -1;
}

/** Returns a pointer to the private area for the device that comes just
 * prior to \a pDevice in the current \a dmxInput device list.  This is
 * used as the private area for the current device in some situations
 * (e.g., when a keyboard and mouse form a pair that should share the
 * same private area).  If the requested private area cannot be located,
 * then NULL is returned. */
void *
dmxCommonCopyPrivate(DeviceIntPtr pDevice)
{
    GETDMXLOCALFROMPDEVICE;
    DMXInputInfo *dmxInput = &dmxInputs[dmxLocal->inputIdx];
    int i;

    for (i = 0; i < dmxInput->numDevs; i++)
        if (dmxInput->devs[i] == dmxLocal && i)
            return dmxInput->devs[i - 1]->private;
    return NULL;
}

/** This routine saves and resets some important state for the backend
 * and console device drivers:
 * - the modifier map is saved and set to 0 (so DMX controls the LEDs)
 * - the key click, bell, led, and repeat masks are saved and set to the
 * values that DMX claims to be using
 *
 * This routine and #dmxCommonRestoreState are used when the pointer
 * enters and leaves the console window, or when the backend window is
 * active or not active (for a full-screen window, this only happens at
 * server startup and server shutdown).
 */
void
dmxCommonSaveState(void *private)
{
    GETPRIVFROMPRIVATE;
    XKeyboardState ks;
    unsigned long i;
    XModifierKeymap *modmap;

    if (dmxInput->console)
        priv = dmxInput->devs[0]->private;
    if (!priv->display || priv->stateSaved)
        return;
    DMXDBG0("dmxCommonSaveState\n");
    if (dmxUseXKB && (priv->xkb = XkbAllocKeyboard())) {
        if (XkbGetIndicatorMap(priv->display, XkbAllIndicatorsMask, priv->xkb)
            || XkbGetNames(priv->display, XkbAllNamesMask, priv->xkb)) {
            dmxLogInput(dmxInput, "Could not get XKB information\n");
            XkbFreeKeyboard(priv->xkb, 0, True);
            priv->xkb = NULL;
        }
        else {
            if (priv->xkb->indicators) {
                priv->savedIndicators = *priv->xkb->indicators;
                for (i = 0; i < XkbNumIndicators; i++)
                    if (priv->xkb->indicators->phys_indicators & (1 << i)) {
                        priv->xkb->indicators->maps[i].flags
                            = XkbIM_NoAutomatic;
                    }
                XkbSetIndicatorMap(priv->display, ~0, priv->xkb);
            }
        }
    }

    XGetKeyboardControl(priv->display, &ks);
    priv->savedKctrl.click = ks.key_click_percent;
    priv->savedKctrl.bell = ks.bell_percent;
    priv->savedKctrl.bell_pitch = ks.bell_pitch;
    priv->savedKctrl.bell_duration = ks.bell_duration;
    priv->savedKctrl.leds = ks.led_mask;
    priv->savedKctrl.autoRepeat = ks.global_auto_repeat;
    for (i = 0; i < 32; i++)
        priv->savedKctrl.autoRepeats[i] = ks.auto_repeats[i];

    dmxCommonKbdSetCtrl(priv->display, &priv->savedKctrl,
                        &priv->dmxLocal->kctrl);

    priv->savedModMap = XGetModifierMapping(priv->display);

    modmap = XNewModifiermap(0);
    XSetModifierMapping(priv->display, modmap);
    if (dmxInput->scrnIdx != -1)
        dmxSync(&dmxScreens[dmxInput->scrnIdx], TRUE);
    XFreeModifiermap(modmap);

    priv->stateSaved = 1;
}

/** This routine restores all the information saved by #dmxCommonSaveState. */
void
dmxCommonRestoreState(void *private)
{
    GETPRIVFROMPRIVATE;
    int retcode = -1;
    CARD32 start;

    if (dmxInput->console)
        priv = dmxInput->devs[0]->private;
    if (!priv->stateSaved)
        return;
    priv->stateSaved = 0;

    DMXDBG0("dmxCommonRestoreState\n");
    if (priv->xkb) {
        *priv->xkb->indicators = priv->savedIndicators;
        XkbSetIndicatorMap(priv->display, ~0, priv->xkb);
        XkbFreeKeyboard(priv->xkb, 0, True);
        priv->xkb = 0;
    }

    for (start = GetTimeInMillis(); GetTimeInMillis() - start < 5000;) {
        CARD32 tmp;

        retcode = XSetModifierMapping(priv->display, priv->savedModMap);
        if (retcode == MappingSuccess)
            break;
        if (retcode == MappingBusy)
            dmxLogInput(dmxInput, "Keyboard busy, waiting\n");
        else
            dmxLogInput(dmxInput, "Keyboard error, waiting\n");

        /* Don't generate X11 protocol for a bit */
        for (tmp = GetTimeInMillis(); GetTimeInMillis() - tmp < 250;) {
            usleep(250);        /* This ends up sleeping only until
                                 * the next key press generates an
                                 * interruption.  We make the delay
                                 * relatively short in case the user
                                 * pressed they keys quickly. */
        }

    }
    if (retcode != MappingSuccess)
        dmxLog(dmxWarning, "Unable to restore keyboard modifier state!\n");

    XFreeModifiermap(priv->savedModMap);
    priv->savedModMap = NULL;

    dmxCommonKbdSetCtrl(priv->display, NULL, &priv->savedKctrl);
    priv->kctrlset = 0;         /* Invalidate copy */
}
