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 {
let mut buf = [0u8; 4];
let size = self
.stdin
.read(&mut buf)
.expect("unable to read from terminal");
let key = if size == 4 {
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 {
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 {
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() {
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 {
libc::STDIN_FILENO
} else {
let tty = std::fs::File::open("/dev/tty").expect("unable to open tty");
tty.as_raw_fd()
}
}