zmk/app/src/endpoints.c
Andrew Childs 843d324525
Boot protocol support
Based on PR#1140 with several changes as described in this comment: https://github.com/zmkfirmware/zmk/pull/1140#issuecomment-1237655563

* Minimize the api changes for bluetooth by keeping
  zmk_hid_get_keyboard_report() and returning the .body like before.
* Keeping the logic about "full" vs "body" entirely within the usb code path.
* The endpoint now calls either zmk_usb_hid_send_keyboard_report() and
  zmk_usb_hid_send_consumer_report() instead of zmk_usb_hid_send_report(...).
    - These functions now internally dispatch on protocol to either
      zmk_hid_get_keyboard_report() or a new function for boot reports
      zmk_hid_get_boot_report().
    - There's a change here from the PR version in the behavior of get_report():
      when in boot protocol, don't include the report id. I believe this is
      correct, in that implicit boot protocol report descriptor does not use
      multiple reports, so any boot protocol report should not include the report
      id field.
* Use a single definition of a boot report, used for regular reports in
  non-6KRO, and for rollover in all branches.
* Handle gaps in the zmk report when producing a boot report in HKRO mode. For
  .example, if it was 8KRO, it would be possible to have the state 0 0 0 0 0 0 0
  17 (by pressing 8 keys, and letting go of the first 7). Copying the first 6
  bytes would not show up the single pressed key.
2022-11-25 20:13:24 +09:00

261 lines
6.9 KiB
C

