Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
target_sources(app PRIVATE src/conditional_layer.c)
target_sources(app PRIVATE src/endpoints.c)
target_sources(app PRIVATE src/events/endpoint_changed.c)
target_sources(app PRIVATE src/events/preferred_transport_changed.c)
target_sources(app PRIVATE src/hid_listener.c)
target_sources(app PRIVATE src/keymap.c)
target_sources(app PRIVATE src/events/layer_state_changed.c)
Expand Down
18 changes: 18 additions & 0 deletions app/include/zmk/events/preferred_transport_changed.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright (c) 2026 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#pragma once

#include <zephyr/kernel.h>

#include <zmk/endpoints_types.h>
#include <zmk/event_manager.h>

struct zmk_preferred_transport_changed {
enum zmk_transport transport;
};

ZMK_EVENT_DECLARE(zmk_preferred_transport_changed);
91 changes: 61 additions & 30 deletions app/src/display/widgets/output_status.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,26 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/event_manager.h>
#include <zmk/events/ble_active_profile_changed.h>
#include <zmk/events/endpoint_changed.h>
#include <zmk/events/preferred_transport_changed.h>
#include <zmk/usb.h>
#include <zmk/ble.h>
#include <zmk/endpoints.h>

static const char TRANSPORT_SYMBOL_NONE[] = LV_SYMBOL_CLOSE;
static const char TRANSPORT_SYMBOL_BLE[] = LV_SYMBOL_WIFI;
static const char TRANSPORT_SYMBOL_USB[] = LV_SYMBOL_USB;
static const char TRANSPORT_SYMBOL_UNKNOWN[] = "?";

static const char ACTIVE_PROFILE_CONNECTED[] = LV_SYMBOL_OK;
static const char ACTIVE_PROFILE_DISCONNECTED[] = LV_SYMBOL_CLOSE;
static const char ACTIVE_PROFILE_UNBONDED[] = LV_SYMBOL_SETTINGS;

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;
int active_profile_index;
bool active_profile_connected;
bool active_profile_bonded;
};
Expand All @@ -31,48 +42,65 @@ static struct output_status_state get_state(const zmk_event_t *_eh) {
return (struct output_status_state){
.selected_endpoint = zmk_endpoint_get_selected(),
.preferred_transport = zmk_endpoint_get_preferred_transport(),
.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(),
};
}

static const char *symbol_for_transport(enum zmk_transport transport) {
switch (transport) {
case ZMK_TRANSPORT_NONE:
return TRANSPORT_SYMBOL_NONE;
case ZMK_TRANSPORT_BLE:
return TRANSPORT_SYMBOL_BLE;
case ZMK_TRANSPORT_USB:
return TRANSPORT_SYMBOL_USB;
default:
return TRANSPORT_SYMBOL_UNKNOWN;
}
}

static const char *symbol_for_active_profile_status(const struct output_status_state state) {
if (state.active_profile_bonded) {
if (state.active_profile_connected) {
return ACTIVE_PROFILE_CONNECTED;
} else {
return ACTIVE_PROFILE_DISCONNECTED;
}
} else {
return ACTIVE_PROFILE_UNBONDED;
}
}

static void set_status_symbol(lv_obj_t *label, struct output_status_state state) {
char text[20] = {};

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;
}
const char *transport_symbol = symbol_for_transport(transport);
bool disconnected = transport == ZMK_TRANSPORT_NONE;

