Skip to content
Open
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
2 changes: 1 addition & 1 deletion app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_KEY_TOGGLE app PRIVATE src/behaviors/behavior_key_toggle.c)
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_HOLD_TAP app PRIVATE src/behaviors/behavior_hold_tap.c)
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_STICKY_KEY app PRIVATE src/behaviors/behavior_sticky_key.c)
target_sources(app PRIVATE src/behaviors/behavior_caps_word.c)
target_sources_ifdef(CONFIG_ZMK_BEHAVIOR_CAPS_WORD app PRIVATE src/behaviors/behavior_caps_word.c)
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)
Expand Down
4 changes: 4 additions & 0 deletions app/Kconfig.behaviors
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ endchoice

endif

config ZMK_BEHAVIOR_CAPS_WORD
bool
default y
depends on DT_HAS_ZMK_BEHAVIOR_CAPS_WORD_ENABLED

config ZMK_BEHAVIOR_HOLD_TAP
bool
Expand Down
7 changes: 7 additions & 0 deletions app/dts/behaviors/caps_word.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@
continue-list = <UNDERSCORE BACKSPACE DELETE>;
display-name = "Caps Word";
};

/omit-if-no-ref/ prog_word: prog_word {
compatible = "zmk,behavior-caps-word";
#binding-cells = <0>;
continue-list = <UNDERSCORE BACKSPACE DELETE>;
shift-list = <MINUS>;
};
};
};

13 changes: 13 additions & 0 deletions app/dts/bindings/behaviors/zmk,behavior-caps-word.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,18 @@ properties:
continue-list:
type: array
required: true
description: |
List of key codes which should continue the caps word behavior but not have
modifiers applied when caps word is active.
Alphanumeric keys are included automatically unless no-default-keys is set.
shift-list:
type: array
description: |
List of key codes which should have modifiers applied when caps word is active.
Alpha keys are included automatically unless no-default-keys is set.
mods:
type: int
description: Bitmask of modifiers to apply. Default is MOD_LSFT.
no-default-keys:
type: boolean
description: Do not automatically include any keys in continue-list or shift-list.
29 changes: 14 additions & 15 deletions app/include/zmk/events/keycode_state_changed.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,34 @@
#include <zmk/keys.h>

struct zmk_keycode_state_changed {
int64_t timestamp;
zmk_key_t keycode;
uint16_t usage_page;
uint32_t keycode;
uint8_t implicit_modifiers;
uint8_t explicit_modifiers;
zmk_mod_flags_t implicit_modifiers;
zmk_mod_flags_t explicit_modifiers;
bool state;
int64_t timestamp;
};

ZMK_EVENT_DECLARE(zmk_keycode_state_changed);

