use channels for keyboard events

this fixes the 'cancel download' bug
pull/15/head
chris west 4 years ago
parent 2ccd55dad7
commit 1b1d1fc8f3

@ -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

@ -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))

@ -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<Mutex<Receiver<Key>>>;
/// 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<RawTerminal<Stdout>>,
/// 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<T> {
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();

Loading…
Cancel
Save