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

/* This file contains common code for all versions of olpc-kbdshim,
 * including a common main().  The various implementions must
 * provide:
 * 
 *  int setup_input(void)
 *      called for early initialization.
 *  void reinit_activity_monitor(void)
 *      called when the idle timers have been changed
 *  void monitor_input(void)
 *      this is the last thing called from main(), and will
 *      probably never return.
 */

#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 <linux/uinput.h>

#include "common.h"

extern char *optarg;
extern int optind, opterr, optopt;

/* log to syslog (instead of stderr) */
static int logtosyslog;

/* higher values for more debug */
static int debug;

/* suppress any actual tranmission or injection of data */
static int noxmit;

/* suppress keyboard keys and touchpad activity when in ebook mode */
static int watch_ebook = 1;

/* which keys do grabbing? */
#define NGRABKEYS 4
static struct keypress {
    int code;
    int pass;
    int pressed;
} grabkey[NGRABKEYS];
static int ngrab;

/* command fifo */
static char *command_fifo = "/var/run/olpc-kbdshim_command";
int fifo_fd = -1;

/* sysactive path -- where we send user activity/idle events */
static char *sysactive_path = "/var/run/powerevents";

/* list of idle deltas.  last timer should stay zero */
int idledeltas[MAXTIMERS + 1] = { 10 * 60, 5 * 60, 5 * 60, 0 };

/* the path to the disable control for the local touchpad. 
 * determined automatically, but can be overridden with -t on the
 * command line */
char local_tpad_enabled[1024];

/* external commands bound to rotate and brightness keys:
 *
 * the rotate command should inform us via our command fifo when
 * a rotate has happened, so that we can adjust the touchpad and
 * d-pad rotation.
 *
 * the brightness command should accept a single argument of
 * "up", "down", "min", or "max" (the last two when the alt
 * modifier is in effect).
 *
 * these bindings are done here for convenience, and could be
 * done anywhere else in the system just as well.  binding
 * them here effectively "steals" them from sugar, which has
 * internal implementations of rotation and brightness control
 * which are no longer correct when this package and olpc-powerd
 * are used.
 *
 * the brightness and volume keys appear on F9/F10 and F11/F12.
 * to accomodate gnome apps, which want full access to the F-keys,
 * kbdshim will (as of july 2010, see d.l.o. #10213) bind to the
 * Fn-modified version of those keys as well, which we've arranged
 * to be KEY_BRIGHTNESSDOWN/UP and KEY_VOLUMEDOWN/UP.
 *
 * either of the F9-F12 or KEY_BRIGHTNESS/KEY_VOLUME set of
 * bindings can be disabled independently, using the following
 * kbdshim commands:
 *  f9 to disable capture of F9-F12,
 *  F9 to enable capture of F9-F12,
 *  fbv to disable capture of KEY_BRIGHTNESSDOWN/UP and KEYVOLUMEDOWN/UP
 *  FBV to enable capture of KEY_BRIGHTNESSDOWN/UP and KEYVOLUMEDOWN/UP
 */
static char *brightness_cmd; /*  probably "olpc-brightness" */
static char *volume_cmd;     /*  probably "olpc-volume" */
static char *rotate_cmd;     /*  probably "olpc-rotate" */

static int reflect_x, reflect_y;  /* inverted mouse (e.g., for ebook mode) */
static enum rotation {
    ROT_NORMAL,
    ROT_LEFT,
    ROT_INVERT,
    ROT_RIGHT
} rotation;

/* are we in ebook mode */
static int ebook_mode;

/* are we scrolling, or not */
static int scrolling;

/* last recorded absolute mouse coordinates.
 * -1 indicates no recorded coords. */
static int last_abs_x = -1;
static int last_abs_y = -1;

#define SCROLL_QUANTUM 20
#define SCROLL_FILTER_PERCENT 33
static int cumul_x, cumul_y;

/* "distance traveled" before we emulate a scroll button event */
static int quantum = SCROLL_QUANTUM;
static int ratio = SCROLL_FILTER_PERCENT;

/* if true, finger moves scrollbars, instead of window contents */
static int reverse = 1;

/* is the D-pad in pointer mode? */
static int dpad_pointer;

/* should we capture F9 through F12 for brightness and volume? */
static int use_F9_F12 = 0;
/* likewise for F21 through F24 for brightness and volume */
static int use_BRT_VOL = 1;

/* product, vendor id for keyboard and touchpad */
struct input_id local_kbd_id = {
    bustype: BUS_I8042,
    product: 0xffff,
    vendor: 0xffff
};
struct input_id local_tpad_id = {
    bustype: BUS_I8042,
    product: 0xffff,
    vendor: 0xffff
};

/* Axis information for touchscreen */
int tscreen_max[ABS_CNT] = { 0 };
int tscreen_min[ABS_CNT] = { 0 };
int tscreen_fuzz[ABS_CNT] = { 0 };
int tscreen_flat[ABS_CNT] = { 0 };

/* swipe gesture thresholds, etc. */
#define QUEUE_SIZE 512
int swipe_margin;
int swipe_trigger_num;
int swipe_trigger_dist;
int swipe_scroll_quantum;


/* output events device */
static int uinp_fd = -1;
static int ts_uinp_fd = -1;

static char *me;