/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <init.h>
#include <settings/settings.h>
#include <zmk/ble.h>
#include <zmk/endpoints.h>
#include <zmk/hid.h>
#include <dt-bindings/zmk/hid_usage_pages.h>
#include <zmk/usb_hid.h>
#include <zmk/hog.h>
#include <zmk/event_manager.h>
#include <zmk/events/ble_active_profile_changed.h>
#include <zmk/events/usb_conn_state_changed.h>
#include <zmk/events/endpoint_selection_changed.h>
#include <logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#define DEFAULT_ENDPOINT \
COND_CODE_1(IS_ENABLED(CONFIG_ZMK_BLE), (ZMK_ENDPOINT_BLE), (ZMK_ENDPOINT_USB))
static enum zmk_endpoint current_endpoint = DEFAULT_ENDPOINT;
static enum zmk_endpoint preferred_endpoint =
ZMK_ENDPOINT_USB; /* Used if multiple endpoints are ready */
static void update_current_endpoint();
#if IS_ENABLED(CONFIG_SETTINGS)
static void endpoints_save_preferred_work(struct k_work *work) {
settings_save_one("endpoints/preferred", &preferred_endpoint, sizeof(preferred_endpoint));
}
static struct k_work_delayable endpoints_save_work;
#endif
static int endpoints_save_preferred() {
#if IS_ENABLED(CONFIG_SETTINGS)
return k_work_reschedule(&endpoints_save_work, K_MSEC(CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE));
#else
return 0;
#endif
}
int zmk_endpoints_select(enum zmk_endpoint endpoint) {
LOG_DBG("Selected endpoint %d", endpoint);
if (preferred_endpoint == endpoint) {
return 0;
}
preferred_endpoint = endpoint;
endpoints_save_preferred();
update_current_endpoint();
return 0;
}
enum zmk_endpoint zmk_endpoints_selected() { return current_endpoint; }
int zmk_endpoints_toggle() {
enum zmk_endpoint new_endpoint =
(preferred_endpoint == ZMK_ENDPOINT_USB) ? ZMK_ENDPOINT_BLE : ZMK_ENDPOINT_USB;
return zmk_endpoints_select(new_endpoint);
}
static int send_keyboard_report() {
switch (current_endpoint) {
#if IS_ENABLED(CONFIG_ZMK_USB)
case ZMK_ENDPOINT_USB: {
int err = zmk_usb_hid_send_keyboard_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_keyboard_report(&zmk_hid_get_keyboard_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;
}
}
static int send_consumer_report() {
switch (current_endpoint) {
#if IS_ENABLED(CONFIG_ZMK_USB)
case ZMK_ENDPOINT_USB: {
int err = zmk_usb_hid_send_consumer_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_consumer_report(&zmk_hid_get_consumer_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);
switch (usage_page) {
case HID_USAGE_KEY:
return send_keyboard_report();
case HID_USAGE_CONSUMER:
return send_consumer_report();
default:
LOG_ERR("Unsupported usage page %d", usage_page);
return -ENOTSUP;
}
}
#if IS_ENABLED(CONFIG_SETTINGS)
static int endpoints_handle_set(const char *name, size_t len, settings_read_cb read_cb,
void *cb_arg) {
LOG_DBG("Setting endpoint value %s", log_strdup(name));
if (settings_name_steq(name, "preferred", NULL)) {
if (len != sizeof(enum zmk_endpoint)) {
LOG_ERR("Invalid endpoint size (got %d expected %d)", len, sizeof(enum zmk_endpoint));
return -EINVAL;
}
int err = read_cb(cb_arg, &preferred_endpoint, sizeof(enum zmk_endpoint));
if (err <= 0) {
LOG_ERR("Failed to read preferred endpoint from settings (err %d)", err);
return err;
}
update_current_endpoint();
}
return 0;
}
struct settings_handler endpoints_handler = {.name = "endpoints", .h_set = endpoints_handle_set};
#endif /* IS_ENABLED(CONFIG_SETTINGS) */
static int zmk_endpoints_init(const struct device *_arg) {
#if IS_ENABLED(CONFIG_SETTINGS)
settings_subsys_init();
int err = settings_register(&endpoints_handler);
if (err) {
LOG_ERR("Failed to register the endpoints settings handler (err %d)", err);
return err;
}
k_work_init_delayable(&endpoints_save_work, endpoints_save_preferred_work);
settings_load_subtree("endpoints");
#endif
return 0;
}
static bool is_usb_ready() {
#if IS_ENABLED(CONFIG_ZMK_USB)
return zmk_usb_is_hid_ready();
#else
return false;
#endif
}
static bool is_ble_ready() {
#if IS_ENABLED(CONFIG_ZMK_BLE)
return zmk_ble_active_profile_is_connected();
#else
return false;
#endif
}
static enum zmk_endpoint get_selected_endpoint() {
if (is_ble_ready()) {
if (is_usb_ready()) {
LOG_DBG("Both endpoints are ready. Using %d", preferred_endpoint);
return preferred_endpoint;
}
LOG_DBG("Only BLE is ready.");
return ZMK_ENDPOINT_BLE;
}
if (is_usb_ready()) {
LOG_DBG("Only USB is ready.");
return ZMK_ENDPOINT_USB;
}
LOG_DBG("No endpoints are ready.");
return DEFAULT_ENDPOINT;
}
static void disconnect_current_endpoint() {
zmk_hid_keyboard_clear();
zmk_hid_consumer_clear();
zmk_endpoints_send_report(HID_USAGE_KEY);
zmk_endpoints_send_report(HID_USAGE_CONSUMER);
}
static void update_current_endpoint() {
enum zmk_endpoint new_endpoint = get_selected_endpoint();
if (new_endpoint != current_endpoint) {
/* Cancel all current keypresses so keys don't stay held on the old endpoint. */
disconnect_current_endpoint();
current_endpoint = new_endpoint;
LOG_INF("Endpoint changed: %d", current_endpoint);
ZMK_EVENT_RAISE(new_zmk_endpoint_selection_changed(
(struct zmk_endpoint_selection_changed){.endpoint = current_endpoint}));
}
}
static int endpoint_listener(const zmk_event_t *eh) {
update_current_endpoint();
return 0;
}
ZMK_LISTENER(endpoint_listener, endpoint_listener);
#if IS_ENABLED(CONFIG_ZMK_USB)
ZMK_SUBSCRIPTION(endpoint_listener, zmk_usb_conn_state_changed);
#endif
#if IS_ENABLED(CONFIG_ZMK_BLE)
ZMK_SUBSCRIPTION(endpoint_listener, zmk_ble_active_profile_changed);
#endif
SYS_INIT(zmk_endpoints_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);