aboutsummaryrefslogtreecommitdiff
path: root/src/app/input
diff options
context:
space:
mode:
Diffstat (limited to 'src/app/input')
-rw-r--r--src/app/input/mod.rs14
-rw-r--r--src/app/input/mouse.rs152
2 files changed, 132 insertions, 34 deletions
diff --git a/src/app/input/mod.rs b/src/app/input/mod.rs
index f77821c9..3202ae26 100644
--- a/src/app/input/mod.rs
+++ b/src/app/input/mod.rs
@@ -493,15 +493,17 @@ impl App {
self.close_popup_pane();
return;
};
- let column = mouse.column.saturating_sub(inner.x);
- let row = mouse.row.saturating_sub(inner.y);
+ let position = crate::input::mouse::Position::Cell {
+ column: mouse.column.saturating_sub(inner.x),
+ row: mouse.row.saturating_sub(inner.y),
+ };
let bytes = match mouse.kind {
MouseEventKind::ScrollUp
| MouseEventKind::ScrollDown
| MouseEventKind::ScrollLeft
| MouseEventKind::ScrollRight => match rt.wheel_routing() {
Some(crate::pane::WheelRouting::MouseReport) => {
- rt.encode_mouse_wheel(mouse.kind, column, row, mouse.modifiers)
+ rt.encode_mouse_wheel(mouse.kind, position, mouse.modifiers)
}
Some(crate::pane::WheelRouting::AlternateScroll) => {
rt.encode_alternate_scroll(mouse.kind)
@@ -517,11 +519,9 @@ impl App {
}
},
MouseEventKind::Down(_) | MouseEventKind::Up(_) | MouseEventKind::Drag(_) => {
- rt.encode_mouse_button(mouse.kind, column, row, mouse.modifiers)
- }
- MouseEventKind::Moved => {
- rt.encode_mouse_motion(mouse.kind, column, row, mouse.modifiers)
+ rt.encode_mouse_button(mouse.kind, position, mouse.modifiers)
}
+ MouseEventKind::Moved => rt.encode_mouse_motion(mouse.kind, position, mouse.modifiers),
};
let Some(bytes) = bytes else {
return;
diff --git a/src/app/input/mouse.rs b/src/app/input/mouse.rs
index 2555d95d..8d8988f9 100644
--- a/src/app/input/mouse.rs
+++ b/src/app/input/mouse.rs
@@ -1667,6 +1667,33 @@ impl AppState {
}
}
+ fn pane_mouse_position(
+ &self,
+ runtime: &crate::terminal::TerminalRuntime,
+ inner: Rect,
+ mouse: MouseEvent,
+ ) -> Option<crate::input::mouse::Position> {
+ let column = mouse.column.saturating_sub(inner.x);
+ let row = mouse.row.saturating_sub(inner.y);
+ let cell = crate::input::mouse::Position::Cell { column, row };
+ let Some(host) = self.host_mouse_pixels else {
+ return Some(cell);
+ };
+ let wants_pixels = runtime.input_state().is_some_and(|state| {
+ state.mouse_protocol_encoding == crate::input::MouseProtocolEncoding::SgrPixels
+ });
+ if !wants_pixels {
+ return Some(cell);
+ }
+ let Some((width_px, height_px)) = runtime.pixel_size() else {
+ return Some(cell);
+ };
+ Some(
+ host.pane_position(inner, width_px, height_px)
+ .unwrap_or(cell),
+ )
+ }
+
pub(super) fn forward_pane_mouse_button(
&self,
terminal_runtimes: &TerminalRuntimeRegistry,
@@ -1680,9 +1707,10 @@ impl AppState {
else {
return false;
};
- let column = mouse.column.saturating_sub(info.inner_rect.x);
- let row = mouse.row.saturating_sub(info.inner_rect.y);
- let Some(bytes) = rt.encode_mouse_button(mouse.kind, column, row, mouse.modifiers) else {
+ let Some(position) = self.pane_mouse_position(rt, info.inner_rect, mouse) else {
+ return false;
+ };
+ let Some(bytes) = rt.encode_mouse_button(mouse.kind, position, mouse.modifiers) else {
return false;
};
rt.scroll_reset();
@@ -1705,9 +1733,10 @@ impl AppState {
else {
return false;
};
- let column = mouse.column.saturating_sub(info.inner_rect.x);
- let row = mouse.row.saturating_sub(info.inner_rect.y);
- let Some(bytes) = rt.encode_mouse_motion(mouse.kind, column, row, mouse.modifiers) else {
+ let Some(position) = self.pane_mouse_position(rt, info.inner_rect, mouse) else {
+ return false;
+ };
+ let Some(bytes) = rt.encode_mouse_motion(mouse.kind, position, mouse.modifiers) else {
return false;
};
if let Err(err) = rt.try_send_bytes(Bytes::from(bytes)) {
@@ -1733,9 +1762,10 @@ impl AppState {
return false;
}
rt.scroll_reset();
- let column = mouse.column.saturating_sub(info.inner_rect.x);
- let row = mouse.row.saturating_sub(info.inner_rect.y);
- let Some(bytes) = rt.encode_mouse_wheel(mouse.kind, column, row, mouse.modifiers) else {
+ let Some(position) = self.pane_mouse_position(rt, info.inner_rect, mouse) else {
+ return false;
+ };
+ let Some(bytes) = rt.encode_mouse_wheel(mouse.kind, position, mouse.modifiers) else {
warn!(pane = info.id.raw(), kind = ?mouse.kind, "failed to encode mouse wheel event");
return true;
};
@@ -1761,18 +1791,7 @@ impl AppState {
match rt.wheel_routing() {
Some(crate::pane::WheelRouting::HostScroll) | None => false,
Some(crate::pane::WheelRouting::MouseReport) => {
- rt.scroll_reset();
- let column = mouse.column.saturating_sub(info.inner_rect.x);
- let row = mouse.row.saturating_sub(info.inner_rect.y);
- let Some(bytes) = rt.encode_mouse_wheel(mouse.kind, column, row, mouse.modifiers)
- else {
- warn!(pane = info.id.raw(), kind = ?mouse.kind, "failed to encode mouse wheel event");
- return true;
- };
- if let Err(err) = rt.try_send_bytes(Bytes::from(bytes)) {
- warn!(pane = info.id.raw(), err = %err, "failed to forward mouse wheel event");
- }
- true
+ self.forward_pane_reported_wheel(terminal_runtimes, info, mouse)
}
Some(crate::pane::WheelRouting::AlternateScroll) => {
rt.scroll_reset();
@@ -2146,7 +2165,7 @@ mod tests {
}
#[tokio::test]
- async fn configured_right_click_passthrough_forwards_full_gesture_to_pane() {
+ async fn configured_right_click_passthrough_forwards_gesture_outside_pane() {
let mut app = app_for_mouse_test();
let mut ws = Workspace::test_new("test");
let pane_id = ws.tabs[0].root_pane;
@@ -2177,11 +2196,11 @@ mod tests {
});
app.handle_mouse(MouseEvent {
modifiers: KeyModifiers::CONTROL,
- ..mouse(MouseEventKind::Drag(MouseButton::Right), col + 1, row + 1)
+ ..mouse(MouseEventKind::Drag(MouseButton::Right), 0, 0)
});
app.handle_mouse(MouseEvent {
modifiers: KeyModifiers::CONTROL,
- ..mouse(MouseEventKind::Up(MouseButton::Right), col + 1, row + 1)
+ ..mouse(MouseEventKind::Up(MouseButton::Right), 0, 0)
});
assert_eq!(app.state.mode, Mode::Terminal);
@@ -2193,11 +2212,11 @@ mod tests {
);
assert_eq!(
input_rx.try_recv().expect("forwarded right mouse drag"),
- Bytes::from_static(b"\x1b[<34;4;5M")
+ Bytes::from_static(b"\x1b[<34;1;1M")
);
assert_eq!(
input_rx.try_recv().expect("forwarded right mouse up"),
- Bytes::from_static(b"\x1b[<2;4;5m")
+ Bytes::from_static(b"\x1b[<2;1;1m")
);
assert!(input_rx.try_recv().is_err());
}
@@ -2321,7 +2340,7 @@ mod tests {
}
#[tokio::test]
- async fn mouse_dispatcher_downgrades_sgr_pixel_motion_to_cell_coordinates() {
+ async fn ordinary_cell_mouse_downgrades_pixel_mode_to_cell_coordinates() {
let mut app = app_for_mouse_test();
let mut ws = Workspace::test_new("test");
let pane_id = ws.tabs[0].root_pane;
@@ -2339,8 +2358,16 @@ mod tests {
app.state.active = Some(0);
app.state.selected = 0;
app.state.mode = Mode::Terminal;
+ app.state.host_cell_size = crate::kitty_graphics::HostCellSize {
+ width_px: 10,
+ height_px: 20,
+ };
crate::ui::compute_view(&mut app.state, Rect::new(0, 0, 106, 20));
let info = app.state.view.pane_infos[0].clone();
+ app.state
+ .runtime_for_pane_in_workspace(&app.terminal_runtimes, 0, pane_id)
+ .unwrap()
+ .resize(info.inner_rect.height, info.inner_rect.width, 10, 20);
assert!(info.inner_rect.x > 0, "sidebar offset should be present");
assert!(info.inner_rect.y > 0, "tab bar offset should be present");
@@ -2358,6 +2385,77 @@ mod tests {
}
#[tokio::test]
+ async fn dedicated_client_pixel_mouse_preserves_subcell_position() {
+ let mut app = app_for_mouse_test();
+ let mut ws = Workspace::test_new("test");
+ let pane_id = ws.tabs[0].root_pane;
+ let (runtime, mut input_rx) =
+ crate::terminal::TerminalRuntime::test_with_channel_and_scrollback_bytes(
+ 80,
+ 18,
+ 0,
+ b"\x1b[?1003h\x1b[?1006h\x1b[?1016h",
+ 4,
+ );
+ ws.insert_test_runtime(pane_id, runtime);
+ app.state.workspaces = vec![ws];
+ app.state.active = Some(0);
+ app.state.selected = 0;
+ app.state.mode = Mode::Terminal;
+ app.state.mouse_capture = false;
+ app.state.host_cell_size = crate::kitty_graphics::HostCellSize {
+ width_px: 10,
+ height_px: 20,
+ };
+ crate::ui::compute_view(&mut app.state, Rect::new(0, 0, 106, 20));
+ let inner = app.state.view.pane_infos[0].inner_rect;
+ let geometry = crate::input::mouse::HostGeometry::new(106, 20, 1_060, 400).unwrap();
+ let x = u32::from(inner.x + 2) * 10 + 8;
+ let y = u32::from(inner.y + 3) * 20 + 9;
+ let report = format!("\x1b[<35;{x};{y}M");
+ app.state.host_mouse_pixels = Some(crate::input::mouse::HostPixels { x, y, geometry });
+ let runtime = app
+ .state
+ .runtime_for_pane_in_workspace(&app.terminal_runtimes, 0, pane_id)
+ .unwrap();
+ assert_eq!(runtime.pixel_size(), None);
+ assert_eq!(
+ app.state.pane_mouse_position(
+ runtime,
+ inner,
+ mouse(MouseEventKind::Moved, inner.x + 2, inner.y + 3),
+ ),
+ Some(crate::input::mouse::Position::Cell { column: 2, row: 3 })
+ );
+ runtime.resize(inner.height, inner.width, 10, 20);
+ let runtime = app
+ .state
+ .runtime_for_pane_in_workspace(&app.terminal_runtimes, 0, pane_id)
+ .unwrap();
+ assert_eq!(
+ runtime.pixel_size(),
+ Some((u32::from(inner.width) * 10, u32::from(inner.height) * 20))
+ );
+ assert_eq!(
+ app.state.pane_mouse_position(
+ runtime,
+ inner,
+ mouse(MouseEventKind::Moved, inner.x + 2, inner.y + 3),
+ ),
+ Some(crate::input::mouse::Position::Pixels { x: 28, y: 69 })
+ );
+ app.state.host_mouse_pixels = None;
+
+ assert!(app.route_client_pixel_mouse(7, report.as_bytes(), geometry));
+ assert_eq!(
+ input_rx.try_recv().expect("forwarded exact mouse motion"),
+ Bytes::from_static(b"\x1b[<35;28;69M")
+ );
+ assert!(input_rx.try_recv().is_err());
+ assert!(app.state.host_mouse_pixels.is_none());
+ }
+
+ #[tokio::test]
async fn mouse_dispatcher_does_not_forward_motion_behind_herdr_modes() {
let mut app = app_for_mouse_test();
let mut ws = Workspace::test_new("test");