#!/usr/bin/env python3 import evdev import argparse import os import sys scan_to_hid = { # Reserved: 0 # ErrorRollOver: 1 # POSTFail: 2 # ErrorUndefined: 3 evdev.ecodes.KEY_A: 0x04, evdev.ecodes.KEY_B: 0x05, evdev.ecodes.KEY_C: 0x06, evdev.ecodes.KEY_D: 0x07, evdev.ecodes.KEY_E: 0x08, evdev.ecodes.KEY_F: 0x09, evdev.ecodes.KEY_G: 0x0a, evdev.ecodes.KEY_H: 0x0b, evdev.ecodes.KEY_I: 0x0c, evdev.ecodes.KEY_J: 0x0d, evdev.ecodes.KEY_K: 0x0e, evdev.ecodes.KEY_L: 0x0f, evdev.ecodes.KEY_M: 0x10, evdev.ecodes.KEY_N: 0x11, evdev.ecodes.KEY_O: 0x12, evdev.ecodes.KEY_P: 0x13, evdev.ecodes.KEY_Q: 0x14, evdev.ecodes.KEY_R: 0x15, evdev.ecodes.KEY_S: 0x16, evdev.ecodes.KEY_T: 0x17, evdev.ecodes.KEY_U: 0x18, evdev.ecodes.KEY_V: 0x19, evdev.ecodes.KEY_W: 0x1a, evdev.ecodes.KEY_X: 0x1b, evdev.ecodes.KEY_Y: 0x1c, evdev.ecodes.KEY_Z: 0x1d, evdev.ecodes.KEY_1: 0x1e, evdev.ecodes.KEY_2: 0x1f, evdev.ecodes.KEY_3: 0x20, evdev.ecodes.KEY_4: 0x21, evdev.ecodes.KEY_5: 0x22, evdev.ecodes.KEY_6: 0x23, evdev.ecodes.KEY_7: 0x24, evdev.ecodes.KEY_8: 0x25, evdev.ecodes.KEY_9: 0x26, evdev.ecodes.KEY_0: 0x27, evdev.ecodes.KEY_ENTER: 0x28, evdev.ecodes.KEY_ESC: 0x29, evdev.ecodes.KEY_BACKSPACE: 0x2a, evdev.ecodes.KEY_TAB: 0x2b, evdev.ecodes.KEY_SPACE: 0x2c, evdev.ecodes.KEY_MINUS: 0x2d, evdev.ecodes.KEY_EQUAL: 0x2e, evdev.ecodes.KEY_LEFTBRACE: 0x2f, evdev.ecodes.KEY_RIGHTBRACE: 0x30, evdev.ecodes.KEY_BACKSLASH: 0x31, # Non-US # and ~: 0x32, evdev.ecodes.KEY_SEMICOLON: 0x33, evdev.ecodes.KEY_APOSTROPHE: 0x34, evdev.ecodes.KEY_GRAVE: 0x35, evdev.ecodes.KEY_COMMA: 0x36, evdev.ecodes.KEY_DOT: 0x37, evdev.ecodes.KEY_SLASH: 0x38, evdev.ecodes.KEY_CAPSLOCK: 0x39, evdev.ecodes.KEY_F1: 0x3a, evdev.ecodes.KEY_F2: 0x3b, evdev.ecodes.KEY_F3: 0x3c, evdev.ecodes.KEY_F4: 0x3d, evdev.ecodes.KEY_F5: 0x3e, evdev.ecodes.KEY_F6: 0x3f, evdev.ecodes.KEY_F7: 0x40, evdev.ecodes.KEY_F8: 0x41, evdev.ecodes.KEY_F9: 0x42, evdev.ecodes.KEY_F10: 0x43, evdev.ecodes.KEY_F11: 0x44, evdev.ecodes.KEY_F12: 0x45, evdev.ecodes.KEY_PRINT: 0x46, evdev.ecodes.KEY_SCROLLLOCK: 0x47, evdev.ecodes.KEY_PAUSE: 0x48, evdev.ecodes.KEY_INSERT: 0x49, evdev.ecodes.KEY_HOME: 0x4a, evdev.ecodes.KEY_PAGEUP: 0x4b, evdev.ecodes.KEY_DELETE: 0x4c, evdev.ecodes.KEY_END: 0x4d, evdev.ecodes.KEY_PAGEDOWN: 0x4e, evdev.ecodes.KEY_RIGHT: 0x4f, evdev.ecodes.KEY_LEFT: 0x50, evdev.ecodes.KEY_DOWN: 0x51, evdev.ecodes.KEY_UP: 0x52, evdev.ecodes.KEY_NUMLOCK: 0x53, evdev.ecodes.KEY_KPSLASH: 0x54, evdev.ecodes.KEY_KPASTERISK: 0x55, evdev.ecodes.KEY_KPMINUS: 0x56, evdev.ecodes.KEY_KPPLUS: 0x57, evdev.ecodes.KEY_KPENTER: 0x58, evdev.ecodes.KEY_KP1: 0x59, evdev.ecodes.KEY_KP2: 0x5a, evdev.ecodes.KEY_KP3: 0x5b, evdev.ecodes.KEY_KP4: 0x5c, evdev.ecodes.KEY_KP5: 0x5d, evdev.ecodes.KEY_KP6: 0x5e, evdev.ecodes.KEY_KP7: 0x5f, evdev.ecodes.KEY_KP8: 0x60, evdev.ecodes.KEY_KP9: 0x61, evdev.ecodes.KEY_KP0: 0x62, evdev.ecodes.KEY_KPDOT: 0x63, # non-us / and |: 0x64, evdev.ecodes.KEY_APPSELECT: 0x65, evdev.ecodes.KEY_POWER: 0x66, evdev.ecodes.KEY_KPEQUAL: 0x67, evdev.ecodes.KEY_F13: 0x68, evdev.ecodes.KEY_F14: 0x69, evdev.ecodes.KEY_F15: 0x6a, evdev.ecodes.KEY_F16: 0x6b, evdev.ecodes.KEY_F17: 0x6c, evdev.ecodes.KEY_F18: 0x6d, evdev.ecodes.KEY_F19: 0x6e, evdev.ecodes.KEY_F20: 0x6f, evdev.ecodes.KEY_F21: 0x70, evdev.ecodes.KEY_F22: 0x71, evdev.ecodes.KEY_F23: 0x72, evdev.ecodes.KEY_F24: 0x73, # execute evdev.ecodes.KEY_HELP: 0x75, evdev.ecodes.KEY_MENU: 0x76, evdev.ecodes.KEY_SELECT: 0x77, evdev.ecodes.KEY_STOP: 0x78, evdev.ecodes.KEY_AGAIN: 0x79, evdev.ecodes.KEY_UNDO: 0x7a, evdev.ecodes.KEY_CUT: 0x7b, evdev.ecodes.KEY_COPY: 0x7c, evdev.ecodes.KEY_PASTE: 0x7d, evdev.ecodes.KEY_FIND: 0x7e, evdev.ecodes.KEY_MUTE: 0x7f, evdev.ecodes.KEY_VOLUMEUP: 0x80, evdev.ecodes.KEY_VOLUMEDOWN: 0x81, # locking caps # locking num # locking scroll evdev.ecodes.KEY_KPCOMMA: 0x85, evdev.ecodes.KEY_KPEQUAL: 0x86, # ... evdev.ecodes.KEY_SYSRQ: 0x9a, # ... } modifiers = { evdev.ecodes.KEY_LEFTCTRL: 1 << 0, evdev.ecodes.KEY_LEFTSHIFT: 1 << 1, evdev.ecodes.KEY_LEFTALT: 1 << 2, evdev.ecodes.KEY_LEFTMETA: 1 << 3, evdev.ecodes.KEY_RIGHTCTRL: 1 << 4, evdev.ecodes.KEY_RIGHTSHIFT: 1 << 5, evdev.ecodes.KEY_RIGHTALT: 1 << 6, evdev.ecodes.KEY_RIGHTMETA: 1 << 7, } if __name__ == '__main__': parser = argparse.ArgumentParser(description='Forward input events to a USB descriptor') parser.add_argument('input', type=str, help='The source evdev device') parser.add_argument('output', type=str, help='The output HID device') args = parser.parse_args() dst = os.open(args.output, os.O_WRONLY) src = evdev.InputDevice(args.input) src.grab() modifier_state = 0 keys_down = set() for event in src.read_loop(): if event.type != evdev.ecodes.EV_KEY: continue data = evdev.categorize(event) print("Got key", data.scancode) modifier = modifiers.get(data.scancode, None) if modifier: if data.keystate == data.key_down: modifier_state |= modifier if data.keystate == data.key_up: modifier_state &= ~modifier else: code = scan_to_hid.get(data.scancode, None) if code is None: print("Ignoring unknown key") continue if data.keystate == data.key_down: if len(keys_down) >= 6: print("Ignoring key due to rollover") continue print("Adding key") keys_down.add(code) if data.keystate == data.key_up: print("Removing key") keys_down.remove(code) # Build the packet packet = [modifier_state, 0] + [k for k in keys_down] packet += [0] * (8 - len(packet)) assert(len(packet) == 8) print("Sending", packet) os.write(dst, bytes(packet))