feat(stenography): Support the Plover HID Protocol
This commit adds tentative support for the Plover HID Protocol, a simple HID-based protocol that works over both USB and BLE. The main value-add of this feature is that it allows ZMK to be used for interfacing with Plover from the Open Steno Project without Plover having to intercept all keyboard keypresses. Usually hobbyist steno machines use legacy serial port protocols for this, but since we can't use those over BLE we might as well develop a protocol that works both for USB and BLE and at the same time remove some of the pain points of those old protocols.
This commit is contained in:
parent
6550c043c4
commit
bf9234c602
11 changed files with 315 additions and 3 deletions
13
app/Kconfig
13
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
|
||||
|
|
|
@ -49,3 +49,4 @@
|
|||
#define HID_USAGE_ARCADE (0x91) // Arcade
|
||||
#define HID_USAGE_GAMING (0x92) // Gaming Device
|
||||
#define HID_USAGE_FIDO (0xF1D0) // FIDO Alliance
|
||||
#define HID_USAGE_VENDOR_PLOVER (0xFF50)// Plover HID vendor-defined usage page
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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),
|
||||
|
|
46
docs/docs/features/stenography.md
Normal file
46
docs/docs/features/stenography.md
Normal file
|
@ -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.
|
|
@ -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 | ✅ | ✅ | |
|
||||
|
|
Loading…
Add table
Reference in a new issue