diff --git a/README.md b/README.md index 6deec25..e90a692 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ By filtering such anomalies, we can hopefully remove chatter without impeding ac ## Installation -Download the repository as a zip and extract the file. The dependencies are listed in the requirements.txt. And you can install it with the command below. +Download the repository as a zip and extract the file. The dependencies are listed in the requirements.txt. And you can install it with the command below. ```shell sudo pip3 install -r requirements.txt @@ -57,6 +57,49 @@ sudo pip3 install -r requirements.txt sudo python3 -m src ``` +### Key Filtering Configuration + +The filter has two operating modes: + +#### Mode 1: Filter ALL keys (original default behavior) +By default, the filter works as it always has - applying the anti-chattering filter to all keyboard keys. This is the behavior when `FILTERED_KEYS` is set to `None` or empty in `src/constants.py`. + +#### Mode 2: Filter only specific keys +You can configure the filter to apply only to specific keys that exhibit chattering. To do this: + +1. Open `src/constants.py` +2. Uncomment and modify the `FILTERED_KEYS` variable with the desired keys +3. Use only the key name (e.g., `"KEY_A"`, `"KEY_SPACE"`) - no need for the `libevdev.EV_KEY.` prefix + +**Configuration examples:** + +```python +# Filter only specific keys +FILTERED_KEYS = { + "KEY_A", # A key with chattering + "KEY_SPACE", # Spacebar with chattering + "KEY_ENTER", # Enter key with chattering +} + +# Filter all letters and numbers +FILTERED_KEYS = { + "KEY_A", "KEY_B", "KEY_C", "KEY_D", "KEY_E", "KEY_F", "KEY_G", "KEY_H", + "KEY_I", "KEY_J", "KEY_K", "KEY_L", "KEY_M", "KEY_N", "KEY_O", "KEY_P", + "KEY_Q", "KEY_R", "KEY_S", "KEY_T", "KEY_U", "KEY_V", "KEY_W", "KEY_X", + "KEY_Y", "KEY_Z", "KEY_0", "KEY_1", "KEY_2", "KEY_3", "KEY_4", "KEY_5", + "KEY_6", "KEY_7", "KEY_8", "KEY_9", "KEY_SPACE", "KEY_ENTER", +} + +# Return to original behavior (filter all keys) +FILTERED_KEYS = None +``` + +**Advantages of specific mode:** +- ✅ Better performance (processes only problematic keys) +- ✅ Preserves modifier keys (Ctrl, Alt, Shift) +- ✅ Doesn't interfere with function or special keys +- ✅ Flexible per-key configuration + ### Customization Options - -k KEYBOARD, --keyboard KEYBOARD @@ -90,12 +133,12 @@ Then, copy the `chattering_fix.service` to `/etc/systemd/system/` and enable it ```shell systemctl enable --now chattering_fix ``` -You can check if the systemd unit file is properly working using +You can check if the systemd unit file is properly working using ```shell systemctl status chattering_fix.service ``` -You can also use +You can also use ```shell journalctl -xeu chattering_fix.service ``` -just to make sure that there are no errors. \ No newline at end of file +just to make sure that there are no errors. diff --git a/src/constants.py b/src/constants.py new file mode 100644 index 0000000..fb134db --- /dev/null +++ b/src/constants.py @@ -0,0 +1,42 @@ +""" +Constants for keyboard chattering filter configuration. + +DEFAULT BEHAVIOR: +- If FILTERED_KEYS is empty (None or empty list), ALL keys will be filtered (original behavior) +- If FILTERED_KEYS contains keys, only those specific keys will be filtered + +To customize which keys should be filtered, define the FILTERED_KEYS list with key names. +Use only the key name (e.g., "KEY_A", "KEY_SPACE") instead of the full code. + +Examples of key names: +- Letters: "KEY_A", "KEY_B", "KEY_C", etc. +- Numbers: "KEY_0", "KEY_1", "KEY_2", etc. +- Special: "KEY_SPACE", "KEY_ENTER", "KEY_BACKSPACE", "KEY_TAB" +- Function: "KEY_F1", "KEY_F2", etc. +- Modifiers: "KEY_LEFTSHIFT", "KEY_RIGHTSHIFT", "KEY_LEFTCTRL", etc. +- Arrows: "KEY_UP", "KEY_DOWN", "KEY_LEFT", "KEY_RIGHT" +""" + +from typing import Optional, Set + +# KEY FILTERING CONFIGURATION + +FILTERED_KEYS = { + # Example: filter only some letters that have chattering + "KEY_P", +} + +# Option 1: Filter ALL keys (original default behavior) +# Leave as None or empty list to maintain original behavior +# FILTERED_KEYS: Optional[Set[str]] = None + +# Option 2: Filter only specific keys +# Uncomment and modify the list below to filter only specific keys +# FILTERED_KEYS = { +# # Example: filter only some letters that have chattering +# "KEY_A", +# "KEY_S", +# "KEY_D", +# "KEY_SPACE", +# "KEY_ENTER", +# } diff --git a/src/filtering.py b/src/filtering.py index 8fc185d..2386f4d 100644 --- a/src/filtering.py +++ b/src/filtering.py @@ -1,9 +1,35 @@ import logging from collections import defaultdict -from typing import DefaultDict, Dict, NoReturn +from typing import DefaultDict, Dict, NoReturn, Set import libevdev +from src.constants import FILTERED_KEYS + + +def _get_filtered_keys_set() -> Set[libevdev.EventCode]: + """ + Converts the list of key names to libevdev codes. + If FILTERED_KEYS is None or empty, returns None (filter all keys). + """ + if not FILTERED_KEYS: + return None + + filtered_codes = set() + for key_name in FILTERED_KEYS: + try: + # Converts "KEY_A" to libevdev.EV_KEY.KEY_A + key_code = getattr(libevdev.EV_KEY, key_name) + filtered_codes.add(key_code) + except AttributeError: + logging.warning(f"Key name '{key_name}' not found in libevdev.EV_KEY. Skipping...") + + return filtered_codes + + +# Converts the key list once at initialization +_FILTERED_KEYS_CODES = _get_filtered_keys_set() + def filter_chattering(evdev: libevdev.Device, threshold: int) -> NoReturn: # grab the device - now only we see the events it emits @@ -11,6 +37,15 @@ def filter_chattering(evdev: libevdev.Device, threshold: int) -> NoReturn: # create a copy of the device that we can write to - this will emit the filtered events to anyone who listens ui_dev = evdev.create_uinput_device() + # Log filtering mode + if _FILTERED_KEYS_CODES is None: + logging.info("Filtering mode: ALL keys (original behavior)") + else: + logging.info(f"Filtering mode: SPECIFIC keys only ({len(_FILTERED_KEYS_CODES)} keys configured)") + if logging.getLogger().isEnabledFor(logging.DEBUG): + key_names = [name for name in FILTERED_KEYS] + logging.debug(f"Filtered keys: {', '.join(sorted(key_names))}") + logging.info("Listening to input events...") while True: @@ -30,6 +65,10 @@ def _from_keystroke(event: libevdev.InputEvent, threshold: int) -> bool: logging.debug(f'FORWARDING {event.code}') return True + if _FILTERED_KEYS_CODES is not None and event.code not in _FILTERED_KEYS_CODES: + logging.debug(f'FORWARDING {event.code} (not in filtered keys list)') + return True + # the values are 0 for up, 1 for down and 2 for hold if event.value == 0: if _key_pressed[event.code]: @@ -55,4 +94,4 @@ def _from_keystroke(event: libevdev.InputEvent, threshold: int) -> bool: _last_key_up: Dict[libevdev.EventCode, int] = {} -_key_pressed: DefaultDict[libevdev.EventCode, bool] = defaultdict(bool) \ No newline at end of file +_key_pressed: DefaultDict[libevdev.EventCode, bool] = defaultdict(bool)