diff --git a/README.md b/README.md index 82575ff..11632cc 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ This tool replaces all those tools, but with major new features: Pronto hex codes are a fairly straightforward way of encoding raw IR, NEC, RC-5 and a few others. -[IRP](http://hifi-remote.com/wiki/index.php?title=IRP_Notation) is a +[IRP Notation](http://hifi-remote.com/wiki/index.php?title=IRP_Notation) is a DSL language which can 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 diff --git a/src/bin/cir.rs b/src/bin/cir.rs index 76a6d84..9a3163e 100644 --- a/src/bin/cir.rs +++ b/src/bin/cir.rs @@ -117,26 +117,26 @@ struct DecodeOptions { eps: Option, /// Save the NFA - #[arg(long = "save-nfa", global = true, help_heading = "ADVANCED")] + #[arg(long = "save-nfa", global = true, help_heading = "DEBUGGING")] save_nfa: bool, /// Save the DFA - #[arg(long = "save-dfa", global = true, help_heading = "ADVANCED")] + #[arg(long = "save-dfa", global = true, help_heading = "DEBUGGING")] save_dfa: bool, } #[derive(Args)] struct BpfDecodeOptions { /// Save the LLVM IR - #[arg(long = "save-llvm-ir", help_heading = "ADVANCED")] + #[arg(long = "save-llvm-ir", help_heading = "DEBUGGING")] save_llvm_ir: bool, /// Save the Assembly - #[arg(long = "save-asm", help_heading = "ADVANCED")] + #[arg(long = "save-asm", help_heading = "DEBUGGING")] save_assembly: bool, /// Save the Object - #[arg(long = "save-object", help_heading = "ADVANCED")] + #[arg(long = "save-object", help_heading = "DEBUGGING")] save_object: bool, } @@ -229,6 +229,22 @@ struct List { mapping: bool, } +#[cfg(target_os = "linux")] +fn parse_scankey(arg: &str) -> Result<(u64, String), String> { + if let Some((scancode, keycode)) = arg.split_once('=') { + let scancode = if let Some(hex) = scancode.strip_prefix("0x") { + u64::from_str_radix(hex, 16) + } else { + str::parse(scancode) + } + .map_err(|e| format!("{e}"))?; + + Ok((scancode, keycode.to_owned())) + } else { + Err("missing `=` separator".into()) + } +} + #[cfg(target_os = "linux")] #[derive(Args)] struct Config { @@ -252,10 +268,23 @@ struct Config { #[arg(long = "period", short = 'P', name = "PERIOD")] period: Option, - // TODO: - // --irp '{}<>()[]' - // set scancode (like ir-keytable --set-key/-k) - // set protocol (like ir-keytabke -P) + /// Load decoder based on IRP Notation + #[arg(long = "irp", short = 'i', name = "IRP")] + irp: Option, + + /// Protocol to enable + #[arg( + long = "protocol", + short = 'p', + value_delimiter = ',', + name = "PROTOCOL" + )] + protocol: Vec, + + /// Scancode to keycode mapping to add + #[arg(long = "set-key", short = 'k', value_parser = parse_scankey, value_delimiter = ',', name = "SCANKEY")] + scankey: Vec<(u64, String)>, + #[clap(flatten)] options: DecodeOptions, @@ -348,7 +377,7 @@ struct TransmitIrp { #[arg(long = "field", short = 'f', value_delimiter = ',')] fields: Vec, - /// IRP protocol + /// IRP Notation #[arg(name = "IRP")] irp: String, } @@ -531,7 +560,7 @@ impl FromArgMatches for TransmitCommands { impl Subcommand for TransmitCommands { fn augment_subcommands(cmd: Command) -> Command { cmd.subcommand(TransmitIrp::augment_args( - Command::new("irp").about("Encode using IRP language and transmit"), + Command::new("irp").about("Encode using IRP Notation and transmit"), )) .subcommand(TransmitKeymap::augment_args( Command::new("keymap").about("Transmit codes from keymap or lircd.conf file"), @@ -546,7 +575,7 @@ impl Subcommand for TransmitCommands { } fn augment_subcommands_for_update(cmd: Command) -> Command { cmd.subcommand(TransmitIrp::augment_args( - Command::new("irp").about("Encode using IRP language and transmit"), + Command::new("irp").about("Encode using IRP Notation and transmit"), )) .subcommand(TransmitKeymap::augment_args( Command::new("keymap").about("Transmit codes from keymap or lircd.conf file"), diff --git a/src/bin/commands/config.rs b/src/bin/commands/config.rs index 35114eb..12d35b9 100644 --- a/src/bin/commands/config.rs +++ b/src/bin/commands/config.rs @@ -6,7 +6,7 @@ use cir::{ rcdev::{enumerate_rc_dev, Rcdev}, }; use evdev::Key; -use irp::Options; +use irp::{Irp, Options}; use log::debug; use std::{ path::{Path, PathBuf}, @@ -85,9 +85,140 @@ pub fn config(config: &crate::Config) { } } - // TODO - // set scancode - // load irp + if !config.scankey.is_empty() { + for (scancode, keycode) in &config.scankey { + let key = match Key::from_str(keycode) { + Ok(key) => key, + Err(_) => { + eprintln!("error: ‘{keycode}’ is not a valid keycode"); + continue; + } + }; + + match rcdev.update_scancode(key, *scancode) { + Ok(_) => (), + Err(e) => { + eprintln!( + "error: failed to update key mapping from scancode {scancode:x?} to {key:?}: {e}" + ); + std::process::exit(1); + } + } + } + } + + if !config.protocol.is_empty() { + let mut res = Vec::new(); + + for name in &config.protocol { + if name.is_empty() { + // nothing to do + } else if name == "all" { + for pos in 0..rcdev.supported_protocols.len() { + if !res.contains(&pos) { + res.push(pos); + } + } + } else if let Some(pos) = rcdev.supported_protocols.iter().position(|e| e == name) { + if !res.contains(&pos) { + res.push(pos); + } + } else { + eprintln!("error: {}: does not support protocol {name}", rcdev.name); + std::process::exit(1); + } + } + + if let Err(e) = rcdev.set_enabled_protocols(&res) { + eprintln!("error: {}: {e}", rcdev.name); + std::process::exit(1); + } + } + + if let Some(irp_notation) = &config.irp { + let irp = match Irp::parse(irp_notation) { + Ok(irp) => irp, + Err(e) => { + eprintln!("error: {irp_notation}: {e}"); + std::process::exit(1); + } + }; + + let mut max_gap = 100000; + + let chdev = 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 !lirc.can_receive_raw() { + eprintln!( + "error: {}: not a raw receiver, irp not supported", + rcdev.name + ); + std::process::exit(1); + } + + lirc + } else { + eprintln!("error: {}: no lirc device, irp not supported", rcdev.name); + std::process::exit(1); + }; + + if let Some(timeout) = config.timeout { + max_gap = timeout; + } else if let Ok(timeout) = chdev.get_timeout() { + let dev_max_gap = (timeout * 9) / 10; + + log::trace!( + "device reports timeout of {}, using 90% of that as {} max_gap", + timeout, + dev_max_gap + ); + + max_gap = dev_max_gap; + } + + let mut options = Options { + name: "irp", + max_gap, + ..Default::default() + }; + + options.nfa = config.options.save_nfa; + options.dfa = config.options.save_dfa; + options.aeps = config.options.aeps.unwrap_or(100); + options.eps = config.options.eps.unwrap_or(3); + + options.llvm_ir = config.bpf_options.save_llvm_ir; + options.assembly = config.bpf_options.save_assembly; + options.object = config.bpf_options.save_object; + + let dfa = match irp.compile(&options) { + Ok(dfa) => dfa, + Err(e) => { + println!("error: irp: {e}"); + std::process::exit(1); + } + }; + + let bpf = match dfa.compile_bpf(&options) { + Ok((bpf, _)) => bpf, + Err(e) => { + eprintln!("error: irp: {e}"); + std::process::exit(1); + } + }; + + if let Err(e) = chdev.attach_bpf(&bpf) { + eprintln!("error: attach bpf: {e}",); + std::process::exit(1); + } + } } pub fn load(load: &crate::Load) {