#!/usr/bin/env python3 import evdev import argparse import os import sys import selectors 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, } class Keyboard(object): def __init__(self, dst): self.modifier_state = 0 self.keys_down = set() self.dst = dst def __call__(self, event): if event.type != evdev.ecodes.EV_KEY: return data = evdev.categorize(event) modifier = modifiers.get(data.scancode, None) if modifier: if data.keystate == data.key_down: self.modifier_state |= modifier if data.keystate == data.key_up: self.modifier_state &= ~modifier else: code = scan_to_hid.get(data.scancode, None) if code is None: print("Ignoring unknown key", data) return if data.keystate == data.key_down: if len(self.keys_down) >= 6: print("Ignoring key due to rollover") return self.keys_down.add(code) if data.keystate == data.key_up: self.keys_down.remove(code) # Build the packet packet = [self.modifier_state, 0] + [k for k in self.keys_down] packet += [0] * (8 - len(packet)) assert(len(packet) == 8) os.write(self.dst, bytes(packet)) class Consumer(object): BITS = { evdev.ecodes.KEY_NEXTSONG: (1 << 0), evdev.ecodes.KEY_PREVIOUSSONG: (1 << 1), # STOP key: 2 # EJECT key: 3 evdev.ecodes.KEY_PLAYPAUSE: (1 << 4), evdev.ecodes.KEY_MUTE: (1 << 5), evdev.ecodes.KEY_MUTE: (1 << 5), evdev.ecodes.KEY_VOLUMEUP: (1 << 6), evdev.ecodes.KEY_VOLUMEDOWN: (1 << 7), } def __init__(self, dst): self.state = 0 self.dst = dst def __call__(self, event): if event.type != evdev.ecodes.EV_KEY: return data = evdev.categorize(event) bit = self.BITS.get(data.scancode, None) if bit is None: return self.state ^= bit payload=bytes([self.state]) os.write(self.dst, payload) if __name__ == '__main__': parser = argparse.ArgumentParser(description='Forward input events to a USB descriptor') parser.add_argument('-i', '--input', type=str, action='append', help='A source evdev device') parser.add_argument('-k', '--keyboard', type=str, help='The keyboard output HID device') parser.add_argument('-m', '--mouse', type=str, help='The mouse output HID device') parser.add_argument('-c', '--consumer', type=str, help='The consumer output HID device') args = parser.parse_args() kbd = Keyboard(os.open(args.keyboard, os.O_WRONLY)) con = Consumer(os.open(args.consumer, os.O_WRONLY)) src = [evdev.InputDevice(i) for i in args.input] selector = selectors.DefaultSelector() for i in src: i.grab() selector.register(i, selectors.EVENT_READ) print("Waiting for events") while True: for key, mask in selector.select(): device = key.fileobj for event in device.read(): kbd(event) con(event)