summaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-27 20:29:49 +0300
committerGitHub <noreply@github.com>2026-09-27 20:29:49 +0300
commit0d1a39dcee6a0f244a036c55935970f40cfa76b8 (patch)
treeb36e41bc3dfb71041e5fa2502c0457e37aea417f /src/client
parent316d176768e99bd80b81696c4e1f310f0643e744 (diff)
feat: accept mouse events on terminal session control (#4685)
Diffstat (limited to 'src/client')
-rw-r--r--src/client/terminal_sessions.rs60
-rw-r--r--src/client/tests/mod.rs61
2 files changed, 119 insertions, 2 deletions
diff --git a/src/client/terminal_sessions.rs b/src/client/terminal_sessions.rs
index 6d3160c8..aeecbf47 100644
--- a/src/client/terminal_sessions.rs
+++ b/src/client/terminal_sessions.rs
@@ -7,8 +7,8 @@ use tracing::info;
use crate::ipc::LocalStream;
use crate::protocol::{
- self, AttachScrollDirection, AttachScrollSource, ClientMessage, RenderEncoding, ServerMessage,
- MAX_GRAPHICS_FRAME_SIZE,
+ self, AttachScrollDirection, AttachScrollSource, ClientMessage, ClientMouseButton,
+ ClientMouseKind, ClientMousePosition, RenderEncoding, ServerMessage, MAX_GRAPHICS_FRAME_SIZE,
};
use crate::server::socket_paths::client_socket_path;
@@ -187,12 +187,40 @@ enum TerminalControlCommand {
#[serde(default)]
modifiers: u8,
},
+ #[serde(rename = "terminal.mouse")]
+ Mouse {
+ action: TerminalControlMouseAction,
+ #[serde(default)]
+ button: TerminalControlMouseButton,
+ column: u16,
+ row: u16,
+ #[serde(default)]
+ modifiers: u8,
+ },
#[serde(rename = "terminal.release")]
Release {},
}
#[derive(Clone, Copy, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
+enum TerminalControlMouseAction {
+ Down,
+ Up,
+ Drag,
+ Move,
+}
+
+#[derive(Clone, Copy, Default, serde::Deserialize)]
+#[serde(rename_all = "snake_case")]
+enum TerminalControlMouseButton {
+ #[default]
+ Left,
+ Right,
+ Middle,
+}
+
+#[derive(Clone, Copy, serde::Deserialize)]
+#[serde(rename_all = "snake_case")]
enum TerminalControlScrollDirection {
Up,
Down,
@@ -273,6 +301,34 @@ pub(super) fn terminal_control_command_from_json(raw: &str) -> Result<ClientMess
modifiers,
})
}
+ TerminalControlCommand::Mouse {
+ action,
+ button,
+ column,
+ row,
+ modifiers,
+ } => {
+ let button = match button {
+ TerminalControlMouseButton::Left => ClientMouseButton::Left,
+ TerminalControlMouseButton::Right => ClientMouseButton::Right,
+ TerminalControlMouseButton::Middle => ClientMouseButton::Middle,
+ };
+ let kind = match action {
+ TerminalControlMouseAction::Down => ClientMouseKind::Down(button),
+ TerminalControlMouseAction::Up => ClientMouseKind::Up(button),
+ TerminalControlMouseAction::Drag => ClientMouseKind::Drag(button),
+ TerminalControlMouseAction::Move => ClientMouseKind::Moved,
+ };
+ // The server encodes the event for the pane's mouse mode and drops it
+ // when the application has not enabled mouse reporting.
+ Ok(ClientMessage::AttachMouse {
+ kind,
+ position: ClientMousePosition::Cell { column, row },
+ geometry: None,
+ modifiers,
+ lines: 1,
+ })
+ }
TerminalControlCommand::Release {} => Ok(ClientMessage::Detach),
}
}
diff --git a/src/client/tests/mod.rs b/src/client/tests/mod.rs
index 8519ea07..2fcac6eb 100644
--- a/src/client/tests/mod.rs
+++ b/src/client/tests/mod.rs
@@ -887,6 +887,67 @@ fn terminal_control_resize_command_maps_to_client_resize() {
}
#[test]
+fn terminal_control_mouse_command_maps_to_attach_mouse() {
+ let action = terminal_control_command_from_json(
+ r#"{"type":"terminal.mouse","action":"down","column":12,"row":5}"#,
+ )
+ .unwrap();
+ let ClientMessage::AttachMouse {
+ kind,
+ position,
+ geometry,
+ modifiers,
+ lines,
+ } = action
+ else {
+ panic!("expected attach mouse command");
+ };
+ assert_eq!(
+ kind,
+ crate::protocol::ClientMouseKind::Down(crate::protocol::ClientMouseButton::Left)
+ );
+ assert_eq!(
+ position,
+ crate::protocol::ClientMousePosition::Cell { column: 12, row: 5 }
+ );
+ assert_eq!((geometry, modifiers, lines), (None, 0, 1));
+}
+
+#[test]
+fn terminal_control_mouse_command_maps_every_action_and_button() {
+ use crate::protocol::{ClientMouseButton as Button, ClientMouseKind as Kind};
+ for (json, expected) in [
+ (r#""action":"up","button":"right""#, Kind::Up(Button::Right)),
+ (
+ r#""action":"drag","button":"middle""#,
+ Kind::Drag(Button::Middle),
+ ),
+ (r#""action":"move""#, Kind::Moved),
+ ] {
+ let raw = format!(r#"{{"type":"terminal.mouse",{json},"column":0,"row":0,"modifiers":4}}"#);
+ let ClientMessage::AttachMouse {
+ kind, modifiers, ..
+ } = terminal_control_command_from_json(&raw).unwrap()
+ else {
+ panic!("expected attach mouse command");
+ };
+ assert_eq!((kind, modifiers), (expected, 4), "{raw}");
+ }
+}
+
+#[test]
+fn terminal_control_mouse_command_rejects_unknown_actions_and_missing_cells() {
+ assert!(terminal_control_command_from_json(
+ r#"{"type":"terminal.mouse","action":"click","column":1,"row":1}"#
+ )
+ .is_err());
+ assert!(terminal_control_command_from_json(
+ r#"{"type":"terminal.mouse","action":"down","column":1}"#
+ )
+ .is_err());
+}
+
+#[test]
fn terminal_control_scroll_command_maps_to_attach_scroll() {
let action = terminal_control_command_from_json(
r#"{"type":"terminal.scroll","direction":"up","lines":3}"#,