[feat] Support for acting as a HID joystick

This commit is contained in:
Adrien Friggeri 2024-02-26 14:19:35 +00:00
parent 2adaa00d10
commit 0b11572391
9 changed files with 220 additions and 11 deletions

View file

@ -213,6 +213,10 @@ config ZMK_BLE_MOUSE_REPORT_QUEUE_SIZE
int "Max number of mouse HID reports to queue for sending over BLE"
default 20
config ZMK_BLE_JOYSTICK_REPORT_QUEUE_SIZE
int "Max number of joystick 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."
@ -375,6 +379,14 @@ config ZMK_MOUSE
#Mouse Options
endmenu
menu "Joystick Options"
config ZMK_JOYSTICK
bool "Enable ZMK joystick emulation"
#Mouse Options
endmenu
menu "Power Management"
config ZMK_BATTERY_REPORTING

View file

@ -73,3 +73,7 @@ int zmk_endpoints_send_report(uint16_t usage_page);
#if IS_ENABLED(CONFIG_ZMK_MOUSE)
int zmk_endpoints_send_mouse_report();
#endif // IS_ENABLE(CONFIG_ZMK_MOUSE)
#if IS_ENABLED(CONFIG_ZMK_JOYSTICK)
int zmk_endpoints_send_joystick_report();
#endif // IS_ENABLE(CONFIG_ZMK_JOYSTICK)

View file

@ -58,6 +58,7 @@
#define ZMK_HID_REPORT_ID_LEDS 0x01
#define ZMK_HID_REPORT_ID_CONSUMER 0x02
#define ZMK_HID_REPORT_ID_MOUSE 0x03
#define ZMK_HID_REPORT_ID_JOYSTICK 0x04
static const uint8_t zmk_hid_report_desc[] = {
HID_USAGE_PAGE(HID_USAGE_GEN_DESKTOP),
@ -175,6 +176,23 @@ static const uint8_t zmk_hid_report_desc[] = {
HID_END_COLLECTION,
HID_END_COLLECTION,
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE)
#if IS_ENABLED(CONFIG_ZMK_JOYSTICK)
HID_USAGE_PAGE(HID_USAGE_GD),
HID_USAGE(HID_USAGE_GD_JOYSTICK),
HID_COLLECTION(HID_COLLECTION_APPLICATION),
HID_REPORT_ID(ZMK_HID_REPORT_ID_JOYSTICK),
HID_COLLECTION(HID_COLLECTION_LOGICAL),
HID_USAGE(HID_USAGE_GD_X),
HID_USAGE(HID_USAGE_GD_Y),
HID_USAGE(HID_USAGE_GD_Z),
HID_LOGICAL_MIN8(-0x7F),
HID_LOGICAL_MAX8(0x7F),
HID_REPORT_SIZE(0x08),
HID_REPORT_COUNT(0x03),
HID_INPUT(ZMK_HID_MAIN_VAL_DATA | ZMK_HID_MAIN_VAL_VAR | ZMK_HID_MAIN_VAL_ABS),
HID_END_COLLECTION,
HID_END_COLLECTION,
#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK)
};
#if IS_ENABLED(CONFIG_ZMK_USB_BOOT)
@ -252,6 +270,19 @@ struct zmk_hid_mouse_report {
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE)
#if IS_ENABLED(CONFIG_ZMK_JOYSTICK)
struct zmk_hid_joystick_report_body {
int8_t x;
int8_t y;
int8_t z;
} __packed;
struct zmk_hid_joystick_report {
uint8_t report_id;
struct zmk_hid_joystick_report_body body;
} __packed;
#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK)
zmk_mod_flags_t zmk_hid_get_explicit_mods(void);
int zmk_hid_register_mod(zmk_mod_t modifier);
int zmk_hid_unregister_mod(zmk_mod_t modifier);
@ -286,6 +317,11 @@ int zmk_hid_mouse_buttons_release(zmk_mouse_button_flags_t buttons);
void zmk_hid_mouse_clear(void);
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE)
#if IS_ENABLED(CONFIG_ZMK_JOYSTICK)
void zmk_hid_joystick_set(uint8_t x, uint8_t y, uint8_t z);
void zmk_hid_joystick_clear(void);
#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK)
struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(void);
struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(void);
@ -296,3 +332,7 @@ zmk_hid_boot_report_t *zmk_hid_get_boot_report();
#if IS_ENABLED(CONFIG_ZMK_MOUSE)
struct zmk_hid_mouse_report *zmk_hid_get_mouse_report();
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE)
#if IS_ENABLED(CONFIG_ZMK_JOYSTICK)
struct zmk_hid_joystick_report *zmk_hid_get_joystick_report();
#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK)

