feat(behaviors): caps-word supports alternative layouts
The problem when using alternative layouts (Colemak, Dvorak, etc) mapped in the OS, or when using a foreign keyboard, there will be alpha characters that the keyboard "thinks" are symbols, and therefore caps-word will not shift them. This adds the ability to configure caps-word to shift these keycodes so that it works as expected.
This commit is contained in:
parent
1e25ee77d2
commit
79aad1af77
14 changed files with 297 additions and 26 deletions
|
@ -13,6 +13,8 @@
|
|||
label = "CAPS_WORD";
|
||||
#binding-cells = <0>;
|
||||
continue-list = <UNDERSCORE BACKSPACE DELETE>;
|
||||
also-mod-list = <>;
|
||||
break-list = <>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -13,3 +13,11 @@ properties:
|
|||
required: true
|
||||
mods:
|
||||
type: int
|
||||
also-mod-list:
|
||||
type: array
|
||||
required: false
|
||||
default: []
|
||||
break-list:
|
||||
type: array
|
||||
required: false
|
||||
default: []
|
||||
|
|
|
@ -30,11 +30,17 @@ struct caps_word_continue_item {
|
|||
uint8_t implicit_modifiers;
|
||||
};
|
||||
|
||||
struct caps_word_item_array {
|
||||
uint8_t length;
|
||||
struct caps_word_continue_item members[];
|
||||
};
|
||||
|
||||
struct behavior_caps_word_config {
|
||||
zmk_mod_flags_t mods;
|
||||
uint8_t index;
|
||||
uint8_t continuations_count;
|
||||
struct caps_word_continue_item continuations[];
|
||||
struct caps_word_item_array *continuations;
|
||||
struct caps_word_item_array *also_mod_list;
|
||||
struct caps_word_item_array *break_list;
|
||||
};
|
||||
|
||||
struct behavior_caps_word_data {
|
||||
|
@ -84,22 +90,55 @@ ZMK_SUBSCRIPTION(behavior_caps_word, zmk_keycode_state_changed);
|
|||
|
||||
static const struct device *devs[DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT)];
|
||||
|
||||
static bool caps_word_is_in_list(const struct caps_word_item_array *list, uint16_t usage_page,
|
||||
uint8_t usage_id, uint8_t implicit_modifiers) {
|
||||
for (int i = 0; i < list->length; i++) {
|
||||
const struct caps_word_continue_item *member = &list->members[i];
|
||||
LOG_DBG("Comparing with 0x%02X - 0x%02X (with implicit mods: 0x%02X)", member->page,
|
||||
member->id, member->implicit_modifiers);
|
||||
|
||||
if (member->page == usage_page && member->id == usage_id &&
|
||||
(member->implicit_modifiers & (implicit_modifiers | zmk_hid_get_explicit_mods())) ==
|
||||
member->implicit_modifiers) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
LOG_DBG("Checking if 0x%02X - 0x%02X is in list", usage_page, usage_id);
|
||||
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
if (caps_word_is_in_list(config->continuations, usage_page, usage_id, implicit_modifiers)) {
|
||||
LOG_DBG("Continuing capsword, found included usage: 0x%02X - 0x%02X", usage_page, usage_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool caps_word_is_in_also_mod_list(const struct behavior_caps_word_config *config,
|
||||
uint16_t usage_page, uint8_t usage_id,
|
||||
uint8_t implicit_modifiers) {
|
||||
LOG_DBG("Checking if usage 0x%02X - 0x%02X is in list", usage_page, usage_id);
|
||||
|
||||
if (caps_word_is_in_list(config->also_mod_list, usage_page, usage_id, implicit_modifiers)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool caps_word_is_in_break_list(const struct behavior_caps_word_config *config,
|
||||
uint16_t usage_page, uint8_t usage_id,
|
||||
uint8_t implicit_modifiers) {
|
||||
LOG_DBG("Checking if usage 0x%02X - 0x%02X is in list", usage_page, usage_id);
|
||||
|
||||
if (caps_word_is_in_list(config->break_list, usage_page, usage_id, implicit_modifiers)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -116,7 +155,11 @@ static bool caps_word_is_numeric(uint8_t usage_id) {
|
|||
|
||||
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)) {
|
||||
if (ev->usage_page != HID_USAGE_KEY ||
|
||||
!(caps_word_is_alpha(ev->keycode) ||
|
||||
caps_word_is_in_also_mod_list(config, ev->usage_page, ev->keycode,
|
||||
ev->implicit_modifiers)) ||
|
||||
caps_word_is_in_break_list(config, ev->usage_page, ev->keycode, ev->implicit_modifiers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -145,10 +188,12 @@ static int caps_word_keycode_state_changed_listener(const zmk_event_t *eh) {
|
|||
|
||||
caps_word_enhance_usage(config, ev);
|
||||
|
||||
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_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)) ||
|
||||
caps_word_is_in_break_list(config, ev->usage_page, ev->keycode,
|
||||
ev->implicit_modifiers)) {
|
||||
LOG_DBG("Deactivating caps_word for 0x%02X - 0x%02X", ev->usage_page, ev->keycode);
|
||||
deactivate_caps_word(dev);
|
||||
}
|
||||
|
@ -172,13 +217,29 @@ static int behavior_caps_word_init(const struct device *dev) {
|
|||
|
||||
#define BREAK_ITEM(i, n) PARSE_BREAK(DT_INST_PROP_BY_IDX(n, continue_list, i))
|
||||
|
||||
#define BREAK_ITEM_2(i, n) PARSE_BREAK(DT_INST_PROP_BY_IDX(n, also_mod_list, i))
|
||||
|
||||
#define BREAK_ITEM_3(i, n) PARSE_BREAK(DT_INST_PROP_BY_IDX(n, break_list, i))
|
||||
|
||||
#define KP_INST(n) \
|
||||
static struct behavior_caps_word_data behavior_caps_word_data_##n = {.active = false}; \
|
||||
static struct caps_word_item_array continuations_##n = { \
|
||||
.members = {UTIL_LISTIFY(DT_INST_PROP_LEN(n, continue_list), BREAK_ITEM, n)}, \
|
||||
.length = DT_INST_PROP_LEN(n, continue_list)}; \
|
||||
static struct caps_word_item_array also_mod_list_##n = { \
|
||||
.members = {UTIL_LISTIFY(DT_INST_PROP_LEN(n, also_mod_list), BREAK_ITEM_2, n)}, \
|
||||
.length = DT_INST_PROP_LEN(n, also_mod_list), \
|
||||
}; \
|
||||
static struct caps_word_item_array break_list_##n = { \
|
||||
.members = {UTIL_LISTIFY(DT_INST_PROP_LEN(n, break_list), BREAK_ITEM_3, n)}, \
|
||||
.length = DT_INST_PROP_LEN(n, break_list), \
|
||||
}; \
|
||||
static struct behavior_caps_word_config behavior_caps_word_config_##n = { \
|
||||
.index = n, \
|
||||
.mods = DT_INST_PROP_OR(n, mods, MOD_LSFT), \
|
||||
.continuations = {UTIL_LISTIFY(DT_INST_PROP_LEN(n, continue_list), BREAK_ITEM, n)}, \
|
||||
.continuations_count = DT_INST_PROP_LEN(n, continue_list), \
|
||||
.continuations = &continuations_##n, \
|
||||
.also_mod_list = &also_mod_list_##n, \
|
||||
.break_list = &break_list_##n, \
|
||||
}; \
|
||||
DEVICE_DT_INST_DEFINE(n, behavior_caps_word_init, NULL, &behavior_caps_word_data_##n, \
|
||||
&behavior_caps_word_config_##n, APPLICATION, \
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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_is_caps_includelist/caps_includelist/p
|
||||
s/.*caps_word_is_in_list/is_in_list/p
|
||||
|
|
|
@ -5,7 +5,8 @@ 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: Checking if 0x07 - 0x2D is in list
|
||||
is_in_list: 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
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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_is_caps_includelist/caps_includelist/p
|
||||
s/.*caps_word_is_in_list/is_in_list/p
|
||||
|
|
|
@ -3,8 +3,9 @@ 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: Checking if 0x07 - 0x2D is in list
|
||||
is_in_list: Comparing with 0x07 - 0x2D (with implicit mods: 0x02)
|
||||
is_in_list: 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
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
s/.*hid_listener_keycode_//p
|
||||
s/.*hid_implicit_modifiers_//p
|
||||
s/.*caps_word_enhance_usage/enhance_usage/p
|
|
@ -0,0 +1,13 @@
|
|||
enhance_usage: 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 0x05 implicit_mods 0x00 explicit_mods 0x00
|
||||
press: Modifiers set to 0x00
|
||||
released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
|
||||
release: Modifiers set to 0x00
|
||||
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
|
||||
press: Modifiers set to 0x00
|
||||
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
|
||||
release: Modifiers set to 0x00
|
|
@ -0,0 +1,35 @@
|
|||
#include <dt-bindings/zmk/keys.h>
|
||||
#include <behaviors.dtsi>
|
||||
#include <dt-bindings/zmk/kscan_mock.h>
|
||||
|
||||
&caps_word {
|
||||
break-list = <B>;
|
||||
};
|
||||
|
||||
/ {
|
||||
keymap {
|
||||
compatible = "zmk,keymap";
|
||||
label = "Default keymap";
|
||||
|
||||
default_layer {
|
||||
bindings = <
|
||||
&caps_word &kp A
|
||||
&kp B
|
||||
>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
&kscan {
|
||||
events = <
|
||||
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(1,0,10)
|
||||
ZMK_MOCK_RELEASE(1,0,10)
|
||||
ZMK_MOCK_PRESS(0,1,10)
|
||||
ZMK_MOCK_RELEASE(0,1,10)
|
||||
>;
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
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_is_in_also_mod_list/also_modlist/p
|
|
@ -0,0 +1,18 @@
|
|||
enhance_usage: 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
|
||||
also_modlist: Checking if usage 0x07 - 0x33 is in list
|
||||
enhance_usage: Enhancing usage 0x33 with modifiers: 0x02
|
||||
caps_includelist: Checking if 0x07 - 0x33 is in list
|
||||
caps_includelist: Continuing capsword, found included usage: 0x07 - 0x33
|
||||
pressed: usage_page 0x07 keycode 0x33 implicit_mods 0x02 explicit_mods 0x00
|
||||
press: Modifiers set to 0x02
|
||||
released: usage_page 0x07 keycode 0x33 implicit_mods 0x00 explicit_mods 0x00
|
||||
release: Modifiers set to 0x00
|
||||
enhance_usage: 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
|
|
@ -0,0 +1,36 @@
|
|||
#include <dt-bindings/zmk/keys.h>
|
||||
#include <behaviors.dtsi>
|
||||
#include <dt-bindings/zmk/kscan_mock.h>
|
||||
#include "../behavior_keymap.dtsi"
|
||||
|
||||
&caps_word {
|
||||
continue-list = <UNDERSCORE BACKSPACE DELETE SEMI>;
|
||||
also-mod-list = <SEMI>;
|
||||
};
|
||||
|
||||
/ {
|
||||
keymap {
|
||||
compatible = "zmk,keymap";
|
||||
label = "Default keymap";
|
||||
|
||||
default_layer {
|
||||
bindings = <
|
||||
&caps_word &kp A
|
||||
&kp SEMI &kp MINUS
|
||||
>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
&kscan {
|
||||
events = <
|
||||
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(1,0,10)
|
||||
ZMK_MOCK_RELEASE(1,0,10)
|
||||
ZMK_MOCK_PRESS(0,1,10)
|
||||
ZMK_MOCK_RELEASE(0,1,10)
|
||||
>;
|
||||
};
|
|
@ -3,6 +3,9 @@ title: Caps Word Behavior
|
|||
sidebar_label: Caps Word
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
## 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.
|
||||
|
@ -53,6 +56,89 @@ In addition, if you would like _multiple_ modifiers, instead of just `MOD_LSFT`,
|
|||
};
|
||||
```
|
||||
|
||||
#### OS-Mapped Layouts or Non-English Characters
|
||||
|
||||
By default, caps word will only capitalize alpha characters a-z. If you use an alternative layout (Colemak, Dvorak etc) _and_ are performing the mapping in the OS rather than in the firmware, then there are some additional configuration options needed.
|
||||
|
||||
As an example, take Colemak mapped in the OS, when you type "o", the keyboard thinks you are pressing `SEMICOLON`. For caps word to produce a capital "O" we need to configure caps word to both remain active _and apply the shift modifier_ when it sees `SEMICOLON`.
|
||||
|
||||
Likewise when you type ";" the keyboard thinks you are pressing `P`. If caps word is active and you type ";" we need to tell caps word to deactivate even though it sees the alpha keycode `P`.
|
||||
|
||||
To do this, there are two optional properties: `also-mod-list` is a list of (non-alpha) keycodes that caps word should also apply the shift modifier to. `break-list` handles the second case, and is a list of (alpha) keycodes that should deactivate caps word.
|
||||
|
||||
##### Example Configurations
|
||||
|
||||
<Tabs
|
||||
defaultValue="os_colemak"
|
||||
values={[
|
||||
{label: 'Colemak', value: 'os_colemak'},
|
||||
{label: 'Dvorak', value: 'os_dvorak'},
|
||||
{label: 'Workman', value: 'os_workman'},
|
||||
]}>
|
||||
|
||||
<TabItem value="os_colemak">
|
||||
|
||||
This will allow caps word to work as expected when using Colemak mapped in the OS. If you are mapping the Colemak layout in your keyboard firmware, there is no need to do this.
|
||||
|
||||
```dtsi title="For OS-mapped Colemak"
|
||||
&caps_word {
|
||||
continue-list = <UNDERSCORE BACKSPACE DELETE SEMICOLON>; // <--- prevent SEMICOLON ("o") from deactivating
|
||||
also-mod-list = <SEMICOLON>; // <--- capitalize SEMICOLON ("o")
|
||||
break-list = <P>; // <--- deactivate on P (";")
|
||||
};
|
||||
|
||||
/ {
|
||||
keymap {
|
||||
...
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="os_dvorak">
|
||||
|
||||
This will allow caps word to work as expected when using Dvorak mapped in the OS. If you are mapping the Dvorak layout in your keyboard firmware, there is no need to do this.
|
||||
|
||||
```dtsi title="For OS-mapped Dvorak"
|
||||
&caps_word {
|
||||
// Prevent from deactivating: "_" "s" "w" "v" "z"
|
||||
continue-list = <BACKSPACE DELETE DOUBLE_QUOTES SEMI COMMA DOT SLASH>;
|
||||
also-mod-list = <SEMI COMMA DOT SLASH>; // <--- capitalize "s" "w" "v" "z"
|
||||
break-list = <Q W E Z>; // <--- deactivate on "'" "," "." ";"
|
||||
};
|
||||
|
||||
/ {
|
||||
keymap {
|
||||
...
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="os_workman">
|
||||
|
||||
This will allow caps word to work as expected when using Workman mapped in the OS. If you are mapping the Workman layout in your keyboard firmware, there is no need to do this.
|
||||
|
||||
```dtsi title="For OS-mapped Workman"
|
||||
&caps_word {
|
||||
continue-list = <UNDERSCORE BACKSPACE DELETE SEMICOLON>; // <--- prevent SEMICOLON ("i") from deactivating
|
||||
also-mod-list = <SEMICOLON>; // <--- capitalize SEMICOLON ("i")
|
||||
break-list = <P>; // <--- deactivate on P (";")
|
||||
};
|
||||
|
||||
/ {
|
||||
keymap {
|
||||
...
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
### Multiple Caps Breaks
|
||||
|
||||
If you want to use multiple caps breaks with different codes to break the caps, you can add additional caps words instances to use in your keymap:
|
||||
|
|
Loading…
Add table
Reference in a new issue