Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow layer behaviors to "lock" layers on #2717

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/dts/behaviors/to_layer.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
compatible = "zmk,behavior-to-layer";
#binding-cells = <1>;
display-name = "To Layer";
locking;
};
};
};
1 change: 1 addition & 0 deletions app/dts/behaviors/toggle_layer.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
compatible = "zmk,behavior-toggle-layer";
#binding-cells = <1>;
display-name = "Toggle Layer";
locking;
};
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ description: Momentary layer on press/release behavior
compatible: "zmk,behavior-momentary-layer"

include: one_param.yaml

properties:
locking:
type: boolean
description: Whether to "lock" the layer active, preventing behaviors without the "locking" property from deactivating the layer
5 changes: 5 additions & 0 deletions app/dts/bindings/behaviors/zmk,behavior-to-layer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ description: To Layer
compatible: "zmk,behavior-to-layer"

include: one_param.yaml

properties:
locking:
type: boolean
description: Whether to "lock" the layer active, preventing behaviors without the "locking" property from deactivating the layer
4 changes: 3 additions & 1 deletion app/dts/bindings/behaviors/zmk,behavior-toggle-layer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ include: one_param.yaml
properties:
toggle-mode:
type: string
required: false
default: "flip"
enum:
- "on"
- "off"
- "flip"
locking:
type: boolean
description: Whether to "lock" the layer active, preventing behaviors without the "locking" property from deactivating the layer
8 changes: 4 additions & 4 deletions app/include/zmk/keymap.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ zmk_keymap_layer_id_t zmk_keymap_layer_default(void);
zmk_keymap_layers_state_t zmk_keymap_layer_state(void);
bool zmk_keymap_layer_active(zmk_keymap_layer_id_t layer);
zmk_keymap_layer_index_t zmk_keymap_highest_layer_active(void);
int zmk_keymap_layer_activate(zmk_keymap_layer_id_t layer);
int zmk_keymap_layer_deactivate(zmk_keymap_layer_id_t layer);
int zmk_keymap_layer_toggle(zmk_keymap_layer_id_t layer);
int zmk_keymap_layer_to(zmk_keymap_layer_id_t layer);
int zmk_keymap_layer_activate(zmk_keymap_layer_id_t layer, bool locking);
int zmk_keymap_layer_deactivate(zmk_keymap_layer_id_t layer, bool locking);
int zmk_keymap_layer_toggle(zmk_keymap_layer_id_t layer, bool locking);
int zmk_keymap_layer_to(zmk_keymap_layer_id_t layer, bool locking);
const char *zmk_keymap_layer_name(zmk_keymap_layer_id_t layer);

