Share resize thread logic between ncurses backends

This commit is contained in:
Alexandre Bury 2018-07-18 19:49:10 -07:00
parent fd75249633
commit e0cc2ea703
4 changed files with 84 additions and 83 deletions

View File

@ -6,10 +6,16 @@
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;
#[cfg(feature = "ncurses-backend")] #[cfg(feature = "ncurses-backend")]
@ -135,3 +141,43 @@ 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

@ -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, AtomicUsize, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::thread;
@ -304,45 +304,14 @@ 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 needs_resize = Arc::clone(&self.needs_resize);
let resize_running = Arc::clone(&running); super::start_resize_thread(
let resize_sender = event_sink.clone(); self.signals.take().unwrap(),
let signals = self.signals.take().unwrap(); event_sink.clone(),
let resize_requests = input_request.clone(); input_request.clone(),
Arc::clone(&running),
thread::spawn(move || { Arc::clone(&self.needs_resize),
// 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);
}
}
}
});
let mut parser = InputParser::new(); let mut parser = InputParser::new();

View File

@ -7,20 +7,20 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::thread;
use crossbeam_channel::{Sender, Receiver}; use crossbeam_channel::{Receiver, Sender};
#[cfg(unix)]
use signal_hook::iterator::Signals;
#[cfg(unix)] #[cfg(unix)]
use libc; use libc;
#[cfg(unix)]
use signal_hook::iterator::Signals;
use backend; use backend;
use event::{Event, Key, MouseButton, MouseEvent}; use event::{Event, Key, MouseButton, MouseEvent};
use theme::{Color, ColorPair, Effect}; use theme::{Color, ColorPair, Effect};
use vec::Vec2; use vec::Vec2;
use super::split_i32;
use self::pancurses::mmask_t; use self::pancurses::mmask_t;
use super::split_i32;
pub struct Backend { pub struct Backend {
// Used // Used
@ -66,7 +66,6 @@ impl InputParser {
fn parse_next(&mut self) { fn parse_next(&mut self) {
let event = if let Some(ev) = self.window.getch() { let event = if let Some(ev) = self.window.getch() {
Some(match ev { Some(match ev {
pancurses::Input::Character('\n') => Event::Key(Key::Enter), pancurses::Input::Character('\n') => Event::Key(Key::Enter),
// TODO: wait for a very short delay. If more keys are // TODO: wait for a very short delay. If more keys are
@ -284,8 +283,6 @@ fn find_closest_pair(pair: ColorPair) -> (i16, i16) {
super::find_closest_pair(pair, pancurses::COLORS() as i16) super::find_closest_pair(pair, pancurses::COLORS() as i16)
} }
impl Backend { impl Backend {
pub fn init() -> Box<backend::Backend> { pub fn init() -> Box<backend::Backend> {
#[cfg(unix)] #[cfg(unix)]
@ -455,34 +452,18 @@ 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 needs_resize = Arc::clone(&self.needs_resize);
let resize_running = Arc::clone(&running);
let resize_sender = event_sink.clone();
#[cfg(unix)] #[cfg(unix)]
{ {
let signals = self.signals.take().unwrap(); super::start_resize_thread(
thread::spawn(move || { self.signals.take().unwrap(),
// This thread will listen to SIGWINCH events and report them. event_sink.clone(),
while resize_running.load(Ordering::Relaxed) { input_request.clone(),
// We know it will only contain SIGWINCH signals, so no need to check. Arc::clone(&running),
for _ in signals.pending() { Arc::clone(&self.needs_resize),
// 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));
}
}
});
} }
// On windows we just forget the sender, so the receiver blocks forever.
#[cfg(not(unix))]
::std::mem::forget(resize_sender);
let mut input_parser = let mut input_parser =
InputParser::new(event_sink, Arc::clone(&self.window)); InputParser::new(event_sink, Arc::clone(&self.window));

View File

@ -81,14 +81,19 @@ impl<F: FnOnce(&mut Cursive) -> () + Send> CbFunc for F {
} }
} }
#[cfg(feature = "termion")] #[cfg(feature = "termion-backend")]
impl Default for Cursive { impl Default for Cursive {
fn default() -> Self { fn default() -> Self {
Self::termion() Self::termion()
} }
} }
#[cfg(all(not(feature = "termion"), feature = "pancurses"))] #[cfg(
all(
not(feature = "termion-backend"),
feature = "pancurses-backend"
)
)]
impl Default for Cursive { impl Default for Cursive {
fn default() -> Self { fn default() -> Self {
Self::pancurses() Self::pancurses()
@ -97,9 +102,9 @@ impl Default for Cursive {
#[cfg( #[cfg(
all( all(
not(feature = "termion"), not(feature = "termion-backend"),
not(feature = "pancurses"), not(feature = "pancurses-backend"),
feature = "bear-lib-terminal" feature = "blt-backend"
) )
)] )]
impl Default for Cursive { impl Default for Cursive {
@ -110,10 +115,10 @@ impl Default for Cursive {
#[cfg( #[cfg(
all( all(
not(feature = "termion"), not(feature = "termion-backend"),
not(feature = "pancurses"), not(feature = "pancurses-backend"),
not(feature = "bear-lib-terminal"), not(feature = "blt-backend"),
feature = "ncurses" feature = "ncurses-backend"
) )
)] )]
impl Default for Cursive { impl Default for Cursive {
@ -158,25 +163,25 @@ impl Cursive {
} }
/// Creates a new Cursive root using a ncurses backend. /// Creates a new Cursive root using a ncurses backend.
#[cfg(feature = "ncurses")] #[cfg(feature = "ncurses-backend")]
pub fn ncurses() -> Self { pub fn ncurses() -> Self {
Self::new(backend::curses::n::Backend::init) Self::new(backend::curses::n::Backend::init)
} }
/// Creates a new Cursive root using a pancurses backend. /// Creates a new Cursive root using a pancurses backend.
#[cfg(feature = "pancurses")] #[cfg(feature = "pancurses-backend")]
pub fn pancurses() -> Self { pub fn pancurses() -> Self {
Self::new(backend::curses::pan::Backend::init) Self::new(backend::curses::pan::Backend::init)
} }
/// Creates a new Cursive root using a termion backend. /// Creates a new Cursive root using a termion backend.
#[cfg(feature = "termion")] #[cfg(feature = "termion-backend")]
pub fn termion() -> Self { pub fn termion() -> Self {
Self::new(backend::termion::Backend::init) Self::new(backend::termion::Backend::init)
} }
/// Creates a new Cursive root using a bear-lib-terminal backend. /// Creates a new Cursive root using a bear-lib-terminal backend.
#[cfg(feature = "bear-lib-terminal")] #[cfg(feature = "blt-backend")]
pub fn blt() -> Self { pub fn blt() -> Self {
Self::new(backend::blt::Backend::init) Self::new(backend::blt::Backend::init)
} }