aboutsummaryrefslogtreecommitdiffhomepage
path: root/app/src/behavior.c
diff options
context:
space:
mode:
authorPeter Johanson <[email protected]>2024-04-17 16:44:22 -0700
committerPete Johanson <[email protected]>2024-06-28 15:10:32 -0600
commit483a4930e992a219d9fe941d1e7369194d34b15f (patch)
tree89c48484823276517d258b659202e5fcd3c0b224 /app/src/behavior.c
parentf7c34c70bad6d09dbdb4bfdfad5a196179dbb8c8 (diff)
downloadzmk-483a4930e992a219d9fe941d1e7369194d34b15f.tar.gz
zmk-483a4930e992a219d9fe941d1e7369194d34b15f.zip
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.
Diffstat (limited to 'app/src/behavior.c')
-rw-r--r--app/src/behavior.c135
1 files changed, 135 insertions, 0 deletions
diff --git a/app/src/behavior.c b/app/src/behavior.c
index 7777155f40..7505aa7f1c 100644
--- a/app/src/behavior.c
+++ b/app/src/behavior.c
@@ -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