#![deny(clippy::undocumented_unsafe_blocks)]
#![cfg_attr(docsrs, feature(doc_cfg))]
use std::io;
use std::marker::PhantomData;
use std::sync::{Mutex, MutexGuard};
use thiserror::Error;
#[cfg(all(unix, not(feature = "__test_unsupported")))]
mod unix;
#[cfg(all(unix, not(feature = "__test_unsupported")))]
use unix as imp;
#[cfg(all(windows, not(feature = "__test_unsupported")))]
mod windows;
#[cfg(all(windows, not(feature = "__test_unsupported")))]
use windows as imp;
#[cfg(any(not(any(unix, windows)), feature = "__test_unsupported"))]
mod unsupported;
#[cfg(any(not(any(unix, windows)), feature = "__test_unsupported"))]
use unsupported as imp;
static TERMINAL_LOCK: Mutex<()> = Mutex::new(());
pub fn terminal() -> io::Result<Terminal> {
imp::terminal().map(Terminal)
}
#[cfg(all(unix, not(feature = "__test_unsupported")))]
pub trait Transceive:
io::Read + io::Write + std::os::fd::AsFd + std::os::fd::AsRawFd + sealed::Sealed
{
}
#[cfg(all(windows, not(feature = "__test_unsupported")))]
pub trait Transceive: io::Read + io::Write + ConsoleHandles + sealed::Sealed {}
#[cfg(any(not(any(unix, windows)), feature = "__test_unsupported"))]
pub trait Transceive: io::Read + io::Write + sealed::Sealed {}
#[cfg(all(windows, not(feature = "__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) -> io::Result<TerminalLock<'_>> {
let mutex_guard = TERMINAL_LOCK.lock().map_err(|_| PoisonError::default())?;
let stdio_locks = self.0.lock_stdio();
Ok(TerminalLock {
inner: &mut self.0,
_stdio_locks: stdio_locks,
_mutex_guard: mutex_guard,
_phantom_data: PhantomData,
})
}
}
#[derive(Debug, Default, Clone, Error)]
#[error("poisoned lock: another task failed inside")]
pub struct PoisonError(PhantomData<()>);
impl From<PoisonError> for io::Error {
fn from(value: PoisonError) -> Self {
io::Error::new(io::ErrorKind::Other, value)
}
}
#[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<'a> io::Read for TerminalLock<'a> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}
}
impl<'a> io::Write for TerminalLock<'a> {
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<'a> io::Read for RawModeGuard<'a> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.0.read(buf)
}
}
impl<'a> io::Write for RawModeGuard<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.0.flush()
}
}