Skip to main content

uncurses/terminal/
size.rs

1//! Terminal window-size detection.
2//!
3//! [`get_window_size`] queries the operating system for the visible terminal
4//! dimensions. Sizes are reported as [`Winsize`]: rows and columns in terminal
5//! cells, plus pixel dimensions when the platform exposes them.
6
7use std::io;
8
9#[cfg(unix)]
10use std::os::fd::{AsFd, AsRawFd};
11#[cfg(windows)]
12use std::os::windows::io::{AsHandle, AsRawHandle};
13#[cfg(windows)]
14use windows_sys::Win32::Foundation::HANDLE;
15
16/// Terminal dimensions in cells and pixels.
17///
18/// `row` and `col` are the cell dimensions used for terminal layout. `xpixel`
19/// and `ypixel` are optional pixel dimensions; they are `0` when the platform
20/// or terminal does not report pixel size.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct Winsize {
23    /// Number of rows (height in cells).
24    pub row: u16,
25    /// Number of columns (width in cells).
26    pub col: u16,
27    /// Width in pixels, or `0` when unknown.
28    pub xpixel: u16,
29    /// Height in pixels, or `0` when unknown.
30    pub ypixel: u16,
31}
32
33impl Default for Winsize {
34    /// Return the conventional fallback size of 80 columns by 24 rows.
35    ///
36    /// Pixel dimensions are unknown and set to `0`.
37    fn default() -> Self {
38        Self {
39            row: 24,
40            col: 80,
41            xpixel: 0,
42            ypixel: 0,
43        }
44    }
45}
46
47impl From<Winsize> for (u16, u16) {
48    /// Convert to a `(width, height)` cell pair.
49    ///
50    /// The returned tuple is `(col, row)`. Pixel fields are dropped.
51    fn from(ws: Winsize) -> Self {
52        (ws.col, ws.row)
53    }
54}
55
56/// Query the terminal size attached to `fd`.
57///
58/// This calls `TIOCGWINSZ` and returns the kernel-provided row, column, and
59/// pixel fields.
60///
61/// # Parameters
62///
63/// * `fd` — descriptor to query.
64///
65/// # Returns
66///
67/// The current [`Winsize`].
68///
69/// # Errors
70///
71/// Returns the OS error if `ioctl` fails, for example because `fd` is not a
72/// terminal.
73///
74/// # Panics
75///
76/// This function does not intentionally panic.
77#[cfg(unix)]
78pub fn get_window_size<F: AsFd>(fd: F) -> io::Result<Winsize> {
79    let mut ws: libc::winsize = unsafe { std::mem::zeroed() };
80    let ret = unsafe { libc::ioctl(fd.as_fd().as_raw_fd(), libc::TIOCGWINSZ, &mut ws) };
81    if ret < 0 {
82        return Err(io::Error::last_os_error());
83    }
84    Ok(Winsize {
85        row: ws.ws_row,
86        col: ws.ws_col,
87        xpixel: ws.ws_xpixel,
88        ypixel: ws.ws_ypixel,
89    })
90}
91
92/// Query the visible console window size attached to `h`.
93///
94/// Pixel dimensions are unavailable on this platform and are returned as `0`.
95///
96/// # Parameters
97///
98/// * `h` — console screen-buffer handle to query.
99///
100/// # Returns
101///
102/// The current [`Winsize`] in cells.
103///
104/// # Errors
105///
106/// Returns the OS error if `GetConsoleScreenBufferInfo` fails, for example
107/// because `h` is not a console output handle.
108///
109/// # Panics
110///
111/// This function does not intentionally panic.
112#[cfg(windows)]
113pub fn get_window_size<H: AsHandle>(h: H) -> io::Result<Winsize> {
114    use windows_sys::Win32::System::Console::{
115        CONSOLE_SCREEN_BUFFER_INFO, GetConsoleScreenBufferInfo,
116    };
117
118    let handle = h.as_handle().as_raw_handle() as HANDLE;
119    let mut info: CONSOLE_SCREEN_BUFFER_INFO = unsafe { std::mem::zeroed() };
120    if unsafe { GetConsoleScreenBufferInfo(handle, &mut info) } == 0 {
121        return Err(io::Error::last_os_error());
122    }
123    let col = (info.srWindow.Right - info.srWindow.Left + 1).max(0) as u16;
124    let row = (info.srWindow.Bottom - info.srWindow.Top + 1).max(0) as u16;
125    Ok(Winsize {
126        row,
127        col,
128        xpixel: 0,
129        ypixel: 0,
130    })
131}
132
133#[cfg(not(any(unix, windows)))]
134/// Query the terminal size on an unsupported platform.
135///
136/// Always returns [`io::ErrorKind::Unsupported`].
137pub fn get_window_size<T>(_: T) -> io::Result<Winsize> {
138    Err(io::Error::new(
139        io::ErrorKind::Unsupported,
140        "get_window_size is not implemented for this platform",
141    ))
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn default_size_is_80x24() {
150        let s = Winsize::default();
151        assert_eq!(s.col, 80);
152        assert_eq!(s.row, 24);
153    }
154}