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;
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 theme::{BaseColor, Color, ColorPair};
use vec::Vec2;
#[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::io;
use std::io::Write;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
@ -304,45 +304,14 @@ impl backend::Backend for Backend {
input_request: Receiver<backend::InputRequest>,
) {
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();
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.
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);
}
}
}
});
super::start_resize_thread(
self.signals.take().unwrap(),
event_sink.clone(),
input_request.clone(),
Arc::clone(&running),
Arc::clone(&self.needs_resize),
);
let mut parser = InputParser::new();

View File

@ -7,20 +7,20 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use crossbeam_channel::{Sender, Receiver};
use crossbeam_channel::{Receiver, Sender};
#[cfg(unix)]
use signal_hook::iterator::Signals;
#[cfg(unix)]
use libc;
#[cfg(unix)]
use signal_hook::iterator::Signals;
use backend;
use event::{Event, Key, MouseButton, MouseEvent};
use theme::{Color, ColorPair, Effect};
use vec::Vec2;
use super::split_i32;
use self::pancurses::mmask_t;
use super::split_i32;
pub struct Backend {
// Used
@ -66,7 +66,6 @@ impl InputParser {
fn parse_next(&mut self) {
let event = if let Some(ev) = self.window.getch() {
Some(match ev {
pancurses::Input::Character('\n') => Event::Key(Key::Enter),
// 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)
}
impl Backend {
pub fn init() -> Box<backend::Backend> {
#[cfg(unix)]
@ -455,33 +452,17 @@ impl backend::Backend for Backend {
input_request: Receiver<backend::InputRequest>,
) {
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)]
{
let signals = self.signals.take().unwrap();
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() {
// 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));
super::start_resize_thread(
self.signals.take().unwrap(),
event_sink.clone(),
input_request.clone(),
Arc::clone(&running),
Arc::clone(&self.needs_resize),
);
}
}
});
}
// On windows we just forget the sender, so the receiver blocks forever.
#[cfg(not(unix))]
::std::mem::forget(resize_sender);
let mut input_parser =
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 {
fn default() -> Self {
Self::termion()
}
}
#[cfg(all(not(feature = "termion"), feature = "pancurses"))]
#[cfg(
all(
not(feature = "termion-backend"),
feature = "pancurses-backend"
)
)]
impl Default for Cursive {
fn default() -> Self {
Self::pancurses()
@ -97,9 +102,9 @@ impl Default for Cursive {
#[cfg(
all(
not(feature = "termion"),
not(feature = "pancurses"),
feature = "bear-lib-terminal"
not(feature = "termion-backend"),
not(feature = "pancurses-backend"),
feature = "blt-backend"
)
)]
impl Default for Cursive {
@ -110,10 +115,10 @@ impl Default for Cursive {
#[cfg(
all(
not(feature = "termion"),
not(feature = "pancurses"),
not(feature = "bear-lib-terminal"),
feature = "ncurses"
not(feature = "termion-backend"),
not(feature = "pancurses-backend"),
not(feature = "blt-backend"),
feature = "ncurses-backend"
)
)]
impl Default for Cursive {
@ -158,25 +163,25 @@ impl Cursive {
}
/// Creates a new Cursive root using a ncurses backend.
#[cfg(feature = "ncurses")]
#[cfg(feature = "ncurses-backend")]
pub fn ncurses() -> Self {
Self::new(backend::curses::n::Backend::init)
}
/// Creates a new Cursive root using a pancurses backend.
#[cfg(feature = "pancurses")]
#[cfg(feature = "pancurses-backend")]
pub fn pancurses() -> Self {
Self::new(backend::curses::pan::Backend::init)
}
/// Creates a new Cursive root using a termion backend.
#[cfg(feature = "termion")]
#[cfg(feature = "termion-backend")]
pub fn termion() -> Self {
Self::new(backend::termion::Backend::init)
}
/// Creates a new Cursive root using a bear-lib-terminal backend.
#[cfg(feature = "bear-lib-terminal")]
#[cfg(feature = "blt-backend")]
pub fn blt() -> Self {
Self::new(backend::blt::Backend::init)
}