From 1b88f7e9ab824ffd900fdd51676b2d1213d7e01d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 17 Mar 2023 19:10:08 -0700 Subject: [PATCH 1/2] support mouse alt scroll (mode 1007) This enables less and other older legacy programs to get mouse scroll events --- src/Surface.zig | 27 ++++++++++++++++++++++++++- src/terminal/Terminal.zig | 1 + src/terminal/ansi.zig | 4 ++++ src/termio/Exec.zig | 2 ++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/Surface.zig b/src/Surface.zig index 5c1d51fe6..8dbff7361 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -1079,13 +1079,38 @@ pub fn scrollCallback(self: *Surface, xoff: f64, yoff: f64) !void { // Positive is up const sign: isize = if (yoff > 0) -1 else 1; - const delta: isize = sign * @max(@divFloor(self.grid_size.rows, 15), 1); + const delta_unsigned = @max(@divFloor(self.grid_size.rows, 15), 1); + const delta: isize = sign * delta_unsigned; log.info("scroll: delta={}", .{delta}); { self.renderer_state.mutex.lock(); defer self.renderer_state.mutex.unlock(); + // If we're in alternate screen with alternate scroll enabled, then + // we convert to cursor keys. This only happens if we're: + // (1) alt screen (2) no explicit mouse reporting and (3) alt + // scroll mode enabled. + if (self.io.terminal.active_screen == .alternate and + self.io.terminal.modes.mouse_event == .none and + self.io.terminal.modes.mouse_alternate_scroll) + { + const up_down_seq = if (delta < 0) "\x1bOA" else "\x1bOB"; + for (0..delta_unsigned) |_| { + _ = self.io_thread.mailbox.push(.{ + .write_stable = up_down_seq, + }, .{ .forever = {} }); + } + + // After sending all our messages we have to notify our IO thread + try self.io_thread.wakeup.notify(); + return; + } + + // We have mouse events, are not in an alternate scroll buffer, + // or have alternate scroll disabled. In this case, we just run + // the normal logic. + // Modify our viewport, this requires a lock since it affects rendering try self.io.terminal.scrollViewport(.{ .delta = delta }); diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 37535769e..4c01f1b00 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -80,6 +80,7 @@ modes: packed struct { deccolm: bool = false, // 3, deccolm_supported: bool = false, // 40 + mouse_alternate_scroll: bool = true, // 1007 mouse_event: MouseEvents = .none, mouse_format: MouseFormat = .x10, diff --git a/src/terminal/ansi.zig b/src/terminal/ansi.zig index 7effc3440..228895972 100644 --- a/src/terminal/ansi.zig +++ b/src/terminal/ansi.zig @@ -101,6 +101,10 @@ pub const Mode = enum(u16) { /// Report mouse position in the SGR format. mouse_format_sgr = 1006, + /// Report mouse scroll events as cursor up/down keys. Any other mouse + /// mode overrides this. + mouse_alternate_scroll = 1007, + /// Report mouse position in the urxvt format. mouse_format_urxvt = 1015, diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 642dbef0a..5bd72007e 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -951,6 +951,8 @@ const StreamHandler = struct { .mouse_format_urxvt => self.terminal.modes.mouse_format = if (enabled) .urxvt else .x10, .mouse_format_sgr_pixels => self.terminal.modes.mouse_format = if (enabled) .sgr_pixels else .x10, + .mouse_alternate_scroll => self.terminal.modes.mouse_alternate_scroll = enabled, + else => if (enabled) log.warn("unimplemented mode: {}", .{mode}), } } From f02dc2f32f5da57dafa65dc61c620d8351f5a734 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 17 Mar 2023 19:23:38 -0700 Subject: [PATCH 2/2] handle horizontal scrolling for mouse reports --- src/Surface.zig | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index 8dbff7361..26903eeed 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -1074,14 +1074,18 @@ pub fn scrollCallback(self: *Surface, xoff: f64, yoff: f64) !void { } else |_| {} } - //log.info("SCROLL: {} {}", .{ xoff, yoff }); - _ = xoff; + // log.info("SCROLL: {} {}", .{ xoff, yoff }); // Positive is up - const sign: isize = if (yoff > 0) -1 else 1; - const delta_unsigned = @max(@divFloor(self.grid_size.rows, 15), 1); - const delta: isize = sign * delta_unsigned; - log.info("scroll: delta={}", .{delta}); + const y_sign: isize = if (yoff > 0) -1 else 1; + const y_delta_unsigned: usize = if (yoff == 0) 0 else @max(@divFloor(self.grid_size.rows, 15), 1); + const y_delta: isize = y_sign * @intCast(isize, y_delta_unsigned); + + // Positive is right + const x_sign: isize = if (xoff < 0) -1 else 1; + const x_delta_unsigned: usize = if (xoff == 0) 0 else 1; + const x_delta: isize = x_sign * @intCast(isize, x_delta_unsigned); + log.info("scroll: delta_y={} delta_x={}", .{ y_delta, x_delta }); { self.renderer_state.mutex.lock(); @@ -1095,11 +1099,22 @@ pub fn scrollCallback(self: *Surface, xoff: f64, yoff: f64) !void { self.io.terminal.modes.mouse_event == .none and self.io.terminal.modes.mouse_alternate_scroll) { - const up_down_seq = if (delta < 0) "\x1bOA" else "\x1bOB"; - for (0..delta_unsigned) |_| { - _ = self.io_thread.mailbox.push(.{ - .write_stable = up_down_seq, - }, .{ .forever = {} }); + if (y_delta_unsigned > 0) { + const seq = if (y_delta < 0) "\x1bOA" else "\x1bOB"; + for (0..y_delta_unsigned) |_| { + _ = self.io_thread.mailbox.push(.{ + .write_stable = seq, + }, .{ .forever = {} }); + } + } + + if (x_delta_unsigned > 0) { + const seq = if (x_delta < 0) "\x1bOC" else "\x1bOD"; + for (0..x_delta_unsigned) |_| { + _ = self.io_thread.mailbox.push(.{ + .write_stable = seq, + }, .{ .forever = {} }); + } } // After sending all our messages we have to notify our IO thread @@ -1112,7 +1127,7 @@ pub fn scrollCallback(self: *Surface, xoff: f64, yoff: f64) !void { // the normal logic. // Modify our viewport, this requires a lock since it affects rendering - try self.io.terminal.scrollViewport(.{ .delta = delta }); + try self.io.terminal.scrollViewport(.{ .delta = y_delta }); // If we're scrolling up or down, then send a mouse event. This requires // a lock since we read terminal state. @@ -1120,6 +1135,10 @@ pub fn scrollCallback(self: *Surface, xoff: f64, yoff: f64) !void { const pos = try self.rt_surface.getCursorPos(); try self.mouseReport(if (yoff < 0) .five else .four, .press, self.mouse.mods, pos); } + if (xoff != 0) { + const pos = try self.rt_surface.getCursorPos(); + try self.mouseReport(if (xoff > 0) .six else .seven, .press, self.mouse.mods, pos); + } } try self.queueRender();