use std::cmp::PartialEq;
use std::io::{self, Read};
use std::os::unix::io::AsRawFd;
use std::time::Duration;
use libc::{termios, tcgetattr, tcsetattr, TCSANOW, ECHO, ICANON, fcntl, F_SETFL, O_NONBLOCK};
use crate::Keys::{Esc, Space};
pub struct KeyPress;
#[derive(Debug, PartialEq)]
pub enum Keys {
Char(char),
Ctrl(char),
Esc,
Space,
Unknown,
}
impl Keys {
pub fn to_string(&self) -> String {
match self {
Keys::Char(c) => c.to_string(),
Keys::Ctrl(c) => format!("Ctrl+{}", c),
Keys::Esc => "Esc".to_string(),
Keys::Space => "Space".to_string(),
Keys::Unknown => "Unknown".to_string(),
}
}
pub fn is_escape(&self) -> bool {
matches!(self, Keys::Esc)
}
pub fn is_space(&self) -> bool {
matches!(self, Keys::Space)
}
pub fn is_ctrl_char(&self, ch: char) -> bool {
matches!(self, Keys::Ctrl(c) if *c == ch)
}
pub fn is_char(&self, ch: char) -> bool {
matches!(self, Keys::Char(c) if *c == ch)
}
}
impl KeyPress {
fn set_raw_mode(fd: i32, enable: bool) {
let mut termios: termios = unsafe { std::mem::zeroed() };
unsafe { tcgetattr(fd, &mut termios) };
if enable {
termios.c_lflag &= !(ICANON | ECHO);
} else {
termios.c_lflag |= ICANON | ECHO;
}
unsafe { tcsetattr(fd, TCSANOW, &termios) };
}
pub fn init() -> KeyPress {
let stdin = io::stdin();
let fd = stdin.as_raw_fd();
Self::set_raw_mode(fd, true);
unsafe {
let flags = fcntl(fd, libc::F_GETFL);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
KeyPress
}
pub fn read_key(&self) -> Option<Keys> {
let mut stdin = io::stdin();
let mut buffer = [0; 1];
match stdin.read(&mut buffer) {
Ok(1) => match buffer[0] {
27 => Some(Keys::Esc),
32 => Some(Keys::Space),
c if c.is_ascii() && c.is_ascii_control() => {
let ctrl_char = (c+ 64) as char;
Some(Keys::Ctrl(c as char))
},
c if c.is_ascii() => Some(Keys::Char(c as char)),
_ => Some(Keys::Unknown),
},
Ok(_) | Err(_) => None,
}
}
pub fn get_key(&self) -> Option<String> {
self.read_key().map(|key| key.to_string())
}
}
impl Drop for KeyPress {
fn drop(&mut self) {
let stdin = io::stdin();
let fd = stdin.as_raw_fd();
KeyPress::set_raw_mode(fd, false);
}
}