diff --git a/src/defaults.c b/src/defaults.c index 54f25ff..e14e93a 100644 --- a/src/defaults.c +++ b/src/defaults.c @@ -14,8 +14,16 @@ const config_t default_config = { .bottom = MAX_SCREEN_COORD, }, .screen_count = 1, - .screen_index = 0, - }, + .screen_index = 1, + .os = OUTPUT_A_OS, + .pos = LEFT, + .screensaver = { + .enabled = SCREENSAVER_A_ENABLED, + .only_if_inactive = SCREENSAVER_A_ONLY_IF_INACTIVE, + .idle_time_us = SCREENSAVER_A_IDLE_TIME_SEC * 1000000, + .max_time_us = SCREENSAVER_A_MAX_TIME_SEC * 1000000, + } + }, .output[OUTPUT_B] = { .number = OUTPUT_B, @@ -26,20 +34,14 @@ const config_t default_config = { .bottom = MAX_SCREEN_COORD, }, .screen_count = 1, - .screen_index = 0, + .screen_index = 1, + .os = OUTPUT_B_OS, + .pos = RIGHT, + .screensaver = { + .enabled = SCREENSAVER_B_ENABLED, + .only_if_inactive = SCREENSAVER_B_ONLY_IF_INACTIVE, + .idle_time_us = SCREENSAVER_B_IDLE_TIME_SEC * 1000000, + .max_time_us = SCREENSAVER_B_MAX_TIME_SEC * 1000000, + } }, - .screensaver[OUTPUT_A] = - { - .enabled = SCREENSAVER_A_ENABLED, - .only_if_inactive = SCREENSAVER_A_ONLY_IF_INACTIVE, - .idle_time_us = SCREENSAVER_A_IDLE_TIME_SEC * 1000000, - .max_time_us = SCREENSAVER_A_MAX_TIME_SEC * 1000000, - }, - .screensaver[OUTPUT_B] = - { - .enabled = SCREENSAVER_B_ENABLED, - .only_if_inactive = SCREENSAVER_B_ONLY_IF_INACTIVE, - .idle_time_us = SCREENSAVER_B_IDLE_TIME_SEC * 1000000, - .max_time_us = SCREENSAVER_B_MAX_TIME_SEC * 1000000, - }, -}; +}; \ No newline at end of file diff --git a/src/handlers.c b/src/handlers.c index 95f9163..21d6ce1 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -31,18 +31,24 @@ void output_toggle_hotkey_handler(device_t *state) { switch_output(state, state->active_output); }; -/* This key combo records switch y top coordinate for different-size monitors */ -void screen_border_hotkey_handler(device_t *state) { - border_size_t *border = &state->config.output[state->active_output].border; - +void get_border_position(device_t *state, border_size_t *border) { /* To avoid having 2 different keys, if we're above half, it's the top coord */ if (state->mouse_y > (MAX_SCREEN_COORD / 2)) border->bottom = state->mouse_y; else border->top = state->mouse_y; +} + + +/* This key combo records switch y top coordinate for different-size monitors */ +void screen_border_hotkey_handler(device_t *state) { + border_size_t *border = &state->config.output[state->active_output].border; + if (CURRENT_BOARD_IS_ACTIVE_OUTPUT) { + get_border_position(state, border); + save_config(state); + } send_packet((uint8_t *)border, SYNC_BORDERS_MSG, sizeof(border_size_t)); - save_config(state); }; /* This key combo puts board A in firmware upgrade mode */ @@ -69,8 +75,8 @@ void wipe_config_hotkey_handler(device_t *state) { } void screensaver_hotkey_handler(device_t *state) { - state->config.screensaver[BOARD_ROLE].enabled ^= 1; - send_value(state->config.screensaver[BOARD_ROLE].enabled, SCREENSAVER_MSG); + state->config.output[BOARD_ROLE].screensaver.enabled ^= 1; + send_value(state->config.output[BOARD_ROLE].screensaver.enabled, SCREENSAVER_MSG); } /* When pressed, toggles the current mouse zoom mode state */ @@ -87,19 +93,18 @@ void mouse_zoom_hotkey_handler(device_t *state) { void handle_keyboard_uart_msg(uart_packet_t *packet, device_t *state) { queue_kbd_report((hid_keyboard_report_t *)packet->data, state); state->last_activity[BOARD_ROLE] = time_us_64(); - state->screensaver_max_time_reached[BOARD_ROLE] = false; } /* Function handles received mouse moves from the other board */ void handle_mouse_abs_uart_msg(uart_packet_t *packet, device_t *state) { - mouse_abs_report_t *mouse_report = (mouse_abs_report_t *)packet->data; + mouse_report_t *mouse_report = (mouse_report_t *)packet->data; queue_mouse_report(mouse_report, state); state->mouse_x = mouse_report->x; state->mouse_y = mouse_report->y; + state->mouse_buttons = mouse_report->buttons; state->last_activity[BOARD_ROLE] = time_us_64(); - state->screensaver_max_time_reached[BOARD_ROLE] = false; } /* Function handles request to switch output */ @@ -135,7 +140,14 @@ void handle_switch_lock_msg(uart_packet_t *packet, device_t *state) { /* Handle border syncing message that lets the other device know about monitor height offset */ void handle_sync_borders_msg(uart_packet_t *packet, device_t *state) { border_size_t *border = &state->config.output[state->active_output].border; - memcpy(border, packet->data, sizeof(border_size_t)); + + if (CURRENT_BOARD_IS_ACTIVE_OUTPUT) { + get_border_position(state, border); + send_packet((uint8_t *)border, SYNC_BORDERS_MSG, sizeof(border_size_t)); + } + else + memcpy(border, packet->data, sizeof(border_size_t)); + save_config(state); } @@ -151,7 +163,7 @@ void handle_wipe_config_msg(uart_packet_t *packet, device_t *state) { } void handle_screensaver_msg(uart_packet_t *packet, device_t *state) { - state->config.screensaver[BOARD_ROLE].enabled = packet->data[0]; + state->config.output[BOARD_ROLE].screensaver.enabled = packet->data[0]; } /**==================================================== * diff --git a/src/hid_parser.c b/src/hid_parser.c index c53bf90..123e3b4 100644 --- a/src/hid_parser.c +++ b/src/hid_parser.c @@ -21,7 +21,6 @@ #include "main.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 }; diff --git a/src/hid_parser.h b/src/hid_parser.h index 4ab2250..f0d62be 100644 --- a/src/hid_parser.h +++ b/src/hid_parser.h @@ -88,11 +88,11 @@ typedef struct { uint8_t usage_count; uint8_t global_usage; uint32_t offset_in_bits; - uint8_t usages[64]; + uint8_t usages[256]; uint8_t *p_usage; collection_t collection; usage_map_t *map; globals_t globals[16]; /* as tag is 4 bits, there can be 16 different tags in global header type */ -} parser_state_t; \ No newline at end of file +} parser_state_t; diff --git a/src/keyboard.c b/src/keyboard.c index 4a02531..1bf42a3 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -162,7 +162,6 @@ void send_key(hid_keyboard_report_t *report, device_t *state) { if (CURRENT_BOARD_IS_ACTIVE_OUTPUT) { queue_kbd_report(report, state); state->last_activity[BOARD_ROLE] = time_us_64(); - state->screensaver_max_time_reached[BOARD_ROLE] = false; } else { send_packet((uint8_t *)report, KEYBOARD_REPORT_MSG, KBD_REPORT_LENGTH); } diff --git a/src/main.h b/src/main.h index 5395639..a74e647 100644 --- a/src/main.h +++ b/src/main.h @@ -47,6 +47,9 @@ #define ENABLE 1 #define DISABLE 0 +#define ABSOLUTE 0 +#define RELATIVE 1 + #define MAX_REPORT_ITEMS 16 #define MOUSE_BOOT_REPORT_LEN 4 @@ -103,6 +106,9 @@ enum packet_type_e { FLASH_LED_MSG = 9, SCREENSAVER_MSG = 10, WIPE_CONFIG_MSG = 11, + REL_MOUSE_REPORT_MSG = 12, + SWAP_OUTPUTS_MSG = 13, + HEARTBEAT_MSG = 14, }; /* @@ -146,7 +152,25 @@ typedef struct { /********* Configuration storage definitions **********/ -#define CURRENT_CONFIG_VERSION 3 +#define CURRENT_CONFIG_VERSION 4 + +enum os_type_e { + LINUX = 1, + MACOS = 2, + WINDOWS = 3, + OTHER = 255, +}; + +enum screen_pos_e { + LEFT = 1, + RIGHT = 2, + MIDDLE = 3, +}; + +enum itf_num_e { + ITF_NUM_HID = 0, + ITF_NUM_HID_REL_M = 1, +}; typedef struct { int top; // When jumping from a smaller to a bigger screen, go to THIS top height @@ -154,16 +178,6 @@ typedef struct { // height } border_size_t; -/* Define output parameters */ -typedef struct { - int number; // Number of this output (e.g. OUTPUT_A = 0 etc) - int screen_count; // How many monitors per output (e.g. Output A is Windows with 3 monitors) - int screen_index; // Current active screen - int speed_x; // Mouse speed per output, in direction X - int speed_y; // Mouse speed per output, in direction Y - border_size_t border; // Screen border size/offset to keep cursor at same height when switching -} output_t; - /* Define screensaver parameters */ typedef struct { uint8_t enabled; @@ -172,13 +186,26 @@ typedef struct { uint64_t max_time_us; } screensaver_t; +/* Define output parameters */ +typedef struct { + int number; // Number of this output (e.g. OUTPUT_A = 0 etc) + int screen_count; // How many monitors per output (e.g. Output A is Windows with 3 monitors) + int screen_index; // Current active screen + int speed_x; // Mouse speed per output, in direction X + int speed_y; // Mouse speed per output, in direction Y + border_size_t border; // Screen border size/offset to keep cursor at same height when switching + enum os_type_e os; // Operating system on this output + enum screen_pos_e pos; // Screen position on this output + screensaver_t screensaver; // Screensaver parameters for this output +} output_t; + /* Data structure defining how configuration is stored */ typedef struct { uint32_t magic_header; uint32_t version; uint8_t force_mouse_boot_mode; output_t output[NUM_SCREENS]; - screensaver_t screensaver[NUM_SCREENS]; + uint8_t screensaver_enabled; // Keep checksum at the end of the struct uint32_t checksum; } config_t; @@ -211,8 +238,8 @@ typedef struct TU_ATTR_PACKED { int16_t x; int16_t y; int8_t wheel; - int8_t pan; -} mouse_abs_report_t; + uint8_t mode; +} mouse_report_t; typedef enum { IDLE, READING_PACKET, PROCESSING_PACKET } receiver_state_t; @@ -222,13 +249,13 @@ typedef struct { uint8_t keyboard_leds[NUM_SCREENS]; // State of keyboard LEDs (index 0 = A, index 1 = B) uint64_t last_activity[NUM_SCREENS]; // Timestamp of the last input activity (-||-) - bool screensaver_max_time_reached[NUM_SCREENS]; // Screensaver maximum time has been reached (will be reset at the next input activity) receiver_state_t receiver_state; // Storing the state for the simple receiver state machine uint64_t core1_last_loop_pass; // Timestamp of last core1 loop execution uint8_t active_output; // Currently selected output (0 = A, 1 = B) int16_t mouse_x; // Store and update the location of our mouse pointer int16_t mouse_y; + int16_t mouse_buttons; // Store and update the state of mouse buttons config_t config; // Device configuration, loaded from flash or defaults used mouse_t mouse_dev; // Mouse device specifics, e.g. stores locations for keys in report @@ -241,9 +268,9 @@ typedef struct { bool mouse_connected; // True when a mouse is connected locally /* Feature flags */ - bool mouse_zoom; // True when "mouse zoom" is enabled - bool switch_lock; // True when device is prevented from switching - bool onboard_led_state; // True when LED is ON + bool mouse_zoom; // True when "mouse zoom" is enabled + bool switch_lock; // True when device is prevented from switching + bool onboard_led_state; // True when LED is ON /* Onboard LED blinky (provide feedback when e.g. mouse connected) */ int32_t blinks_left; // How many blink transitions are left @@ -265,16 +292,15 @@ void process_kbd_queue_task(device_t *); void send_key(hid_keyboard_report_t *, device_t *); /********* Mouse **********/ -bool tud_hid_abs_mouse_report( - uint8_t report_id, uint8_t buttons, int16_t x, int16_t y, int8_t vertical, int8_t horizontal); +bool tud_mouse_report(uint8_t mode, uint8_t buttons, int16_t x, int16_t y, int8_t wheel); void process_mouse_report(uint8_t *, int, device_t *); 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_t *); -void queue_mouse_report(mouse_abs_report_t *, device_t *); -void output_mouse_report(mouse_abs_report_t *, device_t *); +void queue_mouse_report(mouse_report_t *, device_t *); +void output_mouse_report(mouse_report_t *, device_t *); /********* UART **********/ void receive_char(uart_packet_t *, device_t *); diff --git a/src/mouse.c b/src/mouse.c index c2f2f3e..40059ff 100644 --- a/src/mouse.c +++ b/src/mouse.c @@ -46,14 +46,16 @@ void update_mouse_position(device_t *state, mouse_values_t *values) { /* Update movement */ state->mouse_x = move_and_keep_on_screen(state->mouse_x, offset_x); state->mouse_y = move_and_keep_on_screen(state->mouse_y, offset_y); + + /* Update buttons state */ + state->mouse_buttons = values->buttons; } /* If we are active output, queue packet to mouse queue, else send them through UART */ -void output_mouse_report(mouse_abs_report_t *report, device_t *state) { +void output_mouse_report(mouse_report_t *report, device_t *state) { if (CURRENT_BOARD_IS_ACTIVE_OUTPUT) { queue_mouse_report(report, state); state->last_activity[BOARD_ROLE] = time_us_64(); - state->screensaver_max_time_reached[BOARD_ROLE] = false; } else { send_packet((uint8_t *)report, MOUSE_REPORT_MSG, MOUSE_REPORT_LENGTH); } @@ -90,31 +92,81 @@ int16_t scale_y_coordinate(int screen_from, int screen_to, device_t *state) { return ((state->mouse_y - from->border.top) * MAX_SCREEN_COORD) / size_from; } -void switch_screen(device_t *state, int new_x, int output_from, int output_to) { - mouse_abs_report_t hidden_pointer = {.y = MIN_SCREEN_COORD, .x = MAX_SCREEN_COORD}; +void switch_screen(device_t *state, output_t *output, int new_x, int output_from, int output_to, int direction) { + mouse_report_t hidden_pointer = {.y = MIN_SCREEN_COORD, .x = MAX_SCREEN_COORD}; output_mouse_report(&hidden_pointer, state); switch_output(state, output_to); - state->mouse_x = (output_to == OUTPUT_A) ? MIN_SCREEN_COORD : MAX_SCREEN_COORD; - state->mouse_y = scale_y_coordinate(output_from, output_to, state); + state->mouse_x = (direction == LEFT) ? MAX_SCREEN_COORD : MIN_SCREEN_COORD; + state->mouse_y = scale_y_coordinate(output->number, 1 - output->number, state); } -void check_screen_switch(const mouse_values_t *values, device_t *state) { - int new_x = state->mouse_x + values->move_x; +void switch_desktop(device_t *state, output_t *output, int new_index, int direction) { + switch (output->os) { + case MACOS: + /* Send relative mouse movement here as well, one or two pixels in the direction of + movement, BEFORE absolute report sets X to 0 */ + mouse_report_t move_relative_one = {.x = (direction == LEFT) ? 16384-2 : 16384+2, .mode = RELATIVE}; - /* No switching allowed if explicitly disabled */ - if (state->switch_lock) + /* Once doesn't seem reliable enough, do it twice */ + output_mouse_report(&move_relative_one, state); + output_mouse_report(&move_relative_one, state); + + break; + + case WINDOWS: + /* TODO: Switch to relative-only if index > 1, but keep tabs to switch back */ + break; + + case LINUX: + case OTHER: + /* Linux should treat all desktops as a single virtual screen, so you should leave + screen_count at 1 and it should just work */ + break; + } + + state->mouse_x = (direction == RIGHT) ? MIN_SCREEN_COORD : MAX_SCREEN_COORD; + output->screen_index = new_index; +} + +/* BORDER + | + .---------. .---------. | .---------. .---------. .---------. + || B2 || || B1 || | || A1 || || A2 || || A3 || (output, index) + || extra || || main || | || main || || extra || || extra || (main or extra) + '---------' '---------' | '---------' '---------' '---------' + )___( )___( | )___( )___( )___( +*/ +void check_screen_switch(const mouse_values_t *values, device_t *state) { + int new_x = state->mouse_x + values->move_x; + output_t *output = &state->config.output[state->active_output]; + + bool jump_left = new_x < MIN_SCREEN_COORD - JUMP_THRESHOLD; + bool jump_right = new_x > MAX_SCREEN_COORD + JUMP_THRESHOLD; + + int direction = jump_left ? LEFT : RIGHT; + + /* No switching allowed if explicitly disabled or mouse button is held */ + if (state->switch_lock || state->mouse_buttons) return; - /* End of screen left switches screen A->B TODO: make configurable */ - if (new_x < MIN_SCREEN_COORD - JUMP_THRESHOLD && state->active_output == OUTPUT_A) { - switch_screen(state, new_x, OUTPUT_A, OUTPUT_B); + /* No jump condition met == nothing to do, return */ + if (!jump_left && !jump_right) + return; + + /* We want to jump in the direction of the other computer */ + if (output->pos != direction) { + if (output->screen_index == 1) /* We are at the border -> switch outputs */ + switch_screen(state, output, new_x, state->active_output, 1 - state->active_output, direction); + + /* If here, this output has multiple desktops and we are not on the main one */ + else + switch_desktop(state, output, output->screen_index - 1, direction); } - /* End of screen right switches screen B->A TODO: make configurable */ - else if (new_x > MAX_SCREEN_COORD + JUMP_THRESHOLD && state->active_output == OUTPUT_B) { - switch_screen(state, new_x, OUTPUT_B, OUTPUT_A); - } + /* We want to jump away from the other computer, only possible if there is another screen to jump to */ + else if (output->screen_index < output->screen_count) + switch_desktop(state, output, output->screen_index + 1, direction); } void extract_report_values(uint8_t *raw_report, device_t *state, mouse_values_t *values) { @@ -139,12 +191,13 @@ void extract_report_values(uint8_t *raw_report, device_t *state, mouse_values_t values->buttons = get_report_value(raw_report, &state->mouse_dev.buttons); } -mouse_abs_report_t create_mouse_report(device_t *state, mouse_values_t *values) { - mouse_abs_report_t abs_mouse_report = {.buttons = values->buttons, +mouse_report_t create_mouse_report(device_t *state, mouse_values_t *values) { + mouse_report_t abs_mouse_report = {.buttons = values->buttons, .x = state->mouse_x, .y = state->mouse_y, .wheel = values->wheel, - .pan = 0}; + .mode = ABSOLUTE, + }; return abs_mouse_report; } @@ -158,7 +211,7 @@ void process_mouse_report(uint8_t *raw_report, int len, device_t *state) { update_mouse_position(state, &values); /* Create the report for the output PC based on the updated values */ - mouse_abs_report_t report = create_mouse_report(state, &values); + mouse_report_t report = create_mouse_report(state, &values); /* Move the mouse, depending where the output is supposed to go */ output_mouse_report(&report, state); @@ -172,7 +225,7 @@ void process_mouse_report(uint8_t *raw_report, int len, device_t *state) { * ==================================================== */ void process_mouse_queue_task(device_t *state) { - mouse_abs_report_t report = {0}; + mouse_report_t report = {0}; /* We need to be connected to the host to send messages */ if (!state->tud_connected) @@ -187,15 +240,15 @@ void process_mouse_queue_task(device_t *state) { tud_remote_wakeup(); /* ... 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); + bool succeeded = tud_mouse_report( + report.mode, report.buttons, report.x, report.y, report.wheel); /* ... then we can remove it from the queue */ if (succeeded) queue_try_remove(&state->mouse_queue, &report); } -void queue_mouse_report(mouse_abs_report_t *report, device_t *state) { +void queue_mouse_report(mouse_report_t *report, device_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; diff --git a/src/setup.c b/src/setup.c index 931cf93..333a44a 100644 --- a/src/setup.c +++ b/src/setup.c @@ -54,6 +54,12 @@ 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 = PIO_USB_DP_PIN_DEFAULT; + + /* Make HID protocol the default for port B as a fix for devices enumerating + themselves as both keyboards and mice, but having just a single common mode */ + if(BOARD_ROLE == OUTPUT_B) + tuh_hid_set_default_protocol(HID_PROTOCOL_REPORT); + tuh_configure(BOARD_TUH_RHPORT, TUH_CFGID_RPI_PIO_USB_CONFIGURATION, &config); /* Initialize and configure TinyUSB Host */ @@ -80,15 +86,15 @@ void initial_setup(device_t *state) { /* Initialize keyboard and mouse queues */ queue_init(&state->kbd_queue, sizeof(hid_keyboard_report_t), KBD_QUEUE_LENGTH); - queue_init(&state->mouse_queue, sizeof(mouse_abs_report_t), MOUSE_QUEUE_LENGTH); + queue_init(&state->mouse_queue, sizeof(mouse_report_t), MOUSE_QUEUE_LENGTH); /* Setup RP2040 Core 1 */ multicore_reset_core1(); multicore_launch_core1(core1_main); /* Initialize and configure TinyUSB Device */ - tud_init(BOARD_TUD_RHPORT); - + tud_init(BOARD_TUD_RHPORT); + /* Initialize and configure TinyUSB Host */ pio_usb_host_config(); diff --git a/src/tusb_config.h b/src/tusb_config.h index 9c306c9..9afff70 100644 --- a/src/tusb_config.h +++ b/src/tusb_config.h @@ -49,11 +49,6 @@ extern "C" { #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 -#endif - // RHPort number used for device can be defined by board.mk, default to port 0 #ifndef BOARD_DEVICE_RHPORT_NUM #define BOARD_DEVICE_RHPORT_NUM 0 @@ -62,22 +57,13 @@ extern "C" { // RHPort max operational speed can defined by board.mk // Default to Highspeed for MCU with internal HighSpeed PHY (can be port specific), otherwise FullSpeed #ifndef BOARD_DEVICE_RHPORT_SPEED -#if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX \ - || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || CFG_TUSB_MCU == OPT_MCU_NUC505 \ - || CFG_TUSB_MCU == OPT_MCU_CXD56 || CFG_TUSB_MCU == OPT_MCU_SAMX7X) -#define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_HIGH_SPEED -#else #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED #endif -#endif // Device mode with rhport and speed defined by board.mk #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 - /* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. * Tinyusb use follows macros to declare transferring memory so that they can be put * into those specific section. @@ -101,9 +87,25 @@ extern "C" { #define CFG_TUD_ENDPOINT0_SIZE 64 #endif +//------------- DEBUG -------------// +#ifdef DH_DEBUG +#define CFG_TUD_CDC 1 +#define CFG_TUSB_DEBUG 2 +#define CFG_TUD_LOG_LEVEL 3 +#define CFG_TUSB_DEBUG_PRINTF dh_debug_printf +extern int dh_debug_printf(const char *__restrict __format, ...); + +#define CFG_TUD_CDC_RX_BUFSIZE 64 +#define CFG_TUD_CDC_TX_BUFSIZE 64 +#define CFG_TUH_CDC_LINE_CODING_ON_ENUM \ + { 921600, CDC_LINE_CONDING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 } + +#else +#define CFG_TUD_CDC 0 +#endif + //------------- CLASS -------------// -#define CFG_TUD_HID 1 -#define CFG_TUD_CDC 0 +#define CFG_TUD_HID 2 #define CFG_TUD_MSC 0 #define CFG_TUD_MIDI 0 #define CFG_TUD_VENDOR 0 @@ -116,7 +118,7 @@ extern "C" { //-------------------------------------------------------------------- // Size of buffer to hold descriptors and other data used for enumeration -#define CFG_TUH_ENUMERATION_BUFSIZE 256 +#define CFG_TUH_ENUMERATION_BUFSIZE 512 #define CFG_TUH_HUB 1 // max device support (excluding hub device) diff --git a/src/usb.c b/src/usb.c index fe6f0bd..3fb3f27 100644 --- a/src/usb.c +++ b/src/usb.c @@ -114,7 +114,9 @@ void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_re 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); - } + } + + global_state.mouse_dev.protocol = tuh_hid_get_protocol(dev_addr, instance); parse_report_descriptor(&global_state.mouse_dev, MAX_REPORTS, desc_report, desc_len); global_state.mouse_connected = true; diff --git a/src/usb_descriptors.c b/src/usb_descriptors.c index 7cc26fd..4d53fe4 100644 --- a/src/usb_descriptors.c +++ b/src/usb_descriptors.c @@ -59,132 +59,66 @@ uint8_t const *tud_descriptor_device_cb(void) { // HID Report Descriptor //--------------------------------------------------------------------+ +// Relative mouse is used to overcome limitations of multiple desktops on MacOS and Windows + uint8_t const desc_hid_report[] = {TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(REPORT_ID_KEYBOARD)), TUD_HID_REPORT_DESC_ABSMOUSE(HID_REPORT_ID(REPORT_ID_MOUSE))}; +uint8_t const desc_hid_report_relmouse[] = {TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(REPORT_ID_RELMOUSE))}; + // Invoked when received GET HID REPORT DESCRIPTOR // Application return pointer to descriptor // Descriptor contents must exist long enough for transfer to complete uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance) { - (void)instance; + if (instance == ITF_NUM_HID_REL_M) { + return desc_hid_report_relmouse; + } + + /* Default */ return desc_hid_report; } -bool tud_hid_n_abs_mouse_report(uint8_t instance, - uint8_t report_id, - uint8_t buttons, - int16_t x, - int16_t y, - int8_t vertical, - int8_t horizontal) { - mouse_abs_report_t report = {.buttons = buttons, .x = x, .y = y, .wheel = vertical, .pan = horizontal}; - return tud_hid_n_report(instance, report_id, &report, sizeof(report)); +bool tud_mouse_report(uint8_t mode, + uint8_t buttons, + int16_t x, + int16_t y, + int8_t wheel) { + + if (mode == ABSOLUTE) { + mouse_report_t report = {.buttons = buttons, .x = x, .y = y, .wheel = wheel}; + return tud_hid_n_report(ITF_NUM_HID, REPORT_ID_MOUSE, &report, sizeof(report)); + } + else { + hid_mouse_report_t report = {.buttons = buttons, .x = x - 16384, .y = y - 16384, .wheel = wheel, .pan = 0}; + return tud_hid_n_report(ITF_NUM_HID_REL_M, REPORT_ID_RELMOUSE, &report, sizeof(report)); + } } -bool tud_hid_abs_mouse_report( - uint8_t report_id, uint8_t buttons, int16_t x, int16_t y, int8_t vertical, int8_t horizontal) { - return tud_hid_n_abs_mouse_report(0, report_id, buttons, x, y, vertical, horizontal); -} - -//--------------------------------------------------------------------+ -// Configuration Descriptor -//--------------------------------------------------------------------+ - -enum { ITF_NUM_HID, ITF_NUM_TOTAL }; - -#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN) - -#define EPNUM_HID 0x81 - -uint8_t const desc_configuration[] = { - // Config number, interface count, string index, total length, attribute, power in mA - TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 500), - - // Interface number, string index, protocol, report descriptor len, EP In address, size & - // polling interval - TUD_HID_DESCRIPTOR(ITF_NUM_HID, - 0, - HID_ITF_PROTOCOL_NONE, - sizeof(desc_hid_report), - EPNUM_HID, - CFG_TUD_HID_EP_BUFSIZE, - 1)}; - -#if TUD_OPT_HIGH_SPEED -// Per USB specs: high speed capable device must report device_qualifier and -// other_speed_configuration - -// other speed configuration -uint8_t desc_other_speed_config[CONFIG_TOTAL_LEN]; - -// device qualifier is mostly similar to device descriptor since we don't change configuration based -// on speed -tusb_desc_device_qualifier_t const desc_device_qualifier = {.bLength = sizeof(tusb_desc_device_qualifier_t), - .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, - .bcdUSB = USB_BCD, - - .bDeviceClass = 0x00, - .bDeviceSubClass = 0x00, - .bDeviceProtocol = 0x00, - - .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, - .bNumConfigurations = 0x01, - .bReserved = 0x00}; - -// Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request -// Application return pointer to descriptor, whose contents must exist long enough for transfer to -// complete. device_qualifier descriptor describes information about a high-speed capable device -// that would change if the device were operating at the other speed. If not highspeed capable stall -// this request. -uint8_t const *tud_descriptor_device_qualifier_cb(void) { - return (uint8_t const *)&desc_device_qualifier; -} - -// Invoked when received GET OTHER SEED CONFIGURATION DESCRIPTOR request -// Application return pointer to descriptor, whose contents must exist long enough for transfer to -// complete Configuration descriptor in the other speed e.g if high speed then this is for full -// speed and vice versa -uint8_t const *tud_descriptor_other_speed_configuration_cb(uint8_t index) { - (void)index; // for multiple configurations - - // other speed config is basically configuration with type = OHER_SPEED_CONFIG - memcpy(desc_other_speed_config, desc_configuration, CONFIG_TOTAL_LEN); - desc_other_speed_config[1] = TUSB_DESC_OTHER_SPEED_CONFIG; - - // this example use the same configuration for both high and full speed mode - return desc_other_speed_config; -} - -#endif // highspeed - -// Invoked when received GET CONFIGURATION DESCRIPTOR -// Application return pointer to descriptor -// Descriptor contents must exist long enough for transfer to complete -uint8_t const *tud_descriptor_configuration_cb(uint8_t index) { - (void)index; // for multiple configurations - - // This example use the same configuration for both high and full speed mode - return desc_configuration; -} //--------------------------------------------------------------------+ // String Descriptors //--------------------------------------------------------------------+ -// String Descriptor Index -enum { - STRID_LANGID = 0, - STRID_MANUFACTURER, - STRID_PRODUCT, - STRID_SERIAL, -}; - // array of pointer to string descriptors char const *string_desc_arr[] = { (const char[]){0x09, 0x04}, // 0: is supported language is English (0x0409) "Hrvoje Cavrak", // 1: Manufacturer "DeskHop Switch", // 2: Product "0", // 3: Serials, should use chip ID + "MouseHelper", // 4: Relative mouse to work around OS issues +#ifdef DH_DEBUG + "Debug Interface", // 5: Debug Interface +#endif +}; + +// String Descriptor Index +enum { + STRID_LANGID = 0, + STRID_MANUFACTURER, + STRID_PRODUCT, + STRID_SERIAL, + STRID_MOUSE, + STRID_DEBUG, }; static uint16_t _desc_str[32]; @@ -225,3 +159,60 @@ uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { return _desc_str; } + +//--------------------------------------------------------------------+ +// Configuration Descriptor +//--------------------------------------------------------------------+ + +#define EPNUM_HID 0x81 +#define EPNUM_HID_REL_M 0x82 + +#ifndef DH_DEBUG + +enum { ITF_NUM_TOTAL = 2 }; +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN + TUD_HID_DESC_LEN) + +#else + +enum { ITF_NUM_CDC = 2, ITF_NUM_TOTAL = 3 }; +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN + TUD_HID_DESC_LEN + TUD_CDC_DESC_LEN) +#define EPNUM_CDC_NOTIF 0x83 +#define EPNUM_CDC_OUT 0x04 +#define EPNUM_CDC_IN 0x84 + +#endif + + +uint8_t const desc_configuration[] = { + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 500), + + // Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval + TUD_HID_DESCRIPTOR(ITF_NUM_HID, + STRID_PRODUCT, + HID_ITF_PROTOCOL_NONE, + sizeof(desc_hid_report), + EPNUM_HID, + CFG_TUD_HID_EP_BUFSIZE, + 1), + + TUD_HID_DESCRIPTOR(ITF_NUM_HID_REL_M, + STRID_MOUSE, + HID_ITF_PROTOCOL_NONE, + sizeof(desc_hid_report_relmouse), + EPNUM_HID_REL_M, + CFG_TUD_HID_EP_BUFSIZE, + 20), + +#ifdef DH_DEBUG + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_CDC_DESCRIPTOR( + ITF_NUM_CDC, STRID_DEBUG, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, CFG_TUD_CDC_EP_BUFSIZE), +#endif + +}; + +uint8_t const *tud_descriptor_configuration_cb(uint8_t index) { + (void)index; // for multiple configurations + return desc_configuration; +} diff --git a/src/usb_descriptors.h b/src/usb_descriptors.h index a05b53a..197373b 100644 --- a/src/usb_descriptors.h +++ b/src/usb_descriptors.h @@ -29,10 +29,14 @@ enum { REPORT_ID_KEYBOARD = 1, REPORT_ID_MOUSE, - REPORT_ID_CONSUMER_CONTROL, REPORT_ID_COUNT }; +enum +{ + REPORT_ID_RELMOUSE = 1, +}; + #define TUD_HID_REPORT_DESC_ABSMOUSE(...) \ HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ HID_USAGE ( HID_USAGE_DESKTOP_MOUSE ) ,\ @@ -74,92 +78,12 @@ HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ HID_REPORT_COUNT( 1 ) ,\ HID_REPORT_SIZE ( 8 ) ,\ HID_INPUT ( HID_DATA | HID_VARIABLE | HID_RELATIVE ) ,\ - HID_USAGE_PAGE ( HID_USAGE_PAGE_CONSUMER ), \ \ - /* Horizontal wheel scroll [-127, 127] */ \ - HID_USAGE_N ( HID_USAGE_CONSUMER_AC_PAN, 2 ), \ - HID_LOGICAL_MIN ( 0x81 ), \ - HID_LOGICAL_MAX ( 0x7f ), \ + /* Mouse mode (0 = absolute, 1 = relative) */ \ HID_REPORT_COUNT( 1 ), \ HID_REPORT_SIZE ( 8 ), \ - HID_INPUT ( HID_DATA | HID_VARIABLE | HID_RELATIVE ), \ + HID_INPUT ( HID_CONSTANT ), \ HID_COLLECTION_END , \ HID_COLLECTION_END \ -/* Generated report */ -/* */ -/* 0x05, 0x01, Usage Page (Desktop), */ -/* 0x09, 0x06, Usage (Keyboard), */ -/* 0xA1, 0x01, Collection (Application), */ -/* 0x85, 0x01, Report ID (1), */ -/* 0x05, 0x07, Usage Page (Keyboard), */ -/* 0x19, 0xE0, Usage Minimum (KB Leftcontrol), */ -/* 0x29, 0xE7, Usage Maximum (KB Right GUI), */ -/* 0x15, 0x00, Logical Minimum (0), */ -/* 0x25, 0x01, Logical Maximum (1), */ -/* 0x95, 0x08, Report Count (8), */ -/* 0x75, 0x01, Report Size (1), */ -/* 0x81, 0x02, Input (Variable), */ -/* 0x95, 0x01, Report Count (1), */ -/* 0x75, 0x08, Report Size (8), */ -/* 0x81, 0x01, Input (Constant), */ -/* 0x05, 0x08, Usage Page (LED), */ -/* 0x19, 0x01, Usage Minimum (01h), */ -/* 0x29, 0x05, Usage Maximum (05h), */ -/* 0x95, 0x05, Report Count (5), */ -/* 0x75, 0x01, Report Size (1), */ -/* 0x91, 0x02, Output (Variable), */ -/* 0x95, 0x01, Report Count (1), */ -/* 0x75, 0x03, Report Size (3), */ -/* 0x91, 0x01, Output (Constant), */ -/* 0x05, 0x07, Usage Page (Keyboard), */ -/* 0x19, 0x00, Usage Minimum (None), */ -/* 0x2A, 0xFF, 0x00, Usage Maximum (FFh), */ -/* 0x15, 0x00, Logical Minimum (0), */ -/* 0x26, 0xFF, 0x00, Logical Maximum (255), */ -/* 0x95, 0x06, Report Count (6), */ -/* 0x75, 0x08, Report Size (8), */ -/* 0x81, 0x00, Input, */ -/* 0xC0, End Collection, */ -/* 0x05, 0x01, Usage Page (Desktop), */ -/* 0x09, 0x02, Usage (Mouse), */ -/* 0xA1, 0x01, Collection (Application), */ -/* 0x85, 0x02, Report ID (2), */ -/* 0x09, 0x01, Usage (Pointer), */ -/* 0xA1, 0x00, Collection (Physical), */ -/* 0x05, 0x09, Usage Page (Button), */ -/* 0x19, 0x01, Usage Minimum (01h), */ -/* 0x29, 0x05, Usage Maximum (05h), */ -/* 0x15, 0x00, Logical Minimum (0), */ -/* 0x25, 0x01, Logical Maximum (1), */ -/* 0x95, 0x05, Report Count (5), */ -/* 0x75, 0x01, Report Size (1), */ -/* 0x81, 0x02, Input (Variable), */ -/* 0x95, 0x01, Report Count (1), */ -/* 0x75, 0x03, Report Size (3), */ -/* 0x81, 0x01, Input (Constant), */ -/* 0x05, 0x01, Usage Page (Desktop), */ -/* 0x09, 0x30, Usage (X), */ -/* 0x09, 0x31, Usage (Y), */ -/* 0x15, 0x00, Logical Minimum (0), */ -/* 0x26, 0xFF, 0x7F, Logical Maximum (32767), */ -/* 0x75, 0x10, Report Size (16), */ -/* 0x95, 0x02, Report Count (2), */ -/* 0x81, 0x02, Input (Variable), */ -/* 0x09, 0x38, Usage (Wheel), */ -/* 0x15, 0x81, Logical Minimum (-127), */ -/* 0x25, 0x7F, Logical Maximum (127), */ -/* 0x95, 0x01, Report Count (1), */ -/* 0x75, 0x08, Report Size (8), */ -/* 0x81, 0x06, Input (Variable, Relative), */ -/* 0x05, 0x0C, Usage Page (Consumer), */ -/* 0x0A, 0x38, 0x02, Usage (AC Pan), */ -/* 0x15, 0x81, Logical Minimum (-127), */ -/* 0x25, 0x7F, Logical Maximum (127), */ -/* 0x95, 0x01, Report Count (1), */ -/* 0x75, 0x08, Report Size (8), */ -/* 0x81, 0x06, Input (Variable, Relative), */ -/* 0xC0, End Collection, */ -/* 0xC0 End Collection */ - #endif /* USB_DESCRIPTORS_H_ */ diff --git a/src/user_config.h b/src/user_config.h index da424fe..5cf4bb1 100644 --- a/src/user_config.h +++ b/src/user_config.h @@ -29,6 +29,7 @@ * a key that is unlikely to ever appear on a keyboard that you will use. * HID_KEY_F24 is probably a good choice as keyboards with 24 function keys * are rare. + * * */ #define HOTKEY_TOGGLE HID_KEY_CAPS_LOCK @@ -54,13 +55,12 @@ #define MOUSE_SPEED_A_FACTOR_X 16 #define MOUSE_SPEED_A_FACTOR_Y 16 -/* Output B values */ +/* Output B values */ #define MOUSE_SPEED_B_FACTOR_X 16 #define MOUSE_SPEED_B_FACTOR_Y 16 #define JUMP_THRESHOLD 0 - /**================================================== * * ============== Screensaver Config ============== * * ================================================== * @@ -104,8 +104,8 @@ * **/ -#define SCREENSAVER_A_ENABLED 1 -#define SCREENSAVER_B_ENABLED 0 +#define SCREENSAVER_A_ENABLED 0 +#define SCREENSAVER_B_ENABLED 0 /**================================================== * * @@ -114,19 +114,18 @@ * **/ -#define SCREENSAVER_A_IDLE_TIME_SEC 60 +#define SCREENSAVER_A_IDLE_TIME_SEC 240 #define SCREENSAVER_B_IDLE_TIME_SEC 240 /**================================================== * * - * SCREENSAVER_{A|B}_MAX_TIME_SEC: Number of seconds that an output - * can be inactive before the screensaver mode will be deactivated. If - * zero, the screensaver will run indefinitely. + * SCREENSAVER_{A|B}_MAX_TIME_SEC: Number of seconds that the screensaver + * will run on an output before being deactivated. 0 for indefinitely. * **/ -#define SCREENSAVER_A_MAX_TIME_SEC 120 -#define SCREENSAVER_B_MAX_TIME_SEC 0 +#define SCREENSAVER_A_MAX_TIME_SEC 0 +#define SCREENSAVER_B_MAX_TIME_SEC 0 /**================================================== * * @@ -135,5 +134,22 @@ * **/ -#define SCREENSAVER_A_ONLY_IF_INACTIVE 1 -#define SCREENSAVER_B_ONLY_IF_INACTIVE 1 +#define SCREENSAVER_A_ONLY_IF_INACTIVE 0 +#define SCREENSAVER_B_ONLY_IF_INACTIVE 0 + +/**================================================== * + * ================ Output OS Config =============== * + * ================================================== + * + * Defines OS an output connects to. You will need to worry about this only if you have + * multiple desktops and one of your outputs is MacOS or Windows. + * + * Available options: LINUX, MACOS, WINDOWS, OTHER (check main.h for details) + * + * OUTPUT_A_OS: OS for output A + * OUTPUT_B_OS: OS for output B + * + * */ + +#define OUTPUT_A_OS LINUX +#define OUTPUT_B_OS LINUX diff --git a/src/utils.c b/src/utils.c index 52cccea..3e82925 100644 --- a/src/utils.c +++ b/src/utils.c @@ -104,56 +104,37 @@ void save_config(device_t *state) { restore_interrupts(ints); } -/* Have something fun and entertaining when idle */ +/* Have something fun and entertaining when idle. */ void screensaver_task(device_t *state) { - const int mouse_move_delay = 5000; + const int mouse_move_delay = 5000; + screensaver_t *screensaver = &state->config.output[BOARD_ROLE].screensaver; - static mouse_abs_report_t report = {.x = 0, .y = 0}; - static int last_pointer_move = 0; + static mouse_report_t report = {.x = 0, .y = 0}; + static int last_pointer_move = 0; - uint64_t current_time = time_us_64(); - static uint64_t last_activation_time = 0; + uint64_t current_time = time_us_64(); + uint64_t inactivity_period = current_time - state->last_activity[BOARD_ROLE]; /* "Randomly" chosen initial values */ static int dx = 20; static int dy = 25; - /* If the maximum time has been reached, nothing to do here. */ - if (state->screensaver_max_time_reached[BOARD_ROLE]) { - return; - } - /* If we're not enabled, nothing to do here. */ - if (!state->config.screensaver[BOARD_ROLE].enabled) { - last_activation_time = 0; - return; - } + if (!screensaver->enabled) + return; + + /* System is still not idle for long enough to activate or we've been running for too long */ + if (inactivity_period < screensaver->idle_time_us) + return; + + /* We exceeded the maximum permitted screensaver runtime */ + if (screensaver->max_time_us + && inactivity_period > (screensaver->max_time_us + screensaver->idle_time_us)) + return; /* If we're not the selected output and that is required, nothing to do here. */ - if (state->config.screensaver[BOARD_ROLE].only_if_inactive && - CURRENT_BOARD_IS_ACTIVE_OUTPUT) { - last_activation_time = 0; + if (screensaver->only_if_inactive && CURRENT_BOARD_IS_ACTIVE_OUTPUT) return; - } - - /* We are enabled, but idle time still too small to activate. */ - if ((current_time - state->last_activity[BOARD_ROLE]) < - state->config.screensaver[BOARD_ROLE].idle_time_us) { - last_activation_time = 0; - return; - } - - if (last_activation_time == 0) { - last_activation_time = current_time; - } else { - /* We are enabled, but max time has been reached. */ - if ((current_time - last_activation_time) > - state->config.screensaver[BOARD_ROLE].max_time_us) { - state->screensaver_max_time_reached[BOARD_ROLE] = true; - last_activation_time = 0; - return; - } - } /* We're active! Now check if it's time to move the cursor yet. */ if ((time_us_32()) - last_pointer_move < mouse_move_delay) @@ -175,3 +156,23 @@ void screensaver_task(device_t *state) { /* Update timer of the last pointer move */ last_pointer_move = time_us_32(); } + +/* ================================================== * + * Debug functions + * ================================================== */ +#ifdef DH_DEBUG + +int dh_debug_printf(const char *format, ...) { + va_list args; + va_start(args, format); + char buffer[512]; + + int string_len = vsnprintf(buffer, 512, format, args); + + tud_cdc_n_write(0, buffer, string_len); + tud_cdc_write_flush(); + + va_end(args); +} + +#endif \ No newline at end of file