/*
 * 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.
 *  Altered to:
 *   - silence compiler warnings, by Roman Standzikowski.
 *   - silence compiler warnings, by kcgen.
 *   - fix a time-of-check time-of-use issue, by kcgen.
 */

#include "manymouse.h"

#ifdef __linux__

#include <assert.h>
#include <limits.h>
#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;
    char name[64];
} MouseStruct;

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


static int poll_mouse(MouseStruct *mouse, ManyMouseEvent *outevent)
{
    int unhandled = 1;
    while (unhandled)  /* read until failure or valid event. */
    {
        struct input_event event;
        const ssize_t br = 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(__attribute__((unused)) 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 */

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

    if (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof (abscaps)), abscaps) != -1)
    {
        if ( (test_bit(abscaps, ABS_X)) && (test_bit(abscaps, ABS_Y)) )
        {
            /* might be a touchpad... */
            if (test_bit(keycaps, BTN_TOUCH))
            {
                is_mouse = 1;  /* touchpad, touchscreen, or tablet. */
                has_absolutes = 1;
            } /* if */
        } /* if */
    } /* if */

    if (!is_mouse)
        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 */

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

    mouse->fd = fd;

    return 1;  /* we're golden. */
} /* init_mouse */


/* Return 1 if this is really a mouse, 0 otherwise. */
static int open_if_mouse(const char *fname)
{
    assert(fname);

    /* Can we open the file device? */
    const int fd = open(fname, O_RDONLY | O_NONBLOCK);
    if (fd == -1)
        return 0;

    struct stat statbuf;
    memset(&statbuf, 0, sizeof(statbuf));

    /* Can we stat info about the device? */
    if (fstat(fd, &statbuf) == -1) {
        close(fd);
        return 0;
    }

    /* Is it a character device? */
    if (S_ISCHR(statbuf.st_mode) == 0) {
        close(fd);
        return 0;
    }

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

    /* Were we able to initialize the evdev mouse handle? */
    const char * unused_filename = NULL;
    if (init_mouse(unused_filename, fd) != 1) {
        close(fd);
        return 0;
    }

    assert(fd >= 0);
    return 1;
} /* open_if_mouse */


static int linux_evdev_init(void)
{
    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)
    {
        char fname[128];
        snprintf(fname, sizeof (fname), "/dev/input/%.100s", dent->d_name);
        if (open_if_mouse(fname))
            available_mice++;
    } /* while */

    closedir(dirp);

    /* Ensure we're not going to roll over when casting to signed */
    assert(available_mice <= INT_MAX);

    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 ... */

