inhouse pty

This commit is contained in:
stjet
2025-04-30 04:50:11 +00:00
parent 724ffbd494
commit c1afd3f33e
15 changed files with 82 additions and 15 deletions

146
linux/src/fb.rs Normal file
View File

@@ -0,0 +1,146 @@
use std::fs::{ File, OpenOptions };
use std::os::fd::AsRawFd;
use std::ptr;
use libc::{ ioctl, mmap, munmap, c_ulong, c_int };
//https://stackoverflow.com/a/75402838
//https://github.com/torvalds/linux/blob/master/include/uapi/linux/fb.h
const FBIOGET_VSCREENINFO: c_ulong = 0x4600;
const FBIOGET_FSCREENINFO: c_ulong = 0x4602;
//https://www.kernel.org/doc/html/latest/fb/api.html
#[derive(Default)]
#[repr(C)]
pub struct FB_BITFIELD {
offset: u32,
length: u32,
msb_right: u32,
}
#[derive(Default)]
#[repr(C)]
pub struct FB_VAR_SCREENINFO {
pub xres: u32,
pub yres: u32,
pub xres_virtual: u32,
pub yres_virtual: u32,
pub xoffset: u32,
pub yoffset: u32,
pub bits_per_pixel: u32,
pub grayscale: u32,
pub red: FB_BITFIELD,
pub green: FB_BITFIELD,
pub blue: FB_BITFIELD,
pub transp: FB_BITFIELD,
pub nonstd: u32,
pub activate: u32,
pub height: u32,
pub width: u32,
pub accel_flags: u32,
pub pixclock: u32,
pub left_margin: u32,
pub right_margin: u32,
pub upper_margin: u32,
pub lower_margin: u32,
pub hsync_len: u32,
pub wsync_len: u32,
pub sync: u32,
pub vmode: u32,
pub rotate: u32,
pub colorspace: u32,
pub reserved: [u32; 4],
}
#[derive(Default)]
#[repr(C)]
pub struct FB_FIX_SCREENINFO {
pub id: [u8; 16],
pub smem_start: usize,
pub smem_len: u32,
pub r#type: u32,
pub type_aux: u32,
pub visual: u32,
pub xpanstep: u16,
pub ypanstep: u16,
pub ywrapstep: u16,
pub line_length: u32,
pub mmio_len: u32,
pub accel: u32,
pub capabilities: u16,
pub reserved: [u16; 2],
}
pub struct Framebuffer {
pointer: *mut libc::c_void,
pub var_screen_info: FB_VAR_SCREENINFO,
pub fix_screen_info: FB_FIX_SCREENINFO,
size: usize,
}
impl Framebuffer {
pub fn open(path: &str) -> Result<Self, ()> {
let file = Framebuffer::open_file(path)?;
let vi = Framebuffer::get_vscreeninfo(file.as_raw_fd())?;
let fi = Framebuffer::get_fscreeninfo(file.as_raw_fd())?;
//then mmap or something
let size = vi.yres_virtual * fi.line_length * (vi.bits_per_pixel / 8);
let pointer = unsafe {
mmap(ptr::null_mut(), size.try_into().unwrap(), libc::PROT_READ | libc::PROT_WRITE, libc::MAP_SHARED, file.as_raw_fd(), 0)
};
if pointer == libc::MAP_FAILED {
return Err(());
}
Ok(Self {
pointer,
var_screen_info: vi,
fix_screen_info: fi,
size: size as usize,
})
}
fn open_file(path: &str) -> Result<File, ()> {
OpenOptions::new().read(true).write(true).open(path).map_err(|_| ())
}
fn get_vscreeninfo(raw_fd: c_int) -> Result<FB_VAR_SCREENINFO, ()> {
let mut vi: FB_VAR_SCREENINFO = Default::default();
let result = unsafe {
ioctl(raw_fd, FBIOGET_VSCREENINFO.try_into().unwrap(), &mut vi)
};
if result != -1 {
Ok(vi)
} else {
Err(())
}
}
fn get_fscreeninfo(raw_fd: c_int) -> Result<FB_FIX_SCREENINFO, ()> {
let mut fi: FB_FIX_SCREENINFO = Default::default();
let result = unsafe {
ioctl(raw_fd, FBIOGET_FSCREENINFO.try_into().unwrap(), &mut fi)
};
if result != -1 {
Ok(fi)
} else {
Err(())
}
}
pub fn write_frame(&mut self, frame: &[u8]) {
unsafe {
ptr::copy_nonoverlapping(frame.as_ptr(), self.pointer as *mut u8, frame.len());
};
}
}
impl Drop for Framebuffer {
fn drop(&mut self) {
unsafe {
munmap(self.pointer, self.size);
}
}
}

89
linux/src/keys.rs Normal file
View File

