Fix mouse point crash on resize

This resolves an issue with Alacritty crashing after a resize, due to
the last cached mouse point being out of bounds.

Instead of caching the mouse point, it is now computed on demand to make
sure it can never be invalid.

Fixes #4977.
This commit is contained in:
Christian Duerr 2021-04-15 22:16:31 +00:00 committed by GitHub
parent 1f46bb7b92
commit 9cb55621f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 76 additions and 72 deletions

View File

@ -680,7 +680,7 @@ impl Display {
}
// Find highlighted hint at mouse position.
let point = viewport_to_point(term.grid().display_offset(), mouse.point);
let point = mouse.point(&self.size_info, term.grid().display_offset());
let highlighted_hint = hint::highlighted_at(&term, config, point, modifiers);
// Update cursor shape.

View File

@ -209,12 +209,12 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
&& self.terminal.selection.as_ref().map(|s| s.is_empty()) != Some(true)
{
self.update_selection(self.terminal.vi_mode_cursor.point, Side::Right);
} else if self.mouse().left_button_state == ElementState::Pressed
|| self.mouse().right_button_state == ElementState::Pressed
} else if self.mouse.left_button_state == ElementState::Pressed
|| self.mouse.right_button_state == ElementState::Pressed
{
let display_offset = self.terminal.grid().display_offset();
let point = display::viewport_to_point(display_offset, self.mouse().point);
self.update_selection(point, self.mouse().cell_side);
let point = self.mouse.point(&self.size_info(), display_offset);
self.update_selection(point, self.mouse.cell_side);
}
*self.dirty = true;
@ -891,7 +891,6 @@ pub struct Mouse {
pub block_hint_launcher: bool,
pub hint_highlight_dirty: bool,
pub inside_text_area: bool,
pub point: Point<usize>,
pub x: usize,
pub y: usize,
}
@ -911,13 +910,29 @@ impl Default for Mouse {
inside_text_area: Default::default(),
lines_scrolled: Default::default(),
scroll_px: Default::default(),
point: Default::default(),
x: Default::default(),
y: Default::default(),
}
}
}
impl Mouse {
/// Convert mouse pixel coordinates to viewport point.
///
/// If the coordinates are outside of the terminal grid, like positions inside the padding, the
/// coordinates will be clamped to the closest grid coordinates.
#[inline]
pub fn point(&self, size: &SizeInfo, display_offset: usize) -> Point {
let col = self.x.saturating_sub(size.padding_x() as usize) / (size.cell_width() as usize);
let col = min(Column(col), size.last_column());
let line = self.y.saturating_sub(size.padding_y() as usize) / (size.cell_height() as usize);
let line = min(line, size.bottommost_line().0 as usize);
display::viewport_to_point(display_offset, Point::new(line, col))
}
}
/// The event processor.
///
/// Stores some state from received events and dispatches actions when they are

View File

