/*
 * X11 DirectInput device support
 *
 * Copyright (C) 2009 Paul "TBBle" Hampson
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "config.h"
#include "wine/port.h"

#include "x11drv.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(dinput);

typedef void (*mousecallback)(void* iface, DWORD instance, DWORD data);

#ifdef SONAME_LIBXI

#include "wine/library.h"

#include <X11/Xlib.h>
#include <X11/extensions/XInput2.h>

/* These come from test_xi2.c in the xinput utility */
#define BitIsOn(ptr, bit) (((BYTE *) (ptr))[(bit)>>3] & (1 << ((bit) & 7)))
#define SetBit(ptr, bit)  (((BYTE *) (ptr))[(bit)>>3] |= (1 << ((bit) & 7)))

/* XInput stuff */
static void *xinput_handle;
static BOOL xi2 = FALSE;
static mousecallback inputcallback;
static void* owner = NULL;

#define MAKE_FUNCPTR(f) static typeof(f) * p##f;
MAKE_FUNCPTR(XIQueryVersion)
MAKE_FUNCPTR(XIQueryDevice)
MAKE_FUNCPTR(XISelectEvents)
MAKE_FUNCPTR(XIFreeDeviceInfo)
MAKE_FUNCPTR(XIFreeEventData)
#undef MAKE_FUNCPTR

/* Wine mouse driver object instances */
#define WINE_MOUSE_X_AXIS_INSTANCE   0
#define WINE_MOUSE_Y_AXIS_INSTANCE   1
#define WINE_MOUSE_Z_AXIS_INSTANCE   2
#define WINE_MOUSE_BUTTONS_INSTANCE  3

/***********************************************************************
 * Mouse support functions
 */
/* XGenericEvent handler */
static void xge_handler(HWND hwnd, XEvent *event)
{
    XIEvent *xiev = (XIEvent *)event;
    XIRawEvent *raw;
    int button;

    if (!inputcallback) goto cleanup;
    switch (xiev->evtype)
    {
    case XI_RawEvent:
        raw = (XIRawEvent *)xiev;
        /* This is the wrong determiner, libxi isn't providing the
           RawType wire field. */
        switch(raw->detail)
        {
        case 0:
            /* Motion */
            if (BitIsOn(raw->valuators->mask, 0))
                inputcallback( owner, WINE_MOUSE_X_AXIS_INSTANCE,
                               raw->raw_values[0]);
            if (BitIsOn(raw->valuators->mask, 1))
                inputcallback( owner, WINE_MOUSE_Y_AXIS_INSTANCE,
                               raw->raw_values[1]);
            break;
        default:
            /* Button */
            button = raw->detail - 1;
            /* X counts left middle right, Win32 counts left right middle */
            if (button == 1 || button == 2)
                button ^= 0x3;
            if (button == 3 || button == 4)
            {   /* Scroll Wheel */
                inputcallback( owner, WINE_MOUSE_Z_AXIS_INSTANCE,
                               button == 0x3?1:-1 );
            } else
                /* We're missing the down/up information at the moment */
                inputcallback( owner, WINE_MOUSE_BUTTONS_INSTANCE + button, 1 );
        }
        break;
    default:
        TRACE("EVENT type %d, expecting %d\n", xiev->evtype, XI_RawEvent);
    }

cleanup:
    wine_tsx11_lock();
    pXIFreeEventData(xiev);
    wine_tsx11_unlock();
}

/***********************************************************************
 *              X11DRV_dinput_SystemMouse   (X11DRV.@)
 *
 * Report if we are able to supply a mouse device
 */

BOOL CDECL X11DRV_dinput_SystemMouse(BOOL* keyboard, BOOL* mouse)
{
    if (!xi2)
        return FALSE;
    return TRUE;
}

/***********************************************************************
 *              X11DRV_dinput_MouseAcquire   (X11DRV.@)
 *
 * Take an interest in the mouse device
 */

