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::io;
use std::io::Write;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
@ -40,30 +40,27 @@ pub struct Backend {
struct InputParser {
key_codes: HashMap<i32, Event>,
last_mouse_button: Option<MouseButton>,
event_sink: Sender<Option<Event>>,
input_buffer: Option<Event>,
}
impl InputParser {
fn new(event_sink: Sender<Option<Event>>) -> Self {
fn new() -> Self {
InputParser {
key_codes: initialize_keymap(),
last_mouse_button: None,
event_sink,
input_buffer: None,
}
}
fn parse_next(&mut self) {
let ch: i32 = ncurses::getch();
if ch == 410 {
// Ignore resize events.
self.parse_next();
return;
fn parse_next(&mut self) -> Option<Event> {
if let Some(event) = self.input_buffer.take() {
return Some(event);
}
let ch: i32 = ncurses::getch();
if ch == -1 {
self.event_sink.send(None);
return;
return None;
}
// Is it a UTF-8 starting point?
@ -77,7 +74,8 @@ impl InputParser {
} else {
self.parse_ncurses_char(ch)
};
self.event_sink.send(Some(event));
Some(event)
}
fn parse_ncurses_char(&mut self, ch: i32) -> Event {
@ -146,7 +144,7 @@ impl InputParser {
if event.is_none() {
event = Some(e);
} 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 {
pub fn init() -> Box<backend::Backend> {
let signals = Some(Signals::new(&[libc::SIGWINCH]).unwrap());
// Change the locale.
@ -312,37 +309,61 @@ impl backend::Backend for Backend {
let resize_running = Arc::clone(&running);
let resize_sender = event_sink.clone();
let signals = self.signals.take().unwrap();
let resize_requests = input_request.clone();
thread::spawn(move || {
// This thread will listen to SIGWINCH events and report them.
while resize_running.load(Ordering::Relaxed) {
// 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.
// Well, do the actual resizing later on, in the main thread.
// Ncurses isn't really thread-safe so calling resize_term() can crash
// other calls like clear() or refresh().
needs_resize.store(true, Ordering::Relaxed);
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.
thread::spawn(move || {
for req in input_request {
match req {
backend::InputRequest::Peek => {
// When peeking, we want an answer instantly from ncurses
ncurses::timeout(0);
}
backend::InputRequest::Block => {
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);
});
}

View File

@ -131,9 +131,9 @@ impl Cursive {
let theme = theme::load_default();
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();
backend.start_input_thread(event_sink.clone(), input_source);
@ -600,7 +600,8 @@ impl Cursive {
}
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)
}
@ -610,7 +611,8 @@ impl Cursive {
fn poll(&mut self) -> Option<Interruption> {
if !self.expecting_event {
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;
}
@ -623,6 +625,9 @@ impl Cursive {
select! {
recv(self.event_source, event) => {
// Ok, we processed the event.
self.expecting_event = false;
event.unwrap().map(Interruption::Event)
},
recv(self.cb_source, cb) => {
@ -735,16 +740,20 @@ impl Cursive {
self.draw();
self.backend.refresh();
// First, read all events available while peeking.
while let Some(interruption) = self.peek() {
if let Some(interruption) = self.poll() {
self.handle_interruption(interruption);
if !self.running {
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);
if !self.running {
return;
}
}
}
@ -758,7 +767,6 @@ impl Cursive {
if event == Event::WindowResize {
self.clear();
return;
}
if let Event::Mouse {
@ -794,9 +802,6 @@ impl Cursive {
EventResult::Consumed(Some(cb)) => cb(self),
}
}
// Ok, we processed the event.
self.expecting_event = false;
}
Interruption::Callback(cb) => {
cb.call_box(self);