aboutsummaryrefslogtreecommitdiff
path: root/src/event.rs
diff options
context:
space:
mode:
authorJoe Wilm <joe@jwilm.com>2016-12-26 18:33:27 -0500
committerJoe Wilm <joe@jwilm.com>2016-12-26 18:39:30 -0500
commit358c9fa17d9b088bae79f7d352a8cc21878f6303 (patch)
tree40926a8e496360913e3ebd5a0991e7b118eef85f /src/event.rs
parent3b7c7377c913b92c52f45660fa94001db38c8bc6 (diff)
downloadalacritty-358c9fa17d9b088bae79f7d352a8cc21878f6303.tar.gz
alacritty-358c9fa17d9b088bae79f7d352a8cc21878f6303.zip
Major cleanup for event handling
The event handling code grew organically over time, and with that came a number of warts. The primary issue was with passing some random selection of arguments to the input::Processor based on what the input was. There was the issue that if multiple events were drained from a single PollEventsIterator, the terminal mutex was potentially locked and unlocked many times. Finally, and perhaps most importantly, there was no good way to pass necessary state to the Action executor without going through several API layers. To fix all that, the input::ActionContext and input::Processor are now generated once per call to the event::Processor. The input::Processor holds onto the ActionContext so that it doesn't need to be passed through layers of function calls. When a binding is activated, the ActionContext is simply passed to the handler. This did have the effect of breaking the input::Processor tests (specifically, those relating to bindings). The issue was not addressed in this commit since a larger refactor of the bindings is planned which should also improve testability.
Diffstat (limited to 'src/event.rs')
-rw-r--r--src/event.rs186
1 files changed, 119 insertions, 67 deletions
diff --git a/src/event.rs b/src/event.rs
index 706e03fe..0c4c701b 100644
--- a/src/event.rs
+++ b/src/event.rs
@@ -1,26 +1,67 @@
//! Process window events
+use std::borrow::Cow;
use std::fs::File;
use std::io::Write;
use std::sync::{Arc, mpsc};
use serde_json as json;
-use glutin;
+use glutin::{self, ElementState};
+use index::{Line, Column, Side};
use config::Config;
use display::OnResize;
-use input::{self, ActionContext};
+use input::{self, ActionContext, MouseBinding, KeyBinding};
use selection::Selection;
use sync::FairMutex;
use term::{Term, SizeInfo};
use window::Window;
+/// Byte sequences are sent to a `Notify` in response to some events
+pub trait Notify {
+ /// Notify that an escape sequence should be written to the pty
+ ///
+ /// TODO this needs to be able to error somehow
+ fn notify<B: Into<Cow<'static, [u8]>>>(&mut self, B);
+}
+
+/// State of the mouse
+pub struct Mouse {
+ pub x: u32,
+ pub y: u32,
+ pub left_button_state: ElementState,
+ pub scroll_px: i32,
+ pub line: Line,
+ pub column: Column,
+ pub cell_side: Side
+}
+
+impl Default for Mouse {
+ fn default() -> Mouse {
+ Mouse {
+ x: 0,
+ y: 0,
+ left_button_state: ElementState::Released,
+ scroll_px: 0,
+ line: Line(0),
+ column: Column(0),
+ cell_side: Side::Left,
+ }
+ }
+}
+
/// The event processor
+///
+/// Stores some state from received events and dispatches actions when they are
+/// triggered.
pub struct Processor<N> {
+ key_bindings: Vec<KeyBinding>,
+ mouse_bindings: Vec<MouseBinding>,
notifier: N,
- input_processor: input::Processor,
+ mouse: Mouse,
terminal: Arc<FairMutex<Term>>,
resize_tx: mpsc::Sender<(u32, u32)>,
ref_test: bool,
+ size_info: SizeInfo,
pub selection: Selection,
}
@@ -29,11 +70,11 @@ pub struct Processor<N> {
/// Currently this just forwards the notice to the input processor.
impl<N> OnResize for Processor<N> {
fn on_resize(&mut self, size: &SizeInfo) {
- self.input_processor.resize(size);
+ self.size_info = size.to_owned();
}
}
-impl<N: input::Notify> Processor<N> {
+impl<N: Notify> Processor<N> {
/// Create a new event processor
///
/// Takes a writer which is expected to be hooked up to the write end of a
@@ -45,33 +86,44 @@ impl<N: input::Notify> Processor<N> {
config: &Config,
ref_test: bool,
) -> Processor<N> {
- let input_processor = {
+ let size_info = {
let terminal = terminal.lock();
- input::Processor::new(config, terminal.size_info())
+ terminal.size_info().to_owned()
};
Processor {
+ key_bindings: config.key_bindings().to_vec(),
+ mouse_bindings: config.mouse_bindings().to_vec(),
notifier: notifier,
terminal: terminal,
- input_processor: input_processor,
resize_tx: resize_tx,
ref_test: ref_test,
+ mouse: Default::default(),
selection: Default::default(),
+ size_info: size_info,
}
}
- fn handle_event(&mut self, event: glutin::Event, wakeup_request: &mut bool) {
+ /// Handle events from glutin
+ ///
+ /// Doesn't take self mutably due to borrow checking. Kinda uggo but w/e.
+ fn handle_event<'a>(
+ processor: &mut input::Processor<'a, N>,
+ event: glutin::Event,
+ wakeup_request: &mut bool,
+ ref_test: bool,
+ resize_tx: &mpsc::Sender<(u32, u32)>,
+ ) {
match event {
glutin::Event::Closed => {
- if self.ref_test {
+ if ref_test {
// dump grid state
- let terminal = self.terminal.lock();
- let grid = terminal.grid();
+ let grid = processor.ctx.terminal.grid();
let serialized_grid = json::to_string(&grid)
.expect("serialize grid");
- let serialized_size = json::to_string(terminal.size_info())
+ let serialized_size = json::to_string(processor.ctx.terminal.size_info())
.expect("serialize size");
File::create("./grid.json")
@@ -87,7 +139,7 @@ impl<N: input::Notify> Processor<N> {
panic!("window closed");
},
glutin::Event::Resized(w, h) => {
- self.resize_tx.send((w, h)).expect("send new size");
+ resize_tx.send((w, h)).expect("send new size");
// Previously, this marked the terminal state as "dirty", but
// now the wakeup_request controls whether a display update is
@@ -95,67 +147,27 @@ impl<N: input::Notify> Processor<N> {
*wakeup_request = true;
},
glutin::Event::KeyboardInput(state, _code, key, mods, string) => {
- // Acquire term lock
- let terminal = self.terminal.lock();
- let processor = &mut self.input_processor;
-
- {
- let mut context = ActionContext {
- terminal: &terminal,
- notifier: &mut self.notifier,
- selection: &mut self.selection,
- };
-
- processor.process_key(&mut context, state, key, mods, string);
- }
- self.selection.clear();
+ processor.process_key(state, key, mods, string);
+ processor.ctx.selection.clear();
},
glutin::Event::MouseInput(state, button) => {
- let terminal = self.terminal.lock();
- let processor = &mut self.input_processor;
-
- let mut context = ActionContext {
- terminal: &terminal,
- notifier: &mut self.notifier,
- selection: &mut self.selection,
- };
-
- processor.mouse_input(&mut context, state, button);
+ processor.mouse_input(state, button);
*wakeup_request = true;
},
glutin::Event::MouseMoved(x, y) => {
if x > 0 && y > 0 {
- let terminal = self.terminal.lock();
- self.input_processor.mouse_moved(
- &mut self.selection,
- *terminal.mode(),
- x as u32,
- y as u32
- );
- if !self.selection.is_empty() {
+ processor.mouse_moved(x as u32, y as u32);
+
+ if !processor.ctx.selection.is_empty() {
*wakeup_request = true;
}
}
},
glutin::Event::Focused(true) => {
- let mut terminal = self.terminal.lock();
- terminal.dirty = true;
+ *wakeup_request = true;
},
glutin::Event::MouseWheel(scroll_delta, touch_phase) => {
- let mut terminal = self.terminal.lock();
- let processor = &mut self.input_processor;
-
- let mut context = ActionContext {
- terminal: &terminal,
- notifier: &mut self.notifier,
- selection: &mut self.selection,
- };
-
- processor.on_mouse_wheel(
- &mut context,
- scroll_delta,
- touch_phase,
- );
+ processor.on_mouse_wheel(scroll_delta, touch_phase);
},
glutin::Event::Awakened => {
*wakeup_request = true;
@@ -167,19 +179,59 @@ impl<N: input::Notify> Processor<N> {
/// Process at least one event and handle any additional queued events.
pub fn process_events(&mut self, window: &Window) -> bool {
let mut wakeup_request = false;
- for event in window.wait_events() {
- self.handle_event(event, &mut wakeup_request);
- break;
+
+ // These are lazily initialized the first time an event is returned
+ // from the blocking WaitEventsIterator. Otherwise, the pty reader
+ // would be blocked the entire time we wait for input!
+ let terminal;
+ let context;
+ let mut processor: input::Processor<N>;
+
+ // Convenience macro which curries most arguments to handle_event.
+ macro_rules! process {
+ ($event:expr) => {
+ Processor::handle_event(
+ &mut processor,
+ $event,
+ &mut wakeup_request,
+ self.ref_test,
+ &self.resize_tx,
+ )
+ }
+ }
+
+ match window.wait_events().next() {
+ Some(event) => {
+ terminal = self.terminal.lock();
+ context = ActionContext {
+ terminal: &terminal,
+ notifier: &mut self.notifier,
+ selection: &mut self.selection,
+ mouse: &mut self.mouse,
+ size_info: &self.size_info,
+ };
+
+ processor = input::Processor {
+ ctx: context,
+ key_bindings: &self.key_bindings[..],
+ mouse_bindings: &self.mouse_bindings[..]
+ };
+
+ process!(event);
+ },
+ // Glutin guarantees the WaitEventsIterator never returns None.
+ None => unreachable!(),
}
for event in window.poll_events() {
- self.handle_event(event, &mut wakeup_request);
+ process!(event);
}
wakeup_request
}
pub fn update_config(&mut self, config: &Config) {
- self.input_processor.update_config(config);
+ self.key_bindings = config.key_bindings().to_vec();
+ self.mouse_bindings = config.mouse_bindings().to_vec();
}
}