mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
Refactor input system
* Moves from chan to crossbeam-channel * Moves from chan_signal to signal-hook
This commit is contained in:
parent
4c70b79663
commit
6135b0df79
@ -27,8 +27,8 @@ unicode-segmentation = "1.0"
|
|||||||
unicode-width = "0.1"
|
unicode-width = "0.1"
|
||||||
xi-unicode = "0.1.0"
|
xi-unicode = "0.1.0"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
chan = "0.1"
|
|
||||||
term_size = { version = "0.3.1", optional = true }
|
term_size = { version = "0.3.1", optional = true }
|
||||||
|
crossbeam-channel = "0.2.1"
|
||||||
|
|
||||||
[dependencies.num]
|
[dependencies.num]
|
||||||
default-features = false
|
default-features = false
|
||||||
@ -61,8 +61,8 @@ version = "0.1.0"
|
|||||||
optional = true
|
optional = true
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies.chan-signal]
|
[target.'cfg(unix)'.dependencies.signal-hook]
|
||||||
version = "0.3"
|
version = "0.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = "0.5"
|
rand = "0.5"
|
||||||
|
@ -5,19 +5,22 @@
|
|||||||
|
|
||||||
extern crate bear_lib_terminal;
|
extern crate bear_lib_terminal;
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use self::bear_lib_terminal::geometry::Size;
|
use self::bear_lib_terminal::geometry::Size;
|
||||||
use self::bear_lib_terminal::terminal::{
|
use self::bear_lib_terminal::terminal::{
|
||||||
self, state, Event as BltEvent, KeyCode,
|
self, state, Event as BltEvent, KeyCode,
|
||||||
};
|
};
|
||||||
use self::bear_lib_terminal::Color as BltColor;
|
use self::bear_lib_terminal::Color as BltColor;
|
||||||
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
|
|
||||||
use backend;
|
use backend;
|
||||||
use chan;
|
|
||||||
use event::{Event, Key, MouseButton, MouseEvent};
|
use event::{Event, Key, MouseButton, MouseEvent};
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
use theme::{BaseColor, Color, ColorPair, Effect};
|
use theme::{BaseColor, Color, ColorPair, Effect};
|
||||||
use vec::Vec2;
|
use vec::Vec2;
|
||||||
|
|
||||||
|
|
||||||
enum ColorRole {
|
enum ColorRole {
|
||||||
Foreground,
|
Foreground,
|
||||||
Background,
|
Background,
|
||||||
@ -306,17 +309,25 @@ impl backend::Backend for Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_input(
|
fn prepare_input(
|
||||||
&mut self, event_sink: &chan::Sender<Event>, timeout: Duration,
|
&mut self, event_sink: &Sender<Option<Event>>, input_request: backend::InputRequest,
|
||||||
) {
|
) {
|
||||||
// Wait for up to `timeout_ms`.
|
match input_request {
|
||||||
let start = Instant::now();
|
backend::InputRequest::Peek => event_sink.send(self.parse_next()),
|
||||||
while start.elapsed() < timeout {
|
backend::InputRequest::Block => {
|
||||||
if let Some(event) = self.parse_next() {
|
let timeout = ::std::time::Duration::from_millis(30);
|
||||||
event_sink.send(event);
|
// Wait for up to `timeout_ms`.
|
||||||
return;
|
let start = Instant::now();
|
||||||
|
while start.elapsed() < timeout {
|
||||||
|
if let Some(event) = self.parse_next() {
|
||||||
|
event_sink.send(Some(event));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
event_sink.send(Some(Event::Refresh));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
event_sink.send(Event::Refresh);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,9 +10,9 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
use chan;
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
use chan_signal;
|
|
||||||
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};
|
||||||
@ -32,16 +32,19 @@ pub struct Backend {
|
|||||||
// This is set by the SIGWINCH-triggered thread.
|
// This is set by the SIGWINCH-triggered thread.
|
||||||
// When TRUE, we should tell ncurses about the new terminal size.
|
// When TRUE, we should tell ncurses about the new terminal size.
|
||||||
needs_resize: Arc<AtomicBool>,
|
needs_resize: Arc<AtomicBool>,
|
||||||
|
|
||||||
|
// The signal hook to receive SIGWINCH (window resize)
|
||||||
|
signals: Option<Signals>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InputParser {
|
struct InputParser {
|
||||||
key_codes: HashMap<i32, Event>,
|
key_codes: HashMap<i32, Event>,
|
||||||
last_mouse_button: Option<MouseButton>,
|
last_mouse_button: Option<MouseButton>,
|
||||||
event_sink: chan::Sender<Event>,
|
event_sink: Sender<Option<Event>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputParser {
|
impl InputParser {
|
||||||
fn new(event_sink: chan::Sender<Event>) -> Self {
|
fn new(event_sink: Sender<Option<Event>>) -> Self {
|
||||||
InputParser {
|
InputParser {
|
||||||
key_codes: initialize_keymap(),
|
key_codes: initialize_keymap(),
|
||||||
last_mouse_button: None,
|
last_mouse_button: None,
|
||||||
@ -52,6 +55,17 @@ impl InputParser {
|
|||||||
fn parse_next(&mut self) {
|
fn parse_next(&mut self) {
|
||||||
let ch: i32 = ncurses::getch();
|
let ch: i32 = ncurses::getch();
|
||||||
|
|
||||||
|
if ch == 410 {
|
||||||
|
// Ignore resize events.
|
||||||
|
self.parse_next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch == -1 {
|
||||||
|
self.event_sink.send(None);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Is it a UTF-8 starting point?
|
// Is it a UTF-8 starting point?
|
||||||
let event = if 32 <= ch && ch <= 255 && ch != 127 {
|
let event = if 32 <= ch && ch <= 255 && ch != 127 {
|
||||||
utf8::read_char(ch as u8, || Some(ncurses::getch() as u8))
|
utf8::read_char(ch as u8, || Some(ncurses::getch() as u8))
|
||||||
@ -63,7 +77,7 @@ impl InputParser {
|
|||||||
} else {
|
} else {
|
||||||
self.parse_ncurses_char(ch)
|
self.parse_ncurses_char(ch)
|
||||||
};
|
};
|
||||||
self.event_sink.send(event);
|
self.event_sink.send(Some(event));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_ncurses_char(&mut self, ch: i32) -> Event {
|
fn parse_ncurses_char(&mut self, ch: i32) -> Event {
|
||||||
@ -132,7 +146,7 @@ impl InputParser {
|
|||||||
if event.is_none() {
|
if event.is_none() {
|
||||||
event = Some(e);
|
event = Some(e);
|
||||||
} else {
|
} else {
|
||||||
self.event_sink.send(make_event(e));
|
self.event_sink.send(Some(make_event(e)));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -171,6 +185,9 @@ fn write_to_tty(bytes: &[u8]) -> io::Result<()> {
|
|||||||
|
|
||||||
impl Backend {
|
impl Backend {
|
||||||
pub fn init() -> Box<backend::Backend> {
|
pub fn init() -> Box<backend::Backend> {
|
||||||
|
|
||||||
|
let signals = Some(Signals::new(&[libc::SIGWINCH]).unwrap());
|
||||||
|
|
||||||
// Change the locale.
|
// Change the locale.
|
||||||
// For some reasons it's mandatory to get some UTF-8 support.
|
// For some reasons it's mandatory to get some UTF-8 support.
|
||||||
ncurses::setlocale(ncurses::LcCategory::all, "");
|
ncurses::setlocale(ncurses::LcCategory::all, "");
|
||||||
@ -212,6 +229,7 @@ impl Backend {
|
|||||||
current_style: Cell::new(ColorPair::from_256colors(0, 0)),
|
current_style: Cell::new(ColorPair::from_256colors(0, 0)),
|
||||||
pairs: RefCell::new(HashMap::new()),
|
pairs: RefCell::new(HashMap::new()),
|
||||||
needs_resize: Arc::new(AtomicBool::new(false)),
|
needs_resize: Arc::new(AtomicBool::new(false)),
|
||||||
|
signals,
|
||||||
};
|
};
|
||||||
|
|
||||||
Box::new(c)
|
Box::new(c)
|
||||||
@ -285,48 +303,47 @@ impl backend::Backend for Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn start_input_thread(
|
fn start_input_thread(
|
||||||
&mut self, event_sink: chan::Sender<Event>,
|
&mut self, event_sink: Sender<Option<Event>>,
|
||||||
stops: chan::Receiver<bool>,
|
input_request: Receiver<backend::InputRequest>,
|
||||||
) {
|
) {
|
||||||
let resize = chan_signal::notify(&[chan_signal::Signal::WINCH]);
|
let running = Arc::new(AtomicBool::new(true));
|
||||||
let (sender, receiver) = chan::async();
|
|
||||||
|
|
||||||
let needs_resize = Arc::clone(&self.needs_resize);
|
let needs_resize = Arc::clone(&self.needs_resize);
|
||||||
|
|
||||||
// This thread will merge resize and input into event_sink
|
let resize_running = Arc::clone(&running);
|
||||||
|
let resize_sender = event_sink.clone();
|
||||||
|
let signals = self.signals.take().unwrap();
|
||||||
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
loop {
|
// This thread will listen to SIGWINCH events and report them.
|
||||||
chan_select! {
|
while resize_running.load(Ordering::Relaxed) {
|
||||||
resize.recv() => {
|
// We know it will only contain SIGWINCH signals, so no need to check.
|
||||||
// Tell ncurses about the new terminal size.
|
for _ in signals.pending() {
|
||||||
// Well, do the actual resizing later on, in the main thread.
|
// Tell ncurses about the new terminal size.
|
||||||
// Ncurses isn't really thread-safe so calling resize_term() can crash
|
// Well, do the actual resizing later on, in the main thread.
|
||||||
// other calls like clear() or refresh().
|
// Ncurses isn't really thread-safe so calling resize_term() can crash
|
||||||
needs_resize.store(true, Ordering::Relaxed);
|
// other calls like clear() or refresh().
|
||||||
event_sink.send(Event::WindowResize);
|
needs_resize.store(true, Ordering::Relaxed);
|
||||||
},
|
resize_sender.send(Some(Event::WindowResize));
|
||||||
receiver.recv() -> event => {
|
|
||||||
match event {
|
|
||||||
Some(event) => event_sink.send(event),
|
|
||||||
None => return,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut parser = InputParser::new(sender);
|
let mut parser = InputParser::new(event_sink);
|
||||||
// This thread will just fill the input channel
|
|
||||||
thread::spawn(move || {
|
|
||||||
loop {
|
|
||||||
// This sends events to the event sender.
|
|
||||||
parser.parse_next();
|
|
||||||
|
|
||||||
if stops.recv() != Some(false) {
|
// This thread will take input from ncurses for each request.
|
||||||
// If the channel was closed or if `true` was sent, abort.
|
thread::spawn(move || {
|
||||||
break;
|
for req in input_request {
|
||||||
|
match req {
|
||||||
|
backend::InputRequest::Peek => {
|
||||||
|
ncurses::timeout(0);
|
||||||
|
}
|
||||||
|
backend::InputRequest::Block => {
|
||||||
|
ncurses::timeout(-1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
parser.parse_next();
|
||||||
}
|
}
|
||||||
|
running.store(false, Ordering::Relaxed);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,16 +7,19 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
use chan;
|
use crossbeam_channel::{Sender, Receiver};
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use chan_signal;
|
use signal_hook::iterator::Signals;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use libc;
|
||||||
|
|
||||||
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 self::super::split_i32;
|
use super::split_i32;
|
||||||
use self::pancurses::mmask_t;
|
use self::pancurses::mmask_t;
|
||||||
|
|
||||||
pub struct Backend {
|
pub struct Backend {
|
||||||
@ -30,12 +33,16 @@ pub struct Backend {
|
|||||||
// This is set by the SIGWINCH-triggered thread.
|
// This is set by the SIGWINCH-triggered thread.
|
||||||
// When TRUE, we should tell ncurses about the new terminal size.
|
// When TRUE, we should tell ncurses about the new terminal size.
|
||||||
needs_resize: Arc<AtomicBool>,
|
needs_resize: Arc<AtomicBool>,
|
||||||
|
|
||||||
|
// The signal hook to receive SIGWINCH (window resize)
|
||||||
|
#[cfg(unix)]
|
||||||
|
signals: Option<Signals>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InputParser {
|
struct InputParser {
|
||||||
key_codes: HashMap<i32, Event>,
|
key_codes: HashMap<i32, Event>,
|
||||||
last_mouse_button: Option<MouseButton>,
|
last_mouse_button: Option<MouseButton>,
|
||||||
event_sink: chan::Sender<Event>,
|
event_sink: Sender<Option<Event>>,
|
||||||
window: Arc<pancurses::Window>,
|
window: Arc<pancurses::Window>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +54,7 @@ unsafe impl Send for InputParser {}
|
|||||||
|
|
||||||
impl InputParser {
|
impl InputParser {
|
||||||
fn new(
|
fn new(
|
||||||
event_sink: chan::Sender<Event>, window: Arc<pancurses::Window>,
|
event_sink: Sender<Option<Event>>, window: Arc<pancurses::Window>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
InputParser {
|
InputParser {
|
||||||
key_codes: initialize_keymap(),
|
key_codes: initialize_keymap(),
|
||||||
@ -59,7 +66,8 @@ 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() {
|
||||||
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
|
||||||
// pipelined, it may be an escape sequence.
|
// pipelined, it may be an escape sequence.
|
||||||
@ -204,9 +212,9 @@ impl InputParser {
|
|||||||
pancurses::Input::KeyB2 => Event::Key(Key::NumpadCenter),
|
pancurses::Input::KeyB2 => Event::Key(Key::NumpadCenter),
|
||||||
pancurses::Input::KeyC1 => Event::Refresh,
|
pancurses::Input::KeyC1 => Event::Refresh,
|
||||||
pancurses::Input::KeyC3 => Event::Refresh,
|
pancurses::Input::KeyC3 => Event::Refresh,
|
||||||
}
|
})
|
||||||
} else {
|
} else {
|
||||||
Event::Refresh
|
None
|
||||||
};
|
};
|
||||||
self.event_sink.send(event);
|
self.event_sink.send(event);
|
||||||
}
|
}
|
||||||
@ -255,7 +263,7 @@ impl InputParser {
|
|||||||
if event.is_none() {
|
if event.is_none() {
|
||||||
event = Some(e);
|
event = Some(e);
|
||||||
} else {
|
} else {
|
||||||
self.event_sink.send(make_event(e));
|
self.event_sink.send(Some(make_event(e)));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -276,8 +284,13 @@ 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)]
|
||||||
|
let signals = Some(Signals::new(&[libc::SIGWINCH]).unwrap());
|
||||||
|
|
||||||
::std::env::set_var("ESCDELAY", "25");
|
::std::env::set_var("ESCDELAY", "25");
|
||||||
|
|
||||||
let window = pancurses::initscr();
|
let window = pancurses::initscr();
|
||||||
@ -304,6 +317,7 @@ impl Backend {
|
|||||||
pairs: RefCell::new(HashMap::new()),
|
pairs: RefCell::new(HashMap::new()),
|
||||||
window: Arc::new(window),
|
window: Arc::new(window),
|
||||||
needs_resize: Arc::new(AtomicBool::new(false)),
|
needs_resize: Arc::new(AtomicBool::new(false)),
|
||||||
|
signals,
|
||||||
};
|
};
|
||||||
|
|
||||||
Box::new(c)
|
Box::new(c)
|
||||||
@ -365,19 +379,6 @@ fn on_resize() {
|
|||||||
pancurses::resize_term(size.y as i32, size.x as i32);
|
pancurses::resize_term(size.y as i32, size.x as i32);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
fn resize_channel() -> chan::Receiver<chan_signal::Signal> {
|
|
||||||
chan_signal::notify(&[chan_signal::Signal::WINCH])
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
|
||||||
fn resize_channel() -> chan::Receiver<()> {
|
|
||||||
let (sender, receiver) = chan::async();
|
|
||||||
// Forget the sender, so the channel doesn't close, but never completes.
|
|
||||||
::std::mem::forget(sender);
|
|
||||||
receiver
|
|
||||||
}
|
|
||||||
|
|
||||||
impl backend::Backend for Backend {
|
impl backend::Backend for Backend {
|
||||||
fn screen_size(&self) -> Vec2 {
|
fn screen_size(&self) -> Vec2 {
|
||||||
// Coordinates are reversed here
|
// Coordinates are reversed here
|
||||||
@ -449,47 +450,47 @@ impl backend::Backend for Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn start_input_thread(
|
fn start_input_thread(
|
||||||
&mut self, event_sink: chan::Sender<Event>,
|
&mut self, event_sink: Sender<Option<Event>>,
|
||||||
stops: chan::Receiver<bool>,
|
input_request: Receiver<backend::InputRequest>,
|
||||||
) {
|
) {
|
||||||
let resize = resize_channel();
|
let running = Arc::new(AtomicBool::new(true));
|
||||||
let (sender, receiver) = chan::async();
|
|
||||||
let needs_resize = Arc::clone(&self.needs_resize);
|
let needs_resize = Arc::clone(&self.needs_resize);
|
||||||
|
|
||||||
// This thread will merge resize and input into event_sink
|
let resize_running = Arc::clone(&running);
|
||||||
|
let resize_sender = event_sink.clone();
|
||||||
|
let signals = self.signals.take().unwrap();
|
||||||
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
loop {
|
// This thread will listen to SIGWINCH events and report them.
|
||||||
chan_select! {
|
while resize_running.load(Ordering::Relaxed) {
|
||||||
resize.recv() => {
|
// We know it will only contain SIGWINCH signals, so no need to check.
|
||||||
// Tell ncurses about the new terminal size.
|
for _ in signals.pending() {
|
||||||
// Well, do the actual resizing later on, in the main thread.
|
// Tell ncurses about the new terminal size.
|
||||||
// Ncurses isn't really thread-safe so calling resize_term() can crash
|
// Well, do the actual resizing later on, in the main thread.
|
||||||
// other calls like clear() or refresh().
|
// Ncurses isn't really thread-safe so calling resize_term() can crash
|
||||||
needs_resize.store(true, Ordering::Relaxed);
|
// other calls like clear() or refresh().
|
||||||
event_sink.send(Event::WindowResize);
|
needs_resize.store(true, Ordering::Relaxed);
|
||||||
},
|
resize_sender.send(Some(Event::WindowResize));
|
||||||
receiver.recv() -> event => {
|
|
||||||
match event {
|
|
||||||
Some(event) => event_sink.send(event),
|
|
||||||
None => return,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut input_parser =
|
let mut input_parser =
|
||||||
InputParser::new(sender, Arc::clone(&self.window));
|
InputParser::new(event_sink, Arc::clone(&self.window));
|
||||||
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
loop {
|
for req in input_request {
|
||||||
input_parser.parse_next();
|
match req {
|
||||||
|
backend::InputRequest::Peek => {
|
||||||
if stops.recv() != Some(false) {
|
input_parser.window.timeout(0);
|
||||||
// If the channel was closed or if `true` was sent, abort.
|
}
|
||||||
break;
|
backend::InputRequest::Block => {
|
||||||
|
input_parser.window.timeout(-1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
input_parser.parse_next();
|
||||||
}
|
}
|
||||||
|
running.store(false, Ordering::Relaxed);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,7 @@ use event;
|
|||||||
use theme;
|
use theme;
|
||||||
use vec::Vec2;
|
use vec::Vec2;
|
||||||
|
|
||||||
use std::time::Duration;
|
use crossbeam_channel::{Sender};
|
||||||
|
|
||||||
use chan;
|
|
||||||
|
|
||||||
pub struct Backend;
|
pub struct Backend;
|
||||||
|
|
||||||
@ -33,9 +31,10 @@ impl backend::Backend for Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_input(
|
fn prepare_input(
|
||||||
&mut self, event_sink: &chan::Sender<event::Event>, _timeout: Duration,
|
&mut self, event_sink: &Sender<Option<event::Event>>,
|
||||||
|
_input_request: backend::InputRequest,
|
||||||
) {
|
) {
|
||||||
event_sink.send(event::Event::Exit)
|
event_sink.send(Some(event::Event::Exit))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_at(&self, _: Vec2, _: &str) {}
|
fn print_at(&self, _: Vec2, _: &str) {}
|
||||||
|
@ -10,18 +10,24 @@
|
|||||||
use event;
|
use event;
|
||||||
use theme;
|
use theme;
|
||||||
|
|
||||||
use chan::{Receiver, Sender};
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
|
|
||||||
use vec::Vec2;
|
use vec::Vec2;
|
||||||
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
pub mod dummy;
|
pub mod dummy;
|
||||||
|
|
||||||
pub mod blt;
|
pub mod blt;
|
||||||
pub mod curses;
|
pub mod curses;
|
||||||
pub mod termion;
|
pub mod termion;
|
||||||
|
|
||||||
|
/// A request for input, sent to the backend.
|
||||||
|
pub enum InputRequest {
|
||||||
|
/// The backend should respond immediately with an answer, possibly empty.
|
||||||
|
Peek,
|
||||||
|
/// The backend should block until input is available.
|
||||||
|
Block,
|
||||||
|
}
|
||||||
|
|
||||||
/// Trait defining the required methods to be a backend.
|
/// Trait defining the required methods to be a backend.
|
||||||
pub trait Backend {
|
pub trait Backend {
|
||||||
// TODO: take `self` by value?
|
// TODO: take `self` by value?
|
||||||
@ -32,12 +38,15 @@ pub trait Backend {
|
|||||||
fn finish(&mut self);
|
fn finish(&mut self);
|
||||||
|
|
||||||
/// Starts a thread to collect input and send it to the given channel.
|
/// Starts a thread to collect input and send it to the given channel.
|
||||||
|
///
|
||||||
|
/// `event_trigger` will receive a value before any event is needed.
|
||||||
fn start_input_thread(
|
fn start_input_thread(
|
||||||
&mut self, event_sink: Sender<event::Event>, running: Receiver<bool>,
|
&mut self, event_sink: Sender<Option<event::Event>>,
|
||||||
|
input_request: Receiver<InputRequest>,
|
||||||
) {
|
) {
|
||||||
// Dummy implementation for some backends.
|
// Dummy implementation for some backends.
|
||||||
let _ = event_sink;
|
let _ = event_sink;
|
||||||
let _ = running;
|
let _ = input_request;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepares the backend to collect input.
|
/// Prepares the backend to collect input.
|
||||||
@ -45,12 +54,13 @@ 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<event::Event>, timeout: Duration,
|
&mut self, event_sink: &Sender<Option<event::Event>>,
|
||||||
|
input_request: InputRequest,
|
||||||
) {
|
) {
|
||||||
// Dummy implementation for most backends.
|
// Dummy implementation for most backends.
|
||||||
// Little trick to avoid unused variables.
|
// Little trick to avoid unused variables.
|
||||||
let _ = event_sink;
|
let _ = event_sink;
|
||||||
let _ = timeout;
|
let _ = input_request;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Refresh the screen.
|
/// Refresh the screen.
|
||||||
|
@ -5,8 +5,7 @@
|
|||||||
|
|
||||||
extern crate termion;
|
extern crate termion;
|
||||||
|
|
||||||
extern crate chan_signal;
|
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;
|
||||||
@ -16,14 +15,19 @@ 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 libc;
|
||||||
|
|
||||||
use backend;
|
use backend;
|
||||||
use chan;
|
|
||||||
use event::{Event, Key, MouseButton, MouseEvent};
|
use event::{Event, Key, MouseButton, MouseEvent};
|
||||||
|
use theme;
|
||||||
|
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::thread;
|
||||||
use theme;
|
use std::sync::Arc;
|
||||||
use vec::Vec2;
|
use std::sync::atomic::{AtomicBool,Ordering};
|
||||||
|
|
||||||
pub struct Backend {
|
pub struct Backend {
|
||||||
terminal: AlternateScreen<MouseTerminal<RawTerminal<Stdout>>>,
|
terminal: AlternateScreen<MouseTerminal<RawTerminal<Stdout>>>,
|
||||||
@ -31,53 +35,73 @@ pub struct Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct InputParser {
|
struct InputParser {
|
||||||
input: chan::Receiver<TEvent>,
|
// Inner state required to parse input
|
||||||
resize: chan::Receiver<chan_signal::Signal>,
|
|
||||||
event_sink: chan::Sender<Event>,
|
|
||||||
last_button: Option<MouseButton>,
|
last_button: Option<MouseButton>,
|
||||||
|
|
||||||
|
event_due: bool,
|
||||||
|
requests: Sender<()>,
|
||||||
|
input: Receiver<TEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputParser {
|
impl InputParser {
|
||||||
fn new(event_sink: chan::Sender<Event>) -> Self {
|
// Creates a non-blocking abstraction over the usual blocking input
|
||||||
let (sender, receiver) = chan::async();
|
fn new() -> Self {
|
||||||
|
let (input_sender, input_receiver) = crossbeam_channel::bounded(0);
|
||||||
|
let (request_sender, request_receiver) = crossbeam_channel::bounded(0);
|
||||||
|
|
||||||
let resize = chan_signal::notify(&[chan_signal::Signal::WINCH]);
|
// This thread will stop after an event when `InputParser` is dropped.
|
||||||
|
|
||||||
// Fill the input channel
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
for key in ::std::io::stdin().events() {
|
let stdin = ::std::io::stdin();
|
||||||
if let Ok(key) = key {
|
let stdin = stdin.lock();
|
||||||
sender.send(key)
|
let mut events = stdin.events();
|
||||||
}
|
|
||||||
|
for _ in request_receiver {
|
||||||
|
let event: Result<TEvent, ::std::io::Error> = events.next().unwrap();
|
||||||
|
input_sender.send(event.unwrap());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
InputParser {
|
InputParser {
|
||||||
resize,
|
|
||||||
event_sink,
|
|
||||||
last_button: None,
|
last_button: None,
|
||||||
input: receiver,
|
input: input_receiver,
|
||||||
|
requests: request_sender,
|
||||||
|
event_due: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// We pledge to want input.
|
||||||
|
///
|
||||||
|
/// If we were already expecting input, this is a NO-OP.
|
||||||
|
fn request(&mut self) {
|
||||||
|
if !self.event_due {
|
||||||
|
self.requests.send(());
|
||||||
|
self.event_due = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peek(&mut self) -> Option<Event> {
|
||||||
|
self.request();
|
||||||
|
|
||||||
|
let timeout = ::std::time::Duration::from_millis(10);
|
||||||
|
|
||||||
|
let input = select! {
|
||||||
|
recv(self.input, input) => {
|
||||||
|
input
|
||||||
|
}
|
||||||
|
recv(crossbeam_channel::after(timeout)) => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// We got what we came for.
|
||||||
|
self.event_due = false;
|
||||||
|
Some(self.map_key(input.unwrap()))
|
||||||
|
}
|
||||||
|
|
||||||
fn next_event(&mut self) -> Event {
|
fn next_event(&mut self) -> Event {
|
||||||
let result;
|
self.request();
|
||||||
{
|
|
||||||
let input = &self.input;
|
|
||||||
let resize = &self.resize;
|
|
||||||
|
|
||||||
chan_select!{
|
let input = self.input.recv().unwrap();
|
||||||
resize.recv() => return Event::WindowResize,
|
self.event_due = false;
|
||||||
input.recv() -> input => result = Some(input.unwrap()),
|
self.map_key(input)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.map_key(result.unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_next(&mut self) {
|
|
||||||
let event = self.next_event();
|
|
||||||
self.event_sink.send(event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_key(&mut self, event: TEvent) -> Event {
|
fn map_key(&mut self, event: TEvent) -> Event {
|
||||||
@ -266,21 +290,41 @@ impl backend::Backend for Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn start_input_thread(
|
fn start_input_thread(
|
||||||
&mut self, event_sink: chan::Sender<Event>,
|
&mut self, event_sink: Sender<Option<Event>>,
|
||||||
stops: chan::Receiver<bool>,
|
input_request: Receiver<backend::InputRequest>,
|
||||||
) {
|
) {
|
||||||
let mut parser = InputParser::new(event_sink);
|
let running = Arc::new(AtomicBool::new(true));
|
||||||
thread::spawn(move || {
|
let resize_sender = event_sink.clone();
|
||||||
loop {
|
let signals = Signals::new(&[libc::SIGWINCH]).unwrap();
|
||||||
// This sends events to the event sender.
|
|
||||||
parser.parse_next();
|
|
||||||
|
|
||||||
if stops.recv() != Some(false) {
|
let resize_running = Arc::clone(&running);
|
||||||
// If the channel was closed or if `true` was sent, abort.
|
thread::spawn(move || {
|
||||||
break;
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let mut parser = InputParser::new();
|
||||||
|
thread::spawn(move || {
|
||||||
|
for req in input_request {
|
||||||
|
match req {
|
||||||
|
backend::InputRequest::Peek => {
|
||||||
|
event_sink.send(parser.peek());
|
||||||
|
}
|
||||||
|
backend::InputRequest::Block => {
|
||||||
|
event_sink.send(Some(parser.next_event()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
running.store(false, Ordering::Relaxed);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
120
src/cursive.rs
120
src/cursive.rs
@ -3,7 +3,7 @@ use std::collections::HashMap;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use chan;
|
use crossbeam_channel::{self, Receiver, Sender};
|
||||||
|
|
||||||
use backend;
|
use backend;
|
||||||
use direction;
|
use direction;
|
||||||
@ -39,15 +39,15 @@ pub struct Cursive {
|
|||||||
|
|
||||||
backend: Box<backend::Backend>,
|
backend: Box<backend::Backend>,
|
||||||
|
|
||||||
cb_source: chan::Receiver<Box<CbFunc>>,
|
cb_source: Receiver<Box<CbFunc>>,
|
||||||
cb_sink: chan::Sender<Box<CbFunc>>,
|
cb_sink: Sender<Box<CbFunc>>,
|
||||||
|
|
||||||
event_source: chan::Receiver<Event>,
|
event_source: Receiver<Option<Event>>,
|
||||||
event_sink: chan::Sender<Event>,
|
event_sink: Sender<Option<Event>>,
|
||||||
|
|
||||||
// Sends true or false after each event.
|
// Sends true or false after each event.
|
||||||
stop_sink: chan::Sender<bool>,
|
input_trigger: Sender<backend::InputRequest>,
|
||||||
received_event: bool,
|
expecting_event: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes one of the possible interruptions we should handle.
|
/// Describes one of the possible interruptions we should handle.
|
||||||
@ -130,13 +130,13 @@ impl Cursive {
|
|||||||
{
|
{
|
||||||
let theme = theme::load_default();
|
let theme = theme::load_default();
|
||||||
|
|
||||||
let (cb_sink, cb_source) = chan::async();
|
let (cb_sink, cb_source) = crossbeam_channel::unbounded();
|
||||||
let (event_sink, event_source) = chan::async();
|
let (event_sink, event_source) = crossbeam_channel::unbounded();
|
||||||
|
|
||||||
let (stop_sink, stop_source) = chan::async();
|
let (input_sink, input_source) = crossbeam_channel::unbounded();
|
||||||
|
|
||||||
let mut backend = backend_init();
|
let mut backend = backend_init();
|
||||||
backend.start_input_thread(event_sink.clone(), stop_source);
|
backend.start_input_thread(event_sink.clone(), input_source);
|
||||||
|
|
||||||
Cursive {
|
Cursive {
|
||||||
fps: 0,
|
fps: 0,
|
||||||
@ -152,8 +152,8 @@ impl Cursive {
|
|||||||
event_source,
|
event_source,
|
||||||
event_sink,
|
event_sink,
|
||||||
backend,
|
backend,
|
||||||
stop_sink,
|
input_trigger: input_sink,
|
||||||
received_event: false,
|
expecting_event: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,7 +214,7 @@ impl Cursive {
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// [`set_fps`]: #method.set_fps
|
/// [`set_fps`]: #method.set_fps
|
||||||
pub fn cb_sink(&self) -> &chan::Sender<Box<CbFunc>> {
|
pub fn cb_sink(&self) -> &Sender<Box<CbFunc>> {
|
||||||
&self.cb_sink
|
&self.cb_sink
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -585,36 +585,51 @@ impl Cursive {
|
|||||||
self.screen_mut().reposition_layer(layer, position);
|
self.screen_mut().reposition_layer(layer, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait until something happens.
|
fn peek(&mut self) -> Option<Interruption> {
|
||||||
fn poll(&mut self) -> Interruption {
|
// First, try a callback
|
||||||
let input_channel = &self.event_source;
|
select! {
|
||||||
let cb_channel = &self.cb_source;
|
// Skip to input if nothing is ready
|
||||||
|
default => (),
|
||||||
|
recv(self.cb_source, cb) => return Some(Interruption::Callback(cb.unwrap())),
|
||||||
|
}
|
||||||
|
|
||||||
self.backend
|
// No callback? Check input then
|
||||||
.prepare_input(&self.event_sink, Duration::from_millis(30));
|
if self.expecting_event {
|
||||||
|
// We're already blocking.
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
if self.fps > 0 {
|
self.input_trigger.send(backend::InputRequest::Peek);
|
||||||
let timeout = 1000 / self.fps;
|
self.backend.prepare_input(&self.event_sink, backend::InputRequest::Peek);
|
||||||
let timeout = chan::after_ms(timeout);
|
self.event_source.recv().unwrap().map(Interruption::Event)
|
||||||
chan_select! {
|
}
|
||||||
input_channel.recv() -> input => {
|
|
||||||
return Interruption::Event(input.unwrap())
|
/// Wait until something happens.
|
||||||
},
|
///
|
||||||
cb_channel.recv() -> cb => {
|
/// If `peek` is `true`, return `None` immediately if nothing is ready.
|
||||||
return Interruption::Callback(cb.unwrap())
|
fn poll(&mut self) -> Option<Interruption> {
|
||||||
},
|
if !self.expecting_event {
|
||||||
timeout.recv() => {
|
self.input_trigger.send(backend::InputRequest::Block);
|
||||||
return Interruption::Timeout;
|
self.backend.prepare_input(&self.event_sink, backend::InputRequest::Block);
|
||||||
},
|
self.expecting_event = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let timeout = if self.fps > 0 {
|
||||||
|
Duration::from_millis(1000 / self.fps as u64)
|
||||||
} else {
|
} else {
|
||||||
chan_select! {
|
// Defaults to 1 refresh per hour.
|
||||||
input_channel.recv() -> input => {
|
Duration::from_secs(3600)
|
||||||
return Interruption::Event(input.unwrap())
|
};
|
||||||
},
|
|
||||||
cb_channel.recv() -> cb => {
|
select! {
|
||||||
return Interruption::Callback(cb.unwrap())
|
recv(self.event_source, event) => {
|
||||||
},
|
event.unwrap().map(Interruption::Event)
|
||||||
|
},
|
||||||
|
recv(self.cb_source, cb) => {
|
||||||
|
cb.map(Interruption::Callback)
|
||||||
|
},
|
||||||
|
recv(crossbeam_channel::after(timeout)) => {
|
||||||
|
Some(Interruption::Timeout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -720,13 +735,21 @@ impl Cursive {
|
|||||||
self.draw();
|
self.draw();
|
||||||
self.backend.refresh();
|
self.backend.refresh();
|
||||||
|
|
||||||
// Wait for next event.
|
// First, read all events available while peeking.
|
||||||
if self.received_event {
|
while let Some(interruption) = self.peek() {
|
||||||
// Now tell the backend whether he sould keep receiving.
|
self.handle_interruption(interruption);
|
||||||
self.stop_sink.send(!self.running);
|
if !self.running {
|
||||||
self.received_event = false;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
match self.poll() {
|
|
||||||
|
if let Some(interruption) = self.poll() {
|
||||||
|
self.handle_interruption(interruption);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_interruption(&mut self, interruption: Interruption) {
|
||||||
|
match interruption {
|
||||||
Interruption::Event(event) => {
|
Interruption::Event(event) => {
|
||||||
// eprintln!("{:?}, {:?}", event, self.screen_size());
|
// eprintln!("{:?}, {:?}", event, self.screen_size());
|
||||||
if event == Event::Exit {
|
if event == Event::Exit {
|
||||||
@ -735,6 +758,7 @@ impl Cursive {
|
|||||||
|
|
||||||
if event == Event::WindowResize {
|
if event == Event::WindowResize {
|
||||||
self.clear();
|
self.clear();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Event::Mouse {
|
if let Event::Mouse {
|
||||||
@ -772,7 +796,7 @@ impl Cursive {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ok, we processed the event.
|
// Ok, we processed the event.
|
||||||
self.received_event = true;
|
self.expecting_event = false;
|
||||||
}
|
}
|
||||||
Interruption::Callback(cb) => {
|
Interruption::Callback(cb) => {
|
||||||
cb.call_box(self);
|
cb.call_box(self);
|
||||||
|
@ -113,7 +113,7 @@ impl EventResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `self` if it is not `Event::Ignored`, otherwise returns `f()`.
|
/// Returns `self` if it is not `EventResult::Ignored`, otherwise returns `f()`.
|
||||||
pub fn or_else<F>(self, f: F) -> Self
|
pub fn or_else<F>(self, f: F) -> Self
|
||||||
where
|
where
|
||||||
F: FnOnce() -> EventResult,
|
F: FnOnce() -> EventResult,
|
||||||
|
@ -68,7 +68,7 @@ extern crate enumset;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate chan;
|
extern crate crossbeam_channel;
|
||||||
|
|
||||||
#[cfg(any(feature = "ncurses", feature = "pancurses"))]
|
#[cfg(any(feature = "ncurses", feature = "pancurses"))]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@ -77,7 +77,7 @@ extern crate maplit;
|
|||||||
// We use chan_signal to detect SIGWINCH.
|
// We use chan_signal to detect SIGWINCH.
|
||||||
// It's not how windows work, so no need to use that.
|
// It's not how windows work, so no need to use that.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
extern crate chan_signal;
|
extern crate signal_hook;
|
||||||
|
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
extern crate num;
|
extern crate num;
|
||||||
|
Loading…
Reference in New Issue
Block a user