From 834bb49b5e85821b7929836d7178a6becfd60156 Mon Sep 17 00:00:00 2001
From: Michael Sippel <micha@fragmental.art>
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 <micha@fragmental.art>"]
+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::<i16>::new(0, 0);
+    let mut cur_style = TerminalStyle::default();
+
+    let mut input = std::io::stdin();
+
+    loop {
+        match bincode::deserialize_from::<_, (Point2<i16>, Option<TerminalAtom>)>(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 <micha@fragmental.art>"]
+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<i16>,
+    style: TerminalStyle,
+
+    out: File,
+}
+
+impl PerfAtom {
+    fn write_atom(&mut self, pos: Point2<i16>, atom: Option<TerminalAtom>) {
+        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;
+            }
+        }
+    }
+}