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::ffi::CString; use vte::{Params, Parser, Perform}; use zeno::{Join, Mask, Stroke}; // window stuff const WIDTH: usize = 640; const HEIGHT: usize = 360; // yoinked from vte docs struct Performer; impl Perform for Performer { fn print(&mut self, c: char) { println!("[print] {:?}", c); } fn execute(&mut self, byte: u8) { println!("[execute] {:02x}", byte); } fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { println!( "[hook] params={:?}, intermediates={:?}, ignore={:?}, char={:?}", params, intermediates, ignore, c ); } fn put(&mut self, byte: u8) { println!("[put] {:02x}", byte); } fn unhook(&mut self) { println!("[unhook]"); } fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { println!("[osc_dispatch] params={:?} bell_terminated={}", params, bell_terminated); } fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { println!( "[csi_dispatch] params={:#?}, intermediates={:?}, ignore={:?}, char={:?}", params, intermediates, ignore, c ); } fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) { println!( "[esc_dispatch] intermediates={:?}, ignore={:?}, byte={:02x}", intermediates, ignore, byte ); } } fn main() { let mut buffer: Vec = vec![0; WIDTH * HEIGHT]; let mut window = Window::new( "rust-term", WIDTH, HEIGHT, WindowOptions::default(), ).unwrap(); window.set_target_fps(60); let pty = spawn_pty("/home/andromeda/.nix-profile/bin/sh").unwrap(); println!("{:?}", &pty); let pty_flags = fcntl(&pty, F_GETFL).unwrap(); fcntl(&pty, F_SETFL(OFlag::from_bits_truncate(pty_flags) | OFlag::O_NONBLOCK)).unwrap(); let mut statemachine = Parser::new(); let mut performer = Performer; let mut buf = [0u8; 2048]; while window.is_open() && !window.is_key_down(Key::Escape) { let mask = Mask::new("M 8,56 32,8 56,56 Z") .size(WIDTH.try_into().unwrap(), HEIGHT.try_into().unwrap()) .style(Stroke::new(8.0).join(Join::Round)) .render() .0; // windowing stuff for (p, a) in buffer.iter_mut().zip(mask.iter()) { let a0 = *a as u32; *p = a0 << 24 | 0x00FF0000; *p = if a0 > 0 { a0 << 16 | a0 << 8 | a0 } else { 0x00000000 }; } let keys = window.get_keys_pressed(KeyRepeat::No); window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap(); // other stuff match read(&pty, &mut buf) { Ok(0) => (), Ok(n) => statemachine.advance(&mut performer, &buf[..n]), Err(_e) => (), }; 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; }