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, };