static inline struct zmk_keycode_state_changed
zmk_keycode_state_changed_from_encoded(uint32_t encoded, bool pressed, int64_t timestamp) {
uint16_t page = ZMK_HID_USAGE_PAGE(encoded);
uint16_t id = ZMK_HID_USAGE_ID(encoded);
uint8_t implicit_modifiers = 0x00;
uint8_t explicit_modifiers = 0x00;
struct zmk_key_param key = ZMK_KEY_PARAM_DECODE(encoded);
zmk_mod_flags_t implicit_modifiers = 0x00;
zmk_mod_flags_t explicit_modifiers = 0x00;

if (!page) {
page = HID_USAGE_KEY;
if (!key.page) {
key.page = HID_USAGE_KEY;
}

if (is_mod(page, id)) {
explicit_modifiers = SELECT_MODS(encoded);
if (is_mod(key.page, key.id)) {
explicit_modifiers = key.modifiers;
} else {
implicit_modifiers = SELECT_MODS(encoded);
implicit_modifiers = key.modifiers;
}

return (struct zmk_keycode_state_changed){.usage_page = page,
.keycode = id,
return (struct zmk_keycode_state_changed){.usage_page = key.page,
.keycode = key.id,
.implicit_modifiers = implicit_modifiers,
.explicit_modifiers = explicit_modifiers,
.state = pressed,
Expand Down
18 changes: 18 additions & 0 deletions app/include/zmk/keys.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,24 @@ struct zmk_key_event {
bool pressed;
};

/**
* Key data from a devicetree key code parameter.
*/
struct zmk_key_param {
zmk_mod_flags_t modifiers;
uint8_t page;
uint16_t id;
};

/**
* Decode a uint32_t devicetree key code parameter to a struct zmk_key_param.
*/
#define ZMK_KEY_PARAM_DECODE(param) \
(struct zmk_key_param) { \
.modifiers = SELECT_MODS(param), .page = ZMK_HID_USAGE_PAGE(param), \
.id = ZMK_HID_USAGE_ID(param), \
}

static inline bool is_mod(uint8_t usage_page, uint32_t keycode) {
return (keycode >= HID_USAGE_KEY_KEYBOARD_LEFTCONTROL &&
keycode <= HID_USAGE_KEY_KEYBOARD_RIGHT_GUI && usage_page == HID_USAGE_KEY);
Expand Down
122 changes: 74 additions & 48 deletions app/src/behaviors/behavior_caps_word.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,16 @@

LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);

#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)

struct caps_word_continue_item {
uint16_t page;
uint32_t id;
uint8_t implicit_modifiers;
struct key_list {
size_t size;
struct zmk_key_param keys[];
};

struct behavior_caps_word_config {
const struct key_list *continue_keys;
const struct key_list *shift_keys;
zmk_mod_flags_t mods;
uint8_t continuations_count;
struct caps_word_continue_item continuations[];
bool no_default_keys;
};

struct behavior_caps_word_data {
Expand Down Expand Up @@ -87,44 +85,67 @@ ZMK_SUBSCRIPTION(behavior_caps_word, zmk_keycode_state_changed);
#define GET_DEV(inst) DEVICE_DT_INST_GET(inst),
static const struct device *devs[] = {DT_INST_FOREACH_STATUS_OKAY(GET_DEV)};

static bool caps_word_is_caps_includelist(const struct behavior_caps_word_config *config,
uint16_t usage_page, uint8_t usage_id,
uint8_t implicit_modifiers) {
for (int i = 0; i < config->continuations_count; i++) {
const struct caps_word_continue_item *continuation = &config->continuations[i];
LOG_DBG("Comparing with 0x%02X - 0x%02X (with implicit mods: 0x%02X)", continuation->page,
continuation->id, continuation->implicit_modifiers);

if (continuation->page == usage_page && continuation->id == usage_id &&
(continuation->implicit_modifiers &
(implicit_modifiers | zmk_hid_get_explicit_mods())) ==
continuation->implicit_modifiers) {
LOG_DBG("Continuing capsword, found included usage: 0x%02X - 0x%02X", usage_page,
usage_id);
static bool key_list_contains(const struct key_list *list, uint16_t usage_page, zmk_key_t usage_id,
zmk_mod_flags_t modifiers) {
for (int i = 0; i < list->size; i++) {
const struct zmk_key_param *key = &list->keys[i];

if (key->page == usage_page && key->id == usage_id &&
(key->modifiers & modifiers) == key->modifiers) {
return true;
}
}

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_alpha(uint16_t usage_page, zmk_key_t usage_id) {
if (usage_page != HID_USAGE_KEY) {
return false;
}

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 bool caps_word_is_numeric(uint16_t usage_page, zmk_key_t usage_id) {
if (usage_page != HID_USAGE_KEY) {
return false;
}

return ((usage_id >= HID_USAGE_KEY_KEYBOARD_1_AND_EXCLAMATION &&
usage_id <= HID_USAGE_KEY_KEYBOARD_0_AND_RIGHT_PARENTHESIS) ||
(usage_id >= HID_USAGE_KEY_KEYPAD_1_AND_END &&
usage_id <= HID_USAGE_KEY_KEYPAD_0_AND_INSERT)) ||
usage_id == HID_USAGE_KEY_KEYPAD_00 || usage_id == HID_USAGE_KEY_KEYPAD_000;
}

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;
static bool caps_word_should_enhance(const struct behavior_caps_word_config *config,
struct zmk_keycode_state_changed *ev) {
// Unless no-default-keys is set, alpha keys are enhanced.
if (!config->no_default_keys && caps_word_is_alpha(ev->usage_page, ev->keycode)) {
return true;
}

LOG_DBG("Enhancing usage 0x%02X with modifiers: 0x%02X", ev->keycode, config->mods);
ev->implicit_modifiers |= config->mods;
zmk_mod_flags_t modifiers = ev->implicit_modifiers | zmk_hid_get_explicit_mods();

return key_list_contains(config->shift_keys, ev->usage_page, ev->keycode, modifiers);
}

static bool caps_word_should_continue(const struct behavior_caps_word_config *config,
struct zmk_keycode_state_changed *ev) {
// Modifiers do not break a word, nor does any key that is enhanced.
if (is_mod(ev->usage_page, ev->keycode) || caps_word_should_enhance(config, ev)) {
return true;
}

// Unless no-default-keys is set, number keys do not break a word.
if (!config->no_default_keys && caps_word_is_numeric(ev->usage_page, ev->keycode)) {
return true;
}

zmk_mod_flags_t modifiers = ev->implicit_modifiers | zmk_hid_get_explicit_mods();

return key_list_contains(config->continue_keys, ev->usage_page, ev->keycode, modifiers);
}

static int caps_word_keycode_state_changed_listener(const zmk_event_t *eh) {
Expand All @@ -136,19 +157,19 @@ static int caps_word_keycode_state_changed_listener(const zmk_event_t *eh) {
for (int i = 0; i < ARRAY_SIZE(devs); i++) {
const struct device *dev = devs[i];

struct behavior_caps_word_data *data = dev->data;
const struct behavior_caps_word_data *data = dev->data;
if (!data->active) {
continue;
}

const struct behavior_caps_word_config *config = dev->config;

caps_word_enhance_usage(config, ev);
if (caps_word_should_enhance(config, ev)) {
LOG_DBG("Enhancing usage 0x%02X with modifiers: 0x%02X", ev->keycode, config->mods);
ev->implicit_modifiers |= config->mods;
}

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)) {
if (!caps_word_should_continue(config, ev)) {
LOG_DBG("Deactivating caps_word for 0x%02X - 0x%02X", ev->usage_page, ev->keycode);
deactivate_caps_word(dev);
}
Expand All @@ -157,24 +178,29 @@ static int caps_word_keycode_state_changed_listener(const zmk_event_t *eh) {
return ZMK_EV_EVENT_BUBBLE;
}

#define CAPS_WORD_LABEL(i, _n) DT_INST_LABEL(i)
#define KEY_LIST_ITEM(i, n, prop) ZMK_KEY_PARAM_DECODE(DT_INST_PROP_BY_IDX(n, prop, i))

#define PARSE_BREAK(i) \
{.page = ZMK_HID_USAGE_PAGE(i), .id = ZMK_HID_USAGE_ID(i), .implicit_modifiers = SELECT_MODS(i)}

#define BREAK_ITEM(i, n) PARSE_BREAK(DT_INST_PROP_BY_IDX(n, continue_list, i))
#define PROP_KEY_LIST(n, prop) \
COND_CODE_1(DT_NODE_HAS_PROP(DT_DRV_INST(n), prop), \
({ \
.size = DT_INST_PROP_LEN(n, prop), \
.keys = {LISTIFY(DT_INST_PROP_LEN(n, prop), KEY_LIST_ITEM, (, ), n, prop)}, \
}), \
({.size = 0}))

#define KP_INST(n) \
static const struct key_list caps_word_continue_list_##n = PROP_KEY_LIST(n, continue_list); \
static const struct key_list caps_word_shift_list_##n = PROP_KEY_LIST(n, shift_list); \
\
static struct behavior_caps_word_data behavior_caps_word_data_##n = {.active = false}; \
static const struct behavior_caps_word_config behavior_caps_word_config_##n = { \
.mods = DT_INST_PROP_OR(n, mods, MOD_LSFT), \
.continuations = {LISTIFY(DT_INST_PROP_LEN(n, continue_list), BREAK_ITEM, (, ), n)}, \
.continuations_count = DT_INST_PROP_LEN(n, continue_list), \
.continue_keys = &caps_word_continue_list_##n, \
.shift_keys = &caps_word_shift_list_##n, \
.no_default_keys = DT_INST_PROP(n, no_default_keys), \
}; \
BEHAVIOR_DT_INST_DEFINE(n, NULL, NULL, &behavior_caps_word_data_##n, \
&behavior_caps_word_config_##n, POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &behavior_caps_word_driver_api);

DT_INST_FOREACH_STATUS_OKAY(KP_INST)

#endif
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
s/.*hid_listener_keycode_//p
s/.*hid_implicit_modifiers_//p
s/.*caps_word_enhance_usage/enhance_usage/p
s/.*caps_word_is_caps_includelist/caps_includelist/p
s/.*caps_word_keycode_state_changed_listener/state_changed/p
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
enhance_usage: Enhancing usage 0x04 with modifiers: 0x02
state_changed: Enhancing usage 0x04 with modifiers: 0x02
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x02 explicit_mods 0x00
press: Modifiers set to 0x02
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
release: Modifiers set to 0x00
pressed: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
press: Modifiers set to 0x02
caps_includelist: Comparing with 0x07 - 0x2D (with implicit mods: 0x02)
caps_includelist: Continuing capsword, found included usage: 0x07 - 0x2D
pressed: usage_page 0x07 keycode 0x2D implicit_mods 0x00 explicit_mods 0x00
press: Modifiers set to 0x02
released: usage_page 0x07 keycode 0x2D implicit_mods 0x00 explicit_mods 0x00
release: Modifiers set to 0x02
released: usage_page 0x07 keycode 0xE1 implicit_mods 0x00 explicit_mods 0x00
release: Modifiers set to 0x00
enhance_usage: Enhancing usage 0x04 with modifiers: 0x02
state_changed: Enhancing usage 0x04 with modifiers: 0x02
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x02 explicit_mods 0x00
press: Modifiers set to 0x02
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
s/.*hid_listener_keycode_//p
s/.*hid_implicit_modifiers_//p
s/.*caps_word_enhance_usage/enhance_usage/p
s/.*caps_word_is_caps_includelist/caps_includelist/p
s/.*caps_word_keycode_state_changed_listener/state_changed/p
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
enhance_usage: Enhancing usage 0x04 with modifiers: 0x02
state_changed: Enhancing usage 0x04 with modifiers: 0x02
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x02 explicit_mods 0x00
press: Modifiers set to 0x02
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
release: Modifiers set to 0x00
caps_includelist: Comparing with 0x07 - 0x2D (with implicit mods: 0x02)
caps_includelist: Comparing with 0x07 - 0x2D (with implicit mods: 0x00)
caps_includelist: Continuing capsword, found included usage: 0x07 - 0x2D
pressed: usage_page 0x07 keycode 0x2D implicit_mods 0x00 explicit_mods 0x00
press: Modifiers set to 0x00
released: usage_page 0x07 keycode 0x2D implicit_mods 0x00 explicit_mods 0x00
release: Modifiers set to 0x00
enhance_usage: Enhancing usage 0x04 with modifiers: 0x02
state_changed: Enhancing usage 0x04 with modifiers: 0x02
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x02 explicit_mods 0x00
press: Modifiers set to 0x02
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
s/.*hid_listener_keycode_//p
s/.*hid_implicit_modifiers_//p
s/.*caps_word_enhance_usage/enhance_usage/p
s/.*caps_word_is_caps_includelist/caps_includelist/p
s/.*caps_word_keycode_state_changed_listener/state_changed/p
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
enhance_usage: Enhancing usage 0x04 with modifiers: 0x02
state_changed: Enhancing usage 0x04 with modifiers: 0x02
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x02 explicit_mods 0x00
press: Modifiers set to 0x02
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
Expand All @@ -7,7 +7,7 @@ pressed: usage_page 0x07 keycode 0x23 implicit_mods 0x00 explicit_mods 0x00
press: Modifiers set to 0x00
released: usage_page 0x07 keycode 0x23 implicit_mods 0x00 explicit_mods 0x00
release: Modifiers set to 0x00
enhance_usage: Enhancing usage 0x04 with modifiers: 0x02
state_changed: Enhancing usage 0x04 with modifiers: 0x02
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x02 explicit_mods 0x00
press: Modifiers set to 0x02
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
s/.*hid_listener_keycode_//p
s/.*hid_implicit_modifiers_//p
s/.*caps_word_keycode_state_changed_listener/state_changed/p
Loading