Browse Source

feat(endpoints): add preferred endpoint setting

Added a new setting to remember the user's preferred endpoint. When both USB and
BLE are connected, the preferred endpoint will be used.

Added a new behavior to control this setting. It supports commands:

    &end END_USB - Prefer USB output
    &end END_BLE - Prefer BLE output
    &end END_TOG - Toggle between USB and BLE
xmkb
Joel Spadin 4 years ago
parent
commit
600bba25f0
  1. 1
      app/CMakeLists.txt
  2. 1
      app/dts/behaviors.dtsi
  3. 9
      app/dts/behaviors/endpoints.dtsi
  4. 10
      app/dts/bindings/behaviors/zmk,behavior-endpoints.yaml
  5. 13
      app/include/dt-bindings/zmk/endpoints.h
  6. 8
      app/include/zmk/endpoints.h
  7. 44
      app/src/behaviors/behavior_endpoints.c
  8. 110
      app/src/endpoints.c
  9. 59
      docs/docs/behavior/endpoints.md
  10. 1
      docs/sidebars.js

1
app/CMakeLists.txt

@ -39,6 +39,7 @@ target_sources(app PRIVATE src/events/sensor_event.c)
target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/events/ble_active_profile_changed.c) target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/events/ble_active_profile_changed.c)
target_sources_ifdef(CONFIG_USB app PRIVATE src/events/usb_conn_state_changed.c) target_sources_ifdef(CONFIG_USB app PRIVATE src/events/usb_conn_state_changed.c)
if (NOT CONFIG_ZMK_SPLIT_BLE_ROLE_PERIPHERAL) if (NOT CONFIG_ZMK_SPLIT_BLE_ROLE_PERIPHERAL)
target_sources(app PRIVATE src/behaviors/behavior_endpoints.c)
target_sources(app PRIVATE src/behaviors/behavior_key_press.c) target_sources(app PRIVATE src/behaviors/behavior_key_press.c)
target_sources(app PRIVATE src/behaviors/behavior_reset.c) target_sources(app PRIVATE src/behaviors/behavior_reset.c)
target_sources(app PRIVATE src/behaviors/behavior_hold_tap.c) target_sources(app PRIVATE src/behaviors/behavior_hold_tap.c)

1
app/dts/behaviors.dtsi

@ -10,3 +10,4 @@
#include <behaviors/rgb_underglow.dtsi> #include <behaviors/rgb_underglow.dtsi>
#include <behaviors/bluetooth.dtsi> #include <behaviors/bluetooth.dtsi>
#include <behaviors/ext_power.dtsi> #include <behaviors/ext_power.dtsi>
#include <behaviors/endpoints.dtsi>

9
app/dts/behaviors/endpoints.dtsi

@ -0,0 +1,9 @@
/ {
behaviors {
end: behavior_endpoints {
compatible = "zmk,behavior-endpoints";
label = "ENDPOINTS";
#binding-cells = <1>;
};
};
};

10
app/dts/bindings/behaviors/zmk,behavior-endpoints.yaml

@ -0,0 +1,10 @@
#
# Copyright (c) 2020, The ZMK Contributors
# SPDX-License-Identifier: MIT
#
description: Endpoints Behavior
compatible: "zmk,behavior-endpoints"
include: one_param.yaml

13
app/include/dt-bindings/zmk/endpoints.h

@ -0,0 +1,13 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define ENDPOINT_TOGGLE_CMD 0
#define ENDPOINT_USB_CMD 1
#define ENDPOINT_BLE_CMD 2
#define END_TOG ENDPOINT_TOGGLE_CMD
#define END_USB ENDPOINT_USB_CMD
#define END_BLE ENDPOINT_BLE_CMD

8
app/include/zmk/endpoints.h

@ -9,4 +9,12 @@
#include <zmk/keys.h> #include <zmk/keys.h>
#include <zmk/hid.h> #include <zmk/hid.h>
enum zmk_endpoint {
ZMK_ENDPOINT_USB,
ZMK_ENDPOINT_BLE,
};
int zmk_endpoints_select(enum zmk_endpoint endpoint);
int zmk_endpoints_toggle();
int zmk_endpoints_send_report(u8_t usage_report); int zmk_endpoints_send_report(u8_t usage_report);

44
app/src/behaviors/behavior_endpoints.c

@ -0,0 +1,44 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_behavior_endpoints
#include <device.h>
#include <devicetree.h>
#include <drivers/behavior.h>
#include <dt-bindings/zmk/endpoints.h>
#include <zmk/behavior.h>
#include <zmk/endpoints.h>
#include <logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
switch (binding->param1) {
case ENDPOINT_TOGGLE_CMD:
return zmk_endpoints_toggle();
case ENDPOINT_USB_CMD:
return zmk_endpoints_select(ZMK_ENDPOINT_USB);
case ENDPOINT_BLE_CMD:
return zmk_endpoints_select(ZMK_ENDPOINT_BLE);
default:
LOG_ERR("Unknown endpoints command: %d", binding->param1);
}
return -ENOTSUP;
}
static int behavior_ep_init(struct device *dev) { return 0; }
static const struct behavior_driver_api behavior_endpoints_driver_api = {
.binding_pressed = on_keymap_binding_pressed,
};
DEVICE_AND_API_INIT(behavior_end, DT_INST_LABEL(0), behavior_ep_init, NULL, NULL, APPLICATION,
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_endpoints_driver_api);

