mirror of
https://github.com/Xyverle/neutuino.git
synced 2026-06-26 22:23:14 -04:00
clean up & reorganize repo + fix clippy warnings
This commit is contained in:
@@ -181,6 +181,9 @@ pub fn exit_alternate_screen() {
|
||||
}
|
||||
|
||||
pub fn set_window_title(title: &str) {
|
||||
assert!(title.len() <= 255, "Title length longer than maximum of 255");
|
||||
assert!(
|
||||
title.len() <= 255,
|
||||
"Title length longer than maximum of 255"
|
||||
);
|
||||
println!("\x1b]0;{title}\x1b\x5c");
|
||||
}
|
||||
|
||||
211
src/input.rs
211
src/input.rs
@@ -1,191 +1,54 @@
|
||||
use std::mem;
|
||||
use std::os::raw::c_int;
|
||||
use std::io::{self, Read};
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
|
||||
#[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),
|
||||
/// 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>>,
|
||||
}
|
||||
|
||||
#[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);
|
||||
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;
|
||||
}
|
||||
|
||||
Self {
|
||||
stdin,
|
||||
original_termios: termios,
|
||||
match self.recv.try_recv() {
|
||||
Ok(Ok(b)) => {
|
||||
buf[total] = b;
|
||||
total += 1;
|
||||
}
|
||||
Ok(Err(e)) => return Err(e),
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
|
||||
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) };
|
||||
}
|
||||
Ok(total)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub mod platform {
|
||||
use super::*;
|
||||
use std::ptr;
|
||||
impl AsyncReader {
|
||||
pub fn new<R: Read + Send + 'static>(reader: R) -> Self {
|
||||
let (send, recv) = mpsc::channel();
|
||||
|
||||
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,
|
||||
thread::spawn(move || {
|
||||
for i in reader.bytes() {
|
||||
if send.send(i).is_err() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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) };
|
||||
}
|
||||
Self { recv }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
|
||||
/// 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 }
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
#![warn(clippy::all, clippy::pedantic)]
|
||||
|
||||
pub mod ansi;
|
||||
pub mod input;
|
||||
pub mod os;
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
#![allow(unused_imports)]
|
||||
|
||||
#[cfg(unix)]
|
||||
mod unix;
|
||||
|
||||
#[cfg(windows)]
|
||||
mod windows;
|
||||
#[path = "unix.rs"] mod unix;
|
||||
|
||||
#[cfg(unix)]
|
||||
use unix as os;
|
||||
|
||||
#[cfg(windows)]
|
||||
#[path = "windows.rs"] mod windows;
|
||||
|
||||
#[cfg(windows)]
|
||||
use windows as os;
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
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(())
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
#![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 STD_INPUT_HANDLE: u32 = 0xFFFF_FFF6;
|
||||
const STD_OUTPUT_HANDLE: u32 = 0xFFFF_FFF5;
|
||||
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 4;
|
||||
const ENABLE_ECHO_INPUT: u32 = 4;
|
||||
const ENABLE_LINE_INPUT: u32 = 2;
|
||||
@@ -37,12 +36,12 @@ struct ConsoleScreenBufferInfo {
|
||||
dwMaximumWindowSizeY: u16,
|
||||
}
|
||||
|
||||
pub fn is_terminal() -> bool {
|
||||
#[must_use] pub fn is_terminal() -> bool {
|
||||
let handle = get_std_handle(STD_OUTPUT_HANDLE);
|
||||
match handle {
|
||||
Ok(handle) => {
|
||||
let mut dwMode = 0;
|
||||
return unsafe { GetConsoleMode(handle, &mut dwMode) != 0 };
|
||||
unsafe { GetConsoleMode(handle, &mut dwMode) != 0 }
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
@@ -52,8 +51,8 @@ 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;
|
||||
let width = csbi.dwSizeX;
|
||||
let height = csbi.dwSizeY;
|
||||
return Ok((width, height));
|
||||
}
|
||||
Err(io::Error::last_os_error())
|
||||
@@ -88,10 +87,10 @@ pub fn disable_raw_mode() -> io::Result<()> {
|
||||
|
||||
fn get_std_handle(handle: u32) -> io::Result<usize> {
|
||||
let handle = unsafe { GetStdHandle(handle) };
|
||||
if handle != INVALID_HANDLE_VALUE {
|
||||
return Ok(handle);
|
||||
if handle == INVALID_HANDLE_VALUE {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
return Err(io::Error::last_os_error());
|
||||
Ok(handle)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user