mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-18 13:30:29 +00:00
Handle Kitty click_events OSC133 extension
This commit is contained in:
113
src/Surface.zig
113
src/Surface.zig
@@ -3969,6 +3969,15 @@ pub fn mouseButtonCallback(
|
||||
log.warn("error processing links err={}", .{err});
|
||||
}
|
||||
}
|
||||
|
||||
// Handle prompt clicking. If we released our mouse on a prompt
|
||||
// and we support some kind of click events, then we need to
|
||||
// move to it.
|
||||
if (self.maybePromptClick()) |handled| {
|
||||
if (handled) return true;
|
||||
} else |err| {
|
||||
log.warn("error processing prompt click err={}", .{err});
|
||||
}
|
||||
}
|
||||
|
||||
// Report mouse events if enabled
|
||||
@@ -4188,10 +4197,6 @@ pub fn mouseButtonCallback(
|
||||
.y = pt_viewport.y,
|
||||
},
|
||||
}) orelse {
|
||||
// Weird... our viewport x/y that we just converted isn't
|
||||
// found in our pages. This is probably a bug but we don't
|
||||
// want to crash in releases because its harmless. So, we
|
||||
// only assert in debug mode.
|
||||
if (comptime std.debug.runtime_safety) unreachable;
|
||||
break :sel;
|
||||
};
|
||||
@@ -4278,6 +4283,106 @@ pub fn mouseButtonCallback(
|
||||
return false;
|
||||
}
|
||||
|
||||
fn maybePromptClick(self: *Surface) !bool {
|
||||
self.renderer_state.mutex.lock();
|
||||
defer self.renderer_state.mutex.unlock();
|
||||
const t: *terminal.Terminal = self.renderer_state.terminal;
|
||||
const screen: *terminal.Screen = t.screens.active;
|
||||
|
||||
// If our screen doesn't handle any prompt clicks, then we never
|
||||
// do anything.
|
||||
if (screen.semantic_prompt.click == .none) return false;
|
||||
|
||||
// If our cursor isn't currently at a prompt then we don't handle
|
||||
// prompt clicks because we can't move if we're not in a prompt!
|
||||
if (!t.cursorIsAtPrompt()) return false;
|
||||
|
||||
// If we have a selection currently, then releasing the mouse
|
||||
// completes the selection and we don't do prompt moving. I don't
|
||||
// love this logic, I think it should be generalized to "if the
|
||||
// mouse release was on a different cell than the mouse press" but
|
||||
// our mouse state at the time of writing this doesn't support that.
|
||||
if (screen.selection != null) return false;
|
||||
|
||||
// Get the pin for our mouse click.
|
||||
const pos = try self.rt_surface.getCursorPos();
|
||||
const pos_vp = self.posToViewport(pos.x, pos.y);
|
||||
const click_pin: terminal.Pin = pin: {
|
||||
const pin = screen.pages.pin(.{
|
||||
.viewport = .{
|
||||
.x = pos_vp.x,
|
||||
.y = pos_vp.y,
|
||||
},
|
||||
}) orelse {
|
||||
// See mouseButtonCallback for explanation
|
||||
if (comptime std.debug.runtime_safety) unreachable;
|
||||
return false;
|
||||
};
|
||||
|
||||
break :pin pin;
|
||||
};
|
||||
|
||||
// Get our cursor's most current prompt.
|
||||
const prompt_pin: terminal.Pin = prompt_pin: {
|
||||
var it = screen.cursor.page_pin.promptIterator(
|
||||
.left_up,
|
||||
null,
|
||||
);
|
||||
break :prompt_pin it.next() orelse {
|
||||
// This shouldn't be possible because we asserted we're at
|
||||
// a prompt above, so we MUST find some prompt in a left_up search.
|
||||
log.warn("cursor is at prompt but no prompt found", .{});
|
||||
if (comptime std.debug.runtime_safety) unreachable;
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
||||
// If our mouse click is before the prompt, we don't move.
|
||||
// We DO ALLOW clicks AFTER the prompt, specifically with Kitty's
|
||||
// click_events=1 since we rely on the shell to validate out of
|
||||
// bounds clicks. This matches Kitty's logic as best I can tell.
|
||||
if (click_pin.before(prompt_pin)) return false;
|
||||
|
||||
// At this point we've established:
|
||||
// - Screen supports prompt clicks
|
||||
// - Cursor is at a prompt
|
||||
// - Click is at or below our prompt
|
||||
switch (screen.semantic_prompt.click) {
|
||||
// Guarded at the start of this function
|
||||
.none => unreachable,
|
||||
|
||||
.click_events => {
|
||||
// For the event, we always send a left-click press event.
|
||||
// This matches what Kitty sends.
|
||||
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 },
|
||||
);
|
||||
|
||||
// Not that noisy since this only happens on prompt clicks.
|
||||
log.debug(
|
||||
"sending click_events=1 event=ESC{s}",
|
||||
.{resp[1..]},
|
||||
);
|
||||
|
||||
// Ask our IO thread to write the data
|
||||
self.queueIo(.{ .write_small = .{
|
||||
.data = data,
|
||||
.len = @intCast(resp.len),
|
||||
} }, .locked);
|
||||
},
|
||||
|
||||
.cl => |cl| {
|
||||
// TODO: Handle these
|
||||
_ = cl;
|
||||
},
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Performs the "click-to-move" logic to move the cursor to the given
|
||||
/// screen point if possible. This works by converting the path to the
|
||||
/// given point into a series of arrow key inputs.
|
||||
|
||||
Reference in New Issue
Block a user