Overhaul input thread

Remove raw/non raw distinction.

Use a pipe for input thread commands and poll stdin/pipe for events
master
Manos Pitsidianakis 2020-05-29 15:35:29 +03:00
parent 839c1b1eb5
commit b8261ee36a
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
7 changed files with 107 additions and 164 deletions

View File

@ -199,6 +199,13 @@ impl From<std::ffi::NulError> for MeliError {
} }
} }
impl From<nix::Error> for MeliError {
#[inline]
fn from(kind: nix::Error) -> MeliError {
MeliError::new(format!("{}", kind)).set_source(Some(Arc::new(kind)))
}
}
impl From<&str> for MeliError { impl From<&str> for MeliError {
#[inline] #[inline]
fn from(kind: &str) -> MeliError { fn from(kind: &str) -> MeliError {

View File

@ -429,33 +429,28 @@ fn run_app() -> Result<()> {
_ => {debug!(&r);} _ => {debug!(&r);}
} }
match r.unwrap() { match r.unwrap() {
ThreadEvent::Input(Key::Ctrl('z')) => { ThreadEvent::Input((Key::Ctrl('z'), _)) if state.mode != UIMode::Embed => {
state.switch_to_main_screen(); state.switch_to_main_screen();
//_thread_handler.join().expect("Couldn't join on the associated thread"); //_thread_handler.join().expect("Couldn't join on the associated thread");
let self_pid = nix::unistd::Pid::this(); let self_pid = nix::unistd::Pid::this();
nix::sys::signal::kill(self_pid, nix::sys::signal::Signal::SIGSTOP).unwrap(); nix::sys::signal::kill(self_pid, nix::sys::signal::Signal::SIGSTOP).unwrap();
state.switch_to_alternate_screen(); state.switch_to_alternate_screen();
state.restore_input();
// BUG: thread sends input event after one received key // BUG: thread sends input event after one received key
state.update_size(); state.update_size();
state.render(); state.render();
state.redraw(); state.redraw();
}, },
ThreadEvent::Input(Key::Ctrl('l')) => { ThreadEvent::Input(raw_input @ (Key::Ctrl('l'), _)) => {
/* Manual screen redraw */
state.update_size();
state.render();
state.redraw();
},
ThreadEvent::InputRaw(raw_input @ (Key::Ctrl('l'), _)) => {
/* Manual screen redraw */ /* Manual screen redraw */
state.update_size(); state.update_size();
state.render(); state.render();
state.redraw(); state.redraw();
if state.mode == UIMode::Embed {
state.rcv_event(UIEvent::EmbedInput(raw_input)); state.rcv_event(UIEvent::EmbedInput(raw_input));
state.redraw(); state.redraw();
}
}, },
ThreadEvent::Input(k) => { ThreadEvent::Input((k, r)) => {
match state.mode { match state.mode {
UIMode::Normal => { UIMode::Normal => {
match k { match k {
@ -504,17 +499,15 @@ fn run_app() -> Result<()> {
}, },
} }
}, },
UIMode::Embed => state.redraw(), UIMode::Embed => {
state.rcv_event(UIEvent::EmbedInput((k,r)));
state.redraw();
},
UIMode::Fork => { UIMode::Fork => {
break 'inner; // `goto` 'reap loop, and wait on child. break 'inner; // `goto` 'reap loop, and wait on child.
}, },
} }
}, },
ThreadEvent::InputRaw(raw_input) => {
state.rcv_event(UIEvent::EmbedInput(raw_input));
state.redraw();
},
ThreadEvent::RefreshMailbox(event) => { ThreadEvent::RefreshMailbox(event) => {
state.refresh_event(*event); state.refresh_event(*event);
state.redraw(); state.redraw();

View File

@ -176,9 +176,6 @@ impl MailcapEntry {
a => a.to_string(), a => a.to_string(),
}) })
.collect::<Vec<String>>(); .collect::<Vec<String>>();
{
context.input_kill();
}
let cmd_string = format!("{} {}", cmd, args.join(" ")); let cmd_string = format!("{} {}", cmd, args.join(" "));
melib::log( melib::log(
format!("Executing: sh -c \"{}\"", cmd_string.replace("\"", "\\\"")), format!("Executing: sh -c \"{}\"", cmd_string.replace("\"", "\\\"")),
@ -236,7 +233,6 @@ impl MailcapEntry {
} }
} }
context.replies.push_back(UIEvent::Fork(ForkType::Finished)); context.replies.push_back(UIEvent::Fork(ForkType::Finished));
context.restore_input();
Ok(()) Ok(())
} }
} }

