feat(behaviors): Add local ID system for behaviors

* Add a new feature for tracking a given behavior by a new concept
  of a "behavior local ID" which is a stable 16-bit identifier for
  a given behavior, that is resilient to new behaviors being added
  and requires no additional work on the part of the behavior
  authors.
* Add implementations for either settings lookup table, or CRC16
  hashing of behavior device names for generating behavior local
  IDs.
This commit is contained in:
Peter Johanson
2024-04-17 16:44:22 -07:00
committed by Pete Johanson
parent f7c34c70ba
commit 483a4930e9
6 changed files with 217 additions and 1 deletions

View File

@@ -6,9 +6,17 @@
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <zephyr/sys/crc.h>
#include <zephyr/sys/util_macro.h>
#include <string.h>
#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_IDS) && \
IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_ID_TYPE_SETTINGS_TABLE)
#include <zephyr/settings/settings.h>
#endif
#include <drivers/behavior.h>
#include <zmk/behavior.h>
#include <zmk/hid.h>
@@ -185,6 +193,133 @@ int zmk_behavior_validate_binding(const struct zmk_behavior_binding *binding) {
#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
}
#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_IDS)
zmk_behavior_local_id_t zmk_behavior_get_local_id(const char *name) {
if (!name) {
return UINT16_MAX;
}
STRUCT_SECTION_FOREACH(zmk_behavior_local_id_map, item) {
if (z_device_is_ready(item->device) && strcmp(item->device->name, name) == 0) {
return item->local_id;
}
}
return UINT16_MAX;
}
const char *zmk_behavior_find_behavior_name_from_local_id(zmk_behavior_local_id_t local_id) {
STRUCT_SECTION_FOREACH(zmk_behavior_local_id_map, item) {
if (z_device_is_ready(item->device) && item->local_id == local_id) {
return item->device->name;
}
}
return NULL;
}
#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_ID_TYPE_CRC16)
static int behavior_local_id_init(void) {
STRUCT_SECTION_FOREACH(zmk_behavior_local_id_map, item) {
item->local_id = crc16_ansi(item->device->name, strlen(item->device->name));
}
return 0;
}
#elif IS_ENABLED(CONFIG_ZMK_BEHAVIOR_LOCAL_ID_TYPE_SETTINGS_TABLE)
static zmk_behavior_local_id_t largest_local_id = 0;
static int behavior_handle_set(const char *name, size_t len, settings_read_cb read_cb,
void *cb_arg) {
const char *next;
if (settings_name_steq(name, "local_id", &next) && next) {
char *endptr;
uint8_t local_id = strtoul(next, &endptr, 10);
if (*endptr != '\0') {
LOG_WRN("Invalid behavior local ID: %s with endptr %s", next, endptr);
return -EINVAL;
}
if (len >= 64) {
LOG_ERR("Too large binding setting size (got %d expected less than %d)", len, 64);
return -EINVAL;
}
char name[len + 1];
int err = read_cb(cb_arg, name, len);
if (err <= 0) {
LOG_ERR("Failed to handle keymap binding from settings (err %d)", err);
return err;
}
name[len] = '\0';
STRUCT_SECTION_FOREACH(zmk_behavior_local_id_map, item) {
if (strcmp(name, item->device->name) == 0) {
item->local_id = local_id;
largest_local_id = MAX(largest_local_id, local_id);
return 0;
}
}
return -EINVAL;
}
return 0;
}
static int behavior_handle_commit(void) {
STRUCT_SECTION_FOREACH(zmk_behavior_local_id_map, item) {
if (item->local_id != 0) {
continue;
}
if (!item->device || !item->device->name || !device_is_ready(item->device)) {
LOG_WRN("Skipping ID for device that doesn't exist or isn't ready");
continue;
}
item->local_id = ++largest_local_id;
char setting_name[32];
sprintf(setting_name, "behavior/local_id/%d", item->local_id);
// If the `device->name` is readonly in flash, settings save can fail to copy/read it while
// persisting to flash, so copy the device name into memory first before saving.
char device_name[32];
snprintf(device_name, ARRAY_SIZE(device_name), "%s", item->device->name);
settings_save_one(setting_name, device_name, strlen(device_name));
}
return 0;
}
SETTINGS_STATIC_HANDLER_DEFINE(behavior, "behavior", NULL, behavior_handle_set,
behavior_handle_commit, NULL);
static int behavior_local_id_init(void) {
settings_subsys_init();
settings_load_subtree("behavior");
return 0;
}
#else
#error "A behavior local ID mechanism must be selected"
#endif
SYS_INIT(behavior_local_id_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);
#endif
#if IS_ENABLED(CONFIG_LOG)
static int check_behavior_names(void) {
// Behavior names must be unique, but we don't have a good way to enforce this