cir 0.1.1

Linux Infrared Tooling
Documentation
use super::{open_lirc, Purpose};
use cir::lircd_conf;
use irp::{Irp, Message, Pronto};
use log::{debug, error, info, warn};
use std::{ffi::OsStr, fs, path::Path};
use terminal_size::{terminal_size, Width};

pub fn transmit(global_matches: &clap::ArgMatches) {
    let (message, matches) = encode_args(global_matches);
    let dry_run = matches.is_present("DRYRUN");

    let duty_cycle = if let Some(value) = matches.value_of("DUTY_CYCLE") {
        match value.parse() {
            Ok(d @ 1..=99) => Some(d),
            _ => {
                eprintln!("error: ‘{}’ duty cycle is not valid", value);

                std::process::exit(1);
            }
        }
    } else {
        message.duty_cycle
    };

    let carrier = if let Some(value) = matches.value_of("CARRIER") {
        match value.parse() {
            Ok(c @ 0..=1_000_000) => Some(c),
            _ => {
                eprintln!("error: ‘{}’ carrier is not valid", value);

                std::process::exit(1);
            }
        }
    } else {
        message.carrier
    };

    if let Some(carrier) = &carrier {
        if *carrier == 0 {
            info!("carrier: unmodulated (no carrier)");
        } else {
            info!("carrier: {}Hz", carrier);
        }
    }
    if let Some(duty_cycle) = &duty_cycle {
        info!("duty cycle: {}%", duty_cycle);
    }
    info!("rawir: {}", message.print_rawir());

    if !dry_run {
        let mut lircdev = open_lirc(matches, Purpose::Transmit);

        if let Some(values) = global_matches
            .values_of("TRANSMITTERS")
            .or_else(|| matches.values_of("TRANSMITTERS"))
        {
            let mut transmitters: Vec<u32> = Vec::new();
            for t in values {
                match t.parse() {
                    Ok(0) | Err(_) => {
                        eprintln!("error: ‘{}’ is not a valid transmitter number", t);
                        std::process::exit(1);
                    }
                    Ok(v) => transmitters.push(v),
                }
            }

            if !transmitters.is_empty() {
                if !lircdev.can_set_send_transmitter_mask() {
                    eprintln!(
                        "error: {}: device does not support setting transmitters",
                        lircdev
                    );

                    std::process::exit(1);
                }

                let transmitter_count = match lircdev.num_transmitters() {
                    Ok(v) => v,
                    Err(e) => {
                        eprintln!("error: {}: failed to get transmitter count: {}", lircdev, e);

                        std::process::exit(1);
                    }
                };

                if let Some(t) = transmitters.iter().find(|t| **t > transmitter_count) {
                    eprintln!(
                        "error: transmitter {} not valid, device has {} transmitters",
                        t, transmitter_count
                    );

                    std::process::exit(1);
                }

                let mask: u32 = transmitters.iter().fold(0, |acc, t| acc | (1 << (t - 1)));

                info!("debug: setting transmitter mask {:08x}", mask);

                match lircdev.set_transmitter_mask(mask) {
                    Ok(v) => v,
                    Err(e) => {
                        eprintln!("error: {}: failed to set transmitter mask: {}", lircdev, e);

                        std::process::exit(1);
                    }
                }
            }
        }

        if let Some(duty_cycle) = duty_cycle {
            if lircdev.can_set_send_duty_cycle() {
                debug!("setting {} duty cycle {}", lircdev, duty_cycle);

                if let Err(s) = lircdev.set_send_duty_cycle(duty_cycle as u32) {
                    eprintln!("error: {}: {}", lircdev, s);

                    std::process::exit(1);
                }
            } else {
                warn!(
                    "warning: {}: device does not support setting send duty cycle",
                    lircdev
                );
            }
        }

        if let Some(carrier) = carrier {
            if lircdev.can_set_send_carrier() {
                debug!("setting {} send carrier {}", lircdev, carrier);

                if let Err(s) = lircdev.set_send_carrier(carrier as u32) {
                    eprintln!("error: {}: {}", lircdev, s);

                    if carrier == 0 {
                        eprintln!("info: not all lirc devices can send unmodulated");
                    }
                    std::process::exit(1);
                }
            } else {
                eprintln!(
                    "warning: {}: device does not support setting carrier",
                    lircdev
                );
            }
        }

        debug!("transmitting {} data {}", lircdev, message.print_rawir());

        if let Err(s) = lircdev.send(&message.raw) {
            eprintln!("error: {}: {}", lircdev, s);
            std::process::exit(1);
        }
    }
}

