This commit is contained in:
Christoph Cullmann 2025-03-18 22:27:19 +01:00
parent 4dc5811f00
commit ee0a3c3be0
No known key found for this signature in database
61 changed files with 5743 additions and 0 deletions

View file

@ -0,0 +1,79 @@
# SOCD Cleaner
<table>
<tr><td><b>Module</b></td><td><tt>getreuer/socd_cleaner</tt></td></tr>
<tr><td><b>Version</b></td><td>2025-03-07</td></tr>
<tr><td><b>Maintainer</b></td><td>Pascal Getreuer (@getreuer)</td></tr>
<tr><td><b>License</b></td><td><a href="../LICENSE.txt">Apache 2.0</a></td></tr>
<tr><td><b>Documentation</b></td><td>
<a href="https://getreuer.info/posts/keyboards/socd-cleaner">https://getreuer.info/posts/keyboards/socd-cleaner</a>
</td></tr>
</table>
This is a community module adaptation of [SOCD Cleaner
Case](https://getreuer.info/posts/keyboards/socd-cleaner) for Simultaneous
Opposing Cardinal Directions (SOCD) filtering. What this mouthful of a name
means is that when two keys of opposing direction are held at the same time, a
rule is applied to decide which key is sent to the computer. Such filtering is
popular for fast inputs on the WASD keys in gaming.
**Caution: Check game rules before using.** Notably, [Counter-Strike does not
allow SOCD
filtering](https://store.steampowered.com/news/app/730/view/6500469346429600836).
It is your responsibility to disable SOCD Cleaner where it is prohibited.
## Add SOCD to your keymap
Add the following to your `keymap.json`:
```json
{
"modules": ["getreuer/socd_cleaner"]
}
```
Then in your `keymap.c`, add:
```c
socd_cleaner_t socd_opposing_pairs[] = {
{{KC_W, KC_S}, SOCD_CLEANER_LAST},
{{KC_A, KC_D}, SOCD_CLEANER_LAST},
};
```
These lines specify that SOCD filtering is to be performed on the WASD keys
(referred to by keycodes `KC_W`, `KC_A`, `KC_S`, `KC_D`) with last input
priority resolution (`SOCD_CLEANER_LAST`). If you want to do something else,
this is where to change that.
Resolution strategies:
* `SOCD_CLEANER_LAST`: (Recommended) Last input priority with reactivation. The
last key pressed wins. If the last key is released while the opposing key is
still held, the opposing key is reactivated. Rapid alternating inputs can be
made. Repeatedly tapping the `D` key while `A` is held sends `ADADADAD`.
* `SOCD_CLEANER_NEUTRAL`: Neutral resolution. When both keys are pressed, they
cancel and neither is sent.
* `SOCD_CLEANER_0_WINS`: Key 0 always wins, the first key listed in defining the
opposing pair.
* `SOCD_CLEANER_1_WINS`: Key 1 always wins, the second key listed.
* `SOCD_CLEANER_OFF`: SOCD filtering is disabled for this key pair.
SOCD Cleaner is enabled by default. Optionally, use these keycodes to enable and
disable SOCD Cleaner globally for all opposing pairs:
| Keycode | Description |
|-----------|---------------------------|
| `SOCDON` | Turn SOCD Cleaner on. |
| `SOCDOFF` | Turn SOCD Cleaner off. |
| `SOCDTOG` | Toggle SOCD Cleaner. |
See the [SOCD Cleaner
documentation](https://getreuer.info/posts/keyboards/socd-cleaner) for further
explanation and details.

View file

@ -0,0 +1,36 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifdef COMMUNITY_MODULE_SOCD_CLEANER_ENABLE
uint16_t socd_opposing_pairs_count_raw(void) {
return ARRAY_SIZE(socd_opposing_pairs);
}
__attribute__((weak)) uint16_t socd_opposing_pairs_count(void) {
return socd_opposing_pairs_count_raw();
}
socd_cleaner_t* socd_opposing_pairs_get_raw(uint16_t index) {
if (index >= socd_opposing_pairs_count_raw()) {
return NULL;
}
return &socd_opposing_pairs[index];
}
__attribute__((weak)) socd_cleaner_t* socd_opposing_pairs_get(uint16_t index) {
return socd_opposing_pairs_get_raw(index);
}
#endif // COMMUNITY_MODULE_SOCD_CLEANER_ENABLE

View file

@ -0,0 +1,17 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "socd_cleaner.h"

View file

@ -0,0 +1,9 @@
{
"module_name": "SOCD Cleaner",
"maintainer": "getreuer",
"keycodes": [
{"key": "SOCDON"},
{"key": "SOCDOFF"},
{"key": "SOCDTOG"}
]
}

View file

@ -0,0 +1,116 @@
// Copyright 2024-2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @file socd_cleaner.c
* @brief SOCD Cleaner community module implementation
*
* For full documentation, see
* <https://getreuer.info/posts/keyboards/socd-cleaner>
*/
#include "socd_cleaner.h"
ASSERT_COMMUNITY_MODULES_MIN_API_VERSION(1, 0, 0);
// Defined in introspection.c.
uint16_t socd_opposing_pairs_count(void);
socd_cleaner_t* socd_opposing_pairs_get(uint16_t index);
bool socd_cleaner_enabled = true;
static void update_key(uint8_t keycode, bool press) {
if (press) {
add_key(keycode);
} else {
del_key(keycode);
}
}
static bool process_opposing_pair(
uint16_t keycode, keyrecord_t* record, socd_cleaner_t* state) {
if (!state || !state->resolution ||
(keycode != state->keys[0] && keycode != state->keys[1])) {
return true; // Quick return when disabled or on unrelated events.
}
// The current event corresponds to index `i`, 0 or 1, in the SOCD key pair.
const uint8_t i = (keycode == state->keys[1]);
const uint8_t opposing = i ^ 1; // Index of the opposing key.
// Track which keys are physically held (vs. keys in the report).
state->held[i] = record->event.pressed;
// Perform SOCD resolution for events where the opposing key is held.
if (state->held[opposing]) {
switch (state->resolution) {
case SOCD_CLEANER_LAST: // Last input priority with reactivation.
// If the current event is a press, then release the opposing key.
// Otherwise if this is a release, then press the opposing key.
update_key(state->keys[opposing], !state->held[i]);
break;
case SOCD_CLEANER_NEUTRAL: // Neutral resolution.
// Same logic as SOCD_CLEANER_LAST, but skip default handling so that
// the current key has no effect while the opposing key is held.
update_key(state->keys[opposing], !state->held[i]);
// Send updated report (normally, default handling would do this).
send_keyboard_report();
return false; // Skip default handling.
case SOCD_CLEANER_0_WINS: // Key 0 wins.
case SOCD_CLEANER_1_WINS: // Key 1 wins.
if (opposing == (state->resolution - SOCD_CLEANER_0_WINS)) {
// The opposing key is the winner. The current key has no effect.
return false; // Skip default handling.
} else {
// The current key is the winner. Update logic is same as above.
update_key(state->keys[opposing], !state->held[i]);
}
break;
}
}
return true; // Continue default handling to press/release current key.
}
bool process_record_socd_cleaner(uint16_t keycode, keyrecord_t* record) {
switch (keycode) {
case SOCDON: // Turn SOCD Cleaner on.
if (record->event.pressed) {
socd_cleaner_enabled = true;
}
return false;
case SOCDOFF: // Turn SOCD Cleaner off.
if (record->event.pressed) {
socd_cleaner_enabled = false;
}
return false;
case SOCDTOG: // Toggle SOCD Cleaner.
if (record->event.pressed) {
socd_cleaner_enabled = !socd_cleaner_enabled;
}
return false;
}
if (socd_cleaner_enabled) {
for (int i = 0; i < (int)socd_opposing_pairs_count(); ++i) {
socd_cleaner_t* state = socd_opposing_pairs_get(i);
if (!process_opposing_pair(keycode, record, state)) {
return false;
}
}
}
return true;
}

View file

@ -0,0 +1,83 @@
// Copyright 2024-2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @file socd_cleaner.h
* @brief SOCD Cleaner community module: enhance WASD for fast inputs for gaming
*
*
* SOCD Cleaner is a QMK module for Simultaneous Opposing Cardinal Directions
* (SOCD) filtering, which is popular for fast inputs on the WASD keys in
* gaming. When two keys of opposing direction are physically held at the same
* time, a rule is applied to decide which key is sent to the computer.
*
* As controls vary across games, there are multiple possible SOCD resolution
* strategies. SOCD Cleaner implements the following resolutions:
*
* - SOCD_CLEANER_LAST: (Recommended) Last input priority with reactivation.
* The last key pressed wins. Rapid alternating inputs can be made.
* Repeatedly tapping the D key while A is held sends "ADADADAD."
*
* - SOCD_CLEANER_NEUTRAL: Neutral resolution. When both keys are pressed, they
* cancel and neither is sent.
*
* - SOCD_CLEANER_0_WINS: Key 0 always wins, the first key listed in defining
* the `socd_cleaner_t`.
*
* - SOCD_CLEANER_1_WINS: Key 1 always wins, the second key listed.
*
* If you don't know what to pick, SOCD_CLEANER_LAST is recommended. The
* resolution strategy on a `socd_cleaner_t` may be changed at run time by
* assigning to `.resolution`.
*
*
* For full documentation, see
* <https://getreuer.info/posts/keyboards/socd-cleaner>
*/
#pragma once
#include "quantum.h"
#ifdef __cplusplus
extern "C" {
#endif
enum socd_cleaner_resolution {
// Disable SOCD filtering for this key pair.
SOCD_CLEANER_OFF,
// Last input priority with reactivation.
SOCD_CLEANER_LAST,
// Neutral resolution. When both keys are pressed, they cancel.
SOCD_CLEANER_NEUTRAL,
// Key 0 always wins.
SOCD_CLEANER_0_WINS,
// Key 1 always wins.
SOCD_CLEANER_1_WINS,
// Sentinel to count the number of resolution strategies.
SOCD_CLEANER_NUM_RESOLUTIONS,
};
typedef struct {
uint8_t keys[2]; // Basic keycodes for the two opposing keys.
uint8_t resolution; // Resolution strategy.
bool held[2]; // Tracks which keys are physically held.
} socd_cleaner_t;
/** Determines globally whether SOCD cleaner is enabled. */
extern bool socd_cleaner_enabled;
#ifdef __cplusplus
}
#endif