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
This commit is contained in:
Hrvoje Cavrak 2024-08-17 18:47:36 +02:00
parent 1fd0049039
commit a249aa50f1
25 changed files with 438 additions and 292 deletions

View File

@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.6) cmake_minimum_required(VERSION 3.6)
set(VERSION_MAJOR 00) set(VERSION_MAJOR 00)
set(VERSION_MINOR 147) set(VERSION_MINOR 156)
set(PICO_SDK_FETCH_FROM_GIT off) set(PICO_SDK_FETCH_FROM_GIT off)
set(PICO_BOARD=pico) set(PICO_BOARD=pico)

View File

@ -42,7 +42,7 @@ The actual switch happens at the very moment when one arrow stops moving and the
## Keyboard ## 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. 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```. 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). 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 ### 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. 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 + Right ALT``` - Toggle slower mouse mode
- ```Right CTRL + K``` - Lock/Unlock mouse desktop switching - ```Right CTRL + K``` - Lock/Unlock mouse desktop switching
- ```Right CTRL + L``` - Lock both outputs at once (set output OS before, see shortcuts below) - ```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 ### Switch cursor height calibration

Binary file not shown.

View File

@ -67,6 +67,12 @@ void switchlock_hotkey_handler(device_t *state, hid_keyboard_report_t *report) {
send_value(state->switch_lock, SWITCH_LOCK_MSG); 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 */ /* This key combo locks both outputs simultaneously */
void screenlock_hotkey_handler(device_t *state, hid_keyboard_report_t *report) { void screenlock_hotkey_handler(device_t *state, hid_keyboard_report_t *report) {
hid_keyboard_report_t lock_report = {0}, release_keys = {0}; 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); 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) { 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 */ /* 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); 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 */ /* Process api communication messages */
void handle_api_msgs(uart_packet_t *packet, device_t *state) { void handle_api_msgs(uart_packet_t *packet, device_t *state) {
uint8_t value_idx = packet->data[0]; 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); memcpy(ptr, &packet->data[1], map->len);
} }
else if (packet->type == GET_VAL_MSG) { else if (packet->type == GET_VAL_MSG) {
uart_packet_t response = {.type=GET_VAL_MSG, .data={0}}; uart_packet_t response = {.type=GET_VAL_MSG, .data={[0] = value_idx}};
memcpy(response.data, ptr, map->len); memcpy(&response.data[1], ptr, map->len);
queue_try_add(&state->cfg_queue_out, &response); queue_cfg_packet(&response, state);
} }
/* With each GET/SET message, we reset the configuration mode timeout */ /* With each GET/SET message, we reset the configuration mode timeout */
reset_config_timer(state); 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 */ /* Process request packet and create a response */
void handle_request_byte_msg(uart_packet_t *packet, device_t *state) { void handle_request_byte_msg(uart_packet_t *packet, device_t *state) {

View File

@ -122,12 +122,14 @@ enum packet_type_e {
FLASH_LED_MSG = 9, FLASH_LED_MSG = 9,
WIPE_CONFIG_MSG = 10, WIPE_CONFIG_MSG = 10,
HEARTBEAT_MSG = 12, HEARTBEAT_MSG = 12,
RELATIVE_MODE_MSG = 13,
CONSUMER_CONTROL_MSG = 14, CONSUMER_CONTROL_MSG = 14,
SYSTEM_CONTROL_MSG = 15, SYSTEM_CONTROL_MSG = 15,
SAVE_CONFIG_MSG = 18, SAVE_CONFIG_MSG = 18,
REBOOT_MSG = 19, REBOOT_MSG = 19,
GET_VAL_MSG = 20, GET_VAL_MSG = 20,
SET_VAL_MSG = 21, SET_VAL_MSG = 21,
GET_ALL_VALS_MSG = 22,
PROXY_PACKET_MSG = 23, PROXY_PACKET_MSG = 23,
REQUEST_BYTE_MSG = 24, REQUEST_BYTE_MSG = 24,
RESPONSE_BYTE_MSG = 25, RESPONSE_BYTE_MSG = 25,
@ -166,7 +168,7 @@ typedef struct {
#define RAW_PACKET_LENGTH (START_LENGTH + PACKET_LENGTH) #define RAW_PACKET_LENGTH (START_LENGTH + PACKET_LENGTH)
#define UART_QUEUE_LENGTH 256 #define UART_QUEUE_LENGTH 256
#define CFG_QUEUE_LENGTH 128 #define HID_QUEUE_LENGTH 128
#define KBD_QUEUE_LENGTH 128 #define KBD_QUEUE_LENGTH 128
#define MOUSE_QUEUE_LENGTH 512 #define MOUSE_QUEUE_LENGTH 512
@ -345,6 +347,25 @@ typedef struct TU_ATTR_PACKED {
uint8_t mode; uint8_t mode;
} mouse_report_t; } 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 enum { IDLE, READING_PACKET, PROCESSING_PACKET } receiver_state_t;
typedef struct { typedef struct {
@ -370,7 +391,7 @@ typedef struct {
int16_t mouse_buttons; // Store and update the state of mouse buttons int16_t mouse_buttons; // Store and update the state of mouse buttons
config_t config; // Device configuration, loaded from flash or defaults used 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 kbd_queue; // Queue that stores keyboard reports
queue_t mouse_queue; // Queue that stores mouse reports queue_t mouse_queue; // Queue that stores mouse reports
queue_t uart_tx_queue; // Queue that stores outgoing packets 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 process_system_report(uint8_t *, int, uint8_t, hid_interface_t *);
void release_all_keys(device_t *); void release_all_keys(device_t *);
void queue_kbd_report(hid_keyboard_report_t *, 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_key(hid_keyboard_report_t *, device_t *);
void send_consumer_control(uint8_t *, device_t *); void send_consumer_control(uint8_t *, device_t *);
bool key_in_report(uint8_t, const hid_keyboard_report_t *); bool key_in_report(uint8_t, const hid_keyboard_report_t *);
@ -474,7 +497,7 @@ void reboot(void);
/********* Tasks **********/ /********* Tasks **********/
void process_uart_tx_task(device_t *); void process_uart_tx_task(device_t *);
void process_mouse_queue_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 process_kbd_queue_task(device_t *);
void usb_device_task(device_t *); void usb_device_task(device_t *);
void kick_watchdog_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[]; 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_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 *); bool validate_packet(uart_packet_t *);
void queue_cfg_packet(uart_packet_t *, device_t *);
/********* Handlers **********/ /********* Handlers **********/
void output_toggle_hotkey_handler(device_t *, hid_keyboard_report_t *); 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 mouse_zoom_hotkey_handler(device_t *, hid_keyboard_report_t *);
void all_keys_released_handler(device_t *); void all_keys_released_handler(device_t *);
void switchlock_hotkey_handler(device_t *, hid_keyboard_report_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 screenlock_hotkey_handler(device_t *, hid_keyboard_report_t *);
void output_config_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 *); 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_heartbeat_msg(uart_packet_t *, device_t *);
void handle_proxy_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_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); void switch_output(device_t *, uint8_t);

View File

@ -151,4 +151,48 @@ HID_COLLECTION_END \
HID_OUTPUT ( HID_DATA | HID_ARRAY | HID_ABSOLUTE ) ,\ HID_OUTPUT ( HID_DATA | HID_ARRAY | HID_ABSOLUTE ) ,\
HID_COLLECTION_END \ 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_ */ #endif /* USB_DESCRIPTORS_H_ */

View File

@ -23,6 +23,10 @@
* *
* defined as HID_KEY_<something> * defined as HID_KEY_<something>
* *
* 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 * 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 * 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 * 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 ============== * * ============== 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_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_X 16
#define MOUSE_SPEED_B_FACTOR_Y 16 #define MOUSE_SPEED_B_FACTOR_Y 28
#define JUMP_THRESHOLD 0 #define JUMP_THRESHOLD 0

View File

@ -23,7 +23,7 @@
hotkey_combo_t hotkeys[] = { hotkey_combo_t hotkeys[] = {
/* Main keyboard switching hotkey */ /* Main keyboard switching hotkey */
{.modifier = 0, {.modifier = KEYBOARD_MODIFIER_LEFTCTRL,
.keys = {HOTKEY_TOGGLE}, .keys = {HOTKEY_TOGGLE},
.key_count = 1, .key_count = 1,
.pass_to_os = false, .pass_to_os = false,
@ -51,6 +51,13 @@ hotkey_combo_t hotkeys[] = {
.acknowledge = true, .acknowledge = true,
.action_handler = &screenlock_hotkey_handler}, .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 */ /* Erase stored config */
{.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT, {.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT,
.keys = {HID_KEY_F12, HID_KEY_D}, .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 */ /* Decide if consumer control reports go local or to the other board */
void send_consumer_control(uint8_t *raw_report, device_t *state) { void send_consumer_control(uint8_t *raw_report, device_t *state) {
if (CURRENT_BOARD_IS_ACTIVE_OUTPUT) { 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(); state->last_activity[BOARD_ROLE] = time_us_64();
} else { } else {
queue_packet((uint8_t *)raw_report, CONSUMER_CONTROL_MSG, CONSUMER_CONTROL_LENGTH); 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 */ /* Decide if consumer control reports go local or to the other board */
void send_system_control(uint8_t *raw_report, device_t *state) { void send_system_control(uint8_t *raw_report, device_t *state) {
if (CURRENT_BOARD_IS_ACTIVE_OUTPUT) { 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(); state->last_activity[BOARD_ROLE] = time_us_64();
} else { } else {
queue_packet((uint8_t *)raw_report, SYSTEM_CONTROL_MSG, SYSTEM_CONTROL_LENGTH); queue_packet((uint8_t *)raw_report, SYSTEM_CONTROL_MSG, SYSTEM_CONTROL_LENGTH);

View File

@ -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 [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 [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 [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 [5] = {.exec = &process_uart_tx_task, .frequency = _TOP()}, // | Check if there are any packets to send over UART
}; // `----- then go back and repeat forever }; // `----- then go back and repeat forever
const int NUM_TASKS = ARRAY_SIZE(tasks_core0); const int NUM_TASKS = ARRAY_SIZE(tasks_core0);

