summaryrefslogtreecommitdiff
path: root/src/terminal.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/terminal.zig')
-rw-r--r--src/terminal.zig92
1 files changed, 61 insertions, 31 deletions
diff --git a/src/terminal.zig b/src/terminal.zig
index 4751a09f..4d16bef2 100644
--- a/src/terminal.zig
+++ b/src/terminal.zig
@@ -124,6 +124,9 @@ pub const State = struct {
replay: Replay,
reply: [256]u8 = undefined,
reply_len: u16 = 0,
+ /// The grid cell the program last heard the mouse at, so motion is
+ /// reported once per cell.
+ mouse_cell: if (enabled) ?ghostty_vt.Coordinate else void = if (enabled) null else {},
};
const GColor = ghostty_vt.color;
@@ -374,8 +377,7 @@ pub fn bracketedPaste(pane: *const Pane) bool {
pub fn reportsMouse(pane: *const Pane) bool {
if (comptime !enabled) return false;
const state = pane.terminal orelse return false;
- const m = &state.vt.modes;
- return m.get(.mouse_event_normal) or m.get(.mouse_event_button) or m.get(.mouse_event_any);
+ return state.vt.flags.mouse_event != .none;
}
/// ...and wants them in SGR (1006) rather than the legacy X10 bytes.
@@ -395,43 +397,71 @@ pub fn ownsWheel(pane: *const Pane) bool {
(state.vt.screens.active_key == .alternate and state.vt.modes.get(.mouse_alternate_scroll));
}
-pub const Wheel = enum(u8) { up = 64, down = 65, left = 66, right = 67 };
+pub const MouseMods = struct { shift: bool = false, alt: bool = false, ctrl: bool = false };
+pub const MouseButton = enum { left, middle, right, wheel_up, wheel_down, wheel_left, wheel_right };
+pub const MouseAction = enum { press, release, motion };
+
+/// One mouse event for the program, as xterm reports it (ctlseqs; ghostty's
+/// encoder): the tracking mode it set (1000 clicks, 1002 drags, 1003 all
+/// motion) decides whether it hears of it, the format it set (1006 SGR,
+/// 1015 urxvt, 1005 UTF-8, else X10) how, modifiers as shift+4, meta+8,
+/// ctrl+16. `col`,`row` are the 0-based grid cell; a drag that left the
+/// grid is reported clamped to its edge, motion only when the cell
+/// changed. Empty when the program does not hear of it.
+pub fn mouseBytes(pane: *Pane, buf: *[64]u8, action: MouseAction, button: ?MouseButton, mods: MouseMods, col: i32, row: i32, any_pressed: bool) []const u8 {
+ if (comptime !enabled) return &.{};
+ const state = pane.terminal orelse return &.{};
+ const input = ghostty_vt.input;
+ var writer = std.Io.Writer.fixed(buf);
+ var opts: input.MouseEncodeOptions = .{
+ .event = state.vt.flags.mouse_event,
+ .format = state.vt.flags.mouse_format,
+ .size = .{
+ .screen = .{ .width = state.vt.cols, .height = state.vt.rows },
+ .cell = .{ .width = 1, .height = 1 },
+ .padding = .{},
+ },
+ .any_button_pressed = any_pressed,
+ .last_cell = &state.mouse_cell,
+ };
+ // A release is always reported; the rest only over the grid, or held.
+ if (action == .release) opts.any_button_pressed = true;
+ input.encodeMouse(&writer, .{
+ .action = switch (action) {
+ .press => .press,
+ .release => .release,
+ .motion => .motion,
+ },
+ .button = if (button) |b| switch (b) {
+ .left => .left,
+ .middle => .middle,
+ .right => .right,
+ .wheel_up => .four,
+ .wheel_down => .five,
+ .wheel_left => .six,
+ .wheel_right => .seven,
+ } else null,
+ .mods = .{ .shift = mods.shift, .alt = mods.alt, .ctrl = mods.ctrl },
+ .pos = .{ .x = @as(f32, @floatFromInt(col)) + 0.5, .y = @as(f32, @floatFromInt(row)) + 0.5 },
+ }, opts) catch return &.{};
+ return writer.buffered();
+}
/// The bytes a wheel turn at grid cell `col`,`row` (0-based) sends the
-/// program, as xterm sends them: its mouse report in the format it asked
-/// for, else, on the alternate screen, an arrow key (none for a sideways
-/// turn). Null when the wheel is not the program's (`ownsWheel`) or the
-/// cell cannot be said in the legacy format.
-pub fn wheelBytes(pane: *const Pane, buf: *[32]u8, wheel: Wheel, col: u16, row: u16, ctrl: bool) ?[]const u8 {
+/// program: its mouse report while it tracks the mouse, else, on the
+/// alternate screen, an arrow key in the cursor keys' mode (none for a
+/// sideways turn). Null when the wheel is not the program's (`ownsWheel`).
+pub fn wheelBytes(pane: *Pane, buf: *[64]u8, wheel: MouseButton, col: u16, row: u16, mods: MouseMods) ?[]const u8 {
if (comptime !enabled) return null;
const state = pane.terminal orelse return null;
if (!ownsWheel(pane)) return null;
const m = &state.vt.modes;
if (!reportsMouse(pane)) return switch (wheel) {
- .up => if (m.get(.cursor_keys)) "\x1bOA" else "\x1b[A",
- .down => if (m.get(.cursor_keys)) "\x1bOB" else "\x1b[B",
- .left, .right => null,
+ .wheel_up => if (m.get(.cursor_keys)) "\x1bOA" else "\x1b[A",
+ .wheel_down => if (m.get(.cursor_keys)) "\x1bOB" else "\x1b[B",
+ else => null,
};
- const code = @intFromEnum(wheel) + @as(u8, if (ctrl) 16 else 0);
- const x = @as(u32, col) + 1;
- const y = @as(u32, row) + 1;
- if (m.get(.mouse_format_sgr)) return std.fmt.bufPrint(buf, "\x1b[<{d};{d};{d}M", .{ code, x, y }) catch null;
- if (m.get(.mouse_format_urxvt)) return std.fmt.bufPrint(buf, "\x1b[{d};{d};{d}M", .{ 32 + @as(u32, code), x, y }) catch null;
- buf[0..3].* = "\x1b[M".*;
- buf[3] = 32 + code;
- var n: usize = 4;
- for ([_]u32{ x, y }) |v| {
- const c = 32 + v;
- if (m.get(.mouse_format_utf8)) {
- if (c > 2047) return null;
- n += std.unicode.utf8Encode(@intCast(c), buf[n..]) catch return null;
- } else {
- if (c > 255) return null;
- buf[n] = @intCast(c);
- n += 1;
- }
- }
- return buf[0..n];
+ return mouseBytes(pane, buf, .press, wheel, mods, col, row, false);
}
/// The whole scrollback as plain text, `gpa`-owned: what a terminal's body