/*
 * olpc-kbdshim.c -- special touchpad and keyboard support for XO:
 *  - mouse-based scrolling use the XO grab keys
 *  - touchpad and dpad rotation (to match screen rotation)
 *  - user activity monitoring
 *
 * Copyright (C) 2009,2010,2011, Paul G. Fox, inspired in places
 *  by code from mouseemu, by Colin Leroy and others.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see: http://www.gnu.org/licenses
 */

#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>

#include <linux/input.h>

#include "common.h"

/* are we in user-idle state? */
static int idleness;
static int wasidle;

/* input event devices */
static int kbd_fd = -1;
static int tpad_fd = -1;

/* bit array ops */
#define bits2bytes(x) ((((x)-1)/8)+1)
#define test_bit(bit, array) ( array[(bit)/8] & (1 << (bit)%8) )



int
setup_input()
{
    int i;
    int dfd;
    char devname[128];
    unsigned char bit[bits2bytes(EV_MAX)];
    struct input_id id;

    for (i = 0; i < EVENTDEVICES; i++) {

        snprintf(devname, sizeof(devname), "/dev/input/event%d", i);
        if ((dfd = open(devname, O_RDONLY)) < 0)
            continue;

        if (ioctl(dfd, EVIOCGID, &id) < 0) {
            report("failed ioctl EVIOCGID on %d", i);
            close(dfd);
            continue;
        }

        if (ioctl(dfd, EVIOCGBIT(0, EV_MAX), bit) < 0) {
            report("failed ioctl EVIOCGBIT on %d", i);
            close(dfd);
            continue;
        }

        if (kbd_fd < 0 &&
                test_bit(EV_KEY, bit) &&
                test_bit(EV_REP, bit) &&
                is_local_kbd(&id)) {
            kbd_fd = dfd;
            if (ioctl(kbd_fd, EVIOCGRAB, 1) < 0)
                die("ioctl EVIOCGRAB on keyboard");
            report("found keyboard %s (%02x:%02x:%02x)", devname,
                id.bustype, id.vendor, id.product);
            continue;
        }

        if (tpad_fd < 0 &&
                test_bit(EV_REL, bit) &&
                is_local_tpad(&id)) {
            tpad_fd = dfd;
            if (ioctl(tpad_fd, EVIOCGRAB, 1) < 0)
                die("ioctl EVIOCGRAB on touchpad");
            report("found touchpad %s (%02x:%02x:%02x)", devname,
                id.bustype, id.vendor, id.product);
            continue;
        }

        close(dfd);
    }

    if (kbd_fd == -1)
        report("didn't find keyboard");
    if (tpad_fd == -1)
        report("didn't find touchpad");

    return (kbd_fd >= 0 && tpad_fd >= 0);
}

static int
keyboard_event(void)
{
    struct input_event ev[1];

    if (read(kbd_fd, ev, sizeof(ev)) != sizeof(ev))
        die("bad read from keyboard");

    return keyboard_event_worker(ev, 1);
}

static void touchpad_event(void)
{
    struct input_event ev[1];

    if (read(tpad_fd, ev, sizeof(ev)) != sizeof(ev))
        die("bad read from touchpad");

    touchpad_event_worker(ev, 1);
}

static void
indicate_idleness(void)
{
    static char useridle[] = "useridleX";

    useridle[8] = idleness + '1';
    if (idleness < MAXTIMERS) idleness++;

    send_event(useridle);
    wasidle = 1;
}

void
reinit_activity_monitor(void)
{
    wasidle = 1;
    idleness = 0;
}

static void
indicate_activity(void)
{
    if (wasidle)
        send_event("useractive");

    reinit_activity_monitor();
    wasidle = 0;
}

static void
get_command(void)
{
    int n;
    char command[128];
    n = read(fifo_fd, command, sizeof(command)-1);

    if (n < 1) {
        if (n < 0)
            die("read from rotation fifo");
        deinit_fifo();
    }
    command[n] = '\0';

    command_worker(command);
}

void
monitor_input(void)
{
    fd_set inputs, errors;
    int maxfd, r;
    struct timeval tv;
    struct timeval *tvp;
    int pressed;

    indicate_activity();

    maxfd = MAX(fifo_fd, MAX(kbd_fd, tpad_fd));

    while (1) {

        FD_ZERO(&inputs);
        FD_SET(kbd_fd, &inputs);
        FD_SET(tpad_fd, &inputs);
        if (fifo_fd >= 0) FD_SET(fifo_fd, &inputs);

        FD_ZERO(&errors);
        FD_SET(kbd_fd, &errors);
        FD_SET(tpad_fd, &errors);
        if (fifo_fd >= 0) FD_SET(fifo_fd, &errors);

        if (idledeltas[idleness]) {
            tv.tv_sec = idledeltas[idleness];
            tv.tv_usec = 0;
            tvp = &tv;
        } else {
            tvp = 0;
        }

        r = select(maxfd+1, &inputs, NULL, &errors, tvp);
        if (r < 0)
            die("select failed");

        if (r == 0) {
            indicate_idleness();
            continue;
        }

        pressed = 0;

        if (fifo_fd >= 0 && FD_ISSET(fifo_fd, &errors))
            die("select reports error on rotation event fifo");

        if (FD_ISSET(kbd_fd, &errors))
            die("select reports error on keyboard");

        if (FD_ISSET(tpad_fd, &errors))
            die("select reports error on touchpad");

        if (fifo_fd >= 0 && FD_ISSET(fifo_fd, &inputs))
            get_command();

        if (FD_ISSET(kbd_fd, &inputs))
            pressed |= keyboard_event();

        if (FD_ISSET(tpad_fd, &inputs)) {
            touchpad_event();
            pressed = 1;
        }

        if (pressed)
            indicate_activity();

    }

}

