use crate::terminal::Terminal;
use libc::{POLLHUP, POLLOUT, TIOCGWINSZ, c_int, c_ushort, c_void, ioctl, size_t};
use signal_hook::SigId;
use stakker::{Actor, Core, call, fwd_do};
use stakker_mio::mio::Interest;
use stakker_mio::{FdSource, MioPoll, MioSource};
use std::io::{Error, ErrorKind, Result};
use std::mem;
use std::os::unix::io::AsRawFd;
use std::os::unix::net::UnixStream;
#[repr(C)]
#[derive(Default)]
struct WinSize {
row: c_ushort,
col: c_ushort,
xpixel: c_ushort,
ypixel: c_ushort,
}
pub struct Glue {
poll: MioPoll,
term: Actor<Terminal>,
_read: UnixStream,
_winch_src: MioSource<FdSource>,
stdin_src: Option<MioSource<FdSource>>,
sigid: SigId,
saved: Option<libc::termios>,
}
const STDIN_FD: c_int = 0;
const STDOUT_FD: c_int = 1;
impl Glue {
pub fn new(core: &mut Core, term: Actor<Terminal>) -> Result<Self> {
let poll = core.anymap_get::<MioPoll>();
let (read, write) = UnixStream::pair()?;
let sigid = signal_hook::low_level::pipe::register(signal_hook::consts::SIGWINCH, write)?;
read.set_nonblocking(true)?;
let fd = read.as_raw_fd();
let fdsrc = FdSource::new(fd);
let term2 = term.clone();
let fwd = fwd_do!(move |_| {
let mut buf = [0u8; 32];
while 0 < unsafe { libc::read(fd, &mut buf[0] as *mut u8 as *mut _, buf.len()) } {}
call!([term2], handle_resize());
});
let winch_src = poll.add(fdsrc, Interest::READABLE, 16, fwd)?;
if 0 > unsafe { libc::fcntl(STDIN_FD, libc::F_SETFL, libc::O_NONBLOCK) } {
return Err(Error::last_os_error());
}
let mut this = Self {
poll,
term,
_read: read,
_winch_src: winch_src,
stdin_src: None,
sigid,
saved: None,
};
this.input(true);
Ok(this)
}
pub fn get_size(&mut self) -> Result<Option<(i32, i32)>> {
let mut ws = WinSize::default();
match unsafe { ioctl(1, TIOCGWINSZ, &mut ws as *mut _ as *mut u8) } {
-1 => {
let err = Error::last_os_error();
if let Some(25) = err.raw_os_error() {
Ok(None)
} else {
Err(err)
}
}
_ => Ok(Some((i32::from(ws.row), i32::from(ws.col)))),
}
}
pub fn write(&mut self, data: &[u8]) -> Result<()> {
Self::write_aux(data)
}
fn write_aux(mut data: &[u8]) -> Result<()> {
while !data.is_empty() {
let cnt = unsafe {
libc::write(
STDOUT_FD,
&data[0] as *const _ as *const c_void,
data.len() as size_t,
)
};
if cnt < 0 {
let e = Error::last_os_error();
match e.kind() {
ErrorKind::Interrupted => continue,
ErrorKind::WouldBlock => {
let mut pollfd = libc::pollfd {
fd: STDOUT_FD,
events: POLLOUT | POLLHUP,
revents: 0,
};
let ptr = &mut pollfd as *mut _;
let _rv = unsafe { libc::poll(ptr, 1, -1) };
continue;
}
_ => return Err(e),
}
}
data = &data[cnt as usize..];
}
Ok(())
}
pub fn input(&mut self, enable: bool) {
if enable && self.stdin_src.is_none() && self.termios_set_raw() {
let fdsrc = FdSource::new(STDIN_FD);
let term = self.term.clone();
let fwd = fwd_do!(move |_| call!([term], handle_data_in()));
match self.poll.add(fdsrc, Interest::READABLE, 16, fwd) {
Err(e) => call!([self.term], handle_error_in(e)),
Ok(src) => self.stdin_src = Some(src),
}
}
if !enable {
self.stdin_src = None;
self.termios_restore();
}
}
#[allow(clippy::type_complexity)]
pub fn cleanup_fn(&mut self) -> Box<dyn Fn(&[u8]) + Send + Sync + 'static> {
let saved = self.saved;
Box::new(move |reset| {
let _ = Self::write_aux(reset);
if let Some(saved) = saved {
unsafe { libc::tcsetattr(STDIN_FD, libc::TCSANOW, &saved as *const libc::termios) };
};
})
}
pub fn read_data(&mut self, inbuf: &mut Vec<u8>) {
let mut buf = [0u8; 32];
loop {
let cnt = unsafe { libc::read(STDIN_FD, &mut buf[0] as *mut u8 as *mut _, buf.len()) };
if cnt < 0 {
#[allow(unreachable_patterns)]
match errno::errno().0 {
libc::EWOULDBLOCK | libc::EAGAIN => (),
_ => call!([self.term], handle_error_in(Error::last_os_error())),
}
break;
}
inbuf.extend_from_slice(&buf[..cnt as usize]);
}
}
fn termios_set_raw(&mut self) -> bool {
if self.saved.is_some() {
return false;
}
if 0 == unsafe { libc::isatty(STDIN_FD) } {
let err = Error::other("Standard input is not a TTY");
call!([self.term], handle_error_in(err));
return false;
}
let mut tbuf = mem::MaybeUninit::uninit();
if 0 > unsafe { libc::tcgetattr(STDIN_FD, tbuf.as_mut_ptr()) } {
let err = Error::new(Error::last_os_error().kind(), "Unable to get terminal mode");
call!([self.term], handle_error_in(err));
return false;
}
let mut tbuf = unsafe { tbuf.assume_init() };
self.saved = Some(tbuf);
unsafe { libc::cfmakeraw(&mut tbuf as *mut _) };
if 0 > unsafe { libc::tcsetattr(STDIN_FD, libc::TCSANOW, &tbuf as *const libc::termios) } {
let err = Error::new(
Error::last_os_error().kind(),
"Unable to set terminal raw mode",
);
call!([self.term], handle_error_in(err));
return false;
}
true
}
fn termios_restore(&mut self) {
if let Some(saved) = self.saved.take()
&& 0 > unsafe {
libc::tcsetattr(STDIN_FD, libc::TCSANOW, &saved as *const libc::termios)
}
{
let err = Error::new(
Error::last_os_error().kind(),
"Unable to restore terminal mode",
);
call!([self.term], handle_error_in(err));
}
}
}
impl Drop for Glue {
fn drop(&mut self) {
signal_hook::low_level::unregister(self.sigid);
}
}