diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-30 13:49:09 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:17 -0300 |
| commit | 387d6a39bad76c5eb2f56328f7cf7869a9a10559 (patch) | |
| tree | cf7aa5209e1d0c9dc522833239b2a710ca1351d9 /src/terminal.zig | |
| parent | 3921bfe63980552eddeaa930d5d921d9a2868b37 (diff) | |
| download | pardes-387d6a39bad76c5eb2f56328f7cf7869a9a10559.tar.gz pardes-387d6a39bad76c5eb2f56328f7cf7869a9a10559.zip | |
A wheel over a terminal whose program owns it goes to the program: its mouse report when it tracks the mouse, arrow keys on the alternate screen with alternate scroll, so codex, htop and less scroll
codex 0.158 moved its chat onto the alternate screen with any-event SGR
mouse tracking (1049, 1000/1002/1003/1006, 1007 off); its sign-in screen
keeps 1007 on with tracking off. pardes never forwarded a wheel to a
program, only scrolled ghostty's scrollback, which the alternate screen
does not have, so the wheel did nothing there. A shell on the main screen
still scrolls its scrollback as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/terminal.zig')
| -rw-r--r-- | src/terminal.zig | 49 |
1 files changed, 49 insertions, 0 deletions
diff --git a/src/terminal.zig b/src/terminal.zig index 1a3b6a21..4751a09f 100644 --- a/src/terminal.zig +++ b/src/terminal.zig @@ -385,6 +385,55 @@ pub fn mouseFormatSgr(pane: *const Pane) bool { return state.vt.modes.get(.mouse_format_sgr); } +/// A wheel over the program is the program's: it tracks the mouse, or it is +/// on the alternate screen, which has no scrollback, with alternate scroll +/// (DECSET 1007, on unless turned off) asking for arrow keys instead. +pub fn ownsWheel(pane: *const Pane) bool { + if (comptime !enabled) return false; + const state = pane.terminal orelse return false; + return reportsMouse(pane) or + (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 }; + +/// 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 { + 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, + }; + 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]; +} + /// The whole scrollback as plain text, `gpa`-owned: what a terminal's body /// reads and `Save` writes out. Logical lines: rows the terminal wrapped /// are joined again (ghostty's wrap flags, as pty/run's output is), and |
