This commit is contained in:
Joel Spadin 2023-11-15 23:12:34 -05:00 committed by GitHub
commit 59e9e430dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 1198 additions and 301 deletions

View file

@ -70,6 +70,7 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
target_sources(app PRIVATE src/events/keycode_state_changed.c) target_sources(app PRIVATE src/events/keycode_state_changed.c)
if (CONFIG_ZMK_BLE) if (CONFIG_ZMK_BLE)
target_sources(app PRIVATE src/events/ble_auth_state_changed.c)
target_sources(app PRIVATE src/events/ble_active_profile_changed.c) target_sources(app PRIVATE src/events/ble_active_profile_changed.c)
target_sources(app PRIVATE src/behaviors/behavior_bt.c) target_sources(app PRIVATE src/behaviors/behavior_bt.c)
target_sources(app PRIVATE src/ble.c) target_sources(app PRIVATE src/ble.c)
@ -93,6 +94,6 @@ target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/backlight.c)
target_sources(app PRIVATE src/workqueue.c) target_sources(app PRIVATE src/workqueue.c)
target_sources(app PRIVATE src/main.c) target_sources(app PRIVATE src/main.c)
add_subdirectory(src/display/) add_subdirectory_ifdef(CONFIG_ZMK_DISPLAY src/display/)
zephyr_cc_option(-Wfatal-errors) zephyr_cc_option(-Wfatal-errors)

View file

@ -174,10 +174,30 @@ config BT_GATT_AUTO_SEC_REQ
config BT_DEVICE_APPEARANCE config BT_DEVICE_APPEARANCE
default 961 default 961
config ZMK_BLE_PASSKEY_DISPLAY
bool "BLE passkey display"
default n
help
Enable keyboard to display a passkey used to complete pairing. The keyboard
will display a 6-digit passkey, and the user must enter it on the host.
config ZMK_BLE_PASSKEY_CONFIRM
bool "BLE passkey confirmation"
default n
select ZMK_BLE_PASSKEY_DISPLAY
help
Enables a simpler pairing method, where the keyboard and host each display
a passkey, and the user must confirm that they match by pressing enter or
keypad enter.
config ZMK_BLE_PASSKEY_ENTRY config ZMK_BLE_PASSKEY_ENTRY
bool "Require passkey entry on the keyboard to complete pairing" bool "BLE passkey entry"
default n default n
select RING_BUFFER select RING_BUFFER
help
Require passkey entry on the keyboard to complete pairing. The host will
display a 6-digit passkey, and the user must type it using the number or
keypad number keys, then press enter or keypad enter.
config BT_PERIPHERAL_PREF_MIN_INT config BT_PERIPHERAL_PREF_MIN_INT
default 6 default 6

View file

@ -24,6 +24,18 @@ choice ZMK_DISPLAY_STATUS_SCREEN
default ZMK_DISPLAY_STATUS_SCREEN_CUSTOM default ZMK_DISPLAY_STATUS_SCREEN_CUSTOM
endchoice endchoice
choice ZMK_LV_FONT_DEFAULT_SMALL
default ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_16
endchoice
choice ZMK_LV_FONT_DEFAULT_NORMAL
default ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_22
endchoice
choice ZMK_LV_FONT_DEFAULT_LARGE
default ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_28
endchoice
config ZMK_DISPLAY_STATUS_SCREEN_CUSTOM config ZMK_DISPLAY_STATUS_SCREEN_CUSTOM
imply NICE_VIEW_WIDGET_STATUS imply NICE_VIEW_WIDGET_STATUS
@ -46,7 +58,4 @@ config NICE_VIEW_WIDGET_STATUS
endif # !ZMK_SPLIT || ZMK_SPLIT_ROLE_CENTRAL endif # !ZMK_SPLIT || ZMK_SPLIT_ROLE_CENTRAL
config ZMK_DISPLAY_STATUS_SCREEN_BUILT_IN
select LV_FONT_MONTSERRAT_26
endif # SHIELD_NICE_VIEW endif # SHIELD_NICE_VIEW

View file

@ -7,6 +7,7 @@
#pragma once #pragma once
#include <zmk/keys.h> #include <zmk/keys.h>
#include <zmk/ble/auth.h>
#include <zmk/ble/profile.h> #include <zmk/ble/profile.h>
#define ZMK_BLE_IS_CENTRAL \ #define ZMK_BLE_IS_CENTRAL \
@ -20,18 +21,18 @@
#define ZMK_BLE_PROFILE_COUNT CONFIG_BT_MAX_PAIRED #define ZMK_BLE_PROFILE_COUNT CONFIG_BT_MAX_PAIRED
#endif #endif
int zmk_ble_clear_bonds(); int zmk_ble_clear_bonds(void);
int zmk_ble_prof_next(); int zmk_ble_prof_next(void);
int zmk_ble_prof_prev(); int zmk_ble_prof_prev(void);
int zmk_ble_prof_select(uint8_t index); int zmk_ble_prof_select(uint8_t index);
int zmk_ble_active_profile_index(); int zmk_ble_active_profile_index(void);
bt_addr_le_t *zmk_ble_active_profile_addr(); bt_addr_le_t *zmk_ble_active_profile_addr(void);
bool zmk_ble_active_profile_is_open(); bool zmk_ble_active_profile_is_open(void);
bool zmk_ble_active_profile_is_connected(); bool zmk_ble_active_profile_is_connected(void);
char *zmk_ble_active_profile_name(); char *zmk_ble_active_profile_name(void);
int zmk_ble_unpair_all(); struct zmk_ble_auth_state zmk_ble_get_auth_state(void);
#if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) #if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
int zmk_ble_put_peripheral_addr(const bt_addr_le_t *addr); int zmk_ble_put_peripheral_addr(const bt_addr_le_t *addr);

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <stdint.h>
enum zmk_ble_auth_mode {
// Not authenticating
ZMK_BLE_AUTH_MODE_NONE,
// User must confirm keyboard and host are displaying the same passkey.
ZMK_BLE_AUTH_MODE_PASSKEY_CONFIRM,
// Keyboard is dispaying a passkey, and user must enter it on the host.
ZMK_BLE_AUTH_MODE_PASSKEY_DISPLAY,
// Host is displaying a passkey, and user must enter it on the keyboard.
ZMK_BLE_AUTH_MODE_PASSKEY_ENTRY,
};
struct zmk_ble_auth_state {
enum zmk_ble_auth_mode mode;
// Index of the profile being authenticated.
uint8_t profile_index;
// In passkey entry mode, the index of the next digit to enter.
uint8_t cursor_index;
// The current passkey. The value will be in the range 0 - 999999 and should
// be padded with zeros so that six digits are always shown. E.g. the value
// 37 should be shown as 000037.
unsigned int passkey;
};

View file