fn encode_args(matches: &clap::ArgMatches) -> (Message, &clap::ArgMatches) {
    match matches.subcommand() {
        Some(("irp", matches)) => {
            let mut vars = irp::Vartable::new();

            let i = matches.value_of("IRP").unwrap();

            if let Some(values) = matches.values_of("FIELD") {
                for f in values {
                    let list: Vec<&str> = f.split('=').collect();

                    if list.len() != 2 {
                        eprintln!("argument to --field must be X=1");
                        std::process::exit(2);
                    }

                    let value = match if list[1].starts_with("0x") {
                        i64::from_str_radix(&list[1][2..], 16)
                    } else if list[1].starts_with("0o") {
                        i64::from_str_radix(&list[1][2..], 8)
                    } else if list[1].starts_with("0b") {
                        i64::from_str_radix(&list[1][2..], 2)
                    } else {
                        list[1].parse()
                    } {
                        Ok(v) => v,
                        Err(_) => {
                            eprintln!("{}’ is not a valid number", list[1]);
                            std::process::exit(2);
                        }
                    };

                    vars.set(list[0].to_string(), value, 8);
                }
            }

            let repeats = match matches.value_of("REPEATS") {
                None => 1,
                Some(s) => match s.parse() {
                    Ok(num) => num,
                    Err(_) => {
                        eprintln!("error: {} is not numeric", s);
                        std::process::exit(2);
                    }
                },
            };

            let irp = match Irp::parse(i) {
                Ok(m) => m,
                Err(s) => {
                    eprintln!("unable to parse irp ‘{}’: {}", i, s);
                    std::process::exit(2);
                }
            };

            if matches.is_present("PRONTO") {
                match irp.encode_pronto(vars) {
                    Ok(p) => {
                        println!("{}", p);
                        std::process::exit(0);
                    }
                    Err(s) => {
                        eprintln!("error: {}", s);
                        std::process::exit(2);
                    }
                }
            } else {
                match irp.encode(vars, repeats) {
                    Ok(m) => (m, matches),
                    Err(s) => {
                        eprintln!("error: {}", s);
                        std::process::exit(2);
                    }
                }
            }
        }
        Some(("pronto", matches)) => {
            let pronto = matches.value_of("PRONTO").unwrap();

            let repeats = match matches.value_of("REPEATS") {
                None => 0,
                Some(s) => match str::parse(s) {
                    Ok(num) => num,
                    Err(_) => {
                        eprintln!("error: {} is not numeric", s);
                        std::process::exit(2);
                    }
                },
            };

            let pronto = match Pronto::parse(pronto) {
                Ok(pronto) => pronto,
                Err(err) => {
                    eprintln!("error: {}", err);
                    std::process::exit(2);
                }
            };

            (pronto.encode(repeats), matches)
        }
        Some(("rawir", matches)) => encode_rawir(matches),
        Some(("lircd", matches)) => {
            let filename = matches.value_of_os("CONF").unwrap();

            let remotes = match lircd_conf::parse(filename) {
                Ok(r) => r,
                Err(_) => std::process::exit(2),
            };

            let remote = matches.value_of("REMOTE");
            let repeats = match matches.value_of("REPEATS") {
                None => 0,
                Some(s) => match s.parse() {
                    Ok(num) => num,
                    Err(_) => {
                        eprintln!("error: {} is not numeric", s);
                        std::process::exit(2);
                    }
                },
            };

            if let Some(codes) = matches.values_of("CODES") {
                let codes: Vec<&str> = codes.collect();
                let m = lircd_conf::encode(&remotes, remote, &codes, repeats);

                match m {
                    Ok(m) => (m, matches),
                    Err(e) => {
                        error!("{}", e);

                        list_remotes(filename, &remotes, None);

                        std::process::exit(2);
                    }
                }
            } else {
                list_remotes(filename, &remotes, remote);

                std::process::exit(2);
            }
        }
        _ => {
            eprintln!("encode requires a subcommand");
            std::process::exit(2);
        }
    }
}

