crossterm icon indicating copy to clipboard operation
crossterm copied to clipboard

Remove TPUT fallback when fetching terminal size.

Open TimonPost opened this issue 4 years ago • 1 comments

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.

TimonPost avatar Apr 21 '20 17:04 TimonPost

How about using https://crates.io/crates/terminfo instead? Is that an option?

wooster0 avatar Mar 21 '21 09:03 wooster0