From 9a230245f55de8afc723c1526f1f5368e7f1ebff Mon Sep 17 00:00:00 2001 From: Florian Didron Date: Mon, 15 Dec 2025 14:00:38 +0700 Subject: [PATCH] feat(trackpad): mouse fallback when host doesn't support PTP --- drivers/sensors/navigator_trackpad_ptp.c | 172 +++++++++++++++++- tmk_core/protocol/chibios/usb_main.c | 31 +++- .../protocol/chibios/usb_report_handling.c | 8 +- tmk_core/protocol/report.h | 13 ++ tmk_core/protocol/usb_descriptor.c | 33 ++++ 5 files changed, 249 insertions(+), 8 deletions(-) diff --git a/drivers/sensors/navigator_trackpad_ptp.c b/drivers/sensors/navigator_trackpad_ptp.c index a688db23ff..6bd3f84857 100644 --- a/drivers/sensors/navigator_trackpad_ptp.c +++ b/drivers/sensors/navigator_trackpad_ptp.c @@ -3,6 +3,7 @@ // PTP (Precision Touchpad) mode implementation for Navigator trackpad // Converts Cirque Gen 6 sensor data to Windows Precision Touchpad HID reports +// Also provides a fallback mouse collection for systems that don't support PTP #include "navigator_trackpad_ptp.h" #include "navigator_trackpad_common.h" @@ -13,9 +14,30 @@ #ifdef PRECISION_TRACKPAD_ENABLE -// External declaration for report sending +// External declaration for report sending (used for both PTP and mouse reports) extern void send_trackpad(report_digitizer_t *report); +// Input mode: 0 = Mouse, 3 = PTP +// Defined in usb_main.c +extern uint8_t get_trackpad_input_mode(void); + +#define TRACKPAD_INPUT_MODE_MOUSE 0 +#define TRACKPAD_INPUT_MODE_PTP 3 + +// Fallback mouse configuration +#ifndef TRACKPAD_MOUSE_SENSITIVITY +# define TRACKPAD_MOUSE_SENSITIVITY 1.0f +#endif + +// Tap-to-click configuration +#ifndef TRACKPAD_TAP_TERM_MS +# define TRACKPAD_TAP_TERM_MS 150 // Maximum duration for a tap (ms) +#endif + +#ifndef TRACKPAD_TAP_MOVE_THRESHOLD +# define TRACKPAD_TAP_MOVE_THRESHOLD 30 // Maximum movement during tap (in sensor units, ~1.5% of range) +#endif + #if defined(NAVIGATOR_TRACKPAD_PTP_MODE) // Build a finger's 6 bytes into the report buffer @@ -29,6 +51,138 @@ static void build_finger_bytes(uint8_t *buf, uint8_t contact_id, uint16_t x, uin buf[5] = (y >> 8) & 0xFF; // Y high byte } +// Fallback mouse state +static struct { + // Position tracking for relative movement + bool tracking; + uint16_t last_x; + uint16_t last_y; + // Tap detection + bool tap_pending; + uint32_t touch_start_time; + uint16_t touch_start_x; + uint16_t touch_start_y; + // Click state + bool click_active; + uint32_t click_release_time; + // Previous state for change detection + uint8_t prev_buttons; +} mouse_state = {0}; + +// Send fallback mouse report +static void send_mouse_report(int8_t dx, int8_t dy, uint8_t buttons) { + report_trackpad_mouse_t report = { + .report_id = TRACKPAD_MOUSE_REPORT_ID, + .buttons = buttons, + .x = dx, + .y = dy + }; + // Use the same endpoint as PTP - different report ID distinguishes it + send_trackpad((report_digitizer_t *)&report); +} + +// Clamp value to int8_t range +static inline int8_t clamp_to_int8(int32_t value) { + if (value > 127) return 127; + if (value < -127) return -127; + return (int8_t)value; +} + +// Minimum time to hold tap-click before releasing (ms) +#ifndef TRACKPAD_TAP_CLICK_HOLD_MS +# define TRACKPAD_TAP_CLICK_HOLD_MS 20 +#endif + +// Process fallback mouse movement and tap-to-click +static void process_fallback_mouse(cgen6_report_t *sensor_report, bool finger_down, bool prev_finger_down) { + uint32_t now = timer_read32(); + int8_t dx = 0; + int8_t dy = 0; + uint8_t buttons = 0; + + // Handle physical button (from sensor) + if (sensor_report->buttons & 0x01) { + buttons |= 0x01; + } + + // Handle finger down transition (start tracking) + if (finger_down && !prev_finger_down) { + mouse_state.tracking = true; + mouse_state.last_x = sensor_report->fingers[0].x; + mouse_state.last_y = sensor_report->fingers[0].y; + // Start tap detection + mouse_state.tap_pending = true; + mouse_state.touch_start_time = now; + mouse_state.touch_start_x = sensor_report->fingers[0].x; + mouse_state.touch_start_y = sensor_report->fingers[0].y; + } + + // Handle finger movement (compute delta) + if (finger_down && mouse_state.tracking) { + int32_t raw_dx = (int32_t)sensor_report->fingers[0].x - (int32_t)mouse_state.last_x; + int32_t raw_dy = (int32_t)sensor_report->fingers[0].y - (int32_t)mouse_state.last_y; + + // Check if movement exceeded tap threshold BEFORE applying sensitivity + // This uses distance from initial touch point + if (mouse_state.tap_pending) { + int32_t move_x = (int32_t)sensor_report->fingers[0].x - (int32_t)mouse_state.touch_start_x; + int32_t move_y = (int32_t)sensor_report->fingers[0].y - (int32_t)mouse_state.touch_start_y; + int32_t move_dist_sq = move_x * move_x + move_y * move_y; + if (move_dist_sq > TRACKPAD_TAP_MOVE_THRESHOLD * TRACKPAD_TAP_MOVE_THRESHOLD) { + mouse_state.tap_pending = false; + } + // Also invalidate if any single-frame movement is significant + if (raw_dx * raw_dx + raw_dy * raw_dy > 16) { // ~4 units of movement in one frame + mouse_state.tap_pending = false; + } + } + + // Apply sensitivity scaling + raw_dx = (int32_t)(raw_dx * TRACKPAD_MOUSE_SENSITIVITY); + raw_dy = (int32_t)(raw_dy * TRACKPAD_MOUSE_SENSITIVITY); + + dx = clamp_to_int8(raw_dx); + dy = clamp_to_int8(raw_dy); + + mouse_state.last_x = sensor_report->fingers[0].x; + mouse_state.last_y = sensor_report->fingers[0].y; + } + + // Handle finger up transition (end tracking, check for tap) + if (!finger_down && prev_finger_down) { + mouse_state.tracking = false; + + // Check if this was a tap + if (mouse_state.tap_pending) { + uint32_t touch_duration = timer_elapsed32(mouse_state.touch_start_time); + if (touch_duration <= TRACKPAD_TAP_TERM_MS) { + // Valid tap - generate click + mouse_state.click_active = true; + mouse_state.click_release_time = now; + } + } + mouse_state.tap_pending = false; + } + + // Handle tap-generated click (button press then release after hold time) + if (mouse_state.click_active) { + buttons |= 0x01; + // Release after holding for TRACKPAD_TAP_CLICK_HOLD_MS + if (timer_elapsed32(mouse_state.click_release_time) >= TRACKPAD_TAP_CLICK_HOLD_MS) { + mouse_state.click_active = false; + } + } + + // Only send report if there's actual movement or button state changed + bool buttons_changed = (buttons != mouse_state.prev_buttons); + bool has_movement = (dx != 0 || dy != 0); + + if (has_movement || buttons_changed) { + send_mouse_report(dx, dy, buttons); + mouse_state.prev_buttons = buttons; + } +} + // PTP task function - synchronous polling with timer-based throttling static bool navigator_trackpad_ptp_task(void) { static uint32_t last_poll_time = 0; @@ -109,9 +263,19 @@ static bool navigator_trackpad_ptp_task(void) { // Byte 15: Contact count (bits 0-3) + buttons (bits 4-6) report[15] = (contact_count & 0x0F) | ((buttons & 0x01) << 4); - // Send report if any contacts (including lift-offs) or button changed - if (contact_count > 0 || button_changed) { - send_trackpad((report_digitizer_t *)report); + // Get current input mode + uint8_t input_mode = get_trackpad_input_mode(); + + // Send PTP report only in PTP mode (mode 3) + if (input_mode == TRACKPAD_INPUT_MODE_PTP) { + if (contact_count > 0 || button_changed) { + send_trackpad((report_digitizer_t *)report); + } + } + + // Process fallback mouse only in mouse mode (mode 0) + if (input_mode == TRACKPAD_INPUT_MODE_MOUSE) { + process_fallback_mouse(&sensor_report, finger0_tip, prev_finger0_tip); } // Update previous state diff --git a/tmk_core/protocol/chibios/usb_main.c b/tmk_core/protocol/chibios/usb_main.c index 61488a283c..7594289247 100644 --- a/tmk_core/protocol/chibios/usb_main.c +++ b/tmk_core/protocol/chibios/usb_main.c @@ -58,6 +58,20 @@ extern keymap_config_t keymap_config; extern usb_endpoint_in_t usb_endpoints_in[USB_ENDPOINT_IN_COUNT]; extern usb_endpoint_out_t usb_endpoints_out[USB_ENDPOINT_OUT_COUNT]; +#ifdef PRECISION_TRACKPAD_ENABLE +// Trackpad input mode: 0 = Mouse, 3 = PTP (Precision Touchpad) +// Default to mouse mode for maximum compatibility +static uint8_t trackpad_input_mode = 0; + +uint8_t get_trackpad_input_mode(void) { + return trackpad_input_mode; +} + +void set_trackpad_input_mode(uint8_t mode) { + trackpad_input_mode = mode; +} +#endif + static bool __attribute__((__unused__)) send_report_buffered(usb_endpoint_in_lut_t endpoint, void *report, size_t size); static void __attribute__((__unused__)) flush_report_buffered(usb_endpoint_in_lut_t endpoint, bool padded); static bool __attribute__((__unused__)) receive_report(usb_endpoint_out_lut_t endpoint, void *report, size_t size); @@ -245,6 +259,19 @@ static void usb_event_cb(USBDriver *usbp, usbevent_t event) { static uint8_t _Alignas(4) set_report_buf[2]; +#ifdef PRECISION_TRACKPAD_ENABLE +static void set_trackpad_transfer_cb(USBDriver *usbp) { + usb_control_request_t *setup = (usb_control_request_t *)usbp->setup; + uint8_t report_id = setup->wValue.lbyte; + + // Report ID 0x04 is the Input Mode feature report + if (report_id == 0x04 && setup->wLength >= 2) { + // set_report_buf[0] = report_id, set_report_buf[1] = input mode + set_trackpad_input_mode(set_report_buf[1]); + } +} +#endif + static void set_led_transfer_cb(USBDriver *usbp) { usb_control_request_t *setup = (usb_control_request_t *)usbp->setup; @@ -315,8 +342,8 @@ static bool usb_requests_hook_cb(USBDriver *usbp) { #endif #ifdef PRECISION_TRACKPAD_ENABLE case TRACKPAD_INTERFACE: - // Accept SET_REPORT for PTP - Windows may send configuration - usbSetupTransfer(usbp, set_report_buf, sizeof(set_report_buf), NULL); + // Accept SET_REPORT for PTP - handle input mode switching + usbSetupTransfer(usbp, set_report_buf, sizeof(set_report_buf), set_trackpad_transfer_cb); return true; #endif } diff --git a/tmk_core/protocol/chibios/usb_report_handling.c b/tmk_core/protocol/chibios/usb_report_handling.c index f33feec4d2..def56cad9b 100644 --- a/tmk_core/protocol/chibios/usb_report_handling.c +++ b/tmk_core/protocol/chibios/usb_report_handling.c @@ -15,6 +15,10 @@ extern usb_endpoint_in_t usb_endpoints_in[USB_ENDPOINT_IN_COUNT]; extern usb_endpoint_in_lut_t usb_endpoint_interface_lut[TOTAL_INTERFACES]; +#ifdef PRECISION_TRACKPAD_ENABLE +extern uint8_t get_trackpad_input_mode(void); +#endif + void usb_set_report(usb_fs_report_t **reports, const uint8_t *data, size_t length) { if (*reports == NULL) { return; @@ -101,9 +105,9 @@ bool usb_get_report_cb(USBDriver *driver) { return true; case 0x04: // Configuration - Input Mode - // Report input mode (3 = multi-touch) + // Report current input mode (0 = mouse, 3 = PTP) feature_report[0] = 0x04; - feature_report[1] = 3; // Multi-touch mode + feature_report[1] = get_trackpad_input_mode(); usbSetupTransfer(driver, feature_report, 2, NULL); return true; diff --git a/tmk_core/protocol/report.h b/tmk_core/protocol/report.h index d5e4079d1e..2f00c06dd1 100644 --- a/tmk_core/protocol/report.h +++ b/tmk_core/protocol/report.h @@ -282,6 +282,19 @@ _Static_assert(sizeof(report_digitizer_t) == 16, "report_digitizer_t must be 16 // Legacy alias for compatibility typedef report_digitizer_t report_trackpad_t; +// Trackpad fallback mouse report - for systems that don't support PTP +// Report format: [report_id: 1 byte] [buttons: 1 byte] [x: 1 byte] [y: 1 byte] +#define TRACKPAD_MOUSE_REPORT_ID 0x06 + +typedef struct { + uint8_t report_id; + uint8_t buttons; + int8_t x; + int8_t y; +} PACKED report_trackpad_mouse_t; + +_Static_assert(sizeof(report_trackpad_mouse_t) == 4, "report_trackpad_mouse_t must be 4 bytes"); + #if JOYSTICK_AXIS_RESOLUTION > 8 typedef int16_t joystick_axis_t; #else diff --git a/tmk_core/protocol/usb_descriptor.c b/tmk_core/protocol/usb_descriptor.c index a0f3d9f72a..0f6c652e58 100644 --- a/tmk_core/protocol/usb_descriptor.c +++ b/tmk_core/protocol/usb_descriptor.c @@ -590,6 +590,39 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM PrecisionTrackpadReport[] = { HID_RI_POP(0), HID_RI_END_COLLECTION(0), HID_RI_END_COLLECTION(0), + + // Fallback Mouse Collection - for systems that don't support PTP + // Uses report ID 0x06 to avoid conflicts with PTP reports (0x01-0x05) + HID_RI_USAGE_PAGE(8, 0x01), // Generic Desktop + HID_RI_USAGE(8, 0x02), // Mouse + HID_RI_COLLECTION(8, 0x01), // Application + HID_RI_REPORT_ID(8, 0x06), + HID_RI_USAGE(8, 0x01), // Pointer + HID_RI_COLLECTION(8, 0x00), // Physical + // Buttons (3 bits) + HID_RI_USAGE_PAGE(8, 0x09), // Button + HID_RI_USAGE_MINIMUM(8, 0x01), // Button 1 + HID_RI_USAGE_MAXIMUM(8, 0x03), // Button 3 + HID_RI_LOGICAL_MINIMUM(8, 0x00), + HID_RI_LOGICAL_MAXIMUM(8, 0x01), + HID_RI_REPORT_COUNT(8, 0x03), + HID_RI_REPORT_SIZE(8, 0x01), + HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + // Padding (5 bits) + HID_RI_REPORT_COUNT(8, 0x01), + HID_RI_REPORT_SIZE(8, 0x05), + HID_RI_INPUT(8, HID_IOF_CONSTANT), + // X/Y position (2 bytes, relative) + HID_RI_USAGE_PAGE(8, 0x01), // Generic Desktop + HID_RI_USAGE(8, 0x30), // X + HID_RI_USAGE(8, 0x31), // Y + HID_RI_LOGICAL_MINIMUM(8, -127), + HID_RI_LOGICAL_MAXIMUM(8, 127), + HID_RI_REPORT_COUNT(8, 0x02), + HID_RI_REPORT_SIZE(8, 0x08), + HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE), + HID_RI_END_COLLECTION(0), + HID_RI_END_COLLECTION(0), }; #endif