VOID CDECL X11DRV_dinput_MouseAcquire(BOOL foreground, BOOL exclusive,
    mousecallback callback, void *object)
{
    struct x11drv_thread_data *data = x11drv_init_thread_data();
    XIEventMask mask;

    if (!xi2)
    {
        ERR("XInput2 unavailable, this should not be called\n");
        return;
    }

    owner = object;
    inputcallback = callback;

    /* VirtualCorePointer is always 2 */
    mask.deviceid = 2;
    mask.mask_len = 2;
    mask.mask = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 2);
    SetBit(mask.mask, XI_RawEvent);
    wine_tsx11_lock();
    pXISelectEvents(data->display, DefaultRootWindow(data->display), &mask, 1);
    wine_tsx11_unlock();
    HeapFree(GetProcessHeap(), 0, mask.mask);

    TRACE("Acquired mouse in %s %s for %p\n",
        foreground?"foreground":"background",
        exclusive?"exclusively":"non-exclusively",
        object);
}

/***********************************************************************
 *              X11DRV_dinput_MouseUnacquire   (X11DRV.@)
 *
 * Release our interest in the mouse device
 */

VOID CDECL X11DRV_dinput_MouseUnacquire()
{
    struct x11drv_thread_data *data = x11drv_init_thread_data();
    XIEventMask mask;
    if (!xi2)
    {
        ERR("XInput2 unavailable, this should not be called\n");
        return;
    }

    /* VirtualCorePointer is always 2 */
    mask.deviceid = 2;
    mask.mask_len = 2;
    mask.mask = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 2);
    wine_tsx11_lock();
    pXISelectEvents(data->display, DefaultRootWindow(data->display), &mask, 1);
    wine_tsx11_unlock();
    HeapFree(GetProcessHeap(), 0, mask.mask);

    inputcallback = NULL;
    owner = NULL;

    TRACE("Unacquired mouse\n");
}

/***********************************************************************
 *              X11DRV_dinput_Init   (X11DRV.@)
 *
 * Initialise our subsystem
 */

void X11DRV_dinput_Init(Display* display)
{
    /* Confirm we have XI2, and grab our symbols */
    int major = XI_2_Major;
    int minor = XI_2_Minor;
    xinput_handle = wine_dlopen(SONAME_LIBXI, RTLD_NOW, NULL, 0);
    if (xinput_handle)
    {
#define LOAD_FUNCPTR(f) if((p##f = wine_dlsym(xinput_handle, #f, NULL, 0)) == NULL) goto sym_not_found;
        LOAD_FUNCPTR(XIQueryVersion)
        LOAD_FUNCPTR(XIQueryDevice)
        LOAD_FUNCPTR(XISelectEvents)
        LOAD_FUNCPTR(XIFreeDeviceInfo)
        LOAD_FUNCPTR(XIFreeEventData)
#undef LOAD_FUNCPTR
        wine_tsx11_lock();
        if (pXIQueryVersion(display, &major, &minor) == Success &&
            (major * 1000 + minor) >= (XI_2_Major * 1000 + XI_2_Minor))
            xi2 = TRUE;
        wine_tsx11_unlock();
    }
    if (xi2)
    {
        X11DRV_register_event_handler(GenericEvent, xge_handler);
    }
sym_not_found:
    TRACE("XInput2 support: %s\n", xi2 ? "TRUE" : "FALSE");
    return;
}


#else /* SONAME_LIBXI */

/***********************************************************************
 *              X11DRV_dinput_SystemMouse   (X11DRV.@)
 *
 * Report if we are able to supply a mouse device
 */

BOOL CDECL X11DRV_dinput_SystemMouse()
{
    return FALSE;
}

/***********************************************************************
 *              X11DRV_dinput_MouseAcquire   (X11DRV.@)
 *
 * Take an interest in the mouse device
 */

VOID CDECL X11DRV_dinput_MouseAcquire(BOOL foreground, BOOL exclusive,
    mousecallback callback)
{
    ERR("Should not be called when compiled without XInput2\n");
}

/***********************************************************************
 *              X11DRV_dinput_MouseUnacquire   (X11DRV.@)
 *
 * Release our interest in the mouse device
 */

VOID CDECL X11DRV_dinput_MouseUnacquire()
{
    ERR("Should not be called when compiled without XInput2\n");
}

/***********************************************************************
 *              X11DRV_dinput_Init   (X11DRV.@)
 *
 * Initialise our subsystem
 */

void CDECL X11DRV_dinput_Init()
{
    TRACE("XInput2 not available at compile time\n");
}

#endif /* SONAME_LIBXI */

