/*
 * Copyright (C) Joel Eriksson <je@clevcode.org> 2022
 */

#include <sys/select.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>

#include <linux/uinput.h>
#include <linux/input.h>

#define TEST_BIT(bitmask, bit) ((bitmask[(bit)/8]&(1<<((bit)%8))))

#define PUT_BUF(buf, len) do { \
        if (fwrite((const void *) buf, (size_t) len, 1, stdout) != 1) \
            return -1; \
    } while (0)

#define PUT(T, x) do { \
        T v = (T) (x); \
        if (fwrite(&v, sizeof(v), 1, stdout) != 1) \
            return -1; \
    } while (0)

#define PUT_U8(x) PUT(uint8_t, x)
#define PUT_U16(x) PUT(uint16_t, htons(x))
#define PUT_U32(x) PUT(uint32_t, htonl(x))

#define FLUSH() do { fflush(stdout); } while (0)

int dump_devinfo(int fd)
{
    char name[UINPUT_MAX_NAME_SIZE];
    struct input_id iid;
    size_t n;

    memset(name, '\0', sizeof(name));

    if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) {
        perror("ioctl(EVIOCGNAME)");
        return -1;
    }

    n = strlen(name);
    if (n > 0xff) {
        /* should not be possible, UINPUT_MAX_NAME_SIZE is 80 */
        return -1;
    }

    PUT_U8(n);
    PUT_BUF(name, n);

    if (ioctl(fd, EVIOCGID, &iid) < 0) {
        perror("ioctl(EVIOCGID)");
        return -1;
    }

    PUT_U16(iid.vendor);
    PUT_U16(iid.product);
    PUT_U16(iid.version);
    PUT_U16(iid.bustype);

    return 0;
}

int dump_caps(int fd)
{
    unsigned char types[EV_MAX/8+1]; /* event types */
    int t, n;

    memset(&types, '\0', sizeof(types));

    if (ioctl(fd, EVIOCGBIT(0, sizeof(types)), types) < 0) {
        perror("ioctl(EVIOCGBIT(types))");
        return -1;
    }

    /* First pass, just count */
    for (n = t = 0; t < EV_MAX; t++)
        if (TEST_BIT(types, t))
            n++;

    PUT_U8(n); /* EV_MAX == 0x1f, a byte is enough */

    for (t = 0; t < EV_MAX; t++) {
        if (TEST_BIT(types, t)) {
            unsigned char codes[KEY_MAX/8+1]; /* codes for event type t */
            int c;

            PUT_U8(t);

            memset(codes, '\0', sizeof(codes));
            if (ioctl(fd, EVIOCGBIT(t, sizeof(codes)), codes) < 0)
                n = 0;
            else
                for (n = c = 0; c < KEY_MAX; c++)
                    if (TEST_BIT(codes, c))
                        n++;

            PUT_U16(n); /* KEY_MAX == 0x2ff, two bytes is enough */

            for (c = 0; c < KEY_MAX; c++) {
                if (TEST_BIT(codes, c)) {
                    PUT_U16(c);
                    /*
                    if (t == EV_ABS) {
                        struct input_absinfo absinfo;
                        memset(&absinfo, '\0', sizeof(absinfo));
                        if (ioctl(fd, EVIOCGABS(c), &absinfo) < 0) {
                            perror("ioctl(EVIOCGABS)");
                            return -1;
                        }
                        printf("    a value %d\n", absinfo.value);
                        printf("    a minimum %d\n", absinfo.minimum);
                        printf("    a maximum %d\n", absinfo.maximum);
                        printf("    a fuzz %d\n", absinfo.fuzz);
                        printf("    a flat %d\n", absinfo.flat);
                        printf("    a resolution %d\n", absinfo.resolution);
                    }
                    */
                }
            }
        }
    }

    return 0;
}

int proxy_events(int fd)
{
    int nfds = fd + 1;

    for (;;) {
        struct input_event events[1024];
        ssize_t nread;
        fd_set rfds;
        size_t i, n;

        FD_ZERO(&rfds);
        FD_SET(fd, &rfds);

        if (select(nfds, &rfds, NULL, NULL, NULL) < 0) {
            if (errno == EAGAIN || errno == EINTR)
                continue;
            perror("select");
            return -1;
        }

        for (;;) {
            nread = read(fd, &events, sizeof(events));
            if (nread == -1) {
                if (errno == EAGAIN || errno == EINTR)
                    continue;
                perror("read");
                return -1;
            }
            break;
        }

        if (nread == 0)
            break;

        if (((size_t) nread) % sizeof(events[0])) {
            fprintf(stderr, "Read truncated event, that should not happen\n");
            return -1;
        }

        n = (size_t) nread / sizeof(events[0]);

        for (i = 0; i < n; i++) {
            PUT_U16(events[i].type);
            PUT_U16(events[i].code);
            PUT_U32(events[i].value);
        }

        FLUSH();
    }

    return 0;
}

int main(int argc, char **argv)
{
    int fd;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s DEVICE\n", argv[0]);
        return 1;
    }

    if ((fd = open(argv[1], O_RDONLY)) < 0) {
        perror("open");
        return 2;
    }

    if (dump_caps(fd) < 0)
        return 3;

    if (dump_devinfo(fd) < 0)
        return 4;

    FLUSH();

    if (ioctl(fd, EVIOCGRAB, 1) < 0) {
        perror("ioctl(EVIOCGRAB, 1)");
        return 5;
    }

    proxy_events(fd);

    if (ioctl(fd, EVIOCGRAB, 0) < 0) {
        perror("ioctl(EVIOCGRAB, 0)");
        return 6;
    }

    close(fd);

    return 0;
}
