This commit is contained in:
mtgmonkey 2025-07-06 18:15:57 -04:00
parent 0a2ec5f358
commit 883b94f1eb
5 changed files with 203 additions and 55 deletions

View file

@ -5,3 +5,4 @@ edition = "2024"
[dependencies] [dependencies]
nix = { version = "0.30.1", features = ["term", "process", "fs"], default-features = false } nix = { version = "0.30.1", features = ["term", "process", "fs"], default-features = false }
iced = "0.13.1"

6
flake.lock generated
View file

@ -22,11 +22,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1751011381, "lastModified": 1751271578,
"narHash": "sha256-krGXKxvkBhnrSC/kGBmg5MyupUUT5R6IBCLEzx9jhMM=", "narHash": "sha256-P/SQmKDu06x8yv7i0s8bvnnuJYkxVGBWLWHaU+tt4YY=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "30e2e2857ba47844aa71991daa6ed1fc678bcbb7", "rev": "3016b4b15d13f3089db8a41ef937b13a9e33a8df",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -1,9 +1,16 @@
{ {
busybox-sandbox-shell, busybox-sandbox-shell,
expat,
fontconfig,
freetype,
lib, lib,
libGL,
libxkbcommon,
makeWrapper, makeWrapper,
naersk, naersk,
pkg-config, pkg-config,
wayland,
xorg,
... ...
}: }:
naersk.buildPackage rec { naersk.buildPackage rec {
@ -11,6 +18,17 @@ naersk.buildPackage rec {
src = ./.; src = ./.;
buildInputs = [ buildInputs = [
busybox-sandbox-shell busybox-sandbox-shell
expat
fontconfig
freetype
freetype.dev
libGL
libxkbcommon
wayland
xorg.libX11
xorg.libXcursor
xorg.libXi
xorg.libXrandr
]; ];
nativeBuildInputs = [ nativeBuildInputs = [
pkg-config pkg-config

146
src/lib.rs Normal file
View file

@ -0,0 +1,146 @@
use iced::Element;
use iced::widget::{column, scrollable, text, text_input};
use nix::pty::{ForkptyResult, forkpty};
use nix::unistd::write;
use std::fs::File;
use std::io::{Read, Write};
use std::os::unix::io::{AsFd, OwnedFd};
use std::process::Command;
pub fn spawn_pty_with_shell(default_shell: String) -> OwnedFd {
match unsafe { forkpty(None, None) } {
Ok(fork_pty_res) => match fork_pty_res {
ForkptyResult::Parent { master, .. } => {
set_nonblock(&master);
master
}
ForkptyResult::Child => {
Command::new(&default_shell).spawn().unwrap();
std::thread::sleep(std::time::Duration::MAX);
std::process::exit(0);
}
},
Err(e) => panic!("failed to fork {:?}", e),
}
}
pub fn read_from_fd(fd: &OwnedFd) -> Option<Vec<u8>> {
let mut read_buffer = [0; 65536];
let mut file = File::from(fd.try_clone().unwrap());
file.flush();
let file = file.read(&mut read_buffer);
match file {
Ok(file) => Some(read_buffer[..file].to_vec()),
Err(_) => None,
}
}
fn set_nonblock(fd: &OwnedFd) {
let flags = nix::fcntl::fcntl(fd, nix::fcntl::FcntlArg::F_GETFL).unwrap();
let mut flags =
nix::fcntl::OFlag::from_bits(flags & nix::fcntl::OFlag::O_ACCMODE.bits()).unwrap();
flags.set(nix::fcntl::OFlag::O_NONBLOCK, true);
nix::fcntl::fcntl(fd, nix::fcntl::FcntlArg::F_SETFL(flags)).unwrap();
}
#[derive(Debug, Clone)]
pub enum Msg {
HasInput,
InputChanged(String),
}
pub struct Model {
screen_buffer: [u8; 65536],
screen_buffer_index: usize,
fd: OwnedFd,
stdin: OwnedFd,
input: String,
}
impl Model {
fn new(
screen_buffer: [u8; 65536],
screen_buffer_index: usize,
fd: OwnedFd,
stdin: OwnedFd,
input: String,
) -> Self {
Model {
screen_buffer,
screen_buffer_index,
fd,
stdin,
input,
}
}
pub fn update(&mut self, msg: Msg) {
match msg {
Msg::HasInput => {
let mut write_buffer = self.input.as_bytes().to_vec();
write_buffer.push(b'\n');
write(self.fd.as_fd(), &mut write_buffer);
self.input = String::new();
let mut nored = 0;
while nored <= 2 {
let red = read_from_fd(&self.fd);
match &red {
Some(red) => {
nored += 1;
self.update_screen_buffer(red);
}
None => (),
};
}
}
Msg::InputChanged(input) => self.input = input,
}
}
pub fn view(&self) -> Element<'_, Msg> {
scrollable(column![
text(String::from(
String::from_utf8(self.screen_buffer.to_vec())
.unwrap()
.trim_end_matches('\0')
)),
text_input("", &self.input)
.on_input(Msg::InputChanged)
.on_submit(Msg::HasInput)
])
.into()
}
fn update_screen_buffer(&mut self, vec: &Vec<u8>) {
let offset = self.screen_buffer.iter().position(|&c| c == b'\0').unwrap();
for (i, chr) in vec.iter().enumerate() {
self.screen_buffer[i + offset] = chr.clone();
}
}
}
impl Default for Model {
fn default() -> Self {
let mut me = Self::new(
[0; 65536],
0,
spawn_pty_with_shell("/home/mtgmonkey/.nix-profile/bin/dash".to_string()),
std::io::stdin().as_fd().try_clone_to_owned().unwrap(),
String::new(),
);
let mut nored = true;
while nored {
let red = read_from_fd(&me.fd);
match red {
Some(red) => {
nored = false;
me.update_screen_buffer(&red);
}
None => (),
}
}
me
}
}

View file

@ -1,56 +1,39 @@
use nix::pty::{ForkptyResult, forkpty}; use iced::Element;
use nix::unistd::{read, write}; use iced::widget::{button, text};
use std::fs::File;
use std::io::{Read, Write};
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, OwnedFd};
use std::process::Command;
fn main() { use nix::unistd::write;
let default_shell = "/home/mtgmonkey/.nix-profile/bin/dash".to_string();
use std::fs::File;
use std::io::Read;
use std::io::Write;
use std::os::unix::io::{AsFd, OwnedFd};
use rust_term::*;
fn main() -> iced::Result {
iced::run("test", Model::update, Model::view)
/* let default_shell = "/home/mtgmonkey/.nix-profile/bin/dash".to_string();
let fd = spawn_pty_with_shell(default_shell); let fd = spawn_pty_with_shell(default_shell);
set_nonblock(&fd);
let mut write_buffer = "tty\n".as_bytes().to_vec(); let mut write_buffer = "tty\n".as_bytes().to_vec();
write(fd.as_fd(), &mut write_buffer); write(fd.as_fd(), &mut write_buffer);
loop { loop {
let red = read_from_fd(fd.try_clone().unwrap()); let red = read_from_fd(&fd);
match red { match red {
Some(red) => print!("{}", String::from_utf8(red).unwrap()), Some(red) => print!("{}", String::from_utf8(red).unwrap()),
None => { None => {
let mut write_buffer = vec![]; let mut read_buffer = [0; 65536];
std::io::stdin().read_to_end(&mut write_buffer); let mut file = File::from(std::io::stdin().as_fd().try_clone_to_owned().unwrap());
write(fd.as_fd(), &mut write_buffer); file.flush();
let file = file.read(&mut read_buffer);
println!(
"{}",
match file {
Ok(file) => write(fd.as_fd(), &read_buffer[..file]).unwrap(),
Err(_) => 0,
}
);
} }
}; };
} }
} */
fn spawn_pty_with_shell(default_shell: String) -> OwnedFd {
match (unsafe { forkpty(None, None) }) {
Ok(fork_pty_res) => match fork_pty_res {
ForkptyResult::Parent { master, .. } => master,
ForkptyResult::Child => {
Command::new(&default_shell).spawn().unwrap();
std::thread::sleep(std::time::Duration::MAX);
std::process::exit(0);
}
},
Err(e) => panic!("failed to fork {:?}", e),
}
}
fn read_from_fd(fd: OwnedFd) -> Option<Vec<u8>> {
let mut read_buffer = [0; 65536];
let mut file = File::from(fd).read(&mut read_buffer);
match file {
Ok(file) => Some(read_buffer[..file].to_vec()),
Err(_) => None,
}
}
fn set_nonblock(fd: &OwnedFd) {
let flags = nix::fcntl::fcntl(fd, nix::fcntl::FcntlArg::F_GETFL).unwrap();
let mut flags =
nix::fcntl::OFlag::from_bits(flags & nix::fcntl::OFlag::O_ACCMODE.bits()).unwrap();
flags.set(nix::fcntl::OFlag::O_NONBLOCK, true);
nix::fcntl::fcntl(fd, nix::fcntl::FcntlArg::F_SETFL(flags)).unwrap();
} }