From 3263ce5122480fe4d59a1189f67a3aac65949aa1 Mon Sep 17 00:00:00 2001 From: Alex Bennett Date: Sun, 7 Jun 2026 23:01:53 +0800 Subject: [PATCH] terminal: support click_events=2 --- src/Surface.zig | 12 ++++++++---- src/inspector/widgets/screen.zig | 2 +- src/terminal/Screen.zig | 2 +- src/terminal/Terminal.zig | 6 ++---- src/terminal/osc/parsers/semantic_prompt.zig | 19 +++++++++++++++++-- 5 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index f6f5e7b99..2ab09d371 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -4218,20 +4218,24 @@ fn maybePromptClick(self: *Surface) !bool { // Guarded at the start of this function .none => unreachable, - .click_events => { + .click_events => |v| { // For the event, we always send a left-click press event. // This matches what Kitty sends. + const key: u8, const y: u32 = switch (v) { + .Absolute => .{ 1, pos_vp.y + 1 }, + .Relative => .{ 2, pos_vp.y - prompt_pin.y + 1 }, + }; var data: termio.Message.WriteReq.Small.Array = undefined; const resp = try std.fmt.bufPrint( &data, "\x1B[<0;{d};{d}M", - .{ pos_vp.x + 1, pos_vp.y + 1 }, + .{ pos_vp.x + 1, y }, ); // Not that noisy since this only happens on prompt clicks. log.debug( - "sending click_events=1 event=ESC{s}", - .{resp[1..]}, + "sending click_events={} event=ESC{s}", + .{ key, resp[1..] }, ); // Ask our IO thread to write the data diff --git a/src/inspector/widgets/screen.zig b/src/inspector/widgets/screen.zig index bfe961f45..41c113e75 100644 --- a/src/inspector/widgets/screen.zig +++ b/src/inspector/widgets/screen.zig @@ -393,7 +393,7 @@ pub fn semanticPromptTable( _ = cimgui.c.ImGui_TableSetColumnIndex(1); switch (semantic_prompt.click) { .none => cimgui.c.ImGui_TextDisabled("(none)"), - .click_events => cimgui.c.ImGui_Text("click_events"), + .click_events => |click_events| cimgui.c.ImGui_Text("click_events=%s", @tagName(click_events).ptr), .cl => |cl| cimgui.c.ImGui_Text("cl=%s", @tagName(cl).ptr), } } diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index 8ee700252..5c39be02f 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -113,7 +113,7 @@ pub const SemanticPrompt = struct { pub const SemanticClick = union(enum) { none, - click_events, + click_events: osc.semantic_prompt.ClickEvents, cl: osc.semantic_prompt.Click, }; }; diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 3a914794f..b96084df6 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -1226,10 +1226,8 @@ pub fn semanticPrompt( // within a prompt area to SGR mouse events and defers to the // shell to handle them. if (cmd.readOption(.click_events)) |v| { - if (v) { - screen.semantic_prompt.click = .click_events; - break :click; - } + screen.semantic_prompt.click = .{ .click_events = v } ; + break :click; } // If click_events was not set or disabled, fallback to `cl`. diff --git a/src/terminal/osc/parsers/semantic_prompt.zig b/src/terminal/osc/parsers/semantic_prompt.zig index 25152d7c3..2648d2859 100644 --- a/src/terminal/osc/parsers/semantic_prompt.zig +++ b/src/terminal/osc/parsers/semantic_prompt.zig @@ -60,6 +60,16 @@ pub const Command = struct { } }; +// ClickEvents can either be a click_events=1 or click_events=2. +// The click_events=1 sends a click event with the absolute coordinates +// of the click. +// The click_events=2 sends a click event with the coordinates of the click +// relative to the prompt area. +// See https://github.com/ghostty-org/ghostty/issues/10865 and +// https://github.com/kovidgoyal/kitty/issues/9500 +// for further details. +pub const ClickEvents = enum { Absolute, Relative }; + pub const Option = enum { aid, cl, @@ -102,7 +112,7 @@ pub const Option = enum { .err => []const u8, .redraw => Redraw, .special_key => bool, - .click_events => bool, + .click_events => ClickEvents, .cmdline => []const u8, .cmdline_url => []const u8, .exit_code => i32, @@ -200,7 +210,12 @@ pub const Option = enum { .last else null, - .special_key, .click_events => if (value.len == 1) switch (value[0]) { + .click_events => if (value.len == 1) switch (value[0]) { + '1' => .Absolute, + '2' => .Relative, + else => null, + } else null, + .special_key => if (value.len == 1) switch (value[0]) { '0' => false, '1' => true, else => null,