@ -10,10 +10,16 @@
#pragma once #pragma once
struct k_work_q *zmk_display_work_q(); #include <stdbool.h>
#include <zephyr/kernel.h>
bool zmk_display_is_initialized(); struct k_work_q *zmk_display_work_q(void);
int zmk_display_init();
bool zmk_display_is_initialized(void);
int zmk_display_init(void);
void zmk_display_blanking_on(void);
void zmk_display_blanking_off(void);
/** /**
* @brief Macro to define a ZMK event listener that handles the thread safety of fetching * @brief Macro to define a ZMK event listener that handles the thread safety of fetching

View file

@ -0,0 +1,11 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <lvgl.h>
lv_obj_t *zmk_display_pairing_screen(void);

View file

@ -8,4 +8,4 @@
#include <lvgl.h> #include <lvgl.h>
lv_obj_t *zmk_display_status_screen(); lv_obj_t *zmk_display_status_screen(void);

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <lvgl.h>
#include <zephyr/kernel.h>
struct zmk_widget_ble_passkey {
sys_snode_t node;
lv_obj_t *obj;
lv_obj_t *profile;
lv_obj_t *title;
lv_obj_t *passkey;
};
int zmk_widget_ble_passkey_init(struct zmk_widget_ble_passkey *widget, lv_obj_t *parent);
lv_obj_t *zmk_widget_ble_passkey_obj(struct zmk_widget_ble_passkey *widget);

View file

@ -0,0 +1,16 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <zmk/event_manager.h>
#include <zmk/ble/auth.h>
struct zmk_ble_auth_state_changed {
struct zmk_ble_auth_state state;
};
ZMK_EVENT_DECLARE(zmk_ble_auth_state_changed);

View file

@ -36,17 +36,27 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/split/bluetooth/uuid.h> #include <zmk/split/bluetooth/uuid.h>
#include <zmk/event_manager.h> #include <zmk/event_manager.h>
#include <zmk/events/ble_active_profile_changed.h> #include <zmk/events/ble_active_profile_changed.h>
#include <zmk/events/ble_auth_state_changed.h>
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY) || IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_CONFIRM)
#include <zmk/events/keycode_state_changed.h>
#endif
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY) #if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY)
#include <zmk/events/keycode_state_changed.h>
#define PASSKEY_DIGITS 6 #define PASSKEY_DIGITS 6
static struct bt_conn *auth_passkey_entry_conn; static struct bt_conn *auth_passkey_entry_conn;
RING_BUF_DECLARE(passkey_entries, PASSKEY_DIGITS); RING_BUF_DECLARE(passkey_entries, PASSKEY_DIGITS);
#endif /* IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY) */ #endif /* IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY) */
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_DISPLAY)
static bool is_displaying_passkey = false;
static unsigned int current_passkey = 0;
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_CONFIRM)
static struct bt_conn *auth_passkey_confirm_conn;
#endif
#endif
enum advertising_type { enum advertising_type {
ZMK_ADV_NONE, ZMK_ADV_NONE,
ZMK_ADV_DIR, ZMK_ADV_DIR,
@ -82,7 +92,19 @@ static bt_addr_le_t peripheral_addrs[ZMK_SPLIT_BLE_PERIPHERAL_COUNT];
#endif /* IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) */ #endif /* IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) */
static void raise_profile_changed_event() { static void raise_auth_state_changed_event(void) {
ZMK_EVENT_RAISE(new_zmk_ble_auth_state_changed((struct zmk_ble_auth_state_changed){
.state = zmk_ble_get_auth_state(),
}));
}
static void raise_auth_state_changed_event_callback(struct k_work *work) {
raise_auth_state_changed_event();
}
K_WORK_DEFINE(raise_auth_state_changed_event_work, raise_auth_state_changed_event_callback);
static void raise_profile_changed_event(void) {
ZMK_EVENT_RAISE(new_zmk_ble_active_profile_changed((struct zmk_ble_active_profile_changed){ ZMK_EVENT_RAISE(new_zmk_ble_active_profile_changed((struct zmk_ble_active_profile_changed){
.index = active_profile, .profile = &profiles[active_profile]})); .index = active_profile, .profile = &profiles[active_profile]}));
} }
@ -93,7 +115,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); K_WORK_DEFINE(raise_profile_changed_event_work, raise_profile_changed_event_callback);
bool zmk_ble_active_profile_is_open() { bool zmk_ble_active_profile_is_open(void) {
return !bt_addr_le_cmp(&profiles[active_profile].peer, BT_ADDR_LE_ANY); return !bt_addr_le_cmp(&profiles[active_profile].peer, BT_ADDR_LE_ANY);
} }
@ -110,7 +132,7 @@ void set_profile_address(uint8_t index, const bt_addr_le_t *addr) {
k_work_submit(&raise_profile_changed_event_work); k_work_submit(&raise_profile_changed_event_work);
} }
bool zmk_ble_active_profile_is_connected() { bool zmk_ble_active_profile_is_connected(void) {
struct bt_conn *conn; struct bt_conn *conn;
struct bt_conn_info info; struct bt_conn_info info;
bt_addr_le_t *addr = zmk_ble_active_profile_addr(); bt_addr_le_t *addr = zmk_ble_active_profile_addr();
@ -127,42 +149,46 @@ bool zmk_ble_active_profile_is_connected() {
return info.state == BT_CONN_STATE_CONNECTED; return info.state == BT_CONN_STATE_CONNECTED;
} }
#define CHECKED_ADV_STOP() \ static int checked_adv_stop(void) {
err = bt_le_adv_stop(); \ int err = bt_le_adv_stop();
advertising_status = ZMK_ADV_NONE; \ if (err) {
if (err) { \ LOG_ERR("Failed to stop advertising (err %d)", err);
LOG_ERR("Failed to stop advertising (err %d)", err); \ } else {
return err; \ advertising_status = ZMK_ADV_NONE;
} }
return err;
}
#define CHECKED_DIR_ADV() \ static int checked_dir_adv(void) {
addr = zmk_ble_active_profile_addr(); \ bt_addr_le_t *addr = zmk_ble_active_profile_addr();
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, addr); \ struct bt_conn *conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, addr);
if (conn != NULL) { /* TODO: Check status of connection */ \ if (conn != NULL) { /* TODO: Check status of connection */
LOG_DBG("Skipping advertising, profile host is already connected"); \ LOG_DBG("Skipping advertising, profile host is already connected");
bt_conn_unref(conn); \ bt_conn_unref(conn);
return 0; \ return 0;
} \ }
err = bt_le_adv_start(BT_LE_ADV_CONN_DIR_LOW_DUTY(addr), zmk_ble_ad, ARRAY_SIZE(zmk_ble_ad), \ int err = bt_le_adv_start(BT_LE_ADV_CONN_DIR_LOW_DUTY(addr), zmk_ble_ad, ARRAY_SIZE(zmk_ble_ad),
NULL, 0); \ NULL, 0);
if (err) { \ if (err) {
LOG_ERR("Advertising failed to start (err %d)", err); \ LOG_ERR("Advertising failed to start (err %d)", err);
return err; \ } else {
} \
advertising_status = ZMK_ADV_DIR; advertising_status = ZMK_ADV_DIR;
}
return err;
}
#define CHECKED_OPEN_ADV() \ static int checked_open_adv(void) {
err = bt_le_adv_start(ZMK_ADV_CONN_NAME, zmk_ble_ad, ARRAY_SIZE(zmk_ble_ad), NULL, 0); \ int err = bt_le_adv_start(ZMK_ADV_CONN_NAME, zmk_ble_ad, ARRAY_SIZE(zmk_ble_ad), NULL, 0);
if (err) { \ if (err) {
LOG_ERR("Advertising failed to start (err %d)", err); \ LOG_ERR("Advertising failed to start (err %d)", err);
return err; \ } else {
} \
advertising_status = ZMK_ADV_CONN; advertising_status = ZMK_ADV_CONN;
}
return err;
}
int update_advertising() { int update_advertising(void) {
int err = 0; int err = 0;
bt_addr_le_t *addr;
struct bt_conn *conn;
enum advertising_type desired_adv = ZMK_ADV_NONE; enum advertising_type desired_adv = ZMK_ADV_NONE;
if (zmk_ble_active_profile_is_open()) { if (zmk_ble_active_profile_is_open()) {
@ -182,33 +208,37 @@ int update_advertising() {
switch (desired_adv + CURR_ADV(advertising_status)) { switch (desired_adv + CURR_ADV(advertising_status)) {
case ZMK_ADV_NONE + CURR_ADV(ZMK_ADV_DIR): case ZMK_ADV_NONE + CURR_ADV(ZMK_ADV_DIR):
case ZMK_ADV_NONE + CURR_ADV(ZMK_ADV_CONN): case ZMK_ADV_NONE + CURR_ADV(ZMK_ADV_CONN):
CHECKED_ADV_STOP(); err = checked_adv_stop();
break; break;
case ZMK_ADV_DIR + CURR_ADV(ZMK_ADV_DIR): case ZMK_ADV_DIR + CURR_ADV(ZMK_ADV_DIR):
case ZMK_ADV_DIR + CURR_ADV(ZMK_ADV_CONN): case ZMK_ADV_DIR + CURR_ADV(ZMK_ADV_CONN):
CHECKED_ADV_STOP(); err = checked_adv_stop();
CHECKED_DIR_ADV(); if (!err) {
err = checked_dir_adv();
}
break; break;
case ZMK_ADV_DIR + CURR_ADV(ZMK_ADV_NONE): case ZMK_ADV_DIR + CURR_ADV(ZMK_ADV_NONE):
CHECKED_DIR_ADV(); err = checked_dir_adv();
break; break;
case ZMK_ADV_CONN + CURR_ADV(ZMK_ADV_DIR): case ZMK_ADV_CONN + CURR_ADV(ZMK_ADV_DIR):
CHECKED_ADV_STOP(); err = checked_adv_stop();
CHECKED_OPEN_ADV(); if (!err) {
err = checked_open_adv();
}
break; break;
case ZMK_ADV_CONN + CURR_ADV(ZMK_ADV_NONE): case ZMK_ADV_CONN + CURR_ADV(ZMK_ADV_NONE):
CHECKED_OPEN_ADV(); err = checked_open_adv();
break; break;
} }
return 0; return err;
}; };
static void update_advertising_callback(struct k_work *work) { update_advertising(); } static void update_advertising_callback(struct k_work *work) { update_advertising(); }
K_WORK_DEFINE(update_advertising_work, update_advertising_callback); K_WORK_DEFINE(update_advertising_work, update_advertising_callback);
int zmk_ble_clear_bonds() { int zmk_ble_clear_bonds(void) {
LOG_DBG(""); LOG_DBG("");
if (bt_addr_le_cmp(&profiles[active_profile].peer, BT_ADDR_LE_ANY)) { if (bt_addr_le_cmp(&profiles[active_profile].peer, BT_ADDR_LE_ANY)) {
@ -222,7 +252,7 @@ int zmk_ble_clear_bonds() {
return 0; return 0;
}; };
int zmk_ble_active_profile_index() { return active_profile; } int zmk_ble_active_profile_index(void) { return active_profile; }
#if IS_ENABLED(CONFIG_SETTINGS) #if IS_ENABLED(CONFIG_SETTINGS)
static void ble_save_profile_work(struct k_work *work) { static void ble_save_profile_work(struct k_work *work) {
@ -232,7 +262,7 @@ static void ble_save_profile_work(struct k_work *work) {
static struct k_work_delayable ble_save_work; static struct k_work_delayable ble_save_work;
#endif #endif
static int ble_save_profile() { static int ble_save_profile(void) {
#if IS_ENABLED(CONFIG_SETTINGS) #if IS_ENABLED(CONFIG_SETTINGS)
return k_work_reschedule(&ble_save_work, K_MSEC(CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE)); return k_work_reschedule(&ble_save_work, K_MSEC(CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE));
#else #else
@ -260,20 +290,74 @@ int zmk_ble_prof_select(uint8_t index) {
return 0; return 0;
}; };
int zmk_ble_prof_next() { int zmk_ble_prof_next(void) {
LOG_DBG(""); LOG_DBG("");
return zmk_ble_prof_select((active_profile + 1) % ZMK_BLE_PROFILE_COUNT); return zmk_ble_prof_select((active_profile + 1) % ZMK_BLE_PROFILE_COUNT);
}; };
int zmk_ble_prof_prev() { int zmk_ble_prof_prev(void) {
LOG_DBG(""); LOG_DBG("");
return zmk_ble_prof_select((active_profile + ZMK_BLE_PROFILE_COUNT - 1) % return zmk_ble_prof_select((active_profile + ZMK_BLE_PROFILE_COUNT - 1) %
ZMK_BLE_PROFILE_COUNT); ZMK_BLE_PROFILE_COUNT);
}; };
bt_addr_le_t *zmk_ble_active_profile_addr() { return &profiles[active_profile].peer; } bt_addr_le_t *zmk_ble_active_profile_addr(void) { return &profiles[active_profile].peer; }
char *zmk_ble_active_profile_name() { return profiles[active_profile].name; } char *zmk_ble_active_profile_name(void) { return profiles[active_profile].name; }
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY)
static unsigned int get_passkey_entry_passkey(void) {
uint8_t digits[PASSKEY_DIGITS];
uint32_t count = ring_buf_peek(&passkey_entries, digits, PASSKEY_DIGITS);
unsigned int passkey = 0;
for (int i = 0; i < count; i++) {
passkey = (passkey * 10) + digits[i];
}
return passkey;
}
static uint8_t get_passkey_entry_cursor_index(void) {
const uint32_t size = ring_buf_size_get(&passkey_entries);
__ASSERT(size <= 255, "Invalid passkey size %u", size);
return size;
}
#endif
struct zmk_ble_auth_state zmk_ble_get_auth_state(void) {
struct zmk_ble_auth_state state = {
.mode = ZMK_BLE_AUTH_MODE_NONE,
.profile_index = active_profile,
};
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_CONFIRM)
if (auth_passkey_confirm_conn) {
state.mode = ZMK_BLE_AUTH_MODE_PASSKEY_CONFIRM;
state.passkey = current_passkey;
return state;
}
#endif
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_DISPLAY)
if (is_displaying_passkey) {
state.mode = ZMK_BLE_AUTH_MODE_PASSKEY_DISPLAY;
state.passkey = current_passkey;
return state;
}
#endif
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY)
if (auth_passkey_entry_conn) {
state.mode = ZMK_BLE_AUTH_MODE_PASSKEY_ENTRY;
state.passkey = get_passkey_entry_passkey();
state.cursor_index = get_passkey_entry_cursor_index();
return state;
}
#endif
return state;
}
#if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) #if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
@ -427,6 +511,8 @@ static void connected(struct bt_conn *conn, uint8_t err) {
} }
} }
static void passkey_cancel(void);
static void disconnected(struct bt_conn *conn, uint8_t reason) { static void disconnected(struct bt_conn *conn, uint8_t reason) {
char addr[BT_ADDR_LE_STR_LEN]; char addr[BT_ADDR_LE_STR_LEN];
struct bt_conn_info info; struct bt_conn_info info;
@ -442,6 +528,8 @@ static void disconnected(struct bt_conn *conn, uint8_t reason) {
return; return;
} }
passkey_cancel();
// We need to do this in a work callback, otherwise the advertising update will still see the // 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. // connection for a profile as active, and not start advertising yet.
k_work_submit(&update_advertising_work); k_work_submit(&update_advertising_work);
@ -480,15 +568,49 @@ static struct bt_conn_cb conn_callbacks = {
.le_param_updated = le_param_updated, .le_param_updated = le_param_updated,
}; };
/* #if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_DISPLAY)
static void auth_passkey_display(struct bt_conn *conn, unsigned int passkey) { static void auth_passkey_display(struct bt_conn *conn, unsigned int passkey) {
char addr[BT_ADDR_LE_STR_LEN]; char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
LOG_DBG("Passkey for %s: %06u", addr, passkey); LOG_DBG("Passkey for %s: %06u", addr, passkey);
is_displaying_passkey = true;
current_passkey = passkey;
k_work_submit(&raise_auth_state_changed_event_work);
} }
*/
static void auth_passkey_display_stop(void) {
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_CONFIRM)
if (auth_passkey_confirm_conn) {
LOG_DBG("Passkey confirmation stopped");
bt_conn_unref(auth_passkey_confirm_conn);
auth_passkey_confirm_conn = NULL;
}
#endif
if (is_displaying_passkey) {
LOG_DBG("Passkey display stopped");
is_displaying_passkey = false;
current_passkey = 0;
k_work_submit(&raise_auth_state_changed_event_work);
}
}
#endif // IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_DISPLAY)
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_CONFIRM)
static void auth_passkey_confirm(struct bt_conn *conn, unsigned int passkey) {
LOG_DBG("Waiting for passkey confirmation");
auth_passkey_confirm_conn = bt_conn_ref(conn);
auth_passkey_display(conn, passkey);
}
#endif // IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_CONFIRM)
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY) #if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY)
@ -500,25 +622,38 @@ static void auth_passkey_entry(struct bt_conn *conn) {
LOG_DBG("Passkey entry requested for %s", addr); LOG_DBG("Passkey entry requested for %s", addr);
ring_buf_reset(&passkey_entries); ring_buf_reset(&passkey_entries);
auth_passkey_entry_conn = bt_conn_ref(conn); auth_passkey_entry_conn = bt_conn_ref(conn);
k_work_submit(&raise_auth_state_changed_event_work);
} }
static void auth_passkey_entry_stop(void) {
if (auth_passkey_entry_conn) {
LOG_DBG("Passkey entry stopped");
bt_conn_unref(auth_passkey_entry_conn);
auth_passkey_entry_conn = NULL;
k_work_submit(&raise_auth_state_changed_event_work);
}
}
#endif // IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY)
static void passkey_cancel(void) {
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_DISPLAY)
auth_passkey_display_stop();
#endif #endif
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY)
auth_passkey_entry_stop();
#endif
}
static void auth_cancel(struct bt_conn *conn) { static void auth_cancel(struct bt_conn *conn) {
char addr[BT_ADDR_LE_STR_LEN]; char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY)
if (auth_passkey_entry_conn) {
bt_conn_unref(auth_passkey_entry_conn);
auth_passkey_entry_conn = NULL;
}
ring_buf_reset(&passkey_entries);
#endif
LOG_DBG("Pairing cancelled: %s", addr); LOG_DBG("Pairing cancelled: %s", addr);
passkey_cancel();
} }
static enum bt_security_err auth_pairing_accept(struct bt_conn *conn, static enum bt_security_err auth_pairing_accept(struct bt_conn *conn,
@ -540,6 +675,10 @@ static void auth_pairing_complete(struct bt_conn *conn, bool bonded) {
char addr[BT_ADDR_LE_STR_LEN]; char addr[BT_ADDR_LE_STR_LEN];
const bt_addr_le_t *dst = bt_conn_get_dst(conn); const bt_addr_le_t *dst = bt_conn_get_dst(conn);
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_DISPLAY)
auth_passkey_display_stop();
#endif
bt_addr_le_to_str(dst, addr, sizeof(addr)); bt_addr_le_to_str(dst, addr, sizeof(addr));
bt_conn_get_info(conn, &info); bt_conn_get_info(conn, &info);
@ -554,14 +693,20 @@ static void auth_pairing_complete(struct bt_conn *conn, bool bonded) {
return; return;
} }
LOG_DBG("Pairing complete: %s", addr);
set_profile_address(active_profile, dst); set_profile_address(active_profile, dst);
update_advertising(); update_advertising();
}; };
static struct bt_conn_auth_cb zmk_ble_auth_cb_display = { static struct bt_conn_auth_cb zmk_ble_auth_cb_display = {
.pairing_accept = auth_pairing_accept, .pairing_accept = auth_pairing_accept,
// .passkey_display = auth_passkey_display, #if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_CONFIRM)
.passkey_confirm = auth_passkey_confirm,
#endif
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_DISPLAY)
.passkey_display = auth_passkey_display,
#endif
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY) #if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY)
.passkey_entry = auth_passkey_entry, .passkey_entry = auth_passkey_entry,
#endif #endif
@ -656,60 +801,123 @@ static bool zmk_ble_numeric_usage_to_value(const zmk_key_t key, const zmk_key_t
return true; return true;
} }
static int zmk_ble_handle_key_user(struct zmk_keycode_state_changed *event) { static bool get_numeric_usage_value(const zmk_key_t key, uint8_t *value) {
zmk_key_t key = event->keycode; return zmk_ble_numeric_usage_to_value(key, HID_USAGE_KEY_KEYBOARD_1_AND_EXCLAMATION,
HID_USAGE_KEY_KEYBOARD_0_AND_RIGHT_PARENTHESIS, value) ||
LOG_DBG("key %d", key);
if (!auth_passkey_entry_conn) {
LOG_DBG("No connection for passkey entry");
return ZMK_EV_EVENT_BUBBLE;
}
if (!event->state) {
LOG_DBG("Key released, ignoring");
return ZMK_EV_EVENT_BUBBLE;
}
if (key == HID_USAGE_KEY_KEYBOARD_ESCAPE) {
bt_conn_auth_cancel(auth_passkey_entry_conn);
return ZMK_EV_EVENT_HANDLED;
}
if (key == HID_USAGE_KEY_KEYBOARD_RETURN || key == HID_USAGE_KEY_KEYBOARD_RETURN_ENTER) {
uint8_t digits[PASSKEY_DIGITS];
uint32_t count = ring_buf_get(&passkey_entries, digits, PASSKEY_DIGITS);
uint32_t passkey = 0;
for (int i = 0; i < count; i++) {
passkey = (passkey * 10) + digits[i];
}
LOG_DBG("Final passkey: %d", passkey);
bt_conn_auth_passkey_entry(auth_passkey_entry_conn, passkey);
bt_conn_unref(auth_passkey_entry_conn);
auth_passkey_entry_conn = NULL;
return ZMK_EV_EVENT_HANDLED;
}
uint8_t val;
if (!(zmk_ble_numeric_usage_to_value(key, HID_USAGE_KEY_KEYBOARD_1_AND_EXCLAMATION,
HID_USAGE_KEY_KEYBOARD_0_AND_RIGHT_PARENTHESIS, &val) ||
zmk_ble_numeric_usage_to_value(key, HID_USAGE_KEY_KEYPAD_1_AND_END, zmk_ble_numeric_usage_to_value(key, HID_USAGE_KEY_KEYPAD_1_AND_END,
HID_USAGE_KEY_KEYPAD_0_AND_INSERT, &val))) { HID_USAGE_KEY_KEYPAD_0_AND_INSERT, value);
LOG_DBG("Key not a number, ignoring"); }
return ZMK_EV_EVENT_BUBBLE;
}
static void add_passkey_digit(uint8_t value) {
if (ring_buf_space_get(&passkey_entries) <= 0) { if (ring_buf_space_get(&passkey_entries) <= 0) {
uint8_t discard_val; uint8_t discard_val;
ring_buf_get(&passkey_entries, &discard_val, 1); ring_buf_get(&passkey_entries, &discard_val, 1);
} }
ring_buf_put(&passkey_entries, &val, 1);
LOG_DBG("value entered: %d, digits collected so far: %d", val, ring_buf_put(&passkey_entries, &value, 1);
LOG_DBG("value entered: %d, digits collected so far: %d", value,
ring_buf_size_get(&passkey_entries)); ring_buf_size_get(&passkey_entries));
k_work_submit(&raise_auth_state_changed_event_work);
}
static void remove_passkey_digit(void) {
uint8_t digits[PASSKEY_DIGITS];
uint32_t count = ring_buf_get(&passkey_entries, digits, PASSKEY_DIGITS);
if (count == 0) {
return;
}
ring_buf_put(&passkey_entries, digits, count - 1);
k_work_submit(&raise_auth_state_changed_event_work);
}
static int handle_key_passkey_entry(const zmk_key_t key) {
LOG_DBG("Passkey entry: key %d", key);
if (key == HID_USAGE_KEY_KEYBOARD_ESCAPE) {
LOG_DBG("User canceled passkey entry");
bt_conn_auth_cancel(auth_passkey_entry_conn);
return ZMK_EV_EVENT_HANDLED; return ZMK_EV_EVENT_HANDLED;
}
if (key == HID_USAGE_KEY_KEYBOARD_DELETE_BACKSPACE || key == HID_USAGE_KEY_KEYPAD_BACKSPACE) {
remove_passkey_digit();
return ZMK_EV_EVENT_HANDLED;
}
if (key == HID_USAGE_KEY_KEYBOARD_RETURN || key == HID_USAGE_KEY_KEYBOARD_RETURN_ENTER) {
if (get_passkey_entry_cursor_index() < PASSKEY_DIGITS) {
LOG_DBG("Ignoring incomplete passkey");
return ZMK_EV_EVENT_HANDLED;
}
const unsigned int passkey = get_passkey_entry_passkey();
LOG_DBG("Final passkey: %d", passkey);
bt_conn_auth_passkey_entry(auth_passkey_entry_conn, passkey);
auth_passkey_entry_stop();
return ZMK_EV_EVENT_HANDLED;
}
uint8_t val;
if (get_numeric_usage_value(key, &val)) {
add_passkey_digit(val);
return ZMK_EV_EVENT_HANDLED;
}
LOG_DBG("Key not used for passkey entry, ignoring");
return ZMK_EV_EVENT_BUBBLE;
}
#endif // IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY)
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_CONFIRM)
static int handle_key_passkey_confirm(const zmk_key_t key) {
LOG_DBG("Passkey confirm: key %d", key);
if (key == HID_USAGE_KEY_KEYBOARD_ESCAPE) {
LOG_DBG("User canceled passkey confirmation");
bt_conn_auth_cancel(auth_passkey_confirm_conn);
return ZMK_EV_EVENT_HANDLED;
}
if (key == HID_USAGE_KEY_KEYBOARD_RETURN || key == HID_USAGE_KEY_KEYBOARD_RETURN_ENTER) {
LOG_DBG("Passkey confirmed");
bt_conn_auth_passkey_confirm(auth_passkey_confirm_conn);
auth_passkey_display_stop();
return ZMK_EV_EVENT_HANDLED;
}
LOG_DBG("Key not used for passkey confirmation, ignoring");
return ZMK_EV_EVENT_BUBBLE;
}
#endif // IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_CONFIRM)
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY) || IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_CONFIRM)
static int zmk_ble_handle_key_user(struct zmk_keycode_state_changed *event) {
if (!event->state) {
// Key released. Ignore.
return ZMK_EV_EVENT_BUBBLE;
}
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_CONFIRM)
if (auth_passkey_confirm_conn) {
return handle_key_passkey_confirm(event->keycode);
}
#endif
#if IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY)
if (auth_passkey_entry_conn) {
return handle_key_passkey_entry(event->keycode);
}
#endif
// Passkey entry/confirm not active. Ignore.
return ZMK_EV_EVENT_BUBBLE;
} }
static int zmk_ble_listener(const zmk_event_t *eh) { static int zmk_ble_listener(const zmk_event_t *eh) {
@ -726,6 +934,6 @@ static int zmk_ble_listener(const zmk_event_t *eh) {
ZMK_LISTENER(zmk_ble, zmk_ble_listener); ZMK_LISTENER(zmk_ble, zmk_ble_listener);
ZMK_SUBSCRIPTION(zmk_ble, zmk_keycode_state_changed); ZMK_SUBSCRIPTION(zmk_ble, zmk_keycode_state_changed);
#endif /* IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY) */ #endif // IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY) || IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_CONFIRM)
SYS_INIT(zmk_ble_init, APPLICATION, CONFIG_ZMK_BLE_INIT_PRIORITY); SYS_INIT(zmk_ble_init, APPLICATION, CONFIG_ZMK_BLE_INIT_PRIORITY);

View file

@ -1,7 +1,10 @@
# Copyright (c) 2020 The ZMK Contributors # Copyright (c) 2020 The ZMK Contributors
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
target_sources_ifdef(CONFIG_ZMK_DISPLAY app PRIVATE main.c) target_sources(app PRIVATE main.c)
target_sources(app PRIVATE theme.c)
target_sources_ifdef(CONFIG_ZMK_DISPLAY_BLANK_ON_IDLE app PRIVATE idle.c)
target_sources_ifdef(CONFIG_ZMK_DISPLAY_PAIRING_SCREEN_BUILT_IN app PRIVATE pairing_screen.c)
target_sources_ifdef(CONFIG_ZMK_DISPLAY_STATUS_SCREEN_BUILT_IN app PRIVATE status_screen.c) target_sources_ifdef(CONFIG_ZMK_DISPLAY_STATUS_SCREEN_BUILT_IN app PRIVATE status_screen.c)
add_subdirectory_ifdef(CONFIG_ZMK_DISPLAY widgets/) add_subdirectory(widgets/)

View file

@ -7,6 +7,7 @@ menuconfig ZMK_DISPLAY
select DISPLAY select DISPLAY
select LVGL select LVGL
select LV_CONF_MINIMAL select LV_CONF_MINIMAL
imply ZMK_BLE_PASSKEY_DISPLAY
if ZMK_DISPLAY if ZMK_DISPLAY
@ -19,7 +20,7 @@ if LV_USE_THEME_MONO
config ZMK_DISPLAY_INVERT config ZMK_DISPLAY_INVERT
bool "Invert display colors" bool "Invert display colors"
endif endif # LV_USE_THEME_MONO
choice LV_TXT_ENC choice LV_TXT_ENC
default LV_TXT_ENC_UTF8 default LV_TXT_ENC_UTF8
@ -36,7 +37,7 @@ config LV_Z_MEM_POOL_MAX_SIZE
default 8192 default 8192
choice ZMK_DISPLAY_STATUS_SCREEN choice ZMK_DISPLAY_STATUS_SCREEN
prompt "Default status screen for displays" prompt "Status screen for displays"
config ZMK_DISPLAY_STATUS_SCREEN_BUILT_IN config ZMK_DISPLAY_STATUS_SCREEN_BUILT_IN
bool "Built in status screen" bool "Built in status screen"
@ -52,6 +53,20 @@ config ZMK_DISPLAY_STATUS_SCREEN_CUSTOM
endchoice endchoice
if ZMK_BLE && (!ZMK_SPLIT || ZMK_SPLIT_ROLE_CENTRAL)
choice ZMK_DISPLAY_PAIRING_SCREEN
prompt "Bluetooth pairing screen for displays"
config ZMK_DISPLAY_PAIRING_SCREEN_BUILT_IN
bool "Built in bluetooth pairing screen"
select ZMK_WIDGET_BLE_PASSKEY
config ZMK_DISPLAY_PAIRING_SCREEN_CUSTOM
bool "Custom bluetooth pairing screen"
endchoice
endif # ZMK_BLE
choice ZMK_DISPLAY_WORK_QUEUE choice ZMK_DISPLAY_WORK_QUEUE
prompt "Work queue selection for UI updates" prompt "Work queue selection for UI updates"
@ -75,33 +90,16 @@ config ZMK_DISPLAY_DEDICATED_THREAD_PRIORITY
endif # ZMK_DISPLAY_WORK_QUEUE_DEDICATED endif # ZMK_DISPLAY_WORK_QUEUE_DEDICATED
if ZMK_DISPLAY_STATUS_SCREEN_BUILT_IN
config LV_FONT_MONTSERRAT_16
default y
choice LV_FONT_DEFAULT
default LV_FONT_DEFAULT_MONTSERRAT_16
endchoice
config LV_FONT_MONTSERRAT_12
default y
endif # ZMK_DISPLAY_STATUS_SCREEN_BUILT_IN
choice ZMK_LV_FONT_DEFAULT_SMALL choice ZMK_LV_FONT_DEFAULT_SMALL
prompt "Select theme default small font" prompt "Select theme default small font"
default ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_12
help config ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_12
Select theme default small font bool "Montserrat 12"
select LV_FONT_MONTSERRAT_12
config ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_8 config ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_8
bool "Montserrat 8" bool "Montserrat 8"
select LV_FONT_MONTSERRAT_8 select LV_FONT_MONTSERRAT_8
config ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_12
bool "Montserrat 12"
select LV_FONT_MONTSERRAT_12
config ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_14 config ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_14
bool "Montserrat 14" bool "Montserrat 14"
select LV_FONT_MONTSERRAT_14 select LV_FONT_MONTSERRAT_14
@ -176,6 +174,174 @@ choice ZMK_LV_FONT_DEFAULT_SMALL
select LV_FONT_UNSCII_16 select LV_FONT_UNSCII_16
endchoice endchoice
choice ZMK_LV_FONT_DEFAULT_NORMAL
prompt "Select theme default normal font"
config ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_16
bool "Montserrat 16"
select LV_FONT_MONTSERRAT_16
config ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_8
bool "Montserrat 8"
select LV_FONT_MONTSERRAT_8
config ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_12
bool "Montserrat 12"
select LV_FONT_MONTSERRAT_12
config ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_14
bool "Montserrat 14"
select LV_FONT_MONTSERRAT_14
config ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_18
bool "Montserrat 18"
select LV_FONT_MONTSERRAT_18
config ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_20
bool "Montserrat 20"
select LV_FONT_MONTSERRAT_20
config ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_22
bool "Montserrat 22"
select LV_FONT_MONTSERRAT_22
config ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_24
bool "Montserrat 24"
select LV_FONT_MONTSERRAT_24
config ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_26
bool "Montserrat 26"
select LV_FONT_MONTSERRAT_26
config ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_28
bool "Montserrat 28"
select LV_FONT_MONTSERRAT_28
config ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_30
bool "Montserrat 30"
select LV_FONT_MONTSERRAT_30
config ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_32
bool "Montserrat 32"
select LV_FONT_MONTSERRAT_32
config ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_34
bool "Montserrat 34"
select LV_FONT_MONTSERRAT_34
config ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_36
bool "Montserrat 36"
select LV_FONT_MONTSERRAT_36
config ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_38
bool "Montserrat 38"
select LV_FONT_MONTSERRAT_38
config ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_40
bool "Montserrat 40"
select LV_FONT_MONTSERRAT_40
config ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_42
bool "Montserrat 42"
select LV_FONT_MONTSERRAT_42
config ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_44
bool "Montserrat 44"
select LV_FONT_MONTSERRAT_44
config ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_46
bool "Montserrat 46"
select LV_FONT_MONTSERRAT_46
config ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_48
bool "Montserrat 48"
select LV_FONT_MONTSERRAT_48
config ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_12_SUBPX
bool "Montserrat 12 sub-pixel"
select LV_FONT_MONTSERRAT_12_SUBPX
config ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_28_COMPRESSED
bool "Montserrat 28 compressed"
select LV_FONT_MONTSERRAT_28_COMPRESSED
config ZMK_LV_FONT_DEFAULT_NORMAL_DEJAVU_16_PERSIAN_HEBREW
bool "Dejavu 16 Persian, Hebrew, Arabic letters"
select LV_FONT_DEJAVU_16_PERSIAN_HEBREW
config ZMK_LV_FONT_DEFAULT_NORMAL_SIMSUN_16_CJK
bool "Simsun 16 CJK"
select LV_FONT_SIMSUN_16_CJK
config ZMK_LV_FONT_DEFAULT_NORMAL_UNSCII_8
bool "UNSCII 8 (Perfect monospace font)"
select LV_FONT_UNSCII_8
config ZMK_LV_FONT_DEFAULT_NORMAL_UNSCII_16
bool "UNSCII 16 (Perfect monospace font)"
select LV_FONT_UNSCII_16
endchoice
choice ZMK_LV_FONT_DEFAULT_LARGE
prompt "Select theme default large font"
config ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_20
bool "Montserrat 20"
select LV_FONT_MONTSERRAT_20
config ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_8
bool "Montserrat 8"
select LV_FONT_MONTSERRAT_8
config ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_12
bool "Montserrat 12"
select LV_FONT_MONTSERRAT_12
config ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_14
bool "Montserrat 14"
select LV_FONT_MONTSERRAT_14
config ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_16
bool "Montserrat 16"
select LV_FONT_MONTSERRAT_16
config ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_18
bool "Montserrat 18"
select LV_FONT_MONTSERRAT_18
config ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_22
bool "Montserrat 22"
select LV_FONT_MONTSERRAT_22
config ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_24
bool "Montserrat 24"
select LV_FONT_MONTSERRAT_24
config ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_26
bool "Montserrat 26"
select LV_FONT_MONTSERRAT_26
config ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_28
bool "Montserrat 28"
select LV_FONT_MONTSERRAT_28
config ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_30
bool "Montserrat 30"
select LV_FONT_MONTSERRAT_30
config ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_32
bool "Montserrat 32"
select LV_FONT_MONTSERRAT_32
config ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_34
bool "Montserrat 34"
select LV_FONT_MONTSERRAT_34
config ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_36
bool "Montserrat 36"
select LV_FONT_MONTSERRAT_36
config ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_38
bool "Montserrat 38"
select LV_FONT_MONTSERRAT_38
config ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_40
bool "Montserrat 40"
select LV_FONT_MONTSERRAT_40
config ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_42
bool "Montserrat 42"
select LV_FONT_MONTSERRAT_42
config ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_44
bool "Montserrat 44"
select LV_FONT_MONTSERRAT_44
config ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_46
bool "Montserrat 46"
select LV_FONT_MONTSERRAT_46
config ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_48
bool "Montserrat 48"
select LV_FONT_MONTSERRAT_48
config ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_12_SUBPX
bool "Montserrat 12 sub-pixel"
select LV_FONT_MONTSERRAT_12_SUBPX
config ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_28_COMPRESSED
bool "Montserrat 28 compressed"
select LV_FONT_MONTSERRAT_28_COMPRESSED
config ZMK_LV_FONT_DEFAULT_LARGE_DEJAVU_16_PERSIAN_HEBREW
bool "Dejavu 16 Persian, Hebrew, Arabic letters"
select LV_FONT_DEJAVU_16_PERSIAN_HEBREW
config ZMK_LV_FONT_DEFAULT_LARGE_SIMSUN_16_CJK
bool "Simsun 16 CJK"
select LV_FONT_SIMSUN_16_CJK
config ZMK_LV_FONT_DEFAULT_LARGE_UNSCII_8
bool "UNSCII 8 (Perfect monospace font)"
select LV_FONT_UNSCII_8
config ZMK_LV_FONT_DEFAULT_LARGE_UNSCII_16
bool "UNSCII 16 (Perfect monospace font)"
select LV_FONT_UNSCII_16
endchoice
rsource "widgets/Kconfig" rsource "widgets/Kconfig"
endif endif # ZMK_DISPLAY

38
app/src/display/idle.c Normal file
View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zmk/display.h>
#include <zmk/event_manager.h>
#include <zmk/events/activity_state_changed.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
static int display_event_handler(const zmk_event_t *eh) {
struct zmk_activity_state_changed *ev = as_zmk_activity_state_changed(eh);
if (!ev) {
return 0;
}
switch (ev->state) {
case ZMK_ACTIVITY_ACTIVE:
zmk_display_blanking_off();
return 0;
case ZMK_ACTIVITY_IDLE:
case ZMK_ACTIVITY_SLEEP:
zmk_display_blanking_on();
return 0;
default:
LOG_WRN("Unhandled activity state: %d", ev->state);
}
return -EINVAL;
}
ZMK_LISTENER(display_idle, display_event_handler);
ZMK_SUBSCRIPTION(display_idle, zmk_activity_state_changed);

View file

@ -17,21 +17,44 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include "theme.h" #include "theme.h"
#include <zmk/event_manager.h>
#include <zmk/events/activity_state_changed.h>
#include <zmk/display/status_screen.h> #include <zmk/display/status_screen.h>
#include <zmk/event_manager.h>
static const struct device *display = DEVICE_DT_GET(DT_CHOSEN(zephyr_display)); #define HAS_PAIRING_SCREEN \
static bool initialized = false; (IS_ENABLED(CONFIG_ZMK_DISPLAY_PAIRING_SCREEN_BUILT_IN) || \
IS_ENABLED(CONFIG_ZMK_DISPLAY_PAIRING_SCREEN_CUSTOM))
static lv_obj_t *screen; #if HAS_PAIRING_SCREEN
#include <zmk/ble.h>
__attribute__((weak)) lv_obj_t *zmk_display_status_screen() { return NULL; } #include <zmk/display/pairing_screen.h>
#include <zmk/events/ble_auth_state_changed.h>
void display_tick_cb(struct k_work *work) { lv_task_handler(); } #endif
#define TICK_MS 10 #define TICK_MS 10
enum screen_type {
SCREEN_TYPE_STATUS,
#if HAS_PAIRING_SCREEN
SCREEN_TYPE_PAIRING,
#endif
};
static const struct device *display = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
static lv_obj_t *current_screen = NULL;
static bool initialized = false;
static lv_obj_t *status_screen = NULL;
#if HAS_PAIRING_SCREEN
static lv_obj_t *pairing_screen = NULL;
#endif
__attribute__((weak)) lv_obj_t *zmk_display_status_screen(void) { return NULL; }
#if HAS_PAIRING_SCREEN
__attribute__((weak)) lv_obj_t *zmk_display_pairing_screen(void) { return NULL; }
#endif
void display_tick_cb(struct k_work *work) { lv_task_handler(); }
K_WORK_DEFINE(display_tick_work, display_tick_cb); K_WORK_DEFINE(display_tick_work, display_tick_cb);
#if IS_ENABLED(CONFIG_ZMK_DISPLAY_WORK_QUEUE_DEDICATED) #if IS_ENABLED(CONFIG_ZMK_DISPLAY_WORK_QUEUE_DEDICATED)
@ -42,7 +65,7 @@ static struct k_work_q display_work_q;
#endif #endif
struct k_work_q *zmk_display_work_q() { struct k_work_q *zmk_display_work_q(void) {
#if IS_ENABLED(CONFIG_ZMK_DISPLAY_WORK_QUEUE_DEDICATED) #if IS_ENABLED(CONFIG_ZMK_DISPLAY_WORK_QUEUE_DEDICATED)
return &display_work_q; return &display_work_q;
#else #else
@ -50,25 +73,25 @@ struct k_work_q *zmk_display_work_q() {
#endif #endif
} }
void display_timer_cb() { k_work_submit_to_queue(zmk_display_work_q(), &display_tick_work); } static void display_timer_cb(struct k_timer *timer) {
k_work_submit_to_queue(zmk_display_work_q(), &display_tick_work);
}
K_TIMER_DEFINE(display_timer, display_timer_cb, NULL); K_TIMER_DEFINE(display_timer, display_timer_cb, NULL);
void unblank_display_cb(struct k_work *work) { static void unblank_display_cb(struct k_work *work) {
display_blanking_off(display); display_blanking_off(display);
k_timer_start(&display_timer, K_MSEC(TICK_MS), K_MSEC(TICK_MS)); k_timer_start(&display_timer, K_MSEC(TICK_MS), K_MSEC(TICK_MS));
} }
#if IS_ENABLED(CONFIG_ZMK_DISPLAY_BLANK_ON_IDLE) static void blank_display_cb(struct k_work *work) {
void blank_display_cb(struct k_work *work) {
k_timer_stop(&display_timer); k_timer_stop(&display_timer);
display_blanking_on(display); display_blanking_on(display);
} }
K_WORK_DEFINE(blank_display_work, blank_display_cb); K_WORK_DEFINE(blank_display_work, blank_display_cb);
K_WORK_DEFINE(unblank_display_work, unblank_display_cb); K_WORK_DEFINE(unblank_display_work, unblank_display_cb);
static void start_display_updates() { void zmk_display_blanking_off(void) {
if (display == NULL) { if (display == NULL) {
return; return;
} }
@ -76,7 +99,7 @@ static void start_display_updates() {
k_work_submit_to_queue(zmk_display_work_q(), &unblank_display_work); k_work_submit_to_queue(zmk_display_work_q(), &unblank_display_work);
} }
static void stop_display_updates() { void zmk_display_blanking_on(void) {
if (display == NULL) { if (display == NULL) {
return; return;
} }
@ -84,48 +107,82 @@ static void stop_display_updates() {
k_work_submit_to_queue(zmk_display_work_q(), &blank_display_work); k_work_submit_to_queue(zmk_display_work_q(), &blank_display_work);
} }
int zmk_display_is_initialized(void) { return initialized; }
K_MUTEX_DEFINE(screen_state_mutex);
#if HAS_PAIRING_SCREEN
static struct zmk_ble_auth_state ble_auth_state;
#endif #endif
int zmk_display_is_initialized() { return initialized; } static enum screen_type get_screen_type(void) {
enum screen_type screen_type = SCREEN_TYPE_STATUS;
static void initialize_theme() { #if HAS_PAIRING_SCREEN
#if IS_ENABLED(CONFIG_LV_USE_THEME_MONO) k_mutex_lock(&screen_state_mutex, K_FOREVER);
lv_disp_t *disp = lv_disp_get_default();
lv_theme_t *theme =
lv_theme_mono_init(disp, IS_ENABLED(CONFIG_ZMK_DISPLAY_INVERT), CONFIG_LV_FONT_DEFAULT);
theme->font_small = CONFIG_ZMK_LV_FONT_DEFAULT_SMALL;
disp->theme = theme; if (ble_auth_state.mode != ZMK_BLE_AUTH_MODE_NONE) {
#endif // CONFIG_LV_USE_THEME_MONO screen_type = SCREEN_TYPE_PAIRING;
}
k_mutex_unlock(&screen_state_mutex);
#endif
return screen_type;
} }
void initialize_display(struct k_work *work) { static void update_screen(struct k_work *work) {
LOG_DBG(""); enum screen_type new_screen_type = get_screen_type();
lv_obj_t *new_screen = NULL;
if (!device_is_ready(display)) { switch (new_screen_type) {
LOG_ERR("Failed to find display device"); case SCREEN_TYPE_STATUS:
return; new_screen = status_screen;
break;
#if HAS_PAIRING_SCREEN
case SCREEN_TYPE_PAIRING:
new_screen = pairing_screen;
break;
#endif
default:
LOG_ERR("Unhandled screen type: %d", new_screen_type);
}
if (new_screen != current_screen) {
current_screen = new_screen;
lv_scr_load(current_screen);
unblank_display_cb(work);
}
}
K_WORK_DEFINE(update_screen_work, update_screen);
static void initialize_display(struct k_work *work) {
update_screen(work);
if (!current_screen) {
LOG_ERR("No status screen provided");
} }
initialized = true; initialized = true;
initialize_theme();
screen = zmk_display_status_screen();
if (screen == NULL) {
LOG_ERR("No status screen provided");
return;
}
lv_scr_load(screen);
unblank_display_cb(work);
} }
K_WORK_DEFINE(init_work, initialize_display); K_WORK_DEFINE(init_work, initialize_display);
int zmk_display_init() { int zmk_display_init(void) {
if (!device_is_ready(display)) {
LOG_ERR("Failed to find display device");
return -ENODEV;
}
zmk_display_initialize_theme();
status_screen = zmk_display_status_screen();
#if HAS_PAIRING_SCREEN
pairing_screen = zmk_display_pairing_screen();
#endif
#if IS_ENABLED(CONFIG_ZMK_DISPLAY_WORK_QUEUE_DEDICATED) #if IS_ENABLED(CONFIG_ZMK_DISPLAY_WORK_QUEUE_DEDICATED)
k_work_queue_start(&display_work_q, display_work_stack_area, k_work_queue_start(&display_work_q, display_work_stack_area,
K_THREAD_STACK_SIZEOF(display_work_stack_area), K_THREAD_STACK_SIZEOF(display_work_stack_area),
@ -134,33 +191,28 @@ int zmk_display_init() {
k_work_submit_to_queue(zmk_display_work_q(), &init_work); k_work_submit_to_queue(zmk_display_work_q(), &init_work);
LOG_DBG("");
return 0; return 0;
} }
#if IS_ENABLED(CONFIG_ZMK_DISPLAY_BLANK_ON_IDLE) #if HAS_PAIRING_SCREEN
int display_event_handler(const zmk_event_t *eh) { static int handle_ble_auth_state_changed(const struct zmk_ble_auth_state_changed *ev) {
struct zmk_activity_state_changed *ev = as_zmk_activity_state_changed(eh); k_mutex_lock(&screen_state_mutex, K_FOREVER);
if (ev == NULL) { ble_auth_state = ev->state;
return -ENOTSUP; k_mutex_unlock(&screen_state_mutex);
k_work_submit_to_queue(zmk_display_work_q(), &update_screen_work);
return ZMK_EV_EVENT_BUBBLE;
}
static int display_event_handler(const zmk_event_t *eh) {
const struct zmk_ble_auth_state_changed *auth_ev = as_zmk_ble_auth_state_changed(eh);
if (auth_ev) {
return handle_ble_auth_state_changed(auth_ev);
} }
switch (ev->state) { return -ENOTSUP;
case ZMK_ACTIVITY_ACTIVE:
start_display_updates();
break;
case ZMK_ACTIVITY_IDLE:
case ZMK_ACTIVITY_SLEEP:
stop_display_updates();
break;
default:
LOG_WRN("Unhandled activity state: %d", ev->state);
return -EINVAL;
}
return 0;
} }
ZMK_LISTENER(display, display_event_handler); ZMK_LISTENER(display, display_event_handler);
ZMK_SUBSCRIPTION(display, zmk_activity_state_changed); ZMK_SUBSCRIPTION(display, zmk_ble_auth_state_changed);
#endif // HAS_PAIRING_SCREEN
#endif /* IS_ENABLED(CONFIG_ZMK_DISPLAY_BLANK_ON_IDLE) */

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <lvgl.h>
#include <zmk/display/widgets/ble_passkey.h>
static struct zmk_widget_ble_passkey passkey_widget;
lv_obj_t *zmk_display_pairing_screen(void) {
lv_obj_t *screen;
screen = lv_obj_create(NULL);
zmk_widget_ble_passkey_init(&passkey_widget, screen);
lv_obj_t *widget = zmk_widget_ble_passkey_obj(&passkey_widget);
lv_obj_set_width(widget, LV_PCT(100));
lv_obj_set_height(widget, LV_PCT(100));
return screen;
}

View file

@ -34,7 +34,9 @@ static struct zmk_widget_layer_status layer_status_widget;
static struct zmk_widget_wpm_status wpm_status_widget; static struct zmk_widget_wpm_status wpm_status_widget;
#endif #endif
lv_obj_t *zmk_display_status_screen() { static bool is_small_display(void) { return lv_disp_get_ver_res(lv_disp_get_default()) < 40; }
lv_obj_t *zmk_display_status_screen(void) {
lv_obj_t *screen; lv_obj_t *screen;
screen = lv_obj_create(NULL); screen = lv_obj_create(NULL);
@ -56,9 +58,11 @@ lv_obj_t *zmk_display_status_screen() {
#if IS_ENABLED(CONFIG_ZMK_WIDGET_LAYER_STATUS) #if IS_ENABLED(CONFIG_ZMK_WIDGET_LAYER_STATUS)
zmk_widget_layer_status_init(&layer_status_widget, screen); zmk_widget_layer_status_init(&layer_status_widget, screen);
lv_obj_align(zmk_widget_layer_status_obj(&layer_status_widget), LV_ALIGN_BOTTOM_LEFT, 0, 0);
if (is_small_display()) {
lv_obj_set_style_text_font(zmk_widget_layer_status_obj(&layer_status_widget), lv_obj_set_style_text_font(zmk_widget_layer_status_obj(&layer_status_widget),
lv_theme_get_font_small(screen), LV_PART_MAIN); lv_theme_get_font_small(screen), LV_PART_MAIN);
lv_obj_align(zmk_widget_layer_status_obj(&layer_status_widget), LV_ALIGN_BOTTOM_LEFT, 0, 0); }
#endif #endif
#if IS_ENABLED(CONFIG_ZMK_WIDGET_WPM_STATUS) #if IS_ENABLED(CONFIG_ZMK_WIDGET_WPM_STATUS)

View file

@ -1,73 +1,51 @@
/* /*
* Copyright (c) 2022 The ZMK Contributors * Copyright (c) 2023 The ZMK Contributors
* *
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
#include "theme.h"
#include <lvgl.h> #include <lvgl.h>
#include <zephyr/sys/util.h>
#if defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_8) #if IS_ENABLED(CONFIG_LV_USE_THEME_MONO)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_montserrat_8
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_10)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_montserrat_10
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_12)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_montserrat_12
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_14)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_montserrat_14
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_16)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_montserrat_16
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_18)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_montserrat_18
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_20)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_montserrat_20
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_22)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_montserrat_22
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_24)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_montserrat_24
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_26)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_montserrat_26
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_28)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_montserrat_28
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_30)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_montserrat_30
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_32)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_montserrat_32
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_34)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_montserrat_34
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_36)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_montserrat_36
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_38)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_montserrat_38
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_40)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_montserrat_40
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_42)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_montserrat_42
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_44)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_montserrat_44
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_46)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_montserrat_46
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_48)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_montserrat_48
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_12_SUBPX)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_montserrat_12_subpx
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_28_COMPRESSED)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_montserrat_28_compressed
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_DEJAVU_16_PERSIAN_HEBREW)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_dejavu_16_persian_hebrew
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_SIMSUN_16_CJK)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_simsun_16_cjk
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_UNSCII_8)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_unscii_8
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_UNSCII_16)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_unscii_16
#endif
void zmk_display_theme_init(lv_obj_t *obj) { static lv_theme_t *initialize_theme(lv_disp_t *disp) {
lv_theme_t *theme = lv_theme_get_from_obj(obj); lv_theme_t *theme = lv_theme_mono_init(disp, IS_ENABLED(CONFIG_ZMK_DISPLAY_INVERT),
CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL);
if (theme == NULL) {
return;
}
theme->font_small = CONFIG_ZMK_LV_FONT_DEFAULT_SMALL; theme->font_small = CONFIG_ZMK_LV_FONT_DEFAULT_SMALL;
theme->font_normal = CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL;
theme->font_large = CONFIG_ZMK_LV_FONT_DEFAULT_LARGE;
return theme;
}
#else
// Create a basic theme which only sets font sizes.
static lv_theme_t theme;
static void theme_apply(lv_theme_t *th, lv_obj_t *obj) {
LV_UNUSED(th);
LV_UNUSED(obj);
}
static lv_theme_t *initialize_theme(lv_disp_t *disp) {
theme.disp = disp;
theme.font_small = CONFIG_ZMK_LV_FONT_DEFAULT_SMALL;
theme.font_normal = CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL;
theme.font_large = CONFIG_ZMK_LV_FONT_DEFAULT_LARGE;
theme.apply_cb = theme_apply;
return &theme;
}
#endif // IS_ENABLED(CONFIG_LV_USE_THEME_MONO)
void zmk_display_initialize_theme(void) {
lv_disp_t *disp = lv_disp_get_default();
lv_theme_t *theme = initialize_theme(disp);
lv_disp_set_theme(disp, theme);
} }

View file

@ -63,3 +63,117 @@
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_UNSCII_16) #elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_UNSCII_16)
#define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_unscii_16 #define CONFIG_ZMK_LV_FONT_DEFAULT_SMALL &lv_font_unscii_16
#endif #endif
#if defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_8)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_montserrat_8
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_10)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_montserrat_10
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_12)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_montserrat_12
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_14)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_montserrat_14
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_16)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_montserrat_16
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_18)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_montserrat_18
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_20)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_montserrat_20
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_22)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_montserrat_22
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_24)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_montserrat_24
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_26)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_montserrat_26
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_28)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_montserrat_28
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_30)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_montserrat_30
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_32)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_montserrat_32
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_34)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_montserrat_34
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_36)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_montserrat_36
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_38)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_montserrat_38
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_40)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_montserrat_40
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_42)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_montserrat_42
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_44)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_montserrat_44
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_46)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_montserrat_46
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_48)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_montserrat_48
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_12_SUBPX)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_montserrat_12_subpx
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_28_COMPRESSED)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_montserrat_28_compressed
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_DEJAVU_16_PERSIAN_HEBREW)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_dejavu_16_persian_hebrew
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_SIMSUN_16_CJK)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_simsun_16_cjk
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_UNSCII_8)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_unscii_8
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_UNSCII_16)
#define CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL &lv_font_unscii_16
#endif
#if defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_8)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_montserrat_8
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_10)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_montserrat_10
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_12)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_montserrat_12
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_14)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_montserrat_14
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_16)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_montserrat_16
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_18)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_montserrat_18
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_20)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_montserrat_20
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_22)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_montserrat_22
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_24)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_montserrat_24
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_26)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_montserrat_26
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_28)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_montserrat_28
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_30)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_montserrat_30
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_32)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_montserrat_32
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_34)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_montserrat_34
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_36)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_montserrat_36
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_38)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_montserrat_38
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_40)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_montserrat_40
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_42)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_montserrat_42
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_44)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_montserrat_44
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_46)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_montserrat_46
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_48)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_montserrat_48
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_12_SUBPX)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_montserrat_12_subpx
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_28_COMPRESSED)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_montserrat_28_compressed
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_DEJAVU_16_PERSIAN_HEBREW)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_dejavu_16_persian_hebrew
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_SIMSUN_16_CJK)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_simsun_16_cjk
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_UNSCII_8)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_unscii_8
#elif defined(CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_UNSCII_16)
#define CONFIG_ZMK_LV_FONT_DEFAULT_LARGE &lv_font_unscii_16
#endif
void zmk_display_initialize_theme(void);

View file

@ -1,8 +1,12 @@
# Copyright (c) 2020 The ZMK Contributors # Copyright (c) 2020 The ZMK Contributors
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
# Status widgets
target_sources_ifdef(CONFIG_ZMK_WIDGET_BATTERY_STATUS app PRIVATE battery_status.c) 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) target_sources_ifdef(CONFIG_ZMK_WIDGET_OUTPUT_STATUS app PRIVATE output_status.c)
target_sources_ifdef(CONFIG_ZMK_WIDGET_PERIPHERAL_STATUS app PRIVATE peripheral_status.c) target_sources_ifdef(CONFIG_ZMK_WIDGET_PERIPHERAL_STATUS app PRIVATE peripheral_status.c)
target_sources_ifdef(CONFIG_ZMK_WIDGET_LAYER_STATUS app PRIVATE layer_status.c) target_sources_ifdef(CONFIG_ZMK_WIDGET_LAYER_STATUS app PRIVATE layer_status.c)
target_sources_ifdef(CONFIG_ZMK_WIDGET_WPM_STATUS app PRIVATE wpm_status.c) target_sources_ifdef(CONFIG_ZMK_WIDGET_WPM_STATUS app PRIVATE wpm_status.c)
# BLE authentication widgets
target_sources_ifdef(CONFIG_ZMK_WIDGET_BLE_PASSKEY app PRIVATE ble_passkey.c)

View file

@ -36,4 +36,10 @@ config ZMK_WIDGET_WPM_STATUS
select LV_USE_LABEL select LV_USE_LABEL
select ZMK_WPM select ZMK_WPM
config ZMK_WIDGET_BLE_PASSKEY
bool "Widget for displaying BLE pairing passkey"
depends on ZMK_BLE
select LV_USE_LABEL
select LV_USE_GRID
endmenu endmenu

View file

@ -0,0 +1,111 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <lvgl.h>
#include <stdio.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/ble.h>
#include <zmk/ble/auth.h>
#include <zmk/display.h>
#include <zmk/display/widgets/ble_passkey.h>
#include <zmk/event_manager.h>
#include <zmk/events/ble_auth_state_changed.h>
static sys_slist_t widgets = SYS_SLIST_STATIC_INIT(&widgets);
static void get_passkey_entry_text(char *str, size_t size, const struct zmk_ble_auth_state *state) {
if (state->cursor_index == 0) {
snprintf(str, size, "_");
} else if (state->cursor_index == 6) {
snprintf(str, size, "%06u " LV_SYMBOL_NEW_LINE, state->passkey);
} else {
snprintf(str, size, "%0*u_", state->cursor_index, state->passkey);
}
}
static void update_passkey_widget(struct zmk_widget_ble_passkey *widget,
const struct zmk_ble_auth_state *state) {
const int profile_index = state->profile_index + 1;
lv_label_set_text_fmt(widget->profile, LV_SYMBOL_WIFI " %i", profile_index);
switch (state->mode) {
case ZMK_BLE_AUTH_MODE_PASSKEY_CONFIRM:
lv_label_set_text_static(widget->title, "Confirm PIN");
lv_label_set_text_fmt(widget->passkey, "%06u " LV_SYMBOL_NEW_LINE, state->passkey);
break;
case ZMK_BLE_AUTH_MODE_PASSKEY_ENTRY: {
char passkey[16];
lv_label_set_text_static(widget->title, "Enter PIN");
get_passkey_entry_text(passkey, sizeof(passkey), state);
lv_label_set_text(widget->passkey, passkey);
} break;
case ZMK_BLE_AUTH_MODE_PASSKEY_DISPLAY:
default:
lv_label_set_text_static(widget->title, "Pairing PIN");
lv_label_set_text_fmt(widget->passkey, "%06u", state->passkey);
break;
}
}
static void ble_passkey_update_cb(struct zmk_ble_auth_state state) {
struct zmk_widget_ble_passkey *widget;
SYS_SLIST_FOR_EACH_CONTAINER(&widgets, widget, node) { update_passkey_widget(widget, &state); }
}
static struct zmk_ble_auth_state ble_passkey_get_state(const zmk_event_t *eh) {
return zmk_ble_get_auth_state();
}
ZMK_DISPLAY_WIDGET_LISTENER(widget_ble_passkey, struct zmk_ble_auth_state, ble_passkey_update_cb,
ble_passkey_get_state);
ZMK_SUBSCRIPTION(widget_ble_passkey, zmk_ble_auth_state_changed);
static bool is_small_display(void) { return lv_disp_get_ver_res(lv_disp_get_default()) < 40; }
int zmk_widget_ble_passkey_init(struct zmk_widget_ble_passkey *widget, lv_obj_t *parent) {
static const lv_coord_t col_dsc[] = {LV_GRID_CONTENT, LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST};
static const lv_coord_t row_dsc[] = {LV_GRID_CONTENT, LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST};
sys_slist_append(&widgets, &widget->node);
const bool is_small = is_small_display();
const lv_font_t *passkey_font =
is_small ? lv_theme_get_font_normal(parent) : lv_theme_get_font_large(parent);
widget->obj = lv_obj_create(parent);
lv_obj_set_scrollbar_mode(widget->obj, LV_SCROLLBAR_MODE_OFF);
lv_obj_set_style_border_width(widget->obj, 0, 0);
lv_obj_set_grid_dsc_array(widget->obj, col_dsc, row_dsc);
lv_obj_set_layout(widget->obj, LV_LAYOUT_GRID);
if (is_small) {
lv_obj_set_style_pad_all(widget->obj, 0, 0);
}
widget->profile = lv_label_create(widget->obj);
lv_obj_set_style_text_font(widget->profile, lv_theme_get_font_small(parent), LV_PART_MAIN);
lv_obj_set_grid_cell(widget->profile, LV_GRID_ALIGN_START, 0, 1, LV_GRID_ALIGN_START, 0, 1);
widget->title = lv_label_create(widget->obj);
lv_obj_set_style_text_font(widget->title, lv_theme_get_font_small(parent), LV_PART_MAIN);
lv_obj_set_grid_cell(widget->title, LV_GRID_ALIGN_CENTER, 1, 1, LV_GRID_ALIGN_START, 0, 1);
widget->passkey = lv_label_create(widget->obj);
lv_obj_set_style_text_font(widget->passkey, passkey_font, LV_PART_MAIN);
lv_obj_set_grid_cell(widget->passkey, LV_GRID_ALIGN_CENTER, 0, 2, LV_GRID_ALIGN_CENTER, 1, 1);
widget_ble_passkey_init();
return 0;
}
lv_obj_t *zmk_widget_ble_passkey_obj(struct zmk_widget_ble_passkey *widget) { return widget->obj; }

View file

@ -0,0 +1,9 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zmk/events/ble_auth_state_changed.h>
ZMK_EVENT_IMPL(zmk_ble_auth_state_changed);

View file

@ -10,6 +10,23 @@ See [Configuration Overview](index.md) for instructions on how to change these s
## Kconfig ## Kconfig
| Option | Type | Description | Default | | Option | Type | Description | Default |
| ------------------------------------- | ---- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | ------------------------------------- | ---- | ----------------------------------------------------------------------------------------------------------------------- | ------- |
| `CONFIG_ZMK_BLE_PASSKEY_ENTRY` | bool | Enable passkey entry during pairing for enhanced security. (Note: After enabling this, you will need to re-pair all previously paired hosts) | n |
| `CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION` | bool | Low level setting for GATT subscriptions. Set to `n` to work around an annoying Windows bug with battery notifications. | y | | `CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION` | bool | Low level setting for GATT subscriptions. Set to `n` to work around an annoying Windows bug with battery notifications. | y |
### Security
The following options can be used to mitigate man-in-the-middle (MITM) attacks during initial pairing. These require features which are not necessarily present on all keyboards, so they are not enabled by default.
| Option | Type | Description | Default |
| -------------------------------- | ---- | --------------------------------------- | -------------------- |
| `CONFIG_ZMK_BLE_PASSKEY_CONFIRM` | bool | Enable pairing with numeric comparison. | n |
| `CONFIG_ZMK_BLE_PASSKEY_DISPLAY` | bool | Enable passkey display during pairing. | `CONFIG_ZMK_DISPLAY` |
| `CONFIG_ZMK_BLE_PASSKEY_ENTRY` | bool | Enable passkey entry during pairing. | n |
`CONFIG_ZMK_BLE_PASSKEY_ENTRY` enables pairing with passkey entry on the keyboard. The host will display a 6-digit passkey which you must type on the keyboard, then press `ENTER` to complete pairing. Pressing `BACKSPACE` will delete the last digit, and pressing `ESCAPE` will cancel pairing. This requires that you have the digits `N0`-`N9` or `KP_N0`-`KP_N9` and `ENTER` or `KP_ENTER` bound to your keymap.
`CONFIG_ZMK_BLE_PASSKEY_DISPLAY` enables pairing with passkey entry on the host. The keyboard will display a 6-digit passkey which you must enter on the host to complete pairing. This requires only that your keyboard has a display, so it is automatically enabled with `CONFIG_ZMK_DISPLAY`.
`CONFIG_ZMK_BLE_PASSKEY_CONFIRM` enables pairing with numeric comparison. Both the host and keyboard will display a 6-digit passkey. You must check that they match, then press `ENTER` on they keyboard and confirm on the host as well to complete pairing. Pressing `ESCAPE` will cancel pairing. This requires that your keyboard has a display and you have `ENTER` or `KP_ENTER` bound to your keymap.
If multiple of the above options are enabled, the host will typically try to use numeric comparison first, then fallback to one of the passkey entry options.

View file

@ -26,6 +26,8 @@ Definition files:
Note that `CONFIG_ZMK_DISPLAY_INVERT` setting might not work as expected with custom status screens that utilize images. Note that `CONFIG_ZMK_DISPLAY_INVERT` setting might not work as expected with custom status screens that utilize images.
### Status Screen
If `CONFIG_ZMK_DISPLAY` is enabled, exactly zero or one of the following options must be set to `y`. The first option is used if none are set. If `CONFIG_ZMK_DISPLAY` is enabled, exactly zero or one of the following options must be set to `y`. The first option is used if none are set.
| Config | Description | | Config | Description |
@ -33,6 +35,29 @@ If `CONFIG_ZMK_DISPLAY` is enabled, exactly zero or one of the following options
| `CONFIG_ZMK_DISPLAY_STATUS_SCREEN_BUILT_IN` | Use the built-in status screen | | `CONFIG_ZMK_DISPLAY_STATUS_SCREEN_BUILT_IN` | Use the built-in status screen |
| `CONFIG_ZMK_DISPLAY_STATUS_SCREEN_CUSTOM` | Use a custom status screen | | `CONFIG_ZMK_DISPLAY_STATUS_SCREEN_CUSTOM` | Use a custom status screen |
If `CONFIG_ZMK_DISPLAY_STATUS_SCREEN_CUSTOM` is enabled, you must add a C file to the build which implements the following function.
```c
lv_obj_t *zmk_display_status_screen(void);
```
### Pairing Screen
If `CONFIG_ZMK_DISPLAY` and `CONFIG_ZMK_BLE` are enabled, exactly zero or one of the following options must be set to `y`. The first option is used if none are set.
| Config | Description |
| -------------------------------------------- | -------------------------------------------- |
| `CONFIG_ZMK_DISPLAY_PAIRING_SCREEN_BUILT_IN` | Use the built-in bluetooth pairing screen |
| `CONFIG_ZMK_DISPLAY_PAIRING_SCREEN_CUSTOM` | Use a custom bluetooth pairing screen screen |
If `CONFIG_ZMK_DISPLAY_PAIRING_SCREEN_CUSTOM` is enabled, you must add a C file to the build which implements the following function.
```c
lv_obj_t *zmk_display_pairing_screen(void);
```
### Work Queue
If `CONFIG_ZMK_DISPLAY` is enabled, exactly zero or one of the following options must be set to `y`. The first option is used if none are set. If `CONFIG_ZMK_DISPLAY` is enabled, exactly zero or one of the following options must be set to `y`. The first option is used if none are set.
| Config | Description | | Config | Description |
@ -47,9 +72,29 @@ Using a dedicated thread requires more memory but prevents displays with slow up
| `CONFIG_ZMK_DISPLAY_DEDICATED_THREAD_STACK_SIZE` | int | Stack size for the UI thread | 2048 | | `CONFIG_ZMK_DISPLAY_DEDICATED_THREAD_STACK_SIZE` | int | Stack size for the UI thread | 2048 |
| `CONFIG_ZMK_DISPLAY_DEDICATED_THREAD_PRIORITY` | int | Priority for the UI thread | 5 | | `CONFIG_ZMK_DISPLAY_DEDICATED_THREAD_PRIORITY` | int | Priority for the UI thread | 5 |
You must also configure the driver for your display. ZMK provides the following display drivers: ### Default Fonts
- [IL0323](https://github.com/zmkfirmware/zmk/blob/main/app/module/drivers/display/Kconfig.il0323) ZMK's built-in screens use three sizes of fonts:
| Config | Default |
| ----------------------------------- | ------------------------------------------------- |
| `CONFIG_ZMK_LV_FONT_DEFAULT_SMALL` | `CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_12` |
| `CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL` | `CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_16` |
| `CONFIG_ZMK_LV_FONT_DEFAULT_LARGE` | `CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_20` |
See [zmk/app/src/display/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/src/display/Kconfig) for a full list of the possible fonts. To change the font, set the option for the specific font to `y`. For example, to increase all font sizes to be slightly larger than the defaults, you could set
```kconfig
CONFIG_ZMK_LV_FONT_DEFAULT_SMALL_MONTSERRAT_14=y
CONFIG_ZMK_LV_FONT_DEFAULT_NORMAL_MONTSERRAT_18=y
CONFIG_ZMK_LV_FONT_DEFAULT_LARGE_MONTSERRAT_22=y
```
### Display Driver
You may also need to configure the driver for your display. ZMK provides the following display drivers:
- [IL0323 E-Paper Display](https://github.com/zmkfirmware/zmk/blob/main/app/module/drivers/display/Kconfig.il0323)
Zephyr provides several display drivers as well. Search for the name of your display in [Zephyr's Kconfig options](https://docs.zephyrproject.org/latest/kconfig.html) documentation. Zephyr provides several display drivers as well. Search for the name of your display in [Zephyr's Kconfig options](https://docs.zephyrproject.org/latest/kconfig.html) documentation.
@ -57,8 +102,9 @@ Zephyr provides several display drivers as well. Search for the name of your dis
See the Devicetree bindings for your display. Here are the bindings for common displays: See the Devicetree bindings for your display. Here are the bindings for common displays:
- [IL0323](https://github.com/zmkfirmware/zmk/blob/main/app/module/dts/bindings/display/gooddisplay%2Cil0323.yaml) - [IL0323 E-Paper Display](https://github.com/zmkfirmware/zmk/blob/main/app/module/dts/bindings/display/gooddisplay%2Cil0323.yaml)
- [SSD1306 (i2c)](https://docs.zephyrproject.org/latest/build/dts/api/bindings/display/solomon,ssd1306fb-i2c.html) - [LS0xx Memory LCD](https://docs.zephyrproject.org/latest/build/dts/api/bindings/display/sharp%2Cls0xx.html)
- [SSD1306 (spi)](https://docs.zephyrproject.org/latest/build/dts/api/bindings/display/solomon,ssd1306fb-spi.html) - [SSD1306 OLED (i2c)](https://docs.zephyrproject.org/latest/build/dts/api/bindings/display/solomon,ssd1306fb-i2c.html)
- [SSD1306 OLED (spi)](https://docs.zephyrproject.org/latest/build/dts/api/bindings/display/solomon,ssd1306fb-spi.html)
A full list of drivers provided by Zephyr can be found in [Zephyr's Devicetree bindings index](https://docs.zephyrproject.org/latest/build/dts/api/bindings.html). A full list of drivers provided by Zephyr can be found in [Zephyr's Devicetree bindings index](https://docs.zephyrproject.org/latest/build/dts/api/bindings.html).

View file

@ -15,7 +15,7 @@ Bluetooth 4.2 or newer is required in order to connect to a ZMK keyboard. ZMK im
BLE connections between keyboards and hosts are secured by an initial pairing/bonding process that establishes long term keys (LTK) shared between the two sides, using Elliptic Curve Diffie Hellman (ECDH) for key generation. The same security is used to secure the communication between the two sides of split keyboards running ZMK. BLE connections between keyboards and hosts are secured by an initial pairing/bonding process that establishes long term keys (LTK) shared between the two sides, using Elliptic Curve Diffie Hellman (ECDH) for key generation. The same security is used to secure the communication between the two sides of split keyboards running ZMK.
The only known vulnerability in the protocol is a risk of an active man-in-the-middle (MITM) attack exactly during the initial pairing, which can be mitigated in the future using the Numeric Comparison association model. Support for that in ZMK is still experimental, so if you have serious concerns about an active attacker with physical proximity to your device, consider only pairing/bonding your keyboards in a controlled environment. The only known vulnerability in the protocol is a risk of an active man-in-the-middle (MITM) attack exactly during the initial pairing, which can be mitigated using the Numeric Comparison or Passkey Entry association models. These may be enabled with the [`CONFIG_ZMK_BLE_PASSKEY_*` options](../config/bluetooth.md#security). Support for that in ZMK is still experimental, so if you have serious concerns about an active attacker with physical proximity to your device, consider only pairing/bonding your keyboards in a controlled environment.
## Profiles ## Profiles