console_utils/
read.rs

1//! A Cross Platform Read Implementation
2//!
3//! This module provides cross-platform functionality for reading keyboard input,
4//! allowing your console application to handle various key events uniformly.
5
6use std::io;
7
8/// # Key Enum
9///
10/// The `Key` enum represents different keyboard keys that can be captured by the
11/// `read_key` function.
12///
13/// - `ArrowUp`: Represents the arrow up key.
14/// - `ArrowDown`: Represents the arrow down key.
15/// - `ArrowRight`: Represents the arrow right key.
16/// - `ArrowLeft`: Represents the arrow left key.
17/// - `Enter`: Represents the Enter/Return key.
18/// - `Tab`: Represents the Tab key.
19/// - `Backspace`: Represents the Backspace key.
20/// - `Escape`: Represents the Escape key.
21/// - `Char(char)`: Represents any printable character on the keyboard.
22#[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
36/// # Read Key Function
37///
38/// The `read_key` function reads a single key event from the console input
39/// and returns a `Key` enum.
40pub 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/// # Windows Module
53///
54/// The `windows` module contains Windows-specific implementation details for reading
55/// keyboard input. It utilizes the `windows-sys` crate to interact with Windows Console API.
56#[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/// # Unix Module
107///
108/// The `unix` module contains Unix-specific implementation details for reading
109/// keyboard input. It uses the `libc` crate to manipulate terminal attributes.
110#[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    // Internal function for disabling line buffering.
119    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    // Internal function for enabling line buffering.
135    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    // Internal function for reading a key from the console.
151    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                    // Arrow key sequence
159                    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}