View File

@ -64,6 +64,10 @@ void update_mouse_position(device_t *state, mouse_values_t *values) {
output_t *current = &state->config.output[state->active_output]; output_t *current = &state->config.output[state->active_output];
uint8_t reduce_speed = 0; 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 */ /* Check if we are configured to move slowly */
if (state->mouse_zoom) if (state->mouse_zoom)
reduce_speed = MOUSE_ZOOM_SCALING_FACTOR; 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 /* Fix for MACOS: Send relative mouse movement here, one or two pixels in the
direction of movement, BEFORE absolute report sets X to 0 */ direction of movement, BEFORE absolute report sets X to 0 */
mouse_report_t move_relative_one 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) { switch (output->os) {
case MACOS: case MACOS:
/* Once doesn't seem reliable enough, do it twice */ /* Once doesn't seem reliable enough, do it a few times */
output_mouse_report(&move_relative_one, state); for (int i = 0; i < 5; i++)
output_mouse_report(&move_relative_one, state); output_mouse_report(&move_relative_one, state);
break; break;
case WINDOWS: case WINDOWS:
@ -184,8 +188,8 @@ void check_screen_switch(const mouse_values_t *values, device_t *state) {
int direction = jump_left ? LEFT : RIGHT; int direction = jump_left ? LEFT : RIGHT;
/* No switching allowed if explicitly disabled or mouse button is held */ /* No switching allowed if explicitly disabled */
if (state->switch_lock || state->mouse_buttons) if (state->switch_lock)
return; return;
/* No jump condition met == nothing to do, 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 */ /* We want to jump in the direction of the other computer */
if (output->pos != direction) { if (output->pos != direction) {
if (output->screen_index == 1) /* We are at the border -> switch outputs */ 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); /* 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 */ /* If here, this output has multiple desktops and we are not on the main one */
else else
switch_desktop(state, output, output->screen_index - 1, direction); 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 */ /* Workaround for Windows multiple desktops */
if (state->relative_mouse) { if (state->relative_mouse) {
mouse_report.x = SCREEN_MIDPOINT + values->move_x; mouse_report.x = values->move_x;
mouse_report.y = SCREEN_MIDPOINT + values->move_y; mouse_report.y = values->move_y;
mouse_report.mode = RELATIVE; mouse_report.mode = RELATIVE;
mouse_report.buttons = values->buttons;
mouse_report.wheel = values->wheel;
} }
return mouse_report; return mouse_report;

View File

@ -66,4 +66,39 @@ const field_map_t* get_field_map_entry(uint32_t index) {
} }
return NULL; return NULL;
} }
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);
}

View File

@ -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->kbd_queue, sizeof(hid_keyboard_report_t), KBD_QUEUE_LENGTH);
queue_init(&state->mouse_queue, sizeof(mouse_report_t), MOUSE_QUEUE_LENGTH); queue_init(&state->mouse_queue, sizeof(mouse_report_t), MOUSE_QUEUE_LENGTH);
/* Initialize vendor config protocol queue */ /* Initialize generic HID packet queue */
queue_init(&state->cfg_queue_out, sizeof(uart_packet_t), CFG_QUEUE_LENGTH); queue_init(&state->hid_queue_out, sizeof(hid_generic_pkt_t), HID_QUEUE_LENGTH);
/* Initialize UART queue */ /* Initialize UART queue */
queue_init(&state->uart_tx_queue, sizeof(uart_packet_t), UART_QUEUE_LENGTH); 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); watchdog_enable(WATCHDOG_TIMEOUT, WATCHDOG_PAUSE_ON_DEBUG);
} }
/* ========== End of Initial Board Setup ========== */ /* ========== End of Initial Board Setup ========== */

View File

