summaryrefslogtreecommitdiff
path: root/src/mouse.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-30 14:06:31 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commit3f6733c805d873a7af6a6064ace0cf5916f481da (patch)
tree302874e21941dbf8b599b5e407f430e334594f62 /src/mouse.zig
parent387d6a39bad76c5eb2f56328f7cf7869a9a10559 (diff)
downloadpardes-3f6733c805d873a7af6a6064ace0cf5916f481da.tar.gz
pardes-3f6733c805d873a7af6a6064ace0cf5916f481da.zip
A mouse event carries Shift and Alt as well as Ctrl, from SDL and from the tty's input; a program's wheel report carries them as xterm counts them, and Shift keeps the wheel pardes's own scroll
The reports are ghostty's encoder now (shift+4, meta+8, ctrl+16), with the mode and format ghostty tracks. The detached wire carries the two new bits: version 9. The test shell's ESC]777;mouse takes an optional fifth field of modifiers. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/mouse.zig')
-rw-r--r--src/mouse.zig39
1 files changed, 31 insertions, 8 deletions
diff --git a/src/mouse.zig b/src/mouse.zig
index a0ec6e47..e80ea7f8 100644
--- a/src/mouse.zig
+++ b/src/mouse.zig
@@ -313,16 +313,24 @@ pub fn sameBodyCell(a: ?Mouse.BodyHit, b: ?Mouse.BodyHit) bool {
/// A wheel over a terminal whose program owns it (`terminal.ownsWheel`:
/// codex, htop, less) goes to the program as the program asked, one turn
/// per line, instead of scrolling a scrollback the alternate screen does
-/// not have. False when pardes scrolls.
-fn programWheel(p: *Pardes, id: usize, wheel: panes.terminal.Wheel, col: u16, row: u16, ctrl: bool) bool {
+/// not have. Shift keeps it pardes's, as xterm's Shift does. False when
+/// pardes scrolls.
+fn programWheel(p: *Pardes, id: usize, m: Mouse, col: u16, row: u16) bool {
+ if (m.shift) return false;
const pane = p.panes[id] orelse return false;
if (!pane.isTerminal() or !panes.terminal.ownsWheel(pane)) return false;
const r = p.rects[id];
const body_y = p.bodyTop(pane, r);
const gcol = std.math.clamp(col -| (r.x + config.GUTTER), 0, pane.cols -| 1);
const grow = std.math.clamp(row -| body_y, 0, pane.rows -| 1);
- var buf: [32]u8 = undefined;
- const bytes = panes.terminal.wheelBytes(pane, &buf, wheel, gcol, grow, ctrl) orelse return true;
+ var buf: [64]u8 = undefined;
+ const wheel: panes.terminal.MouseButton = switch (m.button) {
+ .wheel_up => .wheel_up,
+ .wheel_down => .wheel_down,
+ .wheel_left => .wheel_left,
+ else => .wheel_right,
+ };
+ const bytes = panes.terminal.wheelBytes(pane, &buf, wheel, gcol, grow, .{ .alt = m.alt, .ctrl = m.ctrl }) orelse return true;
for (0..@intCast(config.wheel_rows)) |_| p.emitWrite(id, bytes);
return true;
}
@@ -525,7 +533,7 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void {
const pane = p.panes[id].?;
if (pane.hasPdf())
panes.Pdf.verticalWheel(p, pane, if (m.button == .wheel_up) -1 else 1)
- else if (!programWheel(p, id, if (m.button == .wheel_up) .up else .down, mcol, mrow, m.ctrl))
+ else if (!programWheel(p, id, m, mcol, mrow))
pane.scrollBy(if (m.button == .wheel_up) -config.wheel_rows else config.wheel_rows);
},
.wheel_left, .wheel_right => {
@@ -533,7 +541,7 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void {
if (!config.wheelTick(&p.wheel_guard, false)) return;
const id = hovered orelse return;
const pane = p.panes[id].?;
- if (programWheel(p, id, if (m.button == .wheel_right) .right else .left, mcol, mrow, m.ctrl)) {
+ if (programWheel(p, id, m, mcol, mrow)) {
// the program's
} else if (pane.hasPdf()) {
panes.Pdf.horizontalWheel(p, pane, if (m.button == .wheel_right) 1 else -1);
@@ -1838,11 +1846,16 @@ test "a wheel over a program that owns it is the program's, in the form it asked
// Grid cell 6,4 (1-based), as a program counts.
const col = r.x + config.GUTTER + 5;
const row = p.bodyTop(pane, r) + 3;
- const Case = struct { modes: []const u8, button: Mouse.Button, ctrl: bool = false, sent: []const u8 };
+ const Case = struct { modes: []const u8, button: Mouse.Button, ctrl: bool = false, alt: bool = false, shift: bool = false, sent: []const u8 };
const cases = [_]Case{
// codex's chat (0.158+): the alternate screen, any-event tracking, SGR.
.{ .modes = "\x1b[?2004h\x1b[>7u\x1b[?1004h\x1b[?1049h\x1b[?1007l\x1b[?1000h\x1b[?1002h\x1b[?1003h\x1b[?1006h", .button = .wheel_up, .sent = "\x1b[<64;6;4M" },
.{ .modes = "", .button = .wheel_down, .ctrl = true, .sent = "\x1b[<81;6;4M" },
+ // Meta is 8, both 24; Shift keeps the wheel pardes's, which has
+ // nothing to scroll on the alternate screen.
+ .{ .modes = "", .button = .wheel_up, .alt = true, .sent = "\x1b[<72;6;4M" },
+ .{ .modes = "", .button = .wheel_up, .alt = true, .ctrl = true, .sent = "\x1b[<88;6;4M" },
+ .{ .modes = "", .button = .wheel_up, .shift = true, .sent = "" },
// htop's legacy bytes (1000 alone), and 1015's.
.{ .modes = "\x1b[?1006l", .button = .wheel_down, .sent = "\x1b[M\x61\x26\x24" },
.{ .modes = "\x1b[?1015h", .button = .wheel_up, .sent = "\x1b[96;6;4M" },
@@ -1857,7 +1870,7 @@ test "a wheel over a program that owns it is the program's, in the form it asked
for (cases) |case| {
p.update(.{ .output = .{ .pane = 0, .bytes = case.modes } });
_ = try ptyWrites(p, &out);
- p.update(.{ .mouse = .{ .button = case.button, .kind = .press, .col = col, .row = row, .ctrl = case.ctrl } });
+ p.update(.{ .mouse = .{ .button = case.button, .kind = .press, .col = col, .row = row, .ctrl = case.ctrl, .alt = case.alt, .shift = case.shift } });
try std.testing.expectEqualStrings(case.sent, try ptyWrites(p, &out));
}
// A shell on the main screen: the wheel scrolls its scrollback, and
@@ -1873,4 +1886,14 @@ test "a wheel over a program that owns it is the program's, in the form it asked
p.update(.{ .mouse = .{ .button = .wheel_up, .kind = .press, .col = col, .row = row } });
try std.testing.expectEqualStrings("", try ptyWrites(p, &out));
try std.testing.expect(pane.scroll() != was);
+ // A program tracking the mouse on the main screen: Shift and the
+ // wheel scroll the scrollback, the program hears nothing.
+ p.update(.{ .output = .{ .pane = 0, .bytes = "\x1b[?1000h\x1b[?1006h" } });
+ _ = try ptyWrites(p, &out);
+ const tracked = pane.scroll();
+ p.update(.{ .mouse = .{ .button = .wheel_up, .kind = .press, .col = col, .row = row, .shift = true } });
+ try std.testing.expectEqualStrings("", try ptyWrites(p, &out));
+ try std.testing.expect(pane.scroll() != tracked);
+ p.update(.{ .mouse = .{ .button = .wheel_up, .kind = .press, .col = col, .row = row } });
+ try std.testing.expectEqualStrings("\x1b[<64;6;4M", try ptyWrites(p, &out));
}