diff --git a/CMakeLists.txt b/CMakeLists.txt index 6718bed..cdf0cdb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,8 @@ set(PICO_BOARD=pico) include(pico_sdk_import.cmake) set(CMAKE_CXX_FLAGS "-Ofast -Wall -mcpu=cortex-m0plus -mtune=cortex-m0plus") +set(PICO_COPY_TO_RAM 1) + project(deskhop_project C CXX ASM) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) @@ -31,6 +33,7 @@ target_include_directories(Pico-PIO-USB PRIVATE ${PICO_PIO_USB_DIR}) set(COMMON_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/usb_descriptors.c + ${CMAKE_CURRENT_LIST_DIR}/src/hid_parser.c ${CMAKE_CURRENT_LIST_DIR}/src/utils.c ${CMAKE_CURRENT_LIST_DIR}/src/handlers.c ${CMAKE_CURRENT_LIST_DIR}/src/setup.c @@ -40,6 +43,8 @@ set(COMMON_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/uart.c ${CMAKE_CURRENT_LIST_DIR}/src/usb.c ${CMAKE_CURRENT_LIST_DIR}/src/main.c + ${PICO_TINYUSB_PATH}/src/portable/raspberrypi/pio_usb/dcd_pio_usb.c + ${PICO_TINYUSB_PATH}/src/portable/raspberrypi/pio_usb/hcd_pio_usb.c ) set(COMMON_INCLUDES @@ -51,8 +56,10 @@ set(COMMON_LINK_LIBRARIES pico_stdlib hardware_uart hardware_gpio + hardware_pio tinyusb_device + tinyusb_host pico_multicore Pico-PIO-USB ) @@ -61,7 +68,7 @@ set(COMMON_LINK_LIBRARIES add_executable(board_A) target_sources(board_A PUBLIC ${COMMON_SOURCES}) -target_compile_definitions(board_A PRIVATE BOARD_ROLE=0) +target_compile_definitions(board_A PRIVATE BOARD_ROLE=0 PIO_USB_USE_TINYUSB=1 PIO_USB_DP_PIN_DEFAULT=14) target_include_directories(board_A PUBLIC ${COMMON_INCLUDES}) target_link_libraries(board_A PUBLIC ${COMMON_LINK_LIBRARIES}) @@ -72,7 +79,7 @@ pico_add_extra_outputs(board_A) # Pico B - Mouse add_executable(board_B) -target_compile_definitions(board_B PRIVATE BOARD_ROLE=1) +target_compile_definitions(board_B PRIVATE BOARD_ROLE=1 PIO_USB_USE_TINYUSB=1 PIO_USB_DP_PIN_DEFAULT=14) target_sources(board_B PUBLIC ${COMMON_SOURCES}) target_include_directories(board_B PUBLIC ${COMMON_INCLUDES}) target_link_libraries(board_B PUBLIC ${COMMON_LINK_LIBRARIES}) diff --git a/binaries/board_A.uf2 b/binaries/board_A.uf2 index 844392a..8502f51 100644 Binary files a/binaries/board_A.uf2 and b/binaries/board_A.uf2 differ diff --git a/binaries/board_B.uf2 b/binaries/board_B.uf2 index 53a27cd..27214df 100644 Binary files a/binaries/board_B.uf2 and b/binaries/board_B.uf2 differ diff --git a/src/handlers.c b/src/handlers.c index 79f58bc..b8e161e 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -1,3 +1,20 @@ +/* + * This file is part of DeskHop (https://github.com/hrvach/deskhop). + * Copyright (c) 2024 Hrvoje Cavrak + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "main.h" /**=================================================== * @@ -5,15 +22,31 @@ * =================================================== */ void output_toggle_hotkey_handler(device_state_t* state) { + /* If switching explicitly disabled, return immediately */ + if (state->switch_lock) + return; + state->active_output ^= 1; switch_output(state->active_output); }; -void fw_upgrade_hotkey_handler(device_state_t* state) { - send_value(ENABLE, FIRMWARE_UPGRADE_MSG); +/* This key combo puts board A in firmware upgrade mode */ +void fw_upgrade_hotkey_handler_A(device_state_t* state) { reset_usb_boot(1 << PICO_DEFAULT_LED_PIN, 0); }; +/* This key combo puts board B in firmware upgrade mode */ +void fw_upgrade_hotkey_handler_B(device_state_t* state) { + send_value(ENABLE, FIRMWARE_UPGRADE_MSG); +}; + + +void switchlock_hotkey_handler(device_state_t* state) { + state->switch_lock ^= 1; + send_value(state->switch_lock, SWITCH_LOCK_MSG); +} + + void mouse_zoom_hotkey_handler(device_state_t* state) { if (state->mouse_zoom) return; @@ -35,29 +68,26 @@ void all_keys_released_handler(device_state_t* state) { void handle_keyboard_uart_msg(uart_packet_t* packet, device_state_t* state) { if (state->active_output == ACTIVE_OUTPUT_B) { - hid_keyboard_report_t* report = (hid_keyboard_report_t*)packet->data; - - tud_hid_keyboard_report(REPORT_ID_KEYBOARD, report->modifier, report->keycode); + queue_kbd_report((hid_keyboard_report_t*)packet->data, state); state->last_activity[ACTIVE_OUTPUT_B] = time_us_64(); } } void handle_mouse_abs_uart_msg(uart_packet_t* packet, device_state_t* state) { - if (state->active_output == ACTIVE_OUTPUT_A) { - const hid_abs_mouse_report_t* mouse_report = (hid_abs_mouse_report_t*)packet->data; - - tud_hid_abs_mouse_report(REPORT_ID_MOUSE, mouse_report->buttons, mouse_report->x, - mouse_report->y, mouse_report->wheel, 0); - - state->last_activity[ACTIVE_OUTPUT_A] = time_us_64(); - } + hid_abs_mouse_report_t* mouse_report = (hid_abs_mouse_report_t*)packet->data; + queue_mouse_report(mouse_report, state); + state->last_activity[ACTIVE_OUTPUT_A] = time_us_64(); } void handle_output_select_msg(uart_packet_t* packet, device_state_t* state) { state->active_output = packet->data[0]; + if (state->tud_connected) + stop_pressing_any_keys(&global_state); + update_leds(state); } +/* On firmware upgrade message, reboot into the BOOTSEL fw upgrade mode */ void handle_fw_upgrade_msg(void) { reset_usb_boot(1 << PICO_DEFAULT_LED_PIN, 0); } @@ -67,14 +97,22 @@ void handle_mouse_zoom_msg(uart_packet_t* packet, device_state_t* state) { } void handle_set_report_msg(uart_packet_t* packet, device_state_t* state) { - // Only board B sends LED state through this message type + /* Only board B sends LED state through this message type */ state->keyboard_leds[ACTIVE_OUTPUT_B] = packet->data[0]; update_leds(state); } -// Update output variable, set LED on/off and notify the other board +void handle_switch_lock_msg(uart_packet_t* packet, device_state_t* state) { + state->switch_lock = packet->data[0]; +} + +/* Update output variable, set LED on/off and notify the other board so they are in sync. */ void switch_output(uint8_t new_output) { global_state.active_output = new_output; update_leds(&global_state); send_value(new_output, OUTPUT_SELECT_MSG); + + /* If we were holding a key down and drag the mouse to another screen, the key gets stuck. + Changing outputs = no more keypresses on the previous system. */ + stop_pressing_any_keys(&global_state); } diff --git a/src/hid_parser.c b/src/hid_parser.c new file mode 100644 index 0000000..3e25a62 --- /dev/null +++ b/src/hid_parser.c @@ -0,0 +1,210 @@ +/* + * This file is part of DeskHop (https://github.com/hrvach/deskhop). + * Copyright (c) 2024 Hrvoje Cavrak + * + * Based on the TinyUSB HID parser routine and the amazing USB2N64 + * adapter (https://github.com/pdaxrom/usb2n64-adapter) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "main.h" +#include "hid_parser.h" + +#define IS_BLOCK_END (collection.start == collection.end) +#define MAX_BUTTONS 16 + +enum { SIZE_0_BIT = 0, SIZE_8_BIT = 1, SIZE_16_BIT = 2, SIZE_32_BIT = 3 }; + +/* Size is 0, 1, 2, or 3, describing cases of no data, 8-bit, 16-bit, + or 32-bit data. */ +uint32_t get_descriptor_value(uint8_t const *report, int size) { + switch (size) { + case SIZE_8_BIT: + return report[0]; + case SIZE_16_BIT: + return tu_u16(report[1], report[0]); + case SIZE_32_BIT: + return tu_u32(report[3], report[2], report[1], report[0]); + default: + return 0; + } +} + +/* We store all globals as unsigned to avoid countless switch/cases. +In case of e.g. min/max, we need to treat some data as signed retroactively. */ +int32_t to_signed(globals_t *data) { + switch (data->hdr.size) { + case SIZE_8_BIT: + return (int8_t)data->val; + case SIZE_16_BIT: + return (int16_t)data->val; + default: + return data->val; + } +} + +/* Given a value struct with size and offset in bits, + find and return a value from the HID report */ + +int32_t get_report_value(uint8_t* report, report_val_t *val) { + /* Calculate the bit offset within the byte */ + uint8_t offset_in_bits = val->offset % 8; + + /* Calculate the remaining bits in the first byte */ + uint8_t remaining_bits = 8 - offset_in_bits; + + /* Calculate the byte offset in the array */ + uint8_t byte_offset = val->offset >> 3; + + /* Create a mask for the specified number of bits */ + uint32_t mask = (1u << val->size) - 1; + + /* Initialize the result value with the bits from the first byte */ + int32_t result = report[byte_offset] >> offset_in_bits; + + /* Move to the next byte and continue fetching bits until the desired length is reached */ + while (val->size > remaining_bits) { + result |= report[++byte_offset] << remaining_bits; + remaining_bits += 8; + } + + /* Apply the mask to retain only the desired number of bits */ + result = result & mask; + + /* Special case if result is negative. + Check if the most significant bit of 'val' is set */ + if (result & ((mask >> 1) + 1)) { + /* If it is set, sign-extend 'val' by filling the higher bits with 1s */ + result |= (0xFFFFFFFFU << val->size); + } + + return result; +} + +/* This method is far from a generalized HID descriptor parsing, but should work + * well enough to find the basic values we care about to move the mouse around. + * Your descriptor for a mouse with 2 wheels and 264 buttons might not parse correctly. + **/ +uint8_t parse_report_descriptor(mouse_t *mouse, uint8_t arr_count, + uint8_t const *report, uint16_t desc_len) { + + /* Get these elements and store them in the proper place in the mouse struct + * For example, to match wheel, we want collection usage to be HID_USAGE_DESKTOP_MOUSE, page to be HID_USAGE_PAGE_DESKTOP, + * usage to be HID_USAGE_DESKTOP_WHEEL, then if all of that is matched we store the value to mouse->wheel */ + const usage_map_t usage_map[] = { + {HID_USAGE_DESKTOP_MOUSE, HID_USAGE_PAGE_BUTTON, HID_USAGE_DESKTOP_POINTER, &mouse->buttons}, + {HID_USAGE_DESKTOP_MOUSE, HID_USAGE_PAGE_DESKTOP, HID_USAGE_DESKTOP_X, &mouse->move_x}, + {HID_USAGE_DESKTOP_MOUSE, HID_USAGE_PAGE_DESKTOP, HID_USAGE_DESKTOP_Y, &mouse->move_y}, + {HID_USAGE_DESKTOP_MOUSE, HID_USAGE_PAGE_DESKTOP, HID_USAGE_DESKTOP_WHEEL, &mouse->wheel}, + }; + + /* Some variables used for keeping tabs on parsing */ + uint8_t usage_count = 0; + uint8_t g_usage = 0; + + uint32_t offset_in_bits = 0; + + uint8_t usages[64] = {0}; + uint8_t* p_usage = usages; + + collection_t collection = {0}; + + /* as tag is 4 bits, there can be 16 different tags in global header type */ + globals_t globals[16] = {0}; + + for (int len = desc_len; len > 0; len--) { + header_t header = *(header_t *)report++; + uint32_t data = get_descriptor_value(report, header.size); + + switch (header.type) { + case RI_TYPE_MAIN: + // Keep count of collections, starts and ends + collection.start += (header.tag == RI_MAIN_COLLECTION); + collection.end += (header.tag == RI_MAIN_COLLECTION_END); + + if (header.tag == RI_MAIN_INPUT) { + for (int i = 0; i < globals[RI_GLOBAL_REPORT_COUNT].val; i++) { + + /* If we don't have as many usages as elements, the usage for the previous + element applies */ + if (i && i >= usage_count ) { + *(p_usage + i) = *(p_usage + usage_count - 1); + } + + const usage_map_t *map = usage_map; + + /* Only focus on the items we care about (buttons, x and y, wheels, etc) */ + for (int j=0; jreport_usage == g_usage && + map->usage_page == globals[RI_GLOBAL_USAGE_PAGE].val && + map->usage == *(p_usage + i)) { + + /* Buttons are the ones that appear multiple times, will handle them properly + For now, let's just aggregate the length and combine them into one :) */ + if (map->element->size) { + map->element->size++; + continue; + } + + /* Store the found element's attributes */ + map->element->offset = offset_in_bits; + map->element->size = globals[RI_GLOBAL_REPORT_SIZE].val; + map->element->min = to_signed(&globals[RI_GLOBAL_LOGICAL_MIN]); + map->element->max = to_signed(&globals[RI_GLOBAL_LOGICAL_MAX]); + } + }; + + /* Iterate times and increase offset by amount, moving by x bits */ + offset_in_bits += globals[RI_GLOBAL_REPORT_SIZE].val; + } + /* Advance the usage array pointer by global report count and reset the count variable */ + p_usage += globals[RI_GLOBAL_REPORT_COUNT].val; + usage_count = 0; + } + break; + + case RI_TYPE_GLOBAL: + /* There are just 16 possible tags, store any one that comes along to an array instead of doing + switch and 16 cases */ + globals[header.tag].val = data; + globals[header.tag].hdr = header; + + if (header.tag == RI_GLOBAL_REPORT_ID) { + /* Important to track, if report IDs are used reports are preceded/offset by a 1-byte ID value */ + if(g_usage == HID_USAGE_DESKTOP_MOUSE) + mouse->report_id = data; + + mouse->uses_report_id = true; + } + break; + + case RI_TYPE_LOCAL: + if (header.tag == RI_LOCAL_USAGE) { + /* If we are not within a collection, the usage tag applies to the entire section */ + if (IS_BLOCK_END) + g_usage = data; + else + *(p_usage + usage_count++) = data; + } + break; + } + + /* If header specified some non-zero length data, move by that much to get to the new byte + we should interpret as a header element */ + report += header.size; + len -= header.size; + } + return 0; +} diff --git a/src/hid_parser.h b/src/hid_parser.h new file mode 100644 index 0000000..4d9381b --- /dev/null +++ b/src/hid_parser.h @@ -0,0 +1,83 @@ +/* + * This file is part of DeskHop (https://github.com/hrvach/deskhop). + * Copyright (c) 2024 Hrvoje Cavrak + * + * Based on the TinyUSB HID parser routine and the amazing USB2N64 + * adapter (https://github.com/pdaxrom/usb2n64-adapter) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#define MAX_REPORTS 32 + +/* Counts how many collection starts and ends we've seen, when they equalize + (and not zero), we are at the end of a block */ +typedef struct { + uint8_t start; + uint8_t end; +} collection_t; + +/* Header byte is unpacked to size/type/tag using this struct */ +typedef struct TU_ATTR_PACKED { + uint8_t size : 2; + uint8_t type : 2; + uint8_t tag : 4; +} header_t; + +/* We store a header block and corresponding data in an array of these + to avoid having to use numerous switch-case checks */ +typedef struct { + header_t hdr; + uint32_t val; +} globals_t; + +// Extended precision mouse movement information +typedef struct { + int32_t move_x; + int32_t move_y; + int32_t wheel; + int32_t pan; + uint32_t buttons; +} mouse_values_t; + +/* Describes where can we find a value in a HID report */ +typedef struct { + uint16_t offset; // In bits + uint8_t size; // In bits + int32_t min; + int32_t max; +} report_val_t; + +/* Defines information about HID report format for the mouse. */ +typedef struct { + report_val_t buttons; + + report_val_t move_x; + report_val_t move_y; + + report_val_t wheel; + + bool uses_report_id; + uint8_t report_id; + uint8_t protocol; +} mouse_t; + +/* For each element type we're interested in there is an entry +in an array of these, defining its usage and in case matched, where to +store the data. */ +typedef struct { + uint8_t report_usage; + uint8_t usage_page; + uint8_t usage; + report_val_t* element; +} usage_map_t; diff --git a/src/keyboard.c b/src/keyboard.c index 448c3da..e13b05e 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -1,3 +1,20 @@ +/* + * This file is part of DeskHop (https://github.com/hrvach/deskhop). + * Copyright (c) 2024 Hrvoje Cavrak + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "main.h" /* ==================================================== * @@ -5,26 +22,74 @@ * ==================================================== */ hotkey_combo_t hotkeys[] = { - // Main keyboard switching hotkey + /* Main keyboard switching hotkey */ {.modifier = 0, .keys = {HOTKEY_TOGGLE}, .key_count = 1, .action_handler = &output_toggle_hotkey_handler}, - // Holding down right ALT slows the mouse down + /* Holding down right ALT slows the mouse down */ {.modifier = KEYBOARD_MODIFIER_RIGHTALT, .keys = {}, - .key_count = 0, - .action_handler = &mouse_zoom_hotkey_handler}, + .key_count = 0, + .action_handler = &mouse_zoom_hotkey_handler}, - // Hold down left shift + right shift + P + H + X ==> firmware upgrade mode + /* Switch lock */ + {.modifier = KEYBOARD_MODIFIER_RIGHTCTRL, + .keys = {HID_KEY_L}, + .key_count = 1, + .action_handler = &switchlock_hotkey_handler}, + + /* Hold down left shift + right shift + F12 + A ==> firmware upgrade mode for board A (kbd) */ {.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT | KEYBOARD_MODIFIER_LEFTSHIFT, - .keys = {HID_KEY_P, HID_KEY_H, HID_KEY_X}, - .key_count = 3, - .action_handler = &fw_upgrade_hotkey_handler} + .keys = {HID_KEY_F12, HID_KEY_A}, + .key_count = 2, + .action_handler = &fw_upgrade_hotkey_handler_A}, + + /* Hold down left shift + right shift + F12 + B ==> firmware upgrade mode for board B (mouse) */ + {.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT | KEYBOARD_MODIFIER_LEFTSHIFT, + .keys = {HID_KEY_F12, HID_KEY_B}, + .key_count = 2, + .action_handler = &fw_upgrade_hotkey_handler_B} }; +/* ==================================================== * + * Keyboard Queue Section + * ==================================================== */ + +void process_kbd_queue_task(device_state_t *state) { + hid_keyboard_report_t report; + + /* If we're not connected, we have nowhere to send reports to. */ + if (!state->tud_connected) + return; + + /* Peek first, if there is anything there... */ + if (!queue_try_peek(&state->kbd_queue, &report)) + return; + + /* ... try sending it to the host, if it's successful */ + bool succeeded = tud_hid_keyboard_report(REPORT_ID_KEYBOARD, report.modifier, report.keycode); + + /* ... then we can remove it from the queue. Race conditions shouldn't happen [tm] */ + if (succeeded) + queue_try_remove(&state->kbd_queue, &report); +} + +void queue_kbd_report(hid_keyboard_report_t *report, device_state_t *state) { + /* It wouldn't be fun to queue up a bunch of messages and then dump them all on host */ + if (!state->tud_connected) + return; + + queue_try_add(&state->kbd_queue, report); +} + +void stop_pressing_any_keys(device_state_t *state) { + static hid_keyboard_report_t no_keys_pressed_report = {0, 0, {0}}; + queue_try_add(&state->kbd_queue, &no_keys_pressed_report); +} + /* ==================================================== * * Parse and interpret the keys pressed on the keyboard * ==================================================== */ @@ -46,26 +111,26 @@ void process_keyboard_report(uint8_t* raw_report, int length, device_state_t* st if (length < KBD_REPORT_LENGTH) return; - // Go through the list of hotkeys, check if any are pressed, then execute their handler + /* Go through the list of hotkeys, check if any are pressed, then execute their handler */ for (int n = 0; n < sizeof(hotkeys) / sizeof(hotkeys[0]); n++) { if (keypress_check(hotkeys[n], keyboard_report)) { hotkeys[n].action_handler(state); return; } - } + } - // If no keys are pressed anymore, take care of checking and deactivating stuff - if (no_keys_are_pressed(keyboard_report)) { + state->key_pressed = !no_keys_are_pressed(keyboard_report); + + /* If no keys are pressed anymore, take care of checking and deactivating stuff */ + if (!state->key_pressed) { all_keys_released_handler(state); } - // If keys need to go to output B, send them through UART, otherwise send a HID report directly + /* If keys need to go to output B, send them through UART, otherwise send a HID report directly */ if (state->active_output == ACTIVE_OUTPUT_B) { send_packet(raw_report, KEYBOARD_REPORT_MSG, KBD_REPORT_LENGTH); - } else { - tud_hid_keyboard_report(REPORT_ID_KEYBOARD, keyboard_report->modifier, - keyboard_report->keycode); - + } else { + queue_kbd_report(keyboard_report, state); state->last_activity[ACTIVE_OUTPUT_A] = time_us_64(); } } @@ -78,7 +143,7 @@ void process_keyboard_report(uint8_t* raw_report, int length, device_state_t* st bool keypress_check(hotkey_combo_t keypress, const hid_keyboard_report_t* report) { int matches = 0; - // We expect all modifiers specified to be detected in the report + /* We expect all modifiers specified to be detected in the report */ if (keypress.modifier != (report->modifier & keypress.modifier)) return false; @@ -89,13 +154,12 @@ bool keypress_check(hotkey_combo_t keypress, const hid_keyboard_report_t* report break; } } - // If any of the keys are not found, we can bail out early. + /* If any of the keys are not found, we can bail out early. */ if (matches < n + 1) { return false; } } - // Getting here means all of the keys were found. + /* Getting here means all of the keys were found. */ return true; -} - +} \ No newline at end of file diff --git a/src/led.c b/src/led.c index 4a8d9e6..b75c631 100644 --- a/src/led.c +++ b/src/led.c @@ -1,3 +1,20 @@ +/* + * This file is part of DeskHop (https://github.com/hrvach/deskhop). + * Copyright (c) 2024 Hrvoje Cavrak + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "main.h" /**==================================================== * @@ -7,6 +24,10 @@ void update_leds(device_state_t* state) { gpio_put(GPIO_LED_PIN, state->active_output == BOARD_ROLE); - if (BOARD_ROLE == KEYBOARD_PICO_A) - pio_usb_kbd_set_leds(state->usb_device, 0, state->keyboard_leds[state->active_output]); + // TODO: Will be done in a callback + if (BOARD_ROLE == PICO_A) { + uint8_t* leds = &(state->keyboard_leds[state->active_output]); + tuh_hid_set_report(global_state.kbd_dev_addr, global_state.kbd_instance, 0, + HID_REPORT_TYPE_OUTPUT, leds, sizeof(uint8_t)); + } } diff --git a/src/main.c b/src/main.c index 36574d8..872752f 100644 --- a/src/main.c +++ b/src/main.c @@ -1,3 +1,20 @@ +/* + * This file is part of DeskHop (https://github.com/hrvach/deskhop). + * Copyright (c) 2024 Hrvoje Cavrak + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "main.h" /********* Global Variable **********/ @@ -11,7 +28,8 @@ void main(void) { // Wait for the board to settle sleep_ms(10); - global_state.usb_device = initial_setup(); + // Initial board setup + initial_setup(); // Initial state, A is the default output switch_output(ACTIVE_OUTPUT_A); @@ -20,21 +38,16 @@ void main(void) { // USB device task, needs to run as often as possible tud_task(); - // If we are not yet connected to the PC, don't bother with host - // If host task becomes too slow, move it to the second core - if (global_state.tud_connected) { - // Execute HOST task periodically - pio_usb_host_task(); - - // Query devices and handle reports - if (global_state.usb_device && global_state.usb_device->connected) { - check_endpoints(&global_state); - } - } - // Verify core1 is still running and if so, reset watchdog timer kick_watchdog(); + + // Check if there were any keypresses and send them + process_kbd_queue_task(&global_state); + + // Check if there were any mouse movements and send them + process_mouse_queue_task(&global_state); } + } void core1_main() { @@ -44,6 +57,10 @@ void core1_main() { // Update the timestamp, so core0 can figure out if we're dead global_state.core1_last_loop_pass = time_us_64(); + // USB host task, needs to run as often as possible + tuh_task(); + + // Receives data over serial from the other board receive_char(&in_packet, &global_state); } } diff --git a/src/main.h b/src/main.h index bf7008c..6208f0c 100644 --- a/src/main.h +++ b/src/main.h @@ -1,3 +1,20 @@ +/* + * This file is part of DeskHop (https://github.com/hrvach/deskhop). + * Copyright (c) 2024 Hrvoje Cavrak + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #pragma once #include "pico/stdlib.h" @@ -10,14 +27,16 @@ #include "pico/bootrom.h" #include "pico/multicore.h" #include "pico/stdlib.h" +#include "pico/util/queue.h" #include "pio_usb.h" #include "tusb.h" #include "usb_descriptors.h" +#include "hid_parser.h" #include "user_config.h" /********* Misc definitions **********/ -#define KEYBOARD_PICO_A 0 -#define MOUSE_PICO_B 1 +#define PICO_A 0 +#define PICO_B 1 #define ACTIVE_OUTPUT_A 0 #define ACTIVE_OUTPUT_B 1 @@ -25,21 +44,28 @@ #define ENABLE 1 #define DISABLE 0 +#define DIRECTION_X 0 +#define DIRECTION_Y 1 + +#define MAX_REPORT_ITEMS 16 +#define MOUSE_BOOT_REPORT_LEN 4 + /********* Pinout definitions **********/ #define PIO_USB_DP_PIN 14 // D+ is pin 14, D- is pin 15 #define GPIO_LED_PIN 25 // LED is connected to pin 25 on a PICO -#if BOARD_ROLE == MOUSE_PICO_B +#if BOARD_ROLE == PICO_B #define SERIAL_TX_PIN 16 #define SERIAL_RX_PIN 17 -#elif BOARD_ROLE == KEYBOARD_PICO_A +#elif BOARD_ROLE == PICO_A #define SERIAL_TX_PIN 12 #define SERIAL_RX_PIN 13 #endif /********* Serial port definitions **********/ #define SERIAL_UART uart0 -#define SERIAL_BAUDRATE 115200 +#define SERIAL_BAUDRATE 3686400 + #define SERIAL_DATA_BITS 8 #define SERIAL_STOP_BITS 1 #define SERIAL_PARITY UART_PARITY_NONE @@ -66,6 +92,7 @@ enum packet_type_e : uint8_t { FIRMWARE_UPGRADE_MSG = 4, MOUSE_ZOOM_MSG = 5, KBD_SET_REPORT_MSG = 6, + SWITCH_LOCK_MSG = 7, }; /* @@ -96,6 +123,9 @@ typedef struct { #define PACKET_LENGTH (TYPE_LENGTH + PACKET_DATA_LENGTH + CHECKSUM_LENGTH) #define RAW_PACKET_LENGTH (START_LENGTH + PACKET_LENGTH) +#define KBD_QUEUE_LENGTH 128 +#define MOUSE_QUEUE_LENGTH 256 + #define KEYS_IN_USB_REPORT 6 #define KBD_REPORT_LENGTH 8 #define MOUSE_REPORT_LENGTH 7 @@ -125,7 +155,9 @@ typedef struct TU_ATTR_PACKED { typedef enum { IDLE, READING_PACKET, PROCESSING_PACKET } receiver_state_t; typedef struct { - usb_device_t* usb_device; // USB device structure (keyboard or mouse) + uint8_t kbd_dev_addr; // Address of the keyboard device + uint8_t kbd_instance; // Keyboard instance (d'uh - isn't this a useless comment) + uint8_t keyboard_leds[2]; // State of keyboard LEDs (index 0 = A, index 1 = B) uint64_t last_activity[2]; // Timestamp of the last input activity (-||-) receiver_state_t receiver_state; // Storing the state for the simple receiver state machine @@ -136,8 +168,17 @@ typedef struct { int16_t mouse_x; // Store and update the location of our mouse pointer int16_t mouse_y; + mouse_t mouse_dev; // Mouse device specifics, e.g. stores locations for keys in report + queue_t kbd_queue; // Queue that stores keyboard reports + queue_t mouse_queue; // Queue that stores mouse reports + bool tud_connected; // True when TinyUSB device successfully connects + bool keyboard_connected; // True when our keyboard is connected locally + bool mouse_connected; // True when a mouse is connected locally bool mouse_zoom; // True when "mouse zoom" is enabled + bool switch_lock; // True when device is prevented from switching + + bool key_pressed; // We are holding down a key (from the PCs point of view) } device_state_t; @@ -146,13 +187,16 @@ void process_mouse_report(uint8_t*, int, device_state_t*); void check_endpoints(device_state_t* state); /********* Setup **********/ -usb_device_t* initial_setup(void); +void initial_setup(void); void serial_init(void); void core1_main(void); /********* Keyboard **********/ bool keypress_check(hotkey_combo_t, const hid_keyboard_report_t*); void process_keyboard_report(uint8_t*, int, device_state_t*); +void stop_pressing_any_keys(device_state_t*); +void queue_kbd_report(hid_keyboard_report_t*, device_state_t*); +void process_kbd_queue_task(device_state_t*); /********* Mouse **********/ bool tud_hid_abs_mouse_report(uint8_t report_id, @@ -162,6 +206,11 @@ bool tud_hid_abs_mouse_report(uint8_t report_id, int8_t vertical, int8_t horizontal); +uint8_t parse_report_descriptor(mouse_t* mouse, uint8_t arr_count, uint8_t const* desc_report, uint16_t desc_len); +int32_t get_report_value(uint8_t* report, report_val_t *val); +void process_mouse_queue_task(device_state_t*); +void queue_mouse_report(hid_abs_mouse_report_t*, device_state_t*); + /********* UART **********/ void receive_char(uart_packet_t*, device_state_t*); void send_packet(const uint8_t*, enum packet_type_e, int); @@ -179,9 +228,11 @@ void kick_watchdog(void); /********* Handlers **********/ void output_toggle_hotkey_handler(device_state_t*); -void fw_upgrade_hotkey_handler(device_state_t*); +void fw_upgrade_hotkey_handler_A(device_state_t*); +void fw_upgrade_hotkey_handler_B(device_state_t*); void mouse_zoom_hotkey_handler(device_state_t*); void all_keys_released_handler(device_state_t*); +void switchlock_hotkey_handler(device_state_t*); void handle_keyboard_uart_msg(uart_packet_t*, device_state_t*); void handle_mouse_abs_uart_msg(uart_packet_t*, device_state_t*); @@ -189,6 +240,7 @@ void handle_output_select_msg(uart_packet_t*, device_state_t*); void handle_fw_upgrade_msg(void); void handle_mouse_zoom_msg(uart_packet_t*, device_state_t*); void handle_set_report_msg(uart_packet_t*, device_state_t*); +void handle_switch_lock_msg(uart_packet_t*, device_state_t*); void switch_output(uint8_t); diff --git a/src/mouse.c b/src/mouse.c index 90d24b0..582873f 100644 --- a/src/mouse.c +++ b/src/mouse.c @@ -1,72 +1,189 @@ +/* + * This file is part of DeskHop (https://github.com/hrvach/deskhop). + * Copyright (c) 2024 Hrvoje Cavrak + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "main.h" -int get_mouse_offset(int8_t movement) { - // Holding a special hotkey enables mouse to slow down as much as possible - // when you need that extra precision - if (global_state.mouse_zoom) - return movement * MOUSE_SPEED_FACTOR >> 2; +int get_mouse_offset(int32_t movement, const int direction) { + int offset = 0; + + if (direction == DIRECTION_X) + offset = movement * MOUSE_SPEED_FACTOR_X; else - return movement * MOUSE_SPEED_FACTOR; + offset = movement * MOUSE_SPEED_FACTOR_Y; + + /* Holding a special hotkey enables mouse to slow down as much as possible + when you need that extra precision */ + if (global_state.mouse_zoom) + offset = offset >> 2; + + return offset; } -void keep_cursor_on_screen(int16_t* position, const int8_t* movement) { - int16_t offset = get_mouse_offset(*movement); +void keep_cursor_on_screen(int16_t* position, const int32_t* movement, const int direction) { + int16_t offset = get_mouse_offset(*movement, direction); - // Lowest we can go is 0 + /* Lowest we can go is 0 */ if (*position + offset < 0) *position = 0; - // Highest we can go is MAX_SCREEN_COORD + /* Highest we can go is MAX_SCREEN_COORD */ else if (*position + offset > MAX_SCREEN_COORD) *position = MAX_SCREEN_COORD; - // We're still on screen, all good + /* We're still on screen, all good */ else *position += offset; } -void check_mouse_switch(const hid_mouse_report_t* mouse_report, device_state_t* state) { - // End of screen right switches screen B->A - if ((state->mouse_x + mouse_report->x) > MAX_SCREEN_COORD && - state->active_output == ACTIVE_OUTPUT_B) { - state->mouse_x = 0; - switch_output(ACTIVE_OUTPUT_A); + +void check_mouse_switch(const mouse_values_t* values, device_state_t* state) { + hid_abs_mouse_report_t report = {.y = 0, .x = MAX_SCREEN_COORD}; + + /* No switching allowed if explicitly disabled */ + if (state->switch_lock) return; + + /* End of screen left switches screen A->B */ + bool jump_from_A_to_B = (state->mouse_x + values->move_x < -MOUSE_JUMP_THRESHOLD && + state->active_output == ACTIVE_OUTPUT_A); + + /* End of screen right switches screen B->A */ + bool jump_from_B_to_A = (state->mouse_x + values->move_x > MAX_SCREEN_COORD + MOUSE_JUMP_THRESHOLD && + state->active_output == ACTIVE_OUTPUT_B); + + if (jump_from_A_to_B || jump_from_B_to_A) { + /* Hide mouse pointer in the upper right corner on the system we are switching FROM + If the mouse is locally attached to the current board or notify other board if not */ + if (state->active_output == state->mouse_connected) + queue_mouse_report(&report, state); + else + send_packet((const uint8_t*)&report, MOUSE_REPORT_MSG, MOUSE_REPORT_LENGTH); + + if (jump_from_A_to_B) { + switch_output(ACTIVE_OUTPUT_B); + state->mouse_x = MAX_SCREEN_COORD; + } else { + switch_output(ACTIVE_OUTPUT_A); + state->mouse_x = 0; + } + } +} + +void extract_values_report_protocol(uint8_t* report, + device_state_t* state, + mouse_values_t* values) { + /* If Report ID is used, the report is prefixed by the report ID so we have to move by 1 byte */ + if (state->mouse_dev.uses_report_id) { + /* Move past the ID to parse the report */ + report++; } - // End of screen left switches screen A->B - if ((state->mouse_x + mouse_report->x) < 0 && state->active_output == ACTIVE_OUTPUT_A) { - state->mouse_x = MAX_SCREEN_COORD; - switch_output(ACTIVE_OUTPUT_B); - return; + values->move_x = get_report_value(report, &state->mouse_dev.move_x); + values->move_y = get_report_value(report, &state->mouse_dev.move_y); + values->wheel = get_report_value(report, &state->mouse_dev.wheel); + values->buttons = get_report_value(report, &state->mouse_dev.buttons); + + /* Mice generally come in 3 categories - 8-bit, 12-bit and 16-bit. */ + switch (state->mouse_dev.move_x.size) { + case 12: + /* If we're already 12 bit, great! */ + break; + case 16: + /* Initially we downscale fancy mice to 12-bits, + adding a 32-bit internal coordinate tracking is TODO */ + values->move_x >>= 4; + values->move_y >>= 4; + break; + default: + /* 8-bit is the default, upscale to 12-bit. */ + values->move_x <<= 4; + values->move_y <<= 4; } } -void process_mouse_report(uint8_t* raw_report, int len, device_state_t* state) { - hid_mouse_report_t* mouse_report = (hid_mouse_report_t*)raw_report; +void extract_values_boot_protocol(uint8_t* report, device_state_t* state, mouse_values_t* values) { + hid_mouse_report_t* mouse_report = (hid_mouse_report_t*)report; + /* For 8-bit values, we upscale them to 12-bit, TODO: 16 bit */ + values->move_x = mouse_report->x << 4; + values->move_y = mouse_report->y << 4; + values->wheel = mouse_report->wheel; + values->buttons = mouse_report->buttons; +} - // We need to enforce the cursor doesn't go off-screen, that would be bad. - keep_cursor_on_screen(&state->mouse_x, &mouse_report->x); - keep_cursor_on_screen(&state->mouse_y, &mouse_report->y); +void process_mouse_report(uint8_t* raw_report, int len, device_state_t* state) { + mouse_values_t values = {0}; + + /* Interpret values depending on the current protocol used */ + if (state->mouse_dev.protocol == HID_PROTOCOL_BOOT) + extract_values_boot_protocol(raw_report, state, &values); + else + extract_values_report_protocol(raw_report, state, &values); + + /* We need to enforce the cursor doesn't go off-screen, that would be bad. */ + keep_cursor_on_screen(&state->mouse_x, &values.move_x, DIRECTION_X); + keep_cursor_on_screen(&state->mouse_y, &values.move_y, DIRECTION_Y); + + hid_abs_mouse_report_t abs_mouse_report = { + .buttons = values.buttons, + .x = state->mouse_x, + .y = state->mouse_y, + .wheel = values.wheel, + .pan = 0 + }; if (state->active_output == ACTIVE_OUTPUT_A) { - hid_abs_mouse_report_t abs_mouse_report; - - abs_mouse_report.buttons = mouse_report->buttons; - abs_mouse_report.x = state->mouse_x; - abs_mouse_report.y = state->mouse_y; - abs_mouse_report.wheel = mouse_report->wheel; - abs_mouse_report.pan = 0; - send_packet((const uint8_t*)&abs_mouse_report, MOUSE_REPORT_MSG, MOUSE_REPORT_LENGTH); - } else { - tud_hid_abs_mouse_report(REPORT_ID_MOUSE, mouse_report->buttons, state->mouse_x, - state->mouse_y, mouse_report->wheel, 0); - + queue_mouse_report(&abs_mouse_report, state); state->last_activity[ACTIVE_OUTPUT_B] = time_us_64(); } - // We use the mouse to switch outputs, the logic is in check_mouse_switch() - check_mouse_switch(mouse_report, state); -} \ No newline at end of file + /* We use the mouse to switch outputs, the logic is in check_mouse_switch() */ + check_mouse_switch(&values, state); +} + +/* ==================================================== * + * Mouse Queue Section + * ==================================================== */ + +void process_mouse_queue_task(device_state_t* state) { + hid_abs_mouse_report_t report = {0}; + + /* We need to be connected to the host to send messages */ + if (!state->tud_connected) + return; + + /* Peek first, if there is anything there... */ + if (!queue_try_peek(&state->mouse_queue, &report)) + return; + + /* ... try sending it to the host, if it's successful */ + bool succeeded = tud_hid_abs_mouse_report(REPORT_ID_MOUSE, report.buttons, report.x, report.y, + report.wheel, report.pan); + + /* ... then we can remove it from the queue */ + if (succeeded) + queue_try_remove(&state->mouse_queue, &report); +} + +void queue_mouse_report(hid_abs_mouse_report_t* report, device_state_t* state) { + /* It wouldn't be fun to queue up a bunch of messages and then dump them all on host */ + if (!state->tud_connected) + return; + + queue_try_add(&state->mouse_queue, report); +} diff --git a/src/setup.c b/src/setup.c index ac41217..ef8c47d 100644 --- a/src/setup.c +++ b/src/setup.c @@ -1,3 +1,20 @@ +/* + * This file is part of DeskHop (https://github.com/hrvach/deskhop). + * Copyright (c) 2024 Hrvoje Cavrak + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + /**================================================== * * ============= Initial Board Setup ============== * * ================================================== */ @@ -34,19 +51,21 @@ void serial_init() { * PIO USB configuration, D+ pin 14, D- pin 15 * ================================================== */ -usb_device_t* pio_usb_init(void) { +void pio_usb_host_config(void) { + /* tuh_configure() must be called before tuh_init() */ static pio_usb_configuration_t config = PIO_USB_DEFAULT_CONFIG; - config.pin_dp = 14; - config.alarm_pool = (void*)alarm_pool_create(2, 1); + config.pin_dp = PIO_USB_DP_PIN; + tuh_configure(BOARD_TUH_RHPORT, TUH_CFGID_RPI_PIO_USB_CONFIGURATION, &config); - return pio_usb_host_init(&config); + /* Initialize and configure TinyUSB Host */ + tuh_init(1); } /* ================================================== * * Perform initial board/usb setup * ================================================== */ -usb_device_t* initial_setup(void) { +void initial_setup(void) { /* PIO USB requires a clock multiple of 12 MHz, setting to 120 MHz */ set_sys_clock_khz(120000, true); @@ -57,23 +76,25 @@ usb_device_t* initial_setup(void) { /* Initialize and configure UART */ serial_init(); - /* Initialize and configure TinyUSB */ - tusb_init(); - + /* Initialize keyboard and mouse queues */ + queue_init(&global_state.kbd_queue, sizeof(hid_keyboard_report_t), KBD_QUEUE_LENGTH); + queue_init(&global_state.mouse_queue, sizeof(hid_abs_mouse_report_t), MOUSE_QUEUE_LENGTH); + /* Setup RP2040 Core 1 */ multicore_reset_core1(); multicore_launch_core1(core1_main); - /* Initialize and configure PIO USB */ - usb_device_t* pio_usb_device = pio_usb_init(); + /* Initialize and configure TinyUSB Device */ + tud_init(BOARD_TUD_RHPORT); + + /* Initialize and configure TinyUSB Host */ + pio_usb_host_config(); /* Update the core1 initial pass timestamp before enabling the watchdog */ global_state.core1_last_loop_pass = time_us_64(); /* Setup the watchdog so we reboot and recover from a crash */ watchdog_enable(WATCHDOG_TIMEOUT, WATCHDOG_PAUSE_ON_DEBUG); - - return pio_usb_device; } -/* ========== End of Initial Board Setup ========== */ \ No newline at end of file +/* ========== End of Initial Board Setup ========== */ diff --git a/src/tusb_config.h b/src/tusb_config.h index 868424e..04e72a4 100644 --- a/src/tusb_config.h +++ b/src/tusb_config.h @@ -34,6 +34,21 @@ // COMMON CONFIGURATION //-------------------------------------------------------------------- +#define CFG_TUSB_OS OPT_OS_PICO + +// Enable device stack +#define CFG_TUD_ENABLED 1 + +// RHPort number used for device is port 0 +#define BOARD_TUD_RHPORT 0 + +// RHPort number used for host is port 1 +#define BOARD_TUH_RHPORT 1 + +// Enable host stack with pio-usb if Pico-PIO-USB library is available +#define CFG_TUH_ENABLED 1 +#define CFG_TUH_RPI_PIO_USB 1 + // defined by board.mk #ifndef CFG_TUSB_MCU #error CFG_TUSB_MCU must be defined @@ -56,17 +71,8 @@ #endif // Device mode with rhport and speed defined by board.mk -#if BOARD_DEVICE_RHPORT_NUM == 0 - #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) -#elif BOARD_DEVICE_RHPORT_NUM == 1 - #define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) -#else - #error "Incorrect RHPort configuration" -#endif - -#ifndef CFG_TUSB_OS -#define CFG_TUSB_OS OPT_OS_NONE -#endif +#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) +#define CFG_TUSB_RHPORT1_MODE (OPT_MODE_HOST | BOARD_DEVICE_RHPORT_SPEED) // CFG_TUSB_DEBUG is defined by compiler in DEBUG build // #define CFG_TUSB_DEBUG 0 @@ -102,7 +108,22 @@ #define CFG_TUD_VENDOR 0 // HID buffer size Should be sufficient to hold ID (if any) + Data -#define CFG_TUD_HID_EP_BUFSIZE 16 +#define CFG_TUD_HID_EP_BUFSIZE 32 + +//-------------------------------------------------------------------- +// HOST CONFIGURATION +//-------------------------------------------------------------------- + +// Size of buffer to hold descriptors and other data used for enumeration +#define CFG_TUH_ENUMERATION_BUFSIZE 256 + +#define CFG_TUH_HUB 1 +// max device support (excluding hub device) +#define CFG_TUH_DEVICE_MAX (CFG_TUH_HUB ? 4 : 1) // hub typically has 4 ports + +#define CFG_TUH_HID 4 +#define CFG_TUH_HID_EPIN_BUFSIZE 64 +#define CFG_TUH_HID_EPOUT_BUFSIZE 64 #ifdef __cplusplus } diff --git a/src/uart.c b/src/uart.c index 9b16043..563bac7 100644 --- a/src/uart.c +++ b/src/uart.c @@ -1,5 +1,21 @@ -#include "main.h" +/* + * This file is part of DeskHop (https://github.com/hrvach/deskhop). + * Copyright (c) 2024 Hrvoje Cavrak + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "main.h" /**================================================== * * =============== Sending Packets ================ * @@ -58,6 +74,10 @@ void process_packet(uart_packet_t* packet, device_state_t* state) { case KBD_SET_REPORT_MSG: handle_set_report_msg(packet, state); break; + + case SWITCH_LOCK_MSG: + handle_switch_lock_msg(packet, state); + break; } } @@ -72,10 +92,10 @@ void receive_char(uart_packet_t* packet, device_state_t* state) { switch (state->receiver_state) { case IDLE: if (uart_is_readable(SERIAL_UART)) { - raw_packet[0] = raw_packet[1]; // Remember the previous byte received - raw_packet[1] = uart_getc(SERIAL_UART); // ... and try to match packet start + raw_packet[0] = raw_packet[1]; /* Remember the previous byte received */ + raw_packet[1] = uart_getc(SERIAL_UART); /* ... and try to match packet start */ - // If we found 0xAA 0x55, we're in sync and can move on to read/process the packet + /* If we found 0xAA 0x55, we're in sync and can move on to read/process the packet */ if (raw_packet[0] == START1 && raw_packet[1] == START2) { state->receiver_state = READING_PACKET; } @@ -86,7 +106,7 @@ void receive_char(uart_packet_t* packet, device_state_t* state) { if (uart_is_readable(SERIAL_UART)) { raw_packet[count++] = uart_getc(SERIAL_UART); - // Check if a complete packet is received + /* Check if a complete packet is received */ if (count >= PACKET_LENGTH) { state->receiver_state = PROCESSING_PACKET; } @@ -96,7 +116,7 @@ void receive_char(uart_packet_t* packet, device_state_t* state) { case PROCESSING_PACKET: process_packet(packet, state); - // Cleanup and return to IDLE when done + /* Cleanup and return to IDLE when done */ count = 0; state->receiver_state = IDLE; break; diff --git a/src/usb.c b/src/usb.c index 44aae76..d095216 100644 --- a/src/usb.c +++ b/src/usb.c @@ -1,35 +1,29 @@ +/* + * This file is part of DeskHop (https://github.com/hrvach/deskhop). + * Copyright (c) 2024 Hrvoje Cavrak + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "main.h" -/**================================================== * - * ========== Query endpoints for reports ========= * - * ================================================== */ - -void check_endpoints(device_state_t* state) { - uint8_t raw_report[64]; - - // Iterate through all endpoints and check for data - for (int ep_idx = 0; ep_idx < PIO_USB_DEV_EP_CNT; ep_idx++) { - endpoint_t* ep = pio_usb_get_endpoint(state->usb_device, ep_idx); - - if (ep == NULL) { - continue; - } - - int len = pio_usb_get_in_data(ep, raw_report, sizeof(raw_report)); - - if (len > 0) { - if (BOARD_ROLE == KEYBOARD_PICO_A) - process_keyboard_report(raw_report, len, state); - else - process_mouse_report(raw_report, len, state); - } - } -} - /**================================================== * * =========== TinyUSB Device Callbacks =========== * * ================================================== */ +/* Invoked when we get GET_REPORT control request. + * We are expected to fill buffer with the report content, update reqlen + * and return its length. We return 0 to STALL the request. */ uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, @@ -58,33 +52,111 @@ void tud_hid_set_report_cb(uint8_t instance, uint8_t leds = buffer[0]; if (KBD_LED_AS_INDICATOR) { - leds = leds & 0xFD; // 1111 1101 (Clear Caps Lock bit) + leds = leds & 0xFD; /* 1111 1101 (Clear Caps Lock bit) */ if (global_state.active_output) leds |= KEYBOARD_LED_CAPSLOCK; } - - global_state.keyboard_leds[global_state.active_output] = leds; - // If we are board B, we need to set this information to the other one since that one - // has the keyboard connected to it (and LEDs you can turn on :-)) - if (BOARD_ROLE == MOUSE_PICO_B) - send_value(leds, KBD_SET_REPORT_MSG); + global_state.keyboard_leds[global_state.active_output] = leds; - // If we are board A, update LEDs directly - else + /* If we are board without the keyboard hooked up directly, we need to send this information + to the other one since that one has the keyboard connected to it (and LEDs you can turn on :)) */ + if (global_state.keyboard_connected) update_leds(&global_state); + else + send_value(leds, KBD_SET_REPORT_MSG); } } -// Invoked when device is mounted -void tud_mount_cb(void) -{ - global_state.tud_connected = true; +/* Invoked when device is mounted */ +void tud_mount_cb(void) { + global_state.tud_connected = true; } -// Invoked when device is unmounted -void tud_umount_cb(void) -{ - global_state.tud_connected = false; -} \ No newline at end of file +/* Invoked when device is unmounted */ +void tud_umount_cb(void) { + global_state.tud_connected = false; +} + +/**================================================== * + * =============== USB HOST Section =============== * + * ================================================== */ + +void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) { + uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); + switch (itf_protocol) { + case HID_ITF_PROTOCOL_KEYBOARD: + global_state.keyboard_connected = false; + break; + + case HID_ITF_PROTOCOL_MOUSE: + global_state.mouse_connected = false; + + /* Clear this so reconnecting a mouse doesn't try to continue in HID REPORT protocol */ + memset(&global_state.mouse_dev, 0, sizeof(global_state.mouse_dev)); + break; + } +} + +void tuh_hid_mount_cb(uint8_t dev_addr, + uint8_t instance, + uint8_t const* desc_report, + uint16_t desc_len) { + uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); + + switch (itf_protocol) { + case HID_ITF_PROTOCOL_KEYBOARD: + /* Keeping this is needed for setting leds from device set_report callback */ + global_state.kbd_dev_addr = dev_addr; + global_state.kbd_instance = instance; + + global_state.keyboard_connected = true; + break; + + case HID_ITF_PROTOCOL_MOUSE: + /* Switch to using protocol report instead of boot report, it's more complicated but + at least we get all the information we need (looking at you, mouse wheel) */ + if (tuh_hid_get_protocol(dev_addr, instance) == HID_PROTOCOL_BOOT) { + tuh_hid_set_protocol(dev_addr, instance, HID_PROTOCOL_REPORT); + } + + parse_report_descriptor(&global_state.mouse_dev, MAX_REPORTS, desc_report, desc_len); + + global_state.mouse_connected = true; + break; + } + + /* Kick off the report querying */ + tuh_hid_receive_report(dev_addr, instance); +} + +/* Invoked when received report from device via interrupt endpoint */ +void tuh_hid_report_received_cb(uint8_t dev_addr, + uint8_t instance, + uint8_t const* report, + uint16_t len) { + (void)len; + uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); + + switch (itf_protocol) { + case HID_ITF_PROTOCOL_KEYBOARD: + process_keyboard_report((uint8_t*)report, len, &global_state); + break; + + case HID_ITF_PROTOCOL_MOUSE: + process_mouse_report((uint8_t*)report, len, &global_state); + break; + } + + /* Continue requesting reports */ + tuh_hid_receive_report(dev_addr, instance); +} + +/* Set protocol in a callback. If we were called, command succeeded. We're only + doing this for the mouse anyway, so we can only be called about the mouse */ +void tuh_hid_set_protocol_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t protocol) { + (void) dev_addr; + (void) idx; + global_state.mouse_dev.protocol = protocol; +} diff --git a/src/user_config.h b/src/user_config.h index f0da291..27626c3 100644 --- a/src/user_config.h +++ b/src/user_config.h @@ -10,7 +10,7 @@ * * */ -#define KBD_LED_AS_INDICATOR 1 +#define KBD_LED_AS_INDICATOR 0 /**===================================================== * * =========== Hotkey for output switching =========== * @@ -33,9 +33,18 @@ * * This affects how fast the mouse moves. * - * MOUSE_SPEED_FACTOR: [1-128], higher values will make very little sense, - * 16 works well for my mouse, but the option to adjust is here if you need it. + * MOUSE_SPEED_FACTOR_X: [1-128], mouse moves at this speed in X direction + * MOUSE_SPEED_FACTOR_Y: [1-128], mouse moves at this speed in Y direction + * + * MOUSE_JUMP_THRESHOLD: [0-32768], sets the "force" you need to use to drag the + * mouse to another screen, 0 meaning no force needed at all, and ~500 some force + * needed, ~1000 no accidental jumps, you need to really mean it. + * + * TODO: make this configurable per-screen. * * */ -#define MOUSE_SPEED_FACTOR 16 +#define MOUSE_SPEED_FACTOR_X 1 +#define MOUSE_SPEED_FACTOR_Y 1 + +#define MOUSE_JUMP_THRESHOLD 0 diff --git a/src/utils.c b/src/utils.c index 1e85b66..deb71fe 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1,10 +1,27 @@ +/* + * This file is part of DeskHop (https://github.com/hrvach/deskhop). + * Copyright (c) 2024 Hrvoje Cavrak + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "main.h" /**================================================== * * ============== Checksum Functions ============== * * ================================================== */ -uint8_t calc_checksum(const uint8_t* data, int length) { +uint8_t calc_checksum(const uint8_t* data, int length) { uint8_t checksum = 0; for (int i = 0; i < length; i++) { @@ -22,16 +39,15 @@ bool verify_checksum(const uart_packet_t* packet) { /**================================================== * * ============== Watchdog Functions ============== * * ================================================== */ - + void kick_watchdog(void) { - // Read the timer AFTER duplicating the core1 timestamp, - // so it doesn't get updated in the meantime. + /* Read the timer AFTER duplicating the core1 timestamp, + so it doesn't get updated in the meantime. */ uint64_t core1_last_loop_pass = global_state.core1_last_loop_pass; uint64_t current_time = time_us_64(); - // If core1 stops updating the timestamp, we'll stop kicking the watchog and reboot - if (current_time - core1_last_loop_pass < CORE1_HANG_TIMEOUT_US) + /* If core1 stops updating the timestamp, we'll stop kicking the watchog and reboot */ + if (current_time - core1_last_loop_pass < CORE1_HANG_TIMEOUT_US) watchdog_update(); } -