8000 Merge pull request #8221 from willshuttleworth/stty-size · uutils/coreutils@39c793c · GitHub
[go: up one dir, main page]

Skip to content

Commit 39c793c

Browse files
authored
Merge pull request #8221 from willshuttleworth/stty-size
stty: add options to set and print terminal size
2 parents e324e2c + 10f8d77 commit 39c793c

File tree

2 files changed

+119
-1
lines changed

2 files changed

+119
-1
lines changed

src/uu/stty/src/stty.rs

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,20 @@ enum ControlCharMappingError {
108108
MultipleChars,
109109
}
110110

111+
enum SpecialSetting {
112+
Rows(u16),
113+
Cols(u16),
114+
}
115+
116+
enum PrintSetting {
117+
Size,
118+
}
119+
111120
enum ArgOptions<'a> {
112121
Flags(AllFlags<'a>),
113122
Mapping((SpecialCharacterIndices, u8)),
123+
Special(SpecialSetting),
124+
Print(PrintSetting),
114125
}
115126

116127
impl<'a> From<AllFlags<'a>> for ArgOptions<'a> {
@@ -280,7 +291,35 @@ fn stty(opts: &Options) -> UResult<()> {
280291
return Err(USimpleError::new(1, format!("invalid argument '{arg}'")));
281292
}
282293
valid_args.push(flag.into());
283-
// not a valid control char or flag
294+
} else if *arg == "rows" {
295+
if let Some(rows) = args_iter.next() {
296+
if let Some(n) = parse_rows_cols(rows) {
297+
valid_args.push(ArgOptions::Special(SpecialSetting::Rows(n)));
298+
} else {
299+
return Err(USimpleError::new(
300+
1,
301+
format!("invalid integer argument: '{rows}'"),
302+
));
303+
}
304+
} else {
305+
return Err(USimpleError::new(1, format!("missing argument to '{arg}'")));
306+
}
307+
} else if *arg == "columns" || *arg == "cols" {
308+
if let Some(cols) = args_iter.next() {
309+
if let Some(n) = parse_rows_cols(cols) {
310+
valid_args.push(ArgOptions::Special(SpecialSetting::Cols(n)));
311+
} else {
312+
return Err(USimpleError::new(
313+
1,
314+
format!("invalid integer argument: '{cols}'"),
315+
));
316+
}
317+
} else {
318+
return Err(USimpleError::new(1, format!("missing argument to '{arg}'")));
319+
}
320+
} else if *arg == "size" {
321+
valid_args.push(ArgOptions::Print(PrintSetting::Size));
322+
// not a valid option
284323
} else {
285324
return Err(USimpleError::new(1, format!("invalid argument '{arg}'")));
286325
}
@@ -294,6 +333,12 @@ fn stty(opts: &Options) -> UResult<()> {
294333
match arg {
295334
ArgOptions::Mapping(mapping) => apply_char_mapping(&mut termios, mapping),
296335
ArgOptions::Flags(flag) => apply_setting(&mut termios, flag),
336+
ArgOptions::Special(setting) => {
337+
apply_special_setting(setting, opts.file.as_raw_fd())?;
338+
}
339+
ArgOptions::Print(setting) => {
340+
print_special_setting(setting, opts.file.as_raw_fd())?;
341+
}
297342
}
298343
}
299344
tcsetattr(
@@ -310,10 +355,30 @@ fn stty(opts: &Options) -> UResult<()> {
310355
Ok(())
311356
}
312357

358+
// GNU uses an unsigned 32 bit integer for row/col sizes, but then wraps around 16 bits
359+
// 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
360+
fn parse_rows_cols(arg: &str) -> Option<u16> {
361+
if let Ok(n) = arg.parse::<u32>() {
362+
return Some((n % (u16::MAX as u32 + 1)) as u16);
363+
}
364+
None
365+
}
366+
313367
fn check_flag_group<T>(flag: &Flag<T>, remove: bool) -> bool {
314368
remove && flag.group.is_some()
315369
}
316370

371+
fn print_special_setting(setting: &PrintSetting, fd: i32) -> nix::Result<()> {
372+
match setting {
373+
PrintSetting::Size => {
374+
let mut size = TermSize::default();
375+
unsafe { tiocgwinsz(fd, &raw mut size)? };
376+
println!("{} {}", size.rows, size.columns);
377+
}
378+
}
379+
Ok(())
380+
}
381+
317382
fn print_terminal_size(termios: &Termios, opts: &Options) -> nix::Result<()> {
318383
let speed = cfgetospeed(termios);
319384

@@ -588,6 +653,17 @@ fn apply_char_mapping(termios: &mut Termios, mapping: &(SpecialCharacterIndices,
588653
termios.control_chars[mapping.0 as usize] = mapping.1;
589654
}
590655

656+
fn apply_special_setting(setting: &SpecialSetting, fd: i32) -> nix::Result<()> {
657+
let mut size = TermSize::default();
658+
unsafe { tiocgwinsz(fd, &raw mut size)? };
659+
match setting {
660+
SpecialSetting::Rows(n) => size.rows = *n,
661+
SpecialSetting::Cols(n) => size.columns = *n,
662+
}
663+
unsafe { tiocswinsz(fd, &raw mut size)? };
664+
Ok(())
665+
}
666+
591667
// GNU stty defines some valid values for the control character mappings
592668
// 1. Standard character, can be a a single char (ie 'C') or hat notation (ie '^C')
593669
// 2. Integer

tests/by-util/test_stty.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ fn all_and_setting() {
4848
.stderr_contains("when specifying an output style, modes may not be set");
4949
}
5050

51+
#[test]
52+
fn all_and_print_setting() {
53+
new_ucmd!()
54+
.args(&["--all", "size"])
55+
.fails()
56+
.stderr_contains("when specifying an output style, modes may not be set");
57+
}
58+
5159
#[test]
5260
fn save_and_all() {
5361
new_ucmd!()
@@ -201,3 +209,37 @@ fn set_mapping() {
201209
.succeeds()
202210
.stdout_contains("intr = ^C");
203211
}
212+
213+
#[test]
214+
fn row_column_sizes() {
215+
new_ucmd!()
216+
.args(&["rows", "-1"])
217+
.fails()
218+
.stderr_contains("invalid integer argument: '-1'");
219+
220+
new_ucmd!()
221+
.args(&["columns", "-1"])
222+
.fails()
223+
.stderr_contains("invalid integer argument: '-1'");
224+
225+
// overflow the u32 used for row/col counts
226+
new_ucmd!()
227+
.args(&["cols", "4294967296"])
228+
.fails()
229+
.stderr_contains("invalid integer argument: '4294967296'");
230+
231+
new_ucmd!()
232+
.args(&["rows", ""])
233+
.fails()
234+
.stderr_contains("invalid integer argument: ''");
235+
236+
new_ucmd!()
237+
.args(&["columns"])
238+
.fails()
239+
.stderr_contains("missing argument to 'columns'");
240+
241+
new_ucmd!()
242+
.args(&["rows"])
243+
.fails()
244+
.stderr_contains("missing argument to 'rows'");
245+
}

0 commit comments

Comments
 (0)
0