diff --git a/README.md b/README.md index ded459c..5741275 100644 --- a/README.md +++ b/README.md @@ -126,9 +126,6 @@ To enable just TLS support, or just Tor support, use `--features`: - [ ] ctrl-c while telneting kills phetch - [ ] ctrl-c in load() not yet implemented -- [ ] ctrl-c in download fails to return to listening state - because of termion bug: - https://gitlab.redox-os.org/redox-os/termion/issues/168 - [ ] gopher://tilde.black/1/users/genin/ ## future features diff --git a/src/gopher.rs b/src/gopher.rs index f43b3c2..faaff50 100644 --- a/src/gopher.rs +++ b/src/gopher.rs @@ -4,6 +4,7 @@ //! URL parsing that recognizes different protocols like telnet and //! IPv6 addresses. +use crate::ui::{self, Key}; use std::{ fs, io::{Read, Result, Write}, @@ -12,7 +13,6 @@ use std::{ os::unix::fs::OpenOptionsExt, time::Duration, }; -use termion::input::TermRead; #[cfg(feature = "tor")] use tor_stream::TorStream; @@ -106,10 +106,16 @@ fn clean_response(res: &mut String) { }) } -/// Downloads a binary to disk. Allows canceling with Ctrl-c. +/// Downloads a binary to disk. Allows canceling with Ctrl-c, but it's +/// kind of hacky - needs the UI receiver passed in. /// Returns a tuple of: /// (path it was saved to, the size in bytes) -pub fn download_url(url: &str, tls: bool, tor: bool) -> Result<(String, usize)> { +pub fn download_url( + url: &str, + tls: bool, + tor: bool, + chan: ui::KeyReceiver, +) -> Result<(String, usize)> { let u = parse_url(url); let filename = u .sel @@ -119,8 +125,6 @@ pub fn download_url(url: &str, tls: bool, tor: bool) -> Result<(String, usize)> .ok_or_else(|| error!("Bad download filename: {}", u.sel))?; let mut path = std::path::PathBuf::from("."); path.push(filename); - let stdin = termion::async_stdin(); - let mut keys = stdin.keys(); let mut stream = request(u.host, u.port, u.sel, tls, tor)?; let mut file = fs::OpenOptions::new() @@ -138,11 +142,13 @@ pub fn download_url(url: &str, tls: bool, tor: bool) -> Result<(String, usize)> } bytes += count; file.write_all(&buf[..count])?; - if let Some(Ok(termion::event::Key::Ctrl('c'))) = keys.next() { - if path.exists() { - fs::remove_file(path)?; + if let Ok(chan) = chan.lock() { + if let Ok(Key::Ctrl('c')) = chan.try_recv() { + if path.exists() { + fs::remove_file(path)?; + } + return Err(error!("Download cancelled")); } - return Err(error!("Download cancelled")); } } Ok((filename.to_string(), bytes)) diff --git a/src/ui.rs b/src/ui.rs index 0d58f5a..966c3ec 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -30,7 +30,10 @@ use std::{ cell::RefCell, io::{stdin, stdout, Result, Stdout, Write}, process::{self, Stdio}, - sync::mpsc, + sync::{ + mpsc::{channel, Receiver}, + Arc, Mutex, + }, thread, time::Duration, }; @@ -43,6 +46,9 @@ use termion::{ /// Alias for a termion Key event. pub type Key = termion::event::Key; +/// Channel to receive Key events on. +pub type KeyReceiver = Arc>>; + /// How many lines to jump by when using page up/down. pub const SCROLL_LINES: usize = 15; @@ -78,6 +84,8 @@ pub struct UI { config: Config, /// Reference to our wrapped Stdout. out: RefCell>, + /// Channel where UI events are sent. + keys: KeyReceiver, } impl UI { @@ -103,6 +111,7 @@ impl UI { config, status: String::new(), out: RefCell::new(out), + keys: Self::spawn_keyboard_listener(), } } @@ -212,8 +221,9 @@ impl UI { fn download(&mut self, url: &str) -> Result<()> { let url = url.to_string(); let (tls, tor) = (self.config.tls, self.config.tor); + let chan = self.keys.clone(); self.spinner(&format!("Downloading {}", url), move || { - gopher::download_url(&url, tls, tor) + gopher::download_url(&url, tls, tor, chan) }) .and_then(|res| res) .and_then(|(path, bytes)| { @@ -292,7 +302,7 @@ impl UI { ) -> Result { let req = thread::spawn(work); - let (tx, rx) = mpsc::channel(); + let (tx, rx) = channel(); let label = label.to_string(); let rows = self.rows() as u16; thread::spawn(move || loop { @@ -412,7 +422,7 @@ impl UI { .expect(ERR_STDOUT); out.flush().expect(ERR_STDOUT); - if let Some(Ok(key)) = stdin().keys().next() { + if let Ok(key) = self.keys.lock().unwrap().recv() { match key { Key::Char('\n') => true, Key::Char('y') | Key::Char('Y') => true, @@ -442,39 +452,36 @@ impl UI { .expect(ERR_STDOUT); out.flush().expect(ERR_STDOUT); - for k in stdin().keys() { - if let Ok(key) = k { - match key { - Key::Char('\n') => { - write!( - out, - "{}{}", - terminal::ClearCurrentLine, - terminal::HideCursor - ) - .expect(ERR_STDOUT); - out.flush().expect(ERR_STDOUT); - return Some(input); - } - Key::Char(c) => input.push(c), - Key::Esc | Key::Ctrl('c') => { - write!( - out, - "{}{}", - terminal::ClearCurrentLine, - terminal::HideCursor - ) - .expect(ERR_STDOUT); - out.flush().expect(ERR_STDOUT); - return None; - } - Key::Backspace | Key::Delete => { - input.pop(); - } - _ => {} + let keys = self.keys.lock().unwrap(); + for key in keys.iter() { + match key { + Key::Char('\n') => { + write!( + out, + "{}{}", + terminal::ClearCurrentLine, + terminal::HideCursor + ) + .expect(ERR_STDOUT); + out.flush().expect(ERR_STDOUT); + return Some(input); } - } else { - break; + Key::Char(c) => input.push(c), + Key::Esc | Key::Ctrl('c') => { + write!( + out, + "{}{}", + terminal::ClearCurrentLine, + terminal::HideCursor + ) + .expect(ERR_STDOUT); + out.flush().expect(ERR_STDOUT); + return None; + } + Key::Backspace | Key::Delete => { + input.pop(); + } + _ => {} } write!( @@ -516,20 +523,29 @@ impl UI { /// Asks the current View to process user input and produce an Action. fn process_view_input(&mut self) -> Action { if let Some(view) = self.views.get_mut(self.focused) { - if let Ok(key) = stdin() - .keys() - .nth(0) - .ok_or_else(|| Action::Error("stdin.keys() error".to_string())) - { - if let Ok(key) = key { - return view.respond(key); - } + if let Ok(key) = self.keys.lock().unwrap().recv() { + return view.respond(key); } } Action::Error("No Gopher page loaded.".into()) } + /// Listen for keyboard events and send them along. + fn spawn_keyboard_listener() -> KeyReceiver { + let (sender, receiver) = channel(); + + thread::spawn(move || { + for event in stdin().keys() { + if let Ok(key) = event { + sender.send(key).unwrap(); + } + } + }); + + Arc::new(Mutex::new(receiver)) + } + /// Ctrl-Z: Suspend Unix process w/ SIGTSTP. fn suspend(&mut self) { let mut out = self.out.borrow_mut();