diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index de19f813..ff9d59d0 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -28,7 +28,6 @@ target_sources(app PRIVATE src/kscan.c) target_sources(app PRIVATE src/matrix_transform.c) target_sources(app PRIVATE src/hid.c) target_sources(app PRIVATE src/sensors.c) -target_sources_ifdef(CONFIG_ZMK_DISPLAY app PRIVATE src/display.c) target_sources(app PRIVATE src/event_manager.c) target_sources_ifdef(CONFIG_ZMK_EXT_POWER app PRIVATE src/ext_power_generic.c) target_sources(app PRIVATE src/events/position_state_changed.c) @@ -69,4 +68,6 @@ target_sources(app PRIVATE src/hid_listener.c) target_sources_ifdef(CONFIG_SETTINGS app PRIVATE src/settings.c) target_sources(app PRIVATE src/main.c) +add_subdirectory(src/display/) + zephyr_cc_option(-Wfatal-errors) diff --git a/app/Kconfig b/app/Kconfig index af61f49d..5751f025 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -168,14 +168,7 @@ endmenu menu "Display/LED Options" -config ZMK_DISPLAY - bool "ZMK display support" - default n - select DISPLAY - select LVGL - select LVGL_THEMES - select LVGL_THEME_MONO - select LVGL_OBJ_LABEL +rsource "src/display/Kconfig" config ZMK_RGB_UNDERGLOW bool "RGB Adressable LED Underglow" @@ -330,5 +323,6 @@ rsource "boards/shields/*/Kconfig.shield" osource "$(ZMK_CONFIG)/boards/shields/*/Kconfig.defconfig" osource "$(ZMK_CONFIG)/boards/shields/*/Kconfig.shield" + source "Kconfig.zephyr" diff --git a/app/dts/bindings/zmk,keymap.yaml b/app/dts/bindings/zmk,keymap.yaml index fb6d9147..56821ded 100644 --- a/app/dts/bindings/zmk,keymap.yaml +++ b/app/dts/bindings/zmk,keymap.yaml @@ -7,6 +7,9 @@ child-binding: description: "A layer to be used in a keymap" properties: + label: + type: string + required: false bindings: type: phandle-array required: true diff --git a/app/include/zmk/ble.h b/app/include/zmk/ble.h index 56980c69..90afbda0 100644 --- a/app/include/zmk/ble.h +++ b/app/include/zmk/ble.h @@ -14,7 +14,9 @@ int zmk_ble_prof_next(); int zmk_ble_prof_prev(); int zmk_ble_prof_select(u8_t index); +int zmk_ble_active_profile_index(); bt_addr_le_t *zmk_ble_active_profile_addr(); +bool zmk_ble_active_profile_is_open(); bool zmk_ble_active_profile_is_connected(); char *zmk_ble_active_profile_name(); diff --git a/app/include/zmk/display/status_screen.h b/app/include/zmk/display/status_screen.h new file mode 100644 index 00000000..34660ad3 --- /dev/null +++ b/app/include/zmk/display/status_screen.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2020 Peter Johanson + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include + +lv_obj_t *zmk_display_status_screen(); \ No newline at end of file diff --git a/app/include/zmk/display/widgets/battery_status.h b/app/include/zmk/display/widgets/battery_status.h new file mode 100644 index 00000000..b87e87ee --- /dev/null +++ b/app/include/zmk/display/widgets/battery_status.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include + +struct zmk_widget_battery_status { + sys_snode_t node; + lv_obj_t *obj; +}; + +int zmk_widget_battery_status_init(struct zmk_widget_battery_status *widget, lv_obj_t *parent); +lv_obj_t *zmk_widget_battery_status_obj(struct zmk_widget_battery_status *widget); \ No newline at end of file diff --git a/app/include/zmk/display/widgets/output_status.h b/app/include/zmk/display/widgets/output_status.h new file mode 100644 index 00000000..66f09271 --- /dev/null +++ b/app/include/zmk/display/widgets/output_status.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include + +struct zmk_widget_output_status { + sys_snode_t node; + lv_obj_t *obj; +}; + +int zmk_widget_output_status_init(struct zmk_widget_output_status *widget, lv_obj_t *parent); +lv_obj_t *zmk_widget_output_status_obj(struct zmk_widget_output_status *widget); \ No newline at end of file diff --git a/app/include/zmk/endpoints.h b/app/include/zmk/endpoints.h index aad688e7..c252ef09 100644 --- a/app/include/zmk/endpoints.h +++ b/app/include/zmk/endpoints.h @@ -16,5 +16,6 @@ enum zmk_endpoint { int zmk_endpoints_select(enum zmk_endpoint endpoint); int zmk_endpoints_toggle(); +enum zmk_endpoint zmk_endpoints_selected(); int zmk_endpoints_send_report(u8_t usage_report); diff --git a/app/src/ble.c b/app/src/ble.c index b00bd5c2..817cb846 100644 --- a/app/src/ble.c +++ b/app/src/ble.c @@ -100,7 +100,7 @@ static void raise_profile_changed_event_callback(struct k_work *work) { K_WORK_DEFINE(raise_profile_changed_event_work, raise_profile_changed_event_callback); -static bool active_profile_is_open() { +bool zmk_ble_active_profile_is_open() { return !bt_addr_le_cmp(&profiles[active_profile].peer, BT_ADDR_LE_ANY); } @@ -169,7 +169,7 @@ int update_advertising() { struct bt_conn *conn; enum advertising_type desired_adv = ZMK_ADV_NONE; - if (active_profile_is_open()) { + if (zmk_ble_active_profile_is_open()) { desired_adv = ZMK_ADV_CONN; } else if (!zmk_ble_active_profile_is_connected()) { desired_adv = ZMK_ADV_CONN; @@ -226,6 +226,8 @@ int zmk_ble_clear_bonds() { return 0; }; +int zmk_ble_active_profile_index() { return active_profile; } + int zmk_ble_prof_select(u8_t index) { LOG_DBG("profile %d", index); if (active_profile == index) { @@ -448,8 +450,8 @@ static enum bt_security_err auth_pairing_accept(struct bt_conn *conn, struct bt_conn_info info; bt_conn_get_info(conn, &info); - LOG_DBG("role %d, open? %s", info.role, active_profile_is_open() ? "yes" : "no"); - if (info.role == BT_CONN_ROLE_SLAVE && !active_profile_is_open()) { + LOG_DBG("role %d, open? %s", info.role, zmk_ble_active_profile_is_open() ? "yes" : "no"); + if (info.role == BT_CONN_ROLE_SLAVE && !zmk_ble_active_profile_is_open()) { LOG_WRN("Rejecting pairing request to taken profile %d", active_profile); return BT_SECURITY_ERR_PAIR_NOT_ALLOWED; } @@ -472,7 +474,7 @@ static void auth_pairing_complete(struct bt_conn *conn, bool bonded) { } #if !IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_ROLE_PERIPHERAL) - if (!active_profile_is_open()) { + if (!zmk_ble_active_profile_is_open()) { LOG_ERR("Pairing completed but current profile is not open: %s", log_strdup(addr)); bt_unpair(BT_ID_DEFAULT, dst); return; diff --git a/app/src/display/CMakeLists.txt b/app/src/display/CMakeLists.txt new file mode 100644 index 00000000..d14f7d08 --- /dev/null +++ b/app/src/display/CMakeLists.txt @@ -0,0 +1,5 @@ + +target_sources_ifdef(CONFIG_ZMK_DISPLAY app PRIVATE main.c) +target_sources_ifdef(CONFIG_ZMK_DISPLAY_STATUS_SCREEN_BUILT_IN app PRIVATE status_screen.c) + +add_subdirectory(widgets/) \ No newline at end of file diff --git a/app/src/display/Kconfig b/app/src/display/Kconfig new file mode 100644 index 00000000..2e8cd2c5 --- /dev/null +++ b/app/src/display/Kconfig @@ -0,0 +1,32 @@ + +menuconfig ZMK_DISPLAY + bool "Enable ZMK Display" + default n + select DISPLAY + select LVGL + select LVGL_THEMES + select LVGL_THEME_MONO + +if ZMK_DISPLAY + +choice LVGL_TXT_ENC + default LVGL_TXT_ENC_UTF8 + +endchoice + +choice ZMK_DISPLAY_STATUS_SCREEN + prompt "Default status screen for displays" + default ZMK_DISPLAY_STATUS_SCREEN_BUILT_IN + +config ZMK_DISPLAY_STATUS_SCREEN_BUILT_IN + bool "Built in status screen" + select LVGL_OBJ_LABEL + +config ZMK_DISPLAY_STATUS_SCREEN_CUSTOM + bool "Custom status screen" + +endchoice + +rsource "widgets/Kconfig" + +endif diff --git a/app/src/display.c b/app/src/display/main.c similarity index 62% rename from app/src/display.c rename to app/src/display/main.c index ecd19086..001061f7 100644 --- a/app/src/display.c +++ b/app/src/display/main.c @@ -14,16 +14,17 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include #include +#include + #define ZMK_DISPLAY_NAME CONFIG_LVGL_DISPLAY_DEV_NAME static struct device *display; static lv_obj_t *screen; -int zmk_display_init() { - lv_obj_t *hello_world_label; - lv_obj_t *count_label; +__attribute__((weak)) lv_obj_t *zmk_display_status_screen() { return NULL; } +int zmk_display_init() { LOG_DBG(""); display = device_get_binding(ZMK_DISPLAY_NAME); @@ -32,18 +33,19 @@ int zmk_display_init() { return -EINVAL; } - screen = lv_obj_create(NULL, NULL); + screen = zmk_display_status_screen(); + + if (screen == NULL) { + LOG_ERR("No status screen provided"); + return 0; + } + lv_scr_load(screen); - hello_world_label = lv_label_create(lv_scr_act(), NULL); - lv_label_set_text(hello_world_label, "ZMK v0.1.0"); - lv_obj_align(hello_world_label, NULL, LV_ALIGN_CENTER, 0, 0); - count_label = lv_label_create(lv_scr_act(), NULL); - lv_label_set_text(count_label, CONFIG_ZMK_KEYBOARD_NAME); - lv_obj_align(count_label, NULL, LV_ALIGN_IN_BOTTOM_MID, 0, 0); lv_task_handler(); display_blanking_off(display); + LOG_DBG(""); return 0; } diff --git a/app/src/display/status_screen.c b/app/src/display/status_screen.c new file mode 100644 index 00000000..1ce1e833 --- /dev/null +++ b/app/src/display/status_screen.c @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020 Peter Johanson + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include + +#include +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#if IS_ENABLED(CONFIG_ZMK_WIDGET_BATTERY_STATUS) +static struct zmk_widget_battery_status battery_status_widget; +#endif + +#if IS_ENABLED(CONFIG_ZMK_WIDGET_OUTPUT_STATUS) +static struct zmk_widget_output_status output_status_widget; +#endif + +lv_obj_t *zmk_display_status_screen() { + lv_obj_t *screen; + lv_obj_t *zmk_version_label; + + screen = lv_obj_create(NULL, NULL); + + zmk_version_label = lv_label_create(screen, NULL); + +#if IS_ENABLED(CONFIG_ZMK_WIDGET_BATTERY_STATUS) + zmk_widget_battery_status_init(&battery_status_widget, screen); + lv_obj_align(zmk_widget_battery_status_obj(&battery_status_widget), NULL, LV_ALIGN_IN_TOP_RIGHT, + 0, 0); +#endif + +#if IS_ENABLED(CONFIG_ZMK_WIDGET_OUTPUT_STATUS) + zmk_widget_output_status_init(&output_status_widget, screen); + lv_obj_align(zmk_widget_output_status_obj(&output_status_widget), NULL, LV_ALIGN_IN_TOP_LEFT, 0, + 0); +#endif + + lv_label_set_text(zmk_version_label, "ZMK v0.1.0"); + lv_obj_align(zmk_version_label, NULL, LV_ALIGN_IN_BOTTOM_MID, 0, 0); + + return screen; +} diff --git a/app/src/display/widgets/CMakeLists.txt b/app/src/display/widgets/CMakeLists.txt new file mode 100644 index 00000000..32ef761d --- /dev/null +++ b/app/src/display/widgets/CMakeLists.txt @@ -0,0 +1,3 @@ + +target_sources_ifdef(CONFIG_ZMK_WIDGET_BATTERY_STATUS app PRIVATE battery_status.c) +target_sources_ifdef(CONFIG_ZMK_WIDGET_OUTPUT_STATUS app PRIVATE output_status.c) diff --git a/app/src/display/widgets/Kconfig b/app/src/display/widgets/Kconfig new file mode 100644 index 00000000..1dbffb5b --- /dev/null +++ b/app/src/display/widgets/Kconfig @@ -0,0 +1,18 @@ + +menu "ZMK Display Widgets" + +config ZMK_WIDGET_BATTERY_STATUS + bool "Widget for battery charge information, using small icons" + depends on BT + default y if BT + select LVGL_OBJ_LABEL + select LVGL_BUILD_IN_FONT_ROBOTO_16 + +config ZMK_WIDGET_OUTPUT_STATUS + bool "Widget for keyboard output status icons" + depends on BT + default y if BT + select LVGL_OBJ_LABEL + select LVGL_BUILD_IN_FONT_ROBOTO_16 + +endmenu diff --git a/app/src/display/widgets/battery_status.c b/app/src/display/widgets/battery_status.c new file mode 100644 index 00000000..7b1afd11 --- /dev/null +++ b/app/src/display/widgets/battery_status.c @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +#include +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#include +#include +#include +#include +#include + +static sys_slist_t widgets = SYS_SLIST_STATIC_INIT(&widgets); +static lv_style_t label_style; + +void battery_status_init() { + if (label_style.text.font != NULL) { + return; + } + + lv_style_copy(&label_style, &lv_style_plain); + label_style.text.color = LV_COLOR_BLACK; + label_style.text.font = &lv_font_roboto_16; + label_style.text.letter_space = 1; + label_style.text.line_space = 1; +} + +void set_battery_symbol(lv_obj_t *label) { + char text[2] = " "; + u8_t level = bt_gatt_bas_get_battery_level(); + +#if IS_ENABLED(CONFIG_USB) + if (zmk_usb_is_powered()) { + strcpy(text, LV_SYMBOL_CHARGE); + } +#endif /* IS_ENABLED(CONFIG_USB) */ + + if (level > 95) { + strcat(text, LV_SYMBOL_BATTERY_FULL); + } else if (level > 65) { + strcat(text, LV_SYMBOL_BATTERY_3); + } else if (level > 35) { + strcat(text, LV_SYMBOL_BATTERY_2); + } else if (level > 5) { + strcat(text, LV_SYMBOL_BATTERY_1); + } else { + strcat(text, LV_SYMBOL_BATTERY_EMPTY); + } + lv_label_set_text(label, text); +} + +int zmk_widget_battery_status_init(struct zmk_widget_battery_status *widget, lv_obj_t *parent) { + battery_status_init(); + widget->obj = lv_label_create(parent, NULL); + lv_label_set_style(widget->obj, LV_LABEL_STYLE_MAIN, &label_style); + + lv_obj_set_size(widget->obj, 40, 15); + set_battery_symbol(widget->obj); + + sys_slist_append(&widgets, &widget->node); + + return 0; +} + +lv_obj_t *zmk_widget_battery_status_obj(struct zmk_widget_battery_status *widget) { + LOG_DBG("Label: %p", widget->obj); + return widget->obj; +} + +int battery_status_listener(const struct zmk_event_header *eh) { + struct zmk_widget_battery_status *widget; + SYS_SLIST_FOR_EACH_CONTAINER(&widgets, widget, node) { set_battery_symbol(widget->obj); } + return 0; +} + +ZMK_LISTENER(widget_battery_status, battery_status_listener) +ZMK_SUBSCRIPTION(widget_battery_status, battery_state_changed); +#if IS_ENABLED(CONFIG_USB) +ZMK_SUBSCRIPTION(widget_battery_status, usb_conn_state_changed); +#endif /* IS_ENABLED(CONFIG_USB) */ diff --git a/app/src/display/widgets/output_status.c b/app/src/display/widgets/output_status.c new file mode 100644 index 00000000..b00d3fcd --- /dev/null +++ b/app/src/display/widgets/output_status.c @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +#include +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#include +#include +#include +#include +#include +#include +#include + +static sys_slist_t widgets = SYS_SLIST_STATIC_INIT(&widgets); +static lv_style_t label_style; + +void output_status_init() { + if (label_style.text.font != NULL) { + return; + } + + lv_style_copy(&label_style, &lv_style_plain); + label_style.text.color = LV_COLOR_BLACK; + label_style.text.font = &lv_font_roboto_16; + label_style.text.letter_space = 1; + label_style.text.line_space = 1; +} + +void set_status_symbol(lv_obj_t *label) { + enum zmk_endpoint selected_endpoint = zmk_endpoints_selected(); + bool active_profile_connected = zmk_ble_active_profile_is_connected(); + bool active_profie_bonded = !zmk_ble_active_profile_is_open(); + u8_t active_profile_index = zmk_ble_active_profile_index(); + char text[6] = {}; + + switch (selected_endpoint) { + case ZMK_ENDPOINT_USB: + LOG_DBG("USB, BOY!"); + strcat(text, LV_SYMBOL_USB " "); + break; + case ZMK_ENDPOINT_BLE: + if (active_profie_bonded) { + if (active_profile_connected) { + LOG_DBG("Bonded & connected!"); + sprintf(text, LV_SYMBOL_WIFI "%i " LV_SYMBOL_OK, active_profile_index); + } else { + LOG_DBG("Bonded but not connected!"); + sprintf(text, LV_SYMBOL_WIFI "%i " LV_SYMBOL_CLOSE, active_profile_index); + } + } else { + LOG_DBG("NOT Bonded!"); + sprintf(text, LV_SYMBOL_WIFI "%i " LV_SYMBOL_SETTINGS, active_profile_index); + } + break; + } + + lv_label_set_text(label, text); +} + +int zmk_widget_output_status_init(struct zmk_widget_output_status *widget, lv_obj_t *parent) { + output_status_init(); + widget->obj = lv_label_create(parent, NULL); + lv_label_set_style(widget->obj, LV_LABEL_STYLE_MAIN, &label_style); + + lv_obj_set_size(widget->obj, 40, 15); + set_status_symbol(widget->obj); + + sys_slist_append(&widgets, &widget->node); + + return 0; +} + +lv_obj_t *zmk_widget_output_status_obj(struct zmk_widget_output_status *widget) { + return widget->obj; +} + +int output_status_listener(const struct zmk_event_header *eh) { + struct zmk_widget_output_status *widget; + SYS_SLIST_FOR_EACH_CONTAINER(&widgets, widget, node) { set_status_symbol(widget->obj); } + return 0; +} + +ZMK_LISTENER(widget_output_status, output_status_listener) +#if defined(CONFIG_USB) +ZMK_SUBSCRIPTION(widget_output_status, usb_conn_state_changed); +#endif +#if defined(CONFIG_ZMK_BLE) +ZMK_SUBSCRIPTION(widget_output_status, ble_active_profile_changed); +#endif diff --git a/app/src/endpoints.c b/app/src/endpoints.c index 406d23b6..4367dd2f 100644 --- a/app/src/endpoints.c +++ b/app/src/endpoints.c @@ -47,6 +47,8 @@ int zmk_endpoints_select(enum zmk_endpoint endpoint) { return 0; } +enum zmk_endpoint zmk_endpoints_selected() { return current_endpoint; } + int zmk_endpoints_toggle() { enum zmk_endpoint new_endpoint = (preferred_endpoint == ZMK_ENDPOINT_USB) ? ZMK_ENDPOINT_BLE : ZMK_ENDPOINT_USB;