aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/terminal/kitty/key.zig
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-04-04 19:22:04 +0300
committerCan Celik <ogulcancelik@gmail.com>2026-04-05 01:23:06 +0300
commit41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch)
tree3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/terminal/kitty/key.zig
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (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.zig169
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(),
+ );
+}