1from bumble.colors import color 2from hid_key_map import base_keys, mod_keys, shift_map 3 4 5# ------------------------------------------------------------------------------ 6def get_key(modifier: str, key: str) -> str: 7 if modifier == '22': 8 modifier = '02' 9 if modifier in mod_keys: 10 modifier = mod_keys[modifier] 11 else: 12 return '' 13 if key in base_keys: 14 key = base_keys[key] 15 else: 16 return '' 17 if (modifier == 'left_shift' or modifier == 'right_shift') and key in shift_map: 18 key = shift_map[key] 19 20 return key 21 22 23class Keyboard: 24 def __init__(self): # type: ignore 25 self.report = [ 26 [ # Bit array for Modifier keys 27 0, # Right GUI - (usually the Windows key) 28 0, # Right ALT 29 0, # Right Shift 30 0, # Right Control 31 0, # Left GUI - (usually the Windows key) 32 0, # Left ALT 33 0, # Left Shift 34 0, # Left Control 35 ], 36 0x00, # Vendor reserved 37 '', # Rest is space for 6 keys 38 '', 39 '', 40 '', 41 '', 42 '', 43 ] 44 45 def decode_keyboard_report(self, input_report: bytes, report_length: int) -> None: 46 if report_length >= 8: 47 modifier = input_report[1] 48 self.report[0] = [int(x) for x in '{0:08b}'.format(modifier)] 49 self.report[0].reverse() # type: ignore 50 51 modifier_key = str((modifier & 0x22).to_bytes(1, "big").hex()) 52 keycodes = [] 53 for k in range(3, report_length): 54 keycodes.append(str(input_report[k].to_bytes(1, "big").hex())) 55 self.report[k - 1] = get_key(modifier_key, keycodes[k - 3]) 56 else: 57 print(color('Warning: Not able to parse report', 'yellow')) 58 59 def print_keyboard_report(self) -> None: 60 print(color('\tKeyboard Input Received', 'green', None, 'bold')) 61 print(color(f'Keys:', 'white', None, 'bold')) 62 for i in range(1, 7): 63 print( 64 color(f' Key{i}{" ":>8s}= ', 'cyan', None, 'bold'), self.report[i + 1] 65 ) 66 print(color(f'\nModifier Keys:', 'white', None, 'bold')) 67 print( 68 color(f' Left Ctrl : ', 'cyan'), 69 f'{self.report[0][0] == 1!s:<5}', # type: ignore 70 color(f' Left Shift : ', 'cyan'), 71 f'{self.report[0][1] == 1!s:<5}', # type: ignore 72 color(f' Left ALT : ', 'cyan'), 73 f'{self.report[0][2] == 1!s:<5}', # type: ignore 74 color(f' Left GUI : ', 'cyan'), 75 f'{self.report[0][3] == 1!s:<5}\n', # type: ignore 76 color(f' Right Ctrl : ', 'cyan'), 77 f'{self.report[0][4] == 1!s:<5}', # type: ignore 78 color(f' Right Shift : ', 'cyan'), 79 f'{self.report[0][5] == 1!s:<5}', # type: ignore 80 color(f' Right ALT : ', 'cyan'), 81 f'{self.report[0][6] == 1!s:<5}', # type: ignore 82 color(f' Right GUI : ', 'cyan'), 83 f'{self.report[0][7] == 1!s:<5}', # type: ignore 84 ) 85 86 87# ------------------------------------------------------------------------------ 88class Mouse: 89 def __init__(self): # type: ignore 90 self.report = [ 91 [ # Bit array for Buttons 92 0, # Button 1 (primary/trigger 93 0, # Button 2 (secondary) 94 0, # Button 3 (tertiary) 95 0, # Button 4 96 0, # Button 5 97 0, # unused padding bits 98 0, # unused padding bits 99 0, # unused padding bits 100 ], 101 0, # X 102 0, # Y 103 0, # Wheel 104 0, # AC Pan 105 ] 106 107 def decode_mouse_report(self, input_report: bytes, report_length: int) -> None: 108 self.report[0] = [int(x) for x in '{0:08b}'.format(input_report[1])] 109 self.report[0].reverse() # type: ignore 110 self.report[1] = input_report[2] 111 self.report[2] = input_report[3] 112 if report_length in [5, 6]: 113 self.report[3] = input_report[4] 114 self.report[4] = input_report[5] if report_length == 6 else 0 115 116 def print_mouse_report(self) -> None: 117 print(color('\tMouse Input Received', 'green', None, 'bold')) 118 print( 119 color(f' Button 1 (primary/trigger) = ', 'cyan'), 120 self.report[0][0] == 1, # type: ignore 121 color(f'\n Button 2 (secondary) = ', 'cyan'), 122 self.report[0][1] == 1, # type: ignore 123 color(f'\n Button 3 (tertiary) = ', 'cyan'), 124 self.report[0][2] == 1, # type: ignore 125 color(f'\n Button4 = ', 'cyan'), 126 self.report[0][3] == 1, # type: ignore 127 color(f'\n Button5 = ', 'cyan'), 128 self.report[0][4] == 1, # type: ignore 129 color(f'\n X (X-axis displacement) = ', 'cyan'), 130 self.report[1], 131 color(f'\n Y (Y-axis displacement) = ', 'cyan'), 132 self.report[2], 133 color(f'\n Wheel = ', 'cyan'), 134 self.report[3], 135 color(f'\n AC PAN = ', 'cyan'), 136 self.report[4], 137 ) 138 139 140# ------------------------------------------------------------------------------ 141class ReportParser: 142 @staticmethod 143 def parse_input_report(input_report: bytes) -> None: 144 145 report_id = input_report[0] # pylint: disable=unsubscriptable-object 146 report_length = len(input_report) 147 148 # Keyboard input report (report id = 1) 149 if report_id == 1 and report_length >= 8: 150 keyboard = Keyboard() # type: ignore 151 keyboard.decode_keyboard_report(input_report, report_length) 152 keyboard.print_keyboard_report() 153 # Mouse input report (report id = 2) 154 elif report_id == 2 and report_length in [4, 5, 6]: 155 mouse = Mouse() # type: ignore 156 mouse.decode_mouse_report(input_report, report_length) 157 mouse.print_mouse_report() 158 else: 159 print(color(f'Warning: Parse Error Report ID {report_id}', 'yellow')) 160