Skip to content

Commit

Permalink
feat: Allow layer behaviors to "lock" layers on
Browse files Browse the repository at this point in the history
  • Loading branch information
Nick-Munnich committed Dec 16, 2024
1 parent d0016b3 commit 8845219
Show file tree
Hide file tree
Showing 18 changed files with 229 additions and 34 deletions.
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;
};
};
};
5 changes: 5 additions & 0 deletions app/dts/bindings/behaviors/zmk,behavior-momentary-layer.yaml
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, true), \
}; \
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, true), \
}; \
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
35 changes: 24 additions & 11 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);
LOG_DBG("layer_changed: layer %d state %d locked %d", layer_id, state, locking);

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: 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
20 changes: 20 additions & 0 deletions app/tests/conditional-layer/locked-layers/keycode_events.snapshot
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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
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
64 changes: 64 additions & 0 deletions app/tests/conditional-layer/locked-layers/native_posix_64.keymap
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)
>;
};
16 changes: 13 additions & 3 deletions app/tests/toggle-layer/behavior_keymap.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,23 @@
#include <dt-bindings/zmk/kscan_mock.h>

/ {
behaviors {
tog_off: toggle_layer_off_only {
compatible = "zmk,behavior-toggle-layer";
#binding-cells = <1>;
display-name = "Toggle Layer Off";
toggle-mode = "off";
locking;
};
};

keymap {
compatible = "zmk,keymap";

default_layer {
bindings = <
&kp B &tog 1
&kp D &to 1>;
&mo 2 &to 1>;
};

lower_layer {
Expand All @@ -20,8 +30,8 @@

raise_layer {
bindings = <
&kp W &kp U
&kp X &kp M>;
&kp W &tog 2
&tog_off 2 &mo 2>;
};
};
};
Loading

0 comments on commit 8845219

Please sign in to comment.