/*
 * Support for Linux evdevs...the /dev/input/event* devices.
 *
 * Please see the file LICENSE.txt in the source's root directory.
 *
 *  This file written by Ryan C. Gordon.
 */

#include "manymouse.h"

#ifdef __linux__

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <linux/input.h>  /* evdev interface...  */

#define test_bit(array, bit)    (array[bit/8] & (1<<(bit%8)))

/* linux allows 32 evdev nodes currently. */
#define MAX_MICE 32
typedef struct
{
    int fd;
    int min_x;
    int min_y;
    int max_x;
    int max_y;
    int eventNum;
    char name[64];
} MouseStruct;

static MouseStruct mice[MAX_MICE];
static unsigned int available_mice = 0;
static unsigned char absOnly = 0;


static int poll_mouse(MouseStruct *mouse, ManyMouseEvent *outevent)
{
    int unhandled = 1;
    while (unhandled)  /* read until failure or valid event. */
    {
        struct input_event event;
        int br = (int)read(mouse->fd, &event, sizeof (event));
        if (br == -1)
        {
            if (errno == EAGAIN)
                return 0;  /* just no new data at the moment. */

            /* mouse was unplugged? */
            close(mouse->fd);  /* stop reading from this mouse. */
            mouse->fd = -1;
            outevent->type = MANYMOUSE_EVENT_DISCONNECT;
            return 1;
        } /* if */

        if (br != sizeof (event))
            return 0;  /* oh well. */

        unhandled = 0;  /* will reset if necessary. */
        outevent->value = event.value;
        if (event.type == EV_REL)
        {
            outevent->type = MANYMOUSE_EVENT_RELMOTION;
            if ((event.code == REL_X) || (event.code == REL_DIAL))
                outevent->item = 0;
            else if (event.code == REL_Y)
                outevent->item = 1;

            else if (event.code == REL_WHEEL)
            {
                outevent->type = MANYMOUSE_EVENT_SCROLL;
                outevent->item = 0;
            } /* else if */

            else if (event.code == REL_HWHEEL)
            {
                outevent->type = MANYMOUSE_EVENT_SCROLL;
                outevent->item = 1;
            } /* else if */

            else
            {
                unhandled = 1;
            } /* else */
        } /* if */

        else if (event.type == EV_ABS)
        {
            outevent->type = MANYMOUSE_EVENT_ABSMOTION;
            if (event.code == ABS_X)
            {
                outevent->item = 0;
                outevent->minval = mouse->min_x;
                outevent->maxval = mouse->max_x;
            } /* if */
            else if (event.code == ABS_Y)
            {
                outevent->item = 1;
                outevent->minval = mouse->min_y;
                outevent->maxval = mouse->max_y;
            } /* if */
            else
            {
                unhandled = 1;
            } /* else */
        } /* else if */

        else if (event.type == EV_KEY)
        {
            outevent->type = MANYMOUSE_EVENT_BUTTON;
            if ((event.code >= BTN_LEFT) && (event.code <= BTN_BACK))
                outevent->item = event.code - BTN_MOUSE;

            /* just in case some device uses this block of events instead... */
            else if ((event.code >= BTN_MISC) && (event.code <= BTN_LEFT))
                outevent->item = (event.code - BTN_MISC);

            else if (event.code == BTN_TOUCH) /* tablet... */
                outevent->item = 0;
            else if (event.code == BTN_STYLUS) /* tablet... */
                outevent->item = 1;
            else if (event.code == BTN_STYLUS2) /* tablet... */
                outevent->item = 2;

            else
            {
                /*printf("unhandled mouse button: 0x%X\n", event.code);*/
                unhandled = 1;
            } /* else */
        } /* else if */
        else
        {
            unhandled = 1;
        } /* else */
    } /* while */

    return 1;  /* got a valid event */
} /* poll_mouse */


