diff options
| author | andromeda <andromeda@lenovo> | 2026-01-27 09:33:50 +0100 |
|---|---|---|
| committer | andromeda <andromeda@lenovo> | 2026-01-27 09:33:50 +0100 |
| commit | c6d10069ab98052ec4d1cf2cdafca7888457264d (patch) | |
| tree | 9244f85432bd956564eb2b0ed3324368eef3b7c6 /src/main.rs | |
| parent | d2ed0e974bb28d4588405153ce443f807e484e76 (diff) | |
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 266 |
1 files changed, 64 insertions, 202 deletions
diff --git a/src/main.rs b/src/main.rs index 8a6a33b..70e0884 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,173 +7,19 @@ use minifb::{Key, KeyRepeat, Window, WindowOptions}; use std::collections::HashMap; use std::ffi::CString; -use std::fmt::Write; use std::vec; -use ttf_parser::{Face, OutlineBuilder, Rect}; +use ttf_parser::Face; -use vte::{Params, Parser, Perform}; +use vte::Parser; -use zeno::{Mask, Transform}; +use zeno::{Fill, Mask, Stroke, Style, Transform}; -const FONT: &[u8] = std::include_bytes!("../fonts/Miracode.ttf"); +mod config; +use config::*; -struct Buffer<T: std::clone::Clone> { - buffer: Vec<T>, - width: usize, - height: usize, -} -impl<T: Clone> Buffer<T> { - 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<u32>, - buffer: Buffer<Option<char>>, - 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) { - if self.cursor.col < self.buffer.width { - // scrolls - while self.cursor.row >= self.buffer.height { - for row in 0..(self.buffer.height - 1) { - for col in 0..self.buffer.width { - self.buffer.set(col, row, self.buffer.get(col, row + 1)); - } - } - for col in 0..self.buffer.width { - self.buffer.set(col, self.buffer.height - 1, None); - } - self.cursor.row -= 1; - } - 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, - 0x08 => { - self.cursor.col = self.cursor.col - 1; - self.buffer.set(self.cursor.col, self.cursor.row, None); - } - _ => (), - } - 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(); - } -} +mod structs; +use structs::*; fn main() { // initialize the font @@ -181,84 +27,96 @@ fn main() { 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, + MODEL_WIDTH, + MODEL_HEIGHT, + MODEL_SCALE, ); // initialize window and its buffer let mut window = Window::new( - "rustty", - model.screenbuffer.width, - model.screenbuffer.height, + WINDOW_TITLE, + model.screenbuffer_width(), + model.screenbuffer_height(), WindowOptions::default(), ) .unwrap(); - window.set_target_fps(60); + window.set_target_fps(WINDOW_TARGET_FPS); + // pty and pid of child let (pty, child) = spawn_pty().unwrap(); + + // set pty as non-blocking fcntl( &pty, F_SETFL(OFlag::from_bits_truncate(fcntl(&pty, F_GETFL).unwrap()) | OFlag::O_NONBLOCK), ) .unwrap(); + // parser for `vte` let mut statemachine = Parser::new(); - let mut buf = [0u8; 2048]; + // buffers for input and writing to the screen + let mut buf = [0u8; INPUT_BUFFER_SIZE]; + let mut mask = vec![0u8; model.screenbuffer_width() * model.screenbuffer_height()]; + + // window is open, child is alive while window.is_open() - && !window.is_key_down(Key::Escape) && waitpid(child, Some(WaitPidFlag::WNOHANG)) == Ok(WaitStatus::StillAlive) { - // mask to render into - let mut mask = vec![0u8; model.screenbuffer.width * model.screenbuffer.height]; + // clear mask from last frame + mask.fill(0u8); // 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 + // remember that `model.buffer` is always 1-indexed + for row in 1..=model.buffer_height() { + for col in 1..=model.buffer_width() { + if let Some(chr) = model.buffer_at(row, col).chr() { + Mask::new(font.get(&chr).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, + // col and row are 1 indexed; we want col to be 0 indexed bc + // this refers to the bottom left corner of each cell, or at + // least behaves like it + (col as f32 - 1.0) * model.cell_width() as f32 * model.scale(), + row as f32 * model.scale() * model.cell_height() as f32, ), )) + .style( + match model.buffer_at(row, col).char_attr() { + CharAttr::Normal => Style::Fill(Fill::NonZero), + CharAttr::Bold => Style::Stroke(Stroke::new(500.0)), + CharAttr::Inverse => Style::Stroke(Stroke::new(50.0)), + _ => Style::Stroke(Stroke::new(50.0)), + } + ) .size( - model.screenbuffer.width as u32, - model.screenbuffer.height as u32, + // fits transformed svg to screen, or trims excess + model.screenbuffer_width() as u32, + model.screenbuffer_height() as u32, ) + // writes it to mask .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; - } + // render mask onto screen + model.set_screenbuffer(&mask); - // update screen with buffer + // update screen with new screenbuffer window .update_with_buffer( - &model.screenbuffer.buffer, - model.screenbuffer.width, - model.screenbuffer.height, + model.screenbuffer_buffer(), + model.screenbuffer_width(), + model.screenbuffer_height(), ) .unwrap(); // other stuff match read(&pty, &mut buf) { Ok(0) => (), + // if there are new chars, feed them to `vte` Ok(n) => statemachine.advance(&mut model, &buf[..n]), Err(_e) => (), }; @@ -268,10 +126,10 @@ fn main() { || window.is_key_down(Key::RightShift) || window.is_key_down(Key::CapsLock); let ctrl = window.is_key_down(Key::LeftCtrl) || window.is_key_down(Key::RightCtrl); + // writes keystrokes to buffer, will be processed by vte next frame if !keys.is_empty() { let bytes: Vec<u8> = keys .iter() - // TODO apply modifiers .map(|key| { key_to_u8( *key, // key @@ -299,12 +157,16 @@ fn spawn_pty() -> Option<(PtyMaster, Pid)> { &CString::new("/usr/bin/env").unwrap(), &[ CString::new("/usr/bin/env").unwrap(), - CString::new("-i").unwrap(), - CString::new("sh").unwrap(), + CString::new("bash").unwrap(), CString::new("--norc").unwrap(), CString::new("--noprofile").unwrap(), ], - &[CString::new("TERM=dumb").unwrap()], + &[ + CString::new("TERM=".to_string() + ENV_TERM).unwrap(), + CString::new("PATH=".to_string() + ENV_PATH).unwrap(), + CString::new("NIXPKGS_CONFIG=/etc/nix/nixpkgs-config.nix").unwrap(), + CString::new("NIX_PATH=nixpkgs=flake:nixpkgs:/nix/var/nix/profiles/per-user/root/channels").unwrap(), + ], ); return None; } @@ -313,7 +175,7 @@ fn spawn_pty() -> Option<(PtyMaster, Pid)> { }; } -// WARNING not functional; missing some keys and also modifiers +// returns unrecognised keys as null bytes fn key_to_u8(key: Key, shift: bool, ctrl: bool) -> u8 { let mut base = match key { Key::Key0 => b'0', @@ -475,7 +337,7 @@ fn generate_font(face: &Face) -> HashMap<char, String> { let mut hm = HashMap::new(); for c in chars { - let mut builder = Builder(String::new()); + let mut builder = Builder::new(); face.outline_glyph(face.glyph_index(c).unwrap(), &mut builder) .unwrap(); hm.entry(c).insert_entry(builder.0); |