@ -148,25 +148,23 @@ void heartbeat_output_task(device_t *state) {
queue_try_add(&global_state.uart_tx_queue, &packet); 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; return;
if (!tud_hid_n_ready(ITF_NUM_HID_VENDOR)) if (!tud_hid_n_ready(packet.instance))
return; return;
write_raw_packet(raw_packet, &packet);
/* ... try sending it to the host, if it's successful */ /* ... 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] */ /* ... then we can remove it from the queue. Race conditions shouldn't happen [tm] */
if (succeeded) 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 */ /* 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); state->dma_ptr = NEXT_RING_IDX(state->dma_ptr);
delta--; delta--;
} }
} }

View File

@ -76,6 +76,7 @@ const uart_handler_t uart_handler[] = {
{.type = SWITCH_LOCK_MSG, .handler = handle_switch_lock_msg}, {.type = SWITCH_LOCK_MSG, .handler = handle_switch_lock_msg},
{.type = SYNC_BORDERS_MSG, .handler = handle_sync_borders_msg}, {.type = SYNC_BORDERS_MSG, .handler = handle_sync_borders_msg},
{.type = FLASH_LED_MSG, .handler = handle_flash_led_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}, {.type = CONSUMER_CONTROL_MSG, .handler = handle_consumer_control_msg},
/* Config */ /* Config */
@ -83,6 +84,7 @@ const uart_handler_t uart_handler[] = {
{.type = SAVE_CONFIG_MSG, .handler = handle_save_config_msg}, {.type = SAVE_CONFIG_MSG, .handler = handle_save_config_msg},
{.type = REBOOT_MSG, .handler = handle_reboot_msg}, {.type = REBOOT_MSG, .handler = handle_reboot_msg},
{.type = GET_VAL_MSG, .handler = handle_api_msgs}, {.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}, {.type = SET_VAL_MSG, .handler = handle_api_msgs},
/* Firmware */ /* Firmware */

View File

@ -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)) 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))}; 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) { 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) { if (mode == RELATIVE) {
mouse_report_t report = {.buttons = buttons, .x = x, .y = y, .wheel = wheel}; instance = ITF_NUM_HID_REL_M;
return tud_hid_n_report(ITF_NUM_HID, REPORT_ID_MOUSE, &report, sizeof(report)); report_id = REPORT_ID_RELMOUSE;
} 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));
} }
return tud_hid_n_report(instance, report_id, &report, sizeof(report));
} }

View File

