diff options
| -rw-r--r-- | src/detached/wire.zig | 10 | ||||
| -rw-r--r-- | src/gui/gui.zig | 66 | ||||
| -rw-r--r-- | src/mouse.zig | 39 | ||||
| -rw-r--r-- | src/pardes.zig | 3 | ||||
| -rw-r--r-- | src/terminal.zig | 92 | ||||
| -rw-r--r-- | src/tty/tty.zig | 2 |
6 files changed, 159 insertions, 53 deletions
diff --git a/src/detached/wire.zig b/src/detached/wire.zig index 051e9317..9a1ab608 100644 --- a/src/detached/wire.zig +++ b/src/detached/wire.zig @@ -12,7 +12,7 @@ const ShaderAnimation = pardes.config.Runtime.ShaderAnimation; /// to be refused, which is what this number is for. /// 8: one layer type: a tag band carries all its rows (not a layer per /// line), and a frame carries the core's placed regions after its layers. -pub const version: u16 = 8; +pub const version: u16 = 9; /// The Chrome palette is carried field by field as its declaration lists /// them, so any change to it changes the wire: bump `version`, then this. @@ -1052,6 +1052,8 @@ pub fn encodeClient(out: []u8, msg: ClientMsg) Error![]const u8 { try w.putU16(m.col); try w.putU16(m.row); try w.putBool(m.ctrl); + try w.putBool(m.shift); + try w.putBool(m.alt); try putBodyHit(&w, m.body_hit); try putTagHit(&w, m.tag_hit); }, @@ -1115,6 +1117,8 @@ pub fn decodeClient(tag: u8, payload: []const u8) Error!ClientMsg { .col = try r.getU16(), .row = try r.getU16(), .ctrl = try r.getBool(), + .shift = try r.getBool(), + .alt = try r.getBool(), .body_hit = try getBodyHit(&r), .tag_hit = try getTagHit(&r), } } }, @@ -1357,13 +1361,15 @@ test "detached wire: every Event variant round-trips" { // the only place a value can be mistranslated and still decode. for (std.enums.values(pardes.Mouse.Button)) |button| { for (std.enums.values(pardes.Mouse.Kind)) |kind| { - const m: pardes.Mouse = .{ .button = button, .kind = kind, .col = 4200, .row = 7, .ctrl = true }; + const m: pardes.Mouse = .{ .button = button, .kind = kind, .col = 4200, .row = 7, .ctrl = true, .shift = true, .alt = true }; const got = (try roundClient(&buf, .{ .event = .{ .mouse = m } })).event.mouse; try testing.expectEqual(m.button, got.button); try testing.expectEqual(m.kind, got.kind); try testing.expectEqual(m.col, got.col); try testing.expectEqual(m.row, got.row); try testing.expectEqual(m.ctrl, got.ctrl); + try testing.expectEqual(m.shift, got.shift); + try testing.expectEqual(m.alt, got.alt); } } } diff --git a/src/gui/gui.zig b/src/gui/gui.zig index 045f22d9..4ce554f3 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -3139,8 +3139,8 @@ const StdinFeed = struct { var seq_start: usize = 0; while (seq_start < len) { - // ESC]777;mouse;<down|up|motion>;<SDL button, 0 for none>;<x>;<y>BEL - // ESC]777;mouse;wheel;<amount>;<x>;<y>BEL + // ESC]777;mouse;<down|up|motion>;<SDL button, 0 for none>;<x>;<y>[;<mods>]BEL + // ESC]777;mouse;wheel;<amount>;<x>;<y>[;<mods>]BEL // A mouse event at window pixels, built as SDL would and run // through dispatch, so a driver exercises the real pointer path // (pixel to cell, tag and body hits). SGR mouse carries only cells. @@ -3159,6 +3159,9 @@ const StdinFeed = struct { const button = std.fmt.parseInt(u8, second, 10) catch 0; const x = std.fmt.parseFloat(f32, parts.next() orelse "") catch 0; const y = std.fmt.parseFloat(f32, parts.next() orelse "") catch 0; + // An optional fifth field holds modifiers as xterm + // counts them: shift 4, meta (alt) 8, ctrl 16. + const held = std.fmt.parseInt(u8, parts.next() orelse "0", 10) catch 0; var sev = std.mem.zeroes(c.SDL_Event); if (std.mem.eql(u8, kind, "wheel")) { // ESC]777;mouse;wheel;<y, SDL's sign: up positive>;<x>;<y>BEL: @@ -3181,7 +3184,14 @@ const StdinFeed = struct { sev.button.x = x; sev.button.y = y; } + const before = c.SDL_GetModState(); + var mods: c.SDL_Keymod = 0; + if (held & 4 != 0) mods |= c.SDL_KMOD_LSHIFT; + if (held & 8 != 0) mods |= c.SDL_KMOD_LALT; + if (held & 16 != 0) mods |= c.SDL_KMOD_LCTRL; + c.SDL_SetModState(mods); dispatch(gp, in, &sev); + c.SDL_SetModState(before); out.n_events += 1; } seq_start += mouse_prefix.len + end + 1; @@ -3256,6 +3266,8 @@ const StdinFeed = struct { .col = @intCast(@max(m.col, 0)), .row = @intCast(@max(m.row, 0)), .ctrl = m.mods.ctrl, + .shift = m.mods.shift, + .alt = m.mods.alt, } }); out.n_events += 1; } @@ -3526,6 +3538,7 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void { }; g.pointer_mapped = true; g.pointer_cell = mc; + const mods = pointerMods(); in.post(.{ .mouse = .{ .button = button, @@ -3535,7 +3548,9 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void { .tag_hit = mc.tag_hit, .body_hit = mc.body_hit, .rule_above = mc.rule_above, - .ctrl = (c.SDL_GetModState() & c.SDL_KMOD_CTRL) != 0, + .ctrl = mods.ctrl, + .shift = mods.shift, + .alt = mods.alt, }, }); }, @@ -3559,6 +3574,7 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void { }; g.pointer_mapped = true; g.pointer_cell = mc; + const mods = pointerMods(); in.post(.{ .mouse = .{ .button = held orelse .none, .kind = if (held != null) .drag else .motion, @@ -3566,6 +3582,9 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void { .row = mc.row, .tag_hit = mc.tag_hit, .body_hit = mc.body_hit, + .ctrl = mods.ctrl, + .shift = mods.shift, + .alt = mods.alt, } }); }, c.SDL_EVENT_MOUSE_WHEEL => { @@ -3581,7 +3600,7 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void { }; g.pointer_mapped = true; g.pointer_cell = mc; - wheelAt(g, in, .{ .x = w.x, .y = w.y, .integer_y = w.integer_y }, mc); + wheelAt(g, in, .{ .x = w.x, .y = w.y, .integer_y = w.integer_y, .mods = pointerMods() }, mc); }, c.SDL_EVENT_FINGER_DOWN, c.SDL_EVENT_FINGER_MOTION, c.SDL_EVENT_FINGER_UP, c.SDL_EVENT_FINGER_CANCELED => { const f = sev.tfinger; @@ -3913,16 +3932,16 @@ fn wheelAt(g: *Gui, in: *Input, w: WheelTurn, mc: MouseCell) void { .pane = @intCast(id), .delta_pixels = -w.y * @as(f32, @floatFromInt(g.cell_h)), } }); - } else if (pane.isTerminal() and pardes.panes.terminal.ownsWheel(pane)) { + } else if (!w.mods.shift and pane.isTerminal() and pardes.panes.terminal.ownsWheel(pane)) { // The program's (codex, htop, less): every whole // turn goes to it, no glide, for nothing of ours - // moves. + // moves. Shift keeps the wheel pardes's. resetScroll(g); g.program_wheel = accumulateWheelDelta(g.program_wheel, w.y); while (g.program_wheel >= 1) : (g.program_wheel -= 1) - in.post(.{ .mouse = .{ .button = .wheel_down, .kind = .press, .col = mc.col, .row = mc.row, .tag_hit = mc.tag_hit, .body_hit = mc.body_hit } }); + in.post(.{ .mouse = .{ .button = .wheel_down, .kind = .press, .col = mc.col, .row = mc.row, .tag_hit = mc.tag_hit, .body_hit = mc.body_hit, .ctrl = w.mods.ctrl, .shift = w.mods.shift, .alt = w.mods.alt } }); while (g.program_wheel <= -1) : (g.program_wheel += 1) - in.post(.{ .mouse = .{ .button = .wheel_up, .kind = .press, .col = mc.col, .row = mc.row, .tag_hit = mc.tag_hit, .body_hit = mc.body_hit } }); + in.post(.{ .mouse = .{ .button = .wheel_up, .kind = .press, .col = mc.col, .row = mc.row, .tag_hit = mc.tag_hit, .body_hit = mc.body_hit, .ctrl = w.mods.ctrl, .shift = w.mods.shift, .alt = w.mods.alt } }); } else { g.scroll_pane = id; g.scroll_col = mc.col; @@ -3938,9 +3957,9 @@ fn wheelAt(g: *Gui, in: *Input, w: WheelTurn, mc: MouseCell) void { } else { g.scroll_delta = accumulateWheelDelta(g.scroll_delta, w.y); while (g.scroll_delta >= 1) : (g.scroll_delta -= 1) - in.post(.{ .mouse = .{ .button = .wheel_down, .kind = .press, .col = mc.col, .row = mc.row, .tag_hit = mc.tag_hit, .body_hit = mc.body_hit } }); + in.post(.{ .mouse = .{ .button = .wheel_down, .kind = .press, .col = mc.col, .row = mc.row, .tag_hit = mc.tag_hit, .body_hit = mc.body_hit, .ctrl = w.mods.ctrl, .shift = w.mods.shift, .alt = w.mods.alt } }); while (g.scroll_delta <= -1) : (g.scroll_delta += 1) - in.post(.{ .mouse = .{ .button = .wheel_up, .kind = .press, .col = mc.col, .row = mc.row, .tag_hit = mc.tag_hit, .body_hit = mc.body_hit } }); + in.post(.{ .mouse = .{ .button = .wheel_up, .kind = .press, .col = mc.col, .row = mc.row, .tag_hit = mc.tag_hit, .body_hit = mc.body_hit, .ctrl = w.mods.ctrl, .shift = w.mods.shift, .alt = w.mods.alt } }); } } if (w.x != 0) in.post(.{ .mouse = .{ @@ -3950,10 +3969,21 @@ fn wheelAt(g: *Gui, in: *Input, w: WheelTurn, mc: MouseCell) void { .row = mc.row, .tag_hit = mc.tag_hit, .body_hit = mc.body_hit, + .ctrl = w.mods.ctrl, + .shift = w.mods.shift, + .alt = w.mods.alt, } }); } -const WheelTurn = struct { x: f32 = 0, y: f32 = 0, integer_y: i32 = 0 }; +const WheelTurn = struct { x: f32 = 0, y: f32 = 0, integer_y: i32 = 0, mods: PointerMods = .{} }; + +/// The modifiers held with a pointer event, as the core's Mouse carries them. +const PointerMods = struct { ctrl: bool = false, shift: bool = false, alt: bool = false }; + +fn pointerMods() PointerMods { + const m = c.SDL_GetModState(); + return .{ .ctrl = (m & c.SDL_KMOD_CTRL) != 0, .shift = (m & c.SDL_KMOD_SHIFT) != 0, .alt = (m & c.SDL_KMOD_ALT) != 0 }; +} fn mouseCellWithGeometry(g: *const Gui, core: ?*const pardes.Pardes, x: f32, y: f32, geometry: WindowGeometry) ?MouseCell { const physical = windowPointToPixels(geometry, x, y); @@ -4934,6 +4964,7 @@ test "a wheel over codex in a terminal reaches its pty as the mouse reports it a g.scroll_notch = 0; g.program_wheel = 0; g.glide = .{}; + defer g.glide.deinit(gpa); g.cell_h = 20; var in: Input = .{ .core = core }; var sent: std.ArrayList(u8) = .empty; @@ -4967,6 +4998,15 @@ test "a wheel over codex in a terminal reaches its pty as the mouse reports it a try std.testing.expectEqualStrings("", try Sent.take(core, &sent)); wheelAt(&g, &in, .{ .y = 0.5 }, mc); try std.testing.expectEqualStrings("\x1b[<64;6;4M", try Sent.take(core, &sent)); + // Alt is meta, 8; Shift keeps the turn pardes's own, a glide. + wheelAt(&g, &in, .{ .y = 1, .integer_y = 1, .mods = .{ .alt = true } }, mc); + try std.testing.expectEqualStrings("\x1b[<72;6;4M", try Sent.take(core, &sent)); + wheelAt(&g, &in, .{ .y = 1, .integer_y = 1, .mods = .{ .shift = true } }, mc); + try std.testing.expectEqualStrings("", try Sent.take(core, &sent)); + try std.testing.expectEqual(@as(i32, -1), g.scroll_notch); + stepGlide(&g, core, gpa); + try std.testing.expectEqualStrings("", try Sent.take(core, &sent)); + resetScroll(&g); // Its sign-in screen: mouse off, alternate scroll on, so arrow keys. core.update(.{ .output = .{ .pane = 0, .bytes = "\x1b[?1006l\x1b[?1003l\x1b[?1002l\x1b[?1000l\x1b[?1007h" } }); _ = try Sent.take(core, &sent); @@ -5171,7 +5211,8 @@ fn stepGlide(g: *Gui, core: *pardes.Pardes, gpa: std.mem.Allocator) void { const down = left > 0; left += if (down) -1 else 1; const was = pane.scroll(); - core.update(.{ .mouse = .{ .button = if (down) .wheel_down else .wheel_up, .kind = .press, .col = g.scroll_col, .row = g.scroll_row } }); + // Shift: pardes's own scroll, never a program's wheel. + core.update(.{ .mouse = .{ .button = if (down) .wheel_down else .wheel_up, .kind = .press, .col = g.scroll_col, .row = g.scroll_row, .shift = true } }); if (pane.scroll() == was) break; moved = true; } @@ -5344,6 +5385,7 @@ fn stepScroll(g: *Gui, core: *pardes.Pardes, gpa: std.mem.Allocator) void { .kind = .press, .col = g.scroll_col, .row = g.scroll_row, + .shift = true, // pardes's own scroll, never a program's wheel } }); if (pane.scroll() == was) { resetScroll(g); 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)); } diff --git a/src/pardes.zig b/src/pardes.zig index f0d22d57..d6533b3b 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -3452,6 +3452,9 @@ pub const Mouse = struct { col: u16, row: u16, ctrl: bool = false, + shift: bool = false, + /// Alt, which xterm's mouse reports call meta. + alt: bool = false, body_hit: ?BodyHit = null, tag_hit: ?TagHit = null, /// A pixel frontend's press on the rule it draws at the top of this row, 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 diff --git a/src/tty/tty.zig b/src/tty/tty.zig index ef7f2761..0b933065 100644 --- a/src/tty/tty.zig +++ b/src/tty/tty.zig @@ -2261,6 +2261,8 @@ fn mouseEvent(m: vaxis.Mouse) ?pardes.Event { .col = @intCast(m.col), .row = @intCast(m.row), .ctrl = m.mods.ctrl, + .shift = m.mods.shift, + .alt = m.mods.alt, } }; } |
