Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
3b3d99c
Create config.py
some1ataplace May 9, 2026
bff5377
Update filtering.py
some1ataplace May 9, 2026
423882a
Update __main__.py
some1ataplace May 9, 2026
f7ebe34
Update keyboard_retrieval.py
some1ataplace May 9, 2026
ebcdb71
Update chattering_fix.sh
some1ataplace May 9, 2026
109888d
Merge branch 'master' into Multiple-Fixes
some1ataplace May 9, 2026
e11d56a
Create mouse_filtering.py
some1ataplace May 9, 2026
6b501dc
Create mouse_retrieval.py
some1ataplace May 9, 2026
6d9dba2
Create mouse_main.py
some1ataplace May 9, 2026
2b41225
Create mouse_config.py
some1ataplace May 9, 2026
4843e07
Create mouse_fix.sh
some1ataplace May 9, 2026
4b34237
Delete src/mouse_fix.sh
some1ataplace May 9, 2026
b4b360b
Create mouse_chattering.sh
some1ataplace May 9, 2026
567f5fa
Create mouse_fix.service
some1ataplace May 9, 2026
bb6e45c
Rename chattering_fix.sh to keyboard_chattering.sh
some1ataplace May 9, 2026
558a871
Rename chattering_fix.service to keyboard_chattering.service
some1ataplace May 9, 2026
0811deb
Rename mouse_fix.service to mouse_chattering.service
some1ataplace May 9, 2026
c01a759
Rename config.py to keyboard_config.py
some1ataplace May 9, 2026
c845b70
Rename filtering.py to keyboard_filtering.py
some1ataplace May 9, 2026
65f321a
Update keyboard_filtering.py
some1ataplace May 9, 2026
179ca9c
Update keyboard_retrieval.py
some1ataplace May 9, 2026
f165665
Update and rename __main__.py to keyboard_main.py
some1ataplace May 9, 2026
f38bc08
Update mouse_filtering.py
some1ataplace May 9, 2026
6aeb242
Update mouse_retrieval.py
some1ataplace May 9, 2026
33c7e80
Update mouse_main.py
some1ataplace May 9, 2026
d413d3f
Update keyboard_chattering.sh
some1ataplace May 9, 2026
3d66341
Update mouse_chattering.sh
some1ataplace May 9, 2026
6071ce5
Update mouse_chattering.sh
some1ataplace May 9, 2026
32903fa
Update mouse_chattering.service
some1ataplace May 9, 2026
b107a6b
Update keyboard_chattering.service
some1ataplace May 9, 2026
193105b
Update README.md
some1ataplace May 9, 2026
d72fb35
Update README.md
some1ataplace May 9, 2026
4f11429
Update keyboard_chattering.sh
some1ataplace May 9, 2026
746c1e8
Update mouse_chattering.sh
some1ataplace May 9, 2026
566701d
Update keyboard_filtering.py
some1ataplace May 9, 2026
1bdf5c8
Update keyboard_main.py
some1ataplace May 9, 2026
02f5184
Update mouse_filtering.py
some1ataplace May 9, 2026
a930fb1
Update mouse_main.py
some1ataplace May 9, 2026
bc84357
Update keyboard_retrieval.py
some1ataplace May 9, 2026
9c39f93
Update mouse_retrieval.py
some1ataplace May 9, 2026
8850586
Update keyboard_retrieval.py
some1ataplace May 9, 2026
8c85f60
Update mouse_retrieval.py
some1ataplace May 9, 2026
eb3d9df
Update README.md
some1ataplace May 9, 2026
e6d8313
Update README.md
some1ataplace May 9, 2026
23ab86e
Update README.md
some1ataplace May 9, 2026
05a37b5
Update keyboard_retrieval.py
some1ataplace May 9, 2026
f983e5f
Update mouse_retrieval.py
some1ataplace May 9, 2026
e6a0707
Update mouse_retrieval.py
some1ataplace May 9, 2026
ec37dd6
Update keyboard_retrieval.py
some1ataplace May 9, 2026
90a59b2
Update keyboard_filtering.py
some1ataplace May 9, 2026
ca50a28
Update mouse_filtering.py
some1ataplace May 9, 2026
e2a6dc3
Update keyboard_filtering.py
some1ataplace May 9, 2026
b0907c4
Update mouse_filtering.py
some1ataplace May 9, 2026
7352b51
Update README.md
some1ataplace May 9, 2026
c194a64
Update README.md
some1ataplace May 9, 2026
90a358c
Update README.md
some1ataplace May 9, 2026
ed5d5a2
Update keyboard_chattering.service
some1ataplace May 20, 2026
ad424b2
Update mouse_chattering.service
some1ataplace May 20, 2026
1822851
Update README.md
some1ataplace May 20, 2026
bf94ecf
Update README.md
some1ataplace May 20, 2026
e688161
Update README.md
some1ataplace May 26, 2026
f237562
Update README.md
some1ataplace May 26, 2026
2c1ec82
Update keyboard_chattering.service
some1ataplace May 26, 2026
8c9334d
Update mouse_chattering.service
some1ataplace May 26, 2026
50e5195
Update keyboard_config.py
some1ataplace May 26, 2026
6438bb0
Update mouse_config.py
some1ataplace May 26, 2026
4638b16
Update keyboard_main.py
some1ataplace May 26, 2026
8faa8fb
Update mouse_main.py
some1ataplace May 26, 2026
ce11435
Update keyboard_chattering.sh
some1ataplace May 26, 2026
3615bc8
Update mouse_chattering.sh
some1ataplace May 26, 2026
c7fb32f
Update mouse_filtering.py
some1ataplace May 26, 2026
cfcc140
Update keyboard_filtering.py
some1ataplace May 26, 2026
f1c2103
Update mouse_retrieval.py
some1ataplace May 26, 2026
874a5dd
Update keyboard_retrieval.py
some1ataplace May 26, 2026
66ec593
Update keyboard_chattering.service
some1ataplace May 26, 2026
8c24ec8
Update mouse_chattering.service
some1ataplace May 26, 2026
c9a6174
Update README.md
some1ataplace May 26, 2026
5702555
Update README.md
some1ataplace May 27, 2026
1657ed4
Update README.md
some1ataplace May 27, 2026
690e128
Update README.md
some1ataplace May 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
582 changes: 526 additions & 56 deletions README.md

