os_input_output

This commit is contained in:
Aram Drevekenin 2020-08-21 21:47:09 +02:00
parent b74dca4fa1
commit 28593b7348
2 changed files with 181 additions and 157 deletions

View file

@ -1,69 +1,56 @@
use std::time::{Duration, Instant};
mod os_input_output;
use std::io;
use futures::future::join_all;
use std::cell::Cell;
use ::std::fmt::{self, Display, Formatter};
use std::cmp::max;
use std::io::{Read, Write};
use std::collections::VecDeque;
use nix::unistd::{read, write, ForkResult};
use nix::fcntl::{fcntl, FcntlArg, OFlag};
use nix::sys::termios::{
tcgetattr,
cfmakeraw,
tcsetattr,
SetArg,
tcdrain,
cfsetispeed,
cfsetospeed,
BaudRate,
};
use nix::pty::{forkpty, Winsize};
use nix::pty::Winsize;
use std::os::unix::io::RawFd;
use std::process::Command;
use ::std::thread;
use ::std::sync::{Arc, Mutex};
use vte;
use async_std::stream::*;
use async_std::task;
use async_std::task::*;
use async_std::prelude::*;
use ::std::pin::*;
use std::sync::mpsc::{channel, Sender, Receiver};
use crate::os_input_output::{get_os_input, OsInputOutput, OsApi};
struct ReadFromPid {
pid: RawFd,
read_buffer: [u8; 115200],
os_input: Box<dyn OsApi>,
}
impl ReadFromPid {
fn new(pid: &RawFd) -> ReadFromPid {
fn new(pid: &RawFd, os_input: Box<dyn OsApi>) -> ReadFromPid {
ReadFromPid {
pid: *pid,
read_buffer: [0; 115200], // TODO: ???
os_input,
}
}
}
impl Stream for ReadFromPid {
type Item = Vec<u8>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let read_result = read(self.pid, &mut self.read_buffer);
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let mut read_buffer = [0; 115200];
let read_result = &self.os_input.read(self.pid, &mut read_buffer);
match read_result {
Ok(res) => {
// TODO: this might become an issue with multiple panes sending data simultaneously
// ...consider returning None if res == 0 and handling it in the task (or sending
// Poll::Pending?)
let res = Some(self.read_buffer[..=res].to_vec());
self.read_buffer = [0; 115200];
let res = Some(read_buffer[..=*res].to_vec());
return Poll::Ready(res)
},
Err(e) => {
match e {
nix::Error::Sys(errno) => {
if errno == nix::errno::Errno::EAGAIN {
if *errno == nix::errno::Errno::EAGAIN {
return Poll::Ready(Some(vec![])) // TODO: better with timeout waker somehow
// task::block_on(task::sleep(Duration::from_millis(10)));
} else {
@ -77,110 +64,6 @@ impl Stream for ReadFromPid {
}
}
fn read_from_pid (pid: RawFd) -> Option<Vec<u8>> {
let mut read_buffer = [0; 115200];
let read_result = read(pid, &mut read_buffer);
match read_result {
Ok(res) => {
let res = Some(read_buffer[..=res].to_vec());
res
// (res, read_buffer)
},
Err(e) => {
match e {
nix::Error::Sys(errno) => {
if errno == nix::errno::Errno::EAGAIN {
None
// (0, read_buffer)
} else {
panic!("error {:?}", e);
}
},
_ => panic!("error {:?}", e)
}
}
}
}
fn into_raw_mode(pid: RawFd) {
let mut tio = tcgetattr(pid).expect("could not get terminal attribute");
cfmakeraw(&mut tio);
match tcsetattr(pid, SetArg::TCSANOW, &mut tio) {
Ok(_) => {},
Err(e) => panic!("error {:?}", e)
};
}
fn set_baud_rate(pid: RawFd) {
let mut tio = tcgetattr(pid).expect("could not get terminal attribute");
cfsetospeed(&mut tio, BaudRate::B115200).expect("could not set baud rate");
cfsetispeed(&mut tio, BaudRate::B115200).expect("could not set baud rate");
tcsetattr(pid, SetArg::TCSANOW, &mut tio).expect("could not set attributes");
}
pub fn get_terminal_size_using_fd(fd: RawFd) -> Winsize {
// TODO: do this with the nix ioctl
use libc::ioctl;
use libc::TIOCGWINSZ;
let mut winsize = Winsize {
ws_row: 0,
ws_col: 0,
ws_xpixel: 0,
ws_ypixel: 0,
};
unsafe { ioctl(fd, TIOCGWINSZ.into(), &mut winsize) };
winsize
}
pub fn set_terminal_size_using_fd(fd: RawFd, columns: u16, rows: u16) {
// TODO: do this with the nix ioctl
use libc::ioctl;
use libc::TIOCSWINSZ;
let winsize = Winsize {
ws_col: columns,
ws_row: rows,
ws_xpixel: 0,
ws_ypixel: 0,
};
unsafe { ioctl(fd, TIOCSWINSZ.into(), &winsize) };
}
fn spawn_terminal (ws: &Winsize) -> (RawFd, RawFd) {
let (pid_primary, pid_secondary): (RawFd, RawFd) = {
match forkpty(Some(ws), None) {
Ok(fork_pty_res) => {
let pid_primary = fork_pty_res.master;
let pid_secondary = match fork_pty_res.fork_result {
ForkResult::Parent { child } => {
fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::empty())).expect("could not fcntl");
// fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)).expect("could not fcntl");
child
},
ForkResult::Child => {
// TODO: why does $SHELL not work?
// Command::new("$SHELL").spawn().expect("failed to spawn");
set_baud_rate(0);
set_terminal_size_using_fd(0, ws.ws_col, ws.ws_row);
Command::new("/usr/bin/fish").spawn().expect("failed to spawn");
::std::thread::sleep(std::time::Duration::from_millis(300000));
panic!("I am secondary, why?!");
},
};
(pid_primary, pid_secondary.as_raw())
}
Err(e) => {
panic!("failed to fork {:?}", e);
}
}
};
(pid_primary, pid_secondary)
}
#[derive(Clone, Debug)]
struct TerminalCharacter {
pub character: char,
@ -294,14 +177,6 @@ impl TerminalOutput {
self.reflow_lines();
self.should_render = true;
}
pub fn set_size(&mut self, ws: &Winsize) {
let orig_cols = self.display_cols;
self.display_rows = ws.ws_row;
self.display_cols = ws.ws_col;
if orig_cols != self.display_cols && orig_cols != 0 {
self.reflow_lines();
}
}
fn reflow_lines (&mut self) {
self.linebreak_indices.clear();
@ -750,21 +625,22 @@ struct Screen {
active_terminal: Option<RawFd>,
terminal1_output: Option<TerminalOutput>,
terminal2_output: Option<TerminalOutput>,
os_api: Box<dyn OsApi>,
}
impl Screen {
pub fn new () -> Self {
pub fn new (full_screen_ws: &Winsize, os_api: Box<dyn OsApi>) -> Self {
let (sender, receiver): (Sender<ScreenInstruction>, Receiver<ScreenInstruction>) = channel();
let full_screen_ws = get_terminal_size_using_fd(0);
Screen {
receiver,
sender,
full_screen_ws,
full_screen_ws: full_screen_ws.clone(),
last_frame: None,
vertical_separator: TerminalCharacter::new('|').ansi_code(String::from("\u{1b}[m")), // TODO: better
terminal1_output: None,
terminal2_output: None,
active_terminal: None,
os_api,
}
}
pub fn add_terminal(&mut self, pid: RawFd, ws: Winsize) {
@ -794,8 +670,8 @@ impl Screen {
pub fn write_to_active_terminal(&self, byte: u8) {
if let Some(active_terminal) = &self.active_terminal {
let mut buffer = [byte];
write(*active_terminal, &mut buffer).expect("failed to write to terminal");
tcdrain(*active_terminal).expect("failed to drain terminal");
self.os_api.write(*active_terminal, &mut buffer).expect("failed to write to terminal");
self.os_api.tcdrain(*active_terminal).expect("failed to drain terminal");
}
}
pub fn render (&mut self) {
@ -883,16 +759,16 @@ impl Screen {
let terminal2_output = self.terminal2_output.as_mut().unwrap();
terminal1_output.reduce_width(10);
terminal2_output.increase_width(10);
set_terminal_size_using_fd(terminal1_output.pid, terminal1_output.display_cols, terminal1_output.display_rows);
set_terminal_size_using_fd(terminal2_output.pid, terminal2_output.display_cols, terminal2_output.display_rows);
self.os_api.set_terminal_size_using_fd(terminal1_output.pid, terminal1_output.display_cols, terminal1_output.display_rows);
self.os_api.set_terminal_size_using_fd(terminal2_output.pid, terminal2_output.display_cols, terminal2_output.display_rows);
}
pub fn resize_right (&mut self) {
let terminal1_output = self.terminal1_output.as_mut().unwrap();
let terminal2_output = self.terminal2_output.as_mut().unwrap();
terminal1_output.increase_width(10);
terminal2_output.reduce_width(10);
set_terminal_size_using_fd(terminal1_output.pid, terminal1_output.display_cols, terminal1_output.display_rows);
set_terminal_size_using_fd(terminal2_output.pid, terminal2_output.display_cols, terminal2_output.display_rows);
self.os_api.set_terminal_size_using_fd(terminal1_output.pid, terminal1_output.display_cols, terminal1_output.display_rows);
self.os_api.set_terminal_size_using_fd(terminal2_output.pid, terminal2_output.display_cols, terminal2_output.display_rows);
}
pub fn move_focus(&mut self) {
let terminal1_output = self.terminal1_output.as_ref().unwrap();
@ -910,25 +786,27 @@ impl Screen {
struct PtyBus {
sender: Sender<ScreenInstruction>,
active_ptys: Vec<JoinHandle<()>>,
os_input: Box<dyn OsApi>,
}
impl PtyBus {
pub fn new (sender: Sender<ScreenInstruction>) -> Self {
pub fn new (sender: Sender<ScreenInstruction>, os_input: Box<dyn OsApi>) -> Self {
PtyBus {
sender,
active_ptys: Vec::new()
active_ptys: Vec::new(),
os_input,
}
}
pub fn spawn_terminal(&mut self, ws: &Winsize) {
let ws = *ws;
let (pid_primary, _pid_secondary): (RawFd, RawFd) = spawn_terminal(&ws);
let (pid_primary, _pid_secondary): (RawFd, RawFd) = self.os_input.spawn_terminal(&ws);
let task_handle = task::spawn({
// let pid_primary = pid_primary.clone();
let sender = self.sender.clone();
let os_input = self.os_input.clone();
async move {
let mut vte_parser = vte::Parser::new();
let mut vte_event_sender = VteEventSender::new(pid_primary, sender.clone());
let mut first_terminal_bytes = ReadFromPid::new(&pid_primary);
let mut first_terminal_bytes = ReadFromPid::new(&pid_primary, os_input);
while let Some(bytes) = first_terminal_bytes.next().await {
let bytes_is_empty = bytes.is_empty();
for byte in bytes {
@ -956,26 +834,32 @@ impl PtyBus {
}
fn main() {
let os_input = get_os_input();
start(os_input);
}
fn start(os_input: OsInputOutput) {
let mut active_threads = vec![];
let stdin = io::stdin();
into_raw_mode(0);
set_baud_rate(0);
let full_screen_ws = os_input.get_terminal_size_using_fd(0);
os_input.into_raw_mode(0);
::std::thread::sleep(std::time::Duration::from_millis(2000));
let mut screen = Screen::new();
let mut screen = Screen::new(&full_screen_ws, Box::new(os_input.clone()));
let send_screen_instructions = screen.sender.clone();
active_threads.push(
thread::Builder::new()
.name("pty".to_string())
.spawn({
let full_screen_ws = get_terminal_size_using_fd(0);
let (first_terminal_ws, second_terminal_ws) = split_horizontally_with_gap(&full_screen_ws);
let first_terminal_ws = first_terminal_ws.clone();
let second_terminal_ws = second_terminal_ws.clone();
let send_screen_instructions = send_screen_instructions.clone();
let os_input = os_input.clone();
move || {
let mut pty_bus = PtyBus::new(send_screen_instructions);
let mut pty_bus = PtyBus::new(send_screen_instructions, Box::new(os_input));
// this is done here so that we can add terminals dynamically on a different
// thread later
pty_bus.spawn_terminal(&first_terminal_ws);

140
src/os_input_output.rs Normal file
View file

@ -0,0 +1,140 @@
use nix::unistd::{read, write, ForkResult};
use nix::fcntl::{fcntl, FcntlArg, OFlag};
use nix::sys::termios::{
tcgetattr,
cfmakeraw,
tcsetattr,
SetArg,
tcdrain,
};
use nix::pty::{forkpty, Winsize};
use std::os::unix::io::RawFd;
use std::process::Command;
fn into_raw_mode(pid: RawFd) {
let mut tio = tcgetattr(pid).expect("could not get terminal attribute");
cfmakeraw(&mut tio);
match tcsetattr(pid, SetArg::TCSANOW, &mut tio) {
Ok(_) => {},
Err(e) => panic!("error {:?}", e)
};
}
pub fn get_terminal_size_using_fd(fd: RawFd) -> Winsize {
// TODO: do this with the nix ioctl
use libc::ioctl;
use libc::TIOCGWINSZ;
let mut winsize = Winsize {
ws_row: 0,
ws_col: 0,
ws_xpixel: 0,
ws_ypixel: 0,
};
unsafe { ioctl(fd, TIOCGWINSZ.into(), &mut winsize) };
winsize
}
pub fn set_terminal_size_using_fd(fd: RawFd, columns: u16, rows: u16) {
// TODO: do this with the nix ioctl
use libc::ioctl;
use libc::TIOCSWINSZ;
let winsize = Winsize {
ws_col: columns,
ws_row: rows,
ws_xpixel: 0,
ws_ypixel: 0,
};
unsafe { ioctl(fd, TIOCSWINSZ.into(), &winsize) };
}
fn spawn_terminal (ws: &Winsize) -> (RawFd, RawFd) {
let (pid_primary, pid_secondary): (RawFd, RawFd) = {
match forkpty(Some(ws), None) {
Ok(fork_pty_res) => {
let pid_primary = fork_pty_res.master;
let pid_secondary = match fork_pty_res.fork_result {
ForkResult::Parent { child } => {
fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::empty())).expect("could not fcntl");
// fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)).expect("could not fcntl");
child
},
ForkResult::Child => {
// TODO: why does $SHELL not work?
// Command::new("$SHELL").spawn().expect("failed to spawn");
// set_terminal_size_using_fd(0, ws.ws_col, ws.ws_row);
Command::new("/usr/bin/fish").spawn().expect("failed to spawn");
::std::thread::sleep(std::time::Duration::from_millis(300000));
panic!("I am secondary, why?!");
},
};
(pid_primary, pid_secondary.as_raw())
}
Err(e) => {
panic!("failed to fork {:?}", e);
}
}
};
(pid_primary, pid_secondary)
}
#[derive(Clone)]
pub struct OsInputOutput {
// pub get_terminal_size_using_fd: Box<dyn Fn(RawFd) -> Winsize + Send>,
// pub set_terminal_size_using_fd: Box<dyn Fn(RawFd, u16, u16) + Send>,
// pub into_raw_mode: Box<dyn Fn(RawFd) + Send>,
// pub spawn_terminal: Box<dyn Fn(&Winsize) -> (RawFd, RawFd) + Send>,
}
pub trait OsApi: Send + Sync {
fn get_terminal_size_using_fd(&self, pid: RawFd) -> Winsize;
fn set_terminal_size_using_fd(&self, pid: RawFd, cols: u16, rows: u16);
fn into_raw_mode(&self, pid: RawFd);
fn spawn_terminal(&self, ws: &Winsize) -> (RawFd, RawFd);
fn read(&self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>;
fn write(&self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>;
fn tcdrain(&self, pid: RawFd) -> Result<(), nix::Error>;
fn box_clone(&self) -> Box<dyn OsApi>;
// let read_result = read(self.pid, &mut self.read_buffer);
}
impl OsApi for OsInputOutput {
fn get_terminal_size_using_fd(&self, pid: RawFd) -> Winsize {
get_terminal_size_using_fd(pid)
}
fn set_terminal_size_using_fd(&self, pid: RawFd, cols: u16, rows: u16) {
set_terminal_size_using_fd(pid, cols, rows);
}
fn into_raw_mode(&self, pid: RawFd) {
into_raw_mode(pid);
}
fn spawn_terminal(&self, ws: &Winsize) -> (RawFd, RawFd) {
spawn_terminal(ws)
}
fn read(&self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
read(pid, buf)
}
fn write(&self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
write(pid, buf)
}
fn tcdrain(&self, pid: RawFd) -> Result<(), nix::Error> {
tcdrain(pid)
}
fn box_clone(&self) -> Box<dyn OsApi> {
Box::new((*self).clone())
}
}
impl Clone for Box<dyn OsApi>
{
fn clone(&self) -> Box<dyn OsApi> {
self.box_clone()
}
}
pub fn get_os_input () -> OsInputOutput {
OsInputOutput {}
}