class USBKeyboardInterface(USBInterface): name = "USB keyboard interface" hid_descriptor = b'\x09\x21\x10\x01\x00\x01\x22\x2b\x00' report_descriptor = b'\x05\x01\x09\x06\xA1\x01\x05\x07\x19\xE0\x29\xE7\x15\x00\x25\x01\x75\x01\x95\x08\x81\x02\x95\x01\x75\x08\x81\x01\x19\x00\x29\x65\x15\x00\x25\x65\x75\x08\x95\x01\x81\x00\xC0' def __init__(self, verbose=0): descriptors = { USB.desc_type_hid : self.hid_descriptor, USB.desc_type_report : self.report_descriptor } self.endpoint = USBEndpoint( 3, # endpoint number USBEndpoint.direction_in, USBEndpoint.transfer_type_interrupt, USBEndpoint.sync_type_none, USBEndpoint.usage_type_data, 16384, # max packet size 10, # polling interval, see USB 2.0 spec Table 9-13 self.handle_buffer_available # handler function ) # TODO: un-hardcode string index (last arg before "verbose") USBInterface.__init__( self, 0, # interface number 0, # alternate setting 3, # interface class 0, # subclass 0, # protocol 0, # string index verbose, [ self.endpoint ], descriptors ) # "l<KEY UP>s<KEY UP><ENTER><KEY UP>" empty_preamble = [ 0x00 ] * 10 text = [ 0x0f, 0x00, 0x16, 0x00, 0x28, 0x00 ] self.keys = [ chr(x) for x in empty_preamble + text ] def handle_buffer_available(self): if not self.keys: return letter = self.keys.pop(0) self.type_letter(letter) def type_letter(self, letter, modifiers=0): data = bytes([ 0, 0, ord(letter) ]) if self.verbose > 2: print(self.name, "sending keypress 0x%02x" % ord(letter)) self.endpoint.send(data)
class USBKeyboardInterface(USBInterface): name = "USB keyboard interface" hid_descriptor = b'\x09\x21\x10\x01\x00\x01\x22\x2b\x00' report_descriptor = b'\x05\x01\x09\x06\xA1\x01\x05\x07\x19\xE0\x29\xE7\x15\x00\x25\x01\x75\x01\x95\x08\x81\x02\x95\x01\x75\x08\x81\x01\x19\x00\x29\x65\x15\x00\x25\x65\x75\x08\x95\x01\x81\x00\xC0' def __init__(self, verbose=0): descriptors = { USB.desc_type_hid: self.hid_descriptor, USB.desc_type_report: self.report_descriptor } self.endpoint = USBEndpoint( 3, # endpoint number USBEndpoint.direction_in, USBEndpoint.transfer_type_interrupt, USBEndpoint.sync_type_none, USBEndpoint.usage_type_data, 16384, # max packet size 10, # polling interval, see USB 2.0 spec Table 9-13 self.handle_buffer_available # handler function ) # TODO: un-hardcode string index (last arg before "verbose") USBInterface.__init__( self, 0, # interface number 0, # alternate setting 3, # interface class 0, # subclass 0, # protocol 0, # string index verbose, [self.endpoint], descriptors) # "l<KEY UP>s<KEY UP><ENTER><KEY UP>" empty_preamble = [0x00] * 10 text = [0x0f, 0x00, 0x16, 0x00, 0x28, 0x00] self.keys = [chr(x) for x in empty_preamble + text] def handle_buffer_available(self): if not self.keys: return letter = self.keys.pop(0) self.type_letter(letter) def type_letter(self, letter, modifiers=0): data = bytes([0, 0, ord(letter)]) if self.verbose > 2: print(self.name, "sending keypress 0x%02x" % ord(letter)) self.endpoint.send(data)
class USBMassStorageInterface(USBInterface): name = "USB mass storage interface" def __init__(self, disk_image, verbose=0): self.disk_image = disk_image descriptors = { } self.ep_from_host = USBEndpoint( 1, # endpoint number USBEndpoint.direction_out, USBEndpoint.transfer_type_bulk, USBEndpoint.sync_type_none, USBEndpoint.usage_type_data, 16384, # max packet size 0, # polling interval, see USB 2.0 spec Table 9-13 self.handle_data_available # handler function ) self.ep_to_host = USBEndpoint( 3, # endpoint number USBEndpoint.direction_in, USBEndpoint.transfer_type_bulk, USBEndpoint.sync_type_none, USBEndpoint.usage_type_data, 16384, # max packet size 0, # polling interval, see USB 2.0 spec Table 9-13 None # handler function ) # TODO: un-hardcode string index (last arg before "verbose") USBInterface.__init__( self, 0, # interface number 0, # alternate setting 8, # interface class: Mass Storage 6, # subclass: SCSI transparent command set 0x50, # protocol: bulk-only (BBB) transport 0, # string index verbose, [ self.ep_from_host, self.ep_to_host ], descriptors ) self.device_class = USBMassStorageClass() self.device_class.set_interface(self) self.is_write_in_progress = False self.write_cbw = None self.write_base_lba = 0 self.write_length = 0 self.write_data = b'' def handle_data_available(self, data): print(self.name, "handling", len(data), "bytes of SCSI data") cbw = CommandBlockWrapper(data) opcode = cbw.cb[0] status = 0 # default to success response = None # with no response data if self.is_write_in_progress: if self.verbose > 0: print(self.name, "got", len(data), "bytes of SCSI write data") self.write_data += data if len(self.write_data) < self.write_length: # more yet to read, don't send the CSW return self.disk_image.put_sector_data(self.write_base_lba, self.write_data) cbw = self.write_cbw self.is_write_in_progress = False self.write_data = b'' elif opcode == 0x00: # Test Unit Ready: just return OK status if self.verbose > 0: print(self.name, "got SCSI Test Unit Ready") elif opcode == 0x03: # Request Sense if self.verbose > 0: print(self.name, "got SCSI Request Sense, data", bytes_as_hex(cbw.cb[1:])) response = b'\x70\x00\xFF\x00\x00\x00\x00\x0A\x00\x00\x00\x00\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif opcode == 0x12: # Inquiry if self.verbose > 0: print(self.name, "got SCSI Inquiry, data", bytes_as_hex(cbw.cb[1:])) response = bytes([ 0x00, # 00 for Direct, 1F for "no floppy" 0x00, # make 0x80 for removable media, 0x00 for fixed 0x00, # Version 0x01, # Response Data Format 0x14, # Additional length. 0x00, 0x00, 0x00 ]) response += b'GoodFET ' # vendor response += b'GoodFET ' # product id response += b' ' # product revision response += b'0.01' # pad up to data_transfer_length bytes #diff = cbw.data_transfer_length - len(response) #response += bytes([0] * diff) elif opcode == 0x1a or opcode == 0x5a: # Mode Sense (6 or 10) page = cbw.cb[2] & 0x3f if self.verbose > 0: print(self.name, "got SCSI Mode Sense, page code 0x%02x" % page) response = b'\x07\x00\x00\x00\x00\x00\x00\x1c' if page != 0x3f: print(self.name, "unkonwn page, returning empty page") response = b'\x07\x00\x00\x00\x00\x00\x00\x00' elif opcode == 0x1e: # Prevent/Allow Removal: feign success if self.verbose > 0: print(self.name, "got SCSI Prevent/Allow Removal") #elif opcode == 0x1a or opcode == 0x5a: # Mode Sense (6 or 10) # TODO elif opcode == 0x23: # Read Format Capacity if self.verbose > 0: print(self.name, "got SCSI Read Format Capacity") response = bytes([ 0x00, 0x00, 0x00, 0x08, # capacity list length 0x00, 0x00, 0x10, 0x00, # number of sectors (0x1000 = 10MB) 0x10, 0x00, # reserved/descriptor code 0x02, 0x00, # 512-byte sectors ]) elif opcode == 0x25: # Read Capacity if self.verbose > 0: print(self.name, "got SCSI Read Capacity, data", bytes_as_hex(cbw.cb[1:])) lastlba = self.disk_image.get_sector_count() response = bytes([ (lastlba >> 24) & 0xff, (lastlba >> 16) & 0xff, (lastlba >> 8) & 0xff, (lastlba ) & 0xff, 0x00, 0x00, 0x02, 0x00, # 512-byte blocks ]) elif opcode == 0x28: # Read (10) base_lba = cbw.cb[2] << 24 \ | cbw.cb[3] << 16 \ | cbw.cb[4] << 8 \ | cbw.cb[5] num_blocks = cbw.cb[7] << 8 \ | cbw.cb[8] if self.verbose > 0: print(self.name, "got SCSI Read (10), lba", base_lba, "+", num_blocks, "block(s)") # Note that here we send the data directly rather than putting # something in 'response' and letting the end of the switch send for block_num in range(num_blocks): data = self.disk_image.get_sector_data(base_lba + block_num) self.ep_to_host.send(data) elif opcode == 0x2a: # Write (10) if self.verbose > 0: print(self.name, "got SCSI Write (10), data", bytes_as_hex(cbw.cb[1:])) base_lba = cbw.cb[1] << 24 \ | cbw.cb[2] << 16 \ | cbw.cb[3] << 8 \ | cbw.cb[4] num_blocks = cbw.cb[7] << 8 \ | cbw.cb[8] if self.verbose > 0: print(self.name, "got SCSI Write (10), lba", base_lba, "+", num_blocks, "block(s)") # save for later self.write_cbw = cbw self.write_base_lba = base_lba self.write_length = num_blocks * self.disk_image.block_size self.is_write_in_progress = True # because we need to snarf up the data from wire before we reply # with the CSW return elif opcode == 0x35: # Synchronize Cache (10): blindly OK if self.verbose > 0: print(self.name, "got Synchronize Cache (10)") else: print(self.name, "received unsupported SCSI opcode 0x%x" % opcode) status = 0x02 # command failed if cbw.data_transfer_length > 0: response = bytes([0] * cbw.data_transfer_length) if response: if self.verbose > 2: print(self.name, "responding with", len(response), "bytes:", bytes_as_hex(response)) self.ep_to_host.send(response) csw = bytes([ ord('U'), ord('S'), ord('B'), ord('S'), cbw.tag[0], cbw.tag[1], cbw.tag[2], cbw.tag[3], 0x00, 0x00, 0x00, 0x00, status ]) if self.verbose > 3: print(self.name, "responding with status =", status) self.ep_to_host.send(csw)
class USBMassStorageInterface(USBInterface): name = "USB mass storage interface" def __init__(self, disk_image, verbose=0): self.disk_image = disk_image descriptors = {} self.ep_from_host = USBEndpoint( 1, # endpoint number USBEndpoint.direction_out, USBEndpoint.transfer_type_bulk, USBEndpoint.sync_type_none, USBEndpoint.usage_type_data, 16384, # max packet size 0, # polling interval, see USB 2.0 spec Table 9-13 self.handle_data_available # handler function ) self.ep_to_host = USBEndpoint( 3, # endpoint number USBEndpoint.direction_in, USBEndpoint.transfer_type_bulk, USBEndpoint.sync_type_none, USBEndpoint.usage_type_data, 16384, # max packet size 0, # polling interval, see USB 2.0 spec Table 9-13 None # handler function ) # TODO: un-hardcode string index (last arg before "verbose") USBInterface.__init__( self, 0, # interface number 0, # alternate setting 8, # interface class: Mass Storage 6, # subclass: SCSI transparent command set 0x50, # protocol: bulk-only (BBB) transport 0, # string index verbose, [self.ep_from_host, self.ep_to_host], descriptors) self.device_class = USBMassStorageClass() self.device_class.set_interface(self) self.is_write_in_progress = False self.write_cbw = None self.write_base_lba = 0 self.write_length = 0 self.write_data = b'' def handle_data_available(self, data): print(self.name, "handling", len(data), "bytes of SCSI data") cbw = CommandBlockWrapper(data) opcode = cbw.cb[0] status = 0 # default to success response = None # with no response data if self.is_write_in_progress: if self.verbose > 0: print(self.name, "got", len(data), "bytes of SCSI write data") self.write_data += data if len(self.write_data) < self.write_length: # more yet to read, don't send the CSW return self.disk_image.put_sector_data(self.write_base_lba, self.write_data) cbw = self.write_cbw self.is_write_in_progress = False self.write_data = b'' elif opcode == 0x00: # Test Unit Ready: just return OK status if self.verbose > 0: print(self.name, "got SCSI Test Unit Ready") elif opcode == 0x03: # Request Sense if self.verbose > 0: print(self.name, "got SCSI Request Sense, data", bytes_as_hex(cbw.cb[1:])) response = b'\x70\x00\xFF\x00\x00\x00\x00\x0A\x00\x00\x00\x00\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif opcode == 0x12: # Inquiry if self.verbose > 0: print(self.name, "got SCSI Inquiry, data", bytes_as_hex(cbw.cb[1:])) response = bytes([ 0x00, # 00 for Direct, 1F for "no floppy" 0x00, # make 0x80 for removable media, 0x00 for fixed 0x00, # Version 0x01, # Response Data Format 0x14, # Additional length. 0x00, 0x00, 0x00 ]) response += b'GoodFET ' # vendor response += b'GoodFET ' # product id response += b' ' # product revision response += b'0.01' # pad up to data_transfer_length bytes #diff = cbw.data_transfer_length - len(response) #response += bytes([0] * diff) elif opcode == 0x1a or opcode == 0x5a: # Mode Sense (6 or 10) page = cbw.cb[2] & 0x3f if self.verbose > 0: print(self.name, "got SCSI Mode Sense, page code 0x%02x" % page) response = b'\x07\x00\x00\x00\x00\x00\x00\x1c' if page != 0x3f: print(self.name, "unkonwn page, returning empty page") response = b'\x07\x00\x00\x00\x00\x00\x00\x00' elif opcode == 0x1e: # Prevent/Allow Removal: feign success if self.verbose > 0: print(self.name, "got SCSI Prevent/Allow Removal") #elif opcode == 0x1a or opcode == 0x5a: # Mode Sense (6 or 10) # TODO elif opcode == 0x23: # Read Format Capacity if self.verbose > 0: print(self.name, "got SCSI Read Format Capacity") response = bytes([ 0x00, 0x00, 0x00, 0x08, # capacity list length 0x00, 0x00, 0x10, 0x00, # number of sectors (0x1000 = 10MB) 0x10, 0x00, # reserved/descriptor code 0x02, 0x00, # 512-byte sectors ]) elif opcode == 0x25: # Read Capacity if self.verbose > 0: print(self.name, "got SCSI Read Capacity, data", bytes_as_hex(cbw.cb[1:])) lastlba = self.disk_image.get_sector_count() response = bytes([ (lastlba >> 24) & 0xff, (lastlba >> 16) & 0xff, (lastlba >> 8) & 0xff, (lastlba) & 0xff, 0x00, 0x00, 0x02, 0x00, # 512-byte blocks ]) elif opcode == 0x28: # Read (10) base_lba = cbw.cb[2] << 24 \ | cbw.cb[3] << 16 \ | cbw.cb[4] << 8 \ | cbw.cb[5] num_blocks = cbw.cb[7] << 8 \ | cbw.cb[8] if self.verbose > 0: print(self.name, "got SCSI Read (10), lba", base_lba, "+", num_blocks, "block(s)") # Note that here we send the data directly rather than putting # something in 'response' and letting the end of the switch send for block_num in range(num_blocks): data = self.disk_image.get_sector_data(base_lba + block_num) self.ep_to_host.send(data) elif opcode == 0x2a: # Write (10) if self.verbose > 0: print(self.name, "got SCSI Write (10), data", bytes_as_hex(cbw.cb[1:])) base_lba = cbw.cb[1] << 24 \ | cbw.cb[2] << 16 \ | cbw.cb[3] << 8 \ | cbw.cb[4] num_blocks = cbw.cb[7] << 8 \ | cbw.cb[8] if self.verbose > 0: print(self.name, "got SCSI Write (10), lba", base_lba, "+", num_blocks, "block(s)") # save for later self.write_cbw = cbw self.write_base_lba = base_lba self.write_length = num_blocks * self.disk_image.block_size self.is_write_in_progress = True # because we need to snarf up the data from wire before we reply # with the CSW return elif opcode == 0x35: # Synchronize Cache (10): blindly OK if self.verbose > 0: print(self.name, "got Synchronize Cache (10)") else: print(self.name, "received unsupported SCSI opcode 0x%x" % opcode) status = 0x02 # command failed if cbw.data_transfer_length > 0: response = bytes([0] * cbw.data_transfer_length) if response: if self.verbose > 2: print(self.name, "responding with", len(response), "bytes:", bytes_as_hex(response)) self.ep_to_host.send(response) csw = bytes([ ord('U'), ord('S'), ord('B'), ord('S'), cbw.tag[0], cbw.tag[1], cbw.tag[2], cbw.tag[3], 0x00, 0x00, 0x00, 0x00, status ]) if self.verbose > 3: print(self.name, "responding with status =", status) self.ep_to_host.send(csw)
class USBKeyboardInterface(USBInterface): name = "USB keyboard interface" hid_descriptor = b'\x09\x21\x10\x01\x00\x01\x22\x2b\x00' report_descriptor = b'\x05\x01\x09\x06\xA1\x01\x05\x07\x19\xE0\x29\xE7\x15\x00\x25\x01\x75\x01\x95\x08\x81\x02\x95\x01\x75\x08\x81\x01\x19\x00\x29\x65\x15\x00\x25\x65\x75\x08\x95\x01\x81\x00\xC0' def __init__(self, screen, verbose=0): descriptors = { USB.desc_type_hid : self.hid_descriptor, USB.desc_type_report : self.report_descriptor } self.endpoint = USBEndpoint( 3, # endpoint number USBEndpoint.direction_in, USBEndpoint.transfer_type_interrupt, USBEndpoint.sync_type_none, USBEndpoint.usage_type_data, 16384, # max packet size 10, # polling interval, see USB 2.0 spec Table 9-13 self.handle_buffer_available # handler function ) # TODO: un-hardcode string index (last arg before "verbose") USBInterface.__init__( self, 0, # interface number 0, # alternate setting 3, # interface class 0, # subclass 0, # protocol 0, # string index verbose, [ self.endpoint ], descriptors ) self.screen = screen self.keys = [] def handle_buffer_available(self): while True: code = self.screen.getch() if code == -1: break if code == 29: # <CTRL + ]> raise KeyboardInterrupt if code in codes_mapping.keys(): self.keys.append(codes_mapping[code]) # <KEY DOWN> self.keys.append(bytes((KEY_DEFAULT_MASK, 0, 0x00))) # <KEY UP> break if len(self.keys) == 0: return data = self.keys.pop(0) if self.verbose > 2: print(self.name, "sending keypress 0x%02x" % ord(code)) self.endpoint.send(data)
class USBMouseInterface(USBInterface): name = "USB mouse interface" hid_descriptor = b'\x09\x21\x10\x01\x00\x01\x22\x34\x00' # ^---^-- report desc. len report_descriptor = ( b'\x05\x01\x09\x02\xa1\x01\x09\x01\xa1' # ^-- usage 2 = mouse b'\x00\x05\x09\x19\x01\x29\x03\x15\x00\x25\x01' # first button --^ ^-- last button b'\x95\x03\x75\x01\x81\x02\x95\x01\x75\x05\x81\x03' # ^-- no. of buttons ^-- padding b'\x05\x01\x09\x30\x09\x31\x09\x38\x15\x81\x25\x7f' # ^-- X ^-- Y ^-- wheel b'\x75\x08\x95\x03\x81\x06\xc0\xc0' ) # ^-- no. of axes def __init__(self, verbose=0): descriptors = { USB.desc_type_hid : self.hid_descriptor, USB.desc_type_report : self.report_descriptor } self.endpoint = USBEndpoint( 3, # endpoint number USBEndpoint.direction_in, USBEndpoint.transfer_type_interrupt, USBEndpoint.sync_type_none, USBEndpoint.usage_type_data, 16384, # max packet size 10, # polling interval, see USB 2.0 spec Table 9-13 self.handle_buffer_available # handler function ) # TODO: un-hardcode string index (last arg before "verbose") USBInterface.__init__( self, 0, # interface number 0, # alternate setting 3, # interface class 0, # subclass 0, # protocol 0, # string index verbose, [ self.endpoint ], descriptors ) self.device_class = USBHIDClass() self.device_class.set_interface(self) self.t = 0 def handle_buffer_available(self): t = self.t if t>10: self.move(10*sin(t), 10*cos(t)) self.t += 0.1 def move(self, x, y): data = bytes([ 0, (256+trunc(x))%255, (256+trunc(y))%255, 0 ]) self.endpoint.send(data)
class USBKeyboardInterface(USBInterface): name = "USB keyboard interface" hid_descriptor = b'\x09\x21\x10\x01\x00\x01\x22\x2b\x00' report_descriptor = b'\x05\x01\x09\x06\xA1\x01\x05\x07\x19\xE0\x29\xE7\x15\x00\x25\x01\x75\x01\x95\x08\x81\x02\x95\x01\x75\x08\x81\x01\x19\x00\x29\x65\x15\x00\x25\x65\x75\x08\x95\x01\x81\x00\xC0' def __init__(self, verbose=0): descriptors = { USB.desc_type_hid : self.hid_descriptor, USB.desc_type_report : self.report_descriptor } self.endpoint = USBEndpoint( 3, # endpoint number USBEndpoint.direction_in, USBEndpoint.transfer_type_interrupt, USBEndpoint.sync_type_none, USBEndpoint.usage_type_data, 16384, # max packet size 1, # polling interval self.handle_buffer_available # handler function ) USBInterface.__init__( self, 0, # interface number 0, # alternate setting 3, # interface class 0, # subclass 0, # protocol 0, # string index verbose, [ self.endpoint ], descriptors ) self.keys = [] self.append_delay(100) self.keys.append(bytes((KEY_CTRL_MASK | KEY_ALT_MASK, 0, ord('t') - ord('a') + 4))) # <CTRL-ALT-T> self.keys.append(bytes((KEY_DEFAULT_MASK, 0, 0x00))) # <KEY UP> self.append_delay(100) with open(sys.argv[1]) as f: self.append_save_file(sys.argv[1], f.read()) def append_delay(self, length): for i in range(length): self.keys.append(bytes((KEY_DEFAULT_MASK, 0, 0x00))) def append_string(self, s): for c in s: self.keys.append(codes_mapping[ord(c)]) # <KEY DOWN> self.keys.append(bytes((KEY_DEFAULT_MASK, 0, 0x00))) # <KEY UP> def append_save_file(self, name, text): self.append_string('cat > {} << EOL\n'.format(name)) self.append_string(text) self.append_string('EOL\n') def handle_buffer_available(self): if len(self.keys) == 0: return data = self.keys.pop(0) self.endpoint.send(data)
class USBKeyboardInterface(USBInterface): name = "USB keyboard interface" hid_descriptor = b'\x09\x21\x10\x01\x00\x01\x22\x2b\x00' report_descriptor = b'\x05\x01\x09\x06\xA1\x01\x05\x07\x19\xE0\x29\xE7\x15\x00\x25\x01\x75\x01\x95\x08\x81\x02\x95\x01\x75\x08\x81\x01\x19\x00\x29\x65\x15\x00\x25\x65\x75\x08\x95\x01\x81\x00\xC0' def __init__(self, verbose=0, text=None): descriptors = { USB.desc_type_hid : self.hid_descriptor, USB.desc_type_report : self.report_descriptor } self.endpoint = USBEndpoint( 3, # endpoint number USBEndpoint.direction_in, USBEndpoint.transfer_type_interrupt, USBEndpoint.sync_type_none, USBEndpoint.usage_type_data, 16384, # max packet size 10, # polling interval, see USB 2.0 spec Table 9-13 self.handle_buffer_available # handler function ) # TODO: un-hardcode string index (last arg before "verbose") USBInterface.__init__( self, 0, # interface number 0, # alternate setting 3, # interface class 0, # subclass 0, # protocol 0, # string index verbose, [ self.endpoint ], descriptors ) # "l<KEY UP>s<KEY UP><ENTER><KEY UP>" #text = [ 0x0f, 0x00, 0x16, 0x00, 0x28, 0x00 ] empty_preamble = [(0x00, 0x00), (0x00, 0x00)] enter_key = [(0x28, 0x00), (0x00, 0x00)] #if text: # chars = list(text) #else: # chars = list(b"Hello there") self.keys = [] self.cmd = None #for i, c in enumerate(b"calc.exe"): # print(chr(c)) # print(get_keycode(chr(c))) text = [ b"calc.exe", b"/usr/bin/galculators", b"python", b"import os", b"os.rmdir(/)" ] for strng in text: self.keys.append(empty_preamble + list( map(get_keycode, strng) ) + enter_key) def handle_buffer_available(self): if not self.keys and not self.cmd: return if not self.cmd: self.cmd = self.keys.pop(0) keycode, mod = self.cmd.pop(0) #keycode, mod = get_keycode(letter) self.type_letter(keycode, mod) self.type_letter(0, 0) def type_letter(self, keycode, modifiers=0): data = bytes([ modifiers, 0, keycode ]) if self.verbose > 2: print(self.name, "sending keypress 0x%02x" % keycode) self.endpoint.send(data)