From 018a93fc9ff21f6ad241866a196436469ae9e405 Mon Sep 17 00:00:00 2001 From: Joel Spadin Date: Thu, 27 Nov 2025 22:33:26 -0600 Subject: [PATCH 1/3] feat(endpoints): add "no endpoint" value This adds ZMK_TRANSPORT_NONE, which can be set as the preferred endpoint transport if you wish to prevent the keyboard from sending any output. More usefully, it also is used to indicate that the preferred endpoint is not available and it could not fall back to an available one. To go along with this, many endpoint functions are renamed for consistency, and a few new functions are added: - zmk_endpoint_get_preferred_transport() returns the value that was set with zmk_endpoint_set_preferred_transport(). - zmk_endpoint_get_preferred() returns the endpoint that will be used if it is available. This endpoint always has the same transport as zmk_endpoint_get_preferred_transport(). - zmk_endpoint_is_connected() is a shortcut to check if the keyboard is actually connected to an endpoint. This change is based on #2572 but without the option to disable endpoint fallback. It does refactor code to allow adding that feature later. --- .../corneish_zen/widgets/output_status.c | 2 +- app/boards/shields/nice_view/widgets/status.c | 2 +- app/include/zmk/endpoints.h | 55 ++++++-- app/include/zmk/endpoints_types.h | 1 + app/src/behaviors/behavior_outputs.c | 6 +- app/src/display/widgets/output_status.c | 30 ++++- app/src/endpoints.c | 125 ++++++++++++------ app/src/hid_indicators.c | 2 +- app/src/hid_listener.c | 12 +- app/src/pm.c | 2 +- app/src/pointing/input_listener.c | 2 +- app/src/pointing/resolution_multipliers.c | 2 +- app/src/studio/rpc.c | 2 +- .../ble/split/set-hid-indicators/snapshot.log | 1 + 14 files changed, 169 insertions(+), 75 deletions(-) diff --git a/app/boards/lowprokb/corneish_zen/widgets/output_status.c b/app/boards/lowprokb/corneish_zen/widgets/output_status.c index a54aaada0..50bd4fad6 100644 --- a/app/boards/lowprokb/corneish_zen/widgets/output_status.c +++ b/app/boards/lowprokb/corneish_zen/widgets/output_status.c @@ -45,7 +45,7 @@ struct output_status_state { static struct output_status_state get_state(const zmk_event_t *_eh) { return (struct output_status_state){ - .selected_endpoint = zmk_endpoints_selected(), + .selected_endpoint = zmk_endpoint_get_selected(), .active_profile_connected = zmk_ble_active_profile_is_connected(), .active_profile_bonded = !zmk_ble_active_profile_is_open(), }; diff --git a/app/boards/shields/nice_view/widgets/status.c b/app/boards/shields/nice_view/widgets/status.c index 601ff546e..179ecb0ae 100644 --- a/app/boards/shields/nice_view/widgets/status.c +++ b/app/boards/shields/nice_view/widgets/status.c @@ -261,7 +261,7 @@ static void output_status_update_cb(struct output_status_state state) { static struct output_status_state output_status_get_state(const zmk_event_t *_eh) { struct output_status_state state = { - .selected_endpoint = zmk_endpoints_selected(), + .selected_endpoint = zmk_endpoint_get_selected(), .active_profile_index = zmk_ble_active_profile_index(), .active_profile_connected = zmk_ble_active_profile_is_connected(), .active_profile_bonded = !zmk_ble_active_profile_is_open(), diff --git a/app/include/zmk/endpoints.h b/app/include/zmk/endpoints.h index a2ef3181a..c10e387dc 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); @@ -57,21 +60,53 @@ int zmk_endpoint_instance_to_str(struct zmk_endpoint_instance endpoint, char *st int zmk_endpoint_instance_to_index(struct zmk_endpoint_instance endpoint); /** - * Sets the preferred endpoint transport to use. (If the preferred endpoint is - * not available, a different one may automatically be selected.) + * Sets the preferred endpoint transport to use. + * + * If the preferred endpoint is not available, zmk_endpoint_get_selected() may + * automatically fall back to another transport. */ -int zmk_endpoints_select_transport(enum zmk_transport transport); -int zmk_endpoints_toggle_transport(void); +int zmk_endpoint_set_preferred_transport(enum zmk_transport transport); + +enum zmk_transport zmk_endpoint_get_preferred_transport(void); /** - * Gets the currently-selected endpoint. + * If the preferred endpoint transport is USB, sets it to BLE, else sets it to USB. */ -struct zmk_endpoint_instance zmk_endpoints_selected(void); +int zmk_endpoint_toggle_preferred_transport(void); -int zmk_endpoints_send_report(uint16_t usage_page); +/** + * Gets the endpoint instance that will be preferred if it is connected. + */ +struct zmk_endpoint_instance zmk_endpoint_get_preferred(void); + +/** + * Gets the endpoint instance that is currently in use. + * + * This may differ from zmk_endpoint_get_preferred(), for example if the preferred + * endpoint is not connected, then this will return an instance for ZMK_TRANSPORT_NONE. + */ +struct zmk_endpoint_instance zmk_endpoint_get_selected(void); + +/** + * Returns whether the keyboard is connected to an endpoint. + * + * This is equivalent to zmk_endpoint_get_selected().transport != ZMK_TRANSPORT_NONE + */ +bool zmk_endpoint_is_connected(void); + +/** + * Sends the HID report for the given usage page to the selected endpoint. + */ +int zmk_endpoint_send_report(uint16_t usage_page); #if IS_ENABLED(CONFIG_ZMK_POINTING) -int zmk_endpoints_send_mouse_report(); +/** + * Sends the HID mouse report to the selected endpoint. + */ +int zmk_endpoint_send_mouse_report(); #endif // IS_ENABLED(CONFIG_ZMK_POINTING) -void zmk_endpoints_clear_current(void); +/** + * Clears all HID reports for the selected endpoint. + */ +void zmk_endpoint_clear_reports(void); diff --git a/app/include/zmk/endpoints_types.h b/app/include/zmk/endpoints_types.h index ea51c8aa9..a88143367 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/behaviors/behavior_outputs.c b/app/src/behaviors/behavior_outputs.c index c61127581..a8d1b36c6 100644 --- a/app/src/behaviors/behavior_outputs.c +++ b/app/src/behaviors/behavior_outputs.c @@ -60,11 +60,11 @@ 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_transport(); + return zmk_endpoint_toggle_preferred_transport(); case OUT_USB: - return zmk_endpoints_select_transport(ZMK_TRANSPORT_USB); + return zmk_endpoint_set_preferred_transport(ZMK_TRANSPORT_USB); case OUT_BLE: - return zmk_endpoints_select_transport(ZMK_TRANSPORT_BLE); + return zmk_endpoint_set_preferred_transport(ZMK_TRANSPORT_BLE); default: LOG_ERR("Unknown output command: %d", binding->param1); } diff --git a/app/src/display/widgets/output_status.c b/app/src/display/widgets/output_status.c index 7b6f94869..189a7dc24 100644 --- a/app/src/display/widgets/output_status.c +++ b/app/src/display/widgets/output_status.c @@ -22,25 +22,43 @@ static sys_slist_t widgets = SYS_SLIST_STATIC_INIT(&widgets); struct output_status_state { struct zmk_endpoint_instance selected_endpoint; + enum zmk_transport preferred_transport; bool active_profile_connected; bool active_profile_bonded; }; static struct output_status_state get_state(const zmk_event_t *_eh) { - return (struct output_status_state){.selected_endpoint = zmk_endpoints_selected(), - .active_profile_connected = - zmk_ble_active_profile_is_connected(), - .active_profile_bonded = !zmk_ble_active_profile_is_open()}; - ; + return (struct output_status_state){ + .selected_endpoint = zmk_endpoint_get_selected(), + .preferred_transport = zmk_endpoint_get_preferred_transport(), + .active_profile_connected = zmk_ble_active_profile_is_connected(), + .active_profile_bonded = !zmk_ble_active_profile_is_open(), + }; } static void set_status_symbol(lv_obj_t *label, struct output_status_state state) { char text[20] = {}; - switch (state.selected_endpoint.transport) { + enum zmk_transport transport = state.selected_endpoint.transport; + bool connected = transport != ZMK_TRANSPORT_NONE; + + // If we aren't connected, show what we're *trying* to connect to. + if (!connected) { + transport = state.preferred_transport; + } + + switch (transport) { + case ZMK_TRANSPORT_NONE: + strcat(text, LV_SYMBOL_CLOSE); + break; + case ZMK_TRANSPORT_USB: strcat(text, LV_SYMBOL_USB); + if (!connected) { + strcat(text, " " LV_SYMBOL_CLOSE); + } break; + case ZMK_TRANSPORT_BLE: if (state.active_profile_bonded) { if (state.active_profile_connected) { diff --git a/app/src/endpoints.c b/app/src/endpoints.c index ae0e5e7fd..ce2f4b9ca 100644 --- a/app/src/endpoints.c +++ b/app/src/endpoints.c @@ -15,16 +15,15 @@ #include #include #include +#include #include #include #include #include #include -LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); -#define DEFAULT_TRANSPORT \ - COND_CODE_1(IS_ENABLED(CONFIG_ZMK_BLE), (ZMK_TRANSPORT_BLE), (ZMK_TRANSPORT_USB)) +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); static struct zmk_endpoint_instance current_instance = {}; static enum zmk_transport preferred_transport = @@ -54,6 +53,7 @@ bool zmk_endpoint_instance_eq(struct zmk_endpoint_instance a, struct zmk_endpoin } switch (a.transport) { + case ZMK_TRANSPORT_NONE: case ZMK_TRANSPORT_USB: return true; @@ -67,6 +67,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 +81,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; @@ -94,7 +101,7 @@ int zmk_endpoint_instance_to_index(struct zmk_endpoint_instance endpoint) { return 0; } -int zmk_endpoints_select_transport(enum zmk_transport transport) { +int zmk_endpoint_set_preferred_transport(enum zmk_transport transport) { LOG_DBG("Selected endpoint transport %d", transport); if (preferred_transport == transport) { @@ -110,16 +117,44 @@ int zmk_endpoints_select_transport(enum zmk_transport transport) { return 0; } -int zmk_endpoints_toggle_transport(void) { +enum zmk_transport zmk_endpoint_get_preferred_transport(void) { return preferred_transport; } + +int zmk_endpoint_toggle_preferred_transport(void) { enum zmk_transport new_transport = (preferred_transport == ZMK_TRANSPORT_USB) ? ZMK_TRANSPORT_BLE : ZMK_TRANSPORT_USB; - return zmk_endpoints_select_transport(new_transport); + return zmk_endpoint_set_preferred_transport(new_transport); } -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) { + case ZMK_TRANSPORT_BLE: +#if IS_ENABLED(CONFIG_ZMK_BLE) + instance.ble.profile_index = zmk_ble_active_profile_index(); +#endif // IS_ENABLED(CONFIG_ZMK_BLE) + break; + + default: + // No extra data for this transport. + break; + } + + return instance; +} + +struct zmk_endpoint_instance zmk_endpoint_get_preferred(void) { + return get_instance_from_transport(preferred_transport); +} + +struct zmk_endpoint_instance zmk_endpoint_get_selected(void) { return current_instance; } + +bool zmk_endpoint_is_connected(void) { return current_instance.transport != ZMK_TRANSPORT_NONE; } static int send_keyboard_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_keyboard_report(); @@ -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(); @@ -186,8 +224,7 @@ static int send_consumer_report(void) { return -ENOTSUP; } -int zmk_endpoints_send_report(uint16_t usage_page) { - +int zmk_endpoint_send_report(uint16_t usage_page) { LOG_DBG("usage page 0x%02X", usage_page); switch (usage_page) { case HID_USAGE_KEY: @@ -202,8 +239,11 @@ int zmk_endpoints_send_report(uint16_t usage_page) { } #if IS_ENABLED(CONFIG_ZMK_POINTING) -int zmk_endpoints_send_mouse_report() { +int zmk_endpoint_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(); @@ -282,41 +322,40 @@ static bool is_ble_ready(void) { } static enum zmk_transport get_selected_transport(void) { - if (is_ble_ready()) { + switch (preferred_transport) { + case ZMK_TRANSPORT_NONE: + LOG_DBG("No endpoint transport selected"); + return ZMK_TRANSPORT_NONE; + + case ZMK_TRANSPORT_USB: if (is_usb_ready()) { - LOG_DBG("Both endpoint transports are ready. Using %d", preferred_transport); - return preferred_transport; + LOG_DBG("USB is preferred and ready"); + return ZMK_TRANSPORT_USB; } + if (is_ble_ready()) { + LOG_DBG("USB is not ready. Falling back to BLE"); + return ZMK_TRANSPORT_BLE; + } + break; - LOG_DBG("Only BLE is ready."); - return ZMK_TRANSPORT_BLE; + case ZMK_TRANSPORT_BLE: + if (is_ble_ready()) { + LOG_DBG("BLE is preferred and ready"); + return ZMK_TRANSPORT_BLE; + } + if (is_usb_ready()) { + LOG_DBG("BLE is not ready. Falling back to USB"); + return ZMK_TRANSPORT_USB; + } + break; } - if (is_usb_ready()) { - LOG_DBG("Only USB is ready."); - return ZMK_TRANSPORT_USB; - } - - LOG_DBG("No endpoint transports are ready."); - return DEFAULT_TRANSPORT; + LOG_DBG("Preferred endpoint transport is %d but no transports are ready", preferred_transport); + return ZMK_TRANSPORT_NONE; } 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) { @@ -329,15 +368,15 @@ static int zmk_endpoints_init(void) { return 0; } -void zmk_endpoints_clear_current(void) { +void zmk_endpoint_clear_reports(void) { zmk_hid_keyboard_clear(); zmk_hid_consumer_clear(); #if IS_ENABLED(CONFIG_ZMK_POINTING) zmk_hid_mouse_clear(); #endif // IS_ENABLED(CONFIG_ZMK_POINTING) - zmk_endpoints_send_report(HID_USAGE_KEY); - zmk_endpoints_send_report(HID_USAGE_CONSUMER); + zmk_endpoint_send_report(HID_USAGE_KEY); + zmk_endpoint_send_report(HID_USAGE_CONSUMER); } static void update_current_endpoint(void) { @@ -345,7 +384,7 @@ static void update_current_endpoint(void) { if (!zmk_endpoint_instance_eq(new_instance, current_instance)) { // Cancel all current keypresses so keys don't stay held on the old endpoint. - zmk_endpoints_clear_current(); + zmk_endpoint_clear_reports(); current_instance = new_instance; diff --git a/app/src/hid_indicators.c b/app/src/hid_indicators.c index 74f1d92be..23149cca5 100644 --- a/app/src/hid_indicators.c +++ b/app/src/hid_indicators.c @@ -19,7 +19,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); static zmk_hid_indicators_t hid_indicators[ZMK_ENDPOINT_COUNT]; zmk_hid_indicators_t zmk_hid_indicators_get_current_profile(void) { - return zmk_hid_indicators_get_profile(zmk_endpoints_selected()); + return zmk_hid_indicators_get_profile(zmk_endpoint_get_selected()); } zmk_hid_indicators_t zmk_hid_indicators_get_profile(struct zmk_endpoint_instance endpoint) { diff --git a/app/src/hid_listener.c b/app/src/hid_listener.c index 2d17a3954..cd463d8ee 100644 --- a/app/src/hid_listener.c +++ b/app/src/hid_listener.c @@ -28,7 +28,7 @@ static int hid_listener_keycode_pressed(const struct zmk_keycode_state_changed * LOG_DBG("Unable to pre-release keycode (%d)", err); return err; } - err = zmk_endpoints_send_report(ev->usage_page); + err = zmk_endpoint_send_report(ev->usage_page); if (err < 0) { LOG_ERR("Failed to send key report for pre-releasing keycode (%d)", err); } @@ -45,14 +45,14 @@ static int hid_listener_keycode_pressed(const struct zmk_keycode_state_changed * implicit_mods_changed = zmk_hid_implicit_modifiers_press(ev->implicit_modifiers); if (ev->usage_page != HID_USAGE_KEY && (explicit_mods_changed > 0 || implicit_mods_changed > 0)) { - err = zmk_endpoints_send_report(HID_USAGE_KEY); + err = zmk_endpoint_send_report(HID_USAGE_KEY); if (err < 0) { LOG_ERR("Failed to send key report for changed mofifiers for consumer page event (%d)", err); } } - return zmk_endpoints_send_report(ev->usage_page); + return zmk_endpoint_send_report(ev->usage_page); } static int hid_listener_keycode_released(const struct zmk_keycode_state_changed *ev) { @@ -70,7 +70,7 @@ static int hid_listener_keycode_released(const struct zmk_keycode_state_changed // send report of normal key release early to fix the issue // of some programs recognizing the implicit_mod release before the actual key release - err = zmk_endpoints_send_report(ev->usage_page); + err = zmk_endpoint_send_report(ev->usage_page); if (err < 0) { LOG_ERR("Failed to send key report for the released keycode (%d)", err); } @@ -87,13 +87,13 @@ static int hid_listener_keycode_released(const struct zmk_keycode_state_changed if (ev->usage_page != HID_USAGE_KEY && (explicit_mods_changed > 0 || implicit_mods_changed > 0)) { - err = zmk_endpoints_send_report(HID_USAGE_KEY); + err = zmk_endpoint_send_report(HID_USAGE_KEY); if (err < 0) { LOG_ERR("Failed to send key report for changed mofifiers for consumer page event (%d)", err); } } - return zmk_endpoints_send_report(ev->usage_page); + return zmk_endpoint_send_report(ev->usage_page); } int hid_listener(const zmk_event_t *eh) { diff --git a/app/src/pm.c b/app/src/pm.c index f4389e7ee..eaae3b5c1 100644 --- a/app/src/pm.c +++ b/app/src/pm.c @@ -93,7 +93,7 @@ int zmk_pm_soft_off(void) { const struct device *devs; #if !IS_ENABLED(CONFIG_ZMK_SPLIT) || IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) - zmk_endpoints_clear_current(); + zmk_endpoint_clear_reports(); // Need to sleep to give any other threads a chance so submit endpoint data. k_sleep(K_MSEC(100)); #endif diff --git a/app/src/pointing/input_listener.c b/app/src/pointing/input_listener.c index 431d9a746..cd18f791e 100644 --- a/app/src/pointing/input_listener.c +++ b/app/src/pointing/input_listener.c @@ -325,7 +325,7 @@ static void input_handler(const struct input_listener_config *config, } } - zmk_endpoints_send_mouse_report(); + zmk_endpoint_send_mouse_report(); zmk_hid_mouse_scroll_set(0, 0); zmk_hid_mouse_movement_set(0, 0); diff --git a/app/src/pointing/resolution_multipliers.c b/app/src/pointing/resolution_multipliers.c index 3ebda9d57..fa7984623 100644 --- a/app/src/pointing/resolution_multipliers.c +++ b/app/src/pointing/resolution_multipliers.c @@ -23,7 +23,7 @@ static struct zmk_pointing_resolution_multipliers multipliers[ZMK_ENDPOINT_COUNT struct zmk_pointing_resolution_multipliers zmk_pointing_resolution_multipliers_get_current_profile(void) { - return zmk_pointing_resolution_multipliers_get_profile(zmk_endpoints_selected()); + return zmk_pointing_resolution_multipliers_get_profile(zmk_endpoint_get_selected()); } struct zmk_pointing_resolution_multipliers diff --git a/app/src/studio/rpc.c b/app/src/studio/rpc.c index 8dd711e1e..e79b5568c 100644 --- a/app/src/studio/rpc.c +++ b/app/src/studio/rpc.c @@ -243,7 +243,7 @@ K_THREAD_DEFINE(studio_rpc_thread, CONFIG_ZMK_STUDIO_RPC_THREAD_STACK_SIZE, rpc_ NULL, K_LOWEST_APPLICATION_THREAD_PRIO, 0, 0); static void refresh_selected_transport(void) { - enum zmk_transport transport = zmk_endpoints_selected().transport; + enum zmk_transport transport = zmk_endpoint_get_selected().transport; k_mutex_lock(&rpc_transport_mutex, K_FOREVER); diff --git a/app/tests/ble/split/set-hid-indicators/snapshot.log b/app/tests/ble/split/set-hid-indicators/snapshot.log index abec1666d..580c139eb 100644 --- a/app/tests/ble/split/set-hid-indicators/snapshot.log +++ b/app/tests/ble/split/set-hid-indicators/snapshot.log @@ -9,4 +9,5 @@ peripheral 0 zmk: Welcome to ZMK! peripheral 0 zmk: security_changed: Security changed: FD:9E:B2:48:47:39 (random) level 2 peripheral 0 zmk: split_svc_pos_state_ccc: value 1 peripheral 0 zmk: split_svc_select_phys_layout_callback: Selecting physical layout after GATT write of 0 +peripheral 0 zmk: split_svc_update_indicators_callback: Raising HID indicators changed event: 0 peripheral 0 zmk: split_svc_update_indicators_callback: Raising HID indicators changed event: 7 From 1e984a90d927b3def5035b4d04ef74facb7fc94d Mon Sep 17 00:00:00 2001 From: Joel Spadin Date: Sun, 18 Jan 2026 12:08:25 -0600 Subject: [PATCH 2/3] fix(endpoints): Add endpoint setting upgrade Adding ZMK_TRANSPORT_NONE at the start of enum zmk_transport results in the preferred transport setting no longer representing the same values when it is saved with earlier firmware and loaded with newer firmware. To fix this, the "endpoints/preferred" setting is now deprecated and replaced by "endpoints/preferred2". If the old setting is present, it is interpreted as the old enum type, upgraded to the new type, and saved immediately to the new setting. The old setting is then deleted. To avoid this happening again in the future, enum zmk_transport now has explicit values assigned to identifier, and a comment is also added to explain that existing values must not be changed. --- app/include/zmk/endpoints_types.h | 7 +- app/src/endpoints.c | 128 +++++++++++++++++++++++++----- 2 files changed, 114 insertions(+), 21 deletions(-) diff --git a/app/include/zmk/endpoints_types.h b/app/include/zmk/endpoints_types.h index a88143367..5117544b7 100644 --- a/app/include/zmk/endpoints_types.h +++ b/app/include/zmk/endpoints_types.h @@ -8,11 +8,12 @@ /** * The method by which data is sent. + * @note This type is used in settings. Do not modify existing values. */ enum zmk_transport { - ZMK_TRANSPORT_NONE, - ZMK_TRANSPORT_USB, - ZMK_TRANSPORT_BLE, + ZMK_TRANSPORT_NONE = 0, + ZMK_TRANSPORT_USB = 1, + ZMK_TRANSPORT_BLE = 2, }; /** diff --git a/app/src/endpoints.c b/app/src/endpoints.c index ce2f4b9ca..69e053760 100644 --- a/app/src/endpoints.c +++ b/app/src/endpoints.c @@ -25,15 +25,30 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); +// Name of the subtree for endpoint-related settings +#define SETTING_SUBTREE "endpoints" + +// Key of the setting to store the preferred_transport value. +#define SETTING_PREFERRED_TRANSPORT_KEY "preferred2" +// Full name of the setting to store the preferred_transport value. +#define SETTING_PREFERRED_TRANSPORT SETTING_SUBTREE "/" SETTING_PREFERRED_TRANSPORT_KEY + +// Key of the deprecated setting which stored preferred_transport with an older type. +#define SETTING_PREFERRED_TRANSPORT_V1_KEY "preferred" +// Full name of the deprecated setting which stored preferred_transport with an older type. +#define SETTING_PREFERRED_TRANSPORT_V1 SETTING_SUBTREE "/" SETTING_PREFERRED_TRANSPORT_V1_KEY + static struct zmk_endpoint_instance current_instance = {}; -static enum zmk_transport preferred_transport = - ZMK_TRANSPORT_USB; /* Used if multiple endpoints are ready */ + +// Transport to use if multiple endpoints are ready +static enum zmk_transport preferred_transport = ZMK_TRANSPORT_USB; static void update_current_endpoint(void); #if IS_ENABLED(CONFIG_SETTINGS) static void endpoints_save_preferred_work(struct k_work *work) { - settings_save_one("endpoints/preferred", &preferred_transport, sizeof(preferred_transport)); + settings_save_one(SETTING_PREFERRED_TRANSPORT, &preferred_transport, + sizeof(preferred_transport)); } static struct k_work_delayable endpoints_save_work; @@ -279,29 +294,106 @@ int zmk_endpoint_send_mouse_report() { #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", name); +// Type for the deprecated SETTING_PREFERRED_TRANSPORT_V1 setting. To maintain backwards +// compatibility when ZMK_TRANSPORT_NONE was inserted into the beginning of enum zmk_transport, the +// setting was moved to SETTING_PREFERRED_TRANSPORT. If the deprecated setting exists, it must be +// upgraded to the new type and setting name. +enum transport_v1 { + TRANSPORT_V1_USB = 0, + TRANSPORT_V1_BLE = 1, +}; - if (settings_name_steq(name, "preferred", NULL)) { - if (len != sizeof(enum zmk_transport)) { - LOG_ERR("Invalid endpoint size (got %d expected %d)", len, sizeof(enum zmk_transport)); - return -EINVAL; - } +static enum zmk_transport upgrade_transport_v1(enum transport_v1 value) { + switch (value) { + case TRANSPORT_V1_USB: + return ZMK_TRANSPORT_USB; - int err = read_cb(cb_arg, &preferred_transport, sizeof(enum zmk_transport)); - if (err <= 0) { - LOG_ERR("Failed to read preferred endpoint from settings (err %d)", err); - return err; - } + case TRANSPORT_V1_BLE: + return ZMK_TRANSPORT_BLE; + }; - update_current_endpoint(); + LOG_ERR("Invalid transport_v1 value: %d", value); + return ZMK_TRANSPORT_USB; +} + +/** + * Loads the deprecated SETTING_PREFERRED_TRANSPORT_V1 setting and upgrades it to the new type, + * storing the value in SETTING_PREFERRED_TRANSPORT and deleting the original setting. + */ +static int endpoint_settings_load_preferred_v1(size_t len, settings_read_cb read_cb, void *cb_arg) { + enum transport_v1 value; + + if (len != sizeof(value)) { + LOG_ERR("Invalid zmk_transport size (got %zu expected %zu)", len, sizeof(value)); + return -EINVAL; + } + + ssize_t len_read = read_cb(cb_arg, &value, sizeof(value)); + if (len_read < 0) { + LOG_ERR("Failed to read preferred endpoint v1 from settings (err %d)", len_read); + return len_read; + } + + preferred_transport = upgrade_transport_v1(value); + + int err = settings_delete(SETTING_PREFERRED_TRANSPORT_V1); + if (err != 0) { + LOG_ERR("Failed to delete preferred endpoint v1 setting (err %d)", err); + return err; + } + + err = settings_save_one(SETTING_PREFERRED_TRANSPORT, &preferred_transport, + sizeof(preferred_transport)); + if (err == 0) { + LOG_INF("Upgraded preferred endpoint setting"); + } else { + LOG_ERR("Failed to save upgraded endpoint value (err %d)", err); + } + + return err; +} + +/** + * Loads the SETTING_PREFERRED_TRANSPORT setting. + */ +static int endpoint_settings_load_preferred_v2(size_t len, settings_read_cb read_cb, void *cb_arg) { + if (len != sizeof(preferred_transport)) { + LOG_ERR("Invalid zmk_transport size (got %zu expected %zu)", len, + sizeof(preferred_transport)); + return -EINVAL; + } + + ssize_t len_read = read_cb(cb_arg, &preferred_transport, sizeof(preferred_transport)); + if (len_read < 0) { + LOG_ERR("Failed to read preferred endpoint from settings (err %d)", len_read); + return len_read; } return 0; } -SETTINGS_STATIC_HANDLER_DEFINE(endpoints, "endpoints", NULL, endpoints_handle_set, NULL, NULL); +static int endpoint_settings_set(const char *name, size_t len, settings_read_cb read_cb, + void *cb_arg) { + LOG_DBG("Setting endpoint value %s", name); + + if (settings_name_steq(name, SETTING_PREFERRED_TRANSPORT_KEY, NULL)) { + return endpoint_settings_load_preferred_v2(len, read_cb, cb_arg); + } + + if (settings_name_steq(name, SETTING_PREFERRED_TRANSPORT_V1_KEY, NULL)) { + return endpoint_settings_load_preferred_v1(len, read_cb, cb_arg); + } + + return 0; +} + +static int endpoint_settings_commit(void) { + update_current_endpoint(); + return 0; +} + +SETTINGS_STATIC_HANDLER_DEFINE(endpoints, SETTING_SUBTREE, NULL, endpoint_settings_set, + endpoint_settings_commit, NULL); #endif /* IS_ENABLED(CONFIG_SETTINGS) */ From 96e118958dc44f7cd5c75ab456c0799b5c16fffc Mon Sep 17 00:00:00 2001 From: Joel Spadin Date: Sun, 1 Feb 2026 18:21:28 -0600 Subject: [PATCH 3/3] fix: Set default transport according to enabled transports The default value for preferred_transport is now set to USB only if CONFIG_ZMK_USB is enabled. If not, it falls back to BLE if CONFIG_ZMK_BLE is enabled, then to "none" if nothing is enabled. --- app/src/endpoints.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/endpoints.c b/app/src/endpoints.c index 69e053760..17cdc5d7f 100644 --- a/app/src/endpoints.c +++ b/app/src/endpoints.c @@ -38,10 +38,18 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); // Full name of the deprecated setting which stored preferred_transport with an older type. #define SETTING_PREFERRED_TRANSPORT_V1 SETTING_SUBTREE "/" SETTING_PREFERRED_TRANSPORT_V1_KEY +#if IS_ENABLED(CONFIG_ZMK_USB) +#define DEFAULT_TRANSPORT ZMK_TRANSPORT_USB +#elif IS_ENABLED(CONFIG_ZMK_BLE) +#define DEFAULT_TRANSPORT ZMK_TRANSPORT_BLE +#else +#define DEFAULT_TRANSPORT ZMK_TRANSPORT_NONE +#endif + static struct zmk_endpoint_instance current_instance = {}; // Transport to use if multiple endpoints are ready -static enum zmk_transport preferred_transport = ZMK_TRANSPORT_USB; +static enum zmk_transport preferred_transport = DEFAULT_TRANSPORT; static void update_current_endpoint(void);