// If ble is connected or the preferred_transport show ble information
// This allows you to navigate through the active ble profiles, even when they are unable
// to be connected to.
if (transport == ZMK_TRANSPORT_BLE || state.preferred_transport == ZMK_TRANSPORT_BLE) {
// When the active transport is disconnected always use the ble symbol
// Otherwise display the active transport.
if (disconnected) {
transport_symbol = TRANSPORT_SYMBOL_BLE;
}

switch (transport) {
case ZMK_TRANSPORT_NONE:
strcat(text, LV_SYMBOL_CLOSE);
break;
const char *active_profile_status = symbol_for_active_profile_status(state);

case ZMK_TRANSPORT_USB:
strcat(text, LV_SYMBOL_USB);
if (!connected) {
strcat(text, " " LV_SYMBOL_CLOSE);
}
break;
snprintf(text, sizeof(text), "%s %i %s", transport_symbol, state.active_profile_index + 1,
active_profile_status);

case ZMK_TRANSPORT_BLE:
if (state.active_profile_bonded) {
if (state.active_profile_connected) {
snprintf(text, sizeof(text), LV_SYMBOL_WIFI " %i " LV_SYMBOL_OK,
state.selected_endpoint.ble.profile_index + 1);
} else {
snprintf(text, sizeof(text), LV_SYMBOL_WIFI " %i " LV_SYMBOL_CLOSE,
state.selected_endpoint.ble.profile_index + 1);
}
} else {
snprintf(text, sizeof(text), LV_SYMBOL_WIFI " %i " LV_SYMBOL_SETTINGS,
state.selected_endpoint.ble.profile_index + 1);
}
break;
} else if (disconnected && state.preferred_transport != ZMK_TRANSPORT_NONE) {
// Indicate that we are disconnected from a preferred transport
snprintf(text, sizeof(text), "%s " LV_SYMBOL_CLOSE,
symbol_for_transport(state.preferred_transport));
} else {
strcat(text, transport_symbol);
}

lv_label_set_text(label, text);
Expand All @@ -86,6 +114,9 @@ static void output_status_update_cb(struct output_status_state state) {
ZMK_DISPLAY_WIDGET_LISTENER(widget_output_status, struct output_status_state,
output_status_update_cb, get_state)
ZMK_SUBSCRIPTION(widget_output_status, zmk_endpoint_changed);
// Update when the preferred transport changes but doesn't trigger an endpoint change
// eg. Connected to usb with ble preferred to preferred being usb.
ZMK_SUBSCRIPTION(widget_output_status, zmk_preferred_transport_changed);
// We don't get an endpoint changed event when the active profile connects/disconnects
// but there wasn't another endpoint to switch from/to, so update on BLE events too.
#if defined(CONFIG_ZMK_BLE)
Expand Down
5 changes: 4 additions & 1 deletion app/src/endpoints.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <zmk/events/ble_active_profile_changed.h>
#include <zmk/events/usb_conn_state_changed.h>
#include <zmk/events/endpoint_changed.h>
#include <zmk/events/preferred_transport_changed.h>

#include <zephyr/logging/log.h>

Expand Down Expand Up @@ -135,7 +136,8 @@ int zmk_endpoint_set_preferred_transport(enum zmk_transport transport) {

endpoints_save_preferred();

update_current_endpoint();
raise_zmk_preferred_transport_changed(
(struct zmk_preferred_transport_changed){.transport = preferred_transport});
Comment on lines +139 to +140
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did the update_current_endpoint() call get removed? Shouldn't it be kept?

Suggested change
raise_zmk_preferred_transport_changed(
(struct zmk_preferred_transport_changed){.transport = preferred_transport});
update_current_endpoint();
raise_zmk_preferred_transport_changed(
(struct zmk_preferred_transport_changed){.transport = preferred_transport});

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seemed more consistent to let update_current_endpoint be called through the endpoint_listener (line 507), now there is an event. That subscription could be removed and update_current_endpoint could be called directly.
Though I would say that it should be called after raising the transport changed event so that if the endpoint does change, a subscriber sees the transport change event before the endpoint change event.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it, I couldn't see where this call had gone in the diff but that makes sense now.

Given that this new event affects details of the preferred transport feature (and is not just fired away for displays to listen to) you may want to split in that change into a separate commit to facilitate the review and hopefully speed up the merge

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can do.


return 0;
}
Expand Down Expand Up @@ -502,6 +504,7 @@ static int endpoint_listener(const zmk_event_t *eh) {
}

ZMK_LISTENER(endpoint_listener, endpoint_listener);
ZMK_SUBSCRIPTION(endpoint_listener, zmk_preferred_transport_changed);
#if IS_ENABLED(CONFIG_ZMK_USB)
ZMK_SUBSCRIPTION(endpoint_listener, zmk_usb_conn_state_changed);
#endif
Expand Down
10 changes: 10 additions & 0 deletions app/src/events/preferred_transport_changed.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright (c) 2026 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#include <zephyr/kernel.h>
#include <zmk/events/preferred_transport_changed.h>

ZMK_EVENT_IMPL(zmk_preferred_transport_changed);