kibi/
unix.rs

1// SPDX-FileCopyrightText: 2020 Ilaï Deutel & Kibi Contributors
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5//! # sys (UNIX)
6//!
7//! UNIX-specific structs and functions. Will be imported as `sys` on UNIX
8//! systems.
9#![expect(unsafe_code)]
10
11use std::io::{self, BufRead};
12use std::sync::atomic::{AtomicBool, Ordering::Relaxed};
13
14// On UNIX systems, termios represents the terminal mode.
15pub use libc::termios as TermMode;
16use libc::{SA_SIGINFO, STDIN_FILENO, STDOUT_FILENO, TCSADRAIN, TIOCGWINSZ, VMIN, VTIME};
17use libc::{c_int, c_void, sigaction, sighandler_t, siginfo_t, winsize};
18
19use crate::Error;
20pub use crate::xdg::*;
21
22fn cerr(err: c_int) -> io::Result<()> {
23    if err < 0 { Err(io::Error::last_os_error()) } else { Ok(()) }
24}
25
26/// Return the current window size as (rows, columns).
27///
28/// We use the `TIOCGWINSZ` ioctl to get window size. If it succeeds, a
29/// `Winsize` struct will be populated.
30/// This ioctl is described here: <http://man7.org/linux/man-pages/man4/tty_ioctl.4.html>
31pub fn get_window_size() -> Result<(usize, usize), Error> {
32    let mut maybe_ws = std::mem::MaybeUninit::<winsize>::uninit();
33    cerr(unsafe { libc::ioctl(STDOUT_FILENO, TIOCGWINSZ, maybe_ws.as_mut_ptr()) })
34        .map_or(None, |()| unsafe { Some(maybe_ws.assume_init()) })
35        .filter(|ws| ws.ws_col != 0 && ws.ws_row != 0)
36        .map_or(Err(Error::InvalidWindowSize), |ws| Ok((ws.ws_row as usize, ws.ws_col as usize)))
37}
38
39/// Stores whether the window size has changed since last call to
40/// `has_window_size_changed`.
41static WSC: AtomicBool = AtomicBool::new(false);
42
43/// Handle a change in window size.
44extern "C" fn handle_wsize(_: c_int, _: *mut siginfo_t, _: *mut c_void) { WSC.store(true, Relaxed) }
45
46#[allow(dead_code)]
47#[expect(clippy::fn_to_numeric_cast_any)]
48/// Register a signal handler that sets a global variable when the window size
49/// changes. After calling this function, use `has_window_size_changed` to query
50/// the global variable.
51pub fn register_winsize_change_signal_handler() -> io::Result<()> {
52    return Ok(());
53    unsafe {
54        let mut maybe_sa = std::mem::MaybeUninit::<sigaction>::uninit();
55        cerr(libc::sigemptyset(&raw mut (*maybe_sa.as_mut_ptr()).sa_mask))?;
56        // We could use sa_handler here, however, sigaction defined in libc does not
57        // have sa_handler field, so we use sa_sigaction instead.
58        (*maybe_sa.as_mut_ptr()).sa_flags = SA_SIGINFO;
59        (*maybe_sa.as_mut_ptr()).sa_sigaction = handle_wsize as *const () as sighandler_t;
60        cerr(sigaction(libc::SIGWINCH, maybe_sa.as_ptr(), std::ptr::null_mut()))
61    }
62}
63
64/// Check if the windows size has changed since the last call to this function.
65/// The `register_winsize_change_signal_handler` needs to be called before this
66/// function.
67pub fn has_window_size_changed() -> bool { WSC.swap(false, Relaxed) }
68
69/// Set the terminal mode.
70pub fn set_term_mode(term: &TermMode) -> io::Result<()> {
71    cerr(unsafe { libc::tcsetattr(STDIN_FILENO, TCSADRAIN, term) })
72}
73
74/// Setup the termios to enable raw mode, and return the original termios.
75///
76/// termios manual is available at: <http://man7.org/linux/man-pages/man3/termios.3.html>
77pub fn enable_raw_mode() -> io::Result<TermMode> {
78    let mut maybe_term = std::mem::MaybeUninit::<TermMode>::uninit();
79    cerr(unsafe { libc::tcgetattr(STDIN_FILENO, maybe_term.as_mut_ptr()) })?;
80    let orig_term = unsafe { maybe_term.assume_init() };
81    let mut term = orig_term;
82    unsafe { libc::cfmakeraw(&raw mut term) };
83    // First sets the minimum number of characters for non-canonical reads
84    // Second sets the timeout in deciseconds for non-canonical reads
85    (term.c_cc[VMIN], term.c_cc[VTIME]) = (0, 1);
86    set_term_mode(&term)?;
87    Ok(orig_term)
88}
89
90/// Construct and lock a new handle to the standard input of the current
91/// process.
92///
93/// # Errors
94///
95/// This function always returns Ok(...). The return type is a Result for
96/// compatibility with other platforms.
97pub fn stdin() -> io::Result<impl BufRead> { Ok(io::stdin().lock()) }
98
99pub fn path(filename: &str) -> std::path::PathBuf { std::path::PathBuf::from(filename) }