capslock-based implementation of caps_word

This commit is contained in:
snoyer 2022-10-07 17:54:00 +04:00
parent f906d740ac
commit 7362b9bb66
4 changed files with 67 additions and 76 deletions

View file

@ -12,7 +12,8 @@
compatible = "zmk,behavior-caps-word"; compatible = "zmk,behavior-caps-word";
label = "CAPS_WORD"; label = "CAPS_WORD";
#binding-cells = <0>; #binding-cells = <0>;
continue-list = <UNDERSCORE BACKSPACE DELETE>; bindings = <&kp CAPSLOCK>;
break-list = <SPACE TAB ENTER ESCAPE>;
}; };
}; };
}; };

View file

@ -8,8 +8,9 @@ compatible: "zmk,behavior-caps-word"
include: zero_param.yaml include: zero_param.yaml
properties: properties:
continue-list: break-list:
type: array type: array
required: true required: true
mods: bindings:
type: int type: phandle-array
required: true

View file

@ -11,45 +11,67 @@
#include <logging/log.h> #include <logging/log.h>
#include <zmk/behavior.h> #include <zmk/behavior.h>
#include <zmk/behavior_queue.h>
#include <zmk/endpoints.h> #include <zmk/endpoints.h>
#include <zmk/event_manager.h> #include <zmk/event_manager.h>
#include <zmk/events/position_state_changed.h>
#include <zmk/events/keycode_state_changed.h> #include <zmk/events/keycode_state_changed.h>
#include <zmk/events/modifiers_state_changed.h>
#include <zmk/keys.h>
#include <zmk/hid.h> #include <zmk/hid.h>
#include <zmk/keys.h>
#include <zmk/keymap.h> #include <zmk/keymap.h>
#include <zmk/led_indicators.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
struct caps_word_continue_item { struct caps_word_break_item {
uint16_t page; uint16_t page;
uint32_t id; uint32_t id;
uint8_t implicit_modifiers; uint8_t implicit_modifiers;
}; };
struct behavior_caps_word_config { struct behavior_caps_word_config {
zmk_mod_flags_t mods;
uint8_t index; uint8_t index;
uint8_t continuations_count; struct zmk_behavior_binding capslock_binding;
struct caps_word_continue_item continuations[]; uint8_t break_items_count;
struct caps_word_break_item break_items[];
}; };
struct behavior_caps_word_data { struct behavior_caps_word_data {
uint32_t position;
bool active; bool active;
}; };
static void toggle_capslock(const struct device *dev) {
const struct behavior_caps_word_config *config = dev->config;
const struct behavior_caps_word_data *data = dev->data;
zmk_behavior_queue_add(data->position, config->capslock_binding, true, 0);
zmk_behavior_queue_add(data->position, config->capslock_binding, false, 0);
}
static void set_capslock_state(const struct device *dev, const bool target_state) {
const bool current_state =
zmk_led_indicators_get_current_flags() & ZMK_LED_INDICATORS_CAPSLOCK_BIT;
if (current_state != target_state) {
toggle_capslock(dev);
} else {
LOG_DBG("capslock state was already %d", target_state);
}
}
static void activate_caps_word(const struct device *dev) { static void activate_caps_word(const struct device *dev) {
struct behavior_caps_word_data *data = dev->data; struct behavior_caps_word_data *data = dev->data;
set_capslock_state(dev, true);
data->active = true; data->active = true;
} }
static void deactivate_caps_word(const struct device *dev) { static void deactivate_caps_word(const struct device *dev) {
struct behavior_caps_word_data *data = dev->data; struct behavior_caps_word_data *data = dev->data;
set_capslock_state(dev, false);
data->active = false; data->active = false;
} }
@ -58,6 +80,8 @@ static int on_caps_word_binding_pressed(struct zmk_behavior_binding *binding,
const struct device *dev = device_get_binding(binding->behavior_dev); const struct device *dev = device_get_binding(binding->behavior_dev);
struct behavior_caps_word_data *data = dev->data; struct behavior_caps_word_data *data = dev->data;
data->position = event.position;
if (data->active) { if (data->active) {
deactivate_caps_word(dev); deactivate_caps_word(dev);
} else { } else {
@ -84,19 +108,18 @@ ZMK_SUBSCRIPTION(behavior_caps_word, zmk_keycode_state_changed);
static const struct device *devs[DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT)]; static const struct device *devs[DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT)];
static bool caps_word_is_caps_includelist(const struct behavior_caps_word_config *config, static bool caps_word_is_break_item(const struct behavior_caps_word_config *config,
uint16_t usage_page, uint8_t usage_id, uint16_t usage_page, uint8_t usage_id,
uint8_t implicit_modifiers) { uint8_t implicit_modifiers) {
for (int i = 0; i < config->continuations_count; i++) { for (int i = 0; i < config->break_items_count; i++) {
const struct caps_word_continue_item *continuation = &config->continuations[i]; const struct caps_word_break_item *break_item = &config->break_items[i];
LOG_DBG("Comparing with 0x%02X - 0x%02X (with implicit mods: 0x%02X)", continuation->page, LOG_DBG("Comparing with 0x%02X - 0x%02X (with implicit mods: 0x%02X)", break_item->page,
continuation->id, continuation->implicit_modifiers); break_item->id, break_item->implicit_modifiers);
if (continuation->page == usage_page && continuation->id == usage_id && if (break_item->page == usage_page && break_item->id == usage_id &&
(continuation->implicit_modifiers & (break_item->implicit_modifiers & (implicit_modifiers | zmk_hid_get_explicit_mods())) ==
(implicit_modifiers | zmk_hid_get_explicit_mods())) == break_item->implicit_modifiers) {
continuation->implicit_modifiers) { LOG_DBG("Stopping capsword, found included usage: 0x%02X - 0x%02X", usage_page,
LOG_DBG("Continuing capsword, found included usage: 0x%02X - 0x%02X", usage_page,
usage_id); usage_id);
return true; return true;
} }
@ -105,25 +128,6 @@ static bool caps_word_is_caps_includelist(const struct behavior_caps_word_config
return false; return false;
} }
static bool caps_word_is_alpha(uint8_t usage_id) {
return (usage_id >= HID_USAGE_KEY_KEYBOARD_A && usage_id <= HID_USAGE_KEY_KEYBOARD_Z);
}
static bool caps_word_is_numeric(uint8_t usage_id) {
return (usage_id >= HID_USAGE_KEY_KEYBOARD_1_AND_EXCLAMATION &&
usage_id <= HID_USAGE_KEY_KEYBOARD_0_AND_RIGHT_PARENTHESIS);
}
static void caps_word_enhance_usage(const struct behavior_caps_word_config *config,
struct zmk_keycode_state_changed *ev) {
if (ev->usage_page != HID_USAGE_KEY || !caps_word_is_alpha(ev->keycode)) {
return;
}
LOG_DBG("Enhancing usage 0x%02X with modifiers: 0x%02X", ev->keycode, config->mods);
ev->implicit_modifiers |= config->mods;
}
static int caps_word_keycode_state_changed_listener(const zmk_event_t *eh) { static int caps_word_keycode_state_changed_listener(const zmk_event_t *eh) {
struct zmk_keycode_state_changed *ev = as_zmk_keycode_state_changed(eh); struct zmk_keycode_state_changed *ev = as_zmk_keycode_state_changed(eh);
if (ev == NULL || !ev->state) { if (ev == NULL || !ev->state) {
@ -143,12 +147,7 @@ static int caps_word_keycode_state_changed_listener(const zmk_event_t *eh) {
const struct behavior_caps_word_config *config = dev->config; const struct behavior_caps_word_config *config = dev->config;
caps_word_enhance_usage(config, ev); if (caps_word_is_break_item(config, ev->usage_page, ev->keycode, ev->implicit_modifiers)) {
if (!caps_word_is_alpha(ev->keycode) && !caps_word_is_numeric(ev->keycode) &&
!is_mod(ev->usage_page, ev->keycode) &&
!caps_word_is_caps_includelist(config, ev->usage_page, ev->keycode,
ev->implicit_modifiers)) {
LOG_DBG("Deactivating caps_word for 0x%02X - 0x%02X", ev->usage_page, ev->keycode); LOG_DBG("Deactivating caps_word for 0x%02X - 0x%02X", ev->usage_page, ev->keycode);
deactivate_caps_word(dev); deactivate_caps_word(dev);
} }
@ -163,22 +162,29 @@ static int behavior_caps_word_init(const struct device *dev) {
return 0; return 0;
} }
#define CAPS_WORD_LABEL(i, _n) DT_INST_LABEL(i)
#define PARSE_BREAK(i) \ #define PARSE_BREAK(i) \
{.page = ZMK_HID_USAGE_PAGE(i), \ {.page = ZMK_HID_USAGE_PAGE(i), \
.id = ZMK_HID_USAGE_ID(i), \ .id = ZMK_HID_USAGE_ID(i), \
.implicit_modifiers = SELECT_MODS(i)}, .implicit_modifiers = SELECT_MODS(i)},
#define BREAK_ITEM(i, n) PARSE_BREAK(DT_INST_PROP_BY_IDX(n, continue_list, i)) #define BREAK_ITEM(i, n) PARSE_BREAK(DT_INST_PROP_BY_IDX(n, break_list, i))
#define _TRANSFORM_ENTRY(idx, node) \
{ \
.behavior_dev = DT_LABEL(DT_INST_PHANDLE_BY_IDX(node, bindings, idx)), \
.param1 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(node, bindings, idx, param1), (0), \
(DT_INST_PHA_BY_IDX(node, bindings, idx, param1))), \
.param2 = COND_CODE_0(DT_INST_PHA_HAS_CELL_AT_IDX(node, bindings, idx, param2), (0), \
(DT_INST_PHA_BY_IDX(node, bindings, idx, param2))), \
}
#define KP_INST(n) \ #define KP_INST(n) \
static struct behavior_caps_word_data behavior_caps_word_data_##n = {.active = false}; \ static struct behavior_caps_word_data behavior_caps_word_data_##n = {.active = false}; \
static struct behavior_caps_word_config behavior_caps_word_config_##n = { \ static struct behavior_caps_word_config behavior_caps_word_config_##n = { \
.index = n, \ .index = n, \
.mods = DT_INST_PROP_OR(n, mods, MOD_LSFT), \ .capslock_binding = _TRANSFORM_ENTRY(0, n), \
.continuations = {UTIL_LISTIFY(DT_INST_PROP_LEN(n, continue_list), BREAK_ITEM, n)}, \ .break_items = {UTIL_LISTIFY(DT_INST_PROP_LEN(n, break_list), BREAK_ITEM, n)}, \
.continuations_count = DT_INST_PROP_LEN(n, continue_list), \ .break_items_count = DT_INST_PROP_LEN(n, break_list), \
}; \ }; \
DEVICE_DT_INST_DEFINE(n, behavior_caps_word_init, NULL, &behavior_caps_word_data_##n, \ DEVICE_DT_INST_DEFINE(n, behavior_caps_word_init, NULL, &behavior_caps_word_data_##n, \
&behavior_caps_word_config_##n, APPLICATION, \ &behavior_caps_word_config_##n, APPLICATION, \

View file

@ -5,9 +5,7 @@ sidebar_label: Caps Word
## Summary ## Summary
The caps word behavior behaves similar to a caps lock, but will automatically deactivate when any key not in a continue list is pressed, or if the caps word key is pressed again. For smaller keyboards using [mod-taps](/docs/behaviors/mod-tap), this can help avoid repeated alternating holds when typing words in all caps. The caps word behavior activates caps lock when pressed, but will automatically deactivate it when any key in the break list is pressed, or if the caps word key is pressed again. For smaller keyboards using [mod-taps](/docs/behaviors/mod-tap), this can help avoid repeated alternating holds when typing words in all caps.
The modifiers are applied only to to the alphabetic (`A` to `Z`) keycodes, to avoid automatically appliying them to numeric values, etc.
### Behavior Binding ### Behavior Binding
@ -21,13 +19,13 @@ Example:
### Configuration ### Configuration
#### Continue List #### Break List
By default, the caps word will remain active when any alphanumeric character or underscore (`UNDERSCORE`), backspace (`BACKSPACE`), or delete (`DELETE`) characters are pressed. Any other non-modifier keycode sent will turn off caps word. If you would like to override this, you can set a new array of keys in the `continue-list` property in your keymap: By default, the caps word will remain active until space (`SPACE`), tab (`TAB`), enter (`ENTER`), or escape (`ESCAPE`) is pressed. If you would like to override this, you can set a new array of keys in the `break-list` property in your keymap:
``` ```
&caps_word { &caps_word {
continue-list = <UNDERSCORE MINUS>; break-list = <SPACE MINUS>;
}; };
/ { / {
@ -37,21 +35,6 @@ By default, the caps word will remain active when any alphanumeric character or
}; };
``` ```
#### Applied Modifier(s)
In addition, if you would like _multiple_ modifiers, instead of just `MOD_LSFT`, you can override the `mods` property:
```
&caps_word {
mods = <MOD_LSFT | MOD_LALT>;
};
/ {
keymap {
...
};
};
```
### Multiple Caps Breaks ### Multiple Caps Breaks
@ -63,7 +46,7 @@ If you want to use multiple caps breaks with different codes to break the caps,
compatible = "zmk,behavior-caps-word"; compatible = "zmk,behavior-caps-word";
label = "PROG_CAPS"; label = "PROG_CAPS";
#binding-cells = <0>; #binding-cells = <0>;
continue-list = <UNDERSCORE>; break-list = <SPACE TAB ENTER MINUS>;
}; };
keymap { keymap {