__attribute__((noreturn))
static void
usage(void)
{
    fprintf(stderr,
        "usage: %s [options]\n"
        " Grab-scroll configuration:\n"
        "   '-g N','-G N' Specify which key(s) are used for grabbing.\n"
        "        A maximum of %d keys can be specified.  (Use \"showkey\"\n"
        "        at the console to find the keycode.)  With '-g', the\n"
        "        key will be processed normally (in addition to instituting\n"
        "        scrolling).  '-G' will cause scrolling but suppress the\n"
        "        key's normal action.  Default is '-g 125 -g 126'.  (The\n"
        "        \"meta\" keys, labeled as \"grab\" keys on the XO laptop.\n"
        "        Use '-g 0' to suppress grab-scoll support.\n"
        "   '-v' to reverse the relative motion of mouse and window\n"
        "        By default the mouse/finger slides the scrollbars, but\n"
        "        with -v it effectively slides the window instead.\n"
        "   '-q N' to set how far the mouse/finger must move before a\n"
        "        scrolling event is generated (default is %d).\n"
        "   '-n N' If the x or y motion is less than N percent of the\n"
        "        other (default N is %d) then the smaller of the two is\n"
        "        dropped. This reduces sideways jitter when scrolling\n"
        "        vertically,and vice versa.\n"
        "\n"
        " Ebook monitoring:\n"
        "   '-e 1|0' to monitor ebook mode, and suppress keyboard and touchpad\n"
        "        inputs when in ebook mode.  (defaults to on).\n"
        "\n"
        " Touchpad/D-pad rotation:\n"
        "   '-R <fifoname>'  If set, this gives the name of a fifo which\n"
        "        will be created and monitored for the purpose of rotating\n"
        "        and reflecting the touchpad and the D-pad.  This can be\n"
        "        used to make their function match the screen's rotation.\n"
        "        This fifo will also receive setup commands related to\n"
        "        user activity monitoring (see below).\n"
        "\n"
        " Keyboard/touchpad identification:\n"
        "   '-K BB:VV:PP' Specifies the bustype, vendor, and product id\n"
        "        for the local keyboard.  BB, VV, and PP are hex\n"
        "        numbers.  ffff can be used as a wildcard for any of them.\n"
        "        Default is '11:ffff:ffff' for the i8042 bus, which will find\n"
        "        the local keyboard.  '03:ffff:ffff' finds any USB keyboard.\n"
        "   '-T BB:VV:PP' Same as -K, but for the pointer device.\n"
        "        Only the local keyboard and touchpad will be affected by\n"
        "        rotation commands (i.e., rotate with the screen)\n"
        "   '-t path' Sets path to the sysfs node which can be used to\n"
        "        disable the local touchpad.  This path is usually found\n"
        "        automatically, but can be overridden if needed\n"
        "\n"
        " User activity monitor:\n"
        "   '-A PATH' Specifies path where user idle/activity events will\n"
        "        be written (default is /var/run/powerevents).\n"
        "\n"
        " Key bindings:\n"
        "   '-r rotate-cmd'  If set, the command to be run when the XO\n"
        "        'rotate' button is detected.  (Will be passed no arguments.)\n"
        "   '-b brightness-cmd'  If set, the command to be run when the XO\n"
        "        brightness keys are used.  (Should accept 'min', 'max', 'up'\n"
        "        or 'down' as the sole argument.)\n"
        "   '-V volume-cmd'  If set, the command to be run when the XO\n"
        "        volume keys are used.  (Should accept 'min', 'max', 'up'\n"
        "        or 'down' as the sole argument.)\n"
        "\n"
        " Daemon options:\n"
        "   '-f' to keep program in foreground.\n"
        "   '-l' use syslog, rather than stderr, for messages.\n"
        "   '-s' to run with elevated (sched_fifo) scheduling priority.\n"
        "   '-d' for debugging (repeat for more verbosity).\n"
        "   '-X' don't actually pass on received keystrokes (for debug).\n"
        "(olpc-kbdshim version %d)\n"
        , me, NGRABKEYS, SCROLL_QUANTUM, SCROLL_FILTER_PERCENT, VERSION);
    exit(1);
}

void
report(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    if (logtosyslog) {
        vsyslog(LOG_NOTICE, fmt, ap);
    }

    fprintf(stderr, "%s: ", me);
    vfprintf(stderr, fmt, ap);
    fputc('\n', stderr);
}

void
dbg(int level, const char *fmt, ...)
{
    va_list ap;

    if (debug < level) return;

    va_start(ap, fmt);
    if (logtosyslog)
        vsyslog(LOG_NOTICE, fmt, ap);

    fputc(' ', stderr);
    vfprintf(stderr, fmt, ap);
    fputc('\n', stderr);
}

__attribute__((noreturn))
void
die(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    if (logtosyslog) {
        vsyslog(LOG_ERR, fmt, ap);
        syslog(LOG_ERR, "exiting -- %m");
    }

    fprintf(stderr, "%s: ", me);
    vfprintf(stderr, fmt, ap);
    fprintf(stderr, " - %s", strerror(errno));
    fputc('\n', stderr);

    exit(1);
}

static void
write_uinput_event(struct input_event *event)
{
    if (!noxmit && write(uinp_fd, event, sizeof(*event)) < 0) {
        report("warning: write event failed, t/c/v 0x%x/0x%x/0x%x",
            event->type, event->code, event->value);
    }
}

/* Manage the uinput device */
void
inject_uinput_event(unsigned int type, unsigned short code, int value)
{
    struct input_event event;

    gettimeofday(&event.time, NULL);
    event.type = type;
    event.code = code;
    event.value = value;
    write_uinput_event(&event);
}

void
deinit_uinput_device(int fd)
{
    if (fd < 0) return;

    /* Destroy the input device */
    if (ioctl(fd, UI_DEV_DESTROY) < 0)
        die("destroy of uinput dev failed");

    /* Close the UINPUT device */
    close(fd);
    fd = -1;
}

void
deinit_both_uinput_devices(void)
{
    deinit_uinput_device(uinp_fd);
    deinit_uinput_device(ts_uinp_fd);
}

