Move resize-specific behaviour to backend

This commit is contained in:
Alexandre Bury 2018-07-18 18:14:25 -07:00
parent 64d0a66b5e
commit fd75249633
2 changed files with 56 additions and 30 deletions

View File

@ -6,7 +6,7 @@ use std::ffi::CString;
use std::fs::File; use std::fs::File;
use std::io; use std::io;
use std::io::Write; use std::io::Write;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::thread;
@ -40,30 +40,27 @@ pub struct Backend {
struct InputParser { struct InputParser {
key_codes: HashMap<i32, Event>, key_codes: HashMap<i32, Event>,
last_mouse_button: Option<MouseButton>, last_mouse_button: Option<MouseButton>,
event_sink: Sender<Option<Event>>, input_buffer: Option<Event>,
} }
impl InputParser { impl InputParser {
fn new(event_sink: Sender<Option<Event>>) -> Self { fn new() -> Self {
InputParser { InputParser {
key_codes: initialize_keymap(), key_codes: initialize_keymap(),
last_mouse_button: None, last_mouse_button: None,
event_sink, input_buffer: None,
} }
} }
fn parse_next(&mut self) { fn parse_next(&mut self) -> Option<Event> {
if let Some(event) = self.input_buffer.take() {
return Some(event);
}
let ch: i32 = ncurses::getch(); let ch: i32 = ncurses::getch();
if ch == 410 {
// Ignore resize events.
self.parse_next();
return;
}
if ch == -1 { if ch == -1 {
self.event_sink.send(None); return None;
return;
} }
// Is it a UTF-8 starting point? // Is it a UTF-8 starting point?
@ -77,7 +74,8 @@ impl InputParser {
} else { } else {
self.parse_ncurses_char(ch) self.parse_ncurses_char(ch)
}; };
self.event_sink.send(Some(event));
Some(event)
} }
fn parse_ncurses_char(&mut self, ch: i32) -> Event { fn parse_ncurses_char(&mut self, ch: i32) -> Event {
@ -146,7 +144,7 @@ impl InputParser {
if event.is_none() { if event.is_none() {
event = Some(e); event = Some(e);
} else { } else {
self.event_sink.send(Some(make_event(e))); self.input_buffer = Some(make_event(e));
} }
}); });
} }
@ -185,7 +183,6 @@ fn write_to_tty(bytes: &[u8]) -> io::Result<()> {
impl Backend { impl Backend {
pub fn init() -> Box<backend::Backend> { pub fn init() -> Box<backend::Backend> {
let signals = Some(Signals::new(&[libc::SIGWINCH]).unwrap()); let signals = Some(Signals::new(&[libc::SIGWINCH]).unwrap());
// Change the locale. // Change the locale.
@ -312,37 +309,61 @@ impl backend::Backend for Backend {
let resize_running = Arc::clone(&running); let resize_running = Arc::clone(&running);
let resize_sender = event_sink.clone(); let resize_sender = event_sink.clone();
let signals = self.signals.take().unwrap(); let signals = self.signals.take().unwrap();
let resize_requests = input_request.clone();
thread::spawn(move || { thread::spawn(move || {
// This thread will listen to SIGWINCH events and report them. // This thread will listen to SIGWINCH events and report them.
while resize_running.load(Ordering::Relaxed) { while resize_running.load(Ordering::Relaxed) {
// We know it will only contain SIGWINCH signals, so no need to check. // We know it will only contain SIGWINCH signals, so no need to check.
for _ in signals.pending() { if signals.wait().count() > 0 {
// Tell ncurses about the new terminal size. // Tell ncurses about the new terminal size.
// Well, do the actual resizing later on, in the main thread. // Well, do the actual resizing later on, in the main thread.
// Ncurses isn't really thread-safe so calling resize_term() can crash // Ncurses isn't really thread-safe so calling resize_term() can crash
// other calls like clear() or refresh(). // other calls like clear() or refresh().
needs_resize.store(true, Ordering::Relaxed); needs_resize.store(true, Ordering::Relaxed);
resize_sender.send(Some(Event::WindowResize)); resize_sender.send(Some(Event::WindowResize));
// We've sent the message.
// This means Cursive was listening, and will now soon be sending a new request.
// This means the input thread accepted a request, but hasn't sent a message yet.
// So we KNOW the input thread is not waiting for a new request.
// We sent an event for free, so pay for it now by consuming a request
while let Some(backend::InputRequest::Peek) =
resize_requests.recv()
{
// At this point Cursive will now listen for input.
// There is a chance the input thread will send his event before us.
// But without some extra atomic flag, it'd be hard to know.
// So instead, keep sending `None`
// Repeat until we receive a blocking call
resize_sender.send(None);
}
} }
} }
}); });
let mut parser = InputParser::new(event_sink); let mut parser = InputParser::new();
// This thread will take input from ncurses for each request. // This thread will take input from ncurses for each request.
thread::spawn(move || { thread::spawn(move || {
for req in input_request { for req in input_request {
match req { match req {
backend::InputRequest::Peek => { backend::InputRequest::Peek => {
// When peeking, we want an answer instantly from ncurses
ncurses::timeout(0); ncurses::timeout(0);
} }
backend::InputRequest::Block => { backend::InputRequest::Block => {
ncurses::timeout(-1); ncurses::timeout(-1);
} }
} }
parser.parse_next();
// Do the actual polling & parsing.
event_sink.send(parser.parse_next());
} }
// The request channel is closed, which means Cursive has been
// dropped, so stop the resize-detection thread as well.
running.store(false, Ordering::Relaxed); running.store(false, Ordering::Relaxed);
}); });
} }

View File

@ -131,9 +131,9 @@ impl Cursive {
let theme = theme::load_default(); let theme = theme::load_default();
let (cb_sink, cb_source) = crossbeam_channel::unbounded(); let (cb_sink, cb_source) = crossbeam_channel::unbounded();
let (event_sink, event_source) = crossbeam_channel::unbounded(); let (event_sink, event_source) = crossbeam_channel::bounded(0);
let (input_sink, input_source) = crossbeam_channel::unbounded(); let (input_sink, input_source) = crossbeam_channel::bounded(0);
let mut backend = backend_init(); let mut backend = backend_init();
backend.start_input_thread(event_sink.clone(), input_source); backend.start_input_thread(event_sink.clone(), input_source);
@ -600,7 +600,8 @@ impl Cursive {
} }
self.input_trigger.send(backend::InputRequest::Peek); self.input_trigger.send(backend::InputRequest::Peek);
self.backend.prepare_input(&self.event_sink, backend::InputRequest::Peek); self.backend
.prepare_input(&self.event_sink, backend::InputRequest::Peek);
self.event_source.recv().unwrap().map(Interruption::Event) self.event_source.recv().unwrap().map(Interruption::Event)
} }
@ -610,7 +611,8 @@ impl Cursive {
fn poll(&mut self) -> Option<Interruption> { fn poll(&mut self) -> Option<Interruption> {
if !self.expecting_event { if !self.expecting_event {
self.input_trigger.send(backend::InputRequest::Block); self.input_trigger.send(backend::InputRequest::Block);
self.backend.prepare_input(&self.event_sink, backend::InputRequest::Block); self.backend
.prepare_input(&self.event_sink, backend::InputRequest::Block);
self.expecting_event = true; self.expecting_event = true;
} }
@ -623,6 +625,9 @@ impl Cursive {
select! { select! {
recv(self.event_source, event) => { recv(self.event_source, event) => {
// Ok, we processed the event.
self.expecting_event = false;
event.unwrap().map(Interruption::Event) event.unwrap().map(Interruption::Event)
}, },
recv(self.cb_source, cb) => { recv(self.cb_source, cb) => {
@ -735,16 +740,20 @@ impl Cursive {
self.draw(); self.draw();
self.backend.refresh(); self.backend.refresh();
// First, read all events available while peeking. if let Some(interruption) = self.poll() {
while let Some(interruption) = self.peek() {
self.handle_interruption(interruption); self.handle_interruption(interruption);
if !self.running { if !self.running {
return; return;
} }
} }
if let Some(interruption) = self.poll() { // Don't block, but try to read any other pending event.
// This lets us batch-process chunks of events, like big copy-paste or mouse drags.
while let Some(interruption) = self.peek() {
self.handle_interruption(interruption); self.handle_interruption(interruption);
if !self.running {
return;
}
} }
} }
@ -758,7 +767,6 @@ impl Cursive {
if event == Event::WindowResize { if event == Event::WindowResize {
self.clear(); self.clear();
return;
} }
if let Event::Mouse { if let Event::Mouse {
@ -794,9 +802,6 @@ impl Cursive {
EventResult::Consumed(Some(cb)) => cb(self), EventResult::Consumed(Some(cb)) => cb(self),
} }
} }
// Ok, we processed the event.
self.expecting_event = false;
} }
Interruption::Callback(cb) => { Interruption::Callback(cb) => {
cb.call_box(self); cb.call_box(self);