Large diffs are not rendered by default.

12 changes: 0 additions & 12 deletions chattering_fix.service

This file was deleted.

3 changes: 0 additions & 3 deletions chattering_fix.sh

This file was deleted.

19 changes: 19 additions & 0 deletions keyboard_chattering.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[Unit]
Description=Keyboard Chattering Fix service
# Ensures that if systemd restarts this during wake-up, the system is fully awake first.
After=sleep.target

[Service]
# Wait 3 seconds before starting the script to give the USB bus and Wayland/X11 time to fully wake up
ExecStartPre=/bin/sleep 3

# Change ExecStart to the absolute path of your shell script
ExecStart=/absolute/path/to/keyboard_chattering.sh

# Systemd will automatically restart the script if it crashes, or if it exits due to a USB sleep/disconnect
Restart=always
RestartSec=5

[Install]
# Start at normal boot. Systemd handles the restarts on wake automatically.
WantedBy=multi-user.target
16 changes: 16 additions & 0 deletions keyboard_chattering.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash
# ==============================================================================
# KEYBOARD CHATTERING FIX - STARTUP SCRIPT
# ==============================================================================
# Change the `cd` path below to the absolute path of this project folder.
# Ensure this script is executable: chmod +x keyboard_chattering.sh
#
# AVAILABLE ARGUMENTS:
# -k "usb-id-here" : The device ID of your keyboard in /dev/input/by-id/
# -t 30 : Bounce filter threshold in milliseconds (Default: 30)
# --keys KEY_A,KEY_SPACE : Explicitly filter only these keys (Leave blank for all)
# -r : Enable auto-reconnect infinite loop (Required for Cron/SysVinit)
# DO NOT USE `-r` if using Systemd! Systemd handles restarts natively.
# ==============================================================================

cd /absolute/path/to/project && sudo python3 -m src.keyboard_main -k <KEYBOARD id> -t 30
19 changes: 19 additions & 0 deletions mouse_chattering.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[Unit]
Description=Mouse Chattering Fix service
# Ensures that if systemd restarts this during wake-up, the system is fully awake first.
After=sleep.target

