use {
super::{
EscapeSequence,
TimedEvent,
},
crate::{
crossterm::{
self,
event::{
Event,
KeyCode,
KeyEvent,
KeyModifiers,
MouseButton,
MouseEvent,
MouseEventKind,
},
terminal,
},
errors::Error,
},
crokey::Combiner,
crossbeam::channel::{
bounded,
unbounded,
Receiver,
Sender,
},
std::{
sync::{
atomic::{
AtomicUsize,
Ordering,
},
Arc,
},
thread,
time::{
Duration,
Instant,
},
},
};
const DOUBLE_CLICK_MAX_DURATION: Duration = Duration::from_millis(700);
const ESCAPE_SEQUENCE_CHANNEL_SIZE: usize = 10;
struct TimedClick {
time: Instant,
x: u16,
y: u16,
}
#[derive(Clone, Copy, Debug)]
pub struct EventSourceOptions {
pub combine_keys: bool,
pub mandate_modifier_for_multiple_keys: bool,
pub discard_raw_key_events: bool,
pub discard_mouse_move: bool,
pub discard_mouse_drag: bool,
}
pub struct EventSource {
is_combining_keys: bool,
rx_events: Receiver<TimedEvent>,
rx_seqs: Receiver<EscapeSequence>,
tx_quit: Sender<bool>,
event_count: Arc<AtomicUsize>,
}
impl Default for EventSourceOptions {
fn default() -> Self {
Self {
combine_keys: false,
mandate_modifier_for_multiple_keys: true,
discard_raw_key_events: true,
discard_mouse_move: true,
discard_mouse_drag: false,
}
}
}
fn is_seq_start(key: KeyEvent) -> bool {
key.code == KeyCode::Char('_') && key.modifiers == KeyModifiers::ALT
}
fn is_seq_end(key: KeyEvent) -> bool {
key.code == KeyCode::Char('\\') && key.modifiers == KeyModifiers::ALT
}
impl EventSource {
pub fn new() -> Result<Self, Error> {
Self::with_options(EventSourceOptions::default())
}
pub fn supports_multi_key_combinations(&self) -> bool {
self.is_combining_keys
}
pub fn with_options(options: EventSourceOptions) -> Result<Self, Error> {
let mut combiner = Combiner::default();
terminal::enable_raw_mode()?;
let is_combining_keys = if options.combine_keys {
combiner.enable_combining()?
} else {
false
};
combiner.set_mandate_modifier_for_multiple_keys(options.mandate_modifier_for_multiple_keys);
let (tx_events, rx_events) = unbounded();
let (tx_seqs, rx_seqs) = bounded(ESCAPE_SEQUENCE_CHANNEL_SIZE);
let (tx_quit, rx_quit) = unbounded();
let event_count = Arc::new(AtomicUsize::new(0));
let internal_event_count = Arc::clone(&event_count);
thread::spawn(move || {
let mut last_up: Option<TimedClick> = None;
let mut current_escape_sequence: Option<EscapeSequence> = None;
let send_and_wait = |event| {
internal_event_count.fetch_add(1, Ordering::SeqCst);
if tx_events.send(event).is_err() {
true } else {
match rx_quit.recv() {
Ok(false) => false,
_ => true,
}
}
};
loop {
let ct_event = match crossterm::event::read() {
Ok(e) => e,
_ => {
continue;
}
};
let in_seq = current_escape_sequence.is_some();
if in_seq {
if let crossterm::event::Event::Key(key) = ct_event {
if is_seq_end(key) {
let mut seq = current_escape_sequence.take().unwrap();
seq.keys.push(key);
if tx_seqs.try_send(seq).is_err() {
}
continue;
} else if !key
.modifiers
.intersects(KeyModifiers::ALT | KeyModifiers::CONTROL)
{
current_escape_sequence.as_mut().unwrap().keys.push(key);
continue;
}
}
let seq = current_escape_sequence.take().unwrap();
for key in seq.keys {
let mut timed_event = TimedEvent::new(Event::Key(key));
timed_event.key_combination = combiner.transform(key);
if options.discard_raw_key_events && timed_event.key_combination.is_none() {
continue;
}
if send_and_wait(timed_event) {
return;
}
}
} else if let crossterm::event::Event::Key(key) = ct_event {
if is_seq_start(key) {
current_escape_sequence = Some(EscapeSequence { keys: vec![key] });
continue;
}
}
if let Event::Mouse(mouse_event) = ct_event {
if options.discard_mouse_move && mouse_event.kind == MouseEventKind::Moved {
continue;
}
if options.discard_mouse_drag
&& matches!(mouse_event.kind, MouseEventKind::Drag(_))
{
continue;
}
}
let mut timed_event = TimedEvent::new(ct_event);
if let Event::Key(key) = &timed_event.event {
timed_event.key_combination = combiner.transform(*key);
if options.discard_raw_key_events && timed_event.key_combination.is_none() {
continue;
}
}
if let Event::Mouse(MouseEvent {
kind, column, row, ..
}) = timed_event.event
{
if matches!(
kind,
MouseEventKind::Down(MouseButton::Left)
| MouseEventKind::Up(MouseButton::Left)
) {
if let Some(TimedClick { time, x, y }) = last_up {
if column == x
&& row == y
&& timed_event.time - time < DOUBLE_CLICK_MAX_DURATION
{
timed_event.double_click = true;
}
}
if kind == MouseEventKind::Up(MouseButton::Left) {
last_up = Some(TimedClick {
time: timed_event.time,
x: column,
y: row,
});
}
}
}
if send_and_wait(timed_event) {
return;
}
}
});
Ok(EventSource {
is_combining_keys,
rx_events,
rx_seqs,
tx_quit,
event_count,
})
}
pub fn unblock(&self, quit: bool) {
self.tx_quit.send(quit).unwrap();
}
pub fn shared_event_count(&self) -> Arc<AtomicUsize> {
Arc::clone(&self.event_count)
}
pub fn receiver(&self) -> Receiver<TimedEvent> {
self.rx_events.clone()
}
pub fn escape_sequence_receiver(&self) -> Receiver<EscapeSequence> {
self.rx_seqs.clone()
}
}
impl Drop for EventSource {
fn drop(&mut self) {
terminal::disable_raw_mode().unwrap();
}
}