diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index efa349052cfd..e8eaca2ccd98 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -87,6 +87,7 @@ target_sources_ifdef(CONFIG_USB_DEVICE_STACK app PRIVATE src/usb.c) target_sources_ifdef(CONFIG_ZMK_USB app PRIVATE src/usb_hid.c) target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/rgb_underglow.c) target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/backlight.c) +target_sources_ifdef(CONFIG_ZMK_TRACKPAD app PRIVATE src/trackpad.c) target_sources(app PRIVATE src/workqueue.c) target_sources(app PRIVATE src/main.c) diff --git a/app/Kconfig b/app/Kconfig index 89a128b5a27e..e43cc80e2a94 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -149,6 +149,10 @@ config ZMK_BLE_CONSUMER_REPORT_QUEUE_SIZE int "Max number of consumer HID reports to queue for sending over BLE" default 5 +config ZMK_BLE_PTP_REPORT_QUEUE_SIZE + int "Max number of trackpad HID reports to queue for sending over BLE" + default 20 + config ZMK_BLE_CLEAR_BONDS_ON_START bool "Configuration that clears all bond information from the keyboard on startup." default n @@ -308,6 +312,64 @@ endif #Display/LED Options endmenu +menuconfig ZMK_TRACKPAD + bool "Enable ZMK trackpad emulation" + default n + +config ZMK_TRACKPAD_TICK_DURATION + int "Trackpad tick duration in ms" + default 8 + +config ZMK_TRACKPAD_MAX_FINGERS + int "Maximum number of fingers reported in PTP" + range 3 5 + default 5 + +config ZMK_TRACKPAD_PHYSICAL_X + int "Maximum X size of trackpad in 0.1cm increments" + default 600 + +config ZMK_TRACKPAD_PHYSICAL_Y + int "Maximum Y size of trackpad in 0.1cm increments" + default 600 + +config ZMK_TRACKPAD_LOGICAL_X + int "Maximum X coordinate value trackpad can report" + default 4095 + +config ZMK_TRACKPAD_LOGICAL_Y + int "Maximum Y coordinate value trackpad can report" + default 4095 + + +if ZMK_TRACKPAD + +choice ZMK_TRACKPAD_WORK_QUEUE + prompt "Work queue selection for trackpad events" + default ZMK_TRACKPAD_WORK_QUEUE_DEDICATED + +config ZMK_TRACKPAD_WORK_QUEUE_SYSTEM + bool "Use default system work queue for trackpad events" + +config ZMK_TRACKPAD_WORK_QUEUE_DEDICATED + bool "Use dedicated work queue for trackpad events" + +endchoice + +if ZMK_TRACKPAD_WORK_QUEUE_DEDICATED + +config ZMK_TRACKPAD_DEDICATED_THREAD_STACK_SIZE + int "Stack size for dedicated trackpad thread/queue" + default 8192 + +config ZMK_TRACKPAD_DEDICATED_THREAD_PRIORITY + int "Thread priority for dedicated trackpad thread/queue" + default 3 + +endif # ZMK_TRACKPAD_WORK_QUEUE_DEDICATED + +endif + menu "Power Management" config ZMK_BATTERY_REPORTING diff --git a/app/include/zmk/endpoints.h b/app/include/zmk/endpoints.h index c8860533e1d4..9125c99414f6 100644 --- a/app/include/zmk/endpoints.h +++ b/app/include/zmk/endpoints.h @@ -13,3 +13,6 @@ int zmk_endpoints_toggle(); enum zmk_endpoint zmk_endpoints_selected(); int zmk_endpoints_send_report(uint16_t usage_page); +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) +int zmk_endpoints_send_ptp_report(); +#endif \ No newline at end of file diff --git a/app/include/zmk/hid.h b/app/include/zmk/hid.h index ab42adaa13e6..afb3ced3f34b 100644 --- a/app/include/zmk/hid.h +++ b/app/include/zmk/hid.h @@ -17,11 +17,20 @@ #define COLLECTION_REPORT 0x03 +#define ZMK_REPORT_ID_KEYBOARD 0x01 +#define ZMK_REPORT_ID_CONSUMER 0x02 + +#define ZMK_REPORT_ID_TRACKPAD 0x05 +#define ZMK_REPORT_ID_FEATURE_PTP_CAPABILITIES 0x06 +#define ZMK_REPORT_ID_FEATURE_PTPHQA 0x07 +#define ZMK_REPORT_ID_FEATURE_PTP_CONFIGURATION 0x08 +#define ZMK_REPORT_ID_FEATURE_PTP_SELECTIVE 0x09 + static const uint8_t zmk_hid_report_desc[] = { HID_USAGE_PAGE(HID_USAGE_GEN_DESKTOP), HID_USAGE(HID_USAGE_GD_KEYBOARD), HID_COLLECTION(HID_COLLECTION_APPLICATION), - HID_REPORT_ID(0x01), + HID_REPORT_ID(ZMK_REPORT_ID_KEYBOARD), HID_USAGE_PAGE(HID_USAGE_KEY), HID_USAGE_MIN8(HID_USAGE_KEY_KEYBOARD_LEFTCONTROL), HID_USAGE_MAX8(HID_USAGE_KEY_KEYBOARD_RIGHT_GUI), @@ -67,7 +76,7 @@ static const uint8_t zmk_hid_report_desc[] = { HID_USAGE_PAGE(HID_USAGE_CONSUMER), HID_USAGE(HID_USAGE_CONSUMER_CONSUMER_CONTROL), HID_COLLECTION(HID_COLLECTION_APPLICATION), - HID_REPORT_ID(0x02), + HID_REPORT_ID(ZMK_REPORT_ID_CONSUMER), HID_USAGE_PAGE(HID_USAGE_CONSUMER), #if IS_ENABLED(CONFIG_ZMK_HID_CONSUMER_REPORT_USAGES_BASIC) @@ -89,6 +98,211 @@ static const uint8_t zmk_hid_report_desc[] = { /* INPUT (Data,Ary,Abs) */ HID_INPUT(0x00), HID_END_COLLECTION, +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) + // PTP Touchpad HID with inspiration from osmakari + /* USAGE_PAGE (Digitizers) */ + HID_USAGE_PAGE(HID_USAGE_DIGITIZERS), + /* USAGE (Touch Pad) */ + HID_USAGE(HID_USAGE_DIGITIZERS_TOUCH_PAD), + /* COLLECTION (Application) */ + HID_COLLECTION(HID_COLLECTION_APPLICATION), + + /* Windows Precision Touchpad Input Reports */ + + /* REPORT_ID (0x05) */ + HID_REPORT_ID(ZMK_REPORT_ID_TRACKPAD), + /* USAGE (Finger) */ + HID_USAGE(HID_USAGE_DIGITIZERS_FINGER), + /* COLLECTION (Logical) */ + HID_COLLECTION(0x02), + /* LOGICAL_MINIMUM (0) */ + HID_LOGICAL_MIN8(0), + /* LOGICAL_MAXIMUM (1) */ + HID_LOGICAL_MAX8(1), + /* USAGE (Confidence) */ + HID_USAGE(0x47), + /* USAGE (Tip switch) */ + HID_USAGE(0x42), + /* REPORT_COUNT (2) */ + HID_REPORT_COUNT(2), + /* REPORT_SIZE (1) */ + HID_REPORT_SIZE(1), + /* INPUT (Data, Var, Abs) */ + HID_INPUT(0x02), + // Padding for bytes + HID_REPORT_COUNT(6), + /* INPUT (Cnst,Var,Abs) */ + HID_INPUT(0x03), + /* REPORT_COUNT (1) */ + HID_REPORT_COUNT(1), + /* REPORT_SIZE (3) */ + HID_REPORT_SIZE(3), + /* LOGICAL_MAXIMUM (24 */ + HID_LOGICAL_MAX8(5), + /* USAGE (Contact Identifier) */ + HID_USAGE(HID_USAGE_DIGITIZERS_CONTACT_IDENTIFIER), + /* INPUT (Data, Var, Abs) */ + HID_INPUT(0x02), + /* REPORT_SIZE (1) */ + HID_REPORT_SIZE(1), + /* REPORT_COUNT (byte padding) */ + HID_REPORT_COUNT(5), + /* INPUT (Cnst,Var,Abs) */ + HID_INPUT(0x03), + /* USAGE_PAGE(Generic Desktop) */ + HID_USAGE_PAGE(HID_USAGE_GD), + /* LOGICAL_MINIMUM (0) */ + HID_LOGICAL_MIN8(0), + /* LOGICAL_MAXIMUM (4095) */ + HID_LOGICAL_MAX16((CONFIG_ZMK_TRACKPAD_LOGICAL_X & 0xFF), + ((CONFIG_ZMK_TRACKPAD_LOGICAL_X >> 8) & 0xFF)), + /* REPORT_SIZE (16) */ + HID_REPORT_SIZE(16), + /* UNIT_EXPONENT (-2) */ + 0x55, + 0x0e, + /* UNIT (CM, EngLinear) */ + 0x65, + 0x11, + /* USAGE (X) */ + HID_USAGE(HID_USAGE_GD_X), + /* PHYSICAL_MINIMUM (0) */ + 0x35, + 0x00, + /* PHYSICAL_MAXIMUM (defined in config) */ + 0x46, + (CONFIG_ZMK_TRACKPAD_PHYSICAL_X & 0xFF), + ((CONFIG_ZMK_TRACKPAD_PHYSICAL_X >> 8) & 0xFF), + /* REPORT_COUNT (1) */ + HID_REPORT_COUNT(1), + /* INPUT (Data, Var, Abs) */ + HID_INPUT(0x02), + // Logimax + HID_LOGICAL_MAX16((CONFIG_ZMK_TRACKPAD_LOGICAL_Y & 0xFF), + ((CONFIG_ZMK_TRACKPAD_LOGICAL_Y >> 8) & 0xFF)), + /* PHYSICAL_MAXIMUM (Defined in config) */ + 0x46, + (CONFIG_ZMK_TRACKPAD_PHYSICAL_Y & 0xFF), + ((CONFIG_ZMK_TRACKPAD_PHYSICAL_Y >> 8) & 0xFF), + /* USAGE (Y) */ + HID_USAGE(HID_USAGE_GD_Y), + /* INPUT (Data, Var, Abs) */ + HID_INPUT(0x02), + /* END_COLLECTION */ + HID_END_COLLECTION, + /* USAGE_PAGE (Digitizers) */ + HID_USAGE_PAGE(HID_USAGE_DIGITIZERS), + /* USAGE (Contact count) */ + HID_USAGE(HID_USAGE_DIGITIZERS_CONTACT_COUNT), + /* LOGICAL_MAXIMUM (127) */ + HID_LOGICAL_MAX8(0x7F), + /* REPORT_COUNT (1) */ + HID_REPORT_COUNT(1), + /* REPORT_SIZE (8) */ + HID_REPORT_SIZE(8), + /* INPUT(Data, Var, Abs) */ + HID_INPUT(0x02), + + /* Device Capabilities Feature Report */ + + /* USAGE_PAGE (Digitizer) */ + HID_USAGE_PAGE(HID_USAGE_DIGITIZERS), + /* REPORT_ID (0x07) */ + HID_REPORT_ID(ZMK_REPORT_ID_FEATURE_PTP_CAPABILITIES), + /* USAGE (Contact Count Maximum) */ + HID_USAGE(HID_USAGE_DIGITIZERS_CONTACT_COUNT_MAXIMUM), + /* USAGE (Pad Type) */ + HID_USAGE(HID_USAGE_DIGITIZERS_PAD_TYPE), + /* REPORT_SIZE (4) */ + HID_REPORT_SIZE(4), + /* REPORT_COUNT (2) */ + HID_REPORT_COUNT(2), + /* LOGICAL_MAXIMUM (15) */ + HID_LOGICAL_MAX8(0x0F), + /* FEATURE (Data, Var, Abs) */ + HID_FEATURE(0x02), + + /* PTPHQA Blob: Necessary for < Windows 10 */ + + /* USAGE_PAGE (Vendor Defined) */ + 0x06, + 0x00, + 0xFF, + /* REPORT_ID (0x08) */ + HID_REPORT_ID(ZMK_REPORT_ID_FEATURE_PTPHQA), + /* HID_USAGE (Vendor Usage 0xC5) */ + HID_USAGE(0xC5), + /* LOGICAL_MINIMUM (0) */ + HID_LOGICAL_MIN8(0), + /* LOGICAL_MAXIMUM (0xFF) */ + HID_LOGICAL_MAX16(0xFF, 0x00), + /* REPORT_SIZE (8) */ + HID_REPORT_SIZE(8), + /* REPORT_COUNT (256) */ + 0x96, + 0x00, + 0x01, + /* FEATURE (Data, Var, Abs) */ + HID_FEATURE(0x02), + /* END_COLLECTION */ + HID_END_COLLECTION, + + // TLC + /* USAGE_PAGE (Digitizer) */ + HID_USAGE_PAGE(HID_USAGE_DIGITIZERS), + /* USAGE (Configuration) */ + HID_USAGE(0x0E), + /* HID_COLLECTION */ + HID_COLLECTION(HID_COLLECTION_APPLICATION), + /* REPORT_ID (Feature 0x09) */ + HID_REPORT_ID(ZMK_REPORT_ID_FEATURE_PTP_CONFIGURATION), + /* USAGE (Finger) */ + HID_USAGE(HID_USAGE_DIGITIZERS_FINGER), + /* COLLECTION (Logical) */ + HID_COLLECTION(0x02), + /* USAGE (Input Mode) */ + HID_USAGE(0x52), + /* LOGICAL_MINIMUM (0) */ + HID_LOGICAL_MIN8(0), + /* LOGICAL_MAXIMUM (10) */ + HID_LOGICAL_MAX8(10), + /* REPORT_SIZE (8) */ + HID_REPORT_SIZE(8), + /* REPORT_COUNT (1) */ + HID_REPORT_COUNT(1), + /* FEATURE (Data, Var, Abs) */ + HID_FEATURE(0x02), + /* END_COLLECTION */ + HID_END_COLLECTION, + + /* USAGE (Finger) */ + HID_USAGE(HID_USAGE_DIGITIZERS_FINGER), + /* COLLECTION (Physical) */ + HID_COLLECTION(HID_COLLECTION_PHYSICAL), + /* REPORT_ID (Feature, 0x0A) */ + HID_REPORT_ID(ZMK_REPORT_ID_FEATURE_PTP_SELECTIVE), + /* USAGE (Surface switch) */ + HID_USAGE(HID_USAGE_DIGITIZERS_SURFACE_SWITCH), + /* USAGE (Button switch) */ + HID_USAGE(HID_USAGE_DIGITIZERS_BUTTON_SWITCH), + /* REPORT_SIZE (1) */ + HID_REPORT_SIZE(1), + /* REPORT_COUNT (2) */ + HID_REPORT_COUNT(2), + /* LOGICAL_MAXIMUM (1) */ + HID_LOGICAL_MAX8(1), + /* FEATURE (Data, Var, Abs) */ + HID_FEATURE(0x02), + /* REPORT_COUNT (6) */ + HID_REPORT_COUNT(6), + /* FEATURE (Cnst, Var, Abs) */ + HID_FEATURE(0x03), + /* END_COLLECTION */ + HID_END_COLLECTION, + /* END_COLLECTION */ + HID_END_COLLECTION, +#endif + }; // struct zmk_hid_boot_report @@ -126,6 +340,67 @@ struct zmk_hid_consumer_report { struct zmk_hid_consumer_report_body body; } __packed; +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) +// Reporting structure from osmakari +// Report for single finger +struct zmk_ptp_finger { + // Confidence (bit 0) and tip switch (bit 1) + uint8_t confidence_tip; + // Contact ID + uint8_t contact_id; + // X + uint16_t x; + // Y + uint16_t y; +} __packed; + +struct zmk_hid_ptp_report_body { + // Finger reporting + struct zmk_ptp_finger finger; + // Contact count + uint8_t contact_count; + // Buttons + // uint8_t buttons; +} __packed; + +// Report containing finger data +struct zmk_hid_ptp_report { + // 0x05 + uint8_t report_id; + struct zmk_hid_ptp_report_body body; + +} __packed; + +// Feature report for configuration +struct zmk_ptp_feature_configuration { + uint8_t report_id; + // 0 for Mouse collection, 3 for Windows Precision Touchpad Collection + uint8_t input_mode; + // Selective reporting: Surface switch (bit 0), Button switch (bit 1) + uint8_t selective_reporting; +} __packed; + +// Feature report for certification +struct zmk_ptp_feature_certification { + uint8_t ptphqa_blob[256]; +} __packed; + +#define PTP_PAD_TYPE_DEPRESSIBLE 0x00 +#define PTP_PAD_TYPE_PRESSURE 0x01 +#define PTP_PAD_TYPE_NON_CLICKABLE 0x02 + +// Feature report for device capabilities +struct zmk_ptp_feature_capabilities { + uint8_t report_id; + // Max touches (L 4bit) and pad type (H 4bit): + // Max touches: number 3-5 + // Pad type: 0 for Depressible, 1 for Non-depressible, 2 for Non-clickable + uint8_t max_touches_pad_type; + +} __packed; + +#endif + zmk_mod_flags_t zmk_hid_get_explicit_mods(); int zmk_hid_register_mod(zmk_mod_t modifier); int zmk_hid_unregister_mod(zmk_mod_t modifier); @@ -152,5 +427,12 @@ int zmk_hid_press(uint32_t usage); int zmk_hid_release(uint32_t usage); bool zmk_hid_is_pressed(uint32_t usage); +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) +void zmk_hid_ptp_set(struct zmk_ptp_finger finger, uint8_t contact_count); +#endif + struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(); struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(); +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) +struct zmk_hid_ptp_report *zmk_hid_get_ptp_report(); +#endif diff --git a/app/include/zmk/hog.h b/app/include/zmk/hog.h index 7523fb661ad0..62deacd6c0d0 100644 --- a/app/include/zmk/hog.h +++ b/app/include/zmk/hog.h @@ -13,3 +13,7 @@ int zmk_hog_init(); int zmk_hog_send_keyboard_report(struct zmk_hid_keyboard_report_body *body); int zmk_hog_send_consumer_report(struct zmk_hid_consumer_report_body *body); +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) +int zmk_hog_send_ptp_report(struct zmk_hid_ptp_report_body *body); +int zmk_hog_send_ptp_report_direct(struct zmk_hid_ptp_report_body *report); +#endif diff --git a/app/include/zmk/trackpad.h b/app/include/zmk/trackpad.h new file mode 100644 index 000000000000..b0230bc7f8ae --- /dev/null +++ b/app/include/zmk/trackpad.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2023 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include + +typedef uint8_t zmk_trackpad_finger_contacts_t; + +struct zmk_trackpad_finger_data_t { + bool present; + bool palm; + uint16_t x, y; +}; + +struct k_work_q *zmk_trackpad_work_q(); diff --git a/app/src/endpoints.c b/app/src/endpoints.c index dbd1a3e6c0e7..c6eee364fa1d 100644 --- a/app/src/endpoints.c +++ b/app/src/endpoints.c @@ -144,6 +144,45 @@ int zmk_endpoints_send_report(uint16_t usage_page) { } } +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) +int zmk_endpoints_send_ptp_report() { + + // LOG_DBG("Trying to sent report %d", 0); + + struct zmk_hid_ptp_report *ptp_report = zmk_hid_get_ptp_report(); + + switch (current_endpoint) { +#if IS_ENABLED(CONFIG_ZMK_USB) + case ZMK_ENDPOINT_USB: { + int err = zmk_usb_hid_send_report((uint8_t *)ptp_report, sizeof(*ptp_report)); + if (err) { + LOG_ERR("FAILED TO SEND OVER USB: %d", err); + } + return err; + } +#endif /* IS_ENABLED(CONFIG_ZMK_USB) */ + +#if IS_ENABLED(CONFIG_ZMK_BLE) + case ZMK_ENDPOINT_BLE: { +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD_WORK_QUEUE_DEDICATED) + int err = zmk_hog_send_ptp_report_direct(&ptp_report->body); +#else + int err = zmk_hog_send_ptp_report(&ptp_report->body); +#endif + if (err) { + LOG_ERR("FAILED TO SEND OVER HOG: %d", err); + } + return err; + } +#endif /* IS_ENABLED(CONFIG_ZMK_BLE) */ + + default: + LOG_ERR("Unsupported endpoint %d", current_endpoint); + return -ENOTSUP; + } +} +#endif + #if IS_ENABLED(CONFIG_SETTINGS) static int endpoints_handle_set(const char *name, size_t len, settings_read_cb read_cb, diff --git a/app/src/hid.c b/app/src/hid.c index 2a6b5d39da20..b2cc1b6fa08f 100644 --- a/app/src/hid.c +++ b/app/src/hid.c @@ -16,6 +16,46 @@ static struct zmk_hid_keyboard_report keyboard_report = { static struct zmk_hid_consumer_report consumer_report = {.report_id = 2, .body = {.keys = {0}}}; +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) + +// Report containing finger data +struct zmk_hid_ptp_report ptp_report = { + .report_id = ZMK_REPORT_ID_TRACKPAD, + .body = {.finger = {.confidence_tip = 0, .contact_id = 0, .x = 0, .y = 0}, .contact_count = 0}}; + +// Feature report for configuration +struct zmk_ptp_feature_configuration ptp_feature_configuration = { + .report_id = ZMK_REPORT_ID_FEATURE_PTP_CONFIGURATION, + .input_mode = 0, + .selective_reporting = 0}; + +// Feature report for ptphqa +struct zmk_ptp_feature_certification ptp_feature_certification = { + .ptphqa_blob = { + 0xfc, 0x28, 0xfe, 0x84, 0x40, 0xcb, 0x9a, 0x87, 0x0d, 0xbe, 0x57, 0x3c, 0xb6, 0x70, 0x09, + 0x88, 0x07, 0x97, 0x2d, 0x2b, 0xe3, 0x38, 0x34, 0xb6, 0x6c, 0xed, 0xb0, 0xf7, 0xe5, 0x9c, + 0xf6, 0xc2, 0x2e, 0x84, 0x1b, 0xe8, 0xb4, 0x51, 0x78, 0x43, 0x1f, 0x28, 0x4b, 0x7c, 0x2d, + 0x53, 0xaf, 0xfc, 0x47, 0x70, 0x1b, 0x59, 0x6f, 0x74, 0x43, 0xc4, 0xf3, 0x47, 0x18, 0x53, + 0x1a, 0xa2, 0xa1, 0x71, 0xc7, 0x95, 0x0e, 0x31, 0x55, 0x21, 0xd3, 0xb5, 0x1e, 0xe9, 0x0c, + 0xba, 0xec, 0xb8, 0x89, 0x19, 0x3e, 0xb3, 0xaf, 0x75, 0x81, 0x9d, 0x53, 0xb9, 0x41, 0x57, + 0xf4, 0x6d, 0x39, 0x25, 0x29, 0x7c, 0x87, 0xd9, 0xb4, 0x98, 0x45, 0x7d, 0xa7, 0x26, 0x9c, + 0x65, 0x3b, 0x85, 0x68, 0x89, 0xd7, 0x3b, 0xbd, 0xff, 0x14, 0x67, 0xf2, 0x2b, 0xf0, 0x2a, + 0x41, 0x54, 0xf0, 0xfd, 0x2c, 0x66, 0x7c, 0xf8, 0xc0, 0x8f, 0x33, 0x13, 0x03, 0xf1, 0xd3, + 0xc1, 0x0b, 0x89, 0xd9, 0x1b, 0x62, 0xcd, 0x51, 0xb7, 0x80, 0xb8, 0xaf, 0x3a, 0x10, 0xc1, + 0x8a, 0x5b, 0xe8, 0x8a, 0x56, 0xf0, 0x8c, 0xaa, 0xfa, 0x35, 0xe9, 0x42, 0xc4, 0xd8, 0x55, + 0xc3, 0x38, 0xcc, 0x2b, 0x53, 0x5c, 0x69, 0x52, 0xd5, 0xc8, 0x73, 0x02, 0x38, 0x7c, 0x73, + 0xb6, 0x41, 0xe7, 0xff, 0x05, 0xd8, 0x2b, 0x79, 0x9a, 0xe2, 0x34, 0x60, 0x8f, 0xa3, 0x32, + 0x1f, 0x09, 0x78, 0x62, 0xbc, 0x80, 0xe3, 0x0f, 0xbd, 0x65, 0x20, 0x08, 0x13, 0xc1, 0xe2, + 0xee, 0x53, 0x2d, 0x86, 0x7e, 0xa7, 0x5a, 0xc5, 0xd3, 0x7d, 0x98, 0xbe, 0x31, 0x48, 0x1f, + 0xfb, 0xda, 0xaf, 0xa2, 0xa8, 0x6a, 0x89, 0xd6, 0xbf, 0xf2, 0xd3, 0x32, 0x2a, 0x9a, 0xe4, + 0xcf, 0x17, 0xb7, 0xb8, 0xf4, 0xe1, 0x33, 0x08, 0x24, 0x8b, 0xc4, 0x43, 0xa5, 0xe5, 0x24, + 0xc2}}; + +// Feature report for device capabilities +struct zmk_ptp_feature_capabilities ptp_feature_capabilities = { + .max_touches_pad_type = CONFIG_ZMK_TRACKPAD_MAX_FINGERS | (PTP_PAD_TYPE_NON_CLICKABLE << 4)}; +#endif + // Keep track of how often a modifier was pressed. // Only release the modifier if the count is 0. static int explicit_modifier_counts[8] = {0, 0, 0, 0, 0, 0, 0, 0}; @@ -263,6 +303,12 @@ bool zmk_hid_is_pressed(uint32_t usage) { } return false; } +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) +void zmk_hid_ptp_set(struct zmk_ptp_finger finger, uint8_t contact_count) { + ptp_report.body.finger = finger; + ptp_report.body.contact_count = contact_count; +} +#endif struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report() { return &keyboard_report; @@ -271,3 +317,9 @@ struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report() { struct zmk_hid_consumer_report *zmk_hid_get_consumer_report() { return &consumer_report; } + +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) +struct zmk_hid_ptp_report *zmk_hid_get_ptp_report() { + return &ptp_report; +} +#endif diff --git a/app/src/hog.c b/app/src/hog.c index 930714b092a4..8485cfd5053d 100644 --- a/app/src/hog.c +++ b/app/src/hog.c @@ -47,15 +47,40 @@ enum { }; static struct hids_report input = { - .id = 0x01, + .id = ZMK_REPORT_ID_KEYBOARD, .type = HIDS_INPUT, }; static struct hids_report consumer_input = { - .id = 0x02, + .id = ZMK_REPORT_ID_CONSUMER, .type = HIDS_INPUT, }; +static struct hids_report trackpad_report = { + .id = ZMK_REPORT_ID_TRACKPAD, + .type = HIDS_INPUT, +}; + +static struct hids_report trackpad_capabilities = { + .id = ZMK_REPORT_ID_FEATURE_PTP_CAPABILITIES, + .type = HIDS_FEATURE, +}; + +static struct hids_report trackpad_hqa = { + .id = ZMK_REPORT_ID_FEATURE_PTPHQA, + .type = HIDS_FEATURE, +}; + +static struct hids_report trackpad_config = { + .id = ZMK_REPORT_ID_FEATURE_PTP_CONFIGURATION, + .type = HIDS_FEATURE, +}; + +static struct hids_report trackpad_selective = { + .id = ZMK_REPORT_ID_FEATURE_PTP_SELECTIVE, + .type = HIDS_FEATURE, +}; + static bool host_requests_notification = false; static uint8_t ctrl_point; // static uint8_t proto_mode; @@ -85,6 +110,33 @@ static ssize_t read_hids_input_report(struct bt_conn *conn, const struct bt_gatt sizeof(struct zmk_hid_keyboard_report_body)); } +static ssize_t read_hids_consumer_input_report(struct bt_conn *conn, + const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) { + struct zmk_hid_consumer_report_body *report_body = &zmk_hid_get_consumer_report()->body; + return bt_gatt_attr_read(conn, attr, buf, len, offset, report_body, + sizeof(struct zmk_hid_consumer_report_body)); +} +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) +static ssize_t read_hids_trackpad_input_report(struct bt_conn *conn, + const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) { + struct zmk_hid_ptp_report_body *report_body = &zmk_hid_get_ptp_report()->body; + return bt_gatt_attr_read(conn, attr, buf, len, offset, report_body, + sizeof(struct zmk_hid_ptp_report_body)); +} +#endif +// TODO +/* +static ssize_t read_hids_trackpad_capabilities_input_report(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, uint16_t len, + uint16_t offset) { + struct zmk_hid_consumer_report_body *report_body = &zmk_hid_get_consumer_report()->body; + return bt_gatt_attr_read(conn, attr, buf, len, offset, report_body, + sizeof(struct zmk_hid_consumer_report_body)); +} + static ssize_t read_hids_consumer_input_report(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { @@ -93,6 +145,24 @@ static ssize_t read_hids_consumer_input_report(struct bt_conn *conn, sizeof(struct zmk_hid_consumer_report_body)); } +static ssize_t read_hids_consumer_input_report(struct bt_conn *conn, + const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) { + struct zmk_hid_consumer_report_body *report_body = &zmk_hid_get_consumer_report()->body; + return bt_gatt_attr_read(conn, attr, buf, len, offset, report_body, + sizeof(struct zmk_hid_consumer_report_body)); +} + +static ssize_t read_hids_consumer_input_report(struct bt_conn *conn, + const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) { + struct zmk_hid_consumer_report_body *report_body = &zmk_hid_get_consumer_report()->body; + return bt_gatt_attr_read(conn, attr, buf, len, offset, report_body, + sizeof(struct zmk_hid_consumer_report_body)); +} +*/ +// ENDTODO + // static ssize_t write_proto_mode(struct bt_conn *conn, // const struct bt_gatt_attr *attr, // const void *buf, uint16_t len, uint16_t offset, @@ -139,6 +209,33 @@ BT_GATT_SERVICE_DEFINE( BT_GATT_CCC(input_ccc_changed, BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT), BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ_ENCRYPT, read_hids_report_ref, NULL, &consumer_input), +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, + BT_GATT_PERM_READ_ENCRYPT, read_hids_trackpad_input_report, NULL, NULL), + BT_GATT_CCC(input_ccc_changed, BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT), + BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ_ENCRYPT, read_hids_report_ref, + NULL, &trackpad_report), +#endif /* \ +BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_hids_consumer_input_report, NULL, NULL), \ +BT_GATT_CCC(input_ccc_changed, BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT), \ +BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ_ENCRYPT, read_hids_report_ref, \ + NULL, &consumer_input), \ +BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_hids_consumer_input_report, NULL, NULL), \ +BT_GATT_CCC(input_ccc_changed, BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT), \ +BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ_ENCRYPT, read_hids_report_ref, \ + NULL, &consumer_input), \ +BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_hids_consumer_input_report, NULL, NULL), \ +BT_GATT_CCC(input_ccc_changed, BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT), \ +BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ_ENCRYPT, read_hids_report_ref, \ + NULL, &consumer_input), \ +BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_hids_consumer_input_report, NULL, NULL), \ +BT_GATT_CCC(input_ccc_changed, BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT), \ +BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ_ENCRYPT, read_hids_report_ref, \ + NULL, &consumer_input),*/ BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_CTRL_POINT, BT_GATT_CHRC_WRITE_WITHOUT_RESP, BT_GATT_PERM_WRITE, NULL, write_ctrl_point, &ctrl_point)); @@ -261,6 +358,78 @@ int zmk_hog_send_consumer_report(struct zmk_hid_consumer_report_body *report) { return 0; }; +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD) +K_MSGQ_DEFINE(zmk_hog_ptp_msgq, sizeof(struct zmk_hid_ptp_report_body), + CONFIG_ZMK_BLE_PTP_REPORT_QUEUE_SIZE, 4); + +void send_ptp_report_callback(struct k_work *work) { + struct zmk_hid_ptp_report_body report; + while (k_msgq_get(&zmk_hog_ptp_msgq, &report, K_NO_WAIT) == 0) { + struct bt_conn *conn = destination_connection(); + if (conn == NULL) { + return; + } + + struct bt_gatt_notify_params notify_params = { + .attr = &hog_svc.attrs[13], + .data = &report, + .len = sizeof(report), + }; + + int err = bt_gatt_notify_cb(conn, ¬ify_params); + if (err) { + LOG_DBG("Error notifying %d", err); + } + + bt_conn_unref(conn); + } +}; + +K_WORK_DEFINE(hog_ptp_work, send_ptp_report_callback); + +int zmk_hog_send_ptp_report(struct zmk_hid_ptp_report_body *report) { + int err = k_msgq_put(&zmk_hog_ptp_msgq, report, K_NO_WAIT); + if (err) { + switch (err) { + case -EAGAIN: { + LOG_WRN("PTP message queue full, dropping report"); + return err; + } + default: + LOG_WRN("Failed to queue ptp report to send (%d)", err); + return err; + } + } + + k_work_submit_to_queue(&hog_work_q, &hog_ptp_work); + + return 0; +}; + +int zmk_hog_send_ptp_report_direct(struct zmk_hid_ptp_report_body *report) { + struct bt_conn *conn = destination_connection(); + if (conn == NULL) { + return 1; + } + + struct bt_gatt_notify_params notify_params = { + .attr = &hog_svc.attrs[13], + .data = report, + .len = sizeof(*report), + }; + + int err = bt_gatt_notify_cb(conn, ¬ify_params); + if (err) { + LOG_DBG("Error notifying %d", err); + return err; + } + + bt_conn_unref(conn); + + return 0; +}; +#endif + int zmk_hog_init(const struct device *_arg) { static const struct k_work_queue_config queue_config = {.name = "HID Over GATT Send Work"}; k_work_queue_start(&hog_work_q, hog_q_stack, K_THREAD_STACK_SIZEOF(hog_q_stack), diff --git a/app/src/trackpad.c b/app/src/trackpad.c new file mode 100644 index 000000000000..c2d0842bb834 --- /dev/null +++ b/app/src/trackpad.c @@ -0,0 +1,97 @@ +#include +#include +#include + +#include "zmk/trackpad.h" +#include "zmk/hid.h" +#include "zmk/endpoints.h" +// #include "drivers/sensor/gen4.h" //Definitions for custom sensor channels used + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +const struct device *trackpad = DEVICE_DT_GET(DT_INST(0, cirque_gen4)); + +static zmk_trackpad_finger_contacts_t present_contacts = 0; +static zmk_trackpad_finger_contacts_t contacts_to_send = 0; + +static struct zmk_ptp_finger fingers[CONFIG_ZMK_TRACKPAD_MAX_FINGERS]; + +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD_WORK_QUEUE_DEDICATED) +K_THREAD_STACK_DEFINE(trackpad_work_stack_area, CONFIG_ZMK_TRACKPAD_DEDICATED_THREAD_STACK_SIZE); +static struct k_work_q trackpad_work_q; +#endif + +struct k_work_q *zmk_trackpad_work_q() { +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD_WORK_QUEUE_DEDICATED) + return &trackpad_work_q; +#else + return &k_sys_work_q; +#endif +} + +static void handle_trackpad(const struct device *dev, const struct sensor_trigger *trig) { + int ret = sensor_sample_fetch(dev); + if (ret < 0) { + LOG_ERR("fetch: %d", ret); + return; + } + LOG_DBG("Trackpad handler trigd %d", 0); + + struct sensor_value contacts, confidence_tip, id, x, y; + sensor_channel_get(dev, SENSOR_CHAN_CONTACTS, &contacts); + // expects bitmap format + present_contacts = contacts.val1; + // released Fingers + sensor_channel_get(dev, SENSOR_CHAN_X, &x); + sensor_channel_get(dev, SENSOR_CHAN_Y, &y); + sensor_channel_get(dev, SENSOR_CHAN_CONFIDENCE_TIP, &confidence_tip); + sensor_channel_get(dev, SENSOR_CHAN_FINGER, &id); + // If finger has changed + fingers[id.val1].confidence_tip = confidence_tip.val1; + fingers[id.val1].contact_id = id.val1; + fingers[id.val1].x = x.val1; + fingers[id.val1].y = y.val1; + contacts_to_send |= BIT(id.val1); +} + +static void zmk_trackpad_tick(struct k_work *work) { + if (contacts_to_send) { + // LOG_DBG("Trackpad sendy thing trigd %d", 0); + for (int i = 0; i < CONFIG_ZMK_TRACKPAD_MAX_FINGERS; i++) + if (contacts_to_send & BIT(i)) { + LOG_DBG("Trackpad sendy thing trigd %d", i); + zmk_hid_ptp_set(fingers[i], present_contacts); + zmk_endpoints_send_ptp_report(); + contacts_to_send &= !BIT(i); + return; + } + } +} + +K_WORK_DEFINE(trackpad_work, zmk_trackpad_tick); + +static void zmk_trackpad_tick_handler(struct k_timer *timer) { + k_work_submit_to_queue(zmk_trackpad_work_q(), &trackpad_work); +} + +K_TIMER_DEFINE(trackpad_tick, zmk_trackpad_tick_handler, NULL); + +static int trackpad_init() { + struct sensor_trigger trigger = { + .type = SENSOR_TRIG_DATA_READY, + .chan = SENSOR_CHAN_ALL, + }; + if (sensor_trigger_set(trackpad, &trigger, handle_trackpad) < 0) { + LOG_ERR("can't set trigger"); + return -EIO; + }; + k_timer_start(&trackpad_tick, K_NO_WAIT, K_MSEC(CONFIG_ZMK_TRACKPAD_TICK_DURATION)); +#if IS_ENABLED(CONFIG_ZMK_TRACKPAD_WORK_QUEUE_DEDICATED) + k_work_queue_start(&trackpad_work_q, trackpad_work_stack_area, + K_THREAD_STACK_SIZEOF(trackpad_work_stack_area), + CONFIG_ZMK_TRACKPAD_DEDICATED_THREAD_PRIORITY, NULL); +#endif + return 0; +} + +SYS_INIT(trackpad_init, APPLICATION, CONFIG_ZMK_KSCAN_INIT_PRIORITY); \ No newline at end of file