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: lock a momentary layer #1984

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 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/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
target_sources(app PRIVATE src/behaviors/behavior_key_repeat.c)
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_MACRO app PRIVATE src/behaviors/behavior_macro.c)
target_sources(app PRIVATE src/behaviors/behavior_momentary_layer.c)
target_sources(app PRIVATE src/behaviors/behavior_momentary_layer_lock.c)
target_sources(app PRIVATE src/behaviors/behavior_mod_morph.c)
target_sources(app PRIVATE src/behaviors/behavior_outputs.c)
target_sources(app PRIVATE src/behaviors/behavior_toggle_layer.c)
Expand Down
1 change: 1 addition & 0 deletions app/dts/behaviors.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <behaviors/gresc.dtsi>
#include <behaviors/sticky_key.dtsi>
#include <behaviors/momentary_layer.dtsi>
#include <behaviors/momentary_layer_lock.dtsi>
#include <behaviors/toggle_layer.dtsi>
#include <behaviors/to_layer.dtsi>
#include <behaviors/reset.dtsi>
Expand Down
15 changes: 15 additions & 0 deletions app/dts/behaviors/momentary_layer_lock.dtsi
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

/ {
behaviors {
/omit-if-no-ref/ molock: behavior_momentary_layer_lock {
compatible = "zmk,behavior-momentary-layer-lock";
#binding-cells = <0>;
bindings = <&to 0>;
};
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright (c) 2023 The ZMK Contributors
# SPDX-License-Identifier: MIT

description: Momentary layer on press/release behavior

compatible: "zmk,behavior-momentary-layer-lock"

include: zero_param.yaml

properties:
bindings:
type: phandle-array
required: true
15 changes: 15 additions & 0 deletions app/include/zmk/momentary_layer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#pragma once

#include <zmk/keymap.h>

// Locks all active momentary layers so they are not disabled when the key is
// released.
//
// Returns a set of the layers that were locked.
zmk_keymap_layers_state_t zmk_lock_active_momentary_layers();
37 changes: 28 additions & 9 deletions app/src/behaviors/behavior_momentary_layer.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,51 @@

#include <zmk/keymap.h>
#include <zmk/behavior.h>
#include <zmk/momentary_layer.h>

LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);

struct behavior_mo_config {};
struct behavior_mo_data {};
struct behavior_mo_data {
zmk_keymap_layers_state_t active_momentary_layers;
zmk_keymap_layers_state_t ignore_on_release;
};

static const struct behavior_mo_config behavior_mo_config = {};
static struct behavior_mo_data behavior_mo_data;

zmk_keymap_layers_state_t zmk_lock_active_momentary_layers() {
return behavior_mo_data.ignore_on_release = behavior_mo_data.active_momentary_layers;
}

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);
int layer = binding->param1;
LOG_DBG("position %d layer %d", event.position, layer);
WRITE_BIT(behavior_mo_data.active_momentary_layers, layer, true);
return zmk_keymap_layer_activate(layer);
}

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);
int layer = binding->param1;
LOG_DBG("position %d layer %d", event.position, layer);
WRITE_BIT(behavior_mo_data.active_momentary_layers, layer, false);

// If the layer is locked, don't deactivate it. Instead clear the
// ignore_on_release flag so the next press/release will.
if (behavior_mo_data.ignore_on_release & BIT(layer)) {
WRITE_BIT(behavior_mo_data.ignore_on_release, layer, false);
return 0;
}

return zmk_keymap_layer_deactivate(binding->param1);
}

static const struct behavior_driver_api behavior_mo_driver_api = {
.binding_pressed = mo_keymap_binding_pressed, .binding_released = mo_keymap_binding_released};

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);
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mo_driver_api);
96 changes: 96 additions & 0 deletions app/src/behaviors/behavior_momentary_layer_lock.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#define DT_DRV_COMPAT zmk_behavior_momentary_layer_lock

#include <zephyr/device.h>
#include <drivers/behavior.h>
#include <zephyr/logging/log.h>

#include <zmk/keymap.h>
#include <zmk/behavior.h>
#include <zmk/momentary_layer.h>

LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);

#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)

struct behavior_mo_lock_config {
struct zmk_behavior_binding fallback_binding;
};

struct behavior_mo_lock_data {
bool is_fallback_binding_pressed;
};

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

static int mo_lock_keymap_binding_pressed(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
LOG_DBG("%d molock pressed", event.position);

zmk_keymap_layers_state_t locked_layers = zmk_lock_active_momentary_layers();

if (!locked_layers) {
LOG_DBG("no layers locked, falling back to %s", binding->behavior_dev);

const struct device *dev = zmk_behavior_get_binding(binding->behavior_dev);
const struct behavior_mo_lock_config *config = dev->config;
struct zmk_behavior_binding fallback_binding = config->fallback_binding;
struct behavior_mo_lock_data *data = dev->data;

data->is_fallback_binding_pressed = true;
return behavior_keymap_binding_pressed(&fallback_binding, event);
} else {
LOG_DBG("locked layers: %#08x", locked_layers);
}

return 0;
}

