Skip to content

Commit 283241e

Browse files
committed
Add gaming layer w/ SOCD
1 parent ba40521 commit 283241e

File tree

4 files changed

+280
-2
lines changed

4 files changed

+280
-2
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/**
16+
* @file socd_cleaner.c
17+
* @brief SOCD Cleaner implementation
18+
*
19+
* For full documentation, see
20+
* <https://getreuer.info/posts/keyboards/socd-cleaner>
21+
*/
22+
23+
#include "socd_cleaner.h"
24+
25+
#ifdef __cplusplus
26+
extern "C" {
27+
#endif
28+
29+
bool socd_cleaner_enabled = true;
30+
31+
static void update_key(uint8_t keycode, bool press) {
32+
if (press) {
33+
add_key(keycode);
34+
} else {
35+
del_key(keycode);
36+
}
37+
}
38+
39+
bool process_socd_cleaner(uint16_t keycode, keyrecord_t* record,
40+
socd_cleaner_t* state) {
41+
if (!socd_cleaner_enabled || !state->resolution ||
42+
(keycode != state->keys[0] && keycode != state->keys[1])) {
43+
return true; // Quick return when disabled or on unrelated events.
44+
}
45+
// The current event corresponds to index `i`, 0 or 1, in the SOCD key pair.
46+
const uint8_t i = (keycode == state->keys[1]);
47+
const uint8_t opposing = i ^ 1; // Index of the opposing key.
48+
49+
// Track which keys are physically held (vs. keys in the report).
50+
state->held[i] = record->event.pressed;
51+
52+
// Perform SOCD resolution for events where the opposing key is held.
53+
if (state->held[opposing]) {
54+
switch (state->resolution) {
55+
case SOCD_CLEANER_LAST: // Last input priority with reactivation.
56+
// If the current event is a press, then release the opposing key.
57+
// Otherwise if this is a release, then press the opposing key.
58+
update_key(state->keys[opposing], !state->held[i]);
59+
break;
60+
61+
case SOCD_CLEANER_NEUTRAL: // Neutral resolution.
62+
// Same logic as SOCD_CLEANER_LAST, but skip default handling so that
63+
// the current key has no effect while the opposing key is held.
64+
update_key(state->keys[opposing], !state->held[i]);
65+
// Send updated report (normally, default handling would do this).
66+
send_keyboard_report();
67+
return false; // Skip default handling.
68+
69+
case SOCD_CLEANER_0_WINS: // Key 0 wins.
70+
case SOCD_CLEANER_1_WINS: // Key 1 wins.
71+
if (opposing == (state->resolution - SOCD_CLEANER_0_WINS)) {
72+
// The opposing key is the winner. The current key has no effect.
73+
return false; // Skip default handling.
74+
} else {
75+
// The current key is the winner. Update logic is same as above.
76+
update_key(state->keys[opposing], !state->held[i]);
77+
}
78+
break;
79+
}
80+
}
81+
return true; // Continue default handling to press/release current key.
82+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/**
16+
* @file socd_cleaner.h
17+
* @brief SOCD Cleaner - enhance WASD for fast inputs for gaming
18+
*
19+
* Overview
20+
* --------
21+
*
22+
* SOCD Cleaner is a QMK library for Simultaneous Opposing Cardinal Directions
23+
* (SOCD) filtering, which is popular for fast inputs on the WASD keys in
24+
* gaming. When two keys of opposing direction are physically held at the same
25+
* time, a rule is applied to decide which key is sent to the computer.
26+
*
27+
*
28+
* Add it to your keymap
29+
* ---------------------
30+
*
31+
* In rules.mk, add `SRC += features/socd_cleaner.c`. Then in keymap.c, add
32+
*
33+
* #include "features/socd_cleaner.h"
34+
*
35+
* socd_cleaner_t socd_v = {{KC_W, KC_S}, SOCD_CLEANER_LAST};
36+
* socd_cleaner_t socd_h = {{KC_A, KC_D}, SOCD_CLEANER_LAST};
37+
*
38+
* bool process_record_user(uint16_t keycode, keyrecord_t* record) {
39+
* if (!process_socd_cleaner(keycode, record, &socd_v)) { return false; }
40+
* if (!process_socd_cleaner(keycode, record, &socd_h)) { return false; }
41+
* // Your macros...
42+
* return true;
43+
* }
44+
*
45+
* Each `socd_cleaner_t` instance defines a pair of opposing keys and an SOCD
46+
* resolution strategy (explained below). In `process_record_user()`, handler
47+
* `process_socd_cleaner()` is called with each `socd_cleaner_t` instance.
48+
*
49+
* NOTE: The keys don't have to be WASD. But they must be basic keycodes
50+
* (https://docs.qmk.fm/keycodes_basic).
51+
*
52+
*
53+
* Enabling / disabling
54+
* --------------------
55+
*
56+
* SOCD filtering is enabled/disabled globally by assigning to variable
57+
* `socd_cleaner_enabled`. For instance, to enable only on a GAME layer:
58+
*
59+
* layer_state_t layer_state_set_user(layer_state_t state) {
60+
* socd_cleaner_enabled = IS_LAYER_ON_STATE(state, GAME);
61+
* return state;
62+
* }
63+
*
64+
* Or filtering can be disabled per `socd_cleaner_t` instance by setting its
65+
* resolution to SOCD_CLEANER_OFF.
66+
*
67+
*
68+
* Resolution strategies
69+
* ---------------------
70+
*
71+
* As controls vary across games, there are multiple possible SOCD resolution
72+
* strategies. SOCD Cleaner implements the following resolutions:
73+
*
74+
* - SOCD_CLEANER_LAST: (Recommended) Last input priority with reactivation.
75+
* The last key pressed wins. Rapid alternating inputs can be made.
76+
* Repeatedly tapping the D key while A is held sends "ADADADAD."
77+
*
78+
* - SOCD_CLEANER_NEUTRAL: Neutral resolution. When both keys are pressed, they
79+
* cancel and neither is sent.
80+
*
81+
* - SOCD_CLEANER_0_WINS: Key 0 always wins, the first key listed in defining
82+
* the `socd_cleaner_t`. For example, the W key always wins in
83+
*
84+
* socd_cleaner_t socd_ud = {{KC_W, KC_S}, SOCD_CLEANER_0_WINS};
85+
*
86+
* - SOCD_CLEANER_1_WINS: Key 1 always wins, the second key listed.
87+
*
88+
* If you don't know what to pick, SOCD_CLEANER_LAST is recommended. The
89+
* resolution strategy on a `socd_cleaner_t` may be changed at run time by
90+
* assigning to `.resolution`.
91+
*
92+
*
93+
* For full documentation, see
94+
* <https://getreuer.info/posts/keyboards/socd-cleaner>
95+
*/
96+
97+
#pragma once
98+
99+
#include "quantum.h"
100+
101+
#ifdef __cplusplus
102+
extern "C" {
103+
#endif
104+
105+
enum socd_cleaner_resolution {
106+
// Disable SOCD filtering for this key pair.
107+
SOCD_CLEANER_OFF,
108+
// Last input priority with reactivation.
109+
SOCD_CLEANER_LAST,
110+
// Neutral resolution. When both keys are pressed, they cancel.
111+
SOCD_CLEANER_NEUTRAL,
112+
// Key 0 always wins.
113+
SOCD_CLEANER_0_WINS,
114+
// Key 1 always wins.
115+
SOCD_CLEANER_1_WINS,
116+
// Sentinel to count the number of resolution strategies.
117+
SOCD_CLEANER_NUM_RESOLUTIONS,
118+
};
119+
120+
typedef struct {
121+
uint8_t keys[2]; // Basic keycodes for the two opposing keys.
122+
uint8_t resolution; // Resolution strategy.
123+
bool held[2]; // Tracks which keys are physically held.
124+
} socd_cleaner_t;
125+
126+
/**
127+
* Handler function for SOCD cleaner.
128+
*
129+
* This function should be called from process_record_user(). The function may
130+
* be called multiple times with different socd_cleaner_t instances to filter
131+
* more than one SOCD key pair.
132+
*/
133+
bool process_socd_cleaner(uint16_t keycode, keyrecord_t* record,
134+
socd_cleaner_t* state);
135+
136+
/** Determines globally whether SOCD cleaner is enabled. */
137+
extern bool socd_cleaner_enabled;
138+
139+
#ifdef __cplusplus
140+
}
141+
#endif

keyboards/PIANTORUV/keymaps/kurushimee/keymap.c

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
1717
*/
1818

1919
#include QMK_KEYBOARD_H
20+
#include "features/socd_cleaner.h"
21+
22+
socd_cleaner_t socd_v = {{KC_W, KC_S}, SOCD_CLEANER_LAST};
23+
socd_cleaner_t socd_h = {{KC_A, KC_D}, SOCD_CLEANER_LAST};
2024

2125
#define ST_GEM QK_STENO_GEMINI
2226

@@ -61,7 +65,7 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
6165
* ,-----------------------------------------. ,-----------------------------------------.
6266
* | F1 | F2 | F3 | F4 | F5 | F6 | | F7 | F8 | F9 | F10 | F11 | F12 |
6367
* |------+------+------+------+------+------| |------+------+------+------+------+------|
64-
* | Ctrl | Reset|Debug | |Plover|PrtSc | | ← | ↓ | ↑ | → | PGUP | Caps |
68+
* | Ctrl | | |Gaming|Plover|PrtSc | | ← | ↓ | ↑ | → | PGUP | Caps |
6569
* |------+------+------+------+------+------| |------+------+------+------+------+------|
6670
* | Shift| | | | | | | Home | End | Ins | Del | PGDN | Alt |
6771
* `---------------------------+------+------+------. ,------+-----------------------------------------'
@@ -70,7 +74,7 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
7074
*/
7175
[2] = LAYOUT_split_3x6_3(
7276
KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12,
73-
KC_LCTL, QK_BOOT, DB_TOGG, XXXXXXX, TG(3), KC_PSCR, KC_LEFT, KC_DOWN, KC_UP, KC_RGHT, KC_PGUP, KC_CAPS,
77+
KC_LCTL, XXXXXXX, XXXXXXX, TG(4), TG(3), KC_PSCR, KC_LEFT, KC_DOWN, KC_UP, KC_RGHT, KC_PGUP, KC_CAPS,
7478
KC_LSFT, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, KC_HOME, KC_END, KC_INS, KC_DEL, KC_PGDN, KC_LALT,
7579
_______, _______, _______, QK_BOOT, _______, _______
7680
),
@@ -91,6 +95,55 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
9195
STN_RE2, STN_S1, STN_TL, STN_PL, STN_HL, STN_ST1, STN_ST3, STN_FR, STN_PR, STN_LR, STN_TR, STN_DR,
9296
TG(3), STN_S2, STN_KL, STN_WL, STN_RL, STN_ST2, STN_ST4, STN_RR, STN_BR, STN_GR, STN_SR, STN_ZR,
9397
STN_A, STN_O, STN_NB, STN_NC, STN_E, STN_U
98+
),
99+
100+
/* Gaming
101+
* ,-----------------------------------------. ,-----------------------------------------.
102+
* | Tab | Q | W | E | R | T | | Y | U | I | O | P | Esc |
103+
* |------+------+------+------+------+------| |------+------+------+------+------+------|
104+
* | Ctrl | A | S | D | F | G | | H | J | K | L | ; | ' |
105+
* |------+------+------+------+------+------| |------+------+------+------+------+------|
106+
* | Shift| Z | X | C | V | B | | N | M | , | . | / | Exit |
107+
* `---------------------------+------+------+------. ,------+-----------------------------------------'
108+
* | Alt |Space |Lower | |Enter | Bksp | GUI |
109+
* `--------------------' `--------------------'
110+
*/
111+
[4] = LAYOUT_split_3x6_3(
112+
KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_ESC,
113+
KC_LCTL, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT,
114+
KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, TG(4),
115+
KC_LALT, KC_SPC, MO(5), KC_ENT, KC_BSPC, KC_LGUI
116+
),
117+
118+
/* Gaming/Lower
119+
* ,-----------------------------------------. ,-----------------------------------------.
120+
* | Tab | 1 | W | 2 | 6 |JmpThr| | ^ | & | * | ( | ) | \ |
121+
* |------+------+------+------+------+------| |------+------+------+------+------+------|
122+
* | Ctrl | A | S | D | 7 | 9 | | - | = | ` | [ | ] | | |
123+
* |------+------+------+------+------+------| |------+------+------+------+------+------|
124+
* | Shift| 3 | 4 | 5 | 8 | 0 | | _ | + | ~ | { | } | Alt |
125+
* `---------------------------+------+------+------. ,------+-----------------------------------------'
126+
* | Alt |Space | | |Enter | Bksp | GUI |
127+
* `--------------------' `--------------------'
128+
*/
129+
[5] = LAYOUT_split_3x6_3(
130+
KC_TAB, KC_1, KC_W, KC_2, KC_6, KC_H, KC_CIRC, KC_AMPR, KC_ASTR, KC_LPRN, KC_RPRN, KC_BSLS,
131+
KC_LCTL, KC_A, KC_S, KC_D, KC_7, KC_9, KC_MINS, KC_EQL, KC_GRV, KC_LBRC, KC_RBRC, KC_PIPE,
132+
KC_LSFT, KC_3, KC_4, KC_5, KC_8, KC_0, KC_UNDS, KC_PLUS, KC_TILD, KC_LCBR, KC_RCBR, KC_LALT,
133+
KC_LALT, KC_SPC, _______, KC_SPC, KC_BSPC, _______
94134
)
95135

96136
};
137+
138+
bool process_record_user(uint16_t keycode, keyrecord_t* record) {
139+
if (!process_socd_cleaner(keycode, record, &socd_v)) { return false; }
140+
if (!process_socd_cleaner(keycode, record, &socd_h)) { return false; }
141+
142+
return true;
143+
}
144+
145+
layer_state_t layer_state_set_user(layer_state_t state) {
146+
socd_cleaner_enabled = IS_LAYER_ON_STATE(state, 4) || IS_LAYER_ON_STATE(state, 5);
147+
return state;
148+
}
149+

keyboards/PIANTORUV/keymaps/kurushimee/rules.mk

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ STENO_ENABLE = yes
55
STENO_PROTOCOL = geminipr
66
AUDIO_ENABLE = no
77
MIDI_ENABLE = no
8+
9+
SRC += features/socd_cleaner.c

0 commit comments

Comments
 (0)