/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */

/* In older X, you can run this and press the "Home" key and see key
 * events received.  In newer X, it gets an X error.
 */

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/XKBlib.h>
#include <X11/keysym.h>
#include <X11/XF86keysym.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int xkb_opcode;
static int xkb_event_base;
static int xkb_error_base;

static void
bind_key_xkb(Display     *xdisplay,
             XkbDescPtr   xkb,
             KeySym       keysym,
             KeyCode      keycode)
{
    XkbMapChangesRec changes;
    XkbMessageAction xma;
    int types[1];

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

    printf("Grabbing keysym 0x%lx keycode 0x%x using xkb\n",
           keysym, keycode);

    /* keysyms */
    if (XkbResizeKeySyms(xkb, keycode, 1) == NULL) {
        fprintf(stderr, "XkbResizeKeySyms() failed\n");
        return;
    }

    XkbKeySymEntry(xkb, keycode, 0, 0) = keysym;

    changes.changed |= XkbKeySymsMask;
    changes.first_key_sym = keycode;
    changes.num_key_syms = 1;

    /* actions */
    memset(&xma, 0, sizeof(xma));
    xma.type = XkbSA_ActionMessage;
    xma.flags = XkbSA_MessageOnPress | XkbSA_MessageOnRelease;
    strcpy((char*)xma.message, "!");

    if (XkbResizeKeyActions(xkb, keycode, 1) == NULL) {
        fprintf(stderr, "XkbResizeKeyActions failed\n");
        return;
    }

    XkbKeyActionEntry(xkb, keycode, 0, 0)->msg = xma;

    changes.changed |= XkbKeyActionsMask;
    changes.first_key_act = keycode;
    changes.num_key_acts = 1;

    /* groups */
    types[0] = XkbOneLevelIndex;

    if (XkbChangeTypesOfKey(xkb, keycode, 1, XkbGroup1Mask, types, NULL) != Success) {
        fprintf(stderr, "XkbChangeTypesOfKey failed\n");
        return;
    }

    changes.changed |= XkbKeyTypesMask;
    changes.first_type = XkbKeyKeyTypeIndex(xkb, keycode, 0);
    changes.num_types = 1;

#define XKB_CHANGE_MAP_NONSENSE 1

    printf("Sending map changes to server\n");
    /* commit to server */
    if (!XkbChangeMap(xdisplay, xkb, &changes)) {
        fprintf(stderr, "XkbChangeMap failed\n");
#if XKB_CHANGE_MAP_NONSENSE
    } else {
        /* XkbChangeMap apparently ignores XkbKeyActionsMask (server map) if
         * used together with XkbKeyTypesMask|XkbKeySymsMask (client map)
         */
        changes.changed = XkbKeyActionsMask;

        if (!XkbChangeMap(xdisplay, xkb, &changes)) {
            fprintf(stderr, "XkbChangeMap(XkbKeyActionsMask) failed\n");
        }
#endif
    }

    printf("Bound key OK\n");
}

static void
bind_keys(Display     *xdisplay,
          XkbDescPtr   xkb)
{
    KeySym keysym;
    KeyCode keycode;

    keysym = XK_Home;
    keycode = XKeysymToKeycode(xdisplay, keysym);

    bind_key_xkb(xdisplay, xkb, keysym, keycode);
}

int
main(int argc,
     char **argv)
{
    Display *xdisplay;
    XEvent xevent;
    XkbDescPtr xkb;

    xdisplay = XOpenDisplay(NULL);

    XSynchronize(xdisplay, True);

    XkbQueryExtension(xdisplay, &xkb_opcode, &xkb_event_base, &xkb_error_base,
                      NULL, NULL);

    xkb = XkbGetMap(xdisplay,
                    XkbKeySymsMask | XkbKeyTypesMask | XkbKeyActionsMask | XkbModifierMapMask | XkbVirtualModsMask,
                    XkbUseCoreKbd);

    XkbSelectEvents(xdisplay, XkbUseCoreKbd,
                    XkbMapNotifyMask | XkbActionMessageMask,
                    XkbMapNotifyMask | XkbActionMessageMask);

    bind_keys(xdisplay, xkb);

    while (XNextEvent(xdisplay, &xevent) == Success) {
        if (xevent.type == MappingNotify) {
            printf("MappingNotify\n");
        } else if (xevent.type == KeyPress) {
            printf("Key press event window 0x%lx root 0x%lx keycode 0x%x\n",
                   xevent.xkey.window,
                   xevent.xkey.root,
                   xevent.xkey.keycode);
        } else if (xevent.type == (xkb_event_base + XkbEventCode)) {
            XkbEvent *xkbevent = (XkbEvent*)&xevent;

            printf("Xkb event received\n");

            if (xkbevent->any.xkb_type == XkbMapNotify) {
                XkbMapNotifyEvent *map = &xkbevent->map;

                printf("XkbMapNotify\n");

                XkbRefreshKeyboardMapping(map);

                /* Need to redo our xkb keybindings as they may have been lost.
                 * Disable XkbMapNotify events for our own changes to avoid infinite
                 * busy loop. */
                XkbSelectEvents(xdisplay, XkbUseCoreKbd,
                                XkbMapNotifyMask, 0);

                bind_keys(xdisplay, xkb);

                XkbSelectEvents(xdisplay, XkbUseCoreKbd,
                                XkbMapNotifyMask, XkbMapNotifyMask);
            } else if (xkbevent->any.xkb_type == XkbActionMessage) {
                printf("XkbActionMessage %s keycode=0x%x mods=0x%x\n",
                       xkbevent->message.press ? "press" : "release",
                       xkbevent->message.keycode,
                       xkbevent->message.mods);
            } else {
                printf("Xkb event xkb_type=%u\n",
                       xkbevent->any.xkb_type);
            }
        } else {
            printf("event type %d\n", xevent.type);
        }
    }

    return 0;
}