aboutsummaryrefslogtreecommitdiff
path: root/alacritty/src/input.rs
diff options
context:
space:
mode:
authorKirill Chibisov <contact@kchibisov.com>2023-07-11 02:22:14 +0000
committerGitHub <noreply@github.com>2023-07-11 02:22:14 +0000
commitdb903503df024a3f5066937fbe0272be88226738 (patch)
tree67dcf4a2ae5d8dd75a08d21b3a4a6947b23ff720 /alacritty/src/input.rs
parent09c4471b4cd6b32a2de79a08c447284b62a80d54 (diff)
downloadr-alacritty-db903503df024a3f5066937fbe0272be88226738.tar.gz
r-alacritty-db903503df024a3f5066937fbe0272be88226738.tar.bz2
r-alacritty-db903503df024a3f5066937fbe0272be88226738.zip
Update to the new winit keyboard API
The main highlight of this update is that alacritty will now use new keyboard API from the winit, which resolves a lot of issues around key bindings, such as ability to bind dead keys. It also fixes long standing issues with the virtual key code bindings and make bindings in general more predictable. It also makes our default Vi key bindings fully working. Given that alacritty was using `VirtualKey` directly in the bindings from the winit, and winit simply removed the enum, we've added internal conversions to minimize the fallout, but new way to specify the bindings should be more intuitive. Other part of this update fixes some forward compatibility bugs with the Wayland backend, given that wayland-rs 0.30 is fully forward compatible. The update also fixes weird Maximized startup issues on GNOME Wayland, however they were present on any sane compositor. Fixes #6842. Fixes #6455. Fixes #6184. Fixes #5684. Fixes #3574. Fixes #3460. Fixes #1336. Fixes #892. Fixes #458. Fixes #55.
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,
}
}