View File

@ -37,6 +37,7 @@ use smallvec::SmallVec;
use std::collections::HashMap; use std::collections::HashMap;
use std::env; use std::env;
use std::io::Write; use std::io::Write;
use std::os::unix::io::RawFd;
use std::thread; use std::thread;
use termion::raw::IntoRawMode; use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen; use termion::screen::AlternateScreen;
@ -45,6 +46,7 @@ use termion::{clear, cursor};
pub type StateStdout = termion::screen::AlternateScreen<termion::raw::RawTerminal<std::io::Stdout>>; pub type StateStdout = termion::screen::AlternateScreen<termion::raw::RawTerminal<std::io::Stdout>>;
struct InputHandler { struct InputHandler {
pipe: (RawFd, RawFd),
rx: Receiver<InputCommand>, rx: Receiver<InputCommand>,
tx: Sender<InputCommand>, tx: Sender<InputCommand>,
} }
@ -54,36 +56,27 @@ impl InputHandler {
/* Clear channel without blocking. switch_to_main_screen() issues a kill when /* Clear channel without blocking. switch_to_main_screen() issues a kill when
* returning from a fork and there's no input thread, so the newly created thread will * returning from a fork and there's no input thread, so the newly created thread will
* receive it and die. */ * receive it and die. */
let _ = self.rx.try_iter().count(); //let _ = self.rx.try_iter().count();
let rx = self.rx.clone(); let rx = self.rx.clone();
let pipe = self.pipe.0;
thread::Builder::new() thread::Builder::new()
.name("input-thread".to_string()) .name("input-thread".to_string())
.spawn(move || { .spawn(move || {
get_events( get_events(
|k| {
tx.send(ThreadEvent::Input(k)).unwrap();
},
|i| { |i| {
tx.send(ThreadEvent::InputRaw(i)).unwrap(); tx.send(ThreadEvent::Input(i)).unwrap();
}, },
&rx, &rx,
None, pipe,
) )
}) })
.unwrap(); .unwrap();
} }
fn kill(&self) { fn kill(&self) {
let _ = nix::unistd::write(self.pipe.1, &[1]);
self.tx.send(InputCommand::Kill).unwrap(); self.tx.send(InputCommand::Kill).unwrap();
} }
fn switch_to_raw(&self) {
self.tx.send(InputCommand::Raw).unwrap();
}
fn switch_from_raw(&self) {
self.tx.send(InputCommand::NoRaw).unwrap();
}
} }
/// A context container for loaded settings, accounts, UI changes, etc. /// A context container for loaded settings, accounts, UI changes, etc.
@ -117,14 +110,6 @@ impl Context {
self.input.kill(); self.input.kill();
} }
pub fn input_from_raw(&self) {
self.input.switch_from_raw();
}
pub fn input_to_raw(&self) {
self.input.switch_to_raw();
}
pub fn restore_input(&self) { pub fn restore_input(&self) {
self.input.restore(self.sender.clone()); self.input.restore(self.sender.clone());
} }
@ -225,6 +210,8 @@ impl State {
* stdin, see get_events() for details * stdin, see get_events() for details
* */ * */
let input_thread = unbounded(); let input_thread = unbounded();
let input_thread_pipe = nix::unistd::pipe()
.map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync + 'static>)?;
let mut backends = Backends::new(); let mut backends = Backends::new();
let settings = Settings::new()?; let settings = Settings::new()?;
let mut plugin_manager = PluginManager::new(); let mut plugin_manager = PluginManager::new();
@ -338,6 +325,7 @@ impl State {
sender, sender,
receiver, receiver,
input: InputHandler { input: InputHandler {
pipe: input_thread_pipe,
rx: input_thread.1, rx: input_thread.1,
tx: input_thread.0, tx: input_thread.0,
}, },
@ -423,7 +411,6 @@ impl State {
.unwrap(); .unwrap();
self.flush(); self.flush();
self.stdout = None; self.stdout = None;
self.context.input.kill();
} }
pub fn switch_to_alternate_screen(&mut self) { pub fn switch_to_alternate_screen(&mut self) {
@ -937,16 +924,10 @@ impl State {
return; return;
} }
UIEvent::ChangeMode(m) => { UIEvent::ChangeMode(m) => {
if self.mode == UIMode::Embed {
self.context.input_from_raw();
}
self.context self.context
.sender .sender
.send(ThreadEvent::UIEvent(UIEvent::ChangeMode(m))) .send(ThreadEvent::UIEvent(UIEvent::ChangeMode(m)))
.unwrap(); .unwrap();
if m == UIMode::Embed {
self.context.input_to_raw();
}
} }
UIEvent::Timer(id) if id == self.draw_rate_limit.id() => { UIEvent::Timer(id) if id == self.draw_rate_limit.id() => {
self.draw_rate_limit.reset(); self.draw_rate_limit.reset();

View File

@ -20,8 +20,7 @@
*/ */
use crate::terminal::position::*; use crate::terminal::position::*;
use melib::log; use melib::{log, ERROR, error::*};
use melib::ERROR;
use nix::fcntl::{open, OFlag}; use nix::fcntl::{open, OFlag};
use nix::libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO}; use nix::libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
@ -29,8 +28,8 @@ use nix::pty::{grantpt, posix_openpt, ptsname, unlockpt, Winsize};
use nix::sys::stat; use nix::sys::stat;
use nix::unistd::{dup2, fork, ForkResult}; use nix::unistd::{dup2, fork, ForkResult};
use nix::{ioctl_none_bad, ioctl_write_ptr_bad}; use nix::{ioctl_none_bad, ioctl_write_ptr_bad};
use std::ffi::CString; use std::ffi::{CString, OsStr};
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd}; use std::os::unix::{ffi::OsStrExt, io::{AsRawFd, FromRawFd, IntoRawFd}};
mod grid; mod grid;
@ -56,7 +55,7 @@ pub fn create_pty(
width: usize, width: usize,
height: usize, height: usize,
command: String, command: String,
) -> nix::Result<Arc<Mutex<EmbedGrid>>> { ) -> Result<Arc<Mutex<EmbedGrid>>> {
// Open a new PTY master // Open a new PTY master
let master_fd = posix_openpt(OFlag::O_RDWR)?; let master_fd = posix_openpt(OFlag::O_RDWR)?;
@ -115,17 +114,31 @@ pub fn create_pty(
std::process::exit(-1); std::process::exit(-1);
} }
} }
if let Err(e) = nix::unistd::execv( /* Find posix sh location, because POSIX shell is not always at /bin/sh */
&CString::new("sh").unwrap(), let path_var = std::process::Command::new("getconf").args(&["PATH"]).output()?.stdout;
&[ for mut p in std::env::split_paths(&OsStr::from_bytes(&path_var[..])) {
&CString::new("-c").unwrap(), p.push("sh");
&CString::new(command.as_bytes()).unwrap(), if p.exists() {
], if let Err(e) = nix::unistd::execv(
) { &CString::new(p.as_os_str().as_bytes()).unwrap(),
log(format!("Could not execute `{}`: {}", command, e,), ERROR); &[
std::process::exit(-1); &CString::new("sh").unwrap(),
&CString::new("-c").unwrap(),
&CString::new(command.as_bytes()).unwrap(),
],
) {
log(format!("Could not execute `{}`: {}", command, e,), ERROR);
std::process::exit(-1);
}
}
} }
/* This path shouldn't be executed. */ log(
format!(
"Could not execute `{}`: did not find the standard POSIX sh shell in PATH = {}",
command, String::from_utf8_lossy(&path_var),
),
ERROR,
);
std::process::exit(-1); std::process::exit(-1);
} }
Ok(ForkResult::Parent { child }) => child, Ok(ForkResult::Parent { child }) => child,