int
setup_uinput(int tscreen)
{
    struct uinput_user_dev uinp;
    int e, i, avg;
    int fd;

    fd = open("/dev/input/uinput", O_WRONLY | O_NDELAY);
    if (fd < 0) {
        fd = open("/dev/uinput", O_WRONLY | O_NDELAY);
        if (fd < 0) {
            report(
                "Unable to open either /dev/input/uinput or /dev/uinput");
            return -1;
        }
    }

    memset(&uinp, 0, sizeof(uinp));

    uinp.id.bustype = BUS_VIRTUAL;
    uinp.id.vendor  = 'p';
    uinp.id.product = 'g';
    uinp.id.version = 'f';

    if (tscreen) {
        strncpy(uinp.name, "olpc-kbdshim virtual touchscreen", UINPUT_MAX_NAME_SIZE);
        /* Set up absolute info */
        memcpy(uinp.absmax,  tscreen_max,  sizeof(tscreen_max));
        memcpy(uinp.absmin,  tscreen_min,  sizeof(tscreen_min));
        memcpy(uinp.absfuzz, tscreen_fuzz, sizeof(tscreen_fuzz));
        memcpy(uinp.absflat, tscreen_flat, sizeof(tscreen_flat));

        /* FIXME: assumes all absmin are 0 */
        avg = (tscreen_max[ABS_X] + tscreen_max[ABS_Y]) / 2;
        swipe_margin = avg / 16;
        swipe_trigger_num = avg / 32;
        swipe_trigger_dist = avg / 16;
        swipe_scroll_quantum = avg / 20;

        e = 0;
        if (!++e || ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0 || 
            !++e || ioctl(fd, UI_SET_EVBIT, EV_ABS) < 0 || // touchscreen
            !++e || ioctl(fd, UI_SET_ABSBIT, ABS_X) < 0 ||
            !++e || ioctl(fd, UI_SET_ABSBIT, ABS_Y) < 0 ||
            !++e || ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_X) < 0 ||
            !++e || ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_Y) < 0 ||
            !++e || ioctl(fd, UI_SET_ABSBIT, ABS_MT_PRESSURE) < 0 ||
            !++e || ioctl(fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID) < 0 ||
            !++e || ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH) < 0 ||
            !++e || write(fd, &uinp, sizeof(uinp)) < 0 ||  // device
            !++e || ioctl(fd, UI_DEV_CREATE) < 0) {
                report("uinput setup failed, step %d", e);
                return -1;
        }

        ts_uinp_fd = fd;

        dbg(1, "touchscreen uinput device established");

    } else {
        strncpy(uinp.name, "olpc-kbdshim virtual input", UINPUT_MAX_NAME_SIZE);

        for (i = 0; i <= KEY_UNKNOWN; i++) {
            if (ioctl(fd, UI_SET_KEYBIT, i) != 0) {
                report("uinput setup failed, code %d", i);
                return -1;
            }
        }

        e = 0;
        if (!++e || ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0 || // keys
            !++e || ioctl(fd, UI_SET_EVBIT, EV_REP) < 0 ||
            !++e || ioctl(fd, UI_SET_EVBIT, EV_REL) < 0 || // mouse
            !++e || ioctl(fd, UI_SET_RELBIT, REL_X) < 0 ||
            !++e || ioctl(fd, UI_SET_RELBIT, REL_Y) < 0 ||
            !++e || ioctl(fd, UI_SET_RELBIT, REL_WHEEL) < 0 ||
            !++e || ioctl(fd, UI_SET_RELBIT, REL_HWHEEL) < 0 ||
            !++e || ioctl(fd, UI_SET_KEYBIT, BTN_LEFT) < 0 ||
            !++e || ioctl(fd, UI_SET_KEYBIT, BTN_RIGHT) < 0 ||
            !++e || ioctl(fd, UI_SET_KEYBIT, BTN_MIDDLE) < 0 ||
            !++e || write(fd, &uinp, sizeof(uinp)) < 0 ||  // device
            !++e || ioctl(fd, UI_DEV_CREATE) < 0) {
                report("uinput setup failed, step %d", e);
                return -1;
        }

        uinp_fd = fd;

        /* disable timer-based autorepeat, see http://dev.laptop.org/ticket/9690 */
        inject_uinput_event(EV_REP, REP_DELAY, 0);
        inject_uinput_event(EV_REP, REP_PERIOD, 0);

        dbg(1, "uinput device established");
    }


    return 0;
}

static int
match_input_id(struct input_id *id, struct input_id *tmpl)
{
    return id->bustype == tmpl->bustype &&
        (id->vendor == tmpl->vendor || tmpl->vendor == 0xffff) &&
        (id->product == tmpl->product || tmpl->product == 0xffff);
}

int
is_local_kbd(struct input_id *id)
{
    return match_input_id(id, &local_kbd_id);
}

int
is_local_tpad(struct input_id *id)
{
    return match_input_id(id, &local_tpad_id);
}

void
send_a_scroll(int x, int y)
{
    if (reverse) {
        x = -x;
        y = -y;
    }

    if (x)  {
        dbg(1, "scroll %s", x > 0 ? "left" : "right");
        inject_uinput_event(EV_REL, REL_HWHEEL, x < 0 ? 1 : -1);
    }
    if (y) {
        dbg(1, "scroll %s", y > 0 ? "up" : "down");
        inject_uinput_event(EV_REL, REL_WHEEL, y < 0 ? -1 : 1);
    }
    inject_uinput_event(EV_SYN, SYN_REPORT, 0);
}

static unsigned char dpad[] = { KEY_KP8, KEY_KP6, KEY_KP2, KEY_KP4,
                                KEY_KP8, KEY_KP6, KEY_KP2, KEY_KP4};

void
handle_dpad_pointer(struct input_event *ev)
{
    int xmot, ymot, numdirs;
    static int dpad_up_pressed; // D-pad directions currently pushed?
    static int dpad_down_pressed;
    static int dpad_left_pressed;
    static int dpad_right_pressed;


    switch(ev->code) {
    case KEY_KP8: dpad_up_pressed = ev->value;          break;
    case KEY_KP2: dpad_down_pressed = ev->value;        break;
    case KEY_KP4: dpad_left_pressed = ev->value;        break;
    case KEY_KP6: dpad_right_pressed = ev->value;       break;
    }

    xmot = ymot = numdirs = 0;

    if (dpad_up_pressed) {
        dbg(3, "dpad up pressed");
        ymot -= 1;
        numdirs++;
    }
    if (dpad_down_pressed) {
        dbg(3, "dpad down pressed");
        ymot += 1;
        numdirs++;
    }
    if (dpad_left_pressed) {
        dbg(3, "dpad left pressed");
        xmot -= 1;
        numdirs++;
    }
    if (dpad_right_pressed) {
        dbg(3, "dpad right pressed");
        xmot += 1;
        numdirs++;
    }

    ev->type = EV_REL;
    if (xmot) {
        ev->code = REL_X;
        ev->value = xmot * (ymot ? 2 : 4);
        dbg(3, "dpad X %d", ev->value);
        if (!noxmit && write(uinp_fd, ev, sizeof(*ev)) < 0) {
            report("uinput write failed: %s", strerror(errno));
        }
    }
    if (ymot) {
        ev->code = REL_Y;
        ev->value = ymot * (xmot ? 2 : 4);
        dbg(3, "dpad Y %d", ev->value);
        if (!noxmit && write(uinp_fd, ev, sizeof(*ev)) < 0) {
            report("uinput write failed: %s", strerror(errno));
        }
    }
    if (xmot || ymot)
        inject_uinput_event(EV_SYN, SYN_REPORT, 0);
}