fn encode_rawir(matches: &clap::ArgMatches) -> (Message, &clap::ArgMatches) {
    enum Part {
        Raw(Message),
        Gap(u32),
    }

    let mut part = Vec::new();

    if let Some(files) = matches.values_of_os("FILE") {
        let mut indices = matches.indices_of("FILE").unwrap();

        for filename in files {
            let input = match fs::read_to_string(filename) {
                Ok(s) => s,
                Err(s) => {
                    error!("{}: {}", Path::new(filename).display(), s);
                    std::process::exit(2);
                }
            };

            match Message::parse(&input) {
                Ok(m) => {
                    part.push((Part::Raw(m), indices.next().unwrap()));
                }
                Err(msg) => match Message::parse_mode2(&input) {
                    Ok(m) => {
                        part.push((Part::Raw(m), indices.next().unwrap()));
                    }
                    Err((line_no, error)) => {
                        error!("{}: parse as rawir: {}", Path::new(filename).display(), msg);
                        error!(
                            "{}:{}: parse as mode2: {}",
                            Path::new(filename).display(),
                            line_no,
                            error
                        );
                        std::process::exit(2);
                    }
                },
            }
        }
    }

    if let Some(rawirs) = matches.values_of("RAWIR") {
        let mut indices = matches.indices_of("RAWIR").unwrap();

        for rawir in rawirs {
            match Message::parse(rawir) {
                Ok(m) => {
                    part.push((Part::Raw(m), indices.next().unwrap()));
                }
                Err(msg) => {
                    error!("{}", msg);
                    std::process::exit(2);
                }
            }
        }
    }

    if let Some(gaps) = matches.values_of("GAP") {
        let mut indices = matches.indices_of("GAP").unwrap();

        for gap in gaps {
            match gap.parse() {
                Ok(0) | Err(_) => {
                    error!("{} is not a valid gap", gap);
                    std::process::exit(2);
                }
                Ok(num) => {
                    part.push((Part::Gap(num), indices.next().unwrap()));
                }
            }
        }
    }

    part.sort_by(|a, b| a.1.cmp(&b.1));

    let mut message = Message::new();
    let mut gap = 125000;

    for (part, _) in part {
        match part {
            Part::Gap(v) => {
                gap = v;
            }
            Part::Raw(raw) => {
                if !message.raw.is_empty() && !message.has_trailing_gap() {
                    message.raw.push(gap);
                }

                message.extend(&raw);
            }
        }
    }

    if message.raw.is_empty() {
        error!("nothing to send");
        std::process::exit(2);
    }

    if !message.has_trailing_gap() {
        message.raw.push(gap);
    }

    (message, matches)
}

fn list_remotes(filename: &OsStr, remotes: &[lircd_conf::Remote], needle: Option<&str>) {
    let size = terminal_size();

    if size.is_some() {
        println!(
            "\nAvailable remotes and codes in {}:\n",
            Path::new(filename).display()
        );
    }

    let mut remote_found = false;

    for remote in remotes {
        if let Some(needle) = needle {
            if remote.name != needle {
                continue;
            }
        }
        remote_found = true;

        let codes = remote
            .codes
            .iter()
            .map(|code| code.name.as_str())
            .chain(remote.raw_codes.iter().map(|code| code.name.as_str()));

        if let Some((Width(term_witdh), _)) = size {
            let mut pos = 2;
            let mut res = String::new();
            let mut first = true;

            for code in codes {
                if first {
                    first = false
                } else {
                    res.push_str(", ");
                }

                if pos + code.len() + 2 < term_witdh as usize {
                    res.push_str(code);
                    pos += code.len() + 2;
                } else {
                    res.push_str("\n  ");
                    res.push_str(code);
                    pos = code.len() + 4;
                }
            }

            println!("Remote:\n  {}\nCodes:\n  {}", remote.name, res);
        } else {
            for code in codes {
                println!("{}", code);
            }
        }
    }

    if !remote_found {
        error!("not remote found");
    }
}