From acd3bbfcca4dfc692b74108bae8e0dc02965c8f4 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Wed, 18 Jul 2018 20:01:26 -0700 Subject: [PATCH] Share resize thread logic between all unix backends --- src/backend/curses/mod.rs | 47 ------------------------------- src/backend/curses/n.rs | 4 +-- src/backend/curses/pan.rs | 6 ++-- src/backend/mod.rs | 58 ++++++++++++++++++++++++++++++++++++--- src/backend/termion.rs | 36 +++++++++++------------- 5 files changed, 76 insertions(+), 75 deletions(-) diff --git a/src/backend/curses/mod.rs b/src/backend/curses/mod.rs index 8fead4e..83048ef 100644 --- a/src/backend/curses/mod.rs +++ b/src/backend/curses/mod.rs @@ -6,14 +6,7 @@ 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; @@ -141,43 +134,3 @@ fn find_closest(color: Color, max_colors: i16) -> i16 { } } } - -#[cfg(unix)] -fn start_resize_thread( - signals: Signals, resize_sender: Sender>, - resize_requests: Receiver, - resize_running: Arc, needs_resize: Arc, -) { - 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); - } - } - } - }); -} diff --git a/src/backend/curses/n.rs b/src/backend/curses/n.rs index 18805db..e09bda4 100644 --- a/src/backend/curses/n.rs +++ b/src/backend/curses/n.rs @@ -305,12 +305,12 @@ impl backend::Backend for Backend { ) { let running = Arc::new(AtomicBool::new(true)); - super::start_resize_thread( + backend::start_resize_thread( self.signals.take().unwrap(), event_sink.clone(), input_request.clone(), Arc::clone(&running), - Arc::clone(&self.needs_resize), + Some(Arc::clone(&self.needs_resize)), ); let mut parser = InputParser::new(); diff --git a/src/backend/curses/pan.rs b/src/backend/curses/pan.rs index a55b383..83ac3b5 100644 --- a/src/backend/curses/pan.rs +++ b/src/backend/curses/pan.rs @@ -285,6 +285,8 @@ fn find_closest_pair(pair: ColorPair) -> (i16, i16) { impl Backend { pub fn init() -> Box { + // We need to create this now, before ncurses initialization + // Otherwise ncurses starts its own signal handling and it's a mess. #[cfg(unix)] let signals = Some(Signals::new(&[libc::SIGWINCH]).unwrap()); @@ -455,12 +457,12 @@ impl backend::Backend for Backend { #[cfg(unix)] { - super::start_resize_thread( + backend::start_resize_thread( self.signals.take().unwrap(), event_sink.clone(), input_request.clone(), Arc::clone(&running), - Arc::clone(&self.needs_resize), + Some(Arc::clone(&self.needs_resize)), ); } diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 0f0cc04..8cc72ae 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -7,11 +7,15 @@ //! using some common libraries. Each of those included backends needs a //! corresonding feature to be enabled. -use event; -use theme; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::thread; use crossbeam_channel::{Receiver, Sender}; +use signal_hook::iterator::Signals; +use event::Event; +use theme; use vec::Vec2; pub mod dummy; @@ -41,7 +45,7 @@ pub trait Backend { /// /// `event_trigger` will receive a value before any event is needed. fn start_input_thread( - &mut self, event_sink: Sender>, + &mut self, event_sink: Sender>, input_request: Receiver, ) { // Dummy implementation for some backends. @@ -54,7 +58,7 @@ pub trait Backend { /// This is only required for non-thread-safe backends like BearLibTerminal /// where we cannot collect input in a separate thread. fn prepare_input( - &mut self, event_sink: &Sender>, + &mut self, event_sink: &Sender>, input_request: InputRequest, ) { // Dummy implementation for most backends. @@ -89,3 +93,49 @@ pub trait Backend { /// Disables the given 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>, + resize_requests: Receiver, resize_running: Arc, + needs_resize: Option>, +) { + 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); + } + } + } + }); +} diff --git a/src/backend/termion.rs b/src/backend/termion.rs index 71cb6c2..c0ebddf 100644 --- a/src/backend/termion.rs +++ b/src/backend/termion.rs @@ -5,7 +5,6 @@ extern crate termion; -use crossbeam_channel::{self, Sender, Receiver}; use self::termion::color as tcolor; use self::termion::event::Event as TEvent; 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::screen::AlternateScreen; use self::termion::style as tstyle; -use signal_hook::iterator::Signals; +use crossbeam_channel::{self, Receiver, Sender}; use libc; +use signal_hook::iterator::Signals; use backend; use event::{Event, Key, MouseButton, MouseEvent}; @@ -25,9 +25,9 @@ use vec::Vec2; use std::cell::Cell; use std::io::{Stdout, Write}; -use std::thread; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; -use std::sync::atomic::{AtomicBool,Ordering}; +use std::thread; pub struct Backend { terminal: AlternateScreen>>, @@ -56,7 +56,8 @@ impl InputParser { let mut events = stdin.events(); for _ in request_receiver { - let event: Result = events.next().unwrap(); + let event: Result = + events.next().unwrap(); input_sender.send(event.unwrap()); } }); @@ -294,22 +295,17 @@ impl backend::Backend for Backend { input_request: Receiver, ) { 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); - thread::spawn(move || { - 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(). - resize_sender.send(Some(Event::WindowResize)); - } - } - }); + #[cfg(unix)] + { + backend::start_resize_thread( + Signals::new(&[libc::SIGWINCH]).unwrap(), + event_sink.clone(), + input_request.clone(), + Arc::clone(&running), + None, + ); + } let mut parser = InputParser::new(); thread::spawn(move || {