diff --git a/app/include/zmk/behavior.h b/app/include/zmk/behavior.h index ab95fd8e7285..dd112242fff5 100644 --- a/app/include/zmk/behavior.h +++ b/app/include/zmk/behavior.h @@ -36,3 +36,5 @@ struct zmk_behavior_binding_event { * unrelated node which shares the same name as a behavior. */ const struct device *zmk_behavior_get_binding(const char *name); + +int zmk_behavior_validate_binding(const struct zmk_behavior_binding *binding); \ No newline at end of file diff --git a/app/include/zmk/keymap.h b/app/include/zmk/keymap.h index 0d7dbaf33b3d..b56cdac9c3b6 100644 --- a/app/include/zmk/keymap.h +++ b/app/include/zmk/keymap.h @@ -24,6 +24,11 @@ int zmk_keymap_layer_toggle(uint8_t layer); int zmk_keymap_layer_to(uint8_t layer); const char *zmk_keymap_layer_name(uint8_t layer); +const struct zmk_behavior_binding *zmk_keymap_get_layer_binding_at_idx(uint8_t layer, + uint8_t binding_idx); +int zmk_keymap_set_layer_binding_at_idx(uint8_t layer, uint8_t binding_idx, + const struct zmk_behavior_binding binding); + int zmk_keymap_position_state_changed(uint8_t source, uint32_t position, bool pressed, int64_t timestamp); diff --git a/app/src/behavior.c b/app/src/behavior.c index fa2005ff1af5..4044fc47230c 100644 --- a/app/src/behavior.c +++ b/app/src/behavior.c @@ -11,6 +11,8 @@ #include #include +#include +#include #include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); @@ -39,6 +41,135 @@ const struct device *z_impl_behavior_get_binding(const char *name) { return NULL; } +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) +static int validate_hid_usage(uint16_t usage_page, uint16_t usage_id) { + LOG_DBG("Validate usage %d in page %d", usage_id, usage_page); + switch (usage_page) { + case HID_USAGE_KEY: + if (usage_id == 0 || usage_id > ZMK_HID_KEYBOARD_NKRO_MAX_USAGE) { + return -EINVAL; + } + break; + case HID_USAGE_CONSUMER: + if (usage_id > + COND_CODE_1(IS_ENABLED(CONFIG_ZMK_HID_CONSUMER_REPORT_USAGES_BASIC), (0xFF), (0xFFF))) { + return -EINVAL; + } + break; + default: + LOG_WRN("Unsupported HID usage page %d", usage_page); + return -EINVAL; + } + + return 0; +} + +static int validate_standard_param(enum behavior_parameter_standard_domain standard_domain, + uint32_t val) { + switch (standard_domain) { + case BEHAVIOR_PARAMETER_STANDARD_DOMAIN_NULL: + if (val != 0) { + return -EINVAL; + } + break; + case BEHAVIOR_PARAMETER_STANDARD_DOMAIN_HID_USAGE: + return validate_hid_usage(ZMK_HID_USAGE_PAGE(val), ZMK_HID_USAGE_ID(val)); + case BEHAVIOR_PARAMETER_STANDARD_DOMAIN_LAYER_INDEX: + if (val >= ZMK_KEYMAP_LEN) { + return -EINVAL; + } + break; + case BEHAVIOR_PARAMETER_STANDARD_DOMAIN_HSV: + // TODO: No real way to validate? Maybe max brightness? + break; + } + + return 0; +} + +static int validate_custom_params(const struct behavior_parameter_metadata_custom *custom, + uint32_t param1, uint32_t param2) { + if (!custom) { + return -ENODEV; + } + + for (int s = 0; s < custom->sets_len; s++) { + const struct behavior_parameter_metadata_custom_set *set = &custom->sets[s]; + + bool had_param1_metadata = false, had_param2_metadata = false; + bool param1_matched = false, param2_matched = false; + + for (int v = 0; v < set->values_len && (!param1_matched || !param2_matched); v++) { + const struct behavior_parameter_value_metadata *value_meta = &set->values[v]; + uint32_t param = value_meta->position == 0 ? param1 : param2; + bool *matched = value_meta->position == 0 ? ¶m1_matched : ¶m2_matched; + + *(value_meta->position == 0 ? &had_param1_metadata : &had_param2_metadata) = true; + + switch (value_meta->type) { + case BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_STANDARD: + if (validate_standard_param(value_meta->standard, param) == 0) { + *matched = true; + } + break; + case BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE: + if (param == value_meta->value) { + *matched = true; + } + break; + case BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_RANGE: + if (param >= value_meta->range.min && param <= value_meta->range.max) { + *matched = true; + } + break; + } + } + + if ((param1_matched || (!had_param1_metadata && param1 == 0)) && + (param2_matched || (!had_param2_metadata && param2 == 0))) { + return 0; + } + } + + return -EINVAL; +} + +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + +int zmk_behavior_validate_binding(const struct zmk_behavior_binding *binding) { +#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) + const struct device *behavior = zmk_behavior_get_binding(binding->behavior_dev); + + if (!behavior) { + return -ENODEV; + } + + struct behavior_parameter_metadata metadata; + int ret = behavior_get_parameter_domains(behavior, &metadata); + + if (ret < 0) { + return ret; + } + + switch (metadata.type) { + case BEHAVIOR_PARAMETER_METADATA_STANDARD: + int ret = validate_standard_param(metadata.standard.param1, binding->param1); + + if (ret < 0) { + return ret; + } + + return validate_standard_param(metadata.standard.param2, binding->param2); + case BEHAVIOR_PARAMETER_METADATA_CUSTOM: + return validate_custom_params(metadata.custom, binding->param1, binding->param2); + default: + return -ENOTSUP; + } +#else + return 0; +#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA) +} + #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 diff --git a/app/src/keymap.c b/app/src/keymap.c index 94bd12048cf8..744150630588 100644 --- a/app/src/keymap.c +++ b/app/src/keymap.c @@ -163,6 +163,31 @@ const char *zmk_keymap_layer_name(uint8_t layer) { return zmk_keymap_layer_names[layer]; } +const struct zmk_behavior_binding *zmk_keymap_get_layer_binding_at_idx(uint8_t layer, + uint8_t binding_idx) { + if (layer >= ZMK_KEYMAP_LAYERS_LEN) { + return NULL; + } + + if (binding_idx >= ZMK_KEYMAP_LEN) { + return NULL; + } + + return &zmk_keymap[layer][binding_idx]; +} + +int zmk_keymap_set_layer_binding_at_idx(uint8_t layer, uint8_t binding_idx, + struct zmk_behavior_binding binding) { + if (layer >= ZMK_KEYMAP_LAYERS_LEN || binding_idx >= ZMK_KEYMAP_LEN) { + return -EINVAL; + } + + // TODO: Need a mutex to protect access to the keymap data? + memcpy(&zmk_keymap[layer][binding_idx], &binding, sizeof(binding)); + + return 0; +} + int invoke_locally(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event, bool pressed) { if (pressed) {