intercept_bounce/
cli.rs

1use clap::{ArgAction, Parser};
2use std::time::Duration;
3
4/// An Interception Tools filter to eliminate keyboard chatter (switch bounce).
5/// Reads Linux input events from stdin, filters rapid duplicate key events,
6/// and writes the filtered events to stdout. Statistics are printed to stderr on exit.
7#[derive(Parser, Debug, Clone)]
8#[command(
9    author,
10    version,
11    about,
12    long_about = "An Interception Tools filter to eliminate keyboard chatter (switch bounce).\n\
13Reads Linux input events from stdin, filters rapid duplicate key events, and writes the filtered events to stdout.\n\
14Statistics are printed to stderr on exit.\n\
15\n\
16EXAMPLES:\n\
17  # Basic filtering (15ms window):\n\
18  sudo sh -c 'intercept -g /dev/input/by-id/your-keyboard-event-device | intercept-bounce --debounce-time 15ms | uinput -d /dev/input/by-id/your-keyboard-event-device'\n\
19\n\
20  # Filtering with bounce logging:\n\
21  sudo sh -c 'intercept -g ... | intercept-bounce --debounce-time 20ms --log-bounces | uinput -d ...'\n\
22\n\
23  # Debugging - log all events (no filtering):\n\
24  sudo sh -c 'intercept -g ... | intercept-bounce --debounce-time 0ms --log-all-events | uinput -d ...'\n\
25\n\
26  # Periodic stats dump:\n\
27  sudo sh -c 'intercept -g ... | intercept-bounce --log-interval 60s | uinput -d ...'\n\
28\n\
29  # udevmon integration (YAML):\n\
30  - JOB: \"intercept -g $DEVNODE | intercept-bounce | uinput -d $DEVNODE\"\n\
31    DEVICE:\n\
32      LINK: \"/dev/input/by-id/usb-Your_Keyboard_Name-event-kbd\" # Replace this!\n\
33\n\
34See README for more details and advanced usage."
35)]
36pub struct Args {
37    /// Debounce time threshold (milliseconds). Duplicate key events (same keycode and value)
38    /// occurring faster than this threshold are discarded. (Default: 25ms).
39    /// The "value" refers to the state of the key: `1` for press, `0` for release, `2` for repeat.
40    /// Only press and release events are debounced. Accepts values like "10ms", "0.5s".
41    #[arg(short = 't', long, default_value = "25ms", value_parser = humantime::parse_duration)]
42    pub debounce_time: Duration,
43
44    // --- Logging & Statistics Options ---
45    /// Threshold for logging "near-miss" events. Passed key events
46    /// occurring within this time of the previous passed event are logged/counted. (Default: 100ms)
47    /// Accepts values like "100ms", "0.1s".
48    #[arg(long, default_value = "100ms", value_parser = humantime::parse_duration)]
49    pub near_miss_threshold_time: Duration,
50
51    /// Periodically dump statistics to stderr. (Default: 15m).
52    /// Set to "0" to disable periodic dumps. Accepts values like "60s", "15m", "1h".
53    #[arg(long, default_value = "15m", value_parser = humantime::parse_duration)]
54    pub log_interval: Duration,
55
56    /// Log details of *every* incoming event to stderr ([PASS] or [DROP]).
57    #[arg(long, action = clap::ArgAction::SetTrue)]
58    pub log_all_events: bool,
59
60    /// Log details of *only dropped* (bounced) key events to stderr.
61    #[arg(long, action = clap::ArgAction::SetTrue)]
62    pub log_bounces: bool,
63
64    /// List available input devices and their capabilities (requires root).
65    #[arg(long, action = clap::ArgAction::SetTrue)]
66    pub list_devices: bool,
67
68    /// Output statistics as JSON format to stderr on exit and periodic dump.
69    #[arg(long, action = clap::ArgAction::SetTrue)]
70    pub stats_json: bool,
71
72    /// Enable verbose logging (internal state, thread startup, etc).
73    #[arg(long, action = clap::ArgAction::SetTrue)]
74    pub verbose: bool,
75
76    /// Size of the ring buffer for storing recently passed events (for debugging).
77    /// Set to 0 to disable. (Default: 0).
78    #[arg(long, default_value = "0")]
79    pub ring_buffer_size: usize,
80
81    /// Key codes or names to debounce. When present, only these keys are debounced
82    /// (all others pass through). Takes precedence over `--ignore-key`. Example:
83    /// `--debounce-key KEY_ENTER` (repeat flag for multiple keys).
84    #[arg(long = "debounce-key", value_name = "KEY", action = ArgAction::Append, value_parser = parse_key_identifier)]
85    pub debounce_keys: Vec<u16>,
86
87    /// Key codes or names to ignore (never debounce) unless they also appear in
88    /// `--debounce-key`. Example: `--ignore-key 114` or `--ignore-key KEY_VOLUMEDOWN`.
89    #[arg(long = "ignore-key", value_name = "KEY", action = ArgAction::Append, value_parser = parse_key_identifier)]
90    pub ignore_keys: Vec<u16>,
91
92    // --- OpenTelemetry Export ---
93    /// OTLP endpoint URL for exporting traces and metrics (e.g., "http://localhost:4317").
94    #[arg(long)]
95    pub otel_endpoint: Option<String>,
96}
97
98pub fn parse_args() -> Args {
99    Args::parse()
100}
101
102fn parse_key_identifier(value: &str) -> Result<u16, String> {
103    crate::filter::keynames::resolve_key_code(value).ok_or_else(|| {
104        format!(
105            "Unknown key identifier '{value}'. Provide either a numeric code or a symbolic name like KEY_VOLUMEDOWN"
106        )
107    })
108}