crossterm
crossterm copied to clipboard
Remove TPUT fallback when fetching terminal size.
Senario
Currently, TPUT is used in cases were /dev/tty
fails us. TPUT was introduced in #283 because crossterm was unable to fetch the terminal size in a subshell. However, this was a temporary solution and should be removed in a future version. The idea of TPUT was first discussed in #276 and further communicated in discord.
Why TPUT works but should be removed because:
- Is a subprocess
- Is used for terminal resize events, async executors would have to wait too long.
- (unresearched claim, but in another thread, it was mentioned ncurses was called by it)
- (unresearched claim, but in another thread, it was mentioned that the binary size would decrease after removing this)
Current Implementation
The current implementation tries to use /dev/tty
in all possible cases where it is possible. If this is not possible it uses STDOUT_FILENO
.
let file = File::open("/dev/tty").map(|file| (FileDesc::new(file.into_raw_fd(), true)));
let fd = if let Ok(file) = &file {
file.raw_fd()
} else {
// Fallback to libc::STDOUT_FILENO if /dev/tty is missing
STDOUT_FILENO
};
Then it will try to use the normal ioctl
to fetch the terminal size with the handle from above. If this is not possible 'then' it will use TPUT for it.
if let Ok(true) = wrap_with_result(unsafe { ioctl(fd, TIOCGWINSZ.into(), &mut size) }) {
Ok((size.ws_col, size.ws_row))
} else {
tput_size().ok_or_else(|| std::io::Error::last_os_error().into())
}
(https://github.com/crossterm-rs/crossterm/blob/master/src/terminal/sys/unix.rs#L26)
Example Code
Some code that fetches terminal size using different file descriptors. It could be useful in the search for a new approach.
Updated sample code
use libc::{ioctl, winsize, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO, TIOCGWINSZ};
use std::os::unix::io::AsRawFd;
fn main() {
let mut size = winsize {
ws_row: 0,
ws_col: 0,
ws_xpixel: 0,
ws_ypixel: 0,
};
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open("/dev/tty")
.unwrap();
unsafe { ioctl(file.as_raw_fd(), TIOCGWINSZ.into(), &mut size) };
println!("/dev/tty cols: {}, lines: {}", size.ws_col, size.ws_row);
unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ.into(), &mut size) };
println!("stdout cols: {}, lines: {}", size.ws_col, size.ws_row);
unsafe { ioctl(STDERR_FILENO, TIOCGWINSZ.into(), &mut size) };
println!("stderr cols: {}, lines: {}", size.ws_col, size.ws_row);
unsafe { ioctl(STDIN_FILENO, TIOCGWINSZ.into(), &mut size) };
println!("stdin cols: {}, lines: {}", size.ws_col, size.ws_row);
}
Improved Scenario
The new scenario works on macOS and Linux and is able to run in both a subshell and normal terminal. If this succeeds the new approach can be merged and released.
How about using https://crates.io/crates/terminfo instead? Is that an option?