use std::io;
use anyhow::Result;
use crossterm::{
execute,
terminal::{self, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{Terminal, prelude::CrosstermBackend};
#[cfg(unix)]
use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd};
#[cfg(unix)]
use std::sync::OnceLock;
#[cfg(unix)]
pub type Tui = Terminal<CrosstermBackend<std::io::BufWriter<std::fs::File>>>;
#[cfg(not(unix))]
pub type Tui = Terminal<CrosstermBackend<io::Stdout>>;
#[cfg(unix)]
static SAVED_STDOUT_FD: OnceLock<i32> = OnceLock::new();
#[cfg(unix)]
pub fn init() -> Result<Tui> {
let tui_fd = unsafe { libc::dup(1) };
anyhow::ensure!(tui_fd >= 0, "dup(stdout) failed");
let file = unsafe { std::fs::File::from_raw_fd(tui_fd) };
let mut writer = std::io::BufWriter::with_capacity(1 << 20, file);
terminal::enable_raw_mode()?;
execute!(writer, EnterAlternateScreen)?;
Ok(Terminal::new(CrosstermBackend::new(writer))?)
}
#[cfg(not(unix))]
pub fn init() -> Result<Tui> {
let mut stdout = io::stdout();
terminal::enable_raw_mode()?;
execute!(stdout, EnterAlternateScreen)?;
let backend = CrosstermBackend::new(stdout);
let terminal = Terminal::new(backend)?;
Ok(terminal)
}
pub fn init_keyboard_enhancement() {
let _ = crossterm::execute!(
io::stdout(),
crossterm::event::PushKeyboardEnhancementFlags(
crossterm::event::KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
| crossterm::event::KeyboardEnhancementFlags::REPORT_EVENT_TYPES
| crossterm::event::KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES
)
);
}
pub fn init_picker() -> ratatui_image::picker::Picker {
ratatui_image::picker::Picker::halfblocks()
}
#[cfg(unix)]
pub fn suppress_stdout() -> Option<OwnedFd> {
let dev_null = std::fs::File::open("/dev/null").ok()?;
let saved_fd = unsafe { libc::dup(1) };
if saved_fd < 0 {
return None;
}
unsafe { libc::dup2(dev_null.as_raw_fd(), 1) };
let _ = SAVED_STDOUT_FD.set(saved_fd);
Some(unsafe { OwnedFd::from_raw_fd(saved_fd) })
}
#[cfg(unix)]
pub fn restore_stdout(saved: &OwnedFd) {
unsafe { libc::dup2(saved.as_raw_fd(), 1) };
}
#[cfg(unix)]
pub fn restore_stdout_from_global() {
if let Some(&fd) = SAVED_STDOUT_FD.get() {
unsafe { libc::dup2(fd, 1) };
}
}
#[cfg(unix)]
pub fn suppress_stderr() -> Option<OwnedFd> {
let dev_null = std::fs::File::open("/dev/null").ok()?;
let saved_fd = unsafe { libc::dup(2) };
if saved_fd < 0 {
return None;
}
unsafe { libc::dup2(dev_null.as_raw_fd(), 2) };
Some(unsafe { OwnedFd::from_raw_fd(saved_fd) })
}
#[cfg(unix)]
pub fn restore_stderr(saved: &OwnedFd) {
unsafe { libc::dup2(saved.as_raw_fd(), 2) };
}
pub fn restore() -> Result<()> {
let mut stdout = io::stdout();
let _ = crossterm::execute!(stdout, crossterm::event::PopKeyboardEnhancementFlags);
execute!(stdout, crossterm::cursor::Show, LeaveAlternateScreen)?;
terminal::disable_raw_mode()?;
Ok(())
}