From 55809160b9623de6b2de6eba1fa56cddec4427b3 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 22 May 2024 20:56:00 +0200 Subject: [PATCH] vaxis: detect pixel mouse mode and translate coordinates to cell offsets This detects support for pixel mouse mode so it can be enabled only if supported. This also translates pixel coordinates to something more compatible with plaine cell coordinates. This make it much easier to write applications that support both. --- examples/pause_tui.zig | 2 +- examples/text_input.zig | 2 +- src/Mouse.zig | 8 ++---- src/Parser.zig | 3 +++ src/Tty.zig | 7 ++++- src/Vaxis.zig | 59 ++++++++++++++++++++++++++++++++--------- src/ctlseqs.zig | 1 + src/event.zig | 1 + 8 files changed, 62 insertions(+), 21 deletions(-) diff --git a/examples/pause_tui.zig b/examples/pause_tui.zig index 61ca79c..1d29460 100644 --- a/examples/pause_tui.zig +++ b/examples/pause_tui.zig @@ -58,7 +58,7 @@ pub fn main() !void { // _always_ be called, but is left to the application to decide when try vx.queryTerminal(); - try vx.setMouseMode(.cells); + try vx.setMouseMode(true); // The main event loop. Vaxis provides a thread safe, blocking, buffered // queue which can serve as the primary event queue for an application diff --git a/examples/text_input.zig b/examples/text_input.zig index 36fcdef..f4a756d 100644 --- a/examples/text_input.zig +++ b/examples/text_input.zig @@ -60,7 +60,7 @@ pub fn main() !void { // _always_ be called, but is left to the application to decide when try vx.queryTerminal(); - try vx.setMouseMode(.pixels); + try vx.setMouseMode(true); // The main event loop. Vaxis provides a thread safe, blocking, buffered // queue which can serve as the primary event queue for an application diff --git a/src/Mouse.zig b/src/Mouse.zig index 192637f..91e4925 100644 --- a/src/Mouse.zig +++ b/src/Mouse.zig @@ -1,12 +1,6 @@ /// A mouse event pub const Mouse = @This(); -pub const ReportingMode = enum { - off, - cells, - pixels, -}; - pub const Shape = enum { default, text, @@ -47,6 +41,8 @@ pub const Type = enum { col: usize, row: usize, +xoffset: usize = 0, +yoffset: usize = 0, button: Button, mods: Modifiers, type: Type, diff --git a/src/Parser.zig b/src/Parser.zig index 885d6bd..5dac9e5 100644 --- a/src/Parser.zig +++ b/src/Parser.zig @@ -404,6 +404,9 @@ pub fn parse(self: *Parser, input: []const u8, paste_allocator: ?std.mem.Allocat // 3: permanently set // 4: permanently reset switch (seq.params[0]) { + 1016 => { + return .{ .event = .cap_sgr_pixels, .n = i + 1 }; + }, 2027 => { switch (seq.params[1]) { 0, 4 => return .{ .event = null, .n = i + 1 }, diff --git a/src/Tty.zig b/src/Tty.zig index b4375cb..48ff67b 100644 --- a/src/Tty.zig +++ b/src/Tty.zig @@ -31,6 +31,7 @@ state: struct { kitty_keyboard: bool = false, bracketed_paste: bool = false, mouse: bool = false, + pixel_mouse: bool = false, cursor: struct { row: usize = 0, col: usize = 0, @@ -179,7 +180,7 @@ pub fn run( }, .mouse => |mouse| { if (@hasField(Event, "mouse")) { - loop.postEvent(.{ .mouse = mouse }); + loop.postEvent(.{ .mouse = loop.vaxis.translateMouse(mouse) }); } }, .focus_in => { @@ -229,6 +230,10 @@ pub fn run( loop.vaxis.caps.unicode = .unicode; loop.vaxis.screen.width_method = .unicode; }, + .cap_sgr_pixels => { + log.info("pixel mouse capability detected", .{}); + loop.vaxis.caps.sgr_pixels = true; + }, .cap_da1 => { std.Thread.Futex.wake(&loop.vaxis.query_futex, 10); }, diff --git a/src/Vaxis.zig b/src/Vaxis.zig index e30b231..7d36e29 100644 --- a/src/Vaxis.zig +++ b/src/Vaxis.zig @@ -31,6 +31,7 @@ pub const Capabilities = struct { kitty_graphics: bool = false, rgb: bool = false, unicode: gwidth.Method = .wcwidth, + sgr_pixels: bool = false, }; pub const Options = struct { @@ -199,6 +200,7 @@ pub fn queryTerminalSend(self: *Vaxis) !void { // doesn't hurt to blindly use them // _ = try tty.write(ctlseqs.decrqm_focus); // _ = try tty.write(ctlseqs.decrqm_sync); + _ = try tty.write(ctlseqs.decrqm_sgr_pixels); _ = try tty.write(ctlseqs.decrqm_unicode); _ = try tty.write(ctlseqs.decrqm_color_theme); // TODO: XTVERSION has a DCS response. uncomment when we can parse @@ -703,25 +705,58 @@ pub fn setMouseShape(self: *Vaxis, shape: Shape) void { } /// Change the mouse reporting mode -pub fn setMouseMode(self: *Vaxis, mode: Mouse.ReportingMode) !void { +pub fn setMouseMode(self: *Vaxis, enable: bool) !void { if (self.tty) |*tty| { - switch (mode) { - .off => { - _ = try tty.write(ctlseqs.mouse_reset); - }, - .cells => { - tty.state.mouse = true; - _ = try tty.write(ctlseqs.mouse_set); - }, - .pixels => { - tty.state.mouse = true; + if (enable) { + tty.state.mouse = true; + if (self.caps.sgr_pixels) { + log.debug("enabling mouse mode: pixel coordinates", .{}); + tty.state.pixel_mouse = true; _ = try tty.write(ctlseqs.mouse_set_pixels); - }, + } else { + log.debug("enabling mouse mode: cell coordinates", .{}); + _ = try tty.write(ctlseqs.mouse_set); + } + } else { + _ = try tty.write(ctlseqs.mouse_reset); } try tty.flush(); } } +/// Translate pixel mouse coordinates to cell + offset +pub fn translateMouse(self: Vaxis, mouse: Mouse) Mouse { + var result = mouse; + const tty = self.tty orelse return result; + if (tty.state.pixel_mouse) { + std.debug.assert(mouse.xoffset == 0); + std.debug.assert(mouse.yoffset == 0); + const xpos = mouse.col; + const ypos = mouse.row; + const xextra = self.screen.width_pix % self.screen.width; + const yextra = self.screen.height_pix % self.screen.height; + const xcell = (self.screen.width_pix - xextra) / self.screen.width; + const ycell = (self.screen.height_pix - yextra) / self.screen.height; + result.col = xpos / xcell; + result.row = ypos / ycell; + result.xoffset = xpos % xcell; + result.yoffset = ypos % ycell; + log.debug("translateMouse x/ypos:{d}/{d} cell:{d}/{d} xtra:{d}/{d} col/rol:{d}/{d} x/y:{d}/{d}", .{ + xpos, ypos, + xcell, ycell, + xextra, yextra, + result.col, result.row, + result.xoffset, result.yoffset, + }); + } else { + log.debug("translateMouse col/rol:{d}/{d} x/y:{d}/{d}", .{ + result.col, result.row, + result.xoffset, result.yoffset, + }); + } + return result; +} + pub fn loadImage( self: *Vaxis, alloc: std.mem.Allocator, diff --git a/src/ctlseqs.zig b/src/ctlseqs.zig index 0754e35..1fb9622 100644 --- a/src/ctlseqs.zig +++ b/src/ctlseqs.zig @@ -4,6 +4,7 @@ pub const tertiary_device_attrs = "\x1b[=c"; pub const device_status_report = "\x1b[5n"; pub const xtversion = "\x1b[>0q"; pub const decrqm_focus = "\x1b[?1004$p"; +pub const decrqm_sgr_pixels = "\x1b[?1016$p"; pub const decrqm_sync = "\x1b[?2026$p"; pub const decrqm_unicode = "\x1b[?2027$p"; pub const decrqm_color_theme = "\x1b[?2031$p"; diff --git a/src/event.zig b/src/event.zig index b02e55e..ba5cd29 100644 --- a/src/event.zig +++ b/src/event.zig @@ -16,6 +16,7 @@ pub const Event = union(enum) { cap_kitty_keyboard, cap_kitty_graphics, cap_rgb, + cap_sgr_pixels, cap_unicode, cap_da1, };