static int init_mouse(const char *fname, int fd)
{
    MouseStruct *mouse = &mice[available_mice];
    int has_absolutes = 0;
    int is_mouse = 0;
    unsigned char relcaps[(REL_MAX / 8) + 1];
    unsigned char abscaps[(ABS_MAX / 8) + 1];
    unsigned char keycaps[(KEY_MAX / 8) + 1];

    memset(relcaps, '\0', sizeof (relcaps));
    memset(abscaps, '\0', sizeof (abscaps));
    memset(keycaps, '\0', sizeof (keycaps));

    if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof (keycaps)), keycaps) == -1)
        return 0;  /* gotta have some buttons!  :)  */

    if (ioctl(fd, EVIOCGBIT(EV_REL, sizeof (relcaps)), relcaps) != -1)
    {
	if ( (test_bit(relcaps, REL_X)) && (test_bit(relcaps, REL_Y)) )
        {
            if (test_bit(keycaps, BTN_MOUSE))
                is_mouse = 1;
        }

#if ALLOW_DIALS_TO_BE_MICE
	if (test_bit(relcaps, REL_DIAL))
            is_mouse = 1;  // griffin powermate?
#endif
    }

    if (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof (abscaps)), abscaps) != -1)
    {
        // rule out extra axis that might be used on some gamepads to filter those out.
        if ( test_bit(abscaps, ABS_X) && test_bit(abscaps, ABS_Y)
            && !(test_bit(abscaps, ABS_RX) || test_bit(abscaps, ABS_RY) ||
                 test_bit(abscaps, ABS_HAT0X) || test_bit(abscaps, ABS_HAT0Y))
           )
        {
            is_mouse = 1;
            has_absolutes = 1;
        }
    }

    if (!is_mouse || (absOnly && is_mouse && !has_absolutes) )
        return 0;

    mouse->min_x = mouse->min_y = mouse->max_x = mouse->max_y = 0;
    if (has_absolutes)
    {
        struct input_absinfo absinfo;
        if (ioctl(fd, EVIOCGABS(ABS_X), &absinfo) == -1)
            return 0;
        mouse->min_x = absinfo.minimum;
        mouse->max_x = absinfo.maximum;

        if (ioctl(fd, EVIOCGABS(ABS_Y), &absinfo) == -1)
            return 0;
        mouse->min_y = absinfo.minimum;
        mouse->max_y = absinfo.maximum;
    }

    if (ioctl(fd, EVIOCGNAME(sizeof (mouse->name)), mouse->name) == -1)
        snprintf(mouse->name, sizeof (mouse->name), "Unknown device");

    mouse->fd = fd;
    /* skipping past "/dev/input/event" so we isolate the number */
    mouse->eventNum = atoi(fname+16);

    return 1;
} /* init_mouse */


/* Return a file descriptor if this is really a mouse, -1 otherwise. */
static int open_if_mouse(const char *fname)
{
    struct stat statbuf;
    int fd;
    int devmajor, devminor;

    if (stat(fname, &statbuf) == -1)
        return 0;

    if (S_ISCHR(statbuf.st_mode) == 0)
        return 0;  /* not a character device... */

    /* evdev node ids are major 13, minor 64-96. Is this safe to check? */
    devmajor = (statbuf.st_rdev & 0xFF00) >> 8;
    devminor = (statbuf.st_rdev & 0x00FF);
    if ( (devmajor != 13) || (devminor < 64) || (devminor > 96) )
        return 0;  /* not an evdev. */

    if ((fd = open(fname, O_RDONLY | O_NONBLOCK)) == -1)
        return 0;

    if (init_mouse(fname, fd))
        return 1;

    close(fd);
    return 0;
} /* open_if_mouse */


static int sort_devices(const void* a, const void* b)
{
    MouseStruct *arg1 = (MouseStruct*)a;
    MouseStruct *arg2 = (MouseStruct*)b;

    // no two devices can share event number
    if (arg1->eventNum < arg2->eventNum) return -1;

    return 1;
}


static int linux_evdev_init(const unsigned char filter)
{
    absOnly = filter;
    DIR *dirp;
    struct dirent *dent;
    int i;

    for (i = 0; i < MAX_MICE; i++)
        mice[i].fd = -1;

    dirp = opendir("/dev/input");
    if (!dirp)
        return -1;

    while ((dent = readdir(dirp)) != NULL)
    {
        if (strstr(dent->d_name, "event") == NULL) continue;
        char fname[384];
        snprintf(fname, sizeof (fname), "/dev/input/%s", dent->d_name);
        if (open_if_mouse(fname))
            available_mice++;

    } /* while */

    closedir(dirp);

    /* Sort devices based on eventfile number (from first to most recently registered) */
    if (available_mice)
        qsort(mice, available_mice, sizeof(MouseStruct), sort_devices);

    return (int)available_mice;
} /* linux_evdev_init */


static void linux_evdev_quit(void)
{
    while (available_mice)
    {
        int fd = mice[available_mice--].fd;
        if (fd != -1)
            close(fd);
    } /* while */
} /* linux_evdev_quit */


static const char *linux_evdev_name(unsigned int index)
{
    return (index < available_mice) ? mice[index].name : NULL;
} /* linux_evdev_name */


static int linux_evdev_poll(ManyMouseEvent *event)
{
    /*
     * (i) is static so we iterate through all mice round-robin. This
     *  prevents a chatty mouse from dominating the queue.
     */
    static unsigned int i = 0;

    if (i >= available_mice)
        i = 0;  /* handle reset condition. */

    if (event != NULL)
    {
        while (i < available_mice)
        {
            MouseStruct *mouse = &mice[i];
            if (mouse->fd != -1)
            {
                if (poll_mouse(mouse, event))
                {
                    event->device = i;
                    return 1;
                } /* if */
            } /* if */
            i++;
        } /* while */
    } /* if */

    return 0;  /* no new events */
} /* linux_evdev_poll */

static const ManyMouseDriver ManyMouseDriver_interface =
{
    "Linux /dev/input/event* interface",
    linux_evdev_init,
    linux_evdev_quit,
    linux_evdev_name,
    linux_evdev_poll
};

const ManyMouseDriver *ManyMouseDriver_evdev = &ManyMouseDriver_interface;

#else
const ManyMouseDriver *ManyMouseDriver_evdev = 0;
#endif  /* ifdef Linux blocker */

/* end of linux_evdev.c ... */