110
app/src/endpoints.c

@ -4,6 +4,9 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
#include <init.h>
#include <settings/settings.h>
#include <zmk/ble.h> #include <zmk/ble.h>
#include <zmk/endpoints.h> #include <zmk/endpoints.h>
#include <zmk/hid.h> #include <zmk/hid.h>
@ -16,21 +19,45 @@
#include <logging/log.h> #include <logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
enum endpoint { #define DEFAULT_ENDPOINT \
ENDPOINT_USB, COND_CODE_1(IS_ENABLED(CONFIG_ZMK_BLE), (ZMK_ENDPOINT_BLE), (ZMK_ENDPOINT_USB))
ENDPOINT_BLE,
}; 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();
int zmk_endpoints_select(enum zmk_endpoint endpoint) {
LOG_DBG("Selected endpoint %d", endpoint);
if (preferred_endpoint == endpoint) {
return 0;
}
preferred_endpoint = endpoint;
#define DEFAULT_ENDPOINT COND_CODE_1(IS_ENABLED(CONFIG_ZMK_BLE), (ENDPOINT_BLE), (ENDPOINT_USB)) #if IS_ENABLED(CONFIG_SETTINGS)
settings_save_one("endpoints/preferred", &preferred_endpoint, sizeof(preferred_endpoint));
#endif
static enum endpoint current_endpoint = DEFAULT_ENDPOINT; update_current_endpoint();
return 0;
}
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_keypad_report() { static int send_keypad_report() {
struct zmk_hid_keypad_report *keypad_report = zmk_hid_get_keypad_report(); struct zmk_hid_keypad_report *keypad_report = zmk_hid_get_keypad_report();
switch (current_endpoint) { switch (current_endpoint) {
#if IS_ENABLED(CONFIG_ZMK_USB) #if IS_ENABLED(CONFIG_ZMK_USB)
case ENDPOINT_USB: { case ZMK_ENDPOINT_USB: {
int err = zmk_usb_hid_send_report((u8_t *)keypad_report, sizeof(*keypad_report)); int err = zmk_usb_hid_send_report((u8_t *)keypad_report, sizeof(*keypad_report));
if (err) { if (err) {
LOG_ERR("FAILED TO SEND OVER USB: %d", err); LOG_ERR("FAILED TO SEND OVER USB: %d", err);
@ -40,7 +67,7 @@ static int send_keypad_report() {
#endif /* IS_ENABLED(CONFIG_ZMK_USB) */ #endif /* IS_ENABLED(CONFIG_ZMK_USB) */
#if IS_ENABLED(CONFIG_ZMK_BLE) #if IS_ENABLED(CONFIG_ZMK_BLE)
case ENDPOINT_BLE: { case ZMK_ENDPOINT_BLE: {
int err = zmk_hog_send_keypad_report(&keypad_report->body); int err = zmk_hog_send_keypad_report(&keypad_report->body);
if (err) { if (err) {
LOG_ERR("FAILED TO SEND OVER HOG: %d", err); LOG_ERR("FAILED TO SEND OVER HOG: %d", err);
@ -60,7 +87,7 @@ static int send_consumer_report() {
switch (current_endpoint) { switch (current_endpoint) {
#if IS_ENABLED(CONFIG_ZMK_USB) #if IS_ENABLED(CONFIG_ZMK_USB)
case ENDPOINT_USB: { case ZMK_ENDPOINT_USB: {
int err = zmk_usb_hid_send_report((u8_t *)consumer_report, sizeof(*consumer_report)); int err = zmk_usb_hid_send_report((u8_t *)consumer_report, sizeof(*consumer_report));
if (err) { if (err) {
LOG_ERR("FAILED TO SEND OVER USB: %d", err); LOG_ERR("FAILED TO SEND OVER USB: %d", err);
@ -70,7 +97,7 @@ static int send_consumer_report() {
#endif /* IS_ENABLED(CONFIG_ZMK_USB) */ #endif /* IS_ENABLED(CONFIG_ZMK_USB) */
#if IS_ENABLED(CONFIG_ZMK_BLE) #if IS_ENABLED(CONFIG_ZMK_BLE)
case ENDPOINT_BLE: { case ZMK_ENDPOINT_BLE: {
int err = zmk_hog_send_consumer_report(&consumer_report->body); int err = zmk_hog_send_consumer_report(&consumer_report->body);
if (err) { if (err) {
LOG_ERR("FAILED TO SEND OVER HOG: %d", err); LOG_ERR("FAILED TO SEND OVER HOG: %d", err);
@ -99,6 +126,49 @@ int zmk_endpoints_send_report(u8_t usage_page) {
} }
} }
#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(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;
}
settings_load();
#endif
return 0;
}
static bool is_usb_ready() { static bool is_usb_ready() {
#if IS_ENABLED(CONFIG_ZMK_USB) #if IS_ENABLED(CONFIG_ZMK_USB)
return zmk_usb_is_hid_ready(); return zmk_usb_is_hid_ready();
@ -115,21 +185,20 @@ static bool is_ble_ready() {
#endif #endif
} }
static enum endpoint get_selected_endpoint() { static enum zmk_endpoint get_selected_endpoint() {
if (is_ble_ready()) { if (is_ble_ready()) {
if (is_usb_ready()) { if (is_usb_ready()) {
LOG_DBG("Both endpoints are ready."); LOG_DBG("Both endpoints are ready. Using %d", preferred_endpoint);
// TODO: add user setting to control this return preferred_endpoint;
return ENDPOINT_USB;
} }
LOG_DBG("Only BLE is ready."); LOG_DBG("Only BLE is ready.");
return ENDPOINT_BLE; return ZMK_ENDPOINT_BLE;
} }
if (is_usb_ready()) { if (is_usb_ready()) {
LOG_DBG("Only USB is ready."); LOG_DBG("Only USB is ready.");
return ENDPOINT_USB; return ZMK_ENDPOINT_USB;
} }
LOG_DBG("No endpoints are ready."); LOG_DBG("No endpoints are ready.");
@ -144,8 +213,8 @@ static void disconnect_current_endpoint() {
zmk_endpoints_send_report(USAGE_CONSUMER); zmk_endpoints_send_report(USAGE_CONSUMER);
} }
static int endpoint_listener(const struct zmk_event_header *eh) { static void update_current_endpoint() {
enum endpoint new_endpoint = get_selected_endpoint(); enum zmk_endpoint new_endpoint = get_selected_endpoint();
if (new_endpoint != current_endpoint) { if (new_endpoint != current_endpoint) {
/* Cancel all current keypresses so keys don't stay held on the old endpoint. */ /* Cancel all current keypresses so keys don't stay held on the old endpoint. */
@ -154,7 +223,10 @@ static int endpoint_listener(const struct zmk_event_header *eh) {
current_endpoint = new_endpoint; current_endpoint = new_endpoint;
LOG_INF("Endpoint changed: %d", current_endpoint); LOG_INF("Endpoint changed: %d", current_endpoint);
} }
}
static int endpoint_listener(const struct zmk_event_header *eh) {
update_current_endpoint();
return 0; return 0;
} }
@ -165,3 +237,5 @@ ZMK_SUBSCRIPTION(endpoint_listener, usb_conn_state_changed);
#if IS_ENABLED(CONFIG_ZMK_BLE) #if IS_ENABLED(CONFIG_ZMK_BLE)
ZMK_SUBSCRIPTION(endpoint_listener, ble_active_profile_changed); ZMK_SUBSCRIPTION(endpoint_listener, ble_active_profile_changed);
#endif #endif
SYS_INIT(zmk_endpoints_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);

59
docs/docs/behavior/endpoints.md

@ -0,0 +1,59 @@
---
title: Endpoint Behavior
sidebar_label: Endpoints
---
## Summary
The endpoint behavior allows selecting whether keyboard input is sent to the
USB or bluetooth connection when both are connected. This allows connecting a
keyboard to USB for power but sending input to a different device over bluetooth.
By default, keyboard input is sent to USB when both endpoints are connected.
Once you select a different endpoint, it will be remembered until you change it again.
## Endpoints Command Defines
Endpoints command defines are provided through the [`dt-bindings/zmk/endpoints.h`](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/endpoints.h)
header, which is added at the top of the keymap file:
```
#include <dt-bindings/zmk/endpoints.h>
```
This allows you to reference the actions defined in this header:
| Define | Action | Alias |
| --------------------- | ---------------------------------------------------- | --------- |
| `ENDPOINT_USB_CMD` | Send keyboard input to USB | `END_USB` |
| `ENDPOINT_BLE_CMD` | Send keyboard input to the current bluetooth profile | `END_BLE` |
| `ENDPOINT_TOGGLE_CMD` | Toggle between USB and BLE | `END_TOG` |
## Endpoints Behavior
The endpoints behavior changes the preferred endpoint on press.
### Behavior Binding
- Reference: `&end`
- Parameter #1: Command, e.g. `END_BLE`
### Example:
1. Behavior binding to prefer sending keyboard input to USB
```
&end END_USB
```
1. Behavior binding to prefer sending keyboard input to the current bluetooth profile
```
&end END_BLE
```
1. Behavior binding to toggle between preferring USB and BLE
```
&end END_TOG
```

1
docs/sidebars.js

@ -22,6 +22,7 @@ module.exports = {
"behavior/mod-tap", "behavior/mod-tap",
"behavior/reset", "behavior/reset",
"behavior/bluetooth", "behavior/bluetooth",
"behavior/endpoints",
"behavior/lighting", "behavior/lighting",
"behavior/power", "behavior/power",
], ],

Loading…
Cancel
Save