/*
Copyright (c) 2003-2007 Ryan C. Gordon and others.

http://icculus.org/manymouse/

This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from
the use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software in a
product, an acknowledgment in the product documentation would be
appreciated but is not required.

2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.

3. This notice may not be removed or altered from any source distribution.

    Ryan C. Gordon <icculus@icculus.org>
*/

// Support for Linux evdevs...the /dev/input/event* devices.

#include "mm.h"

#ifdef __linux__

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.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;
        int 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(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
        {
            int value;
            int minimum;
            int maximum;
            int fuzz;
            int flat;
        } 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 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 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/%s", dent->d_name);
        if (open_if_mouse(fname))
            available_mice++;
    } /* while */

    closedir(dirp);

    return 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)
{
    if (index < available_mice)
        return (mice[index].name);
    return (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 */

#else

static int linux_evdev_init(void) { return (-1); }
static void linux_evdev_quit(void) { }
static const char* linux_evdev_name(unsigned int index) { return (0); }
static int linux_evdev_poll(ManyMouseEvent* event) { return (0); }

#endif /* defined __linux__ */

ManyMouseDriver ManyMouseDriver_evdev = {
    linux_evdev_init,
    linux_evdev_quit,
    linux_evdev_name,
    linux_evdev_poll
};

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