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.
This commit is contained in:
CJ van den Berg 2024-05-22 20:56:00 +02:00 committed by Tim Culverhouse
parent a75cce6e78
commit 55809160b9
8 changed files with 62 additions and 21 deletions

View file

@ -58,7 +58,7 @@ pub fn main() !void {
// _always_ be called, but is left to the application to decide when // _always_ be called, but is left to the application to decide when
try vx.queryTerminal(); try vx.queryTerminal();
try vx.setMouseMode(.cells); try vx.setMouseMode(true);
// The main event loop. Vaxis provides a thread safe, blocking, buffered // The main event loop. Vaxis provides a thread safe, blocking, buffered
// queue which can serve as the primary event queue for an application // queue which can serve as the primary event queue for an application

View file

@ -60,7 +60,7 @@ pub fn main() !void {
// _always_ be called, but is left to the application to decide when // _always_ be called, but is left to the application to decide when
try vx.queryTerminal(); try vx.queryTerminal();
try vx.setMouseMode(.pixels); try vx.setMouseMode(true);
// The main event loop. Vaxis provides a thread safe, blocking, buffered // The main event loop. Vaxis provides a thread safe, blocking, buffered
// queue which can serve as the primary event queue for an application // queue which can serve as the primary event queue for an application

View file

@ -1,12 +1,6 @@
/// A mouse event /// A mouse event
pub const Mouse = @This(); pub const Mouse = @This();
pub const ReportingMode = enum {
off,
cells,
pixels,
};
pub const Shape = enum { pub const Shape = enum {
default, default,
text, text,
@ -47,6 +41,8 @@ pub const Type = enum {
col: usize, col: usize,
row: usize, row: usize,
xoffset: usize = 0,
yoffset: usize = 0,
button: Button, button: Button,
mods: Modifiers, mods: Modifiers,
type: Type, type: Type,

View file

@ -404,6 +404,9 @@ pub fn parse(self: *Parser, input: []const u8, paste_allocator: ?std.mem.Allocat
// 3: permanently set // 3: permanently set
// 4: permanently reset // 4: permanently reset
switch (seq.params[0]) { switch (seq.params[0]) {
1016 => {
return .{ .event = .cap_sgr_pixels, .n = i + 1 };
},
2027 => { 2027 => {
switch (seq.params[1]) { switch (seq.params[1]) {
0, 4 => return .{ .event = null, .n = i + 1 }, 0, 4 => return .{ .event = null, .n = i + 1 },

View file

@ -31,6 +31,7 @@ state: struct {
kitty_keyboard: bool = false, kitty_keyboard: bool = false,
bracketed_paste: bool = false, bracketed_paste: bool = false,
mouse: bool = false, mouse: bool = false,
pixel_mouse: bool = false,
cursor: struct { cursor: struct {
row: usize = 0, row: usize = 0,
col: usize = 0, col: usize = 0,
@ -179,7 +180,7 @@ pub fn run(
}, },
.mouse => |mouse| { .mouse => |mouse| {
if (@hasField(Event, "mouse")) { if (@hasField(Event, "mouse")) {
loop.postEvent(.{ .mouse = mouse }); loop.postEvent(.{ .mouse = loop.vaxis.translateMouse(mouse) });
} }
}, },
.focus_in => { .focus_in => {
@ -229,6 +230,10 @@ pub fn run(
loop.vaxis.caps.unicode = .unicode; loop.vaxis.caps.unicode = .unicode;
loop.vaxis.screen.width_method = .unicode; loop.vaxis.screen.width_method = .unicode;
}, },
.cap_sgr_pixels => {
log.info("pixel mouse capability detected", .{});
loop.vaxis.caps.sgr_pixels = true;
},
.cap_da1 => { .cap_da1 => {
std.Thread.Futex.wake(&loop.vaxis.query_futex, 10); std.Thread.Futex.wake(&loop.vaxis.query_futex, 10);
}, },

View file

@ -31,6 +31,7 @@ pub const Capabilities = struct {
kitty_graphics: bool = false, kitty_graphics: bool = false,
rgb: bool = false, rgb: bool = false,
unicode: gwidth.Method = .wcwidth, unicode: gwidth.Method = .wcwidth,
sgr_pixels: bool = false,
}; };
pub const Options = struct { pub const Options = struct {
@ -199,6 +200,7 @@ pub fn queryTerminalSend(self: *Vaxis) !void {
// doesn't hurt to blindly use them // doesn't hurt to blindly use them
// _ = try tty.write(ctlseqs.decrqm_focus); // _ = try tty.write(ctlseqs.decrqm_focus);
// _ = try tty.write(ctlseqs.decrqm_sync); // _ = try tty.write(ctlseqs.decrqm_sync);
_ = try tty.write(ctlseqs.decrqm_sgr_pixels);
_ = try tty.write(ctlseqs.decrqm_unicode); _ = try tty.write(ctlseqs.decrqm_unicode);
_ = try tty.write(ctlseqs.decrqm_color_theme); _ = try tty.write(ctlseqs.decrqm_color_theme);
// TODO: XTVERSION has a DCS response. uncomment when we can parse // 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 /// 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| { if (self.tty) |*tty| {
switch (mode) { if (enable) {
.off => {
_ = try tty.write(ctlseqs.mouse_reset);
},
.cells => {
tty.state.mouse = true;
_ = try tty.write(ctlseqs.mouse_set);
},
.pixels => {
tty.state.mouse = true; 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); _ = 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(); 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( pub fn loadImage(
self: *Vaxis, self: *Vaxis,
alloc: std.mem.Allocator, alloc: std.mem.Allocator,

View file

@ -4,6 +4,7 @@ pub const tertiary_device_attrs = "\x1b[=c";
pub const device_status_report = "\x1b[5n"; pub const device_status_report = "\x1b[5n";
pub const xtversion = "\x1b[>0q"; pub const xtversion = "\x1b[>0q";
pub const decrqm_focus = "\x1b[?1004$p"; 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_sync = "\x1b[?2026$p";
pub const decrqm_unicode = "\x1b[?2027$p"; pub const decrqm_unicode = "\x1b[?2027$p";
pub const decrqm_color_theme = "\x1b[?2031$p"; pub const decrqm_color_theme = "\x1b[?2031$p";

View file

@ -16,6 +16,7 @@ pub const Event = union(enum) {
cap_kitty_keyboard, cap_kitty_keyboard,
cap_kitty_graphics, cap_kitty_graphics,
cap_rgb, cap_rgb,
cap_sgr_pixels,
cap_unicode, cap_unicode,
cap_da1, cap_da1,
}; };