diff options
Diffstat (limited to 'src/terminal.zig')
| -rw-r--r-- | src/terminal.zig | 92 |
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 |
