| Crates.io | intercept-bounce |
| lib.rs | intercept-bounce |
| version | 0.9.1 |
| created_at | 2025-05-06 01:42:46.025062+00 |
| updated_at | 2025-10-04 21:51:07.298389+00 |
| description | An Interception Tools filter to eliminate keyboard chatter (switch bounce). |
| homepage | |
| repository | https://github.com/sinity/intercept-bounce |
| max_upload_size | |
| id | 1661793 |
| size | 381,842 |
An Interception Tools filter designed to eliminate keyboard chatter (also known as switch bounce) while providing detailed statistics and diagnostics.
It reads raw Linux input_event structs from standard input, filters out rapid duplicate key press/release events based on a configurable time threshold, and writes the filtered events to standard output. Comprehensive statistics about dropped events, timings, and near-misses are printed to standard error on exit or periodically.
--debounce-time, default: 25ms). Key repeats (value=2) are never filtered.--near-miss-threshold-time, default: 100ms). Useful for diagnosing keys with inconsistent timing.--log-all-events: Log details ([PASS]/[DROP]) for (almost) every event.--log-bounces: Log details only for dropped (bounced) key events.--verbose: Enable DEBUG level internal logging.RUST_LOG environment variable for fine-grained tracing filter control (overrides --verbose).--debounce-key KEY_ENTER to limit debouncing to specific keys (multiple instances allowed), or --ignore-key KEY_VOLUMEDOWN to exempt controls entirely; both accept names or numeric codes. If a key appears in both lists, --debounce-key wins so the key is still debounced.--log-interval, default: 15m).--stats-json) for machine parsing.--list-devices).--ring-buffer-size).--otel-endpoint).intercept | intercept-bounce | uinput).assert_cmd), property tests (proptest), and fuzzing (cargo-fuzz).cargo bench).cargo install intercept-bounce
git clone https://github.com/sinity/intercept-bounce.git
cd intercept-bounce
cargo install --path .
With Nix flakes enabled:
# Build and run directly
nix run github:sinity/intercept-bounce -- --help
# Build the package
nix build github:sinity/intercept-bounce
# Install into your Nix profile
nix profile install github:sinity/intercept-bounce
Since intercept-bounce is not yet packaged in the official nixpkgs repository, the recommended way to install it declaratively on NixOS is by adding this repository as a flake input to your system or Home Manager configuration.
Add the flake input:
Modify your top-level flake.nix (e.g., /etc/nixos/flake.nix or ~/.config/home-manager/flake.nix) to include intercept-bounce:
# In your flake.nix
{
description = "Your system configuration";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; # Or your preferred channel
# Add intercept-bounce flake input
intercept-bounce.url = "github:sinity/intercept-bounce";
# Optional: Pin to a specific commit or tag for stability
# intercept-bounce.inputs.nixpkgs.follows = "nixpkgs"; # Ensure it uses your nixpkgs
# intercept-bounce.rev = "YOUR_COMMIT_HASH_HERE";
};
outputs = { self, nixpkgs, intercept-bounce, ... }@inputs:
let
system = "x86_64-linux"; # Or your system architecture
pkgs = import nixpkgs { inherit system; };
in
{
# Example for configuration.nix:
nixosConfigurations.your-hostname = nixpkgs.lib.nixosSystem {
inherit system;
specialArgs = { inherit inputs; }; # Pass inputs to your config module
modules = [
./configuration.nix # Your main configuration file
# ... other modules
];
};
# Example for Home Manager (within a Home Manager flake):
# homeConfigurations."your-username" = home-manager.lib.homeManagerConfiguration {
# inherit pkgs;
# extraSpecialArgs = { inherit inputs; }; # Pass inputs to your config module
# modules = [
# ./home.nix # Your main Home Manager config file
# # ... other modules
# ];
# };
};
}
Add the package to your configuration:
In the NixOS or Home Manager module file referenced above (e.g., configuration.nix or home.nix), add the package to your desired list, referencing it via the inputs:
# In /etc/nixos/configuration.nix or ~/.config/home-manager/home.nix
{ pkgs, inputs, ... }: # Ensure 'inputs' is available here
{
# Example for NixOS system packages:
environment.systemPackages = with pkgs; [
# ... other packages
# Reference the default package from the intercept-bounce flake input
inputs.intercept-bounce.packages.${pkgs.system}.default
];
# Example for Home Manager packages:
# home.packages = with pkgs; [
# inputs.intercept-bounce.packages.${pkgs.system}.default
# ];
# ... rest of your configuration
}
Rebuild your configuration:
Apply the changes using sudo nixos-rebuild switch (for NixOS) or home-manager switch (for Home Manager).
If you are not using NixOS or prefer to install the tool only for your user, you can install it directly into your Nix profile from the flake URL:
nix profile install github:sinity/intercept-bounce
This command is declarative for your user profile but does not integrate the package into the NixOS system configuration.
You can also build or run the package directly without installing it permanently:
# Build the package (output in ./result)
nix build github:sinity/intercept-bounce
# Run directly without installing
nix run github:sinity/intercept-bounce -- --help
The Nix flake also provides a development shell (nix develop) with necessary tools (see Development).
intercept-bounce is designed to be used within an Interception Tools pipeline.
The most common usage involves capturing events from a physical keyboard, filtering them with intercept-bounce, and creating a new virtual keyboard with the filtered output using uinput.
# Find your keyboard device first (e.g., using 'intercept-bounce --list-devices' or 'intercept -L')
# Example device path: /dev/input/by-id/usb-My_Awesome_Keyboard-event-kbd
# Run the pipeline (requires root/sudo)
sudo sh -c 'intercept -g /dev/input/by-id/usb-My_Awesome_Keyboard-event-kbd \
| intercept-bounce --debounce-time 15ms \
| uinput -d /dev/input/by-id/usb-My_Awesome_Keyboard-event-kbd'
# Note: Using the virtual device created by uinput requires configuration. See the "Integration" section below.
Important:
/dev/input/by-id/usb-My_Awesome_Keyboard-event-kbd with the actual path to your keyboard device. Using paths from /dev/input/by-id/ is recommended as they are stable.intercept -g must be the same as the one provided to uinput -d.Use --debounce-key when you only want chatter protection on a handful of controls. Any keys not listed pass straight through.
sudo sh -c 'intercept -g $DEVNODE \
| intercept-bounce --debounce-time 100ms \
--debounce-key KEY_ENTER \
--debounce-key KEY_SPACE \
| uinput -d $DEVNODE'
You can still supply --ignore-key for the allowlisted set—--debounce-key wins if both flags mention the same code—so it’s safe to keep shared configs that exempt volume wheels without losing an explicit per-key allowlist.
Using udevmon (part of Interception Tools) is the recommended way to manage the pipeline automatically when the device is connected/disconnected. Add a job to your /etc/interception/udevmon.yaml (or user-specific config):
- JOB: intercept -g $DEVNODE | intercept-bounce --debounce-time 15ms | uinput -d $DEVNODE
DEVICE:
LINK: /dev/input/by-id/usb-My_Awesome_Keyboard-event-kbd # <-- Change this!
Remember to replace the LINK with the correct path for your keyboard and restart the udevmon service (sudo systemctl restart interception-udevmon or similar).
Usage: intercept-bounce [OPTIONS]
Options:
-t, --debounce-time <DURATION>
Debounce time threshold (e.g., "25ms", "0.01s"). [default: 25ms]
--near-miss-threshold-time <DURATION>
Threshold for logging "near-miss" events (e.g., "100ms"). [default: 100ms]
--log-interval <DURATION>
Periodically dump statistics to stderr (e.g., "15m", "60s", "0s" to disable). [default: 15m]
--log-all-events
Log details of *every* incoming event ([PASS]/[DROP]).
--log-bounces
Log details of *only dropped* (bounced) key events.
--list-devices
List available input devices and their capabilities (requires root).
--stats-json
Output statistics as JSON format to stderr.
--verbose
Enable verbose logging (DEBUG level).
--ring-buffer-size <SIZE>
Size of the ring buffer for storing recently passed events (0 to disable). [default: 0]
--debounce-key <KEY>
Key codes or names to debounce. When present, only these keys are debounced (all others pass through). Repeat the flag to list multiple keys.
--ignore-key <KEY>
Key codes or names to never debounce unless they are also provided via `--debounce-key`.
--otel-endpoint <URL>
OTLP endpoint URL for exporting traces and metrics (e.g., "http://localhost:4317").
-h, --help
Print help
-V, --version
Print version
For detailed explanations of each option, see man intercept-bounce (if installed) or intercept-bounce --help.
intercept-bounce filters key chatter by remembering the timestamp of the last passed event for each unique combination of key code (e.g., KEY_A) and key state (press=1, release=0).
--debounce-time, the new event is considered a bounce and is dropped.--debounce-time, or if the event has a different key code or state, the event is passed through, and its timestamp becomes the new "last passed" time for that specific key/state.This feature helps diagnose keys with inconsistent timing just outside the debounce window.
--near-miss-threshold-time, the event is recorded as a "near-miss" in the statistics.--debounce-time needs adjustment.intercept-bounce collects detailed statistics, printed to stderr on exit (Ctrl+C) or periodically (--log-interval).
--stats-json)Provides a machine-readable JSON object containing all the information from the human-readable report, plus raw timing data arrays and detailed histogram bucket counts. Key top-level fields:
report_type: "Cumulative" or "Periodic".runtime_us: Total runtime (cumulative only).debounce_time_us, near_miss_threshold_us, etc.).key_events_processed, key_events_passed, key_events_dropped).overall_bounce_histogram, overall_near_miss_histogram: Detailed histogram objects.per_key_stats: Array of objects per key, including detailed stats per state (press/release/repeat) with sampled timings_us, min_us/max_us/avg_us, and a bounce_histogram.per_key_near_miss_stats: Array of objects per key/state with sampled timings_us, summary fields, and a near_miss_histogram.
Sample arrays retain only the most recent timings to avoid unbounded memory growth.Refer to the StatsCollector::print_stats_json implementation or the man page for the exact structure.
Histograms show the distribution of timings (bounce or near-miss) in milliseconds across predefined buckets (e.g., <1ms, 1-2ms, 2-4ms, ..., >=128ms). They help visualize the typical duration of bounces or near-misses. The average timing is also calculated.
Logging messages are printed to stderr.
--log-all-events: Logs [PASS] or [DROP] for almost every event, showing type, code, value, key name, and timing info. (Skips EV_SYN/EV_MSC for clarity). Performance impact!--log-bounces: Logs only [DROP] messages for key events, including bounce time. Less verbose than --log-all-events.--verbose: Enables DEBUG level logging, showing internal state, thread activity, etc. Sets default filter to intercept_bounce=debug if RUST_LOG is not set.RUST_LOG Environment Variable: Provides fine-grained control using the tracing_subscriber::EnvFilter format (e.g., RUST_LOG=info, RUST_LOG=intercept_bounce=trace, RUST_LOG=warn,intercept_bounce::filter=debug). Overrides --verbose.Performance Note: High logging verbosity (--log-all-events, RUST_LOG=trace) can significantly impact performance and may cause log messages to be dropped if the logger thread cannot keep up. A warning ("Logger channel full...") will be printed if this happens.
intercept -g <device> | intercept-bounce [OPTIONS] | uinput -d <device>.uinput creates a new virtual input device (e.g., /dev/input/eventX). Your Desktop Environment (Xorg/Wayland) must be configured to use this new virtual device. The original physical device still emits raw events. Configuration methods vary; sometimes automatic, sometimes requiring DE-specific settings (e.g., Xorg InputClass sections, Wayland compositor settings). Use tools like libinput list-devices to identify the virtual device (often contains "Uinput" or "intercept-bounce" in the name).intercept-bounce under Wayland might require specific compositor support or configuration to recognize and prioritize the uinput virtual device.intercept and uinput requires root privileges or specific group memberships (input group for reading /dev/input/event*, potentially custom udev rules for /dev/uinput write access). Using sudo sh -c '...' for the whole pipeline is common. intercept-bounce --list-devices also needs read access.intercept -g and uinput -d is identical and correct. Use stable paths from /dev/input/by-id/. Use intercept-bounce --list-devices or intercept -L to find devices.udevmon status and logs (sudo systemctl status interception-udevmon, journalctl -u interception-udevmon).intercept-bounce with --verbose or --log-all-events to check processing and stderr for errors.uinput.--debounce-time.--debounce-time. Use --log-bounces or statistics (bounce timings/histograms) with a low debounce time first to measure the chatter duration, then set the time slightly higher.2> log.txt) or use udevmon.cargo build
cargo build --release
# Run all tests (unit, integration, property)
cargo test --all-targets --all-features
# Run specific integration test
cargo test --test sanity -- --nocapture drops_bounce
# Run property tests only
cargo test --test property_tests
cargo bench
# Check formatting
cargo fmt --check
# Apply formatting
cargo fmt
# Run clippy lints
cargo clippy --all-targets --all-features -- -D warnings
Common development tasks are available via a separate xtask crate. Run them using cargo run --package xtask -- <command>:
# Generate man page and shell completions (outputs to docs/)
cargo run --package xtask -- generate-docs
# Run checks (equivalent to cargo check)
cargo run --package xtask -- check
# Run tests (equivalent to cargo test)
cargo run --package xtask -- test
# Run clippy (equivalent to cargo clippy -- -D warnings)
cargo run --package xtask -- clippy
# Check formatting (equivalent to cargo fmt --check)
cargo run --package xtask -- fmt-check
If you have Nix installed with flakes enabled, use nix develop to enter a shell with all necessary development tools (Rust toolchain, cargo-fuzz, cargo-audit, interception-tools, man, etc.) and useful aliases (ct for test, cl for clippy, cf for fmt, xt for xtask).
Requires cargo-fuzz:
cargo install cargo-fuzz
# List fuzz targets
cargo fuzz list
# Run the stats fuzzer
cargo fuzz run fuzz_target_stats
Contributions are welcome! Please feel free to open an issue or submit a pull request on GitHub.
Licensed under either of
at your option.