// xcape fork to manage ONLY Super_L
// gcc -O3 -Wl,-z,norelro -fstrict-aliasing -flto -ffunction-sections -fdata-sections -fno-asynchronous-unwind-tables -fno-unwind-tables -fomit-frame-pointer -fvisibility=hidden -fmerge-all-constants -fuse-ld=gold -Wl,--gc-sections,--build-id=none,--as-needed,--strip-all,--compress-debug-sections=zlib $(pkg-config --cflags xtst x11) qwincap.c -s -o qwincap $(pkg-config --libs xtst x11) -pthread && strip --strip-all ./qwincap  && sstrip ./qwincap

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
#include <pthread.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <X11/extensions/record.h>
#include <X11/extensions/XTest.h>
#include <X11/XKBlib.h>

#define MAX_TO_KEYS 32
#define MAX_KEYCODE 255

typedef struct {
    KeyCode to_keys[MAX_TO_KEYS];
    int num_to_keys;
    uint8_t generated_counts[MAX_KEYCODE + 1];
    Bool pressed;
    Bool used;
    struct timeval down_at;
} KeyMap_t;

typedef struct {
    Display *data_conn;
    Display *ctrl_conn;
    XRecordContext record_ctx;
    pthread_t sigwait_thread;
    sigset_t sigset;
    KeyMap_t map;
    struct timeval timeout;
} XCape_t;

void *sig_handler(void *user_data);
void intercept(XPointer user_data, XRecordInterceptData *data);

int main(int argc, char **argv) {
    XCape_t *self = calloc(1, sizeof(XCape_t));
    int dummy, ch;
    char *mapping = NULL;
    XRecordRange *rec_range = XRecordAllocRange();
    XRecordClientSpec client_spec = XRecordAllClients;
    self->timeout.tv_sec = 0;
    self->timeout.tv_usec = 500000;
   rec_range->device_events.first = KeyPress;
   rec_range->device_events.last = ButtonRelease;
    while ((ch = getopt(argc, argv, "e:t:")) != -1) {
        switch (ch) {
        case 'e':
            mapping = optarg;
            break;
        case 't': {
            int ms = atoi(optarg);
            if (ms > 0) {
                self->timeout.tv_sec = ms / 1000;
                self->timeout.tv_usec = (ms % 1000) * 1000;
            } else {
                return EXIT_FAILURE;
            }
            break;
        }
        default:
            return EXIT_SUCCESS;
        }
    }
   self->data_conn = XOpenDisplay(NULL);
    self->ctrl_conn = XOpenDisplay(NULL);
    if (!self->data_conn || !self->ctrl_conn) {
        fprintf(stderr, "X11 display connect error\n");
        exit(EXIT_FAILURE);
    }
    if (!XQueryExtension(self->ctrl_conn, "XTEST", &dummy, &dummy, &dummy) ||
        !XRecordQueryVersion(self->ctrl_conn, &dummy, &dummy) ||
        !XkbQueryExtension(self->ctrl_conn, &dummy, &dummy, &dummy, &dummy, &dummy)) {
        exit(EXIT_FAILURE);
    }
    self->map.num_to_keys = 0;
    if (mapping) {
        char *mapping_copy = strdup(mapping);
        if (mapping_copy) {
            char *p = mapping_copy;
            while (*p) if (*p++ == '|') self->map.num_to_keys++;
            self->map.num_to_keys++;
            free(mapping_copy);
            if (self->map.num_to_keys > MAX_TO_KEYS) {
                fprintf(stderr, "Too many keys\n");
                exit(EXIT_FAILURE);
            }
        }
        char *key;
        int i = 0;
        while ((key = strsep(&mapping, "|")) != NULL) {
            KeySym ks = XStringToKeysym(key);
            if (ks == NoSymbol) continue;
            KeyCode kc = XKeysymToKeycode(self->ctrl_conn, ks);
            if (kc == 0 || kc > MAX_KEYCODE) continue;
            self->map.to_keys[i++] = kc;
        }
        self->map.num_to_keys = i;
    }
    daemon(0, 0);
    sigemptyset(&self->sigset);
    sigaddset(&self->sigset, SIGINT);
    sigaddset(&self->sigset, SIGTERM);
    pthread_sigmask(SIG_BLOCK, &self->sigset, NULL);
    pthread_create(&self->sigwait_thread, NULL, sig_handler, self);
    self->record_ctx = XRecordCreateContext(self->ctrl_conn, 0, &client_spec, 1, &rec_range, 1);
    XSync(self->ctrl_conn, False);
    XRecordEnableContext(self->data_conn, self->record_ctx, intercept, (XPointer)self);
    pthread_join(self->sigwait_thread, NULL);
    XRecordFreeContext(self->ctrl_conn, self->record_ctx);
    XFree(rec_range);
    XCloseDisplay(self->ctrl_conn);
    XCloseDisplay(self->data_conn);
    free(self);
    return EXIT_SUCCESS;
}

void *sig_handler(void *user_data) {
    XCape_t *self = (XCape_t*)user_data;
    int sig;
    sigwait(&self->sigset, &sig);
    XLockDisplay(self->ctrl_conn);
    XRecordDisableContext(self->ctrl_conn, self->record_ctx);
    XSync(self->ctrl_conn, False);
    XUnlockDisplay(self->ctrl_conn);
    return NULL;
}

void intercept(XPointer user_data, XRecordInterceptData *data) {
    XCape_t *self = (XCape_t*)user_data;
    static int mouse_buttons_down = 0;
    KeyCode super_kc = XKeysymToKeycode(self->ctrl_conn, XStringToKeysym("Super_L"));
    XLockDisplay(self->ctrl_conn);
    if (data->category == XRecordFromServer) {
        int key_event = data->data[0];
        KeyCode key_code = data->data[1];
        if (key_code <= MAX_KEYCODE && self->map.generated_counts[key_code] > 0) {
            self->map.generated_counts[key_code]--;
          goto exit;
       }
        if (key_event == ButtonPress) {
            mouse_buttons_down++;
        } else if (key_event == ButtonRelease) {
            if (--mouse_buttons_down < 0) mouse_buttons_down = 0;
        }
        if (key_code == super_kc) {
            if (key_event == KeyPress) {
                self->map.pressed = True;
                gettimeofday(&self->map.down_at, NULL);
                if (mouse_buttons_down > 0) self->map.used = True;
            } else {
                if (!self->map.used) {
                    struct timeval now, delta;
                    gettimeofday(&now, NULL);
                    timersub(&now, &self->map.down_at, &delta);
                    if (timercmp(&delta, &self->timeout, <)) {
                        for (int i = 0; i < self->map.num_to_keys; i++) {
                         KeyCode k = self->map.to_keys[i];
                         XTestFakeKeyEvent(self->ctrl_conn, k, True, 0);
                           if (k <= MAX_KEYCODE) self->map.generated_counts[k]++;
                          }
                        for (int i = 0; i < self->map.num_to_keys; i++) {
                            KeyCode k = self->map.to_keys[i];
                            XTestFakeKeyEvent(self->ctrl_conn, k, False, 0);
                            if (k <= MAX_KEYCODE) self->map.generated_counts[k]++;
                        }
                        XFlush(self->ctrl_conn);
                    }
                }
                self->map.used = False;
                self->map.pressed = False;
            }
        }
    }
exit:
    XUnlockDisplay(self->ctrl_conn);
    XRecordFreeData(data);
}
