mirror of
https://github.com/zsa/qmk_firmware.git
synced 2026-01-08 22:52:28 +00:00
feat(trackpad): mouse fallback when host doesn't support PTP
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user