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