Add child forking functionality

embed
Manos Pitsidianakis 2018-07-21 11:20:13 +03:00
parent ac334b09b1
commit b35407bc7f
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
7 changed files with 199 additions and 39 deletions

View File

@ -10,6 +10,7 @@ path = "src/bin.rs"
[dependencies]
chan = "0.1.21"
chan-signal = "0.3.1"
nix = "*"
melib = { path = "melib", version = "*" }
ui = { path = "ui", version = "*" }

View File

@ -31,7 +31,7 @@ use ui::*;
pub use melib::*;
use std::thread;
use std::io::{stdout, stdin, };
use std::io::{stdout,};
#[macro_use]
extern crate chan;
@ -39,13 +39,24 @@ extern crate chan_signal;
use chan_signal::Signal;
fn make_input_thread(sx: chan::Sender<ThreadEvent>, rx: chan::Receiver<bool>) -> () {
let stdin = std::io::stdin();
thread::Builder::new().name("input-thread".to_string()).spawn(move || {
get_events(stdin,
|k| {
sx.send(ThreadEvent::Input(k));
},
|| {
sx.send(ThreadEvent::UIEventType(UIEventType::ChangeMode(UIMode::Fork)));
}, rx)}).unwrap();
}
fn main() {
/* Lock all stdios */
/* Lock all stdio outs */
let _stdout = stdout();
let mut _stdout = _stdout.lock();
let stdin = stdin();
let stdin = stdin;
/*
let _stderr = stderr();
let mut _stderr = _stderr.lock();
@ -58,15 +69,17 @@ fn main() {
* */
let (sender, receiver) = chan::sync(::std::mem::size_of::<ThreadEvent>());
{
let sender = sender.clone();
thread::Builder::new().name("input-thread".to_string()).spawn(move || {
get_events(stdin, move | k| { sender.send(ThreadEvent::Input(k));
})}).unwrap();
}
/*
* Create async channel to block the input-thread if we need to fork and stop it from reading
* stdin, see get_events() for details
* */
let (tx, rx) = chan::async();
/* Get input thread handle to kill it if we need to */
make_input_thread(sender.clone(), rx.clone());
/* Create the application State. This is the 'System' part of an ECS architecture */
let mut state = State::new(_stdout, sender);
let mut state = State::new(_stdout, sender.clone(), tx );
/* Register some reasonably useful interfaces */
let menu = Entity {component: Box::new(AccountMenu::new(&state.context.accounts)) };
@ -81,23 +94,27 @@ fn main() {
state.register_entity(xdg_notifications);
/* Keep track of the input mode. See ui::UIMode for details */
let mut mode: UIMode = UIMode::Normal;
'main: loop {
state.render();
eprintln!("entered main loop");
'inner: loop {
eprintln!("entered inner loop");
/* Check if any entities have sent reply events to State. */
let events: Vec<UIEvent> = state.context.replies();
for e in events {
state.rcv_event(e);
}
state.redraw();
/* Poll on all channels. Currently we have the input channel for stdin, watching events and the signal watcher. */
/* Poll on all channels. Currently we have the input channel for stdin, watching events and the signal watcher. */
chan_select! {
receiver.recv() -> r => {
eprintln!("received {:?}", r);
match r.unwrap() {
ThreadEvent::Input(k) => {
match mode {
eprintln!(" match input");
match state.mode {
UIMode::Normal => {
match k {
Key::Char('q') | Key::Char('Q') => {
@ -105,8 +122,8 @@ fn main() {
break 'main;
},
Key::Char(';') => {
mode = UIMode::Execute;
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::ChangeMode(mode)});
state.mode = UIMode::Execute;
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::ChangeMode(UIMode::Execute)});
state.redraw();
}
key => {
@ -118,8 +135,8 @@ fn main() {
UIMode::Execute => {
match k {
Key::Char('\n') | Key::Esc => {
mode = UIMode::Normal;
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::ChangeMode(mode)});
state.mode = UIMode::Normal;
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::ChangeMode(UIMode::Normal)});
state.redraw();
},
k @ Key::Char(_) => {
@ -129,6 +146,10 @@ fn main() {
_ => {},
}
},
UIMode::Fork => {
eprintln!("UIMODE FORK");
break 'inner; // `goto` 'reap loop, and wait on child.
},
}
},
ThreadEvent::RefreshMailbox { name : n } => {
@ -137,20 +158,50 @@ fn main() {
/* Don't handle this yet. */
eprintln!("Refresh mailbox {}", n);
},
ThreadEvent::UIEventType(UIEventType::ChangeMode(f)) => {
state.mode = f;
break 'inner; // `goto` 'reap loop, and wait on child.
}
ThreadEvent::UIEventType(e) => {
eprintln!(" match event");
state.rcv_event(UIEvent { id: 0, event_type: e});
state.render();
},
}
},
signal.recv() -> signal => {
if let Some(Signal::WINCH) = signal {
state.update_size();
state.render();
state.redraw();
if state.mode != UIMode::Fork {
if let Some(Signal::WINCH) = signal {
eprintln!("resize, mode is {:?}", state.mode);
state.update_size();
state.render();
state.redraw();
}
}
},
}
} // end of 'inner
'reap: loop {
eprintln!("reached reap loop");
match state.try_wait_on_child() {
Some(true) => {
make_input_thread(sender.clone(), rx.clone());
state.mode = UIMode::Normal;
state.render();
},
Some(false) => {
use std::{thread, time};
let ten_millis = time::Duration::from_millis(500);
thread::sleep(ten_millis);
continue 'reap;
},
None => {break 'reap;},
}
}
}
}

View File

@ -11,3 +11,4 @@ chan = "0.1.21"
notify = "4.0.1"
notify-rust = "^3"
nom = "3.2.0"
chan-signal = "0.3.1"

View File

@ -476,7 +476,42 @@ impl Component for MailListing {
UIEventType::Input(Key::Char('\n')) if self.unfocused == false => {
self.unfocused = true;
self.dirty = true;
},
UIEventType::Input(Key::Char('m')) if self.unfocused == false => {
use std::process::{Command, Stdio};
/* Kill input thread so that spawned command can be sole receiver of stdin */
{
/* I tried thread::park() here but for some reason it never blocked and always
* returned. Spinlocks are also useless because you have to keep the mutex
* guard alive til the child process exits, which requires some effort.
*
* The only problem with this approach is tht the user has to send some input
* in order for the input-thread to wake up and realise it should kill itself.
*
* I tried writing to stdin/tty manually but for some reason rustty didn't
* acknowledge it.
*/
/*
* tx sends to input-thread and it kills itself.
*/
let tx = context.input_thread();
tx.send(true);
}
let mut output = Command::new("vim")
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.spawn()
.expect("failed to execute process") ;
/*
* Main loop will wait on children and when they reap them the loop spawns a new
* input-thread
*/
context.replies.push_back(UIEvent { id: 0, event_type: UIEventType::Fork(output) });
context.replies.push_back(UIEvent { id: 0, event_type: UIEventType::ChangeMode(UIMode::Fork) });
return;
},
UIEventType::Input(Key::Esc) | UIEventType::Input(Key::Char('i')) if self.unfocused == true => {
self.unfocused = false;

View File

@ -328,14 +328,15 @@ impl Component for StatusBar {
(set_y(upper_left, get_y(bottom_right) - height + 1), set_y(bottom_right, get_y(bottom_right) - height+1)),
context);
},
_ => {},
}
}
fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
self.container.rcv_event(event, context);
match event.event_type {
UIEventType::RefreshMailbox((idx_a, idx_f)) => {
let m = &context.accounts[idx_a][idx_f].as_ref().unwrap().as_ref().unwrap();
match &event.event_type {
UIEventType::RefreshMailbox((ref idx_a, ref idx_f)) => {
let m = &context.accounts[*idx_a][*idx_f].as_ref().unwrap().as_ref().unwrap();
self.status = format!("{} |Mailbox: {}, Messages: {}, New: {}", self.mode, m.folder.name(), m.collection.len(), m.collection.iter().filter(|e| !e.is_seen()).count());
self.dirty = true;
@ -344,7 +345,7 @@ impl Component for StatusBar {
let offset = self.status.find('|').unwrap_or(self.status.len());
self.status.replace_range(..offset, &format!("{} ", m));
self.dirty = true;
self.mode = m;
self.mode = m.clone();
match m {
UIMode::Normal => {
self.height = 1;
@ -354,11 +355,12 @@ impl Component for StatusBar {
UIMode::Execute => {
self.height = 2;
},
_ => {},
};
},
UIEventType::ExInput(Key::Char(c)) => {
self.dirty = true;
self.ex_buffer.push(c);
self.ex_buffer.push(*c);
},
UIEventType::Resize => {
self.dirty = true;

View File

@ -1,13 +1,18 @@
/*! A parser module for user commands passed through the Ex mode.
*/
use std;
use nom::digit;
use nom::{digit, alpha};
named!(usize_c<usize>,
map_res!(map_res!(ws!(digit), std::str::from_utf8), std::str::FromStr::from_str));
named!(pub goto<usize>,
preceded!(tag!("b "),
call!(usize_c))
preceded!(tag!("b "),
call!(usize_c))
);
named!(pub sort<&str>,
preceded!(tag!("sort "),
map_res!(call!(alpha), std::str::from_utf8))
);

View File

@ -42,6 +42,9 @@ pub use self::components::*;
extern crate melib;
extern crate notify_rust;
#[macro_use]
extern crate chan;
extern crate chan_signal;
use melib::*;
use std::io::{Write, };
@ -54,13 +57,13 @@ use termion::event::{Key as TermionKey, };
use termion::input::TermRead;
use termion::screen::AlternateScreen;
extern crate chan;
#[macro_use]
extern crate nom;
use chan::Sender;
/// `ThreadEvent` encapsulates all of the possible values we need to transfer between our threads
/// to the main process.
#[derive(Debug)]
pub enum ThreadEvent {
/// User input.
Input(Key),
@ -84,6 +87,8 @@ pub enum UIEventType {
RefreshMailbox((usize,usize)),
//Quit?
Resize,
/// Force redraw.
Fork(std::process::Child),
ChangeMailbox(usize),
ChangeMode(UIMode),
Command(String),
@ -98,10 +103,11 @@ pub struct UIEvent {
pub event_type: UIEventType,
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum UIMode {
Normal,
Execute,
Fork,
}
impl fmt::Display for UIMode {
@ -109,6 +115,7 @@ impl fmt::Display for UIMode {
write!(f, "{}", match *self {
UIMode::Normal => { "NORMAL" },
UIMode::Execute => { "EX" },
UIMode::Fork => { "FORK" },
})
}
}
@ -131,12 +138,17 @@ pub struct Context {
/// Events queue that components send back to the state
replies: VecDeque<UIEvent>,
backends: Backends,
input_thread: chan::Sender<bool>,
}
impl Context {
pub fn replies(&mut self) -> Vec<UIEvent> {
self.replies.drain(0..).collect()
}
pub fn input_thread(&mut self) -> &mut chan::Sender<bool> {
&mut self.input_thread
}
}
@ -148,9 +160,12 @@ pub struct State<W: Write> {
grid: CellBuffer,
stdout: termion::screen::AlternateScreen<termion::raw::RawTerminal<W>>,
child: Option<std::process::Child>,
pub mode: UIMode,
sender: Sender<ThreadEvent>,
entities: Vec<Entity>,
pub context: Context,
}
impl<W: Write> Drop for State<W> {
@ -162,7 +177,7 @@ impl<W: Write> Drop for State<W> {
}
impl<W: Write> State<W> {
pub fn new(stdout: W, sender: Sender<ThreadEvent>) -> Self {
pub fn new(stdout: W, sender: Sender<ThreadEvent>, input_thread: chan::Sender<bool>) -> Self {
let settings = Settings::new();
let backends = Backends::new();
let stdout = AlternateScreen::from(stdout.into_raw_mode().unwrap());
@ -179,15 +194,20 @@ impl<W: Write> State<W> {
rows: rows,
grid: CellBuffer::new(cols, rows, Cell::with_char(' ')),
stdout: stdout,
child: None,
mode: UIMode::Normal,
sender: sender,
entities: Vec::with_capacity(1),
context: Context {
accounts: accounts,
backends: backends,
settings: settings,
dirty_areas: VecDeque::with_capacity(5),
replies: VecDeque::with_capacity(5),
input_thread: input_thread,
},
};
write!(s.stdout, "{}{}{}", cursor::Hide, clear::All, cursor::Goto(1,1)).unwrap();
@ -298,6 +318,12 @@ impl<W: Write> State<W> {
self.parse_command(cmd);
return;
},
UIEventType::Fork(child) => {
self.mode = UIMode::Fork;
self.child = Some(child);
self.stdout.flush().unwrap();
return;
},
_ => {},
}
/* inform each entity */
@ -320,6 +346,25 @@ impl<W: Write> State<W> {
self.rcv_event(UIEvent { id: 0, event_type: UIEventType::RefreshMailbox((account_idx, folder_idx)) });
}
}
pub fn try_wait_on_child(&mut self) -> Option<bool> {
if {
if let Some(ref mut c) = self.child {
let mut w = c.try_wait();
match w {
Ok(Some(_)) => { true },
Ok(None) => { false },
Err(_) => { return None; },
}
} else {
return None;
}
}
{
self.child = None;
return Some(true);
}
Some(false)
}
}
@ -391,11 +436,31 @@ impl From<TermionKey> for Key {
}
}
pub fn get_events(stdin: std::io::Stdin, mut closure: impl FnMut(Key)) -> (){
let stdin = stdin.lock();
for c in stdin.keys() {
if let Ok(k) = c {
closure(Key::from(k));
/*
* If we fork (for example start $EDITOR) we want the input-thread to stop reading from stdin. The
* best way I came up with right now is to send a signal to the thread that is read in the first
* input in stdin after the fork, and then the thread kills itself. The parent process spawns a new
* input-thread when the child returns.
*
* The main loop uses try_wait_on_child() to check if child has exited.
*/
pub fn get_events(stdin: std::io::Stdin, mut closure: impl FnMut(Key), mut exit: impl FnMut(), rx: chan::Receiver<bool>) -> (){
for c in stdin.keys() {
chan_select! {
default => {},
rx.recv() -> val => {
if let Some(true) = val {
exit();
return;
}
}
};
if let Ok(k) = c {
eprintln!("rcvd {:?}", k);
closure(Key::from(k));
}
}
}
}