mirror of
https://github.com/zsa/qmk_firmware.git
synced 2026-01-09 15:12:33 +00:00
feat(trackpad): first Windows Precision inplementation
This commit is contained in:
@@ -163,6 +163,11 @@ ifeq ($(strip $(POINTING_DEVICE_ENABLE)), yes)
|
||||
else ifeq ($(strip $(POINTING_DEVICE_DRIVER)), navigator_trackpad)
|
||||
I2C_DRIVER_REQUIRED = yes
|
||||
SRC += drivers/sensors/navigator.c
|
||||
SRC += drivers/sensors/navigator_trackpad_common.c
|
||||
SRC += drivers/sensors/navigator_trackpad.c
|
||||
SRC += drivers/sensors/navigator_trackpad_mouse.c
|
||||
# Define PTP mode for mouse mode (uses absolute coordinates internally)
|
||||
OPT_DEFS += -DNAVIGATOR_TRACKPAD_PTP_MODE
|
||||
else ifneq ($(filter $(strip $(POINTING_DEVICE_DRIVER)),pmw3360 pmw3389),)
|
||||
SPI_DRIVER_REQUIRED = yes
|
||||
SRC += drivers/sensors/pmw33xx_common.c
|
||||
@@ -171,9 +176,35 @@ ifeq ($(strip $(POINTING_DEVICE_ENABLE)), yes)
|
||||
endif
|
||||
|
||||
PRECISION_TRACKPAD_ENABLE ?= no
|
||||
|
||||
# Valid precision trackpad driver types
|
||||
VALID_PRECISION_TRACKPAD_DRIVER_TYPES := navigator_trackpad custom
|
||||
|
||||
ifeq ($(strip $(PRECISION_TRACKPAD_ENABLE)), yes)
|
||||
OPT_DEFS += -DPRECISION_TRACKPAD_ENABLE
|
||||
SRC += $(QUANTUM_DIR)/precision_trackpad.c
|
||||
# Validate driver type
|
||||
ifeq ($(filter $(PRECISION_TRACKPAD_DRIVER),$(VALID_PRECISION_TRACKPAD_DRIVER_TYPES)),)
|
||||
$(call CATASTROPHIC_ERROR,Invalid PRECISION_TRACKPAD_DRIVER,\
|
||||
PRECISION_TRACKPAD_DRIVER="$(PRECISION_TRACKPAD_DRIVER)" is not a valid PTP driver)
|
||||
else
|
||||
OPT_DEFS += -DPRECISION_TRACKPAD_ENABLE
|
||||
SRC += $(QUANTUM_DIR)/precision_trackpad.c
|
||||
|
||||
# Include driver source (unless custom)
|
||||
ifneq ($(strip $(PRECISION_TRACKPAD_DRIVER)), custom)
|
||||
# Add common code
|
||||
SRC += drivers/sensors/navigator_trackpad_common.c
|
||||
SRC += drivers/sensors/navigator_trackpad_ptp.c
|
||||
SRC += drivers/sensors/navigator.c
|
||||
I2C_DRIVER_REQUIRED = yes
|
||||
|
||||
# Define PTP mode for precision trackpad
|
||||
OPT_DEFS += -DNAVIGATOR_TRACKPAD_PTP_MODE
|
||||
|
||||
# Set driver name macro (used by PRECISION_TRACKPAD_DRIVER macro)
|
||||
OPT_DEFS += -DPRECISION_TRACKPAD_DRIVER_NAME=$(strip $(PRECISION_TRACKPAD_DRIVER))
|
||||
OPT_DEFS += -DPRECISION_TRACKPAD_DRIVER_$(strip $(shell echo $(PRECISION_TRACKPAD_DRIVER) | tr '[:lower:]' '[:upper:]'))
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
QUANTUM_PAINTER_ENABLE ?= no
|
||||
|
||||
@@ -1,888 +1,14 @@
|
||||
// Copyright 2025 ZSA Technology Labs, Inc <contact@zsa.io>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// THIS IS A WORK IN PROGRESS, AS THE TRACKPAD IC's FIRMWARE IS STILL IN DEVELOPMENT
|
||||
// DO NOT USE THIS CODE IN PRODUCTION
|
||||
// Dispatcher/compatibility layer for Navigator trackpad
|
||||
// Provides backward compatibility for existing code
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <math.h>
|
||||
#include "navigator_trackpad.h"
|
||||
#include "i2c_master.h"
|
||||
#include "quantum.h"
|
||||
#include "timer.h"
|
||||
#include "navigator_trackpad_common.h"
|
||||
|
||||
|
||||
#ifdef PROTOCOL_LUFA
|
||||
# error "LUFA is not supported yet"
|
||||
#endif
|
||||
|
||||
#ifdef POINTING_DEVICE_ENABLE
|
||||
const pointing_device_driver_t navigator_trackpad_pointing_device_driver = {.init = navigator_trackpad_device_init, .get_report = navigator_trackpad_get_report, .get_cpi = navigator_trackpad_get_cpi, .set_cpi = navigator_trackpad_set_cpi};
|
||||
#endif
|
||||
|
||||
deferred_token callback_token = 0;
|
||||
uint16_t current_cpi = DEFAULT_CPI_TICK;
|
||||
uint8_t has_motion = 0;
|
||||
extern bool set_scrolling;
|
||||
bool trackpad_init;
|
||||
|
||||
#ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING
|
||||
float macos_scroll_accumulated_h = 0;
|
||||
float macos_scroll_accumulated_v = 0;
|
||||
#endif
|
||||
|
||||
#if defined(NAVIGATOR_TRACKPAD_PTP_MODE)
|
||||
cgen6_report_t ptp_report;
|
||||
trackpad_gesture_t gesture = {0};
|
||||
# ifdef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE
|
||||
scroll_inertia_t scroll_inertia = {0};
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Helper functions to parse ptp reports
|
||||
#if defined(NAVIGATOR_TRACKPAD_PTP_MODE)
|
||||
uint8_t finger_count(cgen6_report_t *report) {
|
||||
uint8_t fingers = 0;
|
||||
if (report->fingers[0].tip) {
|
||||
fingers++;
|
||||
}
|
||||
if (report->fingers[1].tip) {
|
||||
fingers++;
|
||||
}
|
||||
|
||||
return fingers;
|
||||
}
|
||||
#endif
|
||||
|
||||
i2c_status_t cirque_gen6_read_report(uint8_t *data, uint16_t cnt) {
|
||||
i2c_status_t res = i2c_receive(NAVIGATOR_TRACKPAD_ADDRESS, data, cnt, NAVIGATOR_TRACKPAD_TIMEOUT);
|
||||
if (res != I2C_STATUS_SUCCESS) {
|
||||
return res;
|
||||
}
|
||||
wait_us(cnt * 15);
|
||||
return res;
|
||||
}
|
||||
|
||||
void cirque_gen6_clear(void) {
|
||||
uint8_t buf[CGEN6_MAX_PACKET_SIZE];
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
wait_ms(1);
|
||||
if (cirque_gen6_read_report(buf, CGEN6_MAX_PACKET_SIZE) != I2C_STATUS_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t cirque_gen6_read_memory(uint32_t addr, uint8_t *data, uint16_t cnt, bool fast_read) {
|
||||
uint8_t cksum = 0;
|
||||
uint8_t res = CGEN6_SUCCESS;
|
||||
uint8_t len[2];
|
||||
uint16_t read = 0;
|
||||
|
||||
uint8_t preamble[8] = {0x01, 0x09, (uint8_t)(addr & (uint32_t)0x000000FF), (uint8_t)((addr & 0x0000FF00) >> 8), (uint8_t)((addr & 0x00FF0000) >> 16), (uint8_t)((addr & 0xFF000000) >> 24), (uint8_t)(cnt & 0x00FF), (uint8_t)((cnt & 0xFF00) >> 8)};
|
||||
|
||||
// Read the length of the data + 3 bytes (first 2 bytes for the length and the last byte for the checksum)
|
||||
// Create a buffer to store the data
|
||||
uint8_t buf[cnt + 3];
|
||||
if (i2c_transmit_and_receive(NAVIGATOR_TRACKPAD_ADDRESS, preamble, 8, buf, cnt + 3, NAVIGATOR_TRACKPAD_TIMEOUT) != I2C_STATUS_SUCCESS) {
|
||||
res |= CGEN6_I2C_FAILED;
|
||||
trackpad_init = false;
|
||||
}
|
||||
|
||||
// Read the data length
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
cksum += len[i] = buf[i];
|
||||
read++;
|
||||
}
|
||||
|
||||
// Populate the data buffer
|
||||
for (uint16_t i = 2; i < cnt + 2; i++) {
|
||||
cksum += data[i - 2] = buf[i];
|
||||
read++;
|
||||
}
|
||||
|
||||
if (!fast_read) {
|
||||
// Check the checksum
|
||||
if (cksum != buf[read]) {
|
||||
res |= CGEN6_CKSUM_FAILED;
|
||||
}
|
||||
|
||||
// Check the length (incremented first to account for the checksum)
|
||||
if (++read != (len[0] | (len[1] << 8))) {
|
||||
res |= CGEN6_LEN_MISMATCH;
|
||||
}
|
||||
|
||||
wait_ms(1);
|
||||
} else {
|
||||
wait_us(250);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
uint8_t cirque_gen6_write_memory(uint32_t addr, uint8_t *data, uint16_t cnt) {
|
||||
uint8_t res = CGEN6_SUCCESS;
|
||||
uint8_t cksum = 0, i = 0;
|
||||
uint8_t preamble[8] = {0x00, 0x09, (uint8_t)(addr & 0x000000FF), (uint8_t)((addr & 0x0000FF00) >> 8), (uint8_t)((addr & 0x00FF0000) >> 16), (uint8_t)((addr & 0xFF000000) >> 24), (uint8_t)(cnt & 0x00FF), (uint8_t)((cnt & 0xFF00) >> 8)};
|
||||
|
||||
uint8_t buf[cnt + 9];
|
||||
// Calculate the checksum
|
||||
for (; i < 8; i++) {
|
||||
cksum += buf[i] = preamble[i];
|
||||
}
|
||||
|
||||
for (i = 0; i < cnt; i++) {
|
||||
cksum += buf[i + 8] = data[i];
|
||||
}
|
||||
|
||||
buf[cnt + 8] = cksum;
|
||||
|
||||
if (i2c_transmit(NAVIGATOR_TRACKPAD_ADDRESS, buf, cnt + 9, NAVIGATOR_TRACKPAD_TIMEOUT) != I2C_STATUS_SUCCESS) {
|
||||
res |= CGEN6_I2C_FAILED;
|
||||
trackpad_init = false;
|
||||
}
|
||||
|
||||
wait_ms(1);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
uint8_t cirque_gen6_read_reg(uint32_t addr, bool fast_read) {
|
||||
uint8_t data;
|
||||
uint8_t res = cirque_gen6_read_memory(addr, &data, 1, fast_read);
|
||||
if (res != CGEN6_SUCCESS) {
|
||||
printf("Failed to read 8bits from register at address 0x%08X with error 0x%02X\n", (u_int)addr, res);
|
||||
return 0;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
uint16_t cirque_gen6_read_reg_16(uint32_t addr) {
|
||||
uint8_t buf[2];
|
||||
uint8_t res = cirque_gen6_read_memory(addr, buf, 2, false);
|
||||
if (res != CGEN6_SUCCESS) {
|
||||
printf("Failed to read 16bits from register at address 0x%08X with error 0x%02X\n", (u_int)addr, res);
|
||||
return 0;
|
||||
}
|
||||
return (buf[1] << 8) | buf[0];
|
||||
}
|
||||
|
||||
uint32_t cirque_gen6_read_reg_32(uint32_t addr) {
|
||||
uint8_t buf[4];
|
||||
uint8_t res = cirque_gen6_read_memory(addr, buf, 4, false);
|
||||
if (res != CGEN6_SUCCESS) {
|
||||
printf("Failed to read 32bits from register at address 0x%08X with error 0x%02X\n", (u_int)addr, res);
|
||||
return 0;
|
||||
}
|
||||
return (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
|
||||
}
|
||||
|
||||
uint8_t cirque_gen6_write_reg(uint32_t addr, uint8_t data) {
|
||||
return cirque_gen6_write_memory(addr, &data, 1);
|
||||
}
|
||||
|
||||
uint8_t cirque_gen6_write_reg_16(uint32_t addr, uint16_t data) {
|
||||
uint8_t buf[2] = {data & 0xFF, (data >> 8) & 0xFF};
|
||||
return cirque_gen6_write_memory(addr, buf, 2);
|
||||
}
|
||||
|
||||
uint8_t cirque_gen6_write_reg_32(uint32_t addr, uint32_t data) {
|
||||
uint8_t buf[4] = {data & 0xFF, (data >> 8) & 0xFF, (data >> 16) & 0xFF, (data >> 24) & 0xFF};
|
||||
return cirque_gen6_write_memory(addr, buf, 4);
|
||||
}
|
||||
|
||||
uint8_t cirque_gen6_set_relative_mode(void) {
|
||||
uint8_t feed_config4 = cirque_gen6_read_reg(CGEN6_FEED_CONFIG4, false);
|
||||
feed_config4 &= 0xF3;
|
||||
return cirque_gen6_write_reg(CGEN6_FEED_CONFIG4, feed_config4);
|
||||
}
|
||||
|
||||
uint8_t cirque_gen6_set_ptp_mode(void) {
|
||||
uint8_t feed_config4 = cirque_gen6_read_reg(CGEN6_FEED_CONFIG4, false);
|
||||
feed_config4 &= 0xF7;
|
||||
feed_config4 |= 0x04;
|
||||
return cirque_gen6_write_reg(CGEN6_FEED_CONFIG4, feed_config4);
|
||||
}
|
||||
|
||||
uint8_t cirque_gen6_swap_xy(bool set) {
|
||||
uint8_t xy_config = cirque_gen6_read_reg(CGEN6_XY_CONFIG, false);
|
||||
if (set) {
|
||||
xy_config |= 0x04;
|
||||
} else {
|
||||
xy_config &= ~0x04;
|
||||
}
|
||||
return cirque_gen6_write_reg(CGEN6_XY_CONFIG, xy_config);
|
||||
}
|
||||
|
||||
uint8_t cirque_gen6_invert_y(bool set) {
|
||||
uint8_t xy_config = cirque_gen6_read_reg(CGEN6_XY_CONFIG, false);
|
||||
if (set) {
|
||||
xy_config |= 0x02;
|
||||
} else {
|
||||
xy_config &= ~0x02;
|
||||
}
|
||||
return cirque_gen6_write_reg(CGEN6_XY_CONFIG, xy_config);
|
||||
}
|
||||
|
||||
uint8_t cirque_gen6_invert_x(bool set) {
|
||||
uint8_t xy_config = cirque_gen6_read_reg(CGEN6_XY_CONFIG, false);
|
||||
if (set) {
|
||||
xy_config |= 0x01;
|
||||
} else {
|
||||
xy_config &= ~0x01;
|
||||
}
|
||||
return cirque_gen6_write_reg(CGEN6_XY_CONFIG, xy_config);
|
||||
}
|
||||
|
||||
uint8_t cirque_gen6_enable_logical_scaling(bool set) {
|
||||
uint8_t xy_config = cirque_gen6_read_reg(CGEN6_XY_CONFIG, false);
|
||||
if (set) {
|
||||
xy_config &= ~0x08;
|
||||
} else {
|
||||
xy_config |= 0x08;
|
||||
}
|
||||
return cirque_gen6_write_reg(CGEN6_XY_CONFIG, xy_config);
|
||||
}
|
||||
|
||||
void cirque_gen_6_read_report(void) {
|
||||
uint8_t packet[CGEN6_MAX_PACKET_SIZE];
|
||||
if (cirque_gen6_read_report(packet, CGEN6_MAX_PACKET_SIZE) != I2C_STATUS_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t report_id = packet[2];
|
||||
|
||||
#ifdef CONSOLE_ENABLE
|
||||
static uint16_t report_id_counter = 0;
|
||||
if (++report_id_counter % 100 == 0) {
|
||||
printf("Cirque report ID: 0x%02x (expecting 0x01 for PTP)\n", report_id);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(NAVIGATOR_TRACKPAD_PTP_MODE)
|
||||
if (report_id == CGEN6_PTP_REPORT_ID) {
|
||||
ptp_report.fingers[0].tip = (packet[3] & 0x02) >> 1;
|
||||
ptp_report.fingers[0].x = packet[5] << 8 | packet[4];
|
||||
ptp_report.fingers[0].y = packet[7] << 8 | packet[6];
|
||||
ptp_report.fingers[1].tip = (packet[8] & 0x02) >> 1;
|
||||
ptp_report.fingers[1].x = packet[10] << 8 | packet[9];
|
||||
ptp_report.fingers[1].y = packet[12] << 8 | packet[11];
|
||||
ptp_report.buttons = packet[16];
|
||||
}
|
||||
#endif
|
||||
#if defined(NAVIGATOR_TRACKPAD_RELATIVE_MODE)
|
||||
if (report_id == CGEN6_MOUSE_REPORT_ID) {
|
||||
ptp_report.buttons = packet[3];
|
||||
ptp_report.xDelta = packet[4];
|
||||
ptp_report.yDelta = packet[5];
|
||||
ptp_report.scrollDelta = packet[6];
|
||||
ptp_report.panDelta = packet[7];
|
||||
|
||||
has_motion = 1;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Check if the DR pin is asserted, if it is there is motion data to sample.
|
||||
uint8_t cirque_gen6_has_motion(void) {
|
||||
return cirque_gen6_read_reg(CGEN6_I2C_DR, true);
|
||||
}
|
||||
|
||||
uint32_t cirque_gen6_read_callback(uint32_t trigger_time, void *cb_arg) {
|
||||
if (!trackpad_init) {
|
||||
navigator_trackpad_device_init();
|
||||
return NAVIGATOR_TRACKPAD_PROBE;
|
||||
}
|
||||
if (cirque_gen6_has_motion()) {
|
||||
has_motion = 1;
|
||||
cirque_gen_6_read_report();
|
||||
}
|
||||
return NAVIGATOR_TRACKPAD_READ;
|
||||
}
|
||||
|
||||
void navigator_trackpad_device_init(void) {
|
||||
i2c_init();
|
||||
i2c_status_t status = i2c_ping_address(NAVIGATOR_TRACKPAD_ADDRESS, NAVIGATOR_TRACKPAD_TIMEOUT);
|
||||
if (status != I2C_STATUS_SUCCESS) {
|
||||
trackpad_init = false;
|
||||
return;
|
||||
}
|
||||
cirque_gen6_clear();
|
||||
wait_ms(50);
|
||||
|
||||
uint8_t res = CGEN6_SUCCESS;
|
||||
#if defined(NAVIGATOR_TRACKPAD_PTP_MODE)
|
||||
res = cirque_gen6_set_ptp_mode();
|
||||
#elif defined(NAVIGATOR_TRACKPAD_RELATIVE_MODE)
|
||||
res = cirque_gen6_set_relative_mode();
|
||||
#endif
|
||||
|
||||
if (res != CGEN6_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset to the default alignment
|
||||
cirque_gen6_swap_xy(false);
|
||||
cirque_gen6_invert_x(false);
|
||||
cirque_gen6_invert_y(false);
|
||||
cirque_gen6_swap_xy(true);
|
||||
cirque_gen6_invert_x(true);
|
||||
cirque_gen6_invert_y(true);
|
||||
cirque_gen6_enable_logical_scaling(true);
|
||||
|
||||
trackpad_init = true;
|
||||
// Only register the callback for the first time
|
||||
if (!callback_token) {
|
||||
callback_token = defer_exec(NAVIGATOR_TRACKPAD_READ, cirque_gen6_read_callback, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef POINTING_DEVICE_ENABLE
|
||||
report_mouse_t navigator_trackpad_get_report(report_mouse_t mouse_report) {
|
||||
#ifdef PRECISION_TRACKPAD_ENABLE
|
||||
// When PTP is enabled, don't send mouse reports - PTP task handles everything
|
||||
return mouse_report;
|
||||
#include "navigator_trackpad_ptp.h"
|
||||
#else
|
||||
// Mouse mode - process gestures and send mouse reports
|
||||
#if defined(NAVIGATOR_TRACKPAD_PTP_MODE)
|
||||
// Handle pending click release from previous cycle
|
||||
if (gesture.pending_click) {
|
||||
gesture.pending_click = false;
|
||||
mouse_report.buttons = 0;
|
||||
return mouse_report;
|
||||
}
|
||||
|
||||
# ifdef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE
|
||||
// Process scroll inertia when active
|
||||
if (scroll_inertia.active && timer_elapsed(scroll_inertia.timer) >= NAVIGATOR_TRACKPAD_SCROLL_INERTIA_INTERVAL) {
|
||||
scroll_inertia.timer = timer_read();
|
||||
|
||||
// Apply friction to velocity (Q8 fixed point math)
|
||||
// Friction reduces velocity towards zero
|
||||
int16_t friction_x = (scroll_inertia.vx * NAVIGATOR_TRACKPAD_SCROLL_INERTIA_FRICTION) / 256;
|
||||
int16_t friction_y = (scroll_inertia.vy * NAVIGATOR_TRACKPAD_SCROLL_INERTIA_FRICTION) / 256;
|
||||
|
||||
// Ensure we always reduce by at least 1 if not zero
|
||||
if (scroll_inertia.vx > 0 && friction_x < 1) friction_x = 1;
|
||||
if (scroll_inertia.vx < 0 && friction_x > -1) friction_x = -1;
|
||||
if (scroll_inertia.vy > 0 && friction_y < 1) friction_y = 1;
|
||||
if (scroll_inertia.vy < 0 && friction_y > -1) friction_y = -1;
|
||||
|
||||
scroll_inertia.vx -= friction_x;
|
||||
scroll_inertia.vy -= friction_y;
|
||||
|
||||
// Convert Q8 velocity to scroll value
|
||||
# ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING
|
||||
// macOS mode: send raw velocity deltas (descriptor tells macOS the resolution)
|
||||
int16_t scroll_x = scroll_inertia.vx / 256;
|
||||
int16_t scroll_y = scroll_inertia.vy / 256;
|
||||
# else
|
||||
// Hi-res mode: apply multiplier for Windows/Linux
|
||||
int16_t scroll_x = (scroll_inertia.vx * NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER) / 256;
|
||||
int16_t scroll_y = (scroll_inertia.vy * NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER) / 256;
|
||||
# endif
|
||||
|
||||
// Clamp to int8_t range
|
||||
scroll_x = (scroll_x > 127) ? 127 : ((scroll_x < -127) ? -127 : scroll_x);
|
||||
scroll_y = (scroll_y > 127) ? 127 : ((scroll_y < -127) ? -127 : scroll_y);
|
||||
|
||||
// Check if velocity is too low to continue
|
||||
int16_t abs_vx = scroll_inertia.vx < 0 ? -scroll_inertia.vx : scroll_inertia.vx;
|
||||
int16_t abs_vy = scroll_inertia.vy < 0 ? -scroll_inertia.vy : scroll_inertia.vy;
|
||||
|
||||
# ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING
|
||||
// In macOS mode, track consecutive frames with no output to detect janky tail
|
||||
if (scroll_x == 0 && scroll_y == 0) {
|
||||
scroll_inertia.no_output_count++;
|
||||
} else {
|
||||
scroll_inertia.no_output_count = 0;
|
||||
}
|
||||
|
||||
// Stop if: velocity very low OR too many consecutive frames without output
|
||||
bool velocity_too_low = (abs_vx < 64 && abs_vy < 64); // Same as hi-res mode
|
||||
bool stalled = (scroll_inertia.no_output_count > 5); // 5 frames (~35ms) with no output
|
||||
if (velocity_too_low || stalled) {
|
||||
scroll_inertia.active = false;
|
||||
} else
|
||||
# else
|
||||
if (abs_vx < 64 && abs_vy < 64) { // Threshold in Q8 (0.25 in real units)
|
||||
scroll_inertia.active = false;
|
||||
} else
|
||||
# endif
|
||||
{
|
||||
// Apply scroll inversion if configured
|
||||
# ifdef NAVIGATOR_SCROLL_INVERT_X
|
||||
mouse_report.h = -scroll_x;
|
||||
# else
|
||||
mouse_report.h = scroll_x;
|
||||
# endif
|
||||
# ifdef NAVIGATOR_SCROLL_INVERT_Y
|
||||
mouse_report.v = scroll_y;
|
||||
# else
|
||||
mouse_report.v = -scroll_y;
|
||||
# endif
|
||||
return mouse_report;
|
||||
}
|
||||
}
|
||||
# endif
|
||||
#endif // End scroll inertia block
|
||||
|
||||
#if defined(NAVIGATOR_TRACKPAD_RELATIVE_MODE)
|
||||
if (!has_motion || !trackpad_init) {
|
||||
return mouse_report;
|
||||
}
|
||||
|
||||
mouse_report.x = ptp_report.xDelta;
|
||||
mouse_report.y = ptp_report.yDelta;
|
||||
mouse_report.v = ptp_report.scrollDelta;
|
||||
mouse_report.h = ptp_report.panDelta;
|
||||
mouse_report.buttons = ptp_report.buttons;
|
||||
#elif defined(NAVIGATOR_TRACKPAD_PTP_MODE)
|
||||
if (!has_motion || !trackpad_init) {
|
||||
return mouse_report;
|
||||
}
|
||||
// Create local snapshot to avoid race condition with callback updating ptp_report
|
||||
cgen6_report_t local_report = ptp_report;
|
||||
|
||||
uint8_t raw_fingers = finger_count(&local_report);
|
||||
bool is_touching = local_report.fingers[0].tip;
|
||||
bool was_idle = (gesture.state == TP_IDLE);
|
||||
uint8_t fingers = raw_fingers;
|
||||
|
||||
// Handle finger down - record start position (regardless of current state)
|
||||
if (is_touching && was_idle) {
|
||||
gesture.touch_start_time = timer_read();
|
||||
gesture.prev_x = local_report.fingers[0].x;
|
||||
gesture.prev_y = local_report.fingers[0].y;
|
||||
gesture.settled_x = 0; // Will be set after settle time
|
||||
gesture.settled_y = 0;
|
||||
gesture.settled = false;
|
||||
gesture.max_finger_count = fingers;
|
||||
gesture.state = TP_MOVING;
|
||||
# ifdef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE
|
||||
// Stop any ongoing scroll inertia when finger touches
|
||||
scroll_inertia.active = false;
|
||||
scroll_inertia.smooth_vx = 0;
|
||||
scroll_inertia.smooth_vy = 0;
|
||||
# ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING
|
||||
scroll_inertia.no_output_count = 0;
|
||||
# endif
|
||||
# endif
|
||||
# ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING
|
||||
// Reset macOS scroll accumulator when starting new gesture
|
||||
macos_scroll_accumulated_h = 0;
|
||||
macos_scroll_accumulated_v = 0;
|
||||
# endif
|
||||
}
|
||||
|
||||
// Handle finger up - evaluate tap at lift time (libinput style)
|
||||
if (!is_touching && !was_idle) {
|
||||
uint16_t duration = timer_elapsed(gesture.touch_start_time);
|
||||
|
||||
// Calculate distance from settled position (or treat as no movement if never settled)
|
||||
int32_t dist_sq = 0;
|
||||
if (gesture.settled) {
|
||||
int16_t dx = gesture.prev_x - gesture.settled_x;
|
||||
int16_t dy = gesture.prev_y - gesture.settled_y;
|
||||
dist_sq = (int32_t)dx * dx + (int32_t)dy * dy;
|
||||
}
|
||||
|
||||
// Check tap conditions: short duration AND small movement from settled position
|
||||
bool is_tap = (duration <= NAVIGATOR_TRACKPAD_TAP_TIMEOUT) &&
|
||||
(dist_sq <= NAVIGATOR_TRACKPAD_TAP_MOVE_THRESHOLD);
|
||||
|
||||
// Don't trigger single-finger taps that happen shortly after a scroll ends (within 100ms)
|
||||
// But allow two-finger taps (right-click) even after scrolling
|
||||
# ifdef NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS
|
||||
if (is_tap && gesture.max_finger_count == 1 &&
|
||||
timer_elapsed(gesture.last_scroll_end) < 100) {
|
||||
is_tap = false; // Suppress single-finger tap after scrolling
|
||||
}
|
||||
# endif
|
||||
|
||||
if (is_tap) {
|
||||
# ifdef NAVIGATOR_TRACKPAD_ENABLE_DOUBLE_TAP
|
||||
if (gesture.max_finger_count >= 2) {
|
||||
mouse_report.x = 0;
|
||||
mouse_report.y = 0;
|
||||
mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, true, POINTING_DEVICE_BUTTON2);
|
||||
gesture.pending_click = true;
|
||||
} else
|
||||
# endif
|
||||
# ifdef NAVIGATOR_TRACKPAD_ENABLE_TAP
|
||||
if (gesture.max_finger_count == 1) {
|
||||
mouse_report.x = 0;
|
||||
mouse_report.y = 0;
|
||||
mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, true, POINTING_DEVICE_BUTTON1);
|
||||
gesture.pending_click = true;
|
||||
}
|
||||
# endif
|
||||
}
|
||||
|
||||
# if defined(NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE) && defined(NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS)
|
||||
// Start scroll inertia if we were scrolling and have enough velocity
|
||||
if (gesture.state == TP_SCROLLING) {
|
||||
// Use smoothed velocity (already in Q8 format)
|
||||
int16_t abs_vx = scroll_inertia.smooth_vx < 0 ? -scroll_inertia.smooth_vx : scroll_inertia.smooth_vx;
|
||||
int16_t abs_vy = scroll_inertia.smooth_vy < 0 ? -scroll_inertia.smooth_vy : scroll_inertia.smooth_vy;
|
||||
// Trigger threshold is now in Q8 (multiply by 256)
|
||||
if (abs_vx >= (NAVIGATOR_TRACKPAD_SCROLL_INERTIA_TRIGGER * 256) ||
|
||||
abs_vy >= (NAVIGATOR_TRACKPAD_SCROLL_INERTIA_TRIGGER * 256)) {
|
||||
scroll_inertia.vx = scroll_inertia.smooth_vx;
|
||||
scroll_inertia.vy = scroll_inertia.smooth_vy;
|
||||
scroll_inertia.timer = timer_read();
|
||||
scroll_inertia.active = true;
|
||||
}
|
||||
}
|
||||
# endif
|
||||
|
||||
// Record if this was a scroll gesture ending
|
||||
# ifdef NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS
|
||||
if (gesture.state == TP_SCROLLING || gesture.max_finger_count >= 2) {
|
||||
gesture.last_scroll_end = timer_read();
|
||||
}
|
||||
# endif
|
||||
|
||||
gesture.state = TP_IDLE;
|
||||
set_scrolling = false;
|
||||
}
|
||||
|
||||
// Handle ongoing touch - movement and scrolling
|
||||
if (is_touching && !was_idle) {
|
||||
// Track max fingers during gesture
|
||||
if (fingers > gesture.max_finger_count) {
|
||||
gesture.max_finger_count = fingers;
|
||||
}
|
||||
|
||||
# ifdef NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS
|
||||
// Determine mode based on finger count
|
||||
// Once scrolling starts, keep scrolling until all fingers lift (no mid-gesture transitions)
|
||||
if (fingers >= 2 && gesture.state != TP_SCROLLING) {
|
||||
gesture.state = TP_SCROLLING;
|
||||
// Reset position tracking - use finger[0] initially
|
||||
gesture.prev_x = local_report.fingers[0].x;
|
||||
gesture.prev_y = local_report.fingers[0].y;
|
||||
}
|
||||
// Note: We don't transition from SCROLLING back to MOVING mid-gesture anymore
|
||||
// Once scroll starts, it continues until all fingers lift (gesture ends)
|
||||
# endif
|
||||
|
||||
uint16_t duration = timer_elapsed(gesture.touch_start_time);
|
||||
|
||||
// Record settled position once settle time elapses
|
||||
if (!gesture.settled && duration >= NAVIGATOR_TRACKPAD_TAP_SETTLE_TIME) {
|
||||
gesture.settled = true;
|
||||
gesture.settled_x = local_report.fingers[0].x;
|
||||
gesture.settled_y = local_report.fingers[0].y;
|
||||
}
|
||||
|
||||
// Check if we should suppress movement (might still be a tap)
|
||||
int32_t dist_sq = 0;
|
||||
if (gesture.settled) {
|
||||
int16_t dx = local_report.fingers[0].x - gesture.settled_x;
|
||||
int16_t dy = local_report.fingers[0].y - gesture.settled_y;
|
||||
dist_sq = (int32_t)dx * dx + (int32_t)dy * dy;
|
||||
}
|
||||
|
||||
// Only report movement if: timeout exceeded OR moved beyond tap threshold (after settling)
|
||||
bool should_move = (duration > NAVIGATOR_TRACKPAD_TAP_TIMEOUT) ||
|
||||
(gesture.settled && dist_sq > NAVIGATOR_TRACKPAD_TAP_MOVE_THRESHOLD);
|
||||
|
||||
if (should_move) {
|
||||
int16_t delta_x = local_report.fingers[0].x - gesture.prev_x;
|
||||
int16_t delta_y = local_report.fingers[0].y - gesture.prev_y;
|
||||
|
||||
// Clamp deltas to prevent jumps from bad data
|
||||
if (delta_x > NAVIGATOR_TRACKPAD_MAX_DELTA) delta_x = NAVIGATOR_TRACKPAD_MAX_DELTA;
|
||||
if (delta_x < -NAVIGATOR_TRACKPAD_MAX_DELTA) delta_x = -NAVIGATOR_TRACKPAD_MAX_DELTA;
|
||||
if (delta_y > NAVIGATOR_TRACKPAD_MAX_DELTA) delta_y = NAVIGATOR_TRACKPAD_MAX_DELTA;
|
||||
if (delta_y < -NAVIGATOR_TRACKPAD_MAX_DELTA) delta_y = -NAVIGATOR_TRACKPAD_MAX_DELTA;
|
||||
|
||||
if (delta_x != 0 || delta_y != 0) {
|
||||
# ifdef NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS
|
||||
if (gesture.state == TP_SCROLLING) {
|
||||
# ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING
|
||||
// macOS mode: send raw deltas, macOS handles scaling via HIDScrollResolution
|
||||
// Apple trackpads report raw sensor deltas and let macOS apply acceleration
|
||||
int16_t scroll_x = delta_x;
|
||||
int16_t scroll_y = delta_y;
|
||||
# else
|
||||
// Hi-res mode: apply multiplier for Windows/Linux
|
||||
// These OSes divide by the Resolution Multiplier (120)
|
||||
int16_t scroll_x = delta_x * NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER;
|
||||
int16_t scroll_y = delta_y * NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER;
|
||||
# endif
|
||||
|
||||
# ifdef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE
|
||||
// Track velocity for inertia using exponential smoothing (Q8 fixed point)
|
||||
// Alpha = 0.3 (77/256) - balances responsiveness with smoothness
|
||||
// When direction changes, reset smoothing to avoid fighting old momentum
|
||||
int16_t new_vx = delta_x * 256;
|
||||
int16_t new_vy = delta_y * 256;
|
||||
|
||||
// Detect direction change (signs differ and both non-zero)
|
||||
bool dir_change_x = (scroll_inertia.smooth_vx > 0 && new_vx < 0) ||
|
||||
(scroll_inertia.smooth_vx < 0 && new_vx > 0);
|
||||
bool dir_change_y = (scroll_inertia.smooth_vy > 0 && new_vy < 0) ||
|
||||
(scroll_inertia.smooth_vy < 0 && new_vy > 0);
|
||||
|
||||
if (dir_change_x) {
|
||||
// Direction changed - blend toward zero first, then pick up new direction
|
||||
scroll_inertia.smooth_vx = (scroll_inertia.smooth_vx * 128 + new_vx * 128) / 256;
|
||||
} else {
|
||||
// Same direction - normal EMA smoothing
|
||||
scroll_inertia.smooth_vx = (scroll_inertia.smooth_vx * 179 + new_vx * 77) / 256;
|
||||
}
|
||||
|
||||
if (dir_change_y) {
|
||||
scroll_inertia.smooth_vy = (scroll_inertia.smooth_vy * 128 + new_vy * 128) / 256;
|
||||
} else {
|
||||
scroll_inertia.smooth_vy = (scroll_inertia.smooth_vy * 179 + new_vy * 77) / 256;
|
||||
}
|
||||
# endif
|
||||
|
||||
// Clamp to int8_t range for the report
|
||||
scroll_x = (scroll_x > 127) ? 127 : ((scroll_x < -127) ? -127 : scroll_x);
|
||||
scroll_y = (scroll_y > 127) ? 127 : ((scroll_y < -127) ? -127 : scroll_y);
|
||||
|
||||
// Apply scroll inversion if configured
|
||||
# ifdef NAVIGATOR_SCROLL_INVERT_X
|
||||
mouse_report.h = -scroll_x;
|
||||
# else
|
||||
mouse_report.h = scroll_x;
|
||||
# endif
|
||||
# ifdef NAVIGATOR_SCROLL_INVERT_Y
|
||||
mouse_report.v = scroll_y;
|
||||
# else
|
||||
mouse_report.v = -scroll_y;
|
||||
# endif
|
||||
mouse_report.x = 0;
|
||||
mouse_report.y = 0;
|
||||
} else
|
||||
# endif
|
||||
{
|
||||
// One-finger movement: mouse cursor
|
||||
mouse_report.x = (delta_x < 0) ? -powf(-delta_x, 1.2) : powf(delta_x, 1.2);
|
||||
mouse_report.y = (delta_y < 0) ? -powf(-delta_y, 1.2) : powf(delta_y, 1.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update prev position for next frame
|
||||
gesture.prev_x = local_report.fingers[0].x;
|
||||
gesture.prev_y = local_report.fingers[0].y;
|
||||
}
|
||||
#endif
|
||||
|
||||
has_motion = 0;
|
||||
return mouse_report;
|
||||
#endif // PRECISION_TRACKPAD_ENABLE
|
||||
}
|
||||
#endif // POINTING_DEVICE_ENABLE
|
||||
|
||||
void set_cirque_cpi(void) {
|
||||
// traverse the sequence by compairing the cpi_x value with the current cpi_x value
|
||||
// set the cpi to the next value in the sequence
|
||||
switch (current_cpi) {
|
||||
case CPI_1: {
|
||||
current_cpi = CPI_2;
|
||||
break;
|
||||
}
|
||||
case CPI_2: {
|
||||
current_cpi = CPI_3;
|
||||
break;
|
||||
}
|
||||
case CPI_3: {
|
||||
current_cpi = CPI_4;
|
||||
break;
|
||||
}
|
||||
case CPI_4: {
|
||||
current_cpi = CPI_5;
|
||||
break;
|
||||
}
|
||||
case CPI_5: {
|
||||
current_cpi = CPI_6;
|
||||
break;
|
||||
}
|
||||
case CPI_6: {
|
||||
current_cpi = CPI_7;
|
||||
break;
|
||||
}
|
||||
case CPI_7: {
|
||||
current_cpi = CPI_1;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
current_cpi = CPI_4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t navigator_trackpad_get_cpi(void) {
|
||||
return current_cpi;
|
||||
}
|
||||
|
||||
void restore_cpi(uint8_t cpi) {
|
||||
current_cpi = cpi;
|
||||
set_cirque_cpi();
|
||||
}
|
||||
|
||||
void navigator_trackpad_set_cpi(uint16_t cpi) {
|
||||
if (cpi == 0) { // Decrease one tick
|
||||
if (current_cpi > 1) {
|
||||
current_cpi--;
|
||||
}
|
||||
} else {
|
||||
if (current_cpi < CPI_TICKS) {
|
||||
current_cpi++;
|
||||
}
|
||||
}
|
||||
set_cirque_cpi();
|
||||
};
|
||||
|
||||
#ifdef PRECISION_TRACKPAD_ENABLE
|
||||
// External declaration for PTP report sending
|
||||
extern void send_trackpad(report_trackpad_t *report);
|
||||
|
||||
// PTP task function - converts trackpad touches to PTP reports
|
||||
void navigator_trackpad_task(void) {
|
||||
#if defined(NAVIGATOR_TRACKPAD_PTP_MODE)
|
||||
if (!trackpad_init) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Always check for data, even if has_motion isn't set
|
||||
// Windows needs continuous updates for gestures
|
||||
if (!has_motion && !cirque_gen6_has_motion()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read fresh data if available
|
||||
if (cirque_gen6_has_motion()) {
|
||||
cirque_gen_6_read_report();
|
||||
}
|
||||
|
||||
// Create local snapshot to avoid race condition with callback
|
||||
cgen6_report_t local_report = ptp_report;
|
||||
|
||||
uint8_t raw_fingers = finger_count(&local_report);
|
||||
|
||||
#ifdef CONSOLE_ENABLE
|
||||
// Track min/max coordinates to find actual sensor range
|
||||
static uint16_t max_x = 0, max_y = 0;
|
||||
static uint16_t min_x = 0xFFFF, min_y = 0xFFFF;
|
||||
static uint16_t debug_counter = 0;
|
||||
|
||||
if (local_report.fingers[0].tip) {
|
||||
if (local_report.fingers[0].x > max_x) max_x = local_report.fingers[0].x;
|
||||
if (local_report.fingers[0].y > max_y) max_y = local_report.fingers[0].y;
|
||||
if (local_report.fingers[0].x < min_x && local_report.fingers[0].x > 0) min_x = local_report.fingers[0].x;
|
||||
if (local_report.fingers[0].y < min_y && local_report.fingers[0].y > 0) min_y = local_report.fingers[0].y;
|
||||
}
|
||||
if (local_report.fingers[1].tip) {
|
||||
if (local_report.fingers[1].x > max_x) max_x = local_report.fingers[1].x;
|
||||
if (local_report.fingers[1].y > max_y) max_y = local_report.fingers[1].y;
|
||||
if (local_report.fingers[1].x < min_x && local_report.fingers[1].x > 0) min_x = local_report.fingers[1].x;
|
||||
if (local_report.fingers[1].y < min_y && local_report.fingers[1].y > 0) min_y = local_report.fingers[1].y;
|
||||
}
|
||||
|
||||
if (++debug_counter % 100 == 0) { // Print every 100 reports
|
||||
printf("PTP: fingers=%d, range: x=%d-%d, y=%d-%d, current: f0(%d,%d) f1(%d,%d)\n",
|
||||
raw_fingers, min_x, max_x, min_y, max_y,
|
||||
local_report.fingers[0].x, local_report.fingers[0].y,
|
||||
local_report.fingers[1].x, local_report.fingers[1].y);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Initialize PTP report
|
||||
report_trackpad_t ptp = {0};
|
||||
ptp.report_id = REPORT_ID_TRACKPAD;
|
||||
|
||||
// Map Cirque coordinates (0-4095 in PTP mode) to PTP logical range (0-4095)
|
||||
// Physical dimensions: 4 inches (0x190) x 2.75 inches (0x113)
|
||||
// The Cirque sensor already provides 12-bit resolution
|
||||
|
||||
if (raw_fingers >= 1 && local_report.fingers[0].tip) {
|
||||
ptp.contacts[0].confidence = 1; // Assume intentional touch
|
||||
ptp.contacts[0].tip = 1;
|
||||
ptp.contacts[0].contact_id = 0;
|
||||
ptp.contacts[0].x = local_report.fingers[0].x; // Already 12-bit
|
||||
ptp.contacts[0].y = local_report.fingers[0].y;
|
||||
}
|
||||
|
||||
if (raw_fingers >= 2 && local_report.fingers[1].tip) {
|
||||
ptp.contacts[1].confidence = 1;
|
||||
ptp.contacts[1].tip = 1;
|
||||
ptp.contacts[1].contact_id = 1;
|
||||
ptp.contacts[1].x = local_report.fingers[1].x;
|
||||
ptp.contacts[1].y = local_report.fingers[1].y;
|
||||
}
|
||||
|
||||
// Set contact count
|
||||
ptp.contact_count = raw_fingers;
|
||||
|
||||
#ifdef CONSOLE_ENABLE
|
||||
if (raw_fingers > 2) {
|
||||
printf("ERROR: raw_fingers=%d (should be 0-2!)\n", raw_fingers);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Set scan time (in 100μs units)
|
||||
// Use timer_read() which returns milliseconds, convert to 100μs units
|
||||
ptp.scan_time = (timer_read() * 10) & 0xFFFF;
|
||||
|
||||
// Set button states (physical buttons from Cirque)
|
||||
ptp.button1 = (local_report.buttons & 0x01) ? 1 : 0;
|
||||
ptp.button2 = (local_report.buttons & 0x02) ? 1 : 0;
|
||||
ptp.button3 = (local_report.buttons & 0x04) ? 1 : 0;
|
||||
|
||||
// Send the PTP report
|
||||
#ifdef CONSOLE_ENABLE
|
||||
// Log raw bytes being sent
|
||||
static uint16_t byte_log_counter = 0;
|
||||
if (++byte_log_counter % 50 == 0 && raw_fingers > 0) {
|
||||
uint8_t *bytes = (uint8_t *)&ptp;
|
||||
printf("Sending bytes: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
|
||||
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
|
||||
bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14]);
|
||||
}
|
||||
#endif
|
||||
send_trackpad(&ptp);
|
||||
|
||||
#ifdef CONSOLE_ENABLE
|
||||
// Detailed PTP report logging
|
||||
static uint16_t report_counter = 0;
|
||||
if (++report_counter % 50 == 0 || raw_fingers > 0) { // Log every 50 reports or when touching
|
||||
printf("PTP Report: count=%d, scan=%d, btn=%d%d%d\n",
|
||||
ptp.contact_count, ptp.scan_time, ptp.button1, ptp.button2, ptp.button3);
|
||||
if (ptp.contacts[0].tip) {
|
||||
printf(" C0: id=%d, conf=%d, tip=%d, x=%d, y=%d\n",
|
||||
ptp.contacts[0].contact_id, ptp.contacts[0].confidence,
|
||||
ptp.contacts[0].tip, ptp.contacts[0].x, ptp.contacts[0].y);
|
||||
}
|
||||
if (ptp.contacts[1].tip) {
|
||||
printf(" C1: id=%d, conf=%d, tip=%d, x=%d, y=%d\n",
|
||||
ptp.contacts[1].contact_id, ptp.contacts[1].confidence,
|
||||
ptp.contacts[1].tip, ptp.contacts[1].x, ptp.contacts[1].y);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Also send a report with no contacts when fingers lift
|
||||
// This is critical for Windows to detect gesture end
|
||||
static uint8_t prev_contact_count = 0;
|
||||
if (raw_fingers == 0 && prev_contact_count > 0) {
|
||||
// Send empty report to signal fingers lifted
|
||||
report_trackpad_t empty_ptp = {0};
|
||||
empty_ptp.report_id = REPORT_ID_TRACKPAD;
|
||||
empty_ptp.scan_time = (timer_read() * 10) & 0xFFFF;
|
||||
send_trackpad(&empty_ptp);
|
||||
}
|
||||
prev_contact_count = raw_fingers;
|
||||
|
||||
has_motion = 0;
|
||||
#endif
|
||||
}
|
||||
#include "navigator_trackpad_mouse.h"
|
||||
#endif
|
||||
|
||||
@@ -2,211 +2,14 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "report.h"
|
||||
|
||||
#ifdef POINTING_DEVICE_ENABLE
|
||||
# include "pointing_device.h"
|
||||
#endif
|
||||
// Dispatcher/compatibility layer for Navigator trackpad
|
||||
// Includes the appropriate implementation based on build configuration
|
||||
|
||||
#define NAVIGATOR_TRACKPAD_READ 7
|
||||
#define NAVIGATOR_TRACKPAD_PROBE 1000
|
||||
|
||||
#ifndef NAVIGATOR_TRACKPAD_TAP_MOVE_THRESHOLD
|
||||
# define NAVIGATOR_TRACKPAD_TAP_MOVE_THRESHOLD 100 // Max movement (squared) before tap becomes a drag
|
||||
#endif
|
||||
|
||||
#ifndef NAVIGATOR_TRACKPAD_TAP_TIMEOUT
|
||||
# define NAVIGATOR_TRACKPAD_TAP_TIMEOUT 200 // Max duration (ms) for a tap
|
||||
#endif
|
||||
|
||||
#ifndef NAVIGATOR_TRACKPAD_TAP_SETTLE_TIME
|
||||
# define NAVIGATOR_TRACKPAD_TAP_SETTLE_TIME 30 // Ignore movement during initial contact (ms)
|
||||
#endif
|
||||
|
||||
#ifndef NAVIGATOR_TRACKPAD_MAX_DELTA
|
||||
# define NAVIGATOR_TRACKPAD_MAX_DELTA 250 // Max allowed delta per frame to prevent jumps
|
||||
#endif
|
||||
|
||||
#ifndef NAVIGATOR_TRACKPAD_ADDRESS
|
||||
# define NAVIGATOR_TRACKPAD_ADDRESS 0x58
|
||||
#endif
|
||||
|
||||
#ifndef NAVIGATOR_TRACKPAD_TIMEOUT
|
||||
# define NAVIGATOR_TRACKPAD_TIMEOUT 100
|
||||
#endif
|
||||
|
||||
#define NAVIGATOR_TRACKPAD_PTP_MODE
|
||||
#if !defined(NAVIGATOR_TRACKPAD_RELATIVE_MODE) && !defined(NAVIGATOR_TRACKPAD_PTP_MODE)
|
||||
# define NAVIGATOR_TRACKPAD_PTP_MODE
|
||||
#endif
|
||||
|
||||
#define CGEN6_MAX_PACKET_SIZE 17
|
||||
#define CGEN6_PTP_REPORT_ID 0x01
|
||||
#define CGEN6_MOUSE_REPORT_ID 0x06
|
||||
#define CGEN6_ABSOLUTE_REPORT_ID 0x09
|
||||
|
||||
// C3 error codes when reading memory
|
||||
#define CGEN6_SUCCESS 0x00
|
||||
#define CGEN6_CKSUM_FAILED 0x01
|
||||
#define CGEN6_LEN_MISMATCH 0x02
|
||||
#define CGEN6_I2C_FAILED 0x03
|
||||
|
||||
// C3 register addresses
|
||||
#define CGEN6_REG_BASE 0x20000800
|
||||
#define CGEN6_HARDWARE_ID CGEN6_REG_BASE + 0x08
|
||||
#define CGEN6_FIRMWARE_ID CGEN6_REG_BASE + 0x09
|
||||
#define CGEN6_FIRMWARE_REV CGEN6_REG_BASE + 0x10
|
||||
#define CGEN6_VENDOR_ID CGEN6_REG_BASE + 0x0A
|
||||
#define CGEN6_PRODUCT_ID CGEN6_REG_BASE + 0x0C
|
||||
#define CGEN6_VERSION_ID CGEN6_REG_BASE + 0x0E
|
||||
#define CGEN6_FEED_CONFIG4 0x200E000B
|
||||
#define CGEN6_FEED_CONFIG3 0x200E000A
|
||||
#define CGEN6_SYS_CONFIG1 0x20000008
|
||||
#define CGEN6_XY_CONFIG 0x20080018
|
||||
#define CGEN6_SFR_BASE 0x40000008
|
||||
#define CGEN6_GPIO_BASE 0x00052000
|
||||
#define CGEN6_I2C_DR 0x61010000
|
||||
|
||||
#define CPI_TICKS 7
|
||||
#define DEFAULT_CPI_TICK 4
|
||||
#define CPI_1 200
|
||||
#define CPI_2 400
|
||||
#define CPI_3 800
|
||||
#define CPI_4 1024
|
||||
#define CPI_5 1400
|
||||
#define CPI_6 1800
|
||||
#define CPI_7 2048
|
||||
|
||||
#ifndef NAVIGATOR_TRACKPAD_SCROLL_DIVIDER
|
||||
# define NAVIGATOR_TRACKPAD_SCROLL_DIVIDER 10
|
||||
#endif
|
||||
|
||||
#ifndef NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER
|
||||
# define NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER 3
|
||||
#endif
|
||||
|
||||
// Two-finger scrolling (define to enable)
|
||||
// #define NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS
|
||||
|
||||
// macOS scrolling mode (define to enable)
|
||||
// macOS doesn't respect the HID Resolution Multiplier descriptor.
|
||||
// When enabled, sends raw scroll deltas without the multiplier (like Apple trackpads).
|
||||
// macOS applies its own HIDScrollResolution (400 DPI) and acceleration curves.
|
||||
// When disabled (default), applies multiplier for Windows/Linux hi-res scrolling.
|
||||
// #define NAVIGATOR_TRACKPAD_MACOS_SCROLLING
|
||||
|
||||
// Scroll inversion configuration
|
||||
// Define these to invert scroll direction on respective axes
|
||||
// #define NAVIGATOR_SCROLL_INVERT_X
|
||||
// #define NAVIGATOR_SCROLL_INVERT_Y
|
||||
|
||||
// Scroll inertia configuration
|
||||
/*
|
||||
To enable, add to your config.h:
|
||||
#define NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE
|
||||
|
||||
Configurable values (all optional):
|
||||
- NAVIGATOR_TRACKPAD_SCROLL_INERTIA_FRICTION - Higher = stops faster (default: 50, range 1-255)
|
||||
- NAVIGATOR_TRACKPAD_SCROLL_INERTIA_INTERVAL - Time between glide reports in ms (default: 15)
|
||||
- NAVIGATOR_TRACKPAD_SCROLL_INERTIA_TRIGGER - Minimum velocity to trigger glide (default: 3)
|
||||
*/
|
||||
#ifdef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE
|
||||
#ifndef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_FRICTION
|
||||
#define NAVIGATOR_TRACKPAD_SCROLL_INERTIA_FRICTION 5 // Higher = stops faster (1-255)
|
||||
#endif
|
||||
#ifndef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_INTERVAL
|
||||
#define NAVIGATOR_TRACKPAD_SCROLL_INERTIA_INTERVAL 7 // Glide report interval in ms
|
||||
#endif
|
||||
#ifndef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_TRIGGER
|
||||
#define NAVIGATOR_TRACKPAD_SCROLL_INERTIA_TRIGGER 1 // Min velocity to trigger glide
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
int16_t vx; // Current X velocity (Q8 fixed point)
|
||||
int16_t vy; // Current Y velocity (Q8 fixed point)
|
||||
int16_t smooth_vx; // Smoothed X velocity (Q8 fixed point)
|
||||
int16_t smooth_vy; // Smoothed Y velocity (Q8 fixed point)
|
||||
uint16_t timer; // Timer for interval tracking
|
||||
bool active; // Is glide currently active
|
||||
#ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING
|
||||
uint8_t no_output_count; // Counter for consecutive frames with no output
|
||||
#endif
|
||||
} scroll_inertia_t;
|
||||
#endif
|
||||
|
||||
#if defined(NAVIGATOR_TRACKPAD_PTP_MODE)
|
||||
# ifndef MOUSE_EXTENDED_REPORT
|
||||
# define MOUSE_EXTENDED_REPORT
|
||||
# endif
|
||||
typedef struct {
|
||||
uint8_t tip;
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
} cgen6_finger_t;
|
||||
|
||||
typedef struct {
|
||||
cgen6_finger_t fingers[2];
|
||||
uint8_t buttons;
|
||||
} cgen6_report_t;
|
||||
|
||||
// Trackpad gesture state machine
|
||||
typedef enum {
|
||||
TP_IDLE, // No fingers touching
|
||||
TP_MOVING, // One finger movement = mouse cursor
|
||||
TP_SCROLLING, // Two finger movement = scroll
|
||||
} trackpad_state_t;
|
||||
|
||||
typedef struct {
|
||||
trackpad_state_t state;
|
||||
uint16_t touch_start_time; // When finger first touched
|
||||
uint16_t settled_x; // Position after settle time (for tap threshold)
|
||||
uint16_t settled_y;
|
||||
uint16_t prev_x; // Previous position (for delta calculation)
|
||||
uint16_t prev_y;
|
||||
uint8_t max_finger_count; // Max fingers seen during this gesture
|
||||
bool settled; // Has the settle time elapsed?
|
||||
bool pending_click; // Need to send a click release next cycle
|
||||
uint16_t last_scroll_end; // Time when last scroll gesture ended
|
||||
} trackpad_gesture_t;
|
||||
#endif
|
||||
|
||||
#if defined(NAVIGATOR_TRACKPAD_ABSOLUTE_MODE)
|
||||
typedef struct {
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
uint8_t palm;
|
||||
uint8_t z;
|
||||
} finger_data_t;
|
||||
|
||||
typedef struct {
|
||||
finger_data_t fingers[3]; // Cirque support 5 fingers, we only need 3 for our application
|
||||
uint8_t contact_flags;
|
||||
uint8_t buttons;
|
||||
} cgen6_report_t;
|
||||
#endif
|
||||
|
||||
#if defined(NAVIGATOR_TRACKPAD_RELATIVE_MODE)
|
||||
typedef struct {
|
||||
uint8_t buttons;
|
||||
int8_t xDelta;
|
||||
int8_t yDelta;
|
||||
int8_t scrollDelta;
|
||||
int8_t panDelta;
|
||||
} cgen6_report_t;
|
||||
#endif
|
||||
|
||||
#ifdef POINTING_DEVICE_ENABLE
|
||||
const pointing_device_driver_t navigator_trackpad_pointing_device_driver;
|
||||
report_mouse_t navigator_trackpad_get_report(report_mouse_t mouse_report);
|
||||
#endif
|
||||
|
||||
void navigator_trackpad_device_init(void);
|
||||
uint16_t navigator_trackpad_get_cpi(void);
|
||||
void navigator_trackpad_set_cpi(uint16_t cpi);
|
||||
void restore_cpi(uint8_t cpi);
|
||||
#include "navigator_trackpad_common.h"
|
||||
|
||||
#ifdef PRECISION_TRACKPAD_ENABLE
|
||||
void navigator_trackpad_task(void);
|
||||
#include "navigator_trackpad_ptp.h"
|
||||
#else
|
||||
#include "navigator_trackpad_mouse.h"
|
||||
#endif
|
||||
|
||||
373
drivers/sensors/navigator_trackpad_common.c
Normal file
373
drivers/sensors/navigator_trackpad_common.c
Normal file
@@ -0,0 +1,373 @@
|
||||
// Copyright 2025 ZSA Technology Labs, Inc <contact@zsa.io>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Common hardware layer for Navigator trackpad
|
||||
// Shared by both mouse and PTP implementations
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include "navigator_trackpad_common.h"
|
||||
#include "i2c_master.h"
|
||||
#include "quantum.h"
|
||||
#include "timer.h"
|
||||
|
||||
// Shared globals
|
||||
deferred_token callback_token = 0;
|
||||
uint16_t current_cpi = DEFAULT_CPI_TICK;
|
||||
uint8_t has_motion = 0;
|
||||
bool trackpad_init = false;
|
||||
cgen6_report_t ptp_report;
|
||||
|
||||
// I2C communication functions
|
||||
i2c_status_t cirque_gen6_read_report(uint8_t *data, uint16_t cnt) {
|
||||
i2c_status_t res = i2c_receive(NAVIGATOR_TRACKPAD_ADDRESS, data, cnt, NAVIGATOR_TRACKPAD_TIMEOUT);
|
||||
if (res != I2C_STATUS_SUCCESS) {
|
||||
return res;
|
||||
}
|
||||
wait_us(cnt * 15);
|
||||
return res;
|
||||
}
|
||||
|
||||
void cirque_gen6_clear(void) {
|
||||
uint8_t buf[CGEN6_MAX_PACKET_SIZE];
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
wait_ms(1);
|
||||
if (cirque_gen6_read_report(buf, CGEN6_MAX_PACKET_SIZE) != I2C_STATUS_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t cirque_gen6_read_memory(uint32_t addr, uint8_t *data, uint16_t cnt, bool fast_read) {
|
||||
uint8_t cksum = 0;
|
||||
uint8_t res = CGEN6_SUCCESS;
|
||||
uint8_t len[2];
|
||||
uint16_t read = 0;
|
||||
|
||||
uint8_t preamble[8] = {0x01, 0x09, (uint8_t)(addr & (uint32_t)0x000000FF), (uint8_t)((addr & 0x0000FF00) >> 8), (uint8_t)((addr & 0x00FF0000) >> 16), (uint8_t)((addr & 0xFF000000) >> 24), (uint8_t)(cnt & 0x00FF), (uint8_t)((cnt & 0xFF00) >> 8)};
|
||||
|
||||
// Read the length of the data + 3 bytes (first 2 bytes for the length and the last byte for the checksum)
|
||||
// Create a buffer to store the data
|
||||
uint8_t buf[cnt + 3];
|
||||
if (i2c_transmit_and_receive(NAVIGATOR_TRACKPAD_ADDRESS, preamble, 8, buf, cnt + 3, NAVIGATOR_TRACKPAD_TIMEOUT) != I2C_STATUS_SUCCESS) {
|
||||
res |= CGEN6_I2C_FAILED;
|
||||
trackpad_init = false;
|
||||
}
|
||||
|
||||
// Read the data length
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
cksum += len[i] = buf[i];
|
||||
read++;
|
||||
}
|
||||
|
||||
// Populate the data buffer
|
||||
for (uint16_t i = 2; i < cnt + 2; i++) {
|
||||
cksum += data[i - 2] = buf[i];
|
||||
read++;
|
||||
}
|
||||
|
||||
if (!fast_read) {
|
||||
// Check the checksum
|
||||
if (cksum != buf[read]) {
|
||||
res |= CGEN6_CKSUM_FAILED;
|
||||
}
|
||||
|
||||
// Check the length (incremented first to account for the checksum)
|
||||
if (++read != (len[0] | (len[1] << 8))) {
|
||||
res |= CGEN6_LEN_MISMATCH;
|
||||
}
|
||||
|
||||
wait_ms(1);
|
||||
} else {
|
||||
wait_us(250);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
uint8_t cirque_gen6_write_memory(uint32_t addr, uint8_t *data, uint16_t cnt) {
|
||||
uint8_t res = CGEN6_SUCCESS;
|
||||
uint8_t cksum = 0, i = 0;
|
||||
uint8_t preamble[8] = {0x00, 0x09, (uint8_t)(addr & 0x000000FF), (uint8_t)((addr & 0x0000FF00) >> 8), (uint8_t)((addr & 0x00FF0000) >> 16), (uint8_t)((addr & 0xFF000000) >> 24), (uint8_t)(cnt & 0x00FF), (uint8_t)((cnt & 0xFF00) >> 8)};
|
||||
|
||||
uint8_t buf[cnt + 9];
|
||||
// Calculate the checksum
|
||||
for (; i < 8; i++) {
|
||||
cksum += buf[i] = preamble[i];
|
||||
}
|
||||
|
||||
for (i = 0; i < cnt; i++) {
|
||||
cksum += buf[i + 8] = data[i];
|
||||
}
|
||||
|
||||
buf[cnt + 8] = cksum;
|
||||
|
||||
if (i2c_transmit(NAVIGATOR_TRACKPAD_ADDRESS, buf, cnt + 9, NAVIGATOR_TRACKPAD_TIMEOUT) != I2C_STATUS_SUCCESS) {
|
||||
res |= CGEN6_I2C_FAILED;
|
||||
trackpad_init = false;
|
||||
}
|
||||
|
||||
wait_ms(1);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// Register access functions
|
||||
uint8_t cirque_gen6_read_reg(uint32_t addr, bool fast_read) {
|
||||
uint8_t data;
|
||||
uint8_t res = cirque_gen6_read_memory(addr, &data, 1, fast_read);
|
||||
if (res != CGEN6_SUCCESS) {
|
||||
printf("Failed to read 8bits from register at address 0x%08X with error 0x%02X\n", (u_int)addr, res);
|
||||
return 0;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
uint16_t cirque_gen6_read_reg_16(uint32_t addr) {
|
||||
uint8_t buf[2];
|
||||
uint8_t res = cirque_gen6_read_memory(addr, buf, 2, false);
|
||||
if (res != CGEN6_SUCCESS) {
|
||||
printf("Failed to read 16bits from register at address 0x%08X with error 0x%02X\n", (u_int)addr, res);
|
||||
return 0;
|
||||
}
|
||||
return (buf[1] << 8) | buf[0];
|
||||
}
|
||||
|
||||
uint32_t cirque_gen6_read_reg_32(uint32_t addr) {
|
||||
uint8_t buf[4];
|
||||
uint8_t res = cirque_gen6_read_memory(addr, buf, 4, false);
|
||||
if (res != CGEN6_SUCCESS) {
|
||||
printf("Failed to read 32bits from register at address 0x%08X with error 0x%02X\n", (u_int)addr, res);
|
||||
return 0;
|
||||
}
|
||||
return (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
|
||||
}
|
||||
|
||||
uint8_t cirque_gen6_write_reg(uint32_t addr, uint8_t data) {
|
||||
return cirque_gen6_write_memory(addr, &data, 1);
|
||||
}
|
||||
|
||||
uint8_t cirque_gen6_write_reg_16(uint32_t addr, uint16_t data) {
|
||||
uint8_t buf[2] = {data & 0xFF, (data >> 8) & 0xFF};
|
||||
return cirque_gen6_write_memory(addr, buf, 2);
|
||||
}
|
||||
|
||||
uint8_t cirque_gen6_write_reg_32(uint32_t addr, uint32_t data) {
|
||||
uint8_t buf[4] = {data & 0xFF, (data >> 8) & 0xFF, (data >> 16) & 0xFF, (data >> 24) & 0xFF};
|
||||
return cirque_gen6_write_memory(addr, buf, 4);
|
||||
}
|
||||
|
||||
// Configuration functions
|
||||
uint8_t cirque_gen6_set_relative_mode(void) {
|
||||
uint8_t feed_config4 = cirque_gen6_read_reg(CGEN6_FEED_CONFIG4, false);
|
||||
feed_config4 &= 0xF3;
|
||||
return cirque_gen6_write_reg(CGEN6_FEED_CONFIG4, feed_config4);
|
||||
}
|
||||
|
||||
uint8_t cirque_gen6_set_ptp_mode(void) {
|
||||
uint8_t feed_config4 = cirque_gen6_read_reg(CGEN6_FEED_CONFIG4, false);
|
||||
feed_config4 &= 0xF7;
|
||||
feed_config4 |= 0x04;
|
||||
return cirque_gen6_write_reg(CGEN6_FEED_CONFIG4, feed_config4);
|
||||
}
|
||||
|
||||
uint8_t cirque_gen6_swap_xy(bool set) {
|
||||
uint8_t xy_config = cirque_gen6_read_reg(CGEN6_XY_CONFIG, false);
|
||||
if (set) {
|
||||
xy_config |= 0x04;
|
||||
} else {
|
||||
xy_config &= ~0x04;
|
||||
}
|
||||
return cirque_gen6_write_reg(CGEN6_XY_CONFIG, xy_config);
|
||||
}
|
||||
|
||||
uint8_t cirque_gen6_invert_y(bool set) {
|
||||
uint8_t xy_config = cirque_gen6_read_reg(CGEN6_XY_CONFIG, false);
|
||||
if (set) {
|
||||
xy_config |= 0x02;
|
||||
} else {
|
||||
xy_config &= ~0x02;
|
||||
}
|
||||
return cirque_gen6_write_reg(CGEN6_XY_CONFIG, xy_config);
|
||||
}
|
||||
|
||||
uint8_t cirque_gen6_invert_x(bool set) {
|
||||
uint8_t xy_config = cirque_gen6_read_reg(CGEN6_XY_CONFIG, false);
|
||||
if (set) {
|
||||
xy_config |= 0x01;
|
||||
} else {
|
||||
xy_config &= ~0x01;
|
||||
}
|
||||
return cirque_gen6_write_reg(CGEN6_XY_CONFIG, xy_config);
|
||||
}
|
||||
|
||||
uint8_t cirque_gen6_enable_logical_scaling(bool set) {
|
||||
uint8_t xy_config = cirque_gen6_read_reg(CGEN6_XY_CONFIG, false);
|
||||
if (set) {
|
||||
xy_config &= ~0x08;
|
||||
} else {
|
||||
xy_config |= 0x08;
|
||||
}
|
||||
return cirque_gen6_write_reg(CGEN6_XY_CONFIG, xy_config);
|
||||
}
|
||||
|
||||
// Motion detection
|
||||
uint8_t cirque_gen6_has_motion(void) {
|
||||
return cirque_gen6_read_reg(CGEN6_I2C_DR, true);
|
||||
}
|
||||
|
||||
// Report reading - fills ptp_report global
|
||||
void cirque_gen_6_read_report(void) {
|
||||
uint8_t packet[CGEN6_MAX_PACKET_SIZE];
|
||||
if (cirque_gen6_read_report(packet, CGEN6_MAX_PACKET_SIZE) != I2C_STATUS_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t report_id = packet[2];
|
||||
|
||||
// PTP mode report
|
||||
if (report_id == CGEN6_PTP_REPORT_ID) {
|
||||
ptp_report.fingers[0].tip = (packet[3] & 0x02) >> 1;
|
||||
ptp_report.fingers[0].x = packet[5] << 8 | packet[4];
|
||||
ptp_report.fingers[0].y = packet[7] << 8 | packet[6];
|
||||
ptp_report.fingers[1].tip = (packet[8] & 0x02) >> 1;
|
||||
ptp_report.fingers[1].x = packet[10] << 8 | packet[9];
|
||||
ptp_report.fingers[1].y = packet[12] << 8 | packet[11];
|
||||
ptp_report.scan_time = packet[14] << 8 | packet[13];
|
||||
ptp_report.buttons = packet[16];
|
||||
}
|
||||
// Mouse/relative mode report
|
||||
else if (report_id == CGEN6_MOUSE_REPORT_ID) {
|
||||
ptp_report.buttons = packet[3];
|
||||
ptp_report.xDelta = packet[4];
|
||||
ptp_report.yDelta = packet[5];
|
||||
ptp_report.scrollDelta = packet[6];
|
||||
ptp_report.panDelta = packet[7];
|
||||
has_motion = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Deferred callback for polling
|
||||
uint32_t cirque_gen6_read_callback(uint32_t trigger_time, void *cb_arg) {
|
||||
if (!trackpad_init) {
|
||||
navigator_trackpad_device_init();
|
||||
return NAVIGATOR_TRACKPAD_PROBE;
|
||||
}
|
||||
if (cirque_gen6_has_motion()) {
|
||||
has_motion = 1;
|
||||
cirque_gen_6_read_report();
|
||||
}
|
||||
return NAVIGATOR_TRACKPAD_READ;
|
||||
}
|
||||
|
||||
// Device initialization
|
||||
void navigator_trackpad_device_init(void) {
|
||||
i2c_init();
|
||||
i2c_status_t status = i2c_ping_address(NAVIGATOR_TRACKPAD_ADDRESS, NAVIGATOR_TRACKPAD_TIMEOUT);
|
||||
if (status != I2C_STATUS_SUCCESS) {
|
||||
trackpad_init = false;
|
||||
return;
|
||||
}
|
||||
cirque_gen6_clear();
|
||||
wait_ms(50);
|
||||
|
||||
uint8_t res = CGEN6_SUCCESS;
|
||||
#if defined(NAVIGATOR_TRACKPAD_PTP_MODE)
|
||||
res = cirque_gen6_set_ptp_mode();
|
||||
#elif defined(NAVIGATOR_TRACKPAD_RELATIVE_MODE)
|
||||
res = cirque_gen6_set_relative_mode();
|
||||
#endif
|
||||
|
||||
if (res != CGEN6_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset to the default alignment
|
||||
cirque_gen6_swap_xy(false);
|
||||
cirque_gen6_invert_x(false);
|
||||
cirque_gen6_invert_y(false);
|
||||
cirque_gen6_swap_xy(true);
|
||||
cirque_gen6_invert_x(true);
|
||||
cirque_gen6_invert_y(true);
|
||||
cirque_gen6_enable_logical_scaling(true);
|
||||
|
||||
trackpad_init = true;
|
||||
// Only register the callback for the first time
|
||||
if (!callback_token) {
|
||||
callback_token = defer_exec(NAVIGATOR_TRACKPAD_READ, cirque_gen6_read_callback, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// CPI management
|
||||
void set_cirque_cpi(void) {
|
||||
// traverse the sequence by comparing the cpi_x value with the current cpi_x value
|
||||
// set the cpi to the next value in the sequence
|
||||
switch (current_cpi) {
|
||||
case CPI_1: {
|
||||
current_cpi = CPI_2;
|
||||
break;
|
||||
}
|
||||
case CPI_2: {
|
||||
current_cpi = CPI_3;
|
||||
break;
|
||||
}
|
||||
case CPI_3: {
|
||||
current_cpi = CPI_4;
|
||||
break;
|
||||
}
|
||||
case CPI_4: {
|
||||
current_cpi = CPI_5;
|
||||
break;
|
||||
}
|
||||
case CPI_5: {
|
||||
current_cpi = CPI_6;
|
||||
break;
|
||||
}
|
||||
case CPI_6: {
|
||||
current_cpi = CPI_7;
|
||||
break;
|
||||
}
|
||||
case CPI_7: {
|
||||
current_cpi = CPI_1;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
current_cpi = CPI_4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t navigator_trackpad_get_cpi(void) {
|
||||
return current_cpi;
|
||||
}
|
||||
|
||||
void restore_cpi(uint8_t cpi) {
|
||||
current_cpi = cpi;
|
||||
set_cirque_cpi();
|
||||
}
|
||||
|
||||
void navigator_trackpad_set_cpi(uint16_t cpi) {
|
||||
if (cpi == 0) { // Decrease one tick
|
||||
if (current_cpi > 1) {
|
||||
current_cpi--;
|
||||
}
|
||||
} else {
|
||||
if (current_cpi < CPI_TICKS) {
|
||||
current_cpi++;
|
||||
}
|
||||
}
|
||||
set_cirque_cpi();
|
||||
}
|
||||
|
||||
// Helper function to count active fingers
|
||||
uint8_t cirque_gen6_finger_count(cgen6_report_t *report) {
|
||||
uint8_t fingers = 0;
|
||||
if (report->fingers[0].tip) {
|
||||
fingers++;
|
||||
}
|
||||
if (report->fingers[1].tip) {
|
||||
fingers++;
|
||||
}
|
||||
return fingers;
|
||||
}
|
||||
139
drivers/sensors/navigator_trackpad_common.h
Normal file
139
drivers/sensors/navigator_trackpad_common.h
Normal file
@@ -0,0 +1,139 @@
|
||||
// Copyright 2025 ZSA Technology Labs, Inc <contact@zsa.io>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "i2c_master.h"
|
||||
#include "deferred_exec.h"
|
||||
|
||||
// I2C configuration
|
||||
#define NAVIGATOR_TRACKPAD_READ 7
|
||||
#define NAVIGATOR_TRACKPAD_PROBE 1000
|
||||
|
||||
#ifndef NAVIGATOR_TRACKPAD_ADDRESS
|
||||
# define NAVIGATOR_TRACKPAD_ADDRESS 0x58
|
||||
#endif
|
||||
|
||||
#ifndef NAVIGATOR_TRACKPAD_TIMEOUT
|
||||
# define NAVIGATOR_TRACKPAD_TIMEOUT 100
|
||||
#endif
|
||||
|
||||
// Packet and report IDs
|
||||
#define CGEN6_MAX_PACKET_SIZE 17
|
||||
#define CGEN6_PTP_REPORT_ID 0x01
|
||||
#define CGEN6_MOUSE_REPORT_ID 0x06
|
||||
#define CGEN6_ABSOLUTE_REPORT_ID 0x09
|
||||
|
||||
// C3 error codes when reading memory
|
||||
#define CGEN6_SUCCESS 0x00
|
||||
#define CGEN6_CKSUM_FAILED 0x01
|
||||
#define CGEN6_LEN_MISMATCH 0x02
|
||||
#define CGEN6_I2C_FAILED 0x03
|
||||
|
||||
// C3 register addresses
|
||||
#define CGEN6_REG_BASE 0x20000800
|
||||
#define CGEN6_HARDWARE_ID CGEN6_REG_BASE + 0x08
|
||||
#define CGEN6_FIRMWARE_ID CGEN6_REG_BASE + 0x09
|
||||
#define CGEN6_FIRMWARE_REV CGEN6_REG_BASE + 0x10
|
||||
#define CGEN6_VENDOR_ID CGEN6_REG_BASE + 0x0A
|
||||
#define CGEN6_PRODUCT_ID CGEN6_REG_BASE + 0x0C
|
||||
#define CGEN6_VERSION_ID CGEN6_REG_BASE + 0x0E
|
||||
#define CGEN6_FEED_CONFIG4 0x200E000B
|
||||
#define CGEN6_FEED_CONFIG3 0x200E000A
|
||||
#define CGEN6_SYS_CONFIG1 0x20000008
|
||||
#define CGEN6_XY_CONFIG 0x20080018
|
||||
#define CGEN6_SFR_BASE 0x40000008
|
||||
#define CGEN6_GPIO_BASE 0x00052000
|
||||
#define CGEN6_I2C_DR 0x61010000
|
||||
|
||||
// CPI configuration
|
||||
#define CPI_TICKS 7
|
||||
#define DEFAULT_CPI_TICK 4
|
||||
#define CPI_1 200
|
||||
#define CPI_2 400
|
||||
#define CPI_3 800
|
||||
#define CPI_4 1024
|
||||
#define CPI_5 1400
|
||||
#define CPI_6 1800
|
||||
#define CPI_7 2048
|
||||
|
||||
// Physical trackpad dimensions (in 0.01 inch units for HID descriptor)
|
||||
// Navigator trackpad is circular, 40mm diameter (1.575 inches actual)
|
||||
// We report larger physical dimensions to maintain good cursor speed
|
||||
// The OS uses this ratio to calculate movement sensitivity
|
||||
#ifndef TRACKPAD_PHYSICAL_WIDTH
|
||||
# define TRACKPAD_PHYSICAL_WIDTH 157 // 1.57 inches (40mm actual size)
|
||||
#endif
|
||||
#ifndef TRACKPAD_PHYSICAL_HEIGHT
|
||||
# define TRACKPAD_PHYSICAL_HEIGHT 157 // 1.57 inches (40mm actual size)
|
||||
#endif
|
||||
|
||||
// Logical coordinate range from Cirque Gen6 sensor in PTP mode
|
||||
// Actual usable range is approximately 1-897, rounded to 900
|
||||
#define TRACKPAD_LOGICAL_MAX 900
|
||||
|
||||
// Common finger structure (used by both mouse and PTP modes)
|
||||
typedef struct {
|
||||
uint8_t tip;
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
} cgen6_finger_t;
|
||||
|
||||
// Common report structure (used by both modes)
|
||||
typedef struct {
|
||||
cgen6_finger_t fingers[2];
|
||||
uint8_t buttons;
|
||||
uint16_t scan_time; // Sensor timestamp (100μs units) - used by PTP mode
|
||||
int8_t xDelta; // Used by mouse mode
|
||||
int8_t yDelta; // Used by mouse mode
|
||||
int8_t scrollDelta; // Used by mouse mode
|
||||
int8_t panDelta; // Used by mouse mode
|
||||
} cgen6_report_t;
|
||||
|
||||
// Low-level I2C functions
|
||||
i2c_status_t cirque_gen6_read_report(uint8_t *data, uint16_t cnt);
|
||||
void cirque_gen6_clear(void);
|
||||
uint8_t cirque_gen6_read_memory(uint32_t addr, uint8_t *data, uint16_t cnt, bool fast_read);
|
||||
uint8_t cirque_gen6_write_memory(uint32_t addr, uint8_t *data, uint16_t cnt);
|
||||
|
||||
// Register access functions
|
||||
uint8_t cirque_gen6_read_reg(uint32_t addr, bool fast_read);
|
||||
uint16_t cirque_gen6_read_reg_16(uint32_t addr);
|
||||
uint32_t cirque_gen6_read_reg_32(uint32_t addr);
|
||||
uint8_t cirque_gen6_write_reg(uint32_t addr, uint8_t data);
|
||||
uint8_t cirque_gen6_write_reg_16(uint32_t addr, uint16_t data);
|
||||
uint8_t cirque_gen6_write_reg_32(uint32_t addr, uint32_t data);
|
||||
|
||||
// Configuration functions
|
||||
uint8_t cirque_gen6_set_relative_mode(void);
|
||||
uint8_t cirque_gen6_set_ptp_mode(void);
|
||||
uint8_t cirque_gen6_swap_xy(bool set);
|
||||
uint8_t cirque_gen6_invert_y(bool set);
|
||||
uint8_t cirque_gen6_invert_x(bool set);
|
||||
uint8_t cirque_gen6_enable_logical_scaling(bool set);
|
||||
|
||||
// Motion detection and report reading
|
||||
uint8_t cirque_gen6_has_motion(void);
|
||||
void cirque_gen_6_read_report(void);
|
||||
|
||||
// Deferred callback
|
||||
uint32_t cirque_gen6_read_callback(uint32_t trigger_time, void *cb_arg);
|
||||
|
||||
// Device initialization
|
||||
void navigator_trackpad_device_init(void);
|
||||
|
||||
// CPI management
|
||||
uint16_t navigator_trackpad_get_cpi(void);
|
||||
void navigator_trackpad_set_cpi(uint16_t cpi);
|
||||
void restore_cpi(uint8_t cpi);
|
||||
|
||||
// Shared globals
|
||||
extern deferred_token callback_token;
|
||||
extern uint16_t current_cpi;
|
||||
extern uint8_t has_motion;
|
||||
extern bool trackpad_init;
|
||||
extern cgen6_report_t ptp_report; // Shared between modes
|
||||
|
||||
// Helper functions
|
||||
uint8_t cirque_gen6_finger_count(cgen6_report_t *report);
|
||||
374
drivers/sensors/navigator_trackpad_mouse.c
Normal file
374
drivers/sensors/navigator_trackpad_mouse.c
Normal file
@@ -0,0 +1,374 @@
|
||||
// Copyright 2025 ZSA Technology Labs, Inc <contact@zsa.io>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Mouse mode implementation for Navigator trackpad
|
||||
// Handles gesture detection, tap-to-click, two-finger scrolling, and scroll inertia
|
||||
|
||||
#include <math.h>
|
||||
#include "navigator_trackpad_mouse.h"
|
||||
#include "navigator_trackpad_common.h"
|
||||
#include "navigator.h" // For scroll utilities
|
||||
#include "quantum.h"
|
||||
#include "timer.h"
|
||||
|
||||
// Pointing device driver definition
|
||||
const pointing_device_driver_t navigator_trackpad_pointing_device_driver = {
|
||||
.init = navigator_trackpad_device_init,
|
||||
.get_report = navigator_trackpad_get_report,
|
||||
.get_cpi = navigator_trackpad_get_cpi,
|
||||
.set_cpi = navigator_trackpad_set_cpi
|
||||
};
|
||||
|
||||
// Mode-specific globals
|
||||
trackpad_gesture_t gesture = {0};
|
||||
extern bool set_scrolling; // Declared in navigator.c
|
||||
|
||||
#ifdef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE
|
||||
scroll_inertia_t scroll_inertia = {0};
|
||||
#endif
|
||||
|
||||
#ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING
|
||||
float macos_scroll_accumulated_h = 0;
|
||||
float macos_scroll_accumulated_v = 0;
|
||||
#endif
|
||||
|
||||
report_mouse_t navigator_trackpad_get_report(report_mouse_t mouse_report) {
|
||||
#ifdef PRECISION_TRACKPAD_ENABLE
|
||||
// When PTP is enabled, don't send mouse reports - PTP task handles everything
|
||||
return mouse_report;
|
||||
#else
|
||||
// Mouse mode - process gestures and send mouse reports
|
||||
#if defined(NAVIGATOR_TRACKPAD_PTP_MODE)
|
||||
// Handle pending click release from previous cycle
|
||||
if (gesture.pending_click) {
|
||||
gesture.pending_click = false;
|
||||
mouse_report.buttons = 0;
|
||||
return mouse_report;
|
||||
}
|
||||
|
||||
# ifdef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE
|
||||
// Process scroll inertia when active
|
||||
if (scroll_inertia.active && timer_elapsed(scroll_inertia.timer) >= NAVIGATOR_TRACKPAD_SCROLL_INERTIA_INTERVAL) {
|
||||
scroll_inertia.timer = timer_read();
|
||||
|
||||
// Apply friction to velocity (Q8 fixed point math)
|
||||
// Friction reduces velocity towards zero
|
||||
int16_t friction_x = (scroll_inertia.vx * NAVIGATOR_TRACKPAD_SCROLL_INERTIA_FRICTION) / 256;
|
||||
int16_t friction_y = (scroll_inertia.vy * NAVIGATOR_TRACKPAD_SCROLL_INERTIA_FRICTION) / 256;
|
||||
|
||||
// Ensure we always reduce by at least 1 if not zero
|
||||
if (scroll_inertia.vx > 0 && friction_x < 1) friction_x = 1;
|
||||
if (scroll_inertia.vx < 0 && friction_x > -1) friction_x = -1;
|
||||
if (scroll_inertia.vy > 0 && friction_y < 1) friction_y = 1;
|
||||
if (scroll_inertia.vy < 0 && friction_y > -1) friction_y = -1;
|
||||
|
||||
scroll_inertia.vx -= friction_x;
|
||||
scroll_inertia.vy -= friction_y;
|
||||
|
||||
// Convert Q8 velocity to scroll value
|
||||
# ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING
|
||||
// macOS mode: send raw velocity deltas (descriptor tells macOS the resolution)
|
||||
int16_t scroll_x = scroll_inertia.vx / 256;
|
||||
int16_t scroll_y = scroll_inertia.vy / 256;
|
||||
# else
|
||||
// Hi-res mode: apply multiplier for Windows/Linux
|
||||
int16_t scroll_x = (scroll_inertia.vx * NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER) / 256;
|
||||
int16_t scroll_y = (scroll_inertia.vy * NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER) / 256;
|
||||
# endif
|
||||
|
||||
// Clamp to int8_t range
|
||||
scroll_x = (scroll_x > 127) ? 127 : ((scroll_x < -127) ? -127 : scroll_x);
|
||||
scroll_y = (scroll_y > 127) ? 127 : ((scroll_y < -127) ? -127 : scroll_y);
|
||||
|
||||
// Check if velocity is too low to continue
|
||||
int16_t abs_vx = scroll_inertia.vx < 0 ? -scroll_inertia.vx : scroll_inertia.vx;
|
||||
int16_t abs_vy = scroll_inertia.vy < 0 ? -scroll_inertia.vy : scroll_inertia.vy;
|
||||
|
||||
# ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING
|
||||
// In macOS mode, track consecutive frames with no output to detect janky tail
|
||||
if (scroll_x == 0 && scroll_y == 0) {
|
||||
scroll_inertia.no_output_count++;
|
||||
} else {
|
||||
scroll_inertia.no_output_count = 0;
|
||||
}
|
||||
|
||||
// Stop if: velocity very low OR too many consecutive frames without output
|
||||
bool velocity_too_low = (abs_vx < 64 && abs_vy < 64); // Same as hi-res mode
|
||||
bool stalled = (scroll_inertia.no_output_count > 5); // 5 frames (~35ms) with no output
|
||||
if (velocity_too_low || stalled) {
|
||||
scroll_inertia.active = false;
|
||||
} else
|
||||
# else
|
||||
if (abs_vx < 64 && abs_vy < 64) { // Threshold in Q8 (0.25 in real units)
|
||||
scroll_inertia.active = false;
|
||||
} else
|
||||
# endif
|
||||
{
|
||||
// Apply scroll inversion if configured
|
||||
# ifdef NAVIGATOR_SCROLL_INVERT_X
|
||||
mouse_report.h = -scroll_x;
|
||||
# else
|
||||
mouse_report.h = scroll_x;
|
||||
# endif
|
||||
# ifdef NAVIGATOR_SCROLL_INVERT_Y
|
||||
mouse_report.v = scroll_y;
|
||||
# else
|
||||
mouse_report.v = -scroll_y;
|
||||
# endif
|
||||
return mouse_report;
|
||||
}
|
||||
}
|
||||
# endif
|
||||
#endif // End scroll inertia block
|
||||
|
||||
#if defined(NAVIGATOR_TRACKPAD_RELATIVE_MODE)
|
||||
if (!has_motion || !trackpad_init) {
|
||||
return mouse_report;
|
||||
}
|
||||
|
||||
mouse_report.x = ptp_report.xDelta;
|
||||
mouse_report.y = ptp_report.yDelta;
|
||||
mouse_report.v = ptp_report.scrollDelta;
|
||||
mouse_report.h = ptp_report.panDelta;
|
||||
mouse_report.buttons = ptp_report.buttons;
|
||||
#elif defined(NAVIGATOR_TRACKPAD_PTP_MODE)
|
||||
if (!has_motion || !trackpad_init) {
|
||||
return mouse_report;
|
||||
}
|
||||
// Create local snapshot to avoid race condition with callback updating ptp_report
|
||||
cgen6_report_t local_report = ptp_report;
|
||||
|
||||
uint8_t raw_fingers = cirque_gen6_finger_count(&local_report);
|
||||
bool is_touching = local_report.fingers[0].tip;
|
||||
bool was_idle = (gesture.state == TP_IDLE);
|
||||
uint8_t fingers = raw_fingers;
|
||||
|
||||
// Handle finger down - record start position (regardless of current state)
|
||||
if (is_touching && was_idle) {
|
||||
gesture.touch_start_time = timer_read();
|
||||
gesture.prev_x = local_report.fingers[0].x;
|
||||
gesture.prev_y = local_report.fingers[0].y;
|
||||
gesture.settled_x = 0; // Will be set after settle time
|
||||
gesture.settled_y = 0;
|
||||
gesture.settled = false;
|
||||
gesture.max_finger_count = fingers;
|
||||
gesture.state = TP_MOVING;
|
||||
# ifdef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE
|
||||
// Stop any ongoing scroll inertia when finger touches
|
||||
scroll_inertia.active = false;
|
||||
scroll_inertia.smooth_vx = 0;
|
||||
scroll_inertia.smooth_vy = 0;
|
||||
# ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING
|
||||
scroll_inertia.no_output_count = 0;
|
||||
# endif
|
||||
# endif
|
||||
# ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING
|
||||
// Reset macOS scroll accumulator when starting new gesture
|
||||
macos_scroll_accumulated_h = 0;
|
||||
macos_scroll_accumulated_v = 0;
|
||||
# endif
|
||||
}
|
||||
|
||||
// Handle finger up - evaluate tap at lift time (libinput style)
|
||||
if (!is_touching && !was_idle) {
|
||||
uint16_t duration = timer_elapsed(gesture.touch_start_time);
|
||||
|
||||
// Calculate distance from settled position (or treat as no movement if never settled)
|
||||
int32_t dist_sq = 0;
|
||||
if (gesture.settled) {
|
||||
int16_t dx = gesture.prev_x - gesture.settled_x;
|
||||
int16_t dy = gesture.prev_y - gesture.settled_y;
|
||||
dist_sq = (int32_t)dx * dx + (int32_t)dy * dy;
|
||||
}
|
||||
|
||||
// Check tap conditions: short duration AND small movement from settled position
|
||||
bool is_tap = (duration <= NAVIGATOR_TRACKPAD_TAP_TIMEOUT) &&
|
||||
(dist_sq <= NAVIGATOR_TRACKPAD_TAP_MOVE_THRESHOLD);
|
||||
|
||||
// Don't trigger single-finger taps that happen shortly after a scroll ends (within 100ms)
|
||||
// But allow two-finger taps (right-click) even after scrolling
|
||||
# ifdef NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS
|
||||
if (is_tap && gesture.max_finger_count == 1 &&
|
||||
timer_elapsed(gesture.last_scroll_end) < 100) {
|
||||
is_tap = false; // Suppress single-finger tap after scrolling
|
||||
}
|
||||
# endif
|
||||
|
||||
if (is_tap) {
|
||||
# ifdef NAVIGATOR_TRACKPAD_ENABLE_DOUBLE_TAP
|
||||
if (gesture.max_finger_count >= 2) {
|
||||
mouse_report.x = 0;
|
||||
mouse_report.y = 0;
|
||||
mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, true, POINTING_DEVICE_BUTTON2);
|
||||
gesture.pending_click = true;
|
||||
} else
|
||||
# endif
|
||||
# ifdef NAVIGATOR_TRACKPAD_ENABLE_TAP
|
||||
if (gesture.max_finger_count == 1) {
|
||||
mouse_report.x = 0;
|
||||
mouse_report.y = 0;
|
||||
mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, true, POINTING_DEVICE_BUTTON1);
|
||||
gesture.pending_click = true;
|
||||
}
|
||||
# endif
|
||||
}
|
||||
|
||||
# if defined(NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE) && defined(NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS)
|
||||
// Start scroll inertia if we were scrolling and have enough velocity
|
||||
if (gesture.state == TP_SCROLLING) {
|
||||
// Use smoothed velocity (already in Q8 format)
|
||||
int16_t abs_vx = scroll_inertia.smooth_vx < 0 ? -scroll_inertia.smooth_vx : scroll_inertia.smooth_vx;
|
||||
int16_t abs_vy = scroll_inertia.smooth_vy < 0 ? -scroll_inertia.smooth_vy : scroll_inertia.smooth_vy;
|
||||
// Trigger threshold is now in Q8 (multiply by 256)
|
||||
if (abs_vx >= (NAVIGATOR_TRACKPAD_SCROLL_INERTIA_TRIGGER * 256) ||
|
||||
abs_vy >= (NAVIGATOR_TRACKPAD_SCROLL_INERTIA_TRIGGER * 256)) {
|
||||
scroll_inertia.vx = scroll_inertia.smooth_vx;
|
||||
scroll_inertia.vy = scroll_inertia.smooth_vy;
|
||||
scroll_inertia.timer = timer_read();
|
||||
scroll_inertia.active = true;
|
||||
}
|
||||
}
|
||||
# endif
|
||||
|
||||
// Record if this was a scroll gesture ending
|
||||
# ifdef NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS
|
||||
if (gesture.state == TP_SCROLLING || gesture.max_finger_count >= 2) {
|
||||
gesture.last_scroll_end = timer_read();
|
||||
}
|
||||
# endif
|
||||
|
||||
gesture.state = TP_IDLE;
|
||||
set_scrolling = false;
|
||||
}
|
||||
|
||||
// Handle ongoing touch - movement and scrolling
|
||||
if (is_touching && !was_idle) {
|
||||
// Track max fingers during gesture
|
||||
if (fingers > gesture.max_finger_count) {
|
||||
gesture.max_finger_count = fingers;
|
||||
}
|
||||
|
||||
# ifdef NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS
|
||||
// Determine mode based on finger count
|
||||
// Once scrolling starts, keep scrolling until all fingers lift (no mid-gesture transitions)
|
||||
if (fingers >= 2 && gesture.state != TP_SCROLLING) {
|
||||
gesture.state = TP_SCROLLING;
|
||||
// Reset position tracking - use finger[0] initially
|
||||
gesture.prev_x = local_report.fingers[0].x;
|
||||
gesture.prev_y = local_report.fingers[0].y;
|
||||
}
|
||||
// Note: We don't transition from SCROLLING back to MOVING mid-gesture anymore
|
||||
// Once scroll starts, it continues until all fingers lift (gesture ends)
|
||||
# endif
|
||||
|
||||
uint16_t duration = timer_elapsed(gesture.touch_start_time);
|
||||
|
||||
// Record settled position once settle time elapses
|
||||
if (!gesture.settled && duration >= NAVIGATOR_TRACKPAD_TAP_SETTLE_TIME) {
|
||||
gesture.settled = true;
|
||||
gesture.settled_x = local_report.fingers[0].x;
|
||||
gesture.settled_y = local_report.fingers[0].y;
|
||||
}
|
||||
|
||||
// Check if we should suppress movement (might still be a tap)
|
||||
int32_t dist_sq = 0;
|
||||
if (gesture.settled) {
|
||||
int16_t dx = local_report.fingers[0].x - gesture.settled_x;
|
||||
int16_t dy = local_report.fingers[0].y - gesture.settled_y;
|
||||
dist_sq = (int32_t)dx * dx + (int32_t)dy * dy;
|
||||
}
|
||||
|
||||
// Only report movement if: timeout exceeded OR moved beyond tap threshold (after settling)
|
||||
bool should_move = (duration > NAVIGATOR_TRACKPAD_TAP_TIMEOUT) ||
|
||||
(gesture.settled && dist_sq > NAVIGATOR_TRACKPAD_TAP_MOVE_THRESHOLD);
|
||||
|
||||
if (should_move) {
|
||||
int16_t delta_x = local_report.fingers[0].x - gesture.prev_x;
|
||||
int16_t delta_y = local_report.fingers[0].y - gesture.prev_y;
|
||||
|
||||
// Clamp deltas to prevent jumps from bad data
|
||||
if (delta_x > NAVIGATOR_TRACKPAD_MAX_DELTA) delta_x = NAVIGATOR_TRACKPAD_MAX_DELTA;
|
||||
if (delta_x < -NAVIGATOR_TRACKPAD_MAX_DELTA) delta_x = -NAVIGATOR_TRACKPAD_MAX_DELTA;
|
||||
if (delta_y > NAVIGATOR_TRACKPAD_MAX_DELTA) delta_y = NAVIGATOR_TRACKPAD_MAX_DELTA;
|
||||
if (delta_y < -NAVIGATOR_TRACKPAD_MAX_DELTA) delta_y = -NAVIGATOR_TRACKPAD_MAX_DELTA;
|
||||
|
||||
if (delta_x != 0 || delta_y != 0) {
|
||||
# ifdef NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS
|
||||
if (gesture.state == TP_SCROLLING) {
|
||||
# ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING
|
||||
// macOS mode: send raw deltas, macOS handles scaling via HIDScrollResolution
|
||||
// Apple trackpads report raw sensor deltas and let macOS apply acceleration
|
||||
int16_t scroll_x = delta_x;
|
||||
int16_t scroll_y = delta_y;
|
||||
# else
|
||||
// Hi-res mode: apply multiplier for Windows/Linux
|
||||
// These OSes divide by the Resolution Multiplier (120)
|
||||
int16_t scroll_x = delta_x * NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER;
|
||||
int16_t scroll_y = delta_y * NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER;
|
||||
# endif
|
||||
|
||||
# ifdef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE
|
||||
// Track velocity for inertia using exponential smoothing (Q8 fixed point)
|
||||
// Alpha = 0.3 (77/256) - balances responsiveness with smoothness
|
||||
// When direction changes, reset smoothing to avoid fighting old momentum
|
||||
int16_t new_vx = delta_x * 256;
|
||||
int16_t new_vy = delta_y * 256;
|
||||
|
||||
// Detect direction change (signs differ and both non-zero)
|
||||
bool dir_change_x = (scroll_inertia.smooth_vx > 0 && new_vx < 0) ||
|
||||
(scroll_inertia.smooth_vx < 0 && new_vx > 0);
|
||||
bool dir_change_y = (scroll_inertia.smooth_vy > 0 && new_vy < 0) ||
|
||||
(scroll_inertia.smooth_vy < 0 && new_vy > 0);
|
||||
|
||||
if (dir_change_x) {
|
||||
// Direction changed - blend toward zero first, then pick up new direction
|
||||
scroll_inertia.smooth_vx = (scroll_inertia.smooth_vx * 128 + new_vx * 128) / 256;
|
||||
} else {
|
||||
// Same direction - normal EMA smoothing
|
||||
scroll_inertia.smooth_vx = (scroll_inertia.smooth_vx * 179 + new_vx * 77) / 256;
|
||||
}
|
||||
|
||||
if (dir_change_y) {
|
||||
scroll_inertia.smooth_vy = (scroll_inertia.smooth_vy * 128 + new_vy * 128) / 256;
|
||||
} else {
|
||||
scroll_inertia.smooth_vy = (scroll_inertia.smooth_vy * 179 + new_vy * 77) / 256;
|
||||
}
|
||||
# endif
|
||||
|
||||
// Clamp to int8_t range for the report
|
||||
scroll_x = (scroll_x > 127) ? 127 : ((scroll_x < -127) ? -127 : scroll_x);
|
||||
scroll_y = (scroll_y > 127) ? 127 : ((scroll_y < -127) ? -127 : scroll_y);
|
||||
|
||||
// Apply scroll inversion if configured
|
||||
# ifdef NAVIGATOR_SCROLL_INVERT_X
|
||||
mouse_report.h = -scroll_x;
|
||||
# else
|
||||
mouse_report.h = scroll_x;
|
||||
# endif
|
||||
# ifdef NAVIGATOR_SCROLL_INVERT_Y
|
||||
mouse_report.v = scroll_y;
|
||||
# else
|
||||
mouse_report.v = -scroll_y;
|
||||
# endif
|
||||
mouse_report.x = 0;
|
||||
mouse_report.y = 0;
|
||||
} else
|
||||
# endif
|
||||
{
|
||||
// One-finger movement: mouse cursor
|
||||
mouse_report.x = (delta_x < 0) ? -powf(-delta_x, 1.2) : powf(delta_x, 1.2);
|
||||
mouse_report.y = (delta_y < 0) ? -powf(-delta_y, 1.2) : powf(delta_y, 1.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update prev position for next frame
|
||||
gesture.prev_x = local_report.fingers[0].x;
|
||||
gesture.prev_y = local_report.fingers[0].y;
|
||||
}
|
||||
#endif
|
||||
|
||||
has_motion = 0;
|
||||
return mouse_report;
|
||||
#endif // PRECISION_TRACKPAD_ENABLE
|
||||
}
|
||||
109
drivers/sensors/navigator_trackpad_mouse.h
Normal file
109
drivers/sensors/navigator_trackpad_mouse.h
Normal file
@@ -0,0 +1,109 @@
|
||||
// Copyright 2025 ZSA Technology Labs, Inc <contact@zsa.io>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "navigator_trackpad_common.h"
|
||||
#include "pointing_device.h"
|
||||
#include "report.h"
|
||||
|
||||
// Tap detection configuration
|
||||
#ifndef NAVIGATOR_TRACKPAD_TAP_MOVE_THRESHOLD
|
||||
# define NAVIGATOR_TRACKPAD_TAP_MOVE_THRESHOLD 100 // Max movement (squared) before tap becomes a drag
|
||||
#endif
|
||||
|
||||
#ifndef NAVIGATOR_TRACKPAD_TAP_TIMEOUT
|
||||
# define NAVIGATOR_TRACKPAD_TAP_TIMEOUT 200 // Max duration (ms) for a tap
|
||||
#endif
|
||||
|
||||
#ifndef NAVIGATOR_TRACKPAD_TAP_SETTLE_TIME
|
||||
# define NAVIGATOR_TRACKPAD_TAP_SETTLE_TIME 30 // Ignore movement during initial contact (ms)
|
||||
#endif
|
||||
|
||||
#ifndef NAVIGATOR_TRACKPAD_MAX_DELTA
|
||||
# define NAVIGATOR_TRACKPAD_MAX_DELTA 250 // Max allowed delta per frame to prevent jumps
|
||||
#endif
|
||||
|
||||
// Scroll configuration
|
||||
#ifndef NAVIGATOR_TRACKPAD_SCROLL_DIVIDER
|
||||
# define NAVIGATOR_TRACKPAD_SCROLL_DIVIDER 10
|
||||
#endif
|
||||
|
||||
#ifndef NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER
|
||||
# define NAVIGATOR_TRACKPAD_SCROLL_MULTIPLIER 3
|
||||
#endif
|
||||
|
||||
// Two-finger scrolling (define to enable)
|
||||
// #define NAVIGATOR_TRACKPAD_SCROLL_WITH_TWO_FINGERS
|
||||
|
||||
// macOS scrolling mode (define to enable)
|
||||
// macOS doesn't respect the HID Resolution Multiplier descriptor.
|
||||
// When enabled, sends raw scroll deltas without the multiplier (like Apple trackpads).
|
||||
// macOS applies its own HIDScrollResolution (400 DPI) and acceleration curves.
|
||||
// When disabled (default), applies multiplier for Windows/Linux hi-res scrolling.
|
||||
// #define NAVIGATOR_TRACKPAD_MACOS_SCROLLING
|
||||
|
||||
// Scroll inversion configuration
|
||||
// Define these to invert scroll direction on respective axes
|
||||
// #define NAVIGATOR_SCROLL_INVERT_X
|
||||
// #define NAVIGATOR_SCROLL_INVERT_Y
|
||||
|
||||
// Scroll inertia configuration
|
||||
/*
|
||||
To enable, add to your config.h:
|
||||
#define NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE
|
||||
|
||||
Configurable values (all optional):
|
||||
- NAVIGATOR_TRACKPAD_SCROLL_INERTIA_FRICTION - Higher = stops faster (default: 50, range 1-255)
|
||||
- NAVIGATOR_TRACKPAD_SCROLL_INERTIA_INTERVAL - Time between glide reports in ms (default: 15)
|
||||
- NAVIGATOR_TRACKPAD_SCROLL_INERTIA_TRIGGER - Minimum velocity to trigger glide (default: 3)
|
||||
*/
|
||||
#ifdef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_ENABLE
|
||||
#ifndef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_FRICTION
|
||||
#define NAVIGATOR_TRACKPAD_SCROLL_INERTIA_FRICTION 5 // Higher = stops faster (1-255)
|
||||
#endif
|
||||
#ifndef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_INTERVAL
|
||||
#define NAVIGATOR_TRACKPAD_SCROLL_INERTIA_INTERVAL 7 // Glide report interval in ms
|
||||
#endif
|
||||
#ifndef NAVIGATOR_TRACKPAD_SCROLL_INERTIA_TRIGGER
|
||||
#define NAVIGATOR_TRACKPAD_SCROLL_INERTIA_TRIGGER 1 // Min velocity to trigger glide
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
int16_t vx; // Current X velocity (Q8 fixed point)
|
||||
int16_t vy; // Current Y velocity (Q8 fixed point)
|
||||
int16_t smooth_vx; // Smoothed X velocity (Q8 fixed point)
|
||||
int16_t smooth_vy; // Smoothed Y velocity (Q8 fixed point)
|
||||
uint16_t timer; // Timer for interval tracking
|
||||
bool active; // Is glide currently active
|
||||
#ifdef NAVIGATOR_TRACKPAD_MACOS_SCROLLING
|
||||
uint8_t no_output_count; // Counter for consecutive frames with no output
|
||||
#endif
|
||||
} scroll_inertia_t;
|
||||
#endif
|
||||
|
||||
// Trackpad gesture state machine
|
||||
typedef enum {
|
||||
TP_IDLE, // No fingers touching
|
||||
TP_MOVING, // One finger movement = mouse cursor
|
||||
TP_SCROLLING, // Two finger movement = scroll
|
||||
} trackpad_state_t;
|
||||
|
||||
typedef struct {
|
||||
trackpad_state_t state;
|
||||
uint16_t touch_start_time; // When finger first touched
|
||||
uint16_t settled_x; // Position after settle time (for tap threshold)
|
||||
uint16_t settled_y;
|
||||
uint16_t prev_x; // Previous position (for delta calculation)
|
||||
uint16_t prev_y;
|
||||
uint8_t max_finger_count; // Max fingers seen during this gesture
|
||||
bool settled; // Has the settle time elapsed?
|
||||
bool pending_click; // Need to send a click release next cycle
|
||||
uint16_t last_scroll_end; // Time when last scroll gesture ended
|
||||
} trackpad_gesture_t;
|
||||
|
||||
// Mouse mode functions
|
||||
report_mouse_t navigator_trackpad_get_report(report_mouse_t mouse_report);
|
||||
|
||||
// Pointing device driver struct
|
||||
extern const pointing_device_driver_t navigator_trackpad_pointing_device_driver;
|
||||
166
drivers/sensors/navigator_trackpad_ptp.c
Normal file
166
drivers/sensors/navigator_trackpad_ptp.c
Normal file
@@ -0,0 +1,166 @@
|
||||
// Copyright 2025 ZSA Technology Labs, Inc <contact@zsa.io>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// PTP (Precision Touchpad) mode implementation for Navigator trackpad
|
||||
// Converts Cirque Gen 6 sensor data to Windows Precision Touchpad HID reports
|
||||
|
||||
#include "navigator_trackpad_ptp.h"
|
||||
#include "navigator_trackpad_common.h"
|
||||
#include "precision_trackpad_drivers.h"
|
||||
#include "quantum.h"
|
||||
#include "report.h"
|
||||
|
||||
#ifdef PRECISION_TRACKPAD_ENABLE
|
||||
|
||||
// External declaration for PTP report sending
|
||||
extern void send_trackpad(report_trackpad_t *report);
|
||||
|
||||
#if defined(NAVIGATOR_TRACKPAD_PTP_MODE)
|
||||
|
||||
// PTP task function - converts trackpad touches to PTP reports
|
||||
static bool navigator_trackpad_ptp_task(void) {
|
||||
if (!has_motion || !trackpad_init) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create local snapshot to avoid race condition with callback
|
||||
cgen6_report_t local_report = ptp_report;
|
||||
|
||||
uint8_t raw_fingers = cirque_gen6_finger_count(&local_report);
|
||||
|
||||
// Initialize PTP report
|
||||
report_trackpad_t ptp = {0};
|
||||
ptp.report_id = REPORT_ID_TRACKPAD;
|
||||
|
||||
// Sensitivity scaling: Track previous RAW sensor position and accumulated scaled position
|
||||
// Apply sensitivity only to the delta from raw sensor, then accumulate
|
||||
static uint16_t prev_raw_x[2] = {0, 0};
|
||||
static uint16_t prev_raw_y[2] = {0, 0};
|
||||
static uint16_t accum_x[2] = {0, 0};
|
||||
static uint16_t accum_y[2] = {0, 0};
|
||||
static bool was_touching[2] = {false, false};
|
||||
|
||||
if (local_report.fingers[0].tip) {
|
||||
uint16_t raw_x = local_report.fingers[0].x;
|
||||
uint16_t raw_y = local_report.fingers[0].y;
|
||||
|
||||
if (was_touching[0]) {
|
||||
// Calculate delta from RAW sensor data
|
||||
int16_t delta_x = (int16_t)(raw_x - prev_raw_x[0]);
|
||||
int16_t delta_y = (int16_t)(raw_y - prev_raw_y[0]);
|
||||
|
||||
// Apply sensitivity to the delta
|
||||
delta_x = (int16_t)((float)delta_x * NAVIGATOR_TRACKPAD_SENSITIVITY);
|
||||
delta_y = (int16_t)((float)delta_y * NAVIGATOR_TRACKPAD_SENSITIVITY);
|
||||
|
||||
// Add scaled delta to accumulated position
|
||||
int32_t new_x = (int32_t)accum_x[0] + delta_x;
|
||||
int32_t new_y = (int32_t)accum_y[0] + delta_y;
|
||||
|
||||
// Clamp to valid range
|
||||
if (new_x < 0) new_x = 0;
|
||||
if (new_x > 4095) new_x = 4095;
|
||||
if (new_y < 0) new_y = 0;
|
||||
if (new_y > 4095) new_y = 4095;
|
||||
|
||||
accum_x[0] = (uint16_t)new_x;
|
||||
accum_y[0] = (uint16_t)new_y;
|
||||
} else {
|
||||
// First touch - initialize accumulated position to raw position
|
||||
accum_x[0] = raw_x;
|
||||
accum_y[0] = raw_y;
|
||||
}
|
||||
|
||||
ptp.contacts[0].confidence = 1;
|
||||
ptp.contacts[0].tip = 1;
|
||||
ptp.contacts[0].contact_id = 0;
|
||||
ptp.contacts[0].x = accum_x[0];
|
||||
ptp.contacts[0].y = accum_y[0];
|
||||
|
||||
prev_raw_x[0] = raw_x;
|
||||
prev_raw_y[0] = raw_y;
|
||||
was_touching[0] = true;
|
||||
} else {
|
||||
was_touching[0] = false;
|
||||
}
|
||||
|
||||
if (local_report.fingers[1].tip) {
|
||||
uint16_t raw_x = local_report.fingers[1].x;
|
||||
uint16_t raw_y = local_report.fingers[1].y;
|
||||
|
||||
if (was_touching[1]) {
|
||||
// Calculate delta from RAW sensor data
|
||||
int16_t delta_x = (int16_t)(raw_x - prev_raw_x[1]);
|
||||
int16_t delta_y = (int16_t)(raw_y - prev_raw_y[1]);
|
||||
|
||||
// Apply sensitivity to the delta
|
||||
delta_x = (int16_t)((float)delta_x * NAVIGATOR_TRACKPAD_SENSITIVITY);
|
||||
delta_y = (int16_t)((float)delta_y * NAVIGATOR_TRACKPAD_SENSITIVITY);
|
||||
|
||||
// Add scaled delta to accumulated position
|
||||
int32_t new_x = (int32_t)accum_x[1] + delta_x;
|
||||
int32_t new_y = (int32_t)accum_y[1] + delta_y;
|
||||
|
||||
// Clamp to valid range
|
||||
if (new_x < 0) new_x = 0;
|
||||
if (new_x > 4095) new_x = 4095;
|
||||
if (new_y < 0) new_y = 0;
|
||||
if (new_y > 4095) new_y = 4095;
|
||||
|
||||
accum_x[1] = (uint16_t)new_x;
|
||||
accum_y[1] = (uint16_t)new_y;
|
||||
} else {
|
||||
// First touch - initialize accumulated position to raw position
|
||||
accum_x[1] = raw_x;
|
||||
accum_y[1] = raw_y;
|
||||
}
|
||||
|
||||
ptp.contacts[1].confidence = 1;
|
||||
ptp.contacts[1].tip = 1;
|
||||
ptp.contacts[1].contact_id = 1;
|
||||
ptp.contacts[1].x = accum_x[1];
|
||||
ptp.contacts[1].y = accum_y[1];
|
||||
|
||||
prev_raw_x[1] = raw_x;
|
||||
prev_raw_y[1] = raw_y;
|
||||
was_touching[1] = true;
|
||||
} else {
|
||||
was_touching[1] = false;
|
||||
}
|
||||
|
||||
// Set scan time, contact count, and buttons (after contacts per Microsoft spec)
|
||||
// Use the sensor's scan_time for accurate gesture velocity calculation
|
||||
ptp.scan_time = local_report.scan_time;
|
||||
ptp.contact_count = raw_fingers;
|
||||
ptp.button1 = (local_report.buttons & 0x01) ? 1 : 0;
|
||||
ptp.button2 = (local_report.buttons & 0x02) ? 1 : 0;
|
||||
ptp.button3 = (local_report.buttons & 0x04) ? 1 : 0;
|
||||
|
||||
if (raw_fingers > 0) {
|
||||
send_trackpad(&ptp);
|
||||
}
|
||||
|
||||
has_motion = 0;
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
// Stub for when PTP mode is not enabled
|
||||
static bool navigator_trackpad_ptp_task(void) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Internal init function
|
||||
static void navigator_trackpad_ptp_init(void) {
|
||||
navigator_trackpad_device_init(); // Common init
|
||||
}
|
||||
|
||||
// Driver registration
|
||||
const precision_trackpad_driver_t navigator_trackpad_precision_trackpad_driver = {
|
||||
.init = navigator_trackpad_ptp_init,
|
||||
.task = navigator_trackpad_ptp_task,
|
||||
.set_cpi = navigator_trackpad_set_cpi,
|
||||
.get_cpi = navigator_trackpad_get_cpi
|
||||
};
|
||||
|
||||
#endif // PRECISION_TRACKPAD_ENABLE
|
||||
28
drivers/sensors/navigator_trackpad_ptp.h
Normal file
28
drivers/sensors/navigator_trackpad_ptp.h
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2025 ZSA Technology Labs, Inc <contact@zsa.io>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "navigator_trackpad_common.h"
|
||||
#include "precision_trackpad_drivers.h"
|
||||
#include "report.h"
|
||||
|
||||
// Trackpad sensitivity multiplier (1.0 = native, >1.0 = more sensitive)
|
||||
// Higher values make cursor/gestures move faster
|
||||
// Add to your config.h to customize:
|
||||
// #define NAVIGATOR_TRACKPAD_SENSITIVITY 1.5f
|
||||
#ifndef NAVIGATOR_TRACKPAD_SENSITIVITY
|
||||
# define NAVIGATOR_TRACKPAD_SENSITIVITY 1.0f
|
||||
#endif
|
||||
|
||||
#ifdef PRECISION_TRACKPAD_ENABLE
|
||||
// Driver registration
|
||||
extern const precision_trackpad_driver_t navigator_trackpad_precision_trackpad_driver;
|
||||
#ifndef NAVIGATOR_TRACKPAD_PTP_MODE
|
||||
#error "NAVIGATOR_TRACKPAD_PTP_MODE must be defined when using the precision trackpad driver"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef NAVIGATOR_TRACKPAD_SENSITIVITY
|
||||
# define NAVIGATOR_TRACKPAD_SENSITIVITY 1.3f
|
||||
#endif
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright 2024 ZSA Technology Labs
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "precision_trackpad.h"
|
||||
#include "drivers/sensors/navigator_trackpad.h"
|
||||
|
||||
void precision_trackpad_init_kb(void) {
|
||||
// Initialize the navigator trackpad sensor
|
||||
navigator_trackpad_device_init();
|
||||
}
|
||||
|
||||
bool precision_trackpad_task_kb(void) {
|
||||
// Call the PTP task to process sensor data and send reports
|
||||
navigator_trackpad_task();
|
||||
return true; // Activity occurred
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright 2024 ZSA Technology Labs
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "quantum.h"
|
||||
|
||||
#ifdef PRECISION_TRACKPAD_ENABLE
|
||||
|
||||
// Initialize the trackpad
|
||||
void trackpad_init(void);
|
||||
|
||||
// Update trackpad state and send reports
|
||||
void trackpad_task(void);
|
||||
|
||||
#endif // PRECISION_TRACKPAD_ENABLE
|
||||
166
ptp_monitor.py
166
ptp_monitor.py
@@ -1,166 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Linux HID Monitor for Windows Precision Trackpad Reports
|
||||
Reads raw HID reports from /dev/hidrawX and parses them
|
||||
|
||||
Usage: sudo python3 ptp_monitor.py /dev/hidrawX
|
||||
"""
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
|
||||
def parse_ptp_report(data):
|
||||
"""Parse our PTP report format"""
|
||||
if len(data) < 15:
|
||||
return None
|
||||
|
||||
report_id = data[0]
|
||||
|
||||
# Debug: print first few bytes
|
||||
if data[0] == 0x01:
|
||||
print(f"Input Report (0x01): {' '.join(f'{b:02x}' for b in data[:15])}")
|
||||
|
||||
if report_id == 0x01: # REPORT_ID_TRACKPAD
|
||||
# Contact 0 (bytes 1-5)
|
||||
c0_flags = data[1]
|
||||
c0_confidence = (c0_flags >> 0) & 1
|
||||
c0_tip = (c0_flags >> 1) & 1
|
||||
c0_contact_id = (c0_flags >> 2) & 3
|
||||
c0_x = struct.unpack('<H', data[2:4])[0]
|
||||
c0_y = struct.unpack('<H', data[4:6])[0]
|
||||
|
||||
# Contact 1 (bytes 6-10)
|
||||
c1_flags = data[6]
|
||||
c1_confidence = (c1_flags >> 0) & 1
|
||||
c1_tip = (c1_flags >> 1) & 1
|
||||
c1_contact_id = (c1_flags >> 2) & 3
|
||||
c1_x = struct.unpack('<H', data[7:9])[0]
|
||||
c1_y = struct.unpack('<H', data[9:11])[0]
|
||||
|
||||
# Scan time and contact count
|
||||
scan_time = struct.unpack('<H', data[11:13])[0]
|
||||
contact_count = data[13]
|
||||
|
||||
# Buttons
|
||||
buttons = data[14]
|
||||
btn1 = (buttons >> 0) & 1
|
||||
btn2 = (buttons >> 1) & 1
|
||||
btn3 = (buttons >> 2) & 1
|
||||
|
||||
return {
|
||||
'type': 'input',
|
||||
'contact_count': contact_count,
|
||||
'scan_time': scan_time,
|
||||
'c0': {
|
||||
'tip': c0_tip,
|
||||
'confidence': c0_confidence,
|
||||
'id': c0_contact_id,
|
||||
'x': c0_x,
|
||||
'y': c0_y
|
||||
},
|
||||
'c1': {
|
||||
'tip': c1_tip,
|
||||
'confidence': c1_confidence,
|
||||
'id': c1_contact_id,
|
||||
'x': c1_x,
|
||||
'y': c1_y
|
||||
},
|
||||
'buttons': {'b1': btn1, 'b2': btn2, 'b3': btn3}
|
||||
}
|
||||
|
||||
elif report_id in [0x02, 0x03, 0x04, 0x05]: # Feature reports
|
||||
return {
|
||||
'type': 'feature',
|
||||
'report_id': report_id,
|
||||
'data': data[:15]
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
def format_report(report, show_all=False):
|
||||
"""Format report for display"""
|
||||
if report['type'] == 'input':
|
||||
lines = []
|
||||
lines.append(f"Count: {report['contact_count']}, "
|
||||
f"Scan: {report['scan_time']:5d}, "
|
||||
f"Btns: {report['buttons']['b1']}{report['buttons']['b2']}{report['buttons']['b3']}")
|
||||
|
||||
# Only show contacts that are active (or show all if requested)
|
||||
if report['c0']['tip'] or show_all:
|
||||
lines.append(f" C0: id={report['c0']['id']}, "
|
||||
f"conf={report['c0']['confidence']}, "
|
||||
f"tip={report['c0']['tip']}, "
|
||||
f"x={report['c0']['x']:4d}, "
|
||||
f"y={report['c0']['y']:4d}")
|
||||
|
||||
if report['c1']['tip'] or show_all:
|
||||
lines.append(f" C1: id={report['c1']['id']}, "
|
||||
f"conf={report['c1']['confidence']}, "
|
||||
f"tip={report['c1']['tip']}, "
|
||||
f"x={report['c1']['x']:4d}, "
|
||||
f"y={report['c1']['y']:4d}")
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
elif report['type'] == 'feature':
|
||||
hex_str = ' '.join(f'{b:02x}' for b in report['data'])
|
||||
return f"Feature Report ID: 0x{report['report_id']:02x}, Data: {hex_str}"
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: sudo python3 ptp_monitor.py /dev/hidrawX [--all]")
|
||||
print("\nOptions:")
|
||||
print(" --all Show all contacts, even when not touching")
|
||||
print("\nExample:")
|
||||
print(" sudo python3 ptp_monitor.py /dev/hidraw4")
|
||||
sys.exit(1)
|
||||
|
||||
device_path = sys.argv[1]
|
||||
show_all = '--all' in sys.argv
|
||||
|
||||
print(f"Opening {device_path}...")
|
||||
print("Touch the trackpad to see reports (Ctrl+C to exit)")
|
||||
print("=" * 70)
|
||||
|
||||
try:
|
||||
with open(device_path, 'rb') as f:
|
||||
report_count = 0
|
||||
last_report_time = time.time()
|
||||
|
||||
while True:
|
||||
data = f.read(64)
|
||||
if not data:
|
||||
time.sleep(0.001)
|
||||
continue
|
||||
|
||||
report = parse_ptp_report(data)
|
||||
if report:
|
||||
report_count += 1
|
||||
current_time = time.time()
|
||||
delta_ms = (current_time - last_report_time) * 1000
|
||||
last_report_time = current_time
|
||||
|
||||
# Only print if there's activity or we're in verbose mode
|
||||
if report['type'] == 'input':
|
||||
if show_all or report['contact_count'] > 0 or report_count % 100 == 0:
|
||||
print(f"\n[{report_count:5d}] Δ{delta_ms:5.1f}ms")
|
||||
print(format_report(report, show_all))
|
||||
else:
|
||||
# Always show feature reports
|
||||
print(f"\n[{report_count:5d}] Δ{delta_ms:5.1f}ms")
|
||||
print(format_report(report, show_all))
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n" + "=" * 70)
|
||||
print(f"Captured {report_count} reports")
|
||||
except FileNotFoundError:
|
||||
print(f"Error: Device {device_path} not found")
|
||||
print("\nTry: ls -la /sys/class/hidraw/")
|
||||
sys.exit(1)
|
||||
except PermissionError:
|
||||
print(f"Error: Permission denied. Run with sudo:")
|
||||
print(f" sudo python3 {sys.argv[0]} {device_path}")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -4,24 +4,39 @@
|
||||
#include "precision_trackpad.h"
|
||||
#include "timer.h"
|
||||
|
||||
// Weak functions that can be overridden by keyboard-specific implementations
|
||||
__attribute__((weak)) void precision_trackpad_init_kb(void) {}
|
||||
__attribute__((weak)) bool precision_trackpad_task_kb(void) {
|
||||
return false;
|
||||
}
|
||||
// Include the driver implementation
|
||||
#if defined(PRECISION_TRACKPAD_DRIVER_NAVIGATOR_TRACKPAD)
|
||||
# include "drivers/sensors/navigator_trackpad_ptp.h"
|
||||
#endif
|
||||
|
||||
// Driver selection (resolved at compile time by build system)
|
||||
const precision_trackpad_driver_t *precision_trackpad_driver =
|
||||
&PRECISION_TRACKPAD_DRIVER(PRECISION_TRACKPAD_DRIVER_NAME);
|
||||
|
||||
void precision_trackpad_init(void) {
|
||||
precision_trackpad_init_kb();
|
||||
if (precision_trackpad_driver && precision_trackpad_driver->init) {
|
||||
precision_trackpad_driver->init();
|
||||
}
|
||||
}
|
||||
|
||||
bool precision_trackpad_task(void) {
|
||||
static uint32_t last_update = 0;
|
||||
|
||||
// Throttle updates to 125Hz (8ms) to avoid overwhelming USB
|
||||
if (timer_elapsed32(last_update) < 8) {
|
||||
return false;
|
||||
// No throttling - let the driver control timing
|
||||
// PTP mode needs responsive updates for gesture recognition
|
||||
if (precision_trackpad_driver && precision_trackpad_driver->task) {
|
||||
return precision_trackpad_driver->task();
|
||||
}
|
||||
|
||||
last_update = timer_read32();
|
||||
return precision_trackpad_task_kb();
|
||||
return false;
|
||||
}
|
||||
|
||||
void precision_trackpad_set_cpi(uint16_t cpi) {
|
||||
if (precision_trackpad_driver && precision_trackpad_driver->set_cpi) {
|
||||
precision_trackpad_driver->set_cpi(cpi);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t precision_trackpad_get_cpi(void) {
|
||||
if (precision_trackpad_driver && precision_trackpad_driver->get_cpi) {
|
||||
return precision_trackpad_driver->get_cpi();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "precision_trackpad_drivers.h"
|
||||
|
||||
/**
|
||||
* @brief Initialize the precision trackpad
|
||||
@@ -23,3 +24,20 @@ void precision_trackpad_init(void);
|
||||
* @return true if trackpad activity occurred, false otherwise
|
||||
*/
|
||||
bool precision_trackpad_task(void);
|
||||
|
||||
/**
|
||||
* @brief Set the trackpad CPI (counts per inch)
|
||||
*
|
||||
* @param cpi The desired CPI value
|
||||
*/
|
||||
void precision_trackpad_set_cpi(uint16_t cpi);
|
||||
|
||||
/**
|
||||
* @brief Get the current trackpad CPI
|
||||
*
|
||||
* @return Current CPI value
|
||||
*/
|
||||
uint16_t precision_trackpad_get_cpi(void);
|
||||
|
||||
// Driver pointer (set at compile time)
|
||||
extern const precision_trackpad_driver_t *precision_trackpad_driver;
|
||||
|
||||
20
quantum/precision_trackpad_drivers.h
Normal file
20
quantum/precision_trackpad_drivers.h
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2025 ZSA Technology Labs, Inc <@zsa>
|
||||
// Copyright 2025 Florian Didron <fdidron@zsa.io>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// Precision trackpad driver interface
|
||||
typedef struct {
|
||||
void (*init)(void);
|
||||
bool (*task)(void);
|
||||
void (*set_cpi)(uint16_t);
|
||||
uint16_t (*get_cpi)(void);
|
||||
} precision_trackpad_driver_t;
|
||||
|
||||
// Driver registration macros (mirror pointing_device pattern)
|
||||
#define PRECISION_TRACKPAD_DRIVER_CONCAT(name) name##_precision_trackpad_driver
|
||||
#define PRECISION_TRACKPAD_DRIVER(name) PRECISION_TRACKPAD_DRIVER_CONCAT(name)
|
||||
@@ -40,14 +40,16 @@ enum hid_report_ids {
|
||||
REPORT_ID_JOYSTICK,
|
||||
REPORT_ID_DIGITIZER,
|
||||
|
||||
// PTP trackpad uses its own report ID sequence starting at 0x01
|
||||
// to match Windows PTP HID specification
|
||||
REPORT_ID_TRACKPAD = 0x01,
|
||||
REPORT_ID_TRACKPAD_CONFIG = 0x0A,
|
||||
REPORT_ID_TRACKPAD_FEATURE = 0x0B,
|
||||
REPORT_ID_TRACKPAD_MAX_COUNT = 0x0C,
|
||||
REPORT_ID_TRACKPAD_PTPHQA = 0x0D,
|
||||
REPORT_ID_COUNT = REPORT_ID_TRACKPAD_PTPHQA
|
||||
// PTP trackpad uses separate report IDs (per Microsoft PTP specification)
|
||||
// These don't auto-increment from above, they're on a separate interface
|
||||
REPORT_ID_TRACKPAD = 0x01, // Input report (multi-touch data)
|
||||
REPORT_ID_TRACKPAD_MAX_COUNT = 0x02, // Feature: Contact Count Maximum
|
||||
REPORT_ID_TRACKPAD_CONFIG = 0x03, // Feature: Input Mode configuration
|
||||
REPORT_ID_TRACKPAD_FEATURE = 0x04, // Feature: Surface/Button switches
|
||||
REPORT_ID_TRACKPAD_PTPHQA = 0x05, // Feature: Certification blob
|
||||
|
||||
// REPORT_ID_COUNT must be the highest ID from the auto-incrementing sequence
|
||||
REPORT_ID_COUNT = REPORT_ID_DIGITIZER // Highest report ID value
|
||||
};
|
||||
|
||||
#define IS_VALID_REPORT_ID(id) ((id) >= REPORT_ID_ALL && (id) <= REPORT_ID_COUNT)
|
||||
@@ -260,6 +262,7 @@ typedef struct {
|
||||
} PACKED report_trackpad_contact_t;
|
||||
|
||||
// Main trackpad input report
|
||||
// Field order per Microsoft PTP spec: [ReportID][Contacts][ScanTime][Count][Buttons]
|
||||
typedef struct {
|
||||
uint8_t report_id; // Report ID for trackpad
|
||||
report_trackpad_contact_t contacts[2]; // Up to 2 simultaneous contacts
|
||||
|
||||
@@ -385,8 +385,11 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM SharedReport[] = {
|
||||
#endif
|
||||
|
||||
#ifdef PRECISION_TRACKPAD_ENABLE
|
||||
// Include trackpad dimensions
|
||||
#include "drivers/sensors/navigator_trackpad_common.h"
|
||||
|
||||
// Windows Precision Touchpad (PTP) HID Descriptor
|
||||
// Based on Microsoft PTP specification
|
||||
// Follows Microsoft PTP specification exactly
|
||||
const USB_Descriptor_HIDReport_Datatype_t PROGMEM PrecisionTrackpadReport[] = {
|
||||
// Touch Pad Input TLC
|
||||
HID_RI_USAGE_PAGE(8, 0x0D), // Digitizers
|
||||
@@ -394,7 +397,7 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM PrecisionTrackpadReport[] = {
|
||||
HID_RI_COLLECTION(8, 0x01), // Application
|
||||
HID_RI_REPORT_ID(8, REPORT_ID_TRACKPAD),
|
||||
|
||||
// First finger
|
||||
// First finger (contact 0)
|
||||
HID_RI_USAGE(8, 0x22), // Finger
|
||||
HID_RI_COLLECTION(8, 0x02), // Logical
|
||||
HID_RI_LOGICAL_MINIMUM(8, 0x00),
|
||||
@@ -416,21 +419,22 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM PrecisionTrackpadReport[] = {
|
||||
// X/Y position
|
||||
HID_RI_USAGE_PAGE(8, 0x01), // Generic Desktop
|
||||
HID_RI_LOGICAL_MINIMUM(8, 0x00),
|
||||
HID_RI_LOGICAL_MAXIMUM(16, 0x0384), // 900 (actual Cirque range: 1-897)
|
||||
HID_RI_LOGICAL_MAXIMUM(16, TRACKPAD_LOGICAL_MAX), // 4095 (12-bit resolution)
|
||||
HID_RI_REPORT_SIZE(8, 0x10),
|
||||
HID_RI_UNIT_EXPONENT(8, 0x0E), // -2
|
||||
HID_RI_UNIT_EXPONENT(8, 0x0E), // -2 (hundredths)
|
||||
HID_RI_UNIT(8, 0x13), // Inch, English Linear
|
||||
HID_RI_PHYSICAL_MINIMUM(8, 0x00),
|
||||
HID_RI_PHYSICAL_MAXIMUM(16, 0x009D), // 157 (1.57 inches / 40mm)
|
||||
HID_RI_PHYSICAL_MAXIMUM(16, TRACKPAD_PHYSICAL_WIDTH), // Physical width in 0.01 inch
|
||||
HID_RI_REPORT_COUNT(8, 0x01),
|
||||
HID_RI_USAGE(8, 0x30), // X
|
||||
HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
|
||||
HID_RI_PHYSICAL_MAXIMUM(16, 0x009D), // 157 (1.57 inches / 40mm)
|
||||
HID_RI_PHYSICAL_MAXIMUM(16, TRACKPAD_PHYSICAL_HEIGHT), // Physical height in 0.01 inch
|
||||
HID_RI_USAGE(8, 0x31), // Y
|
||||
HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
|
||||
HID_RI_END_COLLECTION(0),
|
||||
|
||||
// Second finger (identical structure)
|
||||
HID_RI_USAGE_PAGE(8, 0x0D), // Digitizers (switch back!)
|
||||
HID_RI_USAGE(8, 0x22), // Finger
|
||||
HID_RI_COLLECTION(8, 0x02), // Logical
|
||||
HID_RI_LOGICAL_MINIMUM(8, 0x00),
|
||||
@@ -451,22 +455,21 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM PrecisionTrackpadReport[] = {
|
||||
|
||||
HID_RI_USAGE_PAGE(8, 0x01), // Generic Desktop
|
||||
HID_RI_LOGICAL_MINIMUM(8, 0x00),
|
||||
HID_RI_LOGICAL_MAXIMUM(16, 0x0384), // 900 (actual Cirque range: 1-897)
|
||||
HID_RI_LOGICAL_MAXIMUM(16, TRACKPAD_LOGICAL_MAX), // 4095 (12-bit resolution)
|
||||
HID_RI_REPORT_SIZE(8, 0x10),
|
||||
HID_RI_UNIT_EXPONENT(8, 0x0E),
|
||||
HID_RI_UNIT(8, 0x13),
|
||||
HID_RI_PHYSICAL_MINIMUM(8, 0x00),
|
||||
HID_RI_PHYSICAL_MAXIMUM(16, 0x009D), // 157 (1.57 inches / 40mm)
|
||||
HID_RI_PHYSICAL_MAXIMUM(16, TRACKPAD_PHYSICAL_WIDTH), // Physical width in 0.01 inch
|
||||
HID_RI_REPORT_COUNT(8, 0x01),
|
||||
HID_RI_USAGE(8, 0x30), // X
|
||||
HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
|
||||
HID_RI_PHYSICAL_MAXIMUM(16, 0x009D), // 157 (1.57 inches / 40mm)
|
||||
HID_RI_PHYSICAL_MAXIMUM(16, TRACKPAD_PHYSICAL_HEIGHT), // Physical height in 0.01 inch
|
||||
HID_RI_USAGE(8, 0x31), // Y
|
||||
HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
|
||||
HID_RI_END_COLLECTION(0),
|
||||
|
||||
// Scan Time
|
||||
HID_RI_USAGE_PAGE(8, 0x0D), // Digitizers
|
||||
// Scan Time (comes AFTER contacts per Microsoft spec)
|
||||
HID_RI_UNIT_EXPONENT(8, 0x0C), // -4
|
||||
HID_RI_UNIT(16, 0x1001), // Seconds
|
||||
HID_RI_LOGICAL_MINIMUM(8, 0x00),
|
||||
@@ -475,10 +478,11 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM PrecisionTrackpadReport[] = {
|
||||
HID_RI_PHYSICAL_MAXIMUM(32, 0x0000FFFF),
|
||||
HID_RI_REPORT_SIZE(8, 0x10),
|
||||
HID_RI_REPORT_COUNT(8, 0x01),
|
||||
HID_RI_USAGE_PAGE(8, 0x0D), // Digitizers
|
||||
HID_RI_USAGE(8, 0x56), // Scan Time
|
||||
HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
|
||||
|
||||
// Contact Count
|
||||
// Contact Count (8 bits, not 4!)
|
||||
HID_RI_USAGE(8, 0x54), // Contact Count
|
||||
HID_RI_LOGICAL_MAXIMUM(8, 0x7F),
|
||||
HID_RI_REPORT_SIZE(8, 0x08),
|
||||
|
||||
Reference in New Issue
Block a user