Skip to content

Commit 727007f

Browse files
committed
Enables support for multiple keyboard devices
Adds support for combining keyboard input from multiple USB HID devices and a remote UART device. This ensures all key presses are captured and sent, regardless of the input source. It also resolves potential issues when switching between keyboards. This is achieved by storing the keyboard state for each device and merging these states into a combined report before sending.
1 parent 6ef3d7b commit 727007f

File tree

7 files changed

+137
-8
lines changed

7 files changed

+137
-8
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
build
2+
3+
.idea/

src/handlers.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,17 @@ void config_enable_hotkey_handler(device_t *state, hid_keyboard_report_t *report
150150

151151
/* Function handles received keypresses from the other board */
152152
void handle_keyboard_uart_msg(uart_packet_t *packet, device_t *state) {
153-
queue_kbd_report((hid_keyboard_report_t *)packet->data, state);
153+
hid_keyboard_report_t *report = (hid_keyboard_report_t *)packet->data;
154+
155+
/* Update the keyboard state for the remote device (using MAX_DEVICES-1 as the index) */
156+
update_kbd_state(state, report, MAX_DEVICES-1);
157+
158+
/* Create a combined report from all device states */
159+
hid_keyboard_report_t combined_report;
160+
combine_kbd_states(state, &combined_report);
161+
162+
/* Queue the combined report */
163+
queue_kbd_report(&combined_report, state);
154164
state->last_activity[BOARD_ROLE] = time_us_64();
155165
}
156166

src/include/keyboard.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ int32_t extract_kbd_data(uint8_t *, int, uint8_t, hid_interface_t *, hid_keyboa
2727

2828
bool check_specific_hotkey(hotkey_combo_t, const hid_keyboard_report_t *);
2929

30+
/*==============================================================================
31+
* Keyboard State Management
32+
*==============================================================================*/
33+
void update_kbd_state(device_t *, hid_keyboard_report_t *, uint8_t);
34+
void combine_kbd_states(device_t *, hid_keyboard_report_t *);
35+
3036
/*==============================================================================
3137
* Keyboard Report Processing
3238
*==============================================================================*/

src/include/structs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ typedef struct {
100100
uint8_t active_output; // Currently selected output (0 = A, 1 = B)
101101
uint8_t board_role; // Which board are we running on? (0 = A, 1 = B, etc.)
102102

103+
// Track keyboard state for each device
104+
hid_keyboard_report_t kbd_states[MAX_DEVICES]; // Store keyboard state for each device
105+
uint8_t kbd_device_count; // Number of active keyboard devices
106+
103107
int16_t pointer_x; // Store and update the location of our mouse pointer
104108
int16_t pointer_y;
105109
int16_t mouse_buttons; // Store and update the state of mouse buttons

src/keyboard.c

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,55 @@ hotkey_combo_t *check_all_hotkeys(hid_keyboard_report_t *report, device_t *state
143143
return NULL;
144144
}
145145

146+
/* ==================================================== *
147+
* Keyboard State Management
148+
* ==================================================== */
149+
150+
/* Update the keyboard state for a specific device */
151+
void update_kbd_state(device_t *state, hid_keyboard_report_t *report, uint8_t device_idx) {
152+
/* Ensure device_idx is within bounds */
153+
if (device_idx >= MAX_DEVICES)
154+
return;
155+
156+
/* Ensure local devices never use the last slot, which is reserved for the remote device */
157+
if (device_idx == MAX_DEVICES-1 && device_idx != 0) {
158+
/* Use the previous slot instead */
159+
device_idx = MAX_DEVICES-2;
160+
}
161+
162+
/* Update the keyboard state for this device */
163+
memcpy(&state->kbd_states[device_idx], report, sizeof(hid_keyboard_report_t));
164+
165+
/* Ensure kbd_device_count is at least device_idx + 1 */
166+
if (state->kbd_device_count <= device_idx)
167+
state->kbd_device_count = device_idx + 1;
168+
}
169+
170+
/* Combine keyboard states from all devices into a single report */
171+
void combine_kbd_states(device_t *state, hid_keyboard_report_t *combined_report) {
172+
/* Initialize combined report */
173+
memset(combined_report, 0, sizeof(hid_keyboard_report_t));
174+
175+
/* Combine modifiers and keys from all devices */
176+
for (uint8_t i = 0; i < state->kbd_device_count; i++) {
177+
/* Combine modifiers with OR operation */
178+
combined_report->modifier |= state->kbd_states[i].modifier;
179+
180+
/* Add keys from this device to the combined report */
181+
for (uint8_t j = 0; j < KEYS_IN_USB_REPORT; j++) {
182+
if (state->kbd_states[i].keycode[j] != 0) {
183+
/* Find an empty slot in the combined report */
184+
for (uint8_t k = 0; k < KEYS_IN_USB_REPORT; k++) {
185+
if (combined_report->keycode[k] == 0) {
186+
combined_report->keycode[k] = state->kbd_states[i].keycode[j];
187+
break;
188+
}
189+
}
190+
}
191+
}
192+
}
193+
}
194+
146195
/* ==================================================== *
147196
* Keyboard Queue Section
148197
* ==================================================== */
@@ -184,16 +233,29 @@ void queue_kbd_report(hid_keyboard_report_t *report, device_t *state) {
184233

185234
void release_all_keys(device_t *state) {
186235
static hid_keyboard_report_t no_keys_pressed_report = {0, 0, {0}};
236+
237+
/* Clear keyboard states for all devices */
238+
for (uint8_t i = 0; i < state->kbd_device_count; i++) {
239+
memset(&state->kbd_states[i], 0, sizeof(hid_keyboard_report_t));
240+
}
241+
242+
/* Send a report with no keys pressed */
187243
queue_try_add(&state->kbd_queue, &no_keys_pressed_report);
188244
}
189245

190246
/* If keys need to go locally, queue packet to kbd queue, else send them through UART */
191247
void send_key(hid_keyboard_report_t *report, device_t *state) {
248+
/* Create a combined report from all device states */
249+
hid_keyboard_report_t combined_report;
250+
combine_kbd_states(state, &combined_report);
251+
192252
if (CURRENT_BOARD_IS_ACTIVE_OUTPUT) {
193-
queue_kbd_report(report, state);
253+
/* Queue the combined report */
254+
queue_kbd_report(&combined_report, state);
194255
state->last_activity[BOARD_ROLE] = time_us_64();
195256
} else {
196-
queue_packet((uint8_t *)report, KEYBOARD_REPORT_MSG, KBD_REPORT_LENGTH);
257+
/* Send the combined report to ensure all keys are included */
258+
queue_packet((uint8_t *)&combined_report, KEYBOARD_REPORT_MSG, KBD_REPORT_LENGTH);
197259
}
198260
}
199261

@@ -235,6 +297,9 @@ void process_keyboard_report(uint8_t *raw_report, int length, uint8_t itf, hid_i
235297

236298
extract_kbd_data(raw_report, length, itf, iface, &new_report);
237299

300+
/* Update the keyboard state for this device */
301+
update_kbd_state(state, &new_report, itf);
302+
238303
/* Check if any hotkey was pressed */
239304
hotkey = check_all_hotkeys(&new_report, state);
240305

@@ -259,6 +324,7 @@ void process_keyboard_report(uint8_t *raw_report, int length, uint8_t itf, hid_i
259324
void process_consumer_report(uint8_t *raw_report, int length, uint8_t itf, hid_interface_t *iface) {
260325
uint8_t new_report[CONSUMER_CONTROL_LENGTH] = {0};
261326
uint16_t *report_ptr = (uint16_t *)new_report;
327+
device_t *state = &global_state;
262328

263329
/* If consumer control is variable, read the values from cc_array and send as array. */
264330
if (iface->consumer.is_variable) {
@@ -276,12 +342,21 @@ void process_consumer_report(uint8_t *raw_report, int length, uint8_t itf, hid_i
276342
new_report[i] = raw_report[i + 1];
277343
}
278344

279-
send_consumer_control(new_report, &global_state);
345+
if (CURRENT_BOARD_IS_ACTIVE_OUTPUT) {
346+
send_consumer_control(new_report, state);
347+
} else {
348+
queue_packet((uint8_t *)new_report, CONSUMER_CONTROL_MSG, CONSUMER_CONTROL_LENGTH);
349+
}
280350
}
281351

282352
void process_system_report(uint8_t *raw_report, int length, uint8_t itf, hid_interface_t *iface) {
283353
uint16_t new_report = raw_report[1];
284354
uint8_t *report_ptr = (uint8_t *)&new_report;
355+
device_t *state = &global_state;
285356

286-
send_system_control(report_ptr, &global_state);
357+
if (CURRENT_BOARD_IS_ACTIVE_OUTPUT) {
358+
send_system_control(report_ptr, state);
359+
} else {
360+
queue_packet(report_ptr, SYSTEM_CONTROL_MSG, SYSTEM_CONTROL_LENGTH);
361+
}
287362
}

src/setup.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,10 @@ void initial_setup(device_t *state) {
231231
queue_init(&state->kbd_queue, sizeof(hid_keyboard_report_t), KBD_QUEUE_LENGTH);
232232
queue_init(&state->mouse_queue, sizeof(mouse_report_t), MOUSE_QUEUE_LENGTH);
233233

234+
/* Initialize keyboard states for all devices */
235+
memset(state->kbd_states, 0, sizeof(state->kbd_states));
236+
state->kbd_device_count = 0;
237+
234238
/* Initialize generic HID packet queue */
235239
queue_init(&state->hid_queue_out, sizeof(hid_generic_pkt_t), HID_QUEUE_LENGTH);
236240

src/usb.c

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,34 @@ void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t cons
188188
if (instance >= MAX_INTERFACES)
189189
return;
190190

191+
/* Calculate a device index that distinguishes between different devices
192+
while staying within the bounds of MAX_DEVICES.
193+
194+
Device index assignment:
195+
- 0: Primary keyboard (the one set in tuh_hid_mount_cb)
196+
- 1: Mouse devices
197+
- MAX_DEVICES-2: Secondary keyboards (e.g., wireless keyboard through unified dongle)
198+
- (dev_addr-1) % (MAX_DEVICES-1): Other devices
199+
200+
Note: Slot MAX_DEVICES-1 is reserved for the remote device (used in handle_keyboard_uart_msg) */
201+
uint8_t device_idx;
202+
203+
if (itf_protocol == HID_ITF_PROTOCOL_KEYBOARD) {
204+
if (dev_addr == global_state.kbd_dev_addr && instance == global_state.kbd_instance) {
205+
/* Primary keyboard */
206+
device_idx = 0;
207+
} else {
208+
/* Secondary keyboard (e.g., wireless keyboard through unified dongle) */
209+
device_idx = (MAX_DEVICES - 2);
210+
}
211+
} else if (itf_protocol == HID_ITF_PROTOCOL_MOUSE) {
212+
/* Mouse devices */
213+
device_idx = 1;
214+
} else {
215+
/* Other devices */
216+
device_idx = (dev_addr - 1) % (MAX_DEVICES - 1);
217+
}
218+
191219
if (iface->uses_report_id || itf_protocol == HID_ITF_PROTOCOL_NONE) {
192220
uint8_t report_id = 0;
193221

@@ -198,14 +226,14 @@ void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t cons
198226
process_report_f receiver = iface->report_handler[report_id];
199227

200228
if (receiver != NULL)
201-
receiver((uint8_t *)report, len, itf_protocol, iface);
229+
receiver((uint8_t *)report, len, device_idx, iface);
202230
}
203231
}
204232
else if (itf_protocol == HID_ITF_PROTOCOL_KEYBOARD) {
205-
process_keyboard_report((uint8_t *)report, len, itf_protocol, iface);
233+
process_keyboard_report((uint8_t *)report, len, device_idx, iface);
206234
}
207235
else if (itf_protocol == HID_ITF_PROTOCOL_MOUSE) {
208-
process_mouse_report((uint8_t *)report, len, itf_protocol, iface);
236+
process_mouse_report((uint8_t *)report, len, device_idx, iface);
209237
}
210238

211239
/* Continue requesting reports */

0 commit comments

Comments
 (0)