int
keyboard_event_worker(struct input_event *ev, int is_local)
{
    int i;
    int pass = 1;
    static int altdown, ctrldown;

    dbg(3, "keyboard evtype %d code %d value %d", ev->type, ev->code, ev->value);


    if (ev->type == EV_KEY) {

        if (is_local && ebook_mode) {
            switch(ev->code) {
            case KEY_KP1:   // 4 game keys
            case KEY_KP3:
            case KEY_KP7:
            case KEY_KP9:
            case KEY_KP4:       // 4 dpad arrow keys
            case KEY_KP2:
            case KEY_KP6:
            case KEY_KP8:
            case KEY_SWITCHVIDEOMODE:  // rotate button
                break;
            default:
                dbg(3, "ebook: discarding keyboard");
                return 0;
            }
        }

        for (i = 0; i < ngrab; i++) {
            if (ev->code == grabkey[i].code) {
                /* keep track of how many grab keys are down */
                if (ev->value) {
                    if (grabkey[i].pressed++ == 0)
                        scrolling++;
                } else {
                    grabkey[i].pressed = 0;
                    if (--scrolling == 0)
                        cumul_x = cumul_y = 0;
                }

                pass = grabkey[i].pass;

                dbg(2, "scrolling %d", scrolling);
                break;
            }
        }
    }

    /* implement arrow key grab-scrolling */
    if (scrolling && ev->value) {
        switch(ev->code) {
        case KEY_UP:    send_a_scroll( 0,-1);  pass = 0; break;
        case KEY_DOWN:  send_a_scroll( 0, 1);  pass = 0; break;
        case KEY_LEFT:  send_a_scroll(-1, 0);  pass = 0; break;
        case KEY_RIGHT: send_a_scroll( 1, 0);  pass = 0; break;
        }
    }

    if (is_local) {
        i = 0;
        switch(ev->code) {
        case KEY_KP4: i++; /* rotate the dpad on the screen bezel */
        case KEY_KP2: i++;
        case KEY_KP6: i++;
        case KEY_KP8:
            ev->code = dpad[rotation + i];
            if (dpad_pointer) {
                handle_dpad_pointer(ev);
                pass = 0;
            }
            break;

        case KEY_SWITCHVIDEOMODE:  // rotate button
            dbg(3, "might rotate");
            if (rotate_cmd && ev->value == 1) { /* ignore value 2 (repeats) */

                // command given, so don't pass the keystroke through.
                pass = 0;

                // if absolute, we can check for presence cheaply
                if (*rotate_cmd != '/' || access(rotate_cmd, X_OK) == 0) {
                    dbg(1, "invoking %s", rotate_cmd);
                    if (system(rotate_cmd) == 0)
                        ;
                }
            }
            break;

        case KEY_F9:        // brightness down key (or minimize, with alt)
        case KEY_F10:       // brightness up key (or maximize, with alt)
            dbg(3, "F9 or F10, %d", use_F9_F12);
            if (!use_F9_F12)
                break;
            goto brightness;

        case KEY_BRIGHTNESSDOWN:
        case KEY_BRIGHTNESSUP:
            dbg(3, "BRTD or BRTU, %d", use_BRT_VOL);
            if (!use_BRT_VOL)
                break;
    brightness:
            dbg(3, "might brighten");
            if (brightness_cmd && ev->value) {
                char cmd[256];
                char *arg;
                int kreduce = (ev->code == KEY_F9 || ev->code == KEY_BRIGHTNESSDOWN);

                // command given, so don't pass the keystroke through.
                pass = 0;

                // if absolute, we can check for presence cheaply
                if (*brightness_cmd != '/' ||
                            access(brightness_cmd, X_OK) == 0) {
                    if (altdown) {
                        arg = kreduce ? "min" : "max";
                    } else if (ctrldown) {
                        arg = kreduce ? "mono" : "color";
                    } else {
                        arg = kreduce ? "down" : "up";
                    }
                    if (snprintf(cmd, sizeof(cmd), "%s %s",
                                    brightness_cmd, arg) <= sizeof(cmd)) {
                        dbg(1, "invoking %s", cmd);
                        if (system(cmd) == 0)
                            ;
                    }
                }
            }
            break;

        case KEY_F11:       // volume down key (or minimize, with alt)
        case KEY_F12:       // volume up key (or maximize, with alt)
            dbg(3, "F11 or F12, %d", use_F9_F12);
            if (!use_F9_F12)
                break;
            goto volume;

        case KEY_VOLUMEDOWN:
        case KEY_VOLUMEUP:
            dbg(3, "VOLD or VOLU, %d", use_BRT_VOL);
            if (!use_BRT_VOL)
                break;
    volume:
            dbg(3, "might adjust volume");
            if (volume_cmd && ev->value) {
                char cmd[256];
                char *arg;
                int kreduce = (ev->code == KEY_F11 || ev->code == KEY_VOLUMEDOWN);

                // command given, so don't pass the keystroke through.
                pass = 0;

                // if absolute, we can check for presence cheaply
                if (*volume_cmd != '/' ||
                            access(volume_cmd, X_OK) == 0) {
                    if (altdown) {
                        arg = kreduce ? "min" : "max";
                    } else {
                        arg = kreduce ? "down" : "up";
                    }
                    if (snprintf(cmd, sizeof(cmd), "%s %s",
                                    volume_cmd, arg) <= sizeof(cmd)) {
                        dbg(1, "invoking %s", cmd);
                        if (system(cmd) == 0)
                            ;
                    }
                }
            }
            break;

        case KEY_LEFTALT:
        case KEY_RIGHTALT:
            if (ev->value == 0)
                altdown--;
            else if (ev->value == 1)
                altdown++;
            else
                /* ignore repeats with ev->value == 2 */ ;
            if (altdown > 2) altdown = 2;       // safety
            else if (altdown < 0) altdown = 0;  // safety
            dbg(3, "altdown now %d", altdown);
            break;

        case KEY_LEFTCTRL:
        case KEY_RIGHTCTRL:
            if (ev->value == 0)
                ctrldown--;
            else if (ev->value == 1)
                ctrldown++;
            else
                /* ignore repeats with ev->value == 2 */ ;
            if (ctrldown > 2) ctrldown = 2;       // safety
            else if (ctrldown < 0) ctrldown = 0;  // safety
            dbg(3, "ctrldown now %d", ctrldown);
            break;
        }
    }

    dbg(3, "keyboard sending");
    if (pass && !noxmit && write(uinp_fd, ev, sizeof(*ev)) < 0) {
        report("uinput write failed: %s", strerror(errno));
    }

    dbg(3, "keyboard_event returning %d", !!(ev->value));
    return !!(ev->value);  /* indicates press */
}

