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