From 804e41ec434798d15b3481776efee1bb16dc6352 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Sun, 17 Jun 2018 17:26:03 -0700 Subject: [PATCH] Fix resize event --- Cargo.toml | 11 +++--- src/backend/curses/mod.rs | 2 ++ src/backend/curses/n.rs | 70 +++++++++++++++++++++++++++++++------ src/backend/curses/pan.rs | 65 ++++++++++++++++++++++++++++++---- src/backend/curses/sizes.rs | 23 ++++++++++++ src/backend/termion.rs | 6 ++-- src/cursive.rs | 29 +++++++++------ src/lib.rs | 1 + 8 files changed, 173 insertions(+), 34 deletions(-) create mode 100644 src/backend/curses/sizes.rs diff --git a/Cargo.toml b/Cargo.toml index 4e73890..a739de5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,10 @@ xi-unicode = "0.1.0" libc = "0.2" chan = "0.1" +[dependencies.ioctl-rs] +optional = true +version = "0.2" + [dependencies.num] default-features = false version = "0.1" @@ -38,7 +42,6 @@ optional = true version = "1.3.1" [dependencies.chan-signal] -optional = true version = "0.3" [dependencies.ncurses] @@ -68,9 +71,9 @@ pretty-bytes = "0.2.2" blt-backend = ["bear-lib-terminal"] default = ["ncurses-backend"] markdown = ["pulldown-cmark"] -ncurses-backend = ["ncurses", "maplit"] -pancurses-backend = ["pancurses", "maplit"] -termion-backend = ["termion", "chan-signal"] +ncurses-backend = ["ncurses", "maplit", "ioctl-rs"] +pancurses-backend = ["pancurses", "maplit", "ioctl-rs"] +termion-backend = ["termion"] [lib] name = "cursive" diff --git a/src/backend/curses/mod.rs b/src/backend/curses/mod.rs index a6e11e3..92b980c 100644 --- a/src/backend/curses/mod.rs +++ b/src/backend/curses/mod.rs @@ -7,6 +7,8 @@ use event::{Event, Key}; use std::collections::HashMap; use theme::{BaseColor, Color, ColorPair}; +mod sizes; + #[cfg(feature = "ncurses")] pub mod n; diff --git a/src/backend/curses/n.rs b/src/backend/curses/n.rs index a3e5da3..f5556a2 100644 --- a/src/backend/curses/n.rs +++ b/src/backend/curses/n.rs @@ -1,29 +1,37 @@ extern crate ncurses; -use self::super::split_i32; -use self::ncurses::mmask_t; -use backend; -use event::{Event, Key, MouseButton, MouseEvent}; -use theme::{Color, ColorPair, Effect}; -use utf8; -use vec::Vec2; - use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::ffi::CString; use std::fs::File; use std::io; use std::io::Write; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use std::thread; use chan; +use chan_signal; use libc; +use backend; +use event::{Event, Key, MouseButton, MouseEvent}; +use theme::{Color, ColorPair, Effect}; +use utf8; +use vec::Vec2; + +use self::super::split_i32; +use self::ncurses::mmask_t; + pub struct Backend { current_style: Cell, // Maps (front, back) ncurses colors to ncurses pairs pairs: RefCell>, + + // This is set by the SIGWINCH-triggered thread. + // When TRUE, we should tell ncurses about the new terminal size. + needs_resize: Arc, } struct InputParser { @@ -202,6 +210,7 @@ impl Backend { let c = Backend { current_style: Cell::new(ColorPair::from_256colors(0, 0)), pairs: RefCell::new(HashMap::new()), + needs_resize: Arc::new(AtomicBool::new(false)), }; Box::new(c) @@ -251,6 +260,17 @@ impl Backend { } } +/// Called when a resize event is detected. +/// +/// We need to have ncurses update its representation of the screen. +fn on_resize() { + // Get size using ioctl + let size = super::sizes::terminal_size(); + + // Send the size to ncurses + ncurses::resize_term(size.y as i32, size.x as i32); +} + impl backend::Backend for Backend { fn screen_size(&self) -> Vec2 { let mut x: i32 = 0; @@ -267,9 +287,35 @@ impl backend::Backend for Backend { &mut self, event_sink: chan::Sender, stops: chan::Receiver, ) { - let mut parser = InputParser::new(event_sink); + let resize = chan_signal::notify(&[chan_signal::Signal::WINCH]); + let (sender, receiver) = chan::async(); - // Start an input thread + let needs_resize = Arc::clone(&self.needs_resize); + + // This thread will merge resize and input into event_sink + thread::spawn(move || { + loop { + chan_select! { + resize.recv() => { + // 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); + event_sink.send(Event::WindowResize); + }, + receiver.recv() -> event => { + match event { + Some(event) => event_sink.send(event), + None => return, + } + } + } + } + }); + + let mut parser = InputParser::new(sender); + // This thread will just fill the input channel thread::spawn(move || { loop { // This sends events to the event sender. @@ -321,6 +367,10 @@ impl backend::Backend for Backend { } fn clear(&self, color: Color) { + if self.needs_resize.swap(false, Ordering::Relaxed) { + on_resize(); + } + let id = self.get_or_create(ColorPair { front: color, back: color, diff --git a/src/backend/curses/pan.rs b/src/backend/curses/pan.rs index 6869012..7f6adfe 100644 --- a/src/backend/curses/pan.rs +++ b/src/backend/curses/pan.rs @@ -1,18 +1,23 @@ extern crate pancurses; -use self::super::split_i32; -use self::pancurses::mmask_t; -use backend; -use chan; -use event::{Event, Key, MouseButton, MouseEvent}; use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::io::{stdout, Write}; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::thread; + +use chan; +use chan_signal; + +use backend; +use event::{Event, Key, MouseButton, MouseEvent}; use theme::{Color, ColorPair, Effect}; use vec::Vec2; +use self::super::split_i32; +use self::pancurses::mmask_t; + pub struct Backend { // Used current_style: Cell, @@ -20,6 +25,10 @@ pub struct Backend { // pancurses needs a handle to the current window. window: Arc, + + // This is set by the SIGWINCH-triggered thread. + // When TRUE, we should tell ncurses about the new terminal size. + needs_resize: Arc, } struct InputParser { @@ -267,7 +276,7 @@ fn find_closest_pair(pair: &ColorPair) -> (i16, i16) { } impl Backend { - pub fn init() -> Box { + pub fn init() -> Box { ::std::env::set_var("ESCDELAY", "25"); let window = pancurses::initscr(); @@ -293,6 +302,7 @@ impl Backend { current_style: Cell::new(ColorPair::from_256colors(0, 0)), pairs: RefCell::new(HashMap::new()), window: Arc::new(window), + needs_resize: Arc::new(AtomicBool::new(false)), }; Box::new(c) @@ -343,6 +353,17 @@ impl Backend { } } +/// Called when a resize event is detected. +/// +/// We need to have ncurses update its representation of the screen. +fn on_resize() { + // Get size using ioctl + let size = super::sizes::terminal_size(); + + // Send the size to ncurses + pancurses::resize_term(size.y as i32, size.x as i32); +} + impl backend::Backend for Backend { fn screen_size(&self) -> Vec2 { // Coordinates are reversed here @@ -393,6 +414,10 @@ impl backend::Backend for Backend { } fn clear(&self, color: Color) { + if self.needs_resize.swap(false, Ordering::Relaxed) { + on_resize(); + } + let id = self.get_or_create(ColorPair { front: color, back: color, @@ -413,8 +438,34 @@ impl backend::Backend for Backend { &mut self, event_sink: chan::Sender, stops: chan::Receiver, ) { + let resize = chan_signal::notify(&[chan_signal::Signal::WINCH]); + let (sender, receiver) = chan::async(); + let needs_resize = Arc::clone(&self.needs_resize); + + // This thread will merge resize and input into event_sink + thread::spawn(move || { + loop { + chan_select! { + resize.recv() => { + // 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); + event_sink.send(Event::WindowResize); + }, + receiver.recv() -> event => { + match event { + Some(event) => event_sink.send(event), + None => return, + } + } + } + } + }); + let mut input_parser = - InputParser::new(event_sink, Arc::clone(&self.window)); + InputParser::new(sender, Arc::clone(&self.window)); thread::spawn(move || { loop { diff --git a/src/backend/curses/sizes.rs b/src/backend/curses/sizes.rs new file mode 100644 index 0000000..6fba9b9 --- /dev/null +++ b/src/backend/curses/sizes.rs @@ -0,0 +1,23 @@ +extern crate ioctl_rs as ioctl; + +use libc::{c_ushort, STDOUT_FILENO}; +use std::mem; + +use vec::Vec2; + +#[repr(C)] +struct TermSize { + row: c_ushort, + col: c_ushort, + _x: c_ushort, + _y: c_ushort, +} + +/// Get the size of the terminal. +pub fn terminal_size() -> Vec2 { + unsafe { + let mut size: TermSize = mem::zeroed(); + ioctl::ioctl(STDOUT_FILENO, ioctl::TIOCGWINSZ, &mut size as *mut _); + Vec2::new(size.col as usize, size.row as usize) + } +} diff --git a/src/backend/termion.rs b/src/backend/termion.rs index c71b4a2..6e761bb 100644 --- a/src/backend/termion.rs +++ b/src/backend/termion.rs @@ -41,6 +41,8 @@ impl InputParser { fn new(event_sink: chan::Sender) -> Self { let (sender, receiver) = chan::async(); + let resize = chan_signal::notify(&[chan_signal::Signal::WINCH]); + // Fill the input channel thread::spawn(move || { for key in ::std::io::stdin().events() { @@ -51,7 +53,7 @@ impl InputParser { }); InputParser { - resize: chan_signal::notify(&[chan_signal::Signal::WINCH]), + resize, event_sink, last_button: None, input: receiver, @@ -181,7 +183,7 @@ impl Effectable for theme::Effect { } impl Backend { - pub fn init() -> Box { + pub fn init() -> Box { print!("{}", termion::cursor::Hide); // TODO: lock stdout diff --git a/src/cursive.rs b/src/cursive.rs index 74a20e0..6a25df8 100644 --- a/src/cursive.rs +++ b/src/cursive.rs @@ -1,12 +1,14 @@ -use backend; -use chan; -use direction; -use event::{Callback, Event, EventResult}; -use printer::Printer; use std::any::Any; use std::collections::HashMap; use std::path::Path; use std::time::Duration; + +use chan; + +use backend; +use direction; +use event::{Callback, Event, EventResult}; +use printer::Printer; use theme; use vec::Vec2; use view::{self, Finder, IntoBoxedView, Position, View}; @@ -121,7 +123,10 @@ impl Default for Cursive { impl Cursive { /// Creates a new Cursive root, and initialize the back-end. - pub fn new(mut backend: Box) -> Self { + pub fn new(backend_init: F) -> Self + where + F: FnOnce() -> Box, + { let theme = theme::load_default(); let (cb_sink, cb_source) = chan::async(); @@ -129,6 +134,7 @@ impl Cursive { let (stop_sink, stop_source) = chan::async(); + let mut backend = backend_init(); backend.start_input_thread(event_sink.clone(), stop_source); Cursive { @@ -152,32 +158,32 @@ impl Cursive { /// Creates a new Cursive root using a ncurses backend. #[cfg(feature = "ncurses")] 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. #[cfg(feature = "pancurses")] 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. #[cfg(feature = "termion")] 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. #[cfg(feature = "bear-lib-terminal")] pub fn blt() -> Self { - Self::new(backend::blt::Backend::init()) + Self::new(backend::blt::Backend::init) } /// Creates a new Cursive root using a dummy backend. /// /// Nothing will be output. This is mostly here for tests. pub fn dummy() -> Self { - Self::new(backend::dummy::Backend::init()) + Self::new(backend::dummy::Backend::init) } /// Returns a sink for asynchronous callbacks. @@ -715,6 +721,7 @@ impl Cursive { // Wait for next event. match self.poll() { Interruption::Event(event) => { + // eprintln!("{:?}, {:?}", event, self.screen_size()); if event == Event::Exit { self.quit(); } diff --git a/src/lib.rs b/src/lib.rs index 018acca..0f404ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -74,6 +74,7 @@ extern crate chan; #[macro_use] extern crate maplit; +extern crate chan_signal; extern crate libc; extern crate num; extern crate owning_ref;