/*
 * xinput_multitouch: test XInput2 multitouch events.
 * (c) 2015 Nicolas George
 *
 * Copying and distribution of this file, with or without modification,
 * are permitted in any medium without royalty provided the copyright
 * notice and this notice are preserved.  This file is offered as-is,
 * without any warranty.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <poll.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XInput2.h>
#include <X11/extensions/shape.h>

const char *device = "ELAN1001:00 04F3:2057";

#define MAX_SLOTS 20
#define OVER_RAD 64

typedef struct {
    int id;
    int x, y;
    Window overlay;
} MT_slot;

Display *display;
MT_slot slot[MAX_SLOTS];

static void
slot_create(MT_slot *s, int x, int y)
{
    Screen *screen = DefaultScreenOfDisplay(display);
    Visual *visual = DefaultVisualOfScreen(screen);
    int depth = DefaultDepthOfScreen(screen);
    XSetWindowAttributes attr;
    unsigned long attr_mask;
    XWMHints wm_hints;
    Window over;
    Pixmap mask;
    GC gc;

    attr.background_pixel = WhitePixelOfScreen(screen);
    attr.override_redirect = True;
    attr_mask = CWBackPixel | CWOverrideRedirect;
    over = XCreateWindow(display, DefaultRootWindow(display),
        x - OVER_RAD, y - OVER_RAD, 2 * OVER_RAD + 1, 2 * OVER_RAD + 1,
        0, depth, InputOutput, visual, attr_mask, &attr);
    wm_hints.flags = InputHint;
    wm_hints.input = False;
    XSetWMHints(display, over, &wm_hints);
    mask = XCreatePixmap(display, over, 2 * OVER_RAD + 1, 2 * OVER_RAD + 1, 1);
    gc = XCreateGC(display, mask, 0, NULL);
    XSetForeground(display, gc, 0);
    XFillRectangle(display, mask, gc, 0, 0, 2 * OVER_RAD + 1, 2 * OVER_RAD + 1);
    XSetForeground(display, gc, 1);
    XFillRectangle(display, mask, gc, OVER_RAD, 0, 1, 2 * OVER_RAD + 1);
    XFillRectangle(display, mask, gc, 0, OVER_RAD, 2 * OVER_RAD + 1, 1);
    XDrawArc(display, mask, gc, 0, 0, OVER_RAD * 2, OVER_RAD * 2, 0, 360 * 64);
    XShapeCombineMask(display, over, ShapeBounding, 0, 0, mask, ShapeSet);
    s->overlay = over;
}

static void
slot_move(int id, int mode, int x, int y)
{
    MT_slot *s;
    unsigned i;

    for (i = 0; i < MAX_SLOTS; i++)
        if (slot[i].id == id)
            break;
    s = &slot[i];
    if (i >= MAX_SLOTS) {
        if (mode < 0) {
            fprintf(stderr, "Update without end\n");
            return;
        }
        for (i = 0; i < MAX_SLOTS; i++)
            if (slot[i].id == -1)
                break;
        if (i >= MAX_SLOTS) {
            fprintf(stderr, "Too many slots, dropping\n");
            return;
        }
        s = &slot[i];
        if (mode == 0)
            fprintf(stderr, "Update without begin\n");
        s->id = id;
        if (s->overlay == None)
            slot_create(s, x, y);
        XMapWindow(display, s->overlay);
    }
    if (mode < 0) {
        s->id = -1;
        XUnmapWindow(display, s->overlay);
        return;
    }
    XMoveWindow(display, s->overlay, x - OVER_RAD, y - OVER_RAD);
}

static void
handle_x11_events(void)
{
    XEvent ev;
    XGenericEventCookie *cookie;
    XIDeviceEvent *de;

    const int xinput2_ext = 131; /* XXX */

    cookie = &ev.xcookie;
    while (XPending(display)) {
        XNextEvent(display, &ev);
        if (ev.type == GenericEvent && ev.xgeneric.extension == xinput2_ext) {
            XGetEventData(display, cookie);
            if (cookie->evtype == XI_TouchUpdate ||
                cookie->evtype == XI_TouchBegin ||
                cookie->evtype == XI_TouchEnd) {
                de = cookie->data;
                slot_move(de->detail,
                    de->evtype == XI_TouchBegin ? +1 :
                    de->evtype == XI_TouchEnd   ? -1 : 0,
                    de->root_x, de->root_y);
            } else if (0 && cookie->evtype == XI_Motion) {

                de = cookie->data;
                slot_move(de->detail, 0, de->root_x, de->root_y);
            } else {
                //printf("unknown event: %d\n", cookie->evtype);
            }
            XFreeEventData(display, cookie);
        }
    }
}

static void
init_slots(void)
{
    unsigned i;

    for (i = 0; i < MAX_SLOTS; i++) {
        slot[i].id = -1;
        slot[i].overlay = None;
    }
}

static void
open_device(void)
{
    XIDeviceInfo *dev_info;
    XIEventMask ev_mask;
    Window window;
    unsigned char ev_mask_map[4] = { 0, 0, 0, 0 };
    int nb_dev_info;
    int i, j;

    display = XOpenDisplay(getenv("DISPLAY2"));
    if (display == NULL) {
        fprintf(stderr, "Failed to open display\n");
        exit(1);
    }
    dev_info = XIQueryDevice(display, XIAllDevices, &nb_dev_info);
    if (dev_info == NULL) {
        fprintf(stderr, "No XInput devices\n");
        exit(1);
    }
    window = DefaultRootWindow(display);
    for (i = 0; i < nb_dev_info; i++) {
        if (strcmp(device, dev_info[i].name) != 0)
            continue;
        ev_mask.deviceid = XIAllDevices;
        ev_mask.mask_len = sizeof(ev_mask_map);
        ev_mask.mask = ev_mask_map;
        for (j = 1; j < XI_LASTEVENT; j++)
            XISetMask(ev_mask.mask, j);
        XIGrabDevice(display, dev_info[i].deviceid, window, CurrentTime, None,
            XIGrabModeSync, XIGrabModeSync, False, &ev_mask);
    }
}

int
main(void)
{
    struct pollfd pfd;

    init_slots();
    open_device();
    pfd.fd = ConnectionNumber(display);
    pfd.events = POLLIN;
    while (1) {
        poll(&pfd, 1, -1);
        handle_x11_events();
    }
    return 0;
}
