Skip to content

Commit 10c54b0

Browse files
authored
Add window_size() for unix (#790)
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".
1 parent ff01914 commit 10c54b0

File tree

5 files changed

+62
-13
lines changed

5 files changed

+62
-13
lines changed

examples/is_tty.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ use crossterm::{
66
use std::io::{stdin, stdout};
77

88
pub fn main() {
9-
println!("{:?}", size().unwrap());
9+
println!("size: {:?}", size().unwrap());
1010
execute!(stdout(), SetSize(10, 10)).unwrap();
11-
println!("{:?}", size().unwrap());
11+
println!("resized: {:?}", size().unwrap());
1212

1313
if stdin().is_tty() {
1414
println!("Is TTY");

src/terminal.rs

+17
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,23 @@ pub fn size() -> io::Result<(u16, u16)> {
137137
sys::size()
138138
}
139139

140+
#[derive(Debug)]
141+
pub struct WindowSize {
142+
pub rows: u16,
143+
pub columns: u16,
144+
pub width: u16,
145+
pub height: u16,
146+
}
147+
148+
/// Returns the terminal size `[WindowSize]`.
149+
///
150+
/// The width and height in pixels may not be reliably implemented or default to 0.
151+
/// For unix, https://man7.org/linux/man-pages/man4/tty_ioctl.4.html documents them as "unused".
152+
/// For windows it is not implemented.
153+
pub fn window_size() -> io::Result<WindowSize> {
154+
sys::window_size()
155+
}
156+
140157
/// Disables line wrapping.
141158
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
142159
pub struct DisableLineWrap;

src/terminal/sys.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44
#[cfg(feature = "events")]
55
pub use self::unix::supports_keyboard_enhancement;
66
#[cfg(unix)]
7-
pub(crate) use self::unix::{disable_raw_mode, enable_raw_mode, is_raw_mode_enabled, size};
7+
pub(crate) use self::unix::{
8+
disable_raw_mode, enable_raw_mode, window_size, is_raw_mode_enabled, size,
9+
};
810
#[cfg(windows)]
911
#[cfg(feature = "events")]
1012
pub use self::windows::supports_keyboard_enhancement;
1113
#[cfg(windows)]
1214
pub(crate) use self::windows::{
13-
clear, disable_raw_mode, enable_raw_mode, is_raw_mode_enabled, scroll_down, scroll_up,
14-
set_size, set_window_title, size,
15+
clear, disable_raw_mode, enable_raw_mode, window_size, is_raw_mode_enabled, scroll_down,
16+
scroll_up, set_size, set_window_title, size,
1517
};
1618

1719
#[cfg(windows)]

src/terminal/sys/unix.rs

+27-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
//! UNIX related logic for terminal manipulation.
22
3-
use crate::terminal::sys::file_descriptor::{tty_fd, FileDesc};
3+
use crate::terminal::{
4+
sys::file_descriptor::{tty_fd, FileDesc},
5+
WindowSize,
6+
};
47
use libc::{
58
cfmakeraw, ioctl, tcgetattr, tcsetattr, termios as Termios, winsize, STDOUT_FILENO, TCSANOW,
69
TIOCGWINSZ,
@@ -20,8 +23,19 @@ pub(crate) fn is_raw_mode_enabled() -> bool {
2023
TERMINAL_MODE_PRIOR_RAW_MODE.lock().is_some()
2124
}
2225

26+
impl From<winsize> for WindowSize {
27+
fn from(size: winsize) -> WindowSize {
28+
WindowSize {
29+
columns: size.ws_col,
30+
rows: size.ws_row,
31+
width: size.ws_xpixel,
32+
height: size.ws_ypixel,
33+
}
34+
}
35+
}
36+
2337
#[allow(clippy::useless_conversion)]
24-
pub(crate) fn size() -> io::Result<(u16, u16)> {
38+
pub(crate) fn window_size() -> io::Result<WindowSize> {
2539
// http://rosettacode.org/wiki/Terminal_control/Dimensions#Library:_BSD_libc
2640
let mut size = winsize {
2741
ws_row: 0,
@@ -38,11 +52,17 @@ pub(crate) fn size() -> io::Result<(u16, u16)> {
3852
STDOUT_FILENO
3953
};
4054

41-
if wrap_with_result(unsafe { ioctl(fd, TIOCGWINSZ.into(), &mut size) }).is_ok()
42-
&& size.ws_col != 0
43-
&& size.ws_row != 0
44-
{
45-
return Ok((size.ws_col, size.ws_row));
55+
if wrap_with_result(unsafe { ioctl(fd, TIOCGWINSZ.into(), &mut size) }).is_ok() {
56+
return Ok(size.into());
57+
}
58+
59+
Err(std::io::Error::last_os_error().into())
60+
}
61+
62+
#[allow(clippy::useless_conversion)]
63+
pub(crate) fn size() -> io::Result<(u16, u16)> {
64+
if let Ok(window_size) = window_size() {
65+
return Ok((window_size.columns, window_size.rows));
4666
}
4767

4868
tput_size().ok_or_else(|| std::io::Error::last_os_error().into())

src/terminal/sys/windows.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ use winapi::{
99
um::wincon::{SetConsoleTitleW, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT},
1010
};
1111

12-
use crate::{cursor, terminal::ClearType};
12+
use crate::{
13+
cursor,
14+
terminal::{ClearType, WindowSize},
15+
};
1316

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

64+
pub(crate) fn window_size() -> io::Result<WindowSize> {
65+
Err(io::Error::new(
66+
io::ErrorKind::Unsupported,
67+
"Window pixel size not implemented for the Windows API.",
68+
))
69+
}
70+
6171
/// Queries the terminal's support for progressive keyboard enhancement.
6272
///
6373
/// This always returns `Ok(false)` on Windows.

0 commit comments

Comments
 (0)