diff --git a/src/Loop.zig b/src/Loop.zig index 8d0fd10..0892a15 100644 --- a/src/Loop.zig +++ b/src/Loop.zig @@ -362,6 +362,10 @@ pub fn handleEventGeneric(self: anytype, vx: *Vaxis, cache: *GraphemeCache, Even log.info("color_scheme_updates capability detected", .{}); vx.caps.color_scheme_updates = true; }, + .cap_multi_cursor => { + log.info("multi cursor capability detected", .{}); + vx.caps.multi_cursor = true; + }, .cap_da1 => { std.Thread.Futex.wake(&vx.query_futex, 10); vx.queries_done.store(true, .unordered); diff --git a/src/Parser.zig b/src/Parser.zig index b97538d..37ac890 100644 --- a/src/Parser.zig +++ b/src/Parser.zig @@ -646,6 +646,17 @@ inline fn parseCsi(input: []const u8, text_buf: []u8) Result { else => return null_event, } }, + 'q' => { + // kitty multi cursor cap (CSI > 1;2;3;29;30;40;100;101 TRAILER) (TRAILER is " q") + const second_final = sequence[sequence.len - 2]; + if (second_final != ' ') return null_event; + // check for any digits. we're not too picky about checking the supported cursor types here + for (sequence[0 .. sequence.len - 2]) |c| switch (c) { + '0'...'9' => return .{ .event = .cap_multi_cursor, .n = sequence.len }, + else => continue, + }; + return null_event; + }, else => return null_event, } } @@ -1077,6 +1088,32 @@ test "parse: multiple codepoint grapheme with more after" { try testing.expectEqual(expected_key.codepoint, actual.codepoint); } +test "parse(csi): kitty multi cursor" { + var buf: [1]u8 = undefined; + { + const input = "\x1b[>1;2;3;29;30;40;100;101 q"; + const result = parseCsi(input, &buf); + const expected: Result = .{ + .event = .cap_multi_cursor, + .n = input.len, + }; + + try testing.expectEqual(expected.n, result.n); + try testing.expectEqual(expected.event, result.event); + } + { + const input = "\x1b[> q"; + const result = parseCsi(input, &buf); + const expected: Result = .{ + .event = null, + .n = input.len, + }; + + try testing.expectEqual(expected.n, result.n); + try testing.expectEqual(expected.event, result.event); + } +} + test "parse(csi): decrpm" { var buf: [1]u8 = undefined; { diff --git a/src/Vaxis.zig b/src/Vaxis.zig index 4dd6864..61161b0 100644 --- a/src/Vaxis.zig +++ b/src/Vaxis.zig @@ -38,6 +38,7 @@ pub const Capabilities = struct { color_scheme_updates: bool = false, explicit_width: bool = false, scaled_text: bool = false, + multi_cursor: bool = false, }; pub const Options = struct { @@ -300,6 +301,7 @@ pub fn queryTerminalSend(vx: *Vaxis, tty: *IoWriter) !void { // why we see a Shift modifier ctlseqs.home ++ ctlseqs.scaled_text_query ++ + ctlseqs.multi_cursor_query ++ ctlseqs.cursor_position_request ++ ctlseqs.xtversion ++ ctlseqs.csi_u_query ++ diff --git a/src/ctlseqs.zig b/src/ctlseqs.zig index e673605..e84c38a 100644 --- a/src/ctlseqs.zig +++ b/src/ctlseqs.zig @@ -14,6 +14,7 @@ pub const sixel_geometry_query = "\x1b[?2;1;0S"; pub const cursor_position_request = "\x1b[6n"; pub const explicit_width_query = "\x1b]66;w=1; \x1b\\"; pub const scaled_text_query = "\x1b]66;s=2; \x1b\\"; +pub const multi_cursor_query = "\x1b[> q"; // mouse. We try for button motion and any motion. terminals will enable the // last one we tried (any motion). This was added because zellij doesn't diff --git a/src/event.zig b/src/event.zig index 8f1cf15..5239de4 100644 --- a/src/event.zig +++ b/src/event.zig @@ -26,4 +26,5 @@ pub const Event = union(enum) { cap_unicode, cap_da1, cap_color_scheme_updates, + cap_multi_cursor, };