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>>(&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 { + key_bindings: Vec, + mouse_bindings: Vec, notifier: N, - input_processor: input::Processor, + mouse: Mouse, terminal: Arc>, resize_tx: mpsc::Sender<(u32, u32)>, ref_test: bool, + size_info: SizeInfo, pub selection: Selection, } @@ -29,11 +70,11 @@ pub struct Processor { /// Currently this just forwards the notice to the input processor. impl OnResize for Processor { fn on_resize(&mut self, size: &SizeInfo) { - self.input_processor.resize(size); + self.size_info = size.to_owned(); } } -impl Processor { +impl Processor { /// 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 Processor { config: &Config, ref_test: bool, ) -> Processor { - 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 Processor { 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 Processor { *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 Processor { /// 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; + + // 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(); } } diff --git a/src/event_loop.rs b/src/event_loop.rs index 44d71f94..a6eed40e 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -11,6 +11,7 @@ use mio::unix::EventedFd; use ansi; use display; +use event; use term::Term; use util::thread; use sync::FairMutex; @@ -52,6 +53,21 @@ pub struct State { parser: ansi::Processor, } +pub struct Notifier(pub ::mio::channel::Sender); + +impl event::Notify for Notifier { + fn notify(&mut self, bytes: B) + where B: Into> + { + let bytes = bytes.into(); + match self.0.send(Msg::Input(bytes)) { + Ok(_) => (), + Err(_) => panic!("expected send event loop msg"), + } + } +} + + impl Default for State { fn default() -> State { State { diff --git a/src/input.rs b/src/input.rs index cad029b9..7e499e78 100644 --- a/src/input.rs +++ b/src/input.rs @@ -23,19 +23,17 @@ //! APIs //! //! TODO handling xmodmap would be good -use std::borrow::Cow; - use copypasta::{Clipboard, Load, Store}; use glutin::{ElementState, VirtualKeyCode, MouseButton}; use glutin::{Mods, mods}; use glutin::{TouchPhase, MouseScrollDelta}; -use config::Config; -use event_loop; +use event::Notify; use index::{Line, Column, Side, Location}; use selection::Selection; use term::mode::{self, TermMode}; use term::{self, Term}; +use event::Mouse; /// Processes input from glutin. /// @@ -43,58 +41,18 @@ use term::{self, Term}; /// are activated. /// /// TODO also need terminal state when processing input -pub struct Processor { - key_bindings: Vec, - mouse_bindings: Vec, - mouse: Mouse, - size_info: term::SizeInfo, +pub struct Processor<'a, N: 'a> { + pub key_bindings: &'a [KeyBinding], + pub mouse_bindings: &'a [MouseBinding], + pub ctx: ActionContext<'a, N>, } -/// State of the mouse -pub struct Mouse { - x: u32, - y: u32, - left_button_state: ElementState, - scroll_px: i32, - line: Line, - column: Column, - 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, - } - } -} - -/// Types that are notified of escape sequences from the `input::Processor`. -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>>(&mut self, B); -} - -pub struct LoopNotifier(pub ::mio::channel::Sender); - -impl Notify for LoopNotifier { - fn notify(&mut self, bytes: B) - where B: Into> - { - let bytes = bytes.into(); - match self.0.send(event_loop::Msg::Input(bytes)) { - Ok(_) => (), - Err(_) => panic!("expected send event loop msg"), - } - } +pub struct ActionContext<'a, N: 'a> { + pub notifier: &'a mut N, + pub terminal: &'a Term, + pub selection: &'a mut Selection, + pub mouse: &'a mut Mouse, + pub size_info: &'a term::SizeInfo, } /// Describes a state and action to take in that state @@ -142,8 +100,8 @@ impl KeyBinding { } #[inline] - fn execute<'a, N: Notify>(&self, context: &mut ActionContext<'a, N>) { - self.binding.action.execute(context) + fn execute<'a, N: Notify>(&self, ctx: &mut ActionContext<'a, N>) { + self.binding.action.execute(ctx) } } @@ -162,8 +120,8 @@ impl MouseBinding { } #[inline] - fn execute<'a, N: Notify>(&self, context: &mut ActionContext<'a, N>) { - self.binding.action.execute(context) + fn execute<'a, N: Notify>(&self, ctx: &mut ActionContext<'a, N>) { + self.binding.action.execute(ctx) } } @@ -186,12 +144,15 @@ impl Action { #[inline] fn execute<'a, N: Notify>(&self, ctx: &mut ActionContext<'a, N>) { match *self { - Action::Esc(ref s) => ctx.notifier.notify(s.clone().into_bytes()), + Action::Esc(ref s) => { + ctx.notifier.notify(s.clone().into_bytes()) + }, Action::Copy => { // so... need access to terminal state. and the selection. unimplemented!(); }, - Action::Paste | Action::PasteSelection => { + Action::Paste | + Action::PasteSelection => { let clip = Clipboard::new().expect("get clipboard"); clip.load_selection() .map(|contents| { @@ -247,64 +208,41 @@ impl Binding { } } -pub struct ActionContext<'a, N: 'a> { - pub notifier: &'a mut N, - pub terminal: &'a Term, - pub selection: &'a mut Selection -} - -impl Processor { - pub fn resize(&mut self, size_info: &term::SizeInfo) { - self.size_info = size_info.to_owned(); - } - - pub fn new(config: &Config, size_info: &term::SizeInfo) -> Processor { - Processor { - key_bindings: config.key_bindings().to_vec(), - mouse_bindings: config.mouse_bindings().to_vec(), - mouse: Mouse::default(), - size_info: size_info.to_owned(), - } - } - +impl<'a, N: Notify + 'a> Processor<'a, N> { #[inline] - pub fn mouse_moved(&mut self, selection: &mut Selection, mode: TermMode, x: u32, y: u32) { + pub fn mouse_moved(&mut self, x: u32, y: u32) { // Record mouse position within window. Pixel coordinates are *not* // translated to grid coordinates here since grid coordinates are rarely // needed and the mouse position updates frequently. - self.mouse.x = x; - self.mouse.y = y; + self.ctx.mouse.x = x; + self.ctx.mouse.y = y; - if let Some((line, column)) = self.size_info.pixels_to_coords(x as usize, y as usize) { - self.mouse.line = line; - self.mouse.column = column; + if let Some((line, column)) = self.ctx.size_info.pixels_to_coords(x as usize, y as usize) { + self.ctx.mouse.line = line; + self.ctx.mouse.column = column; - let cell_x = x as usize % self.size_info.cell_width as usize; - let half_cell_width = (self.size_info.cell_width / 2.0) as usize; + let cell_x = x as usize % self.ctx.size_info.cell_width as usize; + let half_cell_width = (self.ctx.size_info.cell_width / 2.0) as usize; - self.mouse.cell_side = if cell_x > half_cell_width { + self.ctx.mouse.cell_side = if cell_x > half_cell_width { Side::Right } else { Side::Left }; - if self.mouse.left_button_state == ElementState::Pressed && - !mode.contains(mode::MOUSE_REPORT_CLICK) + if self.ctx.mouse.left_button_state == ElementState::Pressed && + !self.ctx.terminal.mode().contains(mode::MOUSE_REPORT_CLICK) { - selection.update(Location { + self.ctx.selection.update(Location { line: line, col: column - }, self.mouse.cell_side); + }, self.ctx.mouse.cell_side); } } } - pub fn mouse_report<'a, N: Notify>( - &mut self, - button: u8, - context: &mut ActionContext<'a, N> - ) { - let (line, column) = (self.mouse.line, self.mouse.column); + pub fn mouse_report(&mut self, button: u8) { + let (line, column) = (self.ctx.mouse.line, self.ctx.mouse.column); if line < Line(223) && column < Column(223) { let msg = vec![ @@ -316,35 +254,27 @@ impl Processor { 32 + 1 + line.0 as u8, ]; - context.notifier.notify(msg); + self.ctx.notifier.notify(msg); } } - pub fn on_mouse_press<'a, N: Notify>( - &mut self, - context: &mut ActionContext<'a, N> - ) { - if context.terminal.mode().contains(mode::MOUSE_REPORT_CLICK) { - self.mouse_report(0, context); + pub fn on_mouse_press(&mut self) { + if self.ctx.terminal.mode().contains(mode::MOUSE_REPORT_CLICK) { + self.mouse_report(0); return; } - context.selection.clear(); + self.ctx.selection.clear(); } - pub fn on_mouse_release<'a, N: Notify>(&mut self, context: &mut ActionContext<'a, N>) { - if context.terminal.mode().contains(mode::MOUSE_REPORT_CLICK) { - self.mouse_report(3, context); + pub fn on_mouse_release(&mut self) { + if self.ctx.terminal.mode().contains(mode::MOUSE_REPORT_CLICK) { + self.mouse_report(3); return; } } - pub fn on_mouse_wheel<'a, N: Notify>( - &mut self, - context: &mut ActionContext<'a, N>, - delta: MouseScrollDelta, - phase: TouchPhase, - ) { + pub fn on_mouse_wheel(&mut self, delta: MouseScrollDelta, phase: TouchPhase) { match delta { MouseScrollDelta::LineDelta(_columns, lines) => { let code = if lines > 0.0 { @@ -354,29 +284,29 @@ impl Processor { }; for _ in 0..(lines.abs() as usize) { - self.mouse_report(code, context); + self.mouse_report(code); } }, MouseScrollDelta::PixelDelta(_x, y) => { match phase { TouchPhase::Started => { // Reset offset to zero - self.mouse.scroll_px = 0; + self.ctx.mouse.scroll_px = 0; }, TouchPhase::Moved => { - self.mouse.scroll_px += y as i32; - let height = self.size_info.cell_height as i32; + self.ctx.mouse.scroll_px += y as i32; + let height = self.ctx.size_info.cell_height as i32; - while self.mouse.scroll_px.abs() >= height { - let button = if self.mouse.scroll_px > 0 { - self.mouse.scroll_px -= height; + while self.ctx.mouse.scroll_px.abs() >= height { + let button = if self.ctx.mouse.scroll_px > 0 { + self.ctx.mouse.scroll_px -= height; 64 } else { - self.mouse.scroll_px += height; + self.ctx.mouse.scroll_px += height; 65 }; - self.mouse_report(button, context); + self.mouse_report(button); } }, _ => (), @@ -385,22 +315,17 @@ impl Processor { } } - pub fn mouse_input<'a, N: Notify>( - &mut self, - context: &mut ActionContext<'a, N>, - state: ElementState, - button: MouseButton, - ) { + pub fn mouse_input(&mut self, state: ElementState, button: MouseButton) { if let MouseButton::Left = button { // TODO handle state changes - if self.mouse.left_button_state != state { - self.mouse.left_button_state = state; + if self.ctx.mouse.left_button_state != state { + self.ctx.mouse.left_button_state = state; match state { ElementState::Pressed => { - self.on_mouse_press(context); + self.on_mouse_press(); }, ElementState::Released => { - self.on_mouse_release(context); + self.on_mouse_release(); } } } @@ -410,17 +335,11 @@ impl Processor { return; } - Processor::process_mouse_bindings( - context, - &self.mouse_bindings[..], - mods::NONE, - button - ); + self.process_mouse_bindings(mods::NONE, button); } - pub fn process_key<'a, N: Notify>( + pub fn process_key( &mut self, - context: &mut ActionContext<'a, N>, state: ElementState, key: Option, mods: Mods, @@ -432,13 +351,13 @@ impl Processor { return; } - if Processor::process_key_bindings(context, &self.key_bindings[..], mods, key) { + if self.process_key_bindings(mods, key) { return; } // Didn't process a binding; print the provided character if let Some(string) = string { - context.notifier.notify(string.into_bytes()); + self.ctx.notifier.notify(string.into_bytes()); } } } @@ -449,16 +368,11 @@ impl Processor { /// for its action to be executed. /// /// Returns true if an action is executed. - fn process_key_bindings<'a, N: Notify>( - context: &mut ActionContext<'a, N>, - bindings: &[KeyBinding], - mods: Mods, - key: VirtualKeyCode - ) -> bool { - for binding in bindings { - if binding.is_triggered_by(context.terminal.mode(), &mods, &key) { + fn process_key_bindings(&mut self, mods: Mods, key: VirtualKeyCode) -> bool { + for binding in self.key_bindings { + if binding.is_triggered_by(self.ctx.terminal.mode(), &mods, &key) { // binding was triggered; run the action - binding.execute(context); + binding.execute(&mut self.ctx); return true; } } @@ -472,27 +386,17 @@ impl Processor { /// for its action to be executed. /// /// Returns true if an action is executed. - fn process_mouse_bindings<'a, N: Notify>( - context: &mut ActionContext<'a, N>, - bindings: &[MouseBinding], - mods: Mods, - button: MouseButton - ) -> bool { - for binding in bindings { - if binding.is_triggered_by(context.terminal.mode(), &mods, &button) { + fn process_mouse_bindings(&mut self, mods: Mods, button: MouseButton) -> bool { + for binding in self.mouse_bindings { + if binding.is_triggered_by(self.ctx.terminal.mode(), &mods, &button) { // binding was triggered; run the action - binding.execute(context); + binding.execute(&mut self.ctx); return true; } } false } - - pub fn update_config(&mut self, config: &Config) { - self.key_bindings = config.key_bindings().to_vec(); - self.mouse_bindings = config.mouse_bindings().to_vec(); - } } #[cfg(test)] diff --git a/src/main.rs b/src/main.rs index 54f4abc8..64fa5991 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,8 +26,7 @@ use alacritty::cli; use alacritty::config::{self, Config}; use alacritty::display::Display; use alacritty::event; -use alacritty::event_loop::EventLoop; -use alacritty::input; +use alacritty::event_loop::{self, EventLoop}; use alacritty::sync::FairMutex; use alacritty::term::{Term}; use alacritty::tty::{self, process_should_exit}; @@ -108,7 +107,7 @@ fn run(mut config: Config, options: cli::Options) -> Result<(), Box> { // // Need the Rc> here since a ref is shared in the resize callback let mut processor = event::Processor::new( - input::LoopNotifier(loop_tx), + event_loop::Notifier(loop_tx), terminal.clone(), display.resize_channel(), &config,