forked from kofal.net/zmk
feat: Allow layer behaviors to "lock" layers on (#2717)
* refactor(core)!: Allow layer behaviors to "lock" layers on Co-authored-by: Cem Aksoylar <caksoylar@users.noreply.github.com> * docs: Added documentation note on locking layers Co-authored-by: Cem Aksoylar <caksoylar@users.noreply.github.com> --------- Co-authored-by: Cem Aksoylar <caksoylar@users.noreply.github.com>
This commit is contained in:
@@ -15,6 +15,8 @@
|
||||
|
||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
|
||||
|
||||
static const struct behavior_parameter_value_metadata param_values[] = {
|
||||
@@ -36,16 +38,22 @@ static const struct behavior_parameter_metadata metadata = {
|
||||
|
||||
#endif
|
||||
|
||||
struct behavior_mo_config {
|
||||
bool locking;
|
||||
};
|
||||
|
||||
static int mo_keymap_binding_pressed(struct zmk_behavior_binding *binding,
|
||||
struct zmk_behavior_binding_event event) {
|
||||
LOG_DBG("position %d layer %d", event.position, binding->param1);
|
||||
return zmk_keymap_layer_activate(binding->param1);
|
||||
const struct behavior_mo_config *cfg = zmk_behavior_get_binding(binding->behavior_dev)->config;
|
||||
return zmk_keymap_layer_activate(binding->param1, cfg->locking);
|
||||
}
|
||||
|
||||
static int mo_keymap_binding_released(struct zmk_behavior_binding *binding,
|
||||
struct zmk_behavior_binding_event event) {
|
||||
LOG_DBG("position %d layer %d", event.position, binding->param1);
|
||||
return zmk_keymap_layer_deactivate(binding->param1);
|
||||
const struct behavior_mo_config *cfg = zmk_behavior_get_binding(binding->behavior_dev)->config;
|
||||
return zmk_keymap_layer_deactivate(binding->param1, cfg->locking);
|
||||
}
|
||||
|
||||
static const struct behavior_driver_api behavior_mo_driver_api = {
|
||||
@@ -56,5 +64,13 @@ static const struct behavior_driver_api behavior_mo_driver_api = {
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
|
||||
};
|
||||
|
||||
BEHAVIOR_DT_INST_DEFINE(0, NULL, NULL, NULL, NULL, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
|
||||
&behavior_mo_driver_api);
|
||||
#define MO_INST(n) \
|
||||
static const struct behavior_mo_config behavior_mo_config_##n = { \
|
||||
.locking = DT_INST_PROP_OR(n, locking, false), \
|
||||
}; \
|
||||
BEHAVIOR_DT_INST_DEFINE(n, NULL, NULL, NULL, &behavior_mo_config_##n, POST_KERNEL, \
|
||||
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mo_driver_api);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(MO_INST)
|
||||
|
||||
#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */
|
||||
@@ -17,10 +17,15 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
|
||||
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
|
||||
|
||||
struct behavior_to_config {
|
||||
bool locking;
|
||||
};
|
||||
|
||||
static int to_keymap_binding_pressed(struct zmk_behavior_binding *binding,
|
||||
struct zmk_behavior_binding_event event) {
|
||||
LOG_DBG("position %d layer %d", event.position, binding->param1);
|
||||
zmk_keymap_layer_to(binding->param1);
|
||||
const struct behavior_to_config *cfg = zmk_behavior_get_binding(binding->behavior_dev)->config;
|
||||
zmk_keymap_layer_to(binding->param1, cfg->locking);
|
||||
return ZMK_BEHAVIOR_OPAQUE;
|
||||
}
|
||||
|
||||
@@ -59,7 +64,13 @@ static const struct behavior_driver_api behavior_to_driver_api = {
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
|
||||
};
|
||||
|
||||
BEHAVIOR_DT_INST_DEFINE(0, NULL, NULL, NULL, NULL, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
|
||||
&behavior_to_driver_api);
|
||||
#define TO_INST(n) \
|
||||
static const struct behavior_to_config behavior_to_config_##n = { \
|
||||
.locking = DT_INST_PROP_OR(n, locking, false), \
|
||||
}; \
|
||||
BEHAVIOR_DT_INST_DEFINE(n, NULL, NULL, NULL, &behavior_to_config_##n, POST_KERNEL, \
|
||||
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_to_driver_api);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(TO_INST)
|
||||
|
||||
#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */
|
||||
|
||||
@@ -25,6 +25,7 @@ enum toggle_mode {
|
||||
|
||||
struct behavior_tog_config {
|
||||
enum toggle_mode toggle_mode;
|
||||
bool locking;
|
||||
};
|
||||
|
||||
static int tog_keymap_binding_pressed(struct zmk_behavior_binding *binding,
|
||||
@@ -34,11 +35,11 @@ static int tog_keymap_binding_pressed(struct zmk_behavior_binding *binding,
|
||||
const struct behavior_tog_config *cfg = zmk_behavior_get_binding(binding->behavior_dev)->config;
|
||||
switch (cfg->toggle_mode) {
|
||||
case ON:
|
||||
return zmk_keymap_layer_activate(binding->param1);
|
||||
return zmk_keymap_layer_activate(binding->param1, cfg->locking);
|
||||
case OFF:
|
||||
return zmk_keymap_layer_deactivate(binding->param1);
|
||||
return zmk_keymap_layer_deactivate(binding->param1, cfg->locking);
|
||||
case FLIP:
|
||||
return zmk_keymap_layer_toggle(binding->param1);
|
||||
return zmk_keymap_layer_toggle(binding->param1, cfg->locking);
|
||||
default:
|
||||
return -ENOTSUP;
|
||||
};
|
||||
@@ -79,13 +80,14 @@ static const struct behavior_driver_api behavior_tog_driver_api = {
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
|
||||
};
|
||||
|
||||
#define KT_INST(n) \
|
||||
#define TG_INST(n) \
|
||||
static const struct behavior_tog_config behavior_tog_config_##n = { \
|
||||
.toggle_mode = DT_ENUM_IDX(DT_DRV_INST(n), toggle_mode), \
|
||||
.locking = DT_INST_PROP_OR(n, locking, false), \
|
||||
}; \
|
||||
BEHAVIOR_DT_INST_DEFINE(n, NULL, NULL, NULL, &behavior_tog_config_##n, POST_KERNEL, \
|
||||
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_tog_driver_api);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(KT_INST)
|
||||
DT_INST_FOREACH_STATUS_OKAY(TG_INST)
|
||||
|
||||
#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */
|
||||
|
||||
@@ -49,28 +49,31 @@ static const struct conditional_layer_cfg CONDITIONAL_LAYER_CFGS[] = {
|
||||
static const int32_t NUM_CONDITIONAL_LAYER_CFGS =
|
||||
sizeof(CONDITIONAL_LAYER_CFGS) / sizeof(*CONDITIONAL_LAYER_CFGS);
|
||||
|
||||
static void conditional_layer_activate(int8_t layer) {
|
||||
// Ensures all layer updates are processed if one conditional layer activates another.
|
||||
static bool conditional_layer_updates_needed;
|
||||
// Tracks which layers have been locked by conditional layer activations.
|
||||
static uint32_t layer_locked_by_conditional = 0;
|
||||
|
||||
static void conditional_layer_activate(int8_t layer, bool locking) {
|
||||
// This may trigger another event that could, in turn, activate additional then-layers. However,
|
||||
// the process will eventually terminate (at worst, when every layer is active).
|
||||
if (!zmk_keymap_layer_active(layer)) {
|
||||
if (!zmk_keymap_layer_active(layer) || (locking && !zmk_keymap_layer_locked(layer))) {
|
||||
LOG_DBG("layer %d", layer);
|
||||
zmk_keymap_layer_activate(layer);
|
||||
zmk_keymap_layer_activate(layer, locking);
|
||||
}
|
||||
}
|
||||
|
||||
static void conditional_layer_deactivate(int8_t layer) {
|
||||
static void conditional_layer_deactivate(int8_t layer, bool locking) {
|
||||
// This may deactivate a then-layer that's already active via another mechanism (e.g., a
|
||||
// momentary layer behavior). However, the same problem arises when multiple keys with the same
|
||||
// &mo binding are held and then one is released, so it's probably not an issue in practice.
|
||||
if (zmk_keymap_layer_active(layer)) {
|
||||
if (zmk_keymap_layer_active(layer) && (!zmk_keymap_layer_locked(layer) || locking)) {
|
||||
LOG_DBG("layer %d", layer);
|
||||
zmk_keymap_layer_deactivate(layer);
|
||||
zmk_keymap_layer_deactivate(layer, locking);
|
||||
}
|
||||
}
|
||||
|
||||
static int layer_state_changed_listener(const zmk_event_t *ev) {
|
||||
static bool conditional_layer_updates_needed;
|
||||
|
||||
conditional_layer_updates_needed = true;
|
||||
|
||||
// Semaphore ensures we don't re-enter the loop in the middle of doing update, and
|
||||
@@ -84,7 +87,6 @@ static int layer_state_changed_listener(const zmk_event_t *ev) {
|
||||
int8_t max_then_layer = -1;
|
||||
uint32_t then_layers = 0;
|
||||
uint32_t then_layer_state = 0;
|
||||
|
||||
conditional_layer_updates_needed = false;
|
||||
|
||||
// On layer state changes, examines each conditional layer config to determine if then-layer
|
||||
@@ -92,23 +94,29 @@ static int layer_state_changed_listener(const zmk_event_t *ev) {
|
||||
for (int i = 0; i < NUM_CONDITIONAL_LAYER_CFGS; i++) {
|
||||
const struct conditional_layer_cfg *cfg = CONDITIONAL_LAYER_CFGS + i;
|
||||
zmk_keymap_layers_state_t mask = cfg->if_layers_state_mask;
|
||||
then_layers |= BIT(cfg->then_layer);
|
||||
WRITE_BIT(then_layers, cfg->then_layer, true);
|
||||
max_then_layer = MAX(max_then_layer, cfg->then_layer);
|
||||
|
||||
// Activate then-layer if and only if all if-layers are already active. Note that we
|
||||
// reevaluate the current layer state for each config since activation of one layer can
|
||||
// also trigger activation of another.
|
||||
if ((zmk_keymap_layer_state() & mask) == mask) {
|
||||
then_layer_state |= BIT(cfg->then_layer);
|
||||
WRITE_BIT(then_layer_state, cfg->then_layer, true);
|
||||
}
|
||||
// Same as above, but for the lock status
|
||||
if ((zmk_keymap_layer_locks() & mask) == mask) {
|
||||
WRITE_BIT(layer_locked_by_conditional, cfg->then_layer, true);
|
||||
}
|
||||
}
|
||||
|
||||
for (uint8_t layer = 0; layer <= max_then_layer; layer++) {
|
||||
if ((BIT(layer) & then_layers) != 0U) {
|
||||
bool locking = (BIT(layer) & layer_locked_by_conditional) != 0U;
|
||||
if ((BIT(layer) & then_layer_state) != 0U) {
|
||||
conditional_layer_activate(layer);
|
||||
conditional_layer_activate(layer, locking);
|
||||
} else {
|
||||
conditional_layer_deactivate(layer);
|
||||
conditional_layer_deactivate(layer, locking);
|
||||
WRITE_BIT(layer_locked_by_conditional, layer, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||
#include <zmk/events/layer_state_changed.h>
|
||||
#include <zmk/events/sensor_event.h>
|
||||
|
||||
static zmk_keymap_layers_state_t _zmk_keymap_layer_locks = 0;
|
||||
static zmk_keymap_layers_state_t _zmk_keymap_layer_state = 0;
|
||||
static zmk_keymap_layer_id_t _zmk_keymap_layer_default = 0;
|
||||
|
||||
@@ -130,7 +131,7 @@ uint8_t map_layer_id_to_index(zmk_keymap_layer_id_t layer_id) {
|
||||
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_KEYMAP_LAYER_REORDERING)
|
||||
|
||||
static inline int set_layer_state(zmk_keymap_layer_id_t layer_id, bool state) {
|
||||
static inline int set_layer_state(zmk_keymap_layer_id_t layer_id, bool state, bool locking) {
|
||||
int ret = 0;
|
||||
if (layer_id >= ZMK_KEYMAP_LAYERS_LEN) {
|
||||
return -EINVAL;
|
||||
@@ -141,12 +142,22 @@ static inline int set_layer_state(zmk_keymap_layer_id_t layer_id, bool state) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Non-forcing disables should not change a locked active layer
|
||||
if (!locking && !state && (_zmk_keymap_layer_locks & BIT(layer_id))) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
zmk_keymap_layers_state_t old_state = _zmk_keymap_layer_state;
|
||||
zmk_keymap_layers_state_t old_locks = _zmk_keymap_layer_locks;
|
||||
WRITE_BIT(_zmk_keymap_layer_state, layer_id, state);
|
||||
if (locking) {
|
||||
WRITE_BIT(_zmk_keymap_layer_locks, layer_id, state);
|
||||
}
|
||||
// Don't send state changes unless there was an actual change
|
||||
if (old_state != _zmk_keymap_layer_state) {
|
||||
LOG_DBG("layer_changed: layer %d state %d", layer_id, state);
|
||||
ret = raise_layer_state_changed(layer_id, state);
|
||||
if (old_state != _zmk_keymap_layer_state || old_locks != _zmk_keymap_layer_locks) {
|
||||
LOG_DBG("layer_changed: layer %d state %d locked %d", layer_id, state, locking);
|
||||
|
||||
ret = raise_layer_state_changed(layer_id, state, locking);
|
||||
if (ret < 0) {
|
||||
LOG_WRN("Failed to raise layer state changed (%d)", ret);
|
||||
}
|
||||
@@ -165,6 +176,8 @@ zmk_keymap_layer_id_t zmk_keymap_layer_default(void) { return _zmk_keymap_layer_
|
||||
|
||||
zmk_keymap_layers_state_t zmk_keymap_layer_state(void) { return _zmk_keymap_layer_state; }
|
||||
|
||||
zmk_keymap_layers_state_t zmk_keymap_layer_locks(void) { return _zmk_keymap_layer_locks; }
|
||||
|
||||
bool zmk_keymap_layer_active_with_state(zmk_keymap_layer_id_t layer,
|
||||
zmk_keymap_layers_state_t state_to_test) {
|
||||
// The default layer is assumed to be ALWAYS ACTIVE so we include an || here to ensure nobody
|
||||
@@ -176,6 +189,10 @@ bool zmk_keymap_layer_active(zmk_keymap_layer_id_t layer) {
|
||||
return zmk_keymap_layer_active_with_state(layer, _zmk_keymap_layer_state);
|
||||
};
|
||||
|
||||
bool zmk_keymap_layer_locked(zmk_keymap_layer_id_t layer) {
|
||||
return zmk_keymap_layer_active_with_state(layer, _zmk_keymap_layer_locks);
|
||||
}
|
||||
|
||||
zmk_keymap_layer_index_t zmk_keymap_highest_layer_active(void) {
|
||||
for (int layer_idx = ZMK_KEYMAP_LAYERS_LEN - 1;
|
||||
layer_idx >= LAYER_ID_TO_INDEX(_zmk_keymap_layer_default); layer_idx--) {
|
||||
@@ -192,26 +209,28 @@ zmk_keymap_layer_index_t zmk_keymap_highest_layer_active(void) {
|
||||
return LAYER_ID_TO_INDEX(zmk_keymap_layer_default());
|
||||
}
|
||||
|
||||
int zmk_keymap_layer_activate(zmk_keymap_layer_id_t layer) { return set_layer_state(layer, true); };
|
||||
|
||||
int zmk_keymap_layer_deactivate(zmk_keymap_layer_id_t layer) {
|
||||
return set_layer_state(layer, false);
|
||||
int zmk_keymap_layer_activate(zmk_keymap_layer_id_t layer, bool locking) {
|
||||
return set_layer_state(layer, true, locking);
|
||||
};
|
||||
|
||||
int zmk_keymap_layer_toggle(zmk_keymap_layer_id_t layer) {
|
||||
if (zmk_keymap_layer_active(layer)) {
|
||||
return zmk_keymap_layer_deactivate(layer);
|
||||
int zmk_keymap_layer_deactivate(zmk_keymap_layer_id_t layer, bool locking) {
|
||||
return set_layer_state(layer, false, locking);
|
||||
};
|
||||
|
||||
int zmk_keymap_layer_toggle(zmk_keymap_layer_id_t layer, bool locking) {
|
||||
if (zmk_keymap_layer_active(layer) && (!locking || zmk_keymap_layer_locked(layer))) {
|
||||
return zmk_keymap_layer_deactivate(layer, locking);
|
||||
}
|
||||
|
||||
return zmk_keymap_layer_activate(layer);
|
||||
return zmk_keymap_layer_activate(layer, locking);
|
||||
};
|
||||
|
||||
int zmk_keymap_layer_to(zmk_keymap_layer_id_t layer) {
|
||||
int zmk_keymap_layer_to(zmk_keymap_layer_id_t layer, bool locking) {
|
||||
for (int i = ZMK_KEYMAP_LAYERS_LEN - 1; i >= 0; i--) {
|
||||
zmk_keymap_layer_deactivate(i);
|
||||
zmk_keymap_layer_deactivate(i, locking);
|
||||
}
|
||||
|
||||
zmk_keymap_layer_activate(layer);
|
||||
zmk_keymap_layer_activate(layer, locking);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -72,10 +72,10 @@ static void update_layer_state(struct temp_layer_state *state, bool activate) {
|
||||
|
||||
state->is_active = activate;
|
||||
if (activate) {
|
||||
zmk_keymap_layer_activate(state->toggle_layer);
|
||||
zmk_keymap_layer_activate(state->toggle_layer, false);
|
||||
LOG_DBG("Layer %d activated", state->toggle_layer);
|
||||
} else {
|
||||
zmk_keymap_layer_deactivate(state->toggle_layer);
|
||||
zmk_keymap_layer_deactivate(state->toggle_layer, false);
|
||||
LOG_DBG("Layer %d deactivated", state->toggle_layer);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user