const struct zmk_behavior_binding *zmk_keymap_get_layer_binding_at_idx(zmk_keymap_layer_id_t layer,
Expand Down
23 changes: 16 additions & 7 deletions app/src/behaviors/behavior_momentary_layer.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,25 @@ static const struct behavior_parameter_metadata metadata = {

#endif

struct behavior_mo_config {};
struct behavior_mo_data {};
struct behavior_mo_config {
bool locking;
};

static int behavior_mo_init(const struct device *dev) { return 0; };

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 = {
Expand All @@ -61,9 +65,14 @@ static const struct behavior_driver_api behavior_mo_driver_api = {
#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
};

static const struct behavior_mo_config behavior_mo_config = {};

static struct behavior_mo_data behavior_mo_data;

BEHAVIOR_DT_INST_DEFINE(0, behavior_mo_init, NULL, &behavior_mo_data, &behavior_mo_config,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mo_driver_api);
#define KT_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, behavior_mo_init, NULL, &behavior_mo_data, &behavior_mo_config_##n, \
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
&behavior_mo_driver_api);

DT_INST_FOREACH_STATUS_OKAY(KT_INST)
17 changes: 14 additions & 3 deletions app/src/behaviors/behavior_to_layer.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,17 @@ 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 behavior_to_init(const struct device *dev) { return 0; };

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;
}

Expand Down Expand Up @@ -61,7 +66,13 @@ static const struct behavior_driver_api behavior_to_driver_api = {
#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
};

BEHAVIOR_DT_INST_DEFINE(0, behavior_to_init, NULL, NULL, NULL, POST_KERNEL,
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_to_driver_api);
#define KT_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, behavior_to_init, NULL, NULL, &behavior_to_config_##n, POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_to_driver_api);

DT_INST_FOREACH_STATUS_OKAY(KT_INST)

#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */
8 changes: 5 additions & 3 deletions app/src/behaviors/behavior_toggle_layer.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ enum toggle_mode {

struct behavior_tog_config {
enum toggle_mode toggle_mode;
bool locking;
};

static int behavior_tog_init(const struct device *dev) { return 0; };
Expand All @@ -36,11 +37,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;
};
Expand Down Expand Up @@ -84,6 +85,7 @@ static const struct behavior_driver_api behavior_tog_driver_api = {
#define KT_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, behavior_tog_init, NULL, NULL, &behavior_tog_config_##n, \
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
Expand Down
4 changes: 2 additions & 2 deletions app/src/conditional_layer.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ static void conditional_layer_activate(int8_t layer) {
// the process will eventually terminate (at worst, when every layer is active).
if (!zmk_keymap_layer_active(layer)) {
LOG_DBG("layer %d", layer);
zmk_keymap_layer_activate(layer);
zmk_keymap_layer_activate(layer, false);
}
}

Expand All @@ -64,7 +64,7 @@ static void conditional_layer_deactivate(int8_t layer) {
// &mo binding are held and then one is released, so it's probably not an issue in practice.
if (zmk_keymap_layer_active(layer)) {
LOG_DBG("layer %d", layer);
zmk_keymap_layer_deactivate(layer);
zmk_keymap_layer_deactivate(layer, false);
}
}

Expand Down
33 changes: 23 additions & 10 deletions app/src/keymap.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -131,7 +132,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;
Expand All @@ -142,11 +143,21 @@ 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 >> layer_id) & 1)) {
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 (ret < 0) {
LOG_WRN("Failed to raise layer state changed (%d)", ret);
Expand Down Expand Up @@ -193,26 +204,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_activate(zmk_keymap_layer_id_t layer, bool locking) {
return set_layer_state(layer, true, locking);
};

int zmk_keymap_layer_deactivate(zmk_keymap_layer_id_t layer) {
return set_layer_state(layer, false);
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) {
int zmk_keymap_layer_toggle(zmk_keymap_layer_id_t layer, bool locking) {
if (zmk_keymap_layer_active(layer)) {
return zmk_keymap_layer_deactivate(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;
}
Expand Down
4 changes: 2 additions & 2 deletions app/src/pointing/input_processor_temp_layer.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,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);
}
}
Expand Down
4 changes: 4 additions & 0 deletions app/tests/conditional-layer/locked-layers/events.patterns
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
s/.*hid_listener_keycode/kp/p
s/.*mo_keymap_binding/mo/p
s/.*tog_keymap_binding/tog/p
s/.*conditional_layer/cl/p
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
kp_pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
tog_pressed: position 2 layer 1
tog_released: position 2 layer 1
kp_pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
tog_pressed: position 3 layer 2
cl_activate: layer 3
tog_released: position 3 layer 2
kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
mo_pressed: position 1 layer 3
mo_released: position 1 layer 3
cl_activate: layer 3
kp_pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
tog_pressed: position 2 layer 1
cl_deactivate: layer 3
tog_released: position 2 layer 1
kp_pressed: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x06 implicit_mods 0x00 explicit_mods 0x00
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#include <behaviors.dtsi>
#include <dt-bindings/zmk/keys.h>
#include <dt-bindings/zmk/kscan_mock.h>

/ {
conditional_layers {
compatible = "zmk,conditional-layers";
tri_layer {
if-layers = <1 2>;
then-layer = <3>;
};
};

keymap {
compatible = "zmk,keymap";
default_layer {
bindings = <
&kp A &mo 3
&tog 1 &tog 2
>;
};
layer_1 {
bindings = <
&kp B &trans
&trans &trans
>;
};
layer_2 {
bindings = <
&kp C &trans
&trans &trans
>;
};
layer_3 {
bindings = <
&kp D &trans
&trans &trans
>;
};
};
};

&kscan {
events = <
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,10)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
ZMK_MOCK_PRESS(1,1,10)
ZMK_MOCK_RELEASE(1,1,10)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_RELEASE(0,1,10)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,10)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
>;
};
Loading
Loading