// ** highly specialized ** xcape fork for my usage with q4os TDE :p
// -----------------------------------------------------------------------------------------------------------------------------------------------
// >> monitor only Super_L, so just use arguments like that: 
//    qsuperl -e 'Control_L|Super_L|Z'         --- equivalent to-->      xcape -e 'Super_L=Control_L|Super_L|Z'       (Super_L is implied)
//           (this is used to map the "dead" windows key to another key combination for the menu shortcut *so don't forget to set Super_L as a dead key to do this*)
//            so you can have a single press on Super_L to open the menu as it is replaced by the sequence you had set (configure TDE accordingly)
//            and have combinations like Super_L+<other keys> working too (configure them to your liking in TDE Keyboard Shortcuts, for ex.: Super_L+r to run program etc... )
//             this is best explained here on Q4OS Forum:      https://www.q4os.org/forum/viewtopic.php?id=5550
//
// >> window tiler from theasmitkid is integrated and mapped to win+enter, win+space and win+<cursur arrows>
//        I really like his tiler idea and ingenious implementation, and as we are already monitoring keys, why not intercept Super_L+<usual keys> for tiling too ?
//        so:    - no need for a separate daemon & - no need for the khotkey daemon (except if you use it for something else, they can work together)
//        The program logic is implemented in tiler.h, to have easier maintenance if something need to be changed in this part.
//            reference here: https://www.q4os.org/forum/viewtopic.php?id=5551
// 
// As I said, higly specialized and targetted at q4os TDE environment, but maybe it can interest you as a base for something else :)
//--------------------------------------------------------------------------------------------------------------------------------------------------



// gcc -O2 -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) qsuperl.c -s -o qsuperl $(pkg-config --libs xtst x11) -pthread && strip --strip-all ./qsuperl  && sstrip ./qsuperl

#include <stdlib.h>
#include <stdio.h>
#include <string.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>
#include <stdint.h>
#include "tiler.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;
KeyCode super_kc;
KeyCode enter_kc;
KeyCode space_kc;
} XCape_t;

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

void *grabbed_key_loop(void *arg) {
    XCape_t *self = (XCape_t*)arg;
    XEvent ev;
    while (1) {
        XNextEvent(self->ctrl_conn, &ev);
if (ev.type == KeyPress) {
	KeyCode kc = ev.xkey.keycode;
	if (kc == self->enter_kc) {
	handle_action(self->ctrl_conn, "fullscreen");
	} else if (kc == XKeysymToKeycode(self->ctrl_conn, XK_Left)) {
	handle_action(self->ctrl_conn, "left");
	} else if (kc == XKeysymToKeycode(self->ctrl_conn, XK_Right)) {
	handle_action(self->ctrl_conn, "right");
	} else if (kc == XKeysymToKeycode(self->ctrl_conn, XK_Up)) {
	handle_action(self->ctrl_conn, "up");
	} else if (kc == XKeysymToKeycode(self->ctrl_conn, XK_Down)) {
	handle_action(self->ctrl_conn, "down");
	}
}
    }
    return NULL;
}


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 = 300000;
    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->super_kc = XKeysymToKeycode(self->ctrl_conn, XStringToKeysym("Super_L"));
    self->enter_kc = XKeysymToKeycode(self->ctrl_conn, XK_Return);
self->space_kc = XKeysymToKeycode(self->ctrl_conn, XK_space);
    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;
    }
    if (!tiler_init(self->ctrl_conn)) {
        fprintf(stderr, "Failed to initialize tiler\n");
        exit(EXIT_FAILURE);
    }
KeyCode super_keycodes[] = {0x85, 0xce};
int masks[] = {0, LockMask, Mod2Mask, LockMask | Mod2Mask};
KeyCode kc_enter = self->enter_kc;
KeyCode kc_left  = XKeysymToKeycode(self->ctrl_conn, XK_Left);
KeyCode kc_right = XKeysymToKeycode(self->ctrl_conn, XK_Right);
KeyCode kc_up    = XKeysymToKeycode(self->ctrl_conn, XK_Up);
KeyCode kc_down  = XKeysymToKeycode(self->ctrl_conn, XK_Down);
KeyCode kc_space = XKeysymToKeycode(self->ctrl_conn, XK_space);
Window root = DefaultRootWindow(self->ctrl_conn);
for (int i = 0; i < sizeof(masks)/sizeof(int); i++) {
    int mod = Mod4Mask | masks[i];
    for (int j = 0; j < sizeof(super_keycodes)/sizeof(KeyCode); j++) {
        XGrabKey(self->ctrl_conn, kc_enter, mod, root, True, GrabModeAsync, GrabModeAsync);
        XGrabKey(self->ctrl_conn, kc_left,  mod, root, True, GrabModeAsync, GrabModeAsync);
        XGrabKey(self->ctrl_conn, kc_right, mod, root, True, GrabModeAsync, GrabModeAsync);
        XGrabKey(self->ctrl_conn, kc_up,    mod, root, True, GrabModeAsync, GrabModeAsync);
        XGrabKey(self->ctrl_conn, kc_down,  mod, root, True, GrabModeAsync, GrabModeAsync);
        XGrabKey(self->ctrl_conn, kc_space, mod, root, True, GrabModeAsync, GrabModeAsync);
    }
}
    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);
    pthread_t grab_thread;
    pthread_create(&grab_thread, NULL, grabbed_key_loop, 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;
static KeyCode left_kc = 0;
static KeyCode right_kc = 0;
static KeyCode up_kc = 0;
static KeyCode down_kc = 0;
  if (left_kc == 0) {
        left_kc = XKeysymToKeycode(self->ctrl_conn, XK_Left);
        right_kc = XKeysymToKeycode(self->ctrl_conn, XK_Right);
        up_kc = XKeysymToKeycode(self->ctrl_conn, XK_Up);
        down_kc = XKeysymToKeycode(self->ctrl_conn, XK_Down);
    }
    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_event == KeyPress && self->map.pressed) {
            if (key_code == self->enter_kc) {
                handle_action(self->ctrl_conn, "fullscreen");
                self->map.used = True;
          } else if (key_code == self->space_kc) {
              handle_action(self->ctrl_conn, "center");
             self->map.used = True;
            } else if (key_code == left_kc) {
                handle_action(self->ctrl_conn, "left");
                self->map.used = True;
            } else if (key_code == right_kc) {
                handle_action(self->ctrl_conn, "right");
                self->map.used = True;
            } else if (key_code == up_kc) {
                handle_action(self->ctrl_conn, "up");
                self->map.used = True;
            } else if (key_code == down_kc) {
                handle_action(self->ctrl_conn, "down");
                self->map.used = True;
            }
            if (self->map.used) {
                goto exit;
            }
        }
        if (key_code == self->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);
}
