#![feature( super_let, debug_closure_helpers, const_trait_impl, generic_assert, deadline_api, deref_patterns, generic_const_exprs, guard_patterns, impl_trait_in_bindings, if_let_guard, import_trait_associated_functions )] #![allow(incomplete_features)] use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd}; use std::process::{Command, exit}; use std::sync::{LazyLock, mpsc}; use std::thread::sleep; use std::time::{Duration, Instant}; pub mod colors; mod keyboard; use anyhow::Result; use dsb::F; use fimg::Image; use minifb::{InputCallback, Key, WindowOptions}; use nix::pty::{ForkptyResult, forkpty}; use nix::sys::wait::waitpid; use nix::unistd::Pid; use swash::{FontRef, Instance}; use term::*; mod term; use libc::{ F_GETFL, F_SETFL, O_NONBLOCK, TIOCSWINSZ, fcntl, ioctl, winsize, }; fn spawn(shell: &str) -> Result<(OwnedFd, Pid)> { let x = unsafe { forkpty(None, None)? }; match x { ForkptyResult::Child => { _ = Command::new(shell) .env("TERM", "pattypan") .spawn()? .wait(); exit(0); } ForkptyResult::Parent { child, master } => { unsafe { assert_eq!( fcntl( master.as_raw_fd(), F_SETFL, fcntl(master.as_raw_fd(), F_GETFL, 0) | O_NONBLOCK, ), 0 ) }; Ok((master, child)) } } } fn read(fd: BorrowedFd) -> Option> { let mut x = [0; 1 << 16]; let n = nix::unistd::read(fd, &mut x).ok()?; Some(x[..n].to_vec()) } fn write(fd: BorrowedFd, x: &[u8]) -> Result<()> { let n = nix::unistd::write(fd, x)?; anyhow::ensure!(n == x.len()); Ok(()) } struct KeyPress(mpsc::Sender<(Key, bool)>); impl InputCallback for KeyPress { fn add_char(&mut self, _: u32) {} fn set_key_state(&mut self, key: Key, state: bool) { self.0.send((key, state)).unwrap(); } } fn main() -> Result<()> { let init = Instant::now(); let mut w = minifb::Window::new( "pattypan", 5, 5, WindowOptions { borderless: true, title: false, resize: true, ..Default::default() }, )?; // input let (ktx, krx) = mpsc::channel(); w.set_input_callback(Box::new(KeyPress(ktx))); w.update(); let (pty, pid) = spawn("bash")?; let pty1 = pty.try_clone()?; let pty2 = pty.try_clone()?; std::thread::spawn(move || { let mut b = keyboard::Board::new(); while let Ok((k, s)) = krx.recv() { let x = b.rx(k, s); write(pty1.as_fd(), &x).unwrap(); } }); // output let (ttx, trx) = mpsc::channel(); std::thread::spawn(move || { loop { if let Some(x) = read(pty2.as_fd()) { ttx.send(x).unwrap(); } sleep(Duration::from_millis(10)); } }); std::thread::spawn(move || { loop { if let nix::sys::wait::WaitStatus::Exited(..) = waitpid(pid, None).unwrap() { exit(0) } } }); while w.get_size().0 < 20 || w.get_size().0 > 5000 { sleep(Duration::from_millis(1)); w.update(); } println!("{:?}", init.elapsed()); let ppem = 20.0; let mut fonts = dsb::Fonts::new( F::FontRef(*FONT, &[(2003265652, 550.0)]), F::instance(*FONT, *BFONT), F::FontRef(*IFONT, &[(2003265652, 550.0)]), F::instance(*IFONT, *BIFONT), ); // let (fw, fh) = dsb::dims(&FONT, ppem); let (cols, rows) = dsb::fit(&FONT, ppem, 20.0, w.get_size()); println!("{}x{}", rows, cols); let mut t = Terminal::new((cols as _, rows as _), false); t.alternate.as_mut().unwrap().view_o = None; let mut i = Image::build(w.get_size().0 as _, w.get_size().1 as _) .fill(colors::BACKGROUND); unsafe { let x = winsize { ws_row: rows as _, ws_col: cols as _, ws_xpixel: w.get_size().0 as _, ws_ypixel: w.get_size().1 as _, }; assert!(ioctl(pty.as_raw_fd(), TIOCSWINSZ, &raw const x) == 0); }; // let cj = // swash::FontRef::from_index(&include_bytes!("../cjk.ttc")[..], 0) // .unwrap(); // let mut f = File::create("x").unwrap(); loop { while w.get_size().0 < 20 || w.get_size().0 > 5000 || w.get_size().1 < 20 || w.get_size().1 > 5000 { sleep(Duration::from_millis(10)); w.update(); } let (cols, rows) = dsb::fit(&FONT, ppem, 20.0, (w.get_size().0, w.get_size().1)); let [cols, rows] = [cols, rows].map(|x| x as u16 - 1); if (cols + 1, rows + 1) != t.cells.size { println!("{}x{}", rows, cols); unsafe { let x = winsize { ws_row: rows, ws_col: cols, ws_xpixel: w.get_size().0 as _, ws_ypixel: w.get_size().1 as _, }; assert!( ioctl(pty.as_raw_fd(), TIOCSWINSZ, &raw const x) == 0 ); }; t.resize((cols, rows)); i = Image::build(w.get_size().0 as u32, w.get_size().1 as u32) .fill(colors::BACKGROUND); } t.scroll(w.get_scroll_wheel().unwrap_or_default().1); let now = Instant::now(); while let Ok(x) = trx.recv_deadline(now + Duration::from_millis(16)) { // f.write_all(&x)?; for char in x { t.rx(char, pty.as_fd()); } } unsafe { let z = ( cols as usize + 1, (t.cells.cells().len() / cols as usize + 1) .min(t.cells.r() as _), ); dsb::render( &t.cells.cells[t.view_o.unwrap_or(t.cells.row) * t.cells.c() as usize..], z, ppem, &mut fonts, 20.0, true, i.as_mut(), (0, 0), ) }; let x = Image::, 1>::from(i.as_ref()); w.update_with_buffer(x.buffer(), w.get_size().0, w.get_size().1)?; } } pub static FONT: LazyLock> = LazyLock::new(|| { FontRef::from_index( std::fs::read("/home/os/CascadiaCodeNF.ttf").unwrap().leak(), 0, ) .unwrap() }); pub static IFONT: LazyLock> = LazyLock::new(|| { FontRef::from_index( std::fs::read("/home/os/CascadiaCodeNFItalic.ttf").unwrap().leak(), // &include_bytes!("/home/os/CascadiaCodeNFItalic.ttf")[..], 0, ) .unwrap() }); pub static BIFONT: LazyLock> = LazyLock::new(|| { IFONT.instances().find_by_name("Bold Italic").unwrap() }); pub static BFONT: LazyLock> = LazyLock::new(|| FONT.instances().find_by_name("Bold").unwrap()); #[test] fn tpaxrse() { println!("-------------------"); use ctlfun::TerminalInputParser; let mut x = TerminalInputParser::new(); for c in "\x1b[6n".as_bytes() { use ctlfun::TerminalInput::*; match x.parse_byte(*c) { Char(x) => { print!("{x}"); } Control(x) => println!("{x:?}"), _ => (), } } panic!(); }