[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" int "Max number of mouse HID reports to queue for sending over BLE"
default 20 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 config ZMK_BLE_CLEAR_BONDS_ON_START
bool "Configuration that clears all bond information from the keyboard on startup." bool "Configuration that clears all bond information from the keyboard on startup."
@ -375,6 +379,14 @@ config ZMK_MOUSE
#Mouse Options #Mouse Options
endmenu endmenu
menu "Joystick Options"
config ZMK_JOYSTICK
bool "Enable ZMK joystick emulation"
#Mouse Options
endmenu
menu "Power Management" menu "Power Management"
config ZMK_BATTERY_REPORTING 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) #if IS_ENABLED(CONFIG_ZMK_MOUSE)
int zmk_endpoints_send_mouse_report(); int zmk_endpoints_send_mouse_report();
#endif // IS_ENABLE(CONFIG_ZMK_MOUSE) #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_LEDS 0x01
#define ZMK_HID_REPORT_ID_CONSUMER 0x02 #define ZMK_HID_REPORT_ID_CONSUMER 0x02
#define ZMK_HID_REPORT_ID_MOUSE 0x03 #define ZMK_HID_REPORT_ID_MOUSE 0x03
#define ZMK_HID_REPORT_ID_JOYSTICK 0x04
static const uint8_t zmk_hid_report_desc[] = { static const uint8_t zmk_hid_report_desc[] = {
HID_USAGE_PAGE(HID_USAGE_GEN_DESKTOP), 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,
HID_END_COLLECTION, HID_END_COLLECTION,
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) #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) #if IS_ENABLED(CONFIG_ZMK_USB_BOOT)
@ -252,6 +270,19 @@ struct zmk_hid_mouse_report {
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) #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); zmk_mod_flags_t zmk_hid_get_explicit_mods(void);
int zmk_hid_register_mod(zmk_mod_t modifier); int zmk_hid_register_mod(zmk_mod_t modifier);
int zmk_hid_unregister_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); void zmk_hid_mouse_clear(void);
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) #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_keyboard_report *zmk_hid_get_keyboard_report(void);
struct zmk_hid_consumer_report *zmk_hid_get_consumer_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) #if IS_ENABLED(CONFIG_ZMK_MOUSE)
struct zmk_hid_mouse_report *zmk_hid_get_mouse_report(); struct zmk_hid_mouse_report *zmk_hid_get_mouse_report();
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) #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) #if IS_ENABLED(CONFIG_ZMK_MOUSE)
int zmk_hog_send_mouse_report(struct zmk_hid_mouse_report_body *body); int zmk_hog_send_mouse_report(struct zmk_hid_mouse_report_body *body);
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) #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) #if IS_ENABLED(CONFIG_ZMK_MOUSE)
int zmk_usb_hid_send_mouse_report(void); int zmk_usb_hid_send_mouse_report(void);
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) #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); 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); return zmk_endpoints_select_transport(new_transport);
} }
struct zmk_endpoint_instance zmk_endpoints_selected(void) { struct zmk_endpoint_instance zmk_endpoints_selected(void) { return current_instance; }
return current_instance;
}
static int send_keyboard_report(void) { static int send_keyboard_report(void) {
switch (current_instance.transport) { switch (current_instance.transport) {
@ -239,6 +237,42 @@ int zmk_endpoints_send_mouse_report() {
} }
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) #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) #if IS_ENABLED(CONFIG_SETTINGS)
static int endpoints_handle_set(const char *name, size_t len, settings_read_cb read_cb, 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) #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. // Keep track of how often a modifier was pressed.
// Only release the modifier if the count is 0. // Only release the modifier if the count is 0.
static int explicit_modifier_counts[8] = {0, 0, 0, 0, 0, 0, 0, 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) #endif // IS_ENABLED(CONFIG_ZMK_MOUSE)
struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(void) { #if IS_ENABLED(CONFIG_ZMK_JOYSTICK)
return &keyboard_report; 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) { void zmk_hid_joystick_clear(void) {
return &consumer_report; 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) #if IS_ENABLED(CONFIG_ZMK_MOUSE)
struct zmk_hid_mouse_report *zmk_hid_get_mouse_report(void) { struct zmk_hid_mouse_report *zmk_hid_get_mouse_report(void) { return &mouse_report; }
return &mouse_report;
}
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) #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) #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 bool host_requests_notification = false;
static uint8_t ctrl_point; static uint8_t ctrl_point;
// static uint8_t proto_mode; // 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) #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, // static ssize_t write_proto_mode(struct bt_conn *conn,
// const struct bt_gatt_attr *attr, // const struct bt_gatt_attr *attr,
// const void *buf, uint16_t len, uint16_t offset, // const void *buf, uint16_t len, uint16_t offset,
@ -208,6 +227,14 @@ BT_GATT_SERVICE_DEFINE(
NULL, &mouse_input), NULL, &mouse_input),
#endif // IS_ENABLED(CONFIG_ZMK_MOUSE) #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) #if IS_ENABLED(CONFIG_ZMK_HID_INDICATORS)
BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT, BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP, 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) #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 int zmk_hog_init(void) {
static const struct k_work_queue_config queue_config = {.name = "HID Over GATT Send Work"}; 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), 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) #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) { static int zmk_usb_hid_init(void) {
hid_dev = device_get_binding("HID_0"); hid_dev = device_get_binding("HID_0");
if (hid_dev == NULL) { if (hid_dev == NULL) {