mssh 0.0.0

Mssh Simple SHell. Bash interpreter/compiler. Will not support all the functionalities.
use core::mem::MaybeUninit;
use std::io;

use crate::libc;
use std::io::Read;

#[allow(non_camel_case_types)]
#[derive(Debug)]
pub enum Key {
    Up,
    Down,
    Left,
    Right,
    Home,
    Tab,
    Backspace,
    PageUp,
    PageDown,
    Insert,
    End,
    Suppr,
    Enter,
    Ctrl_C,
    Ctrl_Z,
    Char(u8),
}

pub struct RawTTy<'a> {
    stdin: io::StdinLock<'a>,
}

impl<'a> RawTTy<'a> {
    pub fn new() -> Self {
        setup_raw_terminal();
        Self {
            stdin: io::stdin().lock(),
        }
    }

    pub fn read_one_key(&mut self) -> Key {
        //blocks until a key is pressed
        //must be called after setup_raw_terminal

        let mut buf = [0u8; 4];
        let size = self
            .stdin
            .read(&mut buf)
            .expect("unable to read from terminal");

        let key = if size == 4 {
            // some special keys
            if buf[0] == 27 && buf[1] == 91 && buf[3] == 126 {
                match buf[2] {
                    50 => Key::Insert,
                    51 => Key::Suppr,
                    53 => Key::PageUp,
                    54 => Key::PageDown,
                    _ => panic!("unhandled key code1 {:?}", buf),
                }
            } else {
                panic!("unhandled key code2 {:?}", buf)
            }
        } else if size == 3 {
            //handle arrows
            if buf[0] == 27 && buf[1] == 91 {
                match buf[2] {
                    65 => Key::Up,
                    66 => Key::Down,
                    67 => Key::Right,
                    68 => Key::Left,
                    70 => Key::End,
                    72 => Key::Home,
                    c => panic!("unhandled key code3 {}", c),
                }
            } else {
                panic! {"unhandled key"}
            }
        } else {
            // handle single char

            match buf[0] {
                9 => Key::Tab,
                13 => Key::Enter,
                127 => Key::Backspace,
                3 => Key::Ctrl_C,
                26 => Key::Ctrl_Z,
                c => Key::Char(c),
            }
        };

        return key;
    }
}

pub fn identify_key_code() {
    let mut raw_tty = RawTTy::new();
    loop {
        let key = raw_tty.read_one_key();
        if let Key::Char(c) = key {
            if c == 'q' as u8 {
                break;
            }
        }
    }
}

pub struct Size {
    pub width: u16,
    pub height: u16,
}

fn error_msg(msg: &'static str) {
    println!("libc error: {}", msg)
}

pub fn get_terminal_size() -> Size {
    let ws = unsafe {
        let mut ptr: MaybeUninit<libc::Winsize> = MaybeUninit::uninit();
        let ret = libc::ioctl(libc::STDOUT_FILENO, libc::TIOCGWINSZ, ptr.as_mut_ptr());
        if ret != 0 {
            error_msg("unable to read terminal size, will use default value");
            return Size {
                width: 94,
                height: 26,
            };
        }
        ptr.assume_init()
    };

    Size {
        width: ws.ws_col,
        height: ws.ws_row,
    }
}

pub fn reset_terminal_mode() {
    // magic values extracted via a println("{:?}", termios)
    let mut termios = libc::Termios {
        c_iflag: 17664,
        c_oflag: 5,
        c_cflag: 191,
        c_lflag: 35387,
        c_line: 0,
        c_cc: [
            3, 28, 127, 21, 4, 0, 1, 0, 17, 0, 26, 0, 18, 15, 23, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0,
        ],
    };

    unsafe {
        let fd = get_stdin_fd();
        if libc::tcsetattr(fd, libc::TCSADRAIN, &mut termios) != 0 {
            error_msg("unable to reset terminal to normal mode");
        }
    }
}

fn setup_raw_terminal() {
    unsafe {
        let fd = get_stdin_fd();
        let mut ptr = MaybeUninit::uninit();

        if libc::tcgetattr(fd, ptr.as_mut_ptr()) != 0 {
            error_msg("unable to get attributes of the terminal");
            return;
        }

        let mut termios = ptr.assume_init();
        let c_oflag = termios.c_oflag;

        libc::cfmakeraw(&mut termios);
        termios.c_oflag = c_oflag;

        if libc::tcsetattr(fd, libc::TCSADRAIN, &mut termios) != 0 {
            error_msg("unable to set terminal in raw mode");
        }
    }
}

unsafe fn get_stdin_fd() -> libc::int {
    use std::os::unix::io::AsRawFd;
    if libc::isatty(libc::STDIN_FILENO) == 1 {
        // interactive sessions will always have an stdin
        libc::STDIN_FILENO
    } else {
        let tty = std::fs::File::open("/dev/tty").expect("unable to open tty");
        tty.as_raw_fd()
    }
}