1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// The following was originally taken and adapated from exa source
// repo: https://github.com/ogham/exa
// commit: b9eb364823d0d4f9085eb220233c704a13d0f611
// license: MIT - Copyright (c) 2014 Benjamin Sago

//! System calls for getting the terminal size.
//!
//! Getting the terminal size is performed using an ioctl command that takes
//! the file handle to the terminal -- which in this case, is stdout -- and
//! populates a structure containing the values.
//!
//! The size is needed when the user wants the output formatted into columns:
//! the default grid view, or the hybrid grid-details view.

#[cfg(not(target_os = "windows"))]
extern crate libc;
#[cfg(target_os = "windows")]
extern crate kernel32;
#[cfg(target_os = "windows")]
extern crate winapi;

pub use platform::dimensions;

#[cfg(any(target_os = "linux",
          target_os = "android",
          target_os = "macos",
          target_os = "ios",
          target_os = "bitrig",
          target_os = "dragonfly",
          target_os = "freebsd",
          target_os = "netbsd",
          target_os = "openbsd",
          target_os = "solaris"))]
mod platform {
    use std::mem::zeroed;
    use libc::{STDOUT_FILENO, c_int, c_ulong, winsize};

    // Unfortunately the actual command is not standardised...
    #[cfg(any(target_os = "linux", target_os = "android"))]
    static TIOCGWINSZ: c_ulong = 0x5413;

    #[cfg(any(target_os = "macos",
              target_os = "ios",
              target_os = "bitrig",
              target_os = "dragonfly",
              target_os = "freebsd",
              target_os = "netbsd",
              target_os = "openbsd"))]
    static TIOCGWINSZ: c_ulong = 0x40087468;

    #[cfg(target_os = "solaris")]
    static TIOCGWINSZ: c_ulong = 0x5468;

    extern "C" {
        fn ioctl(fd: c_int, request: c_ulong, ...) -> c_int;
    }

    /// Runs the ioctl command. Returns (0, 0) if output is not to a terminal, or
    /// there is an error. (0, 0) is an invalid size to have anyway, which is why
    /// it can be used as a nil value.
    unsafe fn get_dimensions() -> winsize {
        let mut window: winsize = zeroed();
        let result = ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut window);

        if result == -1 {
            zeroed()
        } else {
            window
        }
    }

    /// Query the current processes's output, returning its width and height as a
    /// number of characters. Returns `None` if the output isn't to a terminal.
    pub fn dimensions() -> Option<(usize, usize)> {
        let w = unsafe { get_dimensions() };

        if w.ws_col == 0 || w.ws_row == 0 {
            None
        } else {
            Some((w.ws_col as usize, w.ws_row as usize))
        }
    }
}

#[cfg(target_os = "windows")]
mod platform {
    use kernel32::{GetConsoleScreenBufferInfo, GetStdHandle};
    use winapi::{CONSOLE_SCREEN_BUFFER_INFO, COORD, SMALL_RECT, STD_OUTPUT_HANDLE};

    pub fn dimensions() -> Option<(usize, usize)> {
        let null_coord = COORD{
            X: 0,
            Y: 0,
        };
        let null_smallrect = SMALL_RECT{
            Left: 0,
            Top: 0,
            Right: 0,
            Bottom: 0,
        };

        let stdout_h = unsafe { GetStdHandle(STD_OUTPUT_HANDLE) };
        let mut console_data = CONSOLE_SCREEN_BUFFER_INFO{
            dwSize: null_coord,
            dwCursorPosition: null_coord,
            wAttributes: 0,
            srWindow: null_smallrect,
            dwMaximumWindowSize: null_coord,
        };

        if unsafe { GetConsoleScreenBufferInfo(stdout_h, &mut console_data) } != 0 {
            Some(((console_data.srWindow.Right - console_data.srWindow.Left) as usize, (console_data.srWindow.Bottom - console_data.srWindow.Top) as usize))
        } else {
            None
        }
    }
}

// makes project compilable on unsupported platforms
#[cfg(not(any(target_os = "linux",
              target_os = "android",
              target_os = "macos",
              target_os = "ios",
              target_os = "bitrig",
              target_os = "dragonfly",
              target_os = "freebsd",
              target_os = "netbsd",
              target_os = "openbsd",
              target_os = "solaris",
              target_os = "windows")))]
mod platform {
    pub fn dimensions() -> Option<(usize, usize)> {
        None
    }
}