[Service]
# Wait 3 seconds before starting the script to give the USB bus and Wayland/X11 time to fully wake up
ExecStartPre=/bin/sleep 3

# Change ExecStart to the absolute path of your shell script
ExecStart=/absolute/path/to/mouse_chattering.sh

# Systemd will automatically restart the script if it crashes, or if it exits due to a USB sleep/disconnect
Restart=always
RestartSec=5

[Install]
# Start at normal boot. Systemd handles the restarts on wake automatically.
WantedBy=multi-user.target
22 changes: 22 additions & 0 deletions mouse_chattering.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash
# ==============================================================================
# MOUSE CHATTERING FIX - STARTUP SCRIPT
# ==============================================================================
# Change the `cd` path below to the absolute path of this project folder.
# Ensure this script is executable: chmod +x mouse_chattering.sh
#
# AVAILABLE ARGUMENTS:
# -m "usb-id-here" : The device ID of your mouse in /dev/input/by-id/
# -t 30 : Button double-click threshold in ms (Default: 30)
# --buttons BTN_LEFT,BTN_SIDE : Explicitly filter only these buttons (Leave blank for all)
#
# ADVANCED SENSOR ARGUMENTS:
# -sr 150 : Block scroll wheel jumping in reverse direction (ms)
# -sd 30 : Block scroll wheel firing twice in same direction (ms)
# -jl 300 : Block massive cursor teleports exceeding X pixels per frame
#
# -r : Enable auto-reconnect infinite loop (Required for Cron/SysVinit)
# DO NOT USE `-r` if using Systemd! Systemd handles restarts natively.
# ==============================================================================

cd /absolute/path/to/project && sudo python3 -m src.mouse_main -m <MOUSE id> -t 30
50 changes: 0 additions & 50 deletions src/__main__.py

This file was deleted.

60 changes: 0 additions & 60 deletions src/filtering.py

This file was deleted.

73 changes: 73 additions & 0 deletions src/keyboard_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""
Constants for keyboard chattering filter configuration.

