From f9274bdafc7f13e902b8742ec010614c1a8779da Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 26 Aug 2022 10:46:22 -0700 Subject: [PATCH 01/13] define mouse events, all TODO --- src/terminal/Terminal.zig | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 976d0c070..5376a9123 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -68,14 +68,40 @@ modes: packed struct { deccolm: u1 = 0, // 3, deccolm_supported: u1 = 0, // 40 + mouse_event: MouseEvents = .none, + mouse_format: MouseFormat = .x10, + test { // We have this here so that we explicitly fail when we change the // size of modes. The size of modes is NOT particularly important, // we just want to be mentally aware when it happens. - try std.testing.expectEqual(1, @sizeOf(Self)); + try std.testing.expectEqual(2, @sizeOf(Self)); } } = .{}, +/// The event types that can be reported for mouse-related activities. +/// These are all mutually exclusive (hence in a single enum). +pub const MouseEvents = enum(u3) { + none = 0, + + // TODO: + x10 = 1, // 9 + normal = 2, // 1000 + button = 3, // 1002 + any = 4, // 1003 +}; + +/// The format of mouse events when enabled. +/// These are all mutually exclusive (hence in a single enum). +pub const MouseFormat = enum(u3) { + // TODO: + x10 = 0, + utf8 = 1, // 1005 + sgr = 2, // 1006 + urxvt = 3, // 1015 + sgr_pixels = 4, // 1016 +}; + /// Scrolling region is the area of the screen designated where scrolling /// occurs. Wen scrolling the screen, only this viewport is scrolled. const ScrollingRegion = struct { From 43b7727cf84a4a1d20f0526ac699a702c99e542f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 26 Aug 2022 11:27:44 -0700 Subject: [PATCH 02/13] change u1 in mode to bool --- src/Window.zig | 14 +++++++------- src/terminal/Terminal.zig | 28 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Window.zig b/src/Window.zig index b884a1756..f5b09013f 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -1069,7 +1069,7 @@ fn renderTimerCallback(t: *libuv.Timer) void { win.grid.background = bg; win.grid.foreground = fg; } - if (win.terminal.modes.reverse_colors == 1) { + if (win.terminal.modes.reverse_colors) { win.grid.background = fg; win.grid.foreground = bg; } @@ -1080,7 +1080,7 @@ fn renderTimerCallback(t: *libuv.Timer) void { g: f32, b: f32, a: f32, - } = if (win.terminal.modes.reverse_colors == 1) .{ + } = if (win.terminal.modes.reverse_colors) .{ .r = @intToFloat(f32, fg.r) / 255, .g = @intToFloat(f32, fg.g) / 255, .b = @intToFloat(f32, fg.b) / 255, @@ -1167,7 +1167,7 @@ pub fn setCursorCol(self: *Window, col: u16) !void { } pub fn setCursorRow(self: *Window, row: u16) !void { - if (self.terminal.modes.origin == 1) { + if (self.terminal.modes.origin) { // TODO log.err("setCursorRow: implement origin mode", .{}); unreachable; @@ -1234,19 +1234,19 @@ pub fn setTopAndBottomMargin(self: *Window, top: u16, bot: u16) !void { pub fn setMode(self: *Window, mode: terminal.Mode, enabled: bool) !void { switch (mode) { .reverse_colors => { - self.terminal.modes.reverse_colors = @boolToInt(enabled); + self.terminal.modes.reverse_colors = enabled; // Schedule a render since we changed colors try self.render_timer.schedule(); }, .origin => { - self.terminal.modes.origin = @boolToInt(enabled); + self.terminal.modes.origin = enabled; self.terminal.setCursorPos(1, 1); }, .autowrap => { - self.terminal.modes.autowrap = @boolToInt(enabled); + self.terminal.modes.autowrap = enabled; }, .cursor_visible => { @@ -1323,7 +1323,7 @@ pub fn deviceStatusReport( const pos: struct { x: usize, y: usize, - } = if (self.terminal.modes.origin == 1) .{ + } = if (self.terminal.modes.origin) .{ // TODO: what do we do if cursor is outside scrolling region? .x = self.terminal.screen.cursor.x, .y = self.terminal.screen.cursor.y -| self.terminal.scrolling_region.top, diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 5376a9123..6dd77b585 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -61,12 +61,12 @@ scrolling_region: ScrollingRegion, modes: packed struct { const Self = @This(); - reverse_colors: u1 = 0, // 5, - origin: u1 = 0, // 6 - autowrap: u1 = 1, // 7 + reverse_colors: bool = false, // 5, + origin: bool = false, // 6 + autowrap: bool = true, // 7 - deccolm: u1 = 0, // 3, - deccolm_supported: u1 = 0, // 40 + deccolm: bool = false, // 3, + deccolm_supported: bool = false, // 40 mouse_event: MouseEvents = .none, mouse_format: MouseFormat = .x10, @@ -223,10 +223,10 @@ pub fn deccolm(self: *Terminal, alloc: Allocator, mode: DeccolmMode) !void { // bit. If the mode "?40" is set, then "?3" (DECCOLM) is supported. This // doesn't exactly match VT100 semantics but modern terminals no longer // blindly accept mode 3 since its so weird in modern practice. - if (self.modes.deccolm_supported == 0) return; + if (!self.modes.deccolm_supported) return; // Enable it - self.modes.deccolm = @enumToInt(mode); + self.modes.deccolm = mode == .@"132_cols"; // Resize -- we can set cols to 0 because deccolm will force it try self.resize(alloc, 0, self.rows); @@ -243,7 +243,7 @@ pub fn setDeccolmSupported(self: *Terminal, v: bool) void { const tracy = trace(@src()); defer tracy.end(); - self.modes.deccolm_supported = @boolToInt(v); + self.modes.deccolm_supported = v; } /// Resize the underlying terminal. @@ -254,8 +254,8 @@ pub fn resize(self: *Terminal, alloc: Allocator, cols_req: usize, rows: usize) ! // If we have deccolm supported then we are fixed at either 80 or 132 // columns depending on if mode 3 is set or not. // TODO: test - const cols: usize = if (self.modes.deccolm_supported == 1) - @as(usize, if (self.modes.deccolm == 1) 132 else 80) + const cols: usize = if (self.modes.deccolm_supported) + @as(usize, if (self.modes.deccolm) 132 else 80) else cols_req; @@ -396,7 +396,7 @@ pub fn print(self: *Terminal, c: u21) !void { if (width == 0) return; // If we're soft-wrapping, then handle that first. - if (self.screen.cursor.pending_wrap and self.modes.autowrap == 1) + if (self.screen.cursor.pending_wrap and self.modes.autowrap) _ = self.printWrap(); switch (width) { @@ -624,7 +624,7 @@ pub fn setCursorPos(self: *Terminal, row_req: usize, col_req: usize) void { y_offset: usize = 0, x_max: usize, y_max: usize, - } = if (self.modes.origin == 1) .{ + } = if (self.modes.origin) .{ .x_offset = 0, // TODO: left/right margins .y_offset = self.scrolling_region.top, .x_max = self.cols, // TODO: left/right margins @@ -656,7 +656,7 @@ pub fn setCursorColAbsolute(self: *Terminal, col_req: usize) void { // TODO: test - assert(self.modes.origin == 0); // TODO + assert(!self.modes.origin); // TODO if (self.status_display != .main) return; // TODO @@ -1351,7 +1351,7 @@ test "Terminal: setCursorPosition" { try testing.expect(!t.screen.cursor.pending_wrap); // Origin mode - t.modes.origin = 1; + t.modes.origin = true; // No change without a scroll region t.setCursorPos(81, 81); From 93f45af41b74be6130c3442910f641c5f7838a9d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 26 Aug 2022 12:04:54 -0700 Subject: [PATCH 03/13] X10 click-only mouse reporting --- src/Window.zig | 70 +++++++++++++++++++++++++++++++++++++++++-- src/terminal/ansi.zig | 3 ++ 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/Window.zig b/src/Window.zig index f5b09013f..7c118815a 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -769,6 +769,57 @@ fn scrollCallback(window: glfw.Window, xoff: f64, yoff: f64) void { win.render_timer.schedule() catch unreachable; } +fn mouseReport( + self: *Window, + button: glfw.MouseButton, + action: glfw.Action, + mods: glfw.Mods, + unscaled_pos: glfw.Window.CursorPos, +) !void { + // TODO: posToViewport currently clamps to the window boundary, + // do we want to not report mouse events at all outside the window? + + assert(self.terminal.modes.mouse_event != .none); + + _ = mods; + + // Depending on the event, we may do nothing at all. + switch (self.terminal.modes.mouse_event) { + // X10 only reports clicks with mouse button 1, 2, 3. We verify + // the button later. + .x10 => if (action != .press) return, + else => {}, + } + + switch (self.terminal.modes.mouse_format) { + .x10 => { + const button_code: u8 = switch (button) { + .left => 0, + .right => 1, + .middle => 2, + else => return, // unsupported with X10 + }; + + // This format reports X/Y + const pos = self.cursorPosToPixels(unscaled_pos); + const viewport_point = self.posToViewport(pos.xpos, pos.ypos); + if (viewport_point.x > 222 or viewport_point.y > 222) { + log.info("X10 mouse format can only encode X/Y up to 223", .{}); + return; + } + + // + 1 below is because our x/y is 0-indexed and proto wants 1 + var buf = [_]u8{ '\x1b', '[', 'M', 0, 0, 0 }; + buf[3] = 32 + button_code; + buf[4] = 32 + @intCast(u8, viewport_point.x) + 1; + buf[5] = 32 + @intCast(u8, viewport_point.y) + 1; + try self.queueWrite(&buf); + }, + + else => @panic("TODO"), + } +} + fn mouseButtonCallback( window: glfw.Window, button: glfw.MouseButton, @@ -780,10 +831,24 @@ fn mouseButtonCallback( const tracy = trace(@src()); defer tracy.end(); + const win = window.getUserPointer(Window) orelse return; + + // Report mouse events if enabled + if (win.terminal.modes.mouse_event != .none) { + const pos = window.getCursorPos() catch |err| { + log.err("error reading cursor position: {}", .{err}); + return; + }; + + win.mouseReport(button, action, mods, pos) catch |err| { + log.err("error reporting mouse event: {}", .{err}); + return; + }; + } + if (button == .left) { switch (action) { .press => { - const win = window.getUserPointer(Window) orelse return; const pos = win.cursorPosToPixels(window.getCursorPos() catch |err| { log.err("error reading cursor position: {}", .{err}); return; @@ -810,7 +875,6 @@ fn mouseButtonCallback( }, .release => { - const win = window.getUserPointer(Window) orelse return; win.mouse.click_state = .none; log.debug("click end", .{}); }, @@ -1284,6 +1348,8 @@ pub fn setMode(self: *Window, mode: terminal.Mode, enabled: bool) !void { if (enabled) .@"132_cols" else .@"80_cols", ), + .mouse_event_x10 => self.terminal.modes.mouse_event = .x10, + else => if (enabled) log.warn("unimplemented mode: {}", .{mode}), } } diff --git a/src/terminal/ansi.zig b/src/terminal/ansi.zig index 817f3b02e..18aac7d7f 100644 --- a/src/terminal/ansi.zig +++ b/src/terminal/ansi.zig @@ -58,6 +58,9 @@ pub const Mode = enum(u16) { /// Enable or disable automatic line wrapping. autowrap = 7, + /// Click-only (press) mouse reporting. + mouse_event_x10 = 9, + /// Set whether the cursor is visible or not. cursor_visible = 25, From 3096b32f13593db3eff2c29fd8ee638e0a0e3f7b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 26 Aug 2022 12:15:13 -0700 Subject: [PATCH 04/13] mouse normal events in x10 format --- src/Window.zig | 32 ++++++++++++++++++++++++++------ src/terminal/ansi.zig | 3 +++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/Window.zig b/src/Window.zig index 7c118815a..863c0630c 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -787,17 +787,36 @@ fn mouseReport( switch (self.terminal.modes.mouse_event) { // X10 only reports clicks with mouse button 1, 2, 3. We verify // the button later. - .x10 => if (action != .press) return, + .x10 => if (action != .press or + !(button == .left or + button == .right or + button == .middle)) return, + + // Everything + .normal => {}, else => {}, } switch (self.terminal.modes.mouse_format) { .x10 => { - const button_code: u8 = switch (button) { - .left => 0, - .right => 1, - .middle => 2, - else => return, // unsupported with X10 + const button_code: u8 = code: { + var acc: u8 = if (action == .press) switch (button) { + .left => 0, + .right => 1, + .middle => 2, + .four => 64, + .five => 65, + else => return, // unsupported + } else 3; // release is always 3 + + // Normal mode adds in modifiers + if (self.terminal.modes.mouse_event == .normal) { + if (mods.shift) acc += 4; + if (mods.super) acc += 8; + if (mods.control) acc += 16; + } + + break :code acc; }; // This format reports X/Y @@ -1349,6 +1368,7 @@ pub fn setMode(self: *Window, mode: terminal.Mode, enabled: bool) !void { ), .mouse_event_x10 => self.terminal.modes.mouse_event = .x10, + .mouse_event_normal => self.terminal.modes.mouse_event = .normal, else => if (enabled) log.warn("unimplemented mode: {}", .{mode}), } diff --git a/src/terminal/ansi.zig b/src/terminal/ansi.zig index 18aac7d7f..5a4fe8ca8 100644 --- a/src/terminal/ansi.zig +++ b/src/terminal/ansi.zig @@ -69,6 +69,9 @@ pub const Mode = enum(u16) { /// mode ?3 is set or unset. enable_mode_3 = 40, + /// "Normal" mouse events: click/release, scroll + mouse_event_normal = 1000, + /// Alternate screen mode with save cursor and clear on enter. alt_screen_save_cursor_clear_enter = 1049, From a4bab6592dcd6e416beca30e36780f0fad30f743 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 26 Aug 2022 12:24:24 -0700 Subject: [PATCH 05/13] normal events can now track scroll --- src/Window.zig | 32 ++++++++++++++++++++++++++------ src/terminal/Terminal.zig | 7 ++++--- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/Window.zig b/src/Window.zig index 863c0630c..e992e5e0f 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -754,6 +754,27 @@ fn scrollCallback(window: glfw.Window, xoff: f64, yoff: f64) void { const win = window.getUserPointer(Window) orelse return; + // If we're scrolling up or down, then send a mouse event + if (yoff != 0) { + const pos = window.getCursorPos() catch |err| { + log.err("error reading cursor position: {}", .{err}); + return; + }; + + // NOTE: a limitation of glfw (perhaps) is that we can't detect + // scroll buttons (mouse four/five) WITH modifier state. So we always + // report this as a "press" followed immediately by a release with no + // modifiers. + win.mouseReport(if (yoff < 0) .four else .five, .press, .{}, pos) catch |err| { + log.err("error reporting mouse event: {}", .{err}); + return; + }; + win.mouseReport(if (yoff < 0) .four else .five, .release, .{}, pos) catch |err| { + log.err("error reporting mouse event: {}", .{err}); + return; + }; + } + //log.info("SCROLL: {} {}", .{ xoff, yoff }); _ = xoff; @@ -779,12 +800,10 @@ fn mouseReport( // TODO: posToViewport currently clamps to the window boundary, // do we want to not report mouse events at all outside the window? - assert(self.terminal.modes.mouse_event != .none); - - _ = mods; - // Depending on the event, we may do nothing at all. switch (self.terminal.modes.mouse_event) { + .none => return, + // X10 only reports clicks with mouse button 1, 2, 3. We verify // the button later. .x10 => if (action != .press or @@ -794,20 +813,21 @@ fn mouseReport( // Everything .normal => {}, + else => {}, } switch (self.terminal.modes.mouse_format) { .x10 => { const button_code: u8 = code: { - var acc: u8 = if (action == .press) switch (button) { + var acc: u8 = if (action == .press) @as(u8, switch (button) { .left => 0, .right => 1, .middle => 2, .four => 64, .five => 65, else => return, // unsupported - } else 3; // release is always 3 + }) else 3; // release is always 3 // Normal mode adds in modifiers if (self.terminal.modes.mouse_event == .normal) { diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 6dd77b585..e9d77c05a 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -83,10 +83,10 @@ modes: packed struct { /// These are all mutually exclusive (hence in a single enum). pub const MouseEvents = enum(u3) { none = 0, - - // TODO: x10 = 1, // 9 normal = 2, // 1000 + + // TODO: button = 3, // 1002 any = 4, // 1003 }; @@ -94,8 +94,9 @@ pub const MouseEvents = enum(u3) { /// The format of mouse events when enabled. /// These are all mutually exclusive (hence in a single enum). pub const MouseFormat = enum(u3) { - // TODO: x10 = 0, + + // TODO: utf8 = 1, // 1005 sgr = 2, // 1006 urxvt = 3, // 1015 From 9aa5378ffac37a1a8c0b5f2abc9dd48d58b52d9f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 26 Aug 2022 13:55:24 -0700 Subject: [PATCH 06/13] Track/cache button state and mods state --- src/Window.zig | 144 ++++++++++++++++++++------------------ src/input.zig | 1 + src/input/mouse.zig | 38 ++++++++++ src/terminal/Terminal.zig | 2 +- src/terminal/ansi.zig | 4 ++ 5 files changed, 121 insertions(+), 68 deletions(-) create mode 100644 src/input/mouse.zig diff --git a/src/Window.zig b/src/Window.zig index e992e5e0f..83d0a9d2a 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -137,20 +137,26 @@ const Cursor = struct { /// Mouse state for the window. const Mouse = struct { - /// The current state of mouse click. - click_state: ClickState = .none, + /// The last tracked mouse button state by button. + click_state: [input.MouseButton.max]input.MouseButtonState = .{.release} ** input.MouseButton.max, - /// The point at which the mouse click happened. This is in screen + /// The last mods state when the last mouse button (whatever it was) was + /// pressed or release. + mods: input.Mods = .{}, + + /// The point at which the left mouse click happened. This is in screen /// coordinates so that scrolling preserves the location. - click_point: terminal.point.ScreenPoint = .{}, + left_click_point: terminal.point.ScreenPoint = .{}, - /// The starting xpos/ypos of the click. This is only useful initially. - /// As soon as scrolling occurs, these are no longer accurate to calculate - /// the screen point. - click_xpos: f64 = 0, - click_ypos: f64 = 0, + /// The starting xpos/ypos of the left click. Note that if scrolling occurs, + /// these will point to different "cells", but the xpos/ypos will stay + /// stable during scrolling relative to the window. + left_click_xpos: f64 = 0, + left_click_ypos: f64 = 0, - const ClickState = enum { none, left }; + // /// The last + // event_cx: usize = 0, + // event_cy: usize = 0, }; /// Create a new window. This allocates and returns a pointer because we @@ -761,15 +767,7 @@ fn scrollCallback(window: glfw.Window, xoff: f64, yoff: f64) void { return; }; - // NOTE: a limitation of glfw (perhaps) is that we can't detect - // scroll buttons (mouse four/five) WITH modifier state. So we always - // report this as a "press" followed immediately by a release with no - // modifiers. - win.mouseReport(if (yoff < 0) .four else .five, .press, .{}, pos) catch |err| { - log.err("error reporting mouse event: {}", .{err}); - return; - }; - win.mouseReport(if (yoff < 0) .four else .five, .release, .{}, pos) catch |err| { + win.mouseReport(if (yoff < 0) .four else .five, .press, win.mouse.mods, pos) catch |err| { log.err("error reporting mouse event: {}", .{err}); return; }; @@ -792,9 +790,9 @@ fn scrollCallback(window: glfw.Window, xoff: f64, yoff: f64) void { fn mouseReport( self: *Window, - button: glfw.MouseButton, - action: glfw.Action, - mods: glfw.Mods, + button: input.MouseButton, + action: input.MouseButtonState, + mods: input.Mods, unscaled_pos: glfw.Window.CursorPos, ) !void { // TODO: posToViewport currently clamps to the window boundary, @@ -833,7 +831,7 @@ fn mouseReport( if (self.terminal.modes.mouse_event == .normal) { if (mods.shift) acc += 4; if (mods.super) acc += 8; - if (mods.control) acc += 16; + if (mods.ctrl) acc += 16; } break :code acc; @@ -861,8 +859,8 @@ fn mouseReport( fn mouseButtonCallback( window: glfw.Window, - button: glfw.MouseButton, - action: glfw.Action, + glfw_button: glfw.MouseButton, + glfw_action: glfw.Action, mods: glfw.Mods, ) void { _ = mods; @@ -872,6 +870,27 @@ fn mouseButtonCallback( const win = window.getUserPointer(Window) orelse return; + // Convert glfw button to input button + const button: input.MouseButton = switch (glfw_button) { + .left => .left, + .right => .right, + .middle => .middle, + .four => .four, + .five => .five, + .six => .six, + .seven => .seven, + .eight => .eight, + }; + const action: input.MouseButtonState = switch (glfw_action) { + .press => .press, + .release => .release, + else => unreachable, + }; + + // Always record our latest mouse state + win.mouse.click_state[@enumToInt(button)] = action; + win.mouse.mods = @bitCast(input.Mods, mods); + // Report mouse events if enabled if (win.terminal.modes.mouse_event != .none) { const pos = window.getCursorPos() catch |err| { @@ -879,46 +898,36 @@ fn mouseButtonCallback( return; }; - win.mouseReport(button, action, mods, pos) catch |err| { + win.mouseReport( + button, + action, + win.mouse.mods, + pos, + ) catch |err| { log.err("error reporting mouse event: {}", .{err}); return; }; } - if (button == .left) { - switch (action) { - .press => { - const pos = win.cursorPosToPixels(window.getCursorPos() catch |err| { - log.err("error reading cursor position: {}", .{err}); - return; - }); + // For left button clicks we always record some information for + // selection/highlighting purposes. + if (button == .left and action == .press) { + const pos = win.cursorPosToPixels(window.getCursorPos() catch |err| { + log.err("error reading cursor position: {}", .{err}); + return; + }); - // Store it - const point = win.posToViewport(pos.xpos, pos.ypos); - win.mouse.click_state = .left; - win.mouse.click_point = point.toScreen(&win.terminal.screen); - win.mouse.click_xpos = pos.xpos; - win.mouse.click_ypos = pos.ypos; - log.debug("click start state={} viewport={} screen={}", .{ - win.mouse.click_state, - point, - win.mouse.click_point, - }); + // Store it + const point = win.posToViewport(pos.xpos, pos.ypos); + win.mouse.left_click_point = point.toScreen(&win.terminal.screen); + win.mouse.left_click_xpos = pos.xpos; + win.mouse.left_click_ypos = pos.ypos; - // Selection is always cleared - if (win.terminal.selection != null) { - win.terminal.selection = null; - win.render_timer.schedule() catch |err| - log.err("error scheduling render in mouseButtinCallback err={}", .{err}); - } - }, - - .release => { - win.mouse.click_state = .none; - log.debug("click end", .{}); - }, - - .repeat => {}, + // Selection is always cleared + if (win.terminal.selection != null) { + win.terminal.selection = null; + win.render_timer.schedule() catch |err| + log.err("error scheduling render in mouseButtinCallback err={}", .{err}); } } } @@ -934,7 +943,7 @@ fn cursorPosCallback( const win = window.getUserPointer(Window) orelse return; // If the cursor isn't clicked currently, it doesn't matter - if (win.mouse.click_state != .left) return; + if (win.mouse.click_state[@enumToInt(input.MouseButton.left)] != .press) return; // All roads lead to requiring a re-render at this pont. win.render_timer.schedule() catch |err| @@ -983,13 +992,13 @@ fn cursorPosCallback( const cell_xboundary = win.grid.cell_size.width * 0.6; // first xpos of the clicked cell - const cell_xstart = @intToFloat(f32, win.mouse.click_point.x) * win.grid.cell_size.width; - const cell_start_xpos = win.mouse.click_xpos - cell_xstart; + const cell_xstart = @intToFloat(f32, win.mouse.left_click_point.x) * win.grid.cell_size.width; + const cell_start_xpos = win.mouse.left_click_xpos - cell_xstart; // If this is the same cell, then we only start the selection if weve // moved past the boundary point the opposite direction from where we // started. - if (std.meta.eql(screen_point, win.mouse.click_point)) { + if (std.meta.eql(screen_point, win.mouse.left_click_point)) { const cell_xpos = xpos - cell_xstart; const selected: bool = if (cell_start_xpos < cell_xboundary) cell_xpos >= cell_xboundary @@ -1011,9 +1020,9 @@ fn cursorPosCallback( // the starting cell if we started after the boundary, else // we start selection of the prior cell. // - Inverse logic for a point after the start. - const click_point = win.mouse.click_point; + const click_point = win.mouse.left_click_point; const start: terminal.point.ScreenPoint = if (screen_point.before(click_point)) start: { - if (win.mouse.click_xpos > cell_xboundary) { + if (win.mouse.left_click_xpos > cell_xboundary) { break :start click_point; } else { break :start if (click_point.x > 0) terminal.point.ScreenPoint{ @@ -1025,7 +1034,7 @@ fn cursorPosCallback( }; } } else start: { - if (win.mouse.click_xpos < cell_xboundary) { + if (win.mouse.left_click_xpos < cell_xboundary) { break :start click_point; } else { break :start if (click_point.x < win.terminal.screen.cols - 1) terminal.point.ScreenPoint{ @@ -1387,8 +1396,9 @@ pub fn setMode(self: *Window, mode: terminal.Mode, enabled: bool) !void { if (enabled) .@"132_cols" else .@"80_cols", ), - .mouse_event_x10 => self.terminal.modes.mouse_event = .x10, - .mouse_event_normal => self.terminal.modes.mouse_event = .normal, + .mouse_event_x10 => self.terminal.modes.mouse_event = if (enabled) .x10 else .none, + .mouse_event_normal => self.terminal.modes.mouse_event = if (enabled) .normal else .none, + .mouse_event_button => self.terminal.modes.mouse_event = if (enabled) .button else .none, else => if (enabled) log.warn("unimplemented mode: {}", .{mode}), } diff --git a/src/input.zig b/src/input.zig index 14efd40cc..da121890f 100644 --- a/src/input.zig +++ b/src/input.zig @@ -1,5 +1,6 @@ const std = @import("std"); +pub usingnamespace @import("input/mouse.zig"); pub usingnamespace @import("input/key.zig"); pub const Binding = @import("input/Binding.zig"); diff --git a/src/input/mouse.zig b/src/input/mouse.zig new file mode 100644 index 000000000..113521813 --- /dev/null +++ b/src/input/mouse.zig @@ -0,0 +1,38 @@ +/// The state of a mouse button. +pub const MouseButtonState = enum(u1) { + release = 0, + press = 1, +}; + +/// Possible mouse buttons. We only track up to 11 because thats the maximum +/// button input that terminal mouse tracking handles without becoming +/// ambiguous. +/// +/// Its a bit silly to name numbers like this but given its a restricted +/// set, it feels better than passing around raw numeric literals. +pub const MouseButton = enum(u4) { + const Self = @This(); + + /// The maximum value in this enum. This can be used to create a densely + /// packed array, for example. + pub const max = max: { + var cur = 0; + for (@typeInfo(Self).Enum.fields) |field| { + if (field.value > cur) cur = field.value; + } + + break :max cur; + }; + + left = 1, + right = 2, + middle = 3, + four = 4, + five = 5, + six = 6, + seven = 7, + eight = 8, + nine = 9, + ten = 10, + eleven = 11, +}; diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index e9d77c05a..75f739866 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -85,9 +85,9 @@ pub const MouseEvents = enum(u3) { none = 0, x10 = 1, // 9 normal = 2, // 1000 + button = 3, // 1002 // TODO: - button = 3, // 1002 any = 4, // 1003 }; diff --git a/src/terminal/ansi.zig b/src/terminal/ansi.zig index 5a4fe8ca8..cfb56a91d 100644 --- a/src/terminal/ansi.zig +++ b/src/terminal/ansi.zig @@ -72,6 +72,10 @@ pub const Mode = enum(u16) { /// "Normal" mouse events: click/release, scroll mouse_event_normal = 1000, + /// Same as normal mode but also send events for mouse motion + /// while the button is pressed when the cell in the grid changes. + mouse_event_button = 1002, + /// Alternate screen mode with save cursor and clear on enter. alt_screen_save_cursor_clear_enter = 1049, From bd5dd695385516af0849e57b3e0102970b124c9f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 26 Aug 2022 14:17:42 -0700 Subject: [PATCH 07/13] normal event (motion) mouse tracking --- src/Window.zig | 103 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 75 insertions(+), 28 deletions(-) diff --git a/src/Window.zig b/src/Window.zig index 83d0a9d2a..13d8c8aea 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -154,9 +154,8 @@ const Mouse = struct { left_click_xpos: f64 = 0, left_click_ypos: f64 = 0, - // /// The last - // event_cx: usize = 0, - // event_cy: usize = 0, + /// The last x/y sent for mouse reports. + event_point: terminal.point.Viewport = .{}, }; /// Create a new window. This allocates and returns a pointer because we @@ -788,10 +787,13 @@ fn scrollCallback(window: glfw.Window, xoff: f64, yoff: f64) void { win.render_timer.schedule() catch unreachable; } +/// The type of action to report for a mouse event. +const MouseReportAction = enum { press, release, motion }; + fn mouseReport( self: *Window, button: input.MouseButton, - action: input.MouseButtonState, + action: MouseReportAction, mods: input.Mods, unscaled_pos: glfw.Window.CursorPos, ) !void { @@ -809,34 +811,17 @@ fn mouseReport( button == .right or button == .middle)) return, - // Everything - .normal => {}, + // Doesn't report motion + .normal => if (action == .motion) return, - else => {}, + // Everything + .button => {}, + + else => unreachable, } switch (self.terminal.modes.mouse_format) { .x10 => { - const button_code: u8 = code: { - var acc: u8 = if (action == .press) @as(u8, switch (button) { - .left => 0, - .right => 1, - .middle => 2, - .four => 64, - .five => 65, - else => return, // unsupported - }) else 3; // release is always 3 - - // Normal mode adds in modifiers - if (self.terminal.modes.mouse_event == .normal) { - if (mods.shift) acc += 4; - if (mods.super) acc += 8; - if (mods.ctrl) acc += 16; - } - - break :code acc; - }; - // This format reports X/Y const pos = self.cursorPosToPixels(unscaled_pos); const viewport_point = self.posToViewport(pos.xpos, pos.ypos); @@ -845,6 +830,38 @@ fn mouseReport( return; } + // For button events, we only report if we moved cells + if (self.terminal.modes.mouse_event == .button) { + if (self.mouse.event_point.x == viewport_point.x and + self.mouse.event_point.y == viewport_point.y) return; + + // Record our new point + self.mouse.event_point = viewport_point; + } + + const button_code: u8 = code: { + var acc: u8 = if (action == .release) 3 else @as(u8, switch (button) { + .left => 0, + .right => 1, + .middle => 2, + .four => 64, + .five => 65, + else => return, // unsupported + }); + + // X10 doesn't have modifiers + if (self.terminal.modes.mouse_event != .x10) { + if (mods.shift) acc += 4; + if (mods.super) acc += 8; + if (mods.ctrl) acc += 16; + } + + // Motion adds another bit + if (action == .motion) acc += 32; + + break :code acc; + }; + // + 1 below is because our x/y is 0-indexed and proto wants 1 var buf = [_]u8{ '\x1b', '[', 'M', 0, 0, 0 }; buf[3] = 32 + button_code; @@ -898,9 +915,14 @@ fn mouseButtonCallback( return; }; + const report_action: MouseReportAction = switch (action) { + .press => .press, + .release => .release, + }; + win.mouseReport( button, - action, + report_action, win.mouse.mods, pos, ) catch |err| { @@ -942,6 +964,31 @@ fn cursorPosCallback( const win = window.getUserPointer(Window) orelse return; + // Do a mouse report + if (win.terminal.modes.mouse_event == .button) { + // We use the first mouse button we find pressed in order to report + // since the spec (afaict) does not say... + const button_: ?input.MouseButton = button: for (win.mouse.click_state) |state, i| { + if (state == .press) + break :button @intToEnum(input.MouseButton, i); + } else null; + + // A button must be pressed. + if (button_) |button| { + win.mouseReport(button, .motion, win.mouse.mods, .{ + .xpos = unscaled_xpos, + .ypos = unscaled_ypos, + }) catch |err| { + log.err("error reporting mouse event: {}", .{err}); + return; + }; + } + + // If we're doing mouse motion tracking, we do not support text + // selection. + return; + } + // If the cursor isn't clicked currently, it doesn't matter if (win.mouse.click_state[@enumToInt(input.MouseButton.left)] != .press) return; From ab305add6c44fb99137ce0ed4da958784185e0bd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 26 Aug 2022 14:26:42 -0700 Subject: [PATCH 08/13] any event mouse tracking --- src/Window.zig | 63 ++++++++++++++++++++++----------------- src/terminal/Terminal.zig | 2 -- src/terminal/ansi.zig | 4 +++ 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/src/Window.zig b/src/Window.zig index 13d8c8aea..ce1c99b30 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -792,7 +792,7 @@ const MouseReportAction = enum { press, release, motion }; fn mouseReport( self: *Window, - button: input.MouseButton, + button: ?input.MouseButton, action: MouseReportAction, mods: input.Mods, unscaled_pos: glfw.Window.CursorPos, @@ -807,17 +807,19 @@ fn mouseReport( // X10 only reports clicks with mouse button 1, 2, 3. We verify // the button later. .x10 => if (action != .press or - !(button == .left or - button == .right or - button == .middle)) return, + button == null or + !(button.? == .left or + button.? == .right or + button.? == .middle)) return, // Doesn't report motion .normal => if (action == .motion) return, - // Everything - .button => {}, + // Button must be pressed + .button => if (button == null) return, - else => unreachable, + // Everything + .any => {}, } switch (self.terminal.modes.mouse_format) { @@ -831,7 +833,9 @@ fn mouseReport( } // For button events, we only report if we moved cells - if (self.terminal.modes.mouse_event == .button) { + if (self.terminal.modes.mouse_event == .button or + self.terminal.modes.mouse_event == .any) + { if (self.mouse.event_point.x == viewport_point.x and self.mouse.event_point.y == viewport_point.y) return; @@ -840,14 +844,17 @@ fn mouseReport( } const button_code: u8 = code: { - var acc: u8 = if (action == .release) 3 else @as(u8, switch (button) { - .left => 0, - .right => 1, - .middle => 2, - .four => 64, - .five => 65, - else => return, // unsupported - }); + var acc: u8 = if (action == .release or button == null) + 3 + else + @as(u8, switch (button.?) { + .left => 0, + .right => 1, + .middle => 2, + .four => 64, + .five => 65, + else => return, // unsupported + }); // X10 doesn't have modifiers if (self.terminal.modes.mouse_event != .x10) { @@ -965,24 +972,23 @@ fn cursorPosCallback( const win = window.getUserPointer(Window) orelse return; // Do a mouse report - if (win.terminal.modes.mouse_event == .button) { + if (win.terminal.modes.mouse_event == .button or + win.terminal.modes.mouse_event == .any) + { // We use the first mouse button we find pressed in order to report // since the spec (afaict) does not say... - const button_: ?input.MouseButton = button: for (win.mouse.click_state) |state, i| { + const button: ?input.MouseButton = button: for (win.mouse.click_state) |state, i| { if (state == .press) break :button @intToEnum(input.MouseButton, i); } else null; - // A button must be pressed. - if (button_) |button| { - win.mouseReport(button, .motion, win.mouse.mods, .{ - .xpos = unscaled_xpos, - .ypos = unscaled_ypos, - }) catch |err| { - log.err("error reporting mouse event: {}", .{err}); - return; - }; - } + win.mouseReport(button, .motion, win.mouse.mods, .{ + .xpos = unscaled_xpos, + .ypos = unscaled_ypos, + }) catch |err| { + log.err("error reporting mouse event: {}", .{err}); + return; + }; // If we're doing mouse motion tracking, we do not support text // selection. @@ -1446,6 +1452,7 @@ pub fn setMode(self: *Window, mode: terminal.Mode, enabled: bool) !void { .mouse_event_x10 => self.terminal.modes.mouse_event = if (enabled) .x10 else .none, .mouse_event_normal => self.terminal.modes.mouse_event = if (enabled) .normal else .none, .mouse_event_button => self.terminal.modes.mouse_event = if (enabled) .button else .none, + .mouse_event_any => self.terminal.modes.mouse_event = if (enabled) .any else .none, else => if (enabled) log.warn("unimplemented mode: {}", .{mode}), } diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 75f739866..b06f1bf9c 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -86,8 +86,6 @@ pub const MouseEvents = enum(u3) { x10 = 1, // 9 normal = 2, // 1000 button = 3, // 1002 - - // TODO: any = 4, // 1003 }; diff --git a/src/terminal/ansi.zig b/src/terminal/ansi.zig index cfb56a91d..9cc46c679 100644 --- a/src/terminal/ansi.zig +++ b/src/terminal/ansi.zig @@ -76,6 +76,10 @@ pub const Mode = enum(u16) { /// while the button is pressed when the cell in the grid changes. mouse_event_button = 1002, + /// Same as button mode but doesn't require a button to be pressed + /// to track mouse movement. + mouse_event_any = 1003, + /// Alternate screen mode with save cursor and clear on enter. alt_screen_save_cursor_clear_enter = 1049, From b46fe522d582f3575c71bd6c75d64523da46ba48 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 26 Aug 2022 14:42:20 -0700 Subject: [PATCH 09/13] UTF-8 mouse reporting --- src/Window.zig | 102 +++++++++++++++++++++++--------------- src/terminal/Terminal.zig | 2 +- src/terminal/ansi.zig | 3 ++ 3 files changed, 66 insertions(+), 41 deletions(-) diff --git a/src/Window.zig b/src/Window.zig index ce1c99b30..9a7e3a4f7 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -822,53 +822,55 @@ fn mouseReport( .any => {}, } + // This format reports X/Y + const pos = self.cursorPosToPixels(unscaled_pos); + const viewport_point = self.posToViewport(pos.xpos, pos.ypos); + + // For button events, we only report if we moved cells + if (self.terminal.modes.mouse_event == .button or + self.terminal.modes.mouse_event == .any) + { + if (self.mouse.event_point.x == viewport_point.x and + self.mouse.event_point.y == viewport_point.y) return; + + // Record our new point + self.mouse.event_point = viewport_point; + } + + // Get the code we'll actually write + const button_code: u8 = code: { + var acc: u8 = if (action == .release or button == null) + 3 + else + @as(u8, switch (button.?) { + .left => 0, + .right => 1, + .middle => 2, + .four => 64, + .five => 65, + else => return, // unsupported + }); + + // X10 doesn't have modifiers + if (self.terminal.modes.mouse_event != .x10) { + if (mods.shift) acc += 4; + if (mods.super) acc += 8; + if (mods.ctrl) acc += 16; + } + + // Motion adds another bit + if (action == .motion) acc += 32; + + break :code acc; + }; + switch (self.terminal.modes.mouse_format) { .x10 => { - // This format reports X/Y - const pos = self.cursorPosToPixels(unscaled_pos); - const viewport_point = self.posToViewport(pos.xpos, pos.ypos); if (viewport_point.x > 222 or viewport_point.y > 222) { log.info("X10 mouse format can only encode X/Y up to 223", .{}); return; } - // For button events, we only report if we moved cells - if (self.terminal.modes.mouse_event == .button or - self.terminal.modes.mouse_event == .any) - { - if (self.mouse.event_point.x == viewport_point.x and - self.mouse.event_point.y == viewport_point.y) return; - - // Record our new point - self.mouse.event_point = viewport_point; - } - - const button_code: u8 = code: { - var acc: u8 = if (action == .release or button == null) - 3 - else - @as(u8, switch (button.?) { - .left => 0, - .right => 1, - .middle => 2, - .four => 64, - .five => 65, - else => return, // unsupported - }); - - // X10 doesn't have modifiers - if (self.terminal.modes.mouse_event != .x10) { - if (mods.shift) acc += 4; - if (mods.super) acc += 8; - if (mods.ctrl) acc += 16; - } - - // Motion adds another bit - if (action == .motion) acc += 32; - - break :code acc; - }; - // + 1 below is because our x/y is 0-indexed and proto wants 1 var buf = [_]u8{ '\x1b', '[', 'M', 0, 0, 0 }; buf[3] = 32 + button_code; @@ -877,6 +879,24 @@ fn mouseReport( try self.queueWrite(&buf); }, + .utf8 => { + // Maximum of 12 because at most we have 2 fully UTF-8 encoded chars + var buf: [12]u8 = undefined; + buf[0] = '\x1b'; + buf[1] = '['; + buf[2] = 'M'; + + // The button code will always fit in a single u8 + buf[3] = 32 + button_code; + + // UTF-8 encode the x/y + var i: usize = 4; + i += try std.unicode.utf8Encode(@intCast(u21, 32 + viewport_point.x + 1), buf[i..]); + i += try std.unicode.utf8Encode(@intCast(u21, 32 + viewport_point.y + 1), buf[i..]); + + try self.queueWrite(buf[0..i]); + }, + else => @panic("TODO"), } } @@ -1454,6 +1474,8 @@ pub fn setMode(self: *Window, mode: terminal.Mode, enabled: bool) !void { .mouse_event_button => self.terminal.modes.mouse_event = if (enabled) .button else .none, .mouse_event_any => self.terminal.modes.mouse_event = if (enabled) .any else .none, + .mouse_format_utf8 => self.terminal.modes.mouse_format = if (enabled) .utf8 else .x10, + else => if (enabled) log.warn("unimplemented mode: {}", .{mode}), } } diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index b06f1bf9c..093033b2c 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -93,9 +93,9 @@ pub const MouseEvents = enum(u3) { /// These are all mutually exclusive (hence in a single enum). pub const MouseFormat = enum(u3) { x10 = 0, + utf8 = 1, // 1005 // TODO: - utf8 = 1, // 1005 sgr = 2, // 1006 urxvt = 3, // 1015 sgr_pixels = 4, // 1016 diff --git a/src/terminal/ansi.zig b/src/terminal/ansi.zig index 9cc46c679..025de8ecc 100644 --- a/src/terminal/ansi.zig +++ b/src/terminal/ansi.zig @@ -80,6 +80,9 @@ pub const Mode = enum(u16) { /// to track mouse movement. mouse_event_any = 1003, + /// Report mouse position in the utf8 format to support larger screens. + mouse_format_utf8 = 1005, + /// Alternate screen mode with save cursor and clear on enter. alt_screen_save_cursor_clear_enter = 1049, From f551c0ef66355fb4d6f909bd9c198dc93c40f9be Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 26 Aug 2022 14:55:18 -0700 Subject: [PATCH 10/13] SGR reporting --- src/Window.zig | 34 +++++++++++++++++++++++++++++----- src/terminal/Terminal.zig | 2 +- src/terminal/ansi.zig | 3 +++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/Window.zig b/src/Window.zig index 9a7e3a4f7..11e8bb715 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -839,17 +839,26 @@ fn mouseReport( // Get the code we'll actually write const button_code: u8 = code: { - var acc: u8 = if (action == .release or button == null) - 3 - else - @as(u8, switch (button.?) { + var acc: u8 = 0; + + // Determine our initial button value + if (button == null) { + // Null button means motion without a button pressed + acc = 3; + } else if (action == .release and self.terminal.modes.mouse_format != .sgr) { + // Release is 3. It is NOT 3 in SGR mode because SGR can tell + // the application what button was released. + acc = 3; + } else { + acc = switch (button.?) { .left => 0, .right => 1, .middle => 2, .four => 64, .five => 65, else => return, // unsupported - }); + }; + } // X10 doesn't have modifiers if (self.terminal.modes.mouse_event != .x10) { @@ -897,6 +906,20 @@ fn mouseReport( try self.queueWrite(buf[0..i]); }, + .sgr => { + // Response always is at least 4 chars, so this leaves the + // remainder for numbers which are very large... + var buf: [32]u8 = undefined; + const resp = try std.fmt.bufPrint(&buf, "\x1B[<{d};{d};{d}{c}", .{ + button_code, + viewport_point.x + 1, + viewport_point.y + 1, + @as(u8, if (action == .release) 'm' else 'M'), + }); + + try self.queueWrite(resp); + }, + else => @panic("TODO"), } } @@ -1475,6 +1498,7 @@ pub fn setMode(self: *Window, mode: terminal.Mode, enabled: bool) !void { .mouse_event_any => self.terminal.modes.mouse_event = if (enabled) .any else .none, .mouse_format_utf8 => self.terminal.modes.mouse_format = if (enabled) .utf8 else .x10, + .mouse_format_sgr => self.terminal.modes.mouse_format = if (enabled) .sgr else .x10, else => if (enabled) log.warn("unimplemented mode: {}", .{mode}), } diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 093033b2c..63f692dc5 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -94,9 +94,9 @@ pub const MouseEvents = enum(u3) { pub const MouseFormat = enum(u3) { x10 = 0, utf8 = 1, // 1005 + sgr = 2, // 1006 // TODO: - sgr = 2, // 1006 urxvt = 3, // 1015 sgr_pixels = 4, // 1016 }; diff --git a/src/terminal/ansi.zig b/src/terminal/ansi.zig index 025de8ecc..cd1c58385 100644 --- a/src/terminal/ansi.zig +++ b/src/terminal/ansi.zig @@ -83,6 +83,9 @@ pub const Mode = enum(u16) { /// Report mouse position in the utf8 format to support larger screens. mouse_format_utf8 = 1005, + /// Report mouse position in the SGR format. + mouse_format_sgr = 1006, + /// Alternate screen mode with save cursor and clear on enter. alt_screen_save_cursor_clear_enter = 1049, From 1039ad76bf5bb6e03feabe3e27412f69a8b47e3e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 26 Aug 2022 14:57:35 -0700 Subject: [PATCH 11/13] urxvt reporting format --- src/Window.zig | 14 ++++++++++++++ src/terminal/Terminal.zig | 2 +- src/terminal/ansi.zig | 3 +++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/Window.zig b/src/Window.zig index 11e8bb715..6a7c0f042 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -920,6 +920,19 @@ fn mouseReport( try self.queueWrite(resp); }, + .urxvt => { + // Response always is at least 4 chars, so this leaves the + // remainder for numbers which are very large... + var buf: [32]u8 = undefined; + const resp = try std.fmt.bufPrint(&buf, "\x1B[{d};{d};{d}M", .{ + 32 + button_code, + viewport_point.x + 1, + viewport_point.y + 1, + }); + + try self.queueWrite(resp); + }, + else => @panic("TODO"), } } @@ -1499,6 +1512,7 @@ pub fn setMode(self: *Window, mode: terminal.Mode, enabled: bool) !void { .mouse_format_utf8 => self.terminal.modes.mouse_format = if (enabled) .utf8 else .x10, .mouse_format_sgr => self.terminal.modes.mouse_format = if (enabled) .sgr else .x10, + .mouse_format_urxvt => self.terminal.modes.mouse_format = if (enabled) .urxvt else .x10, else => if (enabled) log.warn("unimplemented mode: {}", .{mode}), } diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 63f692dc5..c17172683 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -95,9 +95,9 @@ pub const MouseFormat = enum(u3) { x10 = 0, utf8 = 1, // 1005 sgr = 2, // 1006 + urxvt = 3, // 1015 // TODO: - urxvt = 3, // 1015 sgr_pixels = 4, // 1016 }; diff --git a/src/terminal/ansi.zig b/src/terminal/ansi.zig index cd1c58385..31bbcd8cd 100644 --- a/src/terminal/ansi.zig +++ b/src/terminal/ansi.zig @@ -86,6 +86,9 @@ pub const Mode = enum(u16) { /// Report mouse position in the SGR format. mouse_format_sgr = 1006, + /// Report mouse position in the urxvt format. + mouse_format_urxvt = 1015, + /// Alternate screen mode with save cursor and clear on enter. alt_screen_save_cursor_clear_enter = 1049, From fe6ba027091a7247a0ad21240560d5d100f00f31 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 26 Aug 2022 14:59:44 -0700 Subject: [PATCH 12/13] sgr pixels mouse report format --- src/Window.zig | 15 ++++++++++++++- src/terminal/Terminal.zig | 2 -- src/terminal/ansi.zig | 3 +++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Window.zig b/src/Window.zig index 6a7c0f042..46e043027 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -933,7 +933,19 @@ fn mouseReport( try self.queueWrite(resp); }, - else => @panic("TODO"), + .sgr_pixels => { + // Response always is at least 4 chars, so this leaves the + // remainder for numbers which are very large... + var buf: [32]u8 = undefined; + const resp = try std.fmt.bufPrint(&buf, "\x1B[<{d};{d};{d}{c}", .{ + button_code, + pos.xpos, + pos.ypos, + @as(u8, if (action == .release) 'm' else 'M'), + }); + + try self.queueWrite(resp); + }, } } @@ -1513,6 +1525,7 @@ pub fn setMode(self: *Window, mode: terminal.Mode, enabled: bool) !void { .mouse_format_utf8 => self.terminal.modes.mouse_format = if (enabled) .utf8 else .x10, .mouse_format_sgr => self.terminal.modes.mouse_format = if (enabled) .sgr else .x10, .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, else => if (enabled) log.warn("unimplemented mode: {}", .{mode}), } diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index c17172683..0433ed553 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -96,8 +96,6 @@ pub const MouseFormat = enum(u3) { utf8 = 1, // 1005 sgr = 2, // 1006 urxvt = 3, // 1015 - - // TODO: sgr_pixels = 4, // 1016 }; diff --git a/src/terminal/ansi.zig b/src/terminal/ansi.zig index 31bbcd8cd..bb2a962e8 100644 --- a/src/terminal/ansi.zig +++ b/src/terminal/ansi.zig @@ -89,6 +89,9 @@ pub const Mode = enum(u16) { /// Report mouse position in the urxvt format. mouse_format_urxvt = 1015, + /// Report mouse position in the SGR format as pixels, instead of cells. + mouse_format_sgr_pixels = 1016, + /// Alternate screen mode with save cursor and clear on enter. alt_screen_save_cursor_clear_enter = 1049, From ff460887b51776b10cce1f82258c20f10f9795f5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 26 Aug 2022 15:07:22 -0700 Subject: [PATCH 13/13] fix miscompilation around sgr mode --- src/Window.zig | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Window.zig b/src/Window.zig index 46e043027..e7a06619c 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -907,6 +907,9 @@ fn mouseReport( }, .sgr => { + // Final character to send in the CSI + const final: u8 = if (action == .release) 'm' else 'M'; + // Response always is at least 4 chars, so this leaves the // remainder for numbers which are very large... var buf: [32]u8 = undefined; @@ -914,7 +917,7 @@ fn mouseReport( button_code, viewport_point.x + 1, viewport_point.y + 1, - @as(u8, if (action == .release) 'm' else 'M'), + final, }); try self.queueWrite(resp); @@ -934,6 +937,9 @@ fn mouseReport( }, .sgr_pixels => { + // Final character to send in the CSI + const final: u8 = if (action == .release) 'm' else 'M'; + // Response always is at least 4 chars, so this leaves the // remainder for numbers which are very large... var buf: [32]u8 = undefined; @@ -941,7 +947,7 @@ fn mouseReport( button_code, pos.xpos, pos.ypos, - @as(u8, if (action == .release) 'm' else 'M'), + final, }); try self.queueWrite(resp); @@ -1040,9 +1046,7 @@ fn cursorPosCallback( const win = window.getUserPointer(Window) orelse return; // Do a mouse report - if (win.terminal.modes.mouse_event == .button or - win.terminal.modes.mouse_event == .any) - { + if (win.terminal.modes.mouse_event != .none) { // We use the first mouse button we find pressed in order to report // since the spec (afaict) does not say... const button: ?input.MouseButton = button: for (win.mouse.click_state) |state, i| {