diff options
Diffstat (limited to 'alacritty/src/input.rs')
-rw-r--r-- | alacritty/src/input.rs | 268 |
1 files changed, 121 insertions, 147 deletions
diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs index a9925088..867099a9 100644 --- a/alacritty/src/input.rs +++ b/alacritty/src/input.rs @@ -17,12 +17,16 @@ use std::time::{Duration, Instant}; use log::debug; use winit::dpi::PhysicalPosition; use winit::event::{ - ElementState, KeyboardInput, ModifiersState, MouseButton, MouseScrollDelta, - Touch as TouchEvent, TouchPhase, + ElementState, KeyEvent, Modifiers, MouseButton, MouseScrollDelta, Touch as TouchEvent, + TouchPhase, }; use winit::event_loop::EventLoopWindowTarget; #[cfg(target_os = "macos")] +use winit::keyboard::ModifiersKeyState; +use winit::keyboard::ModifiersState; +#[cfg(target_os = "macos")] use winit::platform::macos::{EventLoopWindowTargetExtMacOS, OptionAsAlt}; +use winit::platform::modifier_supplement::KeyEventExtModifierSupplement; use winit::window::CursorIcon; use alacritty_terminal::ansi::{ClearMode, Handler}; @@ -35,7 +39,9 @@ use alacritty_terminal::term::{ClipboardType, Term, TermMode}; use alacritty_terminal::vi_mode::ViMotion; use crate::clipboard::Clipboard; -use crate::config::{Action, BindingMode, Key, MouseAction, SearchAction, UiConfig, ViAction}; +use crate::config::{ + Action, BindingKey, BindingMode, MouseAction, SearchAction, UiConfig, ViAction, +}; use crate::display::hint::HintMatch; use crate::display::window::Window; use crate::display::{Display, SizeInfo}; @@ -88,9 +94,7 @@ pub trait ActionContext<T: EventListener> { fn mouse_mut(&mut self) -> &mut Mouse; fn mouse(&self) -> &Mouse; fn touch_purpose(&mut self) -> &mut TouchPurpose; - fn received_count(&mut self) -> &mut usize; - fn suppress_chars(&mut self) -> &mut bool; - fn modifiers(&mut self) -> &mut ModifiersState; + fn modifiers(&mut self) -> &mut Modifiers; fn scroll(&mut self, _scroll: Scroll) {} fn window(&mut self) -> &mut Window; fn display(&mut self) -> &mut Display; @@ -421,7 +425,8 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> { // Don't launch URLs if mouse has moved. self.ctx.mouse_mut().block_hint_launcher = true; - if (lmb_pressed || rmb_pressed) && (self.ctx.modifiers().shift() || !self.ctx.mouse_mode()) + if (lmb_pressed || rmb_pressed) + && (self.ctx.modifiers().state().shift_key() || !self.ctx.mouse_mode()) { self.ctx.update_selection(point, cell_side); } else if cell_changed @@ -472,14 +477,14 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> { // Calculate modifiers value. let mut mods = 0; - let modifiers = self.ctx.modifiers(); - if modifiers.shift() { + let modifiers = self.ctx.modifiers().state(); + if modifiers.shift_key() { mods += 4; } - if modifiers.alt() { + if modifiers.alt_key() { mods += 8; } - if modifiers.ctrl() { + if modifiers.control_key() { mods += 16; } @@ -539,7 +544,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> { fn on_mouse_press(&mut self, button: MouseButton) { // Handle mouse mode. - if !self.ctx.modifiers().shift() && self.ctx.mouse_mode() { + if !self.ctx.modifiers().state().shift_key() && self.ctx.mouse_mode() { self.ctx.mouse_mut().click_state = ClickState::None; let code = match button { @@ -547,7 +552,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> { MouseButton::Middle => 1, MouseButton::Right => 2, // Can't properly report more than three buttons.. - MouseButton::Other(_) => return, + MouseButton::Back | MouseButton::Forward | MouseButton::Other(_) => return, }; self.mouse_report(code, ElementState::Pressed); @@ -591,7 +596,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> { self.ctx.clear_selection(); // Start new empty selection. - if self.ctx.modifiers().ctrl() { + if self.ctx.modifiers().state().control_key() { self.ctx.start_selection(SelectionType::Block, point, side); } else { self.ctx.start_selection(SelectionType::Simple, point, side); @@ -616,13 +621,13 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> { } fn on_mouse_release(&mut self, button: MouseButton) { - if !self.ctx.modifiers().shift() && self.ctx.mouse_mode() { + if !self.ctx.modifiers().state().shift_key() && self.ctx.mouse_mode() { let code = match button { MouseButton::Left => 0, MouseButton::Middle => 1, MouseButton::Right => 2, // Can't properly report more than three buttons. - MouseButton::Other(_) => return, + MouseButton::Back | MouseButton::Forward | MouseButton::Other(_) => return, }; self.mouse_report(code, ElementState::Released); return; @@ -705,7 +710,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> { .terminal() .mode() .contains(TermMode::ALT_SCREEN | TermMode::ALTERNATE_SCROLL) - && !self.ctx.modifiers().shift() + && !self.ctx.modifiers().state().shift_key() { let multiplier = f64::from(self.ctx.config().terminal_config.scrolling.multiplier); @@ -870,6 +875,25 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> { } } + /// Reset mouse cursor based on modifier and terminal state. + #[inline] + pub fn reset_mouse_cursor(&mut self) { + let mouse_state = self.cursor_state(); + self.ctx.window().set_mouse_cursor(mouse_state); + } + + /// Modifier state change. + pub fn modifiers_input(&mut self, modifiers: Modifiers) { + *self.ctx.modifiers() = modifiers; + + // Prompt hint highlight update. + self.ctx.mouse_mut().hint_highlight_dirty = true; + + // Update mouse state and check for URL change. + let mouse_state = self.cursor_state(); + self.ctx.window().set_mouse_cursor(mouse_state); + } + pub fn mouse_input(&mut self, state: ElementState, button: MouseButton) { match button { MouseButton::Left => self.ctx.mouse_mut().left_button_state = state, @@ -879,7 +903,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> { } // Skip normal mouse events if the message bar has been clicked. - if self.message_bar_cursor_state() == Some(CursorIcon::Hand) + if self.message_bar_cursor_state() == Some(CursorIcon::Pointer) && state == ElementState::Pressed { let size = self.ctx.size_info(); @@ -894,7 +918,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> { let new_icon = match current_lines.cmp(&new_lines) { Ordering::Less => CursorIcon::Default, - Ordering::Equal => CursorIcon::Hand, + Ordering::Equal => CursorIcon::Pointer, Ordering::Greater => { if self.ctx.mouse_mode() { CursorIcon::Default @@ -917,16 +941,43 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> { } } + /// Attempt to find a binding and execute its action. + /// + /// The provided mode, mods, and key must match what is allowed by a binding + /// for its action to be executed. + fn process_mouse_bindings(&mut self, button: MouseButton) { + let mode = BindingMode::new(self.ctx.terminal().mode(), self.ctx.search_active()); + let mouse_mode = self.ctx.mouse_mode(); + let mods = self.ctx.modifiers().state(); + + for i in 0..self.ctx.config().mouse_bindings().len() { + let mut binding = self.ctx.config().mouse_bindings()[i].clone(); + + // Require shift for all modifiers when mouse mode is active. + if mouse_mode { + binding.mods |= ModifiersState::SHIFT; + } + + if binding.is_triggered_by(mode, mods, &button) { + binding.action.execute(&mut self.ctx); + } + } + } + /// Process key input. - pub fn key_input(&mut self, input: KeyboardInput) { + pub fn key_input(&mut self, key: KeyEvent) { // IME input will be applied on commit and shouldn't trigger key bindings. - if self.ctx.display().ime.preedit().is_some() { + if key.state == ElementState::Released || self.ctx.display().ime.preedit().is_some() { return; } + let text = key.text_with_all_modifiers().unwrap_or_default(); + // All key bindings are disabled while a hint is being selected. if self.ctx.display().hint_state.active() { - *self.ctx.suppress_chars() = false; + for character in text.chars() { + self.ctx.hint_input(character); + } return; } @@ -939,102 +990,68 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> { } } - // Reset character suppression. - *self.ctx.suppress_chars() = false; - - if let ElementState::Pressed = input.state { - *self.ctx.received_count() = 0; - self.process_key_bindings(input); - } - } - - /// Modifier state change. - pub fn modifiers_input(&mut self, modifiers: ModifiersState) { - *self.ctx.modifiers() = modifiers; - - // Prompt hint highlight update. - self.ctx.mouse_mut().hint_highlight_dirty = true; - - // Update mouse state and check for URL change. - let mouse_state = self.cursor_state(); - self.ctx.window().set_mouse_cursor(mouse_state); - } - - /// Reset mouse cursor based on modifier and terminal state. - #[inline] - pub fn reset_mouse_cursor(&mut self) { - let mouse_state = self.cursor_state(); - self.ctx.window().set_mouse_cursor(mouse_state); - } - - /// Process a received character. - pub fn received_char(&mut self, c: char) { - let suppress_chars = *self.ctx.suppress_chars(); - - // Don't insert chars when we have IME running. - if self.ctx.display().ime.preedit().is_some() { + // Key bindings suppress the character input. + if self.process_key_bindings(&key) { return; } - // Handle hint selection over anything else. - if self.ctx.display().hint_state.active() && !suppress_chars { - self.ctx.hint_input(c); + if self.ctx.search_active() { + for character in text.chars() { + self.ctx.search_input(character); + } + return; } - // Pass keys to search and ignore them during `suppress_chars`. - let search_active = self.ctx.search_active(); - if suppress_chars || search_active || self.ctx.terminal().mode().contains(TermMode::VI) { - if search_active && !suppress_chars { - self.ctx.search_input(c); - } - + // Vi mode on its own doesn't have any input, the search input was done before. + if self.ctx.terminal().mode().contains(TermMode::VI) || text.is_empty() { return; } self.ctx.on_terminal_input_start(); - let utf8_len = c.len_utf8(); - let mut bytes = vec![0; utf8_len]; - c.encode_utf8(&mut bytes[..]); - - #[cfg(not(target_os = "macos"))] - let alt_send_esc = true; - - // Don't send ESC when `OptionAsAlt` is used. This doesn't handle - // `Only{Left,Right}` variants due to inability to distinguish them. - #[cfg(target_os = "macos")] - let alt_send_esc = self.ctx.config().window.option_as_alt != OptionAsAlt::None; - - if alt_send_esc - && *self.ctx.received_count() == 0 - && self.ctx.modifiers().alt() - && utf8_len == 1 - { - bytes.insert(0, b'\x1b'); + let mut bytes = Vec::with_capacity(text.len() + 1); + if self.alt_send_esc() && text.len() == 1 { + bytes.push(b'\x1b'); } + bytes.extend_from_slice(text.as_bytes()); self.ctx.write_to_pty(bytes); + } + + /// Whether we should send `ESC` due to `Alt` being pressed. + #[cfg(not(target_os = "macos"))] + fn alt_send_esc(&mut self) -> bool { + self.ctx.modifiers().state().alt_key() + } - *self.ctx.received_count() += 1; + #[cfg(target_os = "macos")] + fn alt_send_esc(&mut self) -> bool { + let option_as_alt = self.ctx.config().window.option_as_alt; + option_as_alt == OptionAsAlt::Both + || (option_as_alt == OptionAsAlt::OnlyLeft + && self.ctx.modifiers().lalt_state() == ModifiersKeyState::Pressed) + || (option_as_alt == OptionAsAlt::OnlyRight + && self.ctx.modifiers().ralt_state() == ModifiersKeyState::Pressed) } /// Attempt to find a binding and execute its action. /// /// The provided mode, mods, and key must match what is allowed by a binding /// for its action to be executed. - fn process_key_bindings(&mut self, input: KeyboardInput) { + fn process_key_bindings(&mut self, key: &KeyEvent) -> bool { let mode = BindingMode::new(self.ctx.terminal().mode(), self.ctx.search_active()); - let mods = *self.ctx.modifiers(); + let mods = self.ctx.modifiers().state(); + + // Don't suppress char if no bindings were triggered. let mut suppress_chars = None; for i in 0..self.ctx.config().key_bindings().len() { let binding = &self.ctx.config().key_bindings()[i]; - let key = match (binding.trigger, input.virtual_keycode) { - (Key::Scancode(_), _) => Key::Scancode(input.scancode), - (_, Some(key)) => Key::Keycode(key), - _ => continue, + let key = match (&binding.trigger, &key.key_without_modifiers()) { + (BindingKey::Scancode(_), _) => BindingKey::Scancode(key.physical_key), + (_, code) => BindingKey::Keycode { key: code.clone(), location: key.location }, }; if binding.is_triggered_by(mode, mods, &key) { @@ -1046,31 +1063,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> { } } - // Don't suppress char if no bindings were triggered. - *self.ctx.suppress_chars() = suppress_chars.unwrap_or(false); - } - - /// Attempt to find a binding and execute its action. - /// - /// The provided mode, mods, and key must match what is allowed by a binding - /// for its action to be executed. - fn process_mouse_bindings(&mut self, button: MouseButton) { - let mode = BindingMode::new(self.ctx.terminal().mode(), self.ctx.search_active()); - let mouse_mode = self.ctx.mouse_mode(); - let mods = *self.ctx.modifiers(); - - for i in 0..self.ctx.config().mouse_bindings().len() { - let mut binding = self.ctx.config().mouse_bindings()[i].clone(); - - // Require shift for all modifiers when mouse mode is active. - if mouse_mode { - binding.mods |= ModifiersState::SHIFT; - } - - if binding.is_triggered_by(mode, mods, &button) { - binding.action.execute(&mut self.ctx); - } - } + suppress_chars.unwrap_or(false) } /// Check mouse icon state in relation to the message bar. @@ -1092,7 +1085,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> { } else if mouse.y <= terminal_end + size.cell_height() as usize && point.column + message_bar::CLOSE_BUTTON_TEXT.len() >= size.columns() { - Some(CursorIcon::Hand) + Some(CursorIcon::Pointer) } else { Some(CursorIcon::Default) } @@ -1110,8 +1103,8 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> { if let Some(mouse_state) = self.message_bar_cursor_state() { mouse_state } else if self.ctx.display().highlighted_hint.as_ref().map_or(false, hint_highlighted) { - CursorIcon::Hand - } else if !self.ctx.modifiers().shift() && self.ctx.mouse_mode() { + CursorIcon::Pointer + } else if !self.ctx.modifiers().state().shift_key() && self.ctx.mouse_mode() { CursorIcon::Default } else { CursorIcon::Text @@ -1158,7 +1151,8 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> { mod tests { use super::*; - use winit::event::{DeviceId, Event as WinitEvent, VirtualKeyCode, WindowEvent}; + use winit::event::{DeviceId, Event as WinitEvent, WindowEvent}; + use winit::keyboard::Key; use winit::window::WindowId; use alacritty_terminal::event::Event as TerminalEvent; @@ -1166,7 +1160,7 @@ mod tests { use crate::config::Binding; use crate::message_bar::MessageBuffer; - const KEY: VirtualKeyCode = VirtualKeyCode::Key0; + const KEY: Key<&'static str> = Key::Character("0"); struct MockEventProxy; impl EventListener for MockEventProxy {} @@ -1177,9 +1171,7 @@ mod tests { pub mouse: &'a mut Mouse, pub clipboard: &'a mut Clipboard, pub message_buffer: &'a mut MessageBuffer, - pub received_count: usize, - pub suppress_chars: bool, - pub modifiers: ModifiersState, + pub modifiers: Modifiers, config: &'a UiConfig, } @@ -1240,15 +1232,7 @@ mod tests { unimplemented!(); } - fn received_count(&mut self) -> &mut usize { - &mut self.received_count - } - - fn suppress_chars(&mut self) -> &mut bool { - &mut self.suppress_chars - } - - fn modifiers(&mut self) -> &mut ModifiersState { + fn modifiers(&mut self) -> &mut Modifiers { &mut self.modifiers } @@ -1324,8 +1308,6 @@ mod tests { mouse: &mut mouse, size_info: &size, clipboard: &mut clipboard, - received_count: 0, - suppress_chars: false, modifiers: Default::default(), message_buffer: &mut message_buffer, config: &cfg, @@ -1379,7 +1361,6 @@ mod tests { state: ElementState::Pressed, button: MouseButton::Left, device_id: unsafe { DeviceId::dummy() }, - modifiers: ModifiersState::default(), }, window_id: unsafe { WindowId::dummy() }, }, @@ -1396,7 +1377,6 @@ mod tests { state: ElementState::Pressed, button: MouseButton::Right, device_id: unsafe { DeviceId::dummy() }, - modifiers: ModifiersState::default(), }, window_id: unsafe { WindowId::dummy() }, }, @@ -1413,7 +1393,6 @@ mod tests { state: ElementState::Pressed, button: MouseButton::Middle, device_id: unsafe { DeviceId::dummy() }, - modifiers: ModifiersState::default(), }, window_id: unsafe { WindowId::dummy() }, }, @@ -1430,7 +1409,6 @@ mod tests { state: ElementState::Pressed, button: MouseButton::Left, device_id: unsafe { DeviceId::dummy() }, - modifiers: ModifiersState::default(), }, window_id: unsafe { WindowId::dummy() }, }, @@ -1447,7 +1425,6 @@ mod tests { state: ElementState::Pressed, button: MouseButton::Left, device_id: unsafe { DeviceId::dummy() }, - modifiers: ModifiersState::default(), }, window_id: unsafe { WindowId::dummy() }, }, @@ -1464,7 +1441,6 @@ mod tests { state: ElementState::Pressed, button: MouseButton::Left, device_id: unsafe { DeviceId::dummy() }, - modifiers: ModifiersState::default(), }, window_id: unsafe { WindowId::dummy() }, }, @@ -1481,7 +1457,6 @@ mod tests { state: ElementState::Pressed, button: MouseButton::Left, device_id: unsafe { DeviceId::dummy() }, - modifiers: ModifiersState::default(), }, window_id: unsafe { WindowId::dummy() }, }, @@ -1498,7 +1473,6 @@ mod tests { state: ElementState::Pressed, button: MouseButton::Right, device_id: unsafe { DeviceId::dummy() }, - modifiers: ModifiersState::default(), }, window_id: unsafe { WindowId::dummy() }, }, @@ -1524,10 +1498,10 @@ mod tests { test_process_binding! { name: process_binding_nomode_controlmod, - binding: Binding { trigger: KEY, mods: ModifiersState::CTRL, action: Action::from("\x1b[1;5D"), mode: BindingMode::empty(), notmode: BindingMode::empty() }, + binding: Binding { trigger: KEY, mods: ModifiersState::CONTROL, action: Action::from("\x1b[1;5D"), mode: BindingMode::empty(), notmode: BindingMode::empty() }, triggers: true, mode: BindingMode::empty(), - mods: ModifiersState::CTRL, + mods: ModifiersState::CONTROL, } test_process_binding! { @@ -1564,9 +1538,9 @@ mod tests { test_process_binding! { name: process_binding_fail_with_extra_mods, - binding: Binding { trigger: KEY, mods: ModifiersState::LOGO, action: Action::from("arst"), mode: BindingMode::empty(), notmode: BindingMode::empty() }, + binding: Binding { trigger: KEY, mods: ModifiersState::SUPER, action: Action::from("arst"), mode: BindingMode::empty(), notmode: BindingMode::empty() }, triggers: false, mode: BindingMode::empty(), - mods: ModifiersState::ALT | ModifiersState::LOGO, + mods: ModifiersState::ALT | ModifiersState::SUPER, } } |