Share resize thread logic between all unix backends

This commit is contained in:
Alexandre Bury 2018-07-18 20:01:26 -07:00
parent e0cc2ea703
commit acd3bbfcca
5 changed files with 76 additions and 75 deletions

View File

@ -6,14 +6,7 @@
extern crate term_size; extern crate term_size;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use crossbeam_channel::{Receiver, Sender};
use signal_hook::iterator::Signals;
use backend;
use event::{Event, Key}; use event::{Event, Key};
use theme::{BaseColor, Color, ColorPair}; use theme::{BaseColor, Color, ColorPair};
use vec::Vec2; use vec::Vec2;
@ -141,43 +134,3 @@ fn find_closest(color: Color, max_colors: i16) -> i16 {
} }
} }
} }
#[cfg(unix)]
fn start_resize_thread(
signals: Signals, resize_sender: Sender<Option<Event>>,
resize_requests: Receiver<backend::InputRequest>,
resize_running: Arc<AtomicBool>, needs_resize: Arc<AtomicBool>,
) {
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.
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);
}
}
}
});
}

View File

@ -305,12 +305,12 @@ impl backend::Backend for Backend {
) { ) {
let running = Arc::new(AtomicBool::new(true)); let running = Arc::new(AtomicBool::new(true));
super::start_resize_thread( backend::start_resize_thread(
self.signals.take().unwrap(), self.signals.take().unwrap(),
event_sink.clone(), event_sink.clone(),
input_request.clone(), input_request.clone(),
Arc::clone(&running), Arc::clone(&running),
Arc::clone(&self.needs_resize), Some(Arc::clone(&self.needs_resize)),
); );
let mut parser = InputParser::new(); let mut parser = InputParser::new();

View File

@ -285,6 +285,8 @@ fn find_closest_pair(pair: ColorPair) -> (i16, i16) {
impl Backend { impl Backend {
pub fn init() -> Box<backend::Backend> { pub fn init() -> Box<backend::Backend> {
// We need to create this now, before ncurses initialization
// Otherwise ncurses starts its own signal handling and it's a mess.
#[cfg(unix)] #[cfg(unix)]
let signals = Some(Signals::new(&[libc::SIGWINCH]).unwrap()); let signals = Some(Signals::new(&[libc::SIGWINCH]).unwrap());
@ -455,12 +457,12 @@ impl backend::Backend for Backend {
#[cfg(unix)] #[cfg(unix)]
{ {
super::start_resize_thread( backend::start_resize_thread(
self.signals.take().unwrap(), self.signals.take().unwrap(),
event_sink.clone(), event_sink.clone(),
input_request.clone(), input_request.clone(),
Arc::clone(&running), Arc::clone(&running),
Arc::clone(&self.needs_resize), Some(Arc::clone(&self.needs_resize)),
); );
} }

View File

@ -7,11 +7,15 @@
//! using some common libraries. Each of those included backends needs a //! using some common libraries. Each of those included backends needs a
//! corresonding feature to be enabled. //! corresonding feature to be enabled.
use event; use std::sync::atomic::{AtomicBool, Ordering};
use theme; use std::sync::Arc;
use std::thread;
use crossbeam_channel::{Receiver, Sender}; use crossbeam_channel::{Receiver, Sender};
use signal_hook::iterator::Signals;
use event::Event;
use theme;
use vec::Vec2; use vec::Vec2;
pub mod dummy; pub mod dummy;
@ -41,7 +45,7 @@ pub trait Backend {
/// ///
/// `event_trigger` will receive a value before any event is needed. /// `event_trigger` will receive a value before any event is needed.
fn start_input_thread( fn start_input_thread(
&mut self, event_sink: Sender<Option<event::Event>>, &mut self, event_sink: Sender<Option<Event>>,
input_request: Receiver<InputRequest>, input_request: Receiver<InputRequest>,
) { ) {
// Dummy implementation for some backends. // Dummy implementation for some backends.
@ -54,7 +58,7 @@ pub trait Backend {
/// This is only required for non-thread-safe backends like BearLibTerminal /// This is only required for non-thread-safe backends like BearLibTerminal
/// where we cannot collect input in a separate thread. /// where we cannot collect input in a separate thread.
fn prepare_input( fn prepare_input(
&mut self, event_sink: &Sender<Option<event::Event>>, &mut self, event_sink: &Sender<Option<Event>>,
input_request: InputRequest, input_request: InputRequest,
) { ) {
// Dummy implementation for most backends. // Dummy implementation for most backends.
@ -89,3 +93,49 @@ pub trait Backend {
/// Disables the given effect. /// Disables the given effect.
fn unset_effect(&self, effect: theme::Effect); fn unset_effect(&self, effect: theme::Effect);
} }
/// This starts a new thread to listen for SIGWINCH signals
///
/// As long as `resize_running` is true, it will listen for SIGWINCH, and,
/// when detected, it wil set `needs_resize` to true and send an event to
/// `resize_sender`. It will also consume an event from `resize_requests`
/// afterward, to keep the balance in the force.
#[cfg(unix)]
fn start_resize_thread(
signals: Signals, resize_sender: Sender<Option<Event>>,
resize_requests: Receiver<InputRequest>, resize_running: Arc<AtomicBool>,
needs_resize: Option<Arc<AtomicBool>>,
) {
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.
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().
if let Some(ref needs_resize) = needs_resize {
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(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);
}
}
}
});
}

View File

@ -5,7 +5,6 @@
extern crate termion; extern crate termion;
use crossbeam_channel::{self, Sender, Receiver};
use self::termion::color as tcolor; use self::termion::color as tcolor;
use self::termion::event::Event as TEvent; use self::termion::event::Event as TEvent;
use self::termion::event::Key as TKey; use self::termion::event::Key as TKey;
@ -15,8 +14,9 @@ use self::termion::input::{MouseTerminal, TermRead};
use self::termion::raw::{IntoRawMode, RawTerminal}; use self::termion::raw::{IntoRawMode, RawTerminal};
use self::termion::screen::AlternateScreen; use self::termion::screen::AlternateScreen;
use self::termion::style as tstyle; use self::termion::style as tstyle;
use signal_hook::iterator::Signals; use crossbeam_channel::{self, Receiver, Sender};
use libc; use libc;
use signal_hook::iterator::Signals;
use backend; use backend;
use event::{Event, Key, MouseButton, MouseEvent}; use event::{Event, Key, MouseButton, MouseEvent};
@ -25,9 +25,9 @@ use vec::Vec2;
use std::cell::Cell; use std::cell::Cell;
use std::io::{Stdout, Write}; use std::io::{Stdout, Write};
use std::thread;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
pub struct Backend { pub struct Backend {
terminal: AlternateScreen<MouseTerminal<RawTerminal<Stdout>>>, terminal: AlternateScreen<MouseTerminal<RawTerminal<Stdout>>>,
@ -56,7 +56,8 @@ impl InputParser {
let mut events = stdin.events(); let mut events = stdin.events();
for _ in request_receiver { for _ in request_receiver {
let event: Result<TEvent, ::std::io::Error> = events.next().unwrap(); let event: Result<TEvent, ::std::io::Error> =
events.next().unwrap();
input_sender.send(event.unwrap()); input_sender.send(event.unwrap());
} }
}); });
@ -294,22 +295,17 @@ impl backend::Backend for Backend {
input_request: Receiver<backend::InputRequest>, input_request: Receiver<backend::InputRequest>,
) { ) {
let running = Arc::new(AtomicBool::new(true)); let running = Arc::new(AtomicBool::new(true));
let resize_sender = event_sink.clone();
let signals = Signals::new(&[libc::SIGWINCH]).unwrap();
let resize_running = Arc::clone(&running); #[cfg(unix)]
thread::spawn(move || { {
while resize_running.load(Ordering::Relaxed) { backend::start_resize_thread(
// We know it will only contain SIGWINCH signals, so no need to check. Signals::new(&[libc::SIGWINCH]).unwrap(),
for _ in signals.pending() { event_sink.clone(),
// Tell ncurses about the new terminal size. input_request.clone(),
// Well, do the actual resizing later on, in the main thread. Arc::clone(&running),
// Ncurses isn't really thread-safe so calling resize_term() can crash None,
// other calls like clear() or refresh(). );
resize_sender.send(Some(Event::WindowResize));
} }
}
});
let mut parser = InputParser::new(); let mut parser = InputParser::new();
thread::spawn(move || { thread::spawn(move || {