diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-30 14:06:31 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:17 -0300 |
| commit | 3f6733c805d873a7af6a6064ace0cf5916f481da (patch) | |
| tree | 302874e21941dbf8b599b5e407f430e334594f62 /src/mouse.zig | |
| parent | 387d6a39bad76c5eb2f56328f7cf7869a9a10559 (diff) | |
| download | pardes-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.zig | 39 |
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)); } |