static void
handle_touchpad_scroll(unsigned int type, unsigned short code, int value)
{
    dbg(3, "touchpad scrolling");
    if (type == EV_REL) {

        if (code == REL_X)
            cumul_x += value;
        else if (code == REL_Y)
            cumul_y += value;

    } else if (type == EV_SYN) {
        /* emit our current scroll input */
        int x = 0, y = 0;

        if (abs(cumul_y) > quantum) {
            if (abs(cumul_x) < ratio * abs(cumul_y) / 100)
                cumul_x = 0;
            y = cumul_y;
            cumul_y = 0;
        }
        if (abs(cumul_x) > quantum) {
            if (abs(cumul_y) < ratio * abs(cumul_x) / 100)
                cumul_y = 0;
            x = cumul_x;
            cumul_x = 0;
        }
        send_a_scroll(x, y);
    }
}

static void
handle_rotation(unsigned short *code, int *value, int is_local)
{
    unsigned short _code = *code;
    int _value = *value;

    /* only rotate/flip the local touchpad */
    if (is_local) {
        if ((reflect_x && _code == REL_X) ||
            (reflect_y && _code == REL_Y)) {
            *value = -_value;
        }

        switch(rotation) {
        case ROT_NORMAL:
            break;
        case ROT_RIGHT:
            if (_code == REL_Y) {
                *code = REL_X;
            } else if (_code == REL_X) {
                *code = REL_Y;
                *value = -_value;
            }
            break;
        case ROT_LEFT:
            if (_code == REL_Y) {
                *code = REL_X;
                *value = -_value;
            } else if (_code == REL_X) {
                *code = REL_Y;
            }
            break;
        case ROT_INVERT:
            *value = -_value;
            break;
        }
    }
}

void
abs_touchpad_event_worker(struct input_event *ev, int is_local)
{
    unsigned short new_code = ev->code;
    unsigned int new_type = ev->type;
    int rel_value = ev->value;

    dbg(3, "touchpad abs evtype %d code %d, rot is %d", ev->type, ev->code, rotation);

    if (ev->type == EV_KEY) {
        switch (ev->code) {
        case BTN_TOUCH:
            if (ev->value == 0) {
                dbg(3, "reset last_abs");
                last_abs_x = -1;
                last_abs_y = -1;
                return;
            }
            break;

        default:
            /* passthrough clicks */
            dbg(3, "abs touchpad sending click");
            write_uinput_event(ev);
            return;
        }
    }

    if (ev->type != EV_ABS && ev->type != EV_SYN)
        return;

    if (ev->type == EV_ABS) {
        /* Translate to a relative movement */
        if (ev->code == ABS_X && last_abs_x == -1) {
            last_abs_x = ev->value;
            return;
        } else if (ev->code == ABS_Y && last_abs_y == -1) {
            last_abs_y = ev->value;
            return;
        }

        /* At this point we're dealing with a relative movement. */
        switch (ev->code) {
        case ABS_X:
            rel_value = ev->value - last_abs_x;
            new_code = REL_X;
            last_abs_x = ev->value;
            break;
        case ABS_Y:
            rel_value = ev->value - last_abs_y;
            new_code = REL_Y;
            last_abs_y = ev->value;
            break;
        default:
            dbg(3, "Unrecognised code");
            return;
        }
        new_type = EV_REL;
        handle_rotation(&new_code, &rel_value, is_local);
    }

    if (scrolling) {
        handle_touchpad_scroll(new_type, new_code, rel_value);
        return;
    }

    /* Pass through SYNs now */
    if (ev->type == EV_SYN) {
            write_uinput_event(ev);
            return;
    }

    /* Otherwise inject our translated event */
    inject_uinput_event(new_type, new_code, rel_value);
}

void
touchpad_event_worker(struct input_event *ev, int is_local)
{
    dbg(3, "touchpad evtype %d code %d, rot is %d", ev->type, ev->code, rotation);

    if (is_local && ebook_mode) {
        dbg(3, "ebook: discarding touchpad");
        return;
    }

    if (ev->type == EV_REL)
        handle_rotation(&ev->code, &ev->value, is_local);

    if (scrolling && ev->type != EV_KEY) { // i.e., EV_REL or EV_SYN
        handle_touchpad_scroll(ev->type, ev->code, ev->value);
    } else {
        /* passthrough */
        dbg(3, "touchpad sending");
        write_uinput_event(ev);
    }
    dbg(3, "touchpad_event done", !!(ev->value));
}


#define TS_MAX 32767

void
touchscreen_event_rotate(struct input_event *ev)
{
    if (ev->type == EV_ABS) {
        if (reflect_x &&
                (ev->code == ABS_X || ev->code == ABS_MT_POSITION_X)) {
            ev->value = tscreen_max[ABS_X] - ev->value;
        }
        if (reflect_y &&
                (ev->code == ABS_X || ev->code == ABS_MT_POSITION_Y)) {
            ev->value = tscreen_max[ABS_Y] - ev->value;
        }
        switch (rotation) {
        case ROT_NORMAL:
            break;
        case ROT_LEFT:
            switch (ev->code) {
            case ABS_X:
                ev->code = ABS_Y;
                ev->value = ev->value *
                                tscreen_max[ABS_Y] / tscreen_max[ABS_X];
                break;
            case ABS_MT_POSITION_X:
                ev->code = ABS_MT_POSITION_Y;
                ev->value = ev->value *
                                tscreen_max[ABS_Y] / tscreen_max[ABS_X];
                break;
            case ABS_Y:
                ev->code = ABS_X;
                ev->value = (tscreen_max[ABS_Y] - ev->value) *
                                tscreen_max[ABS_X] / tscreen_max[ABS_Y];
                break;
            case ABS_MT_POSITION_Y:
                ev->code = ABS_MT_POSITION_X;
                ev->value = (tscreen_max[ABS_Y] - ev->value) *
                                tscreen_max[ABS_X] / tscreen_max[ABS_Y];
                break;
            }
            break;
        case ROT_RIGHT:
            switch (ev->code) {
            case ABS_X:
                ev->code = ABS_Y;
                ev->value = (tscreen_max[ABS_X] - ev->value) *
                                tscreen_max[ABS_Y] / tscreen_max[ABS_X];
                break;
            case ABS_MT_POSITION_X:
                ev->code = ABS_MT_POSITION_Y;
                ev->value = (tscreen_max[ABS_X] - ev->value) *
                                tscreen_max[ABS_Y] / tscreen_max[ABS_X];
                break;
            case ABS_Y:
                ev->code = ABS_X;
                ev->value = ev->value *
                                tscreen_max[ABS_X] / tscreen_max[ABS_Y];
                break;
            case ABS_MT_POSITION_Y:
                ev->code = ABS_MT_POSITION_X;
                ev->value = ev->value *
                                tscreen_max[ABS_X] / tscreen_max[ABS_Y];
                break;
                break;
            }
            break;

        case ROT_INVERT:
            switch (ev->code) {
            case ABS_X:
            case ABS_MT_POSITION_X:
                ev->value = tscreen_max[ABS_X] - ev->value;
                break;
            case ABS_Y:
            case ABS_MT_POSITION_Y:
                ev->value = tscreen_max[ABS_Y] - ev->value;
                break;
            }
            break;
        }
    }
}

