Add child forking functionality
parent
ac334b09b1
commit
b35407bc7f
|
@ -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 = "*" }
|
||||
|
|
95
src/bin.rs
95
src/bin.rs
|
@ -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;},
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,3 +11,4 @@ chan = "0.1.21"
|
|||
notify = "4.0.1"
|
||||
notify-rust = "^3"
|
||||
nom = "3.2.0"
|
||||
chan-signal = "0.3.1"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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))
|
||||
);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue