commit 99759854e2d841829cee48b0882aac5ea740eb44 Author: Ondřej Hruška Date: Sat Dec 10 20:25:57 2022 +0100 initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e983aa7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +.idea/ + + diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..6493fc2 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,67 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +dependencies = [ + "bitflags", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bless" +version = "0.1.0" +dependencies = [ + "anes", + "crossbeam-channel", + "termios", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "libc" +version = "0.2.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" + +[[package]] +name = "termios" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" +dependencies = [ + "libc", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f6a9565 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "bless" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anes = { version = "0.1.6", features = ["parser"] } +crossbeam-channel = "0.5.6" + +# For TTY interface +termios = "0.3.3" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..cb85db2 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,117 @@ +use std::io::{Read, stdin, stdout, Write}; +use std::os::unix::io::RawFd; +use anes::parser::{KeyCode, Sequence}; +use termios::{tcgetattr, tcsetattr, Termios}; + +const STDIN_FILENO : RawFd = 0; + +struct StdinNonblockingGuard { + original_termios : Termios, +} + +impl StdinNonblockingGuard { + pub fn new() -> std::io::Result { + let mut old_tio = unsafe { std::mem::zeroed::() }; + + /* get the terminal settings for stdin */ + tcgetattr(STDIN_FILENO, &mut old_tio)?; + + /* we want to keep the old setting to restore them a the end */ + let mut new_tio = old_tio; + + /* disable canonical mode (buffered i/o) and local echo */ + new_tio.c_lflag &= !termios::ICANON & !termios::ECHO; + + /* set the new settings immediately */ + tcsetattr(STDIN_FILENO, termios::TCSANOW, &new_tio)?; + + Ok(Self { + original_termios: old_tio, + }) + } +} + +impl Drop for StdinNonblockingGuard { + fn drop(&mut self) { + /* restore the former settings */ + let _ = tcsetattr(STDIN_FILENO, termios::TCSANOW, &self.original_termios); + } +} + +enum InputEvent { + Key(anes::parser::Sequence), + EndOfStream, +} + + +struct TerminalInterface { +} + +impl TerminalInterface { + pub fn new() -> (Self, crossbeam_channel::Receiver) { + let (sender, receiver) = crossbeam_channel::bounded(0); + std::thread::spawn(move || { + let stdin_guard = StdinNonblockingGuard::new().unwrap(); + let mut sin = stdin().lock(); + let mut parser = anes::parser::Parser::default(); + + 'input: loop { + let mut buf = [0u8; 64]; + // TODO add a way to kill the thread from outside + let numch = match sin.read(&mut buf[..]) { + Err(_) | Ok(0) => { + let _ = sender.send(InputEvent::EndOfStream); + break 'input; + }, + Ok(numch) => numch + }; + + let received_slice = &buf[0..numch]; + parser.advance(received_slice, numch == buf.len()); + + while let Some(item) = parser.next() { + if sender.send(InputEvent::Key(item)).is_err() { + break 'input; + } + } + } + + drop(stdin_guard); + }); + + (Self {}, receiver) + } +} + +impl Write for TerminalInterface { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + stdout().write(buf) + } + + fn flush(&mut self) -> std::io::Result<()> { + stdout().flush() + } +} + + +fn main() -> std::io::Result<()> { + let (mut term, receiver) = TerminalInterface::new(); + + 'lp: loop { + match receiver.recv() { + Ok(InputEvent::EndOfStream) | Err(_) => { + break 'lp; + } + Ok(InputEvent::Key(k)) => { + write!(term, "{:?}\n", k)?; + + if let Sequence::Key(KeyCode::Esc, _) = k { + write!(term, "ESC, exit\n")?; + break 'lp; + } + } + } + } + + Ok(()) +}