diff --git a/app/Kconfig b/app/Kconfig index b0ffc72ac02..5afa4a4587c 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -145,6 +145,9 @@ config USB_HID_POLL_INTERVAL_MS #ZMK_USB endif +config ZMK_ENDPOINT_DISABLE_FALLBACK + bool "Disable automatic endpoint fallback when preferred endpoint is unavailable" + menuconfig ZMK_BLE bool "BLE (HID over GATT)" select BT diff --git a/app/include/zmk/endpoints.h b/app/include/zmk/endpoints.h index f2aff2bcc2d..598de7c4886 100644 --- a/app/include/zmk/endpoints.h +++ b/app/include/zmk/endpoints.h @@ -14,6 +14,8 @@ */ #define ZMK_ENDPOINT_STR_LEN 10 +#define ZMK_ENDPOINT_NONE_COUNT 1 + #ifdef CONFIG_ZMK_USB #define ZMK_ENDPOINT_USB_COUNT 1 #else @@ -33,7 +35,8 @@ * Note that this value may change between firmware versions, so it should not * be used in any persistent storage. */ -#define ZMK_ENDPOINT_COUNT (ZMK_ENDPOINT_USB_COUNT + ZMK_ENDPOINT_BLE_COUNT) +#define ZMK_ENDPOINT_COUNT \ + (ZMK_ENDPOINT_NONE_COUNT + ZMK_ENDPOINT_USB_COUNT + ZMK_ENDPOINT_BLE_COUNT) bool zmk_endpoint_instance_eq(struct zmk_endpoint_instance a, struct zmk_endpoint_instance b); @@ -64,10 +67,15 @@ int zmk_endpoints_select_transport(enum zmk_transport transport); int zmk_endpoints_toggle_transport(void); /** - * Gets the currently-selected endpoint. + * Gets the currently in use endpoint. */ struct zmk_endpoint_instance zmk_endpoints_selected(void); +/** + * Gets the preferred endpoint. + */ +struct zmk_endpoint_instance zmk_endpoints_preferred(void); + int zmk_endpoints_send_report(uint16_t usage_page); #if IS_ENABLED(CONFIG_ZMK_MOUSE) diff --git a/app/include/zmk/endpoints_types.h b/app/include/zmk/endpoints_types.h index ea51c8aa93d..a8814336712 100644 --- a/app/include/zmk/endpoints_types.h +++ b/app/include/zmk/endpoints_types.h @@ -10,6 +10,7 @@ * The method by which data is sent. */ enum zmk_transport { + ZMK_TRANSPORT_NONE, ZMK_TRANSPORT_USB, ZMK_TRANSPORT_BLE, }; diff --git a/app/src/endpoints.c b/app/src/endpoints.c index b17a664646d..a551208b2c6 100644 --- a/app/src/endpoints.c +++ b/app/src/endpoints.c @@ -23,8 +23,10 @@ #include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); +#if !IS_ENABLED(CONFIG_ZMK_ENDPOINT_DISABLE_FALLBACK) #define DEFAULT_TRANSPORT \ COND_CODE_1(IS_ENABLED(CONFIG_ZMK_BLE), (ZMK_TRANSPORT_BLE), (ZMK_TRANSPORT_USB)) +#endif static struct zmk_endpoint_instance current_instance = {}; static enum zmk_transport preferred_transport = @@ -54,6 +56,9 @@ bool zmk_endpoint_instance_eq(struct zmk_endpoint_instance a, struct zmk_endpoin } switch (a.transport) { + case ZMK_TRANSPORT_NONE: + return true; + case ZMK_TRANSPORT_USB: return true; @@ -67,6 +72,9 @@ bool zmk_endpoint_instance_eq(struct zmk_endpoint_instance a, struct zmk_endpoin int zmk_endpoint_instance_to_str(struct zmk_endpoint_instance endpoint, char *str, size_t len) { switch (endpoint.transport) { + case ZMK_TRANSPORT_NONE: + return snprintf(str, len, "None"); + case ZMK_TRANSPORT_USB: return snprintf(str, len, "USB"); @@ -78,11 +86,15 @@ int zmk_endpoint_instance_to_str(struct zmk_endpoint_instance endpoint, char *st } } -#define INSTANCE_INDEX_OFFSET_USB 0 -#define INSTANCE_INDEX_OFFSET_BLE ZMK_ENDPOINT_USB_COUNT +#define INSTANCE_INDEX_OFFSET_NONE 0 +#define INSTANCE_INDEX_OFFSET_USB (INSTANCE_INDEX_OFFSET_NONE + ZMK_ENDPOINT_NONE_COUNT) +#define INSTANCE_INDEX_OFFSET_BLE (INSTANCE_INDEX_OFFSET_USB + ZMK_ENDPOINT_USB_COUNT) int zmk_endpoint_instance_to_index(struct zmk_endpoint_instance endpoint) { switch (endpoint.transport) { + case ZMK_TRANSPORT_NONE: + return INSTANCE_INDEX_OFFSET_NONE; + case ZMK_TRANSPORT_USB: return INSTANCE_INDEX_OFFSET_USB; @@ -118,6 +130,27 @@ int zmk_endpoints_toggle_transport(void) { struct zmk_endpoint_instance zmk_endpoints_selected(void) { return current_instance; } +static struct zmk_endpoint_instance get_instance_from_transport(enum zmk_transport transport) { + struct zmk_endpoint_instance instance = {.transport = transport}; + switch (instance.transport) { +#if IS_ENABLED(CONFIG_ZMK_BLE) + case ZMK_TRANSPORT_BLE: + instance.ble.profile_index = zmk_ble_active_profile_index(); + break; +#endif // IS_ENABLED(CONFIG_ZMK_BLE) + + default: + // No extra data for this transport. + break; + } + + return instance; +} + +struct zmk_endpoint_instance zmk_endpoints_preferred(void) { + return get_instance_from_transport(preferred_transport); +} + static int send_keyboard_report(void) { switch (current_instance.transport) { case ZMK_TRANSPORT_USB: { @@ -146,6 +179,8 @@ static int send_keyboard_report(void) { return -ENOTSUP; #endif /* IS_ENABLED(CONFIG_ZMK_BLE) */ } + case ZMK_TRANSPORT_NONE: + return 0; } LOG_ERR("Unhandled endpoint transport %d", current_instance.transport); @@ -154,6 +189,9 @@ static int send_keyboard_report(void) { static int send_consumer_report(void) { switch (current_instance.transport) { + case ZMK_TRANSPORT_NONE: + return 0; + case ZMK_TRANSPORT_USB: { #if IS_ENABLED(CONFIG_ZMK_USB) int err = zmk_usb_hid_send_consumer_report(); @@ -204,6 +242,9 @@ int zmk_endpoints_send_report(uint16_t usage_page) { #if IS_ENABLED(CONFIG_ZMK_MOUSE) int zmk_endpoints_send_mouse_report() { switch (current_instance.transport) { + case ZMK_TRANSPORT_NONE: + return 0; + case ZMK_TRANSPORT_USB: { #if IS_ENABLED(CONFIG_ZMK_USB) int err = zmk_usb_hid_send_mouse_report(); @@ -281,7 +322,28 @@ static bool is_ble_ready(void) { #endif } +#if IS_ENABLED(CONFIG_ZMK_ENDPOINT_DISABLE_FALLBACK) + static enum zmk_transport get_selected_transport(void) { + switch (preferred_transport) { + case ZMK_TRANSPORT_NONE: + return ZMK_TRANSPORT_NONE; + + case ZMK_TRANSPORT_BLE: + return is_ble_ready() ? ZMK_TRANSPORT_BLE : ZMK_TRANSPORT_NONE; + + case ZMK_TRANSPORT_USB: + return is_usb_ready() ? ZMK_TRANSPORT_USB : ZMK_TRANSPORT_NONE; + } + + LOG_ERR("Unknown transport %d", preferred_transport); + return ZMK_TRANSPORT_NONE; +} + +#else + +static enum zmk_transport get_selected_transport(void) { + if (is_ble_ready()) { if (is_usb_ready()) { LOG_DBG("Both endpoint transports are ready. Using %d", preferred_transport); @@ -300,23 +362,10 @@ static enum zmk_transport get_selected_transport(void) { LOG_DBG("No endpoint transports are ready."); return DEFAULT_TRANSPORT; } +#endif static struct zmk_endpoint_instance get_selected_instance(void) { - struct zmk_endpoint_instance instance = {.transport = get_selected_transport()}; - - switch (instance.transport) { -#if IS_ENABLED(CONFIG_ZMK_BLE) - case ZMK_TRANSPORT_BLE: - instance.ble.profile_index = zmk_ble_active_profile_index(); - break; -#endif // IS_ENABLED(CONFIG_ZMK_BLE) - - default: - // No extra data for this transport. - break; - } - - return instance; + return get_instance_from_transport(get_selected_transport()); } static int zmk_endpoints_init(void) { diff --git a/docs/docs/config/system.md b/docs/docs/config/system.md index ad719541a39..6c613614fa4 100644 --- a/docs/docs/config/system.md +++ b/docs/docs/config/system.md @@ -13,13 +13,14 @@ Definition file: [zmk/app/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/ ### General -| Config | Type | Description | Default | -| ------------------------------------ | ------ | ----------------------------------------------------------------------------- | ------- | -| `CONFIG_ZMK_KEYBOARD_NAME` | string | The name of the keyboard (max 16 characters) | | -| `CONFIG_ZMK_SETTINGS_RESET_ON_START` | bool | Clears all persistent settings from the keyboard at startup | n | -| `CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE` | int | Milliseconds to wait after a setting change before writing it to flash memory | 60000 | -| `CONFIG_ZMK_WPM` | bool | Enable calculating words per minute | n | -| `CONFIG_HEAP_MEM_POOL_SIZE` | int | Size of the heap memory pool | 8192 | +| Config | Type | Description | Default | +| -------------------------------------- | ------ | --------------------------------------------------------------------------------------------- | ------- | +| `CONFIG_ZMK_KEYBOARD_NAME` | string | The name of the keyboard (max 16 characters) | | +| `CONFIG_ZMK_SETTINGS_RESET_ON_START` | bool | Clears all persistent settings from the keyboard at startup | n | +| `CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE` | int | Milliseconds to wait after a setting change before writing it to flash memory | 60000 | +| `CONFIG_ZMK_WPM` | bool | Enable calculating words per minute | n | +| `CONFIG_HEAP_MEM_POOL_SIZE` | int | Size of the heap memory pool | 8192 | +| `CONFIG_ZMK_ENDPOINT_DISABLE_FALLBACK` | bool | Disable automatically falling back to the other endpoint if preferred endpoint is unavailable | n | ### HID diff --git a/docs/docs/keymaps/behaviors/outputs.md b/docs/docs/keymaps/behaviors/outputs.md index 20ce8a69681..fa55c98e82f 100644 --- a/docs/docs/keymaps/behaviors/outputs.md +++ b/docs/docs/keymaps/behaviors/outputs.md @@ -12,6 +12,8 @@ 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. +By default, if USB is selected but only BLE is available or vice versa the keyboard will output to the connected output. If this behavior is not desired you can change it so the keyboard will insist on using the selected output even if it not available using [`CONFIG_ZMK_ENDPOINT_DISABLE_FALLBACK`](../../config/system.md#general) + :::note[Powering the keyboard via USB] ZMK is not always able to detect if the other end of a USB connection accepts keyboard input or not. So if you are using USB only to power your keyboard (for example with a charger or a portable power bank), you will want