diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-04-04 19:22:04 +0300 |
|---|---|---|
| committer | Can Celik <ogulcancelik@gmail.com> | 2026-04-05 01:23:06 +0300 |
| commit | 41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch) | |
| tree | 3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/terminal/kitty/key.zig | |
| parent | 55c138263e190a1e79d4b997bfb9397306c0ec1f (diff) | |
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/terminal/kitty/key.zig')
| -rw-r--r-- | vendor/libghostty-vt/src/terminal/kitty/key.zig | 169 |
1 files changed, 169 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/terminal/kitty/key.zig b/vendor/libghostty-vt/src/terminal/kitty/key.zig new file mode 100644 index 00000000..8594c4c3 --- /dev/null +++ b/vendor/libghostty-vt/src/terminal/kitty/key.zig @@ -0,0 +1,169 @@ +//! Kitty keyboard protocol support. + +const std = @import("std"); + +/// Stack for the key flags. This implements the push/pop behavior +/// of the CSI > u and CSI < u sequences. We implement the stack as +/// fixed size to avoid heap allocation. +pub const FlagStack = struct { + const len = 8; + + flags: [len]Flags = @splat(.disabled), + idx: u3 = 0, + + /// Return the current stack value + pub fn current(self: FlagStack) Flags { + return self.flags[self.idx]; + } + + /// Perform the "set" operation as described in the spec for + /// the CSI = u sequence. + pub fn set( + self: *FlagStack, + mode: SetMode, + v: Flags, + ) void { + switch (mode) { + .set => self.flags[self.idx] = v, + .@"or" => self.flags[self.idx] = @bitCast( + self.flags[self.idx].int() | v.int(), + ), + .not => self.flags[self.idx] = @bitCast( + self.flags[self.idx].int() & ~v.int(), + ), + } + } + + /// Push a new set of flags onto the stack. If the stack is full + /// then the oldest entry is evicted. + pub fn push(self: *FlagStack, flags: Flags) void { + // Overflow and wrap around if we're full, which evicts + // the oldest entry. + self.idx +%= 1; + self.flags[self.idx] = flags; + } + + /// Pop `n` entries from the stack. This will just wrap around + /// if `n` is greater than the amount in the stack. + pub fn pop(self: *FlagStack, n: usize) void { + // If n is more than our length then we just reset the stack. + // This also avoids a DoS vector where a malicious client + // could send a huge number of pop commands to waste cpu. + if (n >= self.flags.len) { + self.idx = 0; + self.flags = @splat(.disabled); + return; + } + + for (0..n) |_| { + self.flags[self.idx] = .disabled; + self.idx -%= 1; + } + } + + // Make sure we the overflow works as expected + test { + const testing = std.testing; + var stack: FlagStack = .{}; + stack.idx = stack.flags.len - 1; + stack.idx +%= 1; + try testing.expect(stack.idx == 0); + + stack.idx = 0; + stack.idx -%= 1; + try testing.expect(stack.idx == stack.flags.len - 1); + } +}; + +/// The possible flags for the Kitty keyboard protocol. +pub const Flags = packed struct(u5) { + disambiguate: bool = false, + report_events: bool = false, + report_alternates: bool = false, + report_all: bool = false, + report_associated: bool = false, + + /// Kitty keyboard protocol disabled (all flags off). + pub const disabled: Flags = .{ + .disambiguate = false, + .report_events = false, + .report_alternates = false, + .report_all = false, + .report_associated = false, + }; + + /// Sets all modes on. + pub const @"true": Flags = .{ + .disambiguate = true, + .report_events = true, + .report_alternates = true, + .report_all = true, + .report_associated = true, + }; + + pub fn int(self: Flags) u5 { + return @bitCast(self); + } + + // Its easy to get packed struct ordering wrong so this test checks. + test { + const testing = std.testing; + + try testing.expectEqual( + @as(u5, 0b1), + (Flags{ .disambiguate = true }).int(), + ); + try testing.expectEqual( + @as(u5, 0b10), + (Flags{ .report_events = true }).int(), + ); + } +}; + +/// The possible modes for setting the key flags. +pub const SetMode = enum { set, @"or", not }; + +test "FlagStack: push pop" { + const testing = std.testing; + var stack: FlagStack = .{}; + stack.push(.{ .disambiguate = true }); + try testing.expectEqual( + Flags{ .disambiguate = true }, + stack.current(), + ); + + stack.pop(1); + try testing.expectEqual(Flags{}, stack.current()); +} + +test "FlagStack: pop big number" { + const testing = std.testing; + var stack: FlagStack = .{}; + stack.pop(100); + try testing.expectEqual(Flags{}, stack.current()); +} + +test "FlagStack: set" { + const testing = std.testing; + var stack: FlagStack = .{}; + stack.set(.set, .{ .disambiguate = true }); + try testing.expectEqual( + Flags{ .disambiguate = true }, + stack.current(), + ); + + stack.set(.@"or", .{ .report_events = true }); + try testing.expectEqual( + Flags{ + .disambiguate = true, + .report_events = true, + }, + stack.current(), + ); + + stack.set(.not, .{ .report_events = true }); + try testing.expectEqual( + Flags{ .disambiguate = true }, + stack.current(), + ); +} |