static int mo_lock_keymap_binding_released(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
LOG_DBG("%d molock released", event.position);

const struct device *dev = zmk_behavior_get_binding(binding->behavior_dev);
struct behavior_mo_lock_data *data = dev->data;

if (data->is_fallback_binding_pressed) {
const struct behavior_mo_lock_config *config = dev->config;
struct zmk_behavior_binding fallback_binding = config->fallback_binding;

data->is_fallback_binding_pressed = false;
return behavior_keymap_binding_released(&fallback_binding, event);
}

return 0;
}

#define KP_INST(n) \
static struct behavior_mo_lock_config behavior_mo_lock_config_##n = { \
.fallback_binding = \
{ \
.behavior_dev = DEVICE_DT_NAME(DT_INST_PHANDLE_BY_IDX(n, bindings, 0)), \
.param1 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(n, bindings, 0, param1), (0), \
(DT_INST_PHA_BY_IDX(n, bindings, 0, param1))), \
.param2 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(n, bindings, 0, param2), (0), \
(DT_INST_PHA_BY_IDX(n, bindings, 0, param))), \
}, \
}; \
static struct behavior_mo_lock_data behavior_mo_lock_data_##n; \
\
BEHAVIOR_DT_INST_DEFINE(n, behavior_mo_lock_init, NULL, &behavior_mo_lock_data_##n, \
&behavior_mo_lock_config_##n, POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mo_lock_driver_api);

static const struct behavior_driver_api behavior_mo_lock_driver_api = {
.binding_pressed = mo_lock_keymap_binding_pressed,
.binding_released = mo_lock_keymap_binding_released,
};

DT_INST_FOREACH_STATUS_OKAY(KP_INST)

#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */
3 changes: 3 additions & 0 deletions app/tests/momentary-layer-lock/1-normal/events.patterns
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
s/.*hid_listener_keycode/kp/p
s/.*set_layer_state: layer_changed:/layer_changed:/p
s/.*mo_keymap_binding/mo/p
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mo_pressed: position 1 layer 1
layer_changed: layer 1 state 1
mo_released: position 1 layer 1
kp_pressed: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00
22 changes: 22 additions & 0 deletions app/tests/momentary-layer-lock/1-normal/native_posix_64.keymap
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include "../behavior_keymap.dtsi"

&kscan {
events = <
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,10)
ZMK_MOCK_RELEASE(0,1,10)

ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
s/.*hid_listener_keycode/kp/p
s/.*set_layer_state: layer_changed:/layer_changed:/p
s/.*mo_keymap_binding/mo/p
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
mo_pressed: position 1 layer 1
layer_changed: layer 1 state 1
mo_released: position 1 layer 1
kp_pressed: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00
mo_pressed: position 1 layer 1
mo_released: position 1 layer 1
layer_changed: layer 1 state 0
kp_pressed: usage_page 0x07 keycode 0x27 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x27 implicit_mods 0x00 explicit_mods 0x00
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include "../behavior_keymap.dtsi"

&kscan {
events = <
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,10)
ZMK_MOCK_RELEASE(0,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)
>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
s/.*hid_listener_keycode/kp/p
s/.*set_layer_state: layer_changed:/layer_changed:/p
s/.*mo_keymap_binding/mo/p
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
mo_pressed: position 1 layer 1
layer_changed: layer 1 state 1
mo_released: position 1 layer 1
kp_pressed: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00
layer_changed: layer 1 state 0
layer_changed: layer 0 state 1
kp_pressed: usage_page 0x07 keycode 0x27 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x27 implicit_mods 0x00 explicit_mods 0x00
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include "../behavior_keymap.dtsi"

&kscan {
events = <
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,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)
>;
};
3 changes: 3 additions & 0 deletions app/tests/momentary-layer-lock/4-relock/events.patterns
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
s/.*hid_listener_keycode/kp/p
s/.*set_layer_state: layer_changed:/layer_changed:/p
s/.*mo_keymap_binding/mo/p
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
mo_pressed: position 1 layer 1
layer_changed: layer 1 state 1
mo_released: position 1 layer 1
kp_pressed: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00
mo_pressed: position 1 layer 1
mo_released: position 1 layer 1
kp_pressed: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x1E implicit_mods 0x00 explicit_mods 0x00
30 changes: 30 additions & 0 deletions app/tests/momentary-layer-lock/4-relock/native_posix_64.keymap
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#include <dt-bindings/zmk/keys.h>
#include <behaviors.dtsi>
#include <dt-bindings/zmk/kscan_mock.h>
#include "../behavior_keymap.dtsi"

&kscan {
events = <
ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,10)
ZMK_MOCK_RELEASE(0,1,10)

ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)

ZMK_MOCK_PRESS(0,1,10)
ZMK_MOCK_PRESS(1,0,10)
ZMK_MOCK_RELEASE(1,0,10)
ZMK_MOCK_RELEASE(0,1,10)

ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
>;
};
Loading