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 windows_sys::Win32::Globalization::CP_UTF8;
23 use windows_sys::Win32::System::Console::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
24 use windows_sys::Win32::System::Console::GetConsoleMode;
25 use windows_sys::Win32::System::Console::GetStdHandle;
26 use windows_sys::Win32::System::Console::STD_OUTPUT_HANDLE;
27 use windows_sys::Win32::System::Console::SetConsoleMode;
28 use windows_sys::Win32::System::Console::SetConsoleOutputCP;
29 unsafe {
31 let conout = GetStdHandle(STD_OUTPUT_HANDLE);
32 let mut mode = 0;
33 if GetConsoleMode(conout, &mut mode) != 0 {
34 if mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 {
35 SetConsoleMode(conout, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
36 }
37 SetConsoleOutputCP(CP_UTF8);
38 }
39 }
40}
41
42#[cfg(not(windows))]
44pub fn enable_vt_and_utf8() {}
45
46pub fn set_raw_console(enable: bool) -> Result<(), Error> {
48 if enable {
49 crossterm::terminal::enable_raw_mode().map_err(Error::VtOperationFailed)
50 } else {
51 crossterm::terminal::disable_raw_mode().map_err(Error::VtOperationFailed)
52 }
53}
54
55pub fn set_console_title(title: &str) -> Result<(), Error> {
57 crossterm::execute!(std::io::stdout(), crossterm::terminal::SetTitle(title))
58 .map_err(Error::VtOperationFailed)
59}
60
61#[cfg(windows)]
66fn clone_file(file: impl std::os::windows::io::AsHandle) -> std::fs::File {
67 file.as_handle().try_clone_to_owned().unwrap().into()
68}
69
70#[cfg(unix)]
75fn clone_file(file: impl std::os::unix::io::AsFd) -> std::fs::File {
76 file.as_fd().try_clone_to_owned().unwrap().into()
77}
78
79pub fn raw_stdout() -> std::fs::File {
81 clone_file(std::io::stdout())
82}
83
84pub fn raw_stderr() -> std::fs::File {
86 clone_file(std::io::stderr())
87}
88
89#[cfg(unix)]
91pub fn revert_terminal_on_panic() {
92 let orig_termios = get_termios();
93
94 let base_hook = std::panic::take_hook();
95 std::panic::set_hook(Box::new(move |info| {
96 eprintln!("restoring terminal attributes on panic...");
97 set_termios(orig_termios);
98 base_hook(info)
99 }));
100}
101
102#[cfg(unix)]
104#[derive(Copy, Clone)]
105pub struct Termios(libc::termios);
106
107#[cfg(unix)]
109pub fn get_termios() -> Termios {
110 let mut orig_termios = std::mem::MaybeUninit::<libc::termios>::uninit();
111 let ret = unsafe { libc::tcgetattr(libc::STDERR_FILENO, orig_termios.as_mut_ptr()) };
113 if ret != 0 {
114 panic!(
115 "error: could not save term attributes: {}",
116 std::io::Error::last_os_error()
117 );
118 }
119 let orig_termios = unsafe { orig_termios.assume_init() };
121 Termios(orig_termios)
122}
123
124#[cfg(unix)]
126pub fn set_termios(termios: Termios) {
127 let ret = unsafe { libc::tcsetattr(libc::STDERR_FILENO, libc::TCSAFLUSH, &termios.0) };
129 if ret != 0 {
130 panic!(
131 "error: could not restore term attributes via tcsetattr: {}",
132 std::io::Error::last_os_error()
133 );
134 }
135}