use nix::fcntl::{fcntl, F_GETFL, F_SETFL, OFlag}; use nix::pty::{ForkptyResult, forkpty, PtyMaster}; use nix::unistd::{execv, read, write}; use minifb::{Key, KeyRepeat, Window, WindowOptions}; use std::vec; use std::collections::HashMap; use std::ffi::CString; use std::fmt::Write; use ttf_parser::{Face, OutlineBuilder, Rect}; use vte::{Params, Parser, Perform}; use zeno::{Mask, Transform}; // params const SHELL: &str = "/home/andromeda/.nix-profile/bin/sh"; const FONT: &str = "/home/andromeda/.nix-profile/share/fonts/truetype/Miracode.ttf"; struct Buffer { buffer: Vec, width: usize, height: usize, } impl Buffer { fn new(val: T, width: usize, height: usize) -> Self { Buffer { buffer: vec![val; width * height], width: width, height: height, } } fn get(&self, col: usize, row: usize) -> T { self.buffer.get(col + row * self.width).unwrap().clone() } fn set(&mut self, col: usize, row: usize, val: T) { self.buffer[col + row * self.width] = val; } } struct Cursor { col: usize, row: usize, } struct Model { screenbuffer: Buffer, buffer: Buffer>, cell: Rect, cursor: Cursor, } impl Model { // `cell` is the bbox of a cell // width and height are measured in cells fn new(cell: Rect, width: usize, height: usize, scale: f32) -> Self { Model { screenbuffer: Buffer::new( 0, (cell.width() as f32 * width as f32 * scale) as usize, (cell.height() as f32 * height as f32 * scale) as usize ), buffer: Buffer::new( None, width, height ), cell: cell, cursor: Cursor { col: 0, row: 0, }, } } // returns scale fn scale(&self) -> f32 { self.screenbuffer.height as f32 / (self.cell.height() as f32 * self.buffer.height as f32) } } impl Perform for Model { // draw a character to the screen and update states fn print(&mut self, c: char) { self.buffer.set(self.cursor.col, self.cursor.row, Some(c)); self.cursor.col += 1; println!("[print] {:?}", c); } // execute a C0 or C1 control function fn execute(&mut self, byte: u8) { match byte { 0x0D => self.cursor.col = 0, 0x0A => self.cursor.row = self.cursor.row + 1, _ => (), } println!("[execute] {:02x}", byte); } // invoked when a final character arrives in first part of device control string fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { println!( "[hook] params={:?}, intermediates={:?}, ignore={:?}, char={:?}", params, intermediates, ignore, c ); } // pass bytes as part of a device control string to the handle chosen by hook // C0 controls are also passed to this handler fn put(&mut self, byte: u8) { println!("[put] {:02x}", byte); } // called when a device control string is terminated fn unhook(&mut self) { println!("[unhook]"); } // dispatch an operating system command fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { println!("[osc_dispatch] params={:?} bell_terminated={}", params, bell_terminated); } // a final character has arrived for a csi sequence fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { println!( "[csi_dispatch] params={:#?}, intermediates={:?}, ignore={:?}, char={:?}", params, intermediates, ignore, c ); } // the final character of an escape sequence has arrived fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) { println!( "[esc_dispatch] intermediates={:?}, ignore={:?}, byte={:02x}", intermediates, ignore, byte ); } } // yoinked from tty_parser docs struct Builder(String); impl OutlineBuilder for Builder { fn move_to(&mut self, x: f32, y: f32) { write!(&mut self.0, "M {} {} ", x, y).unwrap(); } fn line_to(&mut self, x: f32, y: f32) { write!(&mut self.0, "L {} {} ", x, y).unwrap(); } fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { write!(&mut self.0, "Q {} {} {} {} ", x1, y1, x, y).unwrap(); } fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { write!(&mut self.0, "C {} {} {} {} {} {} ", x1, y1, x2, y2, x, y).unwrap(); } fn close(&mut self) { write!(&mut self.0, "Z ").unwrap(); } } fn main() { // initialize the font let font_data = std::fs::read(FONT).unwrap(); let face = Face::parse(&font_data, 0).unwrap(); let font = generate_font(&face); let mut model = Model::new( Rect { x_min: 0, y_min: 0, x_max: face.height(), y_max: face.height(), }, 80, 24, 0.008, ); // initialize window and its buffer let mut window = Window::new( "rust-term", model.screenbuffer.width, model.screenbuffer.height, WindowOptions::default(), ).unwrap(); window.set_target_fps(60); let pty = spawn_pty(&SHELL).unwrap(); fcntl(&pty, F_SETFL(OFlag::from_bits_truncate(fcntl(&pty, F_GETFL).unwrap()) | OFlag::O_NONBLOCK)).unwrap(); let mut statemachine = Parser::new(); let mut buf = [0u8; 2048]; while window.is_open() && !window.is_key_down(Key::Escape) { // mask to render into let mut mask = vec![0u8; model.screenbuffer.width * model.screenbuffer.height]; // render each char into mask for row in 0..model.buffer.height { for col in 0..model.buffer.width { if let Some(c) = model.buffer.get(col, row) { Mask::new(font.get(&c).map_or("", |v| v)) // get svg .transform(Some( Transform::scale(model.scale(), -model.scale()) // scale it .then_translate( col as f32 * model.cell.width() as f32 * model.scale(), // shift right by the cell width * the scale (1 + row) as f32 * model.scale() * model.cell.height() as f32, ) )) .size(model.screenbuffer.width as u32, model.screenbuffer.height as u32) .render_into(&mut mask, None); } }; }; // render in white/grayscale to screen for (p, m) in model.screenbuffer.buffer.iter_mut().zip(mask.iter()) { let m0 = *m as u32; *p = m0 << 16 | m0 << 8 | m0; } // update screen with buffer window.update_with_buffer(&model.screenbuffer.buffer, model.screenbuffer.width, model.screenbuffer.height).unwrap(); // other stuff match read(&pty, &mut buf) { Ok(0) => (), Ok(n) => statemachine.advance(&mut model, &buf[..n]), Err(_e) => (), }; let keys = window.get_keys_pressed(KeyRepeat::No); if !keys.is_empty() { let bytes: Vec = keys.iter() // TODO apply modifiers .map(|key| key_to_u8(*key, false, false)) .collect(); write(&pty, bytes.as_slice()); }; } } // forks a new pty and returns file descriptor of the master fn spawn_pty(shell: &str) -> Option { // SAFETY safe unless os out of PTYs; incredibly unlikely match unsafe {forkpty(None, None)} { Ok(fork_pty_res) => match fork_pty_res { ForkptyResult::Parent {child:_, master} => { // SAFETY `master` is a valid PtyMaster return Some(unsafe{PtyMaster::from_owned_fd(master)}); } ForkptyResult::Child =>{ let _ = execv::(&CString::new(shell).unwrap(), &[]); return None; } }, Err(_e) => return None, }; } // WARNING not functional; missing some keys and also modifiers fn key_to_u8(key: Key, shift: bool, ctrl: bool) -> u8 { let base = match key { Key::Key0 => b'0', Key::Key1 => b'1', Key::Key2 => b'2', Key::Key3 => b'3', Key::Key4 => b'4', Key::Key5 => b'5', Key::Key6 => b'6', Key::Key7 => b'7', Key::Key8 => b'8', Key::Key9 => b'9', Key::A => b'a', Key::B => b'b', Key::C => b'c', Key::D => b'd', Key::E => b'e', Key::F => b'f', Key::G => b'g', Key::H => b'h', Key::I => b'i', Key::J => b'j', Key::K => b'k', Key::L => b'l', Key::M => b'm', Key::N => b'n', Key::O => b'o', Key::P => b'p', Key::Q => b'q', Key::R => b'r', Key::S => b's', Key::T => b't', Key::U => b'u', Key::V => b'v', Key::W => b'w', Key::X => b'x', Key::Y => b'y', Key::Z => b'z', Key::F1 => 0, Key::F2 => 0, Key::F3 => 0, Key::F4 => 0, Key::F5 => 0, Key::F6 => 0, Key::F7 => 0, Key::F8 => 0, Key::F9 => 0, Key::F10 => 0, Key::F11 => 0, Key::F12 => 0, Key::F13 => 0, Key::F14 => 0, Key::F15 => 0, Key::Down => 0, Key::Left => 0, Key::Right => 0, Key::Up => 0, Key::Apostrophe => b'\'', Key::Backquote => b'`', Key::Backslash => b'\\', Key::Comma => b',', Key::Equal => b'=', Key::LeftBracket => b'[', Key::Minus => b'-', Key::Period => b'.', Key::RightBracket => b']', Key::Semicolon => b';', Key::Slash => b'/', Key::Backspace => 8, Key::Delete => 127, Key::End => 0, Key::Enter => b'\n', Key::Escape => 27, Key::Home => 0, Key::Insert => 0, Key::Menu => 0, Key::PageDown => 0, Key::PageUp => 0, Key::Pause => 0, Key::Space => b' ', Key::Tab => b'\t', Key::NumLock => 0, Key::CapsLock => 0, Key::ScrollLock => 0, Key::LeftShift => 0, Key::RightShift => 0, Key::LeftCtrl => 0, Key::RightCtrl => 0, Key::NumPad0 => 0, Key::NumPad1 => 0, Key::NumPad2 => 0, Key::NumPad3 => 0, Key::NumPad4 => 0, Key::NumPad5 => 0, Key::NumPad6 => 0, Key::NumPad7 => 0, Key::NumPad8 => 0, Key::NumPad9 => 0, Key::NumPadDot => 0, Key::NumPadSlash => 0, Key::NumPadAsterisk => 0, Key::NumPadMinus => 0, Key::NumPadPlus => 0, Key::NumPadEnter => 0, Key::LeftAlt => 0, Key::RightAlt => 0, Key::LeftSuper => 0, Key::RightSuper => 0, Key::Unknown => 0, Key::Count => 0, }; let base_shift = if shift { match base { b'0' => b')', b'1' => b'!', b'2' => b'@', b'3' => b'#', b'4' => b'$', b'5' => b'%', b'6' => b'^', b'7' => b'&', b'8' => b'*', b'9' => b'(', b'a'..=b'z' => base - 32, b'\'' => b'"', b'`' => b'~', b'\\' => b'|', b',' => b'<', b'=' => b'+', b'[' => b'{', b'-' => b'_', b'.' => b'>', b']' => b'}', b';' => b':', b'/' => b'?', _ => base, } } else { base }; let base_shift_ctrl = if ctrl { base_shift & 0x1F } else { base_shift }; return base_shift_ctrl; } // creats a mapping from a `char` to its svg spec for a selection of characters fn generate_font(face: &Face) -> HashMap { let chars = vec![ '\'', '`', '\\', ',', '=', '[', '-', '.', ']', ';', '/', ')', '!', '@', '#', '$', '%', '^', '&', '*', '(', '"', '~', '|', '<', '+', '{', '_', '>', '}', ':', '?' ] .into_iter() .chain('a'..='z') .chain('A'..='Z') .chain('0'..='9'); let mut hm = HashMap::new(); for c in chars { let mut builder = Builder(String::new()); face.outline_glyph(face.glyph_index(c).unwrap(), &mut builder).unwrap(); hm.entry(c).insert_entry(builder.0); } hm }