void
touchscreen_event_worker(struct input_event *ev, int is_local)
{
    static struct input_event queue[QUEUE_SIZE];
    static int queue_pos = 0;
    static enum swipe_state {
        NO_SWIPE,        /* looking for swipe start, no current touch */
        NO_SWIPE_TOUCHED,/* touch which is not a swipe, ignore */
        POTENTIAL_SWIPE, /* swipe maybe started; see 'swipe edge' */
        SWIPING,         /* swipe in progress; see 'swipe edge' for direction */
        DONE_SWIPING,    /* done processing a swipe, wait for touch up */
    } swipe_state = NO_SWIPE;
    static enum {
        NONE,
        FROM_TOP, FROM_BOTTOM, /* scroll wheel swipes */
        FROM_LEFT, FROM_RIGHT, /* wm swipes */
    } swipe_dir = NONE;
    static int wm_pos = 2; /* start at xo home screen */
    static int last_x, last_y;
    int i, good;

    dbg(3, "pre:  touchscreen evtype %d code %d value %d", ev->type, ev->code, ev->value);

    touchscreen_event_rotate(ev);
    dbg(3, "post: touchscreen evtype %d code %d value %d", ev->type, ev->code, ev->value);

    /* queue events */
    memcpy(&(queue[queue_pos]), ev, sizeof(*ev));
    queue_pos = (queue_pos + 1) % QUEUE_SIZE;
    // queue up until we get a SYN
    if (ev->type != EV_SYN) return;
    if (ev->code != SYN_REPORT) return;

    /* scan buffer, look for touches starting at the edges */
    if (swipe_state == NO_SWIPE) {
        swipe_dir = NONE;
        good = 0;
        for (i=0; i<queue_pos; i++) {
            switch (queue[i].type) {
            case EV_ABS:
                switch (queue[i].code) {
                case ABS_X:
                    last_x = queue[i].value;
                    if (queue[i].value<=tscreen_min[ABS_X]+swipe_margin)
                        swipe_dir = FROM_LEFT;
                    if (queue[i].value>=tscreen_max[ABS_X]-swipe_margin)
                        swipe_dir = FROM_RIGHT;
                    break;
                case ABS_Y:
                    last_y = queue[i].value;
                    if (queue[i].value<=tscreen_min[ABS_Y]+swipe_margin)
                        swipe_dir = FROM_TOP;
                    if (queue[i].value>=tscreen_max[ABS_Y]-swipe_margin)
                        swipe_dir = FROM_BOTTOM;
                    break;
                default:
                    break;
                }
                break;
            case EV_KEY:
                if (queue[i].code == BTN_TOUCH && queue[i].value)
                    good = 1;
                break;
            default:
                break;
            }
        }
        if (good) {
            if (swipe_dir == NONE)
                swipe_state = NO_SWIPE_TOUCHED;
            else {
                swipe_state = POTENTIAL_SWIPE;
                report("Starting potential swipe: %d", swipe_dir);
                return; // don't pass this through yet.
            }       
        }
    } else if (swipe_state == NO_SWIPE_TOUCHED ||
               swipe_state == DONE_SWIPING) {
        /* just look for touch release. */
        good=0;
        for (i=0; i<queue_pos; i++)
            if (queue[i].type==EV_KEY &&
                queue[i].code==BTN_TOUCH &&
                !queue[i].value) {
                good=1;
                break;
            }
        if (swipe_state == DONE_SWIPING)
            queue_pos = 0; /* discard all packets during swipe */
        if (good)
            swipe_state = NO_SWIPE;
    } else if (swipe_state == POTENTIAL_SWIPE) {
        /* wait until one of:
         *  SWIPE_TRIGGER_NUM touch packets
         *  distance > SWIPE_TRIGGER_DIST
         *  touch up
         * and then see if this is a real swipe. */
        int dist_x=0, dist_y=0;
        // start at queue_pos-2; queue_pos-1 is a SYN_REPORT
        for (i=queue_pos-2; i>=0; i--) {
            if (queue[i].type==EV_SYN && queue[i].code==SYN_REPORT)
                break;
            if (queue[i].type==EV_ABS && queue[i].code==ABS_X)
                dist_x = last_x - queue[i].value;
            if (queue[i].type==EV_ABS && queue[i].code==ABS_Y)
                dist_y = last_y - queue[i].value;
            if (queue[i].type==EV_KEY && queue[i].code==BTN_TOUCH &&
                !queue[i].value) {
                report("Not a swipe (lifted): pos %d x %d y %d", queue_pos, dist_x, dist_y);
                swipe_state = NO_SWIPE;
            }
        }
        if (dist_x < 0) dist_x = -dist_x;
        if (dist_y < 0) dist_y = -dist_y;
        if (swipe_state == POTENTIAL_SWIPE &&
            (dist_x > swipe_trigger_dist ||
             dist_y > swipe_trigger_dist)) {
            // is this a swipe?
            // XXX should check that swipe is "straight enough"?
            report("It's a swipe: pos %d x %d y %d", queue_pos, dist_x, dist_y);
            swipe_state = SWIPING;
            queue_pos = 0; // yeah, it's a swipe, no need to keep buffering
        }
        if (swipe_state == POTENTIAL_SWIPE &&
            queue_pos > swipe_trigger_num) {
            // held finger down too long w/o travelling far enough, no swipe
            report("Not a swipe (too long): pos %d x %d y %d", queue_pos, dist_x, dist_y);
            swipe_state = NO_SWIPE;
        }
        if (swipe_state != NO_SWIPE)
            return;
    } else if (swipe_state == SWIPING) {
        if (swipe_dir == FROM_LEFT || swipe_dir == FROM_RIGHT) {
            if (swipe_dir == FROM_LEFT && wm_pos > 0)
                wm_pos--;
            if (swipe_dir == FROM_RIGHT && wm_pos < 3)
                wm_pos++;
            inject_uinput_event(EV_KEY, KEY_F1 + wm_pos, 1);
            inject_uinput_event(EV_SYN, SYN_REPORT, 0);
            inject_uinput_event(EV_KEY, KEY_F1 + wm_pos, 0);
            inject_uinput_event(EV_SYN, SYN_REPORT, 0);
            queue_pos = 0; // throw away packet.
            swipe_state = DONE_SWIPING;
            return;
        } else if (swipe_dir == FROM_TOP || swipe_dir == FROM_BOTTOM) {
            // look for ABS_Y
            int y = last_y, dist_y;
            for (i=0; i<queue_pos; i++) {
                if (queue[i].type==EV_ABS && queue[i].code==ABS_Y)
                    y = queue[i].value;
                if (queue[i].type==EV_KEY && queue[i].code==BTN_TOUCH &&
                    !queue[i].value) {
                    // done swiping.
                    queue_pos = 0;
                    swipe_state = NO_SWIPE;
                    return;
                }
            }
            dist_y = last_y - y;
            if (dist_y > swipe_scroll_quantum ||
                dist_y < -swipe_scroll_quantum) {
                last_y = y;
                send_a_scroll(0, dist_y);
            }
            queue_pos = 0;
            return;
        }
        // should never get here.
        queue_pos = 0; // throw away packet.
        swipe_state = DONE_SWIPING;
        return;
    }

    /* passthrough */
    dbg(3, "touchscreen sending %d", queue_pos);
    for (i=0; i<queue_pos; i++) {
        /* Note that we need to write the events one-by-one, because some
         * downstream clients expect to get only one event per read. */
        if (!noxmit && write(ts_uinp_fd, &queue[i], sizeof(queue[i])) < 0) {
            report("ts_uinput write %d failed: %s", i, strerror(errno));
        }
    }
    queue_pos = 0;
}