View File

@ -137,8 +137,7 @@ impl PartialEq<Key> for &Key {
/// Keep track of whether we're accepting normal user input or a pasted string. /// Keep track of whether we're accepting normal user input or a pasted string.
enum InputMode { enum InputMode {
Normal, Normal,
Paste, Paste(Vec<u8>),
PasteRaw(Vec<u8>),
} }
#[derive(Debug)] #[derive(Debug)]
@ -146,12 +145,10 @@ enum InputMode {
pub enum InputCommand { pub enum InputCommand {
/// Exit thread /// Exit thread
Kill, Kill,
/// Send raw bytes as well
Raw,
/// Ignore raw bytes
NoRaw,
} }
use nix::poll::{poll, PollFd, PollFlags};
use std::os::unix::io::{AsRawFd, RawFd};
use termion::input::TermReadEventsAndRaw; use termion::input::TermReadEventsAndRaw;
/* /*
* If we fork (for example start $EDITOR) we want the input-thread to stop reading from stdin. The * If we fork (for example start $EDITOR) we want the input-thread to stop reading from stdin. The
@ -163,111 +160,68 @@ use termion::input::TermReadEventsAndRaw;
*/ */
/// The thread function that listens for user input and forwards it to the main event loop. /// The thread function that listens for user input and forwards it to the main event loop.
pub fn get_events( pub fn get_events(
mut closure: impl FnMut(Key),
closure_raw: impl FnMut((Key, Vec<u8>)),
rx: &Receiver<InputCommand>,
input: Option<(TermionEvent, Vec<u8>)>,
) -> () {
let stdin = std::io::stdin();
let mut input_mode = InputMode::Normal;
let mut paste_buf = String::with_capacity(256);
for c in input
.map(|v| Ok(v))
.into_iter()
.chain(stdin.events_and_raw())
{
select! {
default => {},
recv(rx) -> cmd => {
match cmd.unwrap() {
InputCommand::Kill => return,
InputCommand::Raw => {
get_events_raw(closure, closure_raw, rx, c.ok());
return;
}
InputCommand::NoRaw => unreachable!(),
}
}
};
match c {
Ok((TermionEvent::Key(k), _)) if input_mode == InputMode::Normal => {
closure(Key::from(k));
}
Ok((TermionEvent::Key(TermionKey::Char(k)), _)) if input_mode == InputMode::Paste => {
paste_buf.push(k);
}
Ok((TermionEvent::Unsupported(ref k), _)) if k.as_slice() == BRACKET_PASTE_START => {
input_mode = InputMode::Paste;
}
Ok((TermionEvent::Unsupported(ref k), _)) if k.as_slice() == BRACKET_PASTE_END => {
input_mode = InputMode::Normal;
let ret = Key::from(&paste_buf);
paste_buf.clear();
closure(ret);
}
_ => {} // Mouse events or errors.
}
}
}
/// Same as `get_events` but also forwards the raw bytes of the input as well
pub fn get_events_raw(
closure_nonraw: impl FnMut(Key),
mut closure: impl FnMut((Key, Vec<u8>)), mut closure: impl FnMut((Key, Vec<u8>)),
rx: &Receiver<InputCommand>, rx: &Receiver<InputCommand>,
input: Option<(TermionEvent, Vec<u8>)>, new_command_fd: RawFd,
) -> () { ) -> () {
let stdin = std::io::stdin(); let stdin = std::io::stdin();
let stdin_fd = PollFd::new(std::io::stdin().as_raw_fd(), PollFlags::POLLIN);
let new_command_pollfd = nix::poll::PollFd::new(new_command_fd, nix::poll::PollFlags::POLLIN);
let mut input_mode = InputMode::Normal; let mut input_mode = InputMode::Normal;
let mut paste_buf = String::with_capacity(256); let mut paste_buf = String::with_capacity(256);
for c in input let mut stdin_iter = stdin.events_and_raw();
.map(|v| Ok(v)) 'poll_while: while let Ok(_n_raw) = poll(&mut [new_command_pollfd, stdin_fd], -1) {
.into_iter() //debug!(_n_raw);
.chain(stdin.events_and_raw())
{
select! { select! {
default => {}, default => {
if stdin_fd.revents().is_some() {
if let Some(c) = stdin_iter.next(){
match (c, &mut input_mode) {
(Ok((TermionEvent::Key(k), bytes)), InputMode::Normal) => {
closure((Key::from(k), bytes));
}
(
Ok((TermionEvent::Key(TermionKey::Char(k)), ref mut bytes)), InputMode::Paste(ref mut buf),
) => {
paste_buf.push(k);
let bytes = std::mem::replace(bytes, Vec::new());
buf.extend(bytes.into_iter());
}
(Ok((TermionEvent::Unsupported(ref k), _)), _) if k.as_slice() == BRACKET_PASTE_START => {
input_mode = InputMode::Paste(Vec::new());
}
(Ok((TermionEvent::Unsupported(ref k), _)), InputMode::Paste(ref mut buf))
if k.as_slice() == BRACKET_PASTE_END =>
{
let buf = std::mem::replace(buf, Vec::new());
input_mode = InputMode::Normal;
let ret = Key::from(&paste_buf);
paste_buf.clear();
closure((ret, buf));
}
_ => {} // Mouse events or errors.
}
}
}
},
recv(rx) -> cmd => { recv(rx) -> cmd => {
use nix::sys::time::TimeValLike;
let mut buf = [0;2];
//debug!("get_events_raw will nix::unistd::read");
let mut read_fd_set = nix::sys::select::FdSet::new();
read_fd_set.insert(new_command_fd);
let mut error_fd_set = nix::sys::select::FdSet::new();
error_fd_set.insert(new_command_fd);
let timeval: nix::sys::time::TimeSpec = nix::sys::time::TimeSpec::seconds(2);
if nix::sys::select::pselect(None, Some(&mut read_fd_set), None, Some(&mut error_fd_set), Some(&timeval), None).is_err() || error_fd_set.highest() == Some(new_command_fd) || read_fd_set.highest() != Some(new_command_fd) {
continue 'poll_while;
};
let _ = nix::unistd::read(new_command_fd, buf.as_mut());
match cmd.unwrap() { match cmd.unwrap() {
InputCommand::Kill => return, InputCommand::Kill => return,
InputCommand::NoRaw => {
get_events(closure_nonraw, closure, rx, c.ok());
return;
}
InputCommand::Raw => unreachable!(),
} }
} }
}; };
match (c, &mut input_mode) {
(Ok((TermionEvent::Key(k), bytes)), InputMode::Normal) => {
closure((Key::from(k), bytes));
}
(
Ok((TermionEvent::Key(TermionKey::Char(k)), ref mut bytes)),
InputMode::PasteRaw(ref mut buf),
) => {
paste_buf.push(k);
let bytes = std::mem::replace(bytes, Vec::new());
buf.extend(bytes.into_iter());
}
(Ok((TermionEvent::Unsupported(ref k), _)), _)
if k.as_slice() == BRACKET_PASTE_START =>
{
input_mode = InputMode::PasteRaw(Vec::new());
}
(Ok((TermionEvent::Unsupported(ref k), _)), InputMode::PasteRaw(ref mut buf))
if k.as_slice() == BRACKET_PASTE_END =>
{
let buf = std::mem::replace(buf, Vec::new());
input_mode = InputMode::Normal;
let ret = Key::from(&paste_buf);
paste_buf.clear();
closure((ret, buf));
}
_ => {} // Mouse events or errors.
}
} }
} }

View File

@ -60,9 +60,8 @@ pub enum StatusEvent {
pub enum ThreadEvent { pub enum ThreadEvent {
NewThread(thread::ThreadId, String), NewThread(thread::ThreadId, String),
/// User input. /// User input.
Input(Key), Input((Key, Vec<u8>)),
/// User input and input as raw bytes. /// User input and input as raw bytes.
InputRaw((Key, Vec<u8>)),
/// A watched Mailbox has been refreshed. /// A watched Mailbox has been refreshed.
RefreshMailbox(Box<RefreshEvent>), RefreshMailbox(Box<RefreshEvent>),
UIEvent(UIEvent), UIEvent(UIEvent),