mirror of
https://github.com/Xyverle/neutuino.git
synced 2026-06-26 21:13:13 -04:00
first commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
7
Cargo.lock
generated
Normal file
7
Cargo.lock
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "neutrino"
|
||||||
|
version = "0.1.0"
|
||||||
6
Cargo.toml
Normal file
6
Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "neutrino"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
21
src/ansi.rs
Normal file
21
src/ansi.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
pub struct CursorHome {}
|
||||||
|
|
||||||
|
impl fmt::Display for CursorHome {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Cursor {
|
||||||
|
Move(u16, u16),
|
||||||
|
Up(u16),
|
||||||
|
Down(u16),
|
||||||
|
Right(u16),
|
||||||
|
Left(u16),
|
||||||
|
Column(u16),
|
||||||
|
Home,
|
||||||
|
Save,
|
||||||
|
Restore,
|
||||||
|
}
|
||||||
191
src/input.rs
Normal file
191
src/input.rs
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
use std::mem;
|
||||||
|
use std::os::raw::c_int;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Key {
|
||||||
|
String(String),
|
||||||
|
Char(char),
|
||||||
|
Enter,
|
||||||
|
Escape,
|
||||||
|
Backspace,
|
||||||
|
Tab,
|
||||||
|
ShiftTab,
|
||||||
|
Delete,
|
||||||
|
Home,
|
||||||
|
End,
|
||||||
|
PageUp,
|
||||||
|
PageDown,
|
||||||
|
ArrowUp,
|
||||||
|
ArrowDown,
|
||||||
|
ArrowLeft,
|
||||||
|
ArrowRight,
|
||||||
|
Ctrl(char),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub mod platform {
|
||||||
|
use super::*;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::os::fd::AsRawFd;
|
||||||
|
|
||||||
|
unsafe extern "C" {
|
||||||
|
fn tcgetattr(fd: c_int, termios_p: *mut Termios) -> c_int;
|
||||||
|
fn tcsetattr(fd: c_int, optional_actions: c_int, termios_p: *const Termios) -> c_int;
|
||||||
|
fn fcntl(fd: c_int, cmd: c_int, arg: c_int) -> c_int;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TCSAFLUSH: c_int = 2;
|
||||||
|
const ICANON: c_int = 0o0002;
|
||||||
|
const ECHO: c_int = 0o0010;
|
||||||
|
const O_NONBLOCK: c_int = 0x800;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Termios {
|
||||||
|
c_iflag: u32,
|
||||||
|
c_oflag: u32,
|
||||||
|
c_cflag: u32,
|
||||||
|
c_lflag: u32,
|
||||||
|
c_cc: [u8; 32],
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RawInput {
|
||||||
|
stdin: File,
|
||||||
|
original_termios: Termios,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawInput {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let stdin = File::open("/dev/tty").unwrap();
|
||||||
|
let fd = stdin.as_raw_fd();
|
||||||
|
let mut termios: Termios = unsafe { mem::zeroed() };
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
tcgetattr(fd, &mut termios);
|
||||||
|
let mut new_termios = termios.clone();
|
||||||
|
new_termios.c_lflag &= !(ICANON | ECHO) as u32;
|
||||||
|
tcsetattr(fd, TCSAFLUSH, &new_termios);
|
||||||
|
fcntl(fd, 4, O_NONBLOCK);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
stdin,
|
||||||
|
original_termios: termios,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_key(&mut self) -> [u8; 6] {
|
||||||
|
let mut buf = [0; 6];
|
||||||
|
_ = self.stdin.read(&mut buf);
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for RawInput {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { tcsetattr(self.stdin.as_raw_fd(), TCSAFLUSH, &self.original_termios) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub mod platform {
|
||||||
|
use super::*;
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
|
extern "system" {
|
||||||
|
fn GetStdHandle(nStdHandle: c_int) -> isize;
|
||||||
|
fn SetConsoleMode(hConsoleHandle: isize, dwMode: u32) -> i32;
|
||||||
|
fn GetConsoleMode(hConsoleHandle: isize, lpMode: *mut u32) -> i32;
|
||||||
|
fn ReadConsoleInputW(hConsoleInput: isize, lpBuffer: *mut InputRecord, nLength: u32, lpNumberOfEventsRead: *mut u32) -> i32;
|
||||||
|
fn PeekConsoleInputW(hConsoleInput: isize, lpBuffer: *mut InputRecord, nLength: u32, lpNumberOfEventsRead: *mut u32) -> i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
const STD_INPUT_HANDLE: c_int = -10;
|
||||||
|
const ENABLE_PROCESSED_INPUT: u32 = 0x0001;
|
||||||
|
const ENABLE_LINE_INPUT: u32 = 0x0002;
|
||||||
|
const ENABLE_ECHO_INPUT: u32 = 0x0004;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct InputRecord {
|
||||||
|
event_type: c_ushort,
|
||||||
|
event: KeyEventRecord,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct KeyEventRecord {
|
||||||
|
key_down: i32,
|
||||||
|
repeat_count: c_ushort,
|
||||||
|
virtual_key_code: c_ushort,
|
||||||
|
virtual_scan_code: c_ushort,
|
||||||
|
unicode_char: u16,
|
||||||
|
control_key_state: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RawInput {
|
||||||
|
handle: isize,
|
||||||
|
original_mode: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawInput {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
unsafe {
|
||||||
|
let handle = GetStdHandle(STD_INPUT_HANDLE);
|
||||||
|
let mut mode = 0;
|
||||||
|
GetConsoleMode(handle, &mut mode);
|
||||||
|
SetConsoleMode(handle, mode & !(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
handle,
|
||||||
|
original_mode: mode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_key(&mut self) -> Option<Key> {
|
||||||
|
let mut records: [InputRecord; 1] = unsafe { mem::zeroed() };
|
||||||
|
let mut num_read = 0;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
PeekConsoleInputW(self.handle, records.as_mut_ptr(), 1, &mut num_read);
|
||||||
|
if num_read == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadConsoleInputW(self.handle, records.as_mut_ptr(), 1, &mut num_read);
|
||||||
|
|
||||||
|
if records[0].event.key_down == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match records[0].event.virtual_key_code {
|
||||||
|
0x1B => Some(Key::Escape),
|
||||||
|
0x0D => Some(Key::Enter),
|
||||||
|
0x08 => Some(Key::Backspace),
|
||||||
|
0x09 => Some(Key::Tab),
|
||||||
|
0x2F => Some(Key::ShiftTab),
|
||||||
|
0x2E => Some(Key::Delete),
|
||||||
|
0x24 => Some(Key::Home),
|
||||||
|
0x23 => Some(Key::End),
|
||||||
|
0x21 => Some(Key::PageUp),
|
||||||
|
0x22 => Some(Key::PageDown),
|
||||||
|
0x26 => Some(Key::ArrowUp),
|
||||||
|
0x28 => Some(Key::ArrowDown),
|
||||||
|
0x25 => Some(Key::ArrowLeft),
|
||||||
|
0x27 => Some(Key::ArrowRight),
|
||||||
|
c if c >= 1 && c <= 26 => Some(Key::Ctrl((b'a' + (c - 1) as u8) as char)),
|
||||||
|
c if records[0].event.unicode_char != 0 => Some(Key::Char(records[0].event.unicode_char as u8 as char)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for RawInput {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { SetConsoleMode(self.handle, self.original_mode) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
78
src/input/async.rs
Normal file
78
src/input/async.rs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
|
||||||
|
/// An asynchronous reader.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
/// This acts as any other stream, with the exception that reading from it won't block. Instead,
|
||||||
|
|
||||||
|
/// the buffer will only be partially updated based on how much the internal buffer holds.
|
||||||
|
pub struct AsyncReader {
|
||||||
|
recv: mpsc::Receiver<io::Result<u8>>,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for AsyncReader {
|
||||||
|
|
||||||
|
/// Read from the byte stream.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
/// This will never block, but try to drain the event queue until empty. If the total number of
|
||||||
|
|
||||||
|
/// bytes written is lower than the buffer's length, the event queue is empty or that the event
|
||||||
|
|
||||||
|
/// stream halted.
|
||||||
|
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
|
||||||
|
let mut total = 0;
|
||||||
|
|
||||||
|
|
||||||
|
loop {
|
||||||
|
|
||||||
|
if total >= buf.len() {
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
match self.recv.try_recv() {
|
||||||
|
|
||||||
|
Ok(Ok(b)) => {
|
||||||
|
|
||||||
|
buf[total] = b;
|
||||||
|
|
||||||
|
total += 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Err(e)) => return Err(e),
|
||||||
|
|
||||||
|
Err(_) => break,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Ok(total)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncReader {
|
||||||
|
pub fn<R: Read> new(reader: R) -> Self {
|
||||||
|
let (send, recv) = mpsc::channel();
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
for i in reader.bytes() {
|
||||||
|
if send.send(i).is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self { recv: recv }
|
||||||
|
}
|
||||||
4
src/lib.rs
Normal file
4
src/lib.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#![warn(clippy::all, clippy::pedantic)]
|
||||||
|
|
||||||
|
pub mod os;
|
||||||
|
pub mod ansi;
|
||||||
41
src/os/mod.rs
Normal file
41
src/os/mod.rs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#![allow(unused_imports)]
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
mod unix;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
mod windows;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
use unix as os;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
use windows as os;
|
||||||
|
|
||||||
|
/// Checks if stdout is a terminal
|
||||||
|
pub use os::istty;
|
||||||
|
|
||||||
|
/// Enables ANSI support on Windows terminals
|
||||||
|
///
|
||||||
|
/// ANSI is on by default on *nix machines but still exists for ease of use
|
||||||
|
pub use os::enable_ansi;
|
||||||
|
|
||||||
|
/// Gets the size of the terminal
|
||||||
|
pub use os::get_terminal_size;
|
||||||
|
|
||||||
|
|
||||||
|
/// Struct representing a raw terminal
|
||||||
|
///
|
||||||
|
/// This was done due to weirdness in the termios API (you have to store the original state of the
|
||||||
|
/// terminal to restore it)
|
||||||
|
pub use os::RawTerminal;
|
||||||
|
|
||||||
|
/// Enables raw mode
|
||||||
|
///
|
||||||
|
/// Disables input echoing, line feeding, etc.
|
||||||
|
pub use os::enable_raw_mode;
|
||||||
|
|
||||||
|
/// Disables raw mode
|
||||||
|
///
|
||||||
|
/// Enables input echoing, line feeding, etc.
|
||||||
|
pub use os::disable_raw_mode;
|
||||||
123
src/os/unix.rs
Normal file
123
src/os/unix.rs
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
use std::ffi::{c_int, c_uint, c_ulong, c_ushort};
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
unsafe extern "C" {
|
||||||
|
fn isatty(fd: c_int) -> c_int;
|
||||||
|
fn ioctl(fd: c_int, request: c_ulong, argp: *mut u8) -> c_int;
|
||||||
|
fn tcgetattr(fd: c_int, termios_p: *mut Termios) -> c_int;
|
||||||
|
fn tcsetattr(fd: c_int, optional_actions: c_int, termios: *mut Termios) -> c_int;
|
||||||
|
fn cfmakeraw(termios: *mut Termios);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
const TIOCGWINSZ: c_ulong = 0x5413;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
||||||
|
const TIOCGWINSZ: c_ulong = 0x40087468;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
const NCCS: usize = 32;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
||||||
|
const NCCS: usize = 20;
|
||||||
|
|
||||||
|
const STDIN_FILENO: c_int = 0;
|
||||||
|
const STDOUT_FILENO: c_int = 1;
|
||||||
|
|
||||||
|
const ECHO: c_uint = 0;
|
||||||
|
const ICANON: c_uint = 0;
|
||||||
|
const ISIG: c_uint = 0;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct Winsize {
|
||||||
|
row: c_ushort,
|
||||||
|
col: c_ushort,
|
||||||
|
xpixel: c_ushort,
|
||||||
|
ypixel: c_ushort,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Termios {
|
||||||
|
iflag: c_uint,
|
||||||
|
oflag: c_uint,
|
||||||
|
cflag: c_uint,
|
||||||
|
lflag: c_uint,
|
||||||
|
cc: [u8; NCCS],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct RawTerminal {
|
||||||
|
orig_termios: Termios,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawTerminal {
|
||||||
|
pub fn new() -> io::Result<RawTerminal> {
|
||||||
|
let mut orig_termios = unsafe { std::mem::zeroed::<Termios>() };
|
||||||
|
get_attributes(STDIN_FILENO, &mut orig_termios)?;
|
||||||
|
|
||||||
|
let mut current_termios = orig_termios.clone();
|
||||||
|
unsafe { cfmakeraw(&raw mut current_termios) };
|
||||||
|
set_attributes(STDIN_FILENO, &mut current_termios)?;
|
||||||
|
|
||||||
|
Ok(RawTerminal { orig_termios })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for RawTerminal {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
_ = set_attributes(STDIN_FILENO, &mut self.orig_termios);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn istty() -> bool {
|
||||||
|
unsafe { isatty(1) != 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_terminal_size() -> io::Result<(c_ushort, c_ushort)> {
|
||||||
|
let mut winsize = unsafe { std::mem::zeroed::<Winsize>() };
|
||||||
|
let ioctl_result = unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ, &raw mut winsize as *mut u8) };
|
||||||
|
|
||||||
|
if ioctl_result == 0 {
|
||||||
|
Ok((winsize.col, winsize.row))
|
||||||
|
} else {
|
||||||
|
Err(io::Error::last_os_error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable_raw_mode() -> io::Result<()> {
|
||||||
|
let mut termios = unsafe { std::mem::zeroed::<Termios>() };
|
||||||
|
get_attributes(STDIN_FILENO, &mut termios)?;
|
||||||
|
termios.lflag &= !(ECHO | ISIG | ICANON);
|
||||||
|
set_attributes(STDIN_FILENO, &mut termios)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disable_raw_mode() -> io::Result<()> {
|
||||||
|
let mut termios = unsafe { std::mem::zeroed::<Termios>() };
|
||||||
|
get_attributes(STDIN_FILENO, &mut termios)?;
|
||||||
|
termios.lflag |= ECHO | ISIG | ICANON;
|
||||||
|
set_attributes(STDIN_FILENO, &mut termios)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_attributes(fd: c_int, termios: &mut Termios) -> io::Result<()> {
|
||||||
|
if unsafe { tcgetattr(fd, &raw mut *termios) } != 0 {
|
||||||
|
return Err(io::Error::last_os_error())
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_attributes(fd: c_int, termios: &mut Termios) -> io::Result<()> {
|
||||||
|
if unsafe { tcsetattr(fd, 0, termios as *mut _) } != 0 {
|
||||||
|
return Err(io::Error::last_os_error())
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
98
src/os/unix.rs.new
Normal file
98
src/os/unix.rs.new
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
use std::ffi::{c_int, c_uint, c_ulong, c_ushort};
|
||||||
|
use std::io;
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
unsafe extern "C" {
|
||||||
|
fn isatty(fd: c_int) -> c_int;
|
||||||
|
fn ioctl(fd: c_int, request: c_ulong, argp: *mut u8) -> c_int;
|
||||||
|
fn tcgetattr(fd: c_int, termios_p: *mut Termios) -> c_int;
|
||||||
|
fn tcsetattr(fd: c_int, optional_actions: c_int, termios: *mut Termios) -> c_int;
|
||||||
|
fn cfmakeraw(termios: *mut Termios);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
const TIOCGWINSZ: c_ulong = 0x5413;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
||||||
|
const TIOCGWINSZ: c_ulong = 0x40087468;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
const NCCS: usize = 32;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
||||||
|
const NCCS: usize = 20;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct Winsize {
|
||||||
|
row: c_ushort,
|
||||||
|
col: c_ushort,
|
||||||
|
xpixel: c_ushort,
|
||||||
|
ypixel: c_ushort,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Termios {
|
||||||
|
iflag: c_uint,
|
||||||
|
oflag: c_uint,
|
||||||
|
cflag: c_uint,
|
||||||
|
lflag: c_uint,
|
||||||
|
cc: [u8; NCCS],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct RawTerminal {
|
||||||
|
input: Termios,
|
||||||
|
output: Termios,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawTerminal {
|
||||||
|
pub fn new() -> io::Result<RawTerminal> {
|
||||||
|
let mut term: RawTerminal = unsafe { mem::zeroed() };
|
||||||
|
if unsafe { tcgetattr(0, &raw mut term.input) } != 0 {
|
||||||
|
return Err(io::Error::last_os_error())
|
||||||
|
}
|
||||||
|
if unsafe { tcgetattr(1, &raw mut term.output) } != 0 {
|
||||||
|
return Err(io::Error::last_os_error())
|
||||||
|
}
|
||||||
|
let orig_term = term.clone();
|
||||||
|
unsafe { cfmakeraw(&raw mut term.input) };
|
||||||
|
unsafe { cfmakeraw(&raw mut term.output) };
|
||||||
|
if unsafe { tcsetattr(0, 0, &raw mut term.input) } != 0 {
|
||||||
|
return Err(io::Error::last_os_error())
|
||||||
|
}
|
||||||
|
if unsafe { tcsetattr(1, 0, &raw mut term.output) } != 0 {
|
||||||
|
return Err(io::Error::last_os_error())
|
||||||
|
}
|
||||||
|
Ok(orig_term)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for RawTerminal {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { tcsetattr(0, 0, &raw mut self.input) };
|
||||||
|
unsafe { tcsetattr(1, 0, &raw mut self.input) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn istty() -> bool {
|
||||||
|
unsafe { isatty(1) != 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_terminal_size() -> io::Result<(c_ushort, c_ushort)> {
|
||||||
|
let mut winsize = unsafe { std::mem::zeroed::<Winsize>() };
|
||||||
|
let ioctl_result = unsafe { ioctl(1, TIOCGWINSZ, &raw mut winsize as *mut u8) };
|
||||||
|
|
||||||
|
if ioctl_result == 0 {
|
||||||
|
Ok((winsize.col, winsize.row))
|
||||||
|
} else {
|
||||||
|
Err(io::Error::last_os_error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(())
|
||||||
|
}
|
||||||
135
src/os/windows.rs
Normal file
135
src/os/windows.rs
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use std::ffi::{c_int, c_uint, c_ulong, c_ushort};
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
const STD_INPUT_HANDLE: u32 = 0xFFFFFFF6;
|
||||||
|
const STD_OUTPUT_HANDLE: u32 = 0xFFFFFFF5;
|
||||||
|
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 4;
|
||||||
|
const ENABLE_ECHO_INPUT: u32 = 4;
|
||||||
|
const ENABLE_LINE_INPUT: u32 = 2;
|
||||||
|
const ENABLE_PROCESSED_INPUT: u32 = 1;
|
||||||
|
const INVALID_HANDLE_VALUE: usize = usize::MAX-1;
|
||||||
|
|
||||||
|
unsafe extern "system" {
|
||||||
|
fn GetStdHandle(nStdHandle: u32) -> usize;
|
||||||
|
fn GetConsoleMode(hConsoleHandle: usize, dwMode: *mut u32) -> u32;
|
||||||
|
fn SetConsoleMode(hConsoleHandle: usize, dwMode: *mut u32) -> u32;
|
||||||
|
fn GetConsoleScreenBufferInfo(
|
||||||
|
hConsoleOutput: usize,
|
||||||
|
lpConsoleScreenBufferInfo: *mut ConsoleScreenBufferInfo,
|
||||||
|
) -> u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Default)]
|
||||||
|
struct ConsoleScreenBufferInfo {
|
||||||
|
dwSizeX: u16,
|
||||||
|
dwSizeY: u16,
|
||||||
|
dwCursorPositionX: u16,
|
||||||
|
dwCursorPositionY: u16,
|
||||||
|
wAttributes: u16,
|
||||||
|
srWindowLeft: u16,
|
||||||
|
srWindowTop: u16,
|
||||||
|
srWindowRight: u16,
|
||||||
|
srWindowBottom: u16,
|
||||||
|
dwMaximumWindowSizeX: u16,
|
||||||
|
dwMaximumWindowSizeY: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RawTerminal { }
|
||||||
|
|
||||||
|
impl RawTerminal {
|
||||||
|
pub fn new() -> io::Result<RawTerminal> {
|
||||||
|
let handle = get_std_handle(STD_INPUT_HANDLE)?;
|
||||||
|
let mut dwMode = 0;
|
||||||
|
get_console_mode(handle, &raw mut dwMode)?;
|
||||||
|
dwMode &= !(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
|
||||||
|
set_console_mode(handle, &raw mut dwMode)?;
|
||||||
|
Ok(RawTerminal { })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for RawTerminal {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let handle = get_std_handle(STD_INPUT_HANDLE).unwrap();
|
||||||
|
let mut dwMode = 0;
|
||||||
|
_ = get_console_mode(handle, &raw mut dwMode);
|
||||||
|
dwMode |= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
|
||||||
|
_ = set_console_mode(handle, &raw mut dwMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn istty() -> bool {
|
||||||
|
let handle = get_std_handle(STD_OUTPUT_HANDLE);
|
||||||
|
match handle {
|
||||||
|
Ok(handle) => {
|
||||||
|
let mut dwMode = 0;
|
||||||
|
return unsafe { GetConsoleMode(handle, &mut dwMode) != 0 };
|
||||||
|
},
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_terminal_size() -> io::Result<(u16, u16)> {
|
||||||
|
let handle = get_std_handle(STD_OUTPUT_HANDLE)?;
|
||||||
|
let mut csbi = ConsoleScreenBufferInfo::default();
|
||||||
|
if unsafe { GetConsoleScreenBufferInfo(handle, &mut csbi) != 0 } {
|
||||||
|
let width = csbi.dwSizeX as u16;
|
||||||
|
let height = csbi.dwSizeY as u16;
|
||||||
|
return Ok((width, height));
|
||||||
|
}
|
||||||
|
Err(io::Error::last_os_error())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable_ansi() -> io::Result<()> {
|
||||||
|
let handle = get_std_handle(STD_OUTPUT_HANDLE)?;
|
||||||
|
let mut dwMode = 0;
|
||||||
|
get_console_mode(handle, &raw mut dwMode)?;
|
||||||
|
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||||
|
set_console_mode(handle, &raw mut dwMode)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable_raw_mode() -> io::Result<()> {
|
||||||
|
let handle = get_std_handle(STD_INPUT_HANDLE)?;
|
||||||
|
let mut dwMode = 0;
|
||||||
|
get_console_mode(handle, &raw mut dwMode)?;
|
||||||
|
dwMode &= !(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
|
||||||
|
set_console_mode(handle, &raw mut dwMode)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disable_raw_mode() -> io::Result<()> {
|
||||||
|
let handle = get_std_handle(STD_INPUT_HANDLE)?;
|
||||||
|
let mut dwMode = 0;
|
||||||
|
get_console_mode(handle, &raw mut dwMode)?;
|
||||||
|
dwMode |= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
|
||||||
|
set_console_mode(handle, &raw mut dwMode)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_std_handle(handle: u32) -> io::Result<usize> {
|
||||||
|
let handle = unsafe { GetStdHandle(handle) };
|
||||||
|
if handle != INVALID_HANDLE_VALUE {
|
||||||
|
return Ok(handle);
|
||||||
|
} else {
|
||||||
|
return Err(io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_console_mode(handle: usize, dwMode: *mut u32) -> io::Result<()> {
|
||||||
|
if unsafe { SetConsoleMode(handle, dwMode) == 0 } {
|
||||||
|
Err(io::Error::last_os_error())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_console_mode(handle: usize, dwMode: *mut u32) -> io::Result<()> {
|
||||||
|
if unsafe { GetConsoleMode(handle, dwMode) == 0 } {
|
||||||
|
Err(io::Error::last_os_error())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user