From d8d8b96992c509408ed305a427265f7d0759288b Mon Sep 17 00:00:00 2001 From: Alexander Krikun Date: Tue, 27 Apr 2021 18:24:11 +0300 Subject: [PATCH] Preliminary work for mouse click --- app/Kconfig | 4 ++ app/include/dt-bindings/zmk/mouse.h | 9 ++++ app/include/zmk/hid.h | 46 +++++++++++++++++++ app/include/zmk/hog.h | 1 + app/include/zmk/mouse.h | 13 ++++++ app/src/endpoints.c | 38 ++++++++++++++- app/src/hid.c | 62 +++++++++++++++++++++++++ app/src/hid_listener.c | 15 ++++++ app/src/hog.c | 71 +++++++++++++++++++++++++++++ 9 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 app/include/dt-bindings/zmk/mouse.h create mode 100644 app/include/zmk/mouse.h diff --git a/app/Kconfig b/app/Kconfig index 8817d506..60423891 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -124,6 +124,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_MOUSE_REPORT_QUEUE_SIZE + int "Max number of mouse HID reports to queue for sending over BLE" + default 5 + config ZMK_BLE_CLEAR_BONDS_ON_START bool "Configuration that clears all bond information from the keyboard on startup." default n diff --git a/app/include/dt-bindings/zmk/mouse.h b/app/include/dt-bindings/zmk/mouse.h new file mode 100644 index 00000000..0e0e6341 --- /dev/null +++ b/app/include/dt-bindings/zmk/mouse.h @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ +#pragma once + +#include +#include \ No newline at end of file diff --git a/app/include/zmk/hid.h b/app/include/zmk/hid.h index 95b82d46..65836d28 100644 --- a/app/include/zmk/hid.h +++ b/app/include/zmk/hid.h @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -183,6 +184,33 @@ static const uint8_t zmk_hid_report_desc[] = { 0x00, /* END COLLECTION */ HID_MI_COLLECTION_END, + + 0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */ + 0x09, 0x02, /* Usage (Mouse) */ + 0xA1, 0x01, /* Collection (Application) */ + 0x09, 0x01, /* Usage (Pointer) */ + 0xA1, 0x00, /* Collection (Physical) */ + 0x05, 0x09, /* Usage Page (Button) */ + 0x19, 0x01, /* Usage Minimum (0x01) */ + 0x29, 0x03, /* Usage Maximum (0x03) */ + 0x15, 0x00, /* Logical Minimum (0) */ + 0x25, 0x01, /* Logical Maximum (1) */ + 0x95, 0x03, /* Report Count (3) */ + 0x75, 0x01, /* Report Size (1) */ + 0x81, 0x02, /* Input (Data,Var,Abs,No Wrap,Linear,...) */ + 0x95, 0x01, /* Report Count (1) */ + 0x75, 0x05, /* Report Size (5) */ + 0x81, 0x03, /* Input (Const,Var,Abs,No Wrap,Linear,...) */ + 0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */ + 0x09, 0x30, /* Usage (X) */ + 0x09, 0x31, /* Usage (Y) */ + 0x15, 0x81, /* Logical Minimum (129) */ + 0x25, 0x7F, /* Logical Maximum (127) */ + 0x75, 0x08, /* Report Size (8) */ + 0x95, 0x02, /* Report Count (2) */ + 0x81, 0x06, /* Input (Data,Var,Rel,No Wrap,Linear,...) */ + 0xC0, /* End Collection */ + 0xC0, /* End Collection */ }; // struct zmk_hid_boot_report @@ -220,6 +248,17 @@ struct zmk_hid_consumer_report { struct zmk_hid_consumer_report_body body; } __packed; +struct zmk_hid_mouse_report_body { + zmk_mouse_button_flags_t buttons; + int8_t x; + int8_t y; +} __packed; + +struct zmk_hid_mouse_report { + uint8_t report_id; + struct zmk_hid_mouse_report_body body; +} __packed; + 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); @@ -235,5 +274,12 @@ int zmk_hid_consumer_press(zmk_key_t key); int zmk_hid_consumer_release(zmk_key_t key); void zmk_hid_consumer_clear(); +int zmk_hid_mouse_button_press(zmk_mouse_button_t button); +int zmk_hid_mouse_button_release(zmk_mouse_button_t button); +int zmk_hid_mouse_buttons_press(zmk_mouse_button_flags_t buttons); +int zmk_hid_mouse_buttons_release(zmk_mouse_button_flags_t buttons); +void zmk_hid_mouse_clear(); + struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(); struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(); +struct zmk_hid_mouse_report *zmk_hid_get_mouse_report(); diff --git a/app/include/zmk/hog.h b/app/include/zmk/hog.h index 7523fb66..9debc3ff 100644 --- a/app/include/zmk/hog.h +++ b/app/include/zmk/hog.h @@ -13,3 +13,4 @@ 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); +int zmk_hog_send_mouse_report(struct zmk_hid_mouse_report_body *body); diff --git a/app/include/zmk/mouse.h b/app/include/zmk/mouse.h new file mode 100644 index 00000000..0d4bcb61 --- /dev/null +++ b/app/include/zmk/mouse.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include + +typedef uint8_t zmk_mouse_button_flags_t; +typedef uint8_t zmk_mouse_button_t; \ No newline at end of file diff --git a/app/src/endpoints.c b/app/src/endpoints.c index c15aad87..c4b4e235 100644 --- a/app/src/endpoints.c +++ b/app/src/endpoints.c @@ -106,7 +106,7 @@ static int send_consumer_report() { switch (current_endpoint) { #if IS_ENABLED(CONFIG_ZMK_USB) - case ZMK_ENDPOINT_USB: { + case ZMK_ENDPOINT_USB: { int err = zmk_usb_hid_send_report((uint8_t *)consumer_report, sizeof(*consumer_report)); if (err) { LOG_ERR("FAILED TO SEND OVER USB: %d", err); @@ -116,7 +116,7 @@ static int send_consumer_report() { #endif /* IS_ENABLED(CONFIG_ZMK_USB) */ #if IS_ENABLED(CONFIG_ZMK_BLE) - case ZMK_ENDPOINT_BLE: { + case ZMK_ENDPOINT_BLE: { int err = zmk_hog_send_consumer_report(&consumer_report->body); if (err) { LOG_ERR("FAILED TO SEND OVER HOG: %d", err); @@ -131,6 +131,36 @@ static int send_consumer_report() { } } +static int send_mouse_report() { + struct zmk_hid_mouse_report *mouse_report = zmk_hid_get_mouse_report(); + + switch (current_endpoint) { +#if IS_ENABLED(CONFIG_ZMK_USB) + case ZMK_ENDPOINT_USB: { + int err = zmk_usb_hid_send_report((uint8_t *)mouse_report, sizeof(*mouse_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: { + int err = zmk_hog_send_mouse_report(&mouse_report->body); + 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; + } +} + int zmk_endpoints_send_report(uint16_t usage_page) { LOG_DBG("usage page 0x%02X", usage_page); @@ -139,6 +169,8 @@ int zmk_endpoints_send_report(uint16_t usage_page) { return send_keyboard_report(); case HID_USAGE_CONSUMER: return send_consumer_report(); + case HID_USAGE_GD: + return send_mouse_report(); default: LOG_ERR("Unsupported usage page %d", usage_page); return -ENOTSUP; @@ -229,9 +261,11 @@ static enum zmk_endpoint get_selected_endpoint() { static void disconnect_current_endpoint() { zmk_hid_keyboard_clear(); zmk_hid_consumer_clear(); + zmk_hid_mouse_clear(); zmk_endpoints_send_report(HID_USAGE_KEY); zmk_endpoints_send_report(HID_USAGE_CONSUMER); + zmk_endpoints_send_report(HID_USAGE_GD); } static void update_current_endpoint() { diff --git a/app/src/hid.c b/app/src/hid.c index b524b09f..4a2f2b3d 100644 --- a/app/src/hid.c +++ b/app/src/hid.c @@ -15,6 +15,9 @@ static struct zmk_hid_keyboard_report keyboard_report = { static struct zmk_hid_consumer_report consumer_report = {.report_id = 2, .body = {.keys = {0}}}; +static struct zmk_hid_mouse_report mouse_report = {.report_id = 3, .body = { + .buttons = 0, .x = 0, .y = 0}}; + // 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}; @@ -169,6 +172,61 @@ int zmk_hid_consumer_release(zmk_key_t code) { void zmk_hid_consumer_clear() { memset(&consumer_report.body, 0, sizeof(consumer_report.body)); } + +// Keep track of how often a button was pressed. +// Only release the button if the count is 0. +static int explicit_button_counts[3] = {0, 0, 0}; +static zmk_mod_flags_t explicit_buttons = 0; + +#define SET_MOUSE_BUTTONS(butts) \ + { \ + mouse_report.body.buttons = butts; \ + LOG_DBG("Mouse buttons set to 0x%02X", mouse_report.body.buttons); \ + } + +int zmk_hid_mouse_button_press(zmk_mouse_button_t button) { + explicit_button_counts[button-5]++; + LOG_DBG("Button %d count %d", button, explicit_button_counts[button-5]); + WRITE_BIT(explicit_buttons, button, true); + SET_MOUSE_BUTTONS(explicit_buttons); + return 0; +} + +int zmk_hid_mouse_button_release(zmk_mouse_button_t button) { + if (explicit_button_counts[button-5] <= 0) { + LOG_ERR("Tried to release button %d too often", button); + return -EINVAL; + } + explicit_button_counts[button]--; + LOG_DBG("Button %d count: %d", button, explicit_button_counts[button-5]); + if (explicit_button_counts[button-5] == 0) { + LOG_DBG("Button %d released", button); + WRITE_BIT(explicit_buttons, button, false); + } + SET_MOUSE_BUTTONS(explicit_buttons); + return 0; +} + +int zmk_hid_mouse_buttons_press(zmk_mouse_button_flags_t buttons) { + for (zmk_mod_t i = 5; i < 8; i++) { + if (buttons & (1 << i)) { + zmk_hid_mouse_button_press(i); + } + } + return 0; +} + +int zmk_hid_mouse_buttons_release(zmk_mouse_button_flags_t buttons) { + for (zmk_mod_t i = 5; i < 8; i++) { + if (buttons & (1 << i)) { + zmk_hid_mouse_button_release(i); + } + } + return 0; +} + +void zmk_hid_mouse_clear() { memset(&mouse_report.body, 0, sizeof(mouse_report.body)); } + struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report() { return &keyboard_report; } @@ -176,3 +234,7 @@ struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report() { struct zmk_hid_consumer_report *zmk_hid_get_consumer_report() { return &consumer_report; } + +struct zmk_hid_mouse_report *zmk_hid_get_mouse_report() { + return &mouse_report; +} diff --git a/app/src/hid_listener.c b/app/src/hid_listener.c index d582c169..e3c91215 100644 --- a/app/src/hid_listener.c +++ b/app/src/hid_listener.c @@ -35,6 +35,13 @@ static int hid_listener_keycode_pressed(const struct zmk_keycode_state_changed * return err; } break; + case HID_USAGE_GD: + err = zmk_hid_mouse_buttons_press(ev->keycode); + if (err) { + LOG_ERR("Unable to press button"); + return err; + } + break; } zmk_hid_register_mods(ev->explicit_modifiers); zmk_hid_implicit_modifiers_press(ev->implicit_modifiers); @@ -59,6 +66,14 @@ static int hid_listener_keycode_released(const struct zmk_keycode_state_changed LOG_ERR("Unable to release keycode"); return err; } + break; + case HID_USAGE_GD: + err = zmk_hid_mouse_buttons_release(ev->keycode); + if (err) { + LOG_ERR("Unable to release button"); + return err; + } + break; } zmk_hid_unregister_mods(ev->explicit_modifiers); // There is a minor issue with this code. diff --git a/app/src/hog.c b/app/src/hog.c index e8aceca3..8b285bf1 100644 --- a/app/src/hog.c +++ b/app/src/hog.c @@ -56,6 +56,11 @@ static struct hids_report consumer_input = { .type = HIDS_INPUT, }; +static struct hids_report mouse_input = { + .id = 0x03, + .type = HIDS_INPUT, +}; + static bool host_requests_notification = false; static uint8_t ctrl_point; // static uint8_t proto_mode; @@ -93,6 +98,14 @@ static ssize_t read_hids_consumer_input_report(struct bt_conn *conn, sizeof(struct zmk_hid_consumer_report_body)); } +static ssize_t read_hids_mouse_input_report(struct bt_conn *conn, + const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) { + struct zmk_hid_mouse_report_body *report_body = &zmk_hid_get_mouse_report()->body; + return bt_gatt_attr_read(conn, attr, buf, len, offset, report_body, + sizeof(struct zmk_hid_mouse_report_body)); +} + // 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 +152,13 @@ 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), + + BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, + BT_GATT_PERM_READ_ENCRYPT, read_hids_mouse_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, &mouse_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 +281,57 @@ int zmk_hog_send_consumer_report(struct zmk_hid_consumer_report_body *report) { return 0; }; +K_MSGQ_DEFINE(zmk_hog_mouse_msgq, sizeof(struct zmk_hid_mouse_report_body), +CONFIG_ZMK_BLE_MOUSE_REPORT_QUEUE_SIZE, 4); + + +void send_mouse_report_callback(struct k_work *work) { + struct zmk_hid_mouse_report_body report; + + while (k_msgq_get(&zmk_hog_mouse_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[10], + .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_mouse_work, send_mouse_report_callback); + +int zmk_hog_send_mouse_report(struct zmk_hid_mouse_report_body *report) { + int err = k_msgq_put(&zmk_hog_mouse_msgq, report, K_MSEC(100)); + if (err) { + switch (err) { + case -EAGAIN: { + LOG_WRN("Mouse message queue full, popping first message and queueing again"); + struct zmk_hid_mouse_report_body discarded_report; + k_msgq_get(&zmk_hog_mouse_msgq, &discarded_report, K_NO_WAIT); + return zmk_hog_send_mouse_report(report); + } + default: + LOG_WRN("Failed to queue mouse report to send (%d)", err); + return err; + } + } + + k_work_submit_to_queue(&hog_work_q, &hog_mouse_work); + + return 0; +}; + int zmk_hog_init(const struct device *_arg) { k_work_q_start(&hog_work_q, hog_q_stack, K_THREAD_STACK_SIZEOF(hog_q_stack), CONFIG_ZMK_BLE_THREAD_PRIORITY);