diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 3da50b57..e611f772 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -52,6 +52,7 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL) target_sources(app PRIVATE src/behaviors/behavior_transparent.c) target_sources(app PRIVATE src/behaviors/behavior_none.c) target_sources(app PRIVATE src/behaviors/behavior_sensor_rotate_key_press.c) + target_sources(app PRIVATE src/behaviors/behavior_custom_lock.c) target_sources(app PRIVATE src/combo.c) target_sources(app PRIVATE src/behaviors/behavior_tap_dance.c) target_sources(app PRIVATE src/behavior_queue.c) diff --git a/app/dts/bindings/behaviors/zmk,behavior-custom-lock.yaml b/app/dts/bindings/behaviors/zmk,behavior-custom-lock.yaml new file mode 100644 index 00000000..19fa0225 --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-custom-lock.yaml @@ -0,0 +1,28 @@ +# Copyright (c) 2022 The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Custom lock behavior variable definition + +compatible: "zmk,behavior-custom-lock" + +include: zero_param.yaml + +properties: + persistence: + type: string + default: "disabled" + required: false + enum: + - "disabled" + - "enabled" + - "per-profile" + +child-binding: + description: Key definitions for lock variable + + include: two_param.yaml + + properties: + bindings: + type: phandles + required: true diff --git a/app/src/behaviors/behavior_custom_lock.c b/app/src/behaviors/behavior_custom_lock.c new file mode 100644 index 00000000..bc973d53 --- /dev/null +++ b/app/src/behaviors/behavior_custom_lock.c @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#define DT_DRV_COMPAT zmk_behavior_custom_lock + +#include +#include +#include +#include + +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) + +#define ZMK_BHV_LOCK_KEY_MAX_HELD 10 +#define ZMK_BHV_LOCK_KEY_POSITION_FREE UINT32_MAX + +enum lock_persistence { + PERS_DISABLED = 0, + PERS_ENABLED = 1, + PERS_PER_PROFILE = 2 +}; + +struct behavior_custom_lock_key_config { + const struct device *dev; + char *unlocked_behavior_dev; + char *locked_behavior_dev; +}; + +struct behavior_custom_lock_var_config { + enum lock_persistence persistence; +}; + +struct active_state { + bool def; + bool profiles[ZMK_BLE_PROFILE_COUNT]; +}; + +struct behavior_custom_lock_var_data { + struct active_state active; +}; + +struct active_lock_key { + int layer; + uint32_t position; + struct zmk_behavior_binding binding; +}; + +struct active_lock_key active_lock_keys[ZMK_BHV_LOCK_KEY_MAX_HELD] = {}; + +static struct active_lock_key* find_lock_key(struct zmk_behavior_binding_event *event) { + for (int i = 0; i < ZMK_BHV_LOCK_KEY_MAX_HELD; i++) { + if (active_lock_keys[i].position == event->position && active_lock_keys[i].layer == event->layer) { + return &active_lock_keys[i]; + } + } + return NULL; +} + +static int new_lock_key(struct zmk_behavior_binding_event *event, struct zmk_behavior_binding binding) { + for (int i = 0; i < ZMK_BHV_LOCK_KEY_MAX_HELD; i++) { + struct active_lock_key *const ref_locks = &active_lock_keys[i]; + if (ref_locks->position == ZMK_BHV_LOCK_KEY_POSITION_FREE) { + ref_locks->position = event->position; + ref_locks->layer = event->layer; + ref_locks->binding = binding; + return 0; + } + } + return -ENOMEM; +} + +static void clear_lock_key(struct active_lock_key *lock_key) { + lock_key->position = ZMK_BHV_LOCK_KEY_POSITION_FREE; +} + +static bool* get_active_state(const struct device* dev) { + struct behavior_custom_lock_var_data* data = dev->data; + const struct behavior_custom_lock_var_config *config = dev->config; + + switch (config->persistence) { + case PERS_DISABLED: + case PERS_ENABLED: + return &data->active.def; + case PERS_PER_PROFILE: + if (zmk_endpoints_selected() == ZMK_ENDPOINT_BLE) { + return &data->active.profiles[zmk_ble_active_profile_index()]; + } else { + return &data->active.def; + } + default: + LOG_ERR("Unknown persistence value encountered %d", config->persistence); + } + + LOG_ERR("Couldn't find usable active state"); + return NULL; +} + +static void toggle_active_state(const struct device* dev) { + bool* state = get_active_state(dev); + if (state == NULL) { + LOG_ERR("encountered null state %s", dev->name); + return; + } + + *state = !(*state); +} + +#if IS_ENABLED(CONFIG_SETTINGS) + +static void lock_save_state(const char* name, struct active_state* active) { + char settings_name[30]; + sprintf(settings_name, "bhv/lock/%s", name); + settings_save_one(settings_name, active, sizeof(struct active_state)); +} + +static void lock_delete_state(const char* name) { + char settings_name[30]; + sprintf(settings_name, "bhv/lock/%s", name); + settings_delete(settings_name); +} + +static int lock_settings_load(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg) { + const struct device* dev = device_get_binding(name); + + if (dev == NULL) { + LOG_WRN("Unknown lock device from settings %s - purging from settings", name); + lock_delete_state(name); + return -1; + } + + struct behavior_custom_lock_var_data* data = dev->data; + const struct behavior_custom_lock_var_config *config = dev->config; + int rc; + + if (config->persistence == 0) { + LOG_WRN("key %s is not marked as persistent (%d), deleting", name, config->persistence); + lock_delete_state(name); + return 0; + } + + if (len != sizeof(struct active_state)) { + LOG_DBG("something is of with size %d", len); + return -EINVAL; + } + + rc = read_cb(cb_arg, &data->active, sizeof(struct active_state)); + if (rc >= 0) { + return 0; + } + + return rc; +} + +struct settings_handler lock_settings_conf = {.name = "bhv/lock", .h_set = lock_settings_load}; + +#endif + +static int behavior_lock_init(const struct device *_arg) { + +#if IS_ENABLED(CONFIG_SETTINGS) + settings_subsys_init(); + + int err = settings_register(&lock_settings_conf); + if (err) { + LOG_ERR("Failed to register the ext_power settings handler (err %d)", err); + return err; + } + + settings_load_subtree("bhv/lock"); +#endif + + return 0; +} + + +static int behavior_lock_key_init(const struct device *dev) { + return 0; +}; + +static int behavior_lock_var_init(const struct device *dev) { + for (int i = 0; i < ZMK_BHV_LOCK_KEY_MAX_HELD; i++) { + active_lock_keys[i].position = ZMK_BHV_LOCK_KEY_POSITION_FREE; + } + + return 0; +}; + +static int on_keymap_key_binding_pressed(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + const struct device *dev = device_get_binding(binding->behavior_dev); + const struct behavior_custom_lock_key_config *config = dev->config; + + const struct device *parent = config->dev; + + struct zmk_behavior_binding new_binding = {0}; + if (*get_active_state(parent)) { + new_binding.behavior_dev = config->locked_behavior_dev; + new_binding.param1 = binding->param2; + } else { + new_binding.behavior_dev = config->unlocked_behavior_dev; + new_binding.param1 = binding->param1; + } + + int res = new_lock_key(&event, new_binding); + if (res == -ENOMEM) { + LOG_WRN("Couldn't find space to store current lock press. Ignoring key"); + return ZMK_BEHAVIOR_OPAQUE; + } + + return behavior_keymap_binding_pressed(&new_binding, event); +} + +static int on_keymap_key_binding_released(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + + struct active_lock_key *cur_key = find_lock_key(&event); + + if (cur_key == NULL) { + LOG_WRN("Binding for layer: %d | position: %d not found. Not sure how to proceed", event.layer, event.position); + } else { + struct zmk_behavior_binding binding = cur_key->binding; + clear_lock_key(cur_key); + return behavior_keymap_binding_released(&binding, event); + } + return ZMK_BEHAVIOR_OPAQUE; +} + +static int on_keymap_var_binding_pressed(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + const struct device *dev = device_get_binding(binding->behavior_dev); + struct behavior_custom_lock_var_data* data = dev->data; + const struct behavior_custom_lock_var_config* config = dev->config; + + toggle_active_state(dev); + +#if IS_ENABLED(CONFIG_SETTINGS) + if (config->persistence != 0) { + lock_save_state(dev->name, &data->active); + } +#endif + + return ZMK_BEHAVIOR_OPAQUE; +} + +static int on_keymap_var_binding_released(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) { + return ZMK_BEHAVIOR_OPAQUE; +} + +static const struct behavior_driver_api behavior_lock_key_driver_api = { + .binding_pressed = on_keymap_key_binding_pressed, + .binding_released = on_keymap_key_binding_released +}; + +static const struct behavior_driver_api behavior_lock_var_driver_api = { + .binding_pressed = on_keymap_var_binding_pressed, + .binding_released = on_keymap_var_binding_released +}; + +#define CL_CHILD(id) \ + static struct behavior_custom_lock_key_config behavior_lock_key_config_##id = { \ + .dev = DEVICE_DT_GET(DT_PARENT(id)), \ + .unlocked_behavior_dev = DT_LABEL(DT_PHANDLE_BY_IDX(id, bindings, 0)), \ + .locked_behavior_dev = DT_LABEL(DT_PHANDLE_BY_IDX(id, bindings, 1)), \ + }; \ + DEVICE_DT_DEFINE(id, &behavior_lock_key_init, NULL, NULL, &behavior_lock_key_config_##id, \ + APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_lock_key_driver_api); + + +#define CL_INST(id) \ + DT_INST_FOREACH_CHILD(id, CL_CHILD) \ + static struct behavior_custom_lock_var_data behavior_lock_var_data_##id = { .active = false }; \ + static struct behavior_custom_lock_var_config behavior_lock_var_config_##id = { \ + .persistence = DT_INST_ENUM_IDX(id, persistence), \ + };\ + \ + DEVICE_DT_INST_DEFINE(id, &behavior_lock_var_init, NULL, \ + &behavior_lock_var_data_##id, &behavior_lock_var_config_##id, \ + APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_lock_var_driver_api); + + +DT_INST_FOREACH_STATUS_OKAY(CL_INST) + +SYS_INIT(behavior_lock_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); + +#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */