From d2772aba9403d6eff17c69f7d4659352fc1106d0 Mon Sep 17 00:00:00 2001 From: Sean Young Date: Tue, 7 May 2024 20:18:37 +0100 Subject: [PATCH] Add list and load subcommands Signed-off-by: Sean Young --- README.md | 89 +++++--- src/bin/cir.rs | 85 ++++++-- src/bin/commands/config.rs | 432 ++++++++++++------------------------- src/bin/commands/list.rs | 262 ++++++++++++++++++++++ src/bin/commands/mod.rs | 2 + 5 files changed, 530 insertions(+), 340 deletions(-) create mode 100644 src/bin/commands/list.rs diff --git a/README.md b/README.md index 2c736cf..82575ff 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,29 @@ express [any IR protocol](http://hifi-remote.com/wiki/index.php/DecodeIR). We can parse IRP and compile a decoder to BPF using LLVM. So, any protocol can be supported directly. -## Listing IR devices (cir config) +## List devices (cir list) -This is the cir equivalent of both `ir-keytable` with no arguments and `ir-ctl -f`. +This gives you a list of infrared and CEC devices on your Linux system. It is the cir equivalent of both +`ir-keytable` with no arguments combined with the lirc features from `ir-ctl -f`. +```bash +cir list +``` +This gives you a list of the all the rc devices on the system. +``` +rc0: + Device Name : Media Center Ed. eHome Infrared Remote Transceiver (1784:0008) + Driver : mceusb + Default Keymap : rc-rc6-mce + Input Device : /dev/input/event12 + Input properties : Permission denied (os error 13) + LIRC Device : /dev/lirc0 + LIRC Features : Permission denied (os error 13) + Supported Protocols : rc-5 nec rc-6 jvc sony rc-5-sz sanyo sharp mce_kbd xmp imon rc-mm + Enabled Protocols : rc-6 +``` +Not all information is available, unless you run it as root. ``` -$ cir config rc0: Device Name : Media Center Ed. eHome Infrared Remote Transceiver (1784:0008) Driver : mceusb @@ -57,28 +74,29 @@ rc0: If you have a `.lircd.conf` file or `.toml` keymap, you can transmit with the following command: -``` -$ cir transmit keymap RM-Y173.lircd.conf KEY_CHANNELUP -info: carrier: 38000Hz -info: rawir: +2485 -527 +656 -527 +656 -527 +656 -527 +656 -527 +1262 -527 +656 -527 +656 -527 +1262 -527 +656 -527 +656 -527 +656 -527 +656 -26274 +```bash +cir transmit keymap RM-Y173.lircd.conf KEY_CHANNELUP ``` Alternatively, you can send raw IR directly like so: -``` -$ cir transmit rawir '+9000 -4500 +560' +```bash +cir transmit rawir '+9000 -4500 +560' ``` You can also send files or linux kernel scancodes, using the same options like `ir-ctl`. This supports mode2 files or raw IR files. -``` -$ cir transmit rawir -s input-file -S nec:0xcafe +```bash +cir transmit rawir -s input-file -S nec:0xcafe ``` You can send pronto codes: -``` -$ cir transmit pronto '5000 0073 0000 0001 0001 0001' +```bash +cir transmit pronto '5000 0073 0000 0001 0001 0001' ``` Lastly you use IRP notation and set the parameters. This is great for experimenting with IRP; use the `--dry-run` (`-n`) to avoid sending. +```bash +cir transmit irp -n -fF=2 '{40k,600}<1,-1|2,-1>(4,-1,F:8,^45m)[F:0..255]' +``` +This will give the following output: ``` -$ cir transmit irp -n -fF=2 '{40k,600}<1,-1|2,-1>(4,-1,F:8,^45m)[F:0..255]' info: carrier: 40000Hz info: rawir: +2400 -600 +600 -600 +1200 -600 +600 -600 +600 -600 +600 -600 +600 -600 +600 -600 +600 -32400 ``` @@ -88,47 +106,54 @@ info: rawir: +2400 -600 +600 -600 +1200 -600 +600 -600 +600 -600 +600 -600 +600 Use this if have a `.lircd.conf` file or `.toml` keymap, and want to decode the IR. This does not change any configation. -``` -$ cir decode keymap foo.lircd.conf +```bash +cir decode keymap foo.lircd.conf ``` This will infrared from the first lirc device. You can also decode IR on the command line or a file. -``` -$ cir decode keymap foo.lircd.conf -r '+9000 -4500 +560' +```bash +cir decode keymap foo.lircd.conf -r '+9000 -4500 +560' ``` or -``` -$ cir decode keymap foo.lircd.conf -f input-file +```bash +cir decode keymap foo.lircd.conf -f input-file ``` If you wish to decode using IRP Notation that is possible too: +```bash +cir decode irp '{40k,600}<1,-1|2,-1>(4,-1,F:8,^45m)[F:0..255]' ``` -$ cir decode irp '{40k,600}<1,-1|2,-1>(4,-1,F:8,^45m)[F:0..255]' -``` -Like above the input can be from a lirc device (optionally specify the device with +Like above the input can be from a lirc device (optionally specify the device with `-d /dev/lirc1` or `-s rc`), on the command line (`-r '+100 -200 +100'`) or a file (`-f filename`). -## Configuration (cir config -w) +## Loading of keymaps -This is the cir equivalent of `ir-keytable -w`. +This is the cir equivalent of `ir-keytable -w`, however cir can not just load keymaps, it can +also load `.lircd.conf` files. -``` -$ cir config -s rc0 -w foo.lircd.conf +```bash +cir load -s rc0 foo.lircd.conf ``` This will generate a BPF decoder for `foo.lircd.conf` and load it. -On startup, `ir-keytable -a -s rc0` read the correct keymap from `/etc/rc_maps.cfg`. +On startup, `ir-keytable -a -s rc0` read the correct keymap from `/etc/rc_maps.cfg`. +```bash +cir auto -s rc0 ``` -$ cir auto -s rc0 +## Configuration (cir config) + +`cir config` is usually not needed, this for tweaking things like auto-repeat or lirc timeout. For example: +```bash +cir config -P 125 -D 500 ``` ## Test configuration (cir test) This is the cir equivalent of `ir-keytable -t` -``` -$ cir test +```bash +cir test ``` ## Status @@ -150,4 +175,4 @@ cargo install --git https://github.com/seanyoung/cir cir - The IRP encoder and decoder is compared against IrpTransmogrifier with a large set of inputs. - The parsing, encoding and decoding of lircd.conf files is compared against lirc (see liblircd) - The encoding of linux protocols is compared against ir-ctl (see libirctl) -- There are more tests \ No newline at end of file +- There are many more tests diff --git a/src/bin/cir.rs b/src/bin/cir.rs index f53e306..9d6ac24 100644 --- a/src/bin/cir.rs +++ b/src/bin/cir.rs @@ -36,11 +36,17 @@ enum Commands { Transmit(Transmit), #[cfg(target_os = "linux")] #[command(about = "List IR and CEC devices")] - Config(Config), + List(List), + #[cfg(target_os = "linux")] + #[command(about = "Load keymaps or lircd.conf remotes")] + Load(Load), #[cfg(target_os = "linux")] #[command(about = "Receive IR and print to stdout")] Test(Test), #[cfg(target_os = "linux")] + #[command(about = "Configure IR and CEC devices")] + Config(Config), + #[cfg(target_os = "linux")] #[command(about = "Auto-load keymaps based on configuration")] Auto(Auto), } @@ -120,6 +126,22 @@ struct DecodeOptions { #[arg(long = "save-dfa", global = true, help_heading = "DECODING")] save_dfa: bool, } + +#[derive(Args)] +struct BpfDecodeOptions { + /// Save the LLVM IR + #[arg(long = "save-llvm-ir", help_heading = "DECODING")] + save_llvm_ir: bool, + + /// Save the Assembly + #[arg(long = "save-asm", help_heading = "DECODING")] + save_assembly: bool, + + /// Save the Object + #[arg(long = "save-object", help_heading = "DECODING")] + save_object: bool, +} + #[derive(Subcommand)] enum DecodeCommands { #[command(about = "Decode using IRP Notation")] @@ -169,7 +191,37 @@ struct RcDevice { #[cfg(target_os = "linux")] #[derive(Args)] -struct Config { +struct Load { + #[cfg(target_os = "linux")] + #[clap(flatten)] + device: RcDevice, + + /// Set receiving timeout + #[arg(long = "timeout", short = 't')] + timeout: Option, + + /// Sets the delay before repeating a keystroke + #[arg(long = "delay", short = 'D', name = "DELAY")] + delay: Option, + + /// Sets the period before repeating a keystroke + #[arg(long = "period", short = 'P', name = "PERIOD")] + period: Option, + + /// Load toml or lircd.conf keymap + #[arg(name = "KEYMAP")] + keymaps: Vec, + + #[clap(flatten)] + options: DecodeOptions, + + #[clap(flatten)] + bpf_options: BpfDecodeOptions, +} + +#[cfg(target_os = "linux")] +#[derive(Args)] +struct List { #[cfg(target_os = "linux")] #[clap(flatten)] device: RcDevice, @@ -177,6 +229,14 @@ struct Config { /// Display the scancode to keycode mapping #[arg(long = "read-mapping", short = 'm')] mapping: bool, +} + +#[cfg(target_os = "linux")] +#[derive(Args)] +struct Config { + #[cfg(target_os = "linux")] + #[clap(flatten)] + device: RcDevice, /// Clear existing configuration #[arg(long = "clear", short = 'c')] @@ -194,10 +254,6 @@ struct Config { #[arg(long = "period", short = 'P', name = "PERIOD")] period: Option, - /// Load toml or lircd.conf keymap - #[arg(long = "keymap", short = 'w', name = "KEYMAP")] - keymaps: Vec, - // TODO: // --irp '{}<>()[]' // set scancode (like ir-keytable --set-key/-k) @@ -205,17 +261,8 @@ struct Config { #[clap(flatten)] options: DecodeOptions, - /// Save the LLVM IR - #[arg(long = "save-llvm-ir", help_heading = "DECODING")] - save_llvm_ir: bool, - - /// Save the Assembly - #[arg(long = "save-asm", help_heading = "DECODING")] - save_assembly: bool, - - /// Save the Object - #[arg(long = "save-object", help_heading = "DECODING")] - save_object: bool, + #[clap(flatten)] + bpf_options: BpfDecodeOptions, } #[cfg(target_os = "linux")] @@ -551,6 +598,10 @@ fn main() { }, Commands::Transmit(transmit) => commands::transmit::transmit(transmit), #[cfg(target_os = "linux")] + Commands::List(list) => commands::list::list(list), + #[cfg(target_os = "linux")] + Commands::Load(load) => commands::config::load(load), + #[cfg(target_os = "linux")] Commands::Config(config) => commands::config::config(config), #[cfg(target_os = "linux")] Commands::Test(test) => commands::test::test(test), diff --git a/src/bin/commands/config.rs b/src/bin/commands/config.rs index 5edfecc..ac2ffe2 100644 --- a/src/bin/commands/config.rs +++ b/src/bin/commands/config.rs @@ -3,11 +3,10 @@ use cir::{ lirc::Lirc, lircd_conf, rc_maps::parse_rc_maps_file, - rcdev::{self, enumerate_rc_dev, Rcdev}, + rcdev::{enumerate_rc_dev, Rcdev}, }; -use evdev::{Device, Key}; +use evdev::Key; use irp::Options; -use itertools::Itertools; use log::debug; use std::{ path::{Path, PathBuf}, @@ -15,26 +14,86 @@ use std::{ }; pub fn config(config: &crate::Config) { - if !config.clear - && config.keymaps.is_empty() - && config.delay.is_none() - && config.period.is_none() - { - match rcdev::enumerate_rc_dev() { - Ok(list) => { - print_rc_dev(&list, config); - return; + let mut rcdev = find_devices(&config.device, Purpose::Receive); + + if config.delay.is_some() || config.period.is_some() { + let inputdev = match rcdev.open_input() { + Ok(dev) => dev, + Err(e) => { + eprintln!("error: input: {e}"); + std::process::exit(1); + } + }; + + let mut repeat = inputdev + .get_auto_repeat() + .expect("auto repeat is supported"); + + if let Some(delay) = config.delay { + repeat.delay = delay; + } + + if let Some(period) = config.period { + repeat.period = period; + } + + if let Err(e) = inputdev.update_auto_repeat(&repeat) { + eprintln!("error: failed to update autorepeat: {e}"); + std::process::exit(1); + } + } + + if config.clear { + if let Err(e) = rcdev.clear_scancodes() { + eprintln!("error: input: {e}"); + std::process::exit(1); + } + + if let Some(lircdev) = &rcdev.lircdev { + let lirc = match Lirc::open(PathBuf::from(lircdev)) { + Ok(fd) => fd, + Err(e) => { + eprintln!("error: {lircdev}: {e}"); + std::process::exit(1); + } + }; + + if let Err(e) = lirc.clear_bpf() { + eprintln!("error: {lircdev}: {e}"); + std::process::exit(1); } - Err(err) => { - eprintln!("error: {err}"); + } + } + + if let Some(timeout) = config.timeout { + if let Some(lircdev) = &rcdev.lircdev { + let mut lirc = match Lirc::open(PathBuf::from(lircdev)) { + Ok(fd) => fd, + Err(e) => { + eprintln!("error: {lircdev}: {e}"); + std::process::exit(1); + } + }; + + if let Err(e) = lirc.set_timeout(timeout) { + eprintln!("error: {lircdev}: {e}"); std::process::exit(1); } + } else { + eprintln!("error: {}: no lirc device", rcdev.name); + std::process::exit(1); } } - let mut rcdev = find_devices(&config.device, Purpose::Receive); + // TODO + // set scancode + // load irp +} - if config.delay.is_some() || config.period.is_some() { +pub fn load(load: &crate::Load) { + let mut rcdev = find_devices(&load.device, Purpose::Receive); + + if load.delay.is_some() || load.period.is_some() { let inputdev = match rcdev.open_input() { Ok(dev) => dev, Err(e) => { @@ -47,11 +106,11 @@ pub fn config(config: &crate::Config) { .get_auto_repeat() .expect("auto repeat is supported"); - if let Some(delay) = config.delay { + if let Some(delay) = load.delay { repeat.delay = delay; } - if let Some(period) = config.period { + if let Some(period) = load.period { repeat.period = period; } @@ -61,13 +120,20 @@ pub fn config(config: &crate::Config) { } } - load_keymaps(config.clear, &mut rcdev, Some(config), &config.keymaps); + load_keymaps( + true, + &mut rcdev, + Some(&load.options), + Some(&load.bpf_options), + &load.keymaps, + ); } fn load_keymaps( clear: bool, rcdev: &mut Rcdev, - config: Option<&crate::Config>, + decode_options: Option<&crate::DecodeOptions>, + bpf_decode_options: Option<&crate::BpfDecodeOptions>, keymaps: &[PathBuf], ) { let mut protocols = Vec::new(); @@ -102,9 +168,22 @@ fn load_keymaps( for keymap_filename in keymaps.iter() { if keymap_filename.to_string_lossy().ends_with(".lircd.conf") { - load_lircd(rcdev, &chdev, config, keymap_filename); + load_lircd( + rcdev, + &chdev, + decode_options, + bpf_decode_options, + keymap_filename, + ); } else { - load_keymap(rcdev, &chdev, config, keymap_filename, &mut protocols); + load_keymap( + rcdev, + &chdev, + decode_options, + bpf_decode_options, + keymap_filename, + &mut protocols, + ); } } @@ -118,29 +197,37 @@ pub fn auto(auto: &crate::Auto) { let mut rcdev = find_devices(&auto.device, Purpose::Receive); if rcdev.inputdev.is_none() { - eprintln!("error: input device is missing"); + eprintln!("error: {}: input device is missing", rcdev.name); std::process::exit(1); } match parse_rc_maps_file(&auto.cfgfile) { Ok(keymaps) => { - for map in keymaps { - if map.matches(&rcdev) { - load_keymaps(true, &mut rcdev, None, &[PathBuf::from(map.file)]); - return; - } + let keymaps: Vec<_> = keymaps + .iter() + .filter_map(|map| { + if map.matches(&rcdev) { + Some(PathBuf::from(&map.file)) + } else { + None + } + }) + .collect(); + + if keymaps.is_empty() { + eprintln!( + "{}: error: no match for driver ‘{}’ and default keymap ‘{}’", + auto.cfgfile.display(), + rcdev.driver, + rcdev.default_keymap + ); + std::process::exit(2); + } else { + load_keymaps(true, &mut rcdev, None, None, &keymaps); } - - eprintln!( - "{}: error: no match for driver ‘{}’ and default keymap ‘{}’", - auto.cfgfile.display(), - rcdev.driver, - rcdev.default_keymap - ); - std::process::exit(2); } Err(e) => { - eprintln!("{e}"); + eprintln!("error: {}: {e}", auto.cfgfile.display()); std::process::exit(1); } } @@ -149,7 +236,8 @@ pub fn auto(auto: &crate::Auto) { fn load_keymap( rcdev: &mut Rcdev, chdev: &Option, - config: Option<&crate::Config>, + decode_options: Option<&crate::DecodeOptions>, + bpf_decode_options: Option<&crate::BpfDecodeOptions>, keymap_filename: &Path, protocols: &mut Vec, ) { @@ -226,9 +314,12 @@ fn load_keymap( ..Default::default() }; - if let Some(decode) = &config { - options.nfa = decode.options.save_nfa; - options.dfa = decode.options.save_dfa; + if let Some(decode) = &decode_options { + options.nfa = decode.save_nfa; + options.dfa = decode.save_dfa; + } + + if let Some(decode) = &bpf_decode_options { options.llvm_ir = decode.save_llvm_ir; options.assembly = decode.save_assembly; options.object = decode.save_object; @@ -262,7 +353,8 @@ fn load_keymap( fn load_lircd( rcdev: &mut Rcdev, chdev: &Option, - config: Option<&crate::Config>, + decode_options: Option<&crate::DecodeOptions>, + bpf_decode_options: Option<&crate::BpfDecodeOptions>, keymap_filename: &Path, ) { let remotes = match lircd_conf::parse(keymap_filename) { @@ -295,9 +387,13 @@ fn load_lircd( let mut options = remote.default_options(None, None, max_gap); options.repeat_mask = remote.repeat_mask; - if let Some(decode) = &config { - options.nfa = decode.options.save_nfa; - options.dfa = decode.options.save_dfa; + + if let Some(decode) = &decode_options { + options.nfa = decode.save_nfa; + options.dfa = decode.save_dfa; + } + + if let Some(decode) = &bpf_decode_options { options.llvm_ir = decode.save_llvm_ir; options.assembly = decode.save_assembly; options.object = decode.save_object; @@ -353,252 +449,6 @@ fn load_lircd( } } -fn print_rc_dev(list: &[rcdev::Rcdev], config: &crate::Config) { - let mut printed = 0; - - for rcdev in list { - if let Some(needlelircdev) = &config.device.lirc_dev { - if let Some(lircdev) = &rcdev.lircdev { - if lircdev == needlelircdev { - // ok - } else { - continue; - } - } else { - continue; - } - } else if let Some(needlercdev) = &config.device.rc_dev { - if needlercdev != &rcdev.name { - continue; - } - } - - println!("{}:", rcdev.name); - - println!("\tDevice Name\t\t: {}", rcdev.device_name); - println!("\tDriver\t\t\t: {}", rcdev.driver); - if !rcdev.default_keymap.is_empty() { - println!("\tDefault Keymap\t\t: {}", rcdev.default_keymap); - } - if let Some(inputdev) = &rcdev.inputdev { - println!("\tInput Device\t\t: {inputdev}"); - - match Device::open(inputdev) { - Ok(inputdev) => { - let id = inputdev.input_id(); - - println!("\tBus\t\t\t: {}", id.bus_type()); - - println!( - "\tVendor/product\t\t: {:04x}:{:04x} version 0x{:04x}", - id.vendor(), - id.product(), - id.version() - ); - - if let Some(repeat) = inputdev.get_auto_repeat() { - println!( - "\tRepeat\t\t\t: delay {} ms, period {} ms", - repeat.delay, repeat.period - ); - } - - if config.mapping { - let mut index = 0; - - loop { - match inputdev.get_scancode_by_index(index) { - Ok((keycode, scancode)) => { - match scancode.len() { - 8 => { - // kernel v5.7 and later give 64 bit scancodes - let scancode = - u64::from_ne_bytes(scancode.try_into().unwrap()); - let keycode = evdev::Key::new(keycode as u16); - - println!( - "\tScancode\t\t: 0x{scancode:08x} => {keycode:?}" - ); - } - 4 => { - // kernel v5.6 and earlier give 32 bit scancodes - let scancode = - u32::from_ne_bytes(scancode.try_into().unwrap()); - let keycode = evdev::Key::new(keycode as u16); - - println!( - "\tScancode\t\t: 0x{scancode:08x} => {keycode:?}" - ) - } - len => panic!( - "scancode should be 4 or 8 bytes long, not {len}" - ), - } - - index += 1; - } - Err(err) if err.kind() == std::io::ErrorKind::InvalidInput => break, - Err(err) => { - eprintln!("error: {err}"); - std::process::exit(1); - } - } - } - } - } - Err(err) => { - println!("\tInput properties\t: {err}"); - } - }; - } - if let Some(lircdev) = &rcdev.lircdev { - println!("\tLIRC Device\t\t: {lircdev}"); - - match Lirc::open(PathBuf::from(lircdev)) { - Ok(mut lircdev) => { - if lircdev.can_receive_raw() { - println!("\tLIRC Receiver\t\t: raw receiver"); - - if lircdev.can_get_rec_resolution() { - println!( - "\tLIRC Resolution\t\t: {}", - match lircdev.receiver_resolution() { - Ok(res) => format!("{res} microseconds"), - Err(err) => err.to_string(), - } - ); - } else { - println!("\tLIRC Resolution\t\t: unknown"); - } - - println!( - "\tLIRC Timeout\t\t: {}", - match lircdev.get_timeout() { - Ok(timeout) => format!("{timeout} microseconds"), - Err(err) => err.to_string(), - } - ); - - if lircdev.can_set_timeout() { - println!( - "\tLIRC Timeout Range\t: {}", - match lircdev.get_min_max_timeout() { - Ok(range) => - format!("{} to {} microseconds", range.start, range.end), - Err(err) => err.to_string(), - } - ); - } else { - println!("\tLIRC Receiver Timeout Range\t: none"); - } - - println!( - "\tLIRC Wideband Receiver\t: {}", - if lircdev.can_use_wideband_receiver() { - "yes" - } else { - "no" - } - ); - - println!( - "\tLIRC Measure Carrier\t: {}", - if lircdev.can_measure_carrier() { - "yes" - } else { - "no" - } - ); - } else if lircdev.can_receive_scancodes() { - println!("\tLIRC Receiver\t\t: scancode"); - } else { - println!("\tLIRC Receiver\t\t: none"); - } - - if lircdev.can_send() { - println!("\tLIRC Transmitter\t: yes"); - - println!( - "\tLIRC Set Tx Carrier\t: {}", - if lircdev.can_set_send_carrier() { - "yes" - } else { - "no" - } - ); - - println!( - "\tLIRC Set Tx Duty Cycle\t: {}", - if lircdev.can_set_send_duty_cycle() { - "yes" - } else { - "no" - } - ); - - if lircdev.can_set_send_transmitter_mask() { - println!( - "\tLIRC Transmitters\t: {}", - match lircdev.num_transmitters() { - Ok(count) => format!("{count}"), - Err(err) => err.to_string(), - } - ); - } else { - println!("\tLIRC Transmitters\t: unknown"); - } - } else { - println!("\tLIRC Transmitter\t: no"); - } - - if lircdev.can_receive_raw() { - match lircdev.query_bpf() { - Ok(links) => { - println!("\tBPF protocols\t\t: {}", links.iter().join(" ")); - } - Err(err) => { - println!("\tBPF protocols\t\t: {err}") - } - } - } - } - Err(err) => { - println!("\tLIRC Features\t\t: {err}"); - } - } - } - - if !rcdev.supported_protocols.is_empty() { - println!( - "\tSupported Protocols\t: {}", - rcdev.supported_protocols.join(" ") - ); - - println!( - "\tEnabled Protocols\t: {}", - rcdev - .enabled_protocols - .iter() - .map(|p| &rcdev.supported_protocols[*p]) - .join(" ") - ); - } - - printed += 1; - } - - if printed == 0 { - if let Some(lircdev) = &config.device.lirc_dev { - eprintln!("error: no lirc device named {lircdev}"); - } else if let Some(rcdev) = &config.device.rc_dev { - eprintln!("error: no rc device named {rcdev}"); - } else { - eprintln!("error: no devices found"); - } - std::process::exit(1); - } -} - pub enum Purpose { Receive, Transmit, diff --git a/src/bin/commands/list.rs b/src/bin/commands/list.rs new file mode 100644 index 0000000..5f19166 --- /dev/null +++ b/src/bin/commands/list.rs @@ -0,0 +1,262 @@ +use cir::{lirc::Lirc, rcdev}; +use evdev::Device; +use itertools::Itertools; +use std::path::PathBuf; + +pub fn list(args: &crate::List) { + match rcdev::enumerate_rc_dev() { + Ok(list) => { + print_rc_dev(&list, args); + } + Err(err) => { + eprintln!("error: {err}"); + std::process::exit(1); + } + } +} + +fn print_rc_dev(list: &[rcdev::Rcdev], config: &crate::List) { + let mut printed = 0; + + for rcdev in list { + if let Some(needlelircdev) = &config.device.lirc_dev { + if let Some(lircdev) = &rcdev.lircdev { + if lircdev == needlelircdev { + // ok + } else { + continue; + } + } else { + continue; + } + } else if let Some(needlercdev) = &config.device.rc_dev { + if needlercdev != &rcdev.name { + continue; + } + } + + println!("{}:", rcdev.name); + + println!("\tDevice Name\t\t: {}", rcdev.device_name); + println!("\tDriver\t\t\t: {}", rcdev.driver); + if !rcdev.default_keymap.is_empty() { + println!("\tDefault Keymap\t\t: {}", rcdev.default_keymap); + } + if let Some(inputdev) = &rcdev.inputdev { + println!("\tInput Device\t\t: {inputdev}"); + + match Device::open(inputdev) { + Ok(inputdev) => { + let id = inputdev.input_id(); + + println!("\tBus\t\t\t: {}", id.bus_type()); + + println!( + "\tVendor/product\t\t: {:04x}:{:04x} version 0x{:04x}", + id.vendor(), + id.product(), + id.version() + ); + + if let Some(repeat) = inputdev.get_auto_repeat() { + println!( + "\tRepeat\t\t\t: delay {} ms, period {} ms", + repeat.delay, repeat.period + ); + } + + if config.mapping { + let mut index = 0; + + loop { + match inputdev.get_scancode_by_index(index) { + Ok((keycode, scancode)) => { + match scancode.len() { + 8 => { + // kernel v5.7 and later give 64 bit scancodes + let scancode = + u64::from_ne_bytes(scancode.try_into().unwrap()); + let keycode = evdev::Key::new(keycode as u16); + + println!( + "\tScancode\t\t: 0x{scancode:08x} => {keycode:?}" + ); + } + 4 => { + // kernel v5.6 and earlier give 32 bit scancodes + let scancode = + u32::from_ne_bytes(scancode.try_into().unwrap()); + let keycode = evdev::Key::new(keycode as u16); + + println!( + "\tScancode\t\t: 0x{scancode:08x} => {keycode:?}" + ) + } + len => panic!( + "scancode should be 4 or 8 bytes long, not {len}" + ), + } + + index += 1; + } + Err(err) if err.kind() == std::io::ErrorKind::InvalidInput => break, + Err(err) => { + eprintln!("error: {err}"); + std::process::exit(1); + } + } + } + } + } + Err(err) => { + println!("\tInput properties\t: {err}"); + } + }; + } + if let Some(lircdev) = &rcdev.lircdev { + println!("\tLIRC Device\t\t: {lircdev}"); + + match Lirc::open(PathBuf::from(lircdev)) { + Ok(mut lircdev) => { + if lircdev.can_receive_raw() { + println!("\tLIRC Receiver\t\t: raw receiver"); + + if lircdev.can_get_rec_resolution() { + println!( + "\tLIRC Resolution\t\t: {}", + match lircdev.receiver_resolution() { + Ok(res) => format!("{res} microseconds"), + Err(err) => err.to_string(), + } + ); + } else { + println!("\tLIRC Resolution\t\t: unknown"); + } + + println!( + "\tLIRC Timeout\t\t: {}", + match lircdev.get_timeout() { + Ok(timeout) => format!("{timeout} microseconds"), + Err(err) => err.to_string(), + } + ); + + if lircdev.can_set_timeout() { + println!( + "\tLIRC Timeout Range\t: {}", + match lircdev.get_min_max_timeout() { + Ok(range) => + format!("{} to {} microseconds", range.start, range.end), + Err(err) => err.to_string(), + } + ); + } else { + println!("\tLIRC Receiver Timeout Range\t: none"); + } + + println!( + "\tLIRC Wideband Receiver\t: {}", + if lircdev.can_use_wideband_receiver() { + "yes" + } else { + "no" + } + ); + + println!( + "\tLIRC Measure Carrier\t: {}", + if lircdev.can_measure_carrier() { + "yes" + } else { + "no" + } + ); + } else if lircdev.can_receive_scancodes() { + println!("\tLIRC Receiver\t\t: scancode"); + } else { + println!("\tLIRC Receiver\t\t: none"); + } + + if lircdev.can_send() { + println!("\tLIRC Transmitter\t: yes"); + + println!( + "\tLIRC Set Tx Carrier\t: {}", + if lircdev.can_set_send_carrier() { + "yes" + } else { + "no" + } + ); + + println!( + "\tLIRC Set Tx Duty Cycle\t: {}", + if lircdev.can_set_send_duty_cycle() { + "yes" + } else { + "no" + } + ); + + if lircdev.can_set_send_transmitter_mask() { + println!( + "\tLIRC Transmitters\t: {}", + match lircdev.num_transmitters() { + Ok(count) => format!("{count}"), + Err(err) => err.to_string(), + } + ); + } else { + println!("\tLIRC Transmitters\t: unknown"); + } + } else { + println!("\tLIRC Transmitter\t: no"); + } + + if lircdev.can_receive_raw() { + match lircdev.query_bpf() { + Ok(links) => { + println!("\tBPF protocols\t\t: {}", links.iter().join(" ")); + } + Err(err) => { + println!("\tBPF protocols\t\t: {err}") + } + } + } + } + Err(err) => { + println!("\tLIRC Features\t\t: {err}"); + } + } + } + + if !rcdev.supported_protocols.is_empty() { + println!( + "\tSupported Protocols\t: {}", + rcdev.supported_protocols.join(" ") + ); + + println!( + "\tEnabled Protocols\t: {}", + rcdev + .enabled_protocols + .iter() + .map(|p| &rcdev.supported_protocols[*p]) + .join(" ") + ); + } + + printed += 1; + } + + if printed == 0 { + if let Some(lircdev) = &config.device.lirc_dev { + eprintln!("error: no lirc device named {lircdev}"); + } else if let Some(rcdev) = &config.device.rc_dev { + eprintln!("error: no rc device named {rcdev}"); + } else { + eprintln!("error: no devices found"); + } + std::process::exit(1); + } +} diff --git a/src/bin/commands/mod.rs b/src/bin/commands/mod.rs index cec1773..77a7138 100644 --- a/src/bin/commands/mod.rs +++ b/src/bin/commands/mod.rs @@ -2,5 +2,7 @@ pub mod config; pub mod decode; #[cfg(target_os = "linux")] +pub mod list; +#[cfg(target_os = "linux")] pub mod test; pub mod transmit;