diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 8a3971e8..f76effc0 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -37,11 +37,13 @@ target_sources(app PRIVATE src/events/keycode_state_changed.c) target_sources(app PRIVATE src/events/modifiers_state_changed.c) 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_USB app PRIVATE src/events/usb_conn_state_changed.c) if (NOT CONFIG_ZMK_SPLIT_BLE_ROLE_PERIPHERAL) 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_hold_tap.c) target_sources(app PRIVATE src/behaviors/behavior_momentary_layer.c) + target_sources(app PRIVATE src/behaviors/behavior_outputs.c) target_sources(app PRIVATE src/behaviors/behavior_toggle_layer.c) target_sources(app PRIVATE src/behaviors/behavior_transparent.c) target_sources(app PRIVATE src/behaviors/behavior_none.c) diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi index 36c918cf..a120b84b 100644 --- a/app/dts/behaviors.dtsi +++ b/app/dts/behaviors.dtsi @@ -10,3 +10,4 @@ #include #include #include +#include diff --git a/app/dts/behaviors/outputs.dtsi b/app/dts/behaviors/outputs.dtsi new file mode 100644 index 00000000..a534cbf9 --- /dev/null +++ b/app/dts/behaviors/outputs.dtsi @@ -0,0 +1,9 @@ +/ { + behaviors { + out: behavior_outputs { + compatible = "zmk,behavior-outputs"; + label = "OUTPUTS"; + #binding-cells = <1>; + }; + }; +}; diff --git a/app/dts/bindings/behaviors/zmk,behavior-outputs.yaml b/app/dts/bindings/behaviors/zmk,behavior-outputs.yaml new file mode 100644 index 00000000..8bcefd94 --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-outputs.yaml @@ -0,0 +1,10 @@ +# +# Copyright (c) 2020, The ZMK Contributors +# SPDX-License-Identifier: MIT +# + +description: Output Selection Behavior + +compatible: "zmk,behavior-outputs" + +include: one_param.yaml diff --git a/app/include/dt-bindings/zmk/outputs.h b/app/include/dt-bindings/zmk/outputs.h new file mode 100644 index 00000000..f24380f7 --- /dev/null +++ b/app/include/dt-bindings/zmk/outputs.h @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define OUT_TOG 0 +#define OUT_USB 1 +#define OUT_BLE 2 \ No newline at end of file diff --git a/app/include/zmk/ble.h b/app/include/zmk/ble.h index 1cf71a77..56980c69 100644 --- a/app/include/zmk/ble.h +++ b/app/include/zmk/ble.h @@ -15,6 +15,7 @@ int zmk_ble_prof_prev(); int zmk_ble_prof_select(u8_t index); bt_addr_le_t *zmk_ble_active_profile_addr(); +bool zmk_ble_active_profile_is_connected(); char *zmk_ble_active_profile_name(); int zmk_ble_unpair_all(); diff --git a/app/include/zmk/endpoints.h b/app/include/zmk/endpoints.h index aad6265b..aad688e7 100644 --- a/app/include/zmk/endpoints.h +++ b/app/include/zmk/endpoints.h @@ -9,4 +9,12 @@ #include #include +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); diff --git a/app/include/zmk/events/usb-conn-state-changed.h b/app/include/zmk/events/usb-conn-state-changed.h new file mode 100644 index 00000000..d6cc6985 --- /dev/null +++ b/app/include/zmk/events/usb-conn-state-changed.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include + +#include +#include + +struct usb_conn_state_changed { + struct zmk_event_header header; + enum zmk_usb_conn_state conn_state; +}; + +ZMK_EVENT_DECLARE(usb_conn_state_changed); \ No newline at end of file diff --git a/app/include/zmk/hid.h b/app/include/zmk/hid.h index 1ce5cc9b..fd09a6f4 100644 --- a/app/include/zmk/hid.h +++ b/app/include/zmk/hid.h @@ -168,9 +168,11 @@ int zmk_hid_register_mods(zmk_mod_flags modifiers); int zmk_hid_unregister_mods(zmk_mod_flags modifiers); int zmk_hid_keypad_press(zmk_key key); int zmk_hid_keypad_release(zmk_key key); +void zmk_hid_keypad_clear(); int zmk_hid_consumer_press(zmk_key key); int zmk_hid_consumer_release(zmk_key key); +void zmk_hid_consumer_clear(); struct zmk_hid_keypad_report *zmk_hid_get_keypad_report(); struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(); diff --git a/app/include/zmk/usb.h b/app/include/zmk/usb.h index 452fd54d..30461de2 100644 --- a/app/include/zmk/usb.h +++ b/app/include/zmk/usb.h @@ -12,8 +12,18 @@ #include #include +enum zmk_usb_conn_state { + ZMK_USB_CONN_NONE, + ZMK_USB_CONN_POWERED, + ZMK_USB_CONN_HID, +}; + enum usb_dc_status_code zmk_usb_get_status(); +enum zmk_usb_conn_state zmk_usb_get_conn_state(); + +static inline bool zmk_usb_is_powered() { return zmk_usb_get_conn_state() != ZMK_USB_CONN_NONE; } +static inline bool zmk_usb_is_hid_ready() { return zmk_usb_get_conn_state() == ZMK_USB_CONN_HID; } #ifdef CONFIG_ZMK_USB -int zmk_usb_hid_send_report(u8_t *report, size_t len); +int zmk_usb_hid_send_report(const u8_t *report, size_t len); #endif /* CONFIG_ZMK_USB */ \ No newline at end of file diff --git a/app/src/behaviors/behavior_outputs.c b/app/src/behaviors/behavior_outputs.c new file mode 100644 index 00000000..e5182bd4 --- /dev/null +++ b/app/src/behaviors/behavior_outputs.c @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_outputs + +#include +#include +#include + +#include + +#include +#include + +#include +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 OUT_TOG: + return zmk_endpoints_toggle(); + case OUT_USB: + return zmk_endpoints_select(ZMK_ENDPOINT_USB); + case OUT_BLE: + return zmk_endpoints_select(ZMK_ENDPOINT_BLE); + default: + LOG_ERR("Unknown output command: %d", binding->param1); + } + + return -ENOTSUP; +} + +static int behavior_out_init(struct device *dev) { return 0; } + +static const struct behavior_driver_api behavior_outputs_driver_api = { + .binding_pressed = on_keymap_binding_pressed, +}; + +DEVICE_AND_API_INIT(behavior_out, DT_INST_LABEL(0), behavior_out_init, NULL, NULL, APPLICATION, + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_outputs_driver_api); diff --git a/app/src/ble.c b/app/src/ble.c index 9090582c..f3962ae0 100644 --- a/app/src/ble.c +++ b/app/src/ble.c @@ -94,6 +94,12 @@ static void raise_profile_changed_event() { ZMK_EVENT_RAISE(ev); } +static void raise_profile_changed_event_callback(struct k_work *work) { + raise_profile_changed_event(); +} + +K_WORK_DEFINE(raise_profile_changed_event_work, raise_profile_changed_event_callback); + static bool active_profile_is_open() { return !bt_addr_le_cmp(&profiles[active_profile].peer, BT_ADDR_LE_ANY); } @@ -111,7 +117,7 @@ void set_profile_address(u8_t index, const bt_addr_le_t *addr) { raise_profile_changed_event(); } -bool active_profile_is_connected() { +bool zmk_ble_active_profile_is_connected() { struct bt_conn *conn; bt_addr_le_t *addr = zmk_ble_active_profile_addr(); if (!bt_addr_le_cmp(addr, BT_ADDR_LE_ANY)) { @@ -163,9 +169,9 @@ int update_advertising() { struct bt_conn *conn; enum advertising_type desired_adv = ZMK_ADV_NONE; - if (active_profile_is_open() || !active_profile_is_connected()) { + if (active_profile_is_open()) { desired_adv = ZMK_ADV_CONN; - } else if (!active_profile_is_connected()) { + } else if (!zmk_ble_active_profile_is_connected()) { desired_adv = ZMK_ADV_CONN; // Need to fix directed advertising for privacy centrals. See // https://github.com/zephyrproject-rtos/zephyr/pull/14984 char @@ -327,6 +333,10 @@ static int ble_profiles_handle_set(const char *name, size_t len, settings_read_c struct settings_handler profiles_handler = {.name = "ble", .h_set = ble_profiles_handle_set}; #endif /* IS_ENABLED(CONFIG_SETTINGS) */ +static bool is_conn_active_profile(const struct bt_conn *conn) { + return bt_addr_le_cmp(bt_conn_get_dst(conn), &profiles[active_profile].peer) == 0; +} + static void connected(struct bt_conn *conn, u8_t err) { char addr[BT_ADDR_LE_STR_LEN]; bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); @@ -352,6 +362,11 @@ static void connected(struct bt_conn *conn, u8_t err) { } update_advertising(); + + if (is_conn_active_profile(conn)) { + LOG_DBG("Active profile connected"); + raise_profile_changed_event(); + } } static void disconnected(struct bt_conn *conn, u8_t reason) { @@ -364,6 +379,11 @@ static void disconnected(struct bt_conn *conn, u8_t reason) { // We need to do this in a work callback, otherwise the advertising update will still see the // connection for a profile as active, and not start advertising yet. k_work_submit(&update_advertising_work); + + if (is_conn_active_profile(conn)) { + LOG_DBG("Active profile disconnected"); + k_work_submit(&raise_profile_changed_event_work); + } } static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err err) { diff --git a/app/src/endpoints.c b/app/src/endpoints.c index 79d294ef..0c795890 100644 --- a/app/src/endpoints.c +++ b/app/src/endpoints.c @@ -4,58 +4,238 @@ * SPDX-License-Identifier: MIT */ +#include +#include + +#include #include #include #include #include +#include +#include +#include #include 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(); + +int zmk_endpoints_select(enum zmk_endpoint endpoint) { + LOG_DBG("Selected endpoint %d", endpoint); + + if (preferred_endpoint == endpoint) { + return 0; + } + + preferred_endpoint = endpoint; + +#if IS_ENABLED(CONFIG_SETTINGS) + settings_save_one("endpoints/preferred", &preferred_endpoint, sizeof(preferred_endpoint)); +#endif + + 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() { + struct zmk_hid_keypad_report *keypad_report = zmk_hid_get_keypad_report(); + + switch (current_endpoint) { +#if IS_ENABLED(CONFIG_ZMK_USB) + case ZMK_ENDPOINT_USB: { + int err = zmk_usb_hid_send_report((u8_t *)keypad_report, sizeof(*keypad_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_keypad_report(&keypad_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() { + struct zmk_hid_consumer_report *consumer_report = zmk_hid_get_consumer_report(); + + switch (current_endpoint) { +#if IS_ENABLED(CONFIG_ZMK_USB) + case ZMK_ENDPOINT_USB: { + int err = zmk_usb_hid_send_report((u8_t *)consumer_report, sizeof(*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(&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(u8_t usage_page) { - int err; - struct zmk_hid_keypad_report *keypad_report; - struct zmk_hid_consumer_report *consumer_report; + LOG_DBG("usage page 0x%02X", usage_page); switch (usage_page) { case USAGE_KEYPAD: - keypad_report = zmk_hid_get_keypad_report(); -#ifdef CONFIG_ZMK_USB - if (zmk_usb_hid_send_report((u8_t *)keypad_report, sizeof(struct zmk_hid_keypad_report)) != - 0) { - LOG_DBG("USB Send Failed"); - } -#endif /* CONFIG_ZMK_USB */ - -#ifdef CONFIG_ZMK_BLE - err = zmk_hog_send_keypad_report(&keypad_report->body); - if (err) { - LOG_ERR("FAILED TO SEND OVER HOG: %d", err); - } -#endif /* CONFIG_ZMK_BLE */ - - break; + return send_keypad_report(); case USAGE_CONSUMER: - consumer_report = zmk_hid_get_consumer_report(); -#ifdef CONFIG_ZMK_USB - if (zmk_usb_hid_send_report((u8_t *)consumer_report, - sizeof(struct zmk_hid_consumer_report)) != 0) { - LOG_DBG("USB Send Failed"); - } -#endif /* CONFIG_ZMK_USB */ - -#ifdef CONFIG_ZMK_BLE - err = zmk_hog_send_consumer_report(&consumer_report->body); - if (err) { - LOG_ERR("FAILED TO SEND OVER HOG: %d", err); - } -#endif /* CONFIG_ZMK_BLE */ - - break; + 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(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() { +#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_keypad_clear(); + zmk_hid_consumer_clear(); + + zmk_endpoints_send_report(USAGE_KEYPAD); + zmk_endpoints_send_report(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); + } +} + +static int endpoint_listener(const struct zmk_event_header *eh) { + update_current_endpoint(); + return 0; +} + +ZMK_LISTENER(endpoint_listener, endpoint_listener); +#if IS_ENABLED(CONFIG_ZMK_USB) +ZMK_SUBSCRIPTION(endpoint_listener, usb_conn_state_changed); +#endif +#if IS_ENABLED(CONFIG_ZMK_BLE) +ZMK_SUBSCRIPTION(endpoint_listener, ble_active_profile_changed); +#endif + +SYS_INIT(zmk_endpoints_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/app/src/events/usb_conn_state_changed.c b/app/src/events/usb_conn_state_changed.c new file mode 100644 index 00000000..d845f6d7 --- /dev/null +++ b/app/src/events/usb_conn_state_changed.c @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +ZMK_EVENT_IMPL(usb_conn_state_changed); \ No newline at end of file diff --git a/app/src/hid.c b/app/src/hid.c index 207b1adf..216cec7a 100644 --- a/app/src/hid.c +++ b/app/src/hid.c @@ -68,6 +68,8 @@ int zmk_hid_keypad_release(zmk_key code) { return 0; }; +void zmk_hid_keypad_clear() { memset(&kp_report.body, 0, sizeof(kp_report.body)); } + int zmk_hid_consumer_press(zmk_key code) { TOGGLE_CONSUMER(0U, code); return 0; @@ -78,6 +80,8 @@ int zmk_hid_consumer_release(zmk_key code) { return 0; }; +void zmk_hid_consumer_clear() { memset(&consumer_report.body, 0, sizeof(consumer_report.body)); } + struct zmk_hid_keypad_report *zmk_hid_get_keypad_report() { return &kp_report; } diff --git a/app/src/power.c b/app/src/power.c index 73b3f123..bad54d28 100644 --- a/app/src/power.c +++ b/app/src/power.c @@ -23,14 +23,7 @@ static u32_t power_last_uptime; bool is_usb_power_present() { #ifdef CONFIG_USB - enum usb_dc_status_code usb_status = zmk_usb_get_status(); - switch (usb_status) { - case USB_DC_DISCONNECTED: - case USB_DC_UNKNOWN: - return false; - default: - return true; - } + return zmk_usb_is_powered(); #else return false; #endif /* CONFIG_USB */ diff --git a/app/src/usb.c b/app/src/usb.c index 434b3d4d..d4bc2e41 100644 --- a/app/src/usb.c +++ b/app/src/usb.c @@ -13,6 +13,8 @@ #include #include +#include +#include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); @@ -53,9 +55,34 @@ int zmk_usb_hid_send_report(const u8_t *report, size_t len) { #endif /* CONFIG_ZMK_USB */ +static void raise_usb_status_changed_event() { + struct usb_conn_state_changed *ev = new_usb_conn_state_changed(); + ev->conn_state = zmk_usb_get_conn_state(); + + ZMK_EVENT_RAISE(ev); +} + enum usb_dc_status_code zmk_usb_get_status() { return usb_status; } -void usb_status_cb(enum usb_dc_status_code status, const u8_t *params) { usb_status = status; }; +enum zmk_usb_conn_state zmk_usb_get_conn_state() { + switch (usb_status) { + case USB_DC_DISCONNECTED: + case USB_DC_UNKNOWN: + return ZMK_USB_CONN_NONE; + + case USB_DC_ERROR: + case USB_DC_RESET: + return ZMK_USB_CONN_POWERED; + + default: + return ZMK_USB_CONN_HID; + } +} + +void usb_status_cb(enum usb_dc_status_code status, const u8_t *params) { + usb_status = status; + raise_usb_status_changed_event(); +}; static int zmk_usb_init(struct device *_arg) { int usb_enable_ret; diff --git a/docs/docs/behavior/outputs.md b/docs/docs/behavior/outputs.md new file mode 100644 index 00000000..50afac9c --- /dev/null +++ b/docs/docs/behavior/outputs.md @@ -0,0 +1,59 @@ +--- +title: Output Selection Behavior +sidebar_label: Output Selection +--- + +## Summary + +The output behavior allows selecting whether keyboard output is sent to the +USB or bluetooth connection when both are connected. This allows connecting a +keyboard to USB for power but outputting to a different device over bluetooth. + +By default, output is sent to USB when both USB and BLE are connected. +Once you select a different output, it will be remembered until you change it again. + +## Output Command Defines + +Output command defines are provided through the [`dt-bindings/zmk/outputs.h`](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/outputs.h) +header, which is added at the top of the keymap file: + +``` +#include +``` + +This allows you to reference the actions defined in this header: + +| Define | Action | Alias | +| ------------------- | ----------------------------------------------- | --------- | +| `OUTPUT_USB_CMD` | Prefer sending to USB | `OUT_USB` | +| `OUTPUT_BLE_CMD` | Prefer sending to the current bluetooth profile | `OUT_BLE` | +| `OUTPUT_TOGGLE_CMD` | Toggle between USB and BLE | `OUT_TOG` | + +## Output Selection Behavior + +The output selection behavior changes the preferred output on press. + +### Behavior Binding + +- Reference: `&out` +- Parameter #1: Command, e.g. `OUT_BLE` + +### Example: + +1. Behavior binding to prefer sending keyboard output to USB + + ``` + &out OUT_USB + ``` + +1. Behavior binding to prefer sending keyboard output to the current bluetooth profile + + ``` + &out OUT_BLE + ``` + +1. Behavior binding to toggle between preferring USB and BLE + + ``` + &out OUT_TOG + ``` diff --git a/docs/sidebars.js b/docs/sidebars.js index 54f6576b..c8dc79fc 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -22,6 +22,7 @@ module.exports = { "behavior/mod-tap", "behavior/reset", "behavior/bluetooth", + "behavior/outputs", "behavior/lighting", "behavior/power", ],