aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock25
-rw-r--r--Cargo.toml7
-rw-r--r--src/main.rs100
4 files changed, 133 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..ce9277f
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,25 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "libc"
+version = "0.2.182"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
+
+[[package]]
+name = "pish"
+version = "0.1.0"
+dependencies = [
+ "termios",
+]
+
+[[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..a9a594f
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "pish"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+termios = "0.3"
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..7f372c1
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,100 @@
+use std::io::{self, Read, Write, IsTerminal};
+use std::os::unix::io::AsRawFd;
+use termios::*;
+
+struct ScopedRawMode {
+ fd: i32,
+ settings: Termios,
+}
+
+impl Drop for ScopedRawMode {
+ fn drop(&mut self) {
+ self.disable();
+ }
+}
+
+impl ScopedRawMode {
+ fn on_fd(fd: i32) -> Self {
+ let mut termios = Termios::from_fd(fd).unwrap();
+ let settings = termios.clone();
+ cfmakeraw(&mut termios);
+ tcsetattr(fd, TCSANOW, &termios).unwrap();
+ Self { fd, settings }
+ }
+
+ fn disable(&self) {
+ tcsetattr(self.fd, TCSANOW, &self.settings).unwrap();
+ }
+}
+
+macro_rules! print {
+ ($($x:tt)*) => {{
+ write!(io::stdout(), $($x)*).unwrap();
+ io::stdout().flush().unwrap();
+ }}
+}
+
+fn main() {
+ let stdin = io::stdin();
+ let stdout = io::stdout();
+
+ if !stdin.is_terminal() {
+ println!("need to run in a tty");
+ return;
+ }
+
+ let fd = stdin.as_raw_fd();
+ let _scoped_raw = ScopedRawMode::on_fd(fd); // needs to be a var, gets dropped on scope exit,
+ // even if something panics
+
+ let mut stdin = stdin.lock();
+ let mut stdout = stdout.lock();
+
+ let mut buffer = [0u8; 1];
+
+ loop {
+ let Ok(_) = stdin.read_exact(&mut buffer) else {
+ break;
+ };
+
+ match buffer[0] {
+ // EOF
+ 4 => {
+ break;
+ }
+
+ // Enter
+ b'\r' => {
+ println!("\r");
+ }
+
+ // Backspace (127 on most systems)
+ 127 => {
+ write!(stdout, "\x08 \x08").unwrap();
+ stdout.flush().unwrap();
+ }
+
+ // Escape sequence
+ 27 => {
+ let mut seq = [0u8; 2];
+ stdin.read_exact(&mut seq).unwrap();
+
+ if seq[0] == b'[' {
+ match seq[1] {
+ b'A' => print!("Up"),
+ b'B' => print!("Down"),
+ b'C' => print!("Right"),
+ b'D' => print!("Left"),
+ _ => {}
+ }
+ }
+ }
+
+ // Normal character
+ x => {
+ stdout.write(&[x]).unwrap();
+ stdout.flush().unwrap();
+ }
+ }
+ }
+}