PRECEDENCE RULES:
1. Command Line (--keys / -k): Highest priority. If provided, this file is ignored.
2. This File: Used if no command line argument is provided.
3. Interactive Prompt: Used ONLY if both CLI and this file are empty.
"""
import libevdev

# ==========================================
# 0. DEFAULT DEVICE
# ==========================================
# Set this to avoid the interactive prompt on boot/startup.
# Leave as "" to be asked every time if running manually.
# Example: DEVICE_NAME = "usb-Corsair_Corsair_K70_RGB-event-kbd"
DEVICE_NAME = ""

# ==========================================
# 1. SPECIFIC KEY FILTERING (Allowlist)
# ==========================================
# To filter specific keys, add them to this set.
# Example: FILTERED_KEYS = {"KEY_A", "KEY_SPACE"}
# Leave it empty as set() to filter ALL keys by default.
FILTERED_KEYS = set()

# ==========================================
# 2. PER-KEY THRESHOLDS
# ==========================================
# Override the default threshold for specific keys.
# Great for heavy spacebars (needs more debounce) vs light gaming keys (needs less lag).
KEY_THRESHOLDS = {
# "KEY_SPACE": 50,
# "KEY_A": 15,
}

# ==========================================
# 3. KEY REMAPPING / MACROS
# ==========================================
# Swap keys at the kernel level. Format: {"KEY_PRESSED": "KEY_OUTPUT"}
KEY_MAP = {
# "KEY_CAPSLOCK": "KEY_LEFTCTRL", # Example: Make CapsLock act as Left Ctrl
}


# ==========================================
# REFERENCE: COMMON KEY VALUES TO COPY/PASTE
# ==========================================
# Letters:
# 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
#
# Numbers (Top Row):
# KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0, KEY_MINUS, KEY_EQUAL
#
# Numpad:
# KEY_KP0 to KEY_KP9, KEY_KPMINUS, KEY_KPPLUS, KEY_KPASTERISK, KEY_KPDOT, KEY_KPENTER
#
# Special/Control:
# KEY_SPACE, KEY_ENTER, KEY_BACKSPACE, KEY_TAB, KEY_ESC, KEY_CAPSLOCK
#
# Modifiers:
# KEY_LEFTSHIFT, KEY_RIGHTSHIFT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTMETA (Super/Windows)
#
# Arrows & Navigation:
# KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_HOME, KEY_END, KEY_PAGEUP, KEY_PAGEDOWN, KEY_INSERT, KEY_DELETE
#
# Function Keys:
# KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12
#
# Punctuation:
# KEY_LEFTBRACE, KEY_RIGHTBRACE, KEY_SEMICOLON, KEY_APOSTROPHE, KEY_GRAVE, KEY_BACKSLASH, KEY_COMMA, KEY_DOT, KEY_SLASH
87 changes: 87 additions & 0 deletions src/keyboard_filtering.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import logging
from collections import defaultdict
from typing import DefaultDict, Dict, NoReturn, List
import time
import libevdev

def filter_chattering(evdev: libevdev.Device, default_threshold: int, keys_to_filter: List[libevdev.EventCode],
key_thresholds: dict, key_map: dict) -> NoReturn:

# Reset global states on reconnect to prevent ghost "stuck" keys
global _last_key_code
_last_key_up.clear()
_key_pressed.clear()
_last_key_code = None

time.sleep(1) # Delay to allow Enter key to release natively after running script
evdev.grab()
ui_dev = evdev.create_uinput_device()
logging.info("Listening to keyboard input events...")

while True:
try:
for e in evdev.events():
# Process the event. _from_keystroke returns either the (modified) event, or None to drop it.
processed_event = _from_keystroke(e, default_threshold, keys_to_filter, key_thresholds, key_map)
if processed_event:
ui_dev.send_events([processed_event, libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT, 0)])
except libevdev.EventsDroppedException:
# If the user presses too many keys simultaneously (NKRO overflow), resync the buffer
logging.debug("Kernel buffer overflowed. Resyncing...")
for e in evdev.sync():
processed_event = _from_keystroke(e, default_threshold, keys_to_filter, key_thresholds, key_map)
if processed_event:
ui_dev.send_events([processed_event, libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT, 0)])

def _from_keystroke(event: libevdev.InputEvent, default_threshold: int, keys_to_filter: List[libevdev.EventCode],
key_thresholds: dict, key_map: dict):
global _last_key_code

# Ignore sync/misc events natively
if event.matches(libevdev.EV_SYN) or event.matches(libevdev.EV_MSC):
return None

# REMAPPING: Safely construct a brand new event object to prevent mutating C-bindings
if event.matches(libevdev.EV_KEY) and event.code.name in key_map:
target_key_name = key_map[event.code.name]
event = libevdev.InputEvent(libevdev.evbit(target_key_name), event.value, event.sec, event.usec)
if event.value == 1: # Only log on the key-down to prevent log spam
logging.debug(f'REMAPPED to {target_key_name}')

# Do not filter modifier combinations or natively held keys (value > 1)
if not event.matches(libevdev.EV_KEY) or event.value > 1:
return event

# If an allowlist is provided and this key isn't in it, forward it without filtering
if keys_to_filter and event.code not in keys_to_filter:
return event

# PER-KEY THRESHOLDS: Check if this specific key has a custom delay, else use default.
threshold = key_thresholds.get(event.code.name, default_threshold)

# Process standard Key Up (0) events
if event.value == 0:
if _key_pressed[event.code]:
logging.debug(f'FORWARDING {event.code.name} up')
_last_key_up[event.code] = event.sec * 1E6 + event.usec
_key_pressed[event.code] = False
return event
else:
return None

prev = _last_key_up.get(event.code)
now = event.sec * 1E6 + event.usec

# Check _last_key_code to prevent filtering fast alternating letters (e.g., e -> v -> e)
if prev is None or now - prev > threshold * 1E3 or _last_key_code != event.code:
logging.debug(f'FORWARDING {event.code.name} down')
_key_pressed[event.code] = True
_last_key_code = event.code
return event

logging.info(f'FILTERED {event.code.name} down: bounced within {threshold}ms')
return None

_last_key_up: Dict[libevdev.EventCode, int] = {}
_key_pressed: DefaultDict[libevdev.EventCode, bool] = defaultdict(bool)
_last_key_code = None
Loading