Skip to content

Commit

Permalink
Add window_size() for unix (#790)
Browse files Browse the repository at this point in the history
It is possible to render images in terminals with protocols such as Sixel,
iTerm2's, or Kitty's. For a basic sixel or iTerm2 image printing, it is
sufficient to print some escape sequence with the data, e.g. cat image just
works, the image is displayed and enough lines are scrolled.

But for more sophisticated usage of images, such as TUIs, it is necessary to
know exactly what area that image would cover, in terms of columns/rows of
characters. Then it would be possible to e.g. resize the image to a size that
fits a col/row area precisely, not overdraw the image area, accommodate layouts,
etc.

Thus, provide the window size in pixel width/height, in addition to cols/rows.

The windows implementation always returns a "not implemented" error. The
windows API exposes a font-size, but in logical units, not pixels.

This could be further extended to expose either "logical window size",
or "pixel font size" and "logical font size".
  • Loading branch information
benjajaja committed Aug 5, 2023
1 parent ff01914 commit 10c54b0
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 13 deletions.
4 changes: 2 additions & 2 deletions examples/is_tty.rs
Expand Up @@ -6,9 +6,9 @@ use crossterm::{
use std::io::{stdin, stdout};

pub fn main() {
println!("{:?}", size().unwrap());
println!("size: {:?}", size().unwrap());
execute!(stdout(), SetSize(10, 10)).unwrap();
println!("{:?}", size().unwrap());
println!("resized: {:?}", size().unwrap());

if stdin().is_tty() {
println!("Is TTY");
Expand Down
17 changes: 17 additions & 0 deletions src/terminal.rs
Expand Up @@ -137,6 +137,23 @@ pub fn size() -> io::Result<(u16, u16)> {
sys::size()
}

#[derive(Debug)]
pub struct WindowSize {
pub rows: u16,
pub columns: u16,
pub width: u16,
pub height: u16,
}

/// Returns the terminal size `[WindowSize]`.
///
/// The width and height in pixels may not be reliably implemented or default to 0.
/// For unix, https://man7.org/linux/man-pages/man4/tty_ioctl.4.html documents them as "unused".
/// For windows it is not implemented.
pub fn window_size() -> io::Result<WindowSize> {
sys::window_size()
}

/// Disables line wrapping.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DisableLineWrap;
Expand Down
8 changes: 5 additions & 3 deletions src/terminal/sys.rs
Expand Up @@ -4,14 +4,16 @@
#[cfg(feature = "events")]
pub use self::unix::supports_keyboard_enhancement;
#[cfg(unix)]
pub(crate) use self::unix::{disable_raw_mode, enable_raw_mode, is_raw_mode_enabled, size};
pub(crate) use self::unix::{
disable_raw_mode, enable_raw_mode, window_size, is_raw_mode_enabled, size,
};
#[cfg(windows)]
#[cfg(feature = "events")]
pub use self::windows::supports_keyboard_enhancement;
#[cfg(windows)]
pub(crate) use self::windows::{
clear, disable_raw_mode, enable_raw_mode, is_raw_mode_enabled, scroll_down, scroll_up,
set_size, set_window_title, size,
clear, disable_raw_mode, enable_raw_mode, window_size, is_raw_mode_enabled, scroll_down,
scroll_up, set_size, set_window_title, size,
};

#[cfg(windows)]
Expand Down
34 changes: 27 additions & 7 deletions src/terminal/sys/unix.rs
@@ -1,6 +1,9 @@
//! UNIX related logic for terminal manipulation.

use crate::terminal::sys::file_descriptor::{tty_fd, FileDesc};
use crate::terminal::{
sys::file_descriptor::{tty_fd, FileDesc},
WindowSize,
};
use libc::{
cfmakeraw, ioctl, tcgetattr, tcsetattr, termios as Termios, winsize, STDOUT_FILENO, TCSANOW,
TIOCGWINSZ,
Expand All @@ -20,8 +23,19 @@ pub(crate) fn is_raw_mode_enabled() -> bool {
TERMINAL_MODE_PRIOR_RAW_MODE.lock().is_some()
}

impl From<winsize> for WindowSize {
fn from(size: winsize) -> WindowSize {
WindowSize {
columns: size.ws_col,
rows: size.ws_row,
width: size.ws_xpixel,
height: size.ws_ypixel,
}
}
}

#[allow(clippy::useless_conversion)]
pub(crate) fn size() -> io::Result<(u16, u16)> {
pub(crate) fn window_size() -> io::Result<WindowSize> {
// http://rosettacode.org/wiki/Terminal_control/Dimensions#Library:_BSD_libc
let mut size = winsize {
ws_row: 0,
Expand All @@ -38,11 +52,17 @@ pub(crate) fn size() -> io::Result<(u16, u16)> {
STDOUT_FILENO
};

if wrap_with_result(unsafe { ioctl(fd, TIOCGWINSZ.into(), &mut size) }).is_ok()
&& size.ws_col != 0
&& size.ws_row != 0
{
return Ok((size.ws_col, size.ws_row));
if wrap_with_result(unsafe { ioctl(fd, TIOCGWINSZ.into(), &mut size) }).is_ok() {
return Ok(size.into());
}

Err(std::io::Error::last_os_error().into())
}

#[allow(clippy::useless_conversion)]
pub(crate) fn size() -> io::Result<(u16, u16)> {
if let Ok(window_size) = window_size() {
return Ok((window_size.columns, window_size.rows));
}

tput_size().ok_or_else(|| std::io::Error::last_os_error().into())
Expand Down
12 changes: 11 additions & 1 deletion src/terminal/sys/windows.rs
Expand Up @@ -9,7 +9,10 @@ use winapi::{
um::wincon::{SetConsoleTitleW, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT},
};

use crate::{cursor, terminal::ClearType};
use crate::{
cursor,
terminal::{ClearType, WindowSize},
};

/// bits which can't be set in raw mode
const NOT_RAW_MODE_MASK: DWORD = ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT;
Expand Down Expand Up @@ -58,6 +61,13 @@ pub(crate) fn size() -> io::Result<(u16, u16)> {
))
}

pub(crate) fn window_size() -> io::Result<WindowSize> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"Window pixel size not implemented for the Windows API.",
))
}

/// Queries the terminal's support for progressive keyboard enhancement.
///
/// This always returns `Ok(false)` on Windows.
Expand Down

0 comments on commit 10c54b0

Please sign in to comment.