Skip to content

Commit ae63c0f

Browse files
getreuertzarc
andauthored
[Core] Caps Word "Invert on shift" option: pressing Shift inverts the shift state. (qmk#20092)
Co-authored-by: Nick Brassel <[email protected]>
1 parent 368fee9 commit ae63c0f

File tree

7 files changed

+342
-1
lines changed

7 files changed

+342
-1
lines changed

data/mappings/info_config.hjson

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"BOOTMAGIC_LITE_COLUMN_RIGHT": {"info_key": "split.bootmagic.matrix.1", "value_type": "int"},
2929
"BOTH_SHIFTS_TURNS_ON_CAPS_WORD": {"info_key": "caps_word.both_shifts_turns_on", "value_type": "bool"},
3030
"CAPS_WORD_IDLE_TIMEOUT": {"info_key": "caps_word.idle_timeout", "value_type": "int"},
31+
"CAPS_WORD_INVERT_ON_SHIFT": {"info_key": "caps_word.invert_on_shift", "value_type": "bool"},
3132
"COMBO_COUNT": {"info_key": "combo.count", "value_type": "int"},
3233
"COMBO_TERM": {"info_key": "combo.term", "value_type": "int"},
3334
"DEBOUNCE": {"info_key": "debounce", "value_type": "int"},

data/schemas/keyboard.jsonschema

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,8 @@
227227
"enabled": {"type": "boolean"},
228228
"both_shifts_turns_on": {"type": "boolean"},
229229
"double_tap_shift_turns_on": {"type": "boolean"},
230-
"idle_timeout": {"$ref": "qmk.definitions.v1#/unsigned_int"}
230+
"idle_timeout": {"$ref": "qmk.definitions.v1#/unsigned_int"},
231+
"invert_on_shift": {"type": "boolean"}
231232
}
232233
},
233234
"combo": {

docs/feature_caps_word.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,26 @@ by defining `IS_COMMAND()` in config.h:
9090
9191
## Customizing Caps Word :id=customizing-caps-word
9292
93+
### Invert on shift :id=invert-on-shift
94+
95+
By default, Caps Word turns off when Shift keys are pressed, considering them as
96+
word-breaking. Alternatively with the `CAPS_WORD_INVERT_ON_SHIFT` option,
97+
pressing the Shift key continues Caps Word and inverts the shift state. This
98+
is convenient for uncapitalizing one or a few letters within a word, for
99+
example with Caps Word on, typing "D, B, Shift+A, Shift+A, S" produces "DBaaS",
100+
or typing "P, D, F, Shift+S" produces "PDFs".
101+
102+
Enable it by adding in config.h
103+
104+
```c
105+
#define CAPS_WORD_INVERT_ON_SHIFT
106+
```
107+
108+
This option works with regular Shift keys `KC_LSFT` and `KC_RSFT`, mod-tap Shift
109+
keys, and one-shot Shift keys. Note that while Caps Word is on, one-shot Shift
110+
keys behave like regular Shift keys, and have effect only while they are held.
111+
112+
93113
### Idle timeout :id=idle-timeout
94114

95115
Caps Word turns off automatically if no keys are pressed for

quantum/process_keycode/process_caps_word.c

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,66 @@
1414

1515
#include "process_caps_word.h"
1616

17+
#ifdef CAPS_WORD_INVERT_ON_SHIFT
18+
static uint8_t held_mods = 0;
19+
20+
static bool handle_shift(uint16_t keycode, keyrecord_t* record) {
21+
switch (keycode) {
22+
case OSM(MOD_LSFT):
23+
keycode = KC_LSFT;
24+
break;
25+
case OSM(MOD_RSFT):
26+
keycode = KC_RSFT;
27+
break;
28+
29+
# ifndef NO_ACTION_TAPPING
30+
case QK_MOD_TAP ... QK_MOD_TAP_MAX:
31+
if (record->tap.count == 0) { // Mod-tap key is held.
32+
switch (QK_MOD_TAP_GET_MODS(keycode)) {
33+
case MOD_LSFT:
34+
keycode = KC_LSFT;
35+
break;
36+
case MOD_RSFT:
37+
keycode = KC_RSFT;
38+
break;
39+
}
40+
}
41+
# endif // NO_ACTION_TAPPING
42+
}
43+
44+
if (keycode == KC_LSFT || keycode == KC_RSFT) {
45+
const uint8_t mod = MOD_BIT(keycode);
46+
47+
if (is_caps_word_on()) {
48+
if (record->event.pressed) {
49+
held_mods |= mod;
50+
} else {
51+
held_mods &= ~mod;
52+
}
53+
return false;
54+
} else if ((held_mods & mod) != 0) {
55+
held_mods &= ~mod;
56+
del_mods(mod);
57+
return record->event.pressed;
58+
}
59+
}
60+
61+
return true;
62+
}
63+
#endif // CAPS_WORD_INVERT_ON_SHIFT
64+
1765
bool process_caps_word(uint16_t keycode, keyrecord_t* record) {
1866
if (keycode == QK_CAPS_WORD_TOGGLE) {
1967
if (record->event.pressed) {
2068
caps_word_toggle();
2169
}
2270
return false;
2371
}
72+
#ifdef CAPS_WORD_INVERT_ON_SHIFT
73+
if (!handle_shift(keycode, record)) {
74+
return false;
75+
}
76+
#endif // CAPS_WORD_INVERT_ON_SHIFT
2477

2578
#ifndef NO_ACTION_ONESHOT
2679
const uint8_t mods = get_mods() | get_oneshot_mods();
@@ -111,19 +164,24 @@ bool process_caps_word(uint16_t keycode, keyrecord_t* record) {
111164
if (record->tap.count == 0) { // Mod-tap key is held.
112165
const uint8_t mods = QK_MOD_TAP_GET_MODS(keycode);
113166
switch (mods) {
167+
# ifndef CAPS_WORD_INVERT_ON_SHIFT
114168
case MOD_LSFT:
115169
keycode = KC_LSFT;
116170
break;
117171
case MOD_RSFT:
118172
keycode = KC_RSFT;
119173
break;
174+
# endif // CAPS_WORD_INVERT_ON_SHIFT
120175
case MOD_RSFT | MOD_RALT:
121176
keycode = RSFT(KC_RALT);
122177
break;
123178
case MOD_RALT:
124179
return true;
125180
default:
126181
caps_word_off();
182+
# ifdef CAPS_WORD_INVERT_ON_SHIFT
183+
add_mods(held_mods);
184+
# endif // CAPS_WORD_INVERT_ON_SHIFT
127185
return true;
128186
}
129187
} else {
@@ -163,12 +221,20 @@ bool process_caps_word(uint16_t keycode, keyrecord_t* record) {
163221
clear_weak_mods();
164222
#endif // AUTO_SHIFT_ENABLE
165223
if (caps_word_press_user(keycode)) {
224+
#ifdef CAPS_WORD_INVERT_ON_SHIFT
225+
if (held_mods) {
226+
set_weak_mods(get_weak_mods() ^ MOD_BIT(KC_LSFT));
227+
}
228+
#endif // CAPS_WORD_INVERT_ON_SHIFT
166229
send_keyboard_report();
167230
return true;
168231
}
169232
}
170233

171234
caps_word_off();
235+
#ifdef CAPS_WORD_INVERT_ON_SHIFT
236+
add_mods(held_mods);
237+
#endif // CAPS_WORD_INVERT_ON_SHIFT
172238
return true;
173239
}
174240

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2023 Google LLC
2+
//
3+
// This program is free software: you can redistribute it and/or modify
4+
// it under the terms of the GNU General Public License as published by
5+
// the Free Software Foundation, either version 2 of the License, or
6+
// (at your option) any later version.
7+
//
8+
// This program is distributed in the hope that it will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
// GNU General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU General Public License
14+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
16+
#pragma once
17+
18+
#include "test_common.h"
19+
20+
#define CAPS_WORD_INVERT_ON_SHIFT
21+
#define PERMISSIVE_HOLD
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright 2023 Google LLC
2+
#
3+
# This program is free software: you can redistribute it and/or modify
4+
# it under the terms of the GNU General Public License as published by
5+
# the Free Software Foundation, either version 2 of the License, or
6+
# (at your option) any later version.
7+
#
8+
# This program is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
# GNU General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU General Public License
14+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
16+
CAPS_WORD_ENABLE = yes
17+

0 commit comments

Comments
 (0)