← More about keyboards

⚠  Caution

Check game rules before using. Notably, Counter-Strike does not allow SOCD filtering. It is your responsibility to disable SOCD Cleaner where it is prohibited.

“Remember, a kite rises against, not with, the wind.” — Hamilton Wright Mabie

Overview

SOCD Cleaner is a QMK userspace library 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. See HitBox’s Introduction to SOCD and Resolutions for a detailed description.

SOCD Cleaner with SOCD_CLEANER_LAST resolution.

Capabilities of SOCD Cleaner:

Motivation and status in QMK core

As of August 2024, SOCD filtering is expected to come soon as a core QMK feature as the Key Cancellation feature in PR 24000 together with a following PR. However, as tzarc’s RFC on Key Cancellation explains, there is both significant interest in this feature and general questions about what’s necessary for real usage patterns with SOCD.

I suggest what’s needed in the interim is a userspace library. SOCD Cleaner provides a solution for users interested in getting SOCD filtering now. It also serves as a test bed for exploring what range of SOCD options fit best for in real use. I’ve tried to keep the code short and simple to make it hopefully approachable for others to hack on.

Add it to your keymap

Prerequisites

I’m also supposing that you have created a keymap under directory location

qmk_firmware/keyboards/keyboard-name/keymaps/keymap-name

I’ll refer to this location as the “keymap folder.”

Step 1: module installation

Install my community modules. Then enable module getreuer/socd_cleaner in your keymap.json file. Or if keymap.json does not exist, create it with the following content:

{
  "modules": ["getreuer/socd_cleaner"]
}

Step 2: keymap.c changes

Your keymap folder should contain a file called keymap.c. Edit this file to make the following changes.

After your keymap definition in keymap.c, add:

socd_cleaner_t socd_opposing_pairs[] = {
  {{KC_W, KC_S}, SOCD_CLEANER_LAST},
  {{KC_A, KC_D}, SOCD_CLEANER_LAST},
};

This definition specifies that SOCD filtering will be done 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.

Step 3: toggling SOCD Cleaner

By default, SOCD Cleaner is enabled globally. Here are two suggestions to control when SOCD Cleaner is enabled. These are mutually exclusive, choose one or the other, not both:

Beware that some games do not allow SOCD filtering, including Counter-Strike. Check game rules before using. Implement one of the suggestions above or a similar means so that you can disable SOCD Cleaner where it is prohibited.

Testing

Flash the firmware to the keyboard. To test the effect, use the QMK Configurator key tester to see what key events your keyboard is sending. Repeatedly tapping the D key while A is held should send ADADADAD.

SOCD key pairs

Each socd_cleaner_t instance in the socd_opposing_pairs array is defined by a pair of basic keycodes and a SOCD resolution strategy (more explanation below). For example, the following line defines A and D as opposing keys with SOCD_CLEANER_LAST resolution:

{{KC_A, KC_D}, SOCD_CLEANER_LAST}

📝  Note

Define socd_cleaner_t instances as global variables, outside of process_record_user().

Resolution strategies

Controls vary across games, and there are different SOCD resolution strategies that may be preferred depending on circumstances. The following resolutions are implemented:

If you don’t know what to pick, SOCD_CLEANER_LAST is recommended.

Dynamic strategy

The resolution strategy on a socd_cleaner_t may be changed at run time by assigning to its .resolution field. Supposing GAME1 and GAME2 are custom keycodes, here is how to switch to a different strategy between games:

bool process_record_user(uint16_t keycode, keyrecord_t* record) {
  switch (keycode) {
    case GAME1:
      if (record->event.pressed) {
        socd_opposing_pairs[0].resolution = SOCD_CLEANER_LAST;
        socd_opposing_pairs[1].resolution = SOCD_CLEANER_LAST;
      }
      break;

    case GAME2:
      if (record->event.pressed) {
        socd_opposing_pairs[0].resolution = SOCD_CLEANER_0_WINS;
        socd_opposing_pairs[1].resolution = SOCD_CLEANER_NEUTRAL;
      }
      break;

    // Other macros...
  }
  return true;
}

Explanation

If you are interested in the technical details, here is an outline of how SOCD Cleaner processes a key event.

  1. For every key press or release, process_record_user() is called. In turn, process_socd_cleaner() is called for each SOCD key pair.

  2. In each call of process_socd_cleaner(), the function checks whether the event’s keycode matches a keycode in the socd_cleaner_t. If not, the function returns early.

  3. Otherwise, socd_cleaner_t.held is updated to track which keys are physically held. This may generally differ from what keys are being reported to the host computer.

  4. If the key opposing the current event is held, SOCD resolution is needed. Following the logic outlined below, we add or remove keys from the current keyboard report.

Suppose that the current event is on key 0 and the opposing key 1 is held (the reverse roles are similar):

Acknowledgements

Thank you @Xelus22 for spearheading Key Cancellation in QMK core, and thank you u/Memey-al-la-Creamy for valuable feedback. A thank you to @tzarc, @drashna, @ChristopheL92, @henrebotha, @aldehir, @kqxu1017, and others who contributed to discussion of this topic.

← More about keyboards