Overhaul input thread
Remove raw/non raw distinction. Use a pipe for input thread commands and poll stdin/pipe for eventsasync
parent
839c1b1eb5
commit
b8261ee36a
|
@ -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 {
|
||||
#[inline]
|
||||
fn from(kind: &str) -> MeliError {
|
||||
|
|
25
src/bin.rs
25
src/bin.rs
|
@ -429,33 +429,28 @@ fn run_app() -> Result<()> {
|
|||
_ => {debug!(&r);}
|
||||
}
|
||||
match r.unwrap() {
|
||||
ThreadEvent::Input(Key::Ctrl('z')) => {
|
||||
ThreadEvent::Input((Key::Ctrl('z'), _)) if state.mode != UIMode::Embed => {
|
||||
state.switch_to_main_screen();
|
||||
//_thread_handler.join().expect("Couldn't join on the associated thread");
|
||||
let self_pid = nix::unistd::Pid::this();
|
||||
nix::sys::signal::kill(self_pid, nix::sys::signal::Signal::SIGSTOP).unwrap();
|
||||
state.switch_to_alternate_screen();
|
||||
state.restore_input();
|
||||
// BUG: thread sends input event after one received key
|
||||
state.update_size();
|
||||
state.render();
|
||||
state.redraw();
|
||||
},
|
||||
ThreadEvent::Input(Key::Ctrl('l')) => {
|
||||
/* Manual screen redraw */
|
||||
state.update_size();
|
||||
state.render();
|
||||
state.redraw();
|
||||
},
|
||||
ThreadEvent::InputRaw(raw_input @ (Key::Ctrl('l'), _)) => {
|
||||
ThreadEvent::Input(raw_input @ (Key::Ctrl('l'), _)) => {
|
||||
/* Manual screen redraw */
|
||||
state.update_size();
|
||||
state.render();
|
||||
state.redraw();
|
||||
if state.mode == UIMode::Embed {
|
||||
state.rcv_event(UIEvent::EmbedInput(raw_input));
|
||||
state.redraw();
|
||||
}
|
||||
},
|
||||
ThreadEvent::Input(k) => {
|
||||
ThreadEvent::Input((k, r)) => {
|
||||
match state.mode {
|
||||
UIMode::Normal => {
|
||||
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 => {
|
||||
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) => {
|
||||
state.refresh_event(*event);
|
||||
state.redraw();
|
||||
|
|
|
@ -176,9 +176,6 @@ impl MailcapEntry {
|
|||
a => a.to_string(),
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
{
|
||||
context.input_kill();
|
||||
}
|
||||
let cmd_string = format!("{} {}", cmd, args.join(" "));
|
||||
melib::log(
|
||||
format!("Executing: sh -c \"{}\"", cmd_string.replace("\"", "\\\"")),
|
||||
|
@ -236,7 +233,6 @@ impl MailcapEntry {
|
|||
}
|
||||
}
|
||||
context.replies.push_back(UIEvent::Fork(ForkType::Finished));
|
||||
context.restore_input();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
39
src/state.rs
39
src/state.rs
|
@ -37,6 +37,7 @@ use smallvec::SmallVec;
|
|||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::io::Write;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::thread;
|
||||
use termion::raw::IntoRawMode;
|
||||
use termion::screen::AlternateScreen;
|
||||
|
@ -45,6 +46,7 @@ use termion::{clear, cursor};
|
|||
pub type StateStdout = termion::screen::AlternateScreen<termion::raw::RawTerminal<std::io::Stdout>>;
|
||||
|
||||
struct InputHandler {
|
||||
pipe: (RawFd, RawFd),
|
||||
rx: Receiver<InputCommand>,
|
||||
tx: Sender<InputCommand>,
|
||||
}
|
||||
|
@ -54,36 +56,27 @@ impl InputHandler {
|
|||
/* 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
|
||||
* receive it and die. */
|
||||
let _ = self.rx.try_iter().count();
|
||||
//let _ = self.rx.try_iter().count();
|
||||
let rx = self.rx.clone();
|
||||
let pipe = self.pipe.0;
|
||||
thread::Builder::new()
|
||||
.name("input-thread".to_string())
|
||||
.spawn(move || {
|
||||
get_events(
|
||||
|k| {
|
||||
tx.send(ThreadEvent::Input(k)).unwrap();
|
||||
},
|
||||
|i| {
|
||||
tx.send(ThreadEvent::InputRaw(i)).unwrap();
|
||||
tx.send(ThreadEvent::Input(i)).unwrap();
|
||||
},
|
||||
&rx,
|
||||
None,
|
||||
pipe,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn kill(&self) {
|
||||
let _ = nix::unistd::write(self.pipe.1, &[1]);
|
||||
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.
|
||||
|
@ -117,14 +110,6 @@ impl Context {
|
|||
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) {
|
||||
self.input.restore(self.sender.clone());
|
||||
}
|
||||
|
@ -225,6 +210,8 @@ impl State {
|
|||
* stdin, see get_events() for details
|
||||
* */
|
||||
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 settings = Settings::new()?;
|
||||
let mut plugin_manager = PluginManager::new();
|
||||
|
@ -338,6 +325,7 @@ impl State {
|
|||
sender,
|
||||
receiver,
|
||||
input: InputHandler {
|
||||
pipe: input_thread_pipe,
|
||||
rx: input_thread.1,
|
||||
tx: input_thread.0,
|
||||
},
|
||||
|
@ -423,7 +411,6 @@ impl State {
|
|||
.unwrap();
|
||||
self.flush();
|
||||
self.stdout = None;
|
||||
self.context.input.kill();
|
||||
}
|
||||
|
||||
pub fn switch_to_alternate_screen(&mut self) {
|
||||
|
@ -937,16 +924,10 @@ impl State {
|
|||
return;
|
||||
}
|
||||
UIEvent::ChangeMode(m) => {
|
||||
if self.mode == UIMode::Embed {
|
||||
self.context.input_from_raw();
|
||||
}
|
||||
self.context
|
||||
.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::ChangeMode(m)))
|
||||
.unwrap();
|
||||
if m == UIMode::Embed {
|
||||
self.context.input_to_raw();
|
||||
}
|
||||
}
|
||||
UIEvent::Timer(id) if id == self.draw_rate_limit.id() => {
|
||||
self.draw_rate_limit.reset();
|
||||
|
|
|
@ -20,8 +20,7 @@
|
|||
*/
|
||||
|
||||
use crate::terminal::position::*;
|
||||
use melib::log;
|
||||
use melib::ERROR;
|
||||
use melib::{log, ERROR, error::*};
|
||||
|
||||
use nix::fcntl::{open, OFlag};
|
||||
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::unistd::{dup2, fork, ForkResult};
|
||||
use nix::{ioctl_none_bad, ioctl_write_ptr_bad};
|
||||
use std::ffi::CString;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
|
||||
use std::ffi::{CString, OsStr};
|
||||
use std::os::unix::{ffi::OsStrExt, io::{AsRawFd, FromRawFd, IntoRawFd}};
|
||||
|
||||
mod grid;
|
||||
|
||||
|
@ -56,7 +55,7 @@ pub fn create_pty(
|
|||
width: usize,
|
||||
height: usize,
|
||||
command: String,
|
||||
) -> nix::Result<Arc<Mutex<EmbedGrid>>> {
|
||||
) -> Result<Arc<Mutex<EmbedGrid>>> {
|
||||
// Open a new PTY master
|
||||
let master_fd = posix_openpt(OFlag::O_RDWR)?;
|
||||
|
||||
|
@ -115,9 +114,15 @@ pub fn create_pty(
|
|||
std::process::exit(-1);
|
||||
}
|
||||
}
|
||||
/* Find posix sh location, because POSIX shell is not always at /bin/sh */
|
||||
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[..])) {
|
||||
p.push("sh");
|
||||
if p.exists() {
|
||||
if let Err(e) = nix::unistd::execv(
|
||||
&CString::new("sh").unwrap(),
|
||||
&CString::new(p.as_os_str().as_bytes()).unwrap(),
|
||||
&[
|
||||
&CString::new("sh").unwrap(),
|
||||
&CString::new("-c").unwrap(),
|
||||
&CString::new(command.as_bytes()).unwrap(),
|
||||
],
|
||||
|
@ -125,7 +130,15 @@ pub fn create_pty(
|
|||
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);
|
||||
}
|
||||
Ok(ForkResult::Parent { child }) => child,
|
||||
|
|
|
@ -137,8 +137,7 @@ impl PartialEq<Key> for &Key {
|
|||
/// Keep track of whether we're accepting normal user input or a pasted string.
|
||||
enum InputMode {
|
||||
Normal,
|
||||
Paste,
|
||||
PasteRaw(Vec<u8>),
|
||||
Paste(Vec<u8>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -146,12 +145,10 @@ enum InputMode {
|
|||
pub enum InputCommand {
|
||||
/// Exit thread
|
||||
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;
|
||||
/*
|
||||
* If we fork (for example start $EDITOR) we want the input-thread to stop reading from stdin. The
|
||||
|
@ -163,101 +160,37 @@ use termion::input::TermReadEventsAndRaw;
|
|||
*/
|
||||
/// The thread function that listens for user input and forwards it to the main event loop.
|
||||
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>)),
|
||||
rx: &Receiver<InputCommand>,
|
||||
input: Option<(TermionEvent, Vec<u8>)>,
|
||||
new_command_fd: RawFd,
|
||||
) -> () {
|
||||
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 paste_buf = String::with_capacity(256);
|
||||
for c in input
|
||||
.map(|v| Ok(v))
|
||||
.into_iter()
|
||||
.chain(stdin.events_and_raw())
|
||||
{
|
||||
let mut stdin_iter = stdin.events_and_raw();
|
||||
'poll_while: while let Ok(_n_raw) = poll(&mut [new_command_pollfd, stdin_fd], -1) {
|
||||
//debug!(_n_raw);
|
||||
select! {
|
||||
default => {},
|
||||
recv(rx) -> cmd => {
|
||||
match cmd.unwrap() {
|
||||
InputCommand::Kill => return,
|
||||
InputCommand::NoRaw => {
|
||||
get_events(closure_nonraw, closure, rx, c.ok());
|
||||
return;
|
||||
}
|
||||
InputCommand::Raw => unreachable!(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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::PasteRaw(ref mut buf),
|
||||
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::PasteRaw(Vec::new());
|
||||
(Ok((TermionEvent::Unsupported(ref k), _)), _) if k.as_slice() == BRACKET_PASTE_START => {
|
||||
input_mode = InputMode::Paste(Vec::new());
|
||||
}
|
||||
(Ok((TermionEvent::Unsupported(ref k), _)), InputMode::PasteRaw(ref mut buf))
|
||||
(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());
|
||||
|
@ -270,6 +203,27 @@ pub fn get_events_raw(
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
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() {
|
||||
InputCommand::Kill => return,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Key {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
|
|
|
@ -60,9 +60,8 @@ pub enum StatusEvent {
|
|||
pub enum ThreadEvent {
|
||||
NewThread(thread::ThreadId, String),
|
||||
/// User input.
|
||||
Input(Key),
|
||||
Input((Key, Vec<u8>)),
|
||||
/// User input and input as raw bytes.
|
||||
InputRaw((Key, Vec<u8>)),
|
||||
/// A watched Mailbox has been refreshed.
|
||||
RefreshMailbox(Box<RefreshEvent>),
|
||||
UIEvent(UIEvent),
|
||||
|
|
Loading…
Reference in New Issue