From 834bb49b5e85821b7929836d7178a6becfd60156 Mon Sep 17 00:00:00 2001 From: Michael Sippel Date: Thu, 18 Jan 2024 19:05:25 +0100 Subject: [PATCH] add tty/pty server as separate crates --- display-server-tty/Cargo.toml | 12 ++ display-server-tty/src/main.rs | 67 +++++++ pty-server/Cargo.toml | 13 ++ pty-server/src/main.rs | 331 +++++++++++++++++++++++++++++++++ 4 files changed, 423 insertions(+) create mode 100644 display-server-tty/Cargo.toml create mode 100644 display-server-tty/src/main.rs create mode 100644 pty-server/Cargo.toml create mode 100644 pty-server/src/main.rs diff --git a/display-server-tty/Cargo.toml b/display-server-tty/Cargo.toml new file mode 100644 index 0000000..4d933da --- /dev/null +++ b/display-server-tty/Cargo.toml @@ -0,0 +1,12 @@ +[package] +authors = ["Michael Sippel "] +name = "display_server" +version = "0.1.0" +edition = "2018" + +[dependencies] +nested = { path = "../../nested" } +termion = "1.5.5" +cgmath = { version = "0.18.0", features = ["serde"] } +serde = { version = "1.0", features = ["serde_derive"] } +bincode = "1.3.3" diff --git a/display-server-tty/src/main.rs b/display-server-tty/src/main.rs new file mode 100644 index 0000000..a3f52fc --- /dev/null +++ b/display-server-tty/src/main.rs @@ -0,0 +1,67 @@ +use { + cgmath::{Point2, Vector2}, + nested::terminal::{TerminalAtom, TerminalStyle}, + std::io::{stdout, Read, Write}, + termion::raw::IntoRawMode, +}; + +fn main() { + let mut out = stdout().into_raw_mode().unwrap(); + write!( + out, + "{}{}{}", + termion::cursor::Hide, + termion::cursor::Goto(1, 1), + termion::style::Reset + ) + .unwrap(); + + let mut cur_pos = Point2::::new(0, 0); + let mut cur_style = TerminalStyle::default(); + + let mut input = std::io::stdin(); + + loop { + match bincode::deserialize_from::<_, (Point2, Option)>(input.by_ref()) { + Ok((pos, atom)) => { + if pos != cur_pos { + write!( + out, + "{}", + termion::cursor::Goto(pos.x as u16 + 1, pos.y as u16 + 1) + ) + .unwrap(); + } + + if let Some(atom) = atom { + if cur_style != atom.style { + cur_style = atom.style; + write!(out, "{}", atom.style).expect(""); + } + + write!(out, "{}", atom.c.unwrap_or(' ')).expect(""); + } else { + write!(out, "{} ", termion::style::Reset).expect(""); + cur_style = TerminalStyle::default(); + } + + cur_pos = pos + Vector2::new(1, 0); + + out.flush().unwrap(); + } + Err(err) => { + match *err { + bincode::ErrorKind::Io(_io_error) => break, + err => { + eprintln!("deserialization error\n{:?}", err); + } + } + break; + } + } + } + + // restore conventional terminal settings + write!(out, "{}", termion::cursor::Show).unwrap(); + out.flush().unwrap(); +} diff --git a/pty-server/Cargo.toml b/pty-server/Cargo.toml new file mode 100644 index 0000000..2b1ca1d --- /dev/null +++ b/pty-server/Cargo.toml @@ -0,0 +1,13 @@ +[package] +authors = ["Michael Sippel "] +name = "ansi_parser" +version = "0.1.0" +edition = "2018" + +[dependencies] +nested = { path = "../../nested" } +cgmath = { version = "0.18.0", features = ["serde"] } +serde = { version = "1.0", features = ["serde_derive"] } +bincode = "1.3.3" +vte = "0.10.1" +ansi_colours = "1.0" diff --git a/pty-server/src/main.rs b/pty-server/src/main.rs new file mode 100644 index 0000000..0e4d532 --- /dev/null +++ b/pty-server/src/main.rs @@ -0,0 +1,331 @@ +#![feature(iter_advance_by)] + +use { + cgmath::Point2, + nested::terminal::{TerminalAtom, TerminalStyle}, + std::{ + fs::File, + io::{stdin, Read, Write}, + os::unix::io::FromRawFd, + }, + vte::{Params, Parser, Perform}, +}; + +struct ColorPalett { + black: (u8, u8, u8), + red: (u8, u8, u8), + green: (u8, u8, u8), + yellow: (u8, u8, u8), + blue: (u8, u8, u8), + magenta: (u8, u8, u8), + cyan: (u8, u8, u8), + white: (u8, u8, u8), +} + +struct PerfAtom { + colors: ColorPalett, + term_width: i16, + + cursor: Point2, + style: TerminalStyle, + + out: File, +} + +impl PerfAtom { + fn write_atom(&mut self, pos: Point2, atom: Option) { + self.out + .write(&bincode::serialize(&(pos, atom)).unwrap()) + .expect(""); + } +} + +impl Perform for PerfAtom { + fn print(&mut self, c: char) { + //eprintln!("[print] {:?}", c); + self.write_atom(self.cursor, Some(TerminalAtom::new(c, self.style))); + + self.cursor.x += 1; + if self.cursor.x > self.term_width { + self.cursor.x = 0; + self.cursor.y += 1; + } + } + + fn execute(&mut self, byte: u8) { + //eprintln!("[execute] {:02x}", byte); + match byte { + b'\n' => { + self.cursor.x = 0; + self.cursor.y += 1; + } + _ => {} + } + } + + fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { + eprintln!( + "[hook] params={:?}, intermediates={:?}, ignore={:?}, char={:?}", + params, intermediates, ignore, c + ); + } + + fn put(&mut self, byte: u8) { + eprintln!("[put] {:02x}", byte); + } + + fn unhook(&mut self) { + eprintln!("[unhook]"); + } + + fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { + eprintln!( + "[osc_dispatch] params={:?} bell_terminated={}", + params, bell_terminated + ); + } + + fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { + eprintln!( + "[csi_dispatch] params={:#?}, intermediates={:?}, ignore={:?}, char={:?}", + params, intermediates, ignore, c + ); + + let mut piter = params.into_iter(); + + match c { + // Set SGR + 'm' => { + while let Some(n) = piter.next() { + match n[0] { + 0 => self.style = TerminalStyle::default(), + 1 => self.style = self.style.add(TerminalStyle::bold(true)), + 3 => self.style = self.style.add(TerminalStyle::italic(true)), + 4 => self.style = self.style.add(TerminalStyle::underline(true)), + + 30 => { + self.style = self.style.add(TerminalStyle::fg_color(self.colors.black)) + } + 40 => { + self.style = self.style.add(TerminalStyle::bg_color(self.colors.black)) + } + 31 => self.style = self.style.add(TerminalStyle::fg_color(self.colors.red)), + 41 => self.style = self.style.add(TerminalStyle::bg_color(self.colors.red)), + 32 => { + self.style = self.style.add(TerminalStyle::fg_color(self.colors.green)) + } + 42 => { + self.style = self.style.add(TerminalStyle::bg_color(self.colors.green)) + } + 33 => { + self.style = self.style.add(TerminalStyle::fg_color(self.colors.yellow)) + } + 43 => { + self.style = self.style.add(TerminalStyle::bg_color(self.colors.yellow)) + } + 34 => { + self.style = self.style.add(TerminalStyle::fg_color(self.colors.blue)) + } + 44 => { + self.style = self.style.add(TerminalStyle::bg_color(self.colors.blue)) + } + 35 => { + self.style = + self.style.add(TerminalStyle::fg_color(self.colors.magenta)) + } + 45 => { + self.style = + self.style.add(TerminalStyle::bg_color(self.colors.magenta)) + } + 36 => { + self.style = self.style.add(TerminalStyle::fg_color(self.colors.cyan)) + } + 46 => { + self.style = self.style.add(TerminalStyle::bg_color(self.colors.cyan)) + } + 37 => { + self.style = self.style.add(TerminalStyle::fg_color(self.colors.white)) + } + 47 => { + self.style = self.style.add(TerminalStyle::bg_color(self.colors.white)) + } + + 38 => { + let x = piter.next().unwrap(); + match x[0] { + 2 => { + let r = piter.next().unwrap(); + let g = piter.next().unwrap(); + let b = piter.next().unwrap(); + self.style = self.style.add(TerminalStyle::fg_color(( + r[0] as u8, + g[0] as u8, + b[30] as u8, + ))) + } + 5 => { + let v = piter.next().unwrap(); + self.style = self.style.add(TerminalStyle::fg_color( + ansi_colours::rgb_from_ansi256(v[0] as u8), + )) + } + _ => {} + } + } + + 48 => { + let x = piter.next().unwrap(); + match x[0] { + 2 => { + let r = piter.next().unwrap(); + let g = piter.next().unwrap(); + let b = piter.next().unwrap(); + self.style = self.style.add(TerminalStyle::bg_color(( + r[0] as u8, + g[0] as u8, + b[30] as u8, + ))) + } + 5 => { + let v = piter.next().unwrap(); + self.style = self.style.add(TerminalStyle::bg_color( + ansi_colours::rgb_from_ansi256(v[0] as u8), + )) + } + _ => {} + } + } + + _ => {} + } + } + } + + 'H' => { + if let Some(y) = piter.next() { + self.cursor.y = y[0] as i16 - 1 + }; + if let Some(x) = piter.next() { + self.cursor.x = x[0] as i16 - 1 + }; + + eprintln!("cursor at {:?}", self.cursor); + } + + 'A' => { + self.cursor.y -= piter.next().unwrap()[0] as i16; + } + 'B' => { + self.cursor.y += piter.next().unwrap()[0] as i16; + } + 'C' => { + self.cursor.x += piter.next().unwrap()[0] as i16; + } + 'D' => { + self.cursor.x -= piter.next().unwrap()[0] as i16; + } + 'E' => { + self.cursor.x = 0; + self.cursor.y += piter.next().unwrap()[0] as i16; + } + + 'J' => { + let x = piter.next().unwrap_or(&[0 as u16; 1]); + match x[0] { + 0 => {} + 1 => {} + 2 => { + for y in 0..100 { + for x in 0..self.term_width { + self.write_atom(Point2::new(x, y), None); + } + } + } + + // invalid + _ => {} + } + } + + 'K' => { + let x = piter.next().unwrap(); + match x[0] { + // clear cursor until end + 0 => { + for x in self.cursor.x..self.term_width { + self.write_atom(Point2::new(x, self.cursor.y), None); + } + } + + // clear start until cursor + 1 => { + for x in 0..self.cursor.x { + self.write_atom(Point2::new(x, self.cursor.y), None); + } + } + + // clear entire line + 2 => { + for x in 0..self.term_width { + self.write_atom(Point2::new(x, self.cursor.y), None); + } + } + + // invalid + _ => {} + } + } + + _ => {} + } + } + + fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) { + eprintln!( + "[esc_dispatch] intermediates={:?}, ignore={:?}, byte={:02x}", + intermediates, ignore, byte + ); + } +} + +fn main() { + let input = stdin(); + let mut handle = input.lock(); + + let mut statemachine = Parser::new(); + let mut performer = PerfAtom { + cursor: Point2::new(0, 0), + style: TerminalStyle::default(), + term_width: 200, + out: unsafe { File::from_raw_fd(1) }, + + colors: ColorPalett { + black: (1, 1, 1), + red: (222, 56, 43), + green: (0, 64, 0), + yellow: (255, 199, 6), + blue: (0, 111, 184), + magenta: (118, 38, 113), + cyan: (44, 181, 233), + white: (204, 204, 204), + }, + }; + + let mut buf = [0; 2048]; + + loop { + match handle.read(&mut buf) { + Ok(0) => break, + Ok(n) => { + for byte in &buf[..n] { + statemachine.advance(&mut performer, *byte); + performer.out.flush().unwrap(); + } + } + Err(err) => { + println!("err: {}", err); + break; + } + } + } +}