void
touchscreen_config_axis(int axis, struct input_absinfo *info) {
    if (!(axis >= 0 || axis < ABS_CNT)) return;
    report("Configuring touchscreen axis %d: min %d max %d fuzz %d flat %d",
           axis, info->minimum, info->maximum, info->fuzz, info->flat);
    tscreen_max[axis] = info->maximum;
    tscreen_min[axis] = info->minimum;
    tscreen_fuzz[axis] = info->fuzz;
    tscreen_flat[axis] = info->flat;
}

void
ebook_event_worker(struct input_event *ev)
{
    static int can_disable = 1;  // assume yes, for now

    dbg(3, "ebook evtype %d code %d value %d", ev->type, ev->code, ev->value);

    if (watch_ebook && ev->type == EV_SW && ev->code == SW_TABLET_MODE) {
        ebook_mode = ev->value;

        if (can_disable) {
            int fd;
            fd = open(local_tpad_enabled, O_RDWR|O_NONBLOCK);
            if (fd >= 0) {
                if (write(fd, ebook_mode ? "0":"1", 1) < 0)
        	    report("warning: tpad-enabled write failed");
                close(fd);
            } else {
                report("Unable to open %s\n", local_tpad_enabled);
                can_disable = 0;
            }
        }
    }
}


void command_worker(char *command)
{
    int n;
    unsigned int a, b, c;

    dbg(1, "command is '%s'\n", command);

    switch (command[0]) {
        case 'x': reflect_x = 0; break;
        case 'y': reflect_y = 0; break;
        case 'z': reflect_x = 0;
                  reflect_y = 0; break;

        case 'X': reflect_x = 1; break;
        case 'Y': reflect_y = 1; break;
        case 'Z': reflect_x = 1;
                  reflect_y = 1; break;

        case 'n': rotation = ROT_NORMAL; break;
        case 'r': rotation = ROT_RIGHT; break;
        case 'i': rotation = ROT_INVERT; break;
        case 'l': rotation = ROT_LEFT; break;

        case 'I':
                n = sscanf(command, "I %u %u %u", &a, &b, &c);
                if (n < 1) {
                    a = b = c = 0;
                } else {
                    if (n < 2 || b < a + 1)
                        b = a + 1;
                    if (n < 3 || c < b + 1)
                        c = b + 1;

                }

                { 
                    static unsigned int old_a, old_b, old_c;
                    if (a != old_a || b != old_b || c != old_c) {
                        report("idle timers changed to %d %d %d", a, b, c);
                    } else {
                        dbg(1,"idle timers set to %d %d %d", a, b, c);
                    }
                    old_a = a; old_b = b; old_c = c;
                }

                idledeltas[3] = 0; /* always, so our last sleep is untimed */
                idledeltas[2] = c - b;
                idledeltas[1] = b - a;
                idledeltas[0] = a;

                reinit_activity_monitor();

                break;

        case 'd': dpad_pointer = 0; break;
        case 'D': dpad_pointer = 1; break;

        case 'f':
        case 'F':
                /* F9/f9 to enable/disable capture of F9-F12,
                 * FBV/fbv to enable/disable capture of Fn-BRt, Fn-Vol keys
                 */
                if (!strncasecmp(command, "F9", 2)) {
                    use_F9_F12 = (command[0] == 'F');
                } else if (!strncasecmp(command, "FBV", 3)) {
                    use_BRT_VOL = (command[0] == 'F');
                }
                dbg(1,"fkey, now: use_F9_F12 %d, use_BRT_VOL %d", use_F9_F12, use_BRT_VOL);
                break;

        default:
                rotation = ROT_NORMAL;
                reflect_x = 0; reflect_y = 0;
                break;
    }

    dbg(1, "command done, rot/refl_x/refl_y is %d/%d/%d", rotation, reflect_x, reflect_y);

}

void
send_event(char *e)
{
    int fd, n;
    char evtbuf[128];

    fd = open(sysactive_path, O_RDWR|O_NONBLOCK);
    if (fd >= 0) {
        n = snprintf(evtbuf, 128, "%s %d\n", e, (int)time(0));
        n = write(fd, evtbuf, n);
        close(fd);
    }
}

