aboutsummaryrefslogtreecommitdiff
path: root/alacritty/src/input.rs
diff options
context:
space:
mode:
Diffstat (limited to 'alacritty/src/input.rs')
-rw-r--r--alacritty/src/input.rs268
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,
}
}