conpty/
console.rs

1//! Module contains a handy functions for terminal.
2
3use windows::core::Result as WinResult;
4use windows::Win32::Foundation::WAIT_OBJECT_0;
5use windows::Win32::{
6    Foundation::HANDLE,
7    System::{
8        Console::{
9            GetConsoleMode, GetStdHandle, SetConsoleMode, CONSOLE_MODE,
10            DISABLE_NEWLINE_AUTO_RETURN, ENABLE_ECHO_INPUT, ENABLE_EXTENDED_FLAGS,
11            ENABLE_INSERT_MODE, ENABLE_LINE_INPUT, ENABLE_MOUSE_INPUT, ENABLE_PROCESSED_INPUT,
12            ENABLE_QUICK_EDIT_MODE, ENABLE_VIRTUAL_TERMINAL_INPUT, STD_ERROR_HANDLE,
13            STD_INPUT_HANDLE, STD_OUTPUT_HANDLE,
14        },
15        Threading::WaitForSingleObject,
16    },
17};
18
19use crate::error::Error;
20
21/// Console represents a terminal session with opened stdin, stdout and stderr.
22#[derive(Debug, Clone)]
23pub struct Console {
24    stdin: HANDLE,
25    stdout: HANDLE,
26    stderr: HANDLE,
27    stdin_mode: CONSOLE_MODE,
28    stdout_mode: CONSOLE_MODE,
29    stderr_mode: CONSOLE_MODE,
30}
31
32impl Console {
33    /// Creates a console from default stdin, stdout and stderr.
34    pub fn current() -> Result<Self, Error> {
35        // We don't close these handle on drop because:
36        //  It is not required to CloseHandle when done with the handle retrieved from GetStdHandle.
37        //  The returned value is simply a copy of the value stored in the process table.
38        let stdin = unsafe { GetStdHandle(STD_INPUT_HANDLE)? };
39        let stdout = unsafe { GetStdHandle(STD_OUTPUT_HANDLE)? };
40        let stderr = unsafe { GetStdHandle(STD_ERROR_HANDLE)? };
41
42        let stdin_mode = get_console_mode(stdin)?;
43        let stdout_mode = get_console_mode(stdout)?;
44        let stderr_mode = get_console_mode(stderr)?;
45
46        Ok(Self {
47            stderr,
48            stderr_mode,
49            stdin,
50            stdin_mode,
51            stdout,
52            stdout_mode,
53        })
54    }
55
56    /// Sets terminal in a raw mode.
57    /// Raw mode is a mode where most of consoles processing is ommited.
58    pub fn set_raw(&self) -> Result<(), Error> {
59        set_raw_stdin(self.stdin, self.stdin_mode)?;
60
61        unsafe {
62            SetConsoleMode(self.stdout, self.stdout_mode | DISABLE_NEWLINE_AUTO_RETURN)?;
63        }
64        unsafe {
65            SetConsoleMode(self.stderr, self.stderr_mode | DISABLE_NEWLINE_AUTO_RETURN)?;
66        }
67
68        Ok(())
69    }
70
71    /// Sets terminal in a mode which was initially used on handles.
72    pub fn reset(&self) -> Result<(), Error> {
73        for (handle, mode) in self.streams() {
74            unsafe { SetConsoleMode(handle, mode)? };
75        }
76
77        Ok(())
78    }
79
80    /// Verifies if there's something in stdin to read.
81    ///
82    /// It can be used to determine if the call to `[std::io::stdin].read()` will block
83    pub fn is_stdin_empty(&self) -> Result<bool, Error> {
84        // https://stackoverflow.com/questions/23164492/how-can-i-detect-if-there-is-input-waiting-on-stdin-on-windows
85        let empty = unsafe { WaitForSingleObject(self.stdin, 0) == WAIT_OBJECT_0 };
86        Ok(empty)
87    }
88
89    fn streams(&self) -> [(HANDLE, CONSOLE_MODE); 3] {
90        [
91            (self.stdin, self.stdin_mode),
92            (self.stdout, self.stdout_mode),
93            (self.stderr, self.stderr_mode),
94        ]
95    }
96}
97
98fn get_console_mode(h: HANDLE) -> WinResult<CONSOLE_MODE> {
99    let mut mode = CONSOLE_MODE::default();
100    unsafe {
101        GetConsoleMode(h, &mut mode)?;
102    }
103    Ok(mode)
104}
105
106fn set_raw_stdin(stdin: HANDLE, mut mode: CONSOLE_MODE) -> WinResult<()> {
107    mode &= !ENABLE_ECHO_INPUT;
108    mode &= !ENABLE_LINE_INPUT;
109    mode &= !ENABLE_MOUSE_INPUT;
110    mode &= !ENABLE_LINE_INPUT;
111    mode &= !ENABLE_PROCESSED_INPUT;
112
113    mode |= ENABLE_EXTENDED_FLAGS;
114    mode |= ENABLE_INSERT_MODE;
115    mode |= ENABLE_QUICK_EDIT_MODE;
116
117    let vt_input_supported = true;
118    if vt_input_supported {
119        mode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
120    }
121
122    unsafe {
123        SetConsoleMode(stdin, mode)?;
124    }
125
126    Ok(())
127}