#!/usr/bin/env python3 import binascii import os import sys keyboard = { 'protocol': 1, # keyboard 'subclass': 1, # boot interface 'report_length': 8, 'report_desc': bytes([ 0x05, 0x01, # Usage Page (Generic Desktop Ctrls) 0x09, 0x06, # Usage (Keyboard) 0xA1, 0x01, # Collection (Application) # Control key bitmask 0x05, 0x07, # Usage Page (Kbrd/Keypad) 0x19, 0xE0, # Usage Minimum (0xE0) 0x29, 0xE7, # Usage Maximum (0xE7) 0x15, 0x00, # Logical Minimum (0) 0x25, 0x01, # Logical Maximum (1) 0x75, 0x01, # Report Size (1) 0x95, 0x08, # Report Count (8) 0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) # Reserved byte 0x95, 0x01, # Report Count (1) 0x75, 0x08, # Report Size (8) 0x81, 0x03, # Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) # LED status 0x95, 0x05, # Report Count (5) 0x75, 0x01, # Report Size (1) 0x05, 0x08, # Usage Page (LEDs) 0x19, 0x01, # Usage Minimum (Num Lock) 0x29, 0x05, # Usage Maximum (Kana) 0x91, 0x02, # Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) # Padding 0x95, 0x01, # Report Count (1) 0x75, 0x03, # Report Size (3) 0x91, 0x03, # Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) # 6 general buttons 0x95, 0x06, # Report Count (6) 0x75, 0x08, # Report Size (8) 0x15, 0x00, # Logical Minimum (0) 0x25, 0x65, # Logical Maximum (101) 0x05, 0x07, # Usage Page (Kbrd/Keypad) 0x19, 0x00, # Usage Minimum (0x00) 0x29, 0x9a, # Usage Maximum (0x9a) 0x81, 0x00, # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) 0xC0, # End Collection ]), } mouse = { 'protocol': 2, # mouse 'subclass': 1, # boot interface 'report_length': 4, 'report_desc': bytes([ 0x05, 0x01, # Usage Page (Generic Desktop Ctrls) 0x09, 0x02, # Usage (Mouse) 0xA1, 0x01, # Collection (Application) 0x09, 0x01, # Usage (Pointer) 0xA1, 0x00, # Collection (Physical) 0x05, 0x09, # Usage Page (Button) 0x19, 0x01, # Usage Minimum (0x01) 0x29, 0x05, # Usage Maximum (0x05) 0x15, 0x00, # Logical Minimum (0) 0x25, 0x01, # Logical Maximum (1) 0x95, 0x05, # Report Count (5) 0x75, 0x01, # Report Size (1) 0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0x95, 0x01, # Report Count (1) 0x75, 0x03, # Report Size (3) 0x81, 0x01, # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) 0x05, 0x01, # Usage Page (Generic Desktop Ctrls) 0x09, 0x30, # Usage (X) 0x09, 0x31, # Usage (Y) 0x09, 0x38, # Usage (Wheel) 0x15, 0x81, # Logical Minimum (-127) 0x25, 0x7F, # Logical Maximum (127) 0x75, 0x08, # Report Size (8) 0x95, 0x03, # Report Count (3) 0x81, 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position) 0xC0, # End Collection 0xC0, # End Collection ]) } media = { 'protocol': 1, # Keyboard 'subclass': 0, # None 'report_length': 1, 'report_desc': bytes([ 0x05, 0x0C, # Usage Page (Consumer) 0x09, 0x01, # Usage (Consumer Control) 0xA1, 0x01, # Collection (Application) 0x05, 0x0C, # Usage Page (Consumer) 0x15, 0x00, # Logical Minimum (0) 0x25, 0x01, # Logical Maximum (1) 0x75, 0x01, # Report Size (1) 0x95, 0x08, # Report Count (8) 0x09, 0xB5, # Usage (Scan Next Track) 0x09, 0xB6, # Usage (Scan Previous Track) 0x09, 0xB7, # Usage (Stop) 0x09, 0xB8, # Usage (Eject) 0x09, 0xCD, # Usage (Play/Pause) 0x09, 0xE2, # Usage (Mute) 0x09, 0xE9, # Usage (Volume Increment) 0x09, 0xEA, # Usage (Volume Decrement) 0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0xC0, # End Collection ]) } try: UDC = os.listdir('/sys/class/udc') if len(UDC) != 1: print("Expected a single entry in '/sys/class/udc'") sys.exit(1) UDC = UDC[0] except FileNotFoundError: print ("Unable to query UDC") UDC='foo' #sys.exit (1) files = { 'idVendor': '0x1d6b', # Linux Foundation 'idProduct': '0x0104', # Multifunction Composite Gadget 'bcdDevice': '0x0100', # v1.0.0 'bcdUSB': '0x0200', 'strings': { '0x409': { 'serialnumber': "fedcba9876543210", 'manufacturer': "Quimby", 'product': "Virtual Keyboard", } }, 'configs': { 'c.1': { 'strings': { '0x0409': { 'configuration': 'Config 1: ECM network' } }, 'MaxPower': 250 } }, 'functions': { 'hid.usb0': keyboard, 'hid.usb1': mouse, 'hid.usb2': media, } } name = 'quimby' root = os.path.join('/sys/kernel/config/usb_gadget/', name) try: os.mkdir(root) except FileExistsError: pass def serialise(root, tree): for key,val in tree.items(): child = os.path.join(root, key) if isinstance(val, dict): try: os.mkdir(child) except FileExistsError: pass serialise(child, val) else: with open(child, 'wb') as dst: print(child,val) if not isinstance(val, bytes): val = bytes(str(val).encode()) dst.write(val) serialise(root, files) # Link the functions in for key in files['functions'].keys(): src = os.path.join(root, 'functions', key) dst = os.path.join(root, 'configs', 'c.1', key) os.symlink(src, dst) # UDC needs to be written last with open(f"{root}/UDC", 'w') as dst: dst.write(UDC)