#    Copyright 2007 Jeff Epler <jepler@unpythonic.net>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

import struct, fcntl, array, os, select, glob, fnmatch, time, re

size_shift = 16
def SZ(a,b): return a | (b<<size_shift)

def get_name(f):
    r = fcntl.ioctl(f, SZ(EVIOCGNAME, 1024), '\0' * 1024)
    return r.decode('utf-8', errors='replace').rstrip('\0')

def get_phys(f):
    r = fcntl.ioctl(f, SZ(EVIOCGPHYS, 1024), '\0' * 1024)
    return r.decode('utf-8', errors='replace').rstrip('\0')

# get_uniq seems to return -ENOENT on all my devices
def get_uniq(f):
    r = fcntl.ioctl(f, SZ(EVIOCGUNIQ, 1024), '\0' * 1024)
    return r.decode('utf-8', errors='replace').rstrip('\0')

def get_bits(f, o=0):
    o = EV_invert.get(o, o)
    if o == 'EV_SYN': fmap, rmap, prefix = EV_invert, EV, "SYN"
    elif o == 'EV_KEY': fmap, rmap, prefix = KEYBTN_invert, KEY, "KEY"
    elif o == 'EV_LED': fmap, rmap, prefix = LED_invert, LED, "LED"
    elif o == 'EV_ABS': fmap, rmap, prefix = ABS_invert, ABS, "ABS"
    elif o == 'EV_REL': fmap, rmap, prefix = REL_invert, REL, "REL"
    else: raise ValueError("get_bits: unexpected map %s" % o)

    sz = max(fmap) + 1
    a = fcntl.ioctl(f, SZ(EVIOCGBIT+EV[o], sz), '\0' * ((sz+7)//8))
    ret = set()
    for j, ch in enumerate(a):
        for i in range(8):
            b = 1<<i
            if ch & b:
                k = j*8+i
                if k not in fmap:
                    name = "%s_%d" % (prefix, k)
                    fmap[k] = name
                    rmap[name] = k
                ret.add(fmap[k])
    return ret

def get_keys(f):
    ret = set()

class InputId:
    format = "HHHH"
    size = struct.calcsize(format)
    def __init__(self, buf):
        self.bustype, self.vendor, self.product, self.version = \
            struct.unpack(self.format, buf)
        self.bustype = BUS_invert.get(self.bustype, self.bustype)
    @classmethod
    def get(cls, fd):
        buf = fcntl.ioctl(fd, EVIOCGID, '\0' * cls.size)
        return cls(buf)

    def __repr__(self):
        return "<InputId: %s %04x:%04x version=%d>" % (
            self.bustype, self.vendor, self.product, self.version)
class AbsInfo:
    format = "iiiii"
    size = struct.calcsize(format)
    def __init__(self, *args):
        if len(args) == 1:
            self.value, self.minimum, self.maximum, self.fuzz, self.flat = \
                struct.unpack(self.format, args[0])
        else:
            self.value, self.minimum, self.maximum, self.fuzz, self.flat = args
    def __repr__(self):
        return "<AbsInfo: %8d %8d %8d %8d %8d>" % (
            self.value, self.minimum, self.maximum, self.fuzz, self.flat)
        
    @classmethod
    def get(cls, fd, idx):
        idx = ABS.get(idx, idx)
        buf = fcntl.ioctl(fd, EVIOCGABS + idx, '\0' * cls.size)
        return cls(buf)

    def set(self, fd, idx):
        idx = ABS.get(idx, idx)
        buf = struct.pack(format, self.value, self.minimum, self.maximum, 
                            self.fuzz, self.flat)
        fcntl.ioctl(fd, EVIOCSABS + idx, buf)

def mapcode(type, code):
    if type == EV_REL: return REL_invert.get(code, code)
    if type == EV_ABS: return ABS_invert.get(code, code)
    if type == EV_KEY:
        if code in KEY_invert:
            return KEY_invert[code]
        return BTN_invert.get(code, code)
    if type == EV_REL: return REL_invert.get(code, code)

class Event:
    format = "llHHi"
    size = struct.calcsize(format)

    def __init__(self, buf):
        data = struct.unpack(self.format, buf)
        self.time = data[0] + data[1] * 1e-9
        self.type = EV_invert[data[2]]
        self.code = data[3]
        self.value = data[4]

    @classmethod
    def read(cls, f):
        if isinstance(f, int):
            buf = os.read(f, cls.size)
        else:
            buf = f.read(size)
        return cls(buf)

    @classmethod
    def write(cls, f, *args):
        if len(args) == 3:
            event_time = time.time()
            time_seconds = int(event_time)
            time_ns = int((event_time - time_seconds) * 1e9)
            type, code, value = args
        elif len(args) == 4:
            event_time, type, code, value = args
            time_seconds = int(event_time)
            time_ns = int((event_time - time_seconds) * 1e9)
        else:
            time_seconds, time_ns, type, code, value = args
        if type == 'EV_LED' and isinstance(code, str): code = LED[code]
        type = EV.get(type, type)
        buf = struct.pack(cls.format, time_seconds, time_ns, type, code, value)
        os.write(f, buf)

    def __repr__(self):
        code = self.code
        if isinstance(code, int): code = "%04x" % code
        return "<Event %s %s %8d>" % (self.type, code, self.value)

EV = {
        'EV_SYN': 0x00,
        'EV_KEY': 0x01,
        'EV_REL': 0x02,
        'EV_ABS': 0x03,
        'EV_MSC': 0x04,
        'EV_SW': 0x05,
        'EV_LED': 0x11,
        'EV_SND': 0x12,
        'EV_REP': 0x14,
        'EV_FF': 0x15,
        'EV_PWR': 0x16,
        'EV_FF_STATUS': 0x17,
}
REL = {
        'REL_X': 0x00,
        'REL_Y': 0x01,
        'REL_Z': 0x02,
        'REL_RX': 0x03,
        'REL_RY': 0x04,
        'REL_RZ': 0x05,
        'REL_HWHEEL': 0x06,
        'REL_DIAL': 0x07,
        'REL_WHEEL': 0x08,
        'REL_MISC': 0x09,
}
KEY = {
        'KEY_RESERVED': 0,
        'KEY_ESC': 1,
        'KEY_1': 2,
        'KEY_2': 3,
        'KEY_3': 4,
        'KEY_4': 5,
        'KEY_5': 6,
        'KEY_6': 7,
        'KEY_7': 8,
        'KEY_8': 9,
        'KEY_9': 10,
        'KEY_0': 11,
        'KEY_MINUS': 12,
        'KEY_EQUAL': 13,
        'KEY_BACKSPACE': 14,
        'KEY_TAB': 15,
        'KEY_Q': 16,
        'KEY_W': 17,
        'KEY_E': 18,
        'KEY_R': 19,
        'KEY_T': 20,
        'KEY_Y': 21,
        'KEY_U': 22,
        'KEY_I': 23,
        'KEY_O': 24,
        'KEY_P': 25,
        'KEY_LEFTBRACE': 26,
        'KEY_RIGHTBRACE': 27,
        'KEY_ENTER': 28,
        'KEY_LEFTCTRL': 29,
        'KEY_A': 30,
        'KEY_S': 31,
        'KEY_D': 32,
        'KEY_F': 33,
        'KEY_G': 34,
        'KEY_H': 35,
        'KEY_J': 36,
        'KEY_K': 37,
        'KEY_L': 38,
        'KEY_SEMICOLON': 39,
        'KEY_APOSTROPHE': 40,
        'KEY_GRAVE': 41,
        'KEY_LEFTSHIFT': 42,
        'KEY_BACKSLASH': 43,
        'KEY_Z': 44,
        'KEY_X': 45,
        'KEY_C': 46,
        'KEY_V': 47,
        'KEY_B': 48,
        'KEY_N': 49,
        'KEY_M': 50,
        'KEY_COMMA': 51,
        'KEY_DOT': 52,
        'KEY_SLASH': 53,
        'KEY_RIGHTSHIFT': 54,
        'KEY_KPASTERISK': 55,
        'KEY_LEFTALT': 56,
        'KEY_SPACE': 57,
        'KEY_CAPSLOCK': 58,
        'KEY_F1': 59,
        'KEY_F2': 60,
        'KEY_F3': 61,
        'KEY_F4': 62,
        'KEY_F5': 63,
        'KEY_F6': 64,
        'KEY_F7': 65,
        'KEY_F8': 66,
        'KEY_F9': 67,
        'KEY_F10': 68,
        'KEY_NUMLOCK': 69,
        'KEY_SCROLLLOCK': 70,
        'KEY_KP7': 71,
        'KEY_KP8': 72,
        'KEY_KP9': 73,
        'KEY_KPMINUS': 74,
        'KEY_KP4': 75,
        'KEY_KP5': 76,
        'KEY_KP6': 77,
        'KEY_KPPLUS': 78,
        'KEY_KP1': 79,
        'KEY_KP2': 80,
        'KEY_KP3': 81,
        'KEY_KP0': 82,
        'KEY_KPDOT': 83,
        'KEY_ZENKAKUHANKAKU': 85,
        'KEY_102ND': 86,
        'KEY_F11': 87,
        'KEY_F12': 88,
        'KEY_RO': 89,
        'KEY_KATAKANA': 90,
        'KEY_HIRAGANA': 91,
        'KEY_HENKAN': 92,
        'KEY_KATAKANAHIRAGANA': 93,
        'KEY_MUHENKAN': 94,
        'KEY_KPJPCOMMA': 95,
        'KEY_KPENTER': 96,
        'KEY_RIGHTCTRL': 97,
        'KEY_KPSLASH': 98,
        'KEY_SYSRQ': 99,
        'KEY_RIGHTALT': 100,
        'KEY_LINEFEED': 101,
        'KEY_HOME': 102,
        'KEY_UP': 103,
        'KEY_PAGEUP': 104,
        'KEY_LEFT': 105,
        'KEY_RIGHT': 106,
        'KEY_END': 107,
        'KEY_DOWN': 108,
        'KEY_PAGEDOWN': 109,
        'KEY_INSERT': 110,
        'KEY_DELETE': 111,
        'KEY_MACRO': 112,
        'KEY_MUTE': 113,
        'KEY_VOLUMEDOWN': 114,
        'KEY_VOLUMEUP': 115,
        'KEY_POWER': 116,
        'KEY_KPEQUAL': 117,
        'KEY_KPPLUSMINUS': 118,
        'KEY_PAUSE': 119,
        'KEY_KPCOMMA': 121,
        'KEY_HANGUEL': 122,
        'KEY_HANJA': 123,
        'KEY_YEN': 124,
        'KEY_LEFTMETA': 125,
        'KEY_RIGHTMETA': 126,
        'KEY_COMPOSE': 127,
        'KEY_STOP': 128,
        'KEY_AGAIN': 129,
        'KEY_PROPS': 130,
        'KEY_UNDO': 131,
        'KEY_FRONT': 132,
        'KEY_COPY': 133,
        'KEY_OPEN': 134,
        'KEY_PASTE': 135,
        'KEY_FIND': 136,
        'KEY_CUT': 137,
        'KEY_HELP': 138,
        'KEY_MENU': 139,
        'KEY_CALC': 140,
        'KEY_SETUP': 141,
        'KEY_SLEEP': 142,
        'KEY_WAKEUP': 143,
        'KEY_FILE': 144,
        'KEY_SENDFILE': 145,
        'KEY_DELETEFILE': 146,
        'KEY_XFER': 147,
        'KEY_PROG1': 148,
        'KEY_PROG2': 149,
        'KEY_WWW': 150,
        'KEY_MSDOS': 151,
        'KEY_COFFEE': 152,
        'KEY_DIRECTION': 153,
        'KEY_CYCLEWINDOWS': 154,
        'KEY_MAIL': 155,
        'KEY_BOOKMARKS': 156,
        'KEY_COMPUTER': 157,
        'KEY_BACK': 158,
        'KEY_FORWARD': 159,
        'KEY_CLOSECD': 160,
        'KEY_EJECTCD': 161,
        'KEY_EJECTCLOSECD': 162,
        'KEY_NEXTSONG': 163,
        'KEY_PLAYPAUSE': 164,
        'KEY_PREVIOUSSONG': 165,
        'KEY_STOPCD': 166,
        'KEY_RECORD': 167,
        'KEY_REWIND': 168,
        'KEY_PHONE': 169,
        'KEY_ISO': 170,
        'KEY_CONFIG': 171,
        'KEY_HOMEPAGE': 172,
        'KEY_REFRESH': 173,
        'KEY_EXIT': 174,
        'KEY_MOVE': 175,
        'KEY_EDIT': 176,
        'KEY_SCROLLUP': 177,
        'KEY_SCROLLDOWN': 178,
        'KEY_KPLEFTPAREN': 179,
        'KEY_KPRIGHTPAREN': 180,
        'KEY_NEW': 181,
        'KEY_REDO': 182,
        'KEY_F13': 183,
        'KEY_F14': 184,
        'KEY_F15': 185,
        'KEY_F16': 186,
        'KEY_F17': 187,
        'KEY_F18': 188,
        'KEY_F19': 189,
        'KEY_F20': 190,
        'KEY_F21': 191,
        'KEY_F22': 192,
        'KEY_F23': 193,
        'KEY_F24': 194,
        'KEY_PLAYCD': 200,
        'KEY_PAUSECD': 201,
        'KEY_PROG3': 202,
        'KEY_PROG4': 203,
        'KEY_SUSPEND': 205,
        'KEY_CLOSE': 206,
        'KEY_PLAY': 207,
        'KEY_FASTFORWARD': 208,
        'KEY_BASSBOOST': 209,
        'KEY_PRINT': 210,
        'KEY_HP': 211,
        'KEY_CAMERA': 212,
        'KEY_SOUND': 213,
        'KEY_QUESTION': 214,
        'KEY_EMAIL': 215,
        'KEY_CHAT': 216,
        'KEY_SEARCH': 217,
        'KEY_CONNECT': 218,
        'KEY_FINANCE': 219,
        'KEY_SPORT': 220,
        'KEY_SHOP': 221,
        'KEY_ALTERASE': 222,
        'KEY_CANCEL': 223,
        'KEY_BRIGHTNESSDOWN': 224,
        'KEY_BRIGHTNESSUP': 225,
        'KEY_MEDIA': 226,
        'KEY_SWITCHVIDEOMODE': 227,
        'KEY_KBDILLUMTOGGLE': 228,
        'KEY_KBDILLUMDOWN': 229,
        'KEY_KBDILLUMUP': 230,
        'KEY_SEND': 231,
        'KEY_REPLY': 232,
        'KEY_FORWARDMAIL': 233,
        'KEY_SAVE': 234,
        'KEY_DOCUMENTS': 235,
        'KEY_UNKNOWN': 240,
        'KEY_VIDEO_NEXT':        241,
        'KEY_VIDEO_PREV':        242,
        'KEY_BRIGHTNESS_CYCLE':        243,
        'KEY_BRIGHTNESS_ZERO':        244,
        'KEY_DISPLAY_OFF':        245,
        'KEY_WIMAX':                246,
        'KEY_RFKILL':                247,

        'KEY_OK': 0x160,
        'KEY_SELECT': 0x161,
        'KEY_GOTO': 0x162,
        'KEY_CLEAR': 0x163,
        'KEY_POWER2': 0x164,
        'KEY_OPTION': 0x165,
        'KEY_INFO': 0x166,
        'KEY_TIME': 0x167,
        'KEY_VENDOR': 0x168,
        'KEY_ARCHIVE': 0x169,
        'KEY_PROGRAM': 0x16a,
        'KEY_CHANNEL': 0x16b,
        'KEY_FAVORITES': 0x16c,
        'KEY_EPG': 0x16d,
        'KEY_PVR': 0x16e,
        'KEY_MHP': 0x16f,
        'KEY_LANGUAGE': 0x170,
        'KEY_TITLE': 0x171,
        'KEY_SUBTITLE': 0x172,
        'KEY_ANGLE': 0x173,
        'KEY_ZOOM': 0x174,
        'KEY_MODE': 0x175,
        'KEY_KEYBOARD': 0x176,
        'KEY_SCREEN': 0x177,
        'KEY_PC': 0x178,
        'KEY_TV': 0x179,
        'KEY_TV2': 0x17a,
        'KEY_VCR': 0x17b,
        'KEY_VCR2': 0x17c,
        'KEY_SAT': 0x17d,
        'KEY_SAT2': 0x17e,
        'KEY_CD': 0x17f,
        'KEY_TAPE': 0x180,
        'KEY_RADIO': 0x181,
        'KEY_TUNER': 0x182,
        'KEY_PLAYER': 0x183,
        'KEY_TEXT': 0x184,
        'KEY_DVD': 0x185,
        'KEY_AUX': 0x186,
        'KEY_MP3': 0x187,
        'KEY_AUDIO': 0x188,
        'KEY_VIDEO': 0x189,
        'KEY_DIRECTORY': 0x18a,
        'KEY_LIST': 0x18b,
        'KEY_MEMO': 0x18c,
        'KEY_CALENDAR': 0x18d,
        'KEY_RED': 0x18e,
        'KEY_GREEN': 0x18f,
        'KEY_YELLOW': 0x190,
        'KEY_BLUE': 0x191,
        'KEY_CHANNELUP': 0x192,
        'KEY_CHANNELDOWN': 0x193,
        'KEY_FIRST': 0x194,
        'KEY_LAST': 0x195,
        'KEY_AB': 0x196,
        'KEY_NEXT': 0x197,
        'KEY_RESTART': 0x198,
        'KEY_SLOW': 0x199,
        'KEY_SHUFFLE': 0x19a,
        'KEY_BREAK': 0x19b,
        'KEY_PREVIOUS': 0x19c,
        'KEY_DIGITS': 0x19d,
        'KEY_TEEN': 0x19e,
        'KEY_TWEN': 0x19f,
        'KEY_DEL_EOL': 0x1c0,
        'KEY_DEL_EOS': 0x1c1,
        'KEY_INS_LINE': 0x1c2,
        'KEY_DEL_LINE': 0x1c3,
        'KEY_FN': 0x1d0,
        'KEY_FN_ESC': 0x1d1,
        'KEY_FN_F1': 0x1d2,
        'KEY_FN_F2': 0x1d3,
        'KEY_FN_F3': 0x1d4,
        'KEY_FN_F4': 0x1d5,
        'KEY_FN_F5': 0x1d6,
        'KEY_FN_F6': 0x1d7,
        'KEY_FN_F7': 0x1d8,
        'KEY_FN_F8': 0x1d9,
        'KEY_FN_F9': 0x1da,
        'KEY_FN_F10': 0x1db,
        'KEY_FN_F11': 0x1dc,
        'KEY_FN_F12': 0x1dd,
        'KEY_FN_1': 0x1de,
        'KEY_FN_2': 0x1df,
        'KEY_FN_D': 0x1e0,
        'KEY_FN_E': 0x1e1,
        'KEY_FN_F': 0x1e2,
        'KEY_FN_S': 0x1e3,
        'KEY_FN_B': 0x1e4,
        'KEY_BRL_DOT1':                 0x1f1,
        'KEY_BRL_DOT2':                 0x1f2,
        'KEY_BRL_DOT3':                 0x1f3,
        'KEY_BRL_DOT4':                 0x1f4,
        'KEY_BRL_DOT5':                 0x1f5,
        'KEY_BRL_DOT6':                 0x1f6,
        'KEY_BRL_DOT7':                 0x1f7,
        'KEY_BRL_DOT8':                 0x1f8,
        'KEY_BRL_DOT9':                 0x1f9,
        'KEY_BRL_DOT10':                 0x1fa,
        'KEY_NUMERIC_0':                 0x200,
        'KEY_NUMERIC_1':                 0x201,
        'KEY_NUMERIC_2':                 0x202,
        'KEY_NUMERIC_3':                 0x203,
        'KEY_NUMERIC_4':                 0x204,
        'KEY_NUMERIC_5':                 0x205,
        'KEY_NUMERIC_6':                 0x206,
        'KEY_NUMERIC_7':                 0x207,
        'KEY_NUMERIC_8':                 0x208,
        'KEY_NUMERIC_9':                 0x209,
        'KEY_NUMERIC_STAR':         0x20a,
        'KEY_NUMERIC_POUND':         0x20b,
        'KEY_CAMERA_FOCUS':         0x210,
        'KEY_WPS_BUTTON':         0x211,
        'KEY_TOUCHPAD_TOGGLE':         0x212,
        'KEY_TOUCHPAD_ON':         0x213,
        'KEY_TOUCHPAD_OFF':         0x214,
        'KEY_CAMERA_ZOOMIN':         0x215,
        'KEY_CAMERA_ZOOMOUT':         0x216,
        'KEY_CAMERA_UP':         0x217,
        'KEY_CAMERA_DOWN':         0x218,
        'KEY_CAMERA_LEFT':         0x219,
        'KEY_CAMERA_RIGHT':         0x21a,
}
BTN = {
        'BTN_MISC': 0x100,
        'BTN_0': 0x100,
        'BTN_1': 0x101,
        'BTN_2': 0x102,
        'BTN_3': 0x103,
        'BTN_4': 0x104,
        'BTN_5': 0x105,
        'BTN_6': 0x106,
        'BTN_7': 0x107,
        'BTN_8': 0x108,
        'BTN_9': 0x109,
        'BTN_10': 0x10a,
        'BTN_11': 0x10b,
        'BTN_12': 0x10c,
        'BTN_13': 0x10d,
        'BTN_14': 0x10e,
        'BTN_15': 0x10f,
        'BTN_MOUSE': 0x110,
        'BTN_LEFT': 0x110,
        'BTN_RIGHT': 0x111,
        'BTN_MIDDLE': 0x112,
        'BTN_SIDE': 0x113,
        'BTN_EXTRA': 0x114,
        'BTN_FORWARD': 0x115,
        'BTN_BACK': 0x116,
        'BTN_TASK': 0x117,
        'BTN_JOYSTICK': 0x120,
        'BTN_TRIGGER': 0x120,
        'BTN_THUMB': 0x121,
        'BTN_THUMB2': 0x122,
        'BTN_TOP': 0x123,
        'BTN_TOP2': 0x124,
        'BTN_PINKIE': 0x125,
        'BTN_BASE': 0x126,
        'BTN_BASE2': 0x127,
        'BTN_BASE3': 0x128,
        'BTN_BASE4': 0x129,
        'BTN_BASE5': 0x12a,
        'BTN_BASE6': 0x12b,
        'BTN_DEAD': 0x12f,
        'BTN_GAMEPAD': 0x130,
        'BTN_A': 0x130,
        'BTN_B': 0x131,
        'BTN_C': 0x132,
        'BTN_X': 0x133,
        'BTN_Y': 0x134,
        'BTN_Z': 0x135,
        'BTN_TL': 0x136,
        'BTN_TR': 0x137,
        'BTN_TL2': 0x138,
        'BTN_TR2': 0x139,
        'BTN_SELECT': 0x13a,
        'BTN_START': 0x13b,
        'BTN_MODE': 0x13c,
        'BTN_THUMBL': 0x13d,
        'BTN_THUMBR': 0x13e,
        'BTN_DIGI': 0x140,
        'BTN_TOOL_PEN': 0x140,
        'BTN_TOOL_RUBBER': 0x141,
        'BTN_TOOL_BRUSH': 0x142,
        'BTN_TOOL_PENCIL': 0x143,
        'BTN_TOOL_AIRBRUSH': 0x144,
        'BTN_TOOL_FINGER': 0x145,
        'BTN_TOOL_MOUSE': 0x146,
        'BTN_TOOL_LENS': 0x147,
        'BTN_TOUCH': 0x14a,
        'BTN_STYLUS': 0x14b,
        'BTN_STYLUS2': 0x14c,
        'BTN_TOOL_DOUBLETAP': 0x14d,
        'BTN_TOOL_TRIPLETAP': 0x14e,
        'BTN_TOOL_QUADTAP':     0x14f,
        'BTN_WHEEL': 0x150,
        'BTN_GEAR_DOWN': 0x150,
        'BTN_GEAR_UP': 0x151,

        'BTN_TRIGGER_HAPPY':                 0x2c0,
        'BTN_TRIGGER_HAPPY1':                 0x2c0,
        'BTN_TRIGGER_HAPPY2':                 0x2c1,
        'BTN_TRIGGER_HAPPY3':                 0x2c2,
        'BTN_TRIGGER_HAPPY4':                 0x2c3,
        'BTN_TRIGGER_HAPPY5':                 0x2c4,
        'BTN_TRIGGER_HAPPY6':                 0x2c5,
        'BTN_TRIGGER_HAPPY7':                 0x2c6,
        'BTN_TRIGGER_HAPPY8':                 0x2c7,
        'BTN_TRIGGER_HAPPY9':                 0x2c8,
        'BTN_TRIGGER_HAPPY10':                 0x2c9,
        'BTN_TRIGGER_HAPPY11':                 0x2ca,
        'BTN_TRIGGER_HAPPY12':                 0x2cb,
        'BTN_TRIGGER_HAPPY13':                 0x2cc,
        'BTN_TRIGGER_HAPPY14':                 0x2cd,
        'BTN_TRIGGER_HAPPY15':                 0x2ce,
        'BTN_TRIGGER_HAPPY16':                 0x2cf,
        'BTN_TRIGGER_HAPPY17':                 0x2d0,
        'BTN_TRIGGER_HAPPY18':                 0x2d1,
        'BTN_TRIGGER_HAPPY19':                 0x2d2,
        'BTN_TRIGGER_HAPPY20':                 0x2d3,
        'BTN_TRIGGER_HAPPY21':                 0x2d4,
        'BTN_TRIGGER_HAPPY22':                 0x2d5,
        'BTN_TRIGGER_HAPPY23':                 0x2d6,
        'BTN_TRIGGER_HAPPY24':                 0x2d7,
        'BTN_TRIGGER_HAPPY25':                 0x2d8,
        'BTN_TRIGGER_HAPPY26':                 0x2d9,
        'BTN_TRIGGER_HAPPY27':                 0x2da,
        'BTN_TRIGGER_HAPPY28':                 0x2db,
        'BTN_TRIGGER_HAPPY29':                 0x2dc,
        'BTN_TRIGGER_HAPPY30':                 0x2dd,
        'BTN_TRIGGER_HAPPY31':                 0x2de,
        'BTN_TRIGGER_HAPPY32':                 0x2df,
        'BTN_TRIGGER_HAPPY33':                 0x2e0,
        'BTN_TRIGGER_HAPPY34':                 0x2e1,
        'BTN_TRIGGER_HAPPY35':                 0x2e2,
        'BTN_TRIGGER_HAPPY36':                 0x2e3,
        'BTN_TRIGGER_HAPPY37':                 0x2e4,
        'BTN_TRIGGER_HAPPY38':                 0x2e5,
        'BTN_TRIGGER_HAPPY39':                 0x2e6,
        'BTN_TRIGGER_HAPPY40':                 0x2e7,
}
BUS = {
        'BUS_PCI': 0x01,
        'BUS_ISAPNP': 0x02,
        'BUS_USB': 0x03,
        'BUS_HIL': 0x04,
        'BUS_BLUETOOTH': 0x05,
        'BUS_ISA': 0x10,
        'BUS_I8042': 0x11,
        'BUS_XTKBD': 0x12,
        'BUS_RS232': 0x13,
        'BUS_GAMEPORT': 0x14,
        'BUS_PARPORT': 0x15,
        'BUS_AMIGA': 0x16,
        'BUS_ADB': 0x17,
        'BUS_I2C': 0x18,
        'BUS_HOST': 0x19,
        'BUS_GSC': 0x1A,
        'BUS_ATARI': 0x1B,
        'BUS_SPI':   0x1C,
}
ABS = {
        'ABS_X': 0x00,
        'ABS_Y': 0x01,
        'ABS_Z': 0x02,
        'ABS_RX': 0x03,
        'ABS_RY': 0x04,
        'ABS_RZ': 0x05,
        'ABS_THROTTLE': 0x06,
        'ABS_RUDDER': 0x07,
        'ABS_WHEEL': 0x08,
        'ABS_GAS': 0x09,
        'ABS_BRAKE': 0x0a,
        'ABS_HAT0X': 0x10,
        'ABS_HAT0Y': 0x11,
        'ABS_HAT1X': 0x12,
        'ABS_HAT1Y': 0x13,
        'ABS_HAT2X': 0x14,
        'ABS_HAT2Y': 0x15,
        'ABS_HAT3X': 0x16,
        'ABS_HAT3Y': 0x17,
        'ABS_PRESSURE': 0x18,
        'ABS_DISTANCE': 0x19,
        'ABS_TILT_X': 0x1a,
        'ABS_TILT_Y': 0x1b,
        'ABS_TOOL_WIDTH': 0x1c,
        'ABS_VOLUME': 0x20,
        'ABS_MISC': 0x28,
        'ABS_MT_SLOT':        0x2f,
        'ABS_MT_TOUCH_MAJOR': 0x30,
        'ABS_MT_TOUCH_MINOR': 0x31,
        'ABS_MT_WIDTH_MAJOR': 0x32,
        'ABS_MT_WIDTH_MINOR': 0x33,
        'ABS_MT_ORIENTATION': 0x34,
        'ABS_MT_POSITION_X':  0x35,
        'ABS_MT_POSITION_Y':  0x36,
        'ABS_MT_TOOL_TYPE':   0x37,
        'ABS_MT_BLOB_ID':     0x38,
        'ABS_MT_TRACKING_ID': 0x39,
        'ABS_MT_PRESSURE':    0x3a,
        'ABS_MT_DISTANCE':    0x3b,
}
LED = {
        'LED_NUML': 0x00,
        'LED_CAPSL': 0x01,
        'LED_SCROLLL': 0x02,
        'LED_COMPOSE': 0x03,
        'LED_KANA': 0x04,
        'LED_SLEEP': 0x05,
        'LED_SUSPEND': 0x06,
        'LED_MUTE': 0x07,
        'LED_MISC': 0x08,
        'LED_MAIL': 0x09,
        'LED_CHARGING': 0x0a,
}

def i(x):
    return x - 2**32
EVIOCGVERSION        = i(0x80044501)
EVIOCGID             = i(0x80084502)
EVIOCGREP            = i(0x80084503)
EVIOCSREP            = 0x40084503
EVIOCGKEYCODE        = i(0x80084504)
EVIOCSKEYCODE        = 0x40084504
EVIOCGNAME           = i(0x80004506)
EVIOCGPHYS           = i(0x80004507)
EVIOCGUNIQ           = i(0x80004508)
EVIOCGKEY            = i(0x80004518)
EVIOCGLED            = i(0x80004519)
EVIOCGSND            = i(0x8000451a)
EVIOCGBIT            = i(0x80004520)
EVIOCGABS            = i(0x80144540)
EVIOCSABS            = 0x401445c0
EVIOCSFF             = 0x402c4580
EVIOCRMFF            = 0x40044581
EVIOCGEFFECTS        = i(0x80044584)
EVIOCGRAB            = 0x40044590

def invert(d):
    return dict((v,k) for k,v in d.items())
ABS_invert = invert(ABS)
BTN_invert = invert(BTN)
BUS_invert = invert(BUS)
EV_invert = invert(EV)
KEY_invert = invert(KEY)
LED_invert = invert(LED)
REL_invert = invert(REL)
del invert
KEYBTN_invert = KEY_invert.copy()
KEYBTN_invert.update(BTN_invert)

def humanize(s):
    s = re.split("(\d+)", s)
    def maybe_int(ss):
        if ss and ss[0] in "0123456789": return int(ss)
        return ss
    return list(map(maybe_int, s))

def find(pattern):
    if os.path.exists(pattern):
        return os.open(pattern, os.O_RDWR)
    if os.path.exists("/dev/input/event%s" % pattern):
        return os.open("/dev/input/event%s" % pattern, os.O_RDWR)
    if ":" in pattern:
        pattern, idx = pattern.rsplit(":", 1)
        idx = int(idx)
    else:
        idx = 0

    candidates = glob.glob("/dev/input/event*")
    candidates.sort(key=humanize)
    successful_opens = 0
    for c in candidates:
        try:
            f = os.open(c, os.O_RDWR)
        except os.error:
            continue
        successful_opens += 1

        name = get_name(f)
        print(f"note: name is {repr(name)}")
        if name.find(pattern) != -1 or fnmatch.fnmatch(name, pattern):
            if idx == 0:
                return f
            else:
                idx -= 1
                continue

        try:
            phys = get_phys(f)
        except IOError:
            pass
        else:
            if phys.find(pattern) != -1 or fnmatch.fnmatch(phys, pattern):
                if idx == 0:
                    return f
                else:
                    idx -= 1
                    continue

        id = InputId.get(f)
        sid = "Bus=%s Vendor=%04x Product=%04x Version=%04x" % (\
            id.bustype, id.vendor, id.product, id.version)
        if sid.find(pattern) != -1 or fnmatch.fnmatch(sid, pattern):
            if idx == 0:
                return f
            else:
                idx -= 1
                continue

        sid = "%04x:%04x" % (id.vendor, id.product)
        if sid.find(pattern) != -1 or fnmatch.fnmatch(sid, pattern):
            if idx == 0:
                return f
            else:
                idx -= 1
                continue

        os.close(f)
    if not successful_opens:
        raise LookupError("""\
No input devices could be opened.  This usually indicates a misconfigured
system.  Please read the section 'PERMISSIONS AND UDEV' in the hal_input
manpage""")
    raise LookupError(
        "No input device matching %r was found (%d devices checked)" 
            % (pattern, successful_opens))

def decode(map, mapname, code):
    if isinstance(code, str): return code
    if code in map: return map[code]
    return "%s_%s" % (mapname, code)

class InputDevice:
    def __init__(self, pattern):
        if pattern.startswith("+"):
            exclusive = 1
            pattern = pattern[1:]
        else:
            exclusive = 0

        self.f = find(pattern)

        if exclusive:
            fcntl.ioctl(self.f, EVIOCGRAB, 1)
            
    def fileno(self): return self.f
    def readable(self):
        r, w, x = select.select([self.f], [], [], 0)
        return self.f in r

    def get_bits(self, arg): return get_bits(self.f, arg)
    def get_absinfo(self, arg): return AbsInfo.get(self.f, arg)
    def read_event(self):
        e = Event.read(self.f)
        if e.type == 'EV_KEY': e.code = decode(KEYBTN_invert, 'KEY', e.code)
        elif e.type == 'EV_ABS': e.code = decode(ABS_invert, 'ABS', e.code)
        elif e.type == 'EV_REL': e.code = decode(REL_invert, 'REL', e.code)
        elif e.type == 'EV_LED': e.code = decode(LED_invert, 'LED', e.code)
        return e

    def write_event(self, *args):
        Event.write(self.f, *args)
