use std::ops::{Deref, DerefMut};
use std::io::{self, Stdout};
use std::sync::atomic::{AtomicBool, Ordering};
use ratatui::{
Terminal,
backend::CrosstermBackend,
crossterm::{
terminal::{self, EnterAlternateScreen, LeaveAlternateScreen},
event::{EnableMouseCapture, DisableMouseCapture},
ExecutableCommand,
},
};
use crate::error::{Error, Result};
static IS_OPEN: AtomicBool = AtomicBool::new(false);
#[derive(Debug)]
pub struct ScreenGuard {
terminal: Terminal<CrosstermBackend<Stdout>>,
}
impl ScreenGuard {
pub fn open() -> Result<Self> {
let mut result = Err(Error::ScreenAlreadyOpen);
let _ = IS_OPEN.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |flag| {
if flag {
result = Err(Error::ScreenAlreadyOpen);
return None;
}
if let Err(error) = terminal::enable_raw_mode() {
result = Err(error.into());
return None;
}
if let Err(error) = io::stdout().execute(EnterAlternateScreen) {
result = Err(error.into());
return None;
}
if let Err(error) = io::stdout().execute(EnableMouseCapture) {
result = Err(error.into());
return None;
}
match Terminal::new(CrosstermBackend::new(io::stdout())) {
Ok(terminal) => {
result = Ok(ScreenGuard { terminal });
Some(true)
}
Err(error) => {
result = Err(error.into());
None
}
}
});
result
}
fn finalize(&mut self) -> Result<()> {
terminal::disable_raw_mode()?;
io::stdout().execute(DisableMouseCapture)?;
io::stdout().execute(LeaveAlternateScreen)?;
IS_OPEN.store(false, Ordering::SeqCst);
Ok(())
}
}
impl Deref for ScreenGuard {
type Target = Terminal<CrosstermBackend<Stdout>>;
fn deref(&self) -> &Self::Target {
&self.terminal
}
}
impl DerefMut for ScreenGuard {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.terminal
}
}
impl Drop for ScreenGuard {
fn drop(&mut self) {
if let Err(error) = self.finalize() {
eprintln!("Error restoring terminal: {error:#}");
}
}
}