Features and fixes

- Auto switch-lock when dragging or resizing windows.
     * This was super annoying for me, resizing a window and you
       get too close to the left border, making it shrink suddenly.
       Now if you drag while holding a mouse button pressed, it will
       not switch.

- Added Mac OS multiple desktop workaround
     * you need to tweak defaults.c and set screen_count to e.g. 2 if
       you have 2 desktops on a single mac output, and set OUTPUT_A_OS
       (or OUTPUT_B_OS) to MACOS in user_config.h, then rebuild
     * This will be keyboard-configurable in the future without rebuilding
     * Support still experimental

- HID report protocol default for port B
- Added support for swapping output order
     * defaults.c, output has "pos" you can set to either LEFT or RIGHT
     * this will be hotkey-configurable

- Bugfixes
- Added debugging mode (serial CDC device for printf things)
- Refactored screensaver a bit
This commit is contained in:
Hrvoje Cavrak 2024-03-22 19:48:49 +01:00
parent f0b36569c9
commit 9c69dc3cd6
14 changed files with 371 additions and 338 deletions

View File

@ -14,8 +14,16 @@ const config_t default_config = {
.bottom = MAX_SCREEN_COORD,
},
.screen_count = 1,
.screen_index = 0,
},
.screen_index = 1,
.os = OUTPUT_A_OS,
.pos = LEFT,
.screensaver = {
.enabled = SCREENSAVER_A_ENABLED,
.only_if_inactive = SCREENSAVER_A_ONLY_IF_INACTIVE,
.idle_time_us = SCREENSAVER_A_IDLE_TIME_SEC * 1000000,
.max_time_us = SCREENSAVER_A_MAX_TIME_SEC * 1000000,
}
},
.output[OUTPUT_B] =
{
.number = OUTPUT_B,
@ -26,20 +34,14 @@ const config_t default_config = {
.bottom = MAX_SCREEN_COORD,
},
.screen_count = 1,
.screen_index = 0,
.screen_index = 1,
.os = OUTPUT_B_OS,
.pos = RIGHT,
.screensaver = {
.enabled = SCREENSAVER_B_ENABLED,
.only_if_inactive = SCREENSAVER_B_ONLY_IF_INACTIVE,
.idle_time_us = SCREENSAVER_B_IDLE_TIME_SEC * 1000000,
.max_time_us = SCREENSAVER_B_MAX_TIME_SEC * 1000000,
}
},
.screensaver[OUTPUT_A] =
{
.enabled = SCREENSAVER_A_ENABLED,
.only_if_inactive = SCREENSAVER_A_ONLY_IF_INACTIVE,
.idle_time_us = SCREENSAVER_A_IDLE_TIME_SEC * 1000000,
.max_time_us = SCREENSAVER_A_MAX_TIME_SEC * 1000000,
},
.screensaver[OUTPUT_B] =
{
.enabled = SCREENSAVER_B_ENABLED,
.only_if_inactive = SCREENSAVER_B_ONLY_IF_INACTIVE,
.idle_time_us = SCREENSAVER_B_IDLE_TIME_SEC * 1000000,
.max_time_us = SCREENSAVER_B_MAX_TIME_SEC * 1000000,
},
};
};

View File