int
init_fifo(char *fifo_node)
{
    int fd;
    struct stat sbuf;

#define fifomode 0622  /* allow anyone to adjust the rotation */
    dbg(1, "initializing fifo %s", fifo_node);

    if (mkfifo(fifo_node, fifomode)) {
        if (errno != EEXIST) {
            report("mkfifo of %s failed", fifo_node);
            return -1;
        }

        /* the path exists.  is it a fifo? */
        if (stat(fifo_node, &sbuf) < 0) {
            report("stat of %s failed", fifo_node);
            return -1;
        }

        /* if not, remove and recreate */
        if (!S_ISFIFO(sbuf.st_mode)) {
            unlink(fifo_node);
            if (mkfifo(fifo_node, fifomode)) {
                report("recreate of %s failed", fifo_node);
                return -1;
            }
        }
    }

    /* mkfifo was affected by umask */
    if (chmod(fifo_node, fifomode)) {
        report("chmod of %s to 0%o failed", fifo_node, fifomode);
        return -1;
    }

    /* open for read/write, since writers are itinerant */
    fd = open(fifo_node, O_RDWR);
    if (fd < 0) {
        report("open %s failed", fifo_node);
        return -1;
    }
    dbg(1, "done with unix fifo init", fifo_node);

    return fd;
}

void
deinit_fifo(void)
{
    if (fifo_fd >= 0) {
        close(fifo_fd);
        fifo_fd = -1;
    }
    unlink(command_fifo);
}

void
sighandler(int sig)
{
    deinit_both_uinput_devices();
    deinit_fifo();
    die("got signal %d", sig);
}

int
main(int argc, char *argv[])
{
    int sched_realtime = 0;
    unsigned short b, v, p;
    char *cp, *eargp;
    int c;
    int foreground = 0;

    me = argv[0];
    cp = strrchr(argv[0], '/');
    if (cp) me = cp + 1;

    while ((c = getopt(argc, argv, "flsdXve:q:n:K:T:t:g:G:R:A:r:b:V:")) != -1) {
        switch (c) {

        /* daemon options */
        case 'f':
            foreground = 1;
            break;
        case 'l':
            logtosyslog = 1;
            openlog(me, LOG_PID, LOG_NOTICE);
            break;
        case 's':
            sched_realtime = 1;
            break;
        case 'd':
            debug++;
            break;
        case 'X':
            noxmit = 1;
            break;

        /* algorithmic tuning */
        case 'v':
            reverse = 0;
            break;
        case 'q':
            quantum = strtol(optarg, &eargp, 10);
            if (*eargp != '\0' || quantum <= 0)
                usage();
            break;
        case 'n':
            ratio = strtol(optarg, &eargp, 10);
            if (*eargp != '\0' || ratio <= 0)
                usage();
            break;
        case 'K':
            if (sscanf(optarg, "%hx:%hx:%hx", &b, &v, &p) != 3)
                usage();
            local_kbd_id.bustype = b;
            local_kbd_id.vendor = v;
            local_kbd_id.product = p;
            break;
        case 'T':
            if (sscanf(optarg, "%hx:%hx:%hx", &b, &v, &p) != 3)
                usage();
            local_tpad_id.bustype = b;
            local_tpad_id.vendor = v;
            local_tpad_id.product = p;
            break;
        case 't':
            strncpy(local_tpad_enabled, optarg, sizeof(local_tpad_enabled));
            break;
        case 'g':
        case 'G':
            if (ngrab >= NGRABKEYS) {
                fprintf(stderr, "%s: too many grab keys specified, %d max\n",
                    me, NGRABKEYS);
                exit(1);
            }
            grabkey[ngrab].code = strtol(optarg, &eargp, 10);
            if (*eargp != '\0')
                usage();
            grabkey[ngrab].pass = (c == 'g');
            ngrab++;
            break;
        case 'e':
            watch_ebook = atoi(optarg);
            break;

        case 'R':
            command_fifo = optarg;
            break;

        case 'A':
            sysactive_path = optarg;
            break;

        case 'r':
            rotate_cmd = optarg;
            break;

        case 'b':
            brightness_cmd = optarg;
            break;

        case 'V':
            volume_cmd = optarg;
            break;

        default:
            usage();
            break;
        }
    }

    if (optind < argc) {
        report("found non-option argument(s)");
        usage();
    }

    /* default to XO grab keys, with passthrough.  since they're
     * just modifiers, nothing will happen with them unless X
     * wants it to.
     */
    if (ngrab == 0) {
        grabkey[0].code = KEY_LEFTMETA;
        grabkey[0].pass = 1;
        grabkey[1].code = KEY_RIGHTMETA;
        grabkey[1].pass = 1;
        ngrab = 2;
    }

    report("starting %s version %d", me, VERSION);

    if (!setup_input())
        die("%s: unable to find input devices", me);

    /* initialize uinput, if needed */
    if (!noxmit) {
        if (system("/sbin/modprobe uinput") == 0)
            sleep(1);
        if (setup_uinput(0) < 0)
            die("unable to find uinput device");

    }

    atexit(deinit_both_uinput_devices);
    atexit(deinit_fifo);

    if (command_fifo) {
        fifo_fd = init_fifo(command_fifo);
        if (fifo_fd < 0)
            report("no fifo for commands");
    }

    signal(SIGTERM, sighandler);
    signal(SIGHUP, sighandler);
    signal(SIGINT, sighandler);
    signal(SIGQUIT, sighandler);
    signal(SIGABRT, sighandler);
    signal(SIGUSR1, sighandler);
    signal(SIGUSR2, sighandler);

    if (sched_realtime) {
        struct sched_param sparam;
        int min, max;

        /* first, lock down all our memory */
        long takespace[1024];
        memset(takespace, 0, sizeof(takespace)); /* force paging */
        if (mlockall(MCL_CURRENT|MCL_FUTURE) < 0)
            die("unable to mlockall");

        /* then, raise our scheduling priority */
        min = sched_get_priority_min(SCHED_FIFO);
        max = sched_get_priority_max(SCHED_FIFO);

        sparam.sched_priority = (min + max)/2; /* probably always 50 */
        if (sched_setscheduler(0, SCHED_FIFO, &sparam))
            die("unable to set SCHED_FIFO");

        report("memory locked, scheduler priority set");

    }

    if (!foreground) {
        if (daemon(0, 0) < 0)
            die("failed to daemonize");
    }

    reinit_activity_monitor();

    monitor_input();

    return 0;
}