@ -33,7 +33,7 @@ use crate::config::{Action, BindingMode, Config, Key, SearchAction, ViAction};
use crate::daemon::start_daemon;
use crate::display::hint::HintMatch;
use crate::display::window::Window;
use crate::display::{self, Display};
use crate::display::Display;
use crate::event::{ClickState, Event, Mouse, TYPING_SEARCH_DELAY};
use crate::message_bar::{self, Message};
use crate::scheduler::{Scheduler, TimerId};
@ -341,17 +341,19 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
self.update_selection_scrolling(y);
}
let display_offset = self.ctx.terminal().grid().display_offset();
let old_point = self.ctx.mouse().point(&size_info, display_offset);
let x = min(max(x, 0), size_info.width() as i32 - 1) as usize;
let y = min(max(y, 0), size_info.height() as i32 - 1) as usize;
self.ctx.mouse_mut().x = x;
self.ctx.mouse_mut().y = y;
let inside_text_area = size_info.contains_point(x, y);
let point = self.coords_to_point(x, y);
let cell_side = self.cell_side(x);
let cell_changed = point != self.ctx.mouse().point;
let point = self.ctx.mouse().point(&size_info, display_offset);
let cell_changed = old_point != point;
// If the mouse hasn't changed cells, do nothing.
if !cell_changed
@ -363,7 +365,6 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
self.ctx.mouse_mut().inside_text_area = inside_text_area;
self.ctx.mouse_mut().cell_side = cell_side;
self.ctx.mouse_mut().point = point;
// Update mouse state and check for URL change.
let mouse_state = self.cursor_state();
@ -377,11 +378,8 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
if (lmb_pressed || rmb_pressed) && (self.ctx.modifiers().shift() || !self.ctx.mouse_mode())
{
let display_offset = self.ctx.terminal().grid().display_offset();
let point = display::viewport_to_point(display_offset, point);
self.ctx.update_selection(point, cell_side);
} else if cell_changed
&& point.line < self.ctx.terminal().screen_lines()
&& self.ctx.terminal().mode().intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG)
{
if lmb_pressed {
@ -396,23 +394,6 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
}
}
/// Convert window space pixels to terminal grid coordinates.
///
/// If the coordinates are outside of the terminal grid, like positions inside the padding, the
/// coordinates will be clamped to the closest grid coordinates.
#[inline]
fn coords_to_point(&self, x: usize, y: usize) -> Point<usize> {
let size = self.ctx.size_info();
let column = x.saturating_sub(size.padding_x() as usize) / (size.cell_width() as usize);
let column = min(Column(column), size.last_column());
let line = y.saturating_sub(size.padding_y() as usize) / (size.cell_height() as usize);
let line = min(line, size.bottommost_line().0 as usize);
Point::new(line, column)
}
/// Check which side of a cell an X coordinate lies on.
fn cell_side(&self, x: usize) -> Side {
let size_info = self.ctx.size_info();
@ -435,13 +416,45 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
}
}
fn normal_mouse_report(&mut self, button: u8) {
let Point { line, column } = self.ctx.mouse().point;
fn mouse_report(&mut self, button: u8, state: ElementState) {
let display_offset = self.ctx.terminal().grid().display_offset();
let point = self.ctx.mouse().point(&self.ctx.size_info(), display_offset);
// Assure the mouse point is not in the scrollback.
if point.line >= 0 {
return;
}
// Calculate modifiers value.
let mut mods = 0;
let modifiers = self.ctx.modifiers();
if modifiers.shift() {
mods += 4;
}
if modifiers.alt() {
mods += 8;
}
if modifiers.ctrl() {
mods += 16;
}
// Report mouse events.
if self.ctx.terminal().mode().contains(TermMode::SGR_MOUSE) {
self.sgr_mouse_report(point, button + mods, state);
} else if let ElementState::Released = state {
self.normal_mouse_report(point, 3 + mods);
} else {
self.normal_mouse_report(point, button + mods);
}
}
fn normal_mouse_report(&mut self, point: Point, button: u8) {
let Point { line, column } = point;
let utf8 = self.ctx.terminal().mode().contains(TermMode::UTF8_MOUSE);
let max_point = if utf8 { 2015 } else { 223 };
if line >= max_point || column >= Column(max_point) {
if line >= max_point || column >= max_point {
return;
}
@ -461,49 +474,24 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
}
if utf8 && line >= 95 {
msg.append(&mut mouse_pos_encode(line));
msg.append(&mut mouse_pos_encode(line.0 as usize));
} else {
msg.push(32 + 1 + line as u8);
msg.push(32 + 1 + line.0 as u8);
}
self.ctx.write_to_pty(msg);
}
fn sgr_mouse_report(&mut self, button: u8, state: ElementState) {
let Point { line, column } = self.ctx.mouse().point;
fn sgr_mouse_report(&mut self, point: Point, button: u8, state: ElementState) {
let c = match state {
ElementState::Pressed => 'M',
ElementState::Released => 'm',
};
let msg = format!("\x1b[<{};{};{}{}", button, column + 1, line + 1, c);
let msg = format!("\x1b[<{};{};{}{}", button, point.column + 1, point.line + 1, c);
self.ctx.write_to_pty(msg.into_bytes());
}
fn mouse_report(&mut self, button: u8, state: ElementState) {
// Calculate modifiers value.
let mut mods = 0;
let modifiers = self.ctx.modifiers();
if modifiers.shift() {
mods += 4;
}
if modifiers.alt() {
mods += 8;
}
if modifiers.ctrl() {
mods += 16;
}
// Report mouse events.
if self.ctx.terminal().mode().contains(TermMode::SGR_MOUSE) {
self.sgr_mouse_report(button + mods, state);
} else if let ElementState::Released = state {
self.normal_mouse_report(3 + mods);
} else {
self.normal_mouse_report(button + mods);
}
}
fn on_mouse_press(&mut self, button: MouseButton) {
// Handle mouse mode.
if !self.ctx.modifiers().shift() && self.ctx.mouse_mode() {
@ -543,7 +531,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
// Load mouse point, treating message bar and padding as the closest cell.
let display_offset = self.ctx.terminal().grid().display_offset();
let point = display::viewport_to_point(display_offset, self.ctx.mouse().point);
let point = self.ctx.mouse().point(&self.ctx.size_info(), display_offset);
match button {
MouseButton::Left => self.on_left_click(point),
@ -921,10 +909,13 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
+ size.cell_height() as usize * (size.screen_lines() + search_height);
let mouse = self.ctx.mouse();
let display_offset = self.ctx.terminal().grid().display_offset();
let point = self.ctx.mouse().point(&self.ctx.size_info(), display_offset);
if self.ctx.message().is_none() || (mouse.y <= terminal_end) {
None
} else if mouse.y <= terminal_end + size.cell_height() as usize
&& mouse.point.column + message_bar::CLOSE_BUTTON_TEXT.len() >= size.columns()
&& point.column + message_bar::CLOSE_BUTTON_TEXT.len() >= size.columns()
{
Some(CursorIcon::Hand)
} else {
@ -934,13 +925,11 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
/// Icon state of the cursor.
fn cursor_state(&mut self) -> CursorIcon {
// Define function to check if mouse is on top of a hint.
let display_offset = self.ctx.terminal().grid().display_offset();
let mouse_point = self.ctx.mouse().point;
let hint_highlighted = |hint: &HintMatch| {
let point = display::viewport_to_point(display_offset, mouse_point);
hint.bounds.contains(&point)
};
let point = self.ctx.mouse().point(&self.ctx.size_info(), display_offset);
// Function to check if mouse is on top of a hint.
let hint_highlighted = |hint: &HintMatch| hint.bounds.contains(&point);
if let Some(mouse_state) = self.message_bar_cursor_state() {
mouse_state