diff --git a/src/ansi.rs b/src/ansi.rs
index fa661d1..5ad675e 100644
--- a/src/ansi.rs
+++ b/src/ansi.rs
@@ -2,10 +2,16 @@
//!
//! These should work on *most* terminals (i.e. Xterm compatible terminals)
//!
-//! For these to work on Windows you need to run the `enable_ansi` function in the os module
+//! For these to work on Windows you need to run the `enable_ansi` function inside this module
use std::io::{self, Write};
+#[cfg(unix)]
+pub use crate::unix::enable_ansi;
+
+#[cfg(windows)]
+pub use crate::windows::enable_ansi;
+
/// Sets the terminal to an arbitrary 12-bit/truecolor color in the foreground when printed
#[must_use]
pub fn rgb_color_code_fg(red: u8, green: u8, blue: u8) -> String {
diff --git a/src/os.rs b/src/control.rs
similarity index 93%
rename from src/os.rs
rename to src/control.rs
index 9c2f623..277fe8b 100644
--- a/src/os.rs
+++ b/src/control.rs
@@ -5,10 +5,10 @@
use std::io;
#[cfg(unix)]
-pub use crate::unix::os::*;
+pub use crate::unix::{disable_raw_mode, enable_raw_mode, get_terminal_size};
#[cfg(windows)]
-pub use crate::windows::os::*;
+pub use crate::windows::{disable_raw_mode, enable_raw_mode, get_terminal_size};
/// Struct that calls `enable_raw_mode` on construction
/// and `disable_raw_mode` on destruction
diff --git a/src/input.rs b/src/input.rs
index 226e5ac..a904c8b 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -114,7 +114,7 @@ pub enum KeyType {
}
#[cfg(unix)]
-pub use crate::unix::input::*;
+pub use crate::unix::poll_input;
#[cfg(windows)]
-pub use crate::windows::input::*;
+pub use crate::windows::poll_input;
diff --git a/src/lib.rs b/src/lib.rs
index e52626d..403085b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -47,12 +47,12 @@ mod unix;
mod windows;
pub mod ansi;
+pub mod control;
pub mod input;
-pub mod os;
pub mod prelude {
//! Covenience re-export of common members
pub use crate::ansi::*;
+ pub use crate::control::*;
pub use crate::input::*;
- pub use crate::os::*;
}
diff --git a/src/unix.rs b/src/unix.rs
index 2e97d6f..8f8c080 100644
--- a/src/unix.rs
+++ b/src/unix.rs
@@ -1,5 +1,10 @@
-use std::ffi::{c_int, c_short, c_uint, c_ulong, c_ushort};
+use std::ffi::{c_int, c_uint, c_ulong, c_ushort};
use std::io;
+use std::sync::LazyLock;
+
+use crate::input::{Event, Key, KeyModifiers, KeyType};
+use std::ffi::{c_short, c_void};
+use std::time::Duration;
unsafe extern "C" {
fn ioctl(fd: c_int, request: c_ulong, argp: *mut u8) -> c_int;
@@ -55,297 +60,274 @@ fn set_attributes(fd: c_int, termios: &mut Termios) -> io::Result<()> {
Ok(())
}
-fn make_raw(termios: &mut Termios) {
+static TERMIOS: LazyLock> = LazyLock::new(|| {
+ let mut orig_termios = Termios::default();
+ get_attributes(STDIN_FILENO, &mut orig_termios).ok()?;
+ Some(orig_termios)
+});
+
+/// Enables raw mode, which disables line buffering, input echoing, and output canonicalization
+///
+/// # Errors
+///
+/// If there is no stdin,
+/// stdin is not a tty,
+/// or it fails to change terminal settings
+pub fn enable_raw_mode() -> io::Result<()> {
+ let mut termios = (*TERMIOS).ok_or(io::Error::other("Failed to get terminal properties"))?;
unsafe {
- cfmakeraw(termios);
+ cfmakeraw(&mut termios);
}
- // termios.iflag |= !(ICRNL);
+ set_attributes(STDIN_FILENO, &mut termios)?;
+ Ok(())
}
-pub mod os {
- use super::{STDIN_FILENO, STDOUT_FILENO, TIOCGWINSZ};
- use super::{Termios, Winsize};
- use super::{get_attributes, ioctl, make_raw, set_attributes};
- use std::io;
- use std::sync::LazyLock;
+/// Disables raw mode, which enables line buffering, input echoing, and output canonicalization
+///
+/// # Errors
+///
+/// If there is no stdin,
+/// stdin is not a tty,
+/// or it fails to change terminal settings
+pub fn disable_raw_mode() -> io::Result<()> {
+ let mut termios = (*TERMIOS).ok_or(io::Error::other("Failed to get terminal properties"))?;
+ set_attributes(STDIN_FILENO, &mut termios)?;
+ Ok(())
+}
- static TERMIOS: LazyLock > = LazyLock::new(|| {
- let mut orig_termios = Termios::default();
- get_attributes(STDIN_FILENO, &mut orig_termios).ok()?;
- Some(orig_termios)
- });
+/// Enables ANSI support on Windows terminals
+///
+/// ANSI is on by default on *nix machines but still exists on them for simpler usage
+///
+/// # Errors
+///
+/// Never on *nix
+///
+/// If There is no stdout,
+/// if stdout isn't a TTY, or
+/// if it cannot change terminal properties on Windows
+#[cfg(unix)]
+pub fn enable_ansi() -> io::Result<()> {
+ // ANSI is on by default on unix platforms
+ // This is here for compatibility with the windows version of this API
+ Ok(())
+}
- /// Enables raw mode, which disables line buffering, input echoing, and output canonicalization
- ///
- /// # Errors
- ///
- /// If there is no stdin,
- /// stdin is not a tty,
- /// or it fails to change terminal settings
- pub fn enable_raw_mode() -> io::Result<()> {
- let mut termios =
- (*TERMIOS).ok_or(io::Error::other("Failed to get terminal properties"))?;
- make_raw(&mut termios);
- set_attributes(STDIN_FILENO, &mut termios)?;
- Ok(())
+/// Gets the size of the terminal
+///
+/// Returns in (width, height) format
+///
+/// # Errors
+///
+/// If there is no stdout,
+/// if stdout isn't a TTY, or
+/// if it fails to retrieve the terminal size
+pub fn get_terminal_size() -> io::Result<(u16, u16)> {
+ let mut winsize = Winsize::default();
+ let ioctl_result = unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ, (&raw mut winsize).cast::()) };
+
+ if ioctl_result == 0 {
+ Ok((winsize.col, winsize.row))
+ } else {
+ Err(io::Error::last_os_error())
}
+}
- /// Disables raw mode, which enables line buffering, input echoing, and output canonicalization
- ///
- /// # Errors
- ///
- /// If there is no stdin,
- /// stdin is not a tty,
- /// or it fails to change terminal settings
- pub fn disable_raw_mode() -> io::Result<()> {
- let mut termios =
- (*TERMIOS).ok_or(io::Error::other("Failed to get terminal properties"))?;
- set_attributes(STDIN_FILENO, &mut termios)?;
- Ok(())
+unsafe extern "C" {
+ fn poll(fds: *mut PollFD, nfds: c_ulong, timeout: c_int) -> c_int;
+ fn read(fd: c_int, buf: *mut c_void, count: c_ulong) -> c_short;
+}
+
+#[repr(C)]
+#[derive(Debug, Clone, Copy)]
+struct PollFD {
+ fd: c_int,
+ events: c_short,
+ revents: c_short,
+}
+
+struct ReadIterator {
+ fd: c_int,
+ buf: u8,
+}
+
+impl ReadIterator {
+ fn new(fd: c_int) -> Self {
+ Self { fd, buf: 0 }
}
+}
- /// Enables ANSI support on Windows terminals
- ///
- /// ANSI is on by default on *nix machines but still exists on them for simpler usage
- ///
- /// # Errors
- ///
- /// Never on *nix
- ///
- /// If There is no stdout,
- /// if stdout isn't a TTY, or
- /// if it cannot change terminal properties on Windows
- pub fn enable_ansi() -> io::Result<()> {
- // ANSI is on by default on unix platforms
- // This is here for compatibility with the windows version of this API
- Ok(())
- }
+impl Iterator for ReadIterator {
+ type Item = io::Result;
- /// Gets the size of the terminal
- ///
- /// Returns in (width, height) format
- ///
- /// # Errors
- ///
- /// If there is no stdout,
- /// if stdout isn't a TTY, or
- /// if it fails to retrieve the terminal size
- pub fn get_terminal_size() -> io::Result<(u16, u16)> {
- let mut winsize = Winsize::default();
- let ioctl_result =
- unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ, (&raw mut winsize).cast::()) };
+ fn next(&mut self) -> Option {
+ let bytes_read = unsafe { read(self.fd, (&raw mut self.buf).cast::(), 1) };
- if ioctl_result == 0 {
- Ok((winsize.col, winsize.row))
- } else {
- Err(io::Error::last_os_error())
+ match bytes_read {
+ 1.. => Some(Ok(self.buf)),
+ 0 => None,
+ _ => Some(Err(io::Error::last_os_error())),
}
}
}
-pub mod input {
- use super::{POLLIN, STDIN_FILENO};
- use crate::input::{Event, Key, KeyModifiers, KeyType};
- use std::ffi::{c_int, c_short, c_ulong, c_void};
- use std::io;
- use std::time::Duration;
+/// Attempts to fetch input from stdin
+///
+/// # Errors
+/// If the timeout has expired or
+/// there was an error getting the data
+pub fn poll_input(timeout: Duration) -> io::Result {
+ let mut fds = [PollFD {
+ fd: STDIN_FILENO,
+ events: POLLIN,
+ revents: 0,
+ }];
+ let result = unsafe {
+ #[allow(clippy::cast_possible_truncation)]
+ poll(
+ fds.as_mut_ptr(),
+ fds.len() as c_ulong,
+ timeout.as_millis() as c_int,
+ )
+ };
+ let mut read_iter = ReadIterator::new(STDIN_FILENO);
- unsafe extern "C" {
- fn poll(fds: *mut PollFD, nfds: c_ulong, timeout: c_int) -> c_int;
- fn read(fd: c_int, buf: *mut c_void, count: c_ulong) -> c_short;
+ let timed_out: io::Error = io::ErrorKind::TimedOut.into();
+
+ match result {
+ 1.. => {
+ let item = read_iter.next().ok_or(timed_out)??;
+ try_parse_event(item, &mut read_iter)
+ }
+ 0 => Err(timed_out),
+ _ => Err(io::Error::last_os_error()),
}
+}
- #[repr(C)]
- #[derive(Debug, Clone, Copy)]
- struct PollFD {
- fd: c_int,
- events: c_short,
- revents: c_short,
- }
-
- struct ReadIterator {
- fd: c_int,
- buf: u8,
- }
-
- impl ReadIterator {
- fn new(fd: c_int) -> Self {
- Self { fd, buf: 0 }
+fn try_parse_event(item: u8, iter: &mut I) -> io::Result
+where
+ I: Iterator- >,
+{
+ match item {
+ b'\x1b' => try_parse_ansi_sequence(iter),
+ b'\r' => Ok(Event::Key(
+ Key::Char('\n'),
+ KeyType::Press,
+ KeyModifiers::none(),
+ )),
+ b'\n' => Ok(Event::Key(
+ Key::Char('j'),
+ KeyType::Press,
+ KeyModifiers::none().ctrl(),
+ )),
+ b'\t' => Ok(Event::Key(
+ Key::Char('\t'),
+ KeyType::Press,
+ KeyModifiers::none(),
+ )),
+ b'\x7f' => Ok(Event::Key(
+ Key::Backspace,
+ KeyType::Press,
+ KeyModifiers::none(),
+ )),
+ b'\0' => Ok(Event::Key(Key::Null, KeyType::Press, KeyModifiers::none())),
+ c @ b'\x01'..=b'\x1a' => Ok(Event::Key(
+ Key::Char((c + 96) as char),
+ KeyType::Press,
+ KeyModifiers::none().ctrl(),
+ )),
+ c @ b'\x1c'..=b'\x1f' => Ok(Event::Key(
+ Key::Char((c + 24) as char),
+ KeyType::Press,
+ KeyModifiers::none().ctrl(),
+ )),
+ c => {
+ let character = parse_utf8_char(c, iter)?;
+ Ok(Event::Key(
+ Key::Char(parse_utf8_char(c, iter)?),
+ KeyType::Press,
+ KeyModifiers {
+ shift: character.is_uppercase(),
+ alt: false,
+ ctrl: false,
+ meta: false,
+ },
+ ))
}
}
+}
- impl Iterator for ReadIterator {
- type Item = io::Result
;
+fn parse_utf8_char(c: u8, iter: &mut I) -> io::Result
+where
+ I: Iterator- >,
+{
+ let error = || io::Error::new(io::ErrorKind::InvalidData, "Input char is not valid UTF-8");
+ let mut bytes = vec![c];
- fn next(&mut self) -> Option
{
- let bytes_read = unsafe { read(self.fd, (&raw mut self.buf).cast::(), 1) };
-
- match bytes_read {
- 1.. => Some(Ok(self.buf)),
- 0 => None,
- _ => Some(Err(io::Error::last_os_error())),
- }
+ for _ in 1..=4 {
+ if let Ok(string) = std::str::from_utf8(&bytes) {
+ return Ok(string.chars().next().unwrap());
}
+ bytes.push(iter.next().ok_or_else(error)??);
}
+ Err(error())
+}
- /// Attempts to fetch input from stdin
- ///
- /// # Errors
- /// If the timeout has expired or
- /// there was an error getting the data
- pub fn poll_input(timeout: Duration) -> io::Result {
- let mut fds = [PollFD {
- fd: STDIN_FILENO,
- events: POLLIN,
- revents: 0,
- }];
- let result = unsafe {
- #[allow(clippy::cast_possible_truncation)]
- poll(
- fds.as_mut_ptr(),
- fds.len() as c_ulong,
- timeout.as_millis() as c_int,
- )
- };
- let mut read_iter = ReadIterator::new(STDIN_FILENO);
-
- let timed_out: io::Error = io::ErrorKind::TimedOut.into();
-
- match result {
- 1.. => {
- let item = read_iter.next().ok_or(timed_out)??;
- try_parse_event(item, &mut read_iter)
- }
- 0 => Err(timed_out),
- _ => Err(io::Error::last_os_error()),
- }
- }
-
- fn try_parse_event(item: u8, iter: &mut I) -> io::Result
- where
- I: Iterator- >,
- {
- match item {
- b'\x1b' => try_parse_ansi_sequence(iter),
- b'\r' => Ok(Event::Key(
- Key::Char('\n'),
+fn try_parse_ansi_sequence
(iter: &mut I) -> io::Result
+where
+ I: Iterator- >,
+{
+ let error = io::Error::other("Could not parse event");
+ match iter.next() {
+ Some(Ok(b'O')) => match iter.next() {
+ Some(Ok(val @ b'P'..=b's')) => Ok(Event::Key(
+ Key::F(1 + val - b'P'),
KeyType::Press,
KeyModifiers::none(),
)),
- b'\n' => Ok(Event::Key(
- Key::Char('j'),
- KeyType::Press,
- KeyModifiers::none().ctrl(),
- )),
- b'\t' => Ok(Event::Key(
- Key::Char('\t'),
- KeyType::Press,
- KeyModifiers::none(),
- )),
- b'\x7f' => Ok(Event::Key(
- Key::Backspace,
- KeyType::Press,
- KeyModifiers::none(),
- )),
- b'\0' => Ok(Event::Key(Key::Null, KeyType::Press, KeyModifiers::none())),
- c @ b'\x01'..=b'\x1a' => Ok(Event::Key(
- Key::Char((c + 96) as char),
- KeyType::Press,
- KeyModifiers::none().ctrl(),
- )),
- c @ b'\x1c'..=b'\x1f' => Ok(Event::Key(
- Key::Char((c + 24) as char),
- KeyType::Press,
- KeyModifiers::none().ctrl(),
- )),
- c => {
- let character = parse_utf8_char(c, iter)?;
- Ok(Event::Key(
- Key::Char(parse_utf8_char(c, iter)?),
- KeyType::Press,
- KeyModifiers {
- shift: character.is_uppercase(),
- alt: false,
- ctrl: false,
- meta: false,
- },
- ))
- }
- }
- }
-
- fn parse_utf8_char
(c: u8, iter: &mut I) -> io::Result
- where
- I: Iterator- >,
- {
- let error = || io::Error::new(io::ErrorKind::InvalidData, "Input char is not valid UTF-8");
- let mut bytes = vec![c];
-
- for _ in 1..=4 {
- if let Ok(string) = std::str::from_utf8(&bytes) {
- return Ok(string.chars().next().unwrap());
- }
- bytes.push(iter.next().ok_or_else(error)??);
- }
- Err(error())
- }
-
- fn try_parse_ansi_sequence
(iter: &mut I) -> io::Result
- where
- I: Iterator- >,
- {
- let error = io::Error::other("Could not parse event");
- match iter.next() {
- Some(Ok(b'O')) => match iter.next() {
- Some(Ok(val @ b'P'..=b's')) => Ok(Event::Key(
- Key::F(1 + val - b'P'),
- KeyType::Press,
- KeyModifiers::none(),
- )),
- _ => Err(error),
- },
- Some(Ok(b'[')) => try_parse_csi_sequence(iter).ok_or(error),
_ => Err(error),
- }
+ },
+ Some(Ok(b'[')) => try_parse_csi_sequence(iter).ok_or(error),
+ _ => Err(error),
}
+}
- fn try_parse_csi_sequence
(iter: &mut I) -> Option
- where
- I: Iterator- >,
- {
- match iter.next() {
- Some(Ok(b'[')) => match iter.next() {
- Some(Ok(val @ b'A'..=b'E')) => Some(Event::Key(
- Key::F(1 + val - b'A'),
- KeyType::Press,
- KeyModifiers::none(),
- )),
- _ => None,
- },
- Some(Ok(b'D')) => Some(Event::Key(Key::Left, KeyType::Press, KeyModifiers::none())),
- Some(Ok(b'C')) => Some(Event::Key(Key::Right, KeyType::Press, KeyModifiers::none())),
- Some(Ok(b'A')) => Some(Event::Key(Key::Up, KeyType::Press, KeyModifiers::none())),
- Some(Ok(b'B')) => Some(Event::Key(Key::Down, KeyType::Press, KeyModifiers::none())),
- Some(Ok(b'H')) => Some(Event::Key(Key::Home, KeyType::Press, KeyModifiers::none())),
- Some(Ok(b'F')) => Some(Event::Key(Key::End, KeyType::Press, KeyModifiers::none())),
- Some(Ok(b'Z')) => Some(Event::Key(
- Key::Tab,
+fn try_parse_csi_sequence
(iter: &mut I) -> Option
+where
+ I: Iterator- >,
+{
+ match iter.next() {
+ Some(Ok(b'[')) => match iter.next() {
+ Some(Ok(val @ b'A'..=b'E')) => Some(Event::Key(
+ Key::F(1 + val - b'A'),
KeyType::Press,
- KeyModifiers::none().shift(),
+ KeyModifiers::none(),
)),
_ => None,
- }
- }
-
- #[test]
- fn test_parse_utf8() {
- let string = "abcéŷ¤£€ù%323";
- let ref mut bytes = string.bytes().map(|x| Ok(x));
- let chars = string.chars();
- for c in chars {
- let b = bytes.next().unwrap().unwrap();
- let character = parse_utf8_char(b, bytes).unwrap();
- assert!(c == character);
- }
+ },
+ Some(Ok(b'D')) => Some(Event::Key(Key::Left, KeyType::Press, KeyModifiers::none())),
+ Some(Ok(b'C')) => Some(Event::Key(Key::Right, KeyType::Press, KeyModifiers::none())),
+ Some(Ok(b'A')) => Some(Event::Key(Key::Up, KeyType::Press, KeyModifiers::none())),
+ Some(Ok(b'B')) => Some(Event::Key(Key::Down, KeyType::Press, KeyModifiers::none())),
+ Some(Ok(b'H')) => Some(Event::Key(Key::Home, KeyType::Press, KeyModifiers::none())),
+ Some(Ok(b'F')) => Some(Event::Key(Key::End, KeyType::Press, KeyModifiers::none())),
+ Some(Ok(b'Z')) => Some(Event::Key(
+ Key::Tab,
+ KeyType::Press,
+ KeyModifiers::none().shift(),
+ )),
+ _ => None,
+ }
+}
+
+#[test]
+fn test_parse_utf8() {
+ let string = "abcéŷ¤£€ù%323";
+ let ref mut bytes = string.bytes().map(|x| Ok(x));
+ let chars = string.chars();
+ for c in chars {
+ let b = bytes.next().unwrap().unwrap();
+ let character = parse_utf8_char(b, bytes).unwrap();
+ assert!(c == character);
}
}
diff --git a/src/windows.rs b/src/windows.rs
index a8418f8..9d91f1d 100644
--- a/src/windows.rs
+++ b/src/windows.rs
@@ -1,5 +1,6 @@
-use std::io;
+use crate::input::{Event, Key, KeyModifiers, KeyType};
use std::os::windows::raw::HANDLE;
+use std::{io, mem, time::Duration};
#[link(name = "kernel32")]
unsafe extern "system" {
@@ -62,234 +63,214 @@ fn get_console_mode(handle: HANDLE, mode: &mut u32) -> io::Result<()> {
}
}
-pub mod os {
- use super::{
- ConsoleScreenBufferInfo, GetConsoleScreenBufferInfo, get_console_mode, get_stdin_handle,
- get_stdout_handle, set_console_mode,
- };
- use super::{
- ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT,
- ENABLE_VIRTUAL_TERMINAL_PROCESSING,
- };
- use std::io;
+/// Enables raw mode, which disables line buffering, input echoing, and output canonicalization
+///
+/// # Errors
+///
+/// If there is no stdin,
+/// stdin is not a tty,
+/// or it fails to change terminal settings
+pub fn enable_raw_mode() -> io::Result<()> {
+ let handle = get_stdin_handle()?;
+ let mut mode = 0;
+ get_console_mode(handle, &mut mode)?;
+ mode &= !(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
+ set_console_mode(handle, mode)?;
+ Ok(())
+}
- /// Enables raw mode, which disables line buffering, input echoing, and output canonicalization
- ///
- /// # Errors
- ///
- /// If there is no stdin,
- /// stdin is not a tty,
- /// or it fails to change terminal settings
- pub fn enable_raw_mode() -> io::Result<()> {
- let handle = get_stdin_handle()?;
- let mut mode = 0;
- get_console_mode(handle, &mut mode)?;
- mode &= !(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
- set_console_mode(handle, mode)?;
- Ok(())
+/// Disables raw mode, which enables line buffering, input echoing, and output canonicalization
+///
+/// # Errors
+///
+/// If there is no stdin,
+/// stdin is not a tty,
+/// or it fails to change terminal settings
+pub fn disable_raw_mode() -> io::Result<()> {
+ let handle = get_stdin_handle()?;
+ let mut mode = 0;
+ get_console_mode(handle, &mut mode)?;
+ mode |= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
+ set_console_mode(handle, mode)?;
+ Ok(())
+}
+
+/// Enables ANSI support on Windows terminals
+///
+/// ANSI is on by default on *nix machines but still exists on them for simpler usage
+///
+/// # Errors
+///
+/// Never on *nix
+///
+/// On Windows, if There is no stdout,
+/// if stdout isn't a TTY, or
+/// if it cannot change terminal properties
+pub fn enable_ansi() -> io::Result<()> {
+ let handle = get_stdout_handle()?;
+ let mut mode = 0;
+ get_console_mode(handle, &mut mode)?;
+ mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
+ set_console_mode(handle, mode)?;
+ Ok(())
+}
+
+/// Gets the size of the terminal
+///
+/// Returns in (width, height) format
+///
+/// # Errors
+///
+/// If there is no stdout,
+/// if stdout isn't a TTY, or
+/// if it fails to retrieve the terminal size
+pub fn get_terminal_size() -> io::Result<(u16, u16)> {
+ let handle = get_stdout_handle()?;
+ let mut csbi = ConsoleScreenBufferInfo::default();
+ if unsafe { GetConsoleScreenBufferInfo(handle, &mut csbi) != 0 } {
+ let width = csbi.x;
+ let height = csbi.y;
+ return Ok((width, height));
+ }
+ Err(io::Error::last_os_error())
+}
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+struct InputRecord {
+ event_type: u16,
+ event: EventRecord,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+union EventRecord {
+ key: KeyEventRecord,
+ focus: FocusEventRecord,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+struct KeyEventRecord {
+ key_down: i32,
+ repeat_count: u16,
+ virtual_key_code: u16,
+ virtual_scan_code: u16,
+ u_char: CharUnion,
+ control_key_state: u32,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+struct FocusEventRecord {
+ set_focus: i32,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+union CharUnion {
+ unicode_char: u16,
+ ascii_char: u8,
+}
+
+unsafe extern "system" {
+ fn ReadConsoleInputW(
+ console_input: HANDLE,
+ buffer: *mut InputRecord,
+ length: u32,
+ number_of_events_read: *mut u32,
+ ) -> i32;
+ fn WaitForSingleObject(handle: HANDLE, wait_time_ms: u32) -> u32;
+}
+
+/// Attempts to fetch input from stdin
+///
+/// # Errors
+/// If the timeout has expired or
+/// there was an error getting the data
+pub fn poll_input(timeout: Duration) -> io::Result
{
+ let handle = get_stdin_handle()?;
+ let mut record: InputRecord = unsafe { mem::zeroed() };
+ let mut read = 0;
+
+ // shut up clippy no reasonable person would expect to be able to have a poll longer than a
+ // month
+ #[allow(clippy::cast_possible_truncation)]
+ let wait_time_millis = timeout.as_millis() as u32;
+ let result = unsafe { WaitForSingleObject(handle, wait_time_millis) };
+
+ // The function timed out
+ if result != 0 {
+ return Err(io::ErrorKind::TimedOut.into());
}
- /// Disables raw mode, which enables line buffering, input echoing, and output canonicalization
- ///
- /// # Errors
- ///
- /// If there is no stdin,
- /// stdin is not a tty,
- /// or it fails to change terminal settings
- pub fn disable_raw_mode() -> io::Result<()> {
- let handle = get_stdin_handle()?;
- let mut mode = 0;
- get_console_mode(handle, &mut mode)?;
- mode |= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
- set_console_mode(handle, mode)?;
- Ok(())
- }
+ let result = unsafe { ReadConsoleInputW(handle, &mut record, 1, &mut read) };
- /// Enables ANSI support on Windows terminals
- ///
- /// ANSI is on by default on *nix machines but still exists on them for simpler usage
- ///
- /// # Errors
- ///
- /// Never on *nix
- ///
- /// On Windows, if There is no stdout,
- /// if stdout isn't a TTY, or
- /// if it cannot change terminal properties
- pub fn enable_ansi() -> io::Result<()> {
- let handle = get_stdout_handle()?;
- let mut mode = 0;
- get_console_mode(handle, &mut mode)?;
- mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
- set_console_mode(handle, mode)?;
- Ok(())
+ if result == 0 {
+ Err(io::Error::last_os_error())?;
}
-
- /// Gets the size of the terminal
- ///
- /// Returns in (width, height) format
- ///
- /// # Errors
- ///
- /// If there is no stdout,
- /// if stdout isn't a TTY, or
- /// if it fails to retrieve the terminal size
- pub fn get_terminal_size() -> io::Result<(u16, u16)> {
- let handle = get_stdout_handle()?;
- let mut csbi = ConsoleScreenBufferInfo::default();
- if unsafe { GetConsoleScreenBufferInfo(handle, &mut csbi) != 0 } {
- let width = csbi.x;
- let height = csbi.y;
- return Ok((width, height));
+ match record.event_type {
+ 0x10 => {
+ // Focus Event
+ Err(io::ErrorKind::InvalidData.into())
+ }
+ 0x1 => {
+ // Key Event
+ let key_event: KeyEventRecord = unsafe { record.event.key };
+ if key_event.key_down == 0 {
+ // return Ok(Event::Key(KeyEvent::Null));
+ // I don't quite know why but this seems to happen a lot, until I investigate
+ // more this will have to do
+ return Err(io::ErrorKind::Other.into());
+ }
+ Ok(parse_key_event(&key_event))
+ }
+ _ => {
+ //TODO Make this better
+ Err(io::ErrorKind::InvalidData.into())
}
- Err(io::Error::last_os_error())
}
}
-pub mod input {
- use super::get_stdin_handle;
- use crate::input::{Event, Key, KeyModifiers, KeyType};
+fn parse_key_event(event: &KeyEventRecord) -> Event {
+ let ctrl = event.control_key_state & (0x0008 | 0x0004) != 0; // LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED
+ let shift = event.control_key_state & 0x0010 != 0; // SHIFT_PRESSED
- use std::os::windows::raw::HANDLE;
- use std::{io, mem, time::Duration};
-
- #[repr(C)]
- #[derive(Copy, Clone)]
- struct InputRecord {
- event_type: u16,
- event: EventRecord,
- }
-
- #[repr(C)]
- #[derive(Copy, Clone)]
- union EventRecord {
- key: KeyEventRecord,
- focus: FocusEventRecord,
- }
-
- #[repr(C)]
- #[derive(Copy, Clone)]
- struct KeyEventRecord {
- key_down: i32,
- repeat_count: u16,
- virtual_key_code: u16,
- virtual_scan_code: u16,
- u_char: CharUnion,
- control_key_state: u32,
- }
-
- #[repr(C)]
- #[derive(Copy, Clone)]
- struct FocusEventRecord {
- set_focus: i32,
- }
-
- #[repr(C)]
- #[derive(Copy, Clone)]
- union CharUnion {
- unicode_char: u16,
- ascii_char: u8,
- }
-
- unsafe extern "system" {
- fn ReadConsoleInputW(
- console_input: HANDLE,
- buffer: *mut InputRecord,
- length: u32,
- number_of_events_read: *mut u32,
- ) -> i32;
- fn WaitForSingleObject(handle: HANDLE, wait_time_ms: u32) -> u32;
- }
-
- /// Attempts to fetch input from stdin
- ///
- /// # Errors
- /// If the timeout has expired or
- /// there was an error getting the data
- pub fn poll_input(timeout: Duration) -> io::Result {
- let handle = get_stdin_handle()?;
- let mut record: InputRecord = unsafe { mem::zeroed() };
- let mut read = 0;
-
- // shut up clippy no reasonable person would expect to be able to have a poll longer than a
- // month
+ match event.virtual_key_code {
+ 0x08 => Event::Key(Key::Backspace, KeyType::Press, KeyModifiers::none()),
+ 0x09 => {
+ if shift {
+ Event::Key(Key::Tab, KeyType::Press, KeyModifiers::none().shift())
+ } else {
+ Event::Key(Key::Tab, KeyType::Press, KeyModifiers::none())
+ }
+ }
+ 0x0D => Event::Key(Key::Char('\n'), KeyType::Press, KeyModifiers::none()),
+ 0x1B => Event::Key(Key::Escape, KeyType::Press, KeyModifiers::none()),
+ 0x21 => Event::Key(Key::PageUp, KeyType::Press, KeyModifiers::none()),
+ 0x22 => Event::Key(Key::PageDown, KeyType::Press, KeyModifiers::none()),
+ 0x23 => Event::Key(Key::End, KeyType::Press, KeyModifiers::none()),
+ 0x24 => Event::Key(Key::Home, KeyType::Press, KeyModifiers::none()),
+ 0x25 => Event::Key(Key::Left, KeyType::Press, KeyModifiers::none()),
+ 0x26 => Event::Key(Key::Up, KeyType::Press, KeyModifiers::none()),
+ 0x27 => Event::Key(Key::Right, KeyType::Press, KeyModifiers::none()),
+ 0x28 => Event::Key(Key::Down, KeyType::Press, KeyModifiers::none()),
+ 0x2D => Event::Key(Key::Insert, KeyType::Press, KeyModifiers::none()),
+ 0x2E => Event::Key(Key::Delete, KeyType::Press, KeyModifiers::none()),
+ // I don't think anybody is going to try to press F256 clippy
#[allow(clippy::cast_possible_truncation)]
- let wait_time_millis = timeout.as_millis() as u32;
- let result = unsafe { WaitForSingleObject(handle, wait_time_millis) };
-
- // The function timed out
- if result != 0 {
- return Err(io::ErrorKind::TimedOut.into());
- }
-
- let result = unsafe { ReadConsoleInputW(handle, &mut record, 1, &mut read) };
-
- if result == 0 {
- Err(io::Error::last_os_error())?;
- }
- match record.event_type {
- 0x10 => {
- // Focus Event
- Err(io::ErrorKind::InvalidData.into())
- }
- 0x1 => {
- // Key Event
- let key_event: KeyEventRecord = unsafe { record.event.key };
- if key_event.key_down == 0 {
- // return Ok(Event::Key(KeyEvent::Null));
- // I don't quite know why but this seems to happen a lot, until I investigate
- // more this will have to do
- return Err(io::ErrorKind::Other.into());
- }
- Ok(parse_key_event(&key_event))
- }
- _ => {
- //TODO Make this better
- Err(io::ErrorKind::InvalidData.into())
- }
- }
- }
-
- fn parse_key_event(event: &KeyEventRecord) -> Event {
- let ctrl = event.control_key_state & (0x0008 | 0x0004) != 0; // LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED
- let shift = event.control_key_state & 0x0010 != 0; // SHIFT_PRESSED
-
- match event.virtual_key_code {
- 0x08 => Event::Key(Key::Backspace, KeyType::Press, KeyModifiers::none()),
- 0x09 => {
- if shift {
- Event::Key(Key::Tab, KeyType::Press, KeyModifiers::none().shift())
- } else {
- Event::Key(Key::Tab, KeyType::Press, KeyModifiers::none())
- }
- }
- 0x0D => Event::Key(Key::Char('\n'), KeyType::Press, KeyModifiers::none()),
- 0x1B => Event::Key(Key::Escape, KeyType::Press, KeyModifiers::none()),
- 0x21 => Event::Key(Key::PageUp, KeyType::Press, KeyModifiers::none()),
- 0x22 => Event::Key(Key::PageDown, KeyType::Press, KeyModifiers::none()),
- 0x23 => Event::Key(Key::End, KeyType::Press, KeyModifiers::none()),
- 0x24 => Event::Key(Key::Home, KeyType::Press, KeyModifiers::none()),
- 0x25 => Event::Key(Key::Left, KeyType::Press, KeyModifiers::none()),
- 0x26 => Event::Key(Key::Up, KeyType::Press, KeyModifiers::none()),
- 0x27 => Event::Key(Key::Right, KeyType::Press, KeyModifiers::none()),
- 0x28 => Event::Key(Key::Down, KeyType::Press, KeyModifiers::none()),
- 0x2D => Event::Key(Key::Insert, KeyType::Press, KeyModifiers::none()),
- 0x2E => Event::Key(Key::Delete, KeyType::Press, KeyModifiers::none()),
- // I don't think anybody is going to try to press F256 clippy
- #[allow(clippy::cast_possible_truncation)]
- 0x70..=0x87 => Event::Key(
- Key::F((event.virtual_key_code - 0x6F) as u8),
- KeyType::Press,
- KeyModifiers::none(),
- ), // F1-F24
- _ => {
- let c =
- char::from_u32(u32::from(unsafe { event.u_char.unicode_char })).unwrap_or(' ');
- if ctrl && c.is_ascii_alphabetic() {
- Event::Key(Key::Char(c), KeyType::Press, KeyModifiers::none().ctrl())
- } else {
- Event::Key(Key::Char(c), KeyType::Press, KeyModifiers::none())
- }
+ 0x70..=0x87 => Event::Key(
+ Key::F((event.virtual_key_code - 0x6F) as u8),
+ KeyType::Press,
+ KeyModifiers::none(),
+ ), // F1-F24
+ _ => {
+ let num = u32::from(unsafe { event.u_char.unicode_char });
+ let c = char::from_u32(num).unwrap_or(' ');
+ if ctrl && c.is_ascii_alphabetic() {
+ Event::Key(Key::Char(c), KeyType::Press, KeyModifiers::none().ctrl())
+ } else {
+ Event::Key(Key::Char(c), KeyType::Press, KeyModifiers::none())
}
}
}