#![cfg_attr(docsrs, feature(doc_cfg))]
use cfg_if::cfg_if;
use std::io;
use std::marker::PhantomData;
use std::sync::{Mutex, MutexGuard};
cfg_if! {
if #[cfg(all(unix, not(terminal_trx_test_unsupported)))] {
mod unix;
use unix as imp;
} else if #[cfg(all(windows, not(terminal_trx_test_unsupported)))] {
mod windows;
use windows as imp;
} else {
mod unsupported;
use unsupported as imp;
}
}
#[doc = include_str!("../readme.md")]
#[cfg(doctest)]
pub mod readme_doctests {}
static TERMINAL_LOCK: Mutex<()> = Mutex::new(());
pub fn terminal() -> io::Result<Terminal> {
imp::terminal().map(Terminal)
}
macro_rules! impl_transceive {
($($extra_supertraits:tt)*) => {
pub trait Transceive: io::Read + io::Write $($extra_supertraits)* + sealed::Sealed {}
};
}
cfg_if! {
if #[cfg(terminal_trx_test_unsupported)] {
impl_transceive! { }
} else if #[cfg(unix)] {
impl_transceive! { + std::os::fd::AsFd + std::os::fd::AsRawFd }
} else if #[cfg(windows)] {
impl_transceive! { + ConsoleHandles }
} else {
impl_transceive! { }
}
}
#[cfg(all(windows, not(terminal_trx_test_unsupported)))]
#[cfg_attr(docsrs, doc(cfg(windows)))]
pub trait ConsoleHandles {
fn input_buffer_handle(&self) -> std::os::windows::io::BorrowedHandle<'_>;
fn screen_buffer_handle(&self) -> std::os::windows::io::BorrowedHandle<'_>;
}
mod sealed {
pub trait Sealed {}
}
#[derive(Debug)]
pub struct Terminal(imp::Terminal);
#[cfg(test)]
static_assertions::assert_impl_all!(Terminal: Send, Sync, std::panic::UnwindSafe, std::panic::RefUnwindSafe);
impl sealed::Sealed for Terminal {}
impl Transceive for Terminal {}
impl io::Read for Terminal {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.lock().read(buf)
}
}
impl io::Write for Terminal {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.lock().write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.lock().flush()
}
}
impl Terminal {
pub fn lock(&mut self) -> TerminalLock<'_> {
let mutex_guard = TERMINAL_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let stdio_locks = self.0.lock_stdio();
TerminalLock {
inner: &mut self.0,
_stdio_locks: stdio_locks,
_mutex_guard: mutex_guard,
_phantom_data: PhantomData,
}
}
pub fn has_connected_stdio_stream(&self) -> bool {
self.0.has_connected_stdio_stream()
}
}
#[derive(Debug)]
pub struct TerminalLock<'a> {
inner: &'a mut imp::Terminal,
_mutex_guard: MutexGuard<'static, ()>,
_stdio_locks: StdioLocks,
_phantom_data: PhantomData<*mut ()>,
}
#[cfg(test)]
static_assertions::assert_not_impl_any!(TerminalLock<'_>: Send, Sync);
impl TerminalLock<'_> {
pub fn enable_raw_mode(&mut self) -> io::Result<RawModeGuard<'_>> {
self.inner.enable_raw_mode().map(RawModeGuard)
}
}
impl sealed::Sealed for TerminalLock<'_> {}
impl Transceive for TerminalLock<'_> {}
impl io::Read for TerminalLock<'_> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}
}
impl io::Write for TerminalLock<'_> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
#[derive(Debug)]
struct StdioLocks {
#[allow(dead_code)]
stdin_lock: Option<io::StdinLock<'static>>,
#[allow(dead_code)]
stdout_lock: Option<io::StdoutLock<'static>>,
#[allow(dead_code)]
stderr_lock: Option<io::StderrLock<'static>>,
}
#[derive(Debug)]
pub struct RawModeGuard<'a>(imp::RawModeGuard<'a>);
impl sealed::Sealed for RawModeGuard<'_> {}
impl Transceive for RawModeGuard<'_> {}
impl io::Read for RawModeGuard<'_> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.0.read(buf)
}
}
impl io::Write for RawModeGuard<'_> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.0.flush()
}
}