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 3277f3c..944144b 100644 Binary files a/disk/disk.img and b/disk/disk.img differ 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 {