1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-27 11:07:44 +00:00

Merge pull request #8221 from willshuttleworth/stty-size

stty: add options to set and print terminal size
This commit is contained in:
Daniel Hofstetter 2025-06-19 16:59:00 +02:00 committed by GitHub
commit 39c793c885
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 119 additions and 1 deletions

View file

@ -108,9 +108,20 @@ enum ControlCharMappingError {
MultipleChars,
}
enum SpecialSetting {
Rows(u16),
Cols(u16),
}
enum PrintSetting {
Size,
}
enum ArgOptions<'a> {
Flags(AllFlags<'a>),
Mapping((SpecialCharacterIndices, u8)),
Special(SpecialSetting),
Print(PrintSetting),
}
impl<'a> From<AllFlags<'a>> for ArgOptions<'a> {
@ -280,7 +291,35 @@ fn stty(opts: &Options) -> UResult<()> {
return Err(USimpleError::new(1, format!("invalid argument '{arg}'")));
}
valid_args.push(flag.into());
// not a valid control char or flag
} else if *arg == "rows" {
if let Some(rows) = args_iter.next() {
if let Some(n) = parse_rows_cols(rows) {
valid_args.push(ArgOptions::Special(SpecialSetting::Rows(n)));
} else {
return Err(USimpleError::new(
1,
format!("invalid integer argument: '{rows}'"),
));
}
} else {
return Err(USimpleError::new(1, format!("missing argument to '{arg}'")));
}
} else if *arg == "columns" || *arg == "cols" {
if let Some(cols) = args_iter.next() {
if let Some(n) = parse_rows_cols(cols) {
valid_args.push(ArgOptions::Special(SpecialSetting::Cols(n)));
} else {
return Err(USimpleError::new(
1,
format!("invalid integer argument: '{cols}'"),
));
}
} else {
return Err(USimpleError::new(1, format!("missing argument to '{arg}'")));
}
} else if *arg == "size" {
valid_args.push(ArgOptions::Print(PrintSetting::Size));
// not a valid option
} else {
return Err(USimpleError::new(1, format!("invalid argument '{arg}'")));
}
@ -294,6 +333,12 @@ fn stty(opts: &Options) -> UResult<()> {
match arg {
ArgOptions::Mapping(mapping) => apply_char_mapping(&mut termios, mapping),
ArgOptions::Flags(flag) => apply_setting(&mut termios, flag),
ArgOptions::Special(setting) => {
apply_special_setting(setting, opts.file.as_raw_fd())?;
}
ArgOptions::Print(setting) => {
print_special_setting(setting, opts.file.as_raw_fd())?;
}
}
}
tcsetattr(
@ -310,10 +355,30 @@ fn stty(opts: &Options) -> UResult<()> {
Ok(())
}
// GNU uses an unsigned 32 bit integer for row/col sizes, but then wraps around 16 bits
// this function returns Some(n), where n is a u16 row/col size, or None if the string arg cannot be parsed as a u32
fn parse_rows_cols(arg: &str) -> Option<u16> {
if let Ok(n) = arg.parse::<u32>() {
return Some((n % (u16::MAX as u32 + 1)) as u16);
}
None
}
fn check_flag_group<T>(flag: &Flag<T>, remove: bool) -> bool {
remove && flag.group.is_some()
}
fn print_special_setting(setting: &PrintSetting, fd: i32) -> nix::Result<()> {
match setting {
PrintSetting::Size => {
let mut size = TermSize::default();
unsafe { tiocgwinsz(fd, &raw mut size)? };
println!("{} {}", size.rows, size.columns);
}
}
Ok(())
}
fn print_terminal_size(termios: &Termios, opts: &Options) -> nix::Result<()> {
let speed = cfgetospeed(termios);
@ -588,6 +653,17 @@ fn apply_char_mapping(termios: &mut Termios, mapping: &(SpecialCharacterIndices,
termios.control_chars[mapping.0 as usize] = mapping.1;
}
fn apply_special_setting(setting: &SpecialSetting, fd: i32) -> nix::Result<()> {
let mut size = TermSize::default();
unsafe { tiocgwinsz(fd, &raw mut size)? };
match setting {
SpecialSetting::Rows(n) => size.rows = *n,
SpecialSetting::Cols(n) => size.columns = *n,
}
unsafe { tiocswinsz(fd, &raw mut size)? };
Ok(())
}
// GNU stty defines some valid values for the control character mappings
// 1. Standard character, can be a a single char (ie 'C') or hat notation (ie '^C')
// 2. Integer

View file

@ -48,6 +48,14 @@ fn all_and_setting() {
.stderr_contains("when specifying an output style, modes may not be set");
}
#[test]
fn all_and_print_setting() {
new_ucmd!()
.args(&["--all", "size"])
.fails()
.stderr_contains("when specifying an output style, modes may not be set");
}
#[test]
fn save_and_all() {
new_ucmd!()
@ -201,3 +209,37 @@ fn set_mapping() {
.succeeds()
.stdout_contains("intr = ^C");
}
#[test]
fn row_column_sizes() {
new_ucmd!()
.args(&["rows", "-1"])
.fails()
.stderr_contains("invalid integer argument: '-1'");
new_ucmd!()
.args(&["columns", "-1"])
.fails()
.stderr_contains("invalid integer argument: '-1'");
// overflow the u32 used for row/col counts
new_ucmd!()
.args(&["cols", "4294967296"])
.fails()
.stderr_contains("invalid integer argument: '4294967296'");
new_ucmd!()
.args(&["rows", ""])
.fails()
.stderr_contains("invalid integer argument: ''");
new_ucmd!()
.args(&["columns"])
.fails()
.stderr_contains("missing argument to 'columns'");
new_ucmd!()
.args(&["rows"])
.fails()
.stderr_contains("missing argument to 'rows'");
}