use std::{io, time::Duration};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Key {
ArrowUp,
ArrowDown,
ArrowRight,
ArrowLeft,
Enter,
Tab,
Backspace,
Escape,
Char(char),
Unknown,
}
pub fn read_key() -> io::Result<Key> {
#[cfg(windows)]
{
windows::read_key()
}
#[cfg(unix)]
{
unix::read_key()
}
}
pub fn key_pressed_within(timeout: Duration) -> io::Result<Option<Key>> {
#[cfg(windows)]
{
windows::key_pressed_within(timeout)
}
#[cfg(unix)]
{
unix::key_pressed_within(timeout)
}
}
#[cfg(windows)]
pub mod windows {
use super::Key;
use std::io;
use std::mem;
use std::os::windows::raw::HANDLE;
use std::time::Instant;
use windows_sys::Win32::Foundation::{INVALID_HANDLE_VALUE, WAIT_OBJECT_0, WAIT_TIMEOUT};
use windows_sys::Win32::System::Console::{
GetStdHandle, PeekConsoleInputW, ReadConsoleInputW, INPUT_RECORD, KEY_EVENT,
KEY_EVENT_RECORD, STD_INPUT_HANDLE,
};
use windows_sys::Win32::System::Threading::WaitForSingleObject;
use windows_sys::Win32::UI::Input::KeyboardAndMouse;
pub(crate) fn read_key() -> io::Result<Key> {
let handle = unsafe { GetStdHandle(STD_INPUT_HANDLE) };
let mut buffer: INPUT_RECORD = unsafe { mem::zeroed() };
let mut events_read: u32 = unsafe { mem::zeroed() };
loop {
let success = unsafe { ReadConsoleInputW(handle, &mut buffer, 1, &mut events_read) };
if success == 0 {
return Err(io::Error::last_os_error());
}
if events_read == 0 {
return Err(io::Error::new(
io::ErrorKind::Other,
"ReadConsoleInput returned no events, instead of waiting for an event",
));
}
if events_read == 1 && buffer.EventType == KEY_EVENT as u16 {
let key_event: KEY_EVENT_RECORD = unsafe { mem::transmute(buffer.Event) };
if key_event.bKeyDown != 0 {
return match key_event.wVirtualKeyCode {
KeyboardAndMouse::VK_UP => Ok(Key::ArrowUp),
KeyboardAndMouse::VK_DOWN => Ok(Key::ArrowDown),
KeyboardAndMouse::VK_RIGHT => Ok(Key::ArrowRight),
KeyboardAndMouse::VK_LEFT => Ok(Key::ArrowLeft),
KeyboardAndMouse::VK_RETURN => Ok(Key::Enter),
KeyboardAndMouse::VK_TAB => Ok(Key::Tab),
KeyboardAndMouse::VK_BACK => Ok(Key::Backspace),
KeyboardAndMouse::VK_ESCAPE => Ok(Key::Escape),
c => Ok(Key::Char(char::from_u32(c as u32).unwrap_or_default())),
};
}
}
}
}
pub(super) fn key_pressed_within(timeout: std::time::Duration) -> std::io::Result<Option<Key>> {
unsafe fn ensure_head_is_keydown_or_empty(handle: HANDLE) -> io::Result<bool> {
let mut rec: INPUT_RECORD = mem::zeroed();
let mut read: u32 = 0;
if PeekConsoleInputW(handle, &mut rec, 1, &mut read) == 0 {
return Err(io::Error::last_os_error());
}
if read == 0 {
return Ok(false); }
if rec.EventType == KEY_EVENT as u16 {
let key: KEY_EVENT_RECORD = mem::transmute(rec.Event);
if key.bKeyDown != 0 {
return Ok(true);
}
let mut took: u32 = 0;
if ReadConsoleInputW(handle, &mut rec, 1, &mut took) == 0 {
return Err(io::Error::last_os_error());
}
return Ok(false);
}
let mut took: u32 = 0;
if ReadConsoleInputW(handle, &mut rec, 1, &mut took) == 0 {
return Err(io::Error::last_os_error());
}
Ok(false)
}
let handle: HANDLE = unsafe { GetStdHandle(STD_INPUT_HANDLE) };
if handle == 0 as HANDLE || handle == INVALID_HANDLE_VALUE {
return Err(io::Error::last_os_error());
}
let deadline = Instant::now() + timeout;
loop {
if unsafe { ensure_head_is_keydown_or_empty(handle)? } {
return Ok(Some(read_key()?));
}
let now = Instant::now();
if now >= deadline {
return Ok(None);
}
let remaining_ms = ((deadline - now).as_millis()).min(u32::MAX as u128) as u32;
match unsafe { WaitForSingleObject(handle, remaining_ms) } {
WAIT_TIMEOUT => return Ok(None),
WAIT_OBJECT_0 => continue,
_ => return Err(io::Error::last_os_error()),
}
}
}
}
#[cfg(unix)]
pub mod unix {
use libc::{
poll, pollfd, tcgetattr, tcsetattr, termios, ECHO, ICANON, POLLIN, STDIN_FILENO, TCSANOW,
};
use std::io::{self, Read};
use std::mem;
use std::time::Duration;
use super::Key;
fn get_termios(fd: i32) -> io::Result<termios> {
let mut t: termios = unsafe { mem::zeroed() };
let rc = unsafe { tcgetattr(fd, &mut t as *mut termios) };
if rc != 0 {
Err(io::Error::last_os_error())
} else {
Ok(t)
}
}
fn set_termios(fd: i32, t: &termios) -> io::Result<()> {
let rc = unsafe { tcsetattr(fd, TCSANOW, t as *const termios) };
if rc != 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
struct RawMode {
fd: i32,
saved: termios,
}
impl RawMode {
fn new(fd: i32) -> io::Result<Self> {
let mut current = get_termios(fd)?;
let saved = current;
current.c_lflag &= !(ICANON | ECHO);
set_termios(fd, ¤t)?;
Ok(Self { fd, saved })
}
}
impl Drop for RawMode {
fn drop(&mut self) {
let _ = set_termios(self.fd, &self.saved);
}
}
fn read_key_raw() -> io::Result<Key> {
let mut buffer = [0u8; 3];
let n = std::io::stdin().read(&mut buffer)?;
if n == 0 {
return Ok(Key::Unknown);
}
match buffer[0] {
27 => {
if n >= 3 && buffer[1] == b'[' {
match buffer[2] {
b'A' => Ok(Key::ArrowUp),
b'B' => Ok(Key::ArrowDown),
b'C' => Ok(Key::ArrowRight),
b'D' => Ok(Key::ArrowLeft),
_ => Ok(Key::Unknown),
}
} else if n == 1 {
Ok(Key::Escape)
} else {
Ok(Key::Unknown)
}
}
b'\n' => Ok(Key::Enter),
b'\t' => Ok(Key::Tab),
127 => Ok(Key::Backspace),
c => Ok(Key::Char(c as char)),
}
}
pub(crate) fn read_key() -> io::Result<Key> {
let _rm = RawMode::new(STDIN_FILENO)?;
read_key_raw()
}
pub(super) fn key_pressed_within(timeout: Duration) -> io::Result<Option<Key>> {
let _rm = RawMode::new(STDIN_FILENO)?;
let mut fds = pollfd {
fd: STDIN_FILENO,
events: POLLIN,
revents: 0,
};
let ms = timeout.as_millis().min(i32::MAX as u128) as i32;
let rc = unsafe { poll(&mut fds as *mut pollfd, 1, ms) };
if rc < 0 {
return Err(io::Error::last_os_error());
}
if rc == 0 {
return Ok(None);
}
match read_key_raw() {
Ok(k) => Ok(Some(k)),
Err(e) if e.kind() == io::ErrorKind::WouldBlock => Ok(None),
Err(e) => Err(e),
}
}
}