commit eedc84d871c901992a4dae31c5a17f49f8b4a199 Author: andromeda Date: Fri Jan 16 21:31:03 2026 +0100 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b2ae984 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/result* diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..ade81cc --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,69 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "rustty" +version = "0.1.0" +dependencies = [ + "nix", + "vte", +] + +[[package]] +name = "vte" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5924018406ce0063cd67f8e008104968b74b563ee1b85dde3ed1f7cb87d3dbd" +dependencies = [ + "arrayvec", + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f4b7be2 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "rustty" +version = "0.1.0" +edition = "2024" + +[dependencies] +nix = {default-features = false, features=["term","process","fs"], version="0.30.1"} +vte = "0.15.0" diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..54f02b5 --- /dev/null +++ b/flake.lock @@ -0,0 +1,87 @@ +{ + "nodes": { + "fenix": { + "inputs": { + "nixpkgs": [ + "naersk", + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1752475459, + "narHash": "sha256-z6QEu4ZFuHiqdOPbYss4/Q8B0BFhacR8ts6jO/F/aOU=", + "owner": "nix-community", + "repo": "fenix", + "rev": "bf0d6f70f4c9a9cf8845f992105652173f4b617f", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "naersk": { + "inputs": { + "fenix": "fenix", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1763384566, + "narHash": "sha256-r+wgI+WvNaSdxQmqaM58lVNvJYJ16zoq+tKN20cLst4=", + "owner": "nix-community", + "repo": "naersk", + "rev": "d4155d6ebb70fbe2314959842f744aa7cabbbf6a", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "naersk", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1768305791, + "narHash": "sha256-AIdl6WAn9aymeaH/NvBj0H9qM+XuAuYbGMZaP0zcXAQ=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "1412caf7bf9e660f2f962917c14b1ea1c3bc695e", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable", + "type": "indirect" + } + }, + "root": { + "inputs": { + "naersk": "naersk", + "nixpkgs": "nixpkgs" + } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1752428706, + "narHash": "sha256-EJcdxw3aXfP8Ex1Nm3s0awyH9egQvB2Gu+QEnJn2Sfg=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "591e3b7624be97e4443ea7b5542c191311aa141d", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..6a49e38 --- /dev/null +++ b/flake.nix @@ -0,0 +1,23 @@ +{ + inputs = { + nixpkgs.url = "nixpkgs/nixos-unstable"; + naersk = { + url = "github:nix-community/naersk"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + outputs = { + nixpkgs, + naersk, + ... + }: let + system = "x86_64-linux"; + pkgs = nixpkgs.legacyPackages.${system}; + in { + packages.${system} = { + default = pkgs.callPackage ./package.nix { + naersk = pkgs.callPackage naersk {}; + }; + }; + }; +} diff --git a/package.nix b/package.nix new file mode 100644 index 0000000..84aac79 --- /dev/null +++ b/package.nix @@ -0,0 +1,38 @@ +{ + fontconfig, + freetype, + lib, + libGL, + libxkbcommon, + makeWrapper, + naersk, + pkg-config, + wayland, + ... +}: +naersk.buildPackage rec { + name = "rustty"; + src = ./.; + buildInputs = [ + # fonts + fontconfig + freetype + freetype.dev + + # wayland graphics + libGL + libxkbcommon + wayland + ]; + nativeBuildInputs = [ + pkg-config + makeWrapper + ]; + postInstall = '' + wrapProgram "$out/bin/${meta.mainProgram}" --prefix LD_LIBRARY_PATH : "${lib.makeLibraryPath buildInputs}" + ''; + meta = { + mainProgram = "rustty"; + homepage = "https://mtgmonkey.net"; + }; +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..05fb64e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,98 @@ +use nix::pty::{ForkptyResult, forkpty, PtyMaster}; +use nix::unistd::read; + +use std::vec::Vec; +use std::io::{self, Read, Write}; +use std::process::Command; +use std::thread; +use std::time::Duration; + +use vte::{Params, Parser, Perform}; + +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() { + + // TODO get rid of hard coded `sh` path + let mut pty = spawn_pty("/home/andromeda/.nix-profile/bin/sh").unwrap(); + println!("{:?}", &pty); + + let input = io::stdin(); + let mut handle = input.lock(); + + let mut statemachine = Parser::new(); + let mut performer = Performer; + let mut buf = [0u8; 2048]; + + while true { + match read(&pty, &mut buf) { + Ok(0) => break, + Ok(n) => { + statemachine.advance(&mut performer, &buf[..n]); + }, + Err(_e) => break, + }; + }; +} + +// 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 {master, ..} => { + // SAFETY `master` is a valid PtyMaster + return Some(unsafe{PtyMaster::from_owned_fd(master)}); + } + ForkptyResult::Child =>{ + Command::new(shell).spawn(); + // TODO come up with an elegant solution + thread::sleep(Duration::MAX); + return None; + } + }, + Err(_e) => return None, + }; +}