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();
}
-