@@ -0,0 +1,89 @@
use std::io::{ Read, Stdin };
use std::sync::mpsc::{ channel, Receiver };
use std::thread;
//includes a section on reading keys
//https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html
const ALPHABET: [char; 26] = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
pub enum Key {
Char(char),
Alt(char),
Ctrl(char),
Backspace,
Esc,
ArrowUp,
ArrowDown,
ArrowLeft,
ArrowRight,
Other(u8), //we don't get about anything else, lmao
}
pub struct RawStdin {
//bytes: Peekable<Bytes<StdinLock<'a>>>,
receiver: Receiver<u8>,
}
impl RawStdin {
pub fn new(stdin: Stdin) -> Self {
let (sender, receiver) = channel();
thread::spawn(move || {
let bytes = stdin.lock().bytes();
for b in bytes {
sender.send(b.unwrap()).unwrap();
}
});
RawStdin {
//bytes: stdin.lock().bytes().peekable(),
receiver,
}
}
}
impl Iterator for RawStdin {
type Item = Key;
fn next(&mut self) -> Option<Self::Item> {
let first = self.receiver.recv().unwrap();
Some(match first {
1..=26 => {
//ctrl
if first == 9 {
Key::Char('\t')
} else if first == 13 {
//ctrl+m and enter give the same thing
Key::Char('\n')
} else {
Key::Ctrl(ALPHABET[first as usize - 1])
}
},
27 => {
//escape sequence
//not handling escape sequences that are 3+ bytes is probably going to come back to bite us
let n = self.receiver.try_recv();
if let Ok(b'[') = n {
let n = self.receiver.recv().unwrap();
match n {
b'A' => Key::ArrowUp,
b'B' => Key::ArrowDown,
b'C' => Key::ArrowRight,
b'D' => Key::ArrowLeft,
_ => Key::Other(n),
}
} else if n.is_ok() {
//Alt+<char> sends Esc+<char>
Key::Alt(char::from(n.unwrap()))
} else {
Key::Esc
}
},
127 => {
Key::Backspace
},
_ => {
Key::Char(char::from(first))
},
})
}
}

4
linux/src/lib.rs Normal file
View File

@@ -0,0 +1,4 @@
pub mod fb;
pub mod raw;
pub mod keys;
pub mod pty;

55
linux/src/pty.rs Normal file
View File

@@ -0,0 +1,55 @@
use std::ptr;
use std::process::{ Command, Child };
use std::fs::File;
use std::os::fd::{ OwnedFd, FromRawFd };
use libc::openpty;
//basically the master and slave are linked? slave behaves just like normal terminal
//I don't totally get it, I guess just attach the command's stdout and stderr to ptyslave for reading?
pub struct PtyMaster {
pub file: File,
}
impl PtyMaster {
pub fn new(fd: OwnedFd) -> Self {
Self {
file: File::from(fd),
}
}
}
pub struct PtySlave {
pub file: File,
fd: OwnedFd,
}
impl PtySlave {
pub fn new(fd: OwnedFd) -> Self {
Self {
file: File::from(fd.try_clone().unwrap()),
fd,
}
}
//assume stdin is piped
pub fn attach_and_spawn(&self, command: &mut Command) -> std::io::Result<Child> {
command.stdout(self.fd.try_clone().unwrap());
command.stderr(self.fd.try_clone().unwrap());
command.spawn()
}
}
pub fn open_pty() -> Result<(PtyMaster, PtySlave), ()> {
let mut master_fd = 0;
let mut slave_fd = 0;
let result = unsafe { openpty(&mut master_fd, &mut slave_fd, ptr::null_mut(), ptr::null_mut(), ptr::null_mut()) };
if result == -1 {
Err(())
} else {
let master_fd = unsafe { OwnedFd::from_raw_fd(master_fd) };
let slave_fd = unsafe { OwnedFd::from_raw_fd(slave_fd) };
Ok((PtyMaster::new(master_fd), PtySlave::new(slave_fd)))
}
}

65
linux/src/raw.rs Normal file
View File

@@ -0,0 +1,65 @@
use std::io::Stdout;
use std::mem::zeroed;
use std::os::fd::AsRawFd;
use libc::{ cfmakeraw, c_int, tcgetattr, tcsetattr, termios, TCSAFLUSH };
//https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html
//on TCSAFLUSH: "The TCSAFLUSH argument specifies when to apply the change: in this case, it waits for all pending output to be written to the terminal, and also discards any input that hasn't been read."
//https://www.man7.org/linux/man-pages/man3/termios.3.html
//(use cfmakeraw instead doing all those bitwise stuff manually)
//enter and exit tty raw mode
pub struct RawStdout {
pub stdout: Stdout,
old_termios: termios,
}
impl RawStdout {
pub fn new(stdout: Stdout) -> Self {
RawStdout {
stdout,
old_termios: unsafe { zeroed() },
}
}
pub fn get_termios(raw_fd: c_int) -> Result<termios, ()> {
let mut termios_struct: termios = unsafe { zeroed() };
let result = unsafe {
tcgetattr(raw_fd, &mut termios_struct)
};
if result != -1 {
Ok(termios_struct)
} else {
Err(())
}
}
pub fn enter_raw_mode(&mut self) -> Result<(), ()> {
let raw_fd = self.stdout.as_raw_fd();
let mut termios_struct = Self::get_termios(raw_fd)?;
self.old_termios = termios_struct;
let result = unsafe {
cfmakeraw(&mut termios_struct);
tcsetattr(raw_fd, TCSAFLUSH, &mut termios_struct)
};
if result != -1 {
Ok(())
} else {
Err(())
}
}
pub fn exit_raw_mode(&mut self) -> Result<(), ()> {
let result = unsafe {
tcsetattr(self.stdout.as_raw_fd(), TCSAFLUSH, &mut self.old_termios)
};
if result != -1 {
Ok(())
} else {
Err(())
}
}
}