diff --git a/app/Kconfig b/app/Kconfig index 9902046d..7106538b 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -139,6 +139,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_PLOVER_HID_REPORT_QUEUE_SIZE + int "Max number of plover 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 @@ -180,6 +184,14 @@ endmenu rsource "src/split/Kconfig" +menu "Stenography Support" + +config ZMK_PLOVER_HID + bool "Support for the Plover HID Stenography Protocol" + +# Stenography Support +endmenu + #Basic Keyboard Setup endmenu @@ -387,6 +399,7 @@ menu "KSCAN Settings" config ZMK_KSCAN_EVENT_QUEUE_SIZE int "Size of the event queue for KSCAN events to buffer events" default 4 + default 64 if ZMK_PLOVER_HID #KSCAN Settings endmenu diff --git a/app/include/dt-bindings/zmk/hid_usage_pages.h b/app/include/dt-bindings/zmk/hid_usage_pages.h index 2ccdba55..d0f45f0a 100644 --- a/app/include/dt-bindings/zmk/hid_usage_pages.h +++ b/app/include/dt-bindings/zmk/hid_usage_pages.h @@ -48,4 +48,5 @@ #define HID_USAGE_CAMERA (0x90) // Camera Control #define HID_USAGE_ARCADE (0x91) // Arcade #define HID_USAGE_GAMING (0x92) // Gaming Device -#define HID_USAGE_FIDO (0xF1D0) // FIDO Alliance \ No newline at end of file +#define HID_USAGE_FIDO (0xF1D0) // FIDO Alliance +#define HID_USAGE_VENDOR_PLOVER (0xFF50)// Plover HID vendor-defined usage page diff --git a/app/include/dt-bindings/zmk/keys.h b/app/include/dt-bindings/zmk/keys.h index 3e67c402..7a2d65fe 100644 --- a/app/include/dt-bindings/zmk/keys.h +++ b/app/include/dt-bindings/zmk/keys.h @@ -1439,3 +1439,68 @@ #define C_KEYBOARD_INPUT_ASSIST_CANCEL \ (ZMK_HID_USAGE(HID_USAGE_CONSUMER, HID_USAGE_CONSUMER_KEYBOARD_INPUT_ASSIST_CANCEL)) #define C_KBIA_CANCEL (C_KEYBOARD_INPUT_ASSIST_CANCEL) + +#define PLV_SL (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 0)) +#define PLV_TL (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 1)) +#define PLV_KL (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 2)) +#define PLV_PL (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 3)) +#define PLV_WL (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 4)) +#define PLV_HL (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 5)) +#define PLV_RL (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 6)) +#define PLV_A (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 7)) +#define PLV_O (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 8)) +#define PLV_ST (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 9)) +#define PLV_E (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 10)) +#define PLV_U (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 11)) +#define PLV_FR (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 12)) +#define PLV_RR (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 13)) +#define PLV_PR (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 14)) +#define PLV_BR (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 15)) +#define PLV_LR (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 16)) +#define PLV_GR (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 17)) +#define PLV_TR (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 18)) +#define PLV_SR (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 19)) +#define PLV_DR (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 20)) +#define PLV_ZR (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 21)) +#define PLV_NM (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 22)) +#define PLV_X1 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 23)) +#define PLV_X2 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 24)) +#define PLV_X3 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 25)) +#define PLV_X4 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 26)) +#define PLV_X5 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 27)) +#define PLV_X6 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 28)) +#define PLV_X7 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 29)) +#define PLV_X8 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 30)) +#define PLV_X9 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 31)) +#define PLV_X10 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 32)) +#define PLV_X11 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 33)) +#define PLV_X12 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 34)) +#define PLV_X13 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 35)) +#define PLV_X14 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 36)) +#define PLV_X15 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 37)) +#define PLV_X16 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 38)) +#define PLV_X17 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 39)) +#define PLV_X18 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 40)) +#define PLV_X19 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 41)) +#define PLV_X20 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 42)) +#define PLV_X21 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 43)) +#define PLV_X22 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 44)) +#define PLV_X23 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 45)) +#define PLV_X24 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 46)) +#define PLV_X25 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 47)) +#define PLV_X26 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 48)) +#define PLV_X27 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 49)) +#define PLV_X28 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 50)) +#define PLV_X29 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 51)) +#define PLV_X30 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 52)) +#define PLV_X31 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 53)) +#define PLV_X32 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 54)) +#define PLV_X33 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 55)) +#define PLV_X34 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 56)) +#define PLV_X35 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 57)) +#define PLV_X36 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 58)) +#define PLV_X37 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 59)) +#define PLV_X38 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 60)) +#define PLV_X39 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 61)) +#define PLV_X40 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 62)) +#define PLV_X41 (ZMK_HID_USAGE(HID_USAGE_VENDOR_PLOVER, 63)) diff --git a/app/include/zmk/hid.h b/app/include/zmk/hid.h index 01104d1c..e336410c 100644 --- a/app/include/zmk/hid.h +++ b/app/include/zmk/hid.h @@ -17,6 +17,18 @@ #define COLLECTION_REPORT 0x03 +#define ZMK_HID_KEYBOARD_NKRO_SIZE 6 + +#define ZMK_HID_CONSUMER_NKRO_SIZE 6 + +#define ZMK_HID_PLOVER_SIZE 8 + +// As a workaround for limitations in how some operating systems expose hid +// descriptors to user level code the Plover HID protocol hard codes a report +// id of 0x50 so that the plover side can distinguish between Plover HID +// reports and other reports from the device. +#define PLOVER_HID_REPORT_ID 0x50 + static const uint8_t zmk_hid_report_desc[] = { HID_USAGE_PAGE(HID_USAGE_GEN_DESKTOP), HID_USAGE(HID_USAGE_GD_KEYBOARD), @@ -89,6 +101,34 @@ static const uint8_t zmk_hid_report_desc[] = { /* INPUT (Data,Ary,Abs) */ HID_INPUT(0x00), HID_END_COLLECTION, + +#if IS_ENABLED(CONFIG_ZMK_PLOVER_HID) + /* USAGE (\xffPLV) */ + HID_ITEM(HID_ITEM_TAG_USAGE_PAGE, HID_ITEM_TYPE_GLOBAL, 2), + 0x50, 0xff, + HID_ITEM(HID_ITEM_TAG_USAGE, HID_ITEM_TYPE_LOCAL, 2), + 0x56, 0x4c, + HID_COLLECTION(HID_COLLECTION_APPLICATION), + /* REPORT ID (80) */ + HID_REPORT_ID(PLOVER_HID_REPORT_ID), + /* LOGICAL MINIMUM (0) */ + HID_LOGICAL_MIN8(0x00), + /* LOGICAL MAXIMUM (1) */ + HID_LOGICAL_MAX8(0x01), + /* REPORT SIZE (1) */ + HID_REPORT_SIZE(0x01), + /* REPORT COUNT (64) */ + HID_REPORT_COUNT(0x40), + /* USAGE PAGE (Ordinal) */ + HID_USAGE_PAGE(0x0A), + /* USAGE MINIMUM (0) */ + HID_USAGE_MIN8(0x00), + /* USAGE MAXIMUM (63) */ + HID_USAGE_MAX8(63), + /* INPUT (Cnst,Var,Abs) */ + HID_INPUT(0x02), + HID_END_COLLECTION +#endif /* IS_ENABLED(CONFIG_ZMK_PLOVER_HID) */ }; // struct zmk_hid_boot_report @@ -126,6 +166,15 @@ struct zmk_hid_consumer_report { struct zmk_hid_consumer_report_body body; } __packed; +struct zmk_hid_plover_report_body { + uint8_t buttons[ZMK_HID_PLOVER_SIZE]; +} __packed; + +struct zmk_hid_plover_report { + uint8_t report_id; + struct zmk_hid_plover_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); @@ -152,5 +201,11 @@ int zmk_hid_press(uint32_t usage); int zmk_hid_release(uint32_t usage); bool zmk_hid_is_pressed(uint32_t usage); +int zmk_hid_plover_press(zmk_key_t key); +int zmk_hid_plover_release(zmk_key_t key); +void zmk_hid_plover_clear(); + struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(); struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(); + +struct zmk_hid_plover_report *zmk_hid_get_plover_report(); diff --git a/app/include/zmk/hog.h b/app/include/zmk/hog.h index 7523fb66..481b7975 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_plover_report(struct zmk_hid_plover_report_body *body); diff --git a/app/src/endpoints.c b/app/src/endpoints.c index 33760010..9ec540f2 100644 --- a/app/src/endpoints.c +++ b/app/src/endpoints.c @@ -130,6 +130,35 @@ static int send_consumer_report() { } } +#if IS_ENABLED(CONFIG_ZMK_PLOVER_HID) +static int send_plover_report() { + struct zmk_hid_plover_report *plover_report = zmk_hid_get_plover_report(); + switch (current_endpoint) { +#if IS_ENABLED(CONFIG_ZMK_USB) + case ZMK_ENDPOINT_USB: { + int err = zmk_usb_hid_send_report((uint8_t *)plover_report, sizeof(*plover_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_plover_report(&plover_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; + } +} +#endif /* IS_ENABLED(CONFIG_ZMK_PLOVER_HID) */ + int zmk_endpoints_send_report(uint16_t usage_page) { LOG_DBG("usage page 0x%02X", usage_page); @@ -138,6 +167,12 @@ int zmk_endpoints_send_report(uint16_t usage_page) { return send_keyboard_report(); case HID_USAGE_CONSUMER: return send_consumer_report(); + // FIXME: we should not and the usage page here, we should make sure + // the whole page gets here +#if IS_ENABLED(CONFIG_ZMK_PLOVER_HID) + case (HID_USAGE_VENDOR_PLOVER & 0xFF): + return send_plover_report(); +#endif /* IS_ENABLED(CONFIG_ZMK_PLOVER_HID) */ default: LOG_ERR("Unsupported usage page %d", usage_page); return -ENOTSUP; @@ -228,9 +263,11 @@ static enum zmk_endpoint get_selected_endpoint() { static void disconnect_current_endpoint() { zmk_hid_keyboard_clear(); zmk_hid_consumer_clear(); + zmk_hid_plover_clear(); zmk_endpoints_send_report(HID_USAGE_KEY); zmk_endpoints_send_report(HID_USAGE_CONSUMER); + zmk_endpoints_send_report(HID_USAGE_VENDOR_PLOVER); } static void update_current_endpoint() { diff --git a/app/src/hid.c b/app/src/hid.c index b66a910d..c932840d 100644 --- a/app/src/hid.c +++ b/app/src/hid.c @@ -16,6 +16,8 @@ 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_plover_report plover_report = {.report_id = PLOVER_HID_REPORT_ID, .body = {.buttons = {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}; @@ -240,6 +242,8 @@ int zmk_hid_press(uint32_t usage) { return zmk_hid_keyboard_press(ZMK_HID_USAGE_ID(usage)); case HID_USAGE_CONSUMER: return zmk_hid_consumer_press(ZMK_HID_USAGE_ID(usage)); + case HID_USAGE_VENDOR_PLOVER & 0xFF: + return zmk_hid_plover_press(ZMK_HID_USAGE_ID(usage)); } return -EINVAL; } @@ -250,6 +254,8 @@ int zmk_hid_release(uint32_t usage) { return zmk_hid_keyboard_release(ZMK_HID_USAGE_ID(usage)); case HID_USAGE_CONSUMER: return zmk_hid_consumer_release(ZMK_HID_USAGE_ID(usage)); + case HID_USAGE_VENDOR_PLOVER & 0xFF: + return zmk_hid_plover_release(ZMK_HID_USAGE_ID(usage)); } return -EINVAL; } @@ -264,6 +270,18 @@ bool zmk_hid_is_pressed(uint32_t usage) { return false; } +int zmk_hid_plover_press(zmk_key_t code) { + plover_report.body.buttons[code / 8] |= ( 1 << (7 - (code % 8))); + return 0; +}; + +int zmk_hid_plover_release(zmk_key_t code) { + plover_report.body.buttons[code / 8] &= ~( 1 << (7 - (code % 8))); + return 0; +}; + +void zmk_hid_plover_clear() { memset(&plover_report.body, 0, sizeof(plover_report.body)); } + struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report() { return &keyboard_report; } @@ -271,3 +289,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_plover_report *zmk_hid_get_plover_report() { + return &plover_report; +} diff --git a/app/src/hid_listener.c b/app/src/hid_listener.c index e233b0b8..c96b5b07 100644 --- a/app/src/hid_listener.c +++ b/app/src/hid_listener.c @@ -28,7 +28,7 @@ static int hid_listener_keycode_pressed(const struct zmk_keycode_state_changed * } explicit_mods_changed = zmk_hid_register_mods(ev->explicit_modifiers); implicit_mods_changed = zmk_hid_implicit_modifiers_press(ev->implicit_modifiers); - if (ev->usage_page != HID_USAGE_KEY && + if (ev->usage_page != HID_USAGE_KEY && ev->usage_page != (HID_USAGE_VENDOR_PLOVER & 0xFF) && (explicit_mods_changed > 0 || implicit_mods_changed > 0)) { err = zmk_endpoints_send_report(HID_USAGE_KEY); if (err < 0) { @@ -59,7 +59,7 @@ static int hid_listener_keycode_released(const struct zmk_keycode_state_changed // active and only releasing modifiers at that time. implicit_mods_changed = zmk_hid_implicit_modifiers_release(); ; - if (ev->usage_page != HID_USAGE_KEY && + if (ev->usage_page != HID_USAGE_KEY && ev->usage_page != (HID_USAGE_VENDOR_PLOVER & 0xFF) && (explicit_mods_changed > 0 || implicit_mods_changed > 0)) { err = zmk_endpoints_send_report(HID_USAGE_KEY); if (err < 0) { diff --git a/app/src/hog.c b/app/src/hog.c index 3dd3e874..f0e088be 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 plover_input = { + .id = 0x50, + .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_plover_input_report(struct bt_conn *conn, + const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) { + struct zmk_hid_plover_report_body *report_body = &zmk_hid_get_plover_report()->body; + return bt_gatt_attr_read(conn, attr, buf, len, offset, report_body, + sizeof(struct zmk_hid_plover_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,11 @@ 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_plover_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, &plover_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 +279,59 @@ int zmk_hog_send_consumer_report(struct zmk_hid_consumer_report_body *report) { return 0; }; +K_MSGQ_DEFINE(zmk_hog_plover_msgq, sizeof(struct zmk_hid_plover_report_body), + CONFIG_ZMK_BLE_PLOVER_HID_REPORT_QUEUE_SIZE, 4); + +void send_plover_report_callback(struct k_work *work) { + struct zmk_hid_plover_report_body report; + + while (k_msgq_get(&zmk_hog_plover_msgq, &report, K_NO_WAIT) == 0) { + struct bt_conn *conn = destination_connection(); + if (conn == NULL) { + return; + } + + struct bt_gatt_notify_params notify_params = { + // FIXME: Try to understand this offset calculation, because I just brute-forced it + // until it worked. + // 13 seems to be working, but might be wrong + // or 14 + .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_plover_work, send_plover_report_callback); + +int zmk_hog_send_plover_report(struct zmk_hid_plover_report_body *report) { + int err = k_msgq_put(&zmk_hog_plover_msgq, report, K_MSEC(100)); + if (err) { + switch (err) { + case -EAGAIN: { + LOG_WRN("Plover message queue full, popping first message and queueing again"); + struct zmk_hid_plover_report_body discarded_report; + k_msgq_get(&zmk_hog_plover_msgq, &discarded_report, K_NO_WAIT); + return zmk_hog_send_plover_report(report); + } + default: + LOG_WRN("Failed to queue plover report to send (%d)", err); + return err; + } + } + + k_work_submit_to_queue(&hog_work_q, &hog_plover_work); + + return 0; +} + 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/docs/docs/features/stenography.md b/docs/docs/features/stenography.md new file mode 100644 index 00000000..d92addc7 --- /dev/null +++ b/docs/docs/features/stenography.md @@ -0,0 +1,46 @@ +--- +title: Stenography +--- + +## Summary + +Stenography is a way of writing used by Court Reporters and live captioners to +write at over 200 words per minute, using a chorded keyboard. + +The [Open Steno Project](https://www.openstenoproject.org) has developed an +open-source application named Plover to translate steno strokes. + +Plover supports any NKRO keyboard, but to not have to enable/disable Plover all +the time when wanting to use a normal keyboard usually serial stenography +protocols are used for many hobbyist steno machines. However we can't use those +protocols over BLE, so instead ZMK comes with an implementation of the [Plover +HID](https://github.com/dnaq/plover-machine-hid) protocol that can be used over both +USB and BLE and doesn't require any driver support from the operating system. + +### Configuration + +First enable the Plover HID protocol in your keymaps `.conf` file by adding the line: + +``` +CONFIG_ZMK_PLOVER_HID=y +``` + +Then add normal keypress behaviors to your keymap, but with usage codes that start with `PLV_`, .e.g. + +``` +plover_layer { + bindings = < + &kp &PLV_S1 &kp PLV_TL &kp PLV_PL &kp PLV_HL &kp PLV_ST1 &PLV_ST3 &kp PLV_LR &kp PLV_TR &kp PLV_DR + &kp &PLV_S2 &kp PLV_KL &kp PLV_WL &kp PLV_RL &kp PLV_ST2 &PLV_ST4 &kp PLV_GR &kp PLV_SR &kp PLV_ZR + >; +}; +``` + +To support alternate stenography systems the Plover HID protocol also supports the keys `PLV_X1` to `PLV_X26`. + +After reflashing your keyboard with a stenography enabled keymap you might need to restart your computer or restart the bluetooth +service so that it updates the HID descriptors of your computer. + +Then install the +[plover-machine-hid](https://github.com/dnaq/plover-machine-hid) plugin to +plover according to its installation instructions. diff --git a/docs/docs/intro.md b/docs/docs/intro.md index 142dcafc..0a66afa3 100644 --- a/docs/docs/intro.md +++ b/docs/docs/intro.md @@ -33,6 +33,7 @@ ZMK is currently missing some features found in other popular firmware. This tab | One Shot Keys | ✅ | ✅ | ✅ | | [Combo Keys](features/combos.md) | ✅ | | ✅ | | [Macros](behaviors/macros.md) | ✅ | ✅ | ✅ | +| [Stenography](features/stenography.md) | ✅ | | ✅ | | Mouse Keys | 🚧 | ✅ | ✅ | | Low Active Power Usage | ✅ | | | | Low Power Sleep States | ✅ | ✅ | |