<gh_stars>1-10
from collections import defaultdict

from Xlib import X, XK, display
from Xlib.ext import record
from Xlib.protocol import rq

from ._keyboard_event import KeyboardEvent, KEY_DOWN, KEY_UP, normalize_name


# from ._nixkeyboard import init

def cleanup_key(name):
    if name.startswith('XK_'):
        name = name[3:]

    if name.startswith('KP_'):
        is_keypad = True
        name = name[3:]
    else:
        is_keypad = False

    if name.endswith('_R'):
        name = 'right ' + name[:-2]
    if name.endswith('_L'):
        name = 'left ' + name[:-2]

    return normalize_name(name), is_keypad


keysym_to_keys = defaultdict(list)
name_to_keysyms = defaultdict(list)
for raw_name in dir(XK):
    if not raw_name.startswith('XK_'): continue
    keysym = getattr(XK, raw_name)
    name, is_keypad = cleanup_key(raw_name)
    keysym_to_keys[keysym].append((name, is_keypad))
    name_to_keysyms[name].append(keysym)

local_dpy = None
record_dpy = None
ctx = None


def init():
    # Adapted from https://github.com/python-xlib/python-xlib/blob/master/examples/record_demo.py
    global local_dpy
    global record_dpy
    local_dpy = display.Display()
    record_dpy = display.Display()

    if not record_dpy.has_extension("RECORD"):
        raise ImportError("RECORD extension not found")

    r = record_dpy.record_get_version(0, 0)
    # print("RECORD extension version %d.%d" % (r.major_version, r.minor_version))

    # Create a recording context; we only want key and mouse events
    global ctx
    ctx = record_dpy.record_create_context(
        0,
        [record.AllClients],
        [
            {
                'core_requests': (0, 0),
                'core_replies': (0, 0),
                'ext_requests': (0, 0, 0, 0),
                'ext_replies': (0, 0, 0, 0),
                'delivered_events': (0, 0),
                'device_events': (X.KeyPress, X.KeyPress),
                'errors': (0, 0),
                'client_started': False,
                'client_died': False,
            }
        ]
    )


def listen(callback):
    def handler(reply):
        if reply.category != record.FromServer:
            return
        if reply.client_swapped:
            print("* received swapped protocol data, cowardly ignored")
            return
        if not len(reply.data) or reply.data[0] < 2:
            # not an event
            return

        data = reply.data
        while len(data):
            raw_event, data = rq.EventField(None).parse_binary_value(data, record_dpy.display, None, None)

            event_type = {X.KeyPress: KEY_DOWN, X.KeyRelease: KEY_UP}.get(raw_event.type)
            if event_type:
                keysym = local_dpy.keycode_to_keysym(raw_event.detail, 0)
                # TODO: scan code is not correct.
                if not keysym:
                    event = KeyboardEvent(event_type=event_type, scan_code=raw_event.detail)
                else:
                    try:
                        name, is_keypad = keysym_to_keys[keysym][0]
                    except IndexError:
                        name, is_keypad = None, None
                    event = KeyboardEvent(event_type=event_type, scan_code=keysym, name=name, is_keypad=is_keypad)

                callback(event)

    try:
        # Enable the context; this only returns after a call to record_disable_context,
        # while calling the callback function in the meantime
        record_dpy.record_enable_context(ctx, handler)
    finally:
        # Finally free the context
        record_dpy.record_free_context(ctx)


def map_name(name):
    for keysym in name_to_keysyms[name]:
        yield keysym, ()

from collections import defaultdict

from Xlib import X, XK, display
from Xlib.ext import record
from Xlib.protocol import rq

from ._keyboard_event import KeyboardEvent, KEY_DOWN, KEY_UP, normalize_name


# from ._nixkeyboard import init

def cleanup_key(name):
    if name.startswith('XK_'):
        name = name[3:]

    if name.startswith('KP_'):
        is_keypad = True
        name = name[3:]
    else:
        is_keypad = False

    if name.endswith('_R'):
        name = 'right ' + name[:-2]
    if name.endswith('_L'):
        name = 'left ' + name[:-2]

    return normalize_name(name), is_keypad


keysym_to_keys = defaultdict(list)
name_to_keysyms = defaultdict(list)
for raw_name in dir(XK):
    if not raw_name.startswith('XK_'): continue
    keysym = getattr(XK, raw_name)
    name, is_keypad = cleanup_key(raw_name)
    keysym_to_keys[keysym].append((name, is_keypad))
    name_to_keysyms[name].append(keysym)

local_dpy = None
record_dpy = None
ctx = None


def init():
    # Adapted from https://github.com/python-xlib/python-xlib/blob/master/examples/record_demo.py
    global local_dpy
    global record_dpy
    local_dpy = display.Display()
    record_dpy = display.Display()

    if not record_dpy.has_extension("RECORD"):
        raise ImportError("RECORD extension not found")

    r = record_dpy.record_get_version(0, 0)
    # print("RECORD extension version %d.%d" % (r.major_version, r.minor_version))

    # Create a recording context; we only want key and mouse events
    global ctx
    ctx = record_dpy.record_create_context(
        0,
        [record.AllClients],
        [
            {
                'core_requests': (0, 0),
                'core_replies': (0, 0),
                'ext_requests': (0, 0, 0, 0),
                'ext_replies': (0, 0, 0, 0),
                'delivered_events': (0, 0),
                'device_events': (X.KeyPress, X.KeyPress),
                'errors': (0, 0),
                'client_started': False,
                'client_died': False,
            }
        ]
    )


def listen(callback):
    def handler(reply):
        if reply.category != record.FromServer:
            return
        if reply.client_swapped:
            print("* received swapped protocol data, cowardly ignored")
            return
        if not len(reply.data) or reply.data[0] < 2:
            # not an event
            return

        data = reply.data
        while len(data):
            raw_event, data = rq.EventField(None).parse_binary_value(data, record_dpy.display, None, None)

            event_type = {X.KeyPress: KEY_DOWN, X.KeyRelease: KEY_UP}.get(raw_event.type)
            if event_type:
                keysym = local_dpy.keycode_to_keysym(raw_event.detail, 0)
                # TODO: scan code is not correct.
                if not keysym:
                    event = KeyboardEvent(event_type=event_type, scan_code=raw_event.detail)
                else:
                    try:
                        name, is_keypad = keysym_to_keys[keysym][0]
                    except IndexError:
                        name, is_keypad = None, None
                    event = KeyboardEvent(event_type=event_type, scan_code=keysym, name=name, is_keypad=is_keypad)

                callback(event)

    try:
        # Enable the context; this only returns after a call to record_disable_context,
        # while calling the callback function in the meantime
        record_dpy.record_enable_context(ctx, handler)
    finally:
        # Finally free the context
        record_dpy.record_free_context(ctx)


def map_name(name):
    for keysym in name_to_keysyms[name]:
        yield keysym, ()
# from ._nixkeyboard import init # Adapted from https://github.com/python-xlib/python-xlib/blob/master/examples/record_demo.py # print("RECORD extension version %d.%d" % (r.major_version, r.minor_version)) # Create a recording context; we only want key and mouse events # not an event # TODO: scan code is not correct. # Enable the context; this only returns after a call to record_disable_context, # while calling the callback function in the meantime # Finally free the context