1#![expect(unsafe_code)]
8
9#[cfg(windows)]
11pub fn enable_vt_and_utf8() {
12 use windows_sys::Win32::Globalization::CP_UTF8;
13 use windows_sys::Win32::System::Console::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
14 use windows_sys::Win32::System::Console::GetConsoleMode;
15 use windows_sys::Win32::System::Console::GetStdHandle;
16 use windows_sys::Win32::System::Console::STD_OUTPUT_HANDLE;
17 use windows_sys::Win32::System::Console::SetConsoleMode;
18 use windows_sys::Win32::System::Console::SetConsoleOutputCP;
19 unsafe {
21 let conout = GetStdHandle(STD_OUTPUT_HANDLE);
22 let mut mode = 0;
23 if GetConsoleMode(conout, &mut mode) != 0 {
24 if mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 {
25 SetConsoleMode(conout, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
26 }
27 SetConsoleOutputCP(CP_UTF8);
28 }
29 }
30}
31
32#[cfg(not(windows))]
34pub fn enable_vt_and_utf8() {}
35
36#[cfg(windows)]
41fn clone_file(file: impl std::os::windows::io::AsHandle) -> std::fs::File {
42 file.as_handle().try_clone_to_owned().unwrap().into()
43}
44
45#[cfg(unix)]
50fn clone_file(file: impl std::os::unix::io::AsFd) -> std::fs::File {
51 file.as_fd().try_clone_to_owned().unwrap().into()
52}
53
54pub fn raw_stdout() -> std::fs::File {
56 clone_file(std::io::stdout())
57}
58
59pub fn raw_stderr() -> std::fs::File {
61 clone_file(std::io::stderr())
62}
63
64#[cfg(unix)]
66pub fn revert_terminal_on_panic() {
67 let orig_termios = get_termios();
68
69 let base_hook = std::panic::take_hook();
70 std::panic::set_hook(Box::new(move |info| {
71 eprintln!("restoring terminal attributes on panic...");
72 set_termios(orig_termios);
73 base_hook(info)
74 }));
75}
76
77#[cfg(unix)]
79#[derive(Copy, Clone)]
80pub struct Termios(libc::termios);
81
82#[cfg(unix)]
84pub fn get_termios() -> Termios {
85 let mut orig_termios = std::mem::MaybeUninit::<libc::termios>::uninit();
86 let ret = unsafe { libc::tcgetattr(libc::STDERR_FILENO, orig_termios.as_mut_ptr()) };
88 if ret != 0 {
89 panic!(
90 "error: could not save term attributes: {}",
91 std::io::Error::last_os_error()
92 );
93 }
94 let orig_termios = unsafe { orig_termios.assume_init() };
96 Termios(orig_termios)
97}
98
99#[cfg(unix)]
101pub fn set_termios(termios: Termios) {
102 let ret = unsafe { libc::tcsetattr(libc::STDERR_FILENO, libc::TCSAFLUSH, &termios.0) };
104 if ret != 0 {
105 panic!(
106 "error: could not restore term attributes via tcsetattr: {}",
107 std::io::Error::last_os_error()
108 );
109 }
110}
111
112#[cfg(unix)]
119pub fn open_pty() -> std::io::Result<(std::fs::File, std::fs::File)> {
120 use std::ffi::CStr;
121 use std::ffi::OsStr;
122 use std::os::unix::ffi::OsStrExt as _;
123 use std::os::unix::fs::OpenOptionsExt;
124 use std::os::unix::io::AsRawFd;
125 use std::os::unix::io::FromRawFd;
126
127 let primary_fd = unsafe { libc::posix_openpt(libc::O_RDWR | libc::O_NOCTTY | libc::O_CLOEXEC) };
133 if primary_fd < 0 {
134 return Err(std::io::Error::last_os_error());
135 }
136 let primary = unsafe { std::fs::File::from_raw_fd(primary_fd) };
138
139 unsafe {
142 if libc::grantpt(primary.as_raw_fd()) != 0 {
143 return Err(std::io::Error::last_os_error());
144 }
145 if libc::unlockpt(primary.as_raw_fd()) != 0 {
146 return Err(std::io::Error::last_os_error());
147 }
148 }
149
150 #[cfg(not(target_os = "macos"))]
153 use libc::ptsname_r;
154 #[cfg(target_os = "macos")]
155 unsafe extern "C" {
156 unsafe fn ptsname_r(fd: i32, buf: *mut std::ffi::c_char, buflen: usize) -> i32;
157 }
158
159 let mut name_buf = [0u8; 128];
161 let ret = unsafe {
163 ptsname_r(
164 primary.as_raw_fd(),
165 name_buf.as_mut_ptr().cast(),
166 name_buf.len(),
167 )
168 };
169 if ret != 0 {
170 return Err(std::io::Error::from_raw_os_error(ret));
171 }
172
173 let name = CStr::from_bytes_until_nul(&name_buf).expect("libc contract violation");
174 let secondary = std::fs::OpenOptions::new()
175 .read(true)
176 .write(true)
177 .custom_flags(libc::O_NOCTTY)
178 .open(OsStr::from_bytes(name.to_bytes()))?;
179
180 Ok((primary, secondary))
181}