Skip to main content

uncurses/terminal/
raw.rs

1//! Raw mode and terminal state helpers.
2//!
3//! These free functions operate on terminal descriptors on Unix and console
4//! handles on Windows. [`make_raw_mode`] saves the current configuration,
5//! applies raw-mode settings immediately, and returns the previous [`State`].
6//! Pass that state to [`set_state`] with the same handles to restore it.
7//!
8//! ```text
9//! get_state() ── snapshot only ───────────────────────────────┐
10//!                                                             │
11//! make_raw_mode() ── returns previous State ── raw mode ── set_state()
12//! ```
13//!
14//! [`Terminal::make_raw`](super::Terminal::make_raw) and
15//! [`Terminal::restore`](super::Terminal::restore) wrap this same flow and keep
16//! one saved state inside the terminal handle.
17
18use std::io;
19
20#[cfg(unix)]
21use std::os::fd::{AsFd, AsRawFd};
22#[cfg(windows)]
23use std::os::windows::io::{AsHandle, AsRawHandle};
24#[cfg(windows)]
25use windows_sys::Win32::Foundation::HANDLE;
26
27/// Snapshot of a terminal's configuration.
28///
29/// On Unix this stores a `libc::termios` value read from the input descriptor,
30/// falling back to the output descriptor if necessary. On Windows it stores
31/// both input and output console-mode bitfields.
32///
33/// Use values returned by [`Terminal::get_state`](crate::terminal::Terminal::get_state)
34/// or [`Terminal::make_raw`](crate::terminal::Terminal::make_raw) with
35/// [`Terminal::set_state`](crate::terminal::Terminal::set_state) to restore a
36/// terminal to a previous configuration.
37#[derive(Clone)]
38pub struct State {
39    #[cfg(unix)]
40    /// Saved terminal attributes.
41    pub(crate) termios: libc::termios,
42    #[cfg(windows)]
43    /// Saved input console-mode bits.
44    pub(crate) input_mode: u32,
45    #[cfg(windows)]
46    /// Saved output console-mode bits.
47    pub(crate) output_mode: u32,
48}
49
50#[cfg(windows)]
51unsafe impl Send for State {}
52#[cfg(windows)]
53unsafe impl Sync for State {}
54
55/// Read the current terminal state.
56///
57/// On Unix the input descriptor is tried first and the output descriptor is
58/// the fallback. On Windows both input and output console modes are sampled.
59///
60/// # Parameters
61///
62/// * `input` — terminal input descriptor or handle.
63/// * `output` — terminal output descriptor or handle.
64///
65/// # Returns
66///
67/// A [`State`] describing the current terminal mode.
68///
69/// # Errors
70///
71/// Returns the OS error from the state query. On Unix, an error is returned
72/// only if both input and output descriptors fail.
73///
74/// # Panics
75///
76/// This function does not intentionally panic.
77#[cfg(unix)]
78pub fn get_state<I: AsFd, O: AsFd>(input: I, output: O) -> io::Result<State> {
79    use std::mem::MaybeUninit;
80
81    fn try_read(fd: i32) -> io::Result<libc::termios> {
82        let mut t = MaybeUninit::<libc::termios>::uninit();
83        if unsafe { libc::tcgetattr(fd, t.as_mut_ptr()) } != 0 {
84            return Err(io::Error::last_os_error());
85        }
86        Ok(unsafe { t.assume_init() })
87    }
88
89    let ifd = input.as_fd().as_raw_fd();
90    let ofd = output.as_fd().as_raw_fd();
91    let termios = try_read(ifd).or_else(|_| try_read(ofd))?;
92    Ok(State { termios })
93}
94
95/// Apply `state` to the terminal immediately.
96///
97/// On Unix this uses `TCSANOW`; the input descriptor is tried first and the
98/// output descriptor is the fallback. On Windows both modes stored in
99/// [`State`] are written.
100///
101/// # Parameters
102///
103/// * `input` — terminal input descriptor or handle.
104/// * `output` — terminal output descriptor or handle.
105/// * `state` — state previously returned by [`get_state`] or
106///   [`make_raw_mode`].
107///
108/// # Returns
109///
110/// `Ok(())` when the state was applied.
111///
112/// # Errors
113///
114/// Returns the OS error from applying the state. On Unix, an error is returned
115/// only if applying to both input and output descriptors fails.
116///
117/// # Panics
118///
119/// This function does not intentionally panic.
120#[cfg(unix)]
121pub fn set_state<I: AsFd, O: AsFd>(input: I, output: O, state: &State) -> io::Result<()> {
122    let ifd = input.as_fd().as_raw_fd();
123    let ofd = output.as_fd().as_raw_fd();
124    if unsafe { libc::tcsetattr(ifd, libc::TCSANOW, &state.termios) } == 0 {
125        return Ok(());
126    }
127    if unsafe { libc::tcsetattr(ofd, libc::TCSANOW, &state.termios) } == 0 {
128        return Ok(());
129    }
130    Err(io::Error::last_os_error())
131}
132
133#[cfg(windows)]
134/// Read the current console modes.
135///
136/// Both input and output handles must support `GetConsoleMode`.
137///
138/// # Parameters
139///
140/// * `input` — console input handle.
141/// * `output` — console output handle.
142///
143/// # Returns
144///
145/// A [`State`] containing both console-mode bitfields.
146///
147/// # Errors
148///
149/// Returns the OS error if either console mode cannot be read.
150///
151/// # Panics
152///
153/// This function does not intentionally panic.
154pub fn get_state<I: AsHandle, O: AsHandle>(input: I, output: O) -> io::Result<State> {
155    use windows_sys::Win32::System::Console::GetConsoleMode;
156
157    let ih = input.as_handle().as_raw_handle() as HANDLE;
158    let oh = output.as_handle().as_raw_handle() as HANDLE;
159
160    let mut input_mode: u32 = 0;
161    if unsafe { GetConsoleMode(ih, &mut input_mode) } == 0 {
162        return Err(io::Error::last_os_error());
163    }
164    let mut output_mode: u32 = 0;
165    if unsafe { GetConsoleMode(oh, &mut output_mode) } == 0 {
166        return Err(io::Error::last_os_error());
167    }
168    Ok(State {
169        input_mode,
170        output_mode,
171    })
172}
173
174#[cfg(windows)]
175/// Apply console modes from `state`.
176///
177/// # Parameters
178///
179/// * `input` — console input handle.
180/// * `output` — console output handle.
181/// * `state` — console modes to apply.
182///
183/// # Returns
184///
185/// `Ok(())` when both input and output modes were applied.
186///
187/// # Errors
188///
189/// Returns the OS error if either `SetConsoleMode` call fails. If the output
190/// mode fails after the input mode succeeds, the input mode is not rolled back.
191///
192/// # Panics
193///
194/// This function does not intentionally panic.
195pub fn set_state<I: AsHandle, O: AsHandle>(input: I, output: O, state: &State) -> io::Result<()> {
196    use windows_sys::Win32::System::Console::SetConsoleMode;
197
198    let ih = input.as_handle().as_raw_handle() as HANDLE;
199    let oh = output.as_handle().as_raw_handle() as HANDLE;
200
201    if unsafe { SetConsoleMode(ih, state.input_mode) } == 0 {
202        return Err(io::Error::last_os_error());
203    }
204    if unsafe { SetConsoleMode(oh, state.output_mode) } == 0 {
205        return Err(io::Error::last_os_error());
206    }
207    Ok(())
208}
209
210/// Place the terminal into raw mode.
211///
212/// On Unix this applies a `cfmakeraw(3)`-equivalent termios (`VMIN = 1`,
213/// `VTIME = 0`) using [`set_state`]. The flags match glibc's `cfmakeraw` on
214/// every platform, so raw mode behaves identically everywhere rather than
215/// following each libc's own variation. On Windows the input handle has cooked
216/// input flags and quick-edit cleared and virtual-terminal/window-input flags
217/// set; the output handle has processed output, virtual-terminal processing,
218/// and newline-auto-return disabling set.
219///
220/// # Parameters
221///
222/// * `input` — terminal input descriptor or handle.
223/// * `output` — terminal output descriptor or handle.
224///
225/// # Returns
226///
227/// The pre-call [`State`]. Pass it to [`set_state`] to restore.
228///
229/// # Errors
230///
231/// Returns any error from reading the current state or applying the raw state.
232///
233/// # Panics
234///
235/// This function does not intentionally panic.
236#[cfg(unix)]
237pub fn make_raw_mode<I: AsFd, O: AsFd>(input: I, output: O) -> io::Result<State> {
238    let original = get_state(&input, &output)?;
239    let mut raw = original.termios;
240    raw.c_iflag &= !(libc::IGNBRK
241        | libc::BRKINT
242        | libc::PARMRK
243        | libc::ISTRIP
244        | libc::INLCR
245        | libc::IGNCR
246        | libc::ICRNL
247        | libc::IXON);
248    raw.c_oflag &= !libc::OPOST;
249    raw.c_lflag &= !(libc::ECHO | libc::ECHONL | libc::ICANON | libc::ISIG | libc::IEXTEN);
250    raw.c_cflag &= !(libc::CSIZE | libc::PARENB);
251    raw.c_cflag |= libc::CS8;
252    raw.c_cc[libc::VMIN] = 1;
253    raw.c_cc[libc::VTIME] = 0;
254
255    set_state(&input, &output, &State { termios: raw })?;
256    Ok(original)
257}
258
259#[cfg(windows)]
260/// Place the console into raw mode.
261///
262/// The input handle has cooked input flags cleared and virtual-terminal/window
263/// input flags set; the output handle has virtual-terminal processing and
264/// newline-auto-return disabling set.
265///
266/// # Parameters
267///
268/// * `input` — console input handle.
269/// * `output` — console output handle.
270///
271/// # Returns
272///
273/// The pre-call [`State`]. Pass it to [`set_state`] to restore.
274///
275/// # Errors
276///
277/// Returns any error from reading the current modes or applying the raw modes.
278///
279/// # Panics
280///
281/// This function does not intentionally panic.
282pub fn make_raw_mode<I: AsHandle, O: AsHandle>(input: I, output: O) -> io::Result<State> {
283    use windows_sys::Win32::System::Console::{
284        DISABLE_NEWLINE_AUTO_RETURN, ENABLE_ECHO_INPUT, ENABLE_EXTENDED_FLAGS, ENABLE_LINE_INPUT,
285        ENABLE_PROCESSED_INPUT, ENABLE_PROCESSED_OUTPUT, ENABLE_QUICK_EDIT_MODE,
286        ENABLE_VIRTUAL_TERMINAL_INPUT, ENABLE_VIRTUAL_TERMINAL_PROCESSING, ENABLE_WINDOW_INPUT,
287    };
288
289    let original = get_state(&input, &output)?;
290    // Clearing quick-edit while setting extended-flags is how the console API
291    // disables mouse selection, which would otherwise swallow mouse input.
292    let raw_input = (original.input_mode
293        & !(ENABLE_ECHO_INPUT
294            | ENABLE_PROCESSED_INPUT
295            | ENABLE_LINE_INPUT
296            | ENABLE_QUICK_EDIT_MODE))
297        | ENABLE_VIRTUAL_TERMINAL_INPUT
298        | ENABLE_EXTENDED_FLAGS
299        | ENABLE_WINDOW_INPUT;
300    // Virtual-terminal processing requires processed output, so set it too.
301    let raw_output = original.output_mode
302        | ENABLE_PROCESSED_OUTPUT
303        | ENABLE_VIRTUAL_TERMINAL_PROCESSING
304        | DISABLE_NEWLINE_AUTO_RETURN;
305
306    set_state(
307        &input,
308        &output,
309        &State {
310            input_mode: raw_input,
311            output_mode: raw_output,
312        },
313    )?;
314    Ok(original)
315}
316
317/// Return whether the descriptor is connected to a terminal.
318///
319/// # Parameters
320///
321/// * `fd` — descriptor to test.
322///
323/// # Returns
324///
325/// `true` when `fd` refers to a terminal.
326///
327/// # Errors and panics
328///
329/// This function does not fail or intentionally panic.
330#[cfg(unix)]
331pub fn is_terminal<F: AsFd>(fd: F) -> bool {
332    unsafe { libc::isatty(fd.as_fd().as_raw_fd()) != 0 }
333}
334
335#[cfg(windows)]
336/// Return whether the handle is connected to a console.
337///
338/// # Parameters
339///
340/// * `h` — handle to test.
341///
342/// # Returns
343///
344/// `true` when `h` supports `GetConsoleMode`.
345///
346/// # Errors and panics
347///
348/// This function does not fail or intentionally panic.
349pub fn is_terminal<H: AsHandle>(h: H) -> bool {
350    use windows_sys::Win32::System::Console::GetConsoleMode;
351    let handle = h.as_handle().as_raw_handle() as HANDLE;
352    let mut mode: u32 = 0;
353    unsafe { GetConsoleMode(handle, &mut mode) != 0 }
354}
355
356#[cfg(not(any(unix, windows)))]
357/// Return whether a handle is connected to a terminal.
358///
359/// On unsupported platforms this always returns `false`.
360pub fn is_terminal<T>(_: T) -> bool {
361    false
362}