mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 19:17:43 +00:00
Merge pull request #3672 from tertsdiepraam/stty
Initial implementation of `stty`
This commit is contained in:
commit
8786bb6541
8 changed files with 809 additions and 0 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -445,6 +445,7 @@ dependencies = [
|
||||||
"uu_split",
|
"uu_split",
|
||||||
"uu_stat",
|
"uu_stat",
|
||||||
"uu_stdbuf",
|
"uu_stdbuf",
|
||||||
|
"uu_stty",
|
||||||
"uu_sum",
|
"uu_sum",
|
||||||
"uu_sync",
|
"uu_sync",
|
||||||
"uu_tac",
|
"uu_tac",
|
||||||
|
@ -2908,6 +2909,15 @@ dependencies = [
|
||||||
"uucore",
|
"uucore",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uu_stty"
|
||||||
|
version = "0.0.14"
|
||||||
|
dependencies = [
|
||||||
|
"clap 3.2.17",
|
||||||
|
"nix",
|
||||||
|
"uucore",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uu_sum"
|
name = "uu_sum"
|
||||||
version = "0.0.14"
|
version = "0.0.14"
|
||||||
|
|
|
@ -190,6 +190,7 @@ feat_require_unix = [
|
||||||
"nohup",
|
"nohup",
|
||||||
"pathchk",
|
"pathchk",
|
||||||
"stat",
|
"stat",
|
||||||
|
"stty",
|
||||||
"timeout",
|
"timeout",
|
||||||
"tty",
|
"tty",
|
||||||
"uname",
|
"uname",
|
||||||
|
@ -348,6 +349,7 @@ sort = { optional=true, version="0.0.14", package="uu_sort", path="src/uu/so
|
||||||
split = { optional=true, version="0.0.14", package="uu_split", path="src/uu/split" }
|
split = { optional=true, version="0.0.14", package="uu_split", path="src/uu/split" }
|
||||||
stat = { optional=true, version="0.0.14", package="uu_stat", path="src/uu/stat" }
|
stat = { optional=true, version="0.0.14", package="uu_stat", path="src/uu/stat" }
|
||||||
stdbuf = { optional=true, version="0.0.14", package="uu_stdbuf", path="src/uu/stdbuf" }
|
stdbuf = { optional=true, version="0.0.14", package="uu_stdbuf", path="src/uu/stdbuf" }
|
||||||
|
stty = { optional=true, version="0.0.14", package="uu_stty", path="src/uu/stty" }
|
||||||
sum = { optional=true, version="0.0.14", package="uu_sum", path="src/uu/sum" }
|
sum = { optional=true, version="0.0.14", package="uu_sum", path="src/uu/sum" }
|
||||||
sync = { optional=true, version="0.0.14", package="uu_sync", path="src/uu/sync" }
|
sync = { optional=true, version="0.0.14", package="uu_sync", path="src/uu/sync" }
|
||||||
tac = { optional=true, version="0.0.14", package="uu_tac", path="src/uu/tac" }
|
tac = { optional=true, version="0.0.14", package="uu_tac", path="src/uu/tac" }
|
||||||
|
|
24
src/uu/stty/Cargo.toml
Normal file
24
src/uu/stty/Cargo.toml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
[package]
|
||||||
|
name = "uu_stty"
|
||||||
|
version = "0.0.14"
|
||||||
|
authors = ["uutils developers"]
|
||||||
|
license = "MIT"
|
||||||
|
description = "stty ~ (uutils) print or change terminal characteristics"
|
||||||
|
|
||||||
|
homepage = "https://github.com/uutils/coreutils"
|
||||||
|
repository = "https://github.com/uutils/coreutils/tree/main/src/uu/stty"
|
||||||
|
keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"]
|
||||||
|
categories = ["command-line-utilities"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/stty.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
|
||||||
|
uucore = { version=">=0.0.11", package="uucore", path="../../uucore" }
|
||||||
|
nix = { version="0.25", features = ["term"] }
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "stty"
|
||||||
|
path = "src/main.rs"
|
1
src/uu/stty/LICENSE
Symbolic link
1
src/uu/stty/LICENSE
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../../LICENSE
|
315
src/uu/stty/src/flags.rs
Normal file
315
src/uu/stty/src/flags.rs
Normal file
|
@ -0,0 +1,315 @@
|
||||||
|
// * This file is part of the uutils coreutils package.
|
||||||
|
// *
|
||||||
|
// * For the full copyright and license information, please view the LICENSE file
|
||||||
|
// * that was distributed with this source code.
|
||||||
|
|
||||||
|
// spell-checker:ignore parenb parodd cmspar hupcl cstopb cread clocal crtscts CSIZE
|
||||||
|
// spell-checker:ignore ignbrk brkint ignpar parmrk inpck istrip inlcr igncr icrnl ixoff ixon iuclc ixany imaxbel iutf
|
||||||
|
// spell-checker:ignore opost olcuc ocrnl onlcr onocr onlret ofill ofdel nldly crdly tabdly bsdly vtdly ffdly
|
||||||
|
// spell-checker:ignore isig icanon iexten echoe crterase echok echonl noflsh xcase tostop echoprt prterase echoctl ctlecho echoke crtkill flusho extproc
|
||||||
|
|
||||||
|
use crate::Flag;
|
||||||
|
|
||||||
|
#[cfg(not(any(
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "dragonfly",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "macos",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_os = "openbsd"
|
||||||
|
)))]
|
||||||
|
use nix::sys::termios::BaudRate;
|
||||||
|
use nix::sys::termios::{ControlFlags as C, InputFlags as I, LocalFlags as L, OutputFlags as O};
|
||||||
|
|
||||||
|
pub const CONTROL_FLAGS: &[Flag<C>] = &[
|
||||||
|
Flag::new("parenb", C::PARENB),
|
||||||
|
Flag::new("parodd", C::PARODD),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
all(target_os = "linux", not(target_arch = "mips"))
|
||||||
|
))]
|
||||||
|
Flag::new("cmspar", C::CMSPAR),
|
||||||
|
Flag::new_grouped("cs5", C::CS5, C::CSIZE),
|
||||||
|
Flag::new_grouped("cs6", C::CS6, C::CSIZE),
|
||||||
|
Flag::new_grouped("cs7", C::CS7, C::CSIZE),
|
||||||
|
Flag::new_grouped("cs8", C::CS8, C::CSIZE).sane(),
|
||||||
|
Flag::new("hupcl", C::HUPCL),
|
||||||
|
Flag::new("cstopb", C::CSTOPB),
|
||||||
|
Flag::new("cread", C::CREAD).sane(),
|
||||||
|
Flag::new("clocal", C::CLOCAL),
|
||||||
|
Flag::new("crtscts", C::CRTSCTS),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub const INPUT_FLAGS: &[Flag<I>] = &[
|
||||||
|
Flag::new("ignbrk", I::IGNBRK),
|
||||||
|
Flag::new("brkint", I::BRKINT).sane(),
|
||||||
|
Flag::new("ignpar", I::IGNPAR),
|
||||||
|
Flag::new("parmrk", I::PARMRK),
|
||||||
|
Flag::new("inpck", I::INPCK),
|
||||||
|
Flag::new("istrip", I::ISTRIP),
|
||||||
|
Flag::new("inlcr", I::INLCR),
|
||||||
|
Flag::new("igncr", I::IGNCR),
|
||||||
|
Flag::new("icrnl", I::ICRNL).sane(),
|
||||||
|
Flag::new("ixoff", I::IXOFF),
|
||||||
|
Flag::new("tandem", I::IXOFF),
|
||||||
|
Flag::new("ixon", I::IXON),
|
||||||
|
// not supported by nix
|
||||||
|
// Flag::new("iuclc", I::IUCLC),
|
||||||
|
Flag::new("ixany", I::IXANY),
|
||||||
|
Flag::new("imaxbel", I::IMAXBEL).sane(),
|
||||||
|
#[cfg(any(target_os = "android", target_os = "linux", target_os = "macos"))]
|
||||||
|
Flag::new("iutf8", I::IUTF8),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub const OUTPUT_FLAGS: &[Flag<O>] = &[
|
||||||
|
Flag::new("opost", O::OPOST).sane(),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "haiku",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "openbsd"
|
||||||
|
))]
|
||||||
|
Flag::new("olcuc", O::OLCUC),
|
||||||
|
Flag::new("ocrnl", O::OCRNL),
|
||||||
|
Flag::new("onlcr", O::ONLCR).sane(),
|
||||||
|
Flag::new("onocr", O::ONOCR),
|
||||||
|
Flag::new("onlret", O::ONLRET),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "haiku",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "macos"
|
||||||
|
))]
|
||||||
|
Flag::new("ofill", O::OFILL),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "haiku",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "macos"
|
||||||
|
))]
|
||||||
|
Flag::new("ofdel", O::OFDEL),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "haiku",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "macos"
|
||||||
|
))]
|
||||||
|
Flag::new_grouped("nl0", O::NL0, O::NLDLY).sane(),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "haiku",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "macos"
|
||||||
|
))]
|
||||||
|
Flag::new_grouped("nl1", O::NL1, O::NLDLY),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "haiku",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "macos"
|
||||||
|
))]
|
||||||
|
Flag::new_grouped("cr0", O::CR0, O::CRDLY).sane(),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "haiku",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "macos"
|
||||||
|
))]
|
||||||
|
Flag::new_grouped("cr1", O::CR1, O::CRDLY),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "haiku",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "macos"
|
||||||
|
))]
|
||||||
|
Flag::new_grouped("cr2", O::CR2, O::CRDLY),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "haiku",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "macos"
|
||||||
|
))]
|
||||||
|
Flag::new_grouped("cr3", O::CR3, O::CRDLY),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "haiku",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "macos"
|
||||||
|
))]
|
||||||
|
Flag::new_grouped("tab0", O::TAB0, O::TABDLY).sane(),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "haiku",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "macos"
|
||||||
|
))]
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "haiku",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "macos"
|
||||||
|
))]
|
||||||
|
Flag::new_grouped("tab1", O::TAB1, O::TABDLY),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "haiku",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "macos"
|
||||||
|
))]
|
||||||
|
Flag::new_grouped("tab2", O::TAB2, O::TABDLY),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "haiku",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "macos"
|
||||||
|
))]
|
||||||
|
Flag::new_grouped("tab3", O::TAB3, O::TABDLY),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "haiku",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "macos"
|
||||||
|
))]
|
||||||
|
Flag::new_grouped("bs0", O::BS0, O::BSDLY).sane(),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "haiku",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "macos"
|
||||||
|
))]
|
||||||
|
Flag::new_grouped("bs1", O::BS1, O::BSDLY),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "haiku",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "macos"
|
||||||
|
))]
|
||||||
|
Flag::new_grouped("vt0", O::VT0, O::VTDLY).sane(),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "haiku",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "macos"
|
||||||
|
))]
|
||||||
|
Flag::new_grouped("vt1", O::VT1, O::VTDLY),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "haiku",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "macos"
|
||||||
|
))]
|
||||||
|
Flag::new_grouped("ff0", O::FF0, O::FFDLY).sane(),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "haiku",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "macos"
|
||||||
|
))]
|
||||||
|
Flag::new_grouped("ff1", O::FF1, O::FFDLY),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub const LOCAL_FLAGS: &[Flag<L>] = &[
|
||||||
|
Flag::new("isig", L::ISIG).sane(),
|
||||||
|
Flag::new("icanon", L::ICANON).sane(),
|
||||||
|
Flag::new("iexten", L::IEXTEN).sane(),
|
||||||
|
Flag::new("echo", L::ECHO).sane(),
|
||||||
|
Flag::new("echoe", L::ECHOE).sane(),
|
||||||
|
Flag::new("crterase", L::ECHOE).hidden().sane(),
|
||||||
|
Flag::new("echok", L::ECHOK).sane(),
|
||||||
|
Flag::new("echonl", L::ECHONL),
|
||||||
|
Flag::new("noflsh", L::NOFLSH),
|
||||||
|
// Not supported by nix
|
||||||
|
// Flag::new("xcase", L::XCASE),
|
||||||
|
Flag::new("tostop", L::TOSTOP),
|
||||||
|
Flag::new("echoprt", L::ECHOPRT),
|
||||||
|
Flag::new("prterase", L::ECHOPRT).hidden(),
|
||||||
|
Flag::new("echoctl", L::ECHOCTL).sane(),
|
||||||
|
Flag::new("ctlecho", L::ECHOCTL).sane().hidden(),
|
||||||
|
Flag::new("echoke", L::ECHOKE).sane(),
|
||||||
|
Flag::new("crtkill", L::ECHOKE).sane().hidden(),
|
||||||
|
Flag::new("flusho", L::FLUSHO),
|
||||||
|
Flag::new("extproc", L::EXTPROC),
|
||||||
|
];
|
||||||
|
|
||||||
|
// BSD's use u32 as baud rate, to using the enum is unnecessary.
|
||||||
|
#[cfg(not(any(
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "dragonfly",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "macos",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_os = "openbsd"
|
||||||
|
)))]
|
||||||
|
pub const BAUD_RATES: &[(&str, BaudRate)] = &[
|
||||||
|
("0", BaudRate::B0),
|
||||||
|
("50", BaudRate::B50),
|
||||||
|
("75", BaudRate::B75),
|
||||||
|
("110", BaudRate::B110),
|
||||||
|
("134", BaudRate::B134),
|
||||||
|
("150", BaudRate::B150),
|
||||||
|
("200", BaudRate::B200),
|
||||||
|
("300", BaudRate::B300),
|
||||||
|
("600", BaudRate::B600),
|
||||||
|
("1200", BaudRate::B1200),
|
||||||
|
("1800", BaudRate::B1800),
|
||||||
|
("2400", BaudRate::B2400),
|
||||||
|
("9600", BaudRate::B9600),
|
||||||
|
("19200", BaudRate::B19200),
|
||||||
|
("38400", BaudRate::B38400),
|
||||||
|
("57600", BaudRate::B57600),
|
||||||
|
("115200", BaudRate::B115200),
|
||||||
|
("230400", BaudRate::B230400),
|
||||||
|
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||||
|
("500000", BaudRate::B500000),
|
||||||
|
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||||
|
("576000", BaudRate::B576000),
|
||||||
|
#[cfg(any(target_os = "android", target_os = "linux",))]
|
||||||
|
("921600", BaudRate::B921600),
|
||||||
|
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||||
|
("1000000", BaudRate::B1000000),
|
||||||
|
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||||
|
("1152000", BaudRate::B1152000),
|
||||||
|
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||||
|
("1500000", BaudRate::B1500000),
|
||||||
|
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||||
|
("2000000", BaudRate::B2000000),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
all(target_os = "linux", not(target_arch = "sparc64"))
|
||||||
|
))]
|
||||||
|
("2500000", BaudRate::B2500000),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
all(target_os = "linux", not(target_arch = "sparc64"))
|
||||||
|
))]
|
||||||
|
("3000000", BaudRate::B3000000),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
all(target_os = "linux", not(target_arch = "sparc64"))
|
||||||
|
))]
|
||||||
|
("3500000", BaudRate::B3500000),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
all(target_os = "linux", not(target_arch = "sparc64"))
|
||||||
|
))]
|
||||||
|
("4000000", BaudRate::B4000000),
|
||||||
|
];
|
1
src/uu/stty/src/main.rs
Normal file
1
src/uu/stty/src/main.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
uucore::bin!(uu_stty);
|
401
src/uu/stty/src/stty.rs
Normal file
401
src/uu/stty/src/stty.rs
Normal file
|
@ -0,0 +1,401 @@
|
||||||
|
// * This file is part of the uutils coreutils package.
|
||||||
|
// *
|
||||||
|
// * For the full copyright and license information, please view the LICENSE file
|
||||||
|
// * that was distributed with this source code.
|
||||||
|
|
||||||
|
// spell-checker:ignore tcgetattr tcsetattr tcsanow tiocgwinsz tiocswinsz cfgetospeed ushort
|
||||||
|
|
||||||
|
mod flags;
|
||||||
|
|
||||||
|
use clap::{crate_version, Arg, ArgMatches, Command};
|
||||||
|
use nix::libc::{c_ushort, TIOCGWINSZ, TIOCSWINSZ};
|
||||||
|
use nix::sys::termios::{
|
||||||
|
cfgetospeed, tcgetattr, tcsetattr, ControlFlags, InputFlags, LocalFlags, OutputFlags, Termios,
|
||||||
|
};
|
||||||
|
use nix::{ioctl_read_bad, ioctl_write_ptr_bad};
|
||||||
|
use std::io::{self, stdout};
|
||||||
|
use std::ops::ControlFlow;
|
||||||
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
use uucore::error::{UResult, USimpleError};
|
||||||
|
use uucore::format_usage;
|
||||||
|
|
||||||
|
#[cfg(not(any(
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "dragonfly",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "macos",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_os = "openbsd"
|
||||||
|
)))]
|
||||||
|
use flags::BAUD_RATES;
|
||||||
|
use flags::{CONTROL_FLAGS, INPUT_FLAGS, LOCAL_FLAGS, OUTPUT_FLAGS};
|
||||||
|
|
||||||
|
const NAME: &str = "stty";
|
||||||
|
const USAGE: &str = "\
|
||||||
|
{} [-F DEVICE | --file=DEVICE] [SETTING]...
|
||||||
|
{} [-F DEVICE | --file=DEVICE] [-a|--all]
|
||||||
|
{} [-F DEVICE | --file=DEVICE] [-g|--save]";
|
||||||
|
const SUMMARY: &str = "Print or change terminal characteristics.";
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct Flag<T> {
|
||||||
|
name: &'static str,
|
||||||
|
flag: T,
|
||||||
|
show: bool,
|
||||||
|
sane: bool,
|
||||||
|
group: Option<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Flag<T> {
|
||||||
|
pub const fn new(name: &'static str, flag: T) -> Self {
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
flag,
|
||||||
|
show: true,
|
||||||
|
sane: false,
|
||||||
|
group: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn new_grouped(name: &'static str, flag: T, group: T) -> Self {
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
flag,
|
||||||
|
show: true,
|
||||||
|
sane: false,
|
||||||
|
group: Some(group),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn hidden(mut self) -> Self {
|
||||||
|
self.show = false;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn sane(mut self) -> Self {
|
||||||
|
self.sane = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait TermiosFlag: Copy {
|
||||||
|
fn is_in(&self, termios: &Termios, group: Option<Self>) -> bool;
|
||||||
|
fn apply(&self, termios: &mut Termios, val: bool);
|
||||||
|
}
|
||||||
|
|
||||||
|
mod options {
|
||||||
|
pub const ALL: &str = "all";
|
||||||
|
pub const SAVE: &str = "save";
|
||||||
|
pub const FILE: &str = "file";
|
||||||
|
pub const SETTINGS: &str = "settings";
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Options<'a> {
|
||||||
|
all: bool,
|
||||||
|
save: bool,
|
||||||
|
file: RawFd,
|
||||||
|
settings: Option<Vec<&'a str>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Options<'a> {
|
||||||
|
fn from(matches: &'a ArgMatches) -> io::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
all: matches.is_present(options::ALL),
|
||||||
|
save: matches.is_present(options::SAVE),
|
||||||
|
file: match matches.value_of(options::FILE) {
|
||||||
|
Some(_f) => todo!(),
|
||||||
|
None => stdout().as_raw_fd(),
|
||||||
|
},
|
||||||
|
settings: matches.values_of(options::SETTINGS).map(|v| v.collect()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Needs to be repr(C) because we pass it to the ioctl calls.
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct TermSize {
|
||||||
|
rows: c_ushort,
|
||||||
|
columns: c_ushort,
|
||||||
|
x: c_ushort,
|
||||||
|
y: c_ushort,
|
||||||
|
}
|
||||||
|
|
||||||
|
ioctl_read_bad!(
|
||||||
|
/// Get terminal window size
|
||||||
|
tiocgwinsz,
|
||||||
|
TIOCGWINSZ,
|
||||||
|
TermSize
|
||||||
|
);
|
||||||
|
|
||||||
|
ioctl_write_ptr_bad!(
|
||||||
|
/// Set terminal window size
|
||||||
|
tiocswinsz,
|
||||||
|
TIOCSWINSZ,
|
||||||
|
TermSize
|
||||||
|
);
|
||||||
|
|
||||||
|
#[uucore::main]
|
||||||
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
|
let args = args.collect_lossy();
|
||||||
|
|
||||||
|
let matches = uu_app().get_matches_from(args);
|
||||||
|
|
||||||
|
let opts = Options::from(&matches)?;
|
||||||
|
|
||||||
|
stty(&opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stty(opts: &Options) -> UResult<()> {
|
||||||
|
if opts.save && opts.all {
|
||||||
|
return Err(USimpleError::new(
|
||||||
|
1,
|
||||||
|
"the options for verbose and stty-readable output styles are mutually exclusive",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.settings.is_some() && (opts.save || opts.all) {
|
||||||
|
return Err(USimpleError::new(
|
||||||
|
1,
|
||||||
|
"when specifying an output style, modes may not be set",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Figure out the right error message for when tcgetattr fails
|
||||||
|
let mut termios = tcgetattr(opts.file).expect("Could not get terminal attributes");
|
||||||
|
|
||||||
|
if let Some(settings) = &opts.settings {
|
||||||
|
for setting in settings {
|
||||||
|
if let ControlFlow::Break(false) = apply_setting(&mut termios, setting) {
|
||||||
|
return Err(USimpleError::new(
|
||||||
|
1,
|
||||||
|
format!("invalid argument '{}'", setting),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tcsetattr(opts.file, nix::sys::termios::SetArg::TCSANOW, &termios)
|
||||||
|
.expect("Could not write terminal attributes");
|
||||||
|
} else {
|
||||||
|
print_settings(&termios, opts).expect("TODO: make proper error here from nix error");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_terminal_size(termios: &Termios, opts: &Options) -> nix::Result<()> {
|
||||||
|
let speed = cfgetospeed(termios);
|
||||||
|
|
||||||
|
// BSDs use a u32 for the baud rate, so we can simply print it.
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "dragonfly",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "macos",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_os = "openbsd"
|
||||||
|
))]
|
||||||
|
print!("speed {} baud; ", speed);
|
||||||
|
|
||||||
|
// Other platforms need to use the baud rate enum, so printing the right value
|
||||||
|
// becomes slightly more complicated.
|
||||||
|
#[cfg(not(any(
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "dragonfly",
|
||||||
|
target_os = "ios",
|
||||||
|
target_os = "macos",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_os = "openbsd"
|
||||||
|
)))]
|
||||||
|
for (text, baud_rate) in BAUD_RATES {
|
||||||
|
if *baud_rate == speed {
|
||||||
|
print!("speed {} baud; ", text);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.all {
|
||||||
|
let mut size = TermSize::default();
|
||||||
|
unsafe { tiocgwinsz(opts.file, &mut size as *mut _)? };
|
||||||
|
print!("rows {}; columns {}; ", size.rows, size.columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "redox"))]
|
||||||
|
{
|
||||||
|
// For some reason the normal nix Termios struct does not expose the line,
|
||||||
|
// so we get the underlying libc::termios struct to get that information.
|
||||||
|
let libc_termios: nix::libc::termios = termios.clone().into();
|
||||||
|
let line = libc_termios.c_line;
|
||||||
|
print!("line = {};", line);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_settings(termios: &Termios, opts: &Options) -> nix::Result<()> {
|
||||||
|
print_terminal_size(termios, opts)?;
|
||||||
|
print_flags(termios, opts, CONTROL_FLAGS);
|
||||||
|
print_flags(termios, opts, INPUT_FLAGS);
|
||||||
|
print_flags(termios, opts, OUTPUT_FLAGS);
|
||||||
|
print_flags(termios, opts, LOCAL_FLAGS);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_flags<T: TermiosFlag>(termios: &Termios, opts: &Options, flags: &[Flag<T>]) {
|
||||||
|
let mut printed = false;
|
||||||
|
for &Flag {
|
||||||
|
name,
|
||||||
|
flag,
|
||||||
|
show,
|
||||||
|
sane,
|
||||||
|
group,
|
||||||
|
} in flags
|
||||||
|
{
|
||||||
|
if !show {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let val = flag.is_in(termios, group);
|
||||||
|
if group.is_some() {
|
||||||
|
if val && (!sane || opts.all) {
|
||||||
|
print!("{} ", name);
|
||||||
|
printed = true;
|
||||||
|
}
|
||||||
|
} else if opts.all || val != sane {
|
||||||
|
if !val {
|
||||||
|
print!("-");
|
||||||
|
}
|
||||||
|
print!("{} ", name);
|
||||||
|
printed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if printed {
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply a single setting
|
||||||
|
///
|
||||||
|
/// The value inside the `Break` variant of the `ControlFlow` indicates whether
|
||||||
|
/// the setting has been applied.
|
||||||
|
fn apply_setting(termios: &mut Termios, s: &str) -> ControlFlow<bool> {
|
||||||
|
let (remove, name) = match s.strip_prefix('-') {
|
||||||
|
Some(s) => (true, s),
|
||||||
|
None => (false, s),
|
||||||
|
};
|
||||||
|
apply_flag(termios, CONTROL_FLAGS, name, remove)?;
|
||||||
|
apply_flag(termios, INPUT_FLAGS, name, remove)?;
|
||||||
|
apply_flag(termios, OUTPUT_FLAGS, name, remove)?;
|
||||||
|
apply_flag(termios, LOCAL_FLAGS, name, remove)?;
|
||||||
|
ControlFlow::Break(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply a flag to a slice of flags
|
||||||
|
///
|
||||||
|
/// The value inside the `Break` variant of the `ControlFlow` indicates whether
|
||||||
|
/// the setting has been applied.
|
||||||
|
fn apply_flag<T: TermiosFlag>(
|
||||||
|
termios: &mut Termios,
|
||||||
|
flags: &[Flag<T>],
|
||||||
|
input: &str,
|
||||||
|
remove: bool,
|
||||||
|
) -> ControlFlow<bool> {
|
||||||
|
for Flag {
|
||||||
|
name, flag, group, ..
|
||||||
|
} in flags
|
||||||
|
{
|
||||||
|
if input == *name {
|
||||||
|
// Flags with groups cannot be removed
|
||||||
|
// Since the name matches, we can short circuit and don't have to check the other flags.
|
||||||
|
if remove && group.is_some() {
|
||||||
|
return ControlFlow::Break(false);
|
||||||
|
}
|
||||||
|
// If there is a group, the bits for that group should be cleared before applying the flag
|
||||||
|
if let Some(group) = group {
|
||||||
|
group.apply(termios, false);
|
||||||
|
}
|
||||||
|
flag.apply(termios, !remove);
|
||||||
|
return ControlFlow::Break(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ControlFlow::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn uu_app<'a>() -> Command<'a> {
|
||||||
|
Command::new(uucore::util_name())
|
||||||
|
.name(NAME)
|
||||||
|
.version(crate_version!())
|
||||||
|
.override_usage(format_usage(USAGE))
|
||||||
|
.about(SUMMARY)
|
||||||
|
.infer_long_args(true)
|
||||||
|
.arg(
|
||||||
|
Arg::new(options::ALL)
|
||||||
|
.short('a')
|
||||||
|
.long(options::ALL)
|
||||||
|
.help("print all current settings in human-readable form"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new(options::SAVE)
|
||||||
|
.short('g')
|
||||||
|
.long(options::SAVE)
|
||||||
|
.help("print all current settings in a stty-readable form"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new(options::FILE)
|
||||||
|
.short('F')
|
||||||
|
.long(options::FILE)
|
||||||
|
.takes_value(true)
|
||||||
|
.value_hint(clap::ValueHint::FilePath)
|
||||||
|
.value_name("DEVICE")
|
||||||
|
.help("open and use the specified DEVICE instead of stdin"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new(options::SETTINGS)
|
||||||
|
.takes_value(true)
|
||||||
|
.multiple_values(true)
|
||||||
|
.help("settings to change"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TermiosFlag for ControlFlags {
|
||||||
|
fn is_in(&self, termios: &Termios, group: Option<Self>) -> bool {
|
||||||
|
termios.control_flags.contains(*self)
|
||||||
|
&& group.map_or(true, |g| !termios.control_flags.intersects(g - *self))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply(&self, termios: &mut Termios, val: bool) {
|
||||||
|
termios.control_flags.set(*self, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TermiosFlag for InputFlags {
|
||||||
|
fn is_in(&self, termios: &Termios, group: Option<Self>) -> bool {
|
||||||
|
termios.input_flags.contains(*self)
|
||||||
|
&& group.map_or(true, |g| !termios.input_flags.intersects(g - *self))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply(&self, termios: &mut Termios, val: bool) {
|
||||||
|
termios.input_flags.set(*self, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TermiosFlag for OutputFlags {
|
||||||
|
fn is_in(&self, termios: &Termios, group: Option<Self>) -> bool {
|
||||||
|
termios.output_flags.contains(*self)
|
||||||
|
&& group.map_or(true, |g| !termios.output_flags.intersects(g - *self))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply(&self, termios: &mut Termios, val: bool) {
|
||||||
|
termios.output_flags.set(*self, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TermiosFlag for LocalFlags {
|
||||||
|
fn is_in(&self, termios: &Termios, group: Option<Self>) -> bool {
|
||||||
|
termios.local_flags.contains(*self)
|
||||||
|
&& group.map_or(true, |g| !termios.local_flags.intersects(g - *self))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply(&self, termios: &mut Termios, val: bool) {
|
||||||
|
termios.local_flags.set(*self, val);
|
||||||
|
}
|
||||||
|
}
|
55
tests/by-util/test_stty.rs
Normal file
55
tests/by-util/test_stty.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
// spell-checker:ignore parenb parmrk ixany iuclc onlcr ofdel icanon noflsh
|
||||||
|
|
||||||
|
use crate::common::util::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
||||||
|
fn runs() {
|
||||||
|
new_ucmd!().succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore = "Fails because cargo test does not run in a tty"]
|
||||||
|
fn print_all() {
|
||||||
|
let res = new_ucmd!().succeeds();
|
||||||
|
|
||||||
|
// Random selection of flags to check for
|
||||||
|
for flag in [
|
||||||
|
"parenb", "parmrk", "ixany", "iuclc", "onlcr", "ofdel", "icanon", "noflsh",
|
||||||
|
] {
|
||||||
|
res.stdout_contains(flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn save_and_setting() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["--save", "nl0"])
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("when specifying an output style, modes may not be set");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn all_and_setting() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["--all", "nl0"])
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("when specifying an output style, modes may not be set");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn save_and_all() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["--save", "--all"])
|
||||||
|
.fails()
|
||||||
|
.stderr_contains(
|
||||||
|
"the options for verbose and stty-readable output styles are mutually exclusive",
|
||||||
|
);
|
||||||
|
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["--all", "--save"])
|
||||||
|
.fails()
|
||||||
|
.stderr_contains(
|
||||||
|
"the options for verbose and stty-readable output styles are mutually exclusive",
|
||||||
|
);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue