Add experimental Windows support for tutil::screen
[tutil.git] / src / screen / unix.rs
blobc8d68c7b540ae53c9ef0f24447db22b8d144edd7
1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 //! Unix implementation of `tutil::screen`, tested on Linux, FreeBSD and macOS.
7 use super::{Width, Height};
9 use std::os::raw::c_ushort;
10 use libc::{ioctl, isatty, STDOUT_FILENO, TIOCGWINSZ};
12 /// The struct required by the `TIOCGWINSZ` syscall; specified in the following
13 /// [man page](http://www.delorie.com/djgpp/doc/libc/libc_495.html).
14 #[derive(Debug)]
15 struct WinSize {
16     /// Rows, in characters.
17     ws_row: c_ushort,
18     /// Columns, in characters.
19     ws_col: c_ushort,
20     /// Horizontal size, in pixels.
21     ws_xpixel: c_ushort,
22     /// Vertical size, in pixels.
23     ws_ypixel: c_ushort,
26 /// Returns the terminal screen size.
27 ///
28 /// Returns `None` if the screen size is `(0, 0)` or is not able to be
29 /// determined.
30 pub fn size() -> Option<(Width, Height)> {
31     let is_tty = unsafe { isatty(STDOUT_FILENO) == 1 };
33     if !is_tty { return None; }
35     let (width, height) = unsafe {
36         let mut winsize = WinSize { ws_row: 0, ws_col: 0, ws_xpixel: 0, ws_ypixel: 0 };
37         ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut winsize);
39         (winsize.ws_col, winsize.ws_row)
40     };
42     Some((Width(width), Height(height)))
45 /// Returns the terminal screen width.
46 ///
47 /// Returns `None` if the terminal width is detected as being <= 0 columns or is
48 /// not able to be determined at all.
49 pub fn width() -> Option<Width> {
50     let size = size();
52     if let Some((Width(width), Height(_))) = size {
53         Some(Width(width))
54     } else {
55         None
56     }
59 /// Returns the terminal height.
60 ///
61 /// Returns `None` if the terminal height is detected as being <= 0 rows or is
62 /// not able to be determined at all.
63 pub fn height() -> Option<Height> {
64     let size = size();
66     if let Some((Width(_), Height(height))) = size {
67         Some(Height(height))
68     } else {
69         None
70     }
73 #[cfg(test)]
74 mod test {
75     use super::*;
76     use super::super::{Width, Height};
78     use std::process::{Command, Stdio};
80     #[cfg(target_os = "linux")]
81     fn create_command() -> Command {
82         let mut cmd = Command::new("stty");
83         cmd.arg("--file");
84         cmd.arg("/dev/stderr");
85         cmd.arg("size");
86         cmd.stderr(Stdio::inherit());
87         cmd
88     }
90     #[cfg(any(target_os = "freebsd", target_os = "macos"))]
91     fn create_command() -> Command {
92         let mut cmd = Command::new("stty");
93         cmd.arg("-f");
94         cmd.arg("/dev/stderr");
95         cmd.arg("size");
96         cmd.stderr(Stdio::inherit());
97         cmd
98     }
100     #[test]
101     fn correct_size() {
102         let output = create_command().output().unwrap();
103         let stdout = String::from_utf8(output.stdout).unwrap();
104         assert!(output.status.success());
106         let cols = u16::from_str_radix(stdout.split_whitespace().last().unwrap(), 10).unwrap();
107         let rows = u16::from_str_radix(stdout.split_whitespace().next().unwrap(), 10).unwrap();
109         if let Some((Width(width), Height(height))) = size() {
110             assert_eq!(width, cols);
111             assert_eq!(height, rows);
112         }
113     }
115     #[test]
116     fn correct_width() {
117         let output = create_command().output().unwrap();
118         let stdout = String::from_utf8(output.stdout).unwrap();
119         assert!(output.status.success());
121         let cols = u16::from_str_radix(stdout.split_whitespace().last().unwrap(), 10).unwrap();
123         if let Some((Width(width), Height(_))) = size() {
124             assert_eq!(width, cols);
125         }
126     }
128     #[test]
129     fn correct_height() {
130         let output = create_command().output().unwrap();
131         let stdout = String::from_utf8(output.stdout).unwrap();
132         assert!(output.status.success());
134         let rows = u16::from_str_radix(stdout.split_whitespace().next().unwrap(), 10).unwrap();
136         if let Some((Width(_), Height(height))) = size() {
137             assert_eq!(height, rows);
138         }
139     }