@ -195,6 +195,7 @@ bool validate_packet(uart_packet_t *packet) {
const enum packet_type_e ALLOWED_PACKETS[] = { const enum packet_type_e ALLOWED_PACKETS[] = {
FLASH_LED_MSG, FLASH_LED_MSG,
GET_VAL_MSG, GET_VAL_MSG,
GET_ALL_VALS_MSG,
SET_VAL_MSG, SET_VAL_MSG,
WIPE_CONFIG_MSG, WIPE_CONFIG_MSG,
SAVE_CONFIG_MSG, SAVE_CONFIG_MSG,

View File

@ -1,2 +1,2 @@
render: render:
python3 render.py python3 -B render.py

View File

@ -24,7 +24,7 @@
:root { :root {
--highlight-color: #384955; --highlight-color: #384955;
--font-color: #384955; --font-color: #384955;
--highlight-color2: #5e9f41; --highlight-color2: #5e9f41;
} }
html { html {
@ -492,7 +492,7 @@ input[type='number'].input-inline {
@media (min-width: 40rem) { @media (min-width: 40rem) {
.row { .row {
flex-direction: row; flex-direction: row;
width: calc(100% + 2.0rem); width: calc(100% + 2.0rem);
} }
.row .column { .row .column {
@ -681,14 +681,14 @@ img {
<main class="wrapper"> <main class="wrapper">
<section class="container"> <section class="container">
<div class="row" id="warning" style="display: none;"> <div class="row" id="warning" style="display: none;">
<blockquote> <blockquote>
<h3> Oh, no! </h3> <h3> Oh, no! </h3>
Unfortunately, your browser does not support WebHID or the Permissions Policy is blocking its usage. Please try Chromium <br /> Unfortunately, your browser does not support WebHID or the Permissions Policy is blocking its usage. Please try Chromium <br />
(or Chrome if you have to). Apologies for the inconvenience, hopefully a cross-browser solution happens soon. (or Chrome if you have to). Apologies for the inconvenience, hopefully a cross-browser solution happens soon.
</blockquote> </blockquote>
</div> </div>
<div class="row"> <div class="row">
@ -716,7 +716,7 @@ img {
<path d="m 104.01516,99.341366 a 1.8639801,1.8639801 0 0 0 -1.86329,1.863284 1.8639801,1.8639801 0 0 0 1.86329,1.86328 h 27.4375 a 1.8639801,1.8639801 0 0 0 1.86718,-1.86328 1.8639801,1.8639801 0 0 0 -1.86718,-1.863284 z" /> <path d="m 104.01516,99.341366 a 1.8639801,1.8639801 0 0 0 -1.86329,1.863284 1.8639801,1.8639801 0 0 0 1.86329,1.86328 h 27.4375 a 1.8639801,1.8639801 0 0 0 1.86718,-1.86328 1.8639801,1.8639801 0 0 0 -1.86718,-1.863284 z" />
</g> </g>
</svg> </svg>
<div id="menu-buttons"> <div id="menu-buttons">
<button data-handler="connectHandler" class="button button-clear button-shifted">Connect</button><br /> <button data-handler="connectHandler" class="button button-clear button-shifted">Connect</button><br />
<button data-handler="readHandler" class="button button-clear button-shifted online">Read</button><br /> <button data-handler="readHandler" class="button button-clear button-shifted online">Read</button><br />
@ -727,8 +727,8 @@ img {
<button data-handler="blinkHandler" class="button button-clear button-shifted online">Blink</button><br /> <button data-handler="blinkHandler" class="button button-clear button-shifted online">Blink</button><br />
<button data-handler="blinkBothHandler" class="button button-clear button-shifted online">Blink both</button><br /> <button data-handler="blinkBothHandler" class="button button-clear button-shifted online">Blink both</button><br />
<button data-handler="enterBootloaderHandler" class="button button-clear button-shifted online">Bootloader</button><br /> <button data-handler="enterBootloaderHandler" class="button button-clear button-shifted online">Bootloader</button><br />
<button data-handler="wipeConfigHandler" class="button button-clear button-shifted online">Wipe Config</button><br /> <button data-handler="wipeConfigHandler" class="button button-clear button-shifted online">Wipe Config</button><br />
</div> </div>
</div> </div>
@ -757,20 +757,20 @@ img {
<label class=""> Screen Count</label> <label class=""> Screen Count</label>
<select class="api" data-type="uint32" data-key="11" required> <select class="api" data-type="uint32" data-key="11" required>
<option disabled selected value></option> <option disabled selected value></option>
<option value="1">1</option> <option value="1">1</option>
<option value="2">2</option> <option value="2">2</option>
<option value="3">3</option> <option value="3">3</option>
</select><br /> </select><br />
@ -789,22 +789,22 @@ img {
<input class="input-inline" type="number" name="aInput12" data-type="int32" data-key="12" <input class="input-inline" type="number" name="aInput12" data-type="int32" data-key="12"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
readonly oninput="this.form.aRange12.value=this.value" /> readonly oninput="this.form.aRange12.value=this.value" />
<input class="range api" type="range" name="aRange12" data-type="int32" data-key="12"
<input class="range api" type="range" name="aRange12" data-type="int32" data-key="12"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
min="" max="" oninput="this.form.aInput12.value=this.value" /> min="" max="" oninput="this.form.aInput12.value=this.value" />
</form> </form>
</div> </div>
@ -823,22 +823,22 @@ img {
<input class="input-inline" type="number" name="aInput13" data-type="int32" data-key="13" <input class="input-inline" type="number" name="aInput13" data-type="int32" data-key="13"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
readonly oninput="this.form.aRange13.value=this.value" /> readonly oninput="this.form.aRange13.value=this.value" />
<input class="range api" type="range" name="aRange13" data-type="int32" data-key="13"
<input class="range api" type="range" name="aRange13" data-type="int32" data-key="13"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
min="" max="" oninput="this.form.aInput13.value=this.value" /> min="" max="" oninput="this.form.aInput13.value=this.value" />
</form> </form>
</div> </div>
@ -854,12 +854,12 @@ img {
<label class=""> Border Top</label> <label class=""> Border Top</label>
<input class="api" type="text" name="name14" data-type="int32" data-key="14" <input class="api" type="text" name="name14" data-type="int32" data-key="14"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
/> />
@ -875,12 +875,12 @@ img {
<label class=""> Border Bottom</label> <label class=""> Border Bottom</label>
<input class="api" type="text" name="name15" data-type="int32" data-key="15" <input class="api" type="text" name="name15" data-type="int32" data-key="15"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
/> />
@ -895,24 +895,24 @@ img {
<label class=""> Operating System</label> <label class=""> Operating System</label>
<select class="api" data-type="uint8" data-key="16" required> <select class="api" data-type="uint8" data-key="16" required>
<option disabled selected value></option> <option disabled selected value></option>
<option value="1">Linux</option> <option value="1">Linux</option>
<option value="2">MacOS</option> <option value="2">MacOS</option>
<option value="3">Windows</option> <option value="3">Windows</option>
<option value="4">Android</option> <option value="4">Android</option>
<option value="255">Other</option> <option value="255">Other</option>
</select><br /> </select><br />
@ -927,18 +927,18 @@ img {
<label class=""> Screen Position</label> <label class=""> Screen Position</label>
<select class="api" data-type="uint8" data-key="17" required> <select class="api" data-type="uint8" data-key="17" required>
<option disabled selected value></option> <option disabled selected value></option>
<option value="1">Left</option> <option value="1">Left</option>
<option value="2">Right</option> <option value="2">Right</option>
</select><br /> </select><br />
@ -953,20 +953,20 @@ img {
<label class=""> Cursor Park Position</label> <label class=""> Cursor Park Position</label>
<select class="api" data-type="uint8" data-key="18" required> <select class="api" data-type="uint8" data-key="18" required>
<option disabled selected value></option> <option disabled selected value></option>
<option value="0">Top</option> <option value="0">Top</option>
<option value="1">Bottom</option> <option value="1">Bottom</option>
<option value="3">Previous</option> <option value="3">Previous</option>
</select><br /> </select><br />
@ -983,7 +983,7 @@ img {
@ -998,20 +998,20 @@ img {
<label class=""> Mode</label> <label class=""> Mode</label>
<select class="api" data-type="uint8" data-key="19" required> <select class="api" data-type="uint8" data-key="19" required>
<option disabled selected value></option> <option disabled selected value></option>
<option value="0">Disabled</option> <option value="0">Disabled</option>
<option value="1">Pong</option> <option value="1">Pong</option>
<option value="2">Jitter</option> <option value="2">Jitter</option>
</select><br /> </select><br />
@ -1026,16 +1026,16 @@ img {
<div class="clearfix"> <div class="clearfix">
<label class="label-inline"> Only If Inactive</label> <label class="label-inline"> Only If Inactive</label>
<input class="api" type="checkbox" name="name20" data-type="uint8" data-key="20"
<input class="api" type="checkbox" name="name20" data-type="uint8" data-key="20"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
/> />
</div> </div>
@ -1051,12 +1051,12 @@ img {
<label class=""> Idle Time (μs)</label> <label class=""> Idle Time (μs)</label>
<input class="api" type="text" name="name21" data-type="uint64" data-key="21" <input class="api" type="text" name="name21" data-type="uint64" data-key="21"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
/> />
@ -1072,12 +1072,12 @@ img {
<label class=""> Max Time (μs)</label> <label class=""> Max Time (μs)</label>
<input class="api" type="text" name="name22" data-type="uint64" data-key="22" <input class="api" type="text" name="name22" data-type="uint64" data-key="22"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
/> />
</div> </div>
@ -1088,7 +1088,7 @@ img {
<line x1="50" y1="90" x2="50" y2="75" stroke="black" stroke-width="2" /> <line x1="50" y1="90" x2="50" y2="75" stroke="black" stroke-width="2" />
<rect x="30" y="90" width="40" height="3" stroke="black" stroke-width="2" fill="#d7e5f0" rx="5" ry="5" /> <rect x="30" y="90" width="40" height="3" stroke="black" stroke-width="2" fill="#d7e5f0" rx="5" ry="5" />
</svg> </svg>
<h3>Output B</h3> <h3>Output B</h3>
@ -1105,20 +1105,20 @@ img {
<label class=""> Screen Count</label> <label class=""> Screen Count</label>
<select class="api" data-type="uint32" data-key="41" required> <select class="api" data-type="uint32" data-key="41" required>
<option disabled selected value></option> <option disabled selected value></option>
<option value="1">1</option> <option value="1">1</option>
<option value="2">2</option> <option value="2">2</option>
<option value="3">3</option> <option value="3">3</option>
</select><br /> </select><br />
@ -1137,22 +1137,22 @@ img {
<input class="input-inline" type="number" name="aInput42" data-type="int32" data-key="42" <input class="input-inline" type="number" name="aInput42" data-type="int32" data-key="42"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
readonly oninput="this.form.aRange42.value=this.value" /> readonly oninput="this.form.aRange42.value=this.value" />
<input class="range api" type="range" name="aRange42" data-type="int32" data-key="42"
<input class="range api" type="range" name="aRange42" data-type="int32" data-key="42"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
min="" max="" oninput="this.form.aInput42.value=this.value" /> min="" max="" oninput="this.form.aInput42.value=this.value" />
</form> </form>
</div> </div>
@ -1171,22 +1171,22 @@ img {
<input class="input-inline" type="number" name="aInput43" data-type="int32" data-key="43" <input class="input-inline" type="number" name="aInput43" data-type="int32" data-key="43"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
readonly oninput="this.form.aRange43.value=this.value" /> readonly oninput="this.form.aRange43.value=this.value" />
<input class="range api" type="range" name="aRange43" data-type="int32" data-key="43"
<input class="range api" type="range" name="aRange43" data-type="int32" data-key="43"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
min="" max="" oninput="this.form.aInput43.value=this.value" /> min="" max="" oninput="this.form.aInput43.value=this.value" />
</form> </form>
</div> </div>
@ -1202,12 +1202,12 @@ img {
<label class=""> Border Top</label> <label class=""> Border Top</label>
<input class="api" type="text" name="name44" data-type="int32" data-key="44" <input class="api" type="text" name="name44" data-type="int32" data-key="44"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
/> />
@ -1223,12 +1223,12 @@ img {
<label class=""> Border Bottom</label> <label class=""> Border Bottom</label>
<input class="api" type="text" name="name45" data-type="int32" data-key="45" <input class="api" type="text" name="name45" data-type="int32" data-key="45"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
/> />
@ -1243,24 +1243,24 @@ img {
<label class=""> Operating System</label> <label class=""> Operating System</label>
<select class="api" data-type="uint8" data-key="46" required> <select class="api" data-type="uint8" data-key="46" required>
<option disabled selected value></option> <option disabled selected value></option>
<option value="1">Linux</option> <option value="1">Linux</option>
<option value="2">MacOS</option> <option value="2">MacOS</option>
<option value="3">Windows</option> <option value="3">Windows</option>
<option value="4">Android</option> <option value="4">Android</option>
<option value="255">Other</option> <option value="255">Other</option>
</select><br /> </select><br />
@ -1275,18 +1275,18 @@ img {
<label class=""> Screen Position</label> <label class=""> Screen Position</label>
<select class="api" data-type="uint8" data-key="47" required> <select class="api" data-type="uint8" data-key="47" required>
<option disabled selected value></option> <option disabled selected value></option>
<option value="1">Left</option> <option value="1">Left</option>
<option value="2">Right</option> <option value="2">Right</option>
</select><br /> </select><br />
@ -1301,20 +1301,20 @@ img {
<label class=""> Cursor Park Position</label> <label class=""> Cursor Park Position</label>
<select class="api" data-type="uint8" data-key="48" required> <select class="api" data-type="uint8" data-key="48" required>
<option disabled selected value></option> <option disabled selected value></option>
<option value="0">Top</option> <option value="0">Top</option>
<option value="1">Bottom</option> <option value="1">Bottom</option>
<option value="3">Previous</option> <option value="3">Previous</option>
</select><br /> </select><br />
@ -1331,7 +1331,7 @@ img {
@ -1346,20 +1346,20 @@ img {
<label class=""> Mode</label> <label class=""> Mode</label>
<select class="api" data-type="uint8" data-key="49" required> <select class="api" data-type="uint8" data-key="49" required>
<option disabled selected value></option> <option disabled selected value></option>
<option value="0">Disabled</option> <option value="0">Disabled</option>
<option value="1">Pong</option> <option value="1">Pong</option>
<option value="2">Jitter</option> <option value="2">Jitter</option>
</select><br /> </select><br />
@ -1374,16 +1374,16 @@ img {
<div class="clearfix"> <div class="clearfix">
<label class="label-inline"> Only If Inactive</label> <label class="label-inline"> Only If Inactive</label>
<input class="api" type="checkbox" name="name50" data-type="uint8" data-key="50"
<input class="api" type="checkbox" name="name50" data-type="uint8" data-key="50"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
/> />
</div> </div>
@ -1399,12 +1399,12 @@ img {
<label class=""> Idle Time (μs)</label> <label class=""> Idle Time (μs)</label>
<input class="api" type="text" name="name51" data-type="uint64" data-key="51" <input class="api" type="text" name="name51" data-type="uint64" data-key="51"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
/> />
@ -1420,14 +1420,14 @@ img {
<label class=""> Max Time (μs)</label> <label class=""> Max Time (μs)</label>
<input class="api" type="text" name="name52" data-type="uint64" data-key="52" <input class="api" type="text" name="name52" data-type="uint64" data-key="52"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
/> />
</div> </div>
</div> </div>
@ -1459,7 +1459,7 @@ img {
@ -1474,16 +1474,16 @@ img {
<div class="clearfix"> <div class="clearfix">
<label class="label-inline"> Force Mouse Boot Mode</label> <label class="label-inline"> Force Mouse Boot Mode</label>
<input class="api" type="checkbox" name="name71" data-type="uint8" data-key="71"
<input class="api" type="checkbox" name="name71" data-type="uint8" data-key="71"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
/> />
</div> </div>
@ -1498,16 +1498,16 @@ img {
<div class="clearfix"> <div class="clearfix">
<label class="label-inline"> Enable Acceleration</label> <label class="label-inline"> Enable Acceleration</label>
<input class="api" type="checkbox" name="name75" data-type="uint8" data-key="75"
<input class="api" type="checkbox" name="name75" data-type="uint8" data-key="75"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
/> />
</div> </div>
@ -1526,22 +1526,22 @@ img {
<input class="input-inline" type="number" name="aInput77" data-type="uint16" data-key="77" <input class="input-inline" type="number" name="aInput77" data-type="uint16" data-key="77"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
readonly oninput="this.form.aRange77.value=this.value" /> readonly oninput="this.form.aRange77.value=this.value" />
<input class="range api" type="range" name="aRange77" data-type="uint16" data-key="77"
<input class="range api" type="range" name="aRange77" data-type="uint16" data-key="77"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
min="" max="" oninput="this.form.aInput77.value=this.value" /> min="" max="" oninput="this.form.aInput77.value=this.value" />
</form> </form>
</div> </div>
@ -1558,7 +1558,7 @@ img {
@ -1573,16 +1573,16 @@ img {
<div class="clearfix"> <div class="clearfix">
<label class="label-inline"> Force KBD Boot Protocol</label> <label class="label-inline"> Force KBD Boot Protocol</label>
<input class="api" type="checkbox" name="name72" data-type="uint8" data-key="72"
<input class="api" type="checkbox" name="name72" data-type="uint8" data-key="72"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
/> />
</div> </div>
@ -1597,16 +1597,16 @@ img {
<div class="clearfix"> <div class="clearfix">
<label class="label-inline"> KBD LED as Indicator</label> <label class="label-inline"> KBD LED as Indicator</label>
<input class="api" type="checkbox" name="name73" data-type="uint8" data-key="73"
<input class="api" type="checkbox" name="name73" data-type="uint8" data-key="73"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
/> />
</div> </div>
@ -1621,16 +1621,16 @@ img {
<div class="clearfix"> <div class="clearfix">
<label class="label-inline"> Enforce Ports</label> <label class="label-inline"> Enforce Ports</label>
<input class="api" type="checkbox" name="name76" data-type="uint8" data-key="76"
<input class="api" type="checkbox" name="name76" data-type="uint8" data-key="76"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
/> />
</div> </div>
<input type="submit" value="Save" id="submitButton"> <input type="submit" value="Save" id="submitButton">
@ -1654,12 +1654,12 @@ img {
<label class="label-inline"> Running FW version:</label> <label class="label-inline"> Running FW version:</label>
<input class="content api" type="text" name="name78" data-type="uint16" data-key="78" <input class="content api" type="text" name="name78" data-type="uint16" data-key="78"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
data-hex readonly /> data-hex readonly />
@ -1675,12 +1675,12 @@ img {
<label class="label-inline"> Running FW checksum:</label> <label class="label-inline"> Running FW checksum:</label>
<input class="content api" type="text" name="name79" data-type="uint32" data-key="79" <input class="content api" type="text" name="name79" data-type="uint32" data-key="79"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
data-hex readonly /> data-hex readonly />
</div> </div>
@ -1697,7 +1697,7 @@ var device;
const packetType = { const packetType = {
keyboardReportMsg: 1, mouseReportMsg: 2, outputSelectMsg: 3, firmwareUpgradeMsg: 4, switchLockMsg: 7, keyboardReportMsg: 1, mouseReportMsg: 2, outputSelectMsg: 3, firmwareUpgradeMsg: 4, switchLockMsg: 7,
syncBordersMsg: 8, flashLedMsg: 9, wipeConfigMsg: 10, readConfigMsg: 16, writeConfigMsg: 17, saveConfigMsg: 18, 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) { function calcChecksum(report) {
@ -1710,26 +1710,26 @@ function calcChecksum(report) {
async function sendReport(type, payload = [], sendBoth = false) { async function sendReport(type, payload = [], sendBoth = false) {
if (!device || !device.opened) if (!device || !device.opened)
return; return;
/* First send this one, if the first one gets e.g. rebooted */ /* First send this one, if the first one gets e.g. rebooted */
if (sendBoth) { if (sendBoth) {
var reportProxy = makeReport(type, payload, true); var reportProxy = makeReport(type, payload, true);
await device.sendReport(mgmtReportId, reportProxy); await device.sendReport(mgmtReportId, reportProxy);
} }
var report = makeReport(type, payload, false); var report = makeReport(type, payload, false);
await device.sendReport(mgmtReportId, report); await device.sendReport(mgmtReportId, report);
} }
function makeReport(type, payload, proxy=false) { function makeReport(type, payload, proxy=false) {
var dataOffset = proxy ? 4 : 3; 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) if (proxy)
report = new Uint8Array([0xaa, 0x55, packetType.proxyPacketMsg, type, ...new Array(7).fill(0), type]); report = new Uint8Array([0xaa, 0x55, packetType.proxyPacketMsg, type, ...new Array(7).fill(0), type]);
if (payload) { if (payload) {
report.set([...payload], dataOffset); report.set([...payload], dataOffset);
report[report.length - 1] = calcChecksum(report); report[report.length - 1] = calcChecksum(report);
} }
@ -1784,7 +1784,8 @@ async function connectHandler() {
}); });
device = devices[0]; 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; }); document.querySelectorAll('.online').forEach(element => { element.style.opacity = 1.0; });
await readHandler(); await readHandler();
}); });
@ -1816,8 +1817,12 @@ function setValue(element, value) {
} }
function updateElement(element, event, dataType) { function updateElement(key, event) {
var dataOffset = 3; var dataOffset = 4;
var element = document.querySelector(`[data-key="${key}"]`);
if (!element)
return;
const methods = { const methods = {
"uint32": event.data.getUint32, "uint32": event.data.getUint32,
@ -1829,6 +1834,8 @@ function updateElement(element, event, dataType) {
"int8": event.data.getInt8 "int8": event.data.getInt8
}; };
dataType = element.getAttribute('data-type');
if (dataType in methods) { if (dataType in methods) {
var value = methods[dataType].call(event.data, dataOffset, true); var value = methods[dataType].call(event.data, dataOffset, true);
setValue(element, value); setValue(element, value);
@ -1842,24 +1849,14 @@ async function readHandler() {
if (!device || !device.opened) if (!device || !device.opened)
await connectHandler(); await connectHandler();
const elements = document.querySelectorAll('.api'); await sendReport(packetType.getValAllMsg);
}
for (const element of elements) { async function handleInputReport(event) {
var key = element.getAttribute('data-key'); var data = new Uint8Array(event.data.buffer);
var dataType = element.getAttribute('data-type'); var key = data[3];
await sendReport(packetType.getValMsg, [key]); updateElement(key, event);
let incomingReport = await new Promise((resolve, reject) => {
const handleInputReport = (event) => {
updateElement(element, event, dataType);
device.removeEventListener('inputreport', handleInputReport);
resolve();
}
device.addEventListener('inputreport', handleInputReport);
});
}
} }
async function rebootHandler() { async function rebootHandler() {
@ -1879,7 +1876,7 @@ async function valueChangedHandler(element) {
if (origValue != newValue) { if (origValue != newValue) {
uintBuffer = packValue(element, key, dataType); uintBuffer = packValue(element, key, dataType);
/* Send to both devices */ /* Send to both devices */
await sendReport(packetType.setValMsg, uintBuffer, true); await sendReport(packetType.setValMsg, uintBuffer, true);
@ -1901,7 +1898,7 @@ async function saveHandler() {
continue; continue;
if (origValue != getValue(element)) if (origValue != getValue(element))
await valueChangedHandler(element); await valueChangedHandler(element);
} }
await sendReport(packetType.saveConfigMsg, [], true); await sendReport(packetType.saveConfigMsg, [], true);
} }

File diff suppressed because one or more lines are too long

View File

@ -12,12 +12,12 @@ class FormField:
elem: str | None = None elem: str | None = None
SHORTCUTS = { SHORTCUTS = {
0x73: "None", 0x73: "None",
0x2A: "Backspace", 0x2A: "Backspace",
0x39: "Caps Lock", 0x39: "Caps Lock",
0x2B: "Tab", 0x2B: "Tab",
0x46: "Print Screen", 0x46: "Print Screen",
0x47: "Scroll Lock", 0x47: "Scroll Lock",
0x53: "Num Lock", 0x53: "Num Lock",
} }
@ -26,18 +26,18 @@ STATUS_ = [
FormField(79, "Running FW checksum", None, {}, "uint32", elem="hex_info"), FormField(79, "Running FW checksum", None, {}, "uint32", elem="hex_info"),
] ]
CONFIG_ = [ CONFIG_ = [
FormField(1001, "Mouse", elem="label"), FormField(1001, "Mouse", elem="label"),
FormField(71, "Force Mouse Boot Mode", None, {}, "uint8", "checkbox"), FormField(71, "Force Mouse Boot Mode", None, {}, "uint8", "checkbox"),
FormField(75, "Enable Acceleration", None, {}, "uint8", "checkbox"), FormField(75, "Enable Acceleration", None, {}, "uint8", "checkbox"),
FormField(77, "Jump Treshold", 0, {"min": 0, "max": 1024}, "uint16", "range"), FormField(77, "Jump Treshold", 0, {"min": 0, "max": 1024}, "uint16", "range"),
FormField(1002, "Keyboard", elem="label"), FormField(1002, "Keyboard", elem="label"),
FormField(72, "Force KBD Boot Protocol", None, {}, "uint8", "checkbox"), FormField(72, "Force KBD Boot Protocol", None, {}, "uint8", "checkbox"),
FormField(73, "KBD LED as Indicator", None, {}, "uint8", "checkbox"), FormField(73, "KBD LED as Indicator", None, {}, "uint8", "checkbox"),
FormField(76, "Enforce Ports", None, {}, "uint8", "checkbox"), FormField(76, "Enforce Ports", None, {}, "uint8", "checkbox"),
] ]
OUTPUT_ = [ OUTPUT_ = [
FormField(1, "Screen Count", 1, {1: "1", 2: "2", 3: "3"}, "uint32"), 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(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(7, "Screen Position", 1, {1: "Left", 2: "Right"}, "uint8"),
FormField(8, "Cursor Park Position", 0, {0: "Top", 1: "Bottom", 3: "Previous"}, "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(9, "Mode", 0, {0: "Disabled", 1: "Pong", 2: "Jitter"}, "uint8"),
FormField(10, "Only If Inactive", None, {}, "uint8", "checkbox"), FormField(10, "Only If Inactive", None, {}, "uint8", "checkbox"),
FormField(11, "Idle Time (μs)", None, {}, "uint64"), FormField(11, "Idle Time (μs)", None, {}, "uint64"),

View File

@ -37,12 +37,12 @@ def encode_file(payload):
return base64_compressed_data return base64_compressed_data
if __name__ == "__main__": if __name__ == "__main__":
# Read main template contents # Read main template contents
webpage = render( webpage = render(
INPUT_FILENAME, INPUT_FILENAME,
screen_A=output_A(), screen_A=output_A(),
screen_B=output_B(), screen_B=output_B(),
status=output_status(), status=output_status(),
config=output_config(), config=output_config(),
) )
@ -56,6 +56,6 @@ if __name__ == "__main__":
# Write data to output filename # Write data to output filename
write_file(self_extracting_webpage) write_file(self_extracting_webpage)
# Write unpacked webpage # Write unpacked webpage
write_file(webpage, OUTPUT_UNPACKED) write_file(webpage, OUTPUT_UNPACKED)

View File

@ -3,7 +3,7 @@
{% set key = item.key %} {% set key = item.key %}
{% macro input(item, type='text', class='api', name='name') %} {% macro input(item, type='text', class='api', name='name') %}
<input class="{{ class }}" type="{{ type }}" name="{{ name }}{{ key }}" data-type="{{ item.type }}" data-key="{{ key }}" <input class="{{ class }}" type="{{ type }}" name="{{ name }}{{ key }}" data-type="{{ item.type }}" data-key="{{ key }}"
onchange="valueChangedHandler(this)" onchange="valueChangedHandler(this)"
{% endmacro %} {% endmacro %}
@ -26,26 +26,26 @@
{{ input(item, class='input-inline', type='number', name='aInput') }} {{ input(item, class='input-inline', type='number', name='aInput') }}
readonly oninput="this.form.aRange{{ key }}.value=this.value" /> readonly oninput="this.form.aRange{{ key }}.value=this.value" />
{{ input(item, class='range api', type='range', name='aRange') }} {{ 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" />
</form> </form>
</div> </div>
{% elif item.get("elem") == "checkbox" %} {% elif item.get("elem") == "checkbox" %}
<div class="clearfix"> <div class="clearfix">
{{ label(item) }} {{ label(item) }}
{{ input(item, type="checkbox") }} /> {{ input(item, type="checkbox") }} />
</div> </div>
{% elif item["values"] %} {% elif item["values"] %}
{{ label(item, class='') }} {{ label(item, class='') }}
<select class="api" data-type="{{ item.type }}" data-key="{{ key }}" required> <select class="api" data-type="{{ item.type }}" data-key="{{ key }}" required>
<option disabled selected value></option> <option disabled selected value></option>
{% for k, v in item["values"].items() %} {% for k, v in item["values"].items() %}
<option value="{{ k }}">{{ v }}</option> <option value="{{ k }}">{{ v }}</option>
{% endfor %} {% endfor %}
</select><br /> </select><br />

View File

@ -17,14 +17,14 @@
<main class="wrapper"> <main class="wrapper">
<section class="container"> <section class="container">
<div class="row" id="warning" style="display: none;"> <div class="row" id="warning" style="display: none;">
<blockquote> <blockquote>
<h3> Oh, no! </h3> <h3> Oh, no! </h3>
Unfortunately, your browser does not support WebHID or the Permissions Policy is blocking its usage. Please try Chromium <br /> Unfortunately, your browser does not support WebHID or the Permissions Policy is blocking its usage. Please try Chromium <br />
(or Chrome if you have to). Apologies for the inconvenience, hopefully a cross-browser solution happens soon. (or Chrome if you have to). Apologies for the inconvenience, hopefully a cross-browser solution happens soon.
</blockquote> </blockquote>
</div> </div>
<div class="row"> <div class="row">
@ -52,7 +52,7 @@
<path d="m 104.01516,99.341366 a 1.8639801,1.8639801 0 0 0 -1.86329,1.863284 1.8639801,1.8639801 0 0 0 1.86329,1.86328 h 27.4375 a 1.8639801,1.8639801 0 0 0 1.86718,-1.86328 1.8639801,1.8639801 0 0 0 -1.86718,-1.863284 z" /> <path d="m 104.01516,99.341366 a 1.8639801,1.8639801 0 0 0 -1.86329,1.863284 1.8639801,1.8639801 0 0 0 1.86329,1.86328 h 27.4375 a 1.8639801,1.8639801 0 0 0 1.86718,-1.86328 1.8639801,1.8639801 0 0 0 -1.86718,-1.863284 z" />
</g> </g>
</svg> </svg>
<div id="menu-buttons"> <div id="menu-buttons">
<button data-handler="connectHandler" class="button button-clear button-shifted">Connect</button><br /> <button data-handler="connectHandler" class="button button-clear button-shifted">Connect</button><br />
<button data-handler="readHandler" class="button button-clear button-shifted online">Read</button><br /> <button data-handler="readHandler" class="button button-clear button-shifted online">Read</button><br />
@ -63,8 +63,8 @@
<button data-handler="blinkHandler" class="button button-clear button-shifted online">Blink</button><br /> <button data-handler="blinkHandler" class="button button-clear button-shifted online">Blink</button><br />
<button data-handler="blinkBothHandler" class="button button-clear button-shifted online">Blink both</button><br /> <button data-handler="blinkBothHandler" class="button button-clear button-shifted online">Blink both</button><br />
<button data-handler="enterBootloaderHandler" class="button button-clear button-shifted online">Bootloader</button><br /> <button data-handler="enterBootloaderHandler" class="button button-clear button-shifted online">Bootloader</button><br />
<button data-handler="wipeConfigHandler" class="button button-clear button-shifted online">Wipe Config</button><br /> <button data-handler="wipeConfigHandler" class="button button-clear button-shifted online">Wipe Config</button><br />
</div> </div>
</div> </div>
@ -80,7 +80,7 @@
<h3>Output A</h3> <h3>Output A</h3>
{% for item in screen_A %} {% for item in screen_A %}
{% include "form.html" with context %} {% include "form.html" with context %}
{% endfor %} {% endfor %}
</div> </div>
@ -91,13 +91,13 @@
<line x1="50" y1="90" x2="50" y2="75" stroke="black" stroke-width="2" /> <line x1="50" y1="90" x2="50" y2="75" stroke="black" stroke-width="2" />
<rect x="30" y="90" width="40" height="3" stroke="black" stroke-width="2" fill="#d7e5f0" rx="5" ry="5" /> <rect x="30" y="90" width="40" height="3" stroke="black" stroke-width="2" fill="#d7e5f0" rx="5" ry="5" />
</svg> </svg>
<h3>Output B</h3> <h3>Output B</h3>
{% for item in screen_B %} {% for item in screen_B %}
{% include "form.html" with context %} {% include "form.html" with context %}
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
@ -114,7 +114,7 @@
<h3>Common Config</h3> <h3>Common Config</h3>
{% for item in config %} {% for item in config %}
{% include "form.html" with context %} {% include "form.html" with context %}
{% endfor %} {% endfor %}
<input type="submit" value="Save" id="submitButton"> <input type="submit" value="Save" id="submitButton">
@ -124,7 +124,7 @@
<h3>Device Status</h3> <h3>Device Status</h3>
{% for item in status %} {% for item in status %}
{% include "form.html" with context %} {% include "form.html" with context %}
{% endfor %} {% endfor %}
</div> </div>

View File

@ -4,7 +4,7 @@ var device;
const packetType = { const packetType = {
keyboardReportMsg: 1, mouseReportMsg: 2, outputSelectMsg: 3, firmwareUpgradeMsg: 4, switchLockMsg: 7, keyboardReportMsg: 1, mouseReportMsg: 2, outputSelectMsg: 3, firmwareUpgradeMsg: 4, switchLockMsg: 7,
syncBordersMsg: 8, flashLedMsg: 9, wipeConfigMsg: 10, readConfigMsg: 16, writeConfigMsg: 17, saveConfigMsg: 18, 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) { function calcChecksum(report) {
@ -17,26 +17,26 @@ function calcChecksum(report) {
async function sendReport(type, payload = [], sendBoth = false) { async function sendReport(type, payload = [], sendBoth = false) {
if (!device || !device.opened) if (!device || !device.opened)
return; return;
/* First send this one, if the first one gets e.g. rebooted */ /* First send this one, if the first one gets e.g. rebooted */
if (sendBoth) { if (sendBoth) {
var reportProxy = makeReport(type, payload, true); var reportProxy = makeReport(type, payload, true);
await device.sendReport(mgmtReportId, reportProxy); await device.sendReport(mgmtReportId, reportProxy);
} }
var report = makeReport(type, payload, false); var report = makeReport(type, payload, false);
await device.sendReport(mgmtReportId, report); await device.sendReport(mgmtReportId, report);
} }
function makeReport(type, payload, proxy=false) { function makeReport(type, payload, proxy=false) {
var dataOffset = proxy ? 4 : 3; 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) if (proxy)
report = new Uint8Array([0xaa, 0x55, packetType.proxyPacketMsg, type, ...new Array(7).fill(0), type]); report = new Uint8Array([0xaa, 0x55, packetType.proxyPacketMsg, type, ...new Array(7).fill(0), type]);
if (payload) { if (payload) {
report.set([...payload], dataOffset); report.set([...payload], dataOffset);
report[report.length - 1] = calcChecksum(report); report[report.length - 1] = calcChecksum(report);
} }
@ -91,7 +91,8 @@ async function connectHandler() {
}); });
device = devices[0]; 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; }); document.querySelectorAll('.online').forEach(element => { element.style.opacity = 1.0; });
await readHandler(); await readHandler();
}); });
@ -123,8 +124,12 @@ function setValue(element, value) {
} }
function updateElement(element, event, dataType) { function updateElement(key, event) {
var dataOffset = 3; var dataOffset = 4;
var element = document.querySelector(`[data-key="${key}"]`);
if (!element)
return;
const methods = { const methods = {
"uint32": event.data.getUint32, "uint32": event.data.getUint32,
@ -136,6 +141,8 @@ function updateElement(element, event, dataType) {
"int8": event.data.getInt8 "int8": event.data.getInt8
}; };
dataType = element.getAttribute('data-type');
if (dataType in methods) { if (dataType in methods) {
var value = methods[dataType].call(event.data, dataOffset, true); var value = methods[dataType].call(event.data, dataOffset, true);
setValue(element, value); setValue(element, value);
@ -149,24 +156,14 @@ async function readHandler() {
if (!device || !device.opened) if (!device || !device.opened)
await connectHandler(); await connectHandler();
const elements = document.querySelectorAll('.api'); await sendReport(packetType.getValAllMsg);
}
for (const element of elements) { async function handleInputReport(event) {
var key = element.getAttribute('data-key'); var data = new Uint8Array(event.data.buffer);
var dataType = element.getAttribute('data-type'); var key = data[3];
await sendReport(packetType.getValMsg, [key]); updateElement(key, event);
let incomingReport = await new Promise((resolve, reject) => {
const handleInputReport = (event) => {
updateElement(element, event, dataType);
device.removeEventListener('inputreport', handleInputReport);
resolve();
}
device.addEventListener('inputreport', handleInputReport);
});
}
} }
async function rebootHandler() { async function rebootHandler() {
@ -186,7 +183,7 @@ async function valueChangedHandler(element) {
if (origValue != newValue) { if (origValue != newValue) {
uintBuffer = packValue(element, key, dataType); uintBuffer = packValue(element, key, dataType);
/* Send to both devices */ /* Send to both devices */
await sendReport(packetType.setValMsg, uintBuffer, true); await sendReport(packetType.setValMsg, uintBuffer, true);
@ -208,7 +205,7 @@ async function saveHandler() {
continue; continue;
if (origValue != getValue(element)) if (origValue != getValue(element))
await valueChangedHandler(element); await valueChangedHandler(element);
} }
await sendReport(packetType.saveConfigMsg, [], true); await sendReport(packetType.saveConfigMsg, [], true);
} }

View File

@ -15,7 +15,7 @@
:root { :root {
--highlight-color: #384955; --highlight-color: #384955;
--font-color: #384955; --font-color: #384955;
--highlight-color2: #5e9f41; --highlight-color2: #5e9f41;
} }
html { html {
@ -483,7 +483,7 @@ input[type='number'].input-inline {
@media (min-width: 40rem) { @media (min-width: 40rem) {
.row { .row {
flex-direction: row; flex-direction: row;
width: calc(100% + 2.0rem); width: calc(100% + 2.0rem);
} }
.row .column { .row .column {