View file

@ -15,3 +15,7 @@ int zmk_hog_send_consumer_report(struct zmk_hid_consumer_report_body *body);
#if IS_ENABLED(CONFIG_ZMK_MOUSE)
int zmk_hog_send_mouse_report(struct zmk_hid_mouse_report_body *body);
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE)
#if IS_ENABLED(CONFIG_ZMK_JOYSTICK)
int zmk_hog_send_joystick_report(struct zmk_hid_joystick_report_body *body);
#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK)

View file

@ -13,4 +13,7 @@ int zmk_usb_hid_send_consumer_report(void);
#if IS_ENABLED(CONFIG_ZMK_MOUSE)
int zmk_usb_hid_send_mouse_report(void);
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE)
#if IS_ENABLED(CONFIG_ZMK_JOYSTICK)
int zmk_usb_hid_send_joystick_report(void);
#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK)
void zmk_usb_hid_set_protocol(uint8_t protocol);

View file

@ -116,9 +116,7 @@ int zmk_endpoints_toggle_transport(void) {
return zmk_endpoints_select_transport(new_transport);
}
struct zmk_endpoint_instance zmk_endpoints_selected(void) {
return current_instance;
}
struct zmk_endpoint_instance zmk_endpoints_selected(void) { return current_instance; }
static int send_keyboard_report(void) {
switch (current_instance.transport) {
@ -239,6 +237,42 @@ int zmk_endpoints_send_mouse_report() {
}
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE)
#if IS_ENABLED(CONFIG_ZMK_JOYSTICK)
int zmk_endpoints_send_joystick_report() {
switch (current_instance.transport) {
case ZMK_TRANSPORT_USB: {
#if IS_ENABLED(CONFIG_ZMK_USB)
int err = zmk_usb_hid_send_joystick_report();
if (err) {
LOG_ERR("FAILED TO SEND OVER USB: %d", err);
}
return err;
#else
LOG_ERR("USB endpoint is not supported");
return -ENOTSUP;
#endif /* IS_ENABLED(CONFIG_ZMK_USB) */
}
case ZMK_TRANSPORT_BLE: {
#if IS_ENABLED(CONFIG_ZMK_BLE)
struct zmk_hid_joystick_report *joystick_report = zmk_hid_get_joystick_report();
int err = zmk_hog_send_joystick_report(&joystick_report->body);
if (err) {
LOG_ERR("FAILED TO SEND OVER HOG: %d", err);
}
return err;
#else
LOG_ERR("BLE HOG endpoint is not supported");
return -ENOTSUP;
#endif /* IS_ENABLED(CONFIG_ZMK_BLE) */
}
}
LOG_ERR("Unhandled endpoint transport %d", current_instance.transport);
return -ENOTSUP;
}
#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK)
#if IS_ENABLED(CONFIG_SETTINGS)
static int endpoints_handle_set(const char *name, size_t len, settings_read_cb read_cb,

View file

@ -32,6 +32,13 @@ static struct zmk_hid_mouse_report mouse_report = {.report_id = ZMK_HID_REPORT_I
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE)
#if IS_ENABLED(CONFIG_ZMK_JOYSTICK)
static struct zmk_hid_joystick_report joystick_report = {.report_id = ZMK_HID_REPORT_ID_JOYSTICK,
.body = {.x = 0, .y = 0, .z = 0}};
#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK)
// 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};
@ -434,18 +441,28 @@ void zmk_hid_mouse_clear(void) { memset(&mouse_report.body, 0, sizeof(mouse_repo
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE)
struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(void) {
return &keyboard_report;
}
#if IS_ENABLED(CONFIG_ZMK_JOYSTICK)
void zmk_hid_joystick_set(uint8_t x, uint8_t y, uint8_t z) {
joystick_report.body.x = x;
joystick_report.body.y = y;
joystick_report.body.z = z;
};
struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(void) {
return &consumer_report;
void zmk_hid_joystick_clear(void) {
memset(&joystick_report.body, 0, sizeof(joystick_report.body));
}
#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK)
struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(void) { return &keyboard_report; }
struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(void) { return &consumer_report; }
#if IS_ENABLED(CONFIG_ZMK_MOUSE)
struct zmk_hid_mouse_report *zmk_hid_get_mouse_report(void) {
return &mouse_report;
}
struct zmk_hid_mouse_report *zmk_hid_get_mouse_report(void) { return &mouse_report; }
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE)
#if IS_ENABLED(CONFIG_ZMK_JOYSTICK)
struct zmk_hid_joystick_report *zmk_hid_get_joystick_report(void) { return &joystick_report; };
#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK)

View file

@ -78,6 +78,15 @@ static struct hids_report mouse_input = {
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE)
#if IS_ENABLED(CONFIG_ZMK_JOYSTICK)
static struct hids_report joystick_input = {
.id = ZMK_HID_REPORT_ID_JOYSTICK,
.type = HIDS_INPUT,
};
#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK)
static bool host_requests_notification = false;
static uint8_t ctrl_point;
// static uint8_t proto_mode;
@ -152,6 +161,16 @@ static ssize_t read_hids_mouse_input_report(struct bt_conn *conn, const struct b
}
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE)
#if IS_ENABLED(CONFIG_ZMK_JOYSTICK)
static ssize_t read_hids_joystick_input_report(struct bt_conn *conn,
const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset) {
struct zmk_hid_joystick_report_body *report_body = &zmk_hid_get_joystick_report()->body;
return bt_gatt_attr_read(conn, attr, buf, len, offset, report_body,
sizeof(struct zmk_hid_joystick_report_body));
}
#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK)
// 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,
@ -208,6 +227,14 @@ BT_GATT_SERVICE_DEFINE(
NULL, &mouse_input),
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE)
#if IS_ENABLED(CONFIG_ZMK_JOYSTICK)
BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_READ_ENCRYPT, read_hids_joystick_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, &joystick_input),
#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK)
#if IS_ENABLED(CONFIG_ZMK_HID_INDICATORS)
BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP,
@ -398,6 +425,61 @@ int zmk_hog_send_mouse_report(struct zmk_hid_mouse_report_body *report) {
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE)
#if IS_ENABLED(CONFIG_ZMK_JOYSTICK)
K_MSGQ_DEFINE(zmk_hog_joystick_msgq, sizeof(struct zmk_hid_joystick_report_body),
CONFIG_ZMK_BLE_JOYSTICK_REPORT_QUEUE_SIZE, 4);
void send_joystick_report_callback(struct k_work *work) {
struct zmk_hid_joystick_report_body report;
while (k_msgq_get(&zmk_hog_joystick_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, &notify_params);
if (err == -EPERM) {
bt_conn_set_security(conn, BT_SECURITY_L2);
} else if (err) {
LOG_DBG("Error notifying %d", err);
}
bt_conn_unref(conn);
}
};
K_WORK_DEFINE(hog_joystick_work, send_joystick_report_callback);
int zmk_hog_send_joystick_report(struct zmk_hid_joystick_report_body *report) {
int err = k_msgq_put(&zmk_hog_joystick_msgq, report, K_MSEC(100));
if (err) {
switch (err) {
case -EAGAIN: {
LOG_WRN("Consumer message queue full, popping first message and queueing again");
struct zmk_hid_joystick_report_body discarded_report;
k_msgq_get(&zmk_hog_joystick_msgq, &discarded_report, K_NO_WAIT);
return zmk_hog_send_joystick_report(report);
}
default:
LOG_WRN("Failed to queue joystick report to send (%d)", err);
return err;
}
}
k_work_submit_to_queue(&hog_work_q, &hog_joystick_work);
return 0;
};
#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK)
static int zmk_hog_init(void) {
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),

View file

@ -177,6 +177,19 @@ int zmk_usb_hid_send_mouse_report() {
}
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE)
#if IS_ENABLED(CONFIG_ZMK_JOYSTICK)
int zmk_usb_hid_send_joystick_report() {
#if IS_ENABLED(CONFIG_ZMK_USB_BOOT)
if (hid_protocol == HID_PROTOCOL_BOOT) {
return -ENOTSUP;
}
#endif /* IS_ENABLED(CONFIG_ZMK_USB_BOOT) */
struct zmk_hid_joystick_report *report = zmk_hid_get_joystick_report();
return zmk_usb_hid_send_report((uint8_t *)report, sizeof(*report));
}
#endif // IS_ENABLED(CONFIG_ZMK_JOYSTICK)
static int zmk_usb_hid_init(void) {
hid_dev = device_get_binding("HID_0");
if (hid_dev == NULL) {