This commit is contained in:
Dylan MacKenzie 2024-08-01 13:40:42 +09:00 committed by GitHub
commit 5124284055
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 486 additions and 9 deletions

View file

@ -53,6 +53,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)

View file

@ -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>

View file

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

View file

@ -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

View file

@ -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();

View file

@ -12,6 +12,7 @@
#include <zmk/keymap.h>
#include <zmk/behavior.h>
#include <zmk/momentary_layer.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
@ -37,19 +38,41 @@ static const struct behavior_parameter_metadata metadata = {
#endif
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);
}
@ -61,9 +84,5 @@ 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);
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_mo_driver_api);

View file

@ -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) */

View file

@ -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

View file

@ -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

View file

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

View file

@ -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

View file

@ -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

View file

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

View file

@ -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

View file

@ -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

View file

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

View file

@ -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

View file

@ -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

View file

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

View file

@ -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

View file

@ -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

View file

@ -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,1,10)
ZMK_MOCK_RELEASE(1,1,10)
ZMK_MOCK_RELEASE(0,1,10)
ZMK_MOCK_PRESS(0,0,10)
ZMK_MOCK_RELEASE(0,0,10)
>;
};

View file

@ -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

View file

@ -0,0 +1,2 @@
kp_pressed: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00
kp_released: usage_page 0x07 keycode 0x09 implicit_mods 0x00 explicit_mods 0x00

View file

@ -0,0 +1,17 @@
/*
* 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(1,1,10)
ZMK_MOCK_RELEASE(1,1,10)
>;
};

View file

@ -0,0 +1,27 @@
/ {
behaviors{
kp_molock: kp_molock {
compatible = "zmk,behavior-momentary-layer-lock";
label = "KP_MOLOCK";
#binding-cells = <0>;
bindings = <&kp F>;
};
};
keymap {
compatible = "zmk,keymap";
label ="Default keymap";
default_layer {
bindings = <
&kp N0 &mo 1
&molock &kp_molock>;
};
layer_1 {
bindings = <
&kp N1 &trans
&trans &trans>;
};
};
};

View file

@ -153,6 +153,68 @@ Example:
It is possible to use "toggle layer" to have keys that raise and lower the layers as well.
## Momentary Layer Lock
Even if you mostly use [momentary layers](#momentary-layer) instead of `&to` or `&tog`, it's occasionally useful to permanently enable a layer without needing to hold anything down. Instead of creating an additional `&tog` or `&to` binding for each such layer, you can use `&molock`.
If `&molock` is pressed while any number of `&mo` bindings are being held, those momentary layers will not be deactivated when the corresponding `&mo` key is released. As a result, those momentary layers become "locked" until that `&mo` key is pressed and released a second time or the layer becomes deactivated by some other means (e.g. a `&tog` binding for that layer or a `&to` binding for any other one).
If `&molock` is pressed while no `&mo` bindings are being held, it triggers a user-configurable fallback behavior. The default fallback behavior returns to the base layer (`&to 0`), deactivating any locked momentary layers in the process.
### Behavior Binding
- Reference: `&molock`
Example:
```dts
&molock
```
Lock a symbol layer:
```dts
#define BASE 0
#define SYMS 1
/ {
keymap {
compatible = "zmk,keymap";
base_layer {
bindings = <&mo SYMS &kp Z &kp M &kp K >;
};
symbol_layer {
bindings = <&trans &kp PLUS &kp MINUS &molock>;
};
};
};
```
Holding down the leftmost key (`&mo SYMS`), then pressing and releasing the rightmost key (`&molock`), will lock the symbol layer. Even after releasing the leftmost key, the symbol layer remains active.
To return to the base layer, press and release either the leftmost key (triggering the `&mo SYMS` behavior a second time) or the rightmost key (triggering the default fallback behavior for `&molock`).
### Configuration
You can configure a different fallback behavior by overriding the `bindings` property of the built-in `&molock` behavior. For example, to return to layer 1 (instead of layer 0):
```dts
&molock {
bindings = <&to 1>;
};
```
You can also create any number of custom `&molock` behaviors by using `compatible = "zmk,behavior-momentary-layer-lock"` like so:
```dts
// Presses F if triggered while no momentary layers are active
kp_molock: kp_molock {
compatible = "zmk,behavior-momentary-layer-lock";
bindings = <&kp F>;
};
```
## Conditional Layers
The "conditional layers" feature enables a particular layer when all layers in a specified set are active.

View file

@ -221,6 +221,29 @@ Applies to: `compatible = "zmk,behavior-sensor-rotate-var"`
With `compatible = "zmk,behavior-sensor-rotate-var"`, this behavior forwards the first parameter it receives to the parameter of the first behavior specified in `bindings`, and second parameter to the parameter of the second behavior.
## Momentary Layer Lock
Creates a custom behavior that locks any active momentary layers or triggers the fallback behavior specified in `bindings` if none are active.
See the [momentary layer lock](../behaviors/layers.md#momentary-layer-lock) documentation for more details and examples.
### Devicetree
Definition file: [zmk/app/dts/bindings/behaviors/zmk,behavior-momentary-layer-lock.yaml](https://github.com/zmkfirmware/zmk/blob/main/app/dts/bindings/behaviors/zmk%2Cbehavior-momentary-layer-lock.yaml)
Applies to: `compatible = "zmk,behavior-momentary-layer-lock"`
| Property | Type | Description |
| ---------------- | ------------- | ------------------------------------------------------- |
| `#binding-cells` | int | Must be `0` |
| `bindings` | phandle array | A behavior to trigger if no momentary layers are active |
You can use the following nodes to tweak the default behaviors:
| Node | Behavior |
| --------- | ------------------------------------------------------------------- |
| `&molock` | [Momentary Layer Lock](../behaviors/layers.md#momentary-layer-lock) |
## Sticky Key
Creates a custom behavior that triggers a behavior and keeps it pressed it until another key is pressed and released.