1use clap::{ArgAction, Parser};
2use std::time::Duration;
3
4#[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 #[arg(short = 't', long, default_value = "25ms", value_parser = humantime::parse_duration)]
42 pub debounce_time: Duration,
43
44 #[arg(long, default_value = "100ms", value_parser = humantime::parse_duration)]
49 pub near_miss_threshold_time: Duration,
50
51 #[arg(long, default_value = "15m", value_parser = humantime::parse_duration)]
54 pub log_interval: Duration,
55
56 #[arg(long, action = clap::ArgAction::SetTrue)]
58 pub log_all_events: bool,
59
60 #[arg(long, action = clap::ArgAction::SetTrue)]
62 pub log_bounces: bool,
63
64 #[arg(long, action = clap::ArgAction::SetTrue)]
66 pub list_devices: bool,
67
68 #[arg(long, action = clap::ArgAction::SetTrue)]
70 pub stats_json: bool,
71
72 #[arg(long, action = clap::ArgAction::SetTrue)]
74 pub verbose: bool,
75
76 #[arg(long, default_value = "0")]
79 pub ring_buffer_size: usize,
80
81 #[arg(long = "debounce-key", value_name = "KEY", action = ArgAction::Append, value_parser = parse_key_identifier)]
85 pub debounce_keys: Vec<u16>,
86
87 #[arg(long = "ignore-key", value_name = "KEY", action = ArgAction::Append, value_parser = parse_key_identifier)]
90 pub ignore_keys: Vec<u16>,
91
92 #[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}