From bbedff09235d5fc2844b1ce22ebe75e7a07d3103 Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Wed, 6 Mar 2024 09:32:10 -0500 Subject: [PATCH 1/3] add util: stty --- Cargo.lock | 11 + screen/Cargo.toml | 6 + screen/src/osdata.rs | 220 ++++++++++++++++++ screen/src/stty.rs | 525 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 762 insertions(+) create mode 100644 screen/src/osdata.rs create mode 100644 screen/src/stty.rs diff --git a/Cargo.lock b/Cargo.lock index a47c923..655cb61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -490,8 +490,10 @@ version = "0.1.0" dependencies = [ "clap", "gettext-rs", + "libc", "plib", "terminfo", + "termios", ] [[package]] @@ -710,6 +712,15 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "termios" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" +dependencies = [ + "libc", +] + [[package]] name = "thiserror" version = "1.0.57" diff --git a/screen/Cargo.toml b/screen/Cargo.toml index 59f3abe..779e45a 100644 --- a/screen/Cargo.toml +++ b/screen/Cargo.toml @@ -8,6 +8,12 @@ plib = { path = "../plib" } clap = { version = "4", features = ["derive"] } gettext-rs = { version = "0.7", features = ["gettext-system"] } terminfo = "0.8" +termios = "0.3" +libc = "0.2" + +[[bin]] +name = "stty" +path = "src/stty.rs" [[bin]] name = "tput" diff --git a/screen/src/osdata.rs b/screen/src/osdata.rs new file mode 100644 index 0000000..04e6ecb --- /dev/null +++ b/screen/src/osdata.rs @@ -0,0 +1,220 @@ +// +// Copyright (c) 2024 Jeff Garzik +// +// This file is part of the posixutils-rs project covered under +// the MIT License. For the full license text, please see the LICENSE +// file in the root directory of this project. +// SPDX-License-Identifier: MIT +// + +extern crate libc; + +use std::collections::HashMap; +use termios::os::macos::{ + ALTWERASE, BS0, BS1, BSDLY, CCAR_OFLOW, CCTS_OFLOW, CDSR_OFLOW, CDTR_IFLOW, CR0, CR1, CR2, CR3, + CRDLY, CRTS_IFLOW, ECHOCTL, ECHOKE, ECHOPRT, FF0, FF1, FFDLY, FLUSHO, IMAXBEL, IUTF8, NL0, NL1, + NLDLY, NOKERNINFO, OFDEL, OFILL, ONOEOT, OXTABS, PENDIN, TAB0, TAB1, TAB2, TAB3, TABDLY, VT0, + VT1, VTDLY, +}; +use termios::*; + +pub fn load_speeds() -> HashMap<&'static str, speed_t> { + HashMap::from([ + ("0", libc::B0), + ("54", libc::B50), + ("75", libc::B75), + ("110", libc::B110), + ("134", libc::B134), + ("150", libc::B150), + ("200", libc::B200), + ("300", libc::B300), + ("600", libc::B600), + ("1200", libc::B1200), + ("1800", libc::B1800), + ("2400", libc::B2400), + ("4800", libc::B4800), + ("9600", libc::B9600), + ("19200", libc::B19200), + ("38400", libc::B38400), + ("57600", libc::B57600), + ("115200", libc::B115200), + ("230400", libc::B230400), + ]) +} + +pub fn load_cchar_xlat() -> HashMap { + HashMap::from([ + ('a', '\x01'), + ('A', '\x01'), + ('b', '\x02'), + ('B', '\x02'), + ('c', '\x03'), + ('C', '\x03'), + ('d', '\x04'), + ('D', '\x04'), + ('e', '\x05'), + ('E', '\x05'), + ('f', '\x06'), + ('F', '\x06'), + ('g', '\x07'), + ('G', '\x07'), + ('h', '\x08'), + ('H', '\x08'), + ('i', '\x09'), + ('I', '\x09'), + ('j', '\x0a'), + ('J', '\x0a'), + ('k', '\x0b'), + ('K', '\x0b'), + ('l', '\x0c'), + ('L', '\x0c'), + ('m', '\x0d'), + ('M', '\x0d'), + ('n', '\x0e'), + ('N', '\x0e'), + ('o', '\x0f'), + ('O', '\x0f'), + ('p', '\x10'), + ('P', '\x10'), + ('q', '\x11'), + ('Q', '\x11'), + ('r', '\x12'), + ('R', '\x12'), + ('s', '\x13'), + ('S', '\x13'), + ('t', '\x14'), + ('T', '\x14'), + ('u', '\x15'), + ('U', '\x15'), + ('v', '\x16'), + ('V', '\x16'), + ('w', '\x17'), + ('W', '\x17'), + ('x', '\x18'), + ('X', '\x18'), + ('y', '\x19'), + ('Y', '\x19'), + ('z', '\x1a'), + ('Z', '\x1a'), + ('[', '\x1b'), + ('\\', '\x1c'), + (']', '\x1d'), + ('^', '\x1e'), + ('_', '\x1f'), + ('?', '\x7f'), + ]) +} + +pub enum ParamType { + Cfl(u32, tcflag_t, tcflag_t), + Ispeed(u32), + Ospeed(u32), + Ifl(u32, tcflag_t, tcflag_t), + Ofl(u32, tcflag_t, tcflag_t), + Lfl(u32, tcflag_t, tcflag_t), + Cchar(u32, usize), +} + +pub const PNEG: u32 = 1 << 0; +pub const PARG: u32 = 1 << 1; + +pub fn load_params() -> HashMap<&'static str, ParamType> { + HashMap::from([ + ("parenb", ParamType::Cfl(PNEG, PARENB, PARENB)), + ("parodd", ParamType::Cfl(PNEG, PARODD, PARODD)), + ("cs5", ParamType::Cfl(0, CS5, CSIZE)), + ("cs6", ParamType::Cfl(0, CS6, CSIZE)), + ("cs7", ParamType::Cfl(0, CS7, CSIZE)), + ("cs8", ParamType::Cfl(0, CS8, CSIZE)), + ("ispeed", ParamType::Ispeed(0)), + ("ospeed", ParamType::Ospeed(0)), + ("hupcl", ParamType::Cfl(PNEG, HUPCL, HUPCL)), + /* FIXME: support "hup", alias for "hupcl" */ + ("cstopb", ParamType::Cfl(PNEG, CSTOPB, CSTOPB)), + ("cread", ParamType::Cfl(PNEG, CREAD, CREAD)), + ("clocal", ParamType::Cfl(PNEG, CLOCAL, CLOCAL)), + ("cctsoflow", ParamType::Cfl(PNEG, CCTS_OFLOW, CCTS_OFLOW)), + ("crtsiflow", ParamType::Cfl(PNEG, CRTS_IFLOW, CRTS_IFLOW)), + ("cdtriflow", ParamType::Cfl(PNEG, CDTR_IFLOW, CDTR_IFLOW)), + ("cdsroflow", ParamType::Cfl(PNEG, CDSR_OFLOW, CDSR_OFLOW)), + ("ccaroflow", ParamType::Cfl(PNEG, CCAR_OFLOW, CCAR_OFLOW)), + /* + * input flags + */ + ("ignbrk", ParamType::Ifl(PNEG, IGNBRK, IGNBRK)), + ("brkint", ParamType::Ifl(PNEG, BRKINT, BRKINT)), + ("ignpar", ParamType::Ifl(PNEG, IGNPAR, IGNPAR)), + ("parmrk", ParamType::Ifl(PNEG, PARMRK, PARMRK)), + ("inpck", ParamType::Ifl(PNEG, INPCK, INPCK)), + ("istrip", ParamType::Ifl(PNEG, ISTRIP, ISTRIP)), + ("inlcr", ParamType::Ifl(PNEG, INLCR, INLCR)), + ("igncr", ParamType::Ifl(PNEG, IGNCR, IGNCR)), + ("icrnl", ParamType::Ifl(PNEG, ICRNL, ICRNL)), + ("ixon", ParamType::Ifl(PNEG, IXON, IXON)), + ("ixany", ParamType::Ifl(PNEG, IXANY, IXANY)), + ("ixoff", ParamType::Ifl(PNEG, IXOFF, IXOFF)), + ("ixany", ParamType::Ifl(PNEG, IXANY, IXANY)), + ("ixmaxbel", ParamType::Ifl(PNEG, IMAXBEL, IMAXBEL)), + ("iutf8", ParamType::Ifl(PNEG, IUTF8, IUTF8)), + /* + * output flags + */ + ("opost", ParamType::Ofl(PNEG, OPOST, OPOST)), + ("onlcr", ParamType::Ofl(PNEG, ONLCR, ONLCR)), + ("oxtabs", ParamType::Ofl(PNEG, OXTABS, OXTABS)), + ("onoeot", ParamType::Ofl(PNEG, ONOEOT, ONOEOT)), + ("ocrnl", ParamType::Ofl(PNEG, OCRNL, OCRNL)), + ("onocr", ParamType::Ofl(PNEG, ONOCR, ONOCR)), + ("onlret", ParamType::Ofl(PNEG, ONLRET, ONLRET)), + ("ofill", ParamType::Ofl(PNEG, OFILL, OFILL)), + ("ofdel", ParamType::Ofl(PNEG, OFDEL, OFDEL)), + ("cr0", ParamType::Ofl(0, CR0, CRDLY)), + ("cr1", ParamType::Ofl(0, CR1, CRDLY)), + ("cr2", ParamType::Ofl(0, CR2, CRDLY)), + ("cr3", ParamType::Ofl(0, CR3, CRDLY)), + ("nl0", ParamType::Ofl(0, NL0, NLDLY)), + ("nl1", ParamType::Ofl(0, NL1, NLDLY)), + /* FIXME: support "tabs", alias for "tab0" */ + ("tab0", ParamType::Ofl(0, TAB0, TABDLY)), + ("tab1", ParamType::Ofl(0, TAB1, TABDLY)), + ("tab2", ParamType::Ofl(0, TAB2, TABDLY)), + ("tab3", ParamType::Ofl(0, TAB3, TABDLY)), + ("bs0", ParamType::Ofl(0, BS0, BSDLY)), + ("bs1", ParamType::Ofl(0, BS1, BSDLY)), + ("ff0", ParamType::Ofl(0, FF0, FFDLY)), + ("ff1", ParamType::Ofl(0, FF1, FFDLY)), + ("vt0", ParamType::Ofl(0, VT0, VTDLY)), + ("vt1", ParamType::Ofl(0, VT1, VTDLY)), + /* + * local modes + */ + ("isig", ParamType::Lfl(PNEG, ISIG, ISIG)), + ("icanon", ParamType::Lfl(PNEG, ICANON, ICANON)), + ("iexten", ParamType::Lfl(PNEG, IEXTEN, IEXTEN)), + ("echo", ParamType::Lfl(PNEG, ECHO, ECHO)), + ("echoe", ParamType::Lfl(PNEG, ECHOE, ECHOE)), + ("echok", ParamType::Lfl(PNEG, ECHOK, ECHOK)), + ("echoke", ParamType::Lfl(PNEG, ECHOKE, ECHOKE)), + ("echoctl", ParamType::Lfl(PNEG, ECHOCTL, ECHOCTL)), + ("echoprt", ParamType::Lfl(PNEG, ECHOPRT, ECHOPRT)), + ("echonl", ParamType::Lfl(PNEG, ECHONL, ECHONL)), + ("noflsh", ParamType::Lfl(PNEG, NOFLSH, NOFLSH)), + ("tostop", ParamType::Lfl(PNEG, TOSTOP, TOSTOP)), + ("altwerase", ParamType::Lfl(PNEG, ALTWERASE, ALTWERASE)), + ("flusho", ParamType::Lfl(PNEG, FLUSHO, FLUSHO)), + ("nokerninfo", ParamType::Lfl(PNEG, NOKERNINFO, NOKERNINFO)), + ("pendin", ParamType::Lfl(PNEG, PENDIN, PENDIN)), + /* + * control characters + */ + ("eof", ParamType::Cchar(PARG, VEOF)), + ("eol", ParamType::Cchar(PARG, VEOL)), + ("erase", ParamType::Cchar(PARG, VERASE)), + ("intr", ParamType::Cchar(PARG, VINTR)), + ("kill", ParamType::Cchar(PARG, VKILL)), + ("quit", ParamType::Cchar(PARG, VQUIT)), + ("susp", ParamType::Cchar(PARG, VSUSP)), + ("start", ParamType::Cchar(PARG, VSTART)), + ("stop", ParamType::Cchar(PARG, VSTOP)), + ]) +} diff --git a/screen/src/stty.rs b/screen/src/stty.rs new file mode 100644 index 0000000..5004ada --- /dev/null +++ b/screen/src/stty.rs @@ -0,0 +1,525 @@ +// +// Copyright (c) 2024 Jeff Garzik +// +// This file is part of the posixutils-rs project covered under +// the MIT License. For the full license text, please see the LICENSE +// file in the root directory of this project. +// SPDX-License-Identifier: MIT +// +// TODO: +// - stty get-short display +// - stty operand: /number/ (baud rate) +// - linux support +// + +extern crate clap; +extern crate libc; +extern crate plib; + +mod osdata; + +use clap::Parser; +use gettextrs::{bind_textdomain_codeset, textdomain}; +use osdata::{ParamType, PARG, PNEG}; +use plib::PROJECT_NAME; +use std::collections::HashMap; +use std::io::{self, Error, ErrorKind}; +use termios::{ + cc_t, cfgetispeed, cfgetospeed, cfsetispeed, cfsetospeed, speed_t, tcflag_t, tcsetattr, + Termios, TCSANOW, +}; + +const HDR_SAVE: &'static str = "pfmt1"; + +/// stty - set the options for a terminal +#[derive(Parser, Debug)] +#[command(author, version, about, long_about)] +struct Args { + /// Write to standard output all the current settings, in human-readable form. + #[arg(short, long, group = "mode")] + all: bool, + + /// Write to standard output all the current settings, in stty-readable form. + #[arg(short = 'g', long, group = "mode")] + save: bool, + + /// List of terminal configuration commands + operands: Vec, +} + +fn ti_baud_str(ti: &Termios) -> String { + let ispeed = cfgetispeed(&ti); + let ospeed = cfgetospeed(&ti); + + if ispeed == ospeed { + format!("speed {} baud;", ispeed) + } else { + format!("ispeed {} baud; ospeed {} baud;", ispeed, ospeed) + } +} + +// display short-form stty values +fn stty_show_short(ti: Termios) -> io::Result<()> { + stty_show_long(ti) +} + +fn build_flagstr(name: &str, flag: tcflag_t, pflg: u32, vset: tcflag_t, mask: tcflag_t) -> String { + if (flag & mask) == vset { + format!("{}", name) + } else if (pflg & PNEG) != 0 { + format!("-{}", name) + } else { + String::new() + } +} + +fn flagmap_push( + ti: &Termios, + flagmap: &mut HashMap<&'static str, Vec>, + name: &str, + param: &ParamType, +) { + match param { + ParamType::Ifl(pflg, vset, vclear) => { + let flag = ti.c_iflag; + let flagstr = build_flagstr(name, flag, *pflg, *vset, *vclear); + if !flagstr.is_empty() { + let v = flagmap.get_mut("iflags").unwrap(); + v.push(flagstr); + } + } + ParamType::Ofl(pflg, vset, vclear) => { + let flag = ti.c_oflag; + let flagstr = build_flagstr(name, flag, *pflg, *vset, *vclear); + if !flagstr.is_empty() { + let v = flagmap.get_mut("oflags").unwrap(); + v.push(flagstr); + } + } + ParamType::Cfl(pflg, vset, vclear) => { + let flag = ti.c_cflag; + let flagstr = build_flagstr(name, flag, *pflg, *vset, *vclear); + if !flagstr.is_empty() { + let v = flagmap.get_mut("cflags").unwrap(); + v.push(flagstr); + } + } + ParamType::Lfl(pflg, vset, vclear) => { + let flag = ti.c_lflag; + let flagstr = build_flagstr(name, flag, *pflg, *vset, *vclear); + if !flagstr.is_empty() { + let v = flagmap.get_mut("lflags").unwrap(); + v.push(flagstr); + } + } + _ => {} + }; +} + +fn show_flags(name: &str, flags: &Vec) { + println!("{}: {}", name, flags.join(" ")); +} + +// display long-form stty values +fn stty_show_long(ti: Termios) -> io::Result<()> { + println!("{}", ti_baud_str(&ti)); + + let tty_params = osdata::load_params(); + let flagnames = vec!["lflags", "iflags", "oflags", "cflags"]; + + let mut flagmap: HashMap<&'static str, Vec> = HashMap::new(); + for name in &flagnames { + flagmap.insert(name, Vec::new()); + } + + for (name, param) in tty_params { + flagmap_push(&ti, &mut flagmap, name, ¶m); + } + + for flagname in &flagnames { + show_flags(flagname, flagmap.get(flagname).unwrap()); + } + + Ok(()) +} + +// display compact, parse-able form stty values +fn stty_show_compact(ti: Termios) -> io::Result<()> { + let mut tiv = Vec::new(); + + // encode settings as pairs of (String,u64) + tiv.push((String::from("ifl"), ti.c_iflag as u64)); + tiv.push((String::from("ofl"), ti.c_oflag as u64)); + tiv.push((String::from("cfl"), ti.c_cflag as u64)); + tiv.push((String::from("lfl"), ti.c_lflag as u64)); + tiv.push((String::from("isp"), cfgetispeed(&ti) as u64)); + tiv.push((String::from("osp"), cfgetospeed(&ti) as u64)); + + // encode control chars as pairs of (String,u64) + for (i, cc) in ti.c_cc.iter().enumerate() { + tiv.push((format!("ch{}", i), *cc as u64)); + } + + // convert pairs to list of strings + let mut sv = Vec::new(); + sv.push(String::from(HDR_SAVE)); + for te in tiv { + sv.push(format!("{}={}", te.0, te.1)); + } + + // convert list to single compact string + println!("{}", sv.join(":")); + + Ok(()) +} + +// set termio control chars based on pairmap values +fn set_ti_cchar( + ti: &mut Termios, + pairmap: &HashMap, + mut dirty: bool, + idx: usize, +) -> Result { + // lookup control char. should only fail if data corruption. + let mapkey = format!("ch{}", idx); + let value_res = pairmap.get(&mapkey); + if value_res.is_none() { + return Err("Invalid ent: missing cchar"); + } + let value = *value_res.unwrap() as termios::cc_t; + + // conditionally set the control char. if changed, mark dirty. + if ti.c_cc[idx] != value { + ti.c_cc[idx] = value; + dirty = true; + } + + Ok(dirty) +} + +// set termio flags based on pairmap values +fn set_ti_flags( + ti: &mut Termios, + pairmap: &HashMap, + mut dirty: bool, + setname: &str, +) -> Result { + // lookup flag set name. should only fail if data corruption. + let value_res = pairmap.get(setname); + if value_res.is_none() { + return Err("Invalid ent: missing flags"); + } + let value = *value_res.unwrap() as termios::tcflag_t; + + // conditionally set the specified flag. if changed, mark dirty. + match setname { + "ifl" => { + if ti.c_iflag != value { + ti.c_iflag = value; + dirty = true; + } + } + "ofl" => { + if ti.c_oflag != value { + ti.c_oflag = value; + dirty = true; + } + } + "cfl" => { + if ti.c_cflag != value { + ti.c_cflag = value; + dirty = true; + } + } + "lfl" => { + if ti.c_lflag != value { + ti.c_lflag = value; + dirty = true; + } + } + _ => { + return Err("BUG - should never occur"); + } + } + + Ok(dirty) +} + +// mutate a termio flag based on the provided spec +fn set_ti_flag( + flags: &mut tcflag_t, + val_set: tcflag_t, + val_clear: tcflag_t, + negate: bool, + mut dirty: bool, +) -> bool { + // clear flag bits + let mut newflags = *flags & !val_clear; + + // set flag bits (unless negation) + if !negate { + newflags = newflags | val_set; + } + + // commit flag bits to termio struct, if changed + if newflags != *flags { + dirty = true; + *flags = newflags; + } + + dirty +} + +fn set_ti_cchar_oparg( + xlat: &HashMap, + cc: &mut cc_t, + op_arg: &str, + _dirty: bool, +) -> Result { + if op_arg == "^-" || op_arg == "undef" { + *cc = 0; + return Ok(true); + } + + if !op_arg.starts_with("^") || op_arg.len() != 2 { + return Err("Invalid cchar specification"); + } + let ch = op_arg.chars().nth(1).unwrap(); + let value_res = xlat.get(&ch); + if value_res.is_none() { + return Err("Unknown cchar specification"); + } + + let value = value_res.unwrap(); + *cc = *value as cc_t; + Ok(true) +} + +fn set_ti_speed( + ti: &mut Termios, + speedmap: &HashMap<&str, speed_t>, + is_input: bool, + op_arg: &str, +) -> io::Result<()> { + let speed_res = speedmap.get(op_arg); + if speed_res.is_none() { + return Err(Error::new(ErrorKind::Other, "Invalid speed")); + } + let speed = speed_res.unwrap(); + + if is_input { + let _ = cfsetispeed(ti, *speed)?; + } else { + let _ = cfsetospeed(ti, *speed)?; + } + + Ok(()) +} + +// update termio settings based on pairmap values +fn merge_map(ti: &mut Termios, pairmap: &HashMap) -> Result { + // push flags into termio struct + let mut dirty = false; + dirty = set_ti_flags(ti, pairmap, dirty, "ifl")?; + dirty = set_ti_flags(ti, pairmap, dirty, "ofl")?; + dirty = set_ti_flags(ti, pairmap, dirty, "cfl")?; + dirty = set_ti_flags(ti, pairmap, dirty, "lfl")?; + + // push control chars into termio struct + let cclen = ti.c_cc.len(); + for idx in 0..cclen { + dirty = set_ti_cchar(ti, &pairmap, dirty, idx)?; + } + + Ok(dirty) +} + +// update termio settings based on compact-form input line +fn stty_set_compact(mut ti: Termios, compact: &str) -> io::Result<()> { + // split by ':' + let parts: Vec<&str> = compact.split(":").collect(); + + // assert validates _prior_ user input check, guaranteeing this condition + assert_eq!(parts[0], HDR_SAVE); + + // iterate through strings, assuming they are KEY=VALUE pair strings. + // skip first entry, our header marker. + let mut pairmap = HashMap::new(); + let rawpairs = &parts[1..]; + for pairstr in rawpairs { + // split string into KEY=VALUE + match pairstr.split_once('=') { + // parse value into u64 integer + Some((key, value)) => match value.parse::() { + Ok(n) => { + // it's a valid entry. insert into settings pairmap. + pairmap.insert(String::from(key), n); + } + Err(_e) => { + return Err(Error::new(ErrorKind::Other, "Invalid ent")); + } + }, + None => { + return Err(Error::new(ErrorKind::Other, "Invalid ent")); + } + } + } + + // merge parsed settings into termio struct + let dirty = match merge_map(&mut ti, &pairmap) { + Ok(d) => d, + Err(e) => { + return Err(Error::new(ErrorKind::Other, e)); + } + }; + + // finally, commit new termio settings (if any) + if dirty { + tcsetattr(libc::STDIN_FILENO, TCSANOW, &ti)?; + } + + Ok(()) +} + +// update termio settings based on setting-per-arg parsed values +fn stty_set_long(mut ti: Termios, args: &Args) -> io::Result<()> { + assert!(args.operands.len() > 1); + + // load static list of params + let tty_params = osdata::load_params(); + let cchar_xlat = osdata::load_cchar_xlat(); + let speedmap = osdata::load_speeds(); + + let mut dirty = false; + + // parse each operand + let mut idx = 0; + while idx < args.operands.len() { + let operand_raw = &args.operands[idx]; + + // if operand begins with "-", it is a negation + let mut negate = false; + let operand = { + if operand_raw.starts_with("-") { + negate = true; + &operand_raw[1..] + } else { + &operand_raw[..] + } + }; + + // lookup operand in param map + let param_res = tty_params.get(operand); + if param_res.is_none() { + let errstr = format!("Unknown operand {}", operand); + return Err(Error::new(ErrorKind::Other, errstr)); + } + let param = param_res.unwrap(); + + let flags = match param { + ParamType::Cfl(pflg, _, _) => pflg, + ParamType::Ifl(pflg, _, _) => pflg, + ParamType::Ofl(pflg, _, _) => pflg, + ParamType::Lfl(pflg, _, _) => pflg, + ParamType::Cchar(pflg, _) => pflg, + ParamType::Ispeed(pflg) => pflg, + ParamType::Ospeed(pflg) => pflg, + }; + if negate && ((flags & PNEG) == 0) { + let errstr = format!("Operand {} cannot be negated", operand); + return Err(Error::new(ErrorKind::Other, errstr)); + } + + let mut op_arg = String::new(); + if (flags & PARG) != 0 { + idx = idx + 1; + + if idx == args.operands.len() { + let errstr = format!("Missing operand for {}", operand); + return Err(Error::new(ErrorKind::Other, errstr)); + } + + op_arg = String::from(&args.operands[idx]); + } + + // handle operand + match param { + ParamType::Cfl(_pflg, bset, bclear) => { + dirty = set_ti_flag(&mut ti.c_cflag, *bset, *bclear, negate, dirty); + } + ParamType::Ifl(_pflg, bset, bclear) => { + dirty = set_ti_flag(&mut ti.c_iflag, *bset, *bclear, negate, dirty); + } + ParamType::Ofl(_pflg, bset, bclear) => { + dirty = set_ti_flag(&mut ti.c_oflag, *bset, *bclear, negate, dirty); + } + ParamType::Lfl(_pflg, bset, bclear) => { + dirty = set_ti_flag(&mut ti.c_lflag, *bset, *bclear, negate, dirty); + } + ParamType::Cchar(_pflg, chidx) => { + let dirty_res = + set_ti_cchar_oparg(&cchar_xlat, &mut ti.c_cc[*chidx], &op_arg, dirty); + if let Err(e) = dirty_res { + return Err(Error::new(ErrorKind::Other, e)); + } + + dirty = dirty_res.unwrap(); + } + ParamType::Ispeed(_pflg) => { + set_ti_speed(&mut ti, &speedmap, true, &op_arg)?; + } + ParamType::Ospeed(_pflg) => { + set_ti_speed(&mut ti, &speedmap, false, &op_arg)?; + } + } + + idx = idx + 1; + } + + // finally, commit new termio settings (if any) + // note: speed is changed in set_ti_speed(), not here. + if dirty { + tcsetattr(libc::STDIN_FILENO, TCSANOW, &ti)?; + } + + Ok(()) +} + +// set termio settings based on CLI operands supplied +fn stty_set(ti: Termios, args: &Args) -> io::Result<()> { + if args.operands.len() == 1 && args.operands[0].starts_with(HDR_SAVE) { + stty_set_compact(ti, &args.operands[0]) + } else { + stty_set_long(ti, args) + } +} + +fn main() -> Result<(), Box> { + // parse command line arguments + let args = Args::parse(); + + textdomain(PROJECT_NAME)?; + bind_textdomain_codeset(PROJECT_NAME, "UTF-8")?; + + // load termio settings + let ti = Termios::from_fd(libc::STDIN_FILENO)?; + + // display long form readable, if -a + if args.all { + stty_show_long(ti)?; + + // display computer-parseable, if -g + } else if args.save { + stty_show_compact(ti)?; + + // display short form readable, if no args + } else if args.operands.is_empty() { + stty_show_short(ti)?; + + // otherwise, a list of operands instructing termio updates + } else { + stty_set(ti, &args)?; + } + + Ok(()) +} From ab76ab614379ea42b5bca0e488d880beec432df1 Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Wed, 6 Mar 2024 09:32:35 -0500 Subject: [PATCH 2/3] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ad2eb26..b5cef13 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ https://github.com/jgarzik/posixutils - [x] split - [ ] strings - [ ] strip (Development) - - [ ] stty + - [x] stty - [ ] tabs - [ ] tail - [ ] talk From a3b081c57dfb848f11cd6b1dc793c46229015913 Mon Sep 17 00:00:00 2001 From: Jeff Garzik Date: Wed, 6 Mar 2024 15:52:31 +0000 Subject: [PATCH 3/3] stty: initial linux fixes --- screen/src/osdata.rs | 25 ++++++++++++++++++++++--- screen/src/stty.rs | 3 ++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/screen/src/osdata.rs b/screen/src/osdata.rs index 04e6ecb..230224f 100644 --- a/screen/src/osdata.rs +++ b/screen/src/osdata.rs @@ -10,12 +10,22 @@ extern crate libc; use std::collections::HashMap; + +#[cfg(target_os = "macos")] use termios::os::macos::{ ALTWERASE, BS0, BS1, BSDLY, CCAR_OFLOW, CCTS_OFLOW, CDSR_OFLOW, CDTR_IFLOW, CR0, CR1, CR2, CR3, CRDLY, CRTS_IFLOW, ECHOCTL, ECHOKE, ECHOPRT, FF0, FF1, FFDLY, FLUSHO, IMAXBEL, IUTF8, NL0, NL1, NLDLY, NOKERNINFO, OFDEL, OFILL, ONOEOT, OXTABS, PENDIN, TAB0, TAB1, TAB2, TAB3, TABDLY, VT0, VT1, VTDLY, }; + +#[cfg(target_os = "linux")] +use termios::os::linux::{ + BS0, BS1, BSDLY, CR0, CR1, CR2, CR3, CRDLY, ECHOCTL, ECHOKE, ECHOPRT, FF0, FF1, FFDLY, FLUSHO, + IMAXBEL, IUTF8, NL0, NL1, NLDLY, OFDEL, OFILL, PENDIN, TAB0, TAB1, TAB2, TAB3, TABDLY, VT0, + VT1, VTDLY, +}; + use termios::*; pub fn load_speeds() -> HashMap<&'static str, speed_t> { @@ -133,10 +143,15 @@ pub fn load_params() -> HashMap<&'static str, ParamType> { ("cstopb", ParamType::Cfl(PNEG, CSTOPB, CSTOPB)), ("cread", ParamType::Cfl(PNEG, CREAD, CREAD)), ("clocal", ParamType::Cfl(PNEG, CLOCAL, CLOCAL)), + #[cfg(target_os = "macos")] ("cctsoflow", ParamType::Cfl(PNEG, CCTS_OFLOW, CCTS_OFLOW)), + #[cfg(target_os = "macos")] ("crtsiflow", ParamType::Cfl(PNEG, CRTS_IFLOW, CRTS_IFLOW)), + #[cfg(target_os = "macos")] ("cdtriflow", ParamType::Cfl(PNEG, CDTR_IFLOW, CDTR_IFLOW)), + #[cfg(target_os = "macos")] ("cdsroflow", ParamType::Cfl(PNEG, CDSR_OFLOW, CDSR_OFLOW)), + #[cfg(target_os = "macos")] ("ccaroflow", ParamType::Cfl(PNEG, CCAR_OFLOW, CCAR_OFLOW)), /* * input flags @@ -161,8 +176,6 @@ pub fn load_params() -> HashMap<&'static str, ParamType> { */ ("opost", ParamType::Ofl(PNEG, OPOST, OPOST)), ("onlcr", ParamType::Ofl(PNEG, ONLCR, ONLCR)), - ("oxtabs", ParamType::Ofl(PNEG, OXTABS, OXTABS)), - ("onoeot", ParamType::Ofl(PNEG, ONOEOT, ONOEOT)), ("ocrnl", ParamType::Ofl(PNEG, OCRNL, OCRNL)), ("onocr", ParamType::Ofl(PNEG, ONOCR, ONOCR)), ("onlret", ParamType::Ofl(PNEG, ONLRET, ONLRET)), @@ -185,6 +198,10 @@ pub fn load_params() -> HashMap<&'static str, ParamType> { ("ff1", ParamType::Ofl(0, FF1, FFDLY)), ("vt0", ParamType::Ofl(0, VT0, VTDLY)), ("vt1", ParamType::Ofl(0, VT1, VTDLY)), + #[cfg(target_os = "macos")] + ("oxtabs", ParamType::Ofl(PNEG, OXTABS, OXTABS)), + #[cfg(target_os = "macos")] + ("onoeot", ParamType::Ofl(PNEG, ONOEOT, ONOEOT)), /* * local modes */ @@ -200,10 +217,12 @@ pub fn load_params() -> HashMap<&'static str, ParamType> { ("echonl", ParamType::Lfl(PNEG, ECHONL, ECHONL)), ("noflsh", ParamType::Lfl(PNEG, NOFLSH, NOFLSH)), ("tostop", ParamType::Lfl(PNEG, TOSTOP, TOSTOP)), + #[cfg(target_os = "macos")] ("altwerase", ParamType::Lfl(PNEG, ALTWERASE, ALTWERASE)), ("flusho", ParamType::Lfl(PNEG, FLUSHO, FLUSHO)), - ("nokerninfo", ParamType::Lfl(PNEG, NOKERNINFO, NOKERNINFO)), ("pendin", ParamType::Lfl(PNEG, PENDIN, PENDIN)), + #[cfg(target_os = "macos")] + ("nokerninfo", ParamType::Lfl(PNEG, NOKERNINFO, NOKERNINFO)), /* * control characters */ diff --git a/screen/src/stty.rs b/screen/src/stty.rs index 5004ada..26ff058 100644 --- a/screen/src/stty.rs +++ b/screen/src/stty.rs @@ -8,8 +8,9 @@ // // TODO: // - stty get-short display +// - stty display: control chars // - stty operand: /number/ (baud rate) -// - linux support +// - BUG: linux baud display output incorrect // extern crate clap;