1#![expect(unsafe_code)]
8
9use thiserror::Error;
10
11#[derive(Error, Debug)]
13#[expect(missing_docs)]
14pub enum Error {
15 #[error("failed to perform a virtual terminal operation: {0}")]
16 VtOperationFailed(std::io::Error),
17}
18
19#[cfg(windows)]
21pub fn enable_vt_and_utf8() {
22 use winapi::um::consoleapi;
23 use winapi::um::processenv;
24 use winapi::um::winbase;
25 use winapi::um::wincon;
26 use winapi::um::winnls;
27 unsafe {
29 let conout = processenv::GetStdHandle(winbase::STD_OUTPUT_HANDLE);
30 let mut mode = 0;
31 if consoleapi::GetConsoleMode(conout, &mut mode) != 0 {
32 if mode & wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 {
33 consoleapi::SetConsoleMode(
34 conout,
35 mode | wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING,
36 );
37 }
38 wincon::SetConsoleOutputCP(winnls::CP_UTF8);
39 }
40 }
41}
42
43#[cfg(not(windows))]
45pub fn enable_vt_and_utf8() {}
46
47pub fn set_raw_console(enable: bool) -> Result<(), Error> {
49 if enable {
50 crossterm::terminal::enable_raw_mode().map_err(Error::VtOperationFailed)
51 } else {
52 crossterm::terminal::disable_raw_mode().map_err(Error::VtOperationFailed)
53 }
54}
55
56pub fn set_console_title(title: &str) -> Result<(), Error> {
58 crossterm::execute!(std::io::stdout(), crossterm::terminal::SetTitle(title))
59 .map_err(Error::VtOperationFailed)
60}
61
62#[cfg(windows)]
67fn clone_file(file: impl std::os::windows::io::AsHandle) -> std::fs::File {
68 file.as_handle().try_clone_to_owned().unwrap().into()
69}
70
71#[cfg(unix)]
76fn clone_file(file: impl std::os::unix::io::AsFd) -> std::fs::File {
77 file.as_fd().try_clone_to_owned().unwrap().into()
78}
79
80pub fn raw_stdout() -> std::fs::File {
82 clone_file(std::io::stdout())
83}
84
85pub fn raw_stderr() -> std::fs::File {
87 clone_file(std::io::stderr())
88}
89
90#[cfg(unix)]
92pub fn revert_terminal_on_panic() {
93 let orig_termios = get_termios();
94
95 let base_hook = std::panic::take_hook();
96 std::panic::set_hook(Box::new(move |info| {
97 eprintln!("restoring terminal attributes on panic...");
98 set_termios(orig_termios);
99 base_hook(info)
100 }));
101}
102
103#[cfg(unix)]
105#[derive(Copy, Clone)]
106pub struct Termios(libc::termios);
107
108#[cfg(unix)]
110pub fn get_termios() -> Termios {
111 let mut orig_termios = std::mem::MaybeUninit::<libc::termios>::uninit();
112 let ret = unsafe { libc::tcgetattr(libc::STDERR_FILENO, orig_termios.as_mut_ptr()) };
114 if ret != 0 {
115 panic!(
116 "error: could not save term attributes: {}",
117 std::io::Error::last_os_error()
118 );
119 }
120 let orig_termios = unsafe { orig_termios.assume_init() };
122 Termios(orig_termios)
123}
124
125#[cfg(unix)]
127pub fn set_termios(termios: Termios) {
128 let ret = unsafe { libc::tcsetattr(libc::STDERR_FILENO, libc::TCSAFLUSH, &termios.0) };
130 if ret != 0 {
131 panic!(
132 "error: could not restore term attributes via tcsetattr: {}",
133 std::io::Error::last_os_error()
134 );
135 }
136}