summaryrefslogtreecommitdiff
path: root/src/mouse.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-10-01 12:44:06 -0300
committerGabriel Schneider <[email protected]>2026-10-01 12:58:23 -0300
commit8eeab5df9c09caafc07c93c2fc3baf6e6700afed (patch)
treea35f70177414e360010f36abb62cb05eabd285d5 /src/mouse.zig
parenta99edf403dbcebae2ce557e2ba39078ffe876a92 (diff)
downloadpardes-8eeab5df9c09caafc07c93c2fc3baf6e6700afed.tar.gz
pardes-8eeab5df9c09caafc07c93c2fc3baf6e6700afed.zip
Alt/Option-click is B2 and Super/Cmd-click is B3, sweeps included, as in plan9port: a one-button trackpad can execute and look
The core decides it at the press (mouse.emulateButton) and keeps it to the release, whatever keys are let go between. Ctrl-click stays a definition, and a program tracking the mouse still gets the plain click (Alt as its meta, Super as nothing). The SDL, macOS and web shells now pass Super; the macOS and web shells pass Alt on a click too. A terminal's mouse report has no Super bit, so in a terminal only Alt-click is B2. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/mouse.zig')
-rw-r--r--src/mouse.zig58
1 files changed, 56 insertions, 2 deletions
diff --git a/src/mouse.zig b/src/mouse.zig
index 41362249..21961cf8 100644
--- a/src/mouse.zig
+++ b/src/mouse.zig
@@ -446,7 +446,23 @@ fn programMouse(p: *Pardes, m: Mouse) bool {
}
}
-pub fn handleMouse(p: *Pardes, m: Mouse) void {
+/// plan9port's convention for a mouse or trackpad with one button:
+/// Alt-click is B2 and Super-click (Cmd) is B3, sweeps included, decided at
+/// the press and kept through its release, whatever keys are let go
+/// meanwhile. Ctrl-click keeps its own meaning (a definition). Run after
+/// programMouse: a program that takes the click gets it as it was.
+fn emulateButton(p: *Pardes, m: *Mouse) void {
+ if (m.button != .left) return;
+ if (m.kind == .press) p.click_as = if (m.ctrl) null else if (m.super) .right else if (m.alt) .middle else null;
+ const as = p.click_as orelse return;
+ m.button = as;
+ m.alt = false;
+ m.super = false;
+ if (m.kind == .release) p.click_as = null;
+}
+
+pub fn handleMouse(p: *Pardes, event: Mouse) void {
+ var m = event;
// A click goes somewhere else: the question it leaves unanswered is
// taken back, and the pane stays. A wheel only looks around.
if (m.kind == .press and switch (m.button) {
@@ -496,6 +512,7 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void {
}
p.pointer_inside = true;
if (programMouse(p, m)) return;
+ emulateButton(p, &m);
if (m.button == .none and m.kind == .motion) {
p.raw_hover_intent = true;
look.refreshLookHoverFromRaw(p);
@@ -2007,6 +2024,35 @@ test "a wheel over a program that owns it is the program's, in the form it asked
try std.testing.expectEqualStrings("\x1b[<64;6;4M", try ptyWrites(p, &out));
}
+test "Alt-click is B2 and Super-click B3, kept from the press through the release; Ctrl-click stays B1" {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
+ defer p.deinit();
+ const file = try p.setTestFile("Newcol here\n");
+ p.acknowledgePanelPresentation(&.{});
+ const r = p.rects[0];
+ const col = r.x + config.GUTTER + panes.File.gutterWidth(file) + 1;
+ const row = p.bodyTop(file, r);
+ // A Super-press sweeps with B3's selection, not B1's.
+ p.update(.{ .mouse = .{ .button = .left, .kind = .press, .col = col, .row = row, .super = true } });
+ try std.testing.expect(p.drag == .select and file.sel[2].state != .none and file.sel[0].state == .none);
+ p.update(.{ .mouse = .{ .button = .left, .kind = .release, .col = col, .row = row } });
+ try std.testing.expect(p.drag == .none);
+ try std.testing.expectEqual(@as(?Mouse.Button, null), p.click_as);
+ // Ctrl with Alt is Ctrl's: a B1 click (a definition), never B2.
+ const cols = p.ncol;
+ p.update(.{ .mouse = .{ .button = .left, .kind = .press, .col = col, .row = row, .ctrl = true, .alt = true } });
+ try std.testing.expectEqual(@as(?Mouse.Button, null), p.click_as);
+ p.update(.{ .mouse = .{ .button = .left, .kind = .release, .col = col, .row = row, .ctrl = true } });
+ try std.testing.expectEqual(cols, p.ncol);
+ // Alt-click on Newcol runs it, as B2 does, though Alt is let go
+ // before the button.
+ p.update(.{ .mouse = .{ .button = .left, .kind = .press, .col = col, .row = row, .alt = true } });
+ try std.testing.expectEqual(@as(?Mouse.Button, .middle), p.click_as);
+ p.update(.{ .mouse = .{ .button = .left, .kind = .release, .col = col, .row = row } });
+ try std.testing.expectEqual(cols + 1, p.ncol);
+ try std.testing.expectEqual(@as(?Mouse.Button, null), p.click_as);
+}
+
test "a program tracking the mouse gets its clicks, drags and moves over its grid as xterm reports them; Shift and chrome stay pardes's" {
if (comptime !panes.terminal.enabled) return error.SkipZigTest;
const gpa = std.testing.allocator;
@@ -2023,7 +2069,7 @@ test "a program tracking the mouse gets its clicks, drags and moves over its gri
const r = p.rects[0];
const col = r.x + config.GUTTER + 5;
const row = p.bodyTop(pane, r) + 3;
- const Ev = struct { button: Mouse.Button, kind: Mouse.Kind, dx: i32 = 0, dy: i32 = 0, shift: bool = false, alt: bool = false, ctrl: bool = false };
+ const Ev = struct { button: Mouse.Button, kind: Mouse.Kind, dx: i32 = 0, dy: i32 = 0, shift: bool = false, alt: bool = false, ctrl: bool = false, super: bool = false };
const send = struct {
fn f(pp: *Pardes, o: *std.ArrayList(u8), c: u16, rw: u16, e: Ev) ![]const u8 {
pp.update(.{ .mouse = .{
@@ -2034,6 +2080,7 @@ test "a program tracking the mouse gets its clicks, drags and moves over its gri
.shift = e.shift,
.alt = e.alt,
.ctrl = e.ctrl,
+ .super = e.super,
} });
return ptyWrites(pp, o);
}
@@ -2046,6 +2093,13 @@ test "a program tracking the mouse gets its clicks, drags and moves over its gri
// 1000 has no drags; the release is reported where it lands.
try std.testing.expectEqualStrings("", try send(p, &out, col, row, .{ .button = .left, .kind = .drag, .dx = 1 }));
try std.testing.expectEqualStrings("\x1b[<0;7;4m", try send(p, &out, col, row, .{ .button = .left, .kind = .release, .dx = 1 }));
+ // An Alt- or Super-click (B2, B3 elsewhere) is still the program's
+ // button 1, Alt its meta and Super no bit at all.
+ try std.testing.expectEqualStrings("\x1b[<8;6;4M", try send(p, &out, col, row, .{ .button = .left, .kind = .press, .alt = true }));
+ try std.testing.expectEqualStrings("\x1b[<0;6;4m", try send(p, &out, col, row, .{ .button = .left, .kind = .release }));
+ try std.testing.expectEqualStrings("\x1b[<0;6;4M", try send(p, &out, col, row, .{ .button = .left, .kind = .press, .super = true }));
+ try std.testing.expectEqualStrings("\x1b[<0;6;4m", try send(p, &out, col, row, .{ .button = .left, .kind = .release, .super = true }));
+ try std.testing.expect(p.drag == .none);
// B2 and B3 are pardes's execute and look, as in any pane.
try std.testing.expectEqualStrings("", try send(p, &out, col, row, .{ .button = .right, .kind = .press }));
try std.testing.expect(p.drag == .select);