rterm 0.0.8

A port of suckless terminal to rust.
Documentation
use crate::shell::exec_shell;

use std::collections::VecDeque;
use std::convert::TryFrom;
use std::os::unix::io::RawFd;

use anyhow::Result;
use nix::errno::Errno;
use nix::ioctl_write_ptr_bad;
use nix::libc;
use nix::pty::{forkpty, ForkptyResult};
use nix::sys::signal::{kill, Signal};
use nix::unistd::{read, write, ForkResult, Pid};

ioctl_write_ptr_bad!(resizepty, libc::TIOCSWINSZ, libc::winsize);

pub struct Pty {
    master_fd: RawFd,
    child_pid: Pid,
    write_buf: VecDeque<u8>,
}

impl Pty {
    pub fn new(cols: usize, rows: usize) -> Result<Self> {
        let ws = libc::winsize {
            ws_row: u16::try_from(rows).unwrap(),
            ws_col: u16::try_from(cols).unwrap(),
            ws_xpixel: 0,
            ws_ypixel: 0,
        };
        let ForkptyResult {
            master,
            fork_result,
        } = unsafe { forkpty(Some(&ws), None)? };
        let child = match fork_result {
            ForkResult::Parent { child } => child,
            ForkResult::Child => {
                exec_shell();
                unreachable!();
            }
        };

        Ok(Pty {
            master_fd: master,
            child_pid: child,
            write_buf: VecDeque::new(),
        })
    }

    pub fn resize(&mut self, cols: usize, rows: usize) -> Result<()> {
        let ws = libc::winsize {
            ws_row: u16::try_from(rows).unwrap(),
            ws_col: u16::try_from(cols).unwrap(),
            ws_xpixel: 0,
            ws_ypixel: 0,
        };
        unsafe {
            resizepty(self.master_fd, &ws)?;
        }
        Ok(())
    }

    pub fn fd(&self) -> RawFd {
        self.master_fd
    }

    pub fn read(&self, buf: &mut [u8]) -> Result<usize> {
        match read(self.master_fd, buf) {
            Ok(n) => Ok(n),
            Err(Errno::EIO) => Ok(0),
            Err(err) => Err(err.into()),
        }
    }

    pub fn need_flush(&self) -> bool {
        !self.write_buf.is_empty()
    }

    pub fn flush(&mut self) -> Result<()> {
        let (first, second) = self.write_buf.as_slices();
        let mut n = write(self.master_fd, first)?;
        if n == first.len() {
            n += write(self.master_fd, second)?;
        }
        self.write_buf.drain(..n);
        Ok(())
    }

    pub fn write(&mut self, buf: &[u8]) {
        self.write_buf.extend(buf);
    }
}

impl Drop for Pty {
    fn drop(&mut self) {
        let _ = kill(self.child_pid, Signal::SIGHUP);
    }
}