diff --git a/melib/src/error.rs b/melib/src/error.rs index 32e50b4a..92028cdd 100644 --- a/melib/src/error.rs +++ b/melib/src/error.rs @@ -199,6 +199,13 @@ impl From for MeliError { } } +impl From 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 { diff --git a/src/bin.rs b/src/bin.rs index b9764fbe..95008d95 100644 --- a/src/bin.rs +++ b/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(); diff --git a/src/mailcap.rs b/src/mailcap.rs index 310eb884..31b2abae 100644 --- a/src/mailcap.rs +++ b/src/mailcap.rs @@ -176,9 +176,6 @@ impl MailcapEntry { a => a.to_string(), }) .collect::>(); - { - 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(()) } } diff --git a/src/state.rs b/src/state.rs index 250c5ad3..08740708 100644 --- a/src/state.rs +++ b/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>; struct InputHandler { + pipe: (RawFd, RawFd), rx: Receiver, tx: Sender, } @@ -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)?; 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(); diff --git a/src/terminal/embed.rs b/src/terminal/embed.rs index 456065d5..33e3e95c 100644 --- a/src/terminal/embed.rs +++ b/src/terminal/embed.rs @@ -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>> { +) -> Result>> { // Open a new PTY master let master_fd = posix_openpt(OFlag::O_RDWR)?; @@ -115,17 +114,31 @@ pub fn create_pty( std::process::exit(-1); } } - if let Err(e) = nix::unistd::execv( - &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); + /* 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(p.as_os_str().as_bytes()).unwrap(), + &[ + &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); } Ok(ForkResult::Parent { child }) => child, diff --git a/src/terminal/keys.rs b/src/terminal/keys.rs index 433e75e4..aedbef8a 100644 --- a/src/terminal/keys.rs +++ b/src/terminal/keys.rs @@ -137,8 +137,7 @@ impl PartialEq for &Key { /// Keep track of whether we're accepting normal user input or a pasted string. enum InputMode { Normal, - Paste, - PasteRaw(Vec), + Paste(Vec), } #[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,111 +160,68 @@ 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)), - rx: &Receiver, - input: Option<(TermionEvent, Vec)>, -) -> () { - 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)), rx: &Receiver, - input: Option<(TermionEvent, Vec)>, + 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 => {}, + 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 => { + 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, - 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. - } } } diff --git a/src/types.rs b/src/types.rs index 752fc61e..d497c562 100644 --- a/src/types.rs +++ b/src/types.rs @@ -60,9 +60,8 @@ pub enum StatusEvent { pub enum ThreadEvent { NewThread(thread::ThreadId, String), /// User input. - Input(Key), + Input((Key, Vec)), /// User input and input as raw bytes. - InputRaw((Key, Vec)), /// A watched Mailbox has been refreshed. RefreshMailbox(Box), UIEvent(UIEvent),