@ -31,18 +31,24 @@ void output_toggle_hotkey_handler(device_t *state) {
switch_output(state, state->active_output);
};
/* This key combo records switch y top coordinate for different-size monitors */
void screen_border_hotkey_handler(device_t *state) {
border_size_t *border = &state->config.output[state->active_output].border;
void get_border_position(device_t *state, border_size_t *border) {
/* To avoid having 2 different keys, if we're above half, it's the top coord */
if (state->mouse_y > (MAX_SCREEN_COORD / 2))
border->bottom = state->mouse_y;
else
border->top = state->mouse_y;
}
/* This key combo records switch y top coordinate for different-size monitors */
void screen_border_hotkey_handler(device_t *state) {
border_size_t *border = &state->config.output[state->active_output].border;
if (CURRENT_BOARD_IS_ACTIVE_OUTPUT) {
get_border_position(state, border);
save_config(state);
}
send_packet((uint8_t *)border, SYNC_BORDERS_MSG, sizeof(border_size_t));
save_config(state);
};
/* This key combo puts board A in firmware upgrade mode */
@ -69,8 +75,8 @@ void wipe_config_hotkey_handler(device_t *state) {
}
void screensaver_hotkey_handler(device_t *state) {
state->config.screensaver[BOARD_ROLE].enabled ^= 1;
send_value(state->config.screensaver[BOARD_ROLE].enabled, SCREENSAVER_MSG);
state->config.output[BOARD_ROLE].screensaver.enabled ^= 1;
send_value(state->config.output[BOARD_ROLE].screensaver.enabled, SCREENSAVER_MSG);
}
/* When pressed, toggles the current mouse zoom mode state */
@ -87,19 +93,18 @@ void mouse_zoom_hotkey_handler(device_t *state) {
void handle_keyboard_uart_msg(uart_packet_t *packet, device_t *state) {
queue_kbd_report((hid_keyboard_report_t *)packet->data, state);
state->last_activity[BOARD_ROLE] = time_us_64();
state->screensaver_max_time_reached[BOARD_ROLE] = false;
}
/* Function handles received mouse moves from the other board */
void handle_mouse_abs_uart_msg(uart_packet_t *packet, device_t *state) {
mouse_abs_report_t *mouse_report = (mouse_abs_report_t *)packet->data;
mouse_report_t *mouse_report = (mouse_report_t *)packet->data;
queue_mouse_report(mouse_report, state);
state->mouse_x = mouse_report->x;
state->mouse_y = mouse_report->y;
state->mouse_buttons = mouse_report->buttons;
state->last_activity[BOARD_ROLE] = time_us_64();
state->screensaver_max_time_reached[BOARD_ROLE] = false;
}
/* Function handles request to switch output */
@ -135,7 +140,14 @@ void handle_switch_lock_msg(uart_packet_t *packet, device_t *state) {
/* Handle border syncing message that lets the other device know about monitor height offset */
void handle_sync_borders_msg(uart_packet_t *packet, device_t *state) {
border_size_t *border = &state->config.output[state->active_output].border;
memcpy(border, packet->data, sizeof(border_size_t));
if (CURRENT_BOARD_IS_ACTIVE_OUTPUT) {
get_border_position(state, border);
send_packet((uint8_t *)border, SYNC_BORDERS_MSG, sizeof(border_size_t));
}
else
memcpy(border, packet->data, sizeof(border_size_t));
save_config(state);
}
@ -151,7 +163,7 @@ void handle_wipe_config_msg(uart_packet_t *packet, device_t *state) {
}
void handle_screensaver_msg(uart_packet_t *packet, device_t *state) {
state->config.screensaver[BOARD_ROLE].enabled = packet->data[0];
state->config.output[BOARD_ROLE].screensaver.enabled = packet->data[0];
}
/**==================================================== *

View File

@ -21,7 +21,6 @@
#include "main.h"
#define IS_BLOCK_END (collection.start == collection.end)
#define MAX_BUTTONS 16
enum { SIZE_0_BIT = 0, SIZE_8_BIT = 1, SIZE_16_BIT = 2, SIZE_32_BIT = 3 };

View File

@ -88,11 +88,11 @@ typedef struct {
uint8_t usage_count;
uint8_t global_usage;
uint32_t offset_in_bits;
uint8_t usages[64];
uint8_t usages[256];
uint8_t *p_usage;
collection_t collection;
usage_map_t *map;
globals_t globals[16]; /* as tag is 4 bits, there can be 16 different tags in global header type */
} parser_state_t;
} parser_state_t;

View File

@ -162,7 +162,6 @@ void send_key(hid_keyboard_report_t *report, device_t *state) {
if (CURRENT_BOARD_IS_ACTIVE_OUTPUT) {
queue_kbd_report(report, state);
state->last_activity[BOARD_ROLE] = time_us_64();
state->screensaver_max_time_reached[BOARD_ROLE] = false;
} else {
send_packet((uint8_t *)report, KEYBOARD_REPORT_MSG, KBD_REPORT_LENGTH);
}

View File

@ -47,6 +47,9 @@
#define ENABLE 1
#define DISABLE 0
#define ABSOLUTE 0
#define RELATIVE 1
#define MAX_REPORT_ITEMS 16
#define MOUSE_BOOT_REPORT_LEN 4
@ -103,6 +106,9 @@ enum packet_type_e {
FLASH_LED_MSG = 9,
SCREENSAVER_MSG = 10,
WIPE_CONFIG_MSG = 11,
REL_MOUSE_REPORT_MSG = 12,
SWAP_OUTPUTS_MSG = 13,
HEARTBEAT_MSG = 14,
};
/*
@ -146,7 +152,25 @@ typedef struct {
/********* Configuration storage definitions **********/
#define CURRENT_CONFIG_VERSION 3
#define CURRENT_CONFIG_VERSION 4
enum os_type_e {
LINUX = 1,
MACOS = 2,
WINDOWS = 3,
OTHER = 255,
};
enum screen_pos_e {
LEFT = 1,
RIGHT = 2,
MIDDLE = 3,
};
enum itf_num_e {
ITF_NUM_HID = 0,
ITF_NUM_HID_REL_M = 1,
};
typedef struct {
int top; // When jumping from a smaller to a bigger screen, go to THIS top height
@ -154,16 +178,6 @@ typedef struct {
// height
} border_size_t;
/* Define output parameters */
typedef struct {
int number; // Number of this output (e.g. OUTPUT_A = 0 etc)
int screen_count; // How many monitors per output (e.g. Output A is Windows with 3 monitors)
int screen_index; // Current active screen
int speed_x; // Mouse speed per output, in direction X
int speed_y; // Mouse speed per output, in direction Y
border_size_t border; // Screen border size/offset to keep cursor at same height when switching
} output_t;
/* Define screensaver parameters */
typedef struct {
uint8_t enabled;
@ -172,13 +186,26 @@ typedef struct {
uint64_t max_time_us;
} screensaver_t;
/* Define output parameters */
typedef struct {
int number; // Number of this output (e.g. OUTPUT_A = 0 etc)
int screen_count; // How many monitors per output (e.g. Output A is Windows with 3 monitors)
int screen_index; // Current active screen
int speed_x; // Mouse speed per output, in direction X
int speed_y; // Mouse speed per output, in direction Y
border_size_t border; // Screen border size/offset to keep cursor at same height when switching
enum os_type_e os; // Operating system on this output
enum screen_pos_e pos; // Screen position on this output
screensaver_t screensaver; // Screensaver parameters for this output
} output_t;
/* Data structure defining how configuration is stored */
typedef struct {
uint32_t magic_header;
uint32_t version;
uint8_t force_mouse_boot_mode;
output_t output[NUM_SCREENS];
screensaver_t screensaver[NUM_SCREENS];
uint8_t screensaver_enabled;
// Keep checksum at the end of the struct
uint32_t checksum;
} config_t;
@ -211,8 +238,8 @@ typedef struct TU_ATTR_PACKED {
int16_t x;
int16_t y;
int8_t wheel;
int8_t pan;
} mouse_abs_report_t;
uint8_t mode;
} mouse_report_t;
typedef enum { IDLE, READING_PACKET, PROCESSING_PACKET } receiver_state_t;
@ -222,13 +249,13 @@ typedef struct {
uint8_t keyboard_leds[NUM_SCREENS]; // State of keyboard LEDs (index 0 = A, index 1 = B)
uint64_t last_activity[NUM_SCREENS]; // Timestamp of the last input activity (-||-)
bool screensaver_max_time_reached[NUM_SCREENS]; // Screensaver maximum time has been reached (will be reset at the next input activity)
receiver_state_t receiver_state; // Storing the state for the simple receiver state machine
uint64_t core1_last_loop_pass; // Timestamp of last core1 loop execution
uint8_t active_output; // Currently selected output (0 = A, 1 = B)
int16_t mouse_x; // Store and update the location of our mouse pointer
int16_t mouse_y;
int16_t mouse_buttons; // Store and update the state of mouse buttons
config_t config; // Device configuration, loaded from flash or defaults used
mouse_t mouse_dev; // Mouse device specifics, e.g. stores locations for keys in report
@ -241,9 +268,9 @@ typedef struct {
bool mouse_connected; // True when a mouse is connected locally
/* Feature flags */
bool mouse_zoom; // True when "mouse zoom" is enabled
bool switch_lock; // True when device is prevented from switching
bool onboard_led_state; // True when LED is ON
bool mouse_zoom; // True when "mouse zoom" is enabled
bool switch_lock; // True when device is prevented from switching
bool onboard_led_state; // True when LED is ON
/* Onboard LED blinky (provide feedback when e.g. mouse connected) */
int32_t blinks_left; // How many blink transitions are left
@ -265,16 +292,15 @@ void process_kbd_queue_task(device_t *);
void send_key(hid_keyboard_report_t *, device_t *);
/********* Mouse **********/
bool tud_hid_abs_mouse_report(
uint8_t report_id, uint8_t buttons, int16_t x, int16_t y, int8_t vertical, int8_t horizontal);
bool tud_mouse_report(uint8_t mode, uint8_t buttons, int16_t x, int16_t y, int8_t wheel);
void process_mouse_report(uint8_t *, int, device_t *);
uint8_t
parse_report_descriptor(mouse_t *mouse, uint8_t arr_count, uint8_t const *desc_report, uint16_t desc_len);
int32_t get_report_value(uint8_t *report, report_val_t *val);
void process_mouse_queue_task(device_t *);
void queue_mouse_report(mouse_abs_report_t *, device_t *);
void output_mouse_report(mouse_abs_report_t *, device_t *);
void queue_mouse_report(mouse_report_t *, device_t *);
void output_mouse_report(mouse_report_t *, device_t *);
/********* UART **********/
void receive_char(uart_packet_t *, device_t *);

View File

@ -46,14 +46,16 @@ void update_mouse_position(device_t *state, mouse_values_t *values) {
/* Update movement */
state->mouse_x = move_and_keep_on_screen(state->mouse_x, offset_x);
state->mouse_y = move_and_keep_on_screen(state->mouse_y, offset_y);
/* Update buttons state */
state->mouse_buttons = values->buttons;
}
/* If we are active output, queue packet to mouse queue, else send them through UART */
void output_mouse_report(mouse_abs_report_t *report, device_t *state) {
void output_mouse_report(mouse_report_t *report, device_t *state) {
if (CURRENT_BOARD_IS_ACTIVE_OUTPUT) {
queue_mouse_report(report, state);
state->last_activity[BOARD_ROLE] = time_us_64();
state->screensaver_max_time_reached[BOARD_ROLE] = false;
} else {
send_packet((uint8_t *)report, MOUSE_REPORT_MSG, MOUSE_REPORT_LENGTH);
}
@ -90,31 +92,81 @@ int16_t scale_y_coordinate(int screen_from, int screen_to, device_t *state) {
return ((state->mouse_y - from->border.top) * MAX_SCREEN_COORD) / size_from;
}
void switch_screen(device_t *state, int new_x, int output_from, int output_to) {
mouse_abs_report_t hidden_pointer = {.y = MIN_SCREEN_COORD, .x = MAX_SCREEN_COORD};
void switch_screen(device_t *state, output_t *output, int new_x, int output_from, int output_to, int direction) {
mouse_report_t hidden_pointer = {.y = MIN_SCREEN_COORD, .x = MAX_SCREEN_COORD};
output_mouse_report(&hidden_pointer, state);
switch_output(state, output_to);
state->mouse_x = (output_to == OUTPUT_A) ? MIN_SCREEN_COORD : MAX_SCREEN_COORD;
state->mouse_y = scale_y_coordinate(output_from, output_to, state);
state->mouse_x = (direction == LEFT) ? MAX_SCREEN_COORD : MIN_SCREEN_COORD;
state->mouse_y = scale_y_coordinate(output->number, 1 - output->number, state);
}
void check_screen_switch(const mouse_values_t *values, device_t *state) {
int new_x = state->mouse_x + values->move_x;
void switch_desktop(device_t *state, output_t *output, int new_index, int direction) {
switch (output->os) {
case MACOS:
/* Send relative mouse movement here as well, one or two pixels in the direction of
movement, BEFORE absolute report sets X to 0 */
mouse_report_t move_relative_one = {.x = (direction == LEFT) ? 16384-2 : 16384+2, .mode = RELATIVE};
/* No switching allowed if explicitly disabled */
if (state->switch_lock)
/* Once doesn't seem reliable enough, do it twice */
output_mouse_report(&move_relative_one, state);
output_mouse_report(&move_relative_one, state);
break;
case WINDOWS:
/* TODO: Switch to relative-only if index > 1, but keep tabs to switch back */
break;
case LINUX:
case OTHER:
/* Linux should treat all desktops as a single virtual screen, so you should leave
screen_count at 1 and it should just work */
break;
}
state->mouse_x = (direction == RIGHT) ? MIN_SCREEN_COORD : MAX_SCREEN_COORD;
output->screen_index = new_index;
}
/* BORDER
|
.---------. .---------. | .---------. .---------. .---------.
|| B2 || || B1 || | || A1 || || A2 || || A3 || (output, index)
|| extra || || main || | || main || || extra || || extra || (main or extra)
'---------' '---------' | '---------' '---------' '---------'
)___( )___( | )___( )___( )___(
*/
void check_screen_switch(const mouse_values_t *values, device_t *state) {
int new_x = state->mouse_x + values->move_x;
output_t *output = &state->config.output[state->active_output];
bool jump_left = new_x < MIN_SCREEN_COORD - JUMP_THRESHOLD;
bool jump_right = new_x > MAX_SCREEN_COORD + JUMP_THRESHOLD;
int direction = jump_left ? LEFT : RIGHT;
/* No switching allowed if explicitly disabled or mouse button is held */
if (state->switch_lock || state->mouse_buttons)
return;
/* End of screen left switches screen A->B TODO: make configurable */
if (new_x < MIN_SCREEN_COORD - JUMP_THRESHOLD && state->active_output == OUTPUT_A) {
switch_screen(state, new_x, OUTPUT_A, OUTPUT_B);
/* No jump condition met == nothing to do, return */
if (!jump_left && !jump_right)
return;
/* We want to jump in the direction of the other computer */
if (output->pos != direction) {
if (output->screen_index == 1) /* We are at the border -> switch outputs */
switch_screen(state, output, new_x, state->active_output, 1 - state->active_output, direction);
/* If here, this output has multiple desktops and we are not on the main one */
else
switch_desktop(state, output, output->screen_index - 1, direction);
}
/* End of screen right switches screen B->A TODO: make configurable */
else if (new_x > MAX_SCREEN_COORD + JUMP_THRESHOLD && state->active_output == OUTPUT_B) {
switch_screen(state, new_x, OUTPUT_B, OUTPUT_A);
}
/* We want to jump away from the other computer, only possible if there is another screen to jump to */
else if (output->screen_index < output->screen_count)
switch_desktop(state, output, output->screen_index + 1, direction);
}
void extract_report_values(uint8_t *raw_report, device_t *state, mouse_values_t *values) {
@ -139,12 +191,13 @@ void extract_report_values(uint8_t *raw_report, device_t *state, mouse_values_t
values->buttons = get_report_value(raw_report, &state->mouse_dev.buttons);
}
mouse_abs_report_t create_mouse_report(device_t *state, mouse_values_t *values) {
mouse_abs_report_t abs_mouse_report = {.buttons = values->buttons,
mouse_report_t create_mouse_report(device_t *state, mouse_values_t *values) {
mouse_report_t abs_mouse_report = {.buttons = values->buttons,
.x = state->mouse_x,
.y = state->mouse_y,
.wheel = values->wheel,
.pan = 0};
.mode = ABSOLUTE,
};
return abs_mouse_report;
}
@ -158,7 +211,7 @@ void process_mouse_report(uint8_t *raw_report, int len, device_t *state) {
update_mouse_position(state, &values);
/* Create the report for the output PC based on the updated values */
mouse_abs_report_t report = create_mouse_report(state, &values);
mouse_report_t report = create_mouse_report(state, &values);
/* Move the mouse, depending where the output is supposed to go */
output_mouse_report(&report, state);
@ -172,7 +225,7 @@ void process_mouse_report(uint8_t *raw_report, int len, device_t *state) {
* ==================================================== */
void process_mouse_queue_task(device_t *state) {
mouse_abs_report_t report = {0};
mouse_report_t report = {0};
/* We need to be connected to the host to send messages */
if (!state->tud_connected)
@ -187,15 +240,15 @@ void process_mouse_queue_task(device_t *state) {
tud_remote_wakeup();
/* ... try sending it to the host, if it's successful */
bool succeeded = tud_hid_abs_mouse_report(
REPORT_ID_MOUSE, report.buttons, report.x, report.y, report.wheel, report.pan);
bool succeeded = tud_mouse_report(
report.mode, report.buttons, report.x, report.y, report.wheel);
/* ... then we can remove it from the queue */
if (succeeded)
queue_try_remove(&state->mouse_queue, &report);
}
void queue_mouse_report(mouse_abs_report_t *report, device_t *state) {
void queue_mouse_report(mouse_report_t *report, device_t *state) {
/* It wouldn't be fun to queue up a bunch of messages and then dump them all on host */
if (!state->tud_connected)
return;

View File

@ -54,6 +54,12 @@ void pio_usb_host_config(void) {
/* tuh_configure() must be called before tuh_init() */
static pio_usb_configuration_t config = PIO_USB_DEFAULT_CONFIG;
config.pin_dp = PIO_USB_DP_PIN_DEFAULT;
/* Make HID protocol the default for port B as a fix for devices enumerating
themselves as both keyboards and mice, but having just a single common mode */
if(BOARD_ROLE == OUTPUT_B)
tuh_hid_set_default_protocol(HID_PROTOCOL_REPORT);
tuh_configure(BOARD_TUH_RHPORT, TUH_CFGID_RPI_PIO_USB_CONFIGURATION, &config);
/* Initialize and configure TinyUSB Host */
@ -80,15 +86,15 @@ void initial_setup(device_t *state) {
/* Initialize keyboard and mouse queues */
queue_init(&state->kbd_queue, sizeof(hid_keyboard_report_t), KBD_QUEUE_LENGTH);
queue_init(&state->mouse_queue, sizeof(mouse_abs_report_t), MOUSE_QUEUE_LENGTH);
queue_init(&state->mouse_queue, sizeof(mouse_report_t), MOUSE_QUEUE_LENGTH);
/* Setup RP2040 Core 1 */
multicore_reset_core1();
multicore_launch_core1(core1_main);
/* Initialize and configure TinyUSB Device */
tud_init(BOARD_TUD_RHPORT);
tud_init(BOARD_TUD_RHPORT);
/* Initialize and configure TinyUSB Host */
pio_usb_host_config();

View File

@ -49,11 +49,6 @@ extern "C" {
#define CFG_TUH_ENABLED 1
#define CFG_TUH_RPI_PIO_USB 1
// defined by board.mk
#ifndef CFG_TUSB_MCU
#error CFG_TUSB_MCU must be defined
#endif
// RHPort number used for device can be defined by board.mk, default to port 0
#ifndef BOARD_DEVICE_RHPORT_NUM
#define BOARD_DEVICE_RHPORT_NUM 0
@ -62,22 +57,13 @@ extern "C" {
// RHPort max operational speed can defined by board.mk
// Default to Highspeed for MCU with internal HighSpeed PHY (can be port specific), otherwise FullSpeed
#ifndef BOARD_DEVICE_RHPORT_SPEED
#if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX \
|| CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || CFG_TUSB_MCU == OPT_MCU_NUC505 \
|| CFG_TUSB_MCU == OPT_MCU_CXD56 || CFG_TUSB_MCU == OPT_MCU_SAMX7X)
#define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_HIGH_SPEED
#else
#define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED
#endif
#endif
// Device mode with rhport and speed defined by board.mk
#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED)
#define CFG_TUSB_RHPORT1_MODE (OPT_MODE_HOST | BOARD_DEVICE_RHPORT_SPEED)
// CFG_TUSB_DEBUG is defined by compiler in DEBUG build
// #define CFG_TUSB_DEBUG 0
/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment.
* Tinyusb use follows macros to declare transferring memory so that they can be put
* into those specific section.
@ -101,9 +87,25 @@ extern "C" {
#define CFG_TUD_ENDPOINT0_SIZE 64
#endif
//------------- DEBUG -------------//
#ifdef DH_DEBUG
#define CFG_TUD_CDC 1
#define CFG_TUSB_DEBUG 2
#define CFG_TUD_LOG_LEVEL 3
#define CFG_TUSB_DEBUG_PRINTF dh_debug_printf
extern int dh_debug_printf(const char *__restrict __format, ...);
#define CFG_TUD_CDC_RX_BUFSIZE 64
#define CFG_TUD_CDC_TX_BUFSIZE 64
#define CFG_TUH_CDC_LINE_CODING_ON_ENUM \
{ 921600, CDC_LINE_CONDING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 }
#else
#define CFG_TUD_CDC 0
#endif
//------------- CLASS -------------//
#define CFG_TUD_HID 1
#define CFG_TUD_CDC 0
#define CFG_TUD_HID 2
#define CFG_TUD_MSC 0
#define CFG_TUD_MIDI 0
#define CFG_TUD_VENDOR 0
@ -116,7 +118,7 @@ extern "C" {
//--------------------------------------------------------------------
// Size of buffer to hold descriptors and other data used for enumeration
#define CFG_TUH_ENUMERATION_BUFSIZE 256
#define CFG_TUH_ENUMERATION_BUFSIZE 512
#define CFG_TUH_HUB 1
// max device support (excluding hub device)

View File

@ -114,7 +114,9 @@ void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_re
at least we get all the information we need (looking at you, mouse wheel) */
if (tuh_hid_get_protocol(dev_addr, instance) == HID_PROTOCOL_BOOT) {
tuh_hid_set_protocol(dev_addr, instance, HID_PROTOCOL_REPORT);
}
}
global_state.mouse_dev.protocol = tuh_hid_get_protocol(dev_addr, instance);
parse_report_descriptor(&global_state.mouse_dev, MAX_REPORTS, desc_report, desc_len);
global_state.mouse_connected = true;

View File

@ -59,132 +59,66 @@ uint8_t const *tud_descriptor_device_cb(void) {
// HID Report Descriptor
//--------------------------------------------------------------------+
// Relative mouse is used to overcome limitations of multiple desktops on MacOS and Windows
uint8_t const desc_hid_report[] = {TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(REPORT_ID_KEYBOARD)),
TUD_HID_REPORT_DESC_ABSMOUSE(HID_REPORT_ID(REPORT_ID_MOUSE))};
uint8_t const desc_hid_report_relmouse[] = {TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(REPORT_ID_RELMOUSE))};
// Invoked when received GET HID REPORT DESCRIPTOR
// Application return pointer to descriptor
// Descriptor contents must exist long enough for transfer to complete
uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance) {
(void)instance;
if (instance == ITF_NUM_HID_REL_M) {
return desc_hid_report_relmouse;
}
/* Default */
return desc_hid_report;
}
bool tud_hid_n_abs_mouse_report(uint8_t instance,
uint8_t report_id,
uint8_t buttons,
int16_t x,
int16_t y,
int8_t vertical,
int8_t horizontal) {
mouse_abs_report_t report = {.buttons = buttons, .x = x, .y = y, .wheel = vertical, .pan = horizontal};
return tud_hid_n_report(instance, report_id, &report, sizeof(report));
bool tud_mouse_report(uint8_t mode,
uint8_t buttons,
int16_t x,
int16_t y,
int8_t wheel) {
if (mode == ABSOLUTE) {
mouse_report_t report = {.buttons = buttons, .x = x, .y = y, .wheel = wheel};
return tud_hid_n_report(ITF_NUM_HID, REPORT_ID_MOUSE, &report, sizeof(report));
}
else {
hid_mouse_report_t report = {.buttons = buttons, .x = x - 16384, .y = y - 16384, .wheel = wheel, .pan = 0};
return tud_hid_n_report(ITF_NUM_HID_REL_M, REPORT_ID_RELMOUSE, &report, sizeof(report));
}
}
bool tud_hid_abs_mouse_report(
uint8_t report_id, uint8_t buttons, int16_t x, int16_t y, int8_t vertical, int8_t horizontal) {
return tud_hid_n_abs_mouse_report(0, report_id, buttons, x, y, vertical, horizontal);
}
//--------------------------------------------------------------------+
// Configuration Descriptor
//--------------------------------------------------------------------+
enum { ITF_NUM_HID, ITF_NUM_TOTAL };
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN)
#define EPNUM_HID 0x81
uint8_t const desc_configuration[] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 500),
// Interface number, string index, protocol, report descriptor len, EP In address, size &
// polling interval
TUD_HID_DESCRIPTOR(ITF_NUM_HID,
0,
HID_ITF_PROTOCOL_NONE,
sizeof(desc_hid_report),
EPNUM_HID,
CFG_TUD_HID_EP_BUFSIZE,
1)};
#if TUD_OPT_HIGH_SPEED
// Per USB specs: high speed capable device must report device_qualifier and
// other_speed_configuration
// other speed configuration
uint8_t desc_other_speed_config[CONFIG_TOTAL_LEN];
// device qualifier is mostly similar to device descriptor since we don't change configuration based
// on speed
tusb_desc_device_qualifier_t const desc_device_qualifier = {.bLength = sizeof(tusb_desc_device_qualifier_t),
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
.bcdUSB = USB_BCD,
.bDeviceClass = 0x00,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.bNumConfigurations = 0x01,
.bReserved = 0x00};
// Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long enough for transfer to
// complete. device_qualifier descriptor describes information about a high-speed capable device
// that would change if the device were operating at the other speed. If not highspeed capable stall
// this request.
uint8_t const *tud_descriptor_device_qualifier_cb(void) {
return (uint8_t const *)&desc_device_qualifier;
}
// Invoked when received GET OTHER SEED CONFIGURATION DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long enough for transfer to
// complete Configuration descriptor in the other speed e.g if high speed then this is for full
// speed and vice versa
uint8_t const *tud_descriptor_other_speed_configuration_cb(uint8_t index) {
(void)index; // for multiple configurations
// other speed config is basically configuration with type = OHER_SPEED_CONFIG
memcpy(desc_other_speed_config, desc_configuration, CONFIG_TOTAL_LEN);
desc_other_speed_config[1] = TUSB_DESC_OTHER_SPEED_CONFIG;
// this example use the same configuration for both high and full speed mode
return desc_other_speed_config;
}
#endif // highspeed
// Invoked when received GET CONFIGURATION DESCRIPTOR
// Application return pointer to descriptor
// Descriptor contents must exist long enough for transfer to complete
uint8_t const *tud_descriptor_configuration_cb(uint8_t index) {
(void)index; // for multiple configurations
// This example use the same configuration for both high and full speed mode
return desc_configuration;
}
//--------------------------------------------------------------------+
// String Descriptors
//--------------------------------------------------------------------+
// String Descriptor Index
enum {
STRID_LANGID = 0,
STRID_MANUFACTURER,
STRID_PRODUCT,
STRID_SERIAL,
};
// array of pointer to string descriptors
char const *string_desc_arr[] = {
(const char[]){0x09, 0x04}, // 0: is supported language is English (0x0409)
"Hrvoje Cavrak", // 1: Manufacturer
"DeskHop Switch", // 2: Product
"0", // 3: Serials, should use chip ID
"MouseHelper", // 4: Relative mouse to work around OS issues
#ifdef DH_DEBUG
"Debug Interface", // 5: Debug Interface
#endif
};
// String Descriptor Index
enum {
STRID_LANGID = 0,
STRID_MANUFACTURER,
STRID_PRODUCT,
STRID_SERIAL,
STRID_MOUSE,
STRID_DEBUG,
};
static uint16_t _desc_str[32];
@ -225,3 +159,60 @@ uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
return _desc_str;
}
//--------------------------------------------------------------------+
// Configuration Descriptor
//--------------------------------------------------------------------+
#define EPNUM_HID 0x81
#define EPNUM_HID_REL_M 0x82
#ifndef DH_DEBUG
enum { ITF_NUM_TOTAL = 2 };
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN + TUD_HID_DESC_LEN)
#else
enum { ITF_NUM_CDC = 2, ITF_NUM_TOTAL = 3 };
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN + TUD_HID_DESC_LEN + TUD_CDC_DESC_LEN)
#define EPNUM_CDC_NOTIF 0x83
#define EPNUM_CDC_OUT 0x04
#define EPNUM_CDC_IN 0x84
#endif
uint8_t const desc_configuration[] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 500),
// Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval
TUD_HID_DESCRIPTOR(ITF_NUM_HID,
STRID_PRODUCT,
HID_ITF_PROTOCOL_NONE,
sizeof(desc_hid_report),
EPNUM_HID,
CFG_TUD_HID_EP_BUFSIZE,
1),
TUD_HID_DESCRIPTOR(ITF_NUM_HID_REL_M,
STRID_MOUSE,
HID_ITF_PROTOCOL_NONE,
sizeof(desc_hid_report_relmouse),
EPNUM_HID_REL_M,
CFG_TUD_HID_EP_BUFSIZE,
20),
#ifdef DH_DEBUG
// Interface number, string index, EP notification address and size, EP data address (out, in) and size.
TUD_CDC_DESCRIPTOR(
ITF_NUM_CDC, STRID_DEBUG, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, CFG_TUD_CDC_EP_BUFSIZE),
#endif
};
uint8_t const *tud_descriptor_configuration_cb(uint8_t index) {
(void)index; // for multiple configurations
return desc_configuration;
}

View File

@ -29,10 +29,14 @@ enum
{
REPORT_ID_KEYBOARD = 1,
REPORT_ID_MOUSE,
REPORT_ID_CONSUMER_CONTROL,
REPORT_ID_COUNT
};
enum
{
REPORT_ID_RELMOUSE = 1,
};
#define TUD_HID_REPORT_DESC_ABSMOUSE(...) \
HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\
HID_USAGE ( HID_USAGE_DESKTOP_MOUSE ) ,\
@ -74,92 +78,12 @@ HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\
HID_REPORT_COUNT( 1 ) ,\
HID_REPORT_SIZE ( 8 ) ,\
HID_INPUT ( HID_DATA | HID_VARIABLE | HID_RELATIVE ) ,\
HID_USAGE_PAGE ( HID_USAGE_PAGE_CONSUMER ), \
\
/* Horizontal wheel scroll [-127, 127] */ \
HID_USAGE_N ( HID_USAGE_CONSUMER_AC_PAN, 2 ), \
HID_LOGICAL_MIN ( 0x81 ), \
HID_LOGICAL_MAX ( 0x7f ), \
/* Mouse mode (0 = absolute, 1 = relative) */ \
HID_REPORT_COUNT( 1 ), \
HID_REPORT_SIZE ( 8 ), \
HID_INPUT ( HID_DATA | HID_VARIABLE | HID_RELATIVE ), \
HID_INPUT ( HID_CONSTANT ), \
HID_COLLECTION_END , \
HID_COLLECTION_END \
/* Generated report */
/* */
/* 0x05, 0x01, Usage Page (Desktop), */
/* 0x09, 0x06, Usage (Keyboard), */
/* 0xA1, 0x01, Collection (Application), */
/* 0x85, 0x01, Report ID (1), */
/* 0x05, 0x07, Usage Page (Keyboard), */
/* 0x19, 0xE0, Usage Minimum (KB Leftcontrol), */
/* 0x29, 0xE7, Usage Maximum (KB Right GUI), */
/* 0x15, 0x00, Logical Minimum (0), */
/* 0x25, 0x01, Logical Maximum (1), */
/* 0x95, 0x08, Report Count (8), */
/* 0x75, 0x01, Report Size (1), */
/* 0x81, 0x02, Input (Variable), */
/* 0x95, 0x01, Report Count (1), */
/* 0x75, 0x08, Report Size (8), */
/* 0x81, 0x01, Input (Constant), */
/* 0x05, 0x08, Usage Page (LED), */
/* 0x19, 0x01, Usage Minimum (01h), */
/* 0x29, 0x05, Usage Maximum (05h), */
/* 0x95, 0x05, Report Count (5), */
/* 0x75, 0x01, Report Size (1), */
/* 0x91, 0x02, Output (Variable), */
/* 0x95, 0x01, Report Count (1), */
/* 0x75, 0x03, Report Size (3), */
/* 0x91, 0x01, Output (Constant), */
/* 0x05, 0x07, Usage Page (Keyboard), */
/* 0x19, 0x00, Usage Minimum (None), */
/* 0x2A, 0xFF, 0x00, Usage Maximum (FFh), */
/* 0x15, 0x00, Logical Minimum (0), */
/* 0x26, 0xFF, 0x00, Logical Maximum (255), */
/* 0x95, 0x06, Report Count (6), */
/* 0x75, 0x08, Report Size (8), */
/* 0x81, 0x00, Input, */
/* 0xC0, End Collection, */
/* 0x05, 0x01, Usage Page (Desktop), */
/* 0x09, 0x02, Usage (Mouse), */
/* 0xA1, 0x01, Collection (Application), */
/* 0x85, 0x02, Report ID (2), */
/* 0x09, 0x01, Usage (Pointer), */
/* 0xA1, 0x00, Collection (Physical), */
/* 0x05, 0x09, Usage Page (Button), */
/* 0x19, 0x01, Usage Minimum (01h), */
/* 0x29, 0x05, Usage Maximum (05h), */
/* 0x15, 0x00, Logical Minimum (0), */
/* 0x25, 0x01, Logical Maximum (1), */
/* 0x95, 0x05, Report Count (5), */
/* 0x75, 0x01, Report Size (1), */
/* 0x81, 0x02, Input (Variable), */
/* 0x95, 0x01, Report Count (1), */
/* 0x75, 0x03, Report Size (3), */
/* 0x81, 0x01, Input (Constant), */
/* 0x05, 0x01, Usage Page (Desktop), */
/* 0x09, 0x30, Usage (X), */
/* 0x09, 0x31, Usage (Y), */
/* 0x15, 0x00, Logical Minimum (0), */
/* 0x26, 0xFF, 0x7F, Logical Maximum (32767), */
/* 0x75, 0x10, Report Size (16), */
/* 0x95, 0x02, Report Count (2), */
/* 0x81, 0x02, Input (Variable), */
/* 0x09, 0x38, Usage (Wheel), */
/* 0x15, 0x81, Logical Minimum (-127), */
/* 0x25, 0x7F, Logical Maximum (127), */
/* 0x95, 0x01, Report Count (1), */
/* 0x75, 0x08, Report Size (8), */
/* 0x81, 0x06, Input (Variable, Relative), */
/* 0x05, 0x0C, Usage Page (Consumer), */
/* 0x0A, 0x38, 0x02, Usage (AC Pan), */
/* 0x15, 0x81, Logical Minimum (-127), */
/* 0x25, 0x7F, Logical Maximum (127), */
/* 0x95, 0x01, Report Count (1), */
/* 0x75, 0x08, Report Size (8), */
/* 0x81, 0x06, Input (Variable, Relative), */
/* 0xC0, End Collection, */
/* 0xC0 End Collection */
#endif /* USB_DESCRIPTORS_H_ */

View File

@ -29,6 +29,7 @@
* a key that is unlikely to ever appear on a keyboard that you will use.
* HID_KEY_F24 is probably a good choice as keyboards with 24 function keys
* are rare.
*
* */
#define HOTKEY_TOGGLE HID_KEY_CAPS_LOCK
@ -54,13 +55,12 @@
#define MOUSE_SPEED_A_FACTOR_X 16
#define MOUSE_SPEED_A_FACTOR_Y 16
/* Output B values */
/* Output B values */
#define MOUSE_SPEED_B_FACTOR_X 16
#define MOUSE_SPEED_B_FACTOR_Y 16
#define JUMP_THRESHOLD 0
/**================================================== *
* ============== Screensaver Config ============== *
* ================================================== *
@ -104,8 +104,8 @@
*
**/
#define SCREENSAVER_A_ENABLED 1
#define SCREENSAVER_B_ENABLED 0
#define SCREENSAVER_A_ENABLED 0
#define SCREENSAVER_B_ENABLED 0
/**================================================== *
*
@ -114,19 +114,18 @@
*
**/
#define SCREENSAVER_A_IDLE_TIME_SEC 60
#define SCREENSAVER_A_IDLE_TIME_SEC 240
#define SCREENSAVER_B_IDLE_TIME_SEC 240
/**================================================== *
*
* SCREENSAVER_{A|B}_MAX_TIME_SEC: Number of seconds that an output
* can be inactive before the screensaver mode will be deactivated. If
* zero, the screensaver will run indefinitely.
* SCREENSAVER_{A|B}_MAX_TIME_SEC: Number of seconds that the screensaver
* will run on an output before being deactivated. 0 for indefinitely.
*
**/
#define SCREENSAVER_A_MAX_TIME_SEC 120
#define SCREENSAVER_B_MAX_TIME_SEC 0
#define SCREENSAVER_A_MAX_TIME_SEC 0
#define SCREENSAVER_B_MAX_TIME_SEC 0
/**================================================== *
*
@ -135,5 +134,22 @@
*
**/
#define SCREENSAVER_A_ONLY_IF_INACTIVE 1
#define SCREENSAVER_B_ONLY_IF_INACTIVE 1
#define SCREENSAVER_A_ONLY_IF_INACTIVE 0
#define SCREENSAVER_B_ONLY_IF_INACTIVE 0
/**================================================== *
* ================ Output OS Config =============== *
* ==================================================
*
* Defines OS an output connects to. You will need to worry about this only if you have
* multiple desktops and one of your outputs is MacOS or Windows.
*
* Available options: LINUX, MACOS, WINDOWS, OTHER (check main.h for details)
*
* OUTPUT_A_OS: OS for output A
* OUTPUT_B_OS: OS for output B
*
* */
#define OUTPUT_A_OS LINUX
#define OUTPUT_B_OS LINUX

View File

@ -104,56 +104,37 @@ void save_config(device_t *state) {
restore_interrupts(ints);
}
/* Have something fun and entertaining when idle */
/* Have something fun and entertaining when idle. */
void screensaver_task(device_t *state) {
const int mouse_move_delay = 5000;
const int mouse_move_delay = 5000;
screensaver_t *screensaver = &state->config.output[BOARD_ROLE].screensaver;
static mouse_abs_report_t report = {.x = 0, .y = 0};
static int last_pointer_move = 0;
static mouse_report_t report = {.x = 0, .y = 0};
static int last_pointer_move = 0;
uint64_t current_time = time_us_64();
static uint64_t last_activation_time = 0;
uint64_t current_time = time_us_64();
uint64_t inactivity_period = current_time - state->last_activity[BOARD_ROLE];
/* "Randomly" chosen initial values */
static int dx = 20;
static int dy = 25;
/* If the maximum time has been reached, nothing to do here. */
if (state->screensaver_max_time_reached[BOARD_ROLE]) {
return;
}
/* If we're not enabled, nothing to do here. */
if (!state->config.screensaver[BOARD_ROLE].enabled) {
last_activation_time = 0;
return;
}
if (!screensaver->enabled)
return;
/* System is still not idle for long enough to activate or we've been running for too long */
if (inactivity_period < screensaver->idle_time_us)
return;
/* We exceeded the maximum permitted screensaver runtime */
if (screensaver->max_time_us
&& inactivity_period > (screensaver->max_time_us + screensaver->idle_time_us))
return;
/* If we're not the selected output and that is required, nothing to do here. */
if (state->config.screensaver[BOARD_ROLE].only_if_inactive &&
CURRENT_BOARD_IS_ACTIVE_OUTPUT) {
last_activation_time = 0;
if (screensaver->only_if_inactive && CURRENT_BOARD_IS_ACTIVE_OUTPUT)
return;
}
/* We are enabled, but idle time still too small to activate. */
if ((current_time - state->last_activity[BOARD_ROLE]) <
state->config.screensaver[BOARD_ROLE].idle_time_us) {
last_activation_time = 0;
return;
}
if (last_activation_time == 0) {
last_activation_time = current_time;
} else {
/* We are enabled, but max time has been reached. */
if ((current_time - last_activation_time) >
state->config.screensaver[BOARD_ROLE].max_time_us) {
state->screensaver_max_time_reached[BOARD_ROLE] = true;
last_activation_time = 0;
return;
}
}
/* We're active! Now check if it's time to move the cursor yet. */
if ((time_us_32()) - last_pointer_move < mouse_move_delay)
@ -175,3 +156,23 @@ void screensaver_task(device_t *state) {
/* Update timer of the last pointer move */
last_pointer_move = time_us_32();
}
/* ================================================== *
* Debug functions
* ================================================== */
#ifdef DH_DEBUG
int dh_debug_printf(const char *format, ...) {
va_list args;
va_start(args, format);
char buffer[512];
int string_len = vsnprintf(buffer, 512, format, args);
tud_cdc_n_write(0, buffer, string_len);
tud_cdc_write_flush();
va_end(args);
}
#endif