use std::os::fd::RawFd;
pub struct TerminalState {
fd: RawFd,
termios: libc::termios,
}
impl TerminalState {
pub fn enter_raw_mode(fd: RawFd) -> Option<Self> {
unsafe {
let mut saved: libc::termios = std::mem::zeroed();
if libc::tcgetattr(fd, &mut saved) != 0 {
return None;
}
let mut raw = saved;
libc::cfmakeraw(&mut raw);
libc::tcsetattr(fd, libc::TCSANOW, &raw);
Some(TerminalState { fd, termios: saved })
}
}
pub fn restore(&self) {
unsafe {
libc::tcsetattr(self.fd, libc::TCSANOW, &self.termios);
}
}
}
impl Drop for TerminalState {
fn drop(&mut self) {
self.restore();
}
}
pub fn terminal_size(fd: RawFd) -> (u16, u16) {
unsafe {
let mut ws: libc::winsize = std::mem::zeroed();
if libc::ioctl(fd, libc::TIOCGWINSZ, &mut ws) == 0 {
(ws.ws_row, ws.ws_col)
} else {
(24, 80)
}
}
}
pub fn read_raw(fd: RawFd, buf: &mut [u8]) -> usize {
unsafe {
let n = libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len());
if n > 0 {
n as usize
} else {
0
}
}
}
pub enum StdinEvent {
Ready,
Resize,
Shutdown,
}
pub struct ShutdownSignal {
pipe_write: RawFd,
}
unsafe impl Send for ShutdownSignal {}
impl ShutdownSignal {
pub fn signal(&self) {
unsafe {
libc::write(self.pipe_write, [1u8].as_ptr() as *const libc::c_void, 1);
}
}
}
impl Drop for ShutdownSignal {
fn drop(&mut self) {
unsafe {
libc::close(self.pipe_write);
}
}
}
const EV_STDIN: u64 = 0;
const EV_PIPE: u64 = 1;
const EV_SIGNAL: u64 = 2;
pub struct StdinRelay {
epoll_fd: RawFd,
signal_fd: RawFd,
pipe_read: RawFd,
}
impl StdinRelay {
pub fn new(stdin_fd: RawFd) -> Option<(StdinRelay, ShutdownSignal)> {
unsafe {
let mut fds = [0i32; 2];
if libc::pipe(fds.as_mut_ptr()) != 0 {
return None;
}
let pipe_read = fds[0];
let pipe_write = fds[1];
let epoll_fd = libc::epoll_create1(0);
if epoll_fd < 0 {
libc::close(pipe_read);
libc::close(pipe_write);
return None;
}
let mut ev = libc::epoll_event {
events: libc::EPOLLIN as u32,
u64: EV_STDIN,
};
if libc::epoll_ctl(epoll_fd, libc::EPOLL_CTL_ADD, stdin_fd, &mut ev) < 0 {
libc::close(epoll_fd);
libc::close(pipe_read);
libc::close(pipe_write);
return None;
}
let mut ev = libc::epoll_event {
events: libc::EPOLLIN as u32,
u64: EV_PIPE,
};
libc::epoll_ctl(epoll_fd, libc::EPOLL_CTL_ADD, pipe_read, &mut ev);
let mut sigset: libc::sigset_t = std::mem::zeroed();
libc::sigemptyset(&mut sigset);
libc::sigaddset(&mut sigset, libc::SIGWINCH);
libc::sigprocmask(libc::SIG_BLOCK, &sigset, std::ptr::null_mut());
let signal_fd = libc::signalfd(-1, &sigset, libc::SFD_NONBLOCK);
if signal_fd < 0 {
libc::close(epoll_fd);
libc::close(pipe_read);
libc::close(pipe_write);
return None;
}
let mut ev = libc::epoll_event {
events: libc::EPOLLIN as u32,
u64: EV_SIGNAL,
};
libc::epoll_ctl(epoll_fd, libc::EPOLL_CTL_ADD, signal_fd, &mut ev);
Some((
StdinRelay {
epoll_fd,
signal_fd,
pipe_read,
},
ShutdownSignal { pipe_write },
))
}
}
pub fn wait(&self) -> StdinEvent {
unsafe {
let mut events = [libc::epoll_event { events: 0, u64: 0 }; 4];
let n = libc::epoll_wait(self.epoll_fd, events.as_mut_ptr(), 4, -1);
if n < 1 {
return StdinEvent::Shutdown;
}
for ev in &events[..n as usize] {
match ev.u64 {
EV_STDIN => return StdinEvent::Ready,
EV_PIPE => return StdinEvent::Shutdown,
EV_SIGNAL => {
let mut info: libc::signalfd_siginfo = std::mem::zeroed();
libc::read(
self.signal_fd,
&mut info as *mut _ as *mut libc::c_void,
std::mem::size_of::<libc::signalfd_siginfo>(),
);
return StdinEvent::Resize;
}
_ => {}
}
}
StdinEvent::Shutdown
}
}
}
impl Drop for StdinRelay {
fn drop(&mut self) {
unsafe {
libc::close(self.epoll_fd);
libc::close(self.signal_fd);
libc::close(self.pipe_read);
let mut sigset: libc::sigset_t = std::mem::zeroed();
libc::sigemptyset(&mut sigset);
libc::sigaddset(&mut sigset, libc::SIGWINCH);
libc::sigprocmask(libc::SIG_UNBLOCK, &sigset, std::ptr::null_mut());
}
}
}