1use std::io;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum Key {
11 ArrowUp,
13 ArrowDown,
15 ArrowRight,
17 ArrowLeft,
19 Enter,
21 Tab,
23 Backspace,
25 Escape,
27 Char(char),
29 Unknown,
31}
32
33pub fn read_key() -> io::Result<Key> {
35 #[cfg(windows)]
36 {
37 windows::read_key()
38 }
39
40 #[cfg(unix)]
41 {
42 unix::read_key()
43 }
44}
45
46#[cfg(windows)]
49pub mod windows {
50 use super::Key;
51 use std::io;
52 use std::mem;
53 use windows_sys::Win32::System::Console::{
54 GetStdHandle, ReadConsoleInputW, INPUT_RECORD, KEY_EVENT, KEY_EVENT_RECORD,
55 STD_INPUT_HANDLE,
56 };
57 use windows_sys::Win32::UI::Input::KeyboardAndMouse;
58
59 pub(crate) fn read_key() -> io::Result<Key> {
60 let handle = unsafe { GetStdHandle(STD_INPUT_HANDLE) };
61 let mut buffer: INPUT_RECORD = unsafe { mem::zeroed() };
62
63 let mut events_read: u32 = unsafe { mem::zeroed() };
64
65 loop {
66 let success = unsafe { ReadConsoleInputW(handle, &mut buffer, 1, &mut events_read) };
67 if success == 0 {
68 return Err(io::Error::last_os_error());
69 }
70 if events_read == 0 {
71 return Err(io::Error::new(
72 io::ErrorKind::Other,
73 "ReadConsoleInput returned no events, instead of waiting for an event",
74 ));
75 }
76
77 if events_read == 1 && buffer.EventType == KEY_EVENT as u16 {
78 let key_event: KEY_EVENT_RECORD = unsafe { mem::transmute(buffer.Event) };
79
80 if key_event.bKeyDown != 0 {
81 return match key_event.wVirtualKeyCode {
82 KeyboardAndMouse::VK_UP => Ok(Key::ArrowUp),
83 KeyboardAndMouse::VK_DOWN => Ok(Key::ArrowDown),
84 KeyboardAndMouse::VK_RIGHT => Ok(Key::ArrowRight),
85 KeyboardAndMouse::VK_LEFT => Ok(Key::ArrowLeft),
86 KeyboardAndMouse::VK_RETURN => Ok(Key::Enter),
87 KeyboardAndMouse::VK_TAB => Ok(Key::Tab),
88 KeyboardAndMouse::VK_BACK => Ok(Key::Backspace),
89 KeyboardAndMouse::VK_ESCAPE => Ok(Key::Escape),
90 c => Ok(Key::Char(char::from_u32(c as u32).unwrap_or_default())),
91 };
92 }
93 }
94 }
95 }
96}
97
98#[cfg(unix)]
101pub mod unix {
102 use libc::{tcgetattr, tcsetattr, ECHO, ICANON, STDIN_FILENO, TCSANOW};
103 use std::io::{self, Read};
104 use std::mem;
105
106 use super::Key;
107
108 fn disable_line_buffering() -> io::Result<()> {
110 let mut termios = unsafe { mem::zeroed() };
111 if unsafe { tcgetattr(STDIN_FILENO, &mut termios) } != 0 {
112 return Err(io::Error::last_os_error());
113 }
114
115 termios.c_lflag &= !(ICANON | ECHO);
116
117 if unsafe { tcsetattr(STDIN_FILENO, TCSANOW, &termios) } != 0 {
118 return Err(io::Error::last_os_error());
119 }
120
121 Ok(())
122 }
123
124 fn enable_line_buffering() -> io::Result<()> {
126 let mut termios = unsafe { mem::zeroed() };
127 if unsafe { tcgetattr(STDIN_FILENO, &mut termios) } != 0 {
128 return Err(io::Error::last_os_error());
129 }
130
131 termios.c_lflag |= ICANON | ECHO;
132
133 if unsafe { tcsetattr(STDIN_FILENO, TCSANOW, &termios) } != 0 {
134 return Err(io::Error::last_os_error());
135 }
136
137 Ok(())
138 }
139
140 pub(crate) fn read_key() -> io::Result<Key> {
142 let mut buffer = [0; 3];
143 disable_line_buffering()?;
144 if std::io::stdin().read(&mut buffer).is_ok() {
145 enable_line_buffering()?;
146 match buffer[0] {
147 27 => {
148 if buffer[1] == 91 {
150 match buffer[2] {
151 65 => Ok(Key::ArrowUp),
152 66 => Ok(Key::ArrowDown),
153 67 => Ok(Key::ArrowRight),
154 68 => Ok(Key::ArrowLeft),
155 _ => Ok(Key::Unknown),
156 }
157 } else {
158 Ok(Key::Unknown)
159 }
160 }
161 b'\n' => Ok(Key::Enter),
162 b'\t' => Ok(Key::Tab),
163 127 => Ok(Key::Backspace),
164 c => Ok(Key::Char(c as char)),
165 }
166 } else {
167 Err(io::Error::last_os_error())
168 }
169 }
170}