From a249aa50f17f5fad8b0394c94e419841df650bb4 Mon Sep 17 00:00:00 2001 From: Hrvoje Cavrak Date: Sat, 17 Aug 2024 18:47:36 +0200 Subject: [PATCH] DeskHop 0.63 (Bugfixes, small features) - add gaming mode (use left shift + right shift + G to toggle) - rework HID queue, smoother operation of rotary dials (no packets lost) - fix dragging across multiple screens on the same output - improve read reliability for UI - move default keyboard hotkey for output switching to LCtrl + Caps Lock - change default X/Y speed to match 16:9 geometry --- CMakeLists.txt | 2 +- README.md | 9 +- disk/disk.img | Bin 65536 -> 65536 bytes src/handlers.c | 30 ++- src/include/main.h | 35 +++- src/include/usb_descriptors.h | 44 +++++ src/include/user_config.h | 14 +- src/keyboard.c | 13 +- src/main.c | 2 +- src/mouse.c | 30 +-- src/protocol.c | 37 +++- src/setup.c | 6 +- src/tasks.c | 20 +- src/uart.c | 2 + src/usb_descriptors.c | 17 +- src/utils.c | 1 + webconfig/Makefile | 2 +- webconfig/config-unpacked.htm | 335 +++++++++++++++++----------------- webconfig/config.htm | 2 +- webconfig/form.py | 16 +- webconfig/render.py | 10 +- webconfig/templates/form.html | 14 +- webconfig/templates/main.html | 24 +-- webconfig/templates/script.js | 61 +++---- webconfig/templates/style.css | 4 +- 25 files changed, 438 insertions(+), 292 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ce827d2..56f74f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.6) set(VERSION_MAJOR 00) -set(VERSION_MINOR 147) +set(VERSION_MINOR 156) set(PICO_SDK_FETCH_FROM_GIT off) set(PICO_BOARD=pico) diff --git a/README.md b/README.md index a77549c..6be657d 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ The actual switch happens at the very moment when one arrow stops moving and the ## Keyboard -Acting as a USB Host and querying your keyboard periodically, it looks for a preconfigured hotkey in the hid report (usually Caps Lock for me). When found, it will forward all subsequent characters to the other output. +Acting as a USB Host and querying your keyboard periodically, it looks for a preconfigured hotkey in the hid report (usually Ctrl + Caps Lock for me). When found, it will forward all subsequent characters to the other output. To have a visual indication which output you are using at any given moment, you can repurpose keyboard LEDs and have them provide the necessary feedback. @@ -101,6 +101,10 @@ This will make sure you won't accidentally leave your current screen. To turn of You can lock both computers at once by using ```RIGHT CTRL + L```. To make use of this feature, first set up the OS for each output in config (since the shortcuts are different). +### Gaming Mode + +If you're gaming, there is a chance your game might not work properly with absolute mouse mode. To address that issue, a **gaming mode** is introduced, toggled by ```LEFT SHIFT + RIGHT SHIFT + G```. When in gaming mode, you are locked to the current screen and your mouse behaves like a standard relative mouse. This should also fix various virtual machine issues, currently unsupported operating systems etc. + ### Screensaver Supposedly built in to prevent computer from entering standby, but truth be told - it is just fun to watch. **Off by default**, will make your mouse pointer bounce around the screen like a Pong ball. When enabled, it activates after a period of inactivity defined in user config header and automatically switches off as soon as you send any output towards that screen. @@ -216,7 +220,8 @@ _Usage_ - ```Right CTRL + Right ALT``` - Toggle slower mouse mode - ```Right CTRL + K``` - Lock/Unlock mouse desktop switching - ```Right CTRL + L``` - Lock both outputs at once (set output OS before, see shortcuts below) -- ```Caps Lock``` - Switch between outputs +- ```Left Shift + Right Shift + G``` - Toggle gaming mode (lock to screen, act as standard mouse) +- ```Left CTRL + Caps Lock``` - Switch between outputs ### Switch cursor height calibration diff --git a/disk/disk.img b/disk/disk.img index 3277f3cffc1b4e6273368e9a72507585d15e00ca..944144b91160d255240a08e7af57fb4ef7f61f42 100644 GIT binary patch delta 9605 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 delta 9824 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 F{|`ZQ=?ee= diff --git a/src/handlers.c b/src/handlers.c index af7d85f..c15924d 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -67,6 +67,12 @@ void switchlock_hotkey_handler(device_t *state, hid_keyboard_report_t *report) { send_value(state->switch_lock, SWITCH_LOCK_MSG); } +/* This key combo toggles gaming mode */ +void toggle_relative_mode_handler(device_t *state, hid_keyboard_report_t *report) { + state->relative_mouse ^= 1; + send_value(state->relative_mouse, RELATIVE_MODE_MSG); +}; + /* This key combo locks both outputs simultaneously */ void screenlock_hotkey_handler(device_t *state, hid_keyboard_report_t *report) { hid_keyboard_report_t lock_report = {0}, release_keys = {0}; @@ -199,9 +205,9 @@ void handle_wipe_config_msg(uart_packet_t *packet, device_t *state) { load_config(state); } -/* Process consumer control message, TODO: use queue instead of sending directly */ +/* Process consumer control message */ void handle_consumer_control_msg(uart_packet_t *packet, device_t *state) { - tud_hid_n_report(0, REPORT_ID_CONSUMER, &packet->data[0], CONSUMER_CONTROL_LENGTH); + queue_cc_packet(packet->data, state); } /* Process request to store config to flash */ @@ -219,6 +225,11 @@ void handle_proxy_msg(uart_packet_t *packet, device_t *state) { queue_packet(&packet->data[1], (enum packet_type_e)packet->data[0], PACKET_DATA_LENGTH - 1); } +/* Process request to reboot the board */ +void handle_toggle_relative_msg(uart_packet_t *packet, device_t *state) { + state->relative_mouse = packet->data[0]; +} + /* Process api communication messages */ void handle_api_msgs(uart_packet_t *packet, device_t *state) { uint8_t value_idx = packet->data[0]; @@ -239,15 +250,24 @@ void handle_api_msgs(uart_packet_t *packet, device_t *state) { memcpy(ptr, &packet->data[1], map->len); } else if (packet->type == GET_VAL_MSG) { - uart_packet_t response = {.type=GET_VAL_MSG, .data={0}}; - memcpy(response.data, ptr, map->len); - queue_try_add(&state->cfg_queue_out, &response); + uart_packet_t response = {.type=GET_VAL_MSG, .data={[0] = value_idx}}; + memcpy(&response.data[1], ptr, map->len); + queue_cfg_packet(&response, state); } /* With each GET/SET message, we reset the configuration mode timeout */ reset_config_timer(state); } +/* Handle the "read all" message by calling our "read one" handler for each type */ +void handle_api_read_all_msg(uart_packet_t *packet, device_t *state) { + uart_packet_t result = {.type=GET_VAL_MSG}; + + for (int i = 0; i < get_field_map_length(); i++) { + result.data[0] = get_field_map_index(i)->idx; + handle_api_msgs(&result, state); + } +} /* Process request packet and create a response */ void handle_request_byte_msg(uart_packet_t *packet, device_t *state) { diff --git a/src/include/main.h b/src/include/main.h index 9062478..545ce37 100644 --- a/src/include/main.h +++ b/src/include/main.h @@ -122,12 +122,14 @@ enum packet_type_e { FLASH_LED_MSG = 9, WIPE_CONFIG_MSG = 10, HEARTBEAT_MSG = 12, + RELATIVE_MODE_MSG = 13, CONSUMER_CONTROL_MSG = 14, SYSTEM_CONTROL_MSG = 15, SAVE_CONFIG_MSG = 18, REBOOT_MSG = 19, GET_VAL_MSG = 20, SET_VAL_MSG = 21, + GET_ALL_VALS_MSG = 22, PROXY_PACKET_MSG = 23, REQUEST_BYTE_MSG = 24, RESPONSE_BYTE_MSG = 25, @@ -166,7 +168,7 @@ typedef struct { #define RAW_PACKET_LENGTH (START_LENGTH + PACKET_LENGTH) #define UART_QUEUE_LENGTH 256 -#define CFG_QUEUE_LENGTH 128 +#define HID_QUEUE_LENGTH 128 #define KBD_QUEUE_LENGTH 128 #define MOUSE_QUEUE_LENGTH 512 @@ -345,6 +347,25 @@ typedef struct TU_ATTR_PACKED { uint8_t mode; } mouse_report_t; +/* Used to work around OS issues with absolute coordinates on + multiple desktops (Windows/MacOS) */ +typedef struct { + uint8_t tip_pressure; + uint8_t buttons; // Buttons + uint16_t x; // X coordinate (0-32767) + uint16_t y; // Y coordinate (0-32767) +} touch_report_t; + +/* This stores various packets other than kbd/mouse to go out + (configuration, consumer control, system...) */ +typedef struct { + uint8_t instance; + uint8_t report_id; + uint8_t type; + uint8_t len; + uint8_t data[RAW_PACKET_LENGTH]; +} hid_generic_pkt_t; + typedef enum { IDLE, READING_PACKET, PROCESSING_PACKET } receiver_state_t; typedef struct { @@ -370,7 +391,7 @@ typedef struct { int16_t mouse_buttons; // Store and update the state of mouse buttons config_t config; // Device configuration, loaded from flash or defaults used - queue_t cfg_queue_out; // Queue that stores outgoing vendor config messages + queue_t hid_queue_out; // Queue that stores outgoing hid messages queue_t kbd_queue; // Queue that stores keyboard reports queue_t mouse_queue; // Queue that stores mouse reports queue_t uart_tx_queue; // Queue that stores outgoing packets @@ -427,6 +448,8 @@ void process_consumer_report(uint8_t *, int, uint8_t, hid_interface_t *); void process_system_report(uint8_t *, int, uint8_t, hid_interface_t *); void release_all_keys(device_t *); void queue_kbd_report(hid_keyboard_report_t *, device_t *); +void queue_cc_packet(uint8_t *, device_t *); +void queue_system_packet(uint8_t *, device_t *); void send_key(hid_keyboard_report_t *, device_t *); void send_consumer_control(uint8_t *, device_t *); bool key_in_report(uint8_t, const hid_keyboard_report_t *); @@ -474,7 +497,7 @@ void reboot(void); /********* Tasks **********/ void process_uart_tx_task(device_t *); void process_mouse_queue_task(device_t *); -void process_cfg_queue_task(device_t *); +void process_hid_queue_task(device_t *); void process_kbd_queue_task(device_t *); void usb_device_task(device_t *); void kick_watchdog_task(device_t *); @@ -495,7 +518,10 @@ void reset_config_timer(device_t *); extern const field_map_t api_field_map[]; const field_map_t* get_field_map_entry(uint32_t); +const field_map_t* get_field_map_index(uint32_t); +size_t get_field_map_length(void); bool validate_packet(uart_packet_t *); +void queue_cfg_packet(uart_packet_t *, device_t *); /********* Handlers **********/ void output_toggle_hotkey_handler(device_t *, hid_keyboard_report_t *); @@ -505,6 +531,7 @@ void fw_upgrade_hotkey_handler_B(device_t *, hid_keyboard_report_t *); void mouse_zoom_hotkey_handler(device_t *, hid_keyboard_report_t *); void all_keys_released_handler(device_t *); void switchlock_hotkey_handler(device_t *, hid_keyboard_report_t *); +void toggle_relative_mode_handler(device_t *, hid_keyboard_report_t *); void screenlock_hotkey_handler(device_t *, hid_keyboard_report_t *); void output_config_hotkey_handler(device_t *, hid_keyboard_report_t *); void wipe_config_hotkey_handler(device_t *, hid_keyboard_report_t *); @@ -530,6 +557,8 @@ void handle_response_byte_msg(uart_packet_t *, device_t *); void handle_heartbeat_msg(uart_packet_t *, device_t *); void handle_proxy_msg(uart_packet_t *, device_t *); void handle_api_msgs(uart_packet_t *, device_t *); +void handle_api_read_all_msg(uart_packet_t *, device_t *); +void handle_toggle_relative_msg(uart_packet_t *, device_t *); void switch_output(device_t *, uint8_t); diff --git a/src/include/usb_descriptors.h b/src/include/usb_descriptors.h index 04b7314..0ddc871 100644 --- a/src/include/usb_descriptors.h +++ b/src/include/usb_descriptors.h @@ -151,4 +151,48 @@ HID_COLLECTION_END \ HID_OUTPUT ( HID_DATA | HID_ARRAY | HID_ABSOLUTE ) ,\ HID_COLLECTION_END \ +#define TUD_HID_REPORT_DESC_MOUSEHELP(...) \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_MOUSE ) ,\ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ + /* Report ID if any */\ + __VA_ARGS__ \ + HID_USAGE ( HID_USAGE_DESKTOP_POINTER ) ,\ + HID_COLLECTION ( HID_COLLECTION_PHYSICAL ) ,\ + HID_USAGE_PAGE ( HID_USAGE_PAGE_BUTTON ) ,\ + HID_USAGE_MIN ( 1 ) ,\ + HID_USAGE_MAX ( 5 ) ,\ + HID_LOGICAL_MIN ( 0 ) ,\ + HID_LOGICAL_MAX ( 1 ) ,\ + /* Left, Right, Middle, Backward, Forward buttons */ \ + HID_REPORT_COUNT( 5 ) ,\ + HID_REPORT_SIZE ( 1 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* 3 bit padding */ \ + HID_REPORT_COUNT( 1 ) ,\ + HID_REPORT_SIZE ( 3 ) ,\ + HID_INPUT ( HID_CONSTANT ) ,\ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + /* X, Y position [-127, 127] */ \ + HID_USAGE ( HID_USAGE_DESKTOP_X ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_Y ) ,\ + HID_LOGICAL_MIN_N ( 0x8000, 2 ) ,\ + HID_LOGICAL_MAX_N ( 0x7fff, 2 ) ,\ + HID_REPORT_SIZE ( 16 ) ,\ + HID_REPORT_COUNT( 2 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_RELATIVE ) ,\ + /* Vertical wheel scroll [-127, 127] */ \ + HID_USAGE ( HID_USAGE_DESKTOP_WHEEL ) ,\ + HID_LOGICAL_MIN ( 0x81 ) ,\ + HID_LOGICAL_MAX ( 0x7f ) ,\ + HID_REPORT_COUNT( 1 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_RELATIVE ) ,\ + /* Mouse mode (0 = absolute, 1 = relative) */ \ + HID_REPORT_COUNT( 1 ), \ + HID_REPORT_SIZE ( 8 ), \ + HID_INPUT ( HID_CONSTANT ), \ + HID_COLLECTION_END , \ + HID_COLLECTION_END \ + #endif /* USB_DESCRIPTORS_H_ */ diff --git a/src/include/user_config.h b/src/include/user_config.h index 77a98a7..ecca48e 100644 --- a/src/include/user_config.h +++ b/src/include/user_config.h @@ -23,6 +23,10 @@ * * defined as HID_KEY_ * + * In addition, keyboard.c defines right ctrl as a modifier key required to + * activate this. So, the current shortcut is RIGHT CTRL + whatever is defined + * here. + * * If you do not want to use a key for switching outputs, you may be tempted * to select HID_KEY_NONE here; don't do that! That code appears in many HID * messages and the result will be a non-functional keyboard. Instead, choose @@ -32,7 +36,7 @@ * * */ -#define HOTKEY_TOGGLE HID_KEY_F24 +#define HOTKEY_TOGGLE HID_KEY_CAPS_LOCK /**================================================== * * ============== Mouse Speed Factor ============== * @@ -53,13 +57,13 @@ * * */ -/* Output A values */ +/* Output A values, default is for the most common ~ 16:9 ratio screen */ #define MOUSE_SPEED_A_FACTOR_X 16 -#define MOUSE_SPEED_A_FACTOR_Y 16 +#define MOUSE_SPEED_A_FACTOR_Y 28 -/* Output B values */ +/* Output B values, default is for the most common ~ 16:9 ratio screen */ #define MOUSE_SPEED_B_FACTOR_X 16 -#define MOUSE_SPEED_B_FACTOR_Y 16 +#define MOUSE_SPEED_B_FACTOR_Y 28 #define JUMP_THRESHOLD 0 diff --git a/src/keyboard.c b/src/keyboard.c index 8024356..d25fe34 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -23,7 +23,7 @@ hotkey_combo_t hotkeys[] = { /* Main keyboard switching hotkey */ - {.modifier = 0, + {.modifier = KEYBOARD_MODIFIER_LEFTCTRL, .keys = {HOTKEY_TOGGLE}, .key_count = 1, .pass_to_os = false, @@ -51,6 +51,13 @@ hotkey_combo_t hotkeys[] = { .acknowledge = true, .action_handler = &screenlock_hotkey_handler}, + /* Toggle gaming mode */ + {.modifier = KEYBOARD_MODIFIER_LEFTSHIFT | KEYBOARD_MODIFIER_RIGHTSHIFT, + .keys = {HID_KEY_G}, + .key_count = 1, + .acknowledge = true, + .action_handler = &toggle_relative_mode_handler}, + /* Erase stored config */ {.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT, .keys = {HID_KEY_F12, HID_KEY_D}, @@ -185,7 +192,7 @@ void send_key(hid_keyboard_report_t *report, device_t *state) { /* Decide if consumer control reports go local or to the other board */ void send_consumer_control(uint8_t *raw_report, device_t *state) { if (CURRENT_BOARD_IS_ACTIVE_OUTPUT) { - tud_hid_n_report(0, REPORT_ID_CONSUMER, raw_report, CONSUMER_CONTROL_LENGTH); + queue_cc_packet(raw_report, state); state->last_activity[BOARD_ROLE] = time_us_64(); } else { queue_packet((uint8_t *)raw_report, CONSUMER_CONTROL_MSG, CONSUMER_CONTROL_LENGTH); @@ -195,7 +202,7 @@ void send_consumer_control(uint8_t *raw_report, device_t *state) { /* Decide if consumer control reports go local or to the other board */ void send_system_control(uint8_t *raw_report, device_t *state) { if (CURRENT_BOARD_IS_ACTIVE_OUTPUT) { - tud_hid_n_report(0, REPORT_ID_SYSTEM, raw_report, SYSTEM_CONTROL_LENGTH); + queue_system_packet(raw_report, state); state->last_activity[BOARD_ROLE] = time_us_64(); } else { queue_packet((uint8_t *)raw_report, SYSTEM_CONTROL_MSG, SYSTEM_CONTROL_LENGTH); diff --git a/src/main.c b/src/main.c index 1ae390a..bac5eb5 100644 --- a/src/main.c +++ b/src/main.c @@ -35,7 +35,7 @@ int main(void) { [1] = {.exec = &kick_watchdog_task, .frequency = _HZ(30)}, // | Verify core1 is still running and if so, reset watchdog timer [2] = {.exec = &process_kbd_queue_task, .frequency = _HZ(2000)}, // | Check if there were any keypresses and send them [3] = {.exec = &process_mouse_queue_task, .frequency = _HZ(2000)}, // | Check if there were any mouse movements and send them - [4] = {.exec = &process_cfg_queue_task, .frequency = _HZ(1000)}, // | Check if there are any packets to send over vendor link + [4] = {.exec = &process_hid_queue_task, .frequency = _HZ(1000)}, // | Check if there are any packets to send over vendor link [5] = {.exec = &process_uart_tx_task, .frequency = _TOP()}, // | Check if there are any packets to send over UART }; // `----- then go back and repeat forever const int NUM_TASKS = ARRAY_SIZE(tasks_core0); diff --git a/src/mouse.c b/src/mouse.c index f457fe8..72170a3 100644 --- a/src/mouse.c +++ b/src/mouse.c @@ -64,6 +64,10 @@ void update_mouse_position(device_t *state, mouse_values_t *values) { output_t *current = &state->config.output[state->active_output]; uint8_t reduce_speed = 0; + /* If relative mouse mode is active, just pass mouse movements and update nothing */ + if (state->relative_mouse) + return; + /* Check if we are configured to move slowly */ if (state->mouse_zoom) reduce_speed = MOUSE_ZOOM_SCALING_FACTOR; @@ -141,13 +145,13 @@ void switch_desktop(device_t *state, output_t *output, int new_index, int direct /* Fix for MACOS: Send relative mouse movement here, 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) ? SCREEN_MIDPOINT - 2 : SCREEN_MIDPOINT + 2, .mode = RELATIVE}; + = {.x = (direction == LEFT) ? -5 : 5, .mode = RELATIVE}; switch (output->os) { case MACOS: - /* Once doesn't seem reliable enough, do it twice */ - output_mouse_report(&move_relative_one, state); - output_mouse_report(&move_relative_one, state); + /* Once doesn't seem reliable enough, do it a few times */ + for (int i = 0; i < 5; i++) + output_mouse_report(&move_relative_one, state); break; case WINDOWS: @@ -184,8 +188,8 @@ void check_screen_switch(const mouse_values_t *values, device_t *state) { int direction = jump_left ? LEFT : RIGHT; - /* No switching allowed if explicitly disabled or mouse button is held */ - if (state->switch_lock || state->mouse_buttons) + /* No switching allowed if explicitly disabled */ + if (state->switch_lock) return; /* No jump condition met == nothing to do, return */ @@ -194,9 +198,13 @@ void check_screen_switch(const mouse_values_t *values, device_t *state) { /* 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 (output->screen_index == 1) { /* We are at the border -> switch outputs */ + /* No switching allowed if mouse button is held. Should only apply to the border! */ + if (state->mouse_buttons) + return; + 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); @@ -240,11 +248,9 @@ mouse_report_t create_mouse_report(device_t *state, mouse_values_t *values) { /* Workaround for Windows multiple desktops */ if (state->relative_mouse) { - mouse_report.x = SCREEN_MIDPOINT + values->move_x; - mouse_report.y = SCREEN_MIDPOINT + values->move_y; + mouse_report.x = values->move_x; + mouse_report.y = values->move_y; mouse_report.mode = RELATIVE; - mouse_report.buttons = values->buttons; - mouse_report.wheel = values->wheel; } return mouse_report; diff --git a/src/protocol.c b/src/protocol.c index 210ccbb..1fdd379 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -66,4 +66,39 @@ const field_map_t* get_field_map_entry(uint32_t index) { } return NULL; -} \ No newline at end of file +} + + +const field_map_t* get_field_map_index(uint32_t index) { + return &api_field_map[index]; +} + +size_t get_field_map_length(void) { + return ARRAY_SIZE(api_field_map); +} + +void _queue_packet(uint8_t *payload, device_t *state, uint8_t type, uint8_t len, uint8_t id, uint8_t inst) { + hid_generic_pkt_t generic_packet = { + .instance = inst, + .report_id = id, + .type = type, + .len = len, + }; + + memcpy(generic_packet.data, payload, len); + queue_try_add(&state->hid_queue_out, &generic_packet); +} + +void queue_cfg_packet(uart_packet_t *packet, device_t *state) { + uint8_t raw_packet[RAW_PACKET_LENGTH]; + write_raw_packet(raw_packet, packet); + _queue_packet(raw_packet, state, 0, RAW_PACKET_LENGTH, REPORT_ID_VENDOR, ITF_NUM_HID_VENDOR); +} + +void queue_cc_packet(uint8_t *payload, device_t *state) { + _queue_packet(payload, state, 1, CONSUMER_CONTROL_LENGTH, REPORT_ID_CONSUMER, ITF_NUM_HID); +} + +void queue_system_packet(uint8_t *payload, device_t *state) { + _queue_packet(payload, state, 2, SYSTEM_CONTROL_LENGTH, REPORT_ID_SYSTEM, ITF_NUM_HID); +} diff --git a/src/setup.c b/src/setup.c index 8049268..5813366 100644 --- a/src/setup.c +++ b/src/setup.c @@ -237,8 +237,8 @@ void initial_setup(device_t *state) { queue_init(&state->kbd_queue, sizeof(hid_keyboard_report_t), KBD_QUEUE_LENGTH); queue_init(&state->mouse_queue, sizeof(mouse_report_t), MOUSE_QUEUE_LENGTH); - /* Initialize vendor config protocol queue */ - queue_init(&state->cfg_queue_out, sizeof(uart_packet_t), CFG_QUEUE_LENGTH); + /* Initialize generic HID packet queue */ + queue_init(&state->hid_queue_out, sizeof(hid_generic_pkt_t), HID_QUEUE_LENGTH); /* Initialize UART queue */ queue_init(&state->uart_tx_queue, sizeof(uart_packet_t), UART_QUEUE_LENGTH); @@ -267,4 +267,4 @@ void initial_setup(device_t *state) { watchdog_enable(WATCHDOG_TIMEOUT, WATCHDOG_PAUSE_ON_DEBUG); } -/* ========== End of Initial Board Setup ========== */ \ No newline at end of file +/* ========== End of Initial Board Setup ========== */ diff --git a/src/tasks.c b/src/tasks.c index b6b3aae..6790c0f 100644 --- a/src/tasks.c +++ b/src/tasks.c @@ -148,25 +148,23 @@ void heartbeat_output_task(device_t *state) { queue_try_add(&global_state.uart_tx_queue, &packet); } -/* Process outgoing config report messages. */ -void process_cfg_queue_task(device_t *state) { - uint8_t raw_packet[RAW_PACKET_LENGTH] = {[0] = START1, [1] = START2, [11] = 0}; - uart_packet_t packet; - if (!queue_try_peek(&state->cfg_queue_out, &packet)) +/* Process other outgoing hid report messages. */ +void process_hid_queue_task(device_t *state) { + hid_generic_pkt_t packet; + + if (!queue_try_peek(&state->hid_queue_out, &packet)) return; - if (!tud_hid_n_ready(ITF_NUM_HID_VENDOR)) + if (!tud_hid_n_ready(packet.instance)) return; - write_raw_packet(raw_packet, &packet); - /* ... try sending it to the host, if it's successful */ - bool succeeded = tud_hid_n_report(ITF_NUM_HID_VENDOR, REPORT_ID_VENDOR, raw_packet, RAW_PACKET_LENGTH); + bool succeeded = tud_hid_n_report(packet.instance, packet.report_id, packet.data, packet.len); /* ... then we can remove it from the queue. Race conditions shouldn't happen [tm] */ if (succeeded) - queue_try_remove(&state->cfg_queue_out, &packet); + queue_try_remove(&state->hid_queue_out, &packet); } /* Task that handles copying firmware from the other device to ours */ @@ -221,4 +219,4 @@ void packet_receiver_task(device_t *state) { state->dma_ptr = NEXT_RING_IDX(state->dma_ptr); delta--; } -} \ No newline at end of file +} diff --git a/src/uart.c b/src/uart.c index 73a1760..2b51627 100644 --- a/src/uart.c +++ b/src/uart.c @@ -76,6 +76,7 @@ const uart_handler_t uart_handler[] = { {.type = SWITCH_LOCK_MSG, .handler = handle_switch_lock_msg}, {.type = SYNC_BORDERS_MSG, .handler = handle_sync_borders_msg}, {.type = FLASH_LED_MSG, .handler = handle_flash_led_msg}, + {.type = RELATIVE_MODE_MSG, .handler = handle_toggle_relative_msg}, {.type = CONSUMER_CONTROL_MSG, .handler = handle_consumer_control_msg}, /* Config */ @@ -83,6 +84,7 @@ const uart_handler_t uart_handler[] = { {.type = SAVE_CONFIG_MSG, .handler = handle_save_config_msg}, {.type = REBOOT_MSG, .handler = handle_reboot_msg}, {.type = GET_VAL_MSG, .handler = handle_api_msgs}, + {.type = GET_ALL_VALS_MSG, .handler = handle_api_read_all_msg}, {.type = SET_VAL_MSG, .handler = handle_api_msgs}, /* Firmware */ diff --git a/src/usb_descriptors.c b/src/usb_descriptors.c index f154847..278e3ae 100644 --- a/src/usb_descriptors.c +++ b/src/usb_descriptors.c @@ -58,7 +58,7 @@ uint8_t const desc_hid_report[] = {TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(RE TUD_HID_REPORT_DESC_SYSTEM_CONTROL(HID_REPORT_ID(REPORT_ID_SYSTEM)) }; -uint8_t const desc_hid_report_relmouse[] = {TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(REPORT_ID_RELMOUSE))}; +uint8_t const desc_hid_report_relmouse[] = {TUD_HID_REPORT_DESC_MOUSEHELP(HID_REPORT_ID(REPORT_ID_RELMOUSE))}; uint8_t const desc_hid_report_vendor[] = {TUD_HID_REPORT_DESC_VENDOR_CTRL(HID_REPORT_ID(REPORT_ID_VENDOR))}; @@ -82,15 +82,16 @@ uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance) { } bool tud_mouse_report(uint8_t mode, uint8_t buttons, int16_t x, int16_t y, int8_t wheel) { + mouse_report_t report = {.buttons = buttons, .wheel = wheel, .x = x, .y = y, .mode = mode}; + uint8_t instance = ITF_NUM_HID; + uint8_t report_id = REPORT_ID_MOUSE; - 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 - SCREEN_MIDPOINT, .y = y - SCREEN_MIDPOINT, .wheel = wheel, .pan = 0}; - return tud_hid_n_report(ITF_NUM_HID_REL_M, REPORT_ID_RELMOUSE, &report, sizeof(report)); + if (mode == RELATIVE) { + instance = ITF_NUM_HID_REL_M; + report_id = REPORT_ID_RELMOUSE; } + + return tud_hid_n_report(instance, report_id, &report, sizeof(report)); } diff --git a/src/utils.c b/src/utils.c index 8ef603a..2086a51 100644 --- a/src/utils.c +++ b/src/utils.c @@ -195,6 +195,7 @@ bool validate_packet(uart_packet_t *packet) { const enum packet_type_e ALLOWED_PACKETS[] = { FLASH_LED_MSG, GET_VAL_MSG, + GET_ALL_VALS_MSG, SET_VAL_MSG, WIPE_CONFIG_MSG, SAVE_CONFIG_MSG, diff --git a/webconfig/Makefile b/webconfig/Makefile index 0c76629..001d779 100644 --- a/webconfig/Makefile +++ b/webconfig/Makefile @@ -1,2 +1,2 @@ render: - python3 render.py + python3 -B render.py diff --git a/webconfig/config-unpacked.htm b/webconfig/config-unpacked.htm index 67a5228..d4918da 100644 --- a/webconfig/config-unpacked.htm +++ b/webconfig/config-unpacked.htm @@ -24,7 +24,7 @@ :root { --highlight-color: #384955; --font-color: #384955; - --highlight-color2: #5e9f41; + --highlight-color2: #5e9f41; } html { @@ -492,7 +492,7 @@ input[type='number'].input-inline { @media (min-width: 40rem) { .row { flex-direction: row; - + width: calc(100% + 2.0rem); } .row .column { @@ -681,14 +681,14 @@ img {
-
+
- - + @@ -823,22 +823,22 @@ img { - + - - + min="" max="" oninput="this.form.aInput13.value=this.value" /> - + - + @@ -854,12 +854,12 @@ img { - - + @@ -875,12 +875,12 @@ img { - - + @@ -895,24 +895,24 @@ img { - - + - + - + - + - +
- + @@ -927,18 +927,18 @@ img { - - + - +
- + @@ -953,20 +953,20 @@ img { - - + - + - +
- + @@ -983,7 +983,7 @@ img { - + @@ -998,20 +998,20 @@ img { - - + - + - +
- + @@ -1026,16 +1026,16 @@ img {
+ - -
- + @@ -1051,12 +1051,12 @@ img { - - + @@ -1072,12 +1072,12 @@ img { - - + @@ -1088,7 +1088,7 @@ img { - +

Output B

@@ -1105,20 +1105,20 @@ img { - - + - + - +
- + @@ -1137,22 +1137,22 @@ img { - + - - + min="" max="" oninput="this.form.aInput42.value=this.value" /> - + - + @@ -1171,22 +1171,22 @@ img { - + - - + min="" max="" oninput="this.form.aInput43.value=this.value" /> - + - + @@ -1202,12 +1202,12 @@ img { - - + @@ -1223,12 +1223,12 @@ img { - - + @@ -1243,24 +1243,24 @@ img { - - + - + - + - + - +
- + @@ -1275,18 +1275,18 @@ img { - - + - +
- + @@ -1301,20 +1301,20 @@ img { - - + - + - +
- + @@ -1331,7 +1331,7 @@ img { - + @@ -1346,20 +1346,20 @@ img { - - + - + - +
- + @@ -1374,16 +1374,16 @@ img {
+ - -
- + @@ -1399,12 +1399,12 @@ img { - - + @@ -1420,14 +1420,14 @@ img { - - + - + @@ -1459,7 +1459,7 @@ img { - + @@ -1474,16 +1474,16 @@ img {
+ - -
- + @@ -1498,16 +1498,16 @@ img {
+ - -
- + @@ -1526,22 +1526,22 @@ img { - + - - + min="" max="" oninput="this.form.aInput77.value=this.value" /> - + - + @@ -1558,7 +1558,7 @@ img { - + @@ -1573,16 +1573,16 @@ img {
+ - -
- + @@ -1597,16 +1597,16 @@ img {
+ - -
- + @@ -1621,16 +1621,16 @@ img {
+ - -
- + @@ -1654,12 +1654,12 @@ img { - - + @@ -1675,12 +1675,12 @@ img { - - + @@ -1697,7 +1697,7 @@ var device; const packetType = { keyboardReportMsg: 1, mouseReportMsg: 2, outputSelectMsg: 3, firmwareUpgradeMsg: 4, switchLockMsg: 7, syncBordersMsg: 8, flashLedMsg: 9, wipeConfigMsg: 10, readConfigMsg: 16, writeConfigMsg: 17, saveConfigMsg: 18, - rebootMsg: 19, getValMsg: 20, setValMsg: 21, proxyPacketMsg: 23 + rebootMsg: 19, getValMsg: 20, setValMsg: 21, getValAllMsg: 22, proxyPacketMsg: 23 }; function calcChecksum(report) { @@ -1710,26 +1710,26 @@ function calcChecksum(report) { async function sendReport(type, payload = [], sendBoth = false) { if (!device || !device.opened) - return; - + return; + /* First send this one, if the first one gets e.g. rebooted */ - if (sendBoth) { + if (sendBoth) { var reportProxy = makeReport(type, payload, true); await device.sendReport(mgmtReportId, reportProxy); } - + var report = makeReport(type, payload, false); - await device.sendReport(mgmtReportId, report); + await device.sendReport(mgmtReportId, report); } function makeReport(type, payload, proxy=false) { var dataOffset = proxy ? 4 : 3; - report = new Uint8Array([0xaa, 0x55, type, ...new Array(9).fill(0)]); + report = new Uint8Array([0xaa, 0x55, type, ...new Array(9).fill(0)]); - if (proxy) - report = new Uint8Array([0xaa, 0x55, packetType.proxyPacketMsg, type, ...new Array(7).fill(0), type]); - - if (payload) { + if (proxy) + report = new Uint8Array([0xaa, 0x55, packetType.proxyPacketMsg, type, ...new Array(7).fill(0), type]); + + if (payload) { report.set([...payload], dataOffset); report[report.length - 1] = calcChecksum(report); } @@ -1784,7 +1784,8 @@ async function connectHandler() { }); device = devices[0]; - device.open().then(async () => { + device.open().then(async () => { + device.addEventListener('inputreport', handleInputReport); document.querySelectorAll('.online').forEach(element => { element.style.opacity = 1.0; }); await readHandler(); }); @@ -1816,8 +1817,12 @@ function setValue(element, value) { } -function updateElement(element, event, dataType) { - var dataOffset = 3; +function updateElement(key, event) { + var dataOffset = 4; + var element = document.querySelector(`[data-key="${key}"]`); + + if (!element) + return; const methods = { "uint32": event.data.getUint32, @@ -1829,6 +1834,8 @@ function updateElement(element, event, dataType) { "int8": event.data.getInt8 }; + dataType = element.getAttribute('data-type'); + if (dataType in methods) { var value = methods[dataType].call(event.data, dataOffset, true); setValue(element, value); @@ -1842,24 +1849,14 @@ async function readHandler() { if (!device || !device.opened) await connectHandler(); - const elements = document.querySelectorAll('.api'); + await sendReport(packetType.getValAllMsg); +} - for (const element of elements) { - var key = element.getAttribute('data-key'); - var dataType = element.getAttribute('data-type'); +async function handleInputReport(event) { + var data = new Uint8Array(event.data.buffer); + var key = data[3]; - await sendReport(packetType.getValMsg, [key]); - - let incomingReport = await new Promise((resolve, reject) => { - const handleInputReport = (event) => { - updateElement(element, event, dataType); - - device.removeEventListener('inputreport', handleInputReport); - resolve(); - } - device.addEventListener('inputreport', handleInputReport); - }); - } + updateElement(key, event); } async function rebootHandler() { @@ -1879,7 +1876,7 @@ async function valueChangedHandler(element) { if (origValue != newValue) { uintBuffer = packValue(element, key, dataType); - + /* Send to both devices */ await sendReport(packetType.setValMsg, uintBuffer, true); @@ -1901,7 +1898,7 @@ async function saveHandler() { continue; if (origValue != getValue(element)) - await valueChangedHandler(element); + await valueChangedHandler(element); } await sendReport(packetType.saveConfigMsg, [], true); } diff --git a/webconfig/config.htm b/webconfig/config.htm index 3b762b1..06d24da 100644 --- a/webconfig/config.htm +++ b/webconfig/config.htm @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/webconfig/form.py b/webconfig/form.py index dc655ac..cbce388 100755 --- a/webconfig/form.py +++ b/webconfig/form.py @@ -12,12 +12,12 @@ class FormField: elem: str | None = None SHORTCUTS = { - 0x73: "None", + 0x73: "None", 0x2A: "Backspace", - 0x39: "Caps Lock", - 0x2B: "Tab", + 0x39: "Caps Lock", + 0x2B: "Tab", 0x46: "Print Screen", - 0x47: "Scroll Lock", + 0x47: "Scroll Lock", 0x53: "Num Lock", } @@ -26,18 +26,18 @@ STATUS_ = [ FormField(79, "Running FW checksum", None, {}, "uint32", elem="hex_info"), ] -CONFIG_ = [ +CONFIG_ = [ FormField(1001, "Mouse", elem="label"), FormField(71, "Force Mouse Boot Mode", None, {}, "uint8", "checkbox"), FormField(75, "Enable Acceleration", None, {}, "uint8", "checkbox"), FormField(77, "Jump Treshold", 0, {"min": 0, "max": 1024}, "uint16", "range"), - + FormField(1002, "Keyboard", elem="label"), FormField(72, "Force KBD Boot Protocol", None, {}, "uint8", "checkbox"), FormField(73, "KBD LED as Indicator", None, {}, "uint8", "checkbox"), FormField(76, "Enforce Ports", None, {}, "uint8", "checkbox"), -] +] OUTPUT_ = [ FormField(1, "Screen Count", 1, {1: "1", 2: "2", 3: "3"}, "uint32"), @@ -48,7 +48,7 @@ OUTPUT_ = [ FormField(6, "Operating System", 1, {1: "Linux", 2: "MacOS", 3: "Windows", 4: "Android", 255: "Other"}, "uint8"), FormField(7, "Screen Position", 1, {1: "Left", 2: "Right"}, "uint8"), FormField(8, "Cursor Park Position", 0, {0: "Top", 1: "Bottom", 3: "Previous"}, "uint8"), - FormField(1003, "Screensaver", elem="label"), + FormField(1003, "Screensaver", elem="label"), FormField(9, "Mode", 0, {0: "Disabled", 1: "Pong", 2: "Jitter"}, "uint8"), FormField(10, "Only If Inactive", None, {}, "uint8", "checkbox"), FormField(11, "Idle Time (μs)", None, {}, "uint64"), diff --git a/webconfig/render.py b/webconfig/render.py index aea2cb5..7f8b8aa 100755 --- a/webconfig/render.py +++ b/webconfig/render.py @@ -37,12 +37,12 @@ def encode_file(payload): return base64_compressed_data -if __name__ == "__main__": +if __name__ == "__main__": # Read main template contents webpage = render( - INPUT_FILENAME, - screen_A=output_A(), - screen_B=output_B(), + INPUT_FILENAME, + screen_A=output_A(), + screen_B=output_B(), status=output_status(), config=output_config(), ) @@ -56,6 +56,6 @@ if __name__ == "__main__": # Write data to output filename write_file(self_extracting_webpage) - + # Write unpacked webpage write_file(webpage, OUTPUT_UNPACKED) \ No newline at end of file diff --git a/webconfig/templates/form.html b/webconfig/templates/form.html index bf8cd31..9bad6c7 100644 --- a/webconfig/templates/form.html +++ b/webconfig/templates/form.html @@ -3,7 +3,7 @@ {% set key = item.key %} {% macro input(item, type='text', class='api', name='name') %} - - + {{ input(item, class='range api', type='range', name='aRange') }} - min="{{ item.values.min }}" max="{{ item.values.max }}" oninput="this.form.aInput{{key}}.value=this.value" /> + min="{{ item.values.min }}" max="{{ item.values.max }}" oninput="this.form.aInput{{key}}.value=this.value" /> - + {% elif item.get("elem") == "checkbox" %}
- {{ label(item) }} + {{ label(item) }} {{ input(item, type="checkbox") }} />
{% elif item["values"] %} {{ label(item, class='') }} - - {% for k, v in item["values"].items() %} + {% for k, v in item["values"].items() %} {% endfor %}
diff --git a/webconfig/templates/main.html b/webconfig/templates/main.html index dda7e9b..c1e70f7 100644 --- a/webconfig/templates/main.html +++ b/webconfig/templates/main.html @@ -17,14 +17,14 @@
-
+
- @@ -91,13 +91,13 @@ - +

Output B

{% for item in screen_B %} - {% include "form.html" with context %} + {% include "form.html" with context %} {% endfor %} - + @@ -114,7 +114,7 @@

Common Config

{% for item in config %} - {% include "form.html" with context %} + {% include "form.html" with context %} {% endfor %} @@ -124,7 +124,7 @@

Device Status

{% for item in status %} - {% include "form.html" with context %} + {% include "form.html" with context %} {% endfor %} diff --git a/webconfig/templates/script.js b/webconfig/templates/script.js index 48da171..a71197c 100644 --- a/webconfig/templates/script.js +++ b/webconfig/templates/script.js @@ -4,7 +4,7 @@ var device; const packetType = { keyboardReportMsg: 1, mouseReportMsg: 2, outputSelectMsg: 3, firmwareUpgradeMsg: 4, switchLockMsg: 7, syncBordersMsg: 8, flashLedMsg: 9, wipeConfigMsg: 10, readConfigMsg: 16, writeConfigMsg: 17, saveConfigMsg: 18, - rebootMsg: 19, getValMsg: 20, setValMsg: 21, proxyPacketMsg: 23 + rebootMsg: 19, getValMsg: 20, setValMsg: 21, getValAllMsg: 22, proxyPacketMsg: 23 }; function calcChecksum(report) { @@ -17,26 +17,26 @@ function calcChecksum(report) { async function sendReport(type, payload = [], sendBoth = false) { if (!device || !device.opened) - return; - + return; + /* First send this one, if the first one gets e.g. rebooted */ - if (sendBoth) { + if (sendBoth) { var reportProxy = makeReport(type, payload, true); await device.sendReport(mgmtReportId, reportProxy); } - + var report = makeReport(type, payload, false); - await device.sendReport(mgmtReportId, report); + await device.sendReport(mgmtReportId, report); } function makeReport(type, payload, proxy=false) { var dataOffset = proxy ? 4 : 3; - report = new Uint8Array([0xaa, 0x55, type, ...new Array(9).fill(0)]); + report = new Uint8Array([0xaa, 0x55, type, ...new Array(9).fill(0)]); - if (proxy) - report = new Uint8Array([0xaa, 0x55, packetType.proxyPacketMsg, type, ...new Array(7).fill(0), type]); - - if (payload) { + if (proxy) + report = new Uint8Array([0xaa, 0x55, packetType.proxyPacketMsg, type, ...new Array(7).fill(0), type]); + + if (payload) { report.set([...payload], dataOffset); report[report.length - 1] = calcChecksum(report); } @@ -91,7 +91,8 @@ async function connectHandler() { }); device = devices[0]; - device.open().then(async () => { + device.open().then(async () => { + device.addEventListener('inputreport', handleInputReport); document.querySelectorAll('.online').forEach(element => { element.style.opacity = 1.0; }); await readHandler(); }); @@ -123,8 +124,12 @@ function setValue(element, value) { } -function updateElement(element, event, dataType) { - var dataOffset = 3; +function updateElement(key, event) { + var dataOffset = 4; + var element = document.querySelector(`[data-key="${key}"]`); + + if (!element) + return; const methods = { "uint32": event.data.getUint32, @@ -136,6 +141,8 @@ function updateElement(element, event, dataType) { "int8": event.data.getInt8 }; + dataType = element.getAttribute('data-type'); + if (dataType in methods) { var value = methods[dataType].call(event.data, dataOffset, true); setValue(element, value); @@ -149,24 +156,14 @@ async function readHandler() { if (!device || !device.opened) await connectHandler(); - const elements = document.querySelectorAll('.api'); + await sendReport(packetType.getValAllMsg); +} - for (const element of elements) { - var key = element.getAttribute('data-key'); - var dataType = element.getAttribute('data-type'); +async function handleInputReport(event) { + var data = new Uint8Array(event.data.buffer); + var key = data[3]; - await sendReport(packetType.getValMsg, [key]); - - let incomingReport = await new Promise((resolve, reject) => { - const handleInputReport = (event) => { - updateElement(element, event, dataType); - - device.removeEventListener('inputreport', handleInputReport); - resolve(); - } - device.addEventListener('inputreport', handleInputReport); - }); - } + updateElement(key, event); } async function rebootHandler() { @@ -186,7 +183,7 @@ async function valueChangedHandler(element) { if (origValue != newValue) { uintBuffer = packValue(element, key, dataType); - + /* Send to both devices */ await sendReport(packetType.setValMsg, uintBuffer, true); @@ -208,7 +205,7 @@ async function saveHandler() { continue; if (origValue != getValue(element)) - await valueChangedHandler(element); + await valueChangedHandler(element); } await sendReport(packetType.saveConfigMsg, [], true); } diff --git a/webconfig/templates/style.css b/webconfig/templates/style.css index 913fe21..3d1ab91 100644 --- a/webconfig/templates/style.css +++ b/webconfig/templates/style.css @@ -15,7 +15,7 @@ :root { --highlight-color: #384955; --font-color: #384955; - --highlight-color2: #5e9f41; + --highlight-color2: #5e9f41; } html { @@ -483,7 +483,7 @@ input[type='number'].input-inline { @media (min-width: 40rem) { .row { flex-direction: row; - + width: